/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
/* If you are missing that file, acquire a complete release at teeworlds.com.                */
#ifndef GAME_SERVER_ENTITIES_CHARACTER_H
#define GAME_SERVER_ENTITIES_CHARACTER_H

#include <game/race_state.h>
#include <game/server/entity.h>
#include <game/server/save.h>

class CPlayer;
class CGameTeams;
class CGameWorld;
class IAntibot;
struct CAntibotCharacterData;

enum
{
	FAKETUNE_FREEZE = 1 << 0,
	FAKETUNE_SOLO = 1 << 1,
	FAKETUNE_NOJUMP = 1 << 2,
	FAKETUNE_NOCOLL = 1 << 3,
	FAKETUNE_NOHOOK = 1 << 4,
	FAKETUNE_JETPACK = 1 << 5,
	FAKETUNE_NOHAMMER = 1 << 6,
};

class CCharacter : public CEntity
{
	MACRO_ALLOC_POOL_ID()

	// need to use core
	friend class CSaveTee;
	friend class CSaveHotReloadTee;

public:
	CCharacter(CGameWorld *pWorld, CNetObj_PlayerInput LastInput);
	~CCharacter() override;

	void Reset() override;
	void Destroy() override;
	void PreTick();
	void Tick() override;
	void TickDeferred() override;
	void TickPaused() override;
	void Snap(int SnappingClient) override;
	void SwapClients(int Client1, int Client2) override;

	void PostGlobalSnap();

	bool CanSnapCharacter(int SnappingClient);
	bool IsSnappingCharacterInView(int SnappingClientId);

	bool IsGrounded();

	void SetWeapon(int W);
	void SetJetpack(bool Active);
	void SetEndlessJump(bool Active);
	void SetJumps(int Jumps);
	void SetSolo(bool Solo);
	void SetSuper(bool Super);
	void SetInvincible(bool Invincible);
	void SetCollisionDisabled(bool CollisionDisabled);
	void SetHookHitDisabled(bool HookHitDisabled);
	void SetLiveFrozen(bool Active);
	void SetDeepFrozen(bool Active);
	void HandleWeaponSwitch();
	void DoWeaponSwitch();

	void HandleWeapons();
	void HandleNinja();
	void HandleJetpack();

	void OnPredictedInput(const CNetObj_PlayerInput *pNewInput);
	void OnDirectInput(const CNetObj_PlayerInput *pNewInput);
	void ReleaseHook();
	void ResetHook();
	void ResetInput();
	void FireWeapon();

	void Die(int Killer, int Weapon, bool SendKillMsg = true);
	bool TakeDamage(vec2 Force, int Dmg, int From, int Weapon);
	void SendDeathMessageIfNotInLockedTeam(int Killer, int Weapon, int ModeSpecial);
	void CancelSwapRequests();

	bool Spawn(class CPlayer *pPlayer, vec2 Pos);
	bool Remove();

	bool IncreaseHealth(int Amount);
	bool IncreaseArmor(int Amount);

	void GiveWeapon(int Weapon, bool Remove = false);
	void GiveNinja();
	void RemoveNinja();
	void SetEndlessHook(bool Enable);

	void SetEmote(int Emote, int Tick);
	int DetermineEyeEmote();

	bool Rescue();

	int NeededFaketuning() const { return m_NeededFaketuning; }
	bool IsAlive() const { return m_Alive; }
	bool IsPaused() const { return m_Paused; }
	CPlayer *GetPlayer() { return m_pPlayer; }
	const CPlayer *GetPlayer() const { return m_pPlayer; }
	CClientMask TeamMask();

	void SetPosition(const vec2 &Position);
	void Move(vec2 RelPos);

	void ResetVelocity();
	void SetVelocity(vec2 NewVelocity);
	void SetRawVelocity(vec2 NewVelocity);
	void AddVelocity(vec2 Addition);
	void ApplyMoveRestrictions();

private:
	// player controlling this character
	class CPlayer *m_pPlayer;

	bool m_Alive;
	bool m_Paused;
	int m_PausedTick;
	int m_NeededFaketuning;

	// weapon info
	int m_aHitObjects[MAX_CLIENTS];
	int m_NumObjectsHit;

	int m_LastWeapon;
	int m_QueuedWeapon;

	int m_ReloadTimer;
	int m_AttackTick;

	int m_MoveRestrictions;

	int m_DamageTaken;

	int m_EmoteType;
	int m_EmoteStop;

	// last tick that the player took any action ie some input
	int m_LastAction;

	// these are non-heldback inputs
	CNetObj_PlayerInput m_LatestPrevPrevInput;
	CNetObj_PlayerInput m_LatestPrevInput;
	CNetObj_PlayerInput m_LatestInput;

	// input
	CNetObj_PlayerInput m_PrevInput;
	CNetObj_PlayerInput m_Input;
	CNetObj_PlayerInput m_SavedInput;
	int m_NumInputs;

	int m_DamageTakenTick;

	int m_Health;
	int m_Armor;
	int m_TriggeredEvents7;

	// the player core for the physics
	CCharacterCore m_Core;
	CGameTeams *m_pTeams = nullptr;

	// info for dead reckoning
	int m_ReckoningTick; // tick that we are performing dead reckoning From
	CCharacterCore m_SendCore; // core that we should send
	CCharacterCore m_ReckoningCore; // the dead reckoning core

	// DDRace

	void SnapCharacter(int SnappingClient, int MapId);
	static bool IsSwitchActiveCb(unsigned char Number, void *pUser);
	void SetTimeCheckpoint(int TimeCheckpoint);
	void HandleTiles(int Index);
	float m_Time;
	int m_LastBroadcast;
	void DDRaceInit();
	void HandleSkippableTiles(int Index);
	void ForceSetRescue(int RescueMode);
	void DDRaceTick();
	void DDRacePostCoreTick();
	void HandleBroadcast();
	void HandleTuneLayer();
	void SendZoneMsgs();
	IAntibot *Antibot();

