/*
* movement.cpp
* Functions dealing with player movement.
* ____ _
* | _ \ ___ __ _| |_ __ ___ ___
* | |_) / _ \/ _` | | '_ ` _ \/ __|
* | _ < __/ (_| | | | | | | \__ \
* |_| \_\___|\__,_|_|_| |_| |_|___/
*
* Permission to use, modify and distribute is granted via the
* Creative Commons - Attribution - Non Commercial - Share Alike 3.0 License
* http://creativecommons.org/licenses/by-nc-sa/3.0/
*
* Copyright (C) 2007-2012 Jason Mitchell, Randi Mitchell
* Contributions by Tim Callahan, Jonathan Hseu
* Based on Mordor (C) Brooke Paul, Brett J. Vickers, John P. Freeman
*
*/
#include "mud.h"
#include "commands.h"
#include <string>
#include "guilds.h"
#include "property.h"
#include "move.h"
#include "unique.h"
#include "calendar.h"
//*********************************************************************
// tooFarAway
//*********************************************************************
bool Move::tooFarAway(BaseRoom* pRoom, BaseRoom* tRoom, bool track) {
if(pRoom == tRoom)
return(false);
if(!pRoom || !tRoom || pRoom->flagIsSet(R_JAIL) || tRoom->flagIsSet(R_JAIL))
return(true);
const CatRefInfo* pr = gConfig->getCatRefInfo(pRoom);
if(!pr)
return(true);
const CatRefInfo* tr = gConfig->getCatRefInfo(tRoom);
if(!tr)
return(true);
if(pr->getTeleportZone() != tr->getTeleportZone())
return(true);
if(track && pr->getTrackZone() != tr->getTrackZone())
return(true);
return(false);
}
bool Move::tooFarAway(Creature *player, BaseRoom* room) {
if(player->isStaff())
return(false);
if(!room || Move::tooFarAway(player->getRoomParent(), room, false)) {
player->print("That location is too far away.\n");
return(true);
}
return(false);
}
bool Move::tooFarAway(Creature *player, Creature *target, bstring action) {
if(player->isStaff())
return(false);
BaseRoom* tRoom = target->getRoomParent();
// target is offline, so we need to load their room
if(!tRoom) {
UniqueRoom *room=0;
if(target->currentLocation.room.id && loadRoom(target->currentLocation.room, &room))
tRoom = room;
}
if(!tRoom || Move::tooFarAway(player->getRoomParent(), tRoom, action == "track")) {
player->print("%M is too far away to %s.\n", target, action.c_str());
return(true);
}
return(false);
}
//*********************************************************************
// broadcast
//*********************************************************************
void Move::broadcast(Creature* player, Container* container, bool ordinal, bstring exit, bool hiddenExit) {
bstring strAction = Move::getString(player, ordinal, exit);
bool noShow = (player->pFlagIsSet(P_DM_INVIS));
if(!noShow) {
if(player->isMonster() || (player->isPlayer() && !player->isEffected("mist"))) {
if(hiddenExit)
::broadcast(player->getSock(), container, "%M slips out of sight.", player);
else
::broadcast(player->getSock(), container, "%M %s %s^x.", player, strAction.c_str(), exit.c_str());
} else if(player->isEffected("mist") && !player->flagIsSet(P_DM_INVIS)) {
if(hiddenExit)
::broadcast(player->getSock(), container, "A light mist slips out of sight.");
else
::broadcast(player->getSock(), container, "A light mist %s %s^x.", strAction.c_str(), exit.c_str());
}
}
if(noShow || hiddenExit) {
if(player->isDm())
::broadcast(isDm, player->getSock(), container, "*STAFF* %M %s %s^x.", player, strAction.c_str(), exit.c_str());
if(player->getClass() == CARETAKER)
::broadcast(isCt, player->getSock(), container, "*STAFF* %M %s %s^x.", player, strAction.c_str(), exit.c_str());
if(!player->isCt())
::broadcast(isStaff, player->getSock(), container, "*STAFF* %M %s %s^x.", player, strAction.c_str(), exit.c_str());
}
}
//*********************************************************************
// formatFindExit
//*********************************************************************
// This function allows for multi-word desc-only exits.
bstring Move::formatFindExit(cmd* cmnd) {
bstring str;
int i=0;
str = "";
for(i=1; i<cmnd->num; i++) {
if(cmnd->str[i][0]) {
str += cmnd->str[i];
str += " ";
}
}
if(str.length()>1)
str = str.substr(0, str.length() - 1);
return(str);
}
//*********************************************************************
// isSneaking
//*********************************************************************
// if we're sneaking
bool Move::isSneaking(cmd* cmnd) {
return(!strncmp(cmnd->str[0], "sn", 2));
}
//*********************************************************************
// isOrdinal
//*********************************************************************
// if it's an ordinal move
bool Move::isOrdinal(cmd* cmnd) {
// Ordinal if not go, enter, exit, or sneak
return( strncmp(cmnd->str[0], "go", 2) &&
strncmp(cmnd->str[0], "en", 2) &&
strncmp(cmnd->str[0], "ex", 2) &&
!Move::isSneaking(cmnd) );
}
//*********************************************************************
// recycle
//*********************************************************************
// given the room we just left (everyone is out) this code handles the
// recycling of the room
AreaRoom *Move::recycle(AreaRoom* room, Exit* exit) {
if(room->canDelete()) {
room->setMapMarker(&exit->target.mapmarker);
room->recycle();
} else {
room = new AreaRoom(room->area, &exit->target.mapmarker);
room->area->rooms[room->mapmarker.str()] = room;
}
return(room);
}
//*********************************************************************
// update
//*********************************************************************
// updates some settings on the character once they move
void Move::update(Player* player) {
if( player->getRoomParent()->isUnderwater() && !player->flagIsSet(P_FREE_ACTION)) {
player->lasttime[LT_MOVED].ltime = time(0);
player->lasttime[LT_MOVED].interval = 2L;
}
if(player->flagIsSet(P_SITTING))
player->stand();
player->clearFlag(P_JUST_TRAINED);
player->lasttime[LT_MOVED].misc++;
}
//*********************************************************************
// broadMove
//*********************************************************************
// announces to the room we're leaving that we're leaving
void Move::broadMove(Creature* player, Exit* exit, cmd* cmnd, bool sneaking) {
if(exit->getEnter() != "")
player->printColor("%s\n", exit->getEnter().c_str());
// sneak failure is decided earlier
if(!sneaking) {
Move::broadcast(
player,
player->getRoomParent(),
Move::isOrdinal(cmnd),
exit->getName(),
exit->flagIsSet(X_SECRET) || exit->isConcealed() || exit->flagIsSet(X_DESCRIPTION_ONLY)
);
} else {
if(player->isDm())
::broadcast(isDm, player->getSock(), player->getRoomParent(), "*DM* %M snuck to the %s^x.", player, exit->getCName());
if(player->getClass() == CARETAKER)
::broadcast(isCt, player->getSock(), player->getRoomParent(), "*DM* %M snuck to the %s^x.", player, exit->getCName());
if(!player->isCt())
::broadcast(isStaff, player->getSock(), player->getRoomParent(), "*DM* %M snuck to the %s^x.", player, exit->getCName());
player->checkImprove("sneak", true);
}
}
//*********************************************************************
// track
//*********************************************************************
// takes care of any tracks we leave
bool Move::track(UniqueRoom* room, MapMarker *mapmarker, Exit* exit, Player* player, bool reset) {
if(room && room->flagIsSet(R_PERMENANT_TRACKS))
return(reset);
if(exit->flagIsSet(X_DESCRIPTION_ONLY) || exit->flagIsSet(X_PORTAL))
return(reset);
if( player &&
!player->isEffected("fly") &&
!player->isEffected("levitate") &&
!player->isEffected("pass-without-trace") &&
!player->isStaff())
{
Track *track=0;
if(room)
track = &room->track;
else if(mapmarker) {
Area *area = gConfig->getArea(mapmarker->getArea());
if(area) {
track = area->getTrack(mapmarker);
// only make tracks if they will last longer than 0 minutes
if(!track && area->getTrackDuration(mapmarker)) {
AreaTrack *aTrack = new AreaTrack;
*&aTrack->mapmarker = *mapmarker;
aTrack->setDuration(area->getTrackDuration(mapmarker));
area->addTrack(aTrack);
track = &aTrack->track;
}
}
}
if(track) {
if(!reset) {
track->setNum(0);
track->setSize(NO_SIZE);
}
track->setDirection(exit->getName());
if(player->getSize() > track->getSize())
track->setSize(player->getSize());
track->setNum(track->getNum()-1);
return(true);
}
}
return(reset);
}
void Move::track(UniqueRoom* room, MapMarker *mapmarker, Exit* exit, Player *leader, std::list<Creature*> *followers) {
std::list<Creature*>::iterator it;
bool reset=false;
if(!room && !mapmarker)
return;
if(room && room->flagIsSet(R_PERMENANT_TRACKS))
return;
if(exit->flagIsSet(X_DESCRIPTION_ONLY))
return;
// the leader is not stored in the list
reset = Move::track(room, mapmarker, exit, leader, reset);
for(it = followers->begin() ; it != followers->end() ; it++)
reset = Move::track(room, mapmarker, exit, (*it)->getAsPlayer(), reset);
}
//*********************************************************************
// sneak
//*********************************************************************
// handles our attempted sneaking
bool Move::sneak(Player* player, bool sneaking) {
if(sneaking && player->isEffected("mist"))
player->setFlag(P_SNEAK_WHILE_MISTED);
if(!player->isStaff() && !player->isEffected("mist")) {
// see if they failed sneaking or not
if(sneaking && (
!player->flagIsSet(P_HIDDEN) ||
mrand(1, 100) > player->getSneakChance()
)
) {
player->checkImprove("sneak", false);
player->print("You failed to sneak.\n");
sneaking = false;
}
}
if(!sneaking)
player->unhide();
return(sneaking);
}
//*********************************************************************
// canEnter
//*********************************************************************
// this function determines if we're allowed to go in the exit provided
// it should print the reason for failure
bool Move::canEnter(Player* player, Exit* exit, bool leader) {
// this also keeps builders out of overland rooms
if(!player->checkBuilder(exit->target.room))
return(false);
// sending true to this function will print the reason
// why they can't enter
if(!player->canEnter(exit, true))
return(false);
for(Monster* pet : player->pets) {
if(pet && !pet->canEnter(exit) && !player->checkStaff("%M cannot go that way.\n", pet))
return(false);
}
if(player->isStaff())
return(true);
// This is handled here and not in canEnter. If it were in canEnter,
// they wouldn't be able to flee past the monster.
for(Monster* mons : player->getRoomParent()->monsters) {
if( mons->flagIsSet(M_BLOCK_EXIT) && mons->isEnemy(player) && mons->canSee(player)) {
player->print("%M blocks your exit.\n", mons);
return(false);
}
}
if( (exit->flagIsSet(X_NEEDS_CLIMBING_GEAR) || exit->flagIsSet(X_CLIMBING_GEAR_TO_REPEL)) &&
!player->isEffected("levitate") &&
!player->isEffected("mist"))
{
int fall = (exit->flagIsSet(X_DIFFICULT_CLIMB) ? 50 : 0) + 50 - player->getFallBonus();
if(mrand(1, 100) < fall) {
int dmg = mrand(5, 15 + fall / 10);
player->printColor("You fell and hurt yourself for %s%d^x damage.\n", player->customColorize("*CC:DAMAGE*").c_str(), dmg);
::broadcast(player->getSock(), player->getParent(), "%M fell down.", player);
broadcastGroup(false, player, "%M fell and took *CC:DAMAGE*%d^x damage, %s%s\n",
player, dmg, player->heShe(), player->getStatusStr(dmg));
player->hp.decrease(dmg);
if(player->hp.getCur() <= 0) {
player->print("You fell to your death.\n");
player->hp.setCur(0);
::broadcast(player->getSock(), player->getParent(), "%M died from the fall.\n", player);
player->die(FALL);
return(false);
}
if(exit->flagIsSet(X_NEEDS_CLIMBING_GEAR)) {
player->print("You need climbing gear to go that way.\n");
return(false);
}
}
}
// don't follow someone to an incorrect bound room
// only if you aren't trying to walk yourself through
if( !leader &&
exit->flagIsSet(X_TO_BOUND_ROOM) &&
player->getGroup() &&
player->getGroup()->getLeader() &&
player->getGroup()->getLeader()->getAsPlayer() &&
player->bound != player->getGroup()->getLeader()->getAsPlayer()->bound
)
return(false);
return(true);
}
//*********************************************************************
// canMove
//*********************************************************************
// this runs the checks to see if we're even able to go anywhere
// failure should print a message
bool Move::canMove(Player* player, cmd* cmnd) {
player->clearFlag(P_AFK);
if(!player->ableToDoCommand())
return(false);
if(player->isStaff())
return(true);
if(player->isEffected("hold-person")) {
player->print("You are unable to move right now.\n");
return(false);
}
if( !player->getRoomParent()->flagIsSet(R_LIMBO) && (
player->getTotalBulk() > player->getMaxBulk() ||
player->getWeight() > player->maxWeight()
)
) {
player->print("You are carrying too many things to move!\n");
return(false);
}
if(!player->flagIsSet(P_STUNNED))
if(player->checkConfusion())
return(false);
if(Move::isSneaking(cmnd)) {
if(!player->knowsSkill("sneak")) {
player->print("You don't know how to sneak effectively.\n");
return(false);
}
if(!player->flagIsSet(P_HIDDEN) && !player->isEffected("mist")) {
player->print("You need to hide first.\n");
return(false);
}
if(player->getRoomParent()->isUnderwater()) {
player->print("You can't sneak here! You're too busy swimming!\n");
return(false);
}
if(player->inCombat()) {
player->print("How do you expect to sneak in the middle of battle?!\n");
return(false);
}
if(player->flagIsSet(P_SITTING)) {
player->print("You have to stand up first.\n");
return(false);
}
}
int chance=0, moves=0;
long t = time(0);
long s = LT(player, LT_SPELL);
long u = LT(player, LT_MOVED);
if(u - t > 0 && player->getRoomParent()->isUnderwater() && !player->flagIsSet(P_FREE_ACTION)) {
player->pleaseWait(u - t);
return(0);
}
if(Move::isSneaking(cmnd)) {
if(t < s) {
player->pleaseWait(s - t);
return(0);
}
if(!player->checkAttackTimer())
return(0);
} else {
chance = MAX(1, ((5+(player->dexterity.getCur()/10)*3) - player->getArmorWeight() + player->strength.getCur()));
if(player->getClass() == RANGER || player->getClass() == DRUID)
chance += 5;
if(player->isEffected("levitate"))
chance += 5;
if( player->getRoomParent()->flagIsSet(R_DIFFICULT_TO_MOVE) &&
!player->isEffected("fly") &&
!player->isEffected("mist") &&
!player->flagIsSet(P_FREE_ACTION)
)
moves = 1;
else
moves = 3;
// check for speed move
// TODO: Tweak this check
if( player->lasttime[LT_MOVED].ltime == t || (
player->getRoomParent()->flagIsSet(R_DIFFICULT_TO_MOVE) &&
!player->isEffected("fly") &&
!player->flagIsSet(P_FREE_ACTION) &&
!player->isEffected("mist") &&
mrand(1,100) > chance
))
{
if(player->lasttime[LT_MOVED].misc > moves) {
bool wait = false;
if(moves == 1) {
if(player->getRoomParent()->flagIsSet(R_EARTH_BONUS)) {
player->print("You are stuck in the mud!\n");
::broadcast(player->getSock(), player->getParent(), "%M got stuck in the mud!", player);
} else if(player->getRoomParent()->flagIsSet(R_AIR_BONUS)) {
player->print("The strong wind knocks you from your feet!\n");
::broadcast(player->getSock(), player->getParent(), "%M got blown over by the wind!", player);
} else if(player->getRoomParent()->flagIsSet(R_FIRE_BONUS)) {
player->print("You are knocked down by heat waves!\n");
::broadcast(player->getSock(), player->getParent(), "%M got knocked down by heat waves!", player);
} else if(player->getRoomParent()->flagIsSet(R_WATER_BONUS)) {
player->print("Strong water currents make you lose balance!\n");
::broadcast(player->getSock(), player->getParent(), "%M got knocked down by the current!", player);
} else if(player->getRoomParent()->flagIsSet(R_COLD_BONUS) || player->getRoomParent()->isWinter()) {
player->print("You are stuck in the ice and snow!\n");
::broadcast(player->getSock(), player->getParent(), "%M got stuck in the ice and snow!", player);
} else if(player->getRoomParent()->flagIsSet(R_ELEC_BONUS)) {
player->print("Strong magnetic forces knock you down!\n");
::broadcast(player->getSock(), player->getParent(), "%M got knocked down by magnetic forces!", player);
} else {
player->print("You are stuck!!\n");
::broadcast(player->getSock(), player->getParent(), "%M got stuck!", player);
}
wait = true;
} else {
// Speedwalking is fine as long as there are no aggros in the room; if there are
// there's a chance to trip them up
for(Monster* monster : player->getParent()->monsters) {
if(monster->willAggro(player) || monster->isEnemy(player)) {
*player << ColorOn << setf(CAP) << monster << " startles you.\n" << ColorOff;
wait = true;
break;
}
}
}
if(wait) {
player->pleaseWait(1);
return(false);
}
}
} else {
player->lasttime[LT_MOVED].ltime = t;
player->lasttime[LT_MOVED].misc = 1;
}
if(player->lasttime[LT_PLAYER_STUNNED].ltime+(player->lasttime[LT_PLAYER_STUNNED].interval) > t) {
player->pleaseWait((player->lasttime[LT_PLAYER_STUNNED].interval + 1) - t + player->lasttime[LT_PLAYER_STUNNED].ltime - 1);
return(0);
}
// fix walking bug here
if(player->getLTAttack()+3 > t) {
player->pleaseWait(3-t+player->getLTAttack());
return(false);
// and here
} else if(player->lasttime[LT_SPELL].ltime+3 > t) {
player->pleaseWait(3-t+player->lasttime[LT_SPELL].ltime);
return(false);
// this fixes walking steal
} else if(player->lasttime[LT_STEAL].ltime+3 > t) {
player->pleaseWait(3-t+player->lasttime[LT_STEAL].ltime);
return(false);
}
}
return(true);
}
//*********************************************************************
// getExit
//*********************************************************************
// this looks through the exits in the room and finds the one we want
// to go to
Exit *Move::getExit(Creature* player, cmd* cmnd) {
BaseRoom* room = player->getRoomParent();
Exit *exit=0;
if(!room)
return(NULL);
if(player->isPet())
player = player->getMaster();
if(Move::isOrdinal(cmnd)) {
for(Exit* ext : room->exits) {
if( ext->getName() == cmnd->str[1] &&
player->canSee(ext) &&
!(!player->isStaff() && ext->flagIsSet(X_DESCRIPTION_ONLY)))
{
exit = ext;
break;
}
}
} else {
if(cmnd->fullstr.length() < 3)
return(0);
exit = findExit(player, Move::formatFindExit(cmnd), cmnd->val[cmnd->num-1], room);
}
return(exit);
}
//*********************************************************************
// drunkenStumble
//*********************************************************************
// whether or not a person should stumble when drunk
bool drunkenStumble(const EffectInfo* effect) {
AlcoholState state = getAlcoholState(effect);
switch(state) {
case ALCOHOL_TIPSY:
// 33%
return(!mrand(0,2));
case ALCOHOL_DRUNK:
// 75%
return(!!mrand(0,3));
case ALCOHOL_INEBRIATED:
// 100%
return(true);
default:
// 0%
return(false);
}
}
//*********************************************************************
// getString
//*********************************************************************
// gives us the text of the movement string
bstring Move::getString(Creature* creature, bool ordinal, bstring exit) {
bstring str = "";
int num=0;
if(creature->getAsPlayer()) {
if(creature->isStaff())
str = "wanders to the";
else if(creature->getRoomParent()->isUnderwater())
str = "swam to the";
else if(creature->isEffected("fly"))
str = "flew to the";
else if(creature->isEffected("levitate") || creature->isEffected("mist"))
str = "floats to the";
else if(creature->isEffected("confusion") || drunkenStumble(creature->getEffect("drunkenness")))
str = "stumbles to the";
// Not ordinal, not up, not down, and not out
else if( !ordinal &&
exit != "up" &&
exit != "down" &&
exit != "out"
)
str = "went to the";
else
str = "leaves";
} else {
if( creature->movetype[0][0] ||
creature->movetype[1][0] ||
creature->movetype[2][0]
) {
do {
num = mrand(1, 3);
if(creature->movetype[num-1][0])
str = creature->movetype[num-1];
} while(!creature->movetype[num-1][0]);
} else if(creature->getRoomParent()->isUnderwater())
str = "swam to the";
else if(creature->isEffected("fly"))
str = "flew to the";
else if(creature->isEffected("levitate"))
str = "floats to the";
else
str = "wanders";
}
return(str);
}
//*********************************************************************
// checkFollowed
//*********************************************************************
// this sees if any monsters plan on following the people leaving the
// room, adding them to the list if they want to tag along for the ride
void Move::checkFollowed(Player* player, Exit* exit, BaseRoom* room, std::list<Creature*> *followers) {
Monster *target=0;
if(!room)
return;
MonsterSet::iterator mIt = room->monsters.begin();
while(mIt != room->monsters.end()) {
target = (*mIt++);
if(!target->flagIsSet(M_FOLLOW_ATTACKER) || target->flagIsSet(M_DM_FOLLOW))
continue;
if(!target->hasEnemy())
continue;
Creature* crt = target->getTarget(false);
if(!crt || crt != player) continue;
// Protect the newbie areas from aggro-dragging
if(target->flagIsSet(M_AGGRESSIVE) && exit->flagIsSet(X_PASSIVE_GUARD))
continue;
if(!target->canSee(player))
continue;
if(!target->canEnter(exit))
continue;
if(mrand(1,20) > 10 - (player->dexterity.getCur()/10) + target->dexterity.getCur()/10)
continue;
player->print("%M followed you.\n", target);
::broadcast(player->getSock(), room, "%M follows %N.", target, player);
// prevent players from continuously creating new monsters
if(target->flagIsSet(M_PERMENANT_MONSTER))
target->diePermCrt();
target->clearFlag(M_PERMENANT_MONSTER);
gServer->addActive(target);
target->deleteFromRoom();
followers->push_back(target);
}
}
//*********************************************************************
// finish
//*********************************************************************
// this finishes up all the work of adding people to the room
void Move::finish(Creature* creature, BaseRoom* room, bool self, std::list<Creature*> *followers) {
Player *player = creature->getAsPlayer();
Monster *monster = creature->getAsMonster();
if(player)
player->addToRoom(room);
else
monster->addToRoom(room);
if(followers && !followers->empty()) {
while(!followers->empty()) {
Move::finish(followers->front(), room, false, 0);
followers->pop_front();
}
followers->clear();
}
if(player && room->isUniqueRoom())
player->checkTraps(room->getAsUniqueRoom(), self);
}
//*********************************************************************
// getRoom
//*********************************************************************
// loads up the room. handles moving into the room through an exit
// (walk/flee), looking into the room (scout/yell), and appearing in
// the room (teleport)
// creature is allowed to be null if we're just trying to load the room
// out of the exit.
bool Move::getRoom(Creature* creature, const Exit* exit, BaseRoom **newRoom, bool justLooking, MapMarker* teleport, bool recycle) {
Player* player = 0;
if(creature)
player = creature->getAsPlayer();
Location l;
// pets can go where players can go
if(!player && creature && creature->isPet())
player = creature->getPlayerMaster();
// find out where the exit actually points
if(creature && exit && (
exit->flagIsSet(X_TO_BOUND_ROOM) ||
exit->flagIsSet(X_TO_PREVIOUS) ||
exit->flagIsSet(X_TO_STORAGE_ROOM)
) )
{
// flags can cause us to completely ignore where the exit points
if(exit->flagIsSet(X_TO_PREVIOUS))
l = creature->previousRoom;
else if(player && exit->flagIsSet(X_TO_STORAGE_ROOM)) {
l.room = gConfig->getSingleProperty(player, PROP_STORAGE);
if(!l.room.id) {
player->print("You must first purchase a storage room to go to this exit.\n");
return(0);
} else if(l.room.id == -1) {
player->print("You may not enter that room when you are affiliated with more than one\n");
player->print("storage room. Type \"property\" for instructions on how to remove yourself\n");
player->print("as partial owner from other storage rooms if you no longer wish to be\n");
player->print("affiliated with them.\n");
return(0);
}
} else if(player && exit->flagIsSet(X_TO_BOUND_ROOM))
l = player->getBound();
else
l.room = creature->currentLocation.room;
} else if(!exit || teleport) {
// if we're teleporting
l.mapmarker = *teleport;
} else if(exit->target.mapmarker.getArea()) {
l.mapmarker = exit->target.mapmarker;
} else {
l.room = exit->target.room;
}
if(l.mapmarker.getArea()) {
Area *area = gConfig->getArea(l.mapmarker.getArea());
if(!area) {
if(!teleport && creature) {
creature->print("Off the map in that direction.\n");
if(creature->isStaff())
creature->printColor("^eMove::getRoom failed on an area room.\n");
}
return(false);
}
// if we're in a unique room, don't activate any unique-room triggers on
// the overland unless we're teleporting
if(teleport || !creature || !creature->inUniqueRoom())
l.room = area->getUnique(&l.mapmarker);
if(!l.room.id) {
// don't recycle if we are teleporting or leaving
// a unique room
if( teleport ||
(creature && creature->inAreaRoom() && creature->getAreaRoomParent()->unique.id)
)
recycle = false;
// if we're only looking, don't use the function that will create a new room
// if one doesnt already exit
if(justLooking)
(*newRoom) = area->getRoom(&l.mapmarker);
else
(*newRoom) = area->loadRoom(creature, &l.mapmarker, recycle);
if(!*newRoom)
return(false);
// getUnique gets called again later, so skip the decrement process here
// or the compass will use 2 shots
if(teleport || !creature || !creature->inUniqueRoom())
l.room = (*newRoom)->getAsAreaRoom()->getUnique(creature, true);
}
}
if(l.room.id) {
UniqueRoom* uRoom = 0;
if( (creature && creature->inUniqueRoom() && l.room == creature->currentLocation.room) ||
!loadRoom(l.room, &uRoom) )
{
if(!teleport && creature)
creature->print("Off map in that direction.\n");
return(false);
}
*newRoom = uRoom;
if(creature) {
// sending true to this function tells the player why
// they can't cant enter the room: if teleporting send false
if(!creature->canEnter(uRoom, !teleport)) {
(*newRoom)=0;
return(false);
}
// the rest of these rules are for players only
// exclude pets
if(player && !creature->isPet()) {
for(Monster*pet : player->pets) {
if(pet && !pet->canEnter(uRoom, !teleport)) {
if(!teleport)
player->print("%M won't follow you there.\n", pet);
(*newRoom)=0;
return(false);
}
}
// if they're leaving limbo
if(!justLooking && player->getLocation() == player->getLimboRoom())
player->clearFlag(P_KILLED_BY_MOB);
if(player->inUniqueRoom() && player->getUniqueRoomParent()->info.isArea("stor"))
gConfig->saveStorage(player->getUniqueRoomParent());
}
}
}
BaseRoom* room = (*newRoom);
// when entering the room, we may have to unmist them
if( !justLooking &&
player &&
player->isEffected("mist") &&
!player->isStaff() &&
(room->flagIsSet(R_DISPERSE_MIST) || room->flagIsSet(R_ETHEREAL_PLANE) || room->isUnderwater()) )
{
if(room->isUnderwater())
player->print("Water currents disperse your mist.\n");
else {
player->print("Swirling vapors disperse your mist.\n");
player->stun(mrand(5,8));
}
player->unmist();
}
return(true);
}
//*********************************************************************
// start
//*********************************************************************
// this function starts everyone moving - it finds the room, finds
// the exit, sees if they can enter, then removes them from the current
// room. the final step is completed in Move::finish
BaseRoom* Move::start(Creature* creature, cmd* cmnd, Exit **gExit, bool leader, std::list<Creature*> *followers, int* numPeople, bool& roomPurged) {
BaseRoom *oldRoom = creature->getRoomParent(), *newRoom=0;
// UniqueRoom *uRoom=0;
// AreaRoom* aRoom=0;
Exit *exit=0;
Player *player = creature->getAsPlayer();
Monster* monster = creature->getAsMonster();
bool sneaking=false, wasSneaking=false, mem=false;
if(player) {
if(!Move::canMove(player, cmnd))
return(NULL);
// only players sneak
sneaking = wasSneaking = Move::isSneaking(cmnd);
if(sneaking && player->isStaff() && !player->flagIsSet(P_HIDDEN))
player->setFlag(P_HIDDEN);
}
exit = Move::getExit(creature, cmnd);
if(!exit) {
creature->print("You don't see that exit.\n");
if(monster && monster->getMaster())
monster->getMaster()->print("Your pet doesn't see that exit.\n");
return(NULL);
}
if(player && !Move::canEnter(player, exit, leader))
return(NULL);
if(!Move::getRoom(creature, exit, &newRoom))
return(NULL);
// Rangers and F/T can't sneak with heavy armor on!
if(sneaking && player && player->checkHeavyRestrict("sneak"))
return(NULL);
if(newRoom->isAreaRoom()) {
mem = newRoom->getAsAreaRoom()->getStayInMemory();
newRoom->getAsAreaRoom()->setStayInMemory(true);
}
// we have to run a manual isFull check here because nobody is
// in the room yet
(*numPeople)++;
if(!monster && !player->isStaff()) {
if(newRoom->maxCapacity() && (newRoom->countVisPly() + *numPeople) > newRoom->maxCapacity()) {
creature->print("That room is full.\n");
// reset stayInMemory
if(newRoom->isAreaRoom())
newRoom->getAsAreaRoom()->setStayInMemory(mem);
return(NULL);
}
}
// were they killed by exit effect damage?
if(exit->doEffectDamage(creature))
return(NULL);
// save a copy of the exit for the track function
if(leader) {
*gExit = exit;
}
if(player) {
Move::update(player);
sneaking = Move::sneak(player, sneaking);
}
Move::broadMove(creature, exit, cmnd, sneaking);
// Was the area room purged? If so we have no monsters to follow is in it so
// no need to check that later
int del = creature->deleteFromRoom(false);
if(del & DEL_ROOM_DESTROYED) {
// Room was purged and oldRoom is no longer valid
roomPurged = true;
exit = 0;
oldRoom = 0;
}
// the leader doesn't use the list
if(!leader)
followers->push_back(creature);
// sneaking means only your pet will follow you, even if you fail
// also do this for destroyed portals
bool portal = false;
if(!exit)
portal = true;
if(!portal && player && exit->flagIsSet(X_PORTAL))
portal = Move::usePortal(player, oldRoom, exit);
if(exit)
exit->checkReLock(creature, sneaking);
if(player && (wasSneaking || portal)) {
// TODO: Make pet unhide during failed sneak
for(Monster*pet : player->pets) {
if(pet && oldRoom == pet->getRoomParent())
Move::start(pet, cmnd, 0, 0, followers, numPeople, roomPurged);
}
// reset stayInMemory
if(newRoom->isAreaRoom())
newRoom->getAsAreaRoom()->setStayInMemory(mem);
if(portal && oldRoom && exit)
Move::deletePortal(oldRoom, exit, leader ? player : player->getGroupLeader(), followers);
// portal owners close once they exit the room
if(player && player->flagIsSet(P_PORTAL))
Move::deletePortal(oldRoom, player->getCName(), leader ? player : player->getGroupLeader());
return(newRoom);
}
Group* group = creature->getGroup();
if(group && creature->getGroupStatus() == GROUP_LEADER && !creature->pFlagIsSet(P_DM_INVIS)) {
for(Creature* follower : group->members) {
if(follower == creature || follower->getGroupStatus() < GROUP_MEMBER)
continue;
if(oldRoom == follower->getRoomParent())
Move::start(follower, cmnd, 0, 0, followers, numPeople, roomPurged);
if(roomPurged)
oldRoom = NULL;
}
}
if(leader && (!group || creature->getGroupStatus() != GROUP_LEADER)) {
for(Monster* pet : creature->pets) {
if(oldRoom == pet->getRoomParent())
Move::start(pet, cmnd, 0, 0, followers, numPeople, roomPurged);
if(roomPurged)
oldRoom = NULL;
}
}
if(player && !sneaking && !roomPurged && oldRoom)
Move::checkFollowed(player, exit, oldRoom, followers);
// reset stayInMemory
if(newRoom->isAreaRoom())
newRoom->getAsAreaRoom()->setStayInMemory(mem);
// portal owners close once they exit the room, but this means their group can follow
if(player && player->flagIsSet(P_PORTAL))
Move::deletePortal(oldRoom, player->getCName(), leader ? player : player->getGroupLeader(), followers);
return(newRoom);
}
//*********************************************************************
// cmdGo
//*********************************************************************
// the base go command - it is called by the leader and causes everyone
// following to run the Move::start and Move::finish functions
int cmdGo(Player* player, cmd* cmnd) {
MapMarker *oldMarker=0;
UniqueRoom *oldRoom = player->getUniqueRoomParent();
Exit* exit;
int numPeople=0;
if(!player->getRoomParent()) {
player->print("You must be in a room to go anywhere!\n");
return(0);
}
if(cmnd->num < 2) {
player->print("Go where?\n");
return(0);
}
// keep track of this room
AreaRoom* aRoom = player->getAreaRoomParent();
std::list<Creature*> followers;
if(aRoom) {
oldMarker = new MapMarker;
*oldMarker = aRoom->mapmarker;
}
bool roomPurged = false;
// this removes everyone from the room
BaseRoom* newRoom = 0;
if(!(newRoom = Move::start(player, cmnd, &exit, true, &followers, &numPeople, roomPurged)))
return(0);
if(!roomPurged)
Move::track(oldRoom, oldMarker, exit, player, &followers);
if(oldMarker)
delete oldMarker;
// for display reasons, we only need to kill objects in
// the room they're entering
newRoom->killMortalObjectsOnFloor();
// loadRoom is telling us the room the player used to be in is
// a candidate for recycling
if(newRoom->isAreaRoom() && aRoom == newRoom)
newRoom = Move::recycle(aRoom, exit);
// this adds everyone to the room
Move::finish(player, newRoom, true, &followers);
// we killed objects already, we can skip those this time around
player->getRoomParent()->killMortalObjects(false);
return(0);
}
//*********************************************************************
// cmdMove
//*********************************************************************
// This function attempts to move a player in one of the
// cardinal directions (n,s,e,w,u,d).
int cmdMove(Player* player, cmd* cmnd) {
bstring dir = "", str = "";
player->clearFlag(P_AFK);
// find the ordinal direction
str = cmnd->str[0];
if(!player->ableToDoCommand())
return(0);
if(str == "sw" || !strncmp(str.c_str(), "southw", 6))
dir = "southwest";
else if(str == "nw" || !strncmp(str.c_str(), "northw", 6))
dir = "northwest";
else if(str == "se" || !strncmp(str.c_str(), "southe", 6))
dir = "southeast";
else if(str == "ne" || !strncmp(str.c_str(), "northe", 6))
dir = "northeast";
/* else if(!strcmp(str, "aft")
strcpy(tempstr, "aft");
else if(!strcmp(str, "fore")
strcpy(tempstr, "fore");
else if(!strcmp(str, "starb")
strcpy(tempstr, "starboard");
else if(!strcmp(str, "port")
strcpy(tempstr, "port"); */
else {
switch(str[0]) {
case 'n':
dir = "north";
break;
case 's':
dir = "south";
break;
case 'e':
dir = "east";
break;
case 'w':
dir = "west";
break;
case 'u':
dir = "up";
break;
case 'd':
dir = "down";
break;
case 'o':
case 'l':
dir = "out";
break;
}
}
// fake a "go north" type thing
cmnd->num = 2;
strcpy(cmnd->str[1], dir.c_str());
cmnd->val[1] = 1;
return(cmdGo(player, cmnd));
}
//*********************************************************************
// cmdFlee
//*********************************************************************
// wrapper which allows a player to flee
int cmdFlee(Player* player, cmd* cmnd) {
player->flee();
return(0);
}
//*********************************************************************
// cmdOpen
//*********************************************************************
// This function allows a player to open a door if it is not already
// open and if it is not locked.
int cmdOpen(Player* player, cmd* cmnd) {
Exit *exit=0;
player->clearFlag(P_AFK);
if(!player->ableToDoCommand())
return(0);
if(player->flagIsSet(P_SITTING)) {
player->print("You have to stand up first.\n");
return(0);
}
if(cmnd->num < 2) {
player->print("Open what?\n");
return(0);
}
exit = findExit(player, cmnd);
if(!exit) {
player->print("You don't see that openable exit.\n");
return(0);
}
if(!exit->flagIsSet(X_CLOSED) && !exit->flagIsSet(X_CLOSABLE)) {
player->print("You don't see that openable exit.\n");
return(0);
}
if(exit->flagIsSet(X_TOLL_TO_PASS) && player->getRoomParent()->getTollkeeper() && !player->isCt()) {
player->printColor("You must pay a toll of %ld gold coins to go through the %s^x.\n", tollcost(player, exit, 0), exit->getCName());
return(0);
}
const Monster* guard = player->getRoomParent()->getGuardingExit(exit, player);
if(guard && !player->checkStaff("%M %s.\n", guard, guard->flagIsSet(M_FACTION_NO_GUARD) ? "doesn't like you enough to let you open it" : "won't let you open it"))
return(0);
if(exit->flagIsSet(X_LOCKED) && !player->checkStaff("It's locked.\n"))
return(0);
exit->clearFlag(X_LOCKED);
if(!exit->flagIsSet(X_CLOSED)) {
player->print("It's already open.\n");
return(0);
}
player->unhide();
if(exit->isWall("wall-of-force")) {
player->printColor("The %s^x is blocked by a wall of force.\n", exit->getCName());
return(0);
}
// were they killed by exit effect damage?
if(exit->doEffectDamage(player))
return(0);
exit->clearFlag(X_CLOSED);
exit->ltime.ltime = time(0);
player->printColor("You open the %s^x.\n", exit->getCName());
broadcast(player->getSock(), player->getParent(), "%M opens the %s^x.", player, exit->getCName());
Hooks::run(player, "openExit", exit, "openByCreature");
if(exit->getOpen() != "") {
if(exit->flagIsSet(X_ONOPEN_PLAYER)) {
player->print("%s.\n", exit->getOpen().c_str());
} else {
broadcast(0, player->getRoomParent(), exit->getOpen().c_str());
}
}
return(0);
}
//*********************************************************************
// cmdClose
//*********************************************************************
// This function allows a player to close a door, if the door is not
// already closed, and if indeed it is a door.
int cmdClose(Player* player, cmd* cmnd) {
Exit *exit=0;
player->clearFlag(P_AFK);
if(!player->ableToDoCommand())
return(0);
if(cmnd->num < 2) {
player->print("Close what?\n");
return(0);
}
if(player->flagIsSet(P_SITTING)) {
player->print("You have to stand up first.\n");
return(0);
}
exit = findExit(player, cmnd);
if(!exit) {
player->print("You don't see that closeable exit.\n");
return(0);
}
if(!exit->flagIsSet(X_CLOSABLE)) {
player->print("You don't see that closeable exit.\n");
return(0);
}
if(exit->flagIsSet(X_CLOSED)) {
player->print("It's already closed.\n");
return(0);
}
for(Player* ply : player->getRoomParent()->players) {
if(ply->inCombat()) {
player->print("You cannot do that right now.\n");
return(0);
}
}
if(exit->isWall("wall-of-force")) {
player->printColor("The %s^x is blocked by a wall of force.\n", exit->getCName());
return(0);
}
// were they killed by exit effect damage?
if(exit->doEffectDamage(player))
return(0);
player->unhide();
exit->setFlag(X_CLOSED);
Hooks::run(player, "closeExit", exit, "closeByCreature");
player->printColor("You close the %s^x.\n", exit->getCName());
broadcast(player->getSock(), player->getParent(), "%M closes the %s^x.", player, exit->getCName());
return(0);
}
//*********************************************************************
// cmdUnlock
//*********************************************************************
// This function allows a player to unlock a door if they have the correct
// key, and it is locked.
unsigned short Object::getKey() const { return(keyVal); }
void Object::setKey(unsigned short k) { keyVal = k; }
int cmdUnlock(Player* player, cmd* cmnd) {
Object *object=0;
Exit *exit=0;
player->clearFlag(P_AFK);
if(!player->ableToDoCommand())
return(0);
if(player->flagIsSet(P_SITTING)) {
player->print("You have to stand up first.\n");
return(0);
}
if(cmnd->num < 2) {
player->print("Unlock what?\n");
return(0);
}
exit = findExit(player, cmnd);
if(!exit) {
player->print("Unlock what?\n");
return(0);
}
const Monster* guard = player->getRoomParent()->getGuardingExit(exit, player);
if(guard && !player->checkStaff("%M %s.\n", guard, guard->flagIsSet(M_FACTION_NO_GUARD) ? "doesn't like you enough to let you unlock it" : "won't let you unlock it"))
return(0);
if(!exit->flagIsSet(X_LOCKED) && !player->checkStaff("It's not locked.\n"))
return(0);
if(player->inJail()) {
if(player->getUniqueRoomParent()->countVisPly() > 1) {
player->print("You can't do that! Someone else might get out!\n");
return(0);
}
}
if(exit->flagIsSet(X_TO_STORAGE_ROOM)) {
CatRef sr = gConfig->getSingleProperty(player, PROP_STORAGE);
if(!sr.id) {
player->print("You must first purchase a storage room to unlock this exit.\n");
return(0);
}
}
if(exit->flagIsSet(X_WATCHER_UNLOCK) && (player->isWatcher() || player->isCt())) {
player->unhide();
exit->clearFlag(X_LOCKED);
exit->ltime.ltime = time(0);
player->print("Click.\n");
broadcast(player->getSock(), player->getParent(), "%M unlocks the %s^x.", player, exit->getCName());
broadcast(isWatcher, "^C%s unlocked the %s^x exit in room %s.\n",
player->getCName(), exit->getCName(), player->getRoomParent()->fullName().c_str());
logn("log.watchers", "%s unlocked the %s exit in room %s.\n",
player->getCName(), exit->getCName(), player->getRoomParent()->fullName().c_str());
return(0);
}
if(cmnd->num < 3) {
player->print("Unlock it with what?\n");
return(0);
}
object = player->findObject(player->getAsConstPlayer(), cmnd, 2);
if(!object) {
player->print("You don't have that.\n");
return(0);
}
if(object->getType() == WAND && object->getMagicpower() == S_KNOCK)
return(cmdUseWand(player, cmnd));
if(object->getType() != KEY) {
player->print("That's not a key.\n");
return(0);
}
if(object->getShotsCur() < 1) {
player->printColor("%O is broken.\n", object);
return(0);
}
if(exit->isWall("wall-of-force")) {
player->printColor("The %s^x is blocked by a wall of force.\n", exit->getCName());
return(0);
}
// were they killed by exit effect damage?
if(exit->doEffectDamage(player))
return(0);
if(!object->isKey(player->getUniqueRoomParent(), exit)) {
player->print("Wrong key.\n");
return(0);
}
player->unhide();
exit->clearFlag(X_LOCKED);
exit->ltime.ltime = time(0);
object->decShotsCur();
if(object->use_output[0])
player->print("%s\n", object->use_output);
else
player->print("Click.\n");
broadcast(player->getSock(), player->getParent(), "%M unlocks the %s^x.", player, exit->getCName());
Hooks::run(player, "unlockExit", exit, "unlockByCreature");
if(object->getShotsCur() < 1 && Unique::isUnique(object)) {
player->delObj(object, true);
delete object;
}
return(0);
}
//*********************************************************************
// cmdLock
//*********************************************************************
// This function allows a player to lock a door with the correct key.
int cmdLock(Player* player, cmd* cmnd) {
Object *object=0;
Exit *exit=0;
player->clearFlag(P_AFK);
if(!player->ableToDoCommand())
return(0);
if(cmnd->num < 2) {
player->print("Lock what?\n");
return(0);
}
if(player->flagIsSet(P_SITTING)) {
player->print("You have to stand up first.\n");
return(0);
}
exit = findExit(player, cmnd);
if(!exit) {
player->print("Lock what?\n");
return(0);
}
const Monster* guard = player->getRoomParent()->getGuardingExit(exit, player);
if(guard && !player->checkStaff("%M %s.\n", guard, guard->flagIsSet(M_FACTION_NO_GUARD) ? "doesn't like you enough to let you lock it" : "won't let you lock it"))
return(0);
if(exit->flagIsSet(X_LOCKED)) {
player->print("It's already locked.\n");
return(0);
}
if(cmnd->num < 3) {
player->print("Lock it with what?\n");
return(0);
}
object = player->findObject(player, cmnd, 2);
if(!object) {
player->print("You don't have that.\n");
return(0);
}
if(object->getType() != KEY) {
player->printColor("%O is not a key.\n", object);
return(0);
}
if(!exit->flagIsSet(X_LOCKABLE)) {
player->print("You can't lock it.\n");
return(0);
}
if(!exit->flagIsSet(X_CLOSED)) {
player->print("You have to close it first.\n");
return(0);
}
if(object->getShotsCur() < 1) {
player->printColor("%O is broken.\n", object);
return(0);
}
if(exit->isWall("wall-of-force")) {
player->printColor("The %s^x is blocked by a wall of force.\n", exit->getCName());
return(0);
}
// were they killed by exit effect damage?
if(exit->doEffectDamage(player))
return(0);
if(!object->isKey(player->getUniqueRoomParent(), exit)) {
player->print("Wrong key.\n");
return(0);
}
player->unhide();
exit->setFlag(X_LOCKED);
player->print("Click.\n");
broadcast(player->getSock(), player->getParent(), "%M locks the %s^x.", player, exit->getCName());
Hooks::run(player, "lockExit", exit, "lockByCreature");
if(object->getShotsCur() < 1 && Unique::isUnique(object)) {
player->delObj(object, true);
delete object;
}
return(0);
}
//*********************************************************************
// getCatRef
//*********************************************************************
// we're given a string and expected to find a) the area and
// b) the room id.
void getCatRef(bstring str, CatRef* cr, const Creature* target) {
cr->setDefault(target);
// chop off the end!
bstring::size_type pos = str.Find(" ");
if(pos != bstring::npos)
str = str.substr(0, pos);
pos = str.Find(".");
if(pos == bstring::npos)
pos = str.Find(":");
if(pos != bstring::npos) {
cr->setArea(str.substr(0, pos));
str.erase(0, pos+1);
}
if(str != "" && isdigit(str.getAt(0)))
cr->id = atoi(str.c_str());
else {
if(str != "")
cr->setArea(str);
cr->id = -1;
}
}
//*********************************************************************
// getDestination
//*********************************************************************
// we're given a string and expected to find a) a MapMarker or
// b) a CatRef. the string will start at the point we wish
// to search, but may contain extra crap at the end
void getDestination(bstring str, Location* l, const Creature* target) {
l->mapmarker.reset();
l->room.id = 0;
getDestination(str, &l->mapmarker, &l->room, target);
}
void getDestination(bstring str, MapMarker* mapmarker, CatRef* cr, const Creature* target) {
bstring::size_type pos=0;
char *txt;
int x=0, y=0, z=0;
// chop off the end!
pos = str.find(" ", 0);
if(pos != bstring::npos)
str = str.substr(0, pos);
// 1.-10.7
txt = (char*)strstr(str.c_str(), ".");
if(txt && isdigit(str.getAt(0))) {
txt++;
x = atoi(txt);
txt = strstr(txt, ".");
if(txt) {
txt++;
y = atoi(txt);
txt = strstr(txt, ".");
if(txt) {
txt++;
z = atoi(txt);
}
}
mapmarker->set(atoi(str.c_str()), x, y, z);
} else
getCatRef(str, cr, target);
}