[MUD-Dev] The Aedon rule system (was: FW: Article on Global Verbs & Bulk Bug)

Federico Di Gregorio fog at mixadlive.com
Tue Feb 6 01:33:48 CET 2001


hi *,

i read this article with a lot of interest, because it presents some
problems a discussed with a friend for some months now. the problem of
local/global verbs and consistency is, imho, foundamental in a text
only mud. after a couple of nights spent talking, we found what we
tought to be a nice solution, "stacked parsers" and "rules." i also
wrote a testbed mud server to test those and other ideas. it is not
finished, but we are slowly adding the missing parts. in the meantime,
maybe some of you is interested in something i wrote to explain what
we mean by "rules".  it was put togheter pretty fast, for sending it
to the sourceforge ML after a discussion in IRC. "stacked parsers" are
another story and i'll explain them another time, *if* someone ask for
them. maybe what follows is more than enough to bore to death all of
you...

ah. the prototype mud server (called aedon) is available by anonymous
(no password) cvs (module aedon) at:

	:pserver:anonymous at cvs.initd.org:/var/cvs

ciao,
federico


The Aedon Attribute and Rule System
***********************************

0. Introduction

This document describe some ideas about how to write and extensible
and easily maintainable rule set for a MUD. A testbed implementation
(written in Python) is available as the Aedon MUD server. Please
contact the author, Federico Di Gregorio <fog at debian.org> for more
information, cvs access and the like.


1. Definitions

The term ``instance'' is used to refer to an object in the OO
programming language choosen to write the MUD server. Objects in the
MUD world are referred as ``concretes.'' The term ``attribute'' is
used when talking about concrete attributes, like size, weight, et
al. When referring to object attributes the term ``value'' is used
(yes, this is bad but I am not english and I have a limited
vocabulary.) ``Rule'' is used to refer to both game rules and server
rules.


2. Concretes

Every object present in the simulated world is represented by an
instance of the Concrete class in the server. Concretes are organized
in a hierarchy but this has nothing to do with the OO hierarchy of the
programming language used to write the MUD server. Every concrete has
a *classes* attribute that lists the full number of classes the
concrete belongs to. By looking at the classes attribute one can
extrapolate the full hierarchy tree, but that's all. There is no
inheritance or instance and class methods: nothing of the classical OO
programming. A concrete can belong partially to a class. For example,
a rotten apple is still edible, just not as edible as a good one. We
can express this concept by assigning a value in the range [0,1] for
every class the concrete belongs to.

