blob: f745b6788675087df2355c98df555af67cfa1e88 [file] [log] [blame]
MG Mud User88f12472016-06-24 23:31:02 +02001// MorgenGrauen MUDlib
2//
3// thing/commands.c -- thing description
4//
5// $Id: commands.c 9514 2016-02-23 20:33:09Z Gloinson $
6//
7// Aus Regenbogen MUDLib
8// aus Gueldenland/Wunderland MUDlib (Morgengrauen MUDlib)
9//
10// P_COMMANDS data-structure:
11//
12// AddCmd(verb,fun1,1);
13// AddCmd(verb+syn1a|syn1b&syn2a|syn2b|syn2c,fun2,
14// error1_notify|error2_notify^error2_write);
15// -->
16// ([verb:({fun1,fun2}); // funs
17// ({1,({error1_notify, error2_write^error2_say, 1})}); // flags
18// ({0,({({syn1a,syn1b}),({syn2a,syn2b,syn2c})})}); // rules
19// 0]) // IDs
20//
21// Rules: ({<Regelsatz fuer fun1>, ({<1. Synonymgruppe>,
22// <2. Synonymgruppe, ...}), ...})
23// Flags: ({<Flag fuer fun1>, ({<Fehlermeldung 1. Synonymgruppe>, ... ,
24// [, Index fuer write anstatt notify_fail]}),
25// ... })
26// IDs: 0 oder ({<ID fuer fun1>}) oder ({0, <ID fuer fun2>}) ...
27//
28// IDEA: save no 0s in rules/flags if possible (as in IDs)
29// (adressing especially old-style-AddCmd-MUDs)
30#pragma strict_types
31#pragma save_types
32#pragma range_check
33#pragma no_clone
34#pragma pedantic
35
36#include <moving.h>
37#include <thing/language.h>
38#include <exploration.h>
39
40#define NEED_PROTOTYPES
41#include <thing/description.h>
42#include <thing/commands.h>
43#undef NEED_PROTOTYPES
44
45#ifdef DBG
46#undef DBG
47#endif
48#define DBG(x) printf("Object %O tmpstr=%s\n", explode(object_name(this_object()),"#")[1], x);
49
50private nosave mapping added_cmds;
51
52protected void create()
53{
54}
55
56protected void create_super() {
57 set_next_reset(-1);
58}
59
60varargs void AddCmd(mixed cmd, mixed func, mixed flag, mixed cmdid) {
61 int i,j;
62 closure cl;
63 mixed *rule;
64
65 // potentielle AddCmd mit Regel?
66 if(stringp(cmd)) {
67 // eine Regel? - aufsplitten
68 if((i=member(cmd,'&'))>0) {
69 // ... in Array mit Verknuepfungselementen
70 rule=explode(cmd[(i+1)..],"&");
71 j=sizeof(rule);
72 // ... in Array mit Arrays mit Alternativelementen:
73 // "p|q&r|s" -> ({ ({"p","q"}), ({"r","s"}} })
74 while(j--)
75 rule[j]=explode(rule[j],"|");
76
77 // Regeln von Kommandoverben abschneiden
78 cmd=cmd[0..(i-1)];
79 }
80 // Kommandoverben extrahieren
81 cmd=explode(cmd,"|");
82
83 // Satz von Regeln existiert: Aufsplitten von Fehlermeldungen
84 if(rule)
85 if(stringp(flag)) {
86 mixed *fail;
87 // in einfaches Array mit jeweiligen Fehlermeldungen
88 fail=explode(flag,"|");
89 j=0;
90 i=sizeof(fail);
91 while(j<i) {
92 // write - Fehlermeldung entdeckt - Position ggf. eintragen
93 if(member(fail[j],'^')>=0 && !intp(fail[<1]))
94 fail+=({j});
95 if(member(fail[j],'@')>=0) {
96 int s;
97 flag=regexplode(fail[j], "@WE[A-SU]*[0-9]");
98 s=sizeof(flag);
99 while((s-=2)>0) {
100 int tmpint;
101 tmpint=flag[s][<1]-'1';
102 if(tmpint<0 || tmpint>j)
103 raise_error(sprintf(
104 "AddCmd: error-message %d contains out-of-bounds @WExx-rule.\n",j+1));
105 }
106 }
107 j++;
108 }
109 // "Was?|Wie das?" -> ({"Was?","Wie das?"})
110 // "Was?|Wie das?^|Womit das?|Worauf das?^@WER1 macht was." ->
111 // ({"Was?",
112 // "Wie das?^Womit das?",
113 // "Worauf das?^@WER1 macht was.",1})
114 flag=sizeof(fail);
115 if(flag && flag<sizeof(rule))
116 raise_error(
117 "AddCmd: number of error-messages does not match number of rules.\n");
118 flag=fail; // ueberschreiben mit den parsefreundlichen Format
119 } else if(flag)
120 raise_error("AddCmd: rules exist but flags are not an error-string.\n");
121 } // end if(stringp(cmd)) ... kein Regelstring vorhanden
122
123 // kein Kommandoarray gewesen noch erzeugt?
124 if(!pointerp(cmd))
125 raise_error("AddCmd: missing string/pointer-parameter for command.\n");
126
127 // Closure aus einem String erzeugen, wenn moeglich und sicher
128 // (function_exists() filtert unnoetigerweise auch reine "static" funs,
129 // die genaue Pruefung ueber functionlist() kostet jedoch zuviel)
130 if(stringp(func) &&
131 (!extern_call() || function_exists(func,this_object())) &&
132 closurep(cl=symbol_function(func,this_object())))
133 func=cl;
134
135 // jedes einzelne Verb mit seinen Regeln und Funktionen eintragen
136 i=sizeof(cmd);
137 if(!added_cmds) added_cmds=m_allocate(i,4);
138 while(i--) {
139 string str;
140 str=cmd[i];
141 if(!func)
142 if(extern_call()) func=previous_object();
143 else func=this_object();
144 if(!member(added_cmds,str))
145 added_cmds+=([str:allocate(0);allocate(0);allocate(0);0]);
146 // existierendes Verb ergaenzen
147 added_cmds[str,0]+=({func});
148 added_cmds[str,1]+=({flag});
149 added_cmds[str,2]+=({rule});
150 // ggf. id in das ID-Mapping eintragen
151 if(cmdid) {
152 mixed *tmp;
153 j=sizeof((string*)added_cmds[str,0]);
154 tmp=added_cmds[str,3]||allocate(j);
155 if(sizeof(tmp)<j) tmp+=allocate(j-sizeof(tmp));
156 tmp[<1]=cmdid;
157 added_cmds[str,3]=tmp;
158 }
159 }
160}
161
162// Auswertung fuer ein Verb loeschen
163varargs int RemoveCmd(mixed cmd, int del_norule, mixed onlyid) {
164 int ret;
165
166 // Falls Magier das RemoveCmd falsch nutzen (z.B. analog zu AddCmd)
167 // wird das Regelsystem verwirrt. Da das del_norule nur int sein darf,
168 // gibt es hier eine gute Chance den Fehler abwaertskompatibel zu ent-
169 // decken. Damit Spieler den Fehler nicht mitbekommen, wird hier auf
170 // ein raise_error verzichtet, und statt dessen in ein Logfile ge-
171 // schrieben.
172 if (!intp(del_norule))
173 {
174 log_file("REMOVE_CMD",
175 sprintf("\n-- %s --\nIllegal RemoveCommand() in Object [%O]:\n %O\n",
176 dtime(time()), this_object(), cmd));
177 del_norule=0;
178 onlyid=0;
179 }
180
181 if(!added_cmds || (!cmd && !del_norule && !onlyid))
182 added_cmds=(mapping)0;
183 else {
184 int i, j;
185 mixed *rule, *flag, *fun, *delrule, *ids;
186
187 if(stringp(cmd)) {
188 // Regeln entdeckt - Zerlegen (wie AddCmd)
189 if((i=member(cmd,'&'))>0) {
190 delrule=explode(cmd[(i+1)..],"&");
191 j=sizeof(delrule);
192 while(j--)
193 delrule[j]=explode(delrule[j],"|");
194 cmd=cmd[0..(i-1)];
195 }
196 cmd=explode(cmd,"|");
197 } else if(del_norule || onlyid) cmd=m_indices(added_cmds);
198
199 if(!pointerp(cmd))
200 raise_error("RemoveCmd: missing string/pointer-parameter.\n");
201 i=sizeof(cmd);
202
203 while(i--) {
204 // keine Regeln da und Regeln loeschen erlaubt: alles loeschen
205 if(!delrule && !del_norule && !onlyid) m_delete(added_cmds,cmd[i]);
206 else if(m_contains(&fun, &flag, &rule, &ids, added_cmds, cmd[i])) {
207 j=sizeof(fun);
208 while(j--) {
209 int k;
210 // DBG(rule[j]);
211 // Regeln nicht löschen und Regel?
212 if(!(del_norule && pointerp(rule[j])) &&
213 // nur bestimmte ID löschen und ID passt nicht?
214 !(onlyid && (!pointerp(ids) || sizeof(ids)<=j || ids[j]!=onlyid)) &&
215 // Löschregel existiert und passt nicht auf Regel?
216 !(delrule && (k=sizeof(rule[j]))!=sizeof(delrule))) {
217 // partielles Testen einer Löschregel ...
218 if(delrule) {
219 while(k--)
220 if(!sizeof(rule[j][k]&delrule[k])) break;
221 if(k>=0) continue;
222 }
223 // alles korrekt: Löschen!
224 // (Arraybereich durch leeres Array loeschen)
225 flag[j..j] = allocate(0);
226 fun[j..j] = allocate(0);
227 rule[j..j] = allocate(0);
228 if(ids) {
229 ids[j..j] = allocate(0);
230 if(!sizeof(ids-allocate(1))) ids=(mixed*)0;
231 }
232 ret++;
233 }
234 } // end while(j--) {
235 }
236 // Funktions/Regelliste update oder ggf. Kommando voellig loeschen
237 if(sizeof(rule)) {
238 added_cmds[cmd[i],0]=fun;
239 added_cmds[cmd[i],1]=flag;
240 added_cmds[cmd[i],2]=rule;
241 added_cmds[cmd[i],3]=ids;
242 } else m_delete(added_cmds,cmd[i]);
243 }
244 if(!sizeof(added_cmds)) added_cmds=(mapping)0;
245 }
246 return ret;
247}
248
249// Ausfuehren samt geparstem Inputstring und getriggerten Parserergebnissen
250static int _execute(mixed fun, string str, mixed *parsed) {
251 if(closurep(fun))
252 return ((int)funcall(fun,str,&parsed));
253 if(stringp(fun))
254 return ((int)call_other(this_object(),fun,str,&parsed));
255 return 0;
256}
257
258#define CHECK_PRESENT 1
259#define CHECK_ID 2
260#define CHECK_PUTGETNONE 4
261#define CHECK_PUTGETDROP 8
262#define CHECK_PUTGETTAKE 16
263#define CHECK_PUTGET (CHECK_PUTGETNONE|CHECK_PUTGETDROP|CHECK_PUTGETTAKE)
264
265// Wert fuer Fehlschlag, Fallback-Wert der benutzten while-- - Schleifen
266#define NOMATCHFOUND -1
267
268// Regeln fuer ein (nun unwichtiges) Verb triggern
269static int _process_command(string str, string *noparsestr,
270 mixed fun, mixed flag, mixed rule) {
271 mixed *parsed, *objmatches;
272
273 // eine Regel ... auswerten ...
274 if(pointerp(rule)) {
275 int nrul;
276 parsed=objmatches=allocate(0);
277 int lastmatchpos=NOMATCHFOUND;
278
279 // Abgleichen der gesplitteten Eingabe mit Regeln:
280 // vorwaerts durch die Synonymgruppen
281 int rs=sizeof(rule);
282 while(nrul<rs) {
283 int matchpos;
284 string *synonym;
285 mixed matchstr;
286
287 matchpos=NOMATCHFOUND;
288 matchstr=0;
289
290 // Synonyme extrahieren
291 int nrsynonyms=sizeof(synonym=rule[nrul]);
292
293 // egal wie durch Synonyme bis Match - Abgleich mit Eingabe
294 while(nrsynonyms--) {
295 int tmppos = member(noparsestr,synonym[nrsynonyms]);
296 // ist Synonym im Eingabestring und kommt spaeter als vorheriges Synonym?
297 if(tmppos>=0 && tmppos>lastmatchpos) {
298 // Erfolg: merken der Position im Eingabestring und den matchenden String
299 matchpos=tmppos;
300 matchstr=noparsestr[tmppos];
301 break;
302 }
303 }
304
305 // kein Match durch Synonyme? Pruefe die @-Spezialvariablen.
306 if(matchpos == NOMATCHFOUND) {
307 int check_present;
308 // ist Abpruefen von ID/PRESENT in der Synonymgruppe verlangt
309 // bei present()/find_obs gleich Voraussetzung gueltiger TP mitprufen
310 if(member(synonym,"@ID")>=0) check_present=CHECK_ID;
311 if(this_player()) {
312 if(member(synonym,"@PRESENT")>=0) check_present|=CHECK_PRESENT;
313 else if(member(synonym,"@PUT_GET_NONE")>=0)
314 check_present|=CHECK_PUTGETNONE;
315 else if(member(synonym,"@PUT_GET_TAKE")>=0)
316 check_present|=CHECK_PUTGETDROP;
317 else if(member(synonym,"@PUT_GET_DROP")>=0)
318 check_present|=CHECK_PUTGETTAKE;
319 }
320
321 if(check_present) {
322 // wir fangen hinter dem letzten Match an
323 int q_start=lastmatchpos+1;
324 int r_end=sizeof(noparsestr)-1;
325
326 int range;
327 while((range=r_end-q_start)>=0) {
328 mixed tmpobj;
329
330 // wie weit wollen wir zurückgehen?
331 if(range)
332 if(!(check_present&CHECK_PUTGET))
333 range=range>2?2:range; // 3 Fragmente fuer @ID/@PRESENT (Adverb/Nr)
334 else if(range>4)
335 range=4; // 5 Fragmente fuer @PUT_XXX
336
337 // und jetzt die Substrings pruefen
338 while(range>=0 && !matchstr) {
339 string tmpstr;
340
341 // zu pruefenden String aus den Teilen zusammensetzen
342 if(range) tmpstr=implode(noparsestr[q_start..(q_start+range)]," ");
343 else tmpstr=noparsestr[q_start];
344
345 //DBG(tmpstr);
346 if(check_present&CHECK_PRESENT && // PRESENT ?
347 ((tmpobj=present(tmpstr,this_player())) ||
348 (tmpobj=present(tmpstr,environment(this_player())))))
349 matchstr=tmpobj;
350 else if(check_present&CHECK_ID && id(tmpstr)) // ID ?
351 matchstr=this_object();
352 else if((check_present&CHECK_PUTGET) && // PUT_GET_??? ?
353 (tmpobj=(object*)
354 this_player()->find_obs(tmpstr,
355 ([CHECK_PUTGETNONE:PUT_GET_NONE,
356 CHECK_PUTGETDROP:PUT_GET_TAKE,
357 CHECK_PUTGETTAKE:PUT_GET_DROP])
358 [CHECK_PUTGET&check_present])) &&
359 sizeof(tmpobj)) {
360 if(sizeof(tmpobj)==1) matchstr=tmpobj[0];
361 else { // Arrays werden zwischengespeichert ...
362 objmatches+=({sizeof(parsed),tmpobj});
363 matchstr=tmpstr;
364 }
365 } else { // dieser Substring hat nicht gematcht
366 // ab weniger als 3 Teilen ist das Nichtmatching eines Substrings mit
367 // beendendem Numeral Kriterium fuer Abbruch ("objekt 2" soll matchen)
368 string numeralcheck;
369 if(range && range<=2 &&
370 sizeof(numeralcheck=noparsestr[q_start+range]) &&
371 to_string(to_int(numeralcheck))==numeralcheck)
372 break;
373
374 // Substringlaenge verkuerzen und weiter
375 range--;
376 }
377 }
378
379 // Match gefunden!
380 if(matchstr) {
381 matchpos=range+q_start;
382 // DBG(matchpos);
383 break;
384 }
385 q_start++;
386 } // end while
387 }
388 }
389
390 // Fehlermeldung fuer diese fehlgeschlagene Synonymgruppe setzen
391 if(matchpos == NOMATCHFOUND) {
392 // Fehlermeldungen und ein Eintrag an der Fehlerstelle?
393 if(pointerp(flag) && sizeof(flag)>nrul) {
394
395 matchstr=flag[nrul];
396
397 if(stringp(matchstr) && sizeof(matchstr)) {
398 if(member(matchstr,'@')>=0) {
399 matchstr=replace_personal(&matchstr,({this_player()})+parsed,1);
400 string stamm=((query_verb()[<1]^'e')?query_verb():query_verb()[0..<2]);
401 matchstr=regreplace(matchstr,"@VERB",capitalize(stamm),1);
402 matchstr=regreplace(matchstr,"@verb",stamm,1);
403 }
404
405 // ist Fehlermeldung ein WRITE?
406 // dann return 1 !
407 if(intp(flag[<1]) && flag[<1]<=nrul) {
408 if(member(matchstr,'^')>=0) {
409 matchstr=explode(matchstr,"^");
410 write(capitalize(break_string(matchstr[0],78,0,1)));
411 if(sizeof(matchstr[1]))
412 say(capitalize(break_string(matchstr[1],78,0,1)),({this_player()}) );
413 } else write(capitalize(break_string(matchstr,78,0,1)));
414 return 1;
415 } else notify_fail(capitalize(break_string(matchstr,78,0,1)));
416 }
417 }
418 return 0;
419 }
420
421 // Updaten der Hilfsvariablen
422 parsed+=({matchstr});
423 lastmatchpos=matchpos;
424 nrul++;
425 } // end while(nrul<rs) ... Erfolg ... ab zum naechsten Regelteil
426 }
427
428 // Arrays der @-Objectmatches in Parameter fuer Methode resubstituieren
429 int objsize;
430 if((objsize=sizeof(objmatches)))
431 while((objsize-=2)>=0)
432 parsed[objmatches[objsize]]=objmatches[objsize+1];
433
434 // erfolgreich Methode gefunden/eine Regel bis zum Ende durchgeparst:
435 return(_execute(&fun,&str,&parsed));
436}
437
438// Auswertung - add_action-Fun
439public int _cl(string str) {
440 int nindex;
441 string *keys;
442 mixed *flag;
443 // Verb existiert, Kommandos auch, Eintrag fuer Verb gefunden
444 if(mappingp(added_cmds) && query_verb()) {
445 mixed *fun, *rule, *ids;
446
447 // ist das Verb ein Key im Kommandomapping?
448 if(m_contains(&fun, &flag, &rule, &ids, added_cmds, query_verb())) {
449 string *noparsestr;
450 nindex=sizeof(fun);
451 noparsestr=explode((str||"")," ")-({""});
452 // dann matche ungeparsten Input gegen etwaige Regeln
453 // -- nicht aendern: neue Kommandos sollen alte "ueberschreiben" koennen
454 while(nindex--)
455 if(_process_command(&str, noparsestr,
456 fun[nindex], flag[nindex], rule[nindex]))
457 {
458 GiveEP(EP_CMD, query_verb());
459 return 1;
460 }
461 }
462
463 // keine Regel passte, unscharfe Auswertung auf alle
464 // AddCmdverben ausdehnen
465 keys=m_indices(added_cmds);
466 nindex=sizeof(keys);
467 while(nindex--)
468 if(!strstr(query_verb(),keys[nindex]) &&
469 member((flag=added_cmds[keys[nindex],1]),1)>=0) {
470 int i,ret;
471 i=sizeof(flag);
472 // Reihenfolge nicht aendern !
473 while(i--)
474 if(flag[i]==1) {
475 mixed *exec = added_cmds[keys[nindex],0];
476 if(!pointerp(exec) || sizeof(exec)<=i)
477 catch(raise_error(
478 "AddCmd-Auswertung: CatchAll-Kommando \""+keys[nindex]+"\""
479 " wurde waehrend der Ausfuehrung veraendert oder geloescht. "
480 "Klartext: Das ist schlecht. Sucht ein RemoveCmd? ");
481 publish);
482 else if(_execute(exec[i],str,0)) {
483 GiveEP(EP_CMD, query_verb());
484 return 1;
485 }
486 }
487 }
488 }
489 return 0;
490}
491
492void init() {
493 add_action("_cl","",1);
494}
495
496
497static void _check_copy_commands(string ind, mixed *fun,
498 mixed *flag, mixed *rule) {
499 if(pointerp(fun)) added_cmds[ind,0]=fun+allocate(0);
500 else added_cmds[ind,0]=({fun});
501 if(pointerp(flag)) added_cmds[ind,1]=flag+allocate(0);
502 else if(flag) added_cmds[ind,1]=({flag});
503 else added_cmds[ind,1]=allocate(sizeof(added_cmds[ind]));
504 if(pointerp(rule)) added_cmds[ind,2]=rule+allocate(0);
505 else added_cmds[ind,2]=allocate(sizeof(added_cmds[ind]));
506
507 if(sizeof(added_cmds[ind])!=sizeof(added_cmds[ind,1]) ||
508 sizeof(added_cmds[ind])!=sizeof(added_cmds[ind,2])) {
509 added_cmds=(mapping)0;
510 raise_error("SetProp(P_COMMANDS): corrupt commands-mapping.\n");
511 }
512}
513
514static mapping _set_commands(mapping commands) {
515 if(!commands) added_cmds=(mapping)0;
516 else if(mappingp(commands)) {
517 added_cmds=m_allocate(sizeof(commands),4);
518 walk_mapping(commands,#'_check_copy_commands);
519 }
520 if (mappingp(added_cmds))
521 return(deep_copy(added_cmds));
522 else
523 return (mapping)0;
524}
525
526static mapping _query_commands() {
527 if (mappingp(added_cmds))
528 return(deep_copy(added_cmds));
529 else
530 return (mapping)0;
531}
532