/** * @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; }
/** * @sa G_MoraleStopPanic * @sa G_MoraleRage * @sa G_MoraleStopRage * @sa G_MoraleBehaviour */ static void G_MoralePanic (edict_t * ent, qboolean sanity) { G_ClientPrintf(G_PLAYER_FROM_ENT(ent), PRINT_HUD, _("%s panics!\n"), ent->chr.name); /* drop items in hands */ if (!sanity && ent->chr.teamDef->weapons) { if (RIGHT(ent)) G_ActorInvMove(ent, INVDEF(gi.csi->idRight), RIGHT(ent), INVDEF(gi.csi->idFloor), NONE, NONE, qtrue); if (LEFT(ent)) G_ActorInvMove(ent, INVDEF(gi.csi->idLeft), LEFT(ent), INVDEF(gi.csi->idFloor), NONE, NONE, qtrue); } /* get up */ G_RemoveCrouched(ent); G_ActorSetMaxs(ent); /* send panic */ G_SetPanic(ent); G_EventSendState(G_VisToPM(ent->visflags), ent); /* center view */ G_EventCenterView(ent); /* move around a bit, try to avoid opponents */ AI_ActorThink(G_PLAYER_FROM_ENT(ent), ent); /* kill TUs */ G_ActorSetTU(ent, 0); }
/** * @brief Applies morale behaviour on actors * @note only called when mor_panic is not zero * @sa G_MoralePanic * @sa G_MoraleRage * @sa G_MoraleStopRage * @sa G_MoraleStopPanic */ void G_MoraleBehaviour (int team) { bool enabled = G_IsMoraleEnabled(team); if (!enabled) return; edict_t *ent = NULL; while ((ent = G_EdictsGetNextLivingActorOfTeam(ent, team)) != NULL) { /* this only applies to ET_ACTOR but not to ET_ACTOR2x2 */ if (ent->type != ET_ACTOR || CHRSH_IsTeamDefRobot(ent->chr.teamDef)) continue; /* if panic, determine what kind of panic happens: */ if (!G_IsPanicked(ent) && !G_IsRaged(ent)) { if (ent->morale <= mor_panic->integer) { const float ratio = (float) ent->morale / mor_panic->value; const bool sanity = ratio > (m_sanity->value * frand()); if (ratio > (m_rage->value * frand())) G_MoralePanic(ent, sanity); else G_MoraleRage(ent, sanity); /* if shaken, well .. be shaken; */ } else if (ent->morale <= mor_shaken->integer) { /* shaken is later reset along with reaction fire */ G_SetShaken(ent); G_ClientStateChange(G_PLAYER_FROM_ENT(ent), ent, STATE_REACTION, false); G_EventSendState(G_VisToPM(ent->visflags), ent); G_ClientPrintf(G_PLAYER_FROM_ENT(ent), PRINT_HUD, _("%s is currently shaken."), ent->chr.name); G_PrintStats("%s is shaken (entnum %i).", ent->chr.name, ent->number); } } else { if (G_IsPanicked(ent)) G_MoraleStopPanic(ent); else if (G_IsRaged(ent)) G_MoraleStopRage(ent); } G_ActorSetMaxs(ent); /* morale-regeneration, capped at max: */ int newMorale = ent->morale + MORALE_RANDOM(mor_regeneration->value); const int maxMorale = GET_MORALE(ent->chr.score.skills[ABILITY_MIND]); if (newMorale > maxMorale) ent->morale = maxMorale; else ent->morale = newMorale; /* send phys data and state: */ G_SendStats(ent); } }
/** * @brief Get the weapon firing TUs of the item in the right hand of the edict. * @return -1 if no firedef was found for the item or the reaction fire mode is not activated for the right hand. * @todo why only right hand? * @param[in] ent The reaction firing actor * @param[in] target The target to check reaction fire for (e.g. check whether the weapon that was marked for * using in reaction fire situations can handle the distance between the shooter and the target) * @param[in] invList The items that are checked for reaction fire * @note This does 'not' return the weapon (lowest TU costs, highest damage, highest accuracy) but the first weapon that * would fit for reaction fire. */ static int G_ReactionFireGetTUsForItem (const edict_t *ent, const edict_t *target, const invList_t *invList) { if (invList && invList->item.m && invList->item.t->weapon && (!invList->item.t->reload || invList->item.a > 0)) { const fireDef_t *fdArray = FIRESH_FiredefForWeapon(&invList->item); const chrFiremodeSettings_t *fmSetting; if (fdArray == NULL) return -1; fmSetting = &ent->chr.RFmode; if (fmSetting->hand == ACTOR_HAND_RIGHT && fmSetting->fmIdx >= 0 && fmSetting->fmIdx < MAX_FIREDEFS_PER_WEAPON) { /* If a RIGHT-hand firemode is selected and sane. */ const fireDefIndex_t fmIdx = fmSetting->fmIdx; const int reactionFire = G_PLAYER_FROM_ENT(ent)->reactionLeftover; const fireDef_t *fd = &fdArray[fmIdx]; const int tus = fd->time + reactionFire; if (tus <= ent->TU && fd->range > VectorDist(ent->origin, target->origin)) { return tus; } } } return -1; }
/** * @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 */ static bool G_ReactionFireShoot (edict_t *shooter, const pos3_t at, shoot_types_t type, fireDefIndex_t firemode) { const int minhit = 30; shot_mock_t mock; int i; const player_t *player = G_PLAYER_FROM_ENT(shooter); /* 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; 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, NULL, false, 0); return false; }
/** * @brief Resolve the reaction fire for an entity, this checks that the entity can fire and then takes the shot * @param[in] ent The entity to resolve reaction fire for * @return true if the entity fired (or would have fired if mock), false otherwise */ static qboolean G_ReactionFireTryToShoot (edict_t *ent) { qboolean tookShot; /* check whether this ent has a reaction fire queued */ assert(ent->reactionTarget); /* ent can't take a reaction shot if it's not possible - and check that * the target is still alive */ if (!G_ReactionFireIsPossible(ent, ent->reactionTarget)) { ent->reactionTarget = NULL; return qfalse; } /* take the shot */ tookShot = G_ReactionFireShoot(G_PLAYER_FROM_ENT(ent), ent, ent->reactionTarget->pos, ST_RIGHT_REACTION, ent->chr.RFmode.fmIdx); if (tookShot) { /* clear any shakenness */ G_RemoveShaken(ent); /* check whether further reaction fire is possible */ if (G_ReactionFireIsPossible(ent, ent->reactionTarget)){ /* see how quickly ent can fire (if it can fire at all) */ const int tus = G_ReactionFireGetTUsForItem(ent, ent->reactionTarget, RIGHT(ent)); if (tus >= 0) { /* An enemy getting reaction shot gets more time before * reaction fire is repeated. */ ent->reactionTUs = max(0, ent->reactionTarget->TU - tus); } } } return tookShot; }
/** * @brief Applies morale behaviour on actors * @note only called when mor_panic is not zero * @sa G_MoralePanic * @sa G_MoraleRage * @sa G_MoraleStopRage * @sa G_MoraleStopPanic */ void G_MoraleBehaviour (int team) { edict_t *ent = NULL; int newMorale; while ((ent = G_EdictsGetNextInUse(ent))) { /* this only applies to ET_ACTOR but not to ET_ACTOR2x2 */ if (ent->type == ET_ACTOR && ent->team == team && !G_IsDead(ent)) { /* civilians have a 1:1 chance to randomly run away in multiplayer */ if (sv_maxclients->integer >= 2 && level.activeTeam == TEAM_CIVILIAN && 0.5 > frand()) G_MoralePanic(ent, qfalse); /* multiplayer needs enabled sv_enablemorale */ /* singleplayer has this in every case */ if (G_IsMoraleEnabled()) { /* if panic, determine what kind of panic happens: */ if (ent->morale <= mor_panic->value && !G_IsPaniced(ent) && !G_IsRaged(ent)) { qboolean sanity; if ((float) ent->morale / mor_panic->value > (m_sanity->value * frand())) sanity = qtrue; else sanity = qfalse; if ((float) ent->morale / mor_panic->value > (m_rage->value * frand())) G_MoralePanic(ent, sanity); else G_MoraleRage(ent, sanity); /* if shaken, well .. be shaken; */ } else if (ent->morale <= mor_shaken->value && !G_IsPaniced(ent) && !G_IsRaged(ent)) { /* shaken is later reset along with reaction fire */ G_SetShaken(ent); G_SetState(ent, STATE_REACTION); G_EventSendState(G_VisToPM(ent->visflags), ent); G_ClientPrintf(G_PLAYER_FROM_ENT(ent), PRINT_HUD, _("%s is currently shaken.\n"), ent->chr.name); } else { if (G_IsPaniced(ent)) G_MoraleStopPanic(ent); else if (G_IsRaged(ent)) G_MoraleStopRage(ent); } } G_ActorSetMaxs(ent); /* morale-regeneration, capped at max: */ newMorale = ent->morale + MORALE_RANDOM(mor_regeneration->value); if (newMorale > GET_MORALE(ent->chr.score.skills[ABILITY_MIND])) ent->morale = GET_MORALE(ent->chr.score.skills[ABILITY_MIND]); else ent->morale = newMorale; /* send phys data and state: */ G_SendStats(ent); gi.EndEvents(); } } }
/** * @brief Will inform the player about the real TU reservation * @param ent The actors edict. */ void G_EventActorSendReservations (const edict_t *ent) { G_EventAdd(G_PlayerToPM(G_PLAYER_FROM_ENT(ent)), EV_ACTOR_RESERVATIONCHANGE, ent->number); gi.WriteShort(ent->chr.reservedTus.reaction); gi.WriteShort(ent->chr.reservedTus.shot); gi.WriteShort(ent->chr.reservedTus.crouch); G_EventEnd(); }
/** * @brief Actor has been wounded/treated * @param[in] ent The actor whose wound status has changed * @note This event is sent to the player this actor belongs to */ void G_EventActorWound (const edict_t *ent, const int bodyPart) { const int mask = G_PlayerToPM(G_PLAYER_FROM_ENT(ent)); G_EventAdd(mask, EV_ACTOR_WOUND, ent->number); gi.WriteByte(bodyPart); gi.WriteByte(ent->chr.wounds.woundLevel[bodyPart]); gi.WriteByte(ent->chr.wounds.treatmentLevel[bodyPart]); G_EventEnd(); }
void G_EventReactionFireChange (const edict_t* ent) { const objDef_t *od = ent->chr.RFmode.weapon; G_EventAdd(G_PlayerToPM(G_PLAYER_FROM_ENT(ent)), EV_ACTOR_REACTIONFIRECHANGE, ent->number); gi.WriteByte(ent->chr.RFmode.fmIdx); gi.WriteByte(ent->chr.RFmode.getHand()); gi.WriteShort(od ? od->idx : NONE); G_EventEnd(); }
/** * @sa G_MoralePanic * @sa G_MoraleStopPanic * @sa G_MoraleStopRage * @sa G_MoraleBehaviour */ static void G_MoraleRage (edict_t * ent, qboolean sanity) { if (sanity) G_SetRage(ent); else G_SetInsane(ent); G_EventSendState(G_VisToPM(ent->visflags), ent); if (sanity) gi.BroadcastPrintf(PRINT_HUD, _("%s is on a rampage.\n"), ent->chr.name); else gi.BroadcastPrintf(PRINT_HUD, _("%s is consumed by mad rage!\n"), ent->chr.name); AI_ActorThink(G_PLAYER_FROM_ENT(ent), ent); }
/** * @sa G_MoralePanic * @sa G_MoraleStopPanic * @sa G_MoraleStopRage * @sa G_MoraleBehaviour */ static void G_MoraleRage (edict_t *ent, bool sanity) { if (sanity) { G_SetRage(ent); gi.BroadcastPrintf(PRINT_HUD, _("%s is on a rampage!"), ent->chr.name); G_PrintStats("%s is on a rampage (entnum %i).", ent->chr.name, ent->number); } else { G_SetInsane(ent); gi.BroadcastPrintf(PRINT_HUD, _("%s is consumed by mad rage!"), ent->chr.name); G_PrintStats("%s is consumed by mad rage (entnum %i).", ent->chr.name, ent->number); } G_EventSendState(G_VisToPM(ent->visflags), ent); AI_ActorThink(G_PLAYER_FROM_ENT(ent), ent); }
/** * @brief Updates the reaction fire settings in case something was moved into a hand or from a hand * that would make the current settings invalid * @param[in,out] ent The actor edict to check the settings for * @param[in] fmIdx The fire mode index that should be used for reaction fire * @param[in] hand The hand that should be used for reaction fire * @param[in] od The object/weapon for the reaction fire */ void G_ReactionFireUpdate (edict_t *ent, fireDefIndex_t fmIdx, actorHands_t hand, const objDef_t *od) { ent->chr.RFmode.set(hand, fmIdx, od); /* FiremodeSettings */ if (!G_ActorHasWorkingFireModeSet(ent)) { /* Disable reaction fire if no valid firemode was found. */ G_ClientStateChange(G_PLAYER_FROM_ENT(ent), ent, ~STATE_REACTION, true); return; } G_EventReactionFireChange(ent); /* If reaction fire is active, update the reserved TUs */ if (G_IsReaction(ent)) { G_ReactionFireSettingsReserveTUs(ent); } }
static bool Message_Use (edict_t *self, edict_t *activator) { if (!activator || !G_IsActor(activator)) { return false; } else { player_t *player = G_PLAYER_FROM_ENT(activator); const char *msg = self->message; /* remove gettext marker */ if (msg[0] == '_') msg++; G_ClientPrintf(player, PRINT_HUD, "%s", msg); if (self->spawnflags & 1) G_FreeEdict(self); return false; } }
/** * @brief Resolve the reaction fire for an entity, this checks that the entity can fire and then takes the shot * @param[in] shooter The entity to resolve reaction fire for * @param[in] target The victim of the reaction fire * @return true if the entity fired (or would have fired if mock), false otherwise */ static bool G_ReactionFireTryToShoot (edict_t *shooter, const edict_t *target) { bool tookShot; /* check for valid target */ assert(target); /* shooter can't take a reaction shot if it's not possible - and check that * the target is still alive */ if (!G_ReactionFireIsPossible(shooter, target)) { G_ReactionFireTargetsRemove(shooter, target); return false; } /* take the shot */ tookShot = G_ReactionFireShoot(G_PLAYER_FROM_ENT(shooter), shooter, target->pos, ST_RIGHT_REACTION, shooter->chr.RFmode.fmIdx); if (tookShot) { /* clear any shakenness */ G_RemoveShaken(shooter); } return tookShot; }
/** * @brief Reset the client actions for the given entity * @param[in] ent The entity to reset the client action for * @note This event is send to the player this edict belongs to */ void G_EventResetClientAction (const edict_t* ent) { const int playerMask = G_PlayerToPM(G_PLAYER_FROM_ENT(ent)); G_EventAdd(playerMask, EV_RESET_CLIENT_ACTION, ent->number); G_EventEnd(); }
/** * @brief Deals damage of a give type and amount to a target. * @param[in,out] target What we want to damage. * @param[in] fd The fire definition that defines what type of damage is dealt. * @param[in] damage The value of the damage. * @param[in] attacker The attacker. * @param[in] mock pseudo shooting - only for calculating mock values - NULL for real shots * @param[in] impact impact location - @c NULL for splash damage * @sa G_SplashDamage * @sa G_TakeDamage * @sa G_PrintActorStats */ static void G_Damage (edict_t *target, const fireDef_t *fd, int damage, edict_t *attacker, shot_mock_t *mock, const vec3_t impact) { const bool stunEl = (fd->obj->dmgtype == gi.csi->damStunElectro); const bool stunGas = (fd->obj->dmgtype == gi.csi->damStunGas); const bool shock = (fd->obj->dmgtype == gi.csi->damShock); const bool smoke = (fd->obj->dmgtype == gi.csi->damSmoke); bool isRobot; assert(target); /* Breakables */ if (G_IsBrushModel(target) && G_IsBreakable(target)) { /* Breakables are immune to stun & shock damage. */ if (stunEl || stunGas || shock || mock || smoke) return; if (damage >= target->HP) { /* don't reset the HP value here, this value is used to distinguish * between triggered destroy and a shoot */ assert(target->destroy); target->destroy(target); /* maybe the attacker is seeing something new? */ G_CheckVisTeamAll(attacker->team, 0, attacker); /* check if attacker appears/perishes for any other team */ G_CheckVis(attacker); } else { G_TakeDamage(target, damage); } return; } /* Actors don't die again. */ if (!G_IsLivingActor(target)) return; /* only actors after this point - and they must have a teamdef */ assert(target->chr.teamDef); isRobot = CHRSH_IsTeamDefRobot(target->chr.teamDef); /* Apply armour effects. */ if (damage > 0) { damage = G_ApplyProtection(target, fd->dmgweight, damage); } else if (damage < 0) { /* Robots can't be healed. */ if (isRobot) return; } Com_DPrintf(DEBUG_GAME, " Total damage: %d\n", damage); /* Apply difficulty settings. */ if (sv_maxclients->integer == 1) { if (G_IsAlien(attacker) && !G_IsAlien(target)) damage *= pow(1.18, g_difficulty->value); else if (!G_IsAlien(attacker) && G_IsAlien(target)) damage *= pow(1.18, -g_difficulty->value); } assert(attacker->team >= 0 && attacker->team < MAX_TEAMS); assert(target->team >= 0 && target->team < MAX_TEAMS); if (g_nodamage != NULL && !g_nodamage->integer) { /* hit */ if (mock) { G_UpdateShotMock(mock, attacker, target, damage); } else if (stunEl) { target->STUN += damage; } else if (stunGas) { if (!isRobot) /* Can't stun robots with gas */ target->STUN += damage; } else if (shock) { /* Only do this if it's not one from our own team ... they should have known that there was a flashbang coming. */ if (!isRobot && target->team != attacker->team) { /** @todo there should be a possible protection, too */ /* dazed entity wont reaction fire */ G_RemoveReaction(target); G_ActorReserveTUs(target, 0, target->chr.reservedTus.shot, target->chr.reservedTus.crouch); /* flashbangs kill TUs */ G_ActorSetTU(target, 0); G_SendStats(target); /* entity is dazed */ G_SetDazed(target); G_ClientPrintf(G_PLAYER_FROM_ENT(target), PRINT_HUD, _("Soldier is dazed!\nEnemy used flashbang!")); return; } } else { if (damage < 0) { /* The 'attacker' is healing the target. */ G_TreatActor(target, fd, damage, attacker->team); } else { /* Real damage was dealt. */ G_DamageActor(target, damage, impact); /* Update overall splash damage for stats/score. */ if (!mock && damage > 0 && fd->splrad) /**< Check for >0 and splrad to not count this as direct hit. */ G_UpdateHitScore(attacker, target, fd, damage); } } } if (mock) return; G_CheckDeathOrKnockout(target, attacker, fd, damage); }
/** * @brief Think function for actors that spawn crouched * @param ent The actor */ static void G_ThinkActorGoCrouch (edict_t *ent) { G_ClientStateChange(G_PLAYER_FROM_ENT(ent), ent, STATE_CROUCHED, true); ent->think = NULL; }