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
105 changes: 51 additions & 54 deletions lua/acf/core/utilities/util_sv.lua
Original file line number Diff line number Diff line change
Expand Up @@ -415,81 +415,78 @@ do -- Entity linking
}
}
]]--
local ClassLink = { Link = {}, Unlink = {} }

--- Registers a link or unlink between two classes and how to handle them.
--- @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

--- @class LinkData
--- @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

--- @type table<string,table<string,LinkData>>
local ClassLink = { }

--- Initializes a link in the ClassLink table if it doesn't already exist and returns the result.
--- The Link is initialized directionally (InitLink(Class1,Class2) != InitLink(Class2,Class1))
--- @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
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
end
--- @return LinkData? LinkData The returned link
function ACF.InitLink(Class1, Class2)
if not ClassLink[Class1] then ClassLink[Class1] = {} end
if not ClassLink[Class1][Class2] then ClassLink[Class1][Class2] = {} end
return ClassLink[Class1][Class2]
end

--- Registers that two classes can be linked, as well as how to handle entities of their class being linked.
--- Attempts to retrieve link information from Class 1 to Class2, otherwise tries Class 2 to Class1. If link exists in either direction, return nil.
--- @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)
--- @return LinkData? LinkData The returned link
--- @return boolean Reversed Whether you should reverse your entity arguments when calling with entities
function ACF.GetClassLink(Class1, Class2)
if ClassLink[Class1] ~= nil and ClassLink[Class1][Class2] ~= nil then return ClassLink[Class1][Class2], false end
Copy link
Member

Choose a reason for hiding this comment

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

Does ClassLink store any booleans that could end up being false? If not, comparing if X ~= nil is unnecesary, you can just do if X on that case

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I hallucinated this not working but it seems it works now. The other ones with ~= nil will be fixed.

if ClassLink[Class2] ~= nil and ClassLink[Class2][Class1] ~= nil then return ClassLink[Class2][Class1], true end
return nil, false
end

--- Returns the callback defined previously by ACF.RegisterClassLink between Class1 and Class2.
--- 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
--- @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 LinkFunction 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 LinkData = ACF.InitLink(Class1, Class2)
LinkData.Link = Function
end

--- 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 unlinking function defined between an entity of Class1 and an entity of Class2
--- @param Function LinkFunction 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)
local LinkData = ACF.InitLink(Class1, Class2)
LinkData.Unlink = Function
end

--- Returns the callback defined previously by ACF.RegisterClassUnlink 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 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
--- @param Function LinkFunction The checking function defined between an entity of Class1 and an entity of Class2
function ACF.RegisterClassCheck(Class1, Class2, Function)
local LinkData = ACF.InitLink(Class1, Class2)
LinkData.Check = Function
end

return ClassLink.Unlink[Class1][Class2]
--- 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 ChipDelay number If the link happens from the chip, then delay it by this amount
function ACF.RegisterClassDelay(Class1, Class2, ChipDelay)
local LinkData = ACF.InitLink(Class1, Class2)
LinkData.ChipDelay = ChipDelay
end
end

Expand Down
41 changes: 32 additions & 9 deletions lua/entities/acf_base_scalable/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -61,18 +61,37 @@ 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, Reversed = ACF.GetClassLink(self:GetClass(), Class)

if LinkData == nil then return false, "Links between these two entities are impossible" end
Copy link
Member

Choose a reason for hiding this comment

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

If LinkData never actually receives a false value as a valid result, then you could just change this condition to if not LinkData


local Function = LinkData.Link
local Check = LinkData.Check
local ChipDelay = LinkData.ChipDelay

local A, B = self, Target -- Default argument order
if Reversed then A, B = Target, self end -- If reversed, reverse argument order

if Function then
return Function(self, Target)
elseif self.DefaultLink then
return self:DefaultLink(Target)
if Check then
local result, message = Check(A, B)
Copy link
Member

Choose a reason for hiding this comment

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

Inconsistent capitalization

if result then
if FromChip and ChipDelay then
timer.Simple(ChipDelay, function()
if Check(A, B) then Function(A, B) end
end)
else Function(A, B) end
end
return result, message
end
return Function(A, B)
end

return false, LinkText:format(self.PluralName, Target.PluralName or Class)
Expand All @@ -83,12 +102,16 @@ 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 LinkData, Reversed = ACF.GetClassLink(self:GetClass(), Class)
local Function = LinkData.Unlink

if LinkData == nil then return false, "Links between these two entities are impossible" end
Copy link
Member

Choose a reason for hiding this comment

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

If LinkData never actually receives a false value as a valid result, then you could just change this condition to if not LinkData


local A, B = self, Target -- Default argument order
if Reversed then A, B = Target, self end -- If reversed, reverse argument order

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

return false, UnlinkText:format(self.PluralName, Target.PluralName or Class)
Expand Down
42 changes: 33 additions & 9 deletions lua/entities/acf_base_simple/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -61,18 +61,37 @@ 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, Reversed = ACF.GetClassLink(self:GetClass(), Class)

if LinkData == nil then return false, "Links between these two entities are impossible" end

local Function = LinkData.Link
local Check = LinkData.Check
local ChipDelay = LinkData.ChipDelay

local A, B = self, Target -- Default argument order
if Reversed then A, B = Target, self end -- If reversed, reverse argument order

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

return false, LinkText:format(self.PluralName, Target.PluralName or Class)
Expand All @@ -83,12 +102,17 @@ 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 LinkData, Reversed = ACF.GetClassLink(self:GetClass(), Class)

if LinkData == nil then return false, "Links between these two entities are impossible" end

local Function = LinkData.Unlink

local A, B = self, Target -- Default argument order
if Reversed then A, B = Target, self end -- If reversed, reverse argument order

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

return false, UnlinkText:format(self.PluralName, Target.PluralName or Class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ e2function number entity:acfLinkTo(entity Target, number Notify)
return 0
end

local Sucess, Message = this:Link(Target)
local Sucess, Message = this:Link(Target,true)

if Notify ~= 0 then
ACF.SendNotify(self.player, Sucess, Message)
Expand Down
2 changes: 1 addition & 1 deletion lua/starfall/libs_sh/acffunctions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1105,7 +1105,7 @@ if SERVER then
CheckPerms(instance, This, "entities.acf")
CheckPerms(instance, Target, "entities.acf")

local Success, Message = This:Link(Target)
local Success, Message = This:Link(Target, true)

if notify then
ACF.SendNotify(instance.player, Success, Message)
Expand Down
Loading