/*
===========================================================================
Copyright (C) 2015 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.cpp: 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).
//

#include "entity.h"
#include "scriptmaster.h"
#include "sentient.h"
#include "misc.h"
#include "specialfx.h"
#include "object.h"
#include "player.h"
#include "weaputils.h"
#include "soundman.h"
#include "scriptexception.h"
#include "qfiles.h"
#include "g_spawn.h"
#include "g_phys.h"
#include "debuglines.h"
#include <tiki.h>
#include <utility>

// Generic entity events
Event EV_SoundDone(
    "saydone", EV_DEFAULT, NULL, NULL, "Even used by sound-only special case of say to trigger waittill saydone"
);
Event EV_Classname
(
    "classname",
    EV_DEFAULT,
    "s",
    "nameOfClass",
    "Determines what class to use for this entity,\n"
    "this is pre-processed from the BSP at the start\n"
    "of the level.",
    EV_NORMAL
);
Event EV_SpawnFlags
(
    "spawnflags",
    EV_DEFAULT,
    "i",
    "flags",
    "spawnflags from the BSP,"
    "these are set inside the editor",
    EV_NORMAL
);
Event EV_GetNormalHealth
(
    "normal_health",
    EV_DEFAULT,
    NULL,
    NULL,
    "The Normal Health of the Actor",
    EV_GETTER
);
Event EV_NormalDamage
(
    "normal_damage",
    EV_DEFAULT,
    NULL,
    NULL,
    "The Normal Health of the Actor (0 - 100)",
    EV_NORMAL
);
Event EV_SetTeam
(
    "team",
    EV_DEFAULT,
    "s",
    "moveTeam",
    "used to make multiple entities move together.",
    EV_NORMAL
);
Event EV_Trigger
(
    "trigger",
    EV_DEFAULT,
    "s",
    "name",
    "Trigger the specified target or entity.",
    EV_NORMAL
);
Event EV_Activate
(
    "doActivate",
    EV_DEFAULT,
    "e",
    "activatingEntity",
    "General trigger event for all entities",
    EV_NORMAL
);
Event EV_Use
(
    "doUse",
    EV_DEFAULT,
    "e",
    "activatingEntity",
    "sent to entity when it is used by another entity",
    EV_NORMAL
);

Event EV_FadeNoRemove
(
    "fade",
    EV_DEFAULT,
    "F[0,]F[0,1]",
    "fadetime target_alpha",
    "Fade the entity's alpha, reducing it by 0.03\n"
    "every FRAMETIME, until it has faded out, does not remove the entity",
    EV_NORMAL
);

Event EV_FadeOut
(
    "_fadeout",
    EV_DEFAULT,
    NULL,
    NULL,
    "Fade the entity's alpha and scale out, reducing it by 0.03\n"
    "every FRAMETIME, until it has faded out, removes the entity\n"
    "Once the entity has been completely faded, the entity is removed.",
    EV_NORMAL
);

Event EV_Fade
(
    "fadeout",
    EV_DEFAULT,
    "F[0,]F[0,1]",
    "fadetime target_alpha",
    "Fade the entity's alpha and scale out, reducing it by 0.03\n"
    "every FRAMETIME, until it has faded out. If fadetime or\n"
    "target_alpha are defined, they will override the defaults.\n"
    "Once the entity has been completely faded, the entity is removed.",
    EV_NORMAL
);
Event EV_FadeIn
(
    "fadein",
    EV_DEFAULT,
    "F[0,]F[0,1]",
    "fadetime target_alpha",
    "Fade the entity's alpha and scale in, increasing it by 0.03\n"
    "every FRAMETIME, until it has faded completely in to 1.0.\n"
    "If fadetime or target_alpha are defined, they will override\n"
    "the default values.",
    EV_NORMAL
);
Event EV_Killed(
    "killed",
    EV_DEFAULT,
    "eievvviiii",
    "attacker damage inflictor position direction normal knockback damageflags meansofdeath location",
    "event which is sent to an entity once it as been killed\n\nLocation values:\n-1 General\n0 Pelvis\n1 Lower "
    "Torso\n2 Mid Torso\n3 Upper Torso\n4 Neck\n5 Head\n6 RUpperArm\n7 RForearm\n8 RHand\n9 LUpperArm\n10 LForearm\n11 "
    "LHand\n12 RThigh\n13 RCalf\n14 RFoot\n15 LThigh\n16 LCalf\n17 LFoot\n",
    EV_NORMAL
);
Event EV_GotKill
(
    "gotkill",
    EV_DEFAULT,
    "eieib",
    "victim damage inflictor meansofdeath gib",
    "event sent to attacker when an entity dies",
    EV_NORMAL
);
Event EV_Pain
(
    "pain",
    EV_DEFAULT,
    "eievvviiii",
    "attacker damage inflictor position direction normal knockback damageflags meansofdeath location",
    "used to inflict pain to an entity",
    EV_NORMAL
);
Event EV_Damage(
    "damage",
    EV_DEFAULT,
    "eievvviiii",
    "attacker damage inflictor position direction normal knockback damageflags meansofdeath location",
    "general damage event used by all entities\n\nLocation values:\n-1 General\n0 Pelvis\n1 Lower Torso\n2 Mid "
    "Torso\n3 Upper Torso\n4 Neck\n5 Head\n6 RUpperArm\n7 RForearm\n8 RHand\n9 LUpperArm\n10 LForearm\n11 LHand\n12 "
    "RThigh\n13 RCalf\n14 RFoot\n15 LThigh\n16 LCalf\n17 LFoot\n",
    EV_NORMAL
);
Event EV_Stun
(
    "_stun",
    EV_DEFAULT,
    "f",
    "time",
    "Stun this entity for the specified time",
    EV_NORMAL
);
Event EV_Kill
(
    "kill",
    EV_CONSOLE,
    NULL,
    NULL,
    "console based command to kill yourself if stuck.",
    EV_NORMAL
);
Event EV_Gib
(
    "gib",
    EV_DEFAULT,
    "iIFS",
    "number power scale gibmodel",
    "causes entity to spawn a number of gibs",
    EV_NORMAL
);
Event EV_Hurt
(
    "hurt",
    EV_DEFAULT,
    "iSV",
    "damage means_of_death direction",
    "Inflicts damage if the entity is damageable.  If the number of damage\n"
    "points specified in the command argument is greater or equal than the\n"
    "entity's current health, it will be killed or destroyed.",
    EV_NORMAL
);
Event EV_Heal(
    "heal",
    EV_DEFAULT,
    "f",
    "health",
    "Adds health to an entity,"
    "0-1 fraction of max_health"
);

Event EV_TakeDamage
(
    "takedamage",
    EV_DEFAULT,
    NULL,
    NULL,
    "makes entity take damage.",
    EV_NORMAL
);
Event EV_NoDamage
(
    "nodamage",
    EV_DEFAULT,
    NULL,
    NULL,
    "entity does not take damage.",
    EV_NORMAL
);

Event EV_Stationary
(
    "stationary",
    EV_DEFAULT,
    NULL,
    NULL,
    "entity does not move,"
    "causes no physics to be run on it.",
    EV_NORMAL
);
Event EV_Toss
(
    "toss",
    EV_DEFAULT,
    NULL,
    NULL,
    "entity has gravity applied to it.",
    EV_NORMAL
);

// Physics events
Event EV_MoveDone
(
    "movedone",
    EV_DEFAULT,
    "e",
    "finishedEntity",
    "Sent to commanding thread when done with move .",
    EV_NORMAL
);
Event EV_Touch
(
    "doTouch",
    EV_DEFAULT,
    "e",
    "touchingEntity",
    "sent to entity when touched.",
    EV_NORMAL
);
Event EV_Blocked
(
    "doBlocked",
    EV_DEFAULT,
    "e",
    "obstacle",
    "sent to entity when blocked.",
    EV_NORMAL
);
Event EV_UseBoundingBox
(
    "usebbox",
    EV_DEFAULT,
    NULL,
    NULL,
    "do not perform perfect collision,"
    "use bounding box instead.",
    EV_NORMAL
);
Event EV_Gravity
(
    "gravity",
    EV_DEFAULT,
    "f",
    "gravityValue",
    "Change the gravity on this entity",
    EV_NORMAL
);
Event EV_Stop
(
    "stopped",
    EV_DEFAULT,
    NULL,
    NULL,
    "sent when entity has stopped bouncing for MOVETYPE_TOSS.",
    EV_NORMAL
);
Event EV_Attach
(
    "attach",
    EV_DEFAULT,
    "esI",
    "parent tagname use_angles",
    "attach this entity to the parent's legs tag called tagname",
    EV_NORMAL
);
Event EV_AttachModel
(
    "attachmodel",
    EV_DEFAULT,
    "ssFSBFFFFV",
    "modelname tagname scale targetname detach_at_death removetime fadeintime fadeoutdelay fadetime offset",
    "attach a entity with modelname to this entity to tag called tagname.\n"
    "scale           - scale of attached entities\n"
    "targetname      - targetname for attached entities\n"
    "detach_at_death - when entity dies, should this model be detached.\n"
    "removetime      - when the entity should be removed, if not specified, never.\n"
    "fadeintime      - time to fade the model in over.\n"
    "fadeoutdelay    - time to wait until we fade the attached model out\n"
    "fadeoutspeed    - time the model fades out over\n"
    "offset		     - vector offset for the model from the specified tag",
    EV_NORMAL
);
Event EV_RemoveAttachedModel
(
    "removeattachedmodel",
    EV_DEFAULT,
    "s",
    "tagname",
    "Removes the model attached to this entity at the specified tag.",
    EV_NORMAL
);
Event EV_AttachedModelAnim(
    "attachedmodelanim",
    EV_DEFAULT,
    "ssfs",
    "tagname anim_name crossblend_time model_name",
    "Tells models (or specified model) attached to specified tag to play \n"
    "specified animation.  Crossblend time doesn't work yet."
);
Event EV_Detach
(
    "detach",
    EV_DEFAULT,
    NULL,
    NULL,
    "detach this entity from its parent.",
    EV_NORMAL
);

// script stuff
Event EV_Model
(
    "model",
    EV_DEFAULT,
    "e",
    "modelName",
    "set the model to modelName.",
    EV_NORMAL
);
Event EV_SetModel
(
    "model",
    EV_DEFAULT,
    "e",
    "modelName",
    "set the model to modelName.",
    EV_SETTER
);
Event EV_GetModel
(
    "model",
    EV_DEFAULT,
    NULL,
    NULL,
    "get the modelName.",
    EV_GETTER
);
Event EV_GetBrushModel
(
    "brushmodel",
    EV_DEFAULT,
    NULL,
    NULL,
    "get the brush modelName.",
    EV_GETTER
);
Event EV_Hide
(
    "hide",
    EV_DEFAULT,
    NULL,
    NULL,
    "hide the entity,"
    "opposite of show.",
    EV_NORMAL
);
Event EV_Show
(
    "show",
    EV_DEFAULT,
    NULL,
    NULL,
    "show the entity,"
    "opposite of hide.",
    EV_NORMAL
);
Event EV_BecomeSolid
(
    "solid",
    EV_DEFAULT,
    NULL,
    NULL,
    "make solid.",
    EV_NORMAL
);
Event EV_SafeSolid
(
    "safesolid",
    EV_DEFAULT,
    NULL,
    NULL,
    "make solid but first make sure no one is in my bounds that is thinking.",
    EV_NORMAL
);
Event EV_BecomeNonSolid
(
    "notsolid",
    EV_DEFAULT,
    NULL,
    NULL,
    "make non-solid.",
    EV_NORMAL
);
Event EV_Ghost
(
    "ghost",
    EV_DEFAULT,
    NULL,
    NULL,
    "make non-solid but still send to client regardless of hide status.",
    EV_NORMAL
);
Event EV_TouchTriggers
(
    "touchtriggers",
    EV_DEFAULT,
    NULL,
    NULL,
    "this entity should touch triggers.",
    EV_NORMAL
);

Event EV_Sound
(
    "playsound",
    EV_DEFAULT,
    "sIFS",
    "soundName channel volume min_distance",
    "play a sound coming from this entity.\n"
    "default channel, CHAN_BODY.",
    EV_NORMAL
);
Event EV_StopSound
(
    "stopsound",
    EV_DEFAULT,
    "I",
    "channel",
    "stop the current sound on the specified channel.\n"
    "default channel, CHAN_BODY.",
    EV_NORMAL
);

Event EV_DuckableGlue
(
    "duckableglue",
    EV_DEFAULT,
    "eI",
    "parent glueAngles",
    "glue this entity to the specified entity, but allow ducking.",
    EV_NORMAL
);
Event EV_Glue
(
    "glue",
    EV_DEFAULT,
    "eI",
    "parent glueAngles",
    "glue this entity to the specified entity.",
    EV_NORMAL
);
Event EV_Unglue
(
    "unglue",
    EV_DEFAULT,
    NULL,
    NULL,
    "unglue this entity.",
    EV_NORMAL
);
Event EV_Bind
(
    "bind",
    EV_DEFAULT,
    "eB",
    "parent bind_child",
    "bind this entity to the specified entity.",
    EV_NORMAL
);
Event EV_Unbind
(
    "unbind",
    EV_DEFAULT,
    NULL,
    NULL,
    "unbind this entity.",
    EV_NORMAL
);
Event EV_MakeClayPidgeon
(
    "claypidgeon",
    EV_DEFAULT,
    NULL,
    NULL,
    "turn the entity into a non-solid shootable thing",
    EV_NORMAL
);
Event EV_JoinTeam
(
    "joinTeam",
    EV_DEFAULT,
    "e",
    "teamMember",
    "join a bind team.",
    EV_NORMAL
);
Event EV_QuitTeam
(
    "quitTeam",
    EV_DEFAULT,
    NULL,
    NULL,
    "quit the current bind team",
    EV_NORMAL
);
Event EV_SetScale
(
    "scale",
    EV_DEFAULT,
    "f",
    "newScale",
    "set the scale of the entity",
    EV_NORMAL
);
Event EV_SetScale2
(
    "scale",
    EV_DEFAULT,
    "f",
    "newScale",
    "set the scale of the entity",
    EV_SETTER
);
Event EV_GetScale
(
    "scale",
    EV_DEFAULT,
    "f",
    "newScale",
    "set the scale of the entity",
    EV_GETTER
);
Event EV_SetSize
(
    "setsize",
    EV_DEFAULT,
    "vv",
    "mins maxs",
    "Set the bounding box of the entity to mins and maxs.",
    EV_NORMAL
);
Event EV_SetMins
(
    "_setmins",
    EV_DEFAULT,
    "v",
    "mins",
    "Set the mins of the bounding box of the entity to mins.",
    EV_NORMAL
);
Event EV_SetMaxs
(
    "_setmaxs",
    EV_DEFAULT,
    "v",
    "maxs",
    "Set the maxs of the bounding box of the entity to maxs.",
    EV_NORMAL
);
Event EV_GetMins
(
    "getmins",
    EV_DEFAULT,
    NULL,
    NULL,
    "Get the mins of the bounding box of the entity to mins.",
    EV_GETTER
);
Event EV_GetMaxs
(
    "getmaxs",
    EV_DEFAULT,
    NULL,
    NULL,
    "Get the maxs of the bounding box of the entity to maxs.",
    EV_GETTER
);
Event EV_SetAlpha
(
    "alpha",
    EV_DEFAULT,
    "f",
    "newAlpha",
    "Set the alpha of the entity to alpha.",
    EV_NORMAL
);
Event EV_SetKillTarget
(
    "killtarget",
    EV_DEFAULT,
    "s",
    "targetName",
    "when dying kill entities with this targetName.",
    EV_NORMAL
);
Event EV_SetMass
(
    "mass",
    EV_DEFAULT,
    "f",
    "massAmount",
    "set the mass of this entity.",
    EV_NORMAL
);

Event EV_LoopSound
(
    "loopsound",
    EV_DEFAULT,
    "sFS",
    "soundName volume minimum_distance",
    "play a looped-sound with a certain volume and minimum_distance\n"
    "which is attached to the current entity.",
    EV_NORMAL
);
Event EV_StopLoopSound
(
    "stoploopsound",
    EV_DEFAULT,
    NULL,
    NULL,
    "Stop the looped-sound on this entity.",
    EV_NORMAL
);

Event EV_SurfaceModelEvent(
    "surface",
    EV_DEFAULT,
    "sSSSSSS",
    "surfaceName parameter1 parameter2 parameter3 parameter4 parameter5 parameter6",
    "change a legs surface parameter for the given surface.\n"
    "+ sets the flag, - clears the flag\n"
    "Valid surface commands are:\n"
    "skin1 - set the skin1 offset bit\n"
    "skin2 - set the skin2 offset bit\n"
    "nodraw - don't draw this surface",
    EV_NORMAL
);
// AI sound events
Event EV_BroadcastAIEvent
(
    "ai_event",
    EV_DEFAULT,
    "SF",
    "type radius",
    "Let the AI know that this entity made a sound,\ntype is a string specifying what type of sound it is.\nradius "
    "determines how far the sound reaches.",
    EV_NORMAL
);

// Lighting
Event EV_SetLight
(
    "light",
    EV_DEFAULT,
    "ffff",
    "red green blue radius",
    "Create a dynamic light on this entity.",
    EV_NORMAL
);

Event EV_LightOn
(
    "lightOn",
    EV_DEFAULT,
    NULL,
    NULL,
    "Turn the configured dynamic light on this entity on.",
    EV_NORMAL
);
Event EV_LightOff
(
    "lightOff",
    EV_DEFAULT,
    NULL,
    NULL,
    "Turn the configured dynamic light on this entity off.",
    EV_NORMAL
);
Event EV_LightStyle
(
    "lightStyle",
    EV_DEFAULT,
    "i",
    "lightStyleIndex",
    "What light style to use for this dynamic light on this entity.",
    EV_NORMAL
);
Event EV_LightRed
(
    "lightRed",
    EV_DEFAULT,
    "f",
    "red",
    "Set the red component of the dynamic light on this entity.",
    EV_NORMAL
);
Event EV_LightGreen
(
    "lightGreen",
    EV_DEFAULT,
    "f",
    "red",
    "Set the red component of the dynamic light on this entity.",
    EV_NORMAL
);
Event EV_LightBlue
(
    "lightBlue",
    EV_DEFAULT,
    "f",
    "red",
    "Set the red component of the dynamic light on this entity.",
    EV_NORMAL
);
Event EV_LightRadius
(
    "lightRadius",
    EV_DEFAULT,
    "f",
    "red",
    "Set the red component of the dynamic light on this entity.",
    EV_NORMAL
);

// Entity flag specific
Event EV_EntityFlags(
    "flags",
    EV_DEFAULT,
    "SSSSSS",
    "parameter1 parameter2 parameter3 parameter4 parameter5 parameter6",
    "Change the current entity flags.\n"
    "Valid flags are as follows:\n"
    "+ sets a flag, - clears a flag\n"
    "blood - should it bleed\n"
    "explode - should it explode when dead\n"
    "die_gibs - should it spawn gibs when dead\n"
    "god - makes the entity invincible\n",
    EV_NORMAL
);
Event EV_EntityRenderEffects(
    "rendereffects",
    EV_DEFAULT,
    "SSSSSS",
    "parameter1 parameter2 parameter3 parameter4 parameter5 parameter6",
    "Change the current render effects flags.\n"
    "Valid flags are as follows:\n"
    "+ sets a flag, - clears a flag\n"
    "dontdraw - send the entity to the client, but don't draw\n"
    "betterlighting - do sphere based vertex lighting on the entity\n"
    "lensflare - add a lens glow to the entity at its origin\n"
    "viewlensflare - add a view dependent lens glow to the entity at its origin\n"
    "lightoffset - use the dynamic color values as a light offset to the model\n"
    "skyorigin - this entity is the portal sky origin\n"
    "minlight - this entity always has some lighting on it\n"
    "fullbright - this entity is always fully lit\n"
    "additivedynamiclight - the dynamic light should have an additive effect\n"
    "lightstyledynamiclight - the dynamic light uses a light style, use the\n"
    "'lightstyle' command to set the index of the light style to be used",
    EV_NORMAL
);
Event EV_EntityEffects(
    "effects",
    EV_DEFAULT,
    "SSSSSS",
    "parameter1 parameter2 parameter3 parameter4 parameter5 parameter6",
    "Change the current entity effects flags.\n"
    "Valid flags are as follows:\n"
    "+ sets a flag, - clears a flag\n"
    "antisbjuice - anti sucknblow juice"
    "everyframe - process commands every time entity is rendered",
    EV_NORMAL
);
Event EV_EntitySVFlags(
    "svflags",
    EV_DEFAULT,
    "SSSSSS",
    "parameter1 parameter2 parameter3 parameter4 parameter5 parameter6",
    "Change the current server flags.\n"
    "Valid flags are as follows:\n"
    "+ sets a flag, - clears a flag\n"
    "broadcast - always send this entity to the client",
    EV_NORMAL
);

// Special Effects
Event EV_Censor
(
    "censor",
    EV_DEFAULT,
    NULL,
    NULL,
    "used to ban certain contact when in parentmode\n",
    EV_NORMAL
);
Event EV_Explosion
(
    "explosionattack",
    EV_DEFAULT,
    "sS",
    "explosionModel tagName",
    "Spawn an explosion optionally from a specific tag",
    EV_NORMAL
);

Event EV_ShaderEvent
(
    "shader",
    EV_DEFAULT,
    "sfF",
    "shaderCommand argument1 argument2",
    "change a specific shader parameter for the entity.\n"
    "Valid shader commands are:\n"
    "translation [trans_x] [trans_y] - change the texture translation\n"
    "offset [offset_x] [offset_y] - change the texture offset\n"
    "rotation [rot_speed] - change the texture rotation speed\n"
    "frame [frame_num] - change the animated texture frame\n"
    "wavebase [base] - change the base parameter of the wave function\n"
    "waveamp [amp] - change the amp parameter of the wave function\n"
    "wavebase [phase] - change the phase parameter of the wave function\n"
    "wavefreq [freq] - change the frequency parameter of the wave function\n",
    EV_NORMAL
);

Event EV_ScriptShaderEvent
(
    "scriptshader",
    EV_DEFAULT,
    "sfF",
    "shaderCommand argument1 argument2",
    "alias for shader command, change a specific shader parameter for the entity.\n"
    "Valid shader commands are:\n"
    "translation [trans_x] [trans_y] - change the texture translation\n"
    "offset [offset_x] [offset_y] - change the texture offset\n"
    "rotation [rot_speed] - change the texture rotation speed\n"
    "frame [frame_num] - change the animated texture frame\n"
    "wavebase [base] - change the base parameter of the wave function\n"
    "waveamp [amp] - change the amp parameter of the wave function\n"
    "wavebase [phase] - change the phase parameter of the wave function\n"
    "wavefreq [freq] - change the frequency parameter of the wave function\n",
    EV_NORMAL
);

Event EV_KillAttach
(
    "killattach",
    EV_DEFAULT,
    NULL,
    NULL,
    "kill all the attached entities.",
    EV_NORMAL
);
Event EV_DropToFloor
(
    "droptofloor",
    EV_DEFAULT,
    "F",
    "maxRange",
    "drops the entity to the ground, if maxRange is not specified 8192 is used.",
    EV_NORMAL
);
Event EV_AddToSoundManager
(
    "_addtosoundmanager",
    EV_DEFAULT,
    NULL,
    NULL,
    "adds the current entity to the sound manager.",
    EV_NORMAL
);
Event EV_SetControllerAngles
(
    "setcontrollerangles",
    EV_DEFAULT,
    "iv",
    "num angles",
    "Sets the control angles for the specified bone.",
    EV_NORMAL
);
Event EV_GetControllerAngles
(
    "getcontrollerangles",
    EV_DEFAULT,
    "i",
    "num",
    "Gets the control angles for the specified bone.",
    EV_RETURN
);
Event EV_GetTagPosition
(
    "gettagposition",
    EV_DEFAULT,
    "s",
    "tag_name",
    "Gets the world position of the tag",
    EV_RETURN
);
Event EV_GetTagAngles
(
    "gettagangles",
    EV_DEFAULT,
    "s",
    "tag_name",
    "Gets the world angles of the tag",
    EV_RETURN
);
Event EV_DeathSinkStart
(
    "deathsinkstart",
    EV_DEFAULT,
    NULL,
    NULL,
    "Makes the entity sink into the ground and then get removed (this starts it).",
    EV_NORMAL
);
Event EV_DeathSink
(
    "deathsinkeachframe",
    EV_DEFAULT,
    NULL,
    NULL,
    "Makes the entity sink into the ground and then get removed (this gets called each frame).",
    EV_NORMAL
);
Event EV_DamageType
(
    "damage_type",
    EV_DEFAULT,
    "s",
    "meansofdeathstring",
    "Set the type of damage that this entity can take",
    EV_NORMAL
);
Event EV_DetachAllChildren
(
    "detachallchildren",
    EV_DEFAULT,
    NULL,
    NULL,
    "Detach all the children from the entity.",
    EV_NORMAL
);
Event EV_Entity_MovementStealth
(
    "movementstealth",
    EV_DEFAULT,
    "f",
    "scale",
    "Sets the current movement stealth scalar for the sentient",
    EV_NORMAL
);
Event EV_Entity_Revive
(
    "revive",
    EV_DEFAULT,
    "f",
    "health",
    "sets the health,"
    "even if dead"
);
Event EV_SetHealth
(
    "health",
    EV_CONSOLE | EV_CHEAT,
    "i",
    "newHealth",
    "set the health of the entity to newHealth",
    EV_NORMAL
);
Event EV_SetHealth2
(
    "health",
    EV_CONSOLE | EV_CHEAT,
    "i",
    "newHealth",
    "set the health of the entity to newHealth",
    EV_SETTER
);
Event EV_Entity_GetHealth
(
    "health",
    EV_DEFAULT,
    NULL,
    NULL,
    "entity's health",
    EV_GETTER
);
Event EV_Entity_GetMaxHealth
(
    "max_health",
    EV_DEFAULT,
    NULL,
    NULL,
    "gets the entity's max health",
    EV_GETTER
);
Event EV_Entity_SetMaxHealth
(
    "max_health",
    EV_DEFAULT,
    "i",
    "newHealth",
    "sets max_health without changing health",
    EV_NORMAL
);
Event EV_Entity_SetMaxHealth2
(
    "max_health",
    EV_DEFAULT,
    "i",
    "newHealth",
    "sets max_health without changing health",
    EV_SETTER
);
Event EV_Entity_SetHealthOnly
(
    "healthonly",
    EV_CONSOLE | EV_CHEAT,
    "i",
    "newHealth",
    "set the health of the entity to newHealth without changing max_health",
    EV_NORMAL
);
Event EV_Entity_SetHealthOnly2
(
    "healthonly",
    EV_CONSOLE | EV_CHEAT,
    "i",
    "newHealth",
    "set the health of the entity to newHealth without changing max_health",
    EV_SETTER
);
Event EV_Entity_GetYaw
(
    "yaw",
    EV_DEFAULT,
    NULL,
    NULL,
    "entity's yaw",
    EV_GETTER
);
Event EV_Pusher
(
    "pusher",
    EV_DEFAULT,
    "eevf",
    "inflictor attacker direction force",
    "Push an entity in the specified direction with the specified force",
    EV_NORMAL
);
Event EV_NeverDraw
(
    "neverdraw",
    EV_DEFAULT,
    NULL,
    NULL,
    "Sets this entity to never draw",
    EV_NORMAL
);
Event EV_NormalDraw
(
    "normaldraw",
    EV_DEFAULT,
    NULL,
    NULL,
    "Sets this entity to normal draw",
    EV_NORMAL
);
Event EV_AlwaysDraw
(
    "alwaysdraw",
    EV_DEFAULT,
    NULL,
    NULL,
    "Sets this entity to always draw",
    EV_NORMAL
);
Event EV_IsTouching
(
    "istouching",
    EV_DEFAULT,
    "e",
    "ent",
    "returns 1 if the entities are touching,"
    "0 if not",
    EV_RETURN
);
Event EV_IsInside
(
    "isinside",
    EV_DEFAULT,
    "e",
    "ent",
    "returns 1 if the entity is inside,"
    "0 if not",
    EV_RETURN
);
Event EV_CanSee
(
    "cansee",
    EV_DEFAULT,
    "eFF",
    "entity fov vision_distance",
    "returns 1 if the entities can see eachother, 0 if not",
    EV_RETURN
);
Event EV_CanSeeNoEnts
(
    "canseenoents",
    EV_DEFAULT,
    "eFF",
    "entity fov vision_distance",
    "returns 1 if the entities can see eachother, 0 if not; ignores any entities between them",
    EV_RETURN
);
Event EV_Entity_InPVS
(
    "inpvs",
    EV_DEFAULT,
    "e",
    "entity",
    "returns 1 if the entities have connected pvs,"
    "0 if not",
    EV_RETURN
);
Event EV_SetShaderData
(
    "setshaderdata",
    EV_DEFAULT,
    "ff",
    "data0 data1",
    "sets the shader controllers for this entity.",
    EV_NORMAL
);
Event EV_SetVelocity
(
    "velocity",
    EV_DEFAULT,
    "v",
    "velocity",
    "sets the velocity for this entity.",
    EV_SETTER
);
Event EV_GetVelocity
(
    "velocity",
    EV_DEFAULT,
    NULL,
    NULL,
    "gets the velocity for this entity.",
    EV_GETTER
);
Event EV_GetAVelocity
(
    "avelocity",
    EV_DEFAULT,
    NULL,
    NULL,
    "gets the angular velocity for this entity.",
    EV_GETTER
);
Event EV_ForceActivate
(
    "forceactivate",
    EV_DEFAULT,
    NULL,
    NULL,
    "Forces an entity to activate outside of the player's PVS",
    EV_NORMAL
);
Event EV_ConnectPaths
(
    "connect_paths",
    EV_DEFAULT,
    NULL,
    NULL,
    "Connects all navigation paths which intersect with the specified entity's volume",
    EV_NORMAL
);
Event EV_DisconnectPaths
(
    "disconnect_paths",
    EV_DEFAULT,
    NULL,
    NULL,
    "disonnects all navigation paths which intersect with the specified entity's volume",
    EV_NORMAL
);
Event EV_VolumeDamage
(
    "volumedamage",
    EV_DEFAULT,
    "f",
    "damage",
    "does damage to any entity within this's volume",
    EV_NORMAL
);
Event EV_Entity_AddImmunity
(
    "immune",
    EV_DEFAULT,
    "sSSSSS",
    "immune_string1 immune_string2 immune_string3 immune_string4 immune_string5 immune_string6",
    "Adds to the immunity list for this sentient.",
    EV_NORMAL
);
Event EV_Entity_RemoveImmunity
(
    "removeimmune",
    EV_DEFAULT,
    "sSSSSS",
    "immune_string1 immune_string2 immune_string3 immune_string4 immune_string5 immune_string6",
    "Removes from the immunity list for this sentient.",
    EV_NORMAL
);
Event EV_GetEntnum
(
    "entnum",
    EV_DEFAULT,
    NULL,
    NULL,
    "The entity's entity number",
    EV_GETTER
);
Event EV_GetClassname
(
    "classname",
    EV_DEFAULT,
    NULL,
    NULL,
    "The entity's classname",
    EV_GETTER
);
Event EV_Entity_SetRadnum
(
    "radnum",
    EV_DEFAULT,
    "i",
    "radnum",
    "set the radnum",
    EV_SETTER
);
Event EV_Entity_GetRadnum
(
    "radnum",
    EV_DEFAULT,
    NULL,
    NULL,
    "entity's radnum",
    EV_GETTER
);
Event EV_Entity_SetRotatedBbox
(
    "rotatedbbox",
    EV_DEFAULT,
    "i",
    "on_off",
    "Sets the entity's bbox to rotate with it.",
    EV_SETTER
);
Event EV_Entity_GetRotatedBbox
(
    "rotatedbbox",
    EV_DEFAULT,
    NULL,
    NULL,
    "Gets te entity's bbox to rotate with it.",
    EV_GETTER
);
Event EV_Entity_SinglePlayerCommand
(
    "sp",
    EV_DEFAULT,
    "sSSS",
    "command parm1 parm2 ...",
    "Makes a command be executed only in single player",
    EV_NORMAL
);
Event EV_Entity_MultiPlayerCommand
(
    "dm",
    EV_DEFAULT,
    "sSSS",
    "command parm1 parm2 ...",
    "Makes a command be executed only in multiplayer",
    EV_NORMAL
);
Event EV_Entity_RealismModeCommand
(
    "realism",
    EV_DEFAULT,
    "sSSS",
    "command parm1 parm2 ...",
    "Makes a command be executed only in realism mode",
    EV_NORMAL
);
Event EV_Entity_SPRealismModeCommand
(
    "sprealism",
    EV_DEFAULT,
    "sSSS",
    "command parm1 parm2 ...",
    "Makes a command be executed only in single player realism mode",
    EV_NORMAL
);
Event EV_Entity_DMRealismModeCommand
(
    "dmrealism",
    EV_DEFAULT,
    "sSSS",
    "command parm1 parm2 ...",
    "Makes a command be executed only in multiplayer realism mode",
    EV_NORMAL
);
Event EV_Entity_GetLocalYawFromVector
(
    "GetLocalYawFromVector",
    EV_DEFAULT,
    NULL,
    NULL,
    "Turn a worldspace vector into a local space yaw",
    EV_RETURN
);
Event EV_ShootableOnly
(
    "shootableonly",
    EV_DEFAULT,
    NULL,
    NULL,
    "Makes the entity shootable only.",
    EV_NORMAL
);
Event EV_SetShaderTime
(
    "setshadertime",
    EV_DEFAULT,
    "FF",
    "timeOffset randomTimeOffset",
    "reset the shader time for this entity.",
    EV_NORMAL
);
Event EV_NoTarget
(
    "notarget",
    EV_DEFAULT,
    "b",
    "setNoTarget",
    "flag an entity as no target.",
    EV_NORMAL
);

Event EV_Entity_SetDHack
(
    "depthhack",
    EV_DEFAULT,
    "i",
    "bEnable",
    "Enable or disable depth hack.",
    EV_NORMAL
);
/*
Event EV_Entity_GetZone
(
    "getzone",
    EV_DEFAULT,
    NULL,
    NULL,
    "Gets the current entity zone. NIL if the entity is not in a zone.",
    EV_RETURN
);
Event EV_Entity_Zone
(
    "zone",
    EV_DEFAULT,
    NULL,
    NULL,
    "Get the current entity zone. NIL if the entity is not in a zone.",
    EV_GETTER
);
Event EV_Entity_IsInZone
(
    "isinzone",
    EV_DEFAULT,
    "e",
    "zone",
    "Returns 1 if the entity is in the specified zone. 0 otherwise",
    EV_RETURN
);
*/
#ifdef OPM_FEATURES
Event EV_Entity_SetHintRequireLookAt
(
    "setuselookat",
    EV_DEFAULT,
    "b",
    "lookat",
    "Sets if the client must look at the entity to use it",
    EV_NORMAL
);
Event EV_Entity_SetHintString
(
    "sethintstring",
    EV_DEFAULT,
    "s",
    "string",
    "Sets the hint string for an entity when touching it. '&&1' will be replaced by the +use key on the client. If "
    "hideent/hide is called on this entity, the hint string won't be shown for the client",
    EV_NORMAL
);
Event EV_Entity_SetShader
(
    "setshader",
    EV_DEFAULT,
    "s",
    "shadername",
    "Sets a shader for the entity. An empty string will revert to the normal entity shader.",
    EV_NORMAL
);
#endif