Some examples:

  Note 1: I'll use the python grammar for representing attribute
          values (omitting `"' for the strings.)

  Note 2: the first name below is the object id.
  Note 3: sytara is my favourite human character.

  sytara.classes = {living:1.0, human:1.0, edible:0.9}
  apple.classes = {vegetable:1.0, fruit:1.0, edible:1.0}
  rotten_apple.classes = {vegetable:1.0, fruit:1.0, edible:0.6, 
                         poison:0.1}
  wolf.classes = {living:1.0, animal:1.0, wolf:1.0, edible:0.9}
  sword.classes = {weapon:1.0, sword:1.0, iron:1.0}

The classes attribute is used by the server to choose what rules and
constructors to apply. Read on...

3. Attributes

Apart from classes, a concrete can have any number of attributes. An
attribute is completely defined by its id (or name), its value (int,
float, string, list, map), and tree sorted lists of triggers, get
filters and set filters. A concrete is born with a relatively small
set of attributes (usually a concrete is created by copying another
concrete or an archtype, i.e., a concrete that has been saved to
static storage for future use by developers) but gets more while
interacting with other concretes.

When a concrete is asked for an attribute it does not have, the system
select and applies a *constructor* for the attribute. Constructors are
overloaded. That means that there are different constructors for the
same attribute and one is selected depending of the concrete class.

A little example: the rule system specify that the attribute used to
determine how much food an edible provides is called food_value. The
wolf attack and kills sytara (sic!) then eats the corpse. But a human
hasn't the food_value attribute so the system looks up the food_value
constructor table:

  food_value (DEFAULT) [
      return 0
  ]

  food_value (O.IS_OF(living):1000) [
      return O.get('food')
  ]

The default is to return 0, i.e., the concrete does not provide any
food. But, if the concrete is a living (like sytara was) the second
constructor is applied and the value of food_value is the current food
attribute (sytara had her breakfast just half an hour ago... what a
lucky wolf!)

Attributes can also have sorted lists of filters that gets called in
order on a get() or set(). That's usefull to resolve the effects of
temporary changes to the attributes. Moreover the filters get a handle
on the concrete requesting the get() or set() and can return different
results to different classes of concretes. An example is a spell that
makes sytara less appetible to wolves. The spell resolve its effect by
placing a timed filter on food_value attribute. If a wolf asks for
that attribute (the AI wants to target the concrete yelding the most
food) the filter returns a fake value (maybe even 0 if the spell is
strong enough.) Another way for the spell to work is to filter the
size attribute... maybe the AI does not attack *big*
creatures... eheh.

set() filters can be used in the same way to reduce damage, etc...

There are also triggers, that are called in prioritized order every
time an attribute is accessed until one of them returns a false value
and stops the chain. Triggers are usefull to implement reactions, and
thing like that but this is outside the scope of this paper.


4. Rules

Rules are the core of the system. A rule chain can be started by a
player command (eat apple), by a critter action (the AI governing the
wolf decides it's time to eat and attacks the nearest living, for
example), by a timed event (a spell expires) or by another rule.

A rule has always a *subject* (S) and can have an *object* (O) and a
*complement* (C.) In the examples above the subjects are the player's
character and the wolf, the objects are the apple and the living (my
poor sytara) and the complement the wolf's fangs (the weapon the wolf
is using in the attack.)

A rule is composed by a *master* part, a *default* part (also called
the master rule and the default rule) and by any number of normal
parts (also called simply rules or subrules.) The master part specify
how other parts are to be applied (that's the rule policy.) Policies
are:

  * apply every rule that scores (see below) over a certain value,
    apply default if no rule scores high enought.

  * apply every rule that scores (see below) over a certain value, but
    don't apply the default.

  * apply only the top scoring rule.

  * apply the top scoring rules only if it scores above the given
    value, else apply the default one.

Scoring depends on subject, object and attribute classes and
attributes. A rule scores points when S, O or C are of the given class
(the score is multiplied for their class value) or have certain
attributes. Obviously, when the server is evaluating a rule no new
attributes can be created. Here's a little complete example taken from
the current server:

  eat (MASTER, RULE_CUT_VALUE:750) [
      pass
  ]

  eat (DEFAULT) [
      return 'Mmm... %s does not seems edible.' % O.short_desc
  ]

  eat (S.IS_OF(living):500, O.IS_OF(edible):500) [
      max_food = S.get('food_max', 5)
      food = S.get('food') + O.get('food_value')/S.get('size', 1) * f
      if food > max_food: food = max_food
      S.set('food', food)
      O.destroy()
      return O.get('msg_eat', 'Tastes good.')
  ]

Some explanations: every rule that scores over 750 is applied. By
default an object is not edible, the default rule just print a short
message. If a living tries to eat something edible the last rule is
applied. It gets the food_value attribute from the object (if the
object does not have it, the server invokes the right constructor,
read above) and use it to calculate how much nurriture the object
provides (f is the factor, i.e., rule score/1000.) After that a custom
message (if present) or a default one is returned to the user and the
object destroyed.

Now, let's suppose we want to add a posioned apple to the game. Let's
also suppose that the rule for appling venom effects is already in
place. We can do it by adding the following rule:

  eat (S.IS_OF(living):500, O.IS_OF(food):400, O.SHOULD_BE(poison):100) [
       APPLY(poison)
       return O.get('msg_eat', 'Tastes strange.')
  ]

This rules *requires* the object to be of class poison. But that is
not sufficient to apply the rule. It still has to score at least
750. If the player tries to eat a poisoned dagger, it gets the
"Mmm... the dagger does not seems edible." message, because no rules
score high enough and the default one gets applied.

If the wolf tries to eat rabbit killed by poison (classes =
{living:1.0, animal:1.0, rabbit:1.0, edible:0.8, poison:0.2}, because
the venom makes the rabbit a little bit lesser nutrient and a little
bit poisoned) this rule scores 500*1.0 (the wolf is a living) +
400*0.8 (the rabbit is edible) + 100*0.2 (the posion) = 840. The other
rule scores 900. So both are applied. The only effect of this rule is
to apply the poison rule. The message returned is the one from the
other rule (the one who scored higher): poison level is too low to be
tasted.

Note that the addition of this rule not only enabled us to add
poisoned apples to the world but every kind of poisoned food. It's
like adding some specialized methods at the right hierarchy level in
an OO paradigm, but the is a foundamental difference. We don't need to
care about how the other rules were written (the ancestor methods in
the OO world) nor we need to remember to call them.) Rules are
independent and is the system that selects the right ones to apply.


And that's almost all, waiting for comments,

federico

--
Federico Di Gregorio
MIXAD LIVE Chief of Research & Technology              fog at mixadlive.com
Debian GNU/Linux Developer & Italian Press Contact        fog at debian.org
                             Best friends are often failed lovers. -- Me
_______________________________________________
MUD-Dev mailing list
MUD-Dev at kanga.nu
https://www.kanga.nu/lists/listinfo/mud-dev



More information about the mud-dev-archive mailing list