• 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.

[Wurst] [System] Custom Events

Level 8
Joined
Nov 20, 2011
Messages
202
We need Wurst tags!!!
This looks like **** with jass highlighting!
Here is a peeeq version for better readablity:
http://peeeq.de/code.php?id=6537

JASS:
package CustomEvent /*
*************************************************************
* Description:
* This is a package which allows u to create custom events
* and easily register and unregister actions to them.
*************************************************************
* Requirements: None
*************************************************************
* How it works:
* Uses a list of triggers to realizie dynamic function calls.
* As event response data event bound attributs are used so 
* nothing can get overwritten and it is also very fast due to
* the fact that just two globals need to be set after each 
* action.
*************************************************************
* How to use:
* Basics:
* 	-Create an instance of the class Event to create a new event.
* 	-You can add actions to this event: 
* 	 yourevent.add(function myActionFunction)
* 	-To run all actions on an event just use:
* 	 yourevent.fire()
* 	-To remove an action just destroy the action object that 
* 	 the add function returns.
* 	-To remove an event just destroy it, all refering actions
* 	 get deletet aswell.
*
* Attach data to an action:
* 	Save the action which is returned by the add function:
* 	Action a = myEvent.add(function myActionFunction)
* 	Now u can set the data of the action:
* 	a.setData(myObject to int)
* 
* Get attached data from an action in the action function:
* 	First of all get and save the current action:
* 	Action a = getAction()
* 	Now call get the data from the action:
* 	MyClass obj =	a.getData() castTo MyClass
* 
* Set event response data:
* 	To set the event response data just set the Event attributes:
* 	//This are just some examples
* 	myEvent.u1 = triggerUnit
* 	myEvent.v1 = target.getPos()
* 
* Read event response data in the action function:
* 	Get and safe the Event:
* 	Event e = getEvent()
* 	Read out the object attributes:
* 	//This is just an example what u could do	
* 	e.u1.setPos(e.v1)
* 	You can also write new information back into the response data:
* 	e.s1 = "Teleport successful"
* 
* If u need other datatypes as response data u can 
* just add them.
* 
* If you got still don't understand how it works take a look
* at the example down there.
*************************************************************
* Bugs / Warnings / Hints:
* -No bugs know
* -Keep the instance limit in mind
* -Remember u need to destroy the action by urself
*  or destroy the event
* -You should use a convention for the event data, so
*  u won't get confused by the meanless names. You also can
*  change the names.
*************************************************************
* Credits goes to all members of the inwc IRC who helped me a lot
*************************************************************/

	import HashMap
	import TList
	import Setup

	//Global event response data
	HashMap<int, Event> eventStore = new HashMap<int, Event>()
	Action currentAction = null
	Event currentEvent = null
	
	/**Gets the current running action, use this to destroy the action or to get the stored data in the action*/
	public function getAction() returns Action
		return currentAction
	
	/**Gets the current firing event, use this to read out the event response data*/	
	public function getEvent() returns Event
		return currentEvent
	
	public class Event
		//Event response data
		unit u1 = null
		unit u2 = null
		vec2 v1 = vec2(0, 0)
		vec2 v2 = vec2(0, 0)
		int i1 = 0
		int i2 = 0
		real r1 = 0
		real r2 = 0
		player p1 = null
		player p2 = null
		boolean b1 = false
		boolean b2 = false
		string s1 = null
		string s2 = null
		
		Action first = null
		Action last = null
		boolean destructIfEmtpy = false
		
		/**Adds an new action to this Event, returns and Action object on which u can save data. 
		Destroy the returned object to remove the action*/
		function add(code actionFunc) returns Action
			trigger t = CreateTrigger().addCondition(Condition(actionFunc))
			if first == null
				first = new Action(t, null, null, this)
				last = first
			else
				Action buffer = last
				last = new Action(t, null, buffer, this)
				buffer.next = last
			return last
		
		/**Calls all to the event refering actions, don't forget to set the response data before*/
		function fire()
			Action e = first
			while (e != null)
				Action buffer = e
				e = e.next
				currentAction = buffer
				currentEvent = this
				buffer.content.evaluate()
			
		ondestroy
			Action e = first
			while (e != null)
				Action buffer = e
				e = e.next
				destroy buffer

	public class Action
		
		Action next
		Action prev
		Event referingEvent
		trigger content
		int objData = 0
		
		construct(trigger content, Action next, Action prev, Event referingEvent)
			this.content = content
			this.next = next
			this.prev = prev
			this.referingEvent = referingEvent
			
		/**Attaches data to the action. 
		You can save each kind of objects by cast them to ints: (myObject castTo int)*/
		function setData(int objData)
			this.objData = objData
			
		/**Retrieves data back from the action, don't forget to cast it back*/
		function getData() returns int
			return objData
			
		ondestroy
			DestroyTrigger(content)
			if prev != null
				prev.next = next
			if next != null
				next.prev = prev
			if referingEvent.first == this
				referingEvent.first = next
			if referingEvent.last == this
				referingEvent.last = prev
			if referingEvent.destructIfEmtpy and referingEvent.first == null and referingEvent.last == null
				destroy referingEvent
				
				
	//Example/Snipped: Cast Event	
	init
		//Trigger Init
		trigger t = CreateTrigger()
		t.addAction(function fireCastEvent)
		t.registerAnyUnitEvent(EVENT_PLAYER_UNIT_SPELL_CAST)
		t = null
	
	/**The action function will be just called if the desired ability is casted*/
	public function registerSpellCastAction(int abiCode, code actionFunc) returns Action
		Event e = eventStore.get(abiCode)
		if e == null
			e = new Event()
			eventStore.put(abiCode, e)
		return e.add(actionFunc)
		
	function fireCastEvent()
		Event e = eventStore.get(GetSpellAbilityId())
		if e != null	
			e.u1 = GetTriggerUnit()
			e.u2 = GetSpellTargetUnit()
			e.v1 = vec2(GetSpellTargetX(), GetSpellTargetY())
			eventStore.get(GetSpellAbilityId()).fire()
			