	bool m_SetSavePos[NUM_RESCUEMODES];
	CSaveTee m_RescueTee[NUM_RESCUEMODES];

	enum EUntranslatedMap
	{
		ID_HOOK,
		ID_WEAPON,
		NUM_IDS
	};
	std::optional<int> m_aUntranslatedId[EUntranslatedMap::NUM_IDS];

public:
	CGameTeams *Teams() { return m_pTeams; }
	void SetTeams(CGameTeams *pTeams);
	bool TrySetRescue(int RescueMode);

	void FillAntibot(CAntibotCharacterData *pData);
	void Pause(bool Pause);
	bool Freeze(int Seconds);
	bool Freeze();
	bool Unfreeze();
	void GiveAllWeapons();
	void ResetPickups();
	void ResetJumps();
	ERaceState m_DDRaceState;
	int Team();
	bool CanCollide(int ClientId) override;
	bool SameTeam(int ClientId);
	void StopRecording();
	bool m_NinjaJetpack;
	int m_TeamBeforeSuper;
	int m_FreezeTime;
	bool m_FrozenLastTick;
	int m_TuneZone;
	int m_TuneZoneOld;
	int m_PainSoundTimer;
	int m_LastMove;
	int m_StartTime;
	vec2 m_PrevPos;
	int m_TeleCheckpoint;

	int m_TimeCpBroadcastEndTick;
	int m_LastTimeCp;
	int m_LastTimeCpBroadcasted;
	float m_aCurrentTimeCp[MAX_CHECKPOINTS];

	int m_TileIndex;
	int m_TileFIndex;

	int64_t m_LastStartWarning;
	int64_t m_LastRescue;
	bool m_LastRefillJumps;
	bool m_LastPenalty;
	bool m_LastBonus;
	vec2 m_TeleGunPos;
	bool m_TeleGunTeleport;
	bool m_IsBlueTeleGunTeleport;
	int m_StrongWeakId;

	int m_SpawnTick;
	int m_WeaponChangeTick;

	// Setters/Getters because i don't want to modify vanilla vars access modifiers
	int GetLastWeapon() const { return m_LastWeapon; }
	void SetLastWeapon(int LastWeap) { m_LastWeapon = LastWeap; }
	int GetActiveWeapon() const { return m_Core.m_ActiveWeapon; }
	void SetActiveWeapon(int ActiveWeap) { m_Core.m_ActiveWeapon = ActiveWeap; }
	void SetLastAction(int LastAction) { m_LastAction = LastAction; }
	int GetArmor() const { return m_Armor; }
	void SetArmor(int Armor) { m_Armor = Armor; }
	CCharacterCore GetCore() { return m_Core; }
	void SetCore(const CCharacterCore &Core) { m_Core = Core; }
	const CCharacterCore *Core() const { return &m_Core; }
	bool GetWeaponGot(int Type) { return m_Core.m_aWeapons[Type].m_Got; }
	void SetWeaponGot(int Type, bool Value) { m_Core.m_aWeapons[Type].m_Got = Value; }
	int GetWeaponAmmo(int Type) { return m_Core.m_aWeapons[Type].m_Ammo; }
	void SetWeaponAmmo(int Type, int Value) { m_Core.m_aWeapons[Type].m_Ammo = Value; }
	void SetNinjaActivationDir(vec2 ActivationDir) { m_Core.m_Ninja.m_ActivationDir = ActivationDir; }
	void SetNinjaActivationTick(int ActivationTick) { m_Core.m_Ninja.m_ActivationTick = ActivationTick; }
	void SetNinjaCurrentMoveTime(int CurrentMoveTime) { m_Core.m_Ninja.m_CurrentMoveTime = CurrentMoveTime; }

	int GetLastAction() const { return m_LastAction; }
	bool IsIdle() const { return !m_SavedInput.m_Direction && !m_SavedInput.m_Hook && !m_SavedInput.m_Jump && !(m_SavedInput.m_Fire & 1); }

	bool HasTelegunGun() const { return m_Core.m_HasTelegunGun; }
	bool HasTelegunGrenade() const { return m_Core.m_HasTelegunGrenade; }
	bool HasTelegunLaser() const { return m_Core.m_HasTelegunLaser; }

	bool HammerHitDisabled() const { return m_Core.m_HammerHitDisabled; }
	bool ShotgunHitDisabled() const { return m_Core.m_ShotgunHitDisabled; }
	bool LaserHitDisabled() const { return m_Core.m_LaserHitDisabled; }
	bool GrenadeHitDisabled() const { return m_Core.m_GrenadeHitDisabled; }

	void SetHammerHitDisabled(bool HammerHitDisabled) { m_Core.m_HammerHitDisabled = HammerHitDisabled; }
	void SetShotgunHitDisabled(bool ShotgunHitDisabled) { m_Core.m_ShotgunHitDisabled = ShotgunHitDisabled; }
	void SetGrenadeHitDisabled(bool GrenadeHitDisabled) { m_Core.m_GrenadeHitDisabled = GrenadeHitDisabled; }
	void SetLaserHitDisabled(bool LaserHitDisabled) { m_Core.m_LaserHitDisabled = LaserHitDisabled; }

	bool IsSuper() const { return m_Core.m_Super; }

	CSaveTee &GetLastRescueTeeRef(int Mode = RESCUEMODE_AUTO) { return m_RescueTee[Mode]; }
	CTuningParams *GetTuning(int Zone) { return &TuningList()[Zone]; }
};

#endif
