/* * creature2.cpp * Additional creature routines. * ____ _ * | _ \ ___ __ _| |_ __ ___ ___ * | |_) / _ \/ _` | | '_ ` _ \/ __| * | _ < __/ (_| | | | | | | \__ \ * |_| \_\___|\__,_|_|_| |_| |_|___/ * * 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 "mud.h" //#include "string.h" typedef struct { unsigned short armor; short thaco; unsigned long experience; unsigned short ndice; unsigned short sdice; short pdice; } balanceStat; balanceStat balancedStats[41] = { // armor, thaco, experience, ndice, sdice, pdice {0,0,0,0,0,0}, // 0 {25,20,6,1,4,0}, // 1 {50,19,15,2,3,0}, // 2 {75,18,35,2,3,2}, // 3 {100,17,50,2,3,2}, // 4 {125,16,65,2,3,4}, // 5 {150,15,75,4,2,4}, // 6 {175,14,100,2,4,6}, // 7 {200,13,130,4,3,4}, // 8 {225,12,180,5,3,3}, // 9 {250,11,240,3,5,5}, // 10 {275,10,325,3,6,4}, // 11 {300,9,400,3,6,6}, // 12 {325,8,550,4,4,8}, // 13 {350,7,700,4,5,8}, // 14 {390,6,850,5,5,5}, // 15 {430,5,1000,4,6,8}, // 16 {470,4,1150,4,6,9}, // 17 {510,3,1300,5,6,8}, // 18 {550,2,1450,6,5,10}, // 19 {590,1,1600,4,7,10}, // 20 {630,0,1800,9,5,4}, // 21 {670,-2,2000,8,5,10}, // 22 {710,-4,2500,10,5,6}, // 23 {750,-7,3000,10,5,8}, // 24 {790,-8,3500,15,4,0}, // 25 {830,-8,4000,20,3,4}, // 26 {870,-8,5500,13,4,12}, // 27 {920,-8,7000,13,5,5}, // 28 {960,-9,8500,15,4,10}, // 29 {1000,-10,10000,18,3,16}, // 30 {1040,-10,11625,19,3,17}, // 31 {1080,-10,12000,20,3,17}, // 32 {1120,-10,12375,21,3,18}, // 33 {1160,-11,12750,22,3,18}, // 34 {1200,-12,13125,23,3,19}, // 35 {1240,-12,13500,24,3,19}, // 36 {1280,-12,13875,25,3,20}, // 37 {1320,-12,14250,26,3,20}, // 38 {1360,-13,14625,27,3,21}, // 39 {1400,-14,15000,28,3,23} // 40 }; void Monster::adjust(int buffswitch) { int buff=0, crthp=0; long xpmod=0; if(buffswitch == -1) buff = mrand(1,3)-1; else buff = MAX(0, MIN(buffswitch, 2)); /* Web Editor * _ _ ____ _______ ______ * | \ | |/ __ \__ __| ____| * | \| | | | | | | | |__ * | . ` | | | | | | | __| * | |\ | |__| | | | | |____ * |_| \_|\____/ |_| |______| * * If you change anything here, make sure the changes are reflected in the web * editor! Either edit the PHP yourself or tell Dominus to make the changes. */ if(!flagIsSet(M_CUSTOM)) { if(level >= 1 && level <= MAXALVL) { setArmor(balancedStats[level].armor); weaponSkill = (level-1)*10; defenseSkill = (level-1)*10; setExperience(balancedStats[level].experience); damage.setNumber(balancedStats[level].ndice); damage.setSides(balancedStats[level].sdice); damage.setPlus(balancedStats[level].pdice); } else { setArmor(100); weaponSkill = 1; defenseSkill = 1; setExperience(6); damage.setNumber(1); damage.setSides(4); damage.setPlus(0); } if(!cClass) hp.setMax(level * monType::getHitdice(type)); else { crthp = class_stats[(int) cClass].hpstart + (level*class_stats[(int) cClass].hp); hp.setMax(MAX(crthp, (level * monType::getHitdice(type)))); } hp.restore(); xpmod = experience / 10; xpmod *= monType::getHitdice(type) - monType::getHitdice(HUMANOID); if(xpmod > 0) addExperience(xpmod); else subExperience(xpmod*-1); } switch(buff) { case 1: // Mob is weak. hp.setMax( hp.getMax() - hp.getMax()/5); hp.restore(); coins.set(coins[GOLD] * 4 / 5, GOLD); subExperience(experience / 5); setArmor(armor - 100); damage.setNumber(damage.getNumber() * 9 / 10); damage.setPlus(damage.getPlus() * 9 / 10); break; case 2: // Mob is stronger hp.setMax( hp.getMax() + hp.getMax()/5); hp.restore(); coins.set(coins[GOLD] * 6 / 5, GOLD); addExperience(experience / 5); defenseSkill += 5; weaponSkill += 5; setArmor(armor + 100); damage.setNumber(damage.getNumber() * 11 / 10); damage.setPlus(damage.getPlus() * 11 / 10); break; default: break; } armor = MAX(MIN(armor, MAX_ARMOR), 0); if(level >= 7) setFlag(M_BLOCK_EXIT); } // ********************************************************* // initMonster // ********************************************************* // Parameters: <creature> The creature being initialized // <Opt:loadOriginal> Don't randomize align/gold // <Opt:prototype> Load all objs in inventory // Initializes last times, inventory, scrolls, etc int Monster::initMonster(bool loadOriginal, bool prototype) { int n=0, alnum=0, x=0; long t=0; Object* object=0; t = time(0); // init the timers lasttime[LT_MON_SCAVANGE].ltime = lasttime[LT_MON_WANDER].ltime = lasttime[LT_MOB_THIEF].ltime = lasttime[LT_TICK].ltime = t; lasttime[LT_TICK_SECONDARY].ltime = t; lasttime[LT_TICK_HARMFUL].ltime = t; // Make sure armor is set properly if(armor < (unsigned)(balancedStats[MIN(level, MAXALVL)].armor - 150)) { armor = balancedStats[MIN(level, MAXALVL)].armor; } if(dexterity.getCur() < 200) setAttackDelay(30); else setAttackDelay(20); if(flagIsSet(M_FAST_TICK)) lasttime[LT_TICK].interval = lasttime[LT_TICK_SECONDARY].interval = 15L; else if(flagIsSet(M_REGENERATES)) lasttime[LT_TICK].interval = lasttime[LT_TICK_SECONDARY].interval = 15L; else lasttime[LT_TICK].interval = lasttime[LT_TICK_SECONDARY].interval = 60L - (2*bonus((int)constitution.getCur())); lasttime[LT_TICK_HARMFUL].interval = 30; // Randomize alignment and gold unless we don' want it if(!loadOriginal) { if(flagIsSet(M_ALIGNMENT_VARIES) && alignment != 0) { alnum = mrand(1,100); if(alnum == 1) alignment = 0; else if(alnum < 51) alignment = mrand(1,abs(alignment)) * -1; else alignment = mrand(1,abs(alignment)); } if(!flagIsSet(M_NO_RANDOM_GOLD) && coins[GOLD]) coins.set(mrand(coins[GOLD]/10, coins[GOLD]), GOLD); } // Check for loading of random scrolls if(checkScrollDrop()) { n = new_scroll(level, &object); if(n > 0) { object->value.zero(); addObj(object); object->setFlag(O_JUST_LOADED); } } // Now load up any always drop objects (Trading perms don't drop inventory items) if(!flagIsSet(M_TRADES)) { for(x=0;x<10;x++) { if(!loadObject(carry[x].info, &object)) continue; object->init(!prototype); if( object->flagIsSet(O_ALWAYS_DROPPED) && object->name[0] && object->name[0] != ' ' && object->name[0] != '\0' ) { addObj(object); } else { delete object; continue; } } object = 0; int numDrops = mrand(1,100), whichDrop=0; if(numDrops<90) numDrops=1; else if(numDrops<96) numDrops=2; else numDrops=3; if(prototype) numDrops = 10; for(x=0; x<numDrops; x++) { if(prototype) whichDrop=x; else whichDrop = mrand(0,9); if(carry[whichDrop].info.id && !flagIsSet(M_TRADES)) { if(!loadObject(carry[whichDrop].info, &object)) continue; if( !object->name[0] || object->name[0] == ' ' || object->name[0] == '\0' ) { delete object; continue; } object->init(!prototype); // so we don't get more than one always drop item. if(object->flagIsSet(O_ALWAYS_DROPPED)) { delete object; continue; } object->value.set(mrand((object->value[GOLD]*9)/10,(object->value[GOLD]*11)/10), GOLD); addObj(object); } } } if(weaponSkill < (level * 3)) weaponSkill = (level-1) * mrand(9,11); return(1); } //********************************************************************* // getMobNum //********************************************************************* // This function determines which one of the same kind of monster // is in a room. For example, if there are three winos, it will // return which number wino it is - 1, 2, or 3. - Tim C. int Monster::getNumMobs() const { int i=0; ctag *cp=0; if(flagIsSet(M_DM_FOLLOW) || flagIsSet(M_WAS_PORTED)) return(0); cp = getRoom()->first_mon; while(cp) { if(!strcmp(cp->crt->name, name)) { i++; if(cp->crt == this) return(i); } cp = cp->next_tag; } return(0); } //*********************************************************************** // getAdjustedAlignment //*********************************************************************** int Monster::getAdjustedAlignment() const { if(alignment <= -200) return(BLOODRED); else if(alignment > -200 && alignment < -50) return(REDDISH); else if(alignment >= -50 && alignment < 0) return(PINKISH); else if(alignment == 0) return(NEUTRAL); else if(alignment > 0 && alignment < 50) return(LIGHTBLUE); else if(alignment >= 50 && alignment < 200) return(BLUISH); else if(alignment >= 200) return(ROYALBLUE); return(NEUTRAL); } int Player::getAdjustedAlignment() const { if(alignment <= -400) return(BLOODRED); else if(alignment <= -200 && alignment > -400) return(REDDISH); else if(alignment <= -100 && alignment > -200) return(PINKISH); else if(alignment > -100 && alignment < 100) return(NEUTRAL); else if(alignment >= 100 && alignment < 200) return(LIGHTBLUE); else if(alignment >= 200 && alignment < 400) return(BLUISH); else if(alignment >= 400) return(ROYALBLUE); return(NEUTRAL); } //*********************************************************************** // alignColor //*********************************************************************** bstring Creature::alignColor() const { if(getAdjustedAlignment() < NEUTRAL) return("^r"); else if(getAdjustedAlignment() > NEUTRAL) return("^c"); else return("^x"); } //*********************************************************************** // getRandomMonster //*********************************************************************** Creature *getRandomMonster(BaseRoom *inRoom) { ctag *cp=0; Creature *foundCrt=0; int count=0, roll=0, num=0; num = inRoom->countCrt(); if(!num) return(0); roll = mrand(1, num); cp = inRoom->first_mon; while(cp) { if(cp->crt->isPet()) { cp = cp->next_tag; continue; } count++; if(count == roll) { foundCrt = cp->crt; break; } cp = cp->next_tag; } if(foundCrt) return(foundCrt); return(0); } //*********************************************************************** // getRandomPlayer //*********************************************************************** Creature *getRandomPlayer(BaseRoom *inRoom) { ctag *cp=0; Creature *foundPly=0; int count=0, roll=0, num=0; num = inRoom->countVisPly(); if(!num) return(0); roll = mrand(1, num); cp = inRoom->first_ply; while(cp) { if(cp->crt->flagIsSet(P_DM_INVIS)) { cp = cp->next_tag; continue; } count++; if(count == roll) { foundPly = cp->crt; break; } cp = cp->next_tag; } if(foundPly) return(foundPly); return(0); } //*********************************************************************** // validateAc //*********************************************************************** void Monster::validateAc() { unsigned int ac=0; ac = get_perm_ac(level); if(armor > ac) armor = ac; } //*********************************************************************** // doHarmfulAuras //*********************************************************************** int Monster::doHarmfulAuras() { int a=0,dmg=0,aura=0, saved=0; long i=0,t=0; ctag *cp=0; BaseRoom *inRoom=0; Creature* player=0; if(isPet()) return(0); if(hp.getCur() < hp.getMax()/10) return(0); if(flagIsSet(M_CHARMED)) return(0); for(a=0;a<MAX_AURAS;a++) { if(flagIsSet(M_FIRE_AURA + a)) aura++; } if(!aura) return(0); i = lasttime[LT_M_AURA_ATTACK].ltime; t = time(0); if(t - i < 20L) // Mob has to wait 20 seconds. return(0); else { lasttime[LT_M_AURA_ATTACK].ltime = t; lasttime[LT_M_AURA_ATTACK].interval = 20L; } inRoom = getRoom(); if(!inRoom) return(0); for(a=0;a<MAX_AURAS;a++) { if(!flagIsSet(M_FIRE_AURA + a)) continue; cp = inRoom->first_ply; while(cp) { player = cp->crt; cp = cp->next_tag; if(player->isEffected("petrification") || player->isCt()) continue; dmg = mrand(level/2, (level*3)/2); dmg = MAX(2,dmg); switch(a+M_FIRE_AURA) { case M_FIRE_AURA: if(player->isEffected("heat-protection") || player->isEffected("alwayswarm")) continue; saved = player->chkSave(BRE, this, 0); if(saved) dmg /=2; player->printColor("^R%M's firey aura singes you for %s%d^R damage!\n", this, player->customColorize("*CC:DAMAGE*"), dmg); break; case M_COLD_AURA: if(player->isEffected("warmth") || player->isEffected("alwayscold")) continue; saved = player->chkSave(BRE, this, 0); if(saved) dmg /=2; player->printColor("^C%M's freezing aura chills you for %s%d^C damage!\n", this, player->customColorize("*CC:DAMAGE*"), dmg); break; case M_NAUSEATING_AURA: if(player->immuneToPoison()) continue; saved = player->chkSave(POI, this, 0); if(saved) dmg /=2; player->printColor("^g%M's foul stench chokes and nauseates you for %s%d^g damage.\n", this, player->customColorize("*CC:DAMAGE*"), dmg); break; case M_NEGATIVE_LIFE_AURA: if(player->isUndead() || player->isEffected("drain-shield")) continue; saved = player->chkSave(DEA, this, 0); if(saved) dmg /=2; player->printColor("^m%M's negative aura taps your life for %s%d^m damage.\n", this, player->customColorize("*CC:DAMAGE*"), dmg); break; case M_TURBULENT_WIND_AURA: if(player->isEffected("wind-protection")) continue; saved = player->chkSave(BRE, this, 0); if(saved) dmg /=2; player->printColor("^W%M's turbulent winds buff you about for %s%d^W damage.\n", this, player->customColorize("*CC:DAMAGE*"), dmg); break; } player->hp.decrease(dmg); if(player->checkDie(this)) return(1); }// End while }// End for return(0); } //*********************************************************************** // isGuardLoot //*********************************************************************** bool isGuardLoot(BaseRoom *inRoom, Creature* player, const char *fmt) { ctag *cp=0; cp = inRoom->first_mon; while(cp) { if(cp->crt->flagIsSet(M_GUARD_TREATURE) && !player->checkStaff(fmt, cp->crt)) { return(true); } cp = cp->next_tag; } return(false); } //*********************************************************************** // npcPresent //*********************************************************************** bool npcPresent(Room *inRoom, short trade) { Monster *monster=0; ctag *cp=0; cp = inRoom->first_mon; while(cp) { monster = cp->crt->getMonster(); cp = cp->next_tag; if(monster->getMobTrade() == trade) return(true); } return(false); } //*********************************************************************** // getAlignDiff //*********************************************************************** // This function returns the distance on the alignment scale between // any two different creatures. If either creature is neutral, or if // both creatures are of the same moral character (i.e. both good or // both evil) it will return 0. It is to be used for when opposing // ethos has an effect on anything. int getAlignDiff(Creature *crt1, Creature *crt2) { int alignDiff = 0; if((crt1->getAdjustedAlignment() < NEUTRAL && crt2->getAdjustedAlignment() > NEUTRAL) || (crt2->getAdjustedAlignment() < NEUTRAL && crt1->getAdjustedAlignment() > NEUTRAL) ) { alignDiff = abs(crt1->getAdjustedAlignment()) + abs(crt2->getAdjustedAlignment()); } return(alignDiff); } //********************************************************************* // race aggro //********************************************************************* bool Monster::isRaceAggro(int x, bool checkInvert) const { x--; bool set = (raceAggro[x/8] & 1<<(x%8)); if(!checkInvert) return(set); return(set ? !flagIsSet(M_RACE_AGGRO_INVERT) : flagIsSet(M_RACE_AGGRO_INVERT)); } void Monster::setRaceAggro(int x) { x--; raceAggro[x/8] |= 1<<(x%8); } void Monster::clearRaceAggro(int x) { x--; raceAggro[x/8] &= ~(1<<(x%8)); } //********************************************************************* // class aggro //********************************************************************* bool Monster::isClassAggro(int x, bool checkInvert) const { x--; bool set = (cClassAggro[x/8] & 1<<(x%8)); if(!checkInvert) return(set); return(set ? !flagIsSet(M_RACE_AGGRO_INVERT) : flagIsSet(M_RACE_AGGRO_INVERT)); } void Monster::setClassAggro(int x) { x--; cClassAggro[x/8] |= 1<<(x%8); } void Monster::clearClassAggro(int x) { x--; cClassAggro[x/8] &= ~(1<<(x%8)); } //********************************************************************* // deity aggro //********************************************************************* bool Monster::isDeityAggro(int x, bool checkInvert) const { x--; bool set = (deityAggro[x/8] & 1<<(x%8)); if(!checkInvert) return(set); return(set ? !flagIsSet(M_RACE_AGGRO_INVERT) : flagIsSet(M_RACE_AGGRO_INVERT)); } void Monster::setDeityAggro(int x) { x--; deityAggro[x/8] |= 1<<(x%8); } void Monster::clearDeityAggro(int x) { x--; deityAggro[x/8] &= ~(1<<(x%8)); }