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

Linkage reworks #419

Merged
merged 12 commits into from
Aug 30, 2024
191 changes: 122 additions & 69 deletions lua/acf/core/utilities/util_sv.lua
Original file line number Diff line number Diff line change
Expand Up @@ -399,97 +399,150 @@ do -- Entity linking
return Result
end

--[[
Example structure of ClassLink:
--- @class LinkFunction
--- @field Ent1 table The first entity in the link
--- @field Ent2 table The second entity in the link
--- @field FromChip boolean If the link is from a chip
--- @return boolean? Success Whether the link was successful
--- @return string? Message A message about the link status

--- Represents information about storing the link on an entity
--- @class LinkStore
--- @field StoreName string Any entity of this class will store references to the other class in this attribute
--- @field Ordered boolean If true, maintains the link as an array rather than a lookup table

--- @class LinkOptions
--- @field Link LinkFunction The function to handle linking
--- @field Unlink LinkFunction The function to handle unlinking
--- @field Check LinkFunction The function to check the link status
--- @field ChipDelay number The delay associated with the link if done via chip
--- @field LinkStore1 LinkStore Information about this class stores links to the other class
--- @field LinkStore2 LinkStore Information about how the other class stores links to this class

local ClassLink = { }

function ACF.GetLinkDataSafe(Class1, Class2)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In terms of naming convention, returning a "Safe" value often means a copy of it. In this case, the actual real value is being returned.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to GetLinkData since its usage requires it to return a real value

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Originally called it "Safe" since it safely checks if the table exists and creates a new one if it doesn't already exist.

if not ClassLink[Class1] then ClassLink[Class1] = {} end
if not ClassLink[Class1][Class2] then ClassLink[Class1][Class2] = {} end
return ClassLink[Class1][Class2]
end

ClassLink = {
["Link"] = {
["acf_ammo"] = {
["acf_gun"] = function(Ent1, Ent2) -- Handles linking guns and ammo
}
},
["Unlink"] = {
["acf_ammo"] = {
["acf_gun"] = function(Ent1, Ent2) -- Handles unlinking guns and ammo
}
}
}
]]--
local ClassLink = { Link = {}, Unlink = {} }
--- Adds a link to this entity's list of links of this LinkData
local function AddLink(Ent, LinkEnt, LinkData)
local StoreName, Ordered = LinkData.StoreName, LinkData.Ordered
if not Ent[StoreName] then Ent[StoreName] = {} end
if Ordered then table.insert(Ent[StoreName], LinkEnt)
else Ent[StoreName][LinkEnt] = true end
end

--- Registers a link or unlink between two classes and how to handle them.
--- Removes a link from this entity's list of links of this LinkData
local function RemoveLink(Ent, LinkEnt, LinkData)
local StoreName, Ordered = LinkData.StoreName, LinkData.Ordered
if not Ent[StoreName] then Ent[StoreName] = {} end
if Ordered then table.RemoveByValue(Ent[StoreName], LinkEnt)
else Ent[StoreName][LinkEnt] = nil end
end

--- Registers that two classes can be linked, as well as how to handle entities of their class being linked.
--- @param Class1 string The first class in the link
--- @param Class2 string The other class in the link
--- @param Function fun(Entity1:table, Entity2:table)
local function RegisterNewLink(Action, Class1, Class2, Function)
if not isfunction(Function) then return end

local Target = ClassLink[Action]
local Data1 = Target[Class1]

if not Data1 then
Target[Class1] = {
[Class2] = function(Ent1, Ent2)
return Function(Ent1, Ent2)
end
}
else
Data1[Class2] = function(Ent1, Ent2)
return Function(Ent1, Ent2)
end
--- @param Function fun(Entity1:table, Entity2:table, FromChip:boolean) The linking function defined between an entity of Class1 and an entity of Class2; this should always return a boolean for link status and a string for link message
function ACF.RegisterClassLink(Class1, Class2, Function)
local LinkData1 = ACF.GetLinkDataSafe(Class1,Class2)
LinkData1.Link = function(Ent1, Ent2, FromChip)
if LinkData1.LinkStore1 then AddLink(Ent1, Ent2, LinkData1.LinkStore1) end
if LinkData1.LinkStore2 then AddLink(Ent1, Ent2, LinkData1.LinkStore2) end
return Function(Ent1, Ent2, FromChip)
end

if Class1 == Class2 then return end

local Data2 = Target[Class2]

if not Data2 then
Target[Class2] = {
[Class1] = function(Ent2, Ent1)
return Function(Ent1, Ent2)
end
}
else
Data2[Class1] = function(Ent2, Ent1)
return Function(Ent1, Ent2)
end
local LinkData2 = ACF.GetLinkDataSafe(Class1,Class2)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this be the same as line 451?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, it should be reversed to

local LinkData2 = ACF.GetLinkDataSafe(Class2,Class1)

LinkData2.Link = function(Ent2, Ent1, FromChip)
if LinkData2.LinkStore1 then AddLink(Ent1, Ent2, LinkData2.LinkStore1) end
if LinkData1.LinkStore2 then AddLink(Ent1, Ent2, LinkData1.LinkStore2) end
return Function(Ent1, Ent2, FromChip)
end
end

