/*
* magic.cpp
* Additional spell-casting 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 "magic.h"
// Spells with -1 mp will not check mp and will leave it up to
// the spell function to do this check.
// The order of this array is important! "splno" must equal the
// spot of the struct in the array. The mud will check this on
// startup and will crash if it's not properly defined
struct {
const char *splstr;
int splno;
int (*splfn)(SpellFn);
int mp;
SchoolOfMagic school;
} spllist[] = {
// Name Spell Num Spell Function Mana School of Magic
{ "vigor", S_VIGOR, splVigor, 2, HEALING },
{ "zephyr", S_ZEPHYR, (int (*)(SpellFn))splOffensive, -1, EVOCATION },
{ "infravision", S_INFRAVISION, splInfravision, -1, TRANSMUTATION },
{ "slow-poison", S_SLOW_POISON, splSlowPoison, 8, HEALING },
{ "bless", S_BLESS, splBless, 10, ABJURATION },
{ "protection", S_PROTECTION, splProtection, 10, ABJURATION },
{ "fireball", S_FIREBALL, (int (*)(SpellFn))splOffensive, -1, EVOCATION },
{ "invisibility", S_INVISIBILITY, splInvisibility, 15, ILLUSION },
{ "restore", S_RESTORE, splRestore, -1, HEALING },
{ "detect-invisibility",S_DETECT_INVISIBILITY, splDetectInvisibility, 10, DIVINATION },
{ "detect-magic", S_DETECT_MAGIC, splDetectMagic, 10, DIVINATION },
{ "teleport", S_TELEPORT, splTeleport, -1, TRANSLOCATION },
{ "stun", S_STUN, splStun, 10, ENCHANTMENT },
{ "cyclone", S_CYCLONE, (int (*)(SpellFn))splOffensive, -1, EVOCATION },
{ "atrophy", S_ATROPHY, (int (*)(SpellFn))splOffensive, -1, EVOCATION },
{ "enchant", S_ENCHANT, splEnchant, 25, ENCHANTMENT },
{ "word-of-recall", S_WORD_OF_RECALL, splWordOfRecall, 25, TRANSLOCATION },
{ "summon", S_SUMMON, splSummon, 28, TRANSLOCATION },
{ "mend-wounds", S_MEND_WOUNDS, splMendWounds, 4, HEALING },
{ "heal", S_HEAL, splHeal, 20, HEALING },
{ "track", S_TRACK, splTrack, 13, TRANSLOCATION },
{ "levitate", S_LEVITATE, splLevitate, 10, TRANSMUTATION },
{ "heat-protection", S_HEAT_PROTECTION, splHeatProtection, 12, ABJURATION },
{ "fly", S_FLY, splFly, 15, TRANSMUTATION },
{ "resist-magic", S_RESIST_MAGIC, splResistMagic, 25, ABJURATION },
{ "whirlwind", S_WHIRLWIND, (int (*)(SpellFn))splOffensive, -1, EVOCATION },
{ "rumble", S_RUMBLE, (int (*)(SpellFn))splOffensive, -1, EVOCATION },
{ "burn", S_BURN, (int (*)(SpellFn))splOffensive, -1, EVOCATION },
{ "blister", S_BLISTER, (int (*)(SpellFn))splOffensive, -1, EVOCATION },
{ "dustgust", S_DUSTGUST, (int (*)(SpellFn))splOffensive, -1, EVOCATION },
{ "waterbolt", S_WATERBOLT, (int (*)(SpellFn))splOffensive, -1, EVOCATION },
{ "crush", S_CRUSH, (int (*)(SpellFn))splOffensive, -1, EVOCATION },
{ "shatterstone", S_ENGULF, (int (*)(SpellFn))splOffensive, -1, EVOCATION },
{ "burstflame", S_BURSTFLAME, (int (*)(SpellFn))splOffensive, -1, EVOCATION },
{ "steamblast", S_STEAMBLAST, (int (*)(SpellFn))splOffensive, -1, EVOCATION },
{ "engulf", S_SHATTERSTONE, (int (*)(SpellFn))splOffensive, -1, EVOCATION },
{ "immolate", S_IMMOLATE, (int (*)(SpellFn))splOffensive, -1, EVOCATION },
{ "bloodboil", S_BLOODBOIL, (int (*)(SpellFn))splOffensive, -1, EVOCATION },
{ "tempest", S_TEMPEST, (int (*)(SpellFn))splOffensive, -1, EVOCATION },
{ "earth-tremor", S_EARTH_TREMOR, (int (*)(SpellFn))splOffensive, -1, EVOCATION },
{ "flamefill", S_FLAMEFILL, (int (*)(SpellFn))splOffensive, -1, EVOCATION },
{ "know-aura", S_KNOW_AURA, splKnowAura, 6, DIVINATION },
{ "remove-curse", S_REMOVE_CURSE, splRemoveCurse, 18, ABJURATION },
{ "resist-cold", S_RESIST_COLD, splResistCold, 13, ABJURATION },
{ "breathe-water", S_BREATHE_WATER, splBreatheWater, 12, TRANSMUTATION },
{ "earth-shield", S_STONE_SHIELD, splEarthShield, 12, ABJURATION },
{ "clairvoyance", S_CLAIRVOYANCE, splClairvoyance, 15, DIVINATION },
{ "drain-exp", S_DRAIN_EXP, drain_exp, -1, NECROMANCY },
{ "cure-disease", S_CURE_DISEASE, splCureDisease, 12, HEALING },
{ "cure-blindness", S_CURE_BLINDNESS, splCureBlindness, 15, HEALING },
{ "fear", S_FEAR, splFear, 15, ENCHANTMENT },
{ "room-vigor", S_ROOM_VIGOR, splRoomVigor, 8, HEALING },
{ "transport", S_TRANSPORT, splTransport, -1, TRANSLOCATION },
{ "blind", S_BLINDNESS, splBlind, 20, TRANSMUTATION },
{ "silence", S_SILENCE, splSilence, -1, ENCHANTMENT },
{ "fortune", S_FORTUNE, splFortune, 12, DIVINATION },
{ "curse", S_CURSE, splCurse, 25, ENCHANTMENT },
{ "earthquake", S_EARTHQUAKE, (int (*)(SpellFn))splMultiOffensive,-1, EVOCATION },
{ "tsunami", S_FLOOD, (int (*)(SpellFn))splMultiOffensive,-1, EVOCATION },
{ "inferno", S_FIRESTORM, (int (*)(SpellFn))splMultiOffensive,-1, EVOCATION },
{ "hurricane", S_HURRICANE, (int (*)(SpellFn))splMultiOffensive,-1, EVOCATION },
{ "conjure", S_CONJURE, conjure, -1, CONJURATION },
{ "plane-shift", S_PLANE_SHIFT, splPlaneShift, -1, TRANSLOCATION },
{ "judgement", S_JUDGEMENT, splJudgement, -1, NO_SCHOOL },
{ "resist-water", S_RESIST_WATER, splResistWater, 13, ABJURATION },
{ "resist-fire", S_RESIST_FIRE, splResistFire, 13, ABJURATION },
{ "resist-air", S_RESIST_AIR, splResistAir, 13, ABJURATION },
{ "resist-earth", S_RESIST_EARTH, splResistEarth, 13, ABJURATION },
{ "reflect-magic", S_REFLECT_MAGIC, splReflectMagic, 30, ABJURATION },
{ "animate-dead", S_ANIMIATE_DEAD, animate_dead, -1, NECROMANCY },
{ "annul-magic", S_ANNUL_MAGIC, splAnnulMagic, 30, ABJURATION },
{ "true-sight", S_TRUE_SIGHT, splTrueSight, 18, DIVINATION },
{ "remove-fear", S_REMOVE_FEAR, splRemoveFear, 10, ABJURATION },
{ "remove-silence", S_REMOVE_SILENCE, splRemoveSilence, 15, ABJURATION },
{ "camouflage", S_CAMOUFLAGE, splCamouflage, 15, ILLUSION },
{ "ethereal-travel", S_ETHREAL_TRAVEL, splEtherealTravel, 31, TRANSLOCATION },
{ "drain-shield", S_DRAIN_SHIELD, splDrainShield, 15, ABJURATION },
{ "dispel-evil", S_DISPEL_EVIL, splDispelEvil, 25, ABJURATION },
{ "dispel-good", S_DISPEL_GOOD, splDispelGood, 25, ABJURATION },
{ "undead-ward", S_UNDEAD_WARD, splUndeadWard, 25, ABJURATION },
{ "zap", S_ZAP, (int (*)(SpellFn))splOffensive, -1, EVOCATION },
{ "lightning-bolt", S_LIGHTNING_BOLT, (int (*)(SpellFn))splOffensive, -1, EVOCATION },
{ "shockbolt", S_SHOCKBOLT, (int (*)(SpellFn))splOffensive, -1, EVOCATION },
{ "electrocution", S_ELECTROCUTE, (int (*)(SpellFn))splOffensive, -1, EVOCATION },
{ "thunderbolt", S_THUNDERBOLT, (int (*)(SpellFn))splOffensive, -1, EVOCATION },
{ "chain-lightning", S_CHAIN_LIGHTNING, (int (*)(SpellFn))splMultiOffensive,-1, EVOCATION },
{ "chill", S_CHILL, (int (*)(SpellFn))splOffensive, -1, EVOCATION },
{ "frostbite", S_FROSTBITE, (int (*)(SpellFn))splOffensive, -1, EVOCATION },
{ "sleet", S_SLEET, (int (*)(SpellFn))splOffensive, -1, EVOCATION },
{ "cold-cone", S_COLD_CONE, (int (*)(SpellFn))splOffensive, -1, EVOCATION },
{ "iceblade", S_ICEBLADE, (int (*)(SpellFn))splOffensive, -1, EVOCATION },
{ "blizzard", S_BLIZZARD, (int (*)(SpellFn))splMultiOffensive,-1, EVOCATION },
{ "resist-electric", S_RESIST_ELEC, splResistLightning, 13, ABJURATION },
{ "warmth", S_WARMTH, splWarmth, 12, ABJURATION },
{ "cure-poison", S_CURE_POISON, splCurePoison, 10, HEALING },
{ "haste", S_HASTE, splHaste, 30, TRANSMUTATION },
{ "slow", S_SLOW, splSlow, 30, TRANSMUTATION },
{ "comprehend-languages",S_COMPREHEND_LANGUAGES,splComprehendLanguages, 20, DIVINATION },
{ "stone-to-flesh", S_STONE_TO_FLESH, splStoneToFlesh, 20, TRANSMUTATION },
{ "scare", S_SCARE, splScare, 25, ENCHANTMENT },
{ "hold-person", S_HOLD_PERSON, splHoldPerson, 20, ENCHANTMENT },
{ "entangle", S_ENTANGLE, splEntangle, 20, TRANSMUTATION },
{ "anchor", S_DIMENSIONAL_ANCHOR, splDimensionalAnchor, -1, TRANSLOCATION },
{ "strength", S_STRENGTH, splStrength, 30, TRANSMUTATION },
{ "enfeeblement", S_ENFEEBLEMENT, splEnfeeblement, 30, TRANSMUTATION },
{ "armor", S_ARMOR, splArmor, -1, ABJURATION },
{ "stoneskin", S_STONESKIN, splStoneskin, -1, ABJURATION },
{ "free-action", S_FREE_ACTION, splFreeAction, 15, ABJURATION },
{ "rejuvenate", S_REJUVENATE, splRejuvenate, -1, HEALING },
{ "disintegrate", S_DISINTEGRATE, splDisintegrate, 50, TRANSMUTATION },
{ "resurrect", S_RESURRECT, splResurrect, -1, HEALING },
{ "courage", S_COURAGE, splCourage, 20, ENCHANTMENT },
{ "bloodfusion", S_BLOODFUSION, splBloodfusion, -1, HEALING },
{ "magic-missile", S_MAGIC_MISSILE, splMagicMissile, -1, EVOCATION },
{ "knock", S_KNOCK, splKnock, -1, TRANSMUTATION },
{ "blink", S_BLINK, splBlink, 20, TRANSLOCATION },
{ "harm", S_HARM, splHarm, 30, NECROMANCY },
{ "bind", S_BIND, splBind, 25, TRANSLOCATION },
{ "darkness", S_DARKNESS, splDarkness, -1, EVOCATION },
{ "tongues", S_TONGUES, splTongues, 25, DIVINATION },
{ "enlarge", S_ENLARGE, splEnlarge, 15, TRANSMUTATION },
{ "reduce", S_REDUCE, splReduce, 15, TRANSMUTATION },
{ "insight", S_INSIGHT, splInsight, 30, TRANSMUTATION },
{ "feeblemind", S_FEEBLEMIND, splFeeblemind, 30, TRANSMUTATION },
{ "prayer", S_PRAYER, splPrayer, 30, TRANSMUTATION },
{ "damnation", S_DAMNATION, splDamnation, 30, TRANSMUTATION },
{ "fortitude", S_FORTITUDE, splFortitude, 30, TRANSMUTATION },
{ "weakness", S_WEAKNESS, splWeakness, 30, TRANSMUTATION },
{ "pass-without-trace", S_PASS_WITHOUT_TRACE, splPassWithoutTrace, 8, TRANSMUTATION },
{ "farsight", S_FARSIGHT, splFarsight, 10, DIVINATION },
{ "wind-protection", S_WIND_PROTECTION, splWindProtection, 12, ABJURATION },
{ "static-field", S_STATIC_FIELD, splStaticField, 12, ABJURATION },
{ "portal", S_PORTAL, splPortal, 75, TRANSLOCATION },
// anything after this line has been coded for the new magic system and needs player-testing
{ "detect-hidden", S_DETECT_HIDDEN, splDetectHidden, 5, DIVINATION },
{ "illusion", S_ILLUSION, splIllusion, 25, ILLUSION },
{ "deafness", S_DEAFNESS, splDeafness, 30, TRANSMUTATION },
{ "radiation", S_RADIATION, splRadiation, 6, ABJURATION },
{ "fiery-retribution", S_FIERY_RETRIBUTION, splFieryRetribution, 12, ABJURATION },
{ "aura-of-flame", S_AURA_OF_FLAME, splAuraOfFlame, 18, ABJURATION },
{ "barrier-of-combustion",S_BARRIER_OF_COMBUSTION,splBarrierOfCombustion, 24, ABJURATION },
{ "bounce-magic", S_BOUNCE_MAGIC, splBounceMagic, 10, ABJURATION },
{ "rebound-magic", S_REBOUND_MAGIC, splReboundMagic, 20, ABJURATION },
{ "dense-fog", S_DENSE_FOG, splDenseFog, 10, CONJURATION },
{ "wall-of-fire", S_WALL_OF_FIRE, splWallOfFire, 30, CONJURATION },
{ "wall-of-force", S_WALL_OF_FORCE, splWallOfForce, 50, CONJURATION },
{ "wall-of-thorns", S_WALL_OF_THORNS, splWallOfThorns, 30, CONJURATION },
{ "greater-invisibility",S_GREATER_INVIS, splGreaterInvisibility, 30, ILLUSION },
{ "hallow", S_HALLOW, splHallow, 20, NO_SCHOOL },
{ "unhallow", S_UNHALLOW, splUnhallow, 20, NO_SCHOOL },
{ "toxic-cloud", S_TOXIC_CLOUD, splToxicCloud, 20, CONJURATION },
{ "blur", S_BLUR, splBlur, 10, ILLUSION },
{ "illusory-wall", S_ILLUSORY_WALL, splIllusoryWall, 24, ILLUSION },
{ "globe-of-silence", S_GLOBE_OF_SILENCE, splGlobeOfSilence, 45, ENCHANTMENT },
{ "cancel-magic", S_CANCEL_MAGIC, splCancelMagic, 15, ABJURATION },
{ "dispel-magic", S_DISPEL_MAGIC, splDispelMagic, 20, ABJURATION },
{ "sap-life", S_SAP_LIFE, splNecroDrain, -1, NECROMANCY },
{ "lifetap", S_LIFETAP, splNecroDrain, -1, NECROMANCY },
{ "lifedraw", S_LIFEDRAW, splNecroDrain, -1, NECROMANCY },
{ "draw-spirit", S_DRAW_SPIRIT, splNecroDrain, -1, NECROMANCY },
{ "siphon-life", S_SIPHON_LIFE, splNecroDrain, -1, NECROMANCY },
{ "spirit-strike", S_SPIRIT_STRIKE, splNecroDrain, -1, NECROMANCY },
{ "soulsteal", S_SOULSTEAL, splNecroDrain, -1, NECROMANCY },
{ "touch-of-kesh", S_TOUCH_OF_KESH, splNecroDrain, -1, NECROMANCY },
{ "@", -1, 0, 0, NO_SCHOOL }
};
int spllist_size = sizeof(spllist)/sizeof(*spllist);
//**********************************************************************
// saveSpells
//**********************************************************************
// this creates spells.xml, which the Web Editor requests when it wants
// to know what spells it can use
bool Config::saveSpells() const {
xmlDocPtr xmlDoc;
xmlNodePtr rootNode, curNode;
char filename[80];
xmlDoc = xmlNewDoc(BAD_CAST "1.0");
rootNode = xmlNewDocNode(xmlDoc, NULL, BAD_CAST "Spells", NULL);
xmlDocSetRootElement(xmlDoc, rootNode);
for(int i=0 ; i < spllist_size-1; i++) {
curNode = xml::newStringChild(rootNode, "Spell", spllist[i].splstr);
xml::newNumProp(curNode, "Id", i+1);
}
sprintf(filename, "%s/spells.xml", CONFPATH);
xml::saveFile(filename, xmlDoc);
xmlFreeDoc(xmlDoc);
return(true);
}
//**********************************************************************
// initSpellList
//**********************************************************************
// Make sure someone hasn't messed up the spell table :)
void initSpellList() {
for(int i=0 ; i<spllist_size; i++) {
if(spllist[i].splno != i) {
if(spllist[i].splno != -1) {
printf("Error: Spell list mismatch: %d != %d\n", i, spllist[i].splno);
abort();
}
}
}
}
//*********************************************************************
// get_spell_name
//*********************************************************************
const char *get_spell_name(int nIndex) {
// do bounds checking
ASSERTLOG(nIndex >= 0);
ASSERTLOG(nIndex < spllist_size);
nIndex = MAX(0, MIN(nIndex, spllist_size));
return( spllist[nIndex].splstr );
}
//*********************************************************************
// get_spell_num()
//*********************************************************************
int get_spell_num(int nIndex) {
// do bounds checking
ASSERTLOG(nIndex >= 0);
ASSERTLOG(nIndex < spllist_size);
nIndex = MAX(0, MIN(nIndex, spllist_size));
return(spllist[nIndex].splno);
}
//*********************************************************************
// get_spell_lvl
//*********************************************************************
// this also serves as a list of offensive spells to cast during combat
// (for pets - see Monster::castSpell)
int get_spell_lvl(int sflag) {
int slvl=0;
ASSERTLOG(sflag >= 0 );
ASSERTLOG(sflag < spllist_size );
switch(sflag) {
case S_RUMBLE:
case S_ZEPHYR:
case S_BURN:
case S_BLISTER:
case S_ZAP:
case S_CHILL:
case S_SAP_LIFE:
case S_LIFETAP:
slvl = 1;
break;
case S_CRUSH:
case S_DUSTGUST:
case S_FIREBALL:
case S_WATERBOLT:
case S_LIGHTNING_BOLT:
case S_FROSTBITE:
case S_LIFEDRAW:
slvl = 2;
break;
case S_ENGULF:
case S_WHIRLWIND:
case S_BURSTFLAME:
case S_STEAMBLAST:
case S_SHOCKBOLT:
case S_SLEET:
case S_DRAW_SPIRIT:
slvl = 3;
break;
case S_SHATTERSTONE:
case S_CYCLONE:
case S_IMMOLATE:
case S_BLOODBOIL:
case S_ELECTROCUTE:
case S_COLD_CONE:
case S_SIPHON_LIFE:
slvl = 4;
break;
case S_EARTH_TREMOR:
case S_TEMPEST:
case S_FLAMEFILL:
case S_ATROPHY:
case S_THUNDERBOLT:
case S_ICEBLADE:
case S_SPIRIT_STRIKE:
slvl = 5;
break;
case S_EARTHQUAKE:
case S_HURRICANE:
case S_FIRESTORM:
case S_FLOOD:
case S_CHAIN_LIGHTNING:
case S_BLIZZARD:
case S_SOULSTEAL:
case S_TOUCH_OF_KESH:
slvl = 6;
break;
default:
slvl = 0;
break;
}
return(slvl);
}
//*********************************************************************
// get_spell_function
//*********************************************************************
SpellRet get_spell_function(int nIndex) {
// do bounds checking
ASSERTLOG(nIndex >= 0);
ASSERTLOG(nIndex < spllist_size);
nIndex = MAX(0, MIN(nIndex, spllist_size));
return( spllist[nIndex].splfn );
}
//*********************************************************************
// get_spell_school
//*********************************************************************
SchoolOfMagic get_spell_school(int nIndex) {
// do bounds checking
ASSERTLOG(nIndex >= 0);
ASSERTLOG(nIndex < spllist_size);
nIndex = MAX(0, MIN(nIndex, spllist_size));
return( spllist[nIndex].school );
}
//*********************************************************************
// get_spell_list_size
//*********************************************************************
int get_spell_list_size() {
return(spllist_size);
}
//*********************************************************************
// getSpellMp
//*********************************************************************
// Parameters: <splNo>
int getSpellMp(int spellNum) {
// do bounds checking
ASSERTLOG(spellNum >= 0);
ASSERTLOG(spellNum < spllist_size);
return(spllist[spellNum].mp);
}
//*********************************************************************
// checkMp
//*********************************************************************
// Parameters: <target> The player casting
// <reqMp> The required amount of mp
// Return value: <true> - Has enough mp
// <false> - Doesn't have enough mp
bool Creature::checkMp(int reqMp) {
if(getClass() != LICH && mp.getCur() < reqMp) {
if(!isPet())
print("You need %d magic points to cast that spell.\n", reqMp);
else
print("%M needs %d magic points to cast that spell.\n", this, reqMp);
return(false);
} else if(getClass() == LICH && hp.getCur() <= reqMp) {
if(!isPet())
print("Sure, and kill yourself in the process?\n");
else
print("Sure, and have %N kill %sself in the process?\n", this, himHer());
return(false);
}
return(true);
}
//*********************************************************************
// subMp
//*********************************************************************
// Parameters: <target> The player casting
// <reqMp> The amount of mp to deduct
void Creature::subMp(int reqMp) {
if(getClass() == LICH) {
hp.decrease(reqMp);
} else {
mp.decrease(reqMp);
}
}
//*********************************************************************
// splLevitate
//*********************************************************************
bool checkRefusingMagic(Creature* player, Creature* target, bool healing, bool print) {
if(player->isStaff() || !target->isPlayer())
return(false);
// linkdead players always want healing
if(!healing && target->flagIsSet(P_LINKDEAD)) {
if(print)
player->print("%M doesn't want that cast on them right now.\n", target);
return(0);
}
if(target->getPlayer()->isRefusing(player->name)) {
if(print)
player->print("%M is refusing your magical services.\n", target);
return(true);
}
return(false);
}