diff --git a/Documentation/Prop_data.md b/Documentation/Prop_data.md index 6f4cca6e..713859f1 100644 --- a/Documentation/Prop_data.md +++ b/Documentation/Prop_data.md @@ -126,4 +126,15 @@ destruction. this entity breaks. - **breakable_model **: Which models to spawn when it breaks. - **breakable_count **: The amount of models it'll spawn upon - breaking. \ No newline at end of file + breaking. +- **surfaceprop **: 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 **: Mass of the object, in kilograms. +- **volume **: Volume of the object, in cubic meters. +- **inertia **: Inertia multiplier. +- **damping **: Linear movement damping multiplier. +- **rotdamping **: Angular movement damping multiplier. \ No newline at end of file diff --git a/src/client/event.qc b/src/client/event.qc index 9d7d6bf1..517f2738 100644 --- a/src/client/event.qc +++ b/src/client/event.qc @@ -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; diff --git a/src/client/view.qc b/src/client/view.qc index 0be6f059..76ea460c 100644 --- a/src/client/view.qc +++ b/src/client/view.qc @@ -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(); diff --git a/src/gs-entbase/client.src b/src/gs-entbase/client.src index 72b5566d..607cf521 100644 --- a/src/gs-entbase/client.src +++ b/src/gs-entbase/client.src @@ -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 diff --git a/src/gs-entbase/server.src b/src/gs-entbase/server.src index b749e603..c8a442ff 100644 --- a/src/gs-entbase/server.src +++ b/src/gs-entbase/server.src @@ -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 diff --git a/src/gs-entbase/server/env_physexplosion.qc b/src/gs-entbase/server/env_physexplosion.qc index d6f5872a..269b70a5 100644 --- a/src/gs-entbase/server/env_physexplosion.qc +++ b/src/gs-entbase/server/env_physexplosion.qc @@ -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); diff --git a/src/server/entry.qc b/src/server/entry.qc index 1bed4d60..3a8ee3ea 100644 --- a/src/server/entry.qc +++ b/src/server/entry.qc @@ -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(); diff --git a/src/shared/NSDebris.h b/src/shared/NSDebris.h index ae1cb853..a74717d4 100644 --- a/src/shared/NSDebris.h +++ b/src/shared/NSDebris.h @@ -1,5 +1,5 @@ #ifdef CLIENT -class NSDebris:NSRenderableEntity +class NSDebris:NSPhysicsEntity { public: void NSDebris(void); diff --git a/src/shared/NSPhysicsEntity.h b/src/shared/NSPhysicsEntity.h index 58c744bd..912673a3 100644 --- a/src/shared/NSPhysicsEntity.h +++ b/src/shared/NSPhysicsEntity.h @@ -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. */ diff --git a/src/shared/NSPhysicsEntity.qc b/src/shared/NSPhysicsEntity.qc index c2b635d6..c9f1fb65 100644 --- a/src/shared/NSPhysicsEntity.qc +++ b/src/shared/NSPhysicsEntity.qc @@ -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 diff --git a/src/shared/NSSurfacePropEntity.qc b/src/shared/NSSurfacePropEntity.qc index 91f344e8..50ea060b 100644 --- a/src/shared/NSSurfacePropEntity.qc +++ b/src/shared/NSSurfacePropEntity.qc @@ -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(); diff --git a/src/shared/defs.h b/src/shared/defs.h index 9e20dc04..16e697f7 100644 --- a/src/shared/defs.h +++ b/src/shared/defs.h @@ -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) { diff --git a/src/shared/events.h b/src/shared/events.h index 17ad06d9..6508855b 100644 --- a/src/shared/events.h +++ b/src/shared/events.h @@ -54,6 +54,7 @@ enum EV_SURFIMPACT, EV_SURFIMPACTID, EV_DECALGROUP, + EV_BREAKMODELDATA, EV_BREAKMODEL, EV_BEAMCYLINDER, EV_MUZZLEFLASH, diff --git a/src/shared/pmove.h b/src/shared/pmove.h index 9f7197a8..47ee98b6 100644 --- a/src/shared/pmove.h +++ b/src/shared/pmove.h @@ -34,6 +34,4 @@ typedef enum void PMove_StartFrame(void); #endif -void PMove_Init(void); - -void PMove_Run(void); +void PMove_Init(void); \ No newline at end of file diff --git a/src/shared/pmove.qc b/src/shared/pmove.qc index 44b1d808..f5eae09d 100644 --- a/src/shared/pmove.qc +++ b/src/shared/pmove.qc @@ -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(); -} diff --git a/src/shared/propdata.h b/src/shared/propdata.h index 9c59af10..f43c9e2b 100644 --- a/src/shared/propdata.h +++ b/src/shared/propdata.h @@ -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 */ diff --git a/src/shared/propdata.qc b/src/shared/propdata.qc index b64945e1..b10a9234 100644 --- a/src/shared/propdata.qc +++ b/src/shared/propdata.qc @@ -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) {