From e18ceeaa256ed7cb8b3fde3e4d3d1944c7385b20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Sun, 11 Oct 2026 21:54:37 +0300 Subject: [PATCH] Fix prop_physics Spawn and CreateVPhysics ignoring map settings --- Game.Server/BaseAnimating.cs | 5 ++ Game.Server/Physics.cs | 107 +++++++++++++++++++++++++++++++++++ Game.Server/Props.cs | 99 ++++++++++++++++++++++++++++++-- Game.Shared/PhysicsShared.cs | 12 ++++ 4 files changed, 217 insertions(+), 6 deletions(-) diff --git a/Game.Server/BaseAnimating.cs b/Game.Server/BaseAnimating.cs index 65acc1d43..67ba4ea85 100644 --- a/Game.Server/BaseAnimating.cs +++ b/Game.Server/BaseAnimating.cs @@ -879,6 +879,11 @@ public virtual void HandleAnimEvent(ref AnimEvent animEvent) { public TimeUnit_t LastEventCheck; public TimeUnit_t GetCycle() => Cycle; public void SetCycle(TimeUnit_t cycle) => Cycle = cycle; + + public void SetFadeDistance(float minFadeDist, float maxFadeDist) { + FadeMinDist = minFadeDist; + FadeMaxDist = maxFadeDist; + } public float GetSequenceMoveDist(StudioHdr? studioHdr, int sequence) { Animation.GetSequenceLinearMotion(studioHdr, sequence, GetPoseParameterArray(), out Vector3 ret); diff --git a/Game.Server/Physics.cs b/Game.Server/Physics.cs index 41e64acb5..dd821070d 100644 --- a/Game.Server/Physics.cs +++ b/Game.Server/Physics.cs @@ -47,6 +47,79 @@ public static float PhysGetEntityMass(BaseEntity entity) { return otherMass; } + class SkipKeys : IVPhysicsKeyHandler + { + public void ParseKeyValue(T data, ReadOnlySpan key, ReadOnlySpan value) { } + public void SetDefaults(T data) { } + } + + public static void PhysSolidOverride(ref Solid solid, string? overrideScript) { + if (overrideScript != null) { + bool collisions = solid.Params.EnableCollisions; + + StringBuilder tmpString = new("solid { "); + + nexttoken(out ReadOnlySpan token, overrideScript, ',', out ReadOnlySpan str); + while (!token.IsEmpty) { + tmpString.Append('"'); + tmpString.Append(token); + tmpString.Append("\" "); + nexttoken(out token, str, ',', out str); + } + tmpString.Append('}'); + + IVPhysicsKeyParser parse = physcollision.VPhysicsKeyParserCreate(Encoding.ASCII.GetBytes(tmpString.ToString())); + parse.ParseSolid(ref solid, new SkipKeys()); + physcollision.VPhysicsKeyParserDestroy(parse); + + solid.Params.EnableCollisions = collisions; + } + } + + public static void PhysSetMassCenterOverride(in MassCenterOverride massOverride) { + if (massOverride.EntityName != null) + g_PhysicsHook.MassCenterOverrides.Add(massOverride); + } + + public static int PhysGetMassCenterOverrideIndex(string? name) { + if (name != null && g_PhysicsHook.MassCenterOverrides.Count != 0) { + for (int i = 0; i < g_PhysicsHook.MassCenterOverrides.Count; i++) { + if (g_PhysicsHook.MassCenterOverrides[i].EntityName == name) + return i; + } + } + return -1; + } + + public static void PhysGetMassCenterOverride(BaseEntity entity, VCollide? collide, ref Solid solidOut) { + int index = PhysGetMassCenterOverrideIndex(entity.GetEntityName()); + + if (index >= 0) { + MassCenterOverride massOverride = g_PhysicsHook.MassCenterOverrides[index]; + Vector3 massCenterWS = massOverride.Center; + switch (massOverride.AlignType) { + case MassCenterOverride.Align.Point: + MathLib.VectorITransform(massCenterWS, entity.EntityToWorldTransform(), out solidOut.MassCenterOverride); + break; + case MassCenterOverride.Align.Axis: { + physcollision.CollideGetMassCenter(collide!.Solids![solidOut.Index], out Vector3 massCenterLocal); + MathLib.VectorTransform(massCenterLocal, entity.EntityToWorldTransform(), out Vector3 defaultMassCenterWS); + massCenterWS += massOverride.Axis * + (Vector3.Dot(defaultMassCenterWS, massOverride.Axis) - Vector3.Dot(massOverride.Axis, massOverride.Center)); + MathLib.VectorITransform(massCenterWS, entity.EntityToWorldTransform(), out solidOut.MassCenterOverride); + } + break; + } + g_PhysicsHook.MassCenterOverrides[index] = g_PhysicsHook.MassCenterOverrides[^1]; + g_PhysicsHook.MassCenterOverrides.RemoveAt(g_PhysicsHook.MassCenterOverrides.Count - 1); + + if (solidOut.MassCenterOverride.Length() > DIST_EPSILON) { + Vector3[] massCenter = [solidOut.MassCenterOverride]; + solidOut.Params.MassCenterOverrideFn = () => ref massCenter[0]; + } + } + } + public static void PhysicsImpactSound(BaseEntity entity, IPhysicsObject physObject, SoundEntityChannel channel, int surfaceProps, int surfacePropsHit, float volume, float impactSpeed) { PhysicsSound.AddImpactSound(g_PhysicsHook.ImpactSounds, entity, entity.EntIndex(), channel, physObject, surfaceProps, surfacePropsHit, volume, impactSpeed); } @@ -202,6 +275,38 @@ public static void PhysCallbackDamage(BaseEntity entity, in TakeDamageInfo info) } } +public struct MassCenterOverride +{ + public enum Align + { + Point = 0, + Axis = 1, + } + + public void Defaults() { + EntityName = null; + } + + public void SnapToPoint(string? name, in Vector3 pointWS) { + EntityName = name; + Center = pointWS; + Axis = default; + AlignType = Align.Point; + } + + public void SnapToAxis(string? name, in Vector3 axisStartWS, in Vector3 unitAxisDirWS) { + EntityName = name; + Center = axisStartWS; + Axis = unitAxisDirWS; + AlignType = Align.Axis; + } + + public Vector3 Center; + public Vector3 Axis; + public Align AlignType; + public string? EntityName; +} + public class PhysicsHook : BaseGameSystemPerFrame { public static TimeUnit_t g_PhysAverageSimTime; @@ -341,6 +446,7 @@ public override void LevelShutdownPreEntity() { } public override void LevelShutdownPostEntity() { base.LevelShutdownPostEntity(); + MassCenterOverrides.Clear(); } public override void FrameUpdatePostEntityThink() { base.FrameUpdatePostEntityThink(); @@ -358,4 +464,5 @@ public override void PreClientUpdate() { TimeUnit_t ImpactSoundTime; public readonly List ImpactSounds = []; public readonly List BreakSounds = []; + public readonly List MassCenterOverrides = []; } diff --git a/Game.Server/Props.cs b/Game.Server/Props.cs index 9c8bb6ef4..3f887c0fc 100644 --- a/Game.Server/Props.cs +++ b/Game.Server/Props.cs @@ -16,6 +16,7 @@ namespace Game.Server; using System.Diagnostics; using System.Numerics; +using DEFINE = Source.DEFINE; using FIELD_BPD = Source.FIELD; using FIELD_DP = Source.FIELD; using FIELD_PBM = Source.FIELD; @@ -62,6 +63,13 @@ public class BreakableProp : BaseProp, IBreakableWithPropData public static readonly SendTable DT_BreakableProp = new(DT_BaseAnimating, []); public static readonly new ServerClass ServerClass = new ServerClass(DT_BreakableProp); + public bool PropDataOverrodeBlockLOS() => BlockLOSSetByPropData; + + public void DisableAutoFade() { + FadeScale = 0; + DefaultFadeScale = 0; + } + public void SetDmgModBullet(float dmgMod) => DmgModBullet = dmgMod; public void SetDmgModClub(float dmgMod) => DmgModClub = dmgMod; public void SetDmgModExplosive(float dmgMod) => DmgModExplosive = dmgMod; @@ -82,7 +90,7 @@ public class BreakableProp : BaseProp, IBreakableWithPropData public int GetBreakableCount() => BreakableCount; public void SetMaxBreakableSize(int size) => MaxBreakableSize = size; public int GetMaxBreakableSize() => MaxBreakableSize; - public void SetPropDataBlocksLOS(bool blocksLOS) => SetBlocksLOS(blocksLOS); + public void SetPropDataBlocksLOS(bool blocksLOS) { BlockLOSSetByPropData = true; SetBlocksLOS(blocksLOS); } public void SetPropDataIsAIWalkable(bool blocksLOS) => SetAIWalkable(blocksLOS); public void SetBasePropData(ReadOnlySpan propdata) => BasePropData = propdata.IsEmpty ? null : new(propdata); public ReadOnlySpan GetBasePropData() => BasePropData; @@ -107,6 +115,8 @@ public class BreakableProp : BaseProp, IBreakableWithPropData protected float ExplodeRadius; protected PhysicsMultiplayerMode m_iPhysicsMode; protected MultiplayerBreak MpBreakMode; + bool BlockLOSSetByPropData; + float DefaultFadeScale; } [LinkEntityToClass("func_physbox_multiplayer")] @@ -146,7 +156,36 @@ public class PhysicsProp : BreakableProp [NetworkName("m_bAwake")] public bool Awake; + public static readonly new DataMap DataDesc = new(typeof(PhysicsProp), BaseEntity.DataDesc, [ + DEFINE.KEYFIELD(nameof(MassScale), FieldType.Float, "massscale"), + DEFINE.KEYFIELD(nameof(InertiaScale), FieldType.Float, "inertiascale"), + DEFINE.KEYFIELD(nameof(DamageType), FieldType.Integer, "Damagetype"), + DEFINE.KEYFIELD(nameof(OverrideScript), FieldType.String, "overridescript"), + DEFINE.KEYFIELD(nameof(DamageToEnableMotion), FieldType.Integer, "damagetoenablemotion"), + DEFINE.KEYFIELD(nameof(ForceToEnableMotion), FieldType.Float, "forcetoenablemotion"), + ]); + public override DataMap? GetDataDescMap() => DataDesc; + + public static int g_ActiveGibCount = 0; + + float MassScale; + float InertiaScale; + int DamageType; + string? OverrideScript; + int DamageToEnableMotion; + float ForceToEnableMotion; + + public override void Term() { + if (HasSpawnFlags(SF_PHYSPROP_IS_GIB)) + g_ActiveGibCount--; + + base.Term(); + } + public override void Spawn() { + if (HasSpawnFlags(SF_PHYSPROP_IS_GIB)) + g_ActiveGibCount++; + // Condense classnames to one, except for "prop_physics_override" if (FClassnameIs(this, "physics_prop")) SetClassname("prop_physics"); @@ -160,7 +199,21 @@ public override void Spawn() { if (FClassnameIs(this, "prop_physics_override")) SetClassname("prop_physics"); + if (HasSpawnFlags(SF_PHYSPROP_DEBRIS) || HasInteraction(PropDataInteractions.PhysgunCreateFlare)) + SetCollisionGroup(HasSpawnFlags(SF_PHYSPROP_FORCE_TOUCH_TRIGGERS) ? Source.CollisionGroup.DebrisTrigger : Source.CollisionGroup.Debris); + + if (HasSpawnFlags(SF_PHYSPROP_NO_ROTORWASH_PUSH)) + AddEFlags(EFL.NoRotorWashPush); + CreateVPhysics(); + + if (!PropDataOverrodeBlockLOS()) + CalculateBlockLOS(); + + if (((ReadOnlySpan)GetModelName()).Equals("models/props_c17/door01_left.mdl", StringComparison.Ordinal)) { + SetFadeDistance(-1, 0); + DisableAutoFade(); + } } public static bool PropIsGib(BaseEntity entity) { @@ -174,18 +227,52 @@ public static bool PropIsGib(BaseEntity entity) { public bool IsGib() => (SpawnFlags & SF_PHYSPROP_IS_GIB) != 0; public virtual bool CreateVPhysics() { - SetSolid(SolidType.VPhysics); + bool asleep = HasSpawnFlags(SF_PHYSPROP_START_ASLEEP); - if (m_takedamage == (byte)Damage.No) - SetMoveType(Source.MoveType.None); + Solid tmpSolid = default; + PhysModelParseSolid(ref tmpSolid, this, GetModelIndex()); + + if (MassScale > 0) + tmpSolid.Params.Mass *= MassScale; + + if (InertiaScale > 0) { + tmpSolid.Params.Inertia *= InertiaScale; + if (tmpSolid.Params.Inertia < 0.5) + tmpSolid.Params.Inertia = 0.5f; + } + + PhysGetMassCenterOverride(this, modelinfo.GetVCollide(GetModelIndex()), ref tmpSolid); + if (HasSpawnFlags(SF_PHYSPROP_NO_COLLISIONS)) + tmpSolid.Params.EnableCollisions = false; + + PhysSolidOverride(ref tmpSolid, OverrideScript); - IPhysicsObject? physObj = VPhysicsInitNormal(SolidType.VPhysics, GetSolidFlags(), false); - if (physObj == null) { + IPhysicsObject? physicsObject = VPhysicsInitNormal(SolidType.VPhysics, 0, asleep, ref tmpSolid); + + if (physicsObject == null) { SetSolid(SolidType.None); SetMoveType(Source.MoveType.None); Warning($"ERROR!: Can't create physics object for {GetModelName()}\n"); return false; } + else { + if (DamageType == 1) + PhysSetGameFlags(physicsObject, PhysicsFlags.DamageSlice); + + if (HasSpawnFlags(SF_PHYSPROP_MOTIONDISABLED) || DamageToEnableMotion > 0 || ForceToEnableMotion > 0) + physicsObject.EnableMotion(false); + } + + if (HasInteraction(PropDataInteractions.PhysgunLaunchSpinZ)) { + if ((VPhysicsGetObject()!.GetGameFlags() & PhysicsFlags.DamageSlice) == 0) + PhysSetGameFlags(physicsObject, PhysicsFlags.DamageSlice); + } + + if (HasInteraction(PropDataInteractions.PhysgunDamageNone)) + PhysSetGameFlags(physicsObject, PhysicsFlags.NoImpactDamage); + + if (HasSpawnFlags(SF_PHYSPROP_PREVENT_PICKUP)) + PhysSetGameFlags(physicsObject, PhysicsFlags.NoPlayerPickup); return true; } diff --git a/Game.Shared/PhysicsShared.cs b/Game.Shared/PhysicsShared.cs index d538ed3c7..ad3d597de 100644 --- a/Game.Shared/PhysicsShared.cs +++ b/Game.Shared/PhysicsShared.cs @@ -339,6 +339,18 @@ public static void PhysFrictionEffect(in Vector3 pos, Vector3 vel, float energy, } #endif + public static bool PhysModelParseSolidByIndex(ref Solid solid, BaseEntity entity, int modelIndex, int solidIndex) { + VCollide? collide = modelinfo.GetVCollide(modelIndex); + if (collide == null) + return false; + + return PhysModelParseSolidByIndex(ref solid, entity, collide, solidIndex); + } + + public static bool PhysModelParseSolid(ref Solid solid, BaseEntity entity, int modelIndex) { + return PhysModelParseSolidByIndex(ref solid, entity, modelIndex, -1); + } + public static bool PhysModelParseSolidByIndex(ref Solid solid, BaseEntity entity, VCollide? collide, int solidIndex) { if (collide == null || collide.KeyValues == null) return false;