Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add baseplate entities #426

Open
wants to merge 32 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
5bf556b
Add a simplified entity registration function
marchc1 Aug 31, 2024
128515c
Fix turret name being wrong and annoying me
marchc1 Aug 31, 2024
e24b43c
Add baseplate entities
marchc1 Aug 31, 2024
ece48e0
Add special use-case capability to tool menu
marchc1 Aug 31, 2024
da22dce
Add baseplate menu
marchc1 Aug 31, 2024
6fc2eee
Fix a mass issue with baseplates specifically
marchc1 Aug 31, 2024
6c6de3e
Conversion function for baseplates
marchc1 Aug 31, 2024
97d1c2c
Localize variables
marchc1 Sep 1, 2024
4d2c267
Remove color that doesnt even work
marchc1 Sep 1, 2024
d66a46a
Use Contraption.SetMass here
marchc1 Sep 1, 2024
97c0ba0
Remove this
marchc1 Sep 1, 2024
b7e772a
Update baseplates.lua
marchc1 Sep 1, 2024
8dae6a2
Various things
marchc1 Sep 2, 2024
6693147
Update cl_init.lua
marchc1 Sep 2, 2024
c4762a5
Remove a debug print
marchc1 Sep 2, 2024
9030273
Remove SProps dependency
marchc1 Dec 18, 2024
e0d56c1
Add check for AdvDupe2 before showing baseplate conversion
marchc1 Dec 18, 2024
a81dccf
SProps dependency
marchc1 Dec 18, 2024
6912acc
I hate the menu system
marchc1 Dec 18, 2024
d037e73
Merge branch 'ACF-Team:master' into baseplate-ents
marchc1 Dec 18, 2024
cf9feaa
fix colors, so Red == Forward, Green == right
marchc1 Dec 18, 2024
7aec2ae
Make contraption_sv support weird ACF entities
marchc1 Dec 18, 2024
41de938
Add ACF_Weighable for ACF entities that can be user-weighed
marchc1 Dec 18, 2024
87efcf5
Revert "Make contraption_sv support weird ACF entities"
marchc1 Dec 20, 2024
cda4b2b
Revert "Add ACF_Weighable for ACF entities that can be user-weighed"
marchc1 Dec 20, 2024
16589d7
This hopefully fixes everything properly
marchc1 Dec 20, 2024
0a268bd
Add documentation to AutoRegister
marchc1 Dec 20, 2024
3f2563e
Match physgun gizmo directions
marchc1 Dec 21, 2024
8fb5995
Slight changes and make gizmo show with ACF tool
marchc1 Dec 21, 2024
ba99587
Last thing hopefully, missed this
marchc1 Dec 21, 2024
0e83700
oml
marchc1 Dec 21, 2024
39b2f85
Make min size 0.5, show decimal places in tooltip, fix orientation of…
marchc1 Dec 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions lua/acf/compatibility/baseplate_convert_sv.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
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

function ACF.ConvertEntityToBaseplate(Player, Target)
if not IsValid(Target) then return end

local Owner = Target:CPPIGetOwner()
if not IsValid(Owner) or Owner ~= Player then return end

local PhysObj = Target:GetPhysicsObject()
if not IsValid(PhysObj) then return end

if Target:GetClass() ~= "prop_physics" then return end

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

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

-- 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

-- 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(0, 0, 0), Angle(0, 0, 0), Vector(0, 0, 0), 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 NewBaseplate
end
2 changes: 1 addition & 1 deletion 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
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
70 changes: 70 additions & 0 deletions lua/acf/menu/items_cl/baseplates.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
local ACF = ACF

local gridMaterial = CreateMaterial("acf_bp_vis_spropgrid1", "VertexLitGeneric", {
["$basetexture"] = "hunter/myplastic",
["$model"] = 1,
["$translucent"] = 1,
["$vertexalpha"] = 1,
["$vertexcolor"] = 1
})

local function CreateMenu(Menu)
ACF.SetToolMode("acf_menu", "Spawner", "Baseplate")
ACF.SetClientData("PrimaryClass", "acf_baseplate")
ACF.SetClientData("SecondaryClass", "N/A")

Menu:AddTitle("Baseplate Settings")

Menu:AddLabel("The root entity of all ACF contraptions.")
local SizeX = Menu:AddSlider("Plate Width (gmu)", 36, 96, 2)
local SizeY = Menu:AddSlider("Plate Length (gmu)", 36, 420, 2)
local SizeZ = Menu:AddSlider("Plate Thickness (gmu)", 0.5, 3, 2)

Menu:AddLabel("Comparing the current dimensions with a 105mm Howitzer:")
local Vis = Menu:AddModelPreview("models/howitzer/howitzer_105mm.mdl", true)
Vis:SetSize(30, 300)
function Vis:PreDrawModel(_)
local w, h, t = SizeX:GetValue(), SizeY:GetValue(), SizeZ:GetValue()
self.CamDistance = math.max(w, h, 60) * 1

render.SetMaterial(gridMaterial)
render.DrawBox(Vector(0, 0, 0), Angle(0, 0, 0), Vector(-h / 2, -w / 2, -t / 2), Vector(h / 2, w / 2, t / 2), color_white)
end

SizeX:SetClientData("Width", "OnValueChanged")
SizeX:DefineSetter(function(Panel, _, _, Value)
local X = math.Round(Value, 2)

Panel:SetValue(X)

return X
end)

SizeY:SetClientData("Length", "OnValueChanged")
SizeY:DefineSetter(function(Panel, _, _, Value)
local Y = math.Round(Value, 2)

Panel:SetValue(Y)

return Y
end)

SizeZ:SetClientData("Thickness", "OnValueChanged")
SizeZ:DefineSetter(function(Panel, _, _, Value)
local Z = math.Round(Value, 2)

Panel:SetValue(Z)

return Z
end)

Menu:AddLabel("You can right click on an entity to replace an existing entity with an ACF Baseplate. " ..
"This will, to the best of its abilities (given you're using a cubical prop, with the long side facing forwards, ex. a SProps plate), replace the entity you're looking at with " ..
"a new ACF baseplate.\n\nIt works by taking an Advanced Duplicator 2 copy of the entire contraption from the target entity, replacing the target entity " ..
"in the dupe's class to acf_baseplate, setting the size based off the physical size of the target entity, then removing all entities and re-pasting the dupe. " ..
"\n\nYou will need to manually re-copy the contraption with the Adv. Dupe 2 tool before using it again, but after that, everything should be converted. This is " ..
"an experimental tool, so if something breaks with an ordinary setup, report it at https://github.com/ACF-Team/ACF-3/issues."
)
end

ACF.AddMenuItem(0, "Entities", "Baseplates", "shape_square", CreateMenu)
3 changes: 2 additions & 1 deletion lua/acf/menu/items_cl/turret_menu.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ local Turrets = ACF.Classes.Turrets
local function CreateMenu(Menu)
local Entries = Turrets.GetEntries()

ACF.SetToolMode("acf_menu", "Spawner", "Component")
ACF.SetToolMode("acf_menu", "Spawner", "Turret")

ACF.SetClientData("PrimaryClass", "N/A")
ACF.SetClientData("SecondaryClass", "N/A")
Expand All @@ -29,6 +29,7 @@ local function CreateMenu(Menu)

ClassDesc:SetText(Data.Description or "No description provided.")

ACF.SetToolMode("acf_menu", "Spawner", Data.ID)
ACF.LoadSortedList(ComponentClass, Data.Items, "Name")
end

Expand Down
Loading
Loading