Magic Spells, Infocom Style

One of my projects is a pastiche of the Enchanter Trilogy, originally intended for the ZIL game jam and written in Inform 6. However, when my health began to decline, I had no time to work on the project. Now it is June and I’ve decided to change gears and rewrite the game using TADS 3.
One of the main things that makes the Enchanter trilogy what they are is the spell system.
When I originally created the first part of the game, I borrowed code from Graham Nelson’s ‘Ballences.’ However, in TADS, I basically have to re-invent the wheel.
(If I wanted, I could port the game to Hugo, but I’d honestly prefer to use a system that isn’t as… well… inactive.)
I understand that Bob Bates created a spell system similar to the Infocom one using TopicEntries. Can anyone inform me on how to implement a similar system? It’s been ages since I’ve done anything with T3 so my knowledge is a bit rusty.

I don’t know enough TADS 3 to help, but this might be useful: in the original Enchanter trilogy, every spell was implemented as an object for the purposes of READ, MEMORIZE, and CAST, originally “contained” in its scroll, and moved to the spell book when GNUSTO’d.

For single-word casting purposes, every spell was also its own individual verb, completely separate from the others and implemented with its own rules for choosing objects.

3 Likes

hey,
i’d gladly help out if you gave me some examples of spell usage you’re trying to implement. it’s been so long that i’ve played enchanter that i hardly remember anything of it.

edit:
i just read an article about the game, and should be able to have some code ready later in the day.

here’s a quick first draft for basic spells that have to be memorized. it might be crap, but i’m off to work, and thought i’d post it now instead of who knows when :slight_smile:

#charset "us-ascii"
#include <adv3.h>
#include <en_us.h>

versionInfo: GameID
    name = 'the lobby'
;

gameMain: GameMainDef
  initialPlayerChar = me
;


class Spell: Thing
  isQualifiedName = true
  dobjFor(Gnusto) {
    action {
      "Copied <<name>> to spell book. ";
      moveInto(spellbook);
    }
  }
;

modify Thing
  dobjFor(Rezrov)  {
    check {
      if(!rezrov.isDirectlyIn(spellbook))
        failCheck('You haven\'t memorized the rezrov spell. ');
    }
    action {
      "code to unlock things goes here";
      rezrov.moveInto(me);
    }
  }

  dobjFor(Frotz) {
    check {
      if(!frotz.isDirectlyIn(spellbook))
        failCheck('You haven\'t memorized the frotz spell. ');
    }
    action {
      "code to light things up goes here";
      frotz.moveInto(me);
    }
  }
;

Room 'lobby' 'lobby';

+ Thing 'box' 'box'
;

+ me: Actor;

++ rezrov: Spell 'rezrov' 'rezrov'
  "The rezrov spell. "
;

++ frotz: Spell 'frotz' 'frotz'
  "The frotz spell. "
;

++ spellbook: Container 'spell book' 'spell book'
  "Your trusty old spell book. "
;

DefineTAction(Gnusto)
;

VerbRule(Gnusto)
  'gnusto' singleDobj
  : GnustoAction
  verbPhrase = 'gnusto/gnustoing (what)'
;

DefineTAction(Rezrov)
;

VerbRule(Rezrov)
  'rezrov' singleDobj
  : RezrovAction
  verbPhrase = 'rezrov/rezroving (what)'
;

DefineTAction(Frotz)
;

VerbRule(Frotz)
  'frotz' singleDobj
  : FrotzAction
  verbPhrase = 'frotz/frotzing (what)'
;
1 Like

I haven’t played the Enchanters, so I don’t really know what you’re going for, but hopefully Collin’s got you covered…

Collin, it seems like you have the right idea. In fact, this seems like the right way to go as far as the spell objects themself are concerned.
This is what I require my spell system to do:

  • Gnusto (copy a spell from a scroll into a spell book. Cannot copy a spell unless it’s on a scroll, in which case the scroll vanishes and the spell is moved into said book.)
  • Frotz (cause an object to give off light. This works as expected; sets the object’s brightness level to 3.)
  • Rezrov (open even locked or enchanted objects. Works on locked objects of any kind.)

The code Collin supplied works, but as he said it is a first draft and isn’t complete.
This is what I’m having trouble getting to work.

  • There are 4 ways to cast a spell in the Enchanter series, >CAST FOOBAR, >CAST FOOBAR ON/AT THING, >FOOBAR and >FOOBAR thing.
    The ‘Cast’ verb works just fine as long as spells are located within scope. However, I cannot find a way to create a verb which uses a direct object as the verb word, or a direct object and an indirect object (in the foobar spell’s case, the foobar spell and whatever you’re casting it at.)
  • I am unsure of how to prevent spells from being listed in inventory, and I want to make sure player’s can’t take, drop, examine, Etc. a spell object.

