- Joined
- Aug 18, 2009
- Messages
- 4,097
Note: The code snippets should only represent the text as a basic shape. They are not jass nor any other specific language nor the final realization.
This EventResponse-struct is written more explicitly to show a performant way of passing the data to the target function. See post below.
ExpositionLet's suppose we wanted to create the following spell: A unit shall get buffed - not a standard buff from the object editor - but maybe a timer counts down once from 10 seconds and then, after its expiration, splash damage gets dealt around the target unit. As usual the buff shall be interrupted when the unit dies in the meantime.
scenario 1:
unit is buffed
10 seconds elapse
damage is successfully dealt and buff vanishes
scenario 2:
unit is buffed
4 seconds elapse
unit dies because of reasons, buff is forced to be cleaned up
Now, most mapmakers do it this way that they create a "A unit Dies"-Event and then ask in conditions whether the dying unit possesses the buff. If so, the buff gets removed else the code is terminated at this point.
I see two disadvantages when using this method: It's not very performant and messy to just generically run this trigger for every unit that has nothing to do with it, that did not possess the buff in the first place. Second: You would sometimes like to set a priority in which order triggers using this event are going to fire. Imagine there was a second trigger that immediately deletes the unit upon death and in case this one gets run first, you could not see anymore if the unit has the buff, hence be unable to remove the belongings like the timer. At most, the timer could check it in the ending, which is again filthy and is based on what you can read out of a destroyed unit.
That's why I take another approach: the dynamical call of code. Thereby, when the unit gets the buff, I would attach the code snippet that shall be called upon death directly to the unit. You can also deattach it again, meaning that would be some form of removing events.
Dynamic CodeWhat are the possibilities to dynamically run code?
You know it's a big flaw that you cannot have arrays of the variable type code and nor are there other ways to directly assign code values. What you can do is to use the native containers BoolExpr and TriggerAction. These get called by diverse functions:
via BoolExpr:
TriggerEvaluate, the different Enum-functions for Destructables/Items/Players/Units, the And/Or/Not-Functions and when a trigger is fired by an event, it triggers previously added BoolExprs that were defined using TriggerAddCondition, on some events you can even add a specific, personal BoolExpr
via TriggerAction:
TriggerExecute or again by one of the trigger's events
Another possibility would be to utilize the ExecuteFunc-function which rather takes the function's name as string instead of a code parameter. ExecuteFunc is slow btw.
So what do we need?
- An event object with
- a type (when shall it start)
- a priority (in which order in relation to other events of the same type shall it start)
- a target function to trigger then
- an eventHolder/subject that contains the object (in this case it shall be stored on the unit)
- a source point/function that reacts on the original unit death event and starts all the events of this type
- we would also like to pass information about the event like killer and dying unit in case of a unit death event,
this shall be called an eventResponse object
JASS:
struct EventPriority
static thistype HEADER //suggestions, you would usually like to react faster to specific cases of content stuff, so HEADER-events are usually run with lower priority
static thistype CONTENT
static thistype AI
endstruct
JASS:
struct EventType
static thistype UNIT_DEATH //of course, in reality, you would not really accumulate all types of events in a shared struct, it's only a simplified example here
endstruct
JASS:
struct Event
static method create(type, priority, action) //events are usually created during init and during runtime get assigned to eventHolders/subjects, which is the buffed unit in the above scenario
endstruct
JASS:
struct EventResponse
static integer COUNTER = 0
boolean hasReincarnation
unit killer
unit triggerUnit
function getCurrent
return thistype(thistype.COUNTER)
endfunction
function destroy
set thistype.COUNTER = thistype.COUNTER - 1
endfunction
function create
set thistype.COUNTER = thistype.COUNTER + 1
return thistype(thistype.COUNTER)
endfunction
endstruct
This EventResponse-struct is written more explicitly to show a performant way of passing the data to the target function. See post below.
JASS:
function UnitDies_TriggerEvents
local EventResponse params = EventResponse.create()
params.hasReincarnation = hasReincarnation
params.killer = killer
params.triggerUnit = triggerUnit
for event in eventHolder.events do
event.run() //run event's actions in a new thread
next
params.destroy()
endfunction
JASS:
function TargetFunction
local EventResponse params = EventResponse.GetCurrent()
local unit killer = params.killer
local unit triggerUnit = params.triggerUnit
//do actions
endfunction
Sensitive Events
Now, before finishing, I want to extend the scope. Besides simple events like "UnitDies" you know there are such as "Unit's mana becomes greater than x". Of course, I won't declare EventTypes MANA_GREATER_0, MANA_GREATER_1, MANA_GREATER_2, ..., MANA_GREATER_9999. I would rather assign the arrogated amount of mana to the Event object plus the comparison operator for best. So more parameters can add up or it can already make up an extended type. This here would maybe be called a "LimitEvent".
EventCombinationOnly a short introduction. I might expand this paragraph at a later date.
This concept merges different Event objects to a single gateway which runs action code. You know in artificial intelligence-programming for example, you would want the mage to cast his spell when he has enough mana, cooldown ready, during combat etc. --> when several conditions are fulfilled. However, checking these in intervals via timer is not very performant/senceful, so maybe you would only check them when any related event occurs. But I think it is not that neat either to have both an event and a condition for each element or check everything everytime. An EventCombination would contrast "positive" and "negative" events that constitute a required condition (like cooldown state). A counter tracks how many of these conditions are fulfilled and only when the number hits zero, the current and further triggerings of the added events run your action code.
Last edited: