prop_physics_multiplayer: Initial implementation. Developed with the ODE plugin in mind.

This commit is contained in:
Marco Cawthorne 2022-04-20 10:44:59 -07:00
parent c24c8b2435
commit 8e15f04c38
Signed by: eukara
GPG Key ID: C196CD8BA993248A
14 changed files with 993 additions and 417 deletions

View File

@ -32,6 +32,9 @@ Entity_EntityUpdate(float type, float new)
}
rend.ReceiveEntity(new, readfloat());
break;
case ENT_PHYSICS:
NSPhysicsEntity_ReadEntity(new);
break;
case ENT_MONSTER:
NSMonster_ReadEntity(new);
self.customphysics = Empty;

View File

@ -6,7 +6,6 @@
baseentity.h
decals.h
server/NSOutput.qc
server/NSPhysicsEntity.qc
shared/NSVehicle.h
server/info_null.qc
server/info_notnull.qc

View File

@ -1,362 +0,0 @@
/*
* Copyright (c) 2016-2020 Marco Cawthorne <marco@icculus.org>
*
* 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 ODE_MODE 1
/* all the documented phys vars...*/
.float geomtype;
.float friction;
.float erp;
.float jointtype;
.float mass;
.float bouncefactor;
.float bouncestop;
void
NSPhysicsEntity::PhysicsEnable(void)
{
if (physics_supported() == TRUE) {
SetMovetype(MOVETYPE_PHYSICS);
SetSolid(SOLID_PHYSICS_BOX + m_iShape);
physics_enable(this, TRUE);
} else {
SetMovetype(MOVETYPE_BOUNCE);
SetSolid(SOLID_CORPSE);
}
m_iEnabled = TRUE;
}
void
NSPhysicsEntity::PhysicsDisable(void)
{
if (physics_supported() == TRUE) {
physics_enable(this, FALSE);
SetMovetype(MOVETYPE_NONE);
} else {
SetMovetype(MOVETYPE_NONE);
SetSolid(SOLID_BBOX);
}
m_iEnabled = FALSE;
}
void
NSPhysicsEntity::SetMass(float val)
{
mass = val;
}
float
NSPhysicsEntity::GetMass(void)
{
return mass;
}
void
NSPhysicsEntity::SetFriction(float val)
{
friction = val;
}
float
NSPhysicsEntity::GetFriction(void)
{
return friction;
}
void
NSPhysicsEntity::SetBounceFactor(float val)
{
bouncefactor = val;
}
float
NSPhysicsEntity::GetBounceFactor(void)
{
return bouncefactor;
}
void
NSPhysicsEntity::SetBounceStop(float val)
{
bouncestop = val;
}
float
NSPhysicsEntity::GetBounceStop(void)
{
return bouncestop;
}
void
NSPhysicsEntity::SetInertia(float val)
{
m_flInertiaScale = val;
}
float
NSPhysicsEntity::GetInertia(void)
{
return m_flInertiaScale;
}
float
NSPhysicsEntity::CalculateImpactDamage(int iDamage, int dmgType)
{
int filter = 0i;
/* if we're any of these dmg types, we don't transfer any kinetic energy */
filter |= (dmgType & DMG_BURN);
filter |= (dmgType & DMG_ELECTRO);
filter |= (dmgType & DMG_SOUND);
filter |= (dmgType & DMG_ENERGYBEAM);
filter |= (dmgType & DMG_DROWN);
filter |= (dmgType & DMG_POISON);
filter |= (dmgType & DMG_RADIATION);
filter |= (dmgType & DMG_ACID);
filter |= (dmgType & DMG_ACID);
filter |= (dmgType & DMG_SLOWFREEZE);
if (filter == 0i)
return (float)iDamage * 100;
else
return 0.0f;
}
void
NSPhysicsEntity::ApplyForceCenter(vector vecForce)
{
if (physics_supported() == TRUE) {
physics_addforce(this, vecForce, [0,0,0]);
} else {
velocity = vecForce;
}
}
void
NSPhysicsEntity::ApplyForceOffset(vector vecForce, vector vecOffset)
{
if (physics_supported() == TRUE) {
physics_addforce(this, vecForce, vecOffset);
} else {
velocity = vecForce;
}
}
void
NSPhysicsEntity::ApplyTorqueCenter(vector vecTorque)
{
if (physics_supported() == TRUE)
physics_addtorque(this, vecTorque * m_flInertiaScale);
else {
avelocity = vecTorque;
velocity = vecTorque;
velocity[2] = 96;
}
}
void
NSPhysicsEntity::TouchThink(void)
{
if (!m_iEnabled)
return;
if (physics_supported() == FALSE) {
/* let players collide */
dimension_solid = 255;
dimension_hit = 255;
tracebox(origin, mins, maxs, origin, FALSE, this);
/* stuck */
if (trace_startsolid) {
if (trace_ent.flags & FL_CLIENT) {
if (trace_ent.absmin[2] < absmax[2]) {
PhysicsEnable();
makevectors(vectoangles(origin - trace_ent.origin));
ApplyTorqueCenter(v_forward * 240);
} else {
PhysicsDisable();
velocity = [0,0,0];
}
}
}
}
/* If we barely move, disable the physics simulator */
if (vlen(velocity) <= 1) {
if (m_iEnabled) {
PhysicsDisable();
velocity = [0,0,0];
avelocity = [0,0,0];
}
if (physics_supported() == FALSE) {
vector wantangle;
vector newangle;
wantangle[0] = (int)((angles[0] + 45) / 90) * 90;
wantangle[1] = angles[1];
wantangle[2] = (int)((angles[2] + 45) / 90) * 90;
makevectors(angles);
angles = v_forward;
makevectors(wantangle);
newangle[0] = Math_Lerp(angles[0], v_forward[0], frametime * 5.0f);
newangle[1] = Math_Lerp(angles[1], v_forward[1], frametime * 5.0f);
newangle[2] = Math_Lerp(angles[2], v_forward[2], frametime * 5.0f);
angles = vectoangles(newangle);
}
}
if (physics_supported() == FALSE) {
/* don't let players collide */
dimension_solid = 1;
dimension_hit = 1;
}
/* continue testing next frame */
nextthink = time;
effects &= ~EF_NOSHADOW;
}
void
NSPhysicsEntity::touch(void)
{
if (trace_ent.flags & FL_CLIENT) {
if (trace_ent.absmin[2] > absmax[2]) {
velocity = [0,0,0];
PhysicsDisable();
return;
}
}
PhysicsEnable();
makevectors(vectoangles(origin - other.origin));
ApplyForceOffset(v_forward * 100, origin - other.origin);
}
void
NSPhysicsEntity::Pain(void)
{
float force;
if (m_iFlags & BPHY_NODMGPUSH)
return;
PhysicsEnable();
makevectors(vectoangles(origin - trace_endpos));
force = CalculateImpactDamage(g_dmg_iDamage, g_dmg_iFlags);
if (force > 0.0f)
ApplyForceOffset(v_forward * force, origin - trace_endpos);
/* if we don't have prop data, don't consider death */
if (HasPropData() == false)
health = 10000;
}
void
NSPhysicsEntity::Death(void)
{
Pain();
super::Death();
if (takedamage != DAMAGE_YES) {
takedamage = (DAMAGE_YES);
}
health = 1000;
}
void
NSPhysicsEntity::Respawn(void)
{
SetMovetype(MOVETYPE_PHYSICS);
SetSolid(SOLID_PHYSICS_BOX + m_iShape);
SetModel(GetSpawnModel());
geomtype = GEOMTYPE_TRIMESH;
takedamage = DAMAGE_YES;
#ifndef ODE_MODE
PhysicsDisable();
SetFriction(4.0f);
SetBounceFactor(0.05f);
SetMass(1.0f);
#else
PhysicsDisable();
SetMass(1.0f);
SetFriction(1.0f);
SetBounceFactor(0.1f);
#endif
SetOrigin(GetSpawnOrigin());
if (physics_supported() == FALSE) {
/* don't let players collide */
dimension_solid = 1;
dimension_hit = 1;
}
think = TouchThink;
nextthink = time + 0.1f;
effects &= ~EF_NOSHADOW;
if (HasPropData()) {
health = GetPropData(PROPINFO_HEALTH);
} else {
health = 100000;
}
}
void
NSPhysicsEntity::SpawnKey(string strKey, string strValue)
{
switch (strKey) {
case "physmodel":
m_iShape = stoi(strValue);
if (m_iShape > PHYSM_CYLINDER)
m_iShape = 0;
break;
case "massscale":
mass = stof(strValue);
break;
case "inertiascale":
m_flInertiaScale = stof(strValue);
break;
case "physdamagescale":
break;
case "material":
m_iMaterial = stof(strValue);
break;
case "nodamageforces":
if (strValue == "1")
m_iFlags |= BPHY_NODMGPUSH;
break;
case "Damagetype":
if (strValue == "1")
m_iFlags |= BPHY_SHARP;
break;
default:
super::SpawnKey(strKey, strValue);
break;
}
}
void
NSPhysicsEntity::NSPhysicsEntity(void)
{
mass = 1.0f;
m_flInertiaScale = 1.0f;
super::NSSurfacePropEntity();
cvar_set("physics_ode_iterationsperframe", "1");
cvar_set("physics_ode_movelimit", "0.1");
}