--- Registers that two classes can be linked, as well as how to handle entities of their class being linked.
--- Registers that two classes can be unlinked, as well as how to handle entities of their class being unlinked.
--- @param Class1 string The first class in the link
--- @param Class2 string The other class in the link
--- @param Function fun(Entity1:table, Entity2:table) The linking function defined between an entity of Class1 and an entity of Class2; this should always return a boolean for link status and a string for link message
function ACF.RegisterClassLink(Class1, Class2, Function)
RegisterNewLink("Link", Class1, Class2, Function)
--- @param Function fun(Entity1:table, Entity2:table, FromChip:boolean) The unlinking function defined between an entity of Class1 and an entity of Class2
function ACF.RegisterClassUnlink(Class1, Class2, Function)
local LinkData1 = ACF.GetLinkDataSafe(Class1,Class2)
LinkData1.Unlink = function(Ent1, Ent2, FromChip)
if LinkData1.LinkStore1 then RemoveLink(Ent1, Ent2, LinkData1.LinkStore1) end
if LinkData1.LinkStore2 then RemoveLink(Ent1, Ent2, LinkData1.LinkStore2) end
return Function(Ent1, Ent2, FromChip)
end
if Class1 == Class2 then return end
local LinkData2 = ACF.GetLinkDataSafe(Class1,Class2)
LinkData2.Unlink = function(Ent2, Ent1, FromChip)
if LinkData2.LinkStore1 then RemoveLink(Ent1, Ent2, LinkData2.LinkStore1) end
if LinkData1.LinkStore2 then RemoveLink(Ent1, Ent2, LinkData1.LinkStore2) end
return Function(Ent1, Ent2, FromChip)
end
end

--- Returns the callback defined previously by ACF.RegisterClassLink between Class1 and Class2.
--- Registers a validation check between two classes.
--- @param Class1 string The first class in the link
--- @param Class2 string The other class in the link
--- @return fun(Entity1:table, Entity2:table) | nil # The linking function defined between an entity of Class1 and an entity of Class2, or nil if Class1 has no linking functions
function ACF.GetClassLink(Class1, Class2)
if not ClassLink.Link[Class1] then return end

return ClassLink.Link[Class1][Class2]
--- @param Function fun(Entity1:table, Entity2:table, FromChip:boolean) The checking function defined between an entity of Class1 and an entity of Class2
function ACF.RegisterClassCheck(Class1, Class2, Function)
ACF.GetLinkDataSafe(Class1,Class2).Check = function(Ent1, Ent2, FromChip) return Function(Ent1, Ent2, FromChip) end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Readability, localize the result from ACF.GetLinkDataSafe and then assign it's Check value.

if Class1 == Class2 then return end
ACF.GetLinkDataSafe(Class2,Class1).Check = function(Ent2, Ent1, FromChip) return Function(Ent1, Ent2, FromChip) end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Readability, localize the result from ACF.GetLinkDataSafe and then assign it's Check value.

end

--- Registers that two classes can be unlinked, as well as how to handle entities of their class being unlinked.
--- Registers a chip delay between two classes.
--- @param Class1 string The first class in the link
--- @param Class2 string The other class in the link
--- @param Function fun(Entity1:table, Entity2:table) The unlinking function defined between an entity of Class1 and an entity of Class2
function ACF.RegisterClassUnlink(Class1, Class2, Function)
RegisterNewLink("Unlink", Class1, Class2, Function)
--- @param ChipDelay number If the link happens from the chip, then delay it by this amount
function ACF.RegisterClassDelay(Class1, Class2, ChipDelay)
ACF.GetLinkDataSafe(Class1,Class2).ChipDelay = ChipDelay
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Readability, localize the result from ACF.GetLinkDataSafe and then assign it's ChipDelay value.

if Class1 == Class2 then return end
ACF.GetLinkDataSafe(Class2,Class1).ChipDelay = ChipDelay
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Readability, localize the result from ACF.GetLinkDataSafe and then assign it's ChipDelay value.

end

--- Returns the callback defined previously by ACF.RegisterClassUnlink between Class1 and Class2.
--- @param Class1 string The first class in the link
--- @param Class2 string The other class in the link
--- @return fun(Entity1:table, Entity2:table) | nil # The unlinking function defined between an entity of Class1 and an entity of Class2, or nil if Class1 has no unlinking functions
function ACF.GetClassUnlink(Class1, Class2)
if not ClassLink.Unlink[Class1] then return end
function ACF.RegisterClassStore1(Class1, Class2, LinkStore)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to have these functions being separated?

ACF.GetLinkDataSafe(Class1,Class2).LinkStore1 = LinkStore
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Readability, same reason as before.

if Class1 == Class2 then return end
ACF.GetLinkDataSafe(Class2,Class1).LinkStore2 = LinkStore
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Readability, same reason as before.

end

