• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

Custom events

Status
Not open for further replies.
Level 15
Joined
Aug 7, 2013
Messages
1,338
Hi,

What is the standard way to do your own custom events?

E.g. suppose I have some integer value I keep track of that is a custom resource (e.g. the amount of mushrooms the player has collected). I want a trigger to fire off when they reach some quota.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
JASS:
/* 
*    EVENT_PLAYER is a global player variable.
*    so is EVENT_UNIT. Names are made up ofc. --> eventUnit...
*/
set EVENT_UNIT    = unit//GetUnit(anywhere)
set EVENT_PLAYER  = Player(0)
set EVENT_MY_EVENT = 0
set EVENT_MY_EVENT = 1
/*
* code
*/
and
JASS:
private function fire takes nothing returns boolean
    if not IsUnitAlly(EVENT_UNIT, EVENT_PLAYER) then
        //blabla blub
    endif
    return false
endfunction

local trigger t = CreateTrigger()
call TriggerRegisterVariableEvent(t, "EVENT_MY_EVENT", EQUAL, 1)
call TriggerAddCondition(t, function thistype.fire)
-->
call TriggerRegisterVariableEvent takes trigger whichtrigger, string varName, limitop opcode, real limitvalue
 
Last edited:
Level 15
Joined
Aug 7, 2013
Messages
1,338
Jeez I should have consulted the API first. I didn't know variable events existed. Incredibly helpful!

But why is the variable in quotes? Is that how you tell JASS to not actually evaluate it?

Also does the variable need to be in all caps or any other special format?

Edit: On second thoughts this event is nearly useless. I assume you can't have TriggerRegisterPlayerVariableEvent?

For example, suppose I have 12 variables, one for each player. In the body of the trigger conditions, how do I infer which player the variable belongs to?

Also, how do I get a variable name out of a variable? Again this isn't too useful if it has to be hardcoded in...
 
Level 26
Joined
Aug 18, 2009
Messages
4,097
JASS:
class EventResponse

class Event
    func run(params)
        EventResponse.current = params

        action.run()

    construct create(action)

class Oil
    List<Event> registeredEvents

    func runEvents(args)
        params = new EventResponse()

        params.data = args

        for each event in registeredEvents
            event.run(params)

    func registerEvent(action, args)
        event = new Event(action)

        event.data = args

        registeredEvents.add(event)

JASS:
class QuestCollectOil
    func onEnoughOil
        params = EventResponse.current

        celebrate(params)

    init
        Oil.registerEvent(onEnoughOil, ...)
 
Level 15
Joined
Aug 7, 2013
Messages
1,338
I don't know any of this language you're writing.

Also I'd appreciate a written explanation than just be shown a picture of code...
 
Level 26
Joined
Aug 18, 2009
Messages
4,097
Pseudo-language. You make a container for the function you are going to run. This container can be inserted into various lists, which are fired off from the specific event type runway. The event should be run with event responses in order to deliver data to the target function.

Triggers with TriggerAddCondition/TriggerEvaluate are suitable. Such allow the dynamic call of functions.
 
