add class NSPortal (initial work, plus custom pmove changes).

Work on NSProjectile expansion has begun.
Support for skill cvar referencing within entityDef values.
This commit is contained in:
Marco Cawthorne 2023-05-27 11:58:34 -07:00
parent 6078d31f12
commit 12bb472abc
Signed by: eukara
GPG Key ID: CE2032F0A2882A22
23 changed files with 1187 additions and 14 deletions

Binary file not shown.

View File

@ -0,0 +1,13 @@
{
"spawnflags" "0"
"classname" "worldspawn"
"wad" "C:\Games\Quake\gmapn\fte.wad"
{
( -8 64 -80 ) ( -8 64 -64 ) ( -8 -0 -80 ) nodraw -64 64 180 1 -1
( -64 64 -80 ) ( -0 64 -80 ) ( -64 64 -64 ) nodraw 0 64 -180 1 -1
( -64 64 -64 ) ( -64 -0 -64 ) ( -0 64 -64 ) nodraw 64 0 90 1 1
( -0 -0 -64 ) ( -0 64 -64 ) ( -0 -0 -80 ) warpzone 64 64 0 1 1
( -0 -64 -64 ) ( -0 -64 -80 ) ( -64 -64 -64 ) nodraw 0 64 -180 1 -1
( -0 -0 64 ) ( -64 -0 64 ) ( -0 64 64 ) nodraw 64 0 90 1 1
}
}

View File

@ -0,0 +1,10 @@
debugcone
{
cull none
{
rgbGen vertex
alphaGen vertex
blendFunc blend
}
}

View File

@ -0,0 +1,15 @@
//used on the front face of the portal entities.
warpzone
{
sort portal
surfaceparm nomarks
surfaceparm nodlight
}
//used on side+back faces, just to hide them.
nodraw
{
surfaceparm nomarks
surfaceparm nodlight
surfaceparm nodraw
}

Binary file not shown.

View File

@ -48,6 +48,9 @@ Entity_EntityUpdate(float type, float new)
case ENT_VEHICLE:
basevehicle_readentity(new);
break;
case ENT_PORTAL:
NSPortal_ReadEntity(new);
break;
case ENT_VEH_BRUSH:
func_vehicle_readentity(new);
break;
@ -142,6 +145,9 @@ Entity_EntityUpdate(float type, float new)
case ENT_PUSH:
trigger_push_ReadEntity(new);
break;
case ENT_ENTITYPROJECTILE:
NSProjectile_ReadEntity(new);
break;
default:
//error(sprintf("Unknown entity type update received. (%d)\n", t));
}

View File

@ -148,3 +148,6 @@ void main(void)
#define SAVE_VECTOR(x,y,z) fputs(x, sprintf("%S \"%v\" ", y, z))
#define SAVE_STRING(x,y,z) fputs(x, sprintf("%S \"%s\" ", y, z))
#define SAVE_HEX(x,y,z) fputs(x, sprintf("%S \"%x\" ", y, z))
NSEntity EntityDef_SpawnClassname(string className);
NSEntity EntityDef_CreateClassname(string className);

View File

