[MUD-Dev] Modeling spells/skills as collections of affects

clawrenc at cup.hp.com clawrenc at cup.hp.com
Fri Sep 12 12:45:31 New Zealand Standard Time 1997


In <Pine.OSF.3.96.970911143441.11343A-100000 at saul7.u.washington.edu>,
on 09/11/97 
   at 03:23 PM, Dan Shiovitz <scythe at u.washington.edu> said:

>On Thu, 11 Sep 1997 clawrenc at cup.hp.com wrote:
>> In <Pine.OSF.3.96.970905163356.4223B-100000 at saul4.u.washington.edu>,
>> on 09/05/97 
>>    at 04:49 PM, Dan Shiovitz <scythe at u.washington.edu> said:
>> 
>> >On Thu, 4 Sep 1997 clawrenc at cup.hp.com wrote:
>> >> In <199708312324.3515400 at bedford.net>, on 08/31/97 
>[..] 
>> >I'm still not precisely sure what to do with someone that wears
>> >cursed gauntlets of fumbling that make her miss all the time and
>> >wields the magic  spear Elsinore that always hits its target. 
>> 
>> The spear hits its target after the player drops it.

>Shoot, why didn't I think of that? 

I had the advantage of hanging out with an old D&D player who
delighted in putting his DM's in such impossible positions.  One of
his favourite characters was a short fat guy ("Tubolard"?) who could
chant certain magic words, with each incantation doubling his size
with mass/weight following the cube law.  Other than this singular
ability, Tubolard was stupid, weak, and generally ineffectual.  

In one campaign of note the party had proceeded fairly well thru the
various trials the DM put them up to, had finally cornered the evil
wizard, only to have the evil wizard put his back to the mountain and
then shield himself behind an impenentrable wall of magical force. 
Tubolard's response was to chant his magic words twenty times and then
run at the wizard shouting, "Come to Pappa!".  It took the DM nearly
ten minutes of furious rule book searching and dice rolling to decide
what happened when an effectively irresistable force (the charging
Tubolard now many miles tall), met an impenetrable wall (the shield). 


FWLIW the decision was that the wizard died, squished between his wall
and the mountain, and the entire party died, crushed under the falling
splattered bits of Tubolard as both he and the shield simultaneously
gave way in a cloud of improbability particles...

The same friend also delighted in playing a stupid, weak, constipated,
diabetic incredibly clumsy bard with incredible joke telling talents. 
In one particular campaign they party was stuck ouside a huge well
defended and patrolled fortress containing the evil whatever and the
the bauble the party was in search of.  The party got busy trying to
devise a cunning plot to crawl int hru the sewers, magically hide
themselves with various spells while creating various diversions etc.
Meanwhile the bard walked unhindered into the evil whatever's fortress
disabling every guard, monster, and even the evil whatever himself
with jokes along the way.  Said bard then walked back out in the same
manner, with the priceless bauble tucked under one arm, to then trip
over a pebble in the road outside the fortress further reducing every
guard and the party to helpess giggles....

The rest of the party wasn't terribly fond of this character after
they were beset by a great horde of Orcs for which the bard cracked a
joke leaving every Orc and the rest of the party rolling on the ground
in giggles, followed by the bard walking off unbothered.  The problem
of course being that he left the party giggling on the ground amidst
the giggling Orcs...who soon ceased giggling.

>> >Or maybe I just shouldn't have any always- items in the
>> >game :)
>> 
>> I'd say this is a good suggestion.

>The problem wih just not having them is that the concept I'm thinking
>of is one where always- items are very apppropriate, if rare (eg,
>Macbeth, who could not be slain by any man born of woman -- so he got
>knocked off by a guy ripped forth from his mother's womb who was not
>technically "born"). On the fourth hand, it might be best just to
>handle these few items as special cases; it's entirely possible for 
>mountains to tremble and the seas to boil when the Invincible Warrior
>Karnak battles Dargba the always-victiorious.

Precisely.

