openmohaa/code/fgame/level.cpp
smallmodel c77fcd2cbc
Some checks failed
CodeQL / Analyze (push) Waiting to run
Build branch / build-all (push) Failing after 15s
Clear the edict model when freeing the edict
This fixes an issue where configstrings or game state chars would overflow when many models (such as actors with random headmodels/headskins) are spawned
2025-02-04 21:28:37 +01:00

2766 lines
68 KiB
C++

/*
===========================================================================
Copyright (C) 2015 the OpenMoHAA team
This file is part of OpenMoHAA source code.
OpenMoHAA source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
OpenMoHAA source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
// level.cpp : Global Level Info.
#include "g_local.h"
#include "g_spawn.h"
#include "g_bot.h"
#include "level.h"
#include "parm.h"
#include "navigate.h"
#include "gravpath.h"
#include "entity.h"
#include "dm_manager.h"
#include "player.h"
#include "Entities.h"
#include "health.h"
#include "scriptmaster.h"
#include "scriptthread.h"
#include "scriptvariable.h"
#include "scriptexception.h"
#include <cfloat>
Level level;
gclient_t *spawn_client = NULL;
Event EV_Level_GetTime
(
"time",
EV_DEFAULT,
NULL,
NULL,
"current level time",
EV_GETTER
);
Event EV_Level_GetTotalSecrets
(
"total_secrets",
EV_DEFAULT,
NULL,
NULL,
"count of total secrets",
EV_GETTER
);
Event EV_Level_GetFoundSecrets
(
"found_secrets",
EV_DEFAULT,
NULL,
NULL,
"count of found secrets",
EV_GETTER
);
Event EV_Level_PreSpawnSentient
(
"prespawnsentient",
EV_CODEONLY,
NULL,
NULL,
"internal usage.",
EV_NORMAL
);
Event EV_Level_GetAlarm
(
"alarm",
EV_DEFAULT,
NULL,
NULL,
"zero = global level alarm off,"
"non-zero = alarm on",
EV_GETTER
);
Event EV_Level_SetAlarm
(
"alarm",
EV_DEFAULT,
"i",
"alarm_status",
"zero = global level alarm off,"
"non-zero = alarm on",
EV_SETTER
);
Event EV_Level_SetNoDropHealth
(
"nodrophealth",
EV_DEFAULT,
"i",
"alarm_status",
"zero = automatically drop health according to cvars, non-zero = don't autodrop health (like hard mode)",
EV_SETTER
);
Event EV_Level_SetNoDropWeapons
(
"nodropweapons",
EV_DEFAULT,
"i",
"alarm_status",
"zero = automatically drop weapons according to cvars, non-zero = don't autodrop weapons (like hard mode)",
EV_SETTER
);
Event EV_Level_GetRoundStarted
(
"roundstarted",
EV_DEFAULT,
NULL,
NULL,
"non-zero if round has started",
EV_GETTER
);
Event EV_Level_GetLoopProtection
(
"loop_protection",
EV_DEFAULT,
NULL,
NULL,
"says if infinite loop protection is enabled",
EV_GETTER
);
Event EV_Level_SetLoopProtection
(
"loop_protection",
EV_DEFAULT,
"i",
"loop_protection",
"specify if infinite loop protection is enabled",
EV_SETTER
);
Event EV_Level_GetPapersLevel
(
"papers",
EV_DEFAULT,
NULL,
NULL,
"the level of papers the player currently has",
EV_GETTER
);
Event EV_Level_SetPapersLevel
(
"papers",
EV_DEFAULT,
NULL,
NULL,
"the level of papers the player currently has",
EV_SETTER
);
Event EV_Level_GetDMRespawning
(
"dmrespawning",
EV_DEFAULT,
NULL,
NULL,
"returns 1 if wave-based DM, 0 if respawns are disabled within a round",
EV_GETTER
);
Event EV_Level_SetDMRespawning
(
"dmrespawning",
EV_DEFAULT,
"i",
"allow_respawn",
"set to 1 to turn on wave-based DM, to 0 to disable respawns within a round",
EV_SETTER
);
Event EV_Level_SetDMRespawning2(
"dmrespawning",
EV_DEFAULT,
"i",
"allow_respawn",
"set to 1 to turn on wave-based DM,"
"to 0 to disable respawns within a round"
);
Event EV_Level_GetDMRoundLimit(
"dmroundlimit",
EV_DEFAULT,
NULL,
NULL,
"gets the actual roundlimit, in minutes; may be 'roundlimit' cvar or the default round limit",
EV_GETTER
);
Event EV_Level_SetDMRoundLimit(
"dmroundlimit",
EV_DEFAULT,
"i",
"roundlimit",
"sets the default roundlimit, in minutes; can be overridden by 'roundlimit' cvar",
EV_SETTER
);
Event EV_Level_SetDMRoundLimit2(
"dmroundlimit",
EV_DEFAULT,
"i",
"roundlimit",
"sets the default roundlimit,"
"in minutes; can be overridden by 'roundlimit' cvar"
);
Event EV_Level_GetClockSide
(
"clockside",
EV_DEFAULT,
NULL,
NULL,
"Gets which side the clock is on... 'axis' or 'allies' win when time is up",
EV_GETTER
);
Event EV_Level_SetClockSide
(
"clockside",
EV_DEFAULT,
"s",
"axis_or_allies",
"Sets which side the clock is on... 'axis' or 'allies' win when time is up",
EV_SETTER
);
Event EV_Level_SetClockSide2(
"clockside",
EV_DEFAULT,
"s",
"axis_allies_draw_kills",
"Sets which side the clock is on... 'axis' or 'allies' win when time is up, 'kills' gives the win to the team with "
"more live members,"
"'draw' no one wins"
);
Event EV_Level_GetBombPlantTeam
(
"planting_team",
EV_DEFAULT,
NULL,
NULL,
"Gets which is planting the bomb,"
"'axis' or 'allies'",
EV_GETTER
);
Event EV_Level_SetBombPlantTeam
(
"planting_team",
EV_DEFAULT,
"s",
"axis_or_allies",
"Sets which is planting the bomb,"
"'axis' or 'allies'",
EV_SETTER
);
Event EV_Level_SetBombPlantTeam2(
"planting_team",
EV_DEFAULT,
"s",
"axis_allies_draw_kills",
"which is planting the bomb,"
"'axis' or 'allies'"
);
Event EV_Level_GetTargetsToDestroy
(
"targets_to_destroy",
EV_DEFAULT,
NULL,
NULL,
"Gets the number of bomb targets that must be destroyed",
EV_GETTER
);
Event EV_Level_SetTargetsToDestroy
(
"targets_to_destroy",
EV_DEFAULT,
"i",
"num",
"Sets the number of bomb targets that must be destroyed",
EV_SETTER
);
Event EV_Level_SetTargetsToDestroy2
(
"targets_to_destroy",
EV_DEFAULT,
"i",
"num",
"the number of bomb targets that must be destroyed",
EV_NORMAL
);
Event EV_Level_GetTargetsDestroyed
(
"targets_destroyed",
EV_DEFAULT,
NULL,
NULL,
"Gets the number of bomb targets that have been destroyed",
EV_GETTER
);
Event EV_Level_SetTargetsDestroyed
(
"targets_destroyed",
EV_DEFAULT,
"i",
"num",
"Sets the number of bomb targets that have been destroyed",
EV_SETTER
);
Event EV_Level_SetTargetsDestroyed2
(
"targets_destroyed",
EV_DEFAULT,
"i",
"num",
"the number of bomb targets that have been destroyed",
EV_NORMAL
);
Event EV_Level_GetBombsPlanted
(
"bombs_planted",
EV_DEFAULT,
NULL,
NULL,
"Gets the number of bombs that are set",
EV_GETTER
);
Event EV_Level_SetBombsPlanted
(
"bombs_planted",
EV_DEFAULT,
"i",
"num",
"Sets the number of bombs that are set",
EV_SETTER
);
Event EV_Level_SetBombsPlanted2
(
"bombs_planted",
EV_DEFAULT,
"i",
"num",
"the number of bombs that are set",
EV_NORMAL
);
Event EV_Level_GetRoundBased
(
"roundbased",
EV_DEFAULT,
NULL,
NULL,
"Gets whether or not the game is currently round based or not",
EV_GETTER
);
Event EV_Level_GetObjectiveBased
(
"objectivebased",
EV_DEFAULT,
NULL,
NULL,
"Gets whether or not the game is currently objective based or not",
EV_GETTER
);
Event EV_Level_Rain_Density_Set
(
"rain_density",
EV_DEFAULT,
NULL,
NULL,
"Sets the rain density",
EV_SETTER
);
Event EV_Level_Rain_Density_Get
(
"rain_density",
EV_DEFAULT,
NULL,
NULL,
"Sets the rain density",
EV_GETTER
);
Event EV_Level_Rain_Speed_Set
(
"rain_speed",
EV_DEFAULT,
NULL,
NULL,
"Sets the rain speed",
3
);
Event EV_Level_Rain_Speed_Get
(
"rain_speed",
EV_DEFAULT,
NULL,
NULL,
"Sets the rain speed",
2
);
Event EV_Level_Rain_Speed_Vary_Set
(
"rain_speed_vary",
EV_DEFAULT,
NULL,
NULL,
"Sets the rain speed variance",
EV_SETTER
);
Event EV_Level_Rain_Speed_Vary_Get
(
"rain_speed_vary",
EV_DEFAULT,
NULL,
NULL,
"Sets the rain speed variance",
EV_GETTER
);
Event EV_Level_Rain_Slant_Set
(
"rain_slant",
EV_DEFAULT,
NULL,
NULL,
"Sets the rain slant",
EV_SETTER
);
Event EV_Level_Rain_Slant_Get
(
"rain_slant",
EV_DEFAULT,
NULL,
NULL,
"Sets the rain slant",
EV_GETTER
);
Event EV_Level_Rain_Length_Set
(
"rain_length",
EV_DEFAULT,
NULL,
NULL,
"Sets the rain length",
EV_SETTER
);
Event EV_Level_Rain_Length_Get
(
"rain_length",
EV_DEFAULT,
NULL,
NULL,
"Sets the rain length",
EV_GETTER
);
Event EV_Level_Rain_Min_Dist_Set
(
"rain_min_dist",
EV_DEFAULT,
NULL,
NULL,
"Sets the rain min_dist",
EV_SETTER
);
Event EV_Level_Rain_Min_Dist_Get
(
"rain_min_dist",
EV_DEFAULT,
NULL,
NULL,
"Sets the rain min_dist",
EV_GETTER
);
Event EV_Level_Rain_Width_Set
(
"rain_width",
EV_DEFAULT,
NULL,
NULL,
"Sets the rain width",
EV_SETTER
);
Event EV_Level_Rain_Width_Get
(
"rain_width",
EV_DEFAULT,
NULL,
NULL,
"Sets the rain width",
EV_GETTER
);
Event EV_Level_Rain_Shader_Set
(
"rain_shader",
EV_DEFAULT,
NULL,
NULL,
"Sets the rain ",
EV_SETTER
);
Event EV_Level_Rain_Shader_Get
(
"rain_shader",
EV_DEFAULT,
NULL,
NULL,
"Sets the rain ",
EV_GETTER
);
Event EV_Level_Rain_NumShaders_Set
(
"rain_numshaders",
EV_DEFAULT,
NULL,
NULL,
"Sets the rain numshaders",
EV_SETTER
);
Event EV_Level_Rain_NumShaders_Get
(
"rain_numshaders",
EV_DEFAULT,
NULL,
NULL,
"Sets the rain numshaders",
EV_GETTER
);
Event EV_Level_AddBadPlace
(
"badplace",
EV_DEFAULT,
"svfSF",
"name origin radius [team] [duration]",
"Enables a 'bad place' for AI of team 'american', 'german', or (default) 'both' to avoid, and optionally gives it "
"a duration",
EV_NORMAL
);
Event EV_Level_RemoveBadPlace
(
"removebadplace",
EV_DEFAULT,
"s",
"name",
"Removes a 'bad place'",
EV_NORMAL
);
Event EV_Level_IgnoreClock
(
"ignoreclock",
EV_DEFAULT,
"i",
"ignoreclock",
"Tells a level weather or not to ignore the clock",
EV_NORMAL
);
//
// Added in OPM
//
Event EV_Level_SetForceTeamObjectiveLocation
(
"force_team_objective",
EV_DEFAULT,
"i",
"force",
"Whether or not to force using a separate objective location for each team.\n"
"1 (forced on TOW/Liberation match): different location for each team\n"
"0 (default): teams use same location on non-TOW/non-Liberation match\n",
EV_SETTER
);
Event EV_Level_GetForceTeamObjectiveLocation
(
"force_team_objective",
EV_DEFAULT,
NULL,
NULL,
"Return 1 if each team has a separate objective location",
EV_GETTER
);
extern Event EV_Entity_Start;
CLASS_DECLARATION(Listener, Level, NULL) {
{&EV_Level_GetTime, &Level::GetTime },
{&EV_Level_GetTotalSecrets, &Level::GetTotalSecrets },
{&EV_Level_GetFoundSecrets, &Level::GetFoundSecrets },
{&EV_Level_PreSpawnSentient, &Level::PreSpawnSentient },
{&EV_Level_GetAlarm, &Level::GetAlarm },
{&EV_Level_SetAlarm, &Level::SetAlarm },
{&EV_Level_SetNoDropHealth, &Level::SetNoDropHealth },
{&EV_Level_SetNoDropWeapons, &Level::SetNoDropWeapons },
{&EV_Level_GetRoundStarted, &Level::EventGetRoundStarted },
{&EV_Level_GetLoopProtection, &Level::GetLoopProtection },
{&EV_Level_SetLoopProtection, &Level::SetLoopProtection },
{&EV_Level_GetPapersLevel, &Level::GetPapersLevel },
{&EV_Level_SetPapersLevel, &Level::SetPapersLevel },
{&EV_Level_GetDMRespawning, &Level::EventGetDMRespawning },
{&EV_Level_SetDMRespawning, &Level::EventSetDMRespawning },
{&EV_Level_SetDMRespawning2, &Level::EventSetDMRespawning },
{&EV_Level_GetDMRoundLimit, &Level::EventGetDMRoundLimit },
{&EV_Level_SetDMRoundLimit, &Level::EventSetDMRoundLimit },
{&EV_Level_SetDMRoundLimit2, &Level::EventSetDMRoundLimit },
{&EV_Level_GetClockSide, &Level::EventGetClockSide },
{&EV_Level_SetClockSide, &Level::EventSetClockSide },
{&EV_Level_SetClockSide2, &Level::EventSetClockSide },
{&EV_Level_GetBombPlantTeam, &Level::EventGetBombPlantTeam },
{&EV_Level_SetBombPlantTeam, &Level::EventSetBombPlantTeam },
{&EV_Level_SetBombPlantTeam2, &Level::EventSetBombPlantTeam },
{&EV_Level_GetTargetsToDestroy, &Level::EventGetTargetsToDestroy },
{&EV_Level_SetTargetsToDestroy, &Level::EventSetTargetsToDestroy },
{&EV_Level_SetTargetsToDestroy2, &Level::EventSetTargetsToDestroy },
{&EV_Level_GetTargetsDestroyed, &Level::EventGetTargetsDestroyed },
{&EV_Level_SetTargetsDestroyed, &Level::EventSetTargetsDestroyed },
{&EV_Level_SetTargetsDestroyed2, &Level::EventSetTargetsDestroyed },
{&EV_Level_GetBombsPlanted, &Level::EventGetBombsPlanted },
{&EV_Level_SetBombsPlanted, &Level::EventSetBombsPlanted },
{&EV_Level_SetBombsPlanted2, &Level::EventSetBombsPlanted },
{&EV_Level_GetRoundBased, &Level::EventGetRoundBased },
{&EV_Level_GetObjectiveBased, &Level::EventGetObjectiveBased },
{&EV_Level_Rain_Density_Set, &Level::EventRainDensitySet },
{&EV_Level_Rain_Density_Get, &Level::EventRainDensityGet },
{&EV_Level_Rain_Speed_Set, &Level::EventRainSpeedSet },
{&EV_Level_Rain_Speed_Get, &Level::EventRainSpeedGet },
{&EV_Level_Rain_Speed_Vary_Set, &Level::EventRainSpeedVarySet },
{&EV_Level_Rain_Speed_Vary_Get, &Level::EventRainSpeedVaryGet },
{&EV_Level_Rain_Slant_Set, &Level::EventRainSlantSet },
{&EV_Level_Rain_Slant_Get, &Level::EventRainSlantGet },
{&EV_Level_Rain_Length_Set, &Level::EventRainLengthSet },
{&EV_Level_Rain_Length_Get, &Level::EventRainLengthGet },
{&EV_Level_Rain_Min_Dist_Set, &Level::EventRainMin_DistSet },
{&EV_Level_Rain_Min_Dist_Get, &Level::EventRainMin_DistGet },
{&EV_Level_Rain_Width_Set, &Level::EventRainWidthSet },
{&EV_Level_Rain_Width_Get, &Level::EventRainWidthGet },
{&EV_Level_Rain_Shader_Set, &Level::EventRainShaderSet },
{&EV_Level_Rain_Shader_Get, &Level::EventRainShaderGet },
{&EV_Level_Rain_NumShaders_Set, &Level::EventRainNumShadersSet },
{&EV_Level_Rain_NumShaders_Get, &Level::EventRainNumShadersGet },
{&EV_Level_AddBadPlace, &Level::EventAddBadPlace },
{&EV_Level_RemoveBadPlace, &Level::EventRemoveBadPlace },
{&EV_Level_IgnoreClock, &Level::EventIgnoreClock },
{&EV_Level_SetForceTeamObjectiveLocation, &Level::SetForceTeamObjectiveLocation},
{&EV_Level_GetForceTeamObjectiveLocation, &Level::GetForceTeamObjectiveLocation},
{NULL, NULL }
};
void Level::GetTime(Event *ev)
{
ev->AddFloat(level.time);
}
void Level::GetTotalSecrets(Event *ev)
{
ev->AddInteger(total_secrets);
}
void Level::GetFoundSecrets(Event *ev)
{
ev->AddInteger(found_secrets);
}
Level::Level()
{
Init();
}
Level::~Level() {}
void Level::Init(void)
{
m_HeadSentient[1] = NULL;
m_HeadSentient[0] = NULL;
spawn_entnum = -1;
current_map = NULL;
framenum = 0;
time = 0;
frametime = 0;
level_name = "";
mapname = "";
spawnpoint = "";
nextmap = "";
total_secrets = 0;
found_secrets = 0;
m_iCuriousVoiceTime = 0;
m_iAttackEntryAnimTime = 0;
playerfrozen = false;
intermissiontime = 0.0f;
exitintermission = 0;
memset(&impact_trace, 0, sizeof(trace_t));
cinematic = false;
ai_on = true;
near_exit = false;
mission_failed = false;
m_bAlarm = false;
m_iPapersLevel = 0;
died_already = false;
water_color = vec_zero;
lava_color = vec_zero;
lava_alpha = 0.0f;
water_alpha = 0.0f;
saved_soundtrack = "";
current_soundtrack = "";
automatic_cameras.ClearObjectList();
m_fade_time_start = 0.0f;
m_fade_time = -1.0f;
m_fade_color = vec_zero;
m_fade_style = additive;
m_fade_alpha = 0;
m_letterbox_fraction = 0;
m_letterbox_time = -1;
m_letterbox_time_start = 0;
m_letterbox_dir = letterbox_out;
m_numArenas = 1;
m_voteTime = 0;
m_nextVoteTime = 0;
m_voteYes = 0;
m_voteNo = 0;
m_numVoters = 0;
m_LoopProtection = true;
m_LoopDrop = true;
m_letterbox_time = -1.0f;
m_vObjectiveLocation = vec_zero;
m_vAlliedObjectiveLocation = vec_zero;
m_vAxisObjectiveLocation = vec_zero;
m_bForceTeamObjectiveLocation = false;
svsEndTime = 0;
earthquake_magnitude = 0;
mHealthPopCount = 0;
mbNoDropHealth = false;
mbNoDropWeapons = false;
spawning = false;
m_bIgnoreClock = false;
svsStartFloatTime = 0;
m_fLandmarkXDistMin = 0;
m_fLandmarkXDistMax = 0;
m_fLandmarkYDistMin = 0;
m_fLandmarkYDistMax = 0;
m_pLandmarks = NULL;
m_iMaxLandmarks = 0;
m_iLandmarksCount = 0;
m_badPlaces.ClearObjectList();
m_pAIStats = NULL;
m_bScriptSpawn = false;
m_bRejectSpawn = false;
}
void Level::CleanUp(qboolean samemap, qboolean resetConfigStrings)
{
int i;
DisableListenerNotify++;
if (g_gametype->integer != GT_SINGLE_PLAYER) {
dmManager.Reset();
}
Director.Reset(samemap);
ClearCachedStatemaps();
// clear active current bots
G_ResetBots();
assert(active_edicts.next);
assert(active_edicts.next->prev == &active_edicts);
assert(active_edicts.prev);
assert(active_edicts.prev->next == &active_edicts);
assert(free_edicts.next);
assert(free_edicts.next->prev == &free_edicts);
assert(free_edicts.prev);
assert(free_edicts.prev->next == &free_edicts);
while (active_edicts.next != &active_edicts) {
assert(active_edicts.next != &free_edicts);
assert(active_edicts.prev != &free_edicts);
assert(active_edicts.next);
assert(active_edicts.next->prev == &active_edicts);
assert(active_edicts.prev);
assert(active_edicts.prev->next == &active_edicts);
assert(free_edicts.next);
assert(free_edicts.next->prev == &free_edicts);
assert(free_edicts.prev);
assert(free_edicts.prev->next == &free_edicts);
if (active_edicts.next->entity) {
delete active_edicts.next->entity;
} else {
FreeEdict(active_edicts.next);
}
}
//
// Remove all archived entities
//
for (i = m_SimpleArchivedEntities.NumObjects(); i > 0; i--) {
delete m_SimpleArchivedEntities.ObjectAt(i);
}
cinematic = false;
ai_on = true;
near_exit = false;
mission_failed = false;
died_already = false;
globals.num_entities = game.maxclients + 1;
gi.LocateGameData(g_entities, game.maxclients + 1, sizeof(gentity_t), &game.clients[0].ps, sizeof(gclient_t));
// clear up all AI node information
PathManager.ResetNodes();
// clear out automatic cameras
automatic_cameras.ClearObjectList();
// clear out level script variables
level.Vars()->ClearList();
// Clear out parm vars
parm.Vars()->ClearList();
// initialize the game variables
// these get restored by the persistant data, so we can safely clear them here
game.Vars()->ClearList();
// clearout any waiting events
L_ClearEventList();
// reset all edicts
ResetEdicts();
// reset all grenade hints
GrenadeHint::ResetHints();
// reset projectile targets
ClearProjectileTargets();
// Reset the boss health cvar
gi.cvar_set("bosshealth", "0");
Actor::ResetBodyQueue();
Health::ResetHealthQueue();
if (world) {
world->FreeTargetList();
}
num_earthquakes = 0;
AddWaitTill(STRING_PRESPAWN);
AddWaitTill(STRING_SPAWN);
AddWaitTill(STRING_PLAYERSPAWN);
AddWaitTill(STRING_SKIP);
AddWaitTill(STRING_POSTTHINK);
if (g_gametype->integer >= GT_TEAM_ROUNDS && g_gametype->integer <= GT_LIBERATION) {
AddWaitTill(STRING_ROUNDSTART);
}
if (g_gametype->integer > GT_FFA) {
AddWaitTill(STRING_ALLIESWIN);
AddWaitTill(STRING_AXISWIN);
AddWaitTill(STRING_DRAW);
}
if (resetConfigStrings) {
gi.setConfigstring(CS_RAIN_DENSITY, "0");
gi.setConfigstring(CS_RAIN_SPEED, "2048");
gi.setConfigstring(CS_RAIN_SPEEDVARY, "512");
gi.setConfigstring(CS_RAIN_SLANT, "50");
gi.setConfigstring(CS_RAIN_LENGTH, "90");
gi.setConfigstring(CS_RAIN_MINDIST, "512");
gi.setConfigstring(CS_RAIN_WIDTH, "1");
gi.setConfigstring(CS_RAIN_SHADER, "textures/rain");
gi.setConfigstring(CS_RAIN_NUMSHADERS, "0");
gi.setConfigstring(CS_CURRENT_OBJECTIVE, "");
for (i = 0; i < MAX_OBJECTIVES; i++) {
gi.setConfigstring(CS_OBJECTIVES + i, "");
}
gi.setConfigstring(CS_VOTE_TIME, "");
gi.setConfigstring(CS_VOTE_STRING, "");
gi.setConfigstring(CS_VOTE_YES, "");
gi.setConfigstring(CS_VOTE_NO, "");
gi.setConfigstring(CS_VOTE_UNDECIDED, "");
}
DisableListenerNotify--;
svsStartFloatTime = svsFloatTime;
FreeLandmarks();
}
/*
==============
ResetEdicts
==============
*/
void Level::ResetEdicts(void)
{
int i;
memset(g_entities, 0, game.maxentities * sizeof(g_entities[0]));
// Add all the edicts to the free list
LL_Reset(&free_edicts, next, prev);
LL_Reset(&active_edicts, next, prev);
for (i = 0; i < game.maxentities; i++) {
LL_Add(&free_edicts, &g_entities[i], next, prev);
}
for (i = 0; i < game.maxclients; i++) {
// set client fields on player ents
g_entities[i].client = game.clients + i;
G_InitClientPersistant(&game.clients[i]);
}
globals.num_entities = game.maxclients;
}
qboolean Level::inhibitEntity(int spawnflags)
{
if (!detail->integer && (spawnflags & SPAWNFLAG_DETAIL)) {
return qtrue;
}
if (g_gametype->integer != GT_SINGLE_PLAYER) {
return (spawnflags & SPAWNFLAG_NOT_DEATHMATCH) ? qtrue : qfalse;
}
if (!developer->integer && (spawnflags & SPAWNFLAG_DEVELOPMENT)) {
return qtrue;
}
if (!Q_stricmp(mapname, "t3l2")) {
// Added in 2.0.
// FIXME: there should be a better way to handle
// specific maps
return (spawnflags & SPAWNFLAG_NOT_EASY) ? qtrue : qfalse;
}
switch (skill->integer) {
case 0:
return (spawnflags & SPAWNFLAG_NOT_EASY) != 0;
case 1:
return (spawnflags & SPAWNFLAG_NOT_MEDIUM) != 0;
case 2:
case 3:
return (spawnflags & SPAWNFLAG_NOT_HARD);
}
/*
#ifdef _CONSOLE
if (spawnflags & SPAWNFLAG_NOCONSOLE)
#else
if (spawnflags & SPAWNFLAG_NOPC)
#endif
{
return qtrue;
}
*/
return qfalse;
}
void Level::setSkill(int value)
{
int skill_level;
skill_level = floor((float)value);
skill_level = bound(skill_level, 0, 3);
gi.cvar_set("skill", va("%d", skill_level));
}
void Level::setTime(int levelTime)
{
svsTime = levelTime;
inttime = levelTime - svsStartTime;
svsFloatTime = levelTime / 1000.0f;
time = inttime / 1000.0f;
}
void Level::setFrametime(int frametime)
{
intframetime = frametime;
this->frametime = frametime / 1000.0f;
}
void Level::SpawnEntities(char *entities, int svsTime)
{
int inhibit, radnum = 0, count = 0;
const char *value;
SpawnArgs args;
Listener *listener;
Entity *ent;
int t1, t2;
int start, end;
char name[128];
if (gi.Cvar_Get("g_invulnoverride", "0", 0)->integer == 1) {
// Added in 2.30
// Clear the invulnerable override when loading
gi.cvar_set("g_invulnoverride", "0");
}
Com_Printf("-------------------- Spawning Entities -----------------------\n");
t1 = gi.Milliseconds();
memset(skel_index, 0xff, sizeof(skel_index));
// set up time so functions still have valid times
setTime(svsTime);
setFrametime(50);
gi.LoadResource("*144");
setSkill(skill->integer);
// reset out count of the number of game traces
sv_numtraces = 0;
// parse world
entities = args.Parse(entities);
spawn_entnum = ENTITYNUM_WORLD;
args.SpawnInternal();
gi.LoadResource("*147");
// Set up for a new map
PathManager.LoadNodes();
gi.LoadResource("*147a");
Com_Printf("-------------------- Actual Spawning Entities -----------------------\n");
start = gi.Milliseconds();
// parse ents
inhibit = 0;
for (entities = args.Parse(entities); entities != NULL; entities = args.Parse(entities)) {
// remove things (except the world) from different skill levels or deathmatch
spawnflags = 0;
value = args.getArg("spawnflags");
if (value) {
spawnflags = atoi(value);
value = args.getArg("classname");
if (!value || (Q_stricmp(value, "info_pathnode") && Q_stricmp(value, "info_patharea"))) {
if (inhibitEntity(spawnflags)) {
inhibit++;
continue;
}
}
}
listener = args.SpawnInternal();
if (listener) {
radnum++;
if (listener->isSubclassOf(Entity)) {
count++;
ent = (Entity *)listener;
ent->radnum = radnum;
Q_strncpyz(ent->edict->entname, ent->getClassID(), sizeof(ent->edict->entname));
ent->PostEvent(EV_Entity_Start, -1.0, 0);
Com_sprintf(name, sizeof(name), "i%d", radnum);
gi.LoadResource(name);
}
}
}
end = gi.Milliseconds();
Com_Printf("-------------------- Actual Spawning Entities Done ------------------ %i ms\n", end - start);
gi.LoadResource("*147b");
world->UpdateConfigStrings();
Event *ev = new Event(EV_Level_PreSpawnSentient);
PostEvent(ev, EV_SPAWNENTITIES);
L_ProcessPendingEvents();
gi.LoadResource("*148");
if (g_gametype->integer != GT_SINGLE_PLAYER) {
dmManager.InitGame();
}
gi.LoadResource("*148a");
// Added in OPM
// Check for single-player before pre-creating a player instance
if (game.maxclients == 1 && g_gametype->integer == GT_SINGLE_PLAYER) {
spawn_entnum = 0;
new Player;
}
gi.LoadResource("*148b");
m_LoopProtection = false;
RemoveWaitTill(STRING_PRESPAWN);
Unregister(STRING_PRESPAWN);
m_LoopProtection = true;
gi.LoadResource("*150");
t2 = gi.Milliseconds();
Com_Printf("%i entities spawned\n", count);
Com_Printf("%i simple entities spawned\n", radnum);
Com_Printf("%i entities inhibited\n", inhibit);
Com_Printf("-------------------- Spawning Entities Done ------------------ %i ms\n", t2 - t1);
//
// create landmarks
//
ComputeDMWaypoints();
}
void Level::ComputeDMWaypoints()
{
qboolean shouldSetDefaultLandmark;
float startXDistMin, startXDistMax;
float startYDistMin, startYDistMax;
int i;
if (g_gametype->integer == GT_SINGLE_PLAYER) {
m_fLandmarkXDistMax = 1;
m_fLandmarkYDistMax = 1;
m_fLandmarkXDistMin = 0;
m_fLandmarkYDistMin = 0.0;
return;
}
shouldSetDefaultLandmark = qfalse;
//
// calculate the world bounds from entities
//
if (m_fLandmarkXDistMin == m_fLandmarkXDistMax && m_fLandmarkYDistMin == m_fLandmarkYDistMax
&& m_fLandmarkXDistMax == m_fLandmarkYDistMax) {
shouldSetDefaultLandmark = qtrue;
for (i = 0; i < game.maxentities; i++) {
gentity_t *ent = &g_entities[i];
if (ent->entity) {
AddLandmarkOrigin(ent->entity->origin);
shouldSetDefaultLandmark = qfalse;
}
}
}
if (shouldSetDefaultLandmark) {
startXDistMax = 1;
startYDistMax = 1;
startXDistMin = 0;
startYDistMin = 0;
} else {
startYDistMax = m_fLandmarkYDistMax;
startYDistMin = m_fLandmarkYDistMin;
startXDistMax = m_fLandmarkXDistMax;
startXDistMin = m_fLandmarkXDistMin;
}
m_fLandmarkXDistMin = startXDistMin + (startXDistMax - startXDistMin) / 3.f;
m_fLandmarkXDistMax = startXDistMin + (startXDistMax - startXDistMin) * 2.f / 3.f;
m_fLandmarkYDistMin = startYDistMin + (startYDistMax - startYDistMin) / 3.f;
m_fLandmarkYDistMax = startYDistMin + (startYDistMax - startYDistMin) * 2.f / 3.f;
}
void Level::AddLandmarkOrigin(const Vector& origin)
{
float yaw;
vec3_t angles;
vec3_t dir;
float dist;
yaw = origin.toYaw();
angles[0] = angles[2] = 0;
angles[1] = yaw + 90 - world->m_fNorth;
AngleVectors(angles, dir, NULL, NULL);
dist = origin.lengthXY();
VectorScale(dir, dist, dir);
if (m_fLandmarkYDistMax == m_fLandmarkYDistMin && m_fLandmarkXDistMin == m_fLandmarkXDistMax
&& m_fLandmarkYDistMax == m_fLandmarkXDistMin) {
m_fLandmarkYDistMin = dir[1];
m_fLandmarkXDistMax = dir[0];
m_fLandmarkYDistMax = dir[1] + 1.0;
m_fLandmarkXDistMin = dir[0] - 1.0;
} else {
if (m_fLandmarkYDistMin > dir[1]) {
m_fLandmarkYDistMin = dir[1];
}
if (m_fLandmarkYDistMax < dir[1]) {
m_fLandmarkYDistMax = dir[1];
}
if (m_fLandmarkXDistMax < dir[0]) {
m_fLandmarkXDistMax = dir[0];
}
if (m_fLandmarkXDistMin > dir[0]) {
m_fLandmarkXDistMin = dir[0];
}
}
}
void Level::AddLandmarkName(const str& name, const Vector& origin)
{
landmark_t *landmark;
int i;
if (m_pLandmarks) {
if (m_iLandmarksCount == m_iMaxLandmarks) {
// reallocate the landmark list with twice the size
landmark_t **oldLandmarks = m_pLandmarks;
m_iMaxLandmarks *= 2;
m_pLandmarks = new landmark_t *[m_iMaxLandmarks];
for (i = 0; i < m_iLandmarksCount; i++) {
m_pLandmarks[i] = oldLandmarks[i];
}
delete[] oldLandmarks;
}
} else {
// create the landmark list for the first time
m_iMaxLandmarks = 8;
m_iLandmarksCount = 0;
m_pLandmarks = new landmark_t *[8];
}
//
// create a new landmark
//
landmark = m_pLandmarks[m_iLandmarksCount] = new landmark_t();
landmark->m_sName = name;
landmark->m_vOrigin = origin;
}
void Level::FreeLandmarks()
{
landmark_t *landmark;
if (m_pLandmarks) {
int i;
for (i = 0; i < m_iLandmarksCount; i++) {
landmark = m_pLandmarks[i];
if (landmark) {
delete landmark;
}
}
delete[] m_pLandmarks;
m_pLandmarks = NULL;
m_iLandmarksCount = 0;
m_iMaxLandmarks = 0;
}
}
str Level::GetDynamicDMLocations(const Vector& origin)
{
str name = "nothing";
int i;
float shortestDistSqr = 0;
for (i = 0; i < m_iLandmarksCount; i++) {
landmark_t *landmark = m_pLandmarks[i];
Vector delta = origin - landmark->m_vOrigin;
float distSqr = delta.lengthSquared();
if (i == 0 || distSqr < shortestDistSqr) {
shortestDistSqr = distSqr;
name = landmark->m_sName;
}
}
return name;
}
str Level::GetDMLocation(const Vector& origin)
{
float yaw;
vec3_t angles;
vec3_t dir;
float dist;
if (m_pLandmarks) {
//
// use the dynamic dm locations
//
return GetDynamicDMLocations(origin);
}
yaw = origin.toYaw();
angles[0] = angles[2] = 0;
angles[1] = yaw + 90 - world->m_fNorth;
AngleVectors(angles, dir, NULL, NULL);
dist = origin.lengthXY();
VectorScale(dir, dist, dir);
if (dir[0] >= m_fLandmarkXDistMin) {
if (dir[0] > m_fLandmarkXDistMax) {
if (dir[1] >= m_fLandmarkYDistMin) {
if (dir[1] > m_fLandmarkYDistMax) {
return "North East corner";
} else {
return "East side";
}
} else {
return "South East corner";
}
} else if (dir[1] >= m_fLandmarkYDistMin) {
if (dir[1] > m_fLandmarkYDistMax) {
return "North side";
} else {
return "center";
}
} else {
return "South side";
}
} else if (dir[1] >= m_fLandmarkYDistMin) {
if (dir[1] > m_fLandmarkYDistMax) {
return "North West corner";
} else {
return "West side";
}
} else {
return "South West corner";
}
}
void Level::PreSpawnSentient(Event *ev)
{
GameScript *script;
// general initialization
FindTeams();
script = Director.GetScript(m_mapscript);
if (script) {
gi.DPrintf("Adding script: '%s'\n", m_mapscript.c_str());
m_LoopProtection = false;
Director.ExecuteThread(m_mapscript);
m_LoopProtection = true;
}
PathManager.CreatePaths();
}
bool Level::RoundStarted()
{
return !WaitTillDefined(STRING_ROUNDSTART);
}
bool Level::PreSpawned(void)
{
return !WaitTillDefined(STRING_PRESPAWN);
}
bool Level::Spawned(void)
{
return !WaitTillDefined(STRING_SPAWN);
}
void Level::ServerSpawned(void)
{
int i;
gclient_t *client;
gentity_t *ent;
for (i = 0, client = game.clients; i < game.maxclients; i++, client++) {
client->ps.commandTime = svsTime;
}
if (!Spawned()) {
RemoveWaitTill(STRING_SPAWN);
Director.Pause();
for (ent = active_edicts.next; ent != &active_edicts; ent = ent->next) {
ent->entity->Unregister(STRING_SPAWN);
}
Director.Unpause();
Unregister(STRING_SPAWN);
} else {
Director.LoadMenus();
}
spawning = false;
}
void Level::SetMap(const char *themapname)
{
const char *spawnpos;
int i;
str text;
Init();
spawning = true;
// set a specific spawnpoint if the map was started with a $
spawnpos = strchr((char *)themapname, '$');
if (spawnpos) {
mapname = str(themapname, 0, spawnpos - themapname);
spawnpoint = spawnpos + 1;
} else {
mapname = themapname;
spawnpoint = "";
}
current_map = (char *)themapname;
level_name = mapname;
for (i = 0; i < level_name.length(); i++) {
if (level_name[i] == '.') {
level_name[i] = 0;
break;
}
}
m_mapscript = "maps/" + level_name + ".scr";
m_precachescript = "maps/" + level_name + "_precache.scr";
m_pathfile = "maps/" + level_name + ".pth";
m_mapfile = "maps/" + level_name + ".bsp";
}
void Level::LoadAllScripts(const char *name, const char *extension)
{
char **scriptFiles;
char filename[MAX_QPATH];
int numScripts;
scriptFiles = gi.FS_ListFiles(name, extension, qfalse, &numScripts);
if (!scriptFiles || !numScripts) {
return;
}
for (int i = 0; i < numScripts; i++) {
Com_sprintf(filename, sizeof(filename), "%s/%s", name, scriptFiles[i]);
// Compile the script
Director.GetScript(filename);
}
gi.FS_FreeFileList(scriptFiles);
}
void Level::Precache(void)
{
setTime(svsStartTime);
setFrametime(50);
if (gi.FS_ReadFile(m_precachescript, NULL, qtrue) != -1) {
gi.DPrintf("Adding script: '%s'\n", m_precachescript.c_str());
// temporarily disable the loop protection
// because caching models require time
m_LoopProtection = false;
Director.ExecuteThread(m_precachescript);
m_LoopProtection = true;
}
if (g_gametype->integer == GT_SINGLE_PLAYER) {
LoadAllScripts("anim", ".scr");
}
LoadAllScripts("global", ".scr");
InitVoteOptions();
// Added in OPM
// Cache all player models in multi-player
// This avoids using precache scripts
if (g_gametype->integer != GT_SINGLE_PLAYER) {
char **fileList;
int numFiles;
int i;
fileList = gi.FS_ListFiles("models/player", ".tik", qfalse, &numFiles);
for (i = 0; i < numFiles; i++) {
const char *filename = fileList[i];
const size_t filelen = strlen(filename);
if (!Q_stricmpn(filename, "allied_", 7) || !Q_stricmpn(filename, "american_", 9)
|| !Q_stricmpn(filename, "german_", 7) || !Q_stricmpn(filename, "IT_", 3)
|| !Q_stricmpn(filename, "SC_", 3)) {
CacheResource(va("models/player/%s", filename));
}
}
gi.FS_FreeFileList(fileList);
}
}
/*
================
FindTeams
Chain together all entities with a matching team field.
Entity teams are used for item groups and multi-entity mover groups.
All but the first will have the FL_TEAMSLAVE flag set and teammaster field set
All but the last will have the teamchain field set to the next one
================
*/
void Level::FindTeams()
{
gentity_t *ent, *ent2;
Entity *e, *e2;
int i, j;
int c, c2;
c = 0;
c2 = 0;
for (i = 1, ent = g_entities + i; i < globals.num_entities; i++, ent++) {
if (!ent->inuse) {
continue;
}
e = ent->entity;
if (!e->moveteam.length()) {
continue;
}
if (e->flags & FL_TEAMSLAVE) {
continue;
}
e->teammaster = e;
c++;
c2++;
for (j = i + 1, ent2 = ent + 1; j < globals.num_entities; j++, ent2++) {
if (!ent2->inuse) {
continue;
}
e2 = ent->entity;
if (!e2->moveteam.length()) {
continue;
}
if (e2->flags & FL_TEAMSLAVE) {
continue;
}
if (!strcmp(e->moveteam, e2->moveteam)) {
c2++;
e2->teamchain = e->teamchain;
e->teamchain = e2;
e2->teammaster = e;
e2->flags |= FL_TEAMSLAVE;
// make sure that targets only point at the master
if (e2->targetname) {
e->targetname = e2->targetname;
e2->targetname = NULL;
}
}
}
}
G_Printf("%i teams with %i entities\n", c, c2);
}
gentity_t *Level::AllocEdict(Entity *entity)
{
int i;
gentity_t *edict;
if (spawn_entnum >= 0) {
edict = &g_entities[spawn_entnum];
spawn_entnum = -1;
assert(!edict->inuse && !edict->entity);
// free up the entity pointer in case we took one that still exists
if (edict->inuse && edict->entity) {
delete edict->entity;
}
} else {
edict = &g_entities[game.maxclients];
for (i = game.maxclients; i < globals.num_entities; i++, edict++) {
// the first couple seconds of server time can involve a lot of
// freeing and allocating, so relax the replacement policy
if (!edict->inuse && ((edict->freetime < 2.0f) || (time - edict->freetime > 0.5f))) {
break;
}
}
// allow two spots for none and world
if (i == globals.max_entities - 2.0f) {
// Try one more time before failing, relax timing completely
edict = &g_entities[game.maxclients];
for (i = game.maxclients; i < globals.num_entities; i++, edict++) {
if (!edict->inuse) {
break;
}
}
if (i == globals.max_entities - 2.0f) {
gi.Error(ERR_DROP, "Level::AllocEdict: no free edicts");
}
}
}
LL_Remove(edict, next, prev);
InitEdict(edict);
LL_Add(&active_edicts, edict, next, prev);
// Tell the server about our data since we just spawned something
if ((edict->s.number < ENTITYNUM_WORLD) && (globals.num_entities <= edict->s.number)) {
globals.num_entities = edict->s.number + 1;
gi.LocateGameData(
g_entities, globals.num_entities, sizeof(gentity_t), &game.clients[0].ps, sizeof(game.clients[0])
);
}
edict->entity = entity;
return edict;
}
void Level::FreeEdict(gentity_t *ed)
{
gclient_t *client;
// clear the model so it decreases the user count
// and free memory if the model is not used anywhere
gi.clearmodel(ed);
// unlink from world
gi.unlinkentity(ed);
LL_Remove(ed, next, prev);
client = ed->client;
memset(ed, 0, sizeof(*ed));
ed->client = client;
ed->freetime = time;
ed->inuse = false;
ed->s.number = ed - g_entities;
LL_Add(&free_edicts, ed, next, prev);
}
void Level::InitEdict(gentity_t *e)
{
int i;
e->inuse = true;
e->s.renderfx |= RF_FRAMELERP;
e->s.number = e - g_entities;
// make sure a default scale gets set
e->s.scale = 1.0f;
// make sure the default constantlight gets set, initalize to r 1.0, g 1.0, b 1.0, r 0
e->s.constantLight = 0xffffff;
e->s.wasframe = 0;
e->spawntime = level.time;
for (i = 0; i < NUM_BONE_CONTROLLERS; i++) {
e->s.bone_tag[i] = -1;
VectorClear(e->s.bone_angles[i]);
EulerToQuat(e->s.bone_angles[i], e->s.bone_quat[i]);
}
}
void Level::AddAutomaticCamera(Camera *cam)
{
automatic_cameras.AddUniqueObject(cam);
}
void Level::InitVoteOptions()
{
if (g_gametype->integer == GT_SINGLE_PLAYER) {
return;
}
m_voteOptions.SetupVoteOptions("callvote.cfg");
// clear the vote time
gi.setConfigstring(CS_VOTE_TIME, "");
}
void Level::SendVoteOptionsFile(gentity_t *ent)
{
const char *voteBuffer;
int voteLength;
int clientNum;
int i, j;
char buffer[2068];
clientNum = ent - g_entities;
if (clientNum < 0 || clientNum >= game.maxclients) {
return;
}
voteBuffer = m_voteOptions.GetVoteOptionsFile(&voteLength);
if (voteLength < MAX_VOTEOPTIONS_UPLOAD_BUFFER_LENGTH) {
Q_strncpyz(buffer, voteBuffer, voteLength + 1);
for (i = 0; i < voteLength; i++) {
if (buffer[i] == '"') {
buffer[i] = 1;
}
}
gi.SendServerCommand(clientNum, "vo0 \"\"\n");
gi.SendServerCommand(clientNum, "vo2 \"%s\"\n", buffer);
} else {
const char *cmd;
int destLength;
int offset;
offset = 0;
for (i = voteLength; i > 0; i -= MAX_VOTEOPTIONS_UPLOAD_BUFFER_LENGTH - 1) {
if (offset == 0) {
cmd = "vo0";
destLength = MAX_VOTEOPTIONS_UPLOAD_BUFFER_LENGTH;
} else if (i >= MAX_VOTEOPTIONS_UPLOAD_BUFFER_LENGTH) {
cmd = "vo1";
destLength = MAX_VOTEOPTIONS_UPLOAD_BUFFER_LENGTH;
} else {
cmd = "vo2";
destLength = i;
}
Q_strncpyz(buffer, &voteBuffer[offset], MAX_VOTEOPTIONS_UPLOAD_BUFFER_LENGTH);
for (j = 0; j < destLength; j++) {
if (buffer[j] == '"') {
buffer[j] = 1;
}
}
gi.SendServerCommand(clientNum, "%s \"%s\"\n", cmd, buffer);
offset += MAX_VOTEOPTIONS_UPLOAD_BUFFER_LENGTH - 1;
}
}
}
bool Level::GetVoteOptionMain(int index, str *outOptionCommand, voteoptiontype_t *outOptionType)
{
return m_voteOptions.GetVoteOptionsMain(index, outOptionCommand, outOptionType);
}
bool Level::GetVoteOptionSub(int index, int listIndex, str *outCommand)
{
return m_voteOptions.GetVoteOptionSub(index, listIndex, outCommand);
}
bool Level::GetVoteOptionMainName(int index, str *outVoteName)
{
return m_voteOptions.GetVoteOptionMainName(index, outVoteName);
}
bool Level::GetVoteOptionSubName(int index, int listIndex, str *outName)
{
return m_voteOptions.GetVoteOptionSubName(index, listIndex, outName);
}
void Level::CheckVote(void)
{
gentity_t *ent;
int i;
int numVoters;
int oldVoteYes, oldVoteNo, oldNumVoters;
if (m_nextVoteTime && m_nextVoteTime < inttime) {
m_nextVoteTime = 0;
gi.SendConsoleCommand(va("%s\n", m_voteString.c_str()));
if (sv_sprinton->integer == 1) {
if (sv_runspeed->integer == 250) {
gi.cvar_set("sv_runspeed", "287");
}
} else if (sv_runspeed->integer == 287) {
gi.cvar_set("sv_runspeed", "250");
}
SetupMaplist();
}
if (!m_voteTime) {
return;
}
oldVoteYes = level.m_voteYes;
oldVoteNo = level.m_voteNo;
oldNumVoters = level.m_numVoters;
level.m_voteYes = 0;
level.m_voteNo = 0;
numVoters = 0;
for (i = 0; i < game.maxclients; i++) {
Player *p;
ent = &g_entities[i];
if (!ent->inuse || !ent->client || !ent->entity) {
continue;
}
p = static_cast<Player *>(ent->entity);
if (p->client->ps.voted) {
if (p->HasVotedYes()) {
level.m_voteYes++;
} else {
level.m_voteNo++;
}
}
numVoters++;
}
level.m_numVoters = numVoters;
if ((svsFloatTime - svsStartFloatTime) * 1000 - m_voteTime >= 30000) {
G_PrintToAllClients(va("%s: %s\n", gi.CL_LV_ConvertString("Vote Failed"), m_voteName.c_str()));
m_voteTime = 0;
gi.setConfigstring(CS_VOTE_TIME, "");
} else if (m_voteYes > m_numVoters / 2) {
// Pass arguments to console
G_PrintToAllClients(va("%s: %s\n", gi.CL_LV_ConvertString("Vote Passed"), m_voteName.c_str()));
m_nextVoteTime = level.inttime + 3000;
m_voteTime = 0;
gi.setConfigstring(CS_VOTE_TIME, "");
} else if (m_voteNo >= m_numVoters / 2) {
G_PrintToAllClients(va("%s: %s\n", gi.CL_LV_ConvertString("Vote Failed"), m_voteName.c_str()));
m_voteTime = 0;
gi.setConfigstring(CS_VOTE_TIME, "");
} else {
if (oldVoteYes != level.m_voteYes) {
gi.setConfigstring(CS_VOTE_YES, va("%i", level.m_voteYes));
}
if (oldVoteNo != level.m_voteNo) {
gi.setConfigstring(CS_VOTE_NO, va("%i", level.m_voteNo));
}
if (oldNumVoters != level.m_numVoters || oldVoteYes != level.m_voteYes || oldVoteNo != level.m_voteNo) {
gi.setConfigstring(CS_VOTE_UNDECIDED, va("%i", level.m_numVoters - (level.m_voteYes + level.m_voteNo)));
}
}
}
void Level::SetupMaplist()
{
const char *p;
const char delim[2] = {';', 0};
cvar_t *maplistVar;
int gameTypeNum;
char buffer[1024];
char gameTypeBuffer[12];
if (!strstr(m_voteString.c_str(), "g_gametype")) {
return;
}
Q_strncpyz(buffer, m_voteString.c_str(), sizeof(buffer));
for (p = strtok(buffer, delim); p; p = strtok(NULL, delim)) {
if (strstr(p, "g_gametype")) {
gameTypeBuffer[0] = p[strlen(p) - 1];
gameTypeBuffer[1] = 0;
gameTypeNum = atoi(gameTypeBuffer);
}
}
switch (gameTypeNum) {
case GT_FFA:
gi.ExecuteConsoleCommand(EXEC_NOW, "exec maplist_ffa.cfg");
maplistVar = gi.Cvar_Get("ui_maplist_ffa", "", 0);
break;
case GT_TEAM:
gi.ExecuteConsoleCommand(EXEC_NOW, "exec maplist_team.cfg");
maplistVar = gi.Cvar_Get("ui_maplist_team", "", 0);
break;
case GT_TEAM_ROUNDS:
gi.ExecuteConsoleCommand(EXEC_NOW, "exec maplist_round.cfg");
maplistVar = gi.Cvar_Get("ui_maplist_round", "", 0);
break;
case GT_OBJECTIVE:
gi.ExecuteConsoleCommand(EXEC_NOW, "exec maplist_obj.cfg");
maplistVar = gi.Cvar_Get("ui_maplist_obj", "", 0);
break;
case GT_TOW:
gi.ExecuteConsoleCommand(EXEC_NOW, "exec maplist_tow.cfg");
maplistVar = gi.Cvar_Get("ui_maplist_tow", "", 0);
break;
case GT_LIBERATION:
gi.ExecuteConsoleCommand(EXEC_NOW, "exec maplist_lib.cfg");
maplistVar = gi.Cvar_Get("ui_maplist_lib", "", 0);
break;
}
gi.cvar_set("sv_maplist", maplistVar->string);
}
void Level::GetAlarm(Event *ev)
{
ev->AddInteger(m_bAlarm);
}
void Level::SetAlarm(Event *ev)
{
m_bAlarm = ev->GetInteger(1);
}
void Level::SetNoDropHealth(Event *ev)
{
mbNoDropHealth = ev->GetInteger(1);
}
void Level::SetNoDropWeapons(Event *ev)
{
mbNoDropWeapons = ev->GetInteger(1);
}
void Level::GetLoopProtection(Event *ev)
{
ev->AddInteger(m_LoopProtection);
}
void Level::SetLoopProtection(Event *ev)
{
m_LoopProtection = ev->GetInteger(1);
}
void Level::GetPapersLevel(Event *ev)
{
ev->AddInteger(m_iPapersLevel);
}
void Level::SetPapersLevel(Event *ev)
{
m_iPapersLevel = ev->GetInteger(1);
}
void Level::EventGetRoundStarted(Event *ev)
{
ev->AddInteger(RoundStarted());
}
void Level::EventGetDMRespawning(Event *ev)
{
ev->AddInteger(dmManager.GameAllowsRespawns());
}
void Level::EventSetDMRespawning(Event *ev)
{
dmManager.SetGameAllowsRespawns(ev->GetBoolean(1));
}
void Level::EventGetDMRoundLimit(Event *ev)
{
ev->AddInteger(dmManager.GetRoundLimit());
}
void Level::EventSetDMRoundLimit(Event *ev)
{
int round_limit = ev->GetInteger(1);
if (round_limit < 0) {
ScriptError("round limit must be greater than 0");
}
dmManager.SetDefaultRoundLimit(round_limit);
}
void Level::EventGetClockSide(Event *ev)
{
ev->AddConstString(dmManager.GetClockSide());
}
void Level::EventSetClockSide(Event *ev)
{
const_str clockside = ev->GetConstString(1);
if (clockside < STRING_ALLIES || clockside > STRING_KILLS) {
ScriptError("clockside must be 'axis', 'allies', 'kills', or 'draw'");
}
dmManager.SetClockSide(clockside);
}
void Level::EventGetBombPlantTeam(Event *ev)
{
ev->AddConstString(dmManager.GetBombPlantTeam());
}
void Level::EventSetBombPlantTeam(Event *ev)
{
const_str plant_team = ev->GetConstString(1);
if (plant_team < STRING_ALLIES || plant_team > STRING_AXIS) {
ScriptError("bombplantteam must be 'axis' or 'allies'");
}
dmManager.SetBombPlantTeam(plant_team);
}
void Level::EventGetTargetsToDestroy(Event *ev)
{
ev->AddInteger(dmManager.GetTargetsToDestroy());
}
void Level::EventSetTargetsToDestroy(Event *ev)
{
dmManager.SetTargetsToDestroy(ev->GetInteger(1));
}
void Level::EventGetTargetsDestroyed(Event *ev)
{
ev->AddInteger(dmManager.GetTargetsDestroyed());
}
void Level::EventSetTargetsDestroyed(Event *ev)
{
dmManager.SetTargetsDestroyed(ev->GetInteger(1));
}
void Level::EventGetBombsPlanted(Event *ev)
{
ev->AddInteger(dmManager.GetBombsPlanted());
}
void Level::EventSetBombsPlanted(Event *ev)
{
dmManager.SetBombsPlanted(ev->GetInteger(1));
}
void Level::EventGetRoundBased(Event *ev)
{
ev->AddInteger(g_gametype->integer >= GT_TEAM_ROUNDS);
}
void Level::EventGetObjectiveBased(Event *ev)
{
ev->AddInteger(g_gametype->integer >= GT_OBJECTIVE);
}
str Level::GetRandomHeadModel(const char *model)
{
char s[MAX_STRING_TOKENS];
gi.GetHeadModel(model, G_Random(gi.NumHeadModels(model)), s);
return s;
}
str Level::GetRandomHeadSkin(const char *model)
{
char s[MAX_STRING_TOKENS];
gi.GetHeadSkin(model, G_Random(gi.NumHeadSkins(model)), s);
return s;
}
void Level::AddEarthquake(earthquake_t *e)
{
if (num_earthquakes == MAX_EARTHQUAKES) {
if (earthquakes[0].m_Thread) {
earthquakes[0].m_Thread->Wait(e->endtime - inttime);
}
num_earthquakes--;
for (int i = 0; i < num_earthquakes; i++) {
earthquakes[i] = earthquakes[i + 1];
}
}
earthquakes[num_earthquakes] = *e;
num_earthquakes++;
e->m_Thread->Pause();
}
void Level::DoEarthquakes(void)
{
int i, j;
earthquake_t *e;
int timedelta;
int rampuptime;
int rampdowntime;
float test_magnitude;
if (num_earthquakes <= 0) {
return;
}
earthquake_magnitude = 0.0f;
for (i = num_earthquakes; i > 0; i--) {
e = &earthquakes[i - 1];
if (inttime >= e->endtime || !e->m_Thread) {
if (e->m_Thread) {
e->m_Thread->Wait(0);
}
num_earthquakes--;
for (j = 0; j < num_earthquakes; j++) {
earthquakes[j] = earthquakes[j + 1];
}
} else {
test_magnitude = e->magnitude;
timedelta = inttime - e->starttime;
if (timedelta >= e->duration / 2) {
rampdowntime = 2 * e->duration / 3 + e->starttime;
if (!e->no_rampdown && inttime > rampdowntime) {
test_magnitude *= 1.0f - (inttime - rampdowntime) * 3.0f / e->duration;
}
} else {
rampuptime = e->duration / 3 + e->starttime;
if (!e->no_rampup && inttime < rampuptime) {
test_magnitude *= (inttime - e->starttime) * 3.0f / e->duration;
}
}
if (test_magnitude > earthquake_magnitude) {
earthquake_magnitude = test_magnitude;
}
}
}
}
void Level::EventRainDensitySet(Event *ev)
{
gi.setConfigstring(CS_RAIN_DENSITY, ev->GetString(1));
}
void Level::EventRainDensityGet(Event *ev)
{
ev->AddString(gi.getConfigstring(CS_RAIN_DENSITY));
}
void Level::EventRainSpeedSet(Event *ev)
{
gi.setConfigstring(CS_RAIN_SPEED, ev->GetString(1));
}
void Level::EventRainSpeedGet(Event *ev)
{
ev->AddString(gi.getConfigstring(CS_RAIN_SPEED));
}
void Level::EventRainSpeedVarySet(Event *ev)
{
gi.setConfigstring(CS_RAIN_SPEEDVARY, ev->GetString(1));
}
void Level::EventRainSpeedVaryGet(Event *ev)
{
ev->AddString(gi.getConfigstring(CS_RAIN_SPEEDVARY));
}
void Level::EventRainSlantSet(Event *ev)
{
gi.setConfigstring(CS_RAIN_SLANT, ev->GetString(1));
}
void Level::EventRainSlantGet(Event *ev)
{
ev->AddString(gi.getConfigstring(CS_RAIN_SLANT));
}
void Level::EventRainLengthSet(Event *ev)
{
gi.setConfigstring(CS_RAIN_LENGTH, ev->GetString(1));
}
void Level::EventRainLengthGet(Event *ev)
{
ev->AddString(gi.getConfigstring(CS_RAIN_LENGTH));
}
void Level::EventRainMin_DistSet(Event *ev)
{
gi.setConfigstring(CS_RAIN_MINDIST, ev->GetString(1));
}
void Level::EventRainMin_DistGet(Event *ev)
{
ev->AddString(gi.getConfigstring(CS_RAIN_MINDIST));
}
void Level::EventRainWidthSet(Event *ev)
{
gi.setConfigstring(CS_RAIN_WIDTH, ev->GetString(1));
}
void Level::EventRainWidthGet(Event *ev)
{
ev->AddString(gi.getConfigstring(CS_RAIN_WIDTH));
}
void Level::EventRainShaderSet(Event *ev)
{
gi.setConfigstring(CS_RAIN_SHADER, ev->GetString(1));
}
void Level::EventRainShaderGet(Event *ev)
{
ev->AddString(gi.getConfigstring(CS_RAIN_SHADER));
}
void Level::EventRainNumShadersSet(Event *ev)
{
if (g_protocol <= protocol_e::PROTOCOL_MOH) {
// There is a mistake in 1.11 and below where the NumShader event doesn't work
// Because the response points to EventRainShaderSet
return;
}
gi.setConfigstring(CS_RAIN_NUMSHADERS, ev->GetString(1));
}
void Level::EventRainNumShadersGet(Event *ev)
{
ev->AddString(gi.getConfigstring(CS_RAIN_NUMSHADERS));
}
void Level::EventAddBadPlace(Event *ev)
{
badplace_t bp;
int nArgs;
nArgs = ev->NumArgs();
if (nArgs != 3 && nArgs != 4 && nArgs != 5) {
throw ScriptException("badplace requires 3, 4, or 5 arguments");
}
if (ev->NumArgs() >= 5) {
bp.m_fLifespan = ev->GetFloat(5);
if (bp.m_fLifespan <= 0) {
throw ScriptException("life span must be greater than 0");
}
}
if (ev->NumArgs() >= 4) {
switch (ev->GetConstString(4)) {
case STRING_ALLIES:
case STRING_AMERICAN:
bp.m_iTeamSide = TEAM_ALLIES;
break;
case STRING_AXIS:
case STRING_GERMAN:
bp.m_iTeamSide = TEAM_AXIS;
break;
default:
throw ScriptException("badplace must belong to 'american' or 'german' or 'both'");
}
}
bp.m_fRadius = ev->GetFloat(3);
bp.m_vOrigin = ev->GetVector(2);
bp.m_name = ev->GetConstString(1);
if (bp.m_name == STRING_EMPTY && bp.m_fLifespan == FLT_MAX) {
throw ScriptException("unnamed badplaces must have a specified duration");
}
if (bp.m_name != STRING_EMPTY) {
int i;
// replace badplaces with the same name
for (i = 1; i <= m_badPlaces.NumObjects(); i++) {
const badplace_t& existing = m_badPlaces.ObjectAt(i);
if (existing.m_name == bp.m_name) {
// remove the existing bad place at the pathway
PathSearch::UpdatePathwaysForBadPlace(existing.m_vOrigin, existing.m_fRadius, -1, existing.m_iTeamSide);
m_badPlaces.SetObjectAt(i, bp);
// add the new bad place
PathSearch::UpdatePathwaysForBadPlace(bp.m_vOrigin, bp.m_fRadius, 1, bp.m_iTeamSide);
G_BroadcastAIEvent(NULL, vec_zero, 12, -1);
return;
}
}
}
// add the new bad place
m_badPlaces.AddObject(bp);
PathSearch::UpdatePathwaysForBadPlace(bp.m_vOrigin, bp.m_fRadius, 1, bp.m_iTeamSide);
// notify AI
G_BroadcastAIEvent(NULL, vec_zero, 12, -1);
}
void Level::EventRemoveBadPlace(Event *ev)
{
const_str name;
int i;
if (ev->NumArgs() != 1) {
throw ScriptException("removebadplace requires exactly 1 argument");
}
name = ev->GetConstString(1);
for (i = 1; i <= m_badPlaces.NumObjects(); i++) {
badplace_t& bp = m_badPlaces.ObjectAt(i);
if (bp.m_name == name) {
PathSearch::UpdatePathwaysForBadPlace(bp.m_vOrigin, bp.m_fRadius, -1, bp.m_iTeamSide);
m_badPlaces.RemoveObjectAt(i);
G_BroadcastAIEvent(NULL, vec_zero, 12, -1);
return;
}
}
Com_Printf("removebadplace: name '%s' not found", ev->GetString(1).c_str());
}
void Level::EventIgnoreClock(Event *ev)
{
m_bIgnoreClock = ev->GetBoolean(1);
}
void Level::UpdateBadPlaces()
{
qboolean removed;
int i;
removed = qfalse;
for (i = 1; i <= m_badPlaces.NumObjects(); i++) {
badplace_t& bp = m_badPlaces.ObjectAt(i);
if (level.time < bp.m_fLifespan) {
continue;
} else {
PathSearch::UpdatePathwaysForBadPlace(bp.m_vOrigin, bp.m_fRadius, -1, bp.m_iTeamSide);
m_badPlaces.RemoveObjectAt(i);
removed = qtrue;
i = 0;
}
}
if (removed) {
// tell AI that a bad place was removed
G_BroadcastAIEvent(NULL, vec_zero, 12, -1);
}
}
int Level::GetNearestBadPlace(const Vector& org, float radius, int team) const
{
float bestDistSqr;
int bestBpIndex;
int i;
bestDistSqr = FLT_MAX;
bestBpIndex = 0;
for (i = 1; i <= m_badPlaces.NumObjects(); i++) {
badplace_t& bp = m_badPlaces.ObjectAt(i);
if (team & bp.m_iTeamSide) {
const Vector delta = bp.m_vOrigin - org;
float distSqr = delta.lengthSquared();
float rad = bp.m_fRadius + radius;
if (distSqr < bestDistSqr && distSqr < rad * rad) {
bestDistSqr = distSqr;
bestBpIndex = i;
}
}
}
return bestBpIndex;
}
static void ArchiveBadPlace(Archiver& arc, badplace_t *bp)
{
Director.ArchiveString(arc, bp->m_name);
arc.ArchiveVector(&bp->m_vOrigin);
arc.ArchiveFloat(&bp->m_fRadius);
arc.ArchiveFloat(&bp->m_fLifespan);
}
void Level::Archive(Archiver& arc)
{
bool prespawn;
bool spawn;
Listener::Archive(arc);
if (arc.Saving()) {
prespawn = WaitTillDefined(STRING_PRESPAWN);
spawn = WaitTillDefined(STRING_SPAWN);
}
arc.ArchiveBool(&prespawn);
arc.ArchiveBool(&spawn);
if (arc.Loading()) {
if (!prespawn) {
RemoveWaitTill(STRING_PRESPAWN);
}
if (!spawn) {
RemoveWaitTill(STRING_SPAWN);
}
}
arc.ArchiveInteger(&framenum);
arc.ArchiveString(&level_name);
arc.ArchiveString(&mapname);
arc.ArchiveString(&spawnpoint);
arc.ArchiveString(&nextmap);
arc.ArchiveBoolean(&playerfrozen);
arc.ArchiveFloat(&intermissiontime);
ArchiveEnum(intermissiontype, INTTYPE_e);
arc.ArchiveInteger(&exitintermission);
arc.ArchiveInteger(&total_secrets);
arc.ArchiveInteger(&found_secrets);
arc.ArchiveFloat(&earthquake_magnitude);
arc.ArchiveInteger(&num_earthquakes);
for (int i = 0; i < num_earthquakes; i++) {
arc.ArchiveInteger(&earthquakes[i].duration);
arc.ArchiveFloat(&earthquakes[i].magnitude);
arc.ArchiveBool(&earthquakes[i].no_rampup);
arc.ArchiveBool(&earthquakes[i].no_rampdown);
arc.ArchiveInteger(&earthquakes[i].starttime);
arc.ArchiveInteger(&earthquakes[i].endtime);
arc.ArchiveSafePointer(&earthquakes[i].m_Thread);
}
arc.ArchiveBoolean(&cinematic);
arc.ArchiveBoolean(&ai_on);
arc.ArchiveBoolean(&mission_failed);
arc.ArchiveBoolean(&died_already);
arc.ArchiveVector(&water_color);
arc.ArchiveVector(&lava_color);
arc.ArchiveFloat(&water_alpha);
arc.ArchiveFloat(&lava_alpha);
arc.ArchiveString(&current_soundtrack);
arc.ArchiveString(&saved_soundtrack);
arc.ArchiveVector(&m_fade_color);
arc.ArchiveFloat(&m_fade_alpha);
arc.ArchiveFloat(&m_fade_time);
arc.ArchiveFloat(&m_fade_time_start);
ArchiveEnum(m_fade_style, fadestyle_t);
ArchiveEnum(m_fade_type, fadetype_t);
arc.ArchiveFloat(&m_letterbox_fraction);
arc.ArchiveFloat(&m_letterbox_time);
arc.ArchiveFloat(&m_letterbox_time_start);
ArchiveEnum(m_letterbox_dir, letterboxdir_t);
m_badPlaces.Archive(arc, &ArchiveBadPlace);
arc.ArchiveInteger(&m_iCuriousVoiceTime);
arc.ArchiveInteger(&m_iAttackEntryAnimTime);
arc.ArchiveInteger(&mHealthPopCount);
arc.ArchiveBoolean(&m_bAlarm);
arc.ArchiveBoolean(&mbNoDropHealth);
arc.ArchiveBoolean(&mbNoDropWeapons);
arc.ArchiveInteger(&m_iPapersLevel);
arc.ArchiveInteger(&m_LoopProtection);
// clear skel indexes
memset(skel_index, 0xff, sizeof(skel_index));
if (arc.Loading()) {
str saved = saved_soundtrack;
ChangeSoundtrack(current_soundtrack);
saved_soundtrack = saved;
memset(&impact_trace, 0, sizeof(trace_t));
}
for (int i = 0; i < MAX_HEAD_SENTIENTS; i++) {
arc.ArchiveObjectPointer((Class **)&m_HeadSentient[i]);
}
arc.ArchiveVector(&m_vObjectiveLocation);
arc.ArchiveVector(&m_vAlliedObjectiveLocation);
arc.ArchiveVector(&m_vAxisObjectiveLocation);
for (int i = 0; i < MAX_BODYQUEUE; i++) {
arc.ArchiveSafePointer(&Actor::mBodyQueue[i]);
}
arc.ArchiveInteger(&Actor::mCurBody);
Health::ArchiveStatic(arc);
arc.ArchiveConfigString(CS_CURRENT_OBJECTIVE);
for (int i = CS_OBJECTIVES; i < CS_OBJECTIVES + MAX_OBJECTIVES; i++) {
arc.ArchiveConfigString(i);
}
arc.ArchiveConfigString(CS_RAIN_DENSITY);
arc.ArchiveConfigString(CS_RAIN_SPEED);
arc.ArchiveConfigString(CS_RAIN_SPEEDVARY);
arc.ArchiveConfigString(CS_RAIN_SLANT);
arc.ArchiveConfigString(CS_RAIN_LENGTH);
arc.ArchiveConfigString(CS_RAIN_MINDIST);
arc.ArchiveConfigString(CS_RAIN_WIDTH);
arc.ArchiveConfigString(CS_RAIN_SHADER);
arc.ArchiveConfigString(CS_RAIN_NUMSHADERS);
arc.ArchiveFloat(&svsStartFloatTime);
arc.ArchiveFloat(&m_fLandmarkYDistMax);
arc.ArchiveFloat(&m_fLandmarkYDistMin);
arc.ArchiveFloat(&m_fLandmarkXDistMin);
arc.ArchiveFloat(&m_fLandmarkXDistMax);
}
void WriteStatS(FILE *pFile, const char *value)
{
fprintf(pFile, "%s", value);
}
void WriteStatI(FILE *pFile, int value)
{
fprintf(pFile, "%d", value);
}
void WriteStatF(FILE *pFile, float value)
{
fprintf(pFile, "%.2f", value);
}
void Level::OpenActorStats()
{
str filename;
if (!g_aistats->integer) {
return;
}
filename = "aistats_" + mapname + ".csv";
m_pAIStats = fopen(filename, "wt");
if (m_pAIStats) {
fprintf(
(FILE *)m_pAIStats,
"targetname,weapon,grenade ammo,health,accuracy,hearing,sight,fov,mindist,maxdist,leash,sound "
"awareness,noticescale,enemyshare,grenade aware,model\n"
);
}
}
void Level::WriteActorStats(Actor *actor)
{
const char *name;
str itemName;
Weapon *weapon;
Ammo *ammo;
int ammoAmount;
FILE *pFile;
if (!g_aistats->integer) {
return;
}
if (!m_pAIStats) {
OpenActorStats();
}
pFile = (FILE *)m_pAIStats;
if (!pFile) {
return;
}
name = actor->TargetName().c_str();
itemName = "none";
weapon = actor->GetActiveWeapon(WEAPON_MAIN);
if (weapon) {
itemName = weapon->GetItemName();
}
ammoAmount = 0;
ammo = actor->FindAmmoByName("grenade");
if (ammo) {
ammoAmount = ammo->getAmount();
}
WriteStatS(pFile, name);
WriteStatS(pFile, itemName);
WriteStatI(pFile, ammoAmount);
WriteStatF(pFile, actor->health);
WriteStatF(pFile, actor->mAccuracy * 100);
WriteStatF(pFile, actor->m_fHearing);
WriteStatF(pFile, actor->m_fSight);
WriteStatF(pFile, actor->m_fFov);
WriteStatF(pFile, actor->m_fMinDistance);
WriteStatF(pFile, actor->m_fMaxDistance);
WriteStatF(pFile, actor->m_fLeash);
WriteStatF(pFile, actor->m_fSoundAwareness);
WriteStatF(pFile, actor->m_fMaxNoticeTimeScale * 100);
WriteStatI(pFile, sqrt(actor->m_fMaxShareDistSquared));
WriteStatF(pFile, actor->m_fGrenadeAwareness);
WriteStatS(pFile, actor->model);
fprintf(pFile, "\n");
}
void Level::SetForceTeamObjectiveLocation(Event *ev)
{
m_bForceTeamObjectiveLocation = ev->GetBoolean(1);
}
void Level::GetForceTeamObjectiveLocation(Event *ev)
{
ev->AddInteger(g_gametype->integer >= GT_TOW || m_bForceTeamObjectiveLocation);
}
badplace_t::badplace_t()
: m_fLifespan(FLT_MAX)
, m_iTeamSide(TEAM_ALLIES)
{}