diff options
-rw-r--r-- | engines/xeen/character.cpp | 18 | ||||
-rw-r--r-- | engines/xeen/character.h | 10 | ||||
-rw-r--r-- | engines/xeen/combat.cpp | 62 | ||||
-rw-r--r-- | engines/xeen/combat.h | 4 | ||||
-rw-r--r-- | engines/xeen/dialogs_error.cpp | 30 | ||||
-rw-r--r-- | engines/xeen/dialogs_error.h | 11 | ||||
-rw-r--r-- | engines/xeen/dialogs_spells.cpp | 142 | ||||
-rw-r--r-- | engines/xeen/dialogs_spells.h | 15 | ||||
-rw-r--r-- | engines/xeen/interface.cpp | 31 | ||||
-rw-r--r-- | engines/xeen/resources.cpp | 15 | ||||
-rw-r--r-- | engines/xeen/resources.h | 5 | ||||
-rw-r--r-- | engines/xeen/spells.cpp | 31 | ||||
-rw-r--r-- | engines/xeen/spells.h | 5 |
13 files changed, 356 insertions, 23 deletions
diff --git a/engines/xeen/character.cpp b/engines/xeen/character.cpp index afe385448a..c91b860640 100644 --- a/engines/xeen/character.cpp +++ b/engines/xeen/character.cpp @@ -707,7 +707,7 @@ void Character::clear() { _lloydMap = 0; _hasSpells = false; _currentSpell = 0; - _quickOption = 0; + _quickOption = QUICK_ATTACK; _lloydSide = 0; Common::fill(&_conditions[0], &_conditions[16], 0); _townUnknown = 0; @@ -1822,4 +1822,20 @@ bool Character::hasSpecialItem() const { return false; } +int Character::getClassCategory() const { + switch (_class) { + case CLASS_ARCHER: + case CLASS_SORCERER: + return 1; + + case CLASS_DRUID: + case CLASS_RANGER: + return 2; + + default: + return 0; + } +} + + } // End of namespace Xeen diff --git a/engines/xeen/character.h b/engines/xeen/character.h index be38ba5da1..bf77bef315 100644 --- a/engines/xeen/character.h +++ b/engines/xeen/character.h @@ -85,6 +85,10 @@ enum AttributeCategory { ATTR_ARMOR_CLASS = 8, ATTR_THIEVERY = 9 }; +enum QuickAction { + QUICK_ATTACK = 0, QUICK_SPELL = 1, QUICK_BLOCK = 2, QUICK_RUN = 3 +}; + class XeenEngine; class Character; @@ -235,8 +239,8 @@ public: int _lloydMap; Common::Point _lloydPosition; bool _hasSpells; - int _currentSpell; - int _quickOption; + int8 _currentSpell; + QuickAction _quickOption; InventoryItemsGroup _items; WeaponItems _weapons; ArmorItems _armor; @@ -321,6 +325,8 @@ public: void subtractHitPoints(int amount); bool hasSpecialItem() const; + + int getClassCategory() const; }; } // End of namespace Xeen diff --git a/engines/xeen/combat.cpp b/engines/xeen/combat.cpp index c8f5824db9..b77ef6e95d 100644 --- a/engines/xeen/combat.cpp +++ b/engines/xeen/combat.cpp @@ -100,6 +100,8 @@ Combat::Combat(XeenEngine *vm): _vm(vm) { _partyRan = false; _monster2Attack = -1; _whosSpeed = 0; + _damageType = DT_PHYSICAL; + _oldCharacter = nullptr; } void Combat::clear() { @@ -658,17 +660,21 @@ bool Combat::allHaveGone() const { return true; } +/** + * Returns true if all the characters of the party are disabled + */ bool Combat::charsCantAct() const { for (uint idx = 0; idx < _combatParty.size(); ++idx) { - Condition condition = _combatParty[idx]->worstCondition(); - - if (!(condition == ASLEEP || (condition >= PARALYZED && condition != NO_CONDITION))) + if (!_combatParty[idx]->isDisabledOrDead()) return false; } return true; } +/** + * Return a description of the monsters being faced + */ Common::String Combat::getMonsterDescriptions() { Map &map = *_vm->_map; Common::String lines[3]; @@ -702,25 +708,61 @@ void Combat::attack(Character &c, int v2) { error("TODO"); } +/** + * Flag the currently active character as blocking/defending + */ void Combat::block() { _charsBlocked[_whosTurn] = true; } -bool Combat::castSpell(bool flag) { - error("TODO: castSpell"); -} - +/** + * Perform whatever the current combat character's quick action is + */ void Combat::quickFight() { - error("TODO: quickFight"); + Spells &spells = *_vm->_spells; + Character *c = _combatParty[_whosTurn]; + int spellId; + + switch (c->_quickOption) { + case QUICK_ATTACK: + attack(*c, 0); + break; + case QUICK_SPELL: + if (c->_currentSpell != -1) { + spells.castSpell(SPELLS_ALLOWED[c->getClassCategory()][c->_currentSpell]); + } + break; + case QUICK_BLOCK: + block(); + break; + case QUICK_RUN: + run(); + break; + default: + break; + } } void Combat::giveTreasure() { error("TODO: giveTreasure"); } +/** + * Current selected character is trying to run away + */ void Combat::run() { - error("TODO: run"); -} + Map &map = *_vm->_map; + SoundManager &sound = *_vm->_sound; + if (_vm->getRandomNumber(1, 100) < map.mazeData()._difficulties._chance2Run) { + // Remove the character from the combat party + _combatParty.remove_at(_whosTurn); + setSpeedTable(); + --_whosSpeed; + _whosTurn = -1; + _partyRan = true; + sound.playFX(51); + } +} } // End of namespace Xeen diff --git a/engines/xeen/combat.h b/engines/xeen/combat.h index dee15edf6c..5ff6beb16b 100644 --- a/engines/xeen/combat.h +++ b/engines/xeen/combat.h @@ -81,6 +81,8 @@ public: int _monsterIndex; bool _partyRan; int _whosSpeed; + DamageType _damageType; + Character *_oldCharacter; void monstersAttack(); @@ -121,8 +123,6 @@ public: void block(); - bool castSpell(bool flag); - void quickFight(); void giveTreasure(); diff --git a/engines/xeen/dialogs_error.cpp b/engines/xeen/dialogs_error.cpp index db2603ab87..7f649afd86 100644 --- a/engines/xeen/dialogs_error.cpp +++ b/engines/xeen/dialogs_error.cpp @@ -82,4 +82,34 @@ void ErrorScroll::show(XeenEngine *vm, const Common::String &msg, ErrorWaitType ErrorDialog::show(vm, s, waitType); } +/*------------------------------------------------------------------------*/ + +void CantCast::show(XeenEngine *vm, int spellId, int componentNum) { + CantCast *dlg = new CantCast(vm); + dlg->execute(spellId, componentNum); + delete dlg; +} + +void CantCast::execute(int spellId, int componentNum) { + EventsManager &events = *_vm->_events; + SoundManager &sound = *_vm->_sound; + Window &w = _vm->_screen->_windows[6]; + Mode oldMode = _vm->_mode; + _vm->_mode = MODE_FF; + + sound.playFX(21); + w.open(); + w.writeString(Common::String::format(NOT_ENOUGH_TO_CAST, + SPELL_CAST_COMPONENTS[componentNum - 1])); + w.update(); + + do { + events.pollEventsAndWait(); + } while (!_vm->shouldQuit() && !events.isKeyMousePressed()); + events.clearEvents(); + + w.close(); + _vm->_mode = oldMode; +} + } // End of namespace Xeen diff --git a/engines/xeen/dialogs_error.h b/engines/xeen/dialogs_error.h index 644b7e3b0e..46efdb1683 100644 --- a/engines/xeen/dialogs_error.h +++ b/engines/xeen/dialogs_error.h @@ -49,6 +49,17 @@ public: ErrorWaitType waitType = WT_FREEZE_WAIT); }; +class CantCast: public ButtonContainer { +private: + XeenEngine *_vm; + + CantCast(XeenEngine *vm) : ButtonContainer(), _vm(vm) {} + + void execute(int spellId, int componentNum); +public: + static void show(XeenEngine *vm, int spellId, int componentNum); +}; + } // End of namespace Xeen #endif /* XEEN_DIALOGS_ERROR_H */ diff --git a/engines/xeen/dialogs_spells.cpp b/engines/xeen/dialogs_spells.cpp index 0ce0259b45..fe249d263e 100644 --- a/engines/xeen/dialogs_spells.cpp +++ b/engines/xeen/dialogs_spells.cpp @@ -413,4 +413,146 @@ const char *SpellsScroll::setSpellText(Character *c, int v2) { return nullptr; } +/*------------------------------------------------------------------------*/ + +int CastSpell::show(XeenEngine *vm, int mode) { + Combat &combat = *vm->_combat; + Interface &intf = *vm->_interface; + Party &party = *vm->_party; + Spells &spells = *vm->_spells; + int charNum; + + // Get which character is doing the casting + if (mode == MODE_COMBAT) { + charNum = combat._whosTurn; + } else if (spells._lastCaster >= 0 && spells._lastCaster < (int)party._activeParty.size()) { + charNum = spells._lastCaster; + } else { + for (charNum = (int)party._activeParty.size() - 1; charNum >= 0; --charNum) { + if (party._activeParty[charNum]._hasSpells) { + spells._lastCaster = charNum; + break; + } + } + } + + Character &c = party._activeParty[charNum]; + intf.highlightChar(charNum); + + CastSpell *dlg = new CastSpell(vm); + int spellId = dlg->execute(&c, mode); + delete dlg; + + return spellId; +} + +int CastSpell::show(XeenEngine *vm, Character *c, int mode) { + CastSpell *dlg = new CastSpell(vm); + int spellId = dlg->execute(c, mode); + delete dlg; + + return spellId; +} + +int CastSpell::execute(Character *c, int mode) { + EventsManager &events = *_vm->_events; + Interface &intf = *_vm->_interface; + Party &party = *_vm->_party; + Screen &screen = *_vm->_screen; + SoundManager &sound = *_vm->_sound; + Spells &spells = *_vm->_spells; + Window &w = screen._windows[10]; + int charNum; + + Mode oldMode = (Mode)mode; + int category = c->getClassCategory(); + + w.open(); + loadButtons(); + drawButtons(&screen); + + int spellId = -1; + bool redrawFlag = true; + do { + if (redrawFlag) { + int spellIndex = c->_currentSpell != -1 ? c->_currentSpell : 39; + spellId = SPELLS_ALLOWED[category][spellIndex]; + int gemCost = SPELL_GEM_COST[spellId]; + int spCost = spells.calcSpellPoints(spellId, c->getCurrentLevel()); + + w.writeString(Common::String::format(CAST_SPELL_DETAILS, + c->_name.c_str(), spells._spellNames[spellId].c_str(), + spCost, gemCost, c->_currentSp)); + w.update(); + + _vm->_mode = MODE_3; + redrawFlag = false; + } + + events.updateGameCounter(); + intf.draw3d(true); + + // Wait for event or time expiry + do { + events.pollEventsAndWait(); + checkEvents(_vm); + } while (!_vm->shouldQuit() && events.timeElapsed() < 1 && !_buttonValue); + + switch (_buttonValue) { + case Common::KEYCODE_F1: + case Common::KEYCODE_F2: + case Common::KEYCODE_F3: + case Common::KEYCODE_F4: + case Common::KEYCODE_F5: + case Common::KEYCODE_F6: + // Only allow changing character if the party is not in combat + if (oldMode != MODE_COMBAT) { + _vm->_mode = oldMode; + _buttonValue -= Common::KEYCODE_F1; + + if (_buttonValue < (int)party._activeParty.size()) { + c = &party._activeParty[_buttonValue]; + intf.highlightChar(_buttonValue); + redrawFlag = true; + break; + } + } + break; + + case Common::KEYCODE_ESCAPE: + spellId = -1; + break; + + case Common::KEYCODE_c: + // Cast spell - return the selected spell Id to be cast + if (c->_currentSpell != -1 && !c->noActions()) + _buttonValue = Common::KEYCODE_ESCAPE; + break; + + case Common::KEYCODE_n: + // Select new spell + _vm->_mode = oldMode; + c = SpellsScroll::show(_vm, c, 1); + redrawFlag = true; + break; + + default: + break; + } + } while (!_vm->shouldQuit() && _buttonValue != Common::KEYCODE_ESCAPE); + + w.close(); + intf.unhighlightChar(); + + return spellId; +} + +void CastSpell::loadButtons() { + _iconSprites.load("cast.icn"); + addButton(Common::Rect(234, 108, 259, 128), Common::KEYCODE_d, &_iconSprites); + addButton(Common::Rect(261, 108, 285, 128), Common::KEYCODE_w, &_iconSprites); + addButton(Common::Rect(288, 108, 312, 128), Common::KEYCODE_ESCAPE, &_iconSprites); + addPartyButtons(_vm); +} + } // End of namespace Xeen diff --git a/engines/xeen/dialogs_spells.h b/engines/xeen/dialogs_spells.h index 69e339fa79..d0de44c932 100644 --- a/engines/xeen/dialogs_spells.h +++ b/engines/xeen/dialogs_spells.h @@ -56,6 +56,21 @@ public: static Character *show(XeenEngine *vm, Character *c, int v2); }; +class CastSpell : public ButtonContainer { +private: + XeenEngine *_vm; + SpriteResource _iconSprites; + + CastSpell(XeenEngine *vm) : ButtonContainer(), _vm(vm) {} + + int execute(Character *c, int mode); + + void loadButtons(); +public: + static int show(XeenEngine *vm, int mode); + static int show(XeenEngine *vm, Character *c, int mode); +}; + } // End of namespace Xeen #endif /* XEEN_DIALOGS_SPELLS_H */ diff --git a/engines/xeen/interface.cpp b/engines/xeen/interface.cpp index a8396288ee..3bd3ef12b6 100644 --- a/engines/xeen/interface.cpp +++ b/engines/xeen/interface.cpp @@ -31,6 +31,7 @@ #include "xeen/dialogs_query.h" #include "xeen/dialogs_quests.h" #include "xeen/dialogs_quick_ref.h" +#include "xeen/dialogs_spells.h" #include "xeen/resources.h" #include "xeen/xeen.h" @@ -267,6 +268,7 @@ void Interface::perform() { Map &map = *_vm->_map; Party &party = *_vm->_party; Scripts &scripts = *_vm->_scripts; + Spells &spells = *_vm->_spells; const Common::Rect waitBounds(8, 8, 224, 140); events.updateGameCounter(); @@ -517,6 +519,14 @@ void Interface::perform() { } break; + case Common::KEYCODE_c: { + // Cast spell + int spellId = CastSpell::show(_vm, _vm->_mode); + if (spellId != -1) + spells.castSpell(spellId); + break; + } + case Common::KEYCODE_i: // Show Info dialog _vm->_moveMonsters = false; @@ -585,8 +595,10 @@ void Interface::stepTime() { } void Interface::doStepCode() { + Combat &combat = *_vm->_combat; Map &map = *_vm->_map; Party &party = *_vm->_party; + Scripts &scripts = *_vm->_scripts; int damage = 0; party._stepped = true; @@ -642,8 +654,11 @@ void Interface::doStepCode() { _flipGround = !_flipGround; draw3d(true); - warning("TODO: apply damage"); + int oldVal = scripts._v2; + scripts._v2 = 0; + combat.giveCharDamage(damage, combat._damageType, 0); + scripts._v2 = oldVal; _flipGround = !_flipGround; } else if (party._partyDead) { draw3d(true); @@ -1825,6 +1840,7 @@ void Interface::doCombat() { Party &party = *_vm->_party; Screen &screen = *_vm->_screen; Scripts &scripts = *_vm->_scripts; + Spells &spells = *_vm->_spells; SoundManager &sound = *_vm->_sound; bool upDoorText = _upDoorText; bool reloadMap = false; @@ -1933,14 +1949,13 @@ void Interface::doCombat() { nextChar(); break; - case Common::KEYCODE_c: - // Cast Spell - if (combat.castSpell(false)) { - nextChar(); - } else { - highlightChar(combat._combatParty[combat._whosTurn]->_rosterId); - } + case Common::KEYCODE_c: { + // Cast spell + int spellId = CastSpell::show(_vm, _vm->_mode); + if (spellId != -1) + spells.castSpell(spellId); break; + } case Common::KEYCODE_f: // Quick Fight diff --git a/engines/xeen/resources.cpp b/engines/xeen/resources.cpp index c57b6a72cc..0fe20fb2c4 100644 --- a/engines/xeen/resources.cpp +++ b/engines/xeen/resources.cpp @@ -1498,4 +1498,19 @@ const int NEW_CHARACTER_SPELLS[10][4] = { const char *const COMBAT_DETAILS = "\r\f00\x03c\v000\t000\x02Combat%s%s%s\x1"; +const char *NOT_ENOUGH_TO_CAST = "\x03c\v010Not enough %s to Cast %s"; +const char *SPELL_CAST_COMPONENTS[2] = { "Spell Points", "Gems" }; + +const char *const CAST_SPELL_DETAILS = + "\r\x2\x3""c\v122\t013\f37C\fdast\t040\f37N\fdew" + "\t067ESC\x1\t000\v000\x3""cCast Spell\n" + "\n" + "%s\x3l\n" + "\n" + "Spell Ready:\x3""c\n" + "\n" + "\f09%s\fd\x2\x3l\n" + "\v082Cost\x3r\t000%u/%u\x3l\n" + "Cur SP\x3r\t000%u\x1"; + } // End of namespace Xeen diff --git a/engines/xeen/resources.h b/engines/xeen/resources.h index d6902129e8..30eb4f5b63 100644 --- a/engines/xeen/resources.h +++ b/engines/xeen/resources.h @@ -524,6 +524,11 @@ extern const int NEW_CHARACTER_SPELLS[10][4]; extern const char *const COMBAT_DETAILS; +extern const char *NOT_ENOUGH_TO_CAST; +extern const char *SPELL_CAST_COMPONENTS[2]; + +extern const char *const CAST_SPELL_DETAILS; + } // End of namespace Xeen #endif /* XEEN_RESOURCES_H */ diff --git a/engines/xeen/spells.cpp b/engines/xeen/spells.cpp index 4943d95d2a..20ba480a6b 100644 --- a/engines/xeen/spells.cpp +++ b/engines/xeen/spells.cpp @@ -104,6 +104,37 @@ void Spells::doSpell(int spellId) { (this->*SPELL_LIST[spellId])(); } +void Spells::castSpell(int spellId) { + // TODO + error("TODO: castSpell"); +} + +/** + * Subtract the requirements for a given spell if available, returning + * true if there was sufficient + */ +int Spells::subSpellCost(Character &c, int spellId) { + Party &party = *_vm->_party; + int gemCost = SPELL_GEM_COST[spellId]; + int spCost = SPELL_COSTS[spellId]; + + // Negative SP costs indicate it's dependent on the character's level + if (spCost <= 0) { + spCost = c.getCurrentLevel() * (spCost * -1); + } + + if (spCost > c._currentSp) + // Not enough SP + return 1; + if (gemCost > party._gems) + // Not enough gems + return 2; + + c._currentSp -= spCost; + party._gems -= gemCost; + return 0; +} + void Spells::light() { error("TODO: spell"); } void Spells::awaken() { error("TODO: spell"); } void Spells::magicArrow() { error("TODO: spell"); } diff --git a/engines/xeen/spells.h b/engines/xeen/spells.h index 15389be599..cffb302c51 100644 --- a/engines/xeen/spells.h +++ b/engines/xeen/spells.h @@ -29,6 +29,7 @@ namespace Xeen { class XeenEngine; +class Character; #define MAX_SPELLS_PER_CLASS 40 @@ -158,6 +159,10 @@ public: int calcSpellPoints(int spellId, int expenseFactor) const; void doSpell(int spellId); + + void castSpell(int spellId); + + int subSpellCost(Character &c, int spellId); }; } // End of namespace Xeen |