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

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

int		sm_meat_index;
int		snd_fry;
int		meansOfDeath;

edict_t        *g_edicts;

cvar_t         *deathmatch;
cvar_t         *coop;
cvar_t         *dmflags;
cvar_t         *skill;
cvar_t         *fraglimit;
cvar_t         *timelimit;
cvar_t         *password;
cvar_t         *spectator_password;
cvar_t         *needpass;
cvar_t         *maxclients;
cvar_t         *maxspectators;
cvar_t         *maxentities;
cvar_t         *g_select_empty;
cvar_t         *dedicated;
cvar_t         *oldsave;

cvar_t         *filterban;

cvar_t         *sv_maxvelocity;
cvar_t         *sv_gravity;

cvar_t         *sv_rollspeed;
cvar_t         *sv_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         *sv_cheats;

cvar_t         *flood_msgs;
cvar_t         *flood_persecond;
cvar_t         *flood_waitdelay;

cvar_t         *sv_maplist;

cvar_t         *gib_on;

#ifdef WITH_ACEBOT
cvar_t         *botchat;	/* <-------botchat */
cvar_t         *botauto_respawn;
#endif

#ifdef GAME_MOD
cvar_t         *weap_shell;
cvar_t         *alt_fire_blaster;
cvar_t         *eject_bullets;
cvar_t         *eject_bullets_dm_coop;
cvar_t         *opt_dm_scoreboard;
cvar_t         *help_min;
cvar_t         *camoffset;
#endif

void		SpawnEntities(char *mapname, char *entities, char *spawnpoint);
void		ClientThink(edict_t * ent, usercmd_t * cmd);
qboolean	ClientConnect(edict_t * ent, char *userinfo);
void		ClientUserinfoChanged(edict_t * ent, char *userinfo);
void		ClientDisconnect(edict_t * ent);
void		ClientBegin(edict_t * ent);
void		ClientCommand(edict_t * ent);
void		RunEntity (edict_t * ent);
void		WriteGame (char *filename, qboolean autosave);
void		ReadGame  (char *filename);
void		WriteLevel(char *filename);
void		ReadLevel (char *filename);
void		InitGame  (void);
void		G_RunFrame(void);


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


void
ShutdownGame(void)
{
	gi.dprintf("==== ShutdownGame ====\n");

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


/*
 * ================= GetGameAPI
 *
 * Returns a pointer to the structure with all entry points and global variables
 * =================
 */
game_export_t  *
GetGameAPI(game_import_t * import)
{
	gi = *import;

	globals.apiversion = GAME_API_VERSION;
	globals.Init = InitGame;
	globals.Shutdown = ShutdownGame;
	globals.SpawnEntities = SpawnEntities;

	globals.WriteGame = WriteGame;
	globals.ReadGame = ReadGame;
	globals.WriteLevel = WriteLevel;
	globals.ReadLevel = ReadLevel;

	globals.ClientThink = ClientThink;
	globals.ClientConnect = ClientConnect;
	globals.ClientUserinfoChanged = ClientUserinfoChanged;
	globals.ClientDisconnect = ClientDisconnect;
	globals.ClientBegin = ClientBegin;
	globals.ClientCommand = ClientCommand;

	globals.RunFrame = G_RunFrame;

	globals.ServerCommand = ServerCommand;

	globals.edict_size = sizeof(edict_t);

	return &globals;
}

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

	va_start(argptr, error);
	vsnprintf(text, sizeof(text) - 1, error, argptr);
	va_end(argptr);

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

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

	va_start(argptr, msg);
	vsnprintf(text, sizeof(text) - 1, msg, argptr);
	va_end(argptr);

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

#endif

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


/*
 * ================= ClientEndServerFrames =================
 */
void
ClientEndServerFrames(void)
{
	int		i;
	edict_t        *ent;

	/* calc the player views now that all pushing */
	/* and damage has been added */
	for (i = 0; i < maxclients->value; i++) {
		ent = g_edicts + 1 + i;
		if (!ent->inuse || !ent->client)
			continue;
		ClientEndServerFrame(ent);
	}

}

/*
 * ================= CreateTargetChangeLevel
 *
 * Returns the created target changelevel =================
 */
edict_t        *
CreateTargetChangeLevel(char *map)
{
	edict_t        *ent;

	ent = G_Spawn();
	ent->classname = "target_changelevel";
	Com_sprintf(level.nextmap, sizeof(level.nextmap), "%s", map);
	ent->map = level.nextmap;
	return ent;
}

/*
 * ================= EndDMLevel
 *
 * The timelimit or fraglimit has been exceeded =================
 */
void
EndDMLevel(void)
{
	edict_t        *ent;
	char           *s, *t, *f;
	static const char *seps = " ,\n\r";

	/* stay on same level flag */
	if ((int)dmflags->value & DF_SAME_LEVEL) {
		BeginIntermission(CreateTargetChangeLevel(level.mapname));
		return;
	}
	/* see if it's in the map list */
	if (*sv_maplist->string) {
		s = strdup(sv_maplist->string);
		f = NULL;
		t = strtok(s, seps);
		while (t != NULL) {
			if (Q_stricmp(t, level.mapname) == 0) {
				/* it's in the list, go to the next one */
				t = strtok(NULL, seps);
				if (t == NULL) {	/* end of list, go to
							 * first one */
					if (f == NULL)	/* there isn't a first
							 * one, same level */
						BeginIntermission(CreateTargetChangeLevel(level.mapname));
					else
						BeginIntermission(CreateTargetChangeLevel(f));
				} else
					BeginIntermission(CreateTargetChangeLevel(t));
				free(s);
				return;
			}
			if (!f)
				f = t;
			t = strtok(NULL, seps);
		}
		free(s);
	}
	if (level.nextmap[0])	/* go to a specific map */
		BeginIntermission(CreateTargetChangeLevel(level.nextmap));
	else {			/* search for a changelevel */
		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
			 */
			BeginIntermission(CreateTargetChangeLevel(level.mapname));
			return;
		}
		BeginIntermission(ent);
	}
}


