Client: Allow mods to override entity updates of gs-entbase.

prop_vehicle_drivable: Initial work towards suspension.
This commit is contained in:
Marco Cawthorne 2022-02-11 17:09:08 -08:00
parent 22ab20a7b4
commit 47a37af545
Signed by: eukara
GPG Key ID: C196CD8BA993248A
8 changed files with 707 additions and 585 deletions

View File

@ -21,27 +21,27 @@ entity g_bodies;
void
FX_Corpse_Init(void)
{
entity next = spawn(NSRenderableEntity);
g_bodies = next;
entity next = spawn(NSRenderableEntity);
g_bodies = next;
for ( int i = 0; i <= CORPSES_MAX; i++ ) {
next.classname = "corpse";
next.owner = spawn(NSRenderableEntity);
if ( i == CORPSES_MAX ) {
next.owner = g_bodies;
} else {
next = next.owner;
}
}
for ( int i = 0; i <= CORPSES_MAX; i++ ) {
next.classname = "corpse";
next.owner = spawn(NSRenderableEntity);
if ( i == CORPSES_MAX ) {
next.owner = g_bodies;
} else {
next = next.owner;
}
}
}
entity
FX_Corpse_Next(void)
{
entity r = g_bodies;
g_bodies = g_bodies.owner;
return r;
entity r = g_bodies;
g_bodies = g_bodies.owner;
return r;
}
entity

View File

@ -1,107 +1,107 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <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.
*/
/*
* Begin calculating a route.
* The callback function will be called once the route has finished being calculated.
* The route must be memfreed once it is no longer needed.
* The route must be followed in reverse order (ie: the first node that must be reached
* is at index numnodes-1). If no route is available then the callback will be called with no nodes.
*/
int
Route_RoundDistance(float flDist)
{
float r = fabs(flDist) % 2;
if (r == 0)
return (flDist);
if (flDist < 0)
return -(fabs(flDist) - r);
else
return (flDist + 2 - r);
}
/*
================
Spawn_SelectRandom
================
*/
entity
Route_SelectRandom(string sEntname)
{
static entity eLastSpot;
eLastSpot = find(eLastSpot, classname, sEntname);
return (eLastSpot);
}
/*
================
Route_SelectRandomSpot
================
*/
entity
Route_SelectRandomSpot(void)
{
static entity eLastSpot;
eLastSpot = findfloat(eLastSpot, ::botinfo, BOTINFO_SPAWNPOINT);
return (eLastSpot);
}
void
Bot_RouteCB(entity ent, vector dest, int numnodes, nodeslist_t *nodelist)
{
bot b = (bot)ent;
b.m_iNodes = numnodes;
b.m_iCurNode = numnodes - 1;
b.m_pRoute = nodelist;
b.m_vecLastNode = dest;
//dprint("Bot: Route calculated.\n");
//dprint(sprintf("Bot: # of nodes: %i\n", bot.m_iNodes) );
//dprint(sprintf("Bot: # current node: %i\n", bot.m_iCurNode) );
//dprint(sprintf("Bot: # endpos: %v\n", dest));
}
vector
Route_SelectDestination(bot target)
{
entity dest = world;
if (target.health < 50) {
entity temp;
int bestrange = COST_INFINITE;
int range;
for (temp = world; (temp = findfloat(temp, ::botinfo, BOTINFO_HEALTH));) {
range = vlen(temp.origin - target.origin);
if ((range < bestrange) && (temp.solid == SOLID_TRIGGER)) {
bestrange = range;
dest = temp;
}
}
if (dest) {
//dprint("Route: Going for health!");
return (dest.origin + [0,0,32]);
}
}
dest = Route_SelectRandomSpot();
target.m_eDestination = dest;
return (dest.origin);
}
/*
* Copyright (c) 2016-2020 Marco Hladik <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.
*/
/*
* Begin calculating a route.
* The callback function will be called once the route has finished being calculated.
* The route must be memfreed once it is no longer needed.
* The route must be followed in reverse order (ie: the first node that must be reached
* is at index numnodes-1). If no route is available then the callback will be called with no nodes.
*/
int
Route_RoundDistance(float flDist)
{
float r = fabs(flDist) % 2;
if (r == 0)
return (flDist);
if (flDist < 0)
return -(fabs(flDist) - r);
else
return (flDist + 2 - r);
}
/*
================
Spawn_SelectRandom
================
*/
entity
Route_SelectRandom(string sEntname)
{
static entity eLastSpot;
eLastSpot = find(eLastSpot, classname, sEntname);
return (eLastSpot);
}
/*
================
Route_SelectRandomSpot
================
*/
entity
Route_SelectRandomSpot(void)
{
static entity eLastSpot;
eLastSpot = findfloat(eLastSpot, ::botinfo, BOTINFO_SPAWNPOINT);
return (eLastSpot);
}
void
Bot_RouteCB(entity ent, vector dest, int numnodes, nodeslist_t *nodelist)
{
bot b = (bot)ent;
b.m_iNodes = numnodes;
b.m_iCurNode = numnodes - 1;
b.m_pRoute = nodelist;
b.m_vecLastNode = dest;
//dprint("Bot: Route calculated.\n");
//dprint(sprintf("Bot: # of nodes: %i\n", bot.m_iNodes) );
//dprint(sprintf("Bot: # current node: %i\n", bot.m_iCurNode) );
//dprint(sprintf("Bot: # endpos: %v\n", dest));
}
vector
Route_SelectDestination(bot target)
{
entity dest = world;
if (target.health < 50) {
entity temp;
int bestrange = COST_INFINITE;
int range;
for (temp = world; (temp = findfloat(temp, ::botinfo, BOTINFO_HEALTH));) {
range = vlen(temp.origin - target.origin);
if ((range < bestrange) && (temp.solid == SOLID_TRIGGER)) {
bestrange = range;
dest = temp;
}
}
if (dest) {
//dprint("Route: Going for health!");
return (dest.origin + [0,0,32]);
}
}
dest = Route_SelectRandomSpot();
target.m_eDestination = dest;
return (dest.origin);
}

