mirror of
https://github.com/UberGames/EF2GameSource.git
synced 2024-11-10 06:31:42 +00:00
1362 lines
29 KiB
C++
1362 lines
29 KiB
C++
//-----------------------------------------------------------------------------
|
|
//
|
|
// $Logfile:: /EF2/Code/DLLs/game/level.cpp $
|
|
// $Revision:: 90 $
|
|
// $Date:: 9/26/03 2:36p $
|
|
//
|
|
// Copyright (C) 1999 by Ritual Entertainment, Inc.
|
|
// All rights reserved.
|
|
//
|
|
// This source is may not be distributed and/or modified without
|
|
// expressly written permission by Ritual Entertainment, Inc.
|
|
//
|
|
//
|
|
// DESCRIPTION:
|
|
//
|
|
|
|
#include "_pch_cpp.h"
|
|
#include "level.h"
|
|
#include "scriptmaster.h"
|
|
#include "navigate.h"
|
|
#include "helper_node.h"
|
|
#include "gravpath.h"
|
|
#include "g_spawn.h"
|
|
#include "player.h"
|
|
#include "characterstate.h"
|
|
#include "mp_manager.hpp"
|
|
#include "armor.h"
|
|
#include "CinematicArmature.h"
|
|
#include "earthquake.h"
|
|
#include "teammateroster.hpp"
|
|
|
|
Level level;
|
|
|
|
extern Container<int> SpecialPathNodes;
|
|
|
|
CLASS_DECLARATION( Class, Level, NULL )
|
|
{
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
Level::Level()
|
|
{
|
|
Init();
|
|
}
|
|
|
|
Level::~Level()
|
|
{
|
|
_earthquakes.FreeObjectList();
|
|
}
|
|
|
|
void Level::Init( void )
|
|
{
|
|
spawn_entnum = -1;
|
|
|
|
restart = false;;
|
|
|
|
framenum = 0;
|
|
time = 0;
|
|
frametime = 0;
|
|
|
|
level_name = "";
|
|
mapname = "";
|
|
spawnpoint = "";
|
|
nextmap = "";
|
|
|
|
playerfrozen = false;
|
|
intermissiontime = 0;
|
|
exitintermission = 0;
|
|
|
|
next_edict = NULL;
|
|
|
|
total_secrets = 0;
|
|
found_secrets = 0;
|
|
total_specialItems = 0;
|
|
found_specialItems = 0;
|
|
|
|
_totalEnemiesSpawned = 0;
|
|
|
|
memset( &impact_trace, 0, sizeof( impact_trace ) );
|
|
|
|
cinematic = false;
|
|
ai_on = true;
|
|
|
|
mission_failed = false;
|
|
died_already = false;
|
|
near_exit = false;
|
|
started = false;
|
|
|
|
water_color = vec_zero;
|
|
water_alpha = 0;
|
|
|
|
slime_color = vec_zero;
|
|
slime_alpha = 0;
|
|
|
|
lava_color = vec_zero;
|
|
lava_alpha = 0;
|
|
|
|
saved_soundtrack = "";
|
|
current_soundtrack = "";
|
|
|
|
consoleThread = NULL;
|
|
|
|
|
|
// clear out automatic cameras
|
|
automatic_cameras.ClearObjectList();
|
|
|
|
// init level script variables
|
|
levelVars.ClearList();
|
|
|
|
m_fade_time_start = 0;
|
|
m_fade_time = -1;
|
|
m_fade_color = vec_zero;
|
|
m_fade_alpha = 0;
|
|
m_fade_style = additive;
|
|
m_fade_type = fadein;
|
|
m_letterbox_fraction = 0;
|
|
m_letterbox_time = -1;
|
|
m_letterbox_time_start = 0;
|
|
m_letterbox_dir = letterbox_out;
|
|
|
|
hNodeController = NULL;
|
|
|
|
_cleanup = false;
|
|
|
|
_showIntermission = true;
|
|
|
|
_saveOrientation = true;
|
|
|
|
currentInstanceNumber = 0;
|
|
}
|
|
|
|
//-----------------------------------------------------
|
|
//
|
|
// Name: update
|
|
// Class: Level
|
|
//
|
|
// Description: Updates the level variables. This is called on the frame update.
|
|
//
|
|
// Parameters: levelTime - the game clock time.
|
|
// frameTime - the time the frame occured.
|
|
//
|
|
// Returns: None
|
|
//-----------------------------------------------------
|
|
void Level::update( int levelTime, int frameTime )
|
|
{
|
|
setTime(levelTime, frameTime);
|
|
|
|
if ( level.intermissiontime && dedicated->integer )
|
|
{
|
|
if ( g_endintermission->integer > 0 )
|
|
{
|
|
g_endintermission->integer = 0;
|
|
level.exitintermission = true;
|
|
}
|
|
|
|
// can exit intermission after 10 seconds (default)
|
|
|
|
if ( ( ( level.time - level.intermissiontime ) > level.intermission_advancetime ) &&
|
|
( level.intermission_advancetime != 0 ) )
|
|
{
|
|
if ( multiplayerManager.inMultiplayer() )
|
|
{
|
|
level.exitintermission = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Level::SetIntermissionAdvanceTime(float time)
|
|
{
|
|
intermission_advancetime = time;
|
|
}
|
|
|
|
void Level::EndIntermission()
|
|
{
|
|
exitintermission = true;
|
|
}
|
|
|
|
void Level::CleanUp( qboolean restart )
|
|
{
|
|
_cleanup = true;
|
|
|
|
if ( multiplayerManager.inMultiplayer() )
|
|
{
|
|
multiplayerManager.cleanup( restart );
|
|
}
|
|
else
|
|
{
|
|
Player* p = GetPlayer(0);
|
|
if( p ) p->LevelCleanup();
|
|
}
|
|
|
|
ClearCachedStatemaps();
|
|
ClearCachedFuzzyEngines();
|
|
_playerDeathThread = "";
|
|
#ifndef DEDICATED
|
|
gi.MObjective_ClearObjectiveList();
|
|
#endif
|
|
|
|
AdaptiveArmor::ClearAdaptionList();
|
|
theCinematicArmature.clearCinematicsList();
|
|
|
|
HelperNode::CleanupHelperNodeList();
|
|
|
|
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 );
|
|
}
|
|
}
|
|
|
|
cinematic = false;
|
|
ai_on = true;
|
|
|
|
mission_failed = false;
|
|
died_already = false;
|
|
near_exit = false;
|
|
started = false;
|
|
|
|
globals.num_entities = game.maxclients + 1;
|
|
|
|
// clear up all AI node information
|
|
thePathManager.ResetNodes();
|
|
|
|
// Reset the gravity paths
|
|
gravPathManager.Reset();
|
|
|
|
if ( consoleThread )
|
|
{
|
|
Director.KillThread( consoleThread->ThreadNum() );
|
|
consoleThread = NULL;
|
|
}
|
|
|
|
// close all the scripts
|
|
Director.CloseScript();
|
|
|
|
// invalidate player readiness
|
|
Director.PlayerNotReady();
|
|
|
|
// clear out automatic cameras
|
|
automatic_cameras.ClearObjectList();
|
|
|
|
// clear out level script variables
|
|
levelVars.ClearList();
|
|
|
|
// initialize the game variables
|
|
// these get restored by the persistant data, so we can safely clear them here
|
|
gameVars.ClearList();
|
|
|
|
// clearout any waiting events
|
|
L_ClearEventList();
|
|
|
|
ResetEdicts();
|
|
|
|
// Reset the boss health cvar
|
|
gi.cvar_set( "bosshealth", "0" );
|
|
|
|
_earthquakes.ClearObjectList();
|
|
|
|
_cleanup = false;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
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++ )
|
|
{
|
|
//char savedTeamName[ 16 ];
|
|
|
|
// set client fields on player ents
|
|
g_entities[ i ].client = game.clients + i;
|
|
|
|
//strcpy( savedTeamName, game.clients[i].pers.lastTeam );
|
|
|
|
G_InitClientPersistant (&game.clients[i]);
|
|
|
|
//strcpy( game.clients[i].pers.lastTeam, savedTeamName );
|
|
}
|
|
|
|
globals.num_entities = game.maxclients;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
Start
|
|
|
|
Does all post-spawning setup. This is NOT called for savegames.
|
|
==============
|
|
*/
|
|
void Level::Start( void )
|
|
{
|
|
CThread *gamescript;
|
|
|
|
// initialize secrets
|
|
|
|
levelVars.SetVariable( "total_secrets", total_secrets );
|
|
levelVars.SetVariable( "found_secrets", found_secrets );
|
|
levelVars.SetVariable( "total_specialItems" , total_specialItems );
|
|
levelVars.SetVariable( "found_specialItems" , found_specialItems );
|
|
levelVars.SetVariable( "total_enemies_spawned", _totalEnemiesSpawned );
|
|
|
|
|
|
FindTeams();
|
|
|
|
// call the precache scripts
|
|
|
|
Precache();
|
|
|
|
// start executing the game script
|
|
|
|
if ( game_script.length() )
|
|
{
|
|
gi.ProcessLoadingScreen( "$$LoadingScript$$" );
|
|
|
|
program.Load( game_script );
|
|
|
|
gi.ProcessLoadingScreen( "$$DoneLoadingScript$$" );
|
|
|
|
// Create the main thread
|
|
|
|
gamescript = Director.CreateThread( "main" );
|
|
|
|
if ( gamescript )
|
|
{
|
|
// Run the precache thread if it exists
|
|
|
|
if ( gamescript->labelExists( "precache" ) )
|
|
{
|
|
CThread *precache_script;
|
|
precache_script = Director.CreateThread( "precache" );
|
|
|
|
if ( precache_script )
|
|
precache_script->DelayedStart( 0.0f );
|
|
}
|
|
|
|
// Run the main thread
|
|
|
|
gamescript->DelayedStart( 0.0f );
|
|
}
|
|
}
|
|
|
|
loadLevelStrings();
|
|
started = true;
|
|
}
|
|
|
|
//----------------------------------------------------------------
|
|
// Name: postLoad
|
|
// Class:
|
|
//
|
|
// Description: Does everything necessary to the level after a load has happened
|
|
//
|
|
// Parameters: None
|
|
//
|
|
// Returns: none
|
|
//----------------------------------------------------------------
|
|
|
|
void Level::postLoad( void )
|
|
{
|
|
thePathManager.FindAllTargets();
|
|
TeammateRoster::getInstance()->clearTeammates();
|
|
//thePathManager.InsertNodesIntoGrid();
|
|
//thePathManager.ConnectPathNodes();
|
|
//thePathManager.OptimizeNodes( NULL );
|
|
|
|
thePathManager.SavePaths();
|
|
}
|
|
|
|
//----------------------------------------------------------------
|
|
// Name: postSublevelLoad
|
|
// Class: Level
|
|
//
|
|
// Description: Does everything necessary to the level after a sublevel has been loaded
|
|
//
|
|
// Parameters: const char *mapName - map name string (also possibly contains spawn position)
|
|
//
|
|
// Returns: none
|
|
//----------------------------------------------------------------
|
|
|
|
void Level::postSublevelLoad( const char *spawnPosName )
|
|
{
|
|
int i;
|
|
gentity_t *ent;
|
|
Player *player;
|
|
|
|
|
|
// Save off the spawn position
|
|
|
|
spawnpoint = spawnPosName;
|
|
|
|
// Make sure the player starts in the correct place
|
|
|
|
for( i = 0; i < game.maxclients; i++ )
|
|
{
|
|
ent = &g_entities[ i ];
|
|
|
|
if ( !ent->inuse || !ent->client || !ent->entity )
|
|
continue;
|
|
|
|
if ( ent->entity->isSubclassOf( Player ) )
|
|
{
|
|
player = (Player *)ent->entity;
|
|
|
|
player->ChooseSpawnPoint();
|
|
}
|
|
}
|
|
|
|
|
|
// Get rid of any fading
|
|
|
|
G_ClearFade();
|
|
}
|
|
|
|
qboolean Level::inhibitEntity( int spawnflags )
|
|
{
|
|
if ( !developer->integer && ( spawnflags & SPAWNFLAG_DEVELOPMENT ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if ( !detail->integer && ( spawnflags & SPAWNFLAG_DETAIL ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if ( multiplayerManager.inMultiplayer() )
|
|
{
|
|
if ( spawnflags & SPAWNFLAG_NOT_DEATHMATCH )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
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 false;
|
|
}
|
|
|
|
void Level::setSkill( int value )
|
|
{
|
|
int skill_level;
|
|
|
|
skill_level = (int) floor( value );
|
|
skill_level = bound( skill_level, 0, 3 );
|
|
|
|
gi.cvar_set( "skill", va( "%d", skill_level ) );
|
|
|
|
gameVars.SetVariable( "skill", skill_level );
|
|
}
|
|
|
|
int Level::getSkill( void )
|
|
{
|
|
ScriptVariable* skill_var = gameVars.GetVariable("skill");
|
|
return skill_var->intValue();
|
|
}
|
|
|
|
void Level::setTime( int levelTime, int frameTime )
|
|
{
|
|
inttime = levelTime;
|
|
fixedframetime = 1.0f / sv_fps->value;
|
|
frametime = ( ( float )frameTime / 1000.0f );
|
|
time = ( ( float )levelTime / 1000.0f );
|
|
|
|
if(intermissiontime == 0.0f && mission_failed == false)
|
|
timeInLevel = time;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
SpawnEntities
|
|
|
|
Creates a server's entity / program execution context by
|
|
parsing textual entity definitions out of an ent file.
|
|
==============
|
|
*/
|
|
void Level::SpawnEntities( const char *themapname, const char *entities, int levelTime )
|
|
{
|
|
int inhibit,count=0;
|
|
const char *value;
|
|
SpawnArgs args;
|
|
char *spawnpos;
|
|
|
|
// Init the level variables
|
|
Init();
|
|
|
|
spawnpos = strchr( themapname, '$' );
|
|
if ( spawnpos )
|
|
{
|
|
mapname = str( themapname, 0, spawnpos - themapname );
|
|
spawnpoint = spawnpos + 1;
|
|
}
|
|
else
|
|
{
|
|
mapname = themapname;
|
|
spawnpoint = "";
|
|
}
|
|
|
|
// set up time so functions still have valid times
|
|
setTime( levelTime, 1000 / 20 );
|
|
|
|
if ( !LoadingServer )
|
|
{
|
|
// Get rid of anything left over from the last level
|
|
//CleanUp( false );
|
|
|
|
// Set up for a new map
|
|
thePathManager.Init( mapname );
|
|
|
|
}
|
|
|
|
|
|
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.Spawn();
|
|
|
|
if ( !world )
|
|
Com_Error( ERR_FATAL, "No world\n" );
|
|
|
|
if ( g_gametype->integer == GT_MULTIPLAYER || g_gametype->integer == GT_BOT_SINGLE_PLAYER )
|
|
{
|
|
multiplayerManager.initMultiplayerGame();
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
args.Spawn();
|
|
count++;
|
|
|
|
gi.ProcessLoadingScreen( "$$SpawningEntities$$" );
|
|
}
|
|
|
|
gi.DPrintf( "%i entities spawned\n", count );
|
|
gi.DPrintf( "%i entities inhibited\n", inhibit );
|
|
|
|
// Process the spawn events
|
|
L_ProcessPendingEvents();
|
|
|
|
if ( multiplayerManager.inMultiplayer() )
|
|
{
|
|
multiplayerManager.initItems();
|
|
}
|
|
|
|
// Setup bots
|
|
|
|
if ( gi.Cvar_VariableIntegerValue( "bot_enable" ) && multiplayerManager.inMultiplayer() )
|
|
{
|
|
BotAIShutdown( 0 );
|
|
|
|
BotAISetup( 0 );
|
|
BotAILoadMap( 0 );
|
|
G_InitBots( 0 );
|
|
}
|
|
|
|
if ( !LoadingServer || game.autosaved )
|
|
{
|
|
Start();
|
|
}
|
|
|
|
postLoad();
|
|
|
|
|
|
//-------------------------------------------------------------------------------
|
|
//
|
|
// Deletion Note:
|
|
// Since hNodeController is an Entity, it is deleted
|
|
// when all the other entities are deleted in the clean up function
|
|
// specifically the line
|
|
//
|
|
//
|
|
// if ( active_edicts.next->entity )
|
|
// {
|
|
// delete active_edicts.next->entity;
|
|
// }
|
|
//
|
|
//
|
|
// Since it is already being deleted like this
|
|
// We do not need to explcitily delete the controller... In fact
|
|
// you will error out if you try to.
|
|
//--------------------------------------------------------------------------------
|
|
hNodeController = new HelperNodeController;
|
|
if ( hNodeController )
|
|
hNodeController->SetTargetName( "HelperNodeController" );
|
|
|
|
|
|
|
|
//
|
|
// if this is a single player game, spawn the single player in now
|
|
// this allows us to read persistant data into the player before the client
|
|
// is completely ready
|
|
//
|
|
if ( game.maxclients == 1 )
|
|
{
|
|
spawn_entnum = 0;
|
|
new Player;
|
|
}
|
|
}
|
|
|
|
void Level::NewMap( const char *mapname, const char *entities, int levelTime )
|
|
{
|
|
theCinematicArmature.deleteAllCinematics();
|
|
current_map = mapname;
|
|
current_entities = entities;
|
|
|
|
SpawnEntities( current_map, current_entities, levelTime );
|
|
}
|
|
|
|
void Level::Restart( void )
|
|
{
|
|
theCinematicArmature.deleteAllCinematics();
|
|
|
|
SpawnEntities( current_map, current_entities, inttime );
|
|
|
|
L_ProcessPendingEvents();
|
|
|
|
G_ClientConnect( 0, true, false, true );
|
|
G_ClientBegin( &g_entities[ 0 ], NULL );
|
|
}
|
|
|
|
void Level::PlayerRestart( void )
|
|
{
|
|
// we need to restart through the server code
|
|
gi.SendConsoleCommand( "restart\n" );
|
|
//restart = true;
|
|
level.mission_failed = false;
|
|
}
|
|
|
|
void Level::Archive( Archiver &arc )
|
|
{
|
|
int num;
|
|
int i;
|
|
|
|
Class::Archive( arc );
|
|
|
|
if ( arc.Saving() )
|
|
{
|
|
SafePtr<Earthquake> ent;
|
|
|
|
num = _earthquakes.NumObjects();
|
|
arc.ArchiveInteger( &num );
|
|
|
|
for ( i = 1 ; i <= num ; i++ )
|
|
{
|
|
ent = _earthquakes.ObjectAt( i );
|
|
arc.ArchiveSafePointer( &ent );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SafePtr<Earthquake> ent;
|
|
SafePtr<Earthquake> *entityPointer;
|
|
|
|
arc.ArchiveInteger( &num );
|
|
|
|
_earthquakes.ClearObjectList();
|
|
_earthquakes.Resize( num );
|
|
|
|
for ( i = 1 ; i <= num ; i++ )
|
|
{
|
|
_earthquakes.AddObject( ent );
|
|
|
|
entityPointer = &_earthquakes.ObjectAt( i );
|
|
|
|
arc.ArchiveSafePointer( entityPointer );
|
|
}
|
|
}
|
|
|
|
arc.ArchiveInteger( &_totalEnemiesSpawned );
|
|
|
|
// Don't archive these
|
|
|
|
//const char *current_map;
|
|
//const char *current_entities;
|
|
|
|
//int spawn_entnum;
|
|
arc.ArchiveInteger( ¤tInstanceNumber );
|
|
//int spawnflags;
|
|
|
|
arc.ArchiveInteger( &framenum );
|
|
arc.ArchiveInteger( &inttime );
|
|
arc.ArchiveFloat( &time );
|
|
arc.ArchiveFloat( &timeInLevel );
|
|
arc.ArchiveFloat( &frametime );
|
|
arc.ArchiveFloat( &fixedframetime );
|
|
arc.ArchiveInteger( &startTime );
|
|
|
|
arc.ArchiveString( &level_name );
|
|
arc.ArchiveString( &mapname );
|
|
arc.ArchiveString( &spawnpoint );
|
|
arc.ArchiveString( &nextmap );
|
|
|
|
arc.ArchiveBoolean( &restart );
|
|
arc.ArchiveBoolean( &started );
|
|
|
|
arc.ArchiveBoolean( &playerfrozen );
|
|
|
|
arc.ArchiveFloat( &intermissiontime );
|
|
arc.ArchiveInteger( &exitintermission );
|
|
arc.ArchiveFloat( &intermission_advancetime );
|
|
arc.ArchiveBool( &_showIntermission );
|
|
arc.ArchiveBool( &_saveOrientation );
|
|
|
|
// Don't archive
|
|
//gentity_s *next_edict;
|
|
|
|
arc.ArchiveInteger( &total_secrets );
|
|
arc.ArchiveInteger( &found_secrets );
|
|
arc.ArchiveInteger( &total_specialItems );
|
|
arc.ArchiveInteger( &found_specialItems );
|
|
|
|
arc.ArchiveString( &game_script );
|
|
|
|
// Don't archive
|
|
//trace_t impact_trace;
|
|
|
|
arc.ArchiveBoolean( &cinematic );
|
|
arc.ArchiveBoolean( &ai_on );
|
|
|
|
arc.ArchiveBoolean( &mission_failed );
|
|
arc.ArchiveBoolean( &died_already );
|
|
|
|
arc.ArchiveBoolean( &near_exit );
|
|
|
|
arc.ArchiveVector( &water_color );
|
|
arc.ArchiveFloat( &water_alpha );
|
|
|
|
arc.ArchiveVector( &slime_color );
|
|
arc.ArchiveFloat( &slime_alpha );
|
|
|
|
arc.ArchiveVector( &lava_color );
|
|
arc.ArchiveFloat( &lava_alpha );
|
|
|
|
arc.ArchiveString( ¤t_soundtrack );
|
|
arc.ArchiveString( &saved_soundtrack );
|
|
|
|
arc.ArchiveObjectPointer( ( Class ** )&consoleThread );
|
|
|
|
arc.ArchiveVector( &m_fade_color );
|
|
arc.ArchiveFloat( &m_fade_alpha );
|
|
arc.ArchiveFloat( &m_fade_time );
|
|
arc.ArchiveFloat( & m_fade_time_start );
|
|
ArchiveEnum( m_fade_type, fadetype_t );
|
|
ArchiveEnum( m_fade_style, fadestyle_t );
|
|
|
|
arc.ArchiveFloat( &m_letterbox_fraction );
|
|
arc.ArchiveFloat( &m_letterbox_time );
|
|
arc.ArchiveFloat( &m_letterbox_time_start );
|
|
ArchiveEnum( m_letterbox_dir, letterboxdir_t );
|
|
|
|
arc.ArchiveBool( &_cleanup );
|
|
|
|
arc.ArchiveString( &_playerDeathThread );
|
|
|
|
arc.ArchiveObjectPointer( ( Class ** )&hNodeController );
|
|
|
|
// Don't archive, will already be setup from camera code
|
|
// Container<Camera *> automatic_cameras;
|
|
|
|
arc.ArchiveVector( & m_intermission_origin );
|
|
arc.ArchiveVector( & m_intermission_angle );
|
|
|
|
if ( arc.Loading() )
|
|
{
|
|
str temp_soundtrack;
|
|
|
|
// Change the sound track to the one just loaded
|
|
|
|
temp_soundtrack = saved_soundtrack;
|
|
ChangeSoundtrack( current_soundtrack.c_str() );
|
|
saved_soundtrack = temp_soundtrack;
|
|
|
|
// not archived since we can't save mid-frame
|
|
next_edict = NULL;
|
|
// not archived since we can't save mid-frame
|
|
memset( &impact_trace, 0, sizeof( impact_trace ) );
|
|
|
|
loadLevelStrings();
|
|
}
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------
|
|
//
|
|
// Name: loadLevelStrings
|
|
// Class: Level
|
|
//
|
|
// Description: Loads the string resource file for the level.
|
|
//
|
|
// Parameters: None
|
|
//
|
|
// Returns:
|
|
//-----------------------------------------------------
|
|
void Level::loadLevelStrings( void )
|
|
{
|
|
const char* sublevelName;
|
|
const char* levelName;
|
|
const char* environmentName;
|
|
|
|
gi.GetLevelDefs(mapname, &environmentName, &levelName, &sublevelName);
|
|
gi.SR_LoadLevelStrings(environmentName);
|
|
}
|
|
|
|
/*
|
|
==============
|
|
Precache
|
|
|
|
Calls precache scripts
|
|
==============
|
|
*/
|
|
void Level::Precache( void )
|
|
{
|
|
str filename;
|
|
const char *environmentName;
|
|
const char *levelName;
|
|
const char *sublevelName;
|
|
int i;
|
|
str mapName;
|
|
const char *spawnPoint;
|
|
|
|
//
|
|
// load in global0-9.scr
|
|
//
|
|
for( i = 0; i < 100; i++ )
|
|
{
|
|
filename = va( "global/global%i.scr", i );
|
|
|
|
if ( gi.FS_ReadFile( filename.c_str(), NULL, true ) != -1 )
|
|
level.consoleThread->Parse( filename.c_str() );
|
|
else
|
|
break;
|
|
}
|
|
|
|
// Get the level defs
|
|
if( !current_map )
|
|
return;
|
|
|
|
mapName = current_map;
|
|
spawnPoint = strstr( mapName.c_str(), "$" );
|
|
|
|
if ( spawnPoint )
|
|
{
|
|
mapName.CapLength( spawnPoint - mapName.c_str() );
|
|
}
|
|
|
|
gi.GetLevelDefs( mapName, &environmentName, &levelName, &sublevelName );
|
|
|
|
// Precache global stuff
|
|
|
|
for( i = 0 ; i < 10 ; i++ )
|
|
{
|
|
if ( i == 0 )
|
|
filename = va( "precache/server/global.txt" );
|
|
else
|
|
filename = va( "precache/server/global%d.txt", i );
|
|
|
|
if ( gi.FS_ReadFile( filename.c_str(), NULL, true ) != -1 )
|
|
level.consoleThread->Parse( filename.c_str() );
|
|
else
|
|
break;
|
|
}
|
|
|
|
// Precache environment stuff
|
|
|
|
if ( strlen( environmentName ) )
|
|
{
|
|
for( i = 0 ; i < 10 ; i++ )
|
|
{
|
|
if ( i == 0 )
|
|
filename = va( "precache/server/%s.txt", environmentName );
|
|
else
|
|
filename = va( "precache/server/%s%d.txt", environmentName, i );
|
|
|
|
if ( gi.FS_ReadFile( filename.c_str(), NULL, true ) != -1 )
|
|
level.consoleThread->Parse( filename.c_str() );
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Precache level stuff
|
|
|
|
if ( strlen( levelName ) )
|
|
{
|
|
for( i = 0 ; i < 10 ; i++ )
|
|
{
|
|
if ( i == 0 )
|
|
filename = va( "precache/server/%s.txt", levelName );
|
|
else
|
|
filename = va( "precache/server/%s%d.txt", levelName, i );
|
|
|
|
if ( gi.FS_ReadFile( filename.c_str(), NULL, true ) != -1 )
|
|
level.consoleThread->Parse( filename.c_str() );
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Precache sublevel stuff
|
|
|
|
if ( strlen( sublevelName ) && ( stricmp( levelName, sublevelName ) != 0 ) )
|
|
{
|
|
for( i = 0 ; i < 10 ; i++ )
|
|
{
|
|
if ( i == 0 )
|
|
filename = va( "precache/server/%s.txt", sublevelName );
|
|
else
|
|
filename = va( "precache/server/%s%d.txt", sublevelName, i );
|
|
|
|
if ( gi.FS_ReadFile( filename.c_str(), NULL, true ) != -1 )
|
|
level.consoleThread->Parse( filename.c_str() );
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
// load in universal_script.scr
|
|
|
|
//G_LoadAndExecScript( "global/universal_script.scr", "precache:", true );
|
|
}
|
|
|
|
/*
|
|
================
|
|
FindTeams
|
|
|
|
Chain together all entities with a matching team field.
|
|
|
|
All but the first will have the FL_TEAMSLAVE flag set.
|
|
All but the last will have the teamchain field set to the next one
|
|
================
|
|
*/
|
|
void Level::FindTeams( void )
|
|
{
|
|
gentity_t *e;
|
|
gentity_t *e2;
|
|
gentity_t *next;
|
|
gentity_t *next2;
|
|
Entity *chain;
|
|
Entity *ent;
|
|
Entity *ent2;
|
|
int c;
|
|
int c2;
|
|
|
|
c = 0;
|
|
c2 = 0;
|
|
|
|
for( e = active_edicts.next; e != &active_edicts; e = next )
|
|
{
|
|
assert( e );
|
|
assert( e->inuse );
|
|
assert( e->entity );
|
|
|
|
next = e->next;
|
|
|
|
ent = e->entity;
|
|
if ( !ent->bind_info || ( ent->bind_info->moveteam.length() == 0 ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( ent->flags & FL_TEAMSLAVE )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
chain = ent;
|
|
ent->bind_info->teammaster = ent;
|
|
c++;
|
|
c2++;
|
|
for( e2 = next; e2 != &active_edicts; e2 = next2 )
|
|
{
|
|
assert( e2 );
|
|
assert( e2->inuse );
|
|
assert( e2->entity );
|
|
|
|
next2 = e2->next;
|
|
|
|
ent2 = e2->entity;
|
|
if ( !ent2->bind_info || ( ent2->bind_info->moveteam.length() == 0 ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( ent2->flags & FL_TEAMSLAVE )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( ent->bind_info->moveteam == ent2->bind_info->moveteam )
|
|
{
|
|
c2++;
|
|
chain->bind_info->teamchain = ent2;
|
|
ent2->bind_info->teammaster = ent;
|
|
chain = ent2;
|
|
ent2->flags |= FL_TEAMSLAVE;
|
|
}
|
|
}
|
|
}
|
|
|
|
gi.DPrintf( "%i teams with %i entities\n", c, c2 );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
AllocEdict
|
|
|
|
Either finds a free edict, or allocates a new one.
|
|
Try to avoid reusing an entity that was recently freed, because it
|
|
can cause the client to think the entity morphed into something else
|
|
instead of being removed and recreated, which can cause interpolated
|
|
angles and bad trails.
|
|
=================
|
|
*/
|
|
gentity_t *Level::AllocEdict( Entity *ent )
|
|
{
|
|
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[ maxclients->integer ];
|
|
for ( i = maxclients->integer; 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 + startTime ) ) ||
|
|
( time - edict->freetime > 0.5f )
|
|
)
|
|
)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// allow two spots for none and world
|
|
if ( i == game.maxentities - 2.0f )
|
|
{
|
|
// Try one more time before failing, relax timing completely
|
|
|
|
edict = &g_entities[ maxclients->integer ];
|
|
|
|
for ( i = maxclients->integer; i < globals.num_entities; i++, edict++ )
|
|
{
|
|
if ( !edict->inuse )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( i == game.maxentities - 2.0f )
|
|
{
|
|
gi.Error( ERR_DROP, "Level::AllocEdict: no free edicts" );
|
|
}
|
|
}
|
|
}
|
|
|
|
assert( edict->next );
|
|
assert( edict->prev );
|
|
LL_Remove( edict, next, prev );
|
|
InitEdict( edict );
|
|
assert( active_edicts.next );
|
|
assert( active_edicts.prev );
|
|
LL_Add( &active_edicts, edict, next, prev );
|
|
assert( edict->next );
|
|
assert( edict->prev );
|
|
|
|
assert( edict->next != &free_edicts );
|
|
assert( edict->prev != &free_edicts );
|
|
|
|
// 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 = ent;
|
|
edict->s.instanceNumber = currentInstanceNumber;
|
|
currentInstanceNumber++;
|
|
|
|
if ( currentInstanceNumber < 0 )
|
|
currentInstanceNumber = 0;
|
|
|
|
return edict;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
FreeEdict
|
|
|
|
Marks the edict as free
|
|
=================
|
|
*/
|
|
void Level::FreeEdict( gentity_t *ed )
|
|
{
|
|
gclient_t *client;
|
|
|
|
assert( ed != &free_edicts );
|
|
|
|
// unlink from world
|
|
gi.unlinkentity ( ed );
|
|
|
|
assert( ed->next );
|
|
assert( ed->prev );
|
|
|
|
if ( next_edict == ed )
|
|
{
|
|
next_edict = ed->next;
|
|
}
|
|
|
|
LL_Remove( ed, next, prev );
|
|
|
|
assert( ed->next == ed );
|
|
assert( ed->prev == ed );
|
|
assert( free_edicts.next );
|
|
assert( free_edicts.prev );
|
|
|
|
client = ed->client;
|
|
memset( ed, 0, sizeof( *ed ) );
|
|
ed->client = client;
|
|
ed->freetime = time;
|
|
ed->inuse = false;
|
|
ed->s.number = ed - g_entities;
|
|
|
|
assert( free_edicts.next );
|
|
assert( free_edicts.prev );
|
|
|
|
LL_Add( &free_edicts, ed, next, prev );
|
|
|
|
assert( ed->next );
|
|
assert( ed->prev );
|
|
}
|
|
|
|
void Level::InitEdict( gentity_t *e )
|
|
{
|
|
int i;
|
|
|
|
e->inuse = true;
|
|
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.renderfx |= RF_FRAMELERP;
|
|
e->spawntime = time;
|
|
e->s.frame = 0;
|
|
|
|
e->svflags = 0;
|
|
|
|
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 ] );
|
|
}
|
|
|
|
for( i = 0; i < NUM_MORPH_CONTROLLERS; i++ )
|
|
{
|
|
e->s.morph_controllers[ i ].index = -1;
|
|
e->s.morph_controllers[ i ].percent = 0.0;
|
|
}
|
|
|
|
e->s.animationRate = 1.0f;
|
|
}
|
|
|
|
void Level::AddAutomaticCamera( Camera *cam )
|
|
{
|
|
automatic_cameras.AddUniqueObject( cam );
|
|
}
|
|
|
|
void Level::SetGameScript( const char *scriptname )
|
|
{
|
|
game_script = scriptname;
|
|
}
|
|
|
|
//----------------------------------------------------------------
|
|
// Name: addEarthQuake
|
|
// Class: Level
|
|
//
|
|
// Description: Adds an earthquake to the list
|
|
//
|
|
// Parameters: Entity *earthquake - earthquake to add to the list
|
|
//
|
|
// Returns: none
|
|
//----------------------------------------------------------------
|
|
|
|
void Level::addEarthquake( Earthquake *earthquake )
|
|
{
|
|
if ( !_earthquakes.ObjectInList( earthquake ) )
|
|
{
|
|
_earthquakes.AddObject( earthquake );
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------
|
|
// Name: removeEarthQuake
|
|
// Class: Level
|
|
//
|
|
// Description: Removes an earthquake from the list
|
|
//
|
|
// Parameters: Entity *earthquake - earthquake to remove from the list
|
|
//
|
|
// Returns: none
|
|
//----------------------------------------------------------------
|
|
|
|
void Level::removeEarthquake( Earthquake *earthquake )
|
|
{
|
|
if ( _earthquakes.ObjectInList( earthquake ) )
|
|
{
|
|
_earthquakes.RemoveObject( earthquake );
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------
|
|
// Name: getEarthquakeMagnitudeAtPosition
|
|
// Class: Level
|
|
//
|
|
// Description: Gets the total magnitude of all earthquakes from a particular position
|
|
//
|
|
// Parameters: const Vector &origin - position to test against
|
|
//
|
|
// Returns: float - total magnitude of all the earthquakes at the given position
|
|
//----------------------------------------------------------------
|
|
|
|
float Level::getEarthquakeMagnitudeAtPosition( const Vector &origin )
|
|
{
|
|
int i;
|
|
float totalMagnitude;
|
|
Earthquake *earthquake;
|
|
|
|
totalMagnitude = 0.0f;
|
|
|
|
// Add up all of the earthquakes magnitudes to get the total
|
|
|
|
for( i = 1 ; i <= _earthquakes.NumObjects() ; i++ )
|
|
{
|
|
// This should be a safe cast since only Earthquakes are allowed to be added to this list
|
|
|
|
earthquake = _earthquakes.ObjectAt( i );
|
|
|
|
if ( earthquake )
|
|
{
|
|
// Add this earthquake to the total
|
|
|
|
totalMagnitude += earthquake->getMagnitudeAtPosition( origin );
|
|
}
|
|
}
|
|
|
|
// Return the total magnitude of all the earthquakes
|
|
|
|
return totalMagnitude;
|
|
}
|
|
|
|
void Level::enemySpawned( Entity *enemy )
|
|
{
|
|
_totalEnemiesSpawned++;
|
|
levelVars.SetVariable( "total_enemies_spawned", _totalEnemiesSpawned );
|
|
}
|
|
|
|
void Level::setPlayerDeathThread( const str &threadName )
|
|
{
|
|
_playerDeathThread = threadName;
|
|
}
|
|
|
|
str Level::getPlayerDeathThread( void )
|
|
{
|
|
return _playerDeathThread;
|
|
}
|