/* $Header: /belch_a/users/rearl/tinymuck/src/RCS/create.c,v 1.9 90/09/28 12:19:26 rearl Exp $ */
/*
* $Log: create.c,v $
* Revision 1.9 90/09/28 12:19:26 rearl
* Added MUCKER check to @edit command.
*
* Revision 1.8 90/09/18 07:54:22 rearl
* Miscellaneous stuff -- moved .sp.program.locked to the new INTERNAL flag.
*
* Revision 1.7 90/09/16 04:41:50 rearl
* Preparation code added for disk-based MUCK.
*
* Revision 1.6 90/08/27 03:21:46 rearl
* Changes in link parsing, disk-based MUF source code.
*
* Revision 1.5 90/08/15 02:57:42 rearl
* Removed some extraneous stuff, consolidated others, general cleanup.
*
* Revision 1.4 90/08/05 03:19:08 rearl
* Redid matching routines.
*
* Revision 1.3 90/08/02 18:48:52 rearl
* Fixed some calls to logging functions.
*
* Revision 1.2 90/08/02 02:15:29 rearl
* Fixed JUMP_OK player <-> room correlations. JUMP_OK players now
* create JUMP_OK rooms when they @dig.
*
* Revision 1.1 90/07/19 23:03:22 casie
* Initial revision
*
*
*/
#include "copyright.h"
#include "config.h"
/* Commands that create new objects */
#include "db.h"
#include "params.h"
#include "interface.h"
#include "externs.h"
#include "match.h"
#include "strings.h"
#include <ctype.h>
struct line *read_program(dbref i);
/* parse_linkable_dest()
*
* A utility for open and link which checks whether a given destination
* string is valid. It returns a parsed dbref on success, and NOTHING
* on failure.
*/
static dbref parse_linkable_dest(dbref player, dbref exit,
const char *dest_name)
{
dbref dobj; /* destination room/player/thing/link */
static char buf[BUFFER_LEN];
struct match_data md;
init_match(player, dest_name, NOTYPE, &md);
match_absolute(&md);
match_everything(&md);
match_home(&md);
if ((dobj = match_result(&md)) == NOTHING || dobj == AMBIGUOUS) {
sprintf(buf, "I couldn't find '%s'.", dest_name);
notify(player, buf);
return NOTHING;
#ifndef TELEPORT_TO_PLAYER
}
if (Typeof(dobj) == TYPE_PLAYER) {
sprintf(buf, "You can't link to players. Destination %s ignored.",
unparse_object(player, dobj));
notify(player, buf);
return NOTHING;
#endif /* TELEPORT_TO_PLAYER */
}
if (!can_link(player, exit)) {
notify(player, "You can't link that.");
return NOTHING;
}
if (!can_link_to(player, Typeof(exit), dobj)) {
sprintf(buf, "You can't link to %s.", unparse_object(player, dobj));
notify(player, buf);
return NOTHING;
} else {
return dobj;
}
}
/* exit_loop_check()
*
* Recursive check for loops in destinations of exits. Checks to see
* if any circular references are present in the destination chain.
* Returns 1 if circular reference found, 0 if not.
*/
int exit_loop_check(dbref source, dbref dest)
{
int i;
if (source == dest) return 1; /* That's an easy one! */
if (Typeof(dest) != TYPE_EXIT) return 0;
for (i = 0; i < DBFETCH(dest)->sp.exit.ndest; i++) {
if ( (DBFETCH(dest)->sp.exit.dest)[i] == source) {
return 1; /* Found a loop! */
}
if (Typeof((DBFETCH(dest)->sp.exit.dest)[i]) == TYPE_EXIT) {
if (exit_loop_check(source, (DBFETCH(dest)->sp.exit.dest)[i])) {
return 1; /* Found one recursively */
}
}
}
return 0; /* No loops found */
}
/* use this to create an exit */
void do_open(dbref player, const char *direction, const char *linkto)
{
dbref loc, exit;
dbref good_dest[MAX_LINKS];
int i, ndest;
if(!Builder(player)) {
notify(player, "That command is restricted to authorized builders.");
return;
}
if((loc = getloc(player)) == NOTHING) return;
if(!*direction) {
notify(player, "You must specify a direction or action name to open.");
return;
} else if(!ok_name(direction)) {
notify(player, "That's a strange name for an exit!");
return;
}
if(!controls(player, loc)) {
notify(player, "Permission denied.");
} else if(!payfor(player, EXIT_COST)) {
notify(player,
"Sorry, you don't have enough pennies to open an exit.");
} else {
/* create the exit */
char buf[BUFFER_LEN];
exit = new_object();
/* initialize everything */
NAME(exit) = alloc_string(direction);
DBFETCH(exit)->location = loc;
OWNER(exit) = OWNER(player);
FLAGS(exit) = TYPE_EXIT;
DBFETCH(exit)->sp.exit.ndest = 0;
DBFETCH(exit)->sp.exit.dest = NULL;
/* link it in */
PUSH(exit, DBFETCH(loc)->exits);
DBDIRTY(loc);
/* and we're done */
sprintf(buf, "Exit opened with number %d.", (int) exit);
notify(player, buf);
sprintf(buf, "#%d", (int) exit);
add_attr(player, "It", buf);
/* check second arg to see if we should do a link */
if(*linkto != '\0') {
notify(player, "Trying to link...");
if(!payfor(player, LINK_COST)) {
notify(player, "You don't have enough pennies to link.");
return;
}
ndest = link_exit(player, exit, (char *) linkto, good_dest);
DBFETCH(exit)->sp.exit.ndest = ndest;
DBFETCH(exit)->sp.exit.dest = (dbref *) malloc(sizeof(dbref)*ndest);
for (i = 0; i < ndest; i++) {
(DBFETCH(exit)->sp.exit.dest)[i] = good_dest[i];
}
DBDIRTY(exit);
}
}
}
/*
* link_exit()
*
* This routine connects an exit to a bunch of destinations.
*
* 'player' contains the player's name.
* 'exit' is the the exit whose destinations are to be linked.
* 'dest_name' is a character string containing the list of exits.
*
* 'dest_list' is an array of dbref's where the valid destinations are
* stored.
*
*/
int link_exit(dbref player, dbref exit, char *dest_name, dbref *dest_list)
{
char *p, *q;
int prdest;
dbref dest;
int ndest;
char buf[BUFFER_LEN], qbuf[BUFFER_LEN];
prdest = 0;
ndest = 0;
while (*dest_name) {
while (isspace(*dest_name)) dest_name++; /* skip white space */
p = dest_name;
while (*dest_name && (*dest_name != EXIT_DELIMITER)) dest_name++;
q = strncpy(qbuf, p, BUFFER_LEN); /* copy word */
q[(dest_name - p)] = '\0'; /* terminate it */
if (*dest_name)
for (dest_name++; *dest_name && isspace(*dest_name); dest_name++);
if ((dest = parse_linkable_dest(player, exit, q)) == NOTHING) continue;
switch (Typeof(dest)) {
case TYPE_PLAYER:
case TYPE_ROOM:
case TYPE_PROGRAM:
if (prdest) {
sprintf (buf, "Only one player, room, or program destination allowed. Destination %s ignored.", unparse_object(player, dest));
notify(player, buf);
continue;
}
dest_list[ndest++] = dest;
prdest = 1;
break;
case TYPE_THING:
dest_list[ndest++] = dest;
break;
case TYPE_EXIT:
if (exit_loop_check(exit, dest)) {
sprintf(buf, "Destination %s would create a loop, ignored.",
unparse_object(player, dest));
notify(player, buf);
continue;
}
dest_list[ndest++] = dest;
break;
default:
notify(player, "Internal error: weird object type.");
log_status("PANIC: weird object: Typeof(%d) = %d\n",
dest, Typeof(dest));
break;
}
if (dest == HOME) {
notify(player, "Linked to HOME.");
} else {
sprintf(buf, "Linked to %s.", unparse_object(player, dest));
notify(player, buf);
}
if (ndest >= MAX_LINKS) {
notify(player, "Too many destinations, rest ignored.");
break;
}
}
return ndest;
}
/* do_link
*
* Use this to link to a room that you own. It also sets home for
* objects and things, and drop-to's for rooms.
* It seizes ownership of an unlinked exit, and costs 1 penny
* plus a penny transferred to the exit owner if they aren't you
*
* All destinations must either be owned by you, or be LINK_OK.
*/
void do_link(dbref player, const char *thing_name, const char *dest_name)
{
dbref thing;
dbref dest;
dbref good_dest[MAX_LINKS];
struct match_data md;
int ndest, i;
init_match(player, thing_name, TYPE_EXIT, &md);
match_all_exits(&md);
match_neighbor(&md);
match_possession(&md);
match_me(&md);
match_here(&md);
match_absolute(&md);
if(Arch(player)) {
match_player(&md);
}
if((thing = noisy_match_result(&md)) == NOTHING) return;
switch(Typeof(thing)) {
case TYPE_GARBAGE:
notify(player, "Bad destination.");
return;
case TYPE_EXIT:
/* we're ok, check the usual stuff */
if(DBFETCH(thing)->sp.exit.ndest != 0) {
if(controls(player, thing)) {
notify(player, "That exit is already linked.");
return;
} else {
notify(player, "Permission denied.");
return;
}
}
/* handle costs */
if(OWNER(thing) == OWNER(player)) {
if(!payfor(player, LINK_COST)) {
notify(player,
"It costs a penny to link this exit.");
return;
}
} else {
if(!payfor(player, LINK_COST + EXIT_COST)) {
notify(player,
"It costs two pennies to link this exit.");
return;
} else if(!Builder(player)) {
notify(player,
"Only authorized builders may seize exits.");
return;
} else {
/* pay the owner for his loss */
dbref owner = OWNER(thing);
DBFETCH(owner)->sp.player.pennies += EXIT_COST;
DBDIRTY(owner);
}
}
/* link has been validated and paid for; do it */
OWNER(thing) = OWNER(player);
ndest = link_exit(player, thing, (char *) dest_name, good_dest);
if (ndest == 0) {
notify(player, "No destinations linked.");
DBFETCH(player)->sp.player.pennies += LINK_COST; /* Refund! */
DBDIRTY(player);
break;
}
DBFETCH(thing)->sp.exit.ndest = ndest;
DBFETCH(thing)->sp.exit.dest = (dbref *) malloc(sizeof(dbref)*ndest);
for (i = 0; i < ndest; i++) {
(DBFETCH(thing)->sp.exit.dest)[i] = good_dest[i];
}
break;
case TYPE_THING:
case TYPE_PLAYER:
init_match(player, dest_name, TYPE_ROOM, &md);
match_neighbor(&md);
match_absolute(&md);
match_me(&md);
match_here(&md);
if ((dest = noisy_match_result(&md)) == NOTHING)
return;
if (!controls(player, thing)
|| !can_link_to(player, Typeof(thing), dest)) {
notify(player, "Permission denied.");
return;
}
/* do the link */
if (Typeof(thing) == TYPE_THING) {
DBFETCH(thing)->link = dest;
}
else
DBFETCH(thing)->link = dest;
notify(player, "Home set.");
break;
case TYPE_ROOM: /* room dropto's */
init_match(player, dest_name, TYPE_ROOM, &md);
match_neighbor(&md);
match_absolute(&md);
match_home(&md);
if ((dest = noisy_match_result(&md)) == NOTHING)
break;
if (!controls(player, thing) || !can_link_to(player, Typeof(thing), dest)
|| (thing == dest)) {
notify(player, "Permission denied.");
} else {
DBFETCH(thing)->link = dest; /* dropto */
notify(player, "Dropto set.");
}
break;
case TYPE_PROGRAM:
notify(player, "You can't link programs to things!");
break;
default:
notify(player, "Internal error: weird object type.");
log_status("PANIC: weird object: Typeof(%d) = %d\n",
thing, Typeof(thing));
break;
}
DBDIRTY(thing);
return;
}
/*
* do_dig
*
* Use this to create a room.
*/
void do_dig(dbref player, const char *name, const char *pname)
{
dbref room;
dbref parent;
char buf[BUFFER_LEN];
struct match_data md;
if(!Builder(player)) {
notify(player, "That command is restricted to authorized builders.");
return;
}
if(*name == '\0') {
notify(player, "You must specify a name for the room.");
return;
}
if(!ok_name(name)) {
notify(player, "That's a silly name for a room!");
return;
}
if(!payfor(player, ROOM_COST)) {
notify(player, "Sorry, you don't have enough pennies to dig a room.");
return;
}
room = new_object();
/* Initialize everything */
NAME(room) = alloc_string(name);
DBFETCH(room)->location = GLOBAL_ENVIRONMENT;
OWNER(room) = OWNER(player);
DBFETCH(room)->exits = NOTHING;
DBFETCH(room)->link = NOTHING;
FLAGS(room) = TYPE_ROOM;
PUSH(room, DBFETCH(GLOBAL_ENVIRONMENT)->contents);
DBDIRTY(room);
DBDIRTY(GLOBAL_ENVIRONMENT);
sprintf(buf, "%s created with room number %d.", name, (int) room);
notify(player, buf);
sprintf(buf, "#%d", (int) room);
add_attr(player, "It", buf);
if (*pname) {
notify(player, "Trying to set parent...");
init_match(player, pname, TYPE_ROOM, &md);
match_absolute(&md);
match_here(&md);
if ((parent = noisy_match_result(&md)) == NOTHING
|| parent == AMBIGUOUS) {
notify(player, "Parent set to default.");
} else {
if (!can_link_to(player, Typeof(room), parent) || room == parent) {
notify(player, "Permission denied. Parent set to default.");
} else {
moveto(room, parent);
sprintf(buf, "Parent set to %s.", unparse_object(player, parent));
notify(player, buf);
return;
}
}
/* Open a direction */
{
char buf[80];
sprintf(buf,"#%d",room);
do_open(player,pname,buf);
}
}
}
/*
Use this to create a program.
First, find a program that matches that name. If there's one,
then we put him into edit mode and do it.
Otherwise, we create a new object for him, and call it a program.
*/
void
do_prog(dbref player, const char *name)
{
dbref i;
dbref newprog;
char buf[BUFFER_LEN];
struct match_data md;
if (!Mucker(player)) {
notify(player, "You're no programmer!");
return;
}
if (!*name) {
notify(player, "No program name given.");
return;
}
init_match(player, name, TYPE_PROGRAM, &md);
match_possession(&md);
match_neighbor(&md);
match_absolute(&md);
if ((i = match_result(&md)) == NOTHING) {
newprog = new_object();
NAME(newprog) = alloc_string(name);
sprintf(buf, "A scroll containing a spell called %s", name);
add_attr(newprog, "Desc", buf);
DBFETCH(newprog)->location = player;
FLAGS(newprog) = TYPE_PROGRAM|STICKY;
OWNER(newprog) = OWNER(player);
DBFETCH(newprog)->sp.program.first = 0;
DBFETCH(newprog)->sp.program.curr_line = 0;
DBFETCH(newprog)->sp.program.siz = 0;
DBFETCH(newprog)->sp.program.code = 0;
DBFETCH(newprog)->sp.program.start = 0;
DBFETCH(player)->curr_prog = newprog;
PUSH(newprog, DBFETCH(player)->contents);
DBDIRTY(newprog);
DBDIRTY(player);
sprintf(buf, "Program %s created with number %d.", name, (int) newprog);
notify(player, buf);
sprintf(buf, "#%d", (int) newprog);
add_attr(player, "It", buf);
notify(player, "Entering editor.");
} else if (i == AMBIGUOUS) {
notify(player, "I don't know which one you mean!");
return;
} else {
if ((Typeof(i) != TYPE_PROGRAM) || !controls(player, i)) {
notify(player, "Permission denied!");
return;
}
if (Wizard(i) && !Wizard(player)) {
notify(player, "Can't edit a wiz program!");
return;
}
if (FLAGS(i) & INTERNAL) {
notify(player, "Sorry, this program is currently being edited. Try again later.");
return;
}
DBFETCH(i)->sp.program.first = read_program(i);
FLAGS(i) |= INTERNAL;
DBFETCH(player)->curr_prog = i;
notify(player, "Entering editor.");
/* list current line */
do_list(player, i, 0, 0);
DBDIRTY(i);
}
FLAGS(player) |= INTERACTIVE;
DBDIRTY(player);
}
void
do_edit(dbref player, const char *name)
{
dbref i;
struct match_data md;
if (!Mucker(player)) {
notify(player, "You're no programmer!");
return;
}
if (!*name) {
notify(player, "No program name given.");
return;
}
init_match(player, name, TYPE_PROGRAM, &md);
match_possession(&md);
match_neighbor(&md);
match_absolute(&md);
if ((i = noisy_match_result(&md)) == NOTHING || i == AMBIGUOUS) return;
if ((Typeof(i) != TYPE_PROGRAM) || !controls(player, i)) {
notify(player, "Permission denied!");
return;
}
if (Wizard(i) && !Wizard(player)) {
notify(player, "Can't edit a wiz program!");
return;
}
if (FLAGS(i) & INTERNAL) {
notify(player, "Sorry, this program is currently being edited. Try again later.");
return;
}
FLAGS(i) |= INTERNAL;
DBFETCH(i)->sp.program.first = read_program(i);
DBFETCH(player)->curr_prog = i;
notify(player, "Entering editor.");
/* list current line */
do_list(player, i, 0, 0);
FLAGS(player) |= INTERACTIVE;
DBDIRTY(i);
DBDIRTY(player);
}
/*
* do_create
*
* Use this to create an object.
*/
void do_create(dbref player, char *name, int cost)
{
dbref thing;
static char buf[BUFFER_LEN];
if(!Builder(player)) {
notify(player, "That command is restricted to authorized builders.");
return;
}
if(!name || *name == '\0') {
notify(player, "Create what?");
return;
} else if(!ok_name(name)) {
notify(player, "That's a silly name for a thing!");
return;
} else if(cost < 0) {
notify(player, "You can't create an object for less than nothing!");
return;
} else if(cost < OBJECT_COST) {
cost = OBJECT_COST;
}
if(!payfor(player, cost)) {
notify(player, "Sorry, you don't have enough pennies.");
} else {
/* create the object */
thing = new_object();
/* initialize everything */
NAME(thing) = alloc_string(name);
DBFETCH(thing)->location = player;
OWNER(thing) = OWNER(player);
DBFETCH(thing)->sp.thing.value = OBJECT_ENDOWMENT(cost);
DBFETCH(thing)->exits = NOTHING;
FLAGS(thing) = TYPE_THING;
/* endow the object */
if(DBFETCH(thing)->sp.thing.value > MAX_OBJECT_ENDOWMENT) {
DBFETCH(thing)->sp.thing.value = MAX_OBJECT_ENDOWMENT;
}
DBFETCH(thing)->link = DBFETCH(player)->link; /* set to player's home */
/* link it in */
PUSH(thing, DBFETCH(player)->contents);
DBDIRTY(player);
/* and we're done */
sprintf(buf, "%s created with number %d.", name, (int) thing);
notify(player, buf);
sprintf(buf, "#%d", (int) thing);
add_attr(player, "It", buf);
DBDIRTY(thing);
}
}
/*
* parse_source()
*
* This is a utility used by do_action and do_attach. It parses
* the source string into a dbref, and checks to see that it
* exists.
*
* The return value is the dbref of the source, or NOTHING if an
* error occurs.
*
*/
dbref parse_source(dbref player, const char *source_name)
{
dbref source;
struct match_data md;
init_match(player, source_name, NOTYPE, &md); /* source type can be any */
match_neighbor(&md);
match_me(&md);
match_here(&md);
match_possession(&md);
match_absolute(&md);
if(Arch(player)) {
match_player(&md);
}
source = noisy_match_result(&md);
if (source == NOTHING) return NOTHING;
/* You can only attach actions to things you control */
if(!controls(player, source)) {
notify(player, "Permission denied.");
return NOTHING;
}
if(Typeof(source) == TYPE_EXIT) {
notify(player, "You can't attach an action to an action.");
return NOTHING;
}
if(Typeof(source) == TYPE_PROGRAM) {
notify(player, "You can't attach an action to a program.");
return NOTHING;
}
return source;
}
/*
* set_source()
*
* This routine sets the source of an action to the specified source.
* It is called by do_action and do_attach.
*
*/
void set_source(dbref player, dbref action, dbref source)
{
switch(Typeof(source)) {
case TYPE_ROOM:
PUSH(action, DBFETCH(source)->exits);
break;
case TYPE_THING:
PUSH(action, DBFETCH(source)->exits);
break;
case TYPE_PLAYER:
PUSH(action, DBFETCH(source)->exits);
break;
default:
notify(player, "Internal error: weird object type.");
log_status("PANIC: tried to source %d to %d: type: %d\n",
action, source, Typeof(source));
return;
break;
}
DBDIRTY(source);
DBSTORE(action, location, source);
return;
}
int unset_source(dbref player, dbref loc, dbref action)
{
dbref oldsrc;
if ( (oldsrc = DBFETCH(action)->location) == NOTHING) {
/* old-style, sourceless exit */
if(!member(action, DBFETCH(loc)->exits)) {
notify(player, "You can't do that to an exit in another room.");
return 0;
}
DBSTORE(DBFETCH(player)->location, exits,
remove_first(DBFETCH(DBFETCH(player)->location)->exits, action));
} else {
switch(Typeof(oldsrc)) {
case TYPE_PLAYER:
DBSTORE(oldsrc, exits,
remove_first(DBFETCH(oldsrc)->exits, action));
break;
case TYPE_ROOM:
DBSTORE(oldsrc, exits,
remove_first(DBFETCH(oldsrc)->exits, action));
break;
case TYPE_THING:
DBSTORE(oldsrc, exits,
remove_first(DBFETCH(oldsrc)->exits, action));
break;
default:
log_status("PANIC: source of action #%d was type: %d.\n",
action, Typeof(oldsrc));
return 0;
/*NOTREACHED*/
break;
}
}
return 1;
}
/*
* do_action()
*
* This routine attaches a new existing action to a source object,
* where possible.
* The action will not do anything until it is LINKed.
*
*/
void do_action(dbref player, const char *action_name, const char *source_name)
{
dbref action, source;
static char buf[BUFFER_LEN];
if(!Builder(player)) {
notify(player, "That command is restricted to authorized builders.");
return;
}
if (!*action_name || !*source_name) {
notify(player, "You must specify an action name and a source object.");
return;
} else if (!ok_name(action_name)) {
notify(player, "That's a strange name for an action!");
return;
} else if(!payfor(player, EXIT_COST)) {
notify(player,
"Sorry, you don't have enough pennies to make an action.");
return;
}
if (((source = parse_source(player, source_name)) == NOTHING)) return;
action = new_object();
NAME(action) = alloc_string(action_name);
DBFETCH(action)->location = NOTHING;
OWNER(action) = OWNER(player);
DBFETCH(action)->sp.exit.ndest = 0;
DBFETCH(action)->sp.exit.dest = NULL;
FLAGS(action) = TYPE_EXIT;
set_source(player, action, source);
sprintf(buf, "Action created with number %d and attached.", (int) action);
notify(player, buf);
sprintf(buf, "#%d", (int) action);
add_attr(player, "It", buf);
DBDIRTY(action);
}
/*
* do_attach()
*
* This routine attaches a previously existing action to a source object.
* The action will not do anything unless it is LINKed.
*
*/
void do_attach(dbref player, const char *action_name, const char *source_name)
{
dbref action, source;
dbref loc; /* player's current location */
struct match_data md;
if ( (loc = DBFETCH(player)->location) == NOTHING) return;
if(!Builder(player)) {
notify(player, "That command is restricted to authorized builders.");
return;
}
if (!*action_name || !*source_name) {
notify(player, "You must specify an action name and a source object.");
return;
}
init_match(player, action_name, TYPE_EXIT, &md);
match_all_exits(&md);
match_absolute(&md);
if ( (action = noisy_match_result(&md) ) == NOTHING) return;
if (Typeof(action) != TYPE_EXIT) {
notify(player, "That's not an action!");
return;
} else if (!controls(player, action)) {
notify(player, "Permission denied.");
return;
}
if (((source = parse_source(player, source_name)) == NOTHING)
|| Typeof(source) == TYPE_PROGRAM) return;
if(!unset_source(player, loc, action)) {
return;
}
set_source(player, action, source);
notify(player, "Action re-attached.");
}