ACE Menu System ‐ ‐ Add Your Entities - ACE-Project-Team/ArmoredCombatExtended GitHub Wiki

Overview

The ACE menu uses a tree structure defined in lua/acf/client/cl_acfmenu_gui.lua. Items are registered through definition functions in lua/acf/shared/sh_ace_loader.lua.

File Structure

lua/
├── acf/
│ ├── shared/
│ │ ├── sh_ace_loader.lua # Definition functions & loader
│ │ ├── entities/ # Entity definitions
│ │ │ └── youritem.lua
│ │ └── tools/ # Tool definitions
│ │ └── yourtool.lua
│ └── client/
│ └── cl_acfmenu_gui.lua # Menu GUI
└── entities/
└── ace_yourentity/ # Entity code
├── init.lua
├── cl_init.lua
└── shared.lua

Adding to an Existing Category

If you want to add an item to an existing category (like Entities or Tools), you only need to create a definition file.

Example: Adding to Entities Category

Create lua/acf/shared/entities/myitem.lua:

ACF_DefineEntity("MyItemId", {
    name = "My Item Name",
    ent = "ace_myentity",
    category = "Misc",  -- Sub-folder: "Crew" or "Misc"
    desc = "Description here.\n\nSupports multiple lines.",
    model = "models/props_c17/oildrum001.mdl",
    weight = 10,
    acepoints = 0,
})

Available Categories

Entities - Use ACF_DefineEntity()

  • Sub-categories: Crew, Misc

Tools - Use ACF_DefineVHeatSource()

  • No sub-categories

Creating a New Category

Step 1: Update sh_ace_loader.lua

Add at the top with other tables:

local MyCategory = {}

Add base class:

local mycategory_base = {
    type = "MyCategory"
}

Add GUI binding in the if CLIENT then block:

mycategory_base.guicreate = function(_, Table) MyCategoryGUICreate(Table) end or nil
mycategory_base.guiupdate = function() return end

Add definition function:

function ACF_DefineMyCategory(id, data)
    data.id = id
    table.Inherit(data, mycategory_base)
    MyCategory[id] = data
end

Add folder to loader:

local folders = { -- ... existing folders ... "mycategory" }

Export at bottom:

ACF.Weapons.MyCategory = MyCategory

Step 2: Add Menu Node (cl_acfmenu_gui.lua)

Simple Category

do
    local myNode = HomeNode:AddNode("My Category", "icon16/brick.png")

    for _, ItemData in pairs(FinalContainer["MyCategory"] or {}) do
        local ItemNode = myNode:AddNode(ItemData.name or "No Name", ItemIcon2)
        ItemNode.mytable = ItemData

        function ItemNode:DoClick()
            RunConsoleCommand("acfmenu_type", self.mytable.type)
            acfmenupanel:UpdateDisplay(self.mytable)
        end
    end
end

Category with Sub-folders

do
    local myNode = HomeNode:AddNode("My Category", "icon16/brick.png")
    local subA = myNode:AddNode("Sub A", "icon16/folder.png")
    local subB = myNode:AddNode("Sub B", "icon16/folder.png")

    for _, ItemData in pairs(FinalContainer["MyCategory"] or {}) do
        local targetNode

        if ItemData.category == "SubA" then
            targetNode = subA
        elseif ItemData.category == "SubB" then
            targetNode = subB
        else
            targetNode = myNode
        end

        local ItemNode = targetNode:AddNode(ItemData.name or "No Name", ItemIcon2)
        ItemNode.mytable = ItemData

        function ItemNode:DoClick()
            RunConsoleCommand("acfmenu_type", self.mytable.type)
            acfmenupanel:UpdateDisplay(self.mytable)
        end
    end
end

Step 3: Create GUI Function (cl_acfmenu_gui.lua)

Add at the end of file:

function MyCategoryGUICreate(Table)
    acfmenupanel:CPanelText("Name", Table.name, "DermaDefaultBold")

    if Table.model then
        acfmenupanel.CData.DisplayModel = vgui.Create("DModelPanel", acfmenupanel.CustomDisplay)
        acfmenupanel.CData.DisplayModel:SetModel(Table.model)
        acfmenupanel.CData.DisplayModel:SetCamPos(Vector(50, 50, 40))
        acfmenupanel.CData.DisplayModel:SetLookAt(Vector(0, 0, 10))
        acfmenupanel.CData.DisplayModel:SetFOV(35)
        acfmenupanel.CData.DisplayModel:SetSize(acfmenupanel:GetWide(), acfmenupanel:GetWide() * 0.5)
        acfmenupanel.CData.DisplayModel.LayoutEntity = function() end
        acfmenupanel.CustomDisplay:AddItem(acfmenupanel.CData.DisplayModel)
    end

    acfmenupanel:CPanelText("Desc", "\n" .. Table.desc)

    if Table.weight then
        acfmenupanel:CPanelText("Weight", "\nWeight: " .. Table.weight .. " kg")
    end

    acfmenupanel.CustomDisplay:PerformLayout()
