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..fcb2c2716f --- /dev/null +++ b/lua/entities/gmod_wire_customprop/cl_init.lua @@ -0,0 +1,159 @@ +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) +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) + 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 } + else + ent_tbl.custom_mesh = nil + end + else + return { Mesh = ent_tbl.rendermesh, Material = ent_tbl.Material } + 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) + + 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 +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..6167c7fc70 --- /dev/null +++ b/lua/entities/gmod_wire_customprop/init.lua @@ -0,0 +1,237 @@ +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 + +-- 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) +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 {} +WireLib.CustomProp = WireLib.CustomProp or {} + +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 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 = 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) + local maxHullsize = wire_customprops_hullsize_max:GetFloat() + local quantMinX, quantMinY, quantMinZ = -maxHullsize, -maxHullsize, -maxHullsize + local quantMaxX, quantMaxY, quantMaxZ = maxHullsize, maxHullsize, maxHullsize + + local buffer = {} + + table.insert(buffer, shared.writeInt16(#meshConvexes)) + for _, convex in ipairs(meshConvexes) do + table.insert(buffer, shared.writeInt16(#convex)) + for _, vertex in ipairs(convex) do + 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 + + 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.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 + 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:SetPos(pos) + propent:SetAngles(ang) + 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 + 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.CustomProp.Create, "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..59f6c99c5a --- /dev/null +++ b/lua/entities/gmod_wire_customprop/shared.lua @@ -0,0 +1,95 @@ +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 + +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", + + 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, + + writeInt16 = writeInt16, + + readInt16 = readInt16, + + quantizeFloat16 = quantizeFloat16, + + dequantizeFloat16 = dequantizeFloat16, + + writeQuantizedFloat16 = function(f, min, max) + local i = quantizeFloat16(f, min, max) + return writeInt16(i) + 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 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..4a761fe897 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,20 @@ 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 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["customPropIsEnabled()"] = "Returns 1 if custom props are enabled, 0 otherwise." +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 f0e0146dbd..53a6390f05 100644 --- a/lua/entities/gmod_wire_expression2/core/custom/prop.lua +++ b/lua/entities/gmod_wire_expression2/core/custom/prop.lua @@ -398,6 +398,71 @@ end local CreateSent = PropCore.CreateSent +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 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) + 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 + + -- 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*", "") + self:throw("Failed to spawn custom prop! " .. msg, nil) + 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 + + self.player.customPropLastSpawn = CurTime() + self.data.spawnedProps[entity] = self.data.propSpawnUndo + + return entity +end + -------------------------------------------------------------------------------- __e2setcost(40) e2function entity propSpawn(string model, number frozen) @@ -695,6 +760,78 @@ end -------------------------------------------------------------------------------- +__e2setcost(900) +e2function entity customPropSpawn(table convexes) + 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) + return createCustomProp(self, convexes, Vector(pos[1], pos[2], pos[3]), Angle(ang[1], ang[2], ang[3]), frozen) +end + +-------------------------------------------------------------------------------- + +__e2setcost(1) +[nodiscard] +e2function number customPropCanCreate() + return self.player.customPropsSpawned < wire_customprops_max:GetInt() and 1 or 0 +end + +[nodiscard] +e2function number customPropIsEnabled() + return wire_expression2_propcore_customprops_enabled:GetBool() and 1 or 0 +end + +[nodiscard] +e2function number customPropsLeft() + return math.max(0, math.floor(wire_customprops_max:GetInt() - (self.player.customPropsSpawned or 0))) +end + +[nodiscard] +e2function number customPropsMax() + return wire_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) e2function void entity:propDelete() if not ValidAction(self, this, "delete") then return end @@ -707,7 +844,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]