
#include "doomdef.h"
#include "doomstat.h"
#include "p_local.h"
#include "p_inter.h"
#include "b_bot.h"
#include "g_game.h"
#include "d_ticcmd.h"
#include "m_random.h"
#include "r_main.h"
#include "i_system.h"
#include "p_lnspec.h"

#ifndef M_PI
#define M_PI		3.14159265358979323846	// matches value in gcc v2 math.h
#endif

// Handle non-attack/dodging movement
void B_Roam (skullb_s *bot)
{
	int r;
	player_t *b = bot->player;
	AActor *mo = bot->player->mo;
	pos_t *dest = NULL;

	if (bot->lastpos && P_AproxDistance (mo->x - bot->lastpos->x, mo->y - bot->lastpos->y) <= 32*FRACUNIT)
	{
		delete bot->lastpos;
		bot->lastpos = NULL;
	}

	// Order of item response precedence:
	if (bot->goal)
	{
//		if (players[consoleplayer].camera == mo)
//			Printf (PRINT_HIGH, "B_Move: Dest is goal\n");
		dest = new pos_t ();
		ActorToPos(bot->goal, dest);
	}
	else if (bot->lastpos)
	{
//		if (players[consoleplayer].camera == mo)
//			Printf (PRINT_HIGH, "B_Move: Dest is lastpos\n");
		dest = new pos_t ();
		dest->x = bot->lastpos->x;
		dest->y = bot->lastpos->y;
		dest->z = bot->lastpos->z;
	}
	else if (bot->item)
	{
//		if (players[consoleplayer].camera == mo)
//			Printf (PRINT_HIGH, "B_Move: Dest is item type %d\n", (int)bot->item->type);
		dest = new pos_t ();
		ActorToPos(bot->item, dest);
	}
	else if (bot->node)
	{
//		if (players[consoleplayer].camera == mo)
//			Printf (PRINT_HIGH, "B_Move: Dest is node\n");
		dest = new pos_t ();
		ActorToPos (bot->node, dest);
	}
	else if (bot->posdest)
	{
//		if (players[consoleplayer].camera == mo)
//			Printf (PRINT_HIGH, "B_Move: Dest is position\n");
		dest = new pos_t ();
		dest->x = bot->posdest->x;
		dest->y = bot->posdest->y;
		dest->z = bot->posdest->z;
	}

	if (!dest)
	// No target, so just run around until we find something
	{
//		if (players[consoleplayer].camera == mo)
//			Printf (PRINT_HIGH, "%s: Looking for a path\n", bot->player->userinfo.netname);

		r = P_Random(pr_botmove);
		angle_t an = bot->GetAngle();// >> ANGLETOFINESHIFT;
		fixed_t dist;

		for (dist = 256*FRACUNIT; dist >= 64*FRACUNIT; dist -= 64*FRACUNIT)
		{
			if (B_CheckPath (mo, an, dist))
			{
				bot->posdest = new pos_t ();
				bot->posdest->x = mo->x + (dist>>FRACBITS)*finecosine[an>>ANGLETOFINESHIFT];
				bot->posdest->y = mo->y + (dist>>FRACBITS)*finesine[an>>ANGLETOFINESHIFT];
				bot->posdest->z = mo->z;
				bot->SetAngle (an);

//				if (players[consoleplayer].camera == mo)
//					Printf (PRINT_HIGH, "%s: Found place in front %i units\n", bot->player->userinfo.netname, dist/FRACUNIT);

				break;
			}

			if (B_CheckPath (mo, an + ANG45, dist))
			{
				bot->posdest = new pos_t ();
				bot->posdest->x = mo->x + (dist>>FRACBITS)*finecosine[(an+ANG45)>>ANGLETOFINESHIFT];
				bot->posdest->y = mo->y + (dist>>FRACBITS)*finesine[(an+ANG45)>>ANGLETOFINESHIFT];
				bot->posdest->z = mo->z;
				bot->SetAngle (an + ANG45);

//				if (players[consoleplayer].camera == mo)
//					Printf (PRINT_HIGH, "%s: Found place to left %i units\n", bot->player->userinfo.netname, dist/FRACUNIT);

				break;
			}

			// Left is no good, try right
			if (B_CheckPath (mo, an - ANG45, dist))
			{
				bot->posdest = new pos_t ();
				bot->posdest->x = mo->x + (dist>>FRACBITS)*finecosine[(an-ANG45)>>ANGLETOFINESHIFT];
				bot->posdest->y = mo->y + (dist>>FRACBITS)*finesine[(an-ANG45)>>ANGLETOFINESHIFT];
/*				bot->posdest->x = mo->x + FixedMul (dist, finecosine[an-ANG45>>ANGLETOFINESHIFT]);
				bot->posdest->y = mo->y + FixedMul (dist, finesine[an-ANG45>>ANGLETOFINESHIFT]);*/
				bot->posdest->z = mo->z;
				bot->SetAngle (an - ANG45);

//				if (players[consoleplayer].camera == mo)
//					Printf (PRINT_HIGH, "%s: Found place to right %i units\n", bot->player->userinfo.netname, dist/FRACUNIT);

				break;
			}

//			if (players[consoleplayer].camera == mo)
//				Printf (PRINT_HIGH, "%s: No path at %i units, look again\n", bot->player->userinfo.netname, dist/FRACUNIT);
		}
		if (bot->posdest)
		{
			dest = new pos_t ();
			dest->x = bot->posdest->x;
			dest->y = bot->posdest->y;
			dest->z = bot->posdest->z;
		}
		else
		{
			bot->SetAngle (bot->GetAngle() + ANG45/3);
			bot->forwardmove = -FORWARDWALK;
			return;
		}
	}

	bot->forwardmove = FORWARDRUN;
	bot->SetAngle (R_PointToAngle2 (mo->x, mo->y, dest->x, dest->y));

	delete dest;
}


