///////////////////////////////////////////////////////////////////////////////
//	p_main.c
//
//	initialization and shutdown routines for physics DLL
///////////////////////////////////////////////////////////////////////////////

#include	"windows.h"

#include	"p_global.h"
#include	"p_user.h"

//	Client physics
#include	"p_client.h"

//	console commands for physics DLL
#include	"p_concmds.h"

///////////////////////////////////////////////////////////////////////////////
//	exports
///////////////////////////////////////////////////////////////////////////////

Quake_DllExport	void	*dll_Query	(void);

void *dll_Query(void)
{
	return (NULL);
}

///////////////////////////////////////////////////////////////////////////////
//	globals
///////////////////////////////////////////////////////////////////////////////

game_locals_t	game;
level_locals_t	level;
game_import_t	gi;
game_export_t	globals;
// spawn_temp_t	st;

gclient_t		**ass;

serverState_t serverState;


int	sm_meat_index;
int	snd_fry;

edict_t		*g_edicts;

cvar_t	*deathmatch;
cvar_t	*coop;
cvar_t	*dmflags;
cvar_t	*skill;
cvar_t	*fraglimit;
cvar_t	*timelimit;
cvar_t	*samelevel;
cvar_t	*noexit;
cvar_t	*maxclients;
cvar_t	*maxentities;
cvar_t	*g_select_empty;
cvar_t	*g_unlimited_ammo;
cvar_t	*nomonsters;

cvar_t	*dm_respawn;

cvar_t	*p_maxvelocity;
cvar_t	*p_gravity;

cvar_t	*p_rollspeed;
cvar_t	*p_rollangle;
cvar_t	*gun_x;
cvar_t	*gun_y;
cvar_t	*gun_z;

cvar_t	*run_pitch;
cvar_t	*run_roll;
cvar_t	*bob_up;
cvar_t	*bob_pitch;
cvar_t	*bob_roll;

cvar_t	*p_cheats;

//	Nelno:	Daikatana's base directory
cvar_t	*p_basedir;

//	Nelno:	physics frame rate multiplier -- 1.0 = normal framerate, < 1.0 = slower framerate
cvar_t	*p_frametime;
cvar_t	*p_runphysics;	// 1 = default, 0 = don't do any physics except for clients
cvar_t	*p_sync;	// 1 = default, 0 = don't sync SVF_SYNCTOCLIENT entities
cvar_t	*p_showboxes;  // 0 = default  1 = show bounding boxes of entity in front of client
cvar_t	*p_sendparticles;	//	1 = particle commands are sent during multiplayer (default)

float	p_frametime_scaled;
float	p_realtime;

void SpawnEntities (char *mapname, char *entities, char *spawnpoint);
void (*dll_UpdateBoundingBoxes)	(userEntity_t *ent);

///////////////////////////////////////////////////////////////////////////////
//	P_InitPerLevelCvars
//
//	initialize the CVars that need to be reset each level
//
//	Q2FIXME:	make sure these all need to be loaded each level.  Currently
//				it's just all the cvars that were in P_InitDLLs
///////////////////////////////////////////////////////////////////////////////

void	P_InitPerLevelCvars (void)
{
	// gun_x = gi.cvar ("gun_x", "0", 0);
	// gun_y = gi.cvar ("gun_y", "0", 0);
	// gun_z = gi.cvar ("gun_z", "0", 0);

	p_rollspeed = gi.cvar ("p_rollspeed", "200", 0);
	p_rollangle = gi.cvar ("p_rollangle", "2", 0);
	p_maxvelocity = gi.cvar ("p_maxvelocity", "4000", 0);
	p_gravity = gi.cvar ("p_gravity", "800", 0);

	// latched vars
	p_cheats = gi.cvar ("cheats", "0", CVAR_SERVERINFO|CVAR_LATCH);
	deathmatch = gi.cvar ("deathmatch", "0", CVAR_SERVERINFO|CVAR_LATCH);
	coop = gi.cvar ("coop", "0", CVAR_SERVERINFO|CVAR_LATCH);
	skill = gi.cvar ("skill", "100", CVAR_SERVERINFO|CVAR_LATCH);
	maxclients = gi.cvar ("maxclients", "4", CVAR_SERVERINFO | 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);
	noexit = gi.cvar ("noexit", "1", CVAR_SERVERINFO);
	samelevel = gi.cvar ("samelevel", "0", CVAR_SERVERINFO);
	g_select_empty = gi.cvar ("g_select_empty", "0", CVAR_ARCHIVE);
	g_unlimited_ammo = gi.cvar ("g_unlimited_ammo", "0", CVAR_SERVERINFO);
	dm_respawn = gi.cvar ("dm_respawn", "2", CVAR_SERVERINFO);
	nomonsters = gi.cvar ("nomonsters", "0", CVAR_SERVERINFO);

	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);

	hr_testvert = gi.cvar ("hr_testvert", "0", 0);
	hr_shownormals = gi.cvar ("hr_shownormals", "0", 0);

	//	Nelno: force set???
	p_frametime = gi.cvar_forceset ("p_frametime", "1.0");

   // temp shit for status bar (for screenshots)
	gi.cvar("st_armor","0",0);
	gi.cvar("st_level","0",0);

	p_runphysics = gi.cvar("p_runphysics","1",0);
	p_sync = gi.cvar("p_sync","1",0);
	p_showboxes = gi.cvar("p_showboxes","0",0);
	p_sendparticles = gi.cvar ("p_sendparticles","0",0);

   // weapon default auto-change direction
   gi.cvar("wp_change_order","1",0);

   /////////////////////////////////////////////////////////////////////////////
   //	set up some fields in serverState that relate commonly used cvars
	/////////////////////////////////////////////////////////////////////////////

   serverState.deathmatch = deathmatch->value;
   serverState.skill = skill->value;
}

