/*

  [BC] b_func.cpp
  Last updated: 9/10/2000

  This section of code contains the console commands
  related to the bots, such as "addbot" and "removebot".
  It also contains a console command that displays all
  of the bot path nodes on the level, which can be particularly
  helpful for developers, and for people who need to pay
  close attention to their movement.

*/

#include "doomdef.h"
#include "doomstat.h"
#include "p_local.h"
#include "p_inter.h"
#include "b_bot.h"
#include "g_game.h"
#include "m_random.h"
#include "r_data.h"
#include "r_sky.h"
#include "r_main.h"
#include "st_stuff.h"
#include "stats.h"
#include "i_system.h"
#include "s_sound.h"
#include "vectors.h"
#include "v_video.h"
#include "d_netinf.h"
#include "b_info.h"

class skullb_s;

void skullb_s::Serialize (FArchive &arc)
{
	if (arc.IsStoring ())
	{ // saving to archive
		arc << player
			<< angle
			<< item
			<< goal
			<< prev
			<< line
			<< enemy
			<< missile
			<< ally
			<< last_mate
			<< lastx
			<< lasty
			<< t_respawn
			<< t_strafe
			<< t_react
			<< t_fight
			<< t_rocket
			<< t_turn
			<< t_pissed
			<< t_frust
			<< sidemove
			<< forwardmove
			<< allround
			<< t_chat
			<< bottype
			<< angerlevel
			<< oldx
			<< oldy
			<< thingnum;
	}
	else
	{ // Restoring from archive
		arc	>> player
			>> angle
			>> item
			>> goal
			>> prev
			>> line
			>> enemy
			>> missile
			>> ally
			>> last_mate
			>> lastx
			>> lasty
			>> t_respawn
			>> t_strafe
			>> t_react
			>> t_fight
			>> t_rocket
			>> t_turn
			>> t_pissed
			>> t_frust
			>> sidemove
			>> forwardmove
			>> allround
			>> t_chat
			>> bottype
			>> angerlevel
			>> oldx
			>> oldy
			>> thingnum;
	}
}

FArchive &operator<< (FArchive &arc, pos_t *pos)
{
	if (pos)
		return arc << pos->x << pos->y << pos->z;
	else
		return arc << (BYTE)0xff;
}

FArchive &operator>> (FArchive &arc, pos_t *pos)
{
	BYTE ofs;
	arc >> ofs;
	if (ofs == 0xff)
		pos = NULL;
	else
		arc >> pos->x >> pos->y >> pos->z;

	return arc;
}

FArchive &operator<< (FArchive &arc, skullb_s *bot)
{
	return arc;

	if (bot)
	{
		return arc << bot->player
//			<< bot->angle
			<< bot->item
			<< bot->goal
			<< bot->prev
			<< bot->line
			<< bot->enemy
			<< bot->missile
			<< bot->ally
			<< bot->last_mate
			<< bot->lastx
			<< bot->lasty
			<< bot->t_respawn
			<< bot->t_strafe
			<< bot->t_react
			<< bot->t_fight
			<< bot->t_rocket
			<< bot->t_turn
			<< bot->t_pissed
			<< bot->t_frust
			<< bot->sidemove
			<< bot->forwardmove
			<< bot->allround
			<< bot->t_chat
			<< bot->bottype
			<< bot->angerlevel
			<< bot->oldx
			<< bot->oldy
			<< bot->thingnum;
	}
	else
		return arc << (BYTE)0xff;
}

FArchive &operator>> (FArchive &arc, skullb_s *bot)
{
	return arc;

	BYTE ofs;
	arc >> ofs;
	if (ofs == 0xff)
		bot = NULL;
	else
		arc	>> bot->player
//			>> bot->angle
			>> bot->item
			>> bot->goal
			>> bot->prev
			>> bot->line
			>> bot->enemy
			>> bot->missile
			>> bot->ally
			>> bot->last_mate
			>> bot->lastx
			>> bot->lasty
			>> bot->t_respawn
			>> bot->t_strafe
			>> bot->t_react
			>> bot->t_fight
			>> bot->t_rocket
			>> bot->t_turn
			>> bot->t_pissed
			>> bot->t_frust
			>> bot->sidemove
			>> bot->forwardmove
			>> bot->allround
			>> bot->t_chat
			>> bot->bottype
			>> bot->angerlevel
			>> bot->oldx
			>> bot->oldy
			>> bot->thingnum;

	return arc;
}

// Returns true if a given bot name is valid
BOOL B_CheckName (char *name)
{
	if (!name)
		return true;
	else
	{
		for (int i=0; i<NUMTOTALBOTS; i++)
			if (!stricmp (botinfo[i].name, name))
				return true;
		return false;
	}
}

