Files
scummvm-cursorfix/engines/mm/mm1/game/spells_monsters.cpp
2026-02-02 04:50:13 +01:00

700 lines
19 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "mm/mm1/game/spells_monsters.h"
#include "mm/mm1/maps/maps.h"
#include "mm/mm1/globals.h"
#include "mm/mm1/sound.h"
namespace MM {
namespace MM1 {
namespace Game {
const SpellMonstersSpell SpellsMonsters::SPELLS[MONSTER_SPELLS_COUNT] = {
&SpellsMonsters::spell01_curse,
&SpellsMonsters::spell02_energyBlast,
&SpellsMonsters::spell03_fire,
&SpellsMonsters::spell04_blindness,
&SpellsMonsters::spell05_sprayPoison,
&SpellsMonsters::spell06_sprayAcid,
&SpellsMonsters::spell07_sleep,
&SpellsMonsters::spell08_paralyze,
&SpellsMonsters::spell09_dispel,
&SpellsMonsters::spell10_lightningBolt,
&SpellsMonsters::spell11_strangeGas,
&SpellsMonsters::spell12_explode,
&SpellsMonsters::spell13_fireball,
&SpellsMonsters::spell14_fireBreath,
&SpellsMonsters::spell15_gazes,
&SpellsMonsters::spell16_acidArrow,
&SpellsMonsters::spell17_elements,
&SpellsMonsters::spell18_coldBeam,
&SpellsMonsters::spell19_dancingSword,
&SpellsMonsters::spell20_magicDrain,
&SpellsMonsters::spell21_fingerOfDeath,
&SpellsMonsters::spell22_sunRay,
&SpellsMonsters::spell23_disintegration,
&SpellsMonsters::spell24_commandsEnergy,
&SpellsMonsters::spell25_poison,
&SpellsMonsters::spell26_lightning,
&SpellsMonsters::spell27_frost,
&SpellsMonsters::spell28_spikes,
&SpellsMonsters::spell29_acid,
&SpellsMonsters::spell30_fire,
&SpellsMonsters::spell31_energy,
&SpellsMonsters::spell32_swarm
};
SpellsMonsters::SpellsMonsters() {
}
void SpellsMonsters::castMonsterSpell(const Common::String &monsterName, int spellNum) {
g_globals->_spellsState._mmVal1 = 0;
g_globals->_spellsState._resistanceIndex = 0;
g_globals->_spellsState._resistanceTypeOrTargetCount = RESISTANCE_MAGIC;
g_globals->_spellsState._damage = 0;
// All spell messages start with the monster who casts it
_lines.clear();
_lines.push_back(Line(monsterName));
(this->*SPELLS[spellNum - 1])();
}
void SpellsMonsters::spell01_curse() {
if (casts()) {
add(STRING["monster_spells.a_curse"]);
g_globals->_activeSpells._s.cursed = MIN(255,
(int)g_globals->_activeSpells._s.cursed + 1);
}
}
void SpellsMonsters::spell02_energyBlast() {
if (casts()) {
add(STRING["monster_spells.energy_blast"]);
++g_globals->_spellsState._mmVal1;
g_globals->_spellsState._damage = getRandomNumber(16) + 4;
damageRandomChar();
}
}
void SpellsMonsters::spell03_fire() {
add(Common::String::format("%s %s",
STRING["monster_spells.breathes"].c_str(),
STRING["monster_spells.fire"].c_str()));
++g_globals->_spellsState._resistanceIndex;
g_globals->_spellsState._resistanceTypeOrTargetCount = RESISTANCE_FIRE;
int count = _remainingMonsters[getMonsterIndex()]->_level;
g_globals->_spellsState._damage += count * 6;
damageRandomChar();
}
void SpellsMonsters::spell04_blindness() {
if (casts()) {
add(STRING["monster_spells.blindness"]);
++g_globals->_spellsState._mmVal1;
++g_globals->_spellsState._resistanceIndex;
g_globals->_spellsState._damage = 2;
handlePartyEffects();
}
}
void SpellsMonsters::spell05_sprayPoison() {
add(STRING["monster_spells.sprays_poison"]);
++g_globals->_spellsState._resistanceIndex;
g_globals->_spellsState._resistanceTypeOrTargetCount = RESISTANCE_POISON;
g_globals->_spellsState._damage = POISONED;
handlePartyEffects();
}
void SpellsMonsters::spell06_sprayAcid() {
add(STRING["monster_spells.sprays_acid"]);
++g_globals->_spellsState._resistanceIndex;
g_globals->_spellsState._resistanceTypeOrTargetCount = RESISTANCE_ACID;
g_globals->_spellsState._damage = getRandomNumber((int)POISONED);
add(':');
handlePartyDamage();
}
void SpellsMonsters::spell07_sleep() {
if (casts()) {
add(STRING["monster_spells.sleep"]);
++g_globals->_spellsState._mmVal1;
++g_globals->_spellsState._resistanceIndex;
g_globals->_spellsState._resistanceTypeOrTargetCount = RESISTANCE_PSYCHIC;
g_globals->_spellsState._damage = ASLEEP;
handlePartyEffects();
}
}
void SpellsMonsters::spell08_paralyze() {
if (casts()) {
add(STRING["monster_spells.paralyze"]);
++g_globals->_spellsState._mmVal1;
++g_globals->_spellsState._resistanceIndex;
g_globals->_spellsState._resistanceTypeOrTargetCount = RESISTANCE_FEAR;
g_globals->_spellsState._damage = PARALYZED;
if (_remainingMonsters[getMonsterIndex()]->_level >= 5) {
handlePartyEffects();
} else {
chooseCharacter();
writeConditionEffect();
}
}
}
void SpellsMonsters::spell09_dispel() {
if (casts()) {
add(STRING["monster_spells.dispel"]);
dispelParty();
}
}
void SpellsMonsters::spell10_lightningBolt() {
if (casts()) {
add(STRING["monster_spells.lightning_bolt"]);
++g_globals->_spellsState._mmVal1;
++g_globals->_spellsState._resistanceIndex;
g_globals->_spellsState._resistanceTypeOrTargetCount = RESISTANCE_ELECTRICITY;
g_globals->_spellsState._damage = getRandomNumber(37) + 5;
damageRandomChar();
}
}
void SpellsMonsters::spell11_strangeGas() {
add(Common::String::format("%s %s",
STRING["monster_spells.breathes"].c_str(),
STRING["monster_spells.strange_gas"].c_str()));
++g_globals->_spellsState._resistanceIndex;
g_globals->_spellsState._resistanceTypeOrTargetCount = RESISTANCE_POISON;
g_globals->_spellsState._damage = BAD_CONDITION | STONE;
handlePartyEffects();
}
void SpellsMonsters::spell12_explode() {
add(STRING["monster_spells.explode"]);
++g_globals->_spellsState._resistanceIndex;
g_globals->_spellsState._resistanceTypeOrTargetCount = RESISTANCE_POISON;
g_globals->_spellsState._damage = getRandomNumber(
_remainingMonsters[getMonsterIndex()]->_level);
_remainingMonsters[getMonsterIndex()]->_level = 0;
_remainingMonsters[getMonsterIndex()]->_status = MONFLAG_DEAD;
removeMonster();
add(':');
handlePartyDamage();
}
void SpellsMonsters::spell13_fireball() {
if (casts()) {
add(STRING["monster_spells.fireball"]);
++g_globals->_spellsState._mmVal1;
++g_globals->_spellsState._resistanceIndex;
g_globals->_spellsState._resistanceTypeOrTargetCount = RESISTANCE_FIRE;
// This whole condition choice makes no sense
g_globals->_spellsState._damage += 6 *
_remainingMonsters[getMonsterIndex()]->_level;
g_globals->_spellsState._damage = getRandomNumber(g_globals->_spellsState._damage) + 4;
add(':');
handlePartyDamage();
}
}
void SpellsMonsters::spell14_fireBreath() {
add(Common::String::format("%s %s",
STRING["monster_spells.breathes"].c_str(),
STRING["monster_spells.fire"].c_str()));
++g_globals->_spellsState._resistanceIndex;
g_globals->_spellsState._resistanceTypeOrTargetCount = RESISTANCE_FIRE;
// This whole condition choice makes no sense
g_globals->_spellsState._damage += 8 *
_remainingMonsters[getMonsterIndex()]->_level;
g_globals->_spellsState._damage = getRandomNumber(g_globals->_spellsState._damage);
add(':');
handlePartyDamage();
}
void SpellsMonsters::spell15_gazes() {
add(STRING["monster_spells.gazes"]);
++g_globals->_spellsState._resistanceIndex;
g_globals->_spellsState._damage = BAD_CONDITION | STONE;
chooseCharacter();
writeConditionEffect();
}
void SpellsMonsters::spell16_acidArrow() {
add(STRING["monster_spells.acid_arrow"]);
++g_globals->_spellsState._mmVal1;
++g_globals->_spellsState._resistanceIndex;
g_globals->_spellsState._resistanceTypeOrTargetCount = RESISTANCE_ACID;
g_globals->_spellsState._damage = getRandomNumber(31) + 9;
damageRandomChar();
}
void SpellsMonsters::spell17_elements() {
add(STRING["monster_spells.call_elements"]);
++g_globals->_spellsState._resistanceIndex;
g_globals->_spellsState._damage = getRandomNumber(21) + 39;
damageRandomChar();
}
void SpellsMonsters::spell18_coldBeam() {
if (casts()) {
add(STRING["monster_spells.cold_beam"]);
++g_globals->_spellsState._mmVal1;
++g_globals->_spellsState._resistanceIndex;
g_globals->_spellsState._resistanceTypeOrTargetCount = RESISTANCE_COLD;
g_globals->_spellsState._damage = getRandomNumber(41) + 9;
damageRandomChar();
}
}
void SpellsMonsters::spell19_dancingSword() {
if (casts()) {
add(STRING["monster_spells.dancing_sword"]);
++g_globals->_spellsState._mmVal1;
g_globals->_spellsState._damage = getRandomNumber(14) + 16;
add(':');
handlePartyDamage();
}
}
void SpellsMonsters::spell20_magicDrain() {
add(STRING["monster_spells.magic_drain"]);
for (uint i = 0; i < g_globals->_party.size(); ++i) {
Character &c = g_globals->_party[i];
g_globals->_currCharacter = &c;
c._sp._current = 0;
}
dispelParty();
}
void SpellsMonsters::spell21_fingerOfDeath() {
if (casts()) {
add(STRING["monster_spells.finger_of_death"]);
++g_globals->_spellsState._mmVal1;
++g_globals->_spellsState._resistanceIndex;
g_globals->_spellsState._damage = BAD_CONDITION | DEAD;
chooseCharacter();
writeConditionEffect();
}
}
void SpellsMonsters::spell22_sunRay() {
if (casts()) {
add(STRING["monster_spells.sun_ray"]);
++g_globals->_spellsState._mmVal1;
++g_globals->_spellsState._resistanceIndex;
g_globals->_spellsState._damage = getRandomNumber(51) + 49;
damageRandomChar();
}
}
void SpellsMonsters::spell23_disintegration() {
if (casts()) {
add(STRING["monster_spells.disintegration"]);
++g_globals->_spellsState._mmVal1;
++g_globals->_spellsState._resistanceIndex;
g_globals->_spellsState._damage = ERADICATED;
chooseCharacter();
writeConditionEffect();
}
}
void SpellsMonsters::spell24_commandsEnergy() {
add(STRING["monster_spells.commands_energy"]);
g_globals->_spellsState._damage = SILENCED | PARALYZED | UNCONSCIOUS;
damageRandomChar();
}
void SpellsMonsters::spell25_poison() {
add(Common::String::format("%s %s",
STRING["monster_spells.breathes"].c_str(),
STRING["monster_spells.poison"].c_str()));
g_globals->_spellsState._resistanceTypeOrTargetCount = RESISTANCE_POISON;
g_globals->_spellsState._damage = _remainingMonsters[getMonsterIndex()]->_hp;
++g_globals->_spellsState._resistanceIndex;
add(':');
handlePartyDamage();
}
void SpellsMonsters::spell26_lightning() {
add(Common::String::format("%s %s",
STRING["monster_spells.breathes"].c_str(),
STRING["monster_spells.lightning"].c_str()));
g_globals->_spellsState._resistanceTypeOrTargetCount = RESISTANCE_ELECTRICITY;
g_globals->_spellsState._damage = _remainingMonsters[getMonsterIndex()]->_hp;
++g_globals->_spellsState._resistanceIndex;
add(':');
handlePartyDamage();
}
void SpellsMonsters::spell27_frost() {
add(Common::String::format("%s %s",
STRING["monster_spells.breathes"].c_str(),
STRING["monster_spells.frost"].c_str()));
g_globals->_spellsState._resistanceTypeOrTargetCount = RESISTANCE_COLD;
g_globals->_spellsState._damage = _remainingMonsters[getMonsterIndex()]->_hp;
++g_globals->_spellsState._resistanceIndex;
add(':');
handlePartyDamage();
}
void SpellsMonsters::spell28_spikes() {
add(Common::String::format("%s %s",
STRING["monster_spells.breathes"].c_str(),
STRING["monster_spells.spikes"].c_str()));
g_globals->_spellsState._resistanceTypeOrTargetCount = RESISTANCE_ELECTRICITY;
g_globals->_spellsState._damage = _remainingMonsters[getMonsterIndex()]->_hp;
++g_globals->_spellsState._resistanceIndex;
add(':');
handlePartyDamage();
}
void SpellsMonsters::spell29_acid() {
add(Common::String::format("%s %s",
STRING["monster_spells.breathes"].c_str(),
STRING["monster_spells.acid"].c_str()));
g_globals->_spellsState._resistanceTypeOrTargetCount = RESISTANCE_ACID;
g_globals->_spellsState._damage = _remainingMonsters[getMonsterIndex()]->_hp;
++g_globals->_spellsState._resistanceIndex;
add(':');
handlePartyDamage();
}
void SpellsMonsters::spell30_fire() {
add(Common::String::format("%s %s",
STRING["monster_spells.breathes"].c_str(),
STRING["monster_spells.fire"].c_str()));
g_globals->_spellsState._resistanceTypeOrTargetCount = RESISTANCE_FIRE;
g_globals->_spellsState._damage = _remainingMonsters[getMonsterIndex()]->_hp;
++g_globals->_spellsState._resistanceIndex;
add(':');
handlePartyDamage();
}
void SpellsMonsters::spell31_energy() {
add(STRING["monster_spells.energy"]);
g_globals->_spellsState._damage = _remainingMonsters[getMonsterIndex()]->_hp;
++g_globals->_spellsState._resistanceIndex;
add(':');
handlePartyDamage();
}
void SpellsMonsters::spell32_swarm() {
add(STRING["monster_spells.swarm"]);
++g_globals->_spellsState._resistanceIndex;
g_globals->_spellsState._damage = getRandomNumber(12);
add(':');
handlePartyDamage();
}
bool SpellsMonsters::casts() {
if (canMonsterCast()) {
add(STRING["monster_spells.casts"]);
return true;
} else {
add(STRING["monster_spells.fails_to_cast"]);
return false;
}
}
void SpellsMonsters::addCharName() {
add(g_globals->_currCharacter->_name);
}
void SpellsMonsters::damageRandomChar() {
chooseCharacter();
handleDamage();
}
void SpellsMonsters::chooseCharacter() {
add(':');
// Choose a random character
g_globals->_currCharacter = &g_globals->_party[
getRandomNumber(g_globals->_party.size()) - 1
];
if (g_globals->_currCharacter->_condition & (BAD_CONDITION | UNCONSCIOUS)) {
// Can't use character, so sequently scan party
// to find a character that can be used
for (uint i = 0; i < g_globals->_party.size(); ++i) {
g_globals->_currCharacter = &g_globals->_party[i];
if (g_globals->_currCharacter->_condition & (BAD_CONDITION | UNCONSCIOUS))
break;
}
}
}
bool SpellsMonsters::isCharAffected() const {
int val = g_globals->_currCharacter->_resistances._s._magic._current +
g_globals->_activeSpells._s.magic;
return randomThreshold(val);
}
void SpellsMonsters::handleDamage() {
g_globals->_spellsState._mmVal5 = 1;
_damage = g_globals->_spellsState._damage;
if (charAffected()) {
if (isEffective()) {
if (testElementalResistance()) {
if (g_globals->_activeSpells._s.power_shield)
_damage = 1;
writeDamage();
Common::String str = subtractDamageFromChar();
if (!str.empty())
_lines.push_back(Line(0, _lines.back().y + 1, str));
}
}
}
}
bool SpellsMonsters::charAffected() {
_lines.push_back(Line(0, 2, ""));
addCharName();
add(' ');
if (g_globals->_spellsState._mmVal1 && !isCharAffected()) {
_lines.back()._text += STRING["monster_spells.not_affected"];
return false;
}
return true;
}
bool SpellsMonsters::isEffective() {
if (g_globals->_spellsState._resistanceIndex) {
proc9();
if (g_globals->_spellsState._mmVal7) {
if (g_globals->_spellsState._mmVal5) {
_damage >>= 1;
} else {
_lines.back()._text += STRING["monster_spells.not_affected"];
return false;
}
}
}
return true;
}
bool SpellsMonsters::testElementalResistance() {
bool result = false;
switch (g_globals->_spellsState._resistanceTypeOrTargetCount) {
case RESISTANCE_FIRE:
result = damageType1(); break;
case RESISTANCE_COLD:
result = damageType2(); break;
case RESISTANCE_ELECTRICITY:
result = damageType3(); break;
case RESISTANCE_ACID:
result = damageType4(); break;
case RESISTANCE_FEAR:
result = damageType5(); break;
case RESISTANCE_POISON:
result = damageType6(); break;
case RESISTANCE_PSYCHIC:
result = damageType7(); break;
default:
break;
}
if (!result) {
if (g_globals->_spellsState._mmVal5) {
_damage >>= 2;
} else {
add(STRING["monster_spells.not_affected"]);
return false;
}
}
return true;
}
void SpellsMonsters::writeDamage() {
add(STRING["monster_spells.takes"]);
add(Common::String::format("%d ", _damage));
add(STRING[_damage > 1 ? "monster_spells.points" : "monster_spells.point"]);
add(' ');
if (_lines.back()._text.size() >= 30)
add('!');
else
add(STRING["monster_spells.of_damage"]);
}
void SpellsMonsters::proc9() {
const Character &c = *g_globals->_currCharacter;
int val = c._level._current * 4 + c._luck._current;
if (c._class == PALADIN)
val += 20;
int randVal = getRandomNumber(100);
g_globals->_spellsState._mmVal7 = randVal < 99 && randVal <= val ? 1 : 0;
}
bool SpellsMonsters::damageType1() {
int threshold = g_globals->_currCharacter->_resistances._s._fire +
g_globals->_activeSpells._s.fire;
return randomThreshold(threshold);
}
bool SpellsMonsters::damageType2() {
int threshold = g_globals->_currCharacter->_resistances._s._cold +
g_globals->_activeSpells._s.cold;
return randomThreshold(threshold);
}
bool SpellsMonsters::damageType3() {
int threshold = g_globals->_currCharacter->_resistances._s._electricity +
g_globals->_activeSpells._s.electricity;
return randomThreshold(threshold);
}
bool SpellsMonsters::damageType4() {
int threshold = g_globals->_currCharacter->_resistances._s._acid +
g_globals->_activeSpells._s.acid;
return randomThreshold(threshold);
}
bool SpellsMonsters::damageType5() {
if (g_globals->_activeSpells._s.psychic_protection) {
return false;
} else {
int threshold = g_globals->_currCharacter->_resistances._s._fear +
g_globals->_activeSpells._s.fear;
return randomThreshold(threshold);
}
}
bool SpellsMonsters::damageType6() {
int threshold = g_globals->_currCharacter->_resistances._s._poison +
g_globals->_activeSpells._s.poison;
return randomThreshold(threshold);
}
bool SpellsMonsters::damageType7() {
if (g_globals->_activeSpells._s.psychic_protection) {
return false;
} else {
int threshold = g_globals->_currCharacter->_resistances._s._psychic;
return randomThreshold(threshold);
}
}
void SpellsMonsters::writeConditionEffect() {
g_globals->_spellsState._mmVal5 = 0;
int effectNum;
if (!charAffected() || !isEffective() || !testElementalResistance())
return;
if (g_globals->_spellsState._damage == 0) {
effectNum = 10;
} else if (g_globals->_spellsState._damage & BAD_CONDITION) {
if (!(g_globals->_spellsState._damage & (BAD_CONDITION | DEAD)))
effectNum = 7;
else if (!(g_globals->_spellsState._damage & (BAD_CONDITION | STONE)))
effectNum = 8;
else if (g_globals->_spellsState._damage == ERADICATED)
effectNum = 9;
else
effectNum = 10;
} else {
effectNum = 0;
for (byte bitset = g_globals->_spellsState._damage; bitset & 1;
++effectNum, bitset >>= 1) {
}
}
add(STRING[Common::String::format("spells.char_effects.%d",
effectNum)]);
add('!');
}
void SpellsMonsters::setCondition(byte newCondition) {
Character &c = *g_globals->_currCharacter;
if (!(c._condition & BAD_CONDITION)) {
c._condition |= newCondition;
} else if (newCondition & BAD_CONDITION) {
c._condition = newCondition;
}
}
void SpellsMonsters::handlePartyEffects() {
add(':');
for (uint i = 0; i < g_globals->_party.size(); ++i) {
g_globals->_currCharacter = &g_globals->_party[i];
writeConditionEffect();
}
}
void SpellsMonsters::handlePartyDamage() {
Character *tmp = g_globals->_currCharacter;
for (uint i = 0; i < g_globals->_party.size(); ++i) {
g_globals->_currCharacter = &g_globals->_party[i];
handleDamage();
}
g_globals->_currCharacter = tmp;
}
} // namespace Game
} // namespace MM1
} // namespace MM