>> >I'm not quite clear on why spoofs are a good idea, though. It seems 
>> >like replacing the entire object just to modify its hitroll (or
>> >whatever) is a bit excessive; can't you just have a list of affects
>> >on the object and ask each of them if they modify its hitroll, and
>> >have queries about the object's hitroll receive a consolidation of
>> >all that information?
>> 
>> A spoof is expensive if all you are doing is touching up a hitroll. 
>> They are really built for more complex and object-incasive cases where
>> entire methods are replaced or altered, or the entire behaviour of the
>> object is transformed, such as:

>Hmm, I guess. I can see for cases where you transform a slug into a
>dog, it would be easiest just to spoof it.  (Well, actually, I'd swap
>the slug's mind into another body and put the old body into storage,
>but there you are).  On the other hand, for stuff like the elven
>forest scenario, I would personally give everything in castle krak a
>permanent affect that alters their "draw magic" or "use magic"
>skills.  

How about Bubba who flys a magical flying carpet into the castle,
while bearing the Magical Sword of Glowing Light?  All that magic
works, and Bubba sits untouched in mid air in the throne room with his
sword glowing brightly until Boffo takes the Sceptre to the Elven
Forest.

>The sort of affects I'm visualizing can have functions
>attached to determine their effects, so it's no problem to give them
>something like "if the scepter is in the forest, set draw_magic skill
>to -999, else don't change it". I dunno. This way seems more sensible
>to me .. but perhaps I'm not clear on how you plan to actually
>implement the spoofs?

Its easiest to diagram:

  Object A exists.
  Object A is spoofed:
    Object A is copied, creating Object B.
    Object A is deleted.
    Object C is created with the exact same ObjectID as the old
ObjectA.

Variation:

  Object A exists.
  Object A is spoofed:
    Object A is copied, creating Object B.
    The contents of Object A are deleted (all methods and attributes).
    The contents of the spoof objects are copied into Object A.

Both have the same result: the spoof takes the place of the original
object.

I then allow spoofs to have a "generic method".  Simply put, a generic
method matches any and all incoming calls/messages (I'm message based,
not stack based).  This allows a spoof to accept ALL incoming messages
and then pass them on (edited or not) to the original object (Object B
above), and then forward the (possibly edited) results back to the
original calling object.

Thus a set of spoof intallations might look like:

--<cut>--

Given ObjectX.
ObjectX is spoofed.
  ObjectX is cloned to ObjectY
  The contents of ObjectX are replaced with the contents of the
    spoof.
  The general method on ObjectX passes calls back to ObjectY.
  ObjectY is the original object.
ObjectX is spoofed again.
  ObjectX is cloned to ObjectZ.
  The contents of ObjectX are replaced with the contents of the
    spoof.
  The general method on ObjectX passes call back to ObjectZ.
  The general method on ObjectZ passes calls back to ObjectY.
  ObjectY is the original object.
Object X is spoofed.
  ObjectX is cloned to ObjectA.
  The contents of ObjectX are replaced with the contents of the
    spoof.
  The general method on ObjectX passes call back to ObjectA.
  The general method on ObjectA passes calls back to ObjectZ.
  The general method on ObjectZ passes calls back to ObjectY.
  ObjectY is the original object.
ObjectZ spoof is removed.
  The contents of ObjectZ are replaced with the contents of ObjectY.
  ObjectY is deleted.
  The general method on ObjectX passes call back to ObjectA.
  The general method on ObjectA passes calls back to (new) ObjectZ.
  ObjectZ is the original object.
ObjectX spoof is removed.
  The contents of ObjectX are replaced with the contents of ObjectA.
  ObjectA is deleted.
  The general method on (new)ObjectX passes call back to ObjectZ.
  ObjectZ is the original object.

--<cut>--

The following highlights some of the other fillips:

--<cut>--

> 1. Bubba is carying the blessed suitcase of Googoo.
> 2. Googoo activates the hidden powers of the $suitcase making it
>    green in color, full of gold and Xray proof. This creates a
>    spoof.
> 3. Bubba finds the holy lockpick of Crom and uses it to unlock the
>    suitcase.
> 4. Googoo deactivates the powers of the suitcase (removing the
     spoof).
>
> Is the suitcase still unlocked? Why?

This is one of those side-effects I mentioned above.  Its up to the
spoof author to determine if he is interested in those methods (and
subsequent changes) and if so to intercept or maintain them properly.

