/*
*
*   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
*
*   In addition, as a special exception, the author gives permission to
*   link the code of this program with the Half-Life Game Engine ("HL
*   Engine") and Modified Game Libraries ("MODs") developed by Valve,
*   L.L.C ("Valve").  You must obey the GNU General Public License in all
*   respects for all of the code used other than the HL Engine and MODs
*   from Valve.  If you modify this file, you may extend this exception
*   to your version of the file, but you are not obligated to do so.  If
*   you do not wish to do so, delete this exception statement from your
*   version.
*
*/

#include "precompiled.h"

// Returns true if the radio message is an order to do something
// NOTE: "Report in" is not considered a "command" because it doesnt ask the bot to go somewhere, or change its mind
bool CCSBot::IsRadioCommand(GameEventType event) const
{
	if (event == EVENT_RADIO_AFFIRMATIVE
		|| event == EVENT_RADIO_NEGATIVE
		|| event == EVENT_RADIO_ENEMY_SPOTTED
		|| event == EVENT_RADIO_SECTOR_CLEAR
		|| event == EVENT_RADIO_REPORTING_IN
		|| event == EVENT_RADIO_REPORT_IN_TEAM
		|| event == EVENT_RADIO_ENEMY_DOWN)
		return false;

	return true;
}

// Respond to radio commands from HUMAN players
void CCSBot::RespondToRadioCommands()
{
	// bots use the chatter system to respond to each other
	if (m_radioSubject.IsValid() && m_radioSubject->IsPlayer())
	{
		if (m_radioSubject->IsBot())
		{
			m_lastRadioCommand = EVENT_INVALID;
			return;
		}
	}

	if (m_lastRadioCommand == EVENT_INVALID)
		return;

	// a human player has issued a radio command
	GetChatter()->ResetRadioSilenceDuration();

	// if we are doing something important, ignore the radio
	// unless it is a "report in" request - we can do that while we continue to do other things
	// TODO: Create "uninterruptable" flag
	if (m_lastRadioCommand != EVENT_RADIO_REPORT_IN_TEAM)
	{
		if (IsBusy())
		{
			// consume command
			m_lastRadioCommand = EVENT_INVALID;
			return;
		}
	}

	// wait for reaction time before responding
	// delay needs to be long enough for the radio message we're responding to to finish
	float respondTime = 1.0f + 2.0f * GetProfile()->GetReactionTime();
	if (IsRogue())
		respondTime += 2.0f;

	if (gpGlobals->time - m_lastRadioRecievedTimestamp < respondTime)
		return;

	// rogues won't follow commands, unless already following the player
	if (!IsFollowing() && IsRogue())
	{
		if (IsRadioCommand(m_lastRadioCommand))
		{
			GetChatter()->Negative();
		}

		// consume command
		m_lastRadioCommand = EVENT_INVALID;
		return;
	}

	if (!m_radioSubject)
		return;

	// respond to command
	bool canDo = false;
	const float inhibitAutoFollowDuration = 60.0f;
	switch (m_lastRadioCommand)
	{
	case EVENT_RADIO_REPORT_IN_TEAM:
	{
		GetChatter()->ReportingIn();
		break;
	}
	case EVENT_RADIO_FOLLOW_ME:
	case EVENT_RADIO_COVER_ME:
	case EVENT_RADIO_STICK_TOGETHER_TEAM:
	case EVENT_RADIO_REGROUP_TEAM:
	{
		if (!IsFollowing())
		{
			Follow(m_radioSubject);
			m_radioSubject->AllowAutoFollow();
			canDo = true;
		}
		break;
	}
	case EVENT_RADIO_ENEMY_SPOTTED:
	case EVENT_RADIO_NEED_BACKUP:
	case EVENT_RADIO_TAKING_FIRE:
	{
		if (!IsFollowing())
		{
			Follow(m_radioSubject);
			GetChatter()->Say("OnMyWay");
			m_radioSubject->AllowAutoFollow();
			canDo = false;
		}
		break;
	}
	case EVENT_RADIO_TEAM_FALL_BACK:
	{
		if (TryToRetreat())
			canDo = true;
		break;
	}
	case EVENT_RADIO_HOLD_THIS_POSITION:
	{
		// find the leader's area
		SetTask(HOLD_POSITION);
		StopFollowing();
		m_radioSubject->InhibitAutoFollow(inhibitAutoFollowDuration);
		Hide(TheNavAreaGrid.GetNearestNavArea(&m_radioPosition));
		canDo = true;
		break;
	}
	case EVENT_RADIO_GO_GO_GO:
	case EVENT_RADIO_STORM_THE_FRONT:
	{
		StopFollowing();
		Hunt();
		canDo = true;
		m_radioSubject->InhibitAutoFollow(inhibitAutoFollowDuration);
		break;
	}
	case EVENT_RADIO_GET_OUT_OF_THERE:
	{
		if (TheCSBots()->IsBombPlanted())
		{
			EscapeFromBomb();
			m_radioSubject->InhibitAutoFollow(inhibitAutoFollowDuration);
			canDo = true;
		}
		break;
	}
	case EVENT_RADIO_SECTOR_CLEAR:
	{
		// if this is a defusal scenario, and the bomb is planted,
		// and a human player cleared a bombsite, check it off our list too
		if (TheCSBots()->GetScenario() == CCSBotManager::SCENARIO_DEFUSE_BOMB)
		{
			if (m_iTeam == CT && TheCSBots()->IsBombPlanted())
			{
				const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone(m_radioSubject);
				if (zone)
				{
					GetGameState()->ClearBombsite(zone->m_index);

					// if we are huting for the planted bomb, re-select bombsite
					if (GetTask() == FIND_TICKING_BOMB)
						Idle();

					canDo = true;
				}
			}
		}
		break;
	}
	default:
		// ignore all other radio commands for now
		return;
	}

	if (canDo)
	{
		// affirmative
		GetChatter()->Affirmative();

		// if we agreed to follow a new command, put away our grenade
		if (IsRadioCommand(m_lastRadioCommand) && IsUsingGrenade())
		{
			EquipBestWeapon();
		}
	}

	// consume command
	m_lastRadioCommand = EVENT_INVALID;
}

