| // 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; |
| } |
| } |
| */ |