PropData: read info from Source Engine .phy files

This commit is contained in:
Marco Cawthorne 2023-10-22 23:30:31 -07:00
parent 8cd6d3967d
commit 73bec53034
Signed by: eukara
GPG Key ID: CE2032F0A2882A22
17 changed files with 421 additions and 78 deletions

View File

@ -126,4 +126,15 @@ destruction.
this entity breaks. this entity breaks.
- **breakable_model <string>**: Which models to spawn when it breaks. - **breakable_model <string>**: Which models to spawn when it breaks.
- **breakable_count <int>**: The amount of models it'll spawn upon - **breakable_count <int>**: The amount of models it'll spawn upon
breaking. breaking.
- **surfaceprop <string>**: Surfaceprop override for the object.
## Physics Object Commands {#physcommands}
These are only relevant for when you want to use a phyics object, or rather an object that's handled by NSPhysicsEntity, such as prop_physics.
- **mass <float>**: Mass of the object, in kilograms.
- **volume <float>**: Volume of the object, in cubic meters.
- **inertia <float>**: Inertia multiplier.
- **damping <float>**: Linear movement damping multiplier.
- **rotdamping <float>**: Angular movement damping multiplier.

View File

@ -228,6 +228,9 @@ Event_Parse(float type)
case EV_SHAKE: case EV_SHAKE:
EV_Shake(); EV_Shake();
break; break;
case EV_BREAKMODELDATA:
BreakModel_ReceiveClientData();
break;
case EV_BREAKMODEL: case EV_BREAKMODEL:
BreakModel_Receive(); BreakModel_Receive();
break; break;

View File

