/* This software is Copyright 1989, 1990, 1992, 1993 by various individuals. Please see the accompanying file COPYRIGHT for details. */ #include "config.h" /* Commands that create new objects */ #include "db.h" #include "defs.h" #include "ctype.h" #include "money.h" #include "match.h" #include "params.h" #include "strings.h" #include "externs.h" #include "interface.h" struct line *read_program(dbref i); static int link_exit(dbref, dbref, const char *, dbref *); /* 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 xit, 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); match_builtin(&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, xit)) { notify(player, "You can't link that."); return NOTHING; } if (!can_link_to(player, Typeof(xit), 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; dbref xit; dbref good_dest[MAX_LINKS]; char buf[BUFFER_LEN]; int i; int ndest; struct object *x; if (! Builder(player)) { notify(player,"That command is restricted to authorized builders."); return; } loc = getloc(player); if (loc == NOTHING) return; if (! *direction) { notify(player,"You must specify a direction or action name to open."); return; } if (! ok_name(direction)) { notify(player,"That's a strange name for an exit!"); return; } if (! controls(player,loc)) { notify(player,"Permission denied."); return; } if (! payfor(player,EXIT_COST,"@open exit")) { notify(player,EXIT_ERR); /* defined in money.h */ return; } if (! quota_room(player,1,0)) return; xit = new_object(TYPE_EXIT); x = DBFETCH(xit); x->name = dup_string(direction); x->location = loc; x->owner = player; FLAGS(xit) = TYPE_EXIT; x->sp.exit.ndest = 0; x->sp.exit.dest = NULL; add_ownerlist(xit); PUSH(xit,DBFETCH(loc)->sp.room.exits); DBDIRTY(loc); sprintf(buf,"Exit opened with number %d.",(int)xit); notify(player,buf); if (*linkto) { notify(player,"Trying to link..."); if (! payfor(player,LINK_COST,"@open link")) { notify(player,LINK_ERR); /* defined in money.h */ return; } ndest = link_exit(player,xit,linkto,good_dest); x->sp.exit.ndest = ndest; x->sp.exit.dest = malloc(sizeof(dbref)*ndest); for (i=0;isp.exit.dest[i] = good_dest[i]; } DBDIRTY(xit); } /* * link_exit() * * This routine parses a link-to string into multiple destinations, weeding * out invalid ones. * * 'player' is the player performing the operation. * 'xit' is the the exit whose destinations are being considered. * 'dest_name' is a character string containing the list of exits. * * 'dest_list' is an array [MAX_LINKS] of dbrefs to receive the linktos. */ int link_exit(dbref player, dbref xit, const char *dest_name, dbref *dest_list) { const char *p; char *q; int prdest; dbref dest; int ndest; char buf[BUFFER_LEN]; char qbuf[BUFFER_LEN]; prdest = 0; ndest = 0; while (*dest_name) { while (Cisspace(*dest_name)) dest_name ++; p = dest_name; while (*dest_name && (*dest_name != EXIT_DELIMITER)) dest_name++; snprintf(&qbuf[0],BUFFER_LEN,"%.*s",(int)(dest_name-p),p); q = &qbuf[0]; if (*dest_name) { for (dest_name++;*dest_name&&Cisspace(*dest_name);dest_name++) ; } dest = parse_linkable_dest(player,xit,q); if (dest == NOTHING) continue; if (ndest >= MAX_LINKS) { notify(player,"Too many destinations, rest ignored."); break; } switch (Typeof(dest)) { case TYPE_PLAYER: case TYPE_ROOM: case TYPE_PROGRAM: case TYPE_DAEMON: if (prdest) { snprintf(&buf[0],BUFFER_LEN,"Only one player, room, or program destination allowed. Destination %s ignored.",unparse_object(player,dest)); notify(player,&buf[0]); continue; } add_backlink(xit,dest); dest_list[ndest++] = dest; prdest = 1; break; case TYPE_THING: add_backlink(xit,dest); dest_list[ndest++] = dest; break; case TYPE_EXIT: if (exit_loop_check(xit,dest)) { snprintf(&buf[0],BUFFER_LEN,"Destination %s would create a loop, ignored.",unparse_object(player,dest)); notify(player,&buf[0]); continue; } add_backlink(xit,dest); dest_list[ndest++] = dest; break; case NOTYPE: if (dest == BUILTIN) { dest_list[ndest++] = BUILTIN; break; } /* fall through */ default: notify(player,"Internal error: weird object type."); log_status("Link: weird object: Typeof(%d) = %d",dest,Typeof(dest)); break; } snprintf(&buf[0],BUFFER_LEN,"Linked to %s.",unparse_object(player,dest)); notify(player,&buf[0]); } 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. * * 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; int 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); if (Royalty(player)) { match_absolute(&md); match_player(&md); } thing = noisy_match_result(&md); if (thing == NOTHING) return; switch (Typeof(thing)) { case TYPE_EXIT: if (! controls(player,thing)) { notify(player,"Permission denied."); return; } if (DBFETCH(thing)->sp.exit.ndest != 0) { notify(player,"That exit is already linked."); return; } if (!payfor(player,LINK_COST,"@link")) { notify(player,LINK_INF); /* defined in money.h */ return; } ndest = link_exit(player,thing,dest_name,good_dest); if (ndest == 0) { notify(player,"No destinations linked."); if (!Wizard(player)) add_pennies(player,LINK_COST,"@link"); DBDIRTY(player); break; } DBFETCH(thing)->sp.exit.ndest = ndest; DBFETCH(thing)->sp.exit.dest = malloc(sizeof(dbref)*ndest); for (i=0;isp.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_here(&md); match_me(&md); dest = noisy_match_result(&md); if (dest == NOTHING) return; if ( !controls(player,thing) || !can_link_to(player,Typeof(thing),dest) ) { notify(player,"Permission denied."); return; } switch (Typeof(thing)) { case TYPE_THING: remove_backlinks(thing,DBFETCH(thing)->sp.thing.home); add_backlink(thing,dest); DBFETCH(thing)->sp.thing.home = dest; break; case TYPE_PLAYER: remove_backlinks(thing,DBFETCH(thing)->sp.player.home); add_backlink(thing,dest); DBFETCH(thing)->sp.player.home = dest; break; } notify(player,"Home set."); break; case TYPE_ROOM: init_match(player,dest_name,TYPE_ROOM,&md); match_neighbor(&md); match_absolute(&md); match_possession(&md); match_home(&md); match_me(&md); match_here(&md); dest = noisy_match_result(&md); if (dest == NOTHING) break; if ( !controls(player,thing) || !can_link_to(player,Typeof(thing),dest) || (thing == dest) || ((Typeof(thing) == TYPE_PROGRAM) && !Wizard(player)) ) { notify(player,"Permission denied."); } else { remove_backlinks(thing,DBFETCH(thing)->sp.room.dropto); add_backlink(thing,dest); DBFETCH(thing)->sp.room.dropto = dest; notify(player,"Dropto set."); } break; case TYPE_PROGRAM: notify(player,"You can't link a program to anything!"); break; case TYPE_DAEMON: notify(player,"You can't link a daemon to anything!"); break; default: notify(player,"Internal error: weird object type."); log_status("@link: weird object: Typeof(%d) = %d",thing,Typeof(thing)); break; } DBDIRTY(thing); } /* * 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; struct object *r; 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,"@dig")) { notify(player,DIG_ERR); /* defined in money.h */ return; } if (! quota_room(player,1,0)) return; room = new_object(TYPE_ROOM); FLAGS(room) = TYPE_ROOM | (FLAGS(player) & JUMP_OK); parent = DBFETCH(DBFETCH(player)->location)->location; if (*pname) { init_match(player,pname,TYPE_ROOM,&md); match_absolute(&md); match_here(&md); parent = noisy_match_result(&md); } if ( (parent == NOTHING) || (parent == AMBIGUOUS) || !can_link_to(player,Typeof(room),parent) || (room == parent) ) { parent = GLOBAL_ENVIRONMENT; } r = DBFETCH(room); r->name = dup_string(name); r->location = parent; r->owner = player; r->sp.room.exits = NOTHING; r->sp.room.dropto = NOTHING; r->sp.room.useval = 0; r->sp.room.stamp = curtm(); add_ownerlist(room); PUSH(room,DBFETCH(parent)->contents); DBDIRTY(room); DBDIRTY(parent); sprintf(buf,"%s created with room number #%d, parent #%d.",name,room,parent); notify(player,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; 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); i = match_result(&md); if (i == NOTHING) { if (! quota_room(player,1,0)) return; i = new_object(TYPE_PROGRAM); DBSTORE(i,name,dup_string(name)); sprintf(buf,"A scroll containing a spell called %s",name); DBSTORE(i,description,store_str(buf)); DBSTORE(i,location,player); DBSTORE(i,owner,player); FLAGS(i) = TYPE_PROGRAM; DBSTORE(i,sp.program.first,0); DBSTORE(i,sp.program.curr_line,0); DBSTORE(i,sp.program.codesiz,0); DBSTORE(i,sp.program.codevec,0); DBSTORE(i,sp.program.stabsiz,0); DBSTORE(i,sp.program.stabvec,0); DBSTORE(i,sp.program.start,0); DBSTORE(i,sp.program.proglocks,0); add_ownerlist(i); DBSTORE(player,sp.player.curr_prog,i); PUSH(i,DBFETCH(player)->contents); DBDIRTY(i); DBDIRTY(player); sprintf(buf,"Program %s created with number %d.",name,i); notify(player,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 (check_proglock(i,PROGLOCK_WRITE)) { notify(player, "Sorry, this program is currently being edited. Try again later."); return; } DBSTORE(i,sp.program.first,read_program(i)); DBSTORE(player,sp.player.curr_prog,i); notify(player,"Entering editor."); /* list current line */ do_list(player,i,0,0); DBDIRTY(i); } FLAGS(player) |= INTERACTIVE; add_proglock(i,player,PROGLOCK_WRITE); 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 (check_proglock(i, PROGLOCK_WRITE)) { notify(player, "Sorry, this program is currently being edited. Try again later."); return; } add_proglock(i, player, PROGLOCK_WRITE); DBSTORE(i, sp.program.first, read_program(i)); DBSTORE(player, sp.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 == '\0') { notify(player,"Create what?"); return; } if (! ok_name(name)) { notify(player,"That's a silly name for a thing!"); return; } if (! quota_room(player,1,0)) return; if (cost < 0) { notify(player,"You can't create an object for less than nothing!"); return; } if (cost < OBJECT_COST) cost = OBJECT_COST; if (! payfor(player,cost,"@create")) { notify(player,SOR_ERR); /* defined in money.h */ return; } cost = OBJECT_ENDOWMENT(cost); #if MAX_OBJECT_ENDOWMENT > 0 if (cost > MAX_OBJECT_ENDOWMENT) cost = MAX_OBJECT_ENDOWMENT; #endif thing = new_object(TYPE_THING); DBSTORE(thing,name,dup_string(name)); DBSTORE(thing,location,player); DBSTORE(thing,owner,player); DBSTORE(thing,sp.thing.value,cost); DBSTORE(thing,sp.thing.actions,NOTHING); FLAGS(thing) = TYPE_THING; DBSTORE(thing,sp.thing.home,player); add_ownerlist(thing); add_backlink(thing,DBFETCH(thing)->sp.thing.home); PUSH(thing,DBFETCH(player)->contents); DBDIRTY(player); sprintf(buf,"%s created with number #%d.",name,(int)thing); notify(player,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. * */ static dbref parse_source(dbref player, const char *source_name) { dbref source; struct match_data md; init_match(player,source_name,NOTYPE,&md); match_neighbor(&md); match_me(&md); match_here(&md); match_possession(&md); if (Royalty(player)) { match_absolute(&md); match_player(&md); } source = noisy_match_result(&md); if (source == NOTHING) return(NOTHING); if (!controls(player,source)) { notify(player,"Permission denied."); return(NOTHING); } switch (Typeof(source)) { case TYPE_EXIT: notify(player,"You can't attach an action to an action."); return(NOTHING); break; case TYPE_PROGRAM: notify(player,"You can't attach an action to a program."); return(NOTHING); break; case TYPE_DAEMON: notify(player,"You can't attach an action to a daemon."); return(NOTHING); break; } 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)->sp.room.exits); break; case TYPE_THING: PUSH(action,DBFETCH(source)->sp.thing.actions); break; case TYPE_PLAYER: PUSH(action,DBFETCH(source)->sp.player.actions); break; default: notify(player,"Internal error: weird object type."); log_status("Tried to source %d to %d: type: %d",action,source,Typeof(source)); return; break; } DBDIRTY(source); DBSTORE(action,location,source); } 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)->sp.room.exits)) { notify(player, "You can't do that to an exit in another room."); return 0; } DBSTORE(DBFETCH(player)->location, sp.room.exits, remove_first(DBFETCH(DBFETCH(player)->location)->sp.room.exits, action)); } else { switch(Typeof(oldsrc)) { case TYPE_PLAYER: DBSTORE(oldsrc, sp.player.actions, remove_first(DBFETCH(oldsrc)->sp.player.actions, action)); break; case TYPE_ROOM: DBSTORE(oldsrc, sp.room.exits, remove_first(DBFETCH(oldsrc)->sp.room.exits, action)); break; case TYPE_THING: DBSTORE(oldsrc, sp.thing.actions, remove_first(DBFETCH(oldsrc)->sp.thing.actions, action)); break; default: log_status("PANIC: source of action #%d was type: %d.",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; dbref 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; } if (! ok_name(action_name)) { notify(player,"That's a strange name for an action!"); return; } if (! quota_room(player,1,0)) return; source = parse_source(player,source_name); if (source == NOTHING) return; if (! payfor(player,EXIT_COST,"@action")) { notify(player,ACT_ERR); /* defined in money.h */ return; } action = new_object(TYPE_EXIT); DBSTORE(action,name,dup_string(action_name)); DBSTORE(action,location,NOTHING); DBSTORE(action,owner,player); DBSTORE(action,sp.exit.ndest,0); DBSTORE(action,sp.exit.dest,0); FLAGS(action) = TYPE_EXIT; add_ownerlist(action); set_source(player,action,source); sprintf(buf,"Action created with number %d and attached.",(int)action); notify(player,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); if (Royalty(player)) 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 || Typeof(source) == TYPE_DAEMON) return; if(!unset_source(player, loc, action)) return; set_source(player, action, source); notify(player, "Action re-attached."); }