Added public files
Roughly added all public files. Probably missed some, though.
diff --git a/p/daemon/ch.h b/p/daemon/ch.h
new file mode 100644
index 0000000..0afed78
--- /dev/null
+++ b/p/daemon/ch.h
@@ -0,0 +1,10 @@
+// ch.h - channel daemon defines
+// --
+// $timestamp::$
+
+// existiert aus Kompatibilitaetsgruenden, weil die haelfte vom Mud dieses und
+// die andere Haelfte channel.h inkludiert. Entscheidung, den eindeutigeren
+// Namen channel.h zu benutzen und dieses einfach channel.h inkludieren zu
+// lassen.
+#include "channel.h"
+
diff --git a/p/daemon/channel.h b/p/daemon/channel.h
new file mode 100644
index 0000000..ad1f506
--- /dev/null
+++ b/p/daemon/channel.h
@@ -0,0 +1,59 @@
+// ch.h - channel daemon defines
+// --
+// $timestamp::$
+
+#ifndef __DAEMON_CHANNEL_H__
+#define __DAEMON_CHANNEL_H__
+
+#define P_CHANNELS "channels"
+#define P_STD_CHANNEL "std_channel"
+
+#define CHMASTER "/p/daemon/channeld"
+
+// Message types
+#define MSG_SAY 0
+#define MSG_EMOTE 1
+#define MSG_GEMOTE 2
+#define MSG_EMPTY 3
+
+// Errors
+#define E_ACCESS_DENIED -1
+#define E_ALREADY_JOINED -2
+#define E_NOT_MEMBER -3
+#define E_EMPTY_MESSAGE -4
+#define E_UNKNOWN_TYPE -5
+
+// Commands
+#define C_NEW "new"
+#define C_JOIN "join"
+#define C_LEAVE "leave"
+#define C_SEND "send"
+#define C_LIST "list"
+#define C_FIND "find"
+
+// definition of the list mapping entry
+// ([ channelname : ({ I_MEMBER, I_ACCESS, I_INFO, I_MASTER, I_NAME }) ])
+#define I_MEMBER 0
+#define I_ACCESS 1
+#define I_INFO 2
+#define I_MASTER 3
+#define I_NAME 4
+
+#endif //__DAEMON_CHANNEL_H__
+
+// prototypes
+#ifdef NEED_PROTOTYPES
+
+#ifndef __CHANNEL_H_PROTO__
+#define __CHANNEL_H_PROTO__
+varargs int new(string ch, object pl, mixed info);
+varargs int send(string ch, object pl, string msg, int type);
+
+// ok, keine Prototypen, aber trotzdem nur fuer channeld.c interessant.
+#define MAX_HIST_SIZE 200
+#define MAX_CHANNELS 90
+
+#endif //__CHANNEL_H_PROTO__
+
+#endif //NEED_PROTOTYPES
+
diff --git a/p/daemon/channeld.c b/p/daemon/channeld.c
new file mode 100644
index 0000000..1331d57
--- /dev/null
+++ b/p/daemon/channeld.c
@@ -0,0 +1,647 @@
+// channeld.c
+//
+// $Id: channeld.c 9138 2015-02-03 21:46:56Z Zesstra $
+//
+
+#pragma strong_types
+#pragma no_shadow // keine Shadowing...
+#pragma no_clone
+#pragma no_inherit
+#pragma save_types
+
+#include <sys_debug.h>
+#include <lpctypes.h>
+#include <wizlevels.h>
+
+#include <properties.h>
+#include <config.h>
+#include <language.h>
+
+#define NEED_PROTOTYPES
+#include "channel.h"
+
+#define CMNAME "<MasteR>"
+#define CHANNEL_SAVE "/p/daemon/save/channeld"
+
+#define MEMORY "/secure/memory"
+
+// channels - contains the simple channel information
+// ([ channelname : ({ I_MEMBER, I_ACCESS, I_INFO, I_MASTER })...])
+private nosave mapping lowerch;
+private nosave mapping channels;
+private nosave mapping channelH;
+private nosave mapping stats;
+
+private mapping channelC;
+private mapping channelB;
+
+private int save_me_soon;
+
+// BEGIN OF THE CHANNEL MASTER ADMINISTRATIVE PART
+
+#define RECV 0
+#define SEND 1
+#define FLAG 2
+
+// Channel flags
+// Levelbeschraenkungen gegen Magierlevel (query_wiz_level) pruefen, nicht
+// P_LEVEL.
+#define F_WIZARD 1
+// Keine Gaeste. ;-)
+#define F_NOGUEST 2
+
+private nosave mapping admin = m_allocate(0, 3);
+
+int check(string ch, object pl, string cmd)
+{
+ int level;
+
+ if((admin[ch, FLAG] & F_NOGUEST) && pl->QueryGuest()) return 0;
+
+ if((admin[ch, FLAG] & F_WIZARD) && query_wiz_level(pl) < SEER_LVL) return 0;
+ level = (admin[ch, FLAG] & F_WIZARD
+ ? query_wiz_level(pl)
+ : pl->QueryProp(P_LEVEL));
+
+ switch(cmd)
+ {
+ case C_FIND:
+ case C_LIST:
+ case C_JOIN:
+ if(admin[ch, RECV] == -1) return 0;
+ if(admin[ch, RECV] <= level) return 1;
+ break;
+ case C_SEND:
+ if(admin[ch, SEND] == -1) return 0;
+ if(admin[ch, SEND] <= level) return 1;
+ break;
+ case C_LEAVE:
+ return 1;
+ default: break;
+ }
+ return(0);
+}
+
+private int CountUser(mapping l)
+{
+ mapping n;
+ n = ([]);
+ walk_mapping(l, lambda(({'i/*'*/, 'a/*'*/, 'n/*'*/}),
+ ({#'+=/*'*/, 'n/*'*/,
+ ({#'mkmapping/*'*/,
+ ({#'[/*'*/, 'a/*'*/, 0})})})),
+ &n);
+ return sizeof(n);
+}
+
+private void banned(string n, mixed cmds, string res)
+{
+ res += sprintf("%s [%s], ", capitalize(n), implode(cmds, ","));
+}
+
+private mapping Tcmd = ([]);
+#define TIMEOUT (time() - 60)
+
+void ChannelMessage(mixed msg)
+{
+ string ret, mesg;
+ mixed lag;
+ int max, rekord;
+ string tmp;
+ if(msg[1] == this_object() || !stringp(msg[2]) ||
+ msg[0] != CMNAME || previous_object() != this_object()) return;
+
+
+ mesg = lower_case(msg[2]);
+
+ if(!strstr("hilfe", mesg) && sizeof(mesg) <= 5)
+ ret = "Folgende Kommandos gibt es: hilfe, lag, up[time], statistik, bann";
+ else
+ if(!strstr("lag", mesg) && sizeof(mesg) <= 3)
+ {
+ if(Tcmd["lag"] > TIMEOUT) return;
+ Tcmd["lag"] = time();
+ lag = "/p/daemon/lag-o-daemon"->read_ext_lag_data();
+ ret = sprintf("Lag: %.1f%%/60, %.1f%%/15, %.1f%%/5, %.1f%%/1, "
+ "%.1f%%/20s, %.1f%%/2s",
+ lag[5], lag[4], lag[3], lag[2], lag[1], lag[0]);
+ call_out(#'send/*'*/, 2, CMNAME, this_object(), ret);
+ ret = query_load_average();
+ } else
+ if(!strstr("uptime", mesg) && sizeof(mesg) <= 6)
+ {
+ if(Tcmd["uptime"] > TIMEOUT) return;
+ Tcmd["uptime"] = time();
+ if(file_size("/etc/maxusers") <= 0) {
+ ret = "Diese Information liegt nicht vor.";
+ } else {
+ sscanf(read_file("/etc/maxusers"), "%d %s", max, tmp);
+ sscanf(read_file("/etc/maxusers.ever"), "%d %s", rekord, tmp);
+ ret = sprintf("Das MUD laeuft jetzt %s. Es sind momentan %d Spieler "
+ "eingeloggt; das Maximum lag heute bei %d und der Rekord "
+ "bisher ist %d.", uptime(), sizeof(users()), max, rekord);
+ }
+ } else
+ if(!strstr("statistik", mesg) && sizeof(mesg) <= 9)
+ {
+ if(Tcmd["statistik"] > TIMEOUT) return;
+ Tcmd["statistik"] = time();
+ ret = sprintf(
+ "Im Moment sind insgesamt %d Ebenen mit %d Teilnehmern aktiv.\n"
+ "Der %s wurde das letzte mal am %s von %s neu gestartet.\n"
+ "Seitdem wurden %d Ebenen neu erzeugt und %d zerstoert.\n",
+ sizeof(channels), CountUser(channels), CMNAME,
+ dtime(stats["time"]), stats["boot"], stats["new"], stats["dispose"]);
+ } else
+ if(!strstr(mesg, "bann"))
+ {
+ string pl, cmd;
+ if(mesg == "bann")
+ if(sizeof(channelB))
+ {
+ ret = "";
+ walk_mapping(channelB, #'banned/*'*/, &ret);
+ ret = "Fuer folgende Spieler besteht ein Bann: " + ret;
+ } else ret = "Zur Zeit ist kein Bann aktiv.";
+ else
+ {
+ if(sscanf(mesg, "bann %s %s", pl, cmd) == 2 &&
+ IS_DEPUTY(msg[1]))
+ {
+# define CMDS ({C_FIND, C_LIST, C_JOIN, C_LEAVE, C_SEND, C_NEW})
+ pl = lower_case(pl); cmd = lower_case(cmd);
+ if(member(CMDS, cmd) != -1)
+ {
+ if(!pointerp(channelB[pl]))
+ channelB[pl] = ({});
+ if(member(channelB[pl], cmd) != -1)
+ channelB[pl] -= ({ cmd });
+ else
+ channelB[pl] += ({ cmd });
+ ret = "Fuer '"+capitalize(pl)+"' besteht "
+ + (sizeof(channelB[pl]) ?
+ "folgender Bann: "+implode(channelB[pl], ", ") :
+ "kein Bann mehr.");
+ if(!sizeof(channelB[pl]))
+ channelB = m_copy_delete(channelB, pl);
+ save_object(CHANNEL_SAVE);
+ }
+ else ret = "Das Kommando '"+cmd+"' ist unbekannt. Erlaubte Kommandos: "
+ + implode(CMDS, ", ");
+ }
+ else
+ {
+ if(!IS_ARCH(msg[1])) return;
+ else ret = "Syntax: bann <name> <kommando>";
+ }
+ }
+ }
+ else if(mesg == "lust")
+ {
+ mixed t, up;
+ if(Tcmd["lag"] > TIMEOUT ||
+ Tcmd["statistik"] > TIMEOUT ||
+ Tcmd["uptime"] > TIMEOUT) return;
+ Tcmd["lag"] = time();
+ Tcmd["statistik"] = time();
+ Tcmd["uptime"] = time();
+ lag = "/p/daemon/lag-o-daemon"->read_lag_data();
+
+ sscanf(read_file("/etc/maxusers"), "%d %s", max, tmp);
+ sscanf(read_file("/etc/maxusers.ever"), "%d %s", rekord, tmp);
+
+ t=time()-last_reboot_time();
+ up="";
+ if(t >= 86400)
+ up += sprintf("%dT", t/86400);
+ if(t >= 3600)
+ up += sprintf("%dh", (t=t%86400)/3600);
+ if(t > 60)
+ up += sprintf("%dm", (t=t%3600)/60);
+ up += sprintf("%ds", t%60);
+
+ ret = sprintf("%.1f%%/15 %.1f%%/1 %s %d:%d:%d E:%d T:%d",
+ lag[1], lag[2], up, sizeof(users()), max, rekord,
+ sizeof(channels), CountUser(channels));
+ } else return;
+
+ call_out(#'send/*'*/, 2, CMNAME, this_object(), ret);
+}
+
+// setup() -- set up a channel and register it
+// arguments are stored in the following order:
+// ({ channel name,
+// receive level, send level,
+// flags,
+// description,
+// master obj
+// })
+private void setup(mixed c)
+{
+ closure cl;
+ object m;
+ string d;
+ d = "- Keine Beschreibung -";
+ m = this_object();
+ if(sizeof(c) && sizeof(c[0]) > 1 && c[0][0] == '\\')
+ c[0] = c[0][1..];
+
+ switch(sizeof(c))
+ {
+ case 6:
+ if(!stringp(c[5]) || !sizeof(c[5])
+ || (catch(m=load_object(c[5]);publish) || !objectp(m) ))
+ m = this_object();
+ case 5: d = stringp(c[4]) || closurep(c[4]) ? c[4] : d;
+ case 4: admin[c[0], FLAG] = to_int(c[3]);
+ case 3: admin[c[0], SEND] = to_int(c[2]);
+ case 2: admin[c[0], RECV] = to_int(c[1]);
+ break;
+ case 0:
+ default:
+ return;
+ }
+ switch(new(c[0], m, d))
+ {
+ case E_ACCESS_DENIED:
+ log_file("CHANNEL", sprintf("[%s] %s: %O: error, access denied\n",
+ dtime(time()), c[0], m));
+ break;
+ default:
+ break;
+ }
+ return;
+}
+
+void initialize()
+{
+ mixed tmp;
+ tmp = read_file(object_name(this_object())+".init");
+ tmp = regexp(old_explode(tmp, "\n"), "^[^#]");
+ tmp = map(tmp, #'regexplode/*'*/, "[^:][^:]*$|[ \\t]*:[ \\t]*");
+ tmp = map(tmp, #'regexp/*'*/, "^[^: \\t]");
+ map(tmp, #'setup/*'*/);
+}
+
+// BEGIN OF THE CHANNEL MASTER IMPLEMENTATION
+
+void create()
+{
+ seteuid(getuid());
+ restore_object(CHANNEL_SAVE);
+ if(!channelC) channelC = ([]);
+ if(!channelB) channelB = ([]);
+ channels = ([]);
+
+ /* Die Channel-History wird nicht nur lokal sondern auch noch im Memory
+ gespeichert, dadurch bleibt sie auch ueber ein Reload erhalten.
+ Der folgende Code versucht, den Zeiger aus dem Memory zu holen. Falls
+ das nicht moeglich ist, wird ein neuer erzeugt und gegebenenfalls im
+ Memory abgelegt. */
+
+ // Hab ich die noetigen Rechte im Memory?
+ if (call_other(MEMORY,"HaveRights")) {
+ // Objektpointer laden
+ channelH = (mixed) call_other(MEMORY,"Load","History");
+
+ // Wenns nich geklappt hat, hat der Memory noch keinen Zeiger, dann
+ if (!mappingp(channelH)){
+ // Zeiger erzeugen
+ channelH = ([]);
+ // und in den Memory schreiben
+ call_other(MEMORY,"Save","History",channelH);
+ }
+ } else {
+ // Keine Rechte im Memory, dann wird mit einem lokalen Zeiger gearbeitet.
+ channelH = ([]);
+ }
+
+ stats = (["time": time(),
+ "boot": capitalize(getuid(previous_object())||"<Unbekannt>")]);
+ new(CMNAME, this_object(), "Zentrale Informationen zu den Ebenen");
+ initialize();
+ map_objects(efun::users(), "RegisterChannels");
+ this_object()->send(CMNAME, this_object(),
+ sprintf("%d Ebenen mit %d Teilnehmern initialisiert.",
+ sizeof(channels),
+ CountUser(channels)));
+}
+
+// reset() and cache_to() - Cache Timeout, remove timed out cached channels
+// SEE: new, send
+private int cache_to(string key, mapping m, int t)
+{
+ if(!pointerp(m[key]) || m[key][2] + 43200 > t) return 1;
+ return(0);
+}
+
+varargs void reset(int nonstd)
+{
+ channelC = filter_indices(channelC, #'cache_to/*'*/, channelC, time());
+ if (save_me_soon)
+ {
+ save_me_soon=0;
+ save_object(CHANNEL_SAVE);
+ }
+}
+
+// name() - define the name of this object.
+string name() { return CMNAME; }
+string Name() { return CMNAME; }
+
+// access() - check access by looking for the right argument types and
+// calling access closures respectively
+// SEE: new, join, leave, send, list, users
+// Note: <pl> is usually an object, only the master supplies a string during
+// runtime error handling.
+varargs private int access(mixed ch, mixed pl, string cmd, string txt)
+{
+ mixed co, m;
+
+ if(!stringp(ch) || !sizeof(ch = lower_case(ch)) || !channels[ch])
+ return 0;
+ if(!channels[ch][I_ACCESS]||!previous_object(1)||!extern_call()||
+ previous_object(1)==this_object()||
+ (stringp(channels[ch][I_MASTER])&&
+ previous_object(1)==find_object(channels[ch][I_MASTER]))||
+ getuid(previous_object(1)) == ROOTID)
+ return 2;
+ if(!objectp(pl) ||
+ ((previous_object(1)!=pl) &&(previous_object(1)!=this_object())))
+ return 0;
+ if(pointerp(channelB[getuid(pl)]) &&
+ member(channelB[getuid(pl)], cmd) != -1)
+ return 0;
+ if(stringp(channels[ch][I_MASTER]) &&
+ (!(m = find_object(channels[ch][I_MASTER])) ||
+ (!to_object(channels[ch][I_ACCESS]) ||
+ get_type_info(channels[ch][I_ACCESS])[1])))
+ {
+ string err;
+ if(!objectp(m)) err = catch(load_object(channels[ch][I_MASTER]);publish);
+ if(!err &&
+ ((!to_object(channels[ch][I_ACCESS]) ||
+ get_type_info(channels[ch][I_ACCESS])[1]) &&
+ !closurep(channels[ch][I_ACCESS] =
+ symbol_function("check",
+ find_object(channels[ch][I_MASTER])))))
+ {
+ log_file("CHANNEL", sprintf("[%s] %O -> %O\n",
+ dtime(time()), channels[ch][I_MASTER],
+ err));
+ channels = m_copy_delete(channels, ch);
+ return 0;
+ }
+ this_object()->join(ch, find_object(channels[ch][I_MASTER]));
+ }
+ if(closurep(channels[ch][I_ACCESS]))
+ return funcall(channels[ch][I_ACCESS],
+ channels[ch][I_NAME], pl, cmd, &txt);
+}
+
+// new() - create a new channel
+// a channel with name 'ch' is created, the player is the master
+// info may contain a string which describes the channel or a closure
+// to display up-to-date information, check may contain a closure
+// called when a join/leave/send/list/users message is received
+// SEE: access
+
+#define IGNORE "^/xx"
+
+varargs int new(string ch, object pl, mixed info)
+{
+ mixed pls;
+
+ if(!objectp(pl) || !stringp(ch) || !sizeof(ch) || channels[lower_case(ch)] ||
+ (pl == this_object() && extern_call()) ||
+ sizeof(channels) >= MAX_CHANNELS ||
+ sizeof(regexp(({ object_name(pl) }), IGNORE)) ||
+ (pointerp(channelB[getuid(pl)]) &&
+ member(channelB[getuid(pl)], C_NEW) != -1))
+ return E_ACCESS_DENIED;
+
+ if(!info) {
+ if(channelC[lower_case(ch)]) {
+ ch = channelC[lower_case(ch)][0];
+ info = channelC[lower_case(ch)][1];
+ }
+ else return E_ACCESS_DENIED;
+ }
+ else channelC[lower_case(ch)] = ({ ch, info, time() });
+
+ pls = ({ pl });
+
+ channels[lower_case(ch)] = ({ pls,
+ symbol_function("check", pl) ||
+ #'check/*'*/, info,
+ (!living(pl) &&
+ !clonep(pl) &&
+ pl != this_object()
+ ? object_name(pl)
+ : pl),
+ ch,
+ });
+
+ // ChannelH fuer einen Kanal nur dann initialisieren, wenn es sie noch nich gibt.
+ if ( !pointerp(channelH[lower_case(ch)]) )
+ channelH[lower_case(ch)] = ({});
+
+ if(pl != this_object())
+ log_file("CHANNEL.new", sprintf("[%s] %O: %O %O\n",
+ dtime(time()), ch, pl, info));
+ if(!pl->QueryProp(P_INVIS))
+ this_object()->send(CMNAME, pl,
+ "laesst die Ebene '"+ch+"' entstehen.", MSG_EMOTE);
+ stats["new"]++;
+
+ save_me_soon=1;
+ return(0);
+}
+
+// join() - join a channel
+// this function checks whether the player 'pl' is allowed to join
+// the channel 'ch' and add if successful, one cannot join a channel
+// twice
+// SEE: leave, access
+int join(string ch, object pl)
+{
+ if(!funcall(#'access,&ch, pl, C_JOIN)) return E_ACCESS_DENIED;
+ if(member(channels[ch][I_MEMBER], pl) != -1) return E_ALREADY_JOINED;
+ channels[ch][I_MEMBER] += ({ pl });
+ return(0);
+}
+
+// leave() - leave a channel
+// the access check in this function is just there for completeness
+// one should always be allowed to leave a channel.
+// if there are no players left on the channel it will vanish, unless
+// its master is this object.
+// SEE: join, access
+int leave(string ch, object pl)
+{
+ int pos;
+ if(!funcall(#'access,&ch, pl, C_LEAVE)) return E_ACCESS_DENIED;
+ channels[ch][I_MEMBER] -= ({0}); // kaputte Objekte erstmal raus
+ if((pos = member(channels[ch][I_MEMBER], pl)) == -1) return E_NOT_MEMBER;
+ if(pl == channels[ch][I_MASTER] && sizeof(channels[ch][I_MEMBER]) > 1)
+ {
+ channels[ch][I_MASTER] = channels[ch][I_MEMBER][1];
+ if(!pl->QueryProp(P_INVIS))
+ this_object()->send(ch, pl, "uebergibt die Ebene an "
+ +channels[ch][I_MASTER]->name(WEN)+".", MSG_EMOTE);
+ }
+ channels[ch][I_MEMBER][pos..pos] = ({ });
+
+
+ if(!sizeof(channels[ch][I_MEMBER]) &&
+ !stringp(channels[ch][I_MASTER]))
+ {
+ // delete the channel that has no members
+ if(!pl->QueryProp(P_INVIS))
+ this_object()->send(CMNAME, pl,
+ "verlaesst als "
+ +(pl->QueryProp(P_GENDER) == 1 ? "Letzter" :
+ "Letzte")
+ +" die Ebene '"
+ +channels[ch][I_NAME]
+ +"', worauf diese sich in einem Blitz oktarinen "
+ +"Lichts aufloest.", MSG_EMOTE);
+ channelC[lower_case(ch)] = ({ channels[ch][I_NAME],
+ channels[ch][I_INFO], time() });
+ m_delete(channels, lower_case(ch));
+
+ // Wird ein Channel entfernt, wird auch seine History geloescht
+ channelH = m_copy_delete(channelH, lower_case(ch));
+
+ stats["dispose"]++;
+ save_me_soon=1;
+ }
+ return(0);
+}
+
+// send() - send a message to all recipients of the specified channel 'ch'
+// checks if 'pl' is allowed to send a message and sends if success-
+// ful a message with type 'type'
+// 'pl' must be an object, the message is attributed to it. e.g.
+// ignore checks use it. It can be != previous_object()
+// SEE: access, ch.h
+varargs int send(string ch, object pl, string msg, int type)
+{
+ int a;
+
+ if(!(a = funcall(#'access,&ch, pl, C_SEND, &msg))) return E_ACCESS_DENIED;
+ if(a < 2 && member(channels[ch][I_MEMBER], pl) == -1) return E_NOT_MEMBER;
+ if(!msg || !stringp(msg) || !sizeof(msg)) return E_EMPTY_MESSAGE;
+ map_objects(channels[ch][I_MEMBER],
+ "ChannelMessage", ({ channels[ch][I_NAME], pl, msg, type }));
+ if(sizeof(channelH[ch]) > MAX_HIST_SIZE)
+ channelH[ch] = channelH[ch][1..];
+ channelH[ch] += ({ ({ channels[ch][I_NAME],
+ (stringp(pl)
+ ? pl
+ : (pl->QueryProp(P_INVIS)
+ ? "/("+capitalize(getuid(pl))+")$" : "")
+ + (pl->Name(WER, 2) || "<Unbekannt>")),
+ msg+" <"+strftime("%a, %H:%M:%S")+">\n",
+ type }) });
+ return(0);
+}
+
+// list() - list all channels, that are at least receivable by 'pl'
+// returns a mapping,
+// SEE: access, channels
+private void clean(string n, mixed a) { a[0] -= ({ 0 }); }
+mixed list(object pl)
+{
+ mapping chs;
+
+ chs = filter_indices(channels, #'access/*'*/, pl, C_LIST);
+ walk_mapping(chs, #'clean/*'*/);
+ if(!sizeof(chs)) return E_ACCESS_DENIED;
+ return deep_copy(chs);
+}
+
+// find() - find a channel by its name (may be partial)
+// returns an array for multiple results and 0 for no matching name
+// SEE: access
+mixed find(string ch, object pl)
+{
+ mixed chs, s;
+ if(stringp(ch)) ch = lower_case(ch);
+ if( !sizeof(regexp(({ch}),"^[<>a-z0-9#-]*$")) ) return 0; // RUM
+ if(!sizeof(chs = regexp(m_indices(channels), "^"+ch+"$")))
+ chs = regexp(m_indices(channels), "^"+ch);
+ if((s = sizeof(chs)) > 1)
+ if(sizeof(chs = filter(chs, #'access/*'*/, pl, C_FIND)) == 1)
+ return channels[chs[0]][I_NAME];
+ else return chs;
+ return ((s && funcall(#'access,chs[0], pl, C_FIND)) ? channels[chs[0]][I_NAME] : 0);
+}
+
+// history() - get the history of a channel
+// SEE: access
+mixed history(string ch, object pl)
+{
+ if(!funcall(#'access,&ch, pl, C_JOIN))
+ return E_ACCESS_DENIED;
+ return deep_copy(channelH[ch]);
+}
+
+// remove - remove a channel (wird aus der Shell aufgerufen)
+// SEE: new
+mixed remove(string ch, object pl)
+{
+ mixed members;
+
+ if(previous_object() != this_object())
+ if(!stringp(ch) ||
+ pl != this_player() || this_player() != this_interactive() ||
+ this_interactive() != previous_object() ||
+ !IS_ARCH(this_interactive()))
+ return E_ACCESS_DENIED;
+
+ if(channels[lower_case(ch)]) {
+ channels[lower_case(ch)][I_MEMBER] =
+ filter_objects(channels[lower_case(ch)][I_MEMBER],
+ "QueryProp", P_CHANNELS);
+ map(channels[lower_case(ch)][I_MEMBER],
+ lambda(({'u/*'*/}), ({#'call_other/*'*/, 'u, /*'*/
+ "SetProp", P_CHANNELS,
+ ({#'-/*'*/,
+ ({#'call_other/*'*/, 'u, /*'*/
+ "QueryProp", P_CHANNELS}),
+ '({ lower_case(ch) })/*'*/,})
+ })));
+ channels = m_copy_delete(channels, lower_case(ch));
+
+ // Wird ein Channel entfernt, wird auch seine History geloescht
+ if( pointerp(channelH[lower_case(ch)] ))
+ channelH = m_copy_delete(channelH, lower_case(ch));
+
+ stats["dispose"]++;
+ }
+ if(!channelC[lower_case(ch)])
+ return E_ACCESS_DENIED;
+ channelC = m_copy_delete(channelC, lower_case(ch));
+ save_me_soon=1;
+ return(0);
+}
+
+// Wird aus der Shell aufgerufen
+mixed clear_history(string ch)
+{
+ mixed members;
+ // Sicherheitsabfragen
+ if(previous_object() != this_object())
+ if(!stringp(ch) ||
+ this_player() != this_interactive() ||
+ this_interactive() != previous_object() ||
+ !IS_ARCH(this_interactive()))
+ return E_ACCESS_DENIED;
+
+ // History des Channels loeschen
+ if( pointerp(channelH[lower_case(ch)] ))
+ channelH[lower_case(ch)]=({});
+
+ return 0;
+}
diff --git a/p/daemon/channeld.init b/p/daemon/channeld.init
new file mode 100644
index 0000000..f099d2d
--- /dev/null
+++ b/p/daemon/channeld.init
@@ -0,0 +1,65 @@
+# CHANNEL MASTER INIT FILE
+# To create a new channel:
+# <name>:<recv>:<send>:<flags>:<info>:<master>
+Allgemein: 0: 0: 0:Allgemeine Unterhaltungsebene
+Abenteuer: 0: 0: 0:Fragen die Abenteuer betreffen: /secure/questmaster
+Grats: 0: 0: 0:Gratulationen zu geloesten Abenteuern etc
+Beileid: 0: 0: 0:Beileidsbekundungen jeglicher Art
+Fraternitas: 0: 0: 0:Fraternitas dono Archmagorum
+Tod: 0:-1: 0:Wer stirbt wann?:/room/death/death_room
+TdT: 15:15:1:Tod den Testies!:/room/death/death_room
+Moerder: 0:-1: 0:Guck mal wer da spricht...: /std/corpse
+Seher: 1: 1: 1:Diskussionsebene fuer Seher
+ZT: 0: 0: 0:Nuetzliche Tips fuer die Zaubertranksuche
+#
+# Guild channels
+#
+Klerus: 0: 0: 0:Die Priester: /gilden/klerus
+Bierschuettler: 0: 0: 0:Die Bierschuettler: /gilden/bierschuettler
+Werwoelfe: 0: 0: 0:Die Werwoelfe: /gilden/werwoelfe
+AbGilde: 0: 0: 0:Abenteurer, und solche, die es werden wollen: /gilden/abenteurer
+Zauberer: 0: 0: 0:Die Zauberer: /gilden/zauberer
+Tanjian: 0: 0: 0:Die Tanjian: /gilden/tanjian
+Karate: 0: 0: 0:Die Karateka: /gilden/karate
+Chaos: 0: 0: 0:Chaoten...: /gilden/chaos
+Trves: 0: 0: 0:Neues vom Koru Tschakar Struv: /p/kaempfer/std/k_master
+Magus: 0: 0: 0:Der magische Convent zu Castalla Rosso: /gilden/magus
+Dunkelelfen: 0: 0: 0:Die Dunkelelfen: /gilden/dunkelelfen
+Uruk-Hai: 0: 0: 0:Fuer echte Orks: /gilden/urukhai
+#
+# Wizard channels
+#
+Magier: 15:15: 1:Diskussionsebene fuer Magier
+Erzmagier: 60:60: 1:Erzmagier
+Goetter: 100:100: 1:Goetter
+LPC: 15:15: 1:Fragen zur Programmierung und LPC
+#
+Debug: 20:60: 1:Debug und Fehlermeldungen: /p/daemon/debug
+Entwicklung: 20:60:1:Fehler rund um Eigenentwicklungen: /p/daemon/debug
+Warnungen: 20:60:1:Laufzeit-Warnungen: /p/daemon/debug
+Snoop: 60:-1: 1:Wer snoopt denn hier?: /secure/simul_efun/simul_efun
+FTP: 60:-1: 1:Wer macht denn FTP?: /secure/bbmaster
+#
+# Intermud channels
+#
+Intermud: 0: 5: 2:Intermud chat (Englisch): /secure/udp/channel
+Intercode: 20:20: 3:Programmierfragen (Englisch): /secure/udp/channel
+Interadmin: 45:55: 3:Administration (Englisch): /secure/udp/channel
+D-chat: 0: 5: 2:Verbindet Deutsche MUDs: /secure/udp/channel
+D-linux: 0: 5: 2:Linux im D-Chat: /secure/udp/channel
+D-tv-alles: 0: 5: 2:Deutsches TV Programm: /secure/udp/channel
+D-news: 0: 5: 2:Deutsches Nachrichten: /secure/udp/channel
+D-code: 20:20: 3:MUD Programmier Forum: /secure/udp/channel
+D-adm: 60:60: 3:Deutschsprachige Administration: /secure/udp/channel
+#
+# special
+#
+Wissenschaft:0: 0: 0:Wissenschaftliche Dispute
+Kultur: 0: 0: 0:Kulturelle Ereignisse und Erguesse
+Sport: 0: 0: 0:Sport und Spiel
+Anfaenger: 0: 0: 0:Die Ebene fuer Anfaengerfragen
+twitter: 0: 0: 1:Ein Vogel im Mud?: /secure/misc/twitter
+#
+# Gebietspezifisches
+#
+Metzelorakel:0: 0: 0:Fuer die Metzelwuetigen: /d/ebene/arathorn/orakel/secure/schamane
diff --git a/p/daemon/channeld.trusted b/p/daemon/channeld.trusted
new file mode 100644
index 0000000..8f19633
--- /dev/null
+++ b/p/daemon/channeld.trusted
@@ -0,0 +1,12 @@
+# CHANNELD MASTER TRUSTED OBJECTS FILE
+# <object> : <channels> : <name>
+/secure/master ::
+/secure/udp/channel :IOAWqP:
+/secure/simul_efun ::
+/room/death/death_room :T:
+/secure/questmaster :b:
+/d/inseln/anthea/room/land/muehlstein:T:
+/std/corpse :M:
+/p/daemon/inews :aNqW:DW
+/gilden/karate :K:
+/gilden/chaos :c:
diff --git a/p/daemon/classdb.c b/p/daemon/classdb.c
new file mode 100644
index 0000000..2db40ae
--- /dev/null
+++ b/p/daemon/classdb.c
@@ -0,0 +1,91 @@
+// MorgenGrauen MUDlib
+//
+// /obj/classd.c -- master object for classes.
+//
+// Soll irgendwann mal Klassen aehnlich der Material-DB verwalten, momentan
+// machts es erstmal einfach eine Ersetzung von Aliasen und ein Hinzufuegen
+// von impliziten Klassen.
+//
+// $Id$
+#pragma strict_types,save_types
+#pragma no_clone, no_shadow, no_inherit
+#pragma verbose_errors,combine_strings
+#pragma pedantic, range_check, warn_deprecated
+
+#include <class.h>
+
+// Mapping von Aliasen fuer Klassen. Aliase duerfen nicht rekursiv sein.
+private nosave mapping classaliases =
+ ([
+ ]);
+// Mapping von impliziten Klassen fuer eine gegebene Klasse. Implizite Klassen
+// sind als Array von Klassennamen gegeben. Implizite Klassen duerfen zur Zeit
+// nicht rekursiv sein und keine Aliase enthalten.
+private nosave mapping implicitclasses =
+ ([
+ CL_ZOMBIE: ({CL_UNDEAD}),
+ CL_SKELETON: ({CL_UNDEAD}),
+ CL_GHOUL: ({CL_UNDEAD}),
+ CL_VAMPIRE: ({CL_UNDEAD}),
+ CL_HOBGOBLIN: ({CL_GOBLIN}),
+ CL_MAMMAL_LAND: ({CL_MAMMAL, CL_ANIMAL}),
+ CL_MAMMAL_WATER: ({CL_MAMMAL, CL_ANIMAL}),
+ CL_SNAKE: ({CL_REPTILE}),
+ CL_ARACHNID: ({CL_ANIMAL}),
+ CL_BIRD: ({CL_ANIMAL}),
+ CL_FISH: ({CL_ANIMAL}),
+ CL_FROG: ({CL_ANIMAL}),
+ CL_INSECT: ({CL_ANIMAL}),
+ CL_MAMMAL: ({CL_ANIMAL}),
+ CL_REPTILE: ({CL_ANIMAL}),
+ CL_SNAKE: ({CL_ANIMAL}),
+ CL_GOLEM: ({CL_CONSTRUCT}),
+ ]);
+
+protected void create() {
+}
+
+/** Gibt den Alias der Klasse <cls> zurueck.
+ * @param[in] cls string - Klassenname
+ * @return string* - Alias von Klasse <cls>
+ */
+public string * QueryClassAlias(string cls) {
+ if (!stringp(cls))
+ raise_error(sprintf(
+ "Got wrong argument, expected string, got %.50O\n",cls));
+ return copy(classaliases[cls]) || cls;
+}
+
+/** Gibt die impliziten Klassen der Klasse <cls> zurueck.
+ * @param[in] cls string - Klassenname
+ * @return string* - Liste von impliziten Klassen von <cls>
+ */
+public string * QueryImplicitClasses(string cls) {
+ if (!stringp(cls))
+ raise_error(sprintf(
+ "Got wrong argument, expected string, got %.50O\n", cls));
+ return copy(implicitclasses[cls]) || ({});
+}
+
+/** Adds implicit classes to the classes given in <classes>.
+ * Ausserdem werden Klassenaliase aufgeloest.
+ * @param[in] classes string* - Klassenliste.
+ * @return string* - neue Klassenliste
+ * @attention Kann das uebergebene Array aendern. Das Ergebnisarray muss nicht
+ * unique sein, sondern kann Dubletten enthalten.
+ */
+public string * AddImplicitClasses(string *classes) {
+ if (!pointerp(classes))
+ raise_error(sprintf(
+ "Got wrong argument, expected array, got %.50O\n",classes));
+ foreach(string cls : &classes) {
+ // zuerst Aliase ersetzen
+ if (member(classaliases, cls))
+ cls = classaliases[cls];
+ // dann implizite Klassen addieren.
+ if (member(implicitclasses, cls))
+ classes += implicitclasses[cls];
+ }
+ return classes;
+}
+
diff --git a/p/daemon/debug.c b/p/daemon/debug.c
new file mode 100644
index 0000000..0ad6975
--- /dev/null
+++ b/p/daemon/debug.c
@@ -0,0 +1,128 @@
+
+#pragma strict_types,save_types
+#pragma no_clone,no_shadow
+
+#include <daemon.h>
+#include <logging.h>
+
+static int d_start, d_end, e_start, e_end;
+
+protected void create()
+{
+ d_end = d_start = e_end = e_start = file_size(__DEBUG_LOG__);
+}
+
+int check( string ch, object pl, string cmd, string txt )
+{
+ mixed tmp;
+
+ if ( ch != "Debug" && ch != "Entwicklung" && ch != "Warnungen" )
+ return 0;
+
+ if( objectp(pl) && query_once_interactive(pl) && query_wiz_level(pl) > 15 )
+ switch(cmd){
+ case C_FIND:
+ case C_LIST:
+ case C_JOIN:
+ case C_LEAVE:
+ return 1;
+ case C_SEND:
+ // Wer (noch) nicht auf dem Kanal ist, bekommt auch keine
+ // Ausgabe - sonst gibt es selbige doppelt, da der CHMASTER
+ // einen automatisch den Kanal betreten laesst ...
+ if ( !objectp(pl) || !pointerp(tmp=(mixed)pl->QueryProp(P_CHANNELS)) ||
+ member( tmp, lower_case(ch) ) == -1 )
+ return 1;
+
+ switch( lower_case(txt) ){
+ case "backtrace":
+ {
+ string bt, log;
+ int start, end;
+
+ if ( ch == "Debug" ) {
+ start = d_start;
+ end = d_end;
+ }
+ else if (ch == "Entwicklung") {
+ start = e_start;
+ end = e_end;
+ }
+ else if (ch == "Warnungen") {
+ pl->Message( "[" + ch + ":] Backtrace fuer Warnungen "
+ "nicht verfuegbar!\n");
+ return 0;
+ }
+ else return(1);
+
+ if( start >= end )
+ bt = "[" + ch + ":] Kein Backtrace verfuegbar!\n";
+ else {
+ log = regreplace( read_bytes( __DEBUG_LOG__, start,
+ (end-start>10000)?10000:
+ end - start ) || "\nFehler beim Einlesen des Debuglogs: Kein Backtrace verfuegbar!\n",
+ "(Misplaced|current_object|"
+ "Connection|Host)[^\n]*\n",
+ "", 1 );
+ bt = "[" + ch + ":] -- BACKTRACE BEGIN --\n"
+ + log
+ + "[" + ch + ":] -- BACKTRACE END --\n";
+ }
+
+ call_out( symbol_function( "Message", pl ), 0, bt );
+ }
+ break;
+
+ default:
+ if ( ch != "Warnungen" ) {
+ pl->Message( "[" + ch + ":] Hilfe...\n"
+ "[" + ch + ":] Folgende Kommandos stehen zur "
+ "Verfuegung:\n"
+ "[" + ch + ":] 'backtrace' -- sendet den "
+ "Backtrace zum Fehler\n"
+ "[" + ch + ":] 'hilfe' -- sendet diese "
+ "Hilfeseite\n");
+ }
+ else {
+ pl->Message( "[" + ch + ":] Keine Kommandos verfuegbar!\n");
+ }
+ }
+ }
+
+ return 0;
+}
+
+string name() { return "<Debugger>"; }
+string Name() { return "<Debugger>"; }
+
+void ChannelMessage( mixed a )
+{
+ int fs;
+
+ fs = file_size(__DEBUG_LOG__);
+
+ switch( a[0] ){
+ case "Debug":
+ if ( fs > d_end && fs > e_end ){
+ d_start = max((d_end > e_end ? d_end : e_end),0);
+ d_end = fs;
+ }
+ else if (fs < d_start || fs <= d_end) {
+ // Debuglog wurde wohl neu geoeffnet.
+ d_start = d_end = e_start = e_end = fs;
+ }
+ break;
+
+ case "Entwicklung":
+ if ( fs > d_end && fs > e_end ){
+ e_start = max((e_end > d_end ? e_end : d_end),0);
+ e_end = fs;
+ }
+ else if (fs < e_start || fs <= e_end) {
+ // Debuglog wurde wohl neu geoeffnet.
+ d_start = d_end = e_start = e_end = fs;
+ }
+ break;
+ }
+}
+
diff --git a/p/daemon/dnslookup.c b/p/daemon/dnslookup.c
new file mode 100644
index 0000000..a952324
--- /dev/null
+++ b/p/daemon/dnslookup.c
@@ -0,0 +1,101 @@
+// MorgenGrauen MUDlib
+//
+// dnslookup.c - kleines DNS-Lookup-Objekt mit Cache.
+// Braucht externen DNS-Resolver, der per UDP ansprechbar ist.
+//
+// Autor: Zesstra
+
+/* Im Mudlibmaster in receive_udp() braucht es noch etwas a la:
+ if( message[0..9]=="DNSLOOKUP\n" ) {
+ "/p/daemon/dnslookup"->update( message );
+ return;
+ }
+
+ ext. Objekt erwartet ganz simpel <hostname>\n als Eingabe und sendet zurueck:
+ DNSLOOKUP\n<hostname>\n<ip-adresse>|UNKNOWN\n
+ (Das | steht fuer oder.)
+*/
+
+#pragma strong_types,rtt_checks
+
+// externer server, der den DNS-Resolve macht.
+#define IPLOOKUP_HOST "127.0.0.1"
+#define IPLOOKUP_PORT 8712
+
+mapping cache = ([]);
+
+mapping _query_cache() {return cache;}
+
+protected void create()
+{
+ seteuid(getuid());
+ set_next_reset(3600);
+}
+
+// ja... total simpel... nicht mehr aufwand als noetig.
+void reset()
+{
+ cache = ([]);
+ set_next_reset(10800);
+}
+
+// eigentliche Anfrage an den externen Resolver senden.
+protected void make_request(string hostname)
+{
+ send_udp( IPLOOKUP_HOST, IPLOOKUP_PORT, hostname+"\n" );
+ if (sizeof(cache) > 1000)
+ reset();
+ cache[hostname]=0; // cached request, but unknown result (yet)
+}
+
+// Hostnamen asynchron aufloesen.
+public string resolve( string hostname )
+{
+ if (member(cache, hostname))
+ return cache[hostname];
+ make_request( hostname );
+ return 0; // leider...
+}
+
+// Pruefung, ob von <ip> Verbindungen zu unserer IP nach <localport>
+// weitergeleitet werden.
+public int check_tor(string ip, int localport)
+{
+ string *arr=explode(ip,".");
+ if (!sizeof(arr)==4)
+ return 0;
+ string req =
+ sprintf("%s.%s.%s.%s.%d.60.24.79.87.ip-port.exitlist.torproject.org",
+ arr[3],arr[2],arr[1],arr[0],localport);
+
+ return resolve(req) == "127.0.0.2";
+}
+
+// Pruefung, ob <ip> auf einigen Blacklists steht.
+public int check_dnsbl(string ip)
+{
+ string *arr=explode(ip,".");
+ if (!sizeof(arr)==4)
+ return 0;
+ string req =
+ sprintf("%s.%s.%s.%s.dnsbl.dronebl.org",
+ arr[3],arr[2],arr[1],arr[0]);
+
+ return resolve(req) != 0;
+}
+
+/* wird vom master aufgerufen, wenn eine antwort vom externen
+ * iplookup dienst eintrudelt.
+ */
+public void update( string udp_reply )
+{
+ if( previous_object()!=master() ) return;
+ string *reply = explode(udp_reply,"\n");
+ if( sizeof(reply)<3 ) return;
+
+ if (reply[2] == "UNKNOWN")
+ cache[reply[1]] = 0;
+ else
+ cache[reply[1]] = reply[2];
+}
+
diff --git a/p/daemon/eventd.c b/p/daemon/eventd.c
new file mode 100644
index 0000000..b81d38b
--- /dev/null
+++ b/p/daemon/eventd.c
@@ -0,0 +1,334 @@
+// MorgenGrauen MUDlib
+//
+// /p/daemon/eventd.c -- Event Dispatcher
+//
+// $Id$
+#pragma strict_types,save_types
+#pragma no_clone
+#pragma no_shadow
+#pragma pedantic
+#pragma range_check
+
+#include <wizlevels.h>
+#include <defines.h>
+#include <events.h>
+
+// Fuer Statistiken
+#define STATISTICS
+
+#define HOME(x) (__PATH__(0)+x)
+#define STORE HOME("save/eventd")
+
+
+#define LOG(x) log_file("EVENTS", sprintf(\
+ "[%s] %s\n",dtime(time()),x))
+//#define LOGEVENT(x,y,z) log_file("EVENTS", sprintf(\
+// "[%s] Event %s triggered by %O (Args: %.40O)\n",dtime(time()),x,y,z))
+#define LOGEVENT(x,y,z)
+//#define LOGREGISTER(w,x,y,z) log_file("EVENTLISTENER",sprintf(\
+// "[%s] %O (Fun: %.25s) registered for %s by %O\n",dtime(time()),w,x,y,z))
+#define LOGREGISTER(w,x,y,z)
+//#define LOGUNREGISTER(x,y,z) log_file("EVENTLISTENER",sprintf(\
+// "[%s] %O was unregistered from %s by %O\n",dtime(time()),x,y,z))
+#define LOGUNREGISTER(x,y,z)
+//#define LOGEVENTFINISH(x,y) log_file("EVENTS", sprintf(\
+// "[%s] Event %s (triggered by %O) finished\n",dtime(time()),x,y))
+#define LOGEVENTFINISH(x,y)
+
+#ifndef DEBUG
+#define DEBUG(x) if (find_player("zesstra"))\
+ tell_object(find_player("zesstra"),\
+ "EDBG: "+x+"\n")
+#endif
+
+#define TICK_RESERVE 300000
+#define TICKSPERCALLBACK 30000
+
+// Indizes fuer Callback-Infos (events, active)
+#define CB_FUN 0
+#define CB_CLOSURE 1
+#define CB_OBJECT 2
+// Indizes fuer Pending
+#define P_EID 0
+#define P_TRIGOB 1
+#define P_TRIGOBNAME 2
+#define P_ARGS 3
+#define P_TIME 4
+
+// Indizes fuer Active
+#define A_EID 0
+#define A_TRIGOB 1
+#define A_TRIGOBNAME 2
+#define A_ARGS 3
+#define A_LISTENERS 4
+#define A_TIME 5
+
+/* alle events, die er kennt.
+ Datenstruktur events:
+ ([id:([obname:fun;closure;object, ... ]),
+ id2: ([....]),
+ ]) */
+mapping events=([]);
+/* abzuarbeitende Events, Datenstruktur:
+ ({ ({id, trigob, trigobname, args}),
+ ({id2, trigob, trigobname, args}), ... }) */
+nosave mixed pending=({});
+/* Der gerade aktive Event, der wird gerade abgearbeitet.
+ Datenstruktur active:
+ ({ id:trigob;trigobname;args;([obname:fun;closure;object, ...]), trigtime})
+*/
+nosave mixed active=({});
+
+int lastboot; // Zeitstempel des letzten Reboots
+// Flag, wenn gesetzt, zerstoert sich der Eventd, wenn keine Events
+// abzuarbeiten sind.
+nosave int updateme;
+
+// einfache Statistik, gespeichert wird ein Histogramm. KEys sind die
+// Zeitdifferenzen zwschen Eintragen und Erledigen des Events, Values die
+// Haeufigkeiten.
+nosave mapping stats=([]);
+
+
+protected void process_events();
+protected void save_me();
+varargs int remove(int silent);
+
+// ist der Event-Typ "eid" schon bekannt, d.h. gib es min. 1 Listener?
+// Liefert Anzahl der Listener zurueck.
+int CheckEventID(string eid) {
+ if (!stringp(eid) || !member(events,eid))
+ return 0;
+ return(sizeof(events[eid]));
+}
+
+// entscheidet, ob ein Objekt fuer einen Event eingetragen werden darf. Zum
+// Ueberschreiben in geerbten Dispatchern, in diesem Scheduler sind alle
+// Events oeffentlich. Bekommt die Event-ID, das einzutragende Objekt und das
+// eintragende Objekt uebergeben.
+// 1 fuer Erlaubnis, 0 sonst.
+protected int allowed(string eid, object ob, object po) {
+ return(1);
+}
+
+// entscheidet, ob ein Objekt einen bestimmten Event triggern darf. Zum
+// Ueberschreiben in geerbten Dispatchern, in diesem Scheduler sind alle
+// Events oeffentlich. Bekommt die Event-ID und das triggernde Objekt
+// uebergeben.
+// 1 fuer Erlaubnis, 0 sonst.
+protected int allowedtrigger(string eid, object trigger) {
+ return(1);
+}
+
+// registriert sich fuer einen Event. Wenn es den bisher nicht gibt, wird er
+// implizit erzeugt. Wenn das Objekt ob schon angemeldet war, wird der
+// bisherige Eintrag ueberschrieben.
+// 1 bei Erfolg, <=0 bei Misserfolg
+int RegisterEvent(string eid, string fun, object ob) {
+ object po;
+ if (!stringp(eid) || !stringp(fun) ||
+ !objectp(ob) || !objectp(po=previous_object()))
+ return -1;
+ if (!allowed(eid, ob, po)) return -2;
+ closure cl=symbol_function(fun,ob);
+ if (!closurep(cl))
+ return -3;
+ if (!mappingp(events[eid]))
+ events[eid]=m_allocate(1,3); // 3 Werte pro Key
+ events[eid]+=([object_name(ob):fun;cl;ob]);
+// if (find_call_out(#'save_me)==-1)
+// call_out(#'save_me,15);
+ LOGREGISTER(ob,fun,eid,po);
+ return 1;
+}
+
+// entfernt das Objekt als Listener aus dem Event eid
+// Mehr oder weniger optional, wenn ein event verarbeitet wird und das Objekt
+// ist nicht mehr auffindbar, wird es ebenfalls geloescht. Bei selten
+// getriggerten Events muellt es aber bis dahin den Speicher voll.
+// +1 fuer Erfolg, <= 0 fuer Misserfolg
+int UnregisterEvent(string eid, object ob) {
+ object po;
+ if (!stringp(eid) || !objectp(ob) ||
+ !objectp(po=previous_object()) || !mappingp(events[eid]))
+ return -1;
+ string oname=object_name(ob);
+ if (!member(events[eid],oname))
+ return -2;
+ m_delete(events[eid],oname);
+ if (!sizeof(events[eid]))
+ m_delete(events,eid);
+ // aus aktivem Event austragen, falls es drin sein sollte.
+ if (sizeof(active) && active[A_EID] == eid)
+ {
+ m_delete(active[A_LISTENERS], object_name(ob));
+ }
+// if (find_call_out(#'save_me)==-1)
+// call_out(#'save_me,15);
+ LOGUNREGISTER(ob, eid, po);
+ return 1;
+}
+
+// Loest einen Event aus.
+// 1 fuer Erfolg, <= 0 fuer Misserfolg
+varargs int TriggerEvent(string eid, mixed args) {
+ object trigger;
+ if (!stringp(eid) ||
+ !objectp(trigger=previous_object()))
+ return -1;
+ if (!allowedtrigger(eid, trigger)) return -2;
+ if (!member(events,eid)) return -3;
+ if (sizeof(pending) > __MAX_ARRAY_SIZE__/5)
+ return -4;
+ pending+=({ ({eid,trigger,object_name(trigger), args, time()}) });
+ if (find_call_out(#'process_events) == -1)
+ call_out(#'process_events,0);
+ LOGEVENT(eid,trigger,args);
+ //DEBUG(sprintf("%O",pending));
+ return 1;
+}
+
+protected void process_events() {
+ // erstmal wieder nen call_out eintragen.
+ call_out(#'process_events, 1);
+
+ // solange ueber active und pending laufen, bis keine Ticks mehr da sind,
+ // bzw. in der Schleife abgebrochen wird, weil keine Events mehr da sind.
+ while(get_eval_cost() > TICK_RESERVE) {
+ // HB abschalten, wenn nix zu tun ist.
+ if (!sizeof(active)) {
+ if (!sizeof(pending)) {
+ remove_call_out(#'process_events);
+ break;
+ }
+ // scheint noch min. ein Eintrag in pending zu sein, nach active kopieren,
+ // plus die Callback-Infos aus events
+ active=({pending[0][P_EID],
+ pending[0][P_TRIGOB],pending[0][P_TRIGOBNAME],
+ pending[0][P_ARGS],
+ deep_copy(events[pending[0][P_EID]]),
+ pending[0][P_TIME] });
+
+ if (sizeof(pending)>1)
+ pending=pending[1..]; // und aus pending erstmal loeschen. ;-)
+ else
+ pending=({});
+ //DEBUG(sprintf("Pending: %O",pending));
+ //DEBUG(sprintf("Active: %O",active));
+ }
+ // jetzte den aktiven Event abarbeiten.
+ // Infos aus active holen...
+ string eid=active[A_EID];
+ object trigob=active[A_TRIGOB];
+ string trigobname=active[A_TRIGOBNAME];
+ mixed args=active[A_ARGS];
+ mapping listeners=active[A_LISTENERS];
+ // und ueber alle Listener iterieren
+ foreach(string obname, string fun, closure cl, object listener:
+ listeners) {
+ // erst pruefen, ob noch genug Ticks da sind. wenn nicht, gehts im
+ // naechsten Zyklus weiter.
+ if (get_eval_cost() < TICK_RESERVE) {
+ return;
+ }
+ // wenn Closure und/oder zugehoeriges Objekt nicht existieren, versuchen
+ // wir erstmal, es wiederzufinden. ;-)
+ if (!objectp(query_closure_object(cl))) {
+ if (objectp(listener=find_object(obname)) &&
+ closurep(cl=symbol_function(fun,listener))) {
+ //geklappt, auch in events wieder ergaenzen
+ events[eid][obname,CB_CLOSURE]=cl;
+ events[eid][obname,CB_OBJECT]=listener;
+ }
+ else {
+ // Objekt nicht gefunden oder Closure nicht erzeugbar, austragen
+ m_delete(listeners,obname);
+ // und aus events austragen.
+ m_delete(events[eid],obname);
+ // und naechster Durchgang
+ continue;
+ }
+ }
+ // Objekt noch da, Closure wird als ausfuehrbar betrachtet.
+ catch(limited(#'funcall,({TICKSPERCALLBACK}),cl,eid,trigob,args);publish);
+ // fertig mit diesem Objekt.
+ m_delete(listeners,obname);
+ }
+ // Statistik? Differenzen zwische Erledigungszeit und Eintragszeit bilden
+ // die Keys, die Values werden einfach hochgezaehlt.
+#ifdef STATISTICS
+ stats[time()-active[A_TIME]]++;
+#endif // STATISTICS
+ // ok, fertig mit active.
+ active=({});
+ //DEBUG(sprintf("Fertig: %O %O",eid, trigobname));
+ LOGEVENTFINISH(eid,trigobname);
+ } // while(get_eval_cost() > TICK_RESERVE)
+
+ // Soll dies Ding neugeladen werden? Wenn ja, Selbstzerstoerung, wenn keine
+ // Events mehr da sind.
+ if (updateme && !sizeof(active) && !sizeof(pending)) {
+ DEBUG(sprintf("Update requested\n"));
+ remove(1);
+ }
+}
+
+protected void create() {
+ seteuid(getuid(ME));
+ restore_object(STORE);
+ if (lastboot != __BOOT_TIME__) {
+ // Oh. Reboot war... Alle Events wegschmeissen (es koennten zwar die
+ // Eventanmeldungen von BPs ueberleben, aber auch die sollen sich lieber
+ // im create() anmelden.)
+ events=([]);
+ lastboot=__BOOT_TIME__;;
+ }
+ LOG("Event-Dispatcher loaded");
+}
+
+
+protected void save_me() {
+ save_object(STORE);
+}
+
+varargs int remove(int silent) {
+ save_me();
+ DEBUG(sprintf("remove() called by %O - destructing\n", previous_object()));
+ LOG(sprintf("remove called by %O - destructing",previous_object()));
+ destruct(ME);
+ return 1;
+}
+
+public void reset() {
+ if (updateme && !sizeof(active) && !sizeof(pending)) {
+ DEBUG(sprintf("Update requested\n"));
+ remove(1);
+ }
+}
+
+
+// fuer Debugzwecke. Interface und Verhalten kann sich jederzeit ohne
+// Vorankuendigung aendern.
+mapping QueryEvents() {
+ return(deep_copy(events));
+}
+
+mixed QueryPending() {
+ return(deep_copy(pending));
+}
+
+mixed QueryActive() {
+ return(deep_copy(active));
+}
+
+mapping QueryStats() {
+ return(copy(stats));
+}
+
+int UpdateMe(int flag) {
+ updateme=flag;
+ if (find_call_out(#'process_events)==-1)
+ reset();
+ return flag;
+}
+
diff --git a/p/daemon/finger.c b/p/daemon/finger.c
new file mode 100644
index 0000000..d4b602a
--- /dev/null
+++ b/p/daemon/finger.c
@@ -0,0 +1,443 @@
+/* a finger
+ * Original (c) 14.03.1993 by Taube @Nightfall
+ * Umsetzung fuer Morgengrauen 09.08.1993 by Loco
+
+ Verwendung ausserhalb von Morgengrauen ist gestattet unter folgenden
+ Bedingungen:
+ - Benutzung erfolgt auf eigene Gefahr. Jegliche Verantwortung wird
+ abgelehnt.
+ - Auch in veraenderten oder abgeleiteten Objekten muss ein Hinweis auf
+ die Herkunft erhalten bleiben.
+ Ein Update-Service besteht nicht.
+
+ * 29.Okt 1993 Seherversion.
+ * spaeter auch fuer externen Aufruf verwendbar gemacht
+ * 13.Okt .plan und .project koennen auch in ~loco/plans sein.
+ * 15.Jan 1994 angepasst an neues Speicherformat
+ * 02-05.Dez94 -n, -p
+ *
+ * Gelegentlich minor changes, zuletzt 04.Okt.95
+ */
+
+#pragma strong_types,save_types
+#pragma no_clone, no_shadow
+
+#include <config.h>
+#include <properties.h>
+#include <wizlevels.h>
+#include <new_skills.h>
+#include <userinfo.h>
+#include <config.h>
+
+#define HBINT 2 /* interval between two heart_beats in seconds */
+#define MINIDLE 60 /* minimum idle time in seconds to be stated idle */
+#define TBANISH_EXTRACT 71.. /* Der benoetigte Teil des TBanish-strings */
+
+#define TP this_player()
+#define wiz (local && IS_LEARNER(TP))
+#define seer (local && IS_SEER(TP))
+#define IN ((properties[P_GENDER]==2)?"in":"")
+
+
+#define FLAG_NOPLAN 1
+#define FLAG_LONG 2
+#define FLAG_SPONSOR 4
+#define FLAG_VIS_LOGOUT 8
+#define FLAG_AVATAR 16
+
+
+mapping properties;
+int age,invis,hc_play;
+int filetime;
+mapping relatives;
+
+
+void create()
+{
+ seteuid(getuid());
+ filetime=0;
+}
+
+
+string timediff(int time)
+{
+ string ret;
+
+ ret="";
+ if ( time >= 86400*365 ) {
+ ret+=time/(86400*365)+"a ";
+ time%=(86400*365);
+ }
+ if(time>=86400) {
+ ret+=time/86400+"d ";
+ time%=86400;
+ }
+ if(time<36000) ret+="0";
+ ret+=time/3600+":";
+ time%=3600;
+ if(time<600) ret+="0";
+ ret+=time/60+":";
+ time%=60;
+ if(time<10) ret+="0";
+ ret+=time+"";
+ return ret;
+}
+
+string sponsoring(string name)
+{
+ int i,w;
+ string *s,s2,s3,s4;
+ // Daten einlesen, wenn die daten aelter als 1 Tag sind oder sich
+ // /log/SPONSORS geaendert hat.
+ if ((time() > filetime+86400) ||
+ filetime!=file_time("/log/SPONSOR"))
+ {
+ relatives=m_allocate(0,2);
+ filetime=file_time("/log/SPONSOR");
+ s=explode(read_file("/log/SPONSOR"),"\n");
+ foreach(string str: s) {
+ sscanf(str,"%s: %s macht %s zum Learner.",s2,s3,s4);
+ if (IS_LEARNER(lower_case(s3)) && IS_LEARNER(lower_case(s4)))
+ {
+ relatives[lower_case(s4),0]=s3;
+ s3=lower_case(s3);
+ s4+=" ("+query_wiz_level(lower_case(s4))+")";
+ if (!relatives[s3,1]) relatives[s3,1]=({s4});
+ else relatives[s3,1]+=({s4});
+ }
+ }
+ }
+ s2="";
+ if (relatives[name,0])
+ s2+="Vorfahre: "+relatives[name,0]+"\n";
+ if (relatives[name,1])
+ s2+="Nachfahre(n): "+break_string(implode(relatives[name,1],", "),78,14)[14..];
+ return s2;
+}
+
+varargs string finger_single(string str,int local)
+{
+ mixed *userinfo;
+ string ip,text,ipnum,filename,away;
+ int wizlevel,playerlevel,idle,pos,flags,last;
+ mixed h,data,tmp;
+ object player,ob;
+
+ /*DEBUG### tell_object((find_player("loco")||this_object()),"Finger request: '"+str+"'("+local+")\n");/**/
+ str=lower_case(str);
+ text="";
+ away="";
+ hc_play=0;
+
+ h=explode(str," ");
+ if (sizeof(h)==1) h=explode(str,"+");
+ if (member(h,"-n")>=0) flags=FLAG_NOPLAN;
+ if (member(h,"-p")>=0) flags=0;
+ if (member(h,"-l")>=0) flags|=FLAG_LONG;
+ if (member(h,"-s")>=0) flags|=FLAG_SPONSOR;
+ if (member(h,"-v")>=0) flags|=FLAG_VIS_LOGOUT;
+ if (member(h,"-a")>=0) flags|=FLAG_AVATAR;
+
+ h=(h-({"-n","-p","-l","-s", "-v","-a"}));
+ if (!sizeof(h)) {
+ text="Du solltest schon sagen, wer Dich interessiert!\n";
+ if (local) return write(text),0;
+ else return text;
+ }
+ str=h[0];
+ if (!sizeof(str) || str[0]<'a' || str[0]>'z') {
+ text="Also, so ("+str+") heisst bestimmt kein Spieler hier!\n";
+ if (local) return write(text),0;
+ else return text;
+ }
+
+ /* does this player exist? */
+ str=old_explode(str,".")[0];
+ userinfo=MASTER->get_userinfo(str);
+ player=find_player(str)||find_netdead(str);
+
+ if( (!pointerp(userinfo) || userinfo[USER_LEVEL+1]==-1)
+ && !player) {
+ text="Hmm... diesen Namen gibt es im "MUDNAME" nicht.\n";
+ if (tmp="/secure/master"->QueryBanished(str)){
+ text="Hoppla - dieser Name ist reserviert oder gesperrt (\"gebanisht\")!\n";
+ if ( tmp != "Dieser Name ist gesperrt." )
+ text += "Grund fuer die Sperrung: " + tmp +
+ (member( ({'!','?','.'}), tmp[<1] ) != -1 ? "" : ".") + "\n";
+ }
+ if (local) return write(text),0;
+ else return text;
+ }
+
+ if (player) {
+ hc_play=player->query_hc_play();
+ properties=player->QueryProperties();
+ properties[P_RACE]=player->QueryProp(P_RACE);
+ properties[P_VISIBLE_GUILD]=player->QueryProp(P_VISIBLE_GUILD);
+ properties[P_TITLE]=player->QueryProp(P_TITLE);
+ tmp = player->QueryProp(P_PRESAY);
+ properties[P_PRESAY]=(stringp(tmp) && sizeof(tmp)>1) ? tmp[0..<2] : 0;
+ }
+ else
+ restore_object("/save/"+str[0..0]+"/"+str);
+ if (!properties)
+ {
+ text+="Mist!!! Das Einlesen der Daten klappt nicht wie es soll :-(\n";
+ properties=0;
+ if (!local)
+ return text;
+ write(text);
+ return "";
+ }
+ if ( player && interactive(player) )
+ ipnum = query_ip_number(player);
+ else
+ ipnum = properties[P_CALLED_FROM_IP];
+ // frueher stand in P_CALLED_FROM_IP evtl. auch der Name direkt drin
+ // anstelle der numerischen IP
+ ip=query_ip_name(ipnum)||properties[P_CALLED_FROM_IP];
+ if(player) {
+ if (!interactive(player) || (idle=query_idle(player))<MINIDLE)
+ idle=0;
+ if (!(invis=age=player->QueryProp(P_INVIS)))
+ age=player->QueryProp(P_AGE);
+ } else {
+ if (properties[P_INVIS]) age=properties[P_INVIS];
+ idle=properties[P_LAST_LOGOUT];
+ }
+
+ wizlevel=query_wiz_level(str);
+ if ( (tmp = file_time("/save/"+str[0..0]+"/"+str+".o")) <= 0 )
+ // Hack, um bei ganz "frischen" Spielern (noch kein Savefile vorhanden)
+ // die Ausgabe von 1.1.1970 zu verhindern
+ tmp = time();
+ last=properties[P_LAST_LOGOUT];
+ if ( last <= 0 || (!(flags&FLAG_VIS_LOGOUT) && wiz && wizlevel > 10
+ && properties[P_INVIS] && tmp - last > 300) )
+ last = tmp;
+
+ /* output routine for all */
+ if(player) {
+ h=player->QueryProp(P_RACE);
+ if(interactive(player) && (wiz || !invis)) {
+ text+=capitalize(str)+" ist anwesend,\n"+
+ "und zwar von: "+
+ (wiz ? (ip+(ipnum?" ("+ipnum+")":"")):"")+
+ (stringp(properties[P_LOCATION]) ? (wiz ? "\n [" : "")
+ +capitalize(properties[P_LOCATION])+
+ ((properties[P_LOCATION] != country(ip, ipnum))
+ ? " (ueber "+country(ip, ipnum)+")" : "" )
+ : (wiz?" [":"")+country(ip, ipnum))+(wiz ? "]":"")+".\n";
+ if(idle)
+ text+="Passiv seit: "+timediff(idle)+"\n";
+ if (local)
+ text+="Eingeloggt seit: "+dtime(properties[P_LAST_LOGIN])+"\n";
+ if (properties[P_AWAY])
+ away="z.Z. abwesend, Grund : "+properties[P_AWAY]+"\n";
+ } else
+ text+=capitalize(str)+" ist nicht anwesend.\nZuletzt eingeloggt von: "+
+ (wiz ? (ip+(ipnum&&ipnum!=ip?" ("+ipnum+")":"")):"")+
+ (stringp(properties[P_LOCATION]) ?
+ (wiz ? "\n [": "")
+ +capitalize(properties[P_LOCATION])+" (ueber "+country(ip, ipnum)+")":
+ (wiz?" [":"")+country(ip, ipnum))+(wiz ? "]":"")+".\n"+
+ "Zuletzt ausgeloggt: "+dtime(last)+" ("+timediff(time()-last)+").\n";
+ }
+ else {
+ text+=capitalize(str)+" ist nicht anwesend.\nZuletzt eingeloggt von: "+
+ (wiz ? (ip+(ipnum&&ipnum!=ip?" ("+ipnum+")":"")):"")+
+ (stringp(properties[P_LOCATION]) ?
+ (wiz ? "\n [": "")
+ +capitalize(properties[P_LOCATION])+" (ueber "+country(ip, ipnum)+")":
+ (wiz?" [":"")+country(ip, ipnum))+(wiz ? "]":"")+".\n"+
+ "Zuletzt ausgeloggt: "+dtime(last)+" ("+timediff(time()-last)+").\n";
+ }
+ text+="Voller Name: "+(((h=properties[P_PRESAY]) && h!="") ? h+" " : "")+
+ capitalize((h=properties[P_NAME]) ? h : str)+" "+
+ ((h=properties[P_TITLE]) ? h :
+ ((mappingp(h=properties["guild_title"]) && (h=(h[properties[P_GUILD]?properties[P_GUILD]:"abenteurer"])) ) ? h : "") )
+ +"\n";
+
+ if (properties[P_GHOST]) text+="Hoppla, ein Geist!\n";
+ if ((flags&FLAG_LONG)&&properties[P_LONG])
+ text+="Beschreibung: \n"+break_string(properties[P_LONG],78,2);
+
+ if(wiz ||
+ (properties[P_SHOWEMAIL]=="alle") ||
+ ( (properties[P_SHOWEMAIL]=="freunde") &&
+ objectp(player) &&
+ this_player() &&
+ (ob=present("\n\bfband",player)) &&
+ pointerp(tmp=ob->QueryProp(P_AUTOLOADOBJ)) &&
+ pointerp(tmp=tmp[1]) &&
+ (member(tmp,getuid(this_player()))!=-1))) tmp = 1;
+ else tmp=0;
+
+
+ if (!(h=properties[P_RACE]) && userinfo && pointerp(userinfo) && sizeof(userinfo)>4 &&
+ stringp(userinfo[4]) && sizeof(h=old_explode(userinfo[4],"/"))>2) {
+ h=capitalize(h[2]);
+ h=(["Human":"Mensch","Dwarf":"Zwerg","Darkelf":"Dunkelelf","Orc":"Ork"])[h] || h;
+ }
+
+ if (!stringp(h)) h="<keine>";
+
+ text+="Rasse: "+h+", Gilde: "+
+ ((h=properties[P_VISIBLE_GUILD])?capitalize(h):((h=properties[P_GUILD])?capitalize(h):"Abenteurer"))+
+ ((h=properties[P_VISIBLE_SUBGUILD_TITLE])?" ("+capitalize(h)+")":((h=properties[P_SUBGUILD_TITLE])?" ("+capitalize(h)+")":""))+
+ ", Geschlecht: "+({"neutral ?!","maennlich","weiblich","<verdammt seltsam>"})[properties[P_GENDER]]+"\n"+
+ (seer ? "Alter: "+timediff(age*HBINT)+", " : "")+
+ (wizlevel>=10?"Magierlevel: "+wizlevel+
+ (wizlevel>=GOD_LVL?" (Mudgott)":str=="boing"?" (Mudgott a.D.)":str=="muadib"?" (Apostolischer Visitator)":wizlevel>=ARCH_LVL?" (Erzmagier)":IS_DEPUTY(str)?" (Hilfssheriff)":wizlevel>=ELDER_LVL?" (Weiser)":wizlevel>=LORD_LVL?" (Regionsmagier)":wizlevel>=SPECIAL_LVL?" (Hilfsmagier)":wizlevel>=DOMAINMEMBER_LVL?" (Regionsmitarbeiter)":wizlevel>WIZARD_LVL?" (Vollmagier)":" (Lehrling)"):
+ ("Spielerlevel: "+properties[P_LEVEL]+( wizlevel ? " (Seher"+IN+")" : "" )+
+ (((h=properties[P_GUILD_LEVEL]) && h=h[properties[P_GUILD]]) ?
+ (", Gildenlevel: "+h) : "" )
+ ))+((userinfo&&pointerp(userinfo))?
+ (sprintf("\nDatum des ersten Login: %s",
+ (userinfo[5] > 0) ? dtime(userinfo[5]) : "vor dem 10. Nov 1995")):"")+
+ (tmp ? ("\nE-Mail-Adresse: "+((h=properties[P_MAILADDR]) ? h : "keine")+"\n") : "\n");
+
+ if (properties[P_HOMEPAGE])
+ text+="Homepage: "+properties[P_HOMEPAGE]+"\n";
+
+ if (stringp(data=properties[P_MESSENGER])) {
+ text+=sprintf("Messenger: %s", data);
+ if (intp(data=properties[P_ICQ])) {
+ if (data<0 && IS_WIZARD(this_player())) data*=-1;
+ if (data>0) text += sprintf(", ICQ: %d", data);
+ }
+ text+="\n";
+ } else
+ if (intp(data=properties[P_ICQ])) {
+ if (data<0 && IS_WIZARD(this_player()))
+ data*=-1;
+ if (data>0)
+ text+=sprintf("ICQ: %d\n",data);
+ }
+
+ if (properties[P_MARRIED])
+ text+="Verheiratet mit: "+capitalize(properties[P_MARRIED])+"\n";
+
+ if ( pointerp(properties[P_SIBLINGS]) )
+ text += ({ "Es", "Er", "Sie" })[properties[P_GENDER]] + " ist Bluts" +
+ ({ "verwandt mit ", "bruder von ", "schwester von " })
+ [properties[P_GENDER]] +
+ CountUp(properties[P_SIBLINGS]) + ".\n";
+
+ text+=away;
+
+ if(MASTER->check_late_player(str))
+ {
+ text+=capitalize(str)+" hat uns leider fuer immer verlassen.\n";
+ }
+ else
+ {
+ if (h=MASTER->QueryTBanished(str))
+ text+=capitalize(str)+" will fruehestens "+h[TBANISH_EXTRACT];
+ }
+
+ if (h=properties[P_TESTPLAYER])
+ {
+ text+=capitalize(str)+" ist Testspieler"+IN;
+ if (stringp(h)) text+=" ("+h+")";
+ text+=".\n";
+ }
+ if ( h=properties[P_SECOND])
+ {
+ if (IS_WIZARD(this_player())) {
+ text+=capitalize(str)+" ist";
+ switch((int)properties[P_SECOND_MARK]) {
+ case -1: text+=" unsichtbar markierte"
+ +((int)properties[P_GENDER]!=FEMALE ? "r": "");
+ break;
+ case 0: text+=" nicht namentlich markierte"
+ +((int)properties[P_GENDER]!=FEMALE ? "r": "");
+ break;
+ default:
+ }
+ text+=" Zweitspieler"+IN;
+ if (stringp(h))
+ text+=" ("+capitalize(h)+")";
+ text+=".\n";
+ }
+ else if ((int)properties[P_SECOND_MARK]>0)
+ {
+ text+=capitalize(str)+" ist Zweitspieler"+IN;
+ if (stringp(h))
+ text+=" ("+capitalize(h)+")";
+ text+=".\n";
+ }
+ else if ((int)properties[P_SECOND_MARK]>-1)
+ text+=capitalize(str)+" ist Zweitspieler"+IN+".\n";
+ }
+ if (properties[P_DEADS])
+ {
+ text+="Bisher bereits "+properties[P_DEADS]+" mal gestorben\n";
+ // Bezieht sich nur auf den Zeitraum ab dem 30.11.'94
+ }
+ if(hc_play==1)
+ {
+ text+=capitalize(str)+" ist sehr mutig.\n";
+ }
+ if(hc_play>1)
+ {
+ text+=capitalize(str)+" ist am "+dtime(hc_play)+" in das Nirvana eingegangen.\n";
+ }
+
+ if (/*wiz && */userinfo) {
+ data=userinfo[3];
+ if (sizeof(data))
+ text+="Regionsmagier"+IN+" von : "+implode(map(data,#'capitalize),", ")+".\n";
+ data="/secure/master"->get_domain_homes(str);
+data=filter(data-({"erzmagier"}),#'stringp);
+ if ((wizlevel>=DOMAINMEMBER_LVL) && (sizeof(data)))
+ text+="Regionsmitarbeiter"+IN+" von: "+implode(map(data,#'capitalize),", ")+".\n"; /* #' */
+ }
+
+ if (userinfo) {
+ data=userinfo[8];
+ if (sizeof(data))
+ text += "Gildenmagier"+IN+" von : "+implode(map(data, #'capitalize), ", ")+".\n"; /* #' */
+ }
+
+ // ggf. Avatar-URI mit ausgeben.
+ if (flags & FLAG_AVATAR
+ && stringp(properties[P_AVATAR_URI]))
+ text += "Avatar-URI: " + properties[P_AVATAR_URI] + "\n";
+
+ if (flags & FLAG_SPONSOR)
+ text+=sponsoring(str);
+
+ filename="/players/"+str+"/.project";
+ if(file_size(filename)>=0)
+ text+="Projekt: "+explode(read_file(filename), "\n")[0]+"\n";
+ else {
+ filename="/p/service/loco/plans/"+str+".project";
+ if(file_size(filename)>=0)
+ text+="Projekt: "+explode(read_file(filename), "\n")[0]+"\n";
+ }
+ if (seer && !(flags&FLAG_NOPLAN)) {
+ filename="/players/"+str+"/.plan";
+ if(file_size(filename)>=0)
+ text+="Plan:\n"+read_file(filename);
+ else {
+ filename="/p/service/loco/plans/"+str+".plan";
+ if(file_size(filename)>=0)
+ text+="Plan:\n"+read_file(filename);
+// else
+// text+="Keine .plan-Datei.\n";
+ }
+ }
+ properties=0;
+ if (!local)
+ return text;
+ this_player()->More(text);
+ return "";
+}
+
+string Finger(string str)
+{
+ if(!str || str=="") { notify_fail("Wen denn?\n"); return 0; }
+ else
+ return finger_single(str,1);
+}
diff --git a/p/daemon/fishmaster.c b/p/daemon/fishmaster.c
new file mode 100644
index 0000000..3997584
--- /dev/null
+++ b/p/daemon/fishmaster.c
@@ -0,0 +1,165 @@
+/*Fisch-Master, (c) Vanion@MG, 26.05.02
+
+ Fische die im MG geangelt werden koennen, sollen nicht als endlose
+ Geldquelle dienen. Daher habe ich einen Master gebaut, der sich merkt
+ Welcher Spieler wieviel Fisch geangelt hat. Je mehr Fisch der Spieler
+ faengt, desto billiger wird der Fisch.
+
+ Schnittstelle:
+
+ Der Master hat abgesehen von Standard-Funktionen nur eine Schnittstelle
+
+ public int PriceOfFish(object player, object fish)
+
+ Der erste Parameter ist der Spieler, der den Fisch gecloned hat, der
+ zweite ist der Fisch, den der Spieler gefangen hat. Der Fisch muss
+ hierbei von Fraggles Standardfisch erben.
+
+ Der Preis des Fisches faellt linear je naeher der Spieler an die
+ Maximal-Grenze fuer Fisch kommt (Diese ist durch die Konstante MAX_FISH
+ festgelegt.) Der Master merkt sich ueber einen Zeitraum von FORGET_IT
+ Sekunden, dass der Spieler diesen Fisch gefangen hat. Ist der Zeitraum
+ vergangen, geht der Fisch nicht mehr in die Rechnung ein.
+ */
+
+#pragma strong_types, save_types, rtt_checks
+#pragma no_clone, no_shadow, no_inherit
+
+#define SAVE_FILE __DIR__+"save/fishes"
+
+#define FORGET_IT 1728000 // Nach 20 Tagen
+#define MAX_FISH 20000 // Wenn Spieler Max-Fish geholt hat, hat der
+ // Fisch keinen Wert mehr. (Einheit Gramm)
+
+#include <properties.h>
+
+// Hier wird gespeichert, welcher Spieler wieviel Fisch gefangen hat.
+mapping fishes = m_allocate(0,1);
+static int save_me_soon;
+
+protected void create() {
+ // Ohne das darf dieses Objekt hier nicht schreiben.
+ seteuid(getuid());
+ // Globale Variablen einlesen/initialisieren.
+ restore_object(SAVE_FILE);
+ set_next_reset(4*3600);
+}
+
+static void CleanupData() {
+ int expire_date = time()-FORGET_IT;
+ // Ueber alle Spielernamen laufen.
+ foreach(string pl, mixed *fishlist: fishes) {
+ // Ueber alle Fischdaten aller Spieler laufen.
+ foreach(int *fish: fishlist) {
+ if (fish[0]<expire_date) {
+ fishlist-=({fish});
+ save_me_soon=1;
+ }
+ }
+ if ( !sizeof(fishlist) )
+ fishes = m_delete(fishes, pl);
+ else
+ fishes += ([pl:fishlist]);
+ }
+}
+
+//ZZ: man koennte hier ggf. das neue Gewicht bestimmen statt es bei jedem
+//ZZ:Abruf zu tun.
+// Fuegt einen neuen Fisch in die Liste mit Fischen ein
+static int AddFish(string player_id, int weight) {
+ // Fisch hinzufuegen
+ if ( member(fishes, player_id) )
+ fishes[player_id] += ({({ time(),weight})});
+ else
+ fishes += ([ player_id: ({ ({time(),weight}) }) ]);
+ return 1;
+}
+
+// Hier wird das Gewicht des bereits gefangenen Fisches umgewandelt
+// in den Preis-Prozentsatz, den der Fisch noch kostet.
+static int WeightToPrice(int weight) {
+ if (weight >= MAX_FISH) return 0;
+ if (weight <= 0 ) return 100;
+ // Eine lineare Kurve. Je mehr Fisch geholt, desto billiger
+ // wird der. // Arathorn: Spannend, so eine lineare Kurve. :-)
+ return 100-(weight*100/MAX_FISH);
+}
+
+// Gibt das Gesamtgewicht des gefangenen Fisches zum Spieler zurueck
+static int QueryWeight(string player_id) {
+ int weight;
+ mixed list = fishes[player_id];
+
+ // Erstmal werden ungueltige Fische aus der Liste entfernt
+ //ZZ: ich finde, das muss nicht unbedingt bei jeden Abruf getan werden
+ //ZZ::und nicht fuer alle Spieler. reicht nicht einfach alle 4h?
+ CleanupData();
+
+ // Zusammenzaehlen der Einzelgewichte der gueltigen Fische.
+ foreach(int *fish : fishes[player_id]) {
+ weight += fish[1];
+ }
+ return weight;
+}
+
+// Wenn ein Spieler einen neuen Fisch bekommt, wird der Preis fuer den
+// Fisch bestimmt. Dieser resultiert aus der Menge (Gewicht) des Fisch,
+// die der Spieler in letzter Zeit schon gefangen hat. Zurueckgegeben
+// wird der Preis in Teilen von 100.
+public int PriceOfFish(object player, object fish) {
+ string player_id;
+ int price_of_new_fish;
+
+ // Sind Spieler und Fisch uebergeben?
+ if ( !player || !fish ) return 0;
+
+ // Spieler-ID finden.
+ player_id = getuuid(player);
+
+ // Ist player ein echter Spieler?
+ // NPC clonen sich Fische, die 100% kosten.
+ if (member(users(),player)==-1) return 100;
+
+ // Is der Fisch ein Fisch von Fraggle?
+ if (member(inherit_list(fish), __PATH__(1)+"/fish.c")==-1)
+ return 0;
+
+ // Feststellen wieviel Fisch ein Spieler gefangen hat, und
+ // daraus den Preis berechnen.
+ price_of_new_fish = WeightToPrice(QueryWeight(player_id));
+
+ // Diesen Fisch in die Datenbank eintragen
+ AddFish(player_id, fish->QueryProp(P_WEIGHT));
+
+ // Ich bin neugierig.
+ /*DEBUG(player->Name()+" hat einen Fisch mit "+fish->QueryProp(P_WEIGHT)+
+ " g gefangen. Der Wert liegt bei "+price_of_new_fish+"%");*/
+
+ // Die Aenderung soll persistent sein.
+ save_me_soon=1;
+
+ // Rueckgabe des Fischpreises.
+ return price_of_new_fish;
+}
+
+// Speichern der globalen Variablen
+static void save() {
+ save_object(SAVE_FILE);
+ return;
+}
+
+// Da nichts geerbt wird, muss das destruct() selbst gemacht werden
+// Wir speichern auf jeden Fall.
+varargs int remove(int silent) {
+ save();
+ destruct(this_object());
+ return 1;
+}
+
+// Daten aufraeumen und ggf. wegspeichern
+void reset() {
+ CleanupData();
+ if ( save_me_soon )
+ save();
+ set_next_reset(4*3600); // alle 4 Stunden reicht.
+}
diff --git a/p/daemon/iplookup.c b/p/daemon/iplookup.c
new file mode 100644
index 0000000..7608d8f
--- /dev/null
+++ b/p/daemon/iplookup.c
@@ -0,0 +1,225 @@
+/*
+ * Mudlibseite des iplookup systems, mit dem aus numerischen IP Adressen
+ * der Hostname und der Ort, an dem sich der Rechner befinden berechnet wird.
+ * Ergebnisse werden gecachet.
+ *
+ * (c)2010 rumata @ morgengrauen
+ */
+
+#pragma strict_types,save_types
+#pragma no_clone,no_shadow
+
+// Format der ipmap:
+// Key: numerische ip als string (ohne fuehrende nullen)
+// Data:
+// [ip,0] Hostname, der vom externen Server geliefert wurde.
+// [ip,1] Locationinformation, die vom externen Server kommt.
+// dieser sollte den Ort, oder falls es nicht besser geht, das Land
+// im Klartext liefern.
+// [ip,2] Zeitstempel, bei dessen erreichen der Wert geloescht werden
+// soll.
+// [ip,3] Status der Anfrage
+
+private mapping ipmap;
+#define IPMAP_HOST 0
+#define IPMAP_CITY 1
+#define IPMAP_EXPIRE 2
+#define IPMAP_STATUS 3
+
+// externer server, der der lokationdaten berechnet
+#define IPLOOKUP_HOST "127.0.0.1"
+#define IPLOOKUP_PORT 8711
+#define IPLOOKUP_SAVE "/p/daemon/save/iplookup"
+
+// moegliche statuswerte (platz zum zaehlen der anfragen gelassen)
+#define STATUS_QUERYING 1
+#define STATUS_AUTHORITATIVE 128
+
+// so lange werden die daten im cache gehalten
+#define CACHE_TIME 86400 /* 1 day */
+
+// Ab dieser Groesse betrache ich den cache als "gross genug",
+// um einen cache update in etappen zu rechtfertigen.
+#define LARGE_CACHE_LIMIT 2000
+
+// so lange sind wir bereit auf die antwort einer anfrage beim server zu warten
+#define QUERY_TIME 60 /* 1 min */
+
+#include <config.h>
+#if MUDNAME == "MorgenGrauen"
+#define LOG(x) log_file("rumata/iplookup.log",strftime("%Y-%m-%d %H:%M:%S: ")+(x)+"\n")
+#else
+#define LOG(x)
+#endif
+
+// PROTOTYPES
+
+public void create();
+public void reset();
+static int active_entry( string key, mixed* data );
+static void expire_cache_small();
+static void expire_cache_large( string* keys );
+public void update( string udp_reply );
+
+// IMPLEMENTATION
+
+public void create() {
+ seteuid(getuid());
+ restore_object( IPLOOKUP_SAVE );
+
+ if( !mappingp(ipmap) || widthof(ipmap)!=4 ) {
+ ipmap = m_allocate( 0, 4 );
+ LOG("restore failed, creating new ipmap");
+ } else {
+ LOG(sprintf("created (%d entries)",sizeof(ipmap)));
+ }
+}
+
+public void reset() {
+ if( sizeof(ipmap) < LARGE_CACHE_LIMIT ) {
+ expire_cache_small();
+ } else {
+ LOG(sprintf("reset %d ->",sizeof(ipmap)));
+ expire_cache_large( m_indices(ipmap) );
+ }
+ set_next_reset(10800);
+}
+
+static int active_entry( string key, mixed* data ) {
+ return time() < data[IPMAP_EXPIRE];
+}
+
+// Den cache auf einfache weise aufraeumen.
+static void expire_cache_small() {
+ int s = sizeof(ipmap);
+ ipmap = filter( ipmap, #'active_entry );
+ save_object( IPLOOKUP_SAVE );
+ LOG(sprintf("reset (%d -> %d)",s,sizeof(ipmap)));
+}
+
+// Kompliziertere routine, die den cache etappenweise abarbeitet.
+static void expire_cache_large( string* keys ) {
+ if( !pointerp(keys) ) return;
+ int next=0;
+ foreach( string key: keys ) {
+ if( get_eval_cost() < 100000 ) break;
+ if( ipmap[key,IPMAP_EXPIRE] < time() ) {
+ m_delete( ipmap, key );
+ }
+ next++;
+ }
+ LOG(sprintf("checking %d of %d",next,sizeof(keys)));
+ if( next<sizeof(keys) ) {
+ call_out( #'expire_cache_large, 6, keys[next..] );
+ } else {
+ save_object( IPLOOKUP_SAVE );
+ LOG(sprintf("reset -> %d (done)",sizeof(ipmap)));
+ }
+}
+
+#define SEARCHING "<auf der Suche...>"
+
+/* Erzeugt einen temporaeren Eintrag, der fuer eine Suche steht.
+ * Wenn die Antwort kommt, wird der Eintrag mit den entgueltigen
+ * Daten ueberschrieben.
+ */
+static void make_request( string ipnum ) {
+ send_udp( IPLOOKUP_HOST, IPLOOKUP_PORT, ipnum );
+ ipmap += ([ ipnum : ipnum ; SEARCHING ;
+ time()+QUERY_TIME ; STATUS_QUERYING
+ ]);
+}
+
+/* Liefert zu einer gegebenen ipnum den Ort.
+ * diese Funktion wird von simul_efun aufgerufen.
+ * @param ipnum eine numerische ip-adresse oder ein interactive
+ * @return den Ort (oder das Land) in dem sich die ip-adresse
+ * laut externem Server befindet.
+ */
+public string country( mixed ipnum ) {
+ string host,city;
+ int expire,state;
+
+ if( objectp(ipnum) ) {
+ ipnum = query_ip_number(ipnum);
+ }
+ if( !stringp(ipnum) ) {
+ return "<undefined>";
+ }
+ if( !m_contains( &host, &city, &expire, &state, ipmap, ipnum ) ) {
+ make_request( ipnum );
+ return SEARCHING;
+ }
+
+ return city;
+}
+
+/* Liefert zu einer gegebenen ipnum den Hostnamen.
+ * diese Funktion wird von simul_efun aufgerufen.
+ * @param ipnum eine numerische ip-adresse oder ein interactive
+ * @return den Hostnamen der zu der angegebenen ip-adresse gehoert.
+ * wenn der hostname nicht bekannt ist, wird die ipadresse zurueckgegeben.
+ */
+public string host( mixed ipnum ) {
+ string host,city;
+ int expire,state;
+
+ if( objectp(ipnum) ) {
+ ipnum = query_ip_number(ipnum);
+ }
+ if( !stringp(ipnum) ) {
+ return "<undefined>";
+ }
+ if( !m_contains( &host, &city, &expire, &state, ipmap, ipnum ) ) {
+ make_request( ipnum );
+ return ipnum;
+ }
+
+ return host;
+}
+
+/* wird vom master aufgerufen, wenn eine antwort vom externen
+ * iplookup dienst eintrudelt.
+ */
+public void update( string udp_reply ) {
+ string* reply;
+ if( previous_object()!=master() ) return;
+ reply = explode(udp_reply,"\n");
+ if( sizeof(reply)<4 ) return;
+
+ if( reply[3] == "<unknown>" ) reply[3] = reply[1];
+ if( reply[2] == "<unknown>" ) reply[2] = "irgendwoher";
+ ipmap += ([ reply[1] : reply[3] ; reply[2] ; time()+CACHE_TIME ;
+ STATUS_AUTHORITATIVE ]);
+ //save_object( IPLOOKUP_SAVE );
+}
+
+int remove(int silent) {
+ save_object( IPLOOKUP_SAVE );
+ destruct(this_object());
+ return 1;
+}
+
+// DEBUGGING CODE
+
+#if 0
+public void dump( string key, mixed* data ) {
+ printf( "%s: %s (%s) s=%d bis %d\n", key, data[0], data[1],
+ data[3], data[2] );
+}
+
+public void load(string s) {
+ restore_object(s);
+ if( !mappingp(ipmap) || widthof(ipmap)!=4 ) {
+ ipmap = m_allocate( 0, 4 );
+ LOG("restore failed, creating new ipmap");
+ } else {
+ LOG(sprintf("created (%d entries)",sizeof(ipmap)));
+ }
+}
+
+public void debug() {
+ map( ipmap, #'dump );
+ printf( "= %d Eintraege\n", sizeof(ipmap) );
+}
+#endif
diff --git a/p/daemon/lag-o-daemon.c b/p/daemon/lag-o-daemon.c
new file mode 100644
index 0000000..6d23860
--- /dev/null
+++ b/p/daemon/lag-o-daemon.c
@@ -0,0 +1,272 @@
+#pragma strong_types,save_types,rtt_checks
+#pragma no_clone
+#pragma no_shadow
+#pragma no_inherit
+#pragma pedantic
+
+#include <driver_info.h>
+
+#ifndef DEBUG
+#define DEBUG(x) if (find_player("zesstra"))\
+ tell_object(find_player("zesstra"),"LAG: "+x+"\n")
+#endif
+
+#define SAVEFILE __DIR__+"save/lag-o-daemon"
+#define LOG(x) log_file("LAG",x,200000);
+#define LOGMAX(x) log_file("LAGMAX",x,300000);
+
+// Anzahl an zu merkenden Werten (1800==3600s)
+#define MESSWERTE 1800
+
+nosave float hbstat, obstat;
+nosave mapping lastwarning=([]);
+nosave int *lasttime = utime();
+nosave int lasthbcount = absolute_hb_count();
+float *lagdata = allocate(MESSWERTE,0.0);
+// Lags fuer die letzten 2s (1 HB), 20s (10 HB), 60s (30 HB),
+// 300s (150 HB), 900s (450 HB), 3600s (1800 HB)
+float *lag = allocate(6,0.0);
+int *hbdata = allocate(MESSWERTE);
+// Lags fuer die letzten 2s (1 HB), 20s (10 HB), 60s (30 HB),
+// 300s (150 HB), 900s (450 HB), 3600s (1800 HB)
+int *hbs = allocate(6);
+int *codata = allocate(MESSWERTE);
+// Lags fuer die letzten 2s (1 HB), 20s (10 HB), 60s (30 HB),
+// 300s (150 HB), 900s (450 HB), 3600s (1800 HB)
+int *callouts = allocate(6);
+
+void create() {
+ seteuid(getuid());
+ // Bei nicht einlesbaren Savefile und nach Reboot Werte neu initialisieren.
+ // Denn dann ist die Differenz der HBs bedeutungslos. Erkennung ploetzlich
+ // kleinere HB-Zahl.
+ if (!restore_object(SAVEFILE)
+ || efun::driver_info(DI_NUM_HEARTBEAT_TOTAL_CYCLES) < lasthbcount)
+ {
+ lagdata = allocate(MESSWERTE,0.0);
+ lag = allocate(6,0.0);
+ hbdata = allocate(MESSWERTE);
+ hbs = allocate(6);
+ codata = allocate(MESSWERTE);
+ callouts = allocate(6);
+ }
+ set_heart_beat(1);
+}
+
+int remove(int silent) {
+ save_object(SAVEFILE);
+ destruct(this_object());
+ return 1;
+}
+
+// liefert Mittelwertarray zurueck
+//TODO: <int|string>* results
+private mixed calc_averages(mixed data, mixed results, int index, float scale) {
+ float tmp;
+ int t;
+
+ if (!scale) scale=1.0;
+
+ // 2s
+ results[0] = to_float(data[index]) * scale;
+
+ // alle 10s / 5 HBs den 20s-Wert neu berechnen
+ if ( index%5 == 1) {
+ if (index<9) {
+ foreach(int i: index-4+MESSWERTE .. MESSWERTE-1) {
+ tmp+=data[i];
+ t++;
+ }
+ foreach(int i: 0 .. index) {
+ tmp+=data[i];
+ t++;
+ }
+ }
+ else {
+ foreach(int i: index-9 .. index) {
+ tmp+=data[i];
+ t++;
+ }
+ }
+ results[1]=tmp/to_float(t) * scale;
+ }
+
+ // alle 10s / 5 HBs den 60s-Wert neu berechnen, aber in anderem HB als den
+ // 10s-Wert
+ if ( index%5 == 3) {
+ tmp=0.0;
+ t=0;
+ if (index<29) {
+ foreach(int i: index-14+MESSWERTE .. MESSWERTE-1) {
+ tmp+=data[i];
+ t++;
+ }
+ foreach(int i: 0 .. index) {
+ tmp+=data[i];
+ t++;
+ }
+ }
+ else {
+ foreach(int i: index-29 .. index) {
+ tmp+=data[i];
+ t++;
+ }
+ }
+ results[2]=tmp/to_float(t) * scale;
+ }
+
+ // 300s
+ // alle 40s / 20 HBs den 300s-Wert neu berechnen.
+ if ( index%20 == 12) {
+ tmp=0.0;
+ t=0;
+ if (index<149) {
+ foreach(int i: index-74+MESSWERTE .. MESSWERTE-1) {
+ tmp+=data[i];
+ t++;
+ }
+ foreach(int i: 0 .. index) {
+ tmp+=data[i];
+ t++;
+ }
+ }
+ else {
+ foreach(int i: index-149 .. index) {
+ tmp+=data[i];
+ t++;
+ }
+ }
+ results[3]=tmp/to_float(t) * scale;
+ }
+
+
+ // 900s
+ // alle 80s / 40 HBs den 900s-Wert neu berechnen.
+ if ( index%40 == 31) {
+ tmp=0.0;
+ t=0;
+ if (index<449) {
+ foreach(int i: index-224+MESSWERTE .. MESSWERTE-1) {
+ tmp+=data[i];
+ t++;
+ }
+ foreach(int i: 0 .. index) {
+ tmp+=data[i];
+ t++;
+ }
+ }
+ else {
+ foreach(int i: index-449 .. index) {
+ tmp+=data[i];
+ t++;
+ }
+ }
+ results[4]=tmp/to_float(t) * scale;
+ }
+
+ // 3600s
+ // alle 300s / 150 HBs den 3600s-Wert neu berechnen.
+ if ( index%150 == 128) {
+ tmp=0.0;
+ t=0;
+ if (index<1799) {
+ foreach(int i: index-899+MESSWERTE .. MESSWERTE-1) {
+ tmp+=data[i];
+ t++;
+ }
+ foreach(int i: 0 .. index) {
+ tmp+=data[i];
+ t++;
+ }
+ }
+ else {
+ foreach(int i: index-1799 .. index) {
+ tmp+=data[i];
+ t++;
+ }
+ }
+ results[5]=tmp/to_float(t) * scale;
+ }
+ return results;
+}
+
+protected void heart_beat() {
+ int hbcount=efun::driver_info(DI_NUM_HEARTBEAT_TOTAL_CYCLES);
+ // per Modulo Index auf 0 - 449 begrenzen
+ int index=hbcount%MESSWERTE;
+
+ // mass fuer das lag ist das Verhaeltnis von vergangener Sollzeit (Differenz
+ // HB-Counts * 2) und vergangener Istzeit (Differenz von time()). Und das
+ // ganze wird dann als Abweichung von 1 gespeichert (0, wenn kein Lag)
+ int *nowtime = utime();
+ float timediff = to_float((nowtime[0]-lasttime[0]))
+ + to_float(nowtime[1]-lasttime[1])/1000000.0;
+
+// lagdata[index] = 1.0 -
+// (to_float((hbcount - lasthbcount) * __HEART_BEAT_INTERVAL__)
+// / timediff);
+// lagdata[index] = (timediff -
+// to_float((hbcount - lasthbcount) * __HEART_BEAT_INTERVAL__)) /
+// to_float((hbcount - lasthbcount) * __HEART_BEAT_INTERVAL__);
+ lagdata[index] = abs(to_float((hbcount - lasthbcount) * __HEART_BEAT_INTERVAL__)
+ - timediff) /
+ to_float((hbcount - lasthbcount) * __HEART_BEAT_INTERVAL__);
+
+
+ hbdata[index] = efun::driver_info(DI_NUM_HEARTBEATS_LAST_PROCESSED);
+ codata[index] = efun::driver_info(DI_NUM_CALLOUTS);
+
+ /*
+ DEBUG(sprintf("Index: %d: %d/%.6f => %:6f /1\n",index,
+ (hbcount - lasthbcount)*2,
+ timediff, lagdata[index]));
+ */
+ lasthbcount=hbcount;
+ lasttime=nowtime;
+
+ lag = calc_averages(lagdata, lag, index, 100.0);
+ // Multiplizieren mit 100
+ //DEBUG(sprintf("%O\n",lag));
+ //lag = map(lag, function float (float f) {return f*100.0;} );
+ //DEBUG(sprintf("%O\n",lag));
+ hbs = map(calc_averages(hbdata, hbs, index,1.0),#'to_int);
+ callouts = map(calc_averages(codata, callouts, index,1.0),#'to_int);
+
+ // und ausserdem mal etwas loggen fuer den Moment
+ if (lag[0] > 1000 || callouts[0] > 1000)
+ {
+ LOGMAX(sprintf("%s, %d, %d, %:6f, %:6f, %:6f, %:6f, %:6f, %:6f, "
+ "%d, %d, %d, %d, %d, %d, "
+ "%d, %d, %d, %d, %d, %d, \n",
+ strftime("%y%m%d-%H%M%S"),time(),hbcount,
+ lag[0],lag[1],lag[2],lag[3],lag[4],lag[5],
+ hbs[0],hbs[1],hbs[2],hbs[3],hbs[4],hbs[5],
+ callouts[0],callouts[1],callouts[2],callouts[3],callouts[4],
+ callouts[5]));
+ }
+ else if (hbcount%80 == 0 || lag[0] > 10)
+ {
+ LOG(sprintf("%s, %d, %d, %:6f, %:6f, %:6f, %:6f, %:6f, %:6f, "
+ "%d, %d, %d, %d, %d, %d, "
+ "%d, %d, %d, %d, %d, %d, \n",
+ strftime("%y%m%d-%H%M%S"),time(),hbcount,
+ lag[0],lag[1],lag[2],lag[3],lag[4],lag[5],
+ hbs[0],hbs[1],hbs[2],hbs[3],hbs[4],hbs[5],
+ callouts[0],callouts[1],callouts[2],callouts[3],callouts[4],
+ callouts[5]));
+ }
+}
+
+float *read_lag_data() {
+ return ({lag[2],lag[4],lag[5]});
+}
+float *read_ext_lag_data() {
+ return copy(lag);
+}
+int *read_hb_data() {
+ return copy(hbs);
+}
+int *read_co_data() {
+ return copy(callouts);
+}
+
diff --git a/p/daemon/mand.c b/p/daemon/mand.c
new file mode 100644
index 0000000..e80c50e
--- /dev/null
+++ b/p/daemon/mand.c
@@ -0,0 +1,169 @@
+//
+// mand.c - Manpage Daemon
+//
+// Erweitert und umgearbeitet fuer die Anbindung durch
+// die Magiershell
+//
+// von Mandragon@MG
+//
+// Basiert auf der Arbeit von Hadra,Mupfel und Tyron
+// (alle @Anderland)
+//
+
+#define DEBUGGER "mandragon"
+
+#define DEBUG(x) if (find_player(DEBUGGER)) \
+ tell_object(find_player(DEBUGGER),x+"\n")
+
+
+#pragma strict_types,save_types
+#pragma no_clone,no_shadow
+
+#include <daemon/mand.h>
+
+private nosave mapping pages;
+private nosave string *keys;
+private nosave mapping pages_case;
+private nosave string *keys_case;
+
+static string glob2regexp(string str)
+{
+ str=regreplace(str,"([\\.\\^\\$\\[\\]\\(\\)])","\\\\\\1",1);
+ str=regreplace(str,"\\*",".*",1);
+ return regreplace("^"+str+"$","?",".",1);
+}
+
+void cache_entry(string entry, string dir, string file)
+{
+ string *data;
+ int j;
+
+ if (m_contains(&data,pages,lower_case(entry)))
+ {
+ j=sizeof(data);
+ while(j--) data[--j]=data[j];
+ pages[lower_case(entry)]=data+({ dir+"/"+file, dir+"/"+file});
+ }
+ else
+ {
+ pages[lower_case(entry)]=({entry,dir+"/"+file});
+ keys+=({lower_case(entry)});
+ }
+ if (m_contains(&data,pages_case,entry))
+ {
+ j=sizeof(data);
+ while(j--) data[--j]=data[j];
+ pages_case[entry]=data+({ dir+"/"+file, dir+"/"+file});
+ }
+ else
+ {
+ pages_case[entry]=({entry,dir+"/"+file});
+ keys_case+=({entry});
+ }
+ return;
+}
+
+static void load_synonyms(string dir)
+{
+ string filedata,*syn;
+
+ if (file_size(MAND_DOCDIR+dir+"/.synonym")<=0) return;
+ if (!(filedata=read_file(MAND_DOCDIR+dir+"/.synonym"))) return;
+ syn=regexplode(filedata, "([ \t][ \t]*|\n)")-({""});
+ syn-=regexp(syn,"^([ \t][ \t]*|\n)$");
+ while(sizeof(syn))
+ {
+ cache_entry(syn[0],dir,syn[1]);
+ syn=syn[2..];
+ }
+ return;
+}
+
+static void cache_directory(string dir)
+{
+ string file;
+ int i,j;
+ mixed *files;
+
+ files = get_dir(MAND_DOCDIR+dir+"/*",3)||({});
+ i=sizeof(files);
+ for (j=0;j<i;)
+ {
+ file=files[j++];
+ if (file[0]=='.')
+ {
+ j++;
+ continue;
+ }
+ if (files[j++]==-2)
+ {
+ cache_directory(dir+"/"+file);
+ continue;
+ }
+ cache_entry(file,dir,file);
+ }
+ load_synonyms(dir);
+}
+
+void update_cache()
+{
+ int i;
+ string *docdirs;
+ mixed *tmp;
+
+ tmp=get_dir(MAND_DOCDIR "*",3);
+ docdirs=({});
+ pages=([]);
+ pages_case=([]);
+ keys_case=({});
+ keys=({});
+ i=sizeof(tmp);
+ while(i--)
+ if (tmp[i]==-2)
+ docdirs+=({ tmp[(i--)-1] });
+ docdirs-=({".",".."});
+ docdirs-=MAND_EXCLUDE;
+ i=sizeof(docdirs);
+ while(i--) cache_directory(docdirs[i]);
+ return;
+}
+
+void create()
+{
+ seteuid(getuid());
+ update_cache();
+ set_next_reset(10080); //Einmal pro Woche langt
+ return;
+}
+
+void reset()
+{
+ update_cache();
+ set_next_reset(10080);
+ return;
+}
+
+//
+// locate(): Manpage im Cache suchen:
+// match = 1 -> glob-matching, match=2 -> regexp-matching
+// Pruefung ob str gueltige regexp erfolgt in der Shell
+//
+
+string *locate(string str,int match)
+{
+ string *matches,*ret,str2;
+ int i;
+
+ if (!match&&member(keys_case,str)!=-1) return pages_case[str];
+ str=lower_case(str);
+ if (match==1) str=glob2regexp(str);
+ if (match) matches=regexp(keys,str);
+ else if (member(keys,str)!=-1) matches=({ str });
+ else return ({});
+
+ ret=({});
+ i=sizeof(matches);
+ while(i--)
+ ret+=pages[matches[i]];
+ return ret;
+}
diff --git a/p/daemon/moneylog.c b/p/daemon/moneylog.c
new file mode 100644
index 0000000..f3a59bf
--- /dev/null
+++ b/p/daemon/moneylog.c
@@ -0,0 +1,246 @@
+// Log-Daemon, um Geldbugs zu finden.
+//
+// Noch experimentell, ich feile noch fleissig an den Werten&Co.
+// Wer mir ohne triftigen Grund daran herumpfuscht, der wird standesrechtlich
+// mit faulen Eiern beworfen. ]:->
+//
+// Tiamak
+
+#pragma strong_types,save_types
+#pragma no_clone,no_shadow
+
+#include <wizlevels.h>
+#include <money.h>
+
+#define LOGNAME "ADDMONEY"
+#define SAVEFILE "/p/daemon/save/moneylog"
+
+#define THRESHOLD_HOURLY 10000 // wieviel Geld darf man an einer Stelle pro
+#define THRESHOLD_DAILY 50000 // Stunde/Tag bekommen, ohne dass geloggt wird?
+
+
+private mapping mon, grenzen;
+private int check, next_reset;
+
+
+public void create()
+{
+ // wir muessen Savefile schreiben duerfen
+ seteuid(getuid());
+
+ if ( !restore_object(SAVEFILE) ){
+ mon = m_allocate( 0, 2 );
+ grenzen = m_allocate( 0, 2 );
+ check = 0;
+ next_reset = 3600;
+ }
+
+ if ( next_reset > 3600 )
+ next_reset = 3600;
+
+ if ( next_reset < 60 )
+ next_reset = 60;
+
+ // Auswertung jede Stunde
+ set_next_reset( next_reset );
+ next_reset += time();
+}
+
+
+// Sortierfunktionen
+
+static int _sort_hourly( string fn1, string fn2 )
+{
+ return mon[fn1, 0] > mon[fn2, 0];
+}
+
+
+static int _sort_daily( string fn1, string fn2 )
+{
+ return mon[fn1, 1] > mon[fn2, 1];
+}
+
+
+// im reset() wird die Auswertung vorgenommen
+public void reset()
+{
+ int i;
+ string *files, txt;
+
+ // keinen Aufruf per Hand bitte
+ if ( time() < next_reset )
+ return;
+
+ // Auswertung jede Stunde
+ set_next_reset(3600);
+ next_reset = time() + 3600;
+ check++;
+ txt = "";
+
+ files = sort_array( m_indices(mon), #'_sort_hourly/*'*/ );
+
+ for ( i = sizeof(files); i--; ){
+ if ( !(grenzen[files[i], 0] < 0) &&
+ (grenzen[files[i], 0] && mon[files[i], 0] > grenzen[files[i], 0])
+ || (!grenzen[files[i], 0] && mon[files[i], 0] > THRESHOLD_HOURLY) )
+ txt += sprintf( "%12d --- %s\n", mon[files[i], 0], files[i] );
+
+ mon[files[i], 0] = 0;
+ }
+
+ // nur loggen, wenn es auch etwas zu loggen gibt
+ if ( txt != "" )
+ log_file( LOGNAME, sprintf( "%s: =================================="
+ "==============(stuendlich)===\n",
+ ctime(time())[4..15] ) + txt );
+
+ // der "grosse" Check kommt nur einmal pro Tag
+ if ( check < 24 ){
+ save_object(SAVEFILE);
+ return;
+ }
+
+ check = 0;
+ txt = "";
+ files = sort_array( m_indices(mon), #'_sort_daily/*'*/ );
+
+ for ( i = sizeof(files); i--; )
+ if ( !(grenzen[files[i], 1] < 0) &&
+ (grenzen[files[i], 1] && mon[files[i], 1] > grenzen[files[i], 1])
+ || (!grenzen[files[i], 1] && mon[files[i], 1] > THRESHOLD_DAILY) )
+ txt += sprintf( "%12d --- %s\n", mon[files[i], 1], files[i] );
+
+ if ( txt != "" )
+ log_file( LOGNAME, sprintf( "%s: =================================="
+ "==============(taeglich)=====\n",
+ ctime(time())[4..15] ) + txt );
+
+ mon = m_allocate( 1, 2 );
+ save_object(SAVEFILE);
+}
+
+
+// die eigentliche Logg-Funktion
+public void AddMoney( object ob, int amount )
+{
+ string fn;
+
+ // keine Manipulationen per Hand bitte
+ if ( !objectp(ob) || amount < 0 || !previous_object() ||
+ (!query_once_interactive(previous_object()) &&
+ load_name(previous_object()) != GELD)
+ || IS_LEARNER(previous_object()) )
+ return;
+
+ fn = explode( object_name(ob), "#" )[0];
+
+ mon[fn, 0] += amount;
+ mon[fn, 1] += amount;
+}
+
+
+// Savefile noch schnell abspeichern - wir wollen ja keine Daten verlieren ;-)
+public int remove()
+{
+ next_reset = query_next_reset(this_object()) - time();
+ save_object(SAVEFILE);
+
+ destruct(this_object());
+ return 1;
+}
+
+
+// fuer bestimmte Files kann man die Grenzwerte einzeln setzen
+public varargs int modify_threshold( string fn, int amount1, int amount2 )
+{
+ if ( !this_interactive() || this_interactive() != this_player() ||
+ !IS_ARCH(this_interactive()) || process_call() )
+ return -1;
+
+ if ( !stringp(fn) || !intp(amount1) || !intp(amount2) )
+ return -2;
+
+ if ( !amount1 && !amount2 ){
+ m_delete( grenzen, fn );
+ write( "Eintrag " + fn + " geloescht.\n" );
+ return 1;
+ }
+
+ grenzen[fn, 0] = amount1;
+ grenzen[fn, 1] = amount2;
+
+ write( break_string( "Die Grenzen fuer " + fn + " betragen jetzt " +
+ ((amount1 < 0) ? "unendlich viele" :
+ to_string(amount1)) + " Muenzen fuer den "
+ "stuendlichen und " +
+ ((amount2 < 0) ? "unendlich viele" :
+ to_string(amount2)) + " Muenzen fuer den taeglichen "
+ "Check.", 78 ) );
+
+ save_object(SAVEFILE);
+ return 1;
+}
+
+
+// einzeln gesetzte Grenzwerte abfragen
+public varargs mixed query_thresholds( string str )
+{
+ int i;
+ string *files, txt;
+
+ if ( !this_player() )
+ return deep_copy(grenzen);
+
+ if ( stringp(str) && member( grenzen, str ) ){
+ write( break_string( "Die Grenzen fuer " + str + " betragen " +
+ ((grenzen[str, 0] < 0) ? "unendlich viele" :
+ to_string(grenzen[str, 0])) + " Muenzen fuer den "
+ "stuendlichen und " +
+ ((grenzen[str, 1] < 0) ? "unendlich viele" :
+ to_string(grenzen[str, 1])) +
+ " Muenzen fuer den taeglichen Check.", 78 ) );
+ return 0;
+ }
+
+ if ( stringp(str) && str != "" ){
+ write( "Es sind keine Grenzen fuer " + str + " eingetragen!\n" );
+ return 0;
+ }
+
+ files = sort_array( m_indices(grenzen), #'</*'*/ );
+ txt = "Eingetragene Grenzwerte:\n================================="
+ "=============================================\n";
+
+ for ( i = sizeof(files); i--; )
+ txt += sprintf( "%'.'-48s : %8d (h) %10d (d)\n",
+ files[i], grenzen[files[i], 0], grenzen[files[i], 1] );
+
+ this_player()->More( txt );
+ return 1;
+}
+
+
+// bisher geloggte Daten abfragen
+public mixed query_status()
+{
+ int i;
+ string *files, txt;
+
+ if ( !this_player() )
+ return deep_copy(mon);
+
+ files = sort_array( m_indices(mon), #'</*'*/ );
+ txt = "Geldquellen:\n================================="
+ "=============================================\n";
+
+ for ( i = sizeof(files); i--; )
+ txt += sprintf( "%'.'-48s : %8d (h) %10d (d)\n",
+ files[i], mon[files[i], 0], mon[files[i], 1] );
+
+ this_player()->More( txt );
+ return 1;
+}
+
+
+// keine Shadows bitte
+public varargs int query_prevent_shadow( object ob ) { return 1; }
diff --git a/p/daemon/namefake.c b/p/daemon/namefake.c
new file mode 100644
index 0000000..f4146b2
--- /dev/null
+++ b/p/daemon/namefake.c
@@ -0,0 +1,54 @@
+/* Dieses Objekt dient zum Faken von Namen fuer den Channeld.
+ * Hintergrund: der Channeld braucht beim send() ein Objekt (bzw. die in der
+ * Verarbeitung dann folgenden Programme). Der Mudlibmaster sendet Meldung in
+ * anderer Leute Namen (der darf das auch). Dafuer clont und konfiguriert er
+ * dieses Objekt und uebergibt es an den channeld. Nach 3s raeumen sich Clone
+ * dieses Objektes wieder auf.
+ * Falls der Spieler/NPC existiert, wenn die meldung gesendet wird, wird vom
+ * Master das richtige Objekt uebergeben, nicht dieses hier.
+ */
+
+#include <config.h>
+#include <wizlevels.h>
+
+inherit "/std/secure_thing";
+
+// Envcheck-Mechanismus missbrauchen. ;-)
+protected void check_for_environment(string cloner)
+{
+ if (clonep(this_object()))
+ remove(1);
+}
+
+// Zur Sicherheit auch im Reset
+void reset()
+{
+ if (clonep(this_object()))
+ remove(1);
+}
+
+// Kopie aus /std/thing/properties.c. Man braucht davon wohl kein
+// zugaengliches in jedem thing...
+// Welche externen Objekte duerfen zugreifen?
+nomask private int allowed()
+{
+ if ( (previous_object() && IS_ARCH(getuid(previous_object())) &&
+ this_interactive() && IS_ARCH(this_interactive())) ||
+ (previous_object() && getuid(previous_object()) == ROOTID &&
+ geteuid(previous_object()) == ROOTID) )
+ return 1;
+ return 0;
+}
+
+// Sollte nur von ROOT oder EM+ manipuliert werden.
+// Sprich, das tut so, als seien alle Props SECURED
+public varargs mixed Set( string name, mixed value, int type, int extern )
+{
+ if ((extern || extern_call())
+ && previous_object() != this_object()
+ && !allowed()) // aus thing/properties.c
+ return -1;
+
+ return ::Set(name, value, type, extern);
+}
+
diff --git a/p/daemon/object.h b/p/daemon/object.h
new file mode 100644
index 0000000..7f7e20f
--- /dev/null
+++ b/p/daemon/object.h
@@ -0,0 +1,38 @@
+// MorgenGrauen MUDlib
+//
+// OBJECT.H -- persistent object handling
+//
+// $Date: 1995/03/31 13:30:33 $
+// $Revision: 1.1 $
+/* $Log: object.h,v $
+ * Revision 1.1 1995/03/31 13:30:33 Hate
+ * Initial revision
+ *
+ * Revision 1.1 1994/03/20 17:07:28 Hate
+ * Initial revision
+ *
+ */
+
+#ifndef __OBJECT_H__
+#define __OBJECT_H__
+
+// defines
+#define OBJECTD "/p/daemon/objectd"
+#define OBJECTD_SAVE "/p/daemon/save/objectd"
+
+#endif // __OBJECT_H__
+
+#ifdef NEED_PROTOTYPES
+
+#ifndef __OBJECT_H_PROTO__
+#define __OBJECT_H_PROTO__
+
+// prototypes
+mixed AddObject(string obj, string env);
+mixed RemoveObject(string obj, string env);
+varargs void QueryObject(mixed env);
+
+
+#endif // __OBJECT_H_PROTO__
+
+#endif // NEED_PROTOYPES
diff --git a/p/daemon/objectd.c b/p/daemon/objectd.c
new file mode 100644
index 0000000..1b5d821
--- /dev/null
+++ b/p/daemon/objectd.c
@@ -0,0 +1,150 @@
+// MorgenGrauen MUDlib
+//
+// OBJECTD.C -- object daemon
+//
+// $Date: 1995/04/03 14:47:02 $
+// $Revision: 9510 $
+/* $Log: objectd.c,v $
+ * Revision 1.2 1995/04/03 14:47:02 Wargon
+ * QueryObject() verwendet bei BluePrints jetzt auch AddItem.
+ *
+ * Revision 1.1 1995/03/31 13:30:33 Hate
+ * Initial revision
+ *
+ */
+
+#pragma strong_types,save_types,rtt_checks
+#pragma no_clone, no_shadow
+
+#include <rooms.h>
+#include <properties.h>
+#include <defines.h>
+#include <daemon.h>
+
+mapping objects = ([]);
+private nosave int do_save;
+
+#define CLASS 0
+#define DATA 1
+
+protected void create()
+{
+ seteuid(getuid(ME));
+ restore_object(OBJECTD_SAVE);
+}
+
+protected void reset() {
+ if (do_save)
+ {
+ save_object(OBJECTD_SAVE);
+ do_save=0;
+ }
+}
+
+string AddObject(object obj, string env)
+{
+ if(!obj || !env || !objectp(obj) || !stringp(env))
+ return 0;
+
+ // save information
+ if(!member(objects, env))
+ objects[env] = ({ ({ object_name(obj), obj->QueryProp(P_AUTOLOAD) }) });
+ else
+ objects[env] += ({ ({ object_name(obj), obj->QueryProp(P_AUTOLOAD) }) });
+
+ do_save=1;
+ return env;
+}
+
+int RemoveObject(object|string obj, string env)
+{
+ if(!obj || !env || !stringp(env))
+ return 0;
+
+ // save information
+ if(member(objects, env))
+ {
+ mixed oblist = objects[env];
+ foreach(mixed arr: &oblist)
+ {
+ if (load_name(arr[CLASS]) == load_name(obj))
+ {
+ arr = 0;
+ // nur eins Austragen, nicht alle, falls mehr als einmal angemeldet
+ break;
+ }
+ }
+ objects[env] = oblist - ({0});
+ }
+ if(!sizeof(objects[env]))
+ m_delete(objects, env);
+
+ do_save=1;
+ return 1;
+}
+
+// Fragt nicht wirklich ab, sondern erstellt die angemeldeten Objekt in env.
+// Wenn kein env, wird previous_object() genommen.
+varargs void QueryObject(string envname)
+{
+ object env;
+ // take the caller as the questioning object
+ if(!envname || !stringp(envname))
+ env = previous_object();
+ else
+ env = find_object(envname);
+
+ // target must be a blueprint
+ if(IS_CLONE(env)) return 0;
+
+ envname=object_name(env);
+ if(member(objects, envname))
+ {
+ mixed oblist = objects[envname];
+ foreach(mixed arr : &oblist)
+ {
+ // Wir muessen unterscheiden, ob ein Clone angemeldete wurde oder eine
+ // Blueprint.
+ object o;
+ string *n = explode(arr[CLASS], "#");
+ if (sizeof(n) > 1 && sizeof(n[1]) > 0)
+ o = env->AddItem(n[0], REFRESH_DESTRUCT);
+ else
+ o = env->AddItem(n[0], REFRESH_DESTRUCT, 1);
+ o->SetProp(P_AUTOLOAD, arr[DATA]);
+ // Und das neue Objekt merken (ne Verwendung hat das zur Zeit aber
+ // nicht)
+ arr[CLASS] = object_name(o);
+ }
+ }
+}
+
+// Liefert eine Liste der in env angemeldeten Objekte. Wenn kein env, wird
+// previous_object() genommen.
+public varargs string QueryObjects(string envname)
+{
+ object env;
+ // take the caller as the questioning object
+ if(!envname || !stringp(envname))
+ env = previous_object();
+ else
+ env = find_object(envname);
+
+ // target must be a blueprint
+ if(IS_CLONE(env)) return 0;
+
+ mixed arrarr = objects[object_name(env)];
+ if (arrarr)
+ {
+ return CountUp(map(arrarr, #'[, CLASS),", ",", ");
+ }
+ return 0;
+}
+
+public varargs int remove(int silent)
+{
+ save_object(OBJECTD_SAVE);
+ destruct(ME);
+ return 1;
+}
+
diff --git a/p/daemon/pathd.c b/p/daemon/pathd.c
new file mode 100644
index 0000000..17d34ad
--- /dev/null
+++ b/p/daemon/pathd.c
@@ -0,0 +1,1000 @@
+// Daemon zum automatisierten Erstellen von Karten.
+// Soll spaeter mal wichtige Wege berechnen koennen fuer Anfaenger.
+//
+//
+// Wer mir ohne triftigen Grund daran herumpfuscht, der wird standesrechtlich
+// mit faulen Eiern beworfen. ]:->
+//
+// Tiamak
+
+#pragma strong_types,save_types,rtt_checks
+#pragma no_clone,no_inherit,no_shadow
+#pragma pedantic,range_check,warn_deprecated
+#pragma warn_empty_casts,warn_missing_return,warn_function_inconsistent
+
+#include <strings.h>
+#include <wizlevels.h>
+#include <player.h>
+#include <properties.h>
+#include <rooms.h>
+#include <moving.h>
+
+#define LOGNAME "PATHD"
+#define SAVEFILE "/p/daemon/save/pathd"
+#define DUMPFILE "/p/daemon/save/pathd-dump"
+//#define DEBUG
+
+// 10 verschiedene Leute werden gebraucht, um eine Verbindung zu verifizieren
+#define NEEDED 10
+#define TIMEOUT_NEW 1209600 // 14 Tage
+#define TIMEOUT_OLD 7776000 // 90 Tage
+
+// Art des Ausgangs
+#define NORMAL 0
+#define SPECIAL 1
+#define COMMAND 2
+
+// Kosten fuer unterschiedliche Wege
+#define COST_EXITS ({ 1, 10, 100 })
+#define COST_MANY 3
+#define COST_FEW 5
+#define COST_ONE 10
+// Kosten fuer Weltenwechsel (Para -> Normal)
+#define COST_WORLDCHANGE 100
+
+#ifdef DEBUG
+#define DBG(x) if ( find_player("zesstra") ) \
+ tell_object( find_player("zesstra"), (x)+"\n" );
+#define TELL_USER(x) if ( this_player() ) \
+ tell_object( this_player(), (x)+"\n" );
+#else
+#define DBG(x)
+#define TELL_USER(x)
+#endif
+
+#define LOG(x) log_file( "PATHD", (x), 100000 );
+
+// Variablen
+nosave int time_to_clean_up; // Zeit fuer naechstes Cleanup der Daten
+
+mapping pathmap;
+/* Prefix : ([ <submap> ]),
+ <submap> ist ein Mapping von Raeumen mit einem Array von Verbindungen:
+ ([<room>: ({ conn1, conn2, conn3, ...}), ...])
+ Jede Verbindung ist ein Array mit folgenden Indizes:
+ */
+#define CONN_DEST 0 // Zielraum
+#define CONN_CMD 1 // Kommando
+#define CONN_USERS 2 // Benutzer: ({spieler, seher})
+#define CONN_EXITTYPE 3 // Ausgangsart (s.o.)
+#define CONN_TIMES 4 // Zeit letzter Benutzung: ({spieler,seher})
+#define CONN_PARA 5 // Parawelt
+#define CONN_FLAGS 6 // Flags fuer die Verbindung (z.B. permanent)
+// Indizes in CONN_USERS. Beide Eintraege sind entweder gehashte UIDs ODER -1,
+// wenn die Verbindung von mehr als NEEDED unterschiedlichen gegangen wurde.
+#define CONN_U_PLAYERS 0 // Spieler-UIDs
+#define CONN_U_SEERS 1 // Seher-UIDS
+// Indizes von CONN_TIMES. Zeitstempel der letzten Benutzung...
+#define CONN_T_PLAYERS CONN_U_PLAYERS // ... durch Spieler
+#define CONN_T_SEERS CONN_U_SEERS // ... durch Seher
+
+// Konstanten fuer CONN_FLAGS
+#define CFLAG_PERMANENT 0x1 // Verbindung nicht expiren
+#define CFLAG_DONTUSE 0x2 // Verbindung nicht im Routing nutzen
+#define CFLAG_DUPLICATE 0x4 // Kommando hat mehrere Verbindungen
+
+// laufende Pfadsuchen
+nosave mapping searchlist = ([]);
+/*
+ ([ <uuid> : SL_VISITED; SL_DESTINATION;... ... ])
+ */
+// Indizes in <data>:
+#define SL_START 0
+#define SL_DESTINATION 1 // Zielraum
+#define SL_VISITED 2 // abgelaufene/gepruefte Raeume
+#define SL_CANDIDATES 3 // moegliche Ziele
+#define SL_WIZLEVEL 4 // Wizlevel
+#define SL_PARA 5 // Parawelt
+#define SL_TODO 6 // noch zu pruefende Raume (SL_CANDIDATES-SL_VISITED)
+#define SL_CALLBACK 7 // Closure fuer Callback nach Routenberechnung
+// SL_CANDIDATES: ([<raum>: ({verbindungsdaten}), ...])
+// Indizes im Array <verbindungsdaten>:
+#define SLT_COSTS 0 // Kosten fuer Verbindung bis hier
+#define SLT_WAY 1 // bisheriger Weg von Start bis hier
+#define SLT_COMMANDS 2 // Kommandos fuer Weg von Start bis hier
+
+// cache fuer berechnete Wege
+nosave mapping pathcache = ([]);
+// Aufbau: ([ "startraum": <dests>, ...])
+// <dests>: (["zielraum": <wege>, ...])
+// <wege>: ([parawelt: (<struct path_s>) ])
+
+struct path_s {
+ int costs; // Kosten des kompletten Pfades
+ string *rooms; // Liste von durchlaufenen Raeumen
+ string *cmds; // Liste von Kommandos, um Raeume zu durchlaufen
+ int ttl; // time-to-live, Ablaufzeit des Pfads im Cache
+ int para; // Parawelt
+ int flags; // Flags zu dem Pfad
+};
+// Flags fuer Pfade im Pfadcache
+#define PFLAG_PERMANENT CFLAG_PERMANENT
+#define PFLAG_DONTUSE CFLAG_DONTUSE
+
+
+// Prototypes
+public void create();
+public void add_paths( mixed connections );
+public varargs void show_pathmap( string path );
+public varargs int show_statistic( int verbose, int silent );
+public int change_pathmap( string pfad, mixed *verbindungen );
+public mapping get_pathmap();
+public void reset();
+public varargs int remove( int silent );
+public varargs int query_prevent_shadow( object ob );
+static int insert_paths( mixed *path );
+static mixed *format_paths( mixed buf );
+static void _search( string fname, string start );
+private string _get_prefix( string path );
+protected void cleanup_data( string *names, int idx );
+protected void expire_path_cache(string *startrooms);
+
+
+public void create()
+{
+ // wir muessen das Savefile schreiben duerfen
+ seteuid(getuid());
+
+ if ( !restore_object(SAVEFILE) ) {
+ pathmap = ([]);
+ }
+}
+
+
+// Wird von Spielern aufgerufen mit den Wegen, die sie gelaufen sind
+public void add_paths( mixed connections )
+{
+ mixed paths;
+
+ // keinen Aufruf per Hand bitte
+ if ( !previous_object() || !this_player()
+ || this_interactive() && getuid(this_interactive()) != "zesstra"
+ || file_size( "/std/shells" +
+ explode( object_name(previous_object()), ":" )[0]
+ + ".c" ) < 1 )
+ return;
+
+#if __BOOT_TIME__ < 1357042092
+ // alte Spielerobjekte liefern noch ein Array von Strings statt eines
+ // Arrays von Arrays.
+ connections = map(connections, function mixed (string path)
+ {
+ // Alle Daten kommen als ein String mit '#' als Trenner an.
+ // Muster: Startraum#Zielraum#Verb#Methode der Bewegung#Parawelt
+ mixed buf = explode( path, "#" );
+ // Falls im Verb auch # vorkam (unwahrscheinlich, aber moeglich):
+ buf[2..<3] = ({ implode( buf[2..<3], "#" ) });
+ return buf;
+ }
+ );
+#endif
+
+ // Daten aufbereiten
+ paths = map( connections, #'format_paths/*'*/ ) - ({0});
+ // Neue Raeume eintragen
+ filter( paths, #'insert_paths/*'*/ );
+}
+
+public mixed get_path_from_cache(string from, string to, int para) {
+ // wenn im Cache, aus dem Cache liefern.
+ mapping targets = pathcache[from];
+ if (targets) {
+ mapping paths = targets[to];
+ if (paths) {
+ if (member(paths, para)) {
+ struct path_s p = paths[para];
+ if (p->ttl > time())
+ return ({ from, to, para, p->costs,
+ copy(p->rooms), copy(p->cmds) });
+ else {
+ m_delete(paths, para);
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+// Suchfunktion, um die kuerzeste Verbindung zwischen zwei Raeumen zu finden
+public int SearchPath( string from, string to, int para,
+ closure callback)
+{
+ // Pro UID darf es nur einen Suchauftrag geben.
+ string uid=getuid(previous_object());
+ if ( member(searchlist,uid) )
+ return -1;
+ // und mehr als 5 gleichzeitig erstmal auch nicht.
+ if (sizeof(searchlist)>4)
+ return -2;
+
+ // Anfrage loggen
+ LOG(sprintf("%s: Suche %s -> %s (%d) von %s (%s, %s, %s)\n",
+ strftime("%y%m%d-%H%M%S"), from, to, para, uid,
+ object_name(previous_object()),
+ object_name(this_player()) || "NO-PL",
+ object_name(this_interactive()) || "NO-TI") );
+
+ // wenn Start- oder Zielraum nicht bekannt sind, kann es auch keine Route
+ // geben.
+ if (!member(pathmap[_get_prefix(to)],to)
+ || !member(pathmap[_get_prefix(from)],from) )
+ return -3;
+
+ mixed path = get_path_from_cache(from, to, para);
+ if (path) {
+ if (callback)
+ apply(callback,path);
+ return 2;
+ }
+
+ // Datenstrukturerklaerung: s.o.
+ mapping targets = m_allocate(1200,3);
+ m_add(targets,from,0,({}),({}) );
+ searchlist+= ([ uid: from; to; ({});
+ targets;
+ (query_wiz_level(this_interactive())?1:0);
+ para; ({}); callback]);
+
+ // Die eigentliche Suche starten
+ _search( uid, from );
+
+ return 1;
+}
+
+// Gespeicherte Informationen zu einem Raum oder einem kompletten Gebiet
+// abfragen. Gebiete lassen sich aber nur als "Organisationseinheiten" abfragen.
+// Dabei werden Gebiete unterhalb von /d/* als /d/region/magiername
+// abgespeichert und der Rest unter den ersten beiden Teilpfaden.
+public varargs void show_pathmap( string path )
+{
+ string pre;
+
+ if ( path )
+ {
+ pre = _get_prefix(path);
+
+ if ( pre == path )
+ // Ganzen Teilbaum ausgeben
+ printf( "Pathmap[%O]: %O\n", path, pathmap[path] );
+ else
+ // Nur Infos ueber einen Raum ausgeben
+ printf( "Pathmap[%O]: %O\n", path,
+ pathmap[pre] ? pathmap[pre][path] : 0 );
+ }
+ else
+ // Ohne Argument wird die gesamte Map ausgegeben.
+ // Klappt aber eher nicht (mehr) wegen Buffer overflow. ;^)
+ printf( "Pathmap: %O\n", pathmap );
+}
+
+
+// Statistic zu den gespeicherten Daten anzeigen.
+// Mit 'verbose' wird jede Organisationseinheit einzeln aufgelistet, ohne
+// wird grob nach Regionen zusammengefasst.
+public varargs int show_statistic( int verbose, int silent )
+{
+ int i, ges;
+ string *ind, name;
+ mapping m;
+
+ if ( verbose ){ // Stur jeden Eintrag auflisten
+ ind = sort_array( m_indices(pathmap), #'</*'*/ );
+ for ( i = sizeof(ind); i--; ){
+ if ( !silent )
+ printf( "%-30s: %4d\n", ind[i], sizeof(pathmap[ind[i]]) );
+ ges += sizeof(pathmap[ind[i]]);
+ }
+ }
+ else { // Regionen zusammenfassen
+ ind = m_indices(pathmap);
+ m = ([]);
+
+ for ( i = sizeof(ind); i--; ){
+ if (strstr(ind[i],"/d/") == 0)
+ // /d... jeweils nach zwei Teilpfaden
+ name = implode( explode( ind[i], "/" )[0..2], "/" );
+ else if ( strstr(ind[i],"/players") == 0)
+ // Alle Playerverzeichnisse zusammenfassen ...
+ name = "/players";
+ else
+ // ... den Rest nach einem Teilpfad
+ name = implode( explode( ind[i], "/" )[0..1], "/" );
+
+ if ( !m[name] )
+ m[name] = sizeof( pathmap[ind[i]] );
+ else
+ m[name] += sizeof( pathmap[ind[i]] );
+ }
+
+ ind = sort_array( m_indices(m), #'</*'*/ );
+ for ( i = sizeof(ind); i--; ){
+ if ( !silent )
+ printf( "%-30s: %4d\n", ind[i], m[ind[i]] );
+ ges += m[ind[i]];
+ }
+ }
+
+ if ( !silent )
+ printf( "\nGesamt: %d Raeume.\n", ges );
+
+ return ges;
+}
+
+
+// Manipulieren der internen Pathmap. Nur zum Debuggen und somit
+// nur fuer Zesstra erlaubt. Sonst verhunzt mir noch wer meine Daten. ;^)
+public int change_pathmap( string pfad, mixed *verbindungen )
+{
+ if ( !this_interactive() || getuid(this_interactive()) != "zesstra"
+ || !previous_object() || getuid(previous_object()) != "zesstra" )
+ return -1;
+
+ if ( mappingp(verbindungen) ){
+ if ( !pathmap[_get_prefix(pfad)] )
+ return 0;
+
+ pathmap[_get_prefix(pfad)] = verbindungen;
+ }
+ else {
+ if ( !pathmap[_get_prefix(pfad)][pfad] )
+ return 0;
+
+ pathmap[_get_prefix(pfad)][pfad] = verbindungen;
+ }
+
+ return 1;
+}
+
+// Die Funktion gibt alle dem pathd bekannten Raeume als Mapping von Arrays
+// zurueck. Schlüssel des Mappings sind dabei Gebiete.
+public mapping get_rooms()
+{
+ string *submaps;
+ mapping roommap;
+ int i;
+
+ roommap=([]);
+
+ submaps=m_indices(pathmap);
+
+ i=sizeof(submaps);
+
+ while (i--)
+ if (sizeof(m_indices(pathmap[submaps[i]])))
+ roommap[submaps[i]]=m_indices(pathmap[submaps[i]]);
+
+ return roommap;
+}
+
+// Daten zu einer Verbindung sammeln und aufbereiten
+static mixed *format_paths( mixed buf )
+{
+ string cmd, uid;
+ object ob;
+ mapping exits;
+ int art;
+
+ // Magier und Testspieler koennen auch in nicht angeschlossene Gebiete
+ // und werden deshalb nicht beachtet.
+ if ( IS_LEARNER(previous_object(1)) ||
+ IS_LEARNER(lower_case( previous_object(1)->Query(P_TESTPLAYER)||"" )) )
+ return 0;
+
+ cmd = trim(buf[2],TRIM_BOTH);
+#if __BOOT_TIME__ < 1357042092
+ // " 0" am Ende des Befehls abschneiden. *grummel*
+ if (cmd[<2..]==" 0")
+ cmd = cmd[..<3];
+#endif
+
+ // Wenn der Zielraum als String angegeben wurde, kann der fuehrende
+ // Slash fehlen!
+ if ( buf[1][0] != '/' )
+ buf[1] = "/" + buf[1];
+
+ // Zum Abfragen der zusaetzlichen Daten brauchen wir das Objekt selber
+ catch(ob = load_object(buf[0]);publish);
+ if (!objectp(ob))
+ return 0;
+
+ // Kleiner Hack - bei Transportern etc. ist 'ob' die nicht initialisierte
+ // Blueprint. Jede Abfrage von P_EXITS wuerde nett auf -Debug scrollen.
+ // Da P_IDS im create() auf jeden Fall auf ein Array gesetzt wird,
+ // missbrauche ich das hier als "Ist initialisiert"-Flag.
+ if ( !ob->QueryProp(P_IDS) )
+ return 0;
+
+ // Art des Ausgangs feststellen.
+ if ( mappingp(exits = ob->QueryProp(P_EXITS)) && exits[cmd] )
+ art = NORMAL; // "normaler" Ausgang
+ else if ( mappingp(exits = ob->QueryProp(P_SPECIAL_EXITS)) && exits[cmd] )
+ art = SPECIAL; // SpecialExit
+ else {
+ // Kommandos, die einen in einen anderen Raum bringen
+ art = to_int(buf[3]); // Move-Methode
+
+ // Es zaehlen aber nur Bewegungen, die halbwegs "normal" aussehen,
+ // d.h. kein M_TPORT, M_NOCHECK und M_GO muss gesetzt sein.
+ if ( art & (M_TPORT | M_NOCHECK) || !(art & M_GO) )
+ return 0;
+ else
+ art = COMMAND;
+ }
+
+ // Die UID der Spieler/Seher wird verschluesselt.
+ // Schliesslich brauchen wir sie nur fuer statistische Zwecke und nicht,
+ // um Bewegungsprofile zu erstellen. crypt() reicht fuer diese Zwecke
+ // wohl erstmal.
+ uid = getuid(previous_object(1));
+ uid = crypt( uid, "w3" );
+
+ // Start, Ziel, Verb, Wizlevel(TP), UID(TP), Art des Ausgangs, Parawelt
+ return ({ buf[0], buf[1], cmd, query_wiz_level(previous_object(1)) ? 1 : 0,
+ uid, art, to_int(buf[4]) });
+}
+
+// Prueft alle Verbindungen im Array <conns>, welche das gleiche Kommando wie
+// <conn> haben, ob sie in unterschiedliche Raeume fuehren. Wenn ja, sind sie
+// fuer das Routing nicht nutzbar. Ausnahme: Verbindungen, die in
+// unterschiedliche Parawelten fuehren.
+// Aendert <conns> direkt.
+private void check_and_mark_duplicate_conns_by_conn(mixed conns, mixed conn) {
+ foreach(mixed c: conns) {
+ if ( c[CONN_CMD] == conn[CONN_CMD]
+ && c[CONN_DEST] != conn[CONN_DEST]
+ && c[CONN_PARA] == conn[CONN_PARA] )
+ {
+ c[CONN_FLAGS] |= CFLAG_DUPLICATE;
+ conn[CONN_FLAGS] |= CFLAG_DUPLICATE;
+ }
+ }
+}
+
+private void check_and_mark_duplicate_conns(mixed conns) {
+ foreach(mixed c: conns)
+ check_and_mark_duplicate_conns_by_conn(conns, c);
+}
+
+
+// Neue Verbindung in der Datenbank eintragen
+static int insert_paths( mixed *path )
+{
+ string pre = _get_prefix(path[0]);
+
+ // Falls noch gar kein Eintrag existiert, neu initialisieren
+ if ( !mappingp(pathmap[pre]) )
+ pathmap[pre] = ([]);
+
+ if ( !pointerp(pathmap[pre][path[0]]) )
+ pathmap[pre][path[0]] = ({});
+
+ // Aufbau von 'path':
+ // ({ Start, Ziel, Verb, Wizlevel(TP), UID(TP), Art des Ausgangs, Para })
+ // Aufbau von 'conn' (s. auch ganz oben)
+ // ({ Ziel, Verb, ({ Spieler-UIDs, Seher-UIDs }), Art des Ausgangs,
+ // ({ Letztes Betreten durch Spieler, durch Seher }), Parawelt })
+
+ // Alle Verbindungen des Raumes durchgehen
+ mixed conns = pathmap[pre][path[0]];
+ // Kommandos der Verbindungen sammeln. Wenn es zwei Verbindungen mit dem
+ // gleichen Kommando gibt, sind beide unbenutzbar.
+ mixed cmds = m_allocate(10);
+ int i;
+ for ( i = sizeof(conns); i--; )
+ {
+ mixed conn = conns[i];
+ m_add(cmds,conn[CONN_CMD]); // Kommandos aller Conns merken
+ // Wenn Zielraum, Verb und Parawelt passen ...
+ if ( conn[CONN_DEST] == path[1] && conn[CONN_CMD] == path[2]
+ && conn[CONN_PARA] == path[6] )
+ {
+ // Wenn schon genug Leute diese Verbindung genutzt haben, einfach
+ // nur die Zeit der letzten Benutzung aktualisieren.
+ int index = path[3] ? CONN_U_SEERS : CONN_U_PLAYERS; // Seher?
+ if ( conn[CONN_USERS][index] == -1 )
+ {
+ conn[CONN_TIMES][index] = time();
+ break;
+ }
+ // Ansonsten die (neue?) UID hinzufuegen und die Zeit aktualisieren.
+ else
+ {
+ conn[CONN_USERS][index] =
+ conn[CONN_USERS][index] - ({ path[4] }) + ({ path[4] });
+ if ( sizeof(conn[CONN_USERS][index]) >= NEEDED )
+ conn[CONN_USERS][index] = -1;
+
+ conn[CONN_TIMES][index] = time();
+ break;
+ }
+ }
+ }
+
+ // Falls keine Verbindung gepasst hat, eine neue erzeugen.
+ if ( i < 0 ) {
+ int index = path[3] ? CONN_U_SEERS : CONN_U_PLAYERS; // Seher?
+ mixed uids = ({ ({}), ({}) });
+ uids[index] = ({ path[4] });
+ mixed times = ({ 0, 0 });
+ times[index] = time();
+
+ // Aufbau siehe oben
+ mixed conn = ({ path[1], path[2], uids, path[5], times, path[6], 0 });
+ conns += ({ conn });
+ // wenn die neue Verbindung nen Kommando benutzt, welches schon fuer
+ // eine andere Verbindung aus diesem Raum benutzt wird, sind beide
+ // nicht nutzbar, da das Ziel nicht eindeutig ist. In diesem Fall
+ // muessen alle diese als unbenutzbar fuer das Routing markiert
+ // werden.
+ if (member(cmds, conn[CONN_CMD])) {
+ check_and_mark_duplicate_conns_by_conn(conns,conn);
+ }
+ }
+
+ pathmap[pre][path[0]] = conns;
+
+ // Ob 0 oder 1 ist eigentlich total egal, da das Ergebnis-Array sowieso
+ // verworfen wird.
+ return 0;
+}
+
+private void add_to_cache(string from, string to, int para, int costs,
+ string *rooms, string *cmds) {
+ struct path_s p = (<path_s> costs : costs, rooms: rooms, cmds : cmds,
+ para: para, flags: 0, ttl: time()+86400);
+ if (!member(pathcache, from)) {
+ pathcache[from] = m_allocate(1);
+ pathcache[from][to] = m_allocate(1);
+ }
+ else if (!member(pathcache[from], to)) {
+ pathcache[from][to] = m_allocate(1);
+ }
+ pathcache[from][to][para] = p;
+}
+
+// Diese Funktion berechnet die Kosten einer Verbindung.
+// Ein Seherstatus schliesst die Moeglichkeiten von Spielern ein (seher
+// kann Spielerpfade problemlos nutzen).
+// Wenn eine Verbindung in eine andere Parawelt fuehrt, als in <para>
+// angefragt, wird sie erheblich teurer.
+protected int _calc_cost(mixed* path, int seer, int para)
+{
+ mixed* interest=path[CONN_USERS];
+
+ // Basiskosten nach Ausgangsart
+ int costs = COST_EXITS[path[CONN_EXITTYPE]];
+
+ // Verbindungen in andere als die gewuenschte Parawelt werden bei der
+ // suche nicht beruecksichtigt, aber Verbindung von Para nach Normal und
+ // umgekehrt sind erlaubt. Para->Normal soll aber etwas teurer sein, damit
+ // es bevorzugt wird, in der angefragten Welt zu bleiben.
+ // (Hintergrund: wenn man in Para ist, laeuft man meistens trotzdem durch
+ // Normalweltraeume, weil es keine passenden Pararaeume gibt).
+ if (para != path[CONN_PARA])
+ costs += COST_WORLDCHANGE;
+
+ // wenn schon genug unterschiedliche Leute die Verbindung gelaufen sind,
+ // kostet es die Basiskosten. Fuer Seher auch die Spieler einschliessen.
+ if (interest[CONN_U_PLAYERS]==-1
+ || (seer && interest[CONN_U_SEERS]==-1))
+ return costs;
+ else
+ {
+ // Sonst wird die Verbindung teurer.
+ // Anzahl Spielernutzer kann nicht -1 sein (s.o.).
+ int usercount = sizeof(interest[CONN_U_PLAYERS]);
+ // wenn <seer>, wird ggf. die Anzahl an Sehernutzern drauf addiert.
+ // Wert fuer Seher kann nicht -1 sein (s.o.)
+ if (seer)
+ usercount += sizeof(interest[CONN_U_SEERS]);
+ switch (usercount)
+ {
+ case 0:
+ costs=1000000;
+ break;
+ case 1..2:
+ costs*=COST_ONE;
+ case 3..4:
+ costs*=COST_FEW;
+ default:
+ costs*=COST_MANY;
+ }
+ return costs;
+ }
+ DBG(sprintf("Huch, keine Ahnung, Kosten unbestimmbar: %O",path));
+ return 1000;
+}
+
+// Neuimplementation von Humni, benutzt einfach simpel einen
+// Dijkstra-Algorithmus. "Simpel" schreibe ich, bevor ich die
+// Funktion schreibe, fluchen werde ich gleich.
+static void _search(string fname, string from)
+{
+ // <from> ist jetzt anzuguckende/zu bearbeitende Raum.
+
+ // Die aktuelle Liste an abgesuchten Raeumen, Raeume, in denen
+ // alle Wege bereits eingetragen sind.
+ string* rooms_searched=searchlist[fname,SL_VISITED];
+
+ // Zielort
+ string to = searchlist[fname,SL_DESTINATION];
+
+ // moegliche Ziele
+ mapping targets=searchlist[fname,SL_CANDIDATES];
+
+ // wizlevel
+ int seer = searchlist[fname,SL_WIZLEVEL];
+
+ // para
+ int para=searchlist[fname,SL_PARA];
+
+ // Noch zu bearbeitende Raeume
+ string* to_do=searchlist[fname,SL_TODO];
+
+ DBG("_search() start.");
+
+ while ((get_eval_cost()>900000) && from && (from != to))
+ {
+ // Fuege den aktuellen Raum (from) als Weg hinzu
+ DBG(sprintf("Fuege hinzu: %s",from));
+ rooms_searched+=({from});
+ // Aus den Raeumen drumrum werden die Ausgaenge gesucht
+ // und zur Wegliste hinzugefuegt.
+
+ // Pfade von <from> weg aus der Liste holen
+ mixed* paths=pathmap[_get_prefix(from)][from];
+
+ // Liste der Verbindungen von <from> durchgehen
+ foreach (mixed conn:paths)
+ {
+ int newcost;
+ // wenn der Raum schonmal gesehen wurde, ist hier nix mehr noetig.
+ if (member(rooms_searched,conn[CONN_DEST])>-1)
+ {
+ DBG(sprintf("Raum %s hab ich schon!",conn[CONN_DEST]));
+ }
+ else
+ {
+ // Verbindung je nach Flag nicht nutzen. Ausserdem:
+ // wenn die Verbindung in eine Parawelt fuehrt, aber kein
+ // Seherstatus gegeben ist, existiert die Verbindung nicht.
+ // Weiterhin kommt eine Verbindung nicht in Frage, wenn sie in
+ // eine andere Parawelt fuehrt, weil der Weltuebergang nur an
+ // bestimmten Orten moeglich ist. Ausnahme davon sind Wechsel
+ // zwischen Normalwelt und Parawelt.
+ if (
+ (conn[CONN_FLAGS] & CFLAG_DONTUSE)
+ || (conn[CONN_FLAGS] & CFLAG_DUPLICATE)
+ || (!seer && conn[CONN_PARA])
+ || (conn[CONN_PARA] && conn[CONN_PARA] != para)
+ )
+ continue;
+ newcost = targets[from,SLT_COSTS]; // kosten bis hierher
+ // + Kosten in naechsten Raum.
+ newcost += _calc_cost(conn,seer,para);
+ // es koennte sein, dass es Verbindungen mit gleichem
+ // Start/Ziel gibt, aber unterschiedlichen Kosten. In diesem
+ // Fall wuerde das Ziel der aktuellen Verbindung mehrfach
+ // hinzugefuegt und das letzte gewinnt. Daher sollte diese
+ // Verbindung nur hinzugefuegt werden, wenn noch keine
+ // Verbindung in den Zielraum existiert, die billiger ist.
+ if (member(targets,conn[CONN_DEST])
+ && newcost >= targets[conn[CONN_DEST],SLT_COSTS])
+ continue;
+ // Weg bis hierher plus naechsten Raum
+ string* new_way = targets[from,SLT_WAY];
+ new_way += ({conn[CONN_DEST]});
+ // Kommandos bis hierher plus naechstes Kommando
+ string* new_commands = targets[from,SLT_COMMANDS];
+ new_commands += ({conn[CONN_CMD]});
+ // naechsten Raum in Todo fuer die Pruefung unten vermerken
+ to_do += ({conn[CONN_DEST]});
+ // Und Kosten/Weg/Kommandos bis zum naechsten Raum unter
+ // targets/Kandidaten ablegen.
+ targets += ([conn[CONN_DEST]:newcost;new_way;new_commands]);
+ }
+ }
+ // Nachdem die Raeume alle zu den Kandidaten an moeglichen Wegen
+ // hinzugefuegt wurden, wird nun der neueste kuerzeste Weg gesucht.
+ // Natuerlich nur aus denen, die schon da sind.
+ if (sizeof(to_do)>0)
+ {
+ // Kosten des ersten todo als Startwert nehmen
+ string minraum;
+ int mincosts=__INT_MAX__;
+ // und ueber alle todos laufen um das Minimum zu finden.
+ foreach(string raum: to_do)
+ {
+ if (mincosts>targets[raum,SLT_COSTS])
+ {
+ minraum = raum;
+ mincosts=targets[raum,SLT_COSTS];
+ }
+ }
+ DBG(sprintf("Neuer kuerzester Raum: %s",minraum));
+
+ // der billigst zu erreichende Raum wird das neue <from> fuer den
+ // naechsten Durchlauf
+ from=minraum;
+ // und natuerlich aus dem to_do werfen.
+ to_do-=({minraum});
+ DBG(sprintf("Anzahl untersuchter Raeume: %d, Todo: %d, "
+ "Kandidaten: %d",
+ sizeof(rooms_searched),sizeof(to_do),sizeof(targets)));
+ }
+ else
+ {
+ from=0;
+ }
+ } // while ende
+
+ if (!from)
+ {
+ // Hmpf...
+ TELL_USER("Kein Weg gefunden!");
+ if (searchlist[fname, SL_CALLBACK])
+ funcall(searchlist[fname,SL_CALLBACK],
+ searchlist[fname,SL_START], to, para,
+ 0,0,0);
+ m_delete( searchlist, fname ); // suchauftrag entsorgen
+ return;
+ }
+ else if (from==to)
+ {
+ // JUCHU!
+ TELL_USER(sprintf("Weg gefunden! %O,%O,%O",
+ targets[to,SLT_COSTS],targets[to,SLT_WAY],
+ targets[to,SLT_COMMANDS]));
+ add_to_cache(searchlist[fname,SL_START], to, para,
+ targets[to,SLT_COSTS],targets[to,SLT_WAY],
+ targets[to,SLT_COMMANDS]);
+ if (searchlist[fname, SL_CALLBACK])
+ funcall(searchlist[fname,SL_CALLBACK],
+ searchlist[fname,SL_START], to, para,
+ targets[to,SLT_COSTS],targets[to,SLT_WAY],
+ targets[to,SLT_COMMANDS]);
+ m_delete( searchlist, fname ); // suchauftrag entsorgen
+ return;
+ }
+
+ // Wenn das alles nicht war, waren es die Eval-Ticks. Mist. Spaeter
+ // weitersuchen...
+ searchlist[fname,SL_VISITED]=rooms_searched;
+ searchlist[fname,SL_CANDIDATES]=targets;
+ searchlist[fname,SL_TODO]=to_do;
+ call_out("_search",2,fname,from);
+}
+
+
+// Die Daten ueber Raeume werden quasi gehasht abgespeichert, damit wir nicht
+// so schnell an die Begrenzung bei Arrays und Mappings stossen.
+// Dabei ist der 'Hash' der Anfang des Pfades.
+private string _get_prefix( string path )
+{
+ string *tmp;
+
+ tmp = explode( path, "/" );
+
+ // Bei Raeumen unterhalb von /d/* wird /d/region/magiername benutzt
+ if ( strstr(path, "/d/") == 0)
+ return implode( tmp[0..3], "/" );
+ // Ansonsten die ersten beiden Teilpfade, falls soviele vorhanden sind
+ else if ( sizeof(tmp) < 4 )
+ return implode( tmp[0..1], "/" );
+ else
+ return implode( tmp[0..2], "/" );
+}
+
+// Reset und Datenhaltung
+// reset wird alle 3h gerufen, um das Savefile immer mal wieder zu speichern.
+// Datenaufraeumen aber nur einmal pro Tag.
+public void reset()
+{
+ if ( time_to_clean_up <= time() )
+ {
+ // Einmal pro Tag Reset zum Aufraeumen der Datenbank
+ rm(DUMPFILE".raeume");
+ rm(DUMPFILE".conns");
+ time_to_clean_up = time() + 86400;
+ expire_path_cache(m_indices(pathcache));
+ call_out(#'cleanup_data, 30,
+ sort_array(m_indices(pathmap), #'</*'*/), 0 );
+
+ }
+ save_object(SAVEFILE);
+ set_next_reset(10800);
+}
+
+protected void expire_path_cache(string *startrooms) {
+ foreach(string start : startrooms) {
+ mapping targets = pathcache[start];
+ foreach (string target, mapping paths: targets) {
+ foreach(int para, struct path_s p: paths) {
+ if (p->ttl < time()
+ || !(p->flags & PFLAG_PERMANENT) )
+ m_delete(paths,para);
+ }
+ if (!sizeof(paths))
+ m_delete(targets, target);
+ }
+ startrooms-=({start});
+ if (!sizeof(targets))
+ m_delete(pathcache, start);
+ // recht grosser Wert, weil immer komplette Startraeume in einem
+ // erledigt werden.
+ if (get_eval_cost() < 1000000)
+ break;
+ }
+ if (sizeof(startrooms))
+ call_out(#'expire_path_cache, 4, startrooms);
+}
+
+// Datenbank aufraeumen
+protected void cleanup_data( string *names, int idx )
+{
+ int size, i, j, k;
+ string *rooms;
+ mixed *paths;
+
+ size = sizeof(names);
+
+ // Ein bisserl mitloggen, damit wir Schwachstellen im System auch finden.
+ if ( !idx ){
+ LOG( sprintf("=== %s: Starte cleanup_data(): %O Gebiete, %O Raeume "
+ + "...\n", strftime("%y%m%d-%H%M%S"), size,
+ show_statistic(1, 1) ) );
+ }
+ else {
+ LOG( sprintf("%s: Setze cleanup_data() fort.\n",
+ strftime("%y%m%d-%H%M%S") ) );
+ }
+
+ // Brav gesplittet, damit es kein Lag gibt.
+ // Die Grenze ist recht hoch angesetzt, da immer gleich komplette
+ // Teilbaeume aufgeraeumt werden.
+ while ( get_eval_cost() > 1100000 && idx < size ){
+ rooms = sort_array( m_indices(pathmap[names[idx]]), #'</*'*/ );
+
+ for ( i = sizeof(rooms); i--; ){
+ paths = pathmap[names[idx]][rooms[i]];
+ int conn_count = sizeof(paths);
+ mapping cmds=m_allocate(sizeof(paths));
+
+ for ( j = conn_count; j--; ) {
+ mixed conn = paths[j];
+ for ( k = 0; k < 2; k++ ) { // Spieler/Sehereintraege
+ // Diese Verbindung hat noch keiner genutzt bisher
+ if ( !conn[CONN_TIMES][k] )
+ continue;
+ // Wenn Verbindung permanent, ueberspringen
+ if (conn[CONN_FLAGS] & CFLAG_PERMANENT)
+ continue;
+
+ if ( conn[CONN_USERS][k] == -1 // 'bekanntes' Gebiet
+ && time() - conn[CONN_TIMES][k] > TIMEOUT_OLD ){
+ // LOG( sprintf("*** Loesche alte "
+ // + (k ? "Seher" : "Spieler")
+ // + "-Verbindung %O.\n", paths[j]) );
+ conn[CONN_USERS][k] = ({});
+ conn[CONN_TIMES][k] = 0;
+ }
+ else if ( conn[CONN_USERS][k] != -1 // 'neues' Gebiet
+ && time() - conn[CONN_TIMES][k] > TIMEOUT_NEW ){
+ conn[CONN_USERS][k] = ({});
+ conn[CONN_TIMES][k] = 0;
+ }
+ }
+
+ // Wenn eine Verbindung weder von Spielern noch von Sehern
+ // benutzt wurde in letzter Zeit und sie keine permanented
+ // ist, Verbindung ganz loeschen.
+ if (!(conn[CONN_FLAGS] & CFLAG_PERMANENT)
+ && !conn[CONN_TIMES][CONN_T_PLAYERS]
+ && !conn[CONN_TIMES][CONN_T_SEERS] ) {
+ paths[j..j] = ({}); // conn rauswerfen
+ conn_count--;
+ }
+ else {
+ // Connections nach Kommandos speichern.
+ if (member(cmds, conn[CONN_CMD]))
+ cmds[conn[CONN_CMD]] += ({conn});
+ else
+ cmds[conn[CONN_CMD]] = ({conn});
+ // und das duplicate flag loeschen, das wird spaeter neu
+ // gesetzt, wenn noetig.
+ // (There is no chance that a concurrent search will check
+ // these connections until they are marked again.)
+ conn[CONN_FLAGS] &= ~CFLAG_DUPLICATE;
+ // Connections dumpen
+ if (!(conn[CONN_FLAGS] & CFLAG_DUPLICATE))
+ {
+ mixed u=({conn[CONN_USERS][CONN_U_PLAYERS],
+ conn[CONN_USERS][CONN_U_SEERS] });
+ write_file(DUMPFILE".conns",sprintf(
+ "%s|%s|%s|%d|%d|%d|%d\n",
+ rooms[i],conn[CONN_DEST],conn[CONN_CMD],
+ conn[CONN_EXITTYPE],conn[CONN_PARA],
+ (u[0]==-1 ? 0 : NEEDED-sizeof(u[0])),
+ (u[0]==-1 ? 0 : NEEDED-sizeof(u[0]))
+ ));
+ }
+ }
+ }
+
+ // Ein Raum ohne Verbindungen frisst nur Platz in der Datenbank
+ if ( !conn_count ){
+ // LOG( sprintf("*** Loesche kompletten Raum %O\n", rooms[i]) );
+ m_delete( pathmap[names[idx]], rooms[i] );
+ }
+ else {
+ // wenn Kommandos nicht eindeutig sind, dann werden die
+ // entsprechenden Verbindungen gekennzeichnet. Alle in Frage
+ // kommenden Verbindungen sind jeweils schon zusammensortiert.
+ foreach(string cmd, mixed conns : cmds) {
+ if (sizeof(conns)>1)
+ check_and_mark_duplicate_conns(conns);
+ }
+ // ggf. geaendertes Verbindungsarray wieder ins Mapping
+ // schreiben.
+ pathmap[names[idx]][rooms[i]] = paths;
+ // Raum/Node ins Dump schreiben
+ write_file(DUMPFILE".raeume",sprintf(
+ "%s\n",rooms[i]));
+ }
+ }
+ idx++;
+ }
+ if ( idx >= size ) {
+ LOG( sprintf("=== %s: Beende cleanup_data()! Uebrig: %O Raeume.\n",
+ strftime("%y%m%d-%H%M%S"), show_statistic(1, 1) ) );
+ }
+ else {
+ call_out( #'cleanup_data, 2, names, idx );
+ LOG( sprintf("%s: WARTE 20s bei Evalcost %O\n",
+ strftime("%y%m%d-%H%M%S"), get_eval_cost()) );
+ }
+}
+
+// Beim Entladen speichern
+public varargs int remove( int silent )
+{
+ // Vor dem Entfernen noch schnell die Daten sichern
+ save_object(SAVEFILE);
+ destruct(this_object());
+
+ return 1;
+}
+
+
+// Zum Debuggen. Nur für Tiamak. Bzw. nun fuer Zook, Vanion, Rumata
+#define ALLOWED ({"zook","vanion","rumata","zesstra","humni"})
+#define MATCH(x,y) (sizeof(regexp(x,y)))
+public mapping get_pathmap()
+{
+ if ( !this_interactive() || !MATCH(ALLOWED,getuid(this_interactive()))
+ || !previous_object() || !MATCH(ALLOWED,getuid(previous_object())))
+ return ([]);
+
+ return pathmap;
+}
+public mapping get_pathcache()
+{
+ if ( !this_interactive() || !MATCH(ALLOWED,getuid(this_interactive()))
+ || !previous_object() || !MATCH(ALLOWED,getuid(previous_object())))
+ return ([]);
+
+ return pathcache;
+}
+
+#undef ALLOWED
+#undef MATCH
diff --git a/p/daemon/routingd.c b/p/daemon/routingd.c
new file mode 100644
index 0000000..b293382
--- /dev/null
+++ b/p/daemon/routingd.c
@@ -0,0 +1,202 @@
+#pragma strong_types,save_types
+#pragma no_clone,no_shadow
+
+#include <wizlevels.h>
+
+#define DB_NAME "/p/daemon/save/routingd"
+#define MAX_LEN 50
+#define TP this_player()
+
+mapping domains;
+mapping exits;
+mapping targets;
+static mapping directions;
+static mapping reverse_directions;
+
+void create() {
+ seteuid(getuid());
+ if (!restore_object(DB_NAME)) {
+ domains=(["/d/":1]);
+ exits=([]);
+ targets=([]);
+ }
+
+ // Umsetzung zur Speicherersparnis:
+ directions=(["norden":1,
+ "nordosten":2,
+ "osten":3,
+ "suedosten":4,
+ "oben":5,
+ "sueden":-1,
+ "suedwesten":-2,
+ "westen":-3,
+ "nordwesten":-4,
+ "unten":-5
+ ]);
+ reverse_directions=([1:"norden",
+ 2:"nordosten",
+ 3:"osten",
+ 4:"suedosten",
+ 5:"oben",
+ -1:"sueden",
+ -2:"suedwesten",
+ -3:"westen",
+ -4:"nordwesten",
+ -5:"unten"
+ ]);
+}
+
+// Fuer Debug-Zecke:
+mixed query_exits(string room) {return exits[room];}
+mixed query_targets(string typ) {return targets[typ];}
+
+int AddDomain(string dom, int val) {
+ // Fuegt neue Domain hinzu, in der Routing moeglich sein soll.
+ // +1: Einschliessen
+ // -1: Ausschliessen
+ if (!IS_ARCH(TP)
+ || val<-1
+ || val>1)
+ return 0;
+ if (!val)
+ domains=m_copy_delete(domains,dom);
+ else
+ domains[dom]=val;
+ save_object(DB_NAME);
+ return 1;
+}
+
+int RegisterTarget(string type, string fname) {
+ // Registriert Raum als zu einer Gruppe von Zielen zugehoerig
+ // (z.B. RegisterTarget("pub","/d/ebene/room/PortVain/pub2"))
+ mapping map_ldfied;
+
+ if (!type || !fname)
+ return 0;
+ type=lower_case(type);
+ if (type[0..1]!="##")
+ type="##"+type;
+ if (!mappingp(map_ldfied=targets[type]))
+ map_ldfied=([]);
+ map_ldfied[fname]=1;
+ targets[type]=map_ldfied;
+ return 1;
+}
+
+int RegisterExit(string start, mixed richtungen, string ziel) {
+ // Registriert Ausgang in der angegebenen Richtung von Start nach Ziel,
+ // wenn Routing dort moeglich sein soll.
+ string *dirs;
+ int i,sz,ok,pos;
+ mixed ex,x,richtung;
+
+ if (stringp(richtungen))
+ richtungen=({richtungen});
+ if (!start || !pointerp(richtungen) || !ziel)
+ return 0;
+ dirs=old_explode(start,"/");sz=sizeof(dirs)-1;x="/";ok=0;
+ for (i=0;i<sz;i++) {
+ x=x+dirs[i]+"/";
+ if (domains[x]==-1) // Subdomain ausgeschlossen?
+ return 0;
+ if (domains[x]==1) // Domain erlaubt?
+ ok=1;
+ }
+ if (!ok)
+ return 0;
+
+ if (!pointerp(ex=exits[start])) {
+ if (file_size(start+".c")<=0) // Beim ersten Ausgang auf VC testen
+ return 0; // Keine Virtual Compiler Raeume routen
+ ex=({});
+ }
+ for (i=sizeof(richtungen)-1;i>=0;i--) {
+ richtung=richtungen[i];
+ if (!(x=directions[richtung]))
+ x=richtung;
+
+ pos=member(ex,x);
+ if (pos>=0 && pos+1<sizeof(ex)) // Richtung schon eingetragen?
+ ex[pos+1]=ziel; // Wurde umdefiniert
+ else
+ ex+=({x,ziel}); // ({ richtung, ziel })
+ }
+ exits[start]=ex;
+ return 1;
+}
+
+// Low-Level FindRoute :-)
+mapping do_find_route(string start, mapping is_target) {
+ mapping erreicht;
+ int i,j,k;
+ mixed *newx, *actx, ex;
+ string room, dir, x;
+
+ erreicht=([start:"##START"]);
+ newx=({start});
+ for (k=MAX_LEN;k>=0;k--) {
+ actx=newx; // In diesem Schritt die neu erreichten Raeume pruefen
+ newx=({}); // Noch keine Raeume neu erreicht
+ for (i=sizeof(actx)-1;i>=0;i--) {
+ room=actx[i];
+ // printf("%O\n",room);
+ if (!pointerp(ex=exits[room]))
+ continue;
+ // printf("-> %O\n",ex);
+ for (j=sizeof(ex)-1;j>=1;j-=2) {
+ x=ex[j]; // Zielraum
+ // printf("-> %O\n",x);
+ if (!erreicht[x]) { // Wurde bisher noch nicht erreicht
+ erreicht[x]=({room,ex[j-1]});
+ if (is_target[x]) { // Ein Ziel wurde erreicht
+ erreicht["##ZIEL"]=erreicht[x]; // Kann ja auch Gruppe sein
+ return erreicht;
+ }
+ newx+=({x});
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+varargs mixed FindRoute(string start, string ziel, int etyp) {
+ mapping res,ziele;
+ string x;
+ mixed ex,r,result;
+ int k;
+
+ if (!start || !ziel)
+ return 0;
+ if (!mappingp(ziele=targets[ziel]))
+ ziele=([ziel:1]);
+ if (!mappingp(res=do_find_route(start, ziele)))
+ return 0; // nicht gefunden
+ if (etyp)
+ result=({});
+ else
+ result=([]);
+ x="##ZIEL";
+ for (k=MAX_LEN+2;k>=0;k--) {
+ ex=res[x];
+ if (ex=="##START") // Fertig.
+ return result;
+ if (!(r=reverse_directions[ex[1]])) // richtige Richtungen eintragen.
+ r=ex[1];
+ x=ex[0];
+ if (etyp)
+ result=({r})+result;
+ else
+ result[x]=r;
+ }
+ return 0;
+}
+
+void save() {
+ save_object(DB_NAME);
+}
+
+void reset() {
+ set_next_reset(43200); // Alle 12 Stunden speichern reicht
+ save();
+}
diff --git a/p/daemon/ruestungen.c b/p/daemon/ruestungen.c
new file mode 100644
index 0000000..52029fb
--- /dev/null
+++ b/p/daemon/ruestungen.c
@@ -0,0 +1,56 @@
+#pragma strong_types,save_types
+#pragma no_clone,no_shadow
+
+#include <wizlevels.h>
+
+#define SAVEFILE "/p/daemon/save/ruestungen"
+#define DUMPFILE "/log/RUESTUNGEN"
+
+mapping armours;
+
+void create()
+{
+ seteuid(getuid(this_object()));
+
+ if (!restore_object(SAVEFILE))
+ {
+ armours = ([]);
+ }
+}
+
+void save_me()
+{
+ save_object(SAVEFILE);
+}
+
+void RegisterArmour()
+{ object ob;
+ string id;
+
+ if (!objectp(ob=previous_object()) ||
+ member(inherit_list(ob),"/std/armour.c")==-1)
+ return;
+ id = old_explode(object_name(ob),"#")[0];
+ if (member(armours,id))
+ return;
+ armours += ([ id : 1]);
+ save_me();
+}
+
+int Dump()
+{ string *ind;
+ int i;
+
+ if (!this_interactive() || !IS_LORD(this_interactive()))
+ return -1;
+ if (file_size(DUMPFILE)>=0)
+ rm(DUMPFILE);
+
+ ind = sort_array(m_indices(armours),#'>);
+ if ((i=sizeof(ind))<1)
+ return 0;
+ write_file(DUMPFILE,sprintf("Dumped: %s\n",dtime(time())));
+ for (--i;i>=0;i--)
+ write_file(DUMPFILE,ind[i]+"\n");
+ return 1;
+}
diff --git a/p/daemon/sastatd.c b/p/daemon/sastatd.c
new file mode 100644
index 0000000..c02873e
--- /dev/null
+++ b/p/daemon/sastatd.c
@@ -0,0 +1,135 @@
+// MorgenGrauen MUDlib
+/** \file /p/daemon/sastatd.c
+* Statistikdaemon fuer Skill-Attribute
+* Der Daemon loggt SA-Modifier und schreibt sie von Zeit zu Zeit auf die
+* Platte.
+* \author Zesstra
+* \date 06.08.2008
+* \version $Id$
+*/
+/* Changelog:
+*/
+#pragma strong_types,save_types
+#pragma no_clone,no_inherit,no_shadow
+#pragma pedantic,range_check
+
+#include <defines.h>
+
+/** anfaengliche Groesse des Log-Puffers
+ */
+#define DEFAULT_BUFFER_LEN 200
+/** ab dieser Anzahl an Eintraegen wird im naechsten Backend-Zyklus vom Driver
+ * per Callout der Puffer weggeschrieben. */
+#define BUFFERTHRESHOLD 150
+
+/** Logfile fuer die Stats
+ */
+#define STATLOG "SASTATS"
+/** max. Logfilegroesse (3 MB)
+ */
+#define MAXLOGSIZE 3145728
+
+struct log_entry_s {
+ int timestamp;
+ string liv;
+ string caster;
+ string atrname;
+ mixed value;
+ int duration;
+};
+
+struct log_buffer_s {
+ mixed buf; // Array von log_entry
+ int bufferlen; // Laenge des Buffers, kann dyn. vergroessert werden.
+ int sp; // oberster Eintrag im Buffer (letztendlich nen Stack).
+};
+
+/** speichert Logeintraege mit indiv. Modifiereintraegen.
+ * Array von log_entry_s mit Info ueber Pufferlaenge und zuletzt
+ * geschriebenem Arrayeintrag.
+*/
+struct log_buffer_s logbuf = (<log_buffer_s>
+ buf : allocate(DEFAULT_BUFFER_LEN),
+ bufferlen : DEFAULT_BUFFER_LEN,
+ // -1, weil der sp beim Loggen zuerst erhoeht wird und sonst Elemente 0
+ // leer bleibt.
+ sp : -1 );
+
+/** \def DEBUG
+ Outputs debug message to Maintainer, if Mainteiner is logged in.
+*/
+#ifndef DEBUG
+#define DEBUG(x) if (find_player("zesstra"))\
+ tell_object(find_player("zesstra"),\
+ "EDBG: "+x+"\n")
+#endif
+
+private string FormatLogEntry(struct log_entry_s entry) {
+ return sprintf("[%s] %s, %s, %s, %s, %d\n",
+ strftime("%d%m%y-%H%M%S",entry->timestamp),
+ entry->liv, entry->caster, entry->atrname,
+ entry->value, entry->duration);
+}
+
+private void WriteBuffer() {
+ // relevanten Teil des Puffers extrahieren.
+ mixed buf = logbuf->buf[0..logbuf->sp];
+ // dann alle Eintraege in Strings mappen
+ buf = map(buf, #'FormatLogEntry);
+ // wegschreiben
+ log_file(STATLOG, sprintf("%@s",buf), MAXLOGSIZE);
+ // sp auf den Pufferanfang setzen
+ logbuf->sp = -1;
+}
+
+public void LogModifier(object caster, string atrname,
+ mixed value, int duration) {
+ // nur Spieler sind interessant
+ if (!query_once_interactive(previous_object()))
+ return;
+
+ // sp um eins erhoehen und schauen, ob die Puffergroesse ueberschritten
+ // wurde. Wenn ja, Puffer vergroessern.
+ logbuf->sp++;
+ if (logbuf->sp >= logbuf->bufferlen) {
+ logbuf->buf = logbuf->buf + allocate(50);
+ logbuf->bufferlen += 50;
+ }
+ // eintragen. Interessanterweise ist es tatsaechlich kein Problem, wenn
+ // Structmember und lokale Variablen hier den gleichen Namen habem. ;-)
+ logbuf->buf[logbuf->sp] = (<log_entry_s>
+ timestamp : time(),
+ liv : getuid(previous_object()), // nur Spieler
+ caster : object_name(caster),
+ atrname : atrname,
+ value : to_string(value), // wegen (fluechtiger) Closures
+ duration : duration );
+ // wegschreiben veranlassen
+ if (logbuf->sp > BUFFERTHRESHOLD
+ && find_call_out(#'WriteBuffer) == -1)
+ call_out(#'WriteBuffer, 0);
+}
+
+protected void create() {
+ seteuid(getuid(ME));
+}
+
+varargs public int remove(int silent) {
+ if (logbuf->sp > -1)
+ WriteBuffer();
+ destruct(ME);
+ return 1;
+}
+
+void reset() {
+ // ggf. Puffer wegschreiben.
+ if (logbuf->sp > BUFFERTHRESHOLD)
+ WriteBuffer();
+ // ggf. sehr grosse Puffer verkleinern
+ if (logbuf->bufferlen > DEFAULT_BUFFER_LEN * 2) {
+ logbuf->buf = allocate(DEFAULT_BUFFER_LEN);
+ logbuf->bufferlen = DEFAULT_BUFFER_LEN;
+ logbuf->sp = -1;
+ }
+}
+
diff --git a/p/daemon/svn2news.c b/p/daemon/svn2news.c
new file mode 100644
index 0000000..8cece79
--- /dev/null
+++ b/p/daemon/svn2news.c
@@ -0,0 +1,55 @@
+// MorgenGrauen MUDlib
+//
+// svn2news.c -- Setzt die Subversion-Eintraege in magier.mudlib
+//
+// $Id$
+
+// Erstellt auf Basis von rcs2news von Zook.
+
+#pragma strict_types,save_types
+#pragma no_clone,no_shadow
+
+#include <daemon.h>
+
+#define NEWSFILE "/p/daemon/save/svn2news.txt"
+#define NEWSFILEOLD "/p/daemon/save/svn2news.old"
+
+#define DEBUG(str) if (find_player("zook")) \
+ tell_object(find_player("zook"), sprintf("svn2news: DEBUG: %O\n",str))
+
+static void checkrcs()
+{
+ string str;
+ mixed art;
+ // Letzte Eintrag sollte ein wenig her sein, damit es keine
+ // Ueberschneidungen gibt.
+ if (file_time(NEWSFILE)+1800 > time()) return;
+ if (!(str=read_file(NEWSFILE,0,10000)) || (str=="")) return;
+ art=({"magier.mudlib","SVN2NEWS",0,0,
+ "SVN-Eintraege vom "+dtime(time()),str});
+ if (("/secure/news"->WriteNote(art,1)>=1) ||
+ (("/secure/news"->RemoveNote("magier.mudlib",0)>=1) &&
+ ("/secure/news"->WriteNote(art,1)>=1)))
+ catch(rename(NEWSFILE,NEWSFILEOLD);publish);
+ else log_file("SVN2NEWS",dtime(time())+
+ " : Kann RCS-Eintraege nicht nach magier.mudlib posten!\n");
+}
+
+void reset()
+{
+ // call_out() noetig, da sonst kein weiteres reset() gerufen wird
+ // reset() wird nur gerufen bei einem call einer anderen Funktion
+ // nach einem Reset
+ call_out("checkrcs",1);
+}
+
+void create()
+{
+ seteuid(getuid());
+ reset();
+}
+
+void clean_up(int refc)
+{
+ return 0;
+}
diff --git a/p/daemon/teammaster.c b/p/daemon/teammaster.c
new file mode 100644
index 0000000..76e8ca0
--- /dev/null
+++ b/p/daemon/teammaster.c
@@ -0,0 +1,84 @@
+#pragma strong_types,save_types
+#pragma no_shadow,no_clone
+
+#include <living/team.h>
+#include <wizlevels.h>
+#define ME this_object()
+#define PO previous_object()
+
+static mapping team_names;
+static mapping team_names_reverse;
+
+void create() {
+
+ team_names=([]); // Objekt -> Name
+ team_names_reverse=([]); // Name -> Objekt
+}
+
+static mixed DoRegisterTeam(object ob, string name) {
+ mixed old;
+
+ old=team_names[ob];
+ m_delete(team_names_reverse,old); // Namen Freigeben
+ if (!name) { // UnRegister
+ m_delete(team_names,ob);
+ return name;
+ }
+ team_names[ob]=name;
+ team_names_reverse[name]=ob;
+ return name;
+}
+
+varargs string RegisterTeam(string name) {
+ int i,min;
+
+ if (member(inherit_list(PO),TEAM_OBJECT+".c")<0)
+ return 0;
+ if (stringp(name)){
+ if (!team_names_reverse[name]) // Name noch nicht vergeben
+ return DoRegisterTeam(PO,name);
+ return 0;
+ }
+ min=sizeof(m_indices(team_names_reverse))+2;
+ for (i=1;i<min;i++)
+ if (!team_names_reverse[sprintf("%d",i)]) {
+ min=i;
+ break;
+ }
+ name=sprintf("%d",i);
+ return DoRegisterTeam(PO,name);
+}
+
+void UnregisterTeam() {
+ if (member(inherit_list(PO),TEAM_OBJECT+".c")>=0)
+ DoRegisterTeam(PO,0);
+}
+
+object *ListTeamObjects() {
+ return(m_indices(team_names)-({0}));
+}
+
+string *ListTeamNames() {
+ return(m_indices(team_names_reverse)-({0}));
+}
+
+string *SortedListTeamNames() {
+ return(sort_array(ListTeamNames(),#'<));
+}
+
+void ShowTeamInfos() {
+ object ob;
+ string *ind;
+ int i;
+
+ write(" --- Teamliste: ---\n");
+ ind=SortedListTeamNames();
+ for (i=sizeof(ind)-1;i>=0;i--)
+ if (objectp(ob=team_names_reverse[ind[i]]))
+ ob->ShowTeamInfo();
+ write(" --- (Ende Teamliste) ---\n");
+}
+
+varargs int remove(int silent) {
+ return 0;
+}
diff --git a/p/daemon/tmp_prop_master.c b/p/daemon/tmp_prop_master.c
new file mode 100644
index 0000000..0ce1de7
--- /dev/null
+++ b/p/daemon/tmp_prop_master.c
@@ -0,0 +1,338 @@
+#pragma strong_types,save_types
+#pragma no_clone,no_shadow
+
+#include <thing/properties.h>
+
+#define SAVEFILE "/p/daemon/save/tmp_prop_master"
+#define PROPMASTER "/p/daemon/tmp_prop_master"
+#define INDEX(a,b) (object_name(a)+"##"+b)
+
+
+#ifdef DEBUG
+# undef DEBUG
+#endif
+
+#ifdef DEBUG
+# define DBG(x) if ( find_player("rikus") ) \
+ tell_object( find_player("rikus"), x );
+#else
+# define DBG(x)
+#endif
+
+
+mixed *reset_times;
+mapping in_progress;
+
+
+varargs public int remove( int silent );
+public void reset();
+public void print_debug_info();
+static void call_next_update();
+static void do_update();
+public int IsTmpProp( object ob, string prop );
+public void RemoveTmpProp( string prop );
+varargs public void SetTmpProp( object ob, string prop, mixed newval,
+ int tm, int sp );
+
+
+public void create()
+{
+
+ seteuid(getuid(this_object()));
+
+ if ( !restore_object(SAVEFILE) ){
+ in_progress = ([]);
+ reset_times = ({});
+ }
+
+ call_next_update();
+}
+
+
+varargs public int remove( int silent )
+{
+ save_object(SAVEFILE);
+ destruct(this_object());
+
+ return 1;
+}
+
+
+public void reset()
+{
+ save_object(SAVEFILE);
+}
+
+
+public void print_debug_info()
+{
+ printf( "in_progress: %O\nreset_times: %O\n", in_progress, reset_times );
+}
+
+
+static void call_next_update()
+{
+ int dt;
+
+ while ( remove_call_out("do_update") != -1 )
+ ;
+
+ if ( sizeof(reset_times) ){
+ dt = reset_times[0][0] - time();
+
+ if ( dt <= 0 )
+ dt = 5;
+
+ call_out( "do_update", dt );
+ }
+}
+
+
+static void do_update()
+{
+ int i, max;
+ string index;
+ mixed *val;
+
+ DBG( "do_update() aufgerufen!\n" );
+
+ while ( remove_call_out("do_update") != -1 )
+ ;
+
+ call_out( "do_update", 30 ); // Zur Sicherheit...
+
+ if ( !(max = sizeof(reset_times)) )
+ return;
+
+ for ( i = 0; i < max && reset_times[i][0] <= time(); i++ ){
+ index = reset_times[i][1];
+
+ if ( !pointerp(val = in_progress[index]) || !objectp(val[0]) )
+ {
+ m_delete( in_progress ,index );
+ continue;
+ }
+
+ DBG( sprintf("Entferne Eintrag %O!\n", index) );
+
+ if ( val[4] ){
+ if ( val[0]->Query(val[1], F_VALUE) == val[3] ){
+ DBG( "Alte Property per SetProp() restauriert!\n" );
+ val[0]->SetProp( val[1], val[2] );
+ }
+ }
+ else {
+ if ( val[0]->Query( val[1], F_QUERY_METHOD ) == val[2] ){
+ val[0]->Set( val[1], 0, F_QUERY_METHOD );
+ DBG( "F_QUERY_METHOD entfernt!\n" );
+ }
+
+ if ( val[0]->Query( val[1], F_SET_METHOD ) == val[3] ){
+ val[0]->Set( val[1], 0, F_SET_METHOD );
+ DBG( "F_SET_METHOD entfernt!\n" );
+ }
+ }
+
+ m_delete( in_progress ,index );
+ }
+
+ reset_times = reset_times[i..];
+ call_next_update();
+}
+
+
+// Zur Abfrage, ob eine temporaere Property schon gesetzt ist
+public int IsTmpProp( object ob, string prop )
+{
+ mixed oldval;
+
+ if ( !objectp(ob) || !prop )
+ return 0;
+
+ if ( (oldval = in_progress[INDEX(ob, prop)])
+ && ob == oldval[0] && prop == oldval[1] )
+ return 1;
+
+ return 0;
+}
+
+
+// Entfernen einer temporaeren Property.
+// Ist nicht zum Entfernen per Hand gedacht, sondern kann nur vom Objekt selber
+// aufgerufen werden. Geschieht, wenn bei gesetzter temporaerer F_SET_METHOD
+// ein fremdes SetProp() auf die Property kommt.
+public void RemoveTmpProp( string prop )
+{
+ string index;
+ mixed *val;
+ int i;
+
+ DBG( "RemoveTmpProp!\n");
+ if ( !previous_object() || !stringp(prop) || !sizeof(prop) )
+ return;
+
+ index = INDEX(previous_object(), prop);
+ DBG( sprintf("RemoveTmpProp(%O)!\n", index) );
+
+ if ( !(val = in_progress[index]) ){
+ DBG( "Kein Eintrag vorhanden!\n" );
+ return;
+ }
+
+ if ( objectp(val[0]) ){
+ DBG( "Restauriere alten Zustand ...\n" );
+
+ if ( val[4] ){
+ if ( val[0]->Query(val[1], F_VALUE) == val[3] ){
+ DBG( "Alte Property per SetProp() restauriert!\n" );
+ val[0]->SetProp( val[1], val[2] );
+ }
+ }
+ else {
+ if ( val[0]->Query( val[1], F_QUERY_METHOD ) == val[2] ){
+ val[0]->Set( val[1], 0, F_QUERY_METHOD );
+ DBG( "F_QUERY_METHOD entfernt!\n" );
+ }
+
+ if ( val[0]->Query( val[1], F_SET_METHOD ) == val[3] ){
+ val[0]->Set( val[1], 0, F_SET_METHOD );
+ DBG( "F_SET_METHOD entfernt!\n" );
+ }
+ }
+ }
+
+ for ( i = sizeof(reset_times); i--; )
+ if ( reset_times[i][1] == index ){
+ reset_times[i..i] = ({});
+ break;
+ }
+
+ DBG( "Eintrag entfernt!\n" );
+ m_delete( in_progress, index );
+}
+
+
+// Property 'prop' im Objekt 'ob' fuer 'tm' Sekunden auf Wert 'newval' setzen.
+// Wenn der Schalter 'sp' gesetzt ist, wird dafuer SetProp() verwendet,
+// anstatt eine F_QUERY_METHOD zu benutzen.
+//
+// ACHTUNG:
+//
+// Das Setzen von 'sp' ist hoechst fehleranfaellig und sollte nur in wenigen
+// Spezialfaellen geschehen, in denen eine SET-Methode aufgerufen werden muss
+// (wie bei P_LIGHT z.B.). Ansonsten bitte den Default-Aufruf ohne 'sp'
+// verwenden!
+//
+// Es besteht sonst die Gefahr, dass die Property nicht korrekt zurueckgesetzt
+// wird (wenn dieser Master oder 'ob' zerstoert wird - oder beides wie bei einem
+// Crash) oder dass durch Wechselwirkungen mit anderen Query- oder Set-Methoden
+// ein falscher Wert "restauriert" wird.
+
+varargs public void SetTmpProp( object ob, string prop, mixed newval,
+ int tm, int sp )
+{
+ string index;
+ mixed oldval, tmp;
+ int max, pos, l, r;
+ closure a, b;
+
+ if ( !objectp(ob) || !stringp(prop) || !sizeof(prop) || tm <= 0 )
+ return;
+
+ index = INDEX(ob, prop);
+ DBG( sprintf("SetTmpProp( %O, %O, %O, %O, %O )!\n", ob, prop, newval,
+ tm, sp) );
+
+ // wenn bereits ein temporaerer Prozess laeuft, diesen nun beenden!
+ if ( (oldval = in_progress[index])
+ && ob == oldval[0] && prop == oldval[1] ){
+ DBG( "Es laeuft bereits ein Vorgang fuer diese Property!\n" );
+
+ // Wurde die alte temporaere Property per SetProp() gesetzt, muss der
+ // urspruengliche Wert erst restauriert werden.
+ if ( oldval[4] ){
+ if ( oldval[0]->Query(oldval[1], F_VALUE) == oldval[3] ){
+ DBG( "Alte Property per SetProp() restauriert!\n" );
+ oldval[0]->SetProp( oldval[1], oldval[2] );
+ }
+ }
+ // Ansonsten einfach die F_QUERY- und F_SET_METHOD loeschen.
+ else {
+ if ( oldval[0]->Query( oldval[1], F_QUERY_METHOD ) == oldval[2] ){
+ oldval[0]->Set( oldval[1], 0, F_QUERY_METHOD );
+ DBG( "F_QUERY_METHOD entfernt!\n" );
+ }
+
+ if ( oldval[0]->Query( oldval[1], F_SET_METHOD ) == oldval[3] ){
+ oldval[0]->Set( oldval[1], 0, F_SET_METHOD );
+ DBG( "F_SET_METHOD entfernt!\n" );
+ }
+ }
+
+ for ( l = sizeof(reset_times); l--; )
+ if ( reset_times[l][1] == index ){
+ reset_times[l..l] = ({});
+ DBG( "Timer-Eintrag entfernt!\n" );
+ break;
+ }
+ }
+
+ if ( tm < 1000000 )
+ tm += time();
+
+ if ( tm < time() )
+ return;
+
+ if ( !sp ){
+ // Default: F_QUERY_METHOD setzen
+ oldval = ob->Set( prop, newval, F_QUERY_METHOD );
+
+ // F_SET_METHOD, damit die temporaere Property keinen zwischenzeitlich
+ // gesetzten anderen Wert wieder "restauriert".
+
+ a=symbol_function("RemoveTmpProp",this_object());
+ b=symbol_function("SetProp",ob);
+ tmp=ob->Set(prop,lambda(({'set_value,'prop_name}),({#',,({a,'prop_name}),({#'return,({b,'prop_name,'set_value})})})),F_SET_METHOD);
+ }
+ else{
+ // Fuer Spezialfaelle (P_LIGHT z.B.) wird die Property direkt mit
+ // SetProp() gesetzt und spaeter mit dem alten Wert restauriert.
+ // Achtung - extrem fehleranfaellig und wirklich nur fuer Spezialfaelle!
+
+ // Alten Wert mit Query() sichern anstatt mit QueryProp(), um nicht
+ // irgendwelchen F_QUERY_METHODs auf den Leim zu gehen.
+ oldval = ob->Query( prop, F_VALUE );
+
+ // Rueckgabewert des SetProp() speichern, um spaeter erkennen zu
+ // koennen, ob die Property zwischenzeitlich veraendert wurde.
+ tmp = ob->SetProp( prop, newval );
+ }
+
+ in_progress[index] = ({ ob, prop, oldval, tmp, sp });
+
+ if ( !(max = sizeof(reset_times)) )
+ pos = 0;
+ else{
+ l = 0;
+ r = max - 1;
+
+ while ( l < r ){
+ pos = (l + r) / 2;
+
+ if ( tm < reset_times[pos][0] )
+ r = --pos;
+ else
+ l = ++pos;
+ }
+
+ while ( pos >= 0 && pos < max && tm < reset_times[pos][0] )
+ pos--;
+ }
+
+ reset_times = reset_times[0..pos] + ({ ({tm, index}) })
+ + reset_times[pos+1..];
+
+ DBG( sprintf("Property an Stelle %O eingefuegt!\n", pos+1) );
+
+ call_next_update();
+}
diff --git a/p/daemon/traveld.c b/p/daemon/traveld.c
new file mode 100644
index 0000000..747fd38
--- /dev/null
+++ b/p/daemon/traveld.c
@@ -0,0 +1,285 @@
+/** Doku:
+public int AddRoute(string transporter, string* l_stops)
+ Beschreibung:
+ Setzt die aktuelle Route eines Transporters.
+ Die Route hier ist allerdings nur die Liste von Haltepunkten - nicht der
+ Rest, der in den Routen in den Transporter abgelegt ist.
+ Eine neue Route ersetzt immer die bisherige Route
+ Wird von /std/transport gerufen, wenn P_NO_TRAVELING nicht gesetzt ist.
+ Rueckgabewerte:
+ - 1: Transporter und Haltepunkt Eingetragen.
+ - 0: Transporter oder Haltepunkt kein string|string*.
+ - -1: Es wurde versucht einen Transporter in /p/ oder /players/
+ einzutragen
+ und der Eintragende ist kein EM.
+
+public int RemoveTransporter(string|object transporter)
+public int RemoveStop(string|object stop)
+ Beschreibung:
+ Entfernt einen kompletten Transporter oder einen Haltepunkt aus dem
+ Daemon.
+ Rueckgabewerte:
+ - 1: Erfolg.
+ - 0: Transporter|Haltepunkt existiert nicht.
+ - -1: Keine Berechtigung.
+
+public varargs int|object* HasTransporter(object stop, string transporter_id)
+ Beschreibung:
+ Gibt eine Liste der Transporter aus, den den Haltepunkt anlaufen, oder
+ prueft auf einen bestimmten Transporter.
+ Rueckgabewerte:
+ - 0: Haltepunkt ist kein Objekt, dem Haltepunkt sind keine Transporter
+ zugeordnet,
+ die Transporter sind nicht geladen
+ oder, bei angabe von <transporter>: <transporter> faehrt den Hafen
+ nicht an.
+ - object*: Liste mit einem oder mehreren Transportern.
+ Bemerkung:
+ Auch bei der Angabe von <transporter_id> koennen mehrere Objekte
+ zurueckgegeben werden, es wird mittels id() gefiltert.
+
+public mixed RouteExists(object transporter, string dest)
+ Beschreibung:
+ Prueft auf korrekte Parameter und leitet die Anfrage an
+ /std/transport weiter.
+ Rueckgabewerte:
+ - 0: <transporter> ist kein Objekt oder <dest> ist kein String.
+ - Das Ergebnis von transporter->HasRoute(dest);
+
+public mapping QueryTransporters()
+public mapping QueryAllStops()
+public mapping QueryTransporterByStop(string stop)
+public mapping QueryStopsByTransporter(string transporter)
+ Beschreibung:
+ Gibt das jewilige gefilterte oder ungefilterte Mapping aus.
+ Rueckgabewerte:
+ Mapping mit dem Ergebnis.
+*/
+
+#pragma strong_types,save_types,rtt_checks
+#pragma pedantic,range_check,warn_deprecated
+#pragma warn_empty_casts,warn_missing_return,warn_function_inconsistent
+#pragma no_clone,no_shadow
+
+#include <transport.h>
+#include <wizlevels.h>
+#include <daemon.h>
+
+#define TRANSPORTER 0
+#define STOP 1
+
+#define MEMORY "/secure/memory"
+
+// Key: string Schiff.
+// Value0: string* Haefen, die von <Schiff> angefahren werden.
+mapping transporters = ([]);
+// Key: string Hafen
+// Value0: string* Schiffe, die <Hafen> anfahren.
+mapping stops = ([]);
+
+protected void create()
+{
+ // Vom MEMORY die Daten abholen.
+ // Wenn keine da, war vermutlich Reboot, dann muessen wir warten, bis die
+ // Transporter sich wieder melden. Aber die Mappings muessen ins Memory
+ // geschrieben werden.
+ mapping tmp = MEMORY->Load("stops");
+ if (mappingp(tmp))
+ {
+ stops = tmp;
+ }
+ else
+ {
+ if (MEMORY->Save("stops",stops) != 1)
+ raise_error("Could not save memory to /secure/memory.");
+ }
+ tmp = MEMORY->Load("transporters");
+ if (mappingp(tmp))
+ {
+ transporters = tmp;
+ }
+ else
+ {
+ if (MEMORY->Save("transporters",transporters) != 1)
+ raise_error("Could not save memory to /secure/memory.");
+ }
+}
+
+varargs int remove(int s)
+{
+ destruct(this_object());
+ return 1;
+}
+
+// Loeschfunktion
+private int _remove_data(string|object key, int what)
+{
+ mapping first,second;
+ // Erstmal die Arbeitsmappings befuellen.
+ if(what==TRANSPORTER)
+ {
+ first=transporters;
+ second=stops;
+ }
+ else
+ {
+ first=stops;
+ second=transporters;
+ }
+
+ // Key auf einen String normalisieren.
+ if(objectp(key))
+ {
+ key=object_name(key);
+ }
+
+ if(!member(first,key))
+ {
+ return 0;
+ }
+
+ // Erstmal aus dem jeweils anderen Mapping austragen.
+ foreach(string s : first[key])
+ {
+ // Nicht existent oder nicht zugeordnet -> weiter.
+ if(!member(second,s) || member(second[s],key)==-1)
+ {
+ continue;
+ }
+ second[s]-=({key});
+ if (!sizeof(second[s]))
+ m_delete(second,s);
+ }
+ // Jetzt noch aus dem eigenen Mapping austragen.
+ m_delete(first,key);
+ return 1;
+}
+
+// Ein komplettes Schiff entfernen.
+public int RemoveTransporter(object|string transporter)
+{
+ if (extern_call() && !IS_ARCH(getuid(previous_object()))) return -1;
+ return _remove_data(transporter,TRANSPORTER);
+}
+
+// Entfernt einen kompletten Hafen aus dem Daemon
+public int RemoveStop(object|string stop)
+{
+ if (extern_call() && !IS_ARCH(getuid(previous_object()))) return -1;
+ return _remove_data(stop,STOP);
+}
+
+// Setzt die aktuelle Route eines Transporters.
+// Die Route hier ist allerdings nur die Liste von Haltepunkten - nicht der
+// Rest, der in den Routen in den Transporter abgelegt ist.
+// Wird von /std/transport gerufen, wenn P_NO_TRAVELING nicht gesetzt ist.
+public int AddRoute(string transporter, string* l_stops)
+{
+ if (!stringp(transporter) || !pointerp(l_stops) || !sizeof(l_stops))
+ return 0;
+
+ if ((transporter[0..2] == "/p/" || transporter[0..8] == "/players/")
+ && !IS_ARCH(getuid(previous_object())))
+ {
+ return -1;
+ }
+
+ // Damit sind alle Abfragen auf find_object(transporter|stop) ueberfluessig,
+ // im Zweifelsfall knallt es naemlich hier. ;-)
+ transporter = object_name(load_object(transporter));
+
+ // Wenn die route bereits existiert, austragen. Dies ist noetig, weil die
+ // Haltepunkte ja auch bereinigt werden muessen.
+ if (member(transporters, transporter))
+ RemoveTransporter(transporter);
+
+ // Transporter eintragen, die Route wird dabei immer ueberschrieben, wenn
+ // der Transporter schon bekannt ist. Ziel: hier ist immer die aktuell
+ // konfigurierte Route drin.
+ m_add(transporters, transporter, l_stops);
+
+ // Nach obigen Schema, nur diesmal Haltepunkte und zugehoerige Schiffe
+ // eintragen.
+ foreach(string stop : l_stops)
+ {
+ if (!member(stops,stop))
+ {
+ m_add(stops, stop, ({transporter}));
+ }
+ else if (member(stops[stop],transporter) == -1)
+ {
+ stops[stop] += ({ transporter });
+ }
+ }
+ return 1;
+
+}
+
+// Abfrage ob ein Schiff einen Hafen anfaehrt.
+public varargs int|object* HasTransporter(object stop,
+ string transporter_id)
+{
+ if (!objectp(stop)) return 0;
+
+ <string|object>* trans=stops[object_name(stop)];
+ if (!pointerp(trans)) return 0;
+
+ // Eigentlich sollten sich Transporter ordentlich abmelden... Aber zur
+ // Sicherheit die nicht gefundenen Objekte ausfiltern.
+ trans = map(trans, #'find_object) - ({0});
+
+ // Wenn ne bestimmte ID gesucht wird, nur diese Transporter liefern.
+ if (stringp(transporter_id))
+ trans = filter_objects(trans, "id", transporter_id);
+
+ return sizeof(trans) ? trans : 0;
+}
+
+public mixed RouteExists(object transporter, string dest)
+{
+ if (!objectp(transporter) || !stringp(dest))
+ {
+ return 0;
+ }
+ return transporter->HasRoute(dest);
+}
+
+public mapping QueryTransporters()
+{
+ return deep_copy(transporters);
+}
+
+public mapping QueryAllStops()
+{
+ return deep_copy(stops);
+}
+
+// Liefert alle Transporter mit ihren Haltepunkten zurueck, die diesen Halt ansteuern.
+public mapping QueryTransportByStop(string stop)
+{
+ mapping res=m_allocate(4);
+ foreach(string s, string* arr : transporters)
+ {
+ if(member(arr,stop)!=-1)
+ {
+ m_add(res,s,arr);
+ }
+ }
+ return res;
+}
+
+// Liefert alle Haltepunkte mit ihren Transportern zurueck, die dieser
+// transporter ansteuert.
+public mapping QueryStopsByTransporter(string transporter)
+{
+ mapping res=m_allocate(4);
+ foreach(string s, string* arr : stops)
+ {
+ if(member(arr,transporter)!=-1)
+ {
+ m_add(res,s,arr);
+ }
+ }
+ return res;
+}
+
diff --git a/p/daemon/uptime_master.c b/p/daemon/uptime_master.c
new file mode 100644
index 0000000..84a7124
--- /dev/null
+++ b/p/daemon/uptime_master.c
@@ -0,0 +1,110 @@
+#pragma strong_types, save_types
+#pragma no_clone, no_shadow
+
+#include "/p/daemon/ch.h"
+#include "/p/daemon/uptime_master.h"
+
+/// Prototypen ///
+
+void create();
+void reset();
+
+void SaveMaxUptime();
+mapping QuerMaxUptime();
+
+/// Globale Variablen ///
+static int next_save_uptime;
+static int max_uptime_reached;
+static int last_record_boot;
+
+/// Funktionen ///
+
+void create()
+{
+ seteuid(getuid(this_object()));
+ reset();
+}
+
+void reset()
+{
+ call_out("SaveMaxUptime",1);
+ set_next_reset(300);
+}
+
+// Ist eine Rekord-Uptime erreicht, wird diese hier gespeichert.
+void SaveMaxUptime()
+{
+ int max_shut, max_boot;
+
+ // Lohnt es, schon wieder zu pruefen?
+ if (time()>next_save_uptime)
+ {
+ // Initialisieren
+ if (next_save_uptime==0)
+ {
+ DEBUG("Masterinit");
+
+ // Zeit berechnen, ab der ein Rekord aufgestellt ist.
+ // Vorher braucht nixhts mehr geprueft werden
+ sscanf(read_file(MAX_UPTIME)||"0 0","%d %d",last_record_boot, max_shut);
+ next_save_uptime=last_reboot_time()+(max_shut-last_record_boot)+1;
+
+ } else {
+ DEBUG("Rekord neu speichern.");
+
+ // Neuen Rekord in Datei schreiben.
+ catch(rm(MAX_UPTIME);publish);
+ write_file(MAX_UPTIME, last_reboot_time()+" "+time());
+
+ // Jetzt checken, ob die Uptime grade geknackt wurde ...
+ // Wenn ja, dann eine Meldung an den Master
+ if(last_reboot_time()!=last_record_boot)
+ {
+ DEBUG("Rekordmeldung. MB:"+last_record_boot+" LU:"+last_reboot_time());
+
+ // Nur einmal pro Rekord Uptime melden ...
+ last_record_boot=last_reboot_time();
+
+ // Hurraaah :)
+ CHMASTER->join(RECORD_CHANNEL,this_object());
+ CHMASTER->send(RECORD_CHANNEL,this_object(),
+ "[<MasteR>:Lars] Wow, eine Rekord-Uptime.\n"
+ "[<MasteR>:Merlin] Das erlebt man auch nicht alle Tage ...\n"
+ "[<MasteR>:Der GameDriver] Dann wird es wohl Zeit fuer einen Reboot, oder?\n"
+ "[<MasteR>:Merlin] Faules Stueck, arbeite weiter!\n"
+ "[<MasteR>:Der GameDriver] Och menno!",
+ MSG_EMPTY);
+ CHMASTER->leave(RECORD_CHANNEL,this_object());
+ }
+ }
+ }
+}
+
+// Gibt Daten zur bisher maximalen Uptime zurueck: (299 Ticks)
+// 1.) Wann wurde gebootet
+// 2.) Wann endete die Uptime
+// 3.) Wann wird dieser Rekord gebrochen?
+// 4.) Dauer der Uptime als lesbarer String.
+mapping QueryMaxUptime()
+{
+ int t, tmp, max_boot, max_shut;
+ string s;
+
+ sscanf(read_file(MAX_UPTIME)||"0 0","%d %d",max_boot, max_shut);
+
+ t=max_shut-max_boot;
+
+ s="";
+ if (t>=86400)
+ s+=sprintf("%d Tag%s, ",tmp=t/86400,(tmp==1?"":"e"));
+ if (t>=3600)
+ s+=sprintf("%d Stunde%s, ",tmp=(t=t%86400)/3600,(tmp==1?"":"n"));
+ if (t>60)
+ s+=sprintf("%d Minute%s und ",tmp=(t=t%3600)/60,(tmp==1?"":"n"));
+ s+=sprintf("%d Sekunde%s",t=t%60,(t==1?"":"n"));
+
+ return (["beginn":max_boot,
+ "ende":max_shut,
+ "naechster rekord":last_reboot_time()+(max_shut-max_boot),
+ "rekord als string":s]);
+}
diff --git a/p/daemon/uptime_master.h b/p/daemon/uptime_master.h
new file mode 100644
index 0000000..862e44a
--- /dev/null
+++ b/p/daemon/uptime_master.h
@@ -0,0 +1,14 @@
+#ifndef __UPTIME_MASTER__
+#define __UPTIME_MASTER__
+
+#define UPTIME_MASTER "/p/daemon/uptime_master"
+#define MAX_UPTIME "/p/daemon/save/maxuptime"
+#define RECORD_CHANNEL "<MasteR>"
+
+#define DEBUG(x)
+/*
+#define DEBUG(x) if(find_player("vanion")) tell_object(find_player("vanion"),\
+ "UPTIME: "+x+"\n");
+*/
+
+#endif /* __UPTIME_MASTER__ */
diff --git a/p/daemon/zentralbank.c b/p/daemon/zentralbank.c
new file mode 100644
index 0000000..53074d0
--- /dev/null
+++ b/p/daemon/zentralbank.c
@@ -0,0 +1,236 @@
+// MorgenGrauen MUDlib
+//
+// zentralbank.c -- Zentrale Geld-Verwaltung der Haendler im MG
+// $Id: zentralbank.c 9223 2015-05-27 21:46:58Z Zesstra $
+//
+/*
+ * $Log: zentralbank.c,v $
+ * Revision 1.5 2003/12/30 11:17:34 Vanion
+ * query_prevent_shadow fehlte. Danke fuer den Hinweis an Ogo.
+ *
+ * Revision 1.4 2003/12/30 11:12:02 Jof
+ * Ich hab Dir das File mal geklaut.
+ * Vanion
+ *
+ * Revision 1.3 1995/07/10 07:31:04 Jof
+ * typo fixed
+ *
+ * Revision 1.2 1995/07/10 07:27:59 Jof
+ * Use bank.h
+ * _query_current_money
+ *
+ * Revision 1.1 1995/07/05 16:29:26 Jof
+ * Initial revision
+ *
+ */
+#pragma no_shadow
+#pragma no_clone
+#pragma no_inherit
+#pragma strong_types,save_types
+
+#include <wizlevels.h>
+#include <properties.h>
+#include <money.h>
+
+#define NEED_PROTOTYPES
+#include <bank.h>
+
+#define TIME strftime("%H:%M",time())
+#define LS 1000000
+
+int allmoney,bank_default_percent,store_default_percent,shop_default_percent;
+int falling;
+// Geldmengen: in NPC, in Boersen, rumliegen, auf Seherkarten, Gesamtsumme,
+// Zeitpunkt der Berechnung.
+int *summen = ({0,0,0,0,0,0});
+
+nosave int last_save;
+nosave int counter;
+
+void save_me()
+{
+ save_object("/p/daemon/save/zentralbank");
+ last_save=time();
+}
+
+protected void create()
+{
+ seteuid(getuid());
+ allmoney=500000;
+ bank_default_percent=BANK_DEFAULT_PERCENT;
+ store_default_percent=STORE_PERCENT_LEFT;
+ shop_default_percent=SHOP_PERCENT_LEFT;
+ restore_object("/p/daemon/save/zentralbank");
+}
+
+private void adjust_percents()
+{
+ if (allmoney<50000 && falling>=0) falling=-1;
+ if (allmoney>1000000 && falling<=0) falling=1;
+ if (allmoney<200000 && falling>0 ) falling=0;
+ if (allmoney>80000 && falling<0 ) falling=0;
+ if (falling<0 && !((++counter)%5))
+ {
+ bank_default_percent=bank_default_percent+random(3)+1;
+ store_default_percent=store_default_percent+random(3)+1;
+ shop_default_percent=shop_default_percent+random(3)+1;
+ log_file("ZENTRALBANK",
+ sprintf("%s: PERCENTS SET TO BANK %d STORE %d SHOP %d\n",
+ TIME,bank_default_percent,store_default_percent,
+ shop_default_percent),LS);
+ }
+ if (falling>0)
+ {
+ bank_default_percent/=2;
+ store_default_percent/=2;
+ shop_default_percent/=2;
+ log_file("ZENTRALBANK",
+ sprintf("%s: PERCENTS SET TO BANK %d STORE %d SHOP %d\n",
+ TIME,bank_default_percent,store_default_percent,
+ shop_default_percent),LS);
+ }
+ if (bank_default_percent<1) bank_default_percent=1;
+ if (bank_default_percent>90) bank_default_percent=90;
+ if (store_default_percent<1) store_default_percent=1;
+ if (store_default_percent>90) store_default_percent=90;
+ if (shop_default_percent<1) shop_default_percent=1;
+ if (shop_default_percent>90) shop_default_percent=90;
+ save_me();
+}
+
+void reset()
+{
+ adjust_percents();
+}
+
+private void real_log()
+{
+ log_file("ZBANKSTATUS",sprintf("%s: %d\n",dtime(time()),allmoney),LS);
+}
+
+private void log_state()
+{
+ // nur alle 15min nen Status-Logeintrag. Reicht voellig.
+ if (find_call_out(#'real_log) == -1)
+ call_out(#'real_log,900);
+}
+
+public varargs void PayIn(int amount, int percent)
+{
+ if (amount<=0) return;
+ percent|=bank_default_percent;
+ allmoney+=amount*percent/100;
+ log_file("ZENTRALBANK",
+ sprintf("%s: Einzahlung: %d (%d brutto) von %O\n",
+ TIME,amount*percent/100,amount,previous_object()),LS);
+ log_state();
+}
+
+public int WithDraw(int amount)
+{
+ int got;
+
+ if (amount<=0) return 0;
+ if (allmoney<0) allmoney=0;
+ if (!allmoney)
+ got=0;
+ else
+ {
+ if (amount*3<allmoney)
+ got=amount;
+ else
+ {
+ got=allmoney/3;
+ if (!got) got=1;
+ }
+ allmoney-=got;
+ }
+ log_file("ZENTRALBANK",sprintf("%s: Abhebung: %6d/%6d von %O\n",
+ TIME,got, amount,previous_object()),LS);
+ log_state();
+ return got;
+}
+
+int _query_current_money()
+{
+ return allmoney;
+}
+
+int _query_bank_default_percent()
+{
+ return bank_default_percent;
+}
+
+int _query_shop_percent_left()
+{
+ return shop_default_percent;
+}
+
+int _query_store_percent_left()
+{
+ return store_default_percent;
+}
+
+// Geldmenge im Spiel
+public int *geldmenge() {
+
+ if (summen[5] > time()-3600 || !IS_ELDER(this_player()))
+ return summen;
+
+ summen = allocate(6);
+ object *geld = clones(GELD,2);
+ foreach(object ob: geld) {
+ if (!environment(ob) || IS_LEARNER(environment(ob)))
+ continue;
+ if (environment(ob)->QueryProp(P_NPC))
+ // in NPC
+ summen[0]+=ob->QueryProp(P_AMOUNT);
+ else if (load_name(environment(ob)) == BOERSE)
+ // in boersen
+ summen[1]+=ob->QueryProp(P_AMOUNT);
+ else
+ // sonst rumliegend
+ summen[2]+=ob->QueryProp(P_AMOUNT);
+ }
+ // Seherkarten
+ geld = clones(SEHERKARTE,2);
+ mapping cards=m_allocate(sizeof(geld),1);
+ foreach(object ob: geld) {
+ cards[ob->query_owner()] = ob->QueryProp(P_AMOUNT);
+ }
+ foreach(string owner, int amount: cards)
+ summen[3]+=amount;
+ summen[4]=summen[0]+summen[1]+summen[2]+summen[3];
+ summen[5]=time();
+ return summen;
+}
+
+public void PrintGeldmenge() {
+ int *sum = geldmenge();
+ printf("NPC: %d\n"
+ "Boersen: %d\n"
+ "rumliegend: %d\n"
+ "Seherkarten: %d\n"
+ "Gesamt: %d\n"
+ "Zeit: %s\n",
+ sum[0],sum[1],sum[2],sum[3],sum[4],strftime(sum[5]));
+}
+
+void set_percents(int store,int shop,int bank)
+{
+ store_default_percent=store;
+ shop_default_percent=shop;
+ bank_default_percent=bank;
+ log_file("ZENTRALBANK",
+ sprintf("%s: PERCENTS SET TO BANK %d STORE %d SHOP %d\n",
+ TIME,bank_default_percent,store_default_percent,
+ shop_default_percent),LS);
+}
+
+int remove()
+{
+ save_me();
+ destruct(this_object());
+ return 1;
+}
+