/* $Header: create.c,v 2.0 90/05/05 12:44:12 lachesis Exp $
 * $Log:	create.c,v $
 * Revision 2.0  90/05/05  12:44:12  lachesis
 * Incorporated ABODE and HAVEN flags (remembering to convert FireFoot's
 * usage of those flags to ours).
 * Added Examine of objects that don't belong to you, added GOD_PRIV.
 * 
 * Revision 1.1  90/04/14  14:56:40  lachesis
 * Initial revision
 * 
 */
#include "copyright.h"

/* Commands that create new objects */

#include "db.h"
#include "config.h"
#include "interface.h"
#include "externs.h"
#include "match.h"
#include <strings.h>
#include <ctype.h>

/* utility for open and link */
static dbref parse_linkable_room(dbref player, const char *room_name)
{
  dbref room;
  
  /* skip leading NUMBER_TOKEN if any */
  if(*room_name == NUMBER_TOKEN) room_name++;
  
  /* parse room */
  if(!string_compare(room_name, "here")) {
    room = db[player].location;
  } else if(!string_compare(room_name, "home")) {
    return HOME;            /* HOME is always linkable */
  } else {
    room = parse_dbref(room_name);
  }
  
  /* check room */
  if(room < 0 || room >= db_top
     || Typeof(room) != TYPE_ROOM) {
    notify(player, "That's not a room!");
    return NOTHING;
  } else if(!can_link_to(player, room)) {
    notify(player, "You can't link to that.");
    return NOTHING;
  } else {
    return room;
  }
}

#ifdef ABODE
/* utility for open and link for objects/players */
static dbref parse_abode_room(dbref player, const char *room_name)
{
    dbref room;

    /* skip leading NUMBER_TOKEN if any */
    if(*room_name == NUMBER_TOKEN) room_name++;

    /* parse room */
    if(!string_compare(room_name, "here")) {
        room = db[player].location;
    } else if(!string_compare(room_name, "home")) {
        return HOME;            /* HOME is always linkable */
    } else {
        room = parse_dbref(room_name);
    }

    /* check room */
    if(room < 0 || room >= db_top
       || Typeof(room) != TYPE_ROOM) {
        notify(player, "That's not a room!");
        return NOTHING;
    } else if(! (controls(player, room) || (db[room].flags & ABODE))) {
        notify(player, "You can't link to that.");
        return NOTHING;
    } else {
        return room;
    }
}
#endif /* ABODE */

