/* * MusicMUD Daemon, version 1.0 * Copyright (C) 1998-2003 Abigail Brady * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #include "musicmud.h" #include "MudObject.h" #include "verbs.h" #include "match.h" #include "util.h" #include "misc.h" #include "levels.h" #include "units.h" #include "colour.h" #include "pflags.h" #include <string> #include <vector> #include <algorithm> #include <ctype.h> void sett(MudObject *o, string prop, TeleObject what) { o->set(prop.c_str(), what->id); if (what->owner != what.where) { o->set((prop+"n").c_str(), what.nth); o->set((prop+"o").c_str(), what.where->id); } else { o->unset((prop+"n").c_str()); o->unset((prop+"o").c_str()); } } void set_prons(MudObject *player, TeleObject what) { if (what.what == player) return; gender_t g = get_gender(what); if (g==GENDER_MALE) sett(player, "!him", what); else if (g==GENDER_FEMALE) sett(player, "!her", what); else if (g==GENDER_PLURAL || g==GENDER_ANDRO) sett(player, "!them", what); else sett(player, "!it", what); } void set_prons(MudObject *player, const MudObjectWorld &things) { if (things.getsize()==1) { set_prons(player, things.get(0)); return; } if (things.getsize()==0) { return; } player->unset("!them"); player->unset("!themo"); player->unset("!themp"); MudObject *o; int i; foreach((&things), o, i) { player->array_set("!them", i, o); player->array_unset("!them", i, "o"); player->array_unset("!them", i, "n"); } player->set("!them.count", i); } bool endswith(const char *a, const char *b) { if (strlen(b)>strlen(a)) return 0; a += strlen(a) - strlen(b); return !strcmp(a, b); } const char *ord[] = { "first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "ninth", "tenth", "eleventh", "twelfth", "thirteenth", "fourteenth", "fifteenth", }; int height(MudObject *o) { MudObject *upon = o->get_object(KEY_SITON); if (upon) return upon->get_int("height", 1000); return 0; } int eyelevel(MudObject *o) { return o->get_int("eyelevel", 1500) + height(o) + o->get_flag(FL_SITTING)*-500 + o->get_flag(FL_SLEEPING)*-1000; } bool low_enough(MudObject *who, MudObject *what) { return height(what) <= eyelevel(who); } bool shelf_low_enough(MudObject *who, MudObject *what) { return what->get_int("height", 0) <= eyelevel(who); } static int deordinator(const char *a) { if (!a) return -1; for(int i=0;i<15;i++) { if(streq(ord[i], a)) return i; } char *e = 0; long what = strtol(a, &e, 10); if (*e && (e-a+2)!=(int)strlen(a)) { return -1; } if (what<1) { return -1; } if (endswith(a, "1st")) { return what-1; } if (endswith(a, "2nd")) { return what-1; } if (endswith(a, "3rd")) { return what-1; } if (endswith(a, "th")) { return what-1; } return -1; } struct {int n;const char *str;} card[] = { { 0, "no", }, { 1, "a",}, { 1, "an",}, { 1, "one",}, { 2, "two",}, { 2, "couple",}, { 3, "three",}, { 4, "four",}, { 5, "five",}, { 6, "six",}, { 7, "seven",}, { 8, "eight",}, { 9, "nine",}, { 10, "ten",}, { 11, "eleven",}, { 12, "twelve",}, { 12, "dozen",}, { 13, "thirteen",}, { 14, "fourteen",}, { 15, "fifteen", }, {-1, NULL }, }; int decardinator(const char *a) { if (!a || !*a) return -1; char *e = NULL; long l = strtol(a, &e, 10); if (!*e && l >= 0) return l; for(int i=0;card[i].str;i++) { if(streq(card[i].str, a)) return card[i].n; } return -1; } static bool namesmatch2(const char *o_name, const char *sought) { while (o_name) { while (*o_name==' ') o_name++; string oname = lose_colour(o_name); if (oname.length() >= 3 && strlen(sought)<3) { o_name = strchr(o_name, ' '); continue; } if (strncasecmp(oname.c_str(), sought, strlen(sought))==0) return true; o_name = strchr(o_name, ' '); } return false; } static bool isshort(const char *ch) { if (streq(ch, "a")) return 1; if (streq(ch, "an")) return 1; if (streq(ch, "of")) return 1; if (streq(ch, "the")) return 1; if (streq(ch, "some")) return 1; return 0; } bool names_match(const char *haystack, const char *needle) { if (!haystack) return 0; if (!needle) return 0; char *neadle = strdup(needle); char *n2 = neadle + strlen(neadle); n2--; if (*n2=='s' && strlen(neadle)>1) { *n2 = 0; if (isshort(neadle)) { free(neadle); return 0; } } char *a = strdup(haystack), *b=a; a = strtok(a, " "); while (a) { if (namesmatch2(a, neadle)) { free(neadle); free(b); return 1; } a = strtok(NULL, " "); } free(neadle); free(b); return 0; } static bool jewel(MudObject *what) { const char *w = what->get("wornon"); if (!w) return 0; if (streq(w, "finger")) return 1; if (streq(w, "wrist")) return 1; if (streq(w, "neck")) return 1; if (streq(w, "lapel")) return 1; int wl = what->get_int("wornlevel", 0); if (wl==4) return 1; return 0; } struct MatchInfo { int rating; TeleObject ob; bool operator<(const MatchInfo&mi) const { return rating > mi.rating; } MatchInfo(int r, TeleObject o) : rating(r), ob(o) { } }; typedef vector<MatchInfo> Matches; bool category_match(MudObject *what, const char *str, MudObject *me) { if (streq(str, "player") && is_player(what) && me != what) return 1; if (streq(str, "mobile") && is_mobile(what) && me != what) return 1; if (streq(str, "wielded") && what->owner && what->owner->get_object(KEY_WIELD)==what) return 1; if (streq(str, "worn") && what->owner == what->get_object(KEY_WORNBY)) return 1; if (streq(str, "unworn") && what->owner != what->get_object(KEY_WORNBY) && what->owner==me) return 1; if (streq(str, "held")) { if (what->owner != me) return 0; if (!what->owner) return 0; if (what->owner == what->get_object(KEY_WORNBY)) return 0; if (what->owner->get_object(KEY_WIELD)==what) return 0; if (mount(what->owner)==what) return 0; return 1; } if (what->get_flag(FL_MOBILE)) return 0; if (is_player(what)) return 0; if (what->get_int("food")!=-1 || what->get_int("alcohol")!=-1) { if (!what->get_flag(FL_DRINK) && streq(str, "food")) return 1; if (what->get_flag(FL_DRINK) && streq(str, "drink")) return 1; } if (what->get_int("damage")>=1 && streq(str, "weapon")) return 1; if (what->get_int("heal")>=1 && streq(str, "medical")) return 1; if (what->get("wornon") && what->get_int("heal")<1) { if (jewel(what) && (streq(str, "jewelry") || (streq(str, "jewellery")))) return 1; if (!jewel(what) && streq(str, "clothing")) return 1; } if (what->get_flag(FL_MOUNT) && streq(str, "vehicle")) return 1; if (what->get_flag(FL_WATERTIGHT) && streq(str, "empty") && used_volume(what)==0) return 1; return 0; } void TeleWorld::add(TeleObject ob) { v.push_back(ob); siz++; } bool TeleWorld::has(const TeleObject &ob) const { for (int i = 0;i<getsize();i++) if (get(i)==ob) return 1; return 0; } void TeleWorld::add(MudObject &what, MudObject *where) { TeleObject a(where, &what, 0); v.push_back(a); siz++; } void TeleWorld::add(MudObject &what, MudObject *where, int n) { TeleObject a(where, &what, n); v.push_back(a); siz++; } int TeleWorld::getsize() const { return v.size(); } const TeleObject &TeleWorld::get(int a) const { return v[a]; } MudObject *TeleWorld::get_nth(int a) const { return v[a].what; } int TeleWorld::get_nthnth(int a) const { return v[a].nth; } static void addroom(TeleWorld &tomatch, MudObject *where, int flags, MudObject *forwho, int needglow) { MudObject *o; int i; MudObject *fl=floor(where); if (fl) tomatch.add(*fl, where); foreach(where->children, o, i) if (o != fl) if (((!o->get_flag(FL_EXIT) || !(flags & IGNORE_EXITS)))) if (!needglow || o->get_flag(FL_GLOWING)) if (low_enough(forwho, o)) { if (!(flags & PERSON_ONLY) || is_person(o)) tomatch.add(*o, where); } if (flags & LOOK_MWIELD) { foreach(where->children, o, i) if (o != forwho) { if (MudObject *wpn = weapon(o)) if (((!wpn->get_flag(FL_EXIT) || !(flags & IGNORE_EXITS)))) { if (!needglow || wpn->get_flag(FL_GLOWING)) if (!(flags & PERSON_ONLY) || is_person(o)) tomatch.add(*wpn, o); } if (MudObject *m = mount(o)) if (m->owner==o) if (((!m->get_flag(FL_EXIT) || !(flags & IGNORE_EXITS)))) { if (!needglow || m->get_flag(FL_GLOWING)) if (!(flags & PERSON_ONLY) || is_person(o)) tomatch.add(*m, o); } } } for (int i=0;i<where->array_size("tele");i++) { o = where->array_get_object("tele", i); if (fl == o) continue; if (!(flags & PERSON_ONLY) || is_person(o)) if (!needglow || o->get_flag(FL_GLOWING)) { int c = where->array_get_int("tele", i, "count", 1); if (c > 10) c = 10; for (int j=0;j<c;j++) { tomatch.add(*o, where, j); } } } if (forwho->get_int(KEY_BUNNIED, 0)>1) { MudObject *bun = MUD_BUNNIES; if (!(flags & PERSON_ONLY) || is_person(bun)) if (bun) if (!needglow || bun->get_flag(FL_GLOWING)) tomatch.add(*bun, where); } } TeleObject gett(MudObject *o, string prop) { MudObject *obj = o->get_object(prop.c_str()); if (!obj) return TeleObject(); TeleObject to(o->get_object((prop+"o").c_str())?:obj->owner, obj, o->get_int((prop+"n").c_str(), 0)); return to; } TeleObject gett(MudObject *o, string prop, int i) { MudObject *obj = o->array_get_object(prop.c_str(), i); if (!obj) { return TeleObject(); } TeleObject to(o->array_get_object(prop.c_str(), i, "o")?:obj->owner, obj, o->array_get_int(prop.c_str(), i, "n", 0)); return to; } static Matches match(MudObject *forwho, int argc, const char **argv, tfn_t fn, int flags, int &all_nothing, World<MudObject> *special) { MudObject *mission = forwho->get_object(KEY_ACCEPTED); Matches mi; /* TODO : BUNNIES consider virtual objects putting back? */ TeleWorld tomatch; MudObject *o; int i; MudObject *where = forwho->owner; if (flags & LOOK_ROOM) /* automatically let 'me' be used if room is allowed */ flags |= ALLOW_ME; int roomneedglow = 0; if (!cansee(forwho) || !where || ((where->get_flag(FL_ONFIRE)||!has_light(where))&&!wf_wears_with_flag(forwho, FL_NIGHTVISION))) { // flags &= ~LOOK_ROOM; roomneedglow = 1; /* if the room is dark, then any objects that aren't glowing cannot be seen or even referred to */ } if (flags & LOOK_INV) foreach(forwho->children, o, i) if (((!o->get_flag(FL_EXIT) || !(flags & IGNORE_EXITS))) && (o!=forwho)) tomatch.add(*o, forwho); /* add your inventory to the matcher if you're looking there */ if (flags & LOOK_LINKED) { foreach(where->children, o, i) { MudObject *e = linkedlink(o); if (!e) continue; addroom(tomatch, e, flags|PERSON_ONLY, forwho, roomneedglow); } } if (flags & LOOK_ROOM && where) { addroom(tomatch, where, flags, forwho, roomneedglow); } /* add contents of linked and rooms you're in */ if (flags & LOOK_SPECIAL && special) foreach(special, o, i) if (((!o->get_flag(FL_EXIT) || !(flags & IGNORE_EXITS)))) tomatch.add(*o, where); /* add contents of Special paramter if specified */ if (argc) { all_nothing = 0; } if (argc==1 && streq(argv[0], "$1")) { /* deal with $1 magically */ MudObject *o1 = forwho->get_object("!$1"); if (o1) { int yes = 0; if (flags & LOOK_ROOM && o1->owner == forwho->owner && (!roomneedglow || o1->get_flag(FL_GLOWING))) yes = 1; if (flags & LOOK_INV && o1->owner == forwho) yes = 1; if (yes) { MatchInfo j(1100, o1); mi.push_back(j); } } } if (argc==1) { if (flags & ALLOW_ME) { /* Me matches first */ if (streq(argv[0], "myself") || streq(argv[0], "me")) { MatchInfo j(1100, forwho); mi.push_back(j); } } if (streq(argv[0], "it")) { if (TeleObject it=gett(forwho, "!it")) { if (tomatch.has(it)) { MatchInfo j(1100, it); mi.push_back(j); return mi; } } } if (streq(argv[0], "him")) { if (TeleObject it=gett(forwho, "!him")) { if (tomatch.has(it)) { MatchInfo j(1100, it); mi.push_back(j); return mi; } } } if (streq(argv[0], "her")) { if (TeleObject it=gett(forwho, "!her")) { if (tomatch.has(it)) { MatchInfo j(1100, it); mi.push_back(j); return mi; } } } if (streq(argv[0], "them")) { if (TeleObject it=gett(forwho, "!them")) { if (tomatch.has(it)) { MatchInfo j(1100, it); mi.push_back(j); return mi; } } if (int c = forwho->array_size("!them")) { for (int i=0;i<c;i++) { TeleObject it = gett(forwho, "!them", i); // if (it) { // // ::printf("Matched %s\n", name(it)); // } if (it && tomatch.has(it)) { MatchInfo j(1100, it); mi.push_back(j); } } return mi; } } } foreach((&tomatch), o, i) if (o && visible_to(forwho, o)) { TeleObject ob = tomatch.get(i); const char *oshort = o->get("short"); const char *rname = name(o); const char *pname = 0; string ownames; string pnames; if (ob.where && is_person(ob.where) && ob.where != forwho) { ownames = ob.where->get("short"); ownames += "'s"; } const char *owname = ownames.length()?ownames.c_str():NULL; if (rname) { pnames = plural_name(o); pname = pnames.c_str(); } const char *lname = o->get("long"); const char *altshort = o->get("altshort"); if (argc==1) { if ((is_mobile(forwho) || forwho->get_priv(PFL_SEESTATS)) && streq(o->id, argv[0])) { /* match on id first, but only for captains (and mobs */ MatchInfo j((!fn||fn(ob))?1050:550, ob); mi.push_back(j); continue; } if (streq(oshort, argv[0])) { /* match by short */ MatchInfo j((!fn||fn(ob))?1000:500, ob); mi.push_back(j); continue; } if (streq(altshort, argv[0])) { /* match by altshort */ MatchInfo j((!fn||fn(ob))?950:450, ob); mi.push_back(j); continue; } } if (argc) { /* then match by a bunch of other things. words must exist in one of these and then they're ok, if any non-ok words are given, its not ok */ int clash = 0, ok = 0; for (int j=0;j<argc;j++) { if (isshort(argv[j])) { if (names_match(oshort, argv[j]) || names_match(altshort, argv[j])) { ok = 1; } } if (!names_match(oshort, argv[j]) && !names_match(rname, argv[j]) && !names_match(pname, argv[j]) && !names_match(lname, argv[j]) && !names_match(altshort, argv[j]) && !names_match(owname, argv[j]) && !category_match(o, argv[j], forwho)) { clash = 1; } else { if (!isshort(argv[j])) { ok = 1; } } } if (!clash && ok) { MatchInfo j((!fn||fn(ob))?900:400, ob); mi.push_back(j); continue; } } if (!fn || fn(ob)) { /* deal with 'all'. which is handled by giving this argc 0 */ if (argc==0 && o != forwho) { if (flags & IGNORE_MISSION && (o == mission)) continue; if (o->get_flag(FL_INVISIBLE)) continue; MatchInfo j((!fn||fn(ob))?800:300, ob); mi.push_back(j); continue; } if (argc==1 && !(flags&IGNORE_CATEGORIES)) { if (category_match(o, argv[0], forwho)) { MatchInfo j((!fn||fn(o))?600:200, ob); mi.push_back(j); } } } } /* sort in order of goodness */ sort(mi.begin(), mi.end()); return mi; } static Matches match2(MudObject *forwho, int argc, const char **argv, tfn_t fn, int flags, int &all_nothing, World<MudObject> *special) { int all = 0; int nth = 0; int count = -1; int any = 0; if (flags & DEFAULT_ALL) all = 1; if (argc && streq(argv[0], "all")) { argc--; argv++; all = 1; /* all (except missions) */ } if (argc && streq(argv[0], "ALL")) { argc--; argv++; all = 1; /* all (including missions */ flags &= ~IGNORE_MISSION; } if (argc && streq(argv[0], "any")) { argc--; argv++; if (!argc) { Matches m; all_nothing = 103; return m; } all = 0; any = 1; /* return a random one of the candidates */ } const char *replaced_with = 0; int replaced = 0; if (argc && strncmp(argv[0], "far.", 4)==0) { replaced = 0; replaced_with = argv[0]; argv[0] += 4; flags &= ~LOOK_BOTH; } /* This means that callers must always check the result to see if its valid. */ if (argc && strncmp(argv[0], "near.", 5)==0) { replaced = 0; replaced_with = argv[0]; argv[0] += 5; flags &= ~LOOK_SPECIAL; } if (argc && streq(argv[0], "that") || streq(argv[0], "those")) { argc--; argv++; flags &= ~LOOK_INV; /* that and those look only in the room */ } if (argc && streq(argv[0], "these") || streq(argv[0], "my")) { argc--; argv++; flags &= ~LOOK_ROOM; /* these and my look only in your inv */ } if (argc) { /* convert 1st, 2nd, 3rd, etc to an ordinal number */ nth = deordinator(argv[0]); if (nth == -1) nth = 0; else { argc--; argv++; } /* convert cardinal numbers */ if (argc>1) { count = decardinator(argv[0]); if (count != -1) { argc--; argv++; all = 0; } } } /* deal with 2.foo -> second foo syntax. from diku. */ const char *a = argv[0]; int num=1; if (argc) { while (*a && *a!='.') { if (!isdigit(*a)) { num=0; } a++; } if (num && *a=='.') { nth = atoi(argv[0])-1; if (!replaced_with) { replaced = 0; replaced_with = argv[0]; all = 0; } argv[0] = a+1; } } string s; if (argc && !replaced_with) { const char *z = argv[argc-1]; if (z[0] != '$') { /* deal with foo2 -> second foo */ const char *x = z + strlen(z); while (x > z) { x--; if (!isdigit(*x)) { x++; break; } } if (isdigit(*x) && x != z && x[-1]!='_') { nth = atoi(x)-1; all = 0; s = argv[argc-1]; s = s.substr(0, x-z); replaced = argc-1; replaced_with = argv[argc-1]; argv[argc-1] = s.c_str(); } } } if (argc && streq(argv[0], "them") && count==-1) { all = 1; } Matches mi = match(forwho, argc, argv, fn, flags, all_nothing, special); if ((!argc || !argv[0][0]) && !all) { Matches m; all_nothing = 103; if (replaced_with) { argv[replaced] = replaced_with; } return m; } if (replaced_with) { argv[replaced] = replaced_with; } if (!all) { Matches m2; if (count==-1 && (int)mi.size()>nth && nth>=0) { if (any) { nth = random_number(mi.size()); } m2.push_back(mi[nth]); return m2; } else if (count==-1) { return m2; } else if (count) { if (count>(int)mi.size()) count = mi.size(); for (int i=0;i<count;i++) { if ((int)mi.size()>i) { m2.push_back(mi[i]); } } } return m2; } return mi; } static void onceadd(NewWorld &w, MudObject *o) { if (!w.get(o->id)) { w.add(*o); } } static void onceadd(TeleWorld &sofar, TeleObject ob) { if (!sofar.has(ob)) { sofar.add(ob); } } bool prep(const char *o) { if (streq(o, "in")) return 1; if (streq(o, "to")) return 1; if (streq(o, "from")) return 1; if (streq(o, "for")) return 1; if (streq(o, "with")) return 1; if (streq(o, "into")) return 1; if (streq(o, "onto")) return 1; if (streq(o, "on")) return 1; if (streq(o, "at")) return 1; return 0; } static void andmatch(MudObject *forwho, World<MudObject>*special, int argc, const char **argv, tfn_t fn, int flags, TeleWorld &sofar) { int st = 0; for (int i=0;i<argc;i++) { if (streq(argv[i], "and")) { Matches m2 = match2(forwho, i-st, argv+st, fn, flags, sofar.all_nothing, special); Matches::iterator it = m2.begin(); while (it != m2.end()) { onceadd(sofar, it->ob); it++; } st = i+1; } } Matches m2 = match2(forwho, argc-st, argv+st, fn, flags, sofar.all_nothing, special); Matches::iterator it = m2.begin(); while (it != m2.end()) { onceadd(sofar, it->ob); it++; } } TeleWorld multi_match(MudObject *forwho, int argc, const char **argv, tfn_t fn, int flags, int *nexto, World<MudObject> *special, const char *prepf) { TeleWorld sofar; sofar.all_nothing = 1; sofar.prep = NULL; if (prepf) { int i; for (i=0;i<argc;i++) { if (streq(argv[i], prepf)) { return multi_match(forwho, argc-i, argv+i, fn, flags, nexto, special); } } sofar.all_nothing = 200; return sofar; } if (nexto) { *nexto += argc; } if (prep(argv[0])) { sofar.prep = argv[0]; argc--; argv++; } for (int i=1;i<argc;i++) { if (prep(argv[i])) { if (nexto) { *nexto += (i - argc); } argc = i; } } if (argc>=1) sofar.txt = argv[0]; for (int i=1;i<argc;i++) { sofar.txt += " "; sofar.txt += argv[i]; } int but = 0; for (int i=1;i<argc;i++) { if (streq(argv[i], "but")) { but = i; } } if (but) { TeleWorld all; TeleWorld except; andmatch(forwho, special, but, argv, fn, flags, all); andmatch(forwho, special, argc-but-1, argv+but+1, fn, flags|DEFAULT_ALL, except); for (int i=0;i<all.getsize();i++) { TeleObject ob = all.get(i); if (!except.has(ob)) sofar.add(ob); } return sofar; } andmatch(forwho, special, argc, argv, fn, flags, sofar); return sofar; } NewWorld match(MudObject *forwho, int argc, const char **argv, tfn_t fn, int flags, int *nexto, World<MudObject> *special, const char *prepf) { NewWorld sofar; sofar.prep = NULL; TeleWorld m=multi_match(forwho, argc, argv, fn, flags, nexto, special, prepf); for (int i=0;i<m.getsize();i++) { MudObject *o = m.get_nth(i); onceadd(sofar, o); } sofar.prep = m.prep; sofar.all_nothing = m.all_nothing; sofar.txt = m.txt; return sofar; } NewWorld match(MudObject *forwho, int argc, const char **argv, tfn_t fn, int flags, const char *prep) { return match(forwho, argc, argv, fn, flags, 0, 0, prep); }