/*
===========================================================================
Copyright (C) 2023 the OpenMoHAA team

This file is part of OpenMoHAA source code.

OpenMoHAA source code 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.

OpenMoHAA source code 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 OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

// entity.h: Base class for all enities that are controlled by Sin.  If you have any
// object that should be called on a periodic basis and it is not an entity,
// then you have to have an dummy entity that calls it.
//
// An entity in Sin is any object that is not part of the world->  Any non-world
// object that is visible in Sin is an entity, although it is not required that
// all entities be visible to the player.  Some objects are basically just virtual
// constructs that act as an instigator of certain actions, for example, some
// triggers are invisible and cannot be touched, but when activated by other
// objects can cause things to happen.
//
// All entities are capable of receiving messages from Sin or from other entities.
// Messages received by an entity may be ignored, passed on to their superclass,
// or acted upon by the entity itself.  The programmer must decide on the proper
// action for the entity to take to any message.  There will be many messages
// that are completely irrelevant to an entity and should be ignored.  Some messages
// may require certain states to exist and if they are received by an entity when
// it these states don't exist may indicate a logic error on the part of the
// programmer or map designer and should be reported as warnings (if the problem is
// not severe enough for the game to be halted) or as errors (if the problem should
// not be ignored at any cost).
//

#pragma once

#include "g_local.h"
#include "class.h"
#include "vector.h"
#include "script.h"
#include "listener.h"
#include "simpleentity.h"

// modification flags
#define FLAG_IGNORE 0
#define FLAG_CLEAR  1
#define FLAG_ADD    2

typedef enum {
    DAMAGE_NO,
    DAMAGE_YES, // will take damage if hit
    DAMAGE_AIM  // auto targeting recognizes this
} damage_t;

typedef enum {
    legs,
    torso,
} bodypart_t;

enum eAIEvent {
    AI_EVENT_NONE,
    AI_EVENT_WEAPON_FIRE,
    AI_EVENT_WEAPON_IMPACT,
    AI_EVENT_EXPLOSION,
    AI_EVENT_AMERICAN_VOICE,
    AI_EVENT_GERMAN_VOICE,
    AI_EVENT_AMERICAN_URGENT,
    AI_EVENT_GERMAN_URGENT,
    AI_EVENT_MISC,
    AI_EVENT_MISC_LOUD,
    AI_EVENT_FOOTSTEP,
    AI_EVENT_GRENADE,
    AI_EVENT_BADPLACE, // Added in 2.0
    AI_EVENT_MAX
};

//deadflag
#define DEAD_NO          0
#define DEAD_DYING       1
#define DEAD_DEAD        2
#define DEAD_RESPAWNABLE 3

// Generic entity events
extern Event EV_SoundDone;
extern Event EV_Classname;
extern Event EV_Activate;
extern Event EV_ForceActivate;
extern Event EV_Use;
extern Event EV_FadeNoRemove;
extern Event EV_FadeOut;
extern Event EV_FadeIn;
extern Event EV_Fade;
extern Event EV_Killed;
extern Event EV_GotKill;
extern Event EV_Pain;
extern Event EV_Damage;
extern Event EV_Stun;
extern Event EV_Gib;
extern Event EV_Kill;
extern Event EV_DeathSinkStart;
extern Event EV_Entity_AddImmunity;
extern Event EV_Entity_RemoveImmunity;

// Physics events
extern Event EV_MoveDone;
extern Event EV_Touch;
extern Event EV_Blocked;
extern Event EV_Attach;
extern Event EV_AttachModel;
extern Event EV_RemoveAttachedModel;
extern Event EV_Detach;
extern Event EV_DetachAllChildren;
extern Event EV_UseBoundingBox;
extern Event EV_IsTouching;

// Animation events
extern Event EV_NewAnim;
extern Event EV_LastFrame;
extern Event EV_TakeDamage;
extern Event EV_NoDamage;

// script stuff
extern Event EV_Model;
extern Event EV_GetModel;
extern Event EV_Hide;
extern Event EV_Show;
extern Event EV_BecomeSolid;
extern Event EV_BecomeNonSolid;
extern Event EV_Sound;
extern Event EV_StopSound;
extern Event EV_Bind;
extern Event EV_Unbind;
extern Event EV_Glue;
extern Event EV_Unglue;
extern Event EV_JoinTeam;
extern Event EV_QuitTeam;
extern Event EV_SetHealth;
extern Event EV_SetHealth2;
extern Event EV_Entity_GetHealth;
extern Event EV_SetSize;
extern Event EV_SetAlpha;
extern Event EV_SetTargetName;
extern Event EV_SetTarget;
extern Event EV_SetKillTarget;
extern Event EV_StartAnimating;
extern Event EV_SurfaceModelEvent;
extern Event EV_Stop;
extern Event EV_StopLoopSound;
extern Event EV_SetControllerAngles;
extern Event EV_CanSee;

// dir is 1
// power is 2
// minsize is 3
// maxsize is 4
// percentage is 5
// thickness 6
// entity is 7
// origin 8

// AI sound events
extern Event EV_BroadcastAIEvent;
extern Event EV_Hurt;
extern Event EV_Heal;

// Define ScriptMaster
class ScriptMaster;

//
// Spawn args
//
// "spawnflags"
// "alpha" default 1.0
// "model"
// "origin"
// "targetname"
// "target"
//

// was 8, increased to 16
#define MAX_MODEL_CHILDREN 16
#define MAX_GLUE_CHILDREN  8

#define GL_USEANGLES       1

class Entity;

typedef SafePtr<Entity> EntityPtr;

class Entity : public SimpleEntity
{
public:
    CLASS_PROTOTYPE(Entity);

    // spawning variables
    int        entnum;
    int        radnum;
    gentity_t *edict;
    gclient_t *client;
    int        spawnflags;

    // standard variables
    str model;

    // physics variables
    Vector     mins;
    Vector     maxs;
    Vector     absmin;
    Vector     absmax;
    Vector     velocity;
    Vector     accel;
    Vector     avelocity;
    Vector     aaccel;
    Vector     size;
    int        movetype;
    int        mass;
    float      gravity;
    float      orientation[3][3];
    gentity_t *groundentity;
    cplane_t   groundplane;
    int        groundcontents;

    // Model Binding variables
    int numchildren;
    int children[MAX_MODEL_CHILDREN];

    // Light variables
    float lightRadius;

    // Team variables
    str           moveteam;
    class Entity *teamchain;
    class Entity *teammaster;

    // Binding variables
    class Entity *bindmaster;
    qboolean      bind_use_my_angles;
    Vector        localorigin;
    Vector        localangles;

    // targeting variables
    str killtarget;

    // Character state
    float health;
    float max_health;
    int   deadflag;
    int   flags;

    // underwater variables
    int watertype;
    int waterlevel;

    // Pain and damage variables
    damage_t  takedamage;
    EntityPtr enemy;
    float     pain_finished;
    float     damage_debounce_time;
    int       damage_type;

    // Glue variables
    int           m_iNumGlues;
    EntityPtr     m_pGlues[MAX_GLUE_CHILDREN];
    int           m_pGluesFlags[MAX_GLUE_CHILDREN];
    class Entity *m_pGlueMaster;
    bool          m_bGlueAngles;
    bool          m_bGlueDuckable;
    qboolean      detach_at_death;

    // Path variables
    float              stealthMovementScale; // how much it will notify AIs
    class pathway_ref *m_BlockedPaths;
    int                m_iNumBlockedPaths;

    // immune list
    Container<int> immunities;

#ifdef OPM_FEATURES
    //
    // Added in OPM
    //====
    // miscellaneous
    bool     m_bHintRequiresLookAt;
    str      m_HintString;
    //====
#endif

    Entity();
    virtual ~Entity();

    void EventGetNormalHealth(Event *ev);
    void EventNormalDamage(Event *ev);
    void ClassnameEvent(Event *ev);
    void SpawnFlagsEvent(Event *ev);
    void EventRevive(Event *ev);

    virtual void ShowInfo(float fDot, float fDist);

    qboolean DistanceTo(Vector pos) const;
    qboolean DistanceTo(Entity *ent) const;
    qboolean WithinDistance(Vector pos, float dist) const;
    qboolean WithinDistance(Entity *ent, float dist) const;

    Vector   GetControllerAngles(int num);
    void     SetControllerAngles(int num, vec3_t angles);
    void     SetControllerAngles(Event *ev);
    void     GetControllerAngles(Event *ev);
    void     SetControllerTag(int num, int tag_num);
    int      GetControllerTag(int num) const;
    qboolean GetTagPositionAndOrientation(str tagname, orientation_t *new_or);
    void     GetTagPositionAndOrientation(int tagnum, orientation_t *new_or);
    void     GetTagPosition(Event *ev);
    void     GetTagAngles(Event *ev);

    void EventTrace(Event *ev);
    void EventSightTrace(Event *ev);
    void EventInPVS(Event *ev);
    void IsTouching(Event *ev);
    void IsInside(Event *ev);
    void CanSeeInternal(Event *ev, bool bNoEnts); // new in 2.0
    void CanSee(Event *ev);
    void CanSeeNoEnts(Event *ev);

    void        SetKillTarget(const char *killtarget);
    const char *KillTarget(void);

    void AlwaysDraw(Event *ev);
    void NormalDraw(Event *ev);
    void NeverDraw(Event *ev);

    const char      *getModel() const;
    void             GetModelEvent(Event *ev);
    void             GetBrushModelEvent(Event *ev);
    virtual qboolean setModel(void);
    void             SetSize(void);
    void             setModel(const str            &mdl);
    void             SetModelEvent(Event *ev);

    void         SetTeamEvent(Event *ev);
    virtual void TriggerEvent(Event *ev);
    void         hideModel(void);
    void         EventHideModel(Event *ev);
    virtual void showModel(void);
    void         EventShowModel(Event *ev);
    qboolean     hidden(void);
    void         ProcessInitCommands(void);

    void  setAlpha(float alpha);
    float alpha(void);

    void setMoveType(int type);
    int  getMoveType(void);

    void setSolidType(solid_t type);
    int  getSolidType(void);

    virtual void setContentsSolid(void);

    virtual Vector getParentVector(Vector vec);
    Vector         getLocalVector(Vector vec);

    virtual void setSize(Vector min, Vector max);
    virtual void updateOrigin(void);
    virtual void setLocalOrigin(Vector org);
    void         setOrigin(Vector org) override;
    void         setOriginEvent(Vector org) override;
    virtual void setOrigin(void);
    virtual void addOrigin(Vector org);

    void     GetRawTag(int tagnum, orientation_t *orient);
    qboolean GetRawTag(const char *tagname, orientation_t *orient);

    void     GetTag(int tagnum, orientation_t *orient);
    qboolean GetTag(const char *name, orientation_t *orient);
    void     GetTag(int tagnum, Vector *pos, Vector *forward = NULL, Vector *left = NULL, Vector *up = NULL);
    qboolean GetTag(const char *name, Vector *pos, Vector *forward = NULL, Vector *left = NULL, Vector *up = NULL);

    virtual int   CurrentAnim(int slot = 0) const;
    virtual float CurrentTime(int slot = 0) const;
    void          ClearAnimSlot(int slot);
    void          StartAnimSlot(int slot, int index, float weight);
    void          RestartAnimSlot(int slot);

    void         setAngles(Vector ang) override;
    virtual void setAngles(void);

    void link(void);
    void unlink(void);

    void setContents(int type);
    int  getContents(void);
    void setScale(float scale);

    qboolean droptofloor(float maxfall);
    qboolean isClient(void);

    virtual void SetDeltaAngles(void);
    virtual void DamageEvent(Event *event);
    void         Damage(
                Entity *inflictor,
                Entity *attacker,
                float   damage,
                Vector  position,
                Vector  direction,
                Vector  normal,
                int     knockback,
                int     flags,
                int     meansofdeath,
                int     location = -1
            );

    void     DamageType(Event *ev);
    qboolean IsTouching(Entity *e1);
    qboolean IsInside(Entity *e1);
    qboolean FovCheck(float *delta, float fovdot);
    // bNoEnts since 2.0
    virtual bool CanSee(Entity *ent, float fov, float vision_distance, bool bNoEnts = false);
    virtual bool CanSee(const Vector& org, float fov, float vision_distance, bool bNoEnts = false); // added in 2.0
    bool         AreasConnected(const Entity *other);
    void         FadeNoRemove(Event *ev);
    void         FadeOut(Event *ev);
    void         FadeIn(Event *ev);
    void         Fade(Event *ev);
    void         Sink(Event *ev);

    virtual void     CheckGround(void);
    virtual qboolean HitSky(trace_t *trace);
    virtual qboolean HitSky(void);

    void SafeSolid(Event *ev); // new in 2.0
    void BecomeSolid(Event *ev);
    void BecomeNonSolid(Event *ev);
    void SetSize(Event *ev);
    void SetMins(Event *ev);
    void SetMaxs(Event *ev);
    //==
    // added in 2.0
    void GetMins(Event *ev);
    void GetMaxs(Event *ev);
    //==
    void SetScale(Event *ev);
    void GetScale(Event *ev);
    void SetAlpha(Event *ev);
    void SetKillTarget(Event *ev);
    void TouchTriggersEvent(Event *ev);

    str  GetRandomAlias(str name, AliasListNode_t **ret);
    void SetWaterType(void);

    // model binding functions
    qboolean
    attach(int parent_entity_num, int tag_num, qboolean use_angles = qtrue, Vector attach_offset = Vector("0 0 0"));
    void detach(void);

    //
    // Sound functions
    //
    void         ProcessSoundEvent(Event *ev, qboolean checkSubtitle);
    void         Sound(Event *ev);
    virtual void Sound(
        str     sound_name,
        int     channel       = CHAN_BODY,
        float   volume        = -1.0,
        float   min_dist      = -1.0,
        Vector *origin        = NULL,
        float   pitch         = -1.0f,
        int     argstype      = 0,
        int     doCallback    = 0,
        int     checkSubtitle = 1,
        float   max_dist      = -1.0f
    );
    void StopSound(int channel);
    void StopSound(Event *ev);
    void LoopSound(Event *ev);
    void
    LoopSound(str sound_name, float volume = -1.0, float min_dist = -1.0, float max_dist = -1.0, float pitch = -1.0);
    void StopLoopSound(Event *ev);
    void StopLoopSound(void);
    void StopAllSounds();

    void SetLight(Event *ev);
    void LightOn(Event *ev);
    void LightOff(Event *ev);
    void LightRed(Event *ev);
    void LightGreen(Event *ev);
    void LightBlue(Event *ev);
    void LightRadius(Event *ev);
    void LightStyle(Event *ev);
    void Flags(Event *ev);
    void Effects(Event *ev);
    void RenderEffects(Event *ev);
    void SVFlags(Event *ev);

    void         BroadcastAIEvent(int iType = AI_EVENT_MISC, float rad = SOUND_RADIUS);
    void         BroadcastAIEvent(Event *ev);
    void         Kill(Event *ev);
    virtual void Killed(Event *ev);
    void         SurfaceModelEvent(Event *ev);
    void         SurfaceCommand(const char *surf_name, const char *token);
    virtual void Postthink(void);
    virtual void Think(void);

    void DamageSkin(trace_t *trace, float damage);

    void AttachEvent(Event *ev);
    void AttachModelEvent(Event *ev);
    void RemoveAttachedModelEvent(Event *ev);
    void AttachedModelAnimEvent(Event *ev);
    void DetachEvent(Event *ev);
    void TakeDamageEvent(Event *ev);
    void NoDamageEvent(Event *ev);
    void Gravity(Event *ev);
    void GiveOxygen(float time);
    void UseBoundingBoxEvent(Event *ev);
    void HurtEvent(Event *ev);
    void HealEvent(Event *ev);
    void SetMassEvent(Event *ev);
    void Censor(Event *ev);
    void Ghost(Event *ev);

    void StationaryEvent(Event *ev);
    void TossEvent(Event *ev);
    void Explosion(Event *ev);

    void Shader(Event *ev);

    void KillAttach(Event *ev);
    void SetBloodModel(Event *ev);

    void DropToFloorEvent(Event *ev);

    // Binding methods
    void         joinTeam(Entity *teammember);
    void         quitTeam(void);
    qboolean     isBoundTo(Entity *master);
    virtual void bind(Entity *master, qboolean use_my_angles = qfalse);
    virtual void unbind(void);
    virtual void glue(Entity *master, qboolean use_my_angles = qtrue, qboolean can_duck = qfalse);
    virtual void unglue(void);
    void         GlueEvent(Event *ev);
    void         DuckableGlueEvent(Event *ev);
    void         MakeClayPidgeon(Event *ev);
    void         EventUnglue(Event *ev);

    void JoinTeam(Event *ev);
    void EventQuitTeam(Event *ev);
    void BindEvent(Event *ev);
    void EventUnbind(Event *ev);
    void AddToSoundManager(Event *ev);
    void NoLerpThisFrame(void);

    virtual void addAngles(Vector add);

    void DeathSinkStart(Event *ev);
    void DeathSink(Event *ev);

    void DetachAllChildren(Event *ev);
    void SetMovementStealth(float fStealthScale);
    void EventMovementStealth(Event *ev);

    virtual void VelocityModified(void);

    qboolean CheckEventFlags(Event *event);
    void     PusherEvent(Event *ev);
    void     SetShaderData(Event *ev);

    void GetVelocity(Event *ev);
    void SetVelocity(Event *ev);
    void GetAVelocity(Event *ev);
    void DoForceActivate(void);
    void ForceActivate(Event *ev);

    void ConnectPaths(void);
    void DisconnectPaths(void);

    void         EventConnectPaths(Event *ev);
    void         EventDisconnectPaths(Event *ev);
    virtual void Delete(void);
    void         Remove(Event *ev);
    void         EventSoundDone(Event *ev);

    void VolumeDamage(float damage);
    void EventVolumeDamage(Event *ev);

    virtual qboolean IsDead() const;
    virtual void     AddImmunity(Event *ev);
    virtual void     RemoveImmunity(Event *ev);
    qboolean         Immune(int meansofdeath);

    virtual void ClientThink(void);

    virtual void     EndFrame(void);
    virtual void     CalcBlend(void);
    void             Archive(Archiver            &arc) override;
    virtual bool     AutoArchiveModel(void);
    virtual void     PathnodeClaimRevoked(class PathNode *node);
    virtual qboolean BlocksAIMovement() const;
    virtual qboolean AIDontFace(void) const;
    void             SetHealth(Event *ev);
    void             GetHealth(Event *ev);
    void             EventSetMaxHealth(Event *ev);
    void             EventGetMaxHealth(Event *ev);
    void             EventSetHealthOnly(Event *ev);
    void             GetYaw(Event *ev);

    virtual void PreAnimate(void);
    virtual void PostAnimate(void);
    virtual bool HasVehicle(void) const;
    void         EventGetEntnum(Event *ev);
    void         EventGetClassname(Event *ev);
    void         EventSetRadnum(Event *ev);
    void         EventGetRadnum(Event *ev);
    void         EventSetRotatedBbox(Event *ev);
    void         EventGetRotatedBbox(Event *ev);
    void         MPrintf(const char *msg, ...);
    void         DrawBoundingBox(int showbboxes);

    void EventSinglePlayerCommand(Event *ev);
    void EventMultiPlayerCommand(Event *ev);
    void EventRealismModeCommand(Event *ev);
    void EventSPRealismModeCommand(Event *ev);
    void EventDMRealismModeCommand(Event *ev);
    void GetLocalYawFromVector(Event *ev);
    void EventShootableOnly(Event *ev);
    void SetShaderTime(Event *ev);
    void NoTarget(Event *ev);

    //
    // Custom openmohaa features
    //
    void ProcessHint(gentity_t *client, bool bShow);
    void GetZone(Event *ev);
    void IsInZone(Event *ev);
    void SetDepthHack(Event *ev);
#ifdef OPM_FEATURES
    void SetHintRequireLookAt(Event *ev);
    void SetHintString(Event *ev);
    void SetShader(Event* ev);
#endif
    void PlayNonPvsSound(const str& soundName, float volume = 1);
};

inline void Entity::PreAnimate(void) {}

inline void Entity::PostAnimate(void) {}

inline bool Entity::HasVehicle(void) const
{
    return false;
}

inline int Entity::getSolidType(void)

{
    return edict->solid;
}

inline qboolean Entity::DistanceTo(Vector pos) const

{
    Vector delta;

    delta = origin - pos;
    return delta.length() != 0 ? qtrue : qfalse;
}

inline qboolean Entity::DistanceTo(Entity *ent) const
{
    Vector delta;

    assert(ent);

    if (!ent) {
        // "Infinite" distance
        return 999999;
    }

    delta = origin - ent->origin;
    return delta.length() != 0 ? qtrue : qfalse;
}

inline qboolean Entity::WithinDistance(Vector pos, float dist) const
{
    Vector delta;

    delta = origin - pos;

    // check squared distance
    return ((delta * delta) < (dist * dist)) ? qtrue : qfalse;
}

inline qboolean Entity::WithinDistance(Entity *ent, float dist) const

{
    Vector delta;

    assert(ent);

    if (!ent) {
        return false;
    }

    delta = origin - ent->origin;

    // check squared distance
    return ((delta * delta) < (dist * dist));
}

inline const char *Entity::KillTarget(void)

{
    return killtarget.c_str();
}

inline qboolean Entity::hidden(void)
{
    if (edict->s.renderfx & RF_DONTDRAW) {
        return true;
    }
    return false;
}

inline void Entity::GetBrushModelEvent(Event *ev)
{
    ev->AddString(model);
}

inline void Entity::GetModelEvent(Event *ev)
{
    if (!edict->tiki) {
        ev->AddNil();
        return;
    }

    const char *name = gi.TIKI_NameForNum(edict->tiki);

    if (!name) {
        if (model != "") {
            ev->AddString(model);
        } else {
            ev->AddNil();
            return;
        }
    } else {
        ev->AddString(name);
    }
}

inline void Entity::hideModel(void)
{
    edict->s.renderfx |= RF_DONTDRAW;
    if (getSolidType() <= SOLID_TRIGGER) {
        edict->r.svFlags |= SVF_NOCLIENT;
    }
}

inline void Entity::showModel(void)
{
    edict->s.renderfx &= ~RF_DONTDRAW;
    edict->r.svFlags &= ~SVF_NOCLIENT;
}

inline float Entity::alpha(void)

{
    return edict->s.alpha;
}

inline void Entity::setMoveType(int type)

{
    movetype = type;
}

inline int Entity::getMoveType(void)

{
    return movetype;
}

inline void Entity::unlink(void)

{
    gi.unlinkentity(edict);
}

inline void Entity::setContents(int type)

{
    edict->r.contents = type;
}

inline int Entity::getContents(void)
{
    return edict->r.contents;
}

inline qboolean Entity::isClient(void)
{
    if (client) {
        return true;
    }
    return false;
}

inline void Entity::SetDeltaAngles(void)

{
    int i;

    if (client) {
        for (i = 0; i < 3; i++) {
            client->ps.delta_angles[i] = ANGLE2SHORT(client->ps.viewangles[i]);
        }
    }
}

inline str Entity::GetRandomAlias(str name, AliasListNode_t **ret)
{
    str         realname;
    const char *s;

    if (edict->tiki) {
        s = gi.Alias_FindRandom(edict->tiki, name.c_str(), ret);
    } else {
        s = NULL;
    }

    if (s) {
        realname = s;
    } else {
        s = gi.GlobalAlias_FindRandom(name.c_str(), ret);
        if (s) {
            realname = s;
        }
    }

    return realname;
}

inline bool Entity::AreasConnected(const Entity *other)
{
    return gi.AreasConnected(edict->r.areanum, other->edict->r.areanum) != 0;
}

#include "worldspawn.h"
