The Signal System — a Technical Summary
Introduction
When things happen to objects in the game world, signals are triggered. These signals are hooks onto which developers hang code; code that makes special effects happen, making the world more dynamic.
When a player takes a bite out of a donut, for example, there are certain things the system always does automatically – reduces the bulk of the donut, or if it's the last bite, destroys the donut object.
A developer can make a magical donut with fragments of scripting code in it, code that catches the various ‘eat' signals, so that something special happens when the player takes a bite. For example, perhaps each nibble you take out of the donut realistically increases the player's weight by a small amount 24 hours later? That would be quite simple to do using signals.
Signal Names
Currently all scripting code fragments are written Merry, and the code is stored in properties on the object. These properties have names, and the names are chosen according to a specific naming scheme — all Merry script property names start with the text merry:.
After this language identifier comes the type of signal. The current list of types is:
- act
- react
- witness
- show
The last part of the property name identies which actual signal it is. For example, in the case of the eaten donut, the signal is named eat. To catch a player eating the magic donut, then, you would place your code in the property e.g. merry:react:eat.
Objects And More On Signal Types
There are usually three groups of objets that may take an interest in any given physical event –
- The object performing the action – e.g. the player eating the donut. This is where the act type is used.
- The target of the action – e.g. the donut. Use the react type here.
- The other objects in the room, who observe the action taking place but do not take part in it. Use witness here.
Those are the most straight-forward groups, but there are others –
- When unlocking a door, the player
acts, the doorreact's', but what about the key? The key should also have signals run in it. I may want a key to break when used, or maybe set the player on fire, or… whatever. Thus there is need for more than one type ofreact; one for each class of objects in an action. - When I'm moving from one room to another, there are witnesses both in the room I'm leaving, and in the room I'm entering; there is also both an entrance detail and an exit detail that should be given the chance to react.
- If I shout very loudly, the shout may reach 20 rooms. Each one is full of witnesses.
- It may be the environment itself should also always be given a chance to react to whatever is being done in it.
For all these reasons, we extend the signal type name. Thus when unlocking a door, we have:
merry:act:unlock- in the actor
merry:react:unlock- in the lock
merry:react:unlock-using- in the key
merry:react:unlock-in- in the room
merry:witness:unlock- in everyone watching
When moving between rooms,
-
merry:act:enter -
merry:react:enter-into-
in the current room's exit detail
-
Note: merry:react-pre:enter-into is invalid. ~Death
-
-
merry:react:enter-from-
in the destination room's entrance detail
-
Note: merry:react-pre:enter-from is invalid. Keep in mind, -pre is not required here as it does fire before the character uses the passage. ~Death
-
-
merry:witness:enter-into- in everyone watching in current room
-
merry:witness:enter-from- in everyone watching in destination room
Signal Timing
If all this weren't complicated enough, there is yet another dimensions in which these signals spread out — time. Each signal is actually triggered three times, under different conditions, and each type is used for a different purpose. (There is also the -desc timing used primarily to change output of movement.)
First, the pre signal is triggered almost immediately when the action begins. It occurs before most error checking is done on the parameters. If I try to eat an inedible object, the action should abort with a "You can't eat that." error message, but not before the pre signal has been run. The developer may replace the standard error message, and thus respond in some interesting way to the player trying to eat e.g. a doorknob.
Signals return true or false. The pre signal's return value determines wether to abort the action or let it proceed. This signal is also the only one that is allowed to actually modify the signal's input values. For example, if I had a mute curse, I might have a script that intercepts socials with evokes and turns the evoke into "…".
The main signal itself – is run when the action is just about to irreversibly execute. All sanity checking has been done at this point and the action will execute unless the signal forbids it. Once again, the return value determines wether to abort the signal or not.
Note: Any signal that does choose to abort the action must provide a message to the user explaining why, or the action will fail silently.
The last signal, the post one, is used when the action has already succeeded. It is run in a separate thread, and its return value is ignored (it obviously cannot stop the action).
Kalle: Its return value IS NOT IGNORED! Its return value determines if other post scripts for the same signal may trigger. For example, if 3 gnomes in a room all witness the "entering" signal's post stage, and the first one returns false, the other two's witness scripts will never, ever execute! It can output extra text to the user, of course, but more commonly it is used to handle side-effects of the action, often involving delays. Eating a poisoned donut, the post version of the eat signal may be used to start a 6-hour delay, at the end of which the player vomits profusely and, perhaps, dies. NB Witness does not allow -post signals - Leanan Witness does allow -post signals, but not all signals support post. In fact, witnessing a login attempt MUST be done as -post, because the login signal only triggers the post action. -Kalle.
GEIST - July 31, 2011 - Through various experimentation I have not been able to get react-post:login or witness-post:login to work. I have, however, confirmed that act-post:login does work. If it didn't we'd have big problems, but…
Examples of valid signal names, then, include
merry:witness-re:move-in-
-desc
-desc timing needs to be documented)
Signals and Socials
Social verbs are special in that they normally only cause messages (and possibly prox movement and consent requests), but it is possible to hook actions into specific verbs as well. For example, the developer may write a react-script for ‘kiss' in a ring, and trigger some magical effect when a player kisses a ring.
A word of warning first: it is very easy to accidentally create ‘guess the verb' puzzles using this functionality – i.e. situations where the player is forced to type a specific verb to trigger a script, when from the player's perspective any of the verb's synonyms ought to have the same effect. Guess-the-verb puzzles are strongly discouraged. If you can ‘tug' a rope, you should be able to ‘pull' it with the same effect.
A system of semantic tags has been developed to address this problem, but it is not fully documented yet and likely has some missing or broken semantics in it. For now, you have to create properties for each verb synonym, and use inheritance to point to a single script that handles all the verbs in the same way.
The script names for socials are constructed from the imperative form of the social and, in the case of the react scripts, the role identifiers of the various social objects that are reacting. These role identifiers are not yet settled for all the social verbs. At the moment, they tend to be ‘dob' or ‘iob' most of the time, for direct object and indirect object.
If we wanted to make a sword that glimmers when it's waved, you'd create a script called merry:react:wave-dob (because when you wave a sword, the sword is the direct object – the dob). If you wanted to make a guard who reacts when you wave at him, you'd give him a merry:react:merry-iob (because when you wave -at- something, it's an indirect object – the iob).
So these dob and iob role names are identical to the ‘using' role in the unlock signal example above. Soon, socials should receive more sensible role names, more like ‘using' than the current unintuitive ‘iob'.
Signal Reference
Here is a table of existing signals, the roles they take, and the arguments they set:
| signal | pre | sig | post | arg | type | description | role | description |
|---|---|---|---|---|---|---|---|---|
| quit | X | |||||||
| disconnect | X | disconnect from the game | ||||||
| linkdie | X | |||||||
| login | X | default | obj | fall-back starting location | ||||
| open | X | X | X | what | NRef * | nrefs to attempt to open | * | valid doors to attempt to open |
| close | X | X | X | what | NRef * | nrefs to attempt to close | * | valid doors to attempt to close |
| unlock | X | X | X | what | NRef * | nrefs to attempt to unlock | * | valid doors to attempt to unlock |
| keys | obj * | keys available for use | using | keys available for use | ||||
| explicit | int | did the player specify keys to use? | in | the environment of the player | ||||
| lock | X | X | X | what | NRef * | nrefs to attempt to lock | * | valid doors to attempt to lock |
| keys | obj * | keys available for use | using | keys available for use | ||||
| explicit | int | did the player specify keys to use? | in | the environment of the player | ||||
| drop | X | X | X | what | NRef * | nrefs to attempt to drop | * | valid objects to attempt to drop |
| into | where the dropped objects go | |||||||
| take | X | X | X | what | NRef * | nrefs to attempt to take | * | valid objects to attempt to drop |
| from | where each object is taken from | |||||||
| place | X | X | X | what | NRef * | nrefs to attempt to place | * | valid objects to attempt to place |
| where | object | destination of objects | onto | destination for each object | ||||
| prep | int | intended preposition of objects | ||||||
| stance | int | intended stance of objects | ||||||
| v2 | string | second-person form of verb used | ||||||
| v3 | string | third-person form of verb used | ||||||
| drag | X | X | X | victim | object | who to drag | * | who to drag |
| eat | X | X | X | what | NRef | nref to attempt to eat | * | valid object to attempt to eat |
| drink | X | X | X | what | NRef | nref to attempt to drink | * | valid object to attempt to drink |
| wear | X | X | X | articles | obj * | objects to attempt to wear | * | valid objects to attempt to wear |
| wield | X | X | X | article | object | object to attempt to wield | * | valid object to attempt to wield |
| unwear | X | X | X | articles | obj * | objects to attempt to unwear | * | valid objects to attempt to unwear |
| unwield | X | X | X | article | object | object to attempt to unwield | * | valid object to attempt to unwield |
| allow | X | X | X | who | object | object being allowed | * | object being allowed |
| deny | X | X | X | who | object | object being denied | * | object being denied |
| demand-consent | X | X | X | target | object | object being asked | * | object being asked |
| message | string | what is displayed to target | ||||||
| action | string | action we want to perform | ||||||
| args | mapping | arguments of action we want to perform | ||||||
| enter | X | X | X | what | NRef | detail we're trying to enter | * | detail we're trying to enter |
| from | detail we emerge from | |||||||
| into | detail we enter into | |||||||
| teleport | X | X | X | dest | object | where to go | * | where to go |
| approach | X | X | X | target | NRef | which detail to approach | * | which detail to approach |
| leave | X | X | X | |||||
| offer | X | X | X | what | obj * | objects being offered | * | objects being offered |
| who | object | to whom they're offered | to | to whom they're offered | ||||
| accept | X | X | X | what | obj * | objects being accepted | * | objects being accepted |
| who | object | from whom they're accepted | from | from whom they're accepted | ||||
| revoke | X | X | who | object | object to whom revoked offer was made | * | object to whom revoked offer was made | |
| refuse | X | X | who | object | whose offer to revoke | * | whose offer to revoke | |
| stance | X | X | X | target | NRef | prox target of stance (e.g. altar) | * | prox target of stance |
| stance | int | stance to take, e.g. STANCE_KNEEL | ||||||
| prep | int | preposition to use, e.g. PREP_BEFORE |
Deprecated
There are two old signals that should not be used, but there may be some legacy code using them.
- auto
- solo
There is also an old language Bilbo, which also worked with the Signal System. It's prefix was bilbo: — some legacy objects may still have Bilbo code in it, but you should not re-use them.Old Emails
Old Emails
doors
> My milk container now emits it is closing once spawned... looks rather
> strange. I haven't used it in ages, but originally it didn't emit that
> message. Again, previously worked fine.
>
> <Lazarus:rooms:asylum:props:milk>
The start script does:
0010 if this.details:$detail:exit:door = 1 then
set this.details:$detail:exit:closed to 1
which closes the door… this was silent in the past but now has a message. I realize that's a backwards compatiblity breaker – sorry about that. You can fix it by changing the property to:
0010 if this.details:$detail:exit:door = 1 then
set this.details:$detail:exit:closed:silently to 1
^^^^^^^^
i.e. adding ‘silently' at the end.
– ParWinzell - 25 Feb 2003
all evoke signals
----- Original Message -----
From: "Par Winzell" <zell@skotos.net>
To: <skotos-seven@skotos.net>
Sent: Wednesday, February 19, 2003 1:26 PM
Subject: Re: [skotos-seven] Distant emits
Scott,
> I guess I'm suggesting a signal for merry:witness:emit.
> The only information it needs to catch is any text that
> is sent to the room. Of course, since I have not seen
> the underlying code, this might be impossible. But
> wouldn't this be generally useful in lots of different
> situations? Spying? Watching several room at once?
> Logging of events and occurrences for later use?
Firing an event for every single line of text is questionable, for
performance reasons if nothing else. However, one -is- fired for each
command that has been converted to Merry, and that's what the lighter
examplified, if you looked closer. It defines three scripts:
<Core:Property property="merry:witness-post:command">
<Core:Property property="merry:witness-post:evoke">
<Core:Property property="merry:witness-post:take">
The COMMAND event is triggered for every dynamic verb ('social') that is
executed. That's everything a player types except the actions that have
not yet been converted to Merry.
The EVOKE event is triggered whenever somebody does something that
evokes something. This means you can automatically spy on everything
that is actually said in a room -- every action that involves a
free-form text string. An evoke.
The TAKE is just there as an example of catching an action that has yet
to be converted to Merry. It's possible that I could rig things so that
the COMMAND is sent in those cases, too, if you think that'd be useful.
Zell