Skip to content

Commit

Permalink
Merge branch 'master' into scalable-gearboxes
Browse files Browse the repository at this point in the history
  • Loading branch information
thecraftianman committed Jan 11, 2025
2 parents 28a6588 + db71a5b commit 325ec11
Show file tree
Hide file tree
Showing 28 changed files with 1,308 additions and 132 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ Armored Combat Framework (ACF) is an addon for Garry's Mod that implements a dam

Ammunition is customizable with varying ballistic performance along with armor being customizable for protection against various threats. ACF is intended to be balanced for multiplayer and competitive use but there is, of course, nothing wrong with blasting some NPCs.

![ACF Logo](acf-logo.png)
![ACF Logo](acf-logo-dark.png#gh-light-mode-only)
![ACF Logo](acf-logo-light.png#gh-dark-mode-only)

## Installation

Expand Down
File renamed without changes
Binary file added acf-logo-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
87 changes: 87 additions & 0 deletions lua/acf/compatibility/baseplate_convert_sv.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
local ACF = ACF

local RecursiveEntityRemove
function RecursiveEntityRemove(ent, track)
track = track or {}
if track[ent] == true then return end
local constrained = constraint.GetAllConstrainedEntities(ent)
ent:Remove()
track[ent] = true
for k, _ in pairs(constrained) do
if k ~= ent then RecursiveEntityRemove(k, track) end
end
end

local bpConvertibleModelPaths = {
{
startWith = "models/sprops/rectangles",
addAngles = Angle(0, 0, 0)
}
}

function ACF.ConvertEntityToBaseplate(Player, Target)
if not AdvDupe2 then return false, "Advanced Duplicator 2 is not installed" end

if not IsValid(Target) then return false, "Invalid target" end

local Owner = Target:CPPIGetOwner()
if not IsValid(Owner) or Owner ~= Player then return false, "You do not own this entity" end

local PhysObj = Target:GetPhysicsObject()
if not IsValid(PhysObj) then return false, "Entity is not physical" end

if Target:GetClass() ~= "prop_physics" then return false, "Entity must be typeof 'prop_physics'" end

local foundTranslation
local targetModel = Target:GetModel()

for _, v in ipairs(bpConvertibleModelPaths) do
if string.StartsWith(targetModel, v.startWith) then
foundTranslation = v
break
end
end

if not foundTranslation then return false, "Incompatible model '" .. targetModel .. "'" end

local AMi, AMa = PhysObj:GetAABB()
local BoxSize = AMa - AMi

-- Duplicate the entire thing
local Entities, Constraints = AdvDupe2.duplicator.Copy(Player, Target, {}, {}, vector_origin)

-- Find the baseplate
local Baseplate = Entities[Target:EntIndex()]

-- Setup the dupe table to convert it to a baseplate
local w, l, t = BoxSize.x, BoxSize.y, BoxSize.z
Baseplate.Class = "acf_baseplate"
Baseplate.Length = w
Baseplate.Width = l
Baseplate.Thickness = t
Baseplate.PhysicsObjects[0].Angle = Baseplate.PhysicsObjects[0].Angle + foundTranslation.addAngles

-- Delete everything now
for k, _ in pairs(Entities) do
local e = Entity(k)
if IsValid(e) then e:Remove() end
end

-- Paste the stuff back to the dupe
local Ents = AdvDupe2.duplicator.Paste(Owner, Entities, Constraints, vector_origin, angle_zero, vector_origin, true)
-- Try to find the baseplate
local NewBaseplate
for _, v in pairs(Ents) do
if v:GetClass() == "acf_baseplate" and v:GetPos() == Baseplate.Pos then
NewBaseplate = v
break
end
end

undo.Create("acf_baseplate")
undo.AddEntity(NewBaseplate)
undo.SetPlayer(Player)
undo.Finish()

return true, NewBaseplate
end
5 changes: 3 additions & 2 deletions lua/acf/contraption/contraption_sv.lua
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ do -- ASSUMING DIRECT CONTROL
local Ent = self:GetEntity()

-- Required due for AD2 support, if this isn't present then entities will never get set to their required weight on dupe paste
if Ent.IsACFEntity then Contraption.SetMass(Ent, Ent.ACF.Mass) return end
if Ent.IsACFEntity and not Ent.ACF_UserWeighable then Contraption.SetMass(Ent, Ent.ACF.Mass) return end

if Ent.ACF_OnMassChange then
Ent:ACF_OnMassChange(self:GetMass(), Mass)
Expand Down Expand Up @@ -351,7 +351,8 @@ do -- ASSUMING DIRECT CONTROL
end

function ENT:SetNotSolid(...)
if self.IsACFEntity then ACF.CheckLegal(self) end
-- NOTE: Slight delay added to this check in order to account for baseplate conversion otherwise failing
if self.IsACFEntity and ACF.LegalChecks then timer.Simple(0, function() ACF.CheckLegal(self) end) end

SetNotSolid(self, ...)
end
Expand Down
184 changes: 181 additions & 3 deletions lua/acf/core/classes/entities/registration.lua
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ local function GetEntityTable(Class)

if not Data then
Data = {
Lookup = {},
Count = 0,
List = {},
Lookup = {},
Count = 0,
List = {},
Restrictions = {}
}

Entries[Class] = Data
Expand Down Expand Up @@ -55,11 +56,188 @@ local function AddArguments(Entity, Arguments)
return List
end

local ArgumentTypes = {}

local function AddArgumentRestrictions(Entity, ArgumentRestrictions)
local Restrictions = Entity.Restrictions

for k, v in pairs(ArgumentRestrictions) do
if not v.Type then error("Argument '" .. tostring(k or "<NIL>") .. "' didn't have a Type!") end
if not isstring(v.Type) then error("Argument '" .. tostring(k or "<NIL>") .. "' has a non-string Type! (" .. tostring(v.Type) .. ")") end
if not ArgumentTypes[v.Type] then error("Argument '" .. tostring(k or "<NIL>") .. "' has a non-registered Type! (" .. tostring(v.Type) .. ")") end

Restrictions[k] = v
end
end


--- Adds an argument type and verifier to the ArgumentTypes dictionary.
--- @param Type string The type of data
--- @param Verifier function The verification function. Arguments are: Value:any, Restrictions:table. Must return a Value of the same type and NOT nil!
function Entities.AddArgumentType(Type, Verifier)
if ArgumentTypes[Type] then return end

ArgumentTypes[Type] = Verifier
end

Entities.AddArgumentType("Number", function(Value, Specs)
if not isnumber(Value) then Value = ACF.CheckNumber(Value, Specs.Default or 0) end

if Specs.Decimals then Value = math.Round(Value, Specs.Decimals) end
if Specs.Min then Value = math.max(Value, Specs.Min) end
if Specs.Max then Value = math.min(Value, Specs.Max) end

return Value
end)

--- Adds extra arguments to a class which has been created via Entities.AutoRegister() (or Entities.Register() with no arguments)
--- @param Class string A class previously registered as an entity class
--- @param DataKeys table A key-value table, where key is the name of the data and value defines the type and restrictions of the data.
function Entities.AddStrictArguments(Class, DataKeys)
if not isstring(Class) then return end

local Entity = GetEntityTable(Class)
local Arguments = table.GetKeys(DataKeys)
local List = AddArguments(Entity, Arguments)
AddArgumentRestrictions(Entity, DataKeys)
return List
end

-- Automatically registers an entity. This MUST be the last line in entity/init.lua for everything to work properly
-- Can be passed with an ENT table if you have some weird usecase, but auto defaults to _G.ENT
--- @param ENT table A scripted entity class definition (see https://wiki.facepunch.com/gmod/Structures/ENT)
function Entities.AutoRegister(ENT)
if ENT == nil then ENT = _G.ENT end
if not ENT then error("Called Entities.AutoRegister(), but no entity was in the process of being created.") end

local Class = string.Split(ENT.Folder, "/"); Class = Class[#Class]
ENT.ACF_Class = Class

local Entity = GetEntityTable(Class)
local ArgsList = Entities.AddStrictArguments(Class, ENT.ACF_DataKeys or {})

if CLIENT then return end

if isnumber(ENT.ACF_Limit) then
CreateConVar(
"sbox_max_" .. Class,
ENT.ACF_Limit,
FCVAR_ARCHIVE + FCVAR_NOTIFY,
"Maximum amount of " .. (ENT.PluralName or (Class .. " entities")) .. " a player can create."
)
end

-- Verification function
local function VerifyClientData(ClientData)
local Entity = GetEntityTable(Class)
local List = Entity.List
local Restrictions = Entity.Restrictions

for _, argName in ipairs(List) do
if Restrictions[argName] then
local RestrictionSpecs = Restrictions[argName]
if not ArgumentTypes[RestrictionSpecs.Type] then error("No verification function for type '" .. tostring(RestrictionSpecs.Type or "<NIL>") .. "'") end
ClientData[argName] = ArgumentTypes[RestrictionSpecs.Type](ClientData[argName], RestrictionSpecs)
end
end

if ENT.ACF_OnVerifyClientData then ENT.ACF_OnVerifyClientData(ClientData) end
end

local function UpdateEntityData(self, ClientData)
local Entity = GetEntityTable(Class)
local List = Entity.List

if self.ACF_PreUpdateEntityData then self:ACF_PreUpdateEntityData(ClientData) end
self.ACF = self.ACF or {}
for _, v in ipairs(List) do
self[v] = ClientData[v]
end

if self.ACF_PostUpdateEntityData then self:ACF_PostUpdateEntityData(ClientData) end

ACF.Activate(self, true)
end

function ENT:Update(ClientData)
VerifyClientData(ClientData)

hook.Run("ACF_OnEntityLast", Class, self)

ACF.SaveEntity(self)
UpdateEntityData(self, ClientData)
ACF.RestoreEntity(self)

hook.Run("ACF_OnEntityUpdate", Class, self, ClientData)
if self.UpdateOverlay then self:UpdateOverlay(true) end
net.Start("ACF_UpdateEntity")
net.WriteEntity(self)
net.Broadcast()

return true, (self.PrintName or Class) .. " updated successfully!"
end

local ACF_Limit = ENT.ACF_Limit
function Entity.Spawn(Player, Pos, Angle, ClientData)
if ACF_Limit then
if isfunction(ACF_Limit) then
if not ACF_Limit() then return end
elseif isnumber(ACF_Limit) then
if not Player:CheckLimit("_" .. Class) then return false end
end
end

local CanSpawn = hook.Run("ACF_PreEntitySpawn", Class, Player, ClientData)
if CanSpawn == false then return false end

local New = ents.Create(Class)
if not IsValid(New) then return end

VerifyClientData(ClientData)

New:SetPos(Pos)
New:SetAngles(Angle)
if New.ACF_PreSpawn then
New:ACF_PreSpawn(Player, Pos, Angle, ClientData)
end

New:SetPlayer(Player)
New:Spawn()
Player:AddCount("_" .. Class, New)
Player:AddCleanup("_" .. Class, New)
New.Owner = Player -- MUST be stored on ent for PP
New.DataStore = Entities.GetArguments(Class)

hook.Run("ACF_OnEntitySpawn", Class, New, ClientData)

if New.ACF_PostSpawn then
New:ACF_PostSpawn(Player, Pos, Angle, ClientData)
end

New:ACF_UpdateEntityData(ClientData)
if New.UpdateOverlay then New:UpdateOverlay(true) end
ACF.CheckLegal(New)

return New
end

ENT.ACF_VerifyClientData = VerifyClientData
ENT.ACF_UpdateEntityData = UpdateEntityData

duplicator.RegisterEntityClass(Class, Entity.Spawn, "Pos", "Angle", "Data", unpack(ArgsList))
end

--- Registers a class as a spawnable entity class
--- @param Class string The class to register
--- @param Function fun(Player:entity, Pos:vector, Ang:angle, Data:table):Entity A function defining how to spawn your class (This should be your MakeACF_<something> function)
--- @param ... any #A vararg of arguments to attach to the entity
function Entities.Register(Class, Function, ...)
if Class == nil and Function == nil then
-- Calling Entities.Register with no arguments performs an automatic registration
Entities.AutoRegister(ENT)
return
end

if not isstring(Class) then return end
if not isfunction(Function) then return end

Expand Down
Loading

0 comments on commit 325ec11

Please sign in to comment.