/*

  [BC] b_think.cpp
  Last updated: 10/12/2000

  This section contains the main bot AI. The
  main bot AI loop, B_Think, is called every tic.
  Also included are various bot decision-making
  procedures, such as B_CheckStuff and B_SetEnemy.

*/

#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_main.h"
#include "stats.h"
#include "c_dispatch.h"
#include "ct_chat.h"
#include "Hu_stuff.h"

// Main bot function
void B_Think (skullb_t *bot)
{
	if (paused)
		return;

	ticcmd_t *cmd = &bot->player->cmd;
	AActor *mo = bot->player->mo;

	memset (cmd, 0, sizeof(ticcmd_t));

	// Don't do anything if we're talking
	if (B_Chat (bot))
		return;

	if (demoplayback)
		return;

	// We're dead, so hit space to respawn
	if (!bot->player->health)
	{
		cmd->ucmd.buttons |= BT_USE;
		return;
	}

	// Fix me: No other talking period while pissed/frustrated
	if (!(level.time & 0xff) && !bot->enemy && !bot->t_pissed && !bot->t_frust)
	{
		int x = P_Random (pr_botchat) % 100;

		if (x < bot->info.chatty)
		{
			if (x)
			{
				x = P_Random (pr_botchat)%10;
				bot->chatline = &chatinfo[bot->info.chatinfo].roam[x];
				bot->t_chat = bot->info.chattime;
			}
			else	// 1/100 chance this'll happen
			{
				// Rare chat strings. WHOO!!!!!
				x = P_Random (pr_botchat)%5;
				bot->chatline = &chatinfo[bot->info.chatinfo].rare[x];
				bot->t_chat = bot->info.chattime;
			}
		}

	}

	// Periodically let out some anger
	if (!(level.time & 0xff))
	{
		if (bot->angerlevel)
			bot->angerlevel--;
		else if (bot->angerlevel < 0)
			bot->angerlevel = 0;
	}

	// Set an ally if we're in a mode that has one
	if (teamgame.value || teamplay.value || !deathmatch.value)
		bot->ally = B_SetAlly (bot);
	else
		bot->ally = NULL;
	
	if (teamgame.value)
	{
		B_SetAction (bot);
		B_SetGoal (bot);
	}

	// Weed out any bad destinations/enemies
	B_CheckStuff (bot);

	// Destination setting...
	B_SetEnemy (bot);
	B_Scan (bot);

	// Turning towards destination...
	B_Aim (bot);
	B_Turn (bot);

	// Moving towards destination
	B_Attack (bot);
	B_Move (bot);

	B_Pitch (bot);

	// Only walk if we're on skill 0
	if (!(int)botskill.value)
	{
		if (bot->forwardmove == FORWARDRUN)
			bot->forwardmove = FORWARDWALK;
		if (bot->forwardmove == -FORWARDRUN)
			bot->forwardmove = -FORWARDWALK;
		if (bot->forwardmove == SIDERUN)
			bot->forwardmove = SIDEWALK;
		if (bot->forwardmove == -SIDERUN)
			bot->forwardmove = -SIDEWALK;
	}

	cmd->ucmd.sidemove = bot->sidemove;
	cmd->ucmd.forwardmove = bot->forwardmove;

	if (bot->t_react)
		bot->t_react--;
	if (bot->t_fight)
		bot->t_fight--;
	if (bot->t_rocket)
		bot->t_rocket--;
	if (bot->t_turn)
		bot->t_turn--;
	if (bot->t_pissed)
		bot->t_pissed--;
	if (bot->t_frust)
		bot->t_frust--;
	if (bot->t_fire)
		bot->t_fire--;
	if (bot->t_anticip)
		bot->t_anticip--;
}

// 
// B_CheckItem
// 
// Determines if we should bother picking up an item or not
//

