00001
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026 #include "g_local.h"
00027
00028 #define MAX_WALL_THICKNESS_FOR_SHOOTING_THROUGH 8
00029
00030 typedef enum {
00031 ML_WOUND,
00032 ML_DEATH
00033 } morale_modifiers;
00034
00041 static qboolean G_TeamPointVis (int team, const vec3_t point)
00042 {
00043 edict_t *from = NULL;
00044 vec3_t eye;
00045
00046
00047 while ((from = G_EdictsGetNextLivingActorOfTeam(from, team))) {
00048 if (G_FrustumVis(from, point)) {
00049
00050 VectorCopy(from->origin, eye);
00051 if (G_IsCrouched(from))
00052 eye[2] += EYE_CROUCH;
00053 else
00054 eye[2] += EYE_STAND;
00055
00056
00057 if (!G_TestLine(eye, point))
00058 return qtrue;
00059 }
00060 }
00061
00062
00063 return qfalse;
00064 }
00065
00075 static void G_Morale (int type, const edict_t * victim, const edict_t * attacker, int param)
00076 {
00077 edict_t *ent = NULL;
00078 int newMorale;
00079 float mod;
00080
00081 while ((ent = G_EdictsGetNextInUse(ent))) {
00082
00083 if (ent->type == ET_ACTOR && !G_IsDead(ent) && ent->team != TEAM_CIVILIAN) {
00084 switch (type) {
00085 case ML_WOUND:
00086 case ML_DEATH:
00087
00088 mod = mob_wound->value * param;
00089
00090 if (type == ML_DEATH)
00091 mod += mob_death->value;
00092
00093 if (ent == victim || (G_FrustumVis(ent, victim->origin) && G_ActorVis(ent->origin, victim, qfalse)))
00094 mod *= mof_watching->value;
00095 if (ent->team == attacker->team) {
00096
00097
00098 if (victim->team == attacker->team)
00099 mod *= mof_teamkill->value;
00100 else
00101 mod *= mof_enemy->value;
00102 }
00103
00104 if (G_IsCivilian(victim))
00105 mod *= mof_civilian->value;
00106
00107 if (victim->team == ent->team || (G_IsCivilian(victim) && ent->team != TEAM_ALIEN && sv_maxclients->integer == 1))
00108 mod *= -1;
00109
00110 mod *= mor_default->value + pow(0.5, VectorDist(ent->origin, victim->origin) / mor_distance->value)
00111 * mor_victim->value + pow(0.5, VectorDist(ent->origin, attacker->origin) / mor_distance->value)
00112 * mor_attacker->value;
00113
00114 mod *= (1 - mon_teamfactor->value)
00115 + mon_teamfactor->value * (level.num_spawned[victim->team] + 1)
00116 / (level.num_alive[victim->team] + 1);
00117
00118 if (ent == victim)
00119 mod *= mor_pain->value;
00120 break;
00121 default:
00122 gi.DPrintf("Undefined morale modifier type %i\n", type);
00123 mod = 0;
00124 }
00125
00126
00127 newMorale = ent->morale + (int) (MORALE_RANDOM(mod) + 0.9);
00128 if (newMorale > GET_MORALE(ent->chr.score.skills[ABILITY_MIND]))
00129 ent->morale = GET_MORALE(ent->chr.score.skills[ABILITY_MIND]);
00130 else if (newMorale < 0)
00131 ent->morale = 0;
00132 else
00133 ent->morale = newMorale;
00134
00135
00136 G_SendStats(ent);
00137 }
00138 }
00139 }
00140
00150 static void G_UpdateShotMock (shot_mock_t *mock, const edict_t *shooter, const edict_t *struck, int damage)
00151 {
00152 assert(struck->number != shooter->number || mock->allow_self);
00153
00154 if (damage > 0) {
00155 if (!struck->inuse || G_IsDead(struck))
00156 return;
00157 else if (!G_IsVisibleForTeam(struck, shooter->team))
00158 return;
00159 else if (G_IsCivilian(struck))
00160 mock->civilian += 1;
00161 else if (struck->team == shooter->team)
00162 mock->friendCount += 1;
00163 else if (G_IsActor(struck))
00164 mock->enemyCount += 1;
00165 else
00166 return;
00167
00168 mock->damage += damage;
00169 }
00170 }
00171
00180 static void G_UpdateCharacterBodycount (edict_t *attacker, const fireDef_t *fd, const edict_t *target)
00181 {
00182 chrScoreMission_t *scoreMission;
00183 chrScoreGlobal_t *scoreGlobal;
00184 killtypes_t type;
00185
00186 if (!attacker || !fd || !target)
00187 return;
00188
00189 scoreGlobal = &attacker->chr.score;
00190 scoreMission = attacker->chr.scoreMission;
00191
00192 if (!scoreMission)
00193 return;
00194
00195 switch (target->team) {
00196 case TEAM_ALIEN:
00197 type = KILLED_ENEMIES;
00198 assert(fd->weaponSkill >= 0);
00199 assert(fd->weaponSkill < lengthof(scoreMission->skillKills));
00200 scoreMission->skillKills[fd->weaponSkill]++;
00201 break;
00202 case TEAM_CIVILIAN:
00203 type = KILLED_CIVILIANS;
00204 break;
00205 case TEAM_PHALANX:
00206 type = KILLED_TEAM;
00207 break;
00208 default:
00209 return;
00210 }
00211 if (target->HP <= 0) {
00212 scoreMission->kills[type]++;
00213 scoreGlobal->kills[type]++;
00214 } else {
00215 scoreMission->stuns[type]++;
00216 scoreGlobal->stuns[type]++;
00217 }
00218 }
00219
00227 static void G_UpdateHitScore (edict_t * attacker, const edict_t * target, const fireDef_t * fd, const int splashDamage)
00228 {
00229 chrScoreMission_t *score;
00230 killtypes_t type;
00231
00232 if (!attacker || !target || !fd)
00233 return;
00234
00235 score = attacker->chr.scoreMission;
00236
00237 if (!score)
00238 return;
00239
00240 switch (target->team) {
00241 case TEAM_CIVILIAN:
00242 type = KILLED_CIVILIANS;
00243 break;
00244 case TEAM_ALIEN:
00245 type = KILLED_ENEMIES;
00246 break;
00247 default:
00248 return;
00249 }
00250
00251 if (!splashDamage) {
00252 if (attacker->team == target->team && !score->firedHit[KILLED_TEAM]) {
00253
00254 score->hits[fd->weaponSkill][KILLED_TEAM]++;
00255 score->firedHit[KILLED_TEAM] = qtrue;
00256 }
00257
00258 if (!score->firedHit[type]) {
00259 score->hits[fd->weaponSkill][type]++;
00260 score->firedHit[type] = qtrue;
00261 }
00262 } else {
00263 if (attacker->team == target->team) {
00264
00265 score->hitsSplashDamage[fd->weaponSkill][KILLED_TEAM] += splashDamage;
00266 if (!score->firedSplashHit[KILLED_TEAM]) {
00267 score->hitsSplash[fd->weaponSkill][KILLED_TEAM]++;
00268 score->firedSplashHit[KILLED_TEAM] = qtrue;
00269 }
00270 }
00271
00272 score->hitsSplashDamage[fd->weaponSkill][type] += splashDamage;
00273 if (!score->firedSplashHit[type]) {
00274 score->hitsSplash[fd->weaponSkill][type]++;
00275 score->firedSplashHit[type] = qtrue;
00276 }
00277 }
00278 }
00279
00291 static void G_Damage (edict_t *target, const fireDef_t *fd, int damage, edict_t *attacker, shot_mock_t *mock)
00292 {
00293 const qboolean stunEl = (fd->obj->dmgtype == gi.csi->damStunElectro);
00294 const qboolean stunGas = (fd->obj->dmgtype == gi.csi->damStunGas);
00295 const qboolean shock = (fd->obj->dmgtype == gi.csi->damShock);
00296 qboolean isRobot;
00297
00298 assert(target);
00299
00300
00301 if (G_IsBrushModel(target)) {
00302
00303 if (stunEl || stunGas || shock || mock)
00304 return;
00305
00306 if (damage >= target->HP) {
00307
00308
00309 assert(target->destroy);
00310 target->destroy(target);
00311
00312
00313 G_CheckVisTeamAll(attacker->team, qfalse, attacker);
00314
00315
00316 G_CheckVis(attacker, qtrue);
00317 } else {
00318 G_TakeDamage(target, damage);
00319 }
00320 return;
00321 }
00322
00323
00324 if (!G_IsLivingActor(target))
00325 return;
00326
00327
00328 assert(target->chr.teamDef);
00329 isRobot = CHRSH_IsTeamDefRobot(target->chr.teamDef);
00330
00331
00332 if (damage > 0) {
00333 const int nd = target->chr.teamDef->resistance[fd->dmgweight];
00334 if (CONTAINER(target, gi.csi->idArmour)) {
00335 const objDef_t *ad = CONTAINER(target, gi.csi->idArmour)->item.t;
00336 damage = max(1, damage - ad->protection[fd->dmgweight] - nd);
00337 } else {
00338 damage = max(1, damage - nd);
00339 }
00340 } else if (damage < 0) {
00341
00342 if (isRobot)
00343 return;
00344 }
00345 Com_DPrintf(DEBUG_GAME, " Total damage: %d\n", damage);
00346
00347
00348 if (sv_maxclients->integer == 1) {
00349 if (G_IsAlien(attacker) && !G_IsAlien(target))
00350 damage *= pow(1.18, difficulty->integer);
00351 else if (!G_IsAlien(attacker) && G_IsAlien(target))
00352 damage *= pow(1.18, -difficulty->integer);
00353 }
00354
00355 assert(attacker->team >= 0 && attacker->team < MAX_TEAMS);
00356 assert(target->team >= 0 && target->team < MAX_TEAMS);
00357
00358 if (g_nodamage != NULL && !g_nodamage->integer) {
00359
00360 if (mock) {
00361 G_UpdateShotMock(mock, attacker, target, damage);
00362 } else if (stunEl) {
00363 target->STUN += damage;
00364 } else if (stunGas) {
00365 if (!isRobot)
00366 target->STUN += damage;
00367 } else if (shock) {
00368
00369 if (!isRobot && target->team != attacker->team) {
00371
00372 G_RemoveReaction(target);
00373 G_ActorReserveTUs(target, 0, target->chr.reservedTus.shot, target->chr.reservedTus.crouch);
00374
00375 G_ActorSetTU(target, 0);
00376 G_SendStats(target);
00377
00378 G_SetDazed(target);
00379 G_ClientPrintf(G_PLAYER_FROM_ENT(target), PRINT_HUD, _("Soldier is dazed!\nEnemy used flashbang!\n"));
00380 return;
00381 }
00382 } else {
00383 G_TakeDamage(target, damage);
00384 if (damage < 0) {
00385
00386
00387 if (target->chr.scoreMission)
00388 target->chr.scoreMission->heal += abs(damage);
00389
00392 if (G_IsStunned(target)) {
00393
00394 target->STUN += damage;
00395 G_ActorCheckRevitalise(target);
00396 }
00397 } else {
00398
00399
00400
00401 if (!mock && damage > 0 && fd->splrad)
00402 G_UpdateHitScore(attacker, target, fd, damage);
00403 }
00404 }
00405 }
00406
00407 if (mock)
00408 return;
00409
00410
00411 if (target->HP == 0 || target->HP <= target->STUN) {
00412 G_SendStats(target);
00413
00414 G_PrintActorStats(target, attacker, fd);
00415
00416 G_ActorDieOrStun(target, attacker);
00417
00418
00419 if (mor_panic->integer)
00420 G_Morale(ML_DEATH, target, attacker, damage);
00421
00422
00423 G_UpdateCharacterBodycount(attacker, fd, target);
00424 } else {
00425 target->chr.minHP = min(target->chr.minHP, target->HP);
00426 if (damage > 0) {
00427 if (mor_panic->integer)
00428 G_Morale(ML_WOUND, target, attacker, damage);
00429 } else {
00430 if (target->HP > GET_HP(target->chr.score.skills[ABILITY_POWER]))
00431 target->HP = min(max(GET_HP(target->chr.score.skills[ABILITY_POWER]), 0), target->chr.maxHP);
00432 }
00433 G_SendStats(target);
00434 }
00435 }
00436
00446 static inline qboolean G_FireAffectedSurface (const cBspSurface_t *surface, const fireDef_t *fd)
00447 {
00448 if (!surface)
00449 return qfalse;
00450
00451 if (!(surface->surfaceFlags & SURF_BURN))
00452 return qfalse;
00453
00454 if (fd->obj->dmgtype == gi.csi->damFire || fd->obj->dmgtype == gi.csi->damBlast)
00455 return qtrue;
00456
00457 return qfalse;
00458 }
00459
00468 static void G_SplashDamage (edict_t *ent, const fireDef_t *fd, vec3_t impact, shot_mock_t *mock, const trace_t* tr)
00469 {
00470 edict_t *check = NULL;
00471 vec3_t center;
00472 float dist;
00473 int damage;
00474
00475 const qboolean shock = (fd->obj->dmgtype == gi.csi->damShock);
00476
00477 assert(fd->splrad);
00478
00479 while ((check = G_EdictsGetNextInUse(check))) {
00480
00481
00482 if (shock && !G_FrustumVis(check, impact))
00483 continue;
00484
00485 if (G_IsBrushModel(check) && G_IsBreakable(check))
00486 VectorCenterFromMinsMaxs(check->absmin, check->absmax, center);
00487 else if (G_IsLivingActor(check) || G_IsBreakable(check))
00488 VectorCopy(check->origin, center);
00489 else
00490 continue;
00491
00492
00493 dist = VectorDist(impact, center);
00494 dist = dist > UNIT_SIZE / 2 ? dist - UNIT_SIZE / 2 : 0;
00495 if (dist > fd->splrad)
00496 continue;
00497
00498 if (fd->irgoggles && G_IsActor(check)) {
00499
00500 if (G_FrustumVis(ent, check->origin)) {
00501 if (!mock) {
00502 G_AppearPerishEvent(~G_VisToPM(check->visflags), qtrue, check, ent);
00503 check->visflags |= ~check->visflags;
00504 }
00505 continue;
00506 }
00507 }
00508
00509
00510 if (G_IsLivingActor(check) && !G_ActorVis(impact, check, qfalse))
00511 continue;
00512
00513
00514 if (shock)
00515 damage = 0;
00516 else
00517 damage = fd->spldmg[0] * (1.0 - dist / fd->splrad);
00518
00519 if (mock)
00520 mock->allow_self = qtrue;
00521 G_Damage(check, fd, damage, ent, mock);
00522 if (mock)
00523 mock->allow_self = qfalse;
00524 }
00525
00527 if (tr && G_FireAffectedSurface(tr->surface, fd)) {
00528
00529 VectorMA(impact, 1, tr->plane.normal, impact);
00530 G_SpawnParticle(impact, tr->contentFlags >> 8, "burning");
00531 }
00532 }
00533
00539 static void G_SpawnItemOnFloor (const pos3_t pos, const item_t *item)
00540 {
00541 edict_t *floor;
00542
00543 floor = G_GetFloorItemsFromPos(pos);
00544 if (floor == NULL) {
00545 floor = G_SpawnFloor(pos);
00546
00547 if (!game.i.TryAddToInventory(&game.i, &floor->chr.i, *item, INVDEF(gi.csi->idFloor))) {
00548 G_FreeEdict(floor);
00549 } else {
00550 edict_t *actor = G_GetActorFromPos(pos);
00551
00552
00553 G_CheckVis(floor, qtrue);
00554
00555 if (actor != NULL)
00556 G_GetFloorItems(actor);
00557 }
00558 } else {
00559 if (game.i.TryAddToInventory(&game.i, &floor->chr.i, *item, INVDEF(gi.csi->idFloor))) {
00560
00561 G_EventPerish(floor);
00562 floor->visflags = 0;
00563 G_CheckVis(floor, qtrue);
00564 }
00565 }
00566 }
00567
00568 #define GRENADE_DT 0.1
00569 #define GRENADE_STOPSPEED 60.0
00570
00584 static void G_ShootGrenade (const player_t *player, edict_t *ent, const fireDef_t *fd,
00585 const vec3_t from, const pos3_t at, int mask, const item_t *weapon, shot_mock_t *mock, int z_align)
00586 {
00587 vec3_t last, target, temp;
00588 vec3_t startV, curV, oldPos, newPos;
00589 vec3_t angles;
00590 float dt, time, speed;
00591 float acc;
00592 trace_t tr;
00593 int bounce;
00594 byte flags;
00595
00596
00597 if (G_IsDead(ent))
00598 return;
00599
00600
00601 VectorCopy(from, last);
00602 gi.GridPosToVec(gi.routingMap, ent->fieldSize, at, target);
00603
00604 target[2] -= z_align;
00605
00606
00607 target[2] -= GROUND_DELTA;
00608
00609
00610 dt = gi.GrenadeTarget(last, target, fd->range, fd->launched, fd->rolled, startV);
00611 if (!dt) {
00612 if (!mock)
00613 G_ClientPrintf(player, PRINT_HUD, _("Can't perform action - impossible throw!\n"));
00614 return;
00615 }
00616
00617
00618 speed = VectorLength(startV);
00619 if (speed > fd->range)
00620 speed = fd->range;
00621
00622
00623 acc = GET_ACC(ent->chr.score.skills[ABILITY_ACCURACY], fd->weaponSkill ? ent->chr.score.skills[fd->weaponSkill] : 0);
00624
00625 VecToAngles(startV, angles);
00627 angles[PITCH] += crand() * 2.0f * (fd->spread[0] * (WEAPON_BALANCE + SKILL_BALANCE * acc));
00628 angles[YAW] += crand() * 2.0f * (fd->spread[1] * (WEAPON_BALANCE + SKILL_BALANCE * acc));
00629 AngleVectors(angles, startV, NULL, NULL);
00630 VectorScale(startV, speed, startV);
00631
00632
00633 VectorCopy(last, oldPos);
00634 VectorCopy(startV, curV);
00635 time = 0;
00636 dt = 0;
00637 bounce = 0;
00638 flags = SF_BOUNCING;
00639 for (;;) {
00640
00641 VectorMA(oldPos, GRENADE_DT, curV, newPos);
00642 newPos[2] -= 0.5 * GRAVITY * GRENADE_DT * GRENADE_DT;
00643 curV[2] -= GRAVITY * GRENADE_DT;
00644
00645
00646 tr = G_Trace(oldPos, newPos, ent, MASK_SHOT);
00647 if (tr.fraction < 1.0 || time + dt > 4.0) {
00648 const float bounceFraction = tr.surface ? gi.GetBounceFraction(tr.surface->name) : 1.0f;
00649 int i;
00650
00651
00652 dt += tr.fraction * GRENADE_DT;
00653 time += dt;
00654 bounce++;
00655
00656 if (tr.fraction < 1.0)
00657 VectorCopy(tr.endpos, newPos);
00658
00659
00660 if (!mock) {
00661 for (i = 0; i < MAX_TEAMS; i++)
00662 if (player->pers.team != level.activeTeam && G_TeamPointVis(i, newPos))
00663 mask |= 1 << i;
00664 }
00665
00666
00667 if (VectorLength(curV) < GRENADE_STOPSPEED || time > 4.0 || bounce > fd->bounce
00668 || (!fd->delay && tr.ent && G_IsActor(tr.ent))) {
00669 if (!mock) {
00670
00671 byte impactFlags = flags;
00672 if (tr.ent && G_IsActor(tr.ent))
00673 impactFlags |= SF_BODY;
00674 else
00675 impactFlags |= SF_IMPACT;
00676 G_EventThrow(mask, fd, dt, impactFlags, last, startV);
00677 }
00678
00679 tr.endpos[2] += 10;
00680
00681
00682 if (fd->splrad) {
00683 G_SplashDamage(ent, fd, tr.endpos, mock, &tr);
00684 } else if (!mock) {
00685
00686 if (fd->ammo && !fd->splrad && weapon->t->thrown) {
00687 pos3_t drop;
00688 VecToPos(tr.endpos, drop);
00689 G_SpawnItemOnFloor(drop, weapon);
00690 }
00691 }
00692 return;
00693 }
00694
00695
00696 if (!mock)
00697 G_EventThrow(mask, fd, dt, flags, last, startV);
00698
00699 flags |= SF_BOUNCED;
00700
00701
00702 VectorScale(curV, fd->bounceFac * bounceFraction, curV);
00703 VectorScale(tr.plane.normal, -DotProduct(tr.plane.normal, curV), temp);
00704 VectorAdd(temp, curV, startV);
00705 VectorAdd(temp, startV, curV);
00706
00707
00708 VectorCopy(tr.endpos, last);
00709 VectorCopy(tr.endpos, oldPos);
00710 VectorCopy(curV, startV);
00711 dt = 0;
00712 } else {
00713 dt += GRENADE_DT;
00714 VectorCopy(newPos, oldPos);
00715 }
00716 }
00717 }
00718
00719 #ifdef DEBUG
00720
00725 static void DumpTrace (vec3_t start, trace_t tr)
00726 {
00727 Com_DPrintf(DEBUG_GAME, "start (%i, %i, %i) end (%i, %i, %i)\n",
00728 (int)start[0], (int)start[1], (int)start[2],
00729 (int)tr.endpos[0], (int)tr.endpos[1], (int)tr.endpos[2]);
00730 Com_DPrintf(DEBUG_GAME, "allsolid:%s startsolid:%s fraction:%f contentFlags:%X\n",
00731 tr.allsolid ? "true" : "false",
00732 tr.startsolid ? "true" : "false",
00733 tr.fraction, tr.contentFlags);
00734 Com_DPrintf(DEBUG_GAME, "is entity:%s %s %i\n",
00735 tr.ent ? "yes" : "no",
00736 tr.ent ? tr.ent->classname : "",
00737 tr.ent ? tr.ent->HP : 0);
00738 }
00739
00743 static void DumpAllEntities (void)
00744 {
00745 int i = 0;
00746 edict_t *check = NULL;
00747
00748 while ((check = G_EdictsGetNext(check))) {
00749 Com_DPrintf(DEBUG_GAME, "%i %s %s %s (%i, %i, %i) (%i, %i, %i) [%i, %i, %i] [%i, %i, %i]\n", i,
00750 check->inuse ? "in use" : "unused",
00751 check->classname,
00752 check->model,
00753 (int) check->absmin[0], (int) check->absmin[1], (int) check->absmin[2],
00754 (int) check->absmax[0], (int) check->absmax[1], (int) check->absmax[2],
00755 (int) check->mins[0], (int) check->mins[1], (int) check->mins[2],
00756 (int) check->maxs[0], (int) check->maxs[1], (int) check->maxs[2]);
00757 i++;
00758 }
00759 }
00760 #endif
00761
00776 static void G_ShootSingle (edict_t *ent, const fireDef_t *fd, const vec3_t from, const pos3_t at,
00777 int mask, const item_t *weapon, shot_mock_t *mock, int z_align, int i, shoot_types_t shootType)
00778 {
00779 vec3_t dir;
00780 vec3_t angles;
00781 vec3_t cur_loc;
00782 vec3_t impact;
00783 vec3_t temp;
00784 vec3_t tracefrom;
00785 trace_t tr;
00786 float acc;
00787 float range;
00788 float gauss1;
00789 float gauss2;
00790 float commonfactor;
00791 float injurymultiplier;
00792 int bounce;
00793 int damage;
00794 byte flags;
00795 int throughWall;
00796
00797
00798 if (G_IsDead(ent)) {
00799 Com_DPrintf(DEBUG_GAME, "G_ShootSingle: Shooter is dead, shot not possible.\n");
00800 return;
00801 }
00802
00803
00804 gi.GridPosToVec(gi.routingMap, ent->fieldSize, at, impact);
00805 impact[2] -= z_align;
00806 VectorCopy(from, cur_loc);
00807 VectorSubtract(impact, cur_loc, dir);
00808 VectorNormalize(dir);
00809
00810
00814 VectorMA(cur_loc, sv_shot_origin->value, dir, cur_loc);
00815 VecToAngles(dir, angles);
00816
00817
00818 acc = GET_ACC(ent->chr.score.skills[ABILITY_ACCURACY], fd->weaponSkill ? ent->chr.score.skills[fd->weaponSkill] : 0);
00819
00820
00821 gaussrand(&gauss1, &gauss2);
00822
00823
00824 injurymultiplier = GET_INJURY_MULT(ent->chr.score.skills[ABILITY_MIND], ent->HP, ent->chr.maxHP == 0 ? 100 : ent->chr.maxHP);
00825 Com_DPrintf(DEBUG_GAME, "G_ShootSingle: injury spread multiplier = %5.3f (mind %d, HP %d, maxHP %d)\n", injurymultiplier,
00826 ent->chr.score.skills[ABILITY_MIND], ent->HP, ent->chr.maxHP == 0 ? 100 : ent->chr.maxHP);
00827
00828
00829
00830
00831 commonfactor = (WEAPON_BALANCE + SKILL_BALANCE * acc) * injurymultiplier;
00832 if (G_IsCrouched(ent) && fd->crouch) {
00833 angles[PITCH] += gauss1 * (fd->spread[0] * commonfactor) * fd->crouch;
00834 angles[YAW] += gauss2 * (fd->spread[1] * commonfactor) * fd->crouch;
00835 } else {
00836 angles[PITCH] += gauss1 * (fd->spread[0] * commonfactor);
00837 angles[YAW] += gauss2 * (fd->spread[1] * commonfactor);
00838 }
00839
00840 AngleVectors(angles, dir, NULL, NULL);
00841
00842
00843 throughWall = fd->throughWall;
00844 range = fd->range;
00845 bounce = 0;
00846 flags = 0;
00847
00848
00849 if (FIRESH_IsMedikit(fd))
00850 damage = fd->damage[0] + (fd->damage[1] * crand());
00851 else
00852 damage = max(0, fd->damage[0] + (fd->damage[1] * crand()));
00853
00854 VectorMA(cur_loc, UNIT_SIZE, dir, impact);
00855 tr = G_Trace(cur_loc, impact, ent, MASK_SHOT);
00856 if (tr.ent && (tr.ent->team == ent->team || G_IsCivilian(tr.ent)) && G_IsCrouched(tr.ent) && !FIRESH_IsMedikit(fd))
00857 VectorMA(cur_loc, UNIT_SIZE * 1.4, dir, cur_loc);
00858
00859 VectorCopy(cur_loc, tracefrom);
00860
00861 for (;;) {
00862
00863
00864
00865 VectorMA(cur_loc, range, dir, impact);
00866
00867
00868
00869 tr = G_Trace(tracefrom, impact, ent, MASK_SHOT);
00870
00871 #ifdef DEBUG
00872 DumpAllEntities();
00873 DumpTrace(tracefrom, tr);
00874 #endif
00875
00876
00877 if (tr.startsolid)
00878 break;
00879
00880
00881 VectorCopy(tr.endpos, impact);
00882
00883
00884 if (tr.fraction < 1.0) {
00885 if (tr.ent && G_IsActor(tr.ent)
00886
00887 && !fd->delay)
00888 flags |= SF_BODY;
00889 else if (bounce < fd->bounce)
00890 flags |= SF_BOUNCING;
00891 else
00892 flags |= SF_IMPACT;
00893 }
00894
00895
00896 if (tr.ent && G_IsActor(tr.ent))
00897 mask |= G_TeamToVisMask(tr.ent->team);
00898
00899 if (!mock) {
00900
00901 const qboolean firstShot = (i == 0);
00902 G_EventShoot(ent, mask, fd, firstShot, shootType, flags, &tr, tracefrom, impact);
00903
00904
00905 G_EventShootHidden(mask, fd, qfalse);
00906
00907 if (i == 0 && G_FireAffectedSurface(tr.surface, fd)) {
00908 vec3_t origin;
00909
00910 VectorMA(impact, 1, tr.plane.normal, origin);
00911 G_SpawnParticle(origin, tr.contentFlags >> 8, "fire");
00912 }
00913 }
00914
00915 if (tr.fraction < 1.0 && !fd->bounce) {
00916
00917 if (throughWall && tr.contentFlags & CONTENTS_SOLID) {
00918 throughWall--;
00919 Com_DPrintf(DEBUG_GAME, "Shot through wall, %i walls left.\n", throughWall);
00920
00923 damage /= sqrt(fd->throughWall - throughWall + 1);
00924 VectorMA(tr.endpos, MAX_WALL_THICKNESS_FOR_SHOOTING_THROUGH, dir, tracefrom);
00925 continue;
00926 }
00927
00928
00929 if (fd->splrad) {
00930 VectorMA(impact, sv_shot_origin->value, tr.plane.normal, impact);
00931 G_SplashDamage(ent, fd, impact, mock, &tr);
00932 }
00933 }
00934
00935
00936 if (tr.ent && (G_IsActor(tr.ent) || (G_IsBreakable(tr.ent) && damage > 0))) {
00937 G_Damage(tr.ent, fd, damage, ent, mock);
00938
00939 if (!mock) {
00940
00941 G_UpdateHitScore(ent, tr.ent, fd, 0);
00942 }
00943 break;
00944 }
00945
00946
00947 bounce++;
00948 if (bounce > fd->bounce || tr.fraction >= 1.0)
00949 break;
00950
00951 range -= tr.fraction * range;
00952 VectorCopy(impact, cur_loc);
00953 VectorScale(tr.plane.normal, -DotProduct(tr.plane.normal, dir), temp);
00954 VectorAdd(temp, dir, dir);
00955 VectorAdd(temp, dir, dir);
00956 flags |= SF_BOUNCED;
00957 }
00958
00959 if (!mock) {
00960
00961 if (fd->ammo && !fd->splrad && weapon->t->thrown && !weapon->t->deplete) {
00962 pos3_t drop;
00963
00964 if (G_EdictPosIsSameAs(ent, at)) {
00965 VectorCopy(at, drop);
00966 } else {
00967 impact[2] -= 20;
00968 VecToPos(impact, drop);
00969 }
00970
00971 G_SpawnItemOnFloor(drop, weapon);
00972 }
00973 }
00974 }
00975
00976 static void G_GetShotOrigin (const edict_t *shooter, const fireDef_t *fd, const vec3_t dir, vec3_t shotOrigin)
00977 {
00978
00979 gi.GridPosToVec(gi.routingMap, shooter->fieldSize, shooter->pos, shotOrigin);
00980
00981 shotOrigin[2] += fd->shotOrg[1];
00982
00983 if (fd->shotOrg[0] != 0) {
00984
00985 const float x = dir[1];
00986 const float y = -dir[0];
00987 const float length = sqrt(dir[0] * dir[0] + dir[1] * dir[1]);
00988
00989 shotOrigin[0] += x * fd->shotOrg[0] / length;
00990 shotOrigin[1] += y * fd->shotOrg[0] / length;
00991 }
00992 }
00993
01005 static qboolean G_PrepareShot (edict_t *ent, shoot_types_t shootType, fireDefIndex_t firemode, item_t **weapon, containerIndex_t *container, const fireDef_t **fd)
01006 {
01007 const fireDef_t *fdArray;
01008 objDef_t *od;
01009 item_t *item;
01010
01011 if (shootType >= ST_NUM_SHOOT_TYPES)
01012 gi.Error("G_GetShotFromType: unknown shoot type %i.\n", shootType);
01013
01014 if (IS_SHOT_HEADGEAR(shootType)) {
01015 if (!HEADGEAR(ent))
01016 return qfalse;
01017 item = &HEADGEAR(ent)->item;
01018 *container = gi.csi->idHeadgear;
01019 } else if (IS_SHOT_RIGHT(shootType)) {
01020 if (!RIGHT(ent))
01021 return qfalse;
01022 item = &RIGHT(ent)->item;
01023 *container = gi.csi->idRight;
01024 } else {
01025 if (!LEFT(ent))
01026 return qfalse;
01027 item = &LEFT(ent)->item;
01028 *container = gi.csi->idLeft;
01029 }
01030
01031 if (!item->m)
01032 od = item->t;
01033 else
01034 od = item->m;
01035
01036
01037 fdArray = FIRESH_FiredefForWeapon(item);
01038 if (fdArray == NULL)
01039 return qfalse;
01040
01041 *weapon = item;
01042
01043 assert(firemode >= 0);
01044 *fd = &fdArray[firemode];
01045
01046 return qtrue;
01047 }
01048
01064 qboolean G_ClientShoot (const player_t * player, edict_t* ent, const pos3_t at, shoot_types_t shootType,
01065 fireDefIndex_t firemode, shot_mock_t *mock, qboolean allowReaction, int z_align)
01066 {
01067 const fireDef_t *fd;
01068 item_t *weapon;
01069 vec3_t dir, center, target, shotOrigin;
01070 int i, ammo, prevDir, reactionLeftover, shots;
01071 containerIndex_t container;
01072 int mask;
01073 qboolean quiet;
01074
01075
01076
01077 quiet = (mock != NULL) || G_IsAIPlayer(player);
01078
01079 weapon = NULL;
01080 fd = NULL;
01081 container = 0;
01082 if (!G_PrepareShot(ent, shootType, firemode, &weapon, &container, &fd)) {
01083 if (!weapon && !quiet)
01084 G_ClientPrintf(player, PRINT_HUD, _("Can't perform action - object not activatable!\n"));
01085 return qfalse;
01086 }
01087
01088 ammo = weapon->a;
01089 reactionLeftover = IS_SHOT_REACTION(shootType) ? player->reactionLeftover : 0;
01090
01091
01092
01093 if (allowReaction) {
01094 if (!G_ActionCheckForCurrentTeam(player, ent, fd->time + reactionLeftover))
01095 return qfalse;
01096 } else {
01097 if (!G_ActionCheckWithoutTeam(player, ent, fd->time + reactionLeftover))
01098 return qfalse;
01099 }
01100
01101
01102 if (!fd->irgoggles && G_EdictPosIsSameAs(ent, at))
01103 return qfalse;
01104
01105
01106 if (weapon->t->fireTwoHanded && LEFT(ent)) {
01107 if (!quiet)
01108 G_ClientPrintf(player, PRINT_HUD, _("Can't perform action - weapon cannot be fired one handed!\n"));
01109 return qfalse;
01110 }
01111
01112
01113 if (!ammo && fd->ammo && !weapon->t->thrown) {
01114 if (!quiet)
01115 G_ClientPrintf(player, PRINT_HUD, _("Can't perform action - no ammo!\n"));
01116 return qfalse;
01117 }
01118
01119
01120 gi.GridPosToVec(gi.routingMap, ent->fieldSize, at, target);
01121 if (fd->range < VectorDist(ent->origin, target)) {
01122 if (!quiet)
01123 G_ClientPrintf(player, PRINT_HUD, _("Can't perform action - target out of range!\n"));
01124 return qfalse;
01125 }
01126
01127
01128 if (!mock && ent->chr.scoreMission) {
01129
01131 if (fd->splrad) {
01132
01133 ent->chr.scoreMission->firedSplashTUs[fd->weaponSkill] += fd->time;
01134 ent->chr.scoreMission->firedSplash[fd->weaponSkill]++;
01135 for (i = 0; i < KILLED_NUM_TYPES; i++) {
01137 ent->chr.scoreMission->firedSplashHit[i] = qfalse;
01138 }
01139 } else {
01140
01141 ent->chr.scoreMission->firedTUs[fd->weaponSkill] += fd->time;
01142 ent->chr.scoreMission->fired[fd->weaponSkill]++;
01143 for (i = 0; i < KILLED_NUM_TYPES; i++) {
01145 ent->chr.scoreMission->firedHit[i] = qfalse;
01146 }
01147 }
01148 }
01149
01150
01151 shots = fd->shots;
01152 if (fd->ammo && !weapon->t->thrown) {
01161 if (ammo < fd->ammo) {
01162 shots = fd->shots * ammo / fd->ammo;
01163 ammo = 0;
01164 } else {
01165 ammo -= fd->ammo;
01166 }
01167 if (shots < 1) {
01168 if (!quiet)
01169 G_ClientPrintf(player, PRINT_HUD, _("Can't perform action - not enough ammo!\n"));
01170 return qfalse;
01171 }
01172 }
01173
01174
01175 if (mock)
01176 prevDir = ent->dir;
01177 else
01178 prevDir = 0;
01179
01180 if (!G_EdictPosIsSameAs(ent, at)) {
01181 VectorSubtract(at, ent->pos, dir);
01182 ent->dir = AngleToDir((int) (atan2(dir[1], dir[0]) * todeg));
01183 assert(ent->dir < CORE_DIRECTIONS);
01184
01185 if (!mock) {
01186 G_CheckVisTeamAll(ent->team, qfalse, ent);
01187 G_EventActorTurn(ent);
01188 }
01189 }
01190
01191
01192 target[2] -= z_align;
01193 VectorSubtract(target, ent->origin, dir);
01194 VectorMA(ent->origin, 0.5, dir, center);
01195 mask = 0;
01196 for (i = 0; i < MAX_TEAMS; i++)
01197 if (G_IsVisibleForTeam(ent, i) || G_TeamPointVis(i, target) || G_TeamPointVis(i, center))
01198 mask |= G_TeamToVisMask(i);
01199
01200 if (!mock) {
01201 qboolean itemAlreadyRemoved = qfalse;
01203
01204 if (allowReaction) {
01205 G_ReactionFirePreShot(ent, fd->time);
01206 if (G_IsDead(ent))
01207
01208 return qfalse;
01209 }
01210
01211
01212 G_EventStartShoot(ent, mask, shootType, at);
01213
01214
01215 G_EventShootHidden(mask, fd, qtrue);
01216
01217
01218 if (fd->ammo) {
01219 if (ammo > 0 || !weapon->t->thrown) {
01220 G_EventInventoryAmmo(ent, weapon->m, ammo, shootType);
01221 weapon->a = ammo;
01222 } else {
01223 const invDef_t *invDef = INVDEF(container);
01224 assert(invDef->single);
01225 itemAlreadyRemoved = qtrue;
01226 game.i.EmptyContainer(&game.i, &ent->chr.i, invDef);
01227 G_EventInventoryDelete(ent, G_VisToPM(ent->visflags), invDef, 0, 0);
01228 }
01229 }
01230
01231
01232 if (weapon->t->thrown && weapon->t->oneshot && weapon->t->deplete) {
01233 const invDef_t *invDef = INVDEF(container);
01234 assert(!itemAlreadyRemoved);
01235 assert(invDef->single);
01236 game.i.EmptyContainer(&game.i, &ent->chr.i, invDef);
01237 G_EventInventoryDelete(ent, G_VisToPM(ent->visflags), invDef, 0, 0);
01238 }
01239 }
01240
01241 G_GetShotOrigin(ent, fd, dir, shotOrigin);
01242
01243
01244 for (i = 0; i < shots; i++)
01245 if (fd->gravity)
01246 G_ShootGrenade(player, ent, fd, shotOrigin, at, mask, weapon, mock, z_align);
01247 else
01248 G_ShootSingle(ent, fd, shotOrigin, at, mask, weapon, mock, z_align, i, shootType);
01249
01250 if (!mock) {
01251
01252 if (ent->inuse && !G_IsDead(ent)) {
01253 G_ActorSetTU(ent, max(ent->TU - fd->time, 0));
01254 G_SendStats(ent);
01255 }
01256
01257
01258 gi.EndEvents();
01259
01260
01261 G_MatchEndCheck();
01262
01263
01264 if (allowReaction)
01265 G_ReactionFirePostShot(ent);
01266 } else {
01267 ent->dir = prevDir;
01268 assert(ent->dir < CORE_DIRECTIONS);
01269 }
01270 return qtrue;
01271 }