/** * \file match.c * * \brief Matching of object names. * * These are the PennMUSH name-matching routines, fully re-entrant. * match_result(who,name,type,flags) - return match, AMBIGUOUS, or NOTHING * noisy_match_result(who,name,type,flags) - return match or NOTHING, * and notify player on failures * last_match_result(who,name,type,flags) - return match or NOTHING, * and return the last match found in ambiguous situations * * who = dbref of player to match for * name = string to match on * type = preferred type of match (TYPE_THING, etc.) or NOTYPE * flags = a set of bits indicating what kind of matching to do * * flags are defined in match.h, but here they are for reference: * MAT_CHECK_KEYS - check locks when matching * MAT_GLOBAL - match in master room * MAT_REMOTES - match things not nearby * MAT_NEAR - match things nearby * MAT_CONTROL - do a control check after matching * MAT_ME - match "me" * MAT_HERE - match "here" * MAT_ABSOLUTE - match "#dbref" * MAT_PLAYER - match a player's name * MAT_NEIGHBOR - match something in the same room * MAT_POSSESSION - match something I'm carrying * MAT_EXIT - match an exit * MAT_CARRIED_EXIT - match a carried exit (rare) * MAT_CONTAINER - match a container I'm in * MAT_REMOTE_CONTENTS - match the contents of a remote location * MAT_ENGLISH - match natural english 'my 2nd flower' * MAT_EVERYTHING - me,here,absolute,player,neighbor,possession,exit * MAT_NEARBY - everything near * MAT_OBJECTS - me,absolute,player,neigbor,possession * MAT_NEAR_THINGS - objects near * MAT_REMOTE - absolute,player,remote_contents,exit,remotes * MAT_LIMITED - absolute,player,neighbor */ #include "copyrite.h" #include "config.h" #include <ctype.h> #include <string.h> #include <stdlib.h> #include "conf.h" #include "mushdb.h" #include "externs.h" #include "case.h" #include "match.h" #include "parse.h" #include "flags.h" #include "dbdefs.h" #include "confmagic.h" static dbref match_result_internal (dbref who, const char *name, int type, long flags); static dbref simple_matches(dbref who, const char *name, long flags); static int parse_english(const char **name, long *flags); static dbref match_me(const dbref who, const char *name); static dbref match_here(const dbref who, const char *name); /** Convenience alias for parse_objid */ #define match_absolute(name) parse_objid(name) static dbref match_player(const dbref matcher, const char *match_name); static dbref match_pmatch(const dbref matcher, const char *match_name); static dbref choose_thing(const dbref match_who, const int preferred_type, long int flags, dbref thing1, dbref thing2); extern int check_alias(const char *command, const char *list); /* game.c */ /** A wrapper for returning a match, AMBIGUOUS, or NOTHING. * This function attempts to match a name for who, and * can return the matched dbref, AMBIGUOUS, or NOTHING. * \param who the looker. * \param name name to try to match. * \param type type of object to match. * \param flags match flags. * \return dbref of matched object, or AMBIGUOUS, or NOTHING. */ dbref match_result(const dbref who, const char *name, const int type, const long flags) { return match_result_internal(who, name, type, flags); } /** A noisy wrapper for returning a match or NOTHING. * This function attempts to match a name for who, and * can return the matched dbref or NOTHING (in ambiguous cases, * NOTHING is returned). If no match is made, the looker is notified * of the failure to match or ambiguity. * \param who the looker. * \param name name to try to match. * \param type type of object to match. * \param flags match flags. * \return dbref of matched object, or NOTHING. */ dbref noisy_match_result(const dbref who, const char *name, const int type, const long flags) { return match_result_internal(who, name, type, flags | MAT_NOISY); } /** A noisy wrapper for returning a match or NOTHING. * This function attempts to match a name for who, and * can return the matched dbref or NOTHING. In ambiguous cases, * the last matched thing is returned. * \param who the looker. * \param name name to try to match. * \param type type of object to match. * \param flags match flags. * \return dbref of matched object, or NOTHING. */ dbref last_match_result(const dbref who, const char *name, const int type, const long flags) { return match_result_internal(who, name, type, flags | MAT_LAST); } /** Wrapper for a noisy match with control checks. * This function performs a noisy_match_result() and then checks that * the looker controls the matched object before returning it. * If the control check fails, the looker is notified and NOTHING * is returned. * \param player the looker. * \param name name to try to match. * \return dbref of matched controlled object, or NOTHING. */ dbref match_controlled(dbref player, const char *name) { dbref match; match = noisy_match_result(player, name, NOTYPE, MAT_EVERYTHING); if (GoodObject(match) && !controls(player, match)) { notify(player, T("Permission denied.")); return NOTHING; } else { return match; } } /* The real work. Here's the spec: * str --> "me" * --> "here" * --> "#dbref" * --> "*player" * --> adj-phrase name * --> name * adj-phrase --> adj * --> adj count * --> count * adj --> "my", "me" (restrict match to inventory) * --> "here", "this", "this here" (restrict match to neighbor objects) * --> "toward" (restrict match to exits) * count --> 1st, 21st, etc. * --> 2nd, 22nd, etc. * --> 3rd, 23rd, etc. * --> 4th, 10th, etc. * name --> exit_alias * --> full_obj_name * --> partial_obj_name * * 1. Look for exact matches and return immediately: * a. "me" if requested * b. "here" if requested * c. #dbref, possibly with a control check * c. *player * 2. Parse for adj-phrases and restrict further matching and/or * remember the object count * 3. Look for matches (remote contents, neighbor, inventory, exits, * containers, carried exits) * a. If we don't have an object count, collect the number of exact * and partial matches and the best partial match. * b. If we do have an object count, collect the nth exact match * and the nth match (exact or partial). number of matches is always * 0 or 1. * 4. Make decisions * a. If we got a single exact match, return it * b. If we got multiple exact matches, complain * c. If we got no exact matches, but a single partial match, return it * d. If we got multiple partial matches, complain * e. If we got no matches, complain */ #define MATCH_NONE 0x0 /**< No matches were found */ #define MATCH_EXACT 0x1 /**< At least one exact match found */ #define MATCH_PARTIAL 0x2 /**< At least one partial match found, no exact */ /** Prototype for matching functions */ #define MATCH_FUNC_PROTO(fun_name) \ /* ARGSUSED */ /* try to keep lint happy */ \ static int fun_name(const dbref who, const char *name, const int type, \ const long flags, dbref first, \ dbref *match, int *exact_matches_to_go, int *matches_to_go) /** Common declaration for matching functions */ #define MATCH_FUNC(fun_name) \ static int fun_name(const dbref who, const char *name, const int type, \ const long flags, dbref first __attribute__ ((__unused__)), \ dbref *match, int *exact_matches_to_go, int *matches_to_go) /** Macro to execute matching and store some results */ #define RUN_MATCH_FUNC(fun,first) \ { \ result = fun(who, name, type, flags, first, &match, \ &exact_matches_to_go, &matches_to_go); \ if (result == MATCH_EXACT) { \ exact_match = match; \ /* If it's the nth exact match, we're done */ \ if (matchnum && !exact_matches_to_go) \ goto finished; \ /* If it's the n'th match, remember it */ \ if (matchnum && !matches_to_go) \ last_match = match; \ } else if (result == MATCH_PARTIAL) { \ if (!matchnum || !matches_to_go) \ last_match = match; \ } \ } MATCH_FUNC_PROTO(match_possession); MATCH_FUNC_PROTO(match_neighbor); MATCH_FUNC_PROTO(match_exit); MATCH_FUNC_PROTO(match_exit_internal); MATCH_FUNC_PROTO(match_container); MATCH_FUNC_PROTO(match_list); static dbref match_result_internal(dbref who, const char *name, int type, long flags) { dbref match = NOTHING, last_match = NOTHING, exact_match = NOTHING; int exact_matches_to_go, matches_to_go; int matchnum = 0; int result; /* The quick ones that can never be ambiguous */ match = simple_matches(who, name, flags); if (GoodObject(match)) return match; /* Check for adjective phrases */ matchnum = parse_english(&name, &flags); /* Perform matching. We've already had flags restricted by any * adjective phrases. If matchnum is set, collect the matchnum'th * exact match (and stop) and the matchnum'th match (exact or partial, * and store this in case we don't get enough exact matches). * If not, collect the number of exact and partial matches and the * last exact and partial matches. */ exact_matches_to_go = matches_to_go = matchnum; if (flags & MAT_POSSESSION) RUN_MATCH_FUNC(match_possession, NOTHING); if (flags & MAT_NEIGHBOR) RUN_MATCH_FUNC(match_neighbor, NOTHING); if (flags & MAT_REMOTE_CONTENTS) RUN_MATCH_FUNC(match_possession, NOTHING); if (flags & MAT_EXIT) RUN_MATCH_FUNC(match_exit, NOTHING); if (flags & MAT_CONTAINER) RUN_MATCH_FUNC(match_container, NOTHING); if (flags & MAT_CARRIED_EXIT) RUN_MATCH_FUNC(match_exit_internal, who); finished: /* Set up the default match_result behavior */ if (matchnum) { /* nth exact match? */ if (!exact_matches_to_go) match = exact_match; else if (GoodObject(last_match)) match = last_match; /* nth exact-or-partial match, or nothing? */ /* This shouldn't happen, but just in case we have a valid match, * and an invalid last_match in the matchnum case, fall through and * use the match. */ } else if (GoodObject(exact_match)) { /* How many exact matches? */ if (exact_matches_to_go == -1) match = exact_match; /* Good */ else if (flags & MAT_LAST) match = exact_match; /* Good enough */ else match = AMBIGUOUS; } else { if (!matches_to_go) match = NOTHING; /* No matches */ else if (matches_to_go == -1) match = last_match; /* Good */ else if (flags & MAT_LAST) match = last_match; /* Good enough */ else match = AMBIGUOUS; } /* Handle noisy_match_result */ if (flags & MAT_NOISY) { switch (match) { case NOTHING: notify(who, T("I can't see that here.")); return NOTHING; case AMBIGUOUS: notify(who, T("I don't know which one you mean!")); return NOTHING; default: return match; } } return match; } static dbref simple_matches(dbref who, const char *name, long flags) { dbref match = NOTHING; if (flags & MAT_ME) { match = match_me(who, name); if (GoodObject(match)) return match; } if (flags & MAT_HERE) { match = match_here(who, name); if (GoodObject(match)) return match; } if (!(flags & MAT_NEAR) || Long_Fingers(who)) { if (flags & MAT_ABSOLUTE) { match = match_absolute(name); if (GoodObject(match)) { if (flags & MAT_CONTROL) { /* Check for control */ if (controls(who, match) || nearby(who, match)) return match; } else { return match; } } } if (flags & MAT_PLAYER) { match = match_player(who, name); if (GoodObject(match)) return match; } if (flags & MAT_PMATCH) { match = match_pmatch(who, name); if (GoodObject(match)) return match; } } else { /* We're doing a nearby match and the player doesn't have * long_fingers, so it's a controlled absolute */ match = match_absolute(name); if (GoodObject(match) && (controls(who, match) || nearby(who, match))) return match; } return NOTHING; } /* * adj-phrase --> adj * --> adj count * --> count * adj --> "my", "me" (restrict match to inventory) * --> "here", "this", "this here" (restrict match to neighbor objects) * --> "toward" (restrict match to exits) * count --> 1st, 21st, etc. * --> 2nd, 22nd, etc. * --> 3rd, 23rd, etc. * --> 4th, 10th, etc. * * We return the count, we position the pointer at the end of the adj-phrase * (or at the beginning, if we fail), and we modify the flags if there * are restrictions */ static int parse_english(const char **name, long *flags) { int saveflags = *flags; const char *savename = *name; char *mname; char *e; int count = 0; /* Handle restriction adjectives first */ if (*flags & MAT_NEIGHBOR) { if (!strncasecmp(*name, "this here ", 10)) { *name += 10; *flags &= ~(MAT_POSSESSION | MAT_EXIT); } else if (!strncasecmp(*name, "here ", 5) || !strncasecmp(*name, "this ", 5)) { *name += 5; *flags &= ~(MAT_POSSESSION | MAT_EXIT | MAT_REMOTE_CONTENTS | MAT_CONTAINER); } } if ((*flags & MAT_POSSESSION) && (!strncasecmp(*name, "my ", 3) || !strncasecmp(*name, "me ", 3))) { *name += 3; *flags &= ~(MAT_NEIGHBOR | MAT_EXIT | MAT_CONTAINER | MAT_REMOTE_CONTENTS); } if ((*flags & MAT_EXIT) && (!strncasecmp(*name, "toward ", 7))) { *name += 7; *flags &= ~(MAT_NEIGHBOR | MAT_POSSESSION | MAT_CONTAINER | MAT_REMOTE_CONTENTS); } while (**name == ' ') (*name)++; /* If the name was just 'toward' (with no object name), reset * everything and press on. */ if (!**name) { *name = savename; *flags = saveflags; return 0; } /* Handle count adjectives */ if (!isdigit((unsigned char) **name)) { /* Quick exit */ return 0; } mname = strchr(*name, ' '); if (!mname) { /* Quick exit - count without a noun */ return 0; } /* Ok, let's see if we can get a count adjective */ savename = *name; *mname = '\0'; count = strtoul(*name, &e, 10); if (e && *e) { if (count < 1) { count = -1; } else if ((count > 10) && (count < 14)) { if (strcasecmp(e, "th")) count = -1; } else if ((count % 10) == 1) { if (strcasecmp(e, "st")) count = -1; } else if ((count % 10) == 2) { if (strcasecmp(e, "nd")) count = -1; } else if ((count % 10) == 3) { if (strcasecmp(e, "rd")) count = -1; } else if (strcasecmp(e, "th")) { count = -1; } } *mname = ' '; if (count < 0) { /* An error (like '0th' or '12nd') - this wasn't really a count * adjective. Reset and press on. */ *name = savename; return 0; } /* We've got a count adjective */ *name = mname + 1; while (**name == ' ') (*name)++; return count; } static dbref match_me(const dbref who, const char *name) { return (!strcasecmp(name, "me")) ? who : NOTHING; } static dbref match_here(const dbref who, const char *name) { return (!strcasecmp(name, "here") && GoodObject(Location(who))) ? Location(who) : NOTHING; } static dbref match_player(const dbref matcher, const char *match_name) { dbref match; const char *p; if (*match_name != LOOKUP_TOKEN) return NOTHING; for (p = match_name + 1; isspace((unsigned char) *p); p++) ; /* If lookup_player fails, try a partial match on connected * players, 2.0 style. That can return match, NOTHING, AMBIGUOUS */ match = lookup_player(p); return (match != NOTHING) ? match : visible_short_page(matcher, p); } static dbref match_pmatch(const dbref matcher, const char *match_name) { dbref match; const char *p; for (p = match_name; isspace((unsigned char) *p); p++) ; /* If lookup_player fails, try a partial match on connected * players, 2.0 style. That can return match, NOTHING, AMBIGUOUS */ match = lookup_player(p); return (match != NOTHING) ? match : visible_short_page(matcher, p); } /* Run down a contents list and try to match */ MATCH_FUNC(match_list) { dbref absolute; dbref alias_match; int match_type = MATCH_NONE; int nth_match = (*exact_matches_to_go != 0); /* If we were given an absolute dbref, remember it */ absolute = match_absolute(name); /* If we were given a player name, remember it */ alias_match = lookup_player(name); DOLIST(first, first) { if (first == absolute) { /* Got an absolute match, return it */ *match = first; (*exact_matches_to_go)--; (*matches_to_go)--; return MATCH_EXACT; } else if (can_interact(first, who, INTERACT_MATCH) && (!strcasecmp(Name(first), name) || (GoodObject(alias_match) && (alias_match == first)))) { /* An exact match, but there may be others */ (*exact_matches_to_go)--; (*matches_to_go)--; if (nth_match) { if (!(*exact_matches_to_go)) { /* We're done */ *match = first; return MATCH_EXACT; } } else { if (match_type == MATCH_EXACT) *match = choose_thing(who, type, flags, *match, first); else *match = first; match_type = MATCH_EXACT; } } else if ((match_type != MATCH_EXACT) && string_match(Name(first), name) && can_interact(first, who, INTERACT_MATCH)) { /* A partial match, and we haven't done an exact match yet */ (*matches_to_go)--; if (nth_match) { if (!(*matches_to_go)) *match = first; } else if (match_type == MATCH_PARTIAL) *match = choose_thing(who, type, flags, *match, first); else *match = first; match_type = MATCH_PARTIAL; } } /* If we've made the nth partial match in this round, there's none to go */ if (nth_match && *matches_to_go < 0) *matches_to_go = 0; return match_type; } MATCH_FUNC(match_exit) { dbref loc; loc = (IsRoom(who)) ? who : Location(who); if (flags & MAT_REMOTES) { if (GoodObject(loc)) return match_exit_internal(who, name, type, flags, Zone(loc), match, exact_matches_to_go, matches_to_go); else return NOTHING; } else if (flags & MAT_GLOBAL) return match_exit_internal(who, name, type, flags, MASTER_ROOM, match, exact_matches_to_go, matches_to_go); return match_exit_internal(who, name, type, flags, loc, match, exact_matches_to_go, matches_to_go); } MATCH_FUNC(match_exit_internal) { dbref exit_tmp; dbref absolute; int match_type = MATCH_NONE; int nth_match = (*exact_matches_to_go != 0); if (!GoodObject(first) || !IsRoom(first) || !name || !*name) return NOTHING; /* Store an absolute dbref match if given */ absolute = match_absolute(name); DOLIST(exit_tmp, Exits(first)) { if ((exit_tmp == absolute) && (can_interact(exit_tmp, who, INTERACT_MATCH))) { /* Absolute match. Return immediately */ *match = exit_tmp; (*exact_matches_to_go)--; (*matches_to_go)--; return MATCH_EXACT; } else if (check_alias(name, Name(exit_tmp)) && (can_interact(exit_tmp, who, INTERACT_MATCH))) { /* Matched an exit alias, but there may be more */ (*exact_matches_to_go)--; (*matches_to_go)--; if (nth_match) { if (!(*exact_matches_to_go)) { /* We're done */ *match = exit_tmp; return MATCH_EXACT; } } else { if (match_type == MATCH_EXACT) *match = choose_thing(who, type, flags, *match, exit_tmp); else *match = exit_tmp; match_type = MATCH_EXACT; } } } /* If we've made the nth partial match in this round, there's none to go */ if (nth_match && *matches_to_go < 0) *matches_to_go = 0; return match_type; } MATCH_FUNC(match_possession) { if (!GoodObject(who)) return NOTHING; return match_list(who, name, type, flags, Contents(who), match, exact_matches_to_go, matches_to_go); } MATCH_FUNC(match_container) { if (!GoodObject(who)) return NOTHING; return match_list(who, name, type, flags, Location(who), match, exact_matches_to_go, matches_to_go); } MATCH_FUNC(match_neighbor) { dbref loc; if (!GoodObject(who)) return NOTHING; loc = Location(who); if (!GoodObject(loc)) return NOTHING; return match_list(who, name, type, flags, Contents(loc), match, exact_matches_to_go, matches_to_go); } static dbref choose_thing(const dbref match_who, const int preferred_type, long flags, dbref thing1, dbref thing2) { int has1; int has2; /* If there's only one valid thing, return it */ /* (Apologies to Theodor Geisel) */ if (thing1 == NOTHING) return thing2; else if (thing2 == NOTHING) return thing1; /* If a type is given, and only one thing is of that type, return it */ if (preferred_type != NOTYPE) { if (Typeof(thing1) == preferred_type) { if (Typeof(thing2) != preferred_type) return thing1; } else if (Typeof(thing2) == preferred_type) return thing2; } /* If we've asked for a basic lock check, and only one passes, use that */ if (flags & MAT_CHECK_KEYS) { has1 = could_doit(match_who, thing1); has2 = could_doit(match_who, thing2); if (has1 && !has2) return thing1; else if (has2 && !has1) return thing2; } /* No luck. Return the higher dbref */ return (thing1 > thing2 ? thing1 : thing2); }