/** * @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* 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; } }
/** * @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 Check whether shooter can see his target well enough * @param[in] shooter The entity that might be firing * @param[in] target The entity that might be fired at */ bool ReactionFire::canSee (const Actor* shooter, const Edict* target) const { if (!G_IsVisibleForTeam(target, shooter->getTeam())) return false; /* check in range and visible */ const int spotDist = G_VisCheckDist(shooter); if (VectorDistSqr(shooter->origin, target->origin) > spotDist * spotDist) return false; const bool frustum = G_FrustumVis(shooter, target->origin); if (!frustum) return false; const float actorVis = G_ActorVis(shooter->origin, shooter, target, true); if (actorVis < 0.1) return false; return true; }
/** * @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); } } }
/** * @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"); }
/** * @brief Moves the actor into a position in which he can shoot his target. */ static int AIL_positionshoot (lua_State *L) { pos3_t to, bestPos; vec3_t check; edict_t *ent; int dist; int xl, yl, xh, yh; int min_tu; aiActor_t *target; const byte crouchingState = G_IsCrouched(AIL_ent) ? 1 : 0; /* We need a target. */ assert(lua_isactor(L, 1)); target = lua_toactor(L, 1); /* Make things more simple. */ ent = AIL_ent; dist = G_ActorUsableTUs(ent); /* Calculate move table. */ G_MoveCalc(0, ent, ent->pos, crouchingState, G_ActorUsableTUs(ent)); gi.MoveStore(level.pathingMap); /* set borders */ xl = (int) ent->pos[0] - dist; if (xl < 0) xl = 0; yl = (int) ent->pos[1] - dist; if (yl < 0) yl = 0; xh = (int) ent->pos[0] + dist; if (xh > PATHFINDING_WIDTH) xl = PATHFINDING_WIDTH; yh = (int) ent->pos[1] + dist; if (yh > PATHFINDING_WIDTH) yh = PATHFINDING_WIDTH; /* evaluate moving to every possible location in the search area, * including combat considerations */ min_tu = INT_MAX; for (to[2] = 0; to[2] < PATHFINDING_HEIGHT; to[2]++) for (to[1] = yl; to[1] < yh; to[1]++) for (to[0] = xl; to[0] < xh; to[0]++) { pos_t tu; /* Can we see the target? */ gi.GridPosToVec(gi.routingMap, ent->fieldSize, to, check); tu = G_ActorMoveLength(ent, level.pathingMap, to, true); if (tu > G_ActorUsableTUs(ent) || tu == ROUTING_NOT_REACHABLE) continue; /* Better spot (easier to get to). */ if (tu < min_tu) { if (G_ActorVis(check, ent, target->ent, true) > 0.3) { VectorCopy(to, bestPos); min_tu = tu; } } } /* No position found in range. */ if (min_tu > G_ActorUsableTUs(ent)) { lua_pushboolean(L, 0); return 1; } /* Return the spot. */ lua_pushpos3(L, &bestPos); return 1; }
/** * @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); } }