return ClassLink.Unlink[Class1][Class2]
function ACF.RegisterClassStore2(Class1, Class2, LinkStore)
ACF.GetLinkDataSafe(Class1,Class2).LinkStore2 = LinkStore
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Readability, same reason as before.

if Class1 == Class2 then return end
ACF.GetLinkDataSafe(Class2,Class1).LinkStore1 = LinkStore
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Readability, same reason as before.

end

--- Saves a link in an entity's AD2 entity modifiers
function ACF.AD2SaveLinks(Entity, StoreName, SaveName)
local tbl = {}

if table.IsSequential(Entity[StoreName]) then
for _,ent in ipairs(Entity[StoreName]) do table.insert(tbl,ent:EntIndex()) end
else
for ent,_ in pairs(Entity[StoreName]) do table.insert(tbl,ent:EntIndex()) end
end

duplicator.StoreEntityModifier(Entity, SaveName, tbl)
end

--- Loads a link from an entity's AD2 entity modifiers
function ACF.AD2LoadLinks(Entity, SaveName, EntMods, CreatedEntities)
if EntMods[SaveName] then
for _, id in ipairs(EntMods[SaveName]) do Entity:Link(CreatedEntities[id]) end
EntMods[SaveName] = nil
end
end

--- Unlinks every entity from this entity of this link type
function ACF.UnlinkAll(Entity, StoreName)
if table.IsSequential(Entity[StoreName]) then
for _, ent in ipairs(Entity[StoreName]) do Entity:Unlink(ent) end
else
for ent, _ in pairs(Entity[StoreName]) do Entity:Unlink(ent) end
end
end
end

Expand Down
25 changes: 18 additions & 7 deletions lua/entities/acf_base_scalable/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -61,18 +61,31 @@ end ---------------------------------------------
do -- Entity linking and unlinking --------------
local LinkText = "%s can't be linked to %s."
local UnlinkText = "%s can't be unlinked from %s."
local timer = timer

function ENT:Link(Target)
function ENT:Link(Target, FromChip)
if not IsValid(Target) then return false, "Attempted to link an invalid entity." end
if self == Target then return false, "Can't link an entity to itself." end

local Class = Target:GetClass()
local Function = ACF.GetClassLink(self:GetClass(), Class)
local LinkData = ACF.GetLinkDataSafe(self:GetClass(), Class)
local Function = LinkData.Link
local Check = LinkData.Check
local ChipDelay = LinkData.ChipDelay

if Function then
if Check then
local result, message = Check(self, Target)
if result then
if FromChip and ChipDelay then
timer.Simple(ChipDelay,function()
if Check(self, Target) then Function(self, Target) end
end)
else Function(self,Target) end
end
return result, message
end
return Function(self, Target)
elseif self.DefaultLink then
return self:DefaultLink(Target)
end

return false, LinkText:format(self.PluralName, Target.PluralName or Class)
Expand All @@ -83,12 +96,10 @@ do -- Entity linking and unlinking --------------
if self == Target then return false, "Can't unlink an entity from itself." end

local Class = Target:GetClass()
local Function = ACF.GetClassUnlink(self:GetClass(), Class)
local Function = ACF.GetLinkDataSafe(self:GetClass(), Class).Unlink

if Function then
return Function(self, Target)
elseif self.DefaultUnlink then
return self:DefaultUnlink(Target)
end

return false, UnlinkText:format(self.PluralName, Target.PluralName or Class)
Expand Down
23 changes: 18 additions & 5 deletions lua/entities/acf_base_simple/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -61,18 +61,31 @@ end ---------------------------------------------
do -- Entity linking and unlinking --------------
local LinkText = "%s can't be linked to %s."
local UnlinkText = "%s can't be unlinked from %s."
local timer = timer

function ENT:Link(Target)
function ENT:Link(Target, FromChip)
if not IsValid(Target) then return false, "Attempted to link an invalid entity." end
if self == Target then return false, "Can't link an entity to itself." end

local Class = Target:GetClass()
local Function = ACF.GetClassLink(self:GetClass(), Class)
local LinkData = ACF.GetLinkDataSafe(self:GetClass(), Class)
local Function = LinkData.Link
local Check = LinkData.Check
local ChipDelay = LinkData.ChipDelay

if Function then
if Check then
local result, message = Check(self, Target)
if result then
if FromChip and ChipDelay then
timer.Simple(ChipDelay,function()
if Check(self, Target) then Function(self, Target) end
end)
else Function(self,Target) end
end
return result, message
end
return Function(self, Target)
elseif self.DefaultLink then
return self:DefaultLink(Target)
end

return false, LinkText:format(self.PluralName, Target.PluralName or Class)
Expand All @@ -83,7 +96,7 @@ do -- Entity linking and unlinking --------------
if self == Target then return false, "Can't unlink an entity from itself." end

local Class = Target:GetClass()
local Function = ACF.GetClassUnlink(self:GetClass(), Class)
local Function = ACF.GetLinkDataSafe(self:GetClass(), Class).Unlink

if Function then
return Function(self, Target)
Expand Down
Loading
Loading