blob: c6149ffa7cb8a72da273c9c62fddc7287dfad43d [file] [log] [blame]
// MorgenGrauen MUDlib
//
// living/moving.c -- moving of living objects
//
// $Id: moving.c 9448 2016-01-22 17:52:28Z Zesstra $
#pragma strong_types
#pragma save_types
#pragma range_check
#pragma no_clone
#pragma pedantic
inherit "/std/thing/moving";
#define NEED_PROTOTYPES
#include <hook.h>
#include <living/moving.h>
#include <living/skills.h>
#include <thing/properties.h>
#include <thing/description.h>
#include <moving.h>
#include <new_skills.h>
#include <living.h>
#undef NEED_PROTOTYPES
#include <config.h>
#include <properties.h>
#include <language.h>
#include <wizlevels.h>
#include <defines.h>
protected void create()
{
if (object_name(this_object()) == __FILE__[0..<3])
{
return;
}
offerHook(H_HOOK_MOVE,1);
}
public void AddPursuer(object ob)
{
mixed *pur;
if (!objectp(ob))
return;
if (!pointerp(pur=Query(P_PURSUERS)))
pur=({0,({})});
else if (member(pur[1],ob)!=-1)
return;
SetProp(P_PURSUERS,({ pur[0], pur[1]+({ob})-({0}) }));
ob->_SetPursued(ME);
}
public void RemovePursuer(object ob)
{
mixed *pur;
if (pointerp(pur=Query(P_PURSUERS,F_VALUE))
&& member(pur[1],ob)!=-1)
{
pur[1]-=({ob,0});
if (ob)
ob->_RemovePursued(ME);
if (!pur[0]&&!sizeof(pur[1]))
pur=0;
SetProp(P_PURSUERS,pur);
}
}
public void _SetPursued(object ob)
{
mixed *pur;
if (!pointerp(pur=Query(P_PURSUERS)))
pur=({0,({})});
else
if (objectp(pur[0]))
pur[0]->RemovePursuer(ME);
pur[0]=ob;
pur[1]-=({0});
Set(P_PURSUERS,pur);
}
public void _RemovePursued(object ob)
{
mixed *pur;
if (!pointerp(pur=Query(P_PURSUERS)) || pur[0]!=ob)
return;
pur[0]=0;
pur[1]-=({0});
if (!sizeof(pur[1]))
pur=0;
Set(P_PURSUERS,pur);
}
private void kampfende( object en ) {
if (!objectp(en)) return;
tell_object( ME, capitalize(en->name()) +
" ist jetzt hinter Dir her.\n" );
tell_object( en, "Du verfolgst jetzt " + name(WEN) + ".\n" );
en->InsertSingleEnemy(ME);
}
private int _is_learner(object pl) {
return IS_LEARNER(pl);
}
// a) Pruefungen, ob das move erlaubt ist.
// b) zum Ueberschreiben
protected int PreventMove(object dest, object oldenv, int method) {
// M_NOCHECK? -> Bewegung eh erlaubt (und Rueckgabewert wuerde ignoriert),
// aber PreventInsert/PreventLeave() rufen und ignorieren.
if ((method&M_NOCHECK)) {
// erst PreventLeaveLiving() rufen...
if(environment())
environment()->PreventLeaveLiving(this_object(), dest);
// dann PreventInsertLiving() im Ziel-Env.
dest->PreventInsertLiving(this_object());
// und raus...
return(0);
}
// bei Lebewesen muss die Bewegungsmethode M_GO und M_TPORT sein. Dies ist
// gleichzeigt die Restriktion gegen das Nehmen von Lebewesen, da dort
// M_GET/M_GIVE/M_PUT etc. verwendet wuerde. Bei M_GO und M_TPORT findet
// keine Pruefung statt, ob das Objekt ins Ziel 'reinpasst' (Gewicht, Anzahl
// Objekte usw.).
// Ich finde es etwas merkwuerdig gebaut (Zesstra).
if ( !(method & (M_GO | M_TPORT)) )
return ME_PLAYER;
// alte und neue Umgebung auf NO_TPORT pruefen.
if ( (method & M_TPORT) ) {
if ( environment() &&
(environment()->QueryProp(P_NO_TPORT) & (NO_TPORT_OUT|NO_TPORT)) )
return ME_CANT_TPORT_OUT;
else if ( dest->QueryProp(P_NO_TPORT) & (NO_TPORT_IN|NO_TPORT) )
return ME_CANT_TPORT_IN;
}
// erst PreventLeaveLiving() testen...
if( environment() && environment()->PreventLeaveLiving(this_object(), dest))
return ME_CANT_LEAVE_ENV;
// dann PreventInsertLiving() im Ziel-Env
if (dest->PreventInsertLiving(this_object()))
return ME_CANT_BE_INSERTED;
return 0;
}
// Krams nach dem Move machen und nebenbei zum Ueberschreiben.
protected void NotifyMove(object dest, object oldenv, int method) {
mixed res;
object enem;
// Begruessungsschlag fuer die Gegener
if ( !(method & M_NO_ATTACK) )
InitAttack();
if (!objectp(ME)) return;
// Verfolger nachholen.
if ( pointerp(res = Query(P_PURSUERS)) && sizeof(res[1]) ) {
while ( remove_call_out( "TakeFollowers" ) >= 0 );
call_out( "TakeFollowers", 0 );
}
// und noch das Team nachholen.
if ( oldenv != dest
&& objectp(ME)
&& QueryProp(P_TEAM_AUTOFOLLOW)
&& objectp( enem = IsTeamLeader() ) )
enem->StartFollow(oldenv); // Teamverfolgung
}
varargs public int move( object|string dest, int method, string direction,
string textout, string textin )
{
int para, nightvis, invis, tmp;
object oldenv, *inv;
string fn,vc;
mixed res;
mixed hookData, hookRes;
if (!objectp(dest) && !stringp(dest))
raise_error(sprintf("Wrong argument 1 to move(). 'dest' must be a "
"string or object! Argument was: %.100O\n",
dest));
// altes Env erstmal merken.
oldenv = environment();
//erstmal den richtigen Zielraum suchen, bevor irgendwelche Checks gemacht
//werden...
// Ist der Spieler in einer Parallelwelt?
if ( (para = QueryProp(P_PARA)) && intp(para) ) {
fn = objectp(dest) ? object_name(dest) : dest;
// Falls der Zielraum nicht schon explizit in der Parallelwelt ist,
// neuen Zielraum suchen. Aber nur, wenn fn kein # enthaelt (also kein
// Clone ist), sonst wuerde eine Bewegung nach raum#42^para versucht,
// was dann buggt. ;-) Problem wird offenbar, wenn ein Para-Lebewesen
// im create() eines VC-Raums in Para in den Raum bewegt wird, da
// dieser dann noch nicht vom Driver umbenannt wurde und raum#42
// heisst.
if ( !sizeof(regexp( ({ fn }), "\\^[1-9][0-9]*$" )) &&
strrstr(fn,"#")==-1 )
{
fn += "^" + para;
// Der Parallelwelt-Raum muss existieren und fuer Spieler
// freigegeben sein, damit er zum neuen Ziel wird. Ansonsten
// duerfen nur NPCs, Testspieler und Magier herein.
if ( (find_object(fn)
|| ((file_size(fn+".c")>0 ||
(file_size(vc=implode(explode(fn,"/")[0..<2],"/")+
"/virtual_compiler.c")>0 &&
!catch(tmp=(int)call_other(vc,"QueryValidObject",fn);
publish) && tmp>0)) &&
!catch(load_object(fn);publish) )) &&
(!interactive(ME) || !fn->QueryProp(P_NO_PLAYERS) ||
(method & M_NOCHECK) || IS_LEARNER(ME) ||
(stringp(res = QueryProp(P_TESTPLAYER)) &&
IS_LEARNER( lower_case(res) ))) )
{
dest = fn;
}
else
{
// Wir bleiben in der Normalwelt.
para = 0;
}
}
}
// jetzt erstmal Hooks abpruefen, da sie ggf. die Daten aendern.
// alten P_TMP_MOVE_HOOK pruefen.
if ( res = QueryProp(P_TMP_MOVE_HOOK) ){
if ( pointerp(res) && sizeof(res) >= 3
&& intp(res[0]) && time()<res[0]
&& objectp(res[1]) && stringp(res[2]) ){
if ( res = call_other( res[1], res[2], dest, method, direction,
textout, textin ) ){
if ( pointerp(res) && sizeof(res) == 5 ){
dest = res[0];
method = res[1];
direction = res[2];
textout = res[3];
textin = res[4];
}
else if ( intp(res) && res == -1 )
return ME_CANT_LEAVE_ENV;
}
} else
SetProp( P_TMP_MOVE_HOOK, 0 );
}
// move hook nach neuem Hooksystem triggern.
hookData=({dest,method,direction,textout,textin});
hookRes=HookFlow(H_HOOK_MOVE,hookData);
if(hookRes && pointerp(hookRes) && sizeof(hookRes)>H_RETDATA) {
if(hookRes[H_RETCODE]==H_CANCELLED) {
return ME_CANT_LEAVE_ENV;
}
else if(hookRes[H_RETCODE]==H_ALTERED && hookRes[H_RETDATA] &&
pointerp(hookRes[H_RETDATA]) && sizeof(hookRes[H_RETDATA])>=5 ){
dest = hookRes[H_RETDATA][0];
method = hookRes[H_RETDATA][1];
direction = hookRes[H_RETDATA][2];
textout = hookRes[H_RETDATA][3];
textin = hookRes[H_RETDATA][4];
}
}
// dest auf Object normieren
if (stringp(dest)) dest=load_object(dest);
// jetzt Checks durchfuehren, ob das Move durchgefuehrt werden darf.
if (tmp=PreventMove(dest, oldenv, method)) {
// auf gueltigen Fehler pruefen, wer weiss, was Magier da evtl.
// versehentlich zurueckgeben.
if (VALID_MOVE_ERROR(tmp))
return(tmp);
else
return(ME_DONT_WANT_TO_BE_MOVED);
}
if ( invis = QueryProp(P_INVIS) )
method |= M_SILENT;
if ( objectp(oldenv) ) {
if ( !(method & M_SILENT) ) {
string *mout;
if ( !textout ){
if ( method & M_TPORT )
textout = (string) QueryProp(P_MMSGOUT) ||
(string) QueryProp(P_MSGOUT);
else
textout = (mout = explode( (string)
QueryProp(P_MSGOUT) || "",
"#" ))[0]
|| (string)QueryProp(P_MMSGOUT);
}
if ( !sizeof(direction) )
direction = 0;
inv = all_inventory(environment()) - ({ 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" );
}
// Magier sehen auch Bewegungen, die M_SILENT sind
else if ( interactive(ME) ){
inv = (all_inventory(environment()) & users())
- ({ this_object() });
inv = filter( inv, #'_is_learner/*'*/ );
if ( invis )
fn = "(" + capitalize(getuid(ME)) + ") verschwindet "
"unsichtbar.\n";
else
fn = capitalize(getuid(ME)) + " verschwindet ganz leise.\n";
filter( inv, #'tell_object/*'*/, fn );
}
// Nackenschlag beim Fluechten:
if ( !(method & M_NO_ATTACK) && objectp(ME) )
ExitAttack();
//falls nach ExitAttack() das Living nicht mehr existiert, muss das
//move() auch nicht mehr fortgesetzt werden. Weiter unten gibt es auch
//min. eine Stelle, die nicht prueft und ggf. buggt. Daher erfolgt
//hier ggf. ein Abbruch. 15.11.06 Zesstra
if (!objectp(ME)) return(ME_WAS_DESTRUCTED);
// Nackenschlag kann ME in den Todesraum bewegt haben...
if ( oldenv == environment() ) {
// Fuer alle anwesenden gegner kampfende() aufrufen
filter((QueryEnemies()[0] & all_inventory(oldenv))-({0}),
#'kampfende);
// Bugs im exit() sind ohne catch() einfach mist.
catch(environment()->exit(ME, dest);publish);
}
}
// irgendwas kann das Objekt zerstoert haben, z.B. env->exit().
if (!objectp(ME)) return(ME_WAS_DESTRUCTED);
if ( oldenv != environment() )
// Der Nackenschlag oder exit() koennen einen schon bewegt haben.
// Und wenn es in den Todesraum ist. ;^)
return MOVE_OK;
SetProp( P_PREPARED_SPELL, 0 ); // Spruchvorbereitung abgebrochen
SetProp( P_LAST_MOVE, time() ); // Zeitpunkt der letzten Bewegung
move_object(ME, dest);
if (!objectp(ME))
return(ME_WAS_DESTRUCTED);
dest = environment();
nightvis = UseSkill(SK_NIGHTVISION);
// Meldungen an nicht-Blinde ausgeben, falls keine 'stille' Bewegung
if ( !(method & M_SILENT) ) {
if ( !textin ) {
if ( method & M_TPORT )
textin = (string) QueryProp(P_MMSGIN);
else
textin = (string) QueryProp(P_MSGIN);
}
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" );
}
// sonst: Magier sehen auch M_SILENT-Bewegungen, hier also nur an Magier
// ausgeben, alle anderen sehen eh nix.
else if ( interactive(ME) ) {
inv = (all_inventory(environment()) & users()) - ({this_object()});
inv = filter( inv, #'_is_learner/*'*/ );
if ( invis )
fn = "(" + capitalize(getuid(ME)) + ") taucht unsichtbar auf.\n";
else
fn = capitalize(getuid(ME)) + " schleicht leise herein.\n";
filter( inv, #'tell_object, fn );
}
// "Objekt" ueber das Move informieren.
NotifyMove(dest, oldenv, method);
// InitAttack() in NotifyMove() kann das Objekt zerstoert haben.
if (!objectp(ME))
return(ME_WAS_DESTRUCTED);
//scheint wohl geklappt zu haben.
return MOVE_OK;
}
public void TakeFollowers()
{
mixed *f,env;
int meth,i,r;
f=Query(P_PURSUERS);
if (!pointerp(f))
return;
env=environment();
if(object_name(env) == "/room/netztot") return;
foreach(object follower: f[1]-({0}) ) {
// die pruefung auf objectp ist nicht verrueckt, es kann theo. sein, dass
// Verfolger im PreventFollow() oder in ihrem move/init andere Verfolger
// zerstoeren.
if (objectp(follower) && environment(follower)!=env) {
//meth=M_NOCHECK;
meth=M_GO;
if (follower->Query(P_FOLLOW_SILENT))
meth|=M_SILENT|M_NO_SHOW;
catch(r=follower->PreventFollow(env);publish);
if (!r)
follower->move(env,meth);
else if (r==2)
RemovePursuer(follower);
}
}
}
varargs public int remove()
{ object team;
if (environment())
{
if ( objectp(team=Query(P_TEAM)) )
catch(team->RemoveMember(ME);publish);
environment()->NotifyRemove(ME);
}
destruct(ME);
return 1;
}