CLASS_DECLARATION(SimpleEntity, Entity, NULL) {
    {&EV_Damage,                       &Entity::DamageEvent              },
    {&EV_DamageType,                   &Entity::DamageType               },
    {&EV_Kill,                         &Entity::Kill                     },
    {&EV_Killed,                       &Entity::Killed                   },
    {&EV_FadeNoRemove,                 &Entity::FadeNoRemove             },
    {&EV_FadeOut,                      &Entity::FadeOut                  },
    {&EV_FadeIn,                       &Entity::FadeIn                   },
    {&EV_Fade,                         &Entity::Fade                     },
    {&EV_Hide,                         &Entity::EventHideModel           },
    {&EV_Show,                         &Entity::EventShowModel           },
    {&EV_BecomeSolid,                  &Entity::BecomeSolid              },
    {&EV_SafeSolid,                    &Entity::SafeSolid                },
    {&EV_BecomeNonSolid,               &Entity::BecomeNonSolid           },
    {&EV_Ghost,                        &Entity::Ghost                    },
    {&EV_TouchTriggers,                &Entity::TouchTriggersEvent       },
    {&EV_Sound,                        &Entity::Sound                    },
    {&EV_StopSound,                    &Entity::StopSound                },
    {&EV_SetSize,                      &Entity::SetSize                  },
    {&EV_SetMins,                      &Entity::SetMins                  },
    {&EV_SetMaxs,                      &Entity::SetMaxs                  },
    {&EV_GetMins,                      &Entity::GetMins                  },
    {&EV_GetMaxs,                      &Entity::GetMaxs                  },
    {&EV_SetScale,                     &Entity::SetScale                 },
    {&EV_SetScale2,                    &Entity::SetScale                 },
    {&EV_GetScale,                     &Entity::GetScale                 },
    {&EV_SetAlpha,                     &Entity::SetAlpha                 },
    {&EV_SetKillTarget,                &Entity::SetKillTarget            },
    {&EV_SetMass,                      &Entity::SetMassEvent             },
    {&EV_LoopSound,                    &Entity::LoopSound                },
    {&EV_StopLoopSound,                &Entity::StopLoopSound            },
    {&EV_Model,                        &Entity::SetModelEvent            },
    {&EV_SetModel,                     &Entity::SetModelEvent            },
    {&EV_GetModel,                     &Entity::GetModelEvent            },
    {&EV_GetBrushModel,                &Entity::GetBrushModelEvent       },
    {&EV_SetLight,                     &Entity::SetLight                 },
    {&EV_LightOn,                      &Entity::LightOn                  },
    {&EV_LightOff,                     &Entity::LightOff                 },
    {&EV_LightRed,                     &Entity::LightRed                 },
    {&EV_LightGreen,                   &Entity::LightGreen               },
    {&EV_LightBlue,                    &Entity::LightBlue                },
    {&EV_LightRadius,                  &Entity::LightRadius              },
    {&EV_LightStyle,                   &Entity::LightStyle               },
    {&EV_EntityFlags,                  &Entity::Flags                    },
    {&EV_EntityEffects,                &Entity::Effects                  },
    {&EV_EntitySVFlags,                &Entity::SVFlags                  },
    {&EV_EntityRenderEffects,          &Entity::RenderEffects            },
    {&EV_BroadcastAIEvent,             &Entity::BroadcastAIEvent         },
    {&EV_SurfaceModelEvent,            &Entity::SurfaceModelEvent        },
    {&EV_Attach,                       &Entity::AttachEvent              },
    {&EV_AttachModel,                  &Entity::AttachModelEvent         },
    {&EV_RemoveAttachedModel,          &Entity::RemoveAttachedModelEvent },
    {&EV_AttachedModelAnim,            &Entity::AttachedModelAnimEvent   },
    {&EV_Detach,                       &Entity::DetachEvent              },
    {&EV_TakeDamage,                   &Entity::TakeDamageEvent          },
    {&EV_NoDamage,                     &Entity::NoDamageEvent            },
    {&EV_Gravity,                      &Entity::Gravity                  },
    {&EV_UseBoundingBox,               &Entity::UseBoundingBoxEvent      },
    {&EV_Hurt,                         &Entity::HurtEvent                },
    {&EV_Heal,                         &Entity::HealEvent                },
    {&EV_Classname,                    &Entity::ClassnameEvent           },
    {&EV_SoundDone,                    &Entity::EventSoundDone           },
    {&EV_SpawnFlags,                   &Entity::SpawnFlagsEvent          },
    {&EV_SetTeam,                      &Entity::SetTeamEvent             },
    {&EV_Trigger,                      &Entity::TriggerEvent             },
    {&EV_Censor,                       &Entity::Censor                   },
    {&EV_Stationary,                   &Entity::StationaryEvent          },
    {&EV_Toss,                         &Entity::TossEvent                },
    {&EV_Explosion,                    &Entity::Explosion                },
    {&EV_ShaderEvent,                  &Entity::Shader                   },
    {&EV_ScriptShaderEvent,            &Entity::Shader                   },
    {&EV_KillAttach,                   &Entity::KillAttach               },
    {&EV_DropToFloor,                  &Entity::DropToFloorEvent         },
    {&EV_Glue,                         &Entity::GlueEvent                },
    {&EV_DuckableGlue,                 &Entity::DuckableGlueEvent        },
    {&EV_Unglue,                       &Entity::EventUnglue              },
    {&EV_Bind,                         &Entity::BindEvent                },
    {&EV_Unbind,                       &Entity::EventUnbind              },
    {&EV_MakeClayPidgeon,              &Entity::MakeClayPidgeon          },
    {&EV_JoinTeam,                     &Entity::JoinTeam                 },
    {&EV_QuitTeam,                     &Entity::EventQuitTeam            },
    {&EV_AddToSoundManager,            &Entity::AddToSoundManager        },
    {&EV_SetControllerAngles,          &Entity::SetControllerAngles      },
    {&EV_GetControllerAngles,          &Entity::GetControllerAngles      },
    {&EV_GetTagPosition,               &Entity::GetTagPosition           },
    {&EV_GetTagAngles,                 &Entity::GetTagAngles             },
    {&EV_DeathSinkStart,               &Entity::DeathSinkStart           },
    {&EV_DeathSink,                    &Entity::DeathSink                },
    {&EV_DetachAllChildren,            &Entity::DetachAllChildren        },
    {&EV_Entity_MovementStealth,       &Entity::EventMovementStealth     },
    {&EV_Pusher,                       &Entity::PusherEvent              },
    {&EV_AlwaysDraw,                   &Entity::AlwaysDraw               },
    {&EV_NeverDraw,                    &Entity::NeverDraw                },
    {&EV_NormalDraw,                   &Entity::NormalDraw               },
    {&EV_IsTouching,                   &Entity::IsTouching               },
    {&EV_IsInside,                     &Entity::IsInside                 },
    {&EV_CanSee,                       &Entity::CanSee                   },
    {&EV_Entity_InPVS,                 &Entity::EventInPVS               },
    {&EV_SetShaderData,                &Entity::SetShaderData            },
    {&EV_GetVelocity,                  &Entity::GetVelocity              },
    {&EV_SetVelocity,                  &Entity::SetVelocity              },
    {&EV_GetAVelocity,                 &Entity::GetAVelocity             },
    {&EV_ForceActivate,                &Entity::ForceActivate            },
    {&EV_ScriptThread_Trace,           &Entity::EventTrace               },
    {&EV_ScriptThread_SightTrace,      &Entity::EventSightTrace          },
    {&EV_Entity_Revive,                &Entity::EventRevive              },
    {&EV_SetHealth,                    &Entity::SetHealth                },
    {&EV_SetHealth2,                   &Entity::SetHealth                },
    {&EV_Entity_GetHealth,             &Entity::GetHealth                },
    {&EV_Entity_GetMaxHealth,          &Entity::EventGetMaxHealth        },
    {&EV_Entity_SetMaxHealth,          &Entity::EventSetMaxHealth        },
    {&EV_Entity_SetMaxHealth2,         &Entity::EventSetMaxHealth        },
    {&EV_Entity_SetHealthOnly,         &Entity::EventSetHealthOnly       },
    {&EV_Entity_SetHealthOnly2,        &Entity::EventSetHealthOnly       },
    {&EV_Entity_GetYaw,                &Entity::GetYaw                   },
    {&EV_ConnectPaths,                 &Entity::EventConnectPaths        },
    {&EV_DisconnectPaths,              &Entity::EventDisconnectPaths     },
    {&EV_Remove,                       &Entity::Remove                   },
    {&EV_Delete,                       &Listener::ScriptRemove           },
    {&EV_ScriptRemove,                 &Listener::ScriptRemove           },
    {&EV_VolumeDamage,                 &Entity::EventVolumeDamage        },
    {&EV_GetNormalHealth,              &Entity::EventGetNormalHealth     },
    {&EV_NormalDamage,                 &Entity::EventNormalDamage        },
    {&EV_Entity_AddImmunity,           &Entity::AddImmunity              },
    {&EV_Entity_RemoveImmunity,        &Entity::RemoveImmunity           },
    {&EV_GetEntnum,                    &Entity::EventGetEntnum           },
    {&EV_GetClassname,                 &Entity::EventGetClassname        },
    {&EV_Entity_SetRadnum,             &Entity::EventSetRadnum           },
    {&EV_Entity_GetRadnum,             &Entity::EventGetRadnum           },
    {&EV_Entity_SetRotatedBbox,        &Entity::EventSetRotatedBbox      },
    {&EV_Entity_GetRotatedBbox,        &Entity::EventGetRotatedBbox      },
    {&EV_Entity_SinglePlayerCommand,   &Entity::EventSinglePlayerCommand },
    {&EV_Entity_MultiPlayerCommand,    &Entity::EventMultiPlayerCommand  },
    {&EV_Entity_RealismModeCommand,    &Entity::EventRealismModeCommand  },
    {&EV_Entity_SPRealismModeCommand,  &Entity::EventSPRealismModeCommand},
    {&EV_Entity_DMRealismModeCommand,  &Entity::EventDMRealismModeCommand},
    {&EV_Entity_GetLocalYawFromVector, &Entity::GetLocalYawFromVector    },
    {&EV_ShootableOnly,                &Entity::EventShootableOnly       },
    {&EV_SetShaderTime,                &Entity::SetShaderTime            },
    {&EV_NoTarget,                     &Entity::NoTarget                 },
    /*
    {&EV_Entity_GetZone,               &Entity::GetZone                  },
    {&EV_Entity_Zone,                  &Entity::GetZone                  },
    {&EV_Entity_IsInZone,              &Entity::IsInZone                 },
    */
    {&EV_Entity_SetDHack,              &Entity::SetDepthHack             },
#ifdef OPM_FEATURES
    {&EV_Entity_SetHintRequireLookAt,  &Entity::SetHintRequireLookAt     },
    {&EV_Entity_SetHintString,         &Entity::SetHintString            },
    {&EV_Entity_SetShader,             &Entity::SetShader                },
#endif
    {NULL,                             NULL                              }
};

