- Joined
- Sep 26, 2009
- Messages
- 9,529
GUI just got another incredibly powerful tool added to its arsenal - I present to you:
Action.
Action allows you to treat GUI as a modern interface, with modern garbage collection, loop structure, perfect timing, local variables, and function subroutines. You read that correctly, but did you understand it?
First, have a look at the code:
Still stuck? I can't say I blame you. The code is not for the faint of heart. It is one of the ugliest hacks I've ever pulled off in Lua, but it allows the most beautiful GUI trigger syntax the WarCraft 3 world has ever seen. The below is a showcase (not a final version) of how Lua Spell Event and Action interact with each other to deliver an extremely cool effect, with one short trigger:
Action allows automatic destruction of unit groups, effects and locations/points, depending on whether the object is being overwritten (thanks to @Jampion for that idea), whether the Action_loop has completed, or at the latest, by the time the trigger has wrapped up.
Here is an example of a heal-over-time system. The rejuvenation effect is automatically destroyed at the end of the trigger, because to Action_effect is assigned to it.
Things to understand about how this works:
I feel like I'm still just starting to scratch the surface of what Lua can do for GUI. If Blizzard won't provide GUI the powerful tools it needs to take on the future, I will bring the future to GUI through Lua.
Action.
Action allows you to treat GUI as a modern interface, with modern garbage collection, loop structure, perfect timing, local variables, and function subroutines. You read that correctly, but did you understand it?
First, have a look at the code:
Lua:
--[[
Action v1.3.0.0 by Bribe
What it does:
1) Allows GUI to declare its own functions to be passed to a Lua system.
2) Automatic variable localization and cleanup for effects.
3) Automatic variable localization for units, reals, integers, groups and locations.
4) Recursive local tracking (as long as array indices are not shadowed).
Why it can benefit:
1) Allows you to have all of your functions in one GUI trigger
2) Each trigger and each sub-action within the trigger has access to each others' data.
3) No need to create the same variables for each new trigger if you use the variables provided here.
How it works:
1) In some cases replaces ForForce, allowing you to manipulate the callback function instead.
2) Attaches data via coroutines, allowing all locals powered by this system to be local to the running instance of the trigger, regardless of how many times it waits.
3) To destroy (for example) a unit group: Set DestroyGroup = TempGroup1
--]]
OnGlobalInit(function()
Require "GlobalRemap" --https://www.hiveworkshop.com/threads/global-variable-remapper.339308
Require "PreciseWait" --https://www.hiveworkshop.com/threads/precise-wait-gui-friendly.316960/
Action={}
local systemPrefix="udg_Action_"
local actions={}
local cleanTracker
local topIndex=__jarray()
local oldForForce,lastIndex
oldForForce=AddHook("ForForce", function(whichForce, whichFunc)
if actions[whichForce] then
local index=lastIndex
lastIndex=nil
actions[whichForce](whichFunc, index)
else
oldForForce(whichForce, whichFunc)
end
end)
---@param whichVar string --The name of the user-defined global. It will add the udg_ prefix if you don't feel like adding it yourself.
---@param onForForce fun(function) --Takes the GUI function passed to ForForce and allows you to do whatever you want with it instead.
---@param isArray? boolean --If true, will pass the array index to the onForForce callback.
function Action.create(whichVar, onForForce, isArray)
if whichVar:sub(1,4)~="udg_" then
whichVar="udg_"..whichVar
end
local force = _G[whichVar]
if isArray then
if force then
GlobalRemapArray(whichVar, function(index)
lastIndex=index
return whichVar
end)
end
else
if force then
GlobalRemap(whichVar, function()
lastIndex=nil
return whichVar
end)
end
end
actions[whichVar]=onForForce
end
local durations=__jarray()
local getCoroutine=coroutine.running
Action.create(systemPrefix.."forDuration", function(func)
local co = getCoroutine()
if durations[co] then
while durations[co] > 0 do
func()
end
end
end)
GlobalRemap(systemPrefix.."duration", function() return durations[getCoroutine()] end, function(val) durations[getCoroutine()] = val end)
--Look at this: Every time a trigger runs, it can have its own fully-fledged, perfectly MUI hashtable, without having to initialize or destroy it manually.
local hash = __jarray()
GlobalRemap(systemPrefix.."hash", function()
local parent = topIndex[getCoroutine()]
hash[parent] = hash[parent] or __jarray()
return hash[parent]
end)
---Nearly the same as Precise PolledWait, with the exception that it tracks the duration.
---@param duration real
function Action.wait(duration)
local co = getCoroutine()
if co then
local t = CreateTimer()
TimerStart(t, duration, false, function()
DestroyTimer(t)
if durations[co] then
durations[co] = durations[co] - duration
end
coroutine.resume(co)
end)
coroutine.yield(co)
end
end
GlobalRemap(systemPrefix.."wait", nil, Action.wait)
local function cleanup(co)
if cleanTracker[co] then
for _,obj in ipairs(cleanTracker[co]) do
DestroyEffect(obj)
end
end
end
---Allows the function to be run as a coroutine a certain number of times (determined by the array index)
---Most importantly, the calling function does not wait for these coroutines to complete (just like in GUI when you execute a trigger from another trigger).
---@param func function
---@param count number
Action.create(systemPrefix.."loop", function(func, count)
local top = getCoroutine()
for _=1,count do
local co
co = coroutine.create(function()
func()
cleanup(co)
end)
topIndex[co]=top
coroutine.resume(co)
end
end, true)
local oldAction
oldAction = AddHook("TriggerAddAction", function(trig, func)
return oldAction(trig, function()
local co = getCoroutine()
topIndex[co]=co
func()
cleanup(co)
end)
end, 2)
GlobalRemap(systemPrefix.."index", function() return topIndex[getCoroutine()] end)
for _,name in ipairs {
"point",
"group",
"effect",
"integer",
"real",
"unit"
} do
local tracker=__jarray()
local varName=systemPrefix..name
if name=="effect" then
cleanTracker=tracker
end
GlobalRemapArray(varName, function(index)
local co = getCoroutine()
::start::
local result = rawget(tracker[co], index) --check if the value is already assigned at the level of the current coroutine
if not result and topIndex[co]~=co then
--If not, hunt around and try to find if any calling coroutines have the index assigned.
co = topIndex[co]
goto start
end
return result
end,
function(index, val)
local tb = tracker[getCoroutine()]
if not tb then
tb={}
tracker[getCoroutine()]=tb
elseif name=="effect" and tb[index] then
DestroyEffect(tb[index])
end
tb[index]=val
end)
end
end)
Still stuck? I can't say I blame you. The code is not for the faint of heart. It is one of the ugliest hacks I've ever pulled off in Lua, but it allows the most beautiful GUI trigger syntax the WarCraft 3 world has ever seen. The below is a showcase (not a final version) of how Lua Spell Event and Action interact with each other to deliver an extremely cool effect, with one short trigger:
-
Blizzard Enhancer
-
Events
-
Game - Button for ability Blizzard and order Human Spellbreaker - Spell Steal pressed.
-
Game - OnSpellEffect becomes Equal to 0.00
-
-
Conditions
-
Actions
-
Special Effect - Create a special effect at Spell__TargetPoint using Abilities\Spells\Human\FlameStrike\FlameStrikeTarget.mdl
-
Set VariableSet Action_effect[1] = (Last created special effect)
-
Player Group - Pick every player in Spell__whileChannel and do (Actions)
-
Loop - Actions
-
Player Group - Pick every player in Action_loop[(4 + (2 x Spell__Level))] and do (Actions)
-
Loop - Actions
-
Set VariableSet Spell__wait = 0.25
-
Set VariableSet Action_point[1] = (Spell__TargetPoint offset by (Random real number between 0.00 and (200.00 x Spell__LevelMultiplier)) towards (Random angle) degrees.)
-
Special Effect - Create a special effect at Action_point[1] using Abilities\Spells\Demon\RainOfFire\RainOfFireTarget.mdl
-
Special Effect - Destroy (Last created special effect)
-
Unit - Cause Spell__Caster to damage circular area after 0.00 seconds of radius 20.00 at Action_point[1], dealing 20.00 damage of attack type Spells and damage type Normal
-
Set VariableSet Spell__wait = 0.75
-
-
-
Set VariableSet Spell__wait = 1.00
-
-
-
-
Action allows automatic destruction of unit groups, effects and locations/points, depending on whether the object is being overwritten (thanks to @Jampion for that idea), whether the Action_loop has completed, or at the latest, by the time the trigger has wrapped up.
Here is an example of a heal-over-time system. The rejuvenation effect is automatically destroyed at the end of the trigger, because to Action_effect is assigned to it.
-
Holy Light Enhancer
-
Events
-
Game - Button for ability Holy Light and order Human Spellbreaker - Spell Steal pressed.
-
Game - OnSpellEffect becomes Equal to 0.00
-
-
Conditions
-
Actions
-
Special Effect - Create a special effect attached to the chest of Spell__Target using Abilities\Spells\NightElf\Rejuvenation\RejuvenationTarget.mdl
-
Set VariableSet Action_effect[1] = (Last created special effect)
-
Set VariableSet Action_duration = (2.00 + (2.00 x Spell__LevelMultiplier))
-
Player Group - Pick every player in Action_forDuration and do (Actions)
-
Loop - Actions
-
Set VariableSet Spell__wait = 1.00
-
Unit - Set life of Spell__Target to (((Life of Spell__Target) + 5.00) + (5.00 x Spell__LevelMultiplier))
-
-
-
-
Things to understand about how this works:
- The effect and locations are automatically destroyed/removed at the end.
- The Player Groups are not actually player groups, but hookable declarations to access a block of GUI code as a function.
- Action_loop will start a new coroutine "array index" number of times. This allows subroutines to have their own waits that do not require the root trigger to yield while they do their thing.
- Array indices in Lua start at 1. Any "Action" array value should also have its index starting at 1.
I feel like I'm still just starting to scratch the surface of what Lua can do for GUI. If Blizzard won't provide GUI the powerful tools it needs to take on the future, I will bring the future to GUI through Lua.
Attachments
Last edited: