blob: e2aa3006951e9e05d71880a69dd14ce33e7527ca [file] [log] [blame]
#pragma strict_types
#pragma no_clone
#pragma no_shadow
#pragma no_inherit
#pragma verbose_errors
#pragma combine_strings
#pragma rtt_checks
#pragma pedantic
#pragma warn_deprecated
#include "/secure/config.h"
#include "/secure/wizlevels.h"
#define MAX_ROOMS_PER_LIST 10
// die div. Speicherorte und Pfade
#define SAVEFILE "/secure/ARCH/potions"
#define TIPS(x) "/secure/ARCH/ZT/"+x
#define ORAKEL "/room/orakel"
#define POTIONTOOL "/obj/tools/ptool"
// Fuer die Dump-Funktion
#define POTIONDUMP "/secure/ARCH/POTIONS.dump"
#define DUMP(str) write_file(POTIONDUMP, str)
// Modifikationen loggen. "event" ist eins der Events aus der Liste:
// ADD_POTION, ACTIVATE, DEACTIVATE, MODIFY_PATH, MODIFY_LISTNO
#define LOGFILE_MOD "/log/ARCH/POTIONS_MOD.log"
#define MODLOG(event,num,data) write_file(LOGFILE_MOD, \
sprintf("%17s %-14s %-3d sEUID %s %s\n", \
strftime("%Y-%b-%d %R"), event, num, secure_euid(), data) )
// Indizierungs-Konstanten fuer das potions-Mapping
#define POT_ROOMNAME 0
#define POT_LISTNO 1
// Konstanten fuer div. Rueckgabewerte. Keine 0, da das eine gueltige ZT-ID
// sein kann und abfragende Objekte verwirren koennte.
#define POT_IS_ACTIVE 1
#define POT_ACCESS_DENIED -1
#define POT_WRONG_DATATYPE -2
#define POT_NO_SUCH_ROOM -3
#define POT_ALREADY_REGISTERED -4
#define POT_INVALID_POTION -5
#define POT_NO_SUCH_FILE -6
#define POT_INVALID_LIST_NUMBER -7
#define POT_ALREADY_ACTIVE -8
#define POT_ALREADY_INACTIVE -9
#define POT_NOT_INACTIVE -10
#define POT_NOT_ACTIVE -11
// Zaehler fuer den als naechsten anzulegenden ZT
private int nextroom;
// Liste aller ZTs einschl. inaktive, ([ int num : string room; int list ])
private mapping potions = ([]);
// Liste der inaktiven ZTs, ({ int num })
private int *inactive = ({});
// Cache mit den einzelnen Listen, ([ int list : int *potionlist ])
private nosave mapping lists;
// reverse_table Lookup Cache, ([string room: int number])
private nosave mapping reverse_table = ([]);
// Cache fuer die bereits von der Platte eingelesenen ZTs, um Plattenzugriffe
// insbesondere beim Erzeugen der Tipliste durch das Orakel zu reduzieren.
private nosave mapping tipmap = ([]);
int ActivateRoom(string room);
private int secure() {
return (!process_call() && ARCH_SECURITY);
}
mixed QueryPotionData(int num) {
if ( !secure() )
return POT_ACCESS_DENIED;
return ([num : potions[num,0]; potions[num,1] ]);
}
int *QueryInactivePotions() {
return copy(inactive);
}
private void RebuildCache() {
// Cache invalidieren; vor-initialisiert zur Beschleunigung des Rebuilds.
lists = ([0:({}),1:({}),2:({}),3:({}),4:({}),5:({}),6:({}),7:({})]);
reverse_table = ([]);
foreach (int num, string room, int list : potions) {
m_add(reverse_table, room, num);
lists[list] += ({num});
}
return;
}
int QueryActive(int|string potion) {
if ( extern_call() && !secure() )
return POT_ACCESS_DENIED;
int ret;
// Wenn nach dem Pfad des ZTs gefragt wird, diesen zuerst in die Nummer
// umwandeln durch Lookup im reverse_table Cache
if ( stringp(potion) ) {
// Erst nachschauen, ob der ZT in der Liste ist. Wenn man stattdessen
// direkt den Eintrag aus dem Mapping ausliest, wird 0 zurueckgegeben,
// wenn der Eintrag nicht darin enthalten ist. 0 ist aber ein gueltiger
// ZT, d.h. falls ein Raum einen ZT vergibt, dieser aber nicht im
// Master enthalten ist, wird der ZT 0 stattdessen gutgeschrieben.
if ( member(reverse_table, potion) )
potion = reverse_table[potion];
// Falls der ZT nicht bekannt ist, geben wir einen Fehlercode
// zurueck.
else
return POT_INVALID_POTION;
}
// Ein ZT ist aktiv, wenn er in der Liste potions steht und nicht in der
// Liste der inaktiven ZTs (inactive) steht. Dann dessen Nummer
// zurueckgeben; inaktive zuerst pruefen, weil die inaktiven auch in
// "potions" enthalten sind und somit alle ZTs ausser den ungueltigen
// als aktiv gemeldet wuerden.
if ( member(inactive,potion)>-1 )
ret = POT_NOT_ACTIVE;
else if ( member(potions,potion) && potions[potion,POT_LISTNO]!=-1 )
ret = potion;
// ansonsten ist das kein gueltiger ZT
else
ret = POT_INVALID_POTION;
return ret;
}
private void save_info() {
save_object(SAVEFILE);
}
protected void create() {
seteuid(getuid(this_object()));
if ( !restore_object(SAVEFILE) ) {
// Fehler ausgeben, damit es jemand merkt. Dennoch wird jetzt im Anschluss
// der Cache neu aufgebaut (damit die Datenstrukturen gueltig sind).
catch(raise_error("Potionmaster: no/corrupt savefile! Reinitializing.\n"); publish);
}
RebuildCache();
}
int AddPotionRoom(string room, int list) {
if ( !secure() )
return POT_ACCESS_DENIED;
// Neuer Raum muss ein gueltiger String sein.
if ( !stringp(room) )
return POT_WRONG_DATATYPE;
if ( !intp(list) || list<0 || list>7 )
return POT_WRONG_DATATYPE;
// Pfad normalisieren - Expansion von Platzhaltern ist hier ziemlich
// sinnloss und faellt daher weg (Neue ZTs in /players/ gibts eh nicht
// mehr.)
room=({string})master()->make_path_absolute(room);
// Datei mit dem ZT-Spruch muss existieren.
if ( file_size( TIPS(to_string(nextroom)+".zt") ) < 0 ) {
raise_error("Potionmaster: Tipfile missing, please create "+
to_string(nextroom)+".zt");
return POT_NO_SUCH_FILE;
}
// Neuer Raum darf noch nicht in der Liste enthalten sein.
if ( member(m_values(potions,POT_ROOMNAME), room)!=-1)
return POT_ALREADY_REGISTERED;
// Neuer Raum muss ladbar sein.
if ( catch(load_object(room); publish) )
return POT_NO_SUCH_ROOM;
// Jetzt kann's endlich losgehen, Raum eintragen, nextroom hochzaehlen
m_add(potions, nextroom, room, list);
MODLOG("ADD_POTION", nextroom, room);
// Neu eingetragene ZTs werden auch gleich aktiviert; ActivateRoom()
// baut den Cache selbst neu auf, daher kann das hier entfallen.
ActivateRoom(room);
nextroom++;
save_info();
return nextroom;
}
int ChangeRoomPath(string old, string new) {
if ( !secure() )
return POT_ACCESS_DENIED;
// beide Pfade muessen gueltige Strings sein
if ( !stringp(old) || !stringp(new) )
return POT_WRONG_DATATYPE;
// Pfad normalisieren - Expansion von Platzhaltern ist hier ziemlich
// sinnloss und faellt daher weg (Neue ZTs in /players/ gibts eh nicht
// mehr.)
old=({string})master()->make_path_absolute(old);
new=({string})master()->make_path_absolute(new);
// Der neue Raum darf nicht bereits eingetragen sein, ...
if ( member(reverse_table,new) )
return POT_ALREADY_REGISTERED;
// ... und der alte Raum muss noch eingetragen sein.
if ( !member(reverse_table,old) )
return POT_NO_SUCH_ROOM;
// Neuer Raum muss ladbar sein.
if (catch(load_object(new);publish))
return POT_NO_SUCH_ROOM;
// Aktuelle ZT-Nummer des alten Pfades ermitteln.
int num = reverse_table[old];
// Pfad aendern, Cache neubauen und Savefile speichern
potions[num,POT_ROOMNAME] = new;
RebuildCache();
save_info();
MODLOG("MODIFY_PATH", num, old+" => "+new);
return num;
}
int ActivateRoom(string room) {
if ( !secure() )
return POT_ACCESS_DENIED;
// Aktuelle ZT-Nummer ermitteln. Etwas umstaendlich, da im Fehlerfall -1
// benoetigt wird.
int num = member(reverse_table,room) ? reverse_table[room] : -1;
// Nummer muss existieren
if ( num == -1 )
return POT_INVALID_POTION;
// ZT ist nicht inaktiv, dann kann man ihn auch nicht aktivieren.
if ( member(inactive, num)==-1 )
return POT_ALREADY_ACTIVE;
inactive -= ({num});
RebuildCache();
save_info();
MODLOG("ACTIVATE", num, room);
return num;
}
int SetListNr(string room, int list) {
if ( !secure() )
return POT_ACCESS_DENIED;
// Aktuelle ZT-Nummer ermitteln. Etwa umstaendlich, da im Fehlerfall -1
// benoetigt wird.
int num = member(reverse_table,room) ? reverse_table[room] : -1;
// Nummer muss existieren
if ( num == -1 )
return POT_INVALID_POTION;
// Listennummer muss zwischen 0 und 7 liegen.
if ( list < 0 || list > 7 )
return POT_INVALID_LIST_NUMBER;
// alte Nummer aufschreiben zum Loggen.
int oldlist = potions[num,POT_LISTNO];
// Listennummer in der ZT-Liste aktualisieren.
potions[num,POT_LISTNO] = list;
RebuildCache();
save_info();
MODLOG("MODIFY_LISTNO", num, oldlist+" -> "+list);
return num;
}
int DeactivateRoom(string room) {
if ( !secure() )
return POT_ACCESS_DENIED;
// Aktuelle ZT-Nummer ermitteln. Etwa umstaendlich, da im Fehlerfall -1
// benoetigt wird.
int num = member(reverse_table,room) ? reverse_table[room] : -1;
// Nummer muss existieren
if ( num == -1 )
return POT_INVALID_POTION;
// ZT darf nicht bereits inaktiv sein
if ( member(inactive,num)>-1 )
return POT_ALREADY_INACTIVE;
inactive += ({num});
RebuildCache();
save_info();
MODLOG("DEACTIVATE", num, room);
return num;
}
private int *_create_list(int listno, int anz) {
int *list = ({});
// Listenerzeugung lohnt nur dann, wenn mind. 1 Eintrag gefordert wird und
// die Listennummer im gueltigen Bereich zwischen 0 und 7 ist.
if ( anz>0 && listno>=0 && listno<=7 ) {
int *tmp = lists[listno] - inactive;
// Wenn die Listengroesse maximal genauso gross ist wie die angeforderte
// Anzahl, kann diese vollstaendig uebernommen werden.
if ( sizeof(tmp) <= anz ) {
list = tmp;
} else { // ansonsten soviele Eintraege erzeugen wie angefordert
foreach(int i: anz) {
int j=random(sizeof(tmp));
list += ({tmp[j]});
tmp -= ({tmp[j]});
}
}
}
return list;
}
mixed *InitialList() {
mixed *list=({});
foreach(int i : 8)
list+=_create_list(i,MAX_ROOMS_PER_LIST);
return list;
}
// Aufrufe aus den Spielershells und dem Potiontool sind erlaubt
int HasPotion(object room) {
if ( !query_once_interactive(previous_object()) &&
load_name(previous_object()) != POTIONTOOL )
return POT_ACCESS_DENIED;
// Erst nachschauen, ob der ZT in der Liste ist. Wenn man stattdessen
// direkt den Eintrag aus dem Mapping ausliest, wird 0 zurueckgegeben,
// wenn der Eintrag nicht darin enthalten ist. 0 ist aber ein gueltiger
// ZT, und der darf nur rauskommen, wenn er tatsaechlich gemeint ist,
// nicht als Default bei allen unbekannten Raeumen.
if ( objectp(room) && member(reverse_table, object_name(room)) )
return reverse_table[object_name(room)];
else
return POT_NO_SUCH_ROOM;
}
// Listennummer ermitteln, in der der ZT num enthalten ist.
// Auch inaktive ZTs werden als zu einer Liste gehoerig gemeldet.
int GetListByNumber(int num) {
return member(potions,num) ? potions[num,POT_LISTNO] : POT_INVALID_POTION;
}
// Listennummer ermitteln, in der der inaktive ZT num enthalten ist.
// Da alle Zaubertraenke in einer Gesamtliste enthalten sind, kann direkt das
// Resultat von GetListByNumber() zurueckgegeben werden.
int GetInactListByNumber(int num) {
if ( member(inactive,num) > -1 )
return GetListByNumber(num);
else
return POT_NOT_INACTIVE;
}
// Wird nur von /obj/tools/ptool aufgerufen. Wenn der Aufruf zugelassen wird,
// erfolgt von dort aus ein ChangeRoomPath(), und dort wird bereits geloggt.
// Erzmagier duerfen auch aufrufen.
mixed GetFilenameByNumber(int num) {
if ( extern_call() &&
object_name(previous_object()) != POTIONTOOL &&
!ARCH_SECURITY )
return POT_ACCESS_DENIED;
if ( !member(potions, num) )
return POT_INVALID_POTION;
return potions[num,POT_ROOMNAME];
}
// Wird vom Spielerobjekt gerufen; uebergeben werden der Raum, der initial
// FindPotion() aufrief, die ZT-Liste des Spielers sowie die Liste bereits
// gefundener ZTs.
//
// In std/player/potion.c ist sichergestellt, dass room ein Objekt ist und
// dass die Listen als Int-Arrays uebergeben werden.
//
// Es werden aus historischen Gruenden (noch) beide ZT-Listen uebergeben,
// aber es muss nur knownlist ueberprueft werden, da diese eine Teilmenge
// von potionlist ist und somit jeder ZT in ersterer auf jeden Fall auch
// in letzterer enthalten sein _muss_.
int InList(object room, int *potionlist, int *knownlist) {
if ( !query_once_interactive(previous_object()) && !secure() )
return 0; //POT_ACCESS_DENIED;
int num = QueryActive(object_name(room));
// Zunaechst keinen Fehlercode zurueckgeben, weil das Spielerobjekt
// damit im Moment noch nicht umgehen kann.
if ( num < 0 )
return 0; //POT_INVALID_POTION;
return (member(knownlist,num)>-1);
}
// Wird vom Spielerobjekt gerufen, um einen gefundenen ZT aus dessen ZT-
// Listen austragen zu lassen. Das Spielerobjekt stellt sicher, dass room
// ein Objekt ist, und dass [known_]potionrooms Arrays sind.
//
// ACHTUNG! Sowohl potionrooms, als auch known_potionrooms sind die
// Originaldaten aus dem Spielerobjekt, die hier ALS REFERENZ
// uebergeben werden! Vorsicht bei Aenderungen an der Funktion!
//
// Wenn std/player/potions.c geaendert wird, ist diese Funktion obsolet
// => ggf. komplett rauswerfen. Dann kann auch der Fehlercode in InList()
// reaktiviert und deren Parameter korrigiert werden
void RemoveList(object room, int* potionrooms, int* known_potionrooms) {
// ZT ist aktiv, das wurde bereits in InList() geprueft, das vor dem
// Aufruf von RemoveList() aus dem Spielerobjekt gerufen wird. Daher reicht
// es aus, die ZT-Nummer aus dem reverse_table Lookup zu holen.
// Wenn der Pfad allerdings nicht gelistet ist, muss abgebrochen werden.
// Direktes Auslesen ist unguenstig, denn wenn <old> nicht enthalten ist,
// kommt als Default 0 zurueck, aber die Nummer 0 ist ein gueltiger ZT,
// die darf aber nur rauskommen, wenn wirklich der ZT 0 gemeint ist.
int num;
if ( member(reverse_table, object_name(room)) )
num = reverse_table[object_name(room)];
else
return;
int tmp = member(potionrooms, num);
potionrooms[tmp] = -1;
tmp = member(known_potionrooms, num);
known_potionrooms[tmp] = -1;
return;
}
#define LISTHEADER "################## Liste %d ################## (%d)\n\n"
#define INACT_HEADER "################## Inaktiv ################## (%d)\n\n"
int DumpList() {
if ( !secure() )
return POT_ACCESS_DENIED;
// Zuerst die Caches neu aufbauen, um sicherzustellen, dass die Listen
// aktuell sind.
RebuildCache();
// Dumpfile loeschen
rm(POTIONDUMP);
// Alle Listen der Reihe nach ablaufen. Es wird in jedem Schleifendurchlauf
// einmal auf die Platte geschrieben. Das ist Absicht, weil es speicher-
// technisch sehr aufwendig waere, die Ausgabedaten stattdessen erst in
// einer Variablen zu sammeln und dann wegzuschreiben.
foreach(int *listno : sort_array(m_indices(lists),#'>)) {
// Zuerst den Header der ensprechenden Liste schreiben.
DUMP(sprintf(LISTHEADER, listno, sizeof(lists[listno])));
// Dann alle Potions der Liste durchgehen
foreach(int potion : lists[listno]) {
// Wenn der ZT inaktiv ist, ueberspringen wir den.
if ( member(inactive,potion)>-1 )
continue;
// Raumpfad aus der Gesamtliste holen.
string str = potions[potion,POT_ROOMNAME];
// Wenn der nicht existiert, entsprechenden Hinweis dumpen, ansonsten
// den Raumnamen.
//ZZ: ich finde ja, wir sollten sicherstellen, dass das nicht mehr
//passiert. Ist das mit dem AddPotionRoom oben nicht schon so?
if ( !stringp(str) || !sizeof(str) )
str="KEIN RAUM ZUGEORDNET!!!";
DUMP(sprintf("%3d. %s\n", potion, str));
}
// 2 Leerzeilen zwischen jeder Gruppe einfuegen
DUMP("\n\n");
}
// Zum Schluss den Header der Inaktiv-Liste schreiben.
DUMP(sprintf(INACT_HEADER, sizeof(inactive)));
// Dann alle inaktiven Potions ablaufen
foreach(int i : inactive) {
//ZZ: sonst muesste man hier wohl auch auf den fehlenden Raum pruefen.
DUMP(sprintf("%3d. (Liste %d) %s\n", i, GetListByNumber(i),
potions[i,POT_ROOMNAME]));
}
return 1;
}
//ORAKEL-Routinen
/* Aufbau eines Tips:
Tralala, lalala
Dideldadeldum
Huppsdiwupps
XXXXX
Noch ein zweiter Tip
Dingeldong
%%%%%
Die Prozentzeichen sind das Endezeichen, ab hier koennen eventuelle
Kommentare stehen. Die 5 X sind das Trennzeichen zwischen zwei Tips
zum selben ZT.
*/
// TIPLOG() derzeit unbenutzt
//#define LOGFILE_READ "ARCH/POTIONS_TIP_READ.log"
//#define TIPLOG(x) log_file(LOGFILE_READ, x)
mixed TipLesen(int num) {
string *ret = tipmap[num];
//Funktion darf nur vom Orakel und EM+ gerufen werden.
if ( previous_object() != find_object(ORAKEL) && !secure() )
return POT_ACCESS_DENIED;
// Derzeit kein Log, da diese Informationen tendentiell eher
// uninteressant sind.
// Timestamp ist vom Format "2012-Okt-03 14:18"
/*TIPLOG(sprintf("%s ZT-Tip gelesen: %d von TP: %O, PO: %O, TI: %O\n",
strftime("%Y-%b-%d %R"), num, this_player(), previous_object(),
this_interactive()));*/
// ZT-Spruch ist bereits im Tip-Cache enthalten, dann direkt ausgeben.
if ( pointerp(ret) )
return ret;
// ZT-Spruch auf grundlegende syntaktische Korrektheit pruefen:
// explode() liefert ein Array mit 2 Elementen, wenn die Ende-Markierung
// vorhanden und somit das File diesbezueglich korrekt aufgebaut ist.
string *tip=explode( read_file(TIPS(num+".zt"))||"", "%%%%%" );
if (sizeof(tip) >= 2) {
// Der erste Eintrag in dem Array ist dann der Spruch. Wenn dieser
// die Trenn-Markierung enthaelt, entstehen hier 2 Hinweise, ansonsten
// bleibt es bei einem.
tipmap[num] = explode(tip[0], "XXXXX\n");
return tipmap[num];
}
return POT_NO_SUCH_FILE;
}
// Datenkonvertierung alte Alists => Mappings
/*mapping ConvertData() {
//if ( !secure()) ) return;
// all_rooms ist eine Alist der Form ({ string *room, int *number })
// als 3. Eintrag schreiben wir ueberall -1 rein, das wird spaeter durch die
// Listennummer des betreffenden ZTs ersetzt und erlaubt einen einfachen
// Check auf Datenkonsistenz.
potions = mkmapping( all_rooms[1],
all_rooms[0],
allocate(sizeof(all_rooms[0]), -1) );
// Alle Listen ablaufen
foreach(int *list: active_rooms ) {
// Alle ZTs dieser Liste ablaufen und die entsprechende Listennummer in
// der neuen Datenstruktur setzen.
foreach(int num: list) {
potions[num,POT_LISTNO] = member(active_rooms,list);
}
}
// inactive_rooms ist eine Alist der gleichen Form. Die Schleife addiert
// einfach alle Eintraege darin auf, so dass ein Int-Array entsteht, das
// alle inaktiven ZT-Nummern enthaelt; das allerdings nur fuer Listen mit
// mindestens einem Element.
foreach(int *list : inactive_rooms ) {
if ( sizeof(list) ) {
inactive += list;
// Alle ZTs dieser Liste ablaufen und die entsprechende Listennummer in
// der neuen Datenstruktur setzen.
foreach(int num : list) {
potions[num,POT_LISTNO] = member(inactive_rooms,list);
}
}
// unify array
inactive = m_indices(mkmapping(inactive));
}
mapping invalid = ([ POT_INVALID_LIST_NUMBER : ({}) ]);
walk_mapping(potions, function void (int pnum, string path, int listno) {
if ( listno == -1 )
invalid[POT_INVALID_LIST_NUMBER] += ({pnum});
});
return sizeof(invalid[POT_INVALID_LIST_NUMBER]) ? invalid : potions;
}*/