View File

@ -200,4 +200,8 @@ struct
float m_flShakeAmp;
vector m_vecLag;
/* vehicles */
float m_flVehTransition;
vector m_vecVehEntry;
} g_seats[4], *pSeat;

View File

@ -45,6 +45,7 @@ CSQC_Init(float apilevel, string enginename, float engineversion)
registercommand("player_geomtest");
registercommand("way_menu");
registercommand("dev_explode");
registercommand("dev_modeltest");
/* basic actions */
registercommand("+attack");
@ -660,6 +661,15 @@ CSQC_ConsoleCommand(string sCMD)
traceline(getproperty(VF_ORIGIN), getproperty(VF_ORIGIN) + v_forward * 4096, FALSE, pSeat->m_ePlayer);
dynamiclight_spawnstatic(trace_endpos + (v_forward * -16), 1024, [1,1,1]);
break;
case "dev_modeltest":
entity mt = spawn();
mt.drawmask = MASK_ENGINE;
setmodel(mt, argv(1));
setsize(mt, [0,0,0], [0,0,0]);
setorigin(mt, getproperty(VF_ORIGIN));
mt.angles = getproperty(VF_ANGLES);
mt.angles[0] = mt.angles[2] = 0;
break;
case "dev_explode":
makevectors(getproperty(VF_ANGLES));
traceline(getproperty(VF_ORIGIN), getproperty(VF_ORIGIN) + v_forward * 4096, FALSE, pSeat->m_ePlayer);
@ -880,6 +890,11 @@ CSQC_Ent_Update(float new)
float t;
t = readbyte();
/* client didn't override anything */
if (ClientGame_EntityUpdate(t, new)) {
return;
}
switch (t) {
case ENT_ENTITY:
NSEntity me = (NSEntity)self;
@ -985,9 +1000,7 @@ CSQC_Ent_Update(float new)
ips.ReceiveEntity(new, readfloat());
break;
default:
if (ClientGame_EntityUpdate(t, new) == FALSE) {
//error(sprintf("Unknown entity type update received. (%d)\n", t));
}
}
}

View File