/*
 * ================= CheckNeedPass =================
 */
void
CheckNeedPass(void)
{
	int		need;

	/* if password or spectator_password has changed, update needpass */
	/* as needed */
	if (password->modified || spectator_password->modified) {
		password->modified = spectator_password->modified = false;

		need = 0;

		if (*password->string && Q_stricmp(password->string, "none"))
			need |= 1;
		if (*spectator_password->string && Q_stricmp(spectator_password->string, "none"))
			need |= 2;

		gi.cvar_set("needpass", va("%d", need));
	}
}

/*
 * ================= 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) {
#ifdef WITH_ACEBOT
			safe_bprintf
#else
			gi.bprintf
#endif
			    (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) {

#ifdef WITH_ACEBOT
				safe_bprintf
#else
				gi.bprintf
#endif
				    (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;
	}

}

/*
 * ================ G_RunFrame
 *
 * Advances the world by 0.1 seconds ================
 */
void
G_RunFrame(void)
{
	int		i;
	edict_t        *ent;

#ifdef GAME_MOD
	Blinky_BeginRunFrame();
#endif

	level.framenum++;
	level.time = level.framenum * FRAMETIME;

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

	/* exit intermissions */

	if (level.exitintermission) {
		ExitLevel();
		return;
	}
	//
	/* treat each object in turn */
	/* even the world gets a chance to think */
	    //
	    ent = &g_edicts[0];
	for (i = 0; i < globals.num_edicts; i++, ent++) {
		if (!ent->inuse)
			continue;

		level.current_entity = ent;

		VectorCopy(ent->s.origin, ent->s.old_origin);

		/* if the ground entity moved, make sure we are still on it */
		if ((ent->groundentity) && (ent->groundentity->linkcount != ent->groundentity_linkcount)) {
			ent->groundentity = NULL;
			if (!(ent->flags & (FL_SWIM | FL_FLY)) && (ent->svflags & SVF_MONSTER)) {
				M_CheckGround(ent);
			}
		}
		if (i > 0 && i <= maxclients->value) {
			ClientBeginServerFrame(ent);
#ifdef WITH_ACEBOT
			/* continue; */
#else
			continue;
#endif
		}
		G_RunEntity(ent);
	}

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

	/* see if needpass needs updated */
	CheckNeedPass();

	/* build the playerstate_t structures for all players */
	ClientEndServerFrames();
}
