The GhulScript Specification


-Conversation

A conversation is a set of other conversations and a set of keyword-response
pairs.

        <conversation> := CONV tag { IMPORT { <conversations> } 
                                     NATIVE { <qr_pairs> }}
        <conversation> := tag <conversations> |
        <qr_pairs> := <qr_pair> <qr_pairs> |
        <qr_pair> := <query> tag

The lookup rules determine how to match a keyword to a response. The order of
searching qr_pairs for a keyword match is as follows:

        1. The NATIVE section of a conversation, in the order specified
        2. Each conversation in the IMPORT section, in the order specified
        3. The DEFAULT keyword

Note that rule 2 is recursive (and if I haven't said it yet circular inclusion
of conversations is disallowed). The DEFAULT keyword is a special keyword used
when a search fails. Other special keywords include HAIL, used when starting a
conversation, and BYE, used when ending a conversation.


--Special Keywords

 Normally a keyword is something the player types during a conversation, but
 the following keywords are special in that the engine generates them
 automatically in certain situations.

        DEFAULT If the player keyword does not match anything in the
                conversation, then the engine looks up the response for the
                DEFAULT keyword instead. This gives NPC characters the chance
                to customize their "I don't know" response.

        HAIL    When the conversation first starts up, before the player has a
                chance to give a query, the engine will check if the
                conversation has a response for the HAIL keyword and will
                invoke it if so. This gives NPC characters a chance to have the
                first word.

        BYE     When the conversation terminates, after the player has issued
                his last query and received a response, the engine will check
                if the conversation has a response to the BYE keyword and will
                invoke it if so. This gives NPC characters a chance to say
                "Farewell" or have the last word.

The player may type these keywords and they will be looked up in the usual way.


--Responses

In the grammar above a qr_pair is a <query> (which is a simple string) and a
tag. The tag refers to a response. Each response is defined separately in its
own construct. Because responses are independent you may mix them with
different keywords in different conversations as you like. The syntax for a
response declaration is like this:

        <tagged_response> := RESP tag <response-list>

        <response-list> := { <response> <response-or-nil> }

        <response> := SAY string              |
                      TRADE <trade-list>      |
                      TURN_AWAY               |
                      SET_FLAG flag           |
                      CLEAR_FLAG flag         |
                      TAKE GOLD               |
                      CHANGE_PARM <parm-id> <delta> |
                      TAKE_ITEM tag           |
                      GIVE_ITEM tag           |
                      JOIN                    |
                      ATTACK                  |
                      GET_AMOUNT              |
                      <check> <response-list> <response-list>

                      /* Proposed constructs: */
                      MENU <menu> |
                      BREAK

        <delta> := +int | -int
        <trade-list> := { <trade-entry> <trade-entry-or-nil> }
        <trade-entry> := tag <price>
        <price> := int

        <check> := GET_YES_NO |
                   CHECK_PARM <parm-id> operator int |
                   CHECK_FLAG flag int bool |
                   CHECK_ITEM tag |
                   CHECK_MEMBER tag

        <parm-id> := int

        <menu> := { <menu-item> <menu-item-or-nil> }
        <menu-item> := <name> <cost> <response-list>
        <name> := string
        <cost> := int

        <menu-item-or-nil> := <menu-item> |
        
        <response-or-nil> := <response> |


        SAY     The SAY command prints a line on the console. It prints the
                literal string that follows it with no formatting.

        TRADE   The TRADE command invokes the trading subroutine of the game
                engine, providing it a list of items and prices to trade. The
                engine automatically adds buy/sell and quantity prompts and
                handles the exchange of money and goods.

                When the player opts to Buy he sees the list of goods and
                prices. He can buy as many as he cares to and can afford (in
                other words the merchant has an endless supply of the goods
                listed).

                When the player opts to Sell then the engine scans player
                inventory and lists any of the items from inventory which
                appear on the trade list. The sell price is fixed at 1/4 of the
                buying price. This constant is currently hard-coded in the
                engine and applies to all trade situations.

        TURN_AWAY
                The TURN_AWAY command causes the NPC to terminate the
                conversation without player consent. The player will see a
                console message like "Hawknoz turns away." and then
                conversation mode will be over.

        SET_FLAG
                The SET_FLAG command sets the value of the specified flag,
                causing a subsequent CHECK_FLAG to evaluate to true. The
                opposite of SET_FLAG is CLEAR_FLAG.

        CLEAR_FLAG
                The opposite of SET_FLAG.

        TAKE_GOLD
                Decrement the player's gold counter by the indicated
                amount. The engine will not decrement beyond zero. (Note that
                there is no corresponding GIVE_GOLD simply because I haven't
                needed it).

        CHANGE_PARM
                Change the indicated parameter by the indicated amount. If the
                amount is negative the engine will not decrement beyond
                zero. (Note the absence of a SET_PARM - again because I haven't
                needed it yet).

        TAKE_ITEM
                Decrement one count of the indicated item type from player
                inventory. If the player has none in inventory then this has no
                effect. If the tag does not resolve to an item type then this
                has no effect.

                Note: currently this does not add the item to the NPC's
                inventory, as one might expect.

        GIVE_ITEM
                The opposite of TAKE_ITEM. This does not actually remove
                anything from the NPC's inventory.

        GET_YES_NO
                Prompt the player to answer yes or no. The player must respond
                with one or the other (ie he cannot escape from the
                prompt). "Yes" means true and "no" means false.

         CHECK_PARM
                Check if the given expression is true for the indicated
                parameter.

         CHECK_FLAG
                Test if a flag is set.

         CHECK_ITEM
                Check if the player has at least one count of the indicated
                item in inventory.

         CHECK_MEMBER
                Check if the indicated character has joined the player party.

         
         /* Proposed constructs */
         
         MENU   Use the status window to display a list of choices to the
                player. When the player selects an entry from the list the
                corresponding set of responses is executed. Upon completion -
                unless one of the responses was a BREAK or EXIT - the menu will
                "loop", displaying the list of choices again. MENUs may be
                nested.

         BREAK  Break out of a MENU loop. Execution resumes after the MENU
                block. If the MENUs are nested, only the lowest level MENU is
                aborted.

