tmuck2.4/
tmuck2.4/admin/scripts/
tmuck2.4/docs/
tmuck2.4/minimal-db/
tmuck2.4/minimal-db/data/
tmuck2.4/minimal-db/logs/
tmuck2.4/minimal-db/muf/
tmuck2.4/old/
tmuck2.4/src/
tmuck2.4/src/compile/
tmuck2.4/src/editor/
tmuck2.4/src/game/
tmuck2.4/src/interface/
tmuck2.4/src/scripts/
tmuck2.4/src/utilprogs/
#include "copyright.h"
/* link.c,v 2.6 1997/08/10 03:12:20 dmoore Exp */

#include "config.h"

#include <ctype.h>

#include "db.h"
#include "params.h"
#include "match.h"
#include "buffer.h"
#include "externs.h"

/* The code in this file is essentially the 2.2 code w/ bug fixes. -- David */


/* Should really be in a header file. */
int link_exit(const dbref player, const dbref exit, const char *dest_name, dbref *dest_list);


/* 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(const dbref player, const char *thing_name, const char *dest_name, const char *ignore)
{
    dbref thing;
    dbref dest;
    dbref good_dest[MAX_LINKS];
    dbref *temp;
    struct match_data md;
  
    int  ndest, i;
  
    init_match(player, thing_name, TYPE_EXIT, &md);
    match_everything(&md, Wizard(player));
  
    if ((thing = noisy_match_result(&md)) == NOTHING) return;
  
    switch (Typeof(thing)) {
    case TYPE_EXIT:
	/* we're ok, check the usual stuff */
	if (GetNDest(thing) > 0) {
	    if (controls(player, thing, Wizard(player))) {
		notify(player, "That exit is already linked.");
		return;
	    } else {
		notify(player, "Permission denied.");
		return;
	    }
	}
	/* handle costs */
	if (GetOwner(thing) == 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 */
		IncPennies(GetOwner(thing), EXIT_COST);
	    }
	}
    
	/* link has been validated and paid for; do it */
	chown_object(thing, player);
	ndest = link_exit(player, thing, (char *) dest_name, good_dest);
	if (ndest == 0) {
	    /* refund */
	    notify(player, "No destinations linked.");
	    IncPennies(player, LINK_COST);
	    break;
	}
	SetNDest(thing, ndest);
	MALLOC(temp, dbref, ndest);
	SetDest(thing, temp);
	for (i = 0; i < ndest; i++, temp++) {
	    *temp = good_dest[i];
	}
	break;
    case TYPE_THING:
    case TYPE_PLAYER:
	init_match(player, dest_name, TYPE_ROOM, &md);
	match_me(&md);
	match_neighbor(&md);
	match_absolute(&md);
	match_here(&md);
	if ((dest = noisy_match_result(&md)) == NOTHING)
	    return;
	if (!controls(player, thing, Wizard(player))
	    || !can_link_to(player, Typeof(thing), dest)) {
	    notify(player, "Permission denied.");
	    return;
	}
	/* do the link */
	SetLink(thing, 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, Wizard(player))
	    || !can_link_to(player, Typeof(thing), dest)
	    || (thing == dest)) {
	    notify(player, "Permission denied.");
	} else {
	    SetLink(thing, dest); /* Set a 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",
		   thing, Typeof(thing));
	break;
    }
    return;
}


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

/* 2/2/92 by dmoore to not overflow static buffers. */
static dbref parse_linkable_dest(const dbref player, const dbref exit, const char *dest_name)
{
    dbref  dobj;                /* destination room/player/thing/link */
    struct match_data md;

    init_match(player, dest_name, NOTYPE, &md);
    match_home(&md);
    match_everything(&md, Wizard(player));

    if ((dobj = match_result(&md)) == NOTHING || dobj == AMBIGUOUS) {
	notify(player, "I couldn't find '%s'.", dest_name);
	return NOTHING;
#ifndef TELEPORT_TO_PLAYER
    }
    if (Typeof(dobj) == TYPE_PLAYER) {
	notify(player, "You can't link to players.  Destination %u ignored.",
	       player, dobj);
	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)) {
	notify(player, "You can't link to %u.", player, dobj);
	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.
 */
static int exit_loop_check(const dbref source, const dbref dest)
{
    int ndest, i;
    dbref *temp;

    if (source == dest) return 1; /* That's an easy one! */
    if (Typeof(dest) != TYPE_EXIT) return 0;
    
    ndest = GetNDest(dest);
    temp = GetDest(dest);
    for (i = 0; i < ndest; i++, temp++) {
	if (*temp == source) return 1; /* Found a loop. */
	if (Typeof(*temp) == TYPE_EXIT) {
	    /* Check recursively. */
	    if (exit_loop_check(source, *temp)) return 1;
	}
    }
    return 0;			/* No loops found */
}


/*
 * 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(const dbref player, const dbref exit, const char *dest_name, dbref *dest_list)
{
    int  prdest;
    dbref  dest;
    int  ndest;
    static Buffer buf;
  
    prdest = 0;
    ndest = 0;
  
    while (*dest_name) {
	/* Skip white space. */
	while (*dest_name && isspace(*dest_name)) dest_name++;
	/* Reset buffer to hold exit name. */
	Bufcpy(&buf, "");
	/* Stuff characters of the exit name into the buffer. */
	while (*dest_name && (*dest_name != EXIT_DELIMITER)) {
	    Bufcat_char(&buf, *dest_name);
	    dest_name++;
	}
	/* If the last char was the delimiter, skip over it. */
	if (*dest_name) dest_name++;
    
	dest = parse_linkable_dest(player, exit, Buftext(&buf));
	if (dest == NOTHING) continue;
    
	switch (Typeof(dest)) {
	case TYPE_PLAYER:
	case TYPE_ROOM:
	case TYPE_PROGRAM:
	    if (prdest) {
		notify(player, "Only one player, room, or program destination allowed. Destination %u ignored.", player, dest);
		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)) {
		notify(player, "Destination %u would create a loop, ignored.",
		       player, dest);
		continue;
	    }
	    dest_list[ndest++] = dest;
	    break;
	default:
	    notify(player, "Internal error: weird object type.");
	    log_status("PANIC: weird object: Typeof(%d) = %d",
		       dest, Typeof(dest));
	    break;
	}
	if (dest == HOME) {
	    notify(player, "Linked to HOME.");
	} else {
	    notify(player, "Linked to %u.", player, dest);
	}
	if (ndest >= MAX_LINKS) {
	    notify(player, "Too many destinations, rest ignored.");
	    break;
	}
    }
    return ndest;
}