Esempio n. 1
0
/**
 * @brief Check the world against triggers for the current entity
 * @param[in,out] ent The entity that maybe touches others
 * @return Returns the number of associated client actions
 */
int G_TouchTriggers (edict_t *ent)
{
    int i, num, usedNum = 0;
    edict_t *touch[MAX_EDICTS];

    if (!G_IsLivingActor(ent))
        return 0;

    num = gi.TouchEdicts(ent->absmin, ent->absmax, touch, lengthof(touch), ent);

    G_ResetTriggers(ent, touch, num);

    /* be careful, it is possible to have an entity in this
     * list removed before we get to it (killtriggered) */
    for (i = 0; i < num; i++) {
        edict_t *hit = touch[i];
        if (hit->solid != SOLID_TRIGGER)
            continue;
        if (!hit->touch)
            continue;
        if (hit->touch(hit, ent))
            usedNum++;
        /* now after the use function was executed, we can add the ent to
         * the touched list of the trigger. We do this because we want to be
         * able to check whether another call changes the triggered state for
         * the added entity. We have to do this after the use function was
         * called, because there are triggers that may only be triggered once
         * if someone touches it. */
        G_TriggerAddToList(hit, ent);
    }
    return usedNum;
}
Esempio n. 2
0
/**
 * @brief Checks whether the actor is allowed to activate reaction fire and will informs the player about
 * the reason if this would not work.
 * @param[in] ent The actor to check
 * @return @c true if the actor is allowed to activate it, @c false otherwise
 */
static bool G_ReactionFireCanBeEnabled (const edict_t *ent)
{
	/* check ent is a suitable shooter */
	if (!ent->inuse || !G_IsLivingActor(ent))
		return false;

	if (G_MatchIsRunning() && ent->team != level.activeTeam)
		return false;

	/* actor may not carry weapons at all - so no further checking is needed */
	if (!ent->chr.teamDef->weapons)
		return false;

	if (!G_ActorHasReactionFireEnabledWeapon(ent)) {
		G_ClientPrintf(G_PLAYER_FROM_ENT(ent), PRINT_HUD, _("No reaction fire enabled weapon."));
		return false;
	}

	if (!G_ActorHasWorkingFireModeSet(ent)) {
		G_ClientPrintf(G_PLAYER_FROM_ENT(ent), PRINT_HUD, _("No fire mode selected for reaction fire."));
		return false;
	}

	if (!G_ActorHasEnoughTUsReactionFire(ent)) {
		G_ClientPrintf(G_PLAYER_FROM_ENT(ent), PRINT_HUD, _("Not enough TUs left for activating reaction fire."));
		return false;
	}

	return true;
}
Esempio n. 3
0
/**
 * @brief test if check is visible by from
 * @param[in] team Living team members are always visible. If this is a negative
 * number we inverse the team rules (see comments included). In combination with VT_NOFRUSTUM
 * we can check whether there is any edict (that is no in our team) that can see @c check
 * @param[in] from is from team @c team and must be a living actor
 * @param[in] check The edict we want to get the visibility for
 * @param[in] flags @c VT_NOFRUSTUM, ...
 */
bool G_Vis (const int team, const Edict* from, const Edict* check, const vischeckflags_t flags)
{
	vec3_t eye;

	/* if any of them isn't in use, then they're not visible */
	if (!from->inuse || !check->inuse)
		return false;

	/* only actors and 2x2 units can see anything */
	if (!G_IsLivingActor(from) && !G_IsActiveCamera(from))
		return false;

	/* living team members are always visible */
	if (team >= 0 && check->getTeam() == team && !G_IsDead(check))
		return true;

	/* standard team rules */
	if (team >= 0 && from->getTeam() != team)
		return false;

	/* inverse team rules */
	if (team < 0 && check->getTeam() == -team)
		return false;

	/* check for same pos */
	if (VectorCompare(from->pos, check->pos))
		return true;

	if (!G_IsVisibleOnBattlefield(check))
		return false;

	/* view distance check */
	const int spotDist = G_VisCheckDist(from);
	if (VectorDistSqr(from->origin, check->origin) > spotDist * spotDist)
		return false;

	/* view frustum check */
	if (!(flags & VT_NOFRUSTUM) && !G_FrustumVis(from, check->origin))
		return false;

	/* get viewers eye height */
	G_ActorGetEyeVector(from, eye);

	/* line trace check */
	switch (check->type) {
	case ET_ACTOR:
	case ET_ACTOR2x2:
		return G_ActorVis(eye, from, check, false) > ACTOR_VIS_0;
	case ET_ITEM:
	case ET_CAMERA:
	case ET_PARTICLE:
		return !G_LineVis(eye, check->origin);
	default:
		return false;
	}
}
Esempio n. 4
0
/**
 * @brief Iterate through the living actor entities
 * @param lastEnt The entity found in the previous iteration; if nullptr, we start at the beginning
 */
