/**
* \file move.c
*
* \brief Movement commands for PennMUSH.
*
*
*/
#include "copyrite.h"
#include "config.h"
#include <ctype.h>
#include <string.h>
#include "conf.h"
#include "externs.h"
#include "mushdb.h"
#include "attrib.h"
#include "match.h"
#include "flags.h"
#include "lock.h"
#include "dbdefs.h"
#include "parse.h"
#include "log.h"
#include "command.h"
#include "cmds.h"
#include "game.h"
#include "confmagic.h"
void moveit(dbref what, dbref where, int nomovemsgs);
static void send_contents(dbref loc, dbref dest);
static void maybe_dropto(dbref loc, dbref dropto);
static dbref find_var_dest(dbref player, dbref exit_obj);
static void add_follower(dbref leader, dbref follower);
static void add_following(dbref follower, dbref leader);
static void add_follow(dbref leader, dbref follower, int noisy);
static void del_follower(dbref leader, dbref follower);
static void del_following(dbref follower, dbref leader);
static void del_follow(dbref follower, dbref leader, int noisy);
static char *list_followers(dbref player);
static char *list_following(dbref player);
static int is_following(dbref follower, dbref leader);
static void follower_command(dbref leader, dbref loc, const char *com);
/** A convenience wrapper for enter_room().
* \param what object to move.
* \param where location to move it to.
*/
void
moveto(dbref what, dbref where)
{
enter_room(what, where, 0);
}
/** Send an object somewhere.
* \param what object to move.
* \param where location to move it to.
* \param nomovemsgs if 1, don't show movement messages.
*/
void
moveit(dbref what, dbref where, int nomovemsgs)
{
dbref loc, old;
dbref absloc, absold;
/* Don't move something into something it's holding */
if (recursive_member(where, what, 0))
return;
/* remove what from old loc */
absold = absolute_room(what);
if ((loc = old = Location(what)) != NOTHING) {
Contents(loc) = remove_first(Contents(loc), what);
}
/* test for special cases */
switch (where) {
case NOTHING:
Location(what) = NOTHING;
return; /* NOTHING doesn't have contents */
case HOME:
where = Home(what); /* home */
safe_tel(what, where, nomovemsgs);
return;
/*NOTREACHED */
break;
}
/* now put what in where */
PUSH(what, Contents(where));
Location(what) = where;
absloc = absolute_room(what);
if (!WIZ_NOAENTER || !(Wizard(what) && DarkLegal(what)))
if ((where != NOTHING) && (old != where)) {
did_it(what, what, NULL, NULL, "OXMOVE", NULL, NULL, old);
if (Hearer(what)) {
did_it_interact(what, old, "LEAVE", NULL, "OLEAVE", T("has left."),
"ALEAVE", old, NA_INTER_PRESENCE);
/* If the player is leaving a zone, do zone messages */
/* The tricky bit here is that we only care about the zone of
* the outermost contents */
if (GoodObject(absold) && GoodObject(Zone(absold))
&& (Zone(absloc) != Zone(absold)))
did_it_interact(what, Zone(absold), "ZLEAVE", NULL, "OZLEAVE", NULL,
"AZLEAVE", old, NA_INTER_SEE);
if (GoodObject(old) && !IsRoom(old))
did_it_interact(what, old, NULL, NULL, "OXLEAVE", NULL, NULL, where,
NA_INTER_SEE);
if (!IsRoom(where))
did_it_interact(what, where, NULL, NULL, "OXENTER", NULL, NULL, old,
NA_INTER_SEE);
/* If the player is entering a new zone, do zone messages */
if (!GoodObject(absold)
|| (GoodObject(Zone(absloc)) && (Zone(absloc) != Zone(absold))))
did_it_interact(what, Zone(absloc), "ZENTER", NULL, "OZENTER", NULL,
"AZENTER", where, NA_INTER_SEE);
did_it_interact(what, where, "ENTER", NULL, "OENTER", T("has arrived."),
"AENTER", where, NA_INTER_PRESENCE);
} else {
/* non-listeners only trigger the actions not the messages */
did_it(what, old, NULL, NULL, NULL, NULL, "ALEAVE", old);
if (GoodObject(absold) && GoodObject(Zone(absold))
&& (Zone(absloc) != Zone(absold)))
did_it(what, Zone(absold), NULL, NULL, NULL, NULL, "AZLEAVE", old);
if (!GoodObject(absold)
|| (GoodObject(Zone(absloc)) && (Zone(absloc) != Zone(absold))))
did_it(what, Zone(absloc), NULL, NULL, NULL, NULL, "AZENTER", where);
did_it(what, where, NULL, NULL, NULL, NULL, "AENTER", where);
}
}
if (!nomovemsgs)
did_it_interact(what, what, "MOVE", NULL, "OMOVE", NULL, "AMOVE", where,
NA_INTER_SEE);
}
/** A dropper is an object that can hear and has a connected owner */
#define Dropper(thing) (Hearer(thing) && Connected(Owner(thing)))
static void
send_contents(dbref loc, dbref dest)
{
dbref first;
dbref rest;
first = Contents(loc);
Contents(loc) = NOTHING;
/* blast locations of everything in list */
DOLIST(rest, first) {
Location(rest) = NOTHING;
}
while (first != NOTHING) {
rest = Next(first);
if (Dropper(first) || !eval_lock(first, loc, Dropto_Lock)) {
Location(first) = loc;
PUSH(first, Contents(loc));
} else
enter_room(first, Sticky(first) ? HOME : dest, 0);
first = rest;
}
Contents(loc) = reverse(Contents(loc));
}
static void
maybe_dropto(dbref loc, dbref dropto)
{
dbref thing;
if (loc == dropto)
return; /* bizarre special case */
if (!IsRoom(loc))
return;
/* check for players */
DOLIST(thing, Contents(loc)) {
if (Dropper(thing))
return;
}
/* no players, send everything to the dropto */
send_contents(loc, dropto);
}
/** Enter a container.
* \param player object entering the container.
* \param loc container to enter.
* \param nomovemsgs if 1, don't give movement messages.
*/
void
enter_room(dbref player, dbref loc, int nomovemsgs)
{
dbref old;
dbref dropto;
static int deep = 0;
if (deep++ > 15) {
deep--;
return;
}
if (!GoodObject(player)) {
deep--;
return;
}
/* check for room == HOME */
if (loc == HOME)
loc = Home(player);
if (!Mobile(player)) {
do_rawlog(LT_ERR, T("ERROR: Non object moved!! %d\n"), player);
deep--;
return;
}
if (IsExit(loc)) {
do_rawlog(LT_ERR, T("ERROR: Attempt to move %d to exit %d\n"), player, loc);
deep--;
return;
}
if (loc == player) {
do_rawlog(LT_ERR, T("ERROR: Attempt to move player %d into itself\n"),
player);
deep--;
return;
}
if (recursive_member(loc, player, 0)) {
do_rawlog(LT_ERR,
T("ERROR: Attempt to move player %d into carried object %d\n"),
player, loc);
deep--;
return;
}
/* get old location */
old = Location(player);
/* go there */
moveit(player, loc, nomovemsgs);
/* if old location has STICKY dropto, send stuff through it */
if ((loc != old) && Dropper(player) &&
(old != NOTHING) && (IsRoom(old)) &&
((dropto = Location(old)) != NOTHING) && Sticky(old))
maybe_dropto(old, dropto);
/* autolook */
look_room(player, loc, LOOK_AUTO);
deep--;
}
/** Teleport player to location while removing items they shouldn't take.
* \param player player to teleport.
* \param dest location to teleport player to.
* \param nomovemsgs if 1, don't show movement messages
*/
void
safe_tel(dbref player, dbref dest, int nomovemsgs)
{
dbref first;
dbref rest;
if (dest == HOME)
dest = Home(player);
if (Owner(Location(player)) == Owner(dest)) {
enter_room(player, dest, nomovemsgs);
return;
}
first = Contents(player);
Contents(player) = NOTHING;
/* blast locations of everything in list */
DOLIST(rest, first) {
Location(rest) = NOTHING;
}
while (first != NOTHING) {
rest = Next(first);
/* if thing is ok to take then move to player else send home.
* thing is not okay to move if it's STICKY and its home is not
* the player.
*/
if (!controls(player, first)
&& (Sticky(first)
&& (Home(first) != player)))
enter_room(first, HOME, nomovemsgs);
else {
PUSH(first, Contents(player));
Location(first) = player;
}
first = rest;
}
Contents(player) = reverse(Contents(player));
enter_room(player, dest, nomovemsgs);
}
/** Can a player go in a given direction?
* This checks to see if there's a go-able direction. It doesn't
* check whether the GOTO command is restricted. That should be
* done by the command parser.
* \param player dbref of mover.
* \param direction name of direction to move.
*/
int
can_move(dbref player, const char *direction)
{
if (!strcasecmp(direction, "home") && !Fixed(player))
return 1;
/* otherwise match on exits - don't use GoodObject here! */
return (match_result(player, direction, TYPE_EXIT, MAT_ENGLISH | MAT_EXIT) !=
NOTHING);
}
static dbref
find_var_dest(dbref player, dbref exit_obj)
{
/* This is used to evaluate the u-function DESTINATION on an exit with
* a VARIABLE (ambiguous) link.
*/
char const *abuf, *ap;
char buff[BUFFER_LEN], *bp;
ATTR *a;
dbref dest_room;
/* We'd like a DESTINATION attribute, but we'll settle for EXITTO,
* for portability
*/
a = atr_get(exit_obj, "DESTINATION");
if (!a)
a = atr_get(exit_obj, "EXITTO");
if (!a)
return NOTHING;
abuf = safe_atr_value(a);
if (!abuf)
return NOTHING;
if (!*abuf) {
free((Malloc_t) abuf);
return NOTHING;
}
ap = abuf;
bp = buff;
process_expression(buff, &bp, &ap, exit_obj, player, player,
PE_DEFAULT, PT_DEFAULT, NULL);
*bp = '\0';
dest_room = parse_objid(buff);
free((Malloc_t) abuf);
return (dest_room);
}
/** The move command.
* \param player the enactor.
* \param direction name of direction to move.
* \param type type of motion to check (global, zone, neither).
*/
void
do_move(dbref player, const char *direction, enum move_type type)
{
dbref exit_m, loc, var_dest;
if (!strcasecmp(direction, "home")) {
/* send him home */
/* but steal all his possessions */
if (!Mobile(player) || !GoodObject(Home(player)) ||
recursive_member(Home(player), player, 0)
|| (player == Home(player))) {
notify(player, T("Bad destination."));
return;
}
if ((loc = Location(player)) != NOTHING && !Dark(player) && !Dark(loc)) {
/* tell everybody else */
notify_except(Contents(loc), player,
tprintf(T("%s goes home."), Name(player)), NA_INTER_SEE);
}
/* give the player the messages */
notify(player, T("There's no place like home..."));
notify(player, T("There's no place like home..."));
notify(player, T("There's no place like home..."));
safe_tel(player, HOME, 0);
} else {
/* find the exit */
if (type == MOVE_GLOBAL)
exit_m =
match_result(player, direction, TYPE_EXIT,
MAT_ENGLISH | MAT_EXIT | MAT_GLOBAL | MAT_CHECK_KEYS);
else if (type == MOVE_ZONE)
exit_m =
match_result(player, direction, TYPE_EXIT,
MAT_ENGLISH | MAT_EXIT | MAT_REMOTES | MAT_CHECK_KEYS);
else
exit_m =
match_result(player, direction, TYPE_EXIT,
MAT_ENGLISH | MAT_EXIT | MAT_CHECK_KEYS);
switch (exit_m) {
case NOTHING:
/* try to force the object */
notify(player, T("You can't go that way."));
break;
case AMBIGUOUS:
notify(player, T("I don't know which way you mean!"));
break;
default:
/* we got one */
/* check to see if we're allowed to pass */
if (!eval_lock(player, Location(player), Leave_Lock)) {
fail_lock(player, Location(player), Leave_Lock,
T("You can't go that way."), NOTHING);
return;
}
if (could_doit(player, exit_m)) {
switch (Location(exit_m)) {
case HOME:
var_dest = Home(player);
break;
case AMBIGUOUS:
var_dest = find_var_dest(player, exit_m);
/* Only allowed if the owner of the exit could link to var_dest */
if (GoodObject(var_dest) && !can_link_to(exit_m, var_dest))
var_dest = NOTHING;
break;
default:
var_dest = Location(exit_m);
}
if (!GoodObject(var_dest)) {
do_rawlog(LT_ERR,
T("Exit #%d destination became %d during move.\n"),
exit_m, var_dest);
notify(player, T("Exit destination is invalid."));
return;
}
if (recursive_member(var_dest, player, 0)) {
notify(player, T("Exit destination is invalid."));
return;
}
did_it(player, exit_m, "SUCCESS", NULL, "OSUCCESS", NULL,
"ASUCCESS", NOTHING);
did_it(player, exit_m, "DROP", NULL, "ODROP", NULL, "ADROP", var_dest);
switch (Typeof(var_dest)) {
case TYPE_ROOM:
/* Remember the current room */
loc = Location(player);
/* Move the leader */
enter_room(player, var_dest, 0);
/* Move the followers if the leader is elsewhere */
if (Location(player) != loc)
follower_command(player, loc, tprintf("%s #%d", "goto", exit_m));
break;
case TYPE_PLAYER:
case TYPE_THING:
if (IsGarbage(var_dest)) {
notify(player, T("You can't go that way."));
return;
}
if (Location(var_dest) == NOTHING)
return;
/* Remember the current room */
loc = Location(player);
/* Move the leader */
safe_tel(player, var_dest, 0);
/* Move the followers if the leader is elsewhere */
if (Location(player) != loc)
follower_command(player, loc, tprintf("%s #%d", "goto", exit_m));
break;
case TYPE_EXIT:
notify(player, T("This feature coming soon."));
break;
}
} else
fail_lock(player, exit_m, Basic_Lock, T("You can't go that way."),
NOTHING);
break;
}
}
}
/** Move an exit to the first position in the room's exit list.
* \verbatim
* This implements @firstexit.
* \endverbatim
* \param player the enactor.
* \param what name of exit to promote.
*/
void
do_firstexit(dbref player, const char *what)
{
dbref thing;
dbref loc;
if ((thing =
noisy_match_result(player, what, TYPE_EXIT,
MAT_ENGLISH | MAT_EXIT)) == NOTHING)
return;
loc = Home(thing);
if (!controls(player, loc)) {
notify(player, T("You cannot modify exits in that room."));
return;
}
Exits(loc) = remove_first(Exits(loc), thing);
Source(thing) = loc;
PUSH(thing, Exits(loc));
notify_format(player, T("%s is now the first exit."), Name(thing));
}
/** The get command.
* \param player the enactor.
* \param what name of object to get.
*/
void
do_get(dbref player, const char *what)
{
dbref loc = Location(player);
dbref thing;
char tbuf1[BUFFER_LEN], tbuf2[BUFFER_LEN], *tp;
long match_flags =
MAT_NEIGHBOR | MAT_ABSOLUTE | MAT_CHECK_KEYS | MAT_NEAR | MAT_ENGLISH;
if (!IsRoom(loc) && !EnterOk(loc) && !controls(player, loc)) {
notify(player, T("Permission denied."));
return;
}
if (!Long_Fingers(player))
match_flags |= MAT_CONTROL;
if (match_result(player, what, TYPE_THING, match_flags) == NOTHING) {
if (POSSESSIVE_GET) {
dbref box;
const char *boxname = what;
/* take care of possessive get (stealing) */
box = parse_match_possessor(player, &what);
if (box == NOTHING) {
notify(player, T("I don't see that here."));
return;
} else if (box == AMBIGUOUS) {
notify_format(player, T("I can't tell which %s."), boxname);
return;
}
thing = match_result(box, what, NOTYPE, MAT_POSSESSION);
if (thing == NOTHING) {
notify(player, T("I don't see that here."));
return;
} else if (thing == AMBIGUOUS) {
notify_format(player, T("I can't tell which %s."), what);
return;
}
/* to steal something, you have to be able to get it, and the
* object must be ENTER_OK and not locked against you.
*/
if (could_doit(player, thing) &&
(POSSGET_ON_DISCONNECTED ||
(!IsPlayer(Location(thing)) ||
Connected(Location(thing)))) &&
(controls(player, thing) ||
(EnterOk(Location(thing)) &&
eval_lock(player, Location(thing), Enter_Lock)))) {
notify_format(Location(thing),
T("%s was taken from you."), Name(thing));
notify_format(thing, T("%s took you."), Name(player));
tp = tbuf1;
safe_format(tbuf1, &tp, T("You take %s from %s."), Name(thing),
Name(Location(thing)));
*tp = '\0';
tp = tbuf2;
safe_format(tbuf2, &tp, T("takes %s from %s."), Name(thing),
Name(Location(thing)));
*tp = '\0';
moveto(thing, player);
did_it(player, thing, "SUCCESS", tbuf1, "OSUCCESS", tbuf2, "ASUCCESS",
NOTHING);
did_it_with(player, player, "RECEIVE", NULL, "ORECEIVE", NULL,
"ARECEIVE", NOTHING, thing, NOTHING, NA_INTER_HEAR);
} else
fail_lock(player, thing, Basic_Lock,
T("You can't take that from there."), NOTHING);
} else {
notify(player, T("I don't see that here."));
}
return;
} else {
if ((thing = noisy_match_result(player, what, TYPE_THING, match_flags))
!= NOTHING) {
if (Location(thing) == player) {
notify(player, T("You already have that!"));
return;
}
if (Location(player) == thing) {
notify(player, T("It's all around you!"));
return;
}
if (recursive_member(player, thing, 0)) {
notify(player, T("Bad destination."));
return;
}
switch (Typeof(thing)) {
case TYPE_PLAYER:
case TYPE_THING:
if (thing == player) {
notify(player, T("You cannot get yourself!"));
return;
}
if (could_doit(player, thing)) {
moveto(thing, player);
notify_format(thing, T("%s took you."), Name(player));
tp = tbuf1;
safe_format(tbuf1, &tp, T("You take %s."), Name(thing));
*tp = '\0';
tp = tbuf2;
safe_format(tbuf2, &tp, T("takes %s."), Name(thing));
*tp = '\0';
did_it(player, thing, "SUCCESS", tbuf1, "OSUCCESS", tbuf2,
"ASUCCESS", NOTHING);
did_it_with(player, player, "RECEIVE", NULL, "ORECEIVE", NULL,
"ARECEIVE", NOTHING, thing, NOTHING, NA_INTER_HEAR);
} else
fail_lock(player, thing, Basic_Lock, T("You can't pick that up."),
NOTHING);
break;
case TYPE_EXIT:
notify(player, T("You can't pick up exits."));
return;
default:
notify(player, T("You can't take that!"));
break;
}
}
}
}
/** Drop an object.
* \param player the enactor.
* \param name name of object to drop.
*/
void
do_drop(dbref player, const char *name)
{
dbref loc;
dbref thing;
char tbuf1[BUFFER_LEN], tbuf2[BUFFER_LEN], *tp;
if ((loc = Location(player)) == NOTHING)
return;
switch (thing =
match_result(player, name, TYPE_THING,
MAT_POSSESSION | MAT_ABSOLUTE | MAT_CONTROL |
MAT_ENGLISH)) {
case NOTHING:
notify(player, T("You don't have that!"));
return;
case AMBIGUOUS:
notify(player, T("I don't know which you mean!"));
return;
default:
if (Location(thing) != player) {
/* Shouldn't ever happen. */
notify(player, T("You can't drop that."));
return;
} else if (IsExit(thing)) {
notify(player, T("Sorry you can't drop exits."));
return;
} else if (!eval_lock(player, thing, Drop_Lock)) {
notify(player, T("You can't seem to get rid of that."));
return;
} else if (IsRoom(loc) && !eval_lock(player, loc, Drop_Lock)) {
notify(player, T("You can't seem to drop things here."));
return;
} else if (Sticky(thing) && !Fixed(thing)) {
notify(thing, T("Dropped."));
safe_tel(thing, HOME, 0);
} else if ((Location(loc) != NOTHING) && IsRoom(loc) && !Sticky(loc)
&& eval_lock(thing, loc, Dropto_Lock)) {
/* location has immediate dropto */
notify_format(thing, T("%s drops you."), Name(player));
moveto(thing, Location(loc));
} else {
notify_format(thing, T("%s drops you."), Name(player));
moveto(thing, loc);
}
break;
}
tp = tbuf1;
safe_format(tbuf1, &tp, T("You drop %s."), Name(thing));
*tp = '\0';
tp = tbuf2;
safe_format(tbuf2, &tp, T("drops %s."), Name(thing));
*tp = '\0';
did_it(player, thing, "DROP", tbuf1, "ODROP", tbuf2, "ADROP", NOTHING);
}
/** The empty command.
* This command causes the player to attempt to move everything in
* the thing to the location of the thing.
* Thing must be in player's inventory or in player's location.
* For each item in thing, movement is allowed if one of these is true:
* (a) thing is inside player, and player is allowed to get thing's item
* (b) thing is next to player, player is allowed to get thing's item,
* and player is allowed to drop item in player's location.
* We do not consider the cases of forcing the object to drop the items,
* teleporting the items out, or forcing the items to leave;
* 'empty' implies that the items pass through the player's hands.
*
* There is a choice to be made here with regard to locks - do we
* check locks on the thing (e.g. enter locks) and its location
* (e.g. drop locks) once or each time? If we choose once, we break
* locks that might make decisions based on the number of items there.
* If we choose multiple, we risk running side effects more than once.
* We choose multiple, as that's what would happen if the
* player did it manually.
* \param player the enactor.
* \param what the name of the object to empty.
*/
void
do_empty(dbref player, const char *what)
{
dbref player_loc;
dbref thing, thing_loc;
dbref item;
int empty_ok;
int count = 0;
int next;
if ((player_loc = Location(player)) == NOTHING)
return;
thing =
noisy_match_result(player, what, TYPE_THING | TYPE_PLAYER,
MAT_NEAR_THINGS | MAT_ENGLISH);
if (!GoodObject(thing))
return;
thing_loc = Location(thing);
/* Object to empty must be in player's inventory or location */
if ((thing_loc != player) && (thing_loc != player_loc)) {
notify(player, T("You can't empty that from here."));
return;
}
for (item = first_visible(player, Contents(thing)); GoodObject(item);
item = first_visible(player, next)) {
next = Next(item);
if (IsExit(item))
continue; /* No dropping exits */
empty_ok = 0;
if (player == thing) {
/* empty me: You don't need to get what's in your inventory already */
if (eval_lock(player, item, Drop_Lock) &&
(!IsRoom(thing_loc) || eval_lock(player, thing_loc, Drop_Lock)))
empty_ok = 1;
}
/* Check that player can get stuff from thing */
else if (controls(player, thing) ||
(EnterOk(thing) && eval_lock(player, thing, Enter_Lock))) {
/* Check that player can get item */
if (!could_doit(player, item)) {
/* Send failure message if set, otherwise be quiet */
fail_lock(player, thing, Basic_Lock, NULL, NOTHING);
continue;
}
/* Now check for dropping in the destination */
/* Thing is in player's inventory - sufficient */
if (thing_loc == player)
empty_ok = 1;
/* Thing is in player's location - player must also be able to drop */
else if (eval_lock(player, item, Drop_Lock) &&
(!IsRoom(thing_loc) || eval_lock(player, thing_loc, Drop_Lock)))
empty_ok = 1;
}
/* Now do the work, if we should. That includes triggering messages */
if (empty_ok) {
char tbuf1[BUFFER_LEN], tbuf2[BUFFER_LEN], *tp;
count++;
/* Get messages */
if (thing != player) {
notify_format(thing, T("%s was taken from you."), Name(item));
notify_format(item, T("%s took you."), Name(player));
tp = tbuf1;
safe_format(tbuf1, &tp, T("You take %s from %s."), Name(item),
Name(thing));
*tp = '\0';
tp = tbuf2;
safe_format(tbuf2, &tp, T("takes %s from %s."), Name(item),
Name(thing));
*tp = '\0';
moveto(item, player);
did_it(player, item, "SUCCESS", tbuf1, "OSUCCESS", tbuf2, "ASUCCESS",
NOTHING);
did_it_with(player, player, "RECEIVE", NULL, "ORECEIVE", NULL,
"ARECEIVE", NOTHING, item, NOTHING, NA_INTER_HEAR);
}
/* Drop messages */
if (thing_loc != player) {
if (Sticky(item) && !Fixed(item)) {
safe_tel(thing, HOME, 0);
} else if ((Location(thing_loc) != NOTHING) && IsRoom(thing_loc)
&& !Sticky(thing_loc)
&& eval_lock(item, thing_loc, Dropto_Lock)) {
/* location has immediate dropto */
notify_format(item, T("%s drops you."), Name(player));
moveto(item, Location(thing_loc));
} else {
notify_format(item, T("%s drops you."), Name(player));
moveto(item, thing_loc);
}
tp = tbuf1;
safe_format(tbuf1, &tp, T("You drop %s."), Name(item));
*tp = '\0';
tp = tbuf2;
safe_format(tbuf2, &tp, T("drops %s."), Name(item));
*tp = '\0';
did_it(player, item, "DROP", tbuf1, "ODROP", tbuf2, "ADROP", NOTHING);
}
}
}
notify_format(player, T("You remove %d object%s from %s."),
count, (count == 1) ? "" : "s", Name(thing));
return;
}
/** The enter command.
* \param player the enactor.
* \param what name of object to enter.
*/
void
do_enter(dbref player, const char *what)
{
dbref thing;
dbref loc;
long match_flags = MAT_CHECK_KEYS | MAT_NEIGHBOR | MAT_ENGLISH | MAT_ABSOLUTE;
if ((thing = noisy_match_result(player, what, TYPE_THING, match_flags))
== NOTHING)
return;
switch (Typeof(thing)) {
case TYPE_ROOM:
case TYPE_EXIT:
notify(player, T("Permission denied."));
break;
default:
/* Remember the current room */
loc = Location(player);
/* Only privileged players may enter something remotely */
if ((Location(thing) != loc) && !Hasprivs(player)) {
notify(player, T("I don't see that here."));
return;
}
/* the object must pass the lock. Also, the thing being entered */
/* has to be controlled, or must be enter_ok */
if (!((EnterOk(thing) || controls(player, thing)) &&
(eval_lock(player, thing, Enter_Lock))
)) {
fail_lock(player, thing, Enter_Lock, T("Permission denied."), NOTHING);
return;
}
if (thing == player) {
notify(player, T("Sorry, you must remain beside yourself!"));
return;
}
/* Move the leader */
safe_tel(player, thing, 0);
/* Move the followers if the leader is elsewhere */
if (Location(player) != loc)
follower_command(player, loc, tprintf("%s #%d", "enter", thing));
break;
}
}
/** The leave command.
* \param player the enactor.
*/
void
do_leave(dbref player)
{
dbref loc;
loc = Location(player);
if (IsRoom(loc) || IsGarbage(loc) || IsGarbage(Location(loc))
|| NoLeave(loc)
|| !eval_lock(player, loc, Leave_Lock)
) {
fail_lock(player, loc, Leave_Lock, T("You can't leave."), NOTHING);
return;
}
enter_room(player, Location(loc), 0);
if (Location(player) != loc)
follower_command(player, loc, "leave");
}
/** Is direction a global exit?
* \param player looker.
* \param direction name of exit.
* \retval 1 direction is a global exit.
* \retval 0 direction is not a global exit.
*/
int
global_exit(dbref player, const char *direction)
{
return (GoodObject
(match_result(player, direction, TYPE_EXIT, MAT_GLOBAL | MAT_EXIT)));
}
/** Is direction a remote exit?
* \param player looker.
* \param direction name of exit.
* \retval 1 direction is a remote exit.
* \retval 0 direction is not a remote exit.
*/
int
remote_exit(dbref player, const char *direction)
{
return (GoodObject
(match_result(player, direction, TYPE_EXIT, MAT_REMOTES | MAT_EXIT)));
}
/** Wrapper for exit movement.
* We check local exit, then zone exit, then global. If nothing is
* matched, treat it as local so player will get an error message.
* \param player the mover.
* \param command direction to move.
*/
void
move_wrapper(dbref player, const char *command)
{
if (!Mobile(player))
return;
if (!strcasecmp(command, "home") && Fixed(player)) {
notify(player, T("You can't do that IC!"));
return;
}
if (can_move(player, command))
do_move(player, command, MOVE_NORMAL);
else if ((Zone(Location(player)) != NOTHING) && remote_exit(player, command))
do_move(player, command, MOVE_ZONE);
else if ((Location(player) != MASTER_ROOM)
&& global_exit(player, command))
do_move(player, command, MOVE_GLOBAL);
else
do_move(player, command, MOVE_NORMAL);
}
/* Routines for dealing with the follow commands */
/** The follow command.
* \verbatim
* follow <arg> tries to start following
* follow alone lists who you're following
* \endverbatim
* \param player the enactor.
* \param arg name of object to follow.
*/
void
do_follow(dbref player, const char *arg)
{
dbref leader;
if (arg && *arg) {
/* Who do we want to follow? */
leader = match_result(player, arg, NOTYPE, MAT_NEARBY);
if (leader == AMBIGUOUS) {
notify(player, T("I can't tell which one to follow."));
return;
}
if (!GoodObject(leader) || !GoodObject(Location(player))
|| (IsPlayer(leader) && !Connected(leader))
|| ((DarkLegal(leader)
|| (Dark(Location(player)) && !Light(leader)))
&& !See_All(player))) {
notify(player, T("You don't see that here."));
return;
}
if (!Mobile(leader)) {
notify(player, T("You can only follow players and things."));
return;
}
if (leader == player) {
notify(player, T("You chase your tail for a while and feel silly."));
return;
}
/* Are we already following them? */
if (is_following(player, leader)) {
notify_format(player, T("You're already following %s."), Name(leader));
return;
}
/* Ok, are we allowed to follow them? */
if (!eval_lock(player, leader, Follow_Lock)) {
fail_lock(player, leader, Follow_Lock,
T("You're not alllowed to follow."), Location(player));
return;
}
/* Ok, looks good */
add_follow(leader, player, 1);
} else {
/* List followers */
notify_format(player, T("You are following: %s"), list_following(player));
notify_format(player, T("You are followed by: %s"), list_followers(player));
}
}
/** The unfollow command.
* \verbatim
* unfollow <arg> removes someone from your following list
* unfollow alone removes everyone from your following list.
* \endverbatim
* \param player the enactor.
* \param arg object to stop following.
*/
void
do_unfollow(dbref player, const char *arg)
{
dbref leader;
if (arg && *arg) {
/* Who do we want to stop following? */
leader = match_result(player, arg, NOTYPE, MAT_OBJECTS);
if (leader == AMBIGUOUS) {
notify(player, T("I can't tell which one to stop following."));
return;
}
if (!GoodObject(leader)) {
notify(player, T("I don't see that here."));
return;
}
/* Are we following them? */
if (!is_following(player, leader)) {
notify_format(player, T("You're not following %s."), Name(leader));
return;
}
/* Ok, looks good */
del_follow(leader, player, 1);
} else {
/* Stop following everyone */
clear_following(player, 1);
notify(player, T("You stop following anyone."));
}
}
/** The dismiss command.
* \verbatim
* dismiss <arg> removes someone from your followers list
* dismiss alone removes everyone from your followers list.
* \endverbatim
* \param player the enactor.
* \param arg name of object to dismiss.
*/
void
do_dismiss(dbref player, const char *arg)
{
dbref follower;
if (arg && *arg) {
/* Who do we want to stop leading? */
follower = match_result(player, arg, NOTYPE, MAT_OBJECTS);
if (!GoodObject(follower)) {
notify(player, T("I don't recognize who you want to dismiss."));
return;
}
/* Are we following them? */
if (!is_following(follower, player)) {
notify_format(player, T("%s isn't following you."), Name(follower));
return;
}
/* Ok, looks good */
del_follow(player, follower, 1);
} else {
/* Stop leading everyone */
clear_followers(player, 1);
notify(player, T("You dismiss all your followers."));
}
}
/** The desert command.
* \verbatim
* desert <arg> removes someone from your followers and following list
* desert alone removes everyone from both lists
* \endverbatim
* \param player the enactor.
* \param arg name of object to desert.
*/
void
do_desert(dbref player, const char *arg)
{
dbref who;
if (arg && *arg) {
/* Who do we want to stop leading? */
who = match_result(player, arg, NOTYPE, MAT_OBJECTS);
if (!GoodObject(who)) {
notify(player, T("I don't recognize who you want to desert."));
return;
}
/* Are we following or leading them? */
if (!is_following(who, player)
&& !is_following(player, who)) {
notify_format(player,
T("%s isn't following you, nor vice versa."), Name(who));
return;
}
/* Ok, looks good */
del_follow(player, who, 1);
del_follow(who, player, 1);
} else {
/* Stop leading everyone */
clear_followers(player, 1);
clear_following(player, 1);
notify(player, T("You desert everyone you're leading or following."));
}
}
/* Add someone to a player's FOLLOWERS attribute */
static void
add_follower(dbref leader, dbref follower)
{
ATTR *a;
char tbuf1[BUFFER_LEN];
char *bp;
a = atr_get_noparent(leader, "FOLLOWERS");
if (!a) {
(void) atr_add(leader, "FOLLOWERS", unparse_dbref(follower), GOD, NOTHING);
} else {
bp = tbuf1;
safe_str(atr_value(a), tbuf1, &bp);
safe_chr(' ', tbuf1, &bp);
safe_dbref(follower, tbuf1, &bp);
*bp = '\0';
(void) atr_add(leader, "FOLLOWERS", tbuf1, GOD, NOTHING);
}
}
/* Add someone to a player's FOLLOWING attribute */
static void
add_following(dbref follower, dbref leader)
{
ATTR *a;
char tbuf1[BUFFER_LEN];
char *bp;
a = atr_get_noparent(follower, "FOLLOWING");
if (!a) {
(void) atr_add(follower, "FOLLOWING", unparse_dbref(leader), GOD, NOTHING);
} else {
bp = tbuf1;
safe_str(atr_value(a), tbuf1, &bp);
safe_chr(' ', tbuf1, &bp);
safe_dbref(leader, tbuf1, &bp);
*bp = '\0';
(void) atr_add(follower, "FOLLOWING", tbuf1, GOD, NOTHING);
}
}
static void
add_follow(dbref leader, dbref follower, int noisy)
{
char msg[BUFFER_LEN];
add_follower(leader, follower);
add_following(follower, leader);
if (noisy) {
strcpy(msg, tprintf(T("You begin following %s."), Name(leader)));
notify_format(leader, T("%s begins following you."), Name(follower));
did_it(follower, leader, "FOLLOW", msg, "OFOLLOW", NULL,
"AFOLLOW", NOTHING);
}
}
/* Delete someone from a player's FOLLOWERS attribute */
static void
del_follower(dbref leader, dbref follower)
{
ATTR *a;
char tbuf1[BUFFER_LEN];
char flwr[BUFFER_LEN];
a = atr_get_noparent(leader, "FOLLOWERS");
if (!a)
return; /* No followers, so no deletion */
/* Let's take it apart and put it back together w/o follower */
strcpy(flwr, unparse_dbref(follower));
strcpy(tbuf1, atr_value(a));
(void) atr_add(leader, "FOLLOWERS",
remove_word(tbuf1, flwr, ' '), GOD, NOTHING);
}
/* Delete someone from a player's FOLLOWING attribute */
static void
del_following(dbref follower, dbref leader)
{
ATTR *a;
char tbuf1[BUFFER_LEN];
char ldr[BUFFER_LEN];
a = atr_get_noparent(follower, "FOLLOWING");
if (!a)
return; /* Not following, so no deletion */
/* Let's take it apart and put it back together w/o leader */
strcpy(ldr, unparse_dbref(leader));
strcpy(tbuf1, atr_value(a));
(void) atr_add(follower, "FOLLOWING",
remove_word(tbuf1, ldr, ' '), GOD, NOTHING);
}
static void
del_follow(dbref leader, dbref follower, int noisy)
{
char msg[BUFFER_LEN];
del_follower(leader, follower);
del_following(follower, leader);
if (noisy) {
strcpy(msg, tprintf(T("You stop following %s."), Name(leader)));
notify_format(leader, T("%s stops following you."), Name(follower));
did_it(follower, leader, "UNFOLLOW", msg, "OUNFOLLOW",
NULL, "AUNFOLLOW", NOTHING);
}
}
/* Return a list of names of players who are my followers, comma-separated */
static char *
list_followers(dbref player)
{
ATTR *a;
char tbuf1[BUFFER_LEN];
char *s, *sp;
static char buff[BUFFER_LEN];
char *bp;
dbref who;
int first = 1;
a = atr_get_noparent(player, "FOLLOWERS");
if (!a)
return (char *) "";
strcpy(tbuf1, atr_value(a));
bp = buff;
s = trim_space_sep(tbuf1, ' ');
while (s) {
sp = split_token(&s, ' ');
who = parse_dbref(sp);
if (GoodObject(who)) {
if (!first)
safe_str(", ", buff, &bp);
safe_str(Name(who), buff, &bp);
first = 0;
}
}
*bp = '\0';
return buff;
}
/* Return a list of names of players who I'm following, comma-separated */
static char *
list_following(dbref player)
{
ATTR *a;
char tbuf1[BUFFER_LEN];
char *s, *sp;
static char buff[BUFFER_LEN];
char *bp;
dbref who;
int first = 1;
a = atr_get_noparent(player, "FOLLOWING");
if (!a)
return (char *) "";
strcpy(tbuf1, atr_value(a));
bp = buff;
s = trim_space_sep(tbuf1, ' ');
while (s) {
sp = split_token(&s, ' ');
who = parse_dbref(sp);
if (GoodObject(who)) {
if (!first)
safe_str(", ", buff, &bp);
safe_str(Name(who), buff, &bp);
first = 0;
}
}
*bp = '\0';
return buff;
}
/* Is follower following leader? */
static int
is_following(dbref follower, dbref leader)
{
ATTR *a;
char *s, *sp;
char tbuf1[BUFFER_LEN];
/* There are probably fewer dbrefs on the follower's FOLLOWING list
* than the leader's FOLLOWERS list, so we check the former
*/
a = atr_get_noparent(follower, "FOLLOWING");
if (!a)
return 0; /* Following no one */
strcpy(tbuf1, atr_value(a));
s = trim_space_sep(tbuf1, ' ');
while (s) {
sp = split_token(&s, ' ');
if (parse_dbref(sp) == leader)
return 1;
}
return 0;
}
/** Clear a player's followers list.
* \param leader dbref of player whose list is to be cleared.
* \param noisy if 1, notify the player.
*/
void
clear_followers(dbref leader, int noisy)
{
ATTR *a;
char *s, *sp;
char tbuf1[BUFFER_LEN];
dbref flwr;
a = atr_get_noparent(leader, "FOLLOWERS");
if (!a)
return; /* No one's following me */
strcpy(tbuf1, atr_value(a));
s = trim_space_sep(tbuf1, ' ');
while (s) {
sp = split_token(&s, ' ');
flwr = parse_dbref(sp);
if (GoodObject(flwr)) {
del_following(flwr, leader);
if (noisy)
notify_format(flwr, T("You stop following %s."), Name(leader));
}
}
(void) atr_clr(leader, "FOLLOWERS", GOD);
}
/** Clear a player's following list.
* \param follower dbref of player whose list is to be cleared.
* \param noisy if 1, notify the player.
*/
void
clear_following(dbref follower, int noisy)
{
ATTR *a;
char *s, *sp;
char tbuf1[BUFFER_LEN];
dbref ldr;
a = atr_get_noparent(follower, "FOLLOWING");
if (!a)
return; /* I'm not following anyone */
strcpy(tbuf1, atr_value(a));
s = trim_space_sep(tbuf1, ' ');
while (s) {
sp = split_token(&s, ' ');
ldr = parse_dbref(sp);
if (GoodObject(ldr)) {
del_follower(ldr, follower);
if (noisy)
notify_format(ldr, T("%s stops following you."), Name(follower));
}
}
(void) atr_clr(follower, "FOLLOWING", GOD);
}
/* For all of a leader's followers who are in the same room as the
* leader, run the same command the leader just ran.
*/
static void
follower_command(dbref leader, dbref loc, const char *com)
{
dbref follower;
ATTR *a;
char *s, *sp;
char tbuf1[BUFFER_LEN];
char combuf[BUFFER_LEN];
if (!com || !*com)
return;
strcpy(combuf, com);
a = atr_get_noparent(leader, "FOLLOWERS");
if (!a)
return; /* No followers */
strcpy(tbuf1, atr_value(a));
s = tbuf1;
while (s) {
sp = split_token(&s, ' ');
follower = parse_dbref(sp);
if (GoodObject(follower) && (Location(follower) == loc)
&& (Connected(follower) || IsThing(follower))
&& (!(DarkLegal(leader)
|| (Dark(Location(follower)) && !Light(leader)))
|| See_All(follower))) {
/* This is a follower who was in the room with the leader. Follow. */
notify_format(follower, T("You follow %s."), Name(leader));
process_command(follower, combuf, follower, 0);
}
}
}