View File

@ -16,48 +16,10 @@
#include "../shared/baseentity.h"
#include "NSOutput.h"
#include "NSPhysicsEntity.h"
void FX_Spark(vector, vector);
void FX_BreakModel(int, vector, vector, vector, float);
/* This is required because people who use Hammer do awful things
to get their models to update. We get a multitude of juicy
hacks and symbols that Half-Life's engine strips and now we have to
replicate this behaviour. Be thankful this is not done in-engine for
every game/mod ever.
*/
string Util_FixModel(string mdl)
{
if (!mdl) {
return "";
}
int c = tokenizebyseparator(mdl, "/", "\\ ", "!");
string newpath = "";
for (int i = 0; i < c; i++) {
newpath = sprintf("%s/%s", newpath, argv(i));
}
/* Kill the first / */
newpath = substring(newpath, 1, strlen(newpath)-1);
/* Now we need to fix \/ because I hate people */
c = tokenizebyseparator(newpath, "\\/");
mdl = "";
for (int i = 0; i < c; i++) {
mdl = sprintf("%s/%s", mdl, argv(i));
}
/* Kill the first / again */
mdl = substring(mdl, 1, strlen(mdl)-1);
if (substring(mdl, 0, 1) == "/")
mdl = substring(mdl, 1, -1);
return mdl;
}
/* Backwards compat */
class CBaseMonster:NSMonster
{

View File

@ -8,6 +8,7 @@ shared/NSTrigger.qc
shared/NSEntity.qc
shared/NSRenderableEntity.qc
shared/NSSurfacePropEntity.qc
shared/NSPhysicsEntity.qc
shared/NSBrushTrigger.qc
shared/NSPointTrigger.qc
shared/NSVehicle.qc
@ -28,6 +29,7 @@ shared/func_tankmortar.qc
shared/trigger_camera.qc
shared/trigger_gravity.qc
shared/info_particle_system.qc
shared/prop_physics_multiplayer.qc
shared/prop_vehicle_driveable.qc
shared/prop_rope.qc
shared/worldspawn.qc

View File

@ -26,6 +26,11 @@ class NSEntity:NSTrigger
vector m_vecMins;
vector m_vecMaxs;
/* important spawn values */
vector m_oldOrigin;
vector m_oldAngle;
string m_oldModel;
/* keep track of these variables */
PREDICTED_VECTOR_N(origin);
PREDICTED_VECTOR_N(angles);
@ -61,9 +66,6 @@ class NSEntity:NSTrigger
#ifdef SERVER
/* respawn */
float m_oldSolid;
vector m_oldOrigin;
vector m_oldAngle;
string m_oldModel;
string m_parent;
string m_parent_attachment;
@ -82,10 +84,6 @@ class NSEntity:NSTrigger
/* some ents need this */
nonvirtual void(void) RestoreAngles;
nonvirtual void(void) ClearAngles;
nonvirtual vector(void) GetSpawnOrigin;
nonvirtual vector(void) GetSpawnAngles;
nonvirtual string(void) GetSpawnModel;
#endif
/* sets */
@ -106,6 +104,10 @@ class NSEntity:NSTrigger
virtual void(float) AddFlags;
virtual void(float) RemoveFlags;
/* gets */
nonvirtual vector(void) GetSpawnOrigin;
nonvirtual vector(void) GetSpawnAngles;
nonvirtual string(void) GetSpawnModel;
virtual float(void) GetScale;
virtual entity(void) GetOwner;
virtual vector(void) GetVelocity;

View File

@ -773,7 +773,6 @@ NSEntity::GetFlags(void)
}
#ifdef SERVER
vector
NSEntity::GetSpawnOrigin(void)
{
@ -790,6 +789,7 @@ NSEntity::GetSpawnModel(void)
return m_oldModel;
}
#ifdef SERVER
void
NSEntity::Respawn(void)
{
@ -1026,10 +1026,7 @@ NSEntity::NSEntity(void)
super::NSTrigger();
m_oldAngle = angles;
m_oldOrigin = origin;
m_oldSolid = solid;
m_oldModel = Util_FixModel(model);
blocked = BlockedHandler;
touch = TouchHandler;
@ -1044,4 +1041,8 @@ NSEntity::NSEntity(void)
#else
isCSQC = 1;
#endif
m_oldAngle = angles;
m_oldOrigin = origin;
m_oldModel = Util_FixModel(model);
}

