Turning the 2nd blue sword into an object.
Your verb is handed strings. dobj, iobj and
args are all text — whatever the player typed, split around the
preposition, nothing more. Turning "the 2nd blue sword" into the
actual object in the room is a separate step, and it is yours to make.
That step is a matcher. You give it the string and a list of candidates; it
gives you back one object, or None.
target = pmatch(dobj, pobj, list(pobj.location.contents) + list(pobj.contents))
if not target or not target.existent:
pobj.msg("Examine what?")
return
The candidate list is the important half and it is never chosen for you. A verb
that should only reach what you are carrying passes
pobj.contents; one that reaches inside a chest passes
chest.contents; the pair above is the ordinary "in the room or in
my hands" search. Matching cannot find what you did not offer it, which is the
whole of its access control.
There are two entry points, and choosing between them is a permissions decision rather than a stylistic one.
pmatch | bmatch | |
|---|---|---|
| For | player commands | staff verbs |
| Used by | the room parents #12 and #13, and #9, #11, #20 | #3, the character base |
me, here | yes | yes |
my sword | yes, filtered from your candidates | yes, straight from pobj.contents |
#4711 | no | yes |
$name | no | yes |
| Signature | pmatch(inp, pobj, candidates) | bmatch(inp, pobj, candidates, db) |
The difference that matters is the third row. bmatch resolves
#4711 by going to the database directly — it never consults your
candidate list, so the object need not be in the room, in your hands, or
anywhere near you. That is exactly right for @tel and exactly
wrong for get.
pmatch in anything a player can type
A player command written with bmatch hands every player a
working #N syntax against the whole database, whatever
candidate list you carefully assembled. The candidate list stops being a
boundary the moment # is legal.
The my handling differs too, in a way that is easy to miss.
bmatch("my key", pobj, []) searches pobj.contents and
ignores the candidates entirely. pmatch instead
filters the candidates down to those whose location is the player. If
you curated a list for a reason — only what is visible, only what is not worn —
pmatch respects it and bmatch does not.
Both matchers eventually reach the same parser, which takes the string apart in a fixed order:
| Step | Does what | Example |
|---|---|---|
| 1. Article | a leading the, a, an or some is dropped | the big red ball → big red ball |
| 2. Ordinal | if the first token left is an ordinal, it is consumed | 2nd blue sword → index 1 |
| 3. Noun | the last token | sword |
| 4. Adjectives | everything between | ["big", "red"] |
The candidates are then walked in the order you gave them, keeping every object
whose noun matches and whose adjectives match, and the ordinal-th survivor is
returned. Candidate order is your ordering — the list you passed — which is why
2 door means "the second door as the room lists them".
The noun is tested against three things, in this order: each non-article word
of obj.name, then every entry in obj.aliases, then
obj.noun. A hit on any of them is a match.
Matching is by prefix, case-insensitively, down to a single
character. Against "a silver sword", swo, sw,
sil and plain s all match.
There is no minimum length. s matches every object with a
word starting in "s" — the sword, the sack, the stranger — and
the first in your candidate list wins, silently. That is worth knowing
before you write a verb whose argument is often one character.
name_match is a verb on $match_utils now, not a
function in match_utils.py, and its docstring used to
claim a single character needed an exact whole-word match. It never
did — the guard it described could only ever reject the empty string —
and the docstring was corrected when the verb was ported. The
behaviour is unchanged; it is the description that was wrong.
The name is split on spaces and each word tried separately, so
silver reaches "a silver sword" without a or
sword being involved. Articles inside the name are skipped
while doing it, which is why a does not match every object
in the game.
Every adjective must appear in the order typed, and at a word
boundary. big blue ball matches "a big blue ball";
blue big ball does not.
Only the leading boundary is checked, so berry does not
reach "a blueberry tart" — but blue does. An adjective has to
start a word; it does not have to finish one.
If scanning the name fails, there is a second pass: an adjectives
property, a list of strings, is matched by prefix and also in order. That is how
you make an object answer to a word that does not appear in its name.
@adprop drape.adjectives = ["blue", "velvet", "heavy"]
@adprop, not @set: no object ships with an
adjectives property, and @set refuses a property that
does not already exist locally or by inheritance.
With that set, blue drape finds it even though the name says
"a drape". The two passes are independent: the name is tried whole first, and
only a complete failure falls through to the property.
Three spellings are accepted, and all of them are converted to a 0-based index:
| Form | Examples | Index |
|---|---|---|
| Words | first … twentieth | first → 0 |
| Suffixed | 1st, 2nd, 3rd, 21st | 2nd → 1 |
| Bare integer | 1, 2, 5 | 2 → 1 |
parse_ordinal claims any all-digit token, so an object whose
name is a number cannot be matched by typing that number —
get 7 means "get the seventh thing", not "get the 7". Give
such an object a word alias.
An ordinal with nothing after it — a bare 2nd — matches nothing
rather than defaulting to a noun, and an ordinal past the end of the matches
returns None rather than the last one.
Before any name matching happens, four special forms are tried. Which ones are honoured depends on the matcher, per the table above.
| Input | Resolves to | pmatch | bmatch |
|---|---|---|---|
me | the acting player | yes | yes |
here | pobj.location | yes | yes |
my <x> | your own possessions | yes | yes |
#4711 | that object number | no | yes |
$name | a property of #0 | no | yes |
$name reads the property name off object #0
and resolves it — so $trash_bin follows whatever
#0.trash_bin currently holds. It is the way to name a fixed object
without writing its number into a verb, and a shipped world defines a couple of
dozen of them: $item, $chair, $wearable,
$trainer and so on.
One object, or None. Matchers do not raise, and there is no
ambiguity error: two equally good candidates means the earlier one in your list
wins, silently. If you need to know that the input was ambiguous, ask
match_all and count.
None is not the only failure. An object mid-destruction is still in
the room and still matches its own name, so every unhidden player verb
must check existent after a match:
target = pmatch(dobj, pobj, candidates)
# An object that has stopped existing is not a match, however well its
# name fits: pmatch searches what is in the room, and something mid-
# destruction is still there to be found.
if not target or not target.existent:
pobj.msg("Examine what?")
return
Staff verbs on #3 deliberately skip the check — reaching a
half-destroyed object is sometimes the point of a staff command.
pmatch and bmatch are assembled out of smaller pieces,
all of which are available to verb code in their own right.
| Function | Does |
|---|---|
match(inp, candidates, ordinal=0) | the name/adjective/ordinal engine, with no keywords at all |
match_all(inp, candidates) | every match as a list; the ordinal is ignored |
omatch(inp, pobj, db) | only the keywords: me, here, #N, $name |
name_match(obj, token) | would this one token match this one object |
adj_match(adjectives, obj) | would these adjectives match this object |
parse_ordinal(word) | a 0-based index, or None if it is not an ordinal |
strip_articles(text) | text without a leading article |
smatch(target, query, minlen=0) | plain prefix comparison of two strings; no objects involved |
prep_match(word) | a word to its canonical preposition: onto → on |
split_on_prep(text) | text → (before, prep, after); before keeps the command word |
match_all is what you want for "get all the coins" and for detecting
ambiguity. prep_match is what a verb uses to check that the
preposition it got is the one it wanted — prep_match(prep) != 'from'
accepts from however the player abbreviated it.
match is not the regex match
In a verb namespace, match is the matcher function
above. If your verb has a custom parser and you want the regular
expression's match object, the name for it is regex_match.
Both names are set, and the builtins are injected afterwards, so
match ends up as the matcher whatever the parser did.
Everything a matcher can reach is decided by the list you pass, and nothing else — no visibility test, no reachability test, no ownership test. If a verb should not touch worn items, filter them out of the candidates; do not expect the matcher to.
pobj.contents + pobj.location.contents and
pobj.location.contents + pobj.contents answer differently
when both hold a sword. Neither is wrong; pick the one that suits the
verb, and be aware that ordinals count in that order.
noun are native attributes
They are read straight off the object, not through
add_property. Adding a property with either name shadows
nothing and does not change matching.