/*
 * 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);
	oldsave = gi.cvar("oldsave", "0", CVAR_ARCHIVE);

	/* 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);
#ifdef WITH_ACEBOT
	/* bots */
	botchat = gi.cvar("botchat", "1", CVAR_ARCHIVE);	/* <-------botchat */
	botauto_respawn = gi.cvar("botauto_respawn", "0", CVAR_ARCHIVE);
#endif
#ifdef GAME_MOD
	weap_shell = gi.cvar("weap_shell", "1", CVAR_ARCHIVE);
	alt_fire_blaster = gi.cvar("alt_fire_blaster", "0", CVAR_ARCHIVE);
	eject_bullets = gi.cvar("eject_bullets", "1", CVAR_ARCHIVE);
	eject_bullets_dm_coop = gi.cvar("eject_bullets_dm_coop", "0", 0);
	opt_dm_scoreboard = gi.cvar("opt_dm_scoreboard", "1", CVAR_ARCHIVE);
	help_min = gi.cvar("help_min", "0", CVAR_ARCHIVE);
	camoffset = gi.cvar("camoffset", "10", CVAR_SERVERINFO);
#endif
	/* 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;
}

/* ========================================================= */

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;
	default:
		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 {

			/*
			 * SBF: FIXME - 32 extra bytes alloc'd since the
			 * saved string might not be long enough
			 */
			*(char **)p = gi.TagMalloc(32 + 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__); */
	strcpy(str, "Oct 16 1998");	/* HACK */
	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__)) */
	/* if (strcmp(str, "Oct 16 1998"))	// HACK */
	if (!oldsave->value && strcmp(str, "Oct 16 1998")) {
		fclose(f);
		gi.error("Savegame from an older version :P\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; */
	base = (void *)0x20011cb0;
	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);

	/* 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;
	}
}
