Simple rules combined to complex gameplay
A while ago I was asked about an example of how the Worldforge rules work. The focus was especially on how a series of simple rules allows for more complex game mechanics. Since I wrote down an example of how consumption works I thought I might as well post it here, since it provides a good example of how we think about game rules.
One example could be the “bread” entity type.
Breads can be eaten, so the bread class includes a “usage” of “consume”.
<map name="usages">
<map name="default">
<map name="consume">
<string name="name">Eat</string>
<string name="handler">world.objects.Consumable.consume</string>
<string name="constraint">actor can\_reach tool</string>
</map>
</map>
</map>
There’s a simple constraint on the usage that the actor which performs it must be able to reach the bread. The script for the “consume” action is then handled by “world.objects.Consumable.consume”, found here.
The script will check if there’s a conversion property set ("_consume_mass_conversion") as well as a property specifying the kind of consumption (“consumable_type”). For the bread the consumption type is “plant”. The result is that the bread is destroyed, and a “Nourish” message is sent to the actor (the one which consumed the bread). The Nourish message (or “operation” as we call them) will contain information both of the consumption type (plant) as well as how much nourishment it contains (depending on the size of the entity being consumed as well as any conversion property.
The Nourish op then needs to be handled by the entity that ate the bread. Consider if a human ate the bread. The “human” class will inherit from the “creature” class, which is defined here.
The “__scripts” property contains scripts that define the behaviour and can install “operation handlers”. One of the scripts of “creature” is “world.traits.Nourishable.Nourishable”, which is defined here.
This script will perform a couple of checks and apply modifications, and then increase the “nutrients” property of the entity. The “nutrients” property specifies how much nutrients an entity contains. For a creature it would be how much food it has eaten. Another script, “world.traits.Metabolizing.Metabolizing” then acts on this, and allows the entity to grow or heal if there’s enough nutrients.
By default all “creatures” are omnivores, i.e. they will convert both “plant” and “meat” nourishment to nutrients. But take the wolf for example, as found here.
It has this property, which makes it not receive any nutrients from “plant” nourishments, thus making it a carnivore:
<map name="\_modifier\_consume\_type\_plant">
<float name="default">0</float>
</map>
Conversely a “cow”, as found here, is an herbivore as specified by
<map name="\_modifier\_consume\_type\_meat">
<float name="default">0</float>
</map>
But even plants can be nourished. So plants, also use the same world.traits.Nourishable.Nourishable script. However, they obtain nourishment by consuming from the entity into which they are planted (if the are planted). Therefore they use a script at world.traits.PlantFeeding.PlantFeeding which will at a regular interval send a “Consume” operation to their parent entity, for the “soil” type of consumption.
A typical entity which then would be able to nourish a plant would be the “land”. Other entities would just ignore the Consume op sent from the plant. The “land” contains the “world.traits.PlantNourishing.PlantNourishing” trait/script which allows it to send back Nourish operation to any plant.
So in this way a couple of shared scripts can be reused in many different entities, for nourishment both for entities that eat things (creatures) as well as those that suck nourishment from the ground.
It doesn’t stop there though. The “fire” entity, uses the Fire.py script to at regular interval consume other entities by sending Consume operation of the “fire” type.
This kind of Consume operation will be ignored by entities, unless they implement the “Flammable.py” trait. So a rock wouldn’t be consumed by fire, whereas a log would be, as the latter has the Flammable trait. The speed of fire consumption is further governed by any “_burn_speed” property.
And so on. All game play if defined by having these smaller scripts which interact with properties and send messages/operations to other entities.