/* $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."); }