/* =========================================================================== 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 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 ( "nodropweapon", 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 wether or not the game is currently round based or not", EV_GETTER ); Event EV_Level_GetObjectiveBased ( "objectivebased", EV_DEFAULT, NULL, NULL, "Gets wether 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 ); 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::EventRainShaderSet }, {&EV_Level_Rain_NumShaders_Get, &Level::EventRainShaderGet }, {&EV_Level_AddBadPlace, &Level::EventAddBadPlace }, {&EV_Level_RemoveBadPlace, &Level::EventRemoveBadPlace }, {&EV_Level_IgnoreClock, &Level::EventIgnoreClock }, {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; 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_bSpawnBot = false; 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 = 1; i <= m_SimpleArchivedEntities.NumObjects(); 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 (int i = CS_OBJECTIVES; i < CS_OBJECTIVES + MAX_OBJECTIVES; i++) { gi.setConfigstring(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 (!developer->integer && (spawnflags & SPAWNFLAG_DEVELOPMENT)) { return qtrue; } if (!detail->integer && (spawnflags & SPAWNFLAG_DETAIL)) { return qtrue; } #ifdef _CONSOLE if (spawnflags & SPAWNFLAG_NOCONSOLE) #else if (spawnflags & SPAWNFLAG_NOPC) #endif { return qtrue; } if (g_gametype->integer) { if (spawnflags & SPAWNFLAG_NOT_DEATHMATCH) { return qtrue; } return qfalse; } switch (skill->integer) { case 0: return (spawnflags & SPAWNFLAG_NOT_EASY) != 0; break; case 1: return (spawnflags & SPAWNFLAG_NOT_MEDIUM) != 0; break; case 2: case 3: return (spawnflags & SPAWNFLAG_NOT_HARD); break; } 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]; 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); 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); sprintf(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) { dmManager.InitGame(); } gi.LoadResource("*148a"); if (game.maxclients == 1) { 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) { 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 = (const char *)(spawnpos - themapname); spawnpoint = mapname; } 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(); } /* ================ 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; // 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(ent->entity); if (p->client->ps.voted) { if (p->HasVotedYes()) { level.m_voteYes++; } else { level.m_voteNo++; } } } 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; } strcpy(buffer, m_voteString.c_str()); 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) { //FIXME: macros char s[1024]; int num = random2() * gi.NumHeadModels(model); gi.GetHeadModel(model, num, s); return s; } str Level::GetRandomHeadSkin(const char *model) { //FIXME: macros char s[1024]; int num = random2() * gi.NumHeadSkins(model); gi.GetHeadSkin(model, num, s); return s; } void Level::AddEarthquake(earthquake_t *e) { if (num_earthquakes == MAX_EARTHQUAKES) { if (earthquakes[0].m_Thread) { earthquakes[0].m_Thread->Wait((float)(e->endtime - inttime) / 1000.0f - 0.5f); } 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) { 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 radius = bp.m_fRadius + radius; if (distSqr < bestDistSqr && distSqr < radius * radius) { 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 = classinfo()->WaitTillDefined("prespawn"); spawn = classinfo()->WaitTillDefined("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(¤t_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); } badplace_t::badplace_t() : m_fLifespan(FLT_MAX) , m_iTeamSide(TEAM_ALLIES) {}