Entity::Entity()
{
    int i;

    edict  = level.AllocEdict(this);
    client = edict->client;
    entnum = edict->s.number;
    radnum = -1;

    entflags |= ECF_ENTITY;

    AddWaitTill(STRING_DAMAGE);
    AddWaitTill(STRING_SPAWN);
    AddWaitTill(STRING_SOUNDDONE);

    if (LoadingSavegame) {
        return;
    }

    edict->tiki = NULL;

    // spawning variables
    spawnflags       = level.spawnflags;
    level.spawnflags = 0;

    // rendering variables
    setAlpha(1.0f);
    setScale(1.0f);

    // physics variables
    mass            = 0;
    gravity         = 1.0;
    groundentity    = NULL;
    groundcontents  = 0;
    velocity        = vec_zero;
    avelocity       = vec_zero;
    edict->clipmask = MASK_SOLID;

    m_iNumGlues   = 0;
    m_bGlueAngles = true;
    m_pGlueMaster = NULL;

    // team variables
    teamchain  = NULL;
    teammaster = NULL;

    // bind variables
    bindmaster = NULL;

    // this is an generic entity
    edict->s.eType = ET_GENERAL;

    setContents(0);

    edict->s.parent   = ENTITYNUM_NONE;
    edict->r.ownerNum = ENTITYNUM_NONE;

    // model binding variables
    numchildren = 0;

    for (i = 0; i < MAX_MODEL_CHILDREN; i++) {
        children[i] = ENTITYNUM_NONE;
    }

    for (int i = 0; i < MAX_GLUE_CHILDREN; i++) {
        m_pGluesFlags[i] = 0;
    }

    setOrigin(vec_zero);
    origin.copyTo(edict->s.origin2);

    setAngles(vec_zero);

    setMoveType(MOVETYPE_NONE);
    setSolidType(SOLID_NOT);

    // Character state
    health     = 0;
    max_health = 0;
    deadflag   = DEAD_NO;
    flags      = 0;

    // underwater variables
    watertype  = 0;
    waterlevel = 0;

    // Pain and damage variables
    takedamage           = DAMAGE_NO;
    enemy                = NULL;
    pain_finished        = 0;
    damage_debounce_time = 0;
    damage_type          = -1;

    detach_at_death = qtrue;

    // Light variables
    lightRadius = 0;

    stealthMovementScale = 1.0f;
    m_iNumBlockedPaths   = 0;
    m_BlockedPaths       = NULL;

#ifdef OPM_FEATURES
    m_bHintRequiresLookAt = true;
#endif
}

Entity::~Entity()
{
    Container<Entity *> bindlist;
    Container<Entity *> gluelist;
    Entity             *ent;
    int                 num;
    int                 i;

    // unbind any entities that are bound to me
    // can't unbind within this loop, so make an array
    // and unbind them outside of it.
    num = 0;
    for (ent = teamchain; ent; ent = ent->teamchain) {
        if (ent->bindmaster == this) {
            bindlist.AddObject(ent);
        }
    }

    num = bindlist.NumObjects();
    for (i = 1; i <= num; i++) {
        bindlist.ObjectAt(i)->unbind();
    }

    // detach all glued entities
    num = 0;
    for (i = 0; i < m_iNumGlues; i++) {
        if (m_pGlues[i] && m_pGlues[i]->m_pGlueMaster == this) {
            gluelist.AddObject(m_pGlues[i]);
        }
    }

    num = gluelist.NumObjects();
    for (i = 1; i <= num; i++) {
        gluelist.ObjectAt(i)->unglue();
    }

    bindlist.FreeObjectList();
    gluelist.FreeObjectList();

    unbind();
    quitTeam();

    unglue();
    detach();

    //
    // go through and set our children
    //
    num = numchildren;
    for (i = 0; (i < MAX_MODEL_CHILDREN) && num; i++) {
        if (children[i] == ENTITYNUM_NONE) {
            continue;
        }
        ent = G_GetEntity(children[i]);
        if (ent) {
            ent->PostEvent(EV_Remove, 0);
        }
        num--;
    }

    if (m_BlockedPaths) {
        delete[] m_BlockedPaths;
        m_BlockedPaths = NULL;
    }

    level.FreeEdict(edict);

    entflags &= ~ECF_ENTITY;
}

void Entity::ClassnameEvent(Event *ev)
{
    strncpy(edict->entname, ev->GetString(1), sizeof(edict->entname) - 1);
}

void Entity::SpawnFlagsEvent(Event *ev)
{
    // spawning variables
    spawnflags = ev->GetInteger(1);
    if (spawnflags & SPAWNFLAG_DETAIL) {
        edict->s.renderfx |= RF_DETAIL;
    }
}

void Entity::EventRevive(Event *ev)
{
    float multiplier = 1.0f;

    if (!ev->IsFromScript() && IsSubclassOfSentient()) {
        Sentient *pThis = static_cast<Sentient *>(this);

        if (pThis->m_Team == TEAM_AMERICAN) {
            switch (skill->integer) {
            case 0:
                multiplier = 1.3f;
                break;
            case 1:
                multiplier = 1.0f;
                break;
            case 2:
                multiplier = 0.7f;
                break;
            }
        } else {
            switch (skill->integer) {
            case 0:
                multiplier = 0.7f;
                break;
            case 1:
                multiplier = 1.0f;
                break;
            case 2:
                multiplier = 1.3f;
                break;
            }
        }
    }

    health = ev->GetFloat(1) * multiplier;
    if (health <= 0.f) {
        throw ScriptException("health must be greated than 0");
    }

    deadflag = DEAD_NO;
}

void Entity::SetHealth(Event *ev)
{
    if (IsDead()) {
        ScriptError("cannot give health to dead entities");
    }

    health = ev->GetFloat(1);

    if (health <= 0) {
        ScriptError("health must be greater than 0");
    }

    max_health = health;
}

void Entity::GetHealth(Event *ev)
{
    ev->AddFloat(health);
}

void Entity::EventSetMaxHealth(Event *ev)
{
    max_health = ev->GetFloat(1);
    if (max_health <= 0.0f) {
        ScriptError("max_health must be greater than 0");
    }

    if (health > max_health) {
        health = max_health;
    }
}

void Entity::EventGetMaxHealth(Event *ev)
{
    ev->AddFloat(max_health);
}

void Entity::EventSetHealthOnly(Event *ev)
{
    if (IsDead()) {
        ScriptError("cannot give health to dead entities");
    }

    health = ev->GetFloat(1);
    if (health <= 0.0f) {
        ScriptError("health must be greater than 0");
    }

    if (health > max_health) {
        health = max_health;
    }
}

void Entity::GetYaw(Event *ev)
{
    ev->AddFloat(angles[1]);
}

void Entity::SetKillTarget(const char *text)

{
    if (text) {
        killtarget = text;
    } else {
        killtarget = "";
    }
}

const char *Entity::getModel() const
{
    return gi.TIKI_NameForNum(edict->tiki);
}

void Entity::setModel(const str& mdl)
{
    int animnum;
    int i;

    if (mdl != "" && mdl[0] == '*') {
        model = mdl;

        gi.SetBrushModel(edict, mdl);
        if ((edict->solid == SOLID_BSP) && !edict->s.modelindex) {
            const char* name;

            name = getClassID();
            if (!name) {
                name = getClassname();
            }
            gi.DPrintf("%s with SOLID_BSP and no model - '%s'(%d)\n", name, targetname.c_str(), entnum);

            // Make it non-solid so that the collision code doesn't kick us out.
            setSolidType(SOLID_NOT);
        }

        SetSize();
        return;
    }

    model = CanonicalTikiName(mdl);

    if (!setModel()) {
        Com_Printf("^~^~^ Bad model name '%s'\n", mdl.c_str());
        return;
    }

    mins = vec_zero;
    maxs = vec_zero;

    // make sure to clear all frame infos when switching models
    // so the entity doesn't end up with 'broken' animations
    for (i = 0; i < MAX_FRAMEINFOS; i++) {
        ClearAnimSlot(i);
    }

    if (mdl == "") {
        SetSize();
        return;
    }

    // search for the default idle animation
    animnum = gi.Anim_NumForName(edict->tiki, "idle");
    if (animnum < 0) {
        animnum = 0;
    }

    // play the default idle animation
    StartAnimSlot(0, animnum, 1);
    // process all TIKI init commands
    ProcessInitCommands();
    SetSize();

    if (edict->tiki && !mins.length() && !maxs.length()) {
        vec3_t tempmins, tempmaxs;
        gi.TIKI_CalculateBounds(edict->tiki, edict->s.scale, tempmins, tempmaxs);
        setSize(tempmins, tempmaxs);
    }
}

qboolean Entity::setModel(void)
{
    level.skel_index[edict->s.number] = -1;
    return gi.setmodel(edict, model);
}

void Entity::SetSize(void)
{
    mins = edict->r.mins;
    maxs = edict->r.maxs;

    size = maxs - mins;

    edict->r.radius = size.length() * 0.5;
    edict->radius2  = edict->r.radius * edict->r.radius;
}

void Entity::ProcessInitCommands(void)
{
    dtikianim_t *a;

    if (LoadingSavegame) {
        // Don't process init commands when loading a savegame since
        // it will cause items to be added to inventories unnecessarily.
        // All variables affected by the init commands will be set
        // by the unarchive functions.
        //
        // we do want to process the cache commands though regardless
        return;
    }

    a = edict->tiki->a;

    if (a->num_server_initcmds) {
        int    i, j;
        Event *event;
        for (i = 0; i < a->num_server_initcmds; i++) {
            event = new Event(a->server_initcmds[i].args[0], a->server_initcmds[i].num_args);

            for (j = 1; j < a->server_initcmds[i].num_args; j++) {
                event->AddToken(a->server_initcmds[i].args[j]);
            }

            if (!ProcessEvent(event)) {
                Com_Printf(
                    "^~^~^ Entity::ProcessInitCommands: Bad init server command '%s' in '%s'\n",
                    a->server_initcmds[i].args[0],
                    edict->tiki->name
                );
            }
        }
    }
}

void Entity::EventHideModel(Event *ev)

{
    hideModel();
}

void Entity::EventShowModel(Event *ev)

{
    showModel();
}

void Entity::SetTeamEvent(Event *ev)

{
    moveteam = ev->GetString(1);
}

void Entity::TriggerEvent(Event *ev)
{
    SimpleEntity *ent;
    ScriptVariable arrayVar;
    int i;

    arrayVar = ev->GetValue(1);
    arrayVar.CastConstArrayValue();

    for (i = arrayVar.arraysize(); i > 0; i--) {
        const ScriptVariable *variable = arrayVar[i];
        ent = variable->simpleEntityValue();
        if (ent) {
            Event* event = new Event(EV_Activate);
            event->AddEntity(this);
            ent->ProcessEvent(event);
        }
    }
}

void Entity::setAlpha(float alpha)

{
    if (alpha > 1.0f) {
        alpha = 1.0f;
    }
    if (alpha < 0) {
        alpha = 0;
    }
    edict->s.alpha = alpha;
}

void Entity::setScale(float scale)
{
    edict->s.scale = scale;
}

void Entity::setContentsSolid(void)
{
    setContents(CONTENTS_BBOX);
}

void Entity::setSolidType(solid_t type)
{
    if ((!LoadingSavegame) && (type == SOLID_BSP) && (this != world)
        && (!model.length() || ((model[0] != '*') && (!strstr(model.c_str(), ".bsp"))))) {
        gi.Printf(
            "setSolidType", "SOLID_BSP entity at x%.2f y%.2f z%.2f with no BSP model", origin[0], origin[1], origin[2]
        );
    }
    edict->solid = type;

    //
    // set the appropriate contents type
    if (edict->solid == SOLID_BBOX) {
        if (!getContents()) {
            setContentsSolid();
        }
    } else if (edict->solid == SOLID_NOT) {
        if (getContents()
            & (CONTENTS_CORPSE | CONTENTS_BODY | CONTENTS_UNKNOWN3 | CONTENTS_UNKNOWN2 | CONTENTS_NOBOTCLIP
               | CONTENTS_BBOX | CONTENTS_NOTTEAM2 | CONTENTS_NOTTEAM1 | CONTENTS_LADDER)) {
            setContents(0);
        }
    } else if (edict->solid == SOLID_BSP) {
        if (!getContents()) {
            setContents(CONTENTS_SOLID);
        }
    }

    link();
}

void Entity::setSize(Vector min, Vector max)
{
    Vector delta;

    if (flags & FL_ROTATEDBOUNDS) {
        vec3_t tempmins, tempmaxs;

        //
        // rotate the mins and maxs for the model
        //
        min.copyTo(tempmins);
        max.copyTo(tempmaxs);

        CalculateRotatedBounds2(edict->mat, tempmins, tempmaxs);

        VectorCopy(tempmins, edict->r.mins);
        VectorCopy(tempmaxs, edict->r.maxs);

        SetSize();
    } else {
        if ((min == edict->r.mins) && (max == edict->r.maxs)) {
            return;
        }

        VectorCopy(min, edict->r.mins);
        VectorCopy(max, edict->r.maxs);

        SetSize();

        //
        // get the full mins and maxs for this model
        //
        if (edict->tiki) {
            vec3_t fullmins, fullmaxs;
            Vector delta;

            gi.TIKI_CalculateBounds(edict->tiki, edict->s.scale, fullmins, fullmaxs);

            delta           = Vector(fullmaxs) - Vector(fullmins);
            edict->r.radius = delta.length() * 0.5;
            edict->radius2  = edict->r.radius * edict->r.radius;
        }
    }

    link();
}

Vector Entity::getLocalVector(Vector vec)

{
    Vector pos;

    pos[0] = vec * orientation[0];
    pos[1] = vec * orientation[1];
    pos[2] = vec * orientation[2];

    return pos;
}

void Entity::link(void)
{
    gi.linkentity(edict);
    absmin   = edict->r.absmin;
    absmax   = edict->r.absmax;
    centroid = (absmin + absmax) * 0.5;
    centroid.copyTo(edict->r.centroid);

    // If this has a parent, then set the areanum the same
    // as the parent's
    if (edict->s.parent != ENTITYNUM_NONE) {
        edict->r.areanum = g_entities[edict->s.parent].r.areanum;
    }
}

void Entity::addOrigin(Vector add)
{
    setLocalOrigin(localorigin + add);
}

void Entity::setOrigin(void)
{
    Entity *ent;

    if (edict->s.parent == ENTITYNUM_NONE) {
        setLocalOrigin(localorigin);
    }
    // If entity has a parent, then set the origin as the
    // centroid of the parent, and set edict->s.netorigin
    // as the local origin of the entity which will be used
    // to position this entity on the client.
    else {
        Vector forward, left, up;

        VectorClear(edict->s.netorigin);
        ent = (Entity *)G_GetEntity(edict->s.parent);

        ent->GetTag(edict->s.tag_num & TAG_MASK, &origin, &forward, &left, &up);

        origin += edict->s.attach_offset[0] * forward;
        origin += edict->s.attach_offset[1] * left;
        origin += edict->s.attach_offset[2] * up;

        localorigin = vec_zero;

        updateOrigin();
    }
}

void Entity::updateOrigin(void)
{
    Entity *ent;
    int     i, num;

    origin.copyTo(edict->s.origin);

    link();

    //
    // go through and set our children
    //
    num = numchildren;
    for (i = 0; (i < MAX_MODEL_CHILDREN) && num; i++) {
        if (children[i] == ENTITYNUM_NONE) {
            continue;
        }
        ent = (Entity *)G_GetEntity(children[i]);
        ent->setOrigin();
        num--;
    }

    for (i = 0; i < m_iNumGlues; i++) {
        m_pGlues[i]->setOrigin();
    }
}

void Entity::setOrigin(Vector org)
{
    if (m_pGlueMaster) {
        org      = m_pGlueMaster->origin;
        velocity = m_pGlueMaster->velocity;
    }

    if (bindmaster) {
        origin = org;

        org -= bindmaster->origin;

        if (bind_use_my_angles) {
            MatrixTransformVectorRight(orientation, org, localorigin);
        } else {
            MatrixTransformVectorRight(bindmaster->orientation, org, localorigin);
        }

        origin.copyTo(edict->s.netorigin);
    } else {
        if (edict->s.parent != ENTITYNUM_NONE) {
            detach();
        }

        origin      = org;
        localorigin = org;
        origin.copyTo(edict->s.netorigin);
    }

    updateOrigin();
}

void Entity::setOriginEvent(Vector org)
{
    velocity = vec_zero;
    setOrigin(org);
}

void Entity::setLocalOrigin(Vector org)
{
    if (m_pGlueMaster) {
        org      = m_pGlueMaster->origin;
        velocity = m_pGlueMaster->velocity;
    }

    if (bindmaster) {
        localorigin = org;

        if (bind_use_my_angles) {
            MatrixTransformVector(localorigin, orientation, origin);
        } else {
            MatrixTransformVector(localorigin, bindmaster->orientation, origin);
        }

        origin += bindmaster->origin;
        origin.copyTo(edict->s.netorigin);
    } else {
        origin      = org;
        localorigin = org;
        origin.copyTo(edict->s.netorigin);
    }

    updateOrigin();
}