---Flags

The SET_FLAG, CLEAR_FLAG and CHECK_FLAG responses all operate on flags. A flag
is an unsigned 8-bit integer id for a boolean value. 

Flags may be global or per-conversation. Global flags may be accessed from any
conversation in the game. Per-conversation flags apply only to the conversation
in which they appear. Different conversations may use the same per-conversation
flag id but each will have its own value for the flag. Per-conversation flag
ids have the high bit clear (giving them the range 0 to 127) and global flag
ids have the high bit set (giving them the range 128 to 255).

Note: Initially all flags are clear. When savegames are implemented flag values
will need to be saved/restored, so in the future there will need to be syntax
for specifying initial flag values.


---Parameters

The CHANGE_PARM and CHECK_PARM responses operate on parameters. A parameter is
a signed 32-bit id for an integer value.

The engine reserves all negative parameter ids. The following parameter ids are
currently supported by the engine:

        -1 AMOUNT
                The AMOUNT parameter refers to the value obtained by the last
                GET_AMOUNT response. A value of -1 means the player did not
                enter an amount at the last prompt (i.e. escaped out of the
                prompt).

        -2 GOLD
                This refers to the player's gold counter.

        -3 FOOD
                This refers to the player's food counter.

        -4 ACTIVITY
                This refers to the speaking NPC party's current activity
                code. Activity codes are used by schedules. This parameter
                allows conversation scripts to change their response based on
                what the npc party is currently doing. For example, in u5 the
                NPC merchants would not trade with the player when they were
                not in their shop.

All parameter id's of 0 or greater are available for use by the game script.

All parameters are global in scope.

Note: Initially all non-reserved parameters are zero. When savegames are
implemented parameter values will need to be saved/restored, so in the future
there will need to be syntax for specifying initial parameter values.


---Branching

