| // MorgenGrauen MUDlib |
| // |
| // thing/commands.c -- thing description |
| // |
| // $Id: commands.c 9514 2016-02-23 20:33:09Z Gloinson $ |
| // |
| // Aus Regenbogen MUDLib |
| // aus Gueldenland/Wunderland MUDlib (Morgengrauen MUDlib) |
| // |
| // P_COMMANDS data-structure: |
| // |
| // AddCmd(verb,fun1,1); |
| // AddCmd(verb+syn1a|syn1b&syn2a|syn2b|syn2c,fun2, |
| // error1_notify|error2_notify^error2_write); |
| // --> |
| // ([verb:({fun1,fun2}); // funs |
| // ({1,({error1_notify, error2_write^error2_say, 1})}); // flags |
| // ({0,({({syn1a,syn1b}),({syn2a,syn2b,syn2c})})}); // rules |
| // 0]) // IDs |
| // |
| // Rules: ({<Regelsatz fuer fun1>, ({<1. Synonymgruppe>, |
| // <2. Synonymgruppe, ...}), ...}) |
| // Flags: ({<Flag fuer fun1>, ({<Fehlermeldung 1. Synonymgruppe>, ... , |
| // [, Index fuer write anstatt notify_fail]}), |
| // ... }) |
| // IDs: 0 oder ({<ID fuer fun1>}) oder ({0, <ID fuer fun2>}) ... |
| // |
| // IDEA: save no 0s in rules/flags if possible (as in IDs) |
| // (adressing especially old-style-AddCmd-MUDs) |
| #pragma strict_types |
| #pragma save_types |
| #pragma range_check |
| #pragma no_clone |
| #pragma pedantic |
| |
| #include <moving.h> |
| #include <thing/language.h> |
| #include <exploration.h> |
| |
| #define NEED_PROTOTYPES |
| #include <thing/description.h> |
| #include <thing/commands.h> |
| #undef NEED_PROTOTYPES |
| |
| #ifdef DBG |
| #undef DBG |
| #endif |
| #define DBG(x) printf("Object %O tmpstr=%s\n", explode(object_name(this_object()),"#")[1], x); |
| |
| private nosave mapping added_cmds; |
| |
| protected void create() |
| { |
| } |
| |
| protected void create_super() { |
| set_next_reset(-1); |
| } |
| |
| varargs void AddCmd(mixed cmd, mixed func, mixed flag, mixed cmdid) { |
| int i,j; |
| closure cl; |
| mixed *rule; |
| |
| // potentielle AddCmd mit Regel? |
| if(stringp(cmd)) { |
| // eine Regel? - aufsplitten |
| if((i=member(cmd,'&'))>0) { |
| // ... in Array mit Verknuepfungselementen |
| rule=explode(cmd[(i+1)..],"&"); |
| j=sizeof(rule); |
| // ... in Array mit Arrays mit Alternativelementen: |
| // "p|q&r|s" -> ({ ({"p","q"}), ({"r","s"}} }) |
| while(j--) |
| rule[j]=explode(rule[j],"|"); |
| |
| // Regeln von Kommandoverben abschneiden |
| cmd=cmd[0..(i-1)]; |
| } |
| // Kommandoverben extrahieren |
| cmd=explode(cmd,"|"); |
| |
| // Satz von Regeln existiert: Aufsplitten von Fehlermeldungen |
| if(rule) |
| if(stringp(flag)) { |
| mixed *fail; |
| // in einfaches Array mit jeweiligen Fehlermeldungen |
| fail=explode(flag,"|"); |
| j=0; |
| i=sizeof(fail); |
| while(j<i) { |
| // write - Fehlermeldung entdeckt - Position ggf. eintragen |
| if(member(fail[j],'^')>=0 && !intp(fail[<1])) |
| fail+=({j}); |
| if(member(fail[j],'@')>=0) { |
| int s; |
| flag=regexplode(fail[j], "@WE[A-SU]*[0-9]"); |
| s=sizeof(flag); |
| while((s-=2)>0) { |
| int tmpint; |
| tmpint=flag[s][<1]-'1'; |
| if(tmpint<0 || tmpint>j) |
| raise_error(sprintf( |
| "AddCmd: error-message %d contains out-of-bounds @WExx-rule.\n",j+1)); |
| } |
| } |
| j++; |
| } |
| // "Was?|Wie das?" -> ({"Was?","Wie das?"}) |
| // "Was?|Wie das?^|Womit das?|Worauf das?^@WER1 macht was." -> |
| // ({"Was?", |
| // "Wie das?^Womit das?", |
| // "Worauf das?^@WER1 macht was.",1}) |
| flag=sizeof(fail); |
| if(flag && flag<sizeof(rule)) |
| raise_error( |
| "AddCmd: number of error-messages does not match number of rules.\n"); |
| flag=fail; // ueberschreiben mit den parsefreundlichen Format |
| } else if(flag) |
| raise_error("AddCmd: rules exist but flags are not an error-string.\n"); |
| } // end if(stringp(cmd)) ... kein Regelstring vorhanden |
| |
| // kein Kommandoarray gewesen noch erzeugt? |
| if(!pointerp(cmd)) |
| raise_error("AddCmd: missing string/pointer-parameter for command.\n"); |
| |
| // Closure aus einem String erzeugen, wenn moeglich und sicher |
| // (function_exists() filtert unnoetigerweise auch reine "static" funs, |
| // die genaue Pruefung ueber functionlist() kostet jedoch zuviel) |
| if(stringp(func) && |
| (!extern_call() || function_exists(func,this_object())) && |
| closurep(cl=symbol_function(func,this_object()))) |
| func=cl; |
| |
| // jedes einzelne Verb mit seinen Regeln und Funktionen eintragen |
| i=sizeof(cmd); |
| if(!added_cmds) added_cmds=m_allocate(i,4); |
| while(i--) { |
| string str; |
| str=cmd[i]; |
| if(!func) |
| if(extern_call()) func=previous_object(); |
| else func=this_object(); |
| if(!member(added_cmds,str)) |
| added_cmds+=([str:allocate(0);allocate(0);allocate(0);0]); |
| // existierendes Verb ergaenzen |
| added_cmds[str,0]+=({func}); |
| added_cmds[str,1]+=({flag}); |
| added_cmds[str,2]+=({rule}); |
| // ggf. id in das ID-Mapping eintragen |
| if(cmdid) { |
| mixed *tmp; |
| j=sizeof((string*)added_cmds[str,0]); |
| tmp=added_cmds[str,3]||allocate(j); |
| if(sizeof(tmp)<j) tmp+=allocate(j-sizeof(tmp)); |
| tmp[<1]=cmdid; |
| added_cmds[str,3]=tmp; |
| } |
| } |
| } |
| |
| // Auswertung fuer ein Verb loeschen |
| varargs int RemoveCmd(mixed cmd, int del_norule, mixed onlyid) { |
| int ret; |
| |
| // Falls Magier das RemoveCmd falsch nutzen (z.B. analog zu AddCmd) |
| // wird das Regelsystem verwirrt. Da das del_norule nur int sein darf, |
| // gibt es hier eine gute Chance den Fehler abwaertskompatibel zu ent- |
| // decken. Damit Spieler den Fehler nicht mitbekommen, wird hier auf |
| // ein raise_error verzichtet, und statt dessen in ein Logfile ge- |
| // schrieben. |
| if (!intp(del_norule)) |
| { |
| log_file("REMOVE_CMD", |
| sprintf("\n-- %s --\nIllegal RemoveCommand() in Object [%O]:\n %O\n", |
| dtime(time()), this_object(), cmd)); |
| del_norule=0; |
| onlyid=0; |
| } |
| |
| if(!added_cmds || (!cmd && !del_norule && !onlyid)) |
| added_cmds=(mapping)0; |
| else { |
| int i, j; |
| mixed *rule, *flag, *fun, *delrule, *ids; |
| |
| if(stringp(cmd)) { |
| // Regeln entdeckt - Zerlegen (wie AddCmd) |
| if((i=member(cmd,'&'))>0) { |
| delrule=explode(cmd[(i+1)..],"&"); |
| j=sizeof(delrule); |
| while(j--) |
| delrule[j]=explode(delrule[j],"|"); |
| cmd=cmd[0..(i-1)]; |
| } |
| cmd=explode(cmd,"|"); |
| } else if(del_norule || onlyid) cmd=m_indices(added_cmds); |
| |
| if(!pointerp(cmd)) |
| raise_error("RemoveCmd: missing string/pointer-parameter.\n"); |
| i=sizeof(cmd); |
| |
| while(i--) { |
| // keine Regeln da und Regeln loeschen erlaubt: alles loeschen |
| if(!delrule && !del_norule && !onlyid) m_delete(added_cmds,cmd[i]); |
| else if(m_contains(&fun, &flag, &rule, &ids, added_cmds, cmd[i])) { |
| j=sizeof(fun); |
| while(j--) { |
| int k; |
| // DBG(rule[j]); |
| // Regeln nicht löschen und Regel? |
| if(!(del_norule && pointerp(rule[j])) && |
| // nur bestimmte ID löschen und ID passt nicht? |
| !(onlyid && (!pointerp(ids) || sizeof(ids)<=j || ids[j]!=onlyid)) && |
| // Löschregel existiert und passt nicht auf Regel? |
| !(delrule && (k=sizeof(rule[j]))!=sizeof(delrule))) { |
| // partielles Testen einer Löschregel ... |
| if(delrule) { |
| while(k--) |
| if(!sizeof(rule[j][k]&delrule[k])) break; |
| if(k>=0) continue; |
| } |
| // alles korrekt: Löschen! |
| // (Arraybereich durch leeres Array loeschen) |
| flag[j..j] = allocate(0); |
| fun[j..j] = allocate(0); |
| rule[j..j] = allocate(0); |
| if(ids) { |
| ids[j..j] = allocate(0); |
| if(!sizeof(ids-allocate(1))) ids=(mixed*)0; |
| } |
| ret++; |
| } |
| } // end while(j--) { |
| } |
| // Funktions/Regelliste update oder ggf. Kommando voellig loeschen |
| if(sizeof(rule)) { |
| added_cmds[cmd[i],0]=fun; |
| added_cmds[cmd[i],1]=flag; |
| added_cmds[cmd[i],2]=rule; |
| added_cmds[cmd[i],3]=ids; |
| } else m_delete(added_cmds,cmd[i]); |
| } |
| if(!sizeof(added_cmds)) added_cmds=(mapping)0; |
| } |
| return ret; |
| } |
| |
| // Ausfuehren samt geparstem Inputstring und getriggerten Parserergebnissen |
| static int _execute(mixed fun, string str, mixed *parsed) { |
| if(closurep(fun)) |
| return ((int)funcall(fun,str,&parsed)); |
| if(stringp(fun)) |
| return ((int)call_other(this_object(),fun,str,&parsed)); |
| return 0; |
| } |
| |
| #define CHECK_PRESENT 1 |
| #define CHECK_ID 2 |
| #define CHECK_PUTGETNONE 4 |
| #define CHECK_PUTGETDROP 8 |
| #define CHECK_PUTGETTAKE 16 |
| #define CHECK_PUTGET (CHECK_PUTGETNONE|CHECK_PUTGETDROP|CHECK_PUTGETTAKE) |
| |
| // Wert fuer Fehlschlag, Fallback-Wert der benutzten while-- - Schleifen |
| #define NOMATCHFOUND -1 |
| |
| // Regeln fuer ein (nun unwichtiges) Verb triggern |
| static int _process_command(string str, string *noparsestr, |
| mixed fun, mixed flag, mixed rule) { |
| mixed *parsed, *objmatches; |
| |
| // eine Regel ... auswerten ... |
| if(pointerp(rule)) { |
| int nrul; |
| parsed=objmatches=allocate(0); |
| int lastmatchpos=NOMATCHFOUND; |
| |
| // Abgleichen der gesplitteten Eingabe mit Regeln: |
| // vorwaerts durch die Synonymgruppen |
| int rs=sizeof(rule); |
| while(nrul<rs) { |
| int matchpos; |
| string *synonym; |
| mixed matchstr; |
| |
| matchpos=NOMATCHFOUND; |
| matchstr=0; |
| |
| // Synonyme extrahieren |
| int nrsynonyms=sizeof(synonym=rule[nrul]); |
| |
| // egal wie durch Synonyme bis Match - Abgleich mit Eingabe |
| while(nrsynonyms--) { |
| int tmppos = member(noparsestr,synonym[nrsynonyms]); |
| // ist Synonym im Eingabestring und kommt spaeter als vorheriges Synonym? |
| if(tmppos>=0 && tmppos>lastmatchpos) { |
| // Erfolg: merken der Position im Eingabestring und den matchenden String |
| matchpos=tmppos; |
| matchstr=noparsestr[tmppos]; |
| break; |
| } |
| } |
| |
| // kein Match durch Synonyme? Pruefe die @-Spezialvariablen. |
| if(matchpos == NOMATCHFOUND) { |
| int check_present; |
| // ist Abpruefen von ID/PRESENT in der Synonymgruppe verlangt |
| // bei present()/find_obs gleich Voraussetzung gueltiger TP mitprufen |
| if(member(synonym,"@ID")>=0) check_present=CHECK_ID; |
| if(this_player()) { |
| if(member(synonym,"@PRESENT")>=0) check_present|=CHECK_PRESENT; |
| else if(member(synonym,"@PUT_GET_NONE")>=0) |
| check_present|=CHECK_PUTGETNONE; |
| else if(member(synonym,"@PUT_GET_TAKE")>=0) |
| check_present|=CHECK_PUTGETDROP; |
| else if(member(synonym,"@PUT_GET_DROP")>=0) |
| check_present|=CHECK_PUTGETTAKE; |
| } |
| |
| if(check_present) { |
| // wir fangen hinter dem letzten Match an |
| int q_start=lastmatchpos+1; |
| int r_end=sizeof(noparsestr)-1; |
| |
| int range; |
| while((range=r_end-q_start)>=0) { |
| mixed tmpobj; |
| |
| // wie weit wollen wir zurückgehen? |
| if(range) |
| if(!(check_present&CHECK_PUTGET)) |
| range=range>2?2:range; // 3 Fragmente fuer @ID/@PRESENT (Adverb/Nr) |
| else if(range>4) |
| range=4; // 5 Fragmente fuer @PUT_XXX |
| |
| // und jetzt die Substrings pruefen |
| while(range>=0 && !matchstr) { |
| string tmpstr; |
| |
| // zu pruefenden String aus den Teilen zusammensetzen |
| if(range) tmpstr=implode(noparsestr[q_start..(q_start+range)]," "); |
| else tmpstr=noparsestr[q_start]; |
| |
| //DBG(tmpstr); |
| if(check_present&CHECK_PRESENT && // PRESENT ? |
| ((tmpobj=present(tmpstr,this_player())) || |
| (tmpobj=present(tmpstr,environment(this_player()))))) |
| matchstr=tmpobj; |
| else if(check_present&CHECK_ID && id(tmpstr)) // ID ? |
| matchstr=this_object(); |
| else if((check_present&CHECK_PUTGET) && // PUT_GET_??? ? |
| (tmpobj=(object*) |
| this_player()->find_obs(tmpstr, |
| ([CHECK_PUTGETNONE:PUT_GET_NONE, |
| CHECK_PUTGETDROP:PUT_GET_TAKE, |
| CHECK_PUTGETTAKE:PUT_GET_DROP]) |
| [CHECK_PUTGET&check_present])) && |
| sizeof(tmpobj)) { |
| if(sizeof(tmpobj)==1) matchstr=tmpobj[0]; |
| else { // Arrays werden zwischengespeichert ... |
| objmatches+=({sizeof(parsed),tmpobj}); |
| matchstr=tmpstr; |
| } |
| } else { // dieser Substring hat nicht gematcht |
| // ab weniger als 3 Teilen ist das Nichtmatching eines Substrings mit |
| // beendendem Numeral Kriterium fuer Abbruch ("objekt 2" soll matchen) |
| string numeralcheck; |
| if(range && range<=2 && |
| sizeof(numeralcheck=noparsestr[q_start+range]) && |
| to_string(to_int(numeralcheck))==numeralcheck) |
| break; |
| |
| // Substringlaenge verkuerzen und weiter |
| range--; |
| } |
| } |
| |
| // Match gefunden! |
| if(matchstr) { |
| matchpos=range+q_start; |
| // DBG(matchpos); |
| break; |
| } |
| q_start++; |
| } // end while |
| } |
| } |
| |
| // Fehlermeldung fuer diese fehlgeschlagene Synonymgruppe setzen |
| if(matchpos == NOMATCHFOUND) { |
| // Fehlermeldungen und ein Eintrag an der Fehlerstelle? |
| if(pointerp(flag) && sizeof(flag)>nrul) { |
| |
| matchstr=flag[nrul]; |
| |
| if(stringp(matchstr) && sizeof(matchstr)) { |
| if(member(matchstr,'@')>=0) { |
| matchstr=replace_personal(&matchstr,({this_player()})+parsed,1); |
| string stamm=((query_verb()[<1]^'e')?query_verb():query_verb()[0..<2]); |
| matchstr=regreplace(matchstr,"@VERB",capitalize(stamm),1); |
| matchstr=regreplace(matchstr,"@verb",stamm,1); |
| } |
| |
| // ist Fehlermeldung ein WRITE? |
| // dann return 1 ! |
| if(intp(flag[<1]) && flag[<1]<=nrul) { |
| if(member(matchstr,'^')>=0) { |
| matchstr=explode(matchstr,"^"); |
| write(capitalize(break_string(matchstr[0],78,0,1))); |
| if(sizeof(matchstr[1])) |
| say(capitalize(break_string(matchstr[1],78,0,1)),({this_player()}) ); |
| } else write(capitalize(break_string(matchstr,78,0,1))); |
| return 1; |
| } else notify_fail(capitalize(break_string(matchstr,78,0,1))); |
| } |
| } |
| return 0; |
| } |
| |
| // Updaten der Hilfsvariablen |
| parsed+=({matchstr}); |
| lastmatchpos=matchpos; |
| nrul++; |
| } // end while(nrul<rs) ... Erfolg ... ab zum naechsten Regelteil |
| } |
| |
| // Arrays der @-Objectmatches in Parameter fuer Methode resubstituieren |
| int objsize; |
| if((objsize=sizeof(objmatches))) |
| while((objsize-=2)>=0) |
| parsed[objmatches[objsize]]=objmatches[objsize+1]; |
| |
| // erfolgreich Methode gefunden/eine Regel bis zum Ende durchgeparst: |
| return(_execute(&fun,&str,&parsed)); |
| } |
| |
| // Auswertung - add_action-Fun |
| public int _cl(string str) { |
| int nindex; |
| string *keys; |
| mixed *flag; |
| // Verb existiert, Kommandos auch, Eintrag fuer Verb gefunden |
| if(mappingp(added_cmds) && query_verb()) { |
| mixed *fun, *rule, *ids; |
| |
| // ist das Verb ein Key im Kommandomapping? |
| if(m_contains(&fun, &flag, &rule, &ids, added_cmds, query_verb())) { |
| string *noparsestr; |
| nindex=sizeof(fun); |
| noparsestr=explode((str||"")," ")-({""}); |
| // dann matche ungeparsten Input gegen etwaige Regeln |
| // -- nicht aendern: neue Kommandos sollen alte "ueberschreiben" koennen |
| while(nindex--) |
| if(_process_command(&str, noparsestr, |
| fun[nindex], flag[nindex], rule[nindex])) |
| { |
| GiveEP(EP_CMD, query_verb()); |
| return 1; |
| } |
| } |
| |
| // keine Regel passte, unscharfe Auswertung auf alle |
| // AddCmdverben ausdehnen |
| keys=m_indices(added_cmds); |
| nindex=sizeof(keys); |
| while(nindex--) |
| if(!strstr(query_verb(),keys[nindex]) && |
| member((flag=added_cmds[keys[nindex],1]),1)>=0) { |
| int i,ret; |
| i=sizeof(flag); |
| // Reihenfolge nicht aendern ! |
| while(i--) |
| if(flag[i]==1) { |
| mixed *exec = added_cmds[keys[nindex],0]; |
| if(!pointerp(exec) || sizeof(exec)<=i) |
| catch(raise_error( |
| "AddCmd-Auswertung: CatchAll-Kommando \""+keys[nindex]+"\"" |
| " wurde waehrend der Ausfuehrung veraendert oder geloescht. " |
| "Klartext: Das ist schlecht. Sucht ein RemoveCmd? "); |
| publish); |
| else if(_execute(exec[i],str,0)) { |
| GiveEP(EP_CMD, query_verb()); |
| return 1; |
| } |
| } |
| } |
| } |
| return 0; |
| } |
| |
| void init() { |
| add_action("_cl","",1); |
| } |
| |
| |
| static void _check_copy_commands(string ind, mixed *fun, |
| mixed *flag, mixed *rule) { |
| if(pointerp(fun)) added_cmds[ind,0]=fun+allocate(0); |
| else added_cmds[ind,0]=({fun}); |
| if(pointerp(flag)) added_cmds[ind,1]=flag+allocate(0); |
| else if(flag) added_cmds[ind,1]=({flag}); |
| else added_cmds[ind,1]=allocate(sizeof(added_cmds[ind])); |
| if(pointerp(rule)) added_cmds[ind,2]=rule+allocate(0); |
| else added_cmds[ind,2]=allocate(sizeof(added_cmds[ind])); |
| |
| if(sizeof(added_cmds[ind])!=sizeof(added_cmds[ind,1]) || |
| sizeof(added_cmds[ind])!=sizeof(added_cmds[ind,2])) { |
| added_cmds=(mapping)0; |
| raise_error("SetProp(P_COMMANDS): corrupt commands-mapping.\n"); |
| } |
| } |
| |
| static mapping _set_commands(mapping commands) { |
| if(!commands) added_cmds=(mapping)0; |
| else if(mappingp(commands)) { |
| added_cmds=m_allocate(sizeof(commands),4); |
| walk_mapping(commands,#'_check_copy_commands); |
| } |
| if (mappingp(added_cmds)) |
| return(deep_copy(added_cmds)); |
| else |
| return (mapping)0; |
| } |
| |
| static mapping _query_commands() { |
| if (mappingp(added_cmds)) |
| return(deep_copy(added_cmds)); |
| else |
| return (mapping)0; |
| } |
| |