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