@ -103,7 +103,7 @@ EntityDef_ReadFile(string filePath)
/* we've reached the end of a definition */
if (braceDepth == 0) {
/* we have something somewhat valid I guess */
if (currentDef.entClass != "" && currentDef.spawnClass != "") {
if (currentDef.entClass != "" /*&& currentDef.spawnClass != ""*/) {
g_entDefTable[g_entDefCount].entClass = currentDef.entClass;
g_entDefTable[g_entDefCount].spawnClass = currentDef.spawnClass;
g_entDefTable[g_entDefCount].spawnData = currentDef.spawnData;
@ -184,7 +184,7 @@ EntityDef_PrepareEntity(entity target, int id)
/* check if the spawnclass is an entityDef */
for (int i = 0i; i < g_entDefCount; i++) {
if (g_entDefTable[id].spawnClass == g_entDefTable[i].entClass) {
EntityDef_PrepareEntity(self, i);
EntityDef_PrepareEntity(target, i);
isEntDefBased = true;
break;
}
@ -233,7 +233,7 @@ EntityDef_PrepareEntity(entity target, int id)
targetEnt.Respawn();
/* now we rename the classname for better visibility */
self.classname = g_entDefTable[id].entClass;
targetEnt.classname = g_entDefTable[id].entClass;
__fullspawndata = "";
return targetEnt;
}
@ -248,4 +248,56 @@ EntityDef_SpawnClassname(string className)
}
return __NULL__;
}
NSEntity
EntityDef_CreateClassname(string className)
{
entity oldSelf = self;
NSEntity new = spawn(NSEntity);
self = new;
EntityDef_SpawnClassname(className);
self = oldSelf;
return new;
}
string
EntityDef_GetKeyValue(string className, string keyName)
{
float spawnWords = 0;
string inheritKeys = __NULL__;
/* loop through all defs */
for (int i = 0i; i < g_entDefCount; i++) {
/* found the right def */
//print(sprintf("%S %S\n", className, g_entDefTable[i].entClass));
if (className == g_entDefTable[i].entClass) {
inheritKeys = g_entDefTable[i].inheritKeys;
spawnWords = tokenize_console(g_entDefTable[i].spawnData);
/* iterate over our own spawnkeys first */
for (int c = 0i; c < spawnWords; c+= 2i) {
if (argv(c) == keyName) {
return argv(c+1);
}
}
/* not found, look into the inherit keyword's spawndata */
if (inheritKeys == __NULL__)
return "";
for (int b = 0i; b < g_entDefCount; i++) {
if (inheritKeys == g_entDefTable[b].entClass) {
spawnWords = tokenize_console(g_entDefTable[b].spawnData);
for (int c = 0i; c < spawnWords; c+= 2i) {
if (argv(c) == keyName) {
return argv(c+1);
}
}
}
}
}
}
return "";
}

View File

@ -775,10 +775,10 @@ to remove in case we won't initialize it.
void
CheckSpawn(void() spawnfunc)
{
if (EntityDef_SpawnClassname(self.classname))
return;
if (MapTweak_EntitySpawn(self))
return;
if (EntityDef_SpawnClassname(self.classname))
return;
if (spawnfunc) {
spawnfunc();

View File

@ -47,3 +47,14 @@ Skill_GetValue(string variable, float defaultvalue)
float val = fabs(cvar(sprintf("sk_%s%d", variable, skill)));
return (val == 0) ? defaultvalue : val;
}
/* input string is potentially a skill variable */
float
Skill_GetDefValue(string variable)
{
if (substring(variable, 0, 6) == "skill:") {
return Skill_GetValue(substring(variable, 6, -1), 0);
}
return stof(variable);
}

View File

@ -14,6 +14,8 @@
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
noref .vector v_angle;
/** This entity class represents every player client.
When clients connect via the connect command, they will findthemselves
@ -116,7 +118,7 @@ private:
PREDICTED_FLOAT(viewzoom)
PREDICTED_VECTOR_N(view_ofs)
PREDICTED_VECTOR_N(basevelocity)
PREDICTED_VECTOR(v_angle)
PREDICTED_VECTOR_N(v_angle)
PREDICTED_FLOAT_N(pmove_flags)
PREDICTED_FLOAT(w_attack_next)

View File

@ -27,9 +27,14 @@ public:
virtual void Respawn(void);
virtual void SpawnKey(string, string);
virtual void SetItem(int i);
virtual void SetFloating(int);
virtual void PickupRespawn(void);
nonvirtual void SetItem(int i);
nonvirtual int GetItem(void);
nonvirtual void SetFloating(int);
nonvirtual bool GetFloating(void);
nonvirtual void SetSpinning(bool);
nonvirtual bool GetSpinning(void);
nonvirtual void PickupRespawn(void);
private:
int m_iClip;

View File

@ -17,7 +17,7 @@
#ifdef SERVER
void NSItem::NSItem(void)
{
m_iClip = 0i;
m_iClip = -1;
m_iWasDropped = 0i;
m_iInvItem = 0i;
m_sndAcquire = __NULL__;
@ -122,11 +122,36 @@ void NSItem::SetItem(int i)
SetSize([-16,-16,0], [16,16,16]);
}
int
NSItem::GetItem(void)
{
return m_iInvItem;
}
void NSItem::SetFloating(int i)
{
m_bFloating = i ? true : false;
}
bool
NSItem::GetFloating(void)
{
return m_bFloating;
}
void
NSItem::SetSpinning(bool value)
{
m_bSpins = value;
}
bool
NSItem::GetSpinning(void)
{
return m_bSpins;
}
void
NSItem::PickupRespawn(void)
{

View File

@ -14,6 +14,10 @@
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef MAX_AMMO_TYPES
#define MAX_AMMO_TYPES 16
#endif
/** This entity class represents a moving/pathfinding object.
It knows how to deal with waypoint based nodes and possibly other
types of pathfinding in the future.
@ -29,6 +33,9 @@ private:
nodeslist_t *m_pRoute;
vector m_vecLastNode;
vector m_vecTurnAngle;
/* These are defined in side defs\*.def, ammo_types and ammo_names */
int m_iAmmoTypes[MAX_AMMO_TYPES];
#endif
public:

View File

@ -23,6 +23,9 @@ NSNavAI::NSNavAI(void)
m_pRoute = __NULL__;
m_vecLastNode = [0,0,0];
m_vecTurnAngle = [0,0,0];
for (int i = 0; i < MAX_AMMO_TYPES; i++)
m_iAmmoTypes[i] = 0;
#endif
}

96
src/shared/NSPortal.h Normal file
View File

@ -0,0 +1,96 @@
/*
* 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.
*/
typedef enumflags
{
PORTALFL_CHANGED_ORIGIN_X,
PORTALFL_CHANGED_ORIGIN_Y,
PORTALFL_CHANGED_ORIGIN_Z,
PORTALFL_CHANGED_ANGLES_X,
PORTALFL_CHANGED_ANGLES_Y,
PORTALFL_CHANGED_ANGLES_Z,
PORTALFL_CHANGED_TARG_ENTITY,
PORTALFL_CHANGED_TARG_ORIGIN_X,
PORTALFL_CHANGED_TARG_ORIGIN_Y,
PORTALFL_CHANGED_TARG_ORIGIN_Z,
PORTALFL_CHANGED_TARG_ANGLES_X,
PORTALFL_CHANGED_TARG_ANGLES_Y,
PORTALFL_CHANGED_TARG_ANGLES_Z
} nsportal_changed_t;
class
NSPortal:NSEntity
{
void NSPortal(void);
nonvirtual void _PortalUpdated(void);
nonvirtual vector _OriginTransform(vector);
nonvirtual vector _DirectionTransform(vector);
/** overrides */
virtual vector camera_transform(vector, vector);
#ifdef SERVER
virtual void EvaluateEntity(void);
virtual float SendEntity(entity, float);
/** Sets the NSPortal ID. Only used to link portals together via PortalAutoLink. */
nonvirtual void SetPortalID(int);
/** Will link this portal to another NSPortal. */
nonvirtual bool PortalLinkTo(NSPortal);
/** Will link this portal to the youngest other NSPortal. */
nonvirtual void PortalAutoLink(void);
#endif
#ifdef CLIENT
virtual void ReceiveEntity(float, float);
#endif
private:
NSPortal m_ePortalTarget;
NSPortal m_ePortalTarget_net;
PREDICTED_VECTOR(m_vecTargetPos)
PREDICTED_VECTOR(m_vecTargetAngle)
vector m_vecPortalPos;
vector m_vecPortalN;
vector m_vecPortalS;
vector m_vecPortalT;
/** Will transport an entity from its position to the exit position. */
nonvirtual void TransportEntity(NSEntity);
#ifdef CLIENT
vector m_vecTargetPos;
vector m_vecTargetN;
vector m_vecTargetS;
vector m_vecTargetT;
#endif
#ifdef SERVER
int m_iPortalValue;
#endif
int m_brushNum;
};
#ifdef CLIENT
void NSPortal_ReadEntity(bool);
#endif
#define SOLID_PORTAL 21
.float portalnum;
.float impulse; //used as the radius for the solid_portal csg subtraction

412
src/shared/NSPortal.qc Normal file
View File

@ -0,0 +1,412 @@
/*
* Copyright (c) 2023 Vera Visions LLC.
* Copyright (c) 2014 David Walton
*
* 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.
*/
#define PORTAL_MINS [8, -64, -64]
#define PORTAL_MAXS [0, 64, 64]
void
NSPortal::NSPortal(void)
{
m_ePortalTarget = __NULL__;
m_ePortalTarget_net = __NULL__;
m_vecTargetPos = g_vec_null;
m_vecTargetAngle = g_vec_null;
m_vecPortalPos = g_vec_null;
m_vecPortalN = g_vec_null;
m_vecPortalS = g_vec_null;
m_vecPortalT = g_vec_null;
m_brushNum = -1;
#ifdef CLIENT
m_vecTargetPos = g_vec_null;
m_vecTargetN = g_vec_null;
m_vecTargetS = g_vec_null;
m_vecTargetT = g_vec_null;
#endif
#ifdef SERVER
pvsflags = PVSF_NOREMOVE;
frame1time = 0.0f;
m_iPortalValue = -1;
/* unique portal IDs */
{
float numPortals = 0;
for (NSPortal e = __NULL__; (e = (NSPortal)find(e, ::classname, "NSPortal"));) {
e.portalnum = numPortals++;
}
}
#endif
}
#ifdef SERVER
void
NSPortal::PortalAutoLink(void)
{
/* this just links against the youngest portal of the same ID at this time */
NSPortal youngestPortal, oldestPortal;
oldestPortal = youngestPortal = __NULL__;
float youngestTime = 99999.0f;
float oldestTime = 0.0f;
/* delete any portal with the same id */
for (NSPortal e = __NULL__; (e = (NSPortal)find(e, ::classname, "NSPortal"));) {
if (e == this)
continue;
/* find first AND last portal */
if (e.m_iPortalValue == m_iPortalValue) {
if (oldestTime < e.frame1time) {
oldestTime = e.frame1time;
oldestPortal = e;
}
if (youngestTime > e.frame1time) {
youngestTime = e.frame1time;
youngestPortal = e;
}
}
}
/* remove the first portal from the world */
if (youngestPortal != oldestPortal) {
oldestPortal.Destroy();
}
PortalLinkTo(youngestPortal);
}
bool
NSPortal::PortalLinkTo(NSPortal target)
{
m_ePortalTarget = target;
skin = m_ePortalTarget ? 0 : 1;
SendFlags |= 1;
_PortalUpdated();
if (m_ePortalTarget) {
m_ePortalTarget.m_ePortalTarget = this;
m_ePortalTarget.skin = skin;
m_ePortalTarget.SendFlags |= 1;
m_ePortalTarget._PortalUpdated();
}
return target ? true : false;
}
void
NSPortal::SetPortalID(int value)
{
m_iPortalValue = value;
}
#endif
void
NSPortal::_PortalUpdated(void)
{
/* the displayed surface must be in a known position.
we're not going to compensate for the model here, because I'm too lazy. */
vector newAngles = angles;
//newAngles[0] * -1;
makevectors(newAngles);
m_vecPortalN = v_forward;
m_vecPortalS = -v_right;
m_vecPortalT = v_up;
m_vecPortalPos = origin;
/* expand the size of the object along the plane, and set up a portal region. */
movetype = MOVETYPE_NONE;
solid = skin ? SOLID_BSP : SOLID_PORTAL;
precache_model("models/b_portal.bsp");
setmodel(this, "models/b_portal.bsp");
/* determine size of major axis */
float portalSize = max(size[0], size[1], size[2]);
impulse = portalSize; /* let the engine know how wide the portal should be */
/* make sure the abs size contains the entire portal. */
portalSize = sqrt(portalSize * portalSize * 2);
mins -= portalSize * [1, 1, 1];
maxs += portalSize * [1, 1, 1];
setsize(this, mins, maxs);
}
vector
NSPortal::_DirectionTransform(vector v)
{
/* FIXME: this should include .angles stuff */
vector tmp, r;
tmp[0] = v * m_vecPortalN;
tmp[1] = v * m_vecPortalS;
tmp[2] = v * m_vecPortalT;
r = [0, 0, 0];
if (!m_ePortalTarget) {
#ifdef CSQC
r += tmp[2] * m_vecTargetT;
r -= tmp[1] * m_vecTargetS;
r -= tmp[0] * m_vecTargetN;
#else
r += tmp[2] * this.m_vecPortalT;
r -= tmp[1] * this.m_vecPortalS;
r -= tmp[0] * this.m_vecPortalN;
#endif
} else {
r += tmp[2] * m_ePortalTarget.m_vecPortalT;
r -= tmp[1] * m_ePortalTarget.m_vecPortalS;
r -= tmp[0] * m_ePortalTarget.m_vecPortalN;
}
return r;
}
vector
NSPortal::_OriginTransform(vector p)
{
if (!m_ePortalTarget) {
#ifdef CSQC
return m_vecTargetPos - _DirectionTransform(m_vecPortalPos - p);
#else
return this.m_vecPortalPos - _DirectionTransform(m_vecPortalPos - p);
#endif
}
return m_ePortalTarget.m_vecPortalPos - _DirectionTransform(m_vecPortalPos - p);
}
// need to generate forward/right/up vectors
// return value is the new view origin.
// trace_end_pos needs to contain the pvs origin.
vector
NSPortal::camera_transform(vector originalOrg, vector originalAngles)
{
vector newCameraPos;
newCameraPos = _OriginTransform(originalOrg);
v_forward = _DirectionTransform(v_forward);
v_right = _DirectionTransform(v_right);
v_up = _DirectionTransform(v_up);
//trace from the center of the target to the view, to set trace_endpos for the pvs origin
if (m_ePortalTarget)
traceline(m_ePortalTarget.m_vecPortalPos, newCameraPos, MOVE_NOMONSTERS, this);
else
trace_endpos = this.m_vecPortalPos;
return newCameraPos;
}
/* because when using custom player physics, the engine will not do us any favors
we have to do the portal transformation manually. DO NOT USE THIS METHOD YOURSELF
unless you really know what you're doing. This is a private method! */
void
NSPortal::TransportEntity(NSEntity target)
{
static void getMatrixDiff(vector aX, vector aY, vector aZ, vector bX, vector bY, vector bZ) {
v_forward.x = aX.x * bX.x + aY.x * bX.y + aZ.x * bX.z;
v_forward.y = aX.y * bX.x + aY.y * bX.y + aZ.y * bX.z;
v_forward.z = aX.z * bX.x + aY.z * bX.y + aZ.z * bX.z;
v_right.x = aX.x * bY.x + aY.x * bY.y + aZ.x * bY.z;
v_right.y = aX.y * bY.x + aY.y * bY.y + aZ.y * bY.z;
v_right.z = aX.z * bY.x + aY.z * bY.y + aZ.z * bY.z;
v_up.x = aX.x * bZ.x + aY.x * bZ.y + aZ.x * bZ.z;
v_up.y = aX.y * bZ.x + aY.y * bZ.y + aZ.y * bZ.z;
v_up.z = aX.z * bZ.x + aY.z * bZ.y + aZ.z * bZ.z;
}
vector offsetPos;
vector portalAngleX, portalAngleY, portalAngleZ;
vector exitAngleX, exitAngleY, exitAngleZ;
vector playerDelta;
vector targetAngleX, targetAngleY, targetAngleZ;
vector playerVelX, playerVelY, playerVelZ;
vector angleDiff;
float totalSpeed;
/* angle matrix of the portal entry */
makevectors(GetAngles());
portalAngleX = v_forward * -1;
portalAngleY = v_right * -1;
portalAngleZ = v_up;
/* matrix of target entity angles */
if (target.flags & FL_CLIENT) {
makevectors(input_angles);
} else {
makevectors(target.GetAngles());
}
targetAngleX = v_forward;
targetAngleY = v_right;
targetAngleZ = v_up;
/* angle matrix of portal exit */
makevectors(m_ePortalTarget.GetAngles());
exitAngleX = v_forward;
exitAngleY = v_right;
exitAngleZ = v_up;
/* ! CALCULATE THE NEW POSITION - START ! */
/* get the offset */
offsetPos = target.GetOrigin() - GetOrigin();
/* calculate the delta of player pos to portal using said angle matrix */
playerDelta[0] = dotproduct(offsetPos, portalAngleX);
playerDelta[1] = dotproduct(offsetPos, portalAngleY);
playerDelta[2] = dotproduct(offsetPos, portalAngleZ);
/* now, translate the offset to the exit angle */
offsetPos = m_ePortalTarget.GetOrigin();
offsetPos += exitAngleX * playerDelta[0];
offsetPos += exitAngleY * playerDelta[1];
offsetPos += exitAngleZ * playerDelta[2];
target.SetOrigin(offsetPos); /* apply the new position */
/* ! CALCULATE THE NEW ANGLE - START ! */
/* get the difference between the two rotational matrices */
getMatrixDiff(portalAngleX, portalAngleY, portalAngleZ * -1, targetAngleX, targetAngleY, targetAngleZ);
angleDiff = vectoangles(v_forward, -v_up); /* we now have an euler angle */
/* apply the new angles */
if (target.flags & FL_CLIENT) {
input_angles = m_ePortalTarget.GetAngles() - angleDiff;
target.v_angle = input_angles;
//Client_FixAngle(this, target.v_angle);
} else {
target.angles = m_ePortalTarget.GetAngles() - angleDiff;
}
/* ! CALCULATE DESTINATION VELOCITY - START ! */
/* let's get the current speed first */
totalSpeed = vlen(target.GetVelocity());
/* convert target velocity into a direction matrix */
makevectors(vectoangles(target.GetVelocity()));
playerVelX = v_forward;
playerVelY = v_right;
playerVelZ = v_up;
getMatrixDiff(portalAngleX, portalAngleY, portalAngleZ, playerVelX, playerVelY, playerVelZ);
angleDiff = vectoangles(v_forward, v_up); /* we now have an euler angle again */
/* get the new movement direction relative to the exit portal */
makevectors(m_ePortalTarget.GetAngles() + angleDiff);
/* apply the final velocity */
target.SetVelocity(v_forward * totalSpeed);
}
#ifdef SERVER
void
NSPortal::EvaluateEntity(void)
{
EVALUATE_VECTOR(origin, 0, PORTALFL_CHANGED_ORIGIN_X)
EVALUATE_VECTOR(origin, 1, PORTALFL_CHANGED_ORIGIN_Y)
EVALUATE_VECTOR(origin, 2, PORTALFL_CHANGED_ORIGIN_Z)
EVALUATE_VECTOR(angles, 0, PORTALFL_CHANGED_ANGLES_X)
EVALUATE_VECTOR(angles, 1, PORTALFL_CHANGED_ANGLES_Y)
EVALUATE_VECTOR(angles, 2, PORTALFL_CHANGED_ANGLES_Z)
EVALUATE_FIELD(m_ePortalTarget, PORTALFL_CHANGED_TARG_ENTITY)
EVALUATE_VECTOR(m_vecTargetPos, 0, PORTALFL_CHANGED_TARG_ORIGIN_X)
EVALUATE_VECTOR(m_vecTargetPos, 1, PORTALFL_CHANGED_TARG_ORIGIN_Y)
EVALUATE_VECTOR(m_vecTargetPos, 2, PORTALFL_CHANGED_TARG_ORIGIN_Z)
EVALUATE_VECTOR(m_vecTargetAngle, 0, PORTALFL_CHANGED_TARG_ANGLES_X)
EVALUATE_VECTOR(m_vecTargetAngle, 1, PORTALFL_CHANGED_TARG_ANGLES_Y)
EVALUATE_VECTOR(m_vecTargetAngle, 2, PORTALFL_CHANGED_TARG_ANGLES_Z)
}
float
NSPortal::SendEntity(entity ePEnt, float flChanged)
{
WriteByte(MSG_ENTITY, ENT_PORTAL);
WriteFloat(MSG_ENTITY, flChanged);
SENDENTITY_COORD(origin[0], PORTALFL_CHANGED_ORIGIN_X)
SENDENTITY_COORD(origin[1], PORTALFL_CHANGED_ORIGIN_Y)
SENDENTITY_COORD(origin[2], PORTALFL_CHANGED_ORIGIN_Z)
SENDENTITY_FLOAT(angles[0], PORTALFL_CHANGED_ANGLES_X)
SENDENTITY_FLOAT(angles[1], PORTALFL_CHANGED_ANGLES_Y)
SENDENTITY_FLOAT(angles[2], PORTALFL_CHANGED_ANGLES_Z)
SENDENTITY_ENTITY(m_ePortalTarget, PORTALFL_CHANGED_TARG_ENTITY)
if (1) {
SENDENTITY_COORD(m_vecTargetPos[0], PORTALFL_CHANGED_TARG_ORIGIN_X)
SENDENTITY_COORD(m_vecTargetPos[1], PORTALFL_CHANGED_TARG_ORIGIN_Y)
SENDENTITY_COORD(m_vecTargetPos[2], PORTALFL_CHANGED_TARG_ORIGIN_Z)
SENDENTITY_FLOAT(m_vecTargetAngle[0], PORTALFL_CHANGED_TARG_ANGLES_X)
SENDENTITY_FLOAT(m_vecTargetAngle[1], PORTALFL_CHANGED_TARG_ANGLES_Y)
SENDENTITY_FLOAT(m_vecTargetAngle[2], PORTALFL_CHANGED_TARG_ANGLES_Z)
}
return (true);
}
#endif
#ifdef CLIENT
void
NSPortal::ReceiveEntity(float flNew, float flChanged)
{
if (m_ePortalTarget) {
m_ePortalTarget.m_ePortalTarget = world;
m_ePortalTarget = world;
}
READENTITY_COORD(origin[0], PORTALFL_CHANGED_ORIGIN_X)
READENTITY_COORD(origin[1], PORTALFL_CHANGED_ORIGIN_Y)
READENTITY_COORD(origin[2], PORTALFL_CHANGED_ORIGIN_Z)
READENTITY_FLOAT(angles[0], PORTALFL_CHANGED_ANGLES_X)
READENTITY_FLOAT(angles[1], PORTALFL_CHANGED_ANGLES_Y)
READENTITY_FLOAT(angles[2], PORTALFL_CHANGED_ANGLES_Z)
READENTITY_ENTITY(m_ePortalTarget, PORTALFL_CHANGED_TARG_ENTITY)
READENTITY_COORD(m_vecTargetPos[0], PORTALFL_CHANGED_TARG_ORIGIN_X)
READENTITY_COORD(m_vecTargetPos[1], PORTALFL_CHANGED_TARG_ORIGIN_Y)
READENTITY_COORD(m_vecTargetPos[2], PORTALFL_CHANGED_TARG_ORIGIN_Z)
READENTITY_FLOAT(m_vecTargetAngle[0], PORTALFL_CHANGED_TARG_ANGLES_X)
READENTITY_FLOAT(m_vecTargetAngle[1], PORTALFL_CHANGED_TARG_ANGLES_Y)
READENTITY_FLOAT(m_vecTargetAngle[2], PORTALFL_CHANGED_TARG_ANGLES_Z)
if (m_ePortalTarget) {
if (m_ePortalTarget.m_ePortalTarget)
m_ePortalTarget.m_ePortalTarget.m_ePortalTarget = world;
m_ePortalTarget. m_ePortalTarget = this;
} else {
skin = 1;
makevectors(angles);
m_vecTargetPos = origin;
m_vecTargetN = v_forward;
m_vecTargetS = v_right;
m_vecTargetT = v_up;
}
setorigin(this, origin);
_PortalUpdated();
drawmask = MASK_ENGINE;
predraw = 0;
}
#endif
#ifdef CLIENT
void
NSPortal_ReadEntity(bool new)
{
NSPortal me = (NSPortal)self;
if (new) {
spawnfunc_NSPortal();
}
me.ReceiveEntity(new, readfloat());
}
#endif

View File

@ -14,23 +14,98 @@
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
typedef enumflags
{
PROJ_CHANGED_ORIGIN_X,
PROJ_CHANGED_ORIGIN_Y,
PROJ_CHANGED_ORIGIN_Z,
PROJ_CHANGED_ANGLES_X,
PROJ_CHANGED_ANGLES_Y,
PROJ_CHANGED_ANGLES_Z,
PROJ_CHANGED_MODELINDEX,
PROJ_CHANGED_SIZE,
PROJ_CHANGED_FRAME,
PROJ_CHANGED_SKIN,
PROJ_CHANGED_EFFECTS,
PROJ_CHANGED_BODY,
PROJ_CHANGED_SCALE,
PROJ_CHANGED_VELOCITY,
PROJ_CHANGED_ANGULARVELOCITY,
PROJ_CHANGED_RENDERCOLOR,
PROJ_CHANGED_RENDERAMT,
PROJ_CHANGED_RENDERMODE,
} nsprojectile_changed_t;
.void(entity, entity) m_pImpact;
.float traileffectnum;
/** This entity class represents an interactive projectile.
Objects such as rockets, grenades, bolts etc. should ideally be this. */
class NSProjectile:NSSurfacePropEntity
{
private:
PREDICTED_FLOAT_N(traileffectnum)
#ifdef SERVER
/* sprite animation gubbins */
int m_iProjectileAnimEnd;
int m_iProjectileAnimStart;
float m_flProjectileFramerate;
virtual void m_pImpact(entity, entity) = 0;
/* temp */
float m_flDmgMultiplier;
/* defAPI */
string m_defDamage;
string m_defSplashDamage;
vector m_vecLaunchVelocity;
float m_flThrust; /* TODO */
float m_flThrustStart; /* TODO */
float m_flThrustEnd; /* TODO */
float m_flFrictionLinear; /* TODO */
float m_flBounce;
float m_flMass; /* TODO */
float m_flGravity; /* TODO */
float m_flFuse;
bool m_bDetonateOnFuse;
bool m_bDetonateOnDeath;
bool m_bDetonateOnWorld;
bool m_bDetonateOnActor;
bool m_bImpactEffect; /* TODO */
bool m_bImpactGib; /* TODO */
string m_matDetonate; /* TODO */
float m_flDecalSize; /* TODO */
string m_partSmokeFly;
string m_partModelDetonate;
string m_partSmokeDetonate;
string m_partSmokeBounce;
string m_partSmokeFuse;
int m_iDebrisCount; /* TODO */
vector m_vecLightColor; /* TODO */
float m_flLightRadius; /* TODO */
float m_flLightOffset; /* TODO */
vector m_vecExplodeLightColor; /* TODO */
float m_fExplodelLightRadius; /* TODO */
float m_fExplodelLightFadetime; /* TODO */
string m_sndFly;
string m_sndExplode;
string m_sndBounce;
/* ETQW-additions */
bool m_bIsBullet;
nonvirtual void _AnimateThink(void);
nonvirtual void _AnimateThinkDead(void);
#endif
public:
void NSProjectile(void);
#ifdef CLIENT
virtual void ReceiveEntity(float, float);
#endif
#ifdef SERVER
/** Sets the function that'll be called upon impact of the projectile onto a surface. */
nonvirtual void SetImpact(void(entity, entity));
/** When called, will animated between two frame positions at a specified framerate on loop. */
@ -41,4 +116,24 @@ public:
/** Called upon the projectile touching another object. */
virtual void Touch(entity);
virtual void Spawned(void);
virtual void Death(void);
virtual void Pain(void);
virtual void SpawnKey(string, string);
virtual void EvaluateEntity(void);
virtual float SendEntity(entity, float);
nonvirtual void _FuseEnded(void);
nonvirtual void _Explode(void);
nonvirtual void _LaunchHitscan(vector, vector, float);
/* launch the projectile into the world */
nonvirtual void Launch(vector, vector, float, float, float);
#endif
};
#ifdef CLIENT
void NSProjectile_ReadEntity(bool);
#endif

View File

@ -17,9 +17,187 @@
void
NSProjectile::NSProjectile(void)
{
#ifdef SERVER
m_iProjectileAnimEnd = 0i;
m_iProjectileAnimStart = 0i;
m_flProjectileFramerate = 0.1f; /* default to 10 hz */
/* defAPI */
m_defDamage = __NULL__;
m_defSplashDamage = __NULL__;
m_vecLaunchVelocity = g_vec_null;
m_flThrust = 0.0f;
m_flThrustStart = 0.0f;
m_flThrustEnd = 0.0f;
m_flFrictionLinear = 0.0f;
m_flBounce = 0.0f;
m_flMass = 0.0f;
m_flGravity = 0.0f;
m_flFuse = 0.0f;
m_bDetonateOnFuse = 0.0f;
m_bDetonateOnDeath = 0.0f;
m_bDetonateOnWorld = 0.0f;
m_bDetonateOnActor = 0.0f;
m_bImpactEffect = false;
m_bImpactGib = false;
m_matDetonate = __NULL__;
m_flDecalSize = 0.0f;
m_partSmokeFly = __NULL__;
m_partModelDetonate = __NULL__;
m_partSmokeDetonate = __NULL__;
m_partSmokeBounce = __NULL__;
m_partSmokeFuse = __NULL__;
m_iDebrisCount = 0i;
m_vecLightColor = g_vec_null;
m_flLightRadius = 0.0f;
m_flLightOffset = 0.0f;
m_vecExplodeLightColor = g_vec_null;
m_fExplodelLightRadius = 0.0f;
m_fExplodelLightFadetime = 0.0f;
m_sndFly = __NULL__;
m_sndExplode = __NULL__;
m_sndBounce = __NULL__;
/* ETQW-additions */
m_bIsBullet = false;
#endif
}
#ifdef SERVER
void
NSProjectile::SpawnKey(string strKey, string strValue)
{
switch (strKey) {
case "def_damage": /* direct damage, upon touch */
m_defDamage = ReadString(strValue);
break;
case "def_splash_damage": /* direct damage, upon touch */
m_defSplashDamage = ReadString(strValue);
break;
case "velocity":
m_vecLaunchVelocity = ReadVector(strValue);
break;
case "health": /* amount of dmg the projectile can take */
health = ReadFloat(strValue);
break;
case "model":
model = ReadString(strValue);
m_oldModel = model;
break;
case "velocity":
velocity = ReadVector(strValue);
break;
case "angular_velocity": /* sets avelocity */
avelocity = ReadVector(strValue);
break;
case "thrust": /* rate of acceeleration */
m_flThrust = ReadFloat(strValue);
break;
case "thrust_start": /* when to begin accel */
m_flThrustStart = ReadFloat(strValue);
break;
case "thrust_end": /* when to stop accel */
m_flThrustEnd = ReadFloat(strValue);
break;
case "linear_friction": /* air friction */
m_flFrictionLinear = ReadFloat(strValue);
break;
case "bounce": /* bounce multiplier */
m_flBounce = ReadFloat(strValue);
break;
case "mass":
m_flMass = ReadFloat(strValue);
break;
case "gravity": /* 0 means no gravity */
m_flGravity = ReadFloat(strValue);
break;
case "fuse": /* fuse time in seconds */
m_flFuse = ReadFloat(strValue);
break;
case "detonate_on_fuse": /* should it detonate when fuse runs out? */
m_bDetonateOnFuse = ReadBool(strValue);
break;
case "detonate_on_death": /* should it detonate when it gets damaged enough? */
m_bDetonateOnDeath = ReadBool(strValue);
break;
case "detonate_on_world": /* should it detonate when touching world? */
m_bDetonateOnWorld = ReadBool(strValue);
break;
case "detonate_on_actor": /* should it detonate when touching a monster/player */
m_bDetonateOnActor = ReadBool(strValue);
break;
case "impact_damage_effect": /* blood splats? */
m_bImpactEffect = ReadBool(strValue);
break;
case "impact_gib": /* gibs */
m_bImpactGib = ReadBool(strValue);
break;
case "mtr_detonate":
m_matDetonate = ReadString(strValue);
break;
case "decal_size":
m_flDecalSize = ReadFloat(strValue);
break;
case "smoke_fly":
m_partSmokeFly = ReadString(strValue);
break;
case "model_detonate":
m_partModelDetonate = ReadString(strValue);
break;
case "smoke_detonate":
m_partSmokeDetonate = ReadString(strValue);
break;
case "smoke_bounce":
m_partSmokeBounce = ReadString(strValue);
break;
case "smoke_fuse":
m_partSmokeFuse = ReadString(strValue);
break;
case "debris_count":
m_iDebrisCount = ReadInt(strValue);
break;
/*case "def_debris":
break;
case "def_shrapnel":
break;
case "mtr_light_shader":
break;*/
case "light_color":
m_vecLightColor = ReadVector(strValue);
break;
case "light_radius":
m_flLightRadius = ReadFloat(strValue);
break;
case "light_offset":
m_flLightOffset = ReadFloat(strValue);
break;
/*case "mtr_explode_light_shader":
break;*/
case "explode_light_color":
m_vecExplodeLightColor = ReadVector(strValue);
break;
case "explode_light_radus":
m_fExplodelLightRadius = ReadFloat(strValue);
break;
case "explode_light_fadetime":
m_fExplodelLightFadetime = ReadFloat(strValue);
break;
case "snd_fly":
m_sndFly = ReadString(strValue);
break;
case "snd_explode":
m_sndExplode = ReadString(strValue);
break;
case "snd_bounce":
m_sndBounce = ReadString(strValue);
break;
/* ETQW-additions */
case "is_bullet":
m_bIsBullet = ReadBool(strValue);
break;
default:
super::SpawnKey(strKey, strValue);
}
}
void
@ -39,9 +217,53 @@ NSProjectile::Spawned(void)
void
NSProjectile::Touch(entity eToucher)
{
if (m_pImpact)
m_pImpact(eToucher, this);
if (m_defDamage)
if (eToucher.takedamage != DAMAGE_NO) {
float damageVal;
float baseDamage = Skill_GetDefValue(EntityDef_GetKeyValue(m_defDamage, "damage"));
float randomDamage = Skill_GetDefValue(EntityDef_GetKeyValue(m_defDamage, "damage_random"));
damageVal = (baseDamage + randomDamage);
if (m_flDmgMultiplier >= 0.0)
damageVal *= m_flDmgMultiplier;
Damage_Apply(eToucher, owner, damageVal, WEAPON_NONE, DMG_BLUNT);
}
if (m_bDetonateOnWorld) {
if (eToucher == world || eToucher.takedamage == DAMAGE_NO) {
_Explode();
return;
}
}
if (m_bDetonateOnActor) {
if (eToucher.takedamage != DAMAGE_NO) {
_Explode();
return;
}
}
if (m_partSmokeBounce)
pointparticles(particleeffectnum(m_partSmokeBounce), origin, velocity, 1);
StartSoundDef(m_sndBounce, CHAN_BODY, true);
}
void
NSProjectile::Pain(void)
{
/* do nothing. */
}
void
NSProjectile::Death(void)
{
if (m_bDetonateOnDeath) {
_Explode();
return;
}
/* whatever else comes otherwise? */
Destroy();
}
@ -98,4 +320,187 @@ NSProjectile::AnimateOnce(int startframe, int endframe, float framerate)
frame = startframe;
think = _AnimateThinkDead;
nextthink = time + m_flProjectileFramerate;
}
}
void
NSProjectile::_FuseEnded(void)
{
if (m_bDetonateOnFuse) {
_Explode();
return;
}
if (m_partSmokeFuse)
pointparticles(particleeffectnum(m_partSmokeFuse), origin, velocity, 1);
Destroy();
}
void
NSProjectile::_Explode(void)
{
makevectors(vectoangles(velocity));
vector explodePos = origin - (v_forward * 32);
if (m_partModelDetonate)
pointparticles(particleeffectnum(m_partModelDetonate), explodePos, velocity, 1);
//print(sprintf("%S\n", m_defSplashDamage));
if (m_defSplashDamage) {
float flDamage = Skill_GetDefValue(EntityDef_GetKeyValue(m_defSplashDamage, "damage"));
float flRadius = Skill_GetDefValue(EntityDef_GetKeyValue(m_defSplashDamage, "radius"));
if (m_flDmgMultiplier >= 0.0)
flDamage *= m_flDmgMultiplier;
//print(sprintf("Damage: %d; Radius: %d\n", flDamage, flRadius));
Damage_Radius(origin, owner, flDamage, flRadius, TRUE, WEAPON_NONE);
}
StartSoundDef(m_sndExplode, CHAN_VOICE, true);
Destroy();
}
void
NSProjectile::_LaunchHitscan(vector startPos, vector launchDir, float dmgMultiplier)
{
}
void
NSProjectile::Launch(vector startPos, vector launchDir, float fuseOffset, float powerMultiplier, float dmgMultiplier)
{
vector moveVel = g_vec_null;
if (m_bIsBullet) {
_LaunchHitscan(startPos, launchDir, dmgMultiplier);
return;
}
SetOrigin(startPos);
SetAngles(launchDir);
SetModel(m_oldModel);
if (dmgMultiplier <= 0.0)
dmgMultiplier = 1.0f;
if (powerMultiplier <= 0.0)
powerMultiplier = 1.0f;
m_flDmgMultiplier = dmgMultiplier;
/* convert absolute vel from def into relative */
makevectors(launchDir);
moveVel = (m_vecLaunchVelocity[0] * powerMultiplier) * v_forward;
moveVel += (m_vecLaunchVelocity[1] * powerMultiplier) * v_right;
moveVel += (m_vecLaunchVelocity[2] * powerMultiplier) * v_up;
/* fire slower underwater */
if (pointcontents(startPos) == CONTENT_WATER) {
SetVelocity(moveVel / 10);
} else {
SetVelocity(moveVel);
}
SetAngularVelocity(avelocity);
SetHealth(health);
SetSolid(SOLID_BBOX);
if (m_flBounce > 0) {
SetMovetype(MOVETYPE_BOUNCE);
} else {
SetMovetype(MOVETYPE_FLYMISSILE);
}
if (m_partSmokeFly) {
traileffectnum = particleeffectnum(m_partSmokeFly);
}
if (GetHealth() > 0) {
SetTakedamage(DAMAGE_YES);
} else {
SetTakedamage(DAMAGE_NO);
}
if (m_flFuse > 0) {
ScheduleThink(_FuseEnded, m_flFuse + fuseOffset);
}
StartSoundDef(m_sndFly, CHAN_BODY, true);
SendFlags = (-1);
SendEntity = 0; /* HACK: remove this once Spike fixes CSQC-set traileffectnum etc. */
}
void
NSProjectile::EvaluateEntity(void)
{
EVALUATE_VECTOR(origin, 0, PROJ_CHANGED_ORIGIN_X)
EVALUATE_VECTOR(origin, 1, PROJ_CHANGED_ORIGIN_Y)
EVALUATE_VECTOR(origin, 2, PROJ_CHANGED_ORIGIN_Z)
EVALUATE_VECTOR(angles, 0, PROJ_CHANGED_ANGLES_X)
EVALUATE_VECTOR(angles, 1, PROJ_CHANGED_ANGLES_Y)
EVALUATE_VECTOR(angles, 2, PROJ_CHANGED_ANGLES_Z)
EVALUATE_FIELD(modelindex, PROJ_CHANGED_MODELINDEX)
EVALUATE_FIELD(traileffectnum, PROJ_CHANGED_MODELINDEX)
}
/* Make sure StartFrame calls this */
float
NSProjectile::SendEntity(entity ePEnt, float flChanged)
{
if (!modelindex)
return (0);
if (clienttype(ePEnt) != CLIENTTYPE_REAL)
return (0);
WriteByte(MSG_ENTITY, ENT_ENTITYPROJECTILE);
/* broadcast how much data is expected to be read */
WriteFloat(MSG_ENTITY, flChanged);
SENDENTITY_COORD(origin[0], PROJ_CHANGED_ORIGIN_X)
SENDENTITY_COORD(origin[1], PROJ_CHANGED_ORIGIN_Y)
SENDENTITY_COORD(origin[2], PROJ_CHANGED_ORIGIN_Z)
SENDENTITY_ANGLE(angles[0], PROJ_CHANGED_ANGLES_X)
SENDENTITY_ANGLE(angles[1], PROJ_CHANGED_ANGLES_Y)
SENDENTITY_ANGLE(angles[2], PROJ_CHANGED_ANGLES_Z)
SENDENTITY_SHORT(modelindex, PROJ_CHANGED_MODELINDEX)
SENDENTITY_FLOAT(traileffectnum, PROJ_CHANGED_MODELINDEX)
return (1);
}
#endif
.float emiteffectnum;
#ifdef CLIENT
void
NSProjectile::ReceiveEntity(float flNew, float flChanged)
{
READENTITY_COORD(origin[0], PROJ_CHANGED_ORIGIN_X)
READENTITY_COORD(origin[1], PROJ_CHANGED_ORIGIN_Y)
READENTITY_COORD(origin[2], PROJ_CHANGED_ORIGIN_Z)
READENTITY_ANGLE(angles[0], PROJ_CHANGED_ANGLES_X)
READENTITY_ANGLE(angles[1], PROJ_CHANGED_ANGLES_Y)
READENTITY_ANGLE(angles[2], PROJ_CHANGED_ANGLES_Z)
READENTITY_SHORT(modelindex, PROJ_CHANGED_MODELINDEX)
READENTITY_FLOAT(traileffectnum, PROJ_CHANGED_MODELINDEX)
setmodelindex(this, modelindex);
setorigin(this, origin);
}
#endif
#ifdef CLIENT
void
NSProjectile_ReadEntity(bool new)
{
float fl;
NSProjectile rend = (NSProjectile)self;
if (new) {
spawnfunc_NSProjectile();
}
fl = readfloat();
rend.ReceiveEntity(new, fl);
}
#endif