The locked/unclocked state of the resultant suitcase depends on the
methods defined on the spoof, and its destructor.  *ANYTHING* _could_
be done.  It could easily be arranged to be locked, unlocked, or
something else (eg the destructor installs another spoof, the
destructor replaces the original suitcase object with another object,
the destructor destroys the original suitcase object, whatever).

Simple cases:

  The suitcase is locked.
  The spoof is installed.
  The spoof does not intercept the lock/unlock methods on the
suitcase.
  The spoof's destructor does nothing but remove the spoof.  No clean
    ups.
  The user unlocks the suitcase.
  The spoof is removed.
  The spoof is still unlocked.

  The suitcase is locked.
  The spoof is installed.
    The spoof does not intercept the lock/unlock methods on the
suitcase.
  The spoof's constructor logs the lock/unlock state of the suitcase
as
    it install, and the destructor restores that state.
  The user unlocks the suitcase.
  The spoof is removed.
  The suitcae is locked.

etc.
--<cut>--

and:

--<cut>--
A spoof is just a normal MUD object.  The only specific difference
between a spoof and other objects is that a spoof is used to *replace*
a normal MUD object, relegating the original object to a backup copy. 
This leaves the rest of the system in ignorance -- it has no reason to
think that anything happened.  Thus message calls from other objects,
intended for the spoofed object, now arrive at the spoof, and the
spoof does with them whatever it wants (including passing the message
straight back to the hidden original object).

Diagrammed:

  ObjectA exists.
  ObjectA is spoofed.
    ObjectA is cloned to ObjectB.
    The contents of ObjectA are replaced with the spoof.
  ObjectX now calls ObjectA.get()
    Note that ObjectA is now the spoof.
    ObjectA does not have a method called "get()" defined.
    The general method on the spoof matches get(), and calls
      ObjectB.get()
      ObjectB is of course the original object, just with a
        different objectID now.
      ObjectB.get() is called and does whatever.

Or, to show the spoofing in action:

  ObjectA exists.
  ObjectA is spoofed.
    ObjectA is cloned to ObjectB.
    The contents of ObjectA are replaced with the spoof.
  ObjectX now calls ObjectA.get()
    Note that ObjectA is now the spoof.
    ObjectA __does__ have a method called "get()" defined.
    ObjectA.get() is called instead of any get() method on the
      original ObjectB.
      ObjectB is of course the original object, just with a
        different objectID now.
    ObjectA.get() does whatever.

--<cut>--

and

--<cut>--

I had another look at this specific area over the weekend and came up
with the following:

There are three important processes here:

  1) Determining that an object (no  longer) needs to be monitored.
  2) Determining that a monitored object has changed and thus
triggering 
       the monitor.
  3) Determining what changed.

Harking back to the example of the Great God GooGoo and his holy
relics, #1 is pretty easy in that instance.  GGGG knows ahead of time
what objects he needs to monitor, and this can be hardcoded at the
time GGGG is programmed.

In the case of the Crystalline Tree, #1 is not so simple.  Any object
that is located within the CT needs to be monitored, however those
objects could arrive in the CT as the result of a normal entry (moving
from room to room in the normal and predicted manner), or as the
result of a teleport (*Bink!*  Its there!).  Similarly objects could
be removed from the CT in the same manner, requiring that monitoring
stop.

This is fairly elegantly addressable by altering the base container
methods on the CT to install and remove the monitor on the contained
or removed object.  Simple, elegant, guaranteed.  When ever an object
is added to the contents list it gets a state change monitor installed
on it, and when ever it is removed the monitor is removed.

Other watcher relationships might be less elegant.  Instead of a
simple question of containment (eg the CT watched everything within
itself), it could be some more indirect relationship (eg the
DemonWroth watches all character objects (players) that have ever been
in the same room as the PrincessBride, until those objects have been
in the same room as the GGGG).  The the question is coding this with
all the normal rules intact.

One solution is to have the DW spoof the containment methods on the PB
so that every time the PB enters a room a spoof is set on the room
(and removed from the room she left).  The spoof overlays the
containment methods on the room so that it in turn installs monitors
on any objects that enter the room, _and_ spoofs the object to overlay
_its_ containment methods to check for the presence of the GGGG when
ever it enters a room (and remove the monitor and the spoof if it
does).

