/*
* specials.cpp
* Special attacks and such
* ____ _
* | _ \ ___ __ _| |_ __ ___ ___
* | |_) / _ \/ _` | | '_ ` _ \/ __|
* | _ < __/ (_| | | | | | | \__ \
* |_| \_\___|\__,_|_|_| |_| |_|___/
*
* Permission to use, modify and distribute is granted via the
* Creative Commons - Attribution - Non Commercial - Share Alike 3.0 License
* http://creativecommons.org/licenses/by-nc-sa/3.0/
*
* Copyright (C) 2007-2012 Jason Mitchell, Randi Mitchell
* Contributions by Tim Callahan, Jonathan Hseu
* Based on Mordor (C) Brooke Paul, Brett J. Vickers, John P. Freeman
*
*/
#include "mud.h"
#include "commands.h"
#include "effects.h"
#include "specials.h"
bool Creature::runSpecialAttacks(Creature* victim) {
if(specials.empty())
return(false);
SpecialAttack* attack;
std::list<SpecialAttack*>::const_iterator eIt;
for(eIt = specials.begin() ; eIt != specials.end() ; eIt++) {
attack = *eIt;
// Try running untill we get one that procs
if(useSpecial(attack, victim))
return(true);
}
// If we get here, we couldn't find any specials to run
return(false);
}
SpecialAttack* Creature::getSpecial(const bstring& special) {
std::list<SpecialAttack*>::const_iterator eIt;
for(eIt = specials.begin() ; eIt != specials.end() ; eIt++) {
if((*eIt) && (*eIt)->name == special)
return((*eIt));
}
return(NULL);
}
bool Creature::useSpecial(const bstring& special, Creature* victim) {
return(useSpecial(getSpecial(special), victim));
}
bool Creature::useSpecial(SpecialAttack* attack, Creature* victim) {
if(!attack)
return(false);
if(attack->flagIsSet(SA_REQUIRE_HIDE) && !isHidden())
return(false);
long i, t;
i = attack->ltime.ltime;
t = time(0);
// See if we can use this attack yet
if(t - i < attack->delay)
return(false); // Not enough time has passed yet
// Check random chance that this attack goes off -- Chance of 101 means always go off
if(attack->chance < mrand(1, 100))
return(false);
// Check time here
if(attack->limit) {
if(attack->used >= attack->limit)
return(false);
}
if(attack->flagIsSet(SA_REQUIRE_HIDE) && isHidden())
unhide();
attack->ltime.ltime = time(0);
attack->ltime.interval = attack->delay;
if(!attack->isAreaAttack() && !attack->flagIsSet(SA_SINGLE_TARGET))
attack->setFlag(SA_SINGLE_TARGET);
if(attack->flagIsSet(SA_SINGLE_TARGET)) {
return(doSpecial(attack, victim));
}
bool attacked = false;
if(attack->isAreaAttack() && getRoomParent()) {
attack->printRoomString(this);
BaseRoom* room = getRoomParent();
if(attack->flagIsSet(SA_AE_PLAYER) || attack->flagIsSet(SA_AE_ALL)) {
// First hit players
PlayerSet::iterator pIt = room->players.begin();
while(pIt != room->players.end()) {
Player* ply = (*pIt++);
doSpecial(attack, ply);
attacked = true;
}
// Second, hit pets
MonsterSet::iterator mIt = room->monsters.begin();
while(mIt != room->monsters.end()) {
Monster* mons = (*mIt++);
if(mons->isPet()) {
doSpecial(attack, mons);
attacked = true;
}
}
}
if(attack->flagIsSet(SA_AE_MONSTER) || attack->flagIsSet(SA_AE_ALL)) {
// Hit all non pets
MonsterSet::iterator mIt = room->monsters.begin();
while(mIt != room->monsters.end()) {
Monster* mons = (*mIt++);
if(!mons->isPet()) {
doSpecial(attack, mons);
attacked = true;
}
}
}
}
if(attacked && attack->limit)
attack->used++;
return(attacked);
}
// Run the given special on a target, should only be called from useSpecial
bool Creature::doSpecial(SpecialAttack* attack, Creature* victim) {
Player* pVictim = victim->getAsPlayer();
Monster* mThis = getAsMonster();
if(victim->isMonster() && victim->flagIsSet(M_NO_CIRCLE) && attack->getName() == "circle")
return(false);
if(attack->flagIsSet(SA_NO_UNDEAD) && victim->isUndead())
return(false);
if(attack->flagIsSet(SA_BREATHING_TARGETS) && victim->doesntBreathe())
return(false);
if(attack->flagIsSet(SA_UNDEAD_ONLY) && !victim->isUndead())
return(false);
if(attack->flagIsSet(SA_NO_UNDEAD_WARD) && isUndead() && victim->isEffected("undead-ward"))
return(false);
if(attack->flagIsSet(SA_NO_DRAIN_SHIELD) && victim->isEffected("drain-shield"))
return(false);
if(attack->flagIsSet(SA_NO_ATTACK_ON_LOW_HP) && hp.getCur() <= (hp.getMax()/5))
return(false);
if( attack->flagIsSet(SA_NO_UNCONSCIOUS) &&
pVictim && (
pVictim->isEffected("blindness") ||
pVictim->isEffected("petrification") ||
pVictim->flagIsSet(P_UNCONSCIOUS)
)
)
return(false);
if(attack->flagIsSet(SA_HOLY_WAR)) {
if(cClass != PALADIN && cClass != DEATHKNIGHT)
return(false);
if((cClass == PALADIN && victim->getClass() != DEATHKNIGHT) ||
(cClass == DEATHKNIGHT && victim->getClass() != PALADIN))
return(false);
}
if(attack->flagIsSet(SA_TARGET_NEEDS_MANA) && victim->mp.getCur() <= 0)
return(false);
if(attack->flagIsSet(SA_SINGLE_TARGET)) {
if(attack->limit)
attack->used++;
}
victim->checkTarget(this);
if(attack->type == SPECIAL_WEAPON) {
// General attack, so compute an attack result
int resultFlags = NO_CRITICAL | NO_FUMBLE; // We'll be nice for now...no critical backstabs on mobs :)
if(attack->flagIsSet(SA_NO_DODGE))
resultFlags |= NO_DODGE;
if(attack->flagIsSet(SA_NO_PARRY))
resultFlags |= NO_PARRY;
if(attack->flagIsSet(SA_NO_BLOCK))
resultFlags |= NO_BLOCK;
AttackResult result = getAttackResult(victim, NULL, resultFlags);
switch(result) {
case ATTACK_DODGE:
attack->printFailStrings(this, victim);
victim->dodge(this);
victim->doLagProtect();
return(true);
case ATTACK_PARRY:
attack->printFailStrings(this, victim);
victim->parry(this);
victim->doLagProtect();
return(true);
case ATTACK_MISS:
attack->printFailStrings(this, victim);
victim->doLagProtect();
return(true);
case ATTACK_BLOCK:
printColor("^C%M partially blocked your attack!\n", victim);
victim->printColor("^CYou manage to partially block %N's attack!\n", this);
break;
default:
// Hits etc, do nothing, fall through to the rest of the code
break;
}
}
Damage attackDamage;
attackDamage.set(-1);
bool saved = false;
// Some attacks don't do any damage
if(attack->saveType > SAVE_NONE && attack->saveType <= SAVE_SPELL) {
int bns = 0;
switch(attack->saveBonus) {
case BONUS_LEVEL_INT:
bns = 10*((int)victim->getLevel() - (int)level)+ 3*bonus(victim->intelligence.getCur());
break;
case BONUS_LEVEL_CON:
bns = 10*((int)victim->getLevel() - (int)level)+ 3*bonus(victim->constitution.getCur());
break;
case BONUS_LEVEL_DEX:
bns = 10*((int)victim->getLevel() - (int)level)+ 3*bonus(victim->dexterity.getCur());
default:
break;
}
int maxBonus = attack->maxBonus;
if(maxBonus == -1)
maxBonus = 0;
else if(maxBonus == 0)
maxBonus = 100;
bns = MAX(0,MIN(bns,maxBonus));
// Save types are saves + 1 to account for NO_SAVE
if(victim->chkSave(attack->saveType - 1, this, bns))
saved = true;
} else {
int chance = 50;
switch(attack->saveType) {
case SAVE_STRENGTH:
break;
case SAVE_DEXTERITY:
chance = 50 + (level - victim->getLevel()) * 10 +
(dexterity.getCur() - victim->dexterity.getCur())/5 ;
break;
case SAVE_CONSTITUTION:
break;
case SAVE_INTELLIGENCE:
break;
case SAVE_PIETY:
break;
case SAVE_LEVEL:
chance = 35 + ((level - victim->getLevel()) * 20);
break;
default:
break;
}
//chance -= attack->saveBonus;
chance = MAX(1, MIN(90, chance));
// Chance is chance for the attacker to succeed...so invert the check to see if the victim saved
if(mrand(1, 100) > chance)
saved = true;
}
// Can't be confused with insight, and always confused with feelblemind
if(attack->type == SPECIAL_CONFUSE) {
if(victim->isEffected("insight"))
saved = true;
else if(victim->isEffected("feeblemind"))
saved = false;
}
if(saved) {
attack->printRoomSaveString(this, victim);
attack->printTargetSaveString(this, victim);
// This flag can be used on attacks like circle where nothing happens when they save vs dex
if(attack->flagIsSet(SA_SAVE_NO_DAMAGE)) {
victim->doLagProtect();
return(true); // The attack went off, but got saved
}
}
if(attack->type == SPECIAL_EXP_DRAIN) {
attackDamage.set(attack->damage.roll());
attackDamage.set(MIN((unsigned)attackDamage.get(), victim->getExperience()));
if(saved == true)
attackDamage.set(attackDamage.get() / 2);
}
else if(attack->type == SPECIAL_PETRIFY) {
if(pVictim) {
if((pVictim->isEffected("resist-earth") || pVictim->isEffected("resist-magic")) && mrand(1,100) <= 50)
return(false);
if(!saved) {
pVictim->addEffect("petrification", 0, 0, 0, true, this);
pVictim->clearAsEnemy(); // clears player from all mob's enemy lists in the room.
pVictim->removeFromGroup(false);
}
}
}
else if(attack->type == SPECIAL_CONFUSE) {
if(pVictim->isEffected("resist-magic") && mrand(1,100) <= 75)
return(false);
}
else if(attack->type == SPECIAL_STEAL) {
if(pVictim && !saved)
mThis->steal(pVictim);
}
else if(attack->type != SPECIAL_NO_DAMAGE) {
// Compute damage
if(attack->flagIsSet(SA_HALF_HP_DAMAGE))
attackDamage.set(victim->hp.getCur() / 2);
else if(attack->flagIsSet(SA_RESIST_MAGIC_NO_DAMAGE) && victim->isEffected("resist-magic"))
attackDamage.set(0);
else {
int dn = attack->damage.getNumber();
int ds = attack->damage.getSides();
int dp = attack->damage.getPlus();
if(attack->flagIsSet(SA_INTELLIGENCE_REDUCE)) {
if(victim->intelligence.getCur() < 210)
; // no reduction
else if(victim->intelligence.getCur() < 240)
dn -= 1; // slight reduction
else
dn -= 2; // more reduction!
// Can't reduce it past 1
dn = MAX(dn, 1);
}
attackDamage.set(dice(dn, ds, dp));
}
// Now check for a save to adjust damage
if(saved == true)
attackDamage.set(attackDamage.get() / 2);
// Now check for realm resists, item disolving, etc
if(attack->type != SPECIAL_NO_TYPE) {
// Is the attack elemental damage?
if(attack->type >= SPECIAL_EARTH && attack->type <= SPECIAL_COLD) {
// SPECIAL-realm matches up with the corresponding Realm so we can just
// pass that, after casting it to a Realm
attackDamage.set(victim->checkRealmResist(attackDamage.get(), (Realm)attack->type));
}
// TODO: Check for warmth/heat-protection on elemental attacks
// if( pVictim && (
// (realm == COLD && (pVictim->isEffected("warmth") || pVictim->isEffected("alwayscold"))) ||
// (realm == FIRE && pVictim->isEffected("heat-protection") || pVictim->isEffected("alwayswarm"))
// ) )
}
if(attack->flagIsSet(SA_EARTH_SHIELD_REDUCE))
attackDamage.set(attackDamage.get() / 2);
// More adjustments
if(attack->type == SPECIAL_WEAPON || attack->flagIsSet(SA_CHECK_PHYSICAL_DAMAGE))
victim->modifyDamage(this, PHYSICAL, attackDamage);
else if(attack->flagIsSet(SA_CHECK_NEGATIVE_ENERGY))
victim->modifyDamage(this, NEGATIVE_ENERGY, attackDamage);
if(attack->flagIsSet(SA_BERSERK_REDUCE))
attackDamage.set(mrand(1, 10));
// Put here to avoid other damage reducers
if(!saved && attack->flagIsSet(SA_CAN_DISINTEGRATE) && mrand(1,100) < 2) {
victim->printColor("^Y%M seriously damages you!\n", this);
attackDamage.set(victim->hp.getCur() - 5);
attackDamage.set(MAX(attackDamage.get(), 1));
}
}
if(pVictim && !isPet()) {
if(attack->flagIsSet(SA_ACID)) {
// Check for disolve
if(attack->saveType == SAVE_NONE || !pVictim->chkSave(attack->saveType-1, this, attack->saveBonus))
pVictim->loseAcid();
}
if(attack->flagIsSet(SA_DISSOLVE)) {
if(attack->saveType == SAVE_NONE || !pVictim->chkSave(attack->saveType-1, this,attack->saveBonus))
pVictim->dissolveItem(this);
}
}
if(mThis) {
if(attack->flagIsSet(SA_POISON)) {
// Check for poison
mThis->tryToPoison(victim, attack);
}
if(attack->flagIsSet(SA_DISEASE)) {
// Check for disease
mThis->tryToDisease(victim, attack);
}
if(attack->flagIsSet(SA_BLIND)) {
// Check for blind
mThis->tryToBlind(victim, attack);
}
if(attack->flagIsSet(SA_ZAP_MANA)) {
mThis->zapMp(victim, attack);
}
}
if(attack->flagIsSet(SA_SINGLE_TARGET))
attack->printRoomString(this, victim);
if(attackDamage.get() != -1) {
attack->printTargetString(this, victim, attackDamage.get());
if(attack->flagIsSet(SA_DRAINS_DAMAGE)) {
victim->printColor("^r%M feeds on your energy.\n", this);
hp.increase(attackDamage.get() / 2);
}
if(attack->type == SPECIAL_EXP_DRAIN) {
// Damage is experience, not hp
victim->addExperience(attackDamage.get() * -1);
} else {
if(isPet() && getMaster())
getMaster()->printColor("%M %s %N for %s%d^x damage.\n", this, attack->verb.c_str(), victim, getMaster()->customColorize("*CC:DAMAGE*").c_str(), attackDamage.get());
broadcastGroup(false, victim, "^M%M^x %s ^M%N^x for *CC:DAMAGE*%d^x damage, %s%s\n", this, attack->verb.c_str(),
victim, attackDamage.get(), victim->heShe(), victim->getStatusStr(attackDamage.get()));
if(attack->flagIsSet(SA_CHECK_DIE_ROB)) {
if(doDamage(victim, attackDamage.get(), CHECK_DIE_ROB))
return(true);
} else {
if(doDamage(victim, attackDamage.get(), CHECK_DIE))
return(true);
}
}
// TODO: Dom: make a flag for this?
// 5% chance to get porphyria when bitten by a vampire
if(attack->getName() == "Vampiric Bite")
victim->addPorphyria(this, 5);
} else {
attack->printTargetString(this, victim);
}
// Stun the target if they didn't save
if(attack->stunLength && !saved) {
int stunLength = 0;
if(attack->flagIsSet(SA_RANDOMIZE_STUN)) {
stunLength = mrand(attack->stunLength - 3, attack->stunLength + 3);
stunLength = MAX(1, stunLength);
} else
stunLength = attack->stunLength;
victim->print("You have been stunned for %d seconds!\n", stunLength);
victim->stun(stunLength);
}
victim->doLagProtect();
return(true);
}
bool SpecialAttack::isAreaAttack() const {
return(flagIsSet(SA_AE_ENEMY) || flagIsSet(SA_AE_ALL) || flagIsSet(SA_AE_PLAYER) || flagIsSet(SA_AE_MONSTER));
}
bool SpecialAttack::flagIsSet(int flag) const {
return(flags[flag/8] & 1<<(flag%8));
}
void SpecialAttack::setFlag(int flag) {
flags[flag/8] |= 1<<(flag%8);
}
void SpecialAttack::clearFlag(int flag) {
flags[flag/8] &= ~(1<<(flag%8));
}
bstring SpecialAttack::modifyAttackString(const bstring& input, Creature* viewer, Creature* attacker, Creature* target, int dmg) {
bstring toReturn = input;
/* 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.
*/
toReturn.Replace("*ATTACKER*", attacker->getCrtStr(viewer, CAP).c_str());
toReturn.Replace("*LOW-ATTACKER*", attacker->getCrtStr(viewer).c_str());
toReturn.Replace("*A-DEITY*", gConfig->getDeity(attacker->getDeity())->getName().c_str());
toReturn.Replace("*A-HESHE*", attacker->heShe());
toReturn.Replace("*A-HISHER*", attacker->hisHer());
toReturn.Replace("*A-UPHESHE*", attacker->upHeShe());
toReturn.Replace("*A-UPHISHER*", attacker->upHisHer());
if(target) {
toReturn.Replace("*T-HESHE*", target->heShe());
toReturn.Replace("*T-HISHER*", target->hisHer());
toReturn.Replace("*T-UPHESHE*", target->upHeShe());
toReturn.Replace("*T-UPHISHER*", target->upHisHer());
toReturn.Replace("*TARGET*", target->getCrtStr(viewer, CAP).c_str());
toReturn.Replace("*VICTIM*", target->getCrtStr(viewer, CAP).c_str());
toReturn.Replace("*LOW-TARGET*", target->getCrtStr(viewer).c_str());
toReturn.Replace("*LOW-VICTIM*", target->getCrtStr(viewer).c_str());
}
toReturn.Replace("*CR*", "\n");
if(dmg != -1)
toReturn.Replace("*DAMAGE*", xml::numToStr(dmg).c_str());
toReturn = viewer->customColorize(toReturn);
return(toReturn);
}
void SpecialAttack::printToRoom(BaseRoom* room, const bstring& str, Creature* attacker, Creature* target, int dmg ) {
if(str.empty())
return;
bstring toPrint = "";
for(Player* ply : room->players) {
if(!ply || ply == target)
continue;
toPrint = modifyAttackString(str, ply, attacker, target, dmg);
ply->printColor("%s\n", toPrint.c_str());
}
}
void SpecialAttack::printFailStrings(Creature* attacker, Creature* target) {
printToRoom(target->getRoomParent(), roomFailStr, attacker, target);
bstring toPrint = modifyAttackString(targetFailStr, target, attacker, target);
target->printColor("%s\n", toPrint.c_str());
return;
}
void SpecialAttack::printRoomString(Creature* attacker, Creature* target) {
return(printToRoom(attacker->getRoomParent(), roomStr, attacker, target));
}
void SpecialAttack::printTargetString(Creature* attacker, Creature* target, int dmg) {
bstring toPrint = modifyAttackString(targetStr, target, attacker, target, dmg);
target->printColor("%s\n", toPrint.c_str());
return;
}
void SpecialAttack::printRoomSaveString(Creature* attacker, Creature* target) {
return(printToRoom(attacker->getRoomParent(), roomSaveStr, attacker, target));
}
void SpecialAttack::printTargetSaveString(Creature* attacker, Creature* target, int dmg) {
bstring toPrint = modifyAttackString(targetSaveStr, target, attacker, target, dmg);
target->printColor("%s\n", toPrint.c_str());
return;
}
void SpecialAttack::reset() {
delay = chance = stunLength = limit = used = maxBonus = 0;
type = SPECIAL_NO_TYPE;
saveType = SAVE_NONE;
saveBonus = BONUS_NONE;
ltime.interval = ltime.ltime = ltime.misc = 0;
int i;
for(i = 0 ; i < 8 ; i++)
flags[i] = 0;
return;
}
bstring Creature::getSpecialsFullList() const {
std::ostringstream specialsStr;
specialsStr << "Special Attacks for " << getName() << ":\n";
int num = 0;
SpecialAttack* attack;
std::list<SpecialAttack*>::const_iterator sIt;
for(sIt = specials.begin() ; sIt != specials.end() ; sIt++) {
attack = (*sIt);
specialsStr << *attack;
num++;
}
specialsStr << "\n" << num << " effect(s) found.\n";
bstring toPrint = specialsStr.str();
return(toPrint);
}
int dmSpecials(Player* player, cmd* cmnd) {
Creature* target=0;
Monster* mTarget=0;
if(player->getClass() == BUILDER && !player->canBuildMonsters())
return(cmdNoAuth(player));
if(cmnd->num > 1) {
target = player->getParent()->findCreature(player, cmnd);
if(target && player->getClass() == BUILDER) {
mTarget = target->getAsMonster();
if(mTarget) {
if(mTarget->info.id && !player->checkBuilder(mTarget->info)) {
player->print("Error: monster index not in any of your alotted ranges.\n");
return(0);
}
}
}
if(!target) {
if(player->isCt()) {
cmnd->str[1][0] = up(cmnd->str[1][0]);
target = gServer->findPlayer(cmnd->str[1]);
}
if(!target || !player->canSee(target)) {
player->print("Target not found.\n");
return(0);
}
}
} else {
player->print("Target not found.\n");
return(0);
}
if(cmnd->num > 2) {
if(!strncmp(cmnd->str[2], "-d", 2)) {
target->delSpecials();
player->print("Deleted all specials for %N\n", target);
return(0);
}
SpecialAttack* attack;
/* 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(!(attack = target->addSpecial(cmnd->str[2]))) {
player->printColor("The special ^W%s^x does not exist!\n", cmnd->str[2]);
player->print("Pick from this list:\n");
player->print(" backstab bash\n");
player->print(" kick bite\n");
player->print(" circle smother\n");
player->print(" fire-breath lightning-breath\n");
player->print(" acid-breath frost-cone\n");
player->print(" gas-breath wind-blast\n");
player->print(" psychic-blast energy-drain\n");
player->print(" touch-of-death turn\n");
player->print(" renounce tail-slap\n");
player->print(" trample poisonous-sting\n");
player->print(" petrifying-gaze petrifying-breath\n");
player->print(" confusing-gaze zap-mana\n");
player->print(" death-gaze\n");
return(0);
} else {
player->printColor("Added special ^W%s^x to ^W%M^x.\n", attack->getName().c_str(), target);
return(0);
}
} else {
bstring toPrint = target->getSpecialsFullList();
player->printColor("%s\n", toPrint.c_str());
}
return(0);
}
bool Creature::delSpecials() {
for(SpecialAttack* special : specials) {
delete special;
}
specials.clear();
return(true);
}
std::ostream& operator<<(std::ostream& out, const SpecialAttack& attack) {
//out.setf(std::ios::right, std::ios::adjustfield);
out << "Special Attack:\n";
out << "Name: " << attack.name << "\tType: " << attack.type << "\n";
if(!attack.targetStr.empty())
out << "TargetStr: " << attack.targetStr << "\n";
if(!attack.targetFailStr.empty())
out << "TargetFailStr: " << attack.targetFailStr << "\n";
if(!attack.selfStr.empty())
out << "SelfStr: " << attack.selfStr << "\n";
if(!attack.roomStr.empty())
out << "RoomStr: " << attack.roomStr << "\n";
if(!attack.roomFailStr.empty())
out << "RoomFailStr: " << attack.roomFailStr << "\n";
out << "SaveType: ";
switch(attack.saveType) {
case SAVE_NONE:
out << "None";
break;
case SAVE_LUCK:
out << "Luck";
break;
case SAVE_POISON:
out << "Poison";
break;
case SAVE_DEATH:
out << "Death";
break;
case SAVE_BREATH:
out << "Breath";
break;
case SAVE_MENTAL:
out << "Mental";
break;
case SAVE_SPELL:
out << "Spell";
break;
default:
break;
}
out << "\tSaveBonus: " << attack.saveBonus << "\n";
if(!attack.targetSaveStr.empty())
out << "TargetSaveStr: " << attack.targetSaveStr << "\n";
if(!attack.selfSaveStr.empty())
out << "SelfSaveStr: " << attack.selfSaveStr << "\n";
if(!attack.roomSaveStr.empty())
out << "RoomSaveStr: " << attack.roomSaveStr << "\n";
out << "Chance: " << attack.chance << "%\t Delay: " << attack.delay << "\n"
<< "StunLength: " << attack.stunLength << "\t Used/Limit: " << attack.used << "/" << attack.limit << "\n";
out << "Damage: " << attack.damage.str()
<< " (Low: " << attack.damage.low() << " High: " << attack.damage.high()
<< " Avg: " << attack.damage.average() << ")\n"
<< "Flags: ";
int i, num = 0;
for(i = SA_NO_FLAG + 1; i < SA_MAX_FLAG ; i++) {
if(attack.flagIsSet(i)) {
if(num != 0)
out << ", ";
out << gConfig->getSpecialFlag(i) << "(" << i << ")";
num++;
}
}
if(num == 0)
out << "None";
out << ".\n";
out << "\n";
return out;
}
bstring Creature::getSpecialsList() const {
std::ostringstream specialStr;
specialStr << "Special Attacks: ";
int num = 0;
std::list<SpecialAttack*>::const_iterator aIt;
SpecialAttack* attack;
for(aIt = specials.begin() ; aIt != specials.end() ; aIt++) {
attack = (*aIt);
if(num != 0)
specialStr << ", ";
specialStr << attack->name;
num++;
}
if(num == 0)
specialStr << "None";
specialStr << ".\n";
bstring toPrint = specialStr.str();
return(toPrint);
}
SpecialAttack::SpecialAttack() {
reset();
}
SpecialAttack::SpecialAttack(xmlNodePtr rootNode) {
xmlNodePtr curNode = rootNode->children;
reset();
while(curNode) {
if(NODE_NAME(curNode, "Name")) xml::copyToBString(name, curNode);
else if(NODE_NAME(curNode, "Verb")) xml::copyToBString(verb, curNode);
else if(NODE_NAME(curNode, "TargetStr")) xml::copyToBString(targetStr, curNode);
else if(NODE_NAME(curNode, "TargetFailStr")) xml::copyToBString(targetFailStr, curNode);
else if(NODE_NAME(curNode, "RoomFailStr")) xml::copyToBString(roomFailStr, curNode);
else if(NODE_NAME(curNode, "SelfStr")) xml::copyToBString(selfStr, curNode);
else if(NODE_NAME(curNode, "RoomStr")) xml::copyToBString(roomStr, curNode);
else if(NODE_NAME(curNode, "TargetSaveStr")) xml::copyToBString(targetSaveStr, curNode);
else if(NODE_NAME(curNode, "SelfSaveStr")) xml::copyToBString(selfSaveStr, curNode);
else if(NODE_NAME(curNode, "RoomSaveStr")) xml::copyToBString(roomSaveStr, curNode);
else if(NODE_NAME(curNode, "Delay")) xml::copyToNum(delay, curNode);
else if(NODE_NAME(curNode, "Chance")) xml::copyToNum(chance, curNode);
else if(NODE_NAME(curNode, "StunLength")) xml::copyToNum(stunLength, curNode);
else if(NODE_NAME(curNode, "Limit")) xml::copyToNum(limit, curNode);
else if(NODE_NAME(curNode, "MaxBonus")) xml::copyToNum(maxBonus, curNode);
else if(NODE_NAME(curNode, "SaveType")) saveType = (SpecialSaveType)xml::toNum<int>(xml::getCString(curNode));
else if(NODE_NAME(curNode, "SaveBonus")) saveBonus = (SaveBonus)xml::toNum<int>(xml::getCString(curNode));
else if(NODE_NAME(curNode, "Type")) type = (SpecialType)xml::toNum<int>(xml::getCString(curNode));
else if(NODE_NAME(curNode, "Flags")) loadBits(curNode, flags);
else if(NODE_NAME(curNode, "LastTime")) loadLastTime(curNode, <ime);
else if(NODE_NAME(curNode, "Dice")) damage.load(curNode);
curNode = curNode->next;
}
}
bool SpecialAttack::save(xmlNodePtr rootNode) const {
xmlNodePtr attackNode = xml::newStringChild(rootNode, "SpecialAttack");
xml::newStringChild(attackNode, "Name", name);
xml::newStringChild(attackNode, "Verb", verb);
xml::newStringChild(attackNode, "TargetStr", targetStr);
xml::newStringChild(attackNode, "TargetFailStr", targetFailStr);
xml::newStringChild(attackNode, "RoomFailStr", roomFailStr);
xml::newStringChild(attackNode, "SelfStr", selfStr);
xml::newStringChild(attackNode, "RoomStr", roomStr);
xml::newStringChild(attackNode, "TargetSaveStr", targetSaveStr);
xml::newStringChild(attackNode, "SelfSaveStr", selfSaveStr);
xml::newStringChild(attackNode, "RoomSaveStr", roomSaveStr);
xml::newNumChild(attackNode, "Limit", limit);
xml::newNumChild(attackNode, "Delay", delay);
xml::newNumChild(attackNode, "Chance", chance);
xml::newNumChild(attackNode, "StunLength", stunLength);
xml::newNumChild(attackNode, "Type", type);
xml::newNumChild(attackNode, "SaveType", saveType);
xml::newNumChild(attackNode, "SaveBonus", saveBonus);
xml::newNumChild(attackNode, "MaxBonus", maxBonus);
saveBits(attackNode, "Flags", SA_MAX_FLAG, flags);
saveLastTime(attackNode, 0, ltime);
damage.save(attackNode, "Dice");
return(true);
}
SpecialAttack* Creature::addSpecial(const bstring specialName) {
SpecialAttack* attack=0;
/* 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(specialName == "backstab") {
attack = new SpecialAttack();
attack->name = "Backstab";
attack->verb = "backstabbed";
attack->type = SPECIAL_WEAPON;
attack->targetStr = "^r*ATTACKER* attempts to backstab you!*CR**ATTACKER* backstabs you for ^R*DAMAGE*^r damage!^x";
attack->targetFailStr = "^c*ATTACKER* attempts to backstab you.*CR**A-UPHISHER* backstab failed!^x";
attack->roomStr = "^r*ATTACKER* backstabs *LOW-VICTIM*!^x";
attack->roomFailStr = "^c*ATTACKER*'s backstab missed!^x";
attack->chance = 101; // Always goes off
attack->setFlag(SA_SINGLE_TARGET);
attack->setFlag(SA_REQUIRE_HIDE);
attack->setFlag(SA_NO_PARRY); // Being stabbed from behind...can't parry that
attack->setFlag(SA_NO_BLOCK);
attack->setFlag(SA_CHECK_DIE_ROB);
attack->damage.setNumber(damage.getNumber() * 3);
attack->damage.setSides(damage.getSides());
attack->damage.setPlus(damage.getPlus() * 3);
// No need for a delay here because the attack requires hide
specials.push_back(attack);
} else if(specialName == "bash") {
attack = new SpecialAttack();
attack->name = "Bash";
attack->verb = "bashed";
attack->type = SPECIAL_WEAPON;
attack->targetStr = "^r*ATTACKER* BASHES you for ^R*DAMAGE*^r damage.^x";
attack->roomStr = "^r*ATTACKER* BASHES *LOW-TARGET*.^x";
attack->targetSaveStr = attack->targetFailStr = "^c*ATTACKER* tried to bash you.^x";
attack->roomSaveStr = attack->roomFailStr = "^c*ATTACKER* tried to bash *LOW-TARGET*!^x";
attack->chance = 101; // Always goes off
attack->stunLength = 5;
attack->saveType = SAVE_DEXTERITY;
attack->setFlag(SA_SAVE_NO_DAMAGE);
attack->setFlag(SA_RANDOMIZE_STUN);
attack->setFlag(SA_SINGLE_TARGET);
attack->setFlag(SA_CHECK_DIE_ROB);
attack->damage.setNumber(damage.getNumber()/2);
attack->damage.setSides(damage.getSides());
attack->damage.setPlus(damage.getPlus()/2);
attack->limit = 1; // Can only perform this attack once
specials.push_back(attack);
} else if(specialName == "kick") {
attack = new SpecialAttack();
attack->name = "Kick";
attack->verb = "kicked";
attack->type = SPECIAL_WEAPON;
attack->targetStr = "^r*ATTACKER* kicks you for ^R*DAMAGE*^r damage.^x";
attack->roomStr = "^r*ATTACKER* kicks *LOW-TARGET*.^x";
attack->targetSaveStr = attack->targetFailStr = "^c*ATTACKER* tried to kick you.^x";
attack->roomSaveStr = attack->roomFailStr = "^c*ATTACKER* tried to kick *LOW-TARGET*!^x";
attack->saveType = SAVE_DEXTERITY;
attack->chance = 101; // Always goes off
attack->delay = 12;
attack->setFlag(SA_SINGLE_TARGET);
attack->setFlag(SA_CHECK_DIE_ROB);
attack->setFlag(SA_UNDEAD_WARD_REDUCE);
attack->setFlag(SA_SAVE_NO_DAMAGE);
attack->damage.setNumber(2);
attack->damage.setSides(3);
attack->damage.setPlus(bonus(strength.getCur()) + cClass == FIGHTER ? level / 4 : 0);
// if(flagIsSet(OLD_M_TRAMPLE)) {
// attack->dice[0] *= 2;
// attack->dice[2] *= 2;
// }
specials.push_back(attack);
} else if(specialName == "bite") {
attack = new SpecialAttack();
if(isEffected("vampirism")) {
attack->name = "Vampiric Bite";
attack->setFlag(SA_NIGHT_ONLY);
attack->setFlag(SA_NO_UNDEAD);
attack->saveType = SAVE_DEATH;
// Vampires bite for more!
attack->damage.setNumber(level);
attack->damage.setSides(3);
attack->damage.setPlus(level);
attack->targetSaveStr = "^rYou manage to shove *LOW-ATTACKER* off of you.^x";
attack->roomSaveStr = "*TARGET* shoves *LOW-ATTACKER* away, interrupting *A-HISHER* bloody feast!";
} else {
attack->name = "Bite";
attack->saveType = SAVE_LEVEL;
attack->damage.setNumber(level);
attack->damage.setSides(2);
attack->damage.setPlus(level);
// Using level save as old chance for sucess, so no damage on sucessful save
attack->setFlag(SA_SAVE_NO_DAMAGE);
}
attack->verb = "bit";
attack->chance = 101; // Always goes off
// Kinda a weapon attack
attack->type = SPECIAL_WEAPON;
attack->delay = 30;
attack->stunLength = 5;
attack->setFlag(SA_RANDOMIZE_STUN);
attack->setFlag(SA_SINGLE_TARGET);
attack->targetStr = "^r*ATTACKER* bites you for ^R*DAMAGE*^r damage.^x";
attack->roomStr = "^r*ATTACKER* bites *LOW-TARGET*!^x";
attack->targetFailStr = "^r*ATTACKER* tried to bite you.^x";
attack->roomFailStr = "^r*ATTACKER* tried to bite *LOW-TARGET*.^x";
// Can be dodged, or parried, but not blocked
attack->setFlag(SA_NO_BLOCK);
specials.push_back(attack);
} else if(specialName == "circle") {
attack = new SpecialAttack();
attack->name = "Circle";
attack->verb = "circled";
attack->type = SPECIAL_NO_DAMAGE;
attack->stunLength = 7;
attack->delay = 10;
attack->chance = 25;
attack->saveType = SAVE_DEXTERITY;
attack->saveBonus = BONUS_LEVEL_DEX;
attack->setFlag(SA_SAVE_NO_DAMAGE);
attack->setFlag(SA_RANDOMIZE_STUN);
attack->setFlag(SA_SINGLE_TARGET);
attack->targetStr = "^r*ATTACKER* circles you!^x";
attack->roomStr = "^r*ATTACKER* circles *LOW-TARGET*!^x";
attack->targetSaveStr = "^c*ATTACKER* tried to circle you.^x";
attack->roomSaveStr = "^c*ATTACKER* tried to circle *LOW-TARGET*!^x";
specials.push_back(attack);
} else if(specialName == "smother") {
attack = new SpecialAttack();
attack->name = "Earth Smother";
attack->verb = "smothered";
attack->type = SPECIAL_EARTH;
attack->saveType = SAVE_BREATH;
attack->saveBonus = BONUS_LEVEL_CON;
attack->setFlag(SA_SINGLE_TARGET);
attack->chance = 25;
attack->delay = 60;
attack->targetStr = "^Y*ATTACKER* smothers you for *DAMAGE* damage.^x";
attack->roomStr = "^Y*ATTACKER* smothers *LOW-TARGET*.^x";
attack->targetSaveStr = "^cYou almost avoid the pressing earth.^x";
attack->roomSaveStr = "*TARGET* almost avoids the pressing earth.";
attack->damage.setNumber(level);
attack->damage.setSides(4);
attack->damage.setPlus(0);
specials.push_back(attack);
} else if(specialName == "fire-breath") {
attack = new SpecialAttack();
attack->name = "Fire Breath";
attack->verb = "breathes fire on";
attack->type = SPECIAL_FIRE;
attack->saveType = SAVE_BREATH;
attack->targetStr = "^r*ATTACKER* breathes fire on you!*CR*You take ^R*DAMAGE*^r damage!^x";
attack->roomStr = "^r*ATTACKER* breathes fire on *LOW-TARGET*!^x";
attack->targetSaveStr = "^cYou almost manage to dive out of the way.^x";
attack->roomSaveStr = "*TARGET* almost dives out of the way.";
attack->damage.setNumber(level);
attack->damage.setSides(4);
attack->damage.setPlus(0);
attack->setFlag(SA_SINGLE_TARGET);
attack->delay = 60;
attack->chance = 20; // 20 percent chance to go off every time
specials.push_back(attack);
} else if(specialName == "lightning-breath") {
attack = new SpecialAttack();
attack->name = "Lightning Breath";
attack->verb = "breathes lightning on";
attack->type = SPECIAL_ELECTRIC;
attack->saveType = SAVE_BREATH;
attack->targetStr = "^W*ATTACKER* breathes lightning on you!*CR*You take ^R*DAMAGE*^W damage!^x";
attack->roomStr = "^W*ATTACKER* breathes lightning on *LOW-TARGET*!^x";
attack->targetSaveStr = "^cYou almost manage to dive out of the way.^x";
attack->roomSaveStr = "*TARGET* almost dives out of the way.";
attack->damage.setNumber(level);
attack->damage.setSides(4);
attack->damage.setPlus(0);
attack->setFlag(SA_SINGLE_TARGET);
attack->delay = 60;
attack->chance = 20; // 20 percent chance to go off every time
specials.push_back(attack);
} else if(specialName == "acid-breath") {
attack = new SpecialAttack();
attack->name = "Acid Breath";
attack->verb = "breathed acid on";
attack->type = SPECIAL_BREATH;
attack->saveType = SAVE_BREATH;
attack->targetStr = "^g*ATTACKER* spits acid on you!*CR*You take ^G*DAMAGE*^g damage!^x";
attack->roomStr = "^g*ATTACKER* spits acid on *LOW-TARGET*!^x";
attack->targetSaveStr = "You dove to the side, only being partially hit!";
attack->roomSaveStr = "*TARGET* dove to the ground, only being partially immersed.";
attack->damage.setNumber(level);
attack->damage.setSides(3);
attack->damage.setPlus(0);
attack->setFlag(SA_SINGLE_TARGET);
attack->setFlag(SA_ACID);
attack->delay = 60;
attack->chance = 20; // 20 percent chance to go off every time
specials.push_back(attack);
} else if(specialName == "frost-cone") {
attack = new SpecialAttack();
attack->name = "Frost Cone";
attack->verb = "breathed frost on";
attack->type = SPECIAL_COLD;
attack->saveType = SAVE_BREATH;
attack->targetStr = "^b*ATTACKER* breathes frost on you!*CR*You take ^B*DAMAGE*^b damage!^x";
attack->roomStr = "^b*ATTACKER* breathes frost on *LOW-TARGET*!^x";
attack->targetSaveStr = "^cYou almost manage to dive out of the way.^x";
attack->roomSaveStr = "*TARGET* almost dives out of the way.";
attack->damage.setNumber(level);
attack->damage.setSides(4);
attack->damage.setPlus(0);
attack->setFlag(SA_SINGLE_TARGET);
attack->delay = 60;
attack->chance = 20; // 20 percent chance to go off every time
specials.push_back(attack);
} else if(specialName == "gas-breath") {
attack = new SpecialAttack();
attack->name = "Gas Breath";
attack->verb = "breathed gas on";
attack->type = SPECIAL_BREATH;
attack->saveType = SAVE_POISON;
attack->targetStr = "^G*ATTACKER* breathes poisonous gas on you!*CR*You take ^g*DAMAGE*^G damage!^x";
attack->roomStr = "^G*ATTACKER*'s poisonous breath fills the room!^x";
attack->targetSaveStr = "^cYou manage to hold your breath, coughing erratically.^x";
attack->damage.setNumber(level);
attack->damage.setSides(2);
attack->damage.setPlus(1);
attack->delay = 60;
attack->setFlag(SA_AE_PLAYER);
attack->setFlag(SA_POISON);
attack->setFlag(SA_BREATHING_TARGETS);
attack->chance = 20; // 20 percent chance to go off every time
specials.push_back(attack);
} else if(specialName == "wind-blast") {
attack = new SpecialAttack();
attack->name = "Wind Blast";
attack->verb = "breathes a blast of turbulent wind at";
attack->type = SPECIAL_WIND;
attack->saveType = SAVE_BREATH;
attack->targetStr = "^y*ATTACKER* breathes a blast of turbulent wind at you!*CR*You take ^Y*DAMAGE*^y damage!^x";
attack->roomStr = "^y*ATTACKER* breathes a blast of turbulent wind at *LOW-TARGET*!^x";
attack->targetSaveStr = "^cYou almost manage to dive out of the way.^x";
attack->roomSaveStr = "*TARGET* almost dives out of the way.";
attack->damage.setNumber(level);
attack->damage.setSides(4);
attack->damage.setPlus(0);
attack->setFlag(SA_SINGLE_TARGET);
attack->delay = 60;
attack->chance = 20; // 20 percent chance to go off every time
} else if(specialName == "psychic-blast") {
attack = new SpecialAttack();
attack->name = "Psychic Blast";
attack->verb = "blasts psychic energy at";
attack->type = SPECIAL_GENERAL;
attack->saveType = SAVE_MENTAL;
attack->saveBonus = BONUS_LEVEL_INT;
attack->targetStr = "^C*ATTACKER* blasts you with psychic energy!*CR*You take ^c*DAMAGE*^C damage!^x";
attack->roomStr = "^C*ATTACKER* blasts *LOW-TARGET* with psychic energy^x";
attack->targetSaveStr = "^cYou fight to lock your mind, avoiding the brunt of the attack.^x";
attack->damage.setNumber(level);
attack->damage.setSides(4);
attack->damage.setPlus(0);
attack->delay = 60;
attack->chance = 20; // 20 percent chance to go off every time
attack->setFlag(SA_SINGLE_TARGET);
attack->setFlag(SA_INTELLIGENCE_REDUCE);
attack->setFlag(SA_BERSERK_REDUCE);
attack->setFlag(SA_UNDEAD_WARD_REDUCE);
specials.push_back(attack);
} else if(specialName == "energy-drain") {
attack = new SpecialAttack();
attack->name = "Energy Drain";
attack->verb = "drains";
attack->type = SPECIAL_EXP_DRAIN;
attack->saveType = SAVE_DEATH;
attack->targetStr = "^M*ATTACKER* drains your life energy!*CR*You lose *CC:DAMAGE**DAMAGE*^M experience!^x";
attack->roomStr = "^M*ATTACKER* drains *LOW-TARGET*'s life energy!^x";
attack->targetSaveStr = "^c*ATTACKER* only partially drains your life energy.^x";
attack->damage.setNumber(level);
attack->damage.setSides(10);
attack->damage.setPlus(level*10);
attack->delay = 60;
attack->chance = 10; // 10 percent chance to go off every time
attack->setFlag(SA_SINGLE_TARGET);
attack->setFlag(SA_NO_UNDEAD_WARD);
attack->setFlag(SA_NO_DRAIN_SHIELD);
specials.push_back(attack);
} else if(specialName == "touch-of-death") {
attack = new SpecialAttack();
attack->name = "Touch of Death";
attack->verb = "used the touch of death on";
// Weapon like attack
attack->type = SPECIAL_WEAPON;
attack->saveType = SAVE_DEATH;
attack->saveBonus = BONUS_LEVEL_CON;
attack->limit = 1;
attack->chance = 25;
attack->targetStr = "^r*ATTACKER* used the touch of death on you!^x";
attack->roomStr = "*ATTACKER* used the touch of death on *LOW-TARGET*.";
attack->targetFailStr = "^c*ATTACKER* failed the touch of death on you.^x";
attack->roomFailStr = "*ATTACKER* failed the touch of death on *LOW-TARGET*.";
attack->targetSaveStr = "^R*ATTACKER*'s touch of death was only a glancing blow!^x";
attack->setFlag(SA_HALF_HP_DAMAGE);
attack->setFlag(SA_SINGLE_TARGET);
attack->setFlag(SA_NO_UNDEAD_WARD);
attack->setFlag(SA_NO_UNDEAD);
specials.push_back(attack);
} else if(specialName == "turn") {
attack = new SpecialAttack();
attack->name = "Turn";
attack->verb = "turns";
attack->type = SPECIAL_GENERAL;
attack->saveType = SAVE_LEVEL;
attack->delay = 30;
attack->chance = 101;
attack->targetStr = "^r*ATTACKER* turns you!^x";
attack->roomStr = "*ATTACKER* turns *LOW-TARGET*.";
attack->targetSaveStr = "^c*ATTACKER* failed to turn you.^x";
attack->roomSaveStr = "*ATTACKER* failed to turn *LOW-TARGET*.";
attack->setFlag(SA_HALF_HP_DAMAGE);
attack->setFlag(SA_CAN_DISINTEGRATE);
attack->setFlag(SA_SINGLE_TARGET);
attack->setFlag(SA_NO_UNDEAD_WARD);
attack->setFlag(SA_UNDEAD_ONLY);
attack->setFlag(SA_SAVE_NO_DAMAGE);
specials.push_back(attack);
} else if(specialName == "renounce") {
attack = new SpecialAttack();
attack->name = "Renounce";
attack->verb = "renounces";
attack->type = SPECIAL_GENERAL;
attack->saveType = SAVE_DEATH;
attack->saveBonus = BONUS_LEVEL_CON;
attack->delay = 60;
attack->chance = 101;
attack->targetStr = "^r*ATTACKER* renounced you for *CC:DAMAGE**DAMAGE*^r damage!^x";
attack->roomStr = "*ATTACKER* renounced *LOW-TARGET* with the power of *A-DEITY*!";
attack->targetSaveStr = "^c*ATTACKER* failed to renounce you with the power of *A-DEITY*.^x";
attack->roomSaveStr = "*ATTACKER* tried to renounced *LOW-TARGET* with the power of *A-DEITY*.";
attack->setFlag(SA_HALF_HP_DAMAGE);
attack->setFlag(SA_CAN_DISINTEGRATE);
attack->setFlag(SA_SINGLE_TARGET);
attack->setFlag(SA_HOLY_WAR);
attack->setFlag(SA_SAVE_NO_DAMAGE);
specials.push_back(attack);
} else if(specialName == "tail-slap") {
attack = new SpecialAttack();
attack->name = "Tail Slap";
attack->verb = "tail-slapped";
// Weapon like attack
attack->type = SPECIAL_WEAPON;
attack->saveType = SAVE_LUCK; // Chance to reduce damage
attack->setFlag(SA_NO_ATTACK_ON_LOW_HP);
attack->setFlag(SA_SINGLE_TARGET);
attack->chance = 101;
attack->delay = 20;
attack->damage.setNumber(1);
attack->damage.setSides(level/2);
attack->damage.setPlus(level);
attack->targetStr = "^r*ATTACKER* tail-slapped you for ^R*DAMAGE*^r damage.^x";
attack->roomStr = "^R*ATTACKER* tail-slaps *LOW-TARGET*.^x";
attack->targetFailStr = "^c*ATTACKER* tried to slap you with *A-HISHER* tail!";
attack->roomFailStr = "*ATTACKER* tried to slap *LOW-TARGET* with *A-HISHER* tail.";
attack->setFlag(SA_NO_UNDEAD_WARD);
attack->setFlag(SA_CHECK_DIE_ROB);
attack->setFlag(SA_RANDOMIZE_STUN);
attack->stunLength = 6;
specials.push_back(attack);
} else if(specialName == "trample") {
attack = new SpecialAttack();
attack->name = "Trample";
attack->verb = "trampled";
// Use weapon logic, dodge & ripsote included
attack->type = SPECIAL_WEAPON;
attack->setFlag(SA_NO_BLOCK);
attack->setFlag(SA_SINGLE_TARGET);
attack->saveType = SAVE_LUCK; // Chance to reduce damage
attack->setFlag(SA_NO_UNDEAD_WARD);
attack->setFlag(SA_CHECK_DIE_ROB);
attack->setFlag(SA_RANDOMIZE_STUN);
attack->stunLength = 5;
attack->delay = 90;
attack->chance = 25;
attack->damage.setNumber(damage.getNumber() * 2);
attack->damage.setSides(damage.getSides());
attack->damage.setPlus(damage.getPlus() * 2);
attack->targetStr = "^r*ATTACKER* tramples you for ^R*DAMAGE*^r damage!^x";
attack->roomStr = "^R*ATTACKER* trampled *LOW-VICTIM*!^x";
attack->targetFailStr = "^c*ATTACKER* almost trampled you.^x";
attack->roomFailStr = "*ATTACKER* almost trampled *LOW-VICTIM*.";
specials.push_back(attack);
} else if(specialName == "poisonous-sting") {
attack = new SpecialAttack();
attack->name = "Poisonous Sting";
attack->verb = "stung";
// Use weapon logic, dodge & ripsote included
attack->type = SPECIAL_WEAPON;
attack->saveType = SAVE_LUCK; // Chance to reduce damage
attack->setFlag(SA_NO_BLOCK);
attack->setFlag(SA_SINGLE_TARGET);
attack->setFlag(SA_NO_UNDEAD_WARD);
attack->setFlag(SA_CHECK_DIE_ROB);
attack->setFlag(SA_RANDOMIZE_STUN);
attack->setFlag(SA_POISON);
attack->stunLength = 4;
attack->damage.setNumber(damage.getNumber() * 3 / 2);
attack->damage.setSides(damage.getSides());
attack->damage.setPlus(damage.getPlus() * 3 / 2);
attack->chance = 33;
attack->targetStr = "^r*ATTACKER* stings you for ^R*DAMAGE*^r damage!^x";
attack->roomStr = "^R*ATTACKER* stung *LOW-VICTIM*!^x";
attack->targetFailStr = "^c*ATTACKER* almost stung you.^x";
attack->roomFailStr = "*ATTACKER* almost stung *LOW-VICTIM*.";
specials.push_back(attack);
} else if(specialName == "petrifying-gaze") {
attack = new SpecialAttack();
attack->name = "Petrifying Gaze";
attack->type = SPECIAL_PETRIFY;
attack->saveType = SAVE_DEATH;
attack->saveBonus = BONUS_LEVEL_CON;
attack->maxBonus = 60;
attack->delay = 30;
attack->chance = intelligence.getCur()/5;
attack->targetStr = "^D*ATTACKER* locks *A-HISHER* gaze on you.*CR**A-UPHISHER* gaze turned you to stone!^x";
attack->roomStr = "*ATTACKER* petrified *LOW-TARGET* with *A-HISHER* gaze!";
attack->targetSaveStr = "^D*ATTACKER* locks *A-HISHER* gaze on you.*CR*^cYou avoided *ATTACKER*'s petrifying gaze.^x";
attack->roomSaveStr = "^c*ATTACKER* tried to petrify *LOW-TARGET* with *A-HISHER* gaze.^x";
attack->setFlag(SA_SINGLE_TARGET);
attack->setFlag(SA_NO_UNDEAD_WARD);
specials.push_back(attack);
} else if(specialName == "petrifying-breath") {
attack = new SpecialAttack();
attack->name = "Petrifying Breath";
attack->type = SPECIAL_PETRIFY;
attack->saveType = SAVE_BREATH;
attack->saveBonus = BONUS_LEVEL_DEX;
attack->maxBonus = 75;
attack->chance = 20; // 20 percent chance to go off every time
attack->delay = 60;
attack->targetStr = "^Y*ATTACKER* breathes a cloud of choking smoke at you!^x";
attack->roomStr = "^Y*ATTACKER*'s cloud of choking smoke fills the room!^x";
attack->targetSaveStr = "^cYou managed to avoid *LOW-ATTACKER*'s smoking breath.^x";
attack->roomSaveStr = "*TARGET* managed to avoid *LOW-ATTACKER*'s smoking breath.";
attack->setFlag(SA_AE_PLAYER);
specials.push_back(attack);
} else if(specialName == "confusing-gaze") {
attack = new SpecialAttack();
attack->name = "Confusing Gaze";
attack->type = SPECIAL_CONFUSE;
attack->saveType = SAVE_MENTAL;
attack->saveBonus = BONUS_LEVEL_CON;
attack->targetStr = "^g%*ATTACKER*'s enigmatic gaze clouds your mind.^x";
attack->roomStr = "*ATTACKER* fixes *LOW-TARGET* with *A-HISHER* enigmatic gaze.";
attack->targetSaveStr = "^cYou avoided *LOW-ATTACKER*'s enigmatic gaze.^x";
attack->roomSaveStr = "*ATTACKER* tried to confuse *LOW-TARGET* with *A-HISHER* gaze.";
attack->delay = 30;
attack->chance = intelligence.getCur()/5;
attack->setFlag(SA_SINGLE_TARGET);
attack->setFlag(SA_NO_UNCONSCIOUS);
specials.push_back(attack);
} else if(specialName == "zap-mana") {
attack = new SpecialAttack();
attack->name = "Zap Mana";
attack->type = SPECIAL_NO_DAMAGE;
attack->setFlag(SA_SINGLE_TARGET);
attack->chance = intelligence.getCur()/10;
specials.push_back(attack);
} else if(specialName == "death-gaze") {
attack = new SpecialAttack();
attack->name = "Death Gaze";
attack->saveType = SAVE_DEATH;
attack->saveBonus = BONUS_LEVEL_CON;
attack->targetStr = "^m*ATTACKER* locks *A-HISHER* gaze on you.*CR**A-HISHER*'s deadly gaze drains your life.^x";
attack->roomStr = "*ATTACKER* drains *LOW-TARGET*'s life with *A-HISHER* gaze!";
attack->targetSaveStr = "You avoided *LOW-ATTACKER*'s deadly gaze.";
attack->roomSaveStr = "*ATTACKER* tried to kill *LOW-TARGET* with *A-HISHER* gaze.";
attack->setFlag(SA_NO_UNCONSCIOUS);
attack->setFlag(SA_SINGLE_TARGET);
attack->setFlag(SA_HALF_HP_DAMAGE);
attack->delay = 30;
attack->chance = intelligence.getCur()/5;
specials.push_back(attack);
} else {
std::cout << "Unknown special name - " << specialName << "." << std::endl;
}
return(attack);
}
bstring SpecialAttack::getName() {
return(name);
}