///////////////////////////////////////////////////////////////////////////////
//	P_InitEdicts
//
//	get the memory for all the edicts
///////////////////////////////////////////////////////////////////////////////

void	P_InitEdicts (void)
{
	int			i;
	edict_t		*ent;

	if (g_edicts)
		gi.error ("Edict memory was not freed.\n");

	///////////////////////////////////////////////////////////////////////////
	//	initialize all entities for this game
	///////////////////////////////////////////////////////////////////////////

	game.maxentities = maxentities->value;
	if (maxclients->value * 8 > game.maxentities)
		game.maxentities = maxclients->value * 8;

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

	serverState.maxclients = game.maxclients = maxclients->value;
	game.clients = gi.TagMalloc (game.maxclients * sizeof(game.clients[0]), TAG_GAME);

	//	Q2FIXME:	init persistent data for clients.  Probably should do
	//				in world.dll.  Currently not done anywhere.

	for (i=0 ; i<game.maxclients ; i++)
	{
		g_edicts [i+1].client = game.clients + i;
//		InitClientPersistant (&game.clients[i]);
//		InitClientResp (&game.clients[i]);
	
	}

	globals.num_edicts = game.maxclients+1;

	///////////////////////////////////////////////////////////////////////////////
	//	Nelno:	init up gclient_t struct for everything other than worldspawn and
	//	clients
	///////////////////////////////////////////////////////////////////////////////
	ent = &g_edicts[0];

	for (i = 0; i < game.maxentities; i++, ent++)
	{
		ent->s.render_scale [0] = 1.0;
		ent->s.render_scale [1] = 1.0;
		ent->s.render_scale [2] = 1.0;

		if (i == 0)
		{
			ent->className = "virgin worldspawn";
			continue;
		}
		else if (i <= maxclients->value)
		{
			ent->className = "virgin client";
			continue;
		}

		ent->className = "virgin entity";

		ent->client = gi.TagMalloc (sizeof(gclient_t), TAG_GAME);
	}

	//	Nelno:	set up g_edicts for DLLs using gstate
	serverState.g_edicts = g_edicts;
}

///////////////////////////////////////////////////////////////////////////////
//	P_InitDLLs
//
//	This will be called when the dll is first loaded, which
//	only happens when a new game is begun
//
//	Nelno:	mallocs memory for edicts
///////////////////////////////////////////////////////////////////////////////

void P_InitDLLs (void)
{
	gi.dprintf ("---- P_InitGame ----\n");

	// items
	// InitItems ();

	//	Q2FIXME:	what's this do?
/*
	Com_sprintf (game.helpmessage1, sizeof(game.helpmessage1), "No help message1");

	Com_sprintf (game.helpmessage2, sizeof(game.helpmessage2), "No help message2");
*/

	P_InitPerLevelCvars ();

	P_InitEdicts ();

	//	all DLLs have been loaded, and all of their dll_ServerLoad functions have executed
	//	so run their init functions (init functions can rely on other DLLs already having
	//	been set up).
	DLL_InitFunctions ();
}

///////////////////////////////////////////////////////////////////////////////
//	P_InitChangelevel
//
//	called when a "changelevel" command is issued.
//
//	mallocs memory for edicts and clears per-level Cvars
///////////////////////////////////////////////////////////////////////////////

