/*! \file move.cpp * \brief Routines for moving about. * */ #include "copyright.h" #include "autoconf.h" #include "config.h" #include "externs.h" /* --------------------------------------------------------------------------- * process_leave_loc: Generate messages and actions resulting from leaving a * place. */ static void process_leave_loc(dbref thing, dbref dest, dbref cause, bool canhear, int hush) { dbref loc = Location(thing); if ((loc == NOTHING) || (loc == dest)) { return; } if (dest == HOME) { dest = Home(thing); } if (Html(thing)) { notify_html(thing, T("")); } // Run the LEAVE attributes in the current room if we meet any of // following criteria: // // - The current room has wizard privs. // - Neither the current room nor the moving object are dark. // - The moving object can hear and does not hav wizard privs. // // EXCEPT if we were called with the HUSH_LEAVE key. // #ifdef REALITY_LVLS bool quiet = ( (hush & HUSH_LEAVE) || !IsReal(loc, thing) #else bool quiet = ( (hush & HUSH_LEAVE) #endif // REALITY_LVLS || ( !Wizard(loc) && ( Dark(thing) || Dark(loc)) && ( !canhear || ( Can_Dark(thing) && Dark(thing))))); int oattr = quiet ? 0 : A_OLEAVE; int aattr = A_ALEAVE; int pattr = (quiet || (!mudconf.terse_movemsg && Terse(thing))) ? 0 : A_LEAVE; did_it(thing, loc, pattr, nullptr, oattr, nullptr, aattr, 0, nullptr, 0); // Do OXENTER for receiving room // if ((dest != NOTHING) && !quiet) { did_it(thing, dest, 0, nullptr, A_OXENTER, nullptr, 0, 0, nullptr, 0); } // Display the 'has left' message if we meet any of the following // criteria: // // - Neither the current room nor the moving object are dark. // - The object can hear and is not a dark wizard. // if ( !quiet && !Blind(thing) && !Blind(loc)) { if ( ( !Dark(thing) && !Dark(loc)) || ( canhear && !(Can_Dark(thing) && Dark(thing)))) { #ifdef REALITY_LVLS notify_except2_rlevel(loc, thing, thing, cause, #else notify_except2(loc, thing, thing, cause, #endif // REALITY_LVLS tprintf(M_("%s has left."), Moniker(thing))); } } } /*--------------------------------------------------------------------------- * process_enter_loc: Generate messages and actions resulting from entering * a place. */ static void process_enter_loc(dbref thing, dbref src, dbref cause, bool canhear, int hush) { dbref loc = Location(thing); if ( loc == NOTHING || loc == src) { return; } show_vrml_url(thing, loc); // Run the ENTER attributes in the current room if we meet any of following // criteria: // // - The current room has wizard privs. // - Neither the current room nor the moving object are dark. // - The moving object can hear and does not have wizard privs. // // EXCEPT if we were called with the HUSH_ENTER key. // #ifdef REALITY_LVLS bool quiet = ( (hush & HUSH_ENTER) || !IsReal(loc, thing) #else bool quiet = ( (hush & HUSH_ENTER) #endif // REALITY_LVLS || ( !Wizard(loc) && ( Dark(thing) || Dark(loc)) && ( !canhear || ( Can_Dark(thing) && Dark(thing))))); int oattr = quiet ? 0 : A_OENTER; int aattr = A_AENTER; int pattr = (quiet || (!mudconf.terse_movemsg && Terse(thing))) ? 0 : A_ENTER; did_it(thing, loc, pattr, nullptr, oattr, nullptr, aattr, 0, nullptr, 0); // Do OXLEAVE for sending room. // if ( src != NOTHING && !quiet) { did_it(thing, src, 0, nullptr, A_OXLEAVE, nullptr, 0, 0, nullptr, 0); } // Display the 'has arrived' message if we meet all of the following // criteria: // // - The moving object can hear. // - The object is not a dark wizard. // if ( !quiet && canhear && !Blind(thing) && !Blind(loc) && !(Dark(thing) && Can_Dark(thing))) { #ifdef REALITY_LVLS notify_except2_rlevel(loc, thing, thing, cause, #else notify_except2(loc, thing, thing, cause, #endif // REALITY_LVLS tprintf(M_("%s has arrived."), Moniker(thing))); } } /* --------------------------------------------------------------------------- * move_object: Physically move an object from one place to another. * Does not generate any messages or actions. */ void move_object(dbref thing, dbref dest) { dbref src = Location(thing); // Remove from the source location // if (src != NOTHING) { s_Contents(src, remove_first(Contents(src), thing)); } // Special check for HOME // if (dest == HOME) { dest = Home(thing); } // Add to destination location // if (dest != NOTHING) { s_Contents(dest, insert_first(Contents(dest), thing)); } else { s_Next(thing, NOTHING); } s_Location(thing, dest); // Preload the destination synchronously so look_in() doesn't pay // cold misses, then defer BFS neighbors for later. // if (Good_obj(dest)) { bool bUnlimited = (mudconf.max_cache_size < 0); cache_preload_obj(dest, bUnlimited); if (bUnlimited && mudconf.cache_preload_depth > 0) { cache_preload_deferred_bfs(dest, mudconf.cache_preload_depth); } } // Look around and do the penny check // look_in(thing, dest, (LK_SHOWEXIT | LK_OBEYTERSE)); if ( isPlayer(thing) && mudconf.payfind > 0 && Pennies(thing) < mudconf.paylimit && !Controls(thing, dest) && RandomINT32(0, mudconf.payfind-1) == 0) { giveto(thing, 1); notify(thing, tprintf(M_("You found a %s!"), mudconf.one_coin)); } } // move_the_exit: Move an exit silently from its location to its destination // static void move_the_exit(dbref thing, dbref dest) { dbref exitloc = Exits(thing); s_Exits(exitloc, remove_first(Exits(exitloc), thing)); s_Exits(dest, insert_first(Exits(dest), thing)); s_Exits(thing, dest); } /* --------------------------------------------------------------------------- * send_dropto, process_sticky_dropto, process_dropped_dropto, * process_sacrifice_dropto: Check for and process droptos. */ // send_dropto: Send an object through the dropto of a room // static void send_dropto(dbref thing, dbref player) { if (!Sticky(thing)) { move_via_generic(thing, Dropto(Location(thing)), player, 0); } else { move_via_generic(thing, HOME, player, 0); } divest_object(thing); } // process_sticky_dropto: Call when an object leaves the room to see if // we should empty the room // static void process_sticky_dropto(dbref loc, dbref player) { // Do nothing if checking anything but a sticky room // if ( !Good_obj(loc) || !Has_dropto(loc) || !Sticky(loc)) { return; } // Make sure dropto loc is valid // dbref dropto = Dropto(loc); if ( NOTHING == dropto || dropto == loc) { return; } // Make sure no players hanging out // dbref thing; DOLIST(thing, Contents(loc)) { if ( Connected(Owner(thing)) && Hearer(thing)) { return; } } // Send everything through the dropto // dbref next; s_Contents(loc, reverse_list(Contents(loc))); SAFE_DOLIST(thing, next, Contents(loc)) { send_dropto(thing, player); } } // process_dropped_dropto: Check what to do when someone drops an object. // static void process_dropped_dropto(dbref thing, dbref player) { // If STICKY, send home // if (Sticky(thing)) { move_via_generic(thing, HOME, player, 0); divest_object(thing); return; } // Process the dropto if location is a room and is not STICKY // dbref loc = Location(thing); if (Has_dropto(loc) && (Dropto(loc) != NOTHING) && !Sticky(loc)) send_dropto(thing, player); } /* --------------------------------------------------------------------------- * move_via_generic: Generic move routine, generates standard messages and * actions. */ void move_via_generic(dbref thing, dbref dest, dbref cause, int hush) { if (dest == HOME) { dest = Home(thing); } dbref src = Location(thing); bool canhear = Hearer(thing); process_leave_loc(thing, dest, cause, canhear, hush); move_object(thing, dest); did_it(thing, thing, A_MOVE, nullptr, A_OMOVE, nullptr, A_AMOVE, 0, nullptr, 0); process_enter_loc(thing, src, cause, canhear, hush); } /* --------------------------------------------------------------------------- * move_via_exit: Exit move routine, generic + exit messages + dropto check. */ static void move_via_exit(dbref thing, dbref dest, dbref cause, dbref exit, int hush) { if (dest == HOME) { dest = Home(thing); } dbref src = Location(thing); bool canhear = Hearer(thing); // Dark wizards don't trigger OSUCC/ASUCC bool quiet = ( (Can_Dark(thing) && Dark(thing)) || (hush & HUSH_EXIT)); int aattr = quiet ? 0 : A_ASUCC; int oattr = quiet ? 0 : A_OSUCC; int pattr = (!mudconf.terse_movemsg && Terse(thing)) ? 0 : A_SUCC; did_it(thing, exit, pattr, nullptr, oattr, nullptr, aattr, 0, nullptr, 0); process_leave_loc(thing, dest, cause, canhear, hush); move_object(thing, dest); aattr = quiet ? 0 : A_ADROP; oattr = quiet ? 0 : A_ODROP; pattr = (!mudconf.terse_movemsg && Terse(thing)) ? 0 : A_DROP; did_it(thing, exit, pattr, nullptr, oattr, nullptr, aattr, 0, nullptr, 0); did_it(thing, thing, A_MOVE, nullptr, A_OMOVE, nullptr, A_AMOVE, 0, nullptr, 0); process_enter_loc(thing, src, cause, canhear, hush); process_sticky_dropto(src, thing); } /* --------------------------------------------------------------------------- * move_via_teleport: Teleport move routine, generic + teleport messages + * divestiture + dropto check. */ bool move_via_teleport(dbref thing, dbref dest, dbref cause, int hush) { dbref curr; int count; const UTF8 *failmsg; dbref src = Location(thing); if ( HOME != dest && Good_obj(src)) { curr = src; for (count = mudconf.ntfy_nest_lim; count > 0; count--) { if (!could_doit(thing, curr, A_LTELOUT)) { if ( thing == cause || NOTHING == cause) { failmsg = M_("You can’t teleport out!"); } else { failmsg = M_("You can’t be teleported out!"); notify_quiet(cause, M_("You can’t teleport that out!")); } did_it(thing, src, A_TOFAIL, failmsg, A_OTOFAIL, nullptr, A_ATOFAIL, 0, nullptr, 0); return false; } if (isRoom(curr)) { break; } curr = Location(curr); } } if (isExit(thing)) { move_the_exit(thing, dest); return true; } if (dest == HOME) { dest = Home(thing); } bool canhear = Hearer(thing); if (!(hush & HUSH_LEAVE)) { did_it(thing, thing, 0, nullptr, A_OXTPORT, nullptr, 0, 0, nullptr, 0); } process_leave_loc(thing, dest, NOTHING, canhear, hush); move_object(thing, dest); if (!(hush & HUSH_ENTER)) { did_it(thing, thing, A_TPORT, nullptr, A_OTPORT, nullptr, A_ATPORT, 0, nullptr, 0); } did_it(thing, thing, A_MOVE, nullptr, A_OMOVE, nullptr, A_AMOVE, 0, nullptr, 0); process_enter_loc(thing, src, NOTHING, canhear, hush); divest_object(thing); process_sticky_dropto(src, thing); return true; } /* --------------------------------------------------------------------------- * move_exit: Try to move a player through an exit. */ static dbref get_exit_dest(dbref executor, dbref exit) { dbref aowner; int aflags; UTF8 *atr_gotten = atr_pget(exit, A_EXITVARDEST, &aowner, &aflags); LBuf result = LBuf_Src("get_exit_dest"); UTF8 *ref = result.get(); mux_exec(atr_gotten, LBUF_SIZE-1, result, &ref, exit, executor, executor, AttrTrace(aflags, EV_TOP|EV_FCHECK|EV_EVAL), nullptr, 0); free_lbuf(atr_gotten); *ref = '\0'; dbref dest = NOTHING; if (*result == NUMBER_TOKEN) { dest = mux_atoi64(result + 1); } return dest; } void move_exit(dbref player, dbref exit, bool divest, const UTF8 *failmsg, int hush) { int oattr, aattr; bool bDoit = false; dbref loc = Location(exit); if (atr_get_raw(exit, A_EXITVARDEST) != nullptr) { loc = get_exit_dest(player, exit); } if (loc == HOME) { loc = Home(player); } #ifdef WOD_REALMS if (Good_obj(loc) && (REALM_DO_HIDDEN_FROM_YOU != DoThingToThingVisibility(player, exit, ACTION_IS_MOVING))) { if (isShroud(player)) { bDoit = true; int iShroudWarded = get_atr(T("SHROUD_WARDED")); if (iShroudWarded > 0) { int owner, flags; UTF8 *buff = atr_pget(exit, iShroudWarded, &owner, &flags); if (buff) { if (*buff) { bDoit = false; } free_lbuf(buff); } } } if (!bDoit && isUmbra(player)) { bDoit = true; int iUmbraWarded = get_atr(T("UMBRA_WARDED")); if (iUmbraWarded > 0) { int owner, flags; UTF8 *buff = atr_pget(exit, iUmbraWarded, &owner, &flags); if (buff) { if (*buff) { bDoit = false; } free_lbuf(buff); } } } if (!bDoit && could_doit(player, exit, A_LOCK)) { bDoit = true; } } #else #ifdef REALITY_LVLS if (Good_obj(loc) && IsReal(exit, player) && could_doit(player, exit, A_LOCK)) #else if (Good_obj(loc) && could_doit(player, exit, A_LOCK)) #endif { bDoit = true; } #endif if (bDoit) { switch (Typeof(loc)) { case TYPE_ROOM: move_via_exit(player, loc, NOTHING, exit, hush); if (divest) divest_object(player); break; case TYPE_PLAYER: case TYPE_THING: if (Going(loc)) { notify(player, M_("You can’t go that way.")); return; } move_via_exit(player, loc, NOTHING, exit, hush); divest_object(player); break; case TYPE_EXIT: notify(player, M_("You can’t go that way.")); return; } } else { if ((Can_Dark(player) && Dark(player)) || (hush & HUSH_EXIT)) { oattr = 0; aattr = 0; } else { oattr = A_OFAIL; aattr = A_AFAIL; } did_it(player, exit, A_FAIL, failmsg, oattr, nullptr, aattr, 0, nullptr, 0); } } /* --------------------------------------------------------------------------- * do_move: Move from one place to another via exits or 'home'. */ void do_move(dbref executor, dbref caller, dbref enactor, int eval, int key, UTF8 *direction, const UTF8 *cargs[], int ncargs) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); if (!string_compare(direction, T("home"))) { dbref loc = Location(executor); dbref dest = Home(executor); if ( NOTHING != loc && NOTHING != dest && loc == dest) { notify(executor, M_("You are already home.")); } else { // Go home w/o stuff. // if ( ( Fixed(executor) || Fixed(Owner(executor))) && !(WizRoy(executor))) { notify(executor, mudconf.fixed_home_msg); return; } if ( NOTHING != loc && !Blind(executor) && !Blind(loc) && !Dark(executor) && !Dark(loc)) { // Tell all // notify_except(loc, executor, executor, tprintf(M_("%s goes home."), Moniker(executor)), 0); } // Give the player the messages // for (int i = 0; i < 3; i++) { notify(executor, M_("There’s no place like home...")); } move_via_generic(executor, HOME, NOTHING, 0); divest_object(executor); process_sticky_dropto(loc, executor); } return; } // Find the exit. // init_match_check_keys(executor, direction, TYPE_EXIT); match_exit(); int quiet; dbref exit = match_result(); switch (exit) { case NOTHING: // Try to force the object notify(executor, M_("You can’t go that way.")); break; case AMBIGUOUS: notify(executor, M_("I don’t know which way you mean!")); break; default: quiet = 0; if ((key & MOVE_QUIET) && Controls(executor, exit)) quiet = HUSH_EXIT; move_exit(executor, exit, false, M_("You can’t go that way."), quiet); } } /* --------------------------------------------------------------------------- * do_get: Get an object. */ void do_get(dbref executor, dbref caller, dbref enactor, int eval, int key, UTF8 *what, const UTF8 *cargs[], int ncargs) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); dbref playerloc; if ( !Has_location(executor) || !Good_obj(playerloc = Location(executor))) { return; } // You can only pick up things in rooms and ENTER_OK objects/players. // if ( !isRoom(playerloc) && !Enter_ok(playerloc) && !Controls(executor, playerloc)) { notify(executor, NOPERM_MESSAGE); return; } // Look for the thing locally. // init_match_check_keys(executor, what, TYPE_THING); match_neighbor(); match_exit(); if (Long_Fingers(executor)) { match_absolute(); } dbref thing = match_result(); // Look for the thing in other people's inventories. // if (!Good_obj(thing)) { thing = match_status(executor, match_possessed(executor, executor, what, thing, true)); if (!Good_obj(thing)) { return; } } // If we found it, check to see if we can get it. // dbref thingloc = Location(thing); if (Good_obj(thingloc)) { if (!could_doit(executor, thingloc, A_LGET)) { notify(executor, NOPERM_MESSAGE); return; } } // If we can get it, get it. // const UTF8 *failmsg; int oattr, aattr; bool quiet = false; switch (Typeof(thing)) { case TYPE_PLAYER: case TYPE_THING: // You can't take what you already have. // if (thingloc == executor) { notify(executor, M_("You already have that!")); break; } if ( (key & GET_QUIET) && Controls(executor, thing)) { quiet = true; } if (thing == executor) { notify(executor, M_("You cannot get yourself!")); } else if (could_doit(executor, thing, A_LOCK)) { if (thingloc != playerloc) { notify(thingloc, tprintf(M_("%s was taken from you."), Moniker(thing))); } move_via_generic(thing, executor, executor, 0); notify(thing, M_("Taken.")); oattr = quiet ? 0 : A_OSUCC; aattr = quiet ? 0 : A_ASUCC; did_it(executor, thing, A_SUCC, M_("Taken."), oattr, nullptr, aattr, 0, nullptr, 0); } else { oattr = quiet ? 0 : A_OFAIL; aattr = quiet ? 0 : A_AFAIL; if (thingloc != playerloc) { failmsg = M_("You can’t take that from there."); } else { failmsg = M_("You can’t pick that up."); } did_it(executor, thing, A_FAIL, failmsg, oattr, nullptr, aattr, 0, nullptr, 0); } break; case TYPE_EXIT: // You can't take what you already have. // thingloc = Exits(thing); if (thingloc == executor) { notify(executor, M_("You already have that!")); break; } // You must control either the exit or the location. // if ( !Controls(executor, thing) && !Controls(executor, playerloc)) { notify(executor, NOPERM_MESSAGE); break; } // Do it. // s_Exits(thingloc, remove_first(Exits(thingloc), thing)); s_Exits(executor, insert_first(Exits(executor), thing)); s_Exits(thing, executor); if (!Quiet(executor)) { notify(executor, M_("Exit taken.")); } break; default: notify(executor, M_("You can’t take that!")); break; } } /* --------------------------------------------------------------------------- * do_drop: Drop an object. */ void do_drop(dbref executor, dbref caller, dbref enactor, int eval, int key, UTF8 *name, const UTF8 *cargs[], int ncargs) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); dbref loc = Location(executor); if (!Good_obj(loc)) { return; } dbref exitloc, thing; UTF8 *bp; int oattr, aattr; bool quiet; init_match(executor, name, TYPE_THING); match_possession(); match_carried_exit(); switch (thing = match_result()) { case NOTHING: notify(executor, M_("You don’t have that!")); return; case AMBIGUOUS: notify(executor, M_("I don’t know which you mean!")); return; } switch (Typeof(thing)) { case TYPE_THING: case TYPE_PLAYER: // You have to be carrying it. // if ( ( Location(thing) != executor && !Wizard(executor)) || !could_doit(executor, thing, A_LDROP)) { did_it(executor, thing, A_DFAIL, M_("You can’t drop that."), A_ODFAIL, nullptr, A_ADFAIL, 0, nullptr, 0); return; } // Move it // move_via_generic(thing, Location(executor), executor, 0); notify(thing, M_("Dropped.")); quiet = false; if ( (key & DROP_QUIET) && Controls(executor, thing)) { quiet = true; } { LBuf buf = LBuf_Src("do_drop.did_it"); bp = buf.get(); safe_tprintf_str(buf, &bp, M_("dropped %s."), Moniker(thing)); oattr = quiet ? 0 : A_ODROP; aattr = quiet ? 0 : A_ADROP; did_it(executor, thing, A_DROP, M_("Dropped."), oattr, buf, aattr, 0, nullptr, 0); } // Process droptos // process_dropped_dropto(thing, executor); break; case TYPE_EXIT: // You have to be carrying it. // if ( Exits(thing) != executor && !Wizard(executor)) { notify(executor, M_("You can’t drop that.")); return; } if (!Controls(executor, loc)) { notify(executor, NOPERM_MESSAGE); return; } // Do it. // exitloc = Exits(thing); s_Exits(exitloc, remove_first(Exits(exitloc), thing)); s_Exits(loc, insert_first(Exits(loc), thing)); s_Exits(thing, loc); if (!Quiet(executor)) { notify(executor, M_("Exit dropped.")); } break; default: notify(executor, M_("You can’t drop that.")); break; } } /* --------------------------------------------------------------------------- * do_enter, do_leave: The enter and leave commands. */ void do_enter_internal(dbref player, dbref thing, bool quiet) { int oattr, aattr; if (!Enter_ok(thing) && !Controls(player, thing)) { oattr = quiet ? 0 : A_OEFAIL; aattr = quiet ? 0 : A_AEFAIL; did_it(player, thing, A_EFAIL, NOPERM_MESSAGE, oattr, nullptr, aattr, 0, nullptr, 0); } else if (player == thing) { notify(player, M_("You can’t enter yourself!")); } else if (could_doit(player, thing, A_LENTER)) { dbref loc = Location(player); oattr = quiet ? HUSH_ENTER : 0; move_via_generic(player, thing, NOTHING, oattr); divest_object(player); process_sticky_dropto(loc, player); } else { oattr = quiet ? 0 : A_OEFAIL; aattr = quiet ? 0 : A_AEFAIL; did_it(player, thing, A_EFAIL, M_("You can’t enter that."), oattr, nullptr, aattr, 0, nullptr, 0); } } void do_enter(dbref executor, dbref caller, dbref enactor, int eval, int key, UTF8 *what, const UTF8 *cargs[], int ncargs) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); init_match(executor, what, TYPE_THING); match_neighbor(); if (Long_Fingers(executor)) match_absolute(); // the wizard has long fingers dbref thing = noisy_match_result(); bool bQuiet = false; if (thing == NOTHING) return; switch (Typeof(thing)) { case TYPE_PLAYER: case TYPE_THING: if ((key & MOVE_QUIET) && Controls(executor, thing)) bQuiet = true; do_enter_internal(executor, thing, bQuiet); break; default: notify(executor, NOPERM_MESSAGE); } return; } void do_leave(dbref executor, dbref caller, dbref enactor, int eval, int key) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); dbref loc = Location(executor); dbref newLoc = loc; if ( !Good_obj(loc) || Going(loc) || !Has_location(loc) || !Good_obj(newLoc = Location(loc)) || Going(newLoc)) { notify(executor, M_("You can’t leave.")); return; } int quiet = 0; if ( (key & MOVE_QUIET) && Controls(executor, loc)) { quiet = HUSH_LEAVE; } if (could_doit(executor, loc, A_LLEAVE)) { move_via_generic(executor, newLoc, NOTHING, quiet); } else { int oattr = quiet ? 0 : A_OLFAIL; int aattr = quiet ? 0 : A_ALFAIL; did_it(executor, loc, A_LFAIL, M_("You can’t leave."), oattr, nullptr, aattr, 0, nullptr, 0); } }