/** * @brief Touch trigger * @sa SP_trigger_touch */ static bool Touch_TouchTrigger (Edict* self, Edict* activator) { /* these actors should really not be able to trigger this - they don't move anymore */ assert(!G_IsDead(activator)); self->_owner = G_EdictsFindTargetEntity(self->target); if (!self->owner()) { gi.DPrintf("Target '%s' wasn't found for %s\n", self->target, self->classname); G_FreeEdict(self); return false; } if (self->owner()->flags & FL_CLIENTACTION) { G_ActorSetClientAction(activator, self->owner()); } else if (!(self->spawnflags & TRIGGER_TOUCH_ONCE) || self->touchedNext == nullptr) { if (!self->owner()->use) { gi.DPrintf("Owner of %s doesn't have a use function\n", self->classname); G_FreeEdict(self); return false; } G_UseEdict(self->owner(), activator); } return false; }
/* * G_ClientEndSnapFrame * * Called for each player at the end of the server frame * and right after spawning */ void G_ClientEndSnapFrame( edict_t *ent ) { gclient_t *client; int i; if( trap_GetClientState( PLAYERNUM( ent ) ) < CS_SPAWNED ) return; client = ent->r.client; // set fov if( !client->ps.pmove.stats[PM_STAT_ZOOMTIME] ) client->ps.fov = ent->r.client->fov; else { float frac = (float)client->ps.pmove.stats[PM_STAT_ZOOMTIME] / (float)ZOOMTIME; client->ps.fov = client->fov - ( (float)( client->fov - client->zoomfov ) * frac ); } // If the end of unit layout is displayed, don't give // the player any normal movement attributes if( GS_MatchState() >= MATCH_STATE_POSTMATCH ) { G_SetClientStats( ent ); } else { if( G_IsDead( ent ) && !level.gametype.customDeadBodyCam ) { G_Client_DeadView( ent ); } G_PlayerWorldEffects( ent ); // burn from lava, etc G_ClientDamageFeedback( ent ); // show damage taken along the snap G_SetClientStats( ent ); G_SetClientEffects( ent ); G_SetClientSound( ent ); G_SetClientFrame( ent ); client->ps.plrkeys = client->resp.snap.plrkeys; } G_ReleaseClientPSEvent( client ); // set the delta angle for( i = 0; i < 3; i++ ) client->ps.pmove.delta_angles[i] = ANGLE2SHORT( client->ps.viewangles[i] ) - client->ucmd.angles[i]; // this is pretty hackish. We exploit the fact that servers *do not* transmit // origin2/old_origin for ET_PLAYER/ET_CORPSE entities, and we use it for sending the player velocity if( !G_ISGHOSTING( ent ) ) { ent->r.svflags |= SVF_TRANSMITORIGIN2; VectorCopy( ent->velocity, ent->s.origin2 ); } else ent->r.svflags &= ~SVF_TRANSMITORIGIN2; }
/** * @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 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 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 Rescue trigger * @sa SP_trigger_resuce */ static bool Touch_RescueTrigger (Edict* self, Edict* activator) { /* these actors should really not be able to trigger this - they don't move anymore */ assert(!G_IsDead(activator)); if (self->team == activator->team) G_ActorSetInRescueZone(activator, true); return false; }
static bool G_MissionIsTouched (Edict* self) { 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; } return false; }
void G_AwardResetPlayerComboStats( edict_t *ent ) { int i; int resetvalue; // combo from LB can be cancelled only if player's dead, if he missed or if he hasnt shot with LB for too long resetvalue = ( G_IsDead( ent ) ? 0 : COMBO_FLAG( WEAP_LASERGUN ) ); for( i = 0; i < gs.maxclients; i++ ) game.clients[i].resp.awardInfo.combo[PLAYERNUM( ent )] &= resetvalue; }
static unsigned int G_FindPointedPlayer( edict_t *self ) { trace_t trace; int i, j, bestNum = 0; vec3_t boxpoints[8]; float value, dist, value_best = 0.90f; // if nothing better is found, print nothing edict_t *other; vec3_t vieworg, dir, viewforward; if( G_IsDead( self ) ) { return 0; } // we can't handle the thirdperson modifications in server side :/ VectorSet( vieworg, self->r.client->ps.pmove.origin[0], self->r.client->ps.pmove.origin[1], self->r.client->ps.pmove.origin[2] + self->r.client->ps.viewheight ); AngleVectors( self->r.client->ps.viewangles, viewforward, NULL, NULL ); for( i = 0; i < gs.maxclients; i++ ) { other = PLAYERENT( i ); if( !other->r.inuse ) { continue; } if( !other->r.client ) { continue; } if( other == self ) { continue; } if( !other->r.solid || ( other->r.svflags & SVF_NOCLIENT ) ) { continue; } VectorSubtract( other->s.origin, self->s.origin, dir ); dist = VectorNormalize2( dir, dir ); if( dist > 1000 ) { continue; } value = DotProduct( dir, viewforward ); if( value > value_best ) { BuildBoxPoints( boxpoints, other->s.origin, tv( 4, 4, 4 ), tv( 4, 4, 4 ) ); for( j = 0; j < 8; j++ ) { G_Trace( &trace, vieworg, vec3_origin, vec3_origin, boxpoints[j], self, MASK_SHOT | MASK_OPAQUE ); if( trace.ent && trace.ent == ENTNUM( other ) ) { value_best = value; bestNum = ENTNUM( other ); } } } } return bestNum; }
/** * @brief Sets correct bounding box for actor (state dependent). * @param[in] ent Pointer to entity for which bounding box is being set. * @note Also re-links the actor edict - because the server must know about the * changed bounding box for tracing to work. */ void G_ActorSetMaxs (Edict* ent) { if (G_IsCrouched(ent)) VectorSet(ent->entBox.maxs, PLAYER_WIDTH, PLAYER_WIDTH, PLAYER_CROUCH); else if (G_IsDead(ent) && !CHRSH_IsTeamDefRobot(ent->chr.teamDef)) VectorSet(ent->entBox.maxs, PLAYER_WIDTH, PLAYER_WIDTH, PLAYER_DEAD); else VectorSet(ent->entBox.maxs, PLAYER_WIDTH, PLAYER_WIDTH, PLAYER_STAND); /* Link it. */ gi.LinkEdict(ent); }
/** * @brief Rescue trigger * @sa SP_trigger_resuce */ static bool Touch_RescueTrigger (Edict* self, Edict* activator) { /* these actors should really not be able to trigger this - they don't move anymore */ assert(!G_IsDead(activator)); if (G_IsActor(activator)) { Actor* actor = makeActor(activator); if (self->isSameTeamAs(actor)) actor->setInRescueZone(true); } 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; }
static bool G_ActorStun (Edict* ent, const Edict* attacker) { /* already dead or stunned? */ if (G_IsDead(ent)) return false; /* no other state should be set here */ ent->state = STATE_STUN; G_ActorSetMaxs(ent); ent->link = attacker; G_ActorModifyCounters(attacker, ent, -1, 0, 1); return true; }
/* * G_SetClientEffects */ static void G_SetClientEffects( edict_t *ent ) { gclient_t *client = ent->r.client; if( G_IsDead( ent ) || GS_MatchState() >= MATCH_STATE_POSTMATCH ) return; if( client->ps.inventory[POWERUP_QUAD] > 0 ) { ent->s.effects |= EF_QUAD; if( client->ps.inventory[POWERUP_QUAD] < 6 ) ent->s.effects |= EF_EXPIRING_QUAD; } if( client->ps.inventory[POWERUP_SHELL] > 0 ) { ent->s.effects |= EF_SHELL; if( client->ps.inventory[POWERUP_SHELL] < 6 ) ent->s.effects |= EF_EXPIRING_SHELL; } if( client->ps.inventory[POWERUP_REGEN] > 0 ) { ent->s.effects |= EF_REGEN; if( client->ps.inventory[POWERUP_REGEN] < 6 ) ent->s.effects |= EF_EXPIRING_REGEN; } if( ent->s.weapon ) { firedef_t *firedef = GS_FiredefForPlayerState( &client->ps, ent->s.weapon ); if( firedef && firedef->fire_mode == FIRE_MODE_STRONG ) ent->s.effects |= EF_STRONG_WEAPON; } if( client->ps.pmove.stats[PM_STAT_STUN] ) ent->s.effects |= EF_PLAYER_STUNNED; else ent->s.effects &= ~EF_PLAYER_STUNNED; // show cheaters!!! if( ent->flags & FL_GODMODE ) ent->s.effects |= EF_GODMODE; // add chatting icon effect if( ent->r.client->resp.snap.buttons & BUTTON_BUSYICON ) ent->s.effects |= EF_BUSYICON; }
/** * @brief Update character stats for this mission after successful shoot. * @note Mind you that this code is always from the view of PHALANX soldiers right now, not anybody else! * @param[in,out] attacker Pointer to attacker. * @param[in] fd Pointer to fireDef_t used in shoot. * @param[in] target Pointer to target. * @sa G_UpdateCharacterSkills */ static void G_UpdateCharacterBodycount (edict_t *attacker, const fireDef_t *fd, const edict_t *target) { chrScoreMission_t *scoreMission; chrScoreGlobal_t *scoreGlobal; killtypes_t type; if (!attacker || !target) return; scoreGlobal = &attacker->chr.score; scoreMission = attacker->chr.scoreMission; /* only phalanx soldiers have this */ if (!scoreMission) return; switch (target->team) { case TEAM_ALIEN: type = KILLED_ENEMIES; if (fd) { assert(fd->weaponSkill >= 0); assert(fd->weaponSkill < lengthof(scoreMission->skillKills)); scoreMission->skillKills[fd->weaponSkill]++; } break; case TEAM_CIVILIAN: type = KILLED_CIVILIANS; break; case TEAM_PHALANX: type = KILLED_TEAM; break; default: return; } if (G_IsStunned(target)) { scoreMission->stuns[type]++; scoreGlobal->stuns[type]++; } else if (G_IsDead(target)) { scoreMission->kills[type]++; scoreGlobal->kills[type]++; } }
static bool G_ActorDie (Edict* ent, const Edict* attacker) { const bool stunned = G_IsStunned(ent); G_RemoveStunned(ent); if (G_IsDead(ent)) return false; G_SetState(ent, 1 + rand() % MAX_DEATH); G_ActorSetMaxs(ent); if (stunned) { G_ActorModifyCounters(attacker, ent, 0, 1, 0); G_ActorModifyCounters(ent->link, ent, 0, 0, -1); } else { G_ActorModifyCounters(attacker, ent, -1, 1, 0); } return true; }
/** * @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; } }
/* * G_CheckClientRespawnClick */ void G_CheckClientRespawnClick( edict_t *ent ) { if( !ent->r.inuse || !ent->r.client || !G_IsDead( ent ) ) return; if( GS_MatchState() >= MATCH_STATE_POSTMATCH ) return; if( trap_GetClientState( PLAYERNUM( ent ) ) >= CS_SPAWNED ) { // if the spawnsystem doesn't require to click if( G_SpawnQueue_GetSystem( ent->s.team ) != SPAWNSYSTEM_INSTANT ) { int minDelay = g_respawn_delay_min->integer; // waves system must wait for at least 500 msecs (to see the death, but very short for selfkilling tactics). if( G_SpawnQueue_GetSystem( ent->s.team ) == SPAWNSYSTEM_WAVES ) minDelay = ( g_respawn_delay_min->integer < 500 ) ? 500 : g_respawn_delay_min->integer; // hold system must wait for at least 1000 msecs (to see the death properly) if( G_SpawnQueue_GetSystem( ent->s.team ) == SPAWNSYSTEM_HOLD ) minDelay = ( g_respawn_delay_min->integer < 1300 ) ? 1300 : g_respawn_delay_min->integer; if( level.time >= ent->deathTimeStamp + minDelay ) G_SpawnQueue_AddClient( ent ); } // clicked else if( ent->r.client->resp.snap.buttons & BUTTON_ATTACK ) { if( level.time > ent->deathTimeStamp + g_respawn_delay_min->integer ) G_SpawnQueue_AddClient( ent ); } // didn't click, but too much time passed else if( g_respawn_delay_max->integer && ( level.time > ent->deathTimeStamp + g_respawn_delay_max->integer ) ) { G_SpawnQueue_AddClient( ent ); } } }
/** * @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 Checks whether the requested action is possible * @param[in] player Which player (human player) is trying to do the action * @param[in] ent Which of his units is trying to do the action. */ static bool G_ActionCheck (const player_t *player, edict_t *ent) { /* don't check for a player - but maybe a server action */ if (!player) return true; if (!ent || !ent->inuse) { G_ClientPrintf(player, PRINT_HUD, _("Can't perform action - object not present!")); return false; } if (ent->type != ET_ACTOR && ent->type != ET_ACTOR2x2) { G_ClientPrintf(player, PRINT_HUD, _("Can't perform action - not an actor!")); return false; } if (G_IsStunned(ent)) { G_ClientPrintf(player, PRINT_HUD, _("Can't perform action - actor is stunned!")); return false; } if (G_IsDead(ent)) { G_ClientPrintf(player, PRINT_HUD, _("Can't perform action - actor is dead!")); return false; } if (ent->team != player->pers.team) { G_ClientPrintf(player, PRINT_HUD, _("Can't perform action - not on same team!")); return false; } if (ent->pnum != player->num) { G_ClientPrintf(player, PRINT_HUD, _("Can't perform action - no control over allied actors!")); return false; } /* could be possible */ return true; }
/** * @brief Hurt trigger * @sa SP_trigger_hurt */ bool Touch_HurtTrigger (Edict* self, Edict* activator) { const int damage = G_ApplyProtection(activator, self->dmgtype, self->dmg); 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; /* these actors should really not be able to trigger this - they don't move anymore */ if (G_IsDead(activator)) return false; if (stunEl || (stunGas && !isRobot)) { activator->STUN += damage; } else if (shock) { /** @todo Handle dazed via trigger_hurt */ } else { G_TakeDamage(activator, damage); } return true; }
void G_MissionReset (Edict* self, Edict* activator) { /* Don't reset the mission timer for 'bring item' missions G_MissionThink will handle that */ if (!self->time || self->item) return; 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) || ent == activator)) { return; } touched = touched->next; } if (activator->getTeam() == self->getTeam()) { const char* const actTeam = G_MissionGetTeamString(activator->getTeam()); if (self->targetname) gi.BroadcastPrintf(PRINT_HUD, _("%s forces have left the %s!"), actTeam, self->targetname); else gi.BroadcastPrintf(PRINT_HUD, _("%s forces have left their target zone!"), actTeam); } /* All team actors are gone, reset counter */ self->count = 0; }
/* * GClip_TouchTriggers */ void GClip_TouchTriggers( edict_t *ent ) { int i, num; edict_t *hit; int touch[MAX_EDICTS]; vec3_t mins, maxs; // dead things don't activate triggers! if( ent->r.client && G_IsDead( ent ) ) return; VectorAdd( ent->s.origin, ent->r.mins, mins ); VectorAdd( ent->s.origin, ent->r.maxs, maxs ); // FIXME: should be s.origin + mins and s.origin + maxs because of absmin and absmax padding? num = GClip_AreaEdicts( ent->r.absmin, ent->r.absmax, touch, MAX_EDICTS, AREA_TRIGGERS, 0 ); // 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++ ) { if( !ent->r.inuse ) break; hit = &game.edicts[touch[i]]; if( !hit->r.inuse ) continue; if( !hit->touch && !hit->asTouchFunc ) continue; if( !hit->item && !GClip_EntityContact( mins, maxs, hit ) ) continue; G_CallTouch( hit, ent, NULL, 0 ); } }
/** * @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; }
void G_PMoveTouchTriggers( pmove_t *pm ) { int i, num; edict_t *hit; int touch[MAX_EDICTS]; vec3_t mins, maxs; edict_t *ent; if( pm->playerState->POVnum <= 0 || (int)pm->playerState->POVnum > gs.maxclients ) return; ent = game.edicts + pm->playerState->POVnum; if( !ent->r.client || G_IsDead( ent ) ) // dead things don't activate triggers! return; // update the entity with the new position VectorCopy( pm->playerState->pmove.origin, ent->s.origin ); VectorCopy( pm->playerState->pmove.velocity, ent->velocity ); VectorCopy( pm->playerState->viewangles, ent->s.angles ); ent->viewheight = pm->playerState->viewheight; VectorCopy( pm->mins, ent->r.mins ); VectorCopy( pm->maxs, ent->r.maxs ); ent->waterlevel = pm->waterlevel; ent->watertype = pm->watertype; if( pm->groundentity == -1 ) { ent->groundentity = NULL; } else { ent->groundentity = &game.edicts[pm->groundentity]; ent->groundentity_linkcount = ent->groundentity->r.linkcount; } GClip_LinkEntity( ent ); VectorAdd( pm->playerState->pmove.origin, pm->mins, mins ); VectorAdd( pm->playerState->pmove.origin, pm->maxs, maxs ); num = GClip_AreaEdicts( mins, maxs, touch, MAX_EDICTS, AREA_TRIGGERS, 0 ); // 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++ ) { if( !ent->r.inuse ) break; hit = &game.edicts[touch[i]]; if( !hit->r.inuse ) continue; if( !hit->touch && !hit->asTouchFunc ) continue; if( !hit->item && !GClip_EntityContact( mins, maxs, hit ) ) continue; G_CallTouch( hit, ent, NULL, 0 ); } }
/** * @brief Generates the client events that are send over the netchannel to move an actor * @param[in] player Player who is moving an actor * @param[in] visTeam The team to check the visibility for - if this is 0 we build the forbidden list * above all edicts - for the human controlled actors this would mean that clicking to a grid * position that is not reachable because an invisible actor is standing there would not result in * a single step - as the movement is aborted before. For AI movement this is in general @c 0 - but * not if they e.g. hide. * @param[in] ent Edict to move * @param[in] to The grid position to walk to * @sa CL_ActorStartMove * @sa PA_MOVE */ void G_ClientMove (const player_t * player, int visTeam, edict_t* ent, const pos3_t to) { int status, initTU; dvec_t dvtab[MAX_DVTAB]; int dir; byte numdv, length; pos3_t pos; float div; int oldState; int oldHP; bool autoCrouchRequired = false; byte crouchingState; if (VectorCompare(ent->pos, to)) return; /* check if action is possible */ if (!G_ActionCheckForCurrentTeam(player, ent, TU_MOVE_STRAIGHT)) return; crouchingState = G_IsCrouched(ent) ? 1 : 0; oldState = oldHP = 0; /* calculate move table */ G_MoveCalc(visTeam, ent, ent->pos, crouchingState, ent->TU); length = gi.MoveLength(level.pathingMap, to, crouchingState, false); /* length of ROUTING_NOT_REACHABLE means not reachable */ if (length && length >= ROUTING_NOT_REACHABLE) return; /* Autostand: check if the actor is crouched and player wants autostanding...*/ if (crouchingState && player->autostand) { /* ...and if this is a long walk... */ if (SHOULD_USE_AUTOSTAND(length)) { /* ...make them stand first. If the player really wants them to walk a long * way crouched, he can move the actor in several stages. * Uses the threshold at which standing, moving and crouching again takes * fewer TU than just crawling while crouched. */ G_ClientStateChange(player, ent, STATE_CROUCHED, true); /* change to stand state */ crouchingState = G_IsCrouched(ent) ? 1 : 0; if (!crouchingState) { G_MoveCalc(visTeam, ent, ent->pos, crouchingState, ent->TU); length = gi.MoveLength(level.pathingMap, to, crouchingState, false); autoCrouchRequired = true; } } } /* this let the footstep sounds play even over network */ ent->think = G_PhysicsStep; ent->nextthink = level.time; /* assemble dvec-encoded move data */ VectorCopy(to, pos); initTU = ent->TU; numdv = G_FillDirectionTable(dvtab, lengthof(dvtab), crouchingState, pos); /* make sure to end any other pending events - we rely on EV_ACTOR_MOVE not being active anymore */ gi.EndEvents(); /* everything ok, found valid route? */ if (VectorCompare(pos, ent->pos)) { byte* stepAmount = NULL; int usedTUs = 0; /* no floor inventory at this point */ FLOOR(ent) = NULL; while (numdv > 0) { /* A flag to see if we needed to change crouch state */ int crouchFlag; const byte oldDir = ent->dir; int dvec; /* get next dvec */ numdv--; dvec = dvtab[numdv]; /* This is the direction to make the step into */ dir = getDVdir(dvec); /* turn around first */ status = G_ActorDoTurn(ent, dir); if (status & VIS_STOP) { autoCrouchRequired = false; if (ent->moveinfo.steps == 0) usedTUs += TU_TURN; break; } if (G_ActorShouldStopInMidMove(ent, status, dvtab, numdv)) { /* don't autocrouch if new enemy becomes visible */ autoCrouchRequired = false; /* if something appears on our route that didn't trigger a VIS_STOP, we have to * send the turn event if this is our first step */ if (oldDir != ent->dir && ent->moveinfo.steps == 0) { G_EventActorTurn(ent); usedTUs += TU_TURN; } break; } /* decrease TUs */ div = gi.GetTUsForDirection(dir, G_IsCrouched(ent)); if ((int) (usedTUs + div) > ent->TU) break; usedTUs += div; /* This is now a flag to indicate a change in crouching - we need this for * the stop in mid move call(s), because we need the updated entity position */ crouchFlag = 0; /* Calculate the new position after the decrease in TUs, otherwise the game * remembers the false position if the time runs out */ PosAddDV(ent->pos, crouchFlag, dvec); /* slower if crouched */ if (G_IsCrouched(ent)) ent->speed = ACTOR_SPEED_CROUCHED; else ent->speed = ACTOR_SPEED_NORMAL; ent->speed *= g_actorspeed->value; if (crouchFlag == 0) { /* No change in crouch */ edict_t* clientAction; int contentFlags; vec3_t pointTrace; G_EdictCalcOrigin(ent); VectorCopy(ent->origin, pointTrace); pointTrace[2] += PLAYER_MIN; contentFlags = gi.PointContents(pointTrace); /* link it at new position - this must be done for every edict * movement - to let the server know about it. */ gi.LinkEdict(ent); /* Only the PHALANX team has these stats right now. */ if (ent->chr.scoreMission) { float truediv = gi.GetTUsForDirection(dir, 0); /* regardless of crouching ! */ if (G_IsCrouched(ent)) ent->chr.scoreMission->movedCrouched += truediv; else ent->chr.scoreMission->movedNormal += truediv; } /* write the step to the net */ G_WriteStep(ent, &stepAmount, dvec, contentFlags); /* check if player appears/perishes, seen from other teams */ G_CheckVis(ent, true); /* check for anything appearing, seen by "the moving one" */ status = G_CheckVisTeamAll(ent->team, false, ent); /* Set ent->TU because the reaction code relies on ent->TU being accurate. */ G_ActorSetTU(ent, initTU - usedTUs); clientAction = ent->clientAction; oldState = ent->state; oldHP = ent->HP; /* check triggers at new position */ if (G_TouchTriggers(ent)) { if (!clientAction) status |= VIS_STOP; } G_TouchSolids(ent, 10.0f); /* state has changed - maybe we walked on a trigger_hurt */ if (oldState != ent->state) status |= VIS_STOP; else if (oldHP != ent->HP) status |= VIS_STOP; } else if (crouchFlag == 1) { /* Actor is standing */ G_ClientStateChange(player, ent, STATE_CROUCHED, true); } else if (crouchFlag == -1) { /* Actor is crouching and should stand up */ G_ClientStateChange(player, ent, STATE_CROUCHED, false); } /* check for reaction fire */ if (G_ReactionFireOnMovement(ent)) { status |= VIS_STOP; autoCrouchRequired = false; } /* check for death */ if (((oldHP != 0 && oldHP != ent->HP) || (oldState != ent->state)) && !G_IsDazed(ent)) { /** @todo Handle dazed via trigger_hurt */ /* maybe this was due to rf - then the G_ActorDie was already called */ if (!G_IsDead(ent)) { G_CheckDeathOrKnockout(ent, NULL, NULL, oldHP - ent->HP); } return; } if (G_ActorShouldStopInMidMove(ent, status, dvtab, numdv - 1)) { /* don't autocrouch if new enemy becomes visible */ autoCrouchRequired = false; break; } /* Restore ent->TU because the movement code relies on it not being modified! */ G_ActorSetTU(ent, initTU); } /* submit the TUs / round down */ if (g_notu != NULL && g_notu->integer) G_ActorSetTU(ent, initTU); else G_ActorSetTU(ent, initTU - usedTUs); G_SendStats(ent); /* end the move */ G_GetFloorItems(ent); gi.EndEvents(); } if (autoCrouchRequired) { /* toggle back to crouched state */ G_ClientStateChange(player, ent, STATE_CROUCHED, true); } }
void G_AwardPlayerHit( edict_t *targ, edict_t *attacker, int mod ) { int flag = -1; if( attacker->s.team == targ->s.team && attacker->s.team > TEAM_PLAYERS ) return; switch( mod ) { case MOD_INSTAGUN_W: case MOD_INSTAGUN_S: attacker->r.client->resp.awardInfo.ebhit_count++; if( attacker->r.client->resp.awardInfo.ebhit_count == EBHIT_FOR_AWARD ) { attacker->r.client->resp.awardInfo.ebhit_count = 0; attacker->r.client->resp.awardInfo.accuracy_award++; G_PlayerAward( attacker, S_COLOR_BLUE "Accuracy!" ); } flag = COMBO_FLAG( WEAP_INSTAGUN ); break; case MOD_ELECTROBOLT_W: case MOD_ELECTROBOLT_S: attacker->r.client->resp.awardInfo.ebhit_count++; if( attacker->r.client->resp.awardInfo.ebhit_count == EBHIT_FOR_AWARD ) { attacker->r.client->resp.awardInfo.ebhit_count = 0; attacker->r.client->resp.awardInfo.accuracy_award++; G_PlayerAward( attacker, S_COLOR_BLUE "Accuracy!" ); } flag = COMBO_FLAG( WEAP_ELECTROBOLT ); break; case MOD_ROCKET_W: case MOD_ROCKET_S: case MOD_ROCKET_SPLASH_W: case MOD_ROCKET_SPLASH_S: flag = COMBO_FLAG( WEAP_ROCKETLAUNCHER ); break; case MOD_GUNBLADE_W: case MOD_GUNBLADE_S: flag = COMBO_FLAG( WEAP_GUNBLADE ); break; case MOD_MACHINEGUN_W: case MOD_MACHINEGUN_S: flag = COMBO_FLAG( WEAP_MACHINEGUN ); break; case MOD_RIOTGUN_W: case MOD_RIOTGUN_S: flag = COMBO_FLAG( WEAP_RIOTGUN ); break; case MOD_GRENADE_W: case MOD_GRENADE_S: case MOD_GRENADE_SPLASH_W: case MOD_GRENADE_SPLASH_S: flag = COMBO_FLAG( WEAP_GRENADELAUNCHER ); break; case MOD_PLASMA_W: case MOD_PLASMA_S: case MOD_PLASMA_SPLASH_W: case MOD_PLASMA_SPLASH_S: flag = COMBO_FLAG( WEAP_PLASMAGUN ); break; case MOD_LASERGUN_W: case MOD_LASERGUN_S: flag = COMBO_FLAG( WEAP_LASERGUN ); break; default: break; } if( flag ) { if( attacker->r.client->resp.awardInfo.combo[PLAYERNUM( targ )] == COMBO_FLAG( WEAP_ROCKETLAUNCHER ) && G_IsDead( targ ) ) // RL... { if( flag == COMBO_FLAG( WEAP_ELECTROBOLT ) ) // to EB G_PlayerAward( attacker, S_COLOR_BLUE "RL to EB!" ); else if( flag == COMBO_FLAG( WEAP_LASERGUN ) ) // to LG G_PlayerAward( attacker, S_COLOR_BLUE "RL to LG!" ); else if( flag == COMBO_FLAG( WEAP_RIOTGUN ) ) // to RG G_PlayerAward( attacker, S_COLOR_BLUE "RL to RG!" ); else if( flag == COMBO_FLAG( WEAP_GRENADELAUNCHER ) ) // to GL G_PlayerAward( attacker, S_COLOR_BLUE "RL to GL!" ); //else if( flag == COMBO_FLAG( WEAP_ROCKETLAUNCHER ) ) // to RL // G_PlayerAward( attacker, S_COLOR_BLUE "RL to RL!" ); } else if( attacker->r.client->resp.awardInfo.combo[PLAYERNUM( targ )] == COMBO_FLAG( WEAP_ROCKETLAUNCHER ) && G_IsDead( targ ) ) // GL... { if( flag == COMBO_FLAG( WEAP_ELECTROBOLT ) ) // to EB G_PlayerAward( attacker, S_COLOR_BLUE "GL to EB!" ); else if( flag == COMBO_FLAG( WEAP_LASERGUN ) ) // to LG G_PlayerAward( attacker, S_COLOR_BLUE "GL to LG!" ); else if( flag == COMBO_FLAG( WEAP_RIOTGUN ) ) // to RG G_PlayerAward( attacker, S_COLOR_BLUE "GL to RG!" ); else if( flag == COMBO_FLAG( WEAP_ROCKETLAUNCHER ) ) // to RL G_PlayerAward( attacker, S_COLOR_BLUE "GL to RL!" ); //else if( flag == COMBO_FLAG( WEAP_GRENADELAUNCHER ) ) // to GL // G_PlayerAward( attacker, S_COLOR_BLUE "GL to GL!" ); } else if( attacker->r.client->resp.awardInfo.combo[PLAYERNUM( targ )] == COMBO_FLAG( WEAP_LASERGUN ) && G_IsDead( targ ) ) // LG... { if( flag == COMBO_FLAG( WEAP_ELECTROBOLT ) ) // to EB if( attacker->r.client->resp.awardInfo.lasthit == targ && level.time < attacker->r.client->resp.awardInfo.lasthit_time + LB_TIMEOUT_FOR_COMBO ) G_PlayerAward( attacker, S_COLOR_BLUE "LG to EB!" ); } else if( attacker->r.client->resp.awardInfo.combo[PLAYERNUM( targ )] == COMBO_FLAG( WEAP_GUNBLADE ) && G_IsDead( targ ) ) { if( flag == COMBO_FLAG( WEAP_GUNBLADE ) ) if( attacker->r.client->resp.awardInfo.lasthit == targ && level.time < attacker->r.client->resp.awardInfo.lasthit_time + GUNBLADE_TIMEOUT_FOR_COMBO ) G_PlayerAward( attacker, S_COLOR_BLUE "Gunblade Combo!" ); } attacker->r.client->resp.awardInfo.combo[PLAYERNUM( targ )] = flag; } attacker->r.client->resp.awardInfo.lasthit = targ; attacker->r.client->resp.awardInfo.lasthit_time = level.time; }
/* * G_Teams_TDM_UpdateTeamInfoMessages */ void G_Teams_UpdateTeamInfoMessages( void ) { static int nexttime = 0; static char teammessage[MAX_STRING_CHARS]; edict_t *ent, *e; size_t len; int i, j, team; char entry[MAX_TOKEN_CHARS]; int locationTag; nexttime -= game.snapFrameTime; if( nexttime > 0 ) return; while( nexttime <= 0 ) nexttime += 2000; // time for a new update for( team = TEAM_ALPHA; team < GS_MAX_TEAMS; team++ ) { *teammessage = 0; Q_snprintfz( teammessage, sizeof( teammessage ), "ti \"" ); len = strlen( teammessage ); // add our team info to the string for( i = 0; i < teamlist[team].numplayers; i++ ) { ent = game.edicts + teamlist[team].playerIndices[i]; if( G_IsDead( ent ) ) // don't show dead players continue; if( ent->r.client->teamstate.is_coach ) // don't show coachs continue; // get location name locationTag = G_MapLocationTAGForOrigin( ent->s.origin ); if( locationTag == -1 ) continue; *entry = 0; Q_snprintfz( entry, sizeof( entry ), "%i %i %i %i ", PLAYERNUM( ent ), locationTag, HEALTH_TO_INT( ent->health ), ARMOR_TO_INT( ent->r.client->resp.armor ) ); if( MAX_STRING_CHARS - len > strlen( entry ) ) { Q_strncatz( teammessage, entry, sizeof( teammessage ) ); len = strlen( teammessage ); } } // add closing quote *entry = 0; Q_snprintfz( entry, sizeof( entry ), "\"" ); if( MAX_STRING_CHARS - len > strlen( entry ) ) { Q_strncatz( teammessage, entry, sizeof( teammessage ) ); len = strlen( teammessage ); } for( i = 0; i < teamlist[team].numplayers; i++ ) { ent = game.edicts + teamlist[team].playerIndices[i]; if( !ent->r.inuse || !ent->r.client ) continue; trap_GameCmd( ent, teammessage ); // see if there are spectators chasing this player and send them the layout too for( j = 0; j < teamlist[TEAM_SPECTATOR].numplayers; j++ ) { e = game.edicts + teamlist[TEAM_SPECTATOR].playerIndices[j]; if( !e->r.inuse || !e->r.client ) continue; if( e->r.client->resp.chase.active && e->r.client->resp.chase.target == ENTNUM( ent ) ) trap_GameCmd( e, teammessage ); } } } }
/** * @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; }