void P_InitChangelevel (void)
{
	gi.dprintf ("---- P_InitChangelevel ----\n");

	// items
	// InitItems ();

	//	Q2FIXME:	what's this do?
/*
	Com_sprintf (game.helpmessage1, sizeof(game.helpmessage1), "No help message1");

	Com_sprintf (game.helpmessage2, sizeof(game.helpmessage2), "No help message2");
*/

	P_InitPerLevelCvars ();

	P_InitEdicts ();

	//	all DLLs have been loaded, and all of their dll_ServerLoad functions have executed
	//	so run their init functions (init functions can rely on other DLLs already having
	//	been set up).
	DLL_InitFunctions ();
}

///////////////////////////////////////////////////////////////////////////////
//	P_LevelExit
//
//	called by server when switching maps.  Calls dll entry functions with
//	QDLL_LEVEL_EXIT
///////////////////////////////////////////////////////////////////////////////

void	P_LevelExit (void)
{
	gi.dprintf ("---- P_LevelExit ----\n");

	//	call exit level functions for each DLL loaded by physics.dll
	DLL_ExitLevelFunctions ();

	///////////////////////////////////////////////////////////////////////////
	//	free edicts and any other memory allocated at beginning of last leve
	///////////////////////////////////////////////////////////////////////////

	gi.FreeTags (TAG_LEVEL);
	gi.FreeTags (TAG_GAME);

	g_edicts = NULL;
}

///////////////////////////////////////////////////////////////////////////////
//	P_LevelLoad
//
//	called by server when switching maps, after entities have been spawned and
//	just prior to running the first two physics frames.  Calls dll entry 
//	functions with QDLL_LEVEL_LOAD
///////////////////////////////////////////////////////////////////////////////

void	P_LevelLoad (void)
{
	gi.dprintf ("---- P_LevelLoad ----\n");

	//	call load level functions for each DLL loaded by physics.dll
	DLL_LoadLevelFunctions ();
}

///////////////////////////////////////////////////////////////////////////////
//	P_UnloadDLLs
//
//	called to unload all DLLs from memory
///////////////////////////////////////////////////////////////////////////////

void	P_UnloadDLLs (void)
{
	gi.dprintf ("---- P_UnloadDLLs ----\n");

	//	Unload all of the dlls that physics.dll loaded
	DLL_UnloadDLLs ();

	gi.FreeTags (TAG_LEVEL);
	gi.FreeTags (TAG_GAME);
}

///////////////////////////////////////////////////////////////////////////////
//	GetGameAPI
//
//	Returns a pointer to the structure with all entry points
//	and global variables.  Called only when physics.dll is loaded!!!!!!!!!!!!!!
///////////////////////////////////////////////////////////////////////////////

void	hr_ReadSurfaceNames (void);

//	Q2FIXME:	rename to something more appropriate, since this inits everything
Quake_DllExport game_export_t *GetGameAPI (game_import_t *import)
{
	char	dll_init_dir	[1024];

	gi = *import;

	gi.dprintf("PHYSICS.DLL: GetGameAPI\n");

	///////////////////////////////////////////////////////////////////////////////
	//	initialize server state
	///////////////////////////////////////////////////////////////////////////////

	InitServerState (&gi);

	///////////////////////////////////////////////////////////////////////////////
	//	load other dlls and call their dll_ServerLoad functions
	///////////////////////////////////////////////////////////////////////////////
	
	sprintf (dll_init_dir, "%s\\%s", gi.dk_gamedir, gi.dk_dlldir);
	DLL_LoadDLLs (dll_init_dir);

	///////////////////////////////////////////////////////////////////////////////
	//	set up pointers for calling from server
	///////////////////////////////////////////////////////////////////////////////

	globals.apiversion = GAME_API_VERSION;
	globals.SpawnEntities = SpawnEntities;

	globals.WriteGame = P_WriteGame;
	globals.ReadGame = P_ReadGame;
	globals.WriteLevel = P_WriteLevel;
	globals.ReadLevel = P_ReadLevel;

	globals.ClientThink = Client_Think;
	globals.ClientConnect = dll_ClientConnect;
	globals.ClientUserinfoChanged = dll_ClientUserinfoChanged;
	globals.ClientDisconnect = dll_ClientDisconnect;
	globals.ClientBegin = dll_ClientBegin;
	
	//	Nelno:	added these for DLL init, de-init functions
	globals.LevelExit = P_LevelExit;
	globals.LevelLoad = P_LevelLoad;

	globals.InitDLLs = P_InitDLLs;
	globals.UnloadDLLs = P_UnloadDLLs;

	globals.InitChangelevel = P_InitChangelevel;

	//	Nelno:	no longer used
	globals.ClientCommand = NULL;

	globals.RunFrame = P_RunFrame;
	
	//	Q2FIXME: add this
	globals.ServerCommand = NULL;

	globals.edict_size = sizeof(edict_t);

	//	Nelno:	add console commands for physics DLL here
	//	this is because we're throwing out the ClientCommand crap
	concmd_AddCommands ();

	//	get the list of valid hierarchical surface names
	hr_ReadSurfaceNames ();

	return &globals;
}