hey,
just add this line to the spell class, to prevent spells being listed in the inventory:

 isListedInInventory = (nil)

i should have time to implement the different ways of casting spells later on.

oh, and what’s supposed to happen when you just type frotz, or cast frotz without specifying an object?

The way Infocom implemented this was:

  • The CAST verb looks at the word right after CAST, using a lookup table (rather than an object parser). If that word is GNUSTO, it redirects to the GNUSTO action, if it’s FROTZ, it redirects to the FROTZ action, etc.

  • The version without CAST was the one implemented in depth, separately for each verb: a GNUSTO action, a FROTZ action, etc.

  • The individual actions have their own validation details: for example, all spell objects have a flag set (called SPELLBIT), and GNUSTO only allows objects with that bit set, while FROTZ only allows objects without that bit set.

The way I handled it in Scroll Thief didn’t involve spell-objects at all: instead, GNUSTO is cast on the scroll, and the scrolls had the spells listed as synonyms. (So when you GNUSTO FROTZ, the object is the scroll bearing the FROTZ spell, which is the only noun that has the word “frotz” as a synonym.) I found this a lot easier, personally.

here’s the updated version that supports the different ways of casting spells. it also now checks if you’ve already memorized a spell.

#charset "us-ascii"
#include <adv3.h>
#include <en_us.h>

versionInfo: GameID
    name = 'the lobby'
;

gameMain: GameMainDef
  initialPlayerChar = me
;


class Spell: Thing
  isQualifiedName = true
  isListedInInventory = (nil)
  dobjFor(Gnusto) {
    check {
      if(isDirectlyIn(spellbook))
        failCheck('You\'ve already memorized that. ');
    }
    action {
      "Copied <<name>> to spell book. ";
      moveInto(spellbook);
    }
  }
;

modify Thing
  iobjFor(Cast) {
    check {
      if(!gDobj.isDirectlyIn(spellbook))
        failCheck('You haven\'t memorized the <<gDobj.name>> spell. ');
    }
    action {
      gDobj.moveInto(me);
      gDobj.actOn(self);
    }
  }
  dobjFor(Rezrov)  {
    check {
      if(!rezrov.isDirectlyIn(spellbook))
        failCheck('You haven\'t memorized the rezrov spell. ');
    }
    action {
      rezrov.actOn(self);
      rezrov.moveInto(me);
    }
  }

  dobjFor(Frotz) {
    check {
      if(!frotz.isDirectlyIn(spellbook))
        failCheck('You haven\'t memorized the frotz spell. ');
    }
    action {
      frotz.actOn(self);
      frotz.moveInto(me);
    }
  }
;

Room 'lobby' 'lobby';

+ Thing 'box' 'box'
;

+ me: Actor;

++ rezrov: Spell 'rezrov' 'rezrov'
  "The rezrov spell. "
  actOn(obj) {
    "rezroving <<obj.theName>>";
  }
;

++ frotz: Spell 'frotz' 'frotz'
  "The frotz spell. "
  actOn(obj) {
    "frotzing <<obj.theName>>";
  }
;

++ spellbook: Container 'spell book' 'spell book'
  "Your trusty, old spell book. "
;

DefineTAction(Gnusto)
;

VerbRule(Gnusto)
  'gnusto' singleDobj
  : GnustoAction
  verbPhrase = 'gnusto/gnustoing (what)'
;

DefineTAction(Rezrov)
;

VerbRule(Rezrov)
  'rezrov' singleDobj
  : RezrovAction
  verbPhrase = 'rezrov/rezroving (what)'
;

DefineTAction(Frotz)
;

VerbRule(Frotz)
  'frotz' singleDobj
  : FrotzAction
  verbPhrase = 'frotz/frotzing (what)'
;

DefineTIAction(Cast)
;

VerbRule(Cast)
  'cast' singleDobj ('on' | 'at' |) singleIobj
  : CastAction
  verbPhrase = 'cast/casting (what) (on what)'
;

so this is completely missing scrolls as it is, but everything else looks good (to me :slight_smile: ).

edit:
ah okay, so frotz by itself works, but cast frotz won’t. let’s see…

edit2:
should be fixed now.

here’s a quick way to sort of fake a scroll, inspired by the last reply from @Draconis (thanks!)

+ rezrov: Spell
  vocabWords = 'rezrov spell/scroll'
  name = 'rezrov <<isDirectlyIn(spellbook) ? 'spell' : 'scroll'>>'
  desc {
    if(isDirectlyIn(spellbook))
      "A rezrov spell. ";
    else
      "A scroll containing the rezrov spell ";
  }
  actOn(obj) {
    "rezroving <<obj.theName>>";
  }
;

also, if you change the moveInto(me) occurences into moveInto(nil), the spells disappear after using them.