Edict* G_EdictsGetNextLivingActor (Edict* lastEnt)
{
	Edict* ent = lastEnt;

	while ((ent = G_EdictsGetNextInUse(ent))) {
		if (G_IsLivingActor(ent))
			break;
	}
	return ent;
}
Esempio n. 5
0
bool G_IsActorWounded (const Edict* ent)
{
	if (ent == nullptr || !G_IsLivingActor(ent) || ent->chr.teamDef == nullptr)
		return false;

	for (int i = 0; i < ent->chr.teamDef->bodyTemplate->numBodyParts(); ++i)
		if (ent->chr.wounds.woundLevel[i] > 0)
			return true;

	return false;
}
Esempio n. 6
0
bool G_IsActorWounded (const Edict* ent, bool serious)
{
	if (ent == nullptr || !G_IsLivingActor(ent) || ent->chr.teamDef == nullptr)
		return false;
	const character_t& chr = ent->chr;
	const BodyData* bodyTmp = chr.teamDef->bodyTemplate;
	for (int i = 0; i < bodyTmp->numBodyParts(); ++i)
		if (chr.wounds.woundLevel[i] > serious ? chr.maxHP * bodyTmp->woundThreshold(i) : 0)
			return true;

	return false;
}
Esempio n. 7
0
/**
 * @brief Iterate through the living actor entities
 * @param lastEnt The entity found in the previous iteration; if nullptr, we start at the beginning
 */
Actor* G_EdictsGetNextLivingActor (Actor* lastEnt)
{
	Edict* ent = lastEnt;

	while ((ent = G_EdictsGetNextInUse(ent))) {
		if (G_IsLivingActor(ent)) {
			Actor* actor = static_cast<Actor*>(ent);
			if (actor)
				return actor;
			Sys_Error("dynamic_cast to Actor failed.");
		}
	}
	return nullptr;
}
Esempio n. 8
0
/**
 * @brief Hurt trigger
 * @sa SP_trigger_hurt
 */
bool Touch_HurtTrigger (Edict* self, Edict* activator)
{
	/* Dead actors should really not be able to trigger this - they can't be hurt anymore anyway */
	if (!G_IsLivingActor(activator))
		return false;

	/* If no damage is dealt don't count it as triggered */
	const int damage = G_ApplyProtection(activator, self->dmgtype, self->dmg);
	if (damage == 0)
		return false;

	const bool stunEl = (self->dmgtype == gi.csi->damStunElectro);
	const bool stunGas = (self->dmgtype == gi.csi->damStunGas);
	const bool shock = (self->dmgtype == gi.csi->damShock);
	const bool isRobot = activator->chr.teamDef->robot;
	Actor* actor = makeActor(activator);

	if (stunEl || (stunGas && !isRobot)) {
		actor->addStun(damage);
	} else if (shock) {
		/** @todo Handle dazed via trigger_hurt */
	} else {
		G_TakeDamage(actor, damage);
	}
	/* Play hurt sound unless this is shock damage -- it doesn't do anything
	 * because we don't actually handle it above yet */
	if (!shock) {
		const teamDef_t* teamDef = activator->chr.teamDef;
		const int gender = activator->chr.gender;
		const char* sound = teamDef->getActorSound(gender, SND_HURT);
		G_EventSpawnSound(G_PlayerToPM(activator->getPlayer()), *activator, nullptr, sound);
	}

	G_CheckDeathOrKnockout(actor, nullptr, nullptr, damage);

	return true;
}
Esempio n. 9
0
/**
 * @brief Mission trigger
 * @todo use level.nextmap to spawn another map when every living actor has touched the mission trigger
 * @note Don't set a client action here - otherwise the movement event might
 * be corrupted
 */
