/*
Copyright (C) 1998 Steve Yeager

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.

*/

///////////////////////////////////////////////////////////////////////
//
//  ACE - Quake II Bot Base Code
//
//  Version 1.0
//
//  This file is Copyright(c), Steve Yeager 1998, All Rights Reserved
//
//
//	All other files are Copyright(c) Id Software, Inc.
//
//	Please see liscense.txt in the source directory for the copyright
//	information regarding those files belonging to Id Software, Inc.
//	
//	Should you decide to release a modified version of ACE, you MUST
//	include the following text (minus the BEGIN and END lines) in the 
//	documentation for your modification.
//
//	--- BEGIN ---
//
//	The ACE Bot is a product of Steve Yeager, and is available from
//	the ACE Bot homepage, at http://www.axionfx.com/ace.
//
//	This program is a modification of the ACE Bot, and is therefore
//	in NO WAY supported by Steve Yeager.
//
//	--- END ---
//
//	I, Steve Yeager, hold no responsibility for any harm caused by the
//	use of this source code, especially to small children and animals.
//  It is provided as-is with no implied warranty or support.
//
//  I also wish to thank and acknowledge the great work of others
//  that has helped me to develop this code.
//
//  John Cricket    - For ideas and swapping code.
//  Ryan Feltrin    - For ideas and swapping code.
//  SABIN           - For showing how to do true client based movement.
//  BotEpidemic     - For keeping us up to date.
//  Telefragged.com - For giving ACE a home.
//  Microsoft       - For giving us such a wonderful crash free OS.
//  id              - Need I say more.
//  
//  And to all the other testers, pathers, and players and people
//  who I can't remember who the heck they were, but helped out.
//
///////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////
//
//  acebot_spawn.c - This file contains all of the 
//                   spawing support routines for the ACE bot.
//
///////////////////////////////////////////////////////////////////////

#ifdef _WIN32
#include "..\g_local.h"
#include "..\m_player.h"
#else
#include "../g_local.h"
#include "../m_player.h"
#endif
#include "acebot.h"

///////////////////////////////////////////////////////////////////////
// Had to add this function in this version for some reason.
// any globals are wiped out between level changes....so
// save the bots out to a file. 
//
// NOTE: There is still a bug when the bots are saved for
//       a dm game and then reloaded into a CTF game.
///////////////////////////////////////////////////////////////////////
/*void ACESP_SaveBots()
{
    edict_t *bot;
    FILE *pOut;
	int i,count = 0;
	    			
	if((pOut = fopen("ace\\bots.tmp", "wb" )) == NULL)
		return; // bail
	
	// Get number of bots
	for (i = maxclients->value; i > 0; i--)
	{
		bot = g_edicts + i + 1;

		if (bot->inuse && bot->is_bot)
			count++;
	}
	
	fwrite(&count,sizeof (int),1,pOut); // Write number of bots

	for (i = maxclients->value; i > 0; i--)
	{
		bot = g_edicts + i + 1;

		if (bot->inuse && bot->is_bot)
			fwrite(bot->client->pers.userinfo,sizeof (char) * MAX_INFO_STRING,1,pOut); 
	}
		
    fclose(pOut);
}*/

///////////////////////////////////////////////////////////////////////
// Had to add this function in this version for some reason.
// any globals are wiped out between level changes....so
// load the bots from a file.
//
// Side effect/benefit is that the bots persist between games.
///////////////////////////////////////////////////////////////////////
/*void ACESP_LoadBots()
{
    FILE *pIn;
	char userinfo[MAX_INFO_STRING];
	int i, count;

	if((pIn = fopen("ace\\bots.tmp", "rb" )) == NULL)
		return; // bail
	
	fread(&count,sizeof (int),1,pIn); 

	for(i=0;i<count;i++)
	{
		fread(userinfo,sizeof(char) * MAX_INFO_STRING,1,pIn); 
		ACESP_SpawnBot (NULL, NULL, NULL, userinfo);
	}
		
    fclose(pIn);
}*/

