/* (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_CLIENT_PREDICTION_ENTITIES_CHARACTER_H
#define GAME_CLIENT_PREDICTION_ENTITIES_CHARACTER_H

#include <generated/protocol.h>

#include <game/client/prediction/entity.h>
#include <game/gamecore.h>
#include <game/race_state.h>

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
{
	friend class CGameWorld;

public:
	~CCharacter() override;

	void PreTick() override;
	void Tick() override;
	void TickDeferred() override;

	bool IsGrounded();

	void SetWeapon(int Weapon);
	void SetSolo(bool Solo);
	void SetSuper(bool Super);
	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();

	bool TakeDamage(vec2 Force, int Dmg, int From, int Weapon);

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

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

	bool m_IsLocal;

	CTeamsCore *TeamsCore();
	bool Freeze(int Seconds);
	bool Freeze();
	bool Unfreeze();
	void GiveAllWeapons();
	int Team();
	bool CanCollide(int ClientId) override;
	bool SameTeam(int ClientId);
	bool m_NinjaJetpack;
	int m_FreezeTime;
	bool m_FrozenLastTick;
	vec2 m_PrevPos;
	vec2 m_PrevPrevPos;
	int m_TeleCheckpoint;

	int m_TileIndex;
	int m_TileFIndex;

	bool m_LastRefillJumps;

	// 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 ActiveWeapon);
	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 GetCid() { return m_Id; }
	void SetInput(const CNetObj_PlayerInput *pNewInput)
	{
		m_LatestInput = m_Input = *pNewInput;
		// it is not allowed to aim in the center
		if(m_Input.m_TargetX == 0 && m_Input.m_TargetY == 0)
		{
			m_Input.m_TargetY = m_LatestInput.m_TargetY = -1;
		}
	}
	int GetJumped() const { return m_Core.m_Jumped; }
	int GetAttackTick() const { return m_AttackTick; }
	int GetStrongWeakId() const { return m_StrongWeakId; }

	CCharacter(CGameWorld *pGameWorld, int Id, CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtended = nullptr);
	void Read(CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtended, bool IsLocal);
	void SetCoreWorld(CGameWorld *pGameWorld);

	int m_LastSnapWeapon;
	bool m_KeepHooked;
	int m_GameTeam;
	bool m_CanMoveInFreeze;

	bool Match(CCharacter *pChar) const;
	void ResetPrediction();
	void SetTuneZone(int Zone);
	int GetOverriddenTuneZone() const;
	int GetPureTuneZone() const;

	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; }

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

	// antiping
	void AntiPingInterference(int ClientId, bool DisallowReset = false, bool HasToBeUnfrozen = false);
	bool IsInterfering() const { return m_Interfering; }

private:
	// 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_PainSoundTimer;

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

	// input
	CNetObj_PlayerInput m_PrevInput;
	CNetObj_PlayerInput m_Input;
	CNetObj_PlayerInput m_SavedInput;

	int m_NumInputs;

	// tune
	int m_TuneZone;
	int m_TuneZoneOverride;

	// the player core for the physics
	CCharacterCore m_Core;

	// DDRace

	static bool IsSwitchActiveCb(unsigned char Number, void *pUser);
	void HandleTiles(int Index);
	void HandleSkippableTiles(int Index);
	void DDRaceTick();
	void DDRacePostCoreTick();
	void HandleTuneLayer();

	int m_StrongWeakId;

	int m_LastWeaponSwitchTick;
	int m_LastTuneZoneTick;

	bool m_Interfering;
};

#endif
