blob: 314a97b30a625437a33e7068df38f2fcc4792374 [file] [log] [blame]
MG Mud User88f12472016-06-24 23:31:02 +02001// MorgenGrauen MUDlib
2//
3// questmaster.c -- Questmaster, verwaltet die normalen Quests und
4// die MiniQuests
5//
6// $Id: questmaster.c 9136 2015-02-03 21:39:10Z Zesstra $
7//
8#pragma strict_types
9#pragma no_clone
10#pragma no_shadow
11#pragma no_inherit
12#pragma verbose_errors
13#pragma combine_strings
14//#pragma pedantic
15//#pragma range_check
16#pragma warn_deprecated
17
18#include <config.h>
19#include "/secure/wizlevels.h"
20#include "/secure/questmaster.h"
21#include "/secure/lepmaster.h"
22#include "/secure/telnetneg.h" // P_TTY
23#include <living/description.h> // P_LEVEL
24#include <player/base.h> // P_TESTPLAYER
25#include <daemon.h>
26#include <ansi.h>
27#include <events.h>
28
29#define DEBUG(x) if (funcall(symbol_function('find_player),"arathorn"))\
30 tell_object(funcall(symbol_function('find_player),"arathorn"),\
31 "QM: "+x+"\n")
32
33#define ME this_object()
34#define PL this_player()
35
36#define MQ_DATA_POINTS 0
37#define MQ_DATA_QUESTNO 1
38#define MQ_DATA_TASKDESC 2
39#define MQ_DATA_VISIBLE 3
40#define MQ_DATA_ACTIVE 4
41#define MQ_DATA_TITLE 5
42#define MQ_DATA_DIARYTEXT 6
43#define MQ_DATA_RESTRICTIONS 7
44#define MQ_DATA_ASSIGNED_DOMAIN 8
45#define MQ_DATA_QUERY_PERMITTED 9
46
47private int max_QP = 0;
48private int opt_QP = 0;
49// Die Questliste mit allen Daten
50private mapping quests = ([]);
51
52// Das Mapping mit der MQ-Liste an sich und alle zugehoerigen Daten
53private mapping miniquests = ([]);
54// Nach MQ-Nummern indizierter Cache:
55// Struktur ([int num : ({ int stupse, string mq-object }) ])
56private nosave mapping by_num = ([]);
57// Cache der Objekte, die die MQ-Listen der Regionen abfragen duerfen
58// Struktur ([string path : ({ string mq_object }) ])
59private nosave mapping mq_query_permitted = ([]);
60// Cache fuer die MQ-Punkte von Spielern, wird fuer den jeweiligen Spieler
61// beim Abfragen von dessen MQ-Punkten gefuellt. Spielername wird bei
62// Aenderungen an seinen MQ-Punkten (Bestehen einer MQ, manuelles Setzen
63// oder Loeschen einer seiner MQs) aus dem Cache ausgetragen
64private nosave mapping users_mq = ([]);
65// letzte vergebene MQ-Indexnummer. Es darf niemals eine MQ eine Indexnummer
66// kriegen, die eine andere MQ schonmal hatte, auch wenn die geloescht wurde.
67// (Zumindest nicht, ohne die entsprechenden Bits im Spieler zu loeschen, was
68// zZ nicht passiert.
69private int last_num = 0;
70
71
72void save_info() {
73 save_object(QUESTS);
74}
75
76// Caches aufbauen.
77static void make_num(string mqob_name, int stupse, int index,
78 string taskdesc, int vis, int active, string title,
79 string donedesc, mapping restr, string domain,
80 string *permitted_objs) {
81 by_num += ([ index : ({stupse, mqob_name})]);
82 foreach ( string obj: permitted_objs ) {
83 if ( member(mq_query_permitted, obj) )
84 mq_query_permitted[obj] += ({mqob_name});
85 else
86 mq_query_permitted[obj] = ({mqob_name});
87 }
88}
89
90void create() {
91 seteuid(getuid(ME));
92 if (!restore_object(QUESTS)) {
93 save_info();
94 }
95
96 walk_mapping(miniquests, #'make_num /*'*/ );
97 set_next_reset(43200); // Reset alle 12 Stunden.
98 EVENTD->RegisterEvent(EVT_LIB_QUEST_SOLVED,"HandleQuestSolved",
99 ME);
100}
101
102public int remove(int silent) {
103 save_info();
104 EVENTD->UnregisterEvent(EVT_LIB_QUEST_SOLVED, ME);
105 destruct(ME);
106 return 1;
107}
108
109// Schreibzugriff nur fuer interaktive EMs und ARCH_SECURITY.
110private int allowed_write_access() {
111 if (process_call())
112 return 0;
113 if (ARCH_SECURITY) // prueft auch this_interactive() mit.
114 return 1;
115 return 0;
116}
117
118void reset() {
119 by_num = ([]);
120 mq_query_permitted = ([]);
121 walk_mapping(miniquests, #'make_num /*'*/ );
122 set_next_reset(43200);
123}
124
125/*
126 * (1) ABSCHNITT "NORMALE QUESTS"
127 */
128
129/* Die Quests werden in einem Mapping gespeichert. Der Schluessel ist dabei der
130 Quest-Name, die Eintraege sind Arrays der folgenden Form:
131
132 1. Element ist die Zahl der durch diese Quest zu erwerbenden Questpunkte.
133 2. Element ist die Zahl der Erfahrungspunkte, die der Spieler bekommt,
134 wenn er diese Quest loest.
135 3. Element ist ein Array mit den Filenamen der Objekte, denen es gestattet
136 ist, diese Quest beim Player als geloest zu markieren (Erzmagier duerfen
137 das aber sowieso immer).
138 4. Element ist ein String, der die Quest kurz beschreibt. Dieser String wird
139 dem Spieler vom Orakel als Hinweis gegeben.
140 5. Element ist eine Zahl zwischen -1 und 100, die den Schwierigkeitsgrad der
141 Quest angibt, nach Einschaetzung des EM fuer Quests. Das Orakel kann dann
142 evtl. sinnige Saetze wie "Diese Quest erscheint mir aber noch recht
143 schwer fuer Dich.", oder "Hm, die haettest Du ja schon viel eher loesen
144 koennen." absondern. :)
145
146 Ein Wert von -1 bedeutet eine Seherquest. Diese zaehlt nicht zu den
147 Maximalen Questpunkten, sondern zaehlt als optionale Quest
148 6. Element ist ein Integer von 0 bis 5 und gibt die "Klasse" an;
149 ausgegeben werden dort Sternchen
150 7. Element ist ein Integer, 0 oder 1.
151 0: Quest voruebergehend deaktiviert (suspendiert)
152 1: Quest aktiviert
153 8. Element ist ein String und enthaelt den Namen des Magiers, der die
154 Quest "verbrochen" hat.
155 9. Element ist ein String, der den Namen eines Magiers enthaelt, der
156 evtl. fuer die Wartung der Quest zustaendig ist.
157 10. Element ist eine Zahl von 0 bis 4, die der Quest ein Attribut
158 gibt (0 fuer keines)
159*/
160
161// geaendert:
162// 5 == diff geht nun von -1 bis 100
163// 6 == klasse geht nun von 0 bis 5
164// 10 == attribut geht nun von 0 bis 4
165
166private int RecalculateQP() {
167 int i;
168 mixed q,n;
169
170 if (!allowed_write_access())
171 return -1;
172
173 max_QP=0;
174 opt_QP=0;
175
176 n=m_indices(quests);
177 q=m_values(quests);
178 for (i=sizeof(q)-1;i>=0;i--)
179 if (q[i][Q_ACTIVE]) {
180 if (q[i][Q_DIFF]>=0)
181 max_QP+=q[i][Q_QP];
182 if (q[i][Q_DIFF]==-1)
183 opt_QP+=q[i][Q_QP];
184 }
185
186 return max_QP+opt_QP;
187}
188
189int AddQuest(string name, int questpoints, int experience,
190 string *allowedobj, string hint, int difficulty, int questclass,
191 int active, string wiz, string scndwiz, int questattribute)
192{
193 mixed *quest;
194 int i;
195
196 if (!allowed_write_access()) return 0;
197 if (!stringp(name) || sizeof(name)<5) return -1;
198 if (questpoints<1) return -2;
199 if (!intp(experience)) return -3;
200 if (!pointerp(allowedobj)) return -4;
201 for (i=sizeof(allowedobj)-1;i>=0;i--)
202 {
203 if (!stringp(allowedobj[i]) || allowedobj[i]=="") return -4;
204 allowedobj[i]=(string)"/secure/master"->_get_path(allowedobj[i],0);
205 }
206 if (!stringp(hint) || hint=="") return -5;
207 if (difficulty<-1 || difficulty>100) return -6;
208 if (questclass<0 || questclass>5) return -11;
209 if (active<0 || active>1) return -7;
210 if (!stringp(wiz) || wiz=="" ||
211 file_size("/players/"+(wiz=lower_case(wiz))) != -2) return -8;
212 if (!stringp(scndwiz))
213 scndwiz="";
214 else if (file_size("/players/"+(scndwiz=lower_case(scndwiz))) != -2)
215 return -9;
216 if (questattribute<0 || questattribute>4)
217 return -10;
218
219 if(quests[name]&&(quests[name][5]==0||quests[name][5]==1)&&quests[name][6])
220 max_QP-=quests[name][0];
221
222 quests+=([name: ({questpoints,experience,allowedobj,hint,difficulty,
223 questclass,active,wiz, scndwiz,questattribute,
224 ({0.0,0}) }) ]);
225 RecalculateQP();
226 save_info();
227 QMLOG(sprintf("add: %s %O (%s)",name,quests[name],
228 getuid(this_interactive())));
229 return 1;
230}
231
232int RemoveQuest(string name) {
233 mixed *quest;
234
235 if (!allowed_write_access()) return 0;
236 if (!quests[name]) return -1;
237 QMLOG(sprintf("remove: %s %O (%s)",name,quests[name],
238 getuid(this_interactive())));
239 m_delete(quests,name);
240 RecalculateQP();
241 save_info();
242 return 1;
243}
244
245int QueryNeededQP() {
246 return REQ_QP;
247}
248
249int QueryMaxQP() {
250 return max_QP;
251}
252
253int QueryOptQP() {
254 return opt_QP;
255}
256
257int QueryTotalQP() {
258 return max_QP+opt_QP;
259}
260
261mixed *QueryGroupedKeys() {
262 string *qliste;
263 mixed *qgliste;
264 int i, j;
265
266 qgliste = allocate(sizeof(QGROUPS)+1); // letzte Gruppe sind die Seherquests
267 qliste = m_indices(quests);
268
269 for (i=sizeof(qgliste)-1;i>=0;i--)
270 qgliste[i]=({});
271
272 for (i=sizeof(qliste)-1;i>=0;i--)
273 {
274 // inaktive quest?
275 if (!quests[qliste[i]][Q_ACTIVE])
276 continue;
277 // optionale quest? also Seherquest
278 if (quests[qliste[i]][Q_DIFF]==-1)
279 qgliste[sizeof(QGROUPS)] += ({qliste[i]});
280 else {
281 // dann haben wir also eine normale Quest und daher Einordnung
282 // nach dem Schwierigkeitswert
283 for (j=sizeof(QGROUPS)-1;
284 j>=0 && QGROUPS[j]>=quests[qliste[i]][Q_DIFF];j--)
285 ;
286 qgliste[j] += ({qliste[i]});
287 }
288 }
289 return qgliste;
290}
291
292
293// folgende funk brauch ich glaube ich nicht mehr:
294int QueryDontneed(object pl) {
295 raise_error("Ich glaub, die Func QueryDontneed() braucht kein Mensch mehr. "
296 "(Zook)");
297}
298
299// Die folgende Func braucht man nicht mehr
300int QueryReadyForWiz(object player) {
301 raise_error("Die Func QueryReadyForWiz() braucht keiner mehr. (Zook)");
302}
303
304mixed *QueryQuest(string name) {
305 if(!quests[name])
306 return ({});
307 if( extern_call() )
308 return deep_copy( quests[name] );
309 return quests[name];
310}
311
312int QueryQuestPoints(string name) {
313 if( !quests[name] )
314 return -1;
315
316 return quests[name][Q_QP];
317}
318
319mixed *QueryQuests() {
320 if( extern_call() )
321 return ({m_indices(quests),map(m_values(quests),#'deep_copy /*'*/)});
322 return ({ m_indices(quests), m_values(quests) });
323}
324
325string *QueryAllKeys() {
326 return m_indices(quests);
327}
328
329int SetActive(string name, int flag) {
330 mixed *quest;
331
332 if (!allowed_write_access()) return 0;
333 if (!(quest=quests[name])) return -1;
334 switch(flag)
335 {
336 case 0:
337 if (quest[Q_ACTIVE] == flag)
338 return -2;
339 quest[Q_ACTIVE] = flag;
340 break;
341 case 1:
342 if (quest[Q_ACTIVE] == flag)
343 return -2;
344 quest[Q_ACTIVE] = flag;
345 break;
346 default:
347 return -3;
348 }
349 quests[name]=quest;
350 RecalculateQP();
351 save_info();
352 QMLOG(sprintf("%s: %s (%s)",(flag?"activate":"deactivate"),name,
353 getuid(this_interactive())));
354 return 1;
355}
356
357string name() {
358 return "<Quest>";
359}
360string Name() {
361 return "<Quest>";
362}
363
364void Channel(string msg) {
365 if(!interactive(previous_object()))
366 return;
367 catch(CHMASTER->send("Abenteuer", ME, msg);publish);
368}
369
370 /* quoted from /sys/mail.h: */
371#define MSG_FROM 0
372#define MSG_SENDER 1
373#define MSG_RECIPIENT 2
374#define MSG_CC 3
375#define MSG_BCC 4
376#define MSG_SUBJECT 5
377#define MSG_DATE 6
378#define MSG_ID 7
379#define MSG_BODY 8
380
381void SendMail(string questname, mixed *quest, object player) {
382 mixed* mail;
383 string text;
384
385 mail = allocate(9);
386
387 text =
388 "Hallo "+capitalize(getuid(player))+",\n\n"+
389 break_string("Nachdem Du gerade eben das Abenteuer '"+
390 questname +"' ("+quest[Q_QP]+" Punkte), das "+
391 capitalize(quest[Q_WIZ])+" fuer das "MUDNAME" entworfen hat, "
392 "mit Erfolg bestanden hast, sind "
393 "wir nun an Deiner Meinung dazu interessiert:", 78)+
394 "\n Hat Dir das Abenteuer gefallen und wieso bzw. wieso nicht?\n"
395 " Ist die Einstufung Deiner Meinung nach richtig? (AP und Stufe)\n"
396 " Gab es Probleme oder gar Fehler?\n"
397 " Hast Du Verbesserungsvorschlaege?\n\n";
398
399 text += break_string("Diese Nachricht wurde automatisch verschickt, "
400 "wenn Du mit dem 'r' Kommando darauf antwortest, geht die Antwort "
401 "direkt an Ark als zustaendigem Erzmagier fuer Abenteuer.\n",78);
402
403 if (quest[Q_SCNDWIZ]!="") {
404 text += break_string(
405 "Falls Du mit dem Magier sprechen willst, der zur Zeit das "
406 "Abenteuer technisch betreut, kannst Du Dich an "
407 +capitalize(quest[Q_SCNDWIZ])+ " wenden.",78);
408 }
409
410 mail[MSG_FROM] = "Ark";
411 mail[MSG_SENDER] = "Ark";
412 mail[MSG_RECIPIENT] = getuid(player);
413 mail[MSG_CC]=0;
414 mail[MSG_BCC]=0;
415 mail[MSG_SUBJECT]="Das Abenteuer: "+questname;
416 mail[MSG_DATE]=dtime(time());
417 mail[MSG_ID]=MUDNAME":"+time();
418 mail[MSG_BODY]=text;
419
420 "/secure/mailer"->DeliverMail(mail,0);
421 return;
422}
423
424static int compare (mixed *i, mixed *j) {
425 if (i[4] == j[4])
426 return i[1] > j[1];
427 else
428 return i[4] > j[4];
429}
430
431varargs string liste(mixed pl) {
432 int qgroups, i, j, qrfw;
433 mixed *qlists, *qgrouped, *qtmp;
434 string str;
435 string ja, nein, format, ueberschrift;
436
437 if(!objectp(pl))
438 if(stringp(pl))
439 pl=find_player(pl) || find_netdead(pl);
440 if(!objectp(pl))
441 pl=PL;
442 if(!objectp(pl))
443 return "Ohne Spielernamen/Spielerobjekt gibt es auch keine Liste.\n";
444
445 if ( ((string)pl->QueryProp(P_TTY)) == "ansi")
446 {
447 ja = ANSI_GREEN + "ja" + ANSI_NORMAL;
448 nein = ANSI_RED + "nein" + ANSI_NORMAL;
449 }
450 else
451 {
452 ja = "ja";
453 nein = "nein";
454 }
455
456 str = "";
457 // Festlegen des Ausgabeformates
458 format = "%=-:30s %:3d %-:6s %-:9s %:2s/%-:3s %-:12s %-s\n";
459 ueberschrift = sprintf("%-:30s %:3s %-:6s %-:9s %-:6s %-:12s %-:4s\n",
460 "Abenteuer", "AP", "Klasse", "Attribut",
461 "Stufe", "Autor", "Gel?");
462
463 qgroups = sizeof(QGROUPS);
464 qlists = allocate( qgroups+1 );
465 for( i=qgroups; i>=0; i-- )
466 qlists[i] = ({});
467
468 qgrouped = QueryGroupedKeys();
469
470 for (i=sizeof(qgrouped)-1;i>=0; i--)
471 for (j=sizeof(qgrouped[i])-1;j>=0; j--) {
472 qtmp = QueryQuest(qgrouped[i][j]);
473 qlists[i] += ({ ({
474 qgrouped[i][j],
475 qtmp[Q_QP],
476 QCLASS_STARS(qtmp[Q_CLASS]),
477 capitalize(QATTR_STRINGS[qtmp[Q_ATTR]]),
478 qtmp[Q_DIFF],
479 (qtmp[Q_AVERAGE][1]>10 /*&& IS_ARCH(this_player())*/
480 ? to_string(to_int(qtmp[Q_AVERAGE][0]))
481 : "-"),
482 capitalize(qtmp[Q_WIZ]),
483 (int)pl->QueryQuest(qgrouped[i][j]) == OK ? ja : nein
484 }) });
485 }
486
487 for( i=0; i<qgroups; i++ )
488 {
489 if (sizeof(qlists[i])) {
490 str += "\n" + ueberschrift;
491 str += sprintf("Stufen %d%s:\n",
492 QGROUPS[i]+1,
493 i==qgroups-1?"+":sprintf("-%d", QGROUPS[i+1]));
494 qlists[i] = sort_array( qlists[i], "compare", ME );
495 for( j=0; j<sizeof(qlists[i]); j++ ) {
496 if(qlists[i][j][Q_DIFF]>=0)
497 str += sprintf( format,
498 qlists[i][j][0], qlists[i][j][1], qlists[i][j][2],
499 qlists[i][j][3], sprintf("%d",qlists[i][j][4]),
500 qlists[i][j][5],
501 qlists[i][j][6], qlists[i][j][7]);
502 }
503 str += "\n\n";
504 }
505 }
506
507 qlists[qgroups] = sort_array(qlists[qgroups], "compare", ME);
508 i = qgroups;
509 if (sizeof(qlists[i])) {
510 str += "\n" + ueberschrift;
511 str += "Nur fuer Seher:\n";
512 for( j=0; j<sizeof(qlists[qgroups]); j++ ) {
513 if(qlists[i][j][Q_DIFF]==-1)
514 str += sprintf( format,
515 qlists[i][j][0], qlists[i][j][1], qlists[i][j][2],
516 qlists[i][j][3], "S", qlists[i][j][5],
517 qlists[i][j][6],
518 qlists[i][j][7]);
519 }
520 }
521
522 str +=
523 "\nEine Erklaerung der einzelnen Spalten findest Du unter "
524 "\"hilfe abenteuerliste\".\n";
525
526 return str;
527}
528
529
530// mitloggen, mit welchen durchschnittlichen Leveln Quests so geloest
531// werden...
532void HandleQuestSolved(string eid, object trigob, mixed data) {
533 string qname = data[E_QUESTNAME];
534
535 if (!quests[qname] || !objectp(trigob)
536 || trigob->QueryProp(P_TESTPLAYER) || IS_LEARNER(trigob))
537 return;
538
539 int lvl = (int)trigob->QueryProp(P_LEVEL);
540
541 if (lvl <= 0)
542 return;
543
544 // neuen Durchschnitt berechen.
545 mixed tmp = quests[qname][Q_AVERAGE];
546 float avg = tmp[0];
547 int count = tmp[1];
548 avg *= count;
549 avg += to_float(lvl);
550 tmp[1] = ++count;
551 tmp[0] = avg / count;
552
553 DEBUG(sprintf("%s: %f (%d)\n",qname,
554 quests[qname][Q_AVERAGE][0],
555 quests[qname][Q_AVERAGE][1]));
556}
557
558/*
559 * (2) ABSCHNITT "MINI" QUESTS
560 */
561
562int ClearUsersMQCache() {
563 if (!allowed_write_access())
564 return 0;
565
566 users_mq = ([]);
567
568 return 1;
569}
570
571mixed QueryUsersMQCache() {
572 if (!allowed_write_access())
573 return 0;
574
575 return users_mq;
576}
577
578/* Beschreibung
579 *
580 * Die MiniQuests werden in einem Mapping gespeichert.
581 *
582 * Der Key ist dabei der Name des Objektes, das die Quest im Spieler
583 * markieren darf. Die Daten zu den Miniquests stehen in den Value-Spalten
584 * des Mappings.
585 *
586 * 1. Spalte ist die Zahl der durch diese Quest zu erwerbenden Stufenpunkte
587 * 2. Spalte ist die Nummer unter der die MiniQuest gefuehrt wird.
588 * 3. Spalte ist ein String, der die Quest(aufgabe) kurz beschreibt.
589 * 4. Spalte ist ein Integer, 0 oder 1:
590 * 0 : Quest ist fuer Spieler nicht sichtbar
591 * 1 : Quest ist fuer Spieler z.B. bei einer Anschlagtafel sichtbar
592 * Fuer Spieler unsichtbare MQs sollte es aber nicht mehr geben!
593 * 5. Spalte ist ein Integer, 0 oder 1:
594 * 0 : Quest voruebergehend deaktiviert
595 * 1 : Quest aktiviert
596 * 6. Spalte ist ein String, der den Kurztitel der Miniquest enthaelt
597 * 7. Spalte ist ein String, der eine kurze Beschreibung dessen enthaelt,
598 * was der Spieler im Verlauf der Miniquest erlebt hat.
599 * 8. Spalte ist ein Mapping, dessen Eintraege analog zu P_RESTRICTIONS
600 * gesetzt werden koennen, um anzugeben, welche Voraussetzungen erfuellt
601 * sein muessen, bevor ein Spieler diese Quest beginnen kann.
602 * 9. Spalte ist die Zuordnung der MQ zu den Regionen
603 *10. Spalte ist ein Array aus Strings, das die Objekte enthaelt, die
604 * die Daten dieser Quest abfragen duerfen, um sie an Spieler auszugeben.
605 */
606
607int DumpMiniQuests(object who) {
608 int sum_points;
609
610 if (extern_call() && !allowed_write_access())
611 return 0;
612
613 if ( !objectp(who) || !query_once_interactive(who))
614 who = this_interactive();
615
616 MQMLOG(sprintf("DumpMiniQuests: PO: %O, TI: %O", previous_object(), who));
617 rm(MQ_DUMP_FILE);
618
619 write_file(MQ_DUMP_FILE, "MINIQUESTS: ("+dtime(time())+")\n\n"+
620 " Nr Pkt vis akt vergebendes Objekt\n");
621 string *msg = ({});
622
623 foreach(string obname, int stupse, int nummer, mixed descr, int vis,
624 int active /*, string title, string donedesc, mapping restrictions,
625 string domain, string *permitted_objs*/: miniquests)
626 {
627 msg += ({ sprintf("%4d %4d %4d %4d %s",
628 nummer, stupse, vis, active, obname)});
629 sum_points += stupse;
630 }
631
632 write_file(MQ_DUMP_FILE, implode(sort_array(msg, #'> /*'*/), "\n"));
633 write_file(MQ_DUMP_FILE, sprintf("\n\n"
634 "============================================================\n"
635 +"MiniQuests: %d Miniquests mit %d Punkten.\n\n",
636 sizeof(miniquests), sum_points));
637 return 1;
638}
639
640public int AddMiniQuest(int mquestpoints, string allowedobj, string descr,
641 int active, string title, string donedesc, mapping restrictions,
642 string domain, string *permitted_objs) {
643
644 if (!allowed_write_access())
645 return 0;
646
647 // Parameterpruefung: Questgeber, Restrictions, Region, Titel und
648 // zugelassene Abfrageobjekte muessen gueltig angegeben werden, alles
649 // weitere wird unten ggf. ausgenullt/korrigiert.
650 if (!stringp(allowedobj) || !sizeof(allowedobj) || !mappingp(restrictions)
651 || !stringp(domain) || !stringp(title) || !pointerp(permitted_objs))
652 return -1;
653
654 // Miniquest mit weniger als 1 Stups einzutragen ist wohl unsinnig.
655 if (mquestpoints<1)
656 return -2;
657
658 // Mindestens ein Objekt muss eingetragen werden, das Spielern Informationen
659 // ueber die Aufgabenstellung der MQ geben darf.
660 if ( !sizeof(permitted_objs) )
661 return -3;
662
663 // Pruefen, ob die als Questgeber angegebene Datei existiert.
664 if (allowedobj[<2..] == ".c")
665 allowedobj = allowedobj[0..<3];
666 allowedobj = explode(allowedobj, "#")[0];
667 allowedobj = (string)MASTER->_get_path(allowedobj,0);
668 if (file_size(allowedobj+".c") <=0)
669 return -3;
670
671 // Vergibt das angegebene Objekt schon eine MQ? Dann abbrechen.
672 if (member(miniquests,allowedobj))
673 return -4;
674
675 if (!stringp(descr) || !sizeof(descr))
676 descr = 0;
677 if (!stringp(donedesc) || !sizeof(donedesc))
678 donedesc = 0;
679
680 // Eintrag hinzufuegen, visible ist per Default immer 1.
681 // MQ-Nummer hochzaehlen
682 int nummer = last_num + 1;
683 m_add(miniquests, allowedobj, mquestpoints, nummer, descr, 1, active,
684 title, donedesc, restrictions, domain, permitted_objs);
685 // und nummer als last_num merken.
686 last_num = nummer;
687 save_info();
688 m_add(by_num, nummer, ({mquestpoints, allowedobj}));
689 MQMLOG(sprintf("AddMiniQuest: %s %O (%s)", allowedobj, miniquests[allowedobj],
690 getuid(this_interactive())));
691
692 ClearUsersMQCache();
693 if (find_call_out(#'DumpMiniQuests) == -1)
694 call_out(#'DumpMiniQuests, 60, this_interactive());
695 return 1;
696}
697
698int RemoveMiniQuest(string name) {
699 if (!allowed_write_access())
700 return 0;
701 // Gibt es einen solchen Eintrag ueberhaupt?
702 if (!member(miniquests,name))
703 return -1;
704
705 MQMLOG(sprintf("RemoveMiniQuest: %s %O (%s)",
706 name, m_entry(miniquests, name), getuid(this_interactive())));
707
708 // MQ aus dem MQ-Indexnummern-Cache loeschen.
709 m_delete(by_num, miniquests[name,MQ_DATA_QUESTNO]);
710 // MQ aus der Miniquestliste austragen.
711 m_delete(miniquests, name);
712 save_info();
713
714 // MQ-Punkte-Cache loeschen, da nicht feststellbar ist, welcher der
715 // dort eingetragenen Spieler die gerade ausgetragene MQ geloest hatte.
716 ClearUsersMQCache();
717 if (find_call_out(#'DumpMiniQuests) == -1)
718 call_out(#'DumpMiniQuests, 60, this_interactive());
719 return 1;
720}
721
722int ChangeMiniQuest(mixed mq_obj, int param, mixed newvalue) {
723 if (!allowed_write_access())
724 return 0;
725
726 // MQ weder als Pfad, noch als Indexnummer angegeben?
727 if ( !stringp(mq_obj) && !intp(mq_obj) && !intp(param))
728 return MQ_KEY_INVALID;
729
730 // gewaehlter Parameter ungueltig?
731 if ( param < MQ_DATA_POINTS || param > MQ_DATA_QUERY_PERMITTED )
732 return MQ_KEY_INVALID;
733
734 // Indexnummer der MQ in den Pfad umwandeln
735 if ( intp(mq_obj) )
736 mq_obj = by_num[mq_obj][1];
737
738 // Vergebendes Objekt nicht gefunden? Bloed, das brauchen wir naemlich.
739 if (!stringp(mq_obj))
740 return MQ_KEY_INVALID;
741
742 if ( !member(miniquests, mq_obj) )
743 return MQ_ILLEGAL_OBJ;
744
745 switch(param) {
746 // MQ_DATA_QUESTNO ist nicht aenderbar, daher hier nicht behandelt, so
747 // dass Fallback auf default erfolgt.
748 // Stufenpunkte muessen Integers sein.
749 case MQ_DATA_POINTS:
750 if ( !intp(newvalue) || newvalue < 1 )
751 return MQ_KEY_INVALID;
752 break;
753 // Aufgabenbeschreibung, Titel, "geschafft"-Text und zugeordnete Region
754 // muessen Strings sein
755 case MQ_DATA_TASKDESC:
756 case MQ_DATA_TITLE:
757 case MQ_DATA_DIARYTEXT:
758 case MQ_DATA_ASSIGNED_DOMAIN:
759 if ( !stringp(newvalue) || !sizeof(newvalue) )
760 return MQ_KEY_INVALID;
761 break;
762 // das Sichtbarkeits- und das aktiv/inaktiv-Flag muessen 0/1 sein.
763 case MQ_DATA_VISIBLE:
764 case MQ_DATA_ACTIVE:
765 if ( !intp(newvalue) || newvalue < 0 || newvalue > 1 )
766 return MQ_KEY_INVALID;
767 break;
768 // Die Voraussetzungen muessen als Mapping eingetragen werden, das aber
769 // leer oder Null sein kann, wenn es keine Restriktionen gibt.
770 case MQ_DATA_RESTRICTIONS:
771 if ( !mappingp(newvalue) && newvalue != 0 )
772 return MQ_KEY_INVALID;
773 break;
774 // Regionszuordnung muss ein nicht-leeres Array sein, das nur aus Strings
775 // bestehen darf, die nicht leer sein duerfen.
776 case MQ_DATA_QUERY_PERMITTED:
777 if ( pointerp(newvalue) ) {
778 newvalue = filter(filter(newvalue, #'stringp), #'sizeof);
779 if (!sizeof(newvalue))
780 return MQ_KEY_INVALID;
781 }
782 else
783 return MQ_KEY_INVALID;
784 break;
785 default:
786 return MQ_KEY_INVALID;
787 }
788
789 mixed *altemq = m_entry(miniquests, mq_obj);
790 int nummer = miniquests[mq_obj,MQ_DATA_QUESTNO];
791 miniquests[mq_obj, param] = newvalue;
792 by_num[nummer] = ({miniquests[mq_obj,MQ_DATA_POINTS], mq_obj});
793 save_info();
794
795 MQMLOG(sprintf("ChangeMiniQuest: %s from %O to %O (%s)", mq_obj,
796 altemq, m_entry(miniquests, mq_obj), getuid(this_interactive())));
797
798 ClearUsersMQCache();
799 if (find_call_out(#'DumpMiniQuests) == -1)
800 call_out(#'DumpMiniQuests, 60, this_interactive());
801 return 1;
802}
803
804mixed QueryMiniQuestByName(string name) {
805 if (!allowed_write_access())
806 return 0;
807 return deep_copy(miniquests & ({name}));
808}
809
810mixed QueryMiniQuestByNumber(int nummer) {
811 // Zugriffsabsicherung erfolgt dort auch, daher hier unnoetig
812 return (by_num[nummer]?QueryMiniQuestByName(by_num[nummer][1]):0);
813}
814
815// Das vollstaendige MQ-Mapping nur als Kopie ausliefern.
816mixed QueryMiniQuests() {
817 return allowed_write_access() ? deep_copy(miniquests) : 0;
818}
819
820// De-/Aktivieren einer Miniquest, wirkt als Umschalter, d.h. eine aktive
821// MQ wird durch Aufruf dieser Funktion als inaktiv markiert und umgekehrt.
822int SwitchMiniQuestActive(string name) {
823 if (!allowed_write_access())
824 return -1;
825 // Haben wir eine solche MQ ueberhaupt?
826 if (!member(miniquests, name))
827 return -2;
828
829 // active-Flag invertieren
830 miniquests[name, MQ_DATA_ACTIVE] = !miniquests[name, MQ_DATA_ACTIVE];
831 save_info();
832
833 MQMLOG(sprintf("%s: %s (%s)",
834 (miniquests[name,MQ_DATA_ACTIVE]?"Activate":"Deactivate"), name,
835 getuid(this_interactive()))
836 );
837 return miniquests[name,MQ_DATA_ACTIVE];
838}
839
840int GiveMiniQuest(object winner) {
841 // Spieler muss existieren und interactive sein.
842 if (!winner ||
843 (this_interactive() && (this_interactive() != winner)) ||
844 ((this_player() == winner) && !query_once_interactive(winner)))
845 return MQ_ILLEGAL_OBJ;
846 // Gaeste koennen keine Miniquests bestehen.
847 if (winner->QueryGuest())
848 return MQ_GUEST;
849 // Aufrufendes Objekt existiert gar nicht?
850 if (!previous_object())
851 return MQ_ILLEGAL_OBJ;
852
853 string objname = load_name(previous_object());
854 // Miniquest muss existieren
855 if (!member(miniquests,objname))
856 return MQ_KEY_INVALID;
857 // Inaktive Miniquests koennen nicht vergeben werden.
858 if (!miniquests[objname, MQ_DATA_ACTIVE])
859 return MQ_IS_INACTIVE;
860
861 string mq = (MASTER->query_mq(getuid(winner)) || "");
862
863 // Spieler hat die MQ schonmal bestanden? Dann keine weiteren Aktivitaet
864 // noetig
865 if (test_bit(mq, miniquests[objname, MQ_DATA_QUESTNO]))
866 return MQ_ALREADY_SET;
867
868 catch(mq = set_bit(mq, miniquests[objname,MQ_DATA_QUESTNO]);publish);
869 MASTER->update_mq(getuid(winner), mq);
870
871 MQSOLVEDLOG(sprintf("%s: %s, (#%d), (Stupse %d)",
872 objname, geteuid(winner), miniquests[objname, MQ_DATA_QUESTNO],
873 miniquests[objname, MQ_DATA_POINTS]));
874
875 // Miniquest-Event ausloesen
876 EVENTD->TriggerEvent( EVT_LIB_MINIQUEST_SOLVED, ([
877 E_OBJECT: previous_object(),
878 E_OBNAME: objname,
879 E_PLNAME: getuid(winner),
880 E_MINIQUESTNAME: miniquests[objname, MQ_DATA_TITLE] ]) );
881
882 // Spielereintrag aus dem MQ-Punkte-Cache loeschen
883 m_delete(users_mq, getuid(winner));
884
885 return 1;
886}
887
888int QueryMiniQuestPoints(mixed pl) {
889 string spieler;
890
891 //if (!allowed_write_access())
892 // return 0;
893
894 if (!pl)
895 return -1;
896
897 if (!objectp(pl) && !stringp(pl))
898 return -2;
899
900 if (objectp(pl) && !query_once_interactive(pl))
901 return -3;
902
903 if (objectp(pl))
904 spieler = getuid(pl);
905 else
906 spieler = pl;
907
908 if (!member(users_mq, spieler)) {
909 int mqpoints;
910 int p=-1;
911 string s = (MASTER->query_mq(spieler) || "");
912 while( (p=next_bit(s, p)) != -1) {
913 mqpoints+=by_num[p][0];
914 }
915 users_mq[spieler] = mqpoints;
916 }
917 return users_mq[spieler];
918}
919
920int HasMiniQuest(mixed pl, mixed name) {
921 string mq, spieler;
922
923 if (!pl || !name)
924 return MQ_ILLEGAL_OBJ;
925
926 if (!objectp(pl) && !stringp(pl))
927 return MQ_ILLEGAL_OBJ;
928
929 if (objectp(pl) && !query_once_interactive(pl))
930 return MQ_ILLEGAL_OBJ;
931
932 if (!objectp(name) && !stringp(name) && !intp(name))
933 return MQ_ILLEGAL_OBJ;
934
935 if (objectp(name))
936 name = explode(object_name(name), "#")[0];
937
938 if ( intp(name) )
939 name = by_num[name][1];
940
941 if (objectp(pl))
942 spieler = getuid(pl);
943 else
944 spieler = pl;
945
946 if (!member(miniquests,name))
947 return MQ_KEY_INVALID;
948
949 mq = (MASTER->query_mq(spieler) || "");
950
951 return test_bit(mq, miniquests[name, MQ_DATA_QUESTNO]);
952}
953
954// Zum Von-Hand-Setzen der MiniQuests
955int SetPlayerMiniQuest(string pl, string name) {
956 if(!allowed_write_access())
957 return 0;
958 if(!pl)
959 return MQ_ILLEGAL_OBJ;
960 if(!previous_object())
961 return MQ_ILLEGAL_OBJ;
962
963 if (!member(miniquests,name))
964 return MQ_KEY_INVALID;
965
966 string mq = (MASTER->query_mq(pl) || "");
967
968 if (test_bit(mq, miniquests[name,MQ_DATA_QUESTNO]))
969 return MQ_ALREADY_SET;
970
971 catch (mq = set_bit(mq, miniquests[name, MQ_DATA_QUESTNO]);publish);
972 MASTER->update_mq(pl, mq);
973
974 MQMLOG(sprintf("SetPlayerMiniQuest: %s %s (%s)",
975 pl, name, getuid(this_interactive())));
976 // Spielereintrag aus dem MQ-Punkte-Cache loeschen
977 m_delete(users_mq, pl);
978 return 1;
979}
980
981int ClearPlayerMiniQuest(string pl, string name) {
982 if (!allowed_write_access())
983 return 0;
984 if (!pl)
985 return MQ_ILLEGAL_OBJ;
986 if (!previous_object())
987 return MQ_ILLEGAL_OBJ;
988
989 if (!member(miniquests,name))
990 return MQ_KEY_INVALID;
991
992 string mq = (MASTER->query_mq(pl) || "");
993
994 if (!test_bit(mq, miniquests[name, MQ_DATA_QUESTNO]))
995 return MQ_ALREADY_SET;
996
997 catch (mq = clear_bit(mq, miniquests[name, MQ_DATA_QUESTNO]);publish);
998 MASTER->update_mq(pl, mq);
999
1000 MQMLOG(sprintf("ClearPlayerMiniQuest: %s %s (%s)",
1001 pl, name, getuid(this_interactive())));
1002 // Spielereintrag aus dem MQ-Punkte-Cache loeschen
1003 m_delete(users_mq, pl);
1004 return 1;
1005}
1006
1007// Umtragen von Miniquests von Objekt <old> auf Objekt <new>
1008int MoveMiniQuest(string old_mqob, string new_mqob) {
1009 if ( !allowed_write_access() )
1010 return -1;
1011
1012 // Haben wir ueberhaupt einen solchen Eintrag?
1013 if ( !member(miniquests, old_mqob) )
1014 return -2;
1015
1016 // Pruefen, ob die als <new_mqob> angegebene Datei existiert.
1017 if (new_mqob[<2..] == ".c")
1018 new_mqob = new_mqob[0..<3];
1019 new_mqob = explode(new_mqob, "#")[0];
1020 new_mqob = (string)"/secure/master"->_get_path(new_mqob,0);
1021 if (file_size(new_mqob+".c") <= 0)
1022 return -3;
1023 // Wenn das neue Objekt schon eine MQ vergibt, kann es keine weitere
1024 // annehmen.
1025 if ( member(miniquests, new_mqob) )
1026 return -4;
1027
1028 // Der Miniquestliste einen neuen Key "new" mit den Daten des alten Keys
1029 // hinzufuegen. m_entry() liefert alle Values dazu als Array, und der
1030 // flatten-Operator "..." uebergibt dessen Elemente als einzelne Parameter.
1031 m_add(miniquests, new_mqob, m_entry(miniquests, old_mqob)...);
1032 m_delete(miniquests, old_mqob);
1033 // Nummern-Index auch umtragen, sonst koennen Funktionen wie zB
1034 // QueryMiniQuestByNumber() die neue nicht finden.
1035 by_num[miniquests[new_mqob,MQ_DATA_QUESTNO]][1] = new_mqob;
1036 return 1;
1037}
1038
1039#define FRA_BIB "/d/ebene/miril/fraternitas/room/bibliothek"
1040
1041// Erlaubt die Abfrage aller MQs einer bestimmten Region fuer die Bibliothek
1042// der kleinen und grossen Heldentaten in der Fraternitas.
1043// Gibt ein Mapping der Form ([ indexnummer : titel; erledigt_Beschreibung ])
1044// zurueck.
1045mapping QuerySolvedMQsByDomain(mixed pl, string region) {
1046 if ( !objectp(pl) && !stringp(pl) &&
1047 load_name(previous_object())!=FRA_BIB) /*|| !allowed_write_access())*/
1048 return ([:2]);
1049
1050 mapping res = m_allocate(30,2); // reicht vermutlich
1051 // Die angegebene Region muss in der Spalte MQ_DATA_ASSIGNED_DOMAIN
1052 // enthalten sein, und das abfragende Objekt muss die Fraternitas-Bib sein
1053 foreach(string mqobj, int mqp, int index, string task, int vis, int act,
1054 string title, string donedesc, mapping restr, string domain:
1055 miniquests) {
1056 // aktive MQs der angeforderten Region zusammentragen, die der Spieler
1057 // bestanden hat.
1058 if ( domain == region && act && HasMiniQuest(pl, mqobj) )
1059 m_add(res, index, title, donedesc);
1060 }
1061 //DEBUG(sprintf("%O\n",res));
1062 return res;
1063}
1064#undef FRA_BIB
1065
1066// Abfrage der noch offenen MQs des angegebenen Spielers.
1067// Zurueckgegeben wird ein Mapping mit den Miniquest-Nummern als Keys und
1068// den Aufgabenbeschreibungen als Values, oder ein leeres Mapping, falls
1069// das abfragende Objekt keine Zugriffsberechtigung hat, oder das
1070// uebergebene Spielerobjekt keine offenen Miniquests mehr hat.
1071mapping QueryOpenMiniQuestsForPlayer(object spieler) {
1072 // map() etwas beschleunigen
1073 closure chk_restr = symbol_function("check_restrictions",
1074 "/std/restriction_checker");
1075 // Cache-Eintrag fuer das abfragende Objekt holen
1076 string *list = mq_query_permitted[load_name(previous_object())];
1077 mapping res = ([:2]);
1078
1079 if (!pointerp(list) || !sizeof(list))
1080 return res;
1081 // Liste der MQ-Objekte umwandeln in deren MQ-Nummer plus
1082 // Aufgabenbeschreibung, sofern der Spieler die MQ noch nicht bestanden
1083 // hat, aber die Voraussetzungen erfuellt.
1084 foreach ( string mq_obj : list )
1085 {
1086 // Nur wenn der Spieler die MQ noch nicht hat, kann er ueberhaupt einen
1087 // Tip dazu bekommen.
1088 if ( !HasMiniQuest(spieler, mq_obj) ) {
1089 // Restriction Checker fragen, ob der Spieler statt des Hinweises
1090 // eine Info bekommen muss, dass er eine Vorbedingung nicht erfuellt.
1091 string restr_result = funcall(chk_restr, spieler, miniquests[mq_obj,
1092 MQ_DATA_RESTRICTIONS]);
1093 // Wenn so eine Info dabei rauskommt, wird diese in die Ergebnisliste
1094 // uebernommen. In diesem Fall wird KEIN MQ-Hinweistext ausgeliefert.
1095 if ( stringp(restr_result) )
1096 {
1097 m_add(res, miniquests[mq_obj,MQ_DATA_QUESTNO], 0, restr_result);
1098 }
1099 // Anderenfalls wird der Hinweistext uebernommen; einen Eintrag
1100 // bzgl. eines eventuellen Hinderungsgrundes gibt's dann nicht.
1101 else
1102 {
1103 m_add(res, miniquests[mq_obj,MQ_DATA_QUESTNO],
1104 miniquests[mq_obj,MQ_DATA_TASKDESC], 0);
1105 }
1106 }
1107 }
1108 // Ergebnisliste zurueckgeben.
1109 return res;
1110}
1111
1112// Datenkonverter fuer das bisherige MQ-Mapping von
1113// ([ obj : ({daten1..daten5}) ]) nach
1114// ([ obj : daten1; daten2; ...; daten9 ]), wobei daten6..9 als Leerspalten
1115// erzeugt werden.
1116/*void ConvertMQData() {
1117 if ( !allowed_write_access() )
1118 return;
1119
1120 by_num=([]);
1121 // spaltenweise aus dem miniquests-Mapping ein Array aus Arrays erzeugen
1122 // Zunaechst die Keys des Mappings aufnehmen.
1123 mixed *mqdata = ({m_indices(miniquests)});
1124
1125 // Dann das Datenarray spaltenweise dazu (jedes Array ist eine Spalte).
1126 mqdata += transpose_array(m_values(miniquests));
1127 // 1. Array: Keys, alle weiteren jeweils eine Wertespalte
1128
1129 // Array erzeugen als Array aus 5 Array-Elementen, die alle soviele
1130 // Nullen enthalten, wie es Miniquests gibt,
1131 // ({ ({0,0,...}), ({0,0,...}), ({0,0,...}), ({0,0,...}), ({0,0,...}) })
1132 // dieses hinzufuegen. Sind die hinzukommenden 5 Spalten.
1133 mqdata += allocate(5, allocate(sizeof(miniquests),0));
1134
1135 // Mapping erzeugen, indem mit dem flatten-Operator "..." die Einzel-
1136 // Arrays des Datenpakets uebergeben werden. Erzeugt auf diese Weise
1137 // ([ keys : daten1; daten2; daten3; daten4; daten5; 0; 0; 0; 0; 0,])
1138 miniquests=mkmapping(mqdata...);
1139}
1140
1141// Neue Daten einlesen, Visible-Flag bei allen MQs per Default auf 1 setzen.
1142// Es gibt keine wirklich unsichtbaren MQs mehr.
1143void ReadNewData() {
1144 if ( !allowed_write_access() )
1145 return;
1146
1147 string *import = explode(read_file("/players/arathorn/mqdata"),"\n")-({""});
1148 string *fields;
1149 foreach(string mqdata : import) {
1150 fields = explode(mqdata, "#")-({""});
1151 DEBUG(sprintf("%O\n", fields[0]));
1152 if ( miniquests[fields[4], MQ_DATA_QUESTNO] != to_int(fields[0]) ) {
1153 raise_error("MQ-Nummern stimmen nicht ueberein!\n");
1154 return;
1155 }
1156 // fields[4] ist der MQ-Objektpfad
1157 miniquests[fields[4], MQ_DATA_TITLE] = fields[7];
1158 miniquests[fields[4], MQ_DATA_DIARYTEXT] = fields[6];
1159 miniquests[fields[4], MQ_DATA_TASKDESC] = fields[5];
1160 miniquests[fields[4], MQ_DATA_VISIBLE] = 1; // Default: visible
1161 miniquests[fields[4], MQ_DATA_ASSIGNED_DOMAIN] = fields[8];
1162 miniquests[fields[4], MQ_DATA_QUERY_PERMITTED] = fields[9];
1163 if ( fields[3] != "0" ) {
1164 miniquests[fields[4], MQ_DATA_RESTRICTIONS] =
1165 restore_value(fields[3]+"\n");
1166 }
1167 else miniquests[fields[4], MQ_DATA_RESTRICTIONS] = 0;
1168 if ( fields[9] != "0" )
1169 miniquests[fields[4], MQ_DATA_QUERY_PERMITTED] =
1170 restore_value(fields[9]+"\n");
1171 else miniquests[fields[4], MQ_DATA_QUERY_PERMITTED] = 0;
1172 }
1173}
1174*/