blob: 78a1cf5f00fee7744254bf6948b83ca383b8396b [file] [log] [blame]
MG Mud User88f12472016-06-24 23:31:02 +02001// MorgenGrauen MUDlib
2//
3// living/moving.c -- moving of living objects
4//
5// $Id: moving.c 9448 2016-01-22 17:52:28Z Zesstra $
Vanion50652322020-03-10 21:13:25 +01006#pragma strict_types
MG Mud User88f12472016-06-24 23:31:02 +02007#pragma save_types
8#pragma range_check
9#pragma no_clone
MG Mud User88f12472016-06-24 23:31:02 +020010
11inherit "/std/thing/moving";
12
13#define NEED_PROTOTYPES
14#include <hook.h>
15#include <living/moving.h>
16#include <living/skills.h>
17#include <thing/properties.h>
18#include <thing/description.h>
19#include <moving.h>
20#include <new_skills.h>
21#include <living.h>
22
23#undef NEED_PROTOTYPES
24
25#include <config.h>
26#include <properties.h>
27#include <language.h>
28#include <wizlevels.h>
29#include <defines.h>
30
31
32protected void create()
33{
34 if (object_name(this_object()) == __FILE__[0..<3])
35 {
36 return;
37 }
38 offerHook(H_HOOK_MOVE,1);
39}
40
41public void AddPursuer(object ob)
42{
43 mixed *pur;
44
45 if (!objectp(ob))
46 return;
47
48 if (!pointerp(pur=Query(P_PURSUERS)))
49 pur=({0,({})});
50 else if (member(pur[1],ob)!=-1)
51 return;
52
53 SetProp(P_PURSUERS,({ pur[0], pur[1]+({ob})-({0}) }));
54 ob->_SetPursued(ME);
55}
56
57public void RemovePursuer(object ob)
58{
59 mixed *pur;
60
61 if (pointerp(pur=Query(P_PURSUERS,F_VALUE))
62 && member(pur[1],ob)!=-1)
63 {
64 pur[1]-=({ob,0});
65 if (ob)
66 ob->_RemovePursued(ME);
67 if (!pur[0]&&!sizeof(pur[1]))
68 pur=0;
69 SetProp(P_PURSUERS,pur);
70 }
71}
72
73public void _SetPursued(object ob)
74{
75 mixed *pur;
76
77 if (!pointerp(pur=Query(P_PURSUERS)))
78 pur=({0,({})});
79 else
80 if (objectp(pur[0]))
81 pur[0]->RemovePursuer(ME);
82 pur[0]=ob;
83 pur[1]-=({0});
84 Set(P_PURSUERS,pur);
85}
86
87public void _RemovePursued(object ob)
88{
89 mixed *pur;
90
91 if (!pointerp(pur=Query(P_PURSUERS)) || pur[0]!=ob)
92 return;
93 pur[0]=0;
94 pur[1]-=({0});
95 if (!sizeof(pur[1]))
96 pur=0;
97 Set(P_PURSUERS,pur);
98}
99
100
101private void kampfende( object en ) {
102 if (!objectp(en)) return;
103 tell_object( ME, capitalize(en->name()) +
104 " ist jetzt hinter Dir her.\n" );
105 tell_object( en, "Du verfolgst jetzt " + name(WEN) + ".\n" );
106 en->InsertSingleEnemy(ME);
107}
108
109private int _is_learner(object pl) {
110 return IS_LEARNER(pl);
111}
112
113
114// a) Pruefungen, ob das move erlaubt ist.
115// b) zum Ueberschreiben
116protected int PreventMove(object dest, object oldenv, int method) {
117
118 // M_NOCHECK? -> Bewegung eh erlaubt (und Rueckgabewert wuerde ignoriert),
119 // aber PreventInsert/PreventLeave() rufen und ignorieren.
120 if ((method&M_NOCHECK)) {
121 // erst PreventLeaveLiving() rufen...
122 if(environment())
123 environment()->PreventLeaveLiving(this_object(), dest);
124 // dann PreventInsertLiving() im Ziel-Env.
125 dest->PreventInsertLiving(this_object());
126 // und raus...
127 return(0);
128 }
129
130 // bei Lebewesen muss die Bewegungsmethode M_GO und M_TPORT sein. Dies ist
131 // gleichzeigt die Restriktion gegen das Nehmen von Lebewesen, da dort
132 // M_GET/M_GIVE/M_PUT etc. verwendet wuerde. Bei M_GO und M_TPORT findet
133 // keine Pruefung statt, ob das Objekt ins Ziel 'reinpasst' (Gewicht, Anzahl
134 // Objekte usw.).
135 // Ich finde es etwas merkwuerdig gebaut (Zesstra).
136 if ( !(method & (M_GO | M_TPORT)) )
137 return ME_PLAYER;
138
139 // alte und neue Umgebung auf NO_TPORT pruefen.
140 if ( (method & M_TPORT) ) {
141 if ( environment() &&
142 (environment()->QueryProp(P_NO_TPORT) & (NO_TPORT_OUT|NO_TPORT)) )
143 return ME_CANT_TPORT_OUT;
144 else if ( dest->QueryProp(P_NO_TPORT) & (NO_TPORT_IN|NO_TPORT) )
145 return ME_CANT_TPORT_IN;
146 }
147
148 // erst PreventLeaveLiving() testen...
149 if( environment() && environment()->PreventLeaveLiving(this_object(), dest))
150 return ME_CANT_LEAVE_ENV;
151 // dann PreventInsertLiving() im Ziel-Env
152 if (dest->PreventInsertLiving(this_object()))
153 return ME_CANT_BE_INSERTED;
154
155 return 0;
156}
157
158// Krams nach dem Move machen und nebenbei zum Ueberschreiben.
159protected void NotifyMove(object dest, object oldenv, int method) {
160 mixed res;
161 object enem;
162
163 // Begruessungsschlag fuer die Gegener
164 if ( !(method & M_NO_ATTACK) )
165 InitAttack();
166
167 if (!objectp(ME)) return;
168
169 // Verfolger nachholen.
170 if ( pointerp(res = Query(P_PURSUERS)) && sizeof(res[1]) ) {
171 while ( remove_call_out( "TakeFollowers" ) >= 0 );
172
173 call_out( "TakeFollowers", 0 );
174 }
175
176 // und noch das Team nachholen.
177 if ( oldenv != dest
178 && objectp(ME)
179 && QueryProp(P_TEAM_AUTOFOLLOW)
180 && objectp( enem = IsTeamLeader() ) )
181 enem->StartFollow(oldenv); // Teamverfolgung
182
183}
184
185varargs public int move( object|string dest, int method, string direction,
186 string textout, string textin )
187{
188 int para, nightvis, invis, tmp;
189 object oldenv, *inv;
190 string fn,vc;
191 mixed res;
192 mixed hookData, hookRes;
193
194 if (!objectp(dest) && !stringp(dest))
195 raise_error(sprintf("Wrong argument 1 to move(). 'dest' must be a "
196 "string or object! Argument was: %.100O\n",
197 dest));
198
199 // altes Env erstmal merken.
200 oldenv = environment();
201
202 //erstmal den richtigen Zielraum suchen, bevor irgendwelche Checks gemacht
203 //werden...
204 // Ist der Spieler in einer Parallelwelt?
205 if ( (para = QueryProp(P_PARA)) && intp(para) ) {
206 fn = objectp(dest) ? object_name(dest) : dest;
207
208 // Falls der Zielraum nicht schon explizit in der Parallelwelt ist,
209 // neuen Zielraum suchen. Aber nur, wenn fn kein # enthaelt (also kein
210 // Clone ist), sonst wuerde eine Bewegung nach raum#42^para versucht,
211 // was dann buggt. ;-) Problem wird offenbar, wenn ein Para-Lebewesen
212 // im create() eines VC-Raums in Para in den Raum bewegt wird, da
213 // dieser dann noch nicht vom Driver umbenannt wurde und raum#42
214 // heisst.
215 if ( !sizeof(regexp( ({ fn }), "\\^[1-9][0-9]*$" )) &&
216 strrstr(fn,"#")==-1 )
217 {
218 fn += "^" + para;
219
220 // Der Parallelwelt-Raum muss existieren und fuer Spieler
221 // freigegeben sein, damit er zum neuen Ziel wird. Ansonsten
222 // duerfen nur NPCs, Testspieler und Magier herein.
223 if ( (find_object(fn)
224 || ((file_size(fn+".c")>0 ||
225 (file_size(vc=implode(explode(fn,"/")[0..<2],"/")+
226 "/virtual_compiler.c")>0 &&
Vanion50652322020-03-10 21:13:25 +0100227 !catch(tmp=({int})call_other(vc,"QueryValidObject",fn);
MG Mud User88f12472016-06-24 23:31:02 +0200228 publish) && tmp>0)) &&
229 !catch(load_object(fn);publish) )) &&
230 (!interactive(ME) || !fn->QueryProp(P_NO_PLAYERS) ||
231 (method & M_NOCHECK) || IS_LEARNER(ME) ||
232 (stringp(res = QueryProp(P_TESTPLAYER)) &&
233 IS_LEARNER( lower_case(res) ))) )
234 {
235 dest = fn;
236 }
237 else
238 {
239 // Wir bleiben in der Normalwelt.
240 para = 0;
241 }
242 }
243 }
244
245 // jetzt erstmal Hooks abpruefen, da sie ggf. die Daten aendern.
246 // alten P_TMP_MOVE_HOOK pruefen.
247 if ( res = QueryProp(P_TMP_MOVE_HOOK) ){
248 if ( pointerp(res) && sizeof(res) >= 3
249 && intp(res[0]) && time()<res[0]
250 && objectp(res[1]) && stringp(res[2]) ){
251 if ( res = call_other( res[1], res[2], dest, method, direction,
252 textout, textin ) ){
253 if ( pointerp(res) && sizeof(res) == 5 ){
254 dest = res[0];
255 method = res[1];
256 direction = res[2];
257 textout = res[3];
258 textin = res[4];
259 }
260 else if ( intp(res) && res == -1 )
261 return ME_CANT_LEAVE_ENV;
262 }
263 } else
264 SetProp( P_TMP_MOVE_HOOK, 0 );
265 }
266 // move hook nach neuem Hooksystem triggern.
267 hookData=({dest,method,direction,textout,textin});
268 hookRes=HookFlow(H_HOOK_MOVE,hookData);
269 if(hookRes && pointerp(hookRes) && sizeof(hookRes)>H_RETDATA) {
270 if(hookRes[H_RETCODE]==H_CANCELLED) {
271 return ME_CANT_LEAVE_ENV;
272 }
273 else if(hookRes[H_RETCODE]==H_ALTERED && hookRes[H_RETDATA] &&
274 pointerp(hookRes[H_RETDATA]) && sizeof(hookRes[H_RETDATA])>=5 ){
275 dest = hookRes[H_RETDATA][0];
276 method = hookRes[H_RETDATA][1];
277 direction = hookRes[H_RETDATA][2];
278 textout = hookRes[H_RETDATA][3];
279 textin = hookRes[H_RETDATA][4];
280 }
281 }
282
283 // dest auf Object normieren
284 if (stringp(dest)) dest=load_object(dest);
285
286 // jetzt Checks durchfuehren, ob das Move durchgefuehrt werden darf.
287 if (tmp=PreventMove(dest, oldenv, method)) {
288 // auf gueltigen Fehler pruefen, wer weiss, was Magier da evtl.
289 // versehentlich zurueckgeben.
290 if (VALID_MOVE_ERROR(tmp))
291 return(tmp);
292 else
293 return(ME_DONT_WANT_TO_BE_MOVED);
294 }
295
296 if ( invis = QueryProp(P_INVIS) )
297 method |= M_SILENT;
298
299 if ( objectp(oldenv) ) {
300 if ( !(method & M_SILENT) ) {
301 string *mout;
302 if ( !textout ){
303 if ( method & M_TPORT )
Vanion50652322020-03-10 21:13:25 +0100304 textout = ({string}) QueryProp(P_MMSGOUT) ||
305 ({string}) QueryProp(P_MSGOUT);
MG Mud User88f12472016-06-24 23:31:02 +0200306 else
Vanion50652322020-03-10 21:13:25 +0100307 textout = (mout = explode( ({string})
MG Mud User88f12472016-06-24 23:31:02 +0200308 QueryProp(P_MSGOUT) || "",
309 "#" ))[0]
Vanion50652322020-03-10 21:13:25 +0100310 || ({string})QueryProp(P_MMSGOUT);
MG Mud User88f12472016-06-24 23:31:02 +0200311 }
312
313 if ( !sizeof(direction) )
314 direction = 0;
315
316 inv = all_inventory(environment()) - ({ this_object() });
317 inv = filter( inv, #'living/*'*/ );
318 inv -= filter_objects( inv, "CannotSee", 1 );
319
320 filter( inv, #'tell_object/*'*/,
321 Name( WER, 2 ) + " " + textout +
322 (direction ? " " + direction : "") +
323 (sizeof(mout) > 1 ? mout[1] : "") + ".\n" );
324 }
325 // Magier sehen auch Bewegungen, die M_SILENT sind
326 else if ( interactive(ME) ){
327 inv = (all_inventory(environment()) & users())
328 - ({ this_object() });
329 inv = filter( inv, #'_is_learner/*'*/ );
330
331 if ( invis )
332 fn = "(" + capitalize(getuid(ME)) + ") verschwindet "
333 "unsichtbar.\n";
334 else
335 fn = capitalize(getuid(ME)) + " verschwindet ganz leise.\n";
336
337 filter( inv, #'tell_object/*'*/, fn );
338 }
339
340 // Nackenschlag beim Fluechten:
341 if ( !(method & M_NO_ATTACK) && objectp(ME) )
342 ExitAttack();
343 //falls nach ExitAttack() das Living nicht mehr existiert, muss das
344 //move() auch nicht mehr fortgesetzt werden. Weiter unten gibt es auch
345 //min. eine Stelle, die nicht prueft und ggf. buggt. Daher erfolgt
346 //hier ggf. ein Abbruch. 15.11.06 Zesstra
347 if (!objectp(ME)) return(ME_WAS_DESTRUCTED);
348
349 // Nackenschlag kann ME in den Todesraum bewegt haben...
350 if ( oldenv == environment() ) {
351 // Fuer alle anwesenden gegner kampfende() aufrufen
352 filter((QueryEnemies()[0] & all_inventory(oldenv))-({0}),
353 #'kampfende);
354 // Bugs im exit() sind ohne catch() einfach mist.
355 catch(environment()->exit(ME, dest);publish);
356 }
357 }
358
359 // irgendwas kann das Objekt zerstoert haben, z.B. env->exit().
360 if (!objectp(ME)) return(ME_WAS_DESTRUCTED);
361
362 if ( oldenv != environment() )
363 // Der Nackenschlag oder exit() koennen einen schon bewegt haben.
364 // Und wenn es in den Todesraum ist. ;^)
365 return MOVE_OK;
366
367 SetProp( P_PREPARED_SPELL, 0 ); // Spruchvorbereitung abgebrochen
368 SetProp( P_LAST_MOVE, time() ); // Zeitpunkt der letzten Bewegung
369
370 move_object(ME, dest);
371 if (!objectp(ME))
372 return(ME_WAS_DESTRUCTED);
373
374 dest = environment();
375
376 nightvis = UseSkill(SK_NIGHTVISION);
377 // Meldungen an nicht-Blinde ausgeben, falls keine 'stille' Bewegung
378 if ( !(method & M_SILENT) ) {
379 if ( !textin ) {
380 if ( method & M_TPORT )
Vanion50652322020-03-10 21:13:25 +0100381 textin = ({string}) QueryProp(P_MMSGIN);
MG Mud User88f12472016-06-24 23:31:02 +0200382 else
Vanion50652322020-03-10 21:13:25 +0100383 textin = ({string}) QueryProp(P_MSGIN);
MG Mud User88f12472016-06-24 23:31:02 +0200384 }
385
386 inv = all_inventory(environment()) - ({ this_object() });
387 inv = filter( inv, #'living/*'*/ );
388 inv -= filter_objects( inv, "CannotSee", 1 );
389 filter( inv, #'tell_object/*'*/,
390 capitalize(name( WER, 0 )) + " " + textin + ".\n" );
391 }
392 // sonst: Magier sehen auch M_SILENT-Bewegungen, hier also nur an Magier
393 // ausgeben, alle anderen sehen eh nix.
394 else if ( interactive(ME) ) {
395 inv = (all_inventory(environment()) & users()) - ({this_object()});
396 inv = filter( inv, #'_is_learner/*'*/ );
397 if ( invis )
398 fn = "(" + capitalize(getuid(ME)) + ") taucht unsichtbar auf.\n";
399 else
400 fn = capitalize(getuid(ME)) + " schleicht leise herein.\n";
401 filter( inv, #'tell_object, fn );
402 }
403
404 // "Objekt" ueber das Move informieren.
405 NotifyMove(dest, oldenv, method);
406
407 // InitAttack() in NotifyMove() kann das Objekt zerstoert haben.
408 if (!objectp(ME))
409 return(ME_WAS_DESTRUCTED);
410
411 //scheint wohl geklappt zu haben.
412 return MOVE_OK;
413}
414
415public void TakeFollowers()
416{
417 mixed *f,env;
418 int meth,i,r;
419
420 f=Query(P_PURSUERS);
421 if (!pointerp(f))
422 return;
423 env=environment();
424 if(object_name(env) == "/room/netztot") return;
425 foreach(object follower: f[1]-({0}) ) {
426 // die pruefung auf objectp ist nicht verrueckt, es kann theo. sein, dass
427 // Verfolger im PreventFollow() oder in ihrem move/init andere Verfolger
428 // zerstoeren.
429 if (objectp(follower) && environment(follower)!=env) {
430 //meth=M_NOCHECK;
431 meth=M_GO;
432 if (follower->Query(P_FOLLOW_SILENT))
433 meth|=M_SILENT|M_NO_SHOW;
434 catch(r=follower->PreventFollow(env);publish);
435 if (!r)
436 follower->move(env,meth);
437 else if (r==2)
438 RemovePursuer(follower);
439 }
440 }
441}
442
443varargs public int remove()
444{ object team;
445
446 if (environment())
447 {
448 if ( objectp(team=Query(P_TEAM)) )
449 catch(team->RemoveMember(ME);publish);
450
451 environment()->NotifyRemove(ME);
452 }
453 destruct(ME);
454 return 1;
455}
456