///////////////////////////////////////////////////////////////////////
// Called by PutClient in Server to actually release the bot into the game
// Keep from killin' each other when all spawned at once
///////////////////////////////////////////////////////////////////////
void ACESP_HoldSpawn(edict_t *self)
{
	if (!KillBox (self))
	{	// could't spawn in?
	}

	gi.linkentity (self);

	self->think = ACEAI_Think;
	self->nextthink = level.time + FRAMETIME;

	// send effect
	gi.WriteByte (svc_muzzleflash);
	gi.WriteShort (self-g_edicts);
	gi.WriteByte (MZ_LOGIN);
	gi.multicast (self->s.origin, MULTICAST_PVS);

	if(ctf->value)
	safe_bprintf(PRINT_MEDIUM, "%s joined the %s team.\n",
		self->client->pers.netname, CTFTeamName(self->client->resp.ctf_team));
	else
		safe_bprintf (PRINT_MEDIUM, "%s entered the game\n", self->client->pers.netname);

}

///////////////////////////////////////////////////////////////////////
// Modified version of id's code
///////////////////////////////////////////////////////////////////////
void ACESP_PutClientInServer (edict_t *bot, qboolean respawn, int team)
{
	vec3_t	mins = {-16, -16, -24};
	vec3_t	maxs = {16, 16, 32};
	int		index;
	vec3_t	spawn_origin, spawn_angles;
	gclient_t	*client;
	int		i;
	client_persistant_t	saved;
	client_respawn_t	resp;
	char *s;
	int			spawn_style;
	int			spawn_health;

	// find a spawn point
	// do it before setting health back up, so farthest
	// ranging doesn't count this client
	SelectSpawnPoint (bot, spawn_origin, spawn_angles, &spawn_style, &spawn_health);
	
	index = bot-g_edicts-1;
	client = bot->client;

	// deathmatch wipes most client data every spawn
	if (deathmatch->value)
	{
		char userinfo[MAX_INFO_STRING];

		resp = bot->client->resp;
		memcpy (userinfo, client->pers.userinfo, sizeof(userinfo));
		InitClientPersistant (client, spawn_style);
		ClientUserinfoChanged (bot, userinfo);
	}
	else
		memset (&resp, 0, sizeof(resp));
	
	// clear everything but the persistant data
	saved = client->pers;
	memset (client, 0, sizeof(*client));
	client->pers = saved;
	client->resp = resp;
	
	// copy some data from the client to the entity
	FetchClientEntData (bot);
	
	// clear entity values
	bot->groundentity = NULL;
	bot->client = &game.clients[index];
	bot->takedamage = DAMAGE_AIM;
	bot->movetype = MOVETYPE_WALK;
	bot->viewheight = 24;
	bot->classname = "bot";
	bot->mass = 200;
	bot->solid = SOLID_BBOX;
	bot->deadflag = DEAD_NO;
	bot->air_finished = level.time + 12;
	bot->clipmask = MASK_PLAYERSOLID;
	bot->model = "players/male/tris.md2";
	bot->pain = player_pain;
	bot->die = player_die;
	bot->waterlevel = 0;
	bot->watertype = 0;
	bot->flags &= ~FL_NO_KNOCKBACK;
	bot->svflags &= ~SVF_DEADMONSTER;
	bot->is_jumping = false;
	
	if(ctf->value)
	{
		client->resp.ctf_team = team;
		client->resp.ctf_state = CTF_STATE_START;
		s = Info_ValueForKey (client->pers.userinfo, "skin");
		CTFAssignSkin(bot, s);
	}

	VectorCopy (mins, bot->mins);
	VectorCopy (maxs, bot->maxs);
	VectorClear (bot->velocity);

	// clear playerstate values
	memset (&bot->client->ps, 0, sizeof(client->ps));
	
	client->ps.pmove.origin[0] = spawn_origin[0]*8;
	client->ps.pmove.origin[1] = spawn_origin[1]*8;
	client->ps.pmove.origin[2] = spawn_origin[2]*8;

//ZOID
	client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
//ZOID

	if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV))
	{
		client->ps.fov = 90;
	}
	else
	{
		client->ps.fov = atoi(Info_ValueForKey(client->pers.userinfo, "fov"));
		if (client->ps.fov < 1)
			client->ps.fov = 90;
		else if (client->ps.fov > 160)
			client->ps.fov = 160;
	}

	// Knightmare- fix for null model?
	if (client->pers.weapon && client->pers.weapon->view_model)
		client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model);

	// clear entity state values
	bot->s.effects = 0;
	bot->s.skinnum = bot - g_edicts - 1;
	bot->s.modelindex = MAX_MODELS-1;		// will use the skin specified model
	bot->s.modelindex2 = MAX_MODELS-1;		// custom gun model
	bot->s.frame = 0;
	VectorCopy (spawn_origin, bot->s.origin);
	bot->s.origin[2] += 1;	// make sure off ground

	// set the delta angle
	for (i=0 ; i<3 ; i++)
		client->ps.pmove.delta_angles[i] = ANGLE2SHORT(spawn_angles[i] - client->resp.cmd_angles[i]);

	bot->s.angles[PITCH] = 0;
	bot->s.angles[YAW] = spawn_angles[YAW];
	bot->s.angles[ROLL] = 0;
	VectorCopy (bot->s.angles, client->ps.viewangles);
	VectorCopy (bot->s.angles, client->v_angle);
	
	// force the current weapon up
	client->newweapon = client->pers.weapon;
	ChangeWeapon (bot);

	bot->enemy = NULL;
	bot->movetarget = NULL; 
	bot->state = STATE_MOVE;

	// Set the current node
	bot->current_node = ACEND_FindClosestReachableNode(bot,NODE_DENSITY, NODE_ALL);
	bot->goal_node = bot->current_node;
	bot->next_node = bot->current_node;
	bot->next_move_time = level.time;		
	bot->suicide_timeout = level.time + 15.0;

	// If we are not respawning hold off for up to three seconds before releasing into game
    if(!respawn)
	{
		bot->think = ACESP_HoldSpawn;
		bot->nextthink = level.time + 0.1;
		bot->nextthink = level.time + random()*3.0; // up to three seconds
	}
	else
	{
		if (!KillBox (bot))
		{	// could't spawn in?
		}

		gi.linkentity (bot);

		bot->think = ACEAI_Think;
		bot->nextthink = level.time + FRAMETIME;

			// send effect
		gi.WriteByte (svc_muzzleflash);
		gi.WriteShort (bot-g_edicts);
		gi.WriteByte (MZ_LOGIN);
		gi.multicast (bot->s.origin, MULTICAST_PVS);
	}
	
}