endpackage

Some example spell where the system is used:
peeeq version: http://peeeq.de/code.php?id=6538

JASS:
package CriticalSlow /*
************************************************************
* An very easy spell which slows the enemy down if u hit it
* with a critical hit. This should just demonstrate the use 
* of custom events.
************************************************************/

	import CustomEvent
	
	init
		//No need to save the Action because we never want to destroy it
		registerSpellCastAction('A001', function onCast)
		
	//This is the action function
	function onCast()
		//Here we get the event
		Event e = getEvent()
		//Create an instance of our spell
		new CritSlow(e.u1)
		
	public class CritSlow
		timer t
		Action a
		unit u
		
		construct(unit u)
			this.u = u
			//Imaginie an event which fires when this unit cause a critical hit
			//(A damage system which supports this will be realeased soon)
			a = u.registerCauseCritHit(function onCrit)
			//Set Action data
			a.setData(this castTo int)
			//SpellStuff
			t = getTimer()
			t.setData(this castTo int)
			t.start(30., function destr)
			
		static function destr()
			destroy GetExpiredTimer().getData() castTo CritSlow
	
		static function onCrit()
			//Reading out Event and Action data, and call the target function
			(getAction().getData()) castTo thistype.onCrit(getEvent().u2)
		
		function onCrit(unit target)
			//System which supports this will be also realeased soon
			target.bonusMovespeedTimed(-50, 2)
					
		ondestroy
			t.release()
			destroy a

endpackage
 
Last edited:
It is a bit of an odd implementation. I think you overdid it a bit. :)

(1) IMO you should leave event responses to the user's own class, not the main event class. E.g.:
spell.caster
is a lot more explicit than:
e.u1
Now, "better" is subjective but I believe most people would prefer the first option.