Level 15
Joined
Aug 7, 2013
Messages
1,338
Apologies for my ignorance but I cannot understand your explanation. It would help if these questions could be directly answered (sorry for my lack of understanding :[).

1. How do I convert a variable into its string variable name?

I suppose that this is impossible directly. I'd have to make some kind of table or array that stores the variable names. But that wouldn't work, because the optimizer changes the variable names, so the mapping would break.

2. Do array members also count as variables? I am guessing not, so you cannot register array members.

3. Given a variable event, how do I infer which player that variable belongs to? Suppose I have 12 variables keeping track of some arbitrary value for each player. I write a trigger for say variable 5, which is player 5's value. How do I tell that that variable "belongs" to player 5?
 
Level 26
Joined
Aug 18, 2009
Messages
4,097
1. How do I convert a variable into its string variable name?

I suppose that this is impossible directly. I'd have to make some kind of table or array that stores the variable names.

Positive. But there is no dynamic variable pointer anyway. I guess you are referring to the TriggerRegisterVariableEvent, there you state the identifier of a global variable as string because you cannot pass variable references in jass. TRVE does not hand out event responses like GetTriggerVariableName() and what would a GetTriggerVariable() return? There is no type for that. So converting variables into their string names makes only sense if you are macroing code.

2. Do array members also count as variables? I am guessing not, so you cannot register array members.

TRVE does not support arrays, only simple real variables.

3. Given a variable event, how do I infer which player that variable belongs to? Suppose I have 12 variables keeping track of some arbitrary value for each player. I write a trigger for say variable 5, which is player 5's value. How do I tell that that variable "belongs" to player 5?

You do not need separate TRVE-variables for that. Those are just meant to run the specific trigger/function. Everything else are event responses (parameters) you can transfer. You can do it like I have shown above, allocate an EventResponse object, attach the various data to it, set it as the 'current' globally, then in the target function immediately read the needed information.


However, I was not referring to TRVE. TRVE is only convenient for GUI users because they can write it in the event-block. Jass offers you more possibilities. Instead of marking the triggers with TRVE and setting the variable to call, you can save the triggers themselves and fire them off with TriggerExecute/TriggerEvaluate. You just have to list them up, which you do in the register function, then traverse this list when the event type occurs. You can even have multiple lists because for example you said you want them player-related. Then you attach a list of triggers to a player each. When the event type occurs, you have the player and take its personal execution list to iterate over. This has a larger overhead in registering and in the fire function but the advantage is that you can make more specific events, you do not have to filter for the player in the target function then.

Ps: See 'Custom Events' in my signature.
 
Level 15
Joined
Aug 7, 2013
Messages
1,338
I tried reading through your tutorial, but came to the same conclusions as the commenters. I did not follow it at all.

Perhaps we could simplify things before abstracting to a high level like in your examples.

But first let me get this off my head.

Is it possible to write something like this (keep in mind it is very high level, but you should get the idea).

JASS:
//I know there is no type in JASS for variable, but the idea is
//you can create variable based events for any variable, 
//including those generated dynamically
function createVariableEvent takes integer pid, variable someVariable, integer value returns nothing
//the requisite code for creating the trigger/event 
//that fires off when the passed variable is 
//at the given value passed into the function
endfunction

So I am guessing it is NOT possible to create variable based events off of dynamically generated variables, i.e. local variables (in fact I don't think there's a way to create new variables which aren't locals, so never mind).

So going back down to a low level description, here is a hypothetical situation. I have 12 variables, each called var_i, where i is the identifier, e.g. var_1, var_2, var_3, etc. I want to call arbitrary code when var_i is equal to some value, call it varValue, only for the corresponding player. I also need to be able to recover the player from the event, so that in the event code I have access to the corresponding player object.

Now how would you write this out concretely in JASS? You mentioned it's not required to use TRVE. So what do I use?
 
E.g. suppose I have some integer value I keep track of that is a custom resource (e.g. the amount of mushrooms the player has collected). I want a trigger to fire off when they reach some quota.

JASS:
library SomeShit
    
    globals
        private integer array int
    endglobals
    
    public function Add takes integer id returns nothing
        set int[id] = int[id] + 1 
        if int[id] == 10 then // Keeping track on variable
            call CustomDefeatBJ( Player(id), "Defeat" ) // You can fire "event" here
        endif
    endfunction

endlibrary

//
// Some code
//
// Your function where you collect mushrooms or whatever
//
      //
      call SomeShit_Add(GetPlayerId(GetTriggerPlayer()))
      //

//Or you just add that above into code at first place if it is something small

Variable event completely ignored...
 
Level 26
Joined
Aug 18, 2009
Messages
4,097
JASS:
//I know there is no type in JASS for variable, but the idea is
//you can create variable based events for any variable, 
//including those generated dynamically
function createVariableEvent takes integer pid, variable someVariable, integer value returns nothing
//the requisite code for creating the trigger/event 
//that fires off when the passed variable is 
//at the given value passed into the function
endfunction

No, you cannot. Well could make the compiler hook on each global variable, to replace its setting/reading by functions, which would then call a common function with the variable as string identifier for example. And then you register those variables in createVariableEvent with the same identifier to see the connection.

You could make the compiler do it for each variable and reference but that's highly inefficient, because every variable that does not need it would be checked all the time.

Do it as Kobas wrote. Do not directly influence the variable's value from outside but make function wrappers for it, to parallel run events and whatnot when changing them. And from only there should you use TRVE/dynamic function calls.
 
Level 15
Joined
Aug 7, 2013
Messages
1,338
I am still confused. Kobas' example gave a function, but how does it get called? I need an event for it do I not. Otherwise I won't know when to call, i.e. when the variable changes or the amount is equal to the requisite value. What event would I use where I would make that call..?

For the item collecting example it's simple because you have events like unit picks up item.

But suppose I have an integer value that gets randomized and I want to see if a player won the lottery (got some integer value). There isn't an event for that. What event would I use? Or do I just need to periodically check every so often, like "every 5 game seconds check this variable's value?"
 
Level 26
Joined
Aug 18, 2009
Messages
4,097
What Kobas has shown is not the target function but the launcher.

You make a function "DrawALot" and that one rolls the dices in order to determine if event handlers of type "onWinningLottery" are run. Running these event actions is done dynamically and those are the target functions then.

You do not directly catch the setting of some variable, you write a function that sets both the variable and takes care of what it influences.
 
Level 15
Joined
Aug 7, 2013
Messages
1,338
JASS:
set i = GetRandomInt(1,100)
if i == 50 then
     call whatever()
endif
Have no idea what you want to do, but everything so far I wanted to make in wc3 I was able to using simple natives wc3 has.

Yes, but what is the catalyst. You can't just fire off code magically.

It needs to be in the conditions of some trigger.

Here is a situation.

There is an integer which is randomized. Detect if it is a certain value and execute arbitrary code. What is the event? Do I just periodically check every X seconds for that condition?

JASS:
function main takes nothing returns boolean
  ...
  if i == X then
  //arbitrary code
  endif
  ...
endfunction

function init takes nothing returns nothing
  local trigger t = CreateTrigger()
  call TriggerRegisterWHATEVENT???(t, ...)
  call TriggerAddCondition(t, Condition(function main))
endfunction

Hope that makes it clearer.
 
Don't think about jass like you do with C++ or such languages.
Lolz use timer.

JASS:
library X initializer A

    function B takes nothing returns nothing
        if GetRandomInt(1,2) == 1 then
            call BJDebugMsg("1")
        else
            call BJDebugMsg("2")
        endif
    endfunction

    function A takes nothing returns nothing
        call TimerStart(CreateTimer(), 1.00, true, function B)
    endfunction
    
endlibrary
 
Level 15
Joined
Aug 7, 2013
Messages
1,338
If I understand this correctly, it's going to immediately get a random int and check its value.

But what if I want to only do this checking at an arbitrary point/state in the game?

What about something like this...
JASS:
function main takes nothing returns boolean
 local integer pid = GetPlayerId(GetTriggerPlayer())
 if variables[pid] == someValue then
    ...
 endif
 ...
endfunction

function init takes nothing returns nothing
  local trigger t = CreateTrigger()
  call TriggerRegisterPlayerStateEvent(t, Player(0), PLAYER_STATE_RESOURCE_GOLD, GREATER_THAN_OR_EQUAL, 0)
  call TriggerAddCondition(t, Condition(function main))
endfunction

It will keep running main always, since you can't have negative gold. And it also lets me get "GetTriggerPlayer()" so I can check what value I'd like for a particular player.

I can destroy the trigger and also keep it off until I need it running.

Anyone have a better approach?

Edit: Problem with this---it only fires off if your gold amount changes. Still waiting for better solutions ^^
 
Last edited:
That trigger of yours will fire only when player red gold become larger than 0.
I will suggest checking gold each 1 second for each player.
JASS:
library TEST initializer Init

globals
     integer array Gold
endglobals

function CheckGold takes nothing returns nothing
     local integer i = 0
     loop
          if Gold[i] != GetPlayerState(Player(i), PLAYER_STATE_RESOURCE_GOLD) then
               call DoWhateverOnGoldChange(i) //<- Player id as argument
          endif
          set Gold[i] = GetPlayerState(...)
          exitwhen i > 12
          set i = i + 1
     endloop
endfunction

private function Init takes nothing returns nothing
     call TimerStart(CreateTimer(), 1.00, true, function CheckGold )
endfunction

endlibrary
Writing this without checking it in WE so care about exact names.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
I'm a big fan of examples, so here we go:
JASS:
library Example
    struct Core extends array
        static trigger event_trigger
        readonly static player who
        
        private static integer array counter
    
        private static method countDeath takes nothing returns boolean
            local integer i = GetPlayerId(GetTriggerUnit())
            set counter[i] = counter[i] + 1
            if counter[i] >= 20 then
                /*
                *   Fire the event trigger --> Second.run will run
                */
                set who = Player(i)
                call TriggerEvaluate(event_trigger)
            endif
            return false
        endmethod

        static method getEventPlayer takes nothing returns player
            return who
        endmethod
        
        static method registerToEventTrigger takes boolexpr expr returns nothing
            call TriggerAddCondition(event_trigger, expr)
        endmethod
    
        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            call TriggerRegisterPlayerUnitEvent(t, EVENT_PLAYER_UNIT_DEATH)
            call TriggerAddCondition(t, function thistype.countDeath)
        endmethod
    endstruct
endlibrary

library Example2 uses Example

    private struct Second extends array

        private static method run takes nothing returns boolean
            local player p = Core.getEventPlayer()
            //or local player p = Core.who
            //......
            return false
        endmethod
    
        private static method onInit takes nothing returns nothing
            call Core.registerToEventTrigger(Condition(function thistype.run)) 
        endmethod
        
    endstruct

endlibrary

Edit: There is a resource called "Event" by Nestharus. It supports this matter in a much more optimized way.
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
So highly complicated responses for such a simple question...

Use TriggerRegisterVariableEvent
The variable used for this should only be used for the event.

Then have more global variables that contain the data.

For instance, if your event has something to do with players, then make the global contain a player number.
Set the player number before making the event run.

Example Pseudocode:
Code:
function stuffHappens
    call Debug(GetPlayerName(EventData))
endfunction

set EventData = Player(0)
set Event = 1
//stuff happens
set Event = 0//ready for next time

call TriggerRegisterVariableEvent(trigger,Event,EQUAL,1)
call TriggerRegisterAction(trigger,stuffHappens)
 
Level 15
Joined
Aug 7, 2013
Messages
1,338
If I use TRVE, then do I not need the variable hardcoded in?

Native primitive types do not pass by reference, so setting a local variable to the integer and then passing it to TRVE does nothing.

Though my way doesn't solve that stuff either, but it does allow checking of other values that aren't variables (e.g. array members).

I suppose if I made a wrapper struct around an integer, then I could do it, since that struct and the one it was made by would share the same integer field...
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
If you want a generic event, then TRVE is for you.
Note that you can pass pretty much any amount of variables through globals.

If you want lots of custom events, then make them functions and store them in a hashtable (because you can't store them in arrays).
You can still pass any amount of variables.
 
Level 26
Joined
Aug 18, 2009
Messages
4,097
TRVE realizes a list of triggers that get run when the variable changes. Thus the variable represents the event type. But you should conceal the implementation and use a function instead of writing TRVE everywhere.

Also, as addressed before, TRVE lacks some flexibility. It's only suited for static events. In the scenario you described here, you wanted a player-related event. If you do not want to select the player from event responses in the target function but rather assign the event to the player directly, you see that each player should have a list of triggers to be executed. Therefore, each player would require a TRVE-variable. Now imagine the event holder is not something as static as a player but a unit for example that is created during runtime. You cannot allocate new global variables ingame. Furthermore you cannot deregister TRVE without recreating the trigger. Since you do not realize the list yourself, you have less options to handle it.
 
Status
Not open for further replies.
Top