botskill_t B_SkillLower (botskill_t skill, int num)
{
	int sk = (int)skill;

	if (num <= 0)
		return skill;

	sk -= num;
	if (sk < 0)
		sk = 0;

	return (botskill_t)sk;
}

skullb_s::skullb_s (char *name)
{
	int rnum;
	bottype_t botdata;

	memset (this, 0, sizeof(skullb_s));
	memset (&info, 0, sizeof(botinfo_t));
	line = NULL;
	posdest = lastpos = NULL;
	node = prev = enemy = ally = last_mate = item = goal = missile = NULL;

	if (name)
	{
		for (int i=0; i<NUMTOTALBOTS; i++)
		{
			botdata = (bottype_t)i;
			if (&botinfo[botdata])
			{
				if (!stricmp (botinfo[botdata].name, name))
				{
					bottype = botdata;
					break;
				}
			}
		}
		// We've already handled the "what if there's no match" exception
	}
	else
	{
		// If the user doesn't input a name, don't
		// spawn one of the "special" bots, only one of the
		// normal ones.
		rnum = (P_Random(pr_botspawn) % NUMBOTTYPES);
		bottype = (bottype_t)rnum;
	}

	memcpy (&info, &botinfo[(bottype_t)bottype], sizeof(botinfo_t));

	t_strafe = 1*TICRATE;

	if (botskill.value > 4)
		botskill.OverrideSet (4.0f);
	if (botskill.value < 0)
		botskill.OverrideSet (0.0f);

	// Implement skill settings
	info.accuracy = B_SkillLower (info.accuracy, 4-(int)botskill.value);
	info.intelect = B_SkillLower (info.intelect, 4-(int)botskill.value);
	info.evade    = B_SkillLower (info.evade,    4-(int)botskill.value);
	info.anticip  = B_SkillLower (info.anticip,  4-(int)botskill.value);
	info.reaction = B_SkillLower (info.reaction, 4-(int)botskill.value);

	// Setup combat distance tables
	if (deathmatch.value || teamgame.value)
	{
		combatdist[wp_fist]			= (1*FRACUNIT);
		combatdist[wp_chainsaw]		= (1*FRACUNIT);
		combatdist[wp_pistol]		= (256*FRACUNIT);
		combatdist[wp_shotgun]		= (192*FRACUNIT);
		combatdist[wp_supershotgun]	= (128*FRACUNIT);
		combatdist[wp_chaingun]		= (256*FRACUNIT);
		combatdist[wp_missile]		= (SAFE_SELF_MISDIST*2);
		combatdist[wp_grenlauncher]	= (256*FRACUNIT);
		combatdist[wp_plasma]		= (384*FRACUNIT);
		combatdist[wp_railgun]		= (512*FRACUNIT);
		combatdist[wp_bfg]			= (384*FRACUNIT);
		combatdist[wp_bfg10k]		= (512*FRACUNIT);
	}
	else
	{
		combatdist[wp_fist]			= (1*FRACUNIT);
		combatdist[wp_chainsaw]		= (1*FRACUNIT);
		combatdist[wp_pistol]		= (256*FRACUNIT)/2;
		combatdist[wp_shotgun]		= (192*FRACUNIT)/2;
		combatdist[wp_supershotgun]	= (128*FRACUNIT)/2;
		combatdist[wp_chaingun]		= (256*FRACUNIT)/2;
		combatdist[wp_missile]		= (SAFE_SELF_MISDIST*2)/2;
		combatdist[wp_grenlauncher]	= (256*FRACUNIT)/2;
		combatdist[wp_plasma]		= (384*FRACUNIT)/2;
		combatdist[wp_railgun]		= (512*FRACUNIT)/2;
		combatdist[wp_bfg]			= (384*FRACUNIT)/2;
		combatdist[wp_bfg10k]		= (512*FRACUNIT)/2;
	}

}

skullb_s::~skullb_s ()
{
	if (player)
		player->skullb = NULL;

	if (lastpos)
		delete lastpos;
	lastpos = NULL;

	if (posdest)
		delete posdest;
	posdest = NULL;
}

// skullb_s::LinkToPlayer
//
// Links bot to player and sets
// up userinfo based on bot's botinfo
void skullb_s::LinkToPlayer (int playernum)
{
	player = &players[playernum];
	player->skullb = this;
	playeringame[playernum] = true;
	sprintf (player->userinfo.netname, "%s", info.name);
	player->userinfo.color = V_GetColorFromString (NULL, info.color);
	if (info.skin)
		player->userinfo.skin = R_FindSkin (info.skin);
	else
		player->userinfo.skin = 0;
	player->userinfo.railcolor = info.railcolor;
	player->userinfo.gender = D_GenderToInt (info.gender);
}

