Added Padreics moving NPC
diff --git a/p/service/padreic/mnpc/.readme b/p/service/padreic/mnpc/.readme
new file mode 100644
index 0000000..ba197a4
--- /dev/null
+++ b/p/service/padreic/mnpc/.readme
@@ -0,0 +1,23 @@
+/*
+ * Der ultimative NPC : mnpc.c (C) by Sir Lunch a lot
+ * $Id: mnpc.c,v 1.1 1993/10/12 16:23:07 djc Exp djc $
+ */
+
+Dieser urspruengliche mnpc wurde von mir weiterentwickelt und stark
+optimiert. Er ist gedacht um relativ einfach, effiziente Laufnpcs zu
+programmieren und erspart einem dadurch das Rad erneut erfinden zu muessen.
+Der Urspruengliche mnpc.c von Sir liegt fuer interessierte weiterhin in
++d/unterwelt/std/mnpc.sir.
+
+ Padreic
+
+mnpc.h - die Includedatei mit den zum mnpc gehoerenden Propertys
+mnpc.doc - eine kleine Dokumentation zum mnpc
+mnpc.c - dieses File wird anstelle von std/npc inheritet um einen Laufnpc
+ zu erzeugen.
+moving.c - die eigentlich Routinen wurden ausgelagert, damit sie auch
+ mit anderen stdnpcs zusammen (z.B. Gildennpcs) benutzt werden
+ koennen.
+walk_master.c - dieser Walkmaster muss in den seltensten Faellen direkt
+ verwendet werden, da ihn z.B. der mnpc.c automatisch von selbst
+ benutzt. Sinn des masters: call_out-Ketten einsparen.
diff --git a/p/service/padreic/mnpc/mnpc.c b/p/service/padreic/mnpc/mnpc.c
new file mode 100644
index 0000000..0b22303
--- /dev/null
+++ b/p/service/padreic/mnpc/mnpc.c
@@ -0,0 +1,49 @@
+/* Changelog:
+ * 11.09.2007, Zesstra
+ strong_types und save_types aktiviert.
+*/
+#pragma strong_types
+#pragma save_types
+
+#include "/p/service/padreic/mnpc/mnpc.h"
+
+inherit "/std/npc";
+inherit MNPC_MOVING;
+
+void create()
+{
+ npc::create();
+ moving::mnpc_create();
+}
+
+void reset()
+{
+ npc::reset();
+ moving::mnpc_reset();
+}
+
+void init()
+{
+ npc::init();
+ moving::mnpc_init();
+}
+
+int InsertEnemy(object enemy)
+{
+ int i = npc::InsertEnemy(enemy);
+ if (!i) return 0;
+ moving::mnpc_InsertEnemy(enemy);
+ return i;
+}
+
+varargs int move(object|string dest, int meth, string dir, string out,
+ string in)
+{
+ int i=npc::move(dest, meth, dir, out, in);
+ if (i!=1) return i;
+ moving::mnpc_move();
+ return 1;
+}
+
+int PreventFollow(object dest)
+{ return moving::mnpc_PreventFollow(dest); }
diff --git a/p/service/padreic/mnpc/mnpc.doc b/p/service/padreic/mnpc/mnpc.doc
new file mode 100644
index 0000000..86a9fa4
--- /dev/null
+++ b/p/service/padreic/mnpc/mnpc.doc
@@ -0,0 +1,103 @@
+/*
+ * Der ultimative NPC : mnpc.c (C) by Sir Lunch a lot
+ * $Id: mnpc.c,v 1.1 1993/10/12 16:23:07 djc Exp djc $
+ */
+
+Dieser urspruengliche mnpc wurde von mir weiterentwickelt und stark
+optimiert. Er ist gedacht um relativ einfach, effiziente Laufnpcs zu
+programmieren und erspart einem dadurch das Rad erneut erfinden zu muessen.
+Der Urspruengliche mnpc.c von Sir liegt fuer interessierte weiterhin in
++d/unterwelt/std/mnpc.sir.
+
+Die Eigenschaften im Ueberblick:
+
+MNPC_AREA - In diesem Feld stehen die Raeume, in denen der NPC herumlaufen
+ kann, ({}) bedeutet, das der NPC ueberall hin kann (bitte nur mit
+ aeusserster Vorsicht und Absprache des RMs benutzen!!!).
+ Bsp.: SetProp(MNPC_AREA, ({ "/d/ebene/room/ak_str" }));
+ Dieser NPC kann ueber saemtliche Raeume der Akademiestrasse laufen, also
+ z.b. in einen Raum mit dem Filenamen /d/ebene/room/ak_str1
+ aber auch in alle Raeume in einem Verzeichnis /d/ebene/room/ak_str/*
+
+MNPC_HOME - der Startraum des NPCs in den er spaeter ggf. auch wieder
+ zurueckkehrt. Setzt man diese Property nicht, wird versucht, den Ort des
+ Clonens zu erraten/bestimmen, aber wenn man sicher gehen moechte, dass der
+ MNPC das Home hat, was man denkt, sollte man es explizit setzen.
+
+MNPC_DELAY und. MNPC_RANDOM
+ MNPC_DELAY + random(MNPC_RANDOM) ergibt den Abstand zwischen zwei
+ Bewegungen des NPCs.
+ ACHTUNG:
+ 1. Diese Werte muessen gesetzt werden, _bevor_ MNPC_FLAGS
+ gesetzt werden, da die Set-Methode von MNPC_FLAGS die Anmeldung im
+ walk_master durchfuehrt. Sonst werden die Default-Werte benutzt!
+ 2. Wenn diese Werte geaendert werden, sollte man die MNPC_FLAGS neu
+ setzen, um so die Neuanmeldung im walk_master auszuloesen.
+
+MNPC_WALK_TIME - Zeitangabe wie lange es dauert bis der NPC stehen bleibt
+ falls er keinen Spieler mehr trifft. Dieser Wert sollte etwa 3-10 mal so
+ gross sein, wie die durchschnittliche Laufgeschwindigkeit. Wenn der NPC
+ dann einen reset lang keinen Spieler mehr trifft, movt er wieder in
+ seinen Startraum und wartet dort auf neue Spieler.
+ Defaultmaessig 600 sek.
+
+MNPC_FLAGS - property mit einem oder mehreren gesetzten Flags
+ MNPC_WALK - ueber dieses Flag kann das laufen ein- bzw. ausgeschaltet
+ werden.
+
+ MNPC_FOLLOW_PLAYER - der NPC verfolgt die Spieler, die er trifft
+
+ MNPC_NO_WALK_IN_FIGHT - der NPC bleibt beim kaempfen und rennt nicht weg
+
+ MNPC_GO_HOME_WHEN_STOPPED - der NPC geht sofort nach Hause wenn seine
+ Zeit abgelaufen ist.
+
+ MNPC_ONLY_EXITS - der NPC benutzt beim laufen keine SpecialExits
+ Diese Funktion ist sehr wichtig, da durch SpecialExits auch Raeume
+ betreten werden koennen die nicht in MNPC_AREA erlaubt wurden! Hierauf
+ muss dann bereits bei der Gebietskonstruktion geachtet werden.
+
+ MNPC_EXACT_AREA_MATCH - Die in MNPC_AREA angebeben Filenamen werden exakt
+ geprueft, d.h. tempel1 matcht auch nur tempel1, aber nicht mehr tempel14.
+ Folge: in MNPC_AREA muessten nun _alle_ Raeume einzeln angegeben werden, die
+ betreten werden duerfen.
+
+ MNPC_NO_MOVE_HOME - Der MNPC geht im reset() nicht nach Hause, wenn er
+ laenger keinen Spielerkontakt hatte, sondern bleibt an dem Standort,
+ wo er stehen blieb.
+ Achtung: sollte dieser Raum entladen werden, wird der MNPC dort
+ zuerstoert. Der Heimatraum des MNPC sollte dafuer vorsorgen (s. AddItem).
+ Ebenso kann es passieren, dass der MNPC sehr sehr lange nicht mehr
+ loslaeuft, sollte sein Standort einsam liegen.
+
+/***************************************************************************/
+
+Nun noch ein Beispiel fuer einen Laufnpc im Glockenwald:
+
+ create()
+ {
+ ::create();
+ SetProp(MNPC_DELAY, 30);
+ SetProp(MNPC_RANDOM, 30);
+ SetProp(MNPC_FLAGS, MNPC_WALK|MNPC_FOLLOW_PLAYER|MNPC_NO_WALK_IN_FIGHT);
+ SetProp(MNPC_AREA, ({ "/d/wald/troy/glockenwald" }));
+ ...
+ }
+
+ // diese Funktion entscheidet darueber ob ein Raum betreten werden darf,
+ // oder nicht (hier wird also auch MNPC_AREA abgearbeitet). Ein
+ // Ueberschreiben dieser Funktion kann manchmal sinnvoll sein, um
+ // komplexere Bedingungen zu implementieren.
+ int PreventEnter(string file)
+ {
+ call_other(file, "???");
+ if (::PreventEnter(file) || present("elster", find_object(file)))
+ return 1;
+ return 0;
+ }
+
+Dieser NPC laeuft durch den Glockenwald und sobald er einen Spieler trifft,
+verfolgt er diesen, bis zum Rande des Glockenwaldes. Wenn ein Spieler ihn
+angreift, bleibt er automatisch stehen. Die Spieler sind nur in Raeumen
+sicher, in denen sich eine Elster befindet, denn diese Raeume betritt der
+NPC nicht.
diff --git a/p/service/padreic/mnpc/mnpc.h b/p/service/padreic/mnpc/mnpc.h
new file mode 100644
index 0000000..023ada5
--- /dev/null
+++ b/p/service/padreic/mnpc/mnpc.h
@@ -0,0 +1,74 @@
+#ifndef _MNPC_H_
+#define _MNPC_H_
+
+/*
+ folgende Propertys koennen in mnpc's gesetzt werden...
+*/
+
+// bitte diese #define inheriten und nicht direkten Pfad benutzen
+#define MNPC "/p/service/padreic/mnpc/mnpc.c"
+#define MNPC_MOVING "/p/service/padreic/mnpc/moving.c"
+
+// bitte dieses #define benutzen und nicht den direkten Pfad verwenden
+#define WALK_MASTER "/p/service/padreic/mnpc/walk_master"
+#define MAX_MASTER_TIME 180
+
+// ist automatisch in jedem mnpc gesetzt
+#define P_MNPC "mnpc"
+
+// MNPC_DELAY + random(MNPC_RANDOM) ergibt den Abstand zwischen dem laufen
+#define MNPC_DELAY "mnpc_delay"
+#define MNPC_RANDOM "mnpc_random"
+
+// diese NPC wird aufgerufen nachdem der NPC einen Raum betreten hat.
+#define MNPC_FUNC "mnpc_func"
+
+// Raum angeben in dem der NPC zu Hause ist (dorthin wird im reset ggf. gemovt)
+// falls nicht angegeben, ist es der Ort des Clonens
+#define MNPC_HOME "mnpc_home"
+
+// array die die area in der der NPC sich bewegen darf abgrentzt
+#define MNPC_AREA "mnpc_area"
+
+// wie lange laeuft der NPC bis er stehen bleibt
+#define MNPC_WALK_TIME "mnpc_walk_time"
+
+// hier koennen einige Flags gesetzt werden
+#define MNPC_FLAGS "mnpc_flags"
+
+// die einzelnen Flags...
+#define MNPC_WALK 0x01 /* NPC ist ein Laufnpc */
+#define MNPC_FOLLOW_PLAYER 0x02 /* NPC verfolgt Spieler */
+#define MNPC_FOLLOW_ENEMY 0x06 /* NPC verfolgt feindliche Spieler */
+#define MNPC_ONLY_EXITS 0x08 /* NPC nutzt keine SpecialExits */
+#define MNPC_NO_WALK_IN_FIGHT 0x10 /* NPC bleibt im Kampf stehen */
+#define MNPC_GO_HOME_WHEN_STOPPED 0x20 /* NPC wartet nicht bis zum reset */
+// wird intern in non livings gesetzt - manuelles setzen nicht noetig
+// non livings koennen jedoch _nie_ special exits nutzen...
+#define MNPC_DIRECT_MOVE 0x40 /* NPC laeuft mit move statt mit command_me */
+// Manche Magier wollen in MNPC_AREA keine Substrings, sondern exakte
+// Filenamen angeben. Wenn dieses Flag gesetzt wird, werden die Strings in
+// MNPC_AREA exakt geprueft, nicht mit strstr()
+#define MNPC_EXACT_AREA_MATCH 0x80
+// Manche Magier wollen nicht, dass der MNPC sich im reset nach Hause bewegt
+#define MNPC_NO_MOVE_HOME 0x100
+
+/*
+ Folgendes sind interne Defaultwerte falls die werte nicht gesetzt werden.
+*/
+
+// default delay wird genommen, falls kein bestimmtes eigenes angegeben wird
+#define MNPC_DFLT_DELAY 30
+// standardhome falls kein besseres home ermittelbar ist
+#define MNPC_DFLT_HOME "/room/void"
+// der NPC bleibt nach dieser Zeit stehen und geht im reset nach Hause
+#define MNPC_DFLT_WALK 360 /* vorerst 6 min. */
+// Diese Funktion wird aufgerufen wenn der NPC einen raum betritt
+
+/*
+ interne defines
+*/
+#define MNPC_PURSUER "mnpc:pursuer" /* nur intern */
+#define MNPC_LAST_MEET "mnpc_last_meet"
+
+#endif // _MNPC_H_
diff --git a/p/service/padreic/mnpc/moving.c b/p/service/padreic/mnpc/moving.c
new file mode 100644
index 0000000..b17d503
--- /dev/null
+++ b/p/service/padreic/mnpc/moving.c
@@ -0,0 +1,421 @@
+#pragma save_types,strong_types,rtt_checks,pedantic
+
+inherit "/std/living/moving";
+
+#include <moving.h>
+#include <defines.h>
+#include <properties.h>
+#include "/p/service/padreic/mnpc/mnpc.h"
+#define NEED_PROTOTYPES
+#include <living/combat.h>
+#undef NEED_PROTOTYPES
+#include <combat.h>
+
+#define ENV environment
+#define PO previous_object()
+
+// Letzter Spielerkontakt. -1, wenn der MNPC inaktiv zuhause rumsteht
+static int meet_last_player;
+
+static void mnpc_create()
+{
+ if (PO && member(inherit_list(PO), "/std/room.c")!=-1)
+ SetProp(MNPC_HOME, object_name(PO));
+ else if (PL && ENV(PL))
+ SetProp(MNPC_HOME, object_name(ENV(PL)));
+ else
+ SetProp(MNPC_HOME, MNPC_DFLT_HOME);
+ SetProp(P_MNPC, 1);
+ SetProp(MNPC_AREA, ({}));
+ SetProp(MNPC_DELAY, MNPC_DFLT_DELAY);
+ SetProp(MNPC_FUNC, 0);
+ SetProp(MNPC_RANDOM, 0);
+ SetProp(MNPC_WALK_TIME, MNPC_DFLT_WALK);
+ SetProp(MNPC_FLAGS, 0);
+ SetProp(P_ENABLE_IN_ATTACK_OUT, 1);
+ meet_last_player=time();
+}
+
+protected void RegisterWalk()
+{
+ if ((QueryProp(MNPC_DELAY)+QueryProp(MNPC_RANDOM))<=MAX_MASTER_TIME)
+ WALK_MASTER->RegisterWalker(QueryProp(MNPC_DELAY),
+ QueryProp(MNPC_RANDOM));
+ else
+ call_out("Walk", QueryProp(MNPC_DELAY)+random(QueryProp(MNPC_RANDOM)));
+}
+
+// Can be used to manually restart the MNPC from a different object even if
+// the MNPC had not player contact.
+public int RestartWalk()
+{
+ int flags = QueryProp(MNPC_FLAGS);
+ // Falls nicht laufend, wird gar nichts gemacht.
+ if (flags & MNPC_WALK)
+ {
+ //Spielerkontakt simulieren
+ meet_last_player=time();
+ // Falls MNPC noch registriert ist oder noch einen Callout auf Walk hat,
+ // muss nichts weiter gemacht werden.
+ if (WALK_MASTER->Registration()
+ || find_call_out("Walk") > -1)
+ return -1;
+ // ansonsten MNPC registrieren, falls geeignet.
+ if ((QueryProp(MNPC_DELAY)+QueryProp(MNPC_RANDOM))<=MAX_MASTER_TIME)
+ WALK_MASTER->RegisterWalker(QueryProp(MNPC_DELAY), QueryProp(MNPC_RANDOM));
+ // und mit kurzer Verzoegerung einmal laufen. (ja, absicht, hier
+ // MNPC_DELAY zu nutzen - denn solange dauert das Walk vom Master
+ // mindestens.)
+ call_out("Walk",1+random( min(QueryProp(MNPC_DELAY)-1,8) ));
+ return 1;
+ }
+ return 0;
+}
+
+protected void Stop(int movehome)
+{
+ if (WALK_MASTER->Registration())
+ WALK_MASTER->RemoveWalker();
+ else if (find_call_out("Walk")!=-1)
+ remove_call_out("Walk");
+ if (movehome)
+ {
+ move(QueryProp(MNPC_HOME), M_TPORT|M_NOCHECK);
+ meet_last_player=-1;
+ }
+}
+
+static int _set_mnpc_flags(int flags)
+{
+ if (flags & MNPC_WALK)
+ {
+ if (!QueryProp(MNPC_HOME))
+ raise_error("unknown MNPC_HOME\n");
+ //wenn die Flags neu gesetzt werden, wird der MNPC das zweite Mal im
+ //Master angemeldet -> vorher abmelden (Zesstra)
+ if (QueryProp(MNPC_FLAGS) & MNPC_WALK)
+ {
+ Stop(0);
+ }
+ RegisterWalk();
+ }
+ // else nicht von Bedeutung, da in Walk() das flag getestet wird
+ if (flags & MNPC_FOLLOW_PLAYER)
+ {
+ if (!QueryProp(MNPC_PURSUER))
+ { // wurde dieses Flag neu eingeschaltet?
+ if (environment())
+ { // Verfolgung aufnehmen...
+ object *pursuer = filter(all_inventory(ENV()), #'interactive);
+ filter_objects(pursuer, "AddPursuer", ME);
+ SetProp(MNPC_PURSUER, pursuer);
+ }
+ else
+ SetProp(MNPC_PURSUER, ({}));
+ }
+ }
+ else if (pointerp(QueryProp(MNPC_PURSUER)))
+ { // wird derzeit irgendwer verfolgt?
+ // alle Verfolgungen abbrechen...
+ filter_objects(QueryProp(MNPC_PURSUER)-({ 0 }), "RemovePursuer", ME);
+ SetProp(MNPC_PURSUER, 0); // Speicher freigeben...
+ }
+ else
+ SetProp(MNPC_PURSUER, 0);
+
+ // nur livings koennen command_me nutzen...
+ if (!living(ME))
+ flags |= MNPC_DIRECT_MOVE;
+
+ return Set(MNPC_FLAGS, flags, F_VALUE);
+}
+
+static void mnpc_InsertEnemy(object enemy)
+{
+ if ( (QueryProp(MNPC_FLAGS) & MNPC_FOLLOW_ENEMY) &&
+ (member(QueryProp(MNPC_PURSUER), PL)==-1))
+ {
+ PL->AddPursuer(ME);
+ SetProp(MNPC_PURSUER, QueryProp(MNPC_PURSUER)+({ PL }));
+ }
+}
+
+static void mnpc_reset()
+{
+ int flags = QueryProp(MNPC_FLAGS);
+ // meet_last_player < 0 zeigt an, dass der MNPC schon zuhause ist.
+ if (meet_last_player < 0
+ || !(flags & MNPC_WALK)
+ || (flags & MNPC_NO_MOVE_HOME))
+ return;
+
+ // Lange keinen Spielerkontakt und kein Spieler im Raum: nach Hause gehen.
+ if (QueryProp(MNPC_WALK_TIME)+meet_last_player < time()
+ && environment() && !sizeof(filter(
+ all_inventory(environment()), #'query_once_interactive)))
+ {
+ // Abschalten und Heimgehen und dort warten.
+ Stop(1);
+ }
+}
+
+static int _query_mnpc_last_meet()
+{ return meet_last_player; }
+
+static void mnpc_init()
+{
+ if (interactive(PL))
+ {
+ if (meet_last_player<=0)
+ {
+ RegisterWalk();
+ }
+ if ( (QueryProp(MNPC_FLAGS) & MNPC_FOLLOW_PLAYER) &&
+ (member(QueryProp(MNPC_PURSUER), PL)==-1) &&
+ (!(QueryProp(MNPC_FLAGS) & MNPC_FOLLOW_ENEMY) || IsEnemy(PL)))
+ {
+ PL->AddPursuer(ME);
+ SetProp(MNPC_PURSUER, QueryProp(MNPC_PURSUER)+({ PL }));
+ }
+ meet_last_player=time();
+ }
+ else
+ {
+ // Wenn der reinkommende auch ein MNPC_LAST_MEET groesser 0 hat, ist es
+ // ein MNPC, der noch laeuft. Wenn wir nicht laufen, laufen wir los.
+ // In diesem und auch im anderen Fall uebernehmen wir aber mal seinen
+ // letzten Spielerkontakt, denn der ist juenger als unserer.
+ int lm = PL->QueryProp(MNPC_LAST_MEET);
+ if (meet_last_player<=0 && lm>0)
+ {
+ RegisterWalk();
+ meet_last_player=lm;
+ }
+ else if (meet_last_player<lm)
+ meet_last_player=lm;
+ }
+}
+
+static void mnpc_move()
+{
+ if (environment() && (QueryProp(MNPC_FLAGS) & MNPC_FOLLOW_PLAYER))
+ {
+ object *liv = QueryProp(MNPC_PURSUER) & all_inventory(environment());
+ filter_objects(QueryProp(MNPC_PURSUER)-liv-({ 0 }), "RemovePursuer", ME);
+ SetProp(MNPC_PURSUER, liv);
+ }
+ if (QueryProp(MNPC_FUNC))
+ ME->QueryProp(MNPC_FUNC);
+}
+
+static int PreventEnter(string file)
+// darf der Raum betreten werden?
+{
+ string *area;
+
+ if (!sizeof(area=QueryProp(MNPC_AREA)))
+ return 0; // Raum darf betreten werden
+ else
+ {
+ int i;
+ status exactmatch;
+ exactmatch=QueryProp(MNPC_FLAGS) & MNPC_EXACT_AREA_MATCH;
+ if ((i=strstr(file, "#"))!=-1) file=file[0..i-1];
+ for (i=sizeof(area)-1; i>=0; i--)
+ {
+ if (exactmatch)
+ {
+ //exakter Vergleich, kein Substringvergleich gewuenscht
+ if (file==area[i])
+ return 0; //betreten
+ }
+ else
+ {
+ if (strstr(file, area[i])==0)
+ return 0; // Raum betreten
+ }
+ }
+ return 1; // Raum darf nicht betreten werden
+ }
+}
+
+static int mnpc_PreventFollow(object dest)
+{
+ if (dest && PreventEnter(object_name(dest)))
+ return 2;
+ return 0;
+}
+
+// Bewegungssimulation (Bewegungsmeldung) fuer bewegende non-livings
+static int direct_move(mixed dest, int method, string direction)
+{
+ int res, para, tmp;
+ string textout, textin, *mout, vc, fn;
+ object oldenv, *inv;
+
+ if (living(ME))
+ return call_other(ME, "move", dest, method);
+ else
+ {
+ oldenv = environment();
+ para=QueryProp(P_PARA);
+ if ((para>0) && stringp(dest))
+ {
+ fn=dest+"^"+para;
+ if (find_object(fn) || (file_size(fn+".c")>0))
+ dest=fn;
+ else if (file_size(vc=implode(explode(fn,"/")[0..<2],"/")
+ +"/virtual_compiler.c")>0)
+ {
+ // wenn ein VC existiert, prüfen ob dieser ParaObjecte unterstuetzt
+ // wenn ja, dann testen ob sich Raum laden laesst...
+ if ((!catch(tmp=(int)call_other(vc,"NoParaObjects")) && (!tmp)) &&
+ (!catch(call_other( fn, "???" ))))
+ dest=fn;
+ }
+ }
+
+ res = (int)call_other(ME, "move", dest, M_NOCHECK);
+
+ if (oldenv==environment())
+ return res;
+
+ // als erstes die Meldung fuer das Verlassen des Raumes...
+ if ( method & M_TPORT )
+ textout = (string) QueryProp(P_MMSGOUT) || (string) QueryProp(P_MSGOUT);
+ else
+ {
+ mout = explode( (string) QueryProp(P_MSGOUT) || "", "#" );
+ textout = mout[0] || (string) QueryProp(P_MMSGOUT);
+ }
+
+ if (stringp(textout))
+ {
+ if ( !sizeof(direction) )
+ direction = 0;
+
+ inv = all_inventory(oldenv) - ({ this_object() });
+ inv = filter( inv, #'living);
+ inv -= filter_objects( inv, "CannotSee", 1 );
+ filter( inv, #'tell_object,
+ Name( WER, 2 ) + " " + textout +
+ (direction ? " " + direction : "") +
+ (sizeof(mout) > 1 ? mout[1] : "") + ".\n" );
+ }
+
+ // nun die Meldung für das "Betreten" des Raumes...
+
+ if ( method & M_TPORT )
+ textin = (string) QueryProp(P_MMSGIN);
+ else
+ textin = (string) QueryProp(P_MSGIN);
+
+ if (stringp(textin))
+ {
+ inv = all_inventory(environment()) - ({ this_object() });
+ inv = filter( inv, #'living);
+ inv -= filter_objects( inv, "CannotSee", 1 );
+ filter( inv, #'tell_object,
+ capitalize(name( WER, 0 )) + " " + textin + ".\n" );
+ }
+ }
+ return res;
+}
+
+int Walk()
+{
+ int i;
+ mapping exits;
+ string *rooms, *dirs, *ex, tmp;
+
+ if (!environment())
+ {
+ // darf eigentlich nicht vorkommen.
+ raise_error("MNPC ohne Environment.\n");
+ }
+
+ int flags=QueryProp(MNPC_FLAGS);
+ if (!(flags & MNPC_WALK))
+ return 0;
+
+ //ggf. neuen Callout eintragen, bevor irgendwas anderes gemacht wird.
+ if ((QueryProp(MNPC_DELAY)+QueryProp(MNPC_RANDOM))>MAX_MASTER_TIME)
+ call_out("Walk", QueryProp(MNPC_DELAY)+random(QueryProp(MNPC_RANDOM)));
+
+ // Im Kampf ggf. nicht weitergehen.
+ if ((flags & MNPC_NO_WALK_IN_FIGHT) && InFight())
+ {
+ meet_last_player=time();
+ return 1;
+ }
+
+ // MNPC anhalten, wenn lange kein Spielerkontakt
+ if (QueryProp(MNPC_WALK_TIME)+meet_last_player < time()
+ && !sizeof(filter(all_inventory(environment()),
+ #'query_once_interactive))
+ )
+ {
+ // anhalten und ggf. auch direkt nach Hause gehen.
+ Stop(flags & MNPC_GO_HOME_WHEN_STOPPED);
+ return 0;
+ }
+
+ // Ausgaenge ermitteln.
+ exits = (environment()->QueryProp(P_EXITS));
+ rooms = m_values(exits);
+ dirs = m_indices(exits);
+ ex = ({});
+
+ for (i=sizeof(rooms)-1; i>=0; i--)
+ {
+ if (!PreventEnter(rooms[i]))
+ ex += ({ dirs[i] });
+ }
+ /* Hier muessen wir auf die Zuverlaessigkeit unserer Magier bauen ... */
+ if (flags & MNPC_DIRECT_MOVE)
+ {
+ // im direct mode keine SEs benutzbar...
+ if (sizeof(ex))
+ {
+ tmp=ex[random(sizeof(ex))];
+ direct_move(explode(exits[tmp], "#")[<1], M_GO, "nach "+capitalize(tmp));
+ }
+ else
+ {
+ // Hngl. Nach Hause...
+ direct_move(QueryProp(MNPC_HOME), M_TPORT|M_NOCHECK, 0);
+ meet_last_player=-1;
+ }
+ }
+ else if (flags & MNPC_ONLY_EXITS)
+ {
+ // logischerweise auch keine SEs benutzen...
+ if (sizeof(ex))
+ {
+ command(ex[random(sizeof(ex))]); /* Irgendwohin gehen */
+ }
+ else
+ {
+ // Hngl. Nach Hause...
+ move(QueryProp(MNPC_HOME), M_TPORT|M_NOCHECK);
+ meet_last_player=-1;
+ }
+ }
+ else
+ {
+ // Special Exits mitbenutzen.
+ ex += m_indices(ENV()->QueryProp(P_SPECIAL_EXITS));
+ if (sizeof(ex))
+ {
+ command(ex[random(sizeof(ex))]); /* Irgendwohin gehen */
+ }
+ else
+ {
+ // Hngl. Gar keine Ausgaenge. Nach Hause...
+ move(QueryProp(MNPC_HOME), M_TPORT|M_NOCHECK);
+ meet_last_player=-1;
+ }
+ }
+
+ return 1;
+}
diff --git a/p/service/padreic/mnpc/walk_master.c b/p/service/padreic/mnpc/walk_master.c
new file mode 100644
index 0000000..3e003c8
--- /dev/null
+++ b/p/service/padreic/mnpc/walk_master.c
@@ -0,0 +1,186 @@
+// (c) by Padreic (Padreic@mg.mud.de)
+
+/* 12 Juli 1998, Padreic
+ *
+ * Dieser Master dient zum einsparen von call_out Ketten bei Laufnpcs.
+ * Anmelden bei diesem Master geschieht ueber die Funktion RegisterWalker().
+ * varargs void RegisterWalker(int time, int random, closure walk_closure)
+ * time - in welchen Abstaenden soll das Objekt bewegt werden
+ * rand - dieser Wert wird als random immer beim bewegen zu time addiert
+ * walk_closure - die Closure die immer aufgerufen werden muss, wenn dieser
+ * Parameter weggelassen wird, wird statdessen die Funktion
+ * Walk() im NPC aufgerufen.
+ *
+ * Abgemeldet wird der NPC sobald die Closure bzw. die Walk-Funktion 0
+ * returned. Bei allen Werten !=0 bleibt der NPC aktiv.
+ *
+ * Hinweis: Der NPC muss sich mind. alle 180 sek. bewegen (time+random),
+ * ansonsten kann dieser Master nicht verwendet werden.
+ */
+
+#define MAX_DELAYTIME 90 /* max. delay ist 2*MAX_DELAYTIME */
+#define DEFAULT_WALK_DELAY 180 /* ist der billigste Wert :) */
+#define MAX_JOB_COST 200000 /* Wieviel Zeit darf ein NPC max. nutzen */
+
+// Funktionen zum vereinfachten Zugriff auf die Komprimierten Daten
+// im Walkerarray
+#define TIME(t) (t & 0x00ff) /* 8 Bit = 256 */
+#define RANDOM(r) ((r & 0xff00) >> 8) /* 8 Bit = 256 */
+#define WERT(t, r) ((t & 0x00ff)+((r << 8) & 0xff00)) /* 16 Bit */
+
+static int counter; // zur Orientierung im walker-array
+static int num_walker; // anzahl der walker im array
+//static mixed *walker; // ({ ..., ({ ..., ({ wert, closure }), ...}), ...})
+nosave < < <int|closure>* >* >* walker;
+
+protected void create()
+{
+ if (clonep(this_object())) {
+ destruct(this_object());
+ raise_error("walk_master can't be clonend.\n");
+ }
+ walker=({});
+ num_walker=0;
+ enable_commands(); // ohne das, kein heart_beat()
+}
+
+#define ERROR(x) raise_error(sprintf(x, previous_object()));
+
+// Man muss selbst darauf aufpassen, das sich ein NPC immer nur einmal
+// anmeldet, da sonst auch mehrere Paralelle Walk-Ketten laufen!!!
+// Am besten nie direkt sondern nur ueber einen Standardnpc benutzen.
+varargs void RegisterWalker(int time, int rand, closure walk_closure)
+{
+ int wert, next;
+ closure func;
+
+ // pruefen ob die Paramter zulaessig sind...
+ if (time<0) ERROR("negative time to RegisterWalker() from %O.\n");
+ if (rand<0) ERROR("negative random to RegisterWalker() from %O.\n");
+ if ((time+rand) > (2*MAX_DELAYTIME))
+ ERROR("Too long delaytime from %s to RegisterWalker().\n");
+
+ wert=WERT(time, rand);
+ if (!wert && !rand) wert=DEFAULT_WALK_DELAY;
+ if (walk_closure)
+ func=walk_closure;
+ else {
+ func=symbol_function("Walk", previous_object());
+ if (!func)
+ raise_error("RegisterWalker() call from Object without Walk() function.\n");
+ }
+ if (!num_walker) {
+ set_heart_beat(1);
+ if (!pointerp(walker) || !sizeof(walker))
+ walker=map(allocate(MAX_DELAYTIME+1), #'allocate);
+ }
+ next=counter;
+ next+=(time+random(rand))/2;
+ if (next>MAX_DELAYTIME) next-=MAX_DELAYTIME;
+ walker[next]+=({ ({ wert, func }) });
+ num_walker++;
+}
+
+int dummy_walk() // liefert immer 0 fuer abbrechen...
+{ return 0; }
+
+// Aufruf nach Moeglichkeit bitte vermeiden, da recht aufwendig. Meist ist
+// es leicht im NPC "sauber Buch zu fuehren" und dann ggf. aus Walk()
+// 0 zu returnen.
+void RemoveWalker()
+{
+ int i, j;
+ if (!num_walker) return;
+ for (i=MAX_DELAYTIME; i>=0; i--) {
+ for (j=sizeof(walker[i])-1; j>=0; j--)
+ {
+ if (get_type_info(walker[i][j][1], 2)==previous_object())
+ {
+ if (i==counter) // koennte gerade im heart_beat stecken...
+ walker[i][j]=({ 0, #'dummy_walk });
+ else
+ walker[i][j]=0;
+ num_walker--;
+ }
+ }
+ if (i!=counter) // koennte gerade im heart_beat stecken...
+ walker[i]-=({ 0 });
+ }
+}
+
+// Aufruf nach Moeglichkeit bitte vermeiden, da recht aufwendig. Meist ist
+// es leichter im NPC "sauber Buch zu fuehren" und sich zu merken, ob er
+// bereits angemeldet ist. Liefert zurueck, wie oft ein NPC als Walker
+// angemeldet ist (normalfall nie mehr als 1).
+int Registration()
+{
+ int i, j, reg;
+ if (!num_walker) return 0;
+ reg=0;
+ for (i=MAX_DELAYTIME; i>=0; i--)
+ {
+ for (j=sizeof(walker[i])-1; j>=0; j--)
+ if (get_type_info(walker[i][j][1], 2)==previous_object())
+ reg++;
+ }
+ return reg;
+}
+
+void heart_beat()
+{
+ int i;
+ if (num_walker && i=sizeof(walker[counter])) {
+ int tmp;
+ num_walker-=i;
+ for (i--;i>=0;i--) {
+ if (get_eval_cost() < MAX_JOB_COST) {
+ // nicht abgefertigte NPCs im naechsten heart_beat ausfuehren
+ walker[counter]=walker[counter][0..i];
+ num_walker+=i+1;
+ return;
+ }
+ else {
+ if (walker[counter][i][1] &&
+ !catch(tmp=(int)funcall(walker[counter][i][1])) && tmp) {
+ tmp=counter+(TIME(walker[counter][i][0])
+ +random(RANDOM(walker[counter][i][0])))/2;
+ if (tmp>MAX_DELAYTIME) tmp-=MAX_DELAYTIME;
+ walker[tmp]+=({ walker[counter][i] });
+ num_walker++;
+ }
+ }
+ }
+ walker[counter]=({}); // komplett leeren
+ }
+ if (counter == MAX_DELAYTIME)
+ counter=0;
+ else counter++;
+ if (!num_walker) {
+ set_heart_beat(0);
+ walker=({}); // Speicher freigeben...
+ }
+}
+
+void reset()
+// kostet maximal einen unnoetigen heart_beat() pro reset -> vertretbar
+// dient zu einem wieder anwerfen im Falle eines Fehlers im heart_beat()
+{
+ if (set_heart_beat(0)<=0) {
+ int i;
+ num_walker=0; // neu berechnen...
+ if (!sizeof(walker)) return;
+ for (i=MAX_DELAYTIME; i>=0; i--)
+ num_walker+=sizeof(walker[i]);
+ if (num_walker>0) {
+ write_file(object_name()+".err", sprintf(
+ "%s: Fehler im heart_beat(). %d aktive Prozesse.\n",
+ dtime(time()), num_walker));
+ enable_commands();
+ set_heart_beat(1);
+ }
+ }
+ else set_heart_beat(1);
+}
+
+mixed *WalkerList() // nur fuer Debugzwecke
+{ return ({ num_walker, walker, counter }); }