From a021a8958ee4965c15dc34cd0ff86d028b8e5a34 Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Sat, 31 Oct 2026 22:23:26 +0000 Subject: [PATCH 01/16] Shader fixes/changes --- Game.Assets/sdn/shaders/blurfilter_gl460.vs | 18 +- Game.Assets/sdn/shaders/common_gl460.glsl | 6 + Game.Assets/sdn/shaders/downsample_gl460.vs | 12 +- Game.Assets/sdn/shaders/g_screenquad_gl460.vs | 2 +- Game.Assets/sdn/shaders/g_sobel_gl460.vs | 2 +- .../sdn/shaders/screenspaceeffect_gl460.vs | 2 +- Game.Assets/sdn/shaders/skin_gl460.vs | 4 +- Game.Assets/sdn/shaders/teeth_bump_gl460.vs | 4 +- .../shaders/vertexlitgeneric_bump_gl460.vs | 4 +- Source.MaterialSystem/MaterialSystem.cs | 2 + Source.ShaderAPI.Gl46/ShaderAPIGl46.cs | 44 +++-- Source.StdShader.Gl46/UnlitGeneric.cs | 160 +++++++++++++++--- .../VertexLitGenericHelper.cs | 2 +- 13 files changed, 196 insertions(+), 66 deletions(-) diff --git a/Game.Assets/sdn/shaders/blurfilter_gl460.vs b/Game.Assets/sdn/shaders/blurfilter_gl460.vs index fdd6a0136..11eb4c0cf 100644 --- a/Game.Assets/sdn/shaders/blurfilter_gl460.vs +++ b/Game.Assets/sdn/shaders/blurfilter_gl460.vs @@ -19,10 +19,6 @@ layout(location = 4) out vec2 vs_CoordTap1Neg; layout(location = 5) out vec2 vs_CoordTap2Neg; layout(location = 6) out vec2 vs_CoordTap3Neg; -vec2 FlipV(vec2 uv) -{ - return vec2(uv.x, 1.0 - uv.y); -} #include "common_clipplanes_gl460.vs" @@ -31,11 +27,11 @@ void main() gl_Position = vec4(v_Position, 1.0); WriteUserClipDistances(gl_Position); - vs_CoordTap0 = FlipV(v_TexCoord); - vs_CoordTap1 = FlipV(v_TexCoord + vsTapOffs0); - vs_CoordTap2 = FlipV(v_TexCoord + vsTapOffs1); - vs_CoordTap3 = FlipV(v_TexCoord + vsTapOffs2); - vs_CoordTap1Neg = FlipV(v_TexCoord - vsTapOffs0); - vs_CoordTap2Neg = FlipV(v_TexCoord - vsTapOffs1); - vs_CoordTap3Neg = FlipV(v_TexCoord - vsTapOffs2); + vs_CoordTap0 = v_TexCoord; + vs_CoordTap1 = v_TexCoord + vsTapOffs0; + vs_CoordTap2 = v_TexCoord + vsTapOffs1; + vs_CoordTap3 = v_TexCoord + vsTapOffs2; + vs_CoordTap1Neg = v_TexCoord - vsTapOffs0; + vs_CoordTap2Neg = v_TexCoord - vsTapOffs1; + vs_CoordTap3Neg = v_TexCoord - vsTapOffs2; } diff --git a/Game.Assets/sdn/shaders/common_gl460.glsl b/Game.Assets/sdn/shaders/common_gl460.glsl index 0452db9eb..de7e85150 100644 --- a/Game.Assets/sdn/shaders/common_gl460.glsl +++ b/Game.Assets/sdn/shaders/common_gl460.glsl @@ -8,6 +8,12 @@ const vec3 bumpBasis[3] = vec3[3]( vec3( -0.40824821591377258, -0.7071068286895752, OO_SQRT_3 ) ); +vec3 NormalizeD3D9(vec3 v) +{ + float lenSq = dot(v, v); + return lenSq > 0.0 ? v * inversesqrt(lenSq) : vec3(0.0); +} + vec3 CalcReflectionVectorUnnormalized(vec3 normal, vec3 eyeVector) { // FIXME: might be better of normalizing with a normalizing cube map and diff --git a/Game.Assets/sdn/shaders/downsample_gl460.vs b/Game.Assets/sdn/shaders/downsample_gl460.vs index 48ad038e2..af93796cf 100644 --- a/Game.Assets/sdn/shaders/downsample_gl460.vs +++ b/Game.Assets/sdn/shaders/downsample_gl460.vs @@ -17,10 +17,6 @@ layout(location = 1) out vec2 vs_CoordTap1; layout(location = 2) out vec2 vs_CoordTap2; layout(location = 3) out vec2 vs_CoordTap3; -vec2 FlipV(vec2 uv) -{ - return vec2(uv.x, 1.0 - uv.y); -} #include "common_clipplanes_gl460.vs" @@ -29,8 +25,8 @@ void main() gl_Position = vec4(v_Position, 1.0); WriteUserClipDistances(gl_Position); - vs_CoordTap0 = FlipV(v_TexCoord + vsTapOffs0); - vs_CoordTap1 = FlipV(v_TexCoord + vsTapOffs1); - vs_CoordTap2 = FlipV(v_TexCoord + vsTapOffs2); - vs_CoordTap3 = FlipV(v_TexCoord + vsTapOffs3); + vs_CoordTap0 = v_TexCoord + vsTapOffs0; + vs_CoordTap1 = v_TexCoord + vsTapOffs1; + vs_CoordTap2 = v_TexCoord + vsTapOffs2; + vs_CoordTap3 = v_TexCoord + vsTapOffs3; } diff --git a/Game.Assets/sdn/shaders/g_screenquad_gl460.vs b/Game.Assets/sdn/shaders/g_screenquad_gl460.vs index 74d6f696d..697c8f0e4 100644 --- a/Game.Assets/sdn/shaders/g_screenquad_gl460.vs +++ b/Game.Assets/sdn/shaders/g_screenquad_gl460.vs @@ -17,5 +17,5 @@ void main() { gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(v_Position, 1.0); WriteUserClipDistances(gl_Position); - vs_TexCoord = vec2(v_TexCoord.x, 1.0 - v_TexCoord.y); + vs_TexCoord = v_TexCoord; } diff --git a/Game.Assets/sdn/shaders/g_sobel_gl460.vs b/Game.Assets/sdn/shaders/g_sobel_gl460.vs index 06206fbc5..877b4834d 100644 --- a/Game.Assets/sdn/shaders/g_sobel_gl460.vs +++ b/Game.Assets/sdn/shaders/g_sobel_gl460.vs @@ -17,5 +17,5 @@ void main() { gl_Position = projectionMatrix * viewMatrix * vec4(v_Position, 1.0); WriteUserClipDistances(gl_Position); - vs_TexCoord = vec2(v_TexCoord.x, 1.0 - v_TexCoord.y); + vs_TexCoord = v_TexCoord; } diff --git a/Game.Assets/sdn/shaders/screenspaceeffect_gl460.vs b/Game.Assets/sdn/shaders/screenspaceeffect_gl460.vs index a117fb4a6..7d336452c 100644 --- a/Game.Assets/sdn/shaders/screenspaceeffect_gl460.vs +++ b/Game.Assets/sdn/shaders/screenspaceeffect_gl460.vs @@ -28,7 +28,7 @@ void main() gl_Position = vec4(v_Position, 1.0); #endif WriteUserClipDistances(gl_Position); - vs_BaseTexCoord = vec2(v_TexCoord.x, 1.0 - v_TexCoord.y); + vs_BaseTexCoord = v_TexCoord; vs_TexCoord0 = vec4(vs_BaseTexCoord, 0.0, 1.0); vs_TexCoord1 = vec4(0.0, 0.0, 0.0, 1.0); vs_TexCoord2 = vs_TexCoord0; diff --git a/Game.Assets/sdn/shaders/skin_gl460.vs b/Game.Assets/sdn/shaders/skin_gl460.vs index 7da2acbd4..1c660d129 100644 --- a/Game.Assets/sdn/shaders/skin_gl460.vs +++ b/Game.Assets/sdn/shaders/skin_gl460.vs @@ -69,8 +69,8 @@ void main() // Always normalize since flex path is controlled by runtime // constant not a shader combo and will always generate the normalization worldNormal = normalize(worldNormal); - worldTangentS = normalize(worldTangentS); - worldTangentT = normalize(worldTangentT); + worldTangentS = NormalizeD3D9(worldTangentS); + worldTangentT = NormalizeD3D9(worldTangentT); // Transform into projection space vec4 vProjPos = projectionMatrix * viewMatrix * vec4(worldPos, 1.0); diff --git a/Game.Assets/sdn/shaders/teeth_bump_gl460.vs b/Game.Assets/sdn/shaders/teeth_bump_gl460.vs index aaf23f13a..ba92e53f1 100644 --- a/Game.Assets/sdn/shaders/teeth_bump_gl460.vs +++ b/Game.Assets/sdn/shaders/teeth_bump_gl460.vs @@ -69,8 +69,8 @@ void main() #endif worldNormal = normalize(worldNormal); - worldTangentS = normalize(worldTangentS); - worldTangentT = normalize(worldTangentT); + worldTangentS = NormalizeD3D9(worldTangentS); + worldTangentT = NormalizeD3D9(worldTangentT); vec4 vProjPos = projectionMatrix * viewMatrix * vec4(worldPos, 1.0); gl_Position = vProjPos; diff --git a/Game.Assets/sdn/shaders/vertexlitgeneric_bump_gl460.vs b/Game.Assets/sdn/shaders/vertexlitgeneric_bump_gl460.vs index b88c51fc0..321ce7d7f 100644 --- a/Game.Assets/sdn/shaders/vertexlitgeneric_bump_gl460.vs +++ b/Game.Assets/sdn/shaders/vertexlitgeneric_bump_gl460.vs @@ -76,8 +76,8 @@ void main() // Always normalize since flex path is controlled by runtime // constant not a shader combo and will always generate the normalization worldNormal = normalize(worldNormal); - worldTangentS = normalize(worldTangentS); - worldTangentT = normalize(worldTangentT); + worldTangentS = NormalizeD3D9(worldTangentS); + worldTangentT = NormalizeD3D9(worldTangentT); vs_WorldNormal.xyz = worldNormal.xyz; vs_WorldTangent = vec4(worldTangentS.xyz, vTangent.w); // Propagate binormal sign in world tangent.w diff --git a/Source.MaterialSystem/MaterialSystem.cs b/Source.MaterialSystem/MaterialSystem.cs index d3f800cfb..5b64d1c8d 100644 --- a/Source.MaterialSystem/MaterialSystem.cs +++ b/Source.MaterialSystem/MaterialSystem.cs @@ -129,6 +129,8 @@ public MaterialSystem(IServiceProvider services) { QueuedRenderContexts = [new(this), new(this)]; ExecuteQueuedContextJobs = [new(MatQueueJob.JobKind.ExecuteQueuedContext, QueuedRenderContexts[0]), new(MatQueueJob.JobKind.ExecuteQueuedContext, QueuedRenderContexts[1])]; + MathLib.Init(IMaterialSystem.GAMMA, IMaterialSystem.TEXGAMMA, 0.0f, (int)IMaterialSystem.OVERBRIGHT); + // Link up ShaderDevice.PreInit(this, services); diff --git a/Source.ShaderAPI.Gl46/ShaderAPIGl46.cs b/Source.ShaderAPI.Gl46/ShaderAPIGl46.cs index 8a3721dba..c7867e0cd 100644 --- a/Source.ShaderAPI.Gl46/ShaderAPIGl46.cs +++ b/Source.ShaderAPI.Gl46/ShaderAPIGl46.cs @@ -158,7 +158,7 @@ public unsafe void ClearBuffers(bool clearColor, bool clearDepth, bool clearSten } else { glEnable(GL_SCISSOR_TEST); - glScissor(Viewport.X, renderTargetHeight - (Viewport.Y + Viewport.Height), Viewport.Width, Viewport.Height); + glScissor(Viewport.X, TargetRowY(Viewport.Y, Viewport.Height, renderTargetHeight), Viewport.Width, Viewport.Height); glClear(flags); glDisable(GL_SCISSOR_TEST); } @@ -579,7 +579,7 @@ public void SetScissorRect(int left, int top, int right, int bottom, bool enable GetBackBufferDimensions(out _, out int height); glEnable(GL_SCISSOR_TEST); - glScissor(left, height - bottom, right - left, bottom - top); + glScissor(left, TargetRowY(top, bottom - top, height), right - left, bottom - top); } public void DisableAllLocalLights() { @@ -1471,7 +1471,7 @@ public void SetViewports(ReadOnlySpan viewports) { // skips the first couple of loading screen frames. // TODO: Is this hack still needed? if (frame >= 2) { - glViewport(viewport.X, targetHeight - (viewport.Y + viewport.Height), viewport.Width, viewport.Height); + glViewport(viewport.X, TargetRowY(viewport.Y, viewport.Height, targetHeight), viewport.Width, viewport.Height); glDepthRangef(viewport.MinZ, viewport.MaxZ); } frame++; @@ -2194,7 +2194,7 @@ public unsafe void CreateTextures( bool managed = (creationFlags & CreateTextureFlags.Managed) != 0; bool isDepthBuffer = (creationFlags & CreateTextureFlags.DepthBuffer) != 0; bool isDynamic = (creationFlags & CreateTextureFlags.Dynamic) != 0; - if (isRenderTarget && !isDepthBuffer && SupportsSRGBDecode()) + if (!isDepthBuffer && SupportsSRGBDecode()) creationFlags |= CreateTextureFlags.SRGB; bool isSRGB = (creationFlags & CreateTextureFlags.SRGB) != 0; @@ -2798,6 +2798,16 @@ public void EnableLinearColorSpaceFrameBuffer(bool v) { public IShaderDevice GetShaderDevice() => this; + void UpdateClipOrigin() { + glClipControl(UsingTextureRenderTarget ? GL_UPPER_LEFT : GL_LOWER_LEFT, GL_NEGATIVE_ONE_TO_ONE); + } + + int TargetRowY(int y, int height, int targetHeight) { + if (UsingTextureRenderTarget) + return y; + return targetHeight - (y + height); + } + public void SetRenderTargetEx(int renderTargetID, ShaderAPITextureHandle_t colorTextureHandle = -1, ShaderAPITextureHandle_t depthTextureHandle = -1) { FlushBufferedPrimitives(); @@ -2807,6 +2817,7 @@ public void SetRenderTargetEx(int renderTargetID, ShaderAPITextureHandle_t color if (renderTargetID == 0) { glBindFramebuffer(GL_FRAMEBUFFER, 0); UsingTextureRenderTarget = usingTextureTarget; + UpdateClipOrigin(); } return; } @@ -2814,8 +2825,10 @@ public void SetRenderTargetEx(int renderTargetID, ShaderAPITextureHandle_t color if (colorTextureHandle >= 0) usingTextureTarget = true; - if (renderTargetID == 0) + if (renderTargetID == 0) { UsingTextureRenderTarget = usingTextureTarget; + UpdateClipOrigin(); + } glBindFramebuffer(GL_FRAMEBUFFER, renderFBO); @@ -2859,8 +2872,10 @@ public void CopyRenderTargetToTextureEx(ShaderAPITextureHandle_t textureHandle, System.Drawing.Rectangle src = Unsafe.IsNullRef(ref srcRect) ? new(0, 0, srcWidth, srcHeight) : srcRect; System.Drawing.Rectangle dst = Unsafe.IsNullRef(ref dstRect) ? new(0, 0, tex.Width, tex.Height) : dstRect; - int srcY0 = srcHeight - (src.Y + src.Height); - int dstY0 = tex.Height - (dst.Y + dst.Height); + int srcY0 = TargetRowY(src.Y, src.Height, srcHeight); + int srcY1 = srcY0 + src.Height; + if (!UsingTextureRenderTarget) + (srcY0, srcY1) = (srcY1, srcY0); bool srgbWriteEnabled = !boardStateValid || lastBoardState.SRGBWriteEnable; if (srgbWriteEnabled) @@ -2868,8 +2883,8 @@ public void CopyRenderTargetToTextureEx(ShaderAPITextureHandle_t textureHandle, glNamedFramebufferTexture(copyFBO, GL_COLOR_ATTACHMENT0, GetGL46Texture(textureHandle), 0); glBlitNamedFramebuffer(UsingTextureRenderTarget ? renderFBO : 0, copyFBO, - src.X, srcY0, src.X + src.Width, srcY0 + src.Height, - dst.X, dstY0, dst.X + dst.Width, dstY0 + dst.Height, + src.X, srcY0, src.X + src.Width, srcY1, + dst.X, dst.Y, dst.X + dst.Width, dst.Y + dst.Height, GL_COLOR_BUFFER_BIT, GL_LINEAR); glNamedFramebufferTexture(copyFBO, GL_COLOR_ATTACHMENT0, 0, 0); @@ -3675,12 +3690,15 @@ public unsafe void ReadPixels(int x, int y, int width, int height, Span da glBindFramebuffer(GL_READ_FRAMEBUFFER, (uint)drawFBO); glPixelStorei(GL_PACK_ALIGNMENT, 1); fixed (byte* p = bits) - glReadPixels(x, srcHeight - (y + height), width, height, GL_BGRA, GL_UNSIGNED_BYTE, p); + glReadPixels(x, TargetRowY(y, height, srcHeight), width, height, GL_BGRA, GL_UNSIGNED_BYTE, p); glBindFramebuffer(GL_READ_FRAMEBUFFER, (uint)prevRead); - byte[] flipped = new byte[pitch * height]; - for (int row = 0; row < height; row++) - bits.AsSpan((height - 1 - row) * pitch, pitch).CopyTo(flipped.AsSpan(row * pitch, pitch)); + byte[] flipped = bits; + if (!UsingTextureRenderTarget) { + flipped = new byte[pitch * height]; + for (int row = 0; row < height; row++) + bits.AsSpan((height - 1 - row) * pitch, pitch).CopyTo(flipped.AsSpan(row * pitch, pitch)); + } if (dstFormat == ImageFormat.BGRA8888) flipped.AsSpan(0, Math.Min(flipped.Length, data.Length)).CopyTo(data); diff --git a/Source.StdShader.Gl46/UnlitGeneric.cs b/Source.StdShader.Gl46/UnlitGeneric.cs index 079491879..834d4edf9 100644 --- a/Source.StdShader.Gl46/UnlitGeneric.cs +++ b/Source.StdShader.Gl46/UnlitGeneric.cs @@ -1,4 +1,5 @@ using Source.Common.MaterialSystem; +using Source.Common.ShaderAPI; using Source.Common.ShaderLib; namespace Source.StdShader.Gl46; @@ -49,33 +50,140 @@ public ShaderParam(string name, ShaderParamType type, ReadOnlySpan default public ReadOnlySpan GetHelp() => Info.Help; } + public static readonly ShaderParam ALBEDO = new($"${nameof(ALBEDO)}", ShaderParamType.Texture, "shadertest/BaseTexture", "albedo (Base texture with no baked lighting)"); public static readonly ShaderParam DETAIL = new($"${nameof(DETAIL)}", ShaderParamType.Texture, "shadertest/detail", "detail texture"); - public static readonly ShaderParam DETAILSCALE = new($"${nameof(DETAILSCALE)}", ShaderParamType.Float, "4", "scale of the detail texture" ); - public static readonly ShaderParam ENVMAP = new($"${nameof(ENVMAP)}", ShaderParamType.Texture, "shadertest/shadertest_env", "envmap" ); - public static readonly ShaderParam ENVMAPFRAME = new($"${nameof(ENVMAPFRAME)}", ShaderParamType.Integer, "0", "" ); - public static readonly ShaderParam ENVMAPMASK = new($"${nameof(ENVMAPMASK)}", ShaderParamType.Texture, "shadertest/shadertest_envmask", "envmap mask" ); - public static readonly ShaderParam ENVMAPMASKFRAME = new($"${nameof(ENVMAPMASKFRAME)}", ShaderParamType.Integer, "0", "" ); - public static readonly ShaderParam ENVMAPMASKSCALE = new($"${nameof(ENVMAPMASKSCALE)}", ShaderParamType.Float, "1", "envmap mask scale" ); - public static readonly ShaderParam ENVMAPTINT = new($"${nameof(ENVMAPTINT)}", ShaderParamType.Color, "[1 1 1]", "envmap tint" ); - public static readonly ShaderParam ENVMAPOPTIONAL = new($"${nameof(ENVMAPOPTIONAL)}", ShaderParamType.Bool, "0", "Make the envmap only apply to dx9 and higher hardware" ); - public static readonly ShaderParam DETAILBLENDMODE = new($"${nameof(DETAILBLENDMODE)}", ShaderParamType.Integer, "0", "mode for combining detail texture with base. 0=normal, 1= additive, 2=alpha blend detail over base, 3=crossfade" ); - public static readonly ShaderParam ALPHATESTREFERENCE = new($"${nameof(ALPHATESTREFERENCE)}", ShaderParamType.Float, "0.7", "" ); + public static readonly ShaderParam DETAILFRAME = new($"${nameof(DETAILFRAME)}", ShaderParamType.Integer, "0", "frame number for $detail"); + public static readonly ShaderParam DETAILSCALE = new($"${nameof(DETAILSCALE)}", ShaderParamType.Float, "4", "scale of the detail texture"); + public static readonly ShaderParam ENVMAP = new($"${nameof(ENVMAP)}", ShaderParamType.Texture, "shadertest/shadertest_env", "envmap"); + public static readonly ShaderParam ENVMAPFRAME = new($"${nameof(ENVMAPFRAME)}", ShaderParamType.Integer, "", "envmap frame number"); + public static readonly ShaderParam ENVMAPMASK = new($"${nameof(ENVMAPMASK)}", ShaderParamType.Texture, "shadertest/shadertest_envmask", "envmap mask"); + public static readonly ShaderParam ENVMAPMASKFRAME = new($"${nameof(ENVMAPMASKFRAME)}", ShaderParamType.Integer, "0", ""); + public static readonly ShaderParam ENVMAPMASKTRANSFORM = new($"${nameof(ENVMAPMASKTRANSFORM)}", ShaderParamType.Matrix, "center .5 .5 scale 1 1 rotate 0 translate 0 0", "$envmapmask texcoord transform"); + public static readonly ShaderParam ENVMAPTINT = new($"${nameof(ENVMAPTINT)}", ShaderParamType.Color, "[1 1 1]", "envmap tint"); + public static readonly ShaderParam ENVMAPCONTRAST = new($"${nameof(ENVMAPCONTRAST)}", ShaderParamType.Float, "0.0", "contrast 0 == normal 1 == color*color"); + public static readonly ShaderParam ENVMAPSATURATION = new($"${nameof(ENVMAPSATURATION)}", ShaderParamType.Float, "1.0", "saturation 0 == greyscale 1 == normal"); + public static readonly ShaderParam ALPHATESTREFERENCE = new($"${nameof(ALPHATESTREFERENCE)}", ShaderParamType.Float, "0.7", ""); + public static readonly ShaderParam VERTEXALPHATEST = new($"${nameof(VERTEXALPHATEST)}", ShaderParamType.Integer, "0", ""); + public static readonly ShaderParam HDRCOLORSCALE = new($"${nameof(HDRCOLORSCALE)}", ShaderParamType.Float, "1.0", "hdr color scale"); + public static readonly ShaderParam PHONGEXPONENT = new($"${nameof(PHONGEXPONENT)}", ShaderParamType.Float, "5.0", "Phong exponent for local specular lights"); + public static readonly ShaderParam PHONGTINT = new($"${nameof(PHONGTINT)}", ShaderParamType.Vec3, "5.0", "Phong tint for local specular lights"); + public static readonly ShaderParam PHONGALBEDOTINT = new($"${nameof(PHONGALBEDOTINT)}", ShaderParamType.Bool, "1.0", "Apply tint by albedo (controlled by spec exponent texture"); + public static readonly ShaderParam LIGHTWARPTEXTURE = new($"${nameof(LIGHTWARPTEXTURE)}", ShaderParamType.Texture, "shadertest/BaseTexture", "1D ramp texture for tinting scalar diffuse term"); + public static readonly ShaderParam PHONGWARPTEXTURE = new($"${nameof(PHONGWARPTEXTURE)}", ShaderParamType.Texture, "shadertest/BaseTexture", "2D map for warping specular"); + public static readonly ShaderParam PHONGFRESNELRANGES = new($"${nameof(PHONGFRESNELRANGES)}", ShaderParamType.Vec3, "[0 0.5 1]", "Parameters for remapping fresnel output"); + public static readonly ShaderParam PHONGBOOST = new($"${nameof(PHONGBOOST)}", ShaderParamType.Float, "1.0", "Phong overbrightening factor (specular mask channel should be authored to account for this)"); + public static readonly ShaderParam PHONGEXPONENTTEXTURE = new($"${nameof(PHONGEXPONENTTEXTURE)}", ShaderParamType.Texture, "shadertest/BaseTexture", "Phong Exponent map"); + public static readonly ShaderParam PHONG = new($"${nameof(PHONG)}", ShaderParamType.Bool, "0", "enables phong lighting"); + public static readonly ShaderParam DETAILBLENDMODE = new($"${nameof(DETAILBLENDMODE)}", ShaderParamType.Integer, "0", "mode for combining detail texture with base. 0=normal, 1= additive, 2=alpha blend detail over base, 3=crossfade"); + public static readonly ShaderParam DETAILBLENDFACTOR = new($"${nameof(DETAILBLENDFACTOR)}", ShaderParamType.Float, "1", "blend amount for detail texture."); + public static readonly ShaderParam DETAILTEXTURETRANSFORM = new($"${nameof(DETAILTEXTURETRANSFORM)}", ShaderParamType.Matrix, "center .5 .5 scale 1 1 rotate 0 translate 0 0", "$detail texcoord transform"); + public static readonly ShaderParam SELFILLUMMASK = new($"${nameof(SELFILLUMMASK)}", ShaderParamType.Texture, "shadertest/BaseTexture", "If we bind a texture here, it overrides base alpha (if any) for self illum"); + public static readonly ShaderParam DISTANCEALPHA = new($"${nameof(DISTANCEALPHA)}", ShaderParamType.Bool, "0", "Use distance-coded alpha generated from hi-res texture by vtex."); + public static readonly ShaderParam DISTANCEALPHAFROMDETAIL = new($"${nameof(DISTANCEALPHAFROMDETAIL)}", ShaderParamType.Bool, "0", "Take the distance-coded alpha mask from the detail texture."); + public static readonly ShaderParam SOFTEDGES = new($"${nameof(SOFTEDGES)}", ShaderParamType.Bool, "0", "Enable soft edges to distance coded textures."); + public static readonly ShaderParam SCALEEDGESOFTNESSBASEDONSCREENRES = new($"${nameof(SCALEEDGESOFTNESSBASEDONSCREENRES)}", ShaderParamType.Bool, "0", "Scale the size of the soft edges based upon resolution. 1024x768 = nominal."); + public static readonly ShaderParam EDGESOFTNESSSTART = new($"${nameof(EDGESOFTNESSSTART)}", ShaderParamType.Float, "0.6", "Start value for soft edges for distancealpha."); + public static readonly ShaderParam EDGESOFTNESSEND = new($"${nameof(EDGESOFTNESSEND)}", ShaderParamType.Float, "0.5", "End value for soft edges for distancealpha."); + public static readonly ShaderParam GLOW = new($"${nameof(GLOW)}", ShaderParamType.Bool, "0", "Enable glow/shadow for distance coded textures."); + public static readonly ShaderParam GLOWCOLOR = new($"${nameof(GLOWCOLOR)}", ShaderParamType.Color, "[1 1 1]", "color of outter glow for distance coded line art."); + public static readonly ShaderParam GLOWALPHA = new($"${nameof(GLOWALPHA)}", ShaderParamType.Float, "1", "Base glow alpha amount for glows/shadows with distance alpha."); + public static readonly ShaderParam GLOWSTART = new($"${nameof(GLOWSTART)}", ShaderParamType.Float, "0.7", "start value for glow/shadow"); + public static readonly ShaderParam GLOWEND = new($"${nameof(GLOWEND)}", ShaderParamType.Float, "0.5", "end value for glow/shadow"); + public static readonly ShaderParam GLOWX = new($"${nameof(GLOWX)}", ShaderParamType.Float, "0", "texture offset x for glow mask."); + public static readonly ShaderParam GLOWY = new($"${nameof(GLOWY)}", ShaderParamType.Float, "0", "texture offset y for glow mask."); public static readonly ShaderParam OUTLINE = new($"${nameof(OUTLINE)}", ShaderParamType.Bool, "0", "Enable outline for distance coded textures."); - public static readonly ShaderParam OUTLINECOLOR = new($"${nameof(OUTLINECOLOR)}", ShaderParamType.Color, "[1 1 1]", "color of outline for distance coded images." ); + public static readonly ShaderParam OUTLINECOLOR = new($"${nameof(OUTLINECOLOR)}", ShaderParamType.Color, "[1 1 1]", "color of outline for distance coded images."); + public static readonly ShaderParam OUTLINEALPHA = new($"${nameof(OUTLINEALPHA)}", ShaderParamType.Float, "0.0", "alpha value for outline"); public static readonly ShaderParam OUTLINESTART0 = new($"${nameof(OUTLINESTART0)}", ShaderParamType.Float, "0.0", "outer start value for outline"); public static readonly ShaderParam OUTLINESTART1 = new($"${nameof(OUTLINESTART1)}", ShaderParamType.Float, "0.0", "inner start value for outline"); public static readonly ShaderParam OUTLINEEND0 = new($"${nameof(OUTLINEEND0)}", ShaderParamType.Float, "0.0", "inner end value for outline"); public static readonly ShaderParam OUTLINEEND1 = new($"${nameof(OUTLINEEND1)}", ShaderParamType.Float, "0.0", "outer end value for outline"); - public static readonly ShaderParam SEPARATEDETAILUVS = new($"${nameof(SEPARATEDETAILUVS)}", ShaderParamType.Integer, "0", "" ); + public static readonly ShaderParam SCALEOUTLINESOFTNESSBASEDONSCREENRES = new($"${nameof(SCALEOUTLINESOFTNESSBASEDONSCREENRES)}", ShaderParamType.Bool, "0", "Scale the size of the soft part of the outline based upon resolution. 1024x768 = nominal."); + public static readonly ShaderParam SEPARATEDETAILUVS = new($"${nameof(SEPARATEDETAILUVS)}", ShaderParamType.Bool, "0", "Use texcoord1 for detail texture"); + public static readonly ShaderParam GAMMACOLORREAD = new($"${nameof(GAMMACOLORREAD)}", ShaderParamType.Integer, "0", "Disables SRGB conversion of color texture read."); + public static readonly ShaderParam LINEARWRITE = new($"${nameof(LINEARWRITE)}", ShaderParamType.Integer, "0", "Disables SRGB conversion of shader results."); + public static readonly ShaderParam DEPTHBLEND = new($"${nameof(DEPTHBLEND)}", ShaderParamType.Integer, "0", "fade at intersection boundaries"); + public static readonly ShaderParam DEPTHBLENDSCALE = new($"${nameof(DEPTHBLENDSCALE)}", ShaderParamType.Float, "50.0", "Amplify or reduce DEPTHBLEND fading. Lower values make harder edges."); + public static readonly ShaderParam RECEIVEFLASHLIGHT = new($"${nameof(RECEIVEFLASHLIGHT)}", ShaderParamType.Integer, "0", "Forces this material to receive flashlights."); - - protected override void OnInitShaderParams(IMaterialVar[] vars, ReadOnlySpan materialName) { - InitParamsUnlitGeneric((int)ShaderMaterialVars.BaseTexture, DETAILSCALE, ENVMAPOPTIONAL, ENVMAP, ENVMAPTINT, ENVMAPMASKSCALE, DETAILBLENDMODE); + private void SetupVars(ref VertexLitGeneric_Vars info) { + info.BaseTexture = (int)ShaderMaterialVars.BaseTexture; + info.BaseTextureFrame = (int)ShaderMaterialVars.Frame; + info.BaseTextureTransform = (int)ShaderMaterialVars.BaseTextureTransform; + info.Albedo = ALBEDO; + info.SelfIllumTint = -1; + info.Detail = DETAIL; + info.DetailFrame = DETAILFRAME; + info.DetailScale = DETAILSCALE; + info.DetailTextureCombineMode = DETAILBLENDMODE; + info.DetailTextureBlendFactor = DETAILBLENDFACTOR; + info.DetailTextureTransform = DETAILTEXTURETRANSFORM; + info.Envmap = ENVMAP; + info.EnvmapFrame = ENVMAPFRAME; + info.EnvmapMask = ENVMAPMASK; + info.EnvmapMaskFrame = ENVMAPMASKFRAME; + info.EnvmapMaskTransform = ENVMAPMASKTRANSFORM; + info.EnvmapTint = ENVMAPTINT; + info.Bumpmap = -1; + info.BumpFrame = -1; + info.BumpTransform = -1; + info.EnvmapContrast = ENVMAPCONTRAST; + info.EnvmapSaturation = ENVMAPSATURATION; + info.AlphaTestReference = ALPHATESTREFERENCE; + info.VertexAlphaTest = VERTEXALPHATEST; + info.FlashlightTexture = (int)ShaderMaterialVars.FlashLightTexture; + info.FlashlightTextureFrame = (int)ShaderMaterialVars.FlashLightTextureFrame; + info.HDRColorScale = HDRCOLORSCALE; + info.PhongExponent = -1; + info.PhongExponentTexture = -1; + info.DiffuseWarpTexture = -1; + info.PhongWarpTexture = -1; + info.PhongBoost = -1; + info.PhongFresnelRanges = -1; + info.Phong = -1; + info.PhongTint = -1; + info.PhongAlbedoTint = -1; + info.SelfIllumEnvMapMask_Alpha = -1; + info.AmbientOnly = -1; + info.BaseMapAlphaPhongMask = -1; + info.EnvmapFresnel = -1; + info.SelfIllumMask = -1; + info.DistanceAlpha = DISTANCEALPHA; + info.DistanceAlphaFromDetail = DISTANCEALPHAFROMDETAIL; + info.SoftEdges = SOFTEDGES; + info.EdgeSoftnessStart = EDGESOFTNESSSTART; + info.EdgeSoftnessEnd = EDGESOFTNESSEND; + info.ScaleEdgeSoftnessBasedOnScreenRes = SCALEEDGESOFTNESSBASEDONSCREENRES; + info.Glow = GLOW; + info.GlowColor = GLOWCOLOR; + info.GlowAlpha = GLOWALPHA; + info.GlowStart = GLOWSTART; + info.GlowEnd = GLOWEND; + info.GlowX = GLOWX; + info.GlowY = GLOWY; + info.Outline = OUTLINE; + info.OutlineColor = OUTLINECOLOR; + info.OutlineAlpha = OUTLINEALPHA; + info.OutlineStart0 = OUTLINESTART0; + info.OutlineStart1 = OUTLINESTART1; + info.OutlineEnd0 = OUTLINEEND0; + info.OutlineEnd1 = OUTLINEEND1; + info.ScaleOutlineSoftnessBasedOnScreenRes = SCALEOUTLINESOFTNESSBASEDONSCREENRES; + info.SeparateDetailUVs = SEPARATEDETAILUVS; + info.LinearWrite = LINEARWRITE; + info.GammaColorRead = GAMMACOLORREAD; + info.DepthBlend = DEPTHBLEND; + info.DepthBlendScale = DEPTHBLENDSCALE; + info.ReceiveFlashlight = RECEIVEFLASHLIGHT; } - public override string? GetFallbackShader(IMaterialVar[] vars) { - return null; + protected override void OnInitShaderParams(IMaterialVar[] parms, ReadOnlySpan materialName) { + VertexLitGeneric_Vars vars = new(); + SetupVars(ref vars); + InitParamsVertexLitGeneric(this, parms, materialName, false, ref vars); } + + 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) { @@ -106,13 +214,17 @@ public override ReadOnlySpan GetParamDefault(int paramIndex) { else return ShaderParams[paramIndex - baseClassParamCount].GetDefaultValue(); } - protected override void OnInitShaderInstance(IMaterialVar[] vars, ReadOnlySpan materialName) { - InitUnlitGeneric((int)ShaderMaterialVars.BaseTexture, DETAIL, ENVMAP, ENVMAPMASK); + + protected override void OnInitShaderInstance(IMaterialVar[] parms, ReadOnlySpan materialName) { + VertexLitGeneric_Vars vars = new(); + SetupVars(ref vars); + InitVertexLitGeneric(this, parms, false, ref vars); } - protected override void OnDrawElements(IMaterialVar[] vars, IShaderDynamicAPI shaderAPI, VertexCompressionType vertexCompression) { - VertexShaderUnlitGenericPass((int)ShaderMaterialVars.BaseTexture, (int)ShaderMaterialVars.Frame, (int)ShaderMaterialVars.BaseTextureTransform, - DETAIL, DETAILSCALE, true, ENVMAP, ENVMAPFRAME, ENVMAPMASK, - ENVMAPMASKFRAME, ENVMAPMASKSCALE, ENVMAPTINT, ALPHATESTREFERENCE, - DETAILBLENDMODE, OUTLINE, OUTLINECOLOR, OUTLINESTART0, OUTLINEEND1, SEPARATEDETAILUVS, "unlitgeneric"); + + protected override void OnDrawElements(IMaterialVar[] parms, IShaderDynamicAPI shaderAPI, VertexCompressionType vertexCompression, ref BasePerMaterialContextData? contextData) { + VertexLitGeneric_Vars vars = new(); + SetupVars(ref vars); + // todo flashlight mode + DrawVertexLitGeneric(this, parms, ShaderAPI, ShaderShadow, false, ref vars, vertexCompression, ref contextData); } } diff --git a/Source.StdShader.Gl46/VertexLitGenericHelper.cs b/Source.StdShader.Gl46/VertexLitGenericHelper.cs index 726207594..5ed6cdd12 100644 --- a/Source.StdShader.Gl46/VertexLitGenericHelper.cs +++ b/Source.StdShader.Gl46/VertexLitGenericHelper.cs @@ -938,7 +938,7 @@ private void DrawVertexLitGeneric_Internal(BaseVSShader shader, IMaterialVar[] p shader.Draw(); } - internal void InitVertexLitGeneric(VertexLitGeneric shader, IMaterialVar[] parms, bool vertexLitGeneric, ref VertexLitGeneric_Vars info) { + internal void InitVertexLitGeneric(BaseVSShader shader, IMaterialVar[] parms, bool vertexLitGeneric, ref VertexLitGeneric_Vars info) { if (info.Phong != -1 && parms[info.Phong].GetIntValue() != 0 && HardwareConfig.SupportsPixelShaders_2_b()) { InitSkin(shader, parms, ref info); return; From a4b2953f624c00400b0c052685b891476d7d4028 Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Sat, 31 Oct 2026 22:24:18 +0000 Subject: [PATCH 02/16] More lua --- Game.Client/CDLL_Util.cs | 7 ++ Game.Client/C_BaseAnimating.cs | 2 +- Game.Client/C_BaseViewModel.cs | 14 +-- Game.Client/View.cs | 3 +- Game.Server/GarrysMod/GMOD_Player.cs | 90 ++++++++++++++++ Game.Shared/BaseCombatWeaponShared.cs | 17 +++ Game.Shared/BaseViewModel.cs | 2 + Game.Shared/GarrysMod/LuaEntity.cs | 111 ++++++++++++++++++++ Game.Shared/GarrysMod/LuaEnts.cs | 33 +++++- Game.Shared/GarrysMod/LuaGlobalFunctions.cs | 11 ++ Game.Shared/GarrysMod/LuaPlayer.cs | 2 - Game.Shared/GarrysMod/LuaVector.cs | 2 +- Game.Shared/GarrysMod/WeaponPhysCannon.cs | 72 ------------- Source.GUI.Controls/ListPanel.cs | 7 +- 14 files changed, 286 insertions(+), 87 deletions(-) diff --git a/Game.Client/CDLL_Util.cs b/Game.Client/CDLL_Util.cs index 5ac6feeaa..d3b337d4b 100644 --- a/Game.Client/CDLL_Util.cs +++ b/Game.Client/CDLL_Util.cs @@ -66,6 +66,13 @@ public static int EntitiesInSphere(Span list, in Vector3 center, return sphereEnum.GetCount(); } + public static int EntitiesInBox(Span list, in Vector3 mins, in Vector3 maxs, EntityFlags flagMask, PartitionListMask partitionMask) { + FlaggedEntitiesEnum boxEnum = new(list, flagMask); + partition.EnumerateElementsInBox((SpatialPartitionListMask_t)partitionMask, mins, maxs, false, ref boxEnum); + + return boxEnum.GetCount(); + } + } public static partial class Util_Globals { diff --git a/Game.Client/C_BaseAnimating.cs b/Game.Client/C_BaseAnimating.cs index aed272583..b9023e34e 100644 --- a/Game.Client/C_BaseAnimating.cs +++ b/Game.Client/C_BaseAnimating.cs @@ -1132,7 +1132,7 @@ public int FindTransitionSequence(int currentSequence, int goalSequence, ref int public bool ReceivedSequence; public void SetReceivedSequence() => ReceivedSequence = true; - public bool ShouldResetSequenceOnNewModel() => ReceivedSequence == false; + public virtual bool ShouldResetSequenceOnNewModel() => ReceivedSequence == false; private void UpdateRelevantInterpolatedVars() { if (!IsMarkedForDeletion() && !GetPredictable() && !IsClientCreated() && GetModelPtr() != null && GetModelPtr()!.SequencesAvailable()) AddBaseAnimatingInterpolatedVars(); diff --git a/Game.Client/C_BaseViewModel.cs b/Game.Client/C_BaseViewModel.cs index fcbe58473..56f4d9763 100644 --- a/Game.Client/C_BaseViewModel.cs +++ b/Game.Client/C_BaseViewModel.cs @@ -14,14 +14,14 @@ public partial class C_BaseViewModel [PredictionTable] public static readonly new PredictionField[] Prediction = [ new(nameof(ModelIndex)) { Type = FieldType.Short, Flags = FieldTypeDescFlags.InSendTable | FieldTypeDescFlags.ModelIndex }, - new(nameof(Skin)) { Type = FieldType.Integer }, - new(nameof(Body)) { Type = FieldType.Integer }, - new(nameof(Sequence)) { Type = FieldType.Integer }, - new(nameof(_ViewModelIndex)) { Type = FieldType.Integer }, - new(nameof(PlaybackRate)) { Type = FieldType.Float, Tolerance = 0.125f }, + new(nameof(Skin)) { Type = FieldType.Integer, Flags = FieldTypeDescFlags.InSendTable }, + new(nameof(Body)) { Type = FieldType.Integer, Flags = FieldTypeDescFlags.InSendTable }, + new(nameof(Sequence)) { Type = FieldType.Integer, Flags = FieldTypeDescFlags.InSendTable }, + new(nameof(_ViewModelIndex)) { Type = FieldType.Integer, Flags = FieldTypeDescFlags.InSendTable }, + new(nameof(PlaybackRate)) { Type = FieldType.Float, Flags = FieldTypeDescFlags.InSendTable, Tolerance = 0.125f }, new(nameof(Effects)) { Type = FieldType.Integer, Flags = FieldTypeDescFlags.InSendTable | FieldTypeDescFlags.Override }, - new(nameof(AnimationParity)) { Type = FieldType.Integer }, - nameof(Weapon), + new(nameof(AnimationParity)) { Type = FieldType.Integer, Flags = FieldTypeDescFlags.InSendTable }, + new(nameof(Weapon)) { Flags = FieldTypeDescFlags.InSendTable }, new(nameof(AnimTime)) { Type = FieldType.Float, Flags = 0 }, new(nameof(Owner)) { Flags = FieldTypeDescFlags.Save }, new(nameof(TimeWeaponIdle)) { Type = FieldType.Float }, diff --git a/Game.Client/View.cs b/Game.Client/View.cs index 73764e12d..af65a498a 100644 --- a/Game.Client/View.cs +++ b/Game.Client/View.cs @@ -99,6 +99,7 @@ public class ViewRender : IViewRender static ConVar r_nearz = new(VIEW_NEARZ.ToString(), FCvar.Cheat, "Override the near clipping plane."); static ConVar r_farz = new("-1", FCvar.Cheat, "Override the far clipping plane. -1 means to use the value in env_fog_controller."); + static ConVar mat_viewportscale = new("1.0", FCvar.Archive, "Scale down the main viewport (to reduce GPU impact on CPU profiling)", (1.0f / 640f /*BASE_WIDTH*/), 1.0f); public ViewSetup CurrentView; bool ForceNoVis; @@ -321,7 +322,7 @@ public void Render(ViewRects rect) { viewEye.FOV = ScaleFOVByWidthRatio(viewEye.FOV, limitedAspectRatio); viewEye.FOVViewmodel = ScaleFOVByWidthRatio(viewEye.FOVViewmodel, limitedAspectRatio); - float viewportScale = 1.0f; // mat_viewportscale todo + float viewportScale = mat_viewportscale.GetFloat(); viewEye.UnscaledX = vr.X; viewEye.UnscaledY = vr.Y; viewEye.UnscaledWidth = vr.Width; diff --git a/Game.Server/GarrysMod/GMOD_Player.cs b/Game.Server/GarrysMod/GMOD_Player.cs index d4ba9e614..4747e07cc 100644 --- a/Game.Server/GarrysMod/GMOD_Player.cs +++ b/Game.Server/GarrysMod/GMOD_Player.cs @@ -8,6 +8,8 @@ using Source.Common.Commands; using Source.Common.Engine; using Source.Common.GarrysMod.Lua; +using Source.Common.Hashing; +using Source.Common.Networking; using System.Numerics; @@ -37,6 +39,91 @@ public GMOD_Player() { PlayerColor = new(255, 255, 255); WeaponColor = new(255, 255, 255); Hands = new(); + NeedsUniqueID = true; + } + + TimeUnit_t InitialSpawnTime; + bool NeedsUniqueID; + uint UniqueIDCRCValue; + InlineArray128 UniqueID; + + static uint UniqueIDCRC(ReadOnlySpan str) { + Span bytes = stackalloc byte[str.Length]; + for (int i = 0; i < str.Length; i++) + bytes[i] = (byte)str[i]; + uint crc = 0; + CRC32.Init(ref crc); + CRC32.ProcessBuffer(ref crc, (ReadOnlySpan)bytes); + CRC32.Final(ref crc); + return crc; + } + + void CallPlayerAuthed(ReadOnlySpan uniqueID) { + if (gGM == null || !gGM.CallWithArgs((int)LUA_POOLEDSTRING.PlayerAuthed)) + return; + LuaEntity.Push_Entity(this); + g_Lua!.PushString(GetNetworkIDString()); + g_Lua!.PushString(uniqueID); + gGM.CallNoReturns(3); + } + + public ReadOnlySpan GetUniqueID() { + if (gpGlobals.MaxClients == 1) { + if (NeedsUniqueID) { + NeedsUniqueID = false; + CallPlayerAuthed("1"); + } + return "1"; + } + + if (!NeedsUniqueID) + return ((Span)UniqueID).SliceNullTerminatedString(); + + if (IsBot() || IsFakeClient()) { + ReadOnlySpan name = GetPlayerName(); + if (name.IsEmpty) { + NeedsUniqueID = true; + return "0"; + } + UniqueIDCRCValue = UniqueIDCRC(name); + NeedsUniqueID = false; + sprintf(UniqueID, "%u").U(UniqueIDCRCValue); + return ((Span)UniqueID).SliceNullTerminatedString(); + } + + bool lan = false; + ConVar? sv_lan = cvar.FindVar("sv_lan"); + if (sv_lan != null) + lan = sv_lan.GetInt() != 0; + + Span gmID = stackalloc char[128]; + if (stricmp(GetNetworkIDString(), "STEAM_ID_LAN") == 0 && lan) { + UniqueIDCRCValue = 0; + INetChannelInfo? netInfo = engine.GetPlayerNetInfo(EntIndex()); + if (netInfo != null) { + sprintf(gmID, "gm_%s_gm").S(netInfo.GetAddress()); + UniqueIDCRCValue = UniqueIDCRC(gmID.SliceNullTerminatedString()); + } + NeedsUniqueID = netInfo == null; + sprintf(UniqueID, "%u").U(UniqueIDCRCValue); + return ((Span)UniqueID).SliceNullTerminatedString(); + } + + if (stricmp(GetNetworkIDString(), "STEAMID_PENDING") != 0 && stricmp(GetNetworkIDString(), "STEAM_ID_LAN") != 0 && stricmp(GetNetworkIDString(), "STEAM_ID_PENDING") != 0) { + sprintf(gmID, "gm_%s_gm").S(GetNetworkIDString()); + UniqueIDCRCValue = UniqueIDCRC(gmID.SliceNullTerminatedString()); + NeedsUniqueID = false; + sprintf(UniqueID, "%u").U(UniqueIDCRCValue); + CallPlayerAuthed(((Span)UniqueID).SliceNullTerminatedString()); + return ((Span)UniqueID).SliceNullTerminatedString(); + } + + if (InitialSpawnTime + 60.0 < gpGlobals.CurTime) { + Span cmd = stackalloc char[64]; + sprintf(cmd, "kickid %d \"No SteamID\"\n").D(engine.GetPlayerUserId(Edict()!)); + engine.ServerCommand(cmd.SliceNullTerminatedString()); + } + return "PENDING"; } #if GMOD_DLL @@ -102,6 +189,7 @@ public void SnapToGrid(ref Vector3 vec) { public override void InitialSpawn() { SetModel("models/player.mdl"); base.InitialSpawn(); + InitialSpawnTime = gpGlobals.CurTime; if (gGM != null && gGM.CallWithArgs((int)LUA_POOLEDSTRING.PlayerInitialSpawn)) { LuaEntity.Push_Entity(this); @@ -213,6 +301,8 @@ public virtual void SetWeaponColor(in Vector3 color) { public partial bool SprintEnabled { get; set; } public override void PlayerRunCommand(UserCmd ucmd, IMoveHelper moveHelper) { + if (NeedsUniqueID) + GetUniqueID(); base.PlayerRunCommand(ucmd, moveHelper); } diff --git a/Game.Shared/BaseCombatWeaponShared.cs b/Game.Shared/BaseCombatWeaponShared.cs index b1311eef7..afebe6ff0 100644 --- a/Game.Shared/BaseCombatWeaponShared.cs +++ b/Game.Shared/BaseCombatWeaponShared.cs @@ -899,6 +899,9 @@ public virtual bool DefaultDeploy(ReadOnlySpan viewModel, ReadOnlySpan viewModel, ReadOnlySpan false; #endif [NetworkVar("m_nViewModelIndex")] public partial int _ViewModelIndex { get; set; } diff --git a/Game.Shared/GarrysMod/LuaEntity.cs b/Game.Shared/GarrysMod/LuaEntity.cs index 34457b928..025e63bc2 100644 --- a/Game.Shared/GarrysMod/LuaEntity.cs +++ b/Game.Shared/GarrysMod/LuaEntity.cs @@ -84,6 +84,17 @@ public static void RemoveQueuedEntities() { ProcessingRemoveList = false; } + [LuaMethod] + static int Entity__IsMarkedForDeletion(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + if (IsQueuedForRemoval(ent)) { + lua.PushBool(true); + return 1; + } + lua.PushBool(IsQueuedForRemoval(ent) || ent.IsMarkedForDeletion()); + return 1; + } + [LuaMethod] static int Entity__Remove(ILuaInterface lua) { if (LuaEnts.IsRendering()) { @@ -490,6 +501,106 @@ static int Entity__SetNotSolid(ILuaInterface lua) { return 0; } +#if GAME_DLL + static bool CanModifyPhysics(BaseEntity ent) { + if (ent == GetWorldEntity()) + return false; + if (ent.IsRagdoll() || ent.IsPlayer()) + return false; + return ent is not PropVehicleDriveable; + } +#endif + + [LuaMethod] + static int Entity__PhysicsDestroy(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; +#if CLIENT_DLL + if (ent == C_World.GetClientWorldEntity() || ent.IsRagdoll() || ent.IsPlayer()) + return 0; +#else + if (!CanModifyPhysics(ent)) + return 0; +#endif + if (PhysIsInCallback() && ent.VPhysicsGetObject() != null) { + lua.ErrorFromLua("Cannot destroy physics in a physics callback!"); + lua.PushBool(false); + return 1; + } + ent.VPhysicsDestroyObject(); + return 0; + } + + [LuaMethod] + static int Entity__SetColor(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + Color color = lua.GetColor(2); +#if CLIENT_DLL + ent.ColorRender = color; +#else + ent.SetRenderColor(color.R, color.G, color.B, color.A); +#endif + return 0; + } + + [LuaMethod] + static int Entity__SetColor4Part(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + byte r = (byte)(int)lua.GetNumber(2); + byte g = (byte)(int)lua.GetNumber(3); + byte b = (byte)(int)lua.GetNumber(4); + byte a = (byte)(int)lua.GetNumber(5); +#if CLIENT_DLL + ent.ColorRender = new Color(r, g, b, a); +#else + ent.SetRenderColor(r, g, b, a); +#endif + return 0; + } + +#if GAME_DLL + [LuaMethod] + static int Entity__IsConstraint(ILuaInterface lua) { + // todo: once physconstraint is ported + lua.PushBool(false); + return 1; + } +#endif + + [LuaMethod] + static int Entity__SetRenderMode(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + RenderMode renderMode = (RenderMode)Math.Clamp((int)lua.GetNumber(2), (int)RenderMode.Normal, (int)RenderMode.Count - 1); +#if CLIENT_DLL + ent.SetRenderMode(renderMode, false); +#else + if (ent.RenderMode != (byte)renderMode) + ent.RenderMode = (byte)renderMode; +#endif + return 0; + } + + [LuaMethod] + static int Entity__SetSpawnEffect(ILuaInterface lua) { +#if GAME_DLL + BaseEntity ent = Get_Entity(1, false)!; + if (!lua.GetBool(2)) { + ent.NetworkStateChanged(); + ent.GModFlags &= ~(1 << 1); + return 0; + } + ent.NetworkStateChanged(); + ent.GModFlags |= 1 << 1; +#endif + return 0; + } + + [LuaMethod] + static int Entity__GetSpawnEffect(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + lua.PushBool(((ent.GModFlags >> 1) & 1) != 0); + return 1; + } + [LuaMethod] static int Entity__SetTransmitWithParent(ILuaInterface lua) { BaseEntity ent = Get_Entity(1, false)!; diff --git a/Game.Shared/GarrysMod/LuaEnts.cs b/Game.Shared/GarrysMod/LuaEnts.cs index 730b925f9..e7f26fd20 100644 --- a/Game.Shared/GarrysMod/LuaEnts.cs +++ b/Game.Shared/GarrysMod/LuaEnts.cs @@ -1,3 +1,6 @@ +using Source.Common; + +using System.Numerics; #if CLIENT_DLL || GAME_DLL using Source.Common.GarrysMod.Lua; @@ -198,7 +201,35 @@ static int FindByClass(ILuaInterface lua) { } // todo: FindInSphere - // todo: FindInBox +#if CLIENT_DLL + static readonly C_BaseEntity?[] FindList = new C_BaseEntity?[8192]; +#else + static readonly BaseEntity[] FindList = new BaseEntity[8192]; +#endif + + [LuaFunction] + static int FindInBox(ILuaInterface lua) { + Vector3 a = LuaVector.Get_Vector(1); + Vector3 b = LuaVector.Get_Vector(2); + LuaVector.OrderVectors(a, b, out Vector3 mins, out Vector3 maxs); +#if CLIENT_DLL + int count = Util.EntitiesInBox(FindList, mins, maxs, 0, PartitionListMask.ClientNonStaticEdicts); +#else + int count = Util.EntitiesInBox(FindList, mins, maxs, 0); +#endif + Span list = FindList; + lua.CreateTable(); + int i = 1; + for (int j = 0; j < count; j++) { + if (list[j] == null) + continue; + lua.PushNumber(i++); + LuaEntity.Push_Entity(list[j]); + lua.SetTable(-3); + } + list[..count].Clear(); + return 1; + } [LuaFunction] static int FindByName(ILuaInterface lua) { diff --git a/Game.Shared/GarrysMod/LuaGlobalFunctions.cs b/Game.Shared/GarrysMod/LuaGlobalFunctions.cs index 40a5c7119..58ceb8ce2 100644 --- a/Game.Shared/GarrysMod/LuaGlobalFunctions.cs +++ b/Game.Shared/GarrysMod/LuaGlobalFunctions.cs @@ -121,6 +121,17 @@ static int LocalToWorld(ILuaInterface lua) { } #endif +#if CLIENT_DLL + [LuaGlobal] + static int GetViewEntity(ILuaInterface lua) { + C_BaseEntity? ent = cl_entitylist.GetBaseEntity(render.GetViewEntity()); + if (ent == null) + ent = C_BasePlayer.GetLocalPlayer(); + LuaEntity.Push_Entity(ent); + return 1; + } +#endif + [LuaGlobal] static int WorldToLocal(ILuaInterface lua) { Vector3 pos = LuaVector.Get_Vector(1); diff --git a/Game.Shared/GarrysMod/LuaPlayer.cs b/Game.Shared/GarrysMod/LuaPlayer.cs index cd639c525..d92643e11 100644 --- a/Game.Shared/GarrysMod/LuaPlayer.cs +++ b/Game.Shared/GarrysMod/LuaPlayer.cs @@ -754,14 +754,12 @@ static int Player__GetCurrentCommand(ILuaInterface lua) { return 1; } -#if CLIENT_DLL [LuaMethod] static int Player__UniqueID(ILuaInterface lua) { GMOD_Player player = (GMOD_Player)Get_Player(1, false)!; lua.PushString(player.GetUniqueID()); return 1; } -#endif #if GAME_DLL [LuaMethod] diff --git a/Game.Shared/GarrysMod/LuaVector.cs b/Game.Shared/GarrysMod/LuaVector.cs index b87948209..ea9e5653b 100644 --- a/Game.Shared/GarrysMod/LuaVector.cs +++ b/Game.Shared/GarrysMod/LuaVector.cs @@ -276,7 +276,7 @@ static float Vector__DistToSqr(ref Vector3 a, ref Vector3 b) { return dx * dx + dy * dy + dz * dz; } - static void OrderVectors(in Vector3 a, in Vector3 b, out Vector3 mins, out Vector3 maxs) { + internal static void OrderVectors(in Vector3 a, in Vector3 b, out Vector3 mins, out Vector3 maxs) { mins = default; maxs = default; for (int i = 0; i < 3; i++) { diff --git a/Game.Shared/GarrysMod/WeaponPhysCannon.cs b/Game.Shared/GarrysMod/WeaponPhysCannon.cs index 8f19a8687..cc5bb3c1a 100644 --- a/Game.Shared/GarrysMod/WeaponPhysCannon.cs +++ b/Game.Shared/GarrysMod/WeaponPhysCannon.cs @@ -762,80 +762,8 @@ public override int DrawModel(StudioFlags flags) { return base.DrawModel(flags); } - // This was added to test enginetrace, can remove later - private void DrawTraceTest() { - C_BasePlayer? player = C_BasePlayer.GetLocalPlayer(); - if (player == null) return; - - MathLib.AngleVectors(player.EyeAngles(), out Vector3 forward); - - Vector3 start = player.EyePosition(); - Vector3 end = start + forward * MAX_TRACE_LENGTH; - - Util.TraceLine(start, end, Mask.Shot, player, Source.CollisionGroup.None, out Trace tr); - - GetEffectParameters(EffectType.Core, out Color color, out float scale, out IMaterial material, out Vector3 attachment); - - if (color.A <= 0) - return; - - using MatRenderContextPtr renderContext = new(materials); - renderContext.Bind(material, this); - - float size = scale; - Vector3 origin = tr.EndPos; - Vector3 normal = tr.Plane.Normal; - - Vector3 right, up; - if (MathF.Abs(normal.Z) < 0.9f) - MathLib.CrossProduct(in normal, new Vector3(0, 0, 1), out right); - else - MathLib.CrossProduct(in normal, new Vector3(1, 0, 0), out right); - MathLib.VectorNormalize(ref right); - MathLib.CrossProduct(in normal, in right, out up); - MathLib.VectorNormalize(ref up); - - MeshBuilder meshBuilder = new(); - Vector3 point = default; - - IMesh pMesh = renderContext.GetDynamicMesh(); - meshBuilder.Begin(pMesh, MaterialPrimitiveType.Quads, 1); - - meshBuilder.Color4ubv(color); - meshBuilder.TexCoord2f(0, 0, 1); - MathLib.VectorMA(origin, -size, up, out point); - MathLib.VectorMA(point, -size, right, out point); - meshBuilder.Position3fv(point); - meshBuilder.AdvanceVertex(); - - meshBuilder.Color4ubv(color); - meshBuilder.TexCoord2f(0, 0, 0); - MathLib.VectorMA(origin, size, up, out point); - MathLib.VectorMA(point, -size, right, out point); - meshBuilder.Position3fv(point); - meshBuilder.AdvanceVertex(); - - meshBuilder.Color4ubv(color); - meshBuilder.TexCoord2f(0, 1, 0); - MathLib.VectorMA(origin, size, up, out point); - MathLib.VectorMA(point, size, right, out point); - meshBuilder.Position3fv(point); - meshBuilder.AdvanceVertex(); - - meshBuilder.Color4ubv(color); - meshBuilder.TexCoord2f(0, 1, 1); - MathLib.VectorMA(origin, -size, up, out point); - MathLib.VectorMA(point, size, right, out point); - meshBuilder.Position3fv(point); - meshBuilder.AdvanceVertex(); - - meshBuilder.End(); - pMesh.Draw(); - } - public override void ViewModelDrawn(BaseViewModel viewmodelflags) { DrawEffects(); - DrawTraceTest(); base.ViewModelDrawn(viewmodelflags); } public void UpdateElementPosition() { diff --git a/Source.GUI.Controls/ListPanel.cs b/Source.GUI.Controls/ListPanel.cs index 1af53f569..ecb58a504 100644 --- a/Source.GUI.Controls/ListPanel.cs +++ b/Source.GUI.Controls/ListPanel.cs @@ -512,8 +512,11 @@ void IndexItem(int itemID) { bSortAscending = true; SortFuncSecondary = null; - newitem.SortedTreeIndexes[i] = rbtree.Count; - rbtree.Add(rbtree.Count, item); + int index = rbtree.Count; + while (rbtree.ContainsKey(index)) + index++; + newitem.SortedTreeIndexes[i] = index; + rbtree.Add(index, item); } } From 8fdd1d2699cb724b0382db0018da0e115af8c7a4 Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Sat, 31 Oct 2026 22:56:13 +0000 Subject: [PATCH 03/16] Fix --- Game.Client/C_TEParticleSystem.cs | 2 +- Game.Server.Tests/NetworkTableCRCTests.cs | 4 ++-- Game.Server/TEParticleSystem.cs | 2 +- Game.Server/TEPhysicsProp.cs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Game.Client/C_TEParticleSystem.cs b/Game.Client/C_TEParticleSystem.cs index f9ae7e9d6..dcf332752 100644 --- a/Game.Client/C_TEParticleSystem.cs +++ b/Game.Client/C_TEParticleSystem.cs @@ -9,7 +9,7 @@ public partial class C_TEParticleSystem : C_BaseTempEntity { [NetworkTable] public static readonly NetworkProp[] Props = [ - new(nameof(Origin)) { Encoding = NetEncoding.VectorElements, Bits = 0, Flags = PropFlags.Coord | PropFlags.NoScale | PropFlags.IsAVectorElem }, + new(nameof(Origin)) { Encoding = NetEncoding.VectorElements, Bits = 0, Flags = PropFlags.Coord | PropFlags.NoScale }, ]; [NetworkVar("m_vecOrigin")] diff --git a/Game.Server.Tests/NetworkTableCRCTests.cs b/Game.Server.Tests/NetworkTableCRCTests.cs index 48d31edaf..4225b9925 100644 --- a/Game.Server.Tests/NetworkTableCRCTests.cs +++ b/Game.Server.Tests/NetworkTableCRCTests.cs @@ -224,7 +224,7 @@ static readonly (string Table, int Props, uint CRC)[] ExpectedSendTables = [ ("DT_Sun", 9, 0x4DF9570F), ("DT_Team", 6, 0xE8978EEF), ("DT_TEAntlionDust", 4, 0x586372F5), - ("DT_TEParticleSystem", 4, 0x065AB3D7), + ("DT_TEParticleSystem", 4, 0x5BCAB5BE), ("DT_TEArmorRicochet", 1, 0xB4D64715), ("DT_TEMetalSparks", 2, 0x2414F0A1), ("DT_BaseBeam", 15, 0x28E975D5), @@ -259,7 +259,7 @@ static readonly (string Table, int Props, uint CRC)[] ExpectedSendTables = [ ("DT_TEKillPlayerAttachments", 2, 0x9540E3DA), ("DT_TELargeFunnel", 2, 0x59A289D4), ("DT_TEMuzzleFlash", 5, 0x4D3036F3), - ("DT_TEPhysicsProp", 12, 0x778EA75F), + ("DT_TEPhysicsProp", 12, 0x46C6424C), ("DT_TEPlayerAnimEvent", 3, 0x532041F7), ("DT_TEPlayerDecal", 4, 0xFE26A6F9), ("DT_TEProjectedDecal", 5, 0x42A7776F), diff --git a/Game.Server/TEParticleSystem.cs b/Game.Server/TEParticleSystem.cs index a9c6f1714..733948c37 100644 --- a/Game.Server/TEParticleSystem.cs +++ b/Game.Server/TEParticleSystem.cs @@ -9,7 +9,7 @@ public partial class TEParticleSystem(ReadOnlySpan name) : BaseTempEntity( { [NetworkTable] public static readonly NetworkProp[] Props = [ - new(nameof(Origin)) { Encoding = NetEncoding.VectorElements, Bits = 0, Flags = PropFlags.Coord | PropFlags.NoScale | PropFlags.IsAVectorElem }, + new(nameof(Origin)) { Encoding = NetEncoding.VectorElements, Bits = 0, Flags = PropFlags.Coord | PropFlags.NoScale }, ]; [NetworkVar("m_vecOrigin")] diff --git a/Game.Server/TEPhysicsProp.cs b/Game.Server/TEPhysicsProp.cs index d5baa61fa..c07cd910c 100644 --- a/Game.Server/TEPhysicsProp.cs +++ b/Game.Server/TEPhysicsProp.cs @@ -10,7 +10,7 @@ public partial class TEPhysicsProp(ReadOnlySpan name) : BaseTempEntity(nam [NetworkTable] public static readonly NetworkProp[] Props = [ new(nameof(Origin)) { Bits = 0, Flags = PropFlags.Coord }, - new(nameof(Rotation)) { Encoding = NetEncoding.VectorElements, Bits = 13, Flags = PropFlags.RoundDown | PropFlags.IsAVectorElem }, + new(nameof(Rotation)) { Encoding = NetEncoding.VectorElements, Bits = 13, Flags = PropFlags.RoundDown }, new(nameof(Velocity)) { Bits = 0, Flags = PropFlags.Coord }, new(nameof(ModelIndex)) { Bits = 14 }, new(nameof(Skin)) { Bits = 10 }, From 6608c01f510cc3d27f267ccd6b909e3b8fd1d5fb Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Sun, 1 Nov 2026 16:30:22 +0000 Subject: [PATCH 04/16] LuaRecipientFilter --- Game.Server/RecipientFilter.cs | 33 ++- Game.Shared/GarrysMod/LuaNet.cs | 12 +- Game.Shared/GarrysMod/LuaRecipientFilter.cs | 240 ++++++++++++++++++++ 3 files changed, 280 insertions(+), 5 deletions(-) create mode 100644 Game.Shared/GarrysMod/LuaRecipientFilter.cs diff --git a/Game.Server/RecipientFilter.cs b/Game.Server/RecipientFilter.cs index 39911ec16..a586e4b36 100644 --- a/Game.Server/RecipientFilter.cs +++ b/Game.Server/RecipientFilter.cs @@ -86,7 +86,26 @@ public void AddRecipientsByPVS(in Vector3 origin) { AddPlayersFromBitMask(ref playerbits); } } - public void RemoveRecipientsByPVS(in Vector3 origin) { } + public void RemovePlayersFromBitMask(ref PlayerBitSet playerbits) { + int index = playerbits.FindNextSetBit(0); + + while (index > -1) { + BasePlayer? player = Util.PlayerByIndex(index + 1); + if (player != null) + RemoveRecipient(player); + + index = playerbits.FindNextSetBit(index + 1); + } + } + public void RemoveRecipientsByPVS(in Vector3 origin) { + if (gpGlobals.MaxClients == 1) + Recipients.Clear(); + else { + PlayerBitSet playerbits = default; + engine.Message_DetermineMulticastRecipients(false, origin, ref playerbits); + RemovePlayersFromBitMask(ref playerbits); + } + } public void AddRecipientsByPAS(in Vector3 origin) { if (gpGlobals.MaxClients == 1) AddAllPlayers(); @@ -141,8 +160,16 @@ public void RemoveRecipientsNotOnTeam(Team team) { } public bool IgnorePredictionCull() => IgnoringPredictionCull; #if GMOD_DLL - public bool GMOD_HasRecipient(int slot) => throw new NotImplementedException(); - public bool GMOD_RemoveRecipientsByPAS(in Vector3 origin) => throw new NotImplementedException(); + public bool GMOD_HasRecipient(int playerIndex) => Recipients.Contains(playerIndex); + public void GMOD_RemoveRecipientsByPAS(in Vector3 origin) { + if (gpGlobals.MaxClients == 1) { + Recipients.Clear(); + return; + } + PlayerBitSet playerbits = default; + engine.Message_DetermineMulticastRecipients(true, origin, ref playerbits); + RemovePlayersFromBitMask(ref playerbits); + } #endif } diff --git a/Game.Shared/GarrysMod/LuaNet.cs b/Game.Shared/GarrysMod/LuaNet.cs index 9e4b5091a..3520a2cf1 100644 --- a/Game.Shared/GarrysMod/LuaNet.cs +++ b/Game.Shared/GarrysMod/LuaNet.cs @@ -322,7 +322,11 @@ static int Send(ILuaInterface lua) { filter.AddRecipient(ToBasePlayer(ent)!); } - // todo: else if (obj.GetType() == LuaType.RecipientFilter) for each recipient in Get_CRecipientFilter(1): filter.AddRecipient(UTIL_PlayerByIndex(...)) + else if (obj.GetType() == LuaType.RecipientFilter) { + RecipientFilter other = LuaRecipientFilter.Get_RecipientFilter(1); + for (int i = 0; i < other.GetRecipientCount(); i++) + filter.AddRecipient(Util.PlayerByIndex(other.GetRecipientIndex(i))!); + } else { g_Started = false; LuaEntity.Get_Entity(1, false); @@ -391,7 +395,11 @@ static int SendOmit(ILuaInterface lua) { filter.RemoveRecipient(ToBasePlayer(ent)!); } - // todo: else if (obj.GetType() == LuaType.RecipientFilter) for each recipient in Get_CRecipientFilter(1): filter.RemoveRecipient(UTIL_PlayerByIndex(...)) + else if (obj.GetType() == LuaType.RecipientFilter) { + RecipientFilter other = LuaRecipientFilter.Get_RecipientFilter(1); + for (int i = 0; i < other.GetRecipientCount(); i++) + filter.RemoveRecipient(Util.PlayerByIndex(other.GetRecipientIndex(i))!); + } else { g_Started = false; LuaEntity.Get_Entity(1, false); diff --git a/Game.Shared/GarrysMod/LuaRecipientFilter.cs b/Game.Shared/GarrysMod/LuaRecipientFilter.cs new file mode 100644 index 000000000..b348a842d --- /dev/null +++ b/Game.Shared/GarrysMod/LuaRecipientFilter.cs @@ -0,0 +1,240 @@ +#if GAME_DLL +using Source; +using Source.Common.GarrysMod.Lua; + +namespace Game.Server.GarrysMod; + +public static partial class LuaRecipientFilter +{ + [LuaClass(typeof(RecipientFilter), NullError = "Tried to use a NULL CRecipientFilter!")] + public static readonly LuaClass LC_RecipientFilter = new("CRecipientFilter", LuaType.RecipientFilter, null, null); + + public static void Push_RecipientFilter(RecipientFilter? filter) => LC_RecipientFilter.Push(filter); + + public static RecipientFilter Get_RecipientFilter(int stackPos) { + RecipientFilter? filter = LC_RecipientFilter.Get(stackPos) as RecipientFilter; + if (filter == null) + g_Lua!.Error("Tried to use a NULL CRecipientFilter!"); + return filter!; + } + + static void RemoveInvalidPlayers(RecipientFilter filter) { + for (int i = 1; i <= gpGlobals.MaxClients; i++) { + if (Util.PlayerByIndex(i) == null) + filter.RemoveRecipientByPlayerIndex(i); + } + } + + [LuaGlobal] + static int RecipientFilter(ILuaInterface lua) { + RecipientFilter filter = new(); + if (lua.GetType(1) == LuaType.Nil || !lua.GetBool(1)) + filter.MakeReliable(); + Push_RecipientFilter(filter); + return 1; + } + + [LuaMethod] + static int CRecipientFilter____tostring(ILuaInterface lua) { + RecipientFilter filter = Get_RecipientFilter(1); + RemoveInvalidPlayers(filter); + Span str = stackalloc char[128]; + sprintf(str, "CRecipientFilter [%i]").I(filter.GetRecipientCount()); + lua.PushString(((ReadOnlySpan)str).SliceNullTerminatedString()); + return 1; + } + + [LuaMethod] + static int CRecipientFilter__AddAllPlayers(ILuaInterface lua) { + Get_RecipientFilter(1).AddAllPlayers(); + return 0; + } + + [LuaMethod] + static int CRecipientFilter__RemoveAllPlayers(ILuaInterface lua) { + Get_RecipientFilter(1).RemoveAllRecipients(); + return 0; + } + + [LuaMethod] + static int CRecipientFilter__AddPAS(ILuaInterface lua) { + Get_RecipientFilter(1).AddRecipientsByPAS(LuaVector.Get_Vector(2)); + return 0; + } + + [LuaMethod] + static int CRecipientFilter__RemovePAS(ILuaInterface lua) { + Get_RecipientFilter(1).GMOD_RemoveRecipientsByPAS(LuaVector.Get_Vector(2)); + return 0; + } + + [LuaMethod] + static int CRecipientFilter__AddPVS(ILuaInterface lua) { + Get_RecipientFilter(1).AddRecipientsByPVS(LuaVector.Get_Vector(2)); + return 0; + } + + [LuaMethod] + static int CRecipientFilter__RemovePVS(ILuaInterface lua) { + Get_RecipientFilter(1).RemoveRecipientsByPVS(LuaVector.Get_Vector(2)); + return 0; + } + + [LuaMethod] + static int CRecipientFilter__AddPlayer(ILuaInterface lua) { + RecipientFilter filter = Get_RecipientFilter(1); + BaseEntity? ent = LuaEntity.Get_Entity(2, false); + filter.AddRecipient(ent != null && ent.IsPlayer() ? (BasePlayer)ent : null!); + return 0; + } + + [LuaMethod] + static int CRecipientFilter__RemovePlayer(ILuaInterface lua) { + RecipientFilter filter = Get_RecipientFilter(1); + BaseEntity? ent = LuaEntity.Get_Entity(2, false); + filter.RemoveRecipient(ent != null && ent.IsPlayer() ? (BasePlayer)ent : null!); + return 0; + } + + [LuaMethod] + static int CRecipientFilter__AddPlayers(ILuaInterface lua) { + RecipientFilter filter = Get_RecipientFilter(1); + if (lua.GetType(2) == LuaType.RecipientFilter) { + RecipientFilter? other = LC_RecipientFilter.Get(2) as RecipientFilter; + if (other == null) + return 0; + for (int i = 0; i < other.GetRecipientCount(); i++) + filter.AddRecipient(Util.PlayerByIndex(other.GetRecipientIndex(i))!); + return 0; + } + if (lua.GetType(2) == LuaType.Table) { + LuaObject table = new(2, LuaType.None); + for (int i = 1; ; i++) { + BaseEntity? ent = table.GetMemberEntity(i, null) as BaseEntity; + if (ent == null || !ent.IsPlayer()) + break; + filter.AddRecipient((BasePlayer)ent); + } + table.UnReference(); + } + return 0; + } + + [LuaMethod] + static int CRecipientFilter__RemovePlayers(ILuaInterface lua) { + RecipientFilter filter = Get_RecipientFilter(1); + if (lua.GetType(2) == LuaType.RecipientFilter) { + RecipientFilter? other = LC_RecipientFilter.Get(2) as RecipientFilter; + if (other == null) + return 0; + for (int i = 0; i < other.GetRecipientCount(); i++) + filter.RemoveRecipient(Util.PlayerByIndex(other.GetRecipientIndex(i))!); + return 0; + } + if (lua.GetType(2) == LuaType.Table) { + LuaObject table = new(2, LuaType.None); + for (int i = 1; ; i++) { + BaseEntity? ent = table.GetMemberEntity(i, null) as BaseEntity; + if (ent == null || !ent.IsPlayer()) + break; + filter.RemoveRecipient((BasePlayer)ent); + } + table.UnReference(); + } + return 0; + } + [LuaMethod] + static int CRecipientFilter__RemoveMismatchedPlayers(ILuaInterface lua) { + RecipientFilter filter = Get_RecipientFilter(1); + if (lua.GetType(2) == LuaType.RecipientFilter) { + RecipientFilter? other = LC_RecipientFilter.Get(2) as RecipientFilter; + if (other == null) + return 0; + for (int i = filter.GetRecipientCount() - 1; i >= 0; i--) { + int index = filter.GetRecipientIndex(i); + if (!other.GMOD_HasRecipient(index)) + filter.RemoveRecipientByPlayerIndex(index); + } + return 0; + } + if (lua.GetType(2) == LuaType.Table) { + List keep = []; + LuaObject table = new(2, LuaType.None); + for (int i = 1; ; i++) { + BaseEntity? ent = table.GetMemberEntity(i, null) as BaseEntity; + if (ent == null || !ent.IsPlayer()) + break; + keep.Add(ent.Edict() == null ? 0 : ent.EntIndex()); + } + for (int i = filter.GetRecipientCount() - 1; i >= 0; i--) { + int index = filter.GetRecipientIndex(i); + if (!keep.Contains(index)) + filter.RemoveRecipientByPlayerIndex(index); + } + table.UnReference(); + } + return 0; + } + + [LuaMethod] + static int CRecipientFilter__AddRecipientsByTeam(ILuaInterface lua) { + RecipientFilter filter = Get_RecipientFilter(1); + int team = (int)lua.CheckNumber(2); + for (int i = 1; i <= gpGlobals.MaxClients; i++) { + BasePlayer? player = Util.PlayerByIndex(i); + if (player != null && player.GetTeamNumber() == team) + filter.AddRecipient(player); + } + return 0; + } + + [LuaMethod] + static int CRecipientFilter__RemoveRecipientsByTeam(ILuaInterface lua) { + RecipientFilter filter = Get_RecipientFilter(1); + int team = (int)lua.CheckNumber(2); + for (int i = 1; i <= gpGlobals.MaxClients; i++) { + BasePlayer? player = Util.PlayerByIndex(i); + if (player != null && player.GetTeamNumber() == team) + filter.RemoveRecipient(player); + } + return 0; + } + + [LuaMethod] + static int CRecipientFilter__RemoveRecipientsNotOnTeam(ILuaInterface lua) { + RecipientFilter filter = Get_RecipientFilter(1); + int team = (int)lua.CheckNumber(2); + for (int i = 1; i <= gpGlobals.MaxClients; i++) { + BasePlayer? player = Util.PlayerByIndex(i); + if (player != null && player.GetTeamNumber() != team) + filter.RemoveRecipient(player); + } + return 0; + } + + [LuaMethod] + static int CRecipientFilter__GetCount(ILuaInterface lua) { + RecipientFilter filter = Get_RecipientFilter(1); + RemoveInvalidPlayers(filter); + lua.PushNumber(filter.GetRecipientCount()); + return 1; + } + + [LuaMethod] + static int CRecipientFilter__GetPlayers(ILuaInterface lua) { + RecipientFilter filter = Get_RecipientFilter(1); + RemoveInvalidPlayers(filter); + LuaTable table = new(null, (uint)filter.GetRecipientCount()); + for (int i = 0; i < filter.GetRecipientCount(); i++) { + int index = filter.GetRecipientIndex(i); + if (table.isTable()) { + LuaEntity.Push_Entity(index == 0 ? GetWorldEntity() : Util.EntityByIndex(index)); + table.SetMember(i + 1); + } + } + table.Push(); + table.UnReference(); + return 1; + } +} +#endif From 362cd7bbc26602f3591eca26fd7372debbb8f5bd Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Sun, 1 Nov 2026 16:30:38 +0000 Subject: [PATCH 05/16] LuaDebugOverlay --- Game.Shared/GarrysMod/LuaDebugOverlay.cs | 140 +++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 Game.Shared/GarrysMod/LuaDebugOverlay.cs diff --git a/Game.Shared/GarrysMod/LuaDebugOverlay.cs b/Game.Shared/GarrysMod/LuaDebugOverlay.cs new file mode 100644 index 000000000..36305e49b --- /dev/null +++ b/Game.Shared/GarrysMod/LuaDebugOverlay.cs @@ -0,0 +1,140 @@ +#if CLIENT_DLL || GAME_DLL +using Game.Shared; + +using Source; +using Source.Common.GarrysMod.Lua; + +#if CLIENT_DLL +namespace Game.Client.GarrysMod; +#else +namespace Game.Server.GarrysMod; +#endif + +public static partial class LuaDebugOverlay +{ + [LuaLibrary] + static readonly LuaLibrary LL_Factory_debugoverlay = new("debugoverlay"); + + [LuaFunction] + static int Box(ILuaInterface lua) { + if (developer.GetInt() == 0) + return 0; + double duration = lua.CheckNumberOpt(4, 1.0); + Color color = lua.GetColor(5); + Game.Shared.DebugOverlay.Box(LuaVector.Get_Vector(1), LuaVector.Get_Vector(2), LuaVector.Get_Vector(3), color.R, color.G, color.B, color.A, (float)duration); + return 0; + } + + [LuaFunction] + static int Line(ILuaInterface lua) { + if (developer.GetInt() == 0) + return 0; + double duration = lua.CheckNumberOpt(3, 1.0); + Color color = lua.GetColor(4); + bool ignoreZ = lua.GetType(5) != LuaType.Nil && lua.GetBool(5); + Game.Shared.DebugOverlay.Line(LuaVector.Get_Vector(1), LuaVector.Get_Vector(2), color.R, color.G, color.B, ignoreZ, (float)duration); + return 0; + } + + [LuaFunction] + static int Axis(ILuaInterface lua) { + if (developer.GetInt() == 0) + return 0; + double duration = lua.CheckNumberOpt(4, 1.0); + bool ignoreZ = lua.GetType(5) != LuaType.Nil && lua.GetBool(5); + Game.Shared.DebugOverlay.Axis(LuaVector.Get_Vector(1), LuaAngle.Get_Angle(2), (float)lua.CheckNumber(3), ignoreZ, (float)duration); + return 0; + } + + [LuaFunction] + static int Grid(ILuaInterface lua) { + if (developer.GetInt() == 0) + return 0; + Game.Shared.DebugOverlay.Grid(LuaVector.Get_Vector(1)); + return 0; + } + + [LuaFunction] + static int Text(ILuaInterface lua) { + if (developer.GetInt() == 0) + return 0; + double duration = lua.CheckNumberOpt(3, 1.0); + bool viewCheck = lua.GetType(4) != LuaType.Nil && lua.GetBool(4); + Game.Shared.DebugOverlay.Text(LuaVector.Get_Vector(1), lua.CheckString(2), viewCheck, (float)duration); + return 0; + } + + [LuaFunction] + static int Sphere(ILuaInterface lua) { + if (developer.GetInt() == 0) + return 0; + double duration = lua.CheckNumberOpt(3, 1.0); + Color color = lua.GetColor(4); + bool ignoreZ = lua.GetType(5) != LuaType.Nil && lua.GetBool(5); + Game.Shared.DebugOverlay.Sphere(LuaVector.Get_Vector(1), vec3_angle, (float)lua.CheckNumber(2), color.R, color.G, color.B, color.A, ignoreZ, (float)duration); + return 0; + } + + [LuaFunction] + static int Cross(ILuaInterface lua) { + if (developer.GetInt() == 0) + return 0; + double duration = lua.CheckNumberOpt(3, 1.0); + Color color = lua.GetColor(4); + bool ignoreZ = lua.GetType(5) != LuaType.Nil && lua.GetBool(5); + Game.Shared.DebugOverlay.Cross3D(LuaVector.Get_Vector(1), (float)lua.CheckNumber(2), color.R, color.G, color.B, ignoreZ, (float)duration); + return 0; + } + + [LuaFunction] + static int Triangle(ILuaInterface lua) { + if (developer.GetInt() == 0) + return 0; + double duration = lua.CheckNumberOpt(4, 1.0); + Color color = lua.GetColor(5); + bool ignoreZ = lua.GetType(6) != LuaType.Nil && lua.GetBool(6); + Game.Shared.DebugOverlay.Triangle(LuaVector.Get_Vector(1), LuaVector.Get_Vector(2), LuaVector.Get_Vector(3), color.R, color.G, color.B, color.A, ignoreZ, (float)duration); + return 0; + } + + [LuaFunction] + static int BoxAngles(ILuaInterface lua) { + if (developer.GetInt() == 0) + return 0; + double duration = lua.CheckNumberOpt(5, 1.0); + Color color = lua.GetColor(6); + Game.Shared.DebugOverlay.BoxAngles(LuaVector.Get_Vector(1), LuaVector.Get_Vector(2), LuaVector.Get_Vector(3), LuaAngle.Get_Angle(4), color.R, color.G, color.B, color.A, (float)duration); + return 0; + } + + [LuaFunction] + static int SweptBox(ILuaInterface lua) { + if (developer.GetInt() == 0) + return 0; + double duration = lua.CheckNumberOpt(6, 1.0); + Color color = lua.GetColor(7); + Game.Shared.DebugOverlay.SweptBox(LuaVector.Get_Vector(1), LuaVector.Get_Vector(2), LuaVector.Get_Vector(3), LuaVector.Get_Vector(4), LuaAngle.Get_Angle(5), color.R, color.G, color.B, color.A, (float)duration); + return 0; + } + + [LuaFunction] + static int EntityTextAtPosition(ILuaInterface lua) { + if (developer.GetInt() == 0) + return 0; + double duration = lua.CheckNumberOpt(4, 1.0); + Color color = lua.GetColor(5); + Game.Shared.DebugOverlay.EntityTextAtPosition(LuaVector.Get_Vector(1), (int)lua.CheckNumber(2), lua.CheckString(3), (float)duration, color.R, color.G, color.B, color.A); + return 0; + } + + [LuaFunction] + static int ScreenText(ILuaInterface lua) { + if (developer.GetInt() == 0) + return 0; + double duration = lua.CheckNumberOpt(4, 1.0); + Color color = lua.GetColor(5); + Game.Shared.DebugOverlay.ScreenText((float)lua.CheckNumber(1), (float)lua.CheckNumber(2), lua.CheckString(3), color.R, color.G, color.B, color.A, (float)duration); + return 0; + } +} +#endif From b0526a0d60a03ceffe6c4b622cb14a7d20b507c8 Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Sun, 1 Nov 2026 16:30:44 +0000 Subject: [PATCH 06/16] LuaEffectData --- Game.Shared/GarrysMod/LuaEffectData.cs | 282 +++++++++++++++++++++++++ 1 file changed, 282 insertions(+) create mode 100644 Game.Shared/GarrysMod/LuaEffectData.cs diff --git a/Game.Shared/GarrysMod/LuaEffectData.cs b/Game.Shared/GarrysMod/LuaEffectData.cs new file mode 100644 index 000000000..43154383c --- /dev/null +++ b/Game.Shared/GarrysMod/LuaEffectData.cs @@ -0,0 +1,282 @@ +#if CLIENT_DLL || GAME_DLL +using Game.Shared; + +using Source.Common.GarrysMod.Lua; + +using System.Numerics; + +#if CLIENT_DLL +namespace Game.Client.GarrysMod; +#else +namespace Game.Server.GarrysMod; +#endif + +public static partial class LuaEffectData +{ + [LuaClass(typeof(EffectData), NullError = "Tried to use a NULL CEffectData!")] + public static readonly LuaClass LC_EffectData = new("CEffectData", LuaType.EffectData, null, null); + + public static void Push_EffectData(EffectData? data) => LC_EffectData.Push(data); + + public static EffectData Get_EffectData(int stackPos) { + EffectData? data = LC_EffectData.Get(stackPos) as EffectData; + if (data == null) + g_Lua!.Error("Tried to use a NULL CEffectData!"); + return data!; + } + + [LuaGlobal] + static int EffectData(ILuaInterface lua) { + Push_EffectData(new EffectData()); + return 1; + } + + [LuaMethod] + static int CEffectData____tostring(ILuaInterface lua) { + lua.PushString("CEffectData"); + return 1; + } + + [LuaMethod] + static int CEffectData__IsValid(ILuaInterface lua) { + lua.PushBool(LC_EffectData.Get(1) is EffectData); + return 1; + } + + [LuaMethod] + static int CEffectData__GetOrigin(ILuaInterface lua) { + LuaVector.Push_Vector(Get_EffectData(1).Origin); + return 1; + } + + [LuaMethod] + static int CEffectData__SetOrigin(ILuaInterface lua) { + EffectData data = Get_EffectData(1); + data.Origin = LuaVector.Get_Vector(2); + return 0; + } + + [LuaMethod] + static int CEffectData__GetStart(ILuaInterface lua) { + LuaVector.Push_Vector(Get_EffectData(1).Start); + return 1; + } + + [LuaMethod] + static int CEffectData__SetStart(ILuaInterface lua) { + EffectData data = Get_EffectData(1); + data.Start = LuaVector.Get_Vector(2); + return 0; + } + + [LuaMethod] + static int CEffectData__GetNormal(ILuaInterface lua) { + LuaVector.Push_Vector(Get_EffectData(1).Normal); + return 1; + } + + [LuaMethod] + static int CEffectData__SetNormal(ILuaInterface lua) { + EffectData data = Get_EffectData(1); + data.Normal = LuaVector.Get_Vector(2); + return 0; + } + + [LuaMethod] + static int CEffectData__GetAngles(ILuaInterface lua) { + LuaAngle.Push_Angle(Get_EffectData(1).Angles); + return 1; + } + + [LuaMethod] + static int CEffectData__SetAngles(ILuaInterface lua) { + EffectData data = Get_EffectData(1); + data.Angles = LuaAngle.Get_Angle(2); + return 0; + } + + [LuaMethod] + static int CEffectData__GetFlags(ILuaInterface lua) { + lua.PushNumber(Get_EffectData(1).Flags); + return 1; + } + + [LuaMethod] + static int CEffectData__SetFlags(ILuaInterface lua) { + EffectData data = Get_EffectData(1); + data.Flags = (int)lua.CheckNumber(2); + return 0; + } + + [LuaMethod] + static int CEffectData__GetScale(ILuaInterface lua) { + lua.PushNumber(Get_EffectData(1).Scale); + return 1; + } + + [LuaMethod] + static int CEffectData__SetScale(ILuaInterface lua) { + EffectData data = Get_EffectData(1); + data.Scale = (float)lua.CheckNumber(2); + return 0; + } + + [LuaMethod] + static int CEffectData__GetMagnitude(ILuaInterface lua) { + lua.PushNumber(Get_EffectData(1).Magnitude); + return 1; + } + + [LuaMethod] + static int CEffectData__SetMagnitude(ILuaInterface lua) { + EffectData data = Get_EffectData(1); + data.Magnitude = (float)lua.CheckNumber(2); + return 0; + } + + [LuaMethod] + static int CEffectData__GetRadius(ILuaInterface lua) { + lua.PushNumber(Get_EffectData(1).Radius); + return 1; + } + + [LuaMethod] + static int CEffectData__SetRadius(ILuaInterface lua) { + EffectData data = Get_EffectData(1); + data.Radius = (float)lua.CheckNumber(2); + return 0; + } + + [LuaMethod] + static int CEffectData__GetAttachment(ILuaInterface lua) { + lua.PushNumber(Get_EffectData(1).AttachmentIndex); + return 1; + } + + [LuaMethod] + static int CEffectData__SetAttachment(ILuaInterface lua) { + EffectData data = Get_EffectData(1); + data.AttachmentIndex = (int)lua.CheckNumber(2); + return 0; + } + + [LuaMethod] + static int CEffectData__GetSurfaceProp(ILuaInterface lua) { + lua.PushNumber(Get_EffectData(1).SurfaceProp); + return 1; + } + + [LuaMethod] + static int CEffectData__SetSurfaceProp(ILuaInterface lua) { + EffectData data = Get_EffectData(1); + data.SurfaceProp = (short)(int)lua.CheckNumber(2); + return 0; + } + + [LuaMethod] + static int CEffectData__GetMaterialIndex(ILuaInterface lua) { + lua.PushNumber(Get_EffectData(1).Material); + return 1; + } + + [LuaMethod] + static int CEffectData__SetMaterialIndex(ILuaInterface lua) { + EffectData data = Get_EffectData(1); + data.Material = (int)lua.CheckNumber(2); + return 0; + } + + [LuaMethod] + static int CEffectData__GetDamageType(ILuaInterface lua) { + lua.PushNumber(Get_EffectData(1).DamageType); + return 1; + } + + [LuaMethod] + static int CEffectData__SetDamageType(ILuaInterface lua) { + EffectData data = Get_EffectData(1); + data.DamageType = (int)lua.CheckNumber(2); + return 0; + } + + [LuaMethod] + static int CEffectData__GetHitBox(ILuaInterface lua) { + lua.PushNumber(Get_EffectData(1).HitBox); + return 1; + } + + [LuaMethod] + static int CEffectData__SetHitBox(ILuaInterface lua) { + EffectData data = Get_EffectData(1); + data.HitBox = (int)lua.CheckNumber(2); + return 0; + } + + [LuaMethod] + static int CEffectData__GetColor(ILuaInterface lua) { + lua.PushNumber(Get_EffectData(1).Color); + return 1; + } + + [LuaMethod] + static int CEffectData__SetColor(ILuaInterface lua) { + EffectData data = Get_EffectData(1); + data.Color = (byte)(int)lua.CheckNumber(2); + return 0; + } + +#if CLIENT_DLL + [LuaMethod] + static int CEffectData__GetEntity(ILuaInterface lua) { + LuaEntity.Push_Entity(cl_entitylist.GetBaseEntityFromHandle(Get_EffectData(1).Entity)); + return 1; + } + + [LuaMethod] + static int CEffectData__SetEntity(ILuaInterface lua) { + EffectData data = Get_EffectData(1); + BaseEntity? ent = LuaEntity.Get_Entity(2, true); + if (ent == null) + data.Entity.Invalidate(); + else + data.Entity = ent.GetRefEHandle(); + return 0; + } +#else + [LuaMethod] + static int CEffectData__GetEntIndex(ILuaInterface lua) { + lua.PushNumber(Get_EffectData(1).EntIndex); + return 1; + } + + [LuaMethod] + static int CEffectData__SetEntIndex(ILuaInterface lua) { + EffectData data = Get_EffectData(1); + data.EntIndex = (int)lua.CheckNumber(2); + return 0; + } + + [LuaMethod] + static int CEffectData__GetEntity(ILuaInterface lua) { + EffectData data = Get_EffectData(1); + if (data.EntIndex == 0) { + LuaEntity.Push_Entity(null); + return 1; + } + LuaEntity.Push_Entity(Util.EntityByIndex(data.EntIndex)); + return 1; + } + + [LuaMethod] + static int CEffectData__SetEntity(ILuaInterface lua) { + EffectData data = Get_EffectData(1); + BaseEntity? ent = LuaEntity.Get_Entity(2, true); + if (ent == null || ent.Edict() == null) + data.EntIndex = 0; + else + data.EntIndex = ent.EntIndex(); + return 0; + } +#endif +} +#endif From e4912cca2467c626e999a79b69d7bfb73b34b73e Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Sun, 1 Nov 2026 16:30:49 +0000 Subject: [PATCH 07/16] LuaMoveData --- Game.Shared/GarrysMod/LuaMoveData.cs | 360 +++++++++++++++++++++++++++ 1 file changed, 360 insertions(+) create mode 100644 Game.Shared/GarrysMod/LuaMoveData.cs diff --git a/Game.Shared/GarrysMod/LuaMoveData.cs b/Game.Shared/GarrysMod/LuaMoveData.cs new file mode 100644 index 000000000..ee82b996b --- /dev/null +++ b/Game.Shared/GarrysMod/LuaMoveData.cs @@ -0,0 +1,360 @@ +#if CLIENT_DLL || GAME_DLL +using Game.Shared; + +using Source; +using Source.Common.GarrysMod.Lua; + +#if CLIENT_DLL +namespace Game.Client.GarrysMod; +#else +namespace Game.Server.GarrysMod; +#endif + +public static partial class LuaMoveData +{ + [LuaClass(typeof(MoveData), NullError = "Tried to use a NULL CMoveData!")] + public static readonly LuaClass LC_MoveData = new("CMoveData", LuaType.MoveData, null, null); + + public static void Push_MoveData(MoveData? data) => LC_MoveData.Push(data); + + public static MoveData Get_MoveData(int stackPos) { + MoveData? data = LC_MoveData.Get(stackPos) as MoveData; + if (data == null) + g_Lua!.Error("Tried to use a NULL CMoveData!"); + return data!; + } + + [LuaMethod] + static int CMoveData____tostring(ILuaInterface lua) { + MoveData data = Get_MoveData(1); + Span str = stackalloc char[128]; + sprintf(str, "CMoveData [%i]").I(data.PlayerHandle.GetEntryIndex()); + lua.PushString(((ReadOnlySpan)str).SliceNullTerminatedString()); + return 1; + } + + [LuaMethod] + static int CMoveData__IsValid(ILuaInterface lua) { + lua.PushBool(LC_MoveData.Get(1) is MoveData); + return 1; + } + + [LuaMethod] + static int CMoveData__GetImpulseCommand(ILuaInterface lua) { + lua.PushNumber(Get_MoveData(1).ImpulseCommand); + return 1; + } + + [LuaMethod] + static int CMoveData__SetImpulseCommand(ILuaInterface lua) { + MoveData data = Get_MoveData(1); + data.ImpulseCommand = (int)lua.CheckNumber(2); + return 0; + } + + [LuaMethod] + static int CMoveData__GetMoveAngles(ILuaInterface lua) { + LuaAngle.Push_Angle(Get_MoveData(1).ViewAngles); + return 1; + } + + [LuaMethod] + static int CMoveData__SetMoveAngles(ILuaInterface lua) { + MoveData data = Get_MoveData(1); + data.ViewAngles = LuaAngle.Get_Angle(2); + return 0; + } + + [LuaMethod] + static int CMoveData__GetAbsMoveAngles(ILuaInterface lua) { + LuaAngle.Push_Angle(Get_MoveData(1).AbsViewAngles); + return 1; + } + + [LuaMethod] + static int CMoveData__SetAbsMoveAngles(ILuaInterface lua) { + MoveData data = Get_MoveData(1); + data.AbsViewAngles = LuaAngle.Get_Angle(2); + return 0; + } + + [LuaMethod] + static int CMoveData__GetButtons(ILuaInterface lua) { + lua.PushNumber((int)Get_MoveData(1).Buttons); + return 1; + } + + [LuaMethod] + static int CMoveData__SetButtons(ILuaInterface lua) { + MoveData data = Get_MoveData(1); + data.Buttons = (InButtons)(int)lua.CheckNumber(2); + return 0; + } + + [LuaMethod] + static int CMoveData__AddKey(ILuaInterface lua) { + MoveData data = Get_MoveData(1); + data.Buttons |= (InButtons)(int)lua.CheckNumber(2); + return 0; + } + + [LuaMethod] + static int CMoveData__RemoveKey(ILuaInterface lua) { + MoveData data = Get_MoveData(1); + data.Buttons &= ~(InButtons)(int)lua.CheckNumber(2); + return 0; + } + + [LuaMethod] + static int CMoveData__KeyDown(ILuaInterface lua) { + MoveData data = Get_MoveData(1); + int key = (int)lua.CheckNumber(2); + lua.PushBool(((int)data.Buttons & key) != 0); + return 1; + } + + [LuaMethod] + static int CMoveData__KeyWasDown(ILuaInterface lua) { + MoveData data = Get_MoveData(1); + int key = (int)lua.CheckNumber(2); + lua.PushBool(((int)data.OldButtons & key) != 0); + return 1; + } + + [LuaMethod] + static int CMoveData__KeyPressed(ILuaInterface lua) { + MoveData data = Get_MoveData(1); + int key = (int)lua.CheckNumber(2); + lua.PushBool(((int)data.Buttons & key) != 0 && ((int)data.OldButtons & key) == 0); + return 1; + } + + [LuaMethod] + static int CMoveData__KeyReleased(ILuaInterface lua) { + MoveData data = Get_MoveData(1); + int key = (int)lua.CheckNumber(2); + lua.PushBool(((int)data.Buttons & key) == 0 && ((int)data.OldButtons & key) != 0); + return 1; + } + + [LuaMethod] + static int CMoveData__GetOldButtons(ILuaInterface lua) { + lua.PushNumber((int)Get_MoveData(1).OldButtons); + return 1; + } + + [LuaMethod] + static int CMoveData__SetOldButtons(ILuaInterface lua) { + MoveData data = Get_MoveData(1); + data.OldButtons = (InButtons)(int)lua.CheckNumber(2); + return 0; + } + + [LuaMethod] + static int CMoveData__GetForwardSpeed(ILuaInterface lua) { + lua.PushNumber(Get_MoveData(1).ForwardMove); + return 1; + } + + [LuaMethod] + static int CMoveData__SetForwardSpeed(ILuaInterface lua) { + MoveData data = Get_MoveData(1); + data.ForwardMove = (float)lua.CheckNumber(2); + return 0; + } + + [LuaMethod] + static int CMoveData__GetSideSpeed(ILuaInterface lua) { + lua.PushNumber(Get_MoveData(1).SideMove); + return 1; + } + + [LuaMethod] + static int CMoveData__SetSideSpeed(ILuaInterface lua) { + MoveData data = Get_MoveData(1); + data.SideMove = (float)lua.CheckNumber(2); + return 0; + } + + [LuaMethod] + static int CMoveData__GetUpSpeed(ILuaInterface lua) { + lua.PushNumber(Get_MoveData(1).UpMove); + return 1; + } + + [LuaMethod] + static int CMoveData__SetUpSpeed(ILuaInterface lua) { + MoveData data = Get_MoveData(1); + data.UpMove = (float)lua.CheckNumber(2); + return 0; + } + + [LuaMethod] + static int CMoveData__GetMaxSpeed(ILuaInterface lua) { + lua.PushNumber(Get_MoveData(1).MaxSpeed); + return 1; + } + + [LuaMethod] + static int CMoveData__SetMaxSpeed(ILuaInterface lua) { + MoveData data = Get_MoveData(1); + data.MaxSpeed = (float)lua.CheckNumber(2); + return 0; + } + + [LuaMethod] + static int CMoveData__GetMaxClientSpeed(ILuaInterface lua) { + lua.PushNumber(Get_MoveData(1).ClientMaxSpeed); + return 1; + } + + [LuaMethod] + static int CMoveData__SetMaxClientSpeed(ILuaInterface lua) { + MoveData data = Get_MoveData(1); + data.ClientMaxSpeed = (float)lua.CheckNumber(2); + return 0; + } + + [LuaMethod] + static int CMoveData__GetVelocity(ILuaInterface lua) { + LuaVector.Push_Vector(Get_MoveData(1).Velocity); + return 1; + } + + [LuaMethod] + static int CMoveData__SetVelocity(ILuaInterface lua) { + MoveData data = Get_MoveData(1); + data.Velocity = LuaVector.Get_Vector(2); + return 0; + } + + [LuaMethod] + static int CMoveData__GetAngles(ILuaInterface lua) { + LuaAngle.Push_Angle(Get_MoveData(1).Angles); + return 1; + } + + [LuaMethod] + static int CMoveData__SetAngles(ILuaInterface lua) { + MoveData data = Get_MoveData(1); + data.Angles = LuaAngle.Get_Angle(2); + return 0; + } + + [LuaMethod] + static int CMoveData__GetOldAngles(ILuaInterface lua) { + LuaAngle.Push_Angle(Get_MoveData(1).OldAngles); + return 1; + } + + [LuaMethod] + static int CMoveData__SetOldAngles(ILuaInterface lua) { + MoveData data = Get_MoveData(1); + data.OldAngles = LuaAngle.Get_Angle(2); + return 0; + } + + [LuaMethod] + static int CMoveData__GetFinalStepHeight(ILuaInterface lua) { + lua.PushNumber(Get_MoveData(1).StepHeight); + return 1; + } + + [LuaMethod] + static int CMoveData__SetFinalStepHeight(ILuaInterface lua) { + MoveData data = Get_MoveData(1); + data.StepHeight = (float)lua.CheckNumber(2); + return 0; + } + + [LuaMethod] + static int CMoveData__GetFinalIdealVelocity(ILuaInterface lua) { + LuaVector.Push_Vector(Get_MoveData(1).WishVel); + return 1; + } + + [LuaMethod] + static int CMoveData__SetFinalIdealVelocity(ILuaInterface lua) { + MoveData data = Get_MoveData(1); + data.WishVel = LuaVector.Get_Vector(2); + return 0; + } + + [LuaMethod] + static int CMoveData__GetFinalJumpVelocity(ILuaInterface lua) { + LuaVector.Push_Vector(Get_MoveData(1).JumpVel); + return 1; + } + + [LuaMethod] + static int CMoveData__SetFinalJumpVelocity(ILuaInterface lua) { + MoveData data = Get_MoveData(1); + data.JumpVel = LuaVector.Get_Vector(2); + return 0; + } + + [LuaMethod] + static int CMoveData__GetConstraintCenter(ILuaInterface lua) { + LuaVector.Push_Vector(Get_MoveData(1).ConstraintCenter); + return 1; + } + + [LuaMethod] + static int CMoveData__SetConstraintCenter(ILuaInterface lua) { + MoveData data = Get_MoveData(1); + data.ConstraintCenter = LuaVector.Get_Vector(2); + return 0; + } + + [LuaMethod] + static int CMoveData__GetConstraintRadius(ILuaInterface lua) { + lua.PushNumber(Get_MoveData(1).ConstraintRadius); + return 1; + } + + [LuaMethod] + static int CMoveData__SetConstraintRadius(ILuaInterface lua) { + MoveData data = Get_MoveData(1); + data.ConstraintRadius = (float)lua.CheckNumber(2); + return 0; + } + + [LuaMethod] + static int CMoveData__GetConstraintWidth(ILuaInterface lua) { + lua.PushNumber(Get_MoveData(1).ConstraintWidth); + return 1; + } + + [LuaMethod] + static int CMoveData__SetConstraintWidth(ILuaInterface lua) { + MoveData data = Get_MoveData(1); + data.ConstraintWidth = (float)lua.CheckNumber(2); + return 0; + } + + [LuaMethod] + static int CMoveData__GetConstraintSpeedScale(ILuaInterface lua) { + lua.PushNumber(Get_MoveData(1).ConstraintSpeedFactor); + return 1; + } + + [LuaMethod] + static int CMoveData__SetConstraintSpeedScale(ILuaInterface lua) { + MoveData data = Get_MoveData(1); + data.ConstraintSpeedFactor = (float)lua.CheckNumber(2); + return 0; + } + + [LuaMethod] + static int CMoveData__GetOrigin(ILuaInterface lua) { + LuaVector.Push_Vector(Get_MoveData(1).GetAbsOrigin()); + return 1; + } + + [LuaMethod] + static int CMoveData__SetOrigin(ILuaInterface lua) { + Get_MoveData(1).SetAbsOrigin(LuaVector.Get_Vector(2)); + return 0; + } +} +#endif From 6a2b4af2fd30ca6013e7b41be1e37ada76a7425c Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Sun, 1 Nov 2026 16:31:01 +0000 Subject: [PATCH 08/16] LuaPhysEnv --- Game.Client/Physics.cs | 25 ++++++ Game.Server/Physics.cs | 21 +++++ Game.Shared/GarrysMod/LuaPhysEnv.cs | 124 ++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+) create mode 100644 Game.Shared/GarrysMod/LuaPhysEnv.cs diff --git a/Game.Client/Physics.cs b/Game.Client/Physics.cs index 72cd42358..baf86e9bf 100644 --- a/Game.Client/Physics.cs +++ b/Game.Client/Physics.cs @@ -42,12 +42,34 @@ public override void LevelInitPostEntity() { static readonly ConVar cl_phys_timescale = new("cl_phys_timescale", "1.0", FCvar.Cheat, "Sets the scale of time for client-side physics (ragdolls)"); +#if GMOD_DLL + public static float g_GMOD_PhysTimeScale = 1.0f; + public static bool g_GMOD_PhysicsPaused; + public static TimeUnit_t g_GMOD_LastSimulationTime; + + public static void GMOD_PhysicsSetTimeScale(float scale) { + if (scale <= 0.001f) + scale = 0.001f; + if (5.0f <= scale) + scale = 5.0f; + g_GMOD_PhysTimeScale = scale; + } + + public static float GMOD_PhysicsGetTotalTimeScale() => cl_phys_timescale.GetFloat() * g_GMOD_PhysTimeScale; +#endif internal void PhysicsSimulate() { TimeUnit_t frametime = gpGlobals.FrameTime; if (physenv != null) { +#if GMOD_DLL + TimeUnit_t startTime = engine.Time(); +#endif physenv.DebugCheckContacts(); +#if GMOD_DLL + physenv.Simulate(GMOD_PhysicsGetTotalTimeScale() * frametime); +#else physenv.Simulate(frametime * cl_phys_timescale.GetDouble()); +#endif int activeCount = physenv.GetActiveObjectCount(); IPhysicsObject?[]? activeList = null; @@ -67,6 +89,9 @@ internal void PhysicsSimulate() { ArrayPool.Shared.Return(activeList, true); } +#if GMOD_DLL + g_GMOD_LastSimulationTime = engine.Time() - startTime; +#endif // g_Collisions.BufferTouchEvents(false); // g_Collisions.FrameUpdate(); diff --git a/Game.Server/Physics.cs b/Game.Server/Physics.cs index 71e0a5bb3..062703eab 100644 --- a/Game.Server/Physics.cs +++ b/Game.Server/Physics.cs @@ -36,6 +36,17 @@ public static class PhysicsHookGlobals { public static readonly ConVar phys_timescale = new("phys_timescale", "1", FCvar.Replicated, "Scale time for physics", callback: TimescaleChanged); public static float g_GMOD_PhysTimeScale = 1.0f; + public static bool g_GMOD_PhysicsPaused; + public static TimeUnit_t g_GMOD_LastSimulationTime; + + public static void GMOD_PhysicsSetTimeScale(float scale) { + if (scale <= 0.001f) + scale = 0.001f; + if (5.0f <= scale) + scale = 5.0f; + g_GMOD_PhysTimeScale = scale; + physenv?.ResetSimulationClock(); + } static void TimescaleChanged(IConVar var, in ConVarChangeContext ctx) => physenv?.ResetSimulationClock(); @@ -419,11 +430,18 @@ public override void LevelInitPostEntity() { Paused = false; } +#if GMOD_DLL + public bool ShouldSimulate() => !g_GMOD_PhysicsPaused && physenv != null && !Paused; +#else public bool ShouldSimulate() => physenv != null && !Paused; +#endif void PhysFrame(TimeUnit_t deltaTime) { if (!ShouldSimulate()) return; +#if GMOD_DLL + TimeUnit_t startTime = engine.Time(); +#endif // Trap interrupts and clock changes if (deltaTime > 1.0 || deltaTime < 0.0) @@ -462,6 +480,9 @@ void PhysFrame(TimeUnit_t deltaTime) { } g_Collisions.BufferTouchEvents(false); +#if GMOD_DLL + g_GMOD_LastSimulationTime = engine.Time() - startTime; +#endif g_Collisions.FrameUpdate(); } public override void LevelShutdownPreEntity() { diff --git a/Game.Shared/GarrysMod/LuaPhysEnv.cs b/Game.Shared/GarrysMod/LuaPhysEnv.cs new file mode 100644 index 000000000..a273e2e45 --- /dev/null +++ b/Game.Shared/GarrysMod/LuaPhysEnv.cs @@ -0,0 +1,124 @@ +#if CLIENT_DLL || GAME_DLL +using Source.Common.GarrysMod.Lua; +using Source.Common.Physics; + +using System.Numerics; + +#if CLIENT_DLL +using static Game.Client.PhysicsSystem; + +namespace Game.Client.GarrysMod; +#else +namespace Game.Server.GarrysMod; +#endif + +public static partial class LuaPhysEnv +{ + [LuaLibrary] + static readonly LuaLibrary LL_Factory_physenv = new("physenv"); + + [LuaFunction] + static int GetAirDensity(ILuaInterface lua) { + if (physenv == null) + return 0; + lua.PushNumber(physenv.GetAirDensity()); + return 1; + } + + [LuaFunction] + static int SetAirDensity(ILuaInterface lua) { + if (physenv != null) + physenv.SetAirDensity((float)lua.CheckNumber(1)); + return 0; + } + + [LuaFunction] + static int GetGravity(ILuaInterface lua) { + if (physenv == null) + return 0; + physenv.GetGravity(out Vector3 gravity); + LuaVector.Push_Vector(gravity); + return 1; + } + + [LuaFunction] + static int SetGravity(ILuaInterface lua) { + if (physenv != null) + physenv.SetGravity(LuaVector.Get_Vector(1)); + return 0; + } + // todo: AddSurfaceData + [LuaFunction] + static int GetPerformanceSettings(ILuaInterface lua) { + if (physenv == null) + return 0; + physenv.GetPerformanceSettings(out PhysicsPerformanceParams settings); + LuaTable table = new(null, 0); + table.SetMember("MaxCollisionsPerObjectPerTimestep", settings.MaxCollisionsPerObjectPerTimestep); + table.SetMember("MaxCollisionChecksPerTimestep", settings.MaxCollisionChecksPerTimestep); + table.SetMember("MaxVelocity", settings.MaxVelocity); + table.SetMember("MaxAngularVelocity", settings.MaxAngularVelocity); + table.SetMember("LookAheadTimeObjectsVsWorld", settings.LookAheadTimeObjectsVsWorld); + table.SetMember("LookAheadTimeObjectsVsObject", settings.LookAheadTimeObjectsVsObject); + table.SetMember("MinFrictionMass", settings.MinFrictionMass); + table.SetMember("MaxFrictionMass", settings.MaxFrictionMass); + table.Push(); + table.UnReference(); + return 1; + } + + [LuaFunction] + static int SetPerformanceSettings(ILuaInterface lua) { + if (physenv == null) + return 0; + physenv.GetPerformanceSettings(out PhysicsPerformanceParams settings); + LuaTable table = new(null, 0); + table.SetFromStack(1); + if (!table.isTable()) { + lua.TypeError("table", 1); + table.UnReference(); + return 0; + } + settings.MaxCollisionsPerObjectPerTimestep = table.GetMemberInt("MaxCollisionsPerObjectPerTimestep", settings.MaxCollisionsPerObjectPerTimestep); + settings.MaxCollisionChecksPerTimestep = table.GetMemberInt("MaxCollisionChecksPerTimestep", settings.MaxCollisionChecksPerTimestep); + settings.MaxVelocity = table.GetMemberFloat("MaxVelocity", settings.MaxVelocity); + settings.MaxAngularVelocity = table.GetMemberFloat("MaxAngularVelocity", settings.MaxAngularVelocity); + settings.LookAheadTimeObjectsVsWorld = table.GetMemberFloat("LookAheadTimeObjectsVsWorld", settings.LookAheadTimeObjectsVsWorld); + settings.LookAheadTimeObjectsVsObject = table.GetMemberFloat("LookAheadTimeObjectsVsObject", settings.LookAheadTimeObjectsVsObject); + settings.MinFrictionMass = table.GetMemberFloat("MinFrictionMass", settings.MinFrictionMass); + settings.MaxFrictionMass = table.GetMemberFloat("MaxFrictionMass", settings.MaxFrictionMass); + physenv.SetPerformanceSettings(settings); + table.UnReference(); + return 0; + } + [LuaFunction] + static int GetLastSimulationTime(ILuaInterface lua) { + lua.PushNumber(g_GMOD_LastSimulationTime); + return 1; + } + + [LuaFunction] + static int GetPhysicsPaused(ILuaInterface lua) { + lua.PushBool(g_GMOD_PhysicsPaused); + return 1; + } + + [LuaFunction] + static int SetPhysicsPaused(ILuaInterface lua) { + g_GMOD_PhysicsPaused = lua.GetBool(1); + return 0; + } + + [LuaFunction] + static int GetTimeScale(ILuaInterface lua) { + lua.PushNumber(g_GMOD_PhysTimeScale); + return 1; + } + + [LuaFunction] + static int SetTimeScale(ILuaInterface lua) { + GMOD_PhysicsSetTimeScale((float)lua.CheckNumber(1)); + return 0; + } +} +#endif From 72db9de42cc439c647380a3c40cd5a3576987ed2 Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Sun, 1 Nov 2026 16:31:48 +0000 Subject: [PATCH 09/16] Light is server only --- Game.Server/BaseEntity.cs | 17 +++++++++++++++++ Game.Server/Lights.cs | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Game.Server/BaseEntity.cs b/Game.Server/BaseEntity.cs index 77b3a71b6..68bbe12b9 100644 --- a/Game.Server/BaseEntity.cs +++ b/Game.Server/BaseEntity.cs @@ -3977,15 +3977,32 @@ public override void Spawn() { public class ServerOnlyEntity : BaseEntity { + public ServerOnlyEntity() : base(true) { } + public override EntityCapabilities ObjectCaps() => base.ObjectCaps() & ~EntityCapabilities.AcrossTransition; } public class ServerOnlyPointEntity : ServerOnlyEntity { + public override bool KeyValue(ReadOnlySpan keyName, ReadOnlySpan value) { + if (FStrEq(keyName, "mins") || FStrEq(keyName, "maxs")) { + Warning($"Warning! Can't specify mins/maxs for point entities! ({GetClassname()})\n"); + return true; + } + return base.KeyValue(keyName, value); + } } public class LogicalEntity : ServerOnlyEntity { + public override bool KeyValue(ReadOnlySpan keyName, ReadOnlySpan value) { + if (FStrEq(keyName, "mins") || FStrEq(keyName, "maxs")) { + Warning($"Warning! Can't specify mins/maxs for point entities! ({GetClassname()})\n"); + return true; + } + + return base.KeyValue(keyName, value); + } } diff --git a/Game.Server/Lights.cs b/Game.Server/Lights.cs index 089df82b5..e5d00fba5 100644 --- a/Game.Server/Lights.cs +++ b/Game.Server/Lights.cs @@ -6,7 +6,7 @@ namespace Game.Server; [LinkEntityToClass("light_directional")] [LinkEntityToClass("light_glspot")] [LinkEntityToClass("light_spot")] -public class Light : PointEntity // TODO, server only +public class Light : ServerOnlyPointEntity { } From ea9f2559ebb5d8708f216b592fbc23d1aa4615fb Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Sun, 1 Nov 2026 16:32:30 +0000 Subject: [PATCH 10/16] More methods --- Game.Client/C_BaseAnimating.cs | 3 - Game.Client/C_BaseEntity.cs | 18 +- Game.Server/BaseAnimating.cs | 17 + Game.Server/BoneManipulate.cs | 38 ++ Game.Server/EntityList.cs | 43 ++ Game.Shared/BaseAnimatingShared.cs | 51 ++ Game.Shared/CollisionProperty.cs | 44 ++ Game.Shared/GarrysMod/LuaEngine.cs | 14 + Game.Shared/GarrysMod/LuaEntity.cs | 576 ++++++++++++++++++++- Game.Shared/GarrysMod/LuaEnts.cs | 45 +- Game.Shared/GarrysMod/LuaGame.cs | 45 +- Game.Shared/GarrysMod/LuaPlayer.cs | 231 +++++++++ Game.Shared/GarrysMod/LuaSound.cs | 6 + Game.Shared/GarrysMod/LuaTakeDamageInfo.cs | 16 + Game.Shared/GarrysMod/LuaUtil.cs | 25 + Game.Shared/GarrysMod/LuaVMatrix.cs | 7 + Game.Shared/GarrysMod/LuaVector.cs | 5 +- Game.Shared/PhysicsMainShared.cs | 2 +- Game.Shared/SharedDefs.cs | 8 + Source.Common/GarrysMod/Lua/UserData.cs | 3 - Source.Engine/PrEdict.cs | 19 +- Source.StdShader.Gl46/UnlitTwoTexture.cs | 16 +- 22 files changed, 1201 insertions(+), 31 deletions(-) delete mode 100644 Source.Common/GarrysMod/Lua/UserData.cs diff --git a/Game.Client/C_BaseAnimating.cs b/Game.Client/C_BaseAnimating.cs index b9023e34e..609a4b68a 100644 --- a/Game.Client/C_BaseAnimating.cs +++ b/Game.Client/C_BaseAnimating.cs @@ -234,9 +234,6 @@ public TimeUnit_t GetSequenceGroundSpeed(StudioHdr? studioHdr, int sequence) { return 0; } - public virtual void UpdateModelScale() { - // todo - } bool SequenceLoops; bool SequenceFinished; diff --git a/Game.Client/C_BaseEntity.cs b/Game.Client/C_BaseEntity.cs index 7b1030243..8b6539b5f 100644 --- a/Game.Client/C_BaseEntity.cs +++ b/Game.Client/C_BaseEntity.cs @@ -2520,7 +2520,8 @@ public void SetAbsOrigin(in Vector3 absOrigin) { RemoveEFlags(EFL.DirtyAbsTransform); AbsOrigin = absOrigin; - // TODO: Coordinate frame... + MathLib.MatrixSetColumn(absOrigin, 3, ref CoordinateFrame); + C_BaseEntity? moveParent = GetMoveParent(); if (moveParent == null) { @@ -2528,7 +2529,8 @@ public void SetAbsOrigin(in Vector3 absOrigin) { return; } - // TODO: Handle move parents... + MathLib.VectorITransform(absOrigin, moveParent.EntityToWorldTransform(), out Vector3 origin); + Origin = origin; } public void SetAbsAngles(in QAngle absAngles) { @@ -2541,7 +2543,8 @@ public void SetAbsAngles(in QAngle absAngles) { RemoveEFlags(EFL.DirtyAbsTransform); AbsRotation = absAngles; - // TODO: Set coordinate frame + MathLib.AngleMatrix(absAngles, out CoordinateFrame); + MathLib.MatrixSetColumn(AbsOrigin, 3, ref CoordinateFrame); C_BaseEntity? moveParent = GetMoveParent(); @@ -2550,7 +2553,14 @@ public void SetAbsAngles(in QAngle absAngles) { return; } - // TODO: Handle move parents... + if (AbsRotation == moveParent.GetAbsAngles()) + Rotation = default; + else { + MathLib.MatrixInvert(moveParent.EntityToWorldTransform(), out Matrix3x4 worldToParent); + MathLib.ConcatTransforms(worldToParent, CoordinateFrame, out Matrix3x4 localMatrix); + MathLib.MatrixAngles(localMatrix, out QAngle rotation); + Rotation = rotation; + } } public void MoveToAimEnt() { diff --git a/Game.Server/BaseAnimating.cs b/Game.Server/BaseAnimating.cs index 6b68a62f4..9b1597fc6 100644 --- a/Game.Server/BaseAnimating.cs +++ b/Game.Server/BaseAnimating.cs @@ -140,6 +140,23 @@ public BaseAnimating() { [NetworkVar("m_pFlexManipulator")] public partial EHANDLE FlexManipulator { get; set; } + public virtual void SetBoneManipulator(BaseEntity? manipulator) => BoneManipulator = EHANDLE.FromEntity(manipulator); + + public virtual BaseEntity? GetBoneManipulator(bool create) { + if (BoneManipulator.Get() == null && create) { + BaseEntity? manipulator = CreateEntityByName("manipulate_bone"); + if (manipulator == null) + return null; + + manipulator.SetParent(this, -1); + manipulator.SetLocalOrigin(default); + manipulator.Spawn(); + SetBoneManipulator(manipulator); + } + + return BoneManipulator.Get(); + } + public virtual void SetFlexManipulator(BaseEntity? manipulator) => FlexManipulator = EHANDLE.FromEntity(manipulator); public virtual BaseEntity? GetFlexManipulator(bool create) { diff --git a/Game.Server/BoneManipulate.cs b/Game.Server/BoneManipulate.cs index d1c010ddf..ae12bee97 100644 --- a/Game.Server/BoneManipulate.cs +++ b/Game.Server/BoneManipulate.cs @@ -23,4 +23,42 @@ public partial class BoneManipulate : BaseEntity public partial ref readonly InlineArrayMaxStudioBones BoneScale { get; } [NetworkVar("m_BoneJiggle")] public partial ref readonly InlineArrayMaxStudioBones BoneJiggle { get; } + + InlineArrayMaxStudioBones LocalBonePos; + InlineArrayMaxStudioBones LocalBoneAng; + + public void SetBonePosition(byte index, Vector3 pos, bool networkUpdate) { + if (pos.Length() > 128.0f) + pos = Vector3.Normalize(pos) * 128.0f; + + if (networkUpdate && BonePos[index] != pos) { + NetworkStateChanged(); + BonePosForModify()[index] = pos; + } + + LocalBonePos[index] = pos; + } + + public void SetBoneAngles(byte index, in Vector3 ang, bool networkUpdate) { + if (networkUpdate && BoneAng[index] != ang) { + NetworkStateChanged(); + BoneAngForModify()[index] = ang; + } + + LocalBoneAng[index] = ang; + } + + public void SetBoneScale(byte index, Vector3 scale) { + if (scale.Length() > 512.0f) + scale = Vector3.Normalize(scale) * 512.0f; + + if (BoneScale[index] != scale) { + NetworkStateChanged(); + BoneScaleForModify()[index] = scale; + } + } + + public ref readonly Vector3 GetBonePosition(byte index) => ref LocalBonePos[index]; + public ref readonly Vector3 GetBoneAngles(byte index) => ref LocalBoneAng[index]; + public ref readonly Vector3 GetBoneScale(byte index) => ref BoneScale[index]; } diff --git a/Game.Server/EntityList.cs b/Game.Server/EntityList.cs index 03ee0af54..35d78d72b 100644 --- a/Game.Server/EntityList.cs +++ b/Game.Server/EntityList.cs @@ -202,6 +202,49 @@ public void AddToDeleteList(IServerNetworkable? ent) { public int NumberOfEdicts() => NumEdicts; public bool IsClearingEntities() => ClearingEntities; + public BaseEntity? FindEntityInSphere(BaseEntity? startEntity, in Vector3 center, float radius) { + EntInfo? info = startEntity != null ? NextEntInfo(GetEntInfoPtr(startEntity.GetRefEHandle())) : FirstEntInfo(); + + for (; info != null; info = NextEntInfo(info)) { + BaseEntity? ent = (BaseEntity?)info.Entity; + if (ent == null) { + DevWarning("NULL entity in global entity list!\n"); + continue; + } + + if (ent.Edict() == null) + continue; + + Vector3 relativeCenter = ent.CollisionProp().WorldToCollisionSpace(center); + if (!CollisionUtils.IsBoxIntersectingSphere(ent.CollisionProp().OBBMins(), ent.CollisionProp().OBBMaxs(), relativeCenter, radius)) + continue; + + return ent; + } + + return null; + } + + public BaseEntity? FindEntityByModel(BaseEntity? startEntity, ReadOnlySpan modelName) { + EntInfo? info = startEntity != null ? NextEntInfo(GetEntInfoPtr(startEntity.GetRefEHandle())) : FirstEntInfo(); + + for (; info != null; info = NextEntInfo(info)) { + BaseEntity? ent = (BaseEntity?)info.Entity; + if (ent == null) { + DevWarning("NULL entity in global entity list!\n"); + continue; + } + + if (ent.Edict() == null || ent.GetModelName().IsEmpty) + continue; + + if (FStrEq(ent.GetModelName(), modelName)) + return ent; + } + + return null; + } + public BaseEntity? FirstEnt() => NextEnt(null); public BaseEntity? NextEnt(BaseEntity? currentEnt) { diff --git a/Game.Shared/BaseAnimatingShared.cs b/Game.Shared/BaseAnimatingShared.cs index 9abb166ff..ec6ed3516 100644 --- a/Game.Shared/BaseAnimatingShared.cs +++ b/Game.Shared/BaseAnimatingShared.cs @@ -30,6 +30,57 @@ public partial class int IKCounter; #endif + public void SetModelScale(float scale, float changeDuration = 0.0f) { + if (changeDuration > 0.0f) { + ref ModelScaleData mvs = ref CreateDataObject(DataObjectType.ModelScale); + mvs.ModelScaleStart = ModelScale; + mvs.ModelScaleGoal = scale; + mvs.ModelScaleStartTime = (float)gpGlobals.CurTime; + mvs.ModelScaleFinishTime = mvs.ModelScaleStartTime + changeDuration; +#if !CLIENT_DLL + SetContextThink(UpdateModelScale, gpGlobals.CurTime, "UpdateModelScaleThink"); +#endif + } + else { + ModelScale = scale; + RefreshCollisionBounds(); + + if (HasDataObjectType(DataObjectType.ModelScale)) + DestroyDataObject(DataObjectType.ModelScale); + } + } + +#if CLIENT_DLL + public virtual void UpdateModelScale() { +#else + public void UpdateModelScale() { +#endif + ref ModelScaleData mvs = ref GetDataObject(DataObjectType.ModelScale); + if (Unsafe.IsNullRef(ref mvs)) + return; + + float dt = mvs.ModelScaleFinishTime - mvs.ModelScaleStartTime; + Assert(dt > 0.0f); + + float frac = ((float)gpGlobals.CurTime - mvs.ModelScaleStartTime) / dt; + frac = Math.Clamp(frac, 0.0f, 1.0f); + + if (gpGlobals.CurTime >= mvs.ModelScaleFinishTime) { + ModelScale = mvs.ModelScaleGoal; + DestroyDataObject(DataObjectType.ModelScale); + } + else + ModelScale = MathLib.Lerp(frac, mvs.ModelScaleStart, mvs.ModelScaleGoal); + + RefreshCollisionBounds(); +#if !CLIENT_DLL + if (frac < 1.0f) + SetContextThink(UpdateModelScale, gpGlobals.CurTime, "UpdateModelScaleThink"); +#endif + } + + public void RefreshCollisionBounds() => CollisionProp().RefreshScaledCollisionBounds(); + Vector3 GetIKOrigin() { #if CLIENT_DLL return GetRenderOrigin(); diff --git a/Game.Shared/CollisionProperty.cs b/Game.Shared/CollisionProperty.cs index 9910c90db..a01f37d0a 100644 --- a/Game.Shared/CollisionProperty.cs +++ b/Game.Shared/CollisionProperty.cs @@ -601,6 +601,50 @@ public ref readonly Vector3 OBBSize(){ return ref Size; } + public void SetSurroundingBoundsType(SurroundingBoundsType type) { + SurroundType = (byte)type; + Assert(type != SurroundingBoundsType.UseSpecifiedBounds); + MarkSurroundingBoundsDirty(); + } + + public void SetSurroundingBoundsType(SurroundingBoundsType type, in Vector3 mins, in Vector3 maxs) { + SurroundType = (byte)type; + if (type != SurroundingBoundsType.UseSpecifiedBounds) { + MarkSurroundingBoundsDirty(); + return; + } + + SpecifiedSurroundingMinsPreScaledForModify() = mins; + SpecifiedSurroundingMaxsPreScaledForModify() = maxs; + + BaseAnimating? anim = Outer.GetBaseAnimating(); + if (anim != null && anim.GetModelScale() != 1.0f) { + Vector3 newMins = mins * anim.GetModelScale(); + Vector3 newMaxs = maxs * anim.GetModelScale(); + + SpecifiedSurroundingMinsForModify() = newMins; + SpecifiedSurroundingMaxsForModify() = newMaxs; + SurroundingMins = newMins; + SurroundingMaxs = newMaxs; + } + else { + SpecifiedSurroundingMinsForModify() = mins; + SpecifiedSurroundingMaxsForModify() = maxs; + SurroundingMins = mins; + SurroundingMaxs = maxs; + } + } + + public void RefreshScaledCollisionBounds() { + SetCollisionBounds(MinsPreScaled, MaxsPreScaled); + + SurroundingBoundsType surroundType = (SurroundingBoundsType)SurroundType; + if (surroundType == SurroundingBoundsType.UseSpecifiedBounds) + SetSurroundingBoundsType(surroundType, SpecifiedSurroundingMinsPreScaled, SpecifiedSurroundingMaxsPreScaled); + else + SetSurroundingBoundsType(surroundType); + } + public void SetCollisionBounds(in Vector3 mins, in Vector3 maxs) { if (MinsPreScaled != mins || MaxsPreScaled != maxs) { MinsPreScaledForModify() = mins; diff --git a/Game.Shared/GarrysMod/LuaEngine.cs b/Game.Shared/GarrysMod/LuaEngine.cs index d26e2f4d2..7c6de70f2 100644 --- a/Game.Shared/GarrysMod/LuaEngine.cs +++ b/Game.Shared/GarrysMod/LuaEngine.cs @@ -1,4 +1,5 @@ #if CLIENT_DLL || GAME_DLL || MENU_DLL +using Source.Common.Formats.BSP; using Source.Common.GarrysMod; using Source.Common.GarrysMod.Lua; @@ -201,5 +202,18 @@ static int GetGamemodes(ILuaInterface lua) { table.UnReference(); return 1; } + +#if GAME_DLL + [LuaFunction] + static int LightStyle(ILuaInterface lua) { + uint style = (uint)lua.CheckNumber(1); + if (style < BSPFileCommon.MAX_LIGHTSTYLES) { + engine.LightStyle((int)style, lua.CheckString(2)); + return 0; + } + lua.ErrorFromLua($"Invalid lightstyle id {style}\n"); + return 0; + } +#endif } #endif diff --git a/Game.Shared/GarrysMod/LuaEntity.cs b/Game.Shared/GarrysMod/LuaEntity.cs index 025e63bc2..41285e21a 100644 --- a/Game.Shared/GarrysMod/LuaEntity.cs +++ b/Game.Shared/GarrysMod/LuaEntity.cs @@ -530,6 +530,70 @@ static int Entity__PhysicsDestroy(ILuaInterface lua) { return 0; } +#if GAME_DLL + [LuaMethod] + static int Entity__PhysicsInit(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + if (!CanModifyPhysics(ent)) { + lua.PushBool(false); + return 1; + } + + if (PhysIsInCallback() && ent.VPhysicsGetObject() != null) { + lua.ErrorFromLua("Cannot destroy physics in a physics callback!"); + lua.PushBool(false); + return 1; + } + + Solid solid = default; + if (!PhysModelParseSolid(ref solid, ent, ent.GetModelIndex())) { + lua.PushBool(false); + return 1; + } + + int solidType = Math.Clamp((int)lua.CheckNumber(2), 0, 6); + if (lua.GetType(3) != LuaType.Nil) + solid.MassCenterOverride = LuaVector.Get_Vector(3); + + ent.VPhysicsDestroyObject(); + IPhysicsObject? phys = ent.VPhysicsInitNormal((SolidType)solidType, 0, true, ref solid); + if (phys == null && solidType != 0) { + Warning("Entity:PhysicsInit() - Failed to create physics for entity\n"); + lua.PushBool(false); + return 1; + } + lua.PushBool(true); + return 1; + } + + [LuaMethod] + static int Entity__PhysicsInitStatic(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + if (!CanModifyPhysics(ent)) { + lua.PushBool(false); + return 1; + } + + if (PhysIsInCallback() && ent.VPhysicsGetObject() != null) { + lua.ErrorFromLua("Cannot destroy physics in a physics callback!"); + lua.PushBool(false); + return 1; + } + + ent.VPhysicsDestroyObject(); + int solidType = Math.Clamp((int)lua.CheckNumber(2), 0, 6); + ent.CollisionProp().SetSolid((SolidType)solidType); + IPhysicsObject? phys = ent.VPhysicsInitStatic(); + if (phys == null && solidType != 0) { + Warning("Entity:VPhysicsInitStatic() - Failed to create physics for entity\n"); + lua.PushBool(false); + return 1; + } + lua.PushBool(true); + return 1; + } +#endif + [LuaMethod] static int Entity__SetColor(ILuaInterface lua) { BaseEntity ent = Get_Entity(1, false)!; @@ -1763,8 +1827,13 @@ static int Entity__EmitSound(ILuaInterface lua) { if (ent.IsPlayer() && prediction.InPrediction()) filter.UsePredictionRules(); #else - // if (lua.GetType(9) == LuaType.RecipientFilter) { filter = Get_CRecipientFilter(9) ... } // really important TODO - PASAttenuationFilter filter = new(soundEnt, ATTN_NORM); + RecipientFilter filter; + if (lua.GetType(9) == LuaType.RecipientFilter) { + filter = new(); + filter.CopyFrom(LuaRecipientFilter.Get_RecipientFilter(9)); + } + else + filter = new PASAttenuationFilter(soundEnt, ATTN_NORM); if (ent.IsPlayer()) filter.UsePredictionRules(); #endif @@ -2473,5 +2542,508 @@ static int Entity____newindex(ILuaInterface lua) { ent.LuaCalcAbsolutePosition.UnReference(); return 0; } + + [LuaMethod] + static int Entity__GetEffects(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + lua.PushNumber(ent.Effects); + return 1; + } + + [LuaMethod] + static int Entity__GetElasticity(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + lua.PushNumber(ent.Elasticity); + return 1; + } + + [LuaMethod] + static int Entity__GetFriction(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + lua.PushNumber(ent.Friction); + return 1; + } + + [LuaMethod] + static int Entity__GetGravity(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + lua.PushNumber(ent.GetGravity()); + return 1; + } + + [LuaMethod] + static int Entity__GetAbsVelocity(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + LuaVector.Push_Vector(ent.GetAbsVelocity()); + return 1; + } + + [LuaMethod] + static int Entity__GetBaseVelocity(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + LuaVector.Push_Vector(ent.GetBaseVelocity()); + return 1; + } + + [LuaMethod] + static int Entity__GetLocalAngularVelocity(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + LuaAngle.Push_Angle(ent.GetLocalAngularVelocity()); + return 1; + } + + [LuaMethod] + static int Entity__GetMoveCollide(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + lua.PushNumber(ent.MoveCollide); + return 1; + } + + [LuaMethod] + static int Entity__GetParentAttachment(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + lua.PushNumber(ent.ParentAttachment); + return 1; + } + + [LuaMethod] + static int Entity__GetRenderFX(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + lua.PushNumber(ent.RenderFX); + return 1; + } + + [LuaMethod] + static int Entity__GetRenderMode(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + lua.PushNumber(ent.RenderMode); + return 1; + } + +#if GAME_DLL + [LuaMethod] + static int Entity__GetEFlags(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + lua.PushNumber((int)ent.GetEFlags()); + return 1; + } + + [LuaMethod] + static int Entity__MapCreationID(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + lua.PushNumber(ent.HammerID); + return 1; + } + + [LuaMethod] + static int Entity__CreatedByMap(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + lua.PushBool(ent.HammerID != -1); + return 1; + } + + [LuaMethod] + static int Entity__SetElasticity(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + ent.Elasticity = (float)lua.CheckNumber(2); + return 0; + } +#endif + + [LuaMethod] + static int Entity__IsEFlagSet(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + lua.PushBool(ent.IsEFlagSet((EFL)(int)lua.CheckNumber(2))); + return 1; + } + + [LuaMethod] + static int Entity__IsEffectActive(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + lua.PushBool(ent.IsEffectActive((EntityEffects)(int)lua.CheckNumber(2))); + return 1; + } + + [LuaMethod] + static int Entity__RemoveEffects(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + ent.RemoveEffects((EntityEffects)(int)lua.CheckNumber(2)); + return 0; + } + + [LuaMethod] + static int Entity__AddSolidFlags(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + ent.AddSolidFlags((SolidFlags)(int)lua.CheckNumber(2)); + return 0; + } + + [LuaMethod] + static int Entity__RemoveSolidFlags(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + ent.RemoveSolidFlags((SolidFlags)(int)lua.CheckNumber(2)); + return 0; + } + + [LuaMethod] + static int Entity__SetSolidFlags(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + ent.SetSolidFlags((SolidFlags)(int)lua.CheckNumber(2)); + return 0; + } + + [LuaMethod] + static int Entity__GetSolidFlags(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + lua.PushNumber((int)ent.CollisionProp().GetSolidFlags()); + return 1; + } + + [LuaMethod] + static int Entity__GetSolid(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + lua.PushNumber((int)ent.CollisionProp().GetSolid()); + return 1; + } + + [LuaMethod] + static int Entity__SetSolid(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + int solid = Math.Clamp((int)lua.CheckNumber(2), 0, 6); + ent.CollisionProp().SetSolid((SolidType)solid); + return 0; + } + + [LuaMethod] + static int Entity__IsSolid(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + lua.PushBool(ent.IsSolid()); + return 1; + } + + [LuaMethod] + static int Entity__OBBCenter(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + LuaVector.Push_Vector(ent.CollisionProp().OBBCenter()); + return 1; + } + + [LuaMethod] + static int Entity__BoundingRadius(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + lua.PushNumber(ent.CollisionProp().BoundingRadius()); + return 1; + } + + [LuaMethod] + static int Entity__SetFriction(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + ent.Friction = (float)lua.CheckNumber(2); + return 0; + } + + [LuaMethod] + static int Entity__SetGravity(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + ent.SetGravity((float)lua.CheckNumber(2)); + return 0; + } + + [LuaMethod] + static int Entity__SetRenderFX(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + ent.RenderFX = (byte)(int)lua.CheckNumber(2); + return 0; + } + + [LuaMethod] + static int Entity__SetMoveCollide(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + int moveCollide = Math.Clamp((int)lua.CheckNumber(2), 0, 3); + ent.SetMoveCollide((MoveCollide)moveCollide); + return 0; + } + + [LuaMethod] + static int Entity__GetLocalAngles(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + LuaAngle.Push_Angle(ent.GetLocalAngles()); + return 1; + } + + [LuaMethod] + static int Entity__SetLocalAngles(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + ent.SetLocalAngles(LuaAngle.Get_Angle(2)); + return 0; + } + + [LuaMethod] + static int Entity__GetLocalPos(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + LuaVector.Push_Vector(ent.GetLocalOrigin()); + return 1; + } + + [LuaMethod] + static int Entity__GetFlexNum(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + BaseAnimating? animating = ent.GetBaseAnimating(); + if (animating == null || ent.IsEFlagSet(EFL.KillMe)) { + lua.PushNumber(0); + return 1; + } + lua.PushNumber((int)animating.GetNumFlexControllers()); + return 1; + } + + [LuaMethod] + static int Entity__GetColor(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + lua.PushColor(ent.ColorRender); + return 1; + } + + [LuaMethod] + static int Entity__SetVelocity(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + ent.SetBaseVelocity(LuaVector.Get_Vector(2)); + return 0; + } + + [LuaMethod] + static int Entity__GetChildren(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + lua.CreateTable(); + int i = 1; + for (BaseEntity? child = ent.FirstMoveChild(); child != null; child = child.NextMovePeer()) { + lua.PushNumber(i++); + LuaEntity.Push_Entity(child); + lua.SetTable(-3); + } + return 1; + } + + [LuaMethod] + static int Entity__SetCollisionGroup(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + if (IsQueuedForRemoval(ent) || ent.IsEFlagSet(EFL.KillMe)) + return 0; + int group = Math.Clamp((int)lua.CheckNumber(2), 0, 33); + ent.SetCollisionGroup((CollisionGroup)group); + return 0; + } + +#if GAME_DLL + [LuaMethod] + static int Entity__HasBoneManipulations(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + BaseAnimating? animating = ent.GetBaseAnimating(); + lua.PushBool(animating != null && !ent.IsEFlagSet(EFL.KillMe) && animating.GetBoneManipulator(false) != null); + return 1; + } + + [LuaMethod] + static int Entity__GetManipulateBonePosition(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + BaseAnimating? animating = ent.GetBaseAnimating(); + if (animating == null || ent.IsEFlagSet(EFL.KillMe) || animating.GetBoneManipulator(false) is not BoneManipulate manipulator) { + LuaVector.Push_Vector(default); + return 1; + } + LuaVector.Push_Vector(manipulator.GetBonePosition((byte)(int)lua.CheckNumber(2))); + return 1; + } + + [LuaMethod] + static int Entity__GetManipulateBoneAngles(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + BaseAnimating? animating = ent.GetBaseAnimating(); + if (animating == null || ent.IsEFlagSet(EFL.KillMe) || animating.GetBoneManipulator(false) is not BoneManipulate manipulator) { + LuaAngle.Push_Angle(default); + return 1; + } + Vector3 ang = manipulator.GetBoneAngles((byte)(int)lua.CheckNumber(2)); + LuaAngle.Push_Angle(new QAngle(ang.X, ang.Y, ang.Z)); + return 1; + } + + [LuaMethod] + static int Entity__GetManipulateBoneScale(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + BaseAnimating? animating = ent.GetBaseAnimating(); + if (animating == null || ent.IsEFlagSet(EFL.KillMe) || animating.GetBoneManipulator(false) is not BoneManipulate manipulator) { + LuaVector.Push_Vector(new Vector3(1.0f, 1.0f, 1.0f)); + return 1; + } + LuaVector.Push_Vector(manipulator.GetBoneScale((byte)(int)lua.CheckNumber(2))); + return 1; + } + + [LuaMethod] + static int Entity__ManipulateBonePosition(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + BaseAnimating? animating = ent.GetBaseAnimating(); + if (animating == null || IsQueuedForRemoval(ent) || ent.IsEFlagSet(EFL.KillMe) || animating.GetBoneManipulator(true) is not BoneManipulate manipulator) + return 0; + bool networkUpdate = lua.GetType(4) == LuaType.Nil || lua.GetBool(4); + Vector3 pos = LuaVector.Get_Vector(3); + manipulator.SetBonePosition((byte)(int)lua.CheckNumber(2), pos, networkUpdate); + return 0; + } + + [LuaMethod] + static int Entity__ManipulateBoneAngles(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + BaseAnimating? animating = ent.GetBaseAnimating(); + if (animating == null || IsQueuedForRemoval(ent) || ent.IsEFlagSet(EFL.KillMe) || animating.GetBoneManipulator(true) is not BoneManipulate manipulator) + return 0; + bool networkUpdate = lua.GetType(4) == LuaType.Nil || lua.GetBool(4); + QAngle ang = LuaAngle.Get_Angle(3); + manipulator.SetBoneAngles((byte)(int)lua.CheckNumber(2), new Vector3(ang.X, ang.Y, ang.Z), networkUpdate); + return 0; + } + + [LuaMethod] + static int Entity__ManipulateBoneScale(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + BaseAnimating? animating = ent.GetBaseAnimating(); + if (animating == null || ent.IsEFlagSet(EFL.KillMe) || animating.GetBoneManipulator(true) is not BoneManipulate manipulator) + return 0; + Vector3 scale = LuaVector.Get_Vector(3); + manipulator.SetBoneScale((byte)(int)lua.CheckNumber(2), scale); + return 0; + } + + [LuaMethod] + static int Entity__GetName(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + lua.PushString(ent.GetEntityName() ?? ""); + return 1; + } +#endif + + [LuaMethod] + static int Entity__GetCollisionBounds(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + LuaVector.Push_Vector(ent.CollisionProp().OBBMins()); + LuaVector.Push_Vector(ent.CollisionProp().OBBMaxs()); + return 2; + } + + [LuaMethod] + static int Entity__SetCollisionBounds(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + LuaVector.OrderVectors(LuaVector.Get_Vector(2), LuaVector.Get_Vector(3), out Vector3 mins, out Vector3 maxs); + if (lua.GetType(4) == LuaType.Vector) { + mins -= LuaVector.Get_Vector(4); + maxs -= LuaVector.Get_Vector(4); + } + ent.SetCollisionBounds(mins, maxs); + return 0; + } + + [LuaMethod] + static int Entity__SetCollisionBoundsWS(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + Vector3 a = LuaVector.Get_Vector(2) - ent.GetAbsOrigin(); + Vector3 b = LuaVector.Get_Vector(3) - ent.GetAbsOrigin(); + LuaVector.OrderVectors(a, b, out Vector3 mins, out Vector3 maxs); + if (lua.GetType(4) == LuaType.Vector) { + mins -= LuaVector.Get_Vector(4); + maxs += LuaVector.Get_Vector(4); + } + ent.SetCollisionBounds(mins, maxs); + return 0; + } + +#if GAME_DLL + [LuaMethod] + static int Entity__GetFlexWeight(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + BaseAnimating? animating = ent.GetBaseAnimating(); + if (animating == null || ent.IsEFlagSet(EFL.KillMe)) { + lua.PushNumber(0); + return 1; + } + int index = Math.Clamp((int)lua.CheckNumber(2), 0, 95); + if (animating.GetFlexManipulator(false) is not FlexManipulate manipulator) { + lua.PushNumber(0); + return 1; + } + lua.PushNumber(manipulator.FlexWeights[index]); + return 1; + } + + [LuaMethod] + static int Entity__GetFlexScale(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + BaseAnimating? animating = ent.GetBaseAnimating(); + if (animating == null || ent.IsEFlagSet(EFL.KillMe) || animating.GetFlexManipulator(false) is not FlexManipulate manipulator) { + lua.PushNumber(1.0); + return 1; + } + lua.PushNumber(manipulator.ExScale); + return 1; + } + + [LuaMethod] + static int Entity__SetFlexScale(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + BaseAnimating? animating = ent.GetBaseAnimating(); + if (animating == null || IsQueuedForRemoval(ent) || ent.IsEFlagSet(EFL.KillMe)) + return 0; + animating.GetFlexManipulator(false); + if (animating.GetFlexManipulator(true) is FlexManipulate manipulator) + manipulator.ExScale = (float)lua.CheckNumber(2); + return 0; + } + + [LuaMethod] + static int Entity__HasFlexManipulatior(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + BaseAnimating? animating = ent.GetBaseAnimating(); + lua.PushBool(animating != null && !ent.IsEFlagSet(EFL.KillMe) && animating.GetFlexManipulator(false) != null); + return 1; + } +#endif + +#if CLIENT_DLL + [LuaMethod] + static int Entity__InvalidateBoneCache(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + ent.GetBaseAnimating()?.InvalidateBoneCache(); + return 0; + } +#endif + + [LuaMethod] + static int Entity__GetModelScale(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + BaseAnimating? animating = ent.GetBaseAnimating(); + if (animating == null || ent.IsEFlagSet(EFL.KillMe)) + return 0; + lua.PushNumber(animating.GetModelScale()); + return 1; + } + + [LuaMethod] + static int Entity__SetModelScale(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + BaseAnimating? animating = ent.GetBaseAnimating(); + if (animating == null || IsQueuedForRemoval(ent) || ent.IsEFlagSet(EFL.KillMe)) + return 0; + float scale = (float)lua.CheckNumber(2); + float duration = lua.GetType(3) == LuaType.Number ? (float)lua.CheckNumber(3) : 0.0f; + animating.SetModelScale(scale, duration); + return 0; + } + + [LuaMethod] + static int Entity__SetLocalPos(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + ent.SetLocalOrigin(LuaVector.Get_Vector(2)); + return 0; + } } #endif diff --git a/Game.Shared/GarrysMod/LuaEnts.cs b/Game.Shared/GarrysMod/LuaEnts.cs index e7f26fd20..c5b518a06 100644 --- a/Game.Shared/GarrysMod/LuaEnts.cs +++ b/Game.Shared/GarrysMod/LuaEnts.cs @@ -2,6 +2,8 @@ using System.Numerics; #if CLIENT_DLL || GAME_DLL +using Game.Shared; + using Source.Common.GarrysMod.Lua; #if CLIENT_DLL @@ -200,7 +202,32 @@ static int FindByClass(ILuaInterface lua) { return 1; } - // todo: FindInSphere + [LuaFunction] + static int FindInSphere(ILuaInterface lua) { + Vector3 center = LuaVector.Get_Vector(1); + float radius = (float)lua.CheckNumber(2); + lua.CreateTable(); + int i = 1; +#if CLIENT_DLL + for (BaseEntity? ent = cl_entitylist.FirstBaseEntity(); ent != null; ent = cl_entitylist.NextBaseEntity(ent)) { + if (ent.IsEFlagSet(EFL.KillMe)) + continue; + Vector3 relativeCenter = ent.CollisionProp().WorldToCollisionSpace(center); + if (!CollisionUtils.IsBoxIntersectingSphere(ent.CollisionProp().OBBMins(), ent.CollisionProp().OBBMaxs(), relativeCenter, radius)) + continue; + lua.PushNumber(i++); + LuaEntity.Push_Entity(ent); + lua.SetTable(-3); + } +#else + for (BaseEntity? ent = gEntList.FindEntityInSphere(null, center, radius); ent != null; ent = gEntList.FindEntityInSphere(ent, center, radius)) { + lua.PushNumber(i++); + LuaEntity.Push_Entity(ent); + lua.SetTable(-3); + } +#endif + return 1; + } #if CLIENT_DLL static readonly C_BaseEntity?[] FindList = new C_BaseEntity?[8192]; #else @@ -246,7 +273,21 @@ static int FindByName(ILuaInterface lua) { return 1; } - // todo: FindByModel +#if GAME_DLL + [LuaFunction] + static int FindByModel(ILuaInterface lua) { + string model = new(lua.CheckString(1)); + lua.CreateTable(); + int i = 1; + for (BaseEntity? ent = gEntList.FindEntityByModel(null, model); ent != null; ent = gEntList.FindEntityByModel(ent, model)) { + lua.PushNumber(i++); + LuaEntity.Push_Entity(ent); + lua.SetTable(-3); + } + return 1; + } +#endif + // todo: client FindByModel [LuaFunction] static int GetByIndex(ILuaInterface lua) { diff --git a/Game.Shared/GarrysMod/LuaGame.cs b/Game.Shared/GarrysMod/LuaGame.cs index aaae8c5a9..5d6aedcb8 100644 --- a/Game.Shared/GarrysMod/LuaGame.cs +++ b/Game.Shared/GarrysMod/LuaGame.cs @@ -82,11 +82,39 @@ static int GetMap(ILuaInterface lua) { // todo: GetMapNext // todo: ConsoleCommand // todo: RemoveRagdolls - // todo: SetTimeScale - // todo: SetSkillLevel - // todo: GetTimeScale - // todo: GetSkillLevel - // todo: AddParticles +#if GAME_DLL + [LuaFunction] + static int SetTimeScale(ILuaInterface lua) { + if (g_pGameRules is not GMODGameRules rules) + return 0; + rules.TimeScale = (float)g_Lua!.CheckNumber(1); + return 0; + } + + [LuaFunction] + static int SetSkillLevel(ILuaInterface lua) { + if (g_pGameRules is not GMODGameRules rules) + return 0; + rules.SkillLevel = (int)g_Lua!.CheckNumber(1); + return 0; + } +#endif + + [LuaFunction] + static int GetTimeScale(ILuaInterface lua) { + if (g_pGameRules is not GMODGameRules rules) + return 0; + g_Lua!.PushNumber(rules.TimeScale); + return 1; + } + + [LuaFunction] + static int GetSkillLevel(ILuaInterface lua) { + if (g_pGameRules is not GMODGameRules rules) + return 0; + g_Lua!.PushNumber(rules.SkillLevel); + return 1; + } [LuaFunction] static int MountGMA(ILuaInterface lua) { @@ -123,8 +151,11 @@ static int GetWorld(ILuaInterface lua) { #endif return 1; } - // todo: MaxPlayers - // todo: GetAmmoID + [LuaFunction] + static int GetAmmoID(ILuaInterface lua) { + g_Lua!.PushNumber(GetAmmoDef().Index(g_Lua.CheckString(1))); + return 1; + } // todo: GetAmmoTypes [LuaFunction] diff --git a/Game.Shared/GarrysMod/LuaPlayer.cs b/Game.Shared/GarrysMod/LuaPlayer.cs index d92643e11..25e521fd8 100644 --- a/Game.Shared/GarrysMod/LuaPlayer.cs +++ b/Game.Shared/GarrysMod/LuaPlayer.cs @@ -1080,5 +1080,236 @@ static int GetHumans(ILuaInterface lua) { // return 1; // } #endif + + [LuaMethod] + static int Player__GetCanWalk(ILuaInterface lua) { + GMOD_Player player = (GMOD_Player)Get_Player(1, false)!; + lua.PushBool(player.CanWalk); + return 1; + } + + [LuaMethod] + static int Player__GetCanZoom(ILuaInterface lua) { + GMOD_Player player = (GMOD_Player)Get_Player(1, false)!; + lua.PushBool(player.CanZoom); + return 1; + } + + [LuaMethod] + static int Player__GetDrivingMode(ILuaInterface lua) { + GMOD_Player player = (GMOD_Player)Get_Player(1, false)!; + lua.PushNumber(player.DrivingMode); + return 1; + } + + [LuaMethod] + static int Player__GetJumpPower(ILuaInterface lua) { + GMOD_Player player = (GMOD_Player)Get_Player(1, false)!; + lua.PushNumber(player.JumpPower); + return 1; + } + + [LuaMethod] + static int Player__GetStepSize(ILuaInterface lua) { + GMOD_Player player = (GMOD_Player)Get_Player(1, false)!; + lua.PushNumber(player.StepSize); + return 1; + } + + [LuaMethod] + static int Player__GetViewOffsetDucked(ILuaInterface lua) { + GMOD_Player player = (GMOD_Player)Get_Player(1, false)!; + LuaVector.Push_Vector(player.ViewOffsetDucked); + return 1; + } + + [LuaMethod] + static int Player__GetLaggedMovementValue(ILuaInterface lua) { + BasePlayer player = Get_Player(1, false)!; + lua.PushNumber(player.LaggedMovementValue); + return 1; + } + + [LuaMethod] + static int Player__IsSuitEquipped(ILuaInterface lua) { + BasePlayer player = Get_Player(1, false)!; + lua.PushBool(player.IsSuitEquipped()); + return 1; + } + + [LuaMethod] + static int Player__IsWorldClickingDisabled(ILuaInterface lua) { + BasePlayer player = Get_Player(1, false)!; + lua.PushBool(player.DisableWorldClicking); + return 1; + } + + [LuaMethod] + static int Player__SetCanWalk(ILuaInterface lua) { + GMOD_Player player = (GMOD_Player)Get_Player(1, false)!; + player.CanWalk = lua.GetBool(2); + return 0; + } + + [LuaMethod] + static int Player__SetCanZoom(ILuaInterface lua) { + GMOD_Player player = (GMOD_Player)Get_Player(1, false)!; + player.CanZoom = lua.GetBool(2); + return 0; + } + + [LuaMethod] + static int Player__SetStepSize(ILuaInterface lua) { + GMOD_Player player = (GMOD_Player)Get_Player(1, false)!; + player.StepSize = (float)lua.CheckNumber(2); + return 0; + } + + [LuaMethod] + static int Player__SetViewOffsetDucked(ILuaInterface lua) { + GMOD_Player player = (GMOD_Player)Get_Player(1, false)!; + player.ViewOffsetDucked = LuaVector.Get_Vector(2); + return 0; + } + + [LuaMethod] + static int Player__GetMaxSpeed(ILuaInterface lua) { + BasePlayer player = Get_Player(1, false)!; + lua.PushNumber(player.MaxSpeed()); + return 1; + } + + [LuaMethod] + static int Player__SetMaxSpeed(ILuaInterface lua) { + BasePlayer player = Get_Player(1, false)!; + player.SetMaxSpeed((float)lua.CheckNumber(2)); + return 0; + } + + [LuaMethod] + static int Player__GetWalkSpeed(ILuaInterface lua) { + BasePlayer player = Get_Player(1, false)!; + lua.PushNumber(player.GetWalkSpeed()); + return 1; + } + + [LuaMethod] + static int Player__GetSlowWalkSpeed(ILuaInterface lua) { + BasePlayer player = Get_Player(1, false)!; + lua.PushNumber(player.GetSlowWalkSpeed()); + return 1; + } + + [LuaMethod] + static int Player__GetDuckSpeed(ILuaInterface lua) { + BasePlayer player = Get_Player(1, false)!; + lua.PushNumber(player.GetDuckSpeed()); + return 1; + } + + [LuaMethod] + static int Player__GetUnDuckSpeed(ILuaInterface lua) { + BasePlayer player = Get_Player(1, false)!; + lua.PushNumber(player.GetUnDuckSpeed()); + return 1; + } + + [LuaMethod] + static int Player__IsFrozen(ILuaInterface lua) { + BasePlayer player = Get_Player(1, false)!; + lua.PushBool((player.GetFlags() & EntityFlags.Frozen) != 0); + return 1; + } + + [LuaMethod] + static int Player__GetAllowFullRotation(ILuaInterface lua) { + GMOD_Player player = (GMOD_Player)Get_Player(1, false)!; + lua.PushBool((player.GModPlayerFlags & 16) != 0); + return 1; + } + + [LuaMethod] + static int Player__SetAllowFullRotation(ILuaInterface lua) { + GMOD_Player player = (GMOD_Player)Get_Player(1, false)!; + player.GModPlayerFlags = lua.GetBool(2) ? player.GModPlayerFlags | 16 : player.GModPlayerFlags & ~16; + return 0; + } + + [LuaMethod] + static int Player__GetAvoidPlayers(ILuaInterface lua) { + GMOD_Player player = (GMOD_Player)Get_Player(1, false)!; + lua.PushBool((player.GModPlayerFlags & 8) != 0); + return 1; + } + + [LuaMethod] + static int Player__GetNoCollideWithTeammates(ILuaInterface lua) { + GMOD_Player player = (GMOD_Player)Get_Player(1, false)!; + lua.PushBool((player.GModPlayerFlags & 2) != 0); + return 1; + } + +#if CLIENT_DLL + [LuaMethod] + static int Player__IsMuted(ILuaInterface lua) { + BasePlayer player = Get_Player(1, false)!; + lua.PushBool(GetClientVoiceMgr().IsPlayerBlocked(player.EntIndex())); + return 1; + } + + [LuaMethod] + static int Player__SetMuted(ILuaInterface lua) { + BasePlayer player = Get_Player(1, false)!; + bool muted = lua.GetBool(2); + GetClientVoiceMgr().SetPlayerBlockedState(player.EntIndex(), muted); + return 0; + } +#endif + +#if GAME_DLL + [LuaMethod] + static int Player__SetLaggedMovementValue(ILuaInterface lua) { + BasePlayer player = Get_Player(1, false)!; + float value = (float)lua.CheckNumber(2); + if (0.0f <= value) + player.LaggedMovementValue = value; + return 0; + } + + [LuaMethod] + static int Player__FlashlightIsOn(ILuaInterface lua) { + BasePlayer player = Get_Player(1, false)!; + lua.PushBool(player.FlashlightIsOn()); + return 1; + } + + [LuaMethod] + static int Player__IsConnected(ILuaInterface lua) { + BasePlayer player = Get_Player(1, false)!; + lua.PushBool(player.IsConnected()); + return 1; + } + + [LuaMethod] + static int Player__GetActivity(ILuaInterface lua) { + BasePlayer player = Get_Player(1, false)!; + lua.PushNumber((int)player.GetActivity()); + return 1; + } + + [LuaMethod] + static int Player__GodEnable(ILuaInterface lua) { + BasePlayer player = Get_Player(1, false)!; + player.AddFlag(EntityFlags.GodMode); + return 0; + } + + [LuaMethod] + static int Player__GodDisable(ILuaInterface lua) { + BasePlayer player = Get_Player(1, false)!; + player.RemoveFlag(EntityFlags.GodMode); + return 0; + } +#endif } #endif diff --git a/Game.Shared/GarrysMod/LuaSound.cs b/Game.Shared/GarrysMod/LuaSound.cs index 60ac9acc9..2d6df5bff 100644 --- a/Game.Shared/GarrysMod/LuaSound.cs +++ b/Game.Shared/GarrysMod/LuaSound.cs @@ -97,5 +97,11 @@ static int Add(ILuaInterface lua) { data.UnReference(); return 0; } + + [LuaFunction] + static int AddSoundOverrides(ILuaInterface lua) { + soundemitterbase.AddSoundOverrides(lua.CheckString(1), false); + return 0; + } } #endif diff --git a/Game.Shared/GarrysMod/LuaTakeDamageInfo.cs b/Game.Shared/GarrysMod/LuaTakeDamageInfo.cs index 1b3ed2878..6307aa0a8 100644 --- a/Game.Shared/GarrysMod/LuaTakeDamageInfo.cs +++ b/Game.Shared/GarrysMod/LuaTakeDamageInfo.cs @@ -1,6 +1,8 @@ #if CLIENT_DLL || GAME_DLL using Game.Shared; +using Source; + using Source.Common.GarrysMod.Lua; #if CLIENT_DLL @@ -34,6 +36,20 @@ static int DamageInfo(ILuaInterface lua) { return 1; } + [LuaMethod] + static int CTakeDamageInfo____tostring(ILuaInterface lua) { + Span str = stackalloc char[256]; + sprintf(str, "CTakeDamageInfo [%.1f dmg]").F(Get_TakeDamageInfo(1).GetDamage()); + lua.PushString(((ReadOnlySpan)str).SliceNullTerminatedString()); + return 1; + } + + [LuaMethod] + static int CTakeDamageInfo__IsValid(ILuaInterface lua) { + lua.PushBool(LC_TakeDamageInfo.Get(1) is TakeDamageInfoRef); + return 1; + } + [LuaMethod] static int CTakeDamageInfo__AddDamage(ILuaInterface lua) { Get_TakeDamageInfo(1).AddDamage((float)lua.GetNumber(2)); diff --git a/Game.Shared/GarrysMod/LuaUtil.cs b/Game.Shared/GarrysMod/LuaUtil.cs index f9bbf644f..1440fe531 100644 --- a/Game.Shared/GarrysMod/LuaUtil.cs +++ b/Game.Shared/GarrysMod/LuaUtil.cs @@ -906,6 +906,31 @@ static int JSONToTable(ILuaInterface lua) { } #endif +#if GAME_DLL + [LuaFunction] + static int Effect(ILuaInterface lua) { + EffectData data = LuaEffectData.Get_EffectData(2); + data.AllowOverride = true; + if (lua.GetType(3) == LuaType.Bool) + data.AllowOverride = lua.GetBool(3); + + if (lua.GetType(4) == LuaType.Bool && lua.GetBool(4)) { + PASFilter filter = new(data.Origin); + filter.SetIgnorePredictionCull(true); + TempEnts.DispatchEffect(lua.CheckString(1), data, filter); + return 0; + } + + if (lua.GetType(4) == LuaType.RecipientFilter) { + TempEnts.DispatchEffect(lua.CheckString(1), data, LuaRecipientFilter.Get_RecipientFilter(4)); + return 0; + } + + TempEnts.DispatchEffect(lua.CheckString(1), data); + return 0; + } +#endif + #if MENU_DLL [LuaFunction] static int FullPathToRelative_Menu(ILuaInterface lua) { diff --git a/Game.Shared/GarrysMod/LuaVMatrix.cs b/Game.Shared/GarrysMod/LuaVMatrix.cs index 4a66e25fc..f46dfee70 100644 --- a/Game.Shared/GarrysMod/LuaVMatrix.cs +++ b/Game.Shared/GarrysMod/LuaVMatrix.cs @@ -218,6 +218,13 @@ static int VMatrix__InvertTR(ILuaInterface lua) { return 0; } + [LuaMethod] + static int VMatrix__InvertTR2(ILuaInterface lua) { + ref Matrix4x4 m = ref Get_VMatrix(1); + MathLib.MatrixInverseTR(in m, out m); + return 0; + } + [LuaMethod] static int VMatrix__GetInverseTR(ILuaInterface lua) { ref Matrix4x4 m = ref Get_VMatrix(1); diff --git a/Game.Shared/GarrysMod/LuaVector.cs b/Game.Shared/GarrysMod/LuaVector.cs index ea9e5653b..3b0b37a75 100644 --- a/Game.Shared/GarrysMod/LuaVector.cs +++ b/Game.Shared/GarrysMod/LuaVector.cs @@ -130,7 +130,10 @@ static int Vector__Mul(ILuaInterface lua) { vec.Y *= other.Y; vec.Z *= other.Z; } - // todo: type == LuaType.Matrix: *vec = *Get_VMatrix(2) * *vec; +#if !MENU_DLL + else if (type == LuaType.Matrix) + vec = MathLib.VMul4x3(ref LuaVMatrix.Get_VMatrix(2), vec); +#endif else { float scale = (float)g_Lua.CheckNumber(2); vec.X *= scale; diff --git a/Game.Shared/PhysicsMainShared.cs b/Game.Shared/PhysicsMainShared.cs index fe54e33dc..bbf6caf08 100644 --- a/Game.Shared/PhysicsMainShared.cs +++ b/Game.Shared/PhysicsMainShared.cs @@ -824,7 +824,7 @@ public override void Shutdown() { //StepSimulation new EntityDataInstantiator(), //ModelScale - null!, + new EntityDataInstantiator(), //PositionWatcher new EntityDataInstantiator(), //PhysicsPushList diff --git a/Game.Shared/SharedDefs.cs b/Game.Shared/SharedDefs.cs index 064144d3e..3fd378f40 100644 --- a/Game.Shared/SharedDefs.cs +++ b/Game.Shared/SharedDefs.cs @@ -715,6 +715,14 @@ public struct StepSimulationStep public Quaternion Rotation; } +public struct ModelScaleData +{ + public float ModelScaleStart; + public float ModelScaleGoal; + public float ModelScaleFinishTime; + public float ModelScaleStartTime; +} + public struct StepSimulationData { public bool OriginActive; diff --git a/Source.Common/GarrysMod/Lua/UserData.cs b/Source.Common/GarrysMod/Lua/UserData.cs deleted file mode 100644 index 20367e20f..000000000 --- a/Source.Common/GarrysMod/Lua/UserData.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Source.Common.GarrysMod.Lua; - -public struct UserData; // TODO diff --git a/Source.Engine/PrEdict.cs b/Source.Engine/PrEdict.cs index 4bf8b1209..754478098 100644 --- a/Source.Engine/PrEdict.cs +++ b/Source.Engine/PrEdict.cs @@ -138,7 +138,24 @@ public class ED } private static void SpewEdicts() { - DevWarning("SpewEdicts not implemented\n"); + Dictionary mapEnts = []; + + int edictNum = 1; + for (int i = 0; i < sv.NumEdicts; ++i) { + Edict e = sv.Edicts![i]; + ++edictNum; + string className = new(e.GetClassName()); + mapEnts[className] = mapEnts.GetValueOrDefault(className) + 1; + } + + List> vecEnts = [.. mapEnts]; + vecEnts.Sort((p1, p2) => p1.Value - p2.Value); + + // todo: g_Log.Printf + Msg("Spewing edict counts:\n"); + foreach (KeyValuePair ent in vecEnts) + Msg($"({ent.Value / (float)edictNum * 100.0f:F2}%) {ent.Value}\t{ent.Key}\n"); + Msg($"Total edicts: {edictNum}\n"); } public static void ClearEdict(Edict e) { diff --git a/Source.StdShader.Gl46/UnlitTwoTexture.cs b/Source.StdShader.Gl46/UnlitTwoTexture.cs index 529510f8d..d98c7e140 100644 --- a/Source.StdShader.Gl46/UnlitTwoTexture.cs +++ b/Source.StdShader.Gl46/UnlitTwoTexture.cs @@ -105,8 +105,9 @@ protected override void OnInitShaderParams(IMaterialVar[] parms, ReadOnlySpan materialName) { @@ -115,15 +116,16 @@ protected override void OnInitShaderInstance(IMaterialVar[] parms, ReadOnlySpan< if (parms[TEXTURE2].IsDefined()) LoadTexture(TEXTURE2, (int)TextureFlags.SRGB); - if (parms[CLOAKPASSENABLED].GetIntValue() != 0) - throw new NotImplementedException(); + if (parms[CLOAKPASSENABLED].GetIntValue() != 0) { + // todo: InitCloakBlendedPass + } } protected override void OnDrawElements(IMaterialVar[] parms, IShaderDynamicAPI shaderAPI, VertexCompressionType vertexCompression) { - if (parms[CLOAKPASSENABLED].GetIntValue() != 0) - throw new NotImplementedException(); - bool drawStandardPass = true; + if (parms[CLOAKPASSENABLED].GetIntValue() != 0) { + // todo: CloakBlendedPassIsFullyOpaque + } bool newFlashlightPath = r_flashlight_version2.GetInt() != 0; if (drawStandardPass && (ShaderShadow == null) && (ShaderAPI != null) && From 1384ea57148de6e04154c983d7f022c25190c100 Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Sun, 1 Nov 2026 16:59:12 +0000 Subject: [PATCH 11/16] Dupes --- Game.Shared/GarrysMod/LuaEngine.cs | 75 +++++++++++++++++++++ Game.Shared/GarrysMod/LuaGlobalFunctions.cs | 11 +++ Game.Shared/GarrysMod/LuaUtil.cs | 2 - Source.Engine/Client/ClientState.cs | 4 ++ 4 files changed, 90 insertions(+), 2 deletions(-) diff --git a/Game.Shared/GarrysMod/LuaEngine.cs b/Game.Shared/GarrysMod/LuaEngine.cs index 7c6de70f2..ccefc9427 100644 --- a/Game.Shared/GarrysMod/LuaEngine.cs +++ b/Game.Shared/GarrysMod/LuaEngine.cs @@ -2,6 +2,7 @@ using Source.Common.Formats.BSP; using Source.Common.GarrysMod; using Source.Common.GarrysMod.Lua; +using Source.Common.Hashing; #if CLIENT_DLL namespace Game.Client.GarrysMod; @@ -215,5 +216,79 @@ static int LightStyle(ILuaInterface lua) { return 0; } #endif + +#if CLIENT_DLL + [LuaFunction] + static int WriteDupe(ILuaInterface lua) { + ReadOnlySpan data = lua.GetStringBytes(1); + ReadOnlySpan jpeg = lua.GetStringBytes(2); + string name = MD5Value.Compute(data).ToString(); + + byte[] buffer = new byte[4 + data.Length]; + buffer[0] = (byte)'D'; + buffer[1] = (byte)'U'; + buffer[2] = (byte)'P'; + buffer[3] = (byte)'3'; + data.CopyTo(buffer.AsSpan(4)); + + string path = $"dupes/{name}.dupe"; + if (!filesystem.FileExists(path, "MOD")) + filesystem.WriteFile(path, "MOD", buffer); + + filesystem.WriteFile($"dupes/{name}.jpg", "MOD", jpeg); + lua.PushBool(true); + return 1; + } + + [LuaFunction] + static int OpenDupe(ILuaInterface lua) { + string path = new(lua.CheckString(1)); + if (!GMOD.IsValidPath(path)) + return 0; + + if (path.Length > 13 && strncmp(path, "content/4000/", 13) == 0) { + int slash = path.IndexOf('/', 13); + if (slash != -1 && ulong.TryParse(path.AsSpan(13, slash - 13), out ulong workshopID) && workshopID != 0) + path = filesystem.Addons().GetAddonFilepath(workshopID, false); + } + + long size = filesystem.Size(path, "MOD"); + if (size < 4) + return 0; + + byte[] file = new byte[size]; + if (!filesystem.ReadFile(path, "MOD", file, 0)) + return 0; + + if (file[0] != 'D' || file[1] != 'U' || file[2] != 'P') + return 0; + + int headerSize; + if (file[3] == '1') + headerSize = 16; + else if (file[3] == '3') + headerSize = 4; + else + return 0; + + int dataSize = (int)size - headerSize; + if ((uint)(dataSize - 1) > 2097151) { + Warning($"Dupe Filesize is crazy ({dataSize})\n"); + return 0; + } + + lua.PushString(file.AsSpan(headerSize, dataSize)); + LuaObject data = new(); + data.SetFromStack(-1); + lua.Pop(1); + + LuaTable table = new(null, 0); + table.SetMember("data", data); + table.Push(); + table.UnReference(); + data.UnReference(); + return 1; + } +#endif } #endif diff --git a/Game.Shared/GarrysMod/LuaGlobalFunctions.cs b/Game.Shared/GarrysMod/LuaGlobalFunctions.cs index 58ceb8ce2..2a624aa9f 100644 --- a/Game.Shared/GarrysMod/LuaGlobalFunctions.cs +++ b/Game.Shared/GarrysMod/LuaGlobalFunctions.cs @@ -273,6 +273,17 @@ static void require(string name) { #if GAME_DLL static ConVar? hostnameCvar; + + static readonly ConVarRef sv_logecho = new("sv_logecho"); + + [LuaGlobal] + static int ServerLog(ILuaInterface lua) { + if (sv_logecho.GetInt() == 0) + return 0; + Dbg.Msg($"ServerLog: {lua.GetString(1)}"); + engine.LogPrint(lua.GetString(1)); + return 0; + } #endif #if !MENU_DLL diff --git a/Game.Shared/GarrysMod/LuaUtil.cs b/Game.Shared/GarrysMod/LuaUtil.cs index 1440fe531..1cb9936f5 100644 --- a/Game.Shared/GarrysMod/LuaUtil.cs +++ b/Game.Shared/GarrysMod/LuaUtil.cs @@ -553,7 +553,6 @@ static TraceFilterLua TraceFilterFromTable(LuaObject data) { return filter; } #endif -#if CLIENT_DLL || MENU_DLL static void TableToKeyValues(ILuaInterface lua, ILuaObject table, System.Text.StringBuilder output, string name, int depth) { output.Append('\t', depth).Append('"').Append(name).Append("\"\n"); output.Append('\t', depth).Append("{\n"); @@ -904,7 +903,6 @@ static int JSONToTable(ILuaInterface lua) { } return 1; } -#endif #if GAME_DLL [LuaFunction] diff --git a/Source.Engine/Client/ClientState.cs b/Source.Engine/Client/ClientState.cs index 60e42ba37..a5a27d20c 100644 --- a/Source.Engine/Client/ClientState.cs +++ b/Source.Engine/Client/ClientState.cs @@ -559,7 +559,11 @@ public override bool SetSignonState(SignOnState state, int count) { CL.FullyConnected(); if (NetChannel != null) { NetChannel.SetTimeout(cl_timeout.GetDouble()); +#if GMOD_DLL + NetChannel.SetMaxBufferSize(true, Protocol.MAX_PAYLOAD); +#else NetChannel.SetMaxBufferSize(true, Protocol.MAX_DATAGRAM_PAYLOAD); +#endif } HostState.OnClientConnected(); From 14308dd2f77f519f2b51ca718e6c51bef4162a78 Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Tue, 3 Nov 2026 13:43:14 +0000 Subject: [PATCH 12/16] file library changes --- Game.Shared/GarrysMod/LuaFileLibrary.cs | 56 +++++++++++++------------ 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/Game.Shared/GarrysMod/LuaFileLibrary.cs b/Game.Shared/GarrysMod/LuaFileLibrary.cs index b05ea17c5..ea086c32e 100644 --- a/Game.Shared/GarrysMod/LuaFileLibrary.cs +++ b/Game.Shared/GarrysMod/LuaFileLibrary.cs @@ -98,41 +98,43 @@ public static bool IsFileAccessAllowed(string file, string path, bool write, boo if (str.Length > 0x103) return false; + bool isData = path == "DATA"; if (write) { - if (path != "DATA") + if (!isData) return false; if (!anyExtension && Array.IndexOf(AllowedWriteExtensions, ext) == -1) return false; } + else if (!isData) { + if (Bootil.String.Test.Contains(str, "cache/chromium", true)) + return false; + if (Bootil.String.Test.Contains(str, "ChromiumCache", true)) + return false; + if (Bootil.String.Test.Contains(str, "GameConfig.txt", true)) + return false; + if (Bootil.String.Test.Contains(str, "srcds_addons.txt", true)) + return false; + if (Bootil.String.Test.Contains(str, "CmdSeq.wc", true)) + return false; + if (Bootil.String.Test.Contains(str, "debug_dump.txt", true)) + return false; - if (Bootil.String.Test.Contains(str, "cache/chromium", true)) - return false; - if (Bootil.String.Test.Contains(str, "ChromiumCache", true)) - return false; - if (Bootil.String.Test.Contains(str, "GameConfig.txt", true)) - return false; - if (Bootil.String.Test.Contains(str, "srcds_addons.txt", true)) - return false; - if (Bootil.String.Test.Contains(str, "CmdSeq.wc", true)) - return false; - if (Bootil.String.Test.Contains(str, "debug_dump.txt", true)) - return false; - - if (ext == "vdf" - || Bootil.String.Test.Contains(str, "mount.cfg", true) - || Bootil.String.Test.Contains(str, "server.cfg", true) - || Bootil.String.Test.Contains(str, "sourcetv.cfg", true) - || Bootil.String.Test.Contains(str, "listenserver.cfg", true) - || Bootil.String.Test.Contains(str, "autoexec.cfg", true) - || Bootil.String.Test.Contains(str, "config.cfg", true)) - return false; + if (ext == "vdf" + || Bootil.String.Test.Contains(str, "mount.cfg", true) + || Bootil.String.Test.Contains(str, "server.cfg", true) + || Bootil.String.Test.Contains(str, "sourcetv.cfg", true) + || Bootil.String.Test.Contains(str, "listenserver.cfg", true) + || Bootil.String.Test.Contains(str, "autoexec.cfg", true) + || Bootil.String.Test.Contains(str, "config.cfg", true)) + return false; - if (ext is "dmp" or "mdmp" or "db" or "log") - return false; + if (ext is "dmp" or "mdmp" or "db" or "log") + return false; - if (ext == "txt" && Bootil.String.Test.Contains(str, "crashes/", true)) - return false; + if (Bootil.String.Test.Contains(str, "crashes/", true)) + return false; + } if (!Bootil.String.Test.ContainsOnly(str, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890-_ ./*!@#$%^&()=+;',~`[]{}")) return false; @@ -849,7 +851,7 @@ static int Find(ILuaInterface lua) { else { ReadOnlySpan name = filesystem.FindFirstEx(wildcard, path, out ulong handle); while (!name.IsEmpty) { - if (name[0] != '.') { + if (name[0] != '.' && stricmp(name, "/") != 0) { FileInfo info = new() { Name = name.ToString() }; if (g_SortMethod == "datedesc" || g_SortMethod == "dateasc") info.Time = (long)(filesystem.GetFileTime(basePath + info.Name, path) - DateTime.UnixEpoch).TotalSeconds; From 9a8696fd09bb7f4282d8aad5a1ed17ad43a11bee Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Tue, 3 Nov 2026 13:46:51 +0000 Subject: [PATCH 13/16] Updated lua funcs --- Game.Client/GarrysMod/LuaRender.cs | 11 +++++++---- Game.Client/GarrysMod/LuaSurface.cs | 19 +++++++++++++------ Game.Shared/GarrysMod/LuaEntity.cs | 9 +++++++++ Game.Shared/GarrysMod/LuaNetworkedVars.cs | 14 ++++++++++---- 4 files changed, 39 insertions(+), 14 deletions(-) diff --git a/Game.Client/GarrysMod/LuaRender.cs b/Game.Client/GarrysMod/LuaRender.cs index 83ec1ad07..2b9ee8e0d 100644 --- a/Game.Client/GarrysMod/LuaRender.cs +++ b/Game.Client/GarrysMod/LuaRender.cs @@ -212,10 +212,13 @@ static int SetModelLighting(ILuaInterface lua) { [LuaFunction] static int UpdateScreenEffectTexture(ILuaInterface lua) { - int textureIndex = 0; - if (lua.GetType(1) == LuaType.Number) - textureIndex = (int)lua.GetNumber(1); - ViewScene.UpdateScreenEffectTexture(textureIndex, 0, 0, ScreenWidth(), ScreenHeight(), false); + uint textureIndex = 0; + if (lua.GetType(1) == LuaType.Number) { + textureIndex = (uint)lua.CheckNumber(1); + if (textureIndex > 4) + return 0; + } + ViewScene.UpdateScreenEffectTexture((int)textureIndex, 0, 0, ScreenWidth(), ScreenHeight(), false); return 0; } #endif diff --git a/Game.Client/GarrysMod/LuaSurface.cs b/Game.Client/GarrysMod/LuaSurface.cs index f9f1aabbc..93deadc59 100644 --- a/Game.Client/GarrysMod/LuaSurface.cs +++ b/Game.Client/GarrysMod/LuaSurface.cs @@ -388,7 +388,10 @@ static int DrawPoly(ILuaInterface lua) { vertex.UnReference(); } - surface.DrawTexturedPolygon(((Span)PolyVerts)[..count], true); + if (count < 3) + lua.ArgError(1, "table must contain at least three vertices"); + else + surface.DrawTexturedPolygon(((Span)PolyVerts)[..count], true); vertices.UnReference(); return 0; } @@ -414,12 +417,16 @@ static int DrawCircle(ILuaInterface lua) { } [LuaFunction] static int DrawTexturedRectUV(ILuaInterface lua) { - int x = (int)lua.GetNumber(1); - int y = (int)lua.GetNumber(2); - int w = (int)lua.GetNumber(3); - int h = (int)lua.GetNumber(4); + int x = (int)lua.CheckNumber(1); + int y = (int)lua.CheckNumber(2); + int w = (int)lua.CheckNumber(3); + int h = (int)lua.CheckNumber(4); // TODO: poster cmd split scaling (?) - surface.DrawTexturedSubRect(x, y, w + x, h + y, (float)lua.GetNumber(5), (float)lua.GetNumber(6), (float)lua.GetNumber(7), (float)lua.GetNumber(8)); + float v1 = (float)lua.CheckNumber(8); + float u1 = (float)lua.CheckNumber(7); + float v0 = (float)lua.CheckNumber(6); + float u0 = (float)lua.CheckNumber(5); + surface.DrawTexturedSubRect(x, y, w + x, h + y, u0, v0, u1, v1); return 0; } diff --git a/Game.Shared/GarrysMod/LuaEntity.cs b/Game.Shared/GarrysMod/LuaEntity.cs index 41285e21a..d6b1df1c5 100644 --- a/Game.Shared/GarrysMod/LuaEntity.cs +++ b/Game.Shared/GarrysMod/LuaEntity.cs @@ -1315,6 +1315,11 @@ static int Entity__Activate(ILuaInterface lua) { static int Entity__SetPos(ILuaInterface lua) { BaseEntity ent = Get_Entity(1, false)!; ref Vector3 pos = ref LuaVector.Get_Vector(2); +#if GAME_DLL + bool touchTriggers = false; + if (lua.GetType(3) != LuaType.Nil) + touchTriggers = lua.GetBool(3); +#endif 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($"{ent.GetClassname()}[{ent.EntIndex()}]:SetPos( {pos.X:F6} {pos.Y:F6} {pos.Z:F6} ): Ignoring unreasonable position.\n"); @@ -1322,6 +1327,10 @@ static int Entity__SetPos(ILuaInterface lua) { } ent.SetAbsOrigin(pos); +#if GAME_DLL + if (touchTriggers && !IsQueuedForRemoval(ent) && !ent.IsMarkedForDeletion()) + ent.PhysicsTouchTriggers(); +#endif if (ent.GetMoveType() == MoveType.None || ent.VPhysicsGetObject() == null || ent.IsRagdollProp() || ent.IsJeep()) return 0; #if CLIENT_DLL diff --git a/Game.Shared/GarrysMod/LuaNetworkedVars.cs b/Game.Shared/GarrysMod/LuaNetworkedVars.cs index cd0056156..1ddf482d2 100644 --- a/Game.Shared/GarrysMod/LuaNetworkedVars.cs +++ b/Game.Shared/GarrysMod/LuaNetworkedVars.cs @@ -155,15 +155,15 @@ static void TruncateString(ILuaObject value) { g_Lua.Pop(1); } - void CallProxy(LuaNetworkedVar var, uint handle, string name, ILuaObject value) { + void CallProxy(LuaNetworkedVar var, uint handle, string name, ILuaObject oldValue) { if (g_Lua == null || !var.Proxy.isFunction()) return; var.Proxy.Push(); LuaEntity.Push_Entity(EntityFromHandle(handle)); g_Lua.PushString(name); + oldValue.Push(); var.Value.Push(); - value.Push(); g_Lua.CallInternalNoReturns(4); } @@ -174,11 +174,16 @@ public void SetNetworkedVar(uint handle, string name, ILuaObject value) { return; } + LuaObject oldValue = new(); + oldValue.Set(var.Value); + TruncateString(value); #if GAME_DLL - if (g_Lua!.IsEqual(var.Value, value)) + if (g_Lua!.IsEqual(var.Value, value)) { + oldValue.UnReference(); return; + } var.Value.Set(value); var.NextUpdate = 0; @@ -189,7 +194,8 @@ public void SetNetworkedVar(uint handle, string name, ILuaObject value) { var.Time = (float)gpGlobals.CurTime; #endif - CallProxy(var, handle, name, value); + CallProxy(var, handle, name, oldValue); + oldValue.UnReference(); } public void SetNetworkedVarProxy(uint handle, string name, ILuaObject func) { From 4be9e491f2d9358ad2b3d653a2f0011b235dafb7 Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Tue, 3 Nov 2026 14:15:02 +0000 Subject: [PATCH 14/16] Fix players being deleted when killed --- Game.Server/BaseCombatCharacter.cs | 21 +++++++++++++++++++++ Game.Server/GarrysMod/GMOD_Player.cs | 6 ++++++ Game.Shared/GarrysMod/LuaPlayer.cs | 7 +++++++ 3 files changed, 34 insertions(+) diff --git a/Game.Server/BaseCombatCharacter.cs b/Game.Server/BaseCombatCharacter.cs index 5d6bc2409..ee5a9f599 100644 --- a/Game.Server/BaseCombatCharacter.cs +++ b/Game.Server/BaseCombatCharacter.cs @@ -301,6 +301,27 @@ public void ClearLastKnownArea() { // TODO } + public override void Event_Killed(in TakeDamageInfo info) { + LifeState = (int)Source.LifeState.Dying; + + // todo CalcDamageForceVector, CRagdollMagnet::FindBestMagnet, Weapon_Drop(m_hActiveWeapon, null, weaponForce) + + if (HasSpawnFlags((int)AI_BaseNPC.SF.DropHealthkit)) + Create("item_healthvial", GetAbsOrigin(), GetAbsAngles()); + + EmitSound("BaseCombatCharacter.StopWeaponSounds"); + + if (info.GetAttacker() != null) { + info.GetAttacker()!.Event_KilledOther(this, info); + g_EventQueue.AddEvent(info.GetAttacker(), "KilledNPC", 0.3f, this, this); + } + // todo SendOnKilledGameEvent(info), ShouldGib, Dissolve, BecomeRagdoll + + ClearLastKnownArea(); + + // todo TheNextBots().OnKilled(this, info) + } + public string? RelationshipString; public AI_HullType Hull; diff --git a/Game.Server/GarrysMod/GMOD_Player.cs b/Game.Server/GarrysMod/GMOD_Player.cs index 4747e07cc..d08e1cc30 100644 --- a/Game.Server/GarrysMod/GMOD_Player.cs +++ b/Game.Server/GarrysMod/GMOD_Player.cs @@ -67,6 +67,12 @@ void CallPlayerAuthed(ReadOnlySpan uniqueID) { gGM.CallNoReturns(3); } + public void Kill() { + Health = 0; + Event_Killed(new TakeDamageInfo(this, this, 0, DamageType.NeverGib)); + Event_Dying(new TakeDamageInfo(this, this, 0, DamageType.NeverGib)); + } + public ReadOnlySpan GetUniqueID() { if (gpGlobals.MaxClients == 1) { if (NeedsUniqueID) { diff --git a/Game.Shared/GarrysMod/LuaPlayer.cs b/Game.Shared/GarrysMod/LuaPlayer.cs index 25e521fd8..a28d8ef04 100644 --- a/Game.Shared/GarrysMod/LuaPlayer.cs +++ b/Game.Shared/GarrysMod/LuaPlayer.cs @@ -137,6 +137,13 @@ static int Player__OwnerSteamID64(ILuaInterface lua) { lua.PushString(engine.GMOD_GetPlayerOwnerSteamID(player.Edict()).m_SteamID.ToString()); return 1; } + + [LuaMethod] + static int Player__Kill(ILuaInterface lua) { + GMOD_Player player = (GMOD_Player)Get_Player(1, false)!; + player.Kill(); + return 0; + } #endif [LuaMethod] From ad43ff290f33225969f0f8306bed40c9a8d4e032 Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Tue, 3 Nov 2026 16:34:28 +0000 Subject: [PATCH 15/16] Fixes --- Game.Client/GarrysMod/LuaSurface.cs | 9 ++++++--- Game.Shared/GarrysMod/LuaVMatrix.cs | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Game.Client/GarrysMod/LuaSurface.cs b/Game.Client/GarrysMod/LuaSurface.cs index 93deadc59..2d5111007 100644 --- a/Game.Client/GarrysMod/LuaSurface.cs +++ b/Game.Client/GarrysMod/LuaSurface.cs @@ -388,10 +388,13 @@ static int DrawPoly(ILuaInterface lua) { vertex.UnReference(); } - if (count < 3) + if (count < 3) { + vertices.UnReference(); lua.ArgError(1, "table must contain at least three vertices"); - else - surface.DrawTexturedPolygon(((Span)PolyVerts)[..count], true); + return 0; + } + + surface.DrawTexturedPolygon(((Span)PolyVerts)[..count], true); vertices.UnReference(); return 0; } diff --git a/Game.Shared/GarrysMod/LuaVMatrix.cs b/Game.Shared/GarrysMod/LuaVMatrix.cs index f46dfee70..405ed657e 100644 --- a/Game.Shared/GarrysMod/LuaVMatrix.cs +++ b/Game.Shared/GarrysMod/LuaVMatrix.cs @@ -221,7 +221,8 @@ static int VMatrix__InvertTR(ILuaInterface lua) { [LuaMethod] static int VMatrix__InvertTR2(ILuaInterface lua) { ref Matrix4x4 m = ref Get_VMatrix(1); - MathLib.MatrixInverseTR(in m, out m); + Matrix4x4 src = m; + MathLib.MatrixInverseTR(in src, out m); return 0; } From 4a58fd39e72271d4acb60488f2e467d94e70b7e0 Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Wed, 4 Nov 2026 04:36:02 +0000 Subject: [PATCH 16/16] Fix source generators not running on linux --- Directory.Build.targets | 2 +- Source.GUI.Controls/Menu.cs | 2 +- Source.SDLManager/SDL3_Window.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Directory.Build.targets b/Directory.Build.targets index a5455a1f3..c12cd655e 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -1,7 +1,7 @@ - + diff --git a/Source.GUI.Controls/Menu.cs b/Source.GUI.Controls/Menu.cs index 7bd41b551..7ca919678 100644 --- a/Source.GUI.Controls/Menu.cs +++ b/Source.GUI.Controls/Menu.cs @@ -1022,7 +1022,7 @@ public int GetItemCount() { } public int GetMenuID(int index) { - if (SortedItems[index] == -1) + if (index < 0 || index >= SortedItems.Count) return -1; return SortedItems[index]; diff --git a/Source.SDLManager/SDL3_Window.cs b/Source.SDLManager/SDL3_Window.cs index 29222d635..ed6c0bac3 100644 --- a/Source.SDLManager/SDL3_Window.cs +++ b/Source.SDLManager/SDL3_Window.cs @@ -168,7 +168,7 @@ public void PumpMessages() { WindowEvent newEvent = new(); newEvent.EventType = pressed ? WindowEventType.MouseButtonDown : WindowEventType.MouseButtonUp; - newEvent.MousePos = new((int)(ev.motion.x * MouseXScale), (int)(ev.motion.x * MouseYScale)); + newEvent.MousePos = new((int)(ev.button.x * MouseXScale), (int)(ev.button.y * MouseYScale)); newEvent.MouseButtonFlags = MouseButtons; newEvent.MouseClickCount = (uint)(doublePress ? 2 : 1); newEvent.MouseButton = evButton;