From 692dfa6dd5a2580640967b09f0de69e776eebe5e Mon Sep 17 00:00:00 2001 From: deltamolfar <72973198+deltamolfar@users.noreply.github.com> Date: Thu, 2 Oct 2025 23:13:38 +0300 Subject: [PATCH 01/16] stable --- lua/entities/gmod_wire_customprop/cl_init.lua | 162 ++++++++++++++ lua/entities/gmod_wire_customprop/init.lua | 203 ++++++++++++++++++ lua/entities/gmod_wire_customprop/shared.lua | 133 ++++++++++++ .../core/custom/cl_prop.lua | 1 + .../core/custom/prop.lua | 106 ++++++++- 5 files changed, 604 insertions(+), 1 deletion(-) create mode 100644 lua/entities/gmod_wire_customprop/cl_init.lua create mode 100644 lua/entities/gmod_wire_customprop/init.lua create mode 100644 lua/entities/gmod_wire_customprop/shared.lua diff --git a/lua/entities/gmod_wire_customprop/cl_init.lua b/lua/entities/gmod_wire_customprop/cl_init.lua new file mode 100644 index 0000000000..1cadcf40ea --- /dev/null +++ b/lua/entities/gmod_wire_customprop/cl_init.lua @@ -0,0 +1,162 @@ +local shared = include("shared.lua") + +ENT.DefaultMaterial = Material("models/wireframe") +ENT.Material = ENT.DefaultMaterial + +local Ent_IsValid = FindMetaTable("Entity").IsValid +local Phys_IsValid = FindMetaTable("PhysObj").IsValid +local Ent_GetTable = FindMetaTable("Entity").GetTable + +function ENT:Initialize() + self.rendermesh = Mesh(self.Material) + self.meshapplied = false + self:DrawShadow(false) + self:EnableCustomCollisions( true ) + + -- local mesh + -- SF.CallOnRemove(self, "sf_prop", + -- function() mesh = self.rendermesh end, + -- function() if mesh then mesh:Destroy() end + -- end) +end + +function ENT:OnRemove() + if self.rendermesh then + self.rendermesh:Destroy() + self.rendermesh = nil + end +end + +function ENT:BuildPhysics(ent_tbl, physmesh) + ent_tbl.physmesh = physmesh + self:PhysicsInitMultiConvex(physmesh) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:EnableCustomCollisions(true) + + local phys = self:GetPhysicsObject() + if Phys_IsValid(phys) then + phys:SetMaterial(ent_tbl.GetPhysMaterial(self)) + end +end + +function ENT:BuildRenderMesh(ent_tbl, rendermesh) + local phys = self:GetPhysicsObject() + if not Phys_IsValid(phys) then return end + + local convexes = phys:GetMeshConvexes() + local rendermesh = convexes[1] + for i=2, #convexes do + for k, v in ipairs(convexes[i]) do + rendermesh[#rendermesh+1] = v + end + end + + -- less than 3 can crash + if #rendermesh < 3 then return end + + ent_tbl.rendermesh:BuildFromTriangles(rendermesh) +end + +function ENT:Think() + local physobj = self:GetPhysicsObject() + if Phys_IsValid(physobj) then + physobj:SetPos( self:GetPos() ) + physobj:SetAngles( self:GetAngles() ) + physobj:EnableMotion(false) + physobj:Sleep() + end +end + +function ENT:Draw(flags) + self:DrawModel(flags) +end + +function ENT:GetRenderMesh() + local ent_tbl = Ent_GetTable(self) + if ent_tbl.custom_mesh then + if ent_tbl.custom_mesh_data[ent_tbl.custom_mesh] then + return { Mesh = ent_tbl.custom_mesh, Material = ent_tbl.Material--[[, Matrix = ent_tbl.render_matrix]] } + else + ent_tbl.custom_mesh = nil + end + else + return { Mesh = ent_tbl.rendermesh, Material = ent_tbl.Material--[[, Matrix = ent_tbl.render_matrix]] } + end +end + +local function streamToMesh(meshdata) + local meshConvexes, posMins, posMaxs = {}, Vector(math.huge, math.huge, math.huge), Vector(-math.huge, -math.huge, -math.huge) + + local meshdata = util.Decompress(meshdata, 65536) + + local pos = 1 + local nConvexes + nConvexes, pos = shared.readInt32(meshdata, pos) + for iConvex = 1, nConvexes do + local nVertices + nVertices, pos = shared.readInt32(meshdata, pos) + local convex = {} + for iVertex = 1, nVertices do + local x, y, z + x, pos = shared.readFloat(meshdata, pos) + y, pos = shared.readFloat(meshdata, pos) + z, pos = shared.readFloat(meshdata, pos) + if x > posMaxs.x then posMaxs.x = x end + if y > posMaxs.y then posMaxs.y = y end + if z > posMaxs.z then posMaxs.z = z end + if x < posMins.x then posMins.x = x end + if y < posMins.y then posMins.y = y end + if z < posMins.z then posMins.z = z end + convex[iVertex] = Vector(x, y, z) + end + meshConvexes[iConvex] = convex + end + + return meshConvexes, posMins, posMaxs +end + +net.Receive(shared.classname, function() + local receivedEntity, receivedData + + local function tryApplyData() + if not receivedEntity or not receivedData then return end + + if Ent_IsValid(receivedEntity) and receivedEntity:GetClass()~=shared.classname then return end + local ent_tbl = Ent_GetTable(receivedEntity) + if not (ent_tbl and ent_tbl.rendermesh:IsValid() and receivedData and not ent_tbl.meshapplied) then return end + + ent_tbl.meshapplied = true + + local physmesh, mins, maxs = streamToMesh(receivedData) + ent_tbl.BuildPhysics(receivedEntity, ent_tbl, physmesh) + ent_tbl.BuildRenderMesh(receivedEntity, ent_tbl) + receivedEntity:SetRenderBounds(mins, maxs) + receivedEntity:SetCollisionBounds(mins, maxs) + end + + shared.readReliableEntity(function(self) + receivedEntity = self + tryApplyData() + end) + + net.ReadStream(nil, function(data) + receivedData = data + tryApplyData() + end) +end) + +hook.Add("NetworkEntityCreated", shared.classname.."physics", function(ent) + local ent_tbl = Ent_GetTable(ent) + local mesh = ent_tbl.physmesh + if mesh and not Phys_IsValid(ent:GetPhysicsObject()) then + ent_tbl.BuildPhysics(ent, ent_tbl, mesh) + end +end) + +function ENT:OnPhysMaterialChanged(name, old, new) + local phys = self:GetPhysicsObject() + if Phys_IsValid(phys) then + phys:SetMaterial(new) + end +end diff --git a/lua/entities/gmod_wire_customprop/init.lua b/lua/entities/gmod_wire_customprop/init.lua new file mode 100644 index 0000000000..3ce4b97b0e --- /dev/null +++ b/lua/entities/gmod_wire_customprop/init.lua @@ -0,0 +1,203 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +local shared = include("shared.lua") + +util.AddNetworkString(shared.classname) + +local ENT_META = FindMetaTable("Entity") +local Ent_GetTable = ENT_META.GetTable + +local wire_customprops_hullsize_max = CreateConVar("wire_customprops_hullsize_max", 2048, FCVAR_ARCHIVE, "The max hull size of a custom prop") +local wire_customprops_minvertexdistance = CreateConVar("wire_customprops_minvertexdistance", 0.2, FCVAR_ARCHIVE, "The min distance between two vertices in a custom prop") +local wire_customprops_vertices_max = CreateConVar("wire_customprops_vertices_max", 512, FCVAR_ARCHIVE, "How many vertices custom props can have.", 4, 1024) +local wire_customprops_convexes_max = CreateConVar("wire_customprops_convexes_max", 16, FCVAR_ARCHIVE, "How many convexes custom props can have.", 1, 64) + +function ENT:Initialize() + self.BaseClass.Initialize(self) + + self:PhysicsInitMultiConvex(self.physmesh) self.physmesh = nil + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:EnableCustomCollisions(true) + self:DrawShadow(false) + + self.customForceMode = 0 + self.customForceLinear = Vector() + self.customForceAngular = Vector() + self.customShadowForce = { + pos = Vector(), + angle = Angle(), + secondstoarrive = 1, + dampfactor = 0.2, + maxangular = 1000, + maxangulardamp = 1000, + maxspeed = 1000, + maxspeeddamp = 1000, + teleportdistance = 1000, + } + + self:AddEFlags( EFL_FORCE_CHECK_TRANSMIT ) +end + +function ENT:EnableCustomPhysics(mode) + local ent_tbl = Ent_GetTable(self) + if mode then + ent_tbl.customPhysicsMode = mode + if not ent_tbl.hasMotionController then + self:StartMotionController() + ent_tbl.hasMotionController = true + end + else + ent_tbl.customPhysicsMode = nil + if ent_tbl.hasMotionController then + self:StopMotionController() + ent_tbl.hasMotionController = false + end + end +end + +function ENT:PhysicsSimulate(physObj, dt) + local ent_tbl = Ent_GetTable(self) + local mode = ent_tbl.customPhysicsMode + if mode == 1 then + return ent_tbl.customForceAngular, ent_tbl.customForceLinear, ent_tbl.customForceMode + elseif mode == 2 then + ent_tbl.customShadowForce.deltatime = dt + physObj:ComputeShadowControl(ent_tbl.customShadowForce) + return SIM_NOTHING + else + return SIM_NOTHING + end +end + +function ENT:UpdateTransmitState() + return TRANSMIT_ALWAYS +end + +function ENT:TransmitData(recip) + net.Start(shared.classname) + shared.writeReliableEntity(self) + local stream = net.WriteStream(self.wiremeshdata, nil, true) + if recip then net.Send(recip) else net.Broadcast() end + return stream +end + +hook.Add("PlayerInitialSpawn","CustomProp_SpawnFunc",function(ply) + for k, v in ipairs(ents.FindByClass(shared.classname)) do + v:TransmitData(ply) + end +end) + +local function streamToMesh(meshdata) + local maxConvexesPerProp = maxConvexesPerProp:GetInt() + local maxVerticesPerConvex = maxVerticesPerConvex:GetInt() + + local meshConvexes = {} + local data = util.Decompress(meshdata, 65536) + local pos = 1 + local nConvexes + nConvexes, pos = string.unpack("I4", data, pos) + assert(nConvexes <= maxConvexesPerProp, "Exceeded the max convexes per prop (max: " .. maxConvexesPerProp .. ", got: " .. nConvexes .. ")") + for iConvex = 1, nConvexes do + local nVertices + nVertices, pos = string.unpack("I4", data, pos) + assert(nVertices <= maxVerticesPerConvex, "Exceeded the max vertices per convex (max: " .. maxVerticesPerConvex .. ", got: " .. nVertices .. ")") + local convex = {} + for iVertex = 1, nVertices do + local x, y, z + x, y, z, pos = string.unpack("fff", data, pos) + convex[iVertex] = Vector(x, y, z) + end + meshConvexes[iConvex] = convex + end + return meshConvexes +end + +local function meshToStream(meshConvexes) + local buffer = {} + + buffer[#buffer+1] = shared.writeInt32(#meshConvexes) + for _, convex in ipairs(meshConvexes) do + buffer[#buffer+1] = shared.writeInt32(#convex) + for _, vertex in ipairs(convex) do + buffer[#buffer+1] = shared.writeFloat(vertex[1]) + buffer[#buffer+1] = shared.writeFloat(vertex[2]) + buffer[#buffer+1] = shared.writeFloat(vertex[3]) + end + end + + return util.Compress(table.concat(buffer)) +end + +local function checkMesh(ply, meshConvexes) + local maxHullSize = wire_customprops_hullsize_max:GetFloat() + local mindist = wire_customprops_minvertexdistance:GetFloat() + local maxConvexesPerProp = wire_customprops_convexes_max:GetInt() + local maxVerticesPerConvex = wire_customprops_vertices_max:GetInt() + + assert(#meshConvexes > 0, "Invalid number of convexes (" .. #meshConvexes .. ")") + assert(#meshConvexes <= maxConvexesPerProp, "Exceeded the max convexes per prop (max: " .. maxConvexesPerProp .. ", got: ".. #meshConvexes .. ")") + + for _, convex in ipairs(meshConvexes) do + assert(#convex <= maxVerticesPerConvex, "Exceeded the max vertices per convex (max: " .. maxVerticesPerConvex .. ", got: " .. #convex .. ")") + assert(#convex > 4, "Invalid number of vertices (" .. #convex .. ")") + + for k, vertex in ipairs(convex) do + assert(math.abs(vertex[1]) < maxHullSize and math.abs(vertex[2]) < maxHullSize and math.abs(vertex[3]) < maxHullSize, "The custom prop cannot exceed a hull size of " .. maxHullSize) + assert(vertex[1] == vertex[1] and vertex[2] == vertex[2] and vertex[3] == vertex[3], "Your mesh contains nan values!") + for i = 1, k - 1 do + assert(convex[i]:DistToSqr(vertex) >= mindist, "No two vertices can have a distance less than " .. math.sqrt(mindist)) + end + end + end +end + +function WireLib.createCustomProp(ply, pos, ang, wiremeshdata) + local meshConvexes, meshStream + + if isstring(wiremeshdata) then + meshConvexes = streamToMesh(wiremeshdata) + meshStream = wiremeshdata + elseif istable(wiremeshdata) then + meshConvexes = wiremeshdata + meshStream = meshToStream(wiremeshdata) + else + assert(false, "Invalid meshdata") + end + + checkMesh(self, meshConvexes) + + local propent = ents.Create(shared.classname) + propent.physmesh = meshConvexes + + propent.wiremeshdata = meshStream + propent:Spawn() + + local physobj = propent:GetPhysicsObject() + if not physobj:IsValid() then + propent:Remove() + assert(false, "Custom prop has invalid physics!") + end + + propent:SetOwner(ply) + propent:SetPos(pos) + propent:SetAngles(ang) + -- Transmit after a short delay so the client has a chance to create the entity + -- and register its net receivers (prevents first-send race). + timer.Simple(0, function() + if IsValid(propent) then propent:TransmitData() end + end) + + physobj:EnableCollisions(true) + physobj:EnableDrag(true) + physobj:Wake() + + local totalVertices = 0 + for k, v in ipairs(meshConvexes) do + totalVertices = totalVertices + #v + end + + return propent +end + +duplicator.RegisterEntityClass(shared.classname, createCustomProp, "Pos", "Ang", "wiremeshdata") diff --git a/lua/entities/gmod_wire_customprop/shared.lua b/lua/entities/gmod_wire_customprop/shared.lua new file mode 100644 index 0000000000..d75ee6a7f9 --- /dev/null +++ b/lua/entities/gmod_wire_customprop/shared.lua @@ -0,0 +1,133 @@ +ENT.Type = "anim" +ENT.Base = "base_anim" + +ENT.PrintName = "Wiremod Custom Prop" +ENT.Author = "Sparky & DeltaMolfar" + +ENT.Spawnable = false +ENT.AdminSpawnable = false + +function ENT:SetupDataTables() + self:NetworkVar("String", 0, "PhysMaterial") + + if CLIENT then + self:NetworkVarNotify("PhysMaterial", self.OnPhysMaterialChanged) + end +end + +local Ent_IsValid = FindMetaTable("Entity").IsValid +local Ent_GetTable = FindMetaTable("Entity").GetTable +return { + classname = "gmod_wire_customprop", + + readReliableEntity = function(callback) + index = net.ReadUInt(16) + creationIndex = net.ReadUInt(32) + local startTime = CurTime() + + local function check() + local ent = Entity(index) + if Ent_IsValid(ent) and ent:GetCreationID() == creationIndex and Ent_GetTable(ent).BuildPhysics ~= nil then + ProtectedCall(callback, ent) + return + end + + if CurTime() - startTime < 10 then + timer.Simple(0.01, check) + else + ProtectedCall(callback, nil) + end + end + + check() + end, + + writeReliableEntity = function(ent) + net.WriteUInt(ent:EntIndex(), 16) + net.WriteUInt(ent:GetCreationID(), 32) + end, + + writeInt32 = function(n) + return string.char( + bit.band(n, 0xFF), + bit.band(bit.rshift(n, 8), 0xFF), + bit.band(bit.rshift(n, 16), 0xFF), + bit.band(bit.rshift(n, 24), 0xFF) + ) + end, + + readInt32 = function(data, pos) + local b1 = string.byte(data, pos) + local b2 = string.byte(data, pos + 1) + local b3 = string.byte(data, pos + 2) + local b4 = string.byte(data, pos + 3) + local n = b1 + b2 * 256 + b3 * 65536 + b4 * 16777216 + return n, pos + 4 + end, + + writeFloat = function(f) + -- Handles special cases + if f == 0 then + return string.char(0, 0, 0, 0) + elseif f ~= f then -- NaN + return string.char(0, 0, 192, 127) + elseif f == math.huge then + return string.char(0, 0, 128, 127) + elseif f == -math.huge then + return string.char(0, 0, 128, 255) + end + + local sign = 0 + if f < 0 then sign = 0x80000000; f = -f end + + local mantissa, exponent = math.frexp(f) + exponent = exponent + 126 + + if exponent <= 0 then + mantissa = mantissa * math.ldexp(0.5, exponent) + exponent = 0 + elseif exponent >= 255 then + mantissa = 0 + exponent = 255 + else + mantissa = (mantissa * 2 - 1) * 0x800000 + end + + local bits = sign + bit.lshift(exponent, 23) + math.floor(mantissa + 0.5) + return string.char( + bit.band(bits, 0xFF), + bit.band(bit.rshift(bits, 8), 0xFF), + bit.band(bit.rshift(bits, 16), 0xFF), + bit.band(bit.rshift(bits, 24), 0xFF) + ) + end, + + readFloat = function(data, pos) + local b1 = string.byte(data, pos) + local b2 = string.byte(data, pos + 1) + local b3 = string.byte(data, pos + 2) + local b4 = string.byte(data, pos + 3) + local bits = b1 + b2 * 256 + b3 * 65536 + b4 * 16777216 + + local sign = bit.band(bits, 0x80000000) ~= 0 and -1 or 1 + local exponent = bit.band(bit.rshift(bits, 23), 0xFF) + local mantissa = bit.band(bits, 0x7FFFFF) + + if exponent == 255 then + if mantissa == 0 then + return sign * math.huge, pos + 4 + else + return 0/0, pos + 4 -- NaN + end + elseif exponent == 0 then + if mantissa == 0 then + return 0, pos + 4 + else + return sign * math.ldexp(mantissa / 0x800000, -126), pos + 4 + end + end + + f = sign * math.ldexp(1 + mantissa / 0x800000, exponent - 127) + return f, pos + 4 + end +} \ No newline at end of file diff --git a/lua/entities/gmod_wire_expression2/core/custom/cl_prop.lua b/lua/entities/gmod_wire_expression2/core/custom/cl_prop.lua index 59e52016fd..cae3c86b83 100644 --- a/lua/entities/gmod_wire_expression2/core/custom/cl_prop.lua +++ b/lua/entities/gmod_wire_expression2/core/custom/cl_prop.lua @@ -36,6 +36,7 @@ E2Helper.Descriptions["sentIsEnabled()"] = "Returns 1 if server allows spawning E2Helper.Descriptions["seatSpawn(sn)"] = "Model path, Frozen Spawns a prop with the model denoted by the string filepath. If frozen is 0, then it will spawn unfrozen." E2Helper.Descriptions["seatSpawn(svan)"] = E2Helper.Descriptions["seatSpawn(sn)"] E2Helper.Descriptions["seatSpawn(svans)"] = E2Helper.Descriptions["seatSpawn(sn)"] .. " String seatType, determines what animations the seat will have. For example phx_seat2 and phx_seat3 will have Jeep and Airboat animations." +E2Helper.Descriptions["customPropSpawn(t)"] = "Spawns a custom prop with the given table of format {1 = {}}" E2Helper.Descriptions["propSpawnEffect(n)"] = "Set to 1 to enable prop spawn effect, 0 to disable." E2Helper.Descriptions["propDelete(e:)"] = "Deletes the specified prop." E2Helper.Descriptions["propDelete(t:)"] = "Deletes all the props in the given table, returns the amount of props deleted." diff --git a/lua/entities/gmod_wire_expression2/core/custom/prop.lua b/lua/entities/gmod_wire_expression2/core/custom/prop.lua index f0e0146dbd..8de98be10d 100644 --- a/lua/entities/gmod_wire_expression2/core/custom/prop.lua +++ b/lua/entities/gmod_wire_expression2/core/custom/prop.lua @@ -12,6 +12,9 @@ local sbox_E2_canMakeStatue = CreateConVar("sbox_E2_canMakeStatue", "1", FCVAR_A local wire_expression2_propcore_sents_whitelist = CreateConVar("wire_expression2_propcore_sents_whitelist", 1, FCVAR_ARCHIVE, "If 1 - players can spawn sents only from the default sent list. If 0 - players can spawn sents from both the registered list and the entity tab.", 0, 1) local wire_expression2_propcore_sents_enabled = CreateConVar("wire_expression2_propcore_sents_enabled", 1, FCVAR_ARCHIVE, "If 1 - this allows sents to be spawned. (Doesn't affect the sentSpawn whitelist). If 0 - prevents sentSpawn from being used at all.", 0, 1) local wire_expression2_propcore_canMakeUnbreakable = CreateConVar("wire_expression2_propcore_canMakeUnbreakable", 1, FCVAR_ARCHIVE, "If 1 - this allows props to be made unbreakable. If 0 - prevents propMakeBreakable from being used at all.", 0, 1) +local wire_expression2_propcore_customprops_enabled = CreateConVar("wire_expression2_propcore_customprops_enabled", 1, FCVAR_ARCHIVE, "If 1 - this allows custom props to be spawned. If 0 - prevents customPropSpawn from being used at all.", 0, 1) +local wire_expression2_propcore_customprops_delay = CreateConVar("wire_expression2_propcore_customprops_delay", 2, FCVAR_ARCHIVE, "How many seconds to wait after spawning a custom prop before allowing to spawn another one. (Too low may allow lag abuse)", 0) +local wire_expression2_propcore_customprops_max = CreateConVar("wire_expression2_propcore_customprops_max", 10, FCVAR_ARCHIVE, "The maximum number of custom props a player can spawn.") local isOwner = E2Lib.isOwner local GetBones = E2Lib.GetBones @@ -340,7 +343,7 @@ function PropCore.CreateSent(self, class, pos, angles, freeze, data) entity = stored_sent.t.SpawnFunction(stored_sent.t, self.player, mockTrace, class) else - entity = ents.Create( class ) + entity = ents.Create(class) if IsValid(entity) then entity:SetPos(pos) entity:SetAngles(angles) @@ -398,6 +401,62 @@ end local CreateSent = PropCore.CreateSent +local function spawnCustomProp(self, convexes, pos, ang, freeze) + if self.player.customPropsSpawned and self.player.customPropsSpawned >= wire_expression2_propcore_customprops_max:GetInt() then + return self:throw("You have reached the maximum number of custom props you can spawn! (" .. wire_expression2_propcore_customprops_max:GetInt() .. ")", nil) + end + + if CurTime() < (self.player.customPropLastSpawn or 0) + wire_expression2_propcore_customprops_delay:GetFloat() then + self:throw("You can't spawn a custom prop just yet! (Use customPropCanSpawn or customPropCanSpawnIn to check)", nil) + end + + if wire_expression2_propcore_customprops_enabled:GetBool() == false then + self:throw("Custom prop spawning is disabled by server! (wire_expression2_propcore_customprops_enabled 0)", nil) + end + if not ValidAction(self, nil, "spawn") then return NULL end + + convexes = castE2ValueToLuaValue(TYPE_TABLE, convexes) + PrintTable(convexes) + local success, entity = pcall(WireLib.createCustomProp, self.player, pos, ang, convexes) + + if not success then + -- Remove file/line info from error string + local msg = tostring(entity)--:gsub("^[^:]+:%d+:%s*", "") + self:throw("Failed to spawn custom prop! " .. msg, nil) + end + + local phys = entity:GetPhysicsObject() + -- if IsValid( phys ) then + -- phys:EnableMotion( freeze == 0 ) + -- phys:Wake() + -- end + + self.player:AddCleanup("gmod_wire_customprop", entity) + + if self.data.propSpawnUndo then + undo.Create("gmod_wire_customprop") + undo.AddEntity(entity) + undo.SetPlayer(self.player) + undo.Finish("E2 Custom Prop") + end + + entity:CallOnRemove("wire_expression2_propcore_remove", + function(entity) + self.data.spawnedProps[entity] = nil + + if IsValid(self.player) then + self.player.customPropsSpawned = (self.player.customPropsSpawned or 1) - 1 + end + end + ) + + self.player.customPropLastSpawn = CurTime() + self.data.spawnedProps[entity] = self.data.propSpawnUndo + self.player.customPropsSpawned = (self.player.customPropsSpawned or 0) + 1 + + return entity +end + -------------------------------------------------------------------------------- __e2setcost(40) e2function entity propSpawn(string model, number frozen) @@ -695,6 +754,51 @@ end -------------------------------------------------------------------------------- +__e2setcost(150) +e2function entity customPropSpawn(table convexes) + return spawnCustomProp(self, convexes, self.entity:GetPos() + self.entity:GetUp() * 25, self.entity:GetAngles(), 1) +end + +e2function entity customPropSpawn(table convexes, vector pos, angle ang, number frozen) + return spawnCustomProp(self, convexes, Vector(pos[1], pos[2], pos[3]), Angle(ang[1], ang[2], ang[3]), frozen) +end + +-------------------------------------------------------------------------------- + +__e2setcost(5) +[nodiscard] +e2function number customPropCanSpawn() + return CurTime() >= (self.player.customPropLastSpawn or 0) + wire_expression2_propcore_customprops_delay:GetFloat() and 1 or 0 +end + +[nodiscard] +e2function number customPropCanSpawnIn() + local timeleft = (self.player.customPropLastSpawn or 0) + wire_expression2_propcore_customprops_delay:GetFloat() - CurTime() + return timeleft > 0 and math.ceil(timeleft) or 0 +end + +[nodiscard] +e2function number customPropIsEnabled() + return wire_expression2_propcore_customprops_enabled:GetBool() and 1 or 0 +end + +[nodiscard] +e2function number customPropGetDelay() + return wire_expression2_propcore_customprops_delay:GetFloat() +end + +[nodiscard] +e2function number customPropsLeft() + return math.max(0, math.floor(wire_expression2_propcore_customprops_max:GetInt() - (self.player.customPropsSpawned or 0))) +end + +[nodiscard] +e2function number customPropsMax() + return wire_expression2_propcore_customprops_max:GetInt() +end + +-------------------------------------------------------------------------------- + __e2setcost(10) e2function void entity:propDelete() if not ValidAction(self, this, "delete") then return end From a84910a25f38e4b77fa2a0065074898a13a0d1a9 Mon Sep 17 00:00:00 2001 From: deltamolfar <72973198+deltamolfar@users.noreply.github.com> Date: Thu, 2 Oct 2025 23:37:52 +0300 Subject: [PATCH 02/16] cl descriptions + few util funcs --- .../core/custom/cl_prop.lua | 17 +++++++- .../core/custom/prop.lua | 39 ++++++++++++++++--- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/lua/entities/gmod_wire_expression2/core/custom/cl_prop.lua b/lua/entities/gmod_wire_expression2/core/custom/cl_prop.lua index cae3c86b83..57de6f56a7 100644 --- a/lua/entities/gmod_wire_expression2/core/custom/cl_prop.lua +++ b/lua/entities/gmod_wire_expression2/core/custom/cl_prop.lua @@ -36,7 +36,22 @@ E2Helper.Descriptions["sentIsEnabled()"] = "Returns 1 if server allows spawning E2Helper.Descriptions["seatSpawn(sn)"] = "Model path, Frozen Spawns a prop with the model denoted by the string filepath. If frozen is 0, then it will spawn unfrozen." E2Helper.Descriptions["seatSpawn(svan)"] = E2Helper.Descriptions["seatSpawn(sn)"] E2Helper.Descriptions["seatSpawn(svans)"] = E2Helper.Descriptions["seatSpawn(sn)"] .. " String seatType, determines what animations the seat will have. For example phx_seat2 and phx_seat3 will have Jeep and Airboat animations." -E2Helper.Descriptions["customPropSpawn(t)"] = "Spawns a custom prop with the given table of format {1 = {}}" +E2Helper.Descriptions["customPropSpawn(t)"] = "Spawns a custom prop with the given table of format table(1 = array(vec(), ...), 2 = array(vec(), ...), ...) where each array is a convex." +E2Helper.Descriptions["customPropSpawn(tv)"] = E2Helper.Descriptions["customPropSpawn(t)"] .. "A vector position, where to spawn the prop." +E2Helper.Descriptions["customPropSpawn(ta)"] = E2Helper.Descriptions["customPropSpawn(t)"] .. "An angle rotation, how to rotate the prop." +E2Helper.Descriptions["customPropSpawn(tva)"] = E2Helper.Descriptions["customPropSpawn(t)"] .. "A vector position, where to spawn the prop, and an angle rotation, how to rotate the prop." +E2Helper.Descriptions["customPropSpawn(tvan)"] = E2Helper.Descriptions["customPropSpawn(t)"] .. "A vector position, where to spawn the prop, an angle rotation, how to rotate the prop, and a number frozen/unfrozen." +E2Helper.Descriptions["customPropCanCreate()"] = "Returns 1 if you can create a custom prop, 0 otherwise." +E2Helper.Descriptions["customPropCanSpawnIn()"] = "Returns in seconds how long until you can spawn another custom prop, 0 if you can spawn now." +E2Helper.Descriptions["customPropIsEnabled()"] = "Returns 1 if custom props are enabled, 0 otherwise." +E2Helper.Descriptions["customPropGetDelay()"] = "Returns the delay in seconds between spawning custom props." +E2Helper.Descriptions["customPropsLeft()"] = "Returns how many custom props you can still spawn." +E2Helper.Descriptions["customPropsMax()"] = "Returns the maximum amount of custom props you can spawn." +E2Helper.Descriptions["customPropConvexesMax()"] = "Returns the maximum amount of convexes a custom prop can have." +E2Helper.Descriptions["customPropVerticesMax()"] = "Returns the maximum amount of vertices a convex in a custom prop can have." +E2Helper.Descriptions["customPropMinVertexDistance()"] = "Returns the minimum distance two vertices in a convex can have." +E2Helper.Descriptions["customPropHullSizeMax()"] = "Returns the maximum hull size of a custom prop." +E2Helper.Descriptions["customPropsSpawned()"] = "Returns how many custom props you have currently spawned." E2Helper.Descriptions["propSpawnEffect(n)"] = "Set to 1 to enable prop spawn effect, 0 to disable." E2Helper.Descriptions["propDelete(e:)"] = "Deletes the specified prop." E2Helper.Descriptions["propDelete(t:)"] = "Deletes all the props in the given table, returns the amount of props deleted." diff --git a/lua/entities/gmod_wire_expression2/core/custom/prop.lua b/lua/entities/gmod_wire_expression2/core/custom/prop.lua index 8de98be10d..e57c5294a3 100644 --- a/lua/entities/gmod_wire_expression2/core/custom/prop.lua +++ b/lua/entities/gmod_wire_expression2/core/custom/prop.lua @@ -401,7 +401,11 @@ end local CreateSent = PropCore.CreateSent -local function spawnCustomProp(self, convexes, pos, ang, freeze) +local wire_customprops_hullsize_max = GetConVar("wire_customprops_hullsize_max") +local wire_customprops_minvertexdistance = GetConVar("wire_customprops_minvertexdistance") +local wire_customprops_vertices_max = GetConVar("wire_customprops_vertices_max") +local wire_customprops_convexes_max = GetConVar("wire_customprops_convexes_max") +local function createCustomProp(self, convexes, pos, ang, freeze) if self.player.customPropsSpawned and self.player.customPropsSpawned >= wire_expression2_propcore_customprops_max:GetInt() then return self:throw("You have reached the maximum number of custom props you can spawn! (" .. wire_expression2_propcore_customprops_max:GetInt() .. ")", nil) end @@ -416,7 +420,6 @@ local function spawnCustomProp(self, convexes, pos, ang, freeze) if not ValidAction(self, nil, "spawn") then return NULL end convexes = castE2ValueToLuaValue(TYPE_TABLE, convexes) - PrintTable(convexes) local success, entity = pcall(WireLib.createCustomProp, self.player, pos, ang, convexes) if not success then @@ -756,11 +759,11 @@ end __e2setcost(150) e2function entity customPropSpawn(table convexes) - return spawnCustomProp(self, convexes, self.entity:GetPos() + self.entity:GetUp() * 25, self.entity:GetAngles(), 1) + return createCustomProp(self, convexes, self.entity:GetPos() + self.entity:GetUp() * 25, self.entity:GetAngles(), 1) end e2function entity customPropSpawn(table convexes, vector pos, angle ang, number frozen) - return spawnCustomProp(self, convexes, Vector(pos[1], pos[2], pos[3]), Angle(ang[1], ang[2], ang[3]), frozen) + return createCustomProp(self, convexes, Vector(pos[1], pos[2], pos[3]), Angle(ang[1], ang[2], ang[3]), frozen) end -------------------------------------------------------------------------------- @@ -768,7 +771,7 @@ end __e2setcost(5) [nodiscard] e2function number customPropCanSpawn() - return CurTime() >= (self.player.customPropLastSpawn or 0) + wire_expression2_propcore_customprops_delay:GetFloat() and 1 or 0 + return self.player.customPropsSpawned < wire_expression2_propcore_customprops_max:GetInt() and (CurTime() >= (self.player.customPropLastSpawn or 0) + wire_expression2_propcore_customprops_delay:GetFloat() and 1 or 0) or 0 end [nodiscard] @@ -797,6 +800,32 @@ e2function number customPropsMax() return wire_expression2_propcore_customprops_max:GetInt() end +[nodiscard] +e2function number customPropConvexesMax() + return wire_customprops_convexes_max:GetInt() +end + +[nodiscard] +e2function number customPropVerticesMax() + return wire_customprops_vertices_max:GetInt() +end + +[nodiscard] +e2function number customPropMinVertexDistance() + return wire_customprops_minvertexdistance:GetFloat() +end + +[nodiscard] +e2function number customPropHullSizeMax() + return wire_customprops_hullsize_max:GetFloat() +end + +__e2setcost(1) +[nodiscard] +e2function number customPropsSpawned() + return self.player.customPropsSpawned or 0 +end + -------------------------------------------------------------------------------- __e2setcost(10) From e2765195825b900be949e5082060b7ea2e18e032 Mon Sep 17 00:00:00 2001 From: deltamolfar <72973198+deltamolfar@users.noreply.github.com> Date: Thu, 2 Oct 2025 23:37:59 +0300 Subject: [PATCH 03/16] owner fix --- lua/entities/gmod_wire_customprop/init.lua | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/lua/entities/gmod_wire_customprop/init.lua b/lua/entities/gmod_wire_customprop/init.lua index 3ce4b97b0e..c6c92ac01b 100644 --- a/lua/entities/gmod_wire_customprop/init.lua +++ b/lua/entities/gmod_wire_customprop/init.lua @@ -8,9 +8,9 @@ local ENT_META = FindMetaTable("Entity") local Ent_GetTable = ENT_META.GetTable local wire_customprops_hullsize_max = CreateConVar("wire_customprops_hullsize_max", 2048, FCVAR_ARCHIVE, "The max hull size of a custom prop") -local wire_customprops_minvertexdistance = CreateConVar("wire_customprops_minvertexdistance", 0.2, FCVAR_ARCHIVE, "The min distance between two vertices in a custom prop") -local wire_customprops_vertices_max = CreateConVar("wire_customprops_vertices_max", 512, FCVAR_ARCHIVE, "How many vertices custom props can have.", 4, 1024) -local wire_customprops_convexes_max = CreateConVar("wire_customprops_convexes_max", 16, FCVAR_ARCHIVE, "How many convexes custom props can have.", 1, 64) +local wire_customprops_minvertexdistance = CreateConVar("wire_customprops_minvertexdistance", 0.2, FCVAR_ARCHIVE, "The min distance between two vertices in a custom prop.") +local wire_customprops_vertices_max = CreateConVar("wire_customprops_vertices_max", 512, FCVAR_ARCHIVE, "How many vertices custom props can have.", 4) +local wire_customprops_convexes_max = CreateConVar("wire_customprops_convexes_max", 8, FCVAR_ARCHIVE, "How many convexes custom props can have.", 1) function ENT:Initialize() self.BaseClass.Initialize(self) @@ -179,19 +179,16 @@ function WireLib.createCustomProp(ply, pos, ang, wiremeshdata) assert(false, "Custom prop has invalid physics!") end - propent:SetOwner(ply) propent:SetPos(pos) propent:SetAngles(ang) - -- Transmit after a short delay so the client has a chance to create the entity - -- and register its net receivers (prevents first-send race). - timer.Simple(0, function() - if IsValid(propent) then propent:TransmitData() end - end) + propent:TransmitData() physobj:EnableCollisions(true) physobj:EnableDrag(true) physobj:Wake() + gamemode.Call("PlayerSpawnedSENT", ply, propent) + local totalVertices = 0 for k, v in ipairs(meshConvexes) do totalVertices = totalVertices + #v From c2542f20ed65cd8f350964338a0d91d39537beb5 Mon Sep 17 00:00:00 2001 From: deltamolfar <72973198+deltamolfar@users.noreply.github.com> Date: Tue, 7 Oct 2025 23:31:17 +0300 Subject: [PATCH 04/16] move to 16 bit net --- lua/entities/gmod_wire_customprop/shared.lua | 116 +++++++------------ 1 file changed, 39 insertions(+), 77 deletions(-) diff --git a/lua/entities/gmod_wire_customprop/shared.lua b/lua/entities/gmod_wire_customprop/shared.lua index d75ee6a7f9..68e68610b7 100644 --- a/lua/entities/gmod_wire_customprop/shared.lua +++ b/lua/entities/gmod_wire_customprop/shared.lua @@ -17,6 +17,34 @@ end local Ent_IsValid = FindMetaTable("Entity").IsValid local Ent_GetTable = FindMetaTable("Entity").GetTable + +local writeInt16 = function(n) + return string.char( + bit.band(n, 0xFF), + bit.band(bit.rshift(n, 8), 0xFF) + ) +end + +local readInt16 = function(data, pos) + local b1 = string.byte(data, pos) + local b2 = string.byte(data, pos + 1) + local n = b1 + b2 * 256 + return n, pos + 2 +end + +local quantizeFloat16 = function(f, min, max) + local range = max - min + if range <= 0 then return 0 end + local v = math.floor((math.max(min, math.min(max, f)) - min) / range * 65535 + 0.5) + return v +end + +local dequantizeFloat16 = function(i, min, max) + local range = max - min + if range <= 0 then return min end + return min + (i / 65535) * range +end + return { classname = "gmod_wire_customprop", @@ -47,87 +75,21 @@ return { net.WriteUInt(ent:GetCreationID(), 32) end, - writeInt32 = function(n) - return string.char( - bit.band(n, 0xFF), - bit.band(bit.rshift(n, 8), 0xFF), - bit.band(bit.rshift(n, 16), 0xFF), - bit.band(bit.rshift(n, 24), 0xFF) - ) - end, - - readInt32 = function(data, pos) - local b1 = string.byte(data, pos) - local b2 = string.byte(data, pos + 1) - local b3 = string.byte(data, pos + 2) - local b4 = string.byte(data, pos + 3) - local n = b1 + b2 * 256 + b3 * 65536 + b4 * 16777216 - return n, pos + 4 - end, + writeInt16 = writeInt16, - writeFloat = function(f) - -- Handles special cases - if f == 0 then - return string.char(0, 0, 0, 0) - elseif f ~= f then -- NaN - return string.char(0, 0, 192, 127) - elseif f == math.huge then - return string.char(0, 0, 128, 127) - elseif f == -math.huge then - return string.char(0, 0, 128, 255) - end + readInt16 = readInt16, - local sign = 0 - if f < 0 then sign = 0x80000000; f = -f end + quantizeFloat16 = quantizeFloat16, - local mantissa, exponent = math.frexp(f) - exponent = exponent + 126 + dequantizeFloat16 = dequantizeFloat16, - if exponent <= 0 then - mantissa = mantissa * math.ldexp(0.5, exponent) - exponent = 0 - elseif exponent >= 255 then - mantissa = 0 - exponent = 255 - else - mantissa = (mantissa * 2 - 1) * 0x800000 - end - - local bits = sign + bit.lshift(exponent, 23) + math.floor(mantissa + 0.5) - return string.char( - bit.band(bits, 0xFF), - bit.band(bit.rshift(bits, 8), 0xFF), - bit.band(bit.rshift(bits, 16), 0xFF), - bit.band(bit.rshift(bits, 24), 0xFF) - ) + writeQuantizedFloat16 = function(f, min, max) + local i = quantizeFloat16(f, min, max) + return writeInt16(i) end, - readFloat = function(data, pos) - local b1 = string.byte(data, pos) - local b2 = string.byte(data, pos + 1) - local b3 = string.byte(data, pos + 2) - local b4 = string.byte(data, pos + 3) - local bits = b1 + b2 * 256 + b3 * 65536 + b4 * 16777216 - - local sign = bit.band(bits, 0x80000000) ~= 0 and -1 or 1 - local exponent = bit.band(bit.rshift(bits, 23), 0xFF) - local mantissa = bit.band(bits, 0x7FFFFF) - - if exponent == 255 then - if mantissa == 0 then - return sign * math.huge, pos + 4 - else - return 0/0, pos + 4 -- NaN - end - elseif exponent == 0 then - if mantissa == 0 then - return 0, pos + 4 - else - return sign * math.ldexp(mantissa / 0x800000, -126), pos + 4 - end - end - - f = sign * math.ldexp(1 + mantissa / 0x800000, exponent - 127) - return f, pos + 4 - end + readQuantizedFloat16 = function(data, pos, min, max) + local i, pos2 = readInt16(data, pos) + return dequantizeFloat16(i, min, max), pos2 + end, } \ No newline at end of file From 23a13a13fcc856e59a5e2455e0db947967551c49 Mon Sep 17 00:00:00 2001 From: deltamolfar <72973198+deltamolfar@users.noreply.github.com> Date: Tue, 7 Oct 2025 23:31:33 +0300 Subject: [PATCH 05/16] move to 16 bit, change limits, fix duplicator func --- lua/entities/gmod_wire_customprop/cl_init.lua | 15 ++++--- lua/entities/gmod_wire_customprop/init.lua | 42 +++++++++++++------ 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/lua/entities/gmod_wire_customprop/cl_init.lua b/lua/entities/gmod_wire_customprop/cl_init.lua index 1cadcf40ea..c482245e2d 100644 --- a/lua/entities/gmod_wire_customprop/cl_init.lua +++ b/lua/entities/gmod_wire_customprop/cl_init.lua @@ -85,23 +85,26 @@ function ENT:GetRenderMesh() end end +local wire_customprops_hullsize_max = GetConVar("wire_customprops_hullsize_max") +local quantMinX, quantMinY, quantMinZ = -wire_customprops_hullsize_max:GetFloat(), -wire_customprops_hullsize_max:GetFloat(), -wire_customprops_hullsize_max:GetFloat() +local quantMaxX, quantMaxY, quantMaxZ = wire_customprops_hullsize_max:GetFloat(), wire_customprops_hullsize_max:GetFloat(), wire_customprops_hullsize_max:GetFloat() local function streamToMesh(meshdata) local meshConvexes, posMins, posMaxs = {}, Vector(math.huge, math.huge, math.huge), Vector(-math.huge, -math.huge, -math.huge) - local meshdata = util.Decompress(meshdata, 65536) + meshdata = util.Decompress(meshdata, 65536) local pos = 1 local nConvexes - nConvexes, pos = shared.readInt32(meshdata, pos) + nConvexes, pos = shared.readInt16(meshdata, pos) for iConvex = 1, nConvexes do local nVertices - nVertices, pos = shared.readInt32(meshdata, pos) + nVertices, pos = shared.readInt16(meshdata, pos) local convex = {} for iVertex = 1, nVertices do local x, y, z - x, pos = shared.readFloat(meshdata, pos) - y, pos = shared.readFloat(meshdata, pos) - z, pos = shared.readFloat(meshdata, pos) + x, pos = shared.readQuantizedFloat16(meshdata, pos, quantMinX, quantMaxX) + y, pos = shared.readQuantizedFloat16(meshdata, pos, quantMinY, quantMaxY) + z, pos = shared.readQuantizedFloat16(meshdata, pos, quantMinZ, quantMaxZ) if x > posMaxs.x then posMaxs.x = x end if y > posMaxs.y then posMaxs.y = y end if z > posMaxs.z then posMaxs.z = z end diff --git a/lua/entities/gmod_wire_customprop/init.lua b/lua/entities/gmod_wire_customprop/init.lua index c6c92ac01b..07ebc6d3e0 100644 --- a/lua/entities/gmod_wire_customprop/init.lua +++ b/lua/entities/gmod_wire_customprop/init.lua @@ -7,10 +7,13 @@ util.AddNetworkString(shared.classname) local ENT_META = FindMetaTable("Entity") local Ent_GetTable = ENT_META.GetTable +// Reason why there are more max convexes but less max vertices by default is that client's ENT:BuildPhysics is the main bottleneck. +// It seems to require more time exponentially to the vertices amount. +// The same amount of vertices in total, but broken into different convexes greatly reduces the performance hit. local wire_customprops_hullsize_max = CreateConVar("wire_customprops_hullsize_max", 2048, FCVAR_ARCHIVE, "The max hull size of a custom prop") local wire_customprops_minvertexdistance = CreateConVar("wire_customprops_minvertexdistance", 0.2, FCVAR_ARCHIVE, "The min distance between two vertices in a custom prop.") -local wire_customprops_vertices_max = CreateConVar("wire_customprops_vertices_max", 512, FCVAR_ARCHIVE, "How many vertices custom props can have.", 4) -local wire_customprops_convexes_max = CreateConVar("wire_customprops_convexes_max", 8, FCVAR_ARCHIVE, "How many convexes custom props can have.", 1) +local wire_customprops_vertices_max = CreateConVar("wire_customprops_vertices_max", 64, FCVAR_ARCHIVE, "How many vertices custom props can have.", 4) +local wire_customprops_convexes_max = CreateConVar("wire_customprops_convexes_max", 16, FCVAR_ARCHIVE, "How many convexes custom props can have.", 1) function ENT:Initialize() self.BaseClass.Initialize(self) @@ -78,6 +81,7 @@ function ENT:TransmitData(recip) net.Start(shared.classname) shared.writeReliableEntity(self) local stream = net.WriteStream(self.wiremeshdata, nil, true) + if recip then net.Send(recip) else net.Broadcast() end return stream end @@ -89,23 +93,30 @@ hook.Add("PlayerInitialSpawn","CustomProp_SpawnFunc",function(ply) end) local function streamToMesh(meshdata) - local maxConvexesPerProp = maxConvexesPerProp:GetInt() - local maxVerticesPerConvex = maxVerticesPerConvex:GetInt() + local maxHullsize = wire_customprops_hullsize_max:GetFloat() + local quantMinX, quantMinY, quantMinZ = -maxHullsize, -maxHullsize, -maxHullsize + local quantMaxX, quantMaxY, quantMaxZ = maxHullsize, maxHullsize, maxHullsize + local maxConvexesPerProp = wire_customprops_convexes_max:GetInt() + local maxVerticesPerConvex = wire_customprops_vertices_max:GetInt() local meshConvexes = {} local data = util.Decompress(meshdata, 65536) + if not data or type(data) ~= "string" then return meshConvexes end + local pos = 1 local nConvexes - nConvexes, pos = string.unpack("I4", data, pos) + nConvexes, pos = shared.readInt16(data, pos) assert(nConvexes <= maxConvexesPerProp, "Exceeded the max convexes per prop (max: " .. maxConvexesPerProp .. ", got: " .. nConvexes .. ")") for iConvex = 1, nConvexes do local nVertices - nVertices, pos = string.unpack("I4", data, pos) + nVertices, pos = shared.readInt16(data, pos) assert(nVertices <= maxVerticesPerConvex, "Exceeded the max vertices per convex (max: " .. maxVerticesPerConvex .. ", got: " .. nVertices .. ")") local convex = {} for iVertex = 1, nVertices do local x, y, z - x, y, z, pos = string.unpack("fff", data, pos) + x, pos = shared.readQuantizedFloat16(data, pos, quantMinX, quantMaxX) + y, pos = shared.readQuantizedFloat16(data, pos, quantMinY, quantMaxY) + z, pos = shared.readQuantizedFloat16(data, pos, quantMinZ, quantMaxZ) convex[iVertex] = Vector(x, y, z) end meshConvexes[iConvex] = convex @@ -114,15 +125,19 @@ local function streamToMesh(meshdata) end local function meshToStream(meshConvexes) + local maxHullsize = wire_customprops_hullsize_max:GetFloat() + local quantMinX, quantMinY, quantMinZ = -maxHullsize, -maxHullsize, -maxHullsize + local quantMaxX, quantMaxY, quantMaxZ = maxHullsize, maxHullsize, maxHullsize + local buffer = {} - buffer[#buffer+1] = shared.writeInt32(#meshConvexes) + buffer[#buffer+1] = shared.writeInt16(#meshConvexes) for _, convex in ipairs(meshConvexes) do - buffer[#buffer+1] = shared.writeInt32(#convex) + buffer[#buffer+1] = shared.writeInt16(#convex) for _, vertex in ipairs(convex) do - buffer[#buffer+1] = shared.writeFloat(vertex[1]) - buffer[#buffer+1] = shared.writeFloat(vertex[2]) - buffer[#buffer+1] = shared.writeFloat(vertex[3]) + buffer[#buffer+1] = shared.writeQuantizedFloat16(vertex.x, quantMinX, quantMaxX) + buffer[#buffer+1] = shared.writeQuantizedFloat16(vertex.y, quantMinY, quantMaxY) + buffer[#buffer+1] = shared.writeQuantizedFloat16(vertex.z, quantMinZ, quantMaxZ) end end @@ -131,6 +146,7 @@ end local function checkMesh(ply, meshConvexes) local maxHullSize = wire_customprops_hullsize_max:GetFloat() + local mindist = wire_customprops_minvertexdistance:GetFloat() local maxConvexesPerProp = wire_customprops_convexes_max:GetInt() local maxVerticesPerConvex = wire_customprops_vertices_max:GetInt() @@ -197,4 +213,4 @@ function WireLib.createCustomProp(ply, pos, ang, wiremeshdata) return propent end -duplicator.RegisterEntityClass(shared.classname, createCustomProp, "Pos", "Ang", "wiremeshdata") +duplicator.RegisterEntityClass(shared.classname, WireLib.createCustomProp, "Pos", "Ang", "wiremeshdata") From 265241b4052ed2a1728131d0461899ee453c6513 Mon Sep 17 00:00:00 2001 From: deltamolfar <72973198+deltamolfar@users.noreply.github.com> Date: Tue, 7 Oct 2025 23:33:03 +0300 Subject: [PATCH 06/16] Use tabs instead of spaces --- lua/entities/gmod_wire_customprop/cl_init.lua | 96 ++++++++-------- lua/entities/gmod_wire_customprop/init.lua | 104 +++++++++--------- lua/entities/gmod_wire_customprop/shared.lua | 20 ++-- 3 files changed, 110 insertions(+), 110 deletions(-) diff --git a/lua/entities/gmod_wire_customprop/cl_init.lua b/lua/entities/gmod_wire_customprop/cl_init.lua index c482245e2d..8f505a83be 100644 --- a/lua/entities/gmod_wire_customprop/cl_init.lua +++ b/lua/entities/gmod_wire_customprop/cl_init.lua @@ -21,10 +21,10 @@ function ENT:Initialize() end function ENT:OnRemove() - if self.rendermesh then - self.rendermesh:Destroy() - self.rendermesh = nil - end + if self.rendermesh then + self.rendermesh:Destroy() + self.rendermesh = nil + end end function ENT:BuildPhysics(ent_tbl, physmesh) @@ -91,62 +91,62 @@ local quantMaxX, quantMaxY, quantMaxZ = wire_customprops_hullsize_max:GetFloat() local function streamToMesh(meshdata) local meshConvexes, posMins, posMaxs = {}, Vector(math.huge, math.huge, math.huge), Vector(-math.huge, -math.huge, -math.huge) - meshdata = util.Decompress(meshdata, 65536) - - local pos = 1 - local nConvexes - nConvexes, pos = shared.readInt16(meshdata, pos) - for iConvex = 1, nConvexes do - local nVertices - nVertices, pos = shared.readInt16(meshdata, pos) - local convex = {} - for iVertex = 1, nVertices do - local x, y, z - x, pos = shared.readQuantizedFloat16(meshdata, pos, quantMinX, quantMaxX) + meshdata = util.Decompress(meshdata, 65536) + + local pos = 1 + local nConvexes + nConvexes, pos = shared.readInt16(meshdata, pos) + for iConvex = 1, nConvexes do + local nVertices + nVertices, pos = shared.readInt16(meshdata, pos) + local convex = {} + for iVertex = 1, nVertices do + local x, y, z + x, pos = shared.readQuantizedFloat16(meshdata, pos, quantMinX, quantMaxX) y, pos = shared.readQuantizedFloat16(meshdata, pos, quantMinY, quantMaxY) z, pos = shared.readQuantizedFloat16(meshdata, pos, quantMinZ, quantMaxZ) - if x > posMaxs.x then posMaxs.x = x end - if y > posMaxs.y then posMaxs.y = y end - if z > posMaxs.z then posMaxs.z = z end - if x < posMins.x then posMins.x = x end - if y < posMins.y then posMins.y = y end - if z < posMins.z then posMins.z = z end - convex[iVertex] = Vector(x, y, z) - end - meshConvexes[iConvex] = convex - end - - return meshConvexes, posMins, posMaxs + if x > posMaxs.x then posMaxs.x = x end + if y > posMaxs.y then posMaxs.y = y end + if z > posMaxs.z then posMaxs.z = z end + if x < posMins.x then posMins.x = x end + if y < posMins.y then posMins.y = y end + if z < posMins.z then posMins.z = z end + convex[iVertex] = Vector(x, y, z) + end + meshConvexes[iConvex] = convex + end + + return meshConvexes, posMins, posMaxs end net.Receive(shared.classname, function() local receivedEntity, receivedData - local function tryApplyData() - if not receivedEntity or not receivedData then return end + local function tryApplyData() + if not receivedEntity or not receivedData then return end - if Ent_IsValid(receivedEntity) and receivedEntity:GetClass()~=shared.classname then return end - local ent_tbl = Ent_GetTable(receivedEntity) - if not (ent_tbl and ent_tbl.rendermesh:IsValid() and receivedData and not ent_tbl.meshapplied) then return end + if Ent_IsValid(receivedEntity) and receivedEntity:GetClass()~=shared.classname then return end + local ent_tbl = Ent_GetTable(receivedEntity) + if not (ent_tbl and ent_tbl.rendermesh:IsValid() and receivedData and not ent_tbl.meshapplied) then return end - ent_tbl.meshapplied = true + ent_tbl.meshapplied = true - local physmesh, mins, maxs = streamToMesh(receivedData) - ent_tbl.BuildPhysics(receivedEntity, ent_tbl, physmesh) - ent_tbl.BuildRenderMesh(receivedEntity, ent_tbl) - receivedEntity:SetRenderBounds(mins, maxs) - receivedEntity:SetCollisionBounds(mins, maxs) - end + local physmesh, mins, maxs = streamToMesh(receivedData) + ent_tbl.BuildPhysics(receivedEntity, ent_tbl, physmesh) + ent_tbl.BuildRenderMesh(receivedEntity, ent_tbl) + receivedEntity:SetRenderBounds(mins, maxs) + receivedEntity:SetCollisionBounds(mins, maxs) + end - shared.readReliableEntity(function(self) - receivedEntity = self - tryApplyData() - end) + shared.readReliableEntity(function(self) + receivedEntity = self + tryApplyData() + end) - net.ReadStream(nil, function(data) - receivedData = data - tryApplyData() - end) + net.ReadStream(nil, function(data) + receivedData = data + tryApplyData() + end) end) hook.Add("NetworkEntityCreated", shared.classname.."physics", function(ent) diff --git a/lua/entities/gmod_wire_customprop/init.lua b/lua/entities/gmod_wire_customprop/init.lua index 07ebc6d3e0..bc03758ae4 100644 --- a/lua/entities/gmod_wire_customprop/init.lua +++ b/lua/entities/gmod_wire_customprop/init.lua @@ -97,31 +97,31 @@ local function streamToMesh(meshdata) local quantMinX, quantMinY, quantMinZ = -maxHullsize, -maxHullsize, -maxHullsize local quantMaxX, quantMaxY, quantMaxZ = maxHullsize, maxHullsize, maxHullsize local maxConvexesPerProp = wire_customprops_convexes_max:GetInt() - local maxVerticesPerConvex = wire_customprops_vertices_max:GetInt() - - local meshConvexes = {} - local data = util.Decompress(meshdata, 65536) - if not data or type(data) ~= "string" then return meshConvexes end - - local pos = 1 - local nConvexes - nConvexes, pos = shared.readInt16(data, pos) - assert(nConvexes <= maxConvexesPerProp, "Exceeded the max convexes per prop (max: " .. maxConvexesPerProp .. ", got: " .. nConvexes .. ")") - for iConvex = 1, nConvexes do - local nVertices - nVertices, pos = shared.readInt16(data, pos) - assert(nVertices <= maxVerticesPerConvex, "Exceeded the max vertices per convex (max: " .. maxVerticesPerConvex .. ", got: " .. nVertices .. ")") - local convex = {} - for iVertex = 1, nVertices do - local x, y, z - x, pos = shared.readQuantizedFloat16(data, pos, quantMinX, quantMaxX) - y, pos = shared.readQuantizedFloat16(data, pos, quantMinY, quantMaxY) - z, pos = shared.readQuantizedFloat16(data, pos, quantMinZ, quantMaxZ) - convex[iVertex] = Vector(x, y, z) - end - meshConvexes[iConvex] = convex - end - return meshConvexes + local maxVerticesPerConvex = wire_customprops_vertices_max:GetInt() + + local meshConvexes = {} + local data = util.Decompress(meshdata, 65536) + if not data or type(data) ~= "string" then return meshConvexes end + + local pos = 1 + local nConvexes + nConvexes, pos = shared.readInt16(data, pos) + assert(nConvexes <= maxConvexesPerProp, "Exceeded the max convexes per prop (max: " .. maxConvexesPerProp .. ", got: " .. nConvexes .. ")") + for iConvex = 1, nConvexes do + local nVertices + nVertices, pos = shared.readInt16(data, pos) + assert(nVertices <= maxVerticesPerConvex, "Exceeded the max vertices per convex (max: " .. maxVerticesPerConvex .. ", got: " .. nVertices .. ")") + local convex = {} + for iVertex = 1, nVertices do + local x, y, z + x, pos = shared.readQuantizedFloat16(data, pos, quantMinX, quantMaxX) + y, pos = shared.readQuantizedFloat16(data, pos, quantMinY, quantMaxY) + z, pos = shared.readQuantizedFloat16(data, pos, quantMinZ, quantMaxZ) + convex[iVertex] = Vector(x, y, z) + end + meshConvexes[iConvex] = convex + end + return meshConvexes end local function meshToStream(meshConvexes) @@ -131,41 +131,41 @@ local function meshToStream(meshConvexes) local buffer = {} - buffer[#buffer+1] = shared.writeInt16(#meshConvexes) - for _, convex in ipairs(meshConvexes) do - buffer[#buffer+1] = shared.writeInt16(#convex) - for _, vertex in ipairs(convex) do - buffer[#buffer+1] = shared.writeQuantizedFloat16(vertex.x, quantMinX, quantMaxX) + buffer[#buffer+1] = shared.writeInt16(#meshConvexes) + for _, convex in ipairs(meshConvexes) do + buffer[#buffer+1] = shared.writeInt16(#convex) + for _, vertex in ipairs(convex) do + buffer[#buffer+1] = shared.writeQuantizedFloat16(vertex.x, quantMinX, quantMaxX) buffer[#buffer+1] = shared.writeQuantizedFloat16(vertex.y, quantMinY, quantMaxY) buffer[#buffer+1] = shared.writeQuantizedFloat16(vertex.z, quantMinZ, quantMaxZ) - end - end + end + end - return util.Compress(table.concat(buffer)) + return util.Compress(table.concat(buffer)) end local function checkMesh(ply, meshConvexes) local maxHullSize = wire_customprops_hullsize_max:GetFloat() - local mindist = wire_customprops_minvertexdistance:GetFloat() - local maxConvexesPerProp = wire_customprops_convexes_max:GetInt() - local maxVerticesPerConvex = wire_customprops_vertices_max:GetInt() - - assert(#meshConvexes > 0, "Invalid number of convexes (" .. #meshConvexes .. ")") - assert(#meshConvexes <= maxConvexesPerProp, "Exceeded the max convexes per prop (max: " .. maxConvexesPerProp .. ", got: ".. #meshConvexes .. ")") - - for _, convex in ipairs(meshConvexes) do - assert(#convex <= maxVerticesPerConvex, "Exceeded the max vertices per convex (max: " .. maxVerticesPerConvex .. ", got: " .. #convex .. ")") - assert(#convex > 4, "Invalid number of vertices (" .. #convex .. ")") - - for k, vertex in ipairs(convex) do - assert(math.abs(vertex[1]) < maxHullSize and math.abs(vertex[2]) < maxHullSize and math.abs(vertex[3]) < maxHullSize, "The custom prop cannot exceed a hull size of " .. maxHullSize) - assert(vertex[1] == vertex[1] and vertex[2] == vertex[2] and vertex[3] == vertex[3], "Your mesh contains nan values!") - for i = 1, k - 1 do - assert(convex[i]:DistToSqr(vertex) >= mindist, "No two vertices can have a distance less than " .. math.sqrt(mindist)) - end - end - end + local mindist = wire_customprops_minvertexdistance:GetFloat() + local maxConvexesPerProp = wire_customprops_convexes_max:GetInt() + local maxVerticesPerConvex = wire_customprops_vertices_max:GetInt() + + assert(#meshConvexes > 0, "Invalid number of convexes (" .. #meshConvexes .. ")") + assert(#meshConvexes <= maxConvexesPerProp, "Exceeded the max convexes per prop (max: " .. maxConvexesPerProp .. ", got: ".. #meshConvexes .. ")") + + for _, convex in ipairs(meshConvexes) do + assert(#convex <= maxVerticesPerConvex, "Exceeded the max vertices per convex (max: " .. maxVerticesPerConvex .. ", got: " .. #convex .. ")") + assert(#convex > 4, "Invalid number of vertices (" .. #convex .. ")") + + for k, vertex in ipairs(convex) do + assert(math.abs(vertex[1]) < maxHullSize and math.abs(vertex[2]) < maxHullSize and math.abs(vertex[3]) < maxHullSize, "The custom prop cannot exceed a hull size of " .. maxHullSize) + assert(vertex[1] == vertex[1] and vertex[2] == vertex[2] and vertex[3] == vertex[3], "Your mesh contains nan values!") + for i = 1, k - 1 do + assert(convex[i]:DistToSqr(vertex) >= mindist, "No two vertices can have a distance less than " .. math.sqrt(mindist)) + end + end + end end function WireLib.createCustomProp(ply, pos, ang, wiremeshdata) diff --git a/lua/entities/gmod_wire_customprop/shared.lua b/lua/entities/gmod_wire_customprop/shared.lua index 68e68610b7..59f6c99c5a 100644 --- a/lua/entities/gmod_wire_customprop/shared.lua +++ b/lua/entities/gmod_wire_customprop/shared.lua @@ -1,11 +1,11 @@ -ENT.Type = "anim" -ENT.Base = "base_anim" +ENT.Type = "anim" +ENT.Base = "base_anim" -ENT.PrintName = "Wiremod Custom Prop" -ENT.Author = "Sparky & DeltaMolfar" +ENT.PrintName = "Wiremod Custom Prop" +ENT.Author = "Sparky & DeltaMolfar" -ENT.Spawnable = false -ENT.AdminSpawnable = false +ENT.Spawnable = false +ENT.AdminSpawnable = false function ENT:SetupDataTables() self:NetworkVar("String", 0, "PhysMaterial") @@ -19,10 +19,10 @@ local Ent_IsValid = FindMetaTable("Entity").IsValid local Ent_GetTable = FindMetaTable("Entity").GetTable local writeInt16 = function(n) - return string.char( - bit.band(n, 0xFF), - bit.band(bit.rshift(n, 8), 0xFF) - ) + return string.char( + bit.band(n, 0xFF), + bit.band(bit.rshift(n, 8), 0xFF) + ) end local readInt16 = function(data, pos) From 1bdeff6cd8011516ef762d0539dd190ef8df2464 Mon Sep 17 00:00:00 2001 From: deltamolfar <72973198+deltamolfar@users.noreply.github.com> Date: Fri, 10 Oct 2025 22:29:55 +0300 Subject: [PATCH 07/16] change limits, remove delay --- lua/entities/gmod_wire_customprop/cl_init.lua | 18 ++--- lua/entities/gmod_wire_customprop/init.lua | 25 ++++++- .../core/custom/cl_prop.lua | 2 - .../core/custom/prop.lua | 70 +++++++------------ 4 files changed, 53 insertions(+), 62 deletions(-) diff --git a/lua/entities/gmod_wire_customprop/cl_init.lua b/lua/entities/gmod_wire_customprop/cl_init.lua index 8f505a83be..fcb2c2716f 100644 --- a/lua/entities/gmod_wire_customprop/cl_init.lua +++ b/lua/entities/gmod_wire_customprop/cl_init.lua @@ -11,13 +11,7 @@ function ENT:Initialize() self.rendermesh = Mesh(self.Material) self.meshapplied = false self:DrawShadow(false) - self:EnableCustomCollisions( true ) - - -- local mesh - -- SF.CallOnRemove(self, "sf_prop", - -- function() mesh = self.rendermesh end, - -- function() if mesh then mesh:Destroy() end - -- end) + self:EnableCustomCollisions(true) end function ENT:OnRemove() @@ -40,7 +34,7 @@ function ENT:BuildPhysics(ent_tbl, physmesh) end end -function ENT:BuildRenderMesh(ent_tbl, rendermesh) +function ENT:BuildRenderMesh(ent_tbl) local phys = self:GetPhysicsObject() if not Phys_IsValid(phys) then return end @@ -61,8 +55,8 @@ end function ENT:Think() local physobj = self:GetPhysicsObject() if Phys_IsValid(physobj) then - physobj:SetPos( self:GetPos() ) - physobj:SetAngles( self:GetAngles() ) + physobj:SetPos(self:GetPos()) + physobj:SetAngles(self:GetAngles()) physobj:EnableMotion(false) physobj:Sleep() end @@ -76,12 +70,12 @@ function ENT:GetRenderMesh() local ent_tbl = Ent_GetTable(self) if ent_tbl.custom_mesh then if ent_tbl.custom_mesh_data[ent_tbl.custom_mesh] then - return { Mesh = ent_tbl.custom_mesh, Material = ent_tbl.Material--[[, Matrix = ent_tbl.render_matrix]] } + return { Mesh = ent_tbl.custom_mesh, Material = ent_tbl.Material } else ent_tbl.custom_mesh = nil end else - return { Mesh = ent_tbl.rendermesh, Material = ent_tbl.Material--[[, Matrix = ent_tbl.render_matrix]] } + return { Mesh = ent_tbl.rendermesh, Material = ent_tbl.Material } end end diff --git a/lua/entities/gmod_wire_customprop/init.lua b/lua/entities/gmod_wire_customprop/init.lua index bc03758ae4..9636826d8e 100644 --- a/lua/entities/gmod_wire_customprop/init.lua +++ b/lua/entities/gmod_wire_customprop/init.lua @@ -14,6 +14,10 @@ local wire_customprops_hullsize_max = CreateConVar("wire_customprops_hullsize_ma local wire_customprops_minvertexdistance = CreateConVar("wire_customprops_minvertexdistance", 0.2, FCVAR_ARCHIVE, "The min distance between two vertices in a custom prop.") local wire_customprops_vertices_max = CreateConVar("wire_customprops_vertices_max", 64, FCVAR_ARCHIVE, "How many vertices custom props can have.", 4) local wire_customprops_convexes_max = CreateConVar("wire_customprops_convexes_max", 16, FCVAR_ARCHIVE, "How many convexes custom props can have.", 1) +local wire_customprops_max = CreateConVar("wire_customprops_max", 10, FCVAR_ARCHIVE, "The maximum number of custom props a player can spawn. (0 to disable)", 0) + +WireLib = WireLib or {} +WireLib.CustomProp = WireLib.CustomProp or {} function ENT:Initialize() self.BaseClass.Initialize(self) @@ -168,7 +172,14 @@ local function checkMesh(ply, meshConvexes) end end -function WireLib.createCustomProp(ply, pos, ang, wiremeshdata) +function WireLib.CustomProp.CanSpawn(ply) + ply.WireCustomPropsSpawned = ply.WireCustomPropsSpawned or 0 + return ply.WireCustomPropsSpawned < wire_customprops_max:GetInt() +end + +function WireLib.CustomProp.Create(ply, pos, ang, wiremeshdata) + if not WireLib.CustomProp.CanSpawn(ply) then return nil, "Max amount of custom props spawned for this player reached!" end + local meshConvexes, meshStream if isstring(wiremeshdata) then @@ -210,7 +221,17 @@ function WireLib.createCustomProp(ply, pos, ang, wiremeshdata) totalVertices = totalVertices + #v end + propent:CallOnRemove("wire_customprop_remove", + function(propent) + if IsValid(ply) then + ply.WireCustomPropsSpawned = (ply.WireCustomPropsSpawned or 1) - 1 + end + end + ) + + ply.WireCustomPropsSpawned = (ply.WireCustomPropsSpawned or 0) + 1 + return propent end -duplicator.RegisterEntityClass(shared.classname, WireLib.createCustomProp, "Pos", "Ang", "wiremeshdata") +duplicator.RegisterEntityClass(shared.classname, WireLib.CustomProp.Create, "Pos", "Ang", "wiremeshdata") diff --git a/lua/entities/gmod_wire_expression2/core/custom/cl_prop.lua b/lua/entities/gmod_wire_expression2/core/custom/cl_prop.lua index 57de6f56a7..4a761fe897 100644 --- a/lua/entities/gmod_wire_expression2/core/custom/cl_prop.lua +++ b/lua/entities/gmod_wire_expression2/core/custom/cl_prop.lua @@ -42,9 +42,7 @@ E2Helper.Descriptions["customPropSpawn(ta)"] = E2Helper.Descriptions["customProp E2Helper.Descriptions["customPropSpawn(tva)"] = E2Helper.Descriptions["customPropSpawn(t)"] .. "A vector position, where to spawn the prop, and an angle rotation, how to rotate the prop." E2Helper.Descriptions["customPropSpawn(tvan)"] = E2Helper.Descriptions["customPropSpawn(t)"] .. "A vector position, where to spawn the prop, an angle rotation, how to rotate the prop, and a number frozen/unfrozen." E2Helper.Descriptions["customPropCanCreate()"] = "Returns 1 if you can create a custom prop, 0 otherwise." -E2Helper.Descriptions["customPropCanSpawnIn()"] = "Returns in seconds how long until you can spawn another custom prop, 0 if you can spawn now." E2Helper.Descriptions["customPropIsEnabled()"] = "Returns 1 if custom props are enabled, 0 otherwise." -E2Helper.Descriptions["customPropGetDelay()"] = "Returns the delay in seconds between spawning custom props." E2Helper.Descriptions["customPropsLeft()"] = "Returns how many custom props you can still spawn." E2Helper.Descriptions["customPropsMax()"] = "Returns the maximum amount of custom props you can spawn." E2Helper.Descriptions["customPropConvexesMax()"] = "Returns the maximum amount of convexes a custom prop can have." diff --git a/lua/entities/gmod_wire_expression2/core/custom/prop.lua b/lua/entities/gmod_wire_expression2/core/custom/prop.lua index e57c5294a3..abddab4ccf 100644 --- a/lua/entities/gmod_wire_expression2/core/custom/prop.lua +++ b/lua/entities/gmod_wire_expression2/core/custom/prop.lua @@ -12,9 +12,6 @@ local sbox_E2_canMakeStatue = CreateConVar("sbox_E2_canMakeStatue", "1", FCVAR_A local wire_expression2_propcore_sents_whitelist = CreateConVar("wire_expression2_propcore_sents_whitelist", 1, FCVAR_ARCHIVE, "If 1 - players can spawn sents only from the default sent list. If 0 - players can spawn sents from both the registered list and the entity tab.", 0, 1) local wire_expression2_propcore_sents_enabled = CreateConVar("wire_expression2_propcore_sents_enabled", 1, FCVAR_ARCHIVE, "If 1 - this allows sents to be spawned. (Doesn't affect the sentSpawn whitelist). If 0 - prevents sentSpawn from being used at all.", 0, 1) local wire_expression2_propcore_canMakeUnbreakable = CreateConVar("wire_expression2_propcore_canMakeUnbreakable", 1, FCVAR_ARCHIVE, "If 1 - this allows props to be made unbreakable. If 0 - prevents propMakeBreakable from being used at all.", 0, 1) -local wire_expression2_propcore_customprops_enabled = CreateConVar("wire_expression2_propcore_customprops_enabled", 1, FCVAR_ARCHIVE, "If 1 - this allows custom props to be spawned. If 0 - prevents customPropSpawn from being used at all.", 0, 1) -local wire_expression2_propcore_customprops_delay = CreateConVar("wire_expression2_propcore_customprops_delay", 2, FCVAR_ARCHIVE, "How many seconds to wait after spawning a custom prop before allowing to spawn another one. (Too low may allow lag abuse)", 0) -local wire_expression2_propcore_customprops_max = CreateConVar("wire_expression2_propcore_customprops_max", 10, FCVAR_ARCHIVE, "The maximum number of custom props a player can spawn.") local isOwner = E2Lib.isOwner local GetBones = E2Lib.GetBones @@ -405,22 +402,17 @@ local wire_customprops_hullsize_max = GetConVar("wire_customprops_hullsize_max") local wire_customprops_minvertexdistance = GetConVar("wire_customprops_minvertexdistance") local wire_customprops_vertices_max = GetConVar("wire_customprops_vertices_max") local wire_customprops_convexes_max = GetConVar("wire_customprops_convexes_max") -local function createCustomProp(self, convexes, pos, ang, freeze) - if self.player.customPropsSpawned and self.player.customPropsSpawned >= wire_expression2_propcore_customprops_max:GetInt() then - return self:throw("You have reached the maximum number of custom props you can spawn! (" .. wire_expression2_propcore_customprops_max:GetInt() .. ")", nil) - end +local wire_customprops_max = GetConVar("wire_customprops_max") - if CurTime() < (self.player.customPropLastSpawn or 0) + wire_expression2_propcore_customprops_delay:GetFloat() then - self:throw("You can't spawn a custom prop just yet! (Use customPropCanSpawn or customPropCanSpawnIn to check)", nil) +local function createCustomProp(self, convexes, pos, ang, freeze) + if not WireLib.CustomProp.CanSpawn(self.player) then + return self:throw("You have reached the maximum number of custom props you can spawn! (" .. wire_customprops_max:GetInt() .. ")", nil) end - if wire_expression2_propcore_customprops_enabled:GetBool() == false then - self:throw("Custom prop spawning is disabled by server! (wire_expression2_propcore_customprops_enabled 0)", nil) - end if not ValidAction(self, nil, "spawn") then return NULL end convexes = castE2ValueToLuaValue(TYPE_TABLE, convexes) - local success, entity = pcall(WireLib.createCustomProp, self.player, pos, ang, convexes) + local success, entity = pcall(WireLib.CustomProp.Create, self.player, pos, ang, convexes) if not success then -- Remove file/line info from error string @@ -429,10 +421,6 @@ local function createCustomProp(self, convexes, pos, ang, freeze) end local phys = entity:GetPhysicsObject() - -- if IsValid( phys ) then - -- phys:EnableMotion( freeze == 0 ) - -- phys:Wake() - -- end self.player:AddCleanup("gmod_wire_customprop", entity) @@ -443,19 +431,8 @@ local function createCustomProp(self, convexes, pos, ang, freeze) undo.Finish("E2 Custom Prop") end - entity:CallOnRemove("wire_expression2_propcore_remove", - function(entity) - self.data.spawnedProps[entity] = nil - - if IsValid(self.player) then - self.player.customPropsSpawned = (self.player.customPropsSpawned or 1) - 1 - end - end - ) - self.player.customPropLastSpawn = CurTime() self.data.spawnedProps[entity] = self.data.propSpawnUndo - self.player.customPropsSpawned = (self.player.customPropsSpawned or 0) + 1 return entity end @@ -757,9 +734,21 @@ end -------------------------------------------------------------------------------- -__e2setcost(150) +__e2setcost(900) e2function entity customPropSpawn(table convexes) - return createCustomProp(self, convexes, self.entity:GetPos() + self.entity:GetUp() * 25, self.entity:GetAngles(), 1) + return createCustomProp(self, convexes, self.entity:GetPos() + self.entity:GetUp() * 25, self.entity:GetAngles(), 0) +end + +e2function entity customPropSpawn(table convexes, vector pos) + return createCustomProp(self, convexes, Vector(pos[1], pos[2], pos[3]), self.entity:GetAngles(), 0) +end + +e2function entity customPropSpawn(table convexes, angle ang) + return createCustomProp(self, convexes, self.entity:GetPos() + self.entity:GetUp() * 25, Angle(ang[1], ang[2], ang[3]), 0) +end + +e2function entity customPropSpawn(table convexes, vector pos, angle ang) + return createCustomProp(self, convexes, Vector(pos[1], pos[2], pos[3]), Angle(ang[1], ang[2], ang[3]), 0) end e2function entity customPropSpawn(table convexes, vector pos, angle ang, number frozen) @@ -768,16 +757,10 @@ end -------------------------------------------------------------------------------- -__e2setcost(5) -[nodiscard] -e2function number customPropCanSpawn() - return self.player.customPropsSpawned < wire_expression2_propcore_customprops_max:GetInt() and (CurTime() >= (self.player.customPropLastSpawn or 0) + wire_expression2_propcore_customprops_delay:GetFloat() and 1 or 0) or 0 -end - +__e2setcost(1) [nodiscard] -e2function number customPropCanSpawnIn() - local timeleft = (self.player.customPropLastSpawn or 0) + wire_expression2_propcore_customprops_delay:GetFloat() - CurTime() - return timeleft > 0 and math.ceil(timeleft) or 0 +e2function number customPropCanCreate() + return self.player.customPropsSpawned < wire_customprops_max:GetInt() and 1 or 0 end [nodiscard] @@ -785,19 +768,14 @@ e2function number customPropIsEnabled() return wire_expression2_propcore_customprops_enabled:GetBool() and 1 or 0 end -[nodiscard] -e2function number customPropGetDelay() - return wire_expression2_propcore_customprops_delay:GetFloat() -end - [nodiscard] e2function number customPropsLeft() - return math.max(0, math.floor(wire_expression2_propcore_customprops_max:GetInt() - (self.player.customPropsSpawned or 0))) + return math.max(0, math.floor(wire_customprops_max:GetInt() - (self.player.customPropsSpawned or 0))) end [nodiscard] e2function number customPropsMax() - return wire_expression2_propcore_customprops_max:GetInt() + return wire_customprops_max:GetInt() end [nodiscard] From d1b53c5ffc95b4caffca8d1681896ba09c733b34 Mon Sep 17 00:00:00 2001 From: deltamolfar <72973198+deltamolfar@users.noreply.github.com> Date: Fri, 10 Oct 2025 23:06:35 +0300 Subject: [PATCH 08/16] Add convexes structure checks, add dynamic OPS cost --- .../core/custom/prop.lua | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/lua/entities/gmod_wire_expression2/core/custom/prop.lua b/lua/entities/gmod_wire_expression2/core/custom/prop.lua index abddab4ccf..7d7df49dc1 100644 --- a/lua/entities/gmod_wire_expression2/core/custom/prop.lua +++ b/lua/entities/gmod_wire_expression2/core/custom/prop.lua @@ -404,6 +404,22 @@ local wire_customprops_vertices_max = GetConVar("wire_customprops_vertices_max") local wire_customprops_convexes_max = GetConVar("wire_customprops_convexes_max") local wire_customprops_max = GetConVar("wire_customprops_max") +local function isSequentialArray(t) + if TypeID(t) ~= TYPE_TABLE then return false end + + -- Check all keys are integers 1..n and contiguous + local count = 0 + for k in pairs(t) do + if type(k) ~= "number" or k < 1 or k % 1 ~= 0 then + return false + end + count = count + 1 + end + + -- Verify there are no gaps: #t must equal number of keys + return count == #t +end + local function createCustomProp(self, convexes, pos, ang, freeze) if not WireLib.CustomProp.CanSpawn(self.player) then return self:throw("You have reached the maximum number of custom props you can spawn! (" .. wire_customprops_max:GetInt() .. ")", nil) @@ -412,11 +428,23 @@ local function createCustomProp(self, convexes, pos, ang, freeze) if not ValidAction(self, nil, "spawn") then return NULL end convexes = castE2ValueToLuaValue(TYPE_TABLE, convexes) + + if not isSequentialArray(convexes) then return self:throw("Expected array of convexes (array of arrays of vectors)", nil) end + + -- Add dynamic ops cost, and validate the mesh data structure + for k, v in ipairs(convexes) do + if TypeID(v) ~= TYPE_TABLE then return self:throw("Expected array of convexes (array of arrays of vectors)", nil) end + for k2, v2 in ipairs(v) do + if TypeID(v2) ~= TYPE_VECTOR then return self:throw("Expected array of vertices (array of vectors)", nil) end + self.prf = self.prf + 10 -- Subject to change + end + end + local success, entity = pcall(WireLib.CustomProp.Create, self.player, pos, ang, convexes) if not success then -- Remove file/line info from error string - local msg = tostring(entity)--:gsub("^[^:]+:%d+:%s*", "") + local msg = tostring(entity):gsub("^[^:]+:%d+:%s*", "") self:throw("Failed to spawn custom prop! " .. msg, nil) end @@ -485,7 +513,7 @@ end -------------------------------------------------------------------------------- -__e2setcost(150) +__e2setcost(50) e2function entity sentSpawn(string class) if not ValidAction(self, nil, "spawn") then return NULL end return CreateSent(self, class, self.entity:GetPos()+self.entity:GetUp()*25, self.entity:GetAngles(), 1, {}) From 6de560a27470677abfbccfd42175a20773f9421b Mon Sep 17 00:00:00 2001 From: deltamolfar <72973198+deltamolfar@users.noreply.github.com> Date: Fri, 10 Oct 2025 23:09:07 +0300 Subject: [PATCH 09/16] Remove spaces, use tabs --- .../core/custom/prop.lua | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lua/entities/gmod_wire_expression2/core/custom/prop.lua b/lua/entities/gmod_wire_expression2/core/custom/prop.lua index 7d7df49dc1..291f3fdfaf 100644 --- a/lua/entities/gmod_wire_expression2/core/custom/prop.lua +++ b/lua/entities/gmod_wire_expression2/core/custom/prop.lua @@ -405,19 +405,19 @@ local wire_customprops_convexes_max = GetConVar("wire_customprops_convexes_max") local wire_customprops_max = GetConVar("wire_customprops_max") local function isSequentialArray(t) - if TypeID(t) ~= TYPE_TABLE then return false end + if TypeID(t) ~= TYPE_TABLE then return false end - -- Check all keys are integers 1..n and contiguous - local count = 0 - for k in pairs(t) do - if type(k) ~= "number" or k < 1 or k % 1 ~= 0 then - return false - end - count = count + 1 - end + -- Check all keys are integers 1..n and contiguous + local count = 0 + for k in pairs(t) do + if type(k) ~= "number" or k < 1 or k % 1 ~= 0 then + return false + end + count = count + 1 + end - -- Verify there are no gaps: #t must equal number of keys - return count == #t + -- Verify there are no gaps: #t must equal number of keys + return count == #t end local function createCustomProp(self, convexes, pos, ang, freeze) @@ -444,7 +444,7 @@ local function createCustomProp(self, convexes, pos, ang, freeze) if not success then -- Remove file/line info from error string - local msg = tostring(entity):gsub("^[^:]+:%d+:%s*", "") + local msg = tostring(entity):gsub("^[^:]+:%d+:%s*", "") self:throw("Failed to spawn custom prop! " .. msg, nil) end @@ -846,7 +846,7 @@ e2function void entity:propBreak() end hook.Add("EntityTakeDamage", "WireUnbreakable", function(ent, dmginfo) - if ent.wire_unbreakable then return true end + if ent.wire_unbreakable then return true end end) [nodiscard] From c54236645f3f9558a47eedc452c6cdede71322f8 Mon Sep 17 00:00:00 2001 From: deltamolfar <72973198+deltamolfar@users.noreply.github.com> Date: Fri, 10 Oct 2025 23:44:01 +0300 Subject: [PATCH 10/16] Remove unused var --- lua/entities/gmod_wire_expression2/core/custom/prop.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/lua/entities/gmod_wire_expression2/core/custom/prop.lua b/lua/entities/gmod_wire_expression2/core/custom/prop.lua index 291f3fdfaf..d7e634f4c7 100644 --- a/lua/entities/gmod_wire_expression2/core/custom/prop.lua +++ b/lua/entities/gmod_wire_expression2/core/custom/prop.lua @@ -448,8 +448,6 @@ local function createCustomProp(self, convexes, pos, ang, freeze) self:throw("Failed to spawn custom prop! " .. msg, nil) end - local phys = entity:GetPhysicsObject() - self.player:AddCleanup("gmod_wire_customprop", entity) if self.data.propSpawnUndo then From 3d994544636927eb612b1d5967acf2b7d5c5937a Mon Sep 17 00:00:00 2001 From: deltamolfar <72973198+deltamolfar@users.noreply.github.com> Date: Thu, 16 Oct 2025 10:24:19 +0300 Subject: [PATCH 11/16] Revert out of scope changes --- lua/entities/gmod_wire_expression2/core/custom/prop.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/entities/gmod_wire_expression2/core/custom/prop.lua b/lua/entities/gmod_wire_expression2/core/custom/prop.lua index d7e634f4c7..53a6390f05 100644 --- a/lua/entities/gmod_wire_expression2/core/custom/prop.lua +++ b/lua/entities/gmod_wire_expression2/core/custom/prop.lua @@ -340,7 +340,7 @@ function PropCore.CreateSent(self, class, pos, angles, freeze, data) entity = stored_sent.t.SpawnFunction(stored_sent.t, self.player, mockTrace, class) else - entity = ents.Create(class) + entity = ents.Create( class ) if IsValid(entity) then entity:SetPos(pos) entity:SetAngles(angles) @@ -511,7 +511,7 @@ end -------------------------------------------------------------------------------- -__e2setcost(50) +__e2setcost(150) e2function entity sentSpawn(string class) if not ValidAction(self, nil, "spawn") then return NULL end return CreateSent(self, class, self.entity:GetPos()+self.entity:GetUp()*25, self.entity:GetAngles(), 1, {}) From febd696db6650950bd777b269db9fd5eab7fcc5d Mon Sep 17 00:00:00 2001 From: deltamolfar <72973198+deltamolfar@users.noreply.github.com> Date: Sat, 1 Nov 2025 15:36:49 +0200 Subject: [PATCH 12/16] Use table.insert instead at init.lua --- lua/entities/gmod_wire_customprop/init.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lua/entities/gmod_wire_customprop/init.lua b/lua/entities/gmod_wire_customprop/init.lua index 9636826d8e..6efde635d6 100644 --- a/lua/entities/gmod_wire_customprop/init.lua +++ b/lua/entities/gmod_wire_customprop/init.lua @@ -135,13 +135,13 @@ local function meshToStream(meshConvexes) local buffer = {} - buffer[#buffer+1] = shared.writeInt16(#meshConvexes) + table.insert(buffer, shared.writeInt16(#meshConvexes)) for _, convex in ipairs(meshConvexes) do - buffer[#buffer+1] = shared.writeInt16(#convex) + table.insert(buffer, shared.writeInt16(#convex)) for _, vertex in ipairs(convex) do - buffer[#buffer+1] = shared.writeQuantizedFloat16(vertex.x, quantMinX, quantMaxX) - buffer[#buffer+1] = shared.writeQuantizedFloat16(vertex.y, quantMinY, quantMaxY) - buffer[#buffer+1] = shared.writeQuantizedFloat16(vertex.z, quantMinZ, quantMaxZ) + table.insert(buffer, shared.writeQuantizedFloat16(vertex.x, quantMinX, quantMaxX)) + table.insert(buffer, shared.writeQuantizedFloat16(vertex.y, quantMinY, quantMaxY)) + table.insert(buffer, shared.writeQuantizedFloat16(vertex.z, quantMinZ, quantMaxZ)) end end From 8b41eebd0acb26edb615ed954621bd3395122b02 Mon Sep 17 00:00:00 2001 From: deltamolfar <72973198+deltamolfar@users.noreply.github.com> Date: Mon, 3 Nov 2025 23:28:42 +0200 Subject: [PATCH 13/16] Change "//" to "--" --- lua/entities/gmod_wire_customprop/init.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lua/entities/gmod_wire_customprop/init.lua b/lua/entities/gmod_wire_customprop/init.lua index 6efde635d6..0b47b05127 100644 --- a/lua/entities/gmod_wire_customprop/init.lua +++ b/lua/entities/gmod_wire_customprop/init.lua @@ -7,9 +7,9 @@ util.AddNetworkString(shared.classname) local ENT_META = FindMetaTable("Entity") local Ent_GetTable = ENT_META.GetTable -// Reason why there are more max convexes but less max vertices by default is that client's ENT:BuildPhysics is the main bottleneck. -// It seems to require more time exponentially to the vertices amount. -// The same amount of vertices in total, but broken into different convexes greatly reduces the performance hit. +-- Reason why there are more max convexes but less max vertices by default is that client's ENT:BuildPhysics is the main bottleneck. +-- It seems to require more time exponentially to the vertices amount. +-- The same amount of vertices in total, but broken into different convexes greatly reduces the performance hit. local wire_customprops_hullsize_max = CreateConVar("wire_customprops_hullsize_max", 2048, FCVAR_ARCHIVE, "The max hull size of a custom prop") local wire_customprops_minvertexdistance = CreateConVar("wire_customprops_minvertexdistance", 0.2, FCVAR_ARCHIVE, "The min distance between two vertices in a custom prop.") local wire_customprops_vertices_max = CreateConVar("wire_customprops_vertices_max", 64, FCVAR_ARCHIVE, "How many vertices custom props can have.", 4) From 16b9052909726f77dfe2f75013d6acb9e5469d6b Mon Sep 17 00:00:00 2001 From: deltamolfar <72973198+deltamolfar@users.noreply.github.com> Date: Mon, 3 Nov 2025 23:32:08 +0200 Subject: [PATCH 14/16] Increase default wire_customprops_max limit to 16 (bit-nice value) --- lua/entities/gmod_wire_customprop/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/entities/gmod_wire_customprop/init.lua b/lua/entities/gmod_wire_customprop/init.lua index 0b47b05127..994b3c02c0 100644 --- a/lua/entities/gmod_wire_customprop/init.lua +++ b/lua/entities/gmod_wire_customprop/init.lua @@ -14,7 +14,7 @@ local wire_customprops_hullsize_max = CreateConVar("wire_customprops_hullsize_ma local wire_customprops_minvertexdistance = CreateConVar("wire_customprops_minvertexdistance", 0.2, FCVAR_ARCHIVE, "The min distance between two vertices in a custom prop.") local wire_customprops_vertices_max = CreateConVar("wire_customprops_vertices_max", 64, FCVAR_ARCHIVE, "How many vertices custom props can have.", 4) local wire_customprops_convexes_max = CreateConVar("wire_customprops_convexes_max", 16, FCVAR_ARCHIVE, "How many convexes custom props can have.", 1) -local wire_customprops_max = CreateConVar("wire_customprops_max", 10, FCVAR_ARCHIVE, "The maximum number of custom props a player can spawn. (0 to disable)", 0) +local wire_customprops_max = CreateConVar("wire_customprops_max", 16, FCVAR_ARCHIVE, "The maximum number of custom props a player can spawn. (0 to disable)", 0) WireLib = WireLib or {} WireLib.CustomProp = WireLib.CustomProp or {} From b17974568bb9efa1e1607aa3c51278104952b319 Mon Sep 17 00:00:00 2001 From: deltamolfar <72973198+deltamolfar@users.noreply.github.com> Date: Thu, 11 Dec 2025 09:39:15 +0200 Subject: [PATCH 15/16] Lower the default convexes limit --- lua/entities/gmod_wire_customprop/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/entities/gmod_wire_customprop/init.lua b/lua/entities/gmod_wire_customprop/init.lua index 994b3c02c0..6167c7fc70 100644 --- a/lua/entities/gmod_wire_customprop/init.lua +++ b/lua/entities/gmod_wire_customprop/init.lua @@ -13,7 +13,7 @@ local Ent_GetTable = ENT_META.GetTable local wire_customprops_hullsize_max = CreateConVar("wire_customprops_hullsize_max", 2048, FCVAR_ARCHIVE, "The max hull size of a custom prop") local wire_customprops_minvertexdistance = CreateConVar("wire_customprops_minvertexdistance", 0.2, FCVAR_ARCHIVE, "The min distance between two vertices in a custom prop.") local wire_customprops_vertices_max = CreateConVar("wire_customprops_vertices_max", 64, FCVAR_ARCHIVE, "How many vertices custom props can have.", 4) -local wire_customprops_convexes_max = CreateConVar("wire_customprops_convexes_max", 16, FCVAR_ARCHIVE, "How many convexes custom props can have.", 1) +local wire_customprops_convexes_max = CreateConVar("wire_customprops_convexes_max", 12, FCVAR_ARCHIVE, "How many convexes custom props can have.", 1) local wire_customprops_max = CreateConVar("wire_customprops_max", 16, FCVAR_ARCHIVE, "The maximum number of custom props a player can spawn. (0 to disable)", 0) WireLib = WireLib or {} From 70a173aa5627032cce300ddde2ee66b73a6fbb10 Mon Sep 17 00:00:00 2001 From: deltamolfar <72973198+deltamolfar@users.noreply.github.com> Date: Sun, 14 Dec 2025 19:01:52 +0200 Subject: [PATCH 16/16] Throw NULLs instead if nils --- .../gmod_wire_expression2/core/custom/prop.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lua/entities/gmod_wire_expression2/core/custom/prop.lua b/lua/entities/gmod_wire_expression2/core/custom/prop.lua index 53a6390f05..0752966124 100644 --- a/lua/entities/gmod_wire_expression2/core/custom/prop.lua +++ b/lua/entities/gmod_wire_expression2/core/custom/prop.lua @@ -422,20 +422,20 @@ end local function createCustomProp(self, convexes, pos, ang, freeze) if not WireLib.CustomProp.CanSpawn(self.player) then - return self:throw("You have reached the maximum number of custom props you can spawn! (" .. wire_customprops_max:GetInt() .. ")", nil) + return self:throw("You have reached the maximum number of custom props you can spawn! (" .. wire_customprops_max:GetInt() .. ")", NULL) end if not ValidAction(self, nil, "spawn") then return NULL end convexes = castE2ValueToLuaValue(TYPE_TABLE, convexes) - if not isSequentialArray(convexes) then return self:throw("Expected array of convexes (array of arrays of vectors)", nil) end + if not isSequentialArray(convexes) then return self:throw("Expected array of convexes (array of arrays of vectors)", NULL) end -- Add dynamic ops cost, and validate the mesh data structure for k, v in ipairs(convexes) do - if TypeID(v) ~= TYPE_TABLE then return self:throw("Expected array of convexes (array of arrays of vectors)", nil) end + if TypeID(v) ~= TYPE_TABLE then return self:throw("Expected array of convexes (array of arrays of vectors)", NULL) end for k2, v2 in ipairs(v) do - if TypeID(v2) ~= TYPE_VECTOR then return self:throw("Expected array of vertices (array of vectors)", nil) end + if TypeID(v2) ~= TYPE_VECTOR then return self:throw("Expected array of vertices (array of vectors)", NULL) end self.prf = self.prf + 10 -- Subject to change end end @@ -445,7 +445,7 @@ local function createCustomProp(self, convexes, pos, ang, freeze) if not success then -- Remove file/line info from error string local msg = tostring(entity):gsub("^[^:]+:%d+:%s*", "") - self:throw("Failed to spawn custom prop! " .. msg, nil) + self:throw("Failed to spawn custom prop! " .. msg, NULL) end self.player:AddCleanup("gmod_wire_customprop", entity)