The GET_YES_NO, CHECK_PARM, CHECK_FLAG, CHECK_ITEM and CHECK_MEMBER responses
are all branching responses. They each specify a test and two sets of
responses. If the test evaluates to true then the first set executes, otherwise
the second set executes.


---Examples

These are not final, but are experimental examples for my own benefit while
designing changes to the language.

Example: a healer. This example illustrates how one might use some as-yet
unimplemented constructs like MENU, GET_PARTY_MEMBER, CHANGE_HP and an
unsupported PARTY_MEMBER parameter to script a healer. Note the heavy
duplication of code for each menu response, and also the dubious syntax for
checking if the player escaped out of the GET_PARTY_MEMBER prompt.

RESP r_healer {
    SAY "Hail, traveler! Do you require my healing services?"
    GET_YES_NO {
        MENU {
            "Heal        30g"  {
                CHECK_PARM GOLD >= 30 {
                    SAY "Who requires healing?"
                    GET_PARTY_MEMBER
                    CHECK_PARM PARTY_MEMBER = 0 {
                        SAY "Never mind then."
                    }{                    
                        CHANGE_HP PARTY_MEMBER +10
                    }
                }{
                    SAY "You lack the gold, my friend."
                }
            }
            "Cure       50g" {
                SAY "Whom shall I cure?"
                GET_PARTY_MEMBER
                CHECK_PARM PARTY_MEMBER = 0 {
                    SAY "Never mind then."
                }{
                    SET_POISONED PARTY_MEMBER false
                }
            }
            "Resurrect 500g" {
                SAY "Whom shall I call back from the dead?"
                GET_PARTY_MEMBER
                CHECK_PARM PARTY_MEMBER = 0 {
                    SAY "Never mind then."
                }{
                    SET_DEAD PARTY_MEMBER false
                    CHANGE_HP PARTY_MEMBER +10
                }
            }
            "Done" { 
                SAY "Very well." 
                BREAK /* exit the MENU loop */
            }
        }
    }{
        SAY "Very well."    
    }
}


Example: A weapon merchant. The example illustrates the use of a
yet-unimplemented technique whereby one response "calls" another by referring
to its tag. The r_weaponsmith_hail response invokes the r_sell_to_player
response in order to avoid some of the duplication seen above in the healer
example. "Arguments" to the "called" response take the form of parameters set
by the caller before invoking it.

Note that I left the SELL clause blank because I was at a loss on how to
proceed. There is no proposed syntax for building a MENU list on the fly at
runtime, and that is essentially what is required for the SELL clause as
written. An alternative is not to use a MENU for sell, but to use hand-crafted
checks for items in player inventory followed by offers to buy them one type at
a time off the player.

Also note the use of multiplication in the r_sell_to_player response. This is
currently not implemented.


RESP r_sell_to_player {
    SAY "How many would you like?"
    GET_AMOUNT
    CHECK_PARM AMOUNT > 0 {
        SET_PARM COST (AMOUNT * COST)
        CHECK_PARM GOLD >= COST) {
            SAY "Here you go.";
            TAKE GOLD COST;
            GIVE t_sword AMOUNT;
        }{
            SAY "You don't have enough gold!";
        }
    }{
        SAY "Changed your mind, eh?";
    }     
}

RESP r_weaponsmith_hail {
    SAY "Hail, traveler! Do you require my healing services?"
    GET_YES_NO {
        MENU {
            "Buy"  {
                MENU {
                    "Swords 100g" {


                        /*
                         * "Call" the other response after setting up the
                         * "arguments" to it.
                         */
                        SET_PARM ITEM $t_sword; 
                        SET_PARM COST 100;
                        $r_sell_to_player;

                    }
                    /*** add other items here ***/
                    "Done" { 
                        BREAK;
                    }
                }
            }
            "Sell" { 
            
            }
            "Done" { 
                SAY "Very well.";
                BREAK; /* exit the MENU loop */
            }
        }
    }{
        SAY "Hrumph.";
        EXIT;
    }
}

----------------------------------------------------------------------------
Spell Effects

An effect is a change to an object, map or place.

