g_combat.c

Go to the documentation of this file.
00001 
00006 /*
00007 Copyright (C) 2002-2010 UFO: Alien Invasion.
00008 
00009 This program is free software; you can redistribute it and/or
00010 modify it under the terms of the GNU General Public License
00011 as published by the Free Software Foundation; either version 2
00012 of the License, or (at your option) any later version.
00013 
00014 This program is distributed in the hope that it will be useful,
00015 but WITHOUT ANY WARRANTY; without even the implied warranty of
00016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00017 
00018 See the GNU General Public License for more details.
00019 
00020 You should have received a copy of the GNU General Public License
00021 along with this program; if not, write to the Free Software
00022 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
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     /* test if point is visible from team */
00047     while ((from = G_EdictsGetNextLivingActorOfTeam(from, team))) {
00048         if (G_FrustumVis(from, point)) {
00049             /* get viewers eye height */
00050             VectorCopy(from->origin, eye);
00051             if (G_IsCrouched(from))
00052                 eye[2] += EYE_CROUCH;
00053             else
00054                 eye[2] += EYE_STAND;
00055 
00056             /* line of sight */
00057             if (!G_TestLine(eye, point))
00058                 return qtrue;
00059         }
00060     }
00061 
00062     /* not visible */
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         /* this only applies to ET_ACTOR but not ET_ACTOR2x2 */
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                 /* morale damage depends on the damage */
00088                 mod = mob_wound->value * param;
00089                 /* death hurts morale even more than just damage */
00090                 if (type == ML_DEATH)
00091                     mod += mob_death->value;
00092                 /* seeing how someone gets shot increases the morale change */
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                     /* teamkills are considered to be bad form, but won't cause an increased morale boost for the enemy */
00097                     /* morale boost isn't equal to morale loss (it's lower, but morale gets regenerated) */
00098                     if (victim->team == attacker->team)
00099                         mod *= mof_teamkill->value;
00100                     else
00101                         mod *= mof_enemy->value;
00102                 }
00103                 /* seeing a civilian die is more "acceptable" */
00104                 if (G_IsCivilian(victim))
00105                     mod *= mof_civilian->value;
00106                 /* if an ally (or in singleplayermode, as human, a civilian) got shot, lower the morale, don't heighten it. */
00107                 if (victim->team == ent->team || (G_IsCivilian(victim) && ent->team != TEAM_ALIEN && sv_maxclients->integer == 1))
00108                     mod *= -1;
00109                 /* if you stand near to the attacker or the victim, the morale change is higher. */
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                 /* morale damage depends on the number of living allies */
00114                 mod *= (1 - mon_teamfactor->value)
00115                     + mon_teamfactor->value * (level.num_spawned[victim->team] + 1)
00116                     / (level.num_alive[victim->team] + 1);
00117                 /* being hit isn't fun */
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             /* clamp new morale */
00126             /*+0.9 to allow weapons like flamethrowers to inflict panic (typecast rounding) */
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             /* send phys data */
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     /* only phalanx soldiers have this */
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     /* Abort if no player team. */
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             /* Increase friendly fire counter. */
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             /* Increase friendly fire counter. */
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     /* Breakables */
00301     if (G_IsBrushModel(target)) {
00302         /* Breakables are immune to stun & shock damage. */
00303         if (stunEl || stunGas || shock || mock)
00304             return;
00305 
00306         if (damage >= target->HP) {
00307             /* don't reset the HP value here, this value is used to distinguish
00308              * between triggered destroy and a shoot */
00309             assert(target->destroy);
00310             target->destroy(target);
00311 
00312             /* maybe the attacker is seeing something new? */
00313             G_CheckVisTeamAll(attacker->team, qfalse, attacker);
00314 
00315             /* check if attacker appears/perishes for any other team */
00316             G_CheckVis(attacker, qtrue);
00317         } else {
00318             G_TakeDamage(target, damage);
00319         }
00320         return;
00321     }
00322 
00323     /* Actors don't die again. */
00324     if (!G_IsLivingActor(target))
00325         return;
00326 
00327     /* only actors after this point - and they must have a teamdef */
00328     assert(target->chr.teamDef);
00329     isRobot = CHRSH_IsTeamDefRobot(target->chr.teamDef);
00330 
00331     /* Apply armour effects. */
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         /* Robots can't be healed. */
00342         if (isRobot)
00343             return;
00344     }
00345     Com_DPrintf(DEBUG_GAME, " Total damage: %d\n", damage);
00346 
00347     /* Apply difficulty settings. */
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         /* hit */
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) /* Can't stun robots with gas */
00366                 target->STUN += damage;
00367         } else if (shock) {
00368             /* Only do this if it's not one from our own team ... they should have known that there was a flashbang coming. */
00369             if (!isRobot && target->team != attacker->team) {
00371                 /* dazed entity wont reaction fire */
00372                 G_RemoveReaction(target);
00373                 G_ActorReserveTUs(target, 0, target->chr.reservedTus.shot, target->chr.reservedTus.crouch);
00374                 /* flashbangs kill TUs */
00375                 G_ActorSetTU(target, 0);
00376                 G_SendStats(target);
00377                 /* entity is dazed */
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                 /* The 'attacker' is healing the target. */
00386                 /* Update stats here to get info on how many TUs the target received. */
00387                 if (target->chr.scoreMission)
00388                     target->chr.scoreMission->heal += abs(damage);
00389 
00392                 if (G_IsStunned(target)) {
00393                     /* reduce STUN */
00394                     target->STUN += damage;
00395                     G_ActorCheckRevitalise(target);
00396                 }
00397             } else {
00398                 /* Real damage was dealt. */
00399 
00400                 /* Update overall splash damage for stats/score. */
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     /* Check death/knockout. */
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         /* apply morale changes */
00419         if (mor_panic->integer)
00420             G_Morale(ML_DEATH, target, attacker, damage);
00421 
00422         /* Update number of killed/stunned actors for this attacker. */
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 { /* medikit, etc. */
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         /* If we use a blinding weapon we skip the target if it's looking
00481          * away from the impact location. */
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         /* check for distance */
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             /* check whether this actor (check) is in the field of view of the 'shooter' (ent) */
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         /* check for walls */
00510         if (G_IsLivingActor(check) && !G_ActorVis(impact, check, qfalse))
00511             continue;
00512 
00513         /* do damage */
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         /* move a little away from the impact vector */
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             /* send the inventory */
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             /* make it invisible to send the inventory in the below vis check */
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     /* Check if the shooter is still alive (me may fire with area-damage ammo and have just hit the near ground). */
00597     if (G_IsDead(ent))
00598         return;
00599 
00600     /* get positional data */
00601     VectorCopy(from, last);
00602     gi.GridPosToVec(gi.routingMap, ent->fieldSize, at, target);
00603     /* first apply z_align value */
00604     target[2] -= z_align;
00605 
00606     /* prefer to aim grenades at the ground */
00607     target[2] -= GROUND_DELTA;
00608 
00609     /* calculate parabola */
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     /* cap start speed */
00618     speed = VectorLength(startV);
00619     if (speed > fd->range)
00620         speed = fd->range;
00621 
00622     /* add random effects and get new dir */
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     /* move */
00633     VectorCopy(last, oldPos);
00634     VectorCopy(startV, curV);
00635     time = 0;
00636     dt = 0;
00637     bounce = 0;
00638     flags = SF_BOUNCING;
00639     for (;;) {
00640         /* kinematics */
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         /* trace */
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             /* advance time */
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             /* calculate additional visibility */
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             /* enough bouncing around or we have hit an actor */
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                     /* explode */
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                 /* check if this is a stone, ammo clip or grenade */
00682                 if (fd->splrad) {
00683                     G_SplashDamage(ent, fd, tr.endpos, mock, &tr);
00684                 } else if (!mock) {
00685                     /* spawn the stone on the floor */
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             /* send */
00696             if (!mock)
00697                 G_EventThrow(mask, fd, dt, flags, last, startV);
00698 
00699             flags |= SF_BOUNCED;
00700 
00701             /* bounce */
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             /* prepare next move */
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;         /* Direction from the location of the gun muzzle ("from") to the target ("at") */
00780     vec3_t angles;      
00781     vec3_t cur_loc;     /* The current location of the projectile. */
00782     vec3_t impact;      /* The location of the target (-center?) */
00783     vec3_t temp;
00784     vec3_t tracefrom;   /* sum */
00785     trace_t tr;         /* the traceing */
00786     float acc;          /* Accuracy modifier for the angle of the shot. */
00787     float range;
00788     float gauss1;
00789     float gauss2;       /* For storing 2 gaussian distributed random values. */
00790     float commonfactor; /* common to pitch and yaw spread, avoid extra multiplications */
00791     float injurymultiplier;
00792     int bounce;         /* count the bouncing */
00793     int damage;         /* The damage to be dealt to the target. */
00794     byte flags;
00795     int throughWall;    /* shoot through x walls */
00796 
00797     /* Check if the shooter is still alive (me may fire with area-damage ammo and have just hit the near ground). */
00798     if (G_IsDead(ent)) {
00799         Com_DPrintf(DEBUG_GAME, "G_ShootSingle: Shooter is dead, shot not possible.\n");
00800         return;
00801     }
00802 
00803     /* Calc direction of the shot. */
00804     gi.GridPosToVec(gi.routingMap, ent->fieldSize, at, impact); /* Get the position of the targeted grid-cell. ('impact' is used only temporary here)*/
00805     impact[2] -= z_align;
00806     VectorCopy(from, cur_loc);      /* Set current location of the projectile to the starting (muzzle) location. */
00807     VectorSubtract(impact, cur_loc, dir);   /* Calculate the vector from current location to the target. */
00808     VectorNormalize(dir);           /* Normalize the vector i.e. make length 1.0 */
00809 
00810     /* places the starting-location a bit away from the attacker-model/grid. */
00814     VectorMA(cur_loc, sv_shot_origin->value, dir, cur_loc);
00815     VecToAngles(dir, angles);       /* Get the angles of the direction vector. */
00816 
00817     /* Get accuracy value for this attacker. */
00818     acc = GET_ACC(ent->chr.score.skills[ABILITY_ACCURACY], fd->weaponSkill ? ent->chr.score.skills[fd->weaponSkill] : 0);
00819 
00820     /* Get 2 gaussian distributed random values */
00821     gaussrand(&gauss1, &gauss2);
00822 
00823     /* Calculate spread multiplier to give worse precision when HPs are not at max */
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     /* Modify the angles with the accuracy modifier as a randomizer-range. If the attacker is crouched this modifier is included as well.  */
00829     /* Base spread multiplier comes from the firedef's spread values. Soldier skills further modify the spread.
00830      * A good soldier will tighten the spread, a bad one will widen it, for skillBalanceMinimum values between 0 and 1.*/
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     /* Convert changed angles into new direction. */
00840     AngleVectors(angles, dir, NULL, NULL);
00841 
00842     /* shoot and bounce */
00843     throughWall = fd->throughWall;
00844     range = fd->range;
00845     bounce = 0;
00846     flags = 0;
00847 
00848     /* Are we healing? */
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         /* Calc 'impact' vector that is located at the end of the range
00863          * defined by the fireDef_t. This is not really the impact location,
00864          * but rather the 'endofrange' location, see below for another use.*/
00865         VectorMA(cur_loc, range, dir, impact);
00866 
00867         /* Do the trace from current position of the projectile
00868          * to the end_of_range location.*/
00869         tr = G_Trace(tracefrom, impact, ent, MASK_SHOT);
00870 
00871 #ifdef DEBUG
00872         DumpAllEntities();
00873         DumpTrace(tracefrom, tr);
00874 #endif
00875 
00876         /* maybe we start the trace from within a brush (e.g. in case of throughWall) */
00877         if (tr.startsolid)
00878             break;
00879 
00880         /* _Now_ we copy the correct impact location. */
00881         VectorCopy(tr.endpos, impact);
00882 
00883         /* set flags when trace hit something */
00884         if (tr.fraction < 1.0) {
00885             if (tr.ent && G_IsActor(tr.ent)
00886                 /* check if we differentiate between body and wall */
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         /* victims see shots */
00896         if (tr.ent && G_IsActor(tr.ent))
00897             mask |= G_TeamToVisMask(tr.ent->team);
00898 
00899         if (!mock) {
00900             /* send shot */
00901             const qboolean firstShot = (i == 0);
00902             G_EventShoot(ent, mask, fd, firstShot, shootType, flags, &tr, tracefrom, impact);
00903 
00904             /* send shot sound to the others */
00905             G_EventShootHidden(mask, fd, qfalse);
00906 
00907             if (i == 0 && G_FireAffectedSurface(tr.surface, fd)) {
00908                 vec3_t origin;
00909                 /* sent particle to all players */
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             /* check for shooting through wall */
00917             if (throughWall && tr.contentFlags & CONTENTS_SOLID) {
00918                 throughWall--;
00919                 Com_DPrintf(DEBUG_GAME, "Shot through wall, %i walls left.\n", throughWall);
00920                 /* reduce damage */
00923                 damage /= sqrt(fd->throughWall - throughWall + 1);
00924                 VectorMA(tr.endpos, MAX_WALL_THICKNESS_FOR_SHOOTING_THROUGH, dir, tracefrom);
00925                 continue;
00926             }
00927 
00928             /* do splash damage */
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         /* do damage if the trace hit an entity */
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) { /* check for firedHit is done in G_UpdateHitScore */
00940                 /* Count this as a hit of this firemode. */
00941                 G_UpdateHitScore(ent, tr.ent, fd, 0);
00942             }
00943             break;
00944         }
00945 
00946         /* bounce is checked here to see if the rubber rocket hit walls enough times to wear out*/
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         /* spawn the throwable item on the floor but only if it is not depletable */
00961         if (fd->ammo && !fd->splrad && weapon->t->thrown && !weapon->t->deplete) {
00962             pos3_t drop;
00963 
00964             if (G_EdictPosIsSameAs(ent, at)) { /* throw under his own feet */
00965                 VectorCopy(at, drop);
00966             } else {
00967                 impact[2] -= 20; /* a hack: no-gravity items are flying high */
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     /* get weapon position */
00979     gi.GridPosToVec(gi.routingMap, shooter->fieldSize, shooter->pos, shotOrigin);
00980     /* adjust height: */
00981     shotOrigin[2] += fd->shotOrg[1];
00982     /* adjust horizontal: */
00983     if (fd->shotOrg[0] != 0) {
00984         /* get "right" and "left" of a unit(rotate dir 90 on the x-y plane): */
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         /* assign adjustments: */
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     /* Get firedef from the weapon entry instead */
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     /* just in 'test-whether-it's-possible'-mode or the player is an
01076      * ai - no readable feedback needed */
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     /* check if action is possible
01092      * if allowReaction is false, it is a shot from reaction fire, so don't check the active team */
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     /* Don't allow to shoot yourself */
01102     if (!fd->irgoggles && G_EdictPosIsSameAs(ent, at))
01103         return qfalse;
01104 
01105     /* check that we're not firing a twohanded weapon with one hand! */
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     /* check we're not out of ammo */
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     /* check target is not out of range */
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     /* Count for stats if it's no mock-shot and it's a Phalanx soldier (aliens do not have this info yet). */
01128     if (!mock && ent->chr.scoreMission) {
01129         /* Count this start of the shooting for stats/score. */
01131         if (fd->splrad) {
01132             /* Splash damage */
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             /* Direct hits */
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     /* fire shots */
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     /* rotate the player */
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     /* calculate visibility */
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         /* check whether this has forced any reaction fire */
01204         if (allowReaction) {
01205             G_ReactionFirePreShot(ent, fd->time);
01206             if (G_IsDead(ent))
01207                 /* dead men can't shoot */
01208                 return qfalse;
01209         }
01210 
01211         /* start shoot */
01212         G_EventStartShoot(ent, mask, shootType, at);
01213 
01214         /* send shot sound to the others */
01215         G_EventShootHidden(mask, fd, qtrue);
01216 
01217         /* ammo... */
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 { /* delete the knife or the rifle without ammo */
01223                 const invDef_t *invDef = INVDEF(container);
01224                 assert(invDef->single);
01225                 itemAlreadyRemoved = qtrue; /* for assert only */
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         /* remove throwable oneshot && deplete weapon from inventory */
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     /* Fire all shots. */
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         /* send TUs if ent still alive */
01252         if (ent->inuse && !G_IsDead(ent)) {
01253             G_ActorSetTU(ent, max(ent->TU - fd->time, 0));
01254             G_SendStats(ent);
01255         }
01256 
01257         /* end events */
01258         gi.EndEvents();
01259 
01260         /* check for win/draw conditions */
01261         G_MatchEndCheck();
01262 
01263         /* check for Reaction fire against the shooter */
01264         if (allowReaction)
01265             G_ReactionFirePostShot(ent);
01266     } else {
01267         ent->dir = prevDir;
01268         assert(ent->dir < CORE_DIRECTIONS);
01269     }
01270     return qtrue;
01271 }

Generated by  doxygen 1.6.2