/**
* \file create.c
*
* \brief Functions for creating objects of all types.
*
*
*/
#include "copyrite.h"
#include "config.h"
#include <string.h>
#include "conf.h"
#include "externs.h"
#include "mushdb.h"
#include "attrib.h"
#include "match.h"
#include "extchat.h"
#include "log.h"
#include "flags.h"
#include "dbdefs.h"
#include "lock.h"
#include "parse.h"
#include "game.h"
#include "command.h"
#include "confmagic.h"
static dbref parse_linkable_room(dbref player, const char *room_name);
static dbref check_var_link(const char *dest_name);
static dbref clone_object(dbref player, dbref thing, const char *newname,
int preserve);
struct db_stat_info current_state; /**< Current stats for database */
/* utility for open and link */
static dbref
parse_linkable_room(dbref player, const char *room_name)
{
dbref room;
/* parse room */
if (!strcasecmp(room_name, "here")) {
room = IsExit(player) ? Source(player) : Location(player);
} else if (!strcasecmp(room_name, "home")) {
return HOME; /* HOME is always linkable */
} else {
room = parse_dbref(room_name);
}
/* check room */
if (!GoodObject(room)) {
notify(player, T("That is not a valid object."));
return NOTHING;
} else if (Going(room)) {
notify(player, T("That room is being destroyed. Sorry."));
return NOTHING;
} else if (!can_link_to(player, room)) {
notify(player, T("You can't link to that."));
return NOTHING;
} else {
return room;
}
}
static dbref
check_var_link(const char *dest_name)
{
/* This allows an exit to be linked to a 'variable' destination.
* Such exits can be linked by anyone but the owner's ability
* to link to the destination is checked when it's computed.
*/
if (!strcasecmp("VARIABLE", dest_name))
return AMBIGUOUS;
else
return NOTHING;
}
/** Create an exit.
* This function opens an exit and optionally links it.
* \param player the enactor.
* \param direction the name of the exit.
* \param linkto the room to link to, as a string.
* \param pseudo a phony location for player if a back exit is needed. This is bpass by do_open() as the source room of the back exit.
* \return dbref of the new exit, or NOTHING.
*/
dbref
do_real_open(dbref player, const char *direction, const char *linkto,
dbref pseudo)
{
dbref loc =
(pseudo !=
NOTHING) ? pseudo : (IsExit(player) ? Source(player) : Location(player));
dbref new_exit;
if (!command_check_byname(player, "@dig")) {
notify(player, "Permission denied.");
return NOTHING;
}
if ((loc == NOTHING) || (!IsRoom(loc))) {
notify(player, T("Sorry you can only make exits out of rooms."));
return NOTHING;
}
if (Going(loc)) {
notify(player, T("You can't make an exit in a place that's crumbling."));
return NOTHING;
}
if (!*direction) {
notify(player, T("Open where?"));
return NOTHING;
} else if (!ok_name(direction)) {
notify(player, T("That's a strange name for an exit!"));
return NOTHING;
}
if (!Open_Anywhere(player) && !controls(player, loc)) {
notify(player, T("Permission denied."));
} else if (can_pay_fees(player, EXIT_COST)) {
/* create the exit */
new_exit = new_object();
/* initialize everything */
set_name(new_exit, direction);
Owner(new_exit) = Owner(player);
Zone(new_exit) = Zone(player);
Source(new_exit) = loc;
Type(new_exit) = TYPE_EXIT;
Flags(new_exit) = string_to_bits("FLAG", options.exit_flags);
/* link it in */
PUSH(new_exit, Exits(loc));
/* and we're done */
notify_format(player, T("Opened exit %s"), unparse_dbref(new_exit));
/* check second arg to see if we should do a link */
if (linkto && *linkto != '\0') {
notify(player, T("Trying to link..."));
if ((loc = check_var_link(linkto)) == NOTHING)
loc = parse_linkable_room(player, linkto);
if (loc != NOTHING) {
if (!payfor(player, LINK_COST)) {
notify_format(player, T("You don't have enough %s to link."), MONIES);
} else {
/* it's ok, link it */
Location(new_exit) = loc;
notify_format(player, T("Linked exit #%d to #%d"), new_exit, loc);
}
}
}
current_state.exits++;
local_data_create(new_exit);
return new_exit;
}
return NOTHING;
}
/** Open a new exit.
* \verbatim
* This is the top-level function for @open. It calls do_real_open()
* to do the real work of opening both the exit forward and the exit back.
* \endverbatim
* \param player the enactor.
* \param direction name of the exit forward.
* \param links 1-based array containing name of destination and optionally name of exit back.
*/
void
do_open(dbref player, const char *direction, char **links)
{
dbref forward;
forward = do_real_open(player, direction, links[1], NOTHING);
if (links[2] && GoodObject(forward) && GoodObject(Location(forward))) {
do_real_open(player, links[2], "here", Location(forward));
}
}
/** Unlink an exit or room.
* \verbatim
* This is the top-level function for @unlink, which can unlink an exit
* or remove a drop-to from a room.
* \endverbatim
* \param player the enactor.
* \param name name of the object to unlink.
*/
void
do_unlink(dbref player, const char *name)
{
dbref exit_l, old_loc;
long match_flags = MAT_EXIT | MAT_HERE | MAT_ABSOLUTE;
if (!Wizard(player)) {
match_flags |= MAT_CONTROL;
}
switch (exit_l = match_result(player, name, TYPE_EXIT, match_flags)) {
case NOTHING:
notify(player, T("Unlink what?"));
break;
case AMBIGUOUS:
notify(player, T("I don't know which one you mean!"));
break;
default:
if (!controls(player, exit_l)) {
notify(player, T("Permission denied."));
} else {
switch (Typeof(exit_l)) {
case TYPE_EXIT:
old_loc = Location(exit_l);
Location(exit_l) = NOTHING;
notify_format(player, T("Unlinked exit #%d (Used to lead to %s)."),
exit_l, unparse_object(player, old_loc));
break;
case TYPE_ROOM:
Location(exit_l) = NOTHING;
notify(player, T("Dropto removed."));
break;
default:
notify(player, T("You can't unlink that!"));
break;
}
}
}
}
/** Link an exit, room, player, or thing.
* \verbatim
* This is the top-level function for @link, which is used to link an
* exit to a destination, set a player or thing's home, or set a
* drop-to on a room.
*
* Linking an exit usually seizes ownership of the exit and costs 1 penny.
* 1 penny is also transferred to the former owner.
* \endverbatim
* \param player the enactor.
* \param name the name of the object to link.
* \param room_name the name of the link destination.
* \param preserve if 1, preserve ownership and zone data on exit relink.
*/
void
do_link(dbref player, const char *name, const char *room_name, int preserve)
{
/* Use this to link to a room that you own.
* It usually seizes ownership of the exit and costs 1 penny,
* plus a penny transferred to the exit owner if they aren't you.
* You must own the linked-to room AND specify it by room number.
*/
dbref thing;
dbref room;
if (!room_name || !*room_name) {
do_unlink(player, name);
return;
}
if (!IsRoom(player) && GoodObject(Location(player)) &&
IsExit(Location(player))) {
notify(player, T("You somehow wound up in a exit. No biscuit."));
return;
}
if ((thing = noisy_match_result(player, name, TYPE_EXIT, MAT_EVERYTHING))
!= NOTHING) {
switch (Typeof(thing)) {
case TYPE_EXIT:
if ((room = check_var_link(room_name)) == NOTHING)
room = parse_linkable_room(player, room_name);
if (room == NOTHING)
return;
if (GoodObject(room) && !can_link_to(player, room)) {
notify(player, T("Permission denied."));
break;
}
/* We may link an exit if it's unlinked and we pass the link-lock
* or if we control it.
*/
if (!(controls(player, thing)
|| ((Location(thing) == NOTHING)
&& eval_lock(player, thing, Link_Lock)))) {
notify(player, T("Permission denied."));
return;
}
if (preserve && !Wizard(player)) {
notify(player, T("Permission denied."));
return;
}
/* handle costs */
if (Owner(thing) == Owner(player)) {
if (!payfor(player, LINK_COST)) {
notify_format(player, T("It costs %d %s to link this exit."),
LINK_COST, ((LINK_COST == 1) ? MONEY : MONIES));
return;
}
} else {
if (!payfor(player, LINK_COST + EXIT_COST)) {
int a = LINK_COST + EXIT_COST;
notify_format(player, T("It costs %d %s to link this exit."), a,
((a == 1) ? MONEY : MONIES));
return;
} else if (!preserve) {
/* pay the owner for his loss */
giveto(Owner(thing), EXIT_COST);
chown_object(player, thing, player, 0);
}
}
/* link has been validated and paid for; do it */
if (!preserve) {
Owner(thing) = Owner(player);
Zone(thing) = Zone(player);
}
Location(thing) = room;
/* notify the player */
notify_format(player, T("Linked exit #%d to %s"), thing,
unparse_object(player, room));
break;
case TYPE_PLAYER:
case TYPE_THING:
if ((room =
noisy_match_result(player, room_name, NOTYPE, MAT_EVERYTHING)) < 0) {
notify(player, T("No match."));
return;
}
if (IsExit(room)) {
notify(player, T("That is an exit."));
return;
}
if (thing == room) {
notify(player, T("You may not link something to itself."));
return;
}
/* abode */
if (!controls(player, room) && !Abode(room)) {
notify(player, T("Permission denied."));
break;
}
if (!controls(player, thing)) {
notify(player, T("Permission denied."));
} else if (room == HOME) {
notify(player, T("Can't set home to home."));
} else {
/* do the link */
Home(thing) = room; /* home */
if (!Quiet(player) && !(Quiet(thing) && (Owner(thing) == player)))
notify(player, T("Home set."));
}
break;
case TYPE_ROOM:
if ((room = parse_linkable_room(player, room_name)) == NOTHING)
return;
if ((room != HOME) && (!IsRoom(room))) {
notify(player, T("That is not a room!"));
return;
}
if (!controls(player, thing)) {
notify(player, T("Permission denied."));
} else {
/* do the link, in location */
Location(thing) = room; /* dropto */
notify(player, T("Dropto set."));
}
break;
default:
notify(player, "Internal error: weird object type.");
do_log(LT_ERR, NOTHING, NOTHING,
T("Weird object! Type of #%d is %d"), thing, Typeof(thing));
break;
}
}
}
/** Create a room.
* \verbatim
* This is the top-level interface for @dig.
* \endverbatim
* \param player the enactor.
* \param name the name of the room to create.
* \param argv array of additional arguments to command (exit forward,exit back)
* \param tport if 1, teleport the player to the new room.
* \return dbref of new room, or NOTHING.
*/
dbref
do_dig(dbref player, const char *name, char **argv, int tport)
{
dbref room;
/* we don't need to know player's location! hooray! */
if (*name == '\0') {
notify(player, T("Dig what?"));
} else if (!ok_name(name)) {
notify(player, T("That's a silly name for a room!"));
} else if (can_pay_fees(player, ROOM_COST)) {
room = new_object();
/* Initialize everything */
set_name(room, name);
Owner(room) = Owner(player);
Zone(room) = Zone(player);
Type(room) = TYPE_ROOM;
Flags(room) = string_to_bits("FLAG", options.room_flags);
notify_format(player, T("%s created with room number %d."), name, room);
if (argv[1] && *argv[1]) {
char nbuff[MAX_COMMAND_LEN];
sprintf(nbuff, "#%d", room);
do_real_open(player, argv[1], nbuff, NOTHING);
}
if (argv[2] && *argv[2]) {
do_real_open(player, argv[2], "here", room);
}
current_state.rooms++;
local_data_create(room);
if (tport) {
/* We need to use the full command, because we need NO_TEL
* and Z_TEL checking */
char roomstr[MAX_COMMAND_LEN];
sprintf(roomstr, "#%d", room);
do_teleport(player, "me", roomstr, 0, 0); /* if flag, move the player */
}
return room;
}
return NOTHING;
}
/** Create a thing.
* \verbatim
* This is the top-level function for @create.
* \endverbatim
* \param player the enactor.
* \param name name of thing to create.
* \param cost pennies spent in creation.
* \return dbref of new thing, or NOTHING.
*/
dbref
do_create(dbref player, char *name, int cost)
{
dbref loc;
dbref thing;
if (*name == '\0') {
notify(player, T("Create what?"));
return NOTHING;
} else if (!ok_name(name)) {
notify(player, T("That's a silly name for a thing!"));
return NOTHING;
} else if (cost < OBJECT_COST) {
cost = OBJECT_COST;
}
if (can_pay_fees(player, cost)) {
/* create the object */
thing = new_object();
/* initialize everything */
set_name(thing, name);
if (!IsExit(player)) /* Exits shouldn't have contents! */
Location(thing) = player;
else
Location(thing) = Source(player);
Owner(thing) = Owner(player);
Zone(thing) = Zone(player);
s_Pennies(thing, cost);
Type(thing) = TYPE_THING;
Flags(thing) = string_to_bits("FLAG", options.thing_flags);
/* home is here (if we can link to it) or player's home */
if ((loc = Location(player)) != NOTHING &&
(controls(player, loc) || Abode(loc))) {
Home(thing) = loc; /* home */
} else {
Home(thing) = Home(player); /* home */
}
/* link it in */
if (!IsExit(player))
PUSH(thing, Contents(player));
else
PUSH(thing, Contents(Source(player)));
/* and we're done */
notify_format(player, "Created: Object %s.", unparse_dbref(thing));
current_state.things++;
local_data_create(thing);
return thing;
}
return NOTHING;
}
/* Clone an object. The new object is owned by the cloning player */
static dbref
clone_object(dbref player, dbref thing, const char *newname, int preserve)
{
dbref clone;
clone = new_object();
memcpy(REFDB(clone), REFDB(thing), sizeof(struct object));
Owner(clone) = Owner(player);
Name(clone) = NULL;
if (newname && *newname)
set_name(clone, newname);
else
set_name(clone, Name(thing));
s_Pennies(clone, Pennies(thing));
atr_cpy(clone, thing);
Locks(clone) = NULL;
clone_locks(player, thing, clone);
Zone(clone) = Zone(thing);
Parent(clone) = Parent(thing);
Flags(clone) = clone_flag_bitmask("FLAG", Flags(thing));
if (!preserve) {
clear_flag_internal(clone, "WIZARD");
clear_flag_internal(clone, "ROYALTY");
Warnings(clone) = 0; /* zap warnings */
Powers(clone) = new_flag_bitmask("POWER"); /* zap powers */
} else {
Powers(clone) = clone_flag_bitmask("POWER", Powers(thing));
if (Wizard(clone) || Royalty(clone) || Warnings(clone) ||
!null_flagmask("POWER", Powers(clone)))
notify(player,
T
("Warning: @CLONE/PRESERVE on an object with WIZ, ROY, @powers, or @warnings."));
}
/* We give the clone the same modification time that its
* other clone has, but update the creation time */
CreTime(clone) = mudtime;
Contents(clone) = Location(clone) = Next(clone) = NOTHING;
return clone;
}
/** Clone an object.
* \verbatim
* This is the top-level function for @clone, which creates a duplicate
* of a (non-player) object.
* \endverbatim
* \param player the enactor.
* \param name the name of the object to clone.
* \param newname the name to give the duplicate.
* \param preserve if 1, preserve ownership and privileges on duplicate.
* \return dbref of the duplicate, or NOTHING.
*/
dbref
do_clone(dbref player, char *name, char *newname, int preserve)
{
dbref clone, thing;
char dbnum[BUFFER_LEN];
if (newname && *newname && !ok_name(newname)) {
notify(player, T("That is not a reasonable name."));
return NOTHING;
}
thing = noisy_match_result(player, name, NOTYPE, MAT_EVERYTHING);
if ((thing == NOTHING))
return NOTHING;
if (!controls(player, thing) || IsPlayer(thing) ||
(IsRoom(thing) && !command_check_byname(player, "@dig")) ||
(IsExit(thing) && !command_check_byname(player, "@open")) ||
(IsThing(thing) && !command_check_byname(player, "@create"))) {
notify(player, T("Permission denied."));
return NOTHING;
}
/* don't allow cloning of destructed things */
if (IsGarbage(thing)) {
notify(player, T("There's nothing left of it to clone!"));
return NOTHING;
}
if (preserve && !Wizard(player)) {
notify(player,
T("You cannot @CLONE/PRESERVE. Use normal @CLONE instead."));
return NOTHING;
}
/* make sure owner can afford it */
switch (Typeof(thing)) {
case TYPE_THING:
if (can_pay_fees(player, Pennies(thing))) {
clone = clone_object(player, thing, newname, preserve);
notify_format(player, T("Cloned: Object %s."), unparse_dbref(clone));
if (IsRoom(player))
moveto(clone, player);
else
moveto(clone, Location(player));
current_state.things++;
local_data_clone(clone, thing);
real_did_it(player, clone, NULL, NULL, NULL, NULL, "ACLONE", NOTHING,
global_eval_context.wenv, 0);
return clone;
}
return NOTHING;
break;
case TYPE_ROOM:
if (can_pay_fees(player, ROOM_COST)) {
clone = clone_object(player, thing, newname, preserve);
Exits(clone) = NOTHING;
notify_format(player, T("Cloned: Room #%d."), clone);
current_state.rooms++;
local_data_clone(clone, thing);
real_did_it(player, clone, NULL, NULL, NULL, NULL, "ACLONE", NOTHING,
global_eval_context.wenv, 0);
return clone;
}
return NOTHING;
break;
case TYPE_EXIT:
/* For exits, we don't want people to be able to link it to
a location they can't with @open. So, all this stuff.
*/
switch (Location(thing)) {
case NOTHING:
strcpy(dbnum, "#-1");
break;
case HOME:
strcpy(dbnum, "home");
break;
case AMBIGUOUS:
strcpy(dbnum, "variable");
break;
default:
strcpy(dbnum, unparse_dbref(Location(thing)));
}
if (newname && *newname)
clone = do_real_open(player, newname, dbnum, NOTHING);
else
clone = do_real_open(player, Name(thing), dbnum, NOTHING);
if (!GoodObject(clone))
return NOTHING;
else {
atr_cpy(clone, thing);
clone_locks(player, thing, clone);
Zone(clone) = Zone(thing);
Parent(clone) = Parent(thing);
Flags(clone) = clone_flag_bitmask("FLAG", Flags(thing));
if (!preserve) {
clear_flag_internal(clone, "WIZARD");
clear_flag_internal(clone, "ROYALTY");
Warnings(clone) = 0; /* zap warnings */
Powers(clone) = new_flag_bitmask("POWER"); /* zap powers */
} else {
Warnings(clone) = Warnings(thing);
Powers(clone) = clone_flag_bitmask("POWER", Powers(thing));
}
if (Wizard(clone) || Royalty(clone) || Warnings(clone) ||
!null_flagmask("POWER", Powers(clone)))
notify(player,
T
("Warning: @CLONE/PRESERVE on an exit with WIZ, ROY, @powers, or Warnings."));
notify_format(player, T("Cloned: Exit #%d."), clone);
local_data_clone(clone, thing);
return clone;
}
}
return NOTHING;
}