Esempio n. 1
0
/**
 * @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);
	}
}
Esempio n. 2
0
/**
 * @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;
}
Esempio n. 3
0
/**
 * @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;
}
Esempio n. 4
0
/**
 * @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;
}
Esempio n. 5
0
/**
 * @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
	}
}
Esempio n. 6
0
/**
 * @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;
}
Esempio n. 7
0
/**
 * @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;
}
Esempio n. 8
0
/**
 * @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;
}
Esempio n. 9
0
/**
 * @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
}
Esempio n. 10
0
/**
 * @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
		}
	}
}
Esempio n. 11
0
/**
 * @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
}
Esempio n. 12
0
/**
 * @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);
}
Esempio n. 13
0
/**
 * @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);
	}
}
Esempio n. 14
0
/**
 * @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;
}