///////////////////////////////////////////////////////////////////////
// Respawn the bot
///////////////////////////////////////////////////////////////////////
void ACESP_Respawn (edict_t *self)
{
	CopyToBodyQue (self);

	if(ctf->value)
		ACESP_PutClientInServer (self,true, self->client->resp.ctf_team);
	else
		ACESP_PutClientInServer (self,true,0);

	// add a teleportation effect
	self->s.event = EV_PLAYER_TELEPORT;

		// hold in place briefly
	self->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
	self->client->ps.pmove.pm_time = 14;

	self->client->respawn_time = level.time;
	
}

///////////////////////////////////////////////////////////////////////
// Find a free client spot
///////////////////////////////////////////////////////////////////////
edict_t *ACESP_FindFreeClient (void)
{
	edict_t *bot;
	int	i;
	int max_count=0;
	
	// This is for the naming of the bots
	for (i = maxclients->value; i > 0; i--)
	{
		bot = g_edicts + i + 1;
		
		if(bot->count > max_count)
			max_count = bot->count;
	}

	// Check for free spot
	for (i = maxclients->value; i > 0; i--)
	{
		bot = g_edicts + i + 1;

		if (!bot->inuse)
			break;
	}

	bot->count = max_count + 1; // Will become bot name...

	if (bot->inuse)
		bot = NULL;
	
	return bot;
}