@ -255,8 +255,8 @@ View_DrawViewModel(void)
if (Client_IsSpectator(cl) || XR_Available(pl) == false) { if (Client_IsSpectator(cl) || XR_Available(pl) == false) {
m_eViewModelL.origin = g_view.GetCameraOrigin(); m_eViewModelL.origin = g_view.GetCameraOrigin();
m_eViewModel.origin = g_view.GetCameraOrigin(); m_eViewModel.origin = g_view.GetCameraOrigin();
m_eViewModel.angles = g_view.GetCameraAngle(); //m_eViewModel.angles = g_view.GetCameraAngle();
m_eViewModelL.angles = g_view.GetCameraAngle(); //m_eViewModelL.angles = g_view.GetCameraAngle();
/* we only calculate bob on the right model, to avoid double speed bobbing */ /* we only calculate bob on the right model, to avoid double speed bobbing */
Viewmodel_CalcBob(); Viewmodel_CalcBob();

View File

@ -15,6 +15,7 @@ client/func_dustmotes.qc
client/func_dustcloud.qc client/func_dustcloud.qc
client/func_smokevolume.qc client/func_smokevolume.qc
client/light_environment.qc client/light_environment.qc
client/prop_static.qc
client/infodecal.qc client/infodecal.qc
client/sky_camera.qc client/sky_camera.qc
client/info_notnull.qc client/info_notnull.qc

View File

@ -72,7 +72,7 @@ server/phys_keepupright.qc
server/phys_hinge.qc server/phys_hinge.qc
server/phys_slideconstraint.qc server/phys_slideconstraint.qc
//server/phys_constraintsystem.qc //server/phys_constraintsystem.qc
server/prop_static.qc //server/prop_static.qc
server/point_camera.qc server/point_camera.qc
server/point_servercommand.qc server/point_servercommand.qc
server/point_trigger.qc server/point_trigger.qc

View File

@ -36,7 +36,7 @@ A force-centered explosion, primarily targetted at physics objects and optionall
This entity was introduced in Half-Life 2 (2004). This entity was introduced in Half-Life 2 (2004).
*/ */
class class
env_physexplosion:NSPhysicsConstraint env_physexplosion:NSEntity
{ {
public: public:
void env_physexplosion(void); void env_physexplosion(void);

View File

@ -15,6 +15,7 @@
*/ */
static int g_ent_spawned; static int g_ent_spawned;
.bool gotData;
/** Called once every single tic on the server. */ /** Called once every single tic on the server. */
void void
@ -45,6 +46,7 @@ void
ClientConnect(void) ClientConnect(void)
{ {
int playercount = 0; int playercount = 0;
self.gotData = false;
/* don't carry over team settings from a previous session */ /* don't carry over team settings from a previous session */
forceinfokey(self, "*team", "0"); forceinfokey(self, "*team", "0");
@ -268,6 +270,11 @@ SV_RunClientCommand(void)
cl.SharedInputFrame(); cl.SharedInputFrame();
cl.ServerInputFrame(); cl.ServerInputFrame();
if (self.gotData == false) {
BreakModel_SendClientData(self);
self.gotData = true;
}
} }
/** Any 'cmd' from the client get sent here and handled. /** Any 'cmd' from the client get sent here and handled.
@ -390,18 +397,21 @@ initents(void)
precache_sound("misc/null.wav"); precache_sound("misc/null.wav");
precache_sound("common/null.wav"); precache_sound("common/null.wav");
Sound_Precache("player.gasplight"); Sound_Precache("player.GaspLight");
Sound_Precache("player.gaspheavy"); Sound_Precache("player.GaspHeavy");
Sound_Precache("player.waterenter"); Sound_Precache("player.WaterEnter");
Sound_Precache("player.waterexit"); Sound_Precache("player.WaterExit");
Sound_Precache("Player.Death");
Sound_Precache("Player.Pain");
Sound_Precache("Player.Wade");
Sound_Precache("Player.Swim");
Sound_Precache("Player.DenyWeaonSelection");
Sound_Precache("Player.WeaponSelected");
Sound_Precache("damage_bullet.hit"); Sound_Precache("damage_bullet.hit");
Sound_Precache("player.spraylogo"); Sound_Precache("player.spraylogo");
Sound_Precache("step_wade.left");
Sound_Precache("step_wade.right");
Sound_Precache("step_ladder.left"); Sound_Precache("step_ladder.left");
Sound_Precache("step_ladder.right"); Sound_Precache("step_ladder.right");
Sound_Precache("step_swim.left");
Sound_Precache("step_swim.right");
if (!g_grMode) { if (!g_grMode) {
Game_InitRules(); Game_InitRules();

View File

@ -1,5 +1,5 @@
#ifdef CLIENT #ifdef CLIENT
class NSDebris:NSRenderableEntity class NSDebris:NSPhysicsEntity
{ {
public: public:
void NSDebris(void); void NSDebris(void);

View File

@ -25,7 +25,7 @@ _NSPhysics_Log(string msg)
var float autocvar_phys_pushscale = 1.0f; var float autocvar_phys_pushscale = 1.0f;
var float autocvar_phys_impactforcescale = 1.0f; var float autocvar_phys_impactforcescale = 100.0f;
#ifdef CLIENT #ifdef CLIENT
var bool autocvar_r_showPhysicsInfo = false; var bool autocvar_r_showPhysicsInfo = false;
@ -91,17 +91,19 @@ private:
int m_iFlags; int m_iFlags;
float m_flInertiaScale; float m_flInertiaScale;
float m_flBuoyancyRatio; float m_flBuoyancyRatio;
bool m_bInvincible;
float m_flVolume;
/* performance sanity checks */ /* performance sanity checks */
vector m_vecPrevOrigin; vector m_vecPrevOrigin;
vector m_vecPrevAngles; vector m_vecPrevAngles;
float m_flCheckTime; float m_flCheckTime;
PREDICTED_FLOAT(m_flMass)
virtual void _TouchThink(void); virtual void _TouchThink(void);
#ifdef SERVER #ifdef SERVER
PREDICTED_VECTOR(m_vecNetAngles) PREDICTED_VECTOR(m_vecNetAngles)
PREDICTED_FLOAT_N(mass)
string m_strOnDamaged; string m_strOnDamaged;
#endif #endif
@ -127,6 +129,9 @@ public:
virtual void postdraw(void); virtual void postdraw(void);
#endif #endif
nonvirtual void _UpdateBuoyancy(void);
nonvirtual void _UpdateMass(void);
/** Sets the friction multiplier for this entity. Default is 1.0 */ /** Sets the friction multiplier for this entity. Default is 1.0 */
nonvirtual void SetFriction(float); nonvirtual void SetFriction(float);
/** Returns the friction multiplayer for this entity. */ /** Returns the friction multiplayer for this entity. */

View File

@ -16,6 +16,11 @@
.float max_angular; .float max_angular;
/* taken from VPhysics-Jolt */
const float InchesToMeters = 0.0254f;
const float MetersToInches = 1.0f / 0.0254f;
void void
NSPhysicsEntity::NSPhysicsEntity(void) NSPhysicsEntity::NSPhysicsEntity(void)
{ {
@ -30,17 +35,18 @@ NSPhysicsEntity::NSPhysicsEntity(void)
damp_linear = 1.0f; damp_linear = 1.0f;
damp_angular = 1.0f; damp_angular = 1.0f;
max_angular = -1.0f; max_angular = -1.0f;
m_flMass = 1.0f;
cvar_set("physics_ode_quadtree_depth", "10"); cvar_set("physics_ode_quadtree_depth", "5");
cvar_set("physics_ode_contactsurfacelayer", "0"); cvar_set("physics_ode_contactsurfacelayer", "0");
cvar_set("physics_ode_worldquickstep", "1"); cvar_set("physics_ode_worldquickstep", "1");
cvar_set("physics_ode_worldquickstep_iterations", "20"); cvar_set("physics_ode_worldquickstep_iterations", "20");
cvar_set("physics_ode_contact_mu", "-1"); cvar_set("physics_ode_contact_mu", "1");
cvar_set("physics_ode_contact_erp", "0.96"); cvar_set("physics_ode_contact_erp", "0.96");
cvar_set("physics_ode_contact_cfm", "0.001"); cvar_set("physics_ode_contact_cfm", "0.001");
cvar_set("physics_ode_world_damping", "1"); cvar_set("physics_ode_world_damping", "1");
cvar_set("physics_ode_world_damping_linear", "-1"); cvar_set("physics_ode_world_damping_linear", "-1");
cvar_set("physics_ode_world_damping_linear_threshold", "-1"); cvar_set("physics_ode_world_damping_linear_threshold", "-1");
cvar_set("physics_ode_world_damping_angular", "-1"); cvar_set("physics_ode_world_damping_angular", "-1");
cvar_set("physics_ode_world_damping_angular_threshold", "-1"); cvar_set("physics_ode_world_damping_angular_threshold", "-1");
@ -179,7 +185,7 @@ NSPhysicsEntity::EvaluateEntity(void)
EVALUATE_FIELD(m_flBoneControl3, RDENT_CHANGED_CONTROLLER) EVALUATE_FIELD(m_flBoneControl3, RDENT_CHANGED_CONTROLLER)
EVALUATE_FIELD(m_flBoneControl4, RDENT_CHANGED_CONTROLLER) EVALUATE_FIELD(m_flBoneControl4, RDENT_CHANGED_CONTROLLER)
EVALUATE_FIELD(m_flBoneControl5, RDENT_CHANGED_CONTROLLER) EVALUATE_FIELD(m_flBoneControl5, RDENT_CHANGED_CONTROLLER)
EVALUATE_FIELD(mass, RDENT_CHANGED_SOLIDMOVETYPE) EVALUATE_FIELD(m_flMass, RDENT_CHANGED_SOLIDMOVETYPE)
} }
float float
@ -269,7 +275,7 @@ NSPhysicsEntity::SendEntity(entity ePEnt, float flChanged)
SENDENTITY_ANGLE(m_flBoneControl5, RDENT_CHANGED_CONTROLLER) SENDENTITY_ANGLE(m_flBoneControl5, RDENT_CHANGED_CONTROLLER)
/* physics specific flags */ /* physics specific flags */
SENDENTITY_FLOAT(mass, RDENT_CHANGED_SOLIDMOVETYPE) SENDENTITY_FLOAT(m_flMass, RDENT_CHANGED_SOLIDMOVETYPE)
return (1); return (1);
} }
@ -333,7 +339,7 @@ NSPhysicsEntity::ReceiveEntity(float flNew, float flChanged)
READENTITY_ANGLE(m_flBoneControl5, RDENT_CHANGED_CONTROLLER) READENTITY_ANGLE(m_flBoneControl5, RDENT_CHANGED_CONTROLLER)
/* physics specific flags */ /* physics specific flags */
READENTITY_FLOAT(mass, RDENT_CHANGED_SOLIDMOVETYPE) READENTITY_FLOAT(m_flMass, RDENT_CHANGED_SOLIDMOVETYPE)
if (scale == 0.0) if (scale == 0.0)
scale = 1.0f; scale = 1.0f;
@ -344,6 +350,7 @@ NSPhysicsEntity::ReceiveEntity(float flNew, float flChanged)
_UpdateGeomset(); _UpdateGeomset();
movetype = MOVETYPE_NONE; movetype = MOVETYPE_NONE;
_UpdateMass();
} }
void void
@ -355,8 +362,11 @@ NSPhysicsEntity::postdraw(void)
if not (PointMessage_Visible(WorldSpaceCenter(), g_view.GetCameraOrigin(), g_view.GetCameraAngle())) if not (PointMessage_Visible(WorldSpaceCenter(), g_view.GetCameraOrigin(), g_view.GetCameraAngle()))
return; return;
string renderString = sprintf("Mass: %f\nEnergy: %f\nInertia:%d", float massVolume = (size[0] * size[1] * size[2]);
GetMass(), GetEnergy(), GetInertia()); massVolume *= 0.0254f; /* from meters to inches */
string renderString = sprintf("Mass: %.2f kg (%.2f ODE)\nEnergy: %f kj\nInertia:%d\nVolume: %.2f",
GetMass(), mass, GetEnergy(), GetInertia(), massVolume);
PointMessage_StringAtPos(WorldSpaceCenter(), renderString); PointMessage_StringAtPos(WorldSpaceCenter(), renderString);
} }
@ -530,9 +540,10 @@ NSPhysicsEntity::Pain(void)
if (force > 0.0f) if (force > 0.0f)
ApplyForceOffset(forceDir * force, g_dmg_vecLocation); ApplyForceOffset(forceDir * force, g_dmg_vecLocation);
/* if we don't have prop data, don't consider death */ /* HACK: */
if (HasPropData() == false) if (m_bInvincible) {
health = 10000; health = 10000;
}
/* make sure touch think is called */ /* make sure touch think is called */
nextthink = time; nextthink = time;
@ -547,7 +558,11 @@ NSPhysicsEntity::Death(void)
if (takedamage != DAMAGE_YES) { if (takedamage != DAMAGE_YES) {
takedamage = (DAMAGE_YES); takedamage = (DAMAGE_YES);
} }
health = 1000;
/* HACK: */
if (m_bInvincible) {
health = 10000;
}
/* make sure touch think is called */ /* make sure touch think is called */
nextthink = time; nextthink = time;
@ -567,22 +582,22 @@ NSPhysicsEntity::Respawn(void)
#endif #endif
Sleep(); Sleep();
SetMass(10.0f); SetMass(1.0f);
SetFriction(1.0f); SetFriction(1.0f);
SetBuoyancyRatio(1.0f); SetBuoyancyRatio(1.0f);
bouncefactor = 0.9f; bouncefactor = 0.9f;
bouncestop = 0.1f / cvar("sv_gravity"); bouncestop = 0.1f / cvar("sv_gravity");
//bouncefactor = 0.0f; bouncefactor = 0.0f;
//bouncestop = 0.0f; bouncestop = 0.0f;
geomtype = GEOMTYPE_TRIMESH; geomtype = GEOMTYPE_TRIMESH;
friction = 100.0f; friction = 1.0f;
SetOrigin(GetSpawnOrigin()); SetOrigin(GetSpawnOrigin());
m_flBuoyancyRatio = 1.0f; m_flBuoyancyRatio = 1.0f;
SetDamping(0.0f, 0.0f); SetDamping(1.0f, 1.0f);
EnableGravity(true); EnableGravity(true);
hitcontentsmaski &= ~CONTENTBITS_FLUID; hitcontentsmaski &= ~CONTENTBITS_FLUID;
@ -600,9 +615,25 @@ NSPhysicsEntity::Respawn(void)
#ifdef SERVER #ifdef SERVER
if (HasPropData()) { if (HasPropData()) {
health = GetPropData(PROPINFO_HEALTH); SetHealth(GetPropData(PROPINFO_HEALTH));
SetMass(GetPropData(PROPINFO_MASS));
SetDamping(GetPropData(PROPINFO_DAMPING_LINEAR), GetPropData(PROPINFO_DAMPING_ANGULAR));
SetInertia(GetPropData(PROPINFO_INERTIA));
SetSurfaceData(GetPropData(PROPINFO_SURFACEPROP));
} else { } else {
health = 100000; health = 0;
}
/* no health set, either indistructible or too weak */
if (health == 0) {
/* it has no breakmodels set, therefore it cannot break. */
if (GetPropData(PROPINFO_BREAKMODEL) == __NULL__) {
m_bInvincible = true;
health = 10000;
} else {
/* something like glass bottles, maybe. */
health = 1.0f;
}
} }
#endif #endif
} }
@ -666,6 +697,8 @@ NSPhysicsEntity::ApplyForceCenter(vector vecForce)
{ {
Wake(); Wake();
vecForce *= MetersToInches;
if (physics_supported() == TRUE) { if (physics_supported() == TRUE) {
vector finalForce = vecForce; vector finalForce = vecForce;
physics_addforce(this, finalForce, GetMassCenter()); physics_addforce(this, finalForce, GetMassCenter());
@ -685,6 +718,8 @@ NSPhysicsEntity::ApplyForceOffset(vector vecForce, vector vecOffset)
{ {
Wake(); Wake();
vecForce *= MetersToInches;
if (physics_supported() == TRUE) { if (physics_supported() == TRUE) {
vector finalForce = vecForce; vector finalForce = vecForce;
physics_addforce(this, finalForce, vecOffset); physics_addforce(this, finalForce, vecOffset);
@ -767,7 +802,7 @@ NSPhysicsEntity::GetEnergy(void)
rotationalEnergy = (0.5f * GetMass() * vlen(GetAngularVelocity())); rotationalEnergy = (0.5f * GetMass() * vlen(GetAngularVelocity()));
rotationalEnergy *= rotationalEnergy; rotationalEnergy *= rotationalEnergy;
return linearEnergy + rotationalEnergy; return (linearEnergy + rotationalEnergy) / (InchesToMeters * InchesToMeters);
} }
float float
@ -791,7 +826,7 @@ NSPhysicsEntity::GetInvMass(void)
float float
NSPhysicsEntity::GetMass(void) NSPhysicsEntity::GetMass(void)
{ {
return mass; return m_flMass;
} }
vector vector
@ -897,10 +932,39 @@ NSPhysicsEntity::SetInertia(float val)
m_flInertiaScale = val; m_flInertiaScale = val;
} }
void
NSPhysicsEntity::_UpdateMass(void)
{
/* in ODE, mass is relative. */
float massVolume = (size[0] * size[1] * size[2]);
massVolume *= 0.0254f; /* from inches to meters */
mass = 1.0f;
//mass = m_flMass / massVolume; /* really ODE's thing is density */
}
void
NSPhysicsEntity::_UpdateBuoyancy(void)
{
#if 0
if ( m_flVolume != 0.0f )
{
float flVolume = max( m_flVolume, 5.0f ) * MeterstoInches * MeterstoInches * MeterstoInches;
float flDensity = m_flMass / flVolume;
m_flBuoyancyRatio = flDensity / m_flMaterialDensity;
}
else
{
m_flBuoyancyRatio = 1.0f;
}
#endif
}
void void
NSPhysicsEntity::SetMass(float val) NSPhysicsEntity::SetMass(float val)
{ {
mass = val; m_flMass = val;
_UpdateMass();
} }
void void

View File

@ -659,9 +659,6 @@ NSSurfacePropEntity::Death(void)
{ {
m_flDeathTime = time; m_flDeathTime = time;
print(sprintf("%S", m_strOnBreak));
print("\n");
UseOutput(g_dmg_eAttacker, m_strOnBreak); UseOutput(g_dmg_eAttacker, m_strOnBreak);
if (HasPropData() == false) if (HasPropData() == false)
@ -672,7 +669,8 @@ NSSurfacePropEntity::Death(void)
} else if (GetPropData(PROPINFO_BREAKMODEL) != __NULL__) { } else if (GetPropData(PROPINFO_BREAKMODEL) != __NULL__) {
string gibeffect = GetPropData(PROPINFO_BREAKMODEL); string gibeffect = GetPropData(PROPINFO_BREAKMODEL);
int breakcount = GetPropData(PROPINFO_BREAKCOUNT); int breakcount = GetPropData(PROPINFO_BREAKCOUNT);
BreakModel_Spawn(absmin, absmax, [0,0,0], 100, breakcount, gibeffect); BreakModel_Entity(this, normalize(GetOrigin() - g_dmg_vecLocation), g_dmg_iDamage);
//BreakModel_Spawn(absmin, absmax, [0,0,0], 100, breakcount, gibeffect);
Disappear(); Disappear();
} else { } else {
Disappear(); Disappear();

View File

@ -377,6 +377,29 @@ string Util_FixModel(string mdl)
return mdl; return mdl;
} }
/** Returns a string (usually filename) with only the file extension
at the end replaced with a given, new extension. */
string
Util_ChangeExtension(string baseString, string newExtension)
{
float stringOffset = 0;
string tempString = "";
float foundOffset = 0;
while ((tempString = substring(baseString, stringOffset, 1))) {
if (tempString == ".")
foundOffset = stringOffset;
if (tempString == "")
break;
if not (tempString)
break;
stringOffset++;
}
return strcat(substring(baseString, 0, foundOffset), ".", newExtension);
}
bool bool
Util_IsSingleplayer(void) Util_IsSingleplayer(void)
{ {

View File

@ -54,6 +54,7 @@ enum
EV_SURFIMPACT, EV_SURFIMPACT,
EV_SURFIMPACTID, EV_SURFIMPACTID,
EV_DECALGROUP, EV_DECALGROUP,
EV_BREAKMODELDATA,
EV_BREAKMODEL, EV_BREAKMODEL,
EV_BEAMCYLINDER, EV_BEAMCYLINDER,
EV_MUZZLEFLASH, EV_MUZZLEFLASH,

View File

@ -34,6 +34,4 @@ typedef enum
void PMove_StartFrame(void); void PMove_StartFrame(void);
#endif #endif
void PMove_Init(void); void PMove_Init(void);
void PMove_Run(void);

View File

@ -132,6 +132,7 @@ var float autocvar_pm_wateraccelerate = PMOVE_WATERACCELERATE;
var float autocvar_pm_accelerate = PMOVE_ACCELERATE; var float autocvar_pm_accelerate = PMOVE_ACCELERATE;
var float autocvar_pm_maxspeed = PMOVE_MAXSPEED; var float autocvar_pm_maxspeed = PMOVE_MAXSPEED;
var float autocvar_g_gravity = PMOVE_GRAVITY; var float autocvar_g_gravity = PMOVE_GRAVITY;
var bool autocvar_pm_nospeedcap = false;
void void
PMove_Init(void) PMove_Init(void)
@ -167,7 +168,7 @@ PMove_StartFrame(void)
#endif #endif
/* simple bounds check */ /* simple bounds check */
int bool
PMove_IsStuck(entity eTarget, vector vOffset, vector vecMins, vector vecMaxs) PMove_IsStuck(entity eTarget, vector vOffset, vector vecMins, vector vecMaxs)
{ {
vector bound; vector bound;
@ -180,14 +181,3 @@ PMove_IsStuck(entity eTarget, vector vOffset, vector vecMins, vector vecMaxs)
tracebox(bound, vecMins, vecMaxs, bound, MOVE_NORMAL, eTarget); tracebox(bound, vecMins, vecMaxs, bound, MOVE_NORMAL, eTarget);
return trace_startsolid; return trace_startsolid;
} }
/* it all starts here, this function is called by both CLIENT and SERVER for
obvious prediction purposes. The SERVER will usually do this in the
Game_RunClientCommand function and the CLIENT will do so in both the
prediction places of Predict_PreFrame and Player_ReceiveEntity */
void
PMove_Run(void)
{
player pl = (player)self;
pl.Physics_Run();
}

View File

@ -83,6 +83,12 @@ typedef struct
string breakable_model; /* name of BreakableModels entry in PropData.txt */ string breakable_model; /* name of BreakableModels entry in PropData.txt */
int breakable_count; int breakable_count;
float breakable_skin; float breakable_skin;
float mass;
float damping_linear;
float damping_angular;
float inertia;
float volume;
string surfaceprop;
} propdata_t; } propdata_t;
/* entity will have to have a .propdata field pointing to a propdata id */ /* entity will have to have a .propdata field pointing to a propdata id */
@ -113,7 +119,14 @@ typedef enum
PROPINFO_EXPLOSIVE_RADIUS, PROPINFO_EXPLOSIVE_RADIUS,
PROPINFO_BREAKMODEL, PROPINFO_BREAKMODEL,
PROPINFO_BREAKCOUNT, PROPINFO_BREAKCOUNT,
PROPINFO_SKIN PROPINFO_SKIN,
/* physics related variables. */
PROPINFO_MASS,
PROPINFO_DAMPING_LINEAR,
PROPINFO_DAMPING_ANGULAR,
PROPINFO_INERTIA,
PROPINFO_VOLUME,
PROPINFO_SURFACEPROP
} propinfo_t; } propinfo_t;
__variant Prop_GetInfo(int, int); __variant Prop_GetInfo(int, int);
@ -121,20 +134,25 @@ typedef struct
{ {
string name; string name;
string data; string data;
float modelindex; /* only used for networking */
bool physics; /* differentiate between Source and GS */
} breakmodel_t; } breakmodel_t;
/* entity will have a .breakmodel field pointing to a breakmodel id */ /* entity will have a .breakmodel field pointing to a breakmodel id */
breakmodel_t *g_breakmodel; breakmodel_t *g_breakmodel;
int g_breakmodel_count; int g_breakmodel_count;
int g_breakmodel_end;
var hashtable g_hashbreakmodel; var hashtable g_hashbreakmodel;
#ifdef CLIENT #ifdef CLIENT
void BreakModel_SpawnID(vector smins, vector smaxs, vector dir, float speed, int count, int index); void BreakModel_SpawnID(vector smins, vector smaxs, vector dir, float speed, int count, int index);
void BreakModel_Receive(void); void BreakModel_Receive(void);
void BreakModel_ReceiveClientData(void);
#else #else
void BreakModel_Spawn(vector pos, vector dir, vector spread, float speed, int count, string type); void BreakModel_Spawn(vector pos, vector dir, vector spread, float speed, int count, string type);
void BreakModel_Entity(NSSurfacePropEntity target, vector dir, float speed); void BreakModel_Entity(NSSurfacePropEntity target, vector dir, float speed);
void BreakModel_SendClientData(entity);
#endif #endif
/* necessary API functions */ /* necessary API functions */

View File

@ -71,6 +71,18 @@ Prop_GetInfo(int i, int type)
return (__variant)g_propdata[i].breakable_count; return (__variant)g_propdata[i].breakable_count;
case PROPINFO_SKIN: case PROPINFO_SKIN:
return (__variant)g_propdata[i].breakable_skin; return (__variant)g_propdata[i].breakable_skin;
case PROPINFO_MASS:
return (__variant)g_propdata[i].mass;
case PROPINFO_DAMPING_LINEAR:
return (__variant)g_propdata[i].damping_linear;
case PROPINFO_DAMPING_ANGULAR:
return (__variant)g_propdata[i].damping_angular;
case PROPINFO_INERTIA:
return (__variant)g_propdata[i].inertia;
case PROPINFO_VOLUME:
return (__variant)g_propdata[i].volume;
case PROPINFO_SURFACEPROP:
return (__variant)g_propdata[i].surfaceprop;
default: default:
return __NULL__; return __NULL__;
} }
@ -80,6 +92,9 @@ void
PropData_ParseField(int i, int a) PropData_ParseField(int i, int a)
{ {
switch (argv(0)) { switch (argv(0)) {
case "name":
g_propdata[i].name = argv(1);
break;
case "base": case "base":
g_propdata[i].base = argv(1); g_propdata[i].base = argv(1);
break; break;
@ -122,6 +137,24 @@ PropData_ParseField(int i, int a)
case "breakable_skin": case "breakable_skin":
g_propdata[i].breakable_skin = stof(argv(1)); g_propdata[i].breakable_skin = stof(argv(1));
break; break;
case "mass":
g_propdata[i].mass = stof(argv(1));
break;
case "damping":
g_propdata[i].damping_linear = stof(argv(1));
break;
case "rotdamping":
g_propdata[i].damping_angular = stof(argv(1));
break;
case "inertia":
g_propdata[i].inertia = stof(argv(1));
break;
case "volume":
g_propdata[i].volume = stof(argv(1));
break;
case "surfaceprop":
g_propdata[i].surfaceprop = argv(1);
break;
} }
} }
@ -179,6 +212,62 @@ PropData_Parse(int i, string line, string type)
return (0); return (0);
} }
/* specific to parsing strings from binary .phy files */
int
PropData_ParsePhyFile(int i, string line, string type)
{
int c;
string key;
static string t_name;
static int braced = 0i;
static string gibModel = "";
static string gibFadeTime = "";
c = tokenize(line);
for (int x = 0i; x < c; x++) {
key = argv(x);
switch(key) {
case "{":
braced++;
break;
case "}":
if (braced == 1i && t_name == "break") {
int bID = g_breakmodel_count; /* !!! increment after loading phy file! */
g_breakmodel[bID].modelindex = getmodelindex(type);
g_breakmodel[bID].name = g_propdata[i].name;
g_breakmodel[bID].data = sprintf("%s%S %S\n", g_breakmodel[bID].data, gibModel, gibFadeTime);
hash_add(g_hashbreakmodel, g_breakmodel[bID].name, (int)bID);
g_propdata[i].breakable_model = g_breakmodel[bID].name;
g_propdata[i].breakable_count++;
gibModel = "";
gibFadeTime = 0.0f;
}
braced--;
t_name = "";
break;
default:
if (braced == 1i && t_name == "solid") {
PropData_ParseField(i, c);
} else if (braced == 1i && t_name == "break") {
switch (key) {
case "model":
gibModel = strcat("models/", argv(x+1), ".mdl");
break;
case "fadetime":
gibFadeTime = argv(x+1);
break;
}
} else if (braced == 0i) {
t_name = strtolower(key);
}
}
}
return (0);
}
/* goes through and looks for a specifically named propdata type inside the scripts dir */ /* goes through and looks for a specifically named propdata type inside the scripts dir */
int int
PropData_ForModel(string modelname) PropData_ForModel(string modelname)
@ -196,9 +285,9 @@ PropData_ForModel(string modelname)
index = g_propdata_count; index = g_propdata_count;
modelname = strtolower(modelname); modelname = strtolower(modelname);
dprint("[PROPDATA] Loading model propdata "); print("[PROPDATA] Loading model propdata ");
dprint(modelname); print(modelname);
dprint("\n"); print("\n");
/* create the hash-table if it doesn't exist */ /* create the hash-table if it doesn't exist */
if (!g_hashpropdata) { if (!g_hashpropdata) {
@ -220,7 +309,8 @@ PropData_ForModel(string modelname)
g_propdata = (propdata_t *)memrealloc(g_propdata, sizeof(propdata_t), index, g_propdata_count); g_propdata = (propdata_t *)memrealloc(g_propdata, sizeof(propdata_t), index, g_propdata_count);
#else #else
if (g_propdata_count >= PROPDATA_MAX) { if (g_propdata_count >= PROPDATA_MAX) {
error(sprintf("PropData_ForModel: Reached PROPDATA_MAX (%d)\n", PROPDATA_MAX)); printf(sprintf("PropData_ForModel: Reached PROPDATA_MAX (%d)\n", PROPDATA_MAX));
return -1i;
} }
#endif #endif
@ -228,9 +318,75 @@ PropData_ForModel(string modelname)
fh = fopen(strcat(modelname, ".propdata"), FILE_READ); fh = fopen(strcat(modelname, ".propdata"), FILE_READ);
if (fh < 0) { if (fh < 0) {
g_propdata_count--; /* try the Source Engine version */
NSLog("Can't find propdata for model %s", modelname); fh = fopen(Util_ChangeExtension(modelname, "phy"), FILE_READ);
return -1;
if (fh < 0) {
g_propdata_count--;
NSLog("Can't find propdata for model %s", modelname);
return -1;
}
int fileSize;
int phyID;
int numSolids;
int fileSum;
int filePos = 0i;
int surfaceSize;
filePos = fread(fh, (void*)&fileSize, 4i); /* header size, sanity check */
if (fileSize != 16i) {
error("Only .phy files from Source are supported.");
}
filePos = fread(fh, (void*)&phyID, 4i); /* some header id */
filePos = fread(fh, (void*)&numSolids, 4i); /* read our number of solids. */
filePos = fread(fh, (void*)&fileSum, 4i);
//print(sprintf("num fileSize: %i\n", fileSize));
//print(sprintf("num phyID: %i\n", phyID));
//print(sprintf("num numSolids: %i\n", numSolids));
//print(sprintf("num fileSum: %i\n", fileSum));
/* HACK: We won't support ragdolls, for now. */
if (numSolids > 1)
return -1;
/* we skip over all these to get to the bottom of the file */
for (int i = 0i; i < numSolids; i++) {
filePos = fread(fh, (void*)&surfaceSize, 4);
filePos = fseek(fh, filePos + surfaceSize + 16);
}
/* now comes the propdata */
while ((line = fgets(fh))) {
//printf(line);
//print("\n");
PropData_ParsePhyFile(index, line, modelname);
}
/* push up the breakmodel count by one if we've written into it */
{
string breakModel = Prop_GetInfo(index, PROPINFO_BREAKMODEL);
if (breakModel != "") {
int bID = (int)hash_get(g_hashbreakmodel, Prop_GetInfo(index, PROPINFO_BREAKMODEL), -1);
if (bID != -1) {
if (g_breakmodel[bID].data) {
g_breakmodel[bID].physics = true;
g_breakmodel_count++;
}
}
}
}
//print(sprintf("Added %S at id %i with name %S\n", modelname, index, g_propdata[index].name));
hash_add(g_hashpropdata, modelname, (int)index);
return index;
//error(sprintf("phy file (size %i): size: %i id: %i numSolids: %i\n", fsize(fh), fileSize, phyID, numSolids));
} }
while ((line = fgets(fh))) { while ((line = fgets(fh))) {
/* when we found it, quit */ /* when we found it, quit */
@ -456,8 +612,13 @@ PropData_Init(void)
/* gotta tokenize our inputs again */ /* gotta tokenize our inputs again */
x = tokenize(g_breakmodel[i].data); x = tokenize(g_breakmodel[i].data);
} }
/* We're making assumptions here, but most physics props have their
breakmodels defined along the model. */
g_breakmodel[i].physics = false;
} }
g_breakmodel_end = g_breakmodel_count;
print("PropData initialized.\n"); print("PropData initialized.\n");
} }
@ -491,6 +652,9 @@ BreakModel_SpawnID(vector smins, vector smaxs, vector dir, float speed, int coun
{ {
float x = tokenize(g_breakmodel[index].data); float x = tokenize(g_breakmodel[index].data);
int modelcount = x / 2; int modelcount = x / 2;
bool usePhysics = g_breakmodel[index].physics;
//print(sprintf("breaking into %i models\n", modelcount));
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
vector endpos; vector endpos;
@ -498,7 +662,6 @@ BreakModel_SpawnID(vector smins, vector smaxs, vector dir, float speed, int coun
string fullline; string fullline;
float fadetime; float fadetime;
NSDebris gib; NSDebris gib;
int r;
int p; int p;
int bodygroup = 0; int bodygroup = 0;
vector rendercolor = [1,1,1]; vector rendercolor = [1,1,1];
@ -507,12 +670,17 @@ BreakModel_SpawnID(vector smins, vector smaxs, vector dir, float speed, int coun
float renderfx = RFX_NORMAL; float renderfx = RFX_NORMAL;
string impactDecal = __NULL__; string impactDecal = __NULL__;
/* pick a model between 0 and num) */
r = floor(random(0, modelcount));
/* two entries, always have to skip by 2 */ /* two entries, always have to skip by 2 */
fullline = mname = argv((r * 2)); if (modelcount < count) {
fadetime = stof(argv((r * 2) + 1)); int r = floor(random(0, modelcount));
fullline = mname = argv((r * 2));
fadetime = stof(argv((r * 2) + 1));
} else {
fullline = mname = argv((i * 2));
fadetime = stof(argv((i * 2) + 1));
}
p = tokenizebyseparator(mname, "#"); p = tokenizebyseparator(mname, "#");
/* special char # detected to designate model submodel count */ /* special char # detected to designate model submodel count */
@ -572,16 +740,30 @@ BreakModel_SpawnID(vector smins, vector smaxs, vector dir, float speed, int coun
gib.SetRenderFX(renderfx); gib.SetRenderFX(renderfx);
gib.SetSize([0,0,0], [0,0,0]); gib.SetSize([0,0,0], [0,0,0]);
gib.SetOrigin(endpos); gib.SetOrigin(endpos);
makevectors(dir);
gib.velocity = (v_forward * speed) * 0.75; if (usePhysics == false) {
gib.velocity[0] += (random() - 0.5) * (speed * 0.25); makevectors(dir);
gib.velocity[1] += (random() - 0.5) * (speed * 0.25); gib.velocity = (v_forward * speed) * 0.75;
gib.velocity[2] += (random() - 0.5) * (speed * 0.25); gib.velocity[0] += (random() - 0.5) * (speed * 0.25);
gib.SetAngularVelocity(vectoangles(gib.velocity)); gib.velocity[1] += (random() - 0.5) * (speed * 0.25);
gib.SetMovetype(MOVETYPE_BOUNCE); gib.velocity[2] += (random() - 0.5) * (speed * 0.25);
gib.SetSolid(SOLID_BBOX); gib.SetAngularVelocity(vectoangles(gib.velocity));
gib.SetMovetype(MOVETYPE_BOUNCE);
gib.SetSolid(SOLID_BBOX);
} else {
gib.SetMovetype(MOVETYPE_PHYSICS);
gib.SetSolid(SOLID_NOT);
gib.mass = 1.0f;
gib.friction = 1.0f;
gib.bouncefactor = 0.9f;
gib.bouncestop = 0.1f / cvar("sv_gravity");
gib.geomtype = GEOMTYPE_TRIMESH;
gib.ApplyForceOffset(dir * speed, endpos);
}
gib.ScheduleThink(NSEntity::Destroy, fadetime); gib.ScheduleThink(NSEntity::Destroy, fadetime);
gib.SetImpactDecal(impactDecal); gib.SetImpactDecal(impactDecal);
print(sprintf("%S\n", mname));
#ifdef CLIENT #ifdef CLIENT
gib.drawmask = MASK_ENGINE; gib.drawmask = MASK_ENGINE;
@ -616,10 +798,49 @@ BreakModel_Receive(void)
speed = readfloat(); speed = readfloat();
count = readbyte(); count = readbyte();
/* sanity check */
if (index >= g_breakmodel_count) {
print(sprintf("^1Unable to spawn breakmodel of id %i. Not cached on client. (Client knows of %i)\n", index, g_breakmodel_count));
return;
}
//print(sprintf("i: %i max: %i name: %S\n", index, g_breakmodel_count, g_breakmodel[index].name));
BreakModel_SpawnID(smins, smaxs, dir, speed, count, index); BreakModel_SpawnID(smins, smaxs, dir, speed, count, index);
} }
void
BreakModel_ReceiveClientData(void)
{
int addToCount = readbyte();
for (int i = 0; i < addToCount; i++) {
string modelName = modelnameforindex(readshort());
PropData_ForModel(modelName);
}
}
#else #else
void
BreakModel_SendClientData(entity targetEnt)
{
int extraCount = g_breakmodel_count - g_breakmodel_end;
int startOffset = g_breakmodel_end;
WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET);
WriteByte(MSG_MULTICAST, EV_BREAKMODELDATA);
WriteByte(MSG_MULTICAST, g_breakmodel_count - g_breakmodel_end);
//print(sprintf("%i %i\n", g_breakmodel_end, g_breakmodel_count));
for (int i = g_breakmodel_end; i < g_breakmodel_count; i++) {
WriteShort(MSG_MULTICAST, g_breakmodel[i].modelindex);
}
msg_entity = targetEnt;
multicast(g_vec_null, MULTICAST_ONE_R);
}
void void
BreakModel_Spawn(vector smins, vector smaxs, vector dir, float speed, int count, string type) BreakModel_Spawn(vector smins, vector smaxs, vector dir, float speed, int count, string type)
{ {