bool G_MissionTouch (Edict* self, Edict* activator)
{
	if (!G_IsLivingActor(activator))
		return false;

	Actor* actor = makeActor(activator);
	const char* const actorTeam = G_MissionGetTeamString(actor->getTeam());
	if (!G_IsCivilian(actor) && self->isOpponent(actor)) {
		if (!self->item && self->count) {
			if (self->targetname) {
				gi.BroadcastPrintf(PRINT_HUD, _("%s forces are attacking the %s!"), actorTeam, self->targetname);
			} else {
				const char* const teamName = G_MissionGetTeamString(self->getTeam());
				gi.BroadcastPrintf(PRINT_HUD, _("%s forces are attacking %s target zone!"),
						actorTeam, teamName);
			}

			/* reset king of the hill counter */
			self->count = 0;
		}
		return false;
	}
	if (self->count)
		return false;

	if (self->isSameTeamAs(actor)) {
		self->count = level.actualRound;
		if (!self->item) {
			linkedList_t* touched = self->touchedList;
			while (touched) {
				const Edict* const ent = static_cast<const Edict* const>(touched->data);
				if (!self->isSameTeamAs(ent) && !G_IsDead(ent)) {
					return true;
				}
				touched = touched->next;
			}
			if (self->targetname) {
				gi.BroadcastPrintf(PRINT_HUD, _("%s forces have occupied the %s!"), actorTeam, self->targetname);
			} else {
				gi.BroadcastPrintf(PRINT_HUD, _("%s forces have occupied their target zone!"), actorTeam);
			}
			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 = actor->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->item))
				continue;

			/* drop the weapon - even if out of TUs */
			G_ActorInvMove(actor, cont->def(), item, INVDEF(CID_FLOOR), NONE, NONE, false);
			if (self->targetname) {
				gi.BroadcastPrintf(PRINT_HUD, _("The %s was placed at the %s."), item->def()->name, self->targetname);
			} else {
				gi.BroadcastPrintf(PRINT_HUD, _("The %s was placed."), item->def()->name);
			}
			self->count = level.actualRound;
			return true;
		}
	}

	return false;
}
Esempio n. 10
0
/**
 * @brief Opens/closes a door
 * @note Use function for func_door
 * @todo Check if the door can be opened or closed - there should not be
 * anything in the way (e.g. an actor)
 */
static bool Door_Use (edict_t *door, edict_t *activator)
{
    if (door->doorState == STATE_CLOSED) {
        door->doorState = STATE_OPENED;

        /* change rotation/origin and relink */
        if (door->type == ET_DOOR) {
            if (door->dir & DOOR_OPEN_REVERSE)
                door->angles[door->dir & 3] -= DOOR_ROTATION_ANGLE;
            else
                door->angles[door->dir & 3] += DOOR_ROTATION_ANGLE;
        } else if (door->type == ET_DOOR_SLIDING) {
            Door_SlidingUse(door);
        }
        gi.LinkEdict(door);

        /* maybe the server called this because the door starts opened */
        if (G_MatchIsRunning()) {
            /* let everybody know, that the door opens */
            G_EventDoorOpen(door);
            if (door->noise[0] != '\0')
                G_EventSpawnSound(PM_ALL, false, door, door->origin, door->noise);
        }
    } else if (door->doorState == STATE_OPENED) {
        door->doorState = STATE_CLOSED;

        /* change rotation and relink */
        if (door->type == ET_DOOR) {
            if (door->dir & DOOR_OPEN_REVERSE)
                door->angles[door->dir & 3] += DOOR_ROTATION_ANGLE;
            else
                door->angles[door->dir & 3] -= DOOR_ROTATION_ANGLE;
        } else if (door->type == ET_DOOR_SLIDING) {
            Door_SlidingUse(door);
        }
        gi.LinkEdict(door);

        /* closed is the standard, opened is handled above - we need an active
         * team here already */
        if (G_MatchIsRunning()) {
            /* let everybody know, that the door closes */
            G_EventDoorClose(door);
            if (door->noise[0] != '\0')
                G_EventSpawnSound(PM_ALL, false, door, door->origin, door->noise);
        }
    } else
        return false;

    /* Update model orientation */
    gi.SetInlineModelOrientation(door->model, door->origin, door->angles);
    Com_DPrintf(DEBUG_GAME, "Server processed door movement.\n");

    /* Update path finding table */
    G_RecalcRouting(door->model);

    if (activator && G_IsLivingActor(activator)) {
        /* Check if the player appears/perishes, seen from other teams. */
        G_CheckVis(activator, true);

        /* Calc new vis for the activator. */
        G_CheckVisTeamAll(activator->team, false, activator);
    }

    return true;
}
Esempio n. 11
0
/**
 * @brief Deals splash damage to a target and its surroundings.
 * @param[in] ent The shooting actor
 * @param[in] fd The fire definition that defines what type of damage is dealt and how big the splash radius is.
 * @param[in] impact The impact vector where the grenade is exploding
 * @param[in,out] mock pseudo shooting - only for calculating mock values - NULL for real shots
 * @param[in] tr The trace where the grenade hits something (or not)
 */