// Knightmare- added bot name/skin table
///////////////////////////////////////////////////////////////////////
// Bot name/skin table
///////////////////////////////////////////////////////////////////////
struct botinfo_s
{
	char	name[128];
	char	skin[128];
	int		ingame_count;
};
typedef struct botinfo_s botinfo_t;
botinfo_t bot_info[MAX_BOTS];
int		num_botinfo = 0;

///////////////////////////////////////////////////////////////////////
// Load a table of bots from bots.cfg.
///////////////////////////////////////////////////////////////////////
void ACESP_LoadBotInfo()
{
    FILE *pIn;
	char filename[MAX_OSPATH] = "";
	char	line[256], *parseline;
	char	*token;
	char	botname[128];

	if (num_botinfo > 0) // already loaded
		return;
	// Knightmare- rewote this
	GameDirRelativePath ("bots.cfg", filename);
	if((pIn = fopen(filename, "rb" )) == NULL)
	{
		safe_bprintf(PRINT_MEDIUM,"ACE: No bots.cfg file found, using default bots.\n");
		return; // bail
	}
	safe_bprintf(PRINT_MEDIUM,"ACE: Loading bot data...");

	while (fgets(line, sizeof(line), pIn) && num_botinfo < MAX_BOTS)
	{
		parseline = line;
		token = COM_Parse (&parseline);
		if (!token || !strlen(token)) // catch bad line
			continue;
		strncpy (botname, token, sizeof(botname)-1);
		token = COM_Parse (&parseline);
		if (!token || !strlen(token)) // catch bad line
			continue;
		strncpy (bot_info[num_botinfo].name, botname, sizeof(bot_info[num_botinfo].name)-1);
		strncpy (bot_info[num_botinfo].skin, token, sizeof(bot_info[num_botinfo].skin)-1);
		//gi.dprintf("%s %s\n", bot_info[num_botinfo].name, bot_info[num_botinfo].skin);
		bot_info[num_botinfo].ingame_count = 0;
		num_botinfo++;
	}
	//gi.dprintf("Number of bots loaded: %d\n\n", num_botinfo);
	safe_bprintf(PRINT_MEDIUM, "done.\n");
	fclose(pIn);
}

///////////////////////////////////////////////////////////////////////
// Hardcoded bot skin list
///////////////////////////////////////////////////////////////////////
char *skinnames[] = {
	"female/athena",
	"female/brianna",
	"female/cobalt",
	"female/ensign",
	"female/jezebel",
	"female/jungle",
	"female/lotus",
	"female/stiletto",
	"female/venus",
	"female/voodoo",
	"male/cipher",
	"male/claymore",
	"male/flak",
	"male/grunt",
	"male/howitzer",
	"male/major",
	"male/nightops",
	"male/pointman",
	"male/psycho",
	"male/rampage",
	"male/razor",
	"male/recon",
	"male/scout",
	"male/sniper",
	"male/viper",
	"cyborg/oni911",
	"cyborg/ps9000",
	"cyborg/tyr574",
	0
};

int listSize (char* list[])
{
	int i=0;
	while (list[i]) i++;
	return i;	
}
static int NUM_BOT_SKINS = 0;


