You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
743 lines
17 KiB
743 lines
17 KiB
/* |
|
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" |
|
|
|
field_t fields[] = { |
|
{"classname", FOFS(classname), F_LSTRING}, |
|
{"origin", FOFS(s.origin), F_VECTOR}, |
|
{"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}, |
|
{"angles", FOFS(s.angles), F_VECTOR}, |
|
{"angle", FOFS(s.angles), F_ANGLEHACK}, |
|
{"mass", FOFS(mass), F_INT}, |
|
{"volume", FOFS(volume), F_FLOAT}, |
|
{"attenuation", FOFS(attenuation), F_FLOAT}, |
|
{"map", FOFS(map), F_LSTRING}, |
|
|
|
// 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}, |
|
{"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} |
|
}; |
|
|
|
// -------- just for savegames ---------- |
|
// all pointer fields should be listed here, or savegames |
|
// won't work properly (they will crash and burn). |
|
// this wasn't just tacked on to the fields array, because |
|
// these don't need names, we wouldn't want map fields using |
|
// some of these, and if one were accidentally present twice |
|
// it would double swizzle (fuck) the pointer. |
|
|
|
field_t savefields[] = |
|
{ |
|
{"", FOFS(classname), F_LSTRING}, |
|
{"", FOFS(target), F_LSTRING}, |
|
{"", FOFS(targetname), F_LSTRING}, |
|
{"", FOFS(killtarget), F_LSTRING}, |
|
{"", FOFS(team), F_LSTRING}, |
|
{"", FOFS(pathtarget), F_LSTRING}, |
|
{"", FOFS(deathtarget), F_LSTRING}, |
|
{"", FOFS(combattarget), F_LSTRING}, |
|
{"", FOFS(model), F_LSTRING}, |
|
{"", FOFS(map), F_LSTRING}, |
|
{"", FOFS(message), F_LSTRING}, |
|
|
|
{"", FOFS(client), F_CLIENT}, |
|
{"", FOFS(item), F_ITEM}, |
|
|
|
{"", FOFS(goalentity), F_EDICT}, |
|
{"", FOFS(movetarget), F_EDICT}, |
|
{"", FOFS(enemy), F_EDICT}, |
|
{"", FOFS(oldenemy), F_EDICT}, |
|
{"", FOFS(activator), F_EDICT}, |
|
{"", FOFS(groundentity), F_EDICT}, |
|
{"", FOFS(teamchain), F_EDICT}, |
|
{"", FOFS(teammaster), F_EDICT}, |
|
{"", FOFS(owner), F_EDICT}, |
|
{"", FOFS(mynoise), F_EDICT}, |
|
{"", FOFS(mynoise2), F_EDICT}, |
|
{"", FOFS(target_ent), F_EDICT}, |
|
{"", FOFS(chain), F_EDICT}, |
|
|
|
{NULL, 0, F_INT} |
|
}; |
|
|
|
field_t levelfields[] = |
|
{ |
|
{"", LLOFS(changemap), F_LSTRING}, |
|
|
|
{"", LLOFS(sight_client), F_EDICT}, |
|
{"", LLOFS(sight_entity), F_EDICT}, |
|
{"", LLOFS(sound_entity), F_EDICT}, |
|
{"", LLOFS(sound2_entity), F_EDICT}, |
|
|
|
{NULL, 0, F_INT} |
|
}; |
|
|
|
field_t clientfields[] = |
|
{ |
|
{"", CLOFS(pers.weapon), F_ITEM}, |
|
{"", CLOFS(pers.lastweapon), F_ITEM}, |
|
{"", 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); |
|
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); |
|
|
|
//ZOID |
|
//This game.dll only supports deathmatch |
|
if (!deathmatch->value) { |
|
gi.dprintf("Forcing deathmatch."); |
|
gi.cvar_set("deathmatch", "1"); |
|
} |
|
//force coop off |
|
if (coop->value) |
|
gi.cvar_set("coop", "0"); |
|
//ZOID |
|
|
|
|
|
// change anytime vars |
|
dmflags = gi.cvar ("dmflags", "0", CVAR_SERVERINFO); |
|
fraglimit = gi.cvar ("fraglimit", "0", CVAR_SERVERINFO); |
|
timelimit = gi.cvar ("timelimit", "0", CVAR_SERVERINFO); |
|
//ZOID |
|
capturelimit = gi.cvar ("capturelimit", "0", CVAR_SERVERINFO); |
|
instantweap = gi.cvar ("instantweap", "0", CVAR_SERVERINFO); |
|
//ZOID |
|
password = gi.cvar ("password", "", CVAR_USERINFO); |
|
|
|
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 (); |
|
|
|
Com_sprintf (game.helpmessage1, sizeof(game.helpmessage1), ""); |
|
|
|
Com_sprintf (game.helpmessage2, sizeof(game.helpmessage2), ""); |
|
|
|
// 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; |
|
|
|
//ZOID |
|
CTFInit(); |
|
//ZOID |
|
} |
|
|
|
//========================================================= |
|
|
|
void WriteField1 (FILE *f, field_t *field, byte *base) |
|
{ |
|
void *p; |
|
int len; |
|
int index; |
|
|
|
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; |
|
|
|
default: |
|
gi.error ("WriteEdict: unknown field type"); |
|
} |
|
} |
|
|
|
void WriteField2 (FILE *f, field_t *field, byte *base) |
|
{ |
|
int len; |
|
void *p; |
|
|
|
p = (void *)(base + field->ofs); |
|
switch (field->type) |
|
{ |
|
case F_LSTRING: |
|
case F_GSTRING: |
|
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; |
|
|
|
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_GSTRING: |
|
len = *(int *)p; |
|
if (!len) |
|
*(char **)p = NULL; |
|
else |
|
{ |
|
*(char **)p = gi.TagMalloc (len, TAG_GAME); |
|
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; |
|
|
|
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 = false; |
|
|
|
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=savefields ; 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=savefields ; 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=savefields ; 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); |
|
if (base != (void *)InitGame) |
|
{ |
|
fclose (f); |
|
gi.error ("ReadLevel: function pointers have moved"); |
|
} |
|
|
|
// 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 = false; |
|
} |
|
|
|
// 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; |
|
} |
|
} |
|
|
|
|