Diagrammed:

  DemonWroth installs spoof on PrincessBride
    Spoof(PrincessBride) adds features to
PrincessBride.I_am_contained_by
      and PrincessBride.I_am_leaving_container.
      Spoof(PrincessBride).I_am_contained_by spoofs the room the
        PrincessBride is entering.
      Spoof(PrincessBride).I_am_leaving_container removes the same
spoof
        from the old location.
        Spoof(room) adds features to the room.contain_objects.
          Spoof(Room).contain_objects installs monitors on any objects
            which enter the room (call it "thing").
            Monitor(thing) reports back to the DemonWroth.
          Spoof(Room).contain_objects spoofs the object (thing) that
just
            entered the room, adding features to
thing.I_am_contained_by.
            Spoof(thing).I_am_contained_by checks every room the thing
              enters to see if the GGG is there, and if so removes
itself
              (the spoof), and the monitor that reports back to the
              DemonWroth

--<cut>--

>>   The old Great God GooGoo and his holy relics scenario.  (I'll repost
>> on request).

--<cut>--

I'll see if I can't remember the scenario that originally took me on
this route.  Do note that this is a fairly complex set that I used as
a proof-case for a whole chunk of ideas, with spoofing etc just one of
the results.

  The GreatGodGooGoo has a number of holy relics.
  Each relic has a non-magical base state, and a magical awakened
    state.
  If more than three of GGGG's relics are simultaneously awakened,
    the GGGG does nasty things.
  Relic #1 is a stone which awakens into the Gem of GGGG.
  The gem turns its bearer into a FireGod only for so long as he
    carries the gem.
  The FireGod is very hot.  Food cooks when in his presence, metals
    get painfully hot, and candles melt.
  Other players near the FireGod are slowly damaged by the heat.
  Relic #2 is the Horn of the GGGG, which awakens only while it is
    blown and then reverts to the base state.
  Relic #3 is a sword which awakens to the sword of GGGG.
  The sword has interesting properties when awakened:
    1) If the GGGG is awake (three relics etc), the the sword
       teleports to GGGG.
    2) If it can detect the MistWraithe it magically teleports to the
       MW and begins attacking him.
    3) If it is carried by a FireGod, then the sword is destroyed.
    #1 happens as soon as the GGGG is awake, and the sword can
    detect him and it can teleport.
    #2 happens as soon as it can detect the MW and can teleport.
    #1 has priority over #2, and #2 has priority over #3.
  There is a Magic Sack of Hiding.
  Anything in the MSH is hidden from the rest of the game.  No
    magical effects can either enter of leave the MSH.  Players, and
    the MW can enter the MSH.
  There is a Wizards Lair.
  Any magical effects, such as an awakening, occuring in the WL are
    hidden from the rest of the game.  No magical effects or spells
    can enter the WL, but they can all leave the WL.  ie in fails,
    out works.
  There is a Magic Cloak.
  Anyone wearing the MC appears to be the MW (more than the real
    MW (ie gets preferentially attacked)).
  Anyone carrying the MC appears to be the GGGG (non-preferentially).

Base rule: Any effect on any object must be programmable without
source access to any other objects.

Challenge: Program the above, following the base rule.

Now code the old sceptre/Castle Krak/Elven forest scenario, with the
WL as part of Castle Krak, following the above rule.

--<cut>--

>It's pretty obvious there's months and months of serious debate on
>this stuff that I've missed. I have the vague idea this list is
>archived .. how can I get at the archives?

You request them from me at coder at ibm.net.  I'll MIME attach
everything back to Feb '97 in reply.

Note: The affect/Affect/spoof/watcher threads occurred on the old CC
list and partially on Wout's version of MUD-Dev back in the middle of
last year.  I don't have releasable archives of that traffic yet.

--
J C Lawrence                           Internet: claw at null.net
(Contractor)                           Internet: coder at ibm.net
---------------(*)               Internet: clawrenc at cup.hp.com
...Honorary Member Clan McFUD -- Teamer's Avenging Monolith...




More information about the MUD-Dev mailing list