void skullb_s::SetAngle (angle_t an)
{
//	if (t_turn)
//		return;

	angle = an;

	if (!enemy)
		return;

	t_turn = 4;
	return;

	switch (info.accuracy)
	{
	case bsk_verypoor:
	case bsk_poor:
		t_turn = TICRATE;
		break;
	case bsk_low:
		t_turn = (TICRATE*2)/3;
		break;
	case bsk_medium:
		t_turn = TICRATE/2;
		break;
	case bsk_high:
		t_turn = TICRATE/4;
		break;
	case bsk_excellent:
		t_turn = 4;
		break;
	case bsk_supreme:
		break;
	}
}

angle_t skullb_s::GetAngle ()
{
	return angle;
}

pos_t::pos_t ()
{
	memset (this, 0, sizeof(pos_t));
}

pos_t::~pos_t ()
{
	;
}

//
// B_WeapFromItem
//
// Returns a weapon type from an actor
//
weapontype_t B_WeapFromItem (AActor *item)
{
	for (int i=0; i<NUMWEAPONS; i++)
	{
		if (weaponinfo[i].droptype == item->type)
			return (weapontype_t)i;
	}
	return (weapontype_t)0;
}

//
// B_AmmoFromItem
//
// Returns an ammo type from an actor
//
ammotype_t B_AmmoFromItem (AActor *item)
{
	switch (item->type)
	{
	case MT_CLIP:
	case MT_AMMOBOX:
		return am_clip;
	case MT_SHELLS:
	case MT_SHELLBOX:
		return am_shell;
	case MT_MISSILE:
	case MT_MISSILEBOX:
		return am_misl;
	case MT_CELL:
	case MT_CELLPACK:
		return am_cell;
	default:
		I_Error ("Unknown ammo item: %d", (int)item->type);
		return am_cell;
	}
}

//
// B_CheckPath
//
// Checks for obstructions at a certain angle
// and distance. Returns true if the path is clear,
// and false is the path is blocked.
//
BOOL B_CheckPath (AActor *mo, angle_t ang, fixed_t dist)
{
	fixed_t 	x1, y1, x2, y2;
		
	x1 = mo->x;
	y1 = mo->y;
 	x2 = x1 + (dist>>FRACBITS)*finecosine[ang>>ANGLETOFINESHIFT];
 	y2 = y1 + (dist>>FRACBITS)*finesine[ang>>ANGLETOFINESHIFT];
	usething = mo;

	P_UseLines (mo->player);
	botsector = mo->subsector->sector;

	if (!P_PathTraverse (x1, y1, x2, y2, PT_ADDLINES, PTR_BotPathTraverse))
		return false;
	else
		return true;
}

//doesnt check LOS, checks visibility with a set view angle.
//B_Checksight checks LOS (straight line)
//----------------------------------------------------------------------
//Check if mo1 has free line to mo2
//and if mo2 is within mo1 viewangle (vangle) given with normal degrees.
//if these conditions are true, the function returns true.
//GOOD TO KNOW is that the players view angle
//in doom is 90 degrees infront.
bool Check_LOS (AActor *from, AActor *to, angle_t vangle)
{
	if (!P_CheckSight (from, to, false))
		return false; // out of sight
	if (vangle == ANGLE_MAX)
		return true;
	if (vangle == 0)
		return false; //Looker seems to be blind.

	return abs (R_PointToAngle2 (from->x, from->y, to->x, to->y) - from->angle) <= (int)vangle/2;
}

//
// B_SetAction
//
// Decide what to do in teamgame (defend, whatever)
//
void B_SetAction (skullb_s *bot)
{
	AActor *mo = bot->player->mo;
	int defenders = 0;
	int attackers = 0;

	if (bot->player->team == TM_NONE)
		return;

	if (bot->state == BST_NOTHING)
	{
		return;

		// Let's see what the other bots on our
		// team are doing, and decide our action based
		// off of that
		for (int i=0; i<MAXPLAYERS; i++)
		{
			if (playeringame[i] && players[i].mo->IsTeammate (mo) && players[i].skullb)
			{
				if (players[i].skullb->state == BST_OFFENSE)
					attackers++;
				if (players[i].skullb->state == BST_DEFENSE)
					defenders++;
			}
		}

		// Defense wins championships...
		if (defenders <= attackers)
			bot->state = BST_DEFENSE;
		else
			bot->state = BST_OFFENSE;
	}
}

