/** * @brief Prints some reaction fire data to the console * @param[in] target The target entity */ static void G_ReactionFirePrintSituation (Edict* target) { if (!G_IsAlien(target)) return; Com_Printf("Alien %i at %i/%i/%i TU:%i\n", target->number, target->pos[0], target->pos[1], target->pos[2], target->TU); Actor* shooter = nullptr; /* check all possible shooters */ while ((shooter = G_EdictsGetNextLivingActor(shooter))) { if (G_IsAlien(shooter) || G_IsCivilian(shooter)) continue; char msgHdr[100]; Com_sprintf(msgHdr, sizeof(msgHdr), "S%i: at %i/%i/%i RF: ", shooter->number, shooter->pos[0], shooter->pos[1], shooter->pos[2]); int ttus = rft.getTriggerTUs(shooter, target); if (ttus == -2) Com_Printf("%s not initialized\n", msgHdr); if (ttus == -1) Com_Printf("%s not aiming\n", msgHdr); else if (rft.hasExpired(shooter, target, 0)) Com_Printf("expired\n", msgHdr); else Com_Printf("%s not yet: %i\n", msgHdr, ttus); } }
/** * @brief Perform the reaction fire shot * @param[in] player The player this action belongs to (the human player or the ai) * @param[in] shooter The actor that is trying to shoot * @param[in] at Position to fire on. * @param[in] type What type of shot this is (left, right reaction-left etc...). * @param[in] firemode The firemode index of the ammo for the used weapon (objDef.fd[][x]) . * @return true if everything went ok (i.e. the shot(s) where fired ok), otherwise false. * @sa G_ClientShoot */ static bool G_ReactionFireShoot (const player_t *player, edict_t *shooter, const pos3_t at, shoot_types_t type, fireDefIndex_t firemode) { const int minhit = 30; shot_mock_t mock; int ff, i; /* this is the max amount of friendly units that were hit during the mock calculation */ int maxff; if (G_IsInsane(shooter)) maxff = 100; else if (G_IsRaged(shooter)) maxff = 60; else if (G_IsPaniced(shooter)) maxff = 30; else if (G_IsShaken(shooter)) maxff = 15; else maxff = 5; /* calculate the mock values - e.g. how many friendly units we would hit * when opening the reaction fire */ OBJZERO(mock); for (i = 0; i < 100; i++) if (!G_ClientShoot(player, shooter, at, type, firemode, &mock, false, 0)) break; ff = mock.friendCount + (G_IsAlien(shooter) ? 0 : mock.civilian); if (ff <= maxff && mock.enemyCount >= minhit) return G_ClientShoot(player, shooter, at, type, firemode, NULL, false, 0); return false; }
/** * @brief Perform the reaction fire shot * @param[in] shooter The actor that is trying to shoot * @param[in] at Position to fire on. * @param[in] type What type of shot this is (left, right reaction-left etc...). * @param[in] firemode The firemode index of the ammo for the used weapon (objDef.fd[][x]) . * @return true if everything went ok (i.e. the shot(s) where fired ok), otherwise false. * @sa G_ClientShoot */ bool ReactionFire::shoot (Actor* shooter, const pos3_t at, shoot_types_t type, fireDefIndex_t firemode) { /* this is the max amount of friendly units that were hit during the mock calculation */ int maxff; if (shooter->isInsane()) maxff = 100; else if (shooter->isRaged()) maxff = 60; else if (shooter->isPanicked()) maxff = 30; else if (shooter->isShaken()) maxff = 15; else maxff = 5; /* calculate the mock values - e.g. how many friendly units we would hit * when opening the reaction fire */ const int minhit = 30; shot_mock_t mock; const Player& player = shooter->getPlayer(); for (int i = 0; i < 100; i++) if (!G_ClientShoot(player, shooter, at, type, firemode, &mock, false, 0)) break; const int ff = mock.friendCount + (G_IsAlien(shooter) ? 0 : mock.civilian); if (ff <= maxff && mock.enemyCount >= minhit) return G_ClientShoot(player, shooter, at, type, firemode, nullptr, false, 0); return false; }
/** * @brief Check if given actor is an enemy. * @param[in] actor The actor that makes the check. * @returns @c true if enemies. @c false otherwise * @todo Should we really know if the other actor is controlled by the other team (STATE_XVI)? * aliens would of course know if an actor is infected (becomes part of the hive mind), but humans? */ bool Edict::isOpponent (const Actor* actor) const { const bool actControlled = actor->isState(STATE_XVI); const bool selfControlled = G_IsState(this, STATE_XVI); if (actor->isSameTeamAs(this)) return selfControlled ? !actControlled : actControlled; bool opponent = true; switch (this->getTeam()) { /* Aliens: hostile to everyone */ case TEAM_ALIEN: opponent = !actControlled; break; /* Civilians: Only hostile to aliens */ case TEAM_CIVILIAN: opponent = G_IsAlien(actor) || actControlled; break; /* PHALANX and MP teams: Hostile to aliens and rival teams * (under AI control while under panic/rage or when g_aihumans is non-zero) */ default: opponent = !G_IsCivilian(actor) || actControlled; break; } return selfControlled ? !opponent : opponent; }
/** * @brief Remove a reaction fire target for the given shooter. * @param[in] shooter The reaction firing actor * @param[in] target The potential reaction fire victim */ void ReactionFireTargets::remove (const Edict* shooter, const Edict* target) { ReactionFireTargetList* rfts = find(shooter); assert(rfts); assert(target); for (int i = 0; i < rfts->count; i++) { ReactionFireTarget& t = rfts->targets[i]; if (t.target != target) continue; /* not the last one? */ if (i != rfts->count - 1) { t.target = rfts->targets[rfts->count - 1].target; t.triggerTUs = rfts->targets[rfts->count - 1].triggerTUs; } rfts->count--; G_EventReactionFireRemoveTarget(*shooter, *target, target->moveinfo.steps - 1); #if DEBUG_RF if (!(G_IsAlien(shooter) || G_IsCivilian(shooter))) Com_Printf("S%i: removed\n", shooter->number); #endif } }
/** * @brief Mission trigger * @todo use level.nextmap to spawn another map when every living actor has touched the mission trigger * @todo use level.actualRound to determine the 'King of the Hill' time * @note Don't set a client action here - otherwise the movement event might * be corrupted */ bool G_MissionTouch (edict_t *self, edict_t *activator) { if (!self->owner) return false; switch (self->owner->team) { case TEAM_ALIEN: if (G_IsAlien(activator)) { if (!self->count) { self->count = level.actualRound; gi.BroadcastPrintf(PRINT_HUD, _("Aliens entered target zone!")); } return true; } else { /* reset king of the hill counter */ self->count = 0; } /* general case that also works for multiplayer teams */ default: if (activator->team == self->owner->team) { if (!self->owner->count) { self->owner->count = level.actualRound; if (self->owner->item) { /* search the item in the activator's inventory */ containerIndex_t container; for (container = 0; container < gi.csi->numIDs; container++) { const invDef_t *invDef = INVDEF(container); invList_t *ic; /* ignore items linked from any temp container the actor * must have this in his hands */ if (invDef->temp) continue; for (ic = CONTAINER(activator, container); ic; ic = ic->next) { const objDef_t *od = ic->item.t; /* check whether we found the searched item in the * actor's inventory */ if (Q_streq(od->id, self->owner->item)) { /* drop the weapon - even if out of TUs */ G_ActorInvMove(activator, invDef, ic, INVDEF(gi.csi->idFloor), NONE, NONE, false); gi.BroadcastPrintf(PRINT_HUD, _("Item was placed.")); self->owner->count = level.actualRound; return true; } } } } else { gi.BroadcastPrintf(PRINT_HUD, _("Target zone is occupied!")); } } return true; } else { /* reset king of the hill counter */ self->count = 0; } } return false; }
/** * @brief Mission trigger * @todo use level.nextmap to spawn another map when every living actor has touched the mission trigger * @todo use level.actualRound to determine the 'King of the Hill' time * @note Don't set a client action here - otherwise the movement event might * be corrupted */ bool G_MissionTouch (Edict* self, Edict* activator) { if (!self->owner()) return false; switch (self->owner()->getTeam()) { case TEAM_ALIEN: if (G_IsAlien(activator)) { if (!self->count) { self->count = level.actualRound; gi.BroadcastPrintf(PRINT_HUD, _("Aliens entered target zone!")); } return true; } else { /* reset king of the hill counter */ self->count = 0; } /* general case that also works for multiplayer teams */ default: if (!activator->isSameTeamAs(self->owner())) { /* reset king of the hill counter */ self->count = 0; return false; } if (self->owner()->count) return false; self->owner()->count = level.actualRound; if (!self->owner()->item) { gi.BroadcastPrintf(PRINT_HUD, _("Target zone is occupied!")); return true; } /* search the item in the activator's inventory */ /* ignore items linked from any temp container the actor must have this in his hands */ const Container* cont = nullptr; while ((cont = activator->chr.inv.getNextCont(cont))) { Item* item = nullptr; while ((item = cont->getNextItem(item))) { const objDef_t* od = item->def(); /* check whether we found the searched item in the actor's inventory */ if (!Q_streq(od->id, self->owner()->item)) continue; /* drop the weapon - even if out of TUs */ G_ActorInvMove(makeActor(activator), cont->def(), item, INVDEF(CID_FLOOR), NONE, NONE, false); gi.BroadcastPrintf(PRINT_HUD, _("Item was placed.")); self->owner()->count = level.actualRound; return true; } } break; } return true; }
/** * @brief Perform the reaction fire shot * @param[in] shooter The actor that is trying to shoot * @param[in] at Position to fire on. * @param[in] type What type of shot this is (left, right reaction-left etc...). * @param[in] firemode The firemode index of the ammo for the used weapon (objDef.fd[][x]) . * @return true if everything went ok (i.e. the shot(s) where fired ok), otherwise false. * @sa G_ClientShoot */ bool ReactionFire::shoot (Actor* shooter, const pos3_t at, shoot_types_t type, fireDefIndex_t firemode) { const Item* weapon = nullptr; if (IS_SHOT_RIGHT(type)) { weapon = shooter->getRightHandItem(); if (!weapon) return false; } else { weapon = shooter->getLeftHandItem(); if (!weapon) return false; } const fireDef_t* fdArray = weapon->getFiredefs(); if (!fdArray) return false; /* Adjust the number of samples we take so that we don't end firing thousands of shots * in case the fire mode is multi-shot */ const int shotsPerFD = fdArray[firemode].shots; const int samples = std::max(1, 100 / shotsPerFD); const Player& player = shooter->getPlayer(); shot_mock_t mock; for (int i = 0; i < samples; ++i) if (!G_ClientShoot(player, shooter, at, type, firemode, &mock, false, 0)) break; /* this is the max amount of friendly units that were hit during the mock calculation */ const int maxShots = samples * shotsPerFD; int maxff; if (shooter->isInsane()) maxff = maxShots; else if (shooter->isRaged()) maxff = maxShots * 2 / 3; else if (shooter->isPanicked()) maxff = maxShots / 3; else if (shooter->isShaken()) maxff = maxShots / 6; else maxff = maxShots / 20; /* calculate the mock values - e.g. how many friendly units we would hit * when opening the reaction fire */ const int ff = mock.friendCount + (G_IsAlien(shooter) ? 0 : mock.civilian); const int hits = shooter->isInsane() ? ff + mock.enemyCount : mock.enemyCount; if (ff <= maxff && hits > 0) return G_ClientShoot(player, shooter, at, type, firemode, nullptr, false, 0); return false; }
/** * @brief Add a reaction fire target for the given shooter. * @param[in] shooter The reaction firing actor * @param[in] target The potential reaction fire victim * @param[in] tusForShot The TUs needed for the shot */ void ReactionFireTargets::add (const Edict *shooter, const Edict *target, const int tusForShot) { int i; ReactionFireTargetList *rfts = find(shooter); assert(rfts); assert(target); for (i = 0; i < rfts->count; i++) { if (rfts->targets[i].target == target) /* found it ? */ return; /* shooter already knows that target */ } assert(i < MAX_RF_TARGETS); rfts->targets[i].target = target; rfts->targets[i].triggerTUs = target->TU - tusForShot; rfts->count++; #if DEBUG_RF if (!(G_IsAlien(shooter) || G_IsCivilian(shooter))) Com_Printf("S%i: added\n", shooter->number); #endif }
/** * @brief Remove a reaction fire target for the given shooter. * @param[in] shooter The reaction firing actor * @param[in] target The potential reaction fire victim */ void ReactionFireTargets::remove (Edict *shooter, const Edict *target) { int i; ReactionFireTargetList *rfts = find(shooter); assert(rfts); assert(target); for (i = 0; i < rfts->count; i++) { if (rfts->targets[i].target == target) { /* found it ? */ if (i != rfts->count - 1) { /* not the last one ? */ rfts->targets[i].target = rfts->targets[rfts->count - 1].target; rfts->targets[i].triggerTUs = rfts->targets[rfts->count - 1].triggerTUs; } rfts->count--; #if DEBUG_RF if (!(G_IsAlien(shooter) || G_IsCivilian(shooter))) Com_Printf("S%i: removed\n", shooter->number); #endif } } }
/** * @brief Add a reaction fire target for the given shooter. * @param[in] shooter The reaction firing actor * @param[in] target The potential reaction fire victim * @param[in] tusForShot The TUs needed for the shot */ void ReactionFireTargets::add (const Edict* shooter, const Edict* target, const int tusForShot) { int i; ReactionFireTargetList* rfts = find(shooter); assert(rfts); assert(target); for (i = 0; i < rfts->count; i++) { /* check if shooter already knows that target */ if (rfts->targets[i].target == target) return; } if (i >= MAX_RF_TARGETS) return; rfts->targets[i].target = target; rfts->targets[i].triggerTUs = target->TU - tusForShot; rfts->count++; G_EventReactionFireAddTarget(*shooter, *target, tusForShot, target->moveinfo.steps - 1); #if DEBUG_RF if (!(G_IsAlien(shooter) || G_IsCivilian(shooter))) Com_Printf("S%i: added\n", shooter->number); #endif }
/** * @brief Deals damage of a give type and amount to a target. * @param[in,out] target What we want to damage. * @param[in] fd The fire definition that defines what type of damage is dealt. * @param[in] damage The value of the damage. * @param[in] attacker The attacker. * @param[in] mock pseudo shooting - only for calculating mock values - NULL for real shots * @param[in] impact impact location - @c NULL for splash damage * @sa G_SplashDamage * @sa G_TakeDamage * @sa G_PrintActorStats */ static void G_Damage (edict_t *target, const fireDef_t *fd, int damage, edict_t *attacker, shot_mock_t *mock, const vec3_t impact) { const bool stunEl = (fd->obj->dmgtype == gi.csi->damStunElectro); const bool stunGas = (fd->obj->dmgtype == gi.csi->damStunGas); const bool shock = (fd->obj->dmgtype == gi.csi->damShock); const bool smoke = (fd->obj->dmgtype == gi.csi->damSmoke); bool isRobot; assert(target); /* Breakables */ if (G_IsBrushModel(target) && G_IsBreakable(target)) { /* Breakables are immune to stun & shock damage. */ if (stunEl || stunGas || shock || mock || smoke) return; if (damage >= target->HP) { /* don't reset the HP value here, this value is used to distinguish * between triggered destroy and a shoot */ assert(target->destroy); target->destroy(target); /* maybe the attacker is seeing something new? */ G_CheckVisTeamAll(attacker->team, 0, attacker); /* check if attacker appears/perishes for any other team */ G_CheckVis(attacker); } else { G_TakeDamage(target, damage); } return; } /* Actors don't die again. */ if (!G_IsLivingActor(target)) return; /* only actors after this point - and they must have a teamdef */ assert(target->chr.teamDef); isRobot = CHRSH_IsTeamDefRobot(target->chr.teamDef); /* Apply armour effects. */ if (damage > 0) { damage = G_ApplyProtection(target, fd->dmgweight, damage); } else if (damage < 0) { /* Robots can't be healed. */ if (isRobot) return; } Com_DPrintf(DEBUG_GAME, " Total damage: %d\n", damage); /* Apply difficulty settings. */ if (sv_maxclients->integer == 1) { if (G_IsAlien(attacker) && !G_IsAlien(target)) damage *= pow(1.18, g_difficulty->value); else if (!G_IsAlien(attacker) && G_IsAlien(target)) damage *= pow(1.18, -g_difficulty->value); } assert(attacker->team >= 0 && attacker->team < MAX_TEAMS); assert(target->team >= 0 && target->team < MAX_TEAMS); if (g_nodamage != NULL && !g_nodamage->integer) { /* hit */ if (mock) { G_UpdateShotMock(mock, attacker, target, damage); } else if (stunEl) { target->STUN += damage; } else if (stunGas) { if (!isRobot) /* Can't stun robots with gas */ target->STUN += damage; } else if (shock) { /* Only do this if it's not one from our own team ... they should have known that there was a flashbang coming. */ if (!isRobot && target->team != attacker->team) { /** @todo there should be a possible protection, too */ /* dazed entity wont reaction fire */ G_RemoveReaction(target); G_ActorReserveTUs(target, 0, target->chr.reservedTus.shot, target->chr.reservedTus.crouch); /* flashbangs kill TUs */ G_ActorSetTU(target, 0); G_SendStats(target); /* entity is dazed */ G_SetDazed(target); G_ClientPrintf(G_PLAYER_FROM_ENT(target), PRINT_HUD, _("Soldier is dazed!\nEnemy used flashbang!")); return; } } else { if (damage < 0) { /* The 'attacker' is healing the target. */ G_TreatActor(target, fd, damage, attacker->team); } else { /* Real damage was dealt. */ G_DamageActor(target, damage, impact); /* Update overall splash damage for stats/score. */ if (!mock && damage > 0 && fd->splrad) /**< Check for >0 and splrad to not count this as direct hit. */ G_UpdateHitScore(attacker, target, fd, damage); } } } if (mock) return; G_CheckDeathOrKnockout(target, attacker, fd, damage); }
/** * @brief Applies morale changes to actors around a wounded or killed actor. * @note only called when mor_panic is not zero * @param[in] type Type of morale modifier (@sa morale_modifiers) * @param[in] victim An actor being a victim of the attack. * @param[in] attacker An actor being attacker in this attack. * @param[in] param Used to modify morale changes, for G_Damage() it is value of damage. * @sa G_Damage */ static void G_Morale (morale_modifiers type, const Edict* victim, const Edict* attacker, int param) { Actor* actor = nullptr; while ((actor = G_EdictsGetNextLivingActor(actor))) { /* this only applies to ET_ACTOR but not ET_ACTOR2x2 */ if (actor->type != ET_ACTOR) continue; if (G_IsCivilian(actor)) continue; /* morale damage depends on the damage */ float mod = mob_wound->value * param; if (type == ML_SHOOT) mod *= mob_shoot->value; /* death hurts morale even more than just damage */ if (type == ML_DEATH) mod += mob_death->value; /* seeing how someone gets shot increases the morale change */ if (actor == victim || (G_FrustumVis(actor, victim->origin) && G_ActorVis(actor, victim, false))) mod *= mof_watching->value; if (attacker != nullptr && actor->isSameTeamAs(attacker)) { /* teamkills are considered to be bad form, but won't cause an increased morale boost for the enemy */ /* morale boost isn't equal to morale loss (it's lower, but morale gets regenerated) */ if (victim->isSameTeamAs(attacker)) mod *= mof_teamkill->value; else mod *= mof_enemy->value; } /* seeing a civilian die is more "acceptable" */ if (G_IsCivilian(victim)) mod *= mof_civilian->value; /* if an ally (or in singleplayermode, as human, a civilian) got shot, lower the morale, don't heighten it. */ if (victim->isSameTeamAs(actor) || (G_IsCivilian(victim) && !G_IsAlien(actor) && G_IsSinglePlayer())) mod *= -1; if (attacker != nullptr) { /* if you stand near to the attacker or the victim, the morale change is higher. */ mod *= mor_default->value + pow(0.5f, VectorDist(actor->origin, victim->origin) / mor_distance->value) * mor_victim->value + pow(0.5f, VectorDist(actor->origin, attacker->origin) / mor_distance->value) * mor_attacker->value; } else { mod *= mor_default->value + pow(0.5f, VectorDist(actor->origin, victim->origin) / mor_distance->value) * mor_victim->value; } /* morale damage depends on the number of living allies */ mod *= (1 - mon_teamfactor->value) + mon_teamfactor->value * (level.num_spawned[victim->getTeam()] + 1) / (level.num_alive[victim->getTeam()] + 1); /* being hit isn't fun */ if (actor == victim) mod *= mor_pain->value; /* clamp new morale */ /*+0.9 to allow weapons like flamethrowers to inflict panic (typecast rounding) */ const int newMorale = actor->morale + (int) (MORALE_RANDOM(mod) + 0.9); if (newMorale > GET_MORALE(actor->chr.score.skills[ABILITY_MIND])) actor->setMorale(GET_MORALE(actor->chr.score.skills[ABILITY_MIND])); else if (newMorale < 0) actor->setMorale(0); else actor->setMorale(newMorale); /* send phys data */ G_SendStats(*actor); } }
/** * @brief Deals damage of a give type and amount to a target. * @param[in,out] target What we want to damage. * @param[in] fd The fire definition that defines what type of damage is dealt. * @param[in] damage The value of the damage. * @param[in] attacker The attacker. * @param[in] mock pseudo shooting - only for calculating mock values - nullptr for real shots * @param[in] impact impact location - @c nullptr for splash damage * @return @c true if damage could be dealt (even if it was 0) @c false otherwise * @sa G_SplashDamage * @sa G_TakeDamage * @sa G_PrintActorStats */ static bool G_Damage (Edict* target, const fireDef_t* fd, int damage, Actor* attacker, shot_mock_t* mock, const vec3_t impact) { assert(target); const bool stunEl = (fd->obj->dmgtype == gi.csi->damStunElectro); const bool stunGas = (fd->obj->dmgtype == gi.csi->damStunGas); const bool shock = (fd->obj->dmgtype == gi.csi->damShock); const bool smoke = (fd->obj->dmgtype == gi.csi->damSmoke); /* Breakables */ if (G_IsBrushModel(target) && G_IsBreakable(target)) { /* Breakables are immune to stun & shock damage. */ if (stunEl || stunGas || shock || mock || smoke) return false; if (damage >= target->HP) { /* don't reset the HP value here, this value is used to distinguish * between triggered destroy and a shoot */ assert(target->destroy); target->destroy(target); /* maybe the attacker is seeing something new? */ G_CheckVisTeamAll(attacker->getTeam(), 0, attacker); /* check if attacker appears/perishes for any other team */ G_CheckVis(attacker); } else { G_TakeDamage(target, damage); } return true; } /* Actors don't die again. */ if (!G_IsLivingActor(target)) return false; /* Now we know that the target is an actor */ Actor* victim = makeActor(target); /* only actors after this point - and they must have a teamdef */ assert(victim->chr.teamDef); const bool isRobot = CHRSH_IsTeamDefRobot(victim->chr.teamDef); /* Apply armour effects. */ if (damage > 0) { damage = G_ApplyProtection(victim, fd->dmgweight, damage); } else if (damage < 0) { /* Robots can't be healed. */ if (isRobot) return false; } Com_DPrintf(DEBUG_GAME, " Total damage: %d\n", damage); /* Apply difficulty settings. */ if (G_IsSinglePlayer()) { if (G_IsAlien(attacker) && !G_IsAlien(victim)) damage *= pow(1.18f, g_difficulty->value); else if (!G_IsAlien(attacker) && G_IsAlien(victim)) damage *= pow(1.18f, -g_difficulty->value); } assert(attacker->getTeam() >= 0 && attacker->getTeam() < MAX_TEAMS); assert(victim->getTeam() >= 0 && victim->getTeam() < MAX_TEAMS); if ((g_nodamage != nullptr && !g_nodamage->integer) || mock) { /* hit */ if (mock) { G_UpdateShotMock(mock, attacker, victim, damage); } else if (stunEl) { victim->addStun(damage); } else if (stunGas) { if (!isRobot) /* Can't stun robots with gas */ victim->addStun(damage); } else if (shock) { /* Only do this if it's not one from our own team ... they should have known that there was a flashbang coming. */ if (!isRobot && !victim->isSameTeamAs(attacker)) { /** @todo there should be a possible protection, too */ /* dazed entity wont reaction fire */ victim->removeReaction(); G_ActorReserveTUs(victim, 0, victim->chr.reservedTus.shot, victim->chr.reservedTus.crouch); /* flashbangs kill TUs */ G_ActorSetTU(victim, 0); G_SendStats(*victim); /* entity is dazed */ victim->setDazed(); G_EventSendState(G_VisToPM(victim->visflags), *victim); return !mock; } else { return false; } } else { if (damage < 0) { /* The 'attacker' is healing the victim. */ G_TreatActor(victim, fd, damage, attacker->getTeam()); } else { /* Real damage was dealt. */ G_DamageActor(victim, damage, impact); /* Update overall splash damage for stats/score. */ if (!mock && damage > 0 && fd->splrad) /**< Check for >0 and splrad to not count this as direct hit. */ G_UpdateHitScore(attacker, victim, fd, damage); } } } if (mock) return false; G_CheckDeathOrKnockout(victim, attacker, fd, damage); return true; }