/* $Header: /belch_a/users/rearl/tinymuck/src/RCS/move.c,v 1.11 90/09/16 04:42:35 rearl Exp $ */
/*
* $Log: move.c,v $
* Revision 1.11 90/09/16 04:42:35 rearl
* Preparation code added for disk-based MUCK.
*
* Revision 1.10 90/09/15 22:26:57 rearl
* Fixed moveto bug from HOME.
*
* Revision 1.9 90/09/13 06:28:04 rearl
* send_contents changed to be usable for do_toad() in wiz.c
*
* Revision 1.8 90/09/10 02:20:29 rearl
* Put exec_or_notify in the drop messages of exits.
*
* Revision 1.7 90/09/04 18:39:43 rearl
* Added some prototypes for externs.
*
* Revision 1.6 90/09/01 05:59:21 rearl
* Took out drop code for rooms, setting parent depends on @tel now.
*
* Revision 1.5 90/08/27 03:31:58 rearl
* Added environment support.
*
* Revision 1.4 90/08/15 03:06:01 rearl
* Fixed 0 PENNY_RATE bug. Took out #ifdef GENDER.
*
* Revision 1.3 90/08/05 03:19:47 rearl
* Redid matching routines.
*
* Revision 1.2 90/07/29 17:41:43 rearl
* Fixed moveto problems relating to ROOM programs, also programs
* go to their owner rather than "home" -- programs have no home by
* definition.
*
* Revision 1.1 90/07/19 23:03:55 casie
* Initial revision
*
*
*/
#include "copyright.h"
#include "config.h"
#include "db.h"
#include "params.h"
#include "interface.h"
#include "match.h"
#include "externs.h"
#ifdef RECYCLE
void recycle(dbref, dbref);
#endif /* RECYCLE */
void moveto_internal(dbref, dbref);
void enter_room_internal(dbref, dbref);
void moveto(dbref what, dbref where)
{
if(can_hear(what)) {
enter_room_internal(what, where); }
else {
moveto_internal(what, where); }
}
void moveto_internal(dbref what, dbref where)
{
dbref loc;
/* remove what from old loc */
if((loc = DBFETCH(what)->location) != NOTHING) {
DBSTORE(loc, contents, remove_first(DBFETCH(loc)->contents, what));
}
/* test for special cases */
switch(where) {
case NOTHING:
DBSTORE(what, location, NOTHING);
return; /* NOTHING doesn't have contents */
case HOME:
switch(Typeof(what)) {
case TYPE_PLAYER:
where = DBFETCH(what)->link;
break;
case TYPE_THING:
where = DBFETCH(what)->link;
break;
case TYPE_ROOM:
where = GLOBAL_ENVIRONMENT;
break;
case TYPE_PROGRAM:
where = OWNER(what);
break;
default:
where = GLOBAL_ENVIRONMENT;
}
}
if(parent_loop_check(what, where))
what = PLAYER_START;
if(parent_loop_check(what, where))
what = GLOBAL_ENVIRONMENT;
if(parent_loop_check(what, where))
return;
if(!is_ok(what)) {
printf("Bad what: %d\n",what);
what = PLAYER_START;
}
/* now put what in where */
PUSH(what, DBFETCH(where)->contents);
DBDIRTY(where);
DBSTORE(what, location, where);
}
dbref reverse(dbref);
void send_contents(dbref loc, dbref dest)
{
dbref first;
dbref rest;
puts("starting send_contents");
first = DBFETCH(loc)->contents;
DBSTORE(loc, contents, NOTHING);
/* blast locations of everything in list */
DOLIST(rest, first) {
DBSTORE(rest, location, NOTHING);
}
while(first != NOTHING) {
rest = DBFETCH(first)->next;
if((Typeof(first) != TYPE_THING)
&& (Typeof(first) != TYPE_PROGRAM)) {
moveto(first, loc);
} else {
moveto(first, FLAGS(first) & STICKY ? HOME : dest);
}
first = rest;
}
DBSTORE(loc, contents, reverse(DBFETCH(loc)->contents));
puts("done send_contents");
}
void maybe_dropto(dbref loc, dbref dropto)
{
dbref thing;
if(loc == dropto) return; /* bizarre special case */
/* check for players */
DOLIST(thing, DBFETCH(loc)->contents) {
if(Typeof(thing) == TYPE_PLAYER) return;
}
/* no players, send everything to the dropto */
send_contents(loc, dropto);
}
int parent_loop_check_internal(dbref source, dbref dest, int depth)
{
if(depth > 20) return 1;
if(dest == NOTHING) return 0;
return(parent_loop_check_internal(source, DBFETCH(dest)->location,
depth + 1));
}
int parent_loop_check(dbref source, dbref dest)
{
if(source == NOTHING) return 1;
if(dest == HOME)
dest = DBFETCH(source)->link;
if (dest == NOTHING) return 0;
if(Typeof(source) != TYPE_ROOM || Typeof(dest) != TYPE_ROOM) return 0;
if (source == dest) return 1; /* That's an easy one! */
if(Typeof(dest) == TYPE_GARBAGE) return 1;
return parent_loop_check_internal(source, DBFETCH(dest)->location, 0);
}
void enter_room(dbref player, dbref loc, dbref exit)
{ /*we ignore exit. */
moveto(player,loc);
}
void enter_room_internal(dbref player, dbref loc)
{
dbref old;
dbref dropto;
char buf[BUFFER_LEN];
/* check for room == HOME */
if(loc == HOME) loc = DBFETCH(player)->link; /* home */
/* get old location */
old = DBFETCH(player)->location;
/* check for self-loop */
/* self-loops don't do move or other player notification */
/* but you still get autolook and penny check */
if(loc != old) {
if(old != NOTHING) {
/* notify others unless DARK */
if(!Dark(old) && !Dark(player)) {
sprintf(buf, "%s has left.", NAME(player));
notify_except(DBFETCH(old)->contents, player, buf);
}
}
/* go there */
moveto_internal(player, loc);
/* if old location has STICKY dropto, send stuff through it */
if(old != NOTHING && (dropto = DBFETCH(old)->link) != NOTHING
&& (FLAGS(old) & STICKY)) {
maybe_dropto(old, dropto);
}
/* tell other folks in new location if not DARK */
if(loc != NOTHING && !Dark(loc) && !Dark(player)) {
sprintf(buf, "%s has arrived.", NAME(player));
notify_except(DBFETCH(loc)->contents, player, buf);
}
}
/* autolook */
if(loc != NOTHING)
look_room(player, loc);
#if (PENNY_RATE) != 0
/* check for pennies */
if(!controls(player, loc)
&& DBFETCH(player)->sp.player.pennies <= MAX_PENNIES
&& (random() % PENNY_RATE) == 0) {
notify(player, "You found a penny!");
DBFETCH(player)->sp.player.pennies++;
DBDIRTY(player);
}
#endif /* PENNY_RATE */
}
void send_home(dbref thing)
{
switch(Typeof(thing)) {
case TYPE_PLAYER:
/* send his possessions home first! */
/* that way he sees them when he arrives */
send_contents(thing, HOME);
enter_room(thing, DBFETCH(thing)->link, DBFETCH(thing)->location);
break;
case TYPE_THING:
send_contents(thing,HOME);
moveto(thing, DBFETCH(thing)->link); /* home */
break;
case TYPE_PROGRAM:
moveto(thing, OWNER(thing));
break;
default:
/* no effect */
break;
}
return;
}
int can_move(dbref player, const char *direction)
{
struct match_data md;
if(!string_compare(direction, "home")) return 1;
/* otherwise match on exits */
init_match(player, direction, TYPE_EXIT, &md);
match_all_exits(&md);
return(last_match_result(&md) != NOTHING);
}
/*
* trigger()
*
* This procedure triggers a series of actions, or meta-actions
* which are contained in the 'dest' field of the exit.
* Locks other than the first one are over-ridden.
*
* `player' is the player who triggered the exit
* `exit' is the exit triggered
* `pflag' is a flag which indicates whether player and room exits
* are to be used (non-zero) or ignored (zero). Note that
* player/room destinations triggered via a meta-link are
* ignored.
*
*/
void trigger(dbref player, dbref exit, int pflag)
{
int i;
dbref dest;
int sobjact; /* sticky object action flag, sends home source obj */
int succ;
char buf[BUFFER_LEN];
sobjact = 0;
succ = 0;
for (i = 0; i < DBFETCH(exit)->sp.exit.ndest; i++) {
dest = (DBFETCH(exit)->sp.exit.dest)[i];
if (dest == HOME) dest = DBFETCH(player)->link;
switch(Typeof(dest)) {
case TYPE_ROOM:
if (pflag) {
if (get_attr(exit, "Drop"))
exec_or_notify(player, exit, get_attr(exit, "Drop"));
if (get_attr(exit, "Odrop") && !Dark(player))
{
sprintf(buf, "%s %s", NAME(player),
pronoun_substitute(player, get_attr(exit, "Odrop"), exit));
notify_except(DBFETCH(dest)->contents, player, buf);
}
enter_room(player, dest, exit);
succ = 1;
}
break;
case TYPE_THING:
if (Typeof(DBFETCH(exit)->location) == TYPE_THING) {
moveto (dest, DBFETCH(DBFETCH(exit)->location)->location);
if (!(FLAGS(exit) & STICKY)) {
/* send home source object */
sobjact = 1;
}
} else {
moveto (dest, DBFETCH(exit)->location);
}
if (get_attr(exit, "Succ")) succ = 1;
break;
case TYPE_EXIT: /* It's a meta-link(tm)! */
trigger(player, (DBFETCH(exit)->sp.exit.dest)[i], 0);
if (get_attr(exit, "Succ")) succ = 1;
break;
case TYPE_PLAYER:
if (pflag && DBFETCH(dest)->location != NOTHING) {
succ = 1;
if (FLAGS(dest) & JUMP_OK) {
if (get_attr(exit, "Drop"))
exec_or_notify(player, exit, get_attr(exit, "Drop"));
if (get_attr(exit, "Odrop") && !Dark(player)) {
sprintf(buf, "%s %s", NAME(player),
pronoun_substitute(player, get_attr(exit, "Odrop"), exit));
notify_except(DBFETCH(DBFETCH(dest)->location)->contents, player,
buf);
}
enter_room(player, DBFETCH(dest)->location, exit);
} else {
notify(player, "That player does not wish to be disturbed.");
}
}
break;
case TYPE_PROGRAM:
if (DBFETCH(player)->run) return;
(void) interp(player, dest, exit);
if (!(DBFETCH(player)->run -> pc))
{
free((void *) DBFETCH(player)->run);
DBFETCH(player)->run = 0;
DBDIRTY(player);
}
return;
}
}
if (sobjact) send_home(DBFETCH(exit)->location);
if (!succ && pflag) notify(player, "Done.");
}
void do_move(dbref player, const char *direction)
{
dbref exit;
dbref loc;
struct match_data md;
if(!string_compare(direction, "home")) {
/* send him home */
/* but steal all his possessions */
if((loc = DBFETCH(player)->location) != NOTHING) {
/* tell everybody else */
sprintf(buf, "%s goes home.", NAME(player));
notify_except(DBFETCH(loc)->contents, player, buf);
}
/* give the player the messages */
notify(player, "There's no place like home...");
notify(player, "There's no place like home...");
notify(player, "There's no place like home...");
notify(player, "You wake up back home, without your possessions.");
send_home(player);
} else {
/* find the exit */
init_match_check_keys(player, direction, TYPE_EXIT, &md);
match_all_exits(&md);
switch(exit = match_result(&md)) {
case NOTHING:
notify(player, "You can't go that way.");
break;
case AMBIGUOUS:
notify(player, "I don't know which way you mean!");
break;
default:
/* we got one */
/* check to see if we got through */
loc = DBFETCH(player)->location;
if(can_doit(player, exit, "You can't go that way.")) {
trigger(player, exit, 1);
}
break;
}
}
}
void do_get(dbref player, const char *what)
{
dbref thing;
struct match_data md;
init_match_check_keys(player, what, TYPE_THING, &md);
match_neighbor(&md);
if(Arch(player)) match_absolute(&md); /* the wizard has long fingers */
if((thing = noisy_match_result(&md)) != NOTHING) {
if(DBFETCH(thing)->location == player) {
notify(player, "You already have that!");
return;
}
switch(Typeof(thing)) {
case TYPE_THING:
case TYPE_PROGRAM:
case TYPE_PLAYER:
if(parent_loop_check(thing,player)) {
notify(player,"You can't take that!");
return;
}
if(can_doit(player, thing, "You can't pick that up.")) {
moveto(thing, player);
notify(player, "Taken.");
}
break;
default:
notify(player, "You can't take that!");
break;
}
}
}
void do_drop(dbref player, const char *name)
{
dbref loc;
dbref thing;
char buf[BUFFER_LEN];
struct match_data md;
if((loc = getloc(player)) == NOTHING) return;
init_match(player, name, NOTYPE, &md);
match_possession(&md);
if ((thing = noisy_match_result(&md)) == NOTHING
|| thing == AMBIGUOUS) return;
switch (Typeof(thing)) {
case TYPE_THING:
case TYPE_PROGRAM:
case TYPE_PLAYER:
if(DBFETCH(thing)->location != player) {
/* Shouldn't ever happen. */
notify(player, "You can't drop that.");
break;
}
if ((FLAGS(thing) & STICKY) && Typeof(thing) == TYPE_THING) {
send_home(thing);
} else {
int immediate_dropto = (DBFETCH(loc)->link != NOTHING
&& !(FLAGS(loc) & STICKY));
moveto(thing, (immediate_dropto && (Typeof(loc)==TYPE_ROOM)) ?
DBFETCH(loc)->link : loc);
}
if (get_attr(thing, "Drop"))
exec_or_notify(player, thing, get_attr(thing, "Drop"));
else
notify(player, "Dropped.");
if (get_attr(loc, "Drop") && Typeof(loc)==TYPE_ROOM)
exec_or_notify(thing, loc, get_attr(loc, "Drop"));
if (get_attr(thing, "Odrop")) {
sprintf(buf, "%s %s", NAME(player),
pronoun_substitute(player, get_attr(thing, "Odrop"), thing));
} else {
sprintf(buf, "%s drops %s.", NAME(player), NAME(thing));
}
notify_except(DBFETCH(loc)->contents, player, buf);
if(get_attr(thing, "Adrop")) {
trigobj(thing, get_attr(thing, "Adrop"),player);
}
if(get_attr(loc, "Adrop") && Typeof(loc)==TYPE_ROOM) {
trigobj(loc, get_attr(loc, "Adrop"),thing);
}
if (get_attr(loc, "Odrop") && Typeof(loc)==TYPE_ROOM) {
sprintf(buf, "%s %s", NAME(thing),
pronoun_substitute(thing, get_attr(loc, "Odrop"), loc));
notify_except(DBFETCH(loc)->contents, player, buf);
}
break;
default:
notify(player, "You can't drop that.");
break;
}
}
#ifdef RECYCLE
void do_recycle(dbref player, const char *name)
{
dbref thing;
struct match_data md;
init_match(player, name, TYPE_THING, &md);
match_all_exits(&md);
match_neighbor(&md);
match_possession(&md);
match_here(&md);
match_absolute(&md);
if((thing = noisy_match_result(&md)) != NOTHING) {
if(!controls(player, thing)) {
notify(player, "Permission denied.");
} else {
switch(Typeof(thing)) {
case TYPE_ROOM:
if(OWNER(thing) != player) {
notify(player, "Permission denied.");
return;
}
if(thing == PLAYER_START || thing == GLOBAL_ENVIRONMENT) {
notify(player, "This room may not be recycled.");
return;
}
break;
case TYPE_THING:
if(OWNER(thing) != player) {
notify(player, "Permission denied.");
return;
}
break;
case TYPE_EXIT:
if(OWNER(thing) != player) {
notify(player, "Permission denied.");
return;
}
if(!unset_source(player, DBFETCH(player)->location, thing)) {
return;
}
break;
case TYPE_PLAYER:
notify(player, "You can't recycle a player!");
return;
/*NOTREACHED*/
break;
case TYPE_PROGRAM:
if(OWNER(thing) != player) {
notify(player, "Permission denied.");
return;
}
break;
case TYPE_DAEMON:
if(OWNER(thing) != player) {
notify(player, "Permission denied.");
return;
}
break;
case TYPE_GARBAGE:
notify(player, "That's already garbage!");
return;
/*NOTREACHED*/
break;
}
recycle(player, thing);
notify(player,"Thank you for recycling.");
}
}
}
void recycle(dbref player, dbref thing)
{
extern dbref recyclable;
static int depth = 0;
dbref first;
dbref rest;
char buf[2048];
depth++;
switch(Typeof(thing)) {
case TYPE_ROOM:
DBFETCH(OWNER(thing))->sp.player.pennies += ROOM_COST;
DBDIRTY(OWNER(thing));
for(first = DBFETCH(thing)->exits; first != NOTHING; first = rest) {
rest = DBFETCH(first)->next;
if(DBFETCH(first)->location == NOTHING || DBFETCH(first)->location == thing)
recycle(player, first);
}
notify_except(DBFETCH(thing)->contents, NOTHING,
"The room shakes and crumbles...");
break;
case TYPE_THING:
halt_long(thing);
DBFETCH(OWNER(thing))->sp.player.pennies +=
DBFETCH(thing)->sp.thing.value;
DBDIRTY(OWNER(thing));
for(first = DBFETCH(thing)->exits; first != NOTHING;
first = rest) {
rest = DBFETCH(first)->next;
if(DBFETCH(first)->location == NOTHING
|| DBFETCH(first)->location == thing)
recycle(player, first);
}
notify_except(DBFETCH(thing)->contents, NOTHING,
"This place shakes and crumbles...");
break;
case TYPE_EXIT:
DBFETCH(OWNER(thing))->sp.player.pennies += EXIT_COST;
if(DBFETCH(thing)->sp.exit.ndest != 0)
DBFETCH(OWNER(thing))->sp.player.pennies += LINK_COST;
DBDIRTY(OWNER(thing));
break;
case TYPE_PROGRAM:
sprintf(buf, "muf/%d.m", (int) thing);
unlink(buf);
break;
}
for(rest = 0; rest < db_top; rest++) {
switch(Typeof(rest)) {
case TYPE_ROOM:
if(DBFETCH(rest)->link == thing)
DBFETCH(rest)->link = NOTHING;
if(DBFETCH(rest)->exits == thing)
DBFETCH(rest)->exits = DBFETCH(thing)->next;
if(OWNER(rest) == thing)
OWNER(rest) = GOD;
break;
case TYPE_THING:
if(DBFETCH(rest)->link == thing) {
if(DBFETCH(OWNER(rest))->link == thing)
DBSTORE(OWNER(rest), link, PLAYER_START);
DBFETCH(rest)->link = DBFETCH(OWNER(rest))->link;
}
if(DBFETCH(rest)->exits == thing)
DBFETCH(rest)->exits = DBFETCH(thing)->next;
if(OWNER(rest) == thing)
OWNER(rest) = GOD;
break;
case TYPE_EXIT:
{
int i, j;
for (i = j = 0; i < DBFETCH(rest)->sp.exit.ndest; i++) {
if((DBFETCH(rest)->sp.exit.dest)[i] != thing)
(DBFETCH(rest)->sp.exit.dest)[j++] =
(DBFETCH(rest)->sp.exit.dest)[i];
}
if(j < DBFETCH(rest)->sp.exit.ndest) {
DBFETCH(OWNER(rest))->sp.player.pennies += LINK_COST;
DBDIRTY(OWNER(rest));
DBFETCH(rest)->sp.exit.ndest = j;
}
}
if(OWNER(rest) == thing)
OWNER(rest) = GOD;
break;
case TYPE_PLAYER:
if(DBFETCH(rest)->link == thing)
DBFETCH(rest)->link = PLAYER_START;
if(DBFETCH(rest)->exits == thing)
DBFETCH(rest)->exits = DBFETCH(thing)->next;
if(DBFETCH(rest)->curr_prog == thing)
DBFETCH(rest)->curr_prog = 0;
break;
case TYPE_PROGRAM:
if(OWNER(rest) == thing)
OWNER(rest) = GOD;
break;
case TYPE_DAEMON:
if(OWNER(rest) == thing)
OWNER(rest) = GOD;
}
if(DBFETCH(rest)->location == thing)
DBFETCH(rest)->location = NOTHING;
if(DBFETCH(rest)->contents == thing)
DBFETCH(rest)->contents = DBFETCH(thing)->next;
if(DBFETCH(rest)->next == thing)
DBFETCH(rest)->next = DBFETCH(thing)->next;
DBDIRTY(rest);
}
for(first = DBFETCH(thing)->contents; first != NOTHING; first = rest) {
rest = DBFETCH(first)->next;
if(Typeof(first) == TYPE_PLAYER)
enter_room(first, HOME, DBFETCH(thing)->location);
else
moveto(first, HOME);
}
moveto(thing, NOTHING);
--depth;
db_free_object(thing);
db_clear_object(thing);
NAME(thing) = "<garbage>";
add_attr(thing, "Desc", "<recyclable>");
DBFETCH(thing)->key = TRUE_BOOLEXP;
FLAGS(thing) = TYPE_GARBAGE;
DBFETCH(thing)->next = recyclable;
recyclable = thing;
DBDIRTY(thing);
}
#endif /* RECYCLE */
void do_enter(dbref player, const char *thing)
{
dbref what;
dbref where = DBFETCH(player) -> location;
struct match_data md;
init_match(player,thing,TYPE_THING,&md);
match_neighbor(&md);
if(Arch(player)) {
match_absolute(&md);
match_player(&md);
}
what = noisy_match_result(&md);
if(!is_ok(what))
return;
if(((FLAGS(what)&JUMP_OK) ||
controls(player,what)) &&
(Typeof(what)==TYPE_PLAYER || Typeof(what)==TYPE_THING)) {
if(get_attr(what,"Enter"))
notify(player, get_attr(what,"Enter"));
if(get_attr(what,"Oenter") && !Dark(player)) {
sprintf(buf,"%%n %s", get_attr(what,"Oenter"));
notify_except(DBFETCH(where)->contents, player,
pronoun_substitute(player, buf, what));
}
moveto(player, what);
if(get_attr(what, "Aenter"))
trigobj(what, get_attr(what, "Aenter"), player);
} else {
notify(player,"Permission denied.");
}
}
void do_leave(dbref player)
{
dbref where = DBFETCH(player)->location;
if(Typeof(where) == TYPE_ROOM)
notify(player, "You can't leave.");
else {
if(get_attr(where, "Leave"))
notify(player, get_attr(where, "Enter"));
if(get_attr(where, "Oleave") && !Dark(player)) {
sprintf(buf, "%%n %s", get_attr(where, "Oleave"));
notify_except(DBFETCH(where)->contents, player,
pronoun_substitute(player, buf, where));
}
moveto(player, DBFETCH(where)->location);
if(get_attr(where, "Aleave"))
trigobj(where, get_attr(where, "Aleave"), player);
}
}