/*
* creature.cpp
* Routines that act on creatures
* ____ _
* | _ \ ___ __ _| |_ __ ___ ___
* | |_) / _ \/ _` | | '_ ` _ \/ __|
* | _ < __/ (_| | | | | | | \__ \
* |_| \_\___|\__,_|_|_| |_| |_|___/
*
* 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-2009 Jason Mitchell, Randi Mitchell
* Contributions by Tim Callahan, Jonathan Hseu
* Based on Mordor (C) Brooke Paul, Brett J. Vickers, John P. Freeman
*
*/
#include <fcntl.h>
#include "mud.h"
#include "commands.h"
#include "effects.h"
#include "calendar.h"
//#include <time.h>
#include "move.h"
const short Creature::OFFGUARD_REMOVE=0;
const short Creature::OFFGUARD_NOREMOVE=1;
const short Creature::OFFGUARD_NOPRINT=2;
//*********************************************************************
// sameRoom
//*********************************************************************
bool Creature::inSameRoom(const Creature *b) const {
return(getRoom() == b->getRoom());
}
//*********************************************************************
// find_exact_crt
//*********************************************************************
// This function will attempt to locate a given creature within a given
// list. The first parameter contains a pointer to the creature doing
// the search, the second contains a tag pointer to the first tag in
// the list. The third contains the match string, and the fourth
// contains the number of times to match the string.
Creature *find_exact_crt(Creature* player, ctag *first_ct, char *str, int val) {
Creature* target=0;
ctag *cp=0;
int match=0;
ASSERTLOG( player );
ASSERTLOG( str );
if(!player || !str || !first_ct)
return(0);
cp = first_ct;
while(cp) {
target = cp->crt;
cp = cp->next_tag;
if(!target)
continue;
if(!player->canSee(target))
continue;
if(!strcmp(target->name, str)) {
match++;
if(match == val)
return(target);
}
}
return(0);
}
//*********************************************************************
// getFirstAggro
//*********************************************************************
Creature *getFirstAggro(Creature* creature, Creature* player) {
ctag *cp=0;
Creature *foundCrt=0;
if(creature->isPlayer())
return(creature);
cp = creature->getRoom()->first_mon;
while(cp) {
if(strcmp(cp->crt->name, creature->name)) {
cp = cp->next_tag;
continue;
}
if(cp->crt->getMonster()->isEnmCrt(player->name)) {
foundCrt = cp->crt;
break;
}
cp = cp->next_tag;
}
if(foundCrt)
return(foundCrt);
else
return(creature);
}
//*********************************************************************
// addEnmName
//*********************************************************************
// This function adds a new enemy's name to the front a creature's enemy
// list. The closer to the front of the list, the higher the likelihood
// that you will be attacked.
int Monster::addEnmName(const char *enemy) {
etag *ep=0;
int n = -1;
ep = first_enm;
while(ep) {
if(!strcmp(ep->enemy, enemy)) {
n = ep->damage;
// strncpy(owner, ep->owner, 79);
delEnmCrt(enemy);
break;
}
ep = ep->next_tag;
}
ep = new etag;
if(!ep)
merror("add_enm_name", FATAL);
strncpy(ep->enemy, enemy, 79);
ep->next_tag = 0;
ep->damage = (n > -1) ? n : 0;
if(!first_enm) {
first_enm = ep;
return (n);
}
ep->next_tag = first_enm;
first_enm = ep;
if(n < 0)
NUMHITS = 0;
return (n);
}
//*********************************************************************
// addEnmCrt
//*********************************************************************
// This function adds a new enemy's name to the front a creature's enemy
// list. The closer to the front of the list, the higher the likelihood
// that you will be attacked.
int Monster::addEnmCrt(Creature *target) {
etag *ep=0;
int n = -1;
ASSERTLOG( target );
ep = first_enm;
if(target->isPlayer()) {
// take pity on these people
if(target->isEffected("petrification") || target->isUnconscious())
return(0);
// pets should not attack master
if(isPet() && this == target->getPlayer()->getPet())
return(0);
}
while(ep) {
if(!strcmp(ep->enemy, target->name)) {
n = ep->damage;
// strncpy(owner, ep->owner, 79);
delEnmCrt(target->name);
break;
}
ep = ep->next_tag;
}
ep = new etag;
if(!ep)
merror("add_enm_crt", FATAL);
strncpy(ep->enemy, target->name, 79);
ep->next_tag = 0;
ep->damage = (n > -1) ? n : 0;
if(target->isMonster() && target->isPet())
strncpy(ep->owner, target->following->name, 79);
else
ep->owner[0] = 0;
if(!first_enm) {
first_enm = ep;
return(n);
}
ep->next_tag = first_enm;
first_enm = ep;
if(n < 0)
NUMHITS = 0;
return(n);
}
//*********************************************************************
// delEnmCrt
//*********************************************************************
// This function removes an enemy's name from their enemy list.
int Monster::delEnmCrt(const char *enemy, const char *owner) {
etag *ep=0, *prev=0;
int dmg=0;
ASSERTLOG( enemy );
ep = first_enm;
if(!ep)
return(-1);
if(!strcmp(ep->enemy, enemy)) {
if(!owner || !strcmp(ep->owner, owner)) {
first_enm = ep->next_tag;
dmg = ep->damage;
delete ep;
return(dmg);
}
}
while(ep) {
if(!strcmp(ep->enemy, enemy)) {
if(!owner || !strcmp(ep->owner, owner)) {
prev->next_tag = ep->next_tag;
dmg = ep->damage;
delete ep;
return(dmg);
}
}
prev = ep;
ep = ep->next_tag;
}
return(-1);
}
//*********************************************************************
// endEnmCrt
//*********************************************************************
// This function moves an enemy within a monster's enemy list to the
// very end of the list.
void Monster::endEnmCrt(const char *enemy) {
etag *ep=0, *move=0, *prev=0;
ASSERTLOG( enemy );
move = 0;
move = new etag;
if(!move)
merror("end_enm_crt", FATAL);
strcpy(move->enemy, enemy);
ep = first_enm;
if(ep) {
if(!strcmp(ep->enemy, enemy)) {
first_enm = ep->next_tag;
move->damage = ep->damage;
strcpy(move->owner, ep->owner);
delete ep;
ep = NULL;
}
while(ep) {
if(!strcmp(ep->enemy, enemy)) {
prev->next_tag = ep->next_tag;
move->damage = ep->damage;
strcpy(move->owner, ep->owner);
delete ep;
ep = NULL;
break;
}
prev = ep;
ep = ep->next_tag;
}
}
move->next_tag = 0;
ep = first_enm;
if(!ep) {
first_enm = move;
return;
}
while(ep->next_tag)
ep = ep->next_tag;
ep->next_tag = move;
}
//*********************************************************************
// addEnmDmg
//*********************************************************************
// This function adds the amount of damage indicated by the third
// argument to the enemy total for the creature pointed to by the
// second argument. The first argument contains the name of the player
// who hit the creature.
void Monster::addEnmDmg(const Creature *enemy, int dmg) {
ASSERTLOG( enemy );
etag *ep = first_enm;
while(ep) {
if(!strcmp(enemy->name, ep->enemy)) {
// If pet, make sure owners match
if( (enemy->isPet() && !strcmp(enemy->following->name, ep->owner)) || !enemy->isPet() )
ep->damage += dmg;
}
ep = ep->next_tag;
}
}
//*********************************************************************
// isEnmCrt
//*********************************************************************
// This function returns true if the name passed in the first parameter
// is in the enemy list of the creature pointed to by the second.
bool Monster::isEnmCrt(const char *enemy) const {
etag *ep=0;
ASSERTLOG( enemy );
ep = first_enm;
while(ep) {
if(!strcmp(ep->enemy, enemy))
return(true);
ep = ep->next_tag;
}
return(false);
}
//*********************************************************************
// same_enemies
//*********************************************************************
int same_enemies(Creature* creature, Creature *victim) {
etag *cep=0, *vep=0;
char enm1[1024], enm2[1024];
strcpy(enm1, "");
strcpy(enm2, "");
cep = creature->first_enm;
while(cep) {
if(!cep->enemy) {
cep = cep->next_tag;
continue;
}
strcat(cep->enemy, enm1);
cep = cep->next_tag;
}
vep = victim->first_enm;
while(vep) {
if(!vep->enemy) {
vep = vep->next_tag;
continue;
}
strcat(vep->enemy, enm2);
vep = vep->next_tag;
}
if(!strcmp(enm1, enm2))
return(1);
else
return(0);
}
//*********************************************************************
// nearEnemy
//*********************************************************************
bool Monster::nearEnemy() const {
ctag *cp=0;
if(nearEnmPly())
return(true);
cp = getRoom()->first_mon;
while(cp) {
if(isEnmCrt(cp->crt->name) && !cp->crt->flagIsSet(M_FAST_WANDER))
return(true);
cp = cp->next_tag;
}
return(false);
}
//*********************************************************************
// nearEnmPly
//*********************************************************************
bool Monster::nearEnmPly() const {
ctag *cp = getRoom()->first_ply;
while(cp) {
if(isEnmCrt(cp->crt->name) && !cp->crt->flagIsSet(P_DM_INVIS))
return(true);
cp = cp->next_tag;
}
return(false);
}
//*********************************************************************
// nearEnemy
//*********************************************************************
// Searches for a near enemy, excluding the current player being attacked.
bool Monster::nearEnemy(const Creature* target) const {
ctag *cp=0;
BaseRoom* room = getRoom();
cp = room->first_ply;
while(cp) {
if(isEnmCrt(cp->crt->name) && !cp->crt->flagIsSet(P_DM_INVIS)
&& strcmp(target->name, cp->crt->name))
return(true);
cp = cp->next_tag;
}
cp = room->first_mon;
while(cp) {
if(isEnmCrt(cp->crt->name) && !cp->crt->flagIsSet(M_FAST_WANDER))
return(true);
cp = cp->next_tag;
}
return(false);
}
//*********************************************************************
// findCloseEnm
//*********************************************************************
Creature* Monster::findCloseEnm() {
BaseRoom* room=0;
Creature *attacker=0;
etag *ep=0;
char *enemy=0, *vacant_enemy=0, *gone_enemy=0;
room = getRoom();
ep = first_enm;
while(ep) {
while(1) {
enemy = ep->enemy;
if(!enemy)
ep = ep->next_tag;
if(!ep)
return NULL;
if(enemy)
break;
}
attacker = room->findCreature(this, enemy, 1);
if(attacker) {
if(vacant_enemy)
endEnmCrt(vacant_enemy);
if(gone_enemy)
endEnmCrt(gone_enemy);
// usta be del_enm
return(attacker);
}
if(!gServer->findPlayer(enemy))
gone_enemy = enemy;
else
vacant_enemy = enemy;
ep = ep->next_tag;
}
return(0);
}
//*********************************************************************
// tempPerm
//*********************************************************************
void Object::tempPerm() {
otag *op=0;
setFlag(O_PERM_INV_ITEM);
setFlag(O_TEMP_PERM);
op = first_obj;
while(op) {
op->obj->tempPerm();
op = op->next_tag;
}
}
//*********************************************************************
// diePermCrt
//*********************************************************************
// This function is called whenever a permanent monster is killed. The
// room the monster was in has its permanent monster list checked to
// if the monster was loaded from that room. If it was, then the last-
// time field for that permanent monster is updated.
void Monster::diePermCrt() {
std::map<int, crlasttime>::iterator it;
crlasttime* crtm=0;
Monster *temp_mob=0;
Room *room=0;
char perm[80];
long t = time(0);
int i=0;
strcpy(perm,name);
if(!parent_rom)
return;
room = parent_rom;
for(it = room->permMonsters.begin(); it != room->permMonsters.end() ; it++) {
crtm = &(*it).second;
if(!crtm->cr.id)
continue;
if(crtm->ltime + crtm->interval > t)
continue;
if(!loadMonster(crtm->cr, &temp_mob))
continue;
if(!strcmp(temp_mob->name, name)) {
crtm->ltime = t;
free_crt(temp_mob);
break;
}
free_crt(temp_mob);
}
if(flagIsSet(M_DEATH_SCENE) && !flagIsSet(M_FOLLOW_ATTACKER)) {
int fd,n;
char tmp[2048], file[80],pName[80];
strcpy(pName, name);
for(i=0; pName[i]; i++)
if(pName[i] == ' ')
pName[i] = '_';
sprintf(file,"%s/%s_%d", DDESCPATH, pName, level);
fd = open(file,O_RDONLY,0);
if(fd) {
n = read(fd,tmp,2048);
tmp[n] = 0;
broadcast(NULL, getRoom(), "\n%s", tmp);
}
close(fd);
}
}
//*********************************************************************
// consider
//*********************************************************************
// This function allows the player pointed to by the first argument to
// consider how difficult it would be to kill the creature in the
// second parameter.
bstring Player::consider(Creature* creature) const {
int diff=0;
bstring str = "";
// staff always needle
if(creature->isStaff() && !isStaff())
diff = -4;
else if(isStaff() && !creature->isStaff())
diff = 4;
else {
diff = level - creature->getLevel();
diff = MAX(-4, MIN(4, diff));
}
// upHeShe is used most often
str = creature->upHeShe();
switch(diff) {
case 0:
str += " is a perfect match for you!\n";
break;
case 1:
str += " is not quite as good as you.\n";
break;
case -1:
str += " is a little better than you.\n";
break;
case 2:
str += " shouldn't be too tough to kill.\n";
break;
case -2:
str += " might be tough to kill.\n";
break;
case 3:
str += " should be easy to kill.\n";
break;
case -3:
str += " should be really hard to kill.\n";
break;
case 4:
str = "You could kill ";
str += creature->himHer();
str += " with a needle.\n";
break;
case -4:
str += " could kill you with a needle.\n";
break;
}
return(str);
}
//********************************************************************
// hasCharm
//********************************************************************
// This function returns true if the name passed in the first parameter
// is in the charm list of the creature
bool Creature::hasCharm(bstring charmed) {
etag *cp=0;
if(!this || charmed == "")
return(false);
Player* player = getPlayer();
if(!player)
return(false);
cp = player->first_charm;
while(cp) {
if(!strcmp(cp->enemy, charmed.c_str()))
return(true);
cp = cp->next_tag;
}
return(false);
}
//********************************************************************
// addCharm
//********************************************************************
// This function adds the creature pointed to by the first
// parameter to the charm list of the creature
int Player::addCharm(Creature* creature) {
etag *cp=0;
ASSERTLOG( creature );
if(hasCharm(creature->name))
return(0);
if(creature && !creature->isDm()) {
cp = new etag;
if(!cp)
merror("add_charm_crt", FATAL);
strcpy(cp->enemy, creature->name);
cp->next_tag = first_charm;
first_charm = cp;
}
return(0);
}
//********************************************************************
// delCharm
//********************************************************************
// This function deletes the creature pointed to by the first
// parameter from the charm list of the creature
int Player::delCharm(Creature* creature) {
etag *cp, *prev;
ASSERTLOG( creature );
if(!hasCharm(creature->name))
return(0);
cp = first_charm;
if(!strcmp(cp->enemy, creature->name)) {
first_charm = cp->next_tag;
delete cp;
return(1);
} else
while(cp) {
if(!strcmp(cp->enemy, creature->name)) {
prev->next_tag = cp->next_tag;
delete cp;
return(1);
}
prev = cp;
cp = cp->next_tag;
}
return(0);
}
//*********************************************************************
// mobileEnter
//*********************************************************************
bool mobileEnter(Exit* exit) {
return( !exit->flagIsSet(X_SECRET) &&
!exit->flagIsSet(X_NO_SEE) &&
!exit->flagIsSet(X_LOCKED) &&
!exit->flagIsSet(X_PASSIVE_GUARD) &&
!exit->flagIsSet(X_NO_WANDER) &&
!exit->isConcealed() &&
!exit->flagIsSet(X_DESCRIPTION_ONLY) &&
!exit->isEffected("wall-of-fire") &&
!exit->isEffected("wall-of-force") &&
!exit->isEffected("wall-of-thorns")
);
}
//*********************************************************************
// mobile_crt
//*********************************************************************
// This function provides for self-moving monsters. If
// the M_MOBILE_MONSTER flag is set the creature will move around.
int Monster::mobileCrt() {
BaseRoom *newRoom=0;
Room *uRoom=0;
AreaRoom* aRoom=0, *caRoom = area_room;
xtag *xp=0;
int i=0, num=0, ret=0;
bool mem=false;
if( !flagIsSet(M_MOBILE_MONSTER) ||
flagIsSet(M_PERMENANT_MONSTER) ||
flagIsSet(M_PASSIVE_EXIT_GUARD)
)
return(0);
if(nearEnemy())
return(0);
xp = getRoom()->first_ext;
while(xp) {
// count up exits
if(mobileEnter(xp->ext))
i += 1;
xp = xp->next_tag;
}
if(!i)
return(0);
num = mrand(1, i);
i = 0;
xp = getRoom()->first_ext;
while(xp) {
if(mobileEnter(xp->ext))
i += 1;
if(i == num) {
// get us out of this room
if(!Move::getRoom(this, xp->ext, &uRoom, &aRoom))
return(0);
if(aRoom) {
mem = aRoom->getStayInMemory();
aRoom->setStayInMemory(true);
newRoom = aRoom;
} else
newRoom = uRoom;
// make sure there are no problems with the new room
if(!newRoom)
return(0);
if(newRoom->countCrt() >= newRoom->getMaxMobs())
return(0);
if(xp->ext->flagIsSet(X_CLOSED) && !xp->ext->flagIsSet(X_LOCKED)) {
broadcast(NULL, getRoom(), "%M just opened the %s.", this, xp->ext->name);
xp->ext->clearFlag(X_CLOSED);
}
if(flagIsSet(M_WILL_SNEAK) && flagIsSet(M_HIDDEN))
setFlag(M_SNEAKING);
if( flagIsSet(M_SNEAKING) &&
mrand (1,100) <= (3+dexterity.getCur())*3)
{
broadcast(::isStaff, getSock(), getRoom(), "*DM* %M just snuck to the %s.", this,xp->ext->name);
} else {
if(flagIsSet(M_CHASING_SOMEONE) && (first_enm))
broadcast(NULL, getRoom(), "%M %s to the %s, looking for %s.",
this, Move::getString(this).c_str(), xp->ext->name, first_enm->enemy);
else
broadcast(NULL, getRoom(), "%M just %s to the %s.",
this, Move::getString(this).c_str(), xp->ext->name);
clearFlag(M_SNEAKING);
}
// see if we can recycle this room
deleteFromRoom();
if(caRoom && aRoom == caRoom) {
aRoom = Move::recycle(aRoom, xp->ext);
newRoom = aRoom;
}
addToRoom(newRoom);
// reset stayInMemory
if(aRoom)
aRoom->setStayInMemory(mem);
lasttime[LT_MON_WANDER].ltime = time(0);
lasttime[LT_MON_WANDER].interval = mrand(5,60);
ret = 1;
break;
}
xp = xp->next_tag;
}
if(mrand(1,100) > 80)
clearFlag(M_MOBILE_MONSTER);
return(ret);
}
//*********************************************************************
// monsterCombat
//*********************************************************************
// This function should make monsters attack each other
void Monster::monsterCombat(Monster *target) {
broadcast(NULL, getRoom(), "%M attacks %N.\n", this, target);
addEnmCrt(target);
}
//*********************************************************************
// mobWield
//*********************************************************************
int Monster::mobWield() {
otag *op;
Object *object;
int i=0, found=0;
if(!first_obj)
return(0);
if(ready[WIELD - 1])
return(0);
op = first_obj;
while(op) {
for(i=0;i<10;i++) {
if(carry[i].info == op->obj->info) {
found=1;
break;
}
}
if(!found) {
op = op->next_tag;
continue;
}
if(op->obj->getWearflag() == WIELD) {
if((op->obj->damage.getNumber() + op->obj->damage.getSides() + op->obj->damage.getPlus()) <
(damage.getNumber() + damage.getSides() + damage.getPlus())/2 ||
(op->obj->getShotscur() < 1)) {
op = op->next_tag;
continue;
}
object = op->obj;
break;
}
op = op->next_tag;
}
if(!op)
return(0);
if(!object)
return(0);
equip(object, WIELD);
return(1);
}
//*********************************************************************
// getMobSave
//*********************************************************************
void Monster::getMobSave() {
int lvl=0, cls=0, index=0;
if(cClass == 0)
cls = 4; // All mobs default save as fighters.
else
cls = cClass;
if((saves[POI].chance +
saves[DEA].chance +
saves[BRE].chance +
saves[MEN].chance +
saves[SPL].chance) == 0)
{
saves[POI].chance = 5;
saves[DEA].chance = 5;
saves[BRE].chance = 5;
saves[MEN].chance = 5;
saves[SPL].chance = 5;
if(race) {
saves[POI].chance += gConfig->getRace(race)->getSave(POI);
saves[DEA].chance += gConfig->getRace(race)->getSave(DEA);
saves[BRE].chance += gConfig->getRace(race)->getSave(BRE);
saves[MEN].chance += gConfig->getRace(race)->getSave(MEN);
saves[SPL].chance += gConfig->getRace(race)->getSave(SPL);
}
if(level > 1) {
for(lvl = 2; lvl <= level; lvl++) {
index = (lvl - 2) % 10;
switch (saving_throw_cycle[cls][index]) {
case POI:
saves[POI].chance += 6;
break;
case DEA:
saves[DEA].chance += 6;
break;
case BRE:
saves[BRE].chance += 6;
break;
case MEN:
saves[MEN].chance += 6;
break;
case SPL:
saves[SPL].chance += 6;
break;
}
}
}
saves[LCK].chance = ((saves[POI].chance +
saves[DEA].chance +
saves[BRE].chance +
saves[MEN].chance +
saves[SPL].chance)/5);
}
}
//*********************************************************************
// castDelay
//*********************************************************************
void Creature::castDelay(long delay) {
if(delay < 1)
return;
lasttime[LT_SPELL].ltime = time(0);
lasttime[LT_SPELL].interval = delay;
}
//*********************************************************************
// attackDelay
//*********************************************************************
void Creature::attackDelay(long delay) {
if(delay < 1)
return;
getPlayer()->updateAttackTimer(true, delay*10);
}
//*********************************************************************
// enm_in_group
//*********************************************************************
Creature *enm_in_group(Creature *target) {
Creature *leader=0, *enemy=0;
ctag *cp=0;
int group_count=0, chosen=0, num=0;
if(!target || mrand(1,100) <= 50)
return(target);
if(target->following && !target->following->flagIsSet(P_DM_INVIS))
leader = target->following;
else
leader = target;
cp = leader->first_fol;
while(cp) {
if((cp->crt->flagIsSet(P_DM_INVIS) && cp->crt->isPlayer()) ||
!cp->crt->inSameRoom(target)) {
cp = cp->next_tag;
continue;
}
group_count++;
cp = cp->next_tag;
}
if(group_count <= 1)
return(leader);
else
chosen = mrand(1,group_count);
cp = leader->first_fol;
while(cp) {
if((cp->crt->flagIsSet(P_DM_INVIS) && cp->crt->isPlayer()) ||
!cp->crt->inSameRoom(target)) {
cp = cp->next_tag;
continue;
}
num++;
if(num == chosen) {
enemy = cp->crt;
break;
}
cp = cp->next_tag;
}
if(!enemy)
enemy = target;
return(enemy);
}
//*********************************************************************
// clearEnemyList
//*********************************************************************
void Monster::clearEnemyList() {
etag *ep = first_enm, *prev = 0;
while(ep) {
prev = ep;
ep = ep->next_tag;
if(!delEnmCrt(prev->enemy))
continue;
}
}
//*********************************************************************
// clearMobInventory
//*********************************************************************
void Monster::clearMobInventory() {
otag *op = first_obj;
Object* object=0;
while(op) {
object = op->obj;
op = op->next_tag;
this->delObj(object, false, false, true, false);
delete object;
}
checkDarkness();
}
//*********************************************************************
// cleanMobForSaving
//*********************************************************************
// This function will clean up the mob so it is ready for saving to the database
// ie: it will clear the inventory, any flags that shouldn't be saved to the
// database, clear any following etc.
int Monster::cleanMobForSaving() {
// No saving pets!
if(isPet())
return(-1);
// Clear the inventory
clearMobInventory();
// Clear any flags that shouldn't be set
clearFlag(M_WILL_BE_LOGGED);
// // If the creature is possessed, clean that up
if(flagIsSet(M_DM_FOLLOW)) {
clearFlag(M_DM_FOLLOW);
if(following != NULL) {
following->clearFlag(P_ALIASING);
following->getPlayer()->setAlias(0);
following->print("%1M's soul was saved.\n", this);
doStopFollowing(this, FALSE);
}
}
// Success
return(1);
}
//*********************************************************************
// displayFlags
//*********************************************************************
int Creature::displayFlags() const {
int flags = 0;
if(isEffected("detect-invisible"))
flags |= INV;
if(isEffected("detect-magic"))
flags |= MAG;
if(isEffected("true-sight"))
flags |= MIST;
if(isPlayer()) {
if(cClass == BUILDER)
flags |= ISBD;
if(cClass == CARETAKER)
flags |= ISCT;
if(isDm())
flags |= ISDM;
if(flagIsSet(P_NO_NUMBERS))
flags |= NONUM;
}
return(flags);
}
//*********************************************************************
// displayCreature
//*********************************************************************
int Player::displayCreature(Creature* target) const {
int percent=0, align=0, rank=0, chance=0, flags = displayFlags();
Player *pTarget = target->getPlayer();
Monster *mTarget = target->getMonster();
std::ostringstream oStr;
bstring str = "";
bool space=false;
if(mTarget) {
oStr << "You see " << crt_str(mTarget, 1, flags) << ".\n";
if(mTarget->getDescription() != "")
oStr << mTarget->getDescription() << "\n";
else
oStr << "There is nothing special about " << crt_str(mTarget, 0, flags) << ".\n";
if(mTarget->getMobTrade()) {
rank = mTarget->getSkillLevel()/10;
oStr << "^y" << crt_str(mTarget, 0, flags | CAP) << " is a " << get_trade_string(mTarget->getMobTrade())
<< ". " << mTarget->upHisHer() << " skill level: " << get_skill_string(rank) << ".^x\n";
}
} else if(pTarget) {
oStr << "You see " << pTarget->fullName() << " the "
<< gConfig->getRace(pTarget->getDisplayRace())->getAdjective();
// will they see through the illusion?
if(willIgnoreIllusion() && pTarget->getDisplayRace() != pTarget->getRace())
oStr << " (" << gConfig->getRace(pTarget->getRace())->getAdjective() << ")";
oStr << " "
<< pTarget->getTitle() << ".\n";
if(gConfig->getCalendar()->isBirthday(pTarget)) {
oStr << "^yToday is " << pTarget->name << "'s birthday! " << pTarget->upHeShe()
<< " is " << pTarget->getAge() << " years old.^x\n";
}
if(pTarget->description != "")
oStr << pTarget->description << "\n";
}
if(target->isEffected("vampirism")) {
chance = intelligence.getCur()/10 + piety.getCur()/10;
// vampires and werewolves can sense each other
if(isEffected("vampirism") || isEffected("lycanthropy"))
chance = 101;
if(chance > mrand(0,100)) {
switch(mrand(1,4)) {
case 1:
oStr << target->upHeShe() << " looks awfully pale.\n";
break;
case 2:
oStr << target->upHeShe() << " has an unearthly presence.\n";
break;
case 3:
oStr << target->upHeShe() << " has hypnotic eyes.\n";
break;
default:
oStr << target->upHeShe() << " looks rather pale.\n";
}
}
}
if(target->isEffected("lycanthropy")) {
chance = intelligence.getCur()/10 + piety.getCur()/10;
// vampires and werewolves can sense each other
if(isEffected("vampirism") || isEffected("lycanthropy"))
chance = 101;
if(chance > mrand(0,100)) {
switch(mrand(1,3)) {
case 1:
oStr << target->upHeShe() << " looks awfully shaggy.\n";
break;
case 2:
oStr << target->upHeShe() << " has a feral presence.\n";
break;
default:
oStr << target->upHeShe() << " looks rather shaggy.\n";
}
}
}
if(target->isEffected("slow"))
oStr << target->upHeShe() << " is moving very slowly.\n";
else if(target->isEffected("haste"))
oStr << target->upHeShe() << " is moving unnaturally quick.\n";
if((cClass == CLERIC && deity == JAKAR && level >=7) || isCt())
oStr << "^y" << crt_str(target, 0, flags | CAP) << " is carrying "
<< target->coins[GOLD] << " gold coin"
<< (target->coins[GOLD] != 1 ? "s" : "") << ".^x\n";
if(isEffected("know-aura") || cClass==PALADIN) {
space = true;
oStr << crt_str(target, 0, flags | CAP) << " ";
align = target->getAdjustedAlignment();
switch(align) {
case BLOODRED:
oStr << "has a blood red aura.";
break;
case REDDISH:
oStr << "has a reddish aura.";
break;
case PINKISH:
oStr << "has a pinkish aura.";
break;
case NEUTRAL:
oStr << "has a grey aura.";
break;
case LIGHTBLUE:
oStr << "has a light blue aura.";
break;
case BLUISH:
oStr << "has a bluish aura.";
break;
case ROYALBLUE:
oStr << "has a royal blue aura.";
break;
default:
oStr << "has a grey aura.";
break;
}
}
if(mTarget && mTarget->getRace()) {
if(space)
oStr << " ";
space = true;
oStr << mTarget->upHeShe() << " is ^W"
<< gConfig->getRace(mTarget->getRace())->getAdjective().toLower() << "^x.";
}
if(target->getSize() != NO_SIZE) {
if(space)
oStr << " ";
space = true;
oStr << target->upHeShe() << " is ^W" << getSizeName(target->getSize()) << "^x.";
}
if(space)
oStr << "\n";
if(target->hp.getCur() > 0 && target->hp.getMax())
percent = (100 * (target->hp.getCur())) / (target->hp.getMax());
else
percent = -1;
oStr << target->upHeShe();
if(percent >= 100 || !target->hp.getMax())
oStr << " is in excellent condition.\n";
else if(percent >= 90)
oStr << " has a few scratches.\n";
else if(percent >= 75)
oStr << " has some small wounds and bruises.\n";
else if(percent >= 60)
oStr << " is wincing in pain.\n";
else if(percent >= 35)
oStr << " has quite a few wounds.\n";
else if(percent >= 20)
oStr << " has some big nasty wounds and scratches.\n";
else if(percent >= 10)
oStr << " is bleeding awfully from big wounds.\n";
else if(percent >= 5)
oStr << " is barely clinging to life.\n";
else if(percent >= 0)
oStr << " is nearly dead.\n";
if(pTarget) {
if(pTarget->flagIsSet(P_MISTED)) {
oStr << pTarget->upHeShe() << "%s is currently in mist form.\n";
printColor("%s", oStr.str().c_str());
return(0);
}
if(pTarget->flagIsSet(P_UNCONSCIOUS))
oStr << pTarget->name << " is "
<< (pTarget->flagIsSet(P_SLEEPING) ? "sleeping" : "unconscious") << ".\n";
if(pTarget->isEffected("petrification"))
oStr << pTarget->name << " is petrified.\n";
if(pTarget->isBraindead())
oStr << pTarget->name << "%M is currently brain dead.\n";
} else {
if(mTarget->isEnmCrt(name))
oStr << mTarget->upHeShe() << " looks very angry at you.\n";
else if(mTarget->getPrimeFaction() != "")
oStr << mTarget->upHeShe() << " " << getFactionMessage(mTarget->getPrimeFaction()) << ".\n";
if(mTarget->first_enm) {
if(!strcmp(mTarget->first_enm->enemy, name)) {
if( /* !mTarget->flagIsSet(M_WILL_BASH) &&
!mTarget->flagIsSet(M_WILL_BACKSTAB) && */
!mTarget->flagIsSet(M_HIDDEN) &&
!(mTarget->isInvisible() && isEffected("detect-invisible"))
)
oStr << mTarget->upHeShe() << " is attacking you.\n";
} else
oStr << mTarget->upHeShe() << " is attacking " << mTarget->first_enm->enemy << ".\n";
/// print all the enemies if a CT or DM is looking
if(isCt()) {
etag *ep;
oStr << "Enemy list:\n";
ep = mTarget->first_enm;
while(ep) {
if(ep->owner[0])
oStr << " " << mTarget->upHeShe() << " is attacking " << ep->enemy
<< "(" << ep->owner << "). (" << ep->damage << " DMG)\n";
else
oStr << " " << mTarget->upHeShe() << " is attacking " << ep->enemy
<< ". (" << ep->damage << " DMG)\n";
ep = ep->next_tag;
}
}
}
oStr << consider(mTarget);
// pet code
if(mTarget->isPet() && mTarget->following == this) {
str = listObjects(this, mTarget->first_obj, true);
oStr << mTarget->upHeShe() << " ";
if(str == "")
oStr << "isn't holding anything.\n";
else
oStr << "is carrying: " << str << ".\n";
}
}
printColor("%s", oStr.str().c_str());
target->printEquipList(this);
return(0);
}
//*********************************************************************
// findCrt
//*********************************************************************
int findCrt(Creature * player, ctag *first_ct, int findFlags, char *str, int val, int* match, Creature ** target ) {
ctag *cp;
int found=0;
if(!player || !str || !first_ct)
return(0);
cp = first_ct;
while(cp) {
if(!cp->crt) {
cp = cp->next_tag;
continue;
}
if(!player->canSee(cp->crt)) {
cp = cp->next_tag;
continue;
}
if(keyTxtEqual(cp->crt, str)) {
(*match)++;
if(*match == val) {
*target = cp->crt;
found = 1;
break;
}
}
cp = cp->next_tag;
}
return(found);
}
//*********************************************************************
// checkSpellWearoff
//*********************************************************************
void Monster::checkSpellWearoff() {
long t = time(0);
if(flagIsSet(M_WILL_MOVE_FOR_CASH)) {
if(t > LT(this, LT_MOB_PASV_GUARD)) {
// Been 30 secs, time to stand guard again
setFlag(M_PASSIVE_EXIT_GUARD);
}
}
if(t > LT(this, LT_CHARMED) && flagIsSet(M_CHARMED)) {
printColor("^y%M's demeanor returns to normal.\n", this);
clearFlag(M_CHARMED);
}
}
//*********************************************************************
// canFlee
//*********************************************************************
bool Creature::canFlee() const {
bool crtInRoom=false;
long t=0;
int i=0;
ctag* cp=0;
if(isMonster()) {
if(!flagIsSet(M_WILL_FLEE) || flagIsSet(M_DM_FOLLOW))
return(false);
if(hp.getCur() > hp.getMax()/5)
return(false);
if(flagIsSet(M_PERMENANT_MONSTER))
return(false);
} else {
//Player* pThis = getPlayer();
if(!ableToDoCommand())
return(false);
if(flagIsSet(P_SITTING)) {
print("You have to stand up first.\n");
return(false);
}
if(isEffected("hold-person")) {
print("You are unable to move right now.\n");
return(false);
}
if( (cClass == BERSERKER || cClass == CLERIC) &&
flagIsSet(P_BERSERKED)
) {
printColor("^rYour lust for battle prevents you from fleeing!\n");
return(false);
}
if(!isEffected("fear") && !isStaff()) {
// // Check attack timer first
// pThis->modifyAttackDelay(30);
// bool result = pThis->checkAttackTimer();
// pThis->modifyAttackDelay(-30); // Set the timer back to whwere it was before
// if(result == false)
// return(false);
t = time(0);
i = MAX(getLTAttack(), MAX(lasttime[LT_SPELL].ltime,lasttime[LT_READ_SCROLL].ltime)) + 3L;
if(t < i) {
pleaseWait(i-t);
return(false);
}
}
// confusion and drunkenness overrides following checks
if(isEffected("confusion") || isEffected("drunkenness"))
return(true);
// players can only flee if someone/something else is in the room
cp = getRoom()->first_mon;
while(cp && !crtInRoom) {
if(!cp->crt->isPet())
crtInRoom = true;
cp = cp->next_tag;
}
cp = getRoom()->first_ply;
while(cp && !crtInRoom) {
if(cp->crt == this) {
cp = cp->next_tag;
continue;
}
if(!cp->crt->isStaff())
crtInRoom = true;
cp = cp->next_tag;
}
if(!crtInRoom) {
print("There's nothing to flee from!\n");
return(false);
}
}
return(true);
}
//*********************************************************************
// canFleeToExit
//*********************************************************************
bool Creature::canFleeToExit(const Exit *exit, bool skipScary) {
Player *pThis = getPlayer();
// flags both players and mobs have to obey
if(!canEnter(exit) ||
exit->flagIsSet(X_SECRET) ||
exit->isConcealed(this) ||
exit->flagIsSet(X_DESCRIPTION_ONLY) ||
exit->flagIsSet(X_NO_FLEE) ||
exit->flagIsSet(X_TO_STORAGE_ROOM) ||
exit->flagIsSet(X_TO_BOUND_ROOM) ||
exit->flagIsSet(X_TOLL_TO_PASS) ||
exit->flagIsSet(X_WATCHER_UNLOCK)
)
return(false);
if(pThis) {
if(
(getRoom()->flagIsSet(R_NO_FLEE) && inCombat()) ||
(exit->flagIsSet(X_DIFFICULT_CLIMB) && !isEffected("levitate")) ||
(
(
exit->flagIsSet(X_NEEDS_CLIMBING_GEAR) ||
exit->flagIsSet(X_CLIMBING_GEAR_TO_REPEL) ||
exit->flagIsSet(X_DIFFICULT_CLIMB)
) &&
!isEffected("levitate")
)
) return(false);
int chance=0, *scary;
// should we skip checking for scary exits?
skipScary = skipScary || fd == -1 || exit->target.mapmarker.getArea() || isEffected("fear");
// are they scared of going in that direction
if(!skipScary && pThis->scared_of) {
scary = pThis->scared_of;
while(*scary) {
if(exit->target.room.id && *scary == exit->target.room.id) {
// oldPrint(fd, "Scared of going %s!\n", exit->name);
// there is a chance we will flee to this exit anyway
chance = 65 + bonus((int) dexterity.getCur()) * 5;
if(getRoom()->flagIsSet(R_DIFFICULT_TO_MOVE) || getRoom()->flagIsSet(R_DIFFICULT_FLEE))
chance /=2;
if(mrand(1,100) < chance)
return(false);
// success; means that the player is now scared of this room
if(parent_rom && fd > -1) {
scary = pThis->scared_of;
{
int roomNum = parent_rom->info.id;
if(scary) {
int size = 0;
while(*scary) {
size++;
if(*scary == roomNum)
break;
scary++;
}
if(!*scary) {
// LEAK: Next line reported to be leaky: 10 count
pThis->scared_of = (int *) realloc(pThis->scared_of, (size + 2) * sizeof(int));
(pThis->scared_of)[size] = roomNum;
(pThis->scared_of)[size + 1] = 0;
}
} else {
// LEAK: Next line reported to be leaky: 10 count
pThis->scared_of = (int *) malloc(sizeof(int) * 2);
(pThis->scared_of)[0] = roomNum;
(pThis->scared_of)[1] = 0;
}
}
}
return(true);
}
scary++;
}
}
}
return(true);
}
//*********************************************************************
// getFleeableExit
//*********************************************************************
Exit* Creature::getFleeableExit() {
xtag* xp = getRoom()->first_ext;
int i=0, exit=0;
bool skipScary=false;
// count exits so we can randomly pick one
while(xp) {
if(canFleeToExit(xp->ext))
i++;
xp = xp->next_tag;
}
if(i) {
// pick a random exit
exit = mrand(1, i);
} else if(isPlayer()) {
// force players to skip the scary list
xp = getRoom()->first_ext;
skipScary = true;
i=0;
while(xp) {
if(canFleeToExit(xp->ext, true))
i++;
xp = xp->next_tag;
}
if(i)
exit = mrand(1, i);
}
if(!exit)
return(0);
i=0;
xp = getRoom()->first_ext;
while(xp) {
if(canFleeToExit(xp->ext, skipScary))
i++;
if(i == exit)
return(xp->ext);
xp = xp->next_tag;
}
return(0);
}
//*********************************************************************
// getFleeableRoom
//*********************************************************************
BaseRoom* Creature::getFleeableRoom(Exit* exit) {
AreaRoom* aRoom=0;
Room* uRoom=0;
if(!exit)
return(0);
// exit flags have already been taken care of above, so
// feign teleporting so we don't recycle the room
Move::getRoom(this, exit, &uRoom, &aRoom, false, 0, false);
if(uRoom)
return(uRoom);
else if(aRoom)
return(aRoom);
return(0);
}
//*********************************************************************
// flee
//*********************************************************************
// This function allows a player to flee from an enemy. If successful
// the player will drop their readied weapon and run through one of the
// visible exits, losing 10% or 1000 experience, whichever is less.
int Creature::flee(bool magicTerror) {
Monster* mThis = getMonster();
Player* pThis = getPlayer();
BaseRoom* newRoom=0;
Room* uRoom=0;
MapMarker* mapmarker=0;
Exit* exit=0;
unsigned int n=0;
if(!magicTerror && !canFlee())
return(0);
if(isEffected("fear"))
magicTerror = true;
exit = getFleeableExit();
if(!exit) {
printColor("You failed to escape!\n");
return(0);
}
newRoom = getFleeableRoom(exit);
if(!newRoom) {
printColor("You failed to escape!\n");
return(0);
}
switch(mrand(1,10)) {
case 1:
print("You run like a chicken.\n");
break;
case 2:
print("You flee in terror.\n");
break;
case 3:
print("You run screaming in horror.\n");
break;
case 4:
print("You flee aimlessly in any direction you can.\n");
break;
case 5:
print("You run screaming for your mommy.\n");
break;
case 6:
print("You run as your life flashes before your eyes.\n");
break;
case 7:
print("Your heart throbs as you attempt to escape death.\n");
break;
case 8:
print("Colors and sounds mingle as you frantically flee.\n");
break;
case 9:
print("Fear of death grips you as you flee in panic.\n");
break;
case 10:
print("You run like a coward.\n");
break;
default:
print("You run like a chicken.\n");
break;
}
broadcast(getSock(), getRoom(), "%M flees to the %s.", this, exit->name);
if(mThis) {
mThis->diePermCrt();
if(exit->doEffectDamage(this))
return(2);
mThis->deleteFromRoom();
mThis->addToRoom(newRoom);
} else if(pThis) {
pThis->dropWeapons();
pThis->checkDarkness();
unhide();
if(magicTerror)
printColor("^rYou flee from unnatural fear!\n");
if(area_room)
mapmarker = &(area_room->mapmarker);
Move::track(parent_rom, mapmarker, exit, pThis, false);
if(pThis->flagIsSet(P_ALIASING)) {
pThis->getAlias()->deleteFromRoom();
pThis->getAlias()->addToRoom(newRoom);
}
Move::update(pThis);
pThis->statistics.flee();
if(cClass == PALADIN && deity != GRADIUS && !magicTerror && level >= 10) {
n = level * 8;
n = MIN(experience, n);
print("You lose %d experience for your cowardly retreat.\n", n);
experience -= n;
}
if(exit->doEffectDamage(this))
return(2);
mThis = pThis->getPet();
if(mThis && inSameRoom(mThis))
broadcast(getSock(), getRoom(), "%M flees to the %s with its master.",
mThis, exit->name);
mThis = 0;
pThis->deleteFromRoom();
pThis->addToRoom(newRoom);
}
broadcast(getSock(), newRoom, "%M just fled rapidly into the room.", this);
if(pThis) {
pThis->doPetFollow();
uRoom = newRoom->getUniqueRoom();
if(uRoom)
pThis->checkTraps(uRoom);
}
return(1);
}