/*! \file create.cpp * \brief Commands that create new objects. * * This affects creation and destruction of all object types as well as their * initial and final relationships with other objects. */ #include "copyright.h" #include "autoconf.h" #include "config.h" #include "externs.h" #include "routing.h" // --------------------------------------------------------------------------- // parse_linkable_room: Get a location to link to. // static dbref parse_linkable_room(const dbref player, const UTF8 *room_name) { init_match(player, room_name, NOTYPE); match_everything(MAT_NO_EXITS | MAT_NUMERIC | MAT_HOME); const dbref room = match_result(); // HOME is always linkable // if (room == HOME) { return HOME; } // Make sure we can link to it // if (!Good_obj(room)) { notify_quiet(player, M_("That’s not a valid object.")); return NOTHING; } else if ( !Has_contents(room) || !Linkable(player, room)) { notify_quiet(player, M_("You can’t link to that.")); return NOTHING; } else { return room; } } // --------------------------------------------------------------------------- // open_exit, do_open: Open a new exit and optionally link it somewhere. // static void open_exit(const dbref player, dbref loc, UTF8 *direction, UTF8 *linkto) { if (!Good_obj(loc)) { return; } if ( nullptr == direction || '\0' == direction[0]) { notify_quiet(player, M_("Open where?")); return; } else if (!Controls(player, loc)) { if ( !( Open_ok(loc) && could_doit(player, loc, A_LOPEN))) { notify_quiet(player, NOPERM_MESSAGE); return; } } const dbref exit = create_obj(player, TYPE_EXIT, direction, 0); if (NOTHING == exit) { return; } // Initialize everything and link it in. // s_Exits(exit, loc); s_Next(exit, Exits(loc)); s_Exits(loc, exit); local_data_create(exit); ServerEventsSinkNode *p = g_pServerEventsSinkListHead; while (nullptr != p) { p->pSink->data_create(exit); p = p->pNext; } // and we're done // notify_quiet(player, M_("Opened.")); // See if we should do a link // if (!linkto || !*linkto) { return; } loc = parse_linkable_room(player, linkto); if (Good_obj(loc) || loc == HOME) { // Make sure the player passes the link lock // if ( !Link_Anywhere(player) && !could_doit(player, loc, A_LLINK)) { notify_quiet(player, M_("You can’t link to there.")); return; } // Link it if the player can pay for it // if (!payfor(player, mudconf.linkcost)) { notify_quiet(player, tprintf(M_("You don’t have enough %s to link."), mudconf.many_coins)); } else { s_Location(exit, loc); notify_quiet(player, M_("Linked.")); route_invalidate(); } } } void do_open(const dbref executor, const dbref caller, const dbref enactor, const int eval, const int key, UTF8 *direction, UTF8 *links[], int nlinks, const UTF8 *cargs[], const int ncargs) { UNUSED_PARAMETER(eval); UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); UTF8 *dest; // Create the exit and link to the destination, if there is one // if (nlinks >= 1) { dest = links[0]; } else { dest = nullptr; } dbref loc; if (key == OPEN_INVENTORY) { loc = executor; } else { loc = Location(executor); } open_exit(executor, loc, direction, dest); // Open the back link if we can. // if (nlinks >= 2) { const dbref destnum = parse_linkable_room(executor, dest); // #1188: HOME is a valid *link* destination, but not a real room to // open an exit from. open_exit() would bail on !Good_obj with no // message, leaving a silent one-way open. // if (destnum == HOME) { notify_quiet(executor, T("You can’t open a reverse exit at home.")); } else if (Good_obj(destnum)) { UTF8 buff[I32BUF_SIZE]; mux_ltoa(loc, buff); open_exit(executor, destnum, links[1], buff); } } } // --------------------------------------------------------------------------- // link_exit, do_link: Set destination(exits), dropto(rooms) or // home(player,thing) void link_exit(const dbref player, const dbref exit, const dbref dest) { // Make sure we can link there // if ( dest != HOME && !Link_Anywhere(player) && ( ( !Controls(player, dest) && !Link_ok(dest)) || !could_doit(player, dest, A_LLINK))) { notify_quiet(player, NOPERM_MESSAGE); return; } // Exit must be unlinked or controlled by you // if ( Location(exit) != NOTHING && !Controls(player, exit)) { notify_quiet(player, NOPERM_MESSAGE); return; } if (NoModify(exit) && !WizRoy(player)) { notify_quiet(player, NOPERM_MESSAGE); return; } // Handle costs // int cost = mudconf.linkcost; int quot = 0; if (Owner(exit) != Owner(player)) { cost += mudconf.opencost; quot += mudconf.exit_quota; } if (!canpayfees(player, player, cost, quot)) { return; } // Pay the owner for his loss. // if (Owner(exit) != Owner(player)) { giveto(Owner(exit), mudconf.opencost); add_quota(Owner(exit), quot); s_Owner(exit, Owner(player)); s_Flags(exit, FLAG_WORD1, (db[exit].fs.word[FLAG_WORD1] & ~(INHERIT | WIZARD)) | HALT); } // Link has been validated and paid for, do it and tell the player // s_Location(exit, dest); if (!Quiet(player)) { notify_quiet(player, M_("Linked.")); } route_invalidate(); } void do_link ( const dbref executor, const dbref caller, dbref enactor, const int eval, const int key, const int nargs, UTF8 *what, UTF8 *where, const UTF8 *cargs[], const int ncargs ) { UNUSED_PARAMETER(eval); UNUSED_PARAMETER(nargs); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); // Find the thing to link // init_match(executor, what, TYPE_EXIT); match_everything(0); const dbref thing = noisy_match_result(); if (thing == NOTHING) { return; } // Allow unlink if where is not specified // if (!where || !*where) { do_unlink(executor, caller, enactor, 0, key, what, nullptr, 0); return; } dbref room; UTF8 *buff; switch (Typeof(thing)) { case TYPE_EXIT: // Set destination // room = parse_linkable_room(executor, where); if (Good_obj(room) || room == HOME) { link_exit(executor, thing, room); } break; case TYPE_PLAYER: case TYPE_THING: // Set home. // if (!Controls(executor, thing)) { notify_quiet(executor, NOPERM_MESSAGE); break; } if (NoModify(thing) && !WizRoy(executor)) { notify_quiet(executor, NOPERM_MESSAGE); break; } init_match(executor, where, NOTYPE); match_everything(MAT_NO_EXITS); room = noisy_match_result(); if (!Good_obj(room)) { break; } if (!Has_contents(room)) { notify_quiet(executor, M_("Can’t link to an exit.")); break; } if ( !can_set_home(executor, thing, room) || !could_doit(executor, room, A_LLINK)) { notify_quiet(executor, NOPERM_MESSAGE); } else if (room == HOME) { notify_quiet(executor, M_("Can’t set home to home.")); } else { const dbref nHomeOrig = Home(thing); const dbref nHomeNew = room; s_Home(thing, nHomeNew); if (!Quiet(executor)) { // One msgid for one sentence. Assembling this from three // M_() fragments made the clause order untranslatable: a // translator cannot move "changed from" relative to the // subject, the trailing spaces are load-bearing and invisible // in a PO editor, and "%s(#%d) to " carries no context at all // on its own. Whole-sentence formats are the point of the // Phase 3 slices (#1419). // notify_quiet(executor, tprintf( M_("Home of %s(#%d) changed from %s(#%d) to %s(#%d)."), Name(thing), thing, Name(nHomeOrig), nHomeOrig, Name(nHomeNew), nHomeNew)); } } break; case TYPE_ROOM: // Set dropto. // if (!Controls(executor, thing)) { notify_quiet(executor, NOPERM_MESSAGE); break; } if (NoModify(thing) && !WizRoy(executor)) { notify_quiet(executor, NOPERM_MESSAGE); break; } room = parse_linkable_room(executor, where); if ( !Good_obj(room) && room != HOME) { break; } if ( room != HOME && !isRoom(room)) { notify_quiet(executor, M_("That is not a room!")); } else if ( room != HOME && !Link_Anywhere(executor) && ( ( !Controls(executor, room) && !Link_ok(room)) || !could_doit(executor, room, A_LLINK))) { notify_quiet(executor, NOPERM_MESSAGE); } else { const dbref nDroptoOrig = Dropto(thing); const dbref nDroptoNew = room; s_Dropto(thing, room); if (!Quiet(executor)) { // Same whole-sentence treatment as the home case above (#1419). // notify_quiet(executor, tprintf( M_("Dropto of %s(#%d) changed from %s(#%d) to %s(#%d)."), Name(thing), thing, Name(nDroptoOrig), nDroptoOrig, Name(nDroptoNew), nDroptoNew)); } } break; case TYPE_GARBAGE: notify_quiet(executor, NOPERM_MESSAGE); break; default: STARTLOG(LOG_BUGS, "BUG", "OTYPE"); buff = alloc_mbuf("do_link.LOG.badtype"); mux_sprintf(buff, MBUF_SIZE, T("Strange object type: object #%d = %d"), thing, Typeof(thing)); log_text(buff); free_mbuf(buff); ENDLOG; } } // --------------------------------------------------------------------------- // do_parent: Set an object's parent field. // void do_parent ( const dbref executor, const dbref caller, dbref enactor, const int eval, const int key, const int nargs, UTF8 *tname, UTF8 *pname, const UTF8 *cargs[], const int ncargs ) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(key); UNUSED_PARAMETER(nargs); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); // Get victim. // init_match(executor, tname, NOTYPE); match_everything(0); const dbref thing = noisy_match_result(); if (!Good_obj(thing)) { return; } // Make sure we can do it. // if ( Going(thing) || !Controls(executor, thing)) { notify_quiet(executor, NOPERM_MESSAGE); return; } if (NoModify(thing) && !WizRoy(executor)) { notify_quiet(executor, NOPERM_MESSAGE); return; } // Save the previous parent for @aparent handling. // dbref previous_parent = db[thing].parent; // Find out what the new parent is. // dbref parent = NOTHING; if ('\0' != pname[0]) { init_match(executor, pname, Typeof(thing)); match_everything(0); parent = noisy_match_result(); if (!Good_obj(parent)) { return; } // Make sure we have rights to set parent. // if (!Parentable(executor, parent)) { notify_quiet(executor, NOPERM_MESSAGE); return; } // Verify no recursive reference // int lev; int curr; ITER_PARENTS(parent, curr, lev) { if (curr == thing) { notify_quiet(executor, M_("You can’t have yourself as a parent!")); return; } } } s_Parent(thing, parent); // Now that parent is set, handle zero, one, or two @aparent notifications // as necessary. // if (parent != previous_parent) { route_invalidate(); // Setup the appropriate stack arguments. // UTF8 child[SBUF_SIZE]; const UTF8 removal[] = "1"; const UTF8 addition[] = "0"; UTF8 original_executor[SBUF_SIZE]; const UTF8 *xargs[3]; const int xnargs = 3; xargs[0] = child; xargs[2] = original_executor; dbref aowner; int aflags; // Notify previous parent of the removal. // if (Good_obj(previous_parent)) { UTF8* action = atr_get("do_parent.529", previous_parent, A_APARENT, &aowner, &aflags); if ( nullptr != action && '\0' != action[0]) { mux_sprintf(child, SBUF_SIZE, T("#%d"), thing); mux_sprintf(original_executor, SBUF_SIZE, T("#%d"), executor); xargs[1] = removal; did_it(previous_parent, previous_parent, 0, nullptr, 0, nullptr, A_APARENT, 0, xargs, xnargs); } free_lbuf(action); } // Notify new parent of addition. // if (Good_obj(parent)) { UTF8* action = atr_get("do_parent.550", parent, A_APARENT, &aowner, &aflags); if ( nullptr != action && '\0' != action[0]) { mux_sprintf(child, SBUF_SIZE, T("#%d"), thing); mux_sprintf(original_executor, SBUF_SIZE, T("#%d"), executor); xargs[1] = addition; did_it(parent, parent, 0, nullptr, 0, nullptr, A_APARENT, 0, xargs, xnargs); } free_lbuf(action); } } if ( !Quiet(thing) && !Quiet(executor)) { if (NOTHING == parent) { notify_quiet(executor, M_("Parent cleared.")); } else { notify_quiet(executor, M_("Parent set.")); } } } // --------------------------------------------------------------------------- // do_dig: Create a new room. // void do_dig(const dbref executor, const dbref caller, const dbref enactor, const int eval, const int key, UTF8 *name, UTF8 *args[], const int nargs, const UTF8 *cargs[], const int ncargs) { UNUSED_PARAMETER(eval); UNUSED_PARAMETER(caller); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); // we don't need to know player's location! hooray! // if (!name || !*name) { notify_quiet(executor, M_("Dig what?")); return; } const dbref room = create_obj(executor, TYPE_ROOM, name, 0); if (room == NOTHING) { return; } local_data_create(room); ServerEventsSinkNode *p = g_pServerEventsSinkListHead; while (nullptr != p) { p->pSink->data_create(room); p = p->pNext; } notify(executor, tprintf(M_("%s created as room #%d."), name, room)); UTF8 *buff = alloc_sbuf("do_dig"); if ( nargs >= 1 && args[0] && *args[0]) { mux_ltoa(room, buff); open_exit(executor, Location(executor), args[0], buff); } if ( nargs >= 2 && args[1] && *args[1]) { mux_ltoa(Location(executor), buff); open_exit(executor, room, args[1], buff); } free_sbuf(buff); if (key == DIG_TELEPORT) { (void)move_via_teleport(executor, room, enactor, 0); } } // --------------------------------------------------------------------------- // do_create: Make a new object. // void do_create ( const dbref executor, const dbref caller, dbref enactor, const int eval, const int key, const int nargs, UTF8 *name, UTF8 *coststr, const UTF8 *cargs[], const int ncargs ) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(key); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); int cost = 0; if (!name || !*name) { notify_quiet(executor, M_("Create what?")); return; } else if ( nargs == 2 && (cost = mux_atoi64(coststr)) < 0) { notify_quiet(executor, M_("You can’t create an object for less than nothing!")); return; } const dbref thing = create_obj(executor, TYPE_THING, name, cost); if (thing == NOTHING) { return; } move_via_generic(thing, executor, NOTHING, 0); s_Home(thing, new_home(executor)); if (!Quiet(executor)) { notify(executor, tprintf(M_("%s created as object #%d"), Name(thing), thing)); } local_data_create(thing); ServerEventsSinkNode *p = g_pServerEventsSinkListHead; while (nullptr != p) { p->pSink->data_create(thing); p = p->pNext; } } // --------------------------------------------------------------------------- // do_clone: Create a copy of an object. // void do_clone ( const dbref executor, const dbref caller, dbref enactor, const int eval, int key, int nargs, UTF8 *name, UTF8 *arg2, const UTF8 *cargs[], const int ncargs ) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(nargs); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); dbref loc; int cost; if ( (key & CLONE_INVENTORY) || !Has_location(executor)) { loc = executor; } else { loc = Location(executor); } if (!Good_obj(loc)) { return; } init_match(executor, name, NOTYPE); match_everything(0); dbref thing = noisy_match_result(); if ( NOTHING == thing || AMBIGUOUS == thing) { return; } // Let players clone things set VISUAL. It's easier than retyping // in all that data. // if (!Examinable(executor, thing)) { notify_quiet(executor, NOPERM_MESSAGE); return; } if (isPlayer(thing)) { notify_quiet(executor, M_("You cannot clone players!")); return; } // You can only make a parent link to what you control. // if ( !Controls(executor, thing) && !Parent_ok(thing) && (key & CLONE_FROM_PARENT)) { notify_quiet(executor, tprintf(M_("You don’t control %s, ignoring /parent."), Name(thing))); key &= ~CLONE_FROM_PARENT; } // Cloning an exit splices it into your current location's exit list, so // it requires control of that location -- regardless of /cost (with // /inventory, loc is the executor, which it always controls). The check // must run before the cost branches, not only on the non-/cost path. // if ( TYPE_EXIT == Typeof(thing) && !Controls(executor, loc)) { notify_quiet(executor, NOPERM_MESSAGE); return; } // Determine the cost of cloning // const dbref new_owner = (key & CLONE_PRESERVE) ? Owner(thing) : Owner(executor); if (key & CLONE_SET_COST) { cost = mux_atoi64(arg2); if (cost < mudconf.createmin) cost = mudconf.createmin; if (cost > mudconf.createmax) cost = mudconf.createmax; arg2 = nullptr; } else { cost = 1; switch (Typeof(thing)) { case TYPE_THING: cost = OBJECT_DEPOSIT((mudconf.clone_copy_cost) ? Pennies(thing) : 1); break; case TYPE_ROOM: cost = mudconf.digcost; break; case TYPE_EXIT: cost = mudconf.digcost; break; } } // Go make the clone object. // bool bValid = false; size_t nValidName; const UTF8 *pValidName = nullptr; switch (Typeof(thing)) { case TYPE_THING: pValidName = MakeCanonicalObjectName(arg2, &nValidName, &bValid, mudconf.thing_name_charset); break; case TYPE_ROOM: pValidName = MakeCanonicalObjectName(arg2, &nValidName, &bValid, mudconf.room_name_charset); break; case TYPE_EXIT: pValidName = MakeCanonicalExitName(arg2, &nValidName, &bValid); break; } const UTF8 *clone_name; if (bValid) { clone_name = pValidName; } else { clone_name = Name(thing); } const dbref clone = create_obj(new_owner, Typeof(thing), clone_name, cost); if (clone == NOTHING) { return; } // Copy in the new data. // if (key & CLONE_FROM_PARENT) { s_Parent(clone, thing); } else { atr_cpy(clone, thing, false); } // Reset the name, since we cleared the attributes. // s_Name(clone, clone_name); // Reset the pennies, since it looks like we stamped on that, too. // s_Pennies(clone, OBJECT_ENDOWMENT(cost)); FLAGSET clearflags; TranslateFlags_Clone(clearflags.word, executor, key); SetClearFlags(clone, clearflags.word, nullptr); // Tell creator about it // if (!Quiet(executor)) { if (arg2 && *arg2) { notify(executor, tprintf(M_("%s cloned as %s, new copy is object #%d."), Name(thing), arg2, clone)); } else { notify(executor, tprintf(M_("%s cloned, new copy is object #%d."), Name(thing), clone)); } } // Put the new thing in its new home. Break any dropto or link, then // try to re-establish it. // switch (Typeof(thing)) { case TYPE_THING: s_Home(clone, clone_home(executor, thing)); move_via_generic(clone, loc, executor, 0); break; case TYPE_ROOM: s_Dropto(clone, NOTHING); if (Dropto(thing) != NOTHING) { // #1181: @clone/preserve must not re-link via link_exit — // that path charges open cost and steals ownership to the // executor (wizard), clearing INHERIT|WIZARD and setting HALT. // if (key & CLONE_PRESERVE) { s_Dropto(clone, Dropto(thing)); route_invalidate(); } else { link_exit(executor, clone, Dropto(thing)); } } break; case TYPE_EXIT: s_Exits(loc, insert_first(Exits(loc), clone)); s_Exits(clone, loc); s_Location(clone, NOTHING); if (Location(thing) != NOTHING) { // #1181: same preserve ownership rule as rooms/dropto above. // if (key & CLONE_PRESERVE) { s_Location(clone, Location(thing)); route_invalidate(); } else { link_exit(executor, clone, Location(thing)); } } break; } // If same owner run ACLONE, else halt it. Also copy parent if we can. // if (new_owner == Owner(thing)) { if (!(key & CLONE_FROM_PARENT)) { s_Parent(clone, Parent(thing)); } did_it(executor, clone, 0, nullptr, 0, nullptr, A_ACLONE, 0, nullptr, 0); } else { if ( !(key & CLONE_FROM_PARENT) && (Controls(executor, thing) || Parent_ok(thing))) { s_Parent(clone, Parent(thing)); } s_Halted(clone); } local_data_clone(clone, thing); ServerEventsSinkNode *p = g_pServerEventsSinkListHead; while (nullptr != p) { p->pSink->data_clone(clone, thing); p = p->pNext; } } // --------------------------------------------------------------------------- // do_pcreate: Create new players and robots. // void do_pcreate ( const dbref executor, const dbref caller, const dbref enactor, const int eval, const int key, const int nargs, UTF8 *name, UTF8 *pass, const UTF8 *cargs[], int ncargs ) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(nargs); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); const UTF8 *pmsg; const bool isrobot = (key == PCRE_ROBOT); const dbref newplayer = create_player(name, pass, executor, isrobot, &pmsg); if (newplayer == NOTHING) { notify_quiet(executor, tprintf(M_("Failure creating ‘%s’. %s"), name, pmsg)); return; } AddToPublicChannel(newplayer); AddToPlayerChannels(newplayer); if (isrobot) { move_object(newplayer, Location(executor)); notify_quiet(executor, tprintf(M_("New robot ‘%s’ (#%d) created with password ‘%s’"), name, newplayer, pass)); notify_quiet(executor, M_("Your robot has arrived.")); STARTLOG(LOG_PCREATES, "CRE", "ROBOT"); log_name(newplayer); log_text(T(" created by ")); log_name(executor); ENDLOG; } else { move_object(newplayer, mudconf.start_room); notify_quiet(executor, tprintf(M_("New player ‘%s’ (#%d) created with password ‘%s’"), name, newplayer, pass)); STARTLOG(LOG_PCREATES | LOG_WIZARD, "WIZ", "PCREA"); log_name(newplayer); log_text(T(" created by ")); log_name(executor); ENDLOG; } } // --------------------------------------------------------------------------- // destroyable: Indicates if target of a @destroy is a 'special' object in // the database. // static bool destroyable(dbref victim) { if ( (victim == mudconf.default_home) || (victim == mudconf.start_home) || (victim == mudconf.start_room) || (victim == mudconf.master_room) || (victim == static_cast(0)) || (God(victim))) { return false; } return true; } // --------------------------------------------------------------------------- // can_destroy_player, do_destroy: Destroy things. // static bool can_destroy_player(dbref player, dbref victim) { if (!Wizard(player)) { notify_quiet(player, M_("Sorry, no suicide allowed.")); return false; } if (RealWizard(victim)) { notify_quiet(player, M_("Even you can’t do that!")); return false; } return true; } static void ProcessMasterRoomADestroy(const dbref destroyer, const dbref thing) { const UTF8 *xargs[2]; switch (Typeof(thing)) { case TYPE_ROOM: xargs[1] = T("ROOM"); break; case TYPE_EXIT: xargs[1] = T("EXIT"); break; case TYPE_PLAYER: xargs[1] = T("PLAYER"); break; case TYPE_THING: xargs[1] = T("THING"); break; default: xargs[1] = S_("#-1"); break; } dbref master_room_obj = -1; DOLIST(master_room_obj, Contents(mudconf.master_room)) { if (thing == master_room_obj) { break; } if (Controls(master_room_obj, thing)) { int aowner, aflags; UTF8* act = atr_pget(master_room_obj, A_ADESTROY, &aowner, &aflags); if ('\0' != act[0]) { const int nxargs = 2; CLinearTimeAbsolute lta; UTF8 buf[SBUF_SIZE]; mux_sprintf(buf, SBUF_SIZE, T("#%d"), thing); xargs[0] = buf; wait_que(master_room_obj, destroyer, destroyer, AttrTrace(aflags, 0), false, lta, NOTHING, 0, act, nxargs, (const UTF8 **) xargs, mudstate.global_regs); } free_lbuf(act); } } } void do_destroy(const dbref executor, const dbref caller, dbref enactor, const int eval, int key, UTF8 *what, const UTF8 *cargs[], const int ncargs) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); // You can destroy anything you control. // dbref thing = match_controlled_quiet(executor, what); // If you own a location, you can destroy its exits. // if ( thing == NOTHING && Controls(executor, Location(executor))) { init_match(executor, what, TYPE_EXIT); match_exit(); thing = last_match_result(); } // You may destroy DESTROY_OK things in your inventory. // if (thing == NOTHING) { init_match(executor, what, TYPE_THING); match_possession(); thing = last_match_result(); if ( thing != NOTHING && !(isThing(thing) && Destroy_ok(thing))) { thing = NOPERM; } } // Return an error if we didn't find anything to destroy. // if (match_status(executor, thing) == NOTHING) { return; } // Check SAFE and DESTROY_OK flags. // if ( Safe(thing, executor) && !(key & DEST_OVERRIDE) && !(isThing(thing) && Destroy_ok(thing))) { notify_quiet(executor, M_("Sorry, that object is protected. Use @destroy/override to destroy it.")); return; } // Check INDESTRUCTIBLE flag -- cannot be bypassed, even by Wizards. // if (Indestructible(thing)) { notify_quiet(executor, M_("That object is indestructible.")); return; } // Make sure we're not trying to destroy a special object. // if (!destroyable(thing)) { notify_quiet(executor, M_("You can’t destroy that!")); return; } // Make sure we can do it, on a type-specific basis. // if ( isPlayer(thing) && !can_destroy_player(executor, thing)) { return; } size_t nCased; UTF8 *pCased = mux_strlwr(object_types[Typeof(thing)].name, nCased); if (Going(thing)) { if (!mudconf.destroy_going_now) { notify_quiet(executor, tprintf(M_("No sense beating a dead %s."), pCased)); return; } key |= DEST_INSTANT; } // Check whether we should perform instant destruction. // const dbref ThingOwner = Owner(thing); bool bInstant = (key & DEST_INSTANT) || Destroy_ok(thing) || Destroy_ok(ThingOwner); if (!bInstant) { // Pre-destruction 'crumble' emits and one last possible showstopper. // switch (Typeof(thing)) { case TYPE_ROOM: notify_all(thing, executor, M_("The room shakes and begins to crumble.")); break; case TYPE_PLAYER: atr_add_raw(thing, A_DESTROYER, mux_ltoa_t(executor)); if (!atr_get_raw(thing, A_DESTROYER)) { // Not a likely situation, but the player has too many // attributes to remember it's destroyer, so we we need to // take care of this more immediately. // bInstant = true; notify(executor, M_("Player has a lot of attributes. Performing destruction immediately.")); break; } // FALL THROUGH case TYPE_EXIT: case TYPE_THING: notify(executor, tprintf(M_("The %s shakes and begins to crumble."), pCased)); break; default: notify(executor, M_("Weird object type cannot be destroyed.")); return; } if ( !bInstant && !Quiet(thing) && !Quiet(ThingOwner)) { notify_quiet(ThingOwner, tprintf(M_("You will be rewarded shortly for %s(#%d)."), Moniker(thing), thing)); } } // Imperative Destruction emits. // if (!Quiet(executor)) { if (Good_owner(ThingOwner)) { if (ThingOwner == Owner(executor)) { if (!Quiet(thing)) { notify(executor, tprintf(M_("Destroyed %s(#%d)."), Moniker(thing), thing)); } } else if (ThingOwner == thing) { notify(executor, tprintf(M_("Destroyed %s(#%d)."), Moniker(thing), thing)); } else { LBuf tname = LBuf_Src("destroy_obj"); mux_strncpy(tname, Moniker(ThingOwner), LBUF_SIZE-1); notify(executor, tprintf(M_("Destroyed %s’s %s(#%d)."), tname.get(), Moniker(thing), thing)); } } } ProcessMasterRoomADestroy(executor, thing); if (bInstant) { // Instant destruction by type. // switch (Typeof(thing)) { case TYPE_EXIT: destroy_exit(thing); break; case TYPE_PLAYER: destroy_player(executor, thing); break; case TYPE_ROOM: empty_obj(thing); destroy_obj(thing); break; case TYPE_THING: destroy_thing(thing); break; default: notify(executor, M_("Weird object type cannot be destroyed.")); return; } } s_Going(thing); route_invalidate(); }