@ -1,105 +1,105 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <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 env_particle (1 0 0) (-8 -8 -8) (8 8 8)
Client-side particle effect. Active at runtime, fully client-side.
Cannot be triggered. Repeats automatically.
-------- KEYS --------
"message" : Particle material to use.
"wait" : Delay between emits.
"target" : Name of target, like an info_notnull (client-side)
"velocity" : Speed in units.
"count" : Number of particles to emit.
-------- TRIVIA --------
This entity was introduced in The Wastes (2018).
*/
class env_particle:NSEntity
{
entity m_eTarget;
float m_flNextTime;
float m_flPartID;
float m_flVelocity;
float m_flWait;
int m_iCount;
string m_strTarget;
void(void) env_particle;
virtual void(void) customphysics;
virtual void(string, string) SpawnKey;
};
void env_particle::customphysics(void)
{
vector vecPlayer;
int s = (float)getproperty(VF_ACTIVESEAT);
pSeat = &g_seats[s];
vecPlayer = pSeat->m_vecPredictedOrigin;
if (checkpvs(vecPlayer, this) == FALSE) {
return;
}
if (!m_flVelocity) {
m_flVelocity = 1.0f;
}
if (m_flNextTime > time) {
return;
}
if (m_strTarget) {
m_eTarget = find(world, ::targetname, m_strTarget);
makevectors(vectoangles(m_eTarget.origin - origin) * -1);
angles = v_forward;
}
pointparticles(m_flPartID, origin, angles * m_flVelocity, m_iCount);
m_flNextTime = time + m_flWait;
}
void env_particle::SpawnKey(string strField, string strKey)
{
switch (strField) {
case "message":
m_flPartID = particleeffectnum(strKey);
break;
case "wait":
m_flWait = stof(strKey);
break;
case "target":
m_strTarget = strKey;
break;
case "velocity":
m_flVelocity = stof(strKey);
break;
case "count":
m_iCount = stoi(strKey);
break;
default:
super::SpawnKey(strField, strKey);
}
}
void env_particle::env_particle(void)
{
drawmask = MASK_ENGINE;
Init();
setorigin(this, origin);
setsize(this, [0,0,0], [0,0,0]);
}
/*
* Copyright (c) 2016-2020 Marco Hladik <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 env_particle (1 0 0) (-8 -8 -8) (8 8 8)
Client-side particle effect. Active at runtime, fully client-side.
Cannot be triggered. Repeats automatically.
-------- KEYS --------
"message" : Particle material to use.
"wait" : Delay between emits.
"target" : Name of target, like an info_notnull (client-side)
"velocity" : Speed in units.
"count" : Number of particles to emit.
-------- TRIVIA --------
This entity was introduced in The Wastes (2018).
*/
class env_particle:NSEntity
{
entity m_eTarget;
float m_flNextTime;
float m_flPartID;
float m_flVelocity;
float m_flWait;
int m_iCount;
string m_strTarget;
void(void) env_particle;
virtual void(void) customphysics;
virtual void(string, string) SpawnKey;
};
void env_particle::customphysics(void)
{
vector vecPlayer;
int s = (float)getproperty(VF_ACTIVESEAT);
pSeat = &g_seats[s];
vecPlayer = pSeat->m_vecPredictedOrigin;
if (checkpvs(vecPlayer, this) == FALSE) {
return;
}
if (!m_flVelocity) {
m_flVelocity = 1.0f;
}
if (m_flNextTime > time) {
return;
}
if (m_strTarget) {
m_eTarget = find(world, ::targetname, m_strTarget);
makevectors(vectoangles(m_eTarget.origin - origin) * -1);
angles = v_forward;
}
pointparticles(m_flPartID, origin, angles * m_flVelocity, m_iCount);
m_flNextTime = time + m_flWait;
}
void env_particle::SpawnKey(string strField, string strKey)
{
switch (strField) {
case "message":
m_flPartID = particleeffectnum(strKey);
break;
case "wait":
m_flWait = stof(strKey);
break;
case "target":
m_strTarget = strKey;
break;
case "velocity":
m_flVelocity = stof(strKey);
break;
case "count":
m_iCount = stoi(strKey);
break;
default:
super::SpawnKey(strField, strKey);
}
}
void env_particle::env_particle(void)
{
drawmask = MASK_ENGINE;
Init();
setorigin(this, origin);
setsize(this, [0,0,0], [0,0,0]);
}

View File