A recurring effect is attached to a target and takes effect every turn. Wether
or not an effect is recurring is determined by its duration. A duration of < 0
means it recurs indefinitely (until some other effect removes it). A duration
of > 0 indicates the number of turns for which the effect will recur before
expiring. A duration of 0 means the effect is not recurring. Henceforth, I
DON'T DISTINGUISH BETWEEN RECURRING EFFECTS AND EFFECTS IN THIS DISCUSSION.

A spell attaches a set of effects to a set of targets. A conversation response
may do the same. So may using an item, stepping on a terrain, or engaging a
mech. When it comes to effects, the difference between all these things lies in
how the set of targets is specified. Specifying the set of effects probably
won't change much between them. But this discussion is about spells. I only
mention those other things as a reminder.

An effect is a change, so what can be changed? It depends on the target. I've
listed below some of the most reasonable things I can think of.

        Character Hit Points
        Character Mana Points
        Character Strength
        Character Dexterity
        Character Intelligence
        Character Experience
        Character Level
        Object Glow
        Character or Party Turns
        Object Location
        Character or Party Alignment
        Character, Vehicle, Terrain or Mech Passability
        Character or Party Can Take a Turn
        Character is Dead  
        Hidden Objects are Revealed
        Time is Stopped
        Magic is Negated
        Wind Direction
        Object's Existence
        Terrain Type
        Object's Edibility
        Ambient Glow
        Mechanism Jam
        Mechanism Signals
        Existing Effects (an effect that affects another effect)
        Object User Bits*
        Object User Fields*

Those last two (marked with asterisks) probably don't make a lot of sense to
you now. Why are they there? Well, I can't think of everything a map hacker is
going to want to design into a game. And some things I don't think require
engine support. For example, u4/u5 had a virtue system. Currently nazghul does
not. But by taking advantage of the user bits or user fields a map hacker can
add one. These bits and fields have no semantic value to the engine, but they
can be interpreted by the ghulscript. They can be modified by effects, and they
can be checked by conversation scripts (and perhaps eventually mech
scripts). Remind me to provide an example somewhere if you're interested in,
that's all I'm going to say for now.

An effect specification is a piece of ghulscript which declares an effect. Once
declared, an effect can be referred to elsewhere in the script (such as terrain
or spell type declarations). I'll show the syntax further down (after I figure
out what it is ;-)).

Anyway, the first part of an effect specification is WHAT it affects. I refer
to this as the SUBJECT OF THE EFFECT.

The next part specifies IN WHAT WAY it effects it. This part of the spec
depends upon what type of attribute is affected. Here's a table:

============================================================================
Target Type | Subject      | Subject Type    | Notes
============================================================================
Character   | Alignment    | SET             |
Character   | Dead         | BOOL            | Necessary? Or HP==0 enough?
Character   | Dexterity    | INT             |
Character   | Experience   | INT             |
Character   | Field[0-9]   | INT             | Generic attributes
Character   | Flags        | SET             | Generic flags
Character   | Hit Points   | INT             |
Character   | Immunity     | SET             | Effect immunities
Character   | Intelligence | INT             |
Character   | Level        | INT             |
Character   | Lose Turn    | BOOL            |
Character   | Mana Points  | INT             |
Character   | Occupation   | TAG             | Change jobs
Character   | Passability  | SET             |
Character   | Protections  | SET (?)         |
Character   | Species      | TAG             | Were-shift somebody
Character   | Speed        | INT             |
Character   | Strength     | INT             |
Effect      | Existence    | BOOL            | Remove another effect
Mech        | Passability  | SET             | Applies to current state
Mech        | Jam          | BOOL            |
Mech        | Signal       | INT             |
Object      | Glow         | INT             |
Object      | Location     | (TAG, INT, INT) | Meaning (place, x, y)
Object      | LocationDelt | (TAG, INT, INT) | 
Object      | Visible      | BOOL            | Applies to particular object
Object      | Existence    | BOOL            | Used to destroy an object
Object      | Type         | TAG             | Morph an object
Object      | Edibility    | BOOL            | Turn something into food
Party       | Lose Turn    | BOOL            |
Party       | Speed        | INT             |
Party       | Alignment    | SET             |
Party       | Reveal       | BOOL            | Can see invisible objects
Party       | TimeStop     | BOOL            | Only this party can take turns
Party       | MagicNegated | BOOL            | Spellcasting disabled
Place       | Wind Directi | DIR             | Wind direction
Place       | Ambient Glow | INT             |
Terrain     | Passability  | SET             |
Tile        | Terrain Type | TAG             |
Vehicle     | Passability  | SET             |