void B_SetGoal (skullb_s *bot)
{
	return;

	AActor *mo = bot->player->mo;
	AActor *actor;

	if (bot->player->team == TM_NONE)
		return;

	TThinkerIterator<AActor> iterator;

	while ((actor = iterator.Next ()) && !bot->goal)
	{
		if (bot->state == BST_OFFENSE)
		{
			if (actor->type == teams[bot->player->team].theirs)
				bot->goal = actor;
		}
		else if (bot->state == BST_RETURN)
		{
			if (actor->type == teams[bot->player->team].ours)
				bot->goal = actor;
		}
	}
}

//This function is called every
//tick (for each bot) to set
//the mate (teammate coop mate).
AActor *B_SetAlly (skullb_s *bot)
{
	return NULL;
	
	fixed_t closest_dist, test;
	AActor *target;
	bool p_leader[MAXPLAYERS];

	//is mate alive?
	if (bot->ally)
	{
		if (!bot->ally->health)
			bot->ally = NULL;
		else
		{
			bot->last_mate = bot->ally;
			return bot->ally;
		}
	}

	//Check old_mates status.
	if (bot->last_mate)
		if (!bot->last_mate->health)
			bot->last_mate = NULL;

	for (int i=0; i<MAXPLAYERS; i++)
	{
		if (!playeringame[i] || !players[i].team || i == consoleplayer)
			continue;
		if (players[i].team == bot->player->team)
		{
			bot->ally = players[i].mo;
			break;
		}

		p_leader[i] = false;
		for (int j=0; j<MAXPLAYERS; j++)
		{
			if (players[j].skullb->ally == players[i].mo)
			{
				p_leader[i] = true;
				break;
			}
		}
	}

	target = NULL;
	closest_dist = MAXINT;

	//Check for player friends
	for (i=0; i<MAXPLAYERS; i++)
	{
		if (playeringame[i]
			&& players[i].mo
			&& bot->player->mo != players[i].mo
			&& (bot->player->mo->IsTeammate (players[i].mo) || !deathmatch.value)
			&& players[i].mo->health
			&& ((bot->player->health/2) <= players[i].mo->health || !deathmatch.value)
			&& !p_leader[i]) //taken?
		{

			if (P_CheckSight (bot->player->mo, players[i].mo, true))
			{
				test = P_AproxDistance (players[i].mo->x - bot->player->mo->x,
										players[i].mo->y - bot->player->mo->y);

				if (test < closest_dist)
				{
					closest_dist = test;
					target = players[i].mo;
				}
			}
		}
	}

	return target;
}

