Adding fishing gameplay
Recently I've performed a major refactoring of how entity interaction happens in the Worldforge system. The basis of it is the new "usages" property which declares the actions that are available for each entity. As an example of how this can be used I've implemented a simple "fishing" task. When fishing you need a "fishing rod", and you need worms (or "annelids" as they are called in the system). You get these by digging in the ground with a shovel and then sifting through the resulting piles of earth with a sieve.
The "fishing rod" type is defined here. It has a "Fish" usage, which has a constraint of "get_entity(actor.attached_hand_primary) = tool and contains(actor.contains, child instance_of types.annelid)". The constraint checks that the rod when activated is wielded by the user, and that there's at least one worm in the user's inventory.
It declares on parameter, a "target" which is of type "entity_location" (i.e. a position on another entity) with a constraint of "entity instance_of types.ocean && actor can_reach entity_location with tool". The target constraints requires that the target is of type "ocean" and that the user can reach the position with the fishing pole (the fishing rod has a reach of 5 meters, as defined in the "reach" property).
By declaring these rules using constraints we can remove a lot of boilerplate code from the actual rule, since the simulation engine will take care of checking these constraints before the rule code is invoked. It's also now possible for clients to check the constraints themselves (and disable the usage if the constraints doesn't match).
The usage defers the rule handling to the handler "world.objects.tools.FishingRod.fish". This is a reference to a Python script, to be found here.
The usage handler will immediately create a Fishing task instance, with a "tick interval" of two seconds. The task inherits from StoppableTask, which just means that it will by default have a "stop" usage attached to it. It will otherwise run indefinitely.
Every two seconds the “tick” method is called. It will do a random check to see if there’s a fish on the line. If so, an Imaginary op about this is sent to the client.
If the client stops the task while there’s a fish on the hook a new Fish entity will be spawned, and the worm entity will be consumed.
The gameplay is fairly simple, but it serves as a good example of having the Entity Filter, the Usages and the Task systems all working together to provide gameplay. Since all of the rules are handled by either Python scripts or Type properties the feature can be altered and iterated on in real time without having to restart the server.