As another point to this: what would someone do if they wanted a different event response type? They would have to resort to their own class's event response anyway. Adding to the library itself would be a bad habit, especially when making a resource.

(2) There are two traditional means of making events. First, the method for Nestharus' Event lib:
JASS:
TriggerRegisterVariableEvent(...)
// ^ to register, one trigger per event
var = this
// ^ to fire event
http://www.hiveworkshop.com/forums/jass-resources-412/snippet-event-186555/

The other method is:
JASS:
TriggerAddCondition(t, Condition(code))
// ^ to register, one trigger per event 
TriggerEvaluate(t)
// ^ to fire event

Dynamic events has been something that was scrapped. It only complicates. You hardly need to "unregister" an event. Destroying the trigger will normally suffice. In which case, you definitely want to use conditions instead of actions to avoid leaking.

---

Basically, I think this could be implemented differently.
 
Level 8
Joined
Nov 20, 2011
Messages
202
The user can rewrite the response part ofc. He just needs to add an attribute, or rename the others. I also got a version where there is an extra response object but this was crap aswell.
To use a variable event to fire the custom event i don't like very much but i will use the second way.(edit it soon)
To unregister actions is in my eys one of the main reasion for using custom event systems. This allows you to save a lot of useless function calls.
 
Level 8
Joined
Nov 20, 2011
Messages
202
I would like Nes' Event in Wurst(lighter and faster)

Not realy faster, due to the fact that u can't remove actions. There are so much unnecessary calls especially in damage systems.
Imagnie u got a spell who blocks the next attack with 20 sek cooldown. If u would use Nes system there is an function call on each attack. U know how much attacks an unit get per minute? With my system there would only be a function all every 20 second. This is why unregister actions saves so much performance.

Also take a look at the cast event snippet:
Normaly u write a custom spell on the way that u check if your ability was casted. Now imagine a map with 200 custom spells (this is not realy much), so on each cast you will cause 200 function calls + 200 checks + 200 GetSpellAbilityId(). With my snippet there are just 2 function calls.
 
You wouldn't use actions in such cases because they're synchronous and thus quite a bit slower anyway :p (there's overhead, you'd get the same execution speed with conditions, but there will still be overhead)

(Also, you wouldn't use a system like this for a damage detection system anyway. You'd want to maximize speed and use a trigger of your own which you would refresh every now and then to make sure there are no dead events registered)

The only type of damage detection system that might fail with something like Event by Nes is one that allows registry for individual unit damaged events. There are probably more cases, but this is all I can come up with on the spot now
 
Level 8
Joined
Nov 20, 2011
Messages
202
You wouldn't use actions in such cases because they're synchronous and thus quite a bit slower anyway :p (there's overhead, you'd get the same execution speed with conditions, but there will still be overhead)

At least we are talking about nano seconds, the differnce between actions and conditions is like nothing. Maybe u can fire 20000 actions but 20010 conditions per second. I don't think this will speed up any system more than 0.1%.

(Also, you wouldn't use a system like this for a damage detection system anyway. You'd want to maximize speed and use a trigger of your own which you would refresh every now and then to make sure there are no dead events registered)
So however UNIT_TAKES_DAMAGE is managed intern, i don't think there is a big difference between a trigger with a lot of dead events and a trigger without.
But you still don't get my point:
The main problem with damage detection is, that the main event fires realy often. So if u create spells/passiv/ect just like before, that each spell checks on his own if an action is realy needed, u get so much unnecessary function calls. And this ones are realy slowing down everything very much. Just take a look at my exampels up there. The performance you save by reducing function calls so hardly is much more than to have no dead events in a trigger and use condition instead of actions....

The only type of damage detection system that might fail with something like Event by Nes is one that allows registry for individual unit damaged events. There are probably more cases, but this is all I can come up with on the spot now