void B_Aim (skullb_s *bot)
{
	AActor *enemy = bot->enemy;
	AActor *mo = bot->player->mo;
	fixed_t dist;
	static bool inc[MAXPLAYERS];

	if (!enemy)
		return;

	if (bot->t_react)
		return;

	// Distance to enemy.
	dist = P_AproxDistance ((mo->x + mo->momx) - (enemy->x + enemy->momx),
		(mo->y + mo->momy) - (enemy->y + enemy->momy));

	int i;
	bool right = !!(P_Random (pr_botchat) % 2);
	angle_t	an = R_PointToAngle2 (mo->x, mo->y, enemy->x, enemy->y);

	// [BC] Cajun prediction... maybe use this somewhere
	/*
			{
			//Here goes the prediction.
			dist = P_AproxDistance (mo->x - enemy->x, mo->y - enemy->y);
			fixed_t m = (dist/FRACUNIT) / mobjinfo[MT_PLASMA].speed;
			bot->SetAngle (R_PointToAngle2 (mo->x,
											mo->y,
											enemy->x + FixedMul (enemy->momx, (m*2*FRACUNIT)),
											enemy->y + FixedMul (enemy->momy, (m*2*FRACUNIT))));
			}
	*/


	// Fix me: Implement botskill, accuracy, and intelligence
	switch (mo->player->readyweapon)
	{
		// Splash weapons
	case wp_missile:
	case wp_grenlauncher:
//		bot->SetAngle (R_PointToAngle2 (mo->x, mo->y, enemy->x, enemy->y));
//		break;

		// Projectile weapons
	case wp_plasma:

		switch (bot->info.intelect)
		{
		case bsk_verypoor:
		case bsk_poor:
		case bsk_low:
			// Aim right at the enemy
			bot->SetAngle (an);
			break;
		case bsk_medium:
		case bsk_high:
		case bsk_excellent:
		case bsk_supreme:
			i = P_Random (pr_botchat) % 20;

			if (right)
				bot->SetAngle (an + ANGLE_1*i);
			else
				bot->SetAngle (an - ANGLE_1*i);
			break;

		default:
			I_Error ("Unknown bot skill level: %d", (int)bot->info.accuracy);
			return;
		}

		break;
		// BFG9000
	case wp_bfg:
		bot->SetAngle (R_PointToAngle2 (mo->x, mo->y, enemy->x, enemy->y));
		break;

		// Instant weapons
	case wp_chainsaw:
	case wp_fist:
	default:

		switch (bot->info.accuracy)
		{
		case bsk_verypoor:
			i = P_Random (pr_botchat) % 60;

			if (right)
				bot->SetAngle (an + ANGLE_1*i);
			else
				bot->SetAngle (an - ANGLE_1*i);
			break;
		case bsk_poor:
			i = P_Random (pr_botchat) % 45;

			if (right)
				bot->SetAngle (an + ANGLE_1*i);
			else
				bot->SetAngle (an - ANGLE_1*i);
			break;
		case bsk_low:
			i = P_Random (pr_botchat) % 30;

			if (right)
				bot->SetAngle (an + ANGLE_1*i);
			else
				bot->SetAngle (an - ANGLE_1*i);
			break;
		case bsk_medium:
			i = P_Random (pr_botchat) % 15;

			if (right)
				bot->SetAngle (an + ANGLE_1*i);
			else
				bot->SetAngle (an - ANGLE_1*i);
			break;
		case bsk_high:
			bot->SetAngle (an);
			break;
		case bsk_excellent:
			bot->SetAngle (R_PointToAngle2 (mo->x, mo->y, enemy->x+enemy->momx, enemy->y+enemy->momy));
			break;
		case bsk_supreme:
			bot->SetAngle (R_PointToAngle2 (mo->x+mo->momx, mo->y+mo->momy,
											enemy->x+enemy->momx, enemy->y+enemy->momy));
			break;
		default:
			I_Error ("Unknown bot skill level: %d", (int)bot->info.accuracy);
			return;
		}
	}
}

AActor *B_FindEnemy (skullb_s *bot)
{
	fixed_t closest_dist, temp;
	AActor *target;
	AActor *mo = bot->player->mo;
	angle_t vangle;

	//Note: It's hard to ambush a bot who is not alone
	if (bot->allround || bot->ally)
		vangle = ANGLE_MAX;
	else
		vangle = ENEMY_SCAN_FOV;
	bot->allround = false;

	// Look for monster enemy
	if (!deathmatch.value && !teamgame.value)
	{
		AActor *actor;
		TThinkerIterator<AActor> iterator;

		while (actor = iterator.Next())
		{
			if (!(actor->flags3 & MF3_ENEMY))
				continue;
			if (!Check_LOS (mo, actor, vangle))
				continue;
			if (!P_CheckSight (mo, actor))
				continue;

			return actor;
		}

		// Didn't find anything...
		return NULL;
	}

	target = NULL;
	closest_dist = MAXINT;

	// Search for player enemies
	for (int i=0; i<MAXPLAYERS; i++)
	{
		if (playeringame[i] && !mo->IsTeammate (players[i].mo)
			&& players[i].mo->health && mo != players[i].mo)
		{
			 //Here's a strange one, when bot is standing still, the P_CheckSight within Check_LOS almost always returns false. tought it should be the same checksight as below but.. (below works) something must be fuckin wierd screded up. 
			if (Check_LOS (mo, players[i].mo, vangle))
			{
				if(P_CheckSight(mo, players[i].mo))
				{
					temp = P_AproxDistance (players[i].mo->x - mo->x,
											players[i].mo->y - mo->y);

					//Too dark?
					if (temp > DARK_DIST &&
						players[i].mo->subsector->sector->lightlevel < WHATS_DARK &&
						bot->player->powers[pw_infrared])
						continue;
	
					if (temp < closest_dist)
					{
						closest_dist = temp;
						target = players[i].mo;
					}
				}
			}
		}
	}

	return target;
}

void ActorToPos (AActor *mo, pos_t *pos)
{
	pos->x = mo->x;
	pos->y = mo->y;
	pos->z = mo->z;
}


// [RH] We absolutely do not want to pick things up here. The bot code is
// executed apart from all the other simulation code, so we don't want it
// creating side-effects during gameplay.
bool SafeCheckPosition (AActor *actor, fixed_t x, fixed_t y)
{
	int savedFlags = actor->flags;
	actor->flags &= ~MF_PICKUP;
	bool res = P_CheckPosition (actor, x, y) ? true : false;
	actor->flags = savedFlags;
	return res;
}