BOOL B_CheckItem (AActor *actor, AActor *item)
{
	skullb_t *b = actor->player->skullb;

    if (!item)
		return false;
	if (!(item->flags & MF_SPECIAL))
		return false;
	if (item->flags3 & MF3_WEAPON)
	{
		weapontype_t weapon = B_WeapFromItem (item);

		// If we don't have the weapon, pick it up
		if (!b->player->weaponowned[weapon])
			return true;

		// If we have no more room for the ammo it gives
		if (b->player->ammo[weaponinfo[weapon].ammo] == b->player->maxammo[weaponinfo[weapon].ammo])
			return false;

		// Can't pick it up because we have it and it's not a dropped weapon
		if (!(item->flags & MF_DROPPED))
			return false;

	}
	if (item->flags3 & MF3_AMMO)
	{
		ammotype_t ammo = B_AmmoFromItem (item);

		// If we have no more room for the ammo it gives
		if (b->player->ammo[ammo] >= b->player->maxammo[ammo])
			return false;
	}
	if (item->flags3 & MF3_LOWHEALTH && (actor->health >= b->player->maxlowhealth))
		return false;
	if (item->flags3 & MF3_HEALTH && (actor->health >= b->player->maxhighhealth) && item->type != MT_MAXHTHBONUS)
		return false;
	if (item->flags3 & MF3_LOWARMOR && (actor->player->armorpoints >= 100 ))
		return false;
	if (item->flags3 & MF3_ARMOR && (actor->player->armorpoints >= b->player->maxhighhealth) && item->type != MT_MAXARMBONUS)
		return false;
	if (teamgame.value && item->flags3 & MF3_KEY && !(item->flags & MF_DROPPED))
		return false;

	// Guess we're okay
	return true;
}

void B_SetEnemy (skullb_s *bot)
{
	if (terminator.value)
	{
		// If we can see the terminator, go for him no matter what
		for (int i=0; i<MAXPLAYERS; i++)
		{
			if (!playeringame[i] || players[i].mo == bot->player->mo)
				continue;

			if (players[i].powers[pw_terminator] &&
				P_CheckSight (bot->player->mo, players[i].mo))
				bot->enemy = players[i].mo;
		}
	}

	if (bot->enemy && bot->enemy->health && bot->player->mo && P_CheckSight (bot->player->mo, bot->enemy))
		return;

	bot->allround = !!bot->enemy;
	bot->enemy = B_FindEnemy (bot);

	if (!bot->enemy)
		return;
	
	// Double check the validity of the enemy
	if (!(bot->enemy->flags & MF_SHOOTABLE))
		bot->enemy = NULL;
}

//
// B_CheckTo
//

// Checks if an location is reachable
bool B_CheckTo (skullb_s *bot, pos_t *pos)
{
	AActor *mo;
	fixed_t dist;
	angle_t an;

	mo = bot->player->mo;

	dist = P_AproxDistance (mo->x - pos->x, mo->y - pos->y);
	an = bot->GetAngle();

	if (!B_CheckPath (mo, an, dist))
		return false;
	
	return true;
}

//
// B_CheckStuff
//

// Make sure that our destinations/enemies
// and everything are valid.
void B_CheckStuff (skullb_s *bot)
{
	AActor *mo = bot->player->mo;

	if (bot->item)
	{
		pos_t *pos = new pos_t;
		ActorToPos (bot->item, pos);
		if (!B_CheckTo (bot, pos))
			bot->item = NULL;

		delete pos;
	}

	if (bot->node)
	{
		pos_t *pos = new pos_t;
		ActorToPos (bot->node, pos);

		if (!B_CheckTo (bot, pos)
			|| (P_AproxDistance(mo->x - bot->node->x, mo->y - bot->node->y) < mo->info->radius))
		{
			bot->prev = bot->node;
			bot->node = NULL;
		}

		delete pos;
	}
	if (bot->posdest)
	{
		if (!B_CheckTo (bot, bot->posdest)
			|| (P_AproxDistance(mo->x - bot->posdest->x, mo->y - bot->posdest->y) < mo->info->radius))
		{
			delete bot->posdest;
			bot->posdest = NULL;
		}
	}
	if (bot->lastpos)
	{
		if (!B_CheckTo (bot, bot->lastpos)
			|| (P_AproxDistance(mo->x - bot->lastpos->x, mo->y - bot->lastpos->y) < mo->info->radius))
		{
			delete bot->lastpos;
			bot->lastpos = NULL;
		}
	}
	if (bot->enemy)
	{
		if (!bot->enemy->health || !(bot->enemy->flags & MF_SHOOTABLE))
			bot->enemy = NULL;
	}
}

