tinymux/mux/modules/engine/create.cpp
Stephen Dennis bd86caaba0 nls: one msgid per sentence for the home/dropto change messages (#1419)
Folded in from review.  create.cpp built two sentences out of three M_()
fragments each:

    "Home of %s(#%d) changed from "  +  "%s(#%d) to "  +  "%s(#%d)."

Three catalogue entries for one sentence is worse than leaving it unmarked,
because it looks translated while making the sentence permanently
untranslatable:

  * clause order is frozen -- a translator cannot move "changed from"
    relative to the subject, and languages that invert from/to or put the
    verb last cannot express it at all;
  * the trailing spaces are load-bearing and invisible in a PO editor, so
    dropping one runs the sentence together with nothing to catch it;
  * "%s(#%d) to " is not a sentence in any language and gives the translator
    no context for what "to" joins;
  * "%s(#%d)." and "%s(#%d) to " were shared between the home and dropto
    sites, so one translation had to serve both.

Whole sentences are the point of the Phase 3 slices -- #1568's own summary
gives "makes whole sentences untranslatable as a unit" as the reason for
allowing formats at all.

Now one format each, six conversions, and the LBuf/safe_str assembly goes
away with them.

Output is unchanged; verified against a throwaway game:

    Home of Thing(#3) changed from Limbo(#0) to Limbo(#0).

test-nls 570 msgids clean (down from 572: three fragments became one, twice,
and two of the fragments were shared).  test-format 31744 assertions pass.
Catalogue rebuilt through `make -C mux/po mo`, which now carries msgfmt -c.
Suite 1553 passed / 0 failed / 319 of 319.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 09:28:27 -06:00

1359 lines
34 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*! \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_("Thats not a valid object."));
return NOTHING;
}
else if ( !Has_contents(room)
|| !Linkable(player, room))
{
notify_quiet(player, M_("You cant 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 cant link to there."));
return;
}
// Link it if the player can pay for it
//
if (!payfor(player, mudconf.linkcost))
{
notify_quiet(player,
tprintf(M_("You dont 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 cant 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_("Cant 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_("Cant 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 cant 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 cant 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 dont 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<dbref>(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 cant 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 cant 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 %ss %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();
}