Added public files
Roughly added all public files. Probably missed some, though.
diff --git a/secure/questmaster.c b/secure/questmaster.c
new file mode 100644
index 0000000..314a97b
--- /dev/null
+++ b/secure/questmaster.c
@@ -0,0 +1,1174 @@
+// MorgenGrauen MUDlib
+//
+// questmaster.c -- Questmaster, verwaltet die normalen Quests und
+// die MiniQuests
+//
+// $Id: questmaster.c 9136 2015-02-03 21:39:10Z Zesstra $
+//
+#pragma strict_types
+#pragma no_clone
+#pragma no_shadow
+#pragma no_inherit
+#pragma verbose_errors
+#pragma combine_strings
+//#pragma pedantic
+//#pragma range_check
+#pragma warn_deprecated
+
+#include <config.h>
+#include "/secure/wizlevels.h"
+#include "/secure/questmaster.h"
+#include "/secure/lepmaster.h"
+#include "/secure/telnetneg.h" // P_TTY
+#include <living/description.h> // P_LEVEL
+#include <player/base.h> // P_TESTPLAYER
+#include <daemon.h>
+#include <ansi.h>
+#include <events.h>
+
+#define DEBUG(x) if (funcall(symbol_function('find_player),"arathorn"))\
+ tell_object(funcall(symbol_function('find_player),"arathorn"),\
+ "QM: "+x+"\n")
+
+#define ME this_object()
+#define PL this_player()
+
+#define MQ_DATA_POINTS 0
+#define MQ_DATA_QUESTNO 1
+#define MQ_DATA_TASKDESC 2
+#define MQ_DATA_VISIBLE 3
+#define MQ_DATA_ACTIVE 4
+#define MQ_DATA_TITLE 5
+#define MQ_DATA_DIARYTEXT 6
+#define MQ_DATA_RESTRICTIONS 7
+#define MQ_DATA_ASSIGNED_DOMAIN 8
+#define MQ_DATA_QUERY_PERMITTED 9
+
+private int max_QP = 0;
+private int opt_QP = 0;
+// Die Questliste mit allen Daten
+private mapping quests = ([]);
+
+// Das Mapping mit der MQ-Liste an sich und alle zugehoerigen Daten
+private mapping miniquests = ([]);
+// Nach MQ-Nummern indizierter Cache:
+// Struktur ([int num : ({ int stupse, string mq-object }) ])
+private nosave mapping by_num = ([]);
+// Cache der Objekte, die die MQ-Listen der Regionen abfragen duerfen
+// Struktur ([string path : ({ string mq_object }) ])
+private nosave mapping mq_query_permitted = ([]);
+// Cache fuer die MQ-Punkte von Spielern, wird fuer den jeweiligen Spieler
+// beim Abfragen von dessen MQ-Punkten gefuellt. Spielername wird bei
+// Aenderungen an seinen MQ-Punkten (Bestehen einer MQ, manuelles Setzen
+// oder Loeschen einer seiner MQs) aus dem Cache ausgetragen
+private nosave mapping users_mq = ([]);
+// letzte vergebene MQ-Indexnummer. Es darf niemals eine MQ eine Indexnummer
+// kriegen, die eine andere MQ schonmal hatte, auch wenn die geloescht wurde.
+// (Zumindest nicht, ohne die entsprechenden Bits im Spieler zu loeschen, was
+// zZ nicht passiert.
+private int last_num = 0;
+
+
+void save_info() {
+ save_object(QUESTS);
+}
+
+// Caches aufbauen.
+static void make_num(string mqob_name, int stupse, int index,
+ string taskdesc, int vis, int active, string title,
+ string donedesc, mapping restr, string domain,
+ string *permitted_objs) {
+ by_num += ([ index : ({stupse, mqob_name})]);
+ foreach ( string obj: permitted_objs ) {
+ if ( member(mq_query_permitted, obj) )
+ mq_query_permitted[obj] += ({mqob_name});
+ else
+ mq_query_permitted[obj] = ({mqob_name});
+ }
+}
+
+void create() {
+ seteuid(getuid(ME));
+ if (!restore_object(QUESTS)) {
+ save_info();
+ }
+
+ walk_mapping(miniquests, #'make_num /*'*/ );
+ set_next_reset(43200); // Reset alle 12 Stunden.
+ EVENTD->RegisterEvent(EVT_LIB_QUEST_SOLVED,"HandleQuestSolved",
+ ME);
+}
+
+public int remove(int silent) {
+ save_info();
+ EVENTD->UnregisterEvent(EVT_LIB_QUEST_SOLVED, ME);
+ destruct(ME);
+ return 1;
+}
+
+// Schreibzugriff nur fuer interaktive EMs und ARCH_SECURITY.
+private int allowed_write_access() {
+ if (process_call())
+ return 0;
+ if (ARCH_SECURITY) // prueft auch this_interactive() mit.
+ return 1;
+ return 0;
+}
+
+void reset() {
+ by_num = ([]);
+ mq_query_permitted = ([]);
+ walk_mapping(miniquests, #'make_num /*'*/ );
+ set_next_reset(43200);
+}
+
+/*
+ * (1) ABSCHNITT "NORMALE QUESTS"
+ */
+
+/* Die Quests werden in einem Mapping gespeichert. Der Schluessel ist dabei der
+ Quest-Name, die Eintraege sind Arrays der folgenden Form:
+
+ 1. Element ist die Zahl der durch diese Quest zu erwerbenden Questpunkte.
+ 2. Element ist die Zahl der Erfahrungspunkte, die der Spieler bekommt,
+ wenn er diese Quest loest.
+ 3. Element ist ein Array mit den Filenamen der Objekte, denen es gestattet
+ ist, diese Quest beim Player als geloest zu markieren (Erzmagier duerfen
+ das aber sowieso immer).
+ 4. Element ist ein String, der die Quest kurz beschreibt. Dieser String wird
+ dem Spieler vom Orakel als Hinweis gegeben.
+ 5. Element ist eine Zahl zwischen -1 und 100, die den Schwierigkeitsgrad der
+ Quest angibt, nach Einschaetzung des EM fuer Quests. Das Orakel kann dann
+ evtl. sinnige Saetze wie "Diese Quest erscheint mir aber noch recht
+ schwer fuer Dich.", oder "Hm, die haettest Du ja schon viel eher loesen
+ koennen." absondern. :)
+
+ Ein Wert von -1 bedeutet eine Seherquest. Diese zaehlt nicht zu den
+ Maximalen Questpunkten, sondern zaehlt als optionale Quest
+ 6. Element ist ein Integer von 0 bis 5 und gibt die "Klasse" an;
+ ausgegeben werden dort Sternchen
+ 7. Element ist ein Integer, 0 oder 1.
+ 0: Quest voruebergehend deaktiviert (suspendiert)
+ 1: Quest aktiviert
+ 8. Element ist ein String und enthaelt den Namen des Magiers, der die
+ Quest "verbrochen" hat.
+ 9. Element ist ein String, der den Namen eines Magiers enthaelt, der
+ evtl. fuer die Wartung der Quest zustaendig ist.
+ 10. Element ist eine Zahl von 0 bis 4, die der Quest ein Attribut
+ gibt (0 fuer keines)
+*/
+
+// geaendert:
+// 5 == diff geht nun von -1 bis 100
+// 6 == klasse geht nun von 0 bis 5
+// 10 == attribut geht nun von 0 bis 4
+
+private int RecalculateQP() {
+ int i;
+ mixed q,n;
+
+ if (!allowed_write_access())
+ return -1;
+
+ max_QP=0;
+ opt_QP=0;
+
+ n=m_indices(quests);
+ q=m_values(quests);
+ for (i=sizeof(q)-1;i>=0;i--)
+ if (q[i][Q_ACTIVE]) {
+ if (q[i][Q_DIFF]>=0)
+ max_QP+=q[i][Q_QP];
+ if (q[i][Q_DIFF]==-1)
+ opt_QP+=q[i][Q_QP];
+ }
+
+ return max_QP+opt_QP;
+}
+
+int AddQuest(string name, int questpoints, int experience,
+ string *allowedobj, string hint, int difficulty, int questclass,
+ int active, string wiz, string scndwiz, int questattribute)
+{
+ mixed *quest;
+ int i;
+
+ if (!allowed_write_access()) return 0;
+ if (!stringp(name) || sizeof(name)<5) return -1;
+ if (questpoints<1) return -2;
+ if (!intp(experience)) return -3;
+ if (!pointerp(allowedobj)) return -4;
+ for (i=sizeof(allowedobj)-1;i>=0;i--)
+ {
+ if (!stringp(allowedobj[i]) || allowedobj[i]=="") return -4;
+ allowedobj[i]=(string)"/secure/master"->_get_path(allowedobj[i],0);
+ }
+ if (!stringp(hint) || hint=="") return -5;
+ if (difficulty<-1 || difficulty>100) return -6;
+ if (questclass<0 || questclass>5) return -11;
+ if (active<0 || active>1) return -7;
+ if (!stringp(wiz) || wiz=="" ||
+ file_size("/players/"+(wiz=lower_case(wiz))) != -2) return -8;
+ if (!stringp(scndwiz))
+ scndwiz="";
+ else if (file_size("/players/"+(scndwiz=lower_case(scndwiz))) != -2)
+ return -9;
+ if (questattribute<0 || questattribute>4)
+ return -10;
+
+ if(quests[name]&&(quests[name][5]==0||quests[name][5]==1)&&quests[name][6])
+ max_QP-=quests[name][0];
+
+ quests+=([name: ({questpoints,experience,allowedobj,hint,difficulty,
+ questclass,active,wiz, scndwiz,questattribute,
+ ({0.0,0}) }) ]);
+ RecalculateQP();
+ save_info();
+ QMLOG(sprintf("add: %s %O (%s)",name,quests[name],
+ getuid(this_interactive())));
+ return 1;
+}
+
+int RemoveQuest(string name) {
+ mixed *quest;
+
+ if (!allowed_write_access()) return 0;
+ if (!quests[name]) return -1;
+ QMLOG(sprintf("remove: %s %O (%s)",name,quests[name],
+ getuid(this_interactive())));
+ m_delete(quests,name);
+ RecalculateQP();
+ save_info();
+ return 1;
+}
+
+int QueryNeededQP() {
+ return REQ_QP;
+}
+
+int QueryMaxQP() {
+ return max_QP;
+}
+
+int QueryOptQP() {
+ return opt_QP;
+}
+
+int QueryTotalQP() {
+ return max_QP+opt_QP;
+}
+
+mixed *QueryGroupedKeys() {
+ string *qliste;
+ mixed *qgliste;
+ int i, j;
+
+ qgliste = allocate(sizeof(QGROUPS)+1); // letzte Gruppe sind die Seherquests
+ qliste = m_indices(quests);
+
+ for (i=sizeof(qgliste)-1;i>=0;i--)
+ qgliste[i]=({});
+
+ for (i=sizeof(qliste)-1;i>=0;i--)
+ {
+ // inaktive quest?
+ if (!quests[qliste[i]][Q_ACTIVE])
+ continue;
+ // optionale quest? also Seherquest
+ if (quests[qliste[i]][Q_DIFF]==-1)
+ qgliste[sizeof(QGROUPS)] += ({qliste[i]});
+ else {
+ // dann haben wir also eine normale Quest und daher Einordnung
+ // nach dem Schwierigkeitswert
+ for (j=sizeof(QGROUPS)-1;
+ j>=0 && QGROUPS[j]>=quests[qliste[i]][Q_DIFF];j--)
+ ;
+ qgliste[j] += ({qliste[i]});
+ }
+ }
+ return qgliste;
+}
+
+
+// folgende funk brauch ich glaube ich nicht mehr:
+int QueryDontneed(object pl) {
+ raise_error("Ich glaub, die Func QueryDontneed() braucht kein Mensch mehr. "
+ "(Zook)");
+}
+
+// Die folgende Func braucht man nicht mehr
+int QueryReadyForWiz(object player) {
+ raise_error("Die Func QueryReadyForWiz() braucht keiner mehr. (Zook)");
+}
+
+mixed *QueryQuest(string name) {
+ if(!quests[name])
+ return ({});
+ if( extern_call() )
+ return deep_copy( quests[name] );
+ return quests[name];
+}
+
+int QueryQuestPoints(string name) {
+ if( !quests[name] )
+ return -1;
+
+ return quests[name][Q_QP];
+}
+
+mixed *QueryQuests() {
+ if( extern_call() )
+ return ({m_indices(quests),map(m_values(quests),#'deep_copy /*'*/)});
+ return ({ m_indices(quests), m_values(quests) });
+}
+
+string *QueryAllKeys() {
+ return m_indices(quests);
+}
+
+int SetActive(string name, int flag) {
+ mixed *quest;
+
+ if (!allowed_write_access()) return 0;
+ if (!(quest=quests[name])) return -1;
+ switch(flag)
+ {
+ case 0:
+ if (quest[Q_ACTIVE] == flag)
+ return -2;
+ quest[Q_ACTIVE] = flag;
+ break;
+ case 1:
+ if (quest[Q_ACTIVE] == flag)
+ return -2;
+ quest[Q_ACTIVE] = flag;
+ break;
+ default:
+ return -3;
+ }
+ quests[name]=quest;
+ RecalculateQP();
+ save_info();
+ QMLOG(sprintf("%s: %s (%s)",(flag?"activate":"deactivate"),name,
+ getuid(this_interactive())));
+ return 1;
+}
+
+string name() {
+ return "<Quest>";
+}
+string Name() {
+ return "<Quest>";
+}
+
+void Channel(string msg) {
+ if(!interactive(previous_object()))
+ return;
+ catch(CHMASTER->send("Abenteuer", ME, msg);publish);
+}
+
+ /* quoted from /sys/mail.h: */
+#define MSG_FROM 0
+#define MSG_SENDER 1
+#define MSG_RECIPIENT 2
+#define MSG_CC 3
+#define MSG_BCC 4
+#define MSG_SUBJECT 5
+#define MSG_DATE 6
+#define MSG_ID 7
+#define MSG_BODY 8
+
+void SendMail(string questname, mixed *quest, object player) {
+ mixed* mail;
+ string text;
+
+ mail = allocate(9);
+
+ text =
+ "Hallo "+capitalize(getuid(player))+",\n\n"+
+ break_string("Nachdem Du gerade eben das Abenteuer '"+
+ questname +"' ("+quest[Q_QP]+" Punkte), das "+
+ capitalize(quest[Q_WIZ])+" fuer das "MUDNAME" entworfen hat, "
+ "mit Erfolg bestanden hast, sind "
+ "wir nun an Deiner Meinung dazu interessiert:", 78)+
+ "\n Hat Dir das Abenteuer gefallen und wieso bzw. wieso nicht?\n"
+ " Ist die Einstufung Deiner Meinung nach richtig? (AP und Stufe)\n"
+ " Gab es Probleme oder gar Fehler?\n"
+ " Hast Du Verbesserungsvorschlaege?\n\n";
+
+ text += break_string("Diese Nachricht wurde automatisch verschickt, "
+ "wenn Du mit dem 'r' Kommando darauf antwortest, geht die Antwort "
+ "direkt an Ark als zustaendigem Erzmagier fuer Abenteuer.\n",78);
+
+ if (quest[Q_SCNDWIZ]!="") {
+ text += break_string(
+ "Falls Du mit dem Magier sprechen willst, der zur Zeit das "
+ "Abenteuer technisch betreut, kannst Du Dich an "
+ +capitalize(quest[Q_SCNDWIZ])+ " wenden.",78);
+ }
+
+ mail[MSG_FROM] = "Ark";
+ mail[MSG_SENDER] = "Ark";
+ mail[MSG_RECIPIENT] = getuid(player);
+ mail[MSG_CC]=0;
+ mail[MSG_BCC]=0;
+ mail[MSG_SUBJECT]="Das Abenteuer: "+questname;
+ mail[MSG_DATE]=dtime(time());
+ mail[MSG_ID]=MUDNAME":"+time();
+ mail[MSG_BODY]=text;
+
+ "/secure/mailer"->DeliverMail(mail,0);
+ return;
+}
+
+static int compare (mixed *i, mixed *j) {
+ if (i[4] == j[4])
+ return i[1] > j[1];
+ else
+ return i[4] > j[4];
+}
+
+varargs string liste(mixed pl) {
+ int qgroups, i, j, qrfw;
+ mixed *qlists, *qgrouped, *qtmp;
+ string str;
+ string ja, nein, format, ueberschrift;
+
+ if(!objectp(pl))
+ if(stringp(pl))
+ pl=find_player(pl) || find_netdead(pl);
+ if(!objectp(pl))
+ pl=PL;
+ if(!objectp(pl))
+ return "Ohne Spielernamen/Spielerobjekt gibt es auch keine Liste.\n";
+
+ if ( ((string)pl->QueryProp(P_TTY)) == "ansi")
+ {
+ ja = ANSI_GREEN + "ja" + ANSI_NORMAL;
+ nein = ANSI_RED + "nein" + ANSI_NORMAL;
+ }
+ else
+ {
+ ja = "ja";
+ nein = "nein";
+ }
+
+ str = "";
+ // Festlegen des Ausgabeformates
+ format = "%=-:30s %:3d %-:6s %-:9s %:2s/%-:3s %-:12s %-s\n";
+ ueberschrift = sprintf("%-:30s %:3s %-:6s %-:9s %-:6s %-:12s %-:4s\n",
+ "Abenteuer", "AP", "Klasse", "Attribut",
+ "Stufe", "Autor", "Gel?");
+
+ qgroups = sizeof(QGROUPS);
+ qlists = allocate( qgroups+1 );
+ for( i=qgroups; i>=0; i-- )
+ qlists[i] = ({});
+
+ qgrouped = QueryGroupedKeys();
+
+ for (i=sizeof(qgrouped)-1;i>=0; i--)
+ for (j=sizeof(qgrouped[i])-1;j>=0; j--) {
+ qtmp = QueryQuest(qgrouped[i][j]);
+ qlists[i] += ({ ({
+ qgrouped[i][j],
+ qtmp[Q_QP],
+ QCLASS_STARS(qtmp[Q_CLASS]),
+ capitalize(QATTR_STRINGS[qtmp[Q_ATTR]]),
+ qtmp[Q_DIFF],
+ (qtmp[Q_AVERAGE][1]>10 /*&& IS_ARCH(this_player())*/
+ ? to_string(to_int(qtmp[Q_AVERAGE][0]))
+ : "-"),
+ capitalize(qtmp[Q_WIZ]),
+ (int)pl->QueryQuest(qgrouped[i][j]) == OK ? ja : nein
+ }) });
+ }
+
+ for( i=0; i<qgroups; i++ )
+ {
+ if (sizeof(qlists[i])) {
+ str += "\n" + ueberschrift;
+ str += sprintf("Stufen %d%s:\n",
+ QGROUPS[i]+1,
+ i==qgroups-1?"+":sprintf("-%d", QGROUPS[i+1]));
+ qlists[i] = sort_array( qlists[i], "compare", ME );
+ for( j=0; j<sizeof(qlists[i]); j++ ) {
+ if(qlists[i][j][Q_DIFF]>=0)
+ str += sprintf( format,
+ qlists[i][j][0], qlists[i][j][1], qlists[i][j][2],
+ qlists[i][j][3], sprintf("%d",qlists[i][j][4]),
+ qlists[i][j][5],
+ qlists[i][j][6], qlists[i][j][7]);
+ }
+ str += "\n\n";
+ }
+ }
+
+ qlists[qgroups] = sort_array(qlists[qgroups], "compare", ME);
+ i = qgroups;
+ if (sizeof(qlists[i])) {
+ str += "\n" + ueberschrift;
+ str += "Nur fuer Seher:\n";
+ for( j=0; j<sizeof(qlists[qgroups]); j++ ) {
+ if(qlists[i][j][Q_DIFF]==-1)
+ str += sprintf( format,
+ qlists[i][j][0], qlists[i][j][1], qlists[i][j][2],
+ qlists[i][j][3], "S", qlists[i][j][5],
+ qlists[i][j][6],
+ qlists[i][j][7]);
+ }
+ }
+
+ str +=
+ "\nEine Erklaerung der einzelnen Spalten findest Du unter "
+ "\"hilfe abenteuerliste\".\n";
+
+ return str;
+}
+
+
+// mitloggen, mit welchen durchschnittlichen Leveln Quests so geloest
+// werden...
+void HandleQuestSolved(string eid, object trigob, mixed data) {
+ string qname = data[E_QUESTNAME];
+
+ if (!quests[qname] || !objectp(trigob)
+ || trigob->QueryProp(P_TESTPLAYER) || IS_LEARNER(trigob))
+ return;
+
+ int lvl = (int)trigob->QueryProp(P_LEVEL);
+
+ if (lvl <= 0)
+ return;
+
+ // neuen Durchschnitt berechen.
+ mixed tmp = quests[qname][Q_AVERAGE];
+ float avg = tmp[0];
+ int count = tmp[1];
+ avg *= count;
+ avg += to_float(lvl);
+ tmp[1] = ++count;
+ tmp[0] = avg / count;
+
+ DEBUG(sprintf("%s: %f (%d)\n",qname,
+ quests[qname][Q_AVERAGE][0],
+ quests[qname][Q_AVERAGE][1]));
+}
+
+/*
+ * (2) ABSCHNITT "MINI" QUESTS
+ */
+
+int ClearUsersMQCache() {
+ if (!allowed_write_access())
+ return 0;
+
+ users_mq = ([]);
+
+ return 1;
+}
+
+mixed QueryUsersMQCache() {
+ if (!allowed_write_access())
+ return 0;
+
+ return users_mq;
+}
+
+/* Beschreibung
+ *
+ * Die MiniQuests werden in einem Mapping gespeichert.
+ *
+ * Der Key ist dabei der Name des Objektes, das die Quest im Spieler
+ * markieren darf. Die Daten zu den Miniquests stehen in den Value-Spalten
+ * des Mappings.
+ *
+ * 1. Spalte ist die Zahl der durch diese Quest zu erwerbenden Stufenpunkte
+ * 2. Spalte ist die Nummer unter der die MiniQuest gefuehrt wird.
+ * 3. Spalte ist ein String, der die Quest(aufgabe) kurz beschreibt.
+ * 4. Spalte ist ein Integer, 0 oder 1:
+ * 0 : Quest ist fuer Spieler nicht sichtbar
+ * 1 : Quest ist fuer Spieler z.B. bei einer Anschlagtafel sichtbar
+ * Fuer Spieler unsichtbare MQs sollte es aber nicht mehr geben!
+ * 5. Spalte ist ein Integer, 0 oder 1:
+ * 0 : Quest voruebergehend deaktiviert
+ * 1 : Quest aktiviert
+ * 6. Spalte ist ein String, der den Kurztitel der Miniquest enthaelt
+ * 7. Spalte ist ein String, der eine kurze Beschreibung dessen enthaelt,
+ * was der Spieler im Verlauf der Miniquest erlebt hat.
+ * 8. Spalte ist ein Mapping, dessen Eintraege analog zu P_RESTRICTIONS
+ * gesetzt werden koennen, um anzugeben, welche Voraussetzungen erfuellt
+ * sein muessen, bevor ein Spieler diese Quest beginnen kann.
+ * 9. Spalte ist die Zuordnung der MQ zu den Regionen
+ *10. Spalte ist ein Array aus Strings, das die Objekte enthaelt, die
+ * die Daten dieser Quest abfragen duerfen, um sie an Spieler auszugeben.
+ */
+
+int DumpMiniQuests(object who) {
+ int sum_points;
+
+ if (extern_call() && !allowed_write_access())
+ return 0;
+
+ if ( !objectp(who) || !query_once_interactive(who))
+ who = this_interactive();
+
+ MQMLOG(sprintf("DumpMiniQuests: PO: %O, TI: %O", previous_object(), who));
+ rm(MQ_DUMP_FILE);
+
+ write_file(MQ_DUMP_FILE, "MINIQUESTS: ("+dtime(time())+")\n\n"+
+ " Nr Pkt vis akt vergebendes Objekt\n");
+ string *msg = ({});
+
+ foreach(string obname, int stupse, int nummer, mixed descr, int vis,
+ int active /*, string title, string donedesc, mapping restrictions,
+ string domain, string *permitted_objs*/: miniquests)
+ {
+ msg += ({ sprintf("%4d %4d %4d %4d %s",
+ nummer, stupse, vis, active, obname)});
+ sum_points += stupse;
+ }
+
+ write_file(MQ_DUMP_FILE, implode(sort_array(msg, #'> /*'*/), "\n"));
+ write_file(MQ_DUMP_FILE, sprintf("\n\n"
+ "============================================================\n"
+ +"MiniQuests: %d Miniquests mit %d Punkten.\n\n",
+ sizeof(miniquests), sum_points));
+ return 1;
+}
+
+public int AddMiniQuest(int mquestpoints, string allowedobj, string descr,
+ int active, string title, string donedesc, mapping restrictions,
+ string domain, string *permitted_objs) {
+
+ if (!allowed_write_access())
+ return 0;
+
+ // Parameterpruefung: Questgeber, Restrictions, Region, Titel und
+ // zugelassene Abfrageobjekte muessen gueltig angegeben werden, alles
+ // weitere wird unten ggf. ausgenullt/korrigiert.
+ if (!stringp(allowedobj) || !sizeof(allowedobj) || !mappingp(restrictions)
+ || !stringp(domain) || !stringp(title) || !pointerp(permitted_objs))
+ return -1;
+
+ // Miniquest mit weniger als 1 Stups einzutragen ist wohl unsinnig.
+ if (mquestpoints<1)
+ return -2;
+
+ // Mindestens ein Objekt muss eingetragen werden, das Spielern Informationen
+ // ueber die Aufgabenstellung der MQ geben darf.
+ if ( !sizeof(permitted_objs) )
+ return -3;
+
+ // Pruefen, ob die als Questgeber angegebene Datei existiert.
+ if (allowedobj[<2..] == ".c")
+ allowedobj = allowedobj[0..<3];
+ allowedobj = explode(allowedobj, "#")[0];
+ allowedobj = (string)MASTER->_get_path(allowedobj,0);
+ if (file_size(allowedobj+".c") <=0)
+ return -3;
+
+ // Vergibt das angegebene Objekt schon eine MQ? Dann abbrechen.
+ if (member(miniquests,allowedobj))
+ return -4;
+
+ if (!stringp(descr) || !sizeof(descr))
+ descr = 0;
+ if (!stringp(donedesc) || !sizeof(donedesc))
+ donedesc = 0;
+
+ // Eintrag hinzufuegen, visible ist per Default immer 1.
+ // MQ-Nummer hochzaehlen
+ int nummer = last_num + 1;
+ m_add(miniquests, allowedobj, mquestpoints, nummer, descr, 1, active,
+ title, donedesc, restrictions, domain, permitted_objs);
+ // und nummer als last_num merken.
+ last_num = nummer;
+ save_info();
+ m_add(by_num, nummer, ({mquestpoints, allowedobj}));
+ MQMLOG(sprintf("AddMiniQuest: %s %O (%s)", allowedobj, miniquests[allowedobj],
+ getuid(this_interactive())));
+
+ ClearUsersMQCache();
+ if (find_call_out(#'DumpMiniQuests) == -1)
+ call_out(#'DumpMiniQuests, 60, this_interactive());
+ return 1;
+}
+
+int RemoveMiniQuest(string name) {
+ if (!allowed_write_access())
+ return 0;
+ // Gibt es einen solchen Eintrag ueberhaupt?
+ if (!member(miniquests,name))
+ return -1;
+
+ MQMLOG(sprintf("RemoveMiniQuest: %s %O (%s)",
+ name, m_entry(miniquests, name), getuid(this_interactive())));
+
+ // MQ aus dem MQ-Indexnummern-Cache loeschen.
+ m_delete(by_num, miniquests[name,MQ_DATA_QUESTNO]);
+ // MQ aus der Miniquestliste austragen.
+ m_delete(miniquests, name);
+ save_info();
+
+ // MQ-Punkte-Cache loeschen, da nicht feststellbar ist, welcher der
+ // dort eingetragenen Spieler die gerade ausgetragene MQ geloest hatte.
+ ClearUsersMQCache();
+ if (find_call_out(#'DumpMiniQuests) == -1)
+ call_out(#'DumpMiniQuests, 60, this_interactive());
+ return 1;
+}
+
+int ChangeMiniQuest(mixed mq_obj, int param, mixed newvalue) {
+ if (!allowed_write_access())
+ return 0;
+
+ // MQ weder als Pfad, noch als Indexnummer angegeben?
+ if ( !stringp(mq_obj) && !intp(mq_obj) && !intp(param))
+ return MQ_KEY_INVALID;
+
+ // gewaehlter Parameter ungueltig?
+ if ( param < MQ_DATA_POINTS || param > MQ_DATA_QUERY_PERMITTED )
+ return MQ_KEY_INVALID;
+
+ // Indexnummer der MQ in den Pfad umwandeln
+ if ( intp(mq_obj) )
+ mq_obj = by_num[mq_obj][1];
+
+ // Vergebendes Objekt nicht gefunden? Bloed, das brauchen wir naemlich.
+ if (!stringp(mq_obj))
+ return MQ_KEY_INVALID;
+
+ if ( !member(miniquests, mq_obj) )
+ return MQ_ILLEGAL_OBJ;
+
+ switch(param) {
+ // MQ_DATA_QUESTNO ist nicht aenderbar, daher hier nicht behandelt, so
+ // dass Fallback auf default erfolgt.
+ // Stufenpunkte muessen Integers sein.
+ case MQ_DATA_POINTS:
+ if ( !intp(newvalue) || newvalue < 1 )
+ return MQ_KEY_INVALID;
+ break;
+ // Aufgabenbeschreibung, Titel, "geschafft"-Text und zugeordnete Region
+ // muessen Strings sein
+ case MQ_DATA_TASKDESC:
+ case MQ_DATA_TITLE:
+ case MQ_DATA_DIARYTEXT:
+ case MQ_DATA_ASSIGNED_DOMAIN:
+ if ( !stringp(newvalue) || !sizeof(newvalue) )
+ return MQ_KEY_INVALID;
+ break;
+ // das Sichtbarkeits- und das aktiv/inaktiv-Flag muessen 0/1 sein.
+ case MQ_DATA_VISIBLE:
+ case MQ_DATA_ACTIVE:
+ if ( !intp(newvalue) || newvalue < 0 || newvalue > 1 )
+ return MQ_KEY_INVALID;
+ break;
+ // Die Voraussetzungen muessen als Mapping eingetragen werden, das aber
+ // leer oder Null sein kann, wenn es keine Restriktionen gibt.
+ case MQ_DATA_RESTRICTIONS:
+ if ( !mappingp(newvalue) && newvalue != 0 )
+ return MQ_KEY_INVALID;
+ break;
+ // Regionszuordnung muss ein nicht-leeres Array sein, das nur aus Strings
+ // bestehen darf, die nicht leer sein duerfen.
+ case MQ_DATA_QUERY_PERMITTED:
+ if ( pointerp(newvalue) ) {
+ newvalue = filter(filter(newvalue, #'stringp), #'sizeof);
+ if (!sizeof(newvalue))
+ return MQ_KEY_INVALID;
+ }
+ else
+ return MQ_KEY_INVALID;
+ break;
+ default:
+ return MQ_KEY_INVALID;
+ }
+
+ mixed *altemq = m_entry(miniquests, mq_obj);
+ int nummer = miniquests[mq_obj,MQ_DATA_QUESTNO];
+ miniquests[mq_obj, param] = newvalue;
+ by_num[nummer] = ({miniquests[mq_obj,MQ_DATA_POINTS], mq_obj});
+ save_info();
+
+ MQMLOG(sprintf("ChangeMiniQuest: %s from %O to %O (%s)", mq_obj,
+ altemq, m_entry(miniquests, mq_obj), getuid(this_interactive())));
+
+ ClearUsersMQCache();
+ if (find_call_out(#'DumpMiniQuests) == -1)
+ call_out(#'DumpMiniQuests, 60, this_interactive());
+ return 1;
+}
+
+mixed QueryMiniQuestByName(string name) {
+ if (!allowed_write_access())
+ return 0;
+ return deep_copy(miniquests & ({name}));
+}
+
+mixed QueryMiniQuestByNumber(int nummer) {
+ // Zugriffsabsicherung erfolgt dort auch, daher hier unnoetig
+ return (by_num[nummer]?QueryMiniQuestByName(by_num[nummer][1]):0);
+}
+
+// Das vollstaendige MQ-Mapping nur als Kopie ausliefern.
+mixed QueryMiniQuests() {
+ return allowed_write_access() ? deep_copy(miniquests) : 0;
+}
+
+// De-/Aktivieren einer Miniquest, wirkt als Umschalter, d.h. eine aktive
+// MQ wird durch Aufruf dieser Funktion als inaktiv markiert und umgekehrt.
+int SwitchMiniQuestActive(string name) {
+ if (!allowed_write_access())
+ return -1;
+ // Haben wir eine solche MQ ueberhaupt?
+ if (!member(miniquests, name))
+ return -2;
+
+ // active-Flag invertieren
+ miniquests[name, MQ_DATA_ACTIVE] = !miniquests[name, MQ_DATA_ACTIVE];
+ save_info();
+
+ MQMLOG(sprintf("%s: %s (%s)",
+ (miniquests[name,MQ_DATA_ACTIVE]?"Activate":"Deactivate"), name,
+ getuid(this_interactive()))
+ );
+ return miniquests[name,MQ_DATA_ACTIVE];
+}
+
+int GiveMiniQuest(object winner) {
+ // Spieler muss existieren und interactive sein.
+ if (!winner ||
+ (this_interactive() && (this_interactive() != winner)) ||
+ ((this_player() == winner) && !query_once_interactive(winner)))
+ return MQ_ILLEGAL_OBJ;
+ // Gaeste koennen keine Miniquests bestehen.
+ if (winner->QueryGuest())
+ return MQ_GUEST;
+ // Aufrufendes Objekt existiert gar nicht?
+ if (!previous_object())
+ return MQ_ILLEGAL_OBJ;
+
+ string objname = load_name(previous_object());
+ // Miniquest muss existieren
+ if (!member(miniquests,objname))
+ return MQ_KEY_INVALID;
+ // Inaktive Miniquests koennen nicht vergeben werden.
+ if (!miniquests[objname, MQ_DATA_ACTIVE])
+ return MQ_IS_INACTIVE;
+
+ string mq = (MASTER->query_mq(getuid(winner)) || "");
+
+ // Spieler hat die MQ schonmal bestanden? Dann keine weiteren Aktivitaet
+ // noetig
+ if (test_bit(mq, miniquests[objname, MQ_DATA_QUESTNO]))
+ return MQ_ALREADY_SET;
+
+ catch(mq = set_bit(mq, miniquests[objname,MQ_DATA_QUESTNO]);publish);
+ MASTER->update_mq(getuid(winner), mq);
+
+ MQSOLVEDLOG(sprintf("%s: %s, (#%d), (Stupse %d)",
+ objname, geteuid(winner), miniquests[objname, MQ_DATA_QUESTNO],
+ miniquests[objname, MQ_DATA_POINTS]));
+
+ // Miniquest-Event ausloesen
+ EVENTD->TriggerEvent( EVT_LIB_MINIQUEST_SOLVED, ([
+ E_OBJECT: previous_object(),
+ E_OBNAME: objname,
+ E_PLNAME: getuid(winner),
+ E_MINIQUESTNAME: miniquests[objname, MQ_DATA_TITLE] ]) );
+
+ // Spielereintrag aus dem MQ-Punkte-Cache loeschen
+ m_delete(users_mq, getuid(winner));
+
+ return 1;
+}
+
+int QueryMiniQuestPoints(mixed pl) {
+ string spieler;
+
+ //if (!allowed_write_access())
+ // return 0;
+
+ if (!pl)
+ return -1;
+
+ if (!objectp(pl) && !stringp(pl))
+ return -2;
+
+ if (objectp(pl) && !query_once_interactive(pl))
+ return -3;
+
+ if (objectp(pl))
+ spieler = getuid(pl);
+ else
+ spieler = pl;
+
+ if (!member(users_mq, spieler)) {
+ int mqpoints;
+ int p=-1;
+ string s = (MASTER->query_mq(spieler) || "");
+ while( (p=next_bit(s, p)) != -1) {
+ mqpoints+=by_num[p][0];
+ }
+ users_mq[spieler] = mqpoints;
+ }
+ return users_mq[spieler];
+}
+
+int HasMiniQuest(mixed pl, mixed name) {
+ string mq, spieler;
+
+ if (!pl || !name)
+ return MQ_ILLEGAL_OBJ;
+
+ if (!objectp(pl) && !stringp(pl))
+ return MQ_ILLEGAL_OBJ;
+
+ if (objectp(pl) && !query_once_interactive(pl))
+ return MQ_ILLEGAL_OBJ;
+
+ if (!objectp(name) && !stringp(name) && !intp(name))
+ return MQ_ILLEGAL_OBJ;
+
+ if (objectp(name))
+ name = explode(object_name(name), "#")[0];
+
+ if ( intp(name) )
+ name = by_num[name][1];
+
+ if (objectp(pl))
+ spieler = getuid(pl);
+ else
+ spieler = pl;
+
+ if (!member(miniquests,name))
+ return MQ_KEY_INVALID;
+
+ mq = (MASTER->query_mq(spieler) || "");
+
+ return test_bit(mq, miniquests[name, MQ_DATA_QUESTNO]);
+}
+
+// Zum Von-Hand-Setzen der MiniQuests
+int SetPlayerMiniQuest(string pl, string name) {
+ if(!allowed_write_access())
+ return 0;
+ if(!pl)
+ return MQ_ILLEGAL_OBJ;
+ if(!previous_object())
+ return MQ_ILLEGAL_OBJ;
+
+ if (!member(miniquests,name))
+ return MQ_KEY_INVALID;
+
+ string mq = (MASTER->query_mq(pl) || "");
+
+ if (test_bit(mq, miniquests[name,MQ_DATA_QUESTNO]))
+ return MQ_ALREADY_SET;
+
+ catch (mq = set_bit(mq, miniquests[name, MQ_DATA_QUESTNO]);publish);
+ MASTER->update_mq(pl, mq);
+
+ MQMLOG(sprintf("SetPlayerMiniQuest: %s %s (%s)",
+ pl, name, getuid(this_interactive())));
+ // Spielereintrag aus dem MQ-Punkte-Cache loeschen
+ m_delete(users_mq, pl);
+ return 1;
+}
+
+int ClearPlayerMiniQuest(string pl, string name) {
+ if (!allowed_write_access())
+ return 0;
+ if (!pl)
+ return MQ_ILLEGAL_OBJ;
+ if (!previous_object())
+ return MQ_ILLEGAL_OBJ;
+
+ if (!member(miniquests,name))
+ return MQ_KEY_INVALID;
+
+ string mq = (MASTER->query_mq(pl) || "");
+
+ if (!test_bit(mq, miniquests[name, MQ_DATA_QUESTNO]))
+ return MQ_ALREADY_SET;
+
+ catch (mq = clear_bit(mq, miniquests[name, MQ_DATA_QUESTNO]);publish);
+ MASTER->update_mq(pl, mq);
+
+ MQMLOG(sprintf("ClearPlayerMiniQuest: %s %s (%s)",
+ pl, name, getuid(this_interactive())));
+ // Spielereintrag aus dem MQ-Punkte-Cache loeschen
+ m_delete(users_mq, pl);
+ return 1;
+}
+
+// Umtragen von Miniquests von Objekt <old> auf Objekt <new>
+int MoveMiniQuest(string old_mqob, string new_mqob) {
+ if ( !allowed_write_access() )
+ return -1;
+
+ // Haben wir ueberhaupt einen solchen Eintrag?
+ if ( !member(miniquests, old_mqob) )
+ return -2;
+
+ // Pruefen, ob die als <new_mqob> angegebene Datei existiert.
+ if (new_mqob[<2..] == ".c")
+ new_mqob = new_mqob[0..<3];
+ new_mqob = explode(new_mqob, "#")[0];
+ new_mqob = (string)"/secure/master"->_get_path(new_mqob,0);
+ if (file_size(new_mqob+".c") <= 0)
+ return -3;
+ // Wenn das neue Objekt schon eine MQ vergibt, kann es keine weitere
+ // annehmen.
+ if ( member(miniquests, new_mqob) )
+ return -4;
+
+ // Der Miniquestliste einen neuen Key "new" mit den Daten des alten Keys
+ // hinzufuegen. m_entry() liefert alle Values dazu als Array, und der
+ // flatten-Operator "..." uebergibt dessen Elemente als einzelne Parameter.
+ m_add(miniquests, new_mqob, m_entry(miniquests, old_mqob)...);
+ m_delete(miniquests, old_mqob);
+ // Nummern-Index auch umtragen, sonst koennen Funktionen wie zB
+ // QueryMiniQuestByNumber() die neue nicht finden.
+ by_num[miniquests[new_mqob,MQ_DATA_QUESTNO]][1] = new_mqob;
+ return 1;
+}
+
+#define FRA_BIB "/d/ebene/miril/fraternitas/room/bibliothek"
+
+// Erlaubt die Abfrage aller MQs einer bestimmten Region fuer die Bibliothek
+// der kleinen und grossen Heldentaten in der Fraternitas.
+// Gibt ein Mapping der Form ([ indexnummer : titel; erledigt_Beschreibung ])
+// zurueck.
+mapping QuerySolvedMQsByDomain(mixed pl, string region) {
+ if ( !objectp(pl) && !stringp(pl) &&
+ load_name(previous_object())!=FRA_BIB) /*|| !allowed_write_access())*/
+ return ([:2]);
+
+ mapping res = m_allocate(30,2); // reicht vermutlich
+ // Die angegebene Region muss in der Spalte MQ_DATA_ASSIGNED_DOMAIN
+ // enthalten sein, und das abfragende Objekt muss die Fraternitas-Bib sein
+ foreach(string mqobj, int mqp, int index, string task, int vis, int act,
+ string title, string donedesc, mapping restr, string domain:
+ miniquests) {
+ // aktive MQs der angeforderten Region zusammentragen, die der Spieler
+ // bestanden hat.
+ if ( domain == region && act && HasMiniQuest(pl, mqobj) )
+ m_add(res, index, title, donedesc);
+ }
+ //DEBUG(sprintf("%O\n",res));
+ return res;
+}
+#undef FRA_BIB
+
+// Abfrage der noch offenen MQs des angegebenen Spielers.
+// Zurueckgegeben wird ein Mapping mit den Miniquest-Nummern als Keys und
+// den Aufgabenbeschreibungen als Values, oder ein leeres Mapping, falls
+// das abfragende Objekt keine Zugriffsberechtigung hat, oder das
+// uebergebene Spielerobjekt keine offenen Miniquests mehr hat.
+mapping QueryOpenMiniQuestsForPlayer(object spieler) {
+ // map() etwas beschleunigen
+ closure chk_restr = symbol_function("check_restrictions",
+ "/std/restriction_checker");
+ // Cache-Eintrag fuer das abfragende Objekt holen
+ string *list = mq_query_permitted[load_name(previous_object())];
+ mapping res = ([:2]);
+
+ if (!pointerp(list) || !sizeof(list))
+ return res;
+ // Liste der MQ-Objekte umwandeln in deren MQ-Nummer plus
+ // Aufgabenbeschreibung, sofern der Spieler die MQ noch nicht bestanden
+ // hat, aber die Voraussetzungen erfuellt.
+ foreach ( string mq_obj : list )
+ {
+ // Nur wenn der Spieler die MQ noch nicht hat, kann er ueberhaupt einen
+ // Tip dazu bekommen.
+ if ( !HasMiniQuest(spieler, mq_obj) ) {
+ // Restriction Checker fragen, ob der Spieler statt des Hinweises
+ // eine Info bekommen muss, dass er eine Vorbedingung nicht erfuellt.
+ string restr_result = funcall(chk_restr, spieler, miniquests[mq_obj,
+ MQ_DATA_RESTRICTIONS]);
+ // Wenn so eine Info dabei rauskommt, wird diese in die Ergebnisliste
+ // uebernommen. In diesem Fall wird KEIN MQ-Hinweistext ausgeliefert.
+ if ( stringp(restr_result) )
+ {
+ m_add(res, miniquests[mq_obj,MQ_DATA_QUESTNO], 0, restr_result);
+ }
+ // Anderenfalls wird der Hinweistext uebernommen; einen Eintrag
+ // bzgl. eines eventuellen Hinderungsgrundes gibt's dann nicht.
+ else
+ {
+ m_add(res, miniquests[mq_obj,MQ_DATA_QUESTNO],
+ miniquests[mq_obj,MQ_DATA_TASKDESC], 0);
+ }
+ }
+ }
+ // Ergebnisliste zurueckgeben.
+ return res;
+}
+
+// Datenkonverter fuer das bisherige MQ-Mapping von
+// ([ obj : ({daten1..daten5}) ]) nach
+// ([ obj : daten1; daten2; ...; daten9 ]), wobei daten6..9 als Leerspalten
+// erzeugt werden.
+/*void ConvertMQData() {
+ if ( !allowed_write_access() )
+ return;
+
+ by_num=([]);
+ // spaltenweise aus dem miniquests-Mapping ein Array aus Arrays erzeugen
+ // Zunaechst die Keys des Mappings aufnehmen.
+ mixed *mqdata = ({m_indices(miniquests)});
+
+ // Dann das Datenarray spaltenweise dazu (jedes Array ist eine Spalte).
+ mqdata += transpose_array(m_values(miniquests));
+ // 1. Array: Keys, alle weiteren jeweils eine Wertespalte
+
+ // Array erzeugen als Array aus 5 Array-Elementen, die alle soviele
+ // Nullen enthalten, wie es Miniquests gibt,
+ // ({ ({0,0,...}), ({0,0,...}), ({0,0,...}), ({0,0,...}), ({0,0,...}) })
+ // dieses hinzufuegen. Sind die hinzukommenden 5 Spalten.
+ mqdata += allocate(5, allocate(sizeof(miniquests),0));
+
+ // Mapping erzeugen, indem mit dem flatten-Operator "..." die Einzel-
+ // Arrays des Datenpakets uebergeben werden. Erzeugt auf diese Weise
+ // ([ keys : daten1; daten2; daten3; daten4; daten5; 0; 0; 0; 0; 0,])
+ miniquests=mkmapping(mqdata...);
+}
+
+// Neue Daten einlesen, Visible-Flag bei allen MQs per Default auf 1 setzen.
+// Es gibt keine wirklich unsichtbaren MQs mehr.
+void ReadNewData() {
+ if ( !allowed_write_access() )
+ return;
+
+ string *import = explode(read_file("/players/arathorn/mqdata"),"\n")-({""});
+ string *fields;
+ foreach(string mqdata : import) {
+ fields = explode(mqdata, "#")-({""});
+ DEBUG(sprintf("%O\n", fields[0]));
+ if ( miniquests[fields[4], MQ_DATA_QUESTNO] != to_int(fields[0]) ) {
+ raise_error("MQ-Nummern stimmen nicht ueberein!\n");
+ return;
+ }
+ // fields[4] ist der MQ-Objektpfad
+ miniquests[fields[4], MQ_DATA_TITLE] = fields[7];
+ miniquests[fields[4], MQ_DATA_DIARYTEXT] = fields[6];
+ miniquests[fields[4], MQ_DATA_TASKDESC] = fields[5];
+ miniquests[fields[4], MQ_DATA_VISIBLE] = 1; // Default: visible
+ miniquests[fields[4], MQ_DATA_ASSIGNED_DOMAIN] = fields[8];
+ miniquests[fields[4], MQ_DATA_QUERY_PERMITTED] = fields[9];
+ if ( fields[3] != "0" ) {
+ miniquests[fields[4], MQ_DATA_RESTRICTIONS] =
+ restore_value(fields[3]+"\n");
+ }
+ else miniquests[fields[4], MQ_DATA_RESTRICTIONS] = 0;
+ if ( fields[9] != "0" )
+ miniquests[fields[4], MQ_DATA_QUERY_PERMITTED] =
+ restore_value(fields[9]+"\n");
+ else miniquests[fields[4], MQ_DATA_QUERY_PERMITTED] = 0;
+ }
+}
+*/