shadowart
07-26-2015, 06:29 PM
Most Xenobot scripts have label handlers that look something like this:
function onWalkerSelectLabel(labelName)
if (labelName == "Start") then
Walker.ConditionalGoto((Self.Position().z == 11), "BeginHunt", "ReachDepot")
elseif (labelName == "DepositGold") then
-- Deposit Gold, check balance.
Walker.Stop()
Self.SayToNpc({"hi", "deposit all", "yes"}, 100)
local withdrawManas = math.max(BuyMana - Self.ItemCount(ManaID), 0)*ManaCost
local withdrawHealths = math.max(BuyHealth - Self.ItemCount(HealthID), 0)*HealthCost
local withdrawAmmo = math.max(BuyAmmo - Self.ItemCount(AmmoID), 0)*AmmoCost
local total = math.abs(withdrawManas + withdrawHealths + withdrawAmmo)
if total >= 1 then
Self.SayToNpc({"withdraw " .. total, "yes", "balance"}, 100)
end
Walker.Start()
elseif (labelName == "DepositItems") then
etc...
Essentially, they're just a long series of if-statements that deal with every possible label. However, when you write large scripts (1000 lines plus), this design will get you into trouble.
Let's say you want to write a module that keeps you hasted while refilling. Then you have create a module somewhere in the code, and then in a totally different place you have to make sure the module is started when you leave the spawn and in yet another place you have to stop it when you enter the spawn.
elseif (labelName == "Leave") then
Module.Start("Haster")
elseif etc...
If you see the code above, how will you know if it works or even what it does? The definition of the Haster module might be hundreds of lines away and it might not even exist - maybe you actually named it "Refill Haster" or forgot to copy it from another script.
When you spread out code related to a single concept it makes your code hard to read and understand. This in turn makes it hard to debug, hard to change and hard to reuse in other scripts.
Now look at this:
do
local LabelHandlers = {}
function RegisterLabelHandler(Label, Name, Handler)
local Data = LabelHandlers[Label] or {}
Data[Name] = Handler
LabelHandlers[Label] = Data
end
local function ExecuteAll(FunctionTable, ...)
for _, f in pairs(FunctionTable) do
f(...)
end
end
registerEventListener(WALKER_SELECTLABEL, "HandleLabel")
function HandleLabel(LabelName)
local LabelParts = LabelName:split(";")
local LabelKey = LabelParts[1]
local HandlerTable = LabelHandlers[LabelKey]
if HandlerTable then
ExecuteAll(HandlerTable, unpack(LabelParts))
end
end
end
This is a framework that will allow you to associate actions with labels anywhere in the code. This is how our hasting module would look with my framework:
Module("Refill Haster", function(Haster)
if not Self.isHasted() and not Self.isInPz() then
Self.Cast("utani hur", 50)
end
end)
RegisterLabelHandler("Hunt", "Refill Haster", function()
if Module.IsActive("Refill Haster") then
Module.Stop("Refill Haster")
end
end)
RegisterLabelHandler("Leave", "Refill Haster", function()
if not Module.IsActive("Refill Haster") then
Module.Start("Refill Haster")
end
end)
Everything related to the haster is now contained in one place and the entire code may be displayed at once on a screen. This will, as I said earlier, make the code easier to debug, easier to change and easier to reuse.
But what if I want to do more than just stop the haster when I come to the "Hunt" label? That's totally fine. Maybe you want to run a gold dropper inside the spawn. That could be done like this:
do
local DropGoldBelowThisCap = 500
Module("Gold Dropper", function(Drop)
if Self.Cap() < DropGoldBelowThisCap then
local pos = Self.Position()
Self.DropItem(pos.x, pos.y, pos.z, "gold coin", 100)
end
end, false)
RegisterLabelHandler("Hunt", "Gold Dropper", function()
if not Module.IsActive("Gold Dropper") then
Module.Start("Gold Dropper")
end
end)
RegisterLabelHandler("Leave", "Gold Dropper", function()
if Module.IsActive("Gold Dropper") then
Module.Stop("Gold Dropper")
end
end)
end
Now your script will both start the gold dropper, and stop the haster when you enter the spawn. However, if you just look at the code related to the gold dropper you will have no idea that stuff related to the haster also happens at the "Hunt" label. Nor should you have to. The two concepts are totally unrelated and should not be intermixed within the code.
Of course you sometimes want to write code that executes at many different labels and does slightly different things depending on the label name. For example, let's say you want to write a randomized script and you want to write a label handler that goes to a random label depending on the label it's executed at. This is how you could do it with my framework:
RegisterLabelHandler("RandomGoto", "RandomGoto", function(_, Tag, Limit)
Walker.Goto(Tag..math.random(1, Limit))
end)
What does this code do? If you encounter a label called "RandomGoto;BeforeBank;3" it will randomly go to one of the labels "BeforeBank1", "BeforeBank2" or "BeforeBank3".
This last example has nothing to do with modularity, nor is it something that couldn't be done using a simple series of if-statements. I just wanted to demonstrate that using my framework doesn't prevent you from writing this kind of code.
function onWalkerSelectLabel(labelName)
if (labelName == "Start") then
Walker.ConditionalGoto((Self.Position().z == 11), "BeginHunt", "ReachDepot")
elseif (labelName == "DepositGold") then
-- Deposit Gold, check balance.
Walker.Stop()
Self.SayToNpc({"hi", "deposit all", "yes"}, 100)
local withdrawManas = math.max(BuyMana - Self.ItemCount(ManaID), 0)*ManaCost
local withdrawHealths = math.max(BuyHealth - Self.ItemCount(HealthID), 0)*HealthCost
local withdrawAmmo = math.max(BuyAmmo - Self.ItemCount(AmmoID), 0)*AmmoCost
local total = math.abs(withdrawManas + withdrawHealths + withdrawAmmo)
if total >= 1 then
Self.SayToNpc({"withdraw " .. total, "yes", "balance"}, 100)
end
Walker.Start()
elseif (labelName == "DepositItems") then
etc...
Essentially, they're just a long series of if-statements that deal with every possible label. However, when you write large scripts (1000 lines plus), this design will get you into trouble.
Let's say you want to write a module that keeps you hasted while refilling. Then you have create a module somewhere in the code, and then in a totally different place you have to make sure the module is started when you leave the spawn and in yet another place you have to stop it when you enter the spawn.
elseif (labelName == "Leave") then
Module.Start("Haster")
elseif etc...
If you see the code above, how will you know if it works or even what it does? The definition of the Haster module might be hundreds of lines away and it might not even exist - maybe you actually named it "Refill Haster" or forgot to copy it from another script.
When you spread out code related to a single concept it makes your code hard to read and understand. This in turn makes it hard to debug, hard to change and hard to reuse in other scripts.
Now look at this:
do
local LabelHandlers = {}
function RegisterLabelHandler(Label, Name, Handler)
local Data = LabelHandlers[Label] or {}
Data[Name] = Handler
LabelHandlers[Label] = Data
end
local function ExecuteAll(FunctionTable, ...)
for _, f in pairs(FunctionTable) do
f(...)
end
end
registerEventListener(WALKER_SELECTLABEL, "HandleLabel")
function HandleLabel(LabelName)
local LabelParts = LabelName:split(";")
local LabelKey = LabelParts[1]
local HandlerTable = LabelHandlers[LabelKey]
if HandlerTable then
ExecuteAll(HandlerTable, unpack(LabelParts))
end
end
end
This is a framework that will allow you to associate actions with labels anywhere in the code. This is how our hasting module would look with my framework:
Module("Refill Haster", function(Haster)
if not Self.isHasted() and not Self.isInPz() then
Self.Cast("utani hur", 50)
end
end)
RegisterLabelHandler("Hunt", "Refill Haster", function()
if Module.IsActive("Refill Haster") then
Module.Stop("Refill Haster")
end
end)
RegisterLabelHandler("Leave", "Refill Haster", function()
if not Module.IsActive("Refill Haster") then
Module.Start("Refill Haster")
end
end)
Everything related to the haster is now contained in one place and the entire code may be displayed at once on a screen. This will, as I said earlier, make the code easier to debug, easier to change and easier to reuse.
But what if I want to do more than just stop the haster when I come to the "Hunt" label? That's totally fine. Maybe you want to run a gold dropper inside the spawn. That could be done like this:
do
local DropGoldBelowThisCap = 500
Module("Gold Dropper", function(Drop)
if Self.Cap() < DropGoldBelowThisCap then
local pos = Self.Position()
Self.DropItem(pos.x, pos.y, pos.z, "gold coin", 100)
end
end, false)
RegisterLabelHandler("Hunt", "Gold Dropper", function()
if not Module.IsActive("Gold Dropper") then
Module.Start("Gold Dropper")
end
end)
RegisterLabelHandler("Leave", "Gold Dropper", function()
if Module.IsActive("Gold Dropper") then
Module.Stop("Gold Dropper")
end
end)
end
Now your script will both start the gold dropper, and stop the haster when you enter the spawn. However, if you just look at the code related to the gold dropper you will have no idea that stuff related to the haster also happens at the "Hunt" label. Nor should you have to. The two concepts are totally unrelated and should not be intermixed within the code.
Of course you sometimes want to write code that executes at many different labels and does slightly different things depending on the label name. For example, let's say you want to write a randomized script and you want to write a label handler that goes to a random label depending on the label it's executed at. This is how you could do it with my framework:
RegisterLabelHandler("RandomGoto", "RandomGoto", function(_, Tag, Limit)
Walker.Goto(Tag..math.random(1, Limit))
end)
What does this code do? If you encounter a label called "RandomGoto;BeforeBank;3" it will randomly go to one of the labels "BeforeBank1", "BeforeBank2" or "BeforeBank3".
This last example has nothing to do with modularity, nor is it something that couldn't be done using a simple series of if-statements. I just wanted to demonstrate that using my framework doesn't prevent you from writing this kind of code.