summaryrefslogtreecommitdiff
path: root/source/baseq2/g_save.c
diff options
context:
space:
mode:
Diffstat (limited to 'source/baseq2/g_save.c')
-rw-r--r--source/baseq2/g_save.c770
1 files changed, 770 insertions, 0 deletions
diff --git a/source/baseq2/g_save.c b/source/baseq2/g_save.c
new file mode 100644
index 0000000..4acf70c
--- /dev/null
+++ b/source/baseq2/g_save.c
@@ -0,0 +1,770 @@
+/*
+Copyright (C) 1997-2001 Id Software, Inc.
+
+This program 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.
+
+This program 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 this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+*/
+
+#include "g_local.h"
+
+#define Function(f) {#f, f}
+
+mmove_t mmove_reloc;
+
+field_t fields[] = {
+ {"classname", FOFS(classname), F_LSTRING},
+ {"model", FOFS(model), F_LSTRING},
+ {"spawnflags", FOFS(spawnflags), F_INT},
+ {"speed", FOFS(speed), F_FLOAT},
+ {"accel", FOFS(accel), F_FLOAT},
+ {"decel", FOFS(decel), F_FLOAT},
+ {"target", FOFS(target), F_LSTRING},
+ {"targetname", FOFS(targetname), F_LSTRING},
+ {"pathtarget", FOFS(pathtarget), F_LSTRING},
+ {"deathtarget", FOFS(deathtarget), F_LSTRING},
+ {"killtarget", FOFS(killtarget), F_LSTRING},
+ {"combattarget", FOFS(combattarget), F_LSTRING},
+ {"message", FOFS(message), F_LSTRING},
+ {"team", FOFS(team), F_LSTRING},
+ {"wait", FOFS(wait), F_FLOAT},
+ {"delay", FOFS(delay), F_FLOAT},
+ {"random", FOFS(random), F_FLOAT},
+ {"move_origin", FOFS(move_origin), F_VECTOR},
+ {"move_angles", FOFS(move_angles), F_VECTOR},
+ {"style", FOFS(style), F_INT},
+ {"count", FOFS(count), F_INT},
+ {"health", FOFS(health), F_INT},
+ {"sounds", FOFS(sounds), F_INT},
+ {"light", 0, F_IGNORE},
+ {"dmg", FOFS(dmg), F_INT},
+ {"mass", FOFS(mass), F_INT},
+ {"volume", FOFS(volume), F_FLOAT},
+ {"attenuation", FOFS(attenuation), F_FLOAT},
+ {"map", FOFS(map), F_LSTRING},
+ {"origin", FOFS(s.origin), F_VECTOR},
+ {"angles", FOFS(s.angles), F_VECTOR},
+ {"angle", FOFS(s.angles), F_ANGLEHACK},
+
+ {"goalentity", FOFS(goalentity), F_EDICT, FFL_NOSPAWN},
+ {"movetarget", FOFS(movetarget), F_EDICT, FFL_NOSPAWN},
+ {"enemy", FOFS(enemy), F_EDICT, FFL_NOSPAWN},
+ {"oldenemy", FOFS(oldenemy), F_EDICT, FFL_NOSPAWN},
+ {"activator", FOFS(activator), F_EDICT, FFL_NOSPAWN},
+ {"groundentity", FOFS(groundentity), F_EDICT, FFL_NOSPAWN},
+ {"teamchain", FOFS(teamchain), F_EDICT, FFL_NOSPAWN},
+ {"teammaster", FOFS(teammaster), F_EDICT, FFL_NOSPAWN},
+ {"owner", FOFS(owner), F_EDICT, FFL_NOSPAWN},
+ {"mynoise", FOFS(mynoise), F_EDICT, FFL_NOSPAWN},
+ {"mynoise2", FOFS(mynoise2), F_EDICT, FFL_NOSPAWN},
+ {"target_ent", FOFS(target_ent), F_EDICT, FFL_NOSPAWN},
+ {"chain", FOFS(chain), F_EDICT, FFL_NOSPAWN},
+
+ {"prethink", FOFS(prethink), F_FUNCTION, FFL_NOSPAWN},
+ {"think", FOFS(think), F_FUNCTION, FFL_NOSPAWN},
+ {"blocked", FOFS(blocked), F_FUNCTION, FFL_NOSPAWN},
+ {"touch", FOFS(touch), F_FUNCTION, FFL_NOSPAWN},
+ {"use", FOFS(use), F_FUNCTION, FFL_NOSPAWN},
+ {"pain", FOFS(pain), F_FUNCTION, FFL_NOSPAWN},
+ {"die", FOFS(die), F_FUNCTION, FFL_NOSPAWN},
+
+ {"stand", FOFS(monsterinfo.stand), F_FUNCTION, FFL_NOSPAWN},
+ {"idle", FOFS(monsterinfo.idle), F_FUNCTION, FFL_NOSPAWN},
+ {"search", FOFS(monsterinfo.search), F_FUNCTION, FFL_NOSPAWN},
+ {"walk", FOFS(monsterinfo.walk), F_FUNCTION, FFL_NOSPAWN},
+ {"run", FOFS(monsterinfo.run), F_FUNCTION, FFL_NOSPAWN},
+ {"dodge", FOFS(monsterinfo.dodge), F_FUNCTION, FFL_NOSPAWN},
+ {"attack", FOFS(monsterinfo.attack), F_FUNCTION, FFL_NOSPAWN},
+ {"melee", FOFS(monsterinfo.melee), F_FUNCTION, FFL_NOSPAWN},
+ {"sight", FOFS(monsterinfo.sight), F_FUNCTION, FFL_NOSPAWN},
+ {"checkattack", FOFS(monsterinfo.checkattack), F_FUNCTION, FFL_NOSPAWN},
+ {"currentmove", FOFS(monsterinfo.currentmove), F_MMOVE, FFL_NOSPAWN},
+
+ {"endfunc", FOFS(moveinfo.endfunc), F_FUNCTION, FFL_NOSPAWN},
+
+ // temp spawn vars -- only valid when the spawn function is called
+ {"lip", STOFS(lip), F_INT, FFL_SPAWNTEMP},
+ {"distance", STOFS(distance), F_INT, FFL_SPAWNTEMP},
+ {"height", STOFS(height), F_INT, FFL_SPAWNTEMP},
+ {"noise", STOFS(noise), F_LSTRING, FFL_SPAWNTEMP},
+ {"pausetime", STOFS(pausetime), F_FLOAT, FFL_SPAWNTEMP},
+ {"item", STOFS(item), F_LSTRING, FFL_SPAWNTEMP},
+
+//need for item field in edict struct, FFL_SPAWNTEMP item will be skipped on saves
+ {"item", FOFS(item), F_ITEM},
+
+ {"gravity", STOFS(gravity), F_LSTRING, FFL_SPAWNTEMP},
+ {"sky", STOFS(sky), F_LSTRING, FFL_SPAWNTEMP},
+ {"skyrotate", STOFS(skyrotate), F_FLOAT, FFL_SPAWNTEMP},
+ {"skyaxis", STOFS(skyaxis), F_VECTOR, FFL_SPAWNTEMP},
+ {"minyaw", STOFS(minyaw), F_FLOAT, FFL_SPAWNTEMP},
+ {"maxyaw", STOFS(maxyaw), F_FLOAT, FFL_SPAWNTEMP},
+ {"minpitch", STOFS(minpitch), F_FLOAT, FFL_SPAWNTEMP},
+ {"maxpitch", STOFS(maxpitch), F_FLOAT, FFL_SPAWNTEMP},
+ {"nextmap", STOFS(nextmap), F_LSTRING, FFL_SPAWNTEMP},
+
+ {0, 0, 0, 0}
+
+};
+
+field_t levelfields[] =
+{
+ {"changemap", LLOFS(changemap), F_LSTRING},
+
+ {"sight_client", LLOFS(sight_client), F_EDICT},
+ {"sight_entity", LLOFS(sight_entity), F_EDICT},
+ {"sound_entity", LLOFS(sound_entity), F_EDICT},
+ {"sound2_entity", LLOFS(sound2_entity), F_EDICT},
+
+ {NULL, 0, F_INT}
+};
+
+field_t clientfields[] =
+{
+ {"pers.weapon", CLOFS(pers.weapon), F_ITEM},
+ {"pers.lastweapon", CLOFS(pers.lastweapon), F_ITEM},
+ {"newweapon", CLOFS(newweapon), F_ITEM},
+
+ {NULL, 0, F_INT}
+};
+
+/*
+============
+InitGame
+
+This will be called when the dll is first loaded, which
+only happens when a new game is started or a save game
+is loaded.
+============
+*/
+void InitGame (void)
+{
+ gi.dprintf ("==== InitGame ====\n");
+
+ gun_x = gi.cvar ("gun_x", "0", 0);
+ gun_y = gi.cvar ("gun_y", "0", 0);
+ gun_z = gi.cvar ("gun_z", "0", 0);
+
+ //FIXME: sv_ prefix is wrong for these
+ sv_rollspeed = gi.cvar ("sv_rollspeed", "200", 0);
+ sv_rollangle = gi.cvar ("sv_rollangle", "2", 0);
+ sv_maxvelocity = gi.cvar ("sv_maxvelocity", "2000", 0);
+ sv_gravity = gi.cvar ("sv_gravity", "800", 0);
+
+ // noset vars
+ dedicated = gi.cvar ("dedicated", "0", CVAR_NOSET);
+
+ // latched vars
+ sv_cheats = gi.cvar ("cheats", "0", CVAR_SERVERINFO|CVAR_LATCH);
+ gi.cvar ("gamename", GAMEVERSION , CVAR_SERVERINFO | CVAR_LATCH);
+ gi.cvar ("gamedate", __DATE__ , CVAR_SERVERINFO | CVAR_LATCH);
+
+ maxclients = gi.cvar ("maxclients", "4", CVAR_SERVERINFO | CVAR_LATCH);
+ maxspectators = gi.cvar ("maxspectators", "4", CVAR_SERVERINFO);
+ deathmatch = gi.cvar ("deathmatch", "0", CVAR_LATCH);
+ coop = gi.cvar ("coop", "0", CVAR_LATCH);
+ skill = gi.cvar ("skill", "1", CVAR_LATCH);
+ maxentities = gi.cvar ("maxentities", "1024", CVAR_LATCH);
+
+ // change anytime vars
+ dmflags = gi.cvar ("dmflags", "0", CVAR_SERVERINFO);
+ fraglimit = gi.cvar ("fraglimit", "0", CVAR_SERVERINFO);
+ timelimit = gi.cvar ("timelimit", "0", CVAR_SERVERINFO);
+ password = gi.cvar ("password", "", CVAR_USERINFO);
+ spectator_password = gi.cvar ("spectator_password", "", CVAR_USERINFO);
+ needpass = gi.cvar ("needpass", "0", CVAR_SERVERINFO);
+ filterban = gi.cvar ("filterban", "1", 0);
+
+ g_select_empty = gi.cvar ("g_select_empty", "0", CVAR_ARCHIVE);
+
+ run_pitch = gi.cvar ("run_pitch", "0.002", 0);
+ run_roll = gi.cvar ("run_roll", "0.005", 0);
+ bob_up = gi.cvar ("bob_up", "0.005", 0);
+ bob_pitch = gi.cvar ("bob_pitch", "0.002", 0);
+ bob_roll = gi.cvar ("bob_roll", "0.002", 0);
+
+ // flood control
+ flood_msgs = gi.cvar ("flood_msgs", "4", 0);
+ flood_persecond = gi.cvar ("flood_persecond", "4", 0);
+ flood_waitdelay = gi.cvar ("flood_waitdelay", "10", 0);
+
+ // dm map list
+ sv_maplist = gi.cvar ("sv_maplist", "", 0);
+
+ // items
+ InitItems ();
+
+ game.helpmessage1[0] = 0;
+
+ game.helpmessage2[0] = 0;
+
+ // initialize all entities for this game
+ game.maxentities = maxentities->value;
+ g_edicts = gi.TagMalloc (game.maxentities * sizeof(g_edicts[0]), TAG_GAME);
+ globals.edicts = g_edicts;
+ globals.max_edicts = game.maxentities;
+
+ // initialize all clients for this game
+ game.maxclients = maxclients->value;
+ game.clients = gi.TagMalloc (game.maxclients * sizeof(game.clients[0]), TAG_GAME);
+ globals.num_edicts = game.maxclients+1;
+}
+
+//=========================================================
+
+void WriteField1 (FILE *f, field_t *field, byte *base)
+{
+ void *p;
+ int len;
+ int index;
+
+ if (field->flags & FFL_SPAWNTEMP)
+ return;
+
+ p = (void *)(base + field->ofs);
+ switch (field->type)
+ {
+ case F_INT:
+ case F_FLOAT:
+ case F_ANGLEHACK:
+ case F_VECTOR:
+ case F_IGNORE:
+ break;
+
+ case F_LSTRING:
+ case F_GSTRING:
+ if ( *(char **)p )
+ len = strlen(*(char **)p) + 1;
+ else
+ len = 0;
+ *(int *)p = len;
+ break;
+ case F_EDICT:
+ if ( *(edict_t **)p == NULL)
+ index = -1;
+ else
+ index = *(edict_t **)p - g_edicts;
+ *(int *)p = index;
+ break;
+ case F_CLIENT:
+ if ( *(gclient_t **)p == NULL)
+ index = -1;
+ else
+ index = *(gclient_t **)p - game.clients;
+ *(int *)p = index;
+ break;
+ case F_ITEM:
+ if ( *(edict_t **)p == NULL)
+ index = -1;
+ else
+ index = *(gitem_t **)p - itemlist;
+ *(int *)p = index;
+ break;
+
+ //relative to code segment
+ case F_FUNCTION:
+ if (*(byte **)p == NULL)
+ index = 0;
+ else
+ index = *(byte **)p - ((byte *)InitGame);
+ *(int *)p = index;
+ break;
+
+ //relative to data segment
+ case F_MMOVE:
+ if (*(byte **)p == NULL)
+ index = 0;
+ else
+ index = *(byte **)p - (byte *)&mmove_reloc;
+ *(int *)p = index;
+ break;
+
+ default:
+ gi.error ("WriteEdict: unknown field type");
+ }
+}
+
+
+void WriteField2 (FILE *f, field_t *field, byte *base)
+{
+ int len;
+ void *p;
+
+ if (field->flags & FFL_SPAWNTEMP)
+ return;
+
+ p = (void *)(base + field->ofs);
+ switch (field->type)
+ {
+ case F_LSTRING:
+ if ( *(char **)p )
+ {
+ len = strlen(*(char **)p) + 1;
+ fwrite (*(char **)p, len, 1, f);
+ }
+ break;
+ }
+}
+
+void ReadField (FILE *f, field_t *field, byte *base)
+{
+ void *p;
+ int len;
+ int index;
+
+ if (field->flags & FFL_SPAWNTEMP)
+ return;
+
+ p = (void *)(base + field->ofs);
+ switch (field->type)
+ {
+ case F_INT:
+ case F_FLOAT:
+ case F_ANGLEHACK:
+ case F_VECTOR:
+ case F_IGNORE:
+ break;
+
+ case F_LSTRING:
+ len = *(int *)p;
+ if (!len)
+ *(char **)p = NULL;
+ else
+ {
+ *(char **)p = gi.TagMalloc (len, TAG_LEVEL);
+ fread (*(char **)p, len, 1, f);
+ }
+ break;
+ case F_EDICT:
+ index = *(int *)p;
+ if ( index == -1 )
+ *(edict_t **)p = NULL;
+ else
+ *(edict_t **)p = &g_edicts[index];
+ break;
+ case F_CLIENT:
+ index = *(int *)p;
+ if ( index == -1 )
+ *(gclient_t **)p = NULL;
+ else
+ *(gclient_t **)p = &game.clients[index];
+ break;
+ case F_ITEM:
+ index = *(int *)p;
+ if ( index == -1 )
+ *(gitem_t **)p = NULL;
+ else
+ *(gitem_t **)p = &itemlist[index];
+ break;
+
+ //relative to code segment
+ case F_FUNCTION:
+ index = *(int *)p;
+ if ( index == 0 )
+ *(byte **)p = NULL;
+ else
+ *(byte **)p = ((byte *)InitGame) + index;
+ break;
+
+ //relative to data segment
+ case F_MMOVE:
+ index = *(int *)p;
+ if (index == 0)
+ *(byte **)p = NULL;
+ else
+ *(byte **)p = (byte *)&mmove_reloc + index;
+ break;
+
+ default:
+ gi.error ("ReadEdict: unknown field type");
+ }
+}
+
+//=========================================================
+
+/*
+==============
+WriteClient
+
+All pointer variables (except function pointers) must be handled specially.
+==============
+*/
+void WriteClient (FILE *f, gclient_t *client)
+{
+ field_t *field;
+ gclient_t temp;
+
+ // all of the ints, floats, and vectors stay as they are
+ temp = *client;
+
+ // change the pointers to lengths or indexes
+ for (field=clientfields ; field->name ; field++)
+ {
+ WriteField1 (f, field, (byte *)&temp);
+ }
+
+ // write the block
+ fwrite (&temp, sizeof(temp), 1, f);
+
+ // now write any allocated data following the edict
+ for (field=clientfields ; field->name ; field++)
+ {
+ WriteField2 (f, field, (byte *)client);
+ }
+}
+
+/*
+==============
+ReadClient
+
+All pointer variables (except function pointers) must be handled specially.
+==============
+*/
+void ReadClient (FILE *f, gclient_t *client)
+{
+ field_t *field;
+
+ fread (client, sizeof(*client), 1, f);
+
+ for (field=clientfields ; field->name ; field++)
+ {
+ ReadField (f, field, (byte *)client);
+ }
+}
+
+/*
+============
+WriteGame
+
+This will be called whenever the game goes to a new level,
+and when the user explicitly saves the game.
+
+Game information include cross level data, like multi level
+triggers, help computer info, and all client states.
+
+A single player death will automatically restore from the
+last save position.
+============
+*/
+void WriteGame (char *filename, qboolean autosave)
+{
+ FILE *f;
+ int i;
+ char str[16];
+
+ if (!autosave)
+ SaveClientData ();
+
+ f = fopen (filename, "wb");
+ if (!f)
+ gi.error ("Couldn't open %s", filename);
+
+ memset (str, 0, sizeof(str));
+ strcpy (str, __DATE__);
+ fwrite (str, sizeof(str), 1, f);
+
+ game.autosaved = autosave;
+ fwrite (&game, sizeof(game), 1, f);
+ game.autosaved = qfalse;
+
+ for (i=0 ; i<game.maxclients ; i++)
+ WriteClient (f, &game.clients[i]);
+
+ fclose (f);
+}
+
+void ReadGame (char *filename)
+{
+ FILE *f;
+ int i;
+ char str[16];
+
+ gi.FreeTags (TAG_GAME);
+
+ f = fopen (filename, "rb");
+ if (!f)
+ gi.error ("Couldn't open %s", filename);
+
+ fread (str, sizeof(str), 1, f);
+ if (strcmp (str, __DATE__))
+ {
+ fclose (f);
+ gi.error ("Savegame from an older version.\n");
+ }
+
+ g_edicts = gi.TagMalloc (game.maxentities * sizeof(g_edicts[0]), TAG_GAME);
+ globals.edicts = g_edicts;
+
+ fread (&game, sizeof(game), 1, f);
+ game.clients = gi.TagMalloc (game.maxclients * sizeof(game.clients[0]), TAG_GAME);
+ for (i=0 ; i<game.maxclients ; i++)
+ ReadClient (f, &game.clients[i]);
+
+ fclose (f);
+}
+
+//==========================================================
+
+
+/*
+==============
+WriteEdict
+
+All pointer variables (except function pointers) must be handled specially.
+==============
+*/
+void WriteEdict (FILE *f, edict_t *ent)
+{
+ field_t *field;
+ edict_t temp;
+
+ // all of the ints, floats, and vectors stay as they are
+ temp = *ent;
+
+ // change the pointers to lengths or indexes
+ for (field=fields ; field->name ; field++)
+ {
+ WriteField1 (f, field, (byte *)&temp);
+ }
+
+ // write the block
+ fwrite (&temp, sizeof(temp), 1, f);
+
+ // now write any allocated data following the edict
+ for (field=fields ; field->name ; field++)
+ {
+ WriteField2 (f, field, (byte *)ent);
+ }
+
+}
+
+/*
+==============
+WriteLevelLocals
+
+All pointer variables (except function pointers) must be handled specially.
+==============
+*/
+void WriteLevelLocals (FILE *f)
+{
+ field_t *field;
+ level_locals_t temp;
+
+ // all of the ints, floats, and vectors stay as they are
+ temp = level;
+
+ // change the pointers to lengths or indexes
+ for (field=levelfields ; field->name ; field++)
+ {
+ WriteField1 (f, field, (byte *)&temp);
+ }
+
+ // write the block
+ fwrite (&temp, sizeof(temp), 1, f);
+
+ // now write any allocated data following the edict
+ for (field=levelfields ; field->name ; field++)
+ {
+ WriteField2 (f, field, (byte *)&level);
+ }
+}
+
+
+/*
+==============
+ReadEdict
+
+All pointer variables (except function pointers) must be handled specially.
+==============
+*/
+void ReadEdict (FILE *f, edict_t *ent)
+{
+ field_t *field;
+
+ fread (ent, sizeof(*ent), 1, f);
+
+ for (field=fields ; field->name ; field++)
+ {
+ ReadField (f, field, (byte *)ent);
+ }
+}
+
+/*
+==============
+ReadLevelLocals
+
+All pointer variables (except function pointers) must be handled specially.
+==============
+*/
+void ReadLevelLocals (FILE *f)
+{
+ field_t *field;
+
+ fread (&level, sizeof(level), 1, f);
+
+ for (field=levelfields ; field->name ; field++)
+ {
+ ReadField (f, field, (byte *)&level);
+ }
+}
+
+/*
+=================
+WriteLevel
+
+=================
+*/
+void WriteLevel (char *filename)
+{
+ int i;
+ edict_t *ent;
+ FILE *f;
+ void *base;
+
+ f = fopen (filename, "wb");
+ if (!f)
+ gi.error ("Couldn't open %s", filename);
+
+ // write out edict size for checking
+ i = sizeof(edict_t);
+ fwrite (&i, sizeof(i), 1, f);
+
+ // write out a function pointer for checking
+ base = (void *)InitGame;
+ fwrite (&base, sizeof(base), 1, f);
+
+ // write out level_locals_t
+ WriteLevelLocals (f);
+
+ // write out all the entities
+ for (i=0 ; i<globals.num_edicts ; i++)
+ {
+ ent = &g_edicts[i];
+ if (!ent->inuse)
+ continue;
+ fwrite (&i, sizeof(i), 1, f);
+ WriteEdict (f, ent);
+ }
+ i = -1;
+ fwrite (&i, sizeof(i), 1, f);
+
+ fclose (f);
+}
+
+
+/*
+=================
+ReadLevel
+
+SpawnEntities will allready have been called on the
+level the same way it was when the level was saved.
+
+That is necessary to get the baselines
+set up identically.
+
+The server will have cleared all of the world links before
+calling ReadLevel.
+
+No clients are connected yet.
+=================
+*/
+void ReadLevel (char *filename)
+{
+ int entnum;
+ FILE *f;
+ int i;
+ void *base;
+ edict_t *ent;
+
+ f = fopen (filename, "rb");
+ if (!f)
+ gi.error ("Couldn't open %s", filename);
+
+ // free any dynamic memory allocated by loading the level
+ // base state
+ gi.FreeTags (TAG_LEVEL);
+
+ // wipe all the entities
+ memset (g_edicts, 0, game.maxentities*sizeof(g_edicts[0]));
+ globals.num_edicts = maxclients->value+1;
+
+ // check edict size
+ fread (&i, sizeof(i), 1, f);
+ if (i != sizeof(edict_t))
+ {
+ fclose (f);
+ gi.error ("ReadLevel: mismatched edict size");
+ }
+
+ // check function pointer base address
+ fread (&base, sizeof(base), 1, f);
+#ifdef _WIN32
+ if (base != (void *)InitGame)
+ {
+ fclose (f);
+ gi.error ("ReadLevel: function pointers have moved");
+ }
+#else
+ gi.dprintf("Function offsets %d\n", ((byte *)base) - ((byte *)InitGame));
+#endif
+
+ // load the level locals
+ ReadLevelLocals (f);
+
+ // load all the entities
+ while (1)
+ {
+ if (fread (&entnum, sizeof(entnum), 1, f) != 1)
+ {
+ fclose (f);
+ gi.error ("ReadLevel: failed to read entnum");
+ }
+ if (entnum == -1)
+ break;
+ if (entnum >= globals.num_edicts)
+ globals.num_edicts = entnum+1;
+
+ ent = &g_edicts[entnum];
+ ReadEdict (f, ent);
+
+ // let the server rebuild world links for this ent
+ memset (&ent->area, 0, sizeof(ent->area));
+ gi.linkentity (ent);
+ }
+
+ fclose (f);
+
+ // mark all clients as unconnected
+ for (i=0 ; i<maxclients->value ; i++)
+ {
+ ent = &g_edicts[i+1];
+ ent->client = game.clients + i;
+ ent->client->pers.connected = qfalse;
+ }
+
+ // do any load time things at this point
+ for (i=0 ; i<globals.num_edicts ; i++)
+ {
+ ent = &g_edicts[i];
+
+ if (!ent->inuse)
+ continue;
+
+ // fire any cross-level triggers
+ if (ent->classname)
+ if (strcmp(ent->classname, "target_crosslevel_target") == 0)
+ ent->nextthink = level.time + ent->delay;
+ }
+}