// Main bot movement function. Dodging/attacking movement
// is also handled here
void B_Move (skullb_s *bot)
{
	player_t *b = bot->player;
	AActor *mo = bot->player->mo;
	ticcmd_t *cmd = &bot->player->cmd;

	fixed_t dist;
	
	// Worry about missiles above all else
	if (bot->missile)
	{
		if (bot->t_strafe)
			if (!--bot->t_strafe)
			{
				// Don't change direction while dodging missiles (that could be bad)
				//bot->sidemove = -bot->sidemove;
				bot->t_strafe = 2*TICRATE;
			}

		// Look at the missle and sidestep it
		bot->SetAngle (R_PointToAngle2 (mo->x, mo->y, bot->missile->x, bot->missile->y));
		bot->forwardmove = -FORWARDRUN;
		return;
	}

	// Anticipate a shot: time to dodge!
	if (bot->enemy && bot->t_anticip <= TICRATE/4)
	{
		if (!bot->sidemove )
			bot->sidemove = SIDERUN;

		if (bot->player - players == displayplayer && netstate != NS_SERVER)
			Printf (PRINT_HIGH, "sidemove: %d\nforwardmove: %d\n", bot->sidemove, bot->forwardmove);

		switch (bot->info.anticip)
		{
		case bsk_verypoor:
			// Deer caught in the headlights
			bot->sidemove = 0;
			return;
		case bsk_poor:
			// Always walk right
			bot->sidemove = SIDEWALK;
			break;
		case bsk_low:
			// Always run right
			bot->sidemove = SIDERUN;
			break;
		case bsk_medium:
			// Just switch directions every couple seconds
			if (bot->t_strafe)
			{
				if (!--bot->t_strafe)
				{
					bot->sidemove = -bot->sidemove;
					bot->t_strafe = 2*TICRATE;
				}
			}
			break;
		case bsk_high:
			// Switch directions when we think our opponent will fire
			if (!bot->t_anticip)
				bot->sidemove = -bot->sidemove;
			break;
		case bsk_excellent:
			// Move in a somewhat random direction when we think our opponent will fire
			if (!bot->t_anticip)
			{
				int i;
					
				i = P_Random (pr_botchat) % 2;
				bot->sidemove = i ? SIDERUN : -SIDERUN;

				i = P_Random (pr_botchat) % 2;
				if (i)
					bot->forwardmove = -FORWARDWALK;
			}
			break;
		case bsk_supreme:
			// What a squirmy little fucker!
			if (!bot->t_anticip)
			{
				int i;
					
				i = P_Random (pr_botchat) % 4;

				switch (i)
				{
				case 0:
					bot->sidemove = SIDERUN;
					break;
				case 1:
					bot->sidemove = SIDEWALK;
					break;
				case 2:
					bot->sidemove = -SIDERUN;
					break;
				case 3:
					bot->sidemove = -SIDEWALK;
					break;
				}

				i = P_Random (pr_botchat) % 4;

				switch (i)
				{
				case 0:
					bot->forwardmove = FORWARDRUN;
					break;
				case 1:
					bot->forwardmove = FORWARDWALK;
					break;
				case 2:
					bot->forwardmove = -FORWARDRUN;
					break;
				case 3:
					bot->forwardmove = -FORWARDWALK;
					break;
				}
			}
			break;
		default:
			I_Error ("Unknown bot skill level: %d", (int)bot->info.anticip);
			return;
		}
	}
	// Now handle attack movement
	if (bot->enemy)
	{
		if (!bot->sidemove )
			bot->sidemove = SIDERUN;

		if (bot->t_strafe)
		{
			if (!--bot->t_strafe)
			{
				bot->sidemove = -bot->sidemove;
				bot->t_strafe = 2*TICRATE;
			}
		}

		bool noforward = false, noside = false;

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

		// Remember where we saw him last in case he gets away
		if (!bot->lastpos)
			bot->lastpos = new pos_t ();

		ActorToPos (bot->enemy, bot->lastpos);

		// Check if we'd rather pick up something than fight
		if (bot->item)
		{
			if (bot->item->flags3 & MF3_POWERUP || (bot->item->flags3 & MF3_WEAPON && b->readyweapon == wp_pistol))
			{
				B_Roam (bot);
				return;
			}
		}

		{
			angle_t an;

			an = mo->angle;
			if (bot->sidemove < 0)
				an -= ANG90;
			else
				an += ANG90;

			if (!B_CheckPath (mo, an, 48*FRACUNIT)) // We're blocked, so go the other way!
				bot->sidemove = -bot->sidemove;
		}
/*
		switch (bot->player->readyweapon)
		{
		case wp_supershotgun:

		default:
			if (!B_CheckTo(mo, bot->enemy)
*/

		pos_t *pos = new pos_t;
		ActorToPos(bot->enemy, pos);

		if (B_CheckTo(bot, pos) && dist > bot->combatdist[bot->player->readyweapon])
			bot->forwardmove = FORWARDRUN;
		else
			bot->forwardmove = -FORWARDRUN;

		delete pos;
		return;
	}

	// Follow our buddy if we're tagging along
	if (bot->ally)
	{
		if (bot->t_strafe)
			if (!--bot->t_strafe)
			{
				bot->sidemove = -bot->sidemove;
				bot->t_strafe = 2*TICRATE;
			}

		fixed_t matedist;

		bot->SetAngle (R_PointToAngle2(mo->x, mo->y, bot->ally->x, bot->ally->y));
		matedist = P_AproxDistance(mo->x - bot->ally->x, mo->y - bot->ally->y);

		if (matedist > (FRIEND_DIST*2))
			bot->forwardmove = FORWARDRUN;
		else if (matedist > FRIEND_DIST)
			bot->forwardmove = FORWARDWALK;
		else if (matedist < FRIEND_DIST-(FRIEND_DIST/3)) //Got too close, so move away.
			bot->forwardmove = -FORWARDWALK;
		return;
	}

	if (bot->t_strafe)
		if (!--bot->t_strafe)
		{
			bot->sidemove = -bot->sidemove;
			bot->t_strafe = 2*TICRATE;
		}

	// Roam after an item
	B_Roam (bot);
}