View File

@ -29,6 +29,35 @@ enumflags
BPHY_SHARP
};
enumflags
{
PHYENT_CHANGED_ORIGIN_X,
PHYENT_CHANGED_ORIGIN_Y,
PHYENT_CHANGED_ORIGIN_Z,
PHYENT_CHANGED_ANGLES_X,
PHYENT_CHANGED_ANGLES_Y,
PHYENT_CHANGED_ANGLES_Z,
PHYENT_CHANGED_MODELINDEX,
PHYENT_CHANGED_SIZE,
PHYENT_CHANGED_FLAGS,
PHYENT_CHANGED_SOLID,
PHYENT_CHANGED_FRAME,
PHYENT_CHANGED_SKIN,
PHYENT_CHANGED_MOVETYPE,
PHYENT_CHANGED_EFFECTS,
PHYENT_CHANGED_BODY,
PHYENT_CHANGED_SCALE,
PHYENT_CHANGED_VELOCITY,
#ifdef GS_RENDERFX
PHYENT_CHANGED_RENDERCOLOR,
PHYENT_CHANGED_RENDERAMT,
PHYENT_CHANGED_RENDERMODE,
#else
PHYENT_CHANGED_ALPHA,
#endif
};
class NSPhysicsEntity:NSSurfacePropEntity
{
int m_iEnabled;
@ -41,10 +70,17 @@ class NSPhysicsEntity:NSSurfacePropEntity
/* overrides */
virtual void(void) Respawn;
virtual void(void) touch;
virtual void(void) TouchThink;
#ifdef SERVER
PREDICTED_VECTOR(m_vecNetAngles);
virtual void(void) Pain;
virtual void(void) Death;
virtual void(void) EvaluateEntity;
virtual float(entity, float) SendEntity;
#else
virtual void(float, float) ReceiveEntity;
#endif
virtual void(string, string) SpawnKey;
virtual void(float) SetMass;

View File

@ -0,0 +1,739 @@
/*
* Copyright (c) 2016-2020 Marco Cawthorne <marco@icculus.org>
*
* 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 ODE_MODE 1
#ifdef CLIENT
bool physics_supported(void)
{
return true;
}
#endif
/* all the documented phys vars...*/
.float geomtype;
.float friction;
.float erp;
.float jointtype;
.float mass;
.float bouncefactor;
.float bouncestop;
#ifdef SERVER
void
NSPhysicsEntity::EvaluateEntity(void)
{
#if 0
origin[0] = rint(origin[0]);
origin[1] = rint(origin[1]);
origin[2] = rint(origin[2]);
angles[0] = rint(angles[0]);
angles[1] = rint(angles[1]);
angles[2] = rint(angles[2]);
#endif
if (origin_x != origin_net_x) {
SetSendFlags(PHYENT_CHANGED_ORIGIN_X);
}
if (origin_y != origin_net_y) {
SetSendFlags(PHYENT_CHANGED_ORIGIN_Y);
}
if (origin_z != origin_net_z) {
SetSendFlags(PHYENT_CHANGED_ORIGIN_Z);
}
if (angles_x != angles_net_x) {
SetSendFlags(PHYENT_CHANGED_ANGLES_X);
}
if (angles_y != angles_net_y) {
SetSendFlags(PHYENT_CHANGED_ANGLES_Y);
}
if (angles_z != angles_net_z) {
SetSendFlags(PHYENT_CHANGED_ANGLES_Z);
}
if (ATTR_CHANGED(modelindex)) {
SetSendFlags(PHYENT_CHANGED_MODELINDEX);
}
if (ATTR_CHANGED(solid)) {
SetSendFlags(PHYENT_CHANGED_SOLID);
}
if (ATTR_CHANGED(movetype)) {
SetSendFlags(PHYENT_CHANGED_MOVETYPE);
}
if (ATTR_CHANGED(size)) {
SetSendFlags(PHYENT_CHANGED_SIZE);
}
if (ATTR_CHANGED(frame)) {
SetSendFlags(PHYENT_CHANGED_FRAME);
}
if (ATTR_CHANGED(skin)) {
SetSendFlags(PHYENT_CHANGED_SKIN);
}
if (ATTR_CHANGED(effects)) {
SetSendFlags(PHYENT_CHANGED_EFFECTS);
}
if (ATTR_CHANGED(m_iBody)) {
SetSendFlags(PHYENT_CHANGED_BODY);
}
if (ATTR_CHANGED(scale)) {
SetSendFlags(PHYENT_CHANGED_SCALE);
}
if (ATTR_CHANGED(velocity)) {
SetSendFlags(PHYENT_CHANGED_VELOCITY);
}
SAVE_STATE(origin);
SAVE_STATE(angles);
SAVE_STATE(modelindex);
SAVE_STATE(solid);
SAVE_STATE(movetype);
SAVE_STATE(size);
SAVE_STATE(frame);
SAVE_STATE(skin);
SAVE_STATE(effects);
SAVE_STATE(m_iBody);
SAVE_STATE(scale);
SAVE_STATE(velocity);
#ifdef GS_RENDERFX
if (ATTR_CHANGED(m_iRenderMode)) {
SetSendFlags(PHYENT_CHANGED_RENDERMODE);
}
if (ATTR_CHANGED(m_iRenderFX)) {
SetSendFlags(PHYENT_CHANGED_RENDERMODE);
}
if (ATTR_CHANGED(m_vecRenderColor)) {
SetSendFlags(PHYENT_CHANGED_RENDERCOLOR);
}
if (ATTR_CHANGED(m_flRenderAmt)) {
SetSendFlags(PHYENT_CHANGED_RENDERAMT);
}
#else
if (ATTR_CHANGED(alpha)) {
SetSendFlags(PHYENT_CHANGED_ALPHA);
}
#endif
}
float
NSPhysicsEntity::SendEntity(entity ePEnt, float fChanged)
{
if (!modelindex)
return (0);
if (clienttype(ePEnt) != CLIENTTYPE_REAL)
return (0);
WriteByte(MSG_ENTITY, ENT_PHYSICS);
/* newly popped into the PVS, sadly this is the only hacky way to check
* for this right now. convince the engine maintainer to make this more sensible */
if (fChanged == 0xFFFFFF) {
/* check for defaults. if these are predictable fields, don't even bother
* networking them! you're just wasting bandwidth. */
if (frame == 0)
fChanged &= ~PHYENT_CHANGED_FRAME;
if (skin == 0)
fChanged &= ~PHYENT_CHANGED_SKIN;
if (effects == 0)
fChanged &= ~PHYENT_CHANGED_EFFECTS;
if (m_iBody == 0)
fChanged &= ~PHYENT_CHANGED_BODY;
if (scale == 0.0 || scale == 1.0)
fChanged &= ~PHYENT_CHANGED_SCALE;
if (origin[0] == 0)
fChanged &= ~PHYENT_CHANGED_ORIGIN_X;
if (origin[1] == 0)
fChanged &= ~PHYENT_CHANGED_ORIGIN_Y;
if (origin[2] == 0)
fChanged &= ~PHYENT_CHANGED_ORIGIN_Z;
if (angles[0] == 0)
fChanged &= ~PHYENT_CHANGED_ANGLES_X;
if (angles[1] == 0)
fChanged &= ~PHYENT_CHANGED_ANGLES_Y;
if (angles[2] == 0)
fChanged &= ~PHYENT_CHANGED_ANGLES_Z;
if (velocity == [0,0,0])
fChanged &= ~PHYENT_CHANGED_VELOCITY;
if (mins == [0,0,0] && maxs == [0,0,0])
fChanged &= ~PHYENT_CHANGED_SIZE;
if (solid == SOLID_NOT)
fChanged &= ~PHYENT_CHANGED_SOLID;
if (movetype == MOVETYPE_NONE)
fChanged &= ~PHYENT_CHANGED_MOVETYPE;
#ifdef GS_RENDERFX
if (m_iRenderMode == RM_NORMAL)
fChanged &= ~PHYENT_CHANGED_RENDERMODE;
#endif
}
/* broadcast how much data is expected to be read */
WriteFloat(MSG_ENTITY, fChanged);
/* really trying to get our moneys worth with 23 bits of mantissa */
if (fChanged & PHYENT_CHANGED_ORIGIN_X) {
WriteCoord(MSG_ENTITY, origin[0]);
}
if (fChanged & PHYENT_CHANGED_ORIGIN_Y) {
WriteCoord(MSG_ENTITY, origin[1]);
}
if (fChanged & PHYENT_CHANGED_ORIGIN_Z) {
WriteCoord(MSG_ENTITY, origin[2]);
}
if (fChanged & PHYENT_CHANGED_ANGLES_X) {
WriteShort(MSG_ENTITY, angles[0] * 32767 / 360);
}
if (fChanged & PHYENT_CHANGED_ANGLES_Y) {
WriteShort(MSG_ENTITY, angles[1] * 32767 / 360);
}
if (fChanged & PHYENT_CHANGED_ANGLES_Z) {
WriteShort(MSG_ENTITY, angles[2] * 32767 / 360);
}
if (fChanged & PHYENT_CHANGED_MODELINDEX) {
WriteShort(MSG_ENTITY, modelindex);
}
if (fChanged & PHYENT_CHANGED_SOLID) {
WriteByte(MSG_ENTITY, solid);
}
if (fChanged & PHYENT_CHANGED_MOVETYPE) {
WriteByte(MSG_ENTITY, movetype);
}
if (fChanged & PHYENT_CHANGED_SIZE) {
WriteCoord(MSG_ENTITY, mins[0]);
WriteCoord(MSG_ENTITY, mins[1]);
WriteCoord(MSG_ENTITY, mins[2]);
WriteCoord(MSG_ENTITY, maxs[0]);
WriteCoord(MSG_ENTITY, maxs[1]);
WriteCoord(MSG_ENTITY, maxs[2]);
}
if (fChanged & PHYENT_CHANGED_FRAME) {
WriteByte(MSG_ENTITY, frame);
WriteByte(MSG_ENTITY, frame1time);
}
if (fChanged & PHYENT_CHANGED_SKIN) {
WriteByte(MSG_ENTITY, skin + 128);
}
if (fChanged & PHYENT_CHANGED_EFFECTS) {
WriteFloat(MSG_ENTITY, effects);
}
if (fChanged & PHYENT_CHANGED_BODY) {
WriteByte(MSG_ENTITY, m_iBody);
}
if (fChanged & PHYENT_CHANGED_SCALE) {
WriteFloat(MSG_ENTITY, scale);
}
if (fChanged & PHYENT_CHANGED_VELOCITY) {
WriteFloat(MSG_ENTITY, velocity[0]);
WriteFloat(MSG_ENTITY, velocity[1]);
WriteFloat(MSG_ENTITY, velocity[2]);
}
#ifdef GS_RENDERFX
if (fChanged & PHYENT_CHANGED_RENDERMODE) {
WriteByte(MSG_ENTITY, m_iRenderMode);
WriteByte(MSG_ENTITY, m_iRenderFX);
}
if (fChanged & PHYENT_CHANGED_RENDERCOLOR) {
WriteFloat(MSG_ENTITY, m_vecRenderColor[0]);
WriteFloat(MSG_ENTITY, m_vecRenderColor[1]);
WriteFloat(MSG_ENTITY, m_vecRenderColor[2]);
WriteFloat(MSG_ENTITY, glowmod[0]);
WriteFloat(MSG_ENTITY, glowmod[1]);
WriteFloat(MSG_ENTITY, glowmod[2]);
}
if (fChanged & PHYENT_CHANGED_RENDERAMT) {
WriteFloat(MSG_ENTITY, m_flRenderAmt);
}
#else
if (fChanged & PHYENT_CHANGED_ALPHA) {
WriteFloat(MSG_ENTITY, alpha);
}
#endif
return (1);
}
#else
/*
============
NSPhysicsEntity::ReceiveEntity
============
*/
void
NSPhysicsEntity::ReceiveEntity(float flNew, float flChanged)
{
if (flChanged & PHYENT_CHANGED_ORIGIN_X) {
origin[0] = readcoord();
}
if (flChanged & PHYENT_CHANGED_ORIGIN_Y) {
origin[1] = readcoord();
}
if (flChanged & PHYENT_CHANGED_ORIGIN_Z) {
origin[2] = readcoord();
}
if (flChanged & PHYENT_CHANGED_ANGLES_X) {
angles[0] = readshort() / (32767 / 360);
}
if (flChanged & PHYENT_CHANGED_ANGLES_Y) {
angles[1] = readshort() / (32767 / 360);
}
if (flChanged & PHYENT_CHANGED_ANGLES_Z) {
angles[2] = readshort() / (32767 / 360);
}
if (flChanged & PHYENT_CHANGED_MODELINDEX) {
setmodelindex(this, readshort());
}
if (flChanged & PHYENT_CHANGED_SOLID) {
solid = readbyte();
}
if (flChanged & PHYENT_CHANGED_MOVETYPE) {
movetype = readbyte();
if (movetype == MOVETYPE_PHYSICS) {
movetype = MOVETYPE_NONE;
}
}
if (flChanged & PHYENT_CHANGED_SIZE) {
mins[0] = readcoord();
mins[1] = readcoord();
mins[2] = readcoord();
maxs[0] = readcoord();
maxs[1] = readcoord();
maxs[2] = readcoord();
}
if (flChanged & PHYENT_CHANGED_FRAME) {
frame = readbyte();
frame1time =
frame2time = readbyte();
}
if (flChanged & PHYENT_CHANGED_SKIN) {
skin = readbyte() - 128;
}
if (flChanged & PHYENT_CHANGED_EFFECTS) {
effects = readfloat();
}
if (flChanged & PHYENT_CHANGED_BODY) {
m_iBody = readbyte();
setcustomskin(this, "", sprintf("geomset 0 %i\ngeomset 1 %i\n", m_iBody, m_iBody));
}
if (flChanged & PHYENT_CHANGED_SCALE) {
scale = readfloat();
}
if (flChanged & PHYENT_CHANGED_VELOCITY) {
velocity[0] = readfloat();
velocity[1] = readfloat();
velocity[2] = readfloat();
}
#ifdef GS_RENDERFX
if (flChanged & PHYENT_CHANGED_RENDERMODE) {
m_iRenderMode = readbyte();
m_iRenderFX = readbyte();
}
if (flChanged & PHYENT_CHANGED_RENDERCOLOR) {
m_vecRenderColor[0] = readfloat();
m_vecRenderColor[1] = readfloat();
m_vecRenderColor[2] = readfloat();
glowmod[0] = readfloat();
glowmod[1] = readfloat();
glowmod[2] = readfloat();
}
if (flChanged & PHYENT_CHANGED_RENDERAMT) {
m_flRenderAmt = readfloat();
}
#else
if (flChanged & PHYENT_CHANGED_ALPHA) {
alpha = readfloat();
}
#endif
if (modelindex) {
drawmask = MASK_ENGINE;
} else {
drawmask = 0;
}
if (scale == 0.0)
scale = 1.0f;
if (flChanged & PHYENT_CHANGED_SIZE)
setsize(this, mins * scale, maxs * scale);
setorigin(this, origin);
}
#endif
void
NSPhysicsEntity::PhysicsEnable(void)
{
if (physics_supported() == TRUE) {
SetMovetype(MOVETYPE_PHYSICS);
SetSolid(SOLID_PHYSICS_BOX + m_iShape);
physics_enable(this, TRUE);
} else {
SetMovetype(MOVETYPE_BOUNCE);
SetSolid(SOLID_CORPSE);
}
m_iEnabled = TRUE;
}
void
NSPhysicsEntity::PhysicsDisable(void)
{
if (physics_supported() == TRUE) {
physics_enable(this, FALSE);
SetMovetype(MOVETYPE_NONE);
} else {
SetMovetype(MOVETYPE_NONE);
SetSolid(SOLID_BBOX);
}
m_iEnabled = FALSE;
}
void
NSPhysicsEntity::SetMass(float val)
{
mass = val;
}
float
NSPhysicsEntity::GetMass(void)
{
return mass;
}
void
NSPhysicsEntity::SetFriction(float val)
{
friction = val;
}
float
NSPhysicsEntity::GetFriction(void)
{
return friction;
}
void
NSPhysicsEntity::SetBounceFactor(float val)
{
bouncefactor = val;
}
float
NSPhysicsEntity::GetBounceFactor(void)
{
return bouncefactor;
}
void
NSPhysicsEntity::SetBounceStop(float val)
{
bouncestop = val;
}
float
NSPhysicsEntity::GetBounceStop(void)
{
return bouncestop;
}
void
NSPhysicsEntity::SetInertia(float val)
{
m_flInertiaScale = val;
}
float
NSPhysicsEntity::GetInertia(void)
{
return m_flInertiaScale;
}
float
NSPhysicsEntity::CalculateImpactDamage(int iDamage, int dmgType)
{
int filter = 0i;
/* if we're any of these dmg types, we don't transfer any kinetic energy */
filter |= (dmgType & DMG_BURN);
filter |= (dmgType & DMG_ELECTRO);
filter |= (dmgType & DMG_SOUND);
filter |= (dmgType & DMG_ENERGYBEAM);
filter |= (dmgType & DMG_DROWN);
filter |= (dmgType & DMG_POISON);
filter |= (dmgType & DMG_RADIATION);
filter |= (dmgType & DMG_ACID);
filter |= (dmgType & DMG_ACID);
filter |= (dmgType & DMG_SLOWFREEZE);
if (filter == 0i)
return (float)iDamage * 100;
else
return 0.0f;
}
void
NSPhysicsEntity::ApplyForceCenter(vector vecForce)
{
if (physics_supported() == TRUE) {
physics_addforce(this, vecForce, [0,0,0]);
} else {
velocity = vecForce;
}
/* make sure touch think is called */
nextthink = time;
}
void
NSPhysicsEntity::ApplyForceOffset(vector vecForce, vector vecOffset)
{
if (physics_supported() == TRUE) {
physics_addforce(this, vecForce, vecOffset);
} else {
velocity = vecForce;
}
/* make sure touch think is called */
nextthink = time;
}
void
NSPhysicsEntity::ApplyTorqueCenter(vector vecTorque)
{
if (physics_supported() == TRUE)
physics_addtorque(this, vecTorque * m_flInertiaScale);
else {
avelocity = vecTorque;
velocity = vecTorque;
velocity[2] = 96;
}
/* make sure touch think is called */
nextthink = time;
}
void
NSPhysicsEntity::TouchThink(void)
{
if (!m_iEnabled) {
return;
}
if (physics_supported() == FALSE) {
/* let players collide */
dimension_solid = 255;
dimension_hit = 255;
tracebox(origin, mins, maxs, origin, FALSE, this);
/* stuck */
if (trace_startsolid) {
if (trace_ent.flags & FL_CLIENT) {
if (trace_ent.absmin[2] < absmax[2]) {
PhysicsEnable();
makevectors(vectoangles(origin - trace_ent.origin));
ApplyTorqueCenter(v_forward * 240);
} else {
PhysicsDisable();
velocity = [0,0,0];
}
}
}
}
/* If we barely move, disable the physics simulator */
if (vlen(velocity) <= 1) {
if (m_iEnabled) {
PhysicsDisable();
velocity = [0,0,0];
avelocity = [0,0,0];
}
if (physics_supported() == FALSE) {
vector wantangle;
vector newangle;
wantangle[0] = (int)((angles[0] + 45) / 90) * 90;
wantangle[1] = angles[1];
wantangle[2] = (int)((angles[2] + 45) / 90) * 90;
makevectors(angles);
angles = v_forward;
makevectors(wantangle);
newangle[0] = Math_Lerp(angles[0], v_forward[0], frametime * 5.0f);
newangle[1] = Math_Lerp(angles[1], v_forward[1], frametime * 5.0f);
newangle[2] = Math_Lerp(angles[2], v_forward[2], frametime * 5.0f);
angles = vectoangles(newangle);
}
}
if (physics_supported() == FALSE) {
/* don't let players collide */
dimension_solid = 1;
dimension_hit = 1;
}
/* continue testing next frame */
nextthink = time;
effects &= ~EF_NOSHADOW;
}
#ifdef SERVER
void
NSPhysicsEntity::Pain(void)
{
float force;
if (m_iFlags & BPHY_NODMGPUSH)
return;
PhysicsEnable();
makevectors(vectoangles(origin - trace_endpos));
force = CalculateImpactDamage(g_dmg_iDamage, g_dmg_iFlags);
if (force > 0.0f)
ApplyForceOffset(v_forward * force, origin - trace_endpos);
/* if we don't have prop data, don't consider death */
if (HasPropData() == false)
health = 10000;
/* make sure touch think is called */
nextthink = time;
}
void
NSPhysicsEntity::Death(void)
{
Pain();
super::Death();
if (takedamage != DAMAGE_YES) {
takedamage = (DAMAGE_YES);
}
health = 1000;
/* make sure touch think is called */
nextthink = time;
}
#endif
void
NSPhysicsEntity::Respawn(void)
{
SetMovetype(MOVETYPE_PHYSICS);
SetSolid(SOLID_PHYSICS_BOX + m_iShape);
SetModel(GetSpawnModel());
geomtype = GEOMTYPE_TRIMESH;
#ifdef SERVER
SetTakedamage(DAMAGE_YES);
#endif
#ifndef ODE_MODE
PhysicsDisable();
SetFriction(4.0f);
SetBounceFactor(0.05f);
SetMass(1.0f);
#else
PhysicsDisable();
SetMass(1.0f);
SetFriction(1.0f);
SetBounceFactor(0.1f);
#endif
SetOrigin(GetSpawnOrigin());
if (physics_supported() == FALSE) {
/* don't let players collide */
dimension_solid = 1;
dimension_hit = 1;
}
think = TouchThink;
nextthink = time + 0.1f;
effects &= ~EF_NOSHADOW;
#ifdef SERVER
if (HasPropData()) {
health = GetPropData(PROPINFO_HEALTH);
} else {
health = 100000;
}
#endif
}
void
NSPhysicsEntity::SpawnKey(string strKey, string strValue)
{
switch (strKey) {
case "physmodel":
m_iShape = stoi(strValue);
if (m_iShape > PHYSM_CYLINDER)
m_iShape = 0;
break;
case "massscale":
mass = stof(strValue);
break;
case "inertiascale":
m_flInertiaScale = stof(strValue);
break;
case "physdamagescale":
break;
case "material":
m_iMaterial = stof(strValue);
break;
case "nodamageforces":
if (strValue == "1")
m_iFlags |= BPHY_NODMGPUSH;
break;
case "Damagetype":
if (strValue == "1")
m_iFlags |= BPHY_SHARP;
break;
default:
super::SpawnKey(strKey, strValue);
break;
}
}
void
NSPhysicsEntity::NSPhysicsEntity(void)
{
mass = 1.0f;
m_flInertiaScale = 1.0f;
super::NSSurfacePropEntity();
cvar_set("physics_ode_iterationsperframe", "1");
cvar_set("physics_ode_movelimit", "0.1");
}
#ifdef CLIENT
void
NSPhysicsEntity_ReadEntity(bool new)
{
float fl;
NSPhysicsEntity rend = (NSPhysicsEntity)self;
if (new) {
spawnfunc_NSPhysicsEntity();
}
fl = readfloat();
rend.ReceiveEntity(new, fl);
//print(sprintf("physics ent update: %d %x %d %v\n", self.entnum, fl, self.origin, vlen(self.velocity)));
}
#endif

View File

@ -181,9 +181,6 @@ NSRenderableEntity::SendEntity(entity ePEnt, float fChanged)
WriteCoord(MSG_ENTITY, origin[2]);
}
if (fChanged & BASEFL_CHANGED_ANGLES) {
/* fix angles and try to avoid a net update next frame */
angles = Math_FixDeltaVector(angles);
angles_net = angles;
WriteShort(MSG_ENTITY, angles[0] * 32767 / 360);
WriteShort(MSG_ENTITY, angles[1] * 32767 / 360);
WriteShort(MSG_ENTITY, angles[2] * 32767 / 360);

View File

@ -22,6 +22,7 @@
#include "NSEntity.h"
#include "NSRenderableEntity.h"
#include "NSSurfacePropEntity.h"
#include "NSPhysicsEntity.h"
#include "NSBrushTrigger.h"
#include "NSPointTrigger.h"
#include "NSMonster.h"

View File

@ -0,0 +1,158 @@
/*
* Copyright (c) 2016-2020 Marco Cawthorne <marco@icculus.org>
*
* 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.
*/
/*QUAKED prop_physics_multiplayer (0 0 0.8) (-16 -16 -16) (16 16 16) PRPPHYS_ASLEEP
Physics prop that is optimised for netplay.
It either pushes the player away, or gets pushed away by the player.
The simulation can take place on either client or server.
-------- KEYS --------
"targetname" : Name
"physicsmode" : Which type of push/simulation model to use. See notes.
-------- NOTES --------
'physicsmode' can be one of three things:
1 - push player away
2 - get pushed (server-side)
3 - get pushed (client-side)
-------- TRIVIA --------
This entity was introduced in Half-Life 2 (2004).
*/
#ifndef PHYSICS_STATIC
#define PRPPHYS_ASLEEP 1
class prop_physics_multiplayer:NSPhysicsEntity
{
int m_iPhysicsMode;
void(void) prop_physics_multiplayer;
#ifdef CLIENT
virtual void(void) Init;
#endif
virtual void(void) Respawn;
virtual void(string, string) SpawnKey;
virtual void(entity) Touch;
virtual void(void) TouchThink;
};
void
prop_physics_multiplayer::TouchThink(void)
{
PhysicsEnable();
super::TouchThink();
}
void
prop_physics_multiplayer::Touch(entity eToucher)
{
/* we want to push our toucher away */
if (eToucher && eToucher.flags & FL_CLIENT)
if (m_iPhysicsMode == 1) {
makevectors(eToucher.origin - origin);
eToucher.velocity = v_forward * 256;
}
super::Touch(eToucher);
nextthink = time;
}
void
prop_physics_multiplayer::Respawn(void)
{
#ifdef SERVER
if (m_iPhysicsMode == 3) {
#else
if (m_iPhysicsMode != 3) {
#endif
Destroy();
return;
}
#ifdef SERVER
dimension_solid = 1;
dimension_hit = 1;
#endif
super::Respawn();
if (HasSpawnFlags(PRPPHYS_ASLEEP))
PhysicsDisable();
else
PhysicsEnable();
}
void
prop_physics_multiplayer::SpawnKey(string strKey, string strValue)
{
switch (strKey) {
case "physicsmode":
m_iPhysicsMode = stoi(strValue);
break;
default:
NSPhysicsEntity::SpawnKey(strKey, strValue);
}
}
#ifdef CLIENT
void
prop_physics_multiplayer::Init(void)
{
super::Init();
m_oldAngle = angles;
m_oldOrigin = origin;
m_oldModel = Util_FixModel(model);
Respawn();
drawmask = MASK_ENGINE;
}
#endif
void
prop_physics_multiplayer::prop_physics_multiplayer(void)
{
#ifdef CLIENT
Init();
#else
super::NSPhysicsEntity();
#endif
}
#else
#ifdef SERVER
class prop_physics_multiplayer:NSRenderableEntity
{
void(void) prop_physics_multiplayer;
virtual void(void) Respawn;
};
void
prop_physics_multiplayer::Respawn(void)
{
super::Respawn();
SetSolid(SOLID_BBOX);
}
void
prop_physics_multiplayer::prop_physics_multiplayer(void)
{
super::NSRenderableEntity();
}
#endif
#endif

View File

@ -244,4 +244,41 @@ readcmd(string foo)
}
#else
void(string cmd) readcmd = #0:readcmd;
#endif
#endif
/* This is required because people who use Hammer do awful things
to get their models to update. We get a multitude of juicy
hacks and symbols that Half-Life's engine strips and now we have to
replicate this behaviour. Be thankful this is not done in-engine for
every game/mod ever.
*/
string Util_FixModel(string mdl)
{
if (!mdl) {
return "";
}
int c = tokenizebyseparator(mdl, "/", "\\ ", "!");
string newpath = "";
for (int i = 0; i < c; i++) {
newpath = sprintf("%s/%s", newpath, argv(i));
}
/* Kill the first / */
newpath = substring(newpath, 1, strlen(newpath)-1);
/* Now we need to fix \/ because I hate people */
c = tokenizebyseparator(newpath, "\\/");
mdl = "";
for (int i = 0; i < c; i++) {
mdl = sprintf("%s/%s", mdl, argv(i));
}
/* Kill the first / again */
mdl = substring(mdl, 1, strlen(mdl)-1);
if (substring(mdl, 0, 1) == "/")
mdl = substring(mdl, 1, -1);
return mdl;
}

View File

@ -20,6 +20,7 @@ enum
ENT_NONE,
ENT_ENTITY,
ENT_ENTITYRENDERABLE,
ENT_PHYSICS,
ENT_MONSTER,
ENT_TALKMONSTER,
ENT_PLAYER,