/*
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"

const field_t fields[] = {
	{"classname", FOFS(classname), F_LSTRING},
	{"origin", FOFS(s.origin), F_VECTOR},
	{"model", FOFS(model), F_LSTRING},
	{"model2", FOFS(model2), 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},
	{"killtarget", FOFS(killtarget), 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_FLOAT},
	{"sounds", FOFS(sounds), F_LSTRING},
	{"light", FOFS(light), F_FLOAT},
	{"color", FOFS(color), F_VECTOR},
	{"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},
	{"id", FOFS(count), F_INT},

	// 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},
	{"radius", STOFS(radius), F_FLOAT, FFL_SPAWNTEMP},
	{"roll", STOFS(roll), F_FLOAT, FFL_SPAWNTEMP},
	{"height", STOFS(height), F_INT, FFL_SPAWNTEMP},
	{"phase", STOFS(phase), F_FLOAT, FFL_SPAWNTEMP},
	{"noise", STOFS(noise), F_LSTRING, FFL_SPAWNTEMP},
	{"noise_start", STOFS(noise_start), F_LSTRING, FFL_SPAWNTEMP},
	{"noise_stop", STOFS(noise_stop), 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},
	{"music", STOFS(music), 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},
	{"notsingle", STOFS(notsingle), F_INT, FFL_SPAWNTEMP},
	{"notteam", STOFS(notteam), F_INT, FFL_SPAWNTEMP},
	{"notfree", STOFS(notfree), F_INT, FFL_SPAWNTEMP},
	{"notduel", STOFS(notduel), F_INT, FFL_SPAWNTEMP},
	{"notctf", STOFS(notctf), F_INT, FFL_SPAWNTEMP},
	{"notffa", STOFS(notffa), F_INT, FFL_SPAWNTEMP},
	{"gameteam", STOFS(gameteam), F_INT, FFL_SPAWNTEMP},
	{"weight", STOFS(weight), F_INT, FFL_SPAWNTEMP}, //MBotGame

	{NULL, 0, F_INT, 0}
};

// -------- 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(model), F_LSTRING},
	{"", FOFS(map), F_LSTRING},
	{"", FOFS(message), F_LSTRING},

	{"", FOFS(r.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(r.owner), F_EDICT},
	{"", FOFS(target_ent), F_EDICT},
	{"", FOFS(chain), F_EDICT},

	{"", FOFS(prethink), F_FUNCTION},
	{"", FOFS(think), F_FUNCTION},
	{"", FOFS(blocked), F_FUNCTION},
	{"", FOFS(touch), F_FUNCTION},
	{"", FOFS(use), F_FUNCTION},
	{"", FOFS(pain), F_FUNCTION},
	{"", FOFS(die), F_FUNCTION},

	{"", FOFS(moveinfo.endfunc), F_FUNCTION},

	{NULL, 0, F_INT}
};

field_t		levelfields[] =
{
	{"", LLOFS(changemap), F_LSTRING},

	{NULL, 0, F_INT}
};

field_t		clientfields[] =
{
	//{"", CLOFS(pers.default_weapon), F_INT},

	{NULL, 0, F_INT}
};

//=========================================================

void WriteField1( field_t *field, qbyte *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 - game.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 - itemdefs;
		*(int *)p = index;
		break;

	// relative to code segment
	case F_FUNCTION:
		if (*(qbyte **)p == NULL)
			index = 0;
		else
			index = *(qbyte **)p - ((qbyte *)G_Init);
		*(int *)p = index;
		break;

	default:
		G_Error ("WriteEdict: unknown field type");
	}
}

void WriteField2( int file, field_t *field, qbyte *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;
			trap_FS_Write( *(char **)p, len, file );
		}
		break;
	default:
		break; // Nothing
	}
}

void ReadField( int file, field_t *field, qbyte *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 = G_GameMalloc( len );
			trap_FS_Read( *(char **)p, len, file );
		}
		break;

	case F_GSTRING:
		len = *(int *)p;
		if( !len )
			*(char **)p = NULL;
		else
		{
			*(char **)p = G_GameMalloc( len );
			trap_FS_Read( *(char **)p, len, file );
		}
		break;

	case F_EDICT:
		index = *(int *)p;
		if( index == -1 )
			*(edict_t **)p = NULL;
		else
			*(edict_t **)p = &game.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 = &itemdefs[index];
		break;

	// relative to code segment
	case F_FUNCTION:
		index = *(int *)p;
		if( index == 0 )
			*(qbyte **)p = NULL;
		else
			*(qbyte **)p = ((qbyte *)G_Init) + index;
		break;

	default:
		G_Error ("ReadEdict: unknown field type");
	}
}

//=========================================================

/*
==============
WriteClient

All pointer variables (except function pointers) must be handled specially.
==============
*/
void WriteClient( int file, 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( field, (qbyte *)&temp );

	// write the block
	trap_FS_Write( &temp, sizeof(temp), file );

	// now write any allocated data following the edict
	for( field=clientfields ; field->name ; field++ )
		WriteField2( file, field, (qbyte *)client );
}