The engine can infer the subject type from the target type and subject, so
there's no need to specify it in the script. But map hackers will need this
info as they design effects. If a spec gives a value which is the wrong type
then the engine needs to complain about it at load time.

The value of an effect is the amount or direction by which the subject changes,
or the value to which the subject is set. For scaler and vector subject types
the value of the effect might be a signed offset. This would be added to the
existing value. The possible operations for each subject meta-type are listed
below:

============================================================================
Subject Metatype | Operations
============================================================================
scaler           | assign, add, subtract, multiply
vector           | assign, add, subtract, multiply
tag              | assign, instantiate, destroy(?)
bool             | assign, invert
set              | assign, intersect, union, invert
============================================================================

Multiple operations can be combined.

An effect is a change which is achieved by some means, and the means indicates
wether or not the effect will apply to a particular target. Some targets may be
immune to a particular means so that the effect will not apply. Or some targets
may have modifiers which apply to a particular means which reduce the value of
the effect.

At this point I can speculate about what an effect specification is going to
look like:

EFFECT MagicFireball {
   target_type Character;
   subject     HitPoints;
   value       -10
   method      (MAGIC|BURN)
}

// Reduce the value of burn effects by 10
EFFECT Protection {
   target_type Character;
   subject     Protections;
   value       +10
   method      (BURN)   
}

// Make a character immune to burning effects
EFFECT BurnImmunity {
   target_type Character;
   subject     Immunity;
   value       +BURN
   method      (MAGIC)
}

// Here's an effect which might be produced by a sword:
EFFECT EdgedWeaponDamage {
   target_type Character;
   subject     HitPoints;
   value       -5
   method      (EDGED_WEAPON)
}

// And here's one which might be produced by a shield:
EFFECT EdgedWeaponDamageProtection {
   target_type Character;
   subject     Protections;
   value       +5
   method      (EDGED_WEAPON)
}

But what if the character is immune to heat damage? To accomodate immunities in
the game the effect must specify HOW the effect is achieved. Now the engine has
no interest in the semantics here, so we can use an open set and let map
hackers decide what they want the elements of a set to "mean".

Not to get too far off track, but you can see that I'm thinking about the way
sets should be represented in ghulscript. Currently we use a bitmask. This may
be a bit too esoteric for normal people. So instead I'm considering a new
ghulscript construct to pre-declare set elements similar to an
enumeration. I see no reason to discard the C bitwise operators as a means of
constructing sets.

EFFECT Burn {
   // same as before
   method     (BURN) // where BURN is a script-defined set element
}

So how does the engine know not to burn a character if that character is immune
to burn effects? It does this by checking the character's immunity set. If any
element in the effect method set is not in the character immunity set, then the
effect is applied. For those who like to think about such things, consider an
effect which confers immunity to effects which confer immunity... :-)

How is the effect achieved?

        Poison
        Fire
        Acid
        Sleep
        Paralysis
        Petrify
        Frighten
        Encourage
        Extensible Bits

How is the effect quantified?

        Integer
        Integer Offset
        Boolean
        Direction
        Location
        Location Offset
        Object Type
        Terrain Type
        Terrain Map

How is the target specified?

        Object
        Object Type
        Tile
        Tile Rectangle
        Tile Circle
        Alignment        
        Species
        Occupation
        Circle Arc
        Passability

What special effects apply?

        Change the character's sprite to prone (sleeping, unconscious or dead)
        Shake the screen
        Animated missile
        Animated shockwave (arc-specified)
        Chain lightning
        Meteor strike
        Sounds
        Screen flash
        Alpha transparency