void Entity::GetRawTag(int tagnum, orientation_t *orient)
{
    *orient = G_TIKI_Orientation(edict, tagnum & TAG_MASK);
}

qboolean Entity::GetRawTag(const char *name, orientation_t *orient)
{
    int tagnum;

    tagnum = gi.Tag_NumForName(edict->tiki, name);

    if (tagnum < 0) {
        return false;
    }

    GetRawTag(tagnum, orient);
    return true;
}

void Entity::GetTag(int tagnum, orientation_t *orient)
{
    orientation_t o;
    int           i;

    GetRawTag(tagnum, &o);

    VectorCopy(origin, orient->origin);

    for (i = 0; i < 3; i++) {
        VectorMA(orient->origin, o.origin[i], orientation[i], orient->origin);
    }
    MatrixMultiply(o.axis, orientation, orient->axis);
}

qboolean Entity::GetTag(const char *name, orientation_t *orient)
{
    int tagnum;

    tagnum = gi.Tag_NumForName(edict->tiki, name);

    if (tagnum < 0) {
        return false;
    }

    GetTag(tagnum, orient);
    return true;
}

void Entity::GetTag(int tagnum, Vector *pos, Vector *forward, Vector *left, Vector *up)
{
    orientation_t orient;

    GetTag(tagnum, &orient);

    if (pos) {
        *pos = Vector(orient.origin);
    }
    if (forward) {
        *forward = Vector(orient.axis[0]);
    }
    if (left) {
        *left = Vector(orient.axis[1]);
    }
    if (up) {
        *up = Vector(orient.axis[2]);
    }
}

qboolean Entity::GetTag(const char *name, Vector *pos, Vector *forward, Vector *left, Vector *up)
{
    int tagnum;

    tagnum = gi.Tag_NumForName(edict->tiki, name);

    if (tagnum < 0) {
        return false;
    }

    GetTag(tagnum, pos, forward, left, up);
    return true;
}

int Entity::CurrentAnim(int slot) const
{
    return 0;
}

float Entity::CurrentTime(int slot) const
{
    return 0;
}

void Entity::ClearAnimSlot(int slot)
{
    edict->s.frameInfo[slot].index  = 0;
    edict->s.frameInfo[slot].weight = 0;
    edict->s.frameInfo[slot].time   = 0;
}

void Entity::StartAnimSlot(int slot, int index, float weight)
{
    edict->s.frameInfo[slot].index  = index;
    edict->s.frameInfo[slot].weight = weight;
    edict->s.frameInfo[slot].time   = 0;
}

void Entity::RestartAnimSlot(int slot)
{
    edict->s.frameInfo[slot].time = 0;
}

void Entity::addAngles(Vector add)
{
    if (bindmaster) {
        setAngles(localangles + add);
    } else {
        setAngles(angles + add);
    }
}

void Entity::setAngles(void)
{
    if (bindmaster) {
        setAngles(localangles);
    } else {
        setAngles(angles);
    }
}

void Entity::setAngles(Vector ang)
{
    Entity *ent;
    int     num, i;

    if (m_bGlueAngles && m_pGlueMaster) {
        ang       = m_pGlueMaster->angles;
        avelocity = m_pGlueMaster->avelocity;
    }

    angles[0] = AngleMod(ang[0]);
    angles[1] = AngleMod(ang[1]);
    angles[2] = AngleMod(ang[2]);

    localangles = angles;
    if (bindmaster) {
        float mat[3][3];
        AnglesToAxis(localangles, mat);
        R_ConcatRotations(mat, bindmaster->orientation, orientation);
        MatrixToEulerAngles(orientation, angles);
    } else {
        AnglesToAxis(angles, orientation);
    }

    angles.copyTo(edict->s.netangles);
    angles.copyTo(edict->s.angles);

    // Fill the edicts matrix
    VectorCopy(orientation[0], edict->mat[0]);
    VectorCopy(orientation[1], edict->mat[1]);
    VectorCopy(orientation[2], edict->mat[2]);

    if (edict->s.eFlags & EF_LINKANGLES) {
        angles.copyTo(edict->r.currentAngles);
        link();
    }

    //
    // go through and set our children
    //
    num = numchildren;
    for (i = 0; (i < MAX_MODEL_CHILDREN) && num; i++) {
        if (children[i] == ENTITYNUM_NONE) {
            continue;
        }
        ent = (Entity *)G_GetEntity(children[i]);
        ent->setAngles();
        num--;
    }

    for (i = 0; i < m_iNumGlues; i++) {
        m_pGlues[i]->setAngles();
    }
}

qboolean Entity::droptofloor(float maxfall)

{
    trace_t trace;
    Vector  end;
    Vector  start;

    start = origin + Vector("0 0 1");
    end   = origin;
    end[2] -= maxfall;

    trace = G_Trace(start, mins, maxs, end, this, edict->clipmask, false, "Entity::droptofloor");
    if (trace.fraction == 1 || trace.startsolid || trace.allsolid || !trace.ent) {
        return false;
    }

    setOrigin(trace.endpos);

    return true;
}

void Entity::DamageType(Event *ev)

{
    str damage;
    damage = ev->GetString(1);
    if (damage == "all") {
        damage_type = -1;
    } else {
        damage_type = MOD_string_to_int(damage);
    }
}

void Entity::Damage(
    Entity *inflictor,
    Entity *attacker,
    float   damage,
    Vector  position,
    Vector  direction,
    Vector  normal,
    int     knockback,
    int     dflags,
    int     meansofdeath,
    int     location
)
{
    Event *ev;

    // if our damage types do not match, return
    if (!MOD_matches(meansofdeath, damage_type)) {
        return;
    }

    if (!attacker) {
        attacker = world;
    }
    if (!inflictor) {
        inflictor = world;
    }

    ev = new Event(EV_Damage);
    ev->AddEntity(attacker);
    ev->AddFloat(damage);
    ev->AddEntity(inflictor);
    ev->AddVector(position);
    ev->AddVector(direction);
    ev->AddVector(normal);
    ev->AddInteger(knockback);
    ev->AddInteger(dflags);
    ev->AddInteger(meansofdeath);
    ev->AddInteger(location);
    ProcessEvent(ev);
}

void Entity::DamageEvent(Event *ev)
{
    Entity *inflictor;
    Entity *attacker;
    int     damage;
    Vector  dir;
    Vector  momentum;
    Vector  position, direction, normal;
    int     knockback, damageflags, meansofdeath, location;
    Event  *event;
    float   m;
    EntityPtr This;

    // Use a safe pointer because it might be deleted in Unregister
    This = this;

    if ((takedamage == DAMAGE_NO) || (movetype == MOVETYPE_NOCLIP) || health <= 0) {
        return;
    }

    attacker  = ev->GetEntity(1);
    damage    = ev->GetInteger(2);
    inflictor = ev->GetEntity(3);
    meansofdeath = ev->GetInteger(9);

    if (Immune(meansofdeath)) {
        return;
    }

    if (!attacker) {
        ScriptError("attacker is NULL");
        return;
    }

    if (!inflictor) {
        ScriptError("inflictor is NULL");
        return;
    }

    position  = ev->GetVector(4);
    direction = ev->GetVector(5);
    normal    = ev->GetVector(6);

    knockback    = ev->GetInteger(7);
    damageflags  = ev->GetInteger(8);
    location     = ev->GetInteger(10);

    // figure momentum add
    if ((inflictor != world) && (movetype != MOVETYPE_NONE) && (movetype != MOVETYPE_STATIONARY)
        && (movetype != MOVETYPE_BOUNCE) && (movetype != MOVETYPE_PUSH) && (movetype != MOVETYPE_STOP)
        && (movetype != MOVETYPE_VEHICLE)) {
        dir = origin - (inflictor->origin + (inflictor->mins + inflictor->maxs) * 0.5);
        dir.normalize();

        if (mass < 20) {
            m = 20;
        } else {
            m = mass;
        }

        momentum = dir * damage * (1000.0 / m);
        velocity += momentum;
    }

    if (!deathmatch->integer && IsSubclassOfPlayer()) {
        damage *= 0.15;
    }

    // check for godmode or invincibility
    if (!(flags & FL_GODMODE)) {
        // do the damage
        health -= damage;
    }

    /*
    if (deadflag) {
        // Check for gib.
        if (inflictor->IsSubclassOfProjectile()) {
            Event *gibEv;

            health -= damage;

            gibEv = new Event(EV_Gib);
            gibEv->AddEntity(this);
            gibEv->AddFloat(health);

            ProcessEvent(gibEv);
        }

        return;
    }
    */

    if (health <= 0) {
        if (attacker) {
            event = new Event(EV_GotKill);
            event->AddEntity(this);
            event->AddInteger(damage);
            event->AddEntity(inflictor);
            event->AddInteger(meansofdeath);
            event->AddInteger(0);

            attacker->ProcessEvent(event);
        }

        if (!This) {
            return;
        }

        event = new Event(EV_Killed);
        event->AddEntity(attacker);
        event->AddFloat(damage);
        event->AddEntity(inflictor);
        event->AddVector(position);
        event->AddVector(direction);
        event->AddVector(normal);
        event->AddInteger(knockback);
        event->AddInteger(damageflags);
        event->AddInteger(meansofdeath);
        event->AddInteger(location);

        ProcessEvent(event);

        if (!This) {
            return;
        }

        // Notify scripts
        Unregister(STRING_DAMAGE);
        return;
    }

    event = new Event(EV_Pain);
    event->AddEntity(attacker);
    event->AddFloat(damage);
    event->AddEntity(inflictor);
    event->AddVector(position);
    event->AddVector(direction);
    event->AddVector(normal);
    event->AddInteger(knockback);
    event->AddInteger(damageflags);
    event->AddInteger(meansofdeath);
    event->AddInteger(location);

    ProcessEvent(event);

    if (!This) {
        return;
    }

    // Notify scripts
    Unregister(STRING_DAMAGE);
}

qboolean Entity::IsTouching(Entity *e1)

{
    if (e1->absmin.x > absmax.x) {
        return false;
    }
    if (e1->absmin.y > absmax.y) {
        return false;
    }
    if (e1->absmin.z > absmax.z) {
        return false;
    }
    if (e1->absmax.x < absmin.x) {
        return false;
    }
    if (e1->absmax.y < absmin.y) {
        return false;
    }
    if (e1->absmax.z < absmin.z) {
        return false;
    }

    return true;
}

qboolean Entity::IsInside(Entity *e1)
{
    return gi.HitEntity(edict, e1->edict);
}

qboolean Entity::FovCheck(float *delta, float fovdot)
{
    float deltadot = DotProduct2D(delta, orientation[0]);

    if (fovdot < 0.0f) {
        if (deltadot >= 0.0f) {
            return true;
        }

        return VectorLength2DSquared(delta) * Square(fovdot) > Square(deltadot);
    } else {
        if (deltadot < 0.0f) {
            return false;
        }

        return VectorLength2DSquared(delta) * Square(fovdot) < Square(deltadot);
    }
}

bool Entity::CanSee(Entity *ent, float fov, float vision_distance, bool bNoEnts)
{
    vec2_t delta;
    int mask;

    VectorSub2D(ent->centroid, centroid, delta);

    if (vision_distance > 0 && Square(vision_distance) < VectorLength2DSquared(delta)) {
        return false;
    }

    if (!AreasConnected(ent)) {
        return false;
    }

    if (fov > 0 && fov < 360 && !FovCheck(delta, cos(DEG2RAD(fov / 2.f)))) {
        return false;
    }

    if (bNoEnts) {
        mask = MASK_CANSEE_NOENTS;
    } else {
        mask = MASK_CANSEE;
    }

    return G_SightTrace(centroid, vec_zero, vec_zero, ent->centroid, this, ent, mask, qfalse, "Sentient::CanSee");
}

bool Entity::CanSee(const Vector& org, float fov, float vision_distance, bool bNoEnts)
{
    vec2_t delta;
    int mask;

    VectorSub2D(org, centroid, delta);

    if (vision_distance > 0 && Square(vision_distance) < VectorLength2DSquared(delta)) {
        return false;
    }

    if (fov > 0 && fov < 360 && !FovCheck(delta, cos(DEG2RAD(fov / 2.f)))) {
        return false;
    }

    if (bNoEnts) {
        mask = MASK_CANSEE_NOENTS;
    } else {
        mask = MASK_CANSEE;
    }

    return G_SightTrace(centroid, vec_zero, vec_zero, org, this, NULL, mask, qfalse, "Sentient::CanSee");
}

void Entity::FadeNoRemove(Event *ev)

{
    float rate;
    float target;
    float myalpha;

    if (ev->NumArgs() > 1) {
        target = ev->GetFloat(2);
    } else {
        target = 0;
    }

    if (ev->NumArgs() > 0) {
        rate = ev->GetFloat(1);
        assert(rate);
        if (rate > 0) {
            rate = FRAMETIME / rate;
        }
    } else {
        rate = 0.03f;
    }

    myalpha = edict->s.alpha;
    myalpha -= rate;

    if (myalpha < target) {
        myalpha = target;
    }

    setAlpha(myalpha);

    if (myalpha > target) {
        PostEvent(*ev, FRAMETIME);
    }

    G_SetConstantLight(&edict->s.constantLight, &myalpha, &myalpha, &myalpha, 0);
}

void Entity::FadeOut(Event *ev)

{
    float myscale;
    float myalpha;

    myscale = edict->s.scale;
    myscale -= 0.03f;
    myalpha = edict->s.alpha;
    myalpha -= 0.03f;
    if (myscale < 0) {
        myscale = 0;
    }
    if (myalpha < 0) {
        myalpha = 0;
    }

    if (myscale <= 0 && myalpha <= 0) {
        PostEvent(EV_Remove, 0);
    } else {
        PostEvent(*ev, FRAMETIME);
    }

    setScale(myscale);
    setAlpha(myalpha);
}

void Entity::FadeIn(Event *ev)

{
    float rate;
    float target;
    float myalpha;

    if (ev->NumArgs() > 1) {
        target = ev->GetFloat(2);
    } else {
        target = 1;
    }

    if (ev->NumArgs() > 0) {
        rate = ev->GetFloat(1);
        if (rate > 0) {
            rate = FRAMETIME / rate;
        }
    } else {
        rate = 0.03f;
    }

    myalpha = edict->s.alpha;
    myalpha += rate;

    if (myalpha > target) {
        myalpha = target;
    }

    if (myalpha < target) {
        PostEvent(*ev, FRAMETIME);
    }
    setAlpha(myalpha);
}

void Entity::Fade(Event *ev)

{
    float rate;
    float target;
    float myalpha;

    if (ev->NumArgs() > 1) {
        target = ev->GetFloat(2);
    } else {
        target = 0;
    }

    if (ev->NumArgs() > 0) {
        rate = ev->GetFloat(1);
        if (rate > 0) {
            rate = FRAMETIME / rate;
        }
    } else {
        rate = 0.03f;
    }

    myalpha = edict->s.alpha;
    myalpha -= rate;

    if (myalpha <= 0) {
        PostEvent(EV_Remove, 0);
        return;
    }

    if (myalpha < target) {
        myalpha = target;
    }

    if (myalpha > target) {
        PostEvent(*ev, FRAMETIME);
    }

    setAlpha(myalpha);
    G_SetConstantLight(&edict->s.constantLight, &myalpha, &myalpha, &myalpha, 0);
}

void Entity::Sink(Event *ev)
{
    float  rate;
    float  target;
    float  myalpha;
    Vector org;

    rate = 0.3f;

    if (ev->NumArgs() > 1) {
        target = ev->GetFloat(2);
    } else {
        target = 0;
    }

    if (ev->NumArgs() > 0) {
        rate = ev->GetFloat(1);
        if (rate > 0) {
            rate /= level.frametime;
        }
    }

    myalpha = edict->s.alpha - rate;
    if (myalpha > 0) {
        if (myalpha < target) {
            myalpha = target;
        }

        if (target < myalpha) {
            Event *event = new Event(std::move(*ev));
            PostEvent(event, FRAMETIME);
        }

        setAlpha(myalpha);

        org = origin;
        setOrigin(org);

        Com_Printf("This is the sink?!? alpha: %f origin: %f %f %f\n", myalpha, org.x, org.y, org.z);
        G_SetConstantLight(&edict->s.constantLight, &myalpha, &myalpha, &myalpha, NULL, NULL);
        edict->s.alpha = myalpha;
    } else {
        // No alpha, the entity can be immediately removed
        PostEvent(EV_Remove, 0);
    }
}

void Entity::SetMassEvent(Event *ev)

{
    mass = ev->GetFloat(1);
}

void Entity::CheckGround(void)
{
    Vector  point;
    trace_t trace;

    if (flags & (FL_SWIM | FL_FLY)) {
        return;
    }

    if (velocity.z > 100) {
        groundentity = NULL;
        return;
    }

    // if the hull point one-quarter unit down is solid the entity is on ground
    point = origin;
    point.z -= 0.25;
    trace = G_Trace(origin, mins, maxs, point, this, edict->clipmask, false, "Entity::CheckGround");

    // check steepness
    if ((trace.plane.normal[2] <= 0.7) && !trace.startsolid) {
        groundentity = NULL;
        return;
    }

    groundentity   = trace.ent;
    groundplane    = trace.plane;
    groundcontents = trace.contents;

    if (!trace.startsolid && !trace.allsolid) {
        setOrigin(trace.endpos);
        velocity.z = 0;
    }
}

void Entity::SafeSolid(Event *ev)
{
    trace_t trace;

    setSolidType(SOLID_NOT);

    trace = G_Trace(origin, mins, maxs, origin, this, MASK_SAFESOLID, qfalse, "Entity::SafeSolid");

    if ((trace.startsolid || trace.allsolid) && trace.ent && (trace.ent->entity->IsSubclassOfSentient())) {
        // Try again
        PostEvent(EV_SafeSolid, 0.5f);
    } else {
        ProcessEvent(EV_BecomeSolid);
    }
}

void Entity::BecomeSolid(Event *ev)
{
    if ((model.length()) && ((model[0] == '*') || (strstr(model.c_str(), ".bsp")))) {
        setSolidType(SOLID_BSP);
    } else {
        setSolidType(SOLID_BBOX);
    }
}

void Entity::BecomeNonSolid(Event *ev)
{
    setSolidType(SOLID_NOT);
}

void Entity::Ghost(Event *ev)

{
    // Make not solid, but send still send over whether it is hidden or not
    setSolidType(SOLID_NOT);
    edict->r.svFlags &= ~SVF_NOCLIENT;
}

void Entity::LoopSound(Event *ev)
{
    str   sound_name;
    float volume   = DEFAULT_VOL;
    float min_dist = DEFAULT_MIN_DIST;
    str   min_dist_string;

    if (ev->NumArgs() < 1) {
        return;
    }

    if (level.spawning) {
        // If the server is currently spawning
        // repost the event as loopsound wouldn't work properly
        // on the client
        Event* newev = new Event(*ev);
        PostEvent(newev, level.frametime);
        return;
    }

    // Get parameters

    sound_name = ev->GetString(1);

    if (ev->NumArgs() > 1) {
        volume = ev->GetFloat(2);
    }

    if (ev->NumArgs() > 2) {
        min_dist_string = ev->GetString(3);

        if (min_dist_string == LEVEL_WIDE_STRING) {
            min_dist = LEVEL_WIDE_MIN_DIST;
        } else {
            min_dist = ev->GetFloat(3);
        }
    }

    // Add this sound to loop

    LoopSound(sound_name.c_str(), volume, min_dist);
}

void Entity::LoopSound(str sound_name, float volume, float min_dist, float max_dist, float pitch)
{
    str              name;
    str              random_alias;
    AliasListNode_t *ret = NULL;

    // Get the real sound to be played

    if (sound_name.length() > 0) {
        // Get the real sound to play

        name = GetRandomAlias(sound_name, &ret).c_str();

        if (!name) {
            name = sound_name.c_str();
        }

        if (name.length() && ret) {
            int   aliaschannel  = ret->channel;
            float aliasvolume   = G_Random() * ret->volumeMod + ret->volume;
            float aliasmin_dist = ret->dist;
            float aliasmax_dist = ret->maxDist;
            float aliaspitch    = G_Random() * ret->pitchMod + ret->pitch;
            int   aliasstreamed = ret->streamed;
            // Add the looping sound to the entity

            edict->s.loopSound        = gi.soundindex(name, aliasstreamed);
            edict->s.loopSoundVolume  = volume < 0 ? (aliasvolume) : (aliasvolume * volume);
            edict->s.loopSoundMinDist = min_dist < 0 ? (aliasmin_dist) : (min_dist);
            edict->s.loopSoundMaxDist = max_dist < 0 ? (aliasmax_dist) : (max_dist);
            edict->s.loopSoundPitch   = pitch < 0 ? (aliaspitch) : (aliaspitch * pitch);

            // Local sound will always be heard
            edict->s.loopSoundFlags = aliaschannel == CHAN_LOCAL;
        } else {
            gi.DPrintf(
                "ERROR: Entity::LoopSound: %s needs an alias in ubersound.scr or uberdialog.scr - Please fix.\n",
                sound_name.c_str()
            );
        }
    }
}

void Entity::StopLoopSound(Event *ev)
{
    StopLoopSound();
}

