diff --git a/Game.Assets/sdn/shaders/sprite_gl460.fs b/Game.Assets/sdn/shaders/sprite_gl460.fs new file mode 100644 index 00000000..f8e54ee4 --- /dev/null +++ b/Game.Assets/sdn/shaders/sprite_gl460.fs @@ -0,0 +1,52 @@ +#version 460 +// STATIC: "VERTEXCOLOR" "0..1" +// STATIC: "CONSTANTCOLOR" "0..1" +// STATIC: "SRGB" "0..1" +// DYNAMIC: "HDRENABLED" "0..1" +// DYNAMIC: "PIXELFOGTYPE" "0..2" + +layout(location = 0) in vec2 vs_BaseTexCoord; +layout(location = 1) in vec4 vs_Color; +layout(location = 2) in vec4 vs_WorldPos_ProjPosZ; + +layout(std140, binding = 6) uniform source_ps_constants { + vec4 ps_const[256]; +}; + +out vec4 fragColor; + +#include "common_gl460.fs" + +layout(binding = 0) uniform sampler2D TexSampler; + +#define g_Color ps_const[0] +#define g_HDRColorScale ps_const[1].x +#define g_EyePos_SpecExponent ps_const[11] +#define g_FogParams ps_const[12] + +void main() +{ + vec4 result; + vec4 color = texture(TexSampler, vs_BaseTexCoord); + +#if VERTEXCOLOR + color *= vs_Color; +#endif + +#if CONSTANTCOLOR + color *= g_Color; +#endif + +#if HDRENABLED + color.xyz *= g_HDRColorScale; +#endif + + float fogFactor = CalcPixelFogFactor(PIXELFOGTYPE, g_FogParams, g_EyePos_SpecExponent.xyz, vs_WorldPos_ProjPosZ.xyz, vs_WorldPos_ProjPosZ.w); +#if SRGB + result = FinalOutput(color, fogFactor, PIXELFOGTYPE, TONEMAP_SCALE_LINEAR); +#else + result = FinalOutput(color, fogFactor, PIXELFOGTYPE, TONEMAP_SCALE_GAMMA); +#endif + + fragColor = result; +} diff --git a/Game.Assets/sdn/shaders/sprite_gl460.vs b/Game.Assets/sdn/shaders/sprite_gl460.vs new file mode 100644 index 00000000..180a9bc4 --- /dev/null +++ b/Game.Assets/sdn/shaders/sprite_gl460.vs @@ -0,0 +1,43 @@ +#version 460 +// STATIC: "VERTEXCOLOR" "0..1" +// STATIC: "SRGB" "0..1" + +layout(location = 0) in vec3 v_Position; +layout(location = 2) in vec4 v_Color; +layout(location = 10) in vec4 v_TexCoord0; + +layout(std140, binding = 0) uniform source_matrices { + mat4 viewMatrix; + mat4 projectionMatrix; + mat4 modelMatrix; +}; + +#include "common_gl460.glsl" + +layout(location = 0) out vec2 vs_BaseTexCoord; +layout(location = 1) out vec4 vs_Color; +layout(location = 2) out vec4 vs_WorldPos_ProjPosZ; + +#include "common_clipplanes_gl460.vs" + +void main() +{ + vec3 worldPos = (modelMatrix * vec4(v_Position, 1.0)).xyz; + + vec4 projPos = projectionMatrix * viewMatrix * vec4(worldPos, 1.0); + gl_Position = projPos; + WriteUserClipDistances(gl_Position); + + vs_WorldPos_ProjPosZ = vec4(worldPos, projPos.z); + + vs_Color = vec4(1.0); +#if VERTEXCOLOR +#if SRGB + vs_Color = vec4(GammaToLinear(v_Color.rgb), v_Color.a); +#else + vs_Color = v_Color; +#endif +#endif + + vs_BaseTexCoord = v_TexCoord0.xy; +} diff --git a/Game.Client/C_BaseAnimating.cs b/Game.Client/C_BaseAnimating.cs index fc66f0dd..75e9aa5d 100644 --- a/Game.Client/C_BaseAnimating.cs +++ b/Game.Client/C_BaseAnimating.cs @@ -11,8 +11,10 @@ using Source.Common.Formats.BSP; using Source.Common.Formats.Keyvalues; using Source.Common.Mathematics; +using Source.Common.Physics; using System; +using System.Collections; using System.Drawing; using System.Linq; using System.Net.NetworkInformation; @@ -179,6 +181,21 @@ public void StudioFrameAdvance() { GroundSpeed = (float)GetSequenceGroundSpeed(hdr, GetSequence()) * GetModelScale(); } + public override int VPhysicsGetObjectList(Span list){ + if (IsRagdoll()) { + int i; + for (i = 0; i < Ragdoll.RagdollBoneCount(); ++i) { + if (i >= list.Length) + break; + + list[i] = Ragdoll.GetElement(i); + } + return i; + } + + return base.VPhysicsGetObjectList(list); + } + public TimeUnit_t GetSequenceMoveDist(StudioHdr? studioHdr, int sequence) { Animation.GetSequenceLinearMotion(studioHdr, sequence, PoseParameter, out Vector3 vecReturn); @@ -830,7 +847,7 @@ public string GetSequenceName(int sequence) { return Animation.GetSequenceName(GetModelPtr(), sequence); } - public object? Ragdoll; // TODO + public ClientRagdoll? Ragdoll = null; public override void OnDataChanged(DataUpdateType updateType) { if (Ragdoll != null && GetSequence() != PrevSequence) { diff --git a/Game.Client/C_BaseCombatWeapon.cs b/Game.Client/C_BaseCombatWeapon.cs index 8b839991..ce6dd1e0 100644 --- a/Game.Client/C_BaseCombatWeapon.cs +++ b/Game.Client/C_BaseCombatWeapon.cs @@ -1,8 +1,9 @@ -using Game.Client.HUD; +using Game.Client.HUD; using Game.Shared; using Source; using Source.Common; +using Source.Common.Input; using System.Runtime.CompilerServices; @@ -16,6 +17,8 @@ public partial class C_BaseCombatWeapon : C_BaseAnimating public virtual bool WasDropped() => Dropped; public virtual void SetDropped() => Dropped = true; public virtual void DrawHUD() { } + public virtual bool ShouldFreezeViewAngles() => false; + public virtual int KeyInput(int down, ButtonCode keynum, ReadOnlySpan currentBinding) => 1; public virtual void DrawHUDBackground() { } public override bool IsWeapon() => true; public static BaseCombatWeapon? GetActiveWeapon() { diff --git a/Game.Client/C_BaseEntity.cs b/Game.Client/C_BaseEntity.cs index a8189db8..dc7eefee 100644 --- a/Game.Client/C_BaseEntity.cs +++ b/Game.Client/C_BaseEntity.cs @@ -874,7 +874,17 @@ public C_BaseEntity() { IPhysicsObject? PhysicsObject = null!; public void VPhysicsUpdate(IPhysicsObject physics) { } public IPhysicsObject? VPhysicsGetObject() => PhysicsObject; - public int VPhysicsGetObjectList(Span list) => throw new NotImplementedException(); + public virtual int VPhysicsGetObjectList(Span list) { + IPhysicsObject? phys = VPhysicsGetObject(); + if (phys != null) { + Assert((phys.GetGameFlags() & PhysicsFlags.MultiObjectEntity) == 0); + if (list.Length > 0) { + list[0] = phys; + return 1; + } + } + return 0; + } public bool IsFloating() => false; @@ -1183,8 +1193,17 @@ public void GetRenderBounds(out Vector3 mins, out Vector3 maxs) { if (nModelType == ModelType.Studio || nModelType == ModelType.Brush) modelinfo.GetModelRenderBounds(GetModel(), out mins, out maxs); else { - // todo - mins = maxs = default; + if (GetRenderAngles() == CollisionProp().GetCollisionAngles()) { + mins = CollisionProp().OBBMins(); + maxs = CollisionProp().OBBMaxs(); + } + else { + Assert(CollisionProp().GetCollisionAngles() == vec3_angle); + if (CollisionProp().BoundingRadius() == 0.0f) + mins = maxs = vec3_origin; + else + MathLib.IRotateAABB(EntityToWorldTransform(), CollisionProp().OBBMins(), CollisionProp().OBBMaxs(), out mins, out maxs); + } } } diff --git a/Game.Client/ClientModeShared.cs b/Game.Client/ClientModeShared.cs index 0beeaa86..c9a87219 100644 --- a/Game.Client/ClientModeShared.cs +++ b/Game.Client/ClientModeShared.cs @@ -1,4 +1,4 @@ -using Game.Client.HUD; +using Game.Client.HUD; using Game.Shared; using Source; @@ -165,6 +165,15 @@ public virtual int KeyInput(int down, ButtonCode keynum, ReadOnlySpan curr if (HudElementKeyInput(down, keynum, currentBinding) == 0) return 0; +#if GMOD_DLL + if (surface.IsCursorVisible() && (keynum == ButtonCode.MouseWheelUp || keynum == ButtonCode.MouseWheelDown)) + return 0; +#endif + + BaseCombatWeapon? activeWeapon = C_BaseCombatWeapon.GetActiveWeapon(); + if (activeWeapon != null) + return activeWeapon.KeyInput(down, keynum, currentBinding); + return 1; } diff --git a/Game.Client/ClientThinkList.cs b/Game.Client/ClientThinkList.cs index fff70077..83d22ac7 100644 --- a/Game.Client/ClientThinkList.cs +++ b/Game.Client/ClientThinkList.cs @@ -287,10 +287,12 @@ private void CleanUpDeleteList() { pEntity.SetRemovalFlag(false); IClientThinkable? pThink = cl_entitylist.GetClientThinkableFromHandle(handle); - if (pThink != null) + if (pThink != null) pThink.Release(); } } + + DeleteList.Clear(); } private void AddEntityToFrameThinkList(ThinkEntry pEntry, bool bAlwaysChain, ref int count, Span ppFrameThinkList) { diff --git a/Game.Client/GarrysMod/C_GMOD_Player.cs b/Game.Client/GarrysMod/C_GMOD_Player.cs index 5b1d3b27..6e92f502 100644 --- a/Game.Client/GarrysMod/C_GMOD_Player.cs +++ b/Game.Client/GarrysMod/C_GMOD_Player.cs @@ -75,6 +75,7 @@ public partial class C_GMOD_Player() : C_HL2MP_Player() public Vector3 WeaponColor; public virtual Vector3 GetPlayerColor() => PlayerColor; + public virtual Vector3 GetWeaponColor() => WeaponColor; public virtual void SetPlayerColor(in Vector3 color) { if (PlayerColor != color) diff --git a/Game.Client/HLClient.cs b/Game.Client/HLClient.cs index 1c56b8c2..f69fd194 100644 --- a/Game.Client/HLClient.cs +++ b/Game.Client/HLClient.cs @@ -804,9 +804,7 @@ public void GMOD_OnDrawSkybox() { throw new NotImplementedException(); } - public void IN_MouseWheelAnalog(int value) { - throw new NotImplementedException(); - } + public void IN_MouseWheelAnalog(int value) => input.MouseWheeled(value); public void GMOD_SignOnStateChanged(int userID, int oldState, int newState) { throw new NotImplementedException(); diff --git a/Game.Client/IInput.cs b/Game.Client/IInput.cs index 7963abf0..2d9142f6 100644 --- a/Game.Client/IInput.cs +++ b/Game.Client/IInput.cs @@ -20,6 +20,9 @@ public interface IInput void ExtraMouseSample(double frametime, bool active); void ActivateMouse(); void DeactivateMouse(); +#if GMOD_DLL + void MouseWheeled(int delta); +#endif void ClearStates(); void CAM_Think(); ref UserCmd GetUserCmd(int sequenceNumber); diff --git a/Game.Client/InMain.cs b/Game.Client/InMain.cs index 305f01b9..623c6acb 100644 --- a/Game.Client/InMain.cs +++ b/Game.Client/InMain.cs @@ -273,6 +273,10 @@ public void CreateMove(int sequenceNumber, double inputSampleFrametime, bool act cmd.Buttons |= InButtons.Forward; else if (cmd.ForwardMove < 0) cmd.Buttons |= InButtons.Back; + +#if GMOD_DLL + MouseWheel = 0; +#endif } private void ApplyExtras(ref UserCmd cmd) { @@ -494,6 +498,7 @@ private void ControllerMove(double frametime, ref UserCmd cmd) { private void MouseMove(ref UserCmd cmd) { engine.GetViewAngles(out QAngle viewangles); + QAngle originalViewangles = viewangles; view.StopPitchDrift(); if (!CameraInterceptingMouse && !Surface.IsCursorVisible()) { @@ -506,10 +511,18 @@ private void MouseMove(ref UserCmd cmd) { ResetMouse(); } +#if GMOD_DLL + if (C_BasePlayer.GetLocalPlayer()?.GetActiveWeapon()?.ShouldFreezeViewAngles() == true) + viewangles = originalViewangles; +#endif + engine.SetViewAngles(in viewangles); } private void ApplyMouse(ref QAngle viewangles, ref UserCmd cmd, float mouse_x, float mouse_y) { +#if GMOD_DLL + cmd.ScrollWheelSpeed = (sbyte)MouseWheel; +#endif if ((in_strafe.State & KeyButtonStateFlags.Down) == 0 || lookstrafe.GetInt() != 0) { if (CAM_IsThirdPerson() && thirdperson_platformer.GetInt() != 0) { if (mouse_x != 0) { diff --git a/Game.Client/InMouse.cs b/Game.Client/InMouse.cs index 4efed053..590ea928 100644 --- a/Game.Client/InMouse.cs +++ b/Game.Client/InMouse.cs @@ -1,6 +1,7 @@ using Source.Common.Commands; using Source.Common.GUI; using Source.Common.Launcher; +using Source.GUI.Controls; namespace Game.Client; @@ -43,10 +44,27 @@ public void DeactivateMouse() { AccumulatedMouseXMovement = 0; AccumulatedMouseYMovement = 0; +#if GMOD_DLL + MouseWheel = 0; +#endif launcher.SetWindowRelativeMouseMode(false); } } +#if GMOD_DLL + int MouseWheel; + + public void MouseWheeled(int delta) { + if (!Surface.IsCursorVisible()) { + MouseWheel += delta; + return; + } + + if (input.GetMouseOver() is Panel panel && panel.IsWorldClicker()) + MouseWheel += delta; + } +#endif + ConVar m_filter = new("0", FCvar.Archive, "Mouse filtering (set this to 1 to average the mouse over 2 frames)."); ConVar sensitivity = new("3", FCvar.Archive, "Mouse sensitivity.", 0.0001f, 1000); ConVar m_side = new("0.8", FCvar.Archive, "Mouse side factor.", 0.0001f, 1000); diff --git a/Game.Client/Ragdoll.cs b/Game.Client/Ragdoll.cs new file mode 100644 index 00000000..51aa6e54 --- /dev/null +++ b/Game.Client/Ragdoll.cs @@ -0,0 +1,266 @@ +global using static Game.Client.ClientRagdollGlobals; +global using static Game.Client.RagdollConvars; + +using Game.Shared; + +using Source; +using Source.Common; +using Source.Common.Commands; +using Source.Common.MaterialSystem; +using Source.Common.Mathematics; +using Source.Common.Physics; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Numerics; +using System.Text; + +namespace Game.Client; + +public interface IRagdoll +{ + void RagdollBone(C_BaseEntity ent, Span bones, Span boneSimulated, BoneAccessor boneToWorld); + ref readonly Vector3 GetRagdollOrigin(); + void GetRagdollBounds(out Vector3 mins, out Vector3 maxs); + int RagdollBoneCount(); + IPhysicsObject? GetElement(int elementNum); + void DrawWireframe(); + void VPhysicsUpdate(IPhysicsObject obj); + bool TransformVectorToWorld(int boneIndex, in Vector3 temp, out Vector3 outVec); +} +public static class RagdollConvars +{ + public static readonly ConVar ragdoll_sleepaftertime = new("ragdoll_sleepaftertime", "5.0f", 0, "After this many seconds of being basically stationary, the ragdoll will go to sleep."); +} + +/// +/// Analog of CRagdoll +/// +public class ClientRagdoll : IRagdoll +{ + public void Init( + C_BaseEntity ent, + StudioHdr studiohdr, + in Vector3 forceVector, + int forceBone, + ReadOnlySpan deltaBones0, + ReadOnlySpan deltaBones1, + ReadOnlySpan currentBonePosition, + TimeUnit_t boneDt, + bool fixedConstraints = false + ) { + + } + + public void RagdollBone(C_BaseEntity ent, Span bones, Span boneSimulated, BoneAccessor boneToWorld) { + + } + public ref readonly Vector3 GetRagdollOrigin() { + Ragdoll.List[0].Object!.GetPosition(out Origin, out _); + return ref Origin; + } + public void GetRagdollBounds(out Vector3 mins, out Vector3 maxs) { + mins = Mins; + maxs = Maxs; + } + public int RagdollBoneCount() => Ragdoll.ListCount; + public IPhysicsObject? GetElement(int elementNum) { + return Ragdoll.List[elementNum].Object; + } + public void DrawWireframe() { + IMaterial? wireframe = materials.FindMaterial("shadertest/wireframevertexcolor", MaterialDefines.TEXTURE_GROUP_OTHER); + + int i; + Matrix3x4 matrix; + for (i = 0; i < Ragdoll.ListCount; i++) { + Color debugColor = new(0, 255, 255, 0); + + // draw the actual physics positions, not the cleaned up animation position + Ragdoll.List[i].Object!.GetPositionMatrix(out matrix); + PhysCollide collide = Ragdoll.List[i].Object!.GetCollide(); + engine.DebugDrawPhysCollide(collide, wireframe, matrix, debugColor); + } + } + public void VPhysicsUpdate(IPhysicsObject obj) { + if (LastUpdate == gpGlobals.CurTime) + return; + LastUpdate = gpGlobals.CurTime; + AllAsleep = RagdollIsAsleep(Ragdoll); + if (AllAsleep) { + // NOTE: This is the bbox of the ragdoll's physics + // It's not always correct to use for culling, but it sure beats + // using the radius box! + Vector3 origin = GetRagdollOrigin(); + RagdollComputeExactBbox(Ragdoll, origin, ref Mins, ref Maxs); + Mins -= origin; + Maxs -= origin; + } + else { + Mins.Init(-Radius, -Radius, -Radius); + Maxs.Init(Radius, Radius, Radius); + + if (Ragdoll.Group!.IsInErrorState()) { + C_BaseEntity? entity = (C_BaseEntity?)(Ragdoll.List[0].Object!.GetGameData()); + RagdollSolveSeparation(Ragdoll, entity); + } + } + + // See if we should go to sleep... + CheckSettleStationaryRagdoll(); + } + public bool TransformVectorToWorld(int boneIndex, in Vector3 temp, out Vector3 outVec) { + int listIndex = -1; + if (boneIndex >= 0 && boneIndex < Ragdoll.ListCount) { + for (int i = 0; i < Ragdoll.ListCount; ++i) { + if (Ragdoll.BoneIndex[i] == boneIndex) + listIndex = i; + } + if (listIndex != -1) { + Ragdoll.List[listIndex].Object.LocalToWorld(out outVec, in temp); + return true; + } + } + outVec = default; + return false; + } + + public void BuildRagdollBounds(C_BaseEntity ent) { + Vector3 mins, maxs, size; + modelinfo.GetModelBounds(ent.GetModel(), out mins, out maxs); + size = (maxs - mins) * 0.5f; + Radius = size.Length(); + + Mins.Init(-Radius, -Radius, -Radius); + Maxs.Init(Radius, Radius, Radius); + } + + public IPhysicsConstraintGroup? GetConstraintGroup() => Ragdoll.Group; + + public bool IsValid() => Ragdoll.ListCount > 0; + + public Ragdoll GetRagdoll() => Ragdoll; + + public void ResetRagdollSleepAfterTime() => LastOriginChangeTime = gpGlobals.CurTime; + public TimeUnit_t GetLastVPhysicsUpdateTime() => LastUpdate; + + void CheckSettleStationaryRagdoll() { + Vector3 delta = GetRagdollOrigin() - LastOrigin; + LastOrigin = GetRagdollOrigin(); + for (int i = 0; i < 3; ++i) { + // It's still moving... + if (MathF.Abs(delta[i]) > RAGDOLL_SLEEP_TOLERANCE) { + LastOriginChangeTime = gpGlobals.CurTime; + // Msg( "%d [%p] Still moving\n", gpGlobals->tickcount, this ); + return; + } + } + + // It's totally asleep, don't worry about forcing it to settle + if (AllAsleep) + return; + + // Msg( "%d [%p] Settling\n", gpGlobals->tickcount, this ); + + // It has stopped moving, see if it + TimeUnit_t dt = gpGlobals.CurTime - LastOriginChangeTime; + if (dt < ragdoll_sleepaftertime.GetFloat()) + return; + + // Msg( "%d [%p] FORCE SLEEP\n",gpGlobals->tickcount, this ); + + // Force it to go to sleep + PhysForceRagdollToSleep(); + } + void PhysForceRagdollToSleep() { + for (int i = 0; i < Ragdoll.ListCount; i++) { + if (Ragdoll.List[i].Object != null) { + PhysForceClearVelocity(Ragdoll.List[i].Object!); + Ragdoll.List[i].Object!.Sleep(); + } + } + } + + readonly Ragdoll Ragdoll = new(); + Vector3 Mins, Maxs; + Vector3 Origin; + float Radius; + TimeUnit_t LastUpdate; + bool AllAsleep; + Vector3 LastOrigin; + TimeUnit_t LastOriginChangeTime; +} + +public static class ClientRagdollGlobals +{ + public const float RAGDOLL_SLEEP_TOLERANCE = 1.0f; + public static ClientRagdoll CreateRagdoll( + C_BaseEntity ent, + StudioHdr studiohdr, + in Vector3 forceVector, + int forceBone, + ReadOnlySpan deltaBones0, + ReadOnlySpan deltaBones1, + ReadOnlySpan currentBonePosition, + TimeUnit_t dt, + bool fixedConstraints = false + ) { + ClientRagdoll ragdoll = new(); + ragdoll.Init(ent, studiohdr, forceVector, forceBone, deltaBones0, deltaBones1, currentBonePosition, dt, fixedConstraints); + + if (!ragdoll.IsValid()) + Msg($"Bad ragdoll for {studiohdr.Name()}"); + return null; + + return ragdoll; + } + + struct RagdollRemember + { + public C_BaseEntity Ragdoll; + public long TickCount; + } + struct RagdollMemoryList() + { + public readonly List List = []; + public long TickCount = 0; // is this even used? wtf? + public void Update() { + if (TickCount > gpGlobals.TickCount) { + List.Clear(); + return; + } + + for (int i = List.Count - 1; i >= 0; --i) + if (List[i].TickCount != gpGlobals.TickCount) + List.RemoveAt(i); + } + + public bool IsInList(C_BaseEntity ragdoll) { + for (int i = List.Count() - 1; i >= 0; --i) { + if (List[i].Ragdoll == ragdoll) + return true; + } + + return false; + } + + public void AddToList(C_BaseEntity ragdoll) { + Update(); + List.Add(new() { + Ragdoll = ragdoll, + TickCount = gpGlobals.TickCount + }); + } + } + + static RagdollMemoryList gRagdolls = new(); + + public static void NoteRagdollCreationTick(C_BaseEntity ragdoll) { + gRagdolls.AddToList(ragdoll); + } + + public static bool WasRagdollCreatedOnCurrentTick(C_BaseEntity ragdoll) { + gRagdolls.Update(); + return gRagdolls.IsInList(ragdoll); + } +} diff --git a/Game.Server/BaseEntity.cs b/Game.Server/BaseEntity.cs index a0cbd7f2..297ccc9f 100644 --- a/Game.Server/BaseEntity.cs +++ b/Game.Server/BaseEntity.cs @@ -1686,6 +1686,7 @@ public virtual void VPhysicsShadowCollision(int index, ref GameVCollisionEvent e public virtual void VPhysicsShadowUpdate(IPhysicsObject physics) { } public virtual BasePlayer? HasPhysicsAttacker(TimeUnit_t dt) => null; + public virtual void SetPhysicsAttacker(BasePlayer? entity, TimeUnit_t time) { } public virtual void VPhysicsCollision(int index, ref GameVCollisionEvent ev) { int otherIndex = index == 0 ? 1 : 0; @@ -1781,7 +1782,7 @@ bool VPhysicsInitSetup() { return physicsObject; } - public int VPhysicsGetObjectList(Span list) { + public virtual int VPhysicsGetObjectList(Span list) { IPhysicsObject? phys = VPhysicsGetObject(); if (phys != null) { Assert((phys.GetGameFlags() & PhysicsFlags.MultiObjectEntity) == 0); diff --git a/Game.Server/FourWheelVehiclePhysics.cs b/Game.Server/FourWheelVehiclePhysics.cs new file mode 100644 index 00000000..7ad39684 --- /dev/null +++ b/Game.Server/FourWheelVehiclePhysics.cs @@ -0,0 +1,14 @@ +using Source.Common.Physics; + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Game.Server; + +public class FourWheelVehiclePhysics +{ + public int VPhysicsGetObjectList(Span list) { + throw new NotImplementedException(); + } +} diff --git a/Game.Server/GarrysMod/GMOD_Player.cs b/Game.Server/GarrysMod/GMOD_Player.cs index 64b68548..71d76393 100644 --- a/Game.Server/GarrysMod/GMOD_Player.cs +++ b/Game.Server/GarrysMod/GMOD_Player.cs @@ -58,6 +58,20 @@ public partial class GMOD_Player : HL2MP_Player public bool Transition; + public ReadOnlySpan GetPlayerInfo(ReadOnlySpan name, ReadOnlySpan defaultValue) => GetInfo(name); + + public float GetSnapAngles() => Math.Clamp((float)atof(GetPlayerInfo("gm_snapangles", "45")), -360f, 360f); + + public void SnapToGrid(ref Vector3 vec) { + float grid = Math.Clamp((float)atof(GetPlayerInfo("gm_snapgrid", "0")), 0f, 4096f); + if (grid <= 0) + return; + + vec.X = MathF.Round(vec.X / grid) * grid; + vec.Y = MathF.Round(vec.Y / grid) * grid; + vec.Z = MathF.Round(vec.Z / grid) * grid; + } + public override void InitialSpawn() { SetModel("models/player.mdl"); base.InitialSpawn(); diff --git a/Game.Server/GarrysMod/PhysGunControllerPoint.cs b/Game.Server/GarrysMod/PhysGunControllerPoint.cs new file mode 100644 index 00000000..716ddc18 --- /dev/null +++ b/Game.Server/GarrysMod/PhysGunControllerPoint.cs @@ -0,0 +1,224 @@ +#if GMOD_DLL +using Game.Server.HL2; +using Game.Shared; + +using Source; +using Source.Common; +using Source.Common.Commands; +using Source.Common.Mathematics; +using Source.Common.Physics; + +using System.Numerics; + +namespace Game.Server.GarrysMod; + +public static class PhysGunGlobals +{ + public static bool IsPhysicsObjectValid(IPhysicsObject? obj) { + if (obj == null) + return false; + + ReadOnlySpan objects = physenv.GetObjectList(); + for (int i = 0; i < objects.Length; i++) + if (objects[i] == obj) + return true; + + return false; + } + + public static BaseEntity? GetPlayerHeldEntity(BasePlayer? player) { + if (player == null) + return null; + + return (player.UseEntity.Get() as PlayerPickupController)?.GetGrabController().GetAttached(); + } + + public static Vector3 GetAimForward(BasePlayer player) { + if (player.WorldClicking && !player.DisableWorldClicking) + return player.WorldClickVector; + + if (player.IsInAVehicle()) { + player.CacheVehicleView(); + MathLib.AngleVectors(player.VehicleViewAngles, out Vector3 vehicleForward); + return vehicleForward; + } + + MathLib.AngleVectors(player.EyeAngles(), out Vector3 forward); + return forward; + } + + public static BaseEntity? PhysCannonGetHeldEntity(BaseCombatWeapon? weapon) => (weapon as Game.Shared.GarrysMod.WeaponPhysCannon)?.AttachedObject.Get(); + + public static BaseEntity? PhysGunGetHeldEntity(BaseCombatWeapon? weapon) { + if (weapon is not WeaponPhysGun physgun) + return null; + + return physgun.GetGravCallback().GetAttachedEntity(); + } + + public static void PhysGunForceDrop(BaseCombatWeapon? weapon, BaseEntity? ent) { + if (weapon is not WeaponPhysGun physgun) + return; + + if (ent == null || ent == physgun.GrabbedEntity.Get()) + physgun.DropEntity(); + } + + public static bool GMOD_IsHeldByAnyPlayer(BaseEntity? ent, BasePlayer? exclude) { + for (int i = 1; i <= gpGlobals.MaxClients; i++) { + BasePlayer? player = Util.PlayerByIndex(i); + if (player == null || player == exclude) + continue; + + if (GetPlayerHeldEntity(player) == ent) + return true; + if (PhysCannonGetHeldEntity(player.GetActiveWeapon()) == ent) + return true; + if (PhysGunGetHeldEntity(player.GetActiveWeapon()) == ent) + return true; + } + + return false; + } +} + +public class PhysGunControllerPoint : IMotionEvent +{ + public const float PhysGunMass = 45678; + + public static readonly ConVar physgun_maxAngular = new("physgun_maxAngular", "5000"); + public static readonly ConVar physgun_maxAngularDamping = new("physgun_maxAngularDamping", "10000"); + public static readonly ConVar physgun_maxSpeed = new("physgun_maxSpeed", "5000"); + public static readonly ConVar physgun_maxSpeedDamping = new("physgun_maxSpeedDamping", "10000"); + public static readonly ConVar physgun_DampingFactor = new("physgun_DampingFactor", "0.8"); + public static readonly ConVar physgun_teleportDistance = new("physgun_teleportDistance", "0"); + public static readonly ConVar physgun_timeToArrive = new("physgun_timeToArrive", "0.05"); + public static readonly ConVar physgun_timeToArriveRagdoll = new("physgun_timeToArriveRagdoll", "0.1"); + + IPhysicsObject? attachedObject; + Vector3 localPosition; + Vector3 targetPosition; + QAngle targetRotation; + EHANDLE attachedEntity = new(); + float savedMass = -1; + float timeToArrive; + float savedBuoyancy = -1; + bool isVehicle; + Handle attachedPlayer = new(); + IPhysicsMotionController? controller; + + public BaseEntity? GetAttachedEntity() => attachedEntity.Get(); + + void ValidatePhysObj() { + if (!PhysGunGlobals.IsPhysicsObjectValid(attachedObject)) + attachedObject = null; + } + + public void SetTargetRotation(in QAngle angles) { + if (attachedObject != null) + targetRotation = angles; + } + + public void SetTargetPosition(in Vector3 position, BasePlayer? player) { + ValidatePhysObj(); + if (!attachedEntity.IsValid() || attachedObject == null) + return; + + attachedObject.LocalToWorld(out Vector3 world, localPosition); + attachedObject.GetPosition(out Vector3 objPosition, out _); + targetPosition = position - (world - objPosition); + + if (player != null && player.IsPlayer()) + ((GMOD_Player)player).SnapToGrid(ref targetPosition); + } + + public void AttachEntity(BasePlayer? player, BaseEntity ent, IPhysicsObject phys, in Vector3 grabPosition) { + if (controller != null) + Warning("CPhysGunControllerPoint already has controller?\n"); + + attachedObject = phys; + attachedEntity.Set(ent); + isVehicle = ent.ClassMatches("prop_vehicle_jeep"); + timeToArrive = ent.IsRagdollProp() ? physgun_timeToArriveRagdoll.GetFloat() : physgun_timeToArrive.GetFloat(); + attachedPlayer.Set(player); + + float mass = phys.GetMass(); + savedBuoyancy = -1; + savedMass = mass; + if (mass > PhysGunMass) + savedMass = -1; + else if (ent.IsRagdollProp() || ent.ClassMatches("prop_vehicle_jeep") || ent.ClassMatches("prop_vehicle_apc") || ent.ClassMatches("prop_vehicle_jeep_old")) + savedMass = -1; + else if (savedMass != PhysGunMass) { + savedBuoyancy = phys.GetBuoyancyRatio(); + phys.SetMass(PhysGunMass); + } + + phys.EnableMotion(true); + phys.WorldToLocal(out localPosition, grabPosition); + + controller = physenv.CreateMotionController(this); + controller.AttachObject(phys, true); + PhysSetGameFlags(phys, PhysicsFlags.PlayerHeld); + controller.SetPriority(IPhysicsMotionController.Priority.HighPriority); + + phys.GetPosition(out _, out targetRotation); + SetTargetPosition(grabPosition, player); + } + + public void DetachEntity(bool restore) { + ValidatePhysObj(); + + BaseEntity? ent = attachedEntity.Get(); + if (restore && ent != null && attachedObject != null && ent.VPhysicsGetObject() != null) { + if (savedMass > 0 && savedMass != PhysGunMass) { + attachedObject.SetMass(savedMass); + attachedObject.SetBuoyancyRatio(savedBuoyancy); + } + } + + if (!PhysGunGlobals.GMOD_IsHeldByAnyPlayer(ent, attachedPlayer.Get()) && attachedObject != null) + PhysClearGameFlags(attachedObject, PhysicsFlags.PlayerHeld); + + if (controller != null) + physenv.DestroyMotionController(controller); + + attachedEntity.Set(null); + controller = null; + targetPosition = default; + attachedObject = null; + attachedPlayer.Set(null); + } + + public SimResult Simulate(IPhysicsMotionController? controller, IPhysicsObject? obj, TimeUnit_t deltaTime, out Vector3 linear, out Vector3 angular) { + linear = default; + angular = default; + if (obj == null) + return SimResult.Nothing; + + obj.EnableMotion(true); + obj.Wake(); + + HLShadowControlParams shadow = default; + shadow.TargetPosition = targetPosition; + shadow.TargetRotation = targetRotation; + shadow.MaxAngular = physgun_maxAngular.GetFloat(); + shadow.MaxDampAngular = physgun_maxAngularDamping.GetFloat(); + shadow.MaxSpeed = physgun_maxSpeed.GetFloat(); + shadow.MaxDampSpeed = physgun_maxSpeedDamping.GetFloat(); + shadow.DampFactor = physgun_DampingFactor.GetFloat(); + shadow.TeleportDistance = physgun_teleportDistance.GetFloat(); + + if (isVehicle) { + obj.GetPosition(out _, out QAngle objAngles); + MathLib.AngleVectors(targetRotation, out Vector3 f1); + MathLib.AngleVectors(objAngles, out Vector3 f2); + float sum = MathF.Abs(f1.X * f2.X) + MathF.Abs(f1.Y * f2.Y) + MathF.Abs(f1.Z * f2.Z); + shadow.MaxAngular *= 1 - sum; + } + + obj.ComputeShadowControl(shadow, timeToArrive, deltaTime); + return SimResult.LocalAcceleration; + } +} +#endif diff --git a/Game.Server/Player.cs b/Game.Server/Player.cs index 82343051..9232aad9 100644 --- a/Game.Server/Player.cs +++ b/Game.Server/Player.cs @@ -392,6 +392,10 @@ public enum SF protected PlayerPhysFlag PhysicsFlags; public int Train; + int MouseWheelValue; + public int MouseWheel() => MouseWheelValue; + public void SetMouseWheel(int value) => MouseWheelValue = value; + public void SetPhysicsFlag(PlayerPhysFlag flag, bool set) { if (set) PhysicsFlags |= flag; @@ -1570,6 +1574,7 @@ public void ToggleDuck() { public virtual void PlayerRunCommand(UserCmd userCmd, IMoveHelper s_MoveHelperServer) { TouchedPhysObject = false; + MouseWheelValue = userCmd.ScrollWheelSpeed; if (pl.FixAngle == (int)FixAngle.None) MathLib.VectorCopy(userCmd.ViewAngles, out pl.ViewingAngle); diff --git a/Game.Server/PropVehicleDriveable.cs b/Game.Server/PropVehicleDriveable.cs index fa9216ee..cf5f7c91 100644 --- a/Game.Server/PropVehicleDriveable.cs +++ b/Game.Server/PropVehicleDriveable.cs @@ -1,8 +1,10 @@ using Game.Shared; using Source.Common; +using Source.Common.Physics; using System.Numerics; +using System.Runtime.CompilerServices; namespace Game.Server; using FIELD = Source.FIELD; @@ -56,6 +58,11 @@ public class PropVehicleDriveable : BaseAnimating public Vector3 GunCrosshair; [NetworkName("m_bLocked")] public bool Locked; - + readonly FourWheelVehiclePhysics VehiclePhysics = new(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public FourWheelVehiclePhysics GetPhysics() => VehiclePhysics; public override bool IsVehicle() => true; + + public override int VPhysicsGetObjectList(Span list) { + return GetPhysics().VPhysicsGetObjectList(list); + } } diff --git a/Game.Server/Props.cs b/Game.Server/Props.cs index 5b531866..2d8b33f4 100644 --- a/Game.Server/Props.cs +++ b/Game.Server/Props.cs @@ -152,7 +152,7 @@ public void CheckRemoveRagdolls() { throw new NotImplementedException(); } - public void SetPhysicsAttacker(BasePlayer? entity, TimeUnit_t time) { + public override void SetPhysicsAttacker(BasePlayer? entity, TimeUnit_t time) { PhysicsAttacker.Set(entity); LastPhysicsInfluenceTime = time; } @@ -349,7 +349,7 @@ public class PhysicsProp : BreakableProp float ForceToEnableMotion; bool FirstCollisionAfterLaunch; - bool ThrownByPlayer; + internal bool ThrownByPlayer; public OutputEvent OnPhysGunPickup_ = new(); public OutputEvent OnPhysGunPunt = new(); diff --git a/Game.Server/RagdollProp.cs b/Game.Server/RagdollProp.cs index 8a82537b..823d80b8 100644 --- a/Game.Server/RagdollProp.cs +++ b/Game.Server/RagdollProp.cs @@ -4,6 +4,7 @@ using Game.Shared; using System.Numerics; +using Source.Common.Physics; namespace Game.Server; using FIELD = FIELD; @@ -42,4 +43,12 @@ .. Enumerable.Range(0, RAGDOLL_MAX_ELEMENTS).Select(i => SendPropQAngles(FIELD.O public static Ragdoll? Ragdoll_GetRagdoll(BaseEntity entity) => (entity as RagdollProp)?.GetRagdoll(); public override bool IsRagdollProp() => true; + + public override int VPhysicsGetObjectList(Span list) { + for (int i = 0; i < ragdoll.ListCount; i++) + if (i < list.Length) + list[i] = ragdoll.List[i].Object; + + return ragdoll.ListCount; + } } diff --git a/Game.Shared/GarrysMod/LuaEntity.cs b/Game.Shared/GarrysMod/LuaEntity.cs index edde9063..bea081a5 100644 --- a/Game.Shared/GarrysMod/LuaEntity.cs +++ b/Game.Shared/GarrysMod/LuaEntity.cs @@ -955,6 +955,69 @@ static int Entity__SetModel(ILuaInterface lua) { return 0; } + const int MAX_PHYSOBJECT_LIST = 0x400; + + [LuaMethod] + static int Entity__GetPhysicsObject(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; +#if GAME_DLL + if (ent.Edict() != null && ent.EntIndex() != 0) + LuaPhysObj.Push_PhysObj(ent.VPhysicsGetObject()); + else + LuaPhysObj.Push_PhysObj(g_PhysWorldObject); +#else + LuaPhysObj.Push_PhysObj(ent.VPhysicsGetObject()); +#endif + return 1; + } + + [LuaMethod] + static int Entity__GetPhysicsObjectNum(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; +#if GAME_DLL + if (ent.Edict() == null || ent.EntIndex() == 0) { + LuaPhysObj.Push_PhysObj(g_PhysWorldObject); + return 1; + } +#endif + int index = (int)lua.GetNumber(2); + if (index < 0) { + DevWarning($"Entity:GetPhysicsObjectNum - Out of bounds physics object - min 0, got {index}\n"); + return 0; + } + + IPhysicsObject[] list = System.Buffers.ArrayPool.Shared.Rent(MAX_PHYSOBJECT_LIST); + try { + int count = ent.VPhysicsGetObjectList(list.AsSpan(0, MAX_PHYSOBJECT_LIST)); + if (count == 0) + return 0; + + if (index >= count) { + DevWarning($"Entity:GetPhysicsObjectNum - Out of bounds physics object - max {count - 1}, got {index}\n"); + return 0; + } + + LuaPhysObj.Push_PhysObj(list[index]); + return 1; + } + finally { + System.Buffers.ArrayPool.Shared.Return(list, true); + } + } + + [LuaMethod] + static int Entity__GetPhysicsObjectCount(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + IPhysicsObject[] list = System.Buffers.ArrayPool.Shared.Rent(MAX_PHYSOBJECT_LIST); + try { + lua.PushNumber(ent.VPhysicsGetObjectList(list.AsSpan(0, MAX_PHYSOBJECT_LIST))); + return 1; + } + finally { + System.Buffers.ArrayPool.Shared.Return(list, true); + } + } + [LuaMethod] static int Entity__GetPos(ILuaInterface lua) { BaseEntity ent = Get_Entity(1, false)!; diff --git a/Game.Shared/GarrysMod/LuaObject.cs b/Game.Shared/GarrysMod/LuaObject.cs index 0ecc13ca..e11e35fd 100644 --- a/Game.Shared/GarrysMod/LuaObject.cs +++ b/Game.Shared/GarrysMod/LuaObject.cs @@ -713,7 +713,16 @@ public void SetMemberEntity(ReadOnlySpan name, IHandleEntity? ent) { } #endif public void SetEntity(IHandleEntity? ent) => throw new NotImplementedException(); +#if MENU_DLL public void SetMemberPhysObject(ReadOnlySpan name, IPhysicsObject? obj) => throw new NotImplementedException(); +#else + public void SetMemberPhysObject(ReadOnlySpan name, IPhysicsObject? obj) { + if (!isTable()) + return; + LuaPhysObj.Push_PhysObj(obj); + SetMember(name); + } +#endif } public class LuaTable : LuaObject diff --git a/Game.Shared/GarrysMod/LuaPhysObj.cs b/Game.Shared/GarrysMod/LuaPhysObj.cs new file mode 100644 index 00000000..261ece4e --- /dev/null +++ b/Game.Shared/GarrysMod/LuaPhysObj.cs @@ -0,0 +1,900 @@ +#if CLIENT_DLL || GAME_DLL +using Source.Common.GarrysMod.Lua; +using Source.Common.Mathematics; +using Source.Common.Physics; + +using System.Buffers; +using System.Numerics; + +#if CLIENT_DLL +namespace Game.Client.GarrysMod; +#else +namespace Game.Server.GarrysMod; +#endif + +public static partial class LuaPhysObj +{ + const int MAX_OBJECT_LIST = 0x400; + + [LuaClass(typeof(IPhysicsObject), NullError = "Tried to use a NULL physics object!")] + public static readonly LuaClass LC_PhysObj = new("PhysObj", LuaType.PhysObj, null, null); + + public static void Push_PhysObj(IPhysicsObject? obj) => LC_PhysObj.Push(obj); + + public static bool IsPhysicsObjectValid(IPhysicsObject? obj) { + if (obj == null) + return false; + + ReadOnlySpan objects = physenv.GetObjectList(); + for (int i = 0; i < objects.Length; i++) + if (objects[i] == obj) + return true; + + return false; + } + + public static IPhysicsObject? Get_IPhysicsObject(int stackPos, bool allowNull) { + IPhysicsObject? obj = LC_PhysObj.Get(stackPos) as IPhysicsObject; + if (obj != null) { + if (IsPhysicsObjectValid(obj)) + return obj; + obj = null; + } + + if (!allowNull) + g_Lua!.Error("Tried to use a NULL physics object!"); + + return obj; + } + + static bool IsFinite(in Vector3 v) => float.IsFinite(v.X) && float.IsFinite(v.Y) && float.IsFinite(v.Z); + + static BaseEntity? GetGameEntity(IPhysicsObject obj) => obj.GetGameData() as BaseEntity; + + [LuaMethod] + static int PhysObj__GetName(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + lua.PushString(obj.GetName()); + return 1; + } + + [LuaMethod] + static int PhysObj____eq(ILuaInterface lua) { + IPhysicsObject? a = Get_IPhysicsObject(1, false); + IPhysicsObject? b = Get_IPhysicsObject(2, false); + lua.PushBool(a == b); + return 1; + } + + [LuaMethod] + static int PhysObj____tostring(ILuaInterface lua) { + IPhysicsObject? obj = LC_PhysObj.Get(1) as IPhysicsObject; + if (obj == null || !IsPhysicsObjectValid(obj)) { + lua.PushString("[NULL PhysObject]"); + return 1; + } + + BaseEntity? ent = GetGameEntity(obj); + int index = ent != null ? ent.EntIndex() : -1; + string str = $"PhysObject [{obj.GetName()}][Entity {index}]"; + lua.PushString(str.Length > 0xFF ? str[..0xFF] : str); + return 1; + } + + [LuaMethod] + static int PhysObj__IsValid(ILuaInterface lua) { + IPhysicsObject? obj = Get_IPhysicsObject(1, true); + lua.PushBool(obj != null && IsPhysicsObjectValid(obj)); + return 1; + } + + [LuaMethod] + static int PhysObj__Wake(ILuaInterface lua) { + Get_IPhysicsObject(1, false)!.Wake(); + return 0; + } + + [LuaMethod] + static int PhysObj__Sleep(ILuaInterface lua) { + Get_IPhysicsObject(1, false)!.Sleep(); + return 0; + } + + [LuaMethod] + static int PhysObj__IsAsleep(ILuaInterface lua) { + lua.PushBool(Get_IPhysicsObject(1, false)!.IsAsleep()); + return 1; + } + + [LuaMethod] + static int PhysObj__GetEntity(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + BaseEntity? ent = GetGameEntity(obj); + if (ent == null) + return 0; + + LuaEntity.Push_Entity(ent); + return 1; + } + + [LuaMethod] + static int PhysObj__SetMass(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + float mass = (float)lua.GetNumber(2); + if (0.1 >= mass) + mass = 1.0f; + else if (mass > 50000.0f) + mass = 50000.0f; + + obj.SetMass(mass); + return 0; + } + + [LuaMethod] + static int PhysObj__GetMass(ILuaInterface lua) { + lua.PushNumber(Get_IPhysicsObject(1, false)!.GetMass()); + return 1; + } + + [LuaMethod] + static int PhysObj__GetInvMass(ILuaInterface lua) { + lua.PushNumber(Get_IPhysicsObject(1, false)!.GetInvMass()); + return 1; + } + + [LuaMethod] + static int PhysObj__GetEnergy(ILuaInterface lua) { + lua.PushNumber(Get_IPhysicsObject(1, false)!.GetEnergy()); + return 1; + } + + [LuaMethod] + static int PhysObj__GetInertia(ILuaInterface lua) { + LuaVector.Push_Vector(Get_IPhysicsObject(1, false)!.GetInertia()); + return 1; + } + + [LuaMethod] + static int PhysObj__GetInvInertia(ILuaInterface lua) { + LuaVector.Push_Vector(Get_IPhysicsObject(1, false)!.GetInvInertia()); + return 1; + } + + [LuaMethod] + static int PhysObj__SetInertia(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 inertia = LuaVector.Get_Vector(2); + if (!IsFinite(inertia)) + return 0; + + if (inertia.X == 0) + inertia.X = 1; + if (inertia.Y == 0) + inertia.Y = 1; + if (inertia.Z == 0) + inertia.Z = 1; + + if (obj.IsMotionEnabled()) { + obj.SetInertia(inertia); + return 0; + } + + obj.EnableMotion(true); + obj.SetInertia(inertia); + obj.EnableMotion(false); + return 0; + } + + [LuaMethod] + static int PhysObj__SetDamping(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + float speed = (float)lua.GetNumber(2); + float rot = (float)lua.GetNumber(3); + obj.SetDamping(ref speed, ref rot); + return 0; + } + + [LuaMethod] + static int PhysObj__GetDamping(ILuaInterface lua) { + Get_IPhysicsObject(1, false)!.GetDamping(out float speed, out float rot); + lua.PushNumber(speed); + lua.PushNumber(rot); + return 2; + } + + [LuaMethod] + static int PhysObj__GetSpeedDamping(ILuaInterface lua) { + Get_IPhysicsObject(1, false)!.GetDamping(out float speed, out _); + lua.PushNumber(speed); + return 1; + } + + [LuaMethod] + static int PhysObj__GetRotDamping(ILuaInterface lua) { + Get_IPhysicsObject(1, false)!.GetDamping(out _, out float rot); + lua.PushNumber(rot); + return 1; + } + + [LuaMethod] + static int PhysObj__EnableCollisions(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + obj.EnableCollisions(lua.GetBool(2)); + return 0; + } + + [LuaMethod] + static int PhysObj__EnableGravity(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + obj.EnableGravity(lua.GetBool(2)); + return 0; + } + + [LuaMethod] + static int PhysObj__EnableDrag(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + obj.EnableDrag(lua.GetBool(2)); + return 0; + } + + [LuaMethod] + static int PhysObj__EnableMotion(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + bool enable = lua.GetBool(2); + + BaseEntity? ent = GetGameEntity(obj); + if (ent != null && ent.IsRagdollProp()) { + IPhysicsObject[] list = ArrayPool.Shared.Rent(MAX_OBJECT_LIST); + try { + int count = ent.VPhysicsGetObjectList(list.AsSpan(0, MAX_OBJECT_LIST)); + for (int i = 0; i < count; i++) + list[i].EnableMotion(enable); + } + finally { + ArrayPool.Shared.Return(list, true); + } + } + + obj.EnableMotion(enable); + return 0; + } + + [LuaMethod] + static int PhysObj__IsMotionEnabled(ILuaInterface lua) { + lua.PushBool(Get_IPhysicsObject(1, false)!.IsMotionEnabled()); + return 1; + } + + [LuaMethod] + static int PhysObj__IsCollisionEnabled(ILuaInterface lua) { + lua.PushBool(Get_IPhysicsObject(1, false)!.IsCollisionEnabled()); + return 1; + } + + [LuaMethod] + static int PhysObj__IsGravityEnabled(ILuaInterface lua) { + lua.PushBool(Get_IPhysicsObject(1, false)!.IsGravityEnabled()); + return 1; + } + + [LuaMethod] + static int PhysObj__IsDragEnabled(ILuaInterface lua) { + lua.PushBool(Get_IPhysicsObject(1, false)!.IsDragEnabled()); + return 1; + } + + [LuaMethod] + static int PhysObj__IsMoveable(ILuaInterface lua) { + lua.PushBool(Get_IPhysicsObject(1, false)!.IsMoveable()); + return 1; + } + + [LuaMethod] + static int PhysObj__SetMaterial(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + int index = (int)physprops.GetSurfaceIndex(lua.GetString(2) ?? ""); + if (index > 0) + obj.SetMaterialIndex(index); + return 0; + } + + [LuaMethod] + static int PhysObj__GetMaterial(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + ReadOnlySpan name = physprops.GetPropName(obj.GetMaterialIndex()); + lua.PushString(name.IsEmpty ? "default" : name); + return 1; + } + + [LuaMethod] + static int PhysObj__LocalToWorld(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 local = LuaVector.Get_Vector(2); + obj.LocalToWorld(out Vector3 world, local); + LuaVector.Push_Vector(world); + return 1; + } + + [LuaMethod] + static int PhysObj__WorldToLocal(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 world = LuaVector.Get_Vector(2); + obj.WorldToLocal(out Vector3 local, world); + LuaVector.Push_Vector(local); + return 1; + } + + [LuaMethod] + static int PhysObj__LocalToWorldVector(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 local = LuaVector.Get_Vector(2); + obj.LocalToWorldVector(out Vector3 world, local); + LuaVector.Push_Vector(world); + return 1; + } + + [LuaMethod] + static int PhysObj__WorldToLocalVector(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 world = LuaVector.Get_Vector(2); + obj.WorldToLocalVector(out Vector3 local, world); + LuaVector.Push_Vector(local); + return 1; + } + + [LuaMethod] + static int PhysObj__ApplyTorqueCenter(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 torque = LuaVector.Get_Vector(2); + if (IsFinite(torque)) + obj.ApplyTorqueCenter(torque); + return 0; + } + + [LuaMethod] + static int PhysObj__ApplyForceCenter(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 force = LuaVector.Get_Vector(2); + if (IsFinite(force)) + obj.ApplyForceCenter(force); + return 0; + } + + [LuaMethod] + static int PhysObj__ApplyForceOffset(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 force = LuaVector.Get_Vector(2); + if (IsFinite(force)) { + Vector3 position = LuaVector.Get_Vector(3); + obj.ApplyForceOffset(force, position); + } + return 0; + } + + [LuaMethod] + static int PhysObj__CalculateForceOffset(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 force = LuaVector.Get_Vector(2); + Vector3 position = LuaVector.Get_Vector(3); + obj.CalculateForceOffset(force, position, out Vector3 centerForce, out Vector3 centerTorque); + LuaVector.Push_Vector(centerForce); + LuaVector.Push_Vector(centerTorque); + return 2; + } + + [LuaMethod] + static int PhysObj__CalculateVelocityOffset(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 force = LuaVector.Get_Vector(2); + Vector3 position = LuaVector.Get_Vector(3); + obj.CalculateVelocityOffset(force, position, out Vector3 centerVelocity, out Vector3 centerAngularVelocity); + LuaVector.Push_Vector(centerVelocity); + LuaVector.Push_Vector(centerAngularVelocity); + return 2; + } + + [LuaMethod] + static int PhysObj__SetPos(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + BaseEntity? ent = GetGameEntity(obj); + if (ent != null && ent.IsRagdollProp()) + return 0; + + Vector3 pos = LuaVector.Get_Vector(2); + bool teleport = lua.GetType(3) != LuaType.Nil && lua.GetBool(3); + + float max = MAX_COORD_FLOAT * 8; + if (!(-max < pos.X && pos.X < max && -max < pos.Y && pos.Y < max && -max < pos.Z && pos.Z < max)) { + Warning($"PhysObj:SetPos( {pos.X:F6} {pos.Y:F6} {pos.Z:F6} ): Ignoring unreasonable position.\n"); + return 0; + } + + obj.GetPosition(out _, out QAngle angles); + obj.SetPosition(pos, angles, teleport); + return 0; + } + + [LuaMethod] + static int PhysObj__SetAngles(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + BaseEntity? ent = GetGameEntity(obj); + if (ent != null && ent.IsRagdollProp()) + return 0; + + QAngle angles = LuaAngle.Get_Angle(2); + float max = 360000f * 360000f; + if (!(-max < angles.X && angles.X < max && -max < angles.Y && angles.Y < max && -max < angles.Z && angles.Z < max)) { + Warning($"PhysObj:SetAngles( {angles.X:F6} {angles.Y:F6} {angles.Z:F6} ): Ignoring unreasonable angles.\n"); + return 0; + } + + obj.GetPosition(out Vector3 pos, out _); + obj.SetPosition(pos, angles, true); + return 0; + } + + [LuaMethod] + static int PhysObj__GetPos(ILuaInterface lua) { + Get_IPhysicsObject(1, false)!.GetPosition(out Vector3 pos, out _); + LuaVector.Push_Vector(pos); + return 1; + } + + [LuaMethod] + static int PhysObj__GetAngles(ILuaInterface lua) { + Get_IPhysicsObject(1, false)!.GetPosition(out _, out QAngle angles); + LuaAngle.Push_Angle(angles); + return 1; + } + + [LuaMethod] + static int PhysObj__SetVelocity(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 velocity = LuaVector.Get_Vector(2); + if (!IsFinite(velocity)) + return 0; + + obj.GetVelocity(out _, out Vector3 angular); + obj.SetVelocity(velocity, angular); + return 0; + } + + [LuaMethod] + static int PhysObj__SetVelocityInstantaneous(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 velocity = LuaVector.Get_Vector(2); + if (!IsFinite(velocity)) + return 0; + + obj.GetVelocity(out _, out Vector3 angular); + obj.SetVelocityInstantaneous(velocity, angular); + return 0; + } + + [LuaMethod] + static int PhysObj__SetAngleVelocity(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 angular = LuaVector.Get_Vector(2); + if (!IsFinite(angular)) + return 0; + + obj.GetVelocity(out Vector3 velocity, out _); + obj.SetVelocity(velocity, angular); + return 0; + } + + [LuaMethod] + static int PhysObj__SetAngleVelocityInstantaneous(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 angular = LuaVector.Get_Vector(2); + if (!IsFinite(angular)) + return 0; + + obj.GetVelocity(out Vector3 velocity, out _); + obj.SetVelocityInstantaneous(velocity, angular); + return 0; + } + + [LuaMethod] + static int PhysObj__GetVelocity(ILuaInterface lua) { + Get_IPhysicsObject(1, false)!.GetVelocity(out Vector3 velocity, out _); + LuaVector.Push_Vector(velocity); + return 1; + } + + [LuaMethod] + static int PhysObj__GetVelocityAtPoint(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 point = LuaVector.Get_Vector(2); + obj.GetVelocityAtPoint(point, out Vector3 velocity); + LuaVector.Push_Vector(velocity); + return 1; + } + + [LuaMethod] + static int PhysObj__GetAngleVelocity(ILuaInterface lua) { + Get_IPhysicsObject(1, false)!.GetVelocity(out _, out Vector3 angular); + LuaVector.Push_Vector(angular); + return 1; + } + + [LuaMethod] + static int PhysObj__AddVelocity(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 velocity = LuaVector.Get_Vector(2); + if (IsFinite(velocity)) + obj.AddVelocity(velocity, default); + return 0; + } + + [LuaMethod] + static int PhysObj__AddAngleVelocity(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 angular = LuaVector.Get_Vector(2); + if (IsFinite(angular)) + obj.AddVelocity(default, angular); + return 0; + } + + [LuaMethod] + static int PhysObj__ComputeShadowControl(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + LuaObject table = new(2, (LuaType)(-1)); + if (!table.isTable()) { + lua.TypeError("table", 2); + table.UnReference(); + return 0; + } + + HLShadowControlParams parms = default; + float secondsToArrive = table.GetMemberFloat("secondstoarrive", (float)(gpGlobals.IntervalPerTick * 2)); + float delta = table.GetMemberFloat("delta", 0.1f); + parms.TargetPosition = table.GetMemberVector("pos", default); + parms.MaxAngular = table.GetMemberFloat("maxangular", 1.0f); + parms.MaxDampAngular = table.GetMemberFloat("maxangulardamp", 1.0f); + parms.MaxSpeed = table.GetMemberFloat("maxspeed", 1.0f); + parms.MaxDampSpeed = table.GetMemberFloat("maxspeeddamp", 1.0f); + parms.DampFactor = table.GetMemberFloat("dampfactor", 1.0f); + parms.TeleportDistance = table.GetMemberFloat("teleportdistance", 0.0f); + parms.TargetRotation = table.GetMemberAngle("angle", default); + + if (secondsToArrive != 0) + obj.ComputeShadowControl(parms, secondsToArrive, delta); + else + lua.Error("ComputeShadowControl Error! secondstoarrive cannot be 0!\n"); + + table.UnReference(); + return 0; + } + + [LuaMethod] + static int PhysObj__SetDragCoefficient(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + float drag = (float)lua.GetNumber(2); + obj.SetDragCoefficient(drag, null); + return 0; + } + + [LuaMethod] + static int PhysObj__SetAngleDragCoefficient(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + float drag = (float)lua.GetNumber(2); + obj.SetDragCoefficient(null, drag); + return 0; + } + + [LuaMethod] + static int PhysObj__SetBuoyancyRatio(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + obj.SetBuoyancyRatio((float)lua.GetNumber(2)); + return 0; + } + + [LuaMethod] + static int PhysObj__GetBuoyancyRatio(ILuaInterface lua) { + lua.PushNumber(Get_IPhysicsObject(1, false)!.GetBuoyancyRatio()); + return 1; + } + + [LuaMethod] + static int PhysObj__GetMassCenter(ILuaInterface lua) { + LuaVector.Push_Vector(Get_IPhysicsObject(1, false)!.GetMassCenterLocalSpace()); + return 1; + } + + [LuaMethod] + static int PhysObj__GetContents(ILuaInterface lua) { + lua.PushNumber((double)Get_IPhysicsObject(1, false)!.GetContents()); + return 1; + } + + [LuaMethod] + static int PhysObj__SetContents(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + obj.SetContents((uint)(long)lua.GetNumber(2)); + return 0; + } + + [LuaMethod] + static int PhysObj__OutputDebugInfo(ILuaInterface lua) { + Get_IPhysicsObject(1, false)!.OutputDebugInfo(); + return 0; + } + + [LuaMethod] + static int PhysObj__AlignAngles(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + QAngle from = LuaAngle.Get_Angle(2); + QAngle to = LuaAngle.Get_Angle(3); + + obj.GetPosition(out _, out QAngle physAngles); + + MathLib.AngleMatrix(from, out Matrix3x4 fromMatrix); + MathLib.AngleMatrix(physAngles, out Matrix3x4 physMatrix); + MathLib.MatrixInvert(physMatrix, out Matrix3x4 physInverse); + + Matrix4x4 a = physInverse; + Matrix4x4 b = fromMatrix; + MathLib.MatrixMultiply(a, b, out Matrix4x4 local); + MathLib.MatrixToAngles(local, out QAngle localAngles); + + MathLib.AngleMatrix(to, out Matrix3x4 toMatrix); + MathLib.AngleMatrix(localAngles, out Matrix3x4 localMatrix); + MathLib.MatrixInvert(localMatrix, out Matrix3x4 localInverse); + + Matrix4x4 c = toMatrix; + Matrix4x4 d = localInverse; + MathLib.MatrixMultiply(c, d, out Matrix4x4 result); + MathLib.MatrixToAngles(result, out QAngle resultAngles); + + LuaAngle.Push_Angle(resultAngles); + return 1; + } + + [LuaMethod] + static int PhysObj__RotateAroundAxis(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 axis = LuaVector.Get_Vector(2); + float degrees = (float)lua.GetNumber(3); + + obj.GetPosition(out _, out QAngle physAngles); + MathLib.AngleMatrix(physAngles, out Matrix3x4 physMatrix); + Matrix4x4 phys = physMatrix; + + MathLib.MatrixBuildRotationAboutAxis(out Matrix4x4 rotation, axis, degrees); + MathLib.MatrixMultiply(rotation, phys, out Matrix4x4 result); + MathLib.MatrixToAngles(result, out QAngle resultAngles); + + LuaAngle.Push_Angle(resultAngles); + return 1; + } + + [LuaMethod] + static int PhysObj__UpdateShadow(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 pos = LuaVector.Get_Vector(2); + QAngle angles = LuaAngle.Get_Angle(3); + + float max = MAX_COORD_FLOAT * 8; + if (!(-max < pos.X && pos.X < max && -max < pos.Y && pos.Y < max && -max < pos.Z && pos.Z < max)) { + Warning($"PhysObj:UpdateShadow( {pos.X:F6} {pos.Y:F6} {pos.Z:F6} ): Ignoring unreasonable position.\n"); + return 0; + } + + float maxAngle = 360000f * 360000f; + if (!(-maxAngle < angles.X && angles.X < maxAngle && -maxAngle < angles.Y && angles.Y < maxAngle && -maxAngle < angles.Z && angles.Z < maxAngle)) { + Warning($"PhysObj:UpdateShadow( {angles.X:F6} {angles.Y:F6} {angles.Z:F6} ): Ignoring unreasonable angles.\n"); + return 0; + } + + obj.UpdateShadow(pos, angles, false, (float)lua.GetNumber(4)); + return 0; + } + + [LuaMethod] + static int PhysObj__GetShadowPos(ILuaInterface lua) { + Get_IPhysicsObject(1, false)!.GetShadowPosition(out Vector3 pos, out _); + LuaVector.Push_Vector(pos); + return 1; + } + + [LuaMethod] + static int PhysObj__GetShadowAngles(ILuaInterface lua) { + Get_IPhysicsObject(1, false)!.GetShadowPosition(out _, out QAngle angles); + LuaAngle.Push_Angle(angles); + return 1; + } + + [LuaMethod] + static int PhysObj__IsPenetrating(ILuaInterface lua) { + PhysicsFlags flags = Get_IPhysicsObject(1, false)!.GetGameFlags(); + lua.PushBool((((ushort)flags >> 6) & 1) != 0); + return 1; + } + + [LuaMethod] + static int PhysObj__HasGameFlag(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + ushort flags = (ushort)obj.GetGameFlags(); + lua.PushBool((flags & (int)lua.GetNumber(2)) != 0); + return 1; + } + + [LuaMethod] + static int PhysObj__AddGameFlag(ILuaInterface lua) { + IPhysicsObject? obj = Get_IPhysicsObject(1, false); + ushort flag = (ushort)(int)lua.GetNumber(2); + if (obj != null) + obj.SetGameFlags((PhysicsFlags)(ushort)((ushort)obj.GetGameFlags() | flag)); + return 0; + } + + [LuaMethod] + static int PhysObj__ClearGameFlag(ILuaInterface lua) { + IPhysicsObject? obj = Get_IPhysicsObject(1, false); + ushort flag = (ushort)(int)lua.GetNumber(2); + if (obj != null) + obj.SetGameFlags((PhysicsFlags)(ushort)((ushort)obj.GetGameFlags() & ~flag)); + return 0; + } + + [LuaMethod] + static int PhysObj__GetAABB(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + PhysCollide? collide = obj.GetCollide(); + if (collide == null) + return 0; + + physcollision.CollideGetAABB(out Vector3 mins, out Vector3 maxs, collide, vec3_origin, vec3_angle); + LuaVector.Push_Vector(mins); + LuaVector.Push_Vector(maxs); + return 2; + } + + [LuaMethod] + static int PhysObj__GetVolume(ILuaInterface lua) { + PhysCollide? collide = Get_IPhysicsObject(1, false)!.GetCollide(); + if (collide == null) + return 0; + + lua.PushNumber(physcollision.CollideVolume(collide)); + return 1; + } + + [LuaMethod] + static int PhysObj__GetSurfaceArea(ILuaInterface lua) { + PhysCollide? collide = Get_IPhysicsObject(1, false)!.GetCollide(); + if (collide == null) + return 0; + + lua.PushNumber(physcollision.CollideSurfaceArea(collide)); + return 1; + } + +#if GAME_DLL + [LuaMethod] + static int PhysObj__GetStress(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + BaseEntity? ent = GetGameEntity(obj); + CalculateObjectStress(obj, ent!, out VPhysicsObjectStress stress); + lua.PushNumber(stress.ExertedStress); + lua.PushNumber(stress.ReceivedStress); + return 2; + } +#endif + + [LuaMethod] + static int PhysObj__GetMesh(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + PhysCollide? collide = obj.GetCollide(); + if (collide == null) + return 0; + + int count = physcollision.CreateDebugMesh(collide, out Span verts); + if (verts.IsEmpty) + return 0; + + LuaTable table = new(null, (uint)count); + for (int i = 0; i < count; i++) { + LuaTable entry = new(null, 0); + entry.SetMemberVector("pos", verts[i]); + table.SetMember((float)(i + 1), entry); + entry.UnReference(); + } + + physcollision.DestroyDebugMesh(count, verts); + table.Push(); + table.UnReference(); + return 1; + } + + [LuaMethod] + static int PhysObj__GetMeshConvexes(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + PhysCollide? collide = obj.GetCollide(); + if (collide == null) + return 0; + + ICollisionQuery? query = physcollision.CreateQueryModel(collide); + if (query == null) + return 0; + + int convexCount = query.ConvexCount(); + LuaTable table = new(null, (uint)convexCount); + Span tri = stackalloc Vector3[3]; + for (int c = 0; c < convexCount; c++) { + int triCount = query.TriangleCount(c); + LuaTable convex = new(null, 0); + for (int t = 0; t < triCount; t++) { + tri.Clear(); + query.GetTriangleVerts(c, t, tri); + for (int v = 0; v < 3; v++) { + LuaTable vertex = new(null, 0); + vertex.SetMemberVector("pos", tri[v]); + convex.SetMember((float)(t * 3 + v + 1), vertex); + vertex.UnReference(); + } + } + + table.SetMember((float)(c + 1), convex); + convex.UnReference(); + } + + physcollision.DestroyQueryModel(query); + table.Push(); + table.UnReference(); + return 1; + } + + [LuaMethod] + static int PhysObj__RecheckCollisionFilter(ILuaInterface lua) { + Get_IPhysicsObject(1, false)!.RecheckCollisionFilter(); + return 0; + } + + [LuaMethod] + static int PhysObj__GetPositionMatrix(ILuaInterface lua) { + Get_IPhysicsObject(1, false)!.GetPositionMatrix(out Matrix3x4 matrix); + Matrix4x4 vmatrix = matrix; + LuaVMatrix.Push_VMatrix(vmatrix); + return 1; + } + + [LuaMethod] + static int PhysObj__GetIndex(ILuaInterface lua) { + lua.PushNumber((double)Get_IPhysicsObject(1, false)!.GetGameIndex()); + return 1; + } + + [LuaMethod] + static int PhysObj__GetFrictionSnapshot(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + LuaTable table = new(null, 0); + IPhysicsFrictionSnapshot snapshot = obj.CreateFrictionSnapshot(); + + int index = 0; + while (snapshot.IsValid()) { + LuaTable entry = new(null, 0); + entry.SetMemberPhysObject("Other", snapshot.GetObject(1)); + entry.SetMember("EnergyAbsorbed", snapshot.GetEnergyAbsorbed()); + entry.SetMember("FrictionCoefficient", snapshot.GetFrictionCoefficient()); + entry.SetMember("NormalForce", snapshot.GetNormalForce()); + snapshot.GetSurfaceNormal(out Vector3 normal); + entry.SetMemberVector("Normal", normal); + snapshot.GetContactPoint(out Vector3 contact); + entry.SetMemberVector("ContactPoint", contact); + entry.SetMember("Material", (float)snapshot.GetMaterial(0)); + entry.SetMember("MaterialOther", (float)snapshot.GetMaterial(1)); + index++; + table.SetMember((float)index, entry); + snapshot.NextFrictionData(); + entry.UnReference(); + } + + obj.DestroyFrictionSnapshot(snapshot); + table.Push(); + table.UnReference(); + return 1; + } +} +#endif diff --git a/Game.Shared/GarrysMod/PhysBeam.cs b/Game.Shared/GarrysMod/PhysBeam.cs index 1e3682d0..de77447e 100644 --- a/Game.Shared/GarrysMod/PhysBeam.cs +++ b/Game.Shared/GarrysMod/PhysBeam.cs @@ -1,5 +1,15 @@ #if (CLIENT_DLL || GAME_DLL) && GMOD_DLL using Source.Common; +using Source.Common.Formats.BSP; +#if CLIENT_DLL +using Game.Client.GarrysMod; +using Source.Common.Client; +using Source.Common.Engine; +using Source.Common.GarrysMod.Lua; +using Source.Common.MaterialSystem; +using Source.Common.Mathematics; + +#endif using Game.Shared; using Source; using System.Numerics; @@ -66,5 +76,318 @@ public partial class public bool IsOn; [NetworkName("m_iPhysBone")] public int PhysBone; + + public override EntityCapabilities ObjectCaps() => (base.ObjectCaps() & (EntityCapabilities)0x7ffffffd) | EntityCapabilities.DontSave; + +#if CLIENT_DLL + readonly MaterialReference Glow1 = new(); + readonly MaterialReference Glow2 = new(); + Model? BeamModel; + Model? BeamAModel; + + public override bool ShouldDraw() => !IsEffectActive(EntityEffects.NoDraw); + public override bool IsTransparent() => true; + public override RenderGroup GetRenderGroup() => RenderGroup.TranslucentEntity; + + public override void OnDataChanged(DataUpdateType updateType) { + base.OnDataChanged(updateType); + if (updateType != DataUpdateType.Created) + return; + + Glow1.Init("sprites/physg_glow1", MaterialDefines.TEXTURE_GROUP_CLIENT_EFFECTS, true); + Glow2.Init("sprites/physg_glow2", MaterialDefines.TEXTURE_GROUP_CLIENT_EFFECTS, true); + BeamModel = engine.LoadModel("sprites/physbeam.vmt", false); + BeamAModel = engine.LoadModel("sprites/physbeama.vmt", false); + } + + public void UpdateBeam(BaseEntity? target, in Vector3 holdPos, int bone) { + if (HoldPos != holdPos) + HoldPos = holdPos; + if (TargetEnt.Get() != target) + TargetEnt.Set(target); + if (PhysBone != bone) + PhysBone = bone; + } + + public void SetBeamEnabled(bool on) { + IsOn = on; + if (!on) + UpdateBeam(null, default, 0); + } + + static bool IsVisibleForBeam(BaseEntity entity) { + if (!entity.ShouldDraw()) + return false; + Source.RenderMode mode = entity.GetRenderMode(); + if (mode == Source.RenderMode.None) + return false; + if (mode != Source.RenderMode.Normal && entity.GetRenderColor().A == 0) + return false; + return true; + } + + static void NormalizeInPlace(ref Vector3 v) { + float radius = MathF.Sqrt(v.X * v.X + v.Y * v.Y + v.Z * v.Z); + float iradius = 1.0f / (radius + 1.1920929e-07f); + v *= iradius; + } + + static Color PlayerColorToColor(BaseEntity? owner) { + Color color = new(255, 255, 255, 255); + if (owner is C_GMOD_Player gmod) { + Vector3 pc = gmod.GetWeaponColor(); + NormalizeInPlace(ref pc); + color = new((byte)(int)(pc.X * 255), (byte)(int)(pc.Y * 255), (byte)(int)(pc.Z * 255), 255); + } + return color; + } + + static bool BindSprite(Model? model, IMatRenderContext renderContext) { + if (model == null || modelinfo.GetModelType(model) != ModelType.Sprite) + return false; + + EngineSprite? sprite = (EngineSprite?)modelinfo.GetModelExtraData(model); + IMaterial? material = sprite?.GetMaterial(Source.RenderMode.TransAdd, 0); + if (material == null) + return false; + + renderContext.Bind(material); + return true; + } + + void DrawGlow(in Vector3 origin, bool hasTarget, in QAngle angles) { + using MatRenderContextPtr renderContext = new(materials); + renderContext.Bind(Glow2.Get()!, this); + + MathLib.AngleVectors(angles, out Vector3 forward); + Color color = PlayerColorToColor(GetOwnerEntity()); + + Vector3 pos = origin; + if (!IsOn) { + for (int i = 0; i < 3; i++) { + color.A = (byte)random.RandomInt(120, 255); + float size = (float)(Math.Sin(gpGlobals.CurTime * 5.0 + i * 15.0) * 8.0 + 48.0); + BeamDraw.DrawSprite(pos, size, size, color); + pos += forward * 5.0f; + } + } + else { + int count = hasTarget ? 7 : 2; + for (int i = 0; i < count; i++) { + color.A = (byte)random.RandomInt(100, 255); + float size = random.RandomFloat(16.0f, 64.0f); + BeamDraw.DrawSprite(pos, size, size, color); + pos += forward * 2.0f; + } + } + } + + static void DrawBezierBeam(IMatRenderContext renderContext, in Vector3 p0, in Vector3 p1, in Vector3 p2, float width, in Vector3 color, float scroll) { + float length = (p0 - p2).Length(); + float scrollBase = (float)(scroll - Math.Floor(scroll)); + + BeamSegDraw segDraw = new(); + segDraw.Start(renderContext, 17, null); + for (int i = 0; i <= 16; i++) { + float t = i * 0.0625f; + float omt = 1.0f - t; + BeamSeg seg = default; + seg.Pos = p0 * (omt * omt) + p1 * (2 * t * omt) + p2 * (t * t); + seg.TexCoord = scrollBase - t * length * 0.02f; + seg.Width = width; + seg.Alpha = 1.0f; + seg.Color = (i == 0 || i == 16) ? default : color; + segDraw.NextSeg(ref seg); + } + segDraw.End(); + } + + public override int DrawModel(StudioFlags flags) { + if (GetOwnerEntity() is not C_BasePlayer player) + return 0; + + BaseCombatWeapon? weapon = player.GetActiveWeapon(); + if (weapon == null || !weapon.GetClassname().Equals("weapon_physgun", StringComparison.Ordinal)) + return 0; + + bool firstPerson = weapon.ShouldDrawUsingViewModel(); + BaseViewModel? viewModel = null; + if (!firstPerson) { + if (!IsVisibleForBeam(weapon) || !IsVisibleForBeam(player)) + return 0; + } + else { + if (CurrentViewID() == ViewID.Sky3D) + return 0; + viewModel = player.GetViewModel(0, true); + if (viewModel == null || !IsVisibleForBeam(viewModel)) + return 0; + } + + if (gGM != null && gGM.CallWithArgs((int)LUA_POOLEDSTRING.DrawPhysgunBeam)) { + LuaEntity.Push_Entity(player); + LuaEntity.Push_Entity(weapon); + g_Lua!.PushBool(IsOn); + LuaEntity.Push_Entity(TargetEnt.Get()); + g_Lua.PushNumber(PhysBone); + LuaVector.Push_Vector(HoldPos); + if (!gGM.CallFinish(6)) + return 0; + } + + using MatRenderContextPtr renderContext = new(materials); + + weapon.GetAttachment(1, out Vector3 attachOrigin, out QAngle attachAngles); + if (firstPerson) + viewModel!.GetAttachment(1, out attachOrigin, out attachAngles); + + if (!IsOn) { + if (firstPerson) + renderContext.DepthRange(0, 0.01f); + DrawGlow(attachOrigin, false, attachAngles); + if (firstPerson) + renderContext.DepthRange(0, 1); + return 1; + } + + if (!C_WeaponPhysGun.physgun_drawbeams.GetBool()) + return 0; + + Vector3 forward = C_WeaponPhysGun.GetAimForward(player); + + Vector3 end; + Vector3 mid; + bool hasTarget = false; + BaseEntity? target = TargetEnt.Get(); + if (target != null) { + QAngle targetAngles = target.GetAbsAngles(); + if (targetAngles.X == 0 && targetAngles.Y == 0 && targetAngles.Z == 0) + end = target.GetAbsOrigin() + HoldPos; + else + MathLib.VectorTransform(HoldPos, target.EntityToWorldTransform(), out end); + + if (target.GetSolid() == SolidType.VPhysics) { + Matrix3x4 boneMatrix = C_WeaponPhysGun.GetPhysBoneMatrix(target, PhysBone); + MathLib.VectorTransform(HoldPos, boneMatrix, out end); + } + + float dist = (end - attachOrigin).Length(); + mid = player.Weapon_ShootPosition() + forward * dist * 0.6f; + hasTarget = true; + } + else { + float range = C_WeaponPhysGun.physgun_maxrange.GetFloat(); + Vector3 shoot = player.Weapon_ShootPosition(); + Vector3 traceEnd = shoot + forward * range * 0.5f; + Util.TraceLine(shoot, traceEnd, (Mask)0x600400b, player, Source.CollisionGroup.None, out Trace tr); + end = tr.EndPos; + mid = tr.EndPos; + } + + if (firstPerson) + renderContext.DepthRange(0, 0.01f); + + double timeScale = hasTarget ? 10.0 : -2.0; + BindSprite(hasTarget ? BeamAModel : BeamModel, renderContext.Context); + + Vector3 beamColor = default; + if (player is C_GMOD_Player gmodPlayer) { + beamColor = gmodPlayer.GetWeaponColor(); + NormalizeInPlace(ref beamColor); + } + + double now = gpGlobals.CurTime; + DrawBezierBeam(renderContext.Context, attachOrigin, mid, end, 2.0f, beamColor, (float)(timeScale * now)); + DrawBezierBeam(renderContext.Context, end, mid, attachOrigin, random.RandomFloat(2.0f, 5.0f), beamColor, (float)(timeScale * now * 1.1)); + DrawBezierBeam(renderContext.Context, end, mid, attachOrigin, random.RandomFloat(2.0f, 6.0f), beamColor, (float)(timeScale * now * -0.9)); + + DrawGlow(attachOrigin, hasTarget, attachAngles); + + renderContext.Bind(Glow1.Get()!, this); + Color color = PlayerColorToColor(player); + int endCount = hasTarget ? 7 : 2; + for (int i = 0; i < endCount; i++) { + color.A = (byte)random.RandomInt(100, 255); + float size = random.RandomFloat(1.0f, 16.0f); + BeamDraw.DrawSprite(end, size, size, color); + } + + if (firstPerson) + renderContext.DepthRange(0, 1); + + return 1; + } +#endif + +#if GAME_DLL + Vector3 EndPos; + + public static PhysBeam? Create(BaseEntity? owner, in Vector3 position) { + PhysBeam? beam = BaseEntity.Create("physgun_beam", position, default, null) as PhysBeam; + if (beam == null) + return null; + + beam.SetMoveType(Source.MoveType.None); + beam.AddSolidFlags(SolidFlags.NotSolid); + beam.AddEffects(EntityEffects.NoShadow); + Util.SetSize(beam, new Vector3(-64), new Vector3(64)); + beam.SetOwnerEntity(owner); + beam.SetParent(owner, -1); + beam.AddEFlags(EFL.ForceCheckTransmit); + return beam; + } + + public void UpdateBeam(BaseEntity? target, in Vector3 holdPos, int bone) { + if (HoldPos != holdPos) { + NetworkStateChanged(); + HoldPos = holdPos; + } + if (TargetEnt.Get() != target) { + NetworkStateChanged(); + TargetEnt.Set(target); + } + if (PhysBone != bone) { + NetworkStateChanged(); + PhysBone = bone; + } + if (IsOn) + ComputeEndPos(); + } + + public void SetBeamEnabled(bool on) { + if (IsOn != on) { + NetworkStateChanged(); + IsOn = on; + } + if (!on) + UpdateBeam(null, default, 0); + } + + void ComputeEndPos() { + BasePlayer? owner = GetOwnerEntity() as BasePlayer; + if (owner == null || !owner.IsPlayer() || owner.GetActiveWeapon() == null) + return; + + BaseEntity? target = TargetEnt.Get(); + if (target != null) { + EndPos = target.GetAbsOrigin(); + return; + } + + Vector3 forward = WeaponPhysGun.GetAimForward(owner); + Vector3 start = owner.Weapon_ShootPosition(); + Vector3 end = start + forward * WeaponPhysGun.physgun_maxrange.GetFloat() * 0.5f; + + Ray ray = default; + ray.Init(start, end); + TraceFilterSimple filter = new(owner, Source.CollisionGroup.None); + enginetrace.TraceRay(ray, (Mask)0x600400b, ref filter, out Trace tr); + + if (r_visualizetraces.GetBool()) + Game.Shared.DebugOverlay.DebugDrawLine(tr.StartPos, tr.EndPos, 255, 0, 0, true, -1.0f); + + EndPos = tr.EndPos; + } +#endif } #endif diff --git a/Game.Shared/GarrysMod/WeaponPhysGun.cs b/Game.Shared/GarrysMod/WeaponPhysGun.cs index 1a837072..0581ceb7 100644 --- a/Game.Shared/GarrysMod/WeaponPhysGun.cs +++ b/Game.Shared/GarrysMod/WeaponPhysGun.cs @@ -1,4 +1,4 @@ -#if (CLIENT_DLL || GAME_DLL) && GMOD_DLL +#if (CLIENT_DLL || GAME_DLL) && GMOD_DLL #if CLIENT_DLL global using PhysBeam = Game.Client.C_PhysBeam; #else @@ -9,8 +9,23 @@ using Source; using Source.Common; +using Source.Common.Commands; +using Source.Common.Formats.BSP; +using Source.Common.Mathematics; +using Source.Common.Physics; +#if GAME_DLL +using Game.Server.GarrysMod; +using Source.Common.GarrysMod.Lua; +using System.Buffers; +#endif +#if CLIENT_DLL +using Game.Client.GarrysMod; +using Source.Common.Input; +using Source.Common.GarrysMod.Lua; +#endif using System.Numerics; +using System.Runtime.CompilerServices; #if CLIENT_DLL namespace Game.Client; @@ -71,6 +86,26 @@ public partial class public static readonly new DataMap PredMap = new([], typeof(C_WeaponPhysGun), BaseHL2MPCombatWeapon.PredMap); public override DataMap? GetPredDescMap() => PredMap; #endif + public static readonly ConVar physgun_minrange = new("physgun_minrange", "40", FCvar.Unregistered, "The minimum range of the Physics gun.", 32, 256); + public static readonly ConVar physgun_maxrange = new("physgun_maxrange", "4096", FCvar.Replicated | FCvar.Notify, "The maximum range of the Physics gun.", 128, 32768); + + static readonly ActTable[] acttable = [ + new() { BaseAct = Activity.ACT_MP_STAND_IDLE, WeaponAct = Activity.ACT_HL2MP_IDLE_PHYSGUN, Required = false }, + new() { BaseAct = Activity.ACT_MP_CROUCH_IDLE, WeaponAct = Activity.ACT_HL2MP_IDLE_CROUCH_PHYSGUN, Required = false }, + new() { BaseAct = Activity.ACT_MP_RUN, WeaponAct = Activity.ACT_HL2MP_RUN_PHYSGUN, Required = false }, + new() { BaseAct = Activity.ACT_MP_WALK, WeaponAct = Activity.ACT_HL2MP_WALK_PHYSGUN, Required = false }, + new() { BaseAct = Activity.ACT_MP_CROUCHWALK, WeaponAct = Activity.ACT_HL2MP_WALK_CROUCH_PHYSGUN, Required = false }, + new() { BaseAct = Activity.ACT_MP_ATTACK_STAND_PRIMARYFIRE, WeaponAct = Activity.ACT_HL2MP_GESTURE_RANGE_ATTACK_PHYSGUN, Required = false }, + new() { BaseAct = Activity.ACT_MP_ATTACK_CROUCH_PRIMARYFIRE, WeaponAct = Activity.ACT_HL2MP_GESTURE_RANGE_ATTACK_PHYSGUN, Required = false }, + new() { BaseAct = Activity.ACT_MP_RELOAD_STAND, WeaponAct = Activity.ACT_HL2MP_GESTURE_RELOAD_PHYSGUN, Required = false }, + new() { BaseAct = Activity.ACT_MP_RELOAD_CROUCH, WeaponAct = Activity.ACT_HL2MP_GESTURE_RELOAD_PHYSGUN, Required = false }, + new() { BaseAct = Activity.ACT_MP_JUMP, WeaponAct = Activity.ACT_HL2MP_JUMP_PHYSGUN, Required = false }, + new() { BaseAct = Activity.ACT_MP_SWIM, WeaponAct = Activity.ACT_HL2MP_SWIM_PHYSGUN, Required = false }, + new() { BaseAct = Activity.ACT_MP_SWIM_IDLE, WeaponAct = Activity.ACT_HL2MP_SWIM_IDLE_PHYSGUN, Required = false }, + ]; + + public override ReadOnlySpan ActivityList() => acttable; + [NetworkName("m_hPhysBeam")] public EHANDLE PhysBeam = new(); [NetworkName("m_vHitPosLocal")] @@ -78,74 +113,675 @@ public partial class [NetworkName("m_hGrabbedEntity")] public EHANDLE GrabbedEntity = new(); - public override void Activate() { - base.Activate(); + public override bool CanBePickedUpByNPCs() => false; + public override float GetFireRate() => 0.01f; + public override ReadOnlySpan GetHoldType() => "physgun"; + public override bool HasAnyAmmo() => true; + + float Distance; + float NextReload; + + public static Vector3 GetAimForward(BasePlayer player) { + if (player.WorldClicking && !player.DisableWorldClicking) + return player.WorldClickVector; + + if (player.IsInAVehicle()) { + player.CacheVehicleView(); + MathLib.AngleVectors(player.VehicleViewAngles, out Vector3 vehicleForward); + return vehicleForward; + } + + MathLib.AngleVectors(player.EyeAngles(), out Vector3 forward); + return forward; } - // ActivityList? + public static Matrix3x4 GetPhysBoneMatrix(BaseEntity ent, int bone) { + IPhysicsObject? phys = null; + ent.VPhysicsGetObjectList(new Span(ref phys)); + phys ??= ent.VPhysicsGetObject(); - public -#if CLIENT_DLL - C_WeaponPhysGun -#else - WeaponPhysGun -#endif - () { + Vector3 position; + QAngle angles; + if (phys != null) + phys.GetPosition(out position, out angles); + else { + position = ent.GetAbsOrigin(); + angles = ent.GetAbsAngles(); + } + MathLib.AngleMatrix(angles, position, out Matrix3x4 matrix); + return matrix; } - public override bool CanBePickedUpByNPCs() => false; - public EHANDLE CreatePhysBeam() { - RemovePhysBeam(); - if ((GetEFlags() & EFL.DirtyAbsTransform) != 0) - CalcAbsolutePosition(); +#if GAME_DLL + public static readonly ConVar phys_spinspeed = new("phys_spinspeed", "200", 0, "The sensitivity of Physics Gun rotation when using the movement keys. It's serverside."); - return default; + const float ReloadInterval = 0.01f; + + float LastYaw; + Vector3 Target; + QAngle Angles; + IPhysicsObject? PhysObj; + readonly PhysGunControllerPoint GravCallback = new(); + + public PhysGunControllerPoint GetGravCallback() => GravCallback; + + public WeaponPhysGun() { + Skin = 1; + } + + public override void Activate() { + base.Activate(); + Skin = 1; } + + public override void Precache() { + base.Precache(); + } + + public override void OnRestore() { + base.OnRestore(); + DropEntity(); + CreatePhysBeam(); + } + public override bool Deploy() { DropEntity(); CreatePhysBeam(); return base.Deploy(); } + + public override bool Holster(BaseCombatWeapon? switchingTo) { + DropEntity(); + RemovePhysBeam(); + return base.Holster(switchingTo); + } + public override void Drop(in Vector3 velocity) { DropEntity(); RemovePhysBeam(); base.Drop(velocity); } - public void DropEntity(){ - } - public override float GetFireRate() => 0.01f; - public override ReadOnlySpan GetHoldType() => "physgun"; - public override bool HasAnyAmmo() => true; - public override bool Holster(BaseCombatWeapon switchingTo) { + public override void UpdateOnRemove() { + RemovePhysBeam(); DropEntity(); + base.UpdateOnRemove(); + } + + public void CreatePhysBeam() { RemovePhysBeam(); - return base.Holster(switchingTo); + + Game.Server.PhysBeam? beam = Game.Server.PhysBeam.Create(GetOwnerEntity(), GetAbsOrigin()); + NetworkStateChanged(); + PhysBeam.Set(beam); } - public override void ItemPostFrame() { - base.ItemPostFrame(); + + public void RemovePhysBeam() { + Util.Remove(PhysBeam.Get()); + NetworkStateChanged(); + PhysBeam.Set(null); } - public override void Precache() { - base.Precache(); + + public bool ValidatePhysObj() { + if (PhysObj != null && !PhysGunGlobals.IsPhysicsObjectValid(PhysObj)) + PhysObj = null; + + return PhysObj != null; } - public override void PrimaryAttack() { - base.PrimaryAttack(); + + void SetHitPosLocal(in Vector3 value) { + NetworkStateChanged(); + HitPosLocal = value; } + + void UpdateBeam(BaseEntity? target, BasePlayer owner) { + if (PhysBeam.Get() is not Game.Server.PhysBeam beam) + return; + + beam.UpdateBeam(target, HitPosLocal, 0); + beam.SetAbsOrigin(owner.GetAbsOrigin()); + } + + void UpdateBeam(BaseEntity? target, BasePlayer owner, int bone) { + if (PhysBeam.Get() is not Game.Server.PhysBeam beam) + return; + + beam.UpdateBeam(target, HitPosLocal, bone); + beam.SetAbsOrigin(owner.GetAbsOrigin()); + } + + void EnablePhysBeam(bool on) { + if (PhysBeam.Get() is Game.Server.PhysBeam beam) + beam.SetBeamEnabled(on); + } + public override bool Reload() { - return base.Reload(); + BasePlayer? owner = GetOwner() as BasePlayer; + if (owner == null) + return false; + + if (NextReload > gpGlobals.CurTime) + return false; + + NextReload = (float)gpGlobals.CurTime + ReloadInterval; + + if (gGM != null && gGM.CallWithArgs((int)LUA_POOLEDSTRING.OnPhysgunReload)) { + LuaEntity.Push_Entity(this); + LuaEntity.Push_Entity(owner); + gGM.CallNoReturns(2); + } + + return true; } - public void RemovePhysBeam(){ + public override void PrimaryAttack() { + if (NextPrimaryAttack > gpGlobals.CurTime) + return; + + GMOD_Player? player = ToGMODPlayer(GetOwner()); + if (player == null) + return; + + if (GrabbedEntity.Get() != null) + return; + + Vector3 eye = player.Weapon_ShootPosition(); + Vector3 forward = PhysGunGlobals.GetAimForward(player); + + Ray ray = default; + ray.Init(eye, eye + forward * physgun_maxrange.GetFloat() * 2); + TraceFilterSimple filter = new(player, Source.CollisionGroup.None); + enginetrace.TraceRay(ray, (Mask)0x600400f, ref filter, out Trace tr); + + if (r_visualizetraces.GetBool()) + Game.Shared.DebugOverlay.DebugDrawLine(tr.StartPos, tr.EndPos, 255, 0, 0, true, -1.0f); + + if (tr.Fraction == 1 && !tr.AllSolid && !tr.StartSolid) + return; + + BaseEntity? hit = tr.Ent; + if (!tr.DidHitWorld() && hit != null && gGM != null && gGM.CallWithArgs((int)LUA_POOLEDSTRING.PhysgunPickup)) { + LuaEntity.Push_Entity(player); + LuaEntity.Push_Entity(hit); + if (gGM.CallFinish(2)) { + PhysObj = null; + NetworkStateChanged(); + GrabbedEntity.Set(hit); + } + } + + BaseEntity? grabbed = GrabbedEntity.Get(); + if (grabbed != null) { + if (gGM != null && gGM.CallWithArgs((int)LUA_POOLEDSTRING.OnPhysgunPickup)) { + LuaEntity.Push_Entity(player); + LuaEntity.Push_Entity(grabbed); + gGM.CallNoReturns(2); + } + + LastYaw = player.EyeAngles().Y; + Angles = grabbed.GetAbsAngles(); + + if (grabbed is PhysicsProp prop) + prop.ThrownByPlayer = false; + + Distance = (eye - tr.EndPos).Length(); + + QAngle absAngles = grabbed.GetAbsAngles(); + Vector3 hitLocal; + if (absAngles.X == 0 && absAngles.Y == 0 && absAngles.Z == 0) + hitLocal = tr.EndPos - grabbed.GetAbsOrigin(); + else + MathLib.VectorITransform(tr.EndPos, grabbed.EntityToWorldTransform(), out hitLocal); + + if (grabbed.GetSolid() == SolidType.VPhysics) { + Matrix3x4 boneMatrix = GetPhysBoneMatrix(grabbed, tr.PhysicsBone); + MathLib.VectorITransform(tr.EndPos, boneMatrix, out hitLocal); + } + SetHitPosLocal(hitLocal); + + IPhysicsObject[] list = ArrayPool.Shared.Rent(VPHYSICS_MAX_OBJECT_LIST_COUNT); + try { + int count = grabbed.VPhysicsGetObjectList(list.AsSpan(0, VPHYSICS_MAX_OBJECT_LIST_COUNT)); + PhysObj = (tr.PhysicsBone >= 0 && tr.PhysicsBone < count ? list[tr.PhysicsBone] : null) ?? grabbed.VPhysicsGetObject(); + + if (PhysObj != null) { + PhysObj.Wake(); + PhysObj.GetPosition(out _, out Angles); + GravCallback.AttachEntity(player, grabbed, PhysObj, tr.EndPos); + + if (!grabbed.IsRagdollProp()) { + for (int i = 0; i < count; i++) + list[i]?.EnableMotion(true); + } + } + } + finally { + ArrayPool.Shared.Return(list, true); + } + } + + EnablePhysBeam(true); + UpdateBeam(GrabbedEntity.Get(), player, tr.PhysicsBone); + + if ((player.AfButtonPressed & InButtons.Attack) != 0) + SendWeaponAnim(Activity.ACT_VM_PRIMARYATTACK); } + public override void SecondaryAttack() { - base.SecondaryAttack(); + if (NextSecondaryAttack > gpGlobals.CurTime) + return; + + GMOD_Player? player = ToGMODPlayer(GetOwner()); + if (player == null) + return; + + BaseEntity? grabbed = GrabbedEntity.Get(); + if (grabbed == null) + return; + + NextSecondaryAttack = gpGlobals.CurTime + 0.5; + NextPrimaryAttack = NextSecondaryAttack; + + ValidatePhysObj(); + + if (gGM != null && gGM.CallWithArgs((int)LUA_POOLEDSTRING.OnPhysgunFreeze)) { + LuaEntity.Push_Entity(this); + LuaPhysObj.Push_PhysObj(PhysObj); + LuaEntity.Push_Entity(grabbed); + LuaEntity.Push_Entity(player); + gGM.CallNoReturns(4); + } + + SendWeaponAnim(Activity.ACT_VM_PRIMARYATTACK); + DropEntity(); } + + public void DropEntity() { + GMOD_Player? player = ToGMODPlayer(GetOwner()); + BaseEntity? grabbed = GrabbedEntity.Get(); + + if (grabbed != null && gGM != null && gGM.CallWithArgs((int)LUA_POOLEDSTRING.PhysgunDrop)) { + LuaEntity.Push_Entity(player); + LuaEntity.Push_Entity(grabbed); + gGM.CallNoReturns(2); + } + + EnablePhysBeam(false); + PhysObj = null; + GravCallback.DetachEntity(true); + + if (player != null && grabbed != null) + grabbed.SetPhysicsAttacker(player, gpGlobals.CurTime); + + NetworkStateChanged(); + GrabbedEntity.Set(null); + SetHitPosLocal(default); + } + + public override void ItemPostFrame() { + GMOD_Player? player = ToGMODPlayer(GetOwner()); + if (player == null) + return; + + if (!player.CanAttack()) + return; + + if ((player.AfButtonPressed & InButtons.Reload) != 0) { + Reload(); + return; + } + + if ((player.AfButtonPressed & InButtons.Attack2) != 0) { + SecondaryAttack(); + return; + } + + if ((player.Buttons & (InButtons.Attack | InButtons.Attack2)) != InButtons.Attack) { + DropEntity(); + return; + } + + PrimaryAttack(); + + if (GrabbedEntity.Get() == null) + return; + + float frameTime = (float)gpGlobals.FrameTime; + + if ((player.Buttons & InButtons.Use) != 0) { + player.SetPhysicsFlag(PlayerPhysFlag.DirOverride, true); + + if ((player.Buttons & InButtons.Forward) != 0) + Distance = MathLib.Approach(physgun_maxrange.GetFloat(), Distance, 100 * frameTime); + if ((player.Buttons & InButtons.Back) != 0) + Distance = MathLib.Approach(physgun_minrange.GetFloat(), Distance, 100 * frameTime); + if ((player.Buttons & InButtons.MoveLeft) != 0) + Angles.Y -= phys_spinspeed.GetFloat() * frameTime; + if ((player.Buttons & InButtons.MoveRight) != 0) + Angles.Y += phys_spinspeed.GetFloat() * frameTime; + + ref UserCmd cmd = ref player.GetCurrentUserCommand(); + if (!Unsafe.IsNullRef(ref cmd)) { + float sensitivity = engine.GetClientConVarFloat(player.EntIndex(), "physgun_rotation_sensitivity", 0.05f); + sensitivity = Math.Clamp(sensitivity, -1000f, 1000f); + + QAngle delta = new(-cmd.MouseDeltaY * sensitivity, cmd.MouseDeltaX * sensitivity, 0); + + QAngle eyeYaw = new(0, player.EyeAngles().Y, 0); + MathLib.AngleMatrix(eyeYaw, out Matrix3x4 playerSpace); + + QAngle local = MathLib.TransformAnglesToLocalSpace(Angles, playerSpace); + MathLib.AngleMatrix(local, out Matrix3x4 current); + MathLib.AngleMatrix(delta, out Matrix3x4 deltaMatrix); + MathLib.ConcatTransforms(deltaMatrix, current, out Matrix3x4 combined); + MathLib.MatrixAngles(combined, out QAngle combinedAngles); + + Angles = MathLib.TransformAnglesToWorldSpace(combinedAngles, playerSpace); + } + } + else + player.SetPhysicsFlag(PlayerPhysFlag.DirOverride, false); + + int wheel = player.MouseWheel(); + if (wheel != 0) { + float wheelSpeed = engine.GetClientConVarFloat(player.EntIndex(), "physgun_wheelspeed", 10); + if (MathF.Abs((int)wheelSpeed) < 0.1f) + wheelSpeed = 0.1f; + + Distance += wheel * wheelSpeed; + Distance = Math.Clamp(Distance, physgun_minrange.GetFloat(), physgun_maxrange.GetFloat()); + } + + Target = player.Weapon_ShootPosition() + PhysGunGlobals.GetAimForward(player) * Distance; + + UpdateRotation(); + UpdatePosition(); + } + + static float SnapAngle(float angle, float snap) { + float quotient = angle / snap; + int rounded = float.IsFinite(quotient) && MathF.Abs(quotient) < 2147483648f ? (int)MathF.Round(quotient) : int.MinValue; + return rounded * snap; + } + + public void UpdateRotation() { + GMOD_Player? player = ToGMODPlayer(GetOwner()); + BaseEntity? grabbed = GrabbedEntity.Get(); + if (player == null || grabbed == null) + return; + + float snap = player.GetSnapAngles(); + QAngle snapped = new(SnapAngle(Angles.X, snap), SnapAngle(Angles.Y, snap), SnapAngle(Angles.Z, snap)); + + float eyeYaw = player.EyeAngles().Y; + if (eyeYaw != LastYaw) { + Angles.Y += eyeYaw - LastYaw; + LastYaw = eyeYaw; + } + + QAngle target = Angles; + const InButtons SpeedUse = InButtons.Speed | InButtons.Use; + + if ((player.Buttons & SpeedUse) == SpeedUse) + target = snapped; + + InButtons released = player.AfButtonReleased; + InButtons held = player.Buttons; + if ((released & SpeedUse) == SpeedUse + || ((released & InButtons.Speed) != 0 && (held & InButtons.Use) != 0) + || ((released & InButtons.Use) != 0 && (held & InButtons.Speed) != 0)) { + Angles = target = snapped; + } + + ValidatePhysObj(); + if (PhysObj != null) + GravCallback.SetTargetRotation(target); + else { + if (grabbed.IsNPC()) { + target.X = 0; + target.Z = 0; + } + grabbed.SetAbsAngles(target); + } + } + + public void UpdatePosition() { + GMOD_Player? player = ToGMODPlayer(GetOwner()); + BaseEntity? grabbed = GrabbedEntity.Get(); + if (player == null || grabbed == null) + return; + + if (ValidatePhysObj()) { + GravCallback.SetTargetPosition(Target, player); + return; + } + + Vector3 origin = grabbed.GetAbsOrigin(); + QAngle absAngles = grabbed.GetAbsAngles(); + Vector3 holdWorld; + if (absAngles.X == 0 && absAngles.Y == 0 && absAngles.Z == 0) + holdWorld = HitPosLocal + origin; + else + MathLib.VectorTransform(HitPosLocal, grabbed.EntityToWorldTransform(), out holdWorld); + + Target -= holdWorld - origin; + player?.SnapToGrid(ref Target); + + const Mask solidMask = (Mask)0x400b; + + Util.TraceEntity(grabbed, origin, origin, solidMask, out Trace tr); + if (tr.Fraction < 1 || tr.AllSolid || tr.StartSolid) { + Util.TraceEntity(grabbed, Target, Target, solidMask, out tr); + if (!(tr.Fraction < 1 || tr.AllSolid || tr.StartSolid)) + grabbed.SetAbsOrigin(Target); + } + + Util.TraceEntity(grabbed, origin, Target, solidMask, out tr); + if (tr.Fraction == 1) { + grabbed.SetAbsOrigin(tr.EndPos); + return; + } + + Util.TraceEntity(grabbed, origin, new Vector3(origin.X, Target.Y, Target.Z), solidMask, out tr); + Util.TraceEntity(grabbed, origin, new Vector3(Target.X, origin.Y, Target.Z), solidMask, out tr); + Util.TraceEntity(grabbed, origin, new Vector3(Target.X, Target.Y, origin.Z), solidMask, out tr); + grabbed.SetAbsOrigin(tr.EndPos); + } +#endif +#if CLIENT_DLL + static void ResetIfNonFinite(ConVar cvar) { + if (!float.IsFinite(cvar.GetFloat())) + cvar.SetValue(cvar.GetDefault()); + } + + static void PhysgunSensitivityChanged(IConVar var, in ConVarChangeContext ctx) { + ResetIfNonFinite(physgun_rotation_sensitivity); + ResetIfNonFinite(physgun_wheelspeed); + } + + public static readonly ConVar physgun_rotation_sensitivity = new("physgun_rotation_sensitivity", "0.05", FCvar.UserInfo | FCvar.Archive, "The sensitivity of Physics Gun rotation.", 0.01, 10000, PhysgunSensitivityChanged); + public static readonly ConVar physgun_wheelspeed = new("physgun_wheelspeed", "10.0", FCvar.UserInfo | FCvar.Archive, "The sensitivity of Physics Gun wheel scrolling.", -10000, 10000, PhysgunSensitivityChanged); + public static readonly ConVar physgun_drawbeams = new("physgun_drawbeams", "1", 0, "Whether to draw Physics Gun beams."); + + public override bool Deploy() { + DropEntity(); + return base.Deploy(); + } + + public override bool Holster(BaseCombatWeapon? switchingTo) { + DropEntity(); + return base.Holster(switchingTo); + } + + public override void Drop(in Vector3 velocity) { + DropEntity(); + base.Drop(velocity); + } + public override void UpdateOnRemove() { + DropEntity(); base.UpdateOnRemove(); } - public void UpdatePosition(){ } - public void UpdateRotation(){ } - public bool ValidatePhysObj() => false; + + void EnablePhysBeam(bool on) { + if (PhysBeam.Get() is C_PhysBeam beam) + beam.SetBeamEnabled(on); + } + + public void DropEntity() { + BasePlayer? player = GetOwner() as BasePlayer; + BaseEntity? grabbed = GrabbedEntity.Get(); + if (player != null && grabbed != null) { + if (gGM != null && gGM.CallWithArgs((int)LUA_POOLEDSTRING.PhysgunDrop)) { + LuaEntity.Push_Entity(player); + LuaEntity.Push_Entity(grabbed); + gGM.CallNoReturns(2); + } + } + + EnablePhysBeam(false); + + GrabbedEntity.Set(null); + HitPosLocal = default; + } + + public override bool Reload() { + if (GetOwner() == null) + return false; + + if (NextReload > gpGlobals.CurTime) + return false; + + NextReload = (float)gpGlobals.CurTime + 0.01f; + return true; + } + + public override void SecondaryAttack() { + if (gpGlobals.CurTime < NextSecondaryAttack) + return; + + if (GetOwner() is not BasePlayer || GrabbedEntity.Get() == null) + return; + + NextSecondaryAttack = gpGlobals.CurTime + 0.5; + NextPrimaryAttack = NextSecondaryAttack; + SendWeaponAnim(Activity.ACT_VM_PRIMARYATTACK); + DropEntity(); + } + + public override void PrimaryAttack() { + if (gpGlobals.CurTime < NextPrimaryAttack) + return; + + BasePlayer? player = GetOwner() as BasePlayer; + if (player == null) + return; + + if (GrabbedEntity.Get() != null) + return; + + Vector3 eye = player.Weapon_ShootPosition(); + Vector3 forward = GetAimForward(player); + + Ray ray = default; + ray.Init(eye, eye + forward * physgun_maxrange.GetFloat() * 2); + TraceFilterSimple filter = new(player, Source.CollisionGroup.None); + enginetrace.TraceRay(ray, (Mask)0x600400f, ref filter, out Trace tr); + + if (r_visualizetraces.GetBool()) + Game.Shared.DebugOverlay.DebugDrawLine(tr.StartPos, tr.EndPos, 255, 0, 0, true, -1.0f); + + if (tr.Fraction >= 1 && !tr.AllSolid && !tr.StartSolid) + return; + + BaseEntity? hit = tr.Ent; + if (!tr.DidHitWorld() && hit != null && gGM != null && gGM.CallWithArgs((int)LUA_POOLEDSTRING.PhysgunPickup)) { + LuaEntity.Push_Entity(player); + LuaEntity.Push_Entity(hit); + if (gGM.CallFinish(2)) + GrabbedEntity.Set(hit); + } + + BaseEntity? grabbed = GrabbedEntity.Get(); + if (grabbed != null) { + Distance = (eye - tr.EndPos).Length(); + + QAngle absAngles = grabbed.GetAbsAngles(); + Vector3 hitLocal; + if (absAngles.X == 0 && absAngles.Y == 0 && absAngles.Z == 0) + hitLocal = tr.EndPos - grabbed.GetAbsOrigin(); + else + MathLib.VectorITransform(tr.EndPos, grabbed.EntityToWorldTransform(), out hitLocal); + + if (grabbed.GetSolid() == SolidType.VPhysics) { + Matrix3x4 boneMatrix = GetPhysBoneMatrix(grabbed, tr.PhysicsBone); + MathLib.VectorITransform(tr.EndPos, boneMatrix, out hitLocal); + } + HitPosLocal = hitLocal; + + IPhysicsObject? phys = grabbed.VPhysicsGetObject(); + if (phys != null) { + phys.Wake(); + if (!grabbed.IsRagdollProp()) + phys.EnableMotion(true); + } + } + + EnablePhysBeam(true); + if (PhysBeam.Get() is C_PhysBeam beam) { + beam.UpdateBeam(GrabbedEntity.Get(), HitPosLocal, tr.PhysicsBone); + beam.SetAbsOrigin(player.GetAbsOrigin()); + } + + if ((player.AfButtonPressed & InButtons.Attack) != 0) + SendWeaponAnim(Activity.ACT_VM_PRIMARYATTACK); + } + + public override void ItemPostFrame() { + BasePlayer? player = GetOwner() as BasePlayer; + if (player == null) + return; + + if (!player.CanAttack()) + return; + + if ((player.AfButtonPressed & InButtons.Reload) != 0) { + Reload(); + return; + } + + if ((player.AfButtonPressed & InButtons.Attack2) != 0) { + SecondaryAttack(); + return; + } + + if ((player.Buttons & (InButtons.Attack | InButtons.Attack2)) == InButtons.Attack) + PrimaryAttack(); + else + DropEntity(); + } + + public override bool ShouldFreezeViewAngles() { + const InButtons AttackUse = InButtons.Attack | InButtons.Use; + return GrabbedEntity.Get() != null && (gHUD.KeyBits & AttackUse) == AttackUse; + } + + public override int KeyInput(int down, ButtonCode keynum, ReadOnlySpan currentBinding) { + if (GrabbedEntity.Get() == null || (gHUD.KeyBits & InButtons.Attack) == 0) + return 1; + + if (keynum == ButtonCode.MouseWheelUp) { + gHUD.KeyBits |= InButtons.Weapon1; + return 0; + } + if (keynum == ButtonCode.MouseWheelDown) { + gHUD.KeyBits |= InButtons.Weapon2; + return 0; + } + + return 1; + } +#endif } #endif diff --git a/Game.Shared/PhysicsShared.cs b/Game.Shared/PhysicsShared.cs index c4475843..404bff1f 100644 --- a/Game.Shared/PhysicsShared.cs +++ b/Game.Shared/PhysicsShared.cs @@ -197,6 +197,26 @@ public static PhysicsFlags PhysClearGameFlags(IPhysicsObject phys, PhysicsFlags return flags; } + public static bool PhysHasContactWithOtherInDirection(IPhysicsObject physics, in Vector3 dir) { + bool hit = false; + object? gameData = physics.GetGameData(); + IPhysicsFrictionSnapshot snapshot = physics.CreateFrictionSnapshot(); + while (snapshot.IsValid()) { + IPhysicsObject? other = snapshot.GetObject(1); + if (other!.GetGameData() != gameData) { + snapshot.GetSurfaceNormal(out Vector3 normal); + if (Vector3.Dot(normal, dir) > 0) { + hit = true; + break; + } + } + snapshot.NextFrictionData(); + } + physics.DestroyFrictionSnapshot(snapshot); + + return hit; + } + public static void PhysRecheckObjectPair(IPhysicsObject obj0, IPhysicsObject obj1) { if (!obj0.IsStatic()) obj0.RecheckCollisionFilter(); diff --git a/Game.Shared/RagdollShared.cs b/Game.Shared/RagdollShared.cs index ac2d35fa..f95997e3 100644 --- a/Game.Shared/RagdollShared.cs +++ b/Game.Shared/RagdollShared.cs @@ -1,4 +1,10 @@ +#if CLIENT_DLL || GAME_DLL +global using static Game.Shared.SharedRagdollGlobals; + using Source; +using Source.Common; +using Source.Common.Formats.BSP; +using Source.Common.Mathematics; using Source.Common.Physics; using System.Numerics; @@ -22,6 +28,9 @@ public struct RagdollAnimatedFriction public int MaxAnimatedFriction; } +/// +/// analog of ragdoll_t, NOT CRagdoll!!! +/// public class Ragdoll { public const int RAGDOLL_MAX_ELEMENTS = 32; @@ -35,3 +44,486 @@ public class Ragdoll public InlineArray32 BoneIndex; public RagdollAnimatedFriction AnimFriction; } + +public ref struct RagdollParams +{ + public object? GameData; + public VCollide Collide; + public StudioHdr StudioHdr; + public int ModelIndex; + public Vector3 ForcePosition; + public Vector3 ForceVector; + public int ForceBoneIndex; + public ReadOnlySpan CurrentBones; + public float JointFrictionScale; + public bool AllowStretch; + public bool FixedConstraints; +} + +class RagdollCollisionRules(IPhysicsCollisionSet set) : IVPhysicsKeyHandler +{ + readonly IPhysicsCollisionSet Set = set; + bool SelfCollisions = true; + + public void ParseKeyValue(ref T data, ReadOnlySpan key, ReadOnlySpan value) { + if (strcmpi(key, "selfcollisions") == 0) { + Assert(atoi(value) == 0); + SelfCollisions = false; + } + else if (strcmpi(key, "collisionpair") == 0) { + if (SelfCollisions) { + nexttoken(out ReadOnlySpan token, value, ',', out ReadOnlySpan str); + int index0 = atoi(token); + nexttoken(out token, str, ',', out _); + int index1 = atoi(token); + + Set.EnableCollisions(index0, index1); + } + else + Assert(false); + } + } + + public void SetDefaults(ref T data) { } +} + +class RagdollAnimatedFrictionParser(Ragdoll ragdoll) : IVPhysicsKeyHandler +{ + readonly Ragdoll Ragdoll = ragdoll; + + public void ParseKeyValue(ref T data, ReadOnlySpan key, ReadOnlySpan value) { + if (strcmpi(key, "animfrictionmin") == 0) + Ragdoll.AnimFriction.MinAnimatedFriction = atoi(value); + else if (strcmpi(key, "animfrictionmax") == 0) + Ragdoll.AnimFriction.MaxAnimatedFriction = atoi(value); + else if (strcmpi(key, "animfrictiontimein") == 0) + Ragdoll.AnimFriction.FrictionTimeIn = strtof(value, out _); + else if (strcmpi(key, "animfrictiontimeout") == 0) + Ragdoll.AnimFriction.FrictionTimeOut = strtof(value, out _); + else if (strcmpi(key, "animfrictiontimehold") == 0) + Ragdoll.AnimFriction.FrictionTimeHold = strtof(value, out _); + } + + public void SetDefaults(ref T data) { } +} + +public static class SharedRagdollGlobals +{ + public const int RAGDOLL_MAX_ELEMENTS = 24; + public const int RAGDOLL_INDEX_BITS = 5;// NOTE 1<= RAGDOLL_MAX_ELEMENTS + public const float CORE_DISSOLVE_FADE_START = 0.2f; + public const float CORE_DISSOLVE_MODEL_FADE_START = 0.1f; + public const float CORE_DISSOLVE_MODEL_FADE_LENGTH = 0.05f; + public const float CORE_DISSOLVE_FADEIN_LENGTH = 0.1f; + + static void ParseCustomBlock(IVPhysicsKeyParser parse, IVPhysicsKeyHandler handler) { + object? custom = handler; + parse.ParseCustom(ref custom, handler); + } + + public static void RagdollSetupAnimatedFriction(IPhysicsEnvironment physEnv, Ragdoll ragdoll, int modelIndex) { + VCollide? collide = modelinfo.GetVCollide(modelIndex); + + if (collide != null) { + IVPhysicsKeyParser parse = physcollision.VPhysicsKeyParserCreate(collide.KeyValues); + + while (!parse.Finished()) { + ReadOnlySpan block = parse.GetCurrentBlockName(); + + if (strcmpi(block, "animatedfriction") == 0) + ParseCustomBlock(parse, new RagdollAnimatedFrictionParser(ragdoll)); + else + parse.SkipBlock(); + } + + physcollision.VPhysicsKeyParserDestroy(parse); + } + } + + static void RagdollAddSolid(IPhysicsEnvironment physEnv, Ragdoll ragdoll, in RagdollParams parms, ref Solid solid) { + if (solid.Index >= 0 && solid.Index < parms.Collide.SolidCount) { + Assert(ragdoll.ListCount == solid.Index); + ReadOnlySpan solidName = ((ReadOnlySpan)solid.Name).SliceNullTerminatedString(); + int boneIndex = BoneSetup.Studio_BoneIndexByName(parms.StudioHdr, solidName); + ragdoll.BoneIndex[ragdoll.ListCount] = boneIndex; + + if (boneIndex >= 0) { + if (parms.FixedConstraints) + solid.Params.Mass = 1000.0f; + + solid.Params.RotInertiaLimit = 0.1f; + solid.Params.GameData = parms.GameData; + int surfaceData = (int)physprops.GetSurfaceIndex(((ReadOnlySpan)solid.SurfaceProp).SliceNullTerminatedString()); + + if (surfaceData < 0) + surfaceData = (int)physprops.GetSurfaceIndex("default"); + + solid.Params.Name = parms.StudioHdr.Name(); + ref RagdollElement element = ref ragdoll.List[ragdoll.ListCount]; + element.Object = physEnv.CreatePolyObject(parms.Collide.Solids![solid.Index]!, surfaceData, vec3_origin, vec3_angle, ref solid.Params); + element.Object!.SetPositionMatrix(parms.CurrentBones[boneIndex], true); + element.ParentIndex = -1; + element.Object.SetGameIndex((ushort)ragdoll.ListCount); + + ragdoll.ListCount++; + } + else + Msg($"CRagdollProp::CreateObjects: Couldn't Lookup Bone {solidName}\n"); + } + } + + static void RagdollAddConstraint(IPhysicsEnvironment physEnv, Ragdoll ragdoll, in RagdollParams parms, ref ConstraintRagdollParams constraint) { + if (constraint.ChildIndex == constraint.ParentIndex) { + DevMsg(1, $"Bogus constraint on ragdoll {parms.StudioHdr.Name()}\n"); + constraint.ChildIndex = -1; + constraint.ParentIndex = -1; + } + if (constraint.ChildIndex >= 0 && constraint.ParentIndex >= 0) { + Assert(constraint.ChildIndex < ragdoll.ListCount); + + ref RagdollElement childElement = ref ragdoll.List[constraint.ChildIndex]; + childElement.ParentIndex = constraint.ParentIndex; + + if (parms.JointFrictionScale > 0) { + for (int k = 0; k < 3; k++) + constraint.Axes[k].Torque *= parms.JointFrictionScale; + } + + BoneSetup.Studio_CalcBoneToBoneTransform(parms.StudioHdr, ragdoll.BoneIndex[constraint.ChildIndex], ragdoll.BoneIndex[constraint.ParentIndex], out constraint.ConstraintToAttached); + MathLib.MatrixGetColumn(constraint.ConstraintToAttached, 3, out childElement.OriginParentSpace); + MathLib.SetIdentityMatrix(out constraint.ConstraintToReference); + IPhysicsObject parentObject = ragdoll.List[constraint.ParentIndex].Object!; + if (parms.FixedConstraints) { + ConstraintFixedParams fixedParams = default; + fixedParams.Defaults(); + fixedParams.InitWithCurrentObjectState(childElement.Object!, parentObject); + fixedParams.Constraint.Defaults(); + childElement.Constraint = physEnv.CreateFixedConstraint(childElement.Object!, parentObject, ragdoll.Group!, fixedParams); + } + else + childElement.Constraint = physEnv.CreateRagdollConstraint(childElement.Object!, parentObject, ragdoll.Group!, constraint); + } + } + + static void RagdollCreateObjects(IPhysicsEnvironment physEnv, Ragdoll ragdoll, in RagdollParams parms) { + ragdoll.ListCount = 0; + ragdoll.Group = null; + ragdoll.AllowStretch = parms.AllowStretch; + ragdoll.List = default; + ragdoll.AnimFriction = default; + + if (parms.Collide == null || parms.Collide.SolidCount > RAGDOLL_MAX_ELEMENTS) + return; + + ConstraintGroupParams group = default; + group.Defaults(); + ragdoll.Group = physEnv.CreateConstraintGroup(group); + + IVPhysicsKeyParser parse = physcollision.VPhysicsKeyParserCreate(parms.Collide.KeyValues); + while (!parse.Finished()) { + ReadOnlySpan block = parse.GetCurrentBlockName(); + if (strcmpi(block, "solid") == 0) { + Solid solid = default; + + parse.ParseSolid(ref solid, g_SolidSetup); + RagdollAddSolid(physEnv, ragdoll, parms, ref solid); + } + else if (strcmpi(block, "ragdollconstraint") == 0) { + ConstraintRagdollParams constraint = default; + parse.ParseRagdollConstraint(ref constraint, null); + RagdollAddConstraint(physEnv, ragdoll, parms, ref constraint); + } + else if (strcmpi(block, "collisionrules") == 0) { + IPhysicsCollisionSet set = physics.FindOrCreateCollisionSet((uint)parms.ModelIndex, ragdoll.ListCount); + ParseCustomBlock(parse, new RagdollCollisionRules(set)); + } + else if (strcmpi(block, "animatedfriction") == 0) + ParseCustomBlock(parse, new RagdollAnimatedFrictionParser(ragdoll)); + else + parse.SkipBlock(); + } + physcollision.VPhysicsKeyParserDestroy(parse); + } + + public static void RagdollSetupCollisions(Ragdoll ragdoll, VCollide? collide, int modelIndex) { + Assert(collide != null); + if (collide == null) + return; + + IPhysicsCollisionSet? set = physics.FindCollisionSet((uint)modelIndex); + if (set == null) { + set = physics.FindOrCreateCollisionSet((uint)modelIndex, ragdoll.ListCount); + if (set == null) + return; + + bool foundRules = false; + + IVPhysicsKeyParser parse = physcollision.VPhysicsKeyParserCreate(collide.KeyValues); + while (!parse.Finished()) { + ReadOnlySpan block = parse.GetCurrentBlockName(); + if (strcmpi(block, "collisionrules") == 0) { + IPhysicsCollisionSet setRules = physics.FindOrCreateCollisionSet((uint)modelIndex, ragdoll.ListCount); + ParseCustomBlock(parse, new RagdollCollisionRules(setRules)); + foundRules = true; + } + else + parse.SkipBlock(); + } + physcollision.VPhysicsKeyParserDestroy(parse); + + if (!foundRules) { + int i; + for (i = 0; i < ragdoll.ListCount; i++) { + for (int j = i + 1; j < ragdoll.ListCount; j++) + set.EnableCollisions(i, j); + } + for (i = 0; i < ragdoll.ListCount; i++) { + int parent = ragdoll.List[i].ParentIndex; + if (parent >= 0) { + Assert(ragdoll.List[i].Object != null); + Assert(ragdoll.List[i].Constraint != null); + set.DisableCollisions(i, parent); + } + } + } + } + } + + public static void RagdollActivate(Ragdoll ragdoll, VCollide? collide, int modelIndex, bool forceWake) { + RagdollSetupCollisions(ragdoll, collide, modelIndex); + for (int i = 0; i < ragdoll.ListCount; i++) { + IPhysicsObject obj = ragdoll.List[i].Object!; + obj.SetGameIndex((ushort)i); + PhysSetGameFlags(obj, PhysicsFlags.MultiObjectEntity); + obj.EnableCollisions(true); + + if (forceWake) + obj.Wake(); + } + if (ragdoll.Group != null) { + ragdoll.Group.Activate(); + if (!forceWake) { + for (int i = 0; i < ragdoll.ListCount; i++) + ragdoll.List[i].Object!.Sleep(); + } + } + } + + public static bool RagdollCreate(Ragdoll ragdoll, in RagdollParams parms, IPhysicsEnvironment physEnv) { + RagdollCreateObjects(physEnv, ragdoll, parms); + + if (ragdoll.ListCount == 0) + return false; + + int forceBone = parms.ForceBoneIndex; + + int i; + float totalMass = 0; + for (i = 0; i < ragdoll.ListCount; i++) + totalMass += ragdoll.List[i].Object!.GetMass(); + totalMass = MathF.Max(totalMass, 1); + + Vector3 nudgeForce = parms.ForceVector; + Vector3 forcePosition = parms.ForcePosition; + + Assert(forceBone < ragdoll.ListCount); + + if (forceBone >= 0 && forceBone < ragdoll.ListCount) { + ragdoll.List[forceBone].Object!.ApplyForceCenter(nudgeForce); + ragdoll.List[forceBone].Object!.GetPosition(out forcePosition, out _); + } + + for (i = 0; i < ragdoll.ListCount; i++) + PhysSetGameFlags(ragdoll.List[i].Object!, PhysicsFlags.PartOfRagdoll); + + if (forcePosition != vec3_origin) { + for (i = 0; i < ragdoll.ListCount; i++) { + if (forceBone != i) { + float scale = ragdoll.List[i].Object!.GetMass() / totalMass; + ragdoll.List[i].Object!.ApplyForceOffset(scale * nudgeForce, forcePosition); + } + } + } + + return true; + } + + public static void RagdollApplyAnimationAsVelocity(Ragdoll ragdoll, ReadOnlySpan prevBones, ReadOnlySpan currentBones, TimeUnit_t dt) { + for (int i = 0; i < ragdoll.ListCount; i++) { + int boneIndex = ragdoll.BoneIndex[i]; + BoneSetup.CalcBoneDerivatives(out Vector3 velocity, out Vector3 angVel, prevBones[boneIndex], currentBones[boneIndex], (float)dt); + + // Angular velocity is always applied in local space in vphysics + ragdoll.List[i].Object!.WorldToLocalVector(out Vector3 localAngVelocity, angVel); + ragdoll.List[i].Object!.AddVelocity(velocity, localAngVelocity); + } + } + + public static void RagdollApplyAnimationAsVelocity(Ragdoll ragdoll, ReadOnlySpan boneToWorld) { + for (int i = 0; i < ragdoll.ListCount; i++) { + MathLib.MatrixInvert(boneToWorld[i], out Matrix3x4 inverse); + MathLib.MatrixAngles(inverse, out Quaternion q, out Vector3 pos); + + Vector3 velocity; + float flSpin; + + Vector3 localVelocity; + Vector3 localAngVelocity; + + MathLib.QuaternionAxisAngle(q, out localAngVelocity, out flSpin); + localAngVelocity *= flSpin; + localVelocity = pos; + + // move those bone-local coords back to world space using the ragdoll transform + ragdoll.List[i].Object!.LocalToWorldVector(out velocity, localVelocity); + ragdoll.List[i].Object!.AddVelocity(velocity, localAngVelocity); + } + } + + public static void RagdollDestroy(Ragdoll ragdoll) { + if (ragdoll.ListCount == 0) + return; + + int i; + for (i = 0; i < ragdoll.ListCount; i++) { + physenv.DestroyConstraint(ragdoll.List[i].Constraint!); + ragdoll.List[i].Constraint = null; + } + for (i = 0; i < ragdoll.ListCount; i++) { + if (ragdoll.List[i].Object != null) + physenv.DestroyObject(ragdoll.List[i].Object); + ragdoll.List[i].Object = null; + } + physenv.DestroyConstraintGroup(ragdoll.Group!); + ragdoll.Group = null; + ragdoll.ListCount = 0; + } + + public static int RagdollExtractBoneIndices(Span boneIndexOut, StudioHdr studioHdr, VCollide collide) { + int elementCount = 0; + + IVPhysicsKeyParser parse = physcollision.VPhysicsKeyParserCreate(collide.KeyValues); + while (!parse.Finished()) { + ReadOnlySpan block = parse.GetCurrentBlockName(); + if (strcmpi(block, "solid") == 0) { + Solid solid = default; + parse.ParseSolid(ref solid, null); + if (elementCount < RAGDOLL_MAX_ELEMENTS) { + boneIndexOut[elementCount] = BoneSetup.Studio_BoneIndexByName(studioHdr, ((ReadOnlySpan)solid.Name).SliceNullTerminatedString()); + elementCount++; + } + } + else + parse.SkipBlock(); + } + physcollision.VPhysicsKeyParserDestroy(parse); + + return elementCount; + } + + public static bool RagdollGetBoneMatrix(Ragdoll ragdoll, BoneAccessor boneToWorld, int objectIndex) { + int BoneIndex = ragdoll.BoneIndex[objectIndex]; + if (BoneIndex < 0) + return false; + + ref readonly RagdollElement element = ref ragdoll.List[objectIndex]; + + // during restore if a model has changed since the file was saved, this could be NULL + if (element.Object == null) + return false; + element.Object.GetPositionMatrix(out boneToWorld.GetBoneForWrite(BoneIndex)); + if (element.ParentIndex >= 0 && !ragdoll.AllowStretch) { + // overwrite the position from physics to force rigid attachment + // UNDONE: If we support other types of constraints (or multiple constraints per object) + // make sure these don't fight ! + int parentBoneIndex = ragdoll.BoneIndex[element.ParentIndex]; + MathLib.VectorTransform(element.OriginParentSpace, boneToWorld.GetBone(parentBoneIndex), out Vector3 outVec); + MathLib.MatrixSetColumn(outVec, 3, ref boneToWorld.GetBoneForWrite(BoneIndex)); + } + return true; + } + + public static void RagdollComputeExactBbox(Ragdoll ragdoll, in Vector3 origin, ref Vector3 outMins, ref Vector3 outMaxs) { + outMins = origin; + outMaxs = origin; + + for (int i = 0; i < ragdoll.ListCount; i++) { + IPhysicsObject obj = ragdoll.List[i].Object!; + obj.GetPosition(out Vector3 objectOrg, out QAngle objectAng); + physcollision.CollideGetAABB(out Vector3 mins, out Vector3 maxs, obj.GetCollide(), objectOrg, objectAng); + outMins = Vector3.Min(outMins, mins); + outMaxs = Vector3.Max(outMaxs, maxs); + } + } + + public static bool RagdollIsAsleep(Ragdoll ragdoll) { + for (int i = 0; i < ragdoll.ListCount; i++) { + if (ragdoll.List[i].Object != null && !ragdoll.List[i].Object!.IsAsleep()) + return false; + } + + return true; + } + + public static void RagdollSolveSeparation(Ragdoll ragdoll, BaseEntity? entity) { + Span needsFix = stackalloc byte[256]; + int fixCount = 0; + Assert(ragdoll.ListCount <= needsFix.Length); + for (int i = 0; i < ragdoll.ListCount; i++) { + needsFix[i] = 0; + ref readonly RagdollElement element = ref ragdoll.List[i]; + if (element.Constraint != null && element.ParentIndex >= 0) { + element.Object!.GetPosition(out Vector3 start, out _); + ragdoll.List[element.ParentIndex].Object!.LocalToWorld(out Vector3 target, element.OriginParentSpace); + if (needsFix[element.ParentIndex] != 0) { + needsFix[i] = 1; + ++fixCount; + continue; + } + Vector3 dir = target - start; + if (dir.LengthSquared() > 1.0f) { +#if !TF_CLIENT_DLL + float mass = element.Object.GetMass(); + float massParent = ragdoll.List[element.ParentIndex].Object!.GetMass(); + + if (mass * 2.0f < massParent) { + needsFix[i] = 1; + ++fixCount; + continue; + } +#endif + + if (PhysHasContactWithOtherInDirection(element.Object, dir)) { + Ray ray = default; + ray.Init(target, start); + Util.TraceRay(ray, Mask.Solid, entity, CollisionGroup.None, out Trace tr); + if (tr.DidHit()) { + needsFix[i] = 1; + ++fixCount; + } + } + } + } + } + + if (fixCount != 0) { + for (int i = 0; i < ragdoll.ListCount; i++) { + if (needsFix[i] == 0) + continue; + + ref readonly RagdollElement element = ref ragdoll.List[i]; + IPhysicsObject parentObject = ragdoll.List[element.ParentIndex].Object!; + parentObject.LocalToWorld(out Vector3 target, element.OriginParentSpace); + parentObject.GetVelocityAtPoint(target, out Vector3 velocity); + element.Object!.GetPositionMatrix(out Matrix3x4 xform); + MathLib.MatrixSetColumn(target, 3, ref xform); + element.Object.SetPositionMatrix(xform, true); + element.Object.SetVelocity(velocity, vec3_origin); + } + DevMsg(2, $"TICK:{gpGlobals.TickCount,5}:Ragdoll separation count: {fixCount}\n"); + } + else + ragdoll.Group!.ClearErrorState(); + } +} +#endif diff --git a/Source.Common/BoneSetup.cs b/Source.Common/BoneSetup.cs index 58d92a48..a87afee1 100644 --- a/Source.Common/BoneSetup.cs +++ b/Source.Common/BoneSetup.cs @@ -1646,6 +1646,26 @@ public static int Studio_BoneIndexByName(StudioHdr? studioHdr, ReadOnlySpan 0) + scale = 1.0f / dt; + + MathLib.MatrixAngles(prev, out QAngle startAngles, out Vector3 startPosition); + MathLib.MatrixAngles(current, out QAngle endAngles, out Vector3 endPosition); + + velocity = (endPosition - startPosition) * scale; + MathLib.RotationDeltaAxisAngle(startAngles, endAngles, out Vector3 deltaAxis, out float deltaAngle); + MathLib.VectorScale(deltaAxis, deltaAngle * scale, out angVel); + } + public static void SetupSingleBoneMatrix(StudioHdr ownerHdr, int sequence, int frame, int bone, out Matrix3x4 boneLocal) { MStudioSeqDesc seqdesc = ownerHdr.Seqdesc(sequence); MStudioAnimDesc animdesc = ownerHdr.Animdesc(seqdesc.Anim(0, 0)); diff --git a/Source.Common/Mathematics/MathLib.cs b/Source.Common/Mathematics/MathLib.cs index 773ab692..a57c753d 100644 --- a/Source.Common/Mathematics/MathLib.cs +++ b/Source.Common/Mathematics/MathLib.cs @@ -2401,6 +2401,21 @@ public static void TransformAABB(in Matrix3x4 transform, in Vector3 minsIn, in V maxsOut = worldCenter + worldExtents; } + public static void IRotateAABB(in Matrix3x4 transform, in Vector3 minsIn, in Vector3 maxsIn, out Vector3 minsOut, out Vector3 maxsOut) { + Vector3 oldCenter = (minsIn + maxsIn) * 0.5f; + Vector3 oldExtents = maxsIn - oldCenter; + + VectorIRotate(oldCenter, transform, out Vector3 newCenter); + + Vector3 newExtents; + newExtents.X = MathF.Abs(oldExtents.X * transform[0][0]) + MathF.Abs(oldExtents.Y * transform[1][0]) + MathF.Abs(oldExtents.Z * transform[2][0]); + newExtents.Y = MathF.Abs(oldExtents.X * transform[0][1]) + MathF.Abs(oldExtents.Y * transform[1][1]) + MathF.Abs(oldExtents.Z * transform[2][1]); + newExtents.Z = MathF.Abs(oldExtents.X * transform[0][2]) + MathF.Abs(oldExtents.Y * transform[1][2]) + MathF.Abs(oldExtents.Z * transform[2][2]); + + minsOut = newCenter - newExtents; + maxsOut = newCenter + newExtents; + } + public static float VectorLength(in Vector3 delta) => delta.Length(); public static float Vector2DLength(in Vector2 v) => MathF.Sqrt(DotProduct2D(v, v)); diff --git a/Source.Common/Physics/PhysicsInterface.cs b/Source.Common/Physics/PhysicsInterface.cs index 5406b144..078847c5 100644 --- a/Source.Common/Physics/PhysicsInterface.cs +++ b/Source.Common/Physics/PhysicsInterface.cs @@ -643,8 +643,9 @@ public interface IPhysicsObject void GetDamping(out float speed, out float rot); // coefficients are optional, pass either - void SetDragCoefficient(ref float drag, ref float angularDrag); + void SetDragCoefficient(in float? drag, in float? angularDrag); void SetBuoyancyRatio(float ratio); // Override bouyancy + float GetBuoyancyRatio(); // material index int GetMaterialIndex(); diff --git a/Source.Engine/CL.cs b/Source.Engine/CL.cs index 45c9bc59..5dda926a 100644 --- a/Source.Engine/CL.cs +++ b/Source.Engine/CL.cs @@ -234,7 +234,7 @@ public void FullyConnected() { if (percent == lastShaderPercent) return; lastShaderPercent = percent; - EngineVGui?.UpdateCustomProgressBar((float)done / total, "#LoadingProgress_LoadResources"); + EngineVGui?.UpdateCustomProgressBar((float)done / total, "Compiling shaders..."); // TODO: LOCALIZE THIS STRING #214 }); #endif diff --git a/Source.Engine/EngineServerImpl.cs b/Source.Engine/EngineServerImpl.cs index ccadff25..6e7a986b 100644 --- a/Source.Engine/EngineServerImpl.cs +++ b/Source.Engine/EngineServerImpl.cs @@ -1103,7 +1103,17 @@ public uint GMOD_LoadModel(ReadOnlySpan path) { } public float GetClientConVarFloat(int client, ReadOnlySpan cvar, float fallback) { - throw new NotImplementedException(); + if (client < 1 || client > sv.GetClientCount()) { + DevMsg(1, "GetClientConVarValue: player invalid index %i\n", client); + return fallback; + } + + ReadOnlySpan value = sv.GetClient(client - 1)!.GetUserSetting(cvar); + if (value.IsEmpty || value[0] == '\0') + return fallback; + + float result = (float)atof(value); + return float.IsFinite(result) ? result : fallback; } public ref CSteamID GMOD_GetPlayerOwnerSteamID(Edict client) { diff --git a/Source.Engine/Game.cs b/Source.Engine/Game.cs index bcc70fe4..5a76be65 100644 --- a/Source.Engine/Game.cs +++ b/Source.Engine/Game.cs @@ -152,6 +152,10 @@ private void DispatchInputEvent(in InputEvent ev) { Key?.Event(in ev); break; default: +#if GMOD_DLL + if (ev.Type == InputEventType.IE_AnalogValueChanged && (AnalogCode)ev.Data == AnalogCode.MouseWheel) + clientDLL?.IN_MouseWheelAnalog(ev.Data3); +#endif if (surface?.HandleInputEvent(in ev) ?? false) break; diff --git a/Source.Engine/VGuiBaseUIInterface.cs b/Source.Engine/VGuiBaseUIInterface.cs index 52832fb4..bb2f9f86 100644 --- a/Source.Engine/VGuiBaseUIInterface.cs +++ b/Source.Engine/VGuiBaseUIInterface.cs @@ -489,7 +489,13 @@ public void UpdateCustomProgressBar(float progress, ReadOnlySpan desc) { if (staticGameUIFuncs == null) return; - if (staticGameUIFuncs.UpdateProgressBar(progress, localize.Find(desc))) + if (!desc.IsEmpty && desc[0] == '#') { + ReadOnlySpan localized = localize.Find(desc); + if (!localized.IsEmpty) + desc = localized; + } + + if (staticGameUIFuncs.UpdateProgressBar(progress, desc)) Host.View.RenderGuiOnly(); } public void StartCustomProgress() { diff --git a/Source.Physics/PhysicsMaterial.cs b/Source.Physics/PhysicsMaterial.cs index 518c5d92..1efebcc3 100644 --- a/Source.Physics/PhysicsMaterial.cs +++ b/Source.Physics/PhysicsMaterial.cs @@ -90,6 +90,9 @@ public nint GetReservedSurfaceIndex(ReadOnlySpan propName) { } public nint GetSurfaceIndex(ReadOnlySpan surfacePropName) { + if (surfacePropName.IsStringEmpty) + return -1; + if (surfacePropName[0] == '$') { nint index = GetReservedSurfaceIndex(surfacePropName); if (index >= 0) diff --git a/Source.Physics/PhysicsObject.cs b/Source.Physics/PhysicsObject.cs index 8e35935c..149cea52 100644 --- a/Source.Physics/PhysicsObject.cs +++ b/Source.Physics/PhysicsObject.cs @@ -183,8 +183,6 @@ public void EnableGravity(bool enable) { BodyId.GravityScale = enable ? 1.0f : 0.0f; } - public void EnableDrag(bool enable) => DragEnabled = enable; - public void EnableMotion(bool enable) { if (Static || MotionEnabled == enable) return; @@ -306,10 +304,18 @@ public void GetDamping(out float speed, out float rot) { rot = AngularDamping; } - public void SetDragCoefficient(ref float drag, ref float angularDrag) { - DragCoefficient = drag; - AngularDragCoefficient = angularDrag; - DragEnabled = DragCoefficient != 0.0f || AngularDragCoefficient != 0.0f; + public void EnableDrag(bool enable) { + if (IsStatic()) + return; + DragEnabled = enable; + } + + public void SetDragCoefficient(in float? drag, in float? angularDrag) { + if (drag.HasValue) + DragCoefficient = drag.Value; + if (angularDrag.HasValue) + AngularDragCoefficient = angularDrag.Value; + EnableDrag(DragCoefficient != 0.0f || AngularDragCoefficient != 0.0f); RecomputeDragBases(); } @@ -317,6 +323,8 @@ public void SetDragCoefficient(ref float drag, ref float angularDrag) { public float GetBuoyancyRatio() => BuoyancyRatio; void RecomputeDragBases() { + if (IsStatic() || GetCollide() == null) return; + DragBasis = default; AngDragBasis = default; if (Static || Collide == null) diff --git a/Source.StdShader.Gl46/BaseVSShader.cs b/Source.StdShader.Gl46/BaseVSShader.cs index 1a7abc27..9f0918d7 100644 --- a/Source.StdShader.Gl46/BaseVSShader.cs +++ b/Source.StdShader.Gl46/BaseVSShader.cs @@ -536,6 +536,32 @@ public void SetPixelShaderConstantGammaToLinear(int pixelReg, int constantVar) { ShaderAPI!.SetPixelShaderConstant(pixelReg, val); } + public void SetPixelShaderConstantGammaToLinear(int pixelReg, int constantVar, int constantVar2) { + Assert(!IsSnapshotting()); + IMaterialVar[] shaderParams = Params!; + if (shaderParams == null || constantVar == -1 || constantVar2 == -1) + return; + + IMaterialVar pixelVar = shaderParams[constantVar]; + Assert(pixelVar != null); + IMaterialVar pixelVar2 = shaderParams[constantVar2]; + Assert(pixelVar2 != null); + + Span val = stackalloc float[4]; + if (pixelVar.GetVarType() == MaterialVarType.Vector) + pixelVar.GetVecValue(val[..3]); + else + val[0] = val[1] = val[2] = pixelVar.GetFloatValue(); + + val[3] = pixelVar2.GetFloatValue(); + + val[0] = val[0] > 1.0f ? val[0] : MathLib.GammaToLinear(val[0]); + val[1] = val[1] > 1.0f ? val[1] : MathLib.GammaToLinear(val[1]); + val[2] = val[2] > 1.0f ? val[2] : MathLib.GammaToLinear(val[2]); + + ShaderAPI!.SetPixelShaderConstant(pixelReg, val); + } + public void InitUnlitGeneric(int baseTextureVar, int detailVar, int envmapVar, int envmapMaskVar) { IMaterialVar[] shaderParams = Params!; diff --git a/Source.StdShader.Gl46/Sprite.cs b/Source.StdShader.Gl46/Sprite.cs index 93e28769..33f8132d 100644 --- a/Source.StdShader.Gl46/Sprite.cs +++ b/Source.StdShader.Gl46/Sprite.cs @@ -1,6 +1,340 @@ +using Source.Common; using Source.Common.MaterialSystem; +using Source.Common.Mathematics; +using Source.Common.ShaderAPI; using Source.Common.ShaderLib; namespace Source.StdShader.Gl46; -public class Sprite : UnlitGeneric; +public class Sprite : BaseVSShader +{ + public static string HelpString = "Help for Sprite"; + public static ShaderFlags Flags = ShaderFlags.None; + public static List ShaderParams = []; + public static ShaderParam[] ShaderParamOverrides = new ShaderParam[(int)ShaderMaterialVars.Count]; + + public class ShaderParam + { + public readonly ShaderParamInfo Info; + public readonly int Index; + public ShaderParam(ShaderMaterialVars var, ShaderParamType type, ReadOnlySpan defaultParam, ReadOnlySpan help, ShaderParamFlags flags) { + Info.Name = "override"; + Info.Type = type; + Info.DefaultValue = new(defaultParam); + Info.Help = new(help); + Info.Flags = flags; + + if (ShaderParamOverrides[(int)var] == null) { + + } + else { + AssertMsg(false, "ShaderParamOverrides at var index had null value"); + } + + ShaderParamOverrides[(int)var] = this; + Index = (int)var; + } + public ShaderParam(string name, ShaderParamType type, ReadOnlySpan defaultParam, ReadOnlySpan help, ShaderParamFlags flags = 0) { + Info.Name = name; + Info.Type = type; + Info.DefaultValue = new(defaultParam); + Info.Help = new(help); + Info.Flags = flags; + Index = (int)ShaderMaterialVars.Count + ShaderParams.Count; + ShaderParams.Add(this); + } + public static implicit operator int(ShaderParam param) => param.Index; + public ReadOnlySpan GetName() => Info.Name; + public ShaderParamType GetType() => Info.Type; + public ReadOnlySpan GetDefaultValue() => Info.DefaultValue; + public ShaderParamFlags GetFlags() => Info.Flags; + public ReadOnlySpan GetHelp() => Info.Help; + } + + public static readonly ShaderParam SPRITEORIGIN = new($"${nameof(SPRITEORIGIN)}", ShaderParamType.Vec3, "[0 0 0]", "sprite origin"); + public static readonly ShaderParam SPRITEORIENTATION = new($"${nameof(SPRITEORIENTATION)}", ShaderParamType.Integer, "0", "sprite orientation"); + public static readonly ShaderParam SPRITERENDERMODE = new($"${nameof(SPRITERENDERMODE)}", ShaderParamType.Integer, "0", "sprite rendermode"); + public static readonly ShaderParam IGNOREVERTEXCOLORS = new($"${nameof(IGNOREVERTEXCOLORS)}", ShaderParamType.Bool, "1", "ignore vertex colors"); + public static readonly ShaderParam NOSRGB = new($"${nameof(NOSRGB)}", ShaderParamType.Bool, "0", "do not operate in srgb space"); + public static readonly ShaderParam HDRCOLORSCALE = new($"${nameof(HDRCOLORSCALE)}", ShaderParamType.Float, "1.0", "hdr color scale"); + + const int SPR_VP_PARALLEL_UPRIGHT = 0; + const int SPR_FACING_UPRIGHT = 1; + const int SPR_VP_PARALLEL = 2; + const int SPR_ORIENTED = 3; + const int SPR_VP_PARALLEL_ORIENTED = 4; + + const uint SHADER_USE_VERTEX_COLOR = 1; + const uint SHADER_USE_CONSTANT_COLOR = 2; + + public override string? GetFallbackShader(IMaterialVar[] vars) => null; + public override ShaderFlags GetFlags() => Flags; + public override int GetNumParams() => base.GetNumParams() + ShaderParams.Count; + public override ReadOnlySpan GetParamName(int paramIndex) { + int baseClassParamCount = base.GetNumParams(); + if (paramIndex < baseClassParamCount) + return base.GetParamName(paramIndex); + else + return ShaderParams[paramIndex - baseClassParamCount].GetName(); + } + public override ReadOnlySpan GetParamHelp(int paramIndex) { + int baseClassParamCount = base.GetNumParams(); + if (paramIndex < baseClassParamCount) + return base.GetParamHelp(paramIndex); + else + return ShaderParams[paramIndex - baseClassParamCount].GetHelp(); + } + public override ShaderParamType GetParamType(int paramIndex) { + int baseClassParamCount = base.GetNumParams(); + if (paramIndex < baseClassParamCount) + return base.GetParamType(paramIndex); + else + return ShaderParams[paramIndex - baseClassParamCount].GetType(); + } + public override ReadOnlySpan GetParamDefault(int paramIndex) { + int baseClassParamCount = base.GetNumParams(); + if (paramIndex < baseClassParamCount) + return base.GetParamDefault(paramIndex); + else + return ShaderParams[paramIndex - baseClassParamCount].GetDefaultValue(); + } + + protected override void OnInitShaderParams(IMaterialVar[] parms, ReadOnlySpan materialName) { + if (!parms[(int)ShaderMaterialVars.Alpha].IsDefined()) + parms[(int)ShaderMaterialVars.Alpha].SetFloatValue(1.0f); + + if (!parms[HDRCOLORSCALE].IsDefined()) + parms[HDRCOLORSCALE].SetFloatValue(1.0f); + + if (!parms[NOSRGB].IsDefined()) + parms[NOSRGB].SetIntValue(1); + + SetFlags(parms, MaterialVarFlags.NoDebugOverride); + SetFlags(parms, MaterialVarFlags.VertexColor); + SetFlags(parms, MaterialVarFlags.VertexAlpha); + + if (parms[SPRITEORIENTATION].IsDefined()) { + ReadOnlySpan orientationString = parms[SPRITEORIENTATION].GetStringValue(); + if (orientationString.Equals("parallel_upright", StringComparison.OrdinalIgnoreCase)) + parms[SPRITEORIENTATION].SetIntValue(SPR_VP_PARALLEL_UPRIGHT); + else if (orientationString.Equals("facing_upright", StringComparison.OrdinalIgnoreCase)) + parms[SPRITEORIENTATION].SetIntValue(SPR_FACING_UPRIGHT); + else if (orientationString.Equals("vp_parallel", StringComparison.OrdinalIgnoreCase)) + parms[SPRITEORIENTATION].SetIntValue(SPR_VP_PARALLEL); + else if (orientationString.Equals("oriented", StringComparison.OrdinalIgnoreCase)) + parms[SPRITEORIENTATION].SetIntValue(SPR_ORIENTED); + else if (orientationString.Equals("vp_parallel_oriented", StringComparison.OrdinalIgnoreCase)) + parms[SPRITEORIENTATION].SetIntValue(SPR_VP_PARALLEL_ORIENTED); + else { + Warning("error with $spriteOrientation\n"); + parms[SPRITEORIENTATION].SetIntValue(SPR_VP_PARALLEL_UPRIGHT); + } + } + else + parms[SPRITEORIENTATION].SetIntValue(SPR_VP_PARALLEL_UPRIGHT); + } + + protected override void OnInitShaderInstance(IMaterialVar[] parms, ReadOnlySpan materialName) { + bool srgb = parms[NOSRGB].GetIntValue() == 0; + LoadTexture((int)ShaderMaterialVars.BaseTexture, srgb ? (int)TextureFlags.SRGB : 0); + } + + void SetSpriteCommonShadowState(uint shaderFlags) { + IShaderShadow shaderShadow = ShaderShadow!; + shaderShadow.EnableTexture(Sampler.Sampler0, true); + bool srgb = Params![NOSRGB].GetIntValue() == 0; + shaderShadow.EnableSRGBRead(Sampler.Sampler0, srgb); + + VertexFormat flags = VertexFormat.Position; + if ((shaderFlags & SHADER_USE_VERTEX_COLOR) != 0) + flags |= VertexFormat.Color; + int numTexCoords = 1; + shaderShadow.VertexShaderVertexFormat(flags, numTexCoords, null, 0); + + StaticShaderIndex vshIndex = new(shaderShadow, ShaderType.Vertex, "sprite"); + vshIndex.Set("VERTEXCOLOR", (shaderFlags & SHADER_USE_VERTEX_COLOR) != 0); + vshIndex.Set("SRGB", srgb); + shaderShadow.SetVertexShader("sprite", vshIndex.GetIndex()); + + StaticShaderIndex pshIndex = new(shaderShadow, ShaderType.Pixel, "sprite"); + pshIndex.Set("VERTEXCOLOR", (shaderFlags & SHADER_USE_VERTEX_COLOR) != 0); + pshIndex.Set("CONSTANTCOLOR", (shaderFlags & SHADER_USE_CONSTANT_COLOR) != 0); + pshIndex.Set("SRGB", srgb); + shaderShadow.SetPixelShader("sprite", pshIndex.GetIndex()); + + shaderShadow.EnableSRGBWrite(srgb); + } + + void SetSpriteDynamicShaderIndices(int pixelFogCombo) { + IShaderDynamicAPI shaderAPI = ShaderAPI!; + + MaterialFogMode fogType = shaderAPI.GetSceneFogMode(); + int fogIndex = (fogType == MaterialFogMode.LinearBelowFogZ) ? 1 : 0; + DynamicShaderIndex vshIndex = new(shaderAPI, ShaderType.Vertex); + vshIndex.Set("DOWATERFOG", fogIndex); + shaderAPI.SetVertexShaderIndex(vshIndex.GetIndex()); + + DynamicShaderIndex pshIndex = new(shaderAPI, ShaderType.Pixel); + pshIndex.Set("HDRENABLED", IsHDREnabled()); + pshIndex.Set("PIXELFOGTYPE", pixelFogCombo); + shaderAPI.SetPixelShaderIndex(pshIndex.GetIndex()); + + shaderAPI.SetPixelShaderFogParams((int)PixelShaderConst.FogParams); + + Span eyePos_SpecExponent = [0, 0, 0, 0]; + shaderAPI.GetWorldSpaceCameraPosition(eyePos_SpecExponent); + eyePos_SpecExponent[3] = 0.0f; + shaderAPI.SetPixelShaderConstant((int)PixelShaderConst.EyePosSpecExponent, eyePos_SpecExponent); + } + + void SetSpriteCommonDynamicState(uint shaderFlags) { + bool srgb = Params![NOSRGB].GetIntValue() == 0; + + BindTexture(Sampler.Sampler0, (int)ShaderMaterialVars.BaseTexture, (int)ShaderMaterialVars.Frame); + + SetSpriteDynamicShaderIndices(ShaderAPI!.GetPixelFogCombo()); + + if ((shaderFlags & SHADER_USE_CONSTANT_COLOR) != 0) { + if (srgb) + SetPixelShaderConstantGammaToLinear(0, (int)ShaderMaterialVars.Color, (int)ShaderMaterialVars.Alpha); + else + SetPixelShaderConstant(0, (int)ShaderMaterialVars.Color, (int)ShaderMaterialVars.Alpha); + } + + SetHDRColorScaleConstant(srgb); + } + + void SetHDRColorScaleConstant(bool srgb) { + if (IsHDREnabled()) { + if (srgb) + SetPixelShaderConstantGammaToLinear(1, HDRCOLORSCALE); + else + SetPixelShaderConstant(1, HDRCOLORSCALE); + } + } + + void SetFrameBlendDynamicState(ITexture texture, int frame, float fade, int pixelFogCombo) { + bool srgb = Params![NOSRGB].GetIntValue() == 0; + + ShaderSystem.BindTexture(Sampler.Sampler0, texture, frame); + + SetSpriteDynamicShaderIndices(pixelFogCombo); + + Span color = [0, 0, 0, 1.0f]; + color[0] = color[1] = color[2] = srgb ? MathLib.GammaToLinear(fade) : fade; + ShaderAPI!.SetPixelShaderConstant(0, color); + + SetHDRColorScaleConstant(srgb); + } + + protected override void OnDrawElements(IMaterialVar[] parms, IShaderDynamicAPI shaderAPI, VertexCompressionType vertexCompression) { + if (IsSnapshotting()) + ShaderShadow!.EnableCulling(false); + + switch ((RenderMode)parms[SPRITERENDERMODE].GetIntValue()) { + case RenderMode.Normal: + if (IsSnapshotting()) { + FogToFogColor(); + SetSpriteCommonShadowState(0); + } + else + SetSpriteCommonDynamicState(0); + Draw(); + break; + + case RenderMode.TransColor: + case RenderMode.TransTexture: + case RenderMode.TransAlpha: + if (IsSnapshotting()) { + ShaderShadow!.EnableDepthWrites(false); + ShaderShadow.EnableBlending(true); + ShaderShadow.BlendFunc(ShaderBlendFactor.SrcAlpha, ShaderBlendFactor.OneMinusSrcAlpha); + FogToFogColor(); + SetSpriteCommonShadowState(SHADER_USE_VERTEX_COLOR); + } + else + SetSpriteCommonDynamicState(SHADER_USE_VERTEX_COLOR); + Draw(); + break; + + case RenderMode.Glow: + case RenderMode.WorldGlow: + if (IsSnapshotting()) { + ShaderShadow!.EnableDepthWrites(false); + ShaderShadow.EnableDepthTest(false); + ShaderShadow.EnableBlending(true); + ShaderShadow.BlendFunc(ShaderBlendFactor.SrcAlpha, ShaderBlendFactor.One); + FogToBlack(); + SetSpriteCommonShadowState(SHADER_USE_VERTEX_COLOR); + } + else + SetSpriteCommonDynamicState(SHADER_USE_VERTEX_COLOR); + Draw(); + break; + + case RenderMode.TransAlphaAdd: + if (IsSnapshotting()) { + ShaderShadow!.EnableDepthWrites(false); + ShaderShadow.EnableBlending(true); + ShaderShadow.BlendFunc(ShaderBlendFactor.SrcAlpha, ShaderBlendFactor.OneMinusSrcAlpha); + FogToFogColor(); + SetSpriteCommonShadowState(SHADER_USE_VERTEX_COLOR); + } + else + SetSpriteCommonDynamicState(SHADER_USE_VERTEX_COLOR); + Draw(); + break; + + case RenderMode.TransAdd: { + uint flags = SHADER_USE_CONSTANT_COLOR; + if (parms[IGNOREVERTEXCOLORS].GetIntValue() == 0) + flags |= SHADER_USE_VERTEX_COLOR; + + if (IsSnapshotting()) { + ShaderShadow!.EnableDepthWrites(false); + ShaderShadow.EnableBlending(true); + ShaderShadow.BlendFunc(ShaderBlendFactor.SrcAlpha, ShaderBlendFactor.One); + FogToBlack(); + SetSpriteCommonShadowState(flags); + } + else + SetSpriteCommonDynamicState(flags); + Draw(); + } + break; + + case RenderMode.TransAddFrameBlend: { + float frame = parms[(int)ShaderMaterialVars.Frame].GetFloatValue(); + float fade = parms[(int)ShaderMaterialVars.Alpha].GetFloatValue(); + uint flags = SHADER_USE_CONSTANT_COLOR; + if (parms[IGNOREVERTEXCOLORS].GetIntValue() == 0) + flags |= SHADER_USE_VERTEX_COLOR; + + if (IsSnapshotting()) { + ShaderShadow!.EnableDepthWrites(false); + ShaderShadow.EnableBlending(true); + ShaderShadow.BlendFunc(ShaderBlendFactor.SrcAlpha, ShaderBlendFactor.One); + FogToBlack(); + SetSpriteCommonShadowState(flags); + Draw(); + } + else { + ITexture texture = parms[(int)ShaderMaterialVars.BaseTexture].GetTextureValue()!; + float frameBlendAlpha = 1.0f - (frame - (int)frame); + SetFrameBlendDynamicState(texture, (int)frame, fade * frameBlendAlpha, shaderAPI.GetPixelFogCombo1(true)); + Draw(); + + frameBlendAlpha = frame - (int)frame; + int numAnimationFrames = texture.GetNumAnimationFrames(); + SetFrameBlendDynamicState(texture, ((int)frame + 1) % numAnimationFrames, fade * frameBlendAlpha, shaderAPI.GetPixelFogCombo()); + Draw(); + } + } + break; + + default: + Warning("shader Sprite: Unknown sprite render mode\n"); + break; + } + } +}