static void G_SplashDamage (edict_t *ent, const fireDef_t *fd, vec3_t impact, shot_mock_t *mock, const trace_t* tr)
{
	edict_t *check = NULL;
	vec3_t center;
	float dist;
	int damage;

	const bool shock = (fd->obj->dmgtype == gi.csi->damShock);

	assert(fd->splrad > 0.0);

	while ((check = G_EdictsGetNextInUse(check))) {
		/* If we use a blinding weapon we skip the target if it's looking
		 * away from the impact location. */
		if (shock && !G_FrustumVis(check, impact))
			continue;

		if (G_IsBrushModel(check) && G_IsBreakable(check))
			VectorCenterFromMinsMaxs(check->absmin, check->absmax, center);
		else if (G_IsLivingActor(check) || G_IsBreakable(check))
			VectorCopy(check->origin, center);
		else
			continue;

		/* check for distance */
		dist = VectorDist(impact, center);
		dist = dist > UNIT_SIZE / 2 ? dist - UNIT_SIZE / 2 : 0;
		if (dist > fd->splrad)
			continue;

		if (fd->irgoggles) {
			if (G_IsActor(check)) {
				/* check whether this actor (check) is in the field of view of the 'shooter' (ent) */
				if (G_FrustumVis(ent, check->origin)) {
					if (!mock) {
						const unsigned int playerMask = G_TeamToPM(ent->team) ^ G_VisToPM(check->visflags);
						G_AppearPerishEvent(playerMask, true, check, ent);
						G_VisFlagsAdd(check, G_PMToVis(playerMask));
					}
				}
			}
			continue;
		}

		/* check for walls */
		if (G_IsLivingActor(check) && !G_ActorVis(impact, ent, check, false))
			continue;

		/* do damage */
		if (shock)
			damage = 0;
		else
			damage = fd->spldmg[0] * (1.0 - dist / fd->splrad);

		if (mock)
			mock->allow_self = true;
		G_Damage(check, fd, damage, ent, mock, NULL);
		if (mock)
			mock->allow_self = false;
	}

	/** @todo splash might also hit other surfaces and the trace doesn't handle that */
	if (tr && G_FireAffectedSurface(tr->surface, fd)) {
		/* move a little away from the impact vector */
		VectorMA(impact, 1, tr->plane.normal, impact);
		G_SpawnParticle(impact, tr->contentFlags >> 8, "burning");
	}
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
static bool G_VisShouldStop (const edict_t *ent)
{
	return G_IsLivingActor(ent) && !G_IsCivilian(ent);
}
Esempio n. 14
0
/**
 * @brief Opens/closes a door
 * @note Use function for func_door
 * @todo Check if the door can be opened or closed - there should not be
 * anything in the way (e.g. an actor)
 */
static bool Door_Use (edict_t *door, edict_t *activator)
{
	int opening = 1;

	if (door->doorState == STATE_CLOSED) {
		door->doorState = STATE_OPENED;
		opening = 1;
	} else if (door->doorState == STATE_OPENED) {
		door->doorState = STATE_CLOSED;
		opening = -1;
	} else
		return false;

	/* remember the old location */
	AABB oldAABB;
	gi.GetInlineModelAABB(door->model, oldAABB);
	GridBox rerouteOldBox(oldAABB);

	/* change rotation and relink */
	if (door->type == ET_DOOR) {
		if (door->dir & DOOR_OPEN_REVERSE)
			opening *= -1;
		door->angles[door->dir & 3] += DOOR_ROTATION_ANGLE * opening;
	} else if (door->type == ET_DOOR_SLIDING) {
		Door_SlidingUse(door);
	}
	gi.LinkEdict(door);

	/* maybe the server called this because the door starts opened */
	if (G_MatchIsRunning()) {
		/* let everybody know, that the door moves */
		if (door->doorState == STATE_OPENED)
			G_EventDoorOpen(door);
		else
			G_EventDoorClose(door);
		if (door->noise[0] != '\0') {
			const playermask_t playerMask = G_GetClosePlayerMask(door->origin, UNIT_SIZE * 10);
			G_EventSpawnSound(playerMask, false, door, door->origin, door->noise);
		}
	}

	/* Update model orientation */
	gi.SetInlineModelOrientation(door->model, door->origin, door->angles);
	AABB newAabb;
	gi.GetInlineModelAABB(door->model, newAabb);
	GridBox rerouteNewBox(newAabb);
	Com_DPrintf(DEBUG_GAME, "Server processed door movement.\n");

	/* Update path finding table for the new location of the model */
	G_RecalcRouting(door->model, rerouteOldBox);							/* Update path finding table */
	G_RecalcRouting(door->model, rerouteNewBox);

	if (activator && G_IsLivingActor(activator)) {
		/* Check if the player appears/perishes, seen from other teams. */
		G_CheckVis(activator);

		/* Calc new vis for the activator. */
		G_CheckVisTeamAll(activator->team, 0, activator);
	}

	return true;
}
Esempio n. 15
0
/**
 * @brief Deals splash damage to a target and its surroundings.
 * @param[in] ent The shooting actor
 * @param[in] fd The fire definition that defines what type of damage is dealt and how big the splash radius is.
 * @param[in] impact The impact vector where the grenade is exploding
 * @param[in,out] mock pseudo shooting - only for calculating mock values - nullptr for real shots
 * @param[in] tr The trace where the grenade hits something (or not)
 */
static void G_SplashDamage (Actor* ent, const fireDef_t* fd, vec3_t impact, shot_mock_t* mock, const trace_t* tr)
{
	assert(fd->splrad > 0.0f);

	const bool shock = (fd->obj->dmgtype == gi.csi->damShock);

	Edict* check = nullptr;
	while ((check = G_EdictsGetNextInUse(check))) {
		/* If we use a blinding weapon we skip the target if it's looking
		 * away from the impact location. */
		if (shock && !G_FrustumVis(check, impact))
			continue;

		const bool isActor = G_IsLivingActor(check);
		vec3_t center;
		if (G_IsBrushModel(check) && G_IsBreakable(check))
			check->absBox.getCenter(center);
		else if (isActor || G_IsBreakable(check))
			VectorCopy(check->origin, center);
		else
			continue;

		/* check for distance */
		float dist = VectorDist(impact, center);
		dist = dist > UNIT_SIZE / 2 ? dist - UNIT_SIZE / 2 : 0.0f;
		if (dist > fd->splrad)
			continue;

		if (fd->irgoggles) {
			if (isActor) {
				/* check whether this actor (check) is in the field of view of the 'shooter' (ent) */
				if (G_FrustumVis(ent, check->origin)) {
					if (!mock) {
						vec3_t eyeEnt;
						G_ActorGetEyeVector(ent, eyeEnt);
						if (!G_SmokeVis(eyeEnt, check)) {
							const unsigned int playerMask = G_TeamToPM(ent->getTeam()) ^ G_VisToPM(check->visflags);
							G_AppearPerishEvent(playerMask, true, *check, ent);
							G_VisFlagsAdd(*check, G_PMToVis(playerMask));
						}
					}
				}
			}
			continue;
		}

		/* check for walls */
		if (isActor && G_TestLine(impact, check->origin))
			continue;

		/* do damage */
		const int damage = shock ? 0 : fd->spldmg[0] * (1.0f - dist / fd->splrad);

		if (mock)
			mock->allow_self = true;
		/* Send hurt sounds for actors, but only if they'll recieve damage from this attack */
		if (G_Damage(check, fd, damage, ent, mock, nullptr) && isActor
				&& (G_ApplyProtection(check, fd->dmgweight,  damage) > 0) && !shock) {
			const teamDef_t* teamDef = check->chr.teamDef;
			const int gender = check->chr.gender;
			const char* sound = teamDef->getActorSound(gender, SND_HURT);
			G_EventSpawnSound(G_VisToPM(check->visflags), *check, nullptr, sound);
		}
		if (mock)
			mock->allow_self = false;
	}

	/** @todo splash might also hit other surfaces and the trace doesn't handle that */
	if (tr && G_FireAffectedSurface(tr->surface, fd)) {
		/* move a little away from the impact vector */
		VectorMA(impact, 1, tr->plane.normal, impact);
		G_SpawnParticle(impact, tr->contentFlags >> 8, "burning");
	}
Esempio n. 16
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;
}