void B_Attack (skullb_s *bot)
{
	// Still reacting to something or we don't have an enemy to fight
	if (bot->t_react || !bot->enemy)
		return;

	// No point in firing if we won't hit them
	if (!Check_LOS (bot->player->mo, bot->enemy, SHOOTFOV))
		return;

	switch (bot->player->readyweapon)
	{
	case wp_chaingun:
		// Double tap the chaingun for extreme accuracy
		if (bot->info.accuracy == bsk_supreme)
			if (bot->t_fire)
				return;
		break;
	default:
		break;
	}

	bot->player->cmd.ucmd.buttons |= BT_ATTACK;
}

// [BC] Ahh, the new and improved turning...
void B_Turn (skullb_s *bot)
{
	angle_t distance;
	ticcmd_t *cmd = &bot->player->cmd;

	distance = (int)((bot->GetAngle()>>ANGLETOFINESHIFT) - (bot->player->mo->angle>>ANGLETOFINESHIFT));
	cmd->ucmd.yaw = (short)distance*5;

	if (!bot->enemy)
		return;

	// [BC] Don't act crazy while trying to aim
	switch (bot->info.accuracy)
	{
	case bsk_verypoor:
	case bsk_poor:
	case bsk_low:
		if (cmd->ucmd.yaw > 1000)
			cmd->ucmd.yaw = 1000;
		if (cmd->ucmd.yaw < -1000)
			cmd->ucmd.yaw = -1000;
		break;
	case bsk_medium:
		if (cmd->ucmd.yaw > 2000)
			cmd->ucmd.yaw = 2000;
		if (cmd->ucmd.yaw < -2000)
			cmd->ucmd.yaw = -2000;
		break;
	case bsk_high:
		if (cmd->ucmd.yaw > 3000)
			cmd->ucmd.yaw = 3000;
		if (cmd->ucmd.yaw < -3000)
			cmd->ucmd.yaw = -3000;
		break;

	case bsk_excellent:
		if (cmd->ucmd.yaw > 4000)
			cmd->ucmd.yaw = 4000;
		if (cmd->ucmd.yaw < -4000)
			cmd->ucmd.yaw = -4000;
		break;
	case bsk_supreme:
		if (cmd->ucmd.yaw > 5000)
			cmd->ucmd.yaw = 5000;
		if (cmd->ucmd.yaw < -5000)
			cmd->ucmd.yaw = -5000;
		break;
	}
}