///////////////////////////////////////////////////////////////////////
// Set the name of the bot and update the userinfo
///////////////////////////////////////////////////////////////////////
void ACESP_SetName(edict_t *bot, char *name, char *skin, char *team)
{
	float rnd;
	char userinfo[MAX_INFO_STRING] = "";
	char bot_skin[MAX_INFO_STRING] = "";
	char bot_name[MAX_INFO_STRING] = "";
	int i, r;

	if (NUM_BOT_SKINS == 0)
		NUM_BOT_SKINS = listSize(skinnames);

	// Set the name for the bot.
	// name
	if(strlen(name) == 0)
	{
		// Randomly select from the name/skin table
		if (num_botinfo > 0)
		{
			//int numtries = 0;
			rnd = random();
			for (i = 0; i < num_botinfo; i++)
				if (rnd < ((float)(i+1)/(float)num_botinfo) )
				{	r=i; break; }
			// Search for an unused name starting at selected pos
			for (i = r; i < num_botinfo; i++)
				if (!bot_info[i].ingame_count)
				{
					sprintf(bot_name, bot_info[i].name);
					bot_info[i].ingame_count++;
					break;
				}
			// If none found, loop back
			if (strlen(bot_name) == 0)
				for (i = 0; i < r; i++)
					if (!bot_info[i].ingame_count)
					{
						sprintf(bot_name, bot_info[i].name);
						bot_info[i].ingame_count++;
						break;
					}
			// If no more free bots in table, use a numbered name
			if (strlen(bot_name) == 0)
				sprintf(bot_name,"ACEBot_%d",bot->count);
		}
		else
			sprintf(bot_name,"ACEBot_%d",bot->count);
	}
	else
		strcpy(bot_name,name);

	// skin
	if(strlen(skin) == 0)
	{	// check if this bot is in the table
		for (i = 0; i < num_botinfo; i++)
			if (!Q_stricmp(bot_name, bot_info[i].name))
			{
				sprintf(bot_name, bot_info[i].name); // fix capitalization
				sprintf(bot_skin, bot_info[i].skin);
				bot_info[i].ingame_count++;
				break;
			}
			if (strlen(bot_skin) == 0)
			{	// randomly choose skin 
				rnd = random();
				for (i = 0; i < NUM_BOT_SKINS; i++)
					if (rnd < ((float)(i+1)/(float)NUM_BOT_SKINS) )
					{	r=i; break; }
				sprintf(bot_skin, skinnames[r]);
			}
	}
	else
		strcpy(bot_skin,skin);
	
	// initialise userinfo
	memset (userinfo, 0, sizeof(userinfo));

	// add bot's name/skin/hand to userinfo
	Info_SetValueForKey (userinfo, "name", bot_name);
	Info_SetValueForKey (userinfo, "skin", bot_skin);
	Info_SetValueForKey (userinfo, "hand", "2"); // bot is center handed for now!

	ClientConnect (bot, userinfo);
	// Knightmare- removed this
	//ACESP_SaveBots(); // make sure to save the bots
}