//
// B_ChooseWeapon
//
// Decide which weapon would be best to use in out situation
weapontype_t B_ChooseWeapon (skullb_s *bot)
{
	player_t *player = bot->player;
	AActor *mo = bot->player->mo;
	AActor *enemy = bot->enemy;
	fixed_t dist;

	if (!enemy)
		return player->readyweapon;

	dist = P_AproxDistance (mo->x - enemy->x, mo->y - enemy->y);

	if (dist <= MELEERANGE)
	{
		if (player->weaponowned[wp_supershotgun] && player->ammo[am_shell] >= 2)
			return wp_supershotgun;

		if (player->weaponowned[wp_plasma] && player->ammo[am_cell] >= 20)
			return wp_plasma;

		if (player->weaponowned[wp_railgun] && player->ammo[am_cell] >= 10)
			return wp_railgun;

		if (player->weaponowned[wp_chaingun] && player->ammo[am_clip] >= 20)
			return wp_chaingun;

		if (player->powers[pw_strength])
			return wp_fist;

		if (player->weaponowned[wp_chainsaw])
			return wp_chainsaw;

		if (player->weaponowned[wp_shotgun] && player->ammo[am_shell])
			return wp_shotgun;

		// Didn't find anything good
		return player->readyweapon;
	}

	else if (dist <= 512*FRACUNIT)
	{
		if (player->weaponowned[wp_plasma] && player->ammo[am_cell] >= 20)
			return wp_plasma;

		if (player->weaponowned[wp_supershotgun] && player->ammo[am_shell] >= 2)
			return wp_supershotgun;

		if (player->weaponowned[wp_grenlauncher] && player->ammo[am_misl] >= 2)
			return wp_grenlauncher;

		if (player->weaponowned[wp_missile] && player->ammo[am_misl] >= 2)
			return wp_missile;

		if (player->weaponowned[wp_railgun] && player->ammo[am_cell] >= 10)
			return wp_railgun;

		if (player->weaponowned[wp_chaingun] && player->ammo[am_clip])
			return wp_chaingun;

		if (player->weaponowned[wp_shotgun] && player->ammo[am_shell])
			return wp_shotgun;

		return player->readyweapon;
	}

	else
	{
		if (player->weaponowned[wp_railgun] && player->ammo[am_cell] >= 10)
			return wp_railgun;

		if (player->weaponowned[wp_chaingun] && player->ammo[am_clip])
			return wp_chaingun;

		if (player->weaponowned[wp_shotgun] && player->ammo[am_shell])
			return wp_shotgun;

		if (player->weaponowned[wp_plasma] && player->ammo[am_cell] >= 20)
			return wp_plasma;

		if (player->weaponowned[wp_missile] && player->ammo[am_misl] >= 2)
			return wp_missile;

		if (player->weaponowned[wp_grenlauncher] && player->ammo[am_misl] >= 2)
			return wp_grenlauncher;

		return player->readyweapon;
	}

	return player->readyweapon;
}

/*
mobjtype_t B_ChooseBetween (mobjtype current, mobjtype newobj)
{

}
*/
//
// B_Scan
//
// Scan all mobj's visible to the bot for
// incoming missiles, enemies, and various items
// to pick up.
//