/* 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, const char *dest_name)
{
  dbref  dobj;                /* destination room/player/thing/link */
  static char  buf[BUFFER_LEN];
  
  init_match(player, dest_name, NOTYPE);
  match_absolute();
  match_everything();
  match_home();
  
  if ((dobj = match_result()) == NOTHING) {
    sprintf(buf, "I couldn't find \"%s\".", dest_name);
    notify(player, buf);
    return NOTHING;
  }
  if (dobj == AMBIGUOUS) {
    notify(player, "I don't know which one you mean!");
    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_to(player, 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 < db[dest].sp.exit.ndest; i++) {
    if ( (db[dest].sp.exit.dest)[i] == source) {
      return 1;           /* Found a loop! */
    }
    if (Typeof((db[dest].sp.exit.dest)[i]) == TYPE_EXIT) {
      if (exit_loop_check(source, (db[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;

#ifdef RESTRICTED_BUILDING
    if(!Builder(player)) {
	notify(player, "That command is restricted to authorized builders.");
	return;
    }
#endif /* RESTRICTED_BUILDING */

    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 cookies to open an exit.");
    } else {
	/* create the exit */
	exit = new_object();

	/* initialize everything */
	db[exit].name = alloc_string(direction);
	db[exit].location = loc;
	db[exit].sp.exit.owner = player;
	db[exit].flags = TYPE_EXIT;
	db[exit].sp.exit.ndest = 0;
	db[exit].sp.exit.dest = 0;

	/* link it in */
	PUSH(exit, db[loc].sp.room.exits);

	/* and we're done */
	notify(player, "Opened.");

	/* 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 cookies to link.");
                return;
            }
            ndest = link_exit(player, exit, (char *) linkto, good_dest);
            db[exit].sp.exit.ndest = ndest;
            db[exit].sp.exit.dest = (dbref *) malloc(sizeof(dbref)*ndest);
            for (i = 0; i < ndest; i++) {
                (db[exit].sp.exit.dest)[i] = good_dest[i];
            }
	}
    }
}

/*
 * 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.
 *
 */
#ifdef mips
int
  link_exit(player, exit, dest_name, dest_list)
dbref player, exit;
char *dest_name;
dbref dest_list[];
#else /* !mips */
int link_exit(dbref player, dbref exit, char *dest_name, dbref *dest_list)
#endif /* mips */
{
    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++;
                                                /* go to space or end */
        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++)
	    /*EMPTY*/
	    ;

        if ( (dest = parse_linkable_dest(player, q)) == NOTHING) continue;

        switch (Typeof(dest)) {
          case TYPE_PLAYER:
          case TYPE_ROOM:
            if (prdest) {
                sprintf (buf,
    "Only one player or room destination allowed. Destination %d ignored.",
        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 %d would create a loop, ignored.",
			dest);
		notify(player, buf);
		continue;
            }
            dest_list[ndest++] = dest;
	    break;
          default:
            notify(player, "Internal error: weird object type.");
            fprintf(stderr, "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];

    int  ndest, i;

    init_match(player, thing_name, TYPE_EXIT);
    match_all_exits();
    match_neighbor();
    match_possession();
    match_me();
    match_here();
    if(Wizard(player)) {
	match_absolute();
	match_player();
    }

    if((thing = noisy_match_result()) == NOTHING) return;

    switch(Typeof(thing)) {
      case TYPE_EXIT:
	/* we're ok, check the usual stuff */
	if(db[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(db[thing].sp.exit.owner == player) {
	    if(!payfor(player, LINK_COST)) {
		notify(player,
		       "It costs a cookie to link this exit.");
		return;
	    }
	} else {
	    if(!payfor(player, LINK_COST + EXIT_COST)) {
		notify(player,
		       "It costs two cookies to link this exit.");
		return;
#ifdef RESTRICTED_BUILDING
	    } else if(!Builder(player)) {
		notify(player,
		       "Only authorized builders may seize exits.");
		return;
#endif /* RESTRICTED_BUILDING */
	    } else {
	    /* pay the owner for his loss */
	    db[db[thing].sp.exit.owner].sp.player.pennies
		    += EXIT_COST;
	    }
	}

	/* link has been validated and paid for; do it */
	db[thing].sp.exit.owner = player;
	ndest = link_exit(player, thing, (char *) dest_name, good_dest);
	if (ndest == 0) {
	    notify(player, "No destinations linked.");
	    db[player].sp.player.pennies += LINK_COST; /* Refund! */
	    break;
	}
	db[thing].sp.exit.ndest = ndest;
	db[thing].sp.exit.dest = (dbref *) malloc(sizeof(dbref)*ndest);
	for (i = 0; i < ndest; i++) {
	    (db[thing].sp.exit.dest)[i] = good_dest[i];
	}
	break;
      case TYPE_THING:
      case TYPE_PLAYER:
#ifndef ABODE
	if ( (dest = parse_linkable_room(player, dest_name) ) == NOTHING)
	    return;
#else /* ABODE */
	dest = parse_abode_room(player, dest_name);
	if (dest == NOTHING)
	  return;
#endif /* ABODE */
	if (Typeof(dest) != TYPE_ROOM) {
	    notify(player, "You can only set home to a room.");
	    break;
	}
	if(!controls(player, thing)) {
	    notify(player, "Permission denied.");
	} else if(dest == HOME) {
               notify(player, "Can't set home to 'home'.");
	} else {
	    /* do the link */
	    if (Typeof(thing) == TYPE_THING)
		db[thing].sp.thing.home = dest;
	    else
		db[thing].sp.player.home = dest;
	    notify(player, "Home set.");
	}
	break;
      case TYPE_ROOM:               /* room dropto's */
	dest = parse_linkable_room(player, dest_name);
	if (dest == NOTHING) {
	    notify(player, "You can't link to that.");
	    return;
	}
	if(!controls(player, thing)) {
	    notify(player, "Permission denied.");
	} else {
	    /* do the link, in location */
	    db[thing].sp.room.dropto = dest; /* dropto */
	    notify(player, "Dropto set.");
	}
	break;
      default:
	notify(player, "Internal error: weird object type.");
	fprintf(stderr, "PANIC weird object: Typeof(%d) = %d\n",
		thing, Typeof(thing));
	break;
    }
}

/*
 * do_dig
 *
 * Use this to create a room.
 */
void do_dig(dbref player, const char *name)
{
    dbref room;
    char buf[BUFFER_LEN];

#ifdef RESTRICTED_BUILDING
    if(!Builder(player)) {
        notify(player, "That command is restricted to authorized builders.");
        return;
    }
#endif /* RESTRICTED_BUILDING */

    /* we don't need to know player's location!  hooray! */
    if(*name == '\0') {
        notify(player, "You must specify a name for the room.");
    } else if(!ok_name(name)) {
        notify(player, "That's a silly name for a room!");
    } else if(!payfor(player, ROOM_COST)) {
        notify(player, "Sorry, you don't have enough cookies to dig a room.");
    } else {
        room = new_object();

        /* Initialize everything */
        db[room].name = alloc_string(name);
        db[room].sp.room.owner = player;
        db[room].sp.room.exits = NOTHING;
        db[room].sp.room.dropto = NOTHING;
        db[room].flags = TYPE_ROOM | (db[player].flags & JUMP_OK);

        sprintf(buf, "%s created with room number %d.", name, room);
        notify(player, buf);
    }
}

/*
 * do_create
 *
 * Use this to create an object.
 */
void do_create(dbref player, const char *name, int cost)
{
    dbref loc;
    dbref thing;

    static char  buf[BUFFER_LEN];

#ifdef RESTRICTED_BUILDING
    if(!Builder(player)) {
        notify(player, "That command is restricted to authorized builders.");
        return;
    }
#endif /* RESTRICTED_BUILDING */

    if(*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 cookies.");
    } else {
        /* create the object */
        thing = new_object();

        /* initialize everything */
        db[thing].name = alloc_string(name);
        db[thing].location = player;
        db[thing].sp.thing.owner = player;
        db[thing].sp.thing.value = OBJECT_ENDOWMENT(cost);
        db[thing].sp.thing.actions = NOTHING;
        db[thing].flags = TYPE_THING;

        /* endow the object */
        if(db[thing].sp.thing.value > MAX_OBJECT_ENDOWMENT) {
            db[thing].sp.thing.value = MAX_OBJECT_ENDOWMENT;
        }

        /* home is here (if we can link to it) or player's home */
        if((loc = db[player].location) != NOTHING
           && controls(player, loc)) {
            db[thing].sp.thing.home = loc;      /* home */
        } else {
            db[thing].sp.thing.home = db[player].sp.player.home;
					    /* set to player's home instead */
        }

        /* link it in */
        PUSH(thing, db[player].contents);

        /* and we're done */
	sprintf(buf, "%s created with number %d.", name, thing);
        notify(player, buf);
    }
}

/*
 * 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;

    init_match(player, source_name, NOTHING);   /* source type can be any */
    match_neighbor();
    match_me();
    match_here();
    match_possession();
    if (Wizard(player)) {
        match_absolute();
    }
    source = noisy_match_result();

    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;
    }
    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, db[source].sp.room.exits);
        break;
      case TYPE_THING:
        PUSH(action, db[source].sp.thing.actions);
        break;
      case TYPE_PLAYER:
        PUSH(action, db[source].sp.player.actions);
        break;
      default:
        notify(player, "Internal error: weird object type.");
        fprintf(stderr, "PANIC weird object: Typeof(%d) = %d\n",
                source, Typeof(source));
	return;
	/*NOTREACHED*/
        break;
    }
    db[action].location = source;
    return;
}

/*
 * 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];

#ifdef RESTRICTED_BUILDING
    if(!Builder(player)) {
        notify(player, "That command is restricted to authorized builders.");
        return;
    }
#endif /* RESTRICTED_BUILDING */
    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 cookies to make an action.");
        return;
    }
    if ((source = parse_source(player, source_name)) == NOTHING) return;

    action = new_object();

    db[action].name = alloc_string(action_name);
    db[action].location = NOTHING;
    db[action].sp.exit.owner = player;
    db[action].sp.exit.ndest = 0;
    db[action].sp.exit.dest = NULL;
    db[action].flags = TYPE_EXIT;

    set_source(player, action, source);
    sprintf(buf, "Action created with number %d and attached.", action);
    notify(player, buf);
}

/*
 * 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 oldsrc;               /* action's old source */
    dbref loc;			/* player's current location */

    if ( (loc = db[player].location) == NOTHING) return;

#ifdef RESTRICTED_BUILDING
    if(!Builder(player)) {
        notify(player, "That command is restricted to authorized builders.");
        return;
    }
#endif /* RESTRICTED_BUILDING */
    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);
    match_all_exits();
    if (Wizard(player))
        match_absolute();

    if ( (action = noisy_match_result() ) == 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) return;

    if ( (oldsrc = db[action].location) == NOTHING) {
	/* old-style, sourceless exit */
	if(!member(action, db[loc].sp.room.exits)) {
	    notify(player,
		   "You can't re-attach an exit in another room.");
	    return;
	}
	db[db[player].location].sp.room.exits =
	 remove_first(db[db[player].location].sp.room.exits, action);
    } else {
        switch(Typeof(oldsrc)) {
          case TYPE_PLAYER:
	    db[oldsrc].sp.player.actions =
             remove_first(db[oldsrc].sp.player.actions, action);
            break;
          case TYPE_ROOM:
            db[oldsrc].sp.room.exits =
             remove_first(db[oldsrc].sp.room.exits, action);
            break;
          case TYPE_THING:
            db[oldsrc].sp.thing.actions =
             remove_first(db[oldsrc].sp.thing.actions, action);
            break;
          case TYPE_EXIT:
            fprintf(stderr,
		    "PANIC: Action #%d was sourced to an exit.", action);
	    return;
	    /*NOTREACHED*/
	    break;
        }
    }
    set_source(player, action, source);
    notify(player, "Action re-attached.");
}

int unset_source(dbref player, dbref loc, dbref action,
		 const char *other_room_msg)
{

    dbref oldsrc;
    
    if ( (oldsrc = db[action].location) == NOTHING) {
	/* old-style, sourceless exit */
	if(!member(action, db[loc].sp.room.exits)) {
	    notify(player, other_room_msg);
	    return 0;
	}
	db[db[player].location].sp.room.exits =
	 remove_first(db[db[player].location].sp.room.exits, action);
    } else {
        switch(Typeof(oldsrc)) {
          case TYPE_PLAYER:
	    db[oldsrc].sp.player.actions =
             remove_first(db[oldsrc].sp.player.actions, action);
            break;
          case TYPE_ROOM:
            db[oldsrc].sp.room.exits =
             remove_first(db[oldsrc].sp.room.exits, action);
            break;
          case TYPE_THING:
            db[oldsrc].sp.thing.actions =
             remove_first(db[oldsrc].sp.thing.actions, action);
            break;
          case TYPE_EXIT:
            fprintf(stderr,
		    "PANIC: Action #%d was sourced to an exit.", action);
	    return 0;
	    /*NOTREACHED*/
	    break;
        }
    }
    return 1;
}