void B_Pitch (skullb_s *bot)
{
	AActor *mo = bot->player->mo;
	double aim;
	double diff;

	if (bot->enemy)
	{
		diff = (bot->enemy->z) - mo->z;
		aim = atan (diff / (double)P_AproxDistance (mo->x - bot->enemy->x, mo->y - bot->enemy->y));
		mo->pitch = -(int)(aim * ANGLE_180/M_PI);
	}
/*	else if (bot->dest)
	{
		diff = (bot->dest->z) - mo->z;
		aim = atan (diff / (double)P_AproxDistance (mo->x - bot->dest->x, mo->y - bot->dest->y));
		mo->pitch = -(int)(aim * ANGLE_180/M_PI);
	}*/
	else
		mo->pitch = 0;
}

//Checks if a sector is dangerous.
bool IsDangerous (sector_t *sec)
{
	int special = sec->special & ~SECRET_MASK;

	switch (special)
	{
		case dDamage_Hellslime:			
		case dDamage_Nukage:
		case dDamage_SuperHellslime:
		case dLight_Strobe_Hurt:
		case dDamage_End:
			return true;
		default:
			// [RH] Ignore unknown specials
			break;
	}
	switch (special & DAMAGE_MASK)
	{
		case 0x100: // 2/5 damage per 31 ticks
		case 0x200: // 5/10 damage per 31 ticks
		case 0x300: // 10/20 damage per 31 ticks
			return true;
		default:
			break;
	}

	if (sec->damage)
		return true;

	return false;
}
