blob: 78a056589dc06610c036a8d79917f214541734af [file] [log] [blame]
MG Mud User88f12472016-06-24 23:31:02 +02001#pragma strict_types
2#pragma no_clone
3#pragma no_shadow
4#pragma no_inherit
5#pragma verbose_errors
6#pragma combine_strings
7#pragma rtt_checks
8#pragma pedantic
9#pragma warn_deprecated
10
11#include "/secure/config.h"
12#include "/secure/wizlevels.h"
13
14#define MAX_ROOMS_PER_LIST 10
15
16// die div. Speicherorte und Pfade
17#define SAVEFILE "/secure/ARCH/potions"
18#define TIPS(x) "/secure/ARCH/ZT/"+x
19#define ORAKEL "/room/orakel"
20#define POTIONTOOL "/obj/tools/ptool"
21
22// Fuer die Dump-Funktion
23#define POTIONDUMP "/secure/ARCH/POTIONS.dump"
24#define DUMP(str) write_file(POTIONDUMP, str)
25
26// Modifikationen loggen. "event" ist eins der Events aus der Liste:
27// ADD_POTION, ACTIVATE, DEACTIVATE, MODIFY_PATH, MODIFY_LISTNO
28#define LOGFILE_MOD "/log/ARCH/POTIONS_MOD.log"
29#define MODLOG(event,num,data) write_file(LOGFILE_MOD, \
30 sprintf("%17s %-14s %-3d sEUID %s %s\n", \
31 strftime("%Y-%b-%d %R"), event, num, secure_euid(), data) )
32
33// Indizierungs-Konstanten fuer das potions-Mapping
34#define POT_ROOMNAME 0
35#define POT_LISTNO 1
36
37// Konstanten fuer div. Rueckgabewerte. Keine 0, da das eine gueltige ZT-ID
38// sein kann und abfragende Objekte verwirren koennte.
39#define POT_IS_ACTIVE 1
40#define POT_ACCESS_DENIED -1
41#define POT_WRONG_DATATYPE -2
42#define POT_NO_SUCH_ROOM -3
43#define POT_ALREADY_REGISTERED -4
44#define POT_INVALID_POTION -5
45#define POT_NO_SUCH_FILE -6
46#define POT_INVALID_LIST_NUMBER -7
47#define POT_ALREADY_ACTIVE -8
48#define POT_ALREADY_INACTIVE -9
49#define POT_NOT_INACTIVE -10
50#define POT_NOT_ACTIVE -11
51
52// Zaehler fuer den als naechsten anzulegenden ZT
53private int nextroom;
54
55// Liste aller ZTs einschl. inaktive, ([ int num : string room; int list ])
56private mapping potions = ([]);
57
58// Liste der inaktiven ZTs, ({ int num })
59private int *inactive = ({});
60
61// Cache mit den einzelnen Listen, ([ int list : int *potionlist ])
62private nosave mapping lists;
63
64// reverse_table Lookup Cache, ([string room: int number])
65private nosave mapping reverse_table = ([]);
66
67// Cache fuer die bereits von der Platte eingelesenen ZTs, um Plattenzugriffe
68// insbesondere beim Erzeugen der Tipliste durch das Orakel zu reduzieren.
69private nosave mapping tipmap = ([]);
70
71int ActivateRoom(string room);
72
73private int secure() {
74 return (!process_call() && ARCH_SECURITY);
75}
76
77mixed QueryPotionData(int num) {
78 if ( !secure() )
79 return POT_ACCESS_DENIED;
80 return ([num : potions[num,0]; potions[num,1] ]);
81}
82
83int *QueryInactivePotions() {
84 return copy(inactive);
85}
86
87private void RebuildCache() {
88 // Cache invalidieren; vor-initialisiert zur Beschleunigung des Rebuilds.
89 lists = ([0:({}),1:({}),2:({}),3:({}),4:({}),5:({}),6:({}),7:({})]);
90 foreach (int num, string room, int list : potions) {
91 reverse_table += ([room:num]);
92 lists[list] += ({num});
93 }
94 return;
95}
96
97int QueryActive(mixed potion) {
98 if ( extern_call() && !secure() )
99 return POT_ACCESS_DENIED;
100 int ret;
101 // Wenn nach dem Pfad des ZTs gefragt wird, diesen zuerst in die Nummer
102 // umwandeln durch Lookup im reverse_table Cache
103 if ( stringp(potion) ) {
104 potion = reverse_table[potion];
105 }
106 // Ein ZT ist aktiv, wenn er in der Liste potions steht und nicht in der
107 // Liste der inaktiven ZTs (inactive) steht. Dann dessen Nummer
108 // zurueckgeben; inaktive zuerst pruefen, weil die inaktiven auch in
109 // "potions" enthalten sind und somit alle ZTs ausser den ungueltigen
110 // als aktiv gemeldet wuerden.
111 if ( member(inactive,potion)>-1 )
112 ret = POT_NOT_ACTIVE;
113 else if ( member(potions,potion) && potions[potion,POT_LISTNO]!=-1 )
114 ret = potion;
115 // ansonsten ist das kein gueltiger ZT
116 else
117 ret = POT_INVALID_POTION;
118 return ret;
119}
120
121private void save_info() {
122 save_object(SAVEFILE);
123}
124
125protected void create() {
126 seteuid(getuid(this_object()));
127 if ( !restore_object(SAVEFILE) ) {
128 // Fehler ausgeben, damit es jemand merkt. Dennoch wird jetzt im Anschluss
129 // der Cache neu aufgebaut (damit die Datenstrukturen gueltig sind).
130 catch(raise_error("Potionmaster: no/corrupt savefile! Reinitializing.\n"); publish);
131 }
132 RebuildCache();
133}
134
135int AddPotionRoom(string room, int list) {
136 if ( !secure() )
137 return POT_ACCESS_DENIED;
138 // Neuer Raum muss ein gueltiger String sein.
139 if ( !stringp(room) )
140 return POT_WRONG_DATATYPE;
141 if ( !intp(list) || list<0 || list>7 )
142 return POT_WRONG_DATATYPE;
143
144 // Pfad mit Hilfe des Masters vervollstaendigen (+, ~ etc. ersetzen)
145 room=(string)master()->_get_path(room,0);
146
147 // Datei mit dem ZT-Spruch muss existieren.
148 if ( file_size( TIPS(to_string(nextroom)+".zt") ) < 0 ) {
149 raise_error("Potionmaster: Tipfile missing, please create "+
150 to_string(nextroom)+".zt");
151 return POT_NO_SUCH_FILE;
152 }
153 // Neuer Raum darf noch nicht in der Liste enthalten sein.
154 if ( member(m_values(potions,POT_ROOMNAME), room)!=-1)
155 return POT_ALREADY_REGISTERED;
156 // Neuer Raum muss ladbar sein.
157 if ( catch(load_object(room); publish) )
158 return POT_NO_SUCH_ROOM;
159
160 // Jetzt kann's endlich losgehen, Raum eintragen, nextroom hochzaehlen
161 potions += ([nextroom : room; list]);
162 MODLOG("ADD_POTION", nextroom, room);
163 // Neu eingetragene ZTs werden auch gleich aktiviert; ActivateRoom()
164 // baut den Cache selbst neu auf, daher kann das hier entfallen.
165 ActivateRoom(room);
166 nextroom++;
167 save_info();
168 return nextroom;
169}
170
171int ChangeRoomPath(string old, string new) {
172 if ( !secure() )
173 return POT_ACCESS_DENIED;
174
175 // beide Pfade muessen gueltige Strings sein
176 if ( !stringp(old) || !stringp(new) )
177 return POT_WRONG_DATATYPE;
178
179 // Pfad mit Hilfe des Masters vervollstaendigen (+, ~ etc. ersetzen)
180 old=(string)master()->_get_path(old,0);
181 new=(string)master()->_get_path(new,0);
182
183 // Der neue Raum darf nicht bereits eingetragen sein, ...
184 if ( member(reverse_table,new) )
185 return POT_ALREADY_REGISTERED;
186 // ... und der alte Raum muss noch eingetragen sein.
187 if ( !member(reverse_table,old) )
188 return POT_NO_SUCH_ROOM;
189 // Neuer Raum muss ladbar sein.
190 if (catch(load_object(new);publish))
191 return POT_NO_SUCH_ROOM;
192
193 // Aktuelle ZT-Nummer des alten Pfades ermitteln
194 int num = reverse_table[old];
195 // Pfad aendern, Cache neubauen und Savefile speichern
196 potions[num,POT_ROOMNAME] = new;
197 RebuildCache();
198 save_info();
199 MODLOG("MODIFY_PATH", num, old+" => "+new);
200 return num;
201}
202
203int ActivateRoom(string room) {
204 if ( !secure() )
205 return POT_ACCESS_DENIED;
206 // Aktuelle ZT-Nummer ermitteln. Etwas umstaendlich, da im Fehlerfall -1
207 // benoetigt wird.
208 int num = member(reverse_table,room) ? reverse_table[room] : -1;
209 // Nummer muss existieren
210 if ( num == -1 )
211 return POT_INVALID_POTION;
212 // ZT ist nicht inaktiv, dann kann man ihn auch nicht aktivieren.
213 if ( member(inactive, num)==-1 )
214 return POT_ALREADY_ACTIVE;
215 inactive -= ({num});
216 RebuildCache();
217 save_info();
218 MODLOG("ACTIVATE", num, room);
219 return num;
220}
221
222int SetListNr(string room, int list) {
223 if ( !secure() )
224 return POT_ACCESS_DENIED;
225 // Aktuelle ZT-Nummer ermitteln. Etwa umstaendlich, da im Fehlerfall -1
226 // benoetigt wird.
227 int num = member(reverse_table,room) ? reverse_table[room] : -1;
228 // Nummer muss existieren
229 if ( num == -1 )
230 return POT_INVALID_POTION;
231 // Listennummer muss zwischen 0 und 7 liegen.
232 if ( list < 0 || list > 7 )
233 return POT_INVALID_LIST_NUMBER;
234
235 // alte Nummer aufschreiben zum Loggen.
236 int oldlist = potions[num,POT_LISTNO];
237 // Listennummer in der ZT-Liste aktualisieren.
238 potions[num,POT_LISTNO] = list;
239 RebuildCache();
240 save_info();
241 MODLOG("MODIFY_LISTNO", num, oldlist+" -> "+list);
242 return num;
243}
244
245int DeactivateRoom(string room) {
246 if ( !secure() )
247 return POT_ACCESS_DENIED;
248 // Aktuelle ZT-Nummer ermitteln. Etwa umstaendlich, da im Fehlerfall -1
249 // benoetigt wird.
250 int num = member(reverse_table,room) ? reverse_table[room] : -1;
251 // Nummer muss existieren
252 if ( num == -1 )
253 return POT_INVALID_POTION;
254 // ZT darf nicht bereits inaktiv sein
255 if ( member(inactive,num)>-1 )
256 return POT_ALREADY_INACTIVE;
257 inactive += ({num});
258 RebuildCache();
259 save_info();
260 MODLOG("DEACTIVATE", num, room);
261 return num;
262}
263
264private int *_create_list(int listno, int anz) {
265 int *list = ({});
266 // Listenerzeugung lohnt nur dann, wenn mind. 1 Eintrag gefordert wird und
267 // die Listennummer im gueltigen Bereich zwischen 0 und 7 ist.
268 if ( anz>0 && listno>=0 && listno<=7 ) {
269 int *tmp = lists[listno] - inactive;
270 // Wenn die Listengroesse maximal genauso gross ist wie die angeforderte
271 // Anzahl, kann diese vollstaendig uebernommen werden.
272 if ( sizeof(tmp) <= anz ) {
273 list = tmp;
274 } else { // ansonsten soviele Eintraege erzeugen wie angefordert
275 foreach(int i: anz) {
276 int j=random(sizeof(tmp));
277 list += ({tmp[j]});
278 tmp -= ({tmp[j]});
279 }
280 }
281 }
282 return list;
283}
284
285mixed *InitialList() {
286 mixed *list=({});
287 foreach(int i : 8)
288 list+=_create_list(i,MAX_ROOMS_PER_LIST);
289 return list;
290}
291
292// Aufrufe aus den Spielershells und dem Potiontool sind erlaubt
293int HasPotion(object room) {
294 if ( !query_once_interactive(previous_object()) &&
295 load_name(previous_object()) != POTIONTOOL )
296 return POT_ACCESS_DENIED;
297 return objectp(room) ? reverse_table[object_name(room)] : POT_NO_SUCH_ROOM;
298}
299
300// Listennummer ermitteln, in der der ZT num enthalten ist.
301// Auch inaktive ZTs werden als zu einer Liste gehoerig gemeldet.
302int GetListByNumber(int num) {
303 return member(potions,num) ? potions[num,POT_LISTNO] : POT_INVALID_POTION;
304}
305
306// Listennummer ermitteln, in der der inaktive ZT num enthalten ist.
307// Da alle Zaubertraenke in einer Gesamtliste enthalten sind, kann direkt das
308// Resultat von GetListByNumber() zurueckgegeben werden.
309int GetInactListByNumber(int num) {
310 if ( member(inactive,num) > -1 )
311 return GetListByNumber(num);
312 else
313 return POT_NOT_INACTIVE;
314}
315
316// Wird nur von /obj/tools/ptool aufgerufen. Wenn der Aufruf zugelassen wird,
317// erfolgt von dort aus ein ChangeRoomPath(), und dort wird bereits geloggt.
318// Erzmagier duerfen auch aufrufen.
319mixed GetFilenameByNumber(int num) {
320 if ( extern_call() &&
321 object_name(previous_object()) != POTIONTOOL &&
322 !ARCH_SECURITY )
323 return POT_ACCESS_DENIED;
324 if ( !member(potions, num) )
325 return POT_INVALID_POTION;
326 return potions[num,POT_ROOMNAME];
327}
328
329// Wird vom Spielerobjekt gerufen; uebergeben werden der Raum, der initial
330// FindPotion() aufrief, die ZT-Liste des Spielers sowie die Liste bereits
331// gefundener ZTs.
332//
333// In std/player/potion.c ist sichergestellt, dass room ein Objekt ist und
334// dass die Listen als Int-Arrays uebergeben werden.
335//
336// Es werden aus historischen Gruenden (noch) beide ZT-Listen uebergeben,
337// aber es muss nur knownlist ueberprueft werden, da diese eine Teilmenge
338// von potionlist ist und somit jeder ZT in ersterer auf jeden Fall auch
339// in letzterer enthalten sein _muss_.
340int InList(object room, int *potionlist, int *knownlist) {
341 if ( !query_once_interactive(previous_object()) && !secure() )
342 return 0; //POT_ACCESS_DENIED;
343 int num = QueryActive(object_name(room));
344
345 // Zunaechst keinen Fehlercode zurueckgeben, weil das Spielerobjekt
346 // damit im Moment noch nicht umgehen kann.
347 if ( num < 0 )
348 return 0; //POT_INVALID_POTION;
349
350 return (member(knownlist,num)>-1);
351}
352
353// Wird vom Spielerobjekt gerufen, um einen gefundenen ZT aus dessen ZT-
354// Listen austragen zu lassen. Das Spielerobjekt stellt sicher, dass room
355// ein Objekt ist, und dass [known_]potionrooms Arrays sind.
356//
357// ACHTUNG! Sowohl potionrooms, als auch known_potionrooms sind die
358// Originaldaten aus dem Spielerobjekt, die hier ALS REFERENZ
359// uebergeben werden! Vorsicht bei Aenderungen an der Funktion!
360//
361// Wenn std/player/potions.c geaendert wird, ist diese Funktion obsolet
362// => ggf. komplett rauswerfen. Dann kann auch der Fehlercode in InList()
363// reaktiviert und deren Parameter korrigiert werden
364void RemoveList(object room, int* potionrooms, int* known_potionrooms) {
365 // ZT ist aktiv, das wurde bereits in InList() geprueft, das vor dem
366 // Aufruf von RemoveList() aus dem Spielerobjekt gerufen wird. Daher reicht
367 // es aus, die ZT-Nummer aus dem reverse_table Lookup zu holen.
368 int num = reverse_table[object_name(room)];
369 int tmp = member(potionrooms, num);
370 potionrooms[tmp] = -1;
371 tmp = member(known_potionrooms, num);
372 known_potionrooms[tmp] = -1;
373 return;
374}
375
376#define LISTHEADER "################## Liste %d ################## (%d)\n\n"
377#define INACT_HEADER "################## Inaktiv ################## (%d)\n\n"
378
379int DumpList() {
380 if ( !secure() )
381 return POT_ACCESS_DENIED;
382 // Zuerst die Caches neu aufbauen, um sicherzustellen, dass die Listen
383 // aktuell sind.
384 RebuildCache();
385 // Dumpfile loeschen
386 rm(POTIONDUMP);
387 // Alle Listen der Reihe nach ablaufen. Es wird in jedem Schleifendurchlauf
388 // einmal auf die Platte geschrieben. Das ist Absicht, weil es speicher-
389 // technisch sehr aufwendig waere, die Ausgabedaten stattdessen erst in
390 // einer Variablen zu sammeln und dann wegzuschreiben.
391 foreach(int *listno : sort_array(m_indices(lists),#'>)) {
392 // Zuerst den Header der ensprechenden Liste schreiben.
393 DUMP(sprintf(LISTHEADER, listno, sizeof(lists[listno])));
394 // Dann alle Potions der Liste durchgehen
395 foreach(int potion : lists[listno]) {
396 // Wenn der ZT inaktiv ist, ueberspringen wir den.
397 if ( member(inactive,potion)>-1 )
398 continue;
399 // Raumpfad aus der Gesamtliste holen.
400 string str = potions[potion,POT_ROOMNAME];
401 // Wenn der nicht existiert, entsprechenden Hinweis dumpen, ansonsten
402 // den Raumnamen.
403 //ZZ: ich finde ja, wir sollten sicherstellen, dass das nicht mehr
404 //passiert. Ist das mit dem AddPotionRoom oben nicht schon so?
405 if ( !stringp(str) || !sizeof(str) )
406 str="KEIN RAUM ZUGEORDNET!!!";
407 DUMP(sprintf("%3d. %s\n", potion, str));
408 }
409 // 2 Leerzeilen zwischen jeder Gruppe einfuegen
410 DUMP("\n\n");
411 }
412 // Zum Schluss den Header der Inaktiv-Liste schreiben.
413 DUMP(sprintf(INACT_HEADER, sizeof(inactive)));
414 // Dann alle inaktiven Potions ablaufen
415 foreach(int i : inactive) {
416 //ZZ: sonst muesste man hier wohl auch auf den fehlenden Raum pruefen.
417 DUMP(sprintf("%3d. (Liste %d) %s\n", i, GetListByNumber(i),
418 potions[i,POT_ROOMNAME]));
419 }
420 return 1;
421}
422
423//ORAKEL-Routinen
424
425/* Aufbau eines Tips:
426
427 Tralala, lalala
428 Dideldadeldum
429 Huppsdiwupps
430 XXXXX
431 Noch ein zweiter Tip
432 Dingeldong
433 %%%%%
434
435 Die Prozentzeichen sind das Endezeichen, ab hier koennen eventuelle
436 Kommentare stehen. Die 5 X sind das Trennzeichen zwischen zwei Tips
437 zum selben ZT.
438*/
439
440// TIPLOG() derzeit unbenutzt
441//#define LOGFILE_READ "ARCH/POTIONS_TIP_READ.log"
442//#define TIPLOG(x) log_file(LOGFILE_READ, x)
443
444mixed TipLesen(int num) {
445 string *ret = tipmap[num];
446 //Funktion darf nur vom Orakel und EM+ gerufen werden.
447 if ( previous_object() != find_object(ORAKEL) && !secure() )
448 return POT_ACCESS_DENIED;
449 // Derzeit kein Log, da diese Informationen tendentiell eher
450 // uninteressant sind.
451 // Timestamp ist vom Format "2012-Okt-03 14:18"
452 /*TIPLOG(sprintf("%s ZT-Tip gelesen: %d von TP: %O, PO: %O, TI: %O\n",
453 strftime("%Y-%b-%d %R"), num, this_player(), previous_object(),
454 this_interactive()));*/
455
456 // ZT-Spruch ist bereits im Tip-Cache enthalten, dann direkt ausgeben.
457 if ( pointerp(ret) )
458 return ret;
459
460 // ZT-Spruch auf grundlegende syntaktische Korrektheit pruefen:
461 // explode() liefert ein Array mit 2 Elementen, wenn die Ende-Markierung
462 // vorhanden und somit das File diesbezueglich korrekt aufgebaut ist.
463 string *tip=explode( read_file(TIPS(num+".zt"))||"", "%%%%%" );
464 if (sizeof(tip) >= 2) {
465 // Der erste Eintrag in dem Array ist dann der Spruch. Wenn dieser
466 // die Trenn-Markierung enthaelt, entstehen hier 2 Hinweise, ansonsten
467 // bleibt es bei einem.
468 tipmap[num] = explode(tip[0], "XXXXX\n");
469 return tipmap[num];
470 }
471 return POT_NO_SUCH_FILE;
472}
473
474// Datenkonvertierung alte Alists => Mappings
475/*mapping ConvertData() {
476 //if ( !secure()) ) return;
477 // all_rooms ist eine Alist der Form ({ string *room, int *number })
478 // als 3. Eintrag schreiben wir ueberall -1 rein, das wird spaeter durch die
479 // Listennummer des betreffenden ZTs ersetzt und erlaubt einen einfachen
480 // Check auf Datenkonsistenz.
481 potions = mkmapping( all_rooms[1],
482 all_rooms[0],
483 allocate(sizeof(all_rooms[0]), -1) );
484 // Alle Listen ablaufen
485 foreach(int *list: active_rooms ) {
486 // Alle ZTs dieser Liste ablaufen und die entsprechende Listennummer in
487 // der neuen Datenstruktur setzen.
488 foreach(int num: list) {
489 potions[num,POT_LISTNO] = member(active_rooms,list);
490 }
491 }
492 // inactive_rooms ist eine Alist der gleichen Form. Die Schleife addiert
493 // einfach alle Eintraege darin auf, so dass ein Int-Array entsteht, das
494 // alle inaktiven ZT-Nummern enthaelt; das allerdings nur fuer Listen mit
495 // mindestens einem Element.
496 foreach(int *list : inactive_rooms ) {
497 if ( sizeof(list) ) {
498 inactive += list;
499 // Alle ZTs dieser Liste ablaufen und die entsprechende Listennummer in
500 // der neuen Datenstruktur setzen.
501 foreach(int num : list) {
502 potions[num,POT_LISTNO] = member(inactive_rooms,list);
503 }
504 }
505 // unify array
506 inactive = m_indices(mkmapping(inactive));
507 }
508 mapping invalid = ([ POT_INVALID_LIST_NUMBER : ({}) ]);
509 walk_mapping(potions, function void (int pnum, string path, int listno) {
510 if ( listno == -1 )
511 invalid[POT_INVALID_LIST_NUMBER] += ({pnum});
512 });
513 return sizeof(invalid[POT_INVALID_LIST_NUMBER]) ? invalid : potions;
514}*/
515