/** * @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 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 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 Check whether ent can reaction fire at target, i.e. that it can see it and neither is dead etc. * @param[in] ent The entity that might be firing * @param[in] target The entity that might be fired at * @return @c true if 'ent' can actually fire at 'target', @c false otherwise */ static bool G_ReactionFireIsPossible (const edict_t *ent, const edict_t *target) { float actorVis; bool frustum; /* an entity can't reaction fire at itself */ if (ent == target) return false; /* Don't react in your own turn */ if (ent->team == level.activeTeam) return false; /* ent can't use RF if is in STATE_DAZED (flashbang impact) */ if (G_IsDazed(ent)) return false; if (G_IsDead(target)) return false; /* check ent has reaction fire enabled */ if (!G_IsShaken(ent) && !G_IsReaction(ent)) return false; /* check ent has weapon in RF hand */ /* @todo Should this situation even happen when G_IsReaction(ent) is true? */ if (!ACTOR_GET_INV(ent, ent->chr.RFmode.hand)) { /* print character info if this happens, for now */ gi.DPrintf("Reaction fire enabled but no weapon for hand (name=%s,hand=%i,fmIdx=%i)\n", ent->chr.name, ent->chr.RFmode.hand, ent->chr.RFmode.fmIdx); return false; } if (!G_IsVisibleForTeam(target, ent->team)) return false; /* If reaction fire is triggered by a friendly unit * and the shooter is still sane, don't shoot; * well, if the shooter isn't sane anymore... */ if (G_IsCivilian(target) || target->team == ent->team) if (!G_IsShaken(ent) || (float) ent->morale / mor_shaken->value > frand()) return false; /* check in range and visible */ if (VectorDistSqr(ent->origin, target->origin) > MAX_SPOT_DIST * MAX_SPOT_DIST) return false; frustum = G_FrustumVis(ent, target->origin); if (!frustum) return false; actorVis = G_ActorVis(ent->origin, ent, target, true); if (actorVis <= 0.2) return false; /* okay do it then */ return true; }
/** * @brief Check whether ent can reaction fire at target, i.e. that it can see it and neither is dead etc. * @param[in] ent The entity that might be firing * @param[in] target The entity that might be fired at * @return @c true if 'ent' can actually fire at 'target', @c false otherwise */ static bool G_ReactionFireIsPossible (Edict *ent, const Edict *target) { /* an entity can't reaction fire at itself */ if (ent == target) return false; /* Don't react in your own turn */ if (ent->team == level.activeTeam) return false; /* ent can't use RF if is in STATE_DAZED (flashbang impact) */ if (G_IsDazed(ent)) return false; if (G_IsDead(target)) return false; /* check ent has reaction fire enabled */ if (!G_IsReaction(ent)) return false; /* check ent has weapon in RF hand */ if (!ent->getHandItem(ent->chr.RFmode.getHand())) { /* print character info if this happens, for now */ gi.DPrintf("Reaction fire enabled but no weapon for hand (name=%s,entnum=%i,hand=%i,fmIdx=%i)\n", ent->chr.name, ent->number, ent->chr.RFmode.getHand(), ent->chr.RFmode.getFmIdx()); G_RemoveReaction(ent); return false; } if (!G_IsVisibleForTeam(target, ent->team)) return false; /* If reaction fire is triggered by a friendly unit * and the shooter is still sane, don't shoot; * well, if the shooter isn't sane anymore... */ if (G_IsCivilian(target) || target->team == ent->team) if (!G_IsShaken(ent) || (float) ent->morale / mor_shaken->value > frand()) return false; /* check in range and visible */ const int spotDist = G_VisCheckDist(ent); if (VectorDistSqr(ent->origin, target->origin) > spotDist * spotDist) return false; const bool frustum = G_FrustumVis(ent, target->origin); if (!frustum) return false; const float actorVis = G_ActorVis(ent->origin, ent, target, true); if (actorVis <= 0.2) return false; /* okay do it then */ return true; }
/** * @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_t *from, const edict_t *check, int 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)) return false; /* living team members are always visible */ if (team >= 0 && check->team == team && !G_IsDead(check)) return true; /* standard team rules */ if (team >= 0 && from->team != team) return false; /* inverse team rules */ if (team < 0 && (from->team == -team || G_IsCivilian(from) || check->team != -team)) return false; /* check for same pos */ if (VectorCompare(from->pos, check->pos)) return true; if (!G_IsVisibleOnBattlefield(check)) return false; /* view distance check */ if (VectorDistSqr(from->origin, check->origin) > MAX_SPOT_DIST * MAX_SPOT_DIST) 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_PARTICLE: return !G_LineVis(eye, check->origin); default: return false; } }
/** * @brief Check whether ent can reaction fire at target, i.e. that it can see it and neither is dead etc. * @param[in] ent The entity that might be firing * @param[in] target The entity that might be fired at * @return @c true if 'ent' can actually fire at 'target', @c false otherwise */ static qboolean G_ReactionFireIsPossible (const edict_t *ent, const edict_t *target) { float actorVis; qboolean frustum; /* an entity can't reaction fire at itself */ if (ent == target) return qfalse; /* Don't react in your own turn */ if (ent->team == level.activeTeam) return qfalse; /* ent can't use RF if is in STATE_DAZED (flashbang impact) */ if (G_IsDazed(ent)) return qfalse; if (G_IsDead(target)) return qfalse; /* check ent has reaction fire enabled */ if (!G_IsShaken(ent) && !G_IsReaction(ent)) return qfalse; if (!G_IsVisibleForTeam(target, ent->team)) return qfalse; /* If reaction fire is triggered by a friendly unit * and the shooter is still sane, don't shoot; * well, if the shooter isn't sane anymore... */ if (G_IsCivilian(target) || target->team == ent->team) if (!G_IsShaken(ent) || (float) ent->morale / mor_shaken->value > frand()) return qfalse; /* check in range and visible */ if (VectorDistSqr(ent->origin, target->origin) > MAX_SPOT_DIST * MAX_SPOT_DIST) return qfalse; frustum = G_FrustumVis(ent, target->origin); if (!frustum) return qfalse; actorVis = G_ActorVis(ent->origin, target, qtrue); if (actorVis <= 0.2) return qfalse; /* okay do it then */ return qtrue; }
/** * @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 Function to calculate possible damages for mock pseudoaction. * @param[in,out] mock Pseudo action - only for calculating mock values - NULL for real action. * @param[in] shooter Pointer to attacker for this mock pseudoaction. * @param[in] struck Pointer to victim of this mock pseudoaction. * @param[in] damage Updates mock value of damage. * @note Called only from G_Damage(). * @sa G_Damage */ static void G_UpdateShotMock (shot_mock_t *mock, const edict_t *shooter, const edict_t *struck, int damage) { assert(struck->number != shooter->number || mock->allow_self); if (damage > 0) { if (!struck->inuse || G_IsDead(struck)) return; else if (!G_IsVisibleForTeam(struck, shooter->team)) return; else if (G_IsCivilian(struck)) mock->civilian += 1; else if (struck->team == shooter->team) mock->friendCount += 1; else if (G_IsActor(struck)) mock->enemyCount += 1; else return; mock->damage += damage; } }
/** * @brief Check whether we want to shoot at the target. * @param[in] shooter The entity that might be firing * @param[in] target The entity that might be fired at */ bool ReactionFire::isEnemy (const Actor* shooter, const Edict* target) const { /* an entity can't reaction fire at itself */ if (shooter == target) return false; /* Don't react in your own turn */ if (shooter->getTeam() == level.activeTeam) return false; if (G_IsDead(target)) return false; /* If reaction fire is triggered by a friendly unit * and the shooter is still sane, don't shoot; * well, if the shooter isn't sane anymore... */ if (G_IsCivilian(target) || target->isSameTeamAs(shooter)) if (!shooter->isShaken() || (float) shooter->morale / mor_shaken->value > frand()) return false; return true; }
/** * @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 Function to calculate possible damages for mock pseudoaction. * @param[in,out] mock Pseudo action - only for calculating mock values - nullptr for real action. * @param[in] shooter Pointer to attacker for this mock pseudoaction. * @param[in] struck Pointer to victim of this mock pseudoaction. * @param[in] damage Updates mock value of damage. * @note Called only from G_Damage(). * @sa G_Damage */ static void G_UpdateShotMock (shot_mock_t* mock, const Edict* shooter, const Edict* struck, int damage) { assert(!struck->isSameAs(shooter) || mock->allow_self); if (damage <= 0) return; if (!struck->inuse || G_IsDead(struck)) return; if (!G_IsAI(shooter) && !G_IsVisibleForTeam(struck, shooter->getTeam())) return; if (G_IsCivilian(struck)) mock->civilian += 1; else if (struck->isSameTeamAs(shooter)) mock->friendCount += 1; else if (G_IsActor(struck)) mock->enemyCount += 1; else return; mock->damage += damage; }
/** * @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 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; }
static bool G_VisShouldStop (const edict_t *ent) { return G_IsLivingActor(ent) && !G_IsCivilian(ent); }
/** * @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 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 (int type, const edict_t * victim, const edict_t * attacker, int param) { edict_t *ent = NULL; int newMorale; float mod; while ((ent = G_EdictsGetNextInUse(ent))) { /* this only applies to ET_ACTOR but not ET_ACTOR2x2 */ if (ent->type == ET_ACTOR && !G_IsDead(ent) && ent->team != TEAM_CIVILIAN) { switch (type) { case ML_WOUND: case ML_DEATH: /* morale damage depends on the damage */ mod = mob_wound->value * param; /* 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 (ent == victim || (G_FrustumVis(ent, victim->origin) && G_ActorVis(ent->origin, ent, victim, false))) mod *= mof_watching->value; if (attacker != NULL && ent->team == attacker->team) { /* 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->team == attacker->team) 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->team == ent->team || (G_IsCivilian(victim) && ent->team != TEAM_ALIEN && sv_maxclients->integer == 1)) mod *= -1; if (attacker != NULL) { /* if you stand near to the attacker or the victim, the morale change is higher. */ mod *= mor_default->value + pow(0.5, VectorDist(ent->origin, victim->origin) / mor_distance->value) * mor_victim->value + pow(0.5, VectorDist(ent->origin, attacker->origin) / mor_distance->value) * mor_attacker->value; } else { mod *= mor_default->value + pow(0.5, VectorDist(ent->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->team] + 1) / (level.num_alive[victim->team] + 1); /* being hit isn't fun */ if (ent == victim) mod *= mor_pain->value; break; default: gi.DPrintf("Undefined morale modifier type %i\n", type); mod = 0; break; } /* clamp new morale */ /*+0.9 to allow weapons like flamethrowers to inflict panic (typecast rounding) */ newMorale = ent->morale + (int) (MORALE_RANDOM(mod) + 0.9); if (newMorale > GET_MORALE(ent->chr.score.skills[ABILITY_MIND])) ent->morale = GET_MORALE(ent->chr.score.skills[ABILITY_MIND]); else if (newMorale < 0) ent->morale = 0; else ent->morale = newMorale; /* send phys data */ G_SendStats(ent); } } }