#ifndef GAME_HARD_LINKED
// this is only here so the functions in dk_shared.c and dk_shwin.c can link
void Sys_Error (char *error, ...)
{
	va_list		argptr;
	char		text[1024];

	va_start (argptr, error);
	vsprintf (text, error, argptr);
	va_end (argptr);

	gi.error ("%s", text);
}

void Com_Printf (char *msg, ...)
{
	va_list		argptr;
	char		text[1024];

	va_start (argptr, msg);
	vsprintf (text, msg, argptr);
	va_end (argptr);

	gi.dprintf ("%s", text);
}

#endif

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



#ifdef ORIG

/*
=================
EndDMLevel

The timelimit or fraglimit has been exceeded
=================
*/
void EndDMLevel (void)
{
	edict_t		*ent;

	// stay on same level flag
	if ((int)dmflags->value & DF_SAME_LEVEL)
	{
		ent = G_Spawn ();
		ent->classname = "target_changelevel";
		ent->map = level.mapname;
	}
	else if (level.nextmap)
	{	// go to a specific map
		ent = G_Spawn ();
		ent->classname = "target_changelevel";
		ent->map = level.nextmap;
	}
	else
	{	// search for a changeleve
		ent = G_Find (NULL, FOFS(classname), "target_changelevel");
		if (!ent)
		{	// the map designer didn't include a changelevel,
			// so create a fake ent that goes back to the same level
			ent = G_Spawn ();
			ent->classname = "target_changelevel";
			ent->map = level.mapname;
		}
	}

	BeginIntermission (ent);
}

/*
=================
CheckDMRules
=================
*/
void CheckDMRules (void)
{
	int			i;
	gclient_t	*cl;

	if (level.intermissiontime)
		return;

	if (!deathmatch->value)
		return;

	if (timelimit->value)
	{
		if (level.time >= timelimit->value*60)
		{
			gi.bprintf (PRINT_HIGH, "Timelimit hit.\n");
			EndDMLevel ();
			return;
		}
	}

	if (fraglimit->value)
	{
		for (i=0 ; i<maxclients->value ; i++)
		{
			cl = game.clients + i;
			if (!g_edicts[i+1].inuse)
				continue;

			if (cl->resp.score >= fraglimit->value)
			{
				gi.bprintf (PRINT_HIGH, "Fraglimit hit.\n");
				EndDMLevel ();
				return;
			}
		}
	}
}


/*
=============
ExitLevel
=============
*/
void ExitLevel (void)
{
	int		i;
	edict_t	*ent;
	char	command [256];

	Com_sprintf (command, sizeof(command), "gamemap \"%s\"\n", level.changemap);
	gi.AddCommandString (command);
	level.changemap = NULL;
	level.exitintermission = 0;
	level.intermissiontime = 0;
	ClientEndServerFrames ();

	// clear some things before going to next level
	for (i=0 ; i<maxclients->value ; i++)
	{
		ent = g_edicts + 1 + i;
		if (!ent->inuse)
			continue;
		if (ent->health > ent->client->pers.max_health)
			ent->health = ent->client->pers.max_health;
	}

}


#endif



//////////////////////////////////////////////////////////////////////////////////
/// Physics
//////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////
//	P_RunFrame
//
//	Advances the world by 0.1 seconds
//////////////////////////////////////////////////////////////////////////////////