View File

@ -66,6 +66,7 @@ string __fullspawndata;
#include "NSProjectile.h"
#include "NSItem.h"
#include "NSSpraylogo.h"
#include "NSPortal.h"
#include "../xr/defs.h"
#include "NSClient.h"

View File

@ -20,6 +20,7 @@ typedef enum
ENT_NONE = 0, /**< invalid, but reserved. */
ENT_ENTITY, /**< of type NSEntity */
ENT_ENTITYRENDERABLE, /**< of type NSRenderableEntity */
ENT_ENTITYPROJECTILE, /**< of type NSProjectile */
ENT_SURFPROP, /**< of type NSSurfacePropEntity */
ENT_PHYSICS, /**< of type NSPhysicsEntity */
ENT_MONSTER, /**< of type NSMonster */
@ -27,6 +28,7 @@ typedef enum
ENT_PLAYER, /**< of type NSClientPlayer */
ENT_WEAPON, /**< of type NSWeapon */
ENT_SPECTATOR, /**< of type NSClientSpectator */
ENT_PORTAL, /**< of type NSPortal */
ENT_AMBIENTSOUND, /**< of type ambient_generic */
ENT_BEAM, /**< of type env_beam */
ENT_DLIGHT, /**< of type light_dynamic */

View File

@ -17,6 +17,7 @@ NSMonster.qc
NSTalkMonster.qc
NSProjectile.qc
NSItem.qc
NSPortal.qc
NSClient.qc
NSClientSpectator.qc

View File

@ -581,6 +581,11 @@ PMoveCustom_Move(void)
self.origin = trace_endpos;
continue;
}
} else {
if (trace_ent.solid == SOLID_PORTAL) {
NSPortal portalEntry = (NSPortal)trace_ent;
portalEntry.TransportEntity((NSEntity)self);
}
}
}
@ -597,6 +602,10 @@ PMoveCustom_Move(void)
if (trace_fraction == 1.0) {
return;
} else if (trace_ent.solid == SOLID_PORTAL) {
NSPortal portalEntry = (NSPortal)trace_ent;
portalEntry.TransportEntity((NSEntity)self);
return;
}
/*if (trace_startsolid) {