And exactly this kinds of events save so much function calls:
Image you unit got an 1% dodge chance and you want to realize that if the unit dodges an attack it gets 10% movespeed for a short time. Now you can check on each attack if the unit dodged it, so you waste in average 99 function calls, becuase just 1 of 100 is needed. With my system you would just create an event "onDodge" which only fires if the unit realy dodges an attack. Each spell wich want to do some action when an unit dodges an attack just needs to register an action at the event.
Take a look at my example spell up there this shows you how custom events can be used to safe performance and write much nicer code.
 
Level 26
Joined
Aug 18, 2009
Messages
4,097
It's not only about speed (and you have to consider deregistering and reregistering also takes its share). It allows a more flexible way of scripting. Why would I want to specify extra filters if I can directly attach the event to a subject?

In reality, each event type should have its own register/deregister functions because not all events are as simple. Take the player limit events for example. You want your function to fire when the player exceeds 1000 lumber. Now it would be bollocks to detect every change in lumber in the concrete code. On the other hand, you would not create an event for every possible amount and limit operator. Thus, you pass extra parameters to the register function and the system handles it to its best.
 
Ahem, when doing a DDS, if you want specific unit events, the idea is to have a global event and a unit unique event (see old DamageEvent implementation). The best possible implementation would be to put both the global and unit specific events into the core of the system to maximize the speed.

Unit specific events require a different kind of event system. Rather than registering the event 1 time for each unit, a struct is only registered one time given that it has instances on it, and from there a linked list for the struct is iterated over. When an instance is destroyed, it's removed from the struct. When the struct reaches 0, it unregisters itself. To unregister events properly, a similar recycler to the one found in CT32 should be used.

So both Event and this would fail for unit specific damage events, but Event wasn't built for that.

Given that there is currently no resource for unit specific events following the design I just laid out, maybe one should be written. =)

edit
And btw, a unit specific plugin can be made for DDS, there is a spot for it =)
 
Level 8
Joined
Nov 20, 2011
Messages
202
Ok, but how would you trigger a dodge without the use of a DDS which then would also fire everytime the unit is damaged?

Ye thats true, but the damage system would not only support dodge, also normal hits, criticals hits and so on. In my damage system just one central function gets called and in this everything gets detected, dodge, crits ect.. And the refering events to this actions gets fired.
So yes on each attack u need to have at least one function call, but as soon as u got more than one spell you will save function calls.

I can only repeat myself take a look at the spell example!
 
Last edited:
Level 14
Joined
Dec 12, 2012
Messages
1,007
Ye thats true, but the damage system would not only support dodge, also normal hits, criticals hits and so on. In my damage system just one central function gets called and in this everything gets detected, dodge, crits and so on. And the refering events to this actions gets fired.
So yes on each attack u need to have at least one function call, but as soon as u got more than one spell you will save function calls.

Ok, now that makes more sense.

I can only repeat myself take a look at the spell example!

I looked at the example. Its just you said something different than you meant ;)
 
Level 8
Joined
Nov 20, 2011
Messages
202
Just added the possibility that an event destroys itsself if there are no longer any actions registerd. To achieve this just set the attribute "destroyIfEmpty" equal true. If you destroy the last action of an event the event will get destroyed automaticly.
I also fixed a bug due to which it was not possible to destroy an action while it is executed.
 
Level 14
Joined
Jun 27, 2008
Messages
1,325
To be honest DDS isnt the best example for the usage of events since DDS is something you want to hardcode in many cases (stuff like proccs, evades, etc.). Its not pretty but its fast and simpel and writing events for 50 different things you only use for one single spell or something is overkill. Thats the sad truth and most of the bigger maps i know do it this way.

Anyway,
#SWAG #YOLO #WURST
 
Level 14
Joined
Jun 27, 2008
Messages
1,325
You should add visibility modifiers to the public classes.

Also maybe dont use the form "package ... endpackage", most packages of the stdlib dont use indention for the first level of statements and the "package ..." form. I doesnt really make a difference, but adopting the conventions of stdlib seems like a good idea.
 
Top