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.
- **breakable_model <string>**: Which models to spawn when it breaks.
- **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:
EV_Shake();
break;
case EV_BREAKMODELDATA:
BreakModel_ReceiveClientData();
break;
case EV_BREAKMODEL:
BreakModel_Receive();
break;

View File

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

View File

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

View File

@ -72,7 +72,7 @@ server/phys_keepupright.qc
server/phys_hinge.qc
server/phys_slideconstraint.qc
//server/phys_constraintsystem.qc
server/prop_static.qc
//server/prop_static.qc
server/point_camera.qc
server/point_servercommand.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).
*/
class
env_physexplosion:NSPhysicsConstraint
env_physexplosion:NSEntity
{
public:
void env_physexplosion(void);

View File

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

View File

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

View File

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

View File

@ -16,6 +16,11 @@
.float max_angular;
/* taken from VPhysics-Jolt */
const float InchesToMeters = 0.0254f;
const float MetersToInches = 1.0f / 0.0254f;
void
NSPhysicsEntity::NSPhysicsEntity(void)
{
@ -30,17 +35,18 @@ NSPhysicsEntity::NSPhysicsEntity(void)
damp_linear = 1.0f;
damp_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_worldquickstep", "1");
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_cfm", "0.001");
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_angular", "-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_flBoneControl4, RDENT_CHANGED_CONTROLLER)
EVALUATE_FIELD(m_flBoneControl5, RDENT_CHANGED_CONTROLLER)
EVALUATE_FIELD(mass, RDENT_CHANGED_SOLIDMOVETYPE)
EVALUATE_FIELD(m_flMass, RDENT_CHANGED_SOLIDMOVETYPE)
}
float
@ -269,7 +275,7 @@ NSPhysicsEntity::SendEntity(entity ePEnt, float flChanged)
SENDENTITY_ANGLE(m_flBoneControl5, RDENT_CHANGED_CONTROLLER)
/* physics specific flags */
SENDENTITY_FLOAT(mass, RDENT_CHANGED_SOLIDMOVETYPE)
SENDENTITY_FLOAT(m_flMass, RDENT_CHANGED_SOLIDMOVETYPE)
return (1);
}
@ -333,7 +339,7 @@ NSPhysicsEntity::ReceiveEntity(float flNew, float flChanged)
READENTITY_ANGLE(m_flBoneControl5, RDENT_CHANGED_CONTROLLER)
/* physics specific flags */
READENTITY_FLOAT(mass, RDENT_CHANGED_SOLIDMOVETYPE)
READENTITY_FLOAT(m_flMass, RDENT_CHANGED_SOLIDMOVETYPE)
if (scale == 0.0)
scale = 1.0f;
@ -344,6 +350,7 @@ NSPhysicsEntity::ReceiveEntity(float flNew, float flChanged)
_UpdateGeomset();
movetype = MOVETYPE_NONE;
_UpdateMass();
}
void
@ -355,8 +362,11 @@ NSPhysicsEntity::postdraw(void)
if not (PointMessage_Visible(WorldSpaceCenter(), g_view.GetCameraOrigin(), g_view.GetCameraAngle()))
return;
string renderString = sprintf("Mass: %f\nEnergy: %f\nInertia:%d",
GetMass(), GetEnergy(), GetInertia());
float massVolume = (size[0] * size[1] * size[2]);
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);
}
@ -530,9 +540,10 @@ NSPhysicsEntity::Pain(void)
if (force > 0.0f)
ApplyForceOffset(forceDir * force, g_dmg_vecLocation);
/* if we don't have prop data, don't consider death */
if (HasPropData() == false)
/* HACK: */
if (m_bInvincible) {
health = 10000;
}
/* make sure touch think is called */
nextthink = time;
@ -547,7 +558,11 @@ NSPhysicsEntity::Death(void)
if (takedamage != DAMAGE_YES) {
takedamage = (DAMAGE_YES);
}
health = 1000;
/* HACK: */
if (m_bInvincible) {
health = 10000;
}
/* make sure touch think is called */
nextthink = time;
@ -567,22 +582,22 @@ NSPhysicsEntity::Respawn(void)
#endif
Sleep();
SetMass(10.0f);
SetMass(1.0f);
SetFriction(1.0f);
SetBuoyancyRatio(1.0f);
bouncefactor = 0.9f;
bouncestop = 0.1f / cvar("sv_gravity");
//bouncefactor = 0.0f;
//bouncestop = 0.0f;
bouncefactor = 0.0f;
bouncestop = 0.0f;
geomtype = GEOMTYPE_TRIMESH;
friction = 100.0f;
friction = 1.0f;
SetOrigin(GetSpawnOrigin());
m_flBuoyancyRatio = 1.0f;
SetDamping(0.0f, 0.0f);
SetDamping(1.0f, 1.0f);
EnableGravity(true);
hitcontentsmaski &= ~CONTENTBITS_FLUID;
@ -600,9 +615,25 @@ NSPhysicsEntity::Respawn(void)
#ifdef SERVER
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 {
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
}
@ -666,6 +697,8 @@ NSPhysicsEntity::ApplyForceCenter(vector vecForce)
{
Wake();
vecForce *= MetersToInches;
if (physics_supported() == TRUE) {
vector finalForce = vecForce;
physics_addforce(this, finalForce, GetMassCenter());
@ -685,6 +718,8 @@ NSPhysicsEntity::ApplyForceOffset(vector vecForce, vector vecOffset)
{
Wake();
vecForce *= MetersToInches;
if (physics_supported() == TRUE) {
vector finalForce = vecForce;
physics_addforce(this, finalForce, vecOffset);
@ -767,7 +802,7 @@ NSPhysicsEntity::GetEnergy(void)
rotationalEnergy = (0.5f * GetMass() * vlen(GetAngularVelocity()));
rotationalEnergy *= rotationalEnergy;
return linearEnergy + rotationalEnergy;
return (linearEnergy + rotationalEnergy) / (InchesToMeters * InchesToMeters);
}
float
@ -791,7 +826,7 @@ NSPhysicsEntity::GetInvMass(void)
float
NSPhysicsEntity::GetMass(void)
{
return mass;
return m_flMass;
}
vector
@ -897,10 +932,39 @@ NSPhysicsEntity::SetInertia(float 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
NSPhysicsEntity::SetMass(float val)
{
mass = val;
m_flMass = val;
_UpdateMass();
}
void

View File

@ -659,9 +659,6 @@ NSSurfacePropEntity::Death(void)
{
m_flDeathTime = time;
print(sprintf("%S", m_strOnBreak));
print("\n");
UseOutput(g_dmg_eAttacker, m_strOnBreak);
if (HasPropData() == false)
@ -672,7 +669,8 @@ NSSurfacePropEntity::Death(void)
} else if (GetPropData(PROPINFO_BREAKMODEL) != __NULL__) {
string gibeffect = GetPropData(PROPINFO_BREAKMODEL);
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();
} else {
Disappear();

View File

@ -377,6 +377,29 @@ string Util_FixModel(string 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
Util_IsSingleplayer(void)
{

View File

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

View File

@ -34,6 +34,4 @@ typedef enum
void PMove_StartFrame(void);
#endif
void PMove_Init(void);
void PMove_Run(void);
void PMove_Init(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_maxspeed = PMOVE_MAXSPEED;
var float autocvar_g_gravity = PMOVE_GRAVITY;
var bool autocvar_pm_nospeedcap = false;
void
PMove_Init(void)
@ -167,7 +168,7 @@ PMove_StartFrame(void)
#endif
/* simple bounds check */
int
bool
PMove_IsStuck(entity eTarget, vector vOffset, vector vecMins, vector vecMaxs)
{
vector bound;
@ -180,14 +181,3 @@ PMove_IsStuck(entity eTarget, vector vOffset, vector vecMins, vector vecMaxs)
tracebox(bound, vecMins, vecMaxs, bound, MOVE_NORMAL, eTarget);
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 */
int breakable_count;
float breakable_skin;
float mass;
float damping_linear;
float damping_angular;
float inertia;
float volume;
string surfaceprop;
} propdata_t;
/* entity will have to have a .propdata field pointing to a propdata id */
@ -113,7 +119,14 @@ typedef enum
PROPINFO_EXPLOSIVE_RADIUS,
PROPINFO_BREAKMODEL,
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;
__variant Prop_GetInfo(int, int);
@ -121,20 +134,25 @@ typedef struct
{
string name;
string data;
float modelindex; /* only used for networking */
bool physics; /* differentiate between Source and GS */
} breakmodel_t;
/* entity will have a .breakmodel field pointing to a breakmodel id */
breakmodel_t *g_breakmodel;
int g_breakmodel_count;
int g_breakmodel_end;
var hashtable g_hashbreakmodel;
#ifdef CLIENT
void BreakModel_SpawnID(vector smins, vector smaxs, vector dir, float speed, int count, int index);
void BreakModel_Receive(void);
void BreakModel_ReceiveClientData(void);
#else
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_SendClientData(entity);
#endif
/* necessary API functions */

View File

@ -71,6 +71,18 @@ Prop_GetInfo(int i, int type)
return (__variant)g_propdata[i].breakable_count;
case PROPINFO_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:
return __NULL__;
}
@ -80,6 +92,9 @@ void
PropData_ParseField(int i, int a)
{
switch (argv(0)) {
case "name":
g_propdata[i].name = argv(1);
break;
case "base":
g_propdata[i].base = argv(1);
break;
@ -122,6 +137,24 @@ PropData_ParseField(int i, int a)
case "breakable_skin":
g_propdata[i].breakable_skin = stof(argv(1));
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);
}
/* 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 */
int
PropData_ForModel(string modelname)
@ -196,9 +285,9 @@ PropData_ForModel(string modelname)
index = g_propdata_count;
modelname = strtolower(modelname);
dprint("[PROPDATA] Loading model propdata ");
dprint(modelname);
dprint("\n");
print("[PROPDATA] Loading model propdata ");
print(modelname);
print("\n");
/* create the hash-table if it doesn't exist */
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);
#else
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
@ -228,9 +318,75 @@ PropData_ForModel(string modelname)
fh = fopen(strcat(modelname, ".propdata"), FILE_READ);
if (fh < 0) {
g_propdata_count--;
NSLog("Can't find propdata for model %s", modelname);
return -1;
/* try the Source Engine version */
fh = fopen(Util_ChangeExtension(modelname, "phy"), FILE_READ);
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))) {
/* when we found it, quit */
@ -456,8 +612,13 @@ PropData_Init(void)
/* gotta tokenize our inputs again */
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");
}
@ -491,6 +652,9 @@ BreakModel_SpawnID(vector smins, vector smaxs, vector dir, float speed, int coun
{
float x = tokenize(g_breakmodel[index].data);
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++) {
vector endpos;
@ -498,7 +662,6 @@ BreakModel_SpawnID(vector smins, vector smaxs, vector dir, float speed, int coun
string fullline;
float fadetime;
NSDebris gib;
int r;
int p;
int bodygroup = 0;
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;
string impactDecal = __NULL__;
/* pick a model between 0 and num) */
r = floor(random(0, modelcount));
/* two entries, always have to skip by 2 */
fullline = mname = argv((r * 2));
fadetime = stof(argv((r * 2) + 1));
if (modelcount < count) {
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, "#");
/* 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.SetSize([0,0,0], [0,0,0]);
gib.SetOrigin(endpos);
makevectors(dir);
gib.velocity = (v_forward * speed) * 0.75;
gib.velocity[0] += (random() - 0.5) * (speed * 0.25);
gib.velocity[1] += (random() - 0.5) * (speed * 0.25);
gib.velocity[2] += (random() - 0.5) * (speed * 0.25);
gib.SetAngularVelocity(vectoangles(gib.velocity));
gib.SetMovetype(MOVETYPE_BOUNCE);
gib.SetSolid(SOLID_BBOX);
if (usePhysics == false) {
makevectors(dir);
gib.velocity = (v_forward * speed) * 0.75;
gib.velocity[0] += (random() - 0.5) * (speed * 0.25);
gib.velocity[1] += (random() - 0.5) * (speed * 0.25);
gib.velocity[2] += (random() - 0.5) * (speed * 0.25);
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.SetImpactDecal(impactDecal);
print(sprintf("%S\n", mname));
#ifdef CLIENT
gib.drawmask = MASK_ENGINE;
@ -616,10 +798,49 @@ BreakModel_Receive(void)
speed = readfloat();
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);
}
void
BreakModel_ReceiveClientData(void)
{
int addToCount = readbyte();
for (int i = 0; i < addToCount; i++) {
string modelName = modelnameforindex(readshort());
PropData_ForModel(modelName);
}
}
#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
BreakModel_Spawn(vector smins, vector smaxs, vector dir, float speed, int count, string type)
{