/*
**
**
**---------------------------------------------------------------------------
** Copyright 1999 Martin Colberg
** Copyright 1999-2016 Randy Heit
** Copyright 2005-2016 Christoph Oelckers
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
**    derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
/********************************
* B_Think.c                     *
* Description:                  *
* Movement/Roaming code for     *
* the bot's					    *
*********************************/

#include "doomdef.h"
#include "doomstat.h"
#include "p_local.h"
#include "b_bot.h"
#include "g_game.h"
#include "p_lnspec.h"
#include "a_keys.h"
#include "d_event.h"
#include "p_enemy.h"
#include "d_player.h"
#include "p_spec.h"
#include "p_checkposition.h"
#include "actorinlines.h"

static FRandom pr_botopendoor ("BotOpenDoor");
static FRandom pr_bottrywalk ("BotTryWalk");
static FRandom pr_botnewchasedir ("BotNewChaseDir");

// borrow some tables from p_enemy.cpp
extern dirtype_t opposite[9];
extern dirtype_t diags[4];

//Called while the bot moves after its dest mobj
//which can be a weapon/enemy/item whatever.
void DBot::Roam (ticcmd_t *cmd)
{

	if (Reachable(dest))
	{ // Straight towards it.
		Angle = player->mo->AngleTo(dest);
	}
	else if (player->mo->movedir < 8) // turn towards movement direction if not there yet
	{
		// no point doing this with floating point angles...
		unsigned angle = Angle.BAMs() & (unsigned)(7 << 29);
		int delta = angle - (player->mo->movedir << 29);

		if (delta > 0)
			Angle -= DAngle::fromDeg(45);
		else if (delta < 0)
			Angle += DAngle::fromDeg(45);
	}

	// chase towards destination.
	if (--player->mo->movecount < 0 || !Move (cmd))
	{
		NewChaseDir (cmd);
	}
}

bool DBot::Move (ticcmd_t *cmd)
{
	double tryx, tryy;
	bool try_ok;
	int good;

	if (player->mo->movedir >= DI_NODIR)
	{
		player->mo->movedir = DI_NODIR;	// make sure it's valid.
		return false;
	}

	tryx = player->mo->X() + 8*xspeed[player->mo->movedir];
	tryy = player->mo->Y() + 8*yspeed[player->mo->movedir];

	try_ok = Level->BotInfo.CleanAhead (player->mo, tryx, tryy, cmd);

	if (!try_ok) //Anything blocking that could be opened etc..
	{
		if (!spechit.Size ())
			return false;

		player->mo->movedir = DI_NODIR;

		good = 0;
		spechit_t spechit1;
		line_t *ld;

		while (spechit.Pop (spechit1))
		{
			ld = spechit1.line;
			bool tryit = true;

			if (ld->special == Door_LockedRaise && !P_CheckKeys (player->mo, ld->args[3], false))
				tryit = false;
			else if (ld->special == Generic_Door && !P_CheckKeys (player->mo, ld->args[4], false))
				tryit = false;

			if (tryit &&
				(P_TestActivateLine (ld, player->mo, 0, SPAC_Use) ||
				 P_TestActivateLine (ld, player->mo, 0, SPAC_Push)))
			{
				good |= ld == player->mo->BlockingLine ? 1 : 2;
			}
		}
		if (good && ((pr_botopendoor() >= 203) ^ (good & 1)))
		{
			cmd->ucmd.buttons |= BT_USE;
			cmd->ucmd.forwardmove = FORWARDRUN;
			return true;
		}
		else
			return false;
	}
	else //Move forward.
		cmd->ucmd.forwardmove = FORWARDRUN;

	return true;
}

bool DBot::TryWalk (ticcmd_t *cmd)
{
    if (!Move (cmd))
        return false;

    player->mo->movecount = pr_bottrywalk() & 60;
    return true;
}

