ef2gamesource/dlls/game/entity.cpp
2015-04-26 14:48:05 +02:00

8980 lines
198 KiB
C++

//-----------------------------------------------------------------------------
//
// $Logfile:: /Code/DLLs/game/entity.cpp $
// $Revision:: 271 $
// $Author:: Steven $
// $Date:: 10/13/03 9:43a $
//
// Copyright (C) 2002 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
//
// DESCRIPTION:
// Base class for all entities that are controlled by the game. 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 the game is any object that is not part of the world. Any non-world
// object that is visible in the game 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 the game 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 "_pch_cpp.h"
#include "scriptmaster.h"
#include "weaputils.h"
#include "soundman.h"
#include "earthquake.h"
#include <qcommon/qfiles.h>
#include "mp_manager.hpp"
#include <qcommon/gameplaymanager.h>
// Player events
Event EV_ClientMove
(
"client_move",
EV_DEFAULT,
nullptr,
nullptr,
"The movement packet from the client is processed by this event."
);
Event EV_ClientEndFrame
(
"client_endframe",
EV_DEFAULT,
nullptr,
nullptr,
"Called at the end of each frame for each client."
);
// Generic entity events
Event EV_Classname
(
"classname",
EV_TIKIONLY,
"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."
);
Event EV_SpawnFlags
(
"spawnflags",
EV_CODEONLY,
"i",
"flags",
"spawnflags from the BSP, these are set inside the editor"
);
Event EV_SetTeam
(
"team",
EV_DEFAULT,
"s",
"moveTeam",
"used to make multiple entities move together."
);
Event EV_Trigger
(
"trigger",
EV_SCRIPTONLY,
"s",
"name",
"Trigger the specified target or entity."
);
Event EV_Activate
(
"doActivate",
EV_DEFAULT,
"e",
"activatingEntity",
"General trigger event for all entities"
);
Event EV_Use
(
"doUse",
EV_DEFAULT,
"e",
"activatingEntity",
"sent to entity when it is used by another entity"
);
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"
);
Event EV_FadeOut
(
"_fadeout",
EV_CODEONLY,
nullptr,
nullptr,
"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."
);
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."
);
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."
);
Event EV_Killed
(
"killed",
EV_DEFAULT,
"efei",
"attacker damage inflictor meansofdeath",
"event which is sent to an entity once it as been killed"
);
Event EV_GotKill
(
"gotkill",
EV_DEFAULT,
"eieib",
"victim damage inflictor meansofdeath gib",
"event sent to attacker when an entity dies"
);
Event EV_Pain
(
"pain",
EV_DEFAULT,
"iei",
"damage attacker meansofdeath",
"used to inflict pain to an entity"
);
Event EV_Damage
(
"_damage",
EV_CODEONLY,
"feevvviii",
"damage inflictor attacker position direction normal knockback damageflags meansofdeath",
"general damage event used by all entities"
);
Event EV_Stun
(
"_stun",
EV_CODEONLY,
"f",
"time",
"Stun this entity for the specified time"
);
Event EV_Kill
(
"kill",
EV_CONSOLE,
nullptr,
nullptr,
"console based command to kill yourself if stuck."
);
Event EV_Gib
(
"gib",
EV_DEFAULT,
"iIFS",
"number power scale gibmodel",
"causes entity to spawn a number of gibs"
);
Event EV_Hurt
(
"hurt",
EV_DEFAULT,
"fSV",
"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."
);
Event EV_TakeDamage
(
"takedamage",
EV_DEFAULT,
nullptr,
nullptr,
"makes entity take damage."
);
Event EV_NoDamage
(
"nodamage",
EV_DEFAULT,
nullptr,
nullptr,
"entity does not take damage."
);
Event EV_Stationary
(
"stationary",
EV_DEFAULT,
nullptr,
nullptr,
"entity does not move, causes no physics to be run on it."
);
// Physics events
Event EV_MoveDone
(
"movedone",
EV_DEFAULT,
"e",
"finishedEntity",
"Sent to commanding thread when done with move ."
);
Event EV_Touch
(
"doTouch",
EV_DEFAULT,
"e",
"touchingEntity",
"sent to entity when touched for the first time."
);
Event EV_Contact
(
"inContactWithEntity",
EV_DEFAULT,
"e",
"entityWrapper",
"sent to an entity that is in contact with specified entity."
);
Event EV_LostContact
(
"lostContactWithEntity",
EV_DEFAULT,
"e",
"entityNoLongerInContact",
"sent to an entity no longer in contact with specified entity."
);
Event EV_Blocked
(
"doBlocked",
EV_DEFAULT,
"e",
"obstacle",
"sent to entity when blocked."
);
Event EV_UseBoundingBox
(
"usebbox",
EV_DEFAULT,
nullptr,
nullptr,
"do not perform perfect collision, use bounding box instead."
);
Event EV_Gravity
(
"gravity",
EV_DEFAULT,
"f",
"gravityValue",
"Change the gravity on this entity"
);
Event EV_Stop
(
"stopped",
EV_DEFAULT,
nullptr,
nullptr,
"sent when entity has stopped bouncing for MOVETYPE_TOSS."
);
Event EV_SetFullTrace
(
"fulltrace",
EV_DEFAULT,
"b",
"on_or_off",
"Turns fulltrace physics movement on or off for this entity."
);
Event EV_ProcessInitCommands
(
"processinit",
EV_CODEONLY,
"i",
"modelIndex",
"process the init section of this entity, this is an internal command,\n"
"it is not meant to be called from script."
);
Event EV_Attach
(
"attach",
EV_DEFAULT,
"esIVV",
"parent tagname use_angles offset angles_offset",
"attach this entity to the parent's legs tag called tagname"
);
Event EV_AttachModel
(
"attachmodel",
EV_DEFAULT,
"ssFSBFFFFVV",
"modelname tagname scale targetname detach_at_death removetime fadeintime fadeoutdelay fadetime offset angles_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\n"
"angles_offset - angles offset for the model from the specified tag"
);
Event EV_RemoveAttachedModel
(
"removeattachedmodel",
EV_DEFAULT,
"sFS",
"tagname fadetime modelname",
"Removes the model attached to this entity at the specified tag."
);
Event EV_RemoveAttachedModelByTargetname
(
"removeAttachedModelByTargetname",
EV_DEFAULT,
"s",
"targetname",
"Removes all models that are attached to this entity with the specified targetname."
);
Event EV_Detach
(
"detach",
EV_SCRIPTONLY,
nullptr,
nullptr,
"detach this entity from its parent."
);
Event EV_IncreaseShotCount
(
"increaseshotcount",
EV_DEFAULT,
nullptr,
nullptr,
"boosts the shotsFired on this entity's parent if it is an actor"
);
// script stuff
Event EV_Model
(
"model",
EV_DEFAULT,
"s",
"modelName",
"set the model to modelName."
);
Event EV_Hide
(
"hide",
EV_DEFAULT,
nullptr,
nullptr,
"hide the entity, opposite of show."
);
Event EV_Show
(
"show",
EV_DEFAULT,
nullptr,
nullptr,
"show the entity, opposite of hide."
);
Event EV_BecomeSolid
(
"solid",
EV_DEFAULT,
nullptr,
nullptr,
"make solid."
);
Event EV_BecomeNonSolid
(
"notsolid",
EV_DEFAULT,
nullptr,
nullptr,
"make non-solid."
);
Event EV_Ghost
(
"ghost",
EV_DEFAULT,
nullptr,
nullptr,
"make non-solid but still send to client regardless of hide status."
);
Event EV_TouchTriggers
(
"touchtriggers",
EV_DEFAULT,
"B",
"touch_triggers_bool",
"Sets whether the entity should touch triggers or not"
);
Event EV_Sound
(
"playsound",
EV_DEFAULT,
"sIFS",
"soundName channel volume min_distance",
"play a sound coming from this entity.\n"
"default channel, CHAN_BODY."
);
Event EV_StopSound
(
"stopsound",
EV_DEFAULT,
"I",
"channel",
"stop the current sound on the specified channel.\n"
"default channel, CHAN_BODY."
);
Event EV_Bind
(
"bind",
EV_DEFAULT,
"e",
"parent",
"bind this entity to the specified entity."
);
Event EV_Unbind
(
"unbind",
EV_DEFAULT,
nullptr,
nullptr,
"unbind this entity."
);
Event EV_JoinTeam
(
"joinTeam",
EV_DEFAULT,
"e",
"teamMember",
"join a bind team."
);
Event EV_QuitTeam
(
"quitTeam",
EV_DEFAULT,
nullptr,
nullptr,
"quit the current bind team"
);
Event EV_SetHealth
(
"health",
EV_CHEAT | EV_TIKIONLY,
"i",
"newHealth",
"set the health of the entity to newHealth"
);
Event EV_GetHealth
(
"gethealth",
EV_CHEAT | EV_SCRIPTONLY,
"@f",
"currentHealth",
"Gets the health of the entity"
);
Event EV_SetMaxHealth
(
"maxhealth",
EV_CHEAT,
"i",
"newMaxHealth",
"set the max_health of the entity to newMaxHealth"
);
Event EV_SetScale
(
"scale",
EV_DEFAULT,
"f",
"newScale",
"set the scale of the entity"
);
Event EV_SetRandomScale
(
"randomScale",
EV_DEFAULT,
"fd",
"minScale maxScale",
"Set the scale of the entity randomly between minScale and maxScale"
);
Event EV_SetSize
(
"setsize",
EV_DEFAULT,
"vv",
"mins maxs",
"Set the bounding box of the entity to mins and maxs."
);
Event EV_GetMins
(
"getmins",
EV_SCRIPTONLY,
"@v",
"returnval",
"Returns the minimum extent of the bounding box"
);
Event EV_GetMaxs
(
"getmaxs",
EV_SCRIPTONLY,
"@v",
"returnval",
"Returns the maximum extent of the bounding box"
);
Event EV_SetMins
(
"_setmins",
EV_CODEONLY,
"v",
"mins",
"Set the mins of the bounding box of the entity to mins."
);
Event EV_SetMaxs
(
"_setmaxs",
EV_CODEONLY,
"v",
"maxs",
"Set the maxs of the bounding box of the entity to maxs."
);
Event EV_SetAlpha
(
"alpha",
EV_DEFAULT,
"f",
"newAlpha",
"Set the alpha of the entity to alpha."
);
Event EV_SetOrigin
(
"origin",
EV_SCRIPTONLY,
"v",
"newOrigin",
"Set the origin of the entity to newOrigin."
);
Event EV_SetOriginEveryFrame
(
"setorigineveryframe",
EV_DEFAULT,
nullptr,
nullptr,
"Sets the origin every frame ( for bound objects )"
);
Event EV_GetOrigin
(
"getorigin",
EV_CHEAT | EV_SCRIPTONLY,
"@v",
"returnval",
"Gets the origin of the entity."
);
Event EV_SetTargetName
(
"targetname",
EV_SCRIPTONLY,
"s",
"targetName",
"set the targetname of the entity to targetName."
);
Event EV_GetTargetName
(
"getTargetName",
EV_SCRIPTONLY,
"@s",
"targetName",
"Gets the targetname of the entity with the leading $."
);
Event EV_GetRawTargetName
(
"getRawTargetName",
EV_SCRIPTONLY,
"@s",
"rawTargetName",
"Gets the targetname of the entity without the leading $."
);
Event EV_SetTarget
(
"target",
EV_DEFAULT,
"s",
"targetname_to_target",
"target another entity with targetname_to_target."
);
Event EV_GetTarget
(
"getTarget",
EV_SCRIPTONLY,
"@sB",
"name_of_current_target wantsPrefix",
"Returns the name of the current target."
);
Event EV_GetTargetEntity
(
"gettargetentity",
EV_SCRIPTONLY,
"@e",
"nullptr",
"Returns the current target entity"
);
Event EV_SetKillTarget
(
"killtarget",
EV_DEFAULT,
"s",
"targetName",
"when dying kill entities with this targetName."
);
Event EV_GetModelName
(
"getmodelname",
EV_SCRIPTONLY,
"@s",
"modelname",
"Retrieves the model name of the entity"
);
Event EV_SetAngles
(
"angles",
EV_SCRIPTONLY,
"v[0,360][0,360][0,360]",
"newAngles",
"set the angles of the entity to newAngles."
);
Event EV_GetAngles
(
"getangles",
EV_SCRIPTONLY,
"@v",
"angle_vector",
"Retrieves the entity's angles"
);
Event EV_SetAngle
(
"angle",
EV_DEFAULT,
"f",
"newAngle",
"set the angles of the entity using just one value.\n"
"Sets the yaw of the entity or an up and down\n"
"direction if newAngle is [0-359] or -1 or -2"
);
Event EV_RegisterAlias
(
"alias",
EV_CACHE,
"ssSSSSSS",
"alias realname parameter1 parameter2 parameter3 parameter4 parameter5 paramater6",
"create an alias for the given realname\n"
"Valid parameters are as follows:\n"
"global - all instances act as one\n"
"stop - once used, don't use again\n"
"timeout [seconds] - once used wait [seconds] until using again\n"
"maxuse [times] - can only be used [times] times.\n"
"weight [weight] - random weighting"
);
Event EV_RegisterAliasAndCache
(
"aliascache",
EV_CACHE,
"ssSSSSSS",
"alias realname parameter1 parameter2 parameter3 parameter4 parameter5 paramater6",
"create an alias for the given realname and cache the resource\n"
"Valid parameters are as follows:\n"
"global - all instances act as one\n"
"stop - once used, don't use again\n"
"timeout [seconds] - once used wait [seconds] until using again\n"
"maxuse [times] - can only be used [times] times.\n"
"weight [weight] - random weighting"
);
Event EV_Cache
(
"cache",
EV_CACHE,
"s",
"resourceName",
"pre-cache the given resource."
);
Event EV_AutoCache
(
"autocache",
EV_CACHE | EV_TIKIONLY,
"s",
"resourceName",
"pre-cache the given resource."
);
Event EV_SetMass
(
"mass",
EV_TIKIONLY,
"i",
"massAmount",
"set the mass of this entity."
);
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."
);
Event EV_StopLoopSound
(
"stoploopsound",
EV_DEFAULT,
nullptr,
nullptr,
"Stop the looped-sound on this entity."
);
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"
);
Event EV_SetAnimOnAttachedModel
(
"attachanim",
EV_DEFAULT,
"ss",
"anim_name tag_name",
"sets the anim on an attached entity at the specified tag"
);
Event EV_SetCinematicAnim
(
"setcinematicanim",
EV_DEFAULT,
"s",
"anim_name",
"sets a cinematic anim on an entity"
);
Event EV_CinematicAnimDone
(
"cinematicanimdone",
EV_CODEONLY,
nullptr,
nullptr,
"Called when an entity's cinematic animation is done, "
"it is not meant to be called from script."
);
Event EV_SetEntityExplosionModel
(
"setentityexplosionmodel",
EV_DEFAULT,
"s",
"model_name",
"sets this entitys explosion model which is used in selfDetonate"
);
// AI sound events
Event EV_BroadcastSound
(
"soundevent",
EV_DEFAULT,
"FS",
"soundRadius soundtype",
"Let the AI know that this entity made a sound,\n"
"radius determines how far the sound reaches."
);
Event EV_HeardSound
(
"heardsound",
EV_DEFAULT,
"evi",
"noisyEntity noisyLocation soundtype",
"sent to entities when another makes a sound, not for script use\n"
);
// Conditionals
Event EV_IfSkill
(
"ifskill",
EV_DEFAULT,
"isSSSSS",
"skillLevel command argument1 argument2 argument3 argument4 argument5",
"if the current skill level is skillLevel than execute command"
);
// Lighting
Event EV_SetLight
(
"light",
EV_DEFAULT,
"ffff",
"color_red color_green color_blue radius",
"Create a dynmaic light on this entity."
);
Event EV_LightOn
(
"lightOn",
EV_DEFAULT,
nullptr,
nullptr,
"Turn the configured dynmaic light on this entity on."
);
Event EV_LightOff
(
"lightOff",
EV_DEFAULT,
nullptr,
nullptr,
"Turn the configured dynamic light on this entity off."
);
Event EV_LightStyle
(
"lightStyle",
EV_DEFAULT,
"i",
"lightStyleIndex",
"What light style to use for this dynamic light on this entity."
);
Event EV_LightRed
(
"lightRed",
EV_DEFAULT,
"f",
"red",
"Set the red component of the dynmaic light on this entity."
);
Event EV_LightGreen
(
"lightGreen",
EV_DEFAULT,
"f",
"green",
"Set the green component of the dynmaic light on this entity."
);
Event EV_LightBlue
(
"lightBlue",
EV_DEFAULT,
"f",
"blue",
"Set the blue component of the dynmaic light on this entity."
);
Event EV_LightRadius
(
"lightRadius",
EV_DEFAULT,
"f",
"radius",
"Set the radius of the dynmaic light on this entity."
);
// 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"
"notarget - makes the entity notarget\n"
);
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"
"depthhack - this entity is drawn with depth hack on\n"
"'lightstyle' command to set the index of the light style to be used"
);
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"
"everyframe - process commands every time entity is rendered"
);
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"
);
// Special Effects
Event EV_Censor
(
"censor",
EV_TIKIONLY,
nullptr,
nullptr,
"used to ban certain contact when in parentmode\n"
);
Event EV_Explosion
(
"explosionattack",
EV_DEFAULT,
"sS",
"explosionModel tagName",
"Spawn an explosion optionally from a specific tag"
);
Event EV_DoRadiusDamage
(
"doradiusdamage",
EV_DEFAULT,
"fsffBF",
"damage meansofdeath radius knockback constant_damage repeat_time",
"calls RadiusDamage() using the entity information and owner"
);
Event EV_SelfDetonate
(
"selfdetonate",
EV_DEFAULT,
nullptr,
nullptr,
"spawns and explosion and removes this entity"
);
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"
);
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"
);
Event EV_KillAttach
(
"killattach",
EV_DEFAULT,
nullptr,
nullptr,
"kill all the attached entities."
);
Event EV_DropToFloor
(
"droptofloor",
EV_CODEONLY,
"F",
"maxRange",
"drops the entity to the ground, if maxRange is not specified 8192 is used."
);
Event EV_AddToSoundManager
(
"_addtosoundmanager",
EV_CODEONLY,
nullptr,
nullptr,
"adds the current entity to the sound manager."
);
Event EV_SetControllerAngles
(
"setcontrollerangles",
EV_CODEONLY,
"iv",
"num angles",
"Sets the control angles for the specified bone."
);
Event EV_DeathSinkStart
(
"deathsinkstart",
EV_CODEONLY,
nullptr,
nullptr,
"Makes the entity sink into the ground and then get removed (this starts it)."
);
Event EV_DeathSink
(
"deathsinkeachframe",
EV_CODEONLY,
nullptr,
nullptr,
"Makes the entity sink into the ground and then get removed (this gets called each frame)."
);
Event EV_DamageType
(
"damage_type",
EV_DEFAULT,
"s",
"meansofdeathstring",
"Set the type of damage that this entity can take"
);
Event EV_LookAtMe
(
"lookatme",
EV_DEFAULT,
nullptr,
nullptr,
"Makes the player look at this object if close."
);
Event EV_ProjectilesCanStickToMe
(
"projectilecansticktome",
EV_DEFAULT,
"b",
"can_stick",
"Bool that determines whether projectiles stick in this entity."
);
Event EV_DetachAllChildren
(
"detachallchildren",
EV_SCRIPTONLY,
nullptr,
nullptr,
"Detach all the children from the entity."
);
Event EV_Morph
(
"morph",
EV_DEFAULT,
"sFFB",
"morph_target_name final_percent morph_time return_to_zero",
"Morphs to the specified morph target"
);
Event EV_Unmorph
(
"unmorph",
EV_DEFAULT,
"s",
"morph_target_name",
"Unmorphs the specified morph target"
);
Event EV_MorphControl
(
"morphcontrol",
EV_CODEONLY,
nullptr,
nullptr,
"Does all of the morph work each frame"
);
Event EV_SetObjectProgram
(
"setobjectprogram",
EV_DEFAULT,
"s",
"program",
"sets this objects program"
);
Event EV_ExecuteObjectProgram
(
"executeobjectprogram",
EV_DEFAULT,
"f",
"time",
"executes this objects program at the specified time"
);
Event EV_ProjectileAtk
(
"projectileattack",
EV_DEFAULT,
"sS",
"projectile_name tag_name",
"Launches a projectile"
);
Event EV_ProjectileAttackPoint
(
"projectileattackpoint",
EV_DEFAULT,
"svFF",
"projectileName targetPosition trajectoryAngle lifespan",
"Launches a projectile at the named entity"
);
Event EV_ProjectileAttackEntity
(
"projectileattackentity",
EV_DEFAULT,
"ssFF",
"projectileName entityName trajectoryAngle lifespan",
"Launches a projectile at the named entity"
);
Event EV_ProjectileAttackFromTag
(
"projectileattackfromtag",
EV_DEFAULT,
"ssFF",
"projectileName tagName speed lifespan",
"Launches a projectile from the named tag"
);
Event EV_ProjectileAttackFromPoint
(
"projectileattackfrompoint",
EV_DEFAULT,
"svvFF",
"projectileName position direction speed lifespan",
"Launches a projectile from the desired location"
);
Event EV_TraceAtk
(
"traceattack",
EV_DEFAULT,
"ffSFS",
"damage range means_of_death knockback tag_name",
"Does a trace attack"
);
Event EV_Contents
(
"contents",
EV_DEFAULT,
"sSSSSS",
"ct1 ct2 ct3 ct4 ct5 ct6",
"The content type of the entity"
);
Event EV_Mask
(
"mask",
EV_DEFAULT,
"sSSSSS",
"mask1 mask2 mask3 mask4 mask5 mask6",
"Sets the mask of the entity. Masks can either be real masks or contents. A +before the"
"mask adds it, a - removes it, and nothing sets it"
);
Event EV_DisplayEffect
(
"displayeffect",
EV_DEFAULT,
"sS",
"effect_type effect_name",
"Displays the named effect."
);
Event EV_ForceAlpha
(
"forcealpha",
EV_DEFAULT,
"B",
"bool",
"Sets whether or not alpha is forced"
);
Event EV_SpawnEffect
(
"spawneffect",
EV_DEFAULT,
"ssF",
"modelName tagName removeTime",
"Spawns the effect at the specified tag"
);
Event EV_CreateEarthquake
(
"earthquake",
EV_DEFAULT,
"ffF",
"magnitude duration distance",
"Creates an earthquake"
);
Event EV_SetFloatVar
(
"setfloatvar",
EV_DEFAULT,
"sf",
"variable_name float_value",
"Sets a level, game, or entity variable."
);
Event EV_SetVectorVar
(
"setvectorvar",
EV_DEFAULT,
"sv",
"variable_name vector_value",
"Sets a level, game, or entity variable."
);
Event EV_SetStringVar
(
"setstringvar",
EV_DEFAULT,
"ss",
"variable_name string_value",
"Sets a level, game, or entity variable."
);
Event EV_DoesVarExist
(
"doesVarExist",
EV_SCRIPTONLY,
"@fs",
"float_value variable_name",
"Returns whether or not a variable exists."
);
Event EV_GetFloatVar
(
"getfloatvar",
EV_SCRIPTONLY,
"@fs",
"float_value variable_name",
"Gets a level, game, or entity variable."
);
Event EV_RemoveVariable
(
"removevar",
EV_SCRIPTONLY,
"s",
"variable_name",
"Removes a level, game, or entity variable and frees any memory used by it"
);
Event EV_GetVectorVar
(
"getvectorvar",
EV_SCRIPTONLY,
"@vs",
"vector_value variable_name",
"Gets a level, game, or entity variable."
);
Event EV_GetStringVar
(
"getstringvar",
EV_SCRIPTONLY,
"@ss",
"string_value variable_name",
"Gets a level, game, or entity variable."
);
Event EV_SetUserVar1
(
"uservar1",
EV_DEFAULT,
"s",
"string_value",
"Sets an entity variable."
);
Event EV_SetUserVar2
(
"uservar2",
EV_DEFAULT,
"s",
"string_value",
"Sets an entity variable."
);
Event EV_SetUserVar3
(
"uservar3",
EV_DEFAULT,
"s",
"string_value",
"Sets an entity variable."
);
Event EV_SetUserVar4
(
"uservar4",
EV_DEFAULT,
"s",
"string_value",
"Sets an entity variable."
);
Event EV_AffectingViewMode
(
"viewmode",
EV_DEFAULT,
"s",
"viewModeName",
"Specifies that this entity uses the specified view mode."
);
// These are events that may come from tiki files
Event EV_TikiNote
(
"note",
EV_DEFAULT,
"s",
"note",
"This is a comment"
);
Event EV_TikiTodo
(
"todo",
EV_DEFAULT,
"s",
"todo",
"This is an item that needs to be done"
);
Event EV_SetGroupID
(
"setgroupid",
EV_DEFAULT,
"i",
"groupID",
"Sets the groupID of this entity"
);
Event EV_Multiplayer
(
"multiplayer",
EV_DEFAULT,
"sSSSSS",
"realEventName parm1 parm2 parm3 parm4 parm5",
"Sends off a real event only if this is a multiplayer game"
);
Event EV_DamageModifier
(
"damagemodifier",
EV_DEFAULT,
"ssfFFF",
"modifiertype value multiplier chance minpain maxpain",
"Add a damage modifier to this entities list\n"
" modifiertypes are:\n"
" tikiname, name, group, actortype, targetname, damagetype"
);
Event EV_SetMoveType
(
"setmovetype",
EV_DEFAULT,
"s",
"movetype",
"Sets the move type of this entity\n"
" Valid types are: \n"
" none , stationary , noclip , push , stop\n"
" walk , step , fly , toss , flymissile\n"
" bounce, slider, rope , gib , vehicle"
);
Event EV_HelperNodeCommand
(
"helpernodecommand",
EV_CODEONLY,
"s",
"commandtype",
"Command from a helper node"
);
Event EV_UseDataAnim
(
"useanim",
EV_DEFAULT,
"s",
"animname",
"Animation for the player to play when this entity is used."
);
Event EV_UseDataType
(
"usetype",
EV_DEFAULT,
"s",
"usetype",
"Use type (widget name) for the use icon"
);
Event EV_UseDataThread
(
"usethread",
EV_DEFAULT,
"s",
"threadname",
"Thread to call when this entity is used."
);
Event EV_UseData
(
"usedata",
EV_DEFAULT,
"sss",
"animname usetype threadname",
"Sets data for this usuable entity."
);
Event EV_UseMaxDist
(
"usemaxdist",
EV_DEFAULT,
"f",
"maxdist",
"Sets maximum distance this entity can be used."
);
Event EV_UseCount
(
"usecount",
EV_DEFAULT,
"i",
"count",
"Sets the number of times this entity can be used."
);
Event EV_SetArchetype
(
"archetype",
EV_DEFAULT,
"s",
"archetype",
"Sets the archetype name for this entity"
);
Event EV_SetMissionObjective
(
"missionobjective",
EV_DEFAULT,
"b",
"missionobjective",
"Sets the mission objective flag"
);
Event EV_SetGameplayHealth
(
"gdb_sethealth",
EV_DEFAULT,
"s",
"healthstr",
"Sets the gameplay version of health with keywords."
);
Event EV_SetGameplayDamage
(
"gdb_setdamage",
EV_DEFAULT,
"s",
"damagestr",
"Sets the gameplay version of damage with keywords."
);
Event EV_ProcessGameplayData
(
"processgameplaydata",
EV_TIKIONLY,
nullptr,
nullptr,
"Causes any subclass of entity to process any specific gameplay related data."
);
Event EV_GetVelocity
(
"getvelocity",
EV_SCRIPTONLY,
"@v",
"nullptr",
"Returns the Velocity"
);
Event EV_SetVelocity
(
"setvelocity",
EV_SCRIPTONLY,
"v",
"velocity",
"Sets the Velocity"
);
Event EV_WatchOffset
(
"watchoffset",
EV_DEFAULT,
"v",
"offset",
"Sets the entity's watch offset."
);
Event EV_StartStasis
(
"startStasis",
EV_DEFAULT,
nullptr,
nullptr,
"Makes the entity go into stasis mode"
);
Event EV_StopStasis
(
"stopStasis",
EV_DEFAULT,
nullptr,
nullptr,
"Makes the entity stop its stasis mode"
);
Event EV_SetTargetPos
(
"settargetposition",
EV_DEFAULT,
"s",
"targetbone",
"Sets the Target Position Bone"
);
Event EV_AddHealthOverTime
(
"addHealthOverTime",
EV_DEFAULT,
"ff",
"healthToAdd timeToAdd",
"Specifies how much and howlong to add health."
);
Event EV_SimplePlayDialog
(
"simplePlayDialog",
EV_DEFAULT,
"sFF",
"sound_file volume min_dist",
"Plays a dialog without all the special features the actors' have."
);
Event EV_Warp
(
"warp",
EV_CHEAT,
"v",
"position",
"Warps the entity to the specified position."
);
Event EV_TraceHitsEntity
(
"traceHitsEntity",
EV_SCRIPTONLY,
"sfe",
"tagName length entityToCheck",
"Does a trace to check to see if it hits this entity\n."
"Use this very rarely or a programmer will kill you!"
);
Event EV_SetCustomShader
(
"setcustomshader",
EV_DEFAULT,
"s",
"shader_name",
"Sets Custom Shader FX on Entity"
);
Event EV_ClearCustomShader
(
"clearcustomshader",
EV_DEFAULT,
"s",
"shader_name",
"Clears Custom Shader FX on Entity"
);
Event EV_SetCustomEmitter
(
"setCustomEmitter",
EV_DEFAULT,
"s",
"emitterName",
"Sets up a custom emitter for this entity."
);
Event EV_ClearCustomEmitter
(
"clearCustomEmitter",
EV_DEFAULT,
"s",
"emitterName",
"Clears the custom emitter for this Entity"
);
Event EV_IsWithinDistanceOf
(
"iswithindistanceof",
EV_DEFAULT,
"@fef",
"returnvalue targetEntity distance",
"returns 1.0 if this entity is within the specified distance of the target entity return 0.0 if it is not"
);
Event EV_NetworkDetail
(
"networkDetail",
EV_TIKIONLY,
nullptr,
nullptr,
"Sets this entity as detail that doesn't get sent across the network of set as low bandwidth by the client"
);
CLASS_DECLARATION(Listener, Entity, nullptr)
{
{&EV_DamageModifier, &Entity::AddDamageModifier},
{ &EV_TikiNote, &Entity::TikiNote },
{ &EV_TikiTodo, &Entity::TikiTodo },
{ &EV_Damage, &Entity::DamageEvent },
{ &EV_DamageType, &Entity::DamageType },
{ &EV_Kill, &Entity::Kill },
{ &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_BecomeNonSolid, &Entity::BecomeNonSolid },
{ &EV_Ghost, &Entity::Ghost },
{ &EV_TouchTriggers, &Entity::TouchTriggersEvent },
{ &EV_Sound, &Entity::Sound },
{ &EV_StopSound, &Entity::StopSound },
{ &EV_SetHealth, &Entity::SetHealth },
{ &EV_GetHealth, &Entity::GetHealth },
{ &EV_SetMaxHealth, &Entity::SetMaxHealth },
{ &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_SetRandomScale, &Entity::setRandomScale },
{ &EV_SetAlpha, &Entity::SetAlpha },
{ &EV_SetOrigin, &Entity::SetOrigin },
{ &EV_GetOrigin, &Entity::GetOrigin },
{ &EV_SetTargetName, &Entity::SetTargetName },
{ &EV_GetTargetName, &Entity::GetTargetName },
{ &EV_GetRawTargetName, &Entity::GetRawTargetName },
{ &EV_SetTarget, &Entity::SetTarget },
{ &EV_GetTarget, &Entity::getTarget },
{ &EV_GetTargetEntity, &Entity::GetTargetEntity },
{ &EV_SetKillTarget, &Entity::SetKillTarget },
{ &EV_GetModelName, &Entity::GetModelName },
{ &EV_SetAngles, &Entity::SetAngles },
{ &EV_GetAngles, &Entity::GetAngles },
{ &EV_SetAngle, &Entity::SetAngleEvent },
{ &EV_SetMass, &Entity::SetMassEvent },
{ &EV_SetFullTrace, &Entity::SetFullTraceEvent },
{ &EV_RegisterAlias, &Entity::RegisterAlias },
{ &EV_RegisterAliasAndCache, &Entity::RegisterAliasAndCache },
{ &EV_Cache, &Entity::Cache },
{ &EV_AutoCache, &Entity::Cache },
{ &EV_LoopSound, &Entity::LoopSound },
{ &EV_StopLoopSound, &Entity::StopLoopSound },
{ &EV_Model, &Entity::SetModelEvent },
{ &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_BroadcastSound, &Entity::BroadcastSound },
{ &EV_SurfaceModelEvent, &Entity::SurfaceModelEvent },
{ &EV_ProcessInitCommands, &Entity::ProcessInitCommandsEvent },
{ &EV_Attach, &Entity::AttachEvent },
{ &EV_AttachModel, &Entity::AttachModelEvent },
{ &EV_RemoveAttachedModel, &Entity::RemoveAttachedModelEvent },
{ &EV_RemoveAttachedModelByTargetname, &Entity::removeAttachedModelByTargetname },
{ &EV_Detach, &Entity::DetachEvent },
{ &EV_IncreaseShotCount, &Entity::IncreaseShotCount },
{ &EV_TakeDamage, &Entity::TakeDamageEvent },
{ &EV_NoDamage, &Entity::NoDamageEvent },
{ &EV_Gravity, &Entity::Gravity },
{ &EV_UseBoundingBox, &Entity::UseBoundingBoxEvent },
{ &EV_Hurt, &Entity::HurtEvent },
{ &EV_IfSkill, &Entity::IfSkillEvent },
{ &EV_Classname, &Entity::ClassnameEvent },
{ &EV_SpawnFlags, &Entity::SpawnFlagsEvent },
{ &EV_SetTeam, &Entity::SetTeamEvent },
{ &EV_Trigger, &Entity::TriggerEvent },
{ &EV_Censor, &Entity::Censor },
{ &EV_Stationary, &Entity::StationaryEvent },
{ &EV_Explosion, &Entity::Explosion },
{ &EV_ShaderEvent, &Entity::Shader },
{ &EV_ScriptShaderEvent, &Entity::Shader },
{ &EV_KillAttach, &Entity::KillAttach },
{ &EV_DropToFloor, &Entity::DropToFloorEvent },
{ &EV_Bind, &Entity::BindEvent },
{ &EV_Unbind, &Entity::EventUnbind },
{ &EV_JoinTeam, &Entity::JoinTeam },
{ &EV_QuitTeam, &Entity::EventQuitTeam },
{ &EV_AddToSoundManager, &Entity::AddToSoundManager },
{ &EV_SetControllerAngles, &Entity::SetControllerAngles },
{ &EV_DeathSinkStart, &Entity::DeathSinkStart },
{ &EV_DeathSink, &Entity::DeathSink },
{ &EV_LookAtMe, &Entity::LookAtMe },
{ &EV_ProjectilesCanStickToMe, &Entity::ProjectilesCanStickToMe },
{ &EV_DetachAllChildren, &Entity::DetachAllChildren },
{ &EV_Morph, &Entity::MorphEvent },
{ &EV_Unmorph, &Entity::UnmorphEvent },
{ &EV_MorphControl, &Entity::MorphControl },
{ &EV_SetCinematicAnim, &Entity::SetCinematicAnim },
{ &EV_CinematicAnimDone, &Entity::CinematicAnimDone },
{ &EV_SetAnimOnAttachedModel, &Entity::SetAnimOnAttachedModel },
{ &EV_SetEntityExplosionModel, &Entity::SetEntityExplosionModel },
{ &EV_SetObjectProgram, &Entity::SetObjectProgram },
{ &EV_ExecuteObjectProgram, &Entity::ExecuteProgram },
{ &EV_DoRadiusDamage, &Entity::DoRadiusDamage },
{ &EV_SelfDetonate, &Entity::SelfDetonate },
{ &EV_Anim, &Entity::PassToAnimate },
{ &EV_SetFrame, &Entity::PassToAnimate },
{ &EV_StopAnimating, &Entity::PassToAnimate },
{ &EV_Torso_StopAnimating, &Entity::PassToAnimate },
{ &EV_NewAnim, &Entity::PassToAnimate },
{ &EV_ProjectileAtk, &Entity::ProjectileAtk },
{ &EV_ProjectileAttackPoint, &Entity::ProjectileAttackPoint },
{ &EV_ProjectileAttackEntity, &Entity::ProjectileAttackEntity },
{ &EV_ProjectileAttackFromTag, &Entity::ProjectileAttackFromTag },
{ &EV_ProjectileAttackFromPoint, &Entity::ProjectileAttackFromPoint },
{ &EV_TraceAtk, &Entity::TraceAtk },
{ &EV_Contents, &Entity::Contents },
{ &EV_Mask, &Entity::setMask },
{ &EV_DisplayEffect, &Entity::DisplayEffect },
{ &EV_ForceAlpha, &Entity::ForceAlpha },
{ &EV_SpawnEffect, &Entity::SpawnEffect },
{ &EV_CreateEarthquake, &Entity::CreateEarthquake },
{ &EV_SetFloatVar, &Entity::SetFloatVar },
{ &EV_SetVectorVar, &Entity::SetVectorVar },
{ &EV_SetStringVar, &Entity::SetStringVar },
{ &EV_DoesVarExist, &Entity::doesVarExist },
{ &EV_RemoveVariable, &Entity::RemoveVariable },
{ &EV_GetFloatVar, &Entity::GetFloatVar },
{ &EV_GetVectorVar, &Entity::GetVectorVar },
{ &EV_GetStringVar, &Entity::GetStringVar },
{ &EV_SetUserVar1, &Entity::SetUserVar1 },
{ &EV_SetUserVar2, &Entity::SetUserVar2 },
{ &EV_SetUserVar3, &Entity::SetUserVar3 },
{ &EV_SetUserVar4, &Entity::SetUserVar4 },
{ &EV_AffectingViewMode, &Entity::affectingViewMode },
{ &EV_SetGroupID, &Entity::SetGroupID },
{ &EV_Multiplayer, &Entity::MultiplayerEvent },
{ &EV_SetMoveType, &Entity::setMoveType },
{ &EV_UseDataAnim, &Entity::useDataAnim },
{ &EV_UseDataType, &Entity::useDataType },
{ &EV_UseDataThread, &Entity::useDataThread },
{ &EV_UseData, &Entity::useDataEvent },
{ &EV_UseMaxDist, &Entity::useDataMaxDist },
{ &EV_UseCount, &Entity::useDataCount },
{ &EV_SetArchetype, &Entity::setArchetype },
{ &EV_SetMissionObjective, &Entity::setMissionObjective },
{ &EV_SetGameplayHealth, &Entity::setGameplayHealth },
{ &EV_GetVelocity, &Entity::GetVelocity },
{ &EV_SetVelocity, &Entity::SetVelocity },
{ &EV_WatchOffset, &Entity::SetWatchOffset },
{ &EV_StartStasis, &Entity::startStasis },
{ &EV_StopStasis, &Entity::stopStasis },
{ &EV_SetTargetPos, &Entity::setTargetPos },
{ &EV_AddHealthOverTime, &Entity::addHealthOverTime },
{ &EV_SimplePlayDialog, &Entity::simplePlayDialog },
{ &EV_Warp, &Entity::warp },
{ &EV_TraceHitsEntity, &Entity::traceHitsEntity },
{ &EV_SetOriginEveryFrame, &Entity::setOriginEveryFrame },
{ &EV_SetCustomShader, &Entity::setCustomShader },
{ &EV_ClearCustomShader, &Entity::clearCustomShader },
{ &EV_SetCustomEmitter, &Entity::setCustomEmitter },
{ &EV_ClearCustomEmitter, &Entity::clearCustomEmitter },
{ &EV_IsWithinDistanceOf, &Entity::isWithinDistanceOf },
{ &EV_NetworkDetail, &Entity::setNetworkDetail },
{ nullptr, nullptr }
};
Entity::Entity()
{
Setup();
}
Entity::Entity(int32_t create_flag)
{
Setup();
if (create_flag & EntityCreateFlagAnimate)
{
animate = new Animate(this);
}
if (create_flag & EntityCreateFlagMover)
{
mover = new Mover(this);
}
}
void Entity::Setup()
{
// Pluggable modules
animate = nullptr;
mover = nullptr;
bind_info = nullptr;
morph_info = nullptr;
edict = level.AllocEdict(this);
client = edict->client;
entnum = edict->s.number;
edict->s.clientNum = ENTITYNUM_NONE;
// spawning variables
spawnflags = level.spawnflags;
level.spawnflags = 0;
// rendering variables
setAlpha(1.0f);
setScale(1.0f);
// physics variables
total_delta = Vector(0, 0, 0);
mass = 0;
gravity = 1.0;
groundentity = nullptr;
groundcontents = 0;
velocity = vec_zero;
avelocity = vec_zero;
edict->clipmask = MASK_SOLID;
// bind variables
edict->s.bindparent = ENTITYNUM_NONE;
// this is an generic entity
edict->s.eType = ET_GENERAL;
setContents(0);
edict->s.parent = ENTITYNUM_NONE;
edict->s.pos.trType = TR_LERP;
edict->ownerNum = ENTITYNUM_NONE;
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 = DeadNo;
flags = 0;
// underwater variables
watertype = 0;
waterlevel = 0;
// Pain and damage variables
takedamage = DamageNo;
damage_type = -1;
// Surface variables
numsurfaces = 0;
// Light variables
lightRadius = 0;
look_at_me = false;
projectilesCanStickToMe = true;
explosionModel = "";
_affectingViewModes = 0;
edict->s.infoIcon = 0;
addAffectingViewModes(gi.GetViewModeClassMask("entity"));
_groupID = 0;
damageModSystem = nullptr;
_fulltrace = false;
useData = nullptr;
setTargetPos("");
ObjectProgram = nullptr;
_missionObjective = false;
_networkDetail = false;
}
Entity::~Entity()
{
if (bind_info != nullptr)
{
// unbind any entities that are bound to me
// can't unbind within this loop, so make an array
// and unbind them outside of it.
Container<Entity *> bindlist;
Entity* ent;
for (ent = bind_info->teamchain; ent != nullptr; ent = ent->bind_info->teamchain)
{
if (ent->bind_info->bindmaster == this)
{
bindlist.AddObject(ent);
}
}
auto num = bindlist.NumObjects();
for (auto i = 1; i <= num; i++)
{
bindlist.ObjectAt(i)->unbind();
}
bindlist.FreeObjectList();
unbind();
quitTeam();
detach();
//
// go through and set our children
//
num = bind_info->numchildren;
for (auto i = 0; (i < MAX_MODEL_CHILDREN) && num; i++)
{
if (bind_info->children[i] == ENTITYNUM_NONE)
{
continue;
}
ent = G_GetEntity(bind_info->children[i]);
if (ent != nullptr)
{
ent->PostEvent(EV_Remove, 0.0f);
}
num--;
}
}
if (targetname.length() && world)
{
world->RemoveTargetEntity(targetname, this);
}
level.FreeEdict(edict);
// Pluggable modules
if (animate != nullptr)
{
delete animate;
}
if (mover != nullptr)
{
delete mover;
}
if (bind_info != nullptr)
{
delete bind_info;
}
if (morph_info != nullptr)
{
delete morph_info;
}
entityVars.ClearList();
if (damageModSystem != nullptr)
{
delete damageModSystem;
}
if (useData != nullptr)
{
delete useData;
useData = nullptr;
}
// Note, the ObjectProgram deletion is handled elsewhere
// Make sure to remove our selves from the extra list if we are a mission objective
if (edict->s.missionObjective)
{
G_RemoveEntityFromExtraList(entnum);
}
}
Entity* Entity::FindEntityByName(const str& entityName)
{
for (auto i = 0; i < MAX_GENTITIES; i++)
{
if (g_entities[i].inuse && g_entities[i].entity && entityName == g_entities[i].entname)
{
return g_entities[i].entity;
}
}
return nullptr;
}
void Entity::SetEntNum(int32_t num)
{
if (edict != nullptr)
{
level.FreeEdict(edict);
}
level.spawn_entnum = num;
level.AllocEdict(this);
client = edict->client;
entnum = edict->s.number;
}
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::SetTarget(const char* text)
{
if (text != nullptr)
{
target = text;
} else
{
target = "";
}
}
void Entity::SetTargetName(const char* text)
{
if (targetname.length() && world)
{
world->RemoveTargetEntity(targetname, this);
}
if (text != nullptr)
{
if (text[0] == '$')
{
text++;
}
targetname = text;
} else
{
targetname = "";
}
if (targetname.length() && world)
{
//
// make sure we don't re-targetname the world entity
//
if (this != world || targetname == str("world"))
{
world->AddTargetEntity(targetname, this);
} else
{
error("SetTargetName", "World was re-targeted with targetname %s\n", targetname.c_str());
// this is bad
//assert( 0 );
//gi.WDPrintf( "world was re-targeted with targetname %s\n", targetname.c_str() );
//targetname = "world";
}
}
}
void Entity::SetKillTarget(const char* text)
{
if (text != nullptr)
{
killtarget = text;
} else
{
killtarget = "";
}
}
void Entity::setModel(const char* mdl)
{
str temp;
if (LoadingSavegame && this == world)
{
// don't set model on the world
return;
}
if (mdl == nullptr)
{
mdl = "";
}
// Prepend 'models/' to make things easier
temp = "";
if (strlen(mdl) > 0 && !strchr(mdl, '*') && strnicmp("models/", mdl, 7) && !strstr(mdl, ".spr"))
{
temp = "models/";
}
temp += mdl;
// we use a temp string so that if model was passed into here, we don't
// accidentally free up the string that we're using in the process.
model = temp;
gi.setmodel(edict, model.c_str());
if (gi.IsModel(edict->s.modelindex))
{
Event* ev;
numsurfaces = gi.NumSurfaces(edict->s.modelindex);
if (!LoadingSavegame)
{
CancelEventsOfType(EV_ProcessInitCommands);
ev = new Event(EV_ProcessInitCommands);
ev->AddInteger(edict->s.modelindex);
PostEvent(ev, EV_PROCESS_INIT);
} else
{
ProcessInitCommands(edict->s.modelindex, true);
}
} else if (strstr(mdl, ".spr"))
{
edict->s.eType = ET_SPRITE;
}
// Sanity check to see if we're expecting a B-Model
assert(!(edict->solid == SOLID_BSP && !edict->s.modelindex));
if (edict->solid == SOLID_BSP && !edict->s.modelindex)
{
auto name = getClassID();
if (name == nullptr)
{
name = getClassname();
}
gi.WDPrintf("%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);
}
mins = edict->mins;
maxs = edict->maxs;
size = maxs - mins;
edict->radius = size.length() * 0.5f;
edict->radius2 = edict->radius * edict->radius;
//
// see if we have a mins and maxs set for this model
//
if (gi.IsModel(edict->s.modelindex) && !mins.length() && !maxs.length())
{
vec3_t tempmins, tempmaxs;
int32_t animNum;
animNum = gi.Anim_NumForName(edict->s.modelindex, "idle");
if (animNum >= 0)
{
gi.Frame_Bounds(edict->s.modelindex, animNum, 0, edict->s.scale, tempmins, tempmaxs);
setSize(tempmins, tempmaxs);
}
//vec3_t tempmins, tempmaxs;
//gi.CalculateBounds( edict->s.modelindex, edict->s.scale, tempmins, tempmaxs );
//setSize( tempmins, tempmaxs );
}
if (this->isSubclassOf(Player))
{
//If we're a player, we need to reset the state machine
auto player = dynamic_cast<Player*>(this);
if (player)
{
player->SetAnim("stand_idle", legs, true);
player->SetAnim("stand_idle", torso, true);
player->LoadStateTable();
}
}
}
// Added to set the weapon view model
void Entity::setViewModel(const char* mdl)
{
str temp;
if (LoadingSavegame && this == world)
{
// don't set model on the world
return;
}
if (mdl == nullptr)
{
mdl = "";
}
// Prepend 'models/' to make things easier
temp = "";
if (strlen(mdl) > 0 && !strchr(mdl, '*') && strnicmp("models/", mdl, 7) && !strstr(mdl, ".spr"))
{
temp = "models/";
}
temp += mdl;
// we use a temp string so that if model was passed into here, we don't
// accidentally free up the string that we're using in the process.
model = temp;
gi.setviewmodel(edict, model.c_str());
if (gi.IsModel(edict->s.viewmodelindex))
{
Event* ev;
numsurfaces = gi.NumSurfaces(edict->s.viewmodelindex);
if (!LoadingSavegame)
{
CancelEventsOfType(EV_ProcessInitCommands);
ev = new Event(EV_ProcessInitCommands);
ev->AddInteger(edict->s.viewmodelindex);
PostEvent(ev, EV_PROCESS_INIT);
} else
{
ProcessInitCommands(edict->s.viewmodelindex, true);
}
} else if (strstr(mdl, ".spr") != nullptr)
{
edict->s.eType = ET_SPRITE;
}
// Sanity check to see if we're expecting a B-Model
assert(!(edict->solid == SOLID_BSP && !edict->s.viewmodelindex));
if (edict->solid == SOLID_BSP && !edict->s.viewmodelindex)
{
auto name = getClassID();
if (name == nullptr)
{
name = getClassname();
}
gi.WDPrintf("%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);
}
mins = edict->mins;
maxs = edict->maxs;
size = maxs - mins;
edict->radius = size.length() * 0.5f;
edict->radius2 = edict->radius * edict->radius;
//
// see if we have a mins and maxs set for this model
//
//FIXME
//We only did this on startup, but with the spawnargs as events it would have to
//be here. Do we still need this? It may cause strange effects.
/* if ( gi.IsModel( edict->s.viewmodelindex ) && !mins.length() && !maxs.length() )
{
vec3_t tempmins, tempmaxs;
gi.CalculateBounds( edict->s.viewmodelindex, edict->s.scale, tempmins, tempmaxs );
setSize( tempmins, tempmaxs );
} */
}
void Entity::ProcessInitCommands(int32_t index, qboolean cache)
{
tiki_cmd_t cmds;
if (LoadingSavegame && !cache)
{
// 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;
}
if (gi.InitCommands(index, &cmds))
{
int32_t savedindex;
Event* event;
// because the model has not necessarily been spawned yet, we need to set
// this entity to have this index so that precaches go where they are supposed
// to, this should have no bad effects, since we are only doing it in the
// cache phase of spawning
if (index != edict->s.modelindex)
{
savedindex = edict->s.modelindex;
edict->s.modelindex = index;
} else
{
savedindex = -1;
}
for (auto i = 0; i < cmds.num_cmds; i++)
{
event = new Event(cmds.cmds[i].args[0]);
if (!cache || (event->GetFlags() & EV_CACHE))
{
for (auto j = 1; j < cmds.cmds[i].num_args; j++)
{
event->AddToken(cmds.cmds[i].args[j]);
}
ProcessEvent(event);
} else
{
delete event;
}
}
// restore the modelindex, see above
if (savedindex != -1)
{
edict->s.modelindex = savedindex;
}
}
}
void Entity::ProcessInitCommandsEvent(Event* ev)
{
ProcessInitCommands(ev->GetInteger(1), false);
}
void Entity::EventHideModel(Event*)
{
hideModel();
}
void Entity::EventShowModel(Event*)
{
showModel();
}
void Entity::SetTeamEvent(Event* ev)
{
if (bind_info == nullptr)
{
bind_info = CreateBindInfo();
}
bind_info->moveteam = ev->GetString(1);
}
void Entity::TriggerEvent(Event* ev)
{
auto name = ev->GetString(1);
Event* event;
Entity* ent;
if (name == nullptr)
{
return;
}
// Check for object commands
if (name[0] == '$')
{
auto tlist = world->GetTargetList(str(name + 1));
for (auto i = 1; i <= tlist->list.NumObjects(); i++)
{
ent = tlist->list.ObjectAt(i);
assert(ent);
event = new Event(EV_Activate);
event->SetSource(ev->GetSource());
event->SetThread(ev->GetThread());
event->SetLineNumber(ev->GetLineNumber());
event->AddEntity(this);
ent->ProcessEvent(event);
}
} else if (name[0] == '*') // Check for entnum commands
{
if (!IsNumeric(&name[1]))
{
ev->Error("Expecting numeric value for * command, but found '%s'\n", &name[1]);
} else
{
ent = G_GetEntity(atoi(&name[1]));
if (ent != nullptr)
{
event = new Event(EV_Activate);
event->SetSource(ev->GetSource());
event->SetThread(ev->GetThread());
event->SetLineNumber(ev->GetLineNumber());
event->AddEntity(this);
ent->ProcessEvent(event);
} else
{
ev->Error("Entity not found for * command\n");
}
}
return;
} else
{
ev->Error("Invalid entity reference '%s'.\n", name);
}
}
void Entity::setAlpha(float alpha)
{
if (alpha > 1.0f)
{
alpha = 1.0f;
}
if (alpha < 0.0f)
{
alpha = 0.0f;
}
edict->s.alpha = alpha;
}
void Entity::setScale(float scale)
{
edict->s.scale = scale;
}
void Entity::setSolidType(solid_t type)
{
if (!LoadingSavegame &&
type == SOLID_BSP &&
this != world &&
(!model.length() ||
model[0] != '*' &&
strstr(model.c_str(), ".bsp") == nullptr
)
)
{
error("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())
{
setContents(CONTENTS_SOLID);
}
} else if (edict->solid == SOLID_NOT)
{
if (getContents() == CONTENTS_SOLID)
{
setContents(0);
}
} else if (edict->solid == SOLID_BSP)
{
if (!getContents())
{
setContents(CONTENTS_SOLID);
}
}
link();
edict->svflags &= ~SVF_NOCLIENT;
if (hidden())
{
edict->svflags |= SVF_NOCLIENT;
}
}
void Entity::setSize(Vector min, Vector max)
{
if (flags & FlagRotatedbounds)
{
vec3_t tempmins, tempmaxs;
//
// rotate the mins and maxs for the model
//
min.copyTo(tempmins);
max.copyTo(tempmaxs);
CalculateRotatedBounds2(edict->s.mat, tempmins, tempmaxs);
mins = Vector(tempmins);
maxs = Vector(tempmaxs);
size = max - min;
mins.copyTo(edict->mins);
maxs.copyTo(edict->maxs);
edict->radius = size.length() * 0.5;
edict->radius2 = edict->radius * edict->radius;
} else
{
if (min == edict->mins && max == edict->maxs)
{
return;
}
mins = min;
maxs = max;
size = max - min;
mins.copyTo(edict->mins);
maxs.copyTo(edict->maxs);
//
// get the full mins and maxs for this model
//
/* if ( gi.IsModel( edict->s.modelindex ) )
{
vec3_t fullmins, fullmaxs;
Vector delta;
gi.CalculateBounds( edict->s.modelindex, edict->s.scale, fullmins, fullmaxs );
delta = Vector( fullmaxs ) - Vector( fullmins );
edict->radius = delta.length() * 0.5f;
edict->radius2 = edict->radius * edict->radius;
}
else */
{
edict->radius = size.length() * 0.5;
edict->radius2 = edict->radius * edict->radius;
}
}
link();
}
Vector Entity::getLocalVector(const Vector& vec)
{
Vector pos;
pos[0] = vec * orientation[0];
pos[1] = vec * orientation[1];
pos[2] = vec * orientation[2];
return pos;
}
void Entity::link()
{
if (!level._cleanup)
{
gi.linkentity(edict);
}
absmin = edict->absmin;
absmax = edict->absmax;
centroid = (absmin + absmax) * 0.5f;
centroid.copyTo(edict->centroid);
// If this has a parent, then set the areanum the same
// as the parent's
if (edict->s.parent != ENTITYNUM_NONE)
{
edict->areanum = g_entities[edict->s.parent].areanum;
}
}
void Entity::addOrigin(const Vector& add)
{
setOrigin(GetLocalOrigin() + add);
}
void Entity::setOrigin()
{
setOrigin(GetLocalOrigin());
}
void Entity::setOrigin(const Vector& org)
{
if (bind_info != nullptr && bind_info->bindmaster)
{
SetLocalOrigin(org);
if (bind_info->bind_use_my_angles)
MatrixTransformVector(GetLocalOrigin(), orientation, origin);
else
MatrixTransformVector(GetLocalOrigin(), bind_info->bindmaster->orientation, origin);
origin += bind_info->bindmaster->origin;
origin.copyTo(edict->s.netorigin);
}
// 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 if (edict->s.parent != ENTITYNUM_NONE)
{
orientation_t orient;
VectorClear(edict->s.netorigin);
auto ent = dynamic_cast<Entity *>(G_GetEntity(edict->s.parent));
//ent->GetTag(( edict->s.tag_num & TAG_MASK, &origin );
ent->GetTag(edict->s.tag_num & TAG_MASK, &orient);
MatrixTransformVector(edict->s.attach_offset, orient.axis, origin);
//origin += edict->s.attach_offset;
origin += orient.origin;
SetLocalOrigin(vec_zero);
} else
{
origin = org;
SetLocalOrigin(org);
origin.copyTo(edict->s.netorigin);
}
origin.copyTo(edict->s.origin);
origin.copyTo(edict->currentOrigin);
link();
#if 0
if (this->isClient())
{
i = CurrentAnim();
j = CurrentFrame();
G_DrawCoordSystem( origin, orientation[0], orientation[1], orientation[2], 30 );
gi.Printf( "%s:legs anim:%s frame %i\n", this->getClassname(), gi.Anim_NameForNum( edict->s.modelindex, i ), j );
}
#endif
if (bind_info)
{
// Go through and set our children
auto num = bind_info->numchildren;
for (auto i = 0; i < MAX_MODEL_CHILDREN && num; i++)
{
if (bind_info->children[i] == ENTITYNUM_NONE)
{
continue;
}
auto ent = dynamic_cast<Entity *>(G_GetEntity(bind_info->children[i]));
if (ent != nullptr)
{
ent->setOrigin();
}
num--;
}
/* for( ent = bind_info->teamchain; ent != nullptr; ent = ent->bind_info->teamchain )
{
if ( ent->bind_info->teammaster == this )
ent->setOrigin();
} */
}
}
void Entity::GetRawTag(int32_t tagnum, orientation_t* orient, bodypart_t part)
{
CurrentFrame(part);
//If we don't have a valid animation, we can't get a tag
if (CurrentAnim(part) < 0)
{
return;
}
*orient = gi.Tag_OrientationEx(edict->s.modelindex, CurrentAnim(legs), CurrentFrame(legs), tagnum & TAG_MASK,
edict->s.scale, edict->s.bone_tag, edict->s.bone_quat, 0, 0, 1.0f, (edict->s.anim & ANIM_BLEND) != 0,
(edict->s.torso_anim & ANIM_BLEND) != 0, CurrentAnim(torso), CurrentFrame(torso), 0, 0, 1.0f);
}
qboolean Entity::GetRawTag(const char* name, orientation_t* orient, bodypart_t part)
{
auto tagnum = gi.Tag_NumForName(edict->s.modelindex, name);
if (tagnum < 0)
{
return false;
}
GetRawTag(tagnum, orient, part);
return true;
}
void Entity::GetTag(int32_t tagnum, orientation_t* orient)
{
orientation_t orn;
GetRawTag(tagnum, &orn);
VectorCopy(origin, orient->origin);
for (auto i = 0; i < 3; i++)
{
VectorMA(orient->origin, orn.origin[i], orientation[i], orient->origin);
}
MatrixMultiply(orn.axis, orientation, orient->axis);
}
qboolean Entity::GetTag(const char* name, orientation_t* orient)
{
auto tagnum = gi.Tag_NumForName(edict->s.modelindex, name);
if (tagnum < 0)
{
return false;
}
GetTag(tagnum, orient);
return true;
}
void Entity::GetTag(int32_t tagnum, Vector* pos, Vector* forward, Vector* left, Vector* up)
{
orientation_t orn;
GetTag(tagnum, &orn);
if (pos != nullptr)
{
*pos = Vector(orn.origin);
}
if (forward != nullptr)
{
*forward = Vector(orn.axis[0]);
}
if (left != nullptr)
{
*left = Vector(orn.axis[1]);
}
if (up != nullptr)
{
*up = Vector(orn.axis[2]);
}
}
qboolean Entity::GetTag(const char* name, Vector* pos, Vector* forward, Vector* left, Vector* up)
{
auto tagnum = gi.Tag_NumForName(edict->s.modelindex, name);
if (tagnum < 0)
{
return false;
}
GetTag(tagnum, pos, forward, left, up);
return true;
}
void Entity::addAngles(const Vector& add)
{
if (bind_info && bind_info->bindmaster)
{
setAngles(localangles + add);
} else
{
setAngles(angles + add);
}
}
void Entity::setAngles()
{
if (bind_info && bind_info->bindmaster)
{
setAngles(localangles);
} else
{
setAngles(angles);
}
}
void Entity::setAngles(const Vector& ang)
{
angles[0] = AngleMod(ang[0]);
angles[1] = AngleMod(ang[1]);
angles[2] = AngleMod(ang[2]);
localangles = angles;
if (bind_info && bind_info->bindmaster)
{
float mat[3][3];
AnglesToAxis(localangles, mat);
R_ConcatRotations(mat, bind_info->bindmaster->orientation, orientation);
MatrixToEulerAngles(orientation, angles);
} else if (edict->s.parent != ENTITYNUM_NONE)
{
Entity* parent;
Vector tagPos;
Vector tagForward;
Vector forwardAngles;
vec3_t tempAxis[3];
vec3_t tempAxis2[3];
vec3_t finalAxis[3];
vec3_t tempForward;
vec3_t finalForward;
parent = static_cast<Entity *>(G_GetEntity(edict->s.parent));
parent->GetTag(edict->s.tag_num, &tagPos, &tagForward);
forwardAngles = tagForward.toAngles();
forwardAngles.copyTo(tempForward);
AnglesToAxis(tempForward, tempAxis);
AnglesToAxis(edict->s.attach_angles_offset, tempAxis2);
MatrixMultiply(tempAxis2, tempAxis, finalAxis);
AxisToAngles(finalAxis, finalForward);
angles = finalForward;
AnglesToAxis(angles, orientation);
} else
{
AnglesToAxis(angles, orientation);
}
angles.copyTo(edict->s.netangles);
angles.copyTo(edict->s.angles);
angles.copyTo(edict->currentAngles);
// Fill the edicts matrix
VectorCopy(orientation[0], edict->s.mat[0]);
VectorCopy(orientation[1], edict->s.mat[1]);
VectorCopy(orientation[2], edict->s.mat[2]);
if (this->isSubclassOf(Player))
{
auto player = static_cast<Player*>(this);
player->GetVAngles().copyTo(edict->s.viewangles);
} else
{
edict->s.viewangles[0] = 0.0f;
edict->s.viewangles[1] = 0.0f;
edict->s.viewangles[2] = 0.0f;
}
if (bind_info)
{
// Go through and set our children
auto num = bind_info->numchildren;
for (auto i = 0; i < MAX_MODEL_CHILDREN && num; i++)
{
if (bind_info->children[i] == ENTITYNUM_NONE)
continue;
auto ent = dynamic_cast<Entity *>(G_GetEntity(bind_info->children[i]));
if (ent)
{
ent->setAngles();
VectorClear(ent->edict->s.netangles);
}
num--;
}
/* for( ent = bind_info->teamchain; ent != nullptr; ent = ent->bind_info->teamchain )
{
if ( ent->bind_info->teammaster == this )
ent->setAngles();
} */
}
}
qboolean Entity::droptofloor(float maxfall)
{
auto start = origin;
auto end = origin;
end[2] -= maxfall;
auto trace = G_Trace(start, mins, maxs, end, this, edict->clipmask, false, "Entity::droptofloor");
if (trace.fraction == 1.0f || trace.startsolid || trace.allsolid || !trace.ent)
{
groundentity = world->edict;
return false;
}
setOrigin(trace.endpos);
groundentity = trace.ent;
return true;
}
void Entity::DamageType(Event* ev)
{
str damage = ev->GetString(1);
damage_type = damage == "all" ? -1 : MOD_NameToNum(damage);
}
void Entity::Damage(Entity* inflictor, Entity* attacker, float damage, const Vector& position,
const Vector& direction, const Vector& normal, int32_t knockback, int32_t dflags,
int32_t meansofdeath, int32_t surface_number, int32_t bone_number, Entity* weapon)
{
// if our damage types do not match, return
if (!MOD_matches(meansofdeath, damage_type))
{
return;
}
if (attacker == nullptr)
{
attacker = world;
}
if (inflictor == nullptr)
{
inflictor = world;
}
auto ev = new Event(EV_Damage);
ev->AddFloat(damage);
ev->AddEntity(inflictor);
ev->AddEntity(attacker);
ev->AddVector(position);
ev->AddVector(direction);
ev->AddVector(normal);
ev->AddInteger(knockback);
ev->AddInteger(dflags);
ev->AddInteger(meansofdeath);
ev->AddInteger(surface_number);
ev->AddInteger(bone_number);
ev->AddEntity(weapon);
ProcessEvent(ev);
}
void Entity::DamageEvent(Event* ev)
{
Vector dir;
Vector momentum;
Event* event;
float m;
if (takedamage == DamageNo || movetype == MOVETYPE_NOCLIP)
{
return;
}
auto damage = ev->GetFloat(1);
auto inflictor = ev->GetEntity(2);
auto attacker = ev->GetEntity(3);
if (inflictor == nullptr || attacker == nullptr)
{
return;
}
// figure momentum add
if (inflictor != world &&
movetype != MOVETYPE_NONE &&
movetype != MOVETYPE_STATIONARY &&
movetype != MOVETYPE_BOUNCE &&
movetype != MOVETYPE_PUSH &&
movetype != MOVETYPE_STOP)
{
dir = origin - (inflictor->origin + (inflictor->mins + inflictor->maxs) * 0.5f);
dir.normalize();
m = mass < 50 ? 50 : mass;
momentum = dir * damage * (1700.0f / float(m));
velocity += momentum;
}
// check for god mode or invincibility
if (flags & FlagGodmode)
{
return;
}
// team play damage avoidance
//if ( ( global->teamplay == 1 ) && ( edict->team > 0 ) && ( edict->team == attacker->edict->team ) )
// {
// return;
// }
if (!multiplayerManager.inMultiplayer() && isSubclassOf(Player))
{
damage *= 0.15f;
}
if (deadflag)
{
// Check for gib.
if (inflictor->isSubclassOf(Projectile))
{
health -= damage;
auto gibEv = new Event(EV_Gib);
gibEv->AddEntity(this);
gibEv->AddFloat(health);
ProcessEvent(gibEv);
}
return;
}
// do the damage
health -= damage;
if (health <= 0.0f)
{
if (attacker != nullptr)
{
event = new Event(EV_GotKill);
event->AddEntity(this);
event->AddFloat(damage);
event->AddEntity(inflictor);
event->AddInteger(ev->GetInteger(9));
event->AddInteger(0);
attacker->ProcessEvent(event);
}
event = new Event(EV_Killed);
event->AddEntity(attacker);
event->AddFloat(damage);
event->AddEntity(inflictor);
ProcessEvent(event);
return;
}
event = new Event(EV_Pain);
event->AddFloat(damage);
event->AddEntity(attacker);
ProcessEvent(event);
}
void Entity::Stun(float time)
{
auto ev = new Event(EV_Stun);
ev->AddFloat(time);
ProcessEvent(ev);
}
/*
============
CanDamage
Returns true if the inflictor can directly damage the target. Used for
explosions and melee attacks.
============
*/
qboolean Entity::CanDamage(const Entity* target, const Entity* skip_ent)
{
const Entity* skip_entity = skip_ent != nullptr ? skip_ent : this;
int32_t maskToUse = CONTENTS_SOLID;
auto trace = G_Trace(origin, vec_origin, vec_origin, target->centroid, skip_entity, maskToUse, false, "Entity::CanDamage 1");
if (trace.fraction == 1.0f || trace.ent == target->edict)
{
return true;
}
auto pos = target->centroid + Vector(15.0f, 15.0f, 0.0f);
trace = G_Trace(origin, vec_origin, vec_origin, pos, skip_entity, maskToUse, false, "Entity::CanDamage 3");
if (trace.fraction == 1.0f || trace.ent == target->edict)
{
return true;
}
pos = target->centroid + Vector(-15.0f, 15.0f, 0.0f);
trace = G_Trace(origin, vec_zero, vec_zero, pos, skip_entity, maskToUse, false, "Entity::CanDamage 4");
if (trace.fraction == 1.0f || trace.ent == target->edict)
{
return true;
}
pos = target->centroid + Vector(15.0f, -15.0f, 0.0f);
trace = G_Trace(origin, vec_zero, vec_zero, pos, skip_entity, maskToUse, false, "Entity::CanDamage 5");
if (trace.fraction == 1.0f || trace.ent == target->edict)
{
return true;
}
pos = target->centroid + Vector(-15.0f, -15.0f, 0.0f);
trace = G_Trace(origin, vec_zero, vec_zero, pos, skip_entity, maskToUse, false, "Entity::CanDamage 6");
if (trace.fraction == 1.0f || trace.ent == target->edict)
{
return true;
}
return false;
}
qboolean Entity::IsTouching(const 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;
}
void Entity::FadeNoRemove(Event* ev)
{
float rate;
float target;
if (ev->NumArgs() > 1)
{
target = ev->GetFloat(2);
} else
{
target = 0;
}
if (ev->NumArgs() > 0)
{
rate = ev->GetFloat(1);
assert(rate);
if (rate > 0.0f)
rate = FRAMETIME / rate;
} else
{
rate = 0.03f;
}
auto myalpha = edict->s.alpha - rate;
if (myalpha < target)
{
myalpha = target;
}
setAlpha(myalpha);
if (myalpha > target)
{
PostEvent(*ev, FRAMETIME);
}
G_SetConstantLight(&edict->s.constantLight, &myalpha, &myalpha, &myalpha, nullptr);
}
void Entity::FadeOut(Event* ev)
{
auto myscale = edict->s.scale - 0.03f;
auto myalpha = edict->s.alpha - 0.03f;
if (myscale < 0.0f)
{
myscale = 0.0f;
}
if (myalpha < 0.0f)
{
myalpha = 0.0f;
}
if (myscale <= 0.0f && myalpha <= 0.0f)
{
PostEvent(EV_Remove, 0.0f);
} else
{
PostEvent(*ev, FRAMETIME);
}
setScale(myscale);
setAlpha(myalpha);
}
void Entity::FadeIn(Event* ev)
{
float rate;
float target;
if (ev->NumArgs() > 1)
{
target = ev->GetFloat(2);
} else
{
target = 1;
}
if (ev->NumArgs() > 0)
{
rate = ev->GetFloat(1);
assert(rate);
if (rate > 0.0f)
rate = FRAMETIME / rate;
} else
{
rate = 0.03f;
}
auto myalpha = edict->s.alpha + rate;
if (myalpha > target)
{
myalpha = target;
}
if (myalpha < target)
{
PostEvent(*ev, FRAMETIME);
}
setAlpha(myalpha);
}
void Entity::Fade(Event* ev)
{
float rate;
float target;
if (ev->NumArgs() > 1)
{
target = ev->GetFloat(2);
} else
{
target = 0;
}
if (ev->NumArgs() > 0)
{
rate = ev->GetFloat(1);
assert(rate);
if (rate > 0.0f)
rate = FRAMETIME / rate;
} else
{
rate = 0.03f;
}
auto myalpha = edict->s.alpha - rate;
if (myalpha <= 0.0f)
{
PostEvent(EV_Remove, 0.0f);
return;
}
if (myalpha < target)
{
myalpha = target;
}
if (myalpha > target)
{
PostEvent(*ev, FRAMETIME);
}
setAlpha(myalpha);
G_SetConstantLight(&edict->s.constantLight, &myalpha, &myalpha, &myalpha, nullptr);
}
void Entity::SetMassEvent(Event* ev)
{
mass = ev->GetInteger(1);
}
void Entity::CheckGround()
{
if (flags & (FlagSwim | FlagFly))
{
return;
}
if (velocity.z > 100.0f)
{
groundentity = nullptr;
return;
}
// if the hull point one-quarter unit down is solid the entity is on ground
auto point = origin;
point.z -= sv_groundtracelength->value;
auto trace = G_Trace(origin, mins, maxs, point, this, edict->clipmask, false, "Entity::CheckGround");
if (!(trace.surfaceFlags & SURF_TERRAIN))
{
point.z = origin.z - 0.25f; // put groundtrace dist back to 0.25f for non-terrain check
trace = G_Trace(origin, mins, maxs, point, this, edict->clipmask, false, "Entity::CheckGround");
}
// check steepness
if (trace.plane.normal[2] <= 0.7f && !trace.startsolid)
{
groundentity = nullptr;
return;
}
groundentity = trace.ent;
groundplane = trace.plane;
groundcontents = trace.contents;
if (!trace.startsolid && !trace.allsolid)
{
setOrigin(trace.endpos);
velocity.z = 0;
}
}
void Entity::BecomeSolid(Event*)
{
if (model.length() && (model[0] == '*' || strstr(model.c_str(), ".bsp")))
{
setSolidType(SOLID_BSP);
} else
{
setSolidType(SOLID_BBOX);
}
}
void Entity::BecomeNonSolid(Event*)
{
setSolidType(SOLID_NOT);
}
void Entity::Ghost(Event*)
{
// Make not solid, but send still send over whether it is hidden or not
setSolidType(SOLID_NOT);
edict->svflags &= ~SVF_NOCLIENT;
}
void Entity::LoopSound(Event* ev)
{
if (ev->NumArgs() < 1)
{
return;
}
auto volume = DEFAULT_VOL;
auto min_dist = DEFAULT_MIN_DIST;
// Get parameters
str sound_name = ev->GetString(1);
if (ev->NumArgs() > 1)
{
volume = ev->GetFloat(2);
}
if (ev->NumArgs() > 2)
{
str min_dist_string = ev->GetString(3);
min_dist = min_dist_string == LEVEL_WIDE_STRING ? LEVEL_WIDE_MIN_DIST : ev->GetFloat(3);
if (min_dist >= LEVEL_WIDE_MIN_DIST_CUTOFF)
{
min_dist = LEVEL_WIDE_MIN_DIST;
}
}
// Add this sound to loop
LoopSound(sound_name, volume, min_dist);
}
void Entity::LoopSound(const str& sound_name, float volume, float min_dist)
{
// Get the real sound to be played
if (sound_name.length() > 0)
{
// Get the real sound to play
auto name = gi.GlobalAlias_FindRandom(sound_name.c_str());
if (name == nullptr)
{
auto random_alias = GetRandomAlias(sound_name).c_str();
if (strlen(random_alias) > 0)
{
name = random_alias;
}
}
if (name == nullptr)
{
name = sound_name.c_str();
}
// Add the looping sound to the entity
edict->s.loopSound = gi.soundindex(name);
edict->s.loopSoundVolume = volume;
edict->s.loopSoundMinDist = min_dist;
}
}
void Entity::StopLoopSound(Event*)
{
StopLoopSound();
}
void Entity::StopLoopSound(void)
{
edict->s.loopSound = 0;
}
void Entity::Sound(Event* ev)
{
str sound_name;
auto volume = DEFAULT_VOL;
int32_t channel = CHAN_BODY;
auto min_dist = DEFAULT_MIN_DIST;
str min_dist_string;
// Get sound parameters
for (auto i = 1; i <= ev->NumArgs(); i++)
{
switch (i - 1)
{
case 0:
sound_name = ev->GetString(i);
break;
case 1:
channel = ev->GetInteger(i);
break;
case 2:
volume = ev->GetFloat(i);
break;
case 3:
min_dist_string = ev->GetString(i);
min_dist = min_dist_string == LEVEL_WIDE_STRING ? LEVEL_WIDE_MIN_DIST : ev->GetFloat(i);
if (min_dist >= LEVEL_WIDE_MIN_DIST_CUTOFF)
{
min_dist = LEVEL_WIDE_MIN_DIST;
}
break;
default:
break;
}
}
Sound(sound_name, channel, volume, min_dist, nullptr);
}
void Entity::StopSound(Event* ev)
{
if (ev->NumArgs() < 1)
StopSound(CHAN_BODY);
else
StopSound(ev->GetInteger(1));
}
void Entity::StopSound(int32_t 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*)
{
G_SetConstantLight(&edict->s.constantLight, nullptr, nullptr, nullptr, &lightRadius);
}
void Entity::LightOff(Event*)
{
auto radius = 0.0f;
G_SetConstantLight(&edict->s.constantLight, nullptr, nullptr, nullptr, &radius);
}
void Entity::LightRed(Event* ev)
{
auto r = ev->GetFloat(1);
G_SetConstantLight(&edict->s.constantLight, &r, nullptr, nullptr, nullptr);
}
void Entity::LightGreen(Event* ev)
{
auto g = ev->GetFloat(1);
G_SetConstantLight(&edict->s.constantLight, nullptr, &g, nullptr, nullptr);
}
void Entity::LightBlue(Event* ev)
{
auto b = ev->GetFloat(1);
G_SetConstantLight(&edict->s.constantLight, nullptr, nullptr, &b, nullptr);
}
void Entity::LightRadius(Event* ev)
{
lightRadius = ev->GetFloat(1);
G_SetConstantLight(&edict->s.constantLight, nullptr, nullptr, nullptr, &lightRadius);
}
void Entity::LightStyle(Event* ev)
{
auto style = ev->GetInteger(1);
G_SetConstantLight(&edict->s.constantLight, nullptr, nullptr, nullptr, nullptr, &style);
}
void Entity::SetHealth(Event* ev)
{
health = ev->GetFloat(1);
if (max_health < health)
{
max_health = health;
}
}
//--------------------------------------------------------------
//
// Name: setGameplayHealth
// Class: Entity
//
// Description: This function acts as a filter to the real function.
// It gets data from the database, and then passes it
// along to the original event. This is here as an attempt
// to sway people into using the database standard instead of
// hardcoded numbers.
//
// Parameters: Event *ev
// str -- The value keyword from the database (low, medium, high, etc).
//
// Returns: None
//
//--------------------------------------------------------------
void Entity::setGameplayHealth(Event* ev)
{
if (ev->NumArgs() < 1)
{
return;
}
auto gpm = GameplayManager::getTheGameplayManager();
if (!gpm->hasFormula("Health"))
return;
str healthstr = ev->GetString(1);
auto healthmod = 1.0f;
if (gpm->getDefine(healthstr) != "")
healthmod = float(atof(gpm->getDefine(healthstr)));
GameplayFormulaData fd(this);
auto finalhealth = gpm->calculate("Health", fd, healthmod);
auto newev = new Event(EV_SetHealth);
newev->AddFloat(finalhealth);
ProcessEvent(newev);
}
void Entity::setGameplayDamage(Event*)
{
}
//----------------------------------------------------------------
// Name: addHealth
// Class: Entity
//
// Description: Adds health to the entity up to the max health of the entity or the
// max health specified (if its not 0)
//
// Parameters: float healthToAdd - health we are going to add to the entity
// float maxHealth - if not 0, the max health the entity can have
//
// Returns: none
//----------------------------------------------------------------
void Entity::addHealth(float healthToAdd, float maxHealth)
{
auto tempMaxHealth = maxHealth ? maxHealth : max_health;
auto newHealth = health + healthToAdd;
// Set the new health
setHealth(newHealth > tempMaxHealth ? tempMaxHealth : newHealth);
}
void Entity::GetHealth(Event* ev)
{
ev->ReturnFloat(health);
}
void Entity::SetMaxHealth(Event* ev)
{
max_health = ev->GetFloat(1);
}
void Entity::SetSize(Event* ev)
{
setSize(ev->GetVector(1), ev->GetVector(2));
}
void Entity::SetMins(Event* ev)
{
setSize(ev->GetVector(1), maxs);
}
void Entity::SetMaxs(Event* ev)
{
setSize(mins, ev->GetVector(1));
}
void Entity::GetMins(Event* ev)
{
ev->ReturnVector(mins);
}
void Entity::GetMaxs(Event* ev)
{
ev->ReturnVector(maxs);
}
void Entity::SetScale(Event* ev)
{
setScale(ev->GetFloat(1));
}
void Entity::setRandomScale(Event* ev)
{
auto minScale = ev->GetFloat(1);
auto maxScale = ev->GetFloat(2);
setScale(G_Random(maxScale - minScale) + minScale);
}
void Entity::SetAlpha(Event* ev)
{
setAlpha(ev->GetFloat(1));
}
void Entity::SetOrigin(Event* ev)
{
setOrigin(ev->GetVector(1));
}
void Entity::GetOrigin(Event* ev)
{
ev->ReturnVector(origin);
}
void Entity::SetTargetName(Event* ev)
{
SetTargetName(ev->GetString(1));
}
//----------------------------------------------------------------
// Name: GetRawTargetName
// Class: Entity
//
// Description: Gets the targetname of the entity without the leading $
//
// Parameters: None
//
// Returns: str targetname (through ev) - the targetname of the entity
//----------------------------------------------------------------
void Entity::GetRawTargetName(Event* ev)
{
ev->ReturnString(targetname);
}
//----------------------------------------------------------------
// Name: GetTargetName
// Class: Entity
//
// Description: Gets the targetname of the entity with the leading $
//
// Parameters: None
//
// Returns: str targetname (through ev) - the targetname of the entity
//----------------------------------------------------------------
void Entity::GetTargetName(Event* ev)
{
str nameToReturn;
nameToReturn = "$";
nameToReturn += targetname;
ev->ReturnString(nameToReturn);
}
void Entity::SetTarget(Event* ev)
{
SetTarget(ev->GetString(1));
}
void Entity::getTarget(Event* ev)
{
str nameToReturn;
if (ev->NumArgs() > 0 ? ev->GetBoolean(1) : false)
{
nameToReturn = "$";
}
nameToReturn += target;
ev->ReturnString(nameToReturn);
}
//--------------------------------------------------------------
// Name: GetTargetEntity()
// Class: Entity
//
// Description: Returns the Entity of the target
//
// Parameters: Event *ev
//
// Returns: None ( Entity, though, through the event )
//--------------------------------------------------------------
void Entity::GetTargetEntity(Event* ev)
{
auto tlist = world->GetTargetList(target, false);
if (tlist != nullptr)
{
ev->ReturnEntity(tlist->GetNextEntity(nullptr));
} else
{
ev->ReturnEntity(nullptr);
}
}
void Entity::SetKillTarget(Event* ev)
{
SetKillTarget(ev->GetString(1));
}
//-----------------------------------------------------
//
// Name: GetModelName
// Class: Entity
//
// Description: Retrieves the model name
//
// Parameters: event - the event to request the model name
//
// Returns: None
//-----------------------------------------------------
void Entity::GetModelName(Event* ev)
{
ev->ReturnString(model.c_str());
}
void Entity::SetAngles(Event* ev)
{
setAngles(ev->GetVector(1));
}
//-----------------------------------------------------
//
// Name: GetAngles
// Class: Entity
//
// Description: Retrieves the entity's angles
//
// Parameters: event - event to request the entity's angles
//
// Returns: None
//-----------------------------------------------------
void Entity::GetAngles(Event* ev)
{
ev->ReturnVector(angles);
}
Vector Entity::GetControllerAngles(int32_t num)
{
Vector controller_angles;
assert(num >= 0 && num < NUM_BONE_CONTROLLERS);
if (num < 0 || num >= NUM_BONE_CONTROLLERS)
{
error("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(int32_t num, vec3_t angles)
{
assert(num >= 0 && num < NUM_BONE_CONTROLLERS);
if (num < 0 || num >= NUM_BONE_CONTROLLERS)
{
error("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)
{
if (ev->NumArgs() < 2)
return;
auto num = ev->GetInteger(1);
auto angles = ev->GetVector(2);
SetControllerAngles(num, angles);
}
void Entity::SetControllerTag(int32_t num, int32_t tag_num)
{
assert(num >= 0 && num < NUM_BONE_CONTROLLERS);
if (num < 0 || num >= NUM_BONE_CONTROLLERS)
{
error("SetControllerTag", "Bone controller index out of range (%d)\n", num);
return;
}
edict->s.bone_tag[num] = tag_num;
}
//===============================================================
// Name: SetFullTraceEvent
// Class: Entity
//
// Description: Sets the fulltrace flag. This flag is used by
// G_PushMove to determine if the object needs a
// fulltrace or not. Default for flag is false.
//
// Parameters: Event* -- first argument is boolean
//
// Returns: None
//
//===============================================================
inline void Entity::SetFullTraceEvent(Event* ev)
{
_fulltrace = ev->GetBoolean(1);
}
void Entity::RegisterAlias(Event* ev)
{
char parameters[100];
// Get the parameters for this alias command
parameters[0] = 0;
for (auto i = 3; i <= ev->NumArgs(); i++)
{
strcat(parameters, ev->GetString(i));
strcat(parameters, " ");
}
gi.Alias_Add(edict->s.modelindex, ev->GetString(1), ev->GetString(2), parameters);
}
void Entity::Cache(Event* ev)
{
CacheResource(ev->GetString(1), this);
}
void Entity::RegisterAliasAndCache(Event* ev)
{
RegisterAlias(ev);
CacheResource(ev->GetString(2), this);
}
void Entity::Sound(const str& sound_name, int32_t channel, float volume, float min_dist,
Vector* sound_origin, float pitch_modifier, qboolean onlySendToThisEntity)
{
if (sound_name.length() > 0)
{
// Get the real sound to play
auto name = gi.GlobalAlias_FindRandom(sound_name.c_str());
if (name == nullptr)
{
str random_alias = GetRandomAlias(sound_name).c_str();
if (random_alias.length() > 0)
name = random_alias.c_str();
}
if (name == nullptr)
name = sound_name.c_str();
// Play the sound
if (name != nullptr)
{
vec3_t org;
if (sound_origin != nullptr)
{
sound_origin->copyTo(org);
entnum = ENTITYNUM_NONE;
} else
{
VectorCopy(edict->s.origin, org);
}
gi.Sound(&org, entnum, channel, name, volume, min_dist, pitch_modifier, onlySendToThisEntity);
}
} else
{
warning("Sound", "Null sample pointer");
}
}
qboolean Entity::attach(int32_t parent_entity_num, int32_t tag_num, qboolean use_angles,
Vector offset, Vector angles_offset)
{
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
//
auto parent = dynamic_cast<Entity *>(G_GetEntity(parent_entity_num));
if (!parent->bind_info)
parent->bind_info = CreateBindInfo();
if (parent->bind_info->numchildren < MAX_MODEL_CHILDREN)
{
//
// find a free spot in the parent
//
int32_t i;
for (i = 0; i < MAX_MODEL_CHILDREN; i++)
{
if (parent->bind_info->children[i] == ENTITYNUM_NONE)
{
break;
}
}
edict->s.parent = parent_entity_num;
setSolidType(SOLID_NOT);
parent->bind_info->children[i] = entnum;
parent->bind_info->numchildren++;
if (tag_num >= 0)
{
edict->s.tag_num = tag_num;
}
edict->s.attach_use_angles = use_angles;
offset.copyTo(edict->s.attach_offset);
angles_offset.copyTo(edict->s.attach_angles_offset);
setOrigin();
return true;
}
return false;
}
void Entity::KillAttach(Event*)
{
if (bind_info)
{
// Kill all of this entities children
for (auto i = 0; i < MAX_MODEL_CHILDREN; i++)
{
if (bind_info->children[i] != ENTITYNUM_NONE)
{
// Remove child
auto child = dynamic_cast<Entity *>(G_GetEntity(bind_info->children[i]));
if (child)
child->ProcessEvent(EV_Remove);
// Remove child from this entity
bind_info->children[i] = ENTITYNUM_NONE;
}
}
bind_info->numchildren = 0;
}
}
void Entity::detach()
{
if (edict->s.parent == ENTITYNUM_NONE)
return;
auto parent = dynamic_cast<Entity *>(G_GetEntity(edict->s.parent));
if (!parent)
return;
if (parent->bind_info)
{
auto num = parent->bind_info->numchildren;
for (auto i = 0; i < MAX_MODEL_CHILDREN; i++)
{
if (parent->bind_info->children[i] == ENTITYNUM_NONE)
{
continue;
}
if (parent->bind_info->children[i] == entnum)
{
parent->bind_info->children[i] = ENTITYNUM_NONE;
parent->bind_info->numchildren--;
break;
}
num--;
if (!num)
break;
}
}
edict->s.parent = ENTITYNUM_NONE;
setOrigin(origin);
}
void Entity::Flags(Event* ev)
{
const char* flag;
int32_t mask;
int32_t action;
for (auto i = 1; i <= ev->NumArgs(); i++)
{
flag = ev->GetString(i);
switch (flag[0])
{
case '+':
action = FLAG_ADD;
flag++;
break;
case '-':
action = FLAG_CLEAR;
flag++;
break;
default:
ev->Error("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 (!stricmp(flag, "blood"))
mask = FlagBlood;
else if (!stricmp(flag, "explode"))
mask = FlagDieExplode;
else if (!stricmp(flag, "die_gibs"))
mask = FlagDieGibs;
else if (!stricmp(flag, "autoaim"))
mask = FlagAutoaim;
else if (!stricmp(flag, "god"))
mask = FlagGodmode;
else if (!stricmp(flag, "notarget"))
mask = FlagNotarget;
else
{
mask = 0;
action = FLAG_IGNORE;
ev->Error("Unknown flag '%s'", flag);
}
switch (action)
{
case FLAG_ADD:
flags |= mask;
break;
case FLAG_CLEAR:
flags &= ~mask;
break;
case FLAG_IGNORE:
break;
}
}
if (!com_blood->integer)
{
if (flags & (FlagBlood | FlagDieGibs))
{
flags &= ~FlagBlood;
flags &= ~FlagDieGibs;
}
}
}
void Entity::Effects(Event* ev)
{
const char* flag;
auto mask = 0;
int32_t action;
for (auto i = 1; i <= ev->NumArgs(); i++)
{
flag = ev->GetString(i);
switch (flag[0])
{
case '+':
action = FLAG_ADD;
flag++;
break;
case '-':
action = FLAG_CLEAR;
flag++;
break;
default:
ev->Error("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 (!stricmp(flag, "everyframe"))
mask = EF_EVERYFRAME;
else
{
action = FLAG_IGNORE;
ev->Error("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;
auto mask = 0;
int32_t action;
for (auto i = 1; i <= ev->NumArgs(); i++)
{
flag = ev->GetString(i);
switch (flag[0])
{
case '+':
action = FLAG_ADD;
flag++;
break;
case '-':
action = FLAG_CLEAR;
flag++;
break;
default:
ev->Error("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 (!stricmp(flag, "dontdraw"))
mask = RF_DONTDRAW;
else if (!stricmp(flag, "betterlighting"))
mask = RF_EXTRALIGHT;
else if (!stricmp(flag, "lensflare"))
mask = RF_LENSFLARE;
else if (!stricmp(flag, "viewlensflare"))
mask = RF_VIEWLENSFLARE;
else if (!stricmp(flag, "lightoffset"))
mask = RF_LIGHTOFFSET;
else if (!stricmp(flag, "skyorigin"))
mask = RF_SKYORIGIN;
else if (!stricmp(flag, "fullbright"))
mask = RF_FULLBRIGHT;
else if (!stricmp(flag, "minlight"))
mask = RF_MINLIGHT;
else if (!stricmp(flag, "additivedynamiclight"))
mask = RF_ADDITIVE_DLIGHT;
else if (!stricmp(flag, "lightstyledynamiclight"))
mask = RF_LIGHTSTYLE_DLIGHT;
else if (!stricmp(flag, "shadow"))
mask = RF_SHADOW;
else if (!stricmp(flag, "shadowFromBip01"))
mask = RF_SHADOW_FROM_BIP01;
else if (!stricmp(flag, "preciseshadow"))
mask = RF_SHADOW_PRECISE;
else if (!stricmp(flag, "dontInheritAlpha"))
mask = RF_CHILDREN_DONT_INHERIT_ALPHA;
else if (!stricmp(flag, "invisible"))
mask = RF_INVISIBLE;
else if (!stricmp(flag, "depthhack"))
mask = RF_DEPTHHACK;
else
{
action = FLAG_IGNORE;
ev->Error("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;
auto mask = 0;
int32_t action;
for (auto i = 1; i <= ev->NumArgs(); i++)
{
flag = ev->GetString(i);
switch (flag[0])
{
case '+':
action = FLAG_ADD;
flag++;
break;
case '-':
action = FLAG_CLEAR;
flag++;
break;
default:
ev->Error("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 (!stricmp(flag, "broadcast"))
mask = SVF_BROADCAST;
else if (!stricmp(flag, "sendonce"))
mask = SVF_SENDONCE;
else
{
action = FLAG_IGNORE;
ev->Error("Unknown token %s.", flag);
}
switch (action)
{
case FLAG_ADD:
edict->svflags |= mask;
break;
case FLAG_CLEAR:
edict->svflags &= ~mask;
break;
case FLAG_IGNORE:
break;
}
}
if (edict->svflags & SVF_SENDONCE)
{
// Turn this entity into an event if the SENDONCE flag is sent
edict->s.eType = ET_EVENTS;
edict->svflags &= ~SVF_SENT;
}
}
void Entity::BroadcastSound(float rad, int32_t soundType /*Defaults to SOUNDTYPE_GENERAL*/)
{
if (!(this->flags & FlagNotarget))
{
G_BroadcastSound(this, centroid, rad, soundType);
}
}
void Entity::BroadcastSound(Event* ev)
{
float rad = SOUND_RADIUS;
int32_t soundTypeIdx = SOUNDTYPE_GENERAL;
if (!(this->flags & FlagNotarget))
{
if (ev->NumArgs() > 0) //<-- At least 1 Parameter
{
rad = ev->GetFloat(1);
if (ev->NumArgs() > 1) //<-- At least 2 Parameters
{
auto soundTypeStr = ev->GetString(2);
soundTypeIdx = Soundtype_string_to_int(soundTypeStr);
}
}
if (soundTypeIdx == SOUNDTYPE_FOOTSTEPS_RUN || soundTypeIdx == SOUNDTYPE_FOOTSTEPS_WALK)
rad = ModifyFootstepSoundRadius(rad, soundTypeIdx);
BroadcastSound(rad, soundTypeIdx);
}
}
float Entity::ModifyFootstepSoundRadius(float radius, int32_t soundTypeIdx)
{
auto start = origin;
auto end = origin;
end[2] -= 1000.0f;
auto trace = G_Trace(start, mins, maxs, end, this, edict->clipmask, false, "Entity::ModifyFootstepsRadius");
auto surftype = trace.surfaceFlags & MASK_SURF_TYPE;
switch (surftype)
{
case SURF_TYPE_DIRT:
radius *= .5f;
break;
case SURF_TYPE_ROCK:
break;
case SURF_TYPE_METAL:
radius *= 1.5;
break;
case SURF_TYPE_GRILL:
radius *= 1.25f;
break;
case SURF_TYPE_ORGANIC:
radius *= .5f;
break;
case SURF_TYPE_SQUISHY:
radius *= .5f;
break;
case SURF_TYPE_SAND:
radius *= .5f;
break;
case SURF_TYPE_SNOW:
radius *= .5f;
break;
case SURF_TYPE_METAL_DUCT:
radius *= 1.25f;
break;
case SURF_TYPE_METAL_HOLLOW:
radius *= 1.25f;
break;
case SURF_TYPE_CARPET:
radius *= .5f;
break;
}
//Tone Radius Down for Walking
if (soundTypeIdx == SOUNDTYPE_FOOTSTEPS_WALK)
radius *= .75f;
return radius;
}
void Entity::Think()
{
}
void Entity::SetWaterType()
{
waterlevel = gi.pointcontents(origin, 0) & MASK_WATER ? 1 : 0;
}
void Entity::DamageSkin(trace_t*, float)
{
/* 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*)
{
health = 0.0f;
Damage(this, this, 10.0f, origin, vec_zero, vec_zero, 0, 0, MOD_SUICIDE);
}
void Entity::SurfaceCommand(const char* surf_name, const char* token)
{
const char* current_surface_name;
int32_t surface_num;
int32_t mask;
int32_t 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->s.modelindex, surf_name);
if (surface_num < 0)
{
warning("SurfaceCommand", "group %s not found for entity %s (%d), model %s.\n", surf_name, targetname.c_str(), entnum, model.c_str());
return;
}
} else
{
surface_num = 0;
do_all = true;
}
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 '+' for entity %s (%d), model %s\n", targetname.c_str(), entnum, model.c_str());
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 (!stricmp(token, "skin1"))
{
mask = MDL_SURFACE_SKINOFFSET_BIT0;
} else if (!strcmpi(token, "skin2"))
{
mask = MDL_SURFACE_SKINOFFSET_BIT1;
} else if (!strcmpi(token, "nodraw"))
{
mask = MDL_SURFACE_NODRAW;
} else if (!strcmpi(token, "crossfade"))
{
mask = MDL_SURFACE_CROSSFADE_SKINS;
} else
{
mask = 0;
warning("SurfaceCommand", "Unknown token %s. for entity %s (%d), model %s", token, targetname.c_str(), entnum, model.c_str());
action = FLAG_IGNORE;
}
for (; surface_num < numsurfaces; surface_num++)
{
if (mult)
{
current_surface_name = gi.Surface_NumToName(edict->s.modelindex, 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)
{
auto surf_name = ev->GetString(1);
for (auto i = 2; i <= ev->NumArgs(); i++)
{
auto token = ev->GetString(i);
SurfaceCommand(surf_name, token);
}
}
void Entity::AttachEvent(Event* ev)
{
qboolean use_angles = true;
Vector offset;
Vector angles_offset;
auto parent = ev->GetEntity(1);
auto bone = ev->GetString(2);
if (ev->NumArgs() > 2)
use_angles = ev->GetInteger(3);
if (ev->NumArgs() > 3)
offset = ev->GetVector(4);
if (ev->NumArgs() > 4)
angles_offset = ev->GetVector(5);
if (!parent)
return;
auto tagnum = gi.Tag_NumForName(parent->edict->s.modelindex, bone);
if (tagnum >= 0)
{
attach(parent->entnum, tagnum, use_angles, offset, angles_offset);
} else
{
warning("AttachEvent", "Tag %s not found", bone);
}
}
void Entity::AttachModelEvent(Event* ev)
{
float fade_time, fade_delay;
Vector offset;
Vector angles_offset;
qboolean use_angles = false;
auto obj = new Entity(EntityCreateFlagAnimate);
obj->bind_info = CreateBindInfo();
str modelname = ev->GetString(1);
auto 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->bind_info->detach_at_death = ev->GetInteger(5);
if (ev->NumArgs() > 5)
{
auto remove_time = ev->GetFloat(6);
if (remove_time > 0.0f)
{
auto 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 > 0.0f)
{
obj->setAlpha(0.0f);
fade_event = new Event(EV_FadeIn);
fade_event->AddFloat(fade_time);
obj->PostEvent(fade_event, 0.0f);
}
}
if (ev->NumArgs() > 7)
{
Event* fade_event;
fade_delay = ev->GetFloat(8);
if (fade_delay != -1.0f)
{
if (ev->NumArgs() > 8)
fade_time = ev->GetFloat(9);
else
fade_time = 0.0f;
fade_event = new Event(EV_Fade);
if (fade_time > 0.0f)
fade_event->AddFloat(fade_time);
obj->PostEvent(fade_event, fade_delay);
}
}
if (ev->NumArgs() > 9)
offset = ev->GetVector(10);
if (ev->NumArgs() > 10)
{
angles_offset = ev->GetVector(11);
use_angles = false;
}
obj->setModel(modelname);
if (!obj->animate)
{
auto newAnimate = new Animate;
if (newAnimate)
obj->animate = newAnimate;
}
auto anim_num = gi.Anim_Random(obj->edict->s.modelindex, "idle");
if (anim_num != -1 && obj->animate)
{
obj->animate->NewAnim(anim_num);
}
auto tagnum = gi.Tag_NumForName(edict->s.modelindex, bone);
if (tagnum >= 0)
{
if (!obj->attach(this->entnum, tagnum, use_angles, offset, angles_offset))
{
//warning( "AttachModelEvent", "Could not attach model %s", modelname.c_str() );
delete obj;
return;
}
} else
{
warning("AttachModelEvent", "Tag %s not found", bone);
}
}
void Entity::RemoveAttachedModelEvent(Event* ev)
{
float fade_rate = 0;
str model_name;
if (bind_info)
{
auto tag_name = ev->GetString(1);
auto tag_num = gi.Tag_NumForName(edict->s.modelindex, tag_name);
if (ev->NumArgs() > 1)
fade_rate = ev->GetFloat(2);
if (ev->NumArgs() > 2)
model_name = ev->GetString(3);
if (tag_num >= 0)
{
auto num = bind_info->numchildren;
for (auto i = 0; i < MAX_MODEL_CHILDREN && num; i++)
{
if (bind_info->children[i] == ENTITYNUM_NONE)
continue;
auto ent = dynamic_cast<Entity *>(G_GetEntity(bind_info->children[i]));
if (ent && ent->edict->s.tag_num == tag_num)
{
if (!model_name.length() || model_name == ent->model)
{
if (fade_rate)
{
auto fade_event = new Event(EV_Fade);
fade_event->AddFloat(fade_rate);
fade_event->AddFloat(0.0f);
ent->PostEvent(fade_event, 0.0f);
}
ent->PostEvent(EV_Remove, fade_rate);
}
}
num--;
}
}
}
}
void Entity::removeAttachedModelByTargetname(Event* ev)
{
removeAttachedModelByTargetname(ev->GetString(1));
}
void Entity::removeAttachedModelByTargetname(const str& targetNameToRemove)
{
if (bind_info)
{
auto num = bind_info->numchildren;
for (auto i = 0; i < MAX_MODEL_CHILDREN && num; i++)
{
if (bind_info->children[i] == ENTITYNUM_NONE)
continue;
auto ent = dynamic_cast<Entity *>(G_GetEntity(bind_info->children[i]));
if (ent && stricmp(ent->targetname, targetNameToRemove.c_str()) == 0)
{
ent->PostEvent(EV_Remove, 0.0f);
}
num--;
}
}
}
void Entity::DetachEvent(Event*)
{
if (edict->s.parent == ENTITYNUM_NONE)
{
return;
}
detach();
}
void Entity::TakeDamageEvent(Event*)
{
takedamage = DamageYes;
}
void Entity::NoDamageEvent(Event*)
{
takedamage = DamageNo;
}
void Entity::Gravity(Event* ev)
{
gravity = ev->GetFloat(1);
}
void Entity::UseBoundingBoxEvent(Event*)
{
edict->svflags |= SVF_USEBBOX;
}
void Entity::HurtEvent(Event* ev)
{
float dmg;
int means_of_death;
Vector direction;
if (ev->NumArgs() < 1)
{
dmg = 50.0f;
} else
{
dmg = ev->GetFloat(1);
}
if (ev->NumArgs() > 1)
means_of_death = MOD_NameToNum(ev->GetString(2));
else
means_of_death = MOD_CRUSH;
if (ev->NumArgs() > 2)
{
direction = ev->GetVector(3);
direction.normalize();
} else
{
direction = vec_zero;
}
auto normal = Vector(orientation[0]);
Damage(world, world, dmg, centroid, direction, normal, static_cast<int>(dmg), 0, means_of_death);
}
void Entity::IfSkillEvent(Event* ev)
{
if (skill->value == ev->GetFloat(1))
{
auto numargs = ev->NumArgs();
auto argc = numargs - 2 + 1;
auto event = new Event(ev->GetToken(2));
for (auto i = 1; i < argc; i++)
{
event->AddToken(ev->GetToken(2 + i));
}
ProcessEvent(event);
}
}
void Entity::Censor(Event*)
{
if (com_blood->integer)
return;
auto oldsize = size.length();
setSolidType(SOLID_NOT);
setModel("censored.tik");
gi.CalculateBounds(edict->s.modelindex, 1.0f, mins, maxs);
auto delta = maxs - mins;
auto newsize = delta.length();
edict->s.scale = oldsize / newsize;
mins *= edict->s.scale;
maxs *= edict->s.scale;
setSize(mins, maxs);
setOrigin();
}
void Entity::StationaryEvent(Event*)
{
setMoveType(MOVETYPE_STATIONARY);
}
void Entity::Explosion(Event* ev)
{
auto explosion_origin = origin;
if (ev->NumArgs() > 1)
{
GetTag(ev->GetString(2), &explosion_origin);
}
ExplosionAttack(explosion_origin, this, ev->GetString(1));
}
//----------------------------------------------------------------
// Name: DoRadiusDamage
// Class: Entity
//
// Description: Does radius damage from entity origin,
// optionally re-posting itself to some future time
//
// Parameters: Event *ev
// Event params: (1) float damage
// (2) const char * means of death string
// (3) float radius
// (4) float knockback
// (5) (optional) bool constant damage over distance
// (6) (optional) float forward re-posting time
//
// Returns: nullptr
//----------------------------------------------------------------
void Entity::DoRadiusDamage(Event* ev)
{
auto owner = this;
auto damage = ev->GetFloat(1);
auto modstring = ev->GetString(2);
auto radius = ev->GetFloat(3);
auto knockback = ev->GetFloat(4);
auto constant_damage = false;
if (ev->NumArgs() >= 5) // constant damage info is supplied
constant_damage = ev->GetBoolean(5);
if (this->isSubclassOf(Projectile))
{
auto projectile = static_cast<Projectile *>(this);
owner = projectile->getOwner();
}
if (owner && owner->isSubclassOf(Player))
{
auto player = dynamic_cast<Player *>(owner);
damage = player->getDamageDone(damage, MOD_NameToNum(modstring), false);
}
auto damageDone = RadiusDamage(this,
owner,
damage,
this,
MOD_NameToNum(modstring),
radius,
knockback,
constant_damage
);
if (damageDone && this->isSubclassOf(Projectile))
{
auto projectile = dynamic_cast<Projectile *>(this);
projectile->didDamage();
}
if (ev->NumArgs() == 6)
{
// if repost time is set, re-post this event
auto postTime = ev->GetFloat(6);
if (postTime >= FRAMETIME)
{
auto ev2 = new Event(ev);
PostEvent(ev2, postTime);
}
}
}
void Entity::SelfDetonate(Event*)
{
if (explosionModel.length() == 0)
explosionModel = "fx/fx-sml-exp.tik";
ExplosionAttack(origin, this, explosionModel);
}
void Entity::Shader(Event* ev)
{
if (gi.IsModel(edict->s.modelindex))
{
ev->Error("shader event being called on TIKI model\n");
}
//
// get sub shader command
//
auto 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 (!strcmpi(token, "translation"))
{
TRANSLATION_TO_PKT(int(ev->GetFloat(2)), edict->s.tag_num);
TRANSLATION_TO_PKT(int(ev->GetFloat(3)), edict->s.skinNum);
} else if (!strcmpi(token, "offset"))
{
OFFSET_TO_PKT(ev->GetFloat(2), edict->s.tag_num);
OFFSET_TO_PKT(ev->GetFloat(3), edict->s.skinNum);
} else if (!strcmpi(token, "rotation"))
{
ROTATE_TO_PKT(ev->GetFloat(2), edict->s.tag_num);
} else if (!strcmpi(token, "frame"))
{
edict->s.frame = ev->GetInteger(2);
} else if (!strcmpi(token, "wavebase"))
{
BASE_TO_PKT(ev->GetFloat(2), edict->s.surfaces[0]);
} else if (!strcmpi(token, "waveamp"))
{
AMPLITUDE_TO_PKT(ev->GetFloat(2), edict->s.surfaces[1]);
} else if (!strcmpi(token, "wavephase"))
{
PHASE_TO_PKT(ev->GetFloat(2), edict->s.surfaces[2]);
} else if (!strcmpi(token, "wavefreq"))
{
FREQUENCY_TO_PKT(ev->GetFloat(2), edict->s.surfaces[3]);
}
}
void Entity::DropToFloorEvent(Event* ev)
{
droptofloor(ev->NumArgs() > 0 ? ev->GetFloat(1) : WORLD_SIZE);
}
//*************************************************************************
//
// BIND code
//
//*************************************************************************
qboolean Entity::isBoundTo(const Entity* master)
{
if (bind_info)
{
for (auto ent = bind_info->bindmaster; ent != nullptr; ent = ent->bind_info->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;
}
if (!bind_info)
bind_info = CreateBindInfo();
// unbind myself from my master
unbind();
bind_info->bindmaster = master;
edict->s.bindparent = master->entnum;
bind_info->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(bind_info->bindmaster->orientation, mat);
R_ConcatRotations(mat, orientation, local);
MatrixToEulerAngles(local, ang);
setAngles(ang);
setOrigin(getParentVector(GetLocalOrigin() - bind_info->bindmaster->origin));
return;
}
void Entity::unbind()
{
Entity* next;
Entity* ent;
if (!bind_info || !bind_info->bindmaster)
return;
//bindmaster = nullptr;
// Check this GAMEFIX - should it be origin?
SetLocalOrigin(edict->s.origin);
localangles = Vector(edict->s.angles);
if (!bind_info->teammaster)
{
bind_info->bindmaster = nullptr;
edict->s.bindparent = ENTITYNUM_NONE;
//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
auto prev = bind_info->teammaster;
for (ent = bind_info->teammaster->bind_info->teamchain; ent && ent != this; ent = ent->bind_info->teamchain)
{
prev = ent;
}
// If ent is not pointing to me, then something is very wrong.
assert(ent);
if (!ent)
{
error("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.
auto last = this;
for (next = bind_info->teamchain; next != nullptr; next = next->bind_info->teamchain)
{
if (!next->isBoundTo(this))
{
break;
}
// Tell them I'm now the teammaster
next->bind_info->teammaster = this;
last = next;
}
// disconnect the last member of our team from the old team
last->bind_info->teamchain = nullptr;
// connect up the previous member of the old team to the node that
// follow the last node bound to me (if one exists).
if (bind_info->teammaster != this)
{
prev->bind_info->teamchain = next;
if (!next && bind_info->teammaster == prev)
{
prev->bind_info->teammaster = nullptr;
}
} 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->bind_info->teamchain != nullptr; ent = ent->bind_info->teamchain)
{
ent->bind_info->teammaster = next;
}
next->bind_info->teammaster = next;
next->flags &= ~FlagTeamslave;
}
// If we don't have anyone on our team, then clear the team variables.
if (bind_info->teamchain)
{
// make myself my own team
bind_info->teammaster = this;
} else
{
// no longer a team
bind_info->teammaster = nullptr;
}
flags &= ~FlagTeamslave;
bind_info->bindmaster = nullptr;
edict->s.bindparent = ENTITYNUM_NONE;
}
void Entity::EventUnbind(Event*)
{
unbind();
}
void Entity::BindEvent(Event* ev)
{
auto ent = ev->GetEntity(1);
if (ent)
{
bind(ent);
}
}
Vector Entity::getParentVector(const Vector& vec)
{
Vector pos;
if (!bind_info || !bind_info->bindmaster)
{
return vec;
}
pos[0] = vec * bind_info->bindmaster->orientation[0];
pos[1] = vec * bind_info->bindmaster->orientation[1];
pos[2] = vec * bind_info->bindmaster->orientation[2];
return pos;
}
//
// Team methods
//
void Entity::joinTeam(Entity* teammember)
{
Entity* ent;
Entity* prev;
if (!bind_info)
bind_info = CreateBindInfo();
if (bind_info->teammaster && bind_info->teammaster != this)
{
quitTeam();
}
assert(teammember);
if (!teammember)
{
warning("joinTeam", "Null entity");
return;
}
if (!teammember->bind_info)
teammember->bind_info = CreateBindInfo();
auto master = teammember->bind_info->teammaster;
if (!master)
{
master = teammember;
teammember->bind_info->teammaster = teammember;
teammember->bind_info->teamchain = this;
// make anyone who's bound to me part of the new team
for (ent = bind_info->teamchain; ent != nullptr; ent = ent->bind_info->teamchain)
{
ent->bind_info->teammaster = master;
}
} else
{
// skip past the chain members bound to the entity we're teaming up with
prev = teammember;
auto next = teammember->bind_info->teamchain;
if (bind_info->bindmaster)
{
// if we have a bindmaster, joing after any entities bound to the entity
// we're joining
while (next && next->isBoundTo(teammember))
{
prev = next;
next = next->bind_info->teamchain;
}
} else
{
// if we're not bound to someone, then put us at the end of the team
while (next)
{
prev = next;
next = next->bind_info->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->bind_info->teamchain != nullptr; ent = ent->bind_info->teamchain)
{
ent->bind_info->teamchain->bind_info->teammaster = master;
}
prev->bind_info->teamchain = this;
ent->bind_info->teamchain = next;
}
bind_info->teammaster = master;
flags |= FlagTeamslave;
}
void Entity::quitTeam()
{
if (!bind_info || !bind_info->teammaster)
{
return;
}
if (bind_info->teammaster == this)
{
if (!bind_info->teamchain->bind_info->teamchain)
{
bind_info->teamchain->bind_info->teammaster = nullptr;
} else
{
// make next teammate the teammaster
for (auto ent = bind_info->teamchain; ent; ent = ent->bind_info->teamchain)
{
ent->bind_info->teammaster = bind_info->teamchain;
}
}
bind_info->teamchain->flags &= ~FlagTeamslave;
} else
{
assert(flags & FlagTeamslave);
assert(bind_info->teammaster->bind_info->teamchain);
auto ent = bind_info->teammaster;
while (ent->bind_info->teamchain != this)
{
// this should never happen
assert(ent->bind_info->teamchain);
ent = ent->bind_info->teamchain;
}
ent->bind_info->teamchain = bind_info->teamchain;
if (!bind_info->teammaster->bind_info->teamchain)
{
bind_info->teammaster->bind_info->teammaster = nullptr;
}
}
bind_info->teammaster = nullptr;
bind_info->teamchain = nullptr;
flags &= ~FlagTeamslave;
}
void Entity::EventQuitTeam(Event*)
{
quitTeam();
}
void Entity::JoinTeam(Event* ev)
{
auto ent = ev->GetEntity(1);
if (ent)
{
joinTeam(ent);
}
}
void Entity::AddToSoundManager(Event*)
{
SoundMan.AddEntity(this);
}
qboolean Entity::HitSky(const trace_t* trace)
{
assert(trace);
return trace->surfaceFlags & SURF_SKY ? true : false;
}
qboolean Entity::HitSky()
{
return HitSky(&level.impact_trace);
}
void Entity::SetAngleEvent(Event* ev)
{
auto movedir = G_GetMovedir(ev->GetFloat(1));
setAngles(movedir.toAngles());
}
void Entity::NoLerpThisFrame(void)
{
edict->s.eFlags ^= EF_TELEPORT_BIT;
// Make sure no one is standing on us
for (auto checkEdict = active_edicts.next; checkEdict != &active_edicts; checkEdict = checkEdict->next)
{
assert(checkEdict);
assert(checkEdict->inuse);
if (checkEdict->entity && checkEdict->entity->groundentity == edict)
{
checkEdict->entity->groundentity = nullptr;
}
}
}
void Entity::Postthink(void)
{
}
//-----------------------------------------------------
//
// Name: TouchTriggersEvent
// Class: Entity
//
// Description: Specifies the entity can touch triggers by setting
// the touch triggers flag.
//
// Parameters: ev - the event that specifies whether to turn
// touch triggers on or off. If no param is specified,
// touchtriggers is true.
//
// Returns: None
//-----------------------------------------------------
void Entity::TouchTriggersEvent(Event* ev)
{
if (ev->NumArgs() == 0 || ev->GetBoolean(1) == true)
{
flags |= FlagTouchTriggers;
turnThinkOn();
} else
{
flags &= ~FlagTouchTriggers;
}
}
void Entity::IncreaseShotCount(Event*)
{
auto parent_ent_num = edict->s.parent;
auto parent = G_GetEntity(parent_ent_num);
if (!parent->isSubclassOf(Actor))
return;
auto act = dynamic_cast<Actor *>(parent);
act->shotsFired++;
}
void Entity::DeathSinkStart(Event*)
{
auto time = maxs[2] >= 0.0f && maxs[2] < 200.0f ? maxs[2] / 20.0f : 1.0f;
PostEvent(EV_Remove, time);
// Start the sinking
ProcessEvent(EV_DeathSink);
}
void Entity::DeathSink(Event*)
{
// Sink just a little
origin[2] -= 1.0f;
setOrigin(origin);
// Make sure the sink happens again next frame
PostEvent(EV_DeathSink, FRAMETIME);
}
void Entity::LookAtMe(Event* ev)
{
look_at_me = ev->NumArgs() > 0 ? ev->GetBoolean(1) : true;
}
void Entity::ProjectilesCanStickToMe(Event* ev)
{
projectilesCanStickToMe = ev->GetBoolean(1);
}
void Entity::VelocityModified()
{
}
//--------------------------------------------------------------
//
// Name: DetachAllChildren
// Class: Entity
//
// Description: Detaches all attached models to this entity
//
// Parameters: Event *ev
//
// Returns: None
//
//--------------------------------------------------------------
void Entity::DetachAllChildren(Event*)
{
if (!bind_info) // Abort if bind_info is nullptr for some reason
return;
for (auto i = 0; i < MAX_MODEL_CHILDREN; i++)
{
if (bind_info->children[i] == ENTITYNUM_NONE)
continue;
auto ent = G_GetEntity(bind_info->children[i]);
if (ent)
ent->PostEvent(EV_Remove, 0.0f);
}
}
inline void Entity::Archive(Archiver& arc)
{
int tempInt;
qboolean is_archived;
qboolean true_bool = true;
qboolean false_bool = false;
Listener::Archive(arc);
arc.ArchiveVector(&_localOrigin);
_lastTouchedList.Archive(arc);
arc.ArchiveBool(&_fulltrace);
arc.ArchiveInteger(&_groupID);
// Don't archive entnum, it will be set elsewhere
//int entnum;
G_ArchiveEdict(arc, edict);
arc.ArchiveString(&_archetype);
if (arc.Loading())
{
edict->s.archeTypeIndex = gi.archetypeindex(_archetype);
}
arc.ArchiveBool(&_missionObjective);
arc.ArchiveString(&_targetPos);
arc.ArchiveBool(&_networkDetail);
if (arc.Loading())
{
edict->s.missionObjective = _missionObjective;
if (_missionObjective == true)
G_AddEntityToExtraList(entnum);
}
// Don't archive client
//gclient_t *client;
arc.ArchiveInteger(&spawnflags);
arc.ArchiveString(&model);
if (arc.Loading() && model.length())
setModel(model.c_str());
arc.ArchiveVector(&total_delta);
arc.ArchiveVector(&mins);
arc.ArchiveVector(&maxs);
arc.ArchiveVector(&absmin);
arc.ArchiveVector(&absmax);
arc.ArchiveVector(&centroid);
arc.ArchiveVector(&velocity);
arc.ArchiveVector(&avelocity);
arc.ArchiveVector(&origin);
arc.ArchiveVector(&angles);
arc.ArchiveVector(&size);
arc.ArchiveInteger(&movetype);
arc.ArchiveInteger(&mass);
arc.ArchiveFloat(&gravity);
arc.ArchiveRaw(orientation, sizeof(orientation));
arc.ArchiveVector(&localangles);
if (arc.Saving())
{
if (groundentity)
{
tempInt = groundentity - g_entities;
} else
{
tempInt = -1;
}
}
arc.ArchiveInteger(&tempInt);
if (arc.Loading())
{
if (tempInt == -1)
{
groundentity = nullptr;
} else
{
groundentity = &g_entities[tempInt];
}
}
arc.ArchiveRaw(&groundplane, sizeof(groundplane));
arc.ArchiveInteger(&groundcontents);
arc.ArchiveInteger(&numsurfaces);
arc.ArchiveFloat(&lightRadius);
arc.ArchiveString(&target);
arc.ArchiveString(&targetname);
arc.ArchiveString(&killtarget);
if (arc.Loading())
{
// Don't set this here, they are handled in the world
//SetTargetName( targetname.c_str() );
// Not needed
//SetTarget( target.c_str() );
}
arc.ArchiveFloat(&health);
arc.ArchiveFloat(&max_health);
arc.ArchiveInteger(&deadflag);
arc.ArchiveInteger(&flags);
arc.ArchiveInteger(&watertype);
arc.ArchiveInteger(&waterlevel);
ArchiveEnum(takedamage, EDamageT);
arc.ArchiveInteger(&damage_type);
arc.ArchiveBoolean(&look_at_me);
arc.ArchiveBool(&projectilesCanStickToMe);
arc.ArchiveString(&explosionModel);
entityVars.Archive(arc);
arc.ArchiveUnsigned(&_affectingViewModes);
arc.ArchiveVector(&watch_offset);
// Pluggable modules
if (arc.Loading())
{
arc.ArchiveBoolean(&is_archived);
if (is_archived)
{
animate = new Animate(this);
arc.ArchiveObject(animate);
}
arc.ArchiveBoolean(&is_archived);
if (is_archived)
{
mover = new Mover(this);
arc.ArchiveObject(mover);
}
arc.ArchiveBoolean(&is_archived);
if (is_archived)
{
bind_info = CreateBindInfo();
bind_info->Archive(arc);
}
arc.ArchiveBoolean(&is_archived);
if (is_archived)
{
morph_info = CreateMorphInfo();
morph_info->Archive(arc);
}
} else
{
if (animate)
{
arc.ArchiveBoolean(&true_bool);
arc.ArchiveObject(animate);
} else
{
arc.ArchiveBoolean(&false_bool);
}
if (mover)
{
arc.ArchiveBoolean(&true_bool);
arc.ArchiveObject(mover);
} else
{
arc.ArchiveBoolean(&false_bool);
}
if (bind_info)
{
arc.ArchiveBoolean(&true_bool);
bind_info->Archive(arc);
} else
{
arc.ArchiveBoolean(&false_bool);
}
if (morph_info)
{
arc.ArchiveBoolean(&true_bool);
morph_info->Archive(arc);
} else
{
arc.ArchiveBoolean(&false_bool);
}
}
if (arc.Saving())
{
if (ObjectProgram)
{
arc.ArchiveBoolean(&true_bool);
ObjectProgram->Archive(arc);
} else
{
arc.ArchiveBoolean(&false_bool);
}
} else
{
arc.ArchiveBoolean(&is_archived);
if (is_archived)
ObjectProgram->Archive(arc);
else
ObjectProgram = nullptr;
}
// Archiving of the Damage Modification System
if (arc.Saving())
{
bool exists;
int type;
if (damageModSystem)
{
exists = true;
arc.ArchiveBool(&exists);
auto numobj = damageModSystem->getModifierList().NumObjects();
arc.ArchiveInteger(&numobj);
for (tempInt = 1; tempInt <= numobj; tempInt++)
{
auto dmod = damageModSystem->getModifierList().ObjectAt(tempInt);
if (dmod)
{
type = int(dmod->getType());
arc.ArchiveInteger(&type);
dmod->Archive(arc);
} else
{
// How did a nullptr get in the container??
assert(0);
}
}
} else
{
exists = false;
arc.ArchiveBool(&exists);
}
} else // Loading
{
bool exists;
int numobj, type;
arc.ArchiveBool(&exists);
if (exists)
{
damageModSystem = new DamageModificationSystem();
arc.ArchiveInteger(&numobj);
for (tempInt = 1; tempInt <= numobj; tempInt++)
{
arc.ArchiveInteger(&type);
DamageModifier* newMod = nullptr;
switch (type)
{
case TIKI_NAME:
newMod = new DamageModifierTikiName();
break;
case NAME:
newMod = new DamageModifierName();
break;
case GROUP:
newMod = new DamageModifierGroup();
break;
case ACTOR_TYPE:
newMod = new DamageModifierActorType();
break;
case TARGETNAME:
newMod = new DamageModifierTargetName();
break;
case DAMAGE_TYPE:
newMod = new DamageModifierDamageType();
break;
}
if (newMod)
{
newMod->Archive(arc);
damageModSystem->addDamageModifier(newMod);
}
}
}
}
// Archive the useData member if it exists
if (arc.Saving())
{
auto exists = false;
if (useData)
{
exists = true;
arc.ArchiveBool(&exists);
useData->Archive(arc);
} else
arc.ArchiveBool(&exists);
} else // Loading
{
bool exists;
arc.ArchiveBool(&exists);
if (exists)
{
useData = new UseData();
useData->Archive(arc);
}
}
}
// Animate interface
int32_t Entity::CurrentFrame(bodypart_t part)
{
if (animate)
return animate->CurrentFrame(part);
return 0;
}
int32_t Entity::CurrentAnim(bodypart_t part)
{
if (animate)
return animate->CurrentAnim(part);
return 0;
}
void Entity::PassToAnimate(Event* ev)
{
Event* new_event;
if (!animate)
animate = new Animate(this);
new_event = new Event(ev);
animate->ProcessEvent(new_event);
}
void Entity::SetObjectProgram(Event* ev)
{
ObjectProgram = new Program;
if (!ObjectProgram)
return;
ObjectProgram->Load(ev->GetString(1));
//CThread *gamescript = 0;
//gamescript = Director.CreateThread( "obj_main" , ObjectProgram );
//gamescript->DelayedStart( 0 );
}
void Entity::SetWatchOffset(Event* ev)
{
watch_offset = ev->GetVector(1);
}
void Entity::ExecuteProgram(Event* ev)
{
auto exeTime = 0.0f;
if (!ObjectProgram)
return;
if (ev->NumArgs() > 0)
exeTime = ev->GetFloat(1);
auto* gamescript = Director.CreateThread("obj_main", ObjectProgram);
gamescript->DelayedStart(exeTime);
}
// BindInfo interface
inline BindInfo* CreateBindInfo()
{
auto new_bind_info = new BindInfo;
if (!new_bind_info)
gi.Error(ERR_DROP, "Couldn't alloc BindInfo");
return new_bind_info;
}
// MorphInfo interface
void Entity::MorphEvent(Event* ev)
{
str morph_target_name;
int morph_index;
float final_percent = 100;
float morph_time = 0.5;
qboolean return_to_zero = false;
int i;
qboolean override = true;
int morph_channel = MORPH_CHAN_NONE;
qboolean channel_being_used;
qboolean override_all = false;
qboolean matching_channel;
qboolean unmorph = false;
// Get parms
morph_target_name = ev->GetString(1);
if (ev->NumArgs() > 1)
final_percent = ev->GetFloat(2);
if (ev->NumArgs() > 2)
morph_time = ev->GetFloat(3);
if (ev->NumArgs() > 3)
return_to_zero = ev->GetBoolean(4);
if (ev->NumArgs() > 4)
override = ev->GetBoolean(5);
if (ev->NumArgs() > 5)
morph_channel = ev->GetInteger(6);
// See if this is an expression
if (strnicmp(morph_target_name.c_str(), "exp_", 4) == 0)
{
// Process this expression
dtikimorphtarget_t* morph_targets;
Event* new_event;
const char* morph_name;
int number_of_morph_targets;
morph_targets = gi.GetExpression(edict->s.modelindex, morph_target_name.c_str(), &number_of_morph_targets);
if (morph_targets)
{
for (i = 0; i < number_of_morph_targets; i++)
{
morph_name = gi.Morph_NameForNum(edict->s.modelindex, morph_targets[i].morph_index);
if (morph_name)
{
new_event = new Event(EV_Morph);
new_event->AddString(morph_name);
new_event->AddFloat(morph_targets[i].percent);
new_event->AddFloat(morph_time);
new_event->AddInteger(return_to_zero);
new_event->AddInteger(override);
new_event->AddInteger(morph_channel);
ProcessEvent(new_event);
}
}
return;
}
}
// Find this morph target
morph_index = gi.Morph_NumForName(edict->s.modelindex, morph_target_name.c_str());
// Check unmorphing stuff
if (stricmp(morph_target_name.c_str(), "morph_base") == 0 ||
stricmp(morph_target_name.c_str(), "morph_mouth_base") == 0 ||
stricmp(morph_target_name.c_str(), "morph_brows_base") == 0 ||
stricmp(morph_target_name.c_str(), "morph_eyes_base") == 0)
unmorph = true;
if (morph_index == -1 && !unmorph)
return;
if (morph_channel == MORPH_CHAN_NONE)
morph_channel = GetMorphChannel(morph_target_name.c_str());
// Make sure we have a morph controller block
if (!morph_info)
morph_info = CreateMorphInfo();
// Deal with current morphs
channel_being_used = false;
if (unmorph && morph_channel == MORPH_CHAN_NONE)
override_all = true;
for (i = 0; i < NUM_MORPH_CONTROLLERS; i++)
{
if (morph_info->controllers[i].index != -1)
{
// See if this is a matching channel
if (MorphChannelMatches(morph_channel, morph_info->controllers[i].channel))
matching_channel = true;
else
matching_channel = false;
if (override_all || matching_channel && override)
{
if (morph_info->controllers[i].final_percent != 0)
{
// Override this morph
morph_info->controllers[i].speed = morph_info->controllers[i].current_percent * FRAMETIME / morph_time;
morph_info->controllers[i].final_percent = 0;
StartMorphController();
}
} else if (matching_channel)
{
channel_being_used = true;
}
}
}
// If just unmorphing stuff, stop here
if (unmorph)
return;
// If not overriding and the channel is already being used, stop here
if (!override && channel_being_used)
return;
// Find a free morph controller & add this morph
for (i = 0; i < NUM_MORPH_CONTROLLERS; i++)
{
if (morph_info->controllers[i].index == -1)
{
morph_info->controllers[i].index = morph_index;
morph_info->controllers[i].current_percent = 0;
morph_info->controllers[i].final_percent = final_percent;
morph_info->controllers[i].speed = final_percent * FRAMETIME / morph_time;
morph_info->controllers[i].return_to_zero = return_to_zero;
morph_info->controllers[i].channel = morph_channel;
StartMorphController();
break;
}
}
}
void Entity::UnmorphEvent(Event* ev)
{
str morph_target_name;
int morph_index;
int i;
float morph_time = 0.5;
morph_target_name = ev->GetString(1);
if (ev->NumArgs() > 2)
morph_time = ev->GetFloat(3);
// See if this is an expression
if (strnicmp(morph_target_name.c_str(), "exp_", 4) == 0)
{
// Process this expression
dtikimorphtarget_t* morph_targets;
Event* new_event;
const char* morph_name;
int number_of_morph_targets;
morph_targets = gi.GetExpression(edict->s.modelindex, morph_target_name.c_str(), &number_of_morph_targets);
if (morph_targets)
{
for (i = 0; i < number_of_morph_targets; i++)
{
morph_name = gi.Morph_NameForNum(edict->s.modelindex, morph_targets[i].morph_index);
if (morph_name)
{
new_event = new Event(EV_Unmorph);
new_event->AddString(morph_name);
new_event->AddFloat(morph_time);
ProcessEvent(new_event);
}
}
return;
}
}
morph_index = gi.Morph_NumForName(edict->s.modelindex, morph_target_name.c_str());
if (morph_index == -1 || !morph_info)
return;
// Find this morph controller
for (i = 0; i < NUM_MORPH_CONTROLLERS; i++)
{
if (morph_info->controllers[i].index == morph_index && morph_info->controllers[i].final_percent != 0)
{
morph_info->controllers[i].speed = morph_info->controllers[i].final_percent * FRAMETIME / morph_time;
morph_info->controllers[i].final_percent = 0.0f;
StartMorphController();
break;
}
}
}
void Entity::MorphControl(Event*)
{
qboolean process_next_frame = false;
MorphInfo::TMorph* controller;
for (auto i = 0; i < NUM_MORPH_CONTROLLERS; i++)
{
controller = &morph_info->controllers[i];
if (controller->index != -1)
{
// Lerp the morph percent
if (controller->current_percent != controller->final_percent)
{
if (controller->current_percent < controller->final_percent)
{
controller->current_percent += controller->speed;
if (controller->current_percent > controller->final_percent)
controller->current_percent = controller->final_percent;
} else
{
controller->current_percent -= controller->speed;
if (controller->current_percent < controller->final_percent)
controller->current_percent = controller->final_percent;
}
}
if (controller->current_percent == 0.0f)
{
controller->index = -1;
controller->current_percent = 0.0f;
}
// Return to zero if necessary
if (controller->current_percent == controller->final_percent && controller->return_to_zero)
controller->final_percent = 0.0f;
if (controller->current_percent != controller->final_percent)
process_next_frame = true;
}
// Copy to edict
edict->s.morph_controllers[i].index = controller->index;
edict->s.morph_controllers[i].percent = controller->current_percent / 100.0f;
}
if (process_next_frame)
PostEvent(EV_MorphControl, FRAMETIME);
else
morph_info->controller_on = false;
}
int Entity::GetMorphChannel(const char* morph_name)
{
int32_t morph_channel;
if (stricmp(morph_name, "morph_a-i") == 0)
morph_channel = MORPH_CHAN_MOUTH;
else if (stricmp(morph_name, "morph_c-t") == 0)
morph_channel = MORPH_CHAN_MOUTH;
else if (stricmp(morph_name, "morph_e") == 0)
morph_channel = MORPH_CHAN_MOUTH;
else if (stricmp(morph_name, "morph_f-v") == 0)
morph_channel = MORPH_CHAN_MOUTH;
else if (stricmp(morph_name, "morph_l-th") == 0)
morph_channel = MORPH_CHAN_MOUTH;
else if (stricmp(morph_name, "morph_m-b-p") == 0)
morph_channel = MORPH_CHAN_MOUTH;
else if (stricmp(morph_name, "morph_o") == 0)
morph_channel = MORPH_CHAN_MOUTH;
else if (stricmp(morph_name, "morph_q-w") == 0)
morph_channel = MORPH_CHAN_MOUTH;
else if (stricmp(morph_name, "morph_u") == 0)
morph_channel = MORPH_CHAN_MOUTH;
else if (stricmp(morph_name, "morph_frown") == 0)
morph_channel = MORPH_CHAN_MOUTH;
else if (stricmp(morph_name, "morph_sneer-l") == 0)
morph_channel = MORPH_CHAN_MOUTH;
else if (stricmp(morph_name, "morph_sneer-r") == 0)
morph_channel = MORPH_CHAN_MOUTH;
else if (stricmp(morph_name, "morph_mouth_base") == 0)
morph_channel = MORPH_CHAN_MOUTH;
else if (stricmp(morph_name, "morph_brows-up") == 0)
morph_channel = MORPH_CHAN_BROW;
else if (stricmp(morph_name, "morph_brows_base") == 0)
morph_channel = MORPH_CHAN_BROW;
else if (stricmp(morph_name, "morph_brow-ldn") == 0)
morph_channel = MORPH_CHAN_LEFT_BROW;
else if (stricmp(morph_name, "morph_brow-rdn") == 0)
morph_channel = MORPH_CHAN_RIGHT_BROW;
else if (stricmp(morph_name, "morph_lid-lshut") == 0)
morph_channel = MORPH_CHAN_LEFT_LID;
else if (stricmp(morph_name, "morph_lid-rshut") == 0)
morph_channel = MORPH_CHAN_RIGHT_LID;
else if (stricmp(morph_name, "morph_eyeshut") == 0)
morph_channel = MORPH_CHAN_EYES;
else if (stricmp(morph_name, "morph_eyes_base") == 0)
morph_channel = MORPH_CHAN_EYES;
else
morph_channel = MORPH_CHAN_NONE;
return morph_channel;
}
void Entity::StartMorphController()
{
if (!morph_info->controller_on)
{
morph_info->controller_on = true;
CancelEventsOfType(EV_MorphControl);
PostEvent(EV_MorphControl, FRAMETIME);
}
}
void Entity::SetAnimOnAttachedModel(const str& AnimName, const str& TagName)
{
auto tag_num = gi.Tag_NumForName(this->edict->s.modelindex, TagName.c_str());
if (bind_info)
{
for (auto i = 0; i < MAX_MODEL_CHILDREN; i++)
{
//Check for valid entities
if (bind_info->children[i] == ENTITYNUM_NONE)
continue;
auto attachment = G_GetEntity(bind_info->children[i]);
if (attachment->edict->s.tag_num == tag_num)
{
if (!attachment->animate)
{
attachment->animate = new Animate;
}
auto anim_num = gi.Anim_Random(attachment->edict->s.modelindex, AnimName.c_str());
if (anim_num != -1)
{
attachment->animate->NewAnim(anim_num);
}
}
}
}
}
void Entity::SetAnimOnAttachedModel(Event* ev)
{
str attachmentAnim = ev->GetString(1);
str TagName = ev->GetString(2);
SetAnimOnAttachedModel(attachmentAnim, TagName);
}
void Entity::SetCinematicAnim(const str& AnimName)
{
if (!animate)
{
animate = new Animate(this);
}
auto anim_num = gi.Anim_Random(edict->s.modelindex, AnimName.c_str());
if (anim_num != -1)
{
animate->NewAnim(anim_num);
animate->SetAnimDoneEvent(new Event(EV_CinematicAnimDone));
}
gravity = 0.0f;
edict->contents = CONTENTS_SETCLIP;
edict->clipmask = MASK_SETCLIP;
}
void Entity::SetCinematicAnim(Event* ev)
{
str animName = ev->GetString(1);
SetCinematicAnim(animName);
}
void Entity::CinematicAnimDone()
{
gravity = 1.0f;
edict->contents &= ~CONTENTS_SETCLIP;
edict->clipmask = MASK_SOLID;
}
void Entity::CinematicAnimDone(Event*)
{
CinematicAnimDone();
}
void Entity::SetEntityExplosionModel(Event* ev)
{
explosionModel = ev->GetString(1);
}
//----------------------------------------------------------------
// Name: ProjectileAtk
// Class: Entity
//
// Description: launches a projectile either from the player
// or from a tag
//
// Parameters: event ev contains projectile name and an
// optional tag name
//
// Returns: None
//----------------------------------------------------------------
void Entity::ProjectileAtk(Event* ev)
{
auto projectileName = ev->GetString(1);
Vector position;
Vector direction;
if (ev->NumArgs() > 1)
{
// Projectile name, tagName
str tagName(ev->GetString(2));
GetTag(tagName.c_str(), &position, &direction);
} else
{
// Projectile name, Player is the target
position = origin;
gentity_t* ed;
Entity* enemy = nullptr;
for (auto i = 0; i < game.maxclients; i++)
{
ed = &g_entities[i];
if (!ed->inuse || !ed->entity)
continue;
enemy = ed->entity;
}
if (enemy)
{
direction = enemy->origin - origin;
direction.normalize();
} else
{
angles.AngleVectors(&direction, nullptr, nullptr);
}
}
ProjectileAttack(position, direction, this, projectileName, 1.0f, 0.0f);
}
//----------------------------------------------------------------
// Name: ProjectileAttackPoint
// Class: Entity
//
// Description: launches a projectile towards the given point
//
// Parameters: event ev contains projectile name and entity name
//
// Returns: None
//----------------------------------------------------------------
void Entity::ProjectileAttackPoint(Event* ev)
{
auto projectileName(ev->GetString(1));
auto targetPosition(ev->GetVector(2));
auto direction(targetPosition - origin);
direction.normalize();
auto projectile = ProjectileAttack(origin, direction, this, projectileName, 1.0f, 0.0f);
if (ev->NumArgs() > 2)
{
Angle launchAngle(ev->GetFloat(3));
Trajectory trajectory(origin, targetPosition, launchAngle, projectile->gravity * sv_currentGravity->value);
projectile->velocity = trajectory.GetInitialVelocity();
auto launchDirection(projectile->velocity);
launchDirection.normalize();
projectile->angles = launchDirection.toAngles();
projectile->CancelEventsOfType(EV_Projectile_Explode);
auto event = new Event(EV_Projectile_Explode);
if (ev->NumArgs() > 3)
{
projectile->PostEvent(event, ev->GetFloat(4));
} else
{
projectile->PostEvent(event, trajectory.GetTravelTime());
}
}
}
//----------------------------------------------------------------
// Name: ProjectileAttackEntity
// Class: Entity
//
// Description: launches a projectile either from the named entity
//
// Parameters: event ev contains projectile name and entity name
//
// Returns: None
//----------------------------------------------------------------
void Entity::ProjectileAttackEntity(Event* ev)
{
auto projectileName(ev->GetString(1));
auto target = ev->GetEntity(2);
if (target)
{
auto direction(target->origin - origin);
direction.normalize();
auto* projectile = ProjectileAttack(origin, direction, this, projectileName, 1.0f, 0.0f);
if (!projectile)
return;
if (ev->NumArgs() > 2)
{
Angle launchAngle(ev->GetFloat(3));
Trajectory trajectory(origin, target->centroid, launchAngle, projectile->gravity * -sv_currentGravity->value);
projectile->velocity = trajectory.GetInitialVelocity();
auto launchDirection(projectile->velocity);
launchDirection.normalize();
projectile->angles = launchDirection.toAngles();
projectile->CancelEventsOfType(EV_Projectile_Explode);
auto event = new Event(EV_Projectile_Explode);
if (ev->NumArgs() > 3)
{
projectile->PostEvent(event, ev->GetFloat(4));
} else
{
projectile->PostEvent(event, trajectory.GetTravelTime());
}
}
}
}
//----------------------------------------------------------------
// Name: ProjectileAttackFromTag
// Class: Entity
//
// Description: launches a projectile either from the named tag
//
// Parameters: event ev contains projectile name and tag name
//
// Returns: None
//----------------------------------------------------------------
void Entity::ProjectileAttackFromTag(Event* ev)
{
auto projectileName(ev->GetString(1));
auto tagName(ev->GetString(2));
Vector position;
Vector direction;
GetTag(tagName, &position, &direction);
auto speed = 0.0f;
if (ev->NumArgs() > 3)
{
speed = ev->GetFloat(4);
}
auto projectile = ProjectileAttack(position, direction, this, projectileName, 1.0f, speed);
if (ev->NumArgs() > 4)
{
projectile->CancelEventsOfType(EV_Projectile_Explode);
auto event = new Event(EV_Projectile_Explode);
projectile->PostEvent(event, ev->GetFloat(5));
}
}
//----------------------------------------------------------------
// Name: ProjectileAttackFromPoint
// Class: Entity
//
// Description: launches a projectile either from the desired
// location facing the desired direction
//
// Parameters: event ev contains projectile name, the
// position and direction for the new projectile
//
// Returns: None
//----------------------------------------------------------------
void Entity::ProjectileAttackFromPoint(Event* ev)
{
auto projectileName(ev->GetString(1));
auto position(ev->GetVector(2));
auto direction(ev->GetVector(3));
auto speed = 0.0f;
if (ev->NumArgs() > 3)
{
speed = ev->GetFloat(4);
}
Vector forward;
direction.AngleVectors(&forward);
auto* projectile = ProjectileAttack(origin + position, forward, this, projectileName, 1.0f, speed);
if (ev->NumArgs() > 4)
{
projectile->CancelEventsOfType(EV_Projectile_Explode);
auto* event = new Event(EV_Projectile_Explode);
projectile->PostEvent(event, ev->GetFloat(5));
}
}
void Entity::TraceAtk(Event* ev)
{
Vector position;
Vector forward;
Vector right;
Vector up;
auto knockback = 0.0f;
auto means_of_death_string = "bullet";
//int offsetPitch = 0;
auto damage = ev->GetFloat(1);
auto range = ev->GetFloat(2);
if (ev->NumArgs() > 2)
means_of_death_string = ev->GetString(3);
if (ev->NumArgs() > 3)
knockback = ev->GetFloat(4);
if (ev->NumArgs() > 4)
{
str tag_name;
tag_name = ev->GetString(5);
GetTag(tag_name.c_str(), &position, &forward, &right, &up);
} else
{
position = origin;
angles.AngleVectors(&forward, &right, &up);
}
auto means_of_death = MOD_NameToNum(means_of_death_string);
/*
if ( ev->NumArgs() > 5 )
offsetPitch = ev->GetInteger( 6 );
if ( offsetPitch )
{
Vector ForAngles;
ForAngles = forward.toAngles();
ForAngles[YAW] = AngleNormalize180( ForAngles[YAW] );
ForAngles[PITCH] = AngleNormalize180( ForAngles[PITCH] );
ForAngles[ROLL] = AngleNormalize180( ForAngles[ROLL] );
offsetPitch = AngleNormalize180(offsetPitch);
ForAngles[PITCH] += offsetPitch;
ForAngles[YAW] = AngleNormalize360( ForAngles[YAW] );
ForAngles[PITCH] = AngleNormalize360( ForAngles[PITCH] );
ForAngles[ROLL] = AngleNormalize360( ForAngles[ROLL] );
ForAngles.AngleVectors( &forward );
}
*/
BulletAttack(position, forward, right, up, range, damage, knockback, 0, means_of_death, vec_zero, 1, this);
}
qboolean Entity::MorphChannelMatches(int32_t morph_channel1, int32_t morph_channel2)
{
// Nothing matches with non
if (morph_channel1 == MORPH_CHAN_NONE)
return false;
// See if they match exactly
if (morph_channel1 == morph_channel2)
return true;
// Check special cases
if (morph_channel1 == MORPH_CHAN_BROW && (morph_channel2 == MORPH_CHAN_LEFT_BROW || morph_channel2 == MORPH_CHAN_RIGHT_BROW))
return true;
if (morph_channel2 == MORPH_CHAN_BROW && (morph_channel1 == MORPH_CHAN_LEFT_BROW || morph_channel1 == MORPH_CHAN_RIGHT_BROW))
return true;
if (morph_channel1 == MORPH_CHAN_EYES && (morph_channel2 == MORPH_CHAN_LEFT_LID || morph_channel2 == MORPH_CHAN_RIGHT_LID))
return true;
if (morph_channel2 == MORPH_CHAN_EYES && (morph_channel1 == MORPH_CHAN_LEFT_LID || morph_channel1 == MORPH_CHAN_RIGHT_LID))
return true;
return false;
}
inline MorphInfo* CreateMorphInfo(void)
{
auto new_morph_info = new MorphInfo;
if (!new_morph_info)
gi.Error(ERR_DROP, "Couldn't alloc MorphInfo");
return new_morph_info;
}
enum EContentsOperation
{
ContentsOperationAdd,
ContentsOperationSubtract,
ContentsOperationSet
};
void Entity::Contents(Event* ev)
{
if (ev == nullptr)
return;
int32_t operation;
auto contents = getContents();
auto newFlags = 0;
for (auto i = 1; i <= ev->NumArgs(); i++)
{
auto contentType = ev->GetString(i);
if (contentType == nullptr || strlen(contentType) == 0)
continue;
// Get operation type
if (contentType[0] == '+')
{
operation = ContentsOperationAdd;
contentType++;
} else if (contentType[0] == '-')
{
operation = ContentsOperationSubtract;
contentType++;
} else
operation = ContentsOperationSet;
// Get contents type
if (stricmp(contentType, "shootable") == 0)
newFlags = CONTENTS_SHOOTABLE_ONLY;
else if (stricmp(contentType, "targetable") == 0)
{
newFlags = CONTENTS_TARGETABLE;
setSolidType(SOLID_BBOX);
} else if (stricmp(contentType, "body") == 0)
newFlags = CONTENTS_BODY;
else if (stricmp(contentType, "solid") == 0)
newFlags = CONTENTS_SOLID;
else if (stricmp(contentType, "usable") == 0)
newFlags = CONTENTS_USABLE;
else if (stricmp(contentType, "setclip") == 0)
newFlags = CONTENTS_SETCLIP;
else if (stricmp(contentType, "playerclip") == 0)
newFlags = CONTENTS_PLAYERCLIP;
else if (stricmp(contentType, "monsterclip") == 0)
newFlags = CONTENTS_MONSTERCLIP;
else if (stricmp(contentType, "cameraclip") == 0)
newFlags = CONTENTS_CAMERACLIP;
else if (stricmp(contentType, "weaponclip") == 0)
newFlags = CONTENTS_WEAPONCLIP;
else if (stricmp(contentType, "corpse") == 0)
newFlags = CONTENTS_CORPSE;
else if (stricmp(contentType, "all") == 0)
newFlags = 0xFFFFFFFF;
// Change contents appropriatly
if (operation == ContentsOperationAdd)
contents |= newFlags;
else if (operation == ContentsOperationSubtract)
contents &= ~newFlags;
else if (operation == ContentsOperationSet)
contents = newFlags;
}
setContents(contents);
link();
}
void Entity::setMask(Event* ev)
{
int32_t operation;
auto newMask = 0;
// Get the current mask
auto currentMask = edict->clipmask;
// Loop through all of the parms and change the mask appropriately
for (auto i = 1; i <= ev->NumArgs(); i++)
{
auto maskType = ev->GetString(i);
if (!maskType || strlen(maskType) == 0)
continue;
// Get operation type
if (maskType[0] == '+')
{
operation = ContentsOperationAdd;
maskType++;
} else if (maskType[0] == '-')
{
operation = ContentsOperationSubtract;
maskType++;
} else
{
operation = ContentsOperationSet;
}
// Get contents type
// Masks
if (stricmp(maskType, "solid") == 0)
newMask = MASK_SOLID;
else if (stricmp(maskType, "usable") == 0)
newMask = MASK_USABLE;
else if (stricmp(maskType, "playersolid") == 0)
newMask = MASK_PLAYERSOLID;
else if (stricmp(maskType, "deadsolid") == 0)
newMask = MASK_DEADSOLID;
else if (stricmp(maskType, "monstersolid") == 0)
newMask = MASK_MONSTERSOLID;
else if (stricmp(maskType, "water") == 0)
newMask = MASK_WATER;
else if (stricmp(maskType, "opaque") == 0)
newMask = MASK_OPAQUE;
else if (stricmp(maskType, "shot") == 0)
newMask = MASK_SHOT;
else if (stricmp(maskType, "projectile") == 0)
newMask = MASK_PROJECTILE;
else if (stricmp(maskType, "melee") == 0)
newMask = MASK_MELEE;
else if (stricmp(maskType, "pathsolid") == 0)
newMask = MASK_PATHSOLID;
else if (stricmp(maskType, "camerasolid") == 0)
newMask = MASK_CAMERASOLID;
else if (stricmp(maskType, "setclip") == 0)
newMask = MASK_SETCLIP;
// Contents
else if (stricmp(maskType, "contents_solid") == 0)
newMask = CONTENTS_SOLID;
else if (stricmp(maskType, "contents_usable") == 0)
newMask = CONTENTS_USABLE;
else if (stricmp(maskType, "contents_setclip") == 0)
newMask = CONTENTS_SETCLIP;
else if (stricmp(maskType, "contents_targetable") == 0)
newMask = CONTENTS_TARGETABLE;
else if (stricmp(maskType, "contents_playerclip") == 0)
newMask = CONTENTS_PLAYERCLIP;
else if (stricmp(maskType, "contents_monsterclip") == 0)
newMask = CONTENTS_MONSTERCLIP;
else if (stricmp(maskType, "contents_cameraclip") == 0)
newMask = CONTENTS_CAMERACLIP;
else if (stricmp(maskType, "contents_weaponclip") == 0)
newMask = CONTENTS_WEAPONCLIP;
else if (stricmp(maskType, "contents_shootable") == 0)
newMask = CONTENTS_SHOOTABLE_ONLY;
else if (stricmp(maskType, "contents_body") == 0)
newMask = CONTENTS_BODY;
else if (stricmp(maskType, "contents_corpse") == 0)
newMask = CONTENTS_CORPSE;
// All
else if (stricmp(maskType, "all") == 0)
newMask = 0xFFFFFFFF;
// Change mask appropriately
if (operation == ContentsOperationAdd)
currentMask |= newMask;
else if (operation == ContentsOperationSubtract)
currentMask &= ~newMask;
else if (operation == ContentsOperationSet)
currentMask = newMask;
}
// Save the new mask
edict->clipmask = currentMask;
}
void Entity::getCustomShaderInfo(const str& customShader, str& shaderName, str& soundName)
{
// Setup the defaults
shaderName = customShader;
soundName = "";
// See if we need to get info from the database
if (customShader.length() > 4 && stricmp(customShader.c_str() + customShader.length() - 4, ".gdb") == 0)
{
auto gameplayObjectName = customShader;
gameplayObjectName.CapLength(customShader.length() - 4);
shaderName = G_GetDatabaseString("CustomShader", gameplayObjectName, "ShaderName");
soundName = G_GetDatabaseString("CustomShader", gameplayObjectName, "SoundName");
}
}
//----------------------------------------------------------------
// Name: setCustomShader
// Class: Entity
//
// Description: Sets the custom shader for this entity
//
// Parameters: const char *customShader - name of the shader
//
// Returns: none
//----------------------------------------------------------------
void Entity::setCustomShader(const char* customShader)
{
str shaderName;
str soundName;
if (!customShader || strlen(customShader) == 0)
return;
getCustomShaderInfo(customShader, shaderName, soundName);
// Apply the custom shader
edict->s.customShader = gi.imageindex(shaderName);
edict->s.eFlags |= EF_EFFECT_CUSTOM;
// Start the loop sound associated with the customshader
if (soundName.length())
{
LoopSound(soundName);
}
}
//----------------------------------------------------------------
// Name: setCustomShader
// Class: Entity
//
// Description: Sets the custom shader for this entity
//
// Parameters: Event *ev - event that contains the name of the shader
//
// Returns: none
//----------------------------------------------------------------
void Entity::setCustomShader(Event* ev)
{
setCustomShader(ev->GetString(1));
}
//----------------------------------------------------------------
// Name: clearCustomShader
// Class: Entity
//
// Description: Clears the custom shader for this entity if it matches the shader name passed in
//
// Parameters: const char *customShader - name of the shader to clear, if nullptr will always clear
//
// Returns: none
//----------------------------------------------------------------
void Entity::clearCustomShader(const char* customShader)
{
str shaderName;
str soundName;
if (customShader && edict->s.customShader)
{
getCustomShaderInfo(customShader, shaderName, soundName);
// Only clear the custom shader if it matches the one passed in
auto tempImageIndex = gi.imageindex(shaderName);
if (edict->s.customShader == tempImageIndex)
{
edict->s, customShader = nullptr;
edict->s.eFlags &= ~EF_EFFECT_CUSTOM;
}
// Get rid of the loop sound associated with this custom shader
if (soundName.length())
{
StopLoopSound();
}
} else
{
edict->s, customShader = nullptr;
edict->s.eFlags &= ~EF_EFFECT_CUSTOM;
}
}
//----------------------------------------------------------------
// Name: clearCustomShader
// Class: Entity
//
// Description: Clears the custom shader for this entity if it matches the shader name passed in
//
// Parameters: Event *ev - event that optionally contains the name of the shader to clear
//
// Returns: none
//----------------------------------------------------------------
void Entity::clearCustomShader(Event* ev)
{
if (ev->NumArgs() > 0)
clearCustomShader(ev->GetString(1));
else
clearCustomShader();
}
bool Entity::hasCustomShader(const char* customShader)
{
// See if any custom shader is currently being used
if (!(edict->s.eFlags & EF_EFFECT_CUSTOM))
return false;
if (customShader && edict->s.customShader)
{
int tempImageIndex;
// Check to see if the shader matches the one passed in
tempImageIndex = gi.imageindex(customShader);
if (edict->s.customShader != tempImageIndex)
{
// Not the correct shader
return false;
}
}
// Everything matches
return true;
}
void Entity::setCustomEmitter(const char* customEmitter)
{
if (!customEmitter)
return;
edict->s.customEmitter = gi.imageindex(customEmitter);
edict->s.eFlags |= EF_EMITTER_CUSTOM;
}
void Entity::setCustomEmitter(Event* ev)
{
setCustomEmitter(ev->GetString(1));
}
void Entity::clearCustomEmitter(const char* customEmitter)
{
if (customEmitter && edict->s.customEmitter)
{
// Only clear the custom shader if it matches the one passed in
auto tempImageIndex = gi.imageindex(customEmitter);
if (edict->s.customEmitter == tempImageIndex)
{
edict->s, customEmitter = nullptr;
edict->s.eFlags &= ~EF_EMITTER_CUSTOM;
}
} else
{
edict->s, customEmitter = nullptr;
edict->s.eFlags &= ~EF_EMITTER_CUSTOM;
}
}
void Entity::clearCustomEmitter(Event* ev)
{
if (ev->NumArgs() > 0)
clearCustomEmitter(ev->GetString(1));
else
clearCustomEmitter();
}
void Entity::hideFeaturesForFade()
{
// Shut the eyes
auto event = new Event(EV_Morph);
event->AddString("morph_lid-lshut");
event->AddFloat(100.0f);
event->AddFloat(0.05f);
ProcessEvent(event);
event = new Event(EV_Morph);
event->AddString("morph_lid-rshut");
event->AddFloat(100.0f);
event->AddFloat(0.05f);
ProcessEvent(event);
// Hide the eyes and mouth surfaces
event = new Event(EV_SurfaceModelEvent);
event->AddString("material3");
event->AddString("+nodraw");
ProcessEvent(event);
// Don't let the entity blink
event = new Event(EV_Actor_Blink);
event->AddInteger(0);
ProcessEvent(event);
}
void Entity::showFeaturesForFade()
{
// Open the eyes
auto event = new Event(EV_Unmorph);
event->AddString("morph_lid-lshut");
event->AddFloat(100.0f);
event->AddFloat(0.5f);
ProcessEvent(event);
event = new Event(EV_Unmorph);
event->AddString("morph_lid-rshut");
event->AddFloat(100.0f);
event->AddFloat(0.5f);
ProcessEvent(event);
// Show the eyes and mouth surfaces
event = new Event(EV_SurfaceModelEvent);
event->AddString("material3");
event->AddString("-nodraw");
ProcessEvent(event);
// Allow the entity to blink again
event = new Event(EV_Actor_Blink);
event->AddInteger(1);
ProcessEvent(event);
}
void Entity::DisplayEffect(Event* ev)
{
str effectName;
Event* event;
int i;
Entity* ent;
auto passEvent = false;
GameplayManager* gpm;
str gameplayObjectName;
// Get the parms
str effectType = ev->GetString(1);
if (ev->NumArgs() > 1)
{
effectName = ev->GetString(2);
} else
{
const char* delimiterPtr;
// There is only 1 parm, so lets see if the type and name are both crammed into the 1 parm
delimiterPtr = strstr(effectType.c_str(), "-");
if (delimiterPtr)
{
// Effect name is everything after the -
effectName = delimiterPtr + 1;
// Effect type is everything before the -
effectType.CapLength(delimiterPtr - effectType);
}
}
// Get a pointer to the gameplay manager
gpm = GameplayManager::getTheGameplayManager();
// See if there is a gameplay object (if necessary)
if (strnicmp(effectType.c_str(), "TransportOut", strlen("TransportOut")) == 0 ||
strnicmp(effectType.c_str(), "TransportIn", strlen("TransportIn")) == 0 ||
strnicmp(effectType.c_str(), "FadeOut", strlen("FadeOut")) == 0 ||
strnicmp(effectType.c_str(), "FadeIn", strlen("FadeIn")) == 0)
{
if (strnicmp(effectType.c_str(), "TransportOut", strlen("TransportOut")) == 0)
{
gameplayObjectName = "TransportOut";
} else if (strnicmp(effectType.c_str(), "TransportIn", strlen("TransportIn")) == 0)
{
gameplayObjectName = "TransportIn";
} else if (strnicmp(effectType.c_str(), "FadeOut", strlen("FadeOut")) == 0)
{
gameplayObjectName = "FadeOut";
} else if (strnicmp(effectType.c_str(), "FadeIn", strlen("FadeIn")) == 0)
{
gameplayObjectName = "FadeIn";
}
gameplayObjectName += effectName;
if (!gpm->hasObject(gameplayObjectName))
{
gi.WDPrintf("Can't find object called %s to use in DisplayEffect\n", gameplayObjectName.c_str());
return;
}
}
if (effectType == "TransportOut")
{
str shaderName;
str soundName;
str effectTikiName;
str animName;
str effectPos;
str currentEffectPos;
float effectTime;
float transportTime;
int numEffects;
int immobilize;
int hideFeatures;
int attach;
// Get all of the info from the gameplay database
shaderName = gpm->getStringValue(gameplayObjectName, "ShaderName");
soundName = gpm->getStringValue(gameplayObjectName, "SoundName");
effectTikiName = gpm->getStringValue(gameplayObjectName, "EffectName");
attach = int(gpm->getFloatValue(gameplayObjectName, "AttachEffect"));
animName = gpm->getStringValue(gameplayObjectName, "AnimName");
effectPos = gpm->getStringValue(gameplayObjectName, "EffectPosName");
numEffects = int(gpm->getFloatValue(gameplayObjectName, "NumEffects"));
effectTime = gpm->getFloatValue(gameplayObjectName, "EffectTime");
transportTime = gpm->getFloatValue(gameplayObjectName, "TransportTime");
immobilize = int(gpm->getFloatValue(gameplayObjectName, "Immobilize"));
hideFeatures = int(gpm->getFloatValue(gameplayObjectName, "HideFeatures"));
setCustomShader(shaderName);
setAlpha(0.0f);
edict->s.renderfx |= RF_FORCE_ALPHA_EFFECTS;
edict->s.renderfx &= ~RF_FORCE_ALPHA;
event = new Event(EV_FadeIn);
event->AddFloat(effectTime);
ProcessEvent(event);
event = new Event(EV_DisplayEffect);
event->AddString("TransportOut2");
event->AddString(effectName);
PostEvent(event, effectTime);
if (hideFeatures)
hideFeaturesForFade();
if (edict->s.parent == ENTITYNUM_NONE)
{
if (soundName.length())
{
Sound(soundName);
}
if (effectTikiName.length() > 4)
{
Vector pos;
currentEffectPos = effectPos;
for (i = 0; i < numEffects; i++)
{
if (!effectPos.length() || effectPos == "centroid")
pos = centroid;
else if (effectPos == "origin")
pos = origin;
else if (effectPos == "randombone")
{
int tagNum;
tagNum = int(G_Random(gi.NumTags(edict->s.modelindex)));
currentEffectPos = gi.Tag_NameForNum(edict->s.modelindex, tagNum);
GetTag(tagNum, &pos, nullptr);
} else if (gi.Tag_NumForName(edict->s.modelindex, effectPos.c_str()) >= 0)
GetTag(effectPos, &pos, nullptr);
else
pos = centroid;
if (attach)
attachEffect(effectTikiName, currentEffectPos, effectTime + transportTime);
else
SpawnEffect(effectTikiName, pos, angles, effectTime + transportTime);
}
}
}
if (animate && animName.length() && animate->HasAnim(animName.c_str()))
{
animate->RandomAnimate(animName.c_str());
}
if (immobilize)
{
takedamage = DamageNo;
flags |= FlagImmobile;
}
passEvent = true;
} else if (effectType == "TransportOut2")
{
str shaderName;
float transportTime;
// Get all of the info from the gameplay database
shaderName = gpm->getStringValue(gameplayObjectName, "ShaderName");
transportTime = gpm->getFloatValue(gameplayObjectName, "TransportTime");
CancelEventsOfType(EV_FadeIn);
edict->s.renderfx &= ~RF_FORCE_ALPHA_EFFECTS;
setCustomShader(shaderName.c_str());
setAlpha(1.0f);
edict->s.renderfx |= RF_FORCE_ALPHA;
event = new Event(EV_FadeNoRemove);
event->AddFloat(transportTime);
event->AddFloat(0.0f);
ProcessEvent(event);
event = new Event(EV_DisplayEffect);
event->AddString("TransportOut3");
event->AddString(effectName);
PostEvent(event, transportTime);
} else if (effectType == "TransportOut3")
{
str shaderName;
int hideFeatures;
// Get all of the info from the gameplay database
shaderName = gpm->getStringValue(gameplayObjectName, "ShaderName");
hideFeatures = int(gpm->getFloatValue(gameplayObjectName, "HideFeatures"));
if (hideFeatures)
showFeaturesForFade();
if (shaderName.length())
{
clearCustomShader(shaderName);
}
// Finish up the transport out
event = new Event(EV_EntityRenderEffects);
event->AddString("-shadow");
ProcessEvent(event);
takedamage = DamageYes;
flags &= ~FlagImmobile;
} else if (effectType == "TransportIn")
{
str shaderName;
str soundName;
str effectPos;
str currentEffectPos;
str effectTikiName;
float effectTime;
float transportTime;
int numEffects;
int immobilize;
int attach;
int hideFeatures;
// Cancel other display events so we don't get any overlap (can't do this because it breaks animations that
// have both since animate posts all frame commands un front
//CancelEventsOfType( EV_DisplayEffect );
clearCustomShader();
// Get all of the info from the gameplay database
shaderName = gpm->getStringValue(gameplayObjectName, "ShaderName");
soundName = gpm->getStringValue(gameplayObjectName, "SoundName");
effectTikiName = gpm->getStringValue(gameplayObjectName, "EffectName");
attach = int(gpm->getFloatValue(gameplayObjectName, "AttachEffect"));
effectPos = gpm->getStringValue(gameplayObjectName, "EffectPosName");
numEffects = int(gpm->getFloatValue(gameplayObjectName, "NumEffects"));
effectTime = gpm->getFloatValue(gameplayObjectName, "EffectTime");
transportTime = gpm->getFloatValue(gameplayObjectName, "TransportTime");
immobilize = int(gpm->getFloatValue(gameplayObjectName, "Immobilize"));
hideFeatures = int(gpm->getFloatValue(gameplayObjectName, "HideFeatures"));
edict->s.renderfx &= ~RF_FORCE_ALPHA_EFFECTS;
if (shaderName.length())
{
setCustomShader(shaderName);
}
setAlpha(0.0f);
edict->s.renderfx |= RF_FORCE_ALPHA;
event = new Event(EV_FadeIn);
event->AddFloat(transportTime);
ProcessEvent(event);
event = new Event(EV_DisplayEffect);
event->AddString("TransportIn2");
event->AddString(effectName);
PostEvent(event, transportTime);
if (hideFeatures)
hideFeaturesForFade();
if (edict->s.parent == ENTITYNUM_NONE)
{
if (soundName.length())
{
Sound(soundName);
}
if (effectTikiName.length() > 4)
{
Vector pos;
currentEffectPos = effectPos;
for (i = 0; i < numEffects; i++)
{
if (!effectPos.length() || effectPos == "centroid")
pos = centroid;
else if (effectPos == "origin")
pos = origin;
else if (effectPos == "randombone")
{
int tagNum;
tagNum = int(G_Random(gi.NumTags(edict->s.modelindex)));
currentEffectPos = gi.Tag_NameForNum(edict->s.modelindex, tagNum);
GetTag(tagNum, &pos, nullptr);
} else if (gi.Tag_NumForName(edict->s.modelindex, effectPos.c_str()) >= 0)
GetTag(effectPos, &pos, nullptr);
else
pos = centroid;
if (attach)
attachEffect(effectTikiName, currentEffectPos, effectTime + transportTime);
else
SpawnEffect(effectTikiName, pos, angles, effectTime + transportTime);
}
}
}
if (immobilize)
{
takedamage = DamageNo;
flags |= FlagImmobile;
}
passEvent = true;
} else if (effectType == "TransportIn2")
{
str shaderName;
float effectTime;
// Get all of the info from the gameplay database
shaderName = gpm->getStringValue(gameplayObjectName, "ShaderName");
effectTime = gpm->getFloatValue(gameplayObjectName, "EffectTime");
CancelEventsOfType(EV_FadeIn);
edict->s.renderfx &= ~RF_FORCE_ALPHA;
if (shaderName.length())
{
setCustomShader(shaderName);
}
setAlpha(1.0f);
if (shaderName.length())
{
edict->s.renderfx |= RF_FORCE_ALPHA_EFFECTS;
event = new Event(EV_FadeNoRemove);
event->AddFloat(effectTime);
event->AddFloat(0.0f);
ProcessEvent(event);
}
event = new Event(EV_DisplayEffect);
event->AddString("TransportIn3");
event->AddString(effectName);
PostEvent(event, effectTime);
} else if (effectType == "TransportIn3")
{
str shaderName;
str animName;
int hideFeatures;
// Get all of the info from the gameplay database
shaderName = gpm->getStringValue(gameplayObjectName, "ShaderName");
animName = gpm->getStringValue(gameplayObjectName, "AnimName");
hideFeatures = int(gpm->getFloatValue(gameplayObjectName, "HideFeatures"));
if (shaderName.length())
{
clearCustomShader(shaderName);
edict->s.renderfx &= ~RF_FORCE_ALPHA_EFFECTS;
}
if (animate && animName.length() && animate->HasAnim(animName.c_str()))
{
animate->RandomAnimate(animName.c_str());
}
CancelEventsOfType(EV_FadeNoRemove);
if (hideFeatures)
showFeaturesForFade();
setAlpha(1.0f);
event = new Event(EV_EntityRenderEffects);
event->AddString("+shadow");
ProcessEvent(event);
takedamage = DamageYes;
flags &= ~FlagImmobile;
} else if (effectType == "FadeOut")
{
str shaderName;
str soundName;
str effectTikiName;
str effectPos;
str currentEffectPos;
float effectTime;
float fadeTime;
int numEffects;
int attach;
int hideFeatures;
// Get all of the info from the gameplay database
shaderName = gpm->getStringValue(gameplayObjectName, "ShaderName");
soundName = gpm->getStringValue(gameplayObjectName, "SoundName");
effectTikiName = gpm->getStringValue(gameplayObjectName, "EffectName");
attach = int(gpm->getFloatValue(gameplayObjectName, "AttachEffect"));
effectPos = gpm->getStringValue(gameplayObjectName, "EffectPosName");
numEffects = int(gpm->getFloatValue(gameplayObjectName, "NumEffects"));
effectTime = gpm->getFloatValue(gameplayObjectName, "EffectTime");
fadeTime = gpm->getFloatValue(gameplayObjectName, "FadeTime");
hideFeatures = int(gpm->getFloatValue(gameplayObjectName, "HideFeatures"));
setCustomShader(shaderName);
setAlpha(0.0f);
edict->s.renderfx |= RF_FORCE_ALPHA_EFFECTS;
edict->s.renderfx &= ~RF_FORCE_ALPHA;
if (effectTime > 0.0f)
{
event = new Event(EV_FadeIn);
event->AddFloat(effectTime);
ProcessEvent(event);
}
event = new Event(EV_DisplayEffect);
event->AddString("FadeOut2");
event->AddString(effectName);
PostEvent(event, effectTime);
if (hideFeatures)
hideFeaturesForFade();
if (edict->s.parent == ENTITYNUM_NONE)
{
// Play sound
if (soundName.length())
{
Sound(soundName);
}
// Spawn an effect
if (effectTikiName.length() > 4)
{
Vector pos;
currentEffectPos = effectPos;
for (i = 0; i < numEffects; i++)
{
if (!effectPos.length() || effectPos == "centroid")
pos = centroid;
else if (effectPos == "origin")
pos = origin;
else if (effectPos == "randombone")
{
int tagNum;
tagNum = int(G_Random(gi.NumTags(edict->s.modelindex)));
currentEffectPos = gi.Tag_NameForNum(edict->s.modelindex, tagNum);
GetTag(tagNum, &pos, nullptr);
} else if (gi.Tag_NumForName(edict->s.modelindex, effectPos.c_str()) >= 0)
GetTag(effectPos, &pos, nullptr);
else
pos = centroid;
if (attach)
attachEffect(effectTikiName, currentEffectPos, effectTime + fadeTime);
else
SpawnEffect(effectTikiName, pos, angles, effectTime + fadeTime);
}
}
}
passEvent = true;
} else if (effectType == "FadeOut2")
{
str shaderName;
float fadeTime;
// Get all of the info from the gameplay database
shaderName = gpm->getStringValue(gameplayObjectName, "ShaderName");
fadeTime = gpm->getFloatValue(gameplayObjectName, "FadeTime");
CancelEventsOfType(EV_FadeIn);
edict->s.renderfx &= ~RF_FORCE_ALPHA_EFFECTS;
setCustomShader(shaderName);
setAlpha(1.0f);
edict->s.renderfx |= RF_FORCE_ALPHA;
event = new Event(EV_FadeNoRemove);
event->AddFloat(fadeTime);
event->AddFloat(0.0f);
ProcessEvent(event);
event = new Event(EV_DisplayEffect);
event->AddString("FadeOut3");
event->AddString(effectName);
PostEvent(event, fadeTime);
} else if (effectType == "FadeOut3")
{
/* int hideFeatures;
hideFeatures = gpm->getFloatValue( gameplayObjectName, "HideFeatures" );
if ( hideFeatures )
showFeaturesForFade(); */
} else if (effectType == "FadeIn")
{
str shaderName;
str soundName;
str effectTikiName;
str effectPos;
str currentEffectPos;
//float effectTime;
float fadeTime;
int numEffects;
int attach;
int hideFeatures;
// Get all of the info from the gameplay database
shaderName = gpm->getStringValue(gameplayObjectName, "ShaderName");
soundName = gpm->getStringValue(gameplayObjectName, "SoundName");
effectTikiName = gpm->getStringValue(gameplayObjectName, "EffectName");
attach = int(gpm->getFloatValue(gameplayObjectName, "AttachEffect"));
effectPos = gpm->getStringValue(gameplayObjectName, "EffectPosName");
numEffects = int(gpm->getFloatValue(gameplayObjectName, "NumEffects"));
//effectTime = gpm->getFloatValue( gameplayObjectName, "EffectTime" );
fadeTime = gpm->getFloatValue(gameplayObjectName, "FadeTime");
hideFeatures = int(gpm->getFloatValue(gameplayObjectName, "HideFeatures"));
setCustomShader(shaderName);
setAlpha(0.0f);
edict->s.renderfx &= ~RF_FORCE_ALPHA_EFFECTS;
edict->s.renderfx |= RF_FORCE_ALPHA;
event = new Event(EV_FadeIn);
event->AddFloat(fadeTime);
ProcessEvent(event);
event = new Event(EV_DisplayEffect);
event->AddString("FadeIn2");
event->AddString(effectName);
PostEvent(event, fadeTime);
if (hideFeatures)
hideFeaturesForFade();
if (edict->s.parent == ENTITYNUM_NONE)
{
// Play sound
if (soundName.length())
{
Sound(soundName);
}
// Spawn an effect
if (effectTikiName.length() > 4)
{
Vector pos;
currentEffectPos = effectPos;
for (i = 0; i < numEffects; i++)
{
if (!effectPos.length() || effectPos == "centroid")
pos = centroid;
else if (effectPos == "origin")
pos = origin;
else if (effectPos == "randombone")
{
auto tagNum = int(G_Random(gi.NumTags(edict->s.modelindex)));
currentEffectPos = gi.Tag_NameForNum(edict->s.modelindex, tagNum);
GetTag(tagNum, &pos, nullptr);
} else if (gi.Tag_NumForName(edict->s.modelindex, effectPos.c_str()) >= 0)
GetTag(effectPos, &pos, nullptr);
else
pos = centroid;
if (attach)
attachEffect(effectTikiName, currentEffectPos, fadeTime + fadeTime);
else
SpawnEffect(effectTikiName, pos, angles, fadeTime + fadeTime);
}
}
}
passEvent = true;
} else if (effectType == "FadeIn2")
{
str shaderName;
float effectTime;
// Get all of the info from the gameplay database
shaderName = gpm->getStringValue(gameplayObjectName, "ShaderName");
effectTime = gpm->getFloatValue(gameplayObjectName, "EffectTime");
CancelEventsOfType(EV_FadeIn);
setAlpha(1.0f);
if (shaderName.length() > 0)
{
setCustomShader(shaderName);
edict->s.renderfx |= RF_FORCE_ALPHA_EFFECTS;
edict->s.renderfx |= RF_FORCE_ALPHA;
event = new Event(EV_FadeNoRemove);
event->AddFloat(effectTime);
event->AddFloat(0.0f);
ProcessEvent(event);
}
event = new Event(EV_DisplayEffect);
event->AddString("FadeIn3");
event->AddString(effectName);
PostEvent(event, effectTime);
} else if (effectType == "FadeIn3")
{
str shaderName;
int hideFeatures;
// Get all of the info from the gameplay database
shaderName = gpm->getStringValue(gameplayObjectName, "ShaderName");
hideFeatures = int(gpm->getFloatValue(gameplayObjectName, "HideFeatures"));
CancelEventsOfType(EV_FadeNoRemove);
edict->s.renderfx &= ~RF_FORCE_ALPHA_EFFECTS;
edict->s.renderfx &= ~RF_FORCE_ALPHA;
if (hideFeatures)
showFeaturesForFade();
clearCustomShader(shaderName);
setAlpha(1.0f);
} else if (effectType == "start_invisibility")
{
edict->s.eFlags |= EF_EFFECT_ELECTRIC;
setAlpha(0.1f);
edict->s.renderfx |= RF_FORCE_ALPHA;
addAffectingViewModes(gi.GetViewModeMask("forcevisible"));
passEvent = true;
} else if (effectType == "stop_invisibility")
{
edict->s.eFlags &= ~EF_EFFECT_ELECTRIC;
setAlpha(1.0f);
edict->s.renderfx &= ~RF_FORCE_ALPHA;
removeAffectingViewModes(gi.GetViewModeMask("forcevisible"));
passEvent = true;
} else if (effectType == "electric")
{
edict->s.eFlags |= EF_EFFECT_ELECTRIC;
passEvent = true;
} else if (effectType == "noelectric")
{
edict->s.eFlags &= ~EF_EFFECT_ELECTRIC;
passEvent = true;
} else if (effectType == "failure")
{
edict->s.eFlags |= EF_BEHAVIOR_FAILURE;
passEvent = true;
} else if (effectType == "nofailure")
{
edict->s.eFlags &= ~EF_BEHAVIOR_FAILURE;
passEvent = true;
} else if (effectType == "transport_out" || effectType == "transport_in" || effectType == "borg_transport_out" ||
effectType == "borg_transport_in")
{
gi.WDPrintf("Support for %s will soon be removed please use the new way\n", effectType.c_str());
event = new Event(EV_DisplayEffect);
if (effectType == "transport_out")
{
event->AddString("TransportOut");
event->AddString("Federation");
} else if (effectType == "transport_in")
{
event->AddString("TransportIn");
event->AddString("Federation");
} else if (effectType == "borg_transport_out")
{
event->AddString("TransportOut");
event->AddString("Borg");
} else if (effectType == "borg_transport_in")
{
event->AddString("TransportIn");
event->AddString("Borg");
}
ProcessEvent(event);
return;
}
if (passEvent && bind_info && bind_info->numchildren)
{
for (i = 0; i < MAX_MODEL_CHILDREN; i++)
{
if (bind_info->children[i] == ENTITYNUM_NONE)
continue;
ent = G_GetEntity(bind_info->children[i]);
if (ent)
{
ent->ProcessEvent(*ev);
}
}
}
}
//----------------------------------------------------------------
// Name: clearDisplayEffects
// Class: Entity
//
// Description: Clears all of the display type of effects and sets the alpha back to normal
//
// Parameters: None
//
// Returns: None
//----------------------------------------------------------------
void Entity::clearDisplayEffects()
{
if (edict->s.eFlags | EF_EFFECTS)
{
edict->s.eFlags &= ~EF_EFFECTS;
setAlpha(1.0f);
edict->s.renderfx &= ~RF_FORCE_ALPHA_EFFECTS;
edict->s.renderfx &= ~RF_FORCE_ALPHA;
}
}
void Entity::SpawnEffect(Event* ev)
{
//const str &name, const Vector &origin, const Vector &angles, float removeTime )
Vector tagPos;
Vector tagForward;
auto modelName = ev->GetString(1);
auto tagName = ev->GetString(2);
auto tagAngles = tagForward.toAngles();
auto removeTime = ev->NumArgs() > 2 ? ev->GetFloat(3) : 0.0f;
GetTag(tagName, &tagPos, &tagForward);
SpawnEffect(modelName, tagPos, tagAngles, removeTime);
}
Entity* Entity::SpawnEffect(const str& name, const Vector& origin, const Vector& angles, float removeTime)
{
str modelName;
auto nameLength = name.length();
if (stricmp(name.c_str() + nameLength - 4, ".gdb") == 0)
{
str gameplayObjectName;
GameplayManager* gpm;
gpm = GameplayManager::getTheGameplayManager();
gameplayObjectName = name;
gameplayObjectName.CapLength(nameLength - 4);
if (gpm->hasObject(gameplayObjectName))
{
modelName = gpm->getStringValue(gameplayObjectName, "ModelName");
} else
{
const char* dash;
dash = strstr(gameplayObjectName.c_str(), "-");
if (dash)
{
gameplayObjectName.CapLength(dash - gameplayObjectName.c_str());
gameplayObjectName += "-default";
if (gpm->hasObject(gameplayObjectName))
{
modelName = gpm->getStringValue(gameplayObjectName, "ModelName");
} else
{
gi.WDPrintf("%s not find in the gameplay database\n", name.c_str());
return nullptr;
}
} else
{
gi.WDPrintf("%s not find in the gameplay database\n", name.c_str());
return nullptr;
}
}
} else
{
modelName = name;
}
auto newEntity = new Entity(EntityCreateFlagAnimate);
newEntity->setModel(modelName);
newEntity->angles = angles;
newEntity->setAngles();
newEntity->setOrigin(origin);
newEntity->setSolidType(SOLID_NOT);
if (removeTime > 0.0f)
newEntity->PostEvent(EV_Remove, removeTime);
newEntity->CancelEventsOfType(EV_ProcessInitCommands);
newEntity->ProcessInitCommands(newEntity->edict->s.modelindex);
newEntity->animate->RandomAnimate("idle");
return newEntity;
}
Entity* Entity::SpawnSound(const str& sound, const Vector& pos, float volume, float removeTime)
{
auto newEntity = new Entity(EntityCreateFlagAnimate);
newEntity->setOrigin(pos);
newEntity->setSolidType(SOLID_NOT);
if (removeTime > 0.0f)
newEntity->PostEvent(EV_Remove, removeTime);
auto soundOrigin = pos;
newEntity->CancelEventsOfType(EV_ProcessInitCommands);
newEntity->ProcessInitCommands(newEntity->edict->s.modelindex);
newEntity->Sound(sound, CHAN_BODY, volume, -1.0f, &soundOrigin);
return newEntity;
}
void Entity::attachEffect(Event* ev)
{
auto modelName = ev->GetString(1);
auto tagName = ev->GetString(2);
auto removeTime = ev->NumArgs() > 2 ? ev->GetFloat(3) : 0.0f;
attachEffect(modelName, tagName, removeTime);
}
void Entity::attachEffect(const str& modelName, const str& tagName, float removeTime)
{
auto newEvent = new Event(EV_AttachModel);
newEvent->AddString(modelName);
newEvent->AddString(tagName);
newEvent->AddFloat(1.0f);
newEvent->AddString("");
newEvent->AddInteger(0);
newEvent->AddFloat(removeTime);
ProcessEvent(newEvent);
}
void Entity::ForceAlpha(Event* ev)
{
if (ev->NumArgs() == 0)
edict->s.renderfx |= RF_FORCE_ALPHA;
else if (ev->GetBoolean(1))
edict->s.renderfx |= RF_FORCE_ALPHA;
else
edict->s.renderfx &= ~RF_FORCE_ALPHA;
}
void Entity::CreateEarthquake(Event* ev)
{
if (origin == vec_zero)
{
gi.WDPrintf("Earthquake being started when origin hasn't been set yet in model %s\n", model.c_str());
return;
}
auto distance = 0.0f;
auto magnitude = ev->GetFloat(1);
auto duration = ev->GetFloat(2);
if (ev->NumArgs() > 2)
distance = ev->GetFloat(3);
auto earthquake = new Earthquake;
auto newEvent = new Event(EV_SetOrigin);
newEvent->AddVector(origin);
earthquake->ProcessEvent(newEvent);
newEvent = new Event(EV_Earthquake_SetMagnitude);
newEvent->AddFloat(magnitude);
earthquake->ProcessEvent(newEvent);
newEvent = new Event(EV_Earthquake_SetDuration);
newEvent->AddFloat(duration);
earthquake->ProcessEvent(newEvent);
if (distance)
{
newEvent = new Event(EV_Earthquake_SetDistance);
newEvent->AddFloat(distance);
earthquake->ProcessEvent(newEvent);
}
earthquake->ProcessEvent(EV_Trigger_Effect);
earthquake->PostEvent(EV_Remove, duration);
}
void Entity::SetFloatVar(Event* ev)
{
entityVars.SetVariable(ev->GetString(1), ev->GetFloat(2));
}
void Entity::SetVectorVar(Event* ev)
{
entityVars.SetVariable(ev->GetString(1), ev->GetVector(2));
}
void Entity::SetStringVar(Event* ev)
{
entityVars.SetVariable(ev->GetString(1), ev->GetString(2));
}
void Entity::doesVarExist(Event* ev)
{
ev->ReturnFloat(entityVars.GetVariable(ev->GetString(1)) != nullptr ? 1.0f : 0.0f);
}
void Entity::GetFloatVar(Event* ev)
{
auto var_name = ev->GetString(1);
auto var = entityVars.GetVariable(var_name);
if (var)
ev->ReturnFloat(var->floatValue());
else
{
gi.WDPrintf("%s variable not found\n", var_name);
ev->ReturnFloat(0.0f);
}
}
void Entity::RemoveVariable(Event* ev)
{
if (ev->NumArgs() > 0)
{
str var_name = ev->GetString(1);
if (var_name.length() > 0)
{
entityVars.RemoveVariable(var_name);
}
}
}
void Entity::GetVectorVar(Event* ev)
{
auto var_name = ev->GetString(1);
auto var = entityVars.GetVariable(var_name);
if (var)
ev->ReturnVector(var->vectorValue());
else
{
gi.WDPrintf("%s variable not found\n", var_name);
ev->ReturnVector(Vector(0, 0, 0));
}
}
void Entity::GetStringVar(Event* ev)
{
auto var_name = ev->GetString(1);
auto var = entityVars.GetVariable(var_name);
if (var)
ev->ReturnString(var->stringValue());
else
{
gi.WDPrintf("%s variable not found\n", var_name);
ev->ReturnString("");
}
}
void Entity::SetUserVar1(Event* ev)
{
entityVars.SetVariable("uservar1", ev->GetString(1));
}
void Entity::SetUserVar2(Event* ev)
{
entityVars.SetVariable("uservar2", ev->GetString(1));
}
void Entity::SetUserVar3(Event* ev)
{
entityVars.SetVariable("uservar3", ev->GetString(1));
}
void Entity::SetUserVar4(Event* ev)
{
entityVars.SetVariable("uservar4", ev->GetString(1));
}
Vector Entity::GetClosestCorner(const Vector& position)
{
Vector corner;
Vector closestCorner;
auto distance = 999999999.9f;
for (auto i = 0; i < 4; i++)
{
// corner's based on a top down view of the bounding box -- I am returning
// the vector for the corner on a plane with the origin only.
switch (i)
{
// Upper Left
case 0:
corner.x = origin.x + mins.x;
corner.y = origin.y + maxs.y;
corner.z = origin.z;
break;
// Upper Right
case 1:
corner.x = origin.x + maxs.x;
corner.y = origin.y + maxs.y;
corner.z = origin.z;
break;
// Lower Left
case 2:
corner.x = origin.x + mins.x;
corner.y = origin.y + mins.y;
corner.z = origin.z;
break;
case 3:
corner.x = origin.x + maxs.x;
corner.y = origin.y + mins.y;
corner.z = origin.z;
break;
}
auto compare = corner - position;
auto length = compare.length();
if (length < distance)
{
closestCorner = corner;
distance = length;
}
}
return closestCorner;
}
//----------------------------------------------------------------
// Name: affectingViewMode
// Class: Entity
//
// Description: Adds a new viewmode to the entities applicable viewmodes
//
// Parameters: Event *ev - event (name of the view mode)
//
// Returns: None
//----------------------------------------------------------------
void Entity::affectingViewMode(Event* ev)
{
addAffectingViewModes(gi.GetViewModeMask(ev->GetString(1)));
}
//----------------------------------------------------------------
// Name: addAffectingViewMode
// Class: Entity
//
// Description: Adds the specified viewmode bits to the entities applicable viewmodes
//
// Parameters: unsigned int mask - the bit mask of the relevant viewmodes
//
// Returns: None
//----------------------------------------------------------------
void Entity::addAffectingViewModes(unsigned int mask)
{
_affectingViewModes |= mask;
edict->s.affectingViewModes = _affectingViewModes;
}
void Entity::removeAffectingViewModes(unsigned int mask)
{
_affectingViewModes &= ~mask;
edict->s.affectingViewModes = _affectingViewModes;
}
//--------------------------------------------------------------
// Name: SetGroupID()
// Class: Entity
//
// Description: Grabs the ID from the event, and sends it to the
// group coordinator for registration. In the future
// we need to migrate this so that all group registration
// is done throught he group coordinator alone.
//
// Parameters: Event *ev
//
// Returns: None
//--------------------------------------------------------------
void Entity::SetGroupID(Event* ev)
{
AddToGroup(ev->GetInteger(1));
}
void Entity::AddToGroup(int ID)
{
groupcoordinator->AddEntityToGroup(this, ID);
}
//----------------------------------------------------------------
// Name: TikiTodo and TikiNote
// Class: Entity
//
// Description: These commands may come from tiki files (via TikiMaster).
// They are here so the commands are not considered errors.
// In the future we might want cvars to print todo items.
//
// Parameters: Event *ev
//
// Returns: None
//----------------------------------------------------------------
void Entity::TikiTodo(Event*)
{
}
void Entity::TikiNote(Event*)
{
}
//----------------------------------------------------------------
// Name: MultiplayerEvent
// Class: Entity
//
// Description: This is a passthrough event. It only allows the contained event to be called
// if we are in a multiplayer game
//
// Parameters: Event *ev - contains the real event to process
//
// Returns: None
//----------------------------------------------------------------
void Entity::MultiplayerEvent(Event* ev)
{
// Make sure we are in a multiplayer game
if (multiplayerManager.inMultiplayer())
{
auto event = new Event(ev->GetString(1));
// Get all of the event parms
for (auto i = 2; i <= ev->NumArgs(); i++)
{
event->AddToken(ev->GetToken(i));
}
// Process the event
ProcessEvent(event);
}
}
//--------------------------------------------------------------
//
// Name: AddDamageModifier
// Class: Entity
//
// Description: Adds a new damage modifier to this entities list
//
// Parameters: Event *ev
//
// Returns: None
//
//--------------------------------------------------------------
void Entity::AddDamageModifier(Event* ev)
{
float chance = 1.0f, painBaseLine = 50.0f;
if (ev->NumArgs() > 3)
chance = ev->GetFloat(4);
if (ev->NumArgs() > 4)
painBaseLine = ev->GetFloat(5);
if (!damageModSystem)
damageModSystem = new DamageModificationSystem;
damageModSystem->addDamageModifier(ev->GetString(1), ev->GetString(2), ev->GetFloat(3), chance, painBaseLine);
}
//--------------------------------------------------------------
//
// Name: ResolveDamage
// Class: Entity
//
// Description: Calls the DamageModificationSystem to resolve damage
//
// Parameters: Damage &damage -- Reference to damage to modifiy
//
// Returns:
//
//--------------------------------------------------------------
void Entity::ResolveDamage(::Damage& damage)
{
if (damageModSystem)
damageModSystem->resolveDamage(damage);
}
//--------------------------------------------------------------
// Name: setMoveType()
// Class: Entity
//
// Description: Will convert a string to the appropriate moveType
// and then call setMoveType( type )
//
// Parameters: Event *ev
//
// Returns: None
//--------------------------------------------------------------
void Entity::setMoveType(Event* ev)
{
auto type = ev->GetString(1);
if (!stricmp(type, "none"))
{
velocity = vec_zero;
setMoveType(MOVETYPE_NONE);
} else if (!stricmp(type, "stationary"))
setMoveType(MOVETYPE_STATIONARY);
else if (!stricmp(type, "noclip"))
setMoveType(MOVETYPE_NOCLIP);
else if (!stricmp(type, "push"))
setMoveType(MOVETYPE_PUSH);
else if (!stricmp(type, "stop"))
setMoveType(MOVETYPE_STOP);
else if (!stricmp(type, "walk"))
setMoveType(MOVETYPE_WALK);
else if (!stricmp(type, "step"))
setMoveType(MOVETYPE_STEP);
else if (!stricmp(type, "fly"))
setMoveType(MOVETYPE_FLY);
else if (!stricmp(type, "toss"))
setMoveType(MOVETYPE_TOSS);
else if (!stricmp(type, "flymissile"))
setMoveType(MOVETYPE_FLYMISSILE);
else if (!stricmp(type, "bounce"))
setMoveType(MOVETYPE_BOUNCE);
else if (!stricmp(type, "slide"))
setMoveType(MOVETYPE_SLIDE);
else if (!stricmp(type, "rope"))
setMoveType(MOVETYPE_ROPE);
else if (!stricmp(type, "gib"))
setMoveType(MOVETYPE_GIB);
else if (!stricmp(type, "vehicle"))
setMoveType(MOVETYPE_VEHICLE);
}
//===============================================================
// Name: BuildUseData
// Class: Entity
//
// Description: Creates a usedata structure if need be. Also
// ensures the contents type of a usable object is
// set to something that can be detected by the
// use trace (so it can indeed be used).
//
// Testing this idea out.
//
// Parameters: None
//
// Returns: None
//
//===============================================================
void Entity::BuildUseData()
{
if (useData)
return;
useData = new UseData();
}
//--------------------------------------------------------------
// Name: useDataAnim
// Class: Entity
//
// Description: Sets the useData anim member
//
// Parameters: Event *ev
//
// Returns: None
//--------------------------------------------------------------
void Entity::useDataAnim(Event* ev)
{
BuildUseData();
useData->setUseAnim(ev->GetString(1));
}
//--------------------------------------------------------------
// Name: useDataType
// Class: Entity
//
// Description: Sets the useData type member
//
// Parameters: Event *ev
//
// Returns: None
//--------------------------------------------------------------
void Entity::useDataType(Event* ev)
{
BuildUseData();
useData->setUseType(ev->GetString(1));
}
//--------------------------------------------------------------
// Name: useDataThread
// Class: Entity
//
// Description: Sets the useData thread member
//
// Parameters: Event *ev
//
// Returns: None
//--------------------------------------------------------------
void Entity::useDataThread(Event* ev)
{
BuildUseData();
useData->setUseThread(ev->GetString(1));
}
//--------------------------------------------------------------
// Name: useDataEvent
// Class: Entity
//
// Description: Sets the useData variables
//
// Parameters: Event *ev
//
// Returns: None
//--------------------------------------------------------------
void Entity::useDataEvent(Event* ev)
{
BuildUseData();
useData->setUseAnim(ev->GetString(1));
useData->setUseType(ev->GetString(2));
useData->setUseThread(ev->GetString(3));
}
//--------------------------------------------------------------
// Name: useDataMaxDist
// Class: Entity
//
// Description: Sets the maximum distance this entity can be used.
//
// Parameters: Event *ev
//
// Returns: None
//--------------------------------------------------------------
void Entity::useDataMaxDist(Event* ev)
{
BuildUseData();
useData->setUseMaxDist(ev->GetFloat(1));
}
//--------------------------------------------------------------
// Name: useDataCount
// Class: Entity
//
// Description: Sets the number of times this entity can be used.
//
// Parameters: Event *ev
//
// Returns: None
//--------------------------------------------------------------
void Entity::useDataCount(Event* ev)
{
BuildUseData();
useData->setUseCount(ev->GetInteger(1));
}
//--------------------------------------------------------------
// Name: setArchetype
// Class: Entity
//
// Description: Sets the archetype name for this entity
//
// Parameters: Event *ev
//
// Returns: None
//--------------------------------------------------------------
void Entity::setArchetype(Event* ev)
{
_archetype = ev->GetString(1);
//Sets the archetype index
edict->s.archeTypeIndex = gi.archetypeindex(_archetype);
}
//--------------------------------------------------------------
// Name: getArchetype
// Class: Entity
//
// Description: Gets the archetype name for this entity,
//
// Parameters: None
//
// Returns: const str
//--------------------------------------------------------------
str Entity::getArchetype() const
{
if (_archetype.length() == 0)
return getName();
return _archetype;
}
//-----------------------------------------------------
//
// Name: setMissionObjective
// Class: Entity
//
// Description: Sets the entity to be a mission objective or not.
//
// Parameters: ev
//
// Returns: None
//-----------------------------------------------------
void Entity::setMissionObjective(Event* ev)
{
_missionObjective = ev->GetBoolean(1);
edict->s.missionObjective = ev->GetBoolean(1);
if (edict->s.missionObjective)
{
G_AddEntityToExtraList(entnum);
} else
{
G_RemoveEntityFromExtraList(entnum);
}
}
//--------------------------------------------------------------
// Name: GetVelocity
// Class: Entity
//
// Description: Gets the Velocity of the Entity
//
// Parameters: Event *ev
//
// Returns: None
//--------------------------------------------------------------
void Entity::GetVelocity(Event* ev)
{
ev->ReturnVector(velocity);
}
//--------------------------------------------------------------
// Name: SetVelocity
// Class: Entity
//
// Description: Sets the Velocity of the Entity
//
// Parameters: Event *ev
//
// Returns: None
//--------------------------------------------------------------
void Entity::SetVelocity(Event* ev)
{
velocity = ev->GetVector(1);
}
void Entity::startStasis(Event*)
{
startStasis();
}
void Entity::stopStasis(Event*)
{
stopStasis();
}
void Entity::startStasis()
{
// Immobilize the character
flags |= FlagImmobile;
flags |= FlagStunned;
// Show an effect
setCustomShader("stasis");
if (animate)
{
animate->StopAnimating();
if (edict->s.torso_anim & ANIM_BLEND)
animate->StopAnimating(torso);
}
}
void Entity::stopStasis()
{
// Unimmobilize the character
flags &= ~FlagImmobile;
flags &= ~FlagStunned;
// Stop the effect
clearCustomShader("stasis");
if (this->isSubclassOf(Actor))
{
auto actor = dynamic_cast<Actor*>(this);
actor->resetStateMachine();
} else if (this->isSubclassOf(Player))
{
auto player = dynamic_cast<Player *>(this);
player->SetState("STAND", "START");
}
}
void Entity::setTargetPos(Event* ev)
{
setTargetPos(ev->GetString(1));
}
void Entity::setTargetPos(const str& targetPos)
{
_targetPos = targetPos;
}
str Entity::getTargetPos()
{
return _targetPos;
}
void Entity::addHealthOverTime(Event* ev)
{
auto healthToAdd = ev->GetFloat(1);
auto timeLeft = ev->GetFloat(2);
auto numFrames = timeLeft < level.frametime ? 1 : int(timeLeft / level.frametime);
auto healthToAddThisFrame = healthToAdd / numFrames;
// Actually add the health to the entity
addHealth(healthToAddThisFrame);
// Post the event for the next frame
healthToAdd -= healthToAddThisFrame;
timeLeft -= level.frametime;
//CancelEventsOfType( EV_AddHealthOverTime );
if (timeLeft > 0.0f)
{
auto event = new Event(EV_AddHealthOverTime);
event->AddFloat(healthToAdd);
event->AddFloat(timeLeft);
PostEvent(event, level.frametime);
}
}
void Entity::simplePlayDialog(Event* ev)
{
auto volume = ev->NumArgs() > 1 ? ev->GetFloat(2) : DEFAULT_VOL;
auto min_dist = DEFAULT_MIN_DIST;
char localizedDialogName[MAX_QPATH];
// Get all of the parms
if (ev->NumArgs() > 2)
{
auto minDistString = ev->GetString(3);
if (stricmp(minDistString, LEVEL_WIDE_STRING) == 0)
min_dist = LEVEL_WIDE_MIN_DIST;
else
min_dist = ev->GetFloat(3);
if (min_dist >= LEVEL_WIDE_MIN_DIST_CUTOFF)
min_dist = LEVEL_WIDE_MIN_DIST;
}
// Get the localized name of the dialog
gi.LocalizeFilePath(ev->GetString(1), localizedDialogName);
// Play the sound
Sound(localizedDialogName, CHAN_DIALOG, volume, min_dist);
// Tell the player about the dialog
auto player = GetPlayer(0);
if (player)
{
player->SetupDialog(nullptr, localizedDialogName);
}
}
void Entity::warp(Event* ev)
{
if (ev->NumArgs() > 0)
setOrigin(ev->GetVector(1));
else
setOrigin();
setAngles();
NoLerpThisFrame();
if (bind_info)
{
// Make sure everyone bound to us doesn't lerp
for (auto ent = bind_info->teamchain; ent != nullptr; ent = ent->bind_info->teamchain)
{
if (ent->bind_info->teammaster == this)
{
ent->ProcessEvent(EV_Warp);
}
}
// Make sure everyone attached bound to us doesn't lerp
for (auto i = 0; i < MAX_MODEL_CHILDREN; i++)
{
if (bind_info->children[i] == ENTITYNUM_NONE)
{
continue;
}
auto ent = G_GetEntity(bind_info->children[i]);
if (ent)
{
ent->ProcessEvent(EV_Warp);
}
}
}
}
void Entity::traceHitsEntity(Event* ev)
{
Vector start;
Vector dir;
auto entityToCheck = ev->GetEntity(3);
GetTag(ev->GetString(1), &start, &dir);
auto end = start + dir * ev->GetFloat(2);
auto trace = G_Trace(start, vec_zero, vec_zero, end, nullptr, MASK_SHOT, false, "traceHitsEntity");
// Determine if we hit this entity
if (trace.ent && entityToCheck && trace.ent->entity == entityToCheck)
ev->ReturnFloat(1.0f);
else
ev->ReturnFloat(0.0f);
}
void Entity::setOriginEveryFrame(Event*)
{
auto repost = new Event(EV_SetOriginEveryFrame);
setOrigin();
PostEvent(repost, FRAMETIME);
}
void Entity::isWithinDistanceOf(Event* ev)
{
ev->ReturnFloat(WithinDistance(ev->GetEntity(1), ev->GetFloat(2)) ? 1.0f : 0.0f);
}
void Entity::setNetworkDetail(Event*)
{
_networkDetail = true;
}
bool Entity::isNetworkDetail(void)
{
return _networkDetail;
}