void P_RunFrame (void)
{
	int		i;
	edict_t	*ent;

	//	actual time last physics frame started, used by P_RunSyncFrame
	p_realtime = gi.Sys_Milliseconds ();

	//	calculate p_frametime based on p_frametime multiplier
	p_frametime_scaled = FRAMETIME * p_frametime->value;

	level.framenum++;
	//	Nelno:	FIXME:	is it right not to scale frametime???
	level.time = level.framenum * FRAMETIME;
	serverState.frametime = level.time - serverState.time; 
	serverState.time = level.time;

	//	set gravity
	serverState.gravity = p_gravity->value;

	// choose a client for monsters to target this frame
	// AI_SetSightClient ();

	// exit intermissions
/*
	if (level.exitintermission)
	{
		ExitLevel ();
		return;
	}
*/

	//&&& AMW - display bounding box of entity found in client traceline
	if (p_showboxes->value && (globals.num_edicts > 0))
	{
		// get the client entity
		ent = &g_edicts[1];	
		
		if (ent && ent->client && !strcmp(ent->className,"player") &&
			dll_UpdateBoundingBoxes)
		{
			// function in world.cpp to draw a bounding box around 
			// the entity you're pointed at
			dll_UpdateBoundingBoxes( ent );
		}

	}

	//
	// treat each object in turn
	// even the world gets a chance to think
	//

	ent = &g_edicts[0];

	if (!p_runphysics->value)
	{
		//	special loop for testing with no world physics
		for (i=0 ; i <= maxclients->value; i++, ent++)
		{
			if (!ent->inuse)
				continue;

			level.current_entity = ent;

			///////////////////////////////////////////////////////////////////////////
			//	Run client-only stuff, skip over clients
			///////////////////////////////////////////////////////////////////////////

			//	make sure we're connected!!  inuse only gets set right before ClientBegin is called
			if (!ent->inuse || !ent->client)
				continue;

			Client_BeginServerFrame (ent);

			///////////////////////////////////////////////////////////////////////////
			//	PreThinks!!
			///////////////////////////////////////////////////////////////////////////
			if (ent->prethink)
				ent->prethink (ent);

			P_RunThink (ent, true);

			///////////////////////////////////////////////////////////////////////////
			//	move children
			///////////////////////////////////////////////////////////////////////////

			if (!(ent->flags & FL_HCHILD))
				//	child hr_Moves get called when parent runs
				hr_Move (ent);

			///////////////////////////////////////////////////////////////////////////
			//	PostThinks!!
			///////////////////////////////////////////////////////////////////////////
			if (ent->postthink)
				ent->postthink (ent);
		}
	}
	else
	{
		for (i=0 ; i<globals.num_edicts ; i++, ent++)
		{
			if (!ent->inuse)
				continue;

			level.current_entity = ent;

			//	if the ground entity moved, make sure we are still on it
			//	Nelno:	new physics stuff should make this unnecessary, except maybe for MOVETYPE_STEP...
/*
			if ((ent->groundEntity) && (ent->groundEntity->linkcount != ent->groundEntity_linkcount))
			{
				ent->groundEntity = NULL;
				if ((ent->movetype != MOVETYPE_FLY) && (ent->svflags & SVF_MONSTER) )
				{
					P_CheckGround (ent);
				}
			}
*/
			///////////////////////////////////////////////////////////////////////////
			//	Run client-only stuff, skip over clients
			///////////////////////////////////////////////////////////////////////////

			if (i > 0 && i <= maxclients->value)
			{
				//	make sure we're connected!!  inuse only gets set right before ClientBegin is called
				if (!ent->inuse || !ent->client)
					continue;

				Client_BeginServerFrame (ent);
			}
			else if (!(ent->s.renderfx & (RF_BEAM | RF_SPOTLIGHT)))
				VectorCopy (ent->s.origin, ent->s.old_origin);

			///////////////////////////////////////////////////////////////////////////
			//	PreThinks!!
			///////////////////////////////////////////////////////////////////////////
			if (ent->prethink)
				ent->prethink (ent);

			if (ent->movetype == MOVETYPE_NONE)
			{
				if (ent->think /*&& i > maxclients->value*/ && !(ent->flags & FL_HCHILD))
					P_RunThink (ent, true);
			}
			else
			{
				if (i > maxclients->value && !(ent->flags & FL_HCHILD))
					//	run physics for all but client
					P_RunEntity (ent, true);
				else if (ent->think)
					//	just run the think function
					P_RunThink (ent, true);
			}

			///////////////////////////////////////////////////////////////////////////
			//	move children
			//	DOESN'T MOVE CLIENT CHILDREN, THAT IS DONE IN Client_Think.  
			//	FIXME:	Will this cause hierarhical part lag on bad connections?
			///////////////////////////////////////////////////////////////////////////

			if (i > maxclients->value && !(ent->flags & FL_HCHILD) && (ent->flags & FL_HPARENT) )
				//	child hr_Moves get called when parent runs
				hr_Move (ent);

			///////////////////////////////////////////////////////////////////////////
			//	PostThinks!!
			///////////////////////////////////////////////////////////////////////////
			if (ent->postthink)
				ent->postthink (ent);

			if (ent->svflags & SVF_NEWSPAWN)
				ent->svflags &= ~SVF_NEWSPAWN;
		}
	}

	// see if it is time to end a deathmatch
	// CheckDMRules ();

	//	Nelno: finish up with special routines for clients
	//	adjust client view if client was affected by other objects
	//	do water damage, etc.
	Client_EndServerFrames ();
}
