A Guide To Activities

by Jochen Bedersdorfer

Version 1.1 from 30.11.97

This document describes the steps needed to create Activities in the White Orb System.
Activities are used to implement a special behaviour for creatures or, generally, instances of Physical.

Activities are based on rules, so you should read the rule guide beforehand.
The main class Activity is a subclass of Effect, which means it is used as a normal effect for every instance of Physical. Activities are controlled by a Soul in that the Soul manages different activities and switches them on incoming ActivityEvents.
Naturally this means, that every Activity must have its own corresponding ActivityEvent class. We'll look later at StartFishingActivity for a possible implementation.
The Soul guarantees, that only one activity is in effect at any time. If it receives a StopActivity event or before any activity is active, a DefaultActivity is provided (which does nothing).
Once an activity is put into the effects collection of a physical, it receives events just like other effects and tries to find a rule from its RuleSet that wants to fire on this event (Basically the same happens within NPCSoul).
If a rule is chosen, the activity delivers its actions as usual.
But the activity has also the possibility to add filters or other effects to the parent physical. Just as an example, you could write a filter that prevents movements while you are doing the activity. Or the other way around: Add a filter, that stops the activity, when a MoveEvent comes around.

I'll take a quick look now at relevant methods for the happy creator of Activities.


The Activity Class

If you implement an activity you have to subclass from the Activity class. Now we take a look at the involved methods:

public void init(RuleSet rs, Soul soul);

This initializes the activity and gives it access to the outside world via a Soul. By default, the Soul allocates the ruleset for you, so it is possible to already insert rules before loading the activity. soul and rs are fields of Activity and are used to add rules or to add filters.

public void startActivity(ActivityEvent ae)

This method is called by the soul when the method should start. The parameter ActivityEvent is the corresponding event object, so you have to cast it to the right type, if you want to use it in your own activity. This must be done, for example, for activities that use values from the ActivityEvent such as a GotoActivity, which would need a target location to go to.
If you need to rebuild your ruleset for every start of the activity, this is the place to add rules to it.
Important! Always call super.startActivity(ae) at the end of this method, otherwise your activity won't be started at all !

public void stopActivity()

This is to stop the activity. It is removed from the effects collection and gets a chance to remove its filters. You normally don't need to overwrite this.

public abstract void add/removeFilters()

This methods must be implemented by the activity and are used to add filters (or even effects) mostly to soul.getBody() with soul.getBody().addFilter(...)
Make sure that every filter you add is also removed !

That is all there is to the Activities methods. Look at the API documentation for the messy details.

An Example

Let's look at the fishing activity in detail to see how it's done:
public class FishingActivity extends Activity {
 
  /** Since the Fishing rule is removed, after it found a fish. The rule
    must be inserted again with startActivity. Otherwise do it in init() 
    **/
  public void startActivity(ActivityEvent ae) {
    rs.removeAllRules();
    rs.addRule(Fishing.class);
    super.startActivity(ae);
  }
...
This time I chose startActivity to add the Fishing rule since fishing is removed from the ruleset when it is finished (remember rule.removeMe() ?). Important here once again is to call the parents method.
The dirty work is done in orb.ai.rules.Fishing, so you might take a look at this.

To get the activity to work with a NPCSoul for example, we take a look at the BartenderNPC again:

public class BartenderNPC extends NPCSoul {
  Bartending r;
  public BartenderNPC(Clock clock, Physical c) {
    super(clock,c);
    r = (Bartending)rs.addRule(Bartending.class);
    r.setPriority(200);
    rs.addRule(StartStopActivity.class).setPriority(300);
    loadActivity(FishingActivity.class);
  }
...
As you see, it uses Soul's loadActivity() method to preload the activity in the NPCSoul. This actually means, this NPC learned how to fish !
You can of course add as many activities to a soul as you like. If the soul receives a subclass of ActivityEvent, it will chose the correct activity and put it in effect.
Wait a moment, we don't have a StartFishingActivity yet !
public class StartFishingEvent extends ActivityEvent {
        public StartFishingEvent(Active listener, Item source) {
                super(listener, source);
        }
        
        public StartFishingEvent(ItemHeader source) {
                super(source);
        }
As you see, StartFishingEvent is a subclass of ActivityEvent which in turn is a subclass of DirectEvent. DirectEvent forces you to implement some abstract methods I won't discuss now. See some implemented DirectEvents for details.
So we jump to methods you have to implement in your ActivityEvent subclass:
        public String getName() {
          return "fishing";
        }

        public Class getActivityClass() {
          return FishingActivity.class;
        }
}
The method getName() is mainly used for debugging purposes, but can later be used in the property viewer of the activity.
Most important is getActivityClass() which provides a link to the Activity subclass that is associated with this class.

Some Tips

At last some tips for implementing activities. The author must confess, that he doesn't have THAT much experience in generating activities, but there are some stumbling blocks.

 You should take a look at all the rules you use in an activity to see if they depend on a completely initialized world to function ! As in the example above, when running through the constructor of BartenderNPC the game world probably isn't created completely. Since the activity is loaded and initialized there, you should take care of when you insert the rules.

 It's better if you play it safe by loading activities in doBirth() of NPCSoul. This method is called when the clock starts running and that hopefully means, the world is completely there.

Another thing to take care of is to provide the correct source of the event that starts an activity. It must be the soul itself, since the source is used to start the activity in the execute() method.

If you don't care about rules, you can also subclass processEvent() of activity and provide your own implementation. This is of course not recommended.


I hope you got the idea. If you have suggestions to make more out of activites, don't hesitate.