/* Notes from he author: * * Once upon a time I write a rather nice soul. * Unfortunately the code looked like something the cat * dragged in. The format for adding new feelings to it * was on the verge to ludicrous. It also had limitations * and lacked a few important features.... * * * T H E S O U L I I * * It's back! It's bigger, better, slower and maybe even * understandable. Just when you thought it was safe to * write 'smile' again the man behind such unimaginable * things as The Shell, The Soul and LPC4 strikes again. * * (have patience, it is not finished yet.) * * /Profezzorn@Nannymud (email: hubbe@lysator.liu.se) */ /* configuration: */ #define DEBUG 0 #ifdef LPC4 inherit "/std/object"; #define HAVE_SPRINTF #define WIZARD_P(X) 1 #define PERFECT_EXPLODE #define REPLACE(X,Y,Z) replace(X,Y,Z) #else #ifdef __NANNYMUD__ #define HAVE_SPRINTF #define WIZARD_P(X) ((X)->query_level()>19) #define REPLACE(X,Y,Z) implode(my_explode(X,Y),Z) /* #define PERFECT_EXPLODE */ #else /* __NANNYMUD__ */ /* default stupid configuration */ /* #define HAVE_SPRINTF */ #define WIZARD_P(X) 0 #define REPLACE(X,Y,Z) implode(my_explode(X,Y),Z) /* #define PERFECT_EXPLODE */ #endif /* __NANNYMUD__ */ #endif /* LPC4 */ /* useful defines */ #include "soul.h" /* if we use the exact same 'spaces' string evewhere we save some memory * it also look nicer. */ #define SPACES " " #define MIN(X,Y) ((X)<(Y)?(X):(Y)) #ifdef LPC4 #define MAP(X,Y,Z) map_array((X),Y,(Z)) #define FILTER(X,Y,Z) filter_array((X),Y,(Z)) #else #define FILTER(X,Y,Z) map_array((X),"Y",this_object(),(Z)) #endif #ifdef LPC4 #pragma all_inline #endif #ifdef PERFECT_EXPLODE #define my_explode(X,Y) explode(X,Y) #else /* perfect_explode */ string *my_explode(string a,string b) { string *c; if(a==b) return ({"",""}); if(strlen(b) && a[0..strlen(b)-1]==b) return ({ "" })+ my_explode(a[strlen(b)..strlen(a)-1],b); if(c=explode(a+b,b)) return c; return ({}); } #endif /* perfect_explode */ #ifdef HAVE_SPRINTF /* This linebreak has usually a limit about 10000 bytes */ string fast_linebreak(string text,string pre,int width) { return sprintf("%s%-=*s",pre,width,text); } #else /* have_sprintf */ /* This doesn't have to be able to linebreak very large strings */ string fast_linebreak(string text,string pre,int width) { string *parts; int part; parts=my_explode(text,"\n"); for(part=0;part<sizeof(parts);part++) { string *words,out; int word,len; words=my_explode(parts[part]," "); out=pre; pre=SPACES[1..strlen(pre)]; for(len=0;len<width && word<sizeof(words);len+=strlen(words[word++])) out+=words[word]; parts[part]=out; } return implode(parts,"\n"); } #endif /* have_sprintf */ /* This function is almost only used for error messages */ string show_value(mixed v) { if(stringp(v)) { v=REPLACE(v,"\\","\\\\"); v=REPLACE(v,"\"","\\\""); v=REPLACE(v,"\b","\\b"); v=REPLACE(v,"\n","\\n"); return "\""+v+"\""; }else if(intp(v)){ return v+""; }else if(objectp(v)){ return file_name(v); }else if(pointerp(v)){ return "({"+implode(MAP(v,show_value,0),",")+"})"; }else if(mappingp(v)){ mixed *a,*b; int e; a=m_indices(v); b=m_values(v); for(e=0;e<sizeof(a);e++) a[e]=show_value(a[e])+":"+show_value(b[e]); return "(["+implode(a,",")+"])"; }else{ return "UNKNOWN"; } } /* This routing is simply a must. * Implodes an array ({"foo","bar","gazonk"}) to * "foo, bar and gazonk" * The second (optional) argument can be used to replace 'and' with * something else.(probably or) */ varargs string implode_nicely(string *words,string conjunction) { int s; if(!conjunction) conjunction="and"; switch(s=sizeof(words)) { default: return implode(words[0..s-2],", ")+" "+conjunction+" "+words[s-1]; case 2: return words[0]+" "+conjunction+" "+words[1]; case 1: return words[0]; case 0: return ""; } } /* * Bullshit or not, you be the judge! * We simply want to make sure that strings are shared sometimes to save * memory (if you don't know what shared strings are - don't meddle) */ string share_string(string s) { #ifdef LPC4 return s; #else return m_indices(([s:0]))[0]; #endif } /* inform this_player(), and possible log that an error has occured */ void inform_error(string excuse,string cause) { write(excuse); if(!cause) return; if(WIZARD_P(this_player())) write(cause); #if 0 log_file("SOUL",cause); #endif } /* Check if two arrays are equal. * Note that this function can't be compiled with type-testing in 3.1.2 * because the compiler doesn't think the operator '&' handles arrays. */ #ifdef LPC4 int #endif eq(mixed *a,mixed *b) { return sizeof(a)==sizeof(b) && sizeof(a & b)==sizeof(a); } #ifdef LPC4 /* haven't implemented light in lpc4 mudlib yet */ int is_dark() { return 0; } #else int is_dark() { return set_light(0)<=0; } #endif #ifdef LPC4 #define SUB_ARRAYS(X,Y) ((X)-(Y)) #else sub_arrays(x,y) { return x-y; } #define SUB_ARRAYS(X,Y) ((mixed *)sub_arrays((X),(Y))) #endif #ifdef LPC4 string big_linebreak(string text,string pre,int width) { return fast_linebreak(text,pre,width); } #else /* LPC4 */ /* Standard sprintf has an internal buffer of 10000 bytes. To work around * this bug we call fast_sprintf a few times..... This also helps if you * don't have sprintf and is linebreaking something that has more words * than can be fitted into you arrays. */ string big_linebreak(string s,string pre,int width) { int e; string done,a,b; done=""; while(strlen(s)) { if(strlen(s)<5000) { e=5000; }else{ e=5000; while(e>=0 && s[e]!=' ') e--; if(e==-1) return done+" "+s+"\n"; } done+=fast_linebreak(s[0..e-1],pre,width); pre=SPACES[1..strlen(pre)]; s=s[e+1..strlen(s)-1]; for(e=strlen(done)-1;e>=0 && done[e]!='\n';e--); a=0; b=""; sscanf(s,"%s %s",a,b); while(a && strlen(a)+strlen(done)-e<=width+2) { done+=" "+a; s=b; a=0; b=""; sscanf(s,"%s %s",a,b); } done+="\n"; } return done; } #endif /* LPC4 */ #ifdef LPC4 void more(string str) { write(str); } void more_flush() {} #else string morestring=""; void more(string str) { morestring+=str; } varargs void more_flush(string str) { int e; string a; int rows; if (this_player()->query_rows() > 0) rows = (int)this_player()->query_rows() - 2; else rows = 22; if(str=="q") { morestring=""; return; } for(e=0;e<rows;e++) { if(sscanf(morestring,"%s\n%s",a,morestring)) { write(a+"\n"); }else{ write(morestring); e=4711; morestring=""; } } if(strlen(morestring)) { write("*Press return for more or q to end.->"); input_to("more_flush"); } } #endif /* -Exaggerating, me? */ string number_to_string(int x) { switch(x) { case 0: return "zero"; case 1: return "one"; case 2: return "two"; case 3: return "three"; case 4: return "four"; case 5: return "five"; case 6: return "six"; case 7: return "seven"; case 8: return "eight"; case 9: return "nine"; case 10: return "ten"; case 11: return "eleven"; case 13: return "thirteen"; case 14: return "fourteen"; case 15: return "fifteen"; case 16: return "sixteen"; case 17: return "seventeen"; case 18: return "eightteen"; case 19: return "nineteen"; case 20: return "twenty"; case 30: return "thirty"; case 40: return "fourty"; case 50: return "fifty"; case 60: return "sixty"; case 70: return "seventy"; case 80: return "eighty"; case 90: return "ninety"; case 21..29: case 31..39: case 41..49: case 51..59: case 61..69: case 71..79: case 81..89: case 91..99: return number_to_string((x/10)*10)+number_to_string(x%10); case 100: case 200: case 300: case 400: case 500: case 600: case 700: case 800: case 900: return number_to_string(x/100)+" hundred"; case 101..199: case 201..299: case 301..399: case 401..499: case 501..599: case 601..699: case 701..799: case 801..899: case 901..999: return number_to_string((x/100)*100)+" and "+number_to_string(x%100); case 1000..999999: if(x%1000) { return number_to_string(x/1000)+" thousand "+number_to_string(x%1000); }else{ return number_to_string(x/1000)+" thousand"; } case 1000000: return "one million"; case 1000001..1999999: return "one million "+number_to_string(x%1000000); case 2000000..999999999: if(x%1000000) { return number_to_string(x/1000000)+" millions "+number_to_string(x%1000000); }else{ return number_to_string(x/1000000)+" millions"; } case -0x7fffffff..-1: return "minus "+number_to_string(-x); default: return "many"; } } string nr_times(int x) { switch(x) { case 0: return " no times"; case 1: return ""; case 2: return " twice"; case 3: return " threefold"; default: return " "+number_to_string(x)+" times"; } } #ifdef LPC4 /* simply sorts an array of strings in alphabetical order */ string *sort_words(string *s) { return sort_array(s); } #else /* LPC4 */ int letterorder(string a,string b) { #ifdef MUDOS if(a>b) return 1; if(a<b) return -1; #else return a>b; #endif } /* simply sorts an array of strings in alphabetical order */ string *sort_words(string *s) { return sort_array(s,"letterorder",this_object()); } #endif /* LPC4 */ /* To use shared arrays we need to get them from somewhere global. * we use the masterobject. This routine returns it. */ #ifdef LPC4 object soul_master() { return (object)file_name(); } #else string soul_master() { string f; f=file_name(this_object()); sscanf(f,"%s#",f); return f; } #endif /* LPC4 */ mapping verbs=(mapping)soul_master()->get_verbs(); mapping get_verbs() { if(verbs) return verbs; return ([ "ack":"\b1PRON ack\b$ \bADV \bAT", "admire":"\b1PRON admire\b$ \b2OBJ \bADV", "agree":({"simple",(["prep":" with"])}), "apologize":({"simple",(["prep":" to"])}), "applaud":({"simple",(["prep":""])}), "ask":({"\b1PRON \bADV ask\b$ \bAT: \bMSG?",(["prep":""])}), "ayt":"\b1PRON wave\b$ \b1POSS hand in front of \b2POSS face, \bIS \nSUBJ \bADV there?", "babble":({"default",([C_ADV:"incoherently",C_MSGS:"'something"]),({"\b1PRON babble\b$ \bTEXT \bADV \bAT",(["prep":" to"])})}), "beep":({"default",([C_ADV:"triumphantly",C_BODY:"on the nose"]),({"who_p","\b1PRON \bADV beep\b$ \b1OBJself \bWHERE","\b1PRON \bADV beep\b$ \b2OBJ \bWHERE"})}), "beg":({"who_p","\b1PRON beg\b$ \bADV","\b1PRON beg\b$ \b2OBJ for mercy \bADV"}), "bite":({"who_p","\b1PRON \bADV bite\b$ \b1OBJ lip","\b1PRON bite\b$ \b2OBJ \bADV"}), "blink":({"who_p","\b1PRON blink\b$ \bADV","\b1PRON blink\b$ \bADV at \b2OBJ"}), "blush":({"irregular","\b1PRON blush \bADV","\b1PRON blushes \bADV"}), "boggle":"\b1PRON boggle\b$ \bADV at the concept", "bonk":({"default",([C_BODY:"on the head"]),({"\b1PRON bonk\b$ \b2OBJ \bADV \bWHERE",(["flags":FLAG_TOUCH,])})}), "bounce":"\b1PRON bounce\b$ \bADV", "bow":({"simple",(["prep":" to"])}), "burp":({"default",([C_ADV:"rudely"]),"simple"}), "cackle":({"default",([C_ADV:"gleefully"]),"simple"}), "capitulate":({"default",([C_ADV:"unconditionally"]),({"simple",(["prep":" to"])})}), "caress":({"default",([C_BODY:"on the cheek"]),({"\b1PRON caress\b$ \b2OBJ \bADV \bWHERE",(["flags":FLAG_TOUCH,])})}), "cheer":({"default",([C_ADV:"enthusiastically"]),"\b1PRON cheer\b$ \bADV"}), "chuckle":({"default",([C_ADV:"politely"]),"simple"}), "clap":"\b1PRON clap\b$ \bADV", "clear":"\b1PRON clear\b$ \b1POSS throat \bADV", "clue":"\b1PRON need\b$ a clue \bADV", "comfort":"\b1PRON comfort\b$ \b2OBJ \bADV", "complain":({"simple",(["prep":" about"])}), "congratulate":"\b1PRON congratulate\b$ \b2OBJ \bADV", "cough":({"default",([C_ADV:"noisily"]),"\b1PRON cough\b$ \bADV"}), "cringe":({"default",([C_ADV:"in terror"]),"\b1PRON cringe\b$ \bADV"}), "criticize":({"who_p","\b1PRON criticize\b$ \bMSG \bADV","\b1PRON criticize\b$ \b2OBJ \bADV"}), "cry":({"irregular","\b1PRON cry \bADV","\b1PRON cries \bADV"}), "cuddle":"\b1PRON cuddle\b$ \b2OBJ \bADV", "curse":({"who_p","\b1PRON curse\b$ \bMSG \bADV","\b1PRON curse\b$ \b2OBJ \bADV"}), "curtsey":({"simple",(["prep":" before"])}), "dance":({"simple",(["prep":" with"])}), "dandle":"\b1PRON dandle\b$ \b2OBJ \bADV", "die":({"irregular","\b1PRON fall \bADV down and play dead","\b1PRON falls \bADV to the ground, dead"}), "disagree":({"simple",(["prep":" with"])}), "drool":({"simple",(["prep":" on"])}), "duck":({"who_p","\b1PRON duck\b$ \bADV out of the way","\b1PRON duck\b$ \bADV out of \b2POSS way"}), "embrace":({"default",([C_BODY:"in \nYOUR arms"]),({"\b1PRON embrace\b$ \b2OBJ \bADV \bWHERE",(["flags":FLAG_TOUCH,])})}), "exclaim":({"\b1PRON \bADV exclaim\b$ \bAT: \bMSG!",(["prep":""])}), "excuse":({"who_p","\b1PRON \bADV excuse\b$ \b1OBJself","\b1PRON \bADV excuse\b$ \b1OBJself to \b2OBJ"}), "faint":"\b1PRON faint\b$ \bADV", "fart":"simple", "fear":({"who_p","\b1PRON shiver\b$ \bADV with fear","\b1PRON fear\b$ \b2OBJ \bADV"}), "finger":"\b1PRON give\b$ \b2OBJ the finger", "flex":({"\b1PRON flex\b$ \b1POSS muscles \bADV",(["prep":""])}), "flip":"\b1PRON flip\b$ \bADV head over heels", "fondle":"\b1PRON fondle\b$ \b2OBJ \bADV", "french":"\b1PRON give\b$ \b2OBJ a REAL kiss, it seems to last forever", "frown":"\b1PRON frown\b$ \bADV", "fume":"\b1PRON fume\b$ \bADV", "gasp":({"default",([C_ADV:"in astonishment"]),"\b1PRON gasp\b$ \bADV"}), "giggle":({"default",([C_ADV:"merrily"]),"simple"}), "glare":({"default",([C_ADV:"stonily"]),"simple"}), "gobble":({"who_p","\b1PRON gobble\b$ HOW","\b1PRON gobble\b$ all over \b2OBJ"}), "grimace":"\b1PRON \bADV make\b$ an awful face \bAT", "grin":({"default",([C_ADV:"evilly"]),"simple"}), "gripe":"\b1PRON gripe\b$ to \b2OBJ \bADV", "groan":"simple", "grope":"\b1PRON grope\b$ \b2OBJ \bADV", "grovel":({"simple",(["prep":" before"])}), "growl":"simple", "grumble":"\b1PRON grumble\b$ \bADV", "grunt":"simple", "guffaw":"\b1PRON guffaw\b$ \bADV \bAT", "gurgle":"\b1PRON gurgle\b$ \bADV", "hate":"\b1PRON hate\b$ \b2OBJ \bADV", "hiccup":"\b1PRON hiccup\b$ \bADV", "hide":"\b1PRON hide\b$ \bADV behind \b2OBJ", "hit":({"default",([C_BODY:"in the face"]),({"\b1PRON hit\b$ \b2OBJ \bADV \bWHERE",(["flags":FLAG_TOUCH | FLAG_OFFENSE])})}), "hmm":"\b1PRON hmm\b$ \bADV \bAT", "hold":({"default",([C_BODY:"in \nYOUR arms"]),({"\b1PRON hold\b$ \b2OBJ \bADV \bWHERE",(["flags":FLAG_TOUCH,])})}), "howl":({"default",([C_ADV:"in pain"]),"simple"}), "huff":"\b1PRON huff\b$ \bADV", "hug":"\b1PRON hug\b$ \b2OBJ \bADV", "ignore":"\b1PRON ignore\b$ \b2OBJ \bADV", "jump":({"default",([C_ADV:"up and down in aggravation"]),"\b1PRON jump\b$ \bADV"}), "kick":({"default",([C_ADV:"hard"]),({"\b1PRON kick\b$ \b2OBJ \bADV \bWHERE",(["flags":FLAG_TOUCH,])})}), "kiss":({"irregular","\b1PRON kiss \b2OBJ \bADV","\b1PRON kisses \b2OBJ \bADV"}), "knee":({"default",([C_BODY:"where it hurts"]),({"\b1PRON knee\b$ \b2OBJ \bADV \bWHERE",(["flags":FLAG_TOUCH,])})}), "kneel":({"\b1PRON \bADV fall\b$ on \b1POSS knees \bAT",(["prep":" in front of"])}), "lag":({"default",([C_ADV:"helplessly"]),"\b1PRON lag\b$ \bADV"}), "laugh":"simple", "leer":"simple", "lick":"\b1PRON lick\b$ \b2OBJ \bADV", "lie":({"who_p","\b1PRON lie\b$ \bTEXT \bADV","\b1PRON lie\b$ to \b2OBJ \bADV"}), "like":"\b1PRON like\b$ \b2OBJ \bADV", "listen":({"simple",(["prep":" to"])}), "love":"\b1PRON love\b$ \b2OBJ \bADV", "mercy":"\b1PRON beg\b$ \b2OBJ for mercy", "moan":"simple", "mock":"\b1PRON mock\b$ \b2OBJ \bADV", "mumble":({"\b1PRON mumble\b$ \bTEXT \bADV \bAT",(["prep":" to"])}), "mutter":({"who_p","\b1PRON mutter\b$ \bTEXT \bADV","\b1PRON mutter\b$ to \b2OBJ \bADV"}), "nibble":"\b1PRON nibble\b$ \bADV on \b2POSS ear", "nod":({"default",([C_ADV:"solemnly"]),"simple"}), "nudge":({"\b1PRON nudge\b$ \b2OBJ \bADV \bWHERE",(["flags":FLAG_TOUCH,])}), "pace":({"default",([C_ADV:"impatiently"]),"\b1PRON start\b$ pacing \bADV"}), "pale":"\b1PRON turn\b$ white as ashes \bADV", "panic":"\b1PRON panic\b$ \bADV", "pant":({"default",([C_ADV:"heavily"]),"\b1PRON pant\b$ \bADV \bAT"}), "pat":({"default",([C_BODY:"on the head"]),({"\b1PRON pat\b$ \b2OBJ \bADV \bWHERE",(["flags":FLAG_TOUCH,])})}), "peer":"\b1PRON peer\b$ at \b2OBJ \bADV", "pinch":({"irregular","\b1PRON pinch \b2OBJ \bADV","\b1PRON pinches \b2OBJ \bADV"}), "point":"simple", "poke":({"default",([C_BODY:"in the ribs"]),({"\b1PRON poke\b$ \b2OBJ \bADV \bWHERE",(["flags":FLAG_TOUCH,])})}), "ponder":({"default",([C_ADV:"over some problem"]),"\b1PRON ponder\b$ \bADV"}), "pounce":({"default",([C_ADV:"playfully"]),({"\b1PRON pounce\b$ \b2OBJ \bADV \bWHERE",(["flags":FLAG_TOUCH,])})}), "pout":"\b1PRON pout\b$ \bADV", "puff":"\b1PRON puff\b$ \bADV", "puke":({"simple",(["prep":" on"])}), "punch":({"default",([C_BODY:"in the eye"]),({"irregular","\b1PRON punch \b2OBJ \bADV \bWHERE","\b1PRON punches \b2OBJ \bADV \bWHERE"})}), "purr":"simple", "puzzle":"\b1PRON look\b$ \bADV puzzled \bAT", "raise":"\b1PRON \bADV raise\b$ an eyebrow \bAT", "recoil":({"default",([C_ADV:"with fear"]),({"simple",(["prep":" from"])})}), "relax":({"irregular","\b1PRON relax \bADV","\b1PRON relaxes \bADV"}), "remember":({"\b1PRON remember\b$ \bAT \bADV",(["prep":""])}), "roll":({"default",([C_ADV:"to the ceiling"]),"\b1PRON roll\b$ \b1POSS eyes \bADV"}), "rotate":"\b1PRON rotate\b$ \bADV", "ruffle":"\b1PRON ruffle\b$ \b2POSS hair \bADV", "salute":"\b1PRON salute\b$ \b2OBJ \bADV", "say":({"default",([C_MSGS:"'nothing"]),({"\b1PRON \bADV say\b$ \bTEXT \bAT",(["prep":" to"])})}), "scowl":({"default",([C_ADV:"darkly"]),"simple"}), "scratch":({"default",([C_ADV:"on the head"]),({"who_p",({"irregular","\b1PRON scratch \b1OBJself \bADV","\b1PRON scratches \b1OBJself \bADV"}),({"irregular","\b1PRON scratch \b2OBJ \bADV","\b1PRON scratches \b2OBJ \bADV"})})}), "scream":({"default",([C_ADV:"loudly"]),"\b1PRON scream\b$ \bTEXT \bADV \bAT"}), "shake":({"who_p","\b1PRON shake\b$ \b1POSS head \bADV","\b1PRON shake\b$ hands with \b2OBJ \bADV"}), "shiver":({"default",([C_ADV:"from the cold"]),"\b1PRON shiver\b$ \bADV"}), "shrug":"\b1PRON shrug\b$ \bADV", "sigh":"\b1PRON sigh\b$ \bADV", "sing":({"\b1PRON \bADV sing\b$ \bMSG \bAT",(["prep":" to"])}), "slap":({"default",([C_BODY:"in the face"]),({"\b1PRON slap\b$ \b2OBJ \bADV \bWHERE",(["flags":FLAG_TOUCH,])})}), "smile":({"default",([C_ADV:"happily"]),"simple"}), "smirk":"\b1PRON smirk\b$ \bADV", "snap":"\b1PRON snap\b$ \b1POSS fingers \bAT", "snarl":"simple", "sneeze":({"default",([C_ADV:"loudly"]),"simple"}), "snicker":"\b1PRON snicker\b$ \bADV", "sniff":"\b1PRON sniff\b$ \bADV", "snigger":({"default",([C_ADV:"jeeringly"]),"simple"}), "snore":"\b1PRON snore\b$ \bADV", "snort":"\b1PRON snort\b$ \bADV \bAT", "snuggle":"\b1PRON snuggle\b$ \b2OBJ \bADV", "sob":"\b1PRON sob\b$ \bADV", "spank":({"default",([C_BODY:"on the butt"]),({"\b1PRON spank\b$ \b2OBJ \bADV \bWHERE",(["flags":FLAG_TOUCH,])})}), "spit":({"simple",(["prep":" on"])}), "squeeze":({"default",([C_ADV:"fondly"]),"\b1PRON squeeze\b$ \b2OBJ \bADV"}), "squint":"\b1PRON squint\b$ \bADV", "stare":"simple", "stomp":({"who_p","\b1PRON stomp\b$ \b1POSS foot \bADV","\b1PRON stomp\b$ on \b2POSS foot \bADV"}), "strangle":"\b1PRON strangle\b$ \b2OBJ \bADV", "stroke":({"default",([C_BODY:"on the cheek"]),({"\b1PRON stroke\b$ \b2OBJ \bADV \bWHERE",(["flags":FLAG_TOUCH,])})}), "strut":({"default",([C_ADV:"proudly"]),"\b1PRON strut\b$ \bADV"}), "stumble":"\b1PRON stumble\b$ \bADV", "stupid":"\b1PRON look\b$ \bADV stupid", "sulk":({"default",([C_ADV:"in the corner"]),"\b1PRON sulk\b$ \bADV"}), "surprise":"\b1PRON surprise\b$ \b2OBJ \bADV", "surrender":({"simple",(["prep":" to"])}), "swear":({"\b1PRON swear\b$ \bMSG \bAT \bADV",(["prep":" before"])}), "sweat":"\b1PRON sweat\b$ \bADV", "tackle":({"\b1PRON tackle\b$ \b2OBJ \bADV",(["prep":""])}), "tap":({"default",([C_ADV:"impatiently",C_BODY:"on the shoulder"]),({"who_p","\b1PRON tap\b$ \b1POSS foot \bADV","\b1PRON tap\b$ \b2OBJ \bWHERE"})}), "taunt":"\b1PRON taunt\b$ \b2OBJ \bADV", "tease":"\b1PRON tease\b$ \b2OBJ \bADV", "thank":"\b1PRON thank\b$ \b2OBJ \bADV", "think":({"default",([C_ADV:"carefully"]),"\b1PRON think\b$ \bADV"}), "thumb":"\b1PRON \bADV suck\b$ \b1POSS thumb", "tickle":"\b1PRON tickle\b$ \b2OBJ \bADV", "tongue":"\b1PRON stick\b$ \b1POSS tongue out \bAT", "touch":({"irregular","\b1PRON touch \b2OBJ \bADV","\b1PRON touches \b2OBJ \bADV"}), "tremble":"\b1PRON tremble\b$ \bADV", "twiddle":"\b1PRON twiddle\b$ \b1POSS thumbs \bADV", "twitch":({"irregular","\b1PRON twitch \bADV","\b1PRON twitches \bADV"}), "utter":({"\b1PRON \bADV utter\b$ \bTEXT \bAT",(["prep":" to"])}), "watch":({"default",([C_ADV:"carefully"]),({"who_p",({"irregular","\b1PRON watch the surroundings \bADV","\b1PRON watches the surroundings \bADV"}),({"irregular","\b1PRON watch \b2OBJ \bADV","\b1PRON watches \b2OBJ \bADV"})})}), "wave":({"default",([C_ADV:"happily"]),"simple"}), "whine":"\b1PRON whine\b$ \bADV", "whistle":({"default",([C_ADV:"appreciatively"]),"simple"}), "wiggle":({"\b1PRON wiggle\b$ \b1POSS bottom \bAT \bADV",(["prep":""])}), "wink":({"default",([C_ADV:"suggestivly"]),"simple"}), "wobble":({"\b1PRON wobble\b$ \bAT \bADV",(["prep":""])}), "worship":"\b1PRON worship\b$ \b2OBJ \bADV", "wrinkle":({"\b1PRON wrinkle\b$ \b1POSS nose \bAT \bADV",(["prep":""])}), "yawn":"\b1PRON yawn\b$ \bADV", "yell":({"default",([C_ADV:"in a high pitched voice"]),"\b1PRON yell\b$ \bTEXT \bADV \bAT"}), "yodel":({"\b1PRON yodel\b$ a merry tune \bADV",(["prep":""])}), ]); } mapping adverbs=(mapping)soul_master()->get_adverbs(); /* this function can't take typing in most muds */ mapping get_adverbs() { string *q,f; if(adverbs) return adverbs; f=file_name(this_object()); sscanf(f,"%s#",f); if(f[0]!='/') f="/"+f; f=read_file(f+"_adverbs"); if(f) q=my_explode(f,"\n")-({""}); else q=({}); return mkmapping(q,q); } mapping how=([]); mapping xadverbs=([]); mapping xverbs=([]); mapping bodydata=([]); mixed *failed_action; mixed last_action; string verb_string=(string)soul_master()->get_verb_string(); string get_verb_string() { if(verb_string) return verb_string; verb_string=implode_nicely(sort_words(m_indices(verbs))); verb_string=share_string(big_linebreak(verb_string," ",77)); return verb_string; } string xverb_string; string get_xverb_string() { if(xverb_string) return xverb_string; xverb_string=implode_nicely(sort_words(m_indices(xverbs))); xverb_string=share_string(big_linebreak(xverb_string," ",77)); return xverb_string; } string adverb_string=(string)soul_master()->get_adverb_string(); string get_adverb_string() { if(adverb_string) return adverb_string; adverb_string=implode_nicely(sort_words(m_indices(adverbs))); adverb_string=share_string(big_linebreak(adverb_string," ",77)); return adverb_string; } string xadverb_string; string get_xadverb_string() { if(xadverb_string) return xadverb_string; xadverb_string=implode_nicely(sort_words(m_indices(xadverbs))); xadverb_string=share_string(big_linebreak(xadverb_string," ",77)); return xadverb_string; } int current_flag; int see_names; object *people; /* set_flag: * Sets current_flag, checks if names should show or not. * Used from parse() */ void set_flag(int flag) { current_flag=flag; see_names=!is_dark() || (flag & FLAG_SOUND); } /* obj(), pron() and poss() are used by fix_msg */ string obj(object who,object active) { if(who==active) { if(who==this_player()) { return "yourself"; }else{ return "you"; } }else{ if(see_names) { if(who==this_player()) { return (string)who->query_objective()+"self"; }else{ return (string)who->query_name(); } }else{ return "Someone"; } } } string pron(object who,object active) { if(who==active) { if(who==this_player()) { return "you"; }else{ return "you"; } }else{ if(see_names) { return (string)who->query_name(); }else{ return "Someone"; } } } string poss(object who,object active) { string foo; if(who==active) { if(who==this_player()) { return "your own"; }else{ return "your"; } }else{ if(see_names) { if(who==this_player()) { return (string)who->query_possessive()+" own"; }else{ foo=(string)who->query_name(); if(foo[-1]=='s') return foo+"'"; return foo+"'s"; } }else{ return "Someone's"; } } } /* string fix_msg(string s,mixed *persons,object active) * * This function does the actuall replacing of tokes agains real names. * Input: * s: The string to do the replacing on. * persons: Contains the persons around ordered as follows: * ({ * ({ 1st person(s) }), * ({ 2nd person(s) }), * ({ 3rd person(s) }), * }) * No limitations are imposed, you can have any amount of groups * of people, and every group can contain any amount of people. * Having one person in several groups will not make much sense though. * active: The person who will hear this message. Can be zero to indicate * any person outside the groups. */ string fix_msg(string s,mixed *persons,object active) { string *t,*t2,tmp; int e,d,p; #if DEBUG>3 write("fix_msg '"+s+"' "+show_value(persons)+".\n"); #endif t=my_explode(s,"\b"); for(e=1;e<sizeof(t);e++) { #if DEBUG>3 write("fixing part "+e+" '"+t[e]+"'.\n"); #endif if(sscanf(t[e],"%dPRON%s",p,tmp)==2 || (sscanf(t[e],"%dpron%s",p,tmp)==2 && -1!=member_array(active,persons[p-1]))) { p--; t[e]=implode_nicely(MAP(persons[p],pron,active))+tmp; }else if(sscanf(t[e],"%dPOSS%s",p,tmp)==2 || (sscanf(t[e],"%dposs%s",p,tmp)==2 && -1!=member_array(active,persons[p-1]))){ p--; t[e]=implode_nicely(map_array(persons[p],"poss",this_object(),active))+tmp; }else if(sscanf(t[e],"%dOBJ%s",p,tmp)==2 || (sscanf(t[e],"%dobj%s",p,tmp)==2 && -1!=member_array(active,persons[p-1]))){ p--; t[e]=implode_nicely(MAP(persons[p],obj,active))+tmp; }else if(sscanf(t[e],"%dpron%s",p,tmp)==2){ p--; if(sizeof(persons[p])==1) { t[e]=persons[p][0]->query_pronoun()+tmp; }else{ t[e]="they"+tmp; } }else if(sscanf(t[e],"%dposs%s",p,tmp)==2){ p--; if(sizeof(persons[p])==1) { t[e]=persons[p][0]->query_possessive()+tmp; }else{ t[e]="their"+tmp; } }else if(sscanf(t[e],"%dobj%s",p,tmp)==2){ p--; if(sizeof(persons[p])==1) { if(persons[p][0]==this_player()) t[e]=this_player()->query_objective()+"self"+tmp; else t[e]=persons[p][0]->query_objective()+tmp; }else{ t[e]="them"+tmp; } }else if(sscanf(t[e],"\b%dIS%s",p,tmp)){ p--; if(member_array(active,persons[p])>=0 && sizeof(persons[p])==1) t[e]="is"+tmp; else t[e]="are"+tmp; }else{ t[e]="\b"+t[e]; } } #if DEBUG>3 write("fix_msg returned '"+implode(t,"")+"'.\n"); #endif return implode(t,""); } /* * This is a basic structure in this version of the soul. It is the general * description form for an output message. All lines of commands to the soul * will produce this data before it parsed and outputted. * * FEEL= * ({ * ({ * ({ ({1st persons}),({2nd persons}),.... }), * ({ 1st p message, 2nd p message, .... }), * flags, * ({ ob, fun }), * }), * * ({ * ({ ({1st persons}),({2nd persons}),.... }), * ({ 1st p message, 2nd p message, .... }), * flags, * ({ ob, fun }), * }), * * ..... * }) * * If you know C, maybe this is clearer: * * typedef string message; * typedef object *group; * struct sent * { * group *persons; * message *messages; * int flag; // see FLAGS_ above * }; * struct sent *FEEL; * * The struct sent may (will probably) be expanded in the future. * */ /* parse: * A lot of feelings will begin with \b1OBJ, this function will prevent * repeating \b1OBJ a lot by merging several sentences beginning with * \b1OBJ to one. Example: it would transform "Profezzorn smiles. Profezzorn * chuckles. Profezzorn laughs." to "Profezzorn smiles, chuckles and laughs." * * This function also calls fix_msg on all the strings. ie. the returned * string should be ready to send to the player who. * * The arguments are: * a FEEL structure * the object who, the person who will see this message. * some preparsed mappings produced by the function preparse() below */ string parse(mixed *feel,object who,mapping *preparsed) { int e,cnt; string *msg,*sent,tmp,lastname; object *lastobj; mixed *groups; msg=({}); #if DEBUG write("parse : "+show_value(feel)+" for "+who->query_real_name()+".\n"); #endif sent=lastobj=0; cnt=1; for(e=0;e<sizeof(feel);e++) { if(tmp=preparsed[e][who]) { set_flag(feel[e][2]); groups=feel[e][0]; if(is_dark()) { /* everybody knows if there is sound involved */ if(!(current_flag & FLAG_SOUND)) { if(-1==member_array(who,groups[0])) { /* second only person knows if it is physical */ if(!(current_flag & FLAG_TOUCH) || -1==member_array(who,groups[1])) continue; } } } if(sscanf(tmp,"\b1PRON %s",tmp)) { tmp=fix_msg(tmp,groups,who); if(lastobj && eq(lastobj,groups[0])) { if(sent[sizeof(sent)-1]==tmp) { cnt++; }else{ sent[sizeof(sent)-1]+=nr_times(cnt); sent+=({tmp}); cnt=1; } }else{ if(sent) { msg+=({lastname+" "+implode_nicely(sent)+nr_times(cnt)+"."}); cnt=1; } sent=({tmp}); lastobj=groups[0]; lastname=capitalize(fix_msg("\b1PRON",groups,who)); } }else{ if(sent) { msg+=({lastname+" "+implode_nicely(sent)+nr_times(cnt)+"."}); cnt=1; } msg+=({capitalize(fix_msg(tmp,groups,who))+"."}); lastobj=0; } } } if(sent) { msg+=({lastname+" "+implode_nicely(sent)+nr_times(cnt)+"."}); cnt=1; } return implode(msg," "); } /* Preparse: * The function parse() needs to know quickly which message the object who * is to have. This function makes mappings of the type: * ({ * ([ person: message, ... ]), * ([ person: message, ... ]), * }) * Where each mapping corresponds to an array in the FEEL structure. * */ mapping *preparse(mixed *feel) { int e,d,q; mapping tmp,*ret,*tmp3; mixed *groups; object *group; string msg; #if DEBUG>3 write("Preparse "+show_value(feel)+".\n"); #endif ret=allocate(sizeof(feel)); for(e=0;e<sizeof(feel);e++) { tmp=([]); groups=feel[e][0]; for(d=sizeof(groups)-1;d>=0;d--) { msg=feel[e][1][d]; group=groups[d]; for(q=sizeof(group)-1;q>=0;q--) tmp[group[q]]=msg; } tmp[0]=msg; ret[e]=tmp; } return ret; } /* do magic: * this function is called from reduce_verb, it is meant to do some generic * basic action. data[0] is a string that should form the messages for the * FEEL structures after the basic replacements and stuff has been done. * data[1] is a mapping that can contain the following information: * * ([ * "prep":" to", // preposition: probably at, to or before * "flags":FLAG_SOUND|FLAG_OFFENSE, // See definitions for FLAG_* above * "func",({ ob, func, data }), // may be extended in the future * ]) * There may be more properties for feelings in the future. * do_magic() returns a FEEL struct. */ /* small routine for do_magic() */ private string quote(string s) { return "'"+s+"'"; } mixed *do_magic(mixed *cond) { string st_msg,nd_msg,rd_msg,tmp; mapping xdata; mixed *data; #if DEBUG>2 write("do_magic: "+show_value(cond)+"\n"); #endif data=cond[C_DATA]; st_msg=data[0]; if(sizeof(data)>1) xdata=data[1]; else xdata=([]); if(sizeof(cond[C_ADV])) st_msg=REPLACE(st_msg," \bADV"," "+implode_nicely(cond[C_ADV])); else st_msg=REPLACE(st_msg," \bADV",""); if(sizeof(cond[C_ADJ])) st_msg=REPLACE(st_msg," \bADJ"," "+implode_nicely(cond[C_ADJ])); else st_msg=REPLACE(st_msg," \bADJ",""); if(sizeof(cond[C_BODY])) st_msg=REPLACE(st_msg," \bWHERE"," "+implode_nicely(cond[C_BODY])); else st_msg=REPLACE(st_msg," \bWHERE",""); tmp=xdata["prep"]; if(!tmp) tmp=" at"; if(sizeof(cond[C_WHO])) { st_msg=REPLACE(st_msg," \bAT",tmp+" \b2OBJ"); }else{ st_msg=REPLACE(st_msg," \bAT",""); } st_msg=REPLACE(st_msg," \bTEXT", implode_nicely(MAP(cond[C_MSGS],quote,0))); st_msg=REPLACE(st_msg," \bMSG"," "+implode(cond[C_MSGS]," ")); if(sscanf(st_msg,"%s\b|%s",st_msg,nd_msg)) { if(sscanf(nd_msg,"%s\b|%s",nd_msg,rd_msg)) { nd_msg=REPLACE(nd_msg,"\b$","s"); rd_msg=REPLACE(rd_msg,"\b$","s"); }else{ rd_msg=nd_msg=REPLACE(nd_msg,"\b$","s"); } }else{ nd_msg=rd_msg=REPLACE(st_msg,"\b$","s"); } st_msg=REPLACE(st_msg,"\b$",""); if(sscanf(st_msg,"%s\b2",tmp) && !sizeof(cond[C_WHO])) { notify_fail("You need to do '"+cond[C_VERB]+"' to someone.\n"); return 0; } return ({ ({ ({ ({ this_player() }), cond[C_WHO], SUB_ARRAYS(people,({this_player()})+cond[C_WHO]) }), ({ st_msg, nd_msg, rd_msg }), xdata["flags"], xdata["func"], }) }); } /* reduce verb should return an array on FEEL form */ /* Data is originally from the mapping describing verbs and * cond is a CONDITION structure (see above) */ mixed *reduce_verb(mixed cond) { mixed tmp,ptr,tmp2; mixed data; #if DEBUG>2 write("reduce_verb: "+show_value(cond)+"\n"); #endif data=cond[C_DATA]; if(!pointerp(data)) data=cond[C_DATA]=({data}); if(pointerp(data[0])) { return data; }else if(stringp(data[0])){ switch(data[0]) { /* this adds default to the COND structure * usage: ({"default", ([ C_ADV : ({ defaultadverbs }), ... ]), data }) * reduce_verb is then called with COND modified and data as the new data. */ case "default": for(tmp=0;tmp<sizeof(cond);tmp++) { if(data[1][tmp]) { if(!cond[tmp]) { cond[tmp]=data[1][tmp]; }else if((pointerp(cond[tmp]) && !sizeof(cond[tmp]))){ if(pointerp(data[1][tmp])) { cond[tmp]=data[1][tmp]; }else{ cond[tmp]=({data[1][tmp]}); } } } } cond[C_DATA]=data[2]; return reduce_verb(cond); /* report an error. * this might be used to idicate that a feeling NEEDS an adverb or something. * usage: ({"error","<mortal message>","<wizard message>"}) * if you just give ({"error"}) the standard error will be given. */ case "error": switch(sizeof(data)) { default: case 3: inform_error(data[1],data[2]); break; case 2: inform_error(data[1],0); break; case 1: } return 0; /* Irregular messages. * this is used to give different messages to 1st, 2nd and 3rd persons. * usage: ({"irregular", 1st_person_data, 2nd_person_data, ....}) * reduce_verb is called for each data and then compiled into one feeling */ case "irregular": tmp2=({}); for(tmp=1;tmp<sizeof(data);tmp++) { cond[C_DATA]=data[tmp]; tmp2+=reduce_verb(cond); } for(tmp=1;tmp<sizeof(tmp[0][0]);tmp++) { tmp2[0][0][tmp]=tmp2[ MIN(tmp,sizeof(tmp2)) ][0][tmp]; tmp2[0][1][tmp]=tmp2[ MIN(tmp,sizeof(tmp2)) ][1][tmp]; } return tmp2[0..0]; /* set flags * usage: ({"flags",[ FLAG_SOUND | FLAG_TOUCH | FLAG_OFFENSE ],data}) * flags are added _after_ reduce_verb has been called with the data * see the defines for FLAG_* above */ case "flags": cond[C_DATA]=data[2]; tmp=reduce_verb(cond); for(tmp2=0;tmp2<sizeof(tmp);tmp2++) { while(sizeof(tmp2[tmp])<3) tmp2[tmp]+=({0}); tmp2[tmp][2]=data[1]; } return tmp; /* random * usage: ({"rnd", data1, data2, data3, ... }); * reduce_verb will bee called with one of the data* */ case "rnd": tmp=random(sizeof(data)-1); break; /* self_p - did we do it to ourself? * usage: ({"self_p", data_if_it_was_someone_else, data_if_it_was_ourself}) */ case "self_p": tmp=sizeof(cond[C_WHO])==1 && cond[C_WHO][0]==this_player(); break; /* these check the corresponding index in COND and if it is set, use the first * data, else the second * usage: ({"*_p",not_true_data,true_data}) */ case "who_p": tmp=sizeof(cond[C_WHO]); break; case "adv_p": tmp=sizeof(cond[C_ADV]); break; case "adj_p": tmp=sizeof(cond[C_ADJ]); break; case "msg_p": tmp=sizeof(cond[C_MSGS]); break; case "body_p":tmp=sizeof(cond[C_BODY]);break; /* this is a dumb definition, but it works */ case "simple": data[0]="\b1PRON "+cond[C_VERB]+"\b$ \bADV \bAT"; /* fall through */ default: if(data[0][0]=='/') return reduce_verb((mixed *) call_other(data[0],data[1],data[2],cond)); /* This is a simple-form feeling, we do some magic here */ return do_magic(cond); } tmp++; if(tmp>=sizeof(data)) tmp=sizeof(data)-1; cond[C_DATA]=data[tmp]; return reduce_verb(cond); }else if(objectp(data[0])){ /* * If this function wish to affect cond it should call * previous_object()->reduce_verb(), ie. this function. */ /* * Also note that this function is NOT allowed to have any side * effects, as this feelings might not be done due to errors * while parsing, There is another functionality for doing side * effect functions. (See the 'kiss' verb) */ cond[C_DATA]=(mixed *)data[0]->reduce_verb(data[1],cond); return reduce_verb(cond); }else{ /* Feeling syntax error, someone screwed up */ inform_error("Sorry, the verb '"+cond[C_VERB]+"' doesn't work.\n", "unknown type to reduce_verb ("+show_value(data)+")\n"); return 0; } } /* msg: * This routine is not used by the soul, it provided because it can be * convenient to call from other commands. * Message is a message on basically the same form as data[0] in * do_magic, (or the base message form in the verbs mapping) * first is the first person ie. this_player() * second is the second person. * (both first and second can be several people) */ varargs string msg(string message,mixed first,mixed second) { string st_msg,nd_msg,rd_msg; mixed *tmp; object o; int e; if(sscanf(message,"%s\b|%s",st_msg,nd_msg)) { if(sscanf(nd_msg,"%s\b|%s",nd_msg,rd_msg)) { nd_msg=REPLACE(nd_msg,"\b$","s"); rd_msg=REPLACE(rd_msg,"\b$","s"); }else{ rd_msg=nd_msg=REPLACE(nd_msg,"\b$","s"); } }else{ nd_msg=rd_msg=REPLACE(st_msg=message,"\b$","s"); } st_msg=REPLACE(st_msg,"\b$",""); if(!first) first=({this_player()}); if(!second) second=({}); if(!pointerp(second)) second=({second}); if(!pointerp(first)) first=({first}); tmp=({first,second}); for(e=0;e<sizeof(first);e++) tell_object(o,fix_msg(st_msg,tmp,first[e])); for(e=0;e<sizeof(second);e++) tell_object(o,fix_msg(nd_msg,tmp,second[e])); tell_room(environment(first[0]),fix_msg(rd_msg,tmp,0),first+second); } /* This function outputs a FEEL struct to the players involved. * Normally called from do_feel() */ void output_feeling(mixed *feel) { int e,d,c; mapping done; mixed *preparsed,*group; string tmp; done=([]); preparsed=preparse(feel); for(e=0;e<sizeof(feel);e++) { for(d=0;d<sizeof(feel[e][0]);d++) { group=feel[e][0][d]; for(c=0;c<sizeof(group);c++) { if(done[group[c]]) continue; done[group[c]]=1; tmp=parse(feel,group[c],preparsed); if(strlen(tmp)) tell_object(group[c],tmp+"\n"); } } } } /* pron: name you he/she/it they poss: name's your his/her/its their obj: name you him/her/them them */ int help(string s) { string res; if(!s) return 0; switch(s) { case "feelings": more("General commands available:\n"); more(get_verb_string()); if(m_sizeof(xverbs)) { more("Extra commands available:\n"); more(get_xverb_string()); } more("Persons and adverbs can be shorted to shorted unique prefix.\n"); more("See also: help adverbs and help feeling list\n"); more_flush(); return 1; case "adverbs": more("Adverbs that can be used together with feeling-commands:\n"); more(get_adverb_string()); more_flush(); return 1; case "soul version": write("Soul version 2.0, written by hubbe@lysator.liu.se.\n"); return 1; } } /* get the name of a player/monster * used by the parser */ string get_name(object o) { return lower_case((string)o->query_name()); } /* is it a living object ? * used by the parser */ int isliving(object o) { return !!(o->query_living() && o->short()); } /* prefix: * is pr a 'shortest unique prefix' for any of the strings in * the array dum? If so return that index from dum. If none matched * return 0, if several matched, do a notify_fail using errm and * a list of all matching strings and then return -1 */ mixed prefix(string *dum,string pr,string errm) { string *q; /* regexp gives nasty messages unless we remove these */ pr=REPLACE(pr,"(",""); pr=REPLACE(pr,")",""); pr=REPLACE(pr,"[",""); pr=REPLACE(pr,"]",""); pr=REPLACE(pr,"//",""); q=regexp(dum,"^"+pr); if(sizeof(q)==1) return q[0]; if(!sizeof(q)) return 0; notify_fail(errm+"\n"+ big_linebreak(implode_nicely(q,"or"),"",78)+"\n"); return -1; } /* which number: * returns a string that describes the order of the number * ie 1 -> first, 2-> second etc. etc. */ string which_number(int x) { switch(x) { case 0: return "zeroth"; case 1: return "first"; case 2: return "second"; case 3: return "third"; case 4: return "fourth"; case 5: return "fifth"; case 6: return "sixth"; case 7: return "seventh"; case 8: return "eighth"; case 9: return "ninth"; case 10: return "tenth"; case 11: return "eleventh"; case 12: return "twelvth"; case 13: return "thirteenth"; case 14: return "fourteenth"; case 15: return "fifteenth"; case 16: return "sixteenth"; case 17: return "seventeenth"; case 18: return "eightteenth"; case 19: return "nineteenth"; default: switch(x%100) { case 1: case 21: case 31: case 41: case 51: case 61: case 71: case 81: case 91: return x+"st"; case 2: case 22: case 32: case 42: case 52: case 62: case 72: case 82: case 92: return x+"nd"; case 3: case 23: case 33: case 43: case 53: case 63: case 73: case 83: case 93: return x+"th"; default: return x+"th"; break; } } } mixed *brokendown_data; /* This function does all the parsing! * it parses the words in the string text, and returns an array of * COND structs, one for each sentence. */ mixed *webster(string text) { string *words,tmp,*tmp2,message; int e,u,except; object ob,*oldwho; mixed p,*verbdata; mapping persons; string _how,current_word; mixed *cond; brokendown_data=({}); oldwho=({}); cond=({0,0,0,({}),({}),({}),({}),({})}); /* remove double spaces */ words=my_explode(text," ")-({""}); /* find all present livings */ people=FILTER(all_inventory(environment(this_player())),isliving,0); for(e=0;e<sizeof(words);e++) { current_word=words[e]; #if DEBUG write("Webster: words["+e+"]=\""+current_word+"\"\n"); #endif if(current_word[strlen(current_word)-1]==',') current_word=words[e]=current_word[0..strlen(current_word)-2]; if(how[current_word]) { _how=current_word; continue; } if(current_word[0]=='"') { message=current_word[1..100000]; for(e++;message[strlen(message)-1]!='"' && e<sizeof(words);e++) message+=" "+words[e]; if(message[strlen(message)-1]=='"') { message=message[0..strlen(message)-2]; e--; } cond[C_MSGS]+=({message}); continue; } switch(current_word) { /* null words */ case "and": case "&": case "": case "at": case "to": case "before": case "in": case "on": case "the": break; /* ourselves */ case "me": case "myself": case "I": if(except) cond[C_WHO]-=({this_player()}); else cond[C_WHO]+=({this_player()}); break; /* the people from the last sentence */ case "them": case "him": case "her": case "it": if(current_word=="them") { if(sizeof(oldwho)<2) return notify_fail("Who?\n"),0; }else{ if(sizeof(oldwho)!=1 || ((mixed)oldwho[0]->query_objective())!=current_word) return notify_fail("Who?\n"),0; } if(except) cond[C_WHO]-=oldwho; else cond[C_WHO]+=oldwho; break; /* everybody in the room */ case "all": case "everybody": case "everyone": if(except) { cond[C_WHO]=({}); }else{ cond[C_WHO]+=SUB_ARRAYS(people,({this_player()})) ; } break; /* negate persons */ case "except": case "but": if(!except && !sizeof(cond[C_WHO])) { notify_fail("That '"+current_word+"' doesn't look grammatically right there.\n"); return 0; } except=!except; /* watch those double negations */ break; /* magical adverb */ case "plainly": cond[C_ADV]=({""}); break; /* all the reset */ default: if((p=verbs[current_word]) || (p=xverbs[current_word])) { /* here I must add some magic for when there is a person * with the same name in the room */ if(cond[1]) { brokendown_data+=({cond}); oldwho=cond[C_WHO]; except=0; } cond=({0,current_word,p,({}),({}),({}),({}),({})}); break; } /* find their names */ persons=mkmapping(MAP(people,get_name,0),people); if(ob=persons[current_word]) { if(except) cond[C_WHO]-=({ob}); else cond[C_WHO]+=({ob}); break; } if(adverbs[current_word] || xadverbs[current_word]) { if(_how) { cond[C_ADV]+=({_how+" "+current_word}); _how=0; }else{ cond[C_ADV]+=({current_word}); } break; } if(p=bodydata[current_word]) { cond[C_BODY]+=({p}); break; } if(p=prefix(m_indices(persons),current_word,"Who do you mean?")) { if(p==-1) { failed_action= ({ e?implode(words[0..e-1]," "):"", current_word, implode(words[e+1..sizeof(words)]," ") }); return 0; } words[e]=get_name(persons[p]); if(except) cond[C_WHO]-=({persons[p]}); else cond[C_WHO]+=({persons[p]}); break; } u=e; tmp=current_word; p=prefix(m_indices(adverbs),tmp,"What adverb was that?"); if(p==-1) p=prefix(m_indices(xadverbs),tmp,"What adverb was that?"); while(p==-1 && u+1<sizeof(words)) { u++; tmp+=" "+words[u]; p=prefix(m_indices(adverbs),tmp,"What adverb was that?"); if(p==-1) p=prefix(m_indices(xadverbs),tmp,"What adverb was that?"); } if(p) { if(p==-1) { failed_action= ({ e?implode(words[0..e-1]," "):"", current_word, implode(words[u+1..sizeof(words)]," ") }); return 0; } tmp2=explode(p," "); for(u=0;tmp2 && u<sizeof(tmp2) && e<sizeof(words);u++) { if(tmp2[u]==words[e]) { e++; continue; } if(tmp2[u][0..strlen(words[e])-1]==words[e]) e++; break; } e--; if(_how) { cond[C_ADV]+=({_how+" "+p}); _how=0; }else if(p=="plainly"){ cond[C_ADV]=({""}); }else{ cond[C_ADV]+=({p}); } break; } if(e) notify_fail("The "+which_number(e+1)+" word in that sentence doesn't make sense to me.\n"); return 0; } } if(!cond[1]) { notify_fail("No verb?\n"); return 0; } last_action=implode(words," "); brokendown_data+=({cond}); return brokendown_data; } int do_feel(string str) { int e; mixed *feel; #ifndef LPC4 str=query_verb()+(str?" "+str:""); #endif if(!webster(str)) return 0; feel=({}); for(e=0;e<sizeof(brokendown_data);e++) { mixed *tmp; tmp=reduce_verb(brokendown_data[e]); if(!tmp) return 0; brokendown_data[e][C_DATA]=tmp; feel+=tmp; } output_feeling(feel); return 1; } /* Takes an array of verbs, only removes extra verbs */ void remove_verb(string *v) { int e; for(e=0;e<sizeof(v);e++) xverbs=m_delete(xverbs,v[e]); xverb_string=0; } #ifdef LPC4 void add_verbs(mapping v) { xverbs=v&xverbs; xverb_string=0; } #else add_verbs(mapping v) { remove_verb(m_indices(xverbs) & m_indices(v)); xverbs+=v; xverb_string=0; } #endif mapping query_xverbs() { return xverbs; } #if 1 /* these functions dumps feelings to file in a parsable format, * you may ignore them, they were only used when I ported feelings * to the 2.0 format from the old soul */ #define DUMPFILE "/open/feelingdump" string dump_flags(int flag) { string *tmp; tmp=({}); if(flag & FLAG_SOUND) tmp+=({"FLAG_SOUND"}); if(flag & FLAG_TOUCH) tmp+=({"FLAG_TOUCH"}); if(flag & FLAG_OFFENSE) tmp+=({"FLAG_OFFENSE"}); if(!sizeof(tmp)) return "0"; return implode(tmp,"|"); } string dump_one_xverb(mixed a) { mixed tmp,tmp2,tmp3;; if(!pointerp(a)) return show_value(a); if(stringp(a[0])) { switch(a[0]) { case "flags": return "({\"flags\","+dump_flags(a[1])+","+dump_one_xverb(a[2])+"})"; case "default": tmp=({}); if(a[1][C_METAVERB]) tmp+=({"C_METAVERB:"+show_value(a[1][C_METAVERB])}); if(a[1][C_VERB]) tmp+=({"C_VERB:"+show_value(a[1][C_VERB])}); if(a[1][C_DATA]) tmp+=({"C_DATA:"+show_value(a[1][C_DATA])}); if(a[1][C_WHO]) tmp+=({"C_WHO:"+show_value(a[1][C_WHO])}); if(a[1][C_ADV]) tmp+=({"C_ADV:"+show_value(a[1][C_ADV])}); if(a[1][C_ADJ]) tmp+=({"C_ADJ:"+show_value(a[1][C_ADJ])}); if(a[1][C_MSGS]) tmp+=({"C_MSGS:"+show_value(a[1][C_MSGS])}); if(a[1][C_BODY]) tmp+=({"C_BODY:"+show_value(a[1][C_BODY])}); if(sizeof(tmp)) return "({\"default\",(["+implode(tmp,",")+"]),"+dump_one_xverb(a[2])+"})"; else return dump_one_xverb(a[2]); case "irregular": case "rnd": case "who_p": case "self_p": case "adv_p": case "adj_p": case "msg_p": case "body_p": return "({"+show_value(a[0])+","+ implode(MAP(a[1..1000],dump_one_xverb,0),",")+"})"; default: if(sizeof(a)==2 && mappingp(a[1]) && a[1]["flags"]) { tmp=a[1]+([]); tmp2=tmp["flags"]; tmp=show_value(m_delete(tmp,"flags")); return "({"+show_value(a[0])+ ",([\"flags\":"+dump_flags(tmp2)+","+(tmp[2..10000])+"})"; } } } return show_value(a); } void do_dump_verbs(mapping v) { int e; mixed *a; mixed *b; a=m_indices(v); b=m_values(v); if(sizeof(a)>60) { call_out("do_dump_verbs",2,mkmapping(a[61..10000],b[61..10000])); a=a[0..60]; b=b[0..60]; for(e=0;e<sizeof(a);e++) write_file(DUMPFILE," "+show_value(a[e])+":"+dump_one_xverb(b[e])+",\n"); }else{ for(e=0;e<sizeof(a);e++) write_file(DUMPFILE," "+show_value(a[e])+":"+dump_one_xverb(b[e])+",\n"); write_file(DUMPFILE,"])\n"); write("Done.\n"); } } void dump_verbs(mapping v) { if(!v) v=xverbs; write("Dumping to "+DUMPFILE+".\n"); rm(DUMPFILE); write_file(DUMPFILE,"([\n"); do_dump_verbs(v); } #endif #define OLD_SIMP 0 #define OLD_DEFA 1 #define OLD_DEUX 2 #define OLD_PERS 3 #define OLD_QUAD 4 #define OLD_PREV 5 #define OLD_SHRT 6 #define OLD_PHYS 7 #define OLD_FULL 8 /* this function converts a v1.x feeling to a v2.0 feeling */ string convert_simple_string(string a) { a=REPLACE(a,"\nAT","\bAT"); a=REPLACE(a,"\nHOW","\bADV"); a=REPLACE(a,"\nWHAT","\bMSG"); a=REPLACE(a,"\nMSG","\bTEXT"); a=REPLACE(a,"\nWHERE","\bWHERE"); a=REPLACE(a,"$","\b$"); a=REPLACE(a,"\nIS","\bIS"); a=REPLACE(a,"\nWHO","\b2OBJ"); a=REPLACE(a,"\nPOSS","\b2POSS"); a=REPLACE(a,"\nTHEIR","\b2poss"); a=REPLACE(a,"\nMY","\b1OBJ"); a=REPLACE(a,"\nOBJ","\b2obj"); a=REPLACE(a,"\nYOUR","\b1POSS"); a=REPLACE(a,"\bSUBJ","\b2pron"); return a; } mixed convert_feeling(string verb,mixed *verbdata) { string a; mixed data; mapping xdata; xdata=([]); switch(verbdata[0]) { case OLD_DEFA: /* a="\b1PRON "+verb+"\b$ \bADV \bAT"; */ a="simple"; case OLD_PREV: if(!a) a="\b1PRON "+verb+"\b$"+verbdata[2]+" \b2OBJ \bADV"; case OLD_PHYS: if(!a) { xdata["flags"]|=FLAG_TOUCH; a="\b1PRON "+verb+"\b$"+verbdata[2]+" \b2OBJ \bADV \bWHERE"; } case OLD_SHRT: if(!a) a="\b1PRON "+verb+"\b$"+verbdata[2]+" \bADV"; case OLD_SIMP: if(!a) a=convert_simple_string("\b1PRON"+verbdata[2]); if(sizeof(verbdata)>3 && verbdata[3]!=" at") xdata["prep"]=verbdata[3]; if(!m_sizeof(xdata)) data=a; else data=({a,xdata}); break; case OLD_PERS: data=({"who_p", "\b1PRON"+convert_simple_string(verbdata[2]), "\b1PRON"+convert_simple_string(verbdata[3]) }); break; case OLD_DEUX: data=({"irregular", "\b1PRON"+convert_simple_string(verbdata[2]), "\b1PRON"+convert_simple_string(verbdata[3]) }); break; case OLD_QUAD: data=({"who_p", ({"irregular", "\b1PRON"+convert_simple_string(verbdata[2]), "\b1PRON"+convert_simple_string(verbdata[3]) }), ({"irregular", "\b1PRON"+convert_simple_string(verbdata[4]), "\b1PRON"+convert_simple_string(verbdata[5]) }) }); break; case OLD_FULL: data=({"who_p", ({"flag_p", ({"irregular", "\b1PRON"+convert_simple_string(verbdata[2]), "\b1PRON"+convert_simple_string(verbdata[4]), }), ({"irregular", "\b1PRON"+convert_simple_string(verbdata[5]), "\b1PRON"+convert_simple_string(verbdata[7]), }), }), ({"flag_p", ({"irregular", "\b1PRON"+convert_simple_string(verbdata[8]), "\b1PRON"+convert_simple_string(verbdata[9]), "\b1PRON"+convert_simple_string(verbdata[10]), }), ({"irregular", "\b1PRON"+convert_simple_string(verbdata[11]), "\b1PRON"+convert_simple_string(verbdata[12]), "\b1PRON"+convert_simple_string(verbdata[13]), }), }), }); } if(verbdata[1] && sizeof(verbdata[1])) { verbdata=verbdata[1]; /* convert defaults */ data=({"default",([]),data}); if(verbdata[0]) data[1][C_ADV]=verbdata[0]; if(sizeof(verbdata)>1 && verbdata[1]) data[1][C_MSGS]=verbdata[1]; if(sizeof(verbdata)>2 && verbdata[2]) data[1][C_BODY]=verbdata[2]; } return data; } /* this is the v1.0 compatible add_verb, * it converts everything to the new format and then adds it. */ void add_verb(mapping v) { int e; mixed *a,*b; write("Please tell people to convert to the new soul format.\n"); a=m_indices(v); b=m_values(v); if(sizeof(a)>60) { call_out("add_verb",2,mkmapping(a[61..10000],b[61..10000])); a=a[0..60]; b=b[0..60]; } for(e=0;e<sizeof(a);e++) b[e]=convert_feeling(a[e],b[e]); add_verbs(mkmapping(a,b)); } /* The mudlib might require this */ #ifndef LPC4 void move(object x) { move_object(this_object(),x); } #endif int get() { return 1; } int drop() { return 1; } int id(string s) { return s=="soul"; } int query_prevent_shadow() { return 1; } int query_allow_shadow() { return 0; } void long() { write("You can't see it.\n"); } #ifdef LPC4 void init() { add_action("%s",do_feel,-10000); } #else /* LPC4 */ void init() { add_action("do_feel","",1); remove_call_out("dig"); call_out("dig",5+random(20)); } /* The soul blocks _a_lot_ of actions. Other objects might want a chanse * to look at the input before the soul does. Therefor we dig ourself * down into the inventory and lie last. */ void dig() { if(present("soul 2",environment())) return; #ifndef NATIVE while(this_object() && next_inventory(this_object())) move_object(next_inventory(this_object()),environment(this_object())); #else while(this_object() && next_inventory(this_object())) next_inventory(this_object())->move(environment(this_object())); #endif } #endif /* LPC4 */