/* * attack.cpp * Routines to handle player combat * ____ _ * | _ \ ___ __ _| |_ __ ___ ___ * | |_) / _ \/ _` | | '_ ` _ \/ __| * | _ < __/ (_| | | | | | | \__ \ * |_| \_\___|\__,_|_|_| |_| |_|___/ * * 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 * */ // Mud includes #include "mud.h" #include "factions.h" #include "commands.h" #include "effects.h" // C includes #include <math.h> Creature* Creature::findVictim(bstring toFind, int num, bool aggressive, bool selfOk, bstring noVictim, bstring notFound) { Creature* victim=0; Player *pVictim=0; if(toFind == "") { if(hasAttackableTarget()) { return(getTarget()); } if(!noVictim.empty()) bPrint(noVictim); return(NULL); } else { victim = getRoomParent()->findCreature(this, toFind.c_str(), num, true, true); if(victim) pVictim = victim->getAsPlayer(); if(!victim || (aggressive && (pVictim || victim->isPet()) && toFind.length() < 3) || (!selfOk && victim == this)) { if(!notFound.empty()) bPrint(notFound); return(NULL); } return(victim); } } Creature* Creature::findVictim(cmd* cmnd, int cmndNo, bool aggressive, bool selfOk, bstring noVictim, bstring notFound) { return(findVictim(cmnd->str[cmndNo], cmnd->val[cmndNo], aggressive, selfOk, noVictim, notFound)); } //********************************************************************* // attack //********************************************************************* // This function allows the player pointed to by the first parameter // to attack a monster. int cmdAttack(Creature* creature, cmd* cmnd) { Creature *victim=0; Player *pVictim=0, *pPlayer=0; if(!creature->ableToDoCommand()) return(0); pPlayer = creature->getAsPlayer(); Monster* pet = creature->getAsMonster(); if(pet) { if(cmnd->num < 2) { pet->getMaster()->print("%M stops attacking.\n", pet); pet->clearEnemyList(); return(0); } } if(!(victim = creature->findVictim(cmnd, 1, true, false, "Attack what?\n", "You don't see that here.\n"))) return(0); pVictim = victim->getAsPlayer(); if(!creature->canAttack(victim)) return(0); // pet attack if(!pPlayer) { if(pet) { pet->smashInvis(); pet->getMaster()->smashInvis(); if(pet->isEnemy(victim)) { pet->getMaster()->print("%M will stop attacking %N.\n", pet, victim); pet->clearEnemy(victim); } else { creature->getMaster()->print("%M attacks %N.\n", pet, victim); broadcast(creature->getMaster()->getSock(), pet->getRoomParent(), "%M tells %N to attack %N.", pet->getMaster(), pet, victim); pet->addEnemy(victim); if(!pVictim) { // monster will attack the owner if their pet attacks ((Monster*)victim)->addEnemy(pet->getMaster()); // Activates lag protection. if(pet->getMaster()->flagIsSet(P_LAG_PROTECTION_SET)) pet->getMaster()->setFlag(P_LAG_PROTECTION_ACTIVE); } } } } else pPlayer->attackCreature(victim, ATTACK_NORMAL); return(0); } //********************************************************************* // canAttack //********************************************************************* bool Creature::canAttack(Creature* target, bool stealing) { Creature *check=0; Player *pCheck=0, *pThis = getAsPlayer(); bool holy_war=false; bstring verb = stealing ? "steal from" : "attack"; ASSERTLOG( target ); // monsters don't use this function, but pets have to obey their masters if(!pThis) { if(isPet()) { if(target == getMaster()) { getMaster()->print("Pets cannot %s their masters.\n", verb.c_str()); return(false); } return(getMaster()->canAttack(target)); } else return(true); } clearFlag(P_AFK); if( target->isPet() && target->getMaster() == this && !checkStaff("You cannot %s your pet.\n", verb.c_str()) ) return(false); // if they're trying to kill the pet, we should check the player // for PK-ability check = target->isPet() ? target->getMaster() : target; pCheck = check->getAsPlayer(); // no attacking staff, but let them attack a staff's pet if // it's currently attacking them if( check->isStaff() && cClass < check->getClass() && !(target->isPet() && ((Monster*)target)->isEnemy(this) )) { print("You are not allowed to %s %N.\n", verb.c_str(), target); return(false); } if(isCt()) return(true); // this only happens on autoattack, otherwise the findCreature would prevent us from // getting to this message. for this reason, we don't need to print a message. if(!canSee(target)) return(false); // builders got restricted attacks if(cClass == BUILDER) { if(pCheck) { print("You are not allowed to %s players.\n", verb.c_str()); return(false); } if(!flagIsSet(P_BUILDER_MOBS)) { print("You are not allowed to %s monsters.\n", verb.c_str()); return(false); } if(!pThis->checkBuilder(getUniqueRoomParent())) { print("Error: Room number not inside any of your alotted ranges.\n"); return(false); } } if(flagIsSet(P_SITTING)) { print("You can't do that when sitting. Stand up!\n"); return(false); } // we are attacking a player or a pet if(pCheck) { if(pCheck->flagIsSet(P_DIED_IN_DUEL)) { print("%M just lost a duel!\nYou can't %s ", target, verb.c_str()); if(pCheck==target) print("%s", target->himHer()); else print("%N", target); print(" right now.\n"); return(false); } if(pCheck->isEffected("petrification") && !isCt()) { print("You can't %s %N!", verb.c_str(), target); if(pCheck==target) print("%s", target->upHeShe()); else print("%M", pCheck); print("'s petrified!\n"); return(false); } // will charm stop them? if(vampireCharmed(pCheck) || (pCheck->hasCharm(getName()) && flagIsSet(P_CHARMED))) { print("You like %s too much to do that.\n", pCheck->getCName()); return(false); } else { if(target->isPlayer()) target->getAsPlayer()->delCharm(this); } // check outlawness if(pCheck->flagIsSet(P_OUTLAW)) { if(pCheck->getRoomParent()->isOutlawSafe()) { print("You cannot %s outlaws in this room.\n", verb.c_str()); return(false); } // almost everything else concerns non-outlaws } else { // you can attack pets if they are in combat, // but not their master if(getPkillInCombatDisabled() && !target->isPet() && target->inCombat(false)) { print("Not in the middle of combat.\n"); return(false); } if(getRoomParent()->isPkSafe()) { print("No %s allowed in this room.\n", stealing ? "stealing" : "killing"); return(false); } // if not dueling if(!induel(pThis, pCheck)) { if(pCheck->flagIsSet(P_NO_PKILL)) { print("You cannot %s %N right now.\n", verb.c_str(), check); return(false); } // Paladins-DKs and Enoch-Aramon are always at war holy_war = ((pCheck->getClass() == DEATHKNIGHT && cClass == PALADIN) || (cClass == DEATHKNIGHT && pCheck->getClass() == PALADIN) || (deity == LINOTHAN && pCheck->getDeity() == ARACHNUS) || (deity == ARACHNUS && pCheck->getDeity() == LINOTHAN) || (deity == ENOCH && pCheck->getDeity() == ARAMON) || (deity == ARAMON && pCheck->getDeity() == ENOCH) ); if(!holy_war && (!flagIsSet(P_PLEDGED) || !pCheck->flagIsSet(P_PLEDGED)) && ( ( pThis && !gConfig->getGuild( pThis->getGuild() ) ) || !gConfig->getGuild( pCheck->getGuild() ) ) ) { if(!flagIsSet(P_CHAOTIC)) { print("Sorry, you're lawful.\n"); return(false); } if(!pCheck->flagIsSet(P_CHAOTIC)) { print("Sorry, %N is lawful.\n", check); return(false); } } if(pCheck->getLevel() <= 5 && level >= 10) { printColor("^yPick on someone your own size!\n"); return(false); } if(pCheck->getLevel() >= 10 && level <= 5) { printColor("^yPick on someone you can kill.\n"); return(false); } } } clearFlag(P_NO_PKILL); } else { Monster* mTarget = target->getAsMonster(); // attacking a mob if( mTarget->flagIsSet(M_UNKILLABLE) && !checkStaff("You cannot %s %s.\n", stealing ? "steal from" : "harm", mTarget->himHer())) return(false); if( mTarget->flagIsSet(M_PERMENANT_MONSTER) && level < 3 && mTarget->getLevel() > level && !getRoomParent()->flagIsSet(R_ONE_PERSON_ONLY) && !mTarget->isEnemy(this) && !checkStaff("That wouldn't be very prudent at this time.\n") ) return(false); } if(!stealing && pThis && target->isPlayer()) { for(Monster* pet : target->pets) { if(pet && pet->isPet() && pet->getMaster() == target && this != target->getMaster()) { if(!pet->isEnemy(this)) { pet->addEnemy(this); broadcast(target->getSock(), getRoomParent(), "%M stands loyally before its master!", pet); } break; } } } return(true); } //********************************************************************* // getUnarmedWeaponSkill //********************************************************************* bstring Player::getUnarmedWeaponSkill() const { if(isEffected("lycanthropy")) return("claw"); std::map<bstring, PlayerClass*>::const_iterator it = gConfig->classes.find(getClassString()); if(it == gConfig->classes.end() || !(*it).second) return("bare-hand"); return((*it).second->getUnarmedWeaponSkill()); } //********************************************************************* // checkWeapon //********************************************************************* // Called multiple times during an attack - before swing, after swing, on fumble, // and in the event of our weapon shattering. If the weapon is no longer usable, // it handles the removing of the weapon, the ending of the attack (no more swings // with this weapon) and recalculating the player's attack power. void checkWeapon(Player* player, Object** weapon, bool alwaysRemove, int* loc, int* attacks, bool* wielding, bool multiWeapon, UnequipAction action = UNEQUIP_ADD_TO_INVENTORY) { // Nothing to break if(!*wielding || !weapon) return; // Not broken! if(!alwaysRemove && !player->breakObject(*weapon, *loc)) return; if(*loc != -1) { player->unequip(*loc, action); } else { // ::isDm -> Global isDm function, not class local isDm function broadcast(::isDm, "^g>>> Fumble: BadLoc (Loc:%d) %'s %s\n", *loc, player->getCName(), (*weapon)->getCName()); if(player->ready[WIELD-1] == *weapon) { player->unequip(WIELD); } else if(player->ready[HELD-1] == *weapon) { player->unequip(HELD); } } if(multiWeapon) *attacks = 1; *weapon = 0; *loc = -1; *wielding = false; player->computeAttackPower(); } //********************************************************************* // attackCreature //********************************************************************* // This function does the actual attacking. The first parameter contains // a pointer to the attacker and the second contains a pointer to the // victim. A 1 is returned if the attack results in death. int Player::attackCreature(Creature *victim, AttackType attackType) { Player *pVictim=0; Monster *mVictim=0; bool duelWield = false, wielding = false; bool multiWeapon = false; Object *weapon=0; int attacks=1, attacked=0;//, enchant=0; int loc = -1; EffectInfo* deathSickness = getEffect("death-sickness"); Damage attackDamage; long t = time(0); int drain=0, hit=0, wcdmg=0; bool glow=true; char atk[50]; if(!victim) return(0); pVictim = victim->getAsPlayer(); mVictim = victim->getAsMonster(); if(!ableToDoCommand()) return(0); if(attackType != ATTACK_BASH && attackType != ATTACK_AMBUSH && attackType != ATTACK_MAUL && attackType != ATTACK_KICK) { if(!checkAttackTimer()) return(0); updateAttackTimer(); // TODO: Make this based on the strength of the haste effect // Haste Adjustment // Base attack = 3, haste = -1 for 2 // this results in a 1/3rd reduction in speed, or 2/3rd of normal // This is fine for weapons with 3 delay..but we can have 2, 1, etc delay and // a -1 across the board causes problems, so change it to 2/3rd and 4/3 if(isEffected("frenzy") || isEffected("haste")) setAttackDelay( (int)((getAttackDelay()*2.0)/3.0)); if(isEffected("slow") && !isEffected("haste") && !isEffected("frenzy")) setAttackDelay( (int)((getAttackDelay()*4.0)/3.0)); if(deathSickness && mrand(1,100) < deathSickness->getStrength()) { printColor("^DYou cough heavily as you attack.\n"); modifyAttackDelay(10); } if(isEffected("lycanthropy")) { if(LT(this, LT_MAUL) <= t) { lasttime[LT_MAUL].ltime = t; if(isEffected("slow") && !isEffected("haste") && !isEffected("frenzy")) lasttime[LT_MAUL].interval = 4L; else lasttime[LT_MAUL].interval = 3L; } } } smashInvis(); interruptDelayedActions(); if(isBlind()) modifyAttackDelay(30); if(!pVictim && mVictim) { // Activates Lag protection. if(flagIsSet(P_LAG_PROTECTION_SET)) setFlag(P_LAG_PROTECTION_ACTIVE); if(mVictim->addEnemy(this) < 0 && attackType == ATTACK_NORMAL) { print("You attack %N.\n", mVictim); broadcast(getSock(), getRoomParent(), "%M attacks %N.", this, mVictim); } if(mVictim->flagIsSet(M_ONLY_HARMED_BY_MAGIC) && !checkStaff("Your weapon%s had no effect on %N.\n", duelWield ? "s" : "", mVictim)) { getParent()->wake("Loud noises disturb your sleep.", true); return(0); } } else if(pVictim && attackType == ATTACK_NORMAL) { pVictim->print("%M attacked you!\n", this); broadcast(getSock(), pVictim->getSock(), getRoomParent(), "%M attacked %N!", this, pVictim); } if(attackType != ATTACK_KICK && attackType != ATTACK_MAUL) { // A monk that has no weapon, no holding item, but is wearing gloves gets to use the enchant off of them if (cClass == MONK && !ready[WIELD - 1] && !ready[HELD - 1] && ready[HANDS - 1]) { //enchant = abs(ready[HANDS-1]->adjustment); weapon = NULL; wielding = false; loc = HANDS; } else if (ready[WIELD - 1]) { weapon = ready[WIELD - 1]; //enchant = abs(ready[WIELD-1]->adjustment); wielding = true; loc = WIELD; // No multiple attacks for bash if (attackType != ATTACK_BASH) { // Two attacks for duel wield if (ready[HELD - 1] && ready[HELD - 1]->getWearflag() == WIELD) { duelWield = true; attacks++; } } } } else if(attackType == ATTACK_KICK) { // kick if(ready[FEET-1]) { weapon = ready[FEET-1]; //enchant = abs(weapon->adjustment); duelWield = false; loc = FEET; } } // No numAttacks for ambush if(weapon && weapon->getNumAttacks() && attackType != ATTACK_AMBUSH && attackType != ATTACK_BASH && attackType != ATTACK_MAUL) { // Random number of attacks 1-numAttacks attacks = mrand(1, weapon->getNumAttacks()); // TODO: maybe add a flag so always certain # of attacks multiWeapon = true; // No multi attack weapons if duel wielding duelWield = false; } // While the current attack is less than how many attacks we have, keep going while(attacked++ < attacks) { // Using a do/while so we can do a break and drop to the end do { checkWeapon(this, &weapon, false, &loc, &attacks, &wielding, multiWeapon); if(wielding) { if(breakObject(weapon, loc)) { weapon = NULL; wielding = false; loc = -1; if(multiWeapon) attacks = 1; break; } } int resultFlags = 0; int altSkillLevel = -1; if(attackType == ATTACK_KICK) { // Can't fumble your boots when kicking resultFlags |= NO_FUMBLE; altSkillLevel = (int)getSkillGained("kick"); } else if(attackType == ATTACK_MAUL) { resultFlags |= NO_FUMBLE; altSkillLevel = (int)getSkillGained("maul"); } AttackResult result = getAttackResult(victim, weapon, resultFlags, altSkillLevel); // We can only fumble on the first hit of a multi weapon attack, // so if this is a fumble and NOT the first hit, change it to a normal hit if(result == ATTACK_FUMBLE && multiWeapon && attacked != 1) result = ATTACK_HIT; // Special rules for ambush if(attackType == ATTACK_AMBUSH) { if(result == ATTACK_HIT || result == ATTACK_CRITICAL || result == ATTACK_BLOCK || result == ATTACK_GLANCING) { if(!pVictim && victim->flagIsSet(M_NO_BACKSTAB)) result = ATTACK_MISS; } } statistics.swing(); if( result == ATTACK_HIT || result == ATTACK_CRITICAL || result == ATTACK_BLOCK || result == ATTACK_GLANCING ) { // move glow string into hit if so we arent glowing if we miss if(!canHit(victim, weapon, glow)) break; // only glow once glow=false; float multiplier = 1.0; if(attackType == ATTACK_AMBUSH) multiplier = 1.5; else if(attackType == ATTACK_BASH) { if(cClass == BERSERKER) multiplier = .75; else multiplier = 0.5; if(ready[SHIELD-1] && ready[SHIELD-1]->flagIsSet(O_ENHANCE_BASH)) multiplier += .1; } bool computeBonus = (!multiWeapon || (multiWeapon && attacked==1)); attackDamage.reset(); // Return of 1 means the weapon was shattered or otherwise rendered unsuable if(computeDamage(victim, weapon, attackType, result, attackDamage, computeBonus, drain, multiplier) == 1) checkWeapon(this, &weapon, true, &loc, &attacks, &wielding, multiWeapon, UNEQUIP_DELETE); if(result == ATTACK_BLOCK) { printColor("^C%M partially blocked your attack!\n", victim); victim->printColor("^CYou manage to partially block %N's attack!\n", this); } if(result == ATTACK_CRITICAL) statistics.critical(); else statistics.hit(); if(victim->isPlayer()) victim->getAsPlayer()->statistics.wasHit(); // Determine here how to handle the bonus. // If it's a multi attack weapon, divide the bonus over all of the attacks // If it's a multi attack (ambush, etc) no division of bonus // if(computeBonus) // printColor("^YInitial Bonus: %d^x ", bonus); if(!multiWeapon) { attackDamage.includeBonus(); } else { // Divide the bonus over the number of attacks. If we only have 1 attack, then they of course get the full bonus! attackDamage.includeBonus(attacks); //printColor("^yDamage: %d Bonus: %d Result: %d\n", dmg, (int)round(bonus/attacks), (int)round(dmg + (bonus/attacks))); } // How many attacks have we had with this weapon? (Used for bonus computation on multi hit weapons) // if((!multiWeapon || (multiWeapon && attacked==1))) // attackNum = 1; // else // attackNum = attacked; bool showToRoom = false; bool wasKilled = false, freeTarget = false, meKilled = false; if(attackType == ATTACK_BASH) { strcpy(atk, "bashed"); showToRoom = true; } else if(attackType == ATTACK_KICK) { strcpy(atk, "kicked"); showToRoom = true; } else if(attackType == ATTACK_MAUL) { strcpy(atk, "mauled"); showToRoom = true; } else { getDamageString(atk, this, weapon, result == ATTACK_CRITICAL ? true : false); } if(showToRoom) broadcast(getSock(), getRoomParent(), "%M %s %N.", this, atk, victim); log_immort(false,this, "%s %s %s for %d damage.\n", getCName(), atk, victim->getCName(), attackDamage.get()); printColor("You %s %N for %s%d^x damage.\n", atk, victim, customColorize("*CC:DAMAGE*").c_str(), attackDamage.get()); victim->printColor("%M %s you%s for %s%d^x damage!\n", this, atk, victim->isBrittle() ? "r brittle body" : "", victim->customColorize("*CC:DAMAGE*").c_str(), attackDamage.get()); meKilled = doReflectionDamage(attackDamage, victim); if(!meKilled && weapon && weapon->getMagicpower() && weapon->flagIsSet(O_WEAPON_CASTS) && weapon->getChargesCur() > 0) wcdmg += castWeapon(victim, weapon, wasKilled); broadcastGroup(false, victim, "^M%M^x %s ^M%N^x for *CC:DAMAGE*%d^x damage, %s%s\n", this, atk, victim, attackDamage.get()+drain, victim->heShe(), victim->getStatusStr(attackDamage.get()+drain)); statistics.attackDamage(attackDamage.get()+drain, Statistics::damageWith(this, weapon)); if(weapon && !mrand(0, 3)) weapon->decShotsCur(); checkWeapon(this, &weapon, false, &loc, &attacks, &wielding, multiWeapon); attackDamage.add(wcdmg); if(!meKilled && drain && victim->hp.getCur() - attackDamage.get() > 0) { drain = MIN(victim->hp.getCur() - attackDamage.get(), drain); printColor("Your aura of evil drains %s%d^x hit point%s from your opponent.\n", customColorize("*CC:DAMAGE*").c_str(), drain, drain == 1 ? "" : "s"); victim->printColor("^r%M drains %s%d^r hit points from you!\n", this, victim->customColorize("*CC:DAMAGE*").c_str(), drain); attackDamage.add(drain); if(!pVictim) hp.increase(drain); } if(attackType == ATTACK_BASH) { if(victim->isPlayer()) victim->stun(mrand(4,6)); else victim->stun(mrand(5,8)); checkImprove("bash", true); } else if(attackType == ATTACK_MAUL) { int dur = 0; if(!pVictim) { if(!victim->flagIsSet(M_RESIST_CIRCLE) && !victim->isUndead()) dur = MAX(3, (mrand(3,5) + (MIN(2,((::bonus(strength.getCur()) - ::bonus(victim->strength.getCur()))/2))))); else dur = mrand(2,4); } else dur = MAX(3,(mrand(3,5) + (::bonus(strength.getCur()) - ::bonus(victim->strength.getCur())))); victim->stun(dur); checkImprove("maul", true); } else if(attackType == ATTACK_AMBUSH) { checkImprove("ambush", true); } else if(attackType == ATTACK_KICK) { checkImprove("kick", true); } else if(!pVictim && mVictim) { bstring weaponSkill = ""; if(weapon) weaponSkill = weapon->getWeaponType(); else weaponSkill = getUnarmedWeaponSkill(); if(!weaponSkill.empty()) checkImprove(weaponSkill, true); } getParent()->wake("Loud noises disturb your sleep.", true); if( doDamage(victim, attackDamage.get(), CHECK_DIE, PHYSICAL_DMG, freeTarget) || wasKilled || meKilled ) { Creature::simultaneousDeath(this, victim, false, freeTarget); return(1); } } else if(result == ATTACK_MISS) { statistics.miss(); if(victim->isPlayer()) victim->getAsPlayer()->statistics.wasMissed(); if(attackType == ATTACK_AMBUSH && attacked == 1) { // If we miss on the first attack, no more attacks because ambush was detected attacked = attacks; print("Your ambush failed!\n"); checkImprove("ambush", false); broadcast(getSock(), getRoomParent(), "%s ambush was detected.", upHisHer()); setAttackDelay(getAttackDelay() * 2); break; } else if(attackType == ATTACK_BASH) { print("Your bash failed.\n"); checkImprove("bash", false); victim->print("%M tried to bash you.\n", this); broadcast(getSock(), victim->getSock(), victim->getRoomParent(), "%M tried to bash %N.", this, victim); break; } else if(attackType == ATTACK_KICK) { print("Your kick was ineffective.\n"); checkImprove("kick", false); victim->print("%M tried to kick you.\n", this); broadcast(getSock(), victim->getSock(), victim->getRoomParent(), "%M tried to kick %N.", this, victim); break; } else if(attackType == ATTACK_MAUL) { print("You failed to maul %N.\n", victim); checkImprove("maul", false); victim->print("%M tried to maul you.\n", this); broadcast(getSock(), victim->getSock(), victim->getRoomParent(), "%M tried to maul %N.", this,victim); break; } printColor("^cYou missed.\n"); victim->printColor("^c%M missed.\n", this); if(!pVictim) broadcast(getSock(), victim->getSock(), victim->getRoomParent(), "^c%M missed %N.", this, victim); // TODO: Weapons: Look at this rate of increase if(!pVictim && mVictim) { bstring weaponSkill = ""; if(weapon) weaponSkill = weapon->getWeaponType(); else weaponSkill = getUnarmedWeaponSkill(); if(!weaponSkill.empty()) checkImprove(weaponSkill, false); } } else if(result == ATTACK_DODGE) { victim->dodge(this); } else if(result == ATTACK_PARRY) { int parryResult = victim->parry(this); // check riposte death here if(parryResult == 2) { // Damn, we're dead, no more attacks then getParent()->wake("Loud noises disturb your sleep.", true); return(0); } } else if(result == ATTACK_FUMBLE) { statistics.fumble(); printColor("^gYou FUMBLED your weapon.\n"); broadcast(getSock(), getRoomParent(), "^g%M fumbled %s weapon.", this, hisHer()); checkWeapon(this, &weapon, true, &loc, &attacks, &wielding, multiWeapon); } else { printColor("^RError!!! Unhandled attack result: %d\n", result); } } while(0); // End DO // If duel wielding, weapon is now second weapon, go on for the next attack if(attacked == 1 && duelWield) { weapon = ready[HELD-1]; if(weapon) { //enchant = weapon->adjustment; wielding = true; loc = HELD; } } } // End attack loop if(hit == 1 && !pVictim) victim->flee(); getParent()->wake("Loud noises disturb your sleep.", true); return(0); } //********************************************************************* // castWeapon //********************************************************************* int Creature::castWeapon(Creature* target, Object *weapon, bool &meKilled) { Damage attackDamage; const char *spellname; osp_t *osp; int (*fn)(SpellFn), c=0; int splno=0, slvl=0; if(weapon == NULL) weapon = ready[WIELD-1]; if(weapon == NULL) return(0); if(getRoomParent()->flagIsSet(R_NO_MAGIC) || weapon->getMagicpower() < 1) return(0); splno = weapon->getMagicpower() - 1; // Do we have sufficient charges to cast? if(weapon->getChargesCur() <= 0) return(0); if(splno < 0) return(0); fn = get_spell_function(splno); spellname = get_spell_name(splno); for(c = 0; ospell[c].splno != get_spell_num(splno); c++) if(ospell[c].splno == -1) return(0); osp = (osp_t *)&ospell[c]; if((int(*)(Creature* player, cmd* cmnd, SpellData* spellData, char*, osp_t*))fn == splOffensive) { if(target->isMonster() && (get_spell_lvl(splno) > 0)) { slvl = get_spell_lvl(splno); if( (target->flagIsSet(M_NO_LEVEL_FOUR) && slvl <= 4) || (target->flagIsSet(M_NO_LEVEL_THREE) && slvl <= 3) || (target->flagIsSet(M_NO_LEVEL_TWO) && slvl <= 2) || (target->flagIsSet(M_NO_LEVEL_ONE) && slvl <= 1)) { printColor("^yYour %s's %s spell fails!\n", weapon->getCName(), spellname); weapon->decShotsCur(); return(0); } } attackDamage.set(MAX(1, osp->damage.roll())); target->modifyDamage(this, MAGICAL, attackDamage, osp->realm, weapon, -1); if(target->negAuraRepel()) { printColor("^c%M's negative aura absorbed some of the damage.\n", target); target->printColor("^cYour negative aura repels some of the damage.\n"); } printColor("Your %s casts a %s spell on %s for %s%d^x damage.\n", weapon->getCName(), spellname, target->getCName(), customColorize("*CC:DAMAGE*").c_str(), attackDamage.get()); broadcast(getSock(), target->getSock(), getRoomParent(), "%M's %s casts a %s spell on %N.", this, weapon->getCName(), spellname, target); target->printColor("^M%M's^x %s casts a %s spell on you for %s%d^x damage.\n", this, weapon->getCName(), spellname, target->customColorize("*CC:DAMAGE*").c_str(), attackDamage.get()); meKilled = doReflectionDamage(attackDamage, target); weapon->decChargesCur(); } return(attackDamage.get()); } //******************************************************************** // modifyDamage //******************************************************************** // Modifies the damage of the attack for a variety of reasons: // 1) Offguard (sitting / sleeping) // 2) Lich being brittle // 3) Undead fighting someone with undead-ward // 4) Armor spell // 5) Reflect-magic spell // 6) Magic saving throws // 7) Elemental-realm resistance spells vs magic // 8) Elemental-realm resistance spells vs pets // 9) Resist-magic spell // 10) Lich natural magic resistance // 11) Weapon-resistance spells // 12) Berserked damage reduction (player) // 13) Extra berserked damage // 14) Armor damage reduction // 15) Stoneskin spell // 16) Werewolf silver vulnerability void Creature::modifyDamage(Creature* enemy, int dmgType, Damage& attackDamage, Realm realm, Object* weapon, int saveBonus, short offguard, bool computingBonus) { Player *player = getAsPlayer(); const EffectInfo *effect = 0; int vHp = 0; dmgType = MAX(0, dmgType); // TODO: Dom: drain hp (dk) // // catch off-guard // if(enemy) { if( pFlagIsSet(P_SITTING) || pFlagIsSet(P_SLEEPING)) { if(offguard != OFFGUARD_NOPRINT && !computingBonus) { if(enemy->isPlayer()) enemy->printColor("^rYou catch %N off guard!\n", this); printColor("^r%M catches you off guard!\n", enemy); print("You %s up.\n", flagIsSet(P_SITTING) ? "stand" : "wake"); } // off-guard penalties are worse for sleeping if(flagIsSet(P_SLEEPING)) { stun(mrand(7,15)); attackDamage.set(attackDamage.get() * 2); if(offguard != OFFGUARD_NOREMOVE) wake("", true); } else { stun(mrand(3,4)); attackDamage.set(attackDamage.get() * 3 / 2); if(offguard != OFFGUARD_NOREMOVE) clearFlag(P_SITTING); } if(offguard != OFFGUARD_NOPRINT && !computingBonus) broadcast(enemy->getSock(), getSock(), getRoomParent(), "^r%M caught %N off guard!", enemy, this); } } // // always check undead-ward // if(enemy && enemy->isUndead()) { if(getRoomParent()->isEffected("unhallow")) attackDamage.set(attackDamage.get() * 3 / 2); if(getRoomParent()->isEffected("hallow")) attackDamage.set(attackDamage.get() * 2 / 3); if(isEffected("undead-ward")) attackDamage.set(attackDamage.get() / 2); } if(dmgType == NEGATIVE_ENERGY || dmgType == MAGICAL_NEGATIVE) { if(isEffected("drain-shield")) attackDamage.set(attackDamage.get() / 3); } if(dmgType == MAGICAL || dmgType == MAGICAL_NEGATIVE) { // // reflect-magic works against any magic attack // if(enemy && enemy != this && isEffected("reflect-magic")) { effect = getEffect("reflect-magic"); // the strength of the effect represents the chance to reflect the spell if(effect->getStrength() >= mrand(1,100)) { attackDamage.set(attackDamage.get() / 2); attackDamage.setReflected(attackDamage.get()); enemy->modifyDamage(0, dmgType, attackDamage, realm); } } // // spell saves // if(enemy) { // && !(enemy->isMonster() && slvl < 3) if(chkSave(SPL, enemy, saveBonus)) { attackDamage.set(attackDamage.get() / 2); if(!computingBonus) { printColor("^yYou avoided full damage!\n"); if(enemy != this) enemy->print("%M avoided full damage.\n", this); } } } // // if it's an elemental realm, check resistances // if(realm != NO_REALM) attackDamage.set(checkRealmResist(attackDamage.get(), realm)); // // modify damage for resist-magic // and lich natural resist magic // attackDamage.set(doResistMagic(attackDamage.get(), enemy)); } if(dmgType == PHYSICAL) { // // Spells to damage attacker on physical attacks. The original damage is not changed. // Don't run when computing bonus damage // if(!computingBonus && enemy && enemy != this && isEffected("fire-shield")) { effect = getEffect("fire-shield"); Realm reflectedRealm = NO_REALM; Damage reflectedDamage; // specific to fire-shield attackDamage.setPhysicalReflectedType(REFLECTED_FIRE_SHIELD); reflectedRealm = FIRE; // can be expanded to include other types reflectedDamage.set(effect->getStrength()); enemy->modifyDamage(this, MAGICAL, reflectedDamage, reflectedRealm); // physicalReflected will hurt enemy attackDamage.setPhysicalReflected(reflectedDamage.get()); // physical-shields can be reflected by reflect-magic attackDamage.setDoubleReflected(reflectedDamage.getReflected()); } // // Liches are brittle to physical attacks // if(isBrittle()) { float brittle = 0; if(level < 7) brittle = 8; else if(level < 13) brittle = 5; else if(level < 19) brittle = 3; else if(level < 25) brittle = 2.5; else brittle = 2; attackDamage.set((int)((double)attackDamage.get() + (double)attackDamage.get() / brittle)); } // // check weapon-resistance spell // // TODO: doesn't work? if(isMonster() && enemy && enemy->isPlayer()) { if(weapon) { // if it is footwear, they're kicking something and that's a blunt attack if(weapon->getWearflag() == FEET) { attackDamage.set(doWeaponResist(attackDamage.get(), "crushing")); } else { bstring category = weapon->getWeaponCategory(); if(category != "none") attackDamage.set(doWeaponResist(attackDamage.get(), category)); } } else { // TODO: Dom: ??? // we're using out fists! yarr! (or claws) if(enemy->isEffected("lycanthropy")) { attackDamage.set(doWeaponResist(attackDamage.get(), "slashing")); } else { // Bare-handed attackDamage.set(doWeaponResist(attackDamage.get(), "crushing")); } } } // // players take less damage while berserked // if(enemy && isEffected("berserk")) { // zerkers: 1/5 // everyone else: 1/7 attackDamage.set(attackDamage.get() - (attackDamage.get() / (cClass == BERSERKER ? 5 : 7))); attackDamage.set(MAX(1, attackDamage.get())); } // // monsters do more damage while berserked // if(enemy && isEffected("berserk")) attackDamage.set(attackDamage.get() * 3 / 2); // // armor damage reduction // if(enemy) { float damageReduction = enemy->getDamageReduction(this); attackDamage.set(attackDamage.get() - (int)(attackDamage.get() * damageReduction)); } // // Werewolf silver vulnerability // if(weapon && weapon->flagIsSet(O_SILVER_OBJECT) && isEffected("lycanthropy")) attackDamage.set(attackDamage.get() * 2); } // // if it's a pet, check elemental realm resistance // if(enemy) { bool resistPet=false, immunePet=false, vulnPet=false; checkResistPet(enemy, resistPet, immunePet, vulnPet); if(resistPet) attackDamage.set(attackDamage.get() / 2); if(vulnPet) attackDamage.add(mrand(MAX(1, attackDamage.get()/6),MAX(2, attackDamage.get()/2))); if(immunePet) attackDamage.set(1); } // // armor spell // if(dmgType != MENTAL && isEffected("armor")) { EffectInfo* armor = getEffect("armor"); vHp = armor->getStrength(); if(vHp <= 0 || attackDamage.get() <= 0) vHp=0; //shouldn't happen, but check anyway. vHp -= attackDamage.get(); if(vHp <= 0) { removeEffect("armor"); if(player) { printColor("^y^#Your magical armor has been dispelled.\n"); player->computeAC(); } broadcast(getSock(), getRoomParent(), "%M's magical armor has been dispelled.", this); } else { armor->setStrength(vHp); } } // // stoneskin spell // if(dmgType == PHYSICAL && isEffected("stoneskin")) { EffectInfo* stoneskin = getEffect("stoneskin"); vHp = stoneskin->getStrength(); if(vHp <= 0 || attackDamage.get() <= 0) vHp=0; //shouldn't happen, but check anyway. vHp--; if(vHp <= 0) { removeEffect("stoneskin"); printColor("^g^#Your stoneskin has been dispelled.\n"); broadcast(getSock(), getRoomParent(), "%M's stoneskin has been depleted.", this); } else { stoneskin->setStrength(vHp); } // Stoneskin absorbs 50% damage. attackDamage.set(attackDamage.get() / 2); } attackDamage.set(MAX(0, attackDamage.get())); // check drain last if(dmgType == NEGATIVE_ENERGY || dmgType == MAGICAL_NEGATIVE) { if(canBeDrained()) { if(dmgType == NEGATIVE_ENERGY) attackDamage.setDrain(attackDamage.get() / 2); else attackDamage.setDrain(mrand(0, attackDamage.get() / 4)); // don't drain more than can be drained! attackDamage.setDrain(MIN(attackDamage.getDrain(), hp.getCur())); // liches can't use drain damage as their HP is their MP // they can do more damage instead if(enemy && enemy->getClass() == LICH) { attackDamage.add(attackDamage.getDrain()); attackDamage.setDrain(0); } } } } //********************************************************************* // canBeDrained //********************************************************************* bool Creature::canBeDrained() const { if(isUndead()) return(false); if(isEffected("drain-shield")) return(false); if(monType::noLivingVulnerabilities(type)) return(false); return(true); } //********************************************************************* // doWeaponResist //********************************************************************* int Creature::doWeaponResist(int dmg, bstring weaponCategory) const { if(isEffected("resist-" + weaponCategory)) { dmg /= 2; } if(isEffected("immune-" + weaponCategory)) { dmg = 1; } if(isEffected("vuln-" + weaponCategory)) { dmg = dmg * 3 / 2; } return(dmg); } //********************************************************************* // willAggro //********************************************************************* bool Monster::willAggro(const Player *player) const { // sometimes we should never attack a player, no matter what if(!player || !canSee(player) || player->isStaff()) return(false); if(isEnemy(player)) return(false); if(player->isUnconscious() || player->isEffected("petrification")) return(false); if(isUndead() && player->isEffected("undead-ward")) return(false); if(flagIsSet(M_UNKILLABLE)) return(false); // swimming fish won't attack flying people // check for water, too //if(area_room && player->isEffected("fly") && !isEffected("fly")) // return(false); // under these circumstances, always attack if(flagIsSet(M_ALWAYS_AGGRESSIVE)) return(true); if( primeFaction != "" && !flagIsSet(M_NO_FACTION_AGGRO) && Faction::willAggro(player, primeFaction) ) return(true); // sometimes we might want to be nice to people if(isUndead()) { if(player->isUndead()) return(false); if(player->getClass() == CLERIC && player->getDeity() == ARAMON) return(false); } if(player->getClan() && clan && player->getClan() == clan) return(false); if(player->isEffected("lycanthropy") && isEffected("lycanthropy")) return(false); if(type == ARACHNID && player->getClass() == CLERIC && player->getDeity() == ARAMON) return(false); if(player->isEffected("vampirism") && isEffected("vampirism")) return(false); // will they aggro anyone based on some simple flags that are set? if(flagIsSet(M_AGGRESSIVE)) return(true); if(flagIsSet(M_AGGRESSIVE_GOOD) && player->getAdjustedAlignment() > NEUTRAL) return(true); if(flagIsSet(M_AGGRESSIVE_EVIL) && player->getAdjustedAlignment() < NEUTRAL) return(true); // will they aggro anyone based on their race? std::map<int, RaceData*>::iterator rIt; RaceData* rData=0; for(rIt = gConfig->races.begin() ; rIt != gConfig->races.end() ; rIt++) { rData = (*rIt).second; if(player->isRace(rData->getId()) && isRaceAggro(rData->getId(), true)) return(true); } // will they aggro anyone based on their class? for(int i=1; i<STAFF; i++) { if((player->getClass() == i || player->getSecondClass() == i) && isClassAggro(i, true)) return(true); } // will they aggro anyone based on their deity? std::map<int, DeityData*>::iterator dIt; DeityData* dData=0; for(dIt = gConfig->deities.begin() ; dIt != gConfig->deities.end() ; dIt++) { dData = (*dIt).second; if(player->getDeity() == dData->getId() && isDeityAggro(dData->getId(), true)) return(true); } return(false); } //********************************************************************* // whoToAggro //********************************************************************* Player* Monster::whoToAggro() const { std::list<Player*> players; std::list<Player*>::iterator it; Player* player=0; int total=0, pick=0; const BaseRoom* myRoom = getConstRoomParent(); if(!myRoom) { broadcast(::isDm, "^g *** Monster '%s' has no room in whoToAggro!", getCName()); return(0); } for(Player* ply : myRoom->players) { if(canSee(ply) && !ply->flagIsSet(P_HIDDEN)) { player = ply->getAsPlayer(); if(willAggro(player)) { total += MAX(1, 300 - player->piety.getCur()); players.push_back(player); } } } if(players.empty()) return(0); if(players.size() == 1) return(players.front()); // we now have a list of players we want to aggro; use some criteria to // decide which of them we hate the most // this logic is currently copied from old mordor lowest peity algorithm: // higher piety = lower chance of being picked pick = mrand(1, total); total = 0; for(it = players.begin() ; it != players.end() ; it++) { player = (*it); total += MAX(1, 300 - player->piety.getCur()); if(total >= pick) return(player); } return(0); }