blob: 314a97b30a625437a33e7068df38f2fcc4792374 [file] [log] [blame]
// 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;
}
}
*/