/** * @file spells.cpp * * Implementation of functionality for casting player spells. */ #include "control.h" #include "cursor.h" #include "gamemenu.h" #include "inv.h" #include "missiles.h" namespace devilution { int GetManaAmount(int id, spell_id sn) { int ma; // mana amount // mana adjust int adj = 0; // spell level int sl = plr[id]._pSplLvl[sn] + plr[id]._pISplLvlAdd - 1; if (sl < 0) { sl = 0; } if (sl > 0) { adj = sl * spelldata[sn].sManaAdj; } if (sn == SPL_FIREBOLT) { adj /= 2; } if (sn == SPL_RESURRECT && sl > 0) { adj = sl * (spelldata[SPL_RESURRECT].sManaCost / 8); } if (sn == SPL_HEAL || sn == SPL_HEALOTHER) { ma = (spelldata[SPL_HEAL].sManaCost + 2 * plr[id]._pLevel - adj); } else if (spelldata[sn].sManaCost == 255) { ma = ((BYTE)plr[id]._pMaxManaBase - adj); } else { ma = (spelldata[sn].sManaCost - adj); } if (ma < 0) ma = 0; ma <<= 6; if (gbIsHellfire && plr[id]._pClass == HeroClass::Sorcerer) { ma /= 2; } else if (plr[id]._pClass == HeroClass::Rogue || plr[id]._pClass == HeroClass::Monk || plr[id]._pClass == HeroClass::Bard) { ma -= ma / 4; } if (spelldata[sn].sMinMana > ma >> 6) { ma = spelldata[sn].sMinMana << 6; } return ma; } void UseMana(int id, spell_id sn) { int ma; // mana cost if (id == myplr) { switch (plr[id]._pSplType) { case RSPLTYPE_SKILL: case RSPLTYPE_INVALID: break; case RSPLTYPE_SCROLL: RemoveScroll(id); break; case RSPLTYPE_CHARGES: UseStaffCharge(id); break; case RSPLTYPE_SPELL: #ifdef _DEBUG if (!debug_mode_key_inverted_v) { #endif ma = GetManaAmount(id, sn); plr[id]._pMana -= ma; plr[id]._pManaBase -= ma; drawmanaflag = true; #ifdef _DEBUG } #endif break; } } } /** * @brief Gets a value that represents the specified spellID in 64bit bitmask format. * For example: * - spell ID 1: 0000.0000.0000.0000.0000.0000.0000.0000.0000.0000.0000.0000.0000.0000.0000.0001 * - spell ID 43: 0000.0000.0000.0000.0000.0100.0000.0000.0000.0000.0000.0000.0000.0000.0000.0000 * @param spellId The id of the spell to get a bitmask for. * @return A 64bit bitmask representation for the specified spell. */ uint64_t GetSpellBitmask(int spellId) { return 1ULL << (spellId - 1); } /** * @brief Gets a value indicating whether the player's current readied spell is a valid spell. Readied spells can be * invalidaded in a few scenarios where the spell comes from items, for example (like dropping the only scroll that * provided the spell). * @param player The player whose readied spell is to be checked. * @return 'true' when the readied spell is currently valid, and 'false' otherwise. */ bool IsReadiedSpellValid(const PlayerStruct &player) { switch (player._pRSplType) { case RSPLTYPE_SKILL: case RSPLTYPE_SPELL: case RSPLTYPE_INVALID: return true; case RSPLTYPE_CHARGES: return (player._pISpells & GetSpellBitmask(player._pRSpell)) != 0; case RSPLTYPE_SCROLL: return (player._pScrlSpells & GetSpellBitmask(player._pRSpell)) != 0; default: return false; } } /** * @brief Clears the current player's readied spell selection. * @note Will force a UI redraw in case the values actually change, so that the new spell reflects on the bottom panel. * @param player The player whose readied spell is to be cleared. */ void ClearReadiedSpell(PlayerStruct &player) { if (player._pRSpell != SPL_INVALID) { player._pRSpell = SPL_INVALID; force_redraw = 255; } if (player._pRSplType != RSPLTYPE_INVALID) { player._pRSplType = RSPLTYPE_INVALID; force_redraw = 255; } } /** * @brief Ensures the player's current readied spell is a valid selection for the character. If the current selection is * incompatible with the player's items and spell (for example, if the player does not currently have access to the spell), * the selection is cleared. * @note Will force a UI redraw in case the values actually change, so that the new spell reflects on the bottom panel. * @param player The player whose readied spell is to be checked. */ void EnsureValidReadiedSpell(PlayerStruct &player) { if (!IsReadiedSpellValid(player)) { ClearReadiedSpell(player); } } bool CheckSpell(int id, spell_id sn, spell_type st, bool manaonly) { bool result; #ifdef _DEBUG if (debug_mode_key_inverted_v) return true; #endif result = true; if (!manaonly && pcurs != CURSOR_HAND) { result = false; } else { if (st != RSPLTYPE_SKILL) { if (GetSpellLevel(id, sn) <= 0) { result = false; } else { result = plr[id]._pMana >= GetManaAmount(id, sn); } } } return result; } void CastSpell(int id, int spl, int sx, int sy, int dx, int dy, int spllvl) { direction dir = plr[id]._pdir; if (spl == SPL_FIREWALL || spl == SPL_LIGHTWALL) { dir = plr[id].tempDirection; } for (int i = 0; spelldata[spl].sMissiles[i] != MIS_NULL && i < 3; i++) { AddMissile(sx, sy, dx, dy, dir, spelldata[spl].sMissiles[i], TARGET_MONSTERS, id, 0, spllvl); } if (spl == SPL_TOWN) { UseMana(id, SPL_TOWN); } else if (spl == SPL_CBOLT) { UseMana(id, SPL_CBOLT); for (int i = (spllvl / 2) + 3; i > 0; i--) { AddMissile(sx, sy, dx, dy, dir, MIS_CBOLT, TARGET_MONSTERS, id, 0, spllvl); } } } static void PlacePlayer(int pnum) { int nx, ny, max, min, x, y; DWORD i; bool done; if (plr[pnum].plrlevel == currlevel) { for (i = 0; i < 8; i++) { nx = plr[pnum].position.tile.x + plrxoff2[i]; ny = plr[pnum].position.tile.y + plryoff2[i]; if (PosOkPlayer(pnum, nx, ny)) { break; } } if (!PosOkPlayer(pnum, nx, ny)) { done = false; for (max = 1, min = -1; min > -50 && !done; max++, min--) { for (y = min; y <= max && !done; y++) { ny = plr[pnum].position.tile.y + y; for (x = min; x <= max && !done; x++) { nx = plr[pnum].position.tile.x + x; if (PosOkPlayer(pnum, nx, ny)) { done = true; } } } } } plr[pnum].position.tile = { nx, ny }; dPlayer[nx][ny] = pnum + 1; if (pnum == myplr) { ViewX = nx; ViewY = ny; } } } /** * @param pnum player index * @param rid target player index */ void DoResurrect(int pnum, int rid) { int hp; if ((char)rid != -1) { AddMissile(plr[rid].position.tile.x, plr[rid].position.tile.y, plr[rid].position.tile.x, plr[rid].position.tile.y, 0, MIS_RESURRECTBEAM, TARGET_MONSTERS, pnum, 0, 0); } if (pnum == myplr) { NewCursor(CURSOR_HAND); } if ((char)rid != -1 && plr[rid]._pHitPoints == 0) { if (rid == myplr) { deathflag = false; gamemenu_off(); drawhpflag = true; drawmanaflag = true; } ClrPlrPath(rid); plr[rid].destAction = ACTION_NONE; plr[rid]._pInvincible = false; PlacePlayer(rid); hp = 10 << 6; if (plr[rid]._pMaxHPBase < (10 << 6)) { hp = plr[rid]._pMaxHPBase; } SetPlayerHitPoints(rid, hp); plr[rid]._pHPBase = plr[rid]._pHitPoints + (plr[rid]._pMaxHPBase - plr[rid]._pMaxHP); plr[rid]._pMana = 0; plr[rid]._pManaBase = plr[rid]._pMana + (plr[rid]._pMaxManaBase - plr[rid]._pMaxMana); CalcPlrInv(rid, true); if (plr[rid].plrlevel == currlevel) { StartStand(rid, plr[rid]._pdir); } else { plr[rid]._pmode = PM_STAND; } } } void DoHealOther(int pnum, int rid) { int i, j, hp; if (pnum == myplr) { NewCursor(CURSOR_HAND); } if ((char)rid != -1 && (plr[rid]._pHitPoints >> 6) > 0) { hp = (GenerateRnd(10) + 1) << 6; for (i = 0; i < plr[pnum]._pLevel; i++) { hp += (GenerateRnd(4) + 1) << 6; } for (j = 0; j < GetSpellLevel(pnum, SPL_HEALOTHER); ++j) { hp += (GenerateRnd(6) + 1) << 6; } if (plr[pnum]._pClass == HeroClass::Warrior || plr[pnum]._pClass == HeroClass::Barbarian) { hp *= 2; } else if (plr[pnum]._pClass == HeroClass::Rogue || plr[pnum]._pClass == HeroClass::Bard) { hp += hp / 2; } else if (plr[pnum]._pClass == HeroClass::Monk) { hp *= 3; } plr[rid]._pHitPoints += hp; if (plr[rid]._pHitPoints > plr[rid]._pMaxHP) { plr[rid]._pHitPoints = plr[rid]._pMaxHP; } plr[rid]._pHPBase += hp; if (plr[rid]._pHPBase > plr[rid]._pMaxHPBase) { plr[rid]._pHPBase = plr[rid]._pMaxHPBase; } drawhpflag = true; } } int GetSpellBookLevel(spell_id s) { if (gbIsSpawn) { switch (s) { case SPL_STONE: case SPL_GUARDIAN: case SPL_GOLEM: case SPL_FLARE: case SPL_BONESPIRIT: return -1; default: break; } } if (!gbIsHellfire) { switch (s) { case SPL_NOVA: case SPL_APOCA: return -1; default: if (s > SPL_LASTDIABLO) return -1; break; } } if (gbIsHellfire) { switch (s) { case SPL_ELEMENT: return -1; default: break; } } return spelldata[s].sBookLvl; } int GetSpellStaffLevel(spell_id s) { if (gbIsSpawn) { switch (s) { case SPL_STONE: case SPL_GUARDIAN: case SPL_GOLEM: case SPL_APOCA: case SPL_FLARE: case SPL_BONESPIRIT: return -1; default: break; } } if (!gbIsHellfire && s > SPL_LASTDIABLO) return -1; if (gbIsHellfire) { switch (s) { case SPL_ELEMENT: return -1; default: break; } } return spelldata[s].sStaffLvl; } } // namespace devilution