void B_Scan (skullb_s *bot)
{
	AActor *mo = bot->player->mo;
	AActor *actor;

	TThinkerIterator<AActor> iterator;

	while (actor = iterator.Next ())
	{
		if (Check_LOS (mo, actor, ANGLE_90))
		{
			// Look for special items
			if (!bot->item && actor->flags & MF_SPECIAL)
				if (B_CheckItem (mo, actor))
					bot->item = actor;

			else if (!bot->missile && actor->flags & MF_MISSILE && (P_AproxDistance(mo->x - actor->x, mo->y - actor->y) < AVOID_DIST))
				bot->missile = actor;

			else if (!bot->node && actor->flags3 & MF3_NODE)// && P_CheckSight (mo, actor, false) && (bot->prev != actor && P_AproxDistance (mo->x - actor->x, mo->y - actor->y) <= 32*FRACUNIT))
			{
				// Note: Debug messages are fun!
				if (players[consoleplayer].camera == mo)
					Printf (PRINT_HIGH, "Spotted a node\n");
				if (actor->type == MT_BOTPATH || actor->type == MT_BOTSNIPE)
					bot->node = actor;
				else if (teamgame.value && bot->player->team != TM_NONE)
				{
					if (bot->player->team == TM_BLUE)
					{
						if (bot->state == BST_DEFENSE && ((int)actor->type == (int)MT_BOTBLUEGUARD))
						{
							if (players[consoleplayer].camera == mo)
								Printf (PRINT_HIGH, "Blue defense node\n");
							bot->node = actor;
						}
						else if (bot->state == BST_OFFENSE && ((int)actor->type == (int)MT_BOTBLUEPATH))
						{
							if (players[consoleplayer].camera == mo)
								Printf (PRINT_HIGH, "Blue offense node\n");
							bot->node = actor;
						}
						else if (bot->state == BST_RETURN && actor->type == MT_BOTBLUEGOAL)
						{
							if (players[consoleplayer].camera == mo)
								Printf (PRINT_HIGH, "Blue return node\n");
							bot->node = actor;
						}
					}
					if (bot->player->team == TM_RED)
					{
						if (bot->state == BST_DEFENSE && ((int)actor->type == (int)MT_BOTREDGUARD))
	
						{
							if (players[consoleplayer].camera == mo)
								Printf (PRINT_HIGH, "Red defense node\n");
							bot->node = actor;
						}
						else if (bot->state == BST_OFFENSE && ((int)actor->type == MT_BOTREDPATH))
						{
							if (players[consoleplayer].camera == mo)
								Printf (PRINT_HIGH, "Red offense node\n");
							bot->node = actor;
						}
						else if (bot->state == BST_RETURN && ((int)actor->type == MT_BOTREDGOAL))
						{
							if (players[consoleplayer].camera == mo)
								Printf (PRINT_HIGH, "Red return node\n");
							bot->node = actor;
						}
					}
				}
			}
		}
	}
}

//
// B_Chat
//

// Tic down bot's typing time, and say our message if we're done
bool B_Chat (skullb_s *bot)
{
	// Chat ticker
	if (bot->t_chat)
	{
		if (!bot->chatline || !bot->chatline->string || !botchat.value)
		{
			bot->t_chat = 0;
			bot->player->icon = NULL;
			return false;
		}
		else
			bot->t_chat--;

		// Chat time has expired, time to say our line
		if (!bot->t_chat)
		{
			int plyrnum = bot->player - players;
			Net_Command (DEM_SAY, 0, bot->chatline->string, plyrnum);

			// Handle responses
			for (int i=0; i<MAXPLAYERS; i++)
			{
				if (!players[i].skullb || !playeringame[i])
					continue;
				if ((bot->info.chatinfo != CT_NONE) && (bot->chatline->bot == players[i].skullb->info.chatinfo))
				{
					players[i].skullb->chatline = bot->chatline->line;
					players[i].skullb->t_chat = players[i].skullb->info.chattime;
				}
			}
			bot->chatline = NULL;
		}
		
		// Don't do anything while "typing"
		return true;

	}

	return false;
}