void Entity::StopLoopSound(void)
{
    edict->s.loopSound = 0;
}

void Entity::ProcessSoundEvent(Event *ev, qboolean checkSubtitle)
{
    str   sound_name;
    str   wait;
    bool  waitTillDone = false;
    float volume;
    int   channel = 0;

    // Set defaults
    volume = DEFAULT_VOL;

    if (ev->NumArgs() > 3) {
        Com_Printf("ERROR Sound.  Format is playsound soundname <wait>\n");
        return;
    }

    // Get sound parameters

    sound_name = ev->GetString(1);

    if (ev->NumArgs() > 1) {
        wait = ev->GetString(2);
    }

    if (!Q_stricmp(wait.c_str(), "wait")) {
        waitTillDone = true;

        if (ev->NumArgs() == 3) {
            volume = ev->GetFloat(3);
        }
    } else if (!Q_stricmp(wait.c_str(), "volume")) {
        if (ev->NumArgs() != 3) {
            ScriptError("Entity::Sound: volume not followed by a float");
        }

        volume = ev->GetFloat(3);
    }

    if (volume == DEFAULT_VOL) {
        Sound(sound_name, CHAN_AUTO, volume, 0, NULL, 0, 0, waitTillDone, checkSubtitle, -1.0f);
    } else {
        Sound(sound_name, -1, volume, -1.0f, NULL, -1.0f, 1, waitTillDone, checkSubtitle, -1.0f);
    }
}

void Entity::Sound(Event *ev)
{
    if (level.spawning) {
        Event *event = new Event(EV_Sound);

        for (int i = 1; i <= ev->NumArgs(); i++) {
            event->AddValue(ev->GetValue(i));
        }

        PostEvent(event, level.frametime);
    } else {
        ProcessSoundEvent(ev, true);
    }
}

void Entity::StopSound(Event *ev)

{
    if (ev->NumArgs() < 1) {
        StopSound(CHAN_BODY);
    } else {
        StopSound(ev->GetInteger(1));
    }
}

void Entity::StopSound(int channel)

{
    gi.StopSound(entnum, channel);
}

void Entity::SetLight(Event *ev)

{
    float r, g, b;

    if (ev->NumArgs() == 1) {
        Vector tmp;

        tmp = ev->GetVector(1);
        r   = tmp.x;
        g   = tmp.y;
        b   = tmp.z;
    } else {
        r           = ev->GetFloat(1);
        g           = ev->GetFloat(2);
        b           = ev->GetFloat(3);
        lightRadius = ev->GetFloat(4);
    }

    G_SetConstantLight(&edict->s.constantLight, &r, &g, &b, &lightRadius);
}

void Entity::LightOn(Event *ev)

{
    G_SetConstantLight(&edict->s.constantLight, NULL, NULL, NULL, &lightRadius);
}

void Entity::LightOff(Event *ev)

{
    float radius = 0;

    G_SetConstantLight(&edict->s.constantLight, NULL, NULL, NULL, &radius);
}

void Entity::LightRed(Event *ev)

{
    float r;

    r = ev->GetFloat(1);
    G_SetConstantLight(&edict->s.constantLight, &r, NULL, NULL, NULL);
}

void Entity::LightGreen(Event *ev)

{
    float g;

    g = ev->GetFloat(1);
    G_SetConstantLight(&edict->s.constantLight, NULL, &g, NULL, NULL);
}

void Entity::LightBlue(Event *ev)

{
    float b;

    b = ev->GetFloat(1);
    G_SetConstantLight(&edict->s.constantLight, NULL, NULL, &b, NULL);
}

void Entity::LightRadius(Event *ev)

{
    lightRadius = ev->GetFloat(1);
    G_SetConstantLight(&edict->s.constantLight, NULL, NULL, NULL, &lightRadius);
}

void Entity::LightStyle(Event *ev)

{
    int style;

    style = ev->GetInteger(1);
    G_SetConstantLight(&edict->s.constantLight, NULL, NULL, NULL, NULL, &style);
}

void Entity::SetSize(Event *ev)
{
    Vector min, max;

    min = ev->GetVector(1);
    max = ev->GetVector(2);
    setSize(min, max);
}

void Entity::SetMins(Event *ev)
{
    Vector min;

    min = ev->GetVector(1);
    setSize(min, maxs);
}

void Entity::SetMaxs(Event *ev)
{
    Vector max;

    max = ev->GetVector(1);
    setSize(mins, max);
}

void Entity::GetMins(Event *ev)
{
    ev->AddVector(mins);
}

void Entity::GetMaxs(Event *ev)
{
    ev->AddVector(maxs);
}

void Entity::SetScale(Event *ev)
{
    setScale(ev->GetFloat(1));
}

void Entity::GetScale(Event *ev)
{
    ev->AddFloat(edict->s.scale);
}

void Entity::SetAlpha(Event *ev)

{
    setAlpha(ev->GetFloat(1));
}

void Entity::SetKillTarget(Event *ev)

{
    SetKillTarget(ev->GetString(1));
}

Vector Entity::GetControllerAngles(int num)
{
    Vector controller_angles;

    assert((num >= 0) && (num < NUM_BONE_CONTROLLERS));

    if ((num < 0) || (num >= NUM_BONE_CONTROLLERS)) {
        gi.Printf("GetControllerAngles", "Bone controller index out of range (%d)\n", num);
        return vec_zero;
    }

    controller_angles = edict->s.bone_angles[num];

    return controller_angles;
}

void Entity::SetControllerAngles(int num, vec3_t angles)
{
    assert((num >= 0) && (num < NUM_BONE_CONTROLLERS));
    if ((num < 0) || (num >= NUM_BONE_CONTROLLERS)) {
        gi.Printf("SetControllerAngles", "Bone controller index out of range (%d)\n", num);
        return;
    }

    VectorCopy(angles, edict->s.bone_angles[num]);
    EulerToQuat(edict->s.bone_angles[num], edict->s.bone_quat[num]);
}

void Entity::SetControllerAngles(Event *ev)
{
    int    num;
    Vector angles;

    num    = ev->GetInteger(1);
    angles = ev->GetVector(2);

    // this check is missing in mohaa
    if ((num < 0) || (num >= NUM_BONE_CONTROLLERS)) {
        ScriptError("Bone controller index out of range. Index must be between 0-" STRING(NUM_BONE_CONTROLLERS - 1) "."
        );
    }

    VectorCopy(angles, edict->s.bone_angles[num]);
    EulerToQuat(edict->s.bone_angles[num], edict->s.bone_quat[num]);
}

void Entity::GetControllerAngles(Event *ev)
{
    int num = ev->GetInteger(1);

    // this check is missing in mohaa
    if ((num < 0) || (num >= NUM_BONE_CONTROLLERS)) {
        ScriptError("Bone controller index out of range. Index must be between 0-" STRING(NUM_BONE_CONTROLLERS - 1) "."
        );
    }

    ev->AddVector(edict->s.bone_angles[num]);
}

void Entity::SetControllerTag(int num, int tag_num)
{
    assert((num >= 0) && (num < NUM_BONE_CONTROLLERS));

    if ((num < 0) || (num >= NUM_BONE_CONTROLLERS)) {
        gi.Printf("SetControllerTag", "Bone controller index out of range (%d)\n", num);
        return;
    }

    edict->s.bone_tag[num] = tag_num;
}

void Entity::Sound(
    str     sound_name,
    int     channel,
    float   volume,
    float   min_dist,
    Vector *sound_origin,
    float   pitch,
    int     argstype,
    int     doCallback,
    int     checkSubtitle,
    float   max_dist
)
{
    const char      *name = NULL;
    vec3_t           org;
    str              random_alias;
    AliasListNode_t *ret        = NULL;
    static cvar_t   *g_subtitle = NULL;
    static bool      _tmp       = false;
    int              num;
    int              aliaschannel;
    float            aliasvolume;
    float            aliasmin_dist;
    float            aliasmax_dist;
    float            aliaspitch;

    if (!_tmp) {
        g_subtitle = gi.Cvar_Get("g_subtitle", "0", CVAR_ARCHIVE);
        _tmp       = true;
    }

    if (doCallback && g_gametype->integer != GT_SINGLE_PLAYER) {
        Com_Printf("^~^~^ Callback of sound '%s' ignored.\n", sound_name.c_str());
        doCallback = 0;
    }

    if (sound_name.length() > 0) {
        // Get the real sound to play

        random_alias = GetRandomAlias(sound_name, &ret).c_str();

        if (random_alias.length() > 0) {
            name = random_alias.c_str();
        }

        if (!name) {
            name = sound_name.c_str();
        }

        // Play the sound

        if (name != NULL) {
            if (ret) {
                aliaschannel = ret->channel;
                aliasvolume = G_Random() * ret->volumeMod + ret->volume;
                aliaspitch = G_Random() * ret->pitchMod + ret->pitch;
                aliasmin_dist = ret->dist;
                aliasmax_dist = ret->maxDist;

                if (channel < 0) {
                    channel = ret->channel;
                }

                num = entnum;
                if (sound_origin) {
                    VectorCopy(*sound_origin, org);
                    num = ENTITYNUM_NONE;
                } else {
                    VectorCopy(edict->s.origin, org);

                    if (doCallback) {
                        num |= S_FLAG_DO_CALLBACK;
                    }
                }

                switch (argstype) {
                case 0:
                    volume = aliasvolume;
                    pitch = aliaspitch;
                    min_dist = aliasmin_dist;
                    max_dist = aliasmax_dist;
                    channel = aliaschannel;
                    break;
                case 1:
                    if (volume >= 0.0f) {
                        volume = volume * aliasvolume;
                    } else {
                        volume = aliasvolume;
                    }

                    if (pitch >= 0.0f) {
                        pitch = pitch * aliaspitch;
                    } else {
                        pitch = aliaspitch;
                    }

                    if (min_dist < 0.0f) {
                        min_dist = aliasmin_dist;
                    }

                    if (max_dist < 0.0f) {
                        max_dist = aliasmax_dist;
                    }

                    if (channel < 0) {
                        channel = aliaschannel;
                    }
                    break;
                default:
                    if (volume < 0.0) {
                        volume = aliasvolume;
                    }
                    if (pitch < 0.0) {
                        pitch = aliaspitch;
                    }
                    if (min_dist < 0.0) {
                        min_dist = aliasmin_dist;
                    }
                    if (max_dist < 0.0) {
                        max_dist = aliasmax_dist;
                    }
                    if (channel < 0) {
                        channel = aliaschannel;
                    }
                    break;
                }

                if ((!checkSubtitle || g_subtitle->integer) && ret->subtitle) {
                    Entity *p = G_GetEntity(0);

                    if (g_subtitle->integer == 2 || Square(max_dist) > DistanceSquared(org, p->edict->s.origin)) {
                        cvar_t *curSubtitle = gi.Cvar_Get("curSubtitle", "0", 0);
                        int curSub;

                        gi.cvar_set(va("subtitle%d", curSubtitle->integer), va("%s", ret->subtitle));
                        curSub = curSubtitle->integer + 1;
                        if (curSubtitle->integer + 1 < 0) {
                            curSub = curSubtitle->integer + MAX_SUBTITLES;
                        }
                        
                        gi.cvar_set("curSubtitle", va("%d", (curSubtitle->integer + 1) - MAX_SUBTITLES * (curSub >> 2)));
                    }
                }

                gi.Sound(&org, num, channel, name, volume, min_dist, pitch, max_dist, ret->streamed);
            }
        } else {
            gi.DPrintf(
                "ERROR: Entity::Sound: %s needs an alias in ubersound.scr or uberdialog.scr - Please fix.\n",
                sound_name.c_str()
            );
        }
    } else {
        warning("Sound", "Null sample pointer");
    }
}

qboolean Entity::attach(int parent_entity_num, int tag_num, qboolean use_angles, Vector offset)
{
    int     i;
    Entity *parent;

    if (entnum == parent_entity_num) {
        warning("attach", "Trying to attach to oneself.");
        return false;
    }

    if (edict->s.parent != ENTITYNUM_NONE) {
        detach();
    }

    //
    // make sure this is a modelanim entity so that the attach works properly
    //
    if (edict->s.eType == ET_GENERAL) {
        edict->s.eType = ET_MODELANIM;
    }

    //
    // get the parent
    //
    parent = (Entity *)G_GetEntity(parent_entity_num);

    if (parent->numchildren < MAX_MODEL_CHILDREN) {
        //
        // find a free spot in the parent
        //
        for (i = 0; i < MAX_MODEL_CHILDREN; i++) {
            if (parent->children[i] == ENTITYNUM_NONE) {
                break;
            }
        }

        edict->s.parent = parent_entity_num;
        setSolidType(SOLID_NOT);
        parent->children[i] = entnum;
        parent->numchildren++;
        edict->s.tag_num           = tag_num;
        edict->s.attach_use_angles = use_angles;
        offset.copyTo(edict->s.attach_offset);
        setOrigin();

        return true;
    }
    return false;
}

void Entity::KillAttach(Event *ev)

{
    int     i;
    Entity *child = NULL;

    // Kill all of this entities children

    for (i = 0; i < MAX_MODEL_CHILDREN; i++) {
        if (children[i] != ENTITYNUM_NONE) {
            // Remove child
            child = (Entity *)G_GetEntity(children[i]);

            if (child) {
                child->ProcessEvent(EV_Remove);
            }

            // Remove child from this entity
            children[i] = ENTITYNUM_NONE;
        }
    }

    numchildren = 0;
}

void Entity::detach(void)
{
    int     i;
    int     num;
    Entity *parent;

    if (edict->s.parent == ENTITYNUM_NONE) {
        return;
    }

    parent = (Entity *)G_GetEntity(edict->s.parent);
    if (!parent) {
        return;
    }
    for (i = 0, num = parent->numchildren; i < MAX_MODEL_CHILDREN; i++) {
        if (parent->children[i] == ENTITYNUM_NONE) {
            continue;
        }
        if (parent->children[i] == entnum) {
            parent->children[i] = ENTITYNUM_NONE;
            parent->numchildren--;
            break;
        }
        num--;
        if (!num) {
            break;
        }
    }
    edict->s.parent = ENTITYNUM_NONE;
    setOrigin(origin);
}

void Entity::Flags(Event *ev)
{
    const char *flag;
    int         mask;
    int         action;
    int         i;

    for (i = 1; i <= ev->NumArgs(); i++) {
        action = FLAG_IGNORE;
        flag   = ev->GetString(i);
        switch (flag[0]) {
        case '+':
            action = FLAG_ADD;
            flag++;
            break;
        case '-':
            action = FLAG_CLEAR;
            flag++;
            break;
        default:
            gi.Printf("Entity::Flags", "First character is not '+' or '-', assuming '+'\n");
            action = FLAG_ADD;
            break;
        }

        //
        // WARNING: please change the Event decleration,
        // to match this function, if flags are added or
        // deleted the event must be updated.
        //
        if (!Q_stricmp(flag, "blood")) {
            mask = FL_BLOOD;
        } else if (!Q_stricmp(flag, "explode")) {
            mask = FL_DIE_EXPLODE;
        } else if (!Q_stricmp(flag, "die_gibs")) {
            mask = FL_DIE_GIBS;
        } else if (!Q_stricmp(flag, "autoaim")) {
            mask = FL_AUTOAIM;
        } else if (!Q_stricmp(flag, "god")) {
            mask = FL_GODMODE;
        } else {
            mask   = 0;
            action = FLAG_IGNORE;
            gi.Printf("Unknown flag '%s'", flag);
        }
        switch (action) {
        case FLAG_ADD:
            flags |= mask;
            break;
        case FLAG_CLEAR:
            flags &= ~mask;
            break;
        case FLAG_IGNORE:
            break;
        }
    }
}

void Entity::Effects(Event *ev)
{
    const char *flag;
    int         mask = 0;
    int         action;
    int         i;

    for (i = 1; i <= ev->NumArgs(); i++) {
        action = 0;
        flag   = ev->GetString(i);
        switch (flag[0]) {
        case '+':
            action = FLAG_ADD;
            flag++;
            break;
        case '-':
            action = FLAG_CLEAR;
            flag++;
            break;
        default:
            gi.Printf("Entity::Effects", "First character is not '+' or '-', assuming '+'\n");
            action = FLAG_ADD;
            break;
        }

        //
        // WARNING: please change the Event decleration,
        // to match this function, if flags are added or
        // deleted the event must be updated.
        //
        if (!Q_stricmp(flag, "everyframe")) {
            mask = EF_EVERYFRAME;
        }
        if (!Q_stricmp(flag, "antisbjuice")) {
            mask = EF_ANTISBJUICE;
        } else {
            action = FLAG_IGNORE;
            gi.Printf("Unknown token %s.", flag);
        }

        switch (action) {
        case FLAG_ADD:
            edict->s.eFlags |= mask;
            break;
        case FLAG_CLEAR:
            edict->s.eFlags &= ~mask;
            break;
        case FLAG_IGNORE:
            break;
        }
    }
}

void Entity::RenderEffects(Event *ev)
{
    const char *flag;
    int         mask = 0;
    int         action;
    int         i;

    for (i = 1; i <= ev->NumArgs(); i++) {
        action = 0;
        flag   = ev->GetString(i);
        switch (flag[0]) {
        case '+':
            action = FLAG_ADD;
            flag++;
            break;
        case '-':
            action = FLAG_CLEAR;
            flag++;
            break;
        default:
            gi.Printf("Entity::RenderEffects", "First character is not '+' or '-', assuming '+'\n");
            action = FLAG_ADD;
            break;
        }

        //
        // WARNING: please change the Event decleration,
        // to match this function, if flags are added or
        // deleted the event must be updated.
        //
        if (!Q_stricmp(flag, "dontdraw")) {
            mask = RF_DONTDRAW;
        } else if (!Q_stricmp(flag, "betterlighting")) {
            mask = RF_EXTRALIGHT;
        } else if (!Q_stricmp(flag, "lensflare")) {
            mask = RF_LENSFLARE;
        } else if (!Q_stricmp(flag, "viewlensflare")) {
            mask = RF_VIEWLENSFLARE;
        } else if (!Q_stricmp(flag, "lightoffset")) {
            mask = RF_LIGHTOFFSET;
        } else if (!Q_stricmp(flag, "skyorigin")) {
            mask = RF_SKYORIGIN;
        } else if (!Q_stricmp(flag, "fullbright")) {
            mask = RF_FULLBRIGHT;
        } else if (!Q_stricmp(flag, "minlight")) {
            mask = RF_MINLIGHT;
        } else if (!Q_stricmp(flag, "additivedynamiclight")) {
            mask = RF_ADDITIVE_DLIGHT;
        } else if (!Q_stricmp(flag, "lightstyledynamiclight")) {
            mask = RF_LIGHTSTYLE_DLIGHT;
        } else if (!Q_stricmp(flag, "shadow")) {
            mask = RF_SHADOW;
        } else if (!Q_stricmp(flag, "preciseshadow")) {
            mask = RF_SHADOW_PRECISE;
        } else if (!Q_stricmp(flag, "invisible")) {
            mask = RF_INVISIBLE;
        } else {
            action = FLAG_IGNORE;
            gi.Printf("Unknown token %s.", flag);
        }

        switch (action) {
        case FLAG_ADD:
            edict->s.renderfx |= mask;
            break;
        case FLAG_CLEAR:
            edict->s.renderfx &= ~mask;
            break;
        case FLAG_IGNORE:
            break;
        }
    }
}

void Entity::SVFlags(Event *ev)
{
    const char *flag;
    int         mask         = 0;
    Entity     *ent          = NULL;
    int         singleClient = 0;
    int         action;
    int         i;

    for (i = 1; i <= ev->NumArgs(); i++) {
        action = 0;
        flag   = ev->GetString(i);
        switch (flag[0]) {
        case '+':
            action = FLAG_ADD;
            flag++;
            break;
        case '-':
            action = FLAG_CLEAR;
            flag++;
            break;
        default:
            gi.Printf("Entity::SVFlags: First character is not '+' or '-', assuming '+'\n");
            action = FLAG_ADD;
            break;
        }

        //
        // WARNING: please change the Event decleration,
        // to match this function, if flags are added or
        // deleted the event must be updated.
        //
        if (!Q_stricmp(flag, "broadcast")) {
            mask = SVF_BROADCAST;
        } else if (!Q_stricmp(flag, "sendonce")) {
            mask = SVF_SENDONCE;
        } else if (!Q_stricmp(flag, "singleclient")) {
            mask = SVF_SINGLECLIENT;
            if (action == FLAG_ADD) {
                ent = ev->GetEntity(i + 1);

                if (!ent) {
                    ScriptError("No player specified for +singleclient!");
                }
            }

            i++;
        } else if (!Q_stricmp(flag, "notsingleclient")) {
            mask = SVF_NOTSINGLECLIENT;
            if (action == FLAG_ADD) {
                ent = ev->GetEntity(i + 1);

                if (!ent) {
                    ScriptError("No player specified for +notsingleclient!");
                }
            }

            i++;
        } else {
            action = FLAG_IGNORE;
            gi.Printf("Entity::SVFlags: Unknown token %s.\n", flag);
        }

        switch (action) {
        case FLAG_ADD:
            edict->r.svFlags |= mask;

            if ((mask & SVF_SINGLECLIENT) || (mask & SVF_NOTSINGLECLIENT)) {
                edict->r.singleClient = singleClient;
            }

            break;
        case FLAG_CLEAR:
            edict->r.svFlags &= ~mask;

            if ((mask & SVF_SINGLECLIENT) || (mask & SVF_NOTSINGLECLIENT)) {
                edict->r.singleClient = 0;
            }

            break;
        case FLAG_IGNORE:
            break;
        }
    }

    if (edict->r.svFlags & SVF_SENDONCE) {
        // Turn this entity into an event if the SENDONCE flag is sent
        edict->s.eType = ET_EVENTS;
    }
}