@ -1,330 +1,330 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <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
.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_BOUNCE);
SetSolid(SOLID_CORPSE);
}
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 0
/* 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) {
PhysicsEnable();
makevectors(vectoangles(origin - trace_ent.origin));
ApplyTorqueCenter(v_forward * 240);
}
}
#endif
/* 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 0
/* don't let players collide */
dimension_solid = 1;
dimension_hit = 1;
#endif
/* continue testing next frame */
nextthink = time;
effects &= ~EF_NOSHADOW;
}
void
NSPhysicsEntity::touch(void)
{
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 (!HasPropData()) {
health = 100000;
}
}
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(2.0f);
SetBounceFactor(0.25f);
#else
PhysicsDisable();
SetMass(1.0f);
SetFriction(1.0f);
SetBounceFactor(0.1f);
#endif
SetOrigin(GetSpawnOrigin());
/* 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");
}
/*
* Copyright (c) 2016-2020 Marco Hladik <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
.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_BOUNCE);
SetSolid(SOLID_CORPSE);
}
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 0
/* 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) {
PhysicsEnable();
makevectors(vectoangles(origin - trace_ent.origin));
ApplyTorqueCenter(v_forward * 240);
}
}
#endif
/* 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 0
/* don't let players collide */
dimension_solid = 1;
dimension_hit = 1;
#endif
/* continue testing next frame */
nextthink = time;
effects &= ~EF_NOSHADOW;
}
void
NSPhysicsEntity::touch(void)
{
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 (!HasPropData()) {
health = 100000;
}
}
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(2.0f);
SetBounceFactor(0.25f);
#else
PhysicsDisable();
SetMass(1.0f);
SetFriction(1.0f);
SetBounceFactor(0.1f);
#endif
SetOrigin(GetSpawnOrigin());
/* 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");
}

View File

@ -51,6 +51,7 @@ class NSEntity:NSTrigger
vector net_origin;
vector net_angles;
vector net_velocity;
float net_modelindex;
string m_parent;

View File

@ -30,12 +30,24 @@ Point entity defining a 4-wheel vehicle that you can drive.
This entity was introduced in Half-Life 2 (2004).
*/
enumflags
{
VEHFL_DRIVER,
VEHFL_MODELINDEX,
VEHFL_ORIGIN,
VEHFL_ANGLES,
VEHFL_VELOCITY,
VEHFL_TURNING
};
class prop_vehicle_driveable_wheel
{
void() prop_vehicle_driveable_wheel;
float m_flSuspension;
float m_flSuspensionForce;
#ifdef CLIENT
vector origin_net;
vector velocity_net;
vector angles_net;
@ -43,6 +55,7 @@ class prop_vehicle_driveable_wheel
virtual void(void) PredictPostFrame;
#endif
virtual void(float) UpdateSuspension;
virtual void(float) Move;
virtual void(vector) Bounce;
virtual void(float, float m_flTurn) Accel;
@ -87,6 +100,7 @@ class prop_vehicle_driveable:NSVehicle
#else
virtual void(void) Respawn;
virtual void(void) OnPlayerUse;
virtual void(void) EvaluateEntity;
virtual float(entity, float) SendEntity;
#endif
};
@ -301,6 +315,26 @@ prop_vehicle_driveable_wheel::Accel(float flMoveTime, float m_flTurn)
velocity += vehParent.m_vecGravityDir * flMoveTime * serverkeyfloat("phy_gravity") * trace_fraction;
}
void
prop_vehicle_driveable_wheel::UpdateSuspension(float flTimeLength)
{
float flDamp;
float flForce;
if (fabs(m_flSuspension) > 0.001 || fabs(m_flSuspensionForce) > 0.001) {
m_flSuspension += m_flSuspensionForce * flTimeLength;
flForce = bound(0, flTimeLength * 65, 2);
flDamp = 1 - (flTimeLength * 4);
if (flDamp < 0) {
flDamp = 0;
}
m_flSuspensionForce *= flDamp;
m_flSuspensionForce -= m_flSuspension * flForce;
m_flSuspension = bound(-15, m_flSuspension, 15);
}
}
void
prop_vehicle_driveable_wheel::Physics(float turnrate, float flTimeLength)
{
@ -311,9 +345,23 @@ prop_vehicle_driveable_wheel::Physics(float turnrate, float flTimeLength)
tracebox(owner_pos, mins, maxs, origin, MOVE_NOMONSTERS, owner);
setorigin(this, trace_endpos);
/* see if we're in-air */
other = world;
tracebox(origin, mins, maxs, origin - [0,0,1], MOVE_OTHERONLY, this);
if (!trace_startsolid) {
if ((trace_fraction < 1) && (trace_plane_normal[2] > 0.7)) {
flags |= FL_ONGROUND;
} else {
flags &= ~FL_ONGROUND;
m_flSuspensionForce += flTimeLength * 200.0;
}
}
Accel(flTimeLength / 2, turnrate);
Move(flTimeLength);
Accel(flTimeLength / 2, turnrate);
UpdateSuspension(flTimeLength);
print(sprintf("suspension: %d, force: %d\n", m_flSuspension, m_flSuspensionForce));
}
void
@ -405,8 +453,6 @@ prop_vehicle_driveable::RunVehiclePhysics(void)
angles[0] = Math_FixDelta(angles[0]);
angles[1] = Math_FixDelta(angles[1]);
angles[2] = Math_FixDelta(angles[2]);
angles[0] = bound(-45, angles[0], 45);
angles[2] = bound(-45, angles[2], 45);
velocity[0] = bound(-1000, velocity[0], 1000);
velocity[1] = bound(-1000, velocity[1], 1000);
@ -504,23 +550,34 @@ prop_vehicle_driveable::Respawn(void)
void
prop_vehicle_driveable::ReadEntity(float flSendFlags, float flNew)
{
m_eDriver = findfloat(world, ::entnum, readentitynum());
if (flSendFlags & VEHFL_DRIVER) {
m_eDriver = findfloat(world, ::entnum, readentitynum());
}
modelindex = readshort();
if (flSendFlags & VEHFL_MODELINDEX) {
modelindex = readshort();
}
origin[0] = readcoord();
origin[1] = readcoord();
origin[2] = readcoord();
if (flSendFlags & VEHFL_ORIGIN) {
origin[0] = readcoord();
origin[1] = readcoord();
origin[2] = readcoord();
}
angles[0] = readfloat();
angles[1] = readfloat();
angles[2] = readfloat();
if (flSendFlags & VEHFL_ANGLES) {
angles[0] = readfloat();
angles[1] = readfloat();
angles[2] = readfloat();
}
velocity[0] = readfloat();
velocity[1] = readfloat();
velocity[2] = readfloat();
if (flSendFlags & VEHFL_VELOCITY) {
velocity[0] = readfloat();
velocity[1] = readfloat();
velocity[2] = readfloat();
}
m_flTurn = readfloat();
if (flSendFlags & VEHFL_TURNING)
m_flTurn = readfloat();
if (flNew) {
drawmask = MASK_ENGINE;
@ -532,29 +589,76 @@ prop_vehicle_driveable::ReadEntity(float flSendFlags, float flNew)
}
}
#else
void
prop_vehicle_driveable::EvaluateEntity(void)
{
/* while the engine is still handling physics for these, we can't
* predict when origin/angle might change */
if (net_origin != origin) {
net_origin = origin;
SetSendFlags(VEHFL_ORIGIN);
}
if (net_angles != angles) {
angles[0] = Math_FixDelta(angles[0]);
angles[1] = Math_FixDelta(angles[1]);
angles[2] = Math_FixDelta(angles[2]);
net_angles = angles;
SetSendFlags(VEHFL_ANGLES);
}
if (net_modelindex != modelindex) {
net_modelindex = modelindex;
SetSendFlags(VEHFL_MODELINDEX);
}
if (net_velocity != velocity) {
net_velocity = velocity;
SetSendFlags(VEHFL_VELOCITY);
}
if (m_flTurn_net != m_flTurn) {
m_flTurn_net = m_flTurn;
SetSendFlags(VEHFL_TURNING);
}
if (m_eDriver_net != m_eDriver) {
m_eDriver_net = m_eDriver;
SetSendFlags(VEHFL_DRIVER);
}
}
float
prop_vehicle_driveable::SendEntity(entity ePVSent, float flSendFlags)
{
WriteByte(MSG_ENTITY, ENT_VEH_4WHEEL);
WriteFloat(MSG_ENTITY, flSendFlags);
WriteEntity(MSG_ENTITY, m_eDriver);
if (flSendFlags & VEHFL_DRIVER) {
WriteEntity(MSG_ENTITY, m_eDriver);
}
WriteShort(MSG_ENTITY, modelindex);
if (flSendFlags & VEHFL_MODELINDEX) {
WriteShort(MSG_ENTITY, modelindex);
}
WriteCoord(MSG_ENTITY, origin[0]);
WriteCoord(MSG_ENTITY, origin[1]);
WriteCoord(MSG_ENTITY, origin[2]);
if (flSendFlags & VEHFL_ORIGIN) {
WriteCoord(MSG_ENTITY, origin[0]);
WriteCoord(MSG_ENTITY, origin[1]);
WriteCoord(MSG_ENTITY, origin[2]);
}
WriteFloat(MSG_ENTITY, angles[0]);
WriteFloat(MSG_ENTITY, angles[1]);
WriteFloat(MSG_ENTITY, angles[2]);
if (flSendFlags & VEHFL_ANGLES) {
WriteFloat(MSG_ENTITY, angles[0]);
WriteFloat(MSG_ENTITY, angles[1]);
WriteFloat(MSG_ENTITY, angles[2]);
}
WriteFloat(MSG_ENTITY, velocity[0]);
WriteFloat(MSG_ENTITY, velocity[1]);
WriteFloat(MSG_ENTITY, velocity[2]);
if (flSendFlags & VEHFL_VELOCITY) {
WriteFloat(MSG_ENTITY, velocity[0]);
WriteFloat(MSG_ENTITY, velocity[1]);
WriteFloat(MSG_ENTITY, velocity[2]);
}
if (flSendFlags & VEHFL_TURNING)
WriteFloat(MSG_ENTITY, m_flTurn);
WriteFloat(MSG_ENTITY, m_flTurn);
return TRUE;
}
#endif