end

Creating the Entity

shared.lua

ENT.Type            = "anim"
ENT.Base            = "base_wire_entity"
ENT.PrintName       = "ACE My Entity"
ENT.WireDebugName   = "ACE My Entity"
ENT.Author          = "Your Name"
ENT.Category        = "ACE - Entities"
ENT.Spawnable       = false
ENT.AdminSpawnable  = false

DEFINE_BASECLASS("base_wire_entity")

### cl_init.lua

include("shared.lua")

local ACF_InfoWhileSeated = CreateClientConVar("ACF_GunInfoWhileSeated", 0, true, false)

function ENT:Draw()
    local lply = LocalPlayer()
    local hideBubble = not ACF_InfoWhileSeated:GetBool() and IsValid(lply) and lply:InVehicle()

    self.BaseClass.DoNormalDraw(self, false, hideBubble)
    Wire_Render(self)
end

init.lua

AddCSLuaFile("cl_init.lua")
AddCSLuaFile("shared.lua")

include("shared.lua")

DEFINE_BASECLASS("base_wire_entity")

local EntityTable = ACF.Weapons.MyCategory

function ENT:Initialize()
    self.Inputs = WireLib.CreateInputs(self, {})
    self.Outputs = WireLib.CreateOutputs(self, {
        "MyOutput (Description)",
    })
    self:UpdateOverlayText()
end

function MakeACE_MyEntity(Owner, Pos, Angle, Id, EntityData)
    if not Owner:CheckLimit("_ace_myentity") then return false end

    Id = Id or "MyItemId"

    local entData = EntityTable[Id]
    if not entData then return false end

    local ent = ents.Create("ace_myentity")
    if not IsValid(ent) then return false end

    ent:SetAngles(Angle)
    ent:SetPos(Pos)
    ent.Model = entData.model
    ent.Weight = entData.weight
    ent.ACFName = entData.name
    ent.ACEPoints = entData.acepoints or 0
    ent.Id = Id

    ent:Spawn()
    ent:CPPISetOwner(Owner)
    ent:SetNWNetwork()
    ent:SetModelEasy(entData.model)

    Owner:AddCount("_ace_myentity", ent)
    Owner:AddCleanup("acfmenu", ent)

    return ent
end

list.Set("ACFCvars", "ace_myentity", {"id", "entitydata"})
duplicator.RegisterEntityClass("ace_myentity", MakeACE_MyEntity, "Pos", "Angle", "Id", "Data")

function ENT:SetNWNetwork()
    self:SetNWString("WireName", self.ACFName or "My Entity")
end

function ENT:SetModelEasy(mdl)
    self:SetModel(mdl)
    self.Model = mdl
    self:PhysicsInit(SOLID_VPHYSICS)
    self:SetMoveType(MOVETYPE_VPHYSICS)
    self:SetSolid(SOLID_VPHYSICS)

    local phys = self:GetPhysicsObject()
    if IsValid(phys) then
        phys:SetMass(self.Weight or 10)
    end
end

function ENT:UpdateOverlayText()
    self:SetOverlayText("My Entity\n\nStatus: Active")
end

function ENT:Think()
    self:UpdateOverlayText()
    self:NextThink(CurTime() + 0.1)
    return true
end

GUI Helper Reference

Text

acfmenupanel:CPanelText("Id", "Text") acfmenupanel:CPanelText("Id", "Bold", "DermaDefaultBold")

Slider

acfmenupanel:AmmoSlider("VarName", default, min, max, decimals, "Title", "Desc")

Checkbox

acfmenupanel:AmmoCheckbox("VarName", "Title", "Desc", "Tooltip")

Dropdown

local combo = vgui.Create("DComboBox", acfmenupanel.CustomDisplay) combo:SetSize(acfmenupanel.CustomDisplay:GetWide(), 30) combo:AddChoice("Option", "value") combo.OnSelect = function(_, _, value) RunConsoleCommand("acfmenu_data", value) end acfmenupanel.CustomDisplay:AddItem(combo)


Icon Reference

  • icon16/brick.png - Generic item
  • icon16/bricks.png - Category
  • icon16/cog.png - Misc/settings
  • icon16/user.png - Crew
  • icon16/transmit.png - Sensors
  • icon16/car.png - Mobility
  • icon16/box.png - Ammo
  • icon16/folder.png - Sub-folder
  • icon16/plugin.png - Tools

Checklist

When adding to existing category:

  • Definition file in lua/acf/shared/category/
  • Entity folder with init.lua, cl_init.lua, shared.lua

When creating new category (additional steps):

  • Storage table in sh_ace_loader.lua
  • Base class and GUI binding
  • Definition function
  • Folder in loader list
  • Export table
  • Menu node in cl_acfmenu_gui.lua
  • GUI create function