void Entity::BroadcastAIEvent(int iType, float rad)
{
    if (flags & FL_NOTARGET) {
        return;
    }

    G_BroadcastAIEvent(this, centroid, iType, rad);
}

void Entity::BroadcastAIEvent(Event *ev)
{
    int   iType;
    float rad;
    str   sTypeName;

    if (!(this->flags & FL_NOTARGET)) {
        iType = AI_EVENT_MISC;

        if (ev->NumArgs() > 0) {
            sTypeName = ev->GetString(1);
            iType     = G_AIEventTypeFromString(sTypeName);

            rad = ev->NumArgs() <= 1 ? G_AIEventRadius(iType) : ev->GetFloat(2);
        }

        BroadcastAIEvent(iType, rad);
    }
}

void Entity::Think(void) {}

void Entity::SetWaterType(void)
{
    qboolean isinwater;

    watertype = gi.pointcontents(origin, 0);
    isinwater = watertype & MASK_WATER;

    if (isinwater) {
        waterlevel = 1;
    } else {
        waterlevel = 0;
    }
}

void Entity::DamageSkin(trace_t *trace, float damage)

{
    /* FIXME : Do we need damage skins?
	int surface;

	// FIXME handle different bodyparts
	surface = trace->intersect.surface;
	if ( !edict->s.surfaces[ surface ] )
		 {
	   edict->s.surfaces[ surface ]++;
		 }
	*/
}

void Entity::Kill(Event *ev)
{
    health = 0;
    Damage(this, this, 10, origin, vec_zero, vec_zero, 0, 0, MOD_SUICIDE);
}

void Entity::Killed(Event *ev)
{
    deadflag = DEAD_DEAD;
    if (health > 0) {
        health = 0;
    }
}

void Entity::SurfaceCommand(const char *surf_name, const char *token)
{
    const char *current_surface_name;
    int         surface_num;
    int         mask;
    int         action;
    qboolean    do_all = false;
    qboolean    mult   = false;

    if (surf_name[strlen(surf_name) - 1] == '*') {
        mult        = true;
        surface_num = 0;
    } else if (str(surf_name) != str("all")) {
        surface_num = gi.Surface_NameToNum(edict->tiki, surf_name);

        if (surface_num < 0) {
            warning("SurfaceCommand", "group %s not found.", surf_name);
            return;
        }
    } else {
        surface_num = 0;
        do_all      = true;
    }

    action = 0;
    switch (token[0]) {
    case '+':
        action = FLAG_ADD;
        token++;
        break;
    case '-':
        action = FLAG_CLEAR;
        token++;
        break;
    default:
        warning("Entity::SurfaceModelEvent", "First character is not '+' or '-', assuming '+'\n");
        action = FLAG_ADD;
        break;
    }
    //
    // WARNING: please change the Event decleration,
    // to match this function, if flags are added or
    // deleted the event must be updated.
    //
    if (!Q_stricmp(token, "skin1")) {
        mask = MDL_SURFACE_SKINOFFSET_BIT0;
    } else if (!Q_stricmp(token, "skin2")) {
        mask = MDL_SURFACE_SKINOFFSET_BIT1;
    } else if (!Q_stricmp(token, "nodraw")) {
        mask = MDL_SURFACE_NODRAW;
    } else if (!Q_stricmp(token, "crossfade")) {
        mask = MDL_SURFACE_CROSSFADE_SKINS;
    } else {
        mask = 0;
        warning("SurfaceCommand", "Unknown token %s.", token);
        action = FLAG_IGNORE;
    }

    int numsurfaces;
    if (edict->tiki) {
        numsurfaces = gi.TIKI_NumSurfaces(edict->tiki);
    } else {
        numsurfaces = 0;
    }

    for (; surface_num < numsurfaces; surface_num++) {
        if (mult) {
            current_surface_name = gi.Surface_NumToName(edict->tiki, surface_num);

            if (Q_stricmpn(current_surface_name, surf_name, strlen(surf_name) - 1) != 0) {
                continue;
            }
        }

        switch (action) {
        case FLAG_ADD:
            edict->s.surfaces[surface_num] |= mask;
            break;
        case FLAG_CLEAR:
            edict->s.surfaces[surface_num] &= ~mask;
            break;
        case FLAG_IGNORE:
            break;
        }

        if (!do_all && !mult) {
            break;
        }
    }
}

void Entity::SurfaceModelEvent(Event *ev)
{
    str surf_name;
    str token;
    int i;

    surf_name = ev->GetString(1);

    for (i = 2; i <= ev->NumArgs(); i++) {
        token = ev->GetString(i);
        SurfaceCommand(surf_name, token);
    }
}

void Entity::AttachEvent(Event *ev)
{
    Entity     *parent;
    str         bone;
    int         tagnum;
    qboolean    use_angles = qtrue;
    Vector      offset;

    parent = ev->GetEntity(1);
    bone   = ev->GetString(2);

    if (ev->NumArgs() > 2) {
        use_angles = ev->GetInteger(3);
    }

    if (ev->NumArgs() > 3) {
        offset = ev->GetVector(4);
    }

    if (!parent) {
        return;
    }

    tagnum = gi.Tag_NumForName(parent->edict->tiki, bone);
    if (tagnum >= 0) {
        attach(parent->entnum, tagnum, use_angles, offset);
    } else {
        warning("AttachEvent", "Tag %s not found", bone.c_str());
    }
}

void Entity::AttachModelEvent(Event *ev)
{
    Animate    *obj;
    str         bone;
    str         modelname;
    int         tagnum;
    float       remove_time, fade_time, fade_delay;
    Vector      offset;

    obj = new Animate;

    modelname = ev->GetString(1);
    bone      = ev->GetString(2);
    if (ev->NumArgs() > 2) {
        obj->setScale(ev->GetFloat(3));
    }
    if (ev->NumArgs() > 3) {
        obj->SetTargetName(ev->GetString(4));
    }

    if (ev->NumArgs() > 4) {
        obj->detach_at_death = ev->GetInteger(5);
    }

    if (ev->NumArgs() > 5) {
        remove_time = ev->GetFloat(6);

        if (remove_time != -1) {
            Event *remove_event = new Event(EV_Remove);
            obj->PostEvent(remove_event, remove_time);
        }
    }

    if (ev->NumArgs() > 6) {
        Event *fade_event;

        fade_time = ev->GetFloat(7);

        if (fade_time != -1) {
            obj->setAlpha(0);

            fade_event = new Event(EV_FadeIn);
            fade_event->AddFloat(fade_time);
            obj->PostEvent(fade_event, 0);
        }
    }

    if (ev->NumArgs() > 7) {
        Event *fade_event;

        fade_delay = ev->GetFloat(8);

        if (fade_delay != -1) {
            if (ev->NumArgs() > 8) {
                fade_time = ev->GetFloat(9);
            } else {
                fade_time = 0;
            }

            fade_event = new Event(EV_Fade);

            if (fade_time) {
                fade_event->AddFloat(fade_time);
            }

            obj->PostEvent(fade_event, fade_delay);
        }
    }

    if (ev->NumArgs() > 9) {
        offset = ev->GetVector(10);
    }

    obj->setModel(modelname);

    tagnum = gi.Tag_NumForName(edict->tiki, bone);
    if (tagnum >= 0) {
        if (obj->attach(this->entnum, tagnum, true, offset)) {
            obj->NewAnim("idle");
        } else {
            warning("AttachModelEvent", "Could not attach model %s to tag \"%s\" on entnum #%d (targetname = %s)", modelname.c_str(), bone.c_str(), entnum, targetname.c_str());
            if (g_iInThinks) {
                PostEvent(EV_Remove, 0);
            } else {
                delete obj;
            }
            return;
        }
    } else {
        warning("AttachModelEvent", "Tag %s not found", bone.c_str());
        obj->NewAnim("idle");
    }
}

void Entity::RemoveAttachedModelEvent(Event *ev)
{
    str     tag_name;
    int     tag_num;
    int     num;
    int     i;
    Entity *ent;
    float   fade_rate = 0;
    Event  *fade_event;
    str     model_name;

    tag_name = ev->GetString(1);
    tag_num  = gi.Tag_NumForName(edict->tiki, tag_name);

    if (ev->NumArgs() > 1) {
        fade_rate = ev->GetFloat(2);
    }

    if (ev->NumArgs() > 2) {
        model_name = ev->GetString(3);
    }

    if (tag_num >= 0) {
        num = numchildren;

        for (i = 0; (i < MAX_MODEL_CHILDREN) && num; i++) {
            if (children[i] == ENTITYNUM_NONE) {
                continue;
            }

            ent = (Entity *)G_GetEntity(children[i]);

            if (ent->edict->s.tag_num == tag_num) {
                if (!model_name.length() || model_name == ent->model) {
                    if (fade_rate) {
                        fade_event = new Event(EV_Fade);
                        fade_event->AddFloat(fade_rate);
                        fade_event->AddFloat(0);
                        ent->PostEvent(fade_event, 0);
                    }

                    ent->PostEvent(EV_Remove, fade_rate);
                }
            }

            num--;
        }
    }
}

void Entity::AttachedModelAnimEvent(Event *ev)
{
    int     tag_num;
    int     num;
    int     i;
    Entity *ent;
    str     model_name;
    str     anim_name;
    float   crossblend_time;

    tag_num   = gi.Tag_NumForName(edict->tiki, ev->GetString(1).c_str());
    anim_name = ev->GetString(2);
    if (ev->NumArgs() >= 3) {
        crossblend_time = ev->GetFloat(3);
    }

    if (ev->NumArgs() >= 4) {
        model_name = ev->GetString(4);
    }

    if (tag_num >= 0) {
        num = numchildren;

        // find the children that is glued to the specified tagnum
        for (i = 0; i < MAX_MODEL_CHILDREN && num; i++) {
            if (children[i] == ENTITYNUM_NONE) {
                continue;
            }

            ent = G_GetEntity(children[i]);
            if (ent->edict->s.tag_num != tag_num) {
                // not matching the requested tag num
                continue;
            }

            if (model_name.length() && str::icmp(model_name.c_str(), ent->model.c_str())) {
                // different model
                continue;
            }

            Event *newev = new Event(EV_SetAnim);
            newev->AddString(anim_name);
            ent->PostEvent(newev, 0.f);
        }
    }
}

void Entity::DetachEvent(Event *ev)

{
    if (edict->s.parent == ENTITYNUM_NONE) {
        return;
    }
    detach();
}

void Entity::TakeDamageEvent(Event *ev)
{
    takedamage = DAMAGE_YES;
}

void Entity::NoDamageEvent(Event *ev)
{
    takedamage = DAMAGE_NO;
}

void Entity::Gravity(Event *ev)

{
    gravity = ev->GetFloat(1);
}

void Entity::UseBoundingBoxEvent(Event *ev)
{
    edict->r.svFlags |= SVF_USEBBOX;
}

void Entity::HurtEvent(Event *ev)
{
    Vector normal;
    float  dmg;
    int    means_of_death;
    Vector direction;

    if (ev->NumArgs() < 1) {
        dmg = 50;
    } else {
        dmg = ev->GetFloat(1);
    }

    if (ev->NumArgs() > 1) {
        means_of_death = MOD_string_to_int(ev->GetString(2));
    } else {
        means_of_death = MOD_CRUSH;
    }

    if (ev->NumArgs() > 2) {
        direction = ev->GetVector(3);
        direction.normalize();
    } else {
        direction = vec_zero;
    }

    normal = Vector(orientation[0]);
    Damage(world, world, dmg, centroid, direction, normal, dmg, 0, means_of_death);
}

void Entity::HealEvent(Event *ev)
{
    if (IsDead()) {
        ScriptError("cannot heal dead entities");
    }

    health += ev->GetFloat(1) * max_health;
    if (health > max_health) {
        health = max_health;
    }
}

void Entity::Censor(Event *ev)

{
    Vector delta;
    float  oldsize;
    float  newsize;

    oldsize = size.length();
    setSolidType(SOLID_NOT);
    setModel("censored.tik");
    gi.TIKI_CalculateBounds(edict->tiki, 1, mins, maxs);
    delta          = maxs - mins;
    newsize        = delta.length();
    edict->s.scale = oldsize / newsize;
    mins *= edict->s.scale;
    maxs *= edict->s.scale;
    setSize(mins, maxs);
    setOrigin();
}

void Entity::StationaryEvent(Event *ev)

{
    setMoveType(MOVETYPE_STATIONARY);
}

void Entity::TossEvent(Event *ev)
{
    movetype = MOVETYPE_TOSS;
}

void Entity::Explosion(Event *ev)

{
    str expmodel;
    str tag_name;
    //orientation_t orient;
    Vector explosion_origin;

    expmodel         = ev->GetString(1);
    explosion_origin = origin;

    if (ev->NumArgs() > 1) {
        tag_name = ev->GetString(2);

        //if ( GetRawTag( tag_name.c_str(), &orient, legs ) )
        //	VectorAdd( orient.origin, origin, explosion_origin );

        GetTag(tag_name.c_str(), &explosion_origin);
    }

    ExplosionAttack(explosion_origin, this, expmodel);
}

void Entity::Shader(Event *ev)

{
    str token;

    if (gi.modeltiki(model)) {
        gi.Printf("shader event being called on TIKI model\n");
    }
    //
    // get sub shader command
    //
    token = ev->GetString(1);

    //
    // WARNING: please change the Event decleration,
    // to match this function, if flags are added or
    // deleted the event must be updated.
    //
    if (!Q_stricmp(token, "translation")) {
        float x, y;

        x = ev->GetFloat(2);
        y = ev->GetFloat(3);
        TRANSLATION_TO_PKT(x, edict->s.tag_num);
        TRANSLATION_TO_PKT(y, edict->s.skinNum);
    } else if (!Q_stricmp(token, "offset")) {
        float x, y;

        x = ev->GetFloat(2);
        y = ev->GetFloat(3);
        OFFSET_TO_PKT(x, edict->s.tag_num);
        OFFSET_TO_PKT(y, edict->s.skinNum);
    } else if (!Q_stricmp(token, "rotation")) {
        float rot;

        rot = ev->GetFloat(2);
        ROTATE_TO_PKT(rot, edict->s.tag_num);
    } else if (!Q_stricmp(token, "wavebase")) {
        float base;

        base = ev->GetFloat(2);
        BASE_TO_PKT(base, edict->s.surfaces[0]);
    } else if (!Q_stricmp(token, "waveamp")) {
        float amp;

        amp = ev->GetFloat(2);
        AMPLITUDE_TO_PKT(amp, edict->s.surfaces[1]);
    } else if (!Q_stricmp(token, "wavephase")) {
        float phase;

        phase = ev->GetFloat(2);
        PHASE_TO_PKT(phase, edict->s.surfaces[2]);
    } else if (!Q_stricmp(token, "wavefreq")) {
        float freq;

        freq = ev->GetFloat(2);
        FREQUENCY_TO_PKT(freq, edict->s.surfaces[3]);
    }
}

void Entity::DropToFloorEvent(Event *ev)

{
    float range;

    if (ev->NumArgs() > 0) {
        range = ev->GetFloat(1);
    } else {
        range = MAP_SIZE;
    }
    if (!droptofloor(range)) {}
}

qboolean Entity::isBoundTo(Entity *master)

{
    Entity *ent;

    for (ent = bindmaster; ent != NULL; ent = ent->bindmaster) {
        if (ent == master) {
            return true;
        }
    }

    return false;
}

void Entity::bind(Entity *master, qboolean use_my_angles)
{
    float  mat[3][3];
    float  local[3][3];
    Vector ang;

    assert(master);
    if (!master) {
        warning("bind", "Null master entity");
        return;
    }

    if (master == this) {
        warning("bind", "Trying to bind to oneself.");
        return;
    }

    // unbind myself from my master
    unbind();

    bindmaster         = master;
    bind_use_my_angles = use_my_angles;

    // We are now separated from our previous team and are either
    // an individual, or have a team of our own.  Now we can join
    // the new bindmaster's team.  Bindmaster must be set before
    // joining the team, or we will be placed in the wrong position
    // on the team.
    joinTeam(master);

    // calculate local angles
    TransposeMatrix(bindmaster->orientation, mat);
    R_ConcatRotations(mat, orientation, local);
    MatrixToEulerAngles(local, ang);
    setAngles(ang);

    setLocalOrigin(getParentVector(localorigin - bindmaster->origin));

    return;
}

void Entity::unbind(void)
{
    Entity *prev;
    Entity *next;
    Entity *last;
    Entity *ent;

    if (!bindmaster) {
        return;
    }

    //bindmaster = NULL;

    // Check this GAMEFIX - should it be origin?
    localorigin = Vector(edict->s.origin);
    localangles = Vector(edict->s.angles);

    if (!teammaster) {
        bindmaster = NULL;
        //Teammaster already has been freed
        return;
    }

    // We're still part of a team, so that means I have to extricate myself
    // and any entities that are bound to me from the old team.
    // Find the node previous to me in the team
    prev = teammaster;

    for (ent = teammaster->teamchain; ent && (ent != this); ent = ent->teamchain) {
        prev = ent;
    }

    // If ent is not pointing to me, then something is very wrong.
    assert(ent);
    if (!ent) {
        gi.Printf("unbind", "corrupt team chain\n");
    }

    // Find the last node in my team that is bound to me.
    // Also find the first node not bound to me, if one exists.
    last = this;
    for (next = teamchain; next != NULL; next = next->teamchain) {
        if (!next->isBoundTo(this)) {
            break;
        }

        // Tell them I'm now the teammaster
        next->teammaster = this;
        last             = next;
    }

    // disconnect the last member of our team from the old team
    last->teamchain = NULL;

    // connect up the previous member of the old team to the node that
    // follow the last node bound to me (if one exists).
    if (teammaster != this) {
        prev->teamchain = next;
        if (!next && (teammaster == prev)) {
            prev->teammaster = NULL;
        }
    } else if (next) {
        // If we were the teammaster, then the nodes that were not bound to me are now
        // a disconnected chain.  Make them into their own team.
        for (ent = next; ent->teamchain != NULL; ent = ent->teamchain) {
            ent->teammaster = next;
        }
        next->teammaster = next;
        next->flags &= ~FL_TEAMSLAVE;
    }

    // If we don't have anyone on our team, then clear the team variables.
    if (teamchain) {
        // make myself my own team
        teammaster = this;
    } else {
        // no longer a team
        teammaster = NULL;
    }

    flags &= ~FL_TEAMSLAVE;
    bindmaster = NULL;
}

void Entity::EventUnglue(Event *ev)
{
    unglue();
}

void Entity::GlueEvent(Event *ev)
{
    Entity  *ent;
    qboolean glueAngles;

    if (ev->NumArgs() > 1) {
        glueAngles = ev->GetInteger(2);
    } else {
        glueAngles = qtrue;
    }

    ent = ev->GetEntity(1);
    if (ent) {
        glue(ent, glueAngles, qfalse);
    }
}

void Entity::DuckableGlueEvent(Event *ev)
{
    Entity  *ent;
    qboolean glueAngles;

    if (ev->NumArgs() > 1) {
        glueAngles = ev->GetInteger(2);
    } else {
        glueAngles = qtrue;
    }

    ent = ev->GetEntity(1);
    if (ent) {
        glue(ent, glueAngles, qtrue);
    }
}

void Entity::MakeClayPidgeon(Event *ev)
{
    edict->r.contents = CONTENTS_CLAYPIDGEON;
    setSolidType(SOLID_BBOX);
}

void Entity::EventUnbind(Event *ev)

{
    unbind();
}

void Entity::BindEvent(Event *ev)
{
    Entity *ent;

    ent = ev->GetEntity(1);
    if (ent) {
        bind(ent, false);
    }
}

Vector Entity::getParentVector(Vector vec)
{
    Vector pos;

    if (!bindmaster) {
        return vec;
    }

    pos[0] = vec * bindmaster->orientation[0];
    pos[1] = vec * bindmaster->orientation[1];
    pos[2] = vec * bindmaster->orientation[2];

    return pos;
}

//
// Team methods
//

