From 973c5a0df5bf9f86cc589a7a138899b96684bf23 Mon Sep 17 00:00:00 2001 From: Paul Gilbert Date: Sat, 28 Feb 2015 12:45:33 -0500 Subject: XEEN: Implemented multiAttack --- engines/xeen/character.cpp | 10 ++ engines/xeen/character.h | 2 + engines/xeen/combat.cpp | 276 +++++++++++++++++++++++++++++++++++++++++++-- engines/xeen/combat.h | 13 ++- engines/xeen/interface.cpp | 2 +- engines/xeen/spells.cpp | 37 +++++- 6 files changed, 324 insertions(+), 16 deletions(-) (limited to 'engines/xeen') diff --git a/engines/xeen/character.cpp b/engines/xeen/character.cpp index e005a35f13..d95e061515 100644 --- a/engines/xeen/character.cpp +++ b/engines/xeen/character.cpp @@ -1848,6 +1848,16 @@ bool Character::hasSpecialItem() const { return false; } +bool Character::hasMissileWeapon() const { + for (uint idx = 0; idx < INV_ITEMS_TOTAL; ++idx) { + if (_weapons[idx]._frame == 4) { + return !isDisabledOrDead(); + } + } + + return false; +} + int Character::getClassCategory() const { switch (_class) { case CLASS_ARCHER: diff --git a/engines/xeen/character.h b/engines/xeen/character.h index 9e858da7fa..77ab882305 100644 --- a/engines/xeen/character.h +++ b/engines/xeen/character.h @@ -326,6 +326,8 @@ public: bool hasSpecialItem() const; + bool hasMissileWeapon() const; + int getClassCategory() const; }; diff --git a/engines/xeen/combat.cpp b/engines/xeen/combat.cpp index fca7fcb3ee..763daa7d03 100644 --- a/engines/xeen/combat.cpp +++ b/engines/xeen/combat.cpp @@ -116,7 +116,7 @@ Combat::Combat(XeenEngine *vm): _vm(vm), _missVoc("miss.voc"), _pow1Voc("pow1.vo _whosSpeed = 0; _damageType = DT_PHYSICAL; _oldCharacter = nullptr; - _shootType = 0; + _shootType = ST_0; _monsterDamage = 0; _weaponDamage = 0; _weaponDie = _weaponDice = 0; @@ -125,6 +125,7 @@ Combat::Combat(XeenEngine *vm): _vm(vm), _missVoc("miss.voc"), _pow1Voc("pow1.vo _hitChanceBonus = 0; _dangerPresent = false; _moveMonsters = false; + _rangeType = RT_SINGLE; } void Combat::clear() { @@ -1198,7 +1199,7 @@ void Combat::attack(Character &c, RangeType rangeType) { MonsterStruct &monsterData = map._monsterData[monsterDataIndex]; if (rangeType) { - if (_shootType != 1 || _damageType == DT_MAGIC_ARROW) { + if (_shootType != ST_1 || _damageType == DT_MAGIC_ARROW) { if (!monsterData._magicResistence || monsterData._magicResistence <= _vm->getRandomNumber(1, 100 + _oldCharacter->getCurrentLevel())) { if (_monsterDamage != 0) { @@ -1216,25 +1217,25 @@ void Combat::attack(Character &c, RangeType rangeType) { if ((monsterData._monsterType == MONSTER_ANIMAL || monsterData._monsterType == MONSTER_HUMANOID) && !monsterSavingThrow(monsterDataIndex)) { damage = MIN(monster._hp, 50); - attack2(damage, RT_2); + attack2(damage, RT_ALL); setSpeedTable(); } break; case DT_HOLYWORD: if (monsterData._monsterType == MONSTER_UNDEAD) { - attack2(monster._hp, RT_2); + attack2(monster._hp, RT_ALL); setSpeedTable(); } break; case DT_MASS_DISTORTION: - attack2(MAX(monster._hp / 2, 1), RT_2); + attack2(MAX(monster._hp / 2, 1), RT_ALL); setSpeedTable(); break; case DT_UNDEAD: if (monsterData._monsterType == MONSTER_UNDEAD) damage = 25; else - rangeType = RT_2; + rangeType = RT_ALL; attack2(damage, rangeType); setSpeedTable(); break; @@ -1346,7 +1347,7 @@ void Combat::attack(Character &c, RangeType rangeType) { damage = 0; while (numberOfAttacks-- > 0) { - if (hitMonster(c, RT_CLOSE)) + if (hitMonster(c, RT_SINGLE)) damage += getMonsterDamage(c); } @@ -1593,7 +1594,7 @@ void Combat::quickFight() { switch (c->_quickOption) { case QUICK_ATTACK: - attack(*c, RT_CLOSE); + attack(*c, RT_SINGLE); break; case QUICK_SPELL: if (c->_currentSpell != -1) { @@ -1739,7 +1740,7 @@ int Combat::getMonsterResistence(RangeType rangeType) { MonsterStruct &monsterData = *monster._monsterData; int resistence = 0, damage = 0; - if (rangeType != RT_CLOSE && rangeType != RT_3) { + if (rangeType != RT_SINGLE && rangeType != RT_3) { switch (_damageType) { case DT_PHYSICAL: resistence = monsterData._phsyicalResistence; @@ -1825,4 +1826,261 @@ void Combat::giveExperience(int experience) { } } +void Combat::multiAttack(int powNum) { + Interface &intf = *_vm->_interface; + Map &map = *_vm->_map; + Party &party = *_vm->_party; + SoundManager &sound = *_vm->_sound; + + if (_damageType == DT_POISON_VOLLEY) { + _damageType = DT_POISON; + _shootType = ST_1; + Common::fill(&_shooting[0], &_shooting[6], 1); + } else if (powNum == 11) { + _shootType = ST_1; + bool flag = false; + + if (_damageType == DT_PHYSICAL) { + for (uint idx = 0; idx < party._activeParty.size(); ++idx) { + Character &c = party._activeParty[idx]; + if (c.hasMissileWeapon()) { + _shooting[idx] = 1; + flag = true; + } + } + } else { + _shooting[0] = 1; + flag = true; + } + + if (!flag) { + sound.playFX(21); + return; + } + } else { + _shooting[0] = 1; + _shootType = ST_0; + } + + intf._charsShooting = true; + _powSprites.load(Common::String::format("pow%d.icn", powNum)); + int monsterIndex = _monsterIndex; + int monster2Attack = _monster2Attack; + bool attackedFlag = false; + + Common::Array attackMonsters; + for (int idx = 0; idx < 3; ++idx) { + if (_attackMonsters[idx] != -1) + attackMonsters.push_back(_attackMonsters[idx]); + } + + _monsterIndex = -1; + if (_monster2Attack != -1) { + _monsterIndex--; + if (attackMonsters.empty()) + attackMonsters.resize(1); + attackMonsters[0] = monster2Attack; + } + + for (uint idx = 0; idx < party._activeParty.size(); ++idx) { + Character &c = party._activeParty[idx]; + if (_shooting[idx]) { + if (map._isOutdoors) { + intf._outdoorList._attackImgs1[idx]._scale = 0; + intf._outdoorList._attackImgs2[idx]._scale = 4; + intf._outdoorList._attackImgs3[idx]._scale = 8; + intf._outdoorList._attackImgs4[idx]._scale = 12; + intf._outdoorList._attackImgs1[idx]._sprites = &_powSprites; + intf._outdoorList._attackImgs2[idx]._sprites = nullptr; + intf._outdoorList._attackImgs3[idx]._sprites = nullptr; + intf._outdoorList._attackImgs4[idx]._sprites = nullptr; + } else { + intf._indoorList._attackImgs1[idx]._scale = 0; + intf._indoorList._attackImgs2[idx]._scale = 4; + intf._indoorList._attackImgs3[idx]._scale = 8; + intf._indoorList._attackImgs4[idx]._scale = 12; + intf._indoorList._attackImgs1[idx]._sprites = &_powSprites; + intf._indoorList._attackImgs2[idx]._sprites = nullptr; + intf._indoorList._attackImgs3[idx]._sprites = nullptr; + intf._indoorList._attackImgs4[idx]._sprites = nullptr; + } + } + } + + intf.draw3d(true); + + ++_monsterIndex; + for (uint monIdx = 0; monIdx < attackMonsters.size(); ++monIdx, ++_monsterIndex) { + Common::fill(&_missedShot[0], &_missedShot[8], false); + _monster2Attack = attackMonsters[monIdx]; + attack(*_oldCharacter, RT_GROUP); + attackedFlag = true; + + if (_rangeType == RT_SINGLE) + // Only single shot, so exit now that the attack is done + goto finished; + } + + if (attackedFlag && _rangeType == RT_GROUP) + // Finished group attack, so exit + goto finished; + + if (map._isOutdoors) { + map.getCell(7); + switch (map._currentWall) { + case 1: + case 3: + case 6: + case 7: + case 9: + case 10: + case 12: + sound.playFX(46); + goto finished; + default: + break; + } + } else { + int cell = map.getCell(2); + if (cell < map.mazeData()._difficulties._wallNoPass) { + sound.playFX(46); + goto finished; + } + } + if (!intf._isAttacking) + goto finished; + + intf.draw3d(true); + + // Start handling second teir of monsters in the back + attackMonsters.clear(); + for (uint idx = 3; idx < 6; ++idx) { + if (_attackMonsters[idx] != -1) + attackMonsters.push_back(_attackMonsters[idx]); + } + + ++_monsterIndex; + for (uint monIdx = 0; monIdx < attackMonsters.size(); ++monIdx, ++_monsterIndex) { + Common::fill(&_missedShot[0], &_missedShot[8], false); + _monster2Attack = attackMonsters[monIdx]; + attack(*_oldCharacter, RT_GROUP); + attackedFlag = true; + + if (_rangeType == RT_SINGLE) + // Only single shot, so exit now that the attack is done + goto finished; + } + + if (attackedFlag && _rangeType == RT_GROUP) + // Finished group attack, so exit + goto finished; + + if (map._isOutdoors) { + map.getCell(14); + switch (map._currentWall) { + case 1: + case 3: + case 6: + case 7: + case 9: + case 10: + case 12: + sound.playFX(46); + goto finished; + default: + break; + } + } else { + int cell = map.getCell(7); + if (cell < map.mazeData()._difficulties._wallNoPass) { + sound.playFX(46); + goto finished; + } + } + if (!intf._isAttacking) + goto finished; + + intf.draw3d(true); + + // Start handling third teir of monsters in the back + attackMonsters.clear(); + for (uint idx = 6; idx < 9; ++idx) { + if (_attackMonsters[idx] != -1) + attackMonsters.push_back(_attackMonsters[idx]); + } + + ++_monsterIndex; + for (uint monIdx = 0; monIdx < attackMonsters.size(); ++monIdx, ++_monsterIndex) { + Common::fill(&_missedShot[0], &_missedShot[8], false); + _monster2Attack = attackMonsters[monIdx]; + attack(*_oldCharacter, RT_GROUP); + attackedFlag = true; + + if (_rangeType == RT_SINGLE) + // Only single shot, so exit now that the attack is done + goto finished; + } + + if (attackedFlag && _rangeType == RT_GROUP) + // Finished group attack, so exit + goto finished; + + if (map._isOutdoors) { + map.getCell(27); + switch (map._currentWall) { + case 1: + case 3: + case 6: + case 7: + case 9: + case 10: + case 12: + sound.playFX(46); + goto finished; + default: + break; + } + } else { + int cell = map.getCell(14); + if (cell < map.mazeData()._difficulties._wallNoPass) { + sound.playFX(46); + goto finished; + } + } + if (!intf._isAttacking) + goto finished; + + intf.draw3d(true); + + // Fourth tier + attackMonsters.clear(); + for (uint idx = 9; idx < 12; ++idx) { + if (_attackMonsters[idx] != -1) + attackMonsters.push_back(_attackMonsters[idx]); + } + + ++_monsterIndex; + for (uint monIdx = 0; monIdx < attackMonsters.size(); ++monIdx, ++_monsterIndex) { + Common::fill(&_missedShot[0], &_missedShot[8], false); + _monster2Attack = attackMonsters[monIdx]; + attack(*_oldCharacter, RT_GROUP); + attackedFlag = true; + + if (_rangeType == RT_SINGLE) + // Only single shot, so exit now that the attack is done + goto finished; + } + + if (!(attackedFlag && _rangeType == RT_GROUP)) + goto done; + +finished: + endAttack(); +done: + Common::fill(&_shooting[0], &_shooting[MAX_PARTY_COUNT], 0); + _monster2Attack = monster2Attack; + _monsterIndex = monsterIndex; + party.giveTreasure(); +} + } // End of namespace Xeen diff --git a/engines/xeen/combat.h b/engines/xeen/combat.h index bf35ca819d..9831f90f25 100644 --- a/engines/xeen/combat.h +++ b/engines/xeen/combat.h @@ -39,7 +39,7 @@ enum DamageType { DT_FINGEROFDEATH = 8, DT_HOLYWORD = 9, DT_MASS_DISTORTION = 10, DT_UNDEAD = 11, DT_BEASTMASTER = 12, DT_DRAGONSLEEP = 13, DT_GOLEMSTOPPER = 14, DT_HYPNOTIZE = 15, DT_INSECT_SPRAY = 16, - DT_POISON_VALLEY = 17, DT_MAGIC_ARROW = 18 + DT_POISON_VOLLEY = 17, DT_MAGIC_ARROW = 18 }; enum SpecialAttack { @@ -57,7 +57,11 @@ enum ElementalCategory { }; enum RangeType { - RT_CLOSE = 0, RT_1 = 1, RT_2 = 2, RT_3 = 3 + RT_SINGLE = 0, RT_GROUP = 1, RT_ALL = 2, RT_3 = 3 +}; + +enum ShootType { + ST_0 = 0, ST_1 = 1 }; class XeenEngine; @@ -110,7 +114,6 @@ public: int _whosSpeed; DamageType _damageType; Character *_oldCharacter; - int _shootType; int _monsterDamage; int _weaponDamage; int _weaponDie, _weaponDice; @@ -120,6 +123,8 @@ public: int _hitChanceBonus; bool _dangerPresent; bool _moveMonsters; + RangeType _rangeType; + ShootType _shootType; public: Combat(XeenEngine *vm); @@ -165,6 +170,8 @@ public: void monsterOvercome(); int stopAttack(const Common::Point &diffPt); + + void multiAttack(int powNum); }; } // End of namespace Xeen diff --git a/engines/xeen/interface.cpp b/engines/xeen/interface.cpp index 0354732b2b..7339e1014f 100644 --- a/engines/xeen/interface.cpp +++ b/engines/xeen/interface.cpp @@ -1967,7 +1967,7 @@ void Interface::doCombat() { case Common::KEYCODE_a: // Attack - combat.attack(*combat._combatParty[combat._whosTurn], RT_CLOSE); + combat.attack(*combat._combatParty[combat._whosTurn], RT_SINGLE); nextChar(); break; diff --git a/engines/xeen/spells.cpp b/engines/xeen/spells.cpp index 25aae11af2..53c43b5233 100644 --- a/engines/xeen/spells.cpp +++ b/engines/xeen/spells.cpp @@ -198,10 +198,41 @@ void Spells::addSpellCost(Character &c, int spellId) { party._gems += gemCost; } +void Spells::light() { + Interface &intf = *_vm->_interface; + Party &party = *_vm->_party; + SoundManager &sound = *_vm->_sound; + + ++party._lightCount; + if (intf._intrIndex1) + party._stepped = true; + sound.playFX(39); +} + +void Spells::awaken() { + Interface &intf = *_vm->_interface; + Party &party = *_vm->_party; + SoundManager &sound = *_vm->_sound; + + for (uint idx = 0; idx < party._activeParty.size(); ++idx) { + Character &c = party._activeParty[idx]; + c._conditions[ASLEEP] = 0; + if (c._currentHp > 0) + c._conditions[UNCONSCIOUS] = 0; + } + + intf.drawParty(true); + sound.playFX(30); +} + +void Spells::magicArrow() { + Combat &combat = *_vm->_combat; + combat._monsterDamage = 0; + combat._damageType = DT_MAGIC_ARROW; + combat._rangeType = RT_SINGLE; + combat.multiAttack(11); +} -void Spells::light() { error("TODO: spell"); } -void Spells::awaken() { error("TODO: spell"); } -void Spells::magicArrow() { error("TODO: spell"); } void Spells::firstAid() { error("TODO: spell"); } void Spells::flyingFist() { error("TODO: spell"); } void Spells::energyBlast() { error("TODO: spell"); } -- cgit v1.2.3