// Send voice chatter.  Also sends the entindex.
void CCSBot::StartVoiceFeedback(float duration)
{
	m_voiceFeedbackStartTimestamp = gpGlobals->time;
	m_voiceFeedbackEndTimestamp = duration + gpGlobals->time;

	CBasePlayer *pPlayer = nullptr;
	while ((pPlayer = GetNextRadioRecipient(pPlayer)))
	{
		MESSAGE_BEGIN(MSG_ONE, gmsgBotVoice, nullptr, pPlayer->pev);
			WRITE_BYTE(1);				// active is talking
			WRITE_BYTE(entindex());		// client index speaking
		MESSAGE_END();
	}
}

void CCSBot::EndVoiceFeedback(bool force)
{
	if (!force && !m_voiceFeedbackEndTimestamp)
		return;

	m_voiceFeedbackEndTimestamp = 0;

	MESSAGE_BEGIN(MSG_ALL, gmsgBotVoice);
		WRITE_BYTE(0);
		WRITE_BYTE(ENTINDEX(edict()));
	MESSAGE_END();
}

// Decide if we should move to help the player, return true if we will
bool CCSBot::RespondToHelpRequest(CBasePlayer *them, Place place, float maxRange)
{
	if (IsRogue())
		return false;

	// if we're busy, ignore
	if (IsBusy())
		return false;

	// if we are too far away, ignore
	if (maxRange > 0.0f)
	{
		// compute actual travel distance
		PathCost pc(this);
		real_t travelDistance = NavAreaTravelDistance(m_lastKnownArea, TheNavAreaGrid.GetNearestNavArea(&them->pev->origin), pc);
		if (travelDistance < 0.0f)
			return false;

		if (travelDistance > maxRange)
			return false;
	}

	if (place == UNDEFINED_PLACE)
	{
		// if we have no "place" identifier, go directly to them

		// if we are already there, ignore
		float rangeSq = (them->pev->origin - pev->origin).LengthSquared();
		const float close = 750.0f * 750.0f;

		if (rangeSq < close)
			return true;

		MoveTo(&them->pev->origin, FASTEST_ROUTE);
	}
	else
	{
		// if we are already there, ignore
		if (GetPlace() == place)
			return true;

		// go to where help is needed
		const Vector *pos = GetRandomSpotAtPlace(place);
		if (pos)
		{
			MoveTo(pos, FASTEST_ROUTE);
		}
		else
		{
			MoveTo(&them->pev->origin, FASTEST_ROUTE);
		}
	}

	// acknowledge
	GetChatter()->Say("OnMyWay");

	return true;
}

// Send a radio message
void CCSBot::SendRadioMessage(GameEventType event)
{
	// make sure this is a radio event
	if (event <= EVENT_START_RADIO_1 || event >= EVENT_END_RADIO)
	{
		return;
	}

	PrintIfWatched("%3.1f: SendRadioMessage( %s )\n", gpGlobals->time, GameEventName[event]);

	// note the time the message was sent
	TheCSBots()->SetRadioMessageTimestamp(event, m_iTeam);

	m_lastRadioSentTimestamp = gpGlobals->time;

	char slot[2];
	slot[1] = '\0';

	if (event > EVENT_START_RADIO_1 && event < EVENT_START_RADIO_2)
	{
		slot[0] = '0' + event - EVENT_START_RADIO_1;
		ClientCommand("radio1");
		//Radio1(this, event - EVENT_START_RADIO_1);
	}
	else if (event > EVENT_START_RADIO_2 && event < EVENT_START_RADIO_3)
	{
		slot[0] = '0' + event - EVENT_START_RADIO_2;
		ClientCommand("radio2");
		//Radio2(this, event - EVENT_START_RADIO_2);
	}
	else
	{
		slot[0] = '0' + event - EVENT_START_RADIO_3;
		ClientCommand("radio3");
		//Radio3(this, event - EVENT_START_RADIO_3);
	}

	ClientCommand("menuselect", slot);
	ClientCommand("menuselect", "10");
}