void DBot::NewChaseDir (ticcmd_t *cmd)
{
    dirtype_t   d[3];

    int         tdir;
    dirtype_t   olddir;

    dirtype_t   turnaround;

    if (!dest)
	{
#ifndef BOT_RELEASE_COMPILE
        Printf ("Bot tried move without destination\n");
#endif
		return;
	}

    olddir = (dirtype_t)player->mo->movedir;
    turnaround = opposite[olddir];

	DVector2 delta = player->mo->Vec2To(dest);

    if (delta.X > 10)
        d[1] = DI_EAST;
    else if (delta.X < -10)
        d[1] = DI_WEST;
    else
        d[1] = DI_NODIR;

    if (delta.Y < -10)
        d[2] = DI_SOUTH;
    else if (delta.Y > 10)
        d[2] = DI_NORTH;
    else
        d[2] = DI_NODIR;

    // try direct route
    if (d[1] != DI_NODIR && d[2] != DI_NODIR)
    {
		player->mo->movedir = diags[((delta.Y < 0) << 1) + (delta.X > 0)];
        if (player->mo->movedir != turnaround && TryWalk(cmd))
            return;
    }

    // try other directions
	if (pr_botnewchasedir() > 200
		|| fabs(delta.Y) > fabs(delta.X))
	{
		tdir = d[1];
		d[1] = d[2];
		d[2] = (dirtype_t)tdir;
	}

    if (d[1]==turnaround)
        d[1]=DI_NODIR;
    if (d[2]==turnaround)
        d[2]=DI_NODIR;

    if (d[1]!=DI_NODIR)
    {
        player->mo->movedir = d[1];
        if (TryWalk (cmd))
            return;
    }

    if (d[2]!=DI_NODIR)
    {
        player->mo->movedir = d[2];

        if (TryWalk(cmd))
            return;
    }

    // there is no direct path to the player,
    // so pick another direction.
    if (olddir!=DI_NODIR)
    {
        player->mo->movedir = olddir;

        if (TryWalk(cmd))
            return;
    }

    // randomly determine direction of search
    if (pr_botnewchasedir()&1)
    {
        for ( tdir=DI_EAST;
              tdir<=DI_SOUTHEAST;
              tdir++ )
        {
            if (tdir!=turnaround)
            {
                player->mo->movedir = tdir;

                if (TryWalk(cmd))
                    return;
            }
        }
    }
    else
    {
        for ( tdir=DI_SOUTHEAST;
              tdir != (DI_EAST-1);
              tdir-- )
        {
            if (tdir!=turnaround)
            {
                player->mo->movedir = tdir;

                if (TryWalk(cmd))
                    return;
            }
        }
    }

    if (turnaround !=  DI_NODIR)
    {
        player->mo->movedir = turnaround;
        if (TryWalk(cmd))
            return;
    }

    player->mo->movedir = DI_NODIR;  // can not move
}


//
// B_CleanAhead
// Check if a place is ok to move towards.
// This is also a traverse function for
// bots pre-rocket fire (preventing suicide)
//
bool FCajunMaster::CleanAhead (AActor *thing, double x, double y, ticcmd_t *cmd)
{
	FCheckPosition tm;

    if (!SafeCheckPosition (thing, x, y, tm))
        return false;           // solid wall or thing

    if (!(thing->flags & MF_NOCLIP) )
    {
        if (tm.ceilingz - tm.floorz < thing->Height)
            return false;       // doesn't fit

		double maxmove = MAXMOVEHEIGHT;
		if (!(thing->flags&MF_MISSILE))
		{
			if(tm.floorz > (thing->Sector->floorplane.ZatPoint(x, y)+maxmove)) //Too high wall
				return false;

			//Jumpable
			if(tm.floorz > (thing->Sector->floorplane.ZatPoint(x, y)+thing->MaxStepHeight))
				cmd->ucmd.buttons |= BT_JUMP;


	        if ( !(thing->flags & MF_TELEPORT) &&
	             tm.ceilingz < thing->Top())
	            return false;       // mobj must lower itself to fit

	        // jump out of water
//	        if((thing->eflags & (MF_UNDERWATER|MF_TOUCHWATER))==(MF_UNDERWATER|MF_TOUCHWATER))
//	            maxstep=37;

	        if ( !(thing->flags & MF_TELEPORT) &&
	             (tm.floorz - thing->Z() > thing->MaxStepHeight) )
	            return false;       // too big a step up


			if ( !(thing->flags&(MF_DROPOFF|MF_FLOAT))
			&& tm.floorz - tm.dropoffz > thing->MaxDropOffHeight )
				return false;       // don't stand over a dropoff

		}
    }
    return true;
}

#define OKAYRANGE (5) //counts *2, when angle is in range, turning is not executed.
#define MAXTURN (15) //Max degrees turned in one tic. Lower is smother but may cause the bot not getting where it should = crash
#define TURNSENS 3 //Higher is smoother but slower turn.

void DBot::TurnToAng ()
{
    double maxturn = MAXTURN;

	if (player->ReadyWeapon != NULL)
	{
		if (GetBotInfo(player->ReadyWeapon).flags & BIF_BOT_EXPLOSIVE)
		{
			if (t_roam && !missile)
			{ //Keep angle that where when shot where decided.
				return;
			}
		}


		if(enemy)
			if(!dest) //happens when running after item in combat situations, or normal, prevents weak turns
				if(GetBotInfo(player->ReadyWeapon).projectileType == NULL && GetBotInfo(player->ReadyWeapon).MoveCombatDist > 0)
					if(Check_LOS(enemy, DAngle::fromDeg(SHOOTFOV+5)))
						maxturn = 3;
	}

	DAngle distance = deltaangle(player->mo->Angles.Yaw, Angle);

	if (fabs (distance) < DAngle::fromDeg(OKAYRANGE) && !enemy)
		return;

	distance /= TURNSENS;
	if (fabs (distance) > DAngle::fromDeg(maxturn))
		distance = DAngle::fromDeg(distance < nullAngle ? -maxturn : maxturn);

	player->mo->Angles.Yaw += distance;
}

void DBot::Pitch (AActor *target)
{
	double aim;
	double diff;

	diff = target->Z() - player->mo->Z();
	aim = g_atan(diff / player->mo->Distance2D(target));
	player->mo->Angles.Pitch = DAngle::fromRad(aim);
}

//Checks if a sector is dangerous.
bool FCajunMaster::IsDangerous (sector_t *sec)
{
	return sec->damageamount > 0;
}
