nuclide/src/shared/NSEntity.qc

992 lines
18 KiB
Plaintext

/*
* Copyright (c) 2016-2022 Vera Visions LLC.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
============
NSEntity::NSEntity
client doesn't have to do a whole lot here
============
*/
void
NSEntity::NSEntity(void)
{
blocked = BlockedHandler;
touch = TouchHandler;
#ifdef SERVER
identity = 1;
#endif
}
void
NSEntity::Spawned(void)
{
super::Spawned();
m_oldAngle = angles;
m_oldOrigin = origin;
m_oldModel = Util_FixModel(model);
m_oldSolid = solid;
#ifdef SERVER
m_oldstrTarget = target;
if (m_oldModel) {
precache_model(GetSpawnModel());
}
if (m_strOnTrigger)
m_strOnTrigger = CreateOutput(m_strOnTrigger);
#endif
}
float
NSEntity::EntIndex(void)
{
return num_for_edict(this);
}
void
droptofloorwrapper(entity foo)
{
entity old_self = self;
self = foo;
droptofloor();
self = old_self;
}
void
NSEntity::DropToFloor(void)
{
droptofloorwrapper(this);
}
vector
NSEntity::GetForward(void)
{
makevectors(angles);
return v_forward;
}
vector
NSEntity::GetRight(void)
{
makevectors(angles);
return v_right;
}
vector
NSEntity::GetUp(void)
{
makevectors(angles);
return v_up;
}
/*
============
NSEntity::WorldSpaceCenter
Returns the center of an entity's bounding boxes.
Useful on brush entities that have no real 'origin' defined.
============
*/
vector
NSEntity::WorldSpaceCenter(void)
{
return absmin + (0.5 * (absmax - absmin));
}
/*
============
NSEntity::VisibleVec
Returns whether or not the entity is able to see a given position
============
*/
float
NSEntity::WaterLevel(void)
{
return waterlevel;
}
/*
============
NSEntity::VisibleVec
Returns whether or not the entity is able to see a given position
============
*/
bool
NSEntity::VisibleVec(vector org)
{
vector flDelta;
float flFoV;
makevectors(angles);
flDelta = normalize (org - origin);
flFoV = flDelta * v_forward;
if (flFoV > 0.3f) {
traceline(origin, org, TRUE, this);
if (trace_fraction == 1.0f) {
return (TRUE);
}
}
return (FALSE);
}
/*
============
NSEntity::Visible
Returns whether or not the entity is able to see a given entity
============
*/
bool
NSEntity::Visible(entity ent)
{
vector flDelta;
float flFoV;
makevectors(angles);
flDelta = normalize(ent.origin - origin);
flFoV = flDelta * v_forward;
/* is it in our field of view? */
if (flFoV > 0.3f) {
traceline(origin, ent.origin, MOVE_NORMAL, this);
if (trace_fraction == 1.0f || trace_ent == ent) {
print(sprintf("%s can see %s\n", classname, ent.classname));
return (true);
}
}
print(sprintf("%s can not see %s\n", classname, ent.classname));
return (false);
}
bool
NSEntity::HasSpawnFlags(float sf)
{
return (spawnflags & sf) ? true : false;
}
bool
NSEntity::IsOnGround(void)
{
return (flags & FL_ONGROUND) ? true : false;
}
bool
NSEntity::IsSolid(void)
{
return (solid != SOLID_NOT) ? true : false;
}
entity
NSEntity::GetGroundEntity(void)
{
return groundentity;
}
bool
NSEntity::CreatedByMap(void)
{
return _mapspawned;
}
void
NSEntity::Blocked(entity eBlocker)
{
/* To be handled by sub-classes */
}
void
NSEntity::BlockedHandler(void)
{
Blocked(other);
}
void
NSEntity::Touch(entity eToucher)
{
/* To be handled by sub-classes */
}
void
NSEntity::StartTouch(entity eToucher)
{
/* To be handled by sub-classes */
}
void
NSEntity::EndTouch(entity eToucher)
{
/* To be handled by sub-classes */
}
void
NSEntity::TouchHandler(void)
{
#ifdef SERVER
if (g_grMode.IsTeamplay())
if (m_iTeam > 0i)
if (m_iTeam != other.team) {
return;
}
#endif
/* start touch in case we haven't */
if (m_beingTouched != true)
StartTouch(other);
Touch(other);
m_flTouchTime = time;
m_beingTouched = true;
m_eTouchLast = other;
}
#ifdef CLIENT
/*
============
NSEntity::RendererRestarted
make sure any child-classes precache their assets in here
for vid_reload calls or other sudden deaths (driver restarts)
============
*/
void
NSEntity::RendererRestarted(void)
{
}
/*
============
NSEntity::ReceiveEntity
============
*/
void
NSEntity::ReceiveEntity(float flNew, float flChanged)
{
READENTITY_COORD(origin[0], BASEFL_CHANGED_ORIGIN_X)
READENTITY_COORD(origin[1], BASEFL_CHANGED_ORIGIN_Y)
READENTITY_COORD(origin[2], BASEFL_CHANGED_ORIGIN_Z)
READENTITY_ANGLE(angles[0], BASEFL_CHANGED_ANGLES_X)
READENTITY_ANGLE(angles[1], BASEFL_CHANGED_ANGLES_Y)
READENTITY_ANGLE(angles[2], BASEFL_CHANGED_ANGLES_Z)
READENTITY_SHORT(modelindex, BASEFL_CHANGED_MODELINDEX)
READENTITY_BYTE(solid, BASEFL_CHANGED_SOLID)
READENTITY_BYTE(movetype, BASEFL_CHANGED_FLAGS)
READENTITY_INT(flags, BASEFL_CHANGED_FLAGS)
READENTITY_COORD(mins[0], BASEFL_CHANGED_SIZE)
READENTITY_COORD(mins[1], BASEFL_CHANGED_SIZE)
READENTITY_COORD(mins[2], BASEFL_CHANGED_SIZE)
READENTITY_COORD(maxs[0], BASEFL_CHANGED_SIZE)
READENTITY_COORD(maxs[1], BASEFL_CHANGED_SIZE)
READENTITY_COORD(maxs[2], BASEFL_CHANGED_SIZE)
READENTITY_BYTE(frame, BASEFL_CHANGED_FRAME)
READENTITY_FLOAT(skin, BASEFL_CHANGED_SKIN)
READENTITY_FLOAT(effects, BASEFL_CHANGED_EFFECTS)
READENTITY_FLOAT(scale, BASEFL_CHANGED_SCALE)
READENTITY_COORD(velocity[0], BASEFL_CHANGED_VELOCITY_X)
READENTITY_COORD(velocity[1], BASEFL_CHANGED_VELOCITY_Y)
READENTITY_COORD(velocity[2], BASEFL_CHANGED_VELOCITY_Z)
if (modelindex) {
drawmask = MASK_ENGINE;
} else {
drawmask = 0;
}
if (scale == 0.0f)
scale = 1.0f;
if (flChanged & BASEFL_CHANGED_SIZE)
setsize(this, mins, maxs);
setorigin(this, origin);
}
/*
============
NSEntity::postdraw
============
*/
void
NSEntity::postdraw(void)
{
}
#else
void
NSEntity::Show(void)
{
alpha = 1.0f;
}
void
NSEntity::Hide(void)
{
alpha = 0.0f;
}
/* Make sure StartFrame calls this */
float
NSEntity::SendEntity(entity ePEnt, float flChanged)
{
if (!modelindex)
return (0);
if (clienttype(ePEnt) != CLIENTTYPE_REAL)
return (0);
if (alpha == 0.0f)
return (0);
WriteByte(MSG_ENTITY, ENT_ENTITY);
/* broadcast how much data is expected to be read */
WriteFloat(MSG_ENTITY, flChanged);
SENDENTITY_COORD(origin[0], BASEFL_CHANGED_ORIGIN_X)
SENDENTITY_COORD(origin[1], BASEFL_CHANGED_ORIGIN_Y)
SENDENTITY_COORD(origin[2], BASEFL_CHANGED_ORIGIN_Z)
SENDENTITY_ANGLE(angles[0], BASEFL_CHANGED_ANGLES_X)
SENDENTITY_ANGLE(angles[1], BASEFL_CHANGED_ANGLES_Y)
SENDENTITY_ANGLE(angles[2], BASEFL_CHANGED_ANGLES_Z)
SENDENTITY_SHORT(modelindex, BASEFL_CHANGED_MODELINDEX)
SENDENTITY_BYTE(solid, BASEFL_CHANGED_SOLID)
SENDENTITY_BYTE(movetype, BASEFL_CHANGED_FLAGS)
SENDENTITY_INT(flags, BASEFL_CHANGED_FLAGS)
SENDENTITY_COORD(mins[0], BASEFL_CHANGED_SIZE)
SENDENTITY_COORD(mins[1], BASEFL_CHANGED_SIZE)
SENDENTITY_COORD(mins[2], BASEFL_CHANGED_SIZE)
SENDENTITY_COORD(maxs[0], BASEFL_CHANGED_SIZE)
SENDENTITY_COORD(maxs[1], BASEFL_CHANGED_SIZE)
SENDENTITY_COORD(maxs[2], BASEFL_CHANGED_SIZE)
SENDENTITY_BYTE(frame, BASEFL_CHANGED_FRAME)
SENDENTITY_FLOAT(skin, BASEFL_CHANGED_SKIN)
SENDENTITY_FLOAT(effects, BASEFL_CHANGED_EFFECTS)
SENDENTITY_FLOAT(scale, BASEFL_CHANGED_SCALE)
SENDENTITY_COORD(velocity[0], BASEFL_CHANGED_VELOCITY_X)
SENDENTITY_COORD(velocity[1], BASEFL_CHANGED_VELOCITY_Y)
SENDENTITY_COORD(velocity[2], BASEFL_CHANGED_VELOCITY_Z)
return (1);
}
void
NSEntity::EvaluateEntity(void)
{
EVALUATE_VECTOR(origin, 0, BASEFL_CHANGED_ORIGIN_X)
EVALUATE_VECTOR(origin, 1, BASEFL_CHANGED_ORIGIN_Y)
EVALUATE_VECTOR(origin, 2, BASEFL_CHANGED_ORIGIN_Z)
EVALUATE_VECTOR(angles, 0, BASEFL_CHANGED_ANGLES_X)
EVALUATE_VECTOR(angles, 1, BASEFL_CHANGED_ANGLES_Y)
EVALUATE_VECTOR(angles, 2, BASEFL_CHANGED_ANGLES_Z)
EVALUATE_FIELD(modelindex, BASEFL_CHANGED_MODELINDEX)
EVALUATE_FIELD(solid, BASEFL_CHANGED_SOLID)
EVALUATE_FIELD(movetype, BASEFL_CHANGED_FLAGS)
EVALUATE_FIELD(flags, BASEFL_CHANGED_FLAGS)
EVALUATE_VECTOR(mins, 0, BASEFL_CHANGED_SIZE)
EVALUATE_VECTOR(mins, 1, BASEFL_CHANGED_SIZE)
EVALUATE_VECTOR(mins, 2, BASEFL_CHANGED_SIZE)
EVALUATE_VECTOR(maxs, 0, BASEFL_CHANGED_SIZE)
EVALUATE_VECTOR(maxs, 1, BASEFL_CHANGED_SIZE)
EVALUATE_VECTOR(maxs, 2, BASEFL_CHANGED_SIZE)
EVALUATE_FIELD(frame, BASEFL_CHANGED_FRAME)
EVALUATE_FIELD(skin, BASEFL_CHANGED_SKIN)
EVALUATE_FIELD(effects, BASEFL_CHANGED_EFFECTS)
EVALUATE_FIELD(scale, BASEFL_CHANGED_SCALE)
EVALUATE_VECTOR(velocity, 0, BASEFL_CHANGED_VELOCITY_X)
EVALUATE_VECTOR(velocity, 1, BASEFL_CHANGED_VELOCITY_Y)
EVALUATE_VECTOR(velocity, 2, BASEFL_CHANGED_VELOCITY_Z)
}
/* Make sure StartFrame calls this */
void
NSEntity::ParentUpdate(void)
{
EvaluateEntity();
frame1time += frametime;
if (m_parent) {
NSEntity parent;
entity p = find(world, ::targetname, m_parent);
if (p) {
if (!m_parent_attachment) {
parent = (NSEntity)p;
vector ofs = parent.origin - parent.GetSpawnOrigin();
SetOrigin(GetSpawnOrigin() + ofs);
} else if (m_parent_attachment == "origin") {
SetOrigin(p.origin);
}
}
}
/* handle end-touch */
if (m_beingTouched == true)
if (m_flTouchTime < time) {
EndTouch(m_eTouchLast);
m_beingTouched = false;
m_eTouchLast = __NULL__;
}
}
entity
NSEntity::GetParent(void)
{
return find(world, ::targetname, m_parent);
}
void
NSEntity::SetParent(string name)
{
m_parent = name;
}
void
NSEntity::SetParentAttachment(string name)
{
m_parent_attachment = name;
}
void
NSEntity::ClearParent(void)
{
m_parent = __NULL__;
m_parent_attachment = __NULL__;
}
void
NSEntity::RestoreAngles(void)
{
angles = GetSpawnAngles();
}
void
NSEntity::ClearAngles(void)
{
angles = [0,0,0];
}
#endif
void
NSEntity::SetOwner(entity newOwner)
{
owner = newOwner;
}
void
NSEntity::SetVelocity(vector vecNew)
{
velocity = vecNew;
}
void
NSEntity::SetTouch(void() newTouch)
{
touch = newTouch;
}
/* we want to really use those set functions because they'll notify of any
* networking related changes. otherwise we'll have to keep track of copies
* that get updated every frame */
void
NSEntity::SetSendFlags(float flSendFlags)
{
#ifdef SERVER
SendFlags |= flSendFlags;
#endif
}
void
NSEntity::SetMovetype(float newMovetype)
{
movetype = newMovetype;
}
void
NSEntity::SetGravity(float newGrav)
{
gravity = newGrav;
}
void
NSEntity::SetSolid(float newSolid)
{
solid = newSolid;
}
void
NSEntity::SetScale(float newScale)
{
scale = newScale;
}
void
NSEntity::UpdateBounds(void)
{
vector newMins, newMaxs;
float flScale = 1.0f;
newMins = m_vecMins;
newMaxs = m_vecMaxs;
/* avoid useless computation */
if (angles != [0,0,0]) {
/* adjust bbox according to rotation */
vector vecCorner[8];
newMins = newMaxs = [0,0,0];
for (int i = 0; i < 8; i++) {
vecCorner[i][0] = (i & 1) ? m_vecMins[0] : m_vecMaxs[0];
vecCorner[i][1] = (i & 2) ? m_vecMins[1] : m_vecMaxs[1];
vecCorner[i][2] = (i & 4) ? m_vecMins[2] : m_vecMaxs[2];
vecCorner[i] += origin;
vecCorner[i] = Math_RotateAroundPivot(vecCorner[i], origin, angles[1]);
vecCorner[i] -= origin;
if (!(vecCorner[i][0] <= newMaxs[0]))
newMaxs[0] = vecCorner[i][0];
if (!(vecCorner[i][1] <= newMaxs[1]))
newMaxs[1] = vecCorner[i][1];
if (!(vecCorner[i][2] <= newMaxs[2]))
newMaxs[2] = vecCorner[i][2];
if (!(vecCorner[i][0] >= newMins[0]))
newMins[0] = vecCorner[i][0];
if (!(vecCorner[i][1] >= newMins[1]))
newMins[1] = vecCorner[i][1];
if (!(vecCorner[i][2] >= newMins[2]))
newMins[2] = vecCorner[i][2];
}
}
/* 0.0 is never valid, if you want it to disappear do something else */
if (scale != 0.0)
flScale = scale;
setsize(this, newMins * flScale, newMaxs * flScale);
}
void
NSEntity::SetAngles(vector newAngles)
{
angles = newAngles;
}
void
NSEntity::SetAngularVelocity(vector newAvel)
{
avelocity = newAvel;
}
void
NSEntity::SetSize(vector newMins, vector newMaxs)
{
float flScale = 1.0f;
m_vecMins = newMins;
m_vecMaxs = newMaxs;
/* 0.0 is never valid, if you want it to disappear do something else */
if (scale != 0.0f)
flScale = scale;
setsize(this, newMins * flScale, newMaxs * flScale);
}
void
NSEntity::SetOrigin(vector newOrigin)
{
setorigin(this, newOrigin);
}
void
NSEntity::SetModel(string newModel)
{
model = newModel;
setmodel(this, newModel);
/* mins/maxs have been updated by setmodel */
SetSize(mins, maxs);
}
void
NSEntity::SetModelindex(float newModelIndex)
{
if (newModelIndex == modelindex)
return;
modelindex = newModelIndex;
SetSize(mins, maxs);
}
void
NSEntity::AddFlags(float fl)
{
flags |= fl;
}
void
NSEntity::RemoveFlags(float fl)
{
flags &= ~fl;
}
float
NSEntity::GetScale(void)
{
return scale;
}
entity
NSEntity::GetOwner(void)
{
return owner;
}
vector
NSEntity::GetVelocity(void)
{
return velocity;
}
float
NSEntity::GetSolid(void)
{
return solid;
}
string
NSEntity::GetModel(void)
{
return model;
}
float
NSEntity::GetModelindex(void)
{
return modelindex;
}
float
NSEntity::GetMovetype(void)
{
return movetype;
}
float
NSEntity::GetGravity(void)
{
return gravity;
}
vector
NSEntity::GetAngles(void)
{
return angles;
}
vector
NSEntity::GetAngularVelocity(void)
{
return avelocity;
}
vector
NSEntity::GetOrigin(void)
{
return origin;
}
vector
NSEntity::GetMins(void)
{
return mins;
}
vector
NSEntity::GetMaxs(void)
{
return maxs;
}
vector
NSEntity::GetRealMins(void)
{
return m_vecMins;
}
vector
NSEntity::GetRealMaxs(void)
{
return m_vecMaxs;
}
vector
NSEntity::GetAbsoluteMins(void)
{
return absmin;
}
vector
NSEntity::GetAbsoluteMaxs(void)
{
return absmax;
}
float
NSEntity::GetFlags(void)
{
return flags;
}
vector
NSEntity::GetSpawnOrigin(void)
{
return m_oldOrigin;
}
vector
NSEntity::GetSpawnAngles(void)
{
return m_oldAngle;
}
string
NSEntity::GetSpawnModel(void)
{
return m_oldModel;
}
#ifdef SERVER
void
NSEntity::Respawn(void)
{
NSTrigger::Respawn();
SetSolid(m_oldSolid);
SetAngles(GetSpawnAngles());
SetOrigin(GetSpawnOrigin());
SetModel(GetSpawnModel());
target = m_oldstrTarget; /* FIXME: Move into NSTrigger::Respawn */
}
void
NSEntity::Save(float handle)
{
super::Save(handle);
SaveFloat(handle, "pvsflags", pvsflags);
SaveFloat(handle, "_mapspawned", _mapspawned);
SaveFloat(handle, "scale", scale);
SaveVector(handle, "m_vecMins", m_vecMins);
SaveVector(handle, "m_vecMaxs", m_vecMaxs);
SaveVector(handle, "m_oldOrigin", m_oldOrigin);
SaveVector(handle, "m_oldAngle", m_oldAngle);
SaveString(handle, "m_oldModel", m_oldModel);
SaveFloat(handle, "m_oldSolid", m_oldSolid);
SaveFloat(handle, "m_flTouchTime", m_flTouchTime);
SaveBool(handle, "m_beingTouched", m_beingTouched);
SaveEntity(handle, "m_eTouchLast", m_eTouchLast);
SaveString(handle, "m_parent", m_parent);
SaveString(handle, "m_parent_attachment", m_parent_attachment);
}
void
NSEntity::Restore(string strKey, string strValue)
{
switch (strKey) {
case "pvsflags":
pvsflags = stof(strValue);
break;
case "_mapspawned":
_mapspawned = stof(strValue);
break;
case "scale":
scale = ReadFloat(strValue);
break;
case "m_vecMins":
m_vecMins = ReadVector(strValue);
break;
case "m_vecMaxs":
m_vecMaxs = ReadVector(strValue);
break;
case "m_oldOrigin":
m_oldOrigin = ReadVector(strValue);
break;
case "m_oldAngle":
m_oldAngle = ReadVector(strValue);
break;
case "m_oldModel":
m_oldModel = ReadString(strValue);
break;
case "m_oldSolid":
m_oldSolid = ReadFloat(strValue);
break;
case "m_flTouchTime":
m_flTouchTime = ReadFloat(strValue);
break;
case "m_beingTouched":
m_beingTouched = ReadBool(strValue);
break;
case "m_eTouchLast":
m_eTouchLast = ReadEntity(strValue);
break;
case "m_parent":
m_parent = ReadString(strValue);
break;
case "m_parent_attachment":
m_parent_attachment = ReadString(strValue);
break;
default:
super::Restore(strKey, strValue);
break;
}
}
void
NSEntity::RestoreComplete(void)
{
/* this is where we can handle anything post-loading */
}
void
NSEntity::Input(entity eAct, string strInput, string strData)
{
switch (strInput) {
case "Kill":
think = Util_Destroy;
nextthink = time;
break;
case "KillHierarchy":
/* this works because ents are basically just entnums */
for (entity e = world; (e=findfloat(e, ::owner, this));) {
e.think = Util_Destroy;
e.nextthink = time;
}
think = Util_Destroy;
nextthink = time;
break;
case "SetParent":
SetParent(strData);
break;
case "SetParentAttachment":
SetParentAttachment(strData);
break;
case "ClearParent":
ClearParent();
break;
case "Use":
eActivator = eAct;
if (PlayerUse)
PlayerUse();
break;
default:
NSTrigger::Input(eAct, strInput, strData);
}
}
#endif
/*
============
NSEntity::SpawnKey
note that the engine still likes to try and map key/value
pairs on its own, but we can at least undo some of that in
here if needed
============
*/
void
NSEntity::SpawnKey(string strKey, string strValue)
{
/* we do re-read a lot of the builtin fields in case we want to set
defaults. just in case anybody is wondering. */
switch (strKey) {
case "spawnflags":
spawnflags = stof(strValue);
break;
case "origin":
origin = stov(strValue);
break;
case "model":
model = strValue;
break;
case "angles":
angles = stov(strValue);
break;
case "angle":
angles[1] = stof(strValue);
break;
case "solid":
solid = stof(strValue);
break;
#ifdef SERVER
case "health":
health = stof(strValue);
break;
case "parentname":
SetParent(strValue);
break;
case "ignorepvs":
pvsflags = PVSF_IGNOREPVS;
break;
#endif
default:
NSTrigger::SpawnKey(strKey, strValue);
break;
}
}
/*
============
OnRemoveEntity
Empty method, meant for sub-classes to clean up their contents
============
*/
void
NSEntity::OnRemoveEntity(void)
{
}
/*
============
NSEntity::Destroy
Call if the entity is to be removed the next possible frame.
If you want an entity to be purged immediately, you'll have to jump
through your own hoops. This however will be sufficient 99,99% of the time.
============
*/
void
NSEntity::Destroy(void)
{
OnRemoveEntity();
think = Util_Destroy;
if (!time)
nextthink = time + 0.01;
else
nextthink = time;
}