/** * \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); } } }