///////////////////////////////////////////////////////////////////////
// Spawn the bot
///////////////////////////////////////////////////////////////////////
void ACESP_SpawnBot (char *team, char *name, char *skin, char *userinfo)
{
	edict_t	*bot;
	
	bot = ACESP_FindFreeClient ();
	
	if (!bot)
	{
		safe_bprintf (PRINT_MEDIUM, "Server is full, increase Maxclients.\n");
		return;
	}

	bot->yaw_speed = 100; // yaw speed
	bot->inuse = true;
	bot->is_bot = true;

	// To allow bots to respawn
	if(userinfo == NULL)
		ACESP_SetName(bot, name, skin, team);
	else
		ClientConnect (bot, userinfo);
	
	G_InitEdict (bot);

	InitClientResp (bot->client);
	
	// locate ent at a spawn point
    if (ctf->value)
	{	// Knightmare- rewrote this
		edict_t		*player;
		int i;
		int team1count = 0, team2count = 0, team3count = 0;
		int jointeam;
		float r = random();

		for (i = 1; i <= maxclients->value; i++)
		{
			player = &g_edicts[i];
			if (!player->inuse || !player->client || player == bot)
				continue;
			switch (player->client->resp.ctf_team)
			{
			case CTF_TEAM1:
				team1count++;
				break;
			case CTF_TEAM2:
				team2count++;
				break;
			case CTF_TEAM3: 
				team3count++;
				break;
			}
		}

		if (ttctf->value)
		{
			if (team != NULL && strcmp(team,"red")==0)
				jointeam = CTF_TEAM1;
			else if (team != NULL && strcmp(team,"blue")==0)
				jointeam = CTF_TEAM2;
			else if (team != NULL && strcmp(team,"green")==0)
				jointeam = CTF_TEAM3;
			// join either of the outnumbered teams
			else if (team1count == team2count && team1count < team3count)
				jointeam = (r < 0.5) ? CTF_TEAM1 : CTF_TEAM2;
			else if (team1count == team3count && team1count < team2count)
				jointeam = (r < 0.5) ? CTF_TEAM1 : CTF_TEAM3;
			else if (team2count == team3count && team2count < team1count)
				jointeam = (r < 0.5) ? CTF_TEAM2 : CTF_TEAM3;
			// join outnumbered team
			else if (team1count < team2count && team1count < team3count)
				jointeam = CTF_TEAM1;
			else if (team2count < team1count &&  team2count < team3count) 
				jointeam = CTF_TEAM2;
			else if (team3count < team1count &&  team3count < team2count) 
				jointeam = CTF_TEAM3;
			// pick random team
			else if (r < 0.33)
				jointeam = CTF_TEAM1;
			else if (r < 0.66)
				jointeam = CTF_TEAM2;
			else
				jointeam = CTF_TEAM3;
		}
		else
		{
			if (team != NULL && strcmp(team,"red")==0)
				jointeam = CTF_TEAM1;
			else if (team != NULL && strcmp(team,"blue")==0)
				jointeam = CTF_TEAM2;
			// join outnumbered team
			else if (team1count < team2count)
				jointeam = CTF_TEAM1;
			else if (team2count < team1count)
				jointeam = CTF_TEAM2;
			// pick random team
			else if (r < 0.5)
				jointeam = CTF_TEAM1;
			else
				jointeam = CTF_TEAM2;
		}
		ACESP_PutClientInServer (bot,false, jointeam);
	}
	else
 		ACESP_PutClientInServer (bot,false,0);

	// make sure all view stuff is valid
	ClientEndServerFrame (bot);
	
	ACEIT_PlayerAdded (bot); // let the world know we added another

	ACEAI_PickLongRangeGoal(bot); // pick a new goal

}

///////////////////////////////////////////////////////////////////////
// Remove a bot by name or all bots
///////////////////////////////////////////////////////////////////////
void ACESP_RemoveBot(char *name)
{
	int i, j;
	qboolean freed=false;
	edict_t *bot;

	for(i=0;i<maxclients->value;i++)
	{
		bot = g_edicts + i + 1;
		if(bot->inuse)
		{
			if(bot->is_bot && (Q_stricmp(bot->client->pers.netname,name)==0 || Q_stricmp(name,"all")==0))
			{
				bot->health = 0;
				player_die (bot, bot, bot, 100000, vec3_origin);
				// don't even bother waiting for death frames
				bot->deadflag = DEAD_DEAD;
				bot->inuse = false;
				freed = true;
				ACEIT_PlayerRemoved (bot);

				safe_bprintf (PRINT_MEDIUM, "%s removed\n", bot->client->pers.netname);
				// Knightmare- decrement this bot name's counter and exit loop
				if (Q_stricmp(name,"all"))
				{
					for (j = 0; j < num_botinfo; j++)
						if (!Q_stricmp(name, bot_info[j].name))
						{
							bot_info[j].ingame_count--;
							bot_info[j].ingame_count = max(bot_info[j].ingame_count, 0);
							break;
						}
					break;
				}
			}
		}
	}

	if(!freed)	
		safe_bprintf (PRINT_MEDIUM, "%s not found\n", name);
	// Knightmare- removed this
	//ACESP_SaveBots(); // Save them again
}

