Overview
The event subsystem consists of a couple of classes, the central one is the BoEventManager.
There is always exactly one BoEventManager object in the game. It contains pointers to all BoEventListener objects. Beside that, it contains a queue of all events that have been received already (but not yet been delivered, because it is combined with a timer).
The event manager contains an advance() method that is called once per advance call - in it the timer of every event in the queue is reduced by one and once there is an event in the queue whose timer reached 0, it is delivered to the event listeners.
An event is added to the manager (and therfore to the game) using BoEventManager::queueEvent() - it is delivered in the next advance call (or later, if the event contains a timer).
The next class, probably more important, is the BoEventListener. The event listener does - as the name implies - listen for events. When an event is delivered by the EventManager, the event listener receives the event.
A received event is first delivered to the BoCondition objects in the event listener (more on this below). When a condition is fullfilled, the action that belongs to the condition is executed.
After that, the pure virtual method BoEventListener::processEvent() is executed.
A derived class can do any stupid or smart things in that method. For example, the event listener for local players may inform the player (using a chat message) about that a unit has been produced, when a UnitProduced event is received.
A global event listener however, may even maintain winning conditions and end the game as soon as a player has won.
There are always at least two event listeners in the game: the BoLocalPlayerEventListener and the BoCanvasEventListener. The canvas event listener is the global event listener receiving all events. The localplayer event listener however receives only "global" events, that are always visible and events that occurred at a location that is visible to the local player. See description of the BoEvent class for more on this.
The BoEvent class is a very simple class - it just represents an event.
An event is described by a couple of values, most importantly by the name of the event. The name is (for simplicity) a simple c string (a number would be slightly faster, but e.g. in scripts it would be a lot more difficult to handle).
Other parameters are the UnitId and the PlayerId - of course they only contain useful data for certain events.
An event may also contain a "timer", the "DelayedDelivery". This number describes a number of advance calls that the event manager waits before the event is actually delivered. 20 advance calls are about 1 second at default game speed.
The event may also contain a location (optional of course). The location is the point on the map where an event occurred, this may make sense for a UnitDestroyed event for example. An event with a location is not delivered to players who are not able to see that part of the map.
Finally an event contains an RTTI (this is the type of the class) and additional optional data, depending on the RTTI. For ULong events, the event may contain 2 ULong parameters, for String events, the event may contain 2 strings.
BoCondition
This class is probably the most complex one. It represents logic formulas of the following form:
(C0) OR (C1) OR (C2) OR ... OR (Cn)
Where every C0..Cn is a BoCondition object.
C0 is always part of one EventListener, C1, ..., Cn are always part of C0. C1, ..., Cn are also called "alternatives".
Every condition (i.e. every part of C0, ..., Cn) is a combination of objects of BoEventMatching and BoStatusCondition. These objects are all (!) connected using AND, so the (single) condition is only fullfilled when all of them are true.
As you can see, BoCondition builds up a disjunctive normal form.
BoEventMatching describes when an event that is received, matches an event that is stored in the BoCondition object. It basically is the same as an event - if you have multiple event matchings in a BoCondition, then you are just waiting for multiple events.
BoStatusCondition objects are a bit more comples: they are calls to script functions. These functions must either return 0 (false) or 1 (true). This is very powerful, but can be very slow, too. You should always prefer to use events, if you can chose between events or status conditions.
A BoCondition object in the event manager is fullfilled, when either the object itself is fullfilled (BoCondition::thisConditionDone()) or one of the alternatives is fullfilled. Use BoCondition::conditionDone() to test for both at once.
As soon as the condition is fullfilled, the event listener is supposed to execute an action - this is done by BoCondition::fireAction(). At the moment this is always a "CustomStringEvent", with a dummy parameter. In the future, this is supposed to be either a chat message, or a event with arbitrary parameters, or a call to a script function.
View from outside
When all you want to do is to actually use events, then the entire story above becomes a lot more easy. All that you need to do is the following:
-
- BoEvent* event = new BoEvent("EventName");
-
- event->setUnitId(unitId); // optional
-
- event->setPlayerId(playerId) // optional, but required, if you used setUnitId()
-
- event->setLocation(location); // optional
-
- boGame->queueEvent(event);
The rest is done automatically.
When you want to use the event, all you need to do is to edit the processEvent() method of the relevant eventlistener.
Special note: you are supposed _not_ to touch BoCondition when working in C++. This is mainly required for providing a GUI for certain things (such as winning conditions).