2006-11-17 06:16:43 +00:00
|
|
|
|
/*! \file create.cpp
|
2007-01-03 03:11:53 +00:00
|
|
|
|
* \brief Commands that create new objects.
|
2006-11-17 06:16:43 +00:00
|
|
|
|
*
|
|
|
|
|
|
* This affects creation and destruction of all object types as well as their
|
|
|
|
|
|
* initial and final relationships with other objects.
|
|
|
|
|
|
*/
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
|
|
|
|
|
#include "copyright.h"
|
|
|
|
|
|
#include "autoconf.h"
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
|
#include "externs.h"
|
Implement Phase 1 routing: static unconditional next-hop tables
Add route() softcode function with BFS-based shortest-path routing
over rooms marked NAVIGABLE. The routing table stores only the next-hop
exit for each (source, dest) pair, compressed via diagonal elimination,
adjacent marking, and row redundancy. Lazy rebuild on generation-counter
mismatch triggered by topology changes (@dig, @destroy, @link, @open,
@unlink) and NAVIGABLE flag changes. SQLite schema v10 adds route_nodes,
route_table, route_meta tables for future persistence phases.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 03:18:37 -06:00
|
|
|
|
#include "routing.h"
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// parse_linkable_room: Get a location to link to.
|
|
|
|
|
|
//
|
2022-03-14 16:08:21 -06:00
|
|
|
|
static dbref parse_linkable_room(const dbref player, const UTF8 *room_name)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
|
|
|
|
|
init_match(player, room_name, NOTYPE);
|
|
|
|
|
|
match_everything(MAT_NO_EXITS | MAT_NUMERIC | MAT_HOME);
|
2022-03-14 16:08:21 -06:00
|
|
|
|
const dbref room = match_result();
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
|
|
|
|
|
// HOME is always linkable
|
|
|
|
|
|
//
|
|
|
|
|
|
if (room == HOME)
|
|
|
|
|
|
{
|
|
|
|
|
|
return HOME;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Make sure we can link to it
|
|
|
|
|
|
//
|
|
|
|
|
|
if (!Good_obj(room))
|
|
|
|
|
|
{
|
2026-07-26 21:52:48 -06:00
|
|
|
|
notify_quiet(player, M_("That’s not a valid object."));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
return NOTHING;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if ( !Has_contents(room)
|
|
|
|
|
|
|| !Linkable(player, room))
|
|
|
|
|
|
{
|
2026-07-26 21:52:48 -06:00
|
|
|
|
notify_quiet(player, M_("You can’t link to that."));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
return NOTHING;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
return room;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// open_exit, do_open: Open a new exit and optionally link it somewhere.
|
|
|
|
|
|
//
|
2022-03-14 16:08:21 -06:00
|
|
|
|
static void open_exit(const dbref player, dbref loc, UTF8 *direction, UTF8 *linkto)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
|
|
|
|
|
if (!Good_obj(loc))
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2018-10-03 17:54:51 +00:00
|
|
|
|
if ( nullptr == direction
|
2008-05-31 17:23:15 +00:00
|
|
|
|
|| '\0' == direction[0])
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
2026-07-27 00:47:16 +00:00
|
|
|
|
notify_quiet(player, M_("Open where?"));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (!Controls(player, loc))
|
|
|
|
|
|
{
|
2008-05-31 17:23:15 +00:00
|
|
|
|
if ( !( Open_ok(loc)
|
|
|
|
|
|
&& could_doit(player, loc, A_LOPEN)))
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
|
|
|
|
|
notify_quiet(player, NOPERM_MESSAGE);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2008-05-31 17:23:15 +00:00
|
|
|
|
|
2022-03-14 16:08:21 -06:00
|
|
|
|
const dbref exit = create_obj(player, TYPE_EXIT, direction, 0);
|
2008-05-31 17:23:15 +00:00
|
|
|
|
if (NOTHING == exit)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Initialize everything and link it in.
|
|
|
|
|
|
//
|
|
|
|
|
|
s_Exits(exit, loc);
|
|
|
|
|
|
s_Next(exit, Exits(loc));
|
|
|
|
|
|
s_Exits(loc, exit);
|
|
|
|
|
|
local_data_create(exit);
|
2007-09-01 16:44:47 -07:00
|
|
|
|
ServerEventsSinkNode *p = g_pServerEventsSinkListHead;
|
2018-10-03 17:54:51 +00:00
|
|
|
|
while (nullptr != p)
|
2007-09-01 16:44:47 -07:00
|
|
|
|
{
|
|
|
|
|
|
p->pSink->data_create(exit);
|
|
|
|
|
|
p = p->pNext;
|
|
|
|
|
|
}
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
|
|
|
|
|
// and we're done
|
|
|
|
|
|
//
|
2026-07-27 00:47:16 +00:00
|
|
|
|
notify_quiet(player, M_("Opened."));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
//
|
2026-03-03 21:36:21 -07:00
|
|
|
|
if ( !Link_Anywhere(player)
|
|
|
|
|
|
&& !could_doit(player, loc, A_LLINK))
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
2026-07-26 21:52:48 -06:00
|
|
|
|
notify_quiet(player, M_("You can’t link to there."));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Link it if the player can pay for it
|
|
|
|
|
|
//
|
|
|
|
|
|
if (!payfor(player, mudconf.linkcost))
|
|
|
|
|
|
{
|
|
|
|
|
|
notify_quiet(player,
|
2026-07-27 15:04:54 +00:00
|
|
|
|
tprintf(M_("You don’t have enough %s to link."),
|
2006-09-01 22:59:23 +00:00
|
|
|
|
mudconf.many_coins));
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
s_Location(exit, loc);
|
2026-07-27 00:47:16 +00:00
|
|
|
|
notify_quiet(player, M_("Linked."));
|
Implement Phase 1 routing: static unconditional next-hop tables
Add route() softcode function with BFS-based shortest-path routing
over rooms marked NAVIGABLE. The routing table stores only the next-hop
exit for each (source, dest) pair, compressed via diagonal elimination,
adjacent marking, and row redundancy. Lazy rebuild on generation-counter
mismatch triggered by topology changes (@dig, @destroy, @link, @open,
@unlink) and NAVIGABLE flag changes. SQLite schema v10 adds route_nodes,
route_table, route_meta tables for future persistence phases.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 03:18:37 -06:00
|
|
|
|
route_invalidate();
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-03-14 16:08:21 -06:00
|
|
|
|
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)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
2006-12-27 17:53:42 +00:00
|
|
|
|
UNUSED_PARAMETER(eval);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
UNUSED_PARAMETER(caller);
|
|
|
|
|
|
UNUSED_PARAMETER(enactor);
|
2007-09-02 22:02:33 -07:00
|
|
|
|
UNUSED_PARAMETER(cargs);
|
|
|
|
|
|
UNUSED_PARAMETER(ncargs);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
2007-03-14 00:55:08 +00:00
|
|
|
|
UTF8 *dest;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
|
|
|
|
|
// Create the exit and link to the destination, if there is one
|
|
|
|
|
|
//
|
|
|
|
|
|
if (nlinks >= 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
dest = links[0];
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2018-10-03 17:54:51 +00:00
|
|
|
|
dest = nullptr;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
{
|
2022-03-14 16:08:21 -06:00
|
|
|
|
const dbref destnum = parse_linkable_room(executor, dest);
|
2026-07-25 11:09:32 -06:00
|
|
|
|
// #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,
|
2026-07-26 21:52:48 -06:00
|
|
|
|
T("You can’t open a reverse exit at home."));
|
2026-07-25 11:09:32 -06:00
|
|
|
|
}
|
|
|
|
|
|
else if (Good_obj(destnum))
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
2007-03-14 00:55:08 +00:00
|
|
|
|
UTF8 buff[I32BUF_SIZE];
|
2006-09-01 22:59:23 +00:00
|
|
|
|
mux_ltoa(loc, buff);
|
|
|
|
|
|
open_exit(executor, destnum, links[1], buff);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// link_exit, do_link: Set destination(exits), dropto(rooms) or
|
|
|
|
|
|
// home(player,thing)
|
|
|
|
|
|
|
2026-03-04 13:02:41 -07:00
|
|
|
|
void link_exit(const dbref player, const dbref exit, const dbref dest)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
|
|
|
|
|
// Make sure we can link there
|
|
|
|
|
|
//
|
|
|
|
|
|
if ( dest != HOME
|
2026-03-03 21:36:21 -07:00
|
|
|
|
&& !Link_Anywhere(player)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
&& ( ( !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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Implement NOMODIFY flag (#487).
Add NOMODIFY flag (bit 0x00008000 on FLAG_WORD3) that prevents all
modifications to an object -- attributes, flags, locks, name, owner,
zone, parent, and links -- even by its owner. Only Wizards and
Royalty bypass the restriction. Aliased as NO_MODIFY.
Checks added at 11 choke points across 5 source files:
- bCanSetAttr() and bCanLockAttr() in predicates.cpp
- flag_set() in flags.cpp
- match_controlled_handler(), do_chzone(), do_lock(), do_chown(),
do_unlink() in set.cpp
- do_parent(), link_exit(), do_link() in create.cpp
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 11:19:57 -07:00
|
|
|
|
if (NoModify(exit) && !WizRoy(player))
|
|
|
|
|
|
{
|
|
|
|
|
|
notify_quiet(player, NOPERM_MESSAGE);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2006-09-01 22:59:23 +00:00
|
|
|
|
// 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));
|
2026-03-04 23:16:08 -07:00
|
|
|
|
s_Flags(exit, FLAG_WORD1, (db[exit].fs.word[FLAG_WORD1] & ~(INHERIT | WIZARD)) | HALT);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Link has been validated and paid for, do it and tell the player
|
|
|
|
|
|
//
|
|
|
|
|
|
s_Location(exit, dest);
|
|
|
|
|
|
if (!Quiet(player))
|
|
|
|
|
|
{
|
2026-07-27 00:47:16 +00:00
|
|
|
|
notify_quiet(player, M_("Linked."));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
Implement Phase 1 routing: static unconditional next-hop tables
Add route() softcode function with BFS-based shortest-path routing
over rooms marked NAVIGABLE. The routing table stores only the next-hop
exit for each (source, dest) pair, compressed via diagonal elimination,
adjacent marking, and row redundancy. Lazy rebuild on generation-counter
mismatch triggered by topology changes (@dig, @destroy, @link, @open,
@unlink) and NAVIGABLE flag changes. SQLite schema v10 adds route_nodes,
route_table, route_meta tables for future persistence phases.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 03:18:37 -06:00
|
|
|
|
route_invalidate();
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void do_link
|
|
|
|
|
|
(
|
2022-03-14 16:08:21 -06:00
|
|
|
|
const dbref executor,
|
|
|
|
|
|
const dbref caller,
|
2006-09-01 22:59:23 +00:00
|
|
|
|
dbref enactor,
|
2022-03-14 16:08:21 -06:00
|
|
|
|
const int eval,
|
|
|
|
|
|
const int key,
|
|
|
|
|
|
const int nargs,
|
2007-03-14 00:55:08 +00:00
|
|
|
|
UTF8 *what,
|
2007-09-02 21:44:11 -07:00
|
|
|
|
UTF8 *where,
|
|
|
|
|
|
const UTF8 *cargs[],
|
2022-03-14 16:08:21 -06:00
|
|
|
|
const int ncargs
|
2006-09-01 22:59:23 +00:00
|
|
|
|
)
|
|
|
|
|
|
{
|
2007-09-01 23:47:27 -07:00
|
|
|
|
UNUSED_PARAMETER(eval);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
UNUSED_PARAMETER(nargs);
|
2007-09-02 21:44:11 -07:00
|
|
|
|
UNUSED_PARAMETER(cargs);
|
|
|
|
|
|
UNUSED_PARAMETER(ncargs);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
|
|
|
|
|
// Find the thing to link
|
|
|
|
|
|
//
|
|
|
|
|
|
init_match(executor, what, TYPE_EXIT);
|
|
|
|
|
|
match_everything(0);
|
2022-03-14 16:08:21 -06:00
|
|
|
|
const dbref thing = noisy_match_result();
|
2006-09-01 22:59:23 +00:00
|
|
|
|
if (thing == NOTHING)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Allow unlink if where is not specified
|
|
|
|
|
|
//
|
|
|
|
|
|
if (!where || !*where)
|
|
|
|
|
|
{
|
2018-10-03 17:54:51 +00:00
|
|
|
|
do_unlink(executor, caller, enactor, 0, key, what, nullptr, 0);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
dbref room;
|
2007-03-14 00:55:08 +00:00
|
|
|
|
UTF8 *buff;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
Implement NOMODIFY flag (#487).
Add NOMODIFY flag (bit 0x00008000 on FLAG_WORD3) that prevents all
modifications to an object -- attributes, flags, locks, name, owner,
zone, parent, and links -- even by its owner. Only Wizards and
Royalty bypass the restriction. Aliased as NO_MODIFY.
Checks added at 11 choke points across 5 source files:
- bCanSetAttr() and bCanLockAttr() in predicates.cpp
- flag_set() in flags.cpp
- match_controlled_handler(), do_chzone(), do_lock(), do_chown(),
do_unlink() in set.cpp
- do_parent(), link_exit(), do_link() in create.cpp
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 11:19:57 -07:00
|
|
|
|
if (NoModify(thing) && !WizRoy(executor))
|
|
|
|
|
|
{
|
|
|
|
|
|
notify_quiet(executor, NOPERM_MESSAGE);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2006-09-01 22:59:23 +00:00
|
|
|
|
init_match(executor, where, NOTYPE);
|
|
|
|
|
|
match_everything(MAT_NO_EXITS);
|
|
|
|
|
|
room = noisy_match_result();
|
|
|
|
|
|
if (!Good_obj(room))
|
|
|
|
|
|
{
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Has_contents(room))
|
|
|
|
|
|
{
|
2026-07-26 21:52:48 -06:00
|
|
|
|
notify_quiet(executor, M_("Can’t link to an exit."));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
if ( !can_set_home(executor, thing, room)
|
|
|
|
|
|
|| !could_doit(executor, room, A_LLINK))
|
|
|
|
|
|
{
|
|
|
|
|
|
notify_quiet(executor, NOPERM_MESSAGE);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (room == HOME)
|
|
|
|
|
|
{
|
2026-07-26 21:52:48 -06:00
|
|
|
|
notify_quiet(executor, M_("Can’t set home to home."));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2022-03-14 16:08:21 -06:00
|
|
|
|
const dbref nHomeOrig = Home(thing);
|
|
|
|
|
|
const dbref nHomeNew = room;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
s_Home(thing, nHomeNew);
|
|
|
|
|
|
if (!Quiet(executor))
|
|
|
|
|
|
{
|
2026-07-27 09:28:27 -06:00
|
|
|
|
// 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));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case TYPE_ROOM:
|
|
|
|
|
|
|
|
|
|
|
|
// Set dropto.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (!Controls(executor, thing))
|
|
|
|
|
|
{
|
|
|
|
|
|
notify_quiet(executor, NOPERM_MESSAGE);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
Implement NOMODIFY flag (#487).
Add NOMODIFY flag (bit 0x00008000 on FLAG_WORD3) that prevents all
modifications to an object -- attributes, flags, locks, name, owner,
zone, parent, and links -- even by its owner. Only Wizards and
Royalty bypass the restriction. Aliased as NO_MODIFY.
Checks added at 11 choke points across 5 source files:
- bCanSetAttr() and bCanLockAttr() in predicates.cpp
- flag_set() in flags.cpp
- match_controlled_handler(), do_chzone(), do_lock(), do_chown(),
do_unlink() in set.cpp
- do_parent(), link_exit(), do_link() in create.cpp
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 11:19:57 -07:00
|
|
|
|
if (NoModify(thing) && !WizRoy(executor))
|
|
|
|
|
|
{
|
|
|
|
|
|
notify_quiet(executor, NOPERM_MESSAGE);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2006-09-01 22:59:23 +00:00
|
|
|
|
room = parse_linkable_room(executor, where);
|
|
|
|
|
|
if ( !Good_obj(room)
|
|
|
|
|
|
&& room != HOME)
|
|
|
|
|
|
{
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ( room != HOME
|
|
|
|
|
|
&& !isRoom(room))
|
|
|
|
|
|
{
|
2026-07-27 00:47:16 +00:00
|
|
|
|
notify_quiet(executor, M_("That is not a room!"));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
else if ( room != HOME
|
2026-03-03 21:36:21 -07:00
|
|
|
|
&& !Link_Anywhere(executor)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
&& ( ( !Controls(executor, room)
|
|
|
|
|
|
&& !Link_ok(room))
|
|
|
|
|
|
|| !could_doit(executor, room, A_LLINK)))
|
|
|
|
|
|
{
|
|
|
|
|
|
notify_quiet(executor, NOPERM_MESSAGE);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2022-03-14 16:08:21 -06:00
|
|
|
|
const dbref nDroptoOrig = Dropto(thing);
|
|
|
|
|
|
const dbref nDroptoNew = room;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
s_Dropto(thing, room);
|
|
|
|
|
|
if (!Quiet(executor))
|
|
|
|
|
|
{
|
2026-07-27 09:28:27 -06:00
|
|
|
|
// 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));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case TYPE_GARBAGE:
|
|
|
|
|
|
|
|
|
|
|
|
notify_quiet(executor, NOPERM_MESSAGE);
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
|
|
|
|
|
|
STARTLOG(LOG_BUGS, "BUG", "OTYPE");
|
|
|
|
|
|
buff = alloc_mbuf("do_link.LOG.badtype");
|
2008-12-27 18:37:01 -08:00
|
|
|
|
mux_sprintf(buff, MBUF_SIZE, T("Strange object type: object #%d = %d"),
|
2006-09-01 22:59:23 +00:00
|
|
|
|
thing, Typeof(thing));
|
|
|
|
|
|
log_text(buff);
|
|
|
|
|
|
free_mbuf(buff);
|
|
|
|
|
|
ENDLOG;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// do_parent: Set an object's parent field.
|
|
|
|
|
|
//
|
|
|
|
|
|
void do_parent
|
|
|
|
|
|
(
|
2022-03-14 16:08:21 -06:00
|
|
|
|
const dbref executor,
|
|
|
|
|
|
const dbref caller,
|
2006-09-01 22:59:23 +00:00
|
|
|
|
dbref enactor,
|
2022-03-14 16:08:21 -06:00
|
|
|
|
const int eval,
|
|
|
|
|
|
const int key,
|
|
|
|
|
|
const int nargs,
|
2007-03-14 00:55:08 +00:00
|
|
|
|
UTF8 *tname,
|
2007-09-02 21:44:11 -07:00
|
|
|
|
UTF8 *pname,
|
|
|
|
|
|
const UTF8 *cargs[],
|
2022-03-14 16:08:21 -06:00
|
|
|
|
const int ncargs
|
2006-09-01 22:59:23 +00:00
|
|
|
|
)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(caller);
|
|
|
|
|
|
UNUSED_PARAMETER(enactor);
|
2007-09-01 23:47:27 -07:00
|
|
|
|
UNUSED_PARAMETER(eval);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
UNUSED_PARAMETER(key);
|
|
|
|
|
|
UNUSED_PARAMETER(nargs);
|
2007-09-02 21:44:11 -07:00
|
|
|
|
UNUSED_PARAMETER(cargs);
|
|
|
|
|
|
UNUSED_PARAMETER(ncargs);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
|
|
|
|
|
// Get victim.
|
|
|
|
|
|
//
|
|
|
|
|
|
init_match(executor, tname, NOTYPE);
|
|
|
|
|
|
match_everything(0);
|
2022-03-14 16:08:21 -06:00
|
|
|
|
const dbref thing = noisy_match_result();
|
2006-09-01 22:59:23 +00:00
|
|
|
|
if (!Good_obj(thing))
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Make sure we can do it.
|
|
|
|
|
|
//
|
|
|
|
|
|
if ( Going(thing)
|
|
|
|
|
|
|| !Controls(executor, thing))
|
|
|
|
|
|
{
|
|
|
|
|
|
notify_quiet(executor, NOPERM_MESSAGE);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Implement NOMODIFY flag (#487).
Add NOMODIFY flag (bit 0x00008000 on FLAG_WORD3) that prevents all
modifications to an object -- attributes, flags, locks, name, owner,
zone, parent, and links -- even by its owner. Only Wizards and
Royalty bypass the restriction. Aliased as NO_MODIFY.
Checks added at 11 choke points across 5 source files:
- bCanSetAttr() and bCanLockAttr() in predicates.cpp
- flag_set() in flags.cpp
- match_controlled_handler(), do_chzone(), do_lock(), do_chown(),
do_unlink() in set.cpp
- do_parent(), link_exit(), do_link() in create.cpp
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 11:19:57 -07:00
|
|
|
|
if (NoModify(thing) && !WizRoy(executor))
|
|
|
|
|
|
{
|
|
|
|
|
|
notify_quiet(executor, NOPERM_MESSAGE);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2007-09-28 07:15:37 -07:00
|
|
|
|
// Save the previous parent for @aparent handling.
|
|
|
|
|
|
//
|
|
|
|
|
|
dbref previous_parent = db[thing].parent;
|
|
|
|
|
|
|
2006-09-01 22:59:23 +00:00
|
|
|
|
// Find out what the new parent is.
|
|
|
|
|
|
//
|
2007-09-28 07:15:37 -07:00
|
|
|
|
dbref parent = NOTHING;
|
|
|
|
|
|
if ('\0' != pname[0])
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
|
|
|
|
|
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
|
|
|
|
|
|
//
|
2007-09-28 07:15:37 -07:00
|
|
|
|
int lev;
|
|
|
|
|
|
int curr;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
ITER_PARENTS(parent, curr, lev)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (curr == thing)
|
|
|
|
|
|
{
|
2026-07-26 21:52:48 -06:00
|
|
|
|
notify_quiet(executor, M_("You can’t have yourself as a parent!"));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
s_Parent(thing, parent);
|
2007-09-23 18:49:05 +00:00
|
|
|
|
|
2007-09-28 07:15:37 -07:00
|
|
|
|
// Now that parent is set, handle zero, one, or two @aparent notifications
|
|
|
|
|
|
// as necessary.
|
2007-09-23 18:49:05 +00:00
|
|
|
|
//
|
2007-09-28 07:15:37 -07:00
|
|
|
|
if (parent != previous_parent)
|
|
|
|
|
|
{
|
2026-03-29 03:28:10 -06:00
|
|
|
|
route_invalidate();
|
|
|
|
|
|
|
2007-09-28 07:15:37 -07:00
|
|
|
|
// Setup the appropriate stack arguments.
|
|
|
|
|
|
//
|
|
|
|
|
|
UTF8 child[SBUF_SIZE];
|
2022-03-14 16:08:21 -06:00
|
|
|
|
const UTF8 removal[] = "1";
|
|
|
|
|
|
const UTF8 addition[] = "0";
|
2007-09-28 07:15:37 -07:00
|
|
|
|
UTF8 original_executor[SBUF_SIZE];
|
|
|
|
|
|
const UTF8 *xargs[3];
|
2007-09-23 18:49:05 +00:00
|
|
|
|
|
2022-03-14 16:08:21 -06:00
|
|
|
|
const int xnargs = 3;
|
2007-09-28 07:15:37 -07:00
|
|
|
|
xargs[0] = child;
|
|
|
|
|
|
xargs[2] = original_executor;
|
2007-09-23 18:49:05 +00:00
|
|
|
|
|
2007-09-28 07:15:37 -07:00
|
|
|
|
dbref aowner;
|
|
|
|
|
|
int aflags;
|
2007-09-23 18:49:05 +00:00
|
|
|
|
|
2007-09-28 07:15:37 -07:00
|
|
|
|
// Notify previous parent of the removal.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (Good_obj(previous_parent))
|
|
|
|
|
|
{
|
|
|
|
|
|
UTF8* action = atr_get("do_parent.529", previous_parent, A_APARENT,
|
|
|
|
|
|
&aowner, &aflags);
|
2007-09-23 18:49:05 +00:00
|
|
|
|
|
2018-10-03 17:54:51 +00:00
|
|
|
|
if ( nullptr != action
|
2007-09-28 07:15:37 -07:00
|
|
|
|
&& '\0' != action[0])
|
|
|
|
|
|
{
|
2008-12-27 18:37:01 -08:00
|
|
|
|
mux_sprintf(child, SBUF_SIZE, T("#%d"), thing);
|
|
|
|
|
|
mux_sprintf(original_executor, SBUF_SIZE, T("#%d"), executor);
|
2007-09-28 07:15:37 -07:00
|
|
|
|
xargs[1] = removal;
|
2007-09-23 18:49:05 +00:00
|
|
|
|
|
2018-10-03 17:54:51 +00:00
|
|
|
|
did_it(previous_parent, previous_parent, 0, nullptr, 0, nullptr,
|
2007-09-28 07:15:37 -07:00
|
|
|
|
A_APARENT, 0, xargs, xnargs);
|
|
|
|
|
|
}
|
|
|
|
|
|
free_lbuf(action);
|
|
|
|
|
|
}
|
2007-09-23 18:49:05 +00:00
|
|
|
|
|
2007-09-28 07:15:37 -07:00
|
|
|
|
// Notify new parent of addition.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (Good_obj(parent))
|
|
|
|
|
|
{
|
|
|
|
|
|
UTF8* action = atr_get("do_parent.550", parent, A_APARENT,
|
|
|
|
|
|
&aowner, &aflags);
|
2007-09-23 18:49:05 +00:00
|
|
|
|
|
2018-10-03 17:54:51 +00:00
|
|
|
|
if ( nullptr != action
|
2007-09-28 07:15:37 -07:00
|
|
|
|
&& '\0' != action[0])
|
|
|
|
|
|
{
|
2008-12-27 18:37:01 -08:00
|
|
|
|
mux_sprintf(child, SBUF_SIZE, T("#%d"), thing);
|
|
|
|
|
|
mux_sprintf(original_executor, SBUF_SIZE, T("#%d"), executor);
|
2007-09-28 07:15:37 -07:00
|
|
|
|
xargs[1] = addition;
|
2007-09-23 18:49:05 +00:00
|
|
|
|
|
2018-10-03 17:54:51 +00:00
|
|
|
|
did_it(parent, parent, 0, nullptr, 0, nullptr, A_APARENT, 0,
|
2007-09-28 07:15:37 -07:00
|
|
|
|
xargs, xnargs);
|
|
|
|
|
|
}
|
|
|
|
|
|
free_lbuf(action);
|
|
|
|
|
|
}
|
2007-09-23 18:49:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2007-09-28 07:15:37 -07:00
|
|
|
|
if ( !Quiet(thing)
|
|
|
|
|
|
&& !Quiet(executor))
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
2007-09-28 07:15:37 -07:00
|
|
|
|
if (NOTHING == parent)
|
|
|
|
|
|
{
|
2026-07-27 00:47:16 +00:00
|
|
|
|
notify_quiet(executor, M_("Parent cleared."));
|
2007-09-28 07:15:37 -07:00
|
|
|
|
}
|
2006-09-01 22:59:23 +00:00
|
|
|
|
else
|
2007-09-28 07:15:37 -07:00
|
|
|
|
{
|
2026-07-27 00:47:16 +00:00
|
|
|
|
notify_quiet(executor, M_("Parent set."));
|
2007-09-28 07:15:37 -07:00
|
|
|
|
}
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// do_dig: Create a new room.
|
|
|
|
|
|
//
|
2022-03-14 16:08:21 -06:00
|
|
|
|
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)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
2006-12-27 17:53:42 +00:00
|
|
|
|
UNUSED_PARAMETER(eval);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
UNUSED_PARAMETER(caller);
|
2007-09-02 22:02:33 -07:00
|
|
|
|
UNUSED_PARAMETER(cargs);
|
|
|
|
|
|
UNUSED_PARAMETER(ncargs);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
|
|
|
|
|
// we don't need to know player's location! hooray!
|
|
|
|
|
|
//
|
|
|
|
|
|
if (!name || !*name)
|
|
|
|
|
|
{
|
2026-07-27 00:47:16 +00:00
|
|
|
|
notify_quiet(executor, M_("Dig what?"));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2022-03-14 16:08:21 -06:00
|
|
|
|
const dbref room = create_obj(executor, TYPE_ROOM, name, 0);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
if (room == NOTHING)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
local_data_create(room);
|
2008-01-29 12:16:54 -08:00
|
|
|
|
|
2007-09-01 16:44:47 -07:00
|
|
|
|
ServerEventsSinkNode *p = g_pServerEventsSinkListHead;
|
2018-10-03 17:54:51 +00:00
|
|
|
|
while (nullptr != p)
|
2007-09-01 16:44:47 -07:00
|
|
|
|
{
|
|
|
|
|
|
p->pSink->data_create(room);
|
|
|
|
|
|
p = p->pNext;
|
|
|
|
|
|
}
|
2008-01-29 12:16:54 -08:00
|
|
|
|
|
2026-07-27 15:04:54 +00:00
|
|
|
|
notify(executor, tprintf(M_("%s created as room #%d."), name, room));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
2007-03-14 00:55:08 +00:00
|
|
|
|
UTF8 *buff = alloc_sbuf("do_dig");
|
2006-09-01 22:59:23 +00:00
|
|
|
|
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
|
|
|
|
|
|
(
|
2022-03-14 16:08:21 -06:00
|
|
|
|
const dbref executor,
|
|
|
|
|
|
const dbref caller,
|
2006-09-01 22:59:23 +00:00
|
|
|
|
dbref enactor,
|
2022-03-14 16:08:21 -06:00
|
|
|
|
const int eval,
|
|
|
|
|
|
const int key,
|
|
|
|
|
|
const int nargs,
|
2007-03-14 00:55:08 +00:00
|
|
|
|
UTF8 *name,
|
2007-09-02 21:44:11 -07:00
|
|
|
|
UTF8 *coststr,
|
|
|
|
|
|
const UTF8 *cargs[],
|
2022-03-14 16:08:21 -06:00
|
|
|
|
const int ncargs
|
2006-09-01 22:59:23 +00:00
|
|
|
|
)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(caller);
|
|
|
|
|
|
UNUSED_PARAMETER(enactor);
|
2007-09-01 23:47:27 -07:00
|
|
|
|
UNUSED_PARAMETER(eval);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
UNUSED_PARAMETER(key);
|
2007-09-02 21:44:11 -07:00
|
|
|
|
UNUSED_PARAMETER(cargs);
|
|
|
|
|
|
UNUSED_PARAMETER(ncargs);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
|
|
|
|
|
int cost = 0;
|
|
|
|
|
|
if (!name || !*name)
|
|
|
|
|
|
{
|
2026-07-27 00:47:16 +00:00
|
|
|
|
notify_quiet(executor, M_("Create what?"));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if ( nargs == 2
|
fix(win32): migrate the remaining mux_atol callers to mux_atoi64 (#1373)
Completes the sweep the issue called for. mux_atol returns long, which
is 32-bit on LLP64, so every caller silently truncated on Windows. Two
of those were real defects (the truthiness family and cf_size, fixed in
the preceding commits); the rest were latent, waiting for a value large
enough to matter.
Rather than audit 290 sites for whether each can reach 2^31 today, use
the 64-bit parser everywhere and remove the class. A dbref cannot
overflow now, but nothing stops a later caller passing that same site a
timestamp or a byte count.
Pure 1:1 substitution: 285 lines changed, and every removed line
contained mux_atol while every added line contains mux_atoi64. No
control flow, no types, no behaviour beyond the wider parse.
This is a NO-OP on LP64 -- long is already 64-bit on Linux and macOS, so
the generated code there is unchanged. It only widens the parse on
Windows. Narrowing destinations are unaffected either way: `int x =
mux_atoi64(s)` truncates exactly as `int x = mux_atol(s)` did, on both
models.
Left alone: mux_atol itself in mathutil, its declaration, and three
comments that name it. Callers that genuinely want 32-bit semantics can
still ask for them; none appear to.
Verified on Windows: full solution builds clean with no new warnings,
smoke is 1418 passed / 16 failed / 0 crashes / 306 of 306 dispatched --
identical to before the sweep, with the same 16 build-configuration
failures (exp3 module not loaded, hmac/digest behind UNIX_DIGEST).
Spot checks after the change: the boolean family returns 1 for multiples
of 2^32, cf_size round-trips 3000000000 and still reads -1 as unlimited,
and arithmetic, string and list functions are unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 10:03:45 -06:00
|
|
|
|
&& (cost = mux_atoi64(coststr)) < 0)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
2026-07-26 21:52:48 -06:00
|
|
|
|
notify_quiet(executor, M_("You can’t create an object for less than nothing!"));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2022-03-14 16:08:21 -06:00
|
|
|
|
const dbref thing = create_obj(executor, TYPE_THING, name, cost);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
if (thing == NOTHING)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
move_via_generic(thing, executor, NOTHING, 0);
|
|
|
|
|
|
s_Home(thing, new_home(executor));
|
|
|
|
|
|
if (!Quiet(executor))
|
|
|
|
|
|
{
|
2026-07-27 15:04:54 +00:00
|
|
|
|
notify(executor, tprintf(M_("%s created as object #%d"), Name(thing), thing));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
local_data_create(thing);
|
2008-01-29 12:16:54 -08:00
|
|
|
|
|
2007-09-01 16:44:47 -07:00
|
|
|
|
ServerEventsSinkNode *p = g_pServerEventsSinkListHead;
|
2018-10-03 17:54:51 +00:00
|
|
|
|
while (nullptr != p)
|
2007-09-01 16:44:47 -07:00
|
|
|
|
{
|
|
|
|
|
|
p->pSink->data_create(thing);
|
|
|
|
|
|
p = p->pNext;
|
|
|
|
|
|
}
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// do_clone: Create a copy of an object.
|
|
|
|
|
|
//
|
|
|
|
|
|
void do_clone
|
|
|
|
|
|
(
|
2022-03-14 16:08:21 -06:00
|
|
|
|
const dbref executor,
|
|
|
|
|
|
const dbref caller,
|
2006-09-01 22:59:23 +00:00
|
|
|
|
dbref enactor,
|
2022-03-14 16:08:21 -06:00
|
|
|
|
const int eval,
|
2006-09-01 22:59:23 +00:00
|
|
|
|
int key,
|
|
|
|
|
|
int nargs,
|
2007-03-14 00:55:08 +00:00
|
|
|
|
UTF8 *name,
|
2007-09-02 21:44:11 -07:00
|
|
|
|
UTF8 *arg2,
|
|
|
|
|
|
const UTF8 *cargs[],
|
2022-03-14 16:08:21 -06:00
|
|
|
|
const int ncargs
|
2006-09-01 22:59:23 +00:00
|
|
|
|
)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(caller);
|
|
|
|
|
|
UNUSED_PARAMETER(enactor);
|
2007-09-01 23:47:27 -07:00
|
|
|
|
UNUSED_PARAMETER(eval);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
UNUSED_PARAMETER(nargs);
|
2007-09-02 21:44:11 -07:00
|
|
|
|
UNUSED_PARAMETER(cargs);
|
|
|
|
|
|
UNUSED_PARAMETER(ncargs);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
2022-03-14 16:08:21 -06:00
|
|
|
|
dbref loc;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
int cost;
|
|
|
|
|
|
|
2006-11-29 08:22:07 +00:00
|
|
|
|
if ( (key & CLONE_INVENTORY)
|
|
|
|
|
|
|| !Has_location(executor))
|
|
|
|
|
|
{
|
2006-09-01 22:59:23 +00:00
|
|
|
|
loc = executor;
|
2006-11-29 08:22:07 +00:00
|
|
|
|
}
|
2006-09-01 22:59:23 +00:00
|
|
|
|
else
|
2006-11-29 08:22:07 +00:00
|
|
|
|
{
|
2006-09-01 22:59:23 +00:00
|
|
|
|
loc = Location(executor);
|
2006-11-29 08:22:07 +00:00
|
|
|
|
}
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
|
|
|
|
|
if (!Good_obj(loc))
|
2006-11-29 08:22:07 +00:00
|
|
|
|
{
|
2006-09-01 22:59:23 +00:00
|
|
|
|
return;
|
2006-11-29 08:22:07 +00:00
|
|
|
|
}
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
|
|
|
|
|
init_match(executor, name, NOTYPE);
|
|
|
|
|
|
match_everything(0);
|
2022-03-14 16:08:21 -06:00
|
|
|
|
dbref thing = noisy_match_result();
|
2006-11-29 08:22:07 +00:00
|
|
|
|
if ( NOTHING == thing
|
|
|
|
|
|
|| AMBIGUOUS == thing)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
|
|
|
|
|
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))
|
|
|
|
|
|
{
|
2026-07-27 00:47:16 +00:00
|
|
|
|
notify_quiet(executor, M_("You cannot clone players!"));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
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,
|
2026-07-27 15:04:54 +00:00
|
|
|
|
tprintf(M_("You don’t control %s, ignoring /parent."),
|
2006-09-01 22:59:23 +00:00
|
|
|
|
Name(thing)));
|
|
|
|
|
|
key &= ~CLONE_FROM_PARENT;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Fix command-side verb correctness bugs (@clone, @ps, whisper, @flag, @mark)
Correctness sweep of the command-side verb handlers. Five confirmed bugs
plus a help-text correction, verified by dual-lens review and code reading
(the read-only smoke harness can't exercise these verbs directly):
- @clone/cost on an exit bypassed the "must control current location"
check (it lived only on the non-/cost path), letting a builder splice a
cloned exit into a room they do not control. (#855)
- The @mark/@mark_all/@apply_marked DB-cleaning refusal cited @unmark_all,
which does not exist (produces "Huh?"); corrected to @mark_all/clear.
(#856)
- @ps <object> reported nothing for a controlled object owned by another
player; do_ps was missing the non-player-target else clause that the
sibling @halt has (clear the owner filter). (#857)
- whisper "<quoted name>" skipped the locality/connected gate the unquoted
form applies, giving a success confirmation plus a delivery error and
polluting A_LASTWHISPER; also fixed an adjacent quoted-name continue that
did not advance the parser. (#858)
- @flag/remove of an unknown/empty flag name was silent; now reports an
error like the other flag-name failure paths. (#859)
- report help said 8-hour segments but the code uses 4 (deliberately, per
4a845139f); corrected the help. (#860)
Also restores the "## JIT / DBT Engine" CHANGES heading dropped during an
earlier 2.14.0.8 edit. Build clean, all 1264 smoke tests still pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 06:29:11 -06:00
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2006-09-01 22:59:23 +00:00
|
|
|
|
// Determine the cost of cloning
|
|
|
|
|
|
//
|
2022-03-14 16:08:21 -06:00
|
|
|
|
const dbref new_owner = (key & CLONE_PRESERVE) ? Owner(thing) : Owner(executor);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
if (key & CLONE_SET_COST)
|
|
|
|
|
|
{
|
fix(win32): migrate the remaining mux_atol callers to mux_atoi64 (#1373)
Completes the sweep the issue called for. mux_atol returns long, which
is 32-bit on LLP64, so every caller silently truncated on Windows. Two
of those were real defects (the truthiness family and cf_size, fixed in
the preceding commits); the rest were latent, waiting for a value large
enough to matter.
Rather than audit 290 sites for whether each can reach 2^31 today, use
the 64-bit parser everywhere and remove the class. A dbref cannot
overflow now, but nothing stops a later caller passing that same site a
timestamp or a byte count.
Pure 1:1 substitution: 285 lines changed, and every removed line
contained mux_atol while every added line contains mux_atoi64. No
control flow, no types, no behaviour beyond the wider parse.
This is a NO-OP on LP64 -- long is already 64-bit on Linux and macOS, so
the generated code there is unchanged. It only widens the parse on
Windows. Narrowing destinations are unaffected either way: `int x =
mux_atoi64(s)` truncates exactly as `int x = mux_atol(s)` did, on both
models.
Left alone: mux_atol itself in mathutil, its declaration, and three
comments that name it. Callers that genuinely want 32-bit semantics can
still ask for them; none appear to.
Verified on Windows: full solution builds clean with no new warnings,
smoke is 1418 passed / 16 failed / 0 crashes / 306 of 306 dispatched --
identical to before the sweep, with the same 16 build-configuration
failures (exp3 module not loaded, hmac/digest behind UNIX_DIGEST).
Spot checks after the change: the boolean family returns 1 for multiples
of 2^32, cf_size round-trips 3000000000 and still reads -1 as unlimited,
and arithmetic, string and list functions are unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 10:03:45 -06:00
|
|
|
|
cost = mux_atoi64(arg2);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
if (cost < mudconf.createmin)
|
|
|
|
|
|
cost = mudconf.createmin;
|
|
|
|
|
|
if (cost > mudconf.createmax)
|
|
|
|
|
|
cost = mudconf.createmax;
|
2018-10-03 17:54:51 +00:00
|
|
|
|
arg2 = nullptr;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
cost = 1;
|
|
|
|
|
|
switch (Typeof(thing))
|
|
|
|
|
|
{
|
|
|
|
|
|
case TYPE_THING:
|
|
|
|
|
|
cost = OBJECT_DEPOSIT((mudconf.clone_copy_cost) ?
|
|
|
|
|
|
Pennies(thing) : 1);
|
|
|
|
|
|
break;
|
2007-12-21 17:46:20 -08:00
|
|
|
|
|
2006-09-01 22:59:23 +00:00
|
|
|
|
case TYPE_ROOM:
|
|
|
|
|
|
cost = mudconf.digcost;
|
|
|
|
|
|
break;
|
2007-12-21 17:46:20 -08:00
|
|
|
|
|
2006-09-01 22:59:23 +00:00
|
|
|
|
case TYPE_EXIT:
|
|
|
|
|
|
cost = mudconf.digcost;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Go make the clone object.
|
|
|
|
|
|
//
|
2008-06-01 06:37:03 +00:00
|
|
|
|
bool bValid = false;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
size_t nValidName;
|
2022-03-14 16:08:21 -06:00
|
|
|
|
const UTF8 *pValidName = nullptr;
|
2007-12-21 17:46:20 -08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2007-03-14 00:55:08 +00:00
|
|
|
|
const UTF8 *clone_name;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
if (bValid)
|
|
|
|
|
|
{
|
|
|
|
|
|
clone_name = pValidName;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
clone_name = Name(thing);
|
|
|
|
|
|
}
|
2022-03-14 16:08:21 -06:00
|
|
|
|
const dbref clone = create_obj(new_owner, Typeof(thing), clone_name, cost);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
|
|
|
|
|
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));
|
|
|
|
|
|
|
2006-11-29 17:32:09 +00:00
|
|
|
|
FLAGSET clearflags;
|
|
|
|
|
|
TranslateFlags_Clone(clearflags.word, executor, key);
|
2018-10-03 17:54:51 +00:00
|
|
|
|
SetClearFlags(clone, clearflags.word, nullptr);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
|
|
|
|
|
// Tell creator about it
|
|
|
|
|
|
//
|
|
|
|
|
|
if (!Quiet(executor))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (arg2 && *arg2)
|
|
|
|
|
|
{
|
|
|
|
|
|
notify(executor,
|
2026-07-27 15:04:54 +00:00
|
|
|
|
tprintf(M_("%s cloned as %s, new copy is object #%d."),
|
2006-09-01 22:59:23 +00:00
|
|
|
|
Name(thing), arg2, clone));
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
notify(executor,
|
2026-07-27 15:04:54 +00:00
|
|
|
|
tprintf(M_("%s cloned, new copy is object #%d."),
|
2006-09-01 22:59:23 +00:00
|
|
|
|
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)
|
|
|
|
|
|
{
|
2026-07-25 08:15:17 -06:00
|
|
|
|
// #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));
|
|
|
|
|
|
}
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case TYPE_EXIT:
|
|
|
|
|
|
|
|
|
|
|
|
s_Exits(loc, insert_first(Exits(loc), clone));
|
|
|
|
|
|
s_Exits(clone, loc);
|
|
|
|
|
|
s_Location(clone, NOTHING);
|
|
|
|
|
|
if (Location(thing) != NOTHING)
|
|
|
|
|
|
{
|
2026-07-25 08:15:17 -06:00
|
|
|
|
// #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));
|
|
|
|
|
|
}
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
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));
|
|
|
|
|
|
}
|
2018-10-03 17:54:51 +00:00
|
|
|
|
did_it(executor, clone, 0, nullptr, 0, nullptr, A_ACLONE, 0, nullptr, 0);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
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);
|
2008-01-29 12:16:54 -08:00
|
|
|
|
|
2007-09-01 16:44:47 -07:00
|
|
|
|
ServerEventsSinkNode *p = g_pServerEventsSinkListHead;
|
2018-10-03 17:54:51 +00:00
|
|
|
|
while (nullptr != p)
|
2007-09-01 16:44:47 -07:00
|
|
|
|
{
|
|
|
|
|
|
p->pSink->data_clone(clone, thing);
|
|
|
|
|
|
p = p->pNext;
|
|
|
|
|
|
}
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// do_pcreate: Create new players and robots.
|
|
|
|
|
|
//
|
|
|
|
|
|
void do_pcreate
|
|
|
|
|
|
(
|
2022-03-14 16:08:21 -06:00
|
|
|
|
const dbref executor,
|
|
|
|
|
|
const dbref caller,
|
|
|
|
|
|
const dbref enactor,
|
|
|
|
|
|
const int eval,
|
|
|
|
|
|
const int key,
|
|
|
|
|
|
const int nargs,
|
2007-03-14 00:55:08 +00:00
|
|
|
|
UTF8 *name,
|
2007-09-02 21:44:11 -07:00
|
|
|
|
UTF8 *pass,
|
|
|
|
|
|
const UTF8 *cargs[],
|
|
|
|
|
|
int ncargs
|
2006-09-01 22:59:23 +00:00
|
|
|
|
)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(caller);
|
|
|
|
|
|
UNUSED_PARAMETER(enactor);
|
2007-09-01 23:47:27 -07:00
|
|
|
|
UNUSED_PARAMETER(eval);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
UNUSED_PARAMETER(nargs);
|
2007-09-02 21:44:11 -07:00
|
|
|
|
UNUSED_PARAMETER(cargs);
|
|
|
|
|
|
UNUSED_PARAMETER(ncargs);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
2007-03-14 00:55:08 +00:00
|
|
|
|
const UTF8 *pmsg;
|
2022-03-14 16:08:21 -06:00
|
|
|
|
const bool isrobot = (key == PCRE_ROBOT);
|
|
|
|
|
|
const dbref newplayer = create_player(name, pass, executor, isrobot, &pmsg);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
if (newplayer == NOTHING)
|
|
|
|
|
|
{
|
2026-07-27 15:04:54 +00:00
|
|
|
|
notify_quiet(executor, tprintf(M_("Failure creating ‘%s’. %s"), name, pmsg));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
AddToPublicChannel(newplayer);
|
2026-03-03 21:37:47 -07:00
|
|
|
|
AddToPlayerChannels(newplayer);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
if (isrobot)
|
|
|
|
|
|
{
|
|
|
|
|
|
move_object(newplayer, Location(executor));
|
|
|
|
|
|
notify_quiet(executor,
|
2026-07-27 15:04:54 +00:00
|
|
|
|
tprintf(M_("New robot ‘%s’ (#%d) created with password ‘%s’"),
|
2006-09-01 22:59:23 +00:00
|
|
|
|
name, newplayer, pass));
|
2026-07-27 00:47:16 +00:00
|
|
|
|
notify_quiet(executor, M_("Your robot has arrived."));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
STARTLOG(LOG_PCREATES, "CRE", "ROBOT");
|
|
|
|
|
|
log_name(newplayer);
|
2007-03-14 22:33:04 +00:00
|
|
|
|
log_text(T(" created by "));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
log_name(executor);
|
|
|
|
|
|
ENDLOG;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
move_object(newplayer, mudconf.start_room);
|
|
|
|
|
|
notify_quiet(executor,
|
2026-07-27 15:04:54 +00:00
|
|
|
|
tprintf(M_("New player ‘%s’ (#%d) created with password ‘%s’"),
|
2006-09-01 22:59:23 +00:00
|
|
|
|
name, newplayer, pass));
|
|
|
|
|
|
STARTLOG(LOG_PCREATES | LOG_WIZARD, "WIZ", "PCREA");
|
|
|
|
|
|
log_name(newplayer);
|
2007-03-14 22:33:04 +00:00
|
|
|
|
log_text(T(" created by "));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
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)
|
2026-03-03 18:39:35 -07:00
|
|
|
|
|| (victim == static_cast<dbref>(0))
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|| (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))
|
|
|
|
|
|
{
|
2026-07-27 00:47:16 +00:00
|
|
|
|
notify_quiet(player, M_("Sorry, no suicide allowed."));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (RealWizard(victim))
|
|
|
|
|
|
{
|
2026-07-26 21:52:48 -06:00
|
|
|
|
notify_quiet(player, M_("Even you can’t do that!"));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-03-14 16:08:21 -06:00
|
|
|
|
static void ProcessMasterRoomADestroy(const dbref destroyer, const dbref thing)
|
2007-09-12 00:51:39 +00:00
|
|
|
|
{
|
2007-09-16 14:36:47 -07:00
|
|
|
|
const UTF8 *xargs[2];
|
2007-09-12 00:51:39 +00:00
|
|
|
|
|
2007-09-16 14:36:47 -07:00
|
|
|
|
switch (Typeof(thing))
|
2007-09-12 00:51:39 +00:00
|
|
|
|
{
|
|
|
|
|
|
case TYPE_ROOM:
|
2007-09-16 14:36:47 -07:00
|
|
|
|
xargs[1] = T("ROOM");
|
2007-09-12 00:51:39 +00:00
|
|
|
|
break;
|
2007-09-16 14:36:47 -07:00
|
|
|
|
|
2007-09-12 00:51:39 +00:00
|
|
|
|
case TYPE_EXIT:
|
2007-09-16 14:36:47 -07:00
|
|
|
|
xargs[1] = T("EXIT");
|
2007-09-12 00:51:39 +00:00
|
|
|
|
break;
|
2007-09-16 14:36:47 -07:00
|
|
|
|
|
2007-09-12 00:51:39 +00:00
|
|
|
|
case TYPE_PLAYER:
|
2007-09-16 14:36:47 -07:00
|
|
|
|
xargs[1] = T("PLAYER");
|
2007-09-12 00:51:39 +00:00
|
|
|
|
break;
|
2007-09-16 14:36:47 -07:00
|
|
|
|
|
2007-09-12 00:51:39 +00:00
|
|
|
|
case TYPE_THING:
|
2007-09-16 14:36:47 -07:00
|
|
|
|
xargs[1] = T("THING");
|
2007-09-12 00:51:39 +00:00
|
|
|
|
break;
|
2007-09-16 14:36:47 -07:00
|
|
|
|
|
2007-09-12 00:51:39 +00:00
|
|
|
|
default:
|
2026-07-27 00:47:44 +00:00
|
|
|
|
xargs[1] = S_("#-1");
|
2007-09-16 14:36:47 -07:00
|
|
|
|
break;
|
2007-09-12 00:51:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
dbref master_room_obj = -1;
|
|
|
|
|
|
DOLIST(master_room_obj, Contents(mudconf.master_room))
|
|
|
|
|
|
{
|
2007-09-16 14:36:47 -07:00
|
|
|
|
if (thing == master_room_obj)
|
2007-09-12 00:51:39 +00:00
|
|
|
|
{
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2007-09-16 14:36:47 -07:00
|
|
|
|
if (Controls(master_room_obj, thing))
|
2007-09-12 00:51:39 +00:00
|
|
|
|
{
|
|
|
|
|
|
int aowner, aflags;
|
2007-09-16 14:36:47 -07:00
|
|
|
|
UTF8* act = atr_pget(master_room_obj, A_ADESTROY, &aowner, &aflags);
|
|
|
|
|
|
if ('\0' != act[0])
|
2007-09-12 00:51:39 +00:00
|
|
|
|
{
|
2022-03-14 16:08:21 -06:00
|
|
|
|
const int nxargs = 2;
|
2007-09-16 14:36:47 -07:00
|
|
|
|
CLinearTimeAbsolute lta;
|
|
|
|
|
|
UTF8 buf[SBUF_SIZE];
|
2008-12-27 18:37:01 -08:00
|
|
|
|
mux_sprintf(buf, SBUF_SIZE, T("#%d"), thing);
|
2007-09-16 14:36:47 -07:00
|
|
|
|
xargs[0] = buf;
|
2007-09-28 22:52:23 +00:00
|
|
|
|
wait_que(master_room_obj, destroyer, destroyer,
|
2007-09-12 00:51:39 +00:00
|
|
|
|
AttrTrace(aflags, 0), false, lta, NOTHING, 0, act, nxargs,
|
|
|
|
|
|
(const UTF8 **) xargs, mudstate.global_regs);
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
free_lbuf(act);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-03-14 16:08:21 -06:00
|
|
|
|
void do_destroy(const dbref executor, const dbref caller, dbref enactor, const int eval, int key, UTF8 *what, const UTF8 *cargs[], const int ncargs)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(caller);
|
|
|
|
|
|
UNUSED_PARAMETER(enactor);
|
2006-09-06 06:17:46 +00:00
|
|
|
|
UNUSED_PARAMETER(eval);
|
2007-09-02 13:48:10 -07:00
|
|
|
|
UNUSED_PARAMETER(cargs);
|
|
|
|
|
|
UNUSED_PARAMETER(ncargs);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
|
|
|
|
|
// 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)))
|
|
|
|
|
|
{
|
2026-07-27 00:47:16 +00:00
|
|
|
|
notify_quiet(executor, M_("Sorry, that object is protected. Use @destroy/override to destroy it."));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-04 11:29:40 -07:00
|
|
|
|
// Check INDESTRUCTIBLE flag -- cannot be bypassed, even by Wizards.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (Indestructible(thing))
|
|
|
|
|
|
{
|
2026-07-27 00:47:16 +00:00
|
|
|
|
notify_quiet(executor, M_("That object is indestructible."));
|
2026-03-04 11:29:40 -07:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2006-09-01 22:59:23 +00:00
|
|
|
|
// Make sure we're not trying to destroy a special object.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (!destroyable(thing))
|
|
|
|
|
|
{
|
2026-07-26 21:52:48 -06:00
|
|
|
|
notify_quiet(executor, M_("You can’t destroy that!"));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Make sure we can do it, on a type-specific basis.
|
|
|
|
|
|
//
|
2007-09-16 14:36:47 -07:00
|
|
|
|
if ( isPlayer(thing)
|
|
|
|
|
|
&& !can_destroy_player(executor, thing))
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2007-03-25 17:01:06 +00:00
|
|
|
|
size_t nCased;
|
|
|
|
|
|
UTF8 *pCased = mux_strlwr(object_types[Typeof(thing)].name, nCased);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
if (Going(thing))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!mudconf.destroy_going_now)
|
|
|
|
|
|
{
|
2026-07-27 15:04:54 +00:00
|
|
|
|
notify_quiet(executor, tprintf(M_("No sense beating a dead %s."), pCased));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
key |= DEST_INSTANT;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Check whether we should perform instant destruction.
|
|
|
|
|
|
//
|
2022-03-14 16:08:21 -06:00
|
|
|
|
const dbref ThingOwner = Owner(thing);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
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:
|
2026-07-27 00:47:16 +00:00
|
|
|
|
notify_all(thing, executor, M_("The room shakes and begins to crumble."));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
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;
|
2026-07-27 00:47:16 +00:00
|
|
|
|
notify(executor, M_("Player has a lot of attributes. Performing destruction immediately."));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// FALL THROUGH
|
|
|
|
|
|
|
|
|
|
|
|
case TYPE_EXIT:
|
|
|
|
|
|
case TYPE_THING:
|
2026-07-27 15:04:54 +00:00
|
|
|
|
notify(executor, tprintf(M_("The %s shakes and begins to crumble."),
|
2007-03-25 17:01:06 +00:00
|
|
|
|
pCased));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
2026-07-27 00:47:16 +00:00
|
|
|
|
notify(executor, M_("Weird object type cannot be destroyed."));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ( !bInstant
|
|
|
|
|
|
&& !Quiet(thing)
|
|
|
|
|
|
&& !Quiet(ThingOwner))
|
|
|
|
|
|
{
|
|
|
|
|
|
notify_quiet(ThingOwner,
|
2026-07-27 15:04:54 +00:00
|
|
|
|
tprintf(M_("You will be rewarded shortly for %s(#%d)."),
|
2006-09-01 22:59:23 +00:00
|
|
|
|
Moniker(thing), thing));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Imperative Destruction emits.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (!Quiet(executor))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Good_owner(ThingOwner))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (ThingOwner == Owner(executor))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!Quiet(thing))
|
|
|
|
|
|
{
|
2026-07-27 15:04:54 +00:00
|
|
|
|
notify(executor, tprintf(M_("Destroyed %s(#%d)."),
|
2006-09-01 22:59:23 +00:00
|
|
|
|
Moniker(thing), thing));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (ThingOwner == thing)
|
|
|
|
|
|
{
|
2026-07-27 15:04:54 +00:00
|
|
|
|
notify(executor, tprintf(M_("Destroyed %s(#%d)."),
|
2006-09-01 22:59:23 +00:00
|
|
|
|
Moniker(thing), thing));
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
Migrate 22 alloc_lbuf/free_lbuf sites to LBuf RAII
Replace manual alloc_lbuf/free_lbuf pairs with LBuf RAII wrappers
in 12 engine source files: rob, walk, quota, wiz, session, object,
log, match, move, create, flags, boolexp. This eliminates ~40
explicit free_lbuf calls on error paths that are now handled by
destructors, removing leak risk in early-return and multi-exit
functions (boolexp alone had 13 exit-path frees across two
functions). Buffers returned by atr_get/atr_pget or to callers
are left manual since LBuf cannot adopt externally-allocated
buffers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 17:46:22 -06:00
|
|
|
|
LBuf tname = LBuf_Src("destroy_obj");
|
2006-09-01 22:59:23 +00:00
|
|
|
|
mux_strncpy(tname, Moniker(ThingOwner), LBUF_SIZE-1);
|
2026-07-27 15:04:54 +00:00
|
|
|
|
notify(executor, tprintf(M_("Destroyed %s’s %s(#%d)."),
|
Migrate 22 alloc_lbuf/free_lbuf sites to LBuf RAII
Replace manual alloc_lbuf/free_lbuf pairs with LBuf RAII wrappers
in 12 engine source files: rob, walk, quota, wiz, session, object,
log, match, move, create, flags, boolexp. This eliminates ~40
explicit free_lbuf calls on error paths that are now handled by
destructors, removing leak risk in early-return and multi-exit
functions (boolexp alone had 13 exit-path frees across two
functions). Buffers returned by atr_get/atr_pget or to callers
are left manual since LBuf cannot adopt externally-allocated
buffers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 17:46:22 -06:00
|
|
|
|
tname.get(), Moniker(thing), thing));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2007-09-28 22:52:23 +00:00
|
|
|
|
ProcessMasterRoomADestroy(executor, thing);
|
2007-09-12 00:51:39 +00:00
|
|
|
|
|
2006-09-01 22:59:23 +00:00
|
|
|
|
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:
|
2026-07-27 00:47:16 +00:00
|
|
|
|
notify(executor, M_("Weird object type cannot be destroyed."));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
s_Going(thing);
|
Implement Phase 1 routing: static unconditional next-hop tables
Add route() softcode function with BFS-based shortest-path routing
over rooms marked NAVIGABLE. The routing table stores only the next-hop
exit for each (source, dest) pair, compressed via diagonal elimination,
adjacent marking, and row redundancy. Lazy rebuild on generation-counter
mismatch triggered by topology changes (@dig, @destroy, @link, @open,
@unlink) and NAVIGABLE flag changes. SQLite schema v10 adds route_nodes,
route_table, route_meta tables for future persistence phases.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 03:18:37 -06:00
|
|
|
|
route_invalidate();
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|