void Entity::joinTeam(Entity *teammember)
{
    Entity *ent;
    Entity *master;
    Entity *prev;
    Entity *next;

    if (teammaster && (teammaster != this)) {
        quitTeam();
    }

    assert(teammember);
    if (!teammember) {
        warning("joinTeam", "Null entity");
        return;
    }

    master = teammember->teammaster;
    if (!master) {
        master                 = teammember;
        teammember->teammaster = teammember;
        teammember->teamchain  = this;

        // make anyone who's bound to me part of the new team
        for (ent = teamchain; ent != NULL; ent = ent->teamchain) {
            ent->teammaster = master;
        }
    } else {
        // skip past the chain members bound to the entity we're teaming up with
        prev = teammember;
        next = teammember->teamchain;
        if (bindmaster) {
            // if we have a bindmaster, joing after any entities bound to the entity
            // we're joining
            while (next && ((Entity *)next)->isBoundTo(teammember)) {
                prev = next;
                next = next->teamchain;
            }
        } else {
            // if we're not bound to someone, then put us at the end of the team
            while (next) {
                prev = next;
                next = next->teamchain;
            }
        }

        // make anyone who's bound to me part of the new team and
        // also find the last member of my team
        for (ent = this; ent->teamchain != NULL; ent = ent->teamchain) {
            ent->teamchain->teammaster = master;
        }

        prev->teamchain = this;
        ent->teamchain  = next;
    }

    teammaster = master;
    flags |= FL_TEAMSLAVE;
}

void Entity::quitTeam(void)
{
    Entity *ent;

    if (!teammaster) {
        return;
    }

    if (teammaster == this) {
        if (!teamchain->teamchain) {
            teamchain->teammaster = NULL;
        } else {
            // make next teammate the teammaster
            for (ent = teamchain; ent; ent = ent->teamchain) {
                ent->teammaster = teamchain;
            }
        }

        teamchain->flags &= ~FL_TEAMSLAVE;
    } else {
        assert(flags & FL_TEAMSLAVE);
        assert(teammaster->teamchain);

        ent = teammaster;
        while (ent->teamchain != this) {
            // this should never happen
            assert(ent->teamchain);

            ent = ent->teamchain;
        }

        ent->teamchain = teamchain;

        if (!teammaster->teamchain) {
            teammaster->teammaster = NULL;
        }
    }

    teammaster = NULL;
    teamchain  = NULL;
    flags &= ~FL_TEAMSLAVE;
}

void Entity::EventQuitTeam(Event *ev)

{
    quitTeam();
}

void Entity::JoinTeam(Event *ev)

{
    Entity *ent;

    ent = ev->GetEntity(1);
    if (ent) {
        joinTeam(ent);
    }
}

void Entity::AddToSoundManager(Event *ev)

{
    SoundMan.AddEntity(this);
}

inline qboolean Entity::HitSky(trace_t *trace)
{
    assert(trace);
    if (trace->surfaceFlags & SURF_SKY) {
        return true;
    }
    return false;
}

qboolean Entity::HitSky(void)
{
    return HitSky(&level.impact_trace);
}

void Entity::NoLerpThisFrame(void)
{
    edict->s.eFlags ^= EF_TELEPORT_BIT;
}

void Entity::Postthink(void) {}

void Entity::TouchTriggersEvent(Event *ev)
{
    flags |= FL_TOUCH_TRIGGERS;
}

void Entity::DeathSinkStart(Event *ev)
{
    if (g_gametype->integer != GT_SINGLE_PLAYER) {
        PostEvent(EV_Remove, 5.0f);
    } else {
        // Start the sinking
        ProcessEvent(EV_DeathSink);
    }
}

void Entity::DeathSink(Event *ev)
{
    // Sink just a little

    if (g_gametype->integer != GT_SINGLE_PLAYER) {
        origin[2] -= 0.2f;
        setOrigin(origin);

        // Make sure the sink happens again next frame
        PostEvent(EV_DeathSink, FRAMETIME);
    } else {
        Entity *player = G_GetEntity(0);

        // Remove the entity when the player isn't supposed to see it
        if (player && gi.AreasConnected(this->edict->r.areanum, player->edict->r.areanum)) {
            PostEvent(EV_DeathSink, 1.0f);
        } else {
            PostEvent(EV_Remove, 0);
        }
    }
}

void Entity::VelocityModified(void) {}

void Entity::DetachAllChildren(Event *ev)
{
    int i;

    for (i = 0; i < MAX_MODEL_CHILDREN; i++) {
        Entity *ent;
        if (children[i] == ENTITYNUM_NONE) {
            continue;
        }

        ent = (Entity *)G_GetEntity(children[i]);
        if (ent) {
            ent->PostEvent(EV_Remove, 0);
        }
    }
}

bool    monkeycheck = false;
cvar_t *thereisnomonkey;

qboolean Entity::CheckEventFlags(Event *event)
{
    int flags = GetFlags(event);

    // Special handling of console events
    if (!(flags & (EV_CONSOLE | EV_CHEAT))) {
        if (isSubclassOf(Entity)) {
            Entity *ent;

            ent = (Entity *)this;
            gi.SendServerCommand(
                ent->edict - g_entities, "print \"Command '%s' not available from console\n\"", event->getName()
            );
        }

        // don't process
        return false;
    }

    // don't allow console cheats unless the server says it's ok.

    if ((flags & EV_CHEAT)) {
        if (!monkeycheck) {
            thereisnomonkey = gi.Cvar_Get("thereisnomonkey", "0", CVAR_TEMP);
            monkeycheck     = true;
        }

        if (!thereisnomonkey->integer) {
            gi.cvar_set("cheats", "0");
        }

        if (!sv_cheats->integer) {
            if (isSubclassOf(Entity)) {
                Entity     *ent;
                const char *szConv =
                    gi.LV_ConvertString("You must run the server with '+set cheats 1' to enable this command.");

                ent = (Entity *)this;
                gi.SendServerCommand(ent->edict - g_entities, "print \"%s\n\"", szConv);
            }

            // don't process
            return false;
        }
    }

    // ok to process
    return true;
}

void Entity::SetMovementStealth(float fStealthScale)
{
    if (fStealthScale < 0.0f) {
        stealthMovementScale = 0.0f;
    } else {
        stealthMovementScale = fStealthScale;
    }
}

void Entity::EventMovementStealth(Event *ev)
{
    SetMovementStealth(ev->GetFloat(1));
}

void Entity::PusherEvent(Event *ev)
{
    Entity *inflictor;
    Entity *attacker;
    Vector  dir;
    Vector  momentum;
    float   m;
    float   force;

    if (movetype <= MOVETYPE_NOCLIP || movetype == MOVETYPE_FLYMISSILE || movetype == MOVETYPE_STOP
        || movetype == MOVETYPE_PUSH) {
        return;
    }

    inflictor = ev->GetEntity(1);
    attacker  = ev->GetEntity(2);
    dir       = ev->GetVector(3);
    force     = ev->GetFloat(4);

    m = mass;
    if (m < 20) {
        m = 20.0f;
    }

    m        = 1000.0f / m;
    momentum = dir * force * m;

    velocity += momentum;
}

void Entity::AlwaysDraw(Event *ev)
{
    edict->s.renderfx |= RF_ALWAYSDRAW;
}

void Entity::NormalDraw(Event *ev)
{
    edict->s.renderfx &= RF_DONTDRAW | RF_ALWAYSDRAW;
}

void Entity::NeverDraw(Event *ev)
{
    edict->s.renderfx |= RF_DONTDRAW;
}

//*************************************************************************
//
// BIND code
//
//*************************************************************************

void Entity::glue(Entity *master, qboolean use_my_angles, qboolean can_duck)
{
    int iNumGlues;

    if (m_pGlueMaster) {
        unglue();
    }

    if (!master) {
        warning("glue", "Cannot glue to master because of: Master is NULL\n");
        return;
    }

    if (master == this) {
        warning("glue", "Cannot glue to master because of: Binding an entity to itself\n");
        return;
    }

    iNumGlues = master->m_iNumGlues;
    if (iNumGlues + 1 > MAX_GLUE_CHILDREN) {
        warning("glue", "Cannot glue to master because of: MAX_GLUE_CHILDREN reached\n");
        return;
    }

    for (int i = 0; i < iNumGlues; i++) {
        if (master->m_pGlues[i] == this) {
            warning("glue", "Cannot glue to master because of: entity is already glued\n");
            return;
        }
    }

    master->m_pGlues[iNumGlues] = this;

    if (use_my_angles) {
        m_bGlueAngles                    = true;
        master->m_pGluesFlags[iNumGlues] = GL_USEANGLES;
    } else {
        m_bGlueAngles                    = false;
        master->m_pGluesFlags[iNumGlues] = 0;
    }

    m_pGlueMaster   = master;
    m_bGlueDuckable = can_duck == qtrue;
    master->m_iNumGlues++;
}

void Entity::unglue(void)
{
    int     iNumGlues;
    int     i;
    Entity *master = m_pGlueMaster;

    if (!master) {
        return;
    }

    iNumGlues = master->m_iNumGlues;

    for (i = 0; i < iNumGlues; i++) {
        if (master->m_pGlues[i] == this) {
            break;
        }
    }

    if (i != iNumGlues) {
        for (; i < iNumGlues - 1; i++) {
            master->m_pGlues[i]      = master->m_pGlues[i + 1];
            master->m_pGluesFlags[i] = master->m_pGluesFlags[i + 1];
        }

        master->m_iNumGlues--;

        m_pGlueMaster = NULL;
        m_bGlueAngles = false;
    }
}

void Entity::GetTagPositionAndOrientation(int tagnum, orientation_t *new_or)
{
    int           i;
    orientation_t tag_or;
    float         axis[3][3];

    GetRawTag(tagnum, &tag_or);

    AnglesToAxis(angles, axis);

    VectorCopy(origin, new_or->origin);

    for (i = 0; i < 3; i++) {
        VectorMA(new_or->origin, tag_or.origin[i], axis[i], new_or->origin);
    }

    MatrixMultiply(tag_or.axis, axis, new_or->axis);
}

void Entity::GetTagPosition(Event *ev)
{
    Vector        pos;
    str           tagname;
    int           tagnum;
    int           i;
    orientation_t tag_or;
    float         axis[3][3];

    if (!edict->tiki) {
        ScriptError("Entity '%s' at %.2f %.2f %.2f has no model.", targetname.c_str(), origin[0], origin[1], origin[2]);
    }

    tagname = ev->GetString(1);
    tagnum  = gi.Tag_NumForName(edict->tiki, tagname);
    if (tagnum < 0) {
        ScriptError("Could not find tag '%s' in '%s'", tagname.c_str(), edict->tiki->name);
    }

    tag_or = G_TIKI_Orientation(edict, tagnum & TAG_MASK);
    AnglesToAxis(angles, axis);

    pos = origin;
    for (i = 0; i < 3; i++) {
        VectorMA(pos, tag_or.origin[i], axis[i], pos);
    }

    ev->AddVector(pos);
}

void Entity::GetTagAngles(Event *ev)
{
    orientation_t orient;
    Vector        ang;
    str           tagname;
    int           tagnum;

    if (!edict->tiki) {
        ScriptError("Entity '%s' at %.2f %.2f %.2f has no model.", targetname.c_str(), origin[0], origin[1], origin[2]);
    }

    tagname = ev->GetString(1);
    tagnum  = gi.Tag_NumForName(edict->tiki, tagname);
    if (tagnum < 0) {
        ScriptError("Could not find tag '%s' in '%s'", tagname.c_str(), edict->tiki->name);
    }

    GetTagPositionAndOrientation(tagnum, &orient);
    MatrixToEulerAngles(orient.axis, ang);

    ev->AddVector(ang);
}

void Entity::IsTouching(Event *ev)
{
    Entity *ent = ev->GetEntity(1);

    if (!ent) {
        ScriptError("IsTouching used with a NULL entity.");
    }

    ev->AddInteger(IsTouching(ent));
}

void Entity::IsInside(Event *ev)
{
    Entity *ent = ev->GetEntity(1);

    if (!ent) {
        ScriptError("IsInside used with a NULL entity.");
    }

    ev->AddInteger(IsInside(ent));
}

void Entity::CanSeeInternal(Event *ev, bool bNoEnts)
{
    float fov      = 0.0f;
    float distance = 0.0f;

    if (ev->NumArgs() <= 0 || ev->NumArgs() > 3) {
        ScriptError("cansee should have 1, 2 or 3 arguments");
    }

    if (ev->NumArgs() > 2) {
        distance = ev->GetFloat(3);
        if (distance < 0) {
            ScriptError("distance must be >= 0");
        }
    }

    if (ev->NumArgs() > 1) {
        fov = ev->GetFloat(2);
        if (fov < 0.0f || fov > 360.0f) {
            ScriptError("fov must be in range 0 <= fov <= 360");
        }
    }

    if (ev->IsVectorAt(1)) {
        Vector org = ev->GetVector(1);

        ev->AddInteger(CanSee(org, fov, distance, bNoEnts));
    } else {
        Entity *ent = ev->GetEntity(1);
        if (!ent) {
            ScriptError("NULL entity in parameter 1");
        }

        ev->AddInteger(CanSee(ent, fov, distance, bNoEnts));
    }
}

void Entity::CanSee(Event *ev)
{
    CanSeeInternal(ev, false);
}

void Entity::CanSeeNoEnts(Event *ev)
{
    CanSeeInternal(ev, true);
}

void Entity::EventInPVS(Event *ev)
{
    ev->AddInteger(gi.AreasConnected(edict->r.areanum, ev->GetEntity(1)->edict->r.areanum) != 0);
}

void Entity::SetShaderData(Event *ev)
{
    edict->s.shader_data[0] = ev->GetFloat(1);
    edict->s.shader_data[1] = ev->GetFloat(2);
}

void Entity::MPrintf(const char *fmt, ...)
{
    va_list argptr;
    char    msg[MAXPRINTMSG];

    if (g_monitorNum->integer == entnum || g_monitor->string[0] && g_monitor->string == targetname) {
        va_start(argptr, fmt);
        Q_vsnprintf(msg, sizeof(msg), fmt, argptr);
        va_end(argptr);
        Com_Printf("%s", msg);
    }
}

void Entity::GetVelocity(Event *ev)
{
    ev->AddVector(velocity);
}

void Entity::SetVelocity(Event *ev)
{
    velocity = ev->GetVector(1);
}

void Entity::GetAVelocity(Event *ev)
{
    ev->AddVector(avelocity);
}

void Entity::DoForceActivate(void)
{
    edict->r.lastNetTime = level.inttime;
}

void Entity::ForceActivate(Event *ev)
{
    DoForceActivate();
}

void Entity::EventTrace(Event *ev)
{
    Vector  start, end, mins, maxs;
    int     mask = MASK_SOLID;
    Entity *ent;
    trace_t trace;

    mins = vec_zero;
    maxs = vec_zero;
    ent  = NULL;

    start = ev->GetVector(1);
    end   = ev->GetVector(2);

    if (ev->NumArgs() > 2) {
        if (ev->GetInteger(3)) {
            mask = 1;
        }
    }

    if (ev->NumArgs() > 3) {
        mins = ev->GetVector(4);
    }

    if (ev->NumArgs() > 4) {
        maxs = ev->GetVector(5);
    }

    if (ev->NumArgs() > 5) {
        ent = G_GetEntity(ev->GetInteger(6));
    }

    // call trace
    trace = G_Trace(start, mins, maxs, end, ent, mask, qfalse, "Entity::EventTrace");

    ev->AddVector(trace.endpos);
}

void Entity::EventSightTrace(Event *ev)
{
    Vector   start, end, mins, maxs;
    int      mask = MASK_SOLID;
    Entity  *ent  = nullptr;
    qboolean hit  = qfalse;

    mins = vec_zero;
    maxs = vec_zero;

    start = ev->GetVector(1);
    end   = ev->GetVector(2);

    if (ev->NumArgs() > 2) {
        if (ev->GetInteger(3)) {
            mask = 1;
        }
    }

    if (ev->NumArgs() > 3) {
        mins = ev->GetVector(4);
    }

    if (ev->NumArgs() > 4) {
        maxs = ev->GetVector(5);
    }

    if (ev->NumArgs() > 5) {
        ent = G_GetEntity(ev->GetInteger(6));
    }

    hit = G_SightTrace(start, mins, maxs, end, ent, NULL, mask, qfalse, "Entity::EventSightTrace");

    ev->AddInteger(hit);
}

void Entity::ClientThink(void) {}

void Entity::EndFrame(void) {}

void Entity::CalcBlend(void) {}

void Entity::ShowInfo(float fDot, float fDist)
{
    int  i;
    char szText[512];
    if (fDot > 0.94999999 && fDist < 1024.0 && fDist > 64.0) {
        i = Com_sprintf(szText, sizeof(szText), "%i:%i", entnum, radnum);
        if (targetname.length()) {
            i = Com_sprintf(szText + i, sizeof(szText) - i, ":%s", targetname.c_str());
        }

        if (health != 0) {
            Com_sprintf(szText + i, sizeof(szText) - i, ":%.1f", health);
        }

        G_DebugString(Vector(origin.x + 0, origin.y + 0, origin.z + maxs.z + 65), 1.0, 1.0, 1.0, 1.0, szText);
    }
}

bool Entity::AutoArchiveModel(void)
{
    return true;
}

void Entity::ConnectPaths(void)
{
    PathNode *node;
    int       to;
    int       j;

    for (int i = 0; i < m_iNumBlockedPaths; i++) {
        to   = m_BlockedPaths[i].to;
        node = PathManager.pathnodes[m_BlockedPaths[i].from];

        j                  = node->virtualNumChildren;
        pathway_t *pathway = &node->Child[j];

        do {
            j--;
            pathway--;
        } while (pathway->node != to);

        pathway->numBlockers--;

        if (!pathway->numBlockers) {
            node->ConnectChild(j);
        }
    }

    m_iNumBlockedPaths = 0;
    if (m_BlockedPaths) {
        delete[] m_BlockedPaths;
        m_BlockedPaths = NULL;
    }
}

void Entity::DisconnectPaths(void)
{
    int         i;
    int         j;
    PathNode   *node;
    pathway_t  *pathway;
    pathway_ref paths[1024];
    vec3_t      mins;
    vec3_t      maxs;
    gentity_t  *collision_ent;

    if (m_BlockedPaths) {
        ConnectPaths();
    }

    if (IsSubclassOfVehicle()) {
        VehicleCollisionEntity *ent = ((Vehicle *)this)->GetCollisionEntity();

        if (!ent) {
            return;
        }

        collision_ent = ent->edict;
    } else {
        collision_ent = edict;
    }

    VectorSet(mins, -15.0f, -15.0f, 0);
    VectorSet(maxs, 15.0f, 15.0f, 94.0f);

    for (i = 0; i < PathManager.nodecount; i++) {
        node = PathManager.pathnodes[i];

        for (j = node->virtualNumChildren - 1; j >= 0; j--) {
            pathway = &node->Child[j];

            if (!gi.SightTraceEntity(collision_ent, pathway->pos1, mins, maxs, pathway->pos2, MASK_PATHSOLID, qtrue)) {
                paths[m_iNumBlockedPaths].from = node->nodenum;
                paths[m_iNumBlockedPaths].to   = pathway->node;
                m_iNumBlockedPaths++;

                pathway->numBlockers++;

                if (pathway->numBlockers == 1) {
                    node->DisconnectChild(j);
                }
            }
        }
    }

    if (m_iNumBlockedPaths) {
        m_BlockedPaths = new pathway_ref[m_iNumBlockedPaths];

        for (i = 0; i < m_iNumBlockedPaths; i++) {
            m_BlockedPaths[i] = paths[i];
        }
    }
}

void Entity::EventConnectPaths(Event *ev)
{
    ConnectPaths();
}

void Entity::EventDisconnectPaths(Event *ev)
{
    DisconnectPaths();
}

void Entity::Delete(void)
{
    // Delete the entity the next frame

    if (g_iInThinks) {
        if (ProcessingEvents) {
            // Fixed in OPM
            //  Add a slight delay because otherwise this would cause an infinite loop
            //  when processing pending events
            PostEvent(EV_Remove, 0.001);
            return;
        }

        PostEvent(EV_Remove, 0);
    } else {
        delete this;
    }
}

void Entity::Remove(Event *ev)
{
    if (ev->NumArgs()) {
        ScriptError("Arguments not allowed.");
    }

    Delete();
}

void Entity::EventSoundDone(Event *ev)
{
    int channelNum = ev->GetInteger(1);
    str sfxName    = ev->GetString(2);

    if (gi.S_IsSoundPlaying(channelNum, sfxName)) {
        // Repost the event
        Event *event = new Event(EV_SoundDone);
        event->AddInteger(channelNum);
        event->AddString(sfxName);
        PostEvent(event, level.frametime);
    } else {
        Unregister(STRING_SOUNDDONE);
    }
}

void Entity::VolumeDamage(float damage)
{
    int     i;
    int     numtouch;
    Entity *ent;
    int     touch[MAX_GENTITIES];

    numtouch = gi.AreaEntities(absmin, absmax, touch, MAX_GENTITIES);

    for (i = 0; i < numtouch; i++) {
        ent = G_GetEntity(touch[i]);
        ent->Damage(world, world, damage, origin, vec_zero, vec_zero, 0, DAMAGE_RADIUS, MOD_EXPLOSION);
    }
}

void Entity::EventVolumeDamage(Event *ev)
{
    VolumeDamage(ev->GetFloat(1));
}

void Entity::Archive(Archiver& arc)
{
    int tempInt;
    int i;

    SimpleEntity::Archive(arc);

    G_ArchiveEdict(arc, edict);

    arc.ArchiveInteger(&entnum);
    arc.ArchiveInteger(&radnum);
    arc.ArchiveInteger(&spawnflags);

    arc.ArchiveString(&model);
    if (arc.Loading() && AutoArchiveModel()) {
        if (model.length() && model[0] == '*') {
            // set the brush model
            edict->s.modelindex = atoi(model.c_str() + 1);
        } else {
            setModel();
        }
    }

    arc.ArchiveVector(&mins);
    arc.ArchiveVector(&maxs);
    arc.ArchiveVector(&absmin);
    arc.ArchiveVector(&absmax);
    arc.ArchiveVector(&velocity);
    arc.ArchiveVector(&accel);
    arc.ArchiveVector(&avelocity);
    arc.ArchiveVector(&aaccel);
    arc.ArchiveVector(&size);
    arc.ArchiveInteger(&movetype);
    arc.ArchiveInteger(&mass);
    arc.ArchiveFloat(&gravity);
    arc.ArchiveRaw(orientation, sizeof(orientation));

    if (arc.Saving()) {
        if (groundentity) {
            tempInt = groundentity - g_entities;
        } else {
            tempInt = -1;
        }
    }

    arc.ArchiveInteger(&tempInt);

    if (arc.Loading()) {
        if (tempInt == -1) {
            groundentity = NULL;
        } else {
            groundentity = &g_entities[tempInt];
        }
    }

    arc.ArchiveRaw(&groundplane, sizeof(groundplane));
    arc.ArchiveInteger(&groundcontents);

    arc.ArchiveInteger(&numchildren);
    arc.ArchiveRaw(children, sizeof(children));

    arc.ArchiveFloat(&lightRadius);

    arc.ArchiveString(&moveteam);
    arc.ArchiveObjectPointer((Class **)&teamchain);
    arc.ArchiveObjectPointer((Class **)&teammaster);

    arc.ArchiveObjectPointer((Class **)&bindmaster);
    arc.ArchiveBoolean(&bind_use_my_angles);
    arc.ArchiveVector(&localorigin);
    arc.ArchiveVector(&localangles);

    // add to target list to rebuild targetlists
    arc.ArchiveString(&killtarget);

    arc.ArchiveFloat(&health);
    arc.ArchiveFloat(&max_health);
    arc.ArchiveInteger(&deadflag);
    arc.ArchiveInteger(&flags);

    arc.ArchiveInteger(&watertype);
    arc.ArchiveInteger(&waterlevel);

    ArchiveEnum(takedamage, damage_t);

    arc.ArchiveSafePointer(&enemy);
    arc.ArchiveFloat(&pain_finished);
    arc.ArchiveFloat(&damage_debounce_time);
    arc.ArchiveInteger(&damage_type);

    arc.ArchiveInteger(&m_iNumGlues);

    for (i = 0; i < MAX_GLUE_CHILDREN; i++) {
        arc.ArchiveSafePointer(&m_pGlues[i]);
        arc.ArchiveInteger(&m_pGluesFlags[i]);
    }

    arc.ArchiveObjectPointer((Class **)&m_pGlueMaster);
    arc.ArchiveBool(&m_bGlueAngles);
    arc.ArchiveBool(&m_bGlueDuckable);
    arc.ArchiveBoolean(&detach_at_death);

    arc.ArchiveFloat(&stealthMovementScale);
    arc.ArchiveInteger(&m_iNumBlockedPaths);

    if (arc.Loading()) {
        m_BlockedPaths = new pathway_ref[m_iNumBlockedPaths];
    }

    for (i = 0; i < m_iNumBlockedPaths; i++) {
        arc.ArchiveShort(&m_BlockedPaths[i].from);
        arc.ArchiveShort(&m_BlockedPaths[i].to);
    }

    immunities.Archive(arc);
}

void Entity::EventGetNormalHealth(Event *ev)
{
    ev->AddFloat(health / max_health * 100);
}

void Entity::EventNormalDamage(Event *ev)
{
    Vector position = vec_zero, direction, normal;

    Entity *attacker = world;
    float   damage;

    normal = direction = Vector(0, 1, 0);

    int numArgs = ev->NumArgs();
    if (numArgs == 1) {
        damage = ev->GetFloat(1) / 100 * max_health;
    } else if (numArgs == 2) {
        damage    = ev->GetFloat(1) / 100 * max_health;
        direction = ev->GetVector(2);
    } else {
        ScriptError("Wrong number of arguments for normal_damage.\n");
    }

    Event e1(EV_Damage);

    e1.AddEntity(attacker); //attacker

    e1.AddFloat(damage);             //damage
    e1.AddEntity(attacker);          //inflictor
    e1.AddVector(position);          //position
    e1.AddVector(direction);         //direction
    e1.AddVector(normal);            //normal
    e1.AddInteger(0);                //knockback
    e1.AddInteger(DAMAGE_NONE);      //damageflags
    e1.AddInteger(MOD_NONE);         //meansofdeath
    e1.AddInteger(HITLOC_GENERAL); //location

    ProcessEvent(e1);
}

void Entity::DrawBoundingBox(int showbboxes)
{
    if (showbboxes >= 100) {
        if (g_monitorNum->integer != entnum) {
            if (!*g_monitor->string || g_monitor->string != targetname) {
                return;
            }
        }

        showbboxes -= 100;
    }

    switch (showbboxes) {
    case 1:
        if (edict->s.solid) {
            G_DebugRotatedBBox(origin, angles, mins, maxs, 1, 1, 0, 1);
        }
        break;
    case 2:
        if (edict->s.solid) {
            G_DebugBBox(vec_zero, edict->r.absmin, edict->r.absmax, 1, 0, 1, 1);
        }
        break;
    case 3:
        if (edict->tiki && !(edict->s.renderfx & RF_DONTDRAW)) {
            G_DebugRotatedBBox(origin, angles, mins, maxs, 1, 1, 0, 1);
        }
        break;
    case 4:
        G_DebugRotatedBBox(origin, angles, mins, maxs, 1, 1, 0, 1);
        break;
    case 7:
        if (edict->tiki && !(edict->s.renderfx & RF_DONTDRAW)) {
            Vector vMins, vMaxs;

            gi.TIKI_CalculateBounds(edict->tiki, edict->s.scale, vMins, vMaxs);
            if (!VectorCompare(vMins, vec_zero) || !VectorCompare(vMaxs, vec_zero)) {
                if (edict->s.parent != ENTITYNUM_NONE && edict->s.tag_num >= 0) {
                    int i;
                    Vector vAng;
                    orientation_t oTag, oBox;
                    Entity* pParent;

                    pParent = G_GetEntity(edict->s.parent);
                    AnglesToAxis(pParent->angles, pParent->orientation);
                    oTag = G_TIKI_Orientation(pParent->edict, edict->s.tag_num & TAG_MASK);

                    VectorCopy(pParent->origin, oBox.origin);
                    for (i = 0; i < 3; i++) {
                        VectorMA(oBox.origin, oTag.origin[i], pParent->orientation[i], oBox.origin);
                    }

                    MatrixMultiply(oTag.axis, pParent->orientation, oBox.axis);
                    MatrixToEulerAngles(oBox.axis, vAng);
                    G_DebugRotatedBBox(oBox.origin, vAng, vMins, vMaxs, 0, 1, 1, 1);
                } else {
                    G_DebugRotatedBBox(origin, angles, vMins, vMaxs, 0, 1, 1, 1);
                }
            }
        }
        break;
    case 8:
        if (edict->tiki && !(edict->s.renderfx & RF_DONTDRAW)) {
            G_DebugRotatedBBox(origin, angles, mins, maxs, 1, 1, 0, 1);
            G_DebugBBox(origin, Vector(-4, -4, -4), Vector(4, 4, 4), 0, 1, 1, 1);
        }
        break;
    default:
        break;
    }

    if ((showbboxes >= 9 && showbboxes <= 28) || (showbboxes >= -28 && showbboxes <= -9)) {
        qboolean bDoPlayer = qfalse;
        
        if (showbboxes >= 0) {
            bDoPlayer = qfalse;
        } else {
            showbboxes = -showbboxes;
            bDoPlayer = qtrue;
        }

        if (edict->s.solid == SOLID_NOT) {
            return;
        }

        if ((edict != g_entities && !bDoPlayer) || (edict == g_entities && bDoPlayer)) {
            int i, j;
            int iTagNum;
            int iCap;
            float fRadius;
            const char *pszTagName;
            vec3_t vOffset;
            vec3_t vPos;
            vec3_t vColor;
            orientation_t orTag;
            orientation_t orPosition;

            if (!IsSubclassOfSentient() || !edict->tiki) {
                G_DebugBBox(origin, mins, maxs, 1.0, 1.0, 0.0, 1.0);
                return;
            }

            AnglesToAxis(angles, orientation);

            if (showbboxes <= 9 && showbboxes >= -9) {
                i = 0;
                iCap = 19;
            } else {
                i = showbboxes - 10;
                if (i >= 18) {
                    return;
                }

                iCap = i + 1;
            }

            for (; i < iCap; i++) {
                pszTagName = gi.CM_GetHitLocationInfo(i, &fRadius, vOffset);

                if (sv_testloc_num->integer - 1 == i || (sv_testloc_num->integer && (showbboxes > 9 || showbboxes < -9))) {
                    fRadius = sv_testloc_radius->value;
                    vOffset[0] = sv_testloc_offset_x->value;
                    vOffset[1] = sv_testloc_offset_y->value;
                    vOffset[2] = sv_testloc_offset_z->value;
                    vColor[0] = 1.0;
                    vColor[1] = 0.5;
                    vColor[2] = 0.0;
                } else {
                    vColor[0] = 0.0;
                    vColor[1] = 0.5;
                    vColor[2] = 1.0;
                }

                iTagNum = gi.Tag_NumForName(edict->tiki, pszTagName);
                if (iTagNum < 0) {
                    return;
                }

                orTag = G_TIKI_Orientation(edict, iTagNum & TAG_MASK);
                VectorCopy(origin, orPosition.origin);

                for (j = 0; j < 3; j++) {
                    VectorMA(orPosition.origin, orTag.origin[j], orientation[j], orPosition.origin);
                }

                MatrixMultiply(orTag.axis, orientation, orPosition.axis);
                MatrixTransformVector(vOffset, orPosition.axis, vPos);
                VectorAdd(orPosition.origin, vPos, vPos);

                G_DebugCircle(vPos, fRadius, vColor[0], vColor[1], vColor[2], 1.0);

                pszTagName = gi.CM_GetHitLocationInfoSecondary(i, &fRadius, vOffset);
                vColor[0] = 0.5;
                vColor[1] = 0.0;
                vColor[2] = 1.0;

                if (!pszTagName || sv_testloc_num->integer - 1 == i || (sv_testloc_num->integer && (showbboxes > 9 || showbboxes < -9))) {
                    if (!sv_testloc_secondary->integer) {
                        continue;
                    }

                    if (sv_testloc_num->integer - 1 != i && (!sv_testloc_num->integer || (showbboxes <= 9 && showbboxes >= -9))) {
                        continue;
                    }

                    fRadius = sv_testloc_radius2->value;
                    vOffset[0] = sv_testloc_offset2_x->value;
                    vOffset[1] = sv_testloc_offset2_y->value;
                    vOffset[2] = sv_testloc_offset2_z->value;
                    vColor[0] = 1.0;
                    vColor[1] = 0.0;
                    vColor[2] = 0.5;
                }

                MatrixTransformVector(vOffset, orPosition.axis, vPos);
                VectorAdd(orPosition.origin, vPos, vPos);

                G_DebugCircle(vPos, fRadius, vColor[0], vColor[1], vColor[2], 1.0);
            }
        }
    }
}

qboolean Entity::IsDead(void) const
{
    return deadflag != DEAD_NO;
}

void Entity::AddImmunity(Event *ev)
{
    str immune_string;
    int new_immunity;
    int number_of_immunities;
    int i;

    number_of_immunities = ev->NumArgs();

    for (i = 1; i <= number_of_immunities; i++) {
        immune_string = ev->GetString(i);

        new_immunity = MOD_string_to_int(immune_string);

        if (new_immunity != -1) {
            immunities.AddUniqueObject(new_immunity);
        }
    }
}

void Entity::RemoveImmunity(Event *ev)
{
    str immune_string;
    int old_immunity;
    int number_of_immunities;
    int i;

    number_of_immunities = ev->NumArgs();

    for (i = 1; i <= number_of_immunities; i++) {
        immune_string = ev->GetString(i);

        old_immunity = MOD_string_to_int(immune_string);

        if (old_immunity != -1 && immunities.ObjectInList(old_immunity)) {
            immunities.RemoveObject(old_immunity);
        }
    }
}

qboolean Entity::Immune(int meansofdeath)
{
    int number_of_immunities, i;

    number_of_immunities = immunities.NumObjects();

    for (i = 1; i <= number_of_immunities; i++) {
        if (meansofdeath == immunities.ObjectAt(i)) {
            return true;
        }
    }

    return false;
}

void Entity::PathnodeClaimRevoked(PathNode *node)
{
    return node->Relinquish();
}

qboolean Entity::BlocksAIMovement() const
{
    return true;
}

qboolean Entity::AIDontFace(void) const
{
    return true;
}

void Entity::EventGetEntnum(Event *ev)
{
    ev->AddInteger(entnum);
}

void Entity::EventGetClassname(Event *ev)
{
    ev->AddString(getClassname());
}

void Entity::SetModelEvent(Event *ev)
{
    setModel(ev->GetString(1));
}

void Entity::EventSetRadnum(Event *ev)
{
    radnum = ev->GetInteger(1);
}

void Entity::EventGetRadnum(Event *ev)
{
    ev->AddInteger(radnum);
}

void Entity::EventSetRotatedBbox(Event *ev)
{
    if (ev->GetInteger(1)) {
        edict->s.eFlags |= EF_LINKANGLES;
    } else {
        edict->s.eFlags &= ~EF_LINKANGLES;
    }

    setAngles(angles);
}

void Entity::EventGetRotatedBbox(Event *ev)
{
    ev->AddInteger((edict->s.eFlags & EF_LINKANGLES) ? 1 : 0);
}

void Entity::EventSinglePlayerCommand(Event *ev)
{
    if (g_gametype->integer == GT_SINGLE_PLAYER && !g_realismmode->integer) {
        Event *newev = new Event(ev->GetToken(1), ev->NumArgs() - 1);

        for (int i = 2; i <= ev->NumArgs(); i++) {
            newev->AddToken(ev->GetToken(i));
        }

        ProcessEvent(newev);
    }
}

void Entity::EventMultiPlayerCommand(Event *ev)
{
    if (g_gametype->integer != GT_SINGLE_PLAYER && !g_realismmode->integer) {
        Event *newev = new Event(ev->GetToken(1), ev->NumArgs() - 1);

        for (int i = 2; i <= ev->NumArgs(); i++) {
            newev->AddToken(ev->GetToken(i));
        }

        ProcessEvent(newev);
    }
}

void Entity::EventRealismModeCommand(Event *ev)
{
    if (g_realismmode->integer) {
        Event *newev = new Event(ev->GetToken(1), ev->NumArgs() - 1);

        for (int i = 2; i <= ev->NumArgs(); i++) {
            newev->AddToken(ev->GetToken(i));
        }

        ProcessEvent(newev);
    }
}

void Entity::EventSPRealismModeCommand(Event *ev)
{
    if (g_gametype->integer == GT_SINGLE_PLAYER && g_realismmode->integer) {
        Event *newev = new Event(ev->GetToken(1), ev->NumArgs() - 1);

        for (int i = 2; i <= ev->NumArgs(); i++) {
            newev->AddToken(ev->GetToken(i));
        }

        ProcessEvent(newev);
    }
}

void Entity::EventDMRealismModeCommand(Event *ev)
{
    if (g_gametype->integer != GT_SINGLE_PLAYER && g_realismmode->integer) {
        Event *newev = new Event(ev->GetToken(1), ev->NumArgs() - 1);

        for (int i = 2; i <= ev->NumArgs(); i++) {
            newev->AddToken(ev->GetToken(i));
        }

        ProcessEvent(newev);
    }
}

void Entity::GetLocalYawFromVector(Event *ev)
{
    float  yaw;
    Vector vec;

    vec = ev->GetVector(1);
    yaw = vectoyaw(vec) + 180.0f;
    yaw = AngleSubtract(yaw, angles[1]);
    ev->AddFloat(yaw);
}

void Entity::EventShootableOnly(Event *ev)
{
    edict->r.contents |= CONTENTS_SHOOTONLY | CONTENTS_WEAPONCLIP;
}

void Entity::SetShaderTime(Event *ev)
{
    edict->s.shader_time = level.time;
    if (ev->NumArgs() > 0) {
        edict->s.shader_time += ev->GetFloat(1);
    }

    if (ev->NumArgs() > 1) {
        edict->s.shader_time += ev->GetFloat(1) * random() / 32768.0;
    }
}

qboolean Entity::GetTagPositionAndOrientation(str tagname, orientation_t *new_or)
{
    int tagnum;

    tagnum = gi.Tag_NumForName(edict->tiki, tagname);

    if (tagnum < 0) {
        return false;
    }

    GetTagPositionAndOrientation(tagnum, new_or);
    return true;
}

void Entity::NoTarget(Event *ev)
{
    if (ev->GetBoolean(1)) {
        flags |= FL_NOTARGET;
    } else {
        flags &= ~FL_NOTARGET;
    }
}

void Entity::GetZone(Event *ev)
{
    ev->AddInteger(edict->r.areanum);

    // FIXME: Implement OPM feature
    STUB();

#if 0
	ZoneInfo *zone = getZone();

	if( zone != NULL ) {
		ev->AddListener( zone );
	} else {
		ev->AddNil();
	}
#endif
}

void Entity::IsInZone(Event *ev)
{
    if (edict->r.areanum == ev->GetInteger(1)) {
        ev->AddInteger(1);
    } else {
        ev->AddInteger(0);
    }

    // FIXME: Implement OPM feature
    STUB();

#if 0
	ZoneInfo *zone = ( ZoneInfo * )ev->GetEntity( 1 );
	ZoneInfo *currentZone = getZone();

	if( zone == NULL ) {
		ScriptError( "The zone must be a valid ZoneInfo!!!\n" );
	}

	if( !zone->inheritsFrom( "ZoneInfo" ) ) {
		ScriptError( "The specified zone is not a ZoneInfo!!!\n" );
	}

	if( currentZone == zone || IsTouching( zone ) ) {
		ev->AddInteger( 1 ); // The entity is in the specified zone
	} else {
		ev->AddInteger( 0 ); // The entity is not in the specified zone
	}
#endif
}

void Entity::SetDepthHack(Event *ev)
{
    int     bEnable;
    Entity *entity = (Entity *)this;

    bEnable = ev->GetInteger(1);

    if (bEnable) {
        entity->edict->s.renderfx |= RF_DEPTHHACK;
    } else {
        entity->edict->s.renderfx &= ~RF_DEPTHHACK;
    }
}

#ifdef OPM_FEATURES
void Entity::ProcessHint(gentity_t *client, bool bShow)
{
    Player *player = (Player *)client->entity;

    if (client->client != NULL) {
        qboolean is_hidden = bShow && (edict->s.renderfx & RF_INVISIBLE);
        qboolean can_see   = bShow
                        && player->canUse(
                            this, m_bHintRequiresLookAt
                        ); //( player->IsTouching( this ) || player->CanSee( this, string->fov, 94 ) == 1 );

        if (bShow && can_see && !is_hidden && !player->IsDead() && !player->IsSpectator()) {
            if (sv_specialgame->integer) {
                // FIXME: delete
                /*
				gi.MSG_SetClient(client - g_entities);

				// Send the hint string once
				gi.MSG_StartCGM(CGM_HINTSTRING);
					gi.MSG_WriteString(m_HintString);
				gi.MSG_EndCGM();
				*/
            }
        } else {
        }
    }
}

void Entity::SetHintRequireLookAt(Event *ev)
{
    m_bHintRequiresLookAt = ev->GetBoolean(1);
}

void Entity::SetHintString(Event *ev)
{
    m_HintString = ev->GetString(1);
}

void Entity::SetShader(Event* ev)
{
    str      shadername = ev->GetString(1);
    qboolean fReset = false;

    if (!shadername.length()) {
        shadername = "default";
        fReset = true;
    }

    gi.SendServerCommand(-1, "setshader %d %s %d", entnum, shadername.c_str(), fReset);
}
#endif

void Entity::PlayNonPvsSound(const str& soundName, float volume)
{
    AliasListNode_t* ret;
    str name;

    if (g_gametype->integer == GT_SINGLE_PLAYER) {
        // Ignore in single-player
        return;
    }

    if (!sv_netoptimize->integer) {
        // don't use sound indexes for nothing
        return;
    }

    if (edict->r.num_nonpvs_sounds >= MAX_NONPVS_SOUNDS) {
        return;
    }

    name = GetRandomAlias(soundName, &ret);
    if (name.length() && ret) {
        nonpvs_sound_t* npvs = &edict->r.nonpvs_sounds[edict->r.num_nonpvs_sounds];

        npvs->index = gi.pvssoundindex(name.c_str(), ret->streamed);
        npvs->volume = G_Random() * ret->volumeMod + ret->volume * volume;
        npvs->minDist = ret->dist;
        npvs->maxDist = ret->maxDist;
        npvs->pitch = G_Random() * ret->pitchMod + ret->pitch;
        edict->r.num_nonpvs_sounds++;
    }
}