/*
==============
ReadClient

All pointer variables (except function pointers) must be handled specially.
==============
*/
void ReadClient( int file, gclient_t *client )
{
	field_t		*field;

	trap_FS_Read( client, sizeof(*client), file );

	for( field=clientfields ; field->name ; field++ )
		ReadField( file, field, (qbyte *)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 )
{
	int file, i;
	char str[16];

	/*if( !autosave )
		SaveClientData ();*/

	if( trap_FS_FOpenFile( filename, &file, FS_WRITE ) == -1 )
		G_Error( "Couldn't open %s", filename );

	memset( str, 0, sizeof(str) );
	Q_strncpyz( str, __DATE__, sizeof(str) );
	trap_FS_Write( str, sizeof(str), file );

	game.autosaved = autosave;
	trap_FS_Write( &game, sizeof(game), file );
	game.autosaved = qfalse;

	for( i=0 ; i<game.maxclients ; i++ )
		WriteClient( file, &game.clients[i] );

	trap_FS_FCloseFile( file );
}

void ReadGame( char *filename )
{
	int		file, i;
	char	str[16];

	if( trap_FS_FOpenFile( filename, &file, FS_READ ) == -1 )
		G_Error( "Couldn't open %s", filename );

	trap_FS_Read( str, sizeof(str), file );
	if( strcmp( str, __DATE__ ) )
	{
		trap_FS_FCloseFile( file );
		G_Error( "Savegame from an older version.\n" );
	}

	G_EmptyGamePool();

	trap_FS_Read( &game, sizeof(game), file );

	game.edicts = G_GameMalloc( game.maxentities * sizeof(game.edicts[0]) );
	game.clients = G_GameMalloc( game.maxclients * sizeof(game.clients[0]) );

	trap_LocateEntities( game.edicts, sizeof(game.edicts[0]), game.numentities, game.maxentities );

	for (i=0 ; i<game.maxclients ; i++)
		ReadClient( file, &game.clients[i] );

	trap_FS_FCloseFile( file );
}

//==========================================================


/*
==============
WriteEdict

All pointer variables (except function pointers) must be handled specially.
==============
*/
void WriteEdict( int file, 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( field, (qbyte *)&temp );

	// write the block
	trap_FS_Write( &temp, sizeof(temp), file );

	// now write any allocated data following the edict
	for( field=savefields ; field->name ; field++ )
		WriteField2( file, field, (qbyte *)ent );
}

/*
==============
WriteLevelLocals

All pointer variables (except function pointers) must be handled specially.
==============
*/
void WriteLevelLocals( int file )
{
	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( field, (qbyte *)&temp );

	// write the block
	trap_FS_Write( &temp, sizeof(temp), file );

	// now write any allocated data following the edict
	for( field=levelfields ; field->name ; field++ )
		WriteField2( file, field, (qbyte *)&level );
}


/*
==============
ReadEdict

All pointer variables (except function pointers) must be handled specially.
==============
*/
void ReadEdict( int file, edict_t *ent )
{
	field_t		*field;

	trap_FS_Read( ent, sizeof(*ent), file );

	for( field=savefields ; field->name ; field++ )
		ReadField( file, field, (qbyte *)ent );
}

/*
==============
ReadLevelLocals

All pointer variables (except function pointers) must be handled specially.
==============
*/
void ReadLevelLocals( int file )
{
	field_t		*field;

	trap_FS_Read( &level, sizeof(level), file );

	for( field=levelfields ; field->name ; field++ )
		ReadField( file, field, (qbyte *)&level );
}

/*
=================
WriteLevel

=================
*/
void WriteLevel( char *filename ) // FIXME: should be const
{
	int		i;
	edict_t	*ent;
	int		file;
	void	*base;

	if( trap_FS_FOpenFile( filename, &file, FS_WRITE ) == -1 )
		G_Error ("Couldn't open %s", filename);

	// write out edict size for checking
	i = sizeof(edict_t);
	trap_FS_Write( &i, sizeof(i), file );

	// write out a function pointer for checking
	base = (void *)G_Init;
	trap_FS_Write( &base, sizeof(base), file );

	// write out level_locals_t
	WriteLevelLocals( file );

	// write out all the entities
	for (i=0 ; i<game.numentities ; i++)
	{
		ent = &game.edicts[i];
		if( !ent->r.inuse )
			continue;
		trap_FS_Write( &i, sizeof(i), file );
		WriteEdict( file, ent );
	}
	i = -1;
	trap_FS_Write( &i, sizeof(i), file );

	trap_FS_FCloseFile( file );
}


/*
=================
ReadLevel

SpawnEntities will already 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, i;
	void	*base;
	edict_t	*ent;

	if( trap_FS_FOpenFile( filename, &file, FS_READ ) == -1 )
		G_Error( "Couldn't open %s", filename );

	// free any dynamic memory allocated by loading the level base state
	G_EmptyLevelPool();

	// wipe all the entities
	memset( game.edicts, 0, game.maxentities*sizeof(game.edicts[0]) );
	game.numentities = game.maxclients + 1;

	trap_LocateEntities( game.edicts, sizeof(game.edicts[0]), game.numentities, game.maxentities );

	// check edict size
	trap_FS_Read( &i, sizeof(i), file );
	if( i != sizeof(edict_t) )
	{
		trap_FS_FCloseFile( file );
		G_Error( "ReadLevel: mismatched edict size" );
	}

	// check function pointer base address
	trap_FS_Read( &base, sizeof(base), file );
#ifdef _WIN32
	if( base != (void *)G_Init )
	{
		trap_FS_FCloseFile( file );
		G_Error( "ReadLevel: function pointers have moved" );
	}
#else
	G_Printf( "Function offsets %d\n", ((qbyte *)base) - ((qbyte *)G_Init) );
#endif

	// load the level locals
	ReadLevelLocals( file );
	// load all the entities
	while( 1 )
	{
		if( trap_FS_Read( &entnum, sizeof(entnum), file ) != 1 )
		{
			trap_FS_FCloseFile( file );
			G_Error( "ReadLevel: failed to read entnum" );
		}
		if( entnum == -1 )
			break;
		if( entnum >= game.numentities )
		{
			game.numentities = entnum+1;
			trap_LocateEntities( game.edicts, sizeof(game.edicts[0]), game.numentities, game.maxentities );
		}

		ent = &game.edicts[entnum];
		ReadEdict( file, ent );

		// let the server rebuild world links for this ent
		memset( &ent->r.area, 0, sizeof(ent->r.area) );
		trap_LinkEntity( ent );
	}

	trap_FS_FCloseFile( file );

	// mark all clients as unconnected
	for( i=0 ; i<game.maxclients ; i++ )
	{
		ent = &game.edicts[i+1];
		ent->r.client = game.clients + i;
		ent->r.client->pers.connected = qfalse;
	}

	// do any load time things at this point
	for( i=0 ; i<game.numentities ; i++ )
	{
		ent = &game.edicts[i];

		if( !ent->r.inuse )
			continue;

		// fire any cross-level triggers
		if( ent->classname ) {
			if( !Q_stricmp( ent->classname, "target_crosslevel_target" ) )
				ent->nextthink = level.timemsec + ent->delay * 1000;
		}
	}
}