Examples:

        An Nox (Cure Poison)
        What: Existing Character Effect
        How: Poison
        Value: False
        Target Method: Select Party Member
        
        In Nox Por (Poison)
        What:   Character Hit Points
        How:    Poison
        Value:  -10
        Dur:    Indefinite
        Target: Tile
        Cumul:  No
        Multi:  No        
        
        Because it affects specifically Character hit points, the engine knows
        to search the targeted tile for a Character object. Because it does not
        support multiple targets only the first Character found will be
        affected. Because the effect is not cumulative the engine will search
        the list of existing effects for the Character to make sure a Poison
        effect is not already at work. If not then it checks the Character's
        immunity mask and if the Poison bit is set then the effect is
        ignored. Otherwise it inserts the new effect in the list of effects for
        the character and applies -10 to the Character hit points. Since the
        duration is indefinite the engine does not decrement the duration
        counter.

Effects which create objects are a bit different then what is discussed
above. Such an effect has no subject, as it is not changing an existing
object's attributes. Instead it is creating a new subject.

There are several ways to carry out object creation. First of all, we must
unfortunately distinguish between characters and other object types. The reason
is that most objects have a type, and this type specifies the default values
for an object. But characters do not have a single type. Instead they have a
species, an occupation, a schedule and a conversation. Perhaps they should have
a type which combines all these elements. The downside is you end up with a lot
of character types which will only have a single instance because the type info
is so specific to a single character, which is why I deviated from the rule a
bit. Anyway, besides that distinction we must also recognize that sometimes we
want to create a new object from scratch and sometimes we want to clone an
existing object.

EFFECT SlimeCloneWhenDamaged {
        target_type   Character;
        target_method Effected;
        subject       Existence;
        hook          HpReduced;
        source        ();
        value         new;
        probability   0.25;
}



Prototype header file:

        // What the effect applies to.
        enum effect_target_type {
                effect_target_type_none = 0,
        };

        // How to pick what the effect applies to.
        enum effect_target_method {
                effect_target_method_none = 0,
                effect_target_method_affected,
        };

        // What is being changed about the thing the effect applies to?
        enum effect_subject {
                effect_subject_none = 0,
                effect_subject_affected,
        };

        // List of subject value types.
        enum effect_value_type {
                effect_value_none = 0,
                effect_value_tag,
                effect_value_bool,
                effect_value_int,
                effect_value_float,
        };

        // How is the value of the subject going to be changed?
        struct effect_value {
                enum effect_value_type type;
                union value {
                        char *tag;
                        int _bool:1;
                        int _int;
                        float _float;
                };
        };

        // List of events which invoke an effect ("hooks" an effect is attached
        // to)
        enum effect_hook {
                effect_hook_none = 0,
                effect_hook_hp_reduced,
        };

        // List of ways to specify a target for an effect.
        enum effect_target_method {
                effect_target_none = 0,
                effect_target_effected,  // whatever the effect is attached to
        };

        // List of operators applied to the subject
        enum effect_operator {
                effect_operator_none     = 0,
                effect_operator_assign   = '=',
                effect_operator_new      = 'n',
                effect_operator_delete   = 'd',
                effect_operator_clone    = 'c', // the subject
                effect_operator_add      = '+', // w/ assignment to subject
                effect_operator_subtract = '-', // w/ assignment to subject
                effect_operator_multiply = '*', // w/ assignment to subject
                effect_operator_divide   = '/', // w/ assignment to subject
        };

        // The effect data structure.
        struct effect {
                char *tag;
                struct list list;
                enum effect_target_type type;
                enum effect_target_method target_method;
                enum effect_subject subject;
                enum effect_hook hook;
                enum effect_source source;
                enum effect_operator operator;
                struct effect_value value;
                float probability;
        };

        // Create and initialize an effect structure from ghulscript.
        extern struct effect *effect_load(class Loader *);

        // Apply an effect (attempt to, anyway).
        extern int effect_apply(struct effect *effect, class Object *affected);

        // Properly deallocate an effect structure.
        extern void effect_destroy(structeffect *effect);

