From 0b70baa95e383aaf5287c4af5dea766f1b2388d0 Mon Sep 17 00:00:00 2001 From: AlexVinS Date: Thu, 20 Jul 2017 07:08:49 +0300 Subject: [PATCH] Spells configuration version 2 (effect-based) * Indirect spell effects loading * Json serializer improvements * spell->canBeCastAt do not allow useless cast for any spell * Added proxy caster class for spell-created obstacles * Handle damage from spell-created obstacles inside mechanics * Experimental GameState integration/regression tests * Ignore mod settings and load only "vcmi" mod when running tests * fixed https://bugs.vcmi.eu/view.php?id=2765 (with tests) * Huge improvements of BattleAI regarding spell casts * AI can cast almost any combat spell except TELEPORT, SACRIFICE and obstacle placement spells. * Possible fix for https://bugs.vcmi.eu/view.php?id=1811 * CStack factored out to several classes * [Battle] Allowed RETURN_AFTER_STRIKE effect on server side to be optional * [Battle] Allowed BattleAction have multiple destinations * [Spells] Converted limit|immunity to target condition * [Spells] Use partial configuration reload for backward compatibility handling * [Tests] Started tests for CUnitState * Partial fixes of fire shield effect * [Battle] Do HP calculations in 64 bits * [BattleAI] Use threading for spell cast evaluation * [BattleAI] Made AI be able to evaluate modified turn order (on hypothetical battle state) * Implemented https://bugs.vcmi.eu/view.php?id=2811 * plug rare freeze when hypnotized unit shots vertically * Correctly apply ONLY_MELEE_FIGHT / ONLY_DISTANCE_FIGHT for unit damage, attack & defense * [BattleAI] Try to not waste a cast if battle is actually won already * Extended JsonSerializeFormat API * fixed https://bugs.vcmi.eu/view.php?id=2847 * Any unit effect can be now chained (not only damage like Chain Lightning) ** only damage effect for now actually uses "chainFactor" * Possible quick fix for https://bugs.vcmi.eu/view.php?id=2860 --- AI/BattleAI/AttackPossibility.cpp | 93 +- AI/BattleAI/AttackPossibility.h | 42 +- AI/BattleAI/BattleAI.cbp | 5 + AI/BattleAI/BattleAI.cpp | 430 +++-- AI/BattleAI/BattleAI.h | 14 +- AI/BattleAI/CMakeLists.txt | 2 + AI/BattleAI/EnemyInfo.cpp | 12 +- AI/BattleAI/EnemyInfo.h | 19 +- AI/BattleAI/PossibleSpellcast.cpp | 21 + AI/BattleAI/PossibleSpellcast.h | 29 + AI/BattleAI/PotentialTargets.cpp | 67 +- AI/BattleAI/PotentialTargets.h | 6 +- AI/BattleAI/StackWithBonuses.cpp | 374 ++++- AI/BattleAI/StackWithBonuses.h | 100 +- AI/StupidAI/StupidAI.cpp | 49 +- AI/StupidAI/StupidAI.h | 6 +- AI/VCAI/VCAI.cpp | 4 +- CCallback.cpp | 11 +- CCallback.h | 5 +- .../Sprites/vcmi/battleQueue/defendBig.png | Bin 0 -> 2498 bytes .../Sprites/vcmi/battleQueue/defendSmall.png | Bin 0 -> 733 bytes .../Sprites/vcmi/battleQueue/statesBig.json | 8 + .../Sprites/vcmi/battleQueue/statesSmall.json | 8 + .../vcmi/Sprites/vcmi/battleQueue/waitBig.png | Bin 0 -> 1721 bytes .../Sprites/vcmi/battleQueue/waitSmall.png | Bin 0 -> 759 bytes client/CDefHandler.cpp | 356 ---- client/CDefHandler.h | 94 -- client/CMT.cpp | 40 + client/CMakeLists.txt | 2 - client/CPlayerInterface.cpp | 214 +-- client/CPlayerInterface.h | 9 +- client/CPreGame.cpp | 3 +- client/Client.cpp | 8 +- client/Client.h | 2 +- client/NetPacksClient.cpp | 49 +- client/VCMI_client.cbp | 2 - client/battle/CBattleAnimations.cpp | 86 +- client/battle/CBattleAnimations.h | 5 +- client/battle/CBattleInterface.cpp | 683 ++++---- client/battle/CBattleInterface.h | 43 +- client/battle/CBattleInterfaceClasses.cpp | 155 +- client/battle/CBattleInterfaceClasses.h | 22 +- client/windows/CCastleInterface.cpp | 6 +- client/windows/CCreatureWindow.cpp | 14 +- client/windows/CHeroWindow.cpp | 5 + client/windows/CHeroWindow.h | 4 +- client/windows/CSpellWindow.cpp | 132 +- client/windows/CreaturePurchaseCard.cpp | 1 + client/windows/QuickRecruitmentWindow.cpp | 3 +- config/schemas/settings.json | 7 +- config/schemas/spell.json | 11 +- config/spells/ability.json | 163 +- config/spells/offensive.json | 201 ++- config/spells/other.json | 433 ++++- config/spells/timed.json | 281 ++-- include/vstd/RNG.h | 58 + lib/CArtHandler.cpp | 20 +- lib/CArtHandler.h | 6 - lib/CCreatureHandler.cpp | 14 - lib/CCreatureHandler.h | 6 - lib/CGameInfoCallback.cpp | 6 +- lib/CGameInfoCallback.h | 7 +- lib/CGameInterface.cpp | 25 +- lib/CGameInterface.h | 10 +- lib/CGameState.cpp | 17 +- lib/CGameState.h | 10 +- lib/CMakeLists.txt | 53 +- lib/CModHandler.cpp | 125 +- lib/CModHandler.h | 72 +- lib/CRandomGenerator.cpp | 9 +- lib/CRandomGenerator.h | 42 +- lib/CSkillHandler.cpp | 6 +- lib/CSkillHandler.h | 2 +- lib/CStack.cpp | 824 ++------- lib/CStack.h | 238 +-- lib/CThreadHelper.cpp | 6 +- lib/GameConstants.cpp | 79 +- lib/GameConstants.h | 97 +- lib/HeroBonus.cpp | 151 +- lib/HeroBonus.h | 31 +- lib/IBonusTypeHandler.h | 4 +- lib/IGameEventsReceiver.h | 16 +- lib/IHandlerBase.h | 9 +- lib/JsonNode.h | 2 +- lib/NetPacks.h | 254 +-- lib/NetPacksBase.h | 156 +- lib/NetPacksLib.cpp | 505 ++---- lib/VCMI_Lib.cpp | 16 +- lib/VCMI_Lib.h | 8 +- lib/VCMI_lib.cbp | 70 +- lib/VCMI_lib.vcxproj | 22 + lib/battle/AccessibilityInfo.cpp | 26 +- lib/battle/AccessibilityInfo.h | 7 +- lib/battle/BattleAction.cpp | 151 +- lib/battle/BattleAction.h | 67 +- lib/battle/BattleAttackInfo.cpp | 31 +- lib/battle/BattleAttackInfo.h | 23 +- lib/battle/BattleHex.cpp | 27 +- lib/battle/BattleHex.h | 12 + lib/battle/BattleInfo.cpp | 562 +++++-- lib/battle/BattleInfo.h | 86 +- lib/battle/BattleProxy.cpp | 113 ++ lib/battle/BattleProxy.h | 54 + lib/battle/CBattleInfoCallback.cpp | 823 +++++---- lib/battle/CBattleInfoCallback.h | 62 +- lib/battle/CBattleInfoEssentials.cpp | 205 ++- lib/battle/CBattleInfoEssentials.h | 50 +- lib/battle/CCallbackBase.cpp | 13 +- lib/battle/CCallbackBase.h | 15 +- lib/battle/CObstacleInstance.cpp | 158 +- lib/battle/CObstacleInstance.h | 64 +- lib/battle/CUnitState.cpp | 1047 ++++++++++++ lib/battle/CUnitState.h | 335 ++++ lib/battle/Destination.cpp | 64 + lib/battle/Destination.h | 39 + lib/battle/IBattleState.cpp | 13 + lib/battle/IBattleState.h | 91 + lib/battle/IUnitInfo.h | 43 + lib/battle/ReachabilityInfo.cpp | 18 +- lib/battle/ReachabilityInfo.h | 6 +- lib/battle/SideInBattle.h | 4 +- lib/battle/Unit.cpp | 212 +++ lib/battle/Unit.h | 129 ++ lib/filesystem/FileInfo.cpp | 2 +- lib/filesystem/FileInfo.h | 2 +- lib/mapObjects/CArmedInstance.h | 2 +- lib/mapObjects/CGHeroInstance.cpp | 78 +- lib/mapObjects/CGHeroInstance.h | 25 +- lib/mapObjects/CGPandoraBox.cpp | 32 +- lib/mapObjects/CGTownInstance.cpp | 2 +- lib/mapObjects/CObjectHandler.cpp | 9 +- lib/mapObjects/CQuest.cpp | 2 +- lib/mapObjects/MiscObjects.cpp | 33 +- lib/mapping/CCampaignHandler.cpp | 3 +- lib/mapping/CMapInfo.cpp | 3 +- lib/mapping/CMapService.cpp | 10 +- lib/mapping/CMapService.h | 27 +- lib/mapping/MapFormatJson.cpp | 77 +- lib/registerTypes/RegisterTypes.h | 9 +- lib/serializer/CSerializer.h | 2 +- lib/serializer/JsonDeserializer.cpp | 48 +- lib/serializer/JsonDeserializer.h | 13 +- lib/serializer/JsonSerializeFormat.cpp | 134 +- lib/serializer/JsonSerializeFormat.h | 120 +- lib/serializer/JsonSerializer.cpp | 63 +- lib/serializer/JsonSerializer.h | 16 +- lib/serializer/JsonTreeSerializer.h | 68 + lib/spells/AdventureSpellMechanics.cpp | 2 + lib/spells/BattleSpellMechanics.cpp | 1495 ++++++----------- lib/spells/BattleSpellMechanics.h | 238 +-- lib/spells/CDefaultSpellMechanics.cpp | 759 ++------- lib/spells/CDefaultSpellMechanics.h | 93 +- lib/spells/CSpellHandler.cpp | 676 ++++---- lib/spells/CSpellHandler.h | 169 +- lib/spells/CreatureSpellMechanics.cpp | 122 -- lib/spells/CreatureSpellMechanics.h | 41 - lib/spells/CustomSpellMechanics.cpp | 347 ++++ lib/spells/CustomSpellMechanics.h | 58 + lib/spells/ISpellMechanics.cpp | 799 +++++++-- lib/spells/ISpellMechanics.h | 340 +++- lib/spells/Magic.h | 107 +- lib/spells/Problem.cpp | 54 + lib/spells/Problem.h | 38 + lib/spells/TargetCondition.cpp | 532 ++++++ lib/spells/TargetCondition.h | 84 + lib/spells/effects/Catapult.cpp | 155 ++ lib/spells/effects/Catapult.h | 36 + lib/spells/effects/Clone.cpp | 132 ++ lib/spells/effects/Clone.h | 37 + lib/spells/effects/Damage.cpp | 224 +++ lib/spells/effects/Damage.h | 48 + lib/spells/effects/Dispel.cpp | 142 ++ lib/spells/effects/Dispel.h | 49 + lib/spells/effects/Effect.cpp | 66 + lib/spells/effects/Effect.h | 76 + lib/spells/effects/Effects.cpp | 156 ++ lib/spells/effects/Effects.h | 48 + lib/spells/effects/EffectsFwd.h | 23 + lib/spells/effects/Heal.cpp | 139 ++ lib/spells/effects/Heal.h | 47 + lib/spells/effects/LocationEffect.cpp | 55 + lib/spells/effects/LocationEffect.h | 40 + lib/spells/effects/Obstacle.cpp | 337 ++++ lib/spells/effects/Obstacle.h | 79 + lib/spells/effects/Registry.cpp | 62 + lib/spells/effects/Registry.h | 74 + lib/spells/effects/RemoveObstacle.cpp | 114 ++ lib/spells/effects/RemoveObstacle.h | 52 + lib/spells/effects/Sacrifice.cpp | 179 ++ lib/spells/effects/Sacrifice.h | 43 + lib/spells/effects/Summon.cpp | 180 ++ lib/spells/effects/Summon.h | 49 + lib/spells/effects/Teleport.cpp | 136 ++ lib/spells/effects/Teleport.h | 44 + lib/spells/effects/Timed.cpp | 255 +++ lib/spells/effects/Timed.h | 45 + lib/spells/effects/UnitEffect.cpp | 302 ++++ lib/spells/effects/UnitEffect.h | 60 + server/CGameHandler.cpp | 1216 ++++++++------ server/CGameHandler.h | 16 +- server/NetPacksServer.cpp | 22 +- test/CMakeLists.txt | 59 +- test/CVcmiTestConfig.cpp | 4 +- test/Test.cbp | 55 +- test/battle/BattleHexTest.cpp | 6 +- test/battle/CBattleInfoCallbackTest.cpp | 419 +++++ test/battle/CHealthTest.cpp | 50 +- test/battle/CUnitStateMagicTest.cpp | 223 +++ test/battle/CUnitStateTest.cpp | 241 +++ test/battle/battle_UnitTest.cpp | 144 ++ test/game/CGameStateTest.cpp | 330 ++++ test/map/CMapEditManagerTest.cpp | 5 +- test/map/CMapFormatTest.cpp | 2 - test/mock/mock_BonusBearer.cpp | 47 + test/mock/mock_BonusBearer.h | 32 + test/mock/mock_IGameCallback.cpp | 38 + test/mock/mock_IGameCallback.h | 92 + test/mock/mock_MapService.cpp | 92 + test/mock/mock_MapService.h | 48 + test/mock/mock_UnitEnvironment.h | 20 + test/mock/mock_UnitHealthInfo.h | 7 +- test/mock/mock_UnitInfo.h | 28 + test/mock/mock_battle_IBattleState.h | 53 + test/mock/mock_battle_Unit.h | 91 + test/mock/mock_spells_Mechanics.h | 70 + test/mock/mock_spells_Problem.h | 30 + test/mock/mock_spells_Spell.h | 26 + test/mock/mock_vstd_RNG.h | 25 + test/spells/TargetConditionTest.cpp | 306 ++++ test/spells/effects/CatapultTest.cpp | 138 ++ test/spells/effects/CloneTest.cpp | 235 +++ test/spells/effects/DamageTest.cpp | 224 +++ test/spells/effects/DispelTest.cpp | 213 +++ test/spells/effects/EffectFixture.cpp | 160 ++ test/spells/effects/EffectFixture.h | 103 ++ test/spells/effects/HealTest.cpp | 354 ++++ test/spells/effects/SacrificeTest.cpp | 214 +++ test/spells/effects/SummonTest.cpp | 253 +++ test/spells/effects/TeleportTest.cpp | 72 + test/spells/effects/TimedTest.cpp | 38 + .../AbsoluteLevelConditionTest.cpp | 99 ++ .../AbsoluteSpellConditionTest.cpp | 76 + .../targetConditions/BonusConditionTest.cpp | 56 + .../CreatureConditionTest.cpp | 52 + .../ElementalConditionTest.cpp | 86 + .../HealthValueConditionTest.cpp | 68 + .../ImmunityNegationConditionTest.cpp | 80 + .../NormalLevelConditionTest.cpp | 77 + .../NormalSpellConditionTest.cpp | 79 + .../ReceptiveFeatureConditionTest.cpp | 65 + .../SpellEffectConditionTest.cpp | 68 + .../TargetConditionItemFixture.cpp | 12 + .../TargetConditionItemFixture.h | 43 + test/testdata/MiniTest/header.json | 65 + test/testdata/MiniTest/objects.json | 50 + test/testdata/MiniTest/surface_terrain.json | 8 + 256 files changed, 20904 insertions(+), 7964 deletions(-) create mode 100644 AI/BattleAI/PossibleSpellcast.cpp create mode 100644 AI/BattleAI/PossibleSpellcast.h create mode 100644 Mods/vcmi/Sprites/vcmi/battleQueue/defendBig.png create mode 100644 Mods/vcmi/Sprites/vcmi/battleQueue/defendSmall.png create mode 100644 Mods/vcmi/Sprites/vcmi/battleQueue/statesBig.json create mode 100644 Mods/vcmi/Sprites/vcmi/battleQueue/statesSmall.json create mode 100644 Mods/vcmi/Sprites/vcmi/battleQueue/waitBig.png create mode 100644 Mods/vcmi/Sprites/vcmi/battleQueue/waitSmall.png delete mode 100644 client/CDefHandler.cpp delete mode 100644 client/CDefHandler.h create mode 100644 include/vstd/RNG.h create mode 100644 lib/battle/BattleProxy.cpp create mode 100644 lib/battle/BattleProxy.h create mode 100644 lib/battle/CUnitState.cpp create mode 100644 lib/battle/CUnitState.h create mode 100644 lib/battle/Destination.cpp create mode 100644 lib/battle/Destination.h create mode 100644 lib/battle/IBattleState.cpp create mode 100644 lib/battle/IBattleState.h create mode 100644 lib/battle/IUnitInfo.h create mode 100644 lib/battle/Unit.cpp create mode 100644 lib/battle/Unit.h create mode 100644 lib/serializer/JsonTreeSerializer.h delete mode 100644 lib/spells/CreatureSpellMechanics.cpp delete mode 100644 lib/spells/CreatureSpellMechanics.h create mode 100644 lib/spells/CustomSpellMechanics.cpp create mode 100644 lib/spells/CustomSpellMechanics.h create mode 100644 lib/spells/Problem.cpp create mode 100644 lib/spells/Problem.h create mode 100644 lib/spells/TargetCondition.cpp create mode 100644 lib/spells/TargetCondition.h create mode 100644 lib/spells/effects/Catapult.cpp create mode 100644 lib/spells/effects/Catapult.h create mode 100644 lib/spells/effects/Clone.cpp create mode 100644 lib/spells/effects/Clone.h create mode 100644 lib/spells/effects/Damage.cpp create mode 100644 lib/spells/effects/Damage.h create mode 100644 lib/spells/effects/Dispel.cpp create mode 100644 lib/spells/effects/Dispel.h create mode 100644 lib/spells/effects/Effect.cpp create mode 100644 lib/spells/effects/Effect.h create mode 100644 lib/spells/effects/Effects.cpp create mode 100644 lib/spells/effects/Effects.h create mode 100644 lib/spells/effects/EffectsFwd.h create mode 100644 lib/spells/effects/Heal.cpp create mode 100644 lib/spells/effects/Heal.h create mode 100644 lib/spells/effects/LocationEffect.cpp create mode 100644 lib/spells/effects/LocationEffect.h create mode 100644 lib/spells/effects/Obstacle.cpp create mode 100644 lib/spells/effects/Obstacle.h create mode 100644 lib/spells/effects/Registry.cpp create mode 100644 lib/spells/effects/Registry.h create mode 100644 lib/spells/effects/RemoveObstacle.cpp create mode 100644 lib/spells/effects/RemoveObstacle.h create mode 100644 lib/spells/effects/Sacrifice.cpp create mode 100644 lib/spells/effects/Sacrifice.h create mode 100644 lib/spells/effects/Summon.cpp create mode 100644 lib/spells/effects/Summon.h create mode 100644 lib/spells/effects/Teleport.cpp create mode 100644 lib/spells/effects/Teleport.h create mode 100644 lib/spells/effects/Timed.cpp create mode 100644 lib/spells/effects/Timed.h create mode 100644 lib/spells/effects/UnitEffect.cpp create mode 100644 lib/spells/effects/UnitEffect.h create mode 100644 test/battle/CBattleInfoCallbackTest.cpp create mode 100644 test/battle/CUnitStateMagicTest.cpp create mode 100644 test/battle/CUnitStateTest.cpp create mode 100644 test/battle/battle_UnitTest.cpp create mode 100644 test/game/CGameStateTest.cpp create mode 100644 test/mock/mock_BonusBearer.cpp create mode 100644 test/mock/mock_BonusBearer.h create mode 100644 test/mock/mock_IGameCallback.cpp create mode 100644 test/mock/mock_IGameCallback.h create mode 100644 test/mock/mock_MapService.cpp create mode 100644 test/mock/mock_MapService.h create mode 100644 test/mock/mock_UnitEnvironment.h create mode 100644 test/mock/mock_UnitInfo.h create mode 100644 test/mock/mock_battle_IBattleState.h create mode 100644 test/mock/mock_battle_Unit.h create mode 100644 test/mock/mock_spells_Mechanics.h create mode 100644 test/mock/mock_spells_Problem.h create mode 100644 test/mock/mock_spells_Spell.h create mode 100644 test/mock/mock_vstd_RNG.h create mode 100644 test/spells/TargetConditionTest.cpp create mode 100644 test/spells/effects/CatapultTest.cpp create mode 100644 test/spells/effects/CloneTest.cpp create mode 100644 test/spells/effects/DamageTest.cpp create mode 100644 test/spells/effects/DispelTest.cpp create mode 100644 test/spells/effects/EffectFixture.cpp create mode 100644 test/spells/effects/EffectFixture.h create mode 100644 test/spells/effects/HealTest.cpp create mode 100644 test/spells/effects/SacrificeTest.cpp create mode 100644 test/spells/effects/SummonTest.cpp create mode 100644 test/spells/effects/TeleportTest.cpp create mode 100644 test/spells/effects/TimedTest.cpp create mode 100644 test/spells/targetConditions/AbsoluteLevelConditionTest.cpp create mode 100644 test/spells/targetConditions/AbsoluteSpellConditionTest.cpp create mode 100644 test/spells/targetConditions/BonusConditionTest.cpp create mode 100644 test/spells/targetConditions/CreatureConditionTest.cpp create mode 100644 test/spells/targetConditions/ElementalConditionTest.cpp create mode 100644 test/spells/targetConditions/HealthValueConditionTest.cpp create mode 100644 test/spells/targetConditions/ImmunityNegationConditionTest.cpp create mode 100644 test/spells/targetConditions/NormalLevelConditionTest.cpp create mode 100644 test/spells/targetConditions/NormalSpellConditionTest.cpp create mode 100644 test/spells/targetConditions/ReceptiveFeatureConditionTest.cpp create mode 100644 test/spells/targetConditions/SpellEffectConditionTest.cpp create mode 100644 test/spells/targetConditions/TargetConditionItemFixture.cpp create mode 100644 test/spells/targetConditions/TargetConditionItemFixture.h create mode 100644 test/testdata/MiniTest/header.json create mode 100644 test/testdata/MiniTest/objects.json create mode 100644 test/testdata/MiniTest/surface_terrain.json diff --git a/AI/BattleAI/AttackPossibility.cpp b/AI/BattleAI/AttackPossibility.cpp index bde5b2071..3ede99aa0 100644 --- a/AI/BattleAI/AttackPossibility.cpp +++ b/AI/BattleAI/AttackPossibility.cpp @@ -10,53 +10,88 @@ #include "StdInc.h" #include "AttackPossibility.h" -int AttackPossibility::damageDiff() const +AttackPossibility::AttackPossibility(BattleHex tile_, const BattleAttackInfo & attack_) + : tile(tile_), + attack(attack_) { - if (!priorities) - priorities = new Priorities(); - const auto dealtDmgValue = priorities->stackEvaluator(enemy) * damageDealt; - const auto receivedDmgValue = priorities->stackEvaluator(attack.attacker) * damageReceived; - return dealtDmgValue - receivedDmgValue; } -int AttackPossibility::attackValue() const + +int64_t AttackPossibility::damageDiff() const +{ + //TODO: use target priority from HypotheticBattle + const auto dealtDmgValue = damageDealt; + const auto receivedDmgValue = damageReceived; + + int64_t diff = 0; + + //friendly fire or not + if(attack.attacker->unitSide() == attack.defender->unitSide()) + diff = -dealtDmgValue - receivedDmgValue; + else + diff = dealtDmgValue - receivedDmgValue; + + //mind control + auto actualSide = getCbc()->playerToSide(getCbc()->battleGetOwner(attack.attacker)); + if(actualSide && actualSide.get() != attack.attacker->unitSide()) + diff = -diff; + return diff; +} + +int64_t AttackPossibility::attackValue() const { return damageDiff() + tacticImpact; } -AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo &AttackInfo, const HypotheticChangesToBattleState &state, BattleHex hex) +AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInfo, BattleHex hex) { - auto attacker = AttackInfo.attacker; - auto enemy = AttackInfo.defender; + const std::string cachingStringBlocksRetaliation = "type_BLOCKS_RETALIATION"; + static const auto selectorBlocksRetaliation = Selector::type(Bonus::BLOCKS_RETALIATION); - const int remainingCounterAttacks = getValOr(state.counterAttacksLeft, enemy, enemy->counterAttacks.available()); - const bool counterAttacksBlocked = attacker->hasBonusOfType(Bonus::BLOCKS_RETALIATION) || enemy->hasBonusOfType(Bonus::NO_RETALIATION); - const int totalAttacks = 1 + AttackInfo.attackerBonuses->getBonuses(Selector::type(Bonus::ADDITIONAL_ATTACK), (Selector::effectRange (Bonus::NO_LIMIT).Or(Selector::effectRange(Bonus::ONLY_MELEE_FIGHT))))->totalValue(); + const bool counterAttacksBlocked = attackInfo.attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation); - AttackPossibility ap = {enemy, hex, AttackInfo, 0, 0, 0}; + AttackPossibility ap(hex, attackInfo); - auto curBai = AttackInfo; //we'll modify here the stack counts - for(int i = 0; i < totalAttacks; i++) + ap.attackerState = attackInfo.attacker->acquireState(); + + const int totalAttacks = ap.attackerState->getTotalAttacks(attackInfo.shooting); + + if(!attackInfo.shooting) + ap.attackerState->setPosition(hex); + + auto defenderState = attackInfo.defender->acquireState(); + ap.affectedUnits.push_back(defenderState); + + for(int i = 0; i < totalAttacks; i++) { - std::pair retaliation(0,0); - auto attackDmg = getCbc()->battleEstimateDamage(CRandomGenerator::getDefault(), curBai, &retaliation); - ap.damageDealt = (attackDmg.first + attackDmg.second) / 2; - ap.damageReceived = (retaliation.first + retaliation.second) / 2; + TDmgRange retaliation(0,0); + auto attackDmg = getCbc()->battleEstimateDamage(ap.attack, &retaliation); - if(remainingCounterAttacks <= i || counterAttacksBlocked) - ap.damageReceived = 0; + vstd::amin(attackDmg.first, defenderState->getAvailableHealth()); + vstd::amin(attackDmg.second, defenderState->getAvailableHealth()); - curBai.attackerHealth = attacker->healthAfterAttacked(ap.damageReceived); - curBai.defenderHealth = enemy->healthAfterAttacked(ap.damageDealt); - if(curBai.attackerHealth.getCount() <= 0) + vstd::amin(retaliation.first, ap.attackerState->getAvailableHealth()); + vstd::amin(retaliation.second, ap.attackerState->getAvailableHealth()); + + ap.damageDealt += (attackDmg.first + attackDmg.second) / 2; + + ap.attackerState->afterAttack(attackInfo.shooting, false); + + //FIXME: use ranged retaliation + if(!attackInfo.shooting && defenderState->ableToRetaliate() && !counterAttacksBlocked) + { + ap.damageReceived += (retaliation.first + retaliation.second) / 2; + defenderState->afterAttack(attackInfo.shooting, true); + } + + ap.attackerState->damage(ap.damageReceived); + defenderState->damage(ap.damageDealt); + + if(!ap.attackerState->alive() || !defenderState->alive()) break; - //TODO what about defender? should we break? but in pessimistic scenario defender might be alive } //TODO other damage related to attack (eg. fire shield and other abilities) return ap; } - - -Priorities* AttackPossibility::priorities = nullptr; diff --git a/AI/BattleAI/AttackPossibility.h b/AI/BattleAI/AttackPossibility.h index ac1fb321c..775951c25 100644 --- a/AI/BattleAI/AttackPossibility.h +++ b/AI/BattleAI/AttackPossibility.h @@ -8,43 +8,29 @@ * */ #pragma once -#include "../../lib/CStack.h" +#include "../../lib/battle/CUnitState.h" #include "../../CCallback.h" #include "common.h" - - -struct HypotheticChangesToBattleState -{ - std::map bonusesOfStacks; - std::map counterAttacksLeft; -}; - -class Priorities -{ -public: - std::vector resourceTypeBaseValues; - std::function stackEvaluator; - Priorities() - { - // range::copy(VLC->objh->resVals, std::back_inserter(resourceTypeBaseValues)); - stackEvaluator = [](const CStack*){ return 1.0; }; - } -}; +#include "StackWithBonuses.h" class AttackPossibility { public: - const CStack *enemy; //redundant (to attack.defender) but looks nice BattleHex tile; //tile from which we attack BattleAttackInfo attack; - int damageDealt; - int damageReceived; //usually by counter-attack - int tacticImpact; + std::shared_ptr attackerState; - int damageDiff() const; - int attackValue() const; + std::vector> affectedUnits; - static AttackPossibility evaluate(const BattleAttackInfo &AttackInfo, const HypotheticChangesToBattleState &state, BattleHex hex); - static Priorities * priorities; + int64_t damageDealt = 0; + int64_t damageReceived = 0; //usually by counter-attack + int64_t tacticImpact = 0; + + AttackPossibility(BattleHex tile_, const BattleAttackInfo & attack_); + + int64_t damageDiff() const; + int64_t attackValue() const; + + static AttackPossibility evaluate(const BattleAttackInfo & attackInfo, BattleHex hex); }; diff --git a/AI/BattleAI/BattleAI.cbp b/AI/BattleAI/BattleAI.cbp index 6f7dfa9a4..b424775e1 100644 --- a/AI/BattleAI/BattleAI.cbp +++ b/AI/BattleAI/BattleAI.cbp @@ -58,6 +58,7 @@ + @@ -65,6 +66,7 @@ + @@ -73,8 +75,11 @@ + + + diff --git a/AI/BattleAI/BattleAI.cpp b/AI/BattleAI/BattleAI.cpp index bca39ba2b..5bf1cb3b0 100644 --- a/AI/BattleAI/BattleAI.cpp +++ b/AI/BattleAI/BattleAI.cpp @@ -9,13 +9,57 @@ */ #include "StdInc.h" #include "BattleAI.h" + +#include + #include "StackWithBonuses.h" #include "EnemyInfo.h" +#include "PossibleSpellcast.h" +#include "../../lib/CStopWatch.h" +#include "../../lib/CThreadHelper.h" #include "../../lib/spells/CSpellHandler.h" +#include "../../lib/spells/ISpellMechanics.h" +#include "../../lib/CStack.h"//todo: remove #define LOGL(text) print(text) #define LOGFL(text, formattingEl) print(boost::str(boost::format(text) % formattingEl)) +class RNGStub : public vstd::RNG +{ +public: + vstd::TRandI64 getInt64Range(int64_t lower, int64_t upper) override + { + return [=]()->int64_t + { + return (lower + upper)/2; + }; + } + + vstd::TRand getDoubleRange(double lower, double upper) override + { + return [=]()->double + { + return (lower + upper)/2; + }; + } +}; + +enum class SpellTypes +{ + ADVENTURE, BATTLE, OTHER +}; + +SpellTypes spellType(const CSpell * spell) +{ + if(!spell->isCombatSpell() || spell->isCreatureAbility()) + return SpellTypes::OTHER; + + if(spell->isOffensiveSpell() || spell->hasEffects() || spell->hasBattleEffects()) + return SpellTypes::BATTLE; + + return SpellTypes::OTHER; +} + CBattleAI::CBattleAI() : side(-1), wasWaitingForRealize(false), wasUnlockingGs(false) { @@ -70,31 +114,38 @@ BattleAction CBattleAI::activeStack( const CStack * stack ) //spellcast may finish battle //send special preudo-action BattleAction cancel; - cancel.actionType = Battle::CANCEL; + cancel.actionType = EActionType::CANCEL; return cancel; } if(auto action = considerFleeingOrSurrendering()) return *action; - PotentialTargets targets(stack); + //best action is from effective owner point if view, we are effective owner as we received "activeStack" + + HypotheticBattle hb(getCbc()); + + PotentialTargets targets(stack, &hb); if(targets.possibleAttacks.size()) { auto hlp = targets.bestAction(); if(hlp.attack.shooting) - return BattleAction::makeShotAttack(stack, hlp.enemy); + return BattleAction::makeShotAttack(stack, hlp.attack.defender); else - return BattleAction::makeMeleeAttack(stack, hlp.enemy, hlp.tile); + return BattleAction::makeMeleeAttack(stack, hlp.attack.defender->getPosition(), hlp.tile); } else { if(stack->waited()) { //ThreatMap threatsToUs(stack); // These lines may be usefull but they are't used in the code. - auto dists = getCbc()->battleGetDistances(stack); - const EnemyInfo &ei= *range::min_element(targets.unreachableEnemies, std::bind(isCloser, _1, _2, std::ref(dists))); - if(distToNearestNeighbour(ei.s->position, dists) < GameConstants::BFIELD_SIZE) + auto dists = getCbc()->battleGetDistances(stack, stack->getPosition()); + if(!targets.unreachableEnemies.empty()) { - return goTowards(stack, ei.s->position); + const EnemyInfo &ei= *range::min_element(targets.unreachableEnemies, std::bind(isCloser, _1, _2, std::ref(dists))); + if(distToNearestNeighbour(ei.s->getPosition(), dists) < GameConstants::BFIELD_SIZE) + { + return goTowards(stack, ei.s->getPosition()); + } } } else @@ -116,9 +167,15 @@ BattleAction CBattleAI::activeStack( const CStack * stack ) BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination) { - assert(destination.isValid()); - auto avHexes = cb->battleGetAvailableHexes(stack, false); + if(!destination.isValid()) + { + logAi->error("CBattleAI::goTowards: invalid destination"); + return BattleAction::makeDefend(stack); + } + auto reachability = cb->getReachability(stack); + auto avHexes = cb->battleGetAvailableHexes(reachability, stack); + if(vstd::contains(avHexes, destination)) return BattleAction::makeMove(stack, destination); auto destNeighbours = destination.neighbouringTiles(); @@ -156,7 +213,12 @@ BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination) BattleHex currentDest = bestNeighbor; while(1) { - assert(currentDest.isValid()); + if(!currentDest.isValid()) + { + logAi->error("CBattleAI::goTowards: internal error"); + return BattleAction::makeDefend(stack); + } + if(vstd::contains(avHexes, currentDest)) return BattleAction::makeMove(stack, currentDest); currentDest = reachability.predecessors[currentDest]; @@ -166,22 +228,7 @@ BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination) BattleAction CBattleAI::useCatapult(const CStack * stack) { - throw std::runtime_error("The method or operation is not implemented."); -} - - -enum SpellTypes -{ - OFFENSIVE_SPELL, TIMED_EFFECT, OTHER -}; - -SpellTypes spellType(const CSpell *spell) -{ - if (spell->isOffensiveSpell()) - return OFFENSIVE_SPELL; - if (spell->hasEffects()) - return TIMED_EFFECT; - return OTHER; + throw std::runtime_error("CBattleAI::useCatapult is not implemented."); } void CBattleAI::attemptCastingSpell() @@ -190,7 +237,7 @@ void CBattleAI::attemptCastingSpell() if(!hero) return; - if(cb->battleCanCastSpell(hero, ECastingMode::HERO_CASTING) != ESpellCastProblem::OK) + if(cb->battleCanCastSpell(hero, spells::Mode::HERO) != ESpellCastProblem::OK) return; LOGL("Casting spells sounds like fun. Let's see..."); @@ -198,21 +245,28 @@ void CBattleAI::attemptCastingSpell() std::vector possibleSpells; vstd::copy_if(VLC->spellh->objects, std::back_inserter(possibleSpells), [this, hero] (const CSpell *s) -> bool { - return s->canBeCast(getCbc().get(), ECastingMode::HERO_CASTING, hero) == ESpellCastProblem::OK; + return s->canBeCast(getCbc().get(), spells::Mode::HERO, hero); }); LOGFL("I can cast %d spells.", possibleSpells.size()); vstd::erase_if(possibleSpells, [](const CSpell *s) - {return spellType(s) == OTHER; }); - LOGFL("I know about workings of %d of them.", possibleSpells.size()); + { + return spellType(s) != SpellTypes::BATTLE; + }); + + LOGFL("I know how %d of them works.", possibleSpells.size()); //Get possible spell-target pairs std::vector possibleCasts; for(auto spell : possibleSpells) { - for(auto hex : getTargetsToConsider(spell, hero)) + spells::BattleCast temp(getCbc().get(), hero, spells::Mode::HERO, spell); + + for(auto & target : temp.findPotentialTargets()) { - PossibleSpellcast ps = {spell, hex, 0}; + PossibleSpellcast ps; + ps.dest = target; + ps.spell = spell; possibleCasts.push_back(ps); } } @@ -220,141 +274,229 @@ void CBattleAI::attemptCastingSpell() if(possibleCasts.empty()) return; - std::map valueOfStack; - for(auto stack : cb->battleGetStacks()) + using ValueMap = PossibleSpellcast::ValueMap; + + auto evaluateQueue = [&](ValueMap & values, const std::vector & queue, HypotheticBattle * state, size_t minTurnSpan, bool * enemyHadTurnOut) -> bool { - PotentialTargets pt(stack); - valueOfStack[stack] = pt.bestActionValue(); + bool firstRound = true; + bool enemyHadTurn = false; + size_t ourTurnSpan = 0; + + bool stop = false; + + for(auto & round : queue) + { + if(!firstRound) + state->nextRound(0);//todo: set actual value? + for(auto unit : round) + { + if(!vstd::contains(values, unit->unitId())) + values[unit->unitId()] = 0; + + if(!unit->alive()) + continue; + + if(state->battleGetOwner(unit) != playerID) + { + enemyHadTurn = true; + + if(!firstRound || state->battleCastSpells(unit->unitSide()) == 0) + { + //enemy could counter our spell at this point + //anyway, we do not know what enemy will do + //just stop evaluation + stop = true; + break; + } + } + else if(!enemyHadTurn) + { + ourTurnSpan++; + } + + state->nextTurn(unit->unitId()); + + PotentialTargets pt(unit, state); + + if(!pt.possibleAttacks.empty()) + { + AttackPossibility ap = pt.bestAction(); + + auto swb = state->getForUpdate(unit->unitId()); + *swb = *ap.attackerState; + + if(ap.damageDealt > 0) + swb->removeUnitBonus(Bonus::UntilAttack); + if(ap.damageReceived > 0) + swb->removeUnitBonus(Bonus::UntilBeingAttacked); + + for(auto affected : ap.affectedUnits) + { + swb = state->getForUpdate(affected->unitId()); + *swb = *affected; + + if(ap.damageDealt > 0) + swb->removeUnitBonus(Bonus::UntilBeingAttacked); + if(ap.damageReceived > 0 && ap.attack.defender->unitId() == affected->unitId()) + swb->removeUnitBonus(Bonus::UntilAttack); + } + } + + auto bav = pt.bestActionValue(); + + //best action is from effective owner`s point if view, we need to convert to our point if view + if(state->battleGetOwner(unit) != playerID) + bav = -bav; + values[unit->unitId()] += bav; + } + + firstRound = false; + + if(stop) + break; + } + + if(enemyHadTurnOut) + *enemyHadTurnOut = enemyHadTurn; + + return ourTurnSpan > minTurnSpan; + }; + + RNGStub rngStub; + + ValueMap valueOfStack; + ValueMap healthOfStack; + + TStacks all = cb->battleGetAllStacks(false); + + for(auto unit : all) + { + healthOfStack[unit->unitId()] = unit->getAvailableHealth(); + valueOfStack[unit->unitId()] = 0; } - auto evaluateSpellcast = [&] (const PossibleSpellcast &ps) -> int + auto amount = all.size(); + + std::vector turnOrder; + + cb->battleGetTurnOrder(turnOrder, amount, 2); //no more than 1 turn after current, each unit at least once + { - const int skillLevel = hero->getSpellSchoolLevel(ps.spell); - const int spellPower = hero->getPrimSkillLevel(PrimarySkill::SPELL_POWER); - switch(spellType(ps.spell)) + bool enemyHadTurn = false; + + HypotheticBattle state(cb); + evaluateQueue(valueOfStack, turnOrder, &state, 0, &enemyHadTurn); + + if(!enemyHadTurn) { - case OFFENSIVE_SPELL: - { - int damageDealt = 0, damageReceived = 0; - auto stacksSuffering = ps.spell->getAffectedStacks(cb.get(), ECastingMode::HERO_CASTING, hero, skillLevel, ps.dest); - if(stacksSuffering.empty()) - return -1; - for(auto stack : stacksSuffering) + auto battleIsFinishedOpt = state.battleIsFinished(); + + if(battleIsFinishedOpt) { - const int dmg = ps.spell->calculateDamage(hero, stack, skillLevel, spellPower); - if(stack->owner == playerID) - damageReceived += dmg; - else - damageDealt += dmg; + print("No need to cast a spell. Battle will finish soon."); + return; } - const int damageDiff = damageDealt - damageReceived * 10; - LOGFL("Casting %s on hex %d would deal { %d %d } damage points among %d stacks.", - ps.spell->name % ps.dest % damageDealt % damageReceived % stacksSuffering.size()); - //TODO tactic effect too - return damageDiff; } - case TIMED_EFFECT: + } + + auto evaluateSpellcast = [&] (PossibleSpellcast * ps) + { + int64_t totalGain = 0; + + HypotheticBattle state(cb); + + spells::BattleCast cast(&state, hero, spells::Mode::HERO, ps->spell); + cast.target = ps->dest; + cast.cast(&state, rngStub); + ValueMap newHealthOfStack; + ValueMap newValueOfStack; + + size_t ourUnits = 0; + + for(auto unit : all) { - auto stacksAffected = ps.spell->getAffectedStacks(cb.get(), ECastingMode::HERO_CASTING, hero, skillLevel, ps.dest); - if(stacksAffected.empty()) - return -1; - int totalGain = 0; - for(const CStack * sta : stacksAffected) + newHealthOfStack[unit->unitId()] = unit->getAvailableHealth(); + newValueOfStack[unit->unitId()] = 0; + + if(state.battleGetOwner(unit) == playerID && unit->alive() && unit->willMove()) + ourUnits++; + } + + size_t minTurnSpan = ourUnits/3; //todo: tweak this + + std::vector newTurnOrder; + state.battleGetTurnOrder(newTurnOrder, amount, 2); + + if(evaluateQueue(newValueOfStack, newTurnOrder, &state, minTurnSpan, nullptr)) + { + for(auto unit : all) { - StackWithBonuses swb; - swb.stack = sta; - //todo: handle effect actualization in HypotheticChangesToBattleState - ps.spell->getEffects(swb.bonusesToAdd, skillLevel, false, hero->getEnchantPower(ps.spell)); - ps.spell->getEffects(swb.bonusesToAdd, skillLevel, true, hero->getEnchantPower(ps.spell)); - HypotheticChangesToBattleState state; - state.bonusesOfStacks[swb.stack] = &swb; - PotentialTargets pt(swb.stack, state); - auto newValue = pt.bestActionValue(); - auto oldValue = valueOfStack[swb.stack]; - auto gain = newValue - oldValue; - if(swb.stack->owner != playerID) //enemy - gain = -gain; - LOGFL("Casting %s on %s would improve the stack by %d points (from %d to %d)", - ps.spell->name % sta->nodeName() % (gain) % (oldValue) % (newValue)); - totalGain += gain; + auto newValue = getValOr(newValueOfStack, unit->unitId(), 0); + auto oldValue = getValOr(valueOfStack, unit->unitId(), 0); + + auto healthDiff = newHealthOfStack[unit->unitId()] - healthOfStack[unit->unitId()]; + + if(unit->unitOwner() != playerID) + healthDiff = -healthDiff; + + auto gain = newValue - oldValue + healthDiff; + + if(gain != 0) + totalGain += gain; } - LOGFL("Total gain of cast %s at hex %d is %d", ps.spell->name % (ps.dest.hex) % (totalGain)); - return totalGain; + ps->value = totalGain; } - default: - assert(0); - return 0; + else + { + ps->value = -1; } }; + std::vector> tasks; + for(PossibleSpellcast & psc : possibleCasts) - psc.value = evaluateSpellcast(psc); - auto pscValue = [] (const PossibleSpellcast &ps) -> int + { + tasks.push_back(std::bind(evaluateSpellcast, &psc)); + + } + + uint32_t threadCount = boost::thread::hardware_concurrency(); + + if(threadCount == 0) + { + logGlobal->warn("No information of CPU cores available"); + threadCount = 1; + } + + CStopWatch timer; + + CThreadHelper threadHelper(&tasks, threadCount); + threadHelper.run(); + + LOGFL("Evaluation took %d ms", timer.getDiff()); + + auto pscValue = [] (const PossibleSpellcast &ps) -> int64_t { return ps.value; }; auto castToPerform = *vstd::maxElementByFun(possibleCasts, pscValue); - LOGFL("Best spell is %s. Will cast.", castToPerform.spell->name); - BattleAction spellcast; - spellcast.actionType = Battle::HERO_SPELL; - spellcast.additionalInfo = castToPerform.spell->id; - spellcast.destinationTile = castToPerform.dest; - spellcast.side = side; - spellcast.stackNumber = (!side) ? -1 : -2; - cb->battleMakeAction(&spellcast); -} -std::vector CBattleAI::getTargetsToConsider(const CSpell * spell, const ISpellCaster * caster) const -{ - const CSpell::TargetInfo targetInfo(spell, caster->getSpellSchoolLevel(spell)); - std::vector ret; - if(targetInfo.massive || targetInfo.type == CSpell::NO_TARGET) + if(castToPerform.value > 0) { - ret.push_back(BattleHex()); + LOGFL("Best spell is %s (value %d). Will cast.", castToPerform.spell->name % castToPerform.value); + BattleAction spellcast; + spellcast.actionType = EActionType::HERO_SPELL; + spellcast.actionSubtype = castToPerform.spell->id; + spellcast.setTarget(castToPerform.dest); + spellcast.side = side; + spellcast.stackNumber = (!side) ? -1 : -2; + cb->battleMakeAction(&spellcast); } else { - switch(targetInfo.type) - { - case CSpell::CREATURE: - { - for(const CStack * stack : getCbc()->battleAliveStacks()) - { - bool immune = ESpellCastProblem::OK != spell->isImmuneByStack(caster, stack); - bool casterStack = stack->owner == caster->getOwner(); - - if(!immune) - switch (spell->positiveness) - { - case CSpell::POSITIVE: - if(casterStack || targetInfo.smart) - ret.push_back(stack->position); - break; - case CSpell::NEUTRAL: - ret.push_back(stack->position); - break; - case CSpell::NEGATIVE: - if(!casterStack || targetInfo.smart) - ret.push_back(stack->position); - break; - } - } - } - break; - case CSpell::LOCATION: - { - for(int i = 0; i < GameConstants::BFIELD_SIZE; i++) - if(BattleHex(i).isAvailable()) - ret.push_back(i); - } - break; - - default: - break; - } + LOGFL("Best spell is %s. But it is actually useless (value %d).", castToPerform.spell->name % castToPerform.value); } - return ret; } int CBattleAI::distToNearestNeighbour(BattleHex hex, const ReachabilityInfo::TDistances &dists, BattleHex *chosenHex) @@ -374,18 +516,18 @@ int CBattleAI::distToNearestNeighbour(BattleHex hex, const ReachabilityInfo::TDi void CBattleAI::battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool Side) { - print("battleStart called"); + LOG_TRACE(logAi); side = Side; } bool CBattleAI::isCloser(const EnemyInfo &ei1, const EnemyInfo &ei2, const ReachabilityInfo::TDistances &dists) { - return distToNearestNeighbour(ei1.s->position, dists) < distToNearestNeighbour(ei2.s->position, dists); + return distToNearestNeighbour(ei1.s->getPosition(), dists) < distToNearestNeighbour(ei2.s->getPosition(), dists); } void CBattleAI::print(const std::string &text) const { - logAi->trace("CBattleAI [%p]: %s", this, text); + logAi->trace("%s Battle AI[%p]: %s", playerID.getStr(), this, text); } boost::optional CBattleAI::considerFleeingOrSurrendering() diff --git a/AI/BattleAI/BattleAI.h b/AI/BattleAI/BattleAI.h index 373eca1fb..19b50c6fd 100644 --- a/AI/BattleAI/BattleAI.h +++ b/AI/BattleAI/BattleAI.h @@ -45,13 +45,6 @@ struct CurrentOffensivePotential }; */ // These lines may be usefull but they are't used in the code. -struct PossibleSpellcast -{ - const CSpell *spell; - BattleHex dest; - si32 value; -}; - class CBattleAI : public CBattleGameInterface { int side; @@ -72,7 +65,6 @@ public: boost::optional considerFleeingOrSurrendering(); - std::vector getTargetsToConsider(const CSpell *spell, const ISpellCaster * caster) const; static int distToNearestNeighbour(BattleHex hex, const ReachabilityInfo::TDistances& dists, BattleHex *chosenHex = nullptr); static bool isCloser(const EnemyInfo & ei1, const EnemyInfo & ei2, const ReachabilityInfo::TDistances & dists); @@ -82,7 +74,7 @@ public: //void actionFinished(const BattleAction &action) override;//occurs AFTER every action taken by any stack or by the hero //void actionStarted(const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero //void battleAttack(const BattleAttack *ba) override; //called when stack is performing attack - //void battleStacksAttacked(const std::vector & bsa) override; //called when stack receives damage (after battleAttack()) + //void battleStacksAttacked(const std::vector & bsa, const std::vector & battleLog) override; //called when stack receives damage (after battleAttack()) //void battleEnd(const BattleResult *br) override; //void battleResultsApplied() override; //called when all effects of last battle are applied //void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied; @@ -92,9 +84,5 @@ public: //void battleStacksEffectsSet(const SetStackEffect & sse) override;//called when a specific effect is set to stacks //void battleTriggerEffect(const BattleTriggerEffect & bte) override; //void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side) override; //called by engine when battle starts; side=0 - left, side=1 - right - //void battleStacksHealedRes(const std::vector > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom) override; //called when stacks are healed / resurrected first element of pair - stack id, second - healed hp - //void battleNewStackAppeared(const CStack * stack) override; //not called at the beginning of a battle or by resurrection; called eg. when elemental is summoned - //void battleObstaclesRemoved(const std::set & removedObstacles) override; //called when a certain set of obstacles is removed from batlefield; IDs of them are given //void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack - //void battleStacksRemoved(const BattleStacksRemoved & bsr) override; //called when certain stack is completely removed from battlefield }; diff --git a/AI/BattleAI/CMakeLists.txt b/AI/BattleAI/CMakeLists.txt index 096d10b0c..2c6b77f6c 100644 --- a/AI/BattleAI/CMakeLists.txt +++ b/AI/BattleAI/CMakeLists.txt @@ -8,6 +8,7 @@ set(battleAI_SRCS common.cpp EnemyInfo.cpp main.cpp + PossibleSpellcast.cpp PotentialTargets.cpp StackWithBonuses.cpp ThreatMap.cpp @@ -21,6 +22,7 @@ set(battleAI_HEADERS common.h EnemyInfo.h PotentialTargets.h + PossibleSpellcast.h StackWithBonuses.h ThreatMap.h ) diff --git a/AI/BattleAI/EnemyInfo.cpp b/AI/BattleAI/EnemyInfo.cpp index 17ed65058..8a0f36b77 100644 --- a/AI/BattleAI/EnemyInfo.cpp +++ b/AI/BattleAI/EnemyInfo.cpp @@ -9,13 +9,11 @@ */ #include "StdInc.h" #include "EnemyInfo.h" -#include "../../lib/CRandomGenerator.h" -#include "../../CCallback.h" -#include "common.h" -void EnemyInfo::calcDmg(const CStack * ourStack) +#include "../../lib/battle/Unit.h" + +bool EnemyInfo::operator==(const EnemyInfo & ei) const { - TDmgRange retal, dmg = getCbc()->battleEstimateDamage(CRandomGenerator::getDefault(), ourStack, s, &retal); - adi = (dmg.first + dmg.second) / 2; - adr = (retal.first + retal.second) / 2; + return s->unitId() == ei.s->unitId(); } + diff --git a/AI/BattleAI/EnemyInfo.h b/AI/BattleAI/EnemyInfo.h index f973aa3b7..874e9e2b6 100644 --- a/AI/BattleAI/EnemyInfo.h +++ b/AI/BattleAI/EnemyInfo.h @@ -9,21 +9,16 @@ */ #pragma once -#include "../../lib/battle/BattleHex.h" - -class CStack; +namespace battle +{ + class Unit; +} class EnemyInfo { public: - const CStack * s; - int adi, adr; - std::vector attackFrom; //for melee fight - EnemyInfo(const CStack * _s) : s(_s) + const battle::Unit * s; + EnemyInfo(const battle::Unit * _s) : s(_s) {} - void calcDmg(const CStack * ourStack); - bool operator==(const EnemyInfo& ei) const - { - return s == ei.s; - } + bool operator==(const EnemyInfo & ei) const; }; diff --git a/AI/BattleAI/PossibleSpellcast.cpp b/AI/BattleAI/PossibleSpellcast.cpp new file mode 100644 index 000000000..778813637 --- /dev/null +++ b/AI/BattleAI/PossibleSpellcast.cpp @@ -0,0 +1,21 @@ +/* + * PossibleSpellcast.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" + +#include "PossibleSpellcast.h" + +PossibleSpellcast::PossibleSpellcast() + : spell(nullptr), + dest(), + value(0) +{ +} + +PossibleSpellcast::~PossibleSpellcast() = default; diff --git a/AI/BattleAI/PossibleSpellcast.h b/AI/BattleAI/PossibleSpellcast.h new file mode 100644 index 000000000..b96698be2 --- /dev/null +++ b/AI/BattleAI/PossibleSpellcast.h @@ -0,0 +1,29 @@ +/* + * PossibleSpellcast.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "../../lib/battle/Destination.h" +#include "../../lib/spells/Magic.h" + +class CSpell; + +class PossibleSpellcast +{ +public: + using ValueMap = std::map; + + const CSpell * spell; + spells::Target dest; + int64_t value; + + PossibleSpellcast(); + virtual ~PossibleSpellcast(); +}; diff --git a/AI/BattleAI/PotentialTargets.cpp b/AI/BattleAI/PotentialTargets.cpp index 34a005f00..bf73025ea 100644 --- a/AI/BattleAI/PotentialTargets.cpp +++ b/AI/BattleAI/PotentialTargets.cpp @@ -9,51 +9,76 @@ */ #include "StdInc.h" #include "PotentialTargets.h" +#include "../../lib/CStack.h"//todo: remove -PotentialTargets::PotentialTargets(const CStack * attacker, const HypotheticChangesToBattleState & state) +PotentialTargets::PotentialTargets(const battle::Unit * attacker, const HypotheticBattle * state) { - auto dists = getCbc()->battleGetDistances(attacker); - auto avHexes = getCbc()->battleGetAvailableHexes(attacker, false); + auto attIter = state->stackStates.find(attacker->unitId()); + const battle::Unit * attackerInfo = (attIter == state->stackStates.end()) ? attacker : attIter->second.get(); - for(const CStack *enemy : getCbc()->battleGetStacks()) + auto reachability = state->getReachability(attackerInfo); + auto avHexes = state->battleGetAvailableHexes(reachability, attackerInfo); + + //FIXME: this should part of battleGetAvailableHexes + bool forceTarget = false; + const battle::Unit * forcedTarget = nullptr; + BattleHex forcedHex; + + if(attackerInfo->hasBonusOfType(Bonus::ATTACKS_NEAREST_CREATURE)) { - //Consider only stacks of different owner - if(enemy->side == attacker->side) + forceTarget = true; + auto nearest = state->getNearestStack(attackerInfo); + + if(nearest.first != nullptr) + { + forcedTarget = nearest.first; + forcedHex = nearest.second; + } + } + + auto aliveUnits = state->battleGetUnitsIf([=](const battle::Unit * unit) + { + return unit->isValidTarget() && unit->unitId() != attackerInfo->unitId(); + }); + + for(auto defender : aliveUnits) + { + if(!forceTarget && !state->battleMatchOwner(attackerInfo, defender)) continue; auto GenerateAttackInfo = [&](bool shooting, BattleHex hex) -> AttackPossibility { - auto bai = BattleAttackInfo(attacker, enemy, shooting); - bai.attackerBonuses = getValOr(state.bonusesOfStacks, bai.attacker, bai.attacker); - bai.defenderBonuses = getValOr(state.bonusesOfStacks, bai.defender, bai.defender); + auto bai = BattleAttackInfo(attackerInfo, defender, shooting); - if(hex.isValid()) - { - assert(dists[hex] <= attacker->Speed()); - bai.chargedFields = dists[hex]; - } + if(hex.isValid() && !shooting) + bai.chargedFields = reachability.distances[hex]; - return AttackPossibility::evaluate(bai, state, hex); + return AttackPossibility::evaluate(bai, hex); }; - if(getCbc()->battleCanShoot(attacker, enemy->position)) + if(forceTarget) + { + if(forcedTarget && defender->unitId() == forcedTarget->unitId()) + possibleAttacks.push_back(GenerateAttackInfo(false, forcedHex)); + else + unreachableEnemies.push_back(defender); + } + else if(state->battleCanShoot(attackerInfo, defender->getPosition())) { possibleAttacks.push_back(GenerateAttackInfo(true, BattleHex::INVALID)); } else { for(BattleHex hex : avHexes) - if(CStack::isMeleeAttackPossible(attacker, enemy, hex)) + if(CStack::isMeleeAttackPossible(attackerInfo, defender, hex)) possibleAttacks.push_back(GenerateAttackInfo(false, hex)); - if(!vstd::contains_if(possibleAttacks, [=](const AttackPossibility &pa) { return pa.enemy == enemy; })) - unreachableEnemies.push_back(enemy); + if(!vstd::contains_if(possibleAttacks, [=](const AttackPossibility & pa) { return pa.attack.defender->unitId() == defender->unitId(); })) + unreachableEnemies.push_back(defender); } } } - - int PotentialTargets::bestActionValue() const { if(possibleAttacks.empty()) diff --git a/AI/BattleAI/PotentialTargets.h b/AI/BattleAI/PotentialTargets.h index 27c10799a..6220ebc24 100644 --- a/AI/BattleAI/PotentialTargets.h +++ b/AI/BattleAI/PotentialTargets.h @@ -14,12 +14,10 @@ class PotentialTargets { public: std::vector possibleAttacks; - std::vector unreachableEnemies; - - //std::function GenerateAttackInfo; //args: shooting, destHex + std::vector unreachableEnemies; PotentialTargets(){}; - PotentialTargets(const CStack *attacker, const HypotheticChangesToBattleState &state = HypotheticChangesToBattleState()); + PotentialTargets(const battle::Unit * attacker, const HypotheticBattle * state); AttackPossibility bestAction() const; int bestActionValue() const; diff --git a/AI/BattleAI/StackWithBonuses.cpp b/AI/BattleAI/StackWithBonuses.cpp index 9cdf7ac54..c32567feb 100644 --- a/AI/BattleAI/StackWithBonuses.cpp +++ b/AI/BattleAI/StackWithBonuses.cpp @@ -9,21 +9,383 @@ */ #include "StdInc.h" #include "StackWithBonuses.h" +#include "../../lib/NetPacksBase.h" #include "../../lib/CStack.h" +void actualizeEffect(TBonusListPtr target, const Bonus & ef) +{ + for(auto bonus : *target) //TODO: optimize + { + if(bonus->source == Bonus::SPELL_EFFECT && bonus->type == ef.type && bonus->subtype == ef.subtype) + { + bonus->turnsRemain = std::max(bonus->turnsRemain, ef.turnsRemain); + } + } +} -const TBonusListPtr StackWithBonuses::getAllBonuses(const CSelector &selector, const CSelector &limit, - const CBonusSystemNode * root, const std::string & cachingStr) const +StackWithBonuses::StackWithBonuses(const HypotheticBattle * Owner, const CStack * Stack) + : battle::CUnitState(), + origBearer(Stack), + owner(Owner), + type(Stack->unitType()), + baseAmount(Stack->unitBaseAmount()), + id(Stack->unitId()), + side(Stack->unitSide()), + player(Stack->unitOwner()), + slot(Stack->unitSlot()) +{ + localInit(Owner); + + battle::CUnitState::operator=(*Stack); +} + +StackWithBonuses::StackWithBonuses(const HypotheticBattle * Owner, const battle::UnitInfo & info) + : battle::CUnitState(), + origBearer(nullptr), + owner(Owner), + baseAmount(info.count), + id(info.id), + side(info.side), + slot(SlotID::SUMMONED_SLOT_PLACEHOLDER) +{ + type = info.type.toCreature(); + origBearer = type; + + player = Owner->getSidePlayer(side); + + position = info.position; + summoned = info.summoned; + localInit(Owner); +} + +StackWithBonuses::~StackWithBonuses() = default; + +StackWithBonuses & StackWithBonuses::operator=(const battle::CUnitState & other) +{ + battle::CUnitState::operator=(other); + return *this; +} + +const CCreature * StackWithBonuses::unitType() const +{ + return type; +} + +int32_t StackWithBonuses::unitBaseAmount() const +{ + return baseAmount; +} + +uint32_t StackWithBonuses::unitId() const +{ + return id; +} + +ui8 StackWithBonuses::unitSide() const +{ + return side; +} + +PlayerColor StackWithBonuses::unitOwner() const +{ + return player; +} + +SlotID StackWithBonuses::unitSlot() const +{ + return slot; +} + +const TBonusListPtr StackWithBonuses::getAllBonuses(const CSelector & selector, const CSelector & limit, + const CBonusSystemNode * root, const std::string & cachingStr) const { TBonusListPtr ret = std::make_shared(); - const TBonusListPtr originalList = stack->getAllBonuses(selector, limit, root, cachingStr); - range::copy(*originalList, std::back_inserter(*ret)); - for(auto &bonus : bonusesToAdd) + const TBonusListPtr originalList = origBearer->getAllBonuses(selector, limit, root, cachingStr); + + vstd::copy_if(*originalList, std::back_inserter(*ret), [this](const std::shared_ptr & b) + { + return !vstd::contains(bonusesToRemove, b); + }); + + + for(const Bonus & bonus : bonusesToUpdate) + { + if(selector(&bonus) && (!limit || !limit(&bonus))) + { + if(ret->getFirst(Selector::source(Bonus::SPELL_EFFECT, bonus.sid).And(Selector::typeSubtype(bonus.type, bonus.subtype)))) + { + actualizeEffect(ret, bonus); + } + else + { + auto b = std::make_shared(bonus); + ret->push_back(b); + } + } + } + + for(auto & bonus : bonusesToAdd) { auto b = std::make_shared(bonus); - if(selector(b.get()) && (!limit || !limit(b.get()))) + if(selector(b.get()) && (!limit || !limit(b.get()))) ret->push_back(b); } //TODO limiters? return ret; } + +int64_t StackWithBonuses::getTreeVersion() const +{ + return owner->getTreeVersion(); +} + +void StackWithBonuses::addUnitBonus(const std::vector & bonus) +{ + vstd::concatenate(bonusesToAdd, bonus); +} + +void StackWithBonuses::updateUnitBonus(const std::vector & bonus) +{ + //TODO: optimize, actualize to last value + + vstd::concatenate(bonusesToUpdate, bonus); +} + +void StackWithBonuses::removeUnitBonus(const std::vector & bonus) +{ + for(auto & one : bonus) + { + CSelector selector([&one](const Bonus * b) -> bool + { + //compare everything but turnsRemain, limiter and propagator + return one.duration == b->duration + && one.type == b->type + && one.subtype == b->subtype + && one.source == b->source + && one.val == b->val + && one.sid == b->sid + && one.valType == b->valType + && one.additionalInfo == b->additionalInfo + && one.effectRange == b->effectRange + && one.description == b->description; + }); + + removeUnitBonus(selector); + } +} + +void StackWithBonuses::removeUnitBonus(const CSelector & selector) +{ + TBonusListPtr toRemove = origBearer->getBonuses(selector); + + for(auto b : *toRemove) + bonusesToRemove.insert(b); + + vstd::erase_if(bonusesToAdd, [&](const Bonus & b){return selector(&b);}); + vstd::erase_if(bonusesToUpdate, [&](const Bonus & b){return selector(&b);}); +} + +void StackWithBonuses::spendMana(const spells::PacketSender * server, const int spellCost) const +{ + //TODO: evaluate cast use +} + +HypotheticBattle::HypotheticBattle(Subject realBattle) + : BattleProxy(realBattle), + bonusTreeVersion(1) +{ + auto activeUnit = realBattle->battleActiveUnit(); + activeUnitId = activeUnit ? activeUnit->unitId() : -1; + + nextId = 0xF0000000; +} + +bool HypotheticBattle::unitHasAmmoCart(const battle::Unit * unit) const +{ + //FIXME: check ammocart alive state here + return false; +} + +PlayerColor HypotheticBattle::unitEffectiveOwner(const battle::Unit * unit) const +{ + return battleGetOwner(unit); +} + +std::shared_ptr HypotheticBattle::getForUpdate(uint32_t id) +{ + auto iter = stackStates.find(id); + + if(iter == stackStates.end()) + { + const CStack * s = subject->battleGetStackByID(id, false); + + auto ret = std::make_shared(this, s); + stackStates[id] = ret; + return ret; + } + else + { + return iter->second; + } +} + +battle::Units HypotheticBattle::getUnitsIf(battle::UnitFilter predicate) const +{ + battle::Units proxyed = BattleProxy::getUnitsIf(predicate); + + battle::Units ret; + ret.reserve(proxyed.size()); + + for(auto unit : proxyed) + { + //unit was not changed, trust proxyed data + if(stackStates.find(unit->unitId()) == stackStates.end()) + ret.push_back(unit); + } + + for(auto id_unit : stackStates) + { + if(predicate(id_unit.second.get())) + ret.push_back(id_unit.second.get()); + } + + return ret; +} + +int32_t HypotheticBattle::getActiveStackID() const +{ + return activeUnitId; +} + +void HypotheticBattle::nextRound(int32_t roundNr) +{ + //TODO:HypotheticBattle::nextRound + + for(auto unit : battleAliveUnits()) + { + auto forUpdate = getForUpdate(unit->unitId()); + //TODO: update Bonus::NTurns effects + forUpdate->afterNewRound(); + } +} + +void HypotheticBattle::nextTurn(uint32_t unitId) +{ + activeUnitId = unitId; + auto unit = getForUpdate(unitId); + + unit->removeUnitBonus(Bonus::UntilGetsTurn); + + unit->afterGetsTurn(); +} + +void HypotheticBattle::addUnit(uint32_t id, const JsonNode & data) +{ + battle::UnitInfo info; + info.load(id, data); + std::shared_ptr newUnit = std::make_shared(this, info); + stackStates[newUnit->unitId()] = newUnit; +} + +void HypotheticBattle::moveUnit(uint32_t id, BattleHex destination) +{ + std::shared_ptr changed = getForUpdate(id); + changed->position = destination; +} + +void HypotheticBattle::setUnitState(uint32_t id, const JsonNode & data, int64_t healthDelta) +{ + std::shared_ptr changed = getForUpdate(id); + + changed->load(data); + + if(healthDelta < 0) + { + changed->removeUnitBonus(Bonus::UntilBeingAttacked); + } +} + +void HypotheticBattle::removeUnit(uint32_t id) +{ + std::set ids; + ids.insert(id); + + while(!ids.empty()) + { + auto toRemoveId = *ids.begin(); + + auto toRemove = getForUpdate(toRemoveId); + + if(!toRemove->ghost) + { + toRemove->onRemoved(); + + //TODO: emulate detachFromAll() somehow + + //stack may be removed instantly (not being killed first) + //handle clone remove also here + if(toRemove->cloneID >= 0) + { + ids.insert(toRemove->cloneID); + toRemove->cloneID = -1; + } + + //TODO: cleanup remaining clone links if any +// for(auto s : stacks) +// { +// if(s->cloneID == toRemoveId) +// s->cloneID = -1; +// } + } + + ids.erase(toRemoveId); + } +} + +void HypotheticBattle::addUnitBonus(uint32_t id, const std::vector & bonus) +{ + getForUpdate(id)->addUnitBonus(bonus); + bonusTreeVersion++; +} + +void HypotheticBattle::updateUnitBonus(uint32_t id, const std::vector & bonus) +{ + getForUpdate(id)->updateUnitBonus(bonus); + bonusTreeVersion++; +} + +void HypotheticBattle::removeUnitBonus(uint32_t id, const std::vector & bonus) +{ + getForUpdate(id)->removeUnitBonus(bonus); + bonusTreeVersion++; +} + +void HypotheticBattle::setWallState(int partOfWall, si8 state) +{ + //TODO:HypotheticBattle::setWallState +} + +void HypotheticBattle::addObstacle(const ObstacleChanges & changes) +{ + //TODO:HypotheticBattle::addObstacle +} + +void HypotheticBattle::removeObstacle(uint32_t id) +{ + //TODO:HypotheticBattle::removeObstacle +} + +uint32_t HypotheticBattle::nextUnitId() const +{ + return nextId++; +} + +int64_t HypotheticBattle::getActualDamage(const TDmgRange & damage, int32_t attackerCount, vstd::RNG & rng) const +{ + return (damage.first + damage.second) / 2; +} + +int64_t HypotheticBattle::getTreeVersion() const +{ + return getBattleNode()->getTreeVersion() + bonusTreeVersion; +} diff --git a/AI/BattleAI/StackWithBonuses.h b/AI/BattleAI/StackWithBonuses.h index e68e56433..b37307147 100644 --- a/AI/BattleAI/StackWithBonuses.h +++ b/AI/BattleAI/StackWithBonuses.h @@ -9,15 +9,105 @@ */ #pragma once #include "../../lib/HeroBonus.h" +#include "../../lib/battle/BattleProxy.h" +#include "../../lib/battle/CUnitState.h" +class HypotheticBattle; class CStack; -class StackWithBonuses : public IBonusBearer +class StackWithBonuses : public battle::CUnitState, public virtual IBonusBearer { public: - const CStack *stack; - mutable std::vector bonusesToAdd; - virtual const TBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, - const CBonusSystemNode *root = nullptr, const std::string &cachingStr = "") const override; + std::vector bonusesToAdd; + std::vector bonusesToUpdate; + std::set> bonusesToRemove; + + StackWithBonuses(const HypotheticBattle * Owner, const CStack * Stack); + + StackWithBonuses(const HypotheticBattle * Owner, const battle::UnitInfo & info); + + virtual ~StackWithBonuses(); + + StackWithBonuses & operator= (const battle::CUnitState & other); + + ///IUnitInfo + const CCreature * unitType() const override; + + int32_t unitBaseAmount() const override; + + uint32_t unitId() const override; + ui8 unitSide() const override; + PlayerColor unitOwner() const override; + SlotID unitSlot() const override; + + ///IBonusBearer + const TBonusListPtr getAllBonuses(const CSelector & selector, const CSelector & limit, + const CBonusSystemNode * root = nullptr, const std::string & cachingStr = "") const override; + + int64_t getTreeVersion() const override; + + void addUnitBonus(const std::vector & bonus); + void updateUnitBonus(const std::vector & bonus); + void removeUnitBonus(const std::vector & bonus); + + void removeUnitBonus(const CSelector & selector); + + void spendMana(const spells::PacketSender * server, const int spellCost) const override; + +private: + const IBonusBearer * origBearer; + const HypotheticBattle * owner; + + const CCreature * type; + ui32 baseAmount; + uint32_t id; + ui8 side; + PlayerColor player; + SlotID slot; +}; + +class HypotheticBattle : public BattleProxy, public battle::IUnitEnvironment +{ +public: + std::map> stackStates; + + HypotheticBattle(Subject realBattle); + + bool unitHasAmmoCart(const battle::Unit * unit) const override; + PlayerColor unitEffectiveOwner(const battle::Unit * unit) const override; + + std::shared_ptr getForUpdate(uint32_t id); + + int32_t getActiveStackID() const override; + + battle::Units getUnitsIf(battle::UnitFilter predicate) const override; + + void nextRound(int32_t roundNr) override; + void nextTurn(uint32_t unitId) override; + + void addUnit(uint32_t id, const JsonNode & data) override; + void setUnitState(uint32_t id, const JsonNode & data, int64_t healthDelta) override; + void moveUnit(uint32_t id, BattleHex destination) override; + void removeUnit(uint32_t id) override; + + void addUnitBonus(uint32_t id, const std::vector & bonus) override; + void updateUnitBonus(uint32_t id, const std::vector & bonus) override; + void removeUnitBonus(uint32_t id, const std::vector & bonus) override; + + void setWallState(int partOfWall, si8 state) override; + + void addObstacle(const ObstacleChanges & changes) override; + void removeObstacle(uint32_t id) override; + + uint32_t nextUnitId() const override; + + int64_t getActualDamage(const TDmgRange & damage, int32_t attackerCount, vstd::RNG & rng) const override; + + int64_t getTreeVersion() const; + +private: + int32_t bonusTreeVersion; + int32_t activeUnitId; + mutable uint32_t nextId; }; diff --git a/AI/StupidAI/StupidAI.cpp b/AI/StupidAI/StupidAI.cpp index c61c35cb8..62affac5b 100644 --- a/AI/StupidAI/StupidAI.cpp +++ b/AI/StupidAI/StupidAI.cpp @@ -53,7 +53,7 @@ struct EnemyInfo {} void calcDmg(const CStack * ourStack) { - TDmgRange retal, dmg = cbc->battleEstimateDamage(CRandomGenerator::getDefault(), ourStack, s, &retal); + TDmgRange retal, dmg = cbc->battleEstimateDamage(ourStack, s, &retal); adi = (dmg.first + dmg.second) / 2; adr = (retal.first + retal.second) / 2; } @@ -89,7 +89,7 @@ int distToNearestNeighbour(BattleHex hex, const ReachabilityInfo::TDistances& di bool isCloser(const EnemyInfo & ei1, const EnemyInfo & ei2, const ReachabilityInfo::TDistances & dists) { - return distToNearestNeighbour(ei1.s->position, dists) < distToNearestNeighbour(ei2.s->position, dists); + return distToNearestNeighbour(ei1.s->getPosition(), dists) < distToNearestNeighbour(ei2.s->getPosition(), dists); } } @@ -111,17 +111,16 @@ BattleAction CStupidAI::activeStack( const CStack * stack ) { //boost::this_thread::sleep(boost::posix_time::seconds(2)); print("activeStack called for " + stack->nodeName()); - auto dists = cb->battleGetDistances(stack); + auto dists = cb->battleGetDistances(stack, stack->getPosition()); std::vector enemiesShootable, enemiesReachable, enemiesUnreachable; if(stack->type->idNumber == CreatureID::CATAPULT) { BattleAction attack; static const std::vector wallHexes = {50, 183, 182, 130, 78, 29, 12, 95}; - - attack.destinationTile = *RandomGeneratorUtil::nextItem(wallHexes, CRandomGenerator::getDefault()); - attack.actionType = Battle::CATAPULT; - attack.additionalInfo = 0; + auto seletectedHex = *RandomGeneratorUtil::nextItem(wallHexes, CRandomGenerator::getDefault()); + attack.aimToHex(seletectedHex); + attack.actionType = EActionType::CATAPULT; attack.side = side; attack.stackNumber = stack->ID; @@ -134,13 +133,13 @@ BattleAction CStupidAI::activeStack( const CStack * stack ) for (const CStack *s : cb->battleGetStacks(CBattleCallback::ONLY_ENEMY)) { - if(cb->battleCanShoot(stack, s->position)) + if(cb->battleCanShoot(stack, s->getPosition())) { enemiesShootable.push_back(s); } else { - std::vector avHexes = cb->battleGetAvailableHexes(stack, false); + std::vector avHexes = cb->battleGetAvailableHexes(stack); for (BattleHex hex : avHexes) { @@ -157,7 +156,7 @@ BattleAction CStupidAI::activeStack( const CStack * stack ) } } - if(!vstd::contains(enemiesReachable, s) && s->position.isValid()) + if(!vstd::contains(enemiesReachable, s) && s->getPosition().isValid()) enemiesUnreachable.push_back(s); } } @@ -176,16 +175,16 @@ BattleAction CStupidAI::activeStack( const CStack * stack ) else if(enemiesReachable.size()) { const EnemyInfo &ei= *std::max_element(enemiesReachable.begin(), enemiesReachable.end(), &isMoreProfitable); - return BattleAction::makeMeleeAttack(stack, ei.s, *std::max_element(ei.attackFrom.begin(), ei.attackFrom.end(), &willSecondHexBlockMoreEnemyShooters)); + return BattleAction::makeMeleeAttack(stack, ei.s->getPosition(), *std::max_element(ei.attackFrom.begin(), ei.attackFrom.end(), &willSecondHexBlockMoreEnemyShooters)); } else if(enemiesUnreachable.size()) //due to #955 - a buggy battle may occur when there are no enemies { assert(enemiesUnreachable.size()); const EnemyInfo &ei= *std::min_element(enemiesUnreachable.begin(), enemiesUnreachable.end(), std::bind(isCloser, _1, _2, std::ref(dists))); assert(ei.s); - if(distToNearestNeighbour(ei.s->position, dists) < GameConstants::BFIELD_SIZE) + if(distToNearestNeighbour(ei.s->getPosition(), dists) < GameConstants::BFIELD_SIZE) { - return goTowards(stack, ei.s->position); + return goTowards(stack, ei.s->getPosition()); } } @@ -197,7 +196,7 @@ void CStupidAI::battleAttack(const BattleAttack *ba) print("battleAttack called"); } -void CStupidAI::battleStacksAttacked(const std::vector & bsa) +void CStupidAI::battleStacksAttacked(const std::vector & bsa, const std::vector & battleLog) { print("battleStacksAttacked called"); } @@ -243,31 +242,11 @@ void CStupidAI::battleStart(const CCreatureSet *army1, const CCreatureSet *army2 side = Side; } -void CStupidAI::battleStacksHealedRes(const std::vector > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom) -{ - print("battleStacksHealedRes called"); -} - -void CStupidAI::battleNewStackAppeared(const CStack * stack) -{ - print("battleNewStackAppeared called"); -} - -void CStupidAI::battleObstaclesRemoved(const std::set & removedObstacles) -{ - print("battleObstaclesRemoved called"); -} - void CStupidAI::battleCatapultAttacked(const CatapultAttack & ca) { print("battleCatapultAttacked called"); } -void CStupidAI::battleStacksRemoved(const BattleStacksRemoved & bsr) -{ - print("battleStacksRemoved called"); -} - void CStupidAI::print(const std::string &text) const { logAi->trace("CStupidAI [%p]: %s", this, text); @@ -276,8 +255,8 @@ void CStupidAI::print(const std::string &text) const BattleAction CStupidAI::goTowards(const CStack * stack, BattleHex destination) { assert(destination.isValid()); - auto avHexes = cb->battleGetAvailableHexes(stack, false); auto reachability = cb->getReachability(stack); + auto avHexes = cb->battleGetAvailableHexes(reachability, stack); if(vstd::contains(avHexes, destination)) return BattleAction::makeMove(stack, destination); diff --git a/AI/StupidAI/StupidAI.h b/AI/StupidAI/StupidAI.h index 29d16759e..789873073 100644 --- a/AI/StupidAI/StupidAI.h +++ b/AI/StupidAI/StupidAI.h @@ -27,7 +27,7 @@ public: BattleAction activeStack(const CStack * stack) override; //called when it's turn of that stack void battleAttack(const BattleAttack *ba) override; //called when stack is performing attack - void battleStacksAttacked(const std::vector & bsa) override; //called when stack receives damage (after battleAttack()) + void battleStacksAttacked(const std::vector & bsa, const std::vector & battleLog) override; //called when stack receives damage (after battleAttack()) void battleEnd(const BattleResult *br) override; //void battleResultsApplied() override; //called when all effects of last battle are applied void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied; @@ -37,11 +37,7 @@ public: void battleStacksEffectsSet(const SetStackEffect & sse) override;//called when a specific effect is set to stacks //void battleTriggerEffect(const BattleTriggerEffect & bte) override; void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side) override; //called by engine when battle starts; side=0 - left, side=1 - right - void battleStacksHealedRes(const std::vector > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom) override; //called when stacks are healed / resurrected first element of pair - stack id, second - healed hp - void battleNewStackAppeared(const CStack * stack) override; //not called at the beginning of a battle or by resurrection; called eg. when elemental is summoned - void battleObstaclesRemoved(const std::set & removedObstacles) override; //called when a certain set of obstacles is removed from batlefield; IDs of them are given void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack - void battleStacksRemoved(const BattleStacksRemoved & bsr) override; //called when certain stack is completely removed from battlefield BattleAction goTowards(const CStack * stack, BattleHex hex ); diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 7ba0b84fa..263c0258c 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -1359,7 +1359,7 @@ static const BuildingID unitsUpgrade[] = { BuildingID::DWELL_LVL_1_UP, BuildingI BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_UP, BuildingID::DWELL_LVL_7_UP}; static const BuildingID unitGrowth[] = { BuildingID::FORT, BuildingID::CITADEL, BuildingID::CASTLE, BuildingID::HORDE_1, BuildingID::HORDE_1_UPGR, BuildingID::HORDE_2, BuildingID::HORDE_2_UPGR}; -static const BuildingID spells[] = {BuildingID::MAGES_GUILD_1, BuildingID::MAGES_GUILD_2, BuildingID::MAGES_GUILD_3, +static const BuildingID _spells[] = {BuildingID::MAGES_GUILD_1, BuildingID::MAGES_GUILD_2, BuildingID::MAGES_GUILD_3, BuildingID::MAGES_GUILD_4, BuildingID::MAGES_GUILD_5}; static const BuildingID extra[] = {BuildingID::RESOURCE_SILO, BuildingID::SPECIAL_1, BuildingID::SPECIAL_2, BuildingID::SPECIAL_3, BuildingID::SPECIAL_4, BuildingID::SHIPYARD}; // all remaining buildings @@ -1416,7 +1416,7 @@ void VCAI::buildStructure(const CGTownInstance * t) } //remaining tasks - if(tryBuildNextStructure(t, std::vector(spells, spells + ARRAY_COUNT(spells)))) + if(tryBuildNextStructure(t, std::vector(_spells, _spells + ARRAY_COUNT(_spells)))) return; if(tryBuildAnyStructure(t, std::vector(extra, extra + ARRAY_COUNT(extra)))) return; diff --git a/CCallback.cpp b/CCallback.cpp index 21ffe6694..e5484e006 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -174,7 +174,7 @@ bool CCallback::buildBuilding(const CGTownInstance *town, BuildingID buildingID) int CBattleCallback::battleMakeAction(BattleAction* action) { - assert(action->actionType == Battle::HERO_SPELL); + assert(action->actionType == EActionType::HERO_SPELL); MakeCustomAction mca(*action); sendRequest(&mca); return 0; @@ -279,9 +279,11 @@ void CCallback::buildBoat( const IShipyard *obj ) sendRequest(&bb); } -CCallback::CCallback( CGameState * GS, boost::optional Player, CClient *C ) - :CBattleCallback(GS, Player, C) +CCallback::CCallback(CGameState * GS, boost::optional Player, CClient * C) + : CBattleCallback(Player, C) { + gs = GS; + waitTillRealize = false; unlockGsWhenWaiting = false; } @@ -367,9 +369,8 @@ void CCallback::unregisterBattleInterface(std::shared_ptr cl->additionalBattleInts[*player] -= battleEvents; } -CBattleCallback::CBattleCallback(CGameState *GS, boost::optional Player, CClient *C ) +CBattleCallback::CBattleCallback(boost::optional Player, CClient *C ) { - gs = GS; player = Player; cl = C; } diff --git a/CCallback.h b/CCallback.h index 4cb06a675..a67859705 100644 --- a/CCallback.h +++ b/CCallback.h @@ -17,7 +17,7 @@ class CGameState; struct CPath; class CGObjectInstance; class CArmedInstance; -struct BattleAction; +class BattleAction; class CGTownInstance; struct lua_State; class CClient; @@ -85,10 +85,9 @@ class CBattleCallback : public IBattleCallback, public CPlayerBattleCallback protected: int sendRequest(const CPack *request); //returns requestID (that'll be matched to requestID in PackageApplied) CClient *cl; - //virtual bool hasAccess(int playerId) const; public: - CBattleCallback(CGameState *GS, boost::optional Player, CClient *C); + CBattleCallback(boost::optional Player, CClient *C); int battleMakeAction(BattleAction* action) override;//for casting spells by hero - DO NOT use it for moving active stack bool battleMakeTacticAction(BattleAction * action) override; // performs tactic phase actions diff --git a/Mods/vcmi/Sprites/vcmi/battleQueue/defendBig.png b/Mods/vcmi/Sprites/vcmi/battleQueue/defendBig.png new file mode 100644 index 0000000000000000000000000000000000000000..ef9022ca60476b7033b9997fb6f299f54252d6bb GIT binary patch literal 2498 zcmV;z2|f0SP)>lXf00006VoOIv0RI60 z0RN!9r;`8x010qNS#tmY3ljhU3ljkVnw%H_000McNliru;R+5AE)GjNicSCk03B&m zSad^gZEa<4bN~PV002XBWnpw>WFU8GbZ8()Nlj2>E@cM*00|~ZL_t(o!`+x|a8%_L z$A6n-v(IKX$u66ay&)1dyvU+K5|kQ15wrwTI~B0gDUO4c;v0^1Z2iztJGRwvMr8)a z+R=`TRE1Wjp{---I0y)pBqbypSxI0QxPg$|o9t%qz9oB?WcT*NB7!1BKk%Vv=6<+$ z?!D)K{^x(5bDjsjqpfLh8}n*}p{s8i&2QdsEb}-FuC}df@E9J4Fb?g0$}kMW$YxW< zhBa=(>%NL$n(92pLa#7B`s+F)Ga57ok8C!=!L3GSG-y2YNVPGqMtmnQri!bmil!qu9Vl899UTGOw7|jJA4E-;1oF%y)yArotLYB(a>vqJ&pQdJ zK~DFp%&Vp5N4*LW2$}&6lCNIiH!B<6d*dIVq z6F7yHfoO`Tc81#8TEbxsML9z(GRVC7OIg4EVVtFQ!h<2KBKPg4Ej?`7(nFmWBnMb> zq_DRWUVCm8l2pZEf8dg0{Th&9UzBo}oz9+a(xVaXY`U9Sv+Hm=&1l*f_Tq8OB}*`g zDgterC=@ol9yhYABA;VhR&tjBbRC3%@iCp_fg=>#1&SKGG7zWD&GN=ZP8^px5eYH9 z#)E6d3JjwZmrDSHq@zKI@1tJ{F+L_Usno)gk1gY!<~_9fAQ}a)2OI*TI!JCf9yr46 z>*kZvVb9Auz6K=ks}Dqp#Or_B!jbN7rhBG*S?4B!Vdwx@7^3-aI~gBKG7=wV_I2}l z=;3v=>}h6q^G4P*6`-ebAOIYIrDzgIk943aGD?&y1KF|DXG*6)i-svI$^l77gTNpr zlb~zYiwq6)VJ*z!_xn+Y;sAsPLu4~Lix$=L*drToJI@6MNFFyrn2E&FD2jp*;>tiE zCo-p_jI*QtjHC@L3;`9$zRg4u89G|qS+wXj03upW#9A!L*m#PUUwVzE`(8mm$A+ST zuoO^a`Laly&Itl3wmZ0Pc6Cm?fzQcibh4@A#KL{VhELHg2LZTm%^D_^77~vnNF^g! zEKcrOy&6e?PX!!bK<1TLytbr%9VH0ak!V^aDth$JZ#>)4k2J!eIA=$dg5 zSe#|JrD}!-`p7SI;HW5L-VL=>RCR-(?fi`cR#!LwlK+6 zPIsV}MGXt^Tsw`jigKJzGp0-xvfMi{MxrUf!9zaWl0*tS&CP#d*&RQmVezd1WHUON zb{np;Vjh2DCV`_qdVOuAN5b5(^i~=c-oo~6Z`0Z0W8ty|%<|4*EPpDofer=}p-V3D zmt+K;^ORc)q3zHyW-ol2e>d--_0#>_c+0(%&Agv`@BIn(vgtS-b^x0Hv6GJ0c5D_C zbFQtXd1o_^{c6+s-)gZWlO2LRzLTa2F#^!i>POed@Op2c++_!%gc2Heu3kk{dj)`d z?!GJ6TaNV-iJfKr18bQ)XCsE8vvSTxU=Z{0Zo-2hAVj|sqW|RJ#F`#}5CTPua??%q zjKqhLDk~A3VdJlN5(gR7xbwi zhC?dbw(V!!Y@c{)deo>%*Fja3ixezL@Z6?@#NsMl$n zmxjX$j`j4=-rh-aq=3);L*GIpfGoqo_DxI27aG>oJE0!A??Cl~brgab#IVk(F4v&9X7YsOkUb-!RF zZO|R)#b(P@P9Ts3U0im9b=lPPs8Q3F?Ez!+3$-|fmGZJO7T-L~t8e``CzdawvChCS zbPW9r!LIi(GAW$GN@z?+?N|62kjEuCx{Gk)x)y4{xEZqmz-uFmAWR_BWoO z>9#8L6xd2!q@$k_>Ujf34>HZWB)5v{DNY1rc738<5yTZ4`NGsFrbB+sSn8@IF>;nj zBE#~=MwT~1vCYnad0il_ebdOsBO+HBH3FgkJi>>O2l(VcjgI%_(NG z%fc;7zK`vK5m1XIDHJxgH~)u!?P!@Wi0^DdV0b+kFFk#`kqK`z!ojV^Q|rHHG%a=; zkFQ;1%$+&K5JFtFBMQUgb{K0`)fr7qb%sM=0LI*zQ?CAj&HttS4+;VX9P(+kG5`Po M07*qoM6N<$f*mcDzyJUM literal 0 HcmV?d00001 diff --git a/Mods/vcmi/Sprites/vcmi/battleQueue/defendSmall.png b/Mods/vcmi/Sprites/vcmi/battleQueue/defendSmall.png new file mode 100644 index 0000000000000000000000000000000000000000..b22a1b5d64d0a96e98c740ec1d961a722df4c00d GIT binary patch literal 733 zcmV<30wVp1P)WFU8GbZ8()Nlj2>E@cM*00KctL_t(I%hgj|NYil? zJ^OFdZRV2c+DGi;Pg_l?<}gr}qY_4D3o6`01VJx`^yWGpS*;-IA7Cn#N4^JZkI9Y9g6Ydu?%Y&f|@E`$@pYCzWNAj z1^8ycmCJM&D;h5Bl={9d@i$5pGL?jmOpl_cuSbWuaKO@SVGfT@%vv4p!)qP^ko@Ke zGD-gM-fH zVhb&V&lf`XLnn4u^5JyNyc-Xs`hR=g>ZTC!0g5E&0D>=X?;sosKwQd&Xp;f}z$sY3 zVk%4JGFdGLDZ*OLQbK~I=de7u49#{OOotkw-oXauKcl>&3XVY+1Bhc?CU$!U2TUf$ zkj;rJ*SbKfwFvsgFf#ZChCK$B$zleEC=~ObYc^)>ED-+)GjX(5#7Q$bW*IHLy-lSkdT$%CMIwpQ15W?gKTGN_IXlxiE5;N@ P00000NkvXXu0mjf0K+qH literal 0 HcmV?d00001 diff --git a/Mods/vcmi/Sprites/vcmi/battleQueue/statesBig.json b/Mods/vcmi/Sprites/vcmi/battleQueue/statesBig.json new file mode 100644 index 000000000..e8383883c --- /dev/null +++ b/Mods/vcmi/Sprites/vcmi/battleQueue/statesBig.json @@ -0,0 +1,8 @@ +{ + "basepath": "vcmi/battleQueue/", + "images" : + [ + { "frame" : 0, "file" : "defendBig"}, + { "frame" : 1, "file" : "waitBig"} + ] +} diff --git a/Mods/vcmi/Sprites/vcmi/battleQueue/statesSmall.json b/Mods/vcmi/Sprites/vcmi/battleQueue/statesSmall.json new file mode 100644 index 000000000..796657130 --- /dev/null +++ b/Mods/vcmi/Sprites/vcmi/battleQueue/statesSmall.json @@ -0,0 +1,8 @@ +{ + "basepath": "vcmi/battleQueue/", + "images" : + [ + { "frame" : 0, "file" : "defendSmall"}, + { "frame" : 1, "file" : "waitSmall"} + ] +} diff --git a/Mods/vcmi/Sprites/vcmi/battleQueue/waitBig.png b/Mods/vcmi/Sprites/vcmi/battleQueue/waitBig.png new file mode 100644 index 0000000000000000000000000000000000000000..ed0b70ae650aa15acd67891309d72602aa46c2d7 GIT binary patch literal 1721 zcmV;q21fabP)WFU8GbZ8()Nlj2>E@cM*00tdNL_t(Y$K{r7Y*XhM zhA%s5eVh+C4x~N@$cI^+;AELNiIn1yDa;IQO4TV1lh#Hpv<;~3!d8t`A{IhK^!>HME-JxRW&~W><_&(6T8mPwLfyH;{qKW9r%4gI((oT z3A`kzM?lPZPn}@s&KURZMKQwnFo4R{1&oeOQMRgp$ti(hOfxk( zN>Oncn}2p@9#H^saL?o7zX`R@jgTY+ki3`vpNBHDm!!=X_K;c+807sBNrG1m}ZTsoHli(PDN=;r+oebl`5 zEE_jI13*u2FZs(A031#S`+mL`$!bHgM1TcAu}XY7I>!37wKTu}Yr6Ul@Y0We3K>sK$Z|a7mkZ~$SdNLgJPW~2}Mrqoo#Cp-RHK8 zlfSD^0Zq{~5b*KQp=XFpe1)q>#vcr#8<^I7yHQL`4pOwzNm!q=xQp!{Gik(UJ7*zH zRTocfJ%ysGgeT_K9S)ctZ>PDbDkZX_;xe|pxS5K|s)bG3ttn!6^LqBa>qQTMFNn#l zDdM|pEEE-&abv*Ggb_))lt)7-HZ$p2i#UBwPezc+)MmqCTZ&DBupyy2WbyK*3VwBD zh>6Guz5PChN20X0XyU%BrDrYT_APzkk_rR@^k4lH!vMoD@OV^y`Q{l612AV|mD1_# z)yZ3-;?WQR9YVu8eSQ`OwMr6&%ZljmLA3_VMDWd;Z}3ilVBj9%F#P#19qicAM4<{{ zJ<;9^f-0&5*}fEy23uaJXaAAg$-U-nFY)egc2eqqW2bfEQG@56u17Z@_I0nRTT_HX z4N;+0P+ne6Z*MQ_YisycMI~g+Wq7dX_k7;x!%vxO;oB&0jgc0yu6(J zp`gJ)ki|K6_PqTWrkn=Ud^=W48aX++^!t2F zkGC^D-cI}3qpaSrpKDjT$jw!;SnnI>Lo}6TgvSLZFJW?Ph>kA7pAJ8p&}(Lb(Qz}S zC3!r)ejES1_#Q1g-e$24#=^|%D&Y?rrUx|@stu|=Rb=H3h-0U9Q|U^RxYFrmIxQc7 zN8&eWYdgWNwNB1=CkP#E^@yu|eLxU@z+m3D=X^JuZEK>lvx}U?k5ag-h=1L^P9Tsd z)}cFNM4~$NPu4LU3h~;$zngZyQXtgj-{5LrACaiRvD5k-Q*HGMUTdzUcAcB-%q()2 zWFU8GbZ8()Nlj2>E@cM*00LV{L_t(I%e_-wNK{~n?jNz*-a9?y^8;rtH21N<-K89{fo*YI!qI;<%v zXeERQnPkNJ^g*j|rKO1b*FL+EaOZ)l@L3ybYxcI=&8mLr#Y5U8l#^ZgYgBEu6C;o^k$;eM#AF&D+szkxu>wxU232EwjdVQ*VkiyF#$=6E3e*(RQPMg6;G^1 zw|_d%PylkzHsG=Np-Wj)-)ot0fDHF{sn`8Yidly_Ge`4T?P`nZQS5U-AwTYsKlnJ*K{y6M z09#g-`5VdQYW9hV^`nt_+LoiJMxF`MpQeVTH+?;`V`Gv{;PE__!S`)<9{_xAi@CM+ zNL87`;GCWbqqjE%DYjr~K7Cx3A*FLqD>IL p43N~)3?(Hpt2Dz70`jKk{{l_~@gWU&rm+A3002ovPDHLkV1hj^PT&9l literal 0 HcmV?d00001 diff --git a/client/CDefHandler.cpp b/client/CDefHandler.cpp deleted file mode 100644 index 4069c58ca..000000000 --- a/client/CDefHandler.cpp +++ /dev/null @@ -1,356 +0,0 @@ -/* - * CDefHandler.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#include "StdInc.h" -#include "SDL.h" -#include "CDefHandler.h" - -#include "../lib/filesystem/Filesystem.h" -#include "../lib/VCMI_Lib.h" -#include "CBitmapHandler.h" -#include "gui/SDL_Extensions.h" - -#ifdef unused -static long long pow(long long a, int b) -{ - if (!b) return 1; - long c = a; - while (--b) - a*=c; - return a; -} -#endif - -CDefHandler::CDefHandler() -{ -} -CDefHandler::~CDefHandler() -{ - for (auto & elem : ourImages) - { - if (elem.bitmap) - { - SDL_FreeSurface(elem.bitmap); - elem.bitmap=nullptr; - } - } -} - -void CDefHandler::openFromMemory(ui8 *table, const std::string & name) -{ - SDL_Color palette[256]; - SDefEntry &de = * reinterpret_cast(table); - ui8 *p; - - defName = name; - DEFType = read_le_u32(&de.DEFType); - width = read_le_u32(&de.width); - height = read_le_u32(&de.height); - ui32 totalBlocks = read_le_u32(&de.totalBlocks); - - for (ui32 it=0;it<256;it++) - { - palette[it].r = de.palette[it].R; - palette[it].g = de.palette[it].G; - palette[it].b = de.palette[it].B; - palette[it].a = SDL_ALPHA_OPAQUE; - } - - // The SDefEntryBlock starts just after the SDefEntry - p = reinterpret_cast(&de); - p += sizeof(de); - - int totalEntries=0; - for (ui32 z=0; z(p); - ui32 totalInBlock; - - totalInBlock = read_le_u32(&block.totalInBlock); - - for (ui32 j=SEntries.size(); j(FDef + BaseOffset); - - defType2 = read_le_u32(&sd.defType2); - FullWidth = read_le_u32(&sd.FullWidth); - FullHeight = read_le_u32(&sd.FullHeight); - SpriteWidth = read_le_u32(&sd.SpriteWidth); - SpriteHeight = read_le_u32(&sd.SpriteHeight); - LeftMargin = read_le_u32(&sd.LeftMargin); - TopMargin = read_le_u32(&sd.TopMargin); - RightMargin = FullWidth - SpriteWidth - LeftMargin; - BottomMargin = FullHeight - SpriteHeight - TopMargin; - - //if(LeftMargin + RightMargin < 0) - // SpriteWidth += LeftMargin + RightMargin; //ugly construction... TODO: check how to do it nicer - if(LeftMargin<0) - SpriteWidth+=LeftMargin; - if(RightMargin<0) - SpriteWidth+=RightMargin; - - // Note: this looks bogus because we allocate only FullWidth, not FullWidth+add - add = 4 - FullWidth%4; - if (add==4) - add=0; - - ret = SDL_CreateRGBSurface(SDL_SWSURFACE, FullWidth, FullHeight, 8, 0, 0, 0, 0); - - if(nullptr == ret) - { - logGlobal->error("%s: Unable to create surface", __FUNCTION__); - logGlobal->error("%dX%d", FullWidth, FullHeight); - logGlobal->error(SDL_GetError()); - throw std::runtime_error("Unable to create surface"); - } - - BaseOffset += sizeof(SSpriteDef); - int BaseOffsetor = BaseOffset; - - SDL_Palette * p = SDL_AllocPalette(256); - SDL_SetPaletteColors(p, palette, 0, 256); - SDL_SetSurfacePalette(ret, p); - SDL_FreePalette(p); - - int ftcp=0; - - // If there's a margin anywhere, just blank out the whole surface. - if (TopMargin > 0 || BottomMargin > 0 || LeftMargin > 0 || RightMargin > 0) { - memset( reinterpret_cast(ret->pixels), 0, FullHeight*FullWidth); - } - - // Skip top margin - if (TopMargin > 0) - ftcp += TopMargin*(FullWidth+add); - - switch(defType2) - { - case 0: - { - for (ui32 i=0;i0) - ftcp += LeftMargin; - - memcpy(reinterpret_cast(ret->pixels)+ftcp, &FDef[BaseOffset], SpriteWidth); - ftcp += SpriteWidth; - BaseOffset += SpriteWidth; - - if (RightMargin>0) - ftcp += RightMargin; - } - } - break; - - case 1: - { - const ui32 * RWEntriesLoc = reinterpret_cast(FDef+BaseOffset); - BaseOffset += sizeof(int) * SpriteHeight; - for (ui32 i=0;i0) - ftcp += LeftMargin; - - TotalRowLength=0; - do - { - ui32 SegmentLength; - - SegmentType=FDef[BaseOffset++]; - SegmentLength=FDef[BaseOffset++] + 1; - - if (SegmentType==0xFF) - { - memcpy(reinterpret_cast(ret->pixels)+ftcp, FDef + BaseOffset, SegmentLength); - BaseOffset+=SegmentLength; - } - else - { - memset(reinterpret_cast(ret->pixels)+ftcp, SegmentType, SegmentLength); - } - ftcp += SegmentLength; - TotalRowLength += SegmentLength; - }while(TotalRowLength0) - ftcp += RightMargin; - - if (add>0) - ftcp += add+RowAdd; - } - } - break; - - case 2: - { - BaseOffset = BaseOffsetor + read_le_u16(FDef + BaseOffsetor); - - for (ui32 i=0;i0) - ftcp += LeftMargin; - - TotalRowLength=0; - - do - { - SegmentType=FDef[BaseOffset++]; - ui8 code = SegmentType / 32; - ui8 value = (SegmentType & 31) + 1; - if(code==7) - { - memcpy(reinterpret_cast(ret->pixels)+ftcp, &FDef[BaseOffset], value); - ftcp += value; - BaseOffset += value; - } - else - { - memset(reinterpret_cast(ret->pixels)+ftcp, code, value); - ftcp += value; - } - TotalRowLength+=value; - } while(TotalRowLength0) - ftcp += RightMargin; - - RowAdd=SpriteWidth-TotalRowLength; - - if (add>0) - ftcp += add+RowAdd; - } - } - break; - - case 3: - { - for (ui32 i=0;i0) - ftcp += LeftMargin; - - TotalRowLength=0; - - do - { - SegmentType=FDef[BaseOffset++]; - ui8 code = SegmentType / 32; - ui8 value = (SegmentType & 31) + 1; - - int len = std::min(value, SpriteWidth - TotalRowLength) - std::max(0, -LeftMargin); - vstd::amax(len, 0); - - if(code==7) - { - memcpy((ui8*)ret->pixels + ftcp, FDef + BaseOffset, len); - ftcp += len; - BaseOffset += len; - } - else - { - memset((ui8*)ret->pixels + ftcp, code, len); - ftcp += len; - } - TotalRowLength+=( LeftMargin>=0 ? value : value+LeftMargin ); - }while(TotalRowLength0) - ftcp += RightMargin; - - RowAdd=SpriteWidth-TotalRowLength; - - if (add>0) - ftcp += add+RowAdd; - } - } - break; - - default: - throw std::runtime_error("Unknown sprite format."); - break; - } - - SDL_Color ttcol = ret->format->palette->colors[0]; - Uint32 keycol = SDL_MapRGBA(ret->format, ttcol.r, ttcol.b, ttcol.g, ttcol.a); - SDL_SetColorKey(ret, SDL_TRUE, keycol); - - return ret; -} - -CDefHandler * CDefHandler::giveDef(const std::string & defName) -{ - ResourceID resID(std::string("SPRITES/") + defName, EResType::ANIMATION); - - auto data = CResourceHandler::get()->load(resID)->readAll().first; - if(!data) - throw std::runtime_error("bad def name!"); - auto nh = new CDefHandler(); - nh->openFromMemory(data.get(), defName); - return nh; -} - diff --git a/client/CDefHandler.h b/client/CDefHandler.h deleted file mode 100644 index 0ea257e2a..000000000 --- a/client/CDefHandler.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * CDefHandler.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include "../lib/vcmi_endian.h" - -struct SDL_Surface; -struct SDL_Color; - -struct Cimage -{ - int groupNumber; - std::string imName; //name without extension - SDL_Surface * bitmap; -}; - -// Def entry in file. Integer fields are all little endian and will -// need to be converted. -struct SDefEntryBlock -{ - ui32 unknown1; - ui32 totalInBlock; - ui32 unknown2; - ui32 unknown3; - ui8 data[0]; -} PACKED_STRUCT; - -// Def entry in file. Integer fields are all little endian and will -// need to be converted. -struct SDefEntry -{ - ui32 DEFType; - ui32 width; - ui32 height; - ui32 totalBlocks; - - struct { - ui8 R; - ui8 G; - ui8 B; - } palette[256]; - - // SDefEntry is followed by a series of SDefEntryBlock - // This is commented out because VC++ doesn't accept C99 syntax. - //struct SDefEntryBlock blocks[]; -} PACKED_STRUCT; - -// Def entry in file. Integer fields are all little endian and will -// need to be converted. -struct SSpriteDef -{ - ui32 prSize; - ui32 defType2; - ui32 FullWidth; - ui32 FullHeight; - ui32 SpriteWidth; - ui32 SpriteHeight; - ui32 LeftMargin; - ui32 TopMargin; -} PACKED_STRUCT; - - -class CDefHandler -{ -private: - ui32 DEFType; - struct SEntry - { - std::string name; - int offset; - int group; - } ; - std::vector SEntries ; - - void openFromMemory(ui8 * table, const std::string & name); - SDL_Surface * getSprite (int SIndex, const ui8 * FDef, const SDL_Color * palette) const; -public: - int width, height; //width and height - std::string defName; - std::vector ourImages; - - CDefHandler(); - ~CDefHandler(); - - - static CDefHandler * giveDef(const std::string & defName); -}; diff --git a/client/CMT.cpp b/client/CMT.cpp index a8c4ac299..851f6bf5a 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -714,6 +714,46 @@ void processCommand(const std::string &message) std::cout << "\rExtracting done :)\n"; std::cout << " Extracted files can be found in " << outPath << " directory\n"; } + else if(message=="get config") + { + std::cout << "Command accepted.\t"; + + const bfs::path outPath = + VCMIDirs::get().userCachePath() / "extracted" / "configuration"; + + bfs::create_directories(outPath); + + const std::vector contentNames = {"heroClasses", "artifacts", "creatures", "factions", "objects", "heroes", "spells", "skills"}; + + for(auto contentName : contentNames) + { + auto & content = VLC->modh->content[contentName]; + + auto contentOutPath = outPath / contentName; + bfs::create_directories(contentOutPath); + + for(auto & iter : content.modData) + { + const JsonNode & modData = iter.second.modData; + + for(auto & nameAndObject : modData.Struct()) + { + const JsonNode & object = nameAndObject.second; + + std::string name = CModHandler::normalizeIdentifier(object.meta, "core", nameAndObject.first); + + boost::algorithm::replace_all(name,":","_"); + + const bfs::path filePath = contentOutPath / (name + ".json"); + bfs::ofstream file(filePath); + file << object.toJson(); + } + } + } + + std::cout << "\rExtracting done :)\n"; + std::cout << " Extracted files can be found in " << outPath << " directory\n"; + } else if(message=="get txt") { std::cout << "Command accepted.\t"; diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index c43bb105d..6f23b36bb 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -45,7 +45,6 @@ set(client_SRCS CBitmapHandler.cpp CreatureCostBox.cpp - CDefHandler.cpp CGameInfo.cpp Client.cpp CMessage.cpp @@ -103,7 +102,6 @@ set(client_HEADERS CBitmapHandler.h CreatureCostBox.h - CDefHandler.h CGameInfo.h Client.h CMessage.h diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index be9b56a68..cd34e2e98 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -42,7 +42,8 @@ #include "../lib/JsonNode.h" #include "CMusicHandler.h" #include "../lib/CondSh.h" -#include "../lib/NetPacks.h" +#include "../lib/NetPacksBase.h" +#include "../lib/NetPacks.h"//todo: remove #include "../lib/mapping/CMap.h" #include "../lib/VCMIDirs.h" #include "mapHandler.h" @@ -695,80 +696,91 @@ void CPlayerInterface::battleStart(const CCreatureSet *army1, const CCreatureSet BATTLE_EVENT_POSSIBLE_RETURN; } - -void CPlayerInterface::battleStacksHealedRes(const std::vector > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom) +void CPlayerInterface::battleUnitsChanged(const std::vector & units, const std::vector & customEffects, const std::vector & battleLog) { EVENT_HANDLER_CALLED_BY_CLIENT; BATTLE_EVENT_POSSIBLE_RETURN; - for (auto & healedStack : healedStacks) + for(auto & info : units) { - const CStack * healed = cb->battleGetStackByID(healedStack.first); - if (battleInt->creAnims[healed->ID]->isDead()) + switch(info.operation) { - //stack has been resurrected - battleInt->creAnims[healed->ID]->setType(CCreatureAnim::HOLDING); + case UnitChanges::EOperation::RESET_STATE: + { + const battle::Unit * unit = cb->battleGetUnitByID(info.id); + + if(!unit) + { + logGlobal->error("Invalid unit ID %d", info.id); + continue; + } + + auto iter = battleInt->creAnims.find(info.id); + + if(iter == battleInt->creAnims.end()) + { + logGlobal->error("Unit %d have no animation", info.id); + continue; + } + + CCreatureAnimation * animation = iter->second; + + if(unit->alive() && animation->isDead()) + animation->setType(CCreatureAnim::HOLDING); + + //TODO: handle more cases + } + break; + case UnitChanges::EOperation::REMOVE: + battleInt->stackRemoved(info.id); + break; + case UnitChanges::EOperation::ADD: + { + const CStack * unit = cb->battleGetStackByID(info.id); + if(!unit) + { + logGlobal->error("Invalid unit ID %d", info.id); + continue; + } + battleInt->unitAdded(unit); + } + break; + default: + logGlobal->error("Unknown unit operation %d", (int)info.operation); + break; } } - if(lifeDrain) + battleInt->displayCustomEffects(customEffects); + battleInt->displayBattleLog(battleLog); +} + +void CPlayerInterface::battleObstaclesChanged(const std::vector & obstacles) +{ + EVENT_HANDLER_CALLED_BY_CLIENT; + BATTLE_EVENT_POSSIBLE_RETURN; + + bool needUpdate = false; + + for(auto & change : obstacles) { - const CStack * attacker = cb->battleGetStackByID(healedStacks[0].first, false); - const CStack * defender = cb->battleGetStackByID(lifeDrainFrom, false); - - if(attacker && defender) + if(change.operation == BattleChanges::EOperation::ADD) { - battleInt->displayEffect(52, attacker->position); //TODO: transparency - CCS->soundh->playSound(soundBase::DRAINLIF); - - MetaString text; - attacker->addText(text, MetaString::GENERAL_TXT, 361); - attacker->addNameReplacement(text, false); - text.addReplacement(healedStacks[0].second); - defender->addNameReplacement(text, true); - battleInt->console->addText(text.toString()); + auto instance = cb->battleGetObstacleByID(change.id); + if(instance) + battleInt->obstaclePlaced(*instance); + else + logNetwork->error("Invalid obstacle instance %d", change.id); } else { - logGlobal->error("Unable to display life drain info"); + needUpdate = true; } } - if(tentHeal) - { - const CStack * healer = cb->battleGetStackByID(lifeDrainFrom, false); - const CStack * target = cb->battleGetStackByID(healedStacks[0].first, false); - if(healer && target) - { - MetaString text; - text.addTxt(MetaString::GENERAL_TXT, 414); - healer->addNameReplacement(text, false); - target->addNameReplacement(text, false); - text.addReplacement(healedStacks[0].second); - battleInt->console->addText(text.toString()); - } - else - { - logGlobal->error("Unable to display tent heal info"); - } - } -} - -void CPlayerInterface::battleNewStackAppeared(const CStack * stack) -{ - EVENT_HANDLER_CALLED_BY_CLIENT; - BATTLE_EVENT_POSSIBLE_RETURN; - - battleInt->newStack(stack); -} - -void CPlayerInterface::battleObstaclesRemoved(const std::set & removedObstacles) -{ - EVENT_HANDLER_CALLED_BY_CLIENT; - BATTLE_EVENT_POSSIBLE_RETURN; - - //update accessible hexes - battleInt->redrawBackgroundWithHexes(battleInt->activeStack); + if(needUpdate) + //update accessible hexes + battleInt->redrawBackgroundWithHexes(battleInt->activeStack); } void CPlayerInterface::battleCatapultAttacked(const CatapultAttack & ca) @@ -779,17 +791,6 @@ void CPlayerInterface::battleCatapultAttacked(const CatapultAttack & ca) battleInt->stackIsCatapulting(ca); } -void CPlayerInterface::battleStacksRemoved(const BattleStacksRemoved & bsr) -{ - EVENT_HANDLER_CALLED_BY_CLIENT; - BATTLE_EVENT_POSSIBLE_RETURN; - - for (auto & elem : bsr.stackIDs) //for each removed stack - { - battleInt->stackRemoved(elem); - } -} - void CPlayerInterface::battleNewRound(int round) //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn { EVENT_HANDLER_CALLED_BY_CLIENT; @@ -864,9 +865,9 @@ BattleAction CPlayerInterface::activeStack(const CStack * stack) //called when i BattleAction ret = *(CBattleInterface::givenCommand.data); vstd::clear_pointer(CBattleInterface::givenCommand.data); - if (ret.actionType == Battle::CANCEL) + if(ret.actionType == EActionType::CANCEL) { - if (stackId != ret.stackNumber) + if(stackId != ret.stackNumber) logGlobal->error("Not current active stack action canceled"); logGlobal->trace("Canceled command for %s", stackName); } @@ -932,37 +933,36 @@ void CPlayerInterface::battleTriggerEffect (const BattleTriggerEffect & bte) RETURN_IF_QUICK_COMBAT; battleInt->battleTriggerEffect(bte); } -void CPlayerInterface::battleStacksAttacked(const std::vector & bsa) +void CPlayerInterface::battleStacksAttacked(const std::vector & bsa, const std::vector & battleLog) { EVENT_HANDLER_CALLED_BY_CLIENT; BATTLE_EVENT_POSSIBLE_RETURN; std::vector arg; - for (auto & elem : bsa) + for(auto & elem : bsa) { - const CStack *defender = cb->battleGetStackByID(elem.stackAttacked, false); - const CStack *attacker = cb->battleGetStackByID(elem.attackerID, false); - if (elem.isEffect()) + const CStack * defender = cb->battleGetStackByID(elem.stackAttacked, false); + const CStack * attacker = cb->battleGetStackByID(elem.attackerID, false); + if(elem.isEffect()) { - if (defender && !elem.isSecondary()) - battleInt->displayEffect(elem.effect, defender->position); + if(defender && !elem.isSecondary()) + battleInt->displayEffect(elem.effect, defender->getPosition()); } - if (elem.isSpell()) + if(elem.isSpell()) { - if (defender) - battleInt->displaySpellEffect(elem.spellID, defender->position); + if(defender) + battleInt->displaySpellEffect(elem.spellID, defender->getPosition()); } //FIXME: why action is deleted during enchanter cast? bool remoteAttack = false; - if (LOCPLINT->curAction) - remoteAttack |= LOCPLINT->curAction->actionType != Battle::WALK_AND_ATTACK; + if(LOCPLINT->curAction) + remoteAttack |= LOCPLINT->curAction->actionType != EActionType::WALK_AND_ATTACK; StackAttackedInfo to_put = {defender, elem.damageAmount, elem.killedAmount, attacker, remoteAttack, elem.killed(), elem.willRebirth(), elem.cloneKilled()}; arg.push_back(to_put); } - - battleInt->stacksAreAttacked(arg); + battleInt->stacksAreAttacked(arg, battleLog); } void CPlayerInterface::battleAttack(const BattleAttack * ba) { @@ -982,13 +982,13 @@ void CPlayerInterface::battleAttack(const BattleAttack * ba) if(ba->lucky()) //lucky hit { battleInt->console->addText(attacker->formatGeneralMessage(-45)); - battleInt->displayEffect(18, attacker->position); + battleInt->displayEffect(18, attacker->getPosition()); CCS->soundh->playSound(soundBase::GOODLUCK); } if(ba->unlucky()) //unlucky hit { battleInt->console->addText(attacker->formatGeneralMessage(-44)); - battleInt->displayEffect(48, attacker->position); + battleInt->displayEffect(48, attacker->getPosition()); CCS->soundh->playSound(soundBase::BADLUCK); } if(ba->deathBlow()) @@ -997,12 +997,23 @@ void CPlayerInterface::battleAttack(const BattleAttack * ba) for(auto & elem : ba->bsa) { const CStack * attacked = cb->battleGetStackByID(elem.stackAttacked); - battleInt->displayEffect(73, attacked->position); + battleInt->displayEffect(73, attacked->getPosition()); } CCS->soundh->playSound(soundBase::deathBlow); } + + battleInt->displayCustomEffects(ba->customEffects); + battleInt->waitForAnims(); + auto actionTarget = curAction->getTarget(cb.get()); + + if(actionTarget.empty() || (actionTarget.size() < 2 && !ba->shot())) + { + logNetwork->error("Invalid current action: no destination."); + return; + } + if(ba->shot()) { for(auto & elem : ba->bsa) @@ -1010,17 +1021,22 @@ void CPlayerInterface::battleAttack(const BattleAttack * ba) if(!elem.isSecondary()) //display projectile only for primary target { const CStack * attacked = cb->battleGetStackByID(elem.stackAttacked); - battleInt->stackAttacking(attacker, attacked->position, attacked, true); + battleInt->stackAttacking(attacker, attacked->getPosition(), attacked, true); } } } else { + auto attackFrom = actionTarget.at(0).hexValue; + auto attackTarget = actionTarget.at(1).hexValue; + + //TODO: use information from BattleAttack but not curAction + int shift = 0; - if(ba->counter() && BattleHex::mutualPosition(curAction->destinationTile, attacker->position) < 0) + if(ba->counter() && BattleHex::mutualPosition(attackTarget, attacker->getPosition()) < 0) { - int distp = BattleHex::getDistance(curAction->destinationTile + 1, attacker->position); - int distm = BattleHex::getDistance(curAction->destinationTile - 1, attacker->position); + int distp = BattleHex::getDistance(attackTarget + 1, attacker->getPosition()); + int distm = BattleHex::getDistance(attackTarget - 1, attacker->getPosition()); if(distp < distm) shift = 1; @@ -1028,25 +1044,21 @@ void CPlayerInterface::battleAttack(const BattleAttack * ba) shift = -1; } const CStack * attacked = cb->battleGetStackByID(ba->bsa.begin()->stackAttacked); - battleInt->stackAttacking(attacker, ba->counter() ? curAction->destinationTile + shift : curAction->additionalInfo, attacked, false); + battleInt->stackAttacking(attacker, ba->counter() ? BattleHex(attackTarget + shift) : attackTarget, attacked, false); } //battleInt->waitForAnims(); //FIXME: freeze if(ba->spellLike()) { + //TODO: use information from BattleAttack but not curAction + + auto destination = actionTarget.at(0).hexValue; //display hit animation SpellID spellID = ba->spellID; - battleInt->displaySpellHit(spellID, curAction->destinationTile); + battleInt->displaySpellHit(spellID, destination); } } -void CPlayerInterface::battleObstaclePlaced(const CObstacleInstance &obstacle) -{ - EVENT_HANDLER_CALLED_BY_CLIENT; - BATTLE_EVENT_POSSIBLE_RETURN; - - battleInt->obstaclePlaced(obstacle); -} void CPlayerInterface::battleGateStateChanged(const EGateState state) { diff --git a/client/CPlayerInterface.h b/client/CPlayerInterface.h index c9c4c57a7..fa3727b7d 100644 --- a/client/CPlayerInterface.h +++ b/client/CPlayerInterface.h @@ -212,15 +212,12 @@ public: void battleSpellCast(const BattleSpellCast *sc) override; void battleStacksEffectsSet(const SetStackEffect & sse) override; //called when a specific effect is set to stacks void battleTriggerEffect(const BattleTriggerEffect & bte) override; //various one-shot effect - void battleStacksAttacked(const std::vector & bsa) override; + void battleStacksAttacked(const std::vector & bsa, const std::vector & battleLog) override; void battleStartBefore(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2) override; //called by engine just before battle starts; side=0 - left, side=1 - right void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side) override; //called by engine when battle starts; side=0 - left, side=1 - right - void battleStacksHealedRes(const std::vector > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom) override; //called when stacks are healed / resurrected - void battleNewStackAppeared(const CStack * stack) override; //not called at the beginning of a battle or by resurrection; called eg. when elemental is summoned - void battleObstaclesRemoved(const std::set & removedObstacles) override; //called when a certain set of obstacles is removed from batlefield; IDs of them are given + void battleUnitsChanged(const std::vector & units, const std::vector & customEffects, const std::vector & battleLog) override; + void battleObstaclesChanged(const std::vector & obstacles) override; void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack - void battleStacksRemoved(const BattleStacksRemoved & bsr) override; //called when certain stack is completely removed from battlefield - void battleObstaclePlaced(const CObstacleInstance &obstacle) override; void battleGateStateChanged(const EGateState state) override; void yourTacticPhase(int distance) override; diff --git a/client/CPreGame.cpp b/client/CPreGame.cpp index 5b5eb5a52..8570bb99a 100644 --- a/client/CPreGame.cpp +++ b/client/CPreGame.cpp @@ -3455,7 +3455,8 @@ void CBonusSelection::selectMap(int mapNr, bool initialSelect) //get header std::string & headerStr = ourCampaign->camp->mapPieces.find(mapNr)->second; auto buffer = reinterpret_cast(headerStr.data()); - ourHeader = CMapService::loadMapHeader(buffer, headerStr.size(), scenarioName); + CMapService mapService; + ourHeader = mapService.loadMapHeader(buffer, headerStr.size(), scenarioName); std::map names; names[1] = settings["general"]["playerName"].String(); diff --git a/client/Client.cpp b/client/Client.cpp index 9c74c6ddc..ea15a2617 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -41,6 +41,7 @@ #include "../lib/VCMI_Lib.h" #include "../lib/VCMIDirs.h" #include "../lib/mapping/CMap.h" +#include "../lib/mapping/CMapService.h" #include "../lib/JsonNode.h" #include "mapHandler.h" #include "../lib/CConfigHandler.h" @@ -153,7 +154,7 @@ void CClient::waitForMoveAndSend(PlayerColor color) setThreadName("CClient::waitForMoveAndSend"); assert(vstd::contains(battleints, color)); BattleAction ba = battleints[color]->activeStack(gs->curB->battleGetStackByID(gs->curB->activeStack, false)); - if(ba.actionType != Battle::CANCEL) + if(ba.actionType != EActionType::CANCEL) { logNetwork->trace("Send battle action to server: %s", ba.toString()); MakeAction temp_action(ba); @@ -413,10 +414,11 @@ void CClient::newGame( CConnection *con, StartInfo *si ) c.disableSmartPointerSerialization(); // Initialize game state + CMapService mapService; gs = new CGameState(); logNetwork->info("\tCreating gamestate: %i",tmh.getDiff()); - gs->init(si, settings["general"]["saveRandomMaps"].Bool()); + gs->init(&mapService, si, settings["general"]["saveRandomMaps"].Bool()); logNetwork->info("Initializing GameState (together): %d ms", tmh.getDiff()); // Now after possible random map gen, we know exact player count. @@ -951,7 +953,7 @@ void CClient::installNewBattleInterface(std::shared_ptr ba if(needCallback) { logGlobal->trace("\tInitializing the battle interface for player %s", *color); - auto cbc = std::make_shared(gs, color, this); + auto cbc = std::make_shared(color, this); battleCallbacks[colorUsed] = cbc; battleInterface->init(cbc); } diff --git a/client/Client.h b/client/Client.h index e11360fef..0d7ee709e 100644 --- a/client/Client.h +++ b/client/Client.h @@ -26,7 +26,7 @@ class CGameState; class CGameInterface; class CConnection; class CCallback; -struct BattleAction; +class BattleAction; struct SharedMemory; class CClient; class CScriptingModule; diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index a0084e323..8bd48a0e6 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -645,11 +645,6 @@ void BattleTriggerEffect::applyCl(CClient * cl) callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleTriggerEffect, *this); } -void BattleObstaclePlaced::applyCl(CClient * cl) -{ - callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleObstaclePlaced, *obstacle); -} - void BattleUpdateGateState::applyFirstCl(CClient * cl) { callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleGateStateChanged, state); @@ -667,30 +662,14 @@ void BattleStackMoved::applyFirstCl(CClient *cl) callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStackMoved, movedStack, tilesToMove, distance); } -//void BattleStackAttacked::(CClient *cl) -void BattleStackAttacked::applyFirstCl(CClient *cl) -{ - std::vector bsa; - bsa.push_back(*this); - - callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksAttacked, bsa); -} - void BattleAttack::applyFirstCl(CClient *cl) { callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleAttack, this); - for (auto & elem : bsa) - { - for (int z=0; z > shiftedHealed; - for(auto & elem : healedStacks) - { - shiftedHealed.push_back(std::make_pair(elem.stackId, (ui32)elem.delta)); - } - callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksHealedRes, shiftedHealed, lifeDrain, tentHealing, drainedFrom); + callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleUnitsChanged, changedStacks, customEffects, battleLog); } -void ObstaclesRemoved::applyCl(CClient *cl) +void BattleObstaclesChanged::applyCl(CClient *cl) { //inform interfaces about removed obstacles - callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleObstaclesRemoved, obstacles); + callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleObstaclesChanged, changes); } void CatapultAttack::applyCl(CClient *cl) @@ -744,17 +718,6 @@ void CatapultAttack::applyCl(CClient *cl) callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleCatapultAttacked, *this); } -void BattleStacksRemoved::applyFirstCl(CClient * cl) -{ - //inform interfaces about removed stacks - callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksRemoved, *this); -} - -void BattleStackAdded::applyCl(CClient *cl) -{ - callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleNewStackAppeared, GS(cl)->curB->stacks.back()); -} - CGameState* CPackForClient::GS(CClient *cl) { return cl->gs; diff --git a/client/VCMI_client.cbp b/client/VCMI_client.cbp index 287b4273d..d1a8e725d 100644 --- a/client/VCMI_client.cbp +++ b/client/VCMI_client.cbp @@ -104,8 +104,6 @@ - - diff --git a/client/battle/CBattleAnimations.cpp b/client/battle/CBattleAnimations.cpp index 6fa12c5b9..9b8ca7d5b 100644 --- a/client/battle/CBattleAnimations.cpp +++ b/client/battle/CBattleAnimations.cpp @@ -138,7 +138,7 @@ CAttackAnimation::CAttackAnimation(CBattleInterface *_owner, const CStack *attac dest(_dest), attackedStack(defender), attackingStack(attacker) { assert(attackingStack && "attackingStack is nullptr in CBattleAttack::CBattleAttack !\n"); - attackingStackPosBeforeReturn = attackingStack->position; + attackingStackPosBeforeReturn = attackingStack->getPosition(); } CDefenceAnimation::CDefenceAnimation(StackAttackedInfo _attackedInfo, CBattleInterface * _owner) @@ -184,9 +184,9 @@ bool CDefenceAnimation::init() //reverse unit if necessary - if (attacker && owner->getCurrentPlayerInterface()->cb->isToReverse(stack->position, attacker->position, owner->creDir[stack->ID], attacker->doubleWide(), owner->creDir[attacker->ID])) + if(attacker && owner->getCurrentPlayerInterface()->cb->isToReverse(stack->getPosition(), attacker->getPosition(), owner->creDir[stack->ID], attacker->doubleWide(), owner->creDir[attacker->ID])) { - owner->addNewAnim(new CReverseAnimation(owner, stack, stack->position, true)); + owner->addNewAnim(new CReverseAnimation(owner, stack, stack->getPosition(), true)); return false; } //unit reversed @@ -226,11 +226,10 @@ std::string CDefenceAnimation::getMySound() { if(killed) return battle_sound(stack->getCreature(), killed); - - if (vstd::contains(stack->state, EBattleStackState::DEFENDING_ANIM)) + else if(stack->defendingAnim) return battle_sound(stack->getCreature(), defend); - - return battle_sound(stack->getCreature(), wince); + else + return battle_sound(stack->getCreature(), wince); } CCreatureAnim::EAnimType CDefenceAnimation::getMyAnimType() @@ -243,10 +242,10 @@ CCreatureAnim::EAnimType CDefenceAnimation::getMyAnimType() return CCreatureAnim::DEATH; } - if(vstd::contains(stack->state, EBattleStackState::DEFENDING_ANIM)) + if(stack->defendingAnim) return CCreatureAnim::DEFENCE; - - return CCreatureAnim::HITTED; + else + return CCreatureAnim::HITTED; } void CDefenceAnimation::startAnimation() @@ -324,7 +323,7 @@ bool CMeleeAttackAnimation::init() return false; } - bool toReverse = owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStackPosBeforeReturn, attackedStack->position, owner->creDir[stack->ID], attackedStack->doubleWide(), owner->creDir[attackedStack->ID]); + bool toReverse = owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStackPosBeforeReturn, attackedStack->getPosition(), owner->creDir[stack->ID], attackedStack->doubleWide(), owner->creDir[attackedStack->ID]); if(toReverse) { @@ -366,7 +365,7 @@ bool CMeleeAttackAnimation::init() int mutPos = BattleHex::mutualPosition(attackingStackPosBeforeReturn, dest); if(mutPos == -1 && attackingStack->doubleWide()) { - mutPos = BattleHex::mutualPosition(attackingStackPosBeforeReturn + revShiftattacker, attackedStack->position); + mutPos = BattleHex::mutualPosition(attackingStackPosBeforeReturn + revShiftattacker, attackedStack->getPosition()); } if (mutPos == -1 && attackedStack->doubleWide()) { @@ -558,7 +557,7 @@ CMovementAnimation::CMovementAnimation(CBattleInterface *_owner, const CStack *_ : CBattleStackAnimation(_owner, _stack), destTiles(_destTiles), curentMoveIndex(0), - oldPos(stack->position), + oldPos(stack->getPosition()), begX(0), begY(0), distanceX(0), distanceY(0), timeToMove(0.0), @@ -731,16 +730,18 @@ bool CShootingAnimation::init() } //reverse unit if necessary - if (attackingStack && attackedStack && owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->position, attackedStack->position, owner->creDir[attackingStack->ID], attackingStack->doubleWide(), owner->creDir[attackedStack->ID])) + if (attackingStack && attackedStack && owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->getPosition(), attackedStack->getPosition(), owner->creDir[attackingStack->ID], attackingStack->doubleWide(), owner->creDir[attackedStack->ID])) { - owner->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->position, true)); + owner->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->getPosition(), true)); return false; } // opponent must face attacker ( = different directions) before he can be attacked - if (attackingStack && attackedStack && - owner->creDir[attackingStack->ID] == owner->creDir[attackedStack->ID]) - return false; + //FIXME: this cause freeze + +// if (attackingStack && attackedStack && +// owner->creDir[attackingStack->ID] == owner->creDir[attackedStack->ID]) +// return false; // Create the projectile animation @@ -780,7 +781,7 @@ bool CShootingAnimation::init() int multiplier = spi.reverse ? -1 : 1; double projectileAngle = atan2(fabs((double)destPos.y - fromPos.y), fabs((double)destPos.x - fromPos.x)); - if(shooter->position < dest) + if(shooter->getPosition() < dest) projectileAngle = -projectileAngle; // Calculate projectile start position. Offsets are read out of the CRANIM.TXT. @@ -920,7 +921,7 @@ CCastAnimation::CCastAnimation(CBattleInterface * owner_, const CStack * attacke : CRangedAttackAnimation(owner_, attacker, dest_, defender) { if(!dest_.isValid() && defender) - dest = defender->position; + dest = defender->getPosition(); } bool CCastAnimation::init() @@ -937,17 +938,17 @@ bool CCastAnimation::init() //reverse unit if necessary if(attackedStack) { - if(owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->position, attackedStack->position, owner->creDir[attackingStack->ID], attackingStack->doubleWide(), owner->creDir[attackedStack->ID])) + if(owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->getPosition(), attackedStack->getPosition(), owner->creDir[attackingStack->ID], attackingStack->doubleWide(), owner->creDir[attackedStack->ID])) { - owner->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->position, true)); + owner->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->getPosition(), true)); return false; } } else { - if(dest.isValid() && owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->position, dest, owner->creDir[attackingStack->ID], false, false)) + if(dest.isValid() && owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->getPosition(), dest, owner->creDir[attackingStack->ID], false, false)) { - owner->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->position, true)); + owner->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->getPosition(), true)); return false; } } @@ -962,13 +963,13 @@ bool CCastAnimation::init() // NOTE: two lines below return different positions (very notable with 2-hex creatures). Obtaining via creanims seems to be more precise fromPos = owner->creAnims[attackingStack->ID]->pos.topLeft(); - //xycoord = CClickableHex::getXYUnitAnim(shooter->position, true, shooter, owner); + //xycoord = CClickableHex::getXYUnitAnim(shooter->getPosition(), true, shooter, owner); destPos = CClickableHex::getXYUnitAnim(dest, attackedStack, owner); double projectileAngle = atan2(fabs((double)destPos.y - fromPos.y), fabs((double)destPos.x - fromPos.x)); - if(attackingStack->position < dest) + if(attackingStack->getPosition() < dest) projectileAngle = -projectileAngle; @@ -1045,7 +1046,6 @@ void CCastAnimation::endAnim() CEffectAnimation::CEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx, int _dy, bool _Vflip, bool _alignToBottom) : CBattleAnimation(_owner), destTile(BattleHex::INVALID), - customAnim(_customAnim), x(_x), y(_y), dx(_dx), @@ -1053,13 +1053,29 @@ CEffectAnimation::CEffectAnimation(CBattleInterface * _owner, std::string _custo Vflip(_Vflip), alignToBottom(_alignToBottom) { - logAnim->debug("Created effect animation %s", customAnim); + logAnim->debug("Created effect animation %s", _customAnim); + + customAnim = std::make_shared(_customAnim); } +CEffectAnimation::CEffectAnimation(CBattleInterface * _owner, std::shared_ptr _customAnim, int _x, int _y, int _dx, int _dy) + : CBattleAnimation(_owner), + destTile(BattleHex::INVALID), + customAnim(_customAnim), + x(_x), + y(_y), + dx(_dx), + dy(_dy), + Vflip(false), + alignToBottom(false) +{ + logAnim->debug("Created custom effect animation"); +} + + CEffectAnimation::CEffectAnimation(CBattleInterface * _owner, std::string _customAnim, BattleHex _destTile, bool _Vflip, bool _alignToBottom) : CBattleAnimation(_owner), destTile(_destTile), - customAnim(_customAnim), x(-1), y(-1), dx(0), @@ -1067,24 +1083,18 @@ CEffectAnimation::CEffectAnimation(CBattleInterface * _owner, std::string _custo Vflip(_Vflip), alignToBottom(_alignToBottom) { - logAnim->debug("Created effect animation %s", customAnim); + logAnim->debug("Created effect animation %s", _customAnim); + customAnim = std::make_shared(_customAnim); } - bool CEffectAnimation::init() { if(!isEarliest(true)) return false; - if(customAnim.empty()) - { - endAnim(); - return false; - } - const bool areaEffect = (!destTile.isValid() && x == -1 && y == -1); - std::shared_ptr animation = std::make_shared(customAnim); + std::shared_ptr animation = customAnim; animation->preload(); if(Vflip) diff --git a/client/battle/CBattleAnimations.h b/client/battle/CBattleAnimations.h index c15520196..af340ea25 100644 --- a/client/battle/CBattleAnimations.h +++ b/client/battle/CBattleAnimations.h @@ -238,7 +238,7 @@ class CEffectAnimation : public CBattleAnimation { private: BattleHex destTile; - std::string customAnim; + std::shared_ptr customAnim; int x, y, dx, dy; bool Vflip; bool alignToBottom; @@ -248,6 +248,9 @@ public: void endAnim() override; CEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx = 0, int _dy = 0, bool _Vflip = false, bool _alignToBottom = false); + + CEffectAnimation(CBattleInterface * _owner, std::shared_ptr _customAnim, int _x, int _y, int _dx = 0, int _dy = 0); + CEffectAnimation(CBattleInterface * _owner, std::string _customAnim, BattleHex _destTile, bool _Vflip = false, bool _alignToBottom = false); virtual ~CEffectAnimation(){}; }; diff --git a/client/battle/CBattleInterface.cpp b/client/battle/CBattleInterface.cpp index 2d2180406..8fe3e49d5 100644 --- a/client/battle/CBattleInterface.cpp +++ b/client/battle/CBattleInterface.cpp @@ -15,7 +15,6 @@ #include "CCreatureAnimation.h" #include "../CBitmapHandler.h" -#include "../CDefHandler.h" #include "../CGameInfo.h" #include "../CMessage.h" #include "../CMT.h" @@ -129,18 +128,23 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet tacticsMode = static_cast(tacticianInterface); //create stack queue - bool embedQueue = screen->h < 700; + + bool embedQueue; + + std::string queueSize = settings["battle"]["queueSize"].String(); + + if(queueSize == "auto") + embedQueue = screen->h < 700; + else + embedQueue = screen->h < 700 || queueSize == "small"; + queue = new CStackQueue(embedQueue, this); - if (!embedQueue) + if(!embedQueue) { if (settings["battle"]["showQueue"].Bool()) pos.y += queue->pos.h / 2; //center whole window queue->moveTo(Point(pos.x, pos.y - queue->pos.h)); -// queue->pos.x = pos.x; -// queue->pos.y = pos.y - queue->pos.h; -// pos.h += queue->pos.h; -// center(); } queue->update(); @@ -159,7 +163,7 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet std::vector stacks = curInt->cb->battleGetAllStacks(true); for (const CStack *s : stacks) { - newStack(s); + unitAdded(s); } //preparing menu background and terrain @@ -307,9 +311,9 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet bfield.push_back(hex); } //locking occupied positions on batlefield - for (const CStack *s : stacks) //stacks gained at top of this function - if (s->position >= 0) //turrets have position < 0 - bfield[s->position]->accessible = false; + for(const CStack * s : stacks) //stacks gained at top of this function + if(s->initialPosition >= 0) //turrets have position < 0 + bfield[s->getPosition()]->accessible = false; //preparing graphic with cell borders cellBorders = CSDL_Ext::newSurface(background->w, background->h, cellBorder); @@ -340,31 +344,47 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet //preparing obstacle defs auto obst = curInt->cb->battleGetAllObstacles(); - for (auto & elem : obst) + for(auto & elem : obst) { - const int ID = elem->ID; - if (elem->obstacleType == CObstacleInstance::USUAL) + if(elem->obstacleType == CObstacleInstance::USUAL) { - idToObstacle[ID] = CDefHandler::giveDef(elem->getInfo().defName); - for (auto & _n : idToObstacle[ID]->ourImages) + std::string animationName = elem->getInfo().defName; + + auto cached = animationsCache.find(animationName); + + if(cached == animationsCache.end()) { - CSDL_Ext::setDefaultColorKey(_n.bitmap); + auto animation = std::make_shared(animationName); + animationsCache[animationName] = animation; + obstacleAnimations[elem->uniqueID] = animation; + animation->preload(); + } + else + { + obstacleAnimations[elem->uniqueID] = cached->second; } } else if (elem->obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE) { - idToAbsoluteObstacle[ID] = BitmapHandler::loadBitmap(elem->getInfo().defName); + std::string animationName = elem->getInfo().defName; + + auto cached = animationsCache.find(animationName); + + if(cached == animationsCache.end()) + { + auto animation = std::make_shared(); + animation->setCustom(animationName, 0, 0); + animationsCache[animationName] = animation; + obstacleAnimations[elem->uniqueID] = animation; + animation->preload(); + } + else + { + obstacleAnimations[elem->uniqueID] = cached->second; + } } } - quicksand = CDefHandler::giveDef("C17SPE1.DEF"); - landMine = CDefHandler::giveDef("C09SPF1.DEF"); - fireWall = CDefHandler::giveDef("C07SPF61"); - bigForceField[0] = CDefHandler::giveDef("C15SPE10.DEF"); - bigForceField[1] = CDefHandler::giveDef("C15SPE7.DEF"); - smallForceField[0] = CDefHandler::giveDef("C15SPE1.DEF"); - smallForceField[1] = CDefHandler::giveDef("C15SPE4.DEF"); - for (auto hex : bfield) addChild(hex); @@ -432,17 +452,6 @@ CBattleInterface::~CBattleInterface() for (auto & elem : creAnims) delete elem.second; - for (auto & elem : idToObstacle) - delete elem.second; - - delete quicksand; - delete landMine; - delete fireWall; - delete smallForceField[0]; - delete smallForceField[1]; - delete bigForceField[0]; - delete bigForceField[1]; - delete siegeH; //TODO: play AI tracks if battle was during AI turn @@ -829,7 +838,7 @@ void CBattleInterface::bFleef() void CBattleInterface::reallyFlee() { - giveCommand(Battle::RETREAT,0,0); + giveCommand(EActionType::RETREAT); CCS->curh->changeGraphic(ECursor::ADVENTURE, 0); } @@ -841,7 +850,7 @@ void CBattleInterface::reallySurrender() } else { - giveCommand(Battle::SURRENDER,0,0); + giveCommand(EActionType::SURRENDER); CCS->curh->changeGraphic(ECursor::ADVENTURE, 0); } } @@ -887,7 +896,7 @@ void CBattleInterface::bSpellf() CCS->curh->changeGraphic(ECursor::ADVENTURE,0); - ESpellCastProblem::ESpellCastProblem spellCastProblem = curInt->cb->battleCanCastSpell(myHero, ECastingMode::HERO_CASTING); + ESpellCastProblem::ESpellCastProblem spellCastProblem = curInt->cb->battleCanCastSpell(myHero, spells::Mode::HERO); if(spellCastProblem == ESpellCastProblem::OK) { @@ -921,7 +930,7 @@ void CBattleInterface::bWaitf() return; if (activeStack != nullptr) - giveCommand(Battle::WAIT,0,activeStack->ID); + giveCommand(EActionType::WAIT); } void CBattleInterface::bDefencef() @@ -930,7 +939,7 @@ void CBattleInterface::bDefencef() return; if (activeStack != nullptr) - giveCommand(Battle::DEFEND,0,activeStack->ID); + giveCommand(EActionType::DEFEND); } void CBattleInterface::bConsoleUpf() @@ -949,13 +958,13 @@ void CBattleInterface::bConsoleDownf() console->scrollDown(); } -void CBattleInterface::newStack(const CStack *stack) +void CBattleInterface::unitAdded(const CStack * stack) { creDir[stack->ID] = stack->side == BattleSide::ATTACKER; // must be set before getting stack position - Point coords = CClickableHex::getXYUnitAnim(stack->position, stack, this); + Point coords = CClickableHex::getXYUnitAnim(stack->getPosition(), stack, this); - if (stack->position < 0) //turret + if(stack->initialPosition < 0) //turret { const CCreature *turretCreature = CGI->creh->creatures[siegeH->town->town->clientInfo.siegeShooter]; @@ -963,7 +972,7 @@ void CBattleInterface::newStack(const CStack *stack) // Turret positions are read out of the config/wall_pos.txt int posID = 0; - switch (stack->position) + switch (stack->initialPosition) { case -2: // keep creature posID = 18; @@ -995,7 +1004,7 @@ void CBattleInterface::newStack(const CStack *stack) creAnims[stack->ID]->setType(CCreatureAnim::HOLDING); //loading projectiles for units - if (stack->getCreature()->isShooting()) + if(stack->isShooter()) { initStackProjectile(stack); } @@ -1020,7 +1029,7 @@ void CBattleInterface::initStackProjectile(const CStack * stack) idToProjectile[stack->getCreature()->idNumber] = projectile; } -void CBattleInterface::stackRemoved(int stackID) +void CBattleInterface::stackRemoved(uint32_t stackID) { if (activeStack != nullptr) { @@ -1028,7 +1037,7 @@ void CBattleInterface::stackRemoved(int stackID) { BattleAction *action = new BattleAction(); action->side = defendingHeroInstance ? (curInt->playerID == defendingHeroInstance->tempOwner) : false; - action->actionType = Battle::CANCEL; + action->actionType = EActionType::CANCEL; action->stackNumber = activeStack->ID; givenCommand.setn(action); setActiveStack(nullptr); @@ -1055,16 +1064,16 @@ void CBattleInterface::stackMoved(const CStack *stack, std::vector de waitForAnims(); } -void CBattleInterface::stacksAreAttacked(std::vector attackedInfos) +void CBattleInterface::stacksAreAttacked(std::vector attackedInfos, const std::vector & battleLog) { - for (auto & attackedInfo : attackedInfos) + for(auto & attackedInfo : attackedInfos) { //if (!attackedInfo.cloneKilled) //FIXME: play dead animation for cloned creature before it vanishes addNewAnim(new CDefenceAnimation(attackedInfo, this)); - if (attackedInfo.rebirth) + if(attackedInfo.rebirth) { - displayEffect(50, attackedInfo.defender->position); //TODO: play reverse death animation + displayEffect(50, attackedInfo.defender->getPosition()); //TODO: play reverse death animation CCS->soundh->playSound(soundBase::RESURECT); } } @@ -1100,11 +1109,10 @@ void CBattleInterface::stacksAreAttacked(std::vector attacked stackRemoved(attackedInfo.defender->ID); } - if (targets > 1) - printConsoleAttacked(attackedInfos.front().defender, damage, killed, attackedInfos.front().attacker, true); //creatures perish + if(!battleLog.empty()) + displayBattleLog(battleLog); else - printConsoleAttacked(attackedInfos.front().defender, damage, killed, attackedInfos.front().attacker, false); - + printConsoleAttacked(attackedInfos.front().defender, damage, killed, attackedInfos.front().attacker, (targets > 1)); //creatures perish } void CBattleInterface::stackAttacking( const CStack *attacker, BattleHex dest, const CStack *attacked, bool shooting ) @@ -1130,48 +1138,45 @@ void CBattleInterface::newRound(int number) console->addText(CGI->generaltexth->allTexts[412]); } -void CBattleInterface::giveCommand(Battle::ActionType action, BattleHex tile, ui32 stackID, si32 additional, si32 selected) +void CBattleInterface::giveCommand(EActionType action, BattleHex tile, si32 additional) { - const CStack *stack = curInt->cb->battleGetStackByID(stackID); - if (!stack && action != Battle::HERO_SPELL && action != Battle::RETREAT && action != Battle::SURRENDER) + const CStack * actor = nullptr; + if(action != EActionType::HERO_SPELL && action != EActionType::RETREAT && action != EActionType::SURRENDER) { + actor = activeStack; + } + + auto side = curInt->cb->playerToSide(curInt->playerID); + if(!side) + { + logGlobal->error("Player %s is not in battle", curInt->playerID.getStr()); return; } - if (stack && stack != activeStack) - logGlobal->warn("Warning: giving an order to a non-active stack?"); - - auto ba = new BattleAction(); //is deleted in CPlayerInterface::activeStack() - ba->side = defendingHeroInstance ? (curInt->playerID == defendingHeroInstance->tempOwner) : false; + auto ba = new BattleAction(); //is deleted in CPlayerInterface::activeStack() + ba->side = side.get(); ba->actionType = action; - ba->destinationTile = tile; - ba->stackNumber = stackID; - ba->additionalInfo = additional; - ba->selectedStack = selected; + ba->aimToHex(tile); + ba->actionSubtype = additional; - //some basic validations - switch(action) - { - case Battle::WALK_AND_ATTACK: - assert(curInt->cb->battleGetStackByPos(additional)); //stack to attack must exist - case Battle::WALK: - case Battle::SHOOT: - case Battle::CATAPULT: - assert(tile < GameConstants::BFIELD_SIZE); - break; - } + sendCommand(ba, actor); +} - if (!tacticsMode) +void CBattleInterface::sendCommand(BattleAction *& command, const CStack * actor) +{ + command->stackNumber = actor ? actor->unitId() : ((command->side == BattleSide::ATTACKER) ? -1 : -2); + + if(!tacticsMode) { - logGlobal->trace("Setting command for %s", (stack ? stack->nodeName() : "hero")); + logGlobal->trace("Setting command for %s", (actor ? actor->nodeName() : "hero")); myTurn = false; setActiveStack(nullptr); - givenCommand.setn(ba); + givenCommand.setn(command); } else { - curInt->cb->battleMakeTacticAction(ba); - vstd::clear_pointer(ba); + curInt->cb->battleMakeTacticAction(command); + vstd::clear_pointer(command); setActiveStack(nullptr); //next stack will be activated when action ends } @@ -1283,10 +1288,13 @@ void CBattleInterface::displayBattleFinished() void CBattleInterface::spellCast(const BattleSpellCast * sc) { - const SpellID spellID(sc->id); - const CSpell & spell = *spellID.toSpell(); + const SpellID spellID = sc->spellID; + const CSpell * spell = spellID.toSpell(); - const std::string & castSoundPath = spell.getCastSound(); + if(!spell) + return; + + const std::string & castSoundPath = spell->getCastSound(); if (!castSoundPath.empty()) CCS->soundh->playSound(castSoundPath); @@ -1302,7 +1310,7 @@ void CBattleInterface::spellCast(const BattleSpellCast * sc) { if(casterStack != nullptr) { - srccoord = CClickableHex::getXYUnitAnim(casterStack->position, casterStack, this); + srccoord = CClickableHex::getXYUnitAnim(casterStack->getPosition(), casterStack, this); srccoord.x += 250; srccoord.y += 240; } @@ -1311,7 +1319,7 @@ void CBattleInterface::spellCast(const BattleSpellCast * sc) if(casterStack != nullptr && sc->activeCast) { //todo: custom cast animation for hero - displaySpellCast(spellID, casterStack->position); + displaySpellCast(spellID, casterStack->getPosition()); addNewAnim(new CCastAnimation(this, casterStack, sc->tile, curInt->cb->battleGetStackByPos(sc->tile))); } @@ -1330,7 +1338,7 @@ void CBattleInterface::spellCast(const BattleSpellCast * sc) if (Vflip) angle = -angle; - std::string animToDisplay = spell.animationInfo.selectProjectile(angle); + std::string animToDisplay = spell->animationInfo.selectProjectile(angle); if(!animToDisplay.empty()) { @@ -1357,23 +1365,23 @@ void CBattleInterface::spellCast(const BattleSpellCast * sc) displaySpellHit(spellID, sc->tile); //queuing affect animation - for (auto & elem : sc->affectedCres) + for(auto & elem : sc->affectedCres) { - BattleHex position = curInt->cb->battleGetStackByID(elem, false)->position; - displaySpellEffect(spellID, position); + auto stack = curInt->cb->battleGetStackByID(elem, false); + if(stack) + displaySpellEffect(spellID, stack->getPosition()); } //queuing additional animation - for (auto & elem : sc->customEffects) + for(auto & elem : sc->customEffects) { - BattleHex position = curInt->cb->battleGetStackByID(elem.stack, false)->position; - displayEffect(elem.effect, position); + auto stack = curInt->cb->battleGetStackByID(elem.stack, false); + if(stack) + displayEffect(elem.effect, stack->getPosition()); } //displaying message in console - for (const auto & line : sc->battleLog) - if (!console->addText(line.toString())) - logGlobal->warn("Too long battle log line"); + displayBattleLog(sc->battleLog); waitForAnims(); //mana absorption @@ -1395,19 +1403,19 @@ void CBattleInterface::battleStacksEffectsSet(const SetStackEffect & sse) redrawBackgroundWithHexes(activeStack); } -CBattleInterface::PossibleActions CBattleInterface::getCasterAction(const CSpell * spell, const ISpellCaster * caster, ECastingMode::ECastingMode mode) const +CBattleInterface::PossibleActions CBattleInterface::getCasterAction(const CSpell * spell, const spells::Caster * caster, spells::Mode mode) const { PossibleActions spellSelMode = ANY_LOCATION; const CSpell::TargetInfo ti(spell, caster->getSpellSchoolLevel(spell), mode); - if(ti.massive || ti.type == CSpell::NO_TARGET) + if(ti.massive || ti.type == spells::AimType::NO_TARGET) spellSelMode = NO_LOCATION; - else if(ti.type == CSpell::LOCATION && ti.clearAffected) + else if(ti.type == spells::AimType::LOCATION && ti.clearAffected) spellSelMode = FREE_LOCATION; - else if(ti.type == CSpell::CREATURE) + else if(ti.type == spells::AimType::CREATURE) spellSelMode = AIMED_SPELL_CREATURE; - else if(ti.type == CSpell::OBSTACLE) + else if(ti.type == spells::AimType::OBSTACLE) spellSelMode = OBSTACLE; return spellSelMode; @@ -1429,13 +1437,11 @@ void CBattleInterface::setHeroAnimation(ui8 side, int phase) void CBattleInterface::castThisSpell(SpellID spellID) { - auto ba = new BattleAction(); - ba->actionType = Battle::HERO_SPELL; - ba->additionalInfo = spellID; //spell number - ba->destinationTile = -1; - ba->stackNumber = (attackingHeroInstance->tempOwner == curInt->playerID) ? -1 : -2; - ba->side = defendingHeroInstance ? (curInt->playerID == defendingHeroInstance->tempOwner) : false; - spellToCast = ba; + spellToCast = new BattleAction(); + spellToCast->actionType = EActionType::HERO_SPELL; + spellToCast->actionSubtype = spellID; //spell number + spellToCast->stackNumber = (attackingHeroInstance->tempOwner == curInt->playerID) ? -1 : -2; + spellToCast->side = defendingHeroInstance ? (curInt->playerID == defendingHeroInstance->tempOwner) : false; spellDestSelectMode = true; creatureCasting = false; @@ -1443,11 +1449,11 @@ void CBattleInterface::castThisSpell(SpellID spellID) const CGHeroInstance *castingHero = (attackingHeroInstance->tempOwner == curInt->playerID) ? attackingHeroInstance : defendingHeroInstance; assert(castingHero); // code below assumes non-null hero sp = spellID.toSpell(); - PossibleActions spellSelMode = getCasterAction(sp, castingHero, ECastingMode::HERO_CASTING); + PossibleActions spellSelMode = getCasterAction(sp, castingHero, spells::Mode::HERO); if (spellSelMode == NO_LOCATION) //user does not have to select location { - spellToCast->destinationTile = -1; + spellToCast->aimToHex(BattleHex::INVALID); curInt->cb->battleMakeAction(spellToCast); endCastingSpell(); } @@ -1459,6 +1465,29 @@ void CBattleInterface::castThisSpell(SpellID spellID) } } +void CBattleInterface::displayBattleLog(const std::vector & battleLog) +{ + for(const auto & line : battleLog) + { + std::string formatted = line.toString(); + boost::algorithm::trim(formatted); + if(!console->addText(formatted)) + logGlobal->warn("Too long battle log line"); + } +} + +void CBattleInterface::displayCustomEffects(const std::vector & customEffects) +{ + for(const CustomEffectInfo & one : customEffects) + { + if(one.sound != 0) + CCS->soundh->playSound(soundBase::soundID(one.sound)); + const CStack * s = curInt->cb->battleGetStackByID(one.stack, false); + if(s && one.effect != 0) + displayEffect(one.effect, s->getPosition()); + } +} + void CBattleInterface::displayEffect(ui32 effect, BattleHex destTile) { std::string customAnim = graphics->battleACToDef[effect][0]; @@ -1519,31 +1548,37 @@ void CBattleInterface::displaySpellHit(SpellID spellID, BattleHex destinationTil void CBattleInterface::battleTriggerEffect(const BattleTriggerEffect & bte) { - const CStack *stack = curInt->cb->battleGetStackByID(bte.stackID); - //don't show animation when no HP is regenerated - switch (bte.effect) + const CStack * stack = curInt->cb->battleGetStackByID(bte.stackID); + if(!stack) { + logGlobal->error("Invalid stack ID %d", bte.stackID); + return; + } + //don't show animation when no HP is regenerated + switch(bte.effect) + { + //TODO: move to bonus type handler case Bonus::HP_REGENERATION: - displayEffect(74, stack->position); + displayEffect(74, stack->getPosition()); CCS->soundh->playSound(soundBase::REGENER); break; case Bonus::MANA_DRAIN: - displayEffect(77, stack->position); + displayEffect(77, stack->getPosition()); CCS->soundh->playSound(soundBase::MANADRAI); break; case Bonus::POISON: - displayEffect(67, stack->position); + displayEffect(67, stack->getPosition()); CCS->soundh->playSound(soundBase::POISON); break; case Bonus::FEAR: - displayEffect(15, stack->position); + displayEffect(15, stack->getPosition()); CCS->soundh->playSound(soundBase::FEAR); break; case Bonus::MORALE: { std::string hlp = CGI->generaltexth->allTexts[33]; boost::algorithm::replace_first(hlp,"%s",(stack->getName())); - displayEffect(20,stack->position); + displayEffect(20,stack->getPosition()); CCS->soundh->playSound(soundBase::GOODMRLE); console->addText(hlp); break; @@ -1695,14 +1730,14 @@ void CBattleInterface::enterCreatureCastingMode() if (vstd::contains(possibleActions, NO_LOCATION)) { - const ISpellCaster *caster = activeStack; + const spells::Caster *caster = activeStack; const CSpell *spell = SpellID(creatureSpellToCast).toSpell(); - const bool isCastingPossible = (spell->canBeCastAt(curInt->cb.get(), ECastingMode::CREATURE_ACTIVE_CASTING, caster, BattleHex::INVALID) == ESpellCastProblem::OK); + const bool isCastingPossible = spell->canBeCastAt(curInt->cb.get(), spells::Mode::CREATURE_ACTIVE, caster, BattleHex::INVALID); if (isCastingPossible) { myTurn = false; - giveCommand(Battle::MONSTER_SPELL, BattleHex::INVALID, activeStack->ID, creatureSpellToCast); + giveCommand(EActionType::MONSTER_SPELL, BattleHex::INVALID, creatureSpellToCast); selectedStack = nullptr; CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER); @@ -1734,7 +1769,7 @@ void CBattleInterface::getPossibleActionsForStack(const CStack *stack, const boo if(creatureSpellToCast != -1) { const CSpell *spell = SpellID(creatureSpellToCast).toSpell(); - PossibleActions act = getCasterAction(spell, stack, ECastingMode::CREATURE_ACTIVE_CASTING); + PossibleActions act = getCasterAction(spell, stack, spells::Mode::CREATURE_ACTIVE); if(forceCast) { @@ -1760,7 +1795,7 @@ void CBattleInterface::getPossibleActionsForStack(const CStack *stack, const boo possibleActions.push_back(ATTACK); //all active stacks can attack possibleActions.push_back(WALK_AND_ATTACK); //not all stacks can always walk, but we will check this elsewhere - if (stack->canMove() && stack->Speed()) //probably no reason to try move war machines or bound stacks + if (stack->canMove() && stack->Speed(0, true)) //probably no reason to try move war machines or bound stacks possibleActions.push_back (MOVE_STACK); //all active stacks can attack if (siegeH && stack->hasBonusOfType (Bonus::CATAPULT)) //TODO: check shots @@ -1810,15 +1845,9 @@ void CBattleInterface::endAction(const BattleAction* action) { const CStack *stack = curInt->cb->battleGetStackByID(action->stackNumber); - if(action->actionType == Battle::HERO_SPELL) + if(action->actionType == EActionType::HERO_SPELL) setHeroAnimation(action->side, 0); - if (stack && action->actionType == Battle::WALK && - !creAnims[action->stackNumber]->isIdle()) //walk or walk & attack - { - pendingAnims.push_back(std::make_pair(new CMovementEndAnimation(this, stack, action->destinationTile), false)); - } - //check if we should reverse stacks //for some strange reason, it's not enough TStacks stacks = curInt->cb->battleGetStacks(CBattleCallback::MINE_AND_ENEMY); @@ -1828,7 +1857,7 @@ void CBattleInterface::endAction(const BattleAction* action) if (s && creDir[s->ID] != (s->side == BattleSide::ATTACKER) && s->alive() && creAnims[s->ID]->isIdle()) { - addNewAnim(new CReverseAnimation(this, s, s->position, false)); + addNewAnim(new CReverseAnimation(this, s, s->getPosition(), false)); } } @@ -1837,7 +1866,7 @@ void CBattleInterface::endAction(const BattleAction* action) if (tacticsMode) //stack ended movement in tactics phase -> select the next one bTacticNextStack(stack); - if ( action->actionType == Battle::HERO_SPELL) //we have activated next stack after sending request that has been just realized -> blockmap due to movement has changed + if(action->actionType == EActionType::HERO_SPELL) //we have activated next stack after sending request that has been just realized -> blockmap due to movement has changed redrawBackgroundWithHexes(activeStack); if (activeStack && !animsAreDisplayed.get() && pendingAnims.empty() && !active) @@ -1887,7 +1916,7 @@ void CBattleInterface::blockUI(bool on) if(hero) { - ESpellCastProblem::ESpellCastProblem spellcastingProblem = curInt->cb->battleCanCastSpell(hero, ECastingMode::HERO_CASTING); + ESpellCastProblem::ESpellCastProblem spellcastingProblem = curInt->cb->battleCanCastSpell(hero, spells::Mode::HERO); //if magic is blocked, we leave button active, so the message can be displayed after button click canCastSpells = spellcastingProblem == ESpellCastProblem::OK || spellcastingProblem == ESpellCastProblem::MAGIC_IS_BLOCKED; @@ -1926,7 +1955,7 @@ void CBattleInterface::startAction(const BattleAction* action) setHoveredStack(nullptr); blockUI(true); - if (action->actionType == Battle::END_TACTIC_PHASE) + if(action->actionType == EActionType::END_TACTIC_PHASE) { SDL_FreeSurface(menu); menu = BitmapHandler::loadBitmap("CBAR.bmp"); @@ -1943,11 +1972,13 @@ void CBattleInterface::startAction(const BattleAction* action) } else { - assert(action->actionType == Battle::HERO_SPELL); //only cast spell is valid action without acting stack number + assert(action->actionType == EActionType::HERO_SPELL); //only cast spell is valid action without acting stack number } - if (action->actionType == Battle::WALK - || (action->actionType == Battle::WALK_AND_ATTACK && action->destinationTile != stack->position)) + auto actionTarget = action->getTarget(curInt->cb.get()); + + if(action->actionType == EActionType::WALK + || (action->actionType == EActionType::WALK_AND_ATTACK && actionTarget.at(0).hexValue != stack->getPosition())) { assert(stack); moveStarted = true; @@ -1959,7 +1990,7 @@ void CBattleInterface::startAction(const BattleAction* action) redraw(); // redraw after deactivation, including proper handling of hovered hexes - if(action->actionType == Battle::HERO_SPELL) //when hero casts spell + if(action->actionType == EActionType::HERO_SPELL) //when hero casts spell { setHeroAnimation(action->side, 4); return; @@ -1974,12 +2005,12 @@ void CBattleInterface::startAction(const BattleAction* action) int txtid = 0; switch(action->actionType) { - case Battle::WAIT: + case EActionType::WAIT: txtid = 136; break; - case Battle::BAD_MORALE: + case EActionType::BAD_MORALE: txtid = -34; //negative -> no separate singular/plural form - displayEffect(30,stack->position); + displayEffect(30, stack->getPosition()); CCS->soundh->playSound(soundBase::BADMRLE); break; } @@ -1990,8 +2021,8 @@ void CBattleInterface::startAction(const BattleAction* action) //displaying special abilities switch(action->actionType) { - case Battle::STACK_HEAL: - displayEffect(74, action->destinationTile); + case EActionType::STACK_HEAL: + displayEffect(74, actionTarget.at(0).hexValue); CCS->soundh->playSound(soundBase::REGENER); break; } @@ -2049,7 +2080,7 @@ std::string formatDmgRange(std::pair dmgRange) bool CBattleInterface::canStackMoveHere(const CStack * activeStack, BattleHex myNumber) { - std::vector acc = curInt->cb->battleGetAvailableHexes (activeStack, false); + std::vector acc = curInt->cb->battleGetAvailableHexes(activeStack); BattleHex shiftedDest = myNumber.cloneInDirection(activeStack->destShiftDir(), false); if (vstd::contains(acc, myNumber)) @@ -2080,13 +2111,12 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType) //used when l-clicking -> action to be called upon the click std::function realizeAction; - const CStack *const sactive = activeStack; //Get stack on the hex - first try to grab the alive one, if not found -> allow dead stacks. - const CStack *shere = curInt->cb->battleGetStackByPos(myNumber, true); - if (!shere) + const CStack * shere = curInt->cb->battleGetStackByPos(myNumber, true); + if(!shere) shere = curInt->cb->battleGetStackByPos(myNumber, false); - if (!sactive) + if(!activeStack) return; bool ourStack = false; @@ -2120,7 +2150,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType) { if (!(shere && shere->alive())) //we can walk on dead stacks { - if (canStackMoveHere (sactive, myNumber)) + if(canStackMoveHere(activeStack, myNumber)) legalAction = true; } break; @@ -2129,7 +2159,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType) case WALK_AND_ATTACK: case ATTACK_AND_RETURN: { - if (curInt->cb->battleCanAttack(sactive, shere, myNumber)) + if(curInt->cb->battleCanAttack(activeStack, shere, myNumber)) { if (isTileAttackable(myNumber)) // move isTileAttackable to be part of battleCanAttack? { @@ -2143,26 +2173,26 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType) } break; case SHOOT: - if (curInt->cb->battleCanShoot (activeStack, myNumber)) + if(curInt->cb->battleCanShoot(activeStack, myNumber)) legalAction = true; break; case ANY_LOCATION: if (myNumber > -1) //TODO: this should be checked for all actions { - if (isCastingPossibleHere (sactive, shere, myNumber)) + if(isCastingPossibleHere(activeStack, shere, myNumber)) legalAction = true; } break; case AIMED_SPELL_CREATURE: - if (shere && isCastingPossibleHere (sactive, shere, myNumber)) + if(shere && isCastingPossibleHere(activeStack, shere, myNumber)) legalAction = true; break; case RANDOM_GENIE_SPELL: { - if (shere && ourStack && shere != sactive) //only positive spells for other allied creatures + if(shere && ourStack && shere != activeStack && shere->alive()) //only positive spells for other allied creatures { int spellID = curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), shere, CBattleInfoCallback::RANDOM_GENIE); - if (spellID > -1) + if(spellID > -1) { legalAction = true; } @@ -2170,7 +2200,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType) } break; case OBSTACLE: - if (isCastingPossibleHere (sactive, shere, myNumber)) + if(isCastingPossibleHere(activeStack, shere, myNumber)) legalAction = true; break; case TELEPORT: @@ -2178,7 +2208,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType) //todo: move to mechanics ui8 skill = 0; if (creatureCasting) - skill = sactive->getEffectLevel(SpellID(SpellID::TELEPORT).toSpell()); + skill = activeStack->getEffectLevel(SpellID(SpellID::TELEPORT).toSpell()); else skill = getActiveHero()->getEffectLevel(SpellID(SpellID::TELEPORT).toSpell()); //TODO: explicitely save power, skill @@ -2196,7 +2226,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType) break; case FREE_LOCATION: legalAction = true; - if (!isCastingPossibleHere(sactive, shere, myNumber)) + if(!isCastingPossibleHere(activeStack, shere, myNumber)) { legalAction = false; notLegal = true; @@ -2215,7 +2245,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType) { if (!(shere->hasBonusOfType(Bonus::UNDEAD) || shere->hasBonusOfType(Bonus::NON_LIVING) - || vstd::contains(shere->state, EBattleStackState::SUMMONED) + || shere->summoned || shere->isClone() || shere->hasBonusOfType(Bonus::SIEGE_WEAPON) )) @@ -2278,37 +2308,41 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType) { if(activeStack->doubleWide()) { - std::vector acc = curInt->cb->battleGetAvailableHexes(activeStack, false); + std::vector acc = curInt->cb->battleGetAvailableHexes(activeStack); BattleHex shiftedDest = myNumber.cloneInDirection(activeStack->destShiftDir(), false); if(vstd::contains(acc, myNumber)) - giveCommand(Battle::WALK, myNumber, activeStack->ID); + giveCommand(EActionType::WALK, myNumber); else if(vstd::contains(acc, shiftedDest)) - giveCommand(Battle::WALK, shiftedDest, activeStack->ID); + giveCommand(EActionType::WALK, shiftedDest); } else { - giveCommand (Battle::WALK, myNumber, activeStack->ID); + giveCommand(EActionType::WALK, myNumber); } }; break; case ATTACK: case WALK_AND_ATTACK: case ATTACK_AND_RETURN: //TODO: allow to disable return - { - setBattleCursor(myNumber); //handle direction of cursor and attackable tile - setCursor = false; //don't overwrite settings from the call above //TODO: what does it mean? - realizeAction = [=]() { - BattleHex attackFromHex = fromWhichHexAttack(myNumber); - if (attackFromHex >= 0) //we can be in this line when unreachable creature is L - clicked (as of revision 1308) - { - giveCommand(Battle::WALK_AND_ATTACK, attackFromHex, activeStack->ID, myNumber); - } - }; + setBattleCursor(myNumber); //handle direction of cursor and attackable tile + setCursor = false; //don't overwrite settings from the call above //TODO: what does it mean? - std::string estDmgText = formatDmgRange(curInt->cb->battleEstimateDamage(CRandomGenerator::getDefault(), sactive, shere)); //calculating estimated dmg - consoleMsg = (boost::format(CGI->generaltexth->allTexts[36]) % shere->getName() % estDmgText).str(); //Attack %s (%s damage) - } + bool returnAfterAttack = currentAction == ATTACK_AND_RETURN; + + realizeAction = [=]() + { + BattleHex attackFromHex = fromWhichHexAttack(myNumber); + if(attackFromHex.isValid()) //we can be in this line when unreachable creature is L - clicked (as of revision 1308) + { + auto command = new BattleAction(BattleAction::makeMeleeAttack(activeStack, myNumber, attackFromHex, returnAfterAttack)); + sendCommand(command, activeStack); + } + }; + + std::string estDmgText = formatDmgRange(curInt->cb->battleEstimateDamage(activeStack, shere)); //calculating estimated dmg + consoleMsg = (boost::format(CGI->generaltexth->allTexts[36]) % shere->getName() % estDmgText).str(); //Attack %s (%s damage) + } break; case SHOOT: { @@ -2317,14 +2351,14 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType) else cursorFrame = ECursor::COMBAT_SHOOT; - realizeAction = [=](){giveCommand(Battle::SHOOT, myNumber, activeStack->ID);}; - std::string estDmgText = formatDmgRange(curInt->cb->battleEstimateDamage(CRandomGenerator::getDefault(), sactive, shere)); //calculating estimated dmg + realizeAction = [=](){giveCommand(EActionType::SHOOT, myNumber);}; + std::string estDmgText = formatDmgRange(curInt->cb->battleEstimateDamage(activeStack, shere)); //calculating estimated dmg //printing - Shoot %s (%d shots left, %s damage) - consoleMsg = (boost::format(CGI->generaltexth->allTexts[296]) % shere->getName() % sactive->shots.available() % estDmgText).str(); + consoleMsg = (boost::format(CGI->generaltexth->allTexts[296]) % shere->getName() % activeStack->shots.available() % estDmgText).str(); } break; case AIMED_SPELL_CREATURE: - sp = CGI->spellh->objects[creatureCasting ? creatureSpellToCast : spellToCast->additionalInfo]; //necessary if creature has random Genie spell at same time + sp = CGI->spellh->objects[creatureCasting ? creatureSpellToCast : spellToCast->actionSubtype]; //necessary if creature has random Genie spell at same time consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[27]) % sp->name % shere->getName()); //Cast %s on %s switch (sp->id) { @@ -2337,7 +2371,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType) isCastingPossible = true; break; case ANY_LOCATION: - sp = CGI->spellh->objects[creatureCasting ? creatureSpellToCast : spellToCast->additionalInfo]; //necessary if creature has random Genie spell at same time + sp = CGI->spellh->objects[creatureCasting ? creatureSpellToCast : spellToCast->actionSubtype]; //necessary if creature has random Genie spell at same time consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % sp->name); //Cast %s isCastingPossible = true; break; @@ -2360,7 +2394,6 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType) case SACRIFICE: consoleMsg = (boost::format(CGI->generaltexth->allTexts[549]) % shere->getName()).str(); //sacrifice the %s cursorFrame = ECursor::COMBAT_SACRIFICE; - spellToCast->selectedStack = shere->ID; //sacrificed creature is selected isCastingPossible = true; break; case FREE_LOCATION: @@ -2370,18 +2403,18 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType) case HEAL: cursorFrame = ECursor::COMBAT_HEAL; consoleMsg = (boost::format(CGI->generaltexth->allTexts[419]) % shere->getName()).str(); //Apply first aid to the %s - realizeAction = [=](){ giveCommand(Battle::STACK_HEAL, myNumber, activeStack->ID); }; //command healing + realizeAction = [=](){ giveCommand(EActionType::STACK_HEAL, myNumber); }; //command healing break; case RISE_DEMONS: cursorType = ECursor::SPELLBOOK; realizeAction = [=]() { - giveCommand(Battle::DAEMON_SUMMONING, myNumber, activeStack->ID); + giveCommand(EActionType::DAEMON_SUMMONING, myNumber); }; break; case CATAPULT: cursorFrame = ECursor::COMBAT_SHOOT_CATAPULT; - realizeAction = [=](){ giveCommand(Battle::CATAPULT, myNumber, activeStack->ID); }; + realizeAction = [=](){ giveCommand(EActionType::CATAPULT, myNumber); }; break; case CREATURE_INFO: { @@ -2438,17 +2471,19 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType) realizeAction = [=]() { - if (secondaryTarget) //select that target now + if(secondaryTarget) //select that target now { + possibleActions.clear(); switch (sp->id.toEnum()) { case SpellID::TELEPORT: //don't cast spell yet, only select target - possibleActions.push_back (TELEPORT); - spellToCast->selectedStack = selectedStack->ID; + spellToCast->aimToUnit(shere); + possibleActions.push_back(TELEPORT); break; case SpellID::SACRIFICE: - possibleActions.push_back (SACRIFICE); + spellToCast->aimToHex(myNumber); + possibleActions.push_back(SACRIFICE); break; } } @@ -2458,24 +2493,24 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType) { if (sp) { - giveCommand(Battle::MONSTER_SPELL, myNumber, sactive->ID, creatureSpellToCast); + giveCommand(EActionType::MONSTER_SPELL, myNumber, creatureSpellToCast); } else //unknown random spell { - giveCommand(Battle::MONSTER_SPELL, myNumber, sactive->ID, curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), shere, CBattleInfoCallback::RANDOM_GENIE)); + giveCommand(EActionType::MONSTER_SPELL, myNumber); } } else { - assert (sp); + assert(sp); switch (sp->id.toEnum()) { - case SpellID::SACRIFICE: - spellToCast->destinationTile = selectedStack->position; //cast on first creature that will be resurrected - break; - default: - spellToCast->destinationTile = myNumber; - break; + case SpellID::SACRIFICE: + spellToCast->aimToUnit(shere);//victim + break; + default: + spellToCast->aimToHex(myNumber); + break; } curInt->cb->battleMakeAction(spellToCast); endCastingSpell(); @@ -2484,8 +2519,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType) } }; } - //helper lambda that appropriately realizes action / sets cursor and tooltip - auto realizeThingsToDo = [&]() + { if (eventType == MOVE) { @@ -2506,9 +2540,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType) CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER); this->console->alterText(""); } - }; - - realizeThingsToDo(); + } } bool CBattleInterface::isCastingPossibleHere(const CStack *sactive, const CStack *shere, BattleHex myNumber) @@ -2524,7 +2556,10 @@ bool CBattleInterface::isCastingPossibleHere(const CStack *sactive, const CStack spellID = creatureSpellToCast; //TODO: merge with SpellTocast? } else //hero casting - spellID = spellToCast->additionalInfo; + { + spellID = spellToCast->actionSubtype; + } + sp = nullptr; if (spellID >= 0) @@ -2532,15 +2567,15 @@ bool CBattleInterface::isCastingPossibleHere(const CStack *sactive, const CStack if (sp) { - const ISpellCaster *caster = creatureCasting ? static_cast(sactive) : static_cast(curInt->cb->battleGetMyHero()); + const spells::Caster *caster = creatureCasting ? static_cast(sactive) : static_cast(curInt->cb->battleGetMyHero()); if (caster == nullptr) { isCastingPossible = false;//just in case } else { - const ECastingMode::ECastingMode mode = creatureCasting ? ECastingMode::CREATURE_ACTIVE_CASTING : ECastingMode::HERO_CASTING; - isCastingPossible = (sp->canBeCastAt(curInt->cb.get(), mode, caster, myNumber) == ESpellCastProblem::OK); + const spells::Mode mode = creatureCasting ? spells::Mode::CREATURE_ACTIVE : spells::Mode::HERO; + isCastingPossible = sp->canBeCastAt(curInt->cb.get(), mode, caster, myNumber); } } else @@ -2554,7 +2589,7 @@ bool CBattleInterface::isCastingPossibleHere(const CStack *sactive, const CStack BattleHex CBattleInterface::fromWhichHexAttack(BattleHex myNumber) { //TODO far too much repeating code - BattleHex destHex = -1; + BattleHex destHex; switch(CCS->curh->frame) { case 12: //from bottom right @@ -2597,7 +2632,7 @@ BattleHex CBattleInterface::fromWhichHexAttack(BattleHex myNumber) { if(activeStack->doubleWide() && activeStack->side == BattleSide::DEFENDER) { - std::vector acc = curInt->cb->battleGetAvailableHexes(activeStack, false); + std::vector acc = curInt->cb->battleGetAvailableHexes(activeStack); if (vstd::contains(acc, myNumber)) return myNumber - 1; else @@ -2649,7 +2684,7 @@ BattleHex CBattleInterface::fromWhichHexAttack(BattleHex myNumber) { if(activeStack->doubleWide() && activeStack->side == BattleSide::ATTACKER) { - std::vector acc = curInt->cb->battleGetAvailableHexes(activeStack, false); + std::vector acc = curInt->cb->battleGetAvailableHexes(activeStack); if(vstd::contains(acc, myNumber)) return myNumber + 1; else @@ -2713,67 +2748,39 @@ void CBattleInterface::obstaclePlaced(const CObstacleInstance & oi) //so when multiple obstacles are added, they show up one after another waitForAnims(); - int effectID = -1; soundBase::soundID sound; // FIXME(v.markovtsev): soundh->playSound() is commented in the end => warning std::string defname; switch(oi.obstacleType) { - case CObstacleInstance::QUICKSAND: - effectID = 55; - sound = soundBase::QUIKSAND; - break; - case CObstacleInstance::LAND_MINE: - effectID = 47; - sound = soundBase::LANDMINE; - break; - case CObstacleInstance::FORCE_FIELD: + case CObstacleInstance::SPELL_CREATED: { auto &spellObstacle = dynamic_cast(oi); - if (spellObstacle.casterSide) - { - if (oi.getAffectedTiles().size() < 3) - defname = "C15SPE0.DEF"; //TODO cannot find def for 2-hex force field \ appearing - else - defname = "C15SPE6.DEF"; - } - else - { - if (oi.getAffectedTiles().size() < 3) - defname = "C15SPE0.DEF"; - else - defname = "C15SPE9.DEF"; - } + defname = spellObstacle.appearAnimation; + //TODO: sound + //soundBase::QUIKSAND + //soundBase::LANDMINE + //soundBase::FORCEFLD + //soundBase::fireWall } - sound = soundBase::FORCEFLD; - break; - case CObstacleInstance::FIRE_WALL: - if (oi.getAffectedTiles().size() < 3) - effectID = 43; //small fire wall appearing - else - effectID = 44; //and the big one - sound = soundBase::fireWall; break; default: logGlobal->error("I don't know how to animate appearing obstacle of type %d", (int)oi.obstacleType); return; } - if (effectID >= 0 && graphics->battleACToDef[effectID].empty()) - { - logGlobal->error("Cannot find def for effect type %d", effectID); + auto animation = std::make_shared(defname); + animation->preload(); + + IImage * first = animation->getImage(0, 0); + if(!first) return; - } - if (defname.empty() && effectID >= 0) - defname = graphics->battleACToDef[effectID].front(); - - assert(!defname.empty()); //we assume here that effect graphics have the same size as the usual obstacle image // -> if we know how to blit obstacle, let's blit the effect in the same place - Point whereTo = getObstaclePosition(getObstacleImage(oi), oi); - addNewAnim(new CEffectAnimation(this, defname, whereTo.x, whereTo.y)); + Point whereTo = getObstaclePosition(first, oi); + addNewAnim(new CEffectAnimation(this, animation, whereTo.x, whereTo.y)); //TODO we need to wait after playing sound till it's finished, otherwise it overlaps and sounds really bad //CCS->soundh->playSound(sound); @@ -3083,12 +3090,19 @@ void CBattleInterface::showBackgroundImage(SDL_Surface *to) } } -void CBattleInterface::showAbsoluteObstacles(SDL_Surface *to) +void CBattleInterface::showAbsoluteObstacles(SDL_Surface * to) { //Blit absolute obstacles - for (auto &oi : curInt->cb->battleGetAllObstacles()) - if (oi->obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE) - blitAt(getObstacleImage(*oi), pos.x + oi->getInfo().width, pos.y + oi->getInfo().height, to); + for(auto & oi : curInt->cb->battleGetAllObstacles()) + { + if(oi->obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE) + { + IImage * img = getObstacleImage(*oi); + if(img) + img->draw(to, pos.x + oi->getInfo().width, pos.y + oi->getInfo().height); + } + } + if (siegeH && siegeH->town->hasBuilt(BuildingID::CITADEL)) siegeH->printPartOfWall(to, SiegeHelper::BACKGROUND_MOAT); @@ -3108,7 +3122,7 @@ void CBattleInterface::showHighlightedHexes(SDL_Surface *to) const CStack * const shere = curInt->cb->battleGetStackByPos(currentlyHoveredHex, false); if(shere && shere != activeStack && shere->alive()) { - std::vector v = curInt->cb->battleGetAvailableHexes(shere, true); + std::vector v = curInt->cb->battleGetAvailableHexes(shere, true, nullptr); for(BattleHex hex : v) { if(hex != currentlyHoveredHex) @@ -3137,27 +3151,27 @@ void CBattleInterface::showHighlightedHexes(SDL_Surface *to) } if(settings["battle"]["mouseShadow"].Bool() || delayedBlit) { - const ISpellCaster *caster = nullptr; + const spells::Caster *caster = nullptr; const CSpell *spell = nullptr; + spells::Mode mode = spells::Mode::HERO; + if(spellToCast)//hero casts spell { - spell = SpellID(spellToCast->additionalInfo).toSpell(); + spell = SpellID(spellToCast->actionSubtype).toSpell(); caster = getActiveHero(); } else if(creatureSpellToCast >= 0 && stackCanCastSpell && creatureCasting)//stack casts spell { spell = SpellID(creatureSpellToCast).toSpell(); caster = activeStack; + mode = spells::Mode::CREATURE_ACTIVE; } if(caster && spell) //when casting spell { - //calculating spell school level - ui8 schoolLevel = caster->getSpellSchoolLevel(spell); - // printing shaded hex(es) - auto shaded = spell->rangeInHexes(currentlyHoveredHex, schoolLevel, curInt->cb->battleGetMySide()); + auto shaded = spell->rangeInHexes(curInt->cb.get(), mode, caster, currentlyHoveredHex); for(BattleHex shadedHex : shaded) { if((shadedHex.getX() != 0) && (shadedHex.getX() != GameConstants::BFIELD_WIDTH - 1)) @@ -3293,6 +3307,14 @@ void CBattleInterface::showBattlefieldObjects(SDL_Surface *to) void CBattleInterface::showAliveStacks(SDL_Surface *to, std::vector stacks) { + BattleHex currentActionTarget; + if(curInt->curAction) + { + auto target = curInt->curAction->getTarget(curInt->cb.get()); + if(!target.empty()) + currentActionTarget = target.at(0).hexValue; + } + auto isAmountBoxVisible = [&](const CStack *stack) -> bool { if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON) && stack->getCount() == 1) //do not show box for singular war machines, stacked war machines with box shown are supported as extension feature @@ -3312,14 +3334,14 @@ void CBattleInterface::showAliveStacks(SDL_Surface *to, std::vectorcurAction->stackNumber == stack->ID) //stack is currently taking action (is not a target of another creature's action etc) { - if(curInt->curAction->actionType == Battle::WALK || curInt->curAction->actionType == Battle::SHOOT) //hide when stack walks or shoots + if(curInt->curAction->actionType == EActionType::WALK || curInt->curAction->actionType == EActionType::SHOOT) //hide when stack walks or shoots return false; - else if(curInt->curAction->actionType == Battle::WALK_AND_ATTACK && curInt->curAction->destinationTile != stack->position) //when attacking, hide until walk phase finished + else if(curInt->curAction->actionType == EActionType::WALK_AND_ATTACK && currentActionTarget != stack->getPosition()) //when attacking, hide until walk phase finished return false; } - if(curInt->curAction->actionType == Battle::SHOOT && curInt->curAction->destinationTile == stack->position) //hide if we are ranged attack target + if(curInt->curAction->actionType == EActionType::SHOOT && currentActionTarget == stack->getPosition()) //hide if we are ranged attack target return false; } @@ -3355,8 +3377,8 @@ void CBattleInterface::showAliveStacks(SDL_Surface *to, std::vectorside == BattleSide::ATTACKER ? 1 : -1; const int reverseSideShift = stack->side == BattleSide::ATTACKER ? -1 : 1; - const BattleHex nextPos = stack->position + sideShift; - const bool edge = stack->position % GameConstants::BFIELD_WIDTH == (stack->side == BattleSide::ATTACKER ? GameConstants::BFIELD_WIDTH - 2 : 1); + const BattleHex nextPos = stack->getPosition() + sideShift; + const bool edge = stack->getPosition() % GameConstants::BFIELD_WIDTH == (stack->side == BattleSide::ATTACKER ? GameConstants::BFIELD_WIDTH - 2 : 1); const bool moveInside = !edge && !stackCountOutsideHexes[nextPos]; int xAdd = (stack->side == BattleSide::ATTACKER ? 220 : 202) + (stack->doubleWide() ? 44 : 0) * sideShift + @@ -3389,13 +3411,16 @@ void CBattleInterface::showStacks(SDL_Surface *to, std::vector s } } -void CBattleInterface::showObstacles(SDL_Surface *to, std::vector > &obstacles) +void CBattleInterface::showObstacles(SDL_Surface * to, std::vector> & obstacles) { - for (auto & obstacle : obstacles) + for(auto & obstacle : obstacles) { - SDL_Surface *toBlit = getObstacleImage(*obstacle); - Point p = getObstaclePosition(toBlit, *obstacle); - blitAt(toBlit, p.x, p.y, to); + IImage * img = getObstacleImage(*obstacle); + if(img) + { + Point p = getObstaclePosition(img, *obstacle); + img->draw(to, p.x, p.y); + } } } @@ -3452,11 +3477,7 @@ void CBattleInterface::showInterface(SDL_Surface *to) posWithQueue.h += queue->pos.h; } - //showing queue - if (!bresult) - queue->showAll(to); - else - queue->blitBg(to); + queue->showAll(to); } //printing border around interface @@ -3482,7 +3503,7 @@ BattleObjectsByHex CBattleInterface::sortObjectsByHex() return move->nextHex; } } - return stack->position; + return stack->getPosition(); }; BattleObjectsByHex sorted; @@ -3498,7 +3519,7 @@ BattleObjectsByHex CBattleInterface::sortObjectsByHex() if (creAnims.find(stack->ID) == creAnims.end()) //e.g. for summoned but not yet handled stacks continue; - if (stack->position < 0) // turret shooters are handled separately + if (stack->initialPosition < 0) // turret shooters are handled separately continue; //FIXME: hack to ignore ghost stacks @@ -3507,7 +3528,7 @@ BattleObjectsByHex CBattleInterface::sortObjectsByHex() else if (!creAnims[stack->ID]->isDead()) { if (!creAnims[stack->ID]->isMoving()) - sorted.hex[stack->position].alive.push_back(stack); + sorted.hex[stack->getPosition()].alive.push_back(stack); else { // flying creature - just blit them over everyone else @@ -3518,7 +3539,7 @@ BattleObjectsByHex CBattleInterface::sortObjectsByHex() } } else - sorted.hex[stack->position].dead.push_back(stack); + sorted.hex[stack->getPosition()].dead.push_back(stack); } // Sort battle effects (spells) @@ -3612,52 +3633,55 @@ void CBattleInterface::updateBattleAnimations() } } -SDL_Surface *CBattleInterface::getObstacleImage(const CObstacleInstance &oi) +IImage * CBattleInterface::getObstacleImage(const CObstacleInstance & oi) { int frameIndex = (animCount+1) *25 / getAnimSpeed(); - switch(oi.obstacleType) - { - case CObstacleInstance::USUAL: - return vstd::circularAt(idToObstacle.find(oi.ID)->second->ourImages, frameIndex).bitmap; - case CObstacleInstance::ABSOLUTE_OBSTACLE: - return idToAbsoluteObstacle.find(oi.ID)->second; - case CObstacleInstance::QUICKSAND: - return vstd::circularAt(quicksand->ourImages, frameIndex).bitmap; - case CObstacleInstance::LAND_MINE: - return vstd::circularAt(landMine->ourImages, frameIndex).bitmap; - case CObstacleInstance::FIRE_WALL: - return vstd::circularAt(fireWall->ourImages, frameIndex).bitmap; - case CObstacleInstance::FORCE_FIELD: - { - auto &forceField = dynamic_cast(oi); - if (forceField.getAffectedTiles().size() > 2) - return vstd::circularAt(bigForceField[forceField.casterSide]->ourImages, frameIndex).bitmap; - else - return vstd::circularAt(smallForceField[forceField.casterSide]->ourImages, frameIndex).bitmap; - } + std::shared_ptr animation; - case CObstacleInstance::MOAT://moat is blitted by SiegeHelper, this shouldn't be called - default: - assert(0); - return nullptr; + if(oi.obstacleType == CObstacleInstance::USUAL || oi.obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE) + { + animation = obstacleAnimations[oi.uniqueID]; } + else if(oi.obstacleType == CObstacleInstance::SPELL_CREATED) + { + const SpellCreatedObstacle * spellObstacle = dynamic_cast(&oi); + if(!spellObstacle) + return nullptr; + + std::string animationName = spellObstacle->animation; + + auto cacheIter = animationsCache.find(animationName); + + if(cacheIter == animationsCache.end()) + { + logAi->trace("Creating obstacle animation %s", animationName); + + animation = std::make_shared(animationName); + animation->preload(); + animationsCache[animationName] = animation; + } + else + { + animation = cacheIter->second; + } + } + + if(animation) + { + frameIndex %= animation->size(0); + return animation->getImage(frameIndex, 0); + } + + return nullptr; } -Point CBattleInterface::getObstaclePosition(SDL_Surface *image, const CObstacleInstance &obstacle) +Point CBattleInterface::getObstaclePosition(IImage * image, const CObstacleInstance & obstacle) { - int offset = image->h % 42; - if (obstacle.obstacleType == CObstacleInstance::USUAL) - { - if (obstacle.getInfo().blockedTiles.front() < 0 || offset > 37) //second or part is for holy ground ID=62,65,63 - offset -= 42; - } - else if (obstacle.obstacleType == CObstacleInstance::QUICKSAND) - { - offset -= 42; - } + int offset = obstacle.getAnimationYOffset(image->height()); Rect r = hexPosition(obstacle.pos); - r.y += 42 - image->h + offset; + r.y += 42 - image->height() + offset; + return r.topLeft(); } @@ -3671,11 +3695,14 @@ void CBattleInterface::redrawBackgroundWithHexes(const CStack *activeStack) blitAt(background, 0, 0, backgroundWithHexes); //draw absolute obstacles (cliffs and so on) - for (auto &oi : curInt->cb->battleGetAllObstacles()) + for(auto & oi : curInt->cb->battleGetAllObstacles()) { - if (oi->obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE/* || oi.obstacleType == CObstacleInstance::MOAT*/) - blitAt(getObstacleImage(*oi), oi->getInfo().width, - oi->getInfo().height, backgroundWithHexes); + if(oi->obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE) + { + IImage * img = getObstacleImage(*oi); + if(img) + img->draw(backgroundWithHexes, oi->getInfo().width, oi->getInfo().height); + } } if (settings["battle"]["stackRange"].Bool()) @@ -3719,7 +3746,7 @@ void CBattleInterface::showPiecesOfWall(SDL_Surface *to, std::vector pieces for (auto & stack : curInt->cb->battleGetAllStacks(true)) { - if (stack->position == stackPos) + if(stack->initialPosition == stackPos) { turret = stack; break; diff --git a/client/battle/CBattleInterface.h b/client/battle/CBattleInterface.h index 595d311c5..41dd4724a 100644 --- a/client/battle/CBattleInterface.h +++ b/client/battle/CBattleInterface.h @@ -19,7 +19,6 @@ class CLabel; class CCreatureSet; class CGHeroInstance; -class CDefHandler; class CStack; class CCallback; class CButton; @@ -30,7 +29,7 @@ struct BattleSpellCast; struct CObstacleInstance; template struct CondSh; struct SetStackEffect; -struct BattleAction; +class BattleAction; class CGTownInstance; struct CatapultAttack; struct CatapultProjectileInfo; @@ -46,15 +45,16 @@ struct ProjectileInfo; class CClickableHex; struct BattleHex; struct InfoAboutHero; -struct BattleAction; class CBattleGameInterface; +struct CustomEffectInfo; class CAnimation; +class IImage; /// Small struct which contains information about the id of the attacked stack, the damage dealt,... struct StackAttackedInfo { const CStack *defender; //attacked stack - int32_t dmg; //damage dealt + int64_t dmg; //damage dealt unsigned int amountKilled; //how many creatures in stack has been killed const CStack *attacker; //attacking stack bool indirectAttack; //if true, stack was attacked indirectly - spell or ranged attack @@ -132,15 +132,8 @@ private: std::map> idToProjectile; - std::map idToObstacle; //obstacles located on the battlefield - std::map idToAbsoluteObstacle; //obstacles located on the battlefield - - //TODO these should be loaded only when needed (and freed then) but I believe it's rather work for resource manager, - //so I didn't implement that (having ongoing RM development) - CDefHandler *landMine; - CDefHandler *quicksand; - CDefHandler *fireWall; - CDefHandler *smallForceField[2], *bigForceField[2]; // [side] + std::map> animationsCache; + std::map> obstacleAnimations; std::map creDir; // //TODO: move it to battle callback ui8 animCount; @@ -185,7 +178,9 @@ private: void printConsoleAttacked(const CStack *defender, int dmg, int killed, const CStack *attacker, bool Multiple); std::list projectiles; //projectiles flying on battlefield - void giveCommand(Battle::ActionType action, BattleHex tile, ui32 stackID, si32 additional=-1, si32 selectedStack = -1); + void giveCommand(EActionType action, BattleHex tile = BattleHex(), si32 additional = -1); + void sendCommand(BattleAction *& command, const CStack * actor = nullptr); + bool isTileAttackable(const BattleHex & number) const; //returns true if tile 'number' is neighboring any tile from active stack's range or is one of these tiles bool isCatapultAttackable(BattleHex hex) const; //returns true if given tile can be attacked by catapult @@ -259,12 +254,14 @@ private: BattleObjectsByHex sortObjectsByHex(); void updateBattleAnimations(); - SDL_Surface *getObstacleImage(const CObstacleInstance &oi); - Point getObstaclePosition(SDL_Surface *image, const CObstacleInstance &obstacle); + IImage * getObstacleImage(const CObstacleInstance & oi); + + Point getObstaclePosition(IImage * image, const CObstacleInstance & obstacle); + void redrawBackgroundWithHexes(const CStack *activeStack); /** End of battle screen blitting methods */ - PossibleActions getCasterAction(const CSpell *spell, const ISpellCaster *caster, ECastingMode::ECastingMode mode) const; + PossibleActions getCasterAction(const CSpell *spell, const spells::Caster *caster, spells::Mode mode) const; void setHeroAnimation(ui8 side, int phase); public: @@ -329,12 +326,12 @@ public: //call-ins void startAction(const BattleAction* action); - void newStack(const CStack *stack); //new stack appeared on battlefield - void stackRemoved(int stackID); //stack disappeared from batlefiled + void unitAdded(const CStack * stack); //new stack appeared on battlefield + void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled void stackActivated(const CStack *stack); //active stack has been changed void stackMoved(const CStack *stack, std::vector destHex, int distance); //stack with id number moved to destHex void waitForAnims(); - void stacksAreAttacked(std::vector attackedInfos); //called when a certain amount of stacks has been attacked + void stacksAreAttacked(std::vector attackedInfos, const std::vector & battleLog); //called when a certain amount of stacks has been attacked void stackAttacking(const CStack *attacker, BattleHex dest, const CStack *attacked, bool shooting); //called when stack with id ID is attacking something on hex dest void newRoundFirst( int round ); void newRound(int number); //caled when round is ended; number is the number of round @@ -345,6 +342,10 @@ public: void spellCast(const BattleSpellCast *sc); //called when a hero casts a spell void battleStacksEffectsSet(const SetStackEffect & sse); //called when a specific effect is set to stacks void castThisSpell(SpellID spellID); //called when player has chosen a spell from spellbook + + void displayBattleLog(const std::vector & battleLog); + void displayCustomEffects(const std::vector & customEffects); + void displayEffect(ui32 effect, BattleHex destTile); //displays custom effect on the battlefield void displaySpellCast(SpellID spellID, BattleHex destinationTile); //displays spell`s cast animation @@ -370,7 +371,7 @@ public: void gateStateChanged(const EGateState state); - void initStackProjectile(const CStack *stack); + void initStackProjectile(const CStack * stack); const CGHeroInstance *currentHero() const; InfoAboutHero enemyHero() const; diff --git a/client/battle/CBattleInterfaceClasses.cpp b/client/battle/CBattleInterfaceClasses.cpp index 2853482c0..588a60fe1 100644 --- a/client/battle/CBattleInterfaceClasses.cpp +++ b/client/battle/CBattleInterfaceClasses.cpp @@ -203,7 +203,7 @@ void CBattleHero::clickLeft(tribool down, bool previousState) if(!myHero || down || !myOwner->myTurn) return; - if(myOwner->getCurrentPlayerInterface()->cb->battleCanCastSpell(myHero, ECastingMode::HERO_CASTING) == ESpellCastProblem::OK) //check conditions + if(myOwner->getCurrentPlayerInterface()->cb->battleCanCastSpell(myHero, spells::Mode::HERO) == ESpellCastProblem::OK) //check conditions { for(int it=0; itposition < 0) //creatures in turrets + if(stack && stack->initialPosition < 0) //creatures in turrets { - switch(stack->position) + switch(stack->initialPosition) { case -2: //keep ret = cbi->siegeH->town->town->clientInfo.siegePositions[18]; @@ -669,105 +669,130 @@ CHeroInfoWindow::CHeroInfoWindow(const InfoAboutHero &hero, Point *position) : C new CLabel(39, 186, EFonts::FONT_TINY, EAlignment::CENTER, Colors::WHITE, std::to_string(currentSpellPoints) + "/" + std::to_string(maxSpellPoints)); } -void CStackQueue::update() -{ - stacksSorted.clear(); - owner->getCurrentPlayerInterface()->cb->battleGetStackQueue(stacksSorted, stackBoxes.size()); - if(stacksSorted.size()) - { - for (int i = 0; i < stackBoxes.size() ; i++) - { - stackBoxes[i]->setStack(stacksSorted[i]); - } - } - else - { - //no stacks on battlefield... what to do with queue? - } -} - CStackQueue::CStackQueue(bool Embedded, CBattleInterface * _owner) -:embedded(Embedded), owner(_owner) + : embedded(Embedded), + owner(_owner) { OBJ_CONSTRUCTION_CAPTURING_ALL; if(embedded) { - bg = nullptr; pos.w = QUEUE_SIZE * 37; pos.h = 46; pos.x = screen->w/2 - pos.w/2; pos.y = (screen->h - 600)/2 + 10; + + icons = std::make_shared("CPRSMALL"); + stateIcons = std::make_shared("VCMI/BATTLEQUEUE/STATESSMALL"); } else { - bg = BitmapHandler::loadBitmap("DIBOXBCK"); pos.w = 800; pos.h = 85; + + new CFilledTexture("DIBOXBCK", Rect(0,0, pos.w, pos.h)); + + icons = std::make_shared("TWCRPORT"); + stateIcons = std::make_shared("VCMI/BATTLEQUEUE/STATESSMALL"); + //TODO: where use big icons? + //stateIcons = std::make_shared("VCMI/BATTLEQUEUE/STATESBIG"); } + stateIcons->preload(); stackBoxes.resize(QUEUE_SIZE); for (int i = 0; i < stackBoxes.size(); i++) { - stackBoxes[i] = new StackBox(embedded); + stackBoxes[i] = new StackBox(this); stackBoxes[i]->moveBy(Point(1 + (embedded ? 36 : 80)*i, 0)); } } CStackQueue::~CStackQueue() { - SDL_FreeSurface(bg); } -void CStackQueue::showAll(SDL_Surface * to) +void CStackQueue::update() { - blitBg(to); + std::vector queueData; - CIntObject::showAll(to); -} + owner->getCurrentPlayerInterface()->cb->battleGetTurnOrder(queueData, stackBoxes.size(), 0); -void CStackQueue::blitBg( SDL_Surface * to ) -{ - if(bg) + size_t boxIndex = 0; + + for(size_t turn = 0; turn < queueData.size() && boxIndex < stackBoxes.size(); turn++) { - SDL_SetClipRect(to, &pos); - CSDL_Ext::fillTexture(to, bg); - SDL_SetClipRect(to, nullptr); + for(size_t unitIndex = 0; unitIndex < queueData[turn].size() && boxIndex < stackBoxes.size(); boxIndex++, unitIndex++) + stackBoxes[boxIndex]->setStack(queueData[turn][unitIndex], turn); } + + for(; boxIndex < stackBoxes.size(); boxIndex++) + stackBoxes[boxIndex]->setStack(nullptr); } -void CStackQueue::StackBox::showAll(SDL_Surface * to) -{ - assert(stack); - bg->colorize(stack->owner); - CIntObject::showAll(to); - - if(small) - printAtMiddleLoc(makeNumberShort(stack->getCount()), pos.w/2, pos.h - 7, FONT_SMALL, Colors::WHITE, to); - else - printAtMiddleLoc(makeNumberShort(stack->getCount()), pos.w/2, pos.h - 8, FONT_MEDIUM, Colors::WHITE, to); -} - -void CStackQueue::StackBox::setStack( const CStack *stack ) -{ - this->stack = stack; - assert(stack); - icon->setFrame(stack->getCreature()->iconIndex); -} - -CStackQueue::StackBox::StackBox(bool small): - stack(nullptr), - small(small) +CStackQueue::StackBox::StackBox(CStackQueue * owner) + : bg(nullptr), + icon(nullptr), + amount(nullptr), + stateIcon(nullptr) { OBJ_CONSTRUCTION_CAPTURING_ALL; - bg = new CPicture(small ? "StackQueueSmall" : "StackQueueLarge" ); - - if (small) - { - icon = new CAnimImage("CPRSMALL", 0, 0, 5, 2); - } - else - icon = new CAnimImage("TWCRPORT", 0, 0, 9, 1); + bg = new CPicture(owner->embedded ? "StackQueueSmall" : "StackQueueLarge" ); pos.w = bg->pos.w; pos.h = bg->pos.h; + + if(owner->embedded) + { + icon = new CAnimImage(owner->icons, 0, 0, 5, 2); + amount = new CLabel(pos.w/2, pos.h - 7, FONT_SMALL, CENTER, Colors::WHITE); + } + else + { + icon = new CAnimImage(owner->icons, 0, 0, 9, 1); + amount = new CLabel(pos.w/2, pos.h - 8, FONT_MEDIUM, CENTER, Colors::WHITE); + + int icon_x = pos.w - 17; + int icon_y = pos.h - 18; + + stateIcon = new CAnimImage(owner->stateIcons, 0, 0, icon_x, icon_y); + stateIcon->visible = false; + } +} + +void CStackQueue::StackBox::setStack(const battle::Unit * nStack, size_t turn) +{ + if(nStack) + { + bg->colorize(nStack->unitOwner()); + icon->visible = true; + icon->setFrame(nStack->creatureIconIndex()); + amount->setText(makeNumberShort(nStack->getCount())); + + if(stateIcon) + { + if(nStack->defended(turn) || (turn > 0 && nStack->defended(turn - 1))) + { + stateIcon->setFrame(0, 0); + stateIcon->visible = true; + } + else if(nStack->waited(turn)) + { + stateIcon->setFrame(1, 0); + stateIcon->visible = true; + } + else + { + stateIcon->visible = false; + } + } + } + else + { + bg->colorize(PlayerColor::NEUTRAL); + icon->visible = false; + icon->setFrame(0); + amount->setText(""); + + if(stateIcon) + stateIcon->visible = false; + } } diff --git a/client/battle/CBattleInterfaceClasses.h b/client/battle/CBattleInterfaceClasses.h index 38af30b9a..be4eabbb1 100644 --- a/client/battle/CBattleInterfaceClasses.h +++ b/client/battle/CBattleInterfaceClasses.h @@ -23,6 +23,10 @@ class CToggleGroup; class CLabel; struct BattleResult; class CStack; +namespace battle +{ + class Unit; +} class CAnimImage; class CPlayerInterface; @@ -141,27 +145,23 @@ class CStackQueue : public CIntObject public: CPicture * bg; CAnimImage * icon; - const CStack *stack; - bool small; + CLabel * amount; + CAnimImage * stateIcon; - void showAll(SDL_Surface * to) override; - void setStack(const CStack *nStack); - StackBox(bool small); + void setStack(const battle::Unit * nStack, size_t turn = 0); + StackBox(CStackQueue * owner); }; public: static const int QUEUE_SIZE = 10; const bool embedded; - std::vector stacksSorted; std::vector stackBoxes; - - SDL_Surface * bg; - CBattleInterface * owner; + std::shared_ptr icons; + std::shared_ptr stateIcons; + CStackQueue(bool Embedded, CBattleInterface * _owner); ~CStackQueue(); void update(); - void showAll(SDL_Surface *to) override; - void blitBg(SDL_Surface * to); }; diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 35cc1aa6c..67d4ee30e 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -1641,11 +1641,11 @@ CFortScreen::RecruitArea::RecruitArea(int posX, int posY, const CGTownInstance * new CLabel(78, 11, FONT_SMALL, CENTER, Colors::WHITE, getMyCreature()->namePl); Rect sizes(287, 4, 96, 18); - values.push_back(new LabeledValue(sizes, CGI->generaltexth->allTexts[190], CGI->generaltexth->fcommands[0], getMyCreature()->Attack())); + values.push_back(new LabeledValue(sizes, CGI->generaltexth->allTexts[190], CGI->generaltexth->fcommands[0], getMyCreature()->getAttack(false))); sizes.y+=20; - values.push_back(new LabeledValue(sizes, CGI->generaltexth->allTexts[191], CGI->generaltexth->fcommands[1], getMyCreature()->Defense())); + values.push_back(new LabeledValue(sizes, CGI->generaltexth->allTexts[191], CGI->generaltexth->fcommands[1], getMyCreature()->getDefence(false))); sizes.y+=21; - values.push_back(new LabeledValue(sizes, CGI->generaltexth->allTexts[199], CGI->generaltexth->fcommands[2], getMyCreature()->getMinDamage(), getMyCreature()->getMaxDamage())); + values.push_back(new LabeledValue(sizes, CGI->generaltexth->allTexts[199], CGI->generaltexth->fcommands[2], getMyCreature()->getMinDamage(false), getMyCreature()->getMaxDamage(false))); sizes.y+=20; values.push_back(new LabeledValue(sizes, CGI->generaltexth->allTexts[388], CGI->generaltexth->fcommands[3], getMyCreature()->MaxHealth())); sizes.y+=21; diff --git a/client/windows/CCreatureWindow.cpp b/client/windows/CCreatureWindow.cpp index bc0c6a0b2..f45ea8590 100644 --- a/client/windows/CCreatureWindow.cpp +++ b/client/windows/CCreatureWindow.cpp @@ -219,7 +219,7 @@ void CStackWindow::CWindowSection::createStackInfo(bool showExp, bool showArt) int dmgMultiply = 1; if(parent->info->owner && parent->info->stackNode->hasBonusOfType(Bonus::SIEGE_WEAPON)) - dmgMultiply += parent->info->owner->Attack(); + dmgMultiply += parent->info->owner->getPrimSkillLevel(PrimarySkill::ATTACK); new CPicture("stackWindow/icons", 117, 32); @@ -230,9 +230,9 @@ void CStackWindow::CWindowSection::createStackInfo(bool showExp, bool showArt) if(battleStack != nullptr) // in battle { - printStatBase(EStat::ATTACK, CGI->generaltexth->primarySkillNames[0], parent->info->creature->Attack(), battleStack->Attack()); - printStatBase(EStat::DEFENCE, CGI->generaltexth->primarySkillNames[1], parent->info->creature->Defense(false), battleStack->Defense()); - printStatRange(EStat::DAMAGE, CGI->generaltexth->allTexts[199], parent->info->stackNode->getMinDamage() * dmgMultiply, battleStack->getMaxDamage() * dmgMultiply); + printStatBase(EStat::ATTACK, CGI->generaltexth->primarySkillNames[0], parent->info->creature->getAttack(battleStack->isShooter()), battleStack->getAttack(battleStack->isShooter())); + printStatBase(EStat::DEFENCE, CGI->generaltexth->primarySkillNames[1], parent->info->creature->getDefence(battleStack->isShooter()), battleStack->getDefence(battleStack->isShooter())); + printStatRange(EStat::DAMAGE, CGI->generaltexth->allTexts[199], parent->info->stackNode->getMinDamage(battleStack->isShooter()) * dmgMultiply, battleStack->getMaxDamage(battleStack->isShooter()) * dmgMultiply); printStatBase(EStat::HEALTH, CGI->generaltexth->allTexts[388], parent->info->creature->MaxHealth(), battleStack->MaxHealth()); printStatBase(EStat::SPEED, CGI->generaltexth->zelp[441].first, parent->info->creature->Speed(), battleStack->Speed()); @@ -250,9 +250,9 @@ void CStackWindow::CWindowSection::createStackInfo(bool showExp, bool showArt) const bool shooter = parent->info->stackNode->hasBonusOfType(Bonus::SHOOTER) && parent->info->stackNode->valOfBonuses(Bonus::SHOTS); const bool caster = parent->info->stackNode->valOfBonuses(Bonus::CASTS); - printStatBase(EStat::ATTACK, CGI->generaltexth->primarySkillNames[0], parent->info->creature->Attack(), parent->info->stackNode->Attack()); - printStatBase(EStat::DEFENCE, CGI->generaltexth->primarySkillNames[1], parent->info->creature->Defense(false), parent->info->stackNode->Defense()); - printStatRange(EStat::DAMAGE, CGI->generaltexth->allTexts[199], parent->info->stackNode->getMinDamage() * dmgMultiply, parent->info->stackNode->getMaxDamage() * dmgMultiply); + printStatBase(EStat::ATTACK, CGI->generaltexth->primarySkillNames[0], parent->info->creature->getAttack(shooter), parent->info->stackNode->getAttack(shooter)); + printStatBase(EStat::DEFENCE, CGI->generaltexth->primarySkillNames[1], parent->info->creature->getDefence(shooter), parent->info->stackNode->getDefence(shooter)); + printStatRange(EStat::DAMAGE, CGI->generaltexth->allTexts[199], parent->info->stackNode->getMinDamage(shooter) * dmgMultiply, parent->info->stackNode->getMaxDamage(shooter) * dmgMultiply); printStatBase(EStat::HEALTH, CGI->generaltexth->allTexts[388], parent->info->creature->MaxHealth(), parent->info->stackNode->MaxHealth()); printStatBase(EStat::SPEED, CGI->generaltexth->zelp[441].first, parent->info->creature->Speed(), parent->info->stackNode->Speed()); diff --git a/client/windows/CHeroWindow.cpp b/client/windows/CHeroWindow.cpp index 8a6dce30b..8e47c6fc3 100644 --- a/client/windows/CHeroWindow.cpp +++ b/client/windows/CHeroWindow.cpp @@ -61,6 +61,11 @@ const TBonusListPtr CHeroWithMaybePickedArtifact::getAllBonuses(const CSelector return out; } +int64_t CHeroWithMaybePickedArtifact::getTreeVersion() const +{ + return hero->getTreeVersion(); //this assumes that hero and artifact belongs to main bonus tree +} + CHeroWithMaybePickedArtifact::CHeroWithMaybePickedArtifact(CWindowWithArtifacts *Cww, const CGHeroInstance *Hero) : hero(Hero), cww(Cww) { diff --git a/client/windows/CHeroWindow.h b/client/windows/CHeroWindow.h index 40c9788fa..b39e20db7 100644 --- a/client/windows/CHeroWindow.h +++ b/client/windows/CHeroWindow.h @@ -39,7 +39,7 @@ public: }; //helper class for calculating values of hero bonuses without bonuses from picked up artifact -class CHeroWithMaybePickedArtifact : public IBonusBearer +class CHeroWithMaybePickedArtifact : public virtual IBonusBearer { public: const CGHeroInstance *hero; @@ -47,6 +47,8 @@ public: CHeroWithMaybePickedArtifact(CWindowWithArtifacts *Cww, const CGHeroInstance *Hero); const TBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = nullptr, const std::string &cachingStr = "") const override; + + int64_t getTreeVersion() const override; }; class CHeroWindow: public CWindowObject, public CWindowWithGarrison, public CWindowWithArtifacts diff --git a/client/windows/CSpellWindow.cpp b/client/windows/CSpellWindow.cpp index 16d12037a..e3fb85f6d 100644 --- a/client/windows/CSpellWindow.cpp +++ b/client/windows/CSpellWindow.cpp @@ -34,14 +34,13 @@ #include "../../CCallback.h" -#include "../../lib/CStack.h" #include "../../lib/CConfigHandler.h" #include "../../lib/CGeneralTextHandler.h" -#include "../../lib/CHeroHandler.h" #include "../../lib/spells/CSpellHandler.h" +#include "../../lib/spells/Problem.h" #include "../../lib/GameConstants.h" -#include "../../lib/CGameState.h" -#include "../../lib/mapObjects/CGTownInstance.h" + +#include "../../lib/mapObjects/CGHeroInstance.h" CSpellWindow::InteractiveArea::InteractiveArea(const SDL_Rect & myRect, std::function funcL, int helpTextId, CSpellWindow * _owner) { @@ -125,7 +124,7 @@ CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _m ++sitesPerOurTab[4]; - spell->forEachSchool([&sitesPerOurTab](const SpellSchoolInfo & school, bool & stop) + spell->forEachSchool([&sitesPerOurTab](const spells::SchoolInfo & school, bool & stop) { ++sitesPerOurTab[(ui8)school.id]; }); @@ -536,101 +535,46 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState) owner->myInt->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[206]) % spellCost % owner->myHero->mana)); return; } - //battle spell on adv map or adventure map spell during combat => display infowindow, not cast - if((mySpell->isCombatSpell() && !owner->myInt->battleInt) - || (mySpell->isAdventureSpell() && (owner->myInt->battleInt || owner->myInt->castleInt))) + + //anything that is not combat spell is adventure spell + //this not an error in general to cast even creature ability with hero + const bool combatSpell = mySpell->isCombatSpell(); + if(mySpell->isCombatSpell() != !mySpell->isAdventureSpell()) { - std::vector hlp(1, new CComponent(CComponent::spell, mySpell->id, 0)); - owner->myInt->showInfoDialog(mySpell->getLevelInfo(schoolLevel).description, hlp); + logGlobal->error("Spell have invalid flags"); return; } - //we will cast a spell - if(mySpell->isCombatSpell() && owner->myInt->battleInt) //if battle window is open + const bool inCombat = owner->myInt->battleInt != nullptr; + const bool inCastle = owner->myInt->castleInt != nullptr; + + //battle spell on adv map or adventure map spell during combat => display infowindow, not cast + if((combatSpell ^ inCombat) || inCastle) { - ESpellCastProblem::ESpellCastProblem problem = mySpell->canBeCast(owner->myInt->cb.get(), ECastingMode::HERO_CASTING, owner->myHero); - switch (problem) + std::vector hlp(1, new CComponent(CComponent::spell, mySpell->id, 0)); + owner->myInt->showInfoDialog(mySpell->getLevelInfo(schoolLevel).description, hlp); + } + else if(combatSpell) + { + spells::detail::ProblemImpl problem; + if(mySpell->canBeCast(problem, owner->myInt->cb.get(), spells::Mode::HERO, owner->myHero)) { - case ESpellCastProblem::OK: - { - owner->myInt->battleInt->castThisSpell(mySpell->id); - owner->fexitb(); - return; - } - break; - case ESpellCastProblem::ANOTHER_ELEMENTAL_SUMMONED: - { - std::string text = CGI->generaltexth->allTexts[538], elemental, caster; - const PlayerColor player = owner->myInt->playerID; - - const TStacks stacks = owner->myInt->cb->battleGetStacksIf([player](const CStack * s) - { - return s->owner == player - && vstd::contains(s->state, EBattleStackState::SUMMONED) - && !s->isClone(); - }); - for(const CStack * s : stacks) - { - elemental = s->getCreature()->namePl; - } - if (owner->myHero->type->sex) - { //female - caster = CGI->generaltexth->allTexts[540]; - } - else - { //male - caster = CGI->generaltexth->allTexts[539]; - } - std::string summoner = owner->myHero->name; - - text = boost::str(boost::format(text) % summoner % elemental % caster); - - owner->myInt->showInfoDialog(text); - } - break; - case ESpellCastProblem::SPELL_LEVEL_LIMIT_EXCEEDED: - { - //Recanter's Cloak or similar effect. Try to retrieve bonus - const auto b = owner->myHero->getBonusLocalFirst(Selector::type(Bonus::BLOCK_MAGIC_ABOVE)); - //TODO what about other values and non-artifact sources? - if(b && b->val == 2 && b->source == Bonus::ARTIFACT) - { - std::string artName = CGI->arth->artifacts[b->sid]->Name(); - //The %s prevents %s from casting 3rd level or higher spells. - owner->myInt->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[536]) - % artName % owner->myHero->name)); - } - else if(b && b->source == Bonus::TERRAIN_OVERLAY && b->sid == BFieldType::CURSED_GROUND) - { - owner->myInt->showInfoDialog(CGI->generaltexth->allTexts[537]); - } - else - { - // General message: - // %s recites the incantations but they seem to have no effect. - std::string text = CGI->generaltexth->allTexts[541], caster = owner->myHero->name; - text = boost::str(boost::format(text) % caster); - owner->myInt->showInfoDialog(text); - } - } - break; - case ESpellCastProblem::NO_APPROPRIATE_TARGET: - { - owner->myInt->showInfoDialog(CGI->generaltexth->allTexts[185]); - } - break; - default: - { - // General message: - std::string text = CGI->generaltexth->allTexts[541], caster = owner->myHero->name; - text = boost::str(boost::format(text) % caster); - owner->myInt->showInfoDialog(text); - } + owner->myInt->battleInt->castThisSpell(mySpell->id); + owner->fexitb(); + } + else + { + std::vector texts; + problem.getAll(texts); + if(!texts.empty()) + owner->myInt->showInfoDialog(texts.front()); + else + owner->myInt->showInfoDialog("Unknown problem with this spell, no more information available."); } } - else if(mySpell->isAdventureSpell() && !owner->myInt->battleInt) //adventure spell and not in battle + else //adventure spell { - const CGHeroInstance *h = owner->myHero; + const CGHeroInstance * h = owner->myHero; GH.popInt(owner); auto guard = vstd::makeScopeGuard([this]() @@ -639,9 +583,9 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState) owner->myInt->spellbookSettings.spellbokLastPageAdvmap = owner->currentPage; }); - if(mySpell->getTargetType() == CSpell::LOCATION) + if(mySpell->getTargetType() == spells::AimType::LOCATION) adventureInt->enterCastingMode(mySpell); - else if(mySpell->getTargetType() == CSpell::NO_TARGET) + else if(mySpell->getTargetType() == spells::AimType::NO_TARGET) owner->myInt->cb->castSpell(h, mySpell->id); else logGlobal->error("Invalid spell target type"); @@ -654,7 +598,7 @@ void CSpellWindow::SpellArea::clickRight(tribool down, bool previousState) if(mySpell && down) { std::string dmgInfo; - int causedDmg = owner->myInt->cb->estimateSpellDamage(mySpell, owner->myHero); + auto causedDmg = owner->myInt->cb->estimateSpellDamage(mySpell, owner->myHero); if(causedDmg == 0 || mySpell->id == SpellID::TITANS_LIGHTNING_BOLT) //Titan's Lightning Bolt already has damage info included dmgInfo = ""; else diff --git a/client/windows/CreaturePurchaseCard.cpp b/client/windows/CreaturePurchaseCard.cpp index 91c4e580a..de34ff538 100644 --- a/client/windows/CreaturePurchaseCard.cpp +++ b/client/windows/CreaturePurchaseCard.cpp @@ -16,6 +16,7 @@ #include "../CreatureCostBox.h" #include "QuickRecruitmentWindow.h" #include "../gui/CGuiHandler.h" +#include "../../lib/CCreatureHandler.h" void CreaturePurchaseCard::initButtons() { diff --git a/client/windows/QuickRecruitmentWindow.cpp b/client/windows/QuickRecruitmentWindow.cpp index e9e82e50e..7553aa587 100644 --- a/client/windows/QuickRecruitmentWindow.cpp +++ b/client/windows/QuickRecruitmentWindow.cpp @@ -15,7 +15,8 @@ #include "../gui/CGuiHandler.h" #include "../../CCallback.h" #include "../CreatureCostBox.h" -#include "../lib/ResourceSet.h" +#include "../../lib/ResourceSet.h" +#include "../../lib/CCreatureHandler.h" #include "CreaturePurchaseCard.h" diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 2227dcf5b..3c76a383a 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -196,7 +196,7 @@ "type" : "object", "additionalProperties" : false, "default": {}, - "required" : [ "animationSpeed", "mouseShadow", "cellBorders", "stackRange", "showQueue" ], + "required" : [ "animationSpeed", "mouseShadow", "cellBorders", "stackRange", "showQueue", "queueSize" ], "properties" : { "animationSpeed" : { "type" : "number", @@ -217,6 +217,11 @@ "showQueue" : { "type" : "boolean", "default" : true + }, + "queueSize" : { + "type" : "string", + "default" : "auto", + "enum" : [ "auto", "small", "big" ] } } }, diff --git a/config/schemas/spell.json b/config/schemas/spell.json index e7f1061cb..daec57499 100644 --- a/config/schemas/spell.json +++ b/config/schemas/spell.json @@ -97,6 +97,12 @@ "$ref" : "vcmi:bonus" } }, + "battleEffects":{ + "type": "object", + "additionalProperties" : { + "type": "object" + } + }, "targetModifier":{ "type": "object", "additionalProperties": false, @@ -246,7 +252,10 @@ "$ref" : "#/definitions/flags", "description": "flags structure of bonus names, presence of all bonuses required to be affected by, can't be negated." }, - + "targetCondition":{ + "type": "object", + "additionalProperties" : true + }, "animation":{"$ref": "#/definitions/animation"}, "graphics":{ diff --git a/config/spells/ability.json b/config/spells/ability.json index 946f61544..cd8013947 100644 --- a/config/spells/ability.json +++ b/config/spells/ability.json @@ -16,7 +16,7 @@ "notActive" : { "val" : 0, "type" : "NOT_ACTIVE", - "subtype" : 62, + "subtype" : "spell.stoneGaze", "duration" : [ "UNTIL_BEING_ATTACKED", "N_TURNS" @@ -43,11 +43,13 @@ } } }, - "absoluteImmunity":{ - "SIEGE_WEAPON": true - }, "flags" : { "indifferent": true + }, + "targetCondition" : { + "noneOf" : { + "bonus.SIEGE_WEAPON" : "absolute" + } } }, "poison" : { @@ -80,15 +82,15 @@ } } }, - "absoluteImmunity":{ - "SIEGE_WEAPON": true - }, - "immunity" : { - "UNDEAD": true, - "NON_LIVING": true - }, "flags" : { "negative": true + }, + "targetCondition" : { + "noneOf" : { + "bonus.NON_LIVING" : "normal", + "bonus.SIEGE_WEAPON" : "absolute", + "bonus.UNDEAD" : "normal" + } } }, "bind" : { @@ -149,15 +151,15 @@ } } }, - "absoluteImmunity":{ - "SIEGE_WEAPON": true - }, - "immunity" : { - "UNDEAD": true, - "NON_LIVING": true - }, "flags" : { "negative": true + }, + "targetCondition" : { + "noneOf" : { + "bonus.NON_LIVING" : "normal", + "bonus.SIEGE_WEAPON" : "absolute", + "bonus.UNDEAD" : "normal" + } } }, "paralyze" : { @@ -178,7 +180,7 @@ "notActive" : { "val" : 0, "type" : "NOT_ACTIVE", - "subtype" : 74, + "subtype" : "spell.paralyze", "duration" : [ "UNTIL_BEING_ATTACKED", "N_TURNS" @@ -195,11 +197,13 @@ } } }, - "absoluteImmunity":{ - "SIEGE_WEAPON": true - }, "flags" : { "negative": true + }, + "targetCondition" : { + "noneOf" : { + "bonus.SIEGE_WEAPON" : "absolute" + } } }, "age" : { @@ -226,15 +230,15 @@ } } }, - "absoluteImmunity":{ - "SIEGE_WEAPON": true - }, - "immunity" : { - "UNDEAD": true, - "NON_LIVING": true - }, "flags" : { "negative": true + }, + "targetCondition" : { + "noneOf" : { + "bonus.NON_LIVING" : "normal", + "bonus.SIEGE_WEAPON" : "absolute", + "bonus.UNDEAD" : "normal" + } } }, "deathCloud" : { @@ -249,18 +253,23 @@ }, "levels" : { "base":{ - "range" : "0-1" + "range" : "0-1", + "battleEffects":{ + "damage":{ + "type":"core:damage" + } + } } }, - "absoluteImmunity":{ - "SIEGE_WEAPON": true - }, - "immunity" : { - "UNDEAD": true, - "NON_LIVING": true - }, "flags" : { "indifferent": true + }, + "targetCondition" : { + "noneOf" : { + "bonus.NON_LIVING" : "normal", + "bonus.SIEGE_WEAPON" : "absolute", + "bonus.UNDEAD" : "normal" + } } }, "thunderbolt" : { @@ -275,12 +284,16 @@ }, "levels" : { "base":{ - "range" : "0" + "range" : "0", + "battleEffects":{ + "damage":{ + "type":"core:damage" + } + } } }, "flags" : { "damage": true, - "offensive": true, "negative": true } }, @@ -296,6 +309,15 @@ }, "levels" : { "base":{ + "battleEffects":{ + "dispel":{ + "type":"core:dispel", + "ignoreImmunity" : true, + "dispelNegative":false, + "dispelNeutral":false, + "dispelPositive":true + } + }, "range" : "0" } }, @@ -316,41 +338,50 @@ }, "levels" : { "base":{ + "battleEffects":{ + "destruction":{ + "type":"core:damage", + "killByCount": true + } + }, "range" : "0" } }, - "absoluteImmunity" : { - "UNDEAD": true, - "SIEGE_WEAPON": true, - "NON_LIVING": true - }, "flags" : { "indifferent": true + }, + "targetCondition" : { + "noneOf" : { + "bonus.NON_LIVING" : "absolute", + "bonus.SIEGE_WEAPON" : "absolute", + "bonus.UNDEAD" : "absolute" + } } }, "acidBreath" : { "index" : 80, "targetType": "NO_TARGET", - "animation":{ - //??? - }, - "sounds": { - "cast": "ACID" - }, "levels" : { "base":{ - "range" : "0", - "targetModifier":{"smart":true}, - "cumulativeEffects" : { - "primarySkill" : { - "val" : -3, - "type" : "PRIMARY_SKILL", - "subtype" : "primSkill.defence", - "duration" : "PERMANENT", - "valueType" : "ADDITIVE_VALUE" + "battleEffects":{ + "timed":{ + "type":"core:timed", + "cumulative":true, + "ignoreImmunity" : true, + "bonus" :{ + "primarySkill" : { + "val" : -3, + "type" : "PRIMARY_SKILL", + "subtype" : "primSkill.defence", + "duration" : "PERMANENT", + "valueType" : "ADDITIVE_VALUE" + } + } } - } + }, + "range" : "0", + "targetModifier":{"smart":true} } }, "flags" : { @@ -369,6 +400,12 @@ }, "levels" : { "base":{ + "battleEffects":{ + "damage":{ + "type":"core:damage", + "ignoreImmunity" : true + } + }, "range" : "0" } }, @@ -376,8 +413,10 @@ "damage": true, "indifferent": true }, - "immunity" : { - "DIRECT_DAMAGE_IMMUNITY": true + "targetCondition" : { + "noneOf" : { + "bonus.DIRECT_DAMAGE_IMMUNITY" : "normal" + } } } } diff --git a/config/spells/offensive.json b/config/spells/offensive.json index 09fe6c6d8..a67247413 100644 --- a/config/spells/offensive.json +++ b/config/spells/offensive.json @@ -2,7 +2,7 @@ "magicArrow" : { "index" : 15, "targetType": "CREATURE", - + "animation":{ "projectile": [ {"minimumAngle": 0 ,"defName":"C20SPX4"}, @@ -19,22 +19,26 @@ "levels" : { "base":{ "range" : "0", + "battleEffects" : { + "directDamage" : {"type":"core:damage"} + }, "targetModifier":{"smart":true} } }, "flags" : { "damage": true, - "offensive": true, "negative": true }, - "immunity" : { - "DIRECT_DAMAGE_IMMUNITY": true + "targetCondition" : { + "noneOf" : { + "bonus.DIRECT_DAMAGE_IMMUNITY" : "normal" + } } }, "iceBolt" : { "index" : 16, "targetType": "CREATURE", - + "animation":{ "projectile": [ {"minimumAngle": 0 ,"defName":"C08SPW4"}, @@ -44,54 +48,62 @@ {"minimumAngle": 1.50 ,"defName":"C08SPW0"} ], "hit":["C08SPW5"] - }, + }, "sounds": { "cast": "ICERAY" }, "levels" : { "base":{ "range" : "0", + "battleEffects" : { + "directDamage" : {"type":"core:damage"} + }, "targetModifier":{"smart":true} } }, "flags" : { "damage": true, - "offensive": true, "negative": true }, - "immunity" : { - "DIRECT_DAMAGE_IMMUNITY": true + "targetCondition" : { + "noneOf" : { + "bonus.DIRECT_DAMAGE_IMMUNITY" : "normal" + } } }, "lightningBolt" : { "index" : 17, "targetType": "CREATURE", - + "animation":{ "affect":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"] - }, + }, "sounds": { "cast": "LIGHTBLT" }, "levels" : { "base":{ "range" : "0", + "battleEffects" : { + "directDamage" : {"type":"core:damage"} + }, "targetModifier":{"smart":true} } }, "flags" : { "damage": true, - "offensive": true, "negative": true }, - "immunity" : { - "DIRECT_DAMAGE_IMMUNITY": true + "targetCondition" : { + "noneOf" : { + "bonus.DIRECT_DAMAGE_IMMUNITY" : "normal" + } } }, "implosion" : { "index" : 18, "targetType": "CREATURE", - + "animation":{ "affect":["C05SPE0"] }, @@ -101,245 +113,294 @@ "levels" : { "base":{ "range" : "0", + "battleEffects" : { + "directDamage" : {"type":"core:damage"} + }, "targetModifier":{"smart":true} } }, - "flags" : { "damage": true, - "offensive": true, "negative": true }, - "absoluteImmunity" : { - "SIEGE_WEAPON": true - }, - "immunity" : { - "DIRECT_DAMAGE_IMMUNITY": true + "targetCondition" : { + "noneOf" : { + "bonus.DIRECT_DAMAGE_IMMUNITY" : "normal", + "bonus.SIEGE_WEAPON" : "absolute" + } } }, "chainLightning" : { "index" : 19, "targetType": "CREATURE", - + "animation":{ "affect":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"] - }, + }, "sounds": { "cast": "CHAINLTE" }, "levels" : { "base":{ "range" : "0", + "battleEffects" : { + "directDamage" : { + "type" : "core:damage", + "chainFactor" : 0.5, + "chainLength" : 4 + } + }, "targetModifier":{"smart":true} + }, + "advanced" : { + "battleEffects" : { + "directDamage" : { + "chainLength" : 5 + } + } + }, + "expert" : { + "battleEffects" : { + "directDamage" : { + "chainLength" : 5 + } + } } }, "flags" : { "damage": true, - "offensive": true, "negative": true } }, "frostRing" : { "index" : 20, "targetType": "LOCATION", - + "animation":{ "hit":["C07SPW"] //C07SPW0 ??? - }, + }, "sounds": { "cast": "FROSTING" }, "levels" : { "base":{ "range" : "1", + "battleEffects" : { + "directDamage" : {"type":"core:damage"} + }, "targetModifier":{"smart":false} } }, "flags" : { "damage": true, - "offensive": true, "negative": true }, - "immunity" : { - "DIRECT_DAMAGE_IMMUNITY": true + "targetCondition" : { + "noneOf" : { + "bonus.DIRECT_DAMAGE_IMMUNITY" : "normal" + } } }, "fireball" : { "index" : 21, "targetType": "LOCATION", - + "animation":{ "hit":["C13SPF"] //C13SPF0 ??? - }, + }, "sounds": { "cast": "SPONTCOMB" }, "levels" : { "base":{ "range" : "0,1", + "battleEffects" : { + "directDamage" : {"type":"core:damage"} + }, "targetModifier":{"smart":false} } }, "flags" : { "damage": true, - "offensive": true, "negative": true }, - "immunity" : { - "DIRECT_DAMAGE_IMMUNITY": true + "targetCondition" : { + "noneOf" : { + "bonus.DIRECT_DAMAGE_IMMUNITY" : "normal" + } } }, "inferno" : { "index" : 22, "targetType": "LOCATION", - + "animation":{ "hit":["C04SPF0"] - }, + }, "sounds": { "cast": "FIREBLST" }, "levels" : { "base":{ "range" : "0-2", + "battleEffects" : { + "directDamage" : {"type":"core:damage"} + }, "targetModifier":{"smart":false} } }, "flags" : { "damage": true, - "offensive": true, "negative": true }, - "immunity" : { - "DIRECT_DAMAGE_IMMUNITY": true + "targetCondition" : { + "noneOf" : { + "bonus.DIRECT_DAMAGE_IMMUNITY" : "normal" + } } }, "meteorShower" : { "index" : 23, "targetType": "LOCATION", - + "animation":{ "hit":["C08SPE0"] - }, + }, "sounds": { "cast": "METEOR" }, "levels" : { "base":{ "range" : "0,1", + "battleEffects" : { + "directDamage" : {"type":"core:damage"} + }, "targetModifier":{"smart":false} } }, "flags" : { "damage": true, - "offensive": true, "negative": true }, - "immunity" : { - "DIRECT_DAMAGE_IMMUNITY": true + "targetCondition" : { + "noneOf" : { + "bonus.DIRECT_DAMAGE_IMMUNITY" : "normal" + } } }, "deathRipple" : { "index" : 24, "targetType" : "CREATURE", - + "animation":{ "affect":["C04SPE0"] - }, + }, "sounds": { "cast": "DEATHRIP" }, "levels" : { "base":{ "range" : "X", + "battleEffects" : { + "directDamage" : {"type":"core:damage"} + }, "targetModifier":{"smart":false} } }, "flags" : { "damage": true, - "offensive": true, "negative": true }, - "absoluteImmunity":{ - "SIEGE_WEAPON": true, - "UNDEAD": true, - }, - "immunity" : { - "DIRECT_DAMAGE_IMMUNITY": true + "targetCondition" : { + "noneOf" : { + "bonus.DIRECT_DAMAGE_IMMUNITY" : "normal", + "bonus.SIEGE_WEAPON" : "absolute", + "bonus.UNDEAD" : "absolute" + } } }, "destroyUndead" : { "index" : 25, "targetType" : "CREATURE", - + "animation":{ "affect":["C14SPA0"] - }, + }, "sounds": { "cast": "SACBRETH" }, "levels" : { "base":{ "range" : "X", + "battleEffects" : { + "directDamage" : {"type":"core:damage"} + }, "targetModifier":{"smart":false} } }, "flags" : { "damage": true, - "offensive": true, "negative": true }, - "absoluteLimit" : { - "UNDEAD": true - }, - "immunity" : { - "DIRECT_DAMAGE_IMMUNITY": true + "targetCondition" : { + "allOf" : { + "bonus.UNDEAD" : "absolute" + }, + "noneOf" : { + "bonus.DIRECT_DAMAGE_IMMUNITY" : "normal" + } } }, "armageddon" : { "index" : 26, "targetType" : "CREATURE", - + "animation":{ "hit":["C06SPF0"] - }, + }, "sounds": { "cast": "ARMGEDN" }, "levels" : { "base":{ "range" : "X", + "battleEffects" : { + "directDamage" : {"type":"core:damage"} + }, "targetModifier":{"smart":false} } }, "flags" : { "damage": true, - "offensive": true, "negative": true }, - "immunity" : { - "DIRECT_DAMAGE_IMMUNITY": true + "targetCondition" : { + "noneOf" : { + "bonus.DIRECT_DAMAGE_IMMUNITY" : "normal" + } } }, "titanBolt" : { "index" : 57, "targetType" : "CREATURE", - + "animation":{ "hit":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"] - }, + }, "sounds": { "cast": "LIGHTBLT" }, "levels" : { "base":{ "range" : "0", + "battleEffects" : { + "directDamage" : {"type":"core:damage"} + }, "targetModifier":{"smart":true} } }, "flags" : { "damage": true, - "offensive": true, "negative": true, "special": true } diff --git a/config/spells/other.json b/config/spells/other.json index 9ff82216f..9c2e93a8d 100644 --- a/config/spells/other.json +++ b/config/spells/other.json @@ -8,7 +8,42 @@ }, "levels" : { "base":{ - "range" : "X" + "range" : "X", + "battleEffects" : { + "obstacle" : { + "type":"core:obstacle", + "hidden" : true, + "passable" : true, + "trap" : true, + "trigger" : false, + "patchCount" : 4, + "turnsRemaining" : -1, + "attacker" :{ + "animation" : "C17SPE1", + "appearAnimation" : "C17SPE0", + "offsetY" : -42 + }, + "defender" :{ + "animation" : "C17SPE1", + "appearAnimation" : "C17SPE0", + "offsetY" : -42 + } + } + } + }, + "advanced":{ + "battleEffects":{ + "obstacle":{ + "patchCount" : 6 + } + } + }, + "expert":{ + "battleEffects":{ + "obstacle":{ + "patchCount" : 8 + } + } } }, "flags" : { @@ -24,15 +59,57 @@ }, "levels" : { "base":{ - "range" : "X" + "range" : "X", + "battleEffects" : { + "obstacle" : { + "type":"core:obstacle", + "hidden" : true, + "passable" : true, + "trap" : false, + "trigger" : true, + "removeOnTrigger" : true, + "patchCount" : 4, + "turnsRemaining" : -1, + "attacker" :{ + "animation" : "C09SPF1", + "appearAnimation" : "C09SPF0" + }, + "defender" :{ + "animation" : "C09SPF1", + "appearAnimation" : "C09SPF0" + } + }, + "damage":{ + "type":"core:damage", + "optional":false, + "indirect":true, + "customEffectId" : 82 + } + } + }, + "advanced":{ + "battleEffects":{ + "obstacle":{ + "patchCount" : 6 + } + } + }, + "expert":{ + "battleEffects":{ + "obstacle":{ + "patchCount" : 8 + } + } } }, "flags" : { "damage": true, "indifferent": true }, - "immunity" : { - "DIRECT_DAMAGE_IMMUNITY": true + "targetCondition" : { + "noneOf" : { + "bonus.DIRECT_DAMAGE_IMMUNITY" : "normal" + } } }, "forceField" : { @@ -47,6 +124,61 @@ "range" : "0", "targetModifier":{ "clearAffected": true + }, + "battleEffects":{ + "obstacle":{ + "type":"core:obstacle", + "hidden" : false, + "passable" : false, + "trap" : false, + "trigger" : false, + "patchCount" : 1, + "turnsRemaining" : 2, + "attacker" :{ + "range" : [[""]], + "shape" : [[""], ["TR"]], + "animation" : "C15SPE1", + "appearAnimation" : "C15SPE0" + }, + "defender" :{ + "range" : [[""]], + "shape" : [[""], ["TL"]], + "animation" : "C15SPE4", + "appearAnimation" : "C15SPE0" + } + } + } + }, + "advanced":{ + "battleEffects":{ + "obstacle":{ + "attacker" :{ + "shape" : [[""], ["TR"], ["TR", "TL"]], + "animation" : "C15SPE10", + "appearAnimation" : "C15SPE9" + }, + "defender" :{ + "shape" : [[""], ["TL"], ["TL", "TR"]], + "animation" : "C15SPE7", + "appearAnimation" : "C15SPE6" + } + } + } + }, + "expert":{ + "battleEffects":{ + "obstacle":{ + "attacker" :{ + "shape" : [[""], ["TR"], ["TR", "TL"]], + "animation" : "C15SPE10", + "appearAnimation" : "C15SPE9" + }, + "defender" :{ + "shape" : [[""], ["TL"], ["TL", "TR"]], + "animation" : "C15SPE7", + "appearAnimation" : "C15SPE6" + } + } } } }, @@ -66,6 +198,58 @@ "range" : "0", "targetModifier":{ "clearAffected": true + }, + "battleEffects":{ + "obstacle":{ + "type":"core:obstacle", + "hidden" : false, + "passable" : true, + "trap" : false, + "trigger" : true, + "patchCount" : 1, + "turnsRemaining" : 2, + "attacker" :{ + "shape" : [[""]], + "range" : [[""], ["TR"]], + "animation" : "C07SPF61", + "appearAnimation" : "C07SPF60" + }, + "defender" :{ + "shape" : [[""]], + "range" : [[""], ["TL"]], + "animation" : "C07SPF61", + "appearAnimation" : "C07SPF60" + } + }, + "damage":{ + "type":"core:damage", + "optional":false, + "indirect":true + } + } + }, + "advanced":{ + "battleEffects":{ + "obstacle":{ + "attacker" :{ + "range" : [[""], ["TR"], ["TR", "TL"]] + }, + "defender" :{ + "range" : [[""], ["TL"], ["TL", "TR"]] + } + } + } + }, + "expert":{ + "battleEffects":{ + "obstacle":{ + "attacker" :{ + "range" : [[""], ["TR"], ["TR", "TL"]] + }, + "defender" :{ + "range" : [[""], ["TL"], ["TL", "TR"]] + } + } } } }, @@ -73,8 +257,10 @@ "damage": true, "indifferent": true }, - "immunity" : { - "DIRECT_DAMAGE_IMMUNITY": true + "targetCondition" : { + "noneOf" : { + "bonus.DIRECT_DAMAGE_IMMUNITY" : "normal" + } } }, "earthquake" : { @@ -87,7 +273,27 @@ "levels" : { "base":{ "targetModifier":{"smart":true}, + "battleEffects":{ + "catapult":{ + "type":"core:catapult", + "targetsToAttack": 2 + } + }, "range" : "X" + }, + "advanced":{ + "battleEffects":{ + "catapult":{ + "targetsToAttack": 3 + } + } + }, + "expert":{ + "battleEffects":{ + "catapult":{ + "targetsToAttack": 4 + } + } } }, "flags" : { @@ -107,7 +313,19 @@ }, "levels" : { "base":{ - "targetModifier":{"smart":true}, + "targetModifier":{ + "smart":true + }, + "battleEffects":{ + "dispel":{ + "type":"core:dispel", + "optional":false, + "ignoreImmunity" : true, + "dispelNegative":true, + "dispelNeutral":true, + "dispelPositive":true + } + }, "range" : "0" }, "advanced":{ @@ -115,6 +333,16 @@ }, "expert":{ "targetModifier":{"smart":false}, + "battleEffects":{ + "dispel":{ + "optional":true + }, + "removeObstacle":{ + "optional":true, + "type":"core:removeObstacle", + "removeAllSpells" : true + } + }, "range" : "X" } }, @@ -135,6 +363,21 @@ "levels" : { "base":{ "targetModifier":{"smart":true}, + "battleEffects":{ + "heal":{ + "type":"core:heal", + "healLevel":"heal", + "healPower":"permanent", + "optional":true + }, + "cure":{ + "type":"core:dispel", + "optional":true, + "dispelNegative":true, + "dispelNeutral":false, + "dispelPositive":false + } + }, "range" : "0" }, "expert":{ @@ -158,17 +401,49 @@ "levels" : { "base":{ "range" : "0", + "battleEffects":{ + "heal":{ + "type":"core:heal", + "healLevel":"resurrect", + "healPower":"oneBattle", + "minFullUnits" : 1 + }, + "cure":{ + "type":"core:dispel", + "indirect": true, + "optional":true, + "dispelNegative":true, + "dispelNeutral":false, + "dispelPositive":false + } + }, "targetModifier":{"smart":true} + }, + "advanced":{ + "battleEffects":{ + "heal":{ + "healPower":"permanent" + } + } + }, + "expert":{ + "battleEffects":{ + "heal":{ + "healPower":"permanent" + } + } } }, "flags" : { "rising": true, "positive": true }, - "absoluteImmunity" : { - "UNDEAD": true, - "SIEGE_WEAPON": true, - "NON_LIVING": true + "targetCondition" : { + "noneOf" : { + "bonus.NON_LIVING" : "absolute", + "bonus.SIEGE_WEAPON" : "absolute", + "bonus.UNDEAD" : "absolute" + } } }, "animateDead" : { @@ -184,6 +459,14 @@ "levels" : { "base":{ "range" : "0", + "battleEffects":{ + "heal":{ + "type":"core:heal", + "healLevel":"resurrect", + "healPower":"permanent", + "minFullUnits" : 1 + } + }, "targetModifier":{"smart":true} } }, @@ -191,8 +474,10 @@ "rising": true, "positive": true }, - "absoluteLimit" : { - "UNDEAD": true + "targetCondition" : { + "allOf" : { + "bonus.UNDEAD" : "absolute" + } } }, "sacrifice" : { @@ -208,6 +493,14 @@ "levels" : { "base":{ "range" : "0", + "battleEffects":{ + "sacrifice":{ + "type":"core:sacrifice", + "healLevel":"resurrect", + "healPower":"permanent", + "minFullUnits" : 0 + } + }, "targetModifier":{"smart":true} } }, @@ -215,10 +508,12 @@ "rising": true, "positive": true }, - "absoluteImmunity" : { - "SIEGE_WEAPON": true, - "UNDEAD": true, - "NON_LIVING": true + "targetCondition" : { + "noneOf" : { + "bonus.NON_LIVING" : "absolute", + "bonus.SIEGE_WEAPON" : "absolute", + "bonus.UNDEAD" : "absolute" + } } }, "teleport" : { @@ -231,14 +526,21 @@ "levels" : { "base":{ "range" : "0", + "battleEffects":{ + "teleport":{ + "type":"core:teleport" + } + }, "targetModifier":{"smart":true} } }, - "absoluteImmunity" : { - "SIEGE_WEAPON": true - }, "flags" : { "positive": true + }, + "targetCondition" : { + "noneOf" : { + "bonus.SIEGE_WEAPON" : "absolute" + } } }, "removeObstacle" : { @@ -252,7 +554,28 @@ }, "levels" : { "base":{ - "range" : "0" + "range" : "0", + "battleEffects": { + "removeObstacle" : { + "optional":false, + "type":"core:removeObstacle", + "removeUsual" : true + } + } + }, + "advanced" : { + "battleEffects": { + "removeObstacle" : { + "removeSpells" : ["fireWall"] + } + } + }, + "expert" : { + "battleEffects": { + "removeObstacle" : { + "removeAllSpells" : true + } + } } }, "flags" : { @@ -271,14 +594,36 @@ "levels" : { "base":{ "range" : "0", + "battleEffects":{ + "clone":{ + "maxTier":5, + "type":"core:clone" + } + }, "targetModifier":{"smart":true} + }, + "advanced":{ + "battleEffects":{ + "clone":{ + "maxTier":6 + } + } + }, + "expert":{ + "battleEffects":{ + "clone":{ + "maxTier":1000 + } + } } }, - "absoluteImmunity" : { - "SIEGE_WEAPON": true - }, "flags" : { "positive": true + }, + "targetCondition" : { + "noneOf" : { + "bonus.SIEGE_WEAPON" : "absolute" + } } }, "fireElemental" : { @@ -292,7 +637,15 @@ }, "levels" : { "base":{ - "range" : "X" + "range" : "X", + "battleEffects":{ + "summon":{ + "exclusive":true, + "id":"fireElemental", + "permanent":false, + "type":"core:summon" + } + } } }, "flags" : { @@ -310,7 +663,15 @@ }, "levels" : { "base":{ - "range" : "X" + "range" : "X", + "battleEffects":{ + "summon":{ + "exclusive":true, + "id":"earthElemental", + "permanent":false, + "type":"core:summon" + } + } } }, "flags" : { @@ -328,7 +689,15 @@ }, "levels" : { "base":{ - "range" : "X" + "range" : "X", + "battleEffects":{ + "summon":{ + "exclusive":true, + "id":"waterElemental", + "permanent":false, + "type":"core:summon" + } + } } }, "flags" : { @@ -346,7 +715,15 @@ }, "levels" : { "base":{ - "range" : "X" + "range" : "X", + "battleEffects":{ + "summon":{ + "exclusive":true, + "id":"airElemental", + "permanent":false, + "type":"core:summon" + } + } } }, "flags" : { diff --git a/config/spells/timed.json b/config/spells/timed.json index 84bd3b472..63d4977b4 100644 --- a/config/spells/timed.json +++ b/config/spells/timed.json @@ -108,11 +108,24 @@ "spellDamageReduction" : { "type" : "SPELL_DAMAGE_REDUCTION", "subtype" : 0, - "duration" : "N_TURNS" + "duration" : "N_TURNS", + "val" : 30 + } + } + }, + "advanced" : { + "effects" : { + "spellDamageReduction" : { + "val" : 50 } } }, "expert":{ + "effects" : { + "spellDamageReduction" : { + "val" : 50 + } + }, "range" : "X" } }, @@ -138,11 +151,24 @@ "spellDamageReduction" : { "type" : "SPELL_DAMAGE_REDUCTION", "subtype" : 1, - "duration" : "N_TURNS" + "duration" : "N_TURNS", + "val" : 30 + } + } + }, + "advanced" : { + "effects" : { + "spellDamageReduction" : { + "val" : 50 } } }, "expert":{ + "effects" : { + "spellDamageReduction" : { + "val" : 50 + } + }, "range" : "X" } }, @@ -168,11 +194,24 @@ "spellDamageReduction" : { "type" : "SPELL_DAMAGE_REDUCTION", "subtype" : 2, - "duration" : "N_TURNS" + "duration" : "N_TURNS", + "val" : 30 + } + } + }, + "advanced" : { + "effects" : { + "spellDamageReduction" : { + "val" : 50 } } }, "expert":{ + "effects" : { + "spellDamageReduction" : { + "val" : 50 + } + }, "range" : "X" } }, @@ -198,11 +237,24 @@ "spellDamageReduction" : { "type" : "SPELL_DAMAGE_REDUCTION", "subtype" : 3, - "duration" : "N_TURNS" + "duration" : "N_TURNS", + "val" : 30 + } + } + }, + "advanced" : { + "effects" : { + "spellDamageReduction" : { + "val" : 50 } } }, "expert":{ + "effects" : { + "spellDamageReduction" : { + "val" : 50 + } + }, "range" : "X" } }, @@ -224,26 +276,47 @@ "base":{ "range" : "0", "targetModifier":{"smart":true}, - "effects" : { - "levelSpellImmunity" : { - "val" : 3, - "type" : "LEVEL_SPELL_IMMUNITY", - "valueType" : "INDEPENDENT_MAX", - "duration" : "N_TURNS" + "battleEffects":{ + "spellImmunity":{ + "type":"core:timed", + "bonus":{ + "levelSpellImmunity":{ + "addInfo" : 1, //absolute + "val" : 3, + "type" : "LEVEL_SPELL_IMMUNITY", + "valueType" : "INDEPENDENT_MAX", + "duration" : "N_TURNS" + } + } + }, + "dispel":{ + "type":"core:dispel", + "optional":true, + "dispelNegative":true, + "dispelNeutral":true, + "dispelPositive":false } } }, "advanced":{ - "effects" : { - "levelSpellImmunity" : { - "val" : 4 + "battleEffects":{ + "spellImmunity":{ + "bonus" :{ + "levelSpellImmunity":{ + "val" : 4 + } + } } } }, "expert":{ - "effects" : { - "levelSpellImmunity" : { - "val" : 5 + "battleEffects":{ + "spellImmunity":{ + "bonus":{ + "levelSpellImmunity":{ + "val" : 5 + } + } } } } @@ -310,12 +383,14 @@ "counters" : { "spell.curse": true }, - "absoluteImmunity" : { - "SIEGE_WEAPON": true, - "UNDEAD": true - }, "flags" : { "positive": true + }, + "targetCondition" : { + "noneOf" : { + "bonus.SIEGE_WEAPON" : "absolute", + "bonus.UNDEAD" : "absolute" + } } }, "curse" : { @@ -350,12 +425,14 @@ "counters" : { "spell.bless": true }, - "absoluteImmunity" : { - "SIEGE_WEAPON": true, - "UNDEAD": true - }, "flags" : { "negative": true + }, + "targetCondition" : { + "noneOf" : { + "bonus.SIEGE_WEAPON" : "absolute", + "bonus.UNDEAD" : "absolute" + } } }, "bloodlust" : { @@ -401,11 +478,13 @@ "counters" : { "spell.weakness": true }, - "absoluteImmunity" : { - "SIEGE_WEAPON": true - }, "flags" : { "positive": true + }, + "targetCondition" : { + "noneOf" : { + "bonus.SIEGE_WEAPON" : "absolute" + } } }, "precision" : { @@ -448,11 +527,13 @@ } } }, - "absoluteLimit" : { - "SHOOTER": true - }, "flags" : { "positive": true + }, + "targetCondition" : { + "allOf" : { + "bonus.SHOOTER" : "absolute" + } } }, "weakness" : { @@ -697,16 +778,16 @@ "counters" : { "spell.sorrow":true }, - "absoluteImmunity":{ - "SIEGE_WEAPON": true, - "UNDEAD": true, - }, - "immunity" : { - "MIND_IMMUNITY": true, - "NON_LIVING": true - }, "flags" : { "positive": true + }, + "targetCondition" : { + "noneOf" : { + "bonus.MIND_IMMUNITY" : "normal", + "bonus.NON_LIVING" : "normal", + "bonus.SIEGE_WEAPON" : "absolute", + "bonus.UNDEAD" : "absolute" + } } }, "sorrow" : { @@ -750,16 +831,16 @@ "counters" : { "spell.mirth":true }, - "absoluteImmunity":{ - "SIEGE_WEAPON": true, - "UNDEAD": true, - }, - "immunity" : { - "MIND_IMMUNITY": true, - "NON_LIVING": true - }, "flags" : { "negative": true + }, + "targetCondition" : { + "noneOf" : { + "bonus.MIND_IMMUNITY" : "normal", + "bonus.NON_LIVING" : "normal", + "bonus.SIEGE_WEAPON" : "absolute", + "bonus.UNDEAD" : "absolute" + } } }, "fortune" : { @@ -894,11 +975,13 @@ "counters" : { "spell.slow": true }, - "absoluteImmunity" : { - "SIEGE_WEAPON": true - }, "flags" : { "positive": true + }, + "targetCondition" : { + "noneOf" : { + "bonus.SIEGE_WEAPON" : "absolute" + } } }, "slow" : { @@ -941,15 +1024,16 @@ } } }, - "counters" : { "spell.haste":true }, - "absoluteImmunity" : { - "SIEGE_WEAPON": true - }, "flags" : { "negative": true + }, + "targetCondition" : { + "noneOf" : { + "bonus.SIEGE_WEAPON" : "absolute" + } } }, "slayer" : { @@ -1024,8 +1108,7 @@ "inFrenzy" : { "type" : "IN_FRENZY", "val" : 100, - "duration" : "N_TURNS", - "turns" : 1 + "duration" : "UNTIL_ATTACK" } } }, @@ -1044,11 +1127,13 @@ } } }, - "absoluteImmunity" : { - "SIEGE_WEAPON": true - }, "flags" : { "positive": true + }, + "targetCondition" : { + "noneOf" : { + "bonus.SIEGE_WEAPON" : "absolute" + } } }, "counterstrike" : { @@ -1089,11 +1174,13 @@ } } }, - "absoluteImmunity" : { - "SIEGE_WEAPON": true - }, "flags" : { "positive": true + }, + "targetCondition" : { + "noneOf" : { + "bonus.SIEGE_WEAPON" : "absolute" + } } }, "berserk" : { @@ -1127,16 +1214,16 @@ "counters" : { "spell.hypnotize": true }, - "absoluteImmunity" : { - "SIEGE_WEAPON": true - }, - "immunity" : { - "MIND_IMMUNITY": true, - "UNDEAD": true, - "NON_LIVING": true - }, "flags" : { "negative": true + }, + "targetCondition" : { + "noneOf" : { + "bonus.MIND_IMMUNITY" : "normal", + "bonus.NON_LIVING" : "normal", + "bonus.SIEGE_WEAPON" : "absolute", + "bonus.UNDEAD" : "normal" + } } }, "hypnotize" : { @@ -1192,13 +1279,16 @@ "counters" : { "spell.berserk": true }, - "absoluteImmunity" : { - "SIEGE_WEAPON": true - }, - "immunity" : { - "MIND_IMMUNITY": true, - "UNDEAD": true, - "NON_LIVING": true + "targetCondition" : { + "allOf" : { + "healthValueSpecial" : "absolute" + }, + "noneOf" : { + "bonus.SIEGE_WEAPON":"absolute", + "bonus.MIND_IMMUNITY":"normal", + "bonus.UNDEAD":"normal", + "bonus.NON_LIVING":"normal" + } }, "flags" : { "negative": true @@ -1255,19 +1345,19 @@ } } }, - "absoluteLimit" : { - "SHOOTER": true - }, - "absoluteImmunity" : { - "SIEGE_WEAPON": true - }, - "immunity" : { - "MIND_IMMUNITY": true, - "UNDEAD": true, - "NON_LIVING": true - }, "flags" : { "negative": true + }, + "targetCondition" : { + "allOf" : { + "bonus.SHOOTER" : "absolute" + }, + "noneOf" : { + "bonus.MIND_IMMUNITY" : "normal", + "bonus.NON_LIVING" : "normal", + "bonus.SIEGE_WEAPON" : "absolute", + "bonus.UNDEAD" : "normal" + } } }, "blind" : { @@ -1316,16 +1406,17 @@ } } }, - "absoluteImmunity" : { - "SIEGE_WEAPON": true, - "UNDEAD": true - }, - "immunity" : { - "MIND_IMMUNITY": true, - "NON_LIVING": true - }, "flags" : { "negative": true + }, + "targetCondition" : { + "noneOf" : { + "bonus.MIND_IMMUNITY" : "normal", + "bonus.NON_LIVING" : "normal", + "bonus.SIEGE_WEAPON" : "absolute", + "bonus.UNDEAD" : "absolute" + } } + } } diff --git a/include/vstd/RNG.h b/include/vstd/RNG.h new file mode 100644 index 000000000..5535b13e9 --- /dev/null +++ b/include/vstd/RNG.h @@ -0,0 +1,58 @@ +/* + * RNG.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +namespace vstd +{ + +typedef std::function TRandI64; +typedef std::function TRand; + +class DLL_LINKAGE RNG +{ +public: + + virtual ~RNG() = default; + + virtual TRandI64 getInt64Range(int64_t lower, int64_t upper) = 0; + + virtual TRand getDoubleRange(double lower, double upper) = 0; +}; + +} + +namespace RandomGeneratorUtil +{ + template + auto nextItem(const Container & container, vstd::RNG & rand) -> decltype(std::begin(container)) + { + assert(!container.empty()); + return std::next(container.begin(), rand.getInt64Range(0, container.size() - 1)()); + } + + template + auto nextItem(Container & container, vstd::RNG & rand) -> decltype(std::begin(container)) + { + assert(!container.empty()); + return std::next(container.begin(), rand.getInt64Range(0, container.size() - 1)()); + } + + template + void randomShuffle(std::vector & container, vstd::RNG & rand) + { + int64_t n = (container.end() - container.begin()); + + for(int64_t i = n-1; i>0; --i) + { + std::swap(container.begin()[i],container.begin()[rand.getInt64Range(0, i)()]); + } + } +} diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index 563aadf9d..40754d0ee 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -728,20 +728,6 @@ void CArtHandler::afterLoadFinalization() CBonusSystemNode::treeHasChanged(); } -si32 CArtHandler::decodeArfifact(const std::string& identifier) -{ - auto rawId = VLC->modh->identifiers.getIdentifier("core", "artifact", identifier); - if(rawId) - return rawId.get(); - else - return -1; -} - -std::string CArtHandler::encodeArtifact(const si32 index) -{ - return VLC->arth->artifacts[index]->identifier; -} - CArtifactInstance::CArtifactInstance() { init(); @@ -1407,7 +1393,7 @@ void CArtifactSet::serializeJsonHero(JsonSerializeFormat & handler, CMap * map) for(const ArtSlotInfo & info : artifactsInBackpack) backpackTemp.push_back(info.artifact->artType->id); } - handler.serializeIdArray(NArtifactPosition::backpack, backpackTemp, &CArtHandler::decodeArfifact, &CArtHandler::encodeArtifact); + handler.serializeIdArray(NArtifactPosition::backpack, backpackTemp); if(!handler.saving) { for(const ArtifactID & artifactID : backpackTemp) @@ -1441,12 +1427,12 @@ void CArtifactSet::serializeJsonSlot(JsonSerializeFormat & handler, const Artifa if(info != nullptr && !info->locked) { artifactID = info->artifact->artType->id; - handler.serializeId(NArtifactPosition::namesHero[slot.num], artifactID, ArtifactID::NONE, &CArtHandler::decodeArfifact, &CArtHandler::encodeArtifact); + handler.serializeId(NArtifactPosition::namesHero[slot.num], artifactID, ArtifactID::NONE); } } else { - handler.serializeId(NArtifactPosition::namesHero[slot.num], artifactID, ArtifactID::NONE, &CArtHandler::decodeArfifact, &CArtHandler::encodeArtifact); + handler.serializeId(NArtifactPosition::namesHero[slot.num], artifactID, ArtifactID::NONE); if(artifactID != ArtifactID::NONE) { diff --git a/lib/CArtHandler.h b/lib/CArtHandler.h index a0b1b737a..e0281fcfc 100644 --- a/lib/CArtHandler.h +++ b/lib/CArtHandler.h @@ -271,12 +271,6 @@ public: std::vector getDefaultAllowed() const override; - ///json serialization helper - static si32 decodeArfifact(const std::string & identifier); - - ///json serialization helper - static std::string encodeArtifact(const si32 index); - template void serialize(Handler &h, const int version) { h & artifacts; diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index 85e7c33f4..b9a0d0363 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -479,20 +479,6 @@ std::vector CCreatureHandler::getDefaultAllowed() const return ret; } -si32 CCreatureHandler::decodeCreature(const std::string& identifier) -{ - auto rawId = VLC->modh->identifiers.getIdentifier("core", "creature", identifier); - if(rawId) - return rawId.get(); - else - return -1; -} - -std::string CCreatureHandler::encodeCreature(const si32 index) -{ - return VLC->creh->creatures[index]->identifier; -} - void CCreatureHandler::loadCrExpBon() { if (VLC->modh->modules.STACK_EXP) //reading default stack experience bonuses diff --git a/lib/CCreatureHandler.h b/lib/CCreatureHandler.h index f097f6dbb..21f434ee8 100644 --- a/lib/CCreatureHandler.h +++ b/lib/CCreatureHandler.h @@ -259,12 +259,6 @@ public: std::vector getDefaultAllowed() const override; - ///json serialization helper - static si32 decodeCreature(const std::string & identifier); - - ///json serialization helper - static std::string encodeCreature(const si32 index); - template void serialize(Handler &h, const int version) { //TODO: should be optimized, not all these informations needs to be serialized (same for ccreature) diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index e0500a7c6..2341df890 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -189,14 +189,14 @@ int CGameInfoCallback::getSpellCost(const CSpell * sp, const CGHeroInstance * ca return caster->getSpellCost(sp); } -int CGameInfoCallback::estimateSpellDamage(const CSpell * sp, const CGHeroInstance * hero) const +int64_t CGameInfoCallback::estimateSpellDamage(const CSpell * sp, const CGHeroInstance * hero) const { //boost::shared_lock lock(*gs->mx); ERROR_RET_VAL_IF(hero && !canGetFullInfo(hero), "Cannot get info about caster!", -1); - if (hero) //we see hero's spellbook - return sp->calculateDamage(hero, nullptr, hero->getEffectLevel(sp), hero->getEffectPower(sp)); + if(hero) //we see hero's spellbook + return sp->calculateDamage(hero); else return 0; //mage guild } diff --git a/lib/CGameInfoCallback.h b/lib/CGameInfoCallback.h index 3beabffdc..54ed8185d 100644 --- a/lib/CGameInfoCallback.h +++ b/lib/CGameInfoCallback.h @@ -9,6 +9,7 @@ */ #pragma once +#include "int3.h" #include "ResourceSet.h" // for Res::ERes #include "battle/CPlayerBattleCallback.h" @@ -28,13 +29,15 @@ class CGTeleport; class CMapHeader; struct TeamState; struct QuestInfo; -class int3; struct ShashInt3; +class CGameState; class DLL_LINKAGE CGameInfoCallback : public virtual CCallbackBase { protected: + CGameState * gs; + CGameInfoCallback(); CGameInfoCallback(CGameState *GS, boost::optional Player); bool hasAccess(boost::optional playerId) const; @@ -72,7 +75,7 @@ public: int getHeroCount(PlayerColor player, bool includeGarrisoned) const; bool getHeroInfo(const CGObjectInstance * hero, InfoAboutHero & dest, const CGObjectInstance * selectedObject = nullptr) const; int getSpellCost(const CSpell * sp, const CGHeroInstance * caster) const; //when called during battle, takes into account creatures' spell cost reduction - int estimateSpellDamage(const CSpell * sp, const CGHeroInstance * hero) const; //estimates damage of given spell; returns 0 if spell causes no dmg + int64_t estimateSpellDamage(const CSpell * sp, const CGHeroInstance * hero) const; //estimates damage of given spell; returns 0 if spell causes no dmg const CArtifactInstance * getArtInstance(ArtifactInstanceID aid) const; const CGObjectInstance * getObjInstance(ObjectInstanceID oid) const; diff --git a/lib/CGameInterface.cpp b/lib/CGameInterface.cpp index 39a9a5a2d..da64d7d3f 100644 --- a/lib/CGameInterface.cpp +++ b/lib/CGameInterface.cpp @@ -134,7 +134,7 @@ std::shared_ptr CDynLibHandler::getNewScriptingModule(std::str BattleAction CGlobalAI::activeStack(const CStack * stack) { BattleAction ba; - ba.actionType = Battle::DEFEND; + ba.actionType = EActionType::DEFEND; ba.stackNumber = stack->ID; return ba; } @@ -164,9 +164,9 @@ void CAdventureAI::battleStart(const CCreatureSet * army1, const CCreatureSet * battleAI->battleStart(army1, army2, tile, hero1, hero2, side); } -void CAdventureAI::battleStacksAttacked(const std::vector & bsa) +void CAdventureAI::battleStacksAttacked(const std::vector & bsa, const std::vector & battleLog) { - battleAI->battleStacksAttacked(bsa); + battleAI->battleStacksAttacked(bsa, battleLog); } void CAdventureAI::actionStarted(const BattleAction & action) @@ -189,19 +189,9 @@ void CAdventureAI::battleStacksEffectsSet(const SetStackEffect & sse) battleAI->battleStacksEffectsSet(sse); } -void CAdventureAI::battleStacksRemoved(const BattleStacksRemoved & bsr) +void CAdventureAI::battleObstaclesChanged(const std::vector & obstacles) { - battleAI->battleStacksRemoved(bsr); -} - -void CAdventureAI::battleObstaclesRemoved(const std::set & removedObstacles) -{ - battleAI->battleObstaclesRemoved(removedObstacles); -} - -void CAdventureAI::battleNewStackAppeared(const CStack * stack) -{ - battleAI->battleNewStackAppeared(stack); + battleAI->battleObstaclesChanged(obstacles); } void CAdventureAI::battleStackMoved(const CStack * stack, std::vector dest, int distance) @@ -225,10 +215,9 @@ void CAdventureAI::battleEnd(const BattleResult * br) battleAI.reset(); } -void CAdventureAI::battleStacksHealedRes(const std::vector > & healedStacks, bool lifeDrain, - bool tentHeal, si32 lifeDrainFrom) +void CAdventureAI::battleUnitsChanged(const std::vector & units, const std::vector & customEffects, const std::vector & battleLog) { - battleAI->battleStacksHealedRes(healedStacks, lifeDrain, tentHeal, lifeDrainFrom); + battleAI->battleUnitsChanged(units, customEffects, battleLog); } BattleAction CAdventureAI::activeStack(const CStack * stack) diff --git a/lib/CGameInterface.h b/lib/CGameInterface.h index 0ba0b468d..7f071aa8f 100644 --- a/lib/CGameInterface.h +++ b/lib/CGameInterface.h @@ -43,7 +43,6 @@ struct Bonus; struct PackageApplied; struct SetObjectProperty; struct CatapultAttack; -struct BattleStacksRemoved; struct StackLocation; class CStackInstance; class CCommanderInstance; @@ -134,20 +133,17 @@ public: virtual void battleNewRound(int round) override; virtual void battleCatapultAttacked(const CatapultAttack & ca) override; virtual void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side) override; - virtual void battleStacksAttacked(const std::vector & bsa) override; + virtual void battleStacksAttacked(const std::vector & bsa, const std::vector & battleLog) override; virtual void actionStarted(const BattleAction &action) override; virtual void battleNewRoundFirst(int round) override; virtual void actionFinished(const BattleAction &action) override; virtual void battleStacksEffectsSet(const SetStackEffect & sse) override; - //virtual void battleTriggerEffect(const BattleTriggerEffect & bte); - virtual void battleStacksRemoved(const BattleStacksRemoved & bsr) override; - virtual void battleObstaclesRemoved(const std::set & removedObstacles) override; - virtual void battleNewStackAppeared(const CStack * stack) override; + virtual void battleObstaclesChanged(const std::vector & obstacles) override; virtual void battleStackMoved(const CStack * stack, std::vector dest, int distance) override; virtual void battleAttack(const BattleAttack *ba) override; virtual void battleSpellCast(const BattleSpellCast *sc) override; virtual void battleEnd(const BattleResult *br) override; - virtual void battleStacksHealedRes(const std::vector > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom) override; + virtual void battleUnitsChanged(const std::vector & units, const std::vector & customEffects, const std::vector & battleLog) override; virtual void saveGame(BinarySerializer & h, const int version) override; //saving virtual void loadGame(BinaryDeserializer & h, const int version) override; //loading diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index ef732ccbe..036a89a15 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -33,7 +33,6 @@ #include "rmg/CMapGenerator.h" #include "CStopWatch.h" #include "mapping/CMapEditManager.h" -#include "mapping/CMapService.h" #include "serializer/CTypeList.h" #include "serializer/CMemorySerializer.h" #include "VCMIDirs.h" @@ -703,7 +702,7 @@ CGameState::~CGameState() ptr.second.dellNull(); } -void CGameState::init(StartInfo * si, bool allowSavingRandomMap) +void CGameState::init(const IMapService * mapService, StartInfo * si, bool allowSavingRandomMap) { logGlobal->info("\tUsing random seed: %d", si->seedToBeUsed); getRandomGenerator().setSeed(si->seedToBeUsed); @@ -714,10 +713,10 @@ void CGameState::init(StartInfo * si, bool allowSavingRandomMap) switch(scenarioOps->mode) { case StartInfo::NEW_GAME: - initNewGame(allowSavingRandomMap); + initNewGame(mapService, allowSavingRandomMap); break; case StartInfo::CAMPAIGN: - initCampaign(); + initCampaign(mapService); break; default: logGlobal->error("Wrong mode: %d", static_cast(scenarioOps->mode)); @@ -773,7 +772,7 @@ void CGameState::init(StartInfo * si, bool allowSavingRandomMap) } } -void CGameState::initNewGame(bool allowSavingRandomMap) +void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRandomMap) { if(scenarioOps->createRandomMap()) { @@ -800,7 +799,7 @@ void CGameState::initNewGame(bool allowSavingRandomMap) const std::string fileName = boost::str(boost::format("%s_%d.vmap") % templateName % seed ); const auto fullPath = path / fileName; - CMapService::saveMap(randomMap, fullPath); + mapService->saveMap(randomMap, fullPath); logGlobal->info("Random map has been saved to:"); logGlobal->info(fullPath.string()); @@ -841,11 +840,11 @@ void CGameState::initNewGame(bool allowSavingRandomMap) { logGlobal->info("Open map file: %s", scenarioOps->mapname); const ResourceID mapURI(scenarioOps->mapname, EResType::MAP); - map = CMapService::loadMap(mapURI).release(); + map = mapService->loadMap(mapURI).release(); } } -void CGameState::initCampaign() +void CGameState::initCampaign(const IMapService * mapService) { logGlobal->info("Open campaign map file: %d", scenarioOps->campState->currentMap.get()); auto campaign = scenarioOps->campState; @@ -857,7 +856,7 @@ void CGameState::initCampaign() std::string & mapContent = campaign->camp->mapPieces[*campaign->currentMap]; auto buffer = reinterpret_cast(mapContent.data()); - map = CMapService::loadMap(buffer, mapContent.size(), scenarioName).release(); + map = mapService->loadMap(buffer, mapContent.size(), scenarioName).release(); } void CGameState::checkMapChecksum() diff --git a/lib/CGameState.h b/lib/CGameState.h index cc75984ce..359dff9cc 100644 --- a/lib/CGameState.h +++ b/lib/CGameState.h @@ -26,7 +26,6 @@ class CTown; class CCallback; class IGameCallback; class CCreatureSet; -class CStack; class CQuest; class CGHeroInstance; class CGTownInstance; @@ -54,6 +53,7 @@ class CQuest; class CCampaignScenario; struct EventCondition; class CScenarioTravel; +class IMapService; namespace boost { @@ -127,7 +127,7 @@ struct UpgradeInfo UpgradeInfo(){oldID = CreatureID::NONE;}; }; -struct BattleInfo; +class BattleInfo; DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const EVictoryLossCheckResult & victoryLossCheckResult); @@ -152,7 +152,7 @@ public: CGameState(); virtual ~CGameState(); - void init(StartInfo * si, bool allowSavingRandomMap = false); + void init(const IMapService * mapService, StartInfo * si, bool allowSavingRandomMap = false); ConstTransitivePtr scenarioOps, initialOpts; //second one is a copy of settings received from pregame (not randomized) PlayerColor currentPlayer; //ID of player currently having turn @@ -245,8 +245,8 @@ private: // ----- initialization ----- - void initNewGame(bool allowSavingRandomMap); - void initCampaign(); + void initNewGame(const IMapService * mapService, bool allowSavingRandomMap); + void initCampaign(const IMapService * mapService); void checkMapChecksum(); void initGrailPosition(); void initRandomFactionsForPlayers(); diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 382f081bb..3216bbe21 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -10,14 +10,19 @@ set(lib_SRCS battle/BattleAttackInfo.cpp battle/BattleHex.cpp battle/BattleInfo.cpp + battle/BattleProxy.cpp battle/CBattleInfoCallback.cpp battle/CBattleInfoEssentials.cpp battle/CCallbackBase.cpp battle/CObstacleInstance.cpp battle/CPlayerBattleCallback.cpp + battle/CUnitState.cpp + battle/Destination.cpp + battle/IBattleState.cpp battle/ReachabilityInfo.cpp battle/SideInBattle.cpp battle/SiegeInfo.cpp + battle/Unit.cpp filesystem/AdapterLoaders.cpp filesystem/CArchiveLoader.cpp @@ -93,12 +98,29 @@ set(lib_SRCS spells/AdventureSpellMechanics.cpp spells/BattleSpellMechanics.cpp - spells/CDefaultSpellMechanics.cpp - spells/CreatureSpellMechanics.cpp spells/CSpellHandler.cpp spells/ISpellMechanics.cpp + spells/Problem.cpp + spells/TargetCondition.cpp spells/ViewSpellInt.cpp + spells/effects/Catapult.cpp + spells/effects/Clone.cpp + spells/effects/Damage.cpp + spells/effects/Dispel.cpp + spells/effects/Effect.cpp + spells/effects/Effects.cpp + spells/effects/Heal.cpp + spells/effects/LocationEffect.cpp + spells/effects/Obstacle.cpp + spells/effects/Registry.cpp + spells/effects/UnitEffect.cpp + spells/effects/Summon.cpp + spells/effects/Teleport.cpp + spells/effects/Timed.cpp + spells/effects/RemoveObstacle.cpp + spells/effects/Sacrifice.cpp + CAndroidVMHelper.cpp CArtHandler.cpp CBonusTypeHandler.cpp @@ -143,14 +165,20 @@ set(lib_HEADERS battle/BattleAttackInfo.h battle/BattleHex.h battle/BattleInfo.h + battle/BattleProxy.h battle/CBattleInfoCallback.h battle/CBattleInfoEssentials.h battle/CCallbackBase.h battle/CObstacleInstance.h battle/CPlayerBattleCallback.h + battle/CUnitState.h + battle/Destination.h + battle/IBattleState.h + battle/IUnitInfo.h battle/ReachabilityInfo.h battle/SideInBattle.h battle/SiegeInfo.h + battle/Unit.h filesystem/AdapterLoaders.h filesystem/CArchiveLoader.h @@ -227,14 +255,31 @@ set(lib_HEADERS spells/AdventureSpellMechanics.h spells/BattleSpellMechanics.h - spells/CDefaultSpellMechanics.h - spells/CreatureSpellMechanics.h spells/CSpellHandler.h spells/ISpellMechanics.h spells/Magic.h spells/SpellMechanics.h + spells/Problem.h + spells/TargetCondition.h spells/ViewSpellInt.h + spells/effects/Catapult.h + spells/effects/Clone.h + spells/effects/Damage.h + spells/effects/Dispel.h + spells/effects/Effect.h + spells/effects/Effects.h + spells/effects/EffectsFwd.h + spells/effects/Heal.h + spells/effects/LocationEffect.h + spells/effects/Obstacle.h + spells/effects/Registry.h + spells/effects/UnitEffect.h + spells/effects/Summon.h + spells/effects/Teleport.h + spells/effects/Timed.h + spells/effects/RemoveObstacle.h + spells/effects/Sacrifice.h AI_Base.h CAndroidVMHelper.h CArtHandler.h diff --git a/lib/CModHandler.cpp b/lib/CModHandler.cpp index 5aa5bc04e..ba9244b3d 100644 --- a/lib/CModHandler.cpp +++ b/lib/CModHandler.cpp @@ -316,7 +316,7 @@ void CIdentifierStorage::finalize() state = FINISHED; } -CContentHandler::ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler, std::string objectName): +ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler, std::string objectName): handler(handler), objectName(objectName), originalData(handler->loadLegacyData(VLC->modh->settings.data["textData"][objectName].Float())) @@ -327,7 +327,7 @@ CContentHandler::ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler, } } -bool CContentHandler::ContentTypeHandler::preloadModData(std::string modName, std::vector fileList, bool validate) +bool ContentTypeHandler::preloadModData(std::string modName, std::vector fileList, bool validate) { bool result; JsonNode data = JsonUtils::assembleFromFiles(fileList, result); @@ -362,7 +362,7 @@ bool CContentHandler::ContentTypeHandler::preloadModData(std::string modName, st return result; } -bool CContentHandler::ContentTypeHandler::loadMod(std::string modName, bool validate) +bool ContentTypeHandler::loadMod(std::string modName, bool validate) { ModInfo & modInfo = modData[modName]; bool result = true; @@ -387,42 +387,47 @@ bool CContentHandler::ContentTypeHandler::loadMod(std::string modName, bool vali // try to add H3 object data size_t index = data["index"].Float(); - if (originalData.size() > index) + if(originalData.size() > index) { logMod->trace("found original data in loadMod(%s) at index %d", name, index); JsonUtils::merge(originalData[index], data); - performValidate(originalData[index],name); - handler->loadObject(modName, name, originalData[index], index); + std::swap(originalData[index], data); originalData[index].clear(); // do not use same data twice (same ID) } else { - logMod->debug("no original data in loadMod(%s) at index %d", name, index); - performValidate(data, name); - handler->loadObject(modName, name, data, index); + logMod->warn("no original data in loadMod(%s) at index %d", name, index); } - continue; + performValidate(data, name); + handler->loadObject(modName, name, data, index); + } + else + { + // normal new object + logMod->trace("no index in loadMod(%s)", name); + performValidate(data,name); + handler->loadObject(modName, name, data); } - // normal new object - logMod->trace("no index in loadMod(%s)", name); - performValidate(data,name); - handler->loadObject(modName, name, data); } return result; } -void CContentHandler::ContentTypeHandler::loadCustom() +void ContentTypeHandler::loadCustom() { handler->loadCustom(); } -void CContentHandler::ContentTypeHandler::afterLoadFinalization() +void ContentTypeHandler::afterLoadFinalization() { handler->afterLoadFinalization(); } CContentHandler::CContentHandler() +{ +} + +void CContentHandler::init() { handlers.insert(std::make_pair("heroClasses", ContentTypeHandler(&VLC->heroh->classes, "heroClass"))); handlers.insert(std::make_pair("artifacts", ContentTypeHandler(VLC->arth, "artifact"))); @@ -507,6 +512,11 @@ void CContentHandler::load(CModInfo & mod) logMod->info("\t\t[SKIP] %s", mod.name); } +const ContentTypeHandler & CContentHandler::operator[](const std::string & name) const +{ + return handlers.at(name); +} + static JsonNode loadModSettings(std::string path) { if (CResourceHandler::get("local")->existsResource(ResourceID(path))) @@ -810,31 +820,42 @@ std::vector CModHandler::getModList(std::string path) void CModHandler::loadMods(std::string path, std::string parent, const JsonNode & modSettings, bool enableMods) { - for (std::string modName : getModList(path)) + for(std::string modName : getModList(path)) + loadOneMod(modName, parent, modSettings, enableMods); +} + +void CModHandler::loadOneMod(std::string modName, std::string parent, const JsonNode & modSettings, bool enableMods) +{ + boost::to_lower(modName); + std::string modFullName = parent.empty() ? modName : parent + '.' + modName; + + if(CResourceHandler::get("initial")->existsResource(ResourceID(CModInfo::getModFile(modFullName)))) { - boost::to_lower(modName); - std::string modFullName = parent.empty() ? modName : parent + '.' + modName; + CModInfo mod(modFullName, modSettings[modName], JsonNode(ResourceID(CModInfo::getModFile(modFullName)))); + if (!parent.empty()) // this is submod, add parent to dependencies + mod.dependencies.insert(parent); - if (CResourceHandler::get("initial")->existsResource(ResourceID(CModInfo::getModFile(modFullName)))) - { - CModInfo mod(modFullName, modSettings[modName], JsonNode(ResourceID(CModInfo::getModFile(modFullName)))); - if (!parent.empty()) // this is submod, add parent to dependecies - mod.dependencies.insert(parent); + allMods[modFullName] = mod; + if (mod.enabled && enableMods) + activeMods.push_back(modFullName); - allMods[modFullName] = mod; - if (mod.enabled && enableMods) - activeMods.push_back(modFullName); - - loadMods(CModInfo::getModDir(modFullName) + '/', modFullName, modSettings[modName]["mods"], enableMods && mod.enabled); - } + loadMods(CModInfo::getModDir(modFullName) + '/', modFullName, modSettings[modName]["mods"], enableMods && mod.enabled); } } -void CModHandler::loadMods() +void CModHandler::loadMods(bool onlyEssential) { - const JsonNode modConfig = loadModSettings("config/modSettings.json"); + JsonNode modConfig; - loadMods("", "", modConfig["activeMods"], true); + if(onlyEssential) + { + loadOneMod("vcmi", "", modConfig, true);//only vcmi and submods + } + else + { + modConfig = loadModSettings("config/modSettings.json"); + loadMods("", "", modConfig["activeMods"], true); + } coreMod = CModInfo("core", modConfig["core"], JsonNode(ResourceID("config/gameConfig.json"))); coreMod.name = "Original game files"; @@ -941,9 +962,10 @@ void CModHandler::load() { CStopWatch totalTime, timer; - CContentHandler content; logMod->info("\tInitializing content handler: %d ms", timer.getDiff()); + content.init(); + for(const TModID & modName : activeMods) { logMod->trace("Generating checksum for %s", modName); @@ -976,7 +998,7 @@ void CModHandler::load() logMod->info("\tAll game content loaded in %d ms", totalTime.getDiff()); } -void CModHandler::afterLoad() +void CModHandler::afterLoad(bool onlyEssential) { JsonNode modSettings; for (auto & modEntry : allMods) @@ -987,8 +1009,12 @@ void CModHandler::afterLoad() } modSettings["core"] = coreMod.saveLocalData(); - FileStream file(*CResourceHandler::get()->getResourceName(ResourceID("config/modSettings.json")), std::ofstream::out | std::ofstream::trunc); - file << modSettings.toJson(); + if(!onlyEssential) + { + FileStream file(*CResourceHandler::get()->getResourceName(ResourceID("config/modSettings.json")), std::ofstream::out | std::ofstream::trunc); + file << modSettings.toJson(); + } + } std::string CModHandler::normalizeIdentifier(const std::string & scope, const std::string & remoteScope, const std::string & identifier) @@ -1026,10 +1052,27 @@ void CModHandler::parseIdentifier(const std::string & fullIdentifier, std::strin std::string CModHandler::makeFullIdentifier(const std::string & scope, const std::string & type, const std::string & identifier) { - auto p = splitString(identifier, ':'); + if(type == "") + logGlobal->error("Full identifier (%s %s) requires type name", scope, identifier); - if(p.first != "") - return p.first + ":" + type + "." + p.second;//ignore type if identifier is scoped + std::string actualScope = scope; + std::string actualName = identifier; + + //ignore scope if identifier is scoped + auto scopeAndName = splitString(identifier, ':'); + + if(scopeAndName.first != "") + { + actualScope = scopeAndName.first; + actualName = scopeAndName.second; + } + + if(actualScope == "") + { + return actualName == "" ? type : type + "." + actualName; + } else - return scope == "" ? (identifier == "" ? type : type + "." + identifier) : scope + ":" + type + "." + identifier; + { + return actualName == "" ? actualScope+ ":" + type : actualScope + ":" + type + "." + actualName; + } } diff --git a/lib/CModHandler.h b/lib/CModHandler.h index a3ed87b50..5cefecccf 100644 --- a/lib/CModHandler.h +++ b/lib/CModHandler.h @@ -108,40 +108,38 @@ public: } }; -/// class used to load all game data into handlers. Used only during loading -class CContentHandler +/// internal type to handle loading of one data type (e.g. artifacts, creatures) +class DLL_LINKAGE ContentTypeHandler { - /// internal type to handle loading of one data type (e.g. artifacts, creatures) - class ContentTypeHandler +public: + struct ModInfo { - struct ModInfo - { - /// mod data from this mod and for this mod - JsonNode modData; - /// mod data for this mod from other mods (patches) - JsonNode patches; - }; - - /// handler to which all data will be loaded - IHandlerBase * handler; - - std::string objectName; - - /// contains all loaded H3 data - std::vector originalData; - std::map modData; - - public: - ContentTypeHandler(IHandlerBase * handler, std::string objectName); - - /// local version of methods in ContentHandler - /// returns true if loading was successful - bool preloadModData(std::string modName, std::vector fileList, bool validate); - bool loadMod(std::string modName, bool validate); - void loadCustom(); - void afterLoadFinalization(); + /// mod data from this mod and for this mod + JsonNode modData; + /// mod data for this mod from other mods (patches) + JsonNode patches; }; + /// handler to which all data will be loaded + IHandlerBase * handler; + std::string objectName; + /// contains all loaded H3 data + std::vector originalData; + std::map modData; + + ContentTypeHandler(IHandlerBase * handler, std::string objectName); + + /// local version of methods in ContentHandler + /// returns true if loading was successful + bool preloadModData(std::string modName, std::vector fileList, bool validate); + bool loadMod(std::string modName, bool validate); + void loadCustom(); + void afterLoadFinalization(); +}; + +/// class used to load all game data into handlers. Used only during loading +class DLL_LINKAGE CContentHandler +{ /// preloads all data from fileList as data from modName. bool preloadModData(std::string modName, JsonNode modConfig, bool validate); @@ -150,9 +148,10 @@ class CContentHandler std::map handlers; public: - /// fully initialize object. Will cause reading of H3 config files CContentHandler(); + void init(); + /// preloads all data from fileList as data from modName. void preloadData(CModInfo & mod); @@ -163,6 +162,8 @@ public: /// all data was loaded, time for final validation / integration void afterLoadFinalization(); + + const ContentTypeHandler & operator[] (const std::string & name) const; }; typedef std::string TModID; @@ -246,14 +247,17 @@ class DLL_LINKAGE CModHandler std::vector resolveDependencies(std::vector input) const; std::vector getModList(std::string path); - void loadMods(std::string path, std::string namePrefix, const JsonNode & modSettings, bool enableMods); + void loadMods(std::string path, std::string parent, const JsonNode & modSettings, bool enableMods); + void loadOneMod(std::string modName, std::string parent, const JsonNode & modSettings, bool enableMods); public: CIdentifierStorage identifiers; + CContentHandler content; //(!)Do not serialize + /// receives list of available mods and trying to load mod.json from all of them void initializeConfig(); - void loadMods(); + void loadMods(bool onlyEssential = false); void loadModFilesystems(); CModInfo & getModData(TModID modId); @@ -264,7 +268,7 @@ public: /// load content from all available mods void load(); - void afterLoad(); + void afterLoad(bool onlyEssential); struct DLL_LINKAGE hardcodedFeatures { diff --git a/lib/CRandomGenerator.cpp b/lib/CRandomGenerator.cpp index b4ff51570..88fb9b422 100644 --- a/lib/CRandomGenerator.cpp +++ b/lib/CRandomGenerator.cpp @@ -32,7 +32,12 @@ void CRandomGenerator::resetSeed() TRandI CRandomGenerator::getIntRange(int lower, int upper) { - return std::bind(TIntDist(lower, upper), std::ref(rand)); + return std::bind(TIntDist(lower, upper), std::ref(rand)); +} + +vstd::TRandI64 CRandomGenerator::getInt64Range(int64_t lower, int64_t upper) +{ + return std::bind(TInt64Dist(lower, upper), std::ref(rand)); } int CRandomGenerator::nextInt(int upper) @@ -50,7 +55,7 @@ int CRandomGenerator::nextInt() return TIntDist()(rand); } -TRand CRandomGenerator::getDoubleRange(double lower, double upper) +vstd::TRand CRandomGenerator::getDoubleRange(double lower, double upper) { return std::bind(TRealDist(lower, upper), std::ref(rand)); } diff --git a/lib/CRandomGenerator.h b/lib/CRandomGenerator.h index 06f188107..0d052b32b 100644 --- a/lib/CRandomGenerator.h +++ b/lib/CRandomGenerator.h @@ -10,16 +10,18 @@ #pragma once +#include + typedef std::mt19937 TGenerator; typedef std::uniform_int_distribution TIntDist; +typedef std::uniform_int_distribution TInt64Dist; typedef std::uniform_real_distribution TRealDist; typedef std::function TRandI; -typedef std::function TRand; /// The random generator randomly generates integers and real numbers("doubles") between /// a given range. This is a header only class and mainly a wrapper for /// convenient usage of the standard random API. An instance of this RNG is not thread safe. -class DLL_LINKAGE CRandomGenerator : boost::noncopyable +class DLL_LINKAGE CRandomGenerator : public vstd::RNG, boost::noncopyable { public: /// Seeds the generator by default with the product of the current time in milliseconds and the @@ -36,22 +38,24 @@ public: /// e.g.: auto a = gen.getIntRange(0,10); a(); a(); a(); /// requires: lower <= upper TRandI getIntRange(int lower, int upper); - + + vstd::TRandI64 getInt64Range(int64_t lower, int64_t upper) override; + /// Generates an integer between 0 and upper. /// requires: 0 <= upper int nextInt(int upper); /// requires: lower <= upper int nextInt(int lower, int upper); - + /// Generates an integer between 0 and the maximum value it can hold. int nextInt(); /// Generate several double/real numbers within the same range. /// e.g.: auto a = gen.getDoubleRange(4.5,10.2); a(); a(); a(); /// requires: lower <= upper - TRand getDoubleRange(double lower, double upper); - + vstd::TRand getDoubleRange(double lower, double upper) override; + /// Generates a double between 0 and upper. /// requires: 0 <= upper double nextDouble(double upper); @@ -94,29 +98,3 @@ public: } }; -namespace RandomGeneratorUtil -{ - template - auto nextItem(const Container & container, CRandomGenerator & rand) -> decltype(std::begin(container)) - { - assert(!container.empty()); - return std::next(container.begin(), rand.nextInt(container.size() - 1)); - } - - template - auto nextItem(Container & container, CRandomGenerator & rand) -> decltype(std::begin(container)) - { - assert(!container.empty()); - return std::next(container.begin(), rand.nextInt(container.size() - 1)); - } - - template - void randomShuffle(std::vector& container, CRandomGenerator & rand) - { - int n = (container.end() - container.begin()); - for (int i = n-1; i>0; --i) - { - std::swap (container.begin()[i],container.begin()[rand.nextInt(i)]); - } - } -} diff --git a/lib/CSkillHandler.cpp b/lib/CSkillHandler.cpp index 0f3c4e175..a8d4e4d6a 100644 --- a/lib/CSkillHandler.cpp +++ b/lib/CSkillHandler.cpp @@ -22,10 +22,6 @@ #include "CModHandler.h" #include "StringConstants.h" -#include "CStack.h" -#include "battle/BattleInfo.h" -#include "battle/CBattleInfoCallback.h" - ///CSkill CSkill::LevelInfo::LevelInfo() { @@ -158,7 +154,7 @@ const std::string & CSkillHandler::skillName(int skill) const return objects[skill]->name; } -CSkill * CSkillHandler::loadFromJson(const JsonNode & json, const std::string & identifier) +CSkill * CSkillHandler::loadFromJson(const JsonNode & json, const std::string & identifier, size_t index) { CSkill * skill = nullptr; diff --git a/lib/CSkillHandler.h b/lib/CSkillHandler.h index 9248ba9d8..1e6d1362f 100644 --- a/lib/CSkillHandler.h +++ b/lib/CSkillHandler.h @@ -83,5 +83,5 @@ public: } protected: - CSkill * loadFromJson(const JsonNode & json, const std::string & identifier) override; + CSkill * loadFromJson(const JsonNode & json, const std::string & identifier, size_t index) override; }; diff --git a/lib/CStack.cpp b/lib/CStack.cpp index 6af0a1b20..1e9a4579a 100644 --- a/lib/CStack.cpp +++ b/lib/CStack.cpp @@ -9,326 +9,34 @@ */ #include "StdInc.h" #include "CStack.h" + +#include + #include "CGeneralTextHandler.h" #include "battle/BattleInfo.h" #include "spells/CSpellHandler.h" -#include "CRandomGenerator.h" #include "NetPacks.h" -///CAmmo -CAmmo::CAmmo(const CStack * Owner, CSelector totalSelector): - CStackResource(Owner), totalProxy(Owner, totalSelector) -{ - -} - -int32_t CAmmo::available() const -{ - return total() - used; -} - -bool CAmmo::canUse(int32_t amount) const -{ - return available() - amount >= 0; -} - -void CAmmo::reset() -{ - used = 0; -} - -int32_t CAmmo::total() const -{ - return totalProxy->totalValue(); -} - -void CAmmo::use(int32_t amount) -{ - if(available() - amount < 0) - { - logGlobal->error("Stack ammo overuse"); - used += available(); - } - else - used += amount; -} - -///CShots -CShots::CShots(const CStack * Owner): - CAmmo(Owner, Selector::type(Bonus::SHOTS)) -{ - -} - -void CShots::use(int32_t amount) -{ - //don't remove ammo if we control a working ammo cart - bool hasAmmoCart = false; - - for(const CStack * st : owner->battle->stacks) - { - if(owner->battle->battleMatchOwner(st, owner, true) && st->getCreature()->idNumber == CreatureID::AMMO_CART && st->alive()) - { - hasAmmoCart = true; - break; - } - } - - if(!hasAmmoCart) - CAmmo::use(amount); -} - -///CCasts -CCasts::CCasts(const CStack * Owner): - CAmmo(Owner, Selector::type(Bonus::CASTS)) -{ - -} - -///CRetaliations -CRetaliations::CRetaliations(const CStack * Owner): - CAmmo(Owner, Selector::type(Bonus::ADDITIONAL_RETALIATION)), totalCache(0) -{ - -} - -int32_t CRetaliations::total() const -{ - //after dispell bonus should remain during current round - int32_t val = 1 + totalProxy->totalValue(); - vstd::amax(totalCache, val); - return totalCache; -} - -void CRetaliations::reset() -{ - CAmmo::reset(); - totalCache = 0; -} - -///CHealth -CHealth::CHealth(const IUnitHealthInfo * Owner): - owner(Owner) -{ - reset(); -} - -CHealth::CHealth(const CHealth & other): - owner(other.owner), - firstHPleft(other.firstHPleft), - fullUnits(other.fullUnits), - resurrected(other.resurrected) -{ - -} - -void CHealth::init() -{ - reset(); - fullUnits = owner->unitBaseAmount() > 1 ? owner->unitBaseAmount() - 1 : 0; - firstHPleft = owner->unitBaseAmount() > 0 ? owner->unitMaxHealth() : 0; -} - -void CHealth::addResurrected(int32_t amount) -{ - resurrected += amount; - vstd::amax(resurrected, 0); -} - -int64_t CHealth::available() const -{ - return static_cast(firstHPleft) + owner->unitMaxHealth() * fullUnits; -} - -int64_t CHealth::total() const -{ - return static_cast(owner->unitMaxHealth()) * owner->unitBaseAmount(); -} - -void CHealth::damage(int32_t & amount) -{ - const int32_t oldCount = getCount(); - - const bool withKills = amount >= firstHPleft; - - if(withKills) - { - int64_t totalHealth = available(); - if(amount > totalHealth) - amount = totalHealth; - totalHealth -= amount; - if(totalHealth <= 0) - { - fullUnits = 0; - firstHPleft = 0; - } - else - { - setFromTotal(totalHealth); - } - } - else - { - firstHPleft -= amount; - } - - addResurrected(getCount() - oldCount); -} - -void CHealth::heal(int32_t & amount, EHealLevel level, EHealPower power) -{ - const int32_t unitHealth = owner->unitMaxHealth(); - const int32_t oldCount = getCount(); - - int32_t maxHeal = std::numeric_limits::max(); - - switch(level) - { - case EHealLevel::HEAL: - maxHeal = std::max(0, unitHealth - firstHPleft); - break; - case EHealLevel::RESURRECT: - maxHeal = total() - available(); - break; - default: - assert(level == EHealLevel::OVERHEAL); - break; - } - - vstd::amax(maxHeal, 0); - vstd::abetween(amount, 0, maxHeal); - - if(amount == 0) - return; - - int64_t availableHealth = available(); - - availableHealth += amount; - setFromTotal(availableHealth); - - if(power == EHealPower::ONE_BATTLE) - addResurrected(getCount() - oldCount); - else - assert(power == EHealPower::PERMANENT); -} - -void CHealth::setFromTotal(const int64_t totalHealth) -{ - const int32_t unitHealth = owner->unitMaxHealth(); - firstHPleft = totalHealth % unitHealth; - fullUnits = totalHealth / unitHealth; - - if(firstHPleft == 0 && fullUnits >= 1) - { - firstHPleft = unitHealth; - fullUnits -= 1; - } -} - -void CHealth::reset() -{ - fullUnits = 0; - firstHPleft = 0; - resurrected = 0; -} - -int32_t CHealth::getCount() const -{ - return fullUnits + (firstHPleft > 0 ? 1 : 0); -} - -int32_t CHealth::getFirstHPleft() const -{ - return firstHPleft; -} - -int32_t CHealth::getResurrected() const -{ - return resurrected; -} - -void CHealth::fromInfo(const CHealthInfo & info) -{ - firstHPleft = info.firstHPleft; - fullUnits = info.fullUnits; - resurrected = info.resurrected; -} - -void CHealth::toInfo(CHealthInfo & info) const -{ - info.firstHPleft = firstHPleft; - info.fullUnits = fullUnits; - info.resurrected = resurrected; -} - -void CHealth::takeResurrected() -{ - if(resurrected != 0) - { - int64_t totalHealth = available(); - - totalHealth -= resurrected * owner->unitMaxHealth(); - vstd::amax(totalHealth, 0); - setFromTotal(totalHealth); - resurrected = 0; - } -} - ///CStack -CStack::CStack(const CStackInstance * Base, PlayerColor O, int I, ui8 Side, SlotID S): - base(Base), ID(I), owner(O), slot(S), side(Side), - counterAttacks(this), shots(this), casts(this), health(this), cloneID(-1), - position() +CStack::CStack(const CStackInstance * Base, PlayerColor O, int I, ui8 Side, SlotID S) + : CBonusSystemNode(STACK_BATTLE), + CUnitState(), + base(Base), + ID(I), + type(Base->type), + baseAmount(base->count), + owner(O), + slot(S), + side(Side), + initialPosition() { - assert(base); - type = base->type; - baseAmount = base->count; health.init(); //??? - setNodeType(STACK_BATTLE); } -CStack::CStack(): - counterAttacks(this), shots(this), casts(this), health(this) -{ - init(); - setNodeType(STACK_BATTLE); -} - -CStack::CStack(const CStackBasicDescriptor * stack, PlayerColor O, int I, ui8 Side, SlotID S): - base(nullptr), ID(I), owner(O), slot(S), side(Side), - counterAttacks(this), shots(this), casts(this), health(this), cloneID(-1), - position() -{ - type = stack->type; - baseAmount = stack->count; - health.init(); //??? - setNodeType(STACK_BATTLE); -} - -int32_t CStack::getKilled() const -{ - int32_t res = baseAmount - health.getCount() + health.getResurrected(); - vstd::amax(res, 0); - return res; -} - -int32_t CStack::getCount() const -{ - return health.getCount(); -} - -int32_t CStack::getFirstHPleft() const -{ - return health.getFirstHPleft(); -} - -const CCreature * CStack::getCreature() const -{ - return type; -} - -void CStack::init() +CStack::CStack() + : CBonusSystemNode(STACK_BATTLE), + CUnitState() { base = nullptr; type = nullptr; @@ -337,14 +45,32 @@ void CStack::init() owner = PlayerColor::NEUTRAL; slot = SlotID(255); side = 1; - position = BattleHex(); - cloneID = -1; + initialPosition = BattleHex(); +} + +CStack::CStack(const CStackBasicDescriptor * stack, PlayerColor O, int I, ui8 Side, SlotID S) + : CBonusSystemNode(STACK_BATTLE), + CUnitState(), + base(nullptr), + ID(I), + type(stack->type), + baseAmount(stack->count), + owner(O), + slot(S), + side(Side), + initialPosition() +{ + health.init(); //??? +} + +const CCreature * CStack::getCreature() const +{ + return type; } void CStack::localInit(BattleInfo * battleInfo) { battle = battleInfo; - cloneID = -1; assert(type); exportBonuses(); @@ -359,185 +85,35 @@ void CStack::localInit(BattleInfo * battleInfo) attachTo(const_cast(type)); } - shots.reset(); - counterAttacks.reset(); - casts.reset(); - health.init(); + CUnitState::localInit(this); + position = initialPosition; } ui32 CStack::level() const { if(base) - return base->getLevel(); //creatture or commander + return base->getLevel(); //creature or commander else return std::max(1, (int)getCreature()->level); //war machine, clone etc } si32 CStack::magicResistance() const { - si32 magicResistance; - if(base) //TODO: make war machines receive aura of magic resistance + si32 magicResistance = IBonusBearer::magicResistance(); + + si32 auraBonus = 0; + + for(auto one : battle->battleAdjacentUnits(this)) { - magicResistance = base->magicResistance(); - int auraBonus = 0; - for(const CStack * stack : base->armyObj->battle-> batteAdjacentCreatures(this)) - { - if(stack->owner == owner) - { - vstd::amax(auraBonus, stack->valOfBonuses(Bonus::SPELL_RESISTANCE_AURA)); //max value - } - } - magicResistance += auraBonus; - vstd::amin(magicResistance, 100); + if(one->unitOwner() == owner) + vstd::amax(auraBonus, one->valOfBonuses(Bonus::SPELL_RESISTANCE_AURA)); //max value } - else - magicResistance = type->magicResistance(); + magicResistance += auraBonus; + vstd::amin(magicResistance, 100); + return magicResistance; } -bool CStack::willMove(int turn) const -{ - return (turn ? true : !vstd::contains(state, EBattleStackState::DEFENDING)) - && !moved(turn) - && canMove(turn); -} - -bool CStack::canMove(int turn) const -{ - return alive() - && !hasBonus(Selector::type(Bonus::NOT_ACTIVE).And(Selector::turns(turn))); //eg. Ammo Cart or blinded creature -} - -bool CStack::canCast() const -{ - return casts.canUse(1);//do not check specific cast abilities here -} - -bool CStack::isCaster() const -{ - return casts.total() > 0;//do not check specific cast abilities here -} - -bool CStack::canShoot() const -{ - return shots.canUse(1) && hasBonusOfType(Bonus::SHOOTER); -} - -bool CStack::isShooter() const -{ - return shots.total() > 0 && hasBonusOfType(Bonus::SHOOTER); -} - -bool CStack::moved(int turn) const -{ - if(!turn) - return vstd::contains(state, EBattleStackState::MOVED); - else - return false; -} - -bool CStack::waited(int turn) const -{ - if(!turn) - return vstd::contains(state, EBattleStackState::WAITING); - else - return false; -} - -bool CStack::doubleWide() const -{ - return getCreature()->doubleWide; -} - -BattleHex CStack::occupiedHex() const -{ - return occupiedHex(position); -} - -BattleHex CStack::occupiedHex(BattleHex assumedPos) const -{ - if(doubleWide()) - { - if(side == BattleSide::ATTACKER) - return assumedPos - 1; - else - return assumedPos + 1; - } - else - { - return BattleHex::INVALID; - } -} - -std::vector CStack::getHexes() const -{ - return getHexes(position); -} - -std::vector CStack::getHexes(BattleHex assumedPos) const -{ - return getHexes(assumedPos, doubleWide(), side); -} - -std::vector CStack::getHexes(BattleHex assumedPos, bool twoHex, ui8 side) -{ - std::vector hexes; - hexes.push_back(assumedPos); - - if(twoHex) - { - if(side == BattleSide::ATTACKER) - hexes.push_back(assumedPos - 1); - else - hexes.push_back(assumedPos + 1); - } - - return hexes; -} - -bool CStack::coversPos(BattleHex pos) const -{ - return vstd::contains(getHexes(), pos); -} - -std::vector CStack::getSurroundingHexes(BattleHex attackerPos) const -{ - BattleHex hex = (attackerPos != BattleHex::INVALID) ? attackerPos : position; //use hypothetical position - std::vector hexes; - if(doubleWide()) - { - const int WN = GameConstants::BFIELD_WIDTH; - if(side == BattleSide::ATTACKER) - { - //position is equal to front hex - BattleHex::checkAndPush(hex - ((hex / WN) % 2 ? WN + 2 : WN + 1), hexes); - BattleHex::checkAndPush(hex - ((hex / WN) % 2 ? WN + 1 : WN), hexes); - BattleHex::checkAndPush(hex - ((hex / WN) % 2 ? WN : WN - 1), hexes); - BattleHex::checkAndPush(hex - 2, hexes); - BattleHex::checkAndPush(hex + 1, hexes); - BattleHex::checkAndPush(hex + ((hex / WN) % 2 ? WN - 2 : WN - 1), hexes); - BattleHex::checkAndPush(hex + ((hex / WN) % 2 ? WN - 1 : WN), hexes); - BattleHex::checkAndPush(hex + ((hex / WN) % 2 ? WN : WN + 1), hexes); - } - else - { - BattleHex::checkAndPush(hex - ((hex / WN) % 2 ? WN + 1 : WN), hexes); - BattleHex::checkAndPush(hex - ((hex / WN) % 2 ? WN : WN - 1), hexes); - BattleHex::checkAndPush(hex - ((hex / WN) % 2 ? WN - 1 : WN - 2), hexes); - BattleHex::checkAndPush(hex + 2, hexes); - BattleHex::checkAndPush(hex - 1, hexes); - BattleHex::checkAndPush(hex + ((hex / WN) % 2 ? WN - 1 : WN), hexes); - BattleHex::checkAndPush(hex + ((hex / WN) % 2 ? WN : WN + 1), hexes); - BattleHex::checkAndPush(hex + ((hex / WN) % 2 ? WN + 1 : WN + 2), hexes); - } - return hexes; - } - else - { - return hex.neighbouringTiles(); - } -} - BattleHex::EDir CStack::destShiftDir() const { if(doubleWide()) @@ -595,7 +171,8 @@ const CGHeroInstance * CStack::getMyHero() const std::string CStack::nodeName() const { std::ostringstream oss; - oss << "Battle stack [" << ID << "]: " << health.getCount() << " creatures of "; + oss << owner.getStr(); + oss << " battle stack [" << ID << "]: " << getCount() << " of "; if(type) oss << type->namePl; else @@ -607,156 +184,95 @@ std::string CStack::nodeName() const return oss.str(); } -CHealth CStack::healthAfterAttacked(int32_t & damage) const +void CStack::prepareAttacked(BattleStackAttacked & bsa, vstd::RNG & rand) const { - return healthAfterAttacked(damage, health); + auto newState = acquireState(); + prepareAttacked(bsa, rand, newState); } -CHealth CStack::healthAfterAttacked(int32_t & damage, const CHealth & customHealth) const +void CStack::prepareAttacked(BattleStackAttacked & bsa, vstd::RNG & rand, std::shared_ptr customState) { - CHealth res = customHealth; + auto initialCount = customState->getCount(); - if(isClone()) - { - // block ability should not kill clone (0 damage) - if(damage > 0) - { - damage = 1;//??? what should be actual damage against clone? - res.reset(); - } - } - else - { - res.damage(damage); - } + customState->damage(bsa.damageAmount); - return res; -} + bsa.killedAmount = initialCount - customState->getCount(); -CHealth CStack::healthAfterHealed(int32_t & toHeal, EHealLevel level, EHealPower power) const -{ - CHealth res = health; - - if(level == EHealLevel::HEAL && power == EHealPower::ONE_BATTLE) - logGlobal->error("Heal for one battle does not make sense", nodeName(), toHeal); - else if(isClone()) - logGlobal->error("Attempt to heal clone: %s for %d HP", nodeName(), toHeal); - else - res.heal(toHeal, level, power); - - return res; -} - -void CStack::prepareAttacked(BattleStackAttacked & bsa, CRandomGenerator & rand) const -{ - prepareAttacked(bsa, rand, health); -} - -void CStack::prepareAttacked(BattleStackAttacked & bsa, CRandomGenerator & rand, const CHealth & customHealth) const -{ - CHealth afterAttack = healthAfterAttacked(bsa.damageAmount, customHealth); - - bsa.killedAmount = customHealth.getCount() - afterAttack.getCount(); - afterAttack.toInfo(bsa.newHealth); - bsa.newHealth.stackId = ID; - bsa.newHealth.delta = -bsa.damageAmount; - - if(afterAttack.available() <= 0 && isClone()) + if(!customState->alive() && customState->isClone()) { bsa.flags |= BattleStackAttacked::CLONE_KILLED; - return; // no rebirth I believe } - - if(afterAttack.available() <= 0) //stack killed + else if(!customState->alive()) //stack killed { bsa.flags |= BattleStackAttacked::KILLED; - int resurrectFactor = valOfBonuses(Bonus::REBIRTH); - if(resurrectFactor > 0 && canCast()) //there must be casts left - { - int resurrectedStackCount = baseAmount * resurrectFactor / 100; + auto resurrectValue = customState->valOfBonuses(Bonus::REBIRTH); - // last stack has proportional chance to rebirth - //FIXME: diff is always 0 - auto diff = baseAmount * resurrectFactor / 100.0 - resurrectedStackCount; - if(diff > rand.nextDouble(0, 0.99)) + if(resurrectValue > 0 && customState->canCast()) //there must be casts left + { + double resurrectFactor = resurrectValue / 100; + + auto baseAmount = customState->unitBaseAmount(); + + double resurrectedRaw = baseAmount * resurrectFactor; + + int32_t resurrectedCount = static_cast(floor(resurrectedRaw)); + + int32_t resurrectedAdd = static_cast(baseAmount - (resurrectedCount/resurrectFactor)); + + auto rangeGen = rand.getInt64Range(0, 99); + + for(int32_t i = 0; i < resurrectedAdd; i++) { - resurrectedStackCount += 1; + if(resurrectValue > rangeGen()) + resurrectedCount += 1; } - if(hasBonusOfType(Bonus::REBIRTH, 1)) + if(customState->hasBonusOfType(Bonus::REBIRTH, 1)) { // resurrect at least one Sacred Phoenix - vstd::amax(resurrectedStackCount, 1); + vstd::amax(resurrectedCount, 1); } - if(resurrectedStackCount > 0) + if(resurrectedCount > 0) { + customState->casts.use(); bsa.flags |= BattleStackAttacked::REBIRTH; - //TODO: use StackHealedOrResurrected - bsa.newHealth.firstHPleft = MaxHealth(); - bsa.newHealth.fullUnits = resurrectedStackCount - 1; - bsa.newHealth.resurrected = 0; //TODO: add one-battle rebirth? + int64_t toHeal = customState->MaxHealth() * resurrectedCount; + //TODO: add one-battle rebirth? + customState->heal(toHeal, EHealLevel::RESURRECT, EHealPower::PERMANENT); + customState->counterAttacks.use(customState->counterAttacks.available()); } } } + + customState->save(bsa.newState.data); + bsa.newState.healthDelta = -bsa.damageAmount; + bsa.newState.id = customState->unitId(); + bsa.newState.operation = UnitChanges::EOperation::RESET_STATE; } -bool CStack::isMeleeAttackPossible(const CStack * attacker, const CStack * defender, BattleHex attackerPos, BattleHex defenderPos) +bool CStack::isMeleeAttackPossible(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPos, BattleHex defenderPos) { if(!attackerPos.isValid()) - attackerPos = attacker->position; + attackerPos = attacker->getPosition(); if(!defenderPos.isValid()) - defenderPos = defender->position; + defenderPos = defender->getPosition(); return (BattleHex::mutualPosition(attackerPos, defenderPos) >= 0)//front <=> front || (attacker->doubleWide()//back <=> front - && BattleHex::mutualPosition(attackerPos + (attacker->side == BattleSide::ATTACKER ? -1 : 1), defenderPos) >= 0) + && BattleHex::mutualPosition(attackerPos + (attacker->unitSide() == BattleSide::ATTACKER ? -1 : 1), defenderPos) >= 0) || (defender->doubleWide()//front <=> back - && BattleHex::mutualPosition(attackerPos, defenderPos + (defender->side == BattleSide::ATTACKER ? -1 : 1)) >= 0) + && BattleHex::mutualPosition(attackerPos, defenderPos + (defender->unitSide() == BattleSide::ATTACKER ? -1 : 1)) >= 0) || (defender->doubleWide() && attacker->doubleWide()//back <=> back - && BattleHex::mutualPosition(attackerPos + (attacker->side == BattleSide::ATTACKER ? -1 : 1), defenderPos + (defender->side == BattleSide::ATTACKER ? -1 : 1)) >= 0); + && BattleHex::mutualPosition(attackerPos + (attacker->unitSide() == BattleSide::ATTACKER ? -1 : 1), defenderPos + (defender->unitSide() == BattleSide::ATTACKER ? -1 : 1)) >= 0); } -bool CStack::ableToRetaliate() const -{ - return alive() - && (counterAttacks.canUse() || hasBonusOfType(Bonus::UNLIMITED_RETALIATIONS)) - && !hasBonusOfType(Bonus::SIEGE_WEAPON) - && !hasBonusOfType(Bonus::HYPNOTIZED) - && !hasBonusOfType(Bonus::NO_RETALIATION); -} - std::string CStack::getName() const { - return (health.getCount() == 1) ? type->nameSing : type->namePl; //War machines can't use base -} - -bool CStack::isValidTarget(bool allowDead) const -{ - return (alive() || (allowDead && isDead())) && position.isValid() && !isTurret(); -} - -bool CStack::isDead() const -{ - return !alive() && !isGhost(); -} - -bool CStack::isClone() const -{ - return vstd::contains(state, EBattleStackState::CLONED); -} - -bool CStack::isGhost() const -{ - return vstd::contains(state, EBattleStackState::GHOST); -} - -bool CStack::isTurret() const -{ - return type->idNumber == CreatureID::ARROW_TOWERS; + return (getCount() == 1) ? type->nameSing : type->namePl; //War machines can't use base } bool CStack::canBeHealed() const @@ -766,75 +282,9 @@ bool CStack::canBeHealed() const && !hasBonusOfType(Bonus::SIEGE_WEAPON); } -void CStack::makeGhost() +const CCreature * CStack::unitType() const { - state.erase(EBattleStackState::ALIVE); - state.insert(EBattleStackState::GHOST_PENDING); -} - -bool CStack::alive() const //determines if stack is alive -{ - return vstd::contains(state, EBattleStackState::ALIVE); -} - -ui8 CStack::getSpellSchoolLevel(const CSpell * spell, int * outSelectedSchool) const -{ - int skill = valOfBonuses(Selector::typeSubtype(Bonus::SPELLCASTER, spell->id)); - vstd::abetween(skill, 0, 3); - return skill; -} - -ui32 CStack::getSpellBonus(const CSpell * spell, ui32 base, const CStack * affectedStack) const -{ - //stacks does not have sorcery-like bonuses (yet?) - return base; -} - -int CStack::getEffectLevel(const CSpell * spell) const -{ - return getSpellSchoolLevel(spell); -} - -int CStack::getEffectPower(const CSpell * spell) const -{ - return valOfBonuses(Bonus::CREATURE_SPELL_POWER) * health.getCount() / 100; -} - -int CStack::getEnchantPower(const CSpell * spell) const -{ - int res = valOfBonuses(Bonus::CREATURE_ENCHANT_POWER); - if(res <= 0) - res = 3;//default for creatures - return res; -} - -int CStack::getEffectValue(const CSpell * spell) const -{ - return valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, spell->id.toEnum()) * health.getCount(); -} - -const PlayerColor CStack::getOwner() const -{ - return battle->battleGetOwner(this); -} - -void CStack::getCasterName(MetaString & text) const -{ - //always plural name in case of spell cast. - addNameReplacement(text, true); -} - -void CStack::getCastDescription(const CSpell * spell, const std::vector & attacked, MetaString & text) const -{ - text.addTxt(MetaString::GENERAL_TXT, 565);//The %s casts %s - //todo: use text 566 for single creature - getCasterName(text); - text.addReplacement(MetaString::SPELL_NAME, spell->id.toEnum()); -} - -int32_t CStack::unitMaxHealth() const -{ - return MaxHealth(); + return type; } int32_t CStack::unitBaseAmount() const @@ -842,46 +292,60 @@ int32_t CStack::unitBaseAmount() const return baseAmount; } -void CStack::addText(MetaString & text, ui8 type, int32_t serial, const boost::logic::tribool & plural) const +bool CStack::unitHasAmmoCart(const battle::Unit * unit) const { - if(boost::logic::indeterminate(plural)) - serial = VLC->generaltexth->pluralText(serial, health.getCount()); - else if(plural) - serial = VLC->generaltexth->pluralText(serial, 2); - else - serial = VLC->generaltexth->pluralText(serial, 1); + bool hasAmmoCart = false; - text.addTxt(type, serial); + for(const CStack * st : battle->stacks) + { + if(battle->battleMatchOwner(st, unit, true) && st->getCreature()->idNumber == CreatureID::AMMO_CART && st->alive()) + { + hasAmmoCart = true; + break; + } + } + return hasAmmoCart; } -void CStack::addNameReplacement(MetaString & text, const boost::logic::tribool & plural) const +PlayerColor CStack::unitEffectiveOwner(const battle::Unit * unit) const { - if(boost::logic::indeterminate(plural)) - text.addCreReplacement(type->idNumber, health.getCount()); - else if(plural) - text.addReplacement(MetaString::CRE_PL_NAMES, type->idNumber.num); - else - text.addReplacement(MetaString::CRE_SING_NAMES, type->idNumber.num); + return battle->battleGetOwner(unit); } -std::string CStack::formatGeneralMessage(const int32_t baseTextId) const +uint32_t CStack::unitId() const { - const int32_t textId = VLC->generaltexth->pluralText(baseTextId, health.getCount()); - - MetaString text; - text.addTxt(MetaString::GENERAL_TXT, textId); - text.addCreReplacement(type->idNumber, health.getCount()); - - return text.toString(); + return ID; } -void CStack::setHealth(const CHealthInfo & value) +ui8 CStack::unitSide() const { - health.reset(); - health.fromInfo(value); + return side; } -void CStack::setHealth(const CHealth & value) +PlayerColor CStack::unitOwner() const { - health = value; + return owner; +} + +SlotID CStack::unitSlot() const +{ + return slot; +} + +std::string CStack::getDescription() const +{ + return nodeName(); +} + +void CStack::spendMana(const spells::PacketSender * server, const int spellCost) const +{ + if(spellCost != 1) + logGlobal->warn("Unexpected spell cost %d for creature", spellCost); + + BattleSetStackProperty ssp; + ssp.stackID = unitId(); + ssp.which = BattleSetStackProperty::CASTS; + ssp.val = -spellCost; + ssp.absolute = false; + server->sendAndApply(&ssp); } diff --git a/lib/CStack.h b/lib/CStack.h index cb38c2031..7b6a38270 100644 --- a/lib/CStack.h +++ b/lib/CStack.h @@ -9,179 +9,42 @@ */ #pragma once +#include "JsonNode.h" +#include "HeroBonus.h" +#include "CCreatureHandler.h" //todo: remove #include "battle/BattleHex.h" -#include "CCreatureHandler.h" #include "mapObjects/CGHeroInstance.h" // for commander serialization +#include "battle/CUnitState.h" + struct BattleStackAttacked; -struct BattleInfo; -class CStack; -class CHealthInfo; +class BattleInfo; -template -class DLL_LINKAGE CStackResource -{ -public: - CStackResource(const CStack * Owner): - owner(Owner) - { - reset(); - } - - virtual void reset() - { - used = 0; - }; - -protected: - const CStack * owner; - Quantity used; -}; - -class DLL_LINKAGE CAmmo : public CStackResource -{ -public: - CAmmo(const CStack * Owner, CSelector totalSelector); - - int32_t available() const; - bool canUse(int32_t amount = 1) const; - virtual void reset() override; - virtual int32_t total() const; - virtual void use(int32_t amount = 1); - - template void serialize(Handler & h, const int version) - { - if(!h.saving) - reset(); - h & used; - } -protected: - CBonusProxy totalProxy; -}; - -class DLL_LINKAGE CShots : public CAmmo -{ -public: - CShots(const CStack * Owner); - void use(int32_t amount = 1) override; -}; - -class DLL_LINKAGE CCasts : public CAmmo -{ -public: - CCasts(const CStack * Owner); -}; - -class DLL_LINKAGE CRetaliations : public CAmmo -{ -public: - CRetaliations(const CStack * Owner); - int32_t total() const override; - void reset() override; -private: - mutable int32_t totalCache; -}; - -class DLL_LINKAGE IUnitHealthInfo -{ -public: - virtual int32_t unitMaxHealth() const = 0; - virtual int32_t unitBaseAmount() const = 0; -}; - -class DLL_LINKAGE CHealth -{ -public: - CHealth(const IUnitHealthInfo * Owner); - CHealth(const CHealth & other); - - void init(); - void reset(); - - void damage(int32_t & amount); - void heal(int32_t & amount, EHealLevel level, EHealPower power); - - int32_t getCount() const; - int32_t getFirstHPleft() const; - int32_t getResurrected() const; - - int64_t available() const; - int64_t total() const; - - void toInfo(CHealthInfo & info) const; - void fromInfo(const CHealthInfo & info); - - void takeResurrected(); - - template void serialize(Handler & h, const int version) - { - if(!h.saving) - reset(); - h & firstHPleft; - h & fullUnits; - h & resurrected; - } -private: - void addResurrected(int32_t amount); - void setFromTotal(const int64_t totalHealth); - const IUnitHealthInfo * owner; - - int32_t firstHPleft; - int32_t fullUnits; - int32_t resurrected; -}; - -class DLL_LINKAGE CStack : public CBonusSystemNode, public ISpellCaster, public IUnitHealthInfo +class DLL_LINKAGE CStack : public CBonusSystemNode, public battle::CUnitState, public battle::IUnitEnvironment { public: const CStackInstance * base; //garrison slot from which stack originates (nullptr for war machines, summoned cres, etc) ui32 ID; //unique ID of stack - ui32 baseAmount; const CCreature * type; + ui32 baseAmount; PlayerColor owner; //owner - player color (255 for neutrals) SlotID slot; //slot - position in garrison (may be 255 for neutrals/called creatures) ui8 side; - BattleHex position; //position on battlefield; -2 - keep, -3 - lower tower, -4 - upper tower - - CRetaliations counterAttacks; - CShots shots; - CCasts casts; - CHealth health; - - ///id of alive clone of this stack clone if any - si32 cloneID; - std::set state; + BattleHex initialPosition; //position on battlefield; -2 - keep, -3 - lower tower, -4 - upper tower CStack(const CStackInstance * base, PlayerColor O, int I, ui8 Side, SlotID S); CStack(const CStackBasicDescriptor * stack, PlayerColor O, int I, ui8 Side, SlotID S = SlotID(255)); CStack(); ~CStack(); - int32_t getKilled() const; - int32_t getCount() const; - int32_t getFirstHPleft() const; - const CCreature * getCreature() const; + const CCreature * getCreature() const; //deprecated std::string nodeName() const override; - void init(); //set initial (invalid) values void localInit(BattleInfo * battleInfo); std::string getName() const; //plural or singular - bool willMove(int turn = 0) const; //if stack has remaining move this turn - bool ableToRetaliate() const; //if stack can retaliate after attacked - - bool moved(int turn = 0) const; //if stack was already moved this turn - bool waited(int turn = 0) const; - - bool canCast() const; - bool isCaster() const; - - bool canMove(int turn = 0) const; //if stack can move - - bool canShoot() const; - bool isShooter() const; bool canBeHealed() const; //for first aid tent - only harmed stacks that are not war machines @@ -190,68 +53,32 @@ public: std::vector activeSpells() const; //returns vector of active spell IDs sorted by time of cast const CGHeroInstance * getMyHero() const; //if stack belongs to hero (directly or was by him summoned) returns hero, nullptr otherwise - static bool isMeleeAttackPossible(const CStack * attacker, const CStack * defender, BattleHex attackerPos = BattleHex::INVALID, BattleHex defenderPos = BattleHex::INVALID); - - bool doubleWide() const; - BattleHex occupiedHex() const; //returns number of occupied hex (not the position) if stack is double wide; otherwise -1 - BattleHex occupiedHex(BattleHex assumedPos) const; //returns number of occupied hex (not the position) if stack is double wide and would stand on assumedPos; otherwise -1 - std::vector getHexes() const; //up to two occupied hexes, starting from front - std::vector getHexes(BattleHex assumedPos) const; //up to two occupied hexes, starting from front - static std::vector getHexes(BattleHex assumedPos, bool twoHex, ui8 side); //up to two occupied hexes, starting from front - bool coversPos(BattleHex position) const; //checks also if unit is double-wide - std::vector getSurroundingHexes(BattleHex attackerPos = BattleHex::INVALID) const; // get six or 8 surrounding hexes depending on creature size + static bool isMeleeAttackPossible(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPos = BattleHex::INVALID, BattleHex defenderPos = BattleHex::INVALID); BattleHex::EDir destShiftDir() const; - CHealth healthAfterAttacked(int32_t & damage) const; - CHealth healthAfterAttacked(int32_t & damage, const CHealth & customHealth) const; + void prepareAttacked(BattleStackAttacked & bsa, vstd::RNG & rand) const; //requires bsa.damageAmout filled + static void prepareAttacked(BattleStackAttacked & bsa, vstd::RNG & rand, std::shared_ptr customState); //requires bsa.damageAmout filled - CHealth healthAfterHealed(int32_t & toHeal, EHealLevel level, EHealPower power) const; - - void prepareAttacked(BattleStackAttacked & bsa, CRandomGenerator & rand) const; //requires bsa.damageAmout filled - void prepareAttacked(BattleStackAttacked & bsa, CRandomGenerator & rand, const CHealth & customHealth) const; //requires bsa.damageAmout filled - - ///ISpellCaster - - ui8 getSpellSchoolLevel(const CSpell * spell, int * outSelectedSchool = nullptr) const override; - ui32 getSpellBonus(const CSpell * spell, ui32 base, const CStack * affectedStack) const override; - - ///default spell school level for effect calculation - int getEffectLevel(const CSpell * spell) const override; - - ///default spell-power for damage/heal calculation - int getEffectPower(const CSpell * spell) const override; - - ///default spell-power for timed effects duration - int getEnchantPower(const CSpell * spell) const override; - - ///damage/heal override(ignores spell configuration, effect level and effect power) - int getEffectValue(const CSpell * spell) const override; - - const PlayerColor getOwner() const override; - void getCasterName(MetaString & text) const override; - void getCastDescription(const CSpell * spell, const std::vector & attacked, MetaString & text) const override; - - ///IUnitHealthInfo - - int32_t unitMaxHealth() const override; + const CCreature * unitType() const override; int32_t unitBaseAmount() const override; - ///MetaStrings + uint32_t unitId() const override; + ui8 unitSide() const override; + PlayerColor unitOwner() const override; + SlotID unitSlot() const override; - void addText(MetaString & text, ui8 type, int32_t serial, const boost::logic::tribool & plural = boost::logic::indeterminate) const; - void addNameReplacement(MetaString & text, const boost::logic::tribool & plural = boost::logic::indeterminate) const; - std::string formatGeneralMessage(const int32_t baseTextId) const; + std::string getDescription() const override; - ///Non const API for NetPacks + bool unitHasAmmoCart(const battle::Unit * unit) const override; + PlayerColor unitEffectiveOwner(const battle::Unit * unit) const override; - ///stack will be ghost in next battle state update - void makeGhost(); - void setHealth(const CHealthInfo & value); - void setHealth(const CHealth & value); + void spendMana(const spells::PacketSender * server, const int spellCost) const override; template void serialize(Handler & h, const int version) { + //this assumes that stack objects is newly created + //stackState is not serialized here assert(isIndependentNode()); h & static_cast(*this); h & type; @@ -260,12 +87,7 @@ public: h & owner; h & slot; h & side; - h & position; - h & state; - h & shots; - h & casts; - h & counterAttacks; - h & health; + h & initialPosition; const CArmedInstance * army = (base ? base->armyObj : nullptr); SlotID extSlot = (base ? base->armyObj->findStack(base) : SlotID()); @@ -279,6 +101,7 @@ public: { h & army; h & extSlot; + if(extSlot == SlotID::COMMANDER_SLOT_PLACEHOLDER) { auto hero = dynamic_cast(army); @@ -300,17 +123,8 @@ public: base = &army->getStack(extSlot); } } - } - bool alive() const; - bool isClone() const; - bool isDead() const; - bool isGhost() const; //determines if stack was removed - bool isValidTarget(bool allowDead = false) const; //non-turret non-ghost stacks (can be attacked or be object of magic effect) - bool isTurret() const; - - friend class CShots; //for BattleInfo access private: const BattleInfo * battle; //do not serialize }; diff --git a/lib/CThreadHelper.cpp b/lib/CThreadHelper.cpp index ce32a2048..9dfffadcc 100644 --- a/lib/CThreadHelper.cpp +++ b/lib/CThreadHelper.cpp @@ -25,13 +25,11 @@ CThreadHelper::CThreadHelper(std::vector > *Tasks, int Thr void CThreadHelper::run() { boost::thread_group grupa; - std::vector thr; for(int i=0;iarth->artifacts.at(*this); } +si32 ArtifactID::decode(const std::string & identifier) +{ + auto rawId = VLC->modh->identifiers.getIdentifier("core", "artifact", identifier); + if(rawId) + return rawId.get(); + else + return -1; +} + +std::string ArtifactID::encode(const si32 index) +{ + return VLC->arth->artifacts.at(index)->identifier; +} + const CCreature * CreatureID::toCreature() const { return VLC->creh->creatures.at(*this); } +si32 CreatureID::decode(const std::string & identifier) +{ + auto rawId = VLC->modh->identifiers.getIdentifier("core", "creature", identifier); + if(rawId) + return rawId.get(); + else + return -1; +} + +std::string CreatureID::encode(const si32 index) +{ + return VLC->creh->creatures.at(index)->identifier; +} + const CSpell * SpellID::toSpell() const { if(num < 0 || num >= VLC->spellh->objects.size()) @@ -66,6 +95,20 @@ const CSpell * SpellID::toSpell() const return VLC->spellh->objects[*this]; } +si32 SpellID::decode(const std::string & identifier) +{ + auto rawId = VLC->modh->identifiers.getIdentifier("core", "spell", identifier); + if(rawId) + return rawId.get(); + else + return -1; +} + +std::string SpellID::encode(const si32 index) +{ + return VLC->spellh->objects.at(index)->identifier; +} + const CSkill * SecondarySkill::toSkill() const { return VLC->skillh->objects.at(*this); @@ -110,26 +153,26 @@ std::string PlayerColor::getStrCap(bool L10n) const return ret; } -std::ostream & operator<<(std::ostream & os, const Battle::ActionType actionType) +std::ostream & operator<<(std::ostream & os, const EActionType actionType) { - static const std::map actionTypeToString = + static const std::map actionTypeToString = { - {Battle::END_TACTIC_PHASE, "End tactic phase"}, - {Battle::INVALID, "Invalid"}, - {Battle::NO_ACTION, "No action"}, - {Battle::HERO_SPELL, "Hero spell"}, - {Battle::WALK, "Walk"}, - {Battle::DEFEND, "Defend"}, - {Battle::RETREAT, "Retreat"}, - {Battle::SURRENDER, "Surrender"}, - {Battle::WALK_AND_ATTACK, "Walk and attack"}, - {Battle::SHOOT, "Shoot"}, - {Battle::WAIT, "Wait"}, - {Battle::CATAPULT, "Catapult"}, - {Battle::MONSTER_SPELL, "Monster spell"}, - {Battle::BAD_MORALE, "Bad morale"}, - {Battle::STACK_HEAL, "Stack heal"}, - {Battle::DAEMON_SUMMONING, "Daemon summoning"} + {EActionType::END_TACTIC_PHASE, "End tactic phase"}, + {EActionType::INVALID, "Invalid"}, + {EActionType::NO_ACTION, "No action"}, + {EActionType::HERO_SPELL, "Hero spell"}, + {EActionType::WALK, "Walk"}, + {EActionType::DEFEND, "Defend"}, + {EActionType::RETREAT, "Retreat"}, + {EActionType::SURRENDER, "Surrender"}, + {EActionType::WALK_AND_ATTACK, "Walk and attack"}, + {EActionType::SHOOT, "Shoot"}, + {EActionType::WAIT, "Wait"}, + {EActionType::CATAPULT, "Catapult"}, + {EActionType::MONSTER_SPELL, "Monster spell"}, + {EActionType::BAD_MORALE, "Bad morale"}, + {EActionType::STACK_HEAL, "Stack heal"}, + {EActionType::DAEMON_SUMMONING, "Daemon summoning"} }; auto it = actionTypeToString.find(actionType); diff --git a/lib/GameConstants.h b/lib/GameConstants.h index 8d76f3724..bf63ed05d 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -15,10 +15,6 @@ namespace GameConstants { DLL_LINKAGE extern const std::string VCMI_VERSION; - const int BFIELD_WIDTH = 17; - const int BFIELD_HEIGHT = 11; - const int BFIELD_SIZE = BFIELD_WIDTH * BFIELD_HEIGHT; - const int PUZZLE_MAP_PIECES = 48; const int MAX_HEROES_PER_PLAYER = 8; @@ -443,27 +439,15 @@ namespace ESpellCastProblem { enum ESpellCastProblem { - OK, NO_HERO_TO_CAST_SPELL, ALREADY_CASTED_THIS_TURN, NO_SPELLBOOK, ANOTHER_ELEMENTAL_SUMMONED, + OK, NO_HERO_TO_CAST_SPELL, CASTS_PER_TURN_LIMIT, NO_SPELLBOOK, HERO_DOESNT_KNOW_SPELL, NOT_ENOUGH_MANA, ADVMAP_SPELL_INSTEAD_OF_BATTLE_SPELL, - SECOND_HEROS_SPELL_IMMUNITY, SPELL_LEVEL_LIMIT_EXCEEDED, NO_SPELLS_TO_DISPEL, + SPELL_LEVEL_LIMIT_EXCEEDED, NO_SPELLS_TO_DISPEL, NO_APPROPRIATE_TARGET, STACK_IMMUNE_TO_SPELL, WRONG_SPELL_TARGET, ONGOING_TACTIC_PHASE, MAGIC_IS_BLOCKED, //For Orb of Inhibition and similar - no casting at all - NOT_DECIDED, INVALID }; } -namespace ECastingMode -{ - enum ECastingMode - { - HERO_CASTING, AFTER_ATTACK_CASTING, //also includes cast before attack - MAGIC_MIRROR, CREATURE_ACTIVE_CASTING, ENCHANTER_CASTING, - SPELL_LIKE_ATTACK, - PASSIVE_CASTING//f.e. opening battle spells - }; -} - namespace EMarketMode { enum EMarketMode @@ -474,26 +458,6 @@ namespace EMarketMode }; } -namespace EBattleStackState -{ - enum EBattleStackState - { - ALIVE = 180, - SUMMONED, CLONED, - GHOST, //stack was removed from battlefield - HAD_MORALE, - WAITING, - MOVED, - DEFENDING, - FEAR, - //remember to drain mana only once per turn - DRAINED_MANA, - //only for defending animation - DEFENDING_ANIM, - GHOST_PENDING// stack will become GHOST in next battle state update - }; -} - namespace ECommander { enum SecondarySkills {ATTACK, DEFENSE, HEALTH, DAMAGE, SPEED, SPELL_POWER, CASTS, RESISTANCE}; @@ -518,8 +482,6 @@ namespace EWallState DESTROYED, DAMAGED, INTACT - - }; } @@ -778,30 +740,27 @@ namespace Date }; } -namespace Battle +enum class EActionType : int32_t { - enum ActionType - { - CANCEL = -3, - END_TACTIC_PHASE = -2, - INVALID = -1, - NO_ACTION = 0, - HERO_SPELL, - WALK, DEFEND, - RETREAT, - SURRENDER, - WALK_AND_ATTACK, - SHOOT, - WAIT, - CATAPULT, - MONSTER_SPELL, - BAD_MORALE, - STACK_HEAL, - DAEMON_SUMMONING - }; -} + CANCEL = -3, + END_TACTIC_PHASE = -2, + INVALID = -1, + NO_ACTION = 0, + HERO_SPELL, + WALK, DEFEND, + RETREAT, + SURRENDER, + WALK_AND_ATTACK, + SHOOT, + WAIT, + CATAPULT, + MONSTER_SPELL, + BAD_MORALE, + STACK_HEAL, + DAEMON_SUMMONING +}; -std::ostream & operator<<(std::ostream & os, const Battle::ActionType actionType); +DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const EActionType actionType); class DLL_LINKAGE ETerrainType { @@ -969,6 +928,10 @@ public: DLL_LINKAGE const CArtifact * toArtifact() const; + ///json serialization helpers + static si32 decode(const std::string & identifier); + static std::string encode(const si32 index); + ID_LIKE_CLASS_COMMON(ArtifactID, EArtifactID) EArtifactID num; @@ -1017,6 +980,10 @@ public: ID_LIKE_CLASS_COMMON(CreatureID, ECreatureID) ECreatureID num; + + ///json serialization helpers + static si32 decode(const std::string & identifier); + static std::string encode(const si32 index); }; ID_LIKE_OPERATORS(CreatureID, CreatureID::ECreatureID) @@ -1060,6 +1027,10 @@ public: ID_LIKE_CLASS_COMMON(SpellID, ESpellID) ESpellID num; + + ///json serialization helpers + static si32 decode(const std::string & identifier); + static std::string encode(const si32 index); }; ID_LIKE_OPERATORS(SpellID, SpellID::ESpellID) @@ -1108,7 +1079,7 @@ enum class EHealPower : ui8 // Typedef declarations typedef ui8 TFaction; typedef si64 TExpType; -typedef std::pair TDmgRange; +typedef std::pair TDmgRange; typedef si32 TBonusSubtype; typedef si32 TQuantity; diff --git a/lib/HeroBonus.cpp b/lib/HeroBonus.cpp index df749ad8c..dacf655a4 100644 --- a/lib/HeroBonus.cpp +++ b/lib/HeroBonus.cpp @@ -81,20 +81,59 @@ const std::map bonusPropagatorMap = }; //untested ///CBonusProxy -CBonusProxy::CBonusProxy(const IBonusBearer * Target, CSelector Selector): - cachedLast(0), target(Target), selector(Selector), data() +CBonusProxy::CBonusProxy(const IBonusBearer * Target, CSelector Selector) + : cachedLast(0), + target(Target), + selector(Selector), + data() { } +CBonusProxy::CBonusProxy(const CBonusProxy & other) + : cachedLast(other.cachedLast), + target(other.target), + selector(other.selector), + data(other.data) +{ + +} + +CBonusProxy::CBonusProxy(CBonusProxy && other) + : cachedLast(0), + target(other.target), + selector(), + data() +{ + std::swap(cachedLast, other.cachedLast); + std::swap(selector, other.selector); + std::swap(data, other.data); +} + +CBonusProxy & CBonusProxy::operator=(const CBonusProxy & other) +{ + cachedLast = other.cachedLast; + selector = other.selector; + data = other.data; + return *this; +} + +CBonusProxy & CBonusProxy::operator=(CBonusProxy && other) +{ + std::swap(cachedLast, other.cachedLast); + std::swap(selector, other.selector); + std::swap(data, other.data); + return *this; +} + TBonusListPtr CBonusProxy::get() const { - if(CBonusSystemNode::treeChanged != cachedLast || !data) + if(target->getTreeVersion() != cachedLast || !data) { //TODO: support limiters - data = target->getAllBonuses(selector, nullptr); + data = target->getAllBonuses(selector, Selector::all); data->eliminateDuplicates(); - cachedLast = CBonusSystemNode::treeChanged; + cachedLast = target->getTreeVersion(); } return data; } @@ -104,7 +143,7 @@ const BonusList * CBonusProxy::operator->() const return get().get(); } -int CBonusSystemNode::treeChanged = 1; +std::atomic CBonusSystemNode::treeChanged(1); const bool CBonusSystemNode::cachingEnabled = true; BonusList::BonusList(bool BelongsToTree) : belongsToTree(BelongsToTree) @@ -408,48 +447,44 @@ int IBonusBearer::LuckVal() const return vstd::abetween(ret, -3, +3); } -si32 IBonusBearer::Attack() const -{ - si32 ret = valOfBonuses(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK); - - if (double frenzyPower = valOfBonuses(Bonus::IN_FRENZY)) //frenzy for attacker - { - ret += (frenzyPower/100) * (double)Defense(false); - } - vstd::amax(ret, 0); - - return ret; -} - -si32 IBonusBearer::Defense(bool withFrenzy) const -{ - si32 ret = valOfBonuses(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE); - - if(withFrenzy && hasBonusOfType(Bonus::IN_FRENZY)) //frenzy for defender - { - return 0; - } - vstd::amax(ret, 0); - - return ret; -} - ui32 IBonusBearer::MaxHealth() const { - return std::max(1, valOfBonuses(Bonus::STACK_HEALTH)); //never 0 + const std::string cachingStr = "type_STACK_HEALTH"; + static const auto selector = Selector::type(Bonus::STACK_HEALTH); + auto value = valOfBonuses(selector, cachingStr); + return std::max(1, value); //never 0 } -ui32 IBonusBearer::getMinDamage() const +int IBonusBearer::getAttack(bool ranged) const { - std::stringstream cachingStr; - cachingStr << "type_" << Bonus::CREATURE_DAMAGE << "s_0Otype_" << Bonus::CREATURE_DAMAGE << "s_1"; - return valOfBonuses(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 0).Or(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 1)), cachingStr.str()); + const std::string cachingStr = "type_PRIMARY_SKILLs_ATTACK"; + + static const auto selector = Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK); + + return getBonuses(selector, nullptr, cachingStr)->totalValue(); } -ui32 IBonusBearer::getMaxDamage() const + +int IBonusBearer::getDefence(bool ranged) const { - std::stringstream cachingStr; - cachingStr << "type_" << Bonus::CREATURE_DAMAGE << "s_0Otype_" << Bonus::CREATURE_DAMAGE << "s_2"; - return valOfBonuses(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 0).Or(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 2)), cachingStr.str()); + const std::string cachingStr = "type_PRIMARY_SKILLs_DEFENSE"; + + static const auto selector = Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE); + + return getBonuses(selector, nullptr, cachingStr)->totalValue(); +} + +int IBonusBearer::getMinDamage(bool ranged) const +{ + const std::string cachingStr = "type_CREATURE_DAMAGEs_0Otype_CREATURE_DAMAGEs_1"; + static const auto selector = Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 0).Or(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 1)); + return valOfBonuses(selector, cachingStr); +} + +int IBonusBearer::getMaxDamage(bool ranged) const +{ + const std::string cachingStr = "type_CREATURE_DAMAGEs_0Otype_CREATURE_DAMAGEs_2"; + static const auto selector = Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 0).Or(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 2)); + return valOfBonuses(selector, cachingStr); } si32 IBonusBearer::manaLimit() const @@ -461,13 +496,7 @@ si32 IBonusBearer::manaLimit() const int IBonusBearer::getPrimSkillLevel(PrimarySkill::PrimarySkill id) const { - int ret = 0; - if(id == PrimarySkill::ATTACK) - ret = Attack(); - else if(id == PrimarySkill::DEFENSE) - ret = Defense(); - else - ret = valOfBonuses(Bonus::PRIMARY_SKILL, id); + int ret = valOfBonuses(Bonus::PRIMARY_SKILL, id); vstd::amax(ret, id/2); //minimal value is 0 for attack and defense and 1 for spell power and knowledge return ret; @@ -478,7 +507,7 @@ si32 IBonusBearer::magicResistance() const return valOfBonuses(Bonus::MAGIC_RESISTANCE); } -ui32 IBonusBearer::Speed(int turn, bool useBind ) const +ui32 IBonusBearer::Speed(int turn, bool useBind) const { //war machines cannot move if(hasBonus(Selector::type(Bonus::SIEGE_WEAPON).And(Selector::turns(turn)))) @@ -505,8 +534,8 @@ bool IBonusBearer::isLiving() const //TODO: theoreticaly there exists "LIVING" b const std::shared_ptr IBonusBearer::getBonus(const CSelector &selector) const { - auto bonuses = getAllBonuses(Selector::all, Selector::all); - return bonuses->getFirst(selector); + auto bonuses = getAllBonuses(selector, Selector::all); + return bonuses->getFirst(Selector::all); } std::shared_ptr CBonusSystemNode::getBonusLocalFirst(const CSelector &selector) @@ -657,7 +686,19 @@ const TBonusListPtr CBonusSystemNode::getAllBonusesWithoutCaching(const CSelecto return ret; } -CBonusSystemNode::CBonusSystemNode() : bonuses(true), exportedBonuses(true), nodeType(UNKNOWN), cachedLast(0) +CBonusSystemNode::CBonusSystemNode() + : bonuses(true), + exportedBonuses(true), + nodeType(UNKNOWN), + cachedLast(0) +{ +} + +CBonusSystemNode::CBonusSystemNode(ENodeTypes NodeType) + : bonuses(true), + exportedBonuses(true), + nodeType(NodeType), + cachedLast(0) { } @@ -1048,6 +1089,12 @@ void CBonusSystemNode::treeHasChanged() treeChanged++; } +int64_t CBonusSystemNode::getTreeVersion() const +{ + int64_t ret = treeChanged; + return ret << 32; +} + int NBonus::valOf(const CBonusSystemNode *obj, Bonus::BonusType type, int subtype) { if(obj) diff --git a/lib/HeroBonus.h b/lib/HeroBonus.h index 2f76c76ea..9446f1e13 100644 --- a/lib/HeroBonus.h +++ b/lib/HeroBonus.h @@ -64,16 +64,21 @@ public: } }; -class DLL_LINKAGE CBonusProxy : public boost::noncopyable +class DLL_LINKAGE CBonusProxy { public: CBonusProxy(const IBonusBearer * Target, CSelector Selector); + CBonusProxy(const CBonusProxy & other); + CBonusProxy(CBonusProxy && other); + + CBonusProxy & operator=(CBonusProxy && other); + CBonusProxy & operator=(const CBonusProxy & other); TBonusListPtr get() const; const BonusList * operator->() const; private: - mutable int cachedLast; + mutable int64_t cachedLast; const IBonusBearer * target; CSelector selector; mutable TBonusListPtr data; @@ -607,12 +612,15 @@ public: bool hasBonusFrom(Bonus::BonusSource source, ui32 sourceID) const; //various hlp functions for non-trivial values - ui32 getMinDamage() const; //used for stacks and creatures only - ui32 getMaxDamage() const; + //used for stacks and creatures only + + virtual int getMinDamage(bool ranged) const; + virtual int getMaxDamage(bool ranged) const; + virtual int getAttack(bool ranged) const; + virtual int getDefence(bool ranged) const; + int MoraleVal() const; //range [-3, +3] int LuckVal() const; //range [-3, +3] - si32 Attack() const; //get attack of stack with all modificators - si32 Defense(bool withFrenzy = true) const; //get defense of stack with all modificators ui32 MaxHealth() const; //get max HP of stack with all modifiers bool isLiving() const; //non-undead, non-non living or alive virtual si32 magicResistance() const; @@ -620,9 +628,11 @@ public: si32 manaLimit() const; //maximum mana value for this hero (basically 10*knowledge) int getPrimSkillLevel(PrimarySkill::PrimarySkill id) const; + + virtual int64_t getTreeVersion() const = 0; }; -class DLL_LINKAGE CBonusSystemNode : public IBonusBearer, public boost::noncopyable +class DLL_LINKAGE CBonusSystemNode : public virtual IBonusBearer, public boost::noncopyable { public: enum ENodeTypes @@ -642,8 +652,8 @@ private: static const bool cachingEnabled; mutable BonusList cachedBonuses; - mutable int cachedLast; - static int treeChanged; + mutable int64_t cachedLast; + static std::atomic treeChanged; // Setting a value to cachingStr before getting any bonuses caches the result for later requests. // This string needs to be unique, that's why it has to be setted in the following manner: @@ -656,6 +666,7 @@ private: public: explicit CBonusSystemNode(); + explicit CBonusSystemNode(ENodeTypes NodeType); CBonusSystemNode(CBonusSystemNode && other); virtual ~CBonusSystemNode(); @@ -711,6 +722,8 @@ public: static void treeHasChanged(); + int64_t getTreeVersion() const override; + template void serialize(Handler &h, const int version) { // h & bonuses; diff --git a/lib/IBonusTypeHandler.h b/lib/IBonusTypeHandler.h index 609f5abfb..9f6dafc0a 100644 --- a/lib/IBonusTypeHandler.h +++ b/lib/IBonusTypeHandler.h @@ -19,6 +19,6 @@ class IBonusTypeHandler public: virtual ~IBonusTypeHandler(){}; - virtual std::string bonusToString(const std::shared_ptr& bonus, const IBonusBearer *bearer, bool description) const = 0; - virtual std::string bonusToGraphics(const std::shared_ptr& bonus) const = 0; + virtual std::string bonusToString(const std::shared_ptr & bonus, const IBonusBearer * bearer, bool description) const = 0; + virtual std::string bonusToGraphics(const std::shared_ptr & bonus) const = 0; }; diff --git a/lib/IGameEventsReceiver.h b/lib/IGameEventsReceiver.h index c6f228bdd..9fe8fc005 100644 --- a/lib/IGameEventsReceiver.h +++ b/lib/IGameEventsReceiver.h @@ -32,12 +32,11 @@ struct Bonus; class IMarket; struct SetObjectProperty; struct PackageApplied; -struct BattleAction; +class BattleAction; struct BattleStackAttacked; struct BattleResult; struct BattleSpellCast; struct CatapultAttack; -struct BattleStacksRemoved; class CStack; class CCreatureSet; struct BattleAttack; @@ -47,6 +46,10 @@ class CComponent; struct CObstacleInstance; struct CPackForServer; class EVictoryLossCheckResult; +struct MetaString; +struct CustomEffectInfo; +class ObstacleChanges; +class UnitChanges; class DLL_LINKAGE IBattleEventsReceiver { @@ -54,7 +57,7 @@ public: virtual void actionFinished(const BattleAction &action){};//occurs AFTER every action taken by any stack or by the hero virtual void actionStarted(const BattleAction &action){};//occurs BEFORE every action taken by any stack or by the hero virtual void battleAttack(const BattleAttack *ba){}; //called when stack is performing attack - virtual void battleStacksAttacked(const std::vector & bsa){}; //called when stack receives damage (after battleAttack()) + virtual void battleStacksAttacked(const std::vector & bsa, const std::vector & battleLog){}; //called when stack receives damage (after battleAttack()) virtual void battleEnd(const BattleResult *br){}; virtual void battleNewRoundFirst(int round){}; //called at the beginning of each turn before changes are applied; virtual void battleNewRound(int round){}; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn @@ -64,12 +67,9 @@ public: virtual void battleTriggerEffect(const BattleTriggerEffect & bte){}; //called for various one-shot effects virtual void battleStartBefore(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2) {}; //called just before battle start virtual void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side){}; //called by engine when battle starts; side=0 - left, side=1 - right - virtual void battleStacksHealedRes(const std::vector > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom){}; //called when stacks are healed / resurrected first element of pair - stack id, second - healed hp - virtual void battleNewStackAppeared(const CStack * stack){}; //not called at the beginning of a battle or by resurrection; called eg. when elemental is summoned - virtual void battleObstaclesRemoved(const std::set & removedObstacles){}; //called when a certain set of obstacles is removed from batlefield; IDs of them are given + virtual void battleUnitsChanged(const std::vector & units, const std::vector & customEffects, const std::vector & battleLog){}; + virtual void battleObstaclesChanged(const std::vector & obstacles){}; virtual void battleCatapultAttacked(const CatapultAttack & ca){}; //called when catapult makes an attack - virtual void battleStacksRemoved(const BattleStacksRemoved & bsr){}; //called when certain stack is completely removed from battlefield - virtual void battleObstaclePlaced(const CObstacleInstance &obstacle){}; virtual void battleGateStateChanged(const EGateState state){}; }; diff --git a/lib/IHandlerBase.h b/lib/IHandlerBase.h index 024e7968f..c6ed04ced 100644 --- a/lib/IHandlerBase.h +++ b/lib/IHandlerBase.h @@ -11,7 +11,6 @@ #include "../lib/ConstTransitivePtr.h" #include "VCMI_Lib.h" - //#include "CModHandler.h" class JsonNode; @@ -69,8 +68,7 @@ public: void loadObject(std::string scope, std::string name, const JsonNode & data) override { auto type_name = getTypeName(); - auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name)); - object->id = _ObjectID(objects.size()); + auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name), objects.size()); objects.push_back(object); @@ -79,8 +77,7 @@ public: void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override { auto type_name = getTypeName(); - auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name)); - object->id = _ObjectID(index); + auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name), index); assert(objects[index] == nullptr); // ensure that this id was not loaded before objects[index] = object; @@ -101,7 +98,7 @@ public: return objects[raw_id]; } protected: - virtual _Object * loadFromJson(const JsonNode & json, const std::string & identifier) = 0; + virtual _Object * loadFromJson(const JsonNode & json, const std::string & identifier, size_t index) = 0; virtual const std::string getTypeName() const = 0; public: //todo: make private std::vector> objects; diff --git a/lib/JsonNode.h b/lib/JsonNode.h index f55f55606..852c36696 100644 --- a/lib/JsonNode.h +++ b/lib/JsonNode.h @@ -305,7 +305,7 @@ namespace JsonDetail return node.Bool(); } }; -} // namespace JsonDetail +} template Type JsonNode::convertTo() const diff --git a/lib/NetPacks.h b/lib/NetPacks.h index 893efcec1..edb02a327 100644 --- a/lib/NetPacks.h +++ b/lib/NetPacks.h @@ -12,7 +12,6 @@ #include "NetPacksBase.h" #include "battle/BattleAction.h" -#include "JsonNode.h" #include "mapObjects/CGHeroInstance.h" #include "ConstTransitivePtr.h" #include "int3.h" @@ -23,10 +22,6 @@ #include "spells/ViewSpellInt.h" -class CClient; -class CGameState; -class CGameHandler; -class CConnection; class CCampaignState; class CArtifact; class CSelectionScreen; @@ -37,45 +32,7 @@ struct ArtSlotInfo; struct QuestInfo; class CMapInfo; struct StartInfo; - -struct CPackForClient : public CPack -{ - CPackForClient(){}; - - CGameState* GS(CClient *cl); - void applyFirstCl(CClient *cl)//called before applying to gs - {} - void applyCl(CClient *cl)//called after applying to gs - {} -}; - -struct CPackForServer : public CPack -{ - PlayerColor player; - CConnection *c; - CGameState* GS(CGameHandler *gh); - CPackForServer(): - player(PlayerColor::NEUTRAL), - c(nullptr) - { - } - - bool applyGh(CGameHandler *gh) //called after applying to gs - { - logGlobal->error("Should not happen... applying plain CPackForServer"); - return false; - } - -protected: - void throwNotAllowedAction(); - void throwOnWrongOwner(CGameHandler * gh, ObjectInstanceID id); - void throwOnWrongPlayer(CGameHandler * gh, PlayerColor player); - void throwAndCompain(CGameHandler * gh, std::string txt); - bool isPlayerOwns(CGameHandler * gh, ObjectInstanceID id); - -private: - void wrongPlayerMessage(CGameHandler * gh, PlayerColor expectedplayer); -}; +class IBattleState; struct Query : public CPackForClient { @@ -1351,7 +1308,7 @@ struct MapObjectSelectDialog : public Query } }; -struct BattleInfo; +class BattleInfo; struct BattleStart : public CPackForClient { BattleStart() @@ -1440,12 +1397,16 @@ struct BattleStackMoved : public CPackForClient { ui32 stack; std::vector tilesToMove; - ui8 distance, teleporting; + int distance; + bool teleporting; BattleStackMoved() - :stack(0), distance(0), teleporting(0) + : stack(0), + distance(0), + teleporting(false) {}; void applyFirstCl(CClient *cl); - void applyGs(CGameState *gs); + DLL_LINKAGE void applyGs(CGameState *gs); + DLL_LINKAGE void applyBattle(IBattleState * battleState); template void serialize(Handler &h, const int version) { h & stack; @@ -1454,52 +1415,46 @@ struct BattleStackMoved : public CPackForClient } }; -struct StacksHealedOrResurrected : public CPackForClient +struct BattleUnitsChanged : public CPackForClient { - StacksHealedOrResurrected() - :lifeDrain(false), tentHealing(false), drainedFrom(0), cure(false) - {} + BattleUnitsChanged(){} DLL_LINKAGE void applyGs(CGameState *gs); + DLL_LINKAGE void applyBattle(IBattleState * battleState); void applyCl(CClient *cl); - std::vector healedStacks; - bool lifeDrain; //if true, this heal is an effect of life drain or soul steal - bool tentHealing; //if true, than it's healing via First Aid Tent - si32 drainedFrom; //if life drain or soul steal - then stack life was drain from, if tentHealing - stack that is a healer - bool cure; //archangel cast also remove negative effects + std::vector changedStacks; + std::vector battleLog; + std::vector customEffects; - template void serialize(Handler &h, const int version) + template void serialize(Handler & h, const int version) { - h & healedStacks; - h & lifeDrain; - h & tentHealing; - h & drainedFrom; - h & cure; + h & changedStacks; + h & battleLog; + h & customEffects; } }; -struct BattleStackAttacked : public CPackForClient +struct BattleStackAttacked { BattleStackAttacked(): stackAttacked(0), attackerID(0), killedAmount(0), damageAmount(0), - newHealth(), + newState(), flags(0), effect(0), spellID(SpellID::NONE) {}; - void applyFirstCl(CClient * cl); - //void applyCl(CClient *cl); + DLL_LINKAGE void applyGs(CGameState *gs); + DLL_LINKAGE void applyBattle(IBattleState * battleState); ui32 stackAttacked, attackerID; ui32 killedAmount; - si32 damageAmount; - CHealthInfo newHealth; + int64_t damageAmount; + UnitChanges newState; enum EFlags {KILLED = 1, EFFECT = 2/*deprecated */, SECONDARY = 4, REBIRTH = 8, CLONE_KILLED = 16, SPELL_EFFECT = 32 /*, BONUS_EFFECT = 64 */}; ui32 flags; //uses EFlags (above) ui32 effect; //set only if flag EFFECT is set SpellID spellID; //only if flag SPELL_EFFECT is set - std::vector healedStacks; //used when life drain bool killed() const//if target stack was killed { @@ -1526,20 +1481,15 @@ struct BattleStackAttacked : public CPackForClient { return flags & REBIRTH; } - bool lifeDrain() const //if this attack involves life drain effect - { - return healedStacks.size() > 0; - } template void serialize(Handler &h, const int version) { h & stackAttacked; h & attackerID; - h & newHealth; + h & newState; h & flags; h & killedAmount; h & damageAmount; h & effect; - h & healedStacks; h & spellID; } bool operator<(const BattleStackAttacked &b) const @@ -1557,6 +1507,8 @@ struct BattleAttack : public CPackForClient DLL_LINKAGE void applyGs(CGameState *gs); void applyCl(CClient *cl); + BattleUnitsChanged attackerChanges; + std::vector bsa; ui32 stackAttacking; ui32 flags; //uses Eflags (below) @@ -1564,6 +1516,9 @@ struct BattleAttack : public CPackForClient SpellID spellID; //for SPELL_LIKE + std::vector battleLog; + std::vector customEffects; + bool shot() const//distance attack - decrease number of shots { return flags & SHOT; @@ -1598,6 +1553,9 @@ struct BattleAttack : public CPackForClient h & stackAttacking; h & flags; h & spellID; + h & battleLog; + h & customEffects; + h & attackerChanges; } }; @@ -1627,24 +1585,9 @@ struct EndAction : public CPackForClient struct BattleSpellCast : public CPackForClient { - ///custom effect (resistance, reflection, etc) - struct CustomEffect - { - /// WoG AC format - ui32 effect; - ui32 stack; - template void serialize(Handler &h, const int version) - { - h & effect; - h & stack; - } - }; - BattleSpellCast() { side = 0; - id = 0; - skill = 0; manaGained = 0; casterStack = -1; castByHero = true; @@ -1655,11 +1598,10 @@ struct BattleSpellCast : public CPackForClient bool activeCast; ui8 side; //which hero did cast spell: 0 - attacker, 1 - defender - ui32 id; //id of spell - ui8 skill; //caster's skill level + SpellID spellID; //id of spell ui8 manaGained; //mana channeling ability BattleHex tile; //destination tile (may not be set in some global/mass spells - std::vector customEffects; + std::vector customEffects; std::set affectedCres; //ids of creatures affected by this spell, generally used if spell does not set any effect (like dispel or cure) si32 casterStack;// -1 if not cated by creature, >=0 caster stack ID bool castByHero; //if true - spell has been cast by hero, otherwise by a creature @@ -1668,8 +1610,7 @@ struct BattleSpellCast : public CPackForClient template void serialize(Handler &h, const int version) { h & side; - h & id; - h & skill; + h & spellID; h & manaGained; h & tile; h & customEffects; @@ -1684,27 +1625,20 @@ struct BattleSpellCast : public CPackForClient struct SetStackEffect : public CPackForClient { SetStackEffect(){}; - DLL_LINKAGE void applyGs(CGameState *gs); - void applyCl(CClient *cl); + DLL_LINKAGE void applyGs(CGameState * gs); + DLL_LINKAGE void applyBattle(IBattleState * battleState); + void applyCl(CClient * cl); - std::vector stacks; //affected stacks (IDs) - - //regular effects - std::vector effect; //bonuses to apply - std::vector > uniqueBonuses; //bonuses per single stack - - //cumulative effects - std::vector cumulativeEffects; //bonuses to apply - std::vector > cumulativeUniqueBonuses; //bonuses per single stack + std::vector>> toAdd; + std::vector>> toUpdate; + std::vector>> toRemove; std::vector battleLog; - template void serialize(Handler &h, const int version) + template void serialize(Handler & h, const int version) { - h & stacks; - h & effect; - h & uniqueBonuses; - h & cumulativeEffects; - h & cumulativeUniqueBonuses; + h & toAdd; + h & toUpdate; + h & toRemove; h & battleLog; } }; @@ -1712,13 +1646,18 @@ struct SetStackEffect : public CPackForClient struct StacksInjured : public CPackForClient { StacksInjured(){} - DLL_LINKAGE void applyGs(CGameState *gs); - void applyCl(CClient *cl); + DLL_LINKAGE void applyGs(CGameState * gs); + DLL_LINKAGE void applyBattle(IBattleState * battleState); + + void applyCl(CClient * cl); std::vector stacks; - template void serialize(Handler &h, const int version) + std::vector battleLog; + + template void serialize(Handler & h, const int version) { h & stacks; + h & battleLog; } }; @@ -1736,18 +1675,19 @@ struct BattleResultsApplied : public CPackForClient } }; -struct ObstaclesRemoved : public CPackForClient +struct BattleObstaclesChanged : public CPackForClient { - ObstaclesRemoved(){} + BattleObstaclesChanged(){} - DLL_LINKAGE void applyGs(CGameState *gs); - void applyCl(CClient *cl); + DLL_LINKAGE void applyGs(CGameState * gs); + DLL_LINKAGE void applyBattle(IBattleState * battleState); + void applyCl(CClient * cl); - std::set obstacles; //uniqueIDs of removed obstacles + std::vector changes; - template void serialize(Handler &h, const int version) + template void serialize(Handler & h, const int version) { - h & obstacles; + h & changes; } }; @@ -1759,9 +1699,7 @@ struct ELF_VISIBILITY CatapultAttack : public CPackForClient ui8 attackedPart; ui8 damageDealt; - DLL_LINKAGE std::string toString() const; - - template void serialize(Handler &h, const int version) + template void serialize(Handler & h, const int version) { h & destinationTile; h & attackedPart; @@ -1772,9 +1710,9 @@ struct ELF_VISIBILITY CatapultAttack : public CPackForClient DLL_LINKAGE CatapultAttack(); DLL_LINKAGE ~CatapultAttack(); - DLL_LINKAGE void applyGs(CGameState *gs); - void applyCl(CClient *cl); - DLL_LINKAGE std::string toString() const override; + DLL_LINKAGE void applyGs(CGameState * gs); + DLL_LINKAGE void applyBattle(IBattleState * battleState); + void applyCl(CClient * cl); std::vector< AttackInfo > attackedParts; int attacker; //if -1, then a spell caused this @@ -1786,49 +1724,6 @@ struct ELF_VISIBILITY CatapultAttack : public CPackForClient } }; -struct BattleStacksRemoved : public CPackForClient -{ - BattleStacksRemoved(){} - - DLL_LINKAGE void applyGs(CGameState *gs); - void applyFirstCl(CClient *cl);//inform client before stack objects are destroyed - - std::set stackIDs; //IDs of removed stacks - - template void serialize(Handler &h, const int version) - { - h & stackIDs; - } -}; - -struct BattleStackAdded : public CPackForClient -{ - BattleStackAdded() - : side(0), amount(0), pos(0), summoned(0), newStackID(0) - {}; - - DLL_LINKAGE void applyGs(CGameState *gs); - void applyCl(CClient *cl); - - ui8 side; - CreatureID creID; - int amount; - int pos; - int summoned; //if true, remove it afterwards - - ///Actual stack ID, set on apply, do not serialize - int newStackID; - - template void serialize(Handler &h, const int version) - { - h & side; - h & creID; - h & amount; - h & pos; - h & summoned; - } -}; - struct BattleSetStackProperty : public CPackForClient { BattleSetStackProperty() @@ -1877,21 +1772,6 @@ struct BattleTriggerEffect : public CPackForClient } }; -struct BattleObstaclePlaced : public CPackForClient -{ - BattleObstaclePlaced(){}; - - DLL_LINKAGE void applyGs(CGameState *gs); //effect - void applyCl(CClient *cl); //play animations & stuff - - std::shared_ptr obstacle; - - template void serialize(Handler &h, const int version) - { - h & obstacle; - } -}; - struct BattleUpdateGateState : public CPackForClient { BattleUpdateGateState():state(EGateState::NONE){}; diff --git a/lib/NetPacksBase.h b/lib/NetPacksBase.h index 12dd77e29..cf5f2eede 100644 --- a/lib/NetPacksBase.h +++ b/lib/NetPacksBase.h @@ -9,7 +9,10 @@ */ #pragma once +class CClient; class CGameState; +class CGameHandler; +class CConnection; class CStackBasicDescriptor; class CGHeroInstance; class CStackInstance; @@ -17,9 +20,11 @@ class CArmedInstance; class CArtifactSet; class CBonusSystemNode; struct ArtSlotInfo; +class BattleInfo; #include "ConstTransitivePtr.h" #include "GameConstants.h" +#include "JsonNode.h" struct DLL_LINKAGE CPack { @@ -31,11 +36,49 @@ struct DLL_LINKAGE CPack logNetwork->error("CPack serialized... this should not happen!"); assert(false && "CPack serialized"); } - void applyGs(CGameState *gs) { } - virtual std::string toString() const { return boost::str(boost::format("{CPack: type '%s'}") % typeid(this).name()); } + + void applyGs(CGameState * gs) + {} }; -std::ostream & operator<<(std::ostream & out, const CPack * pack); +struct CPackForClient : public CPack +{ + CPackForClient(){}; + + CGameState* GS(CClient *cl); + void applyFirstCl(CClient *cl)//called before applying to gs + {} + void applyCl(CClient *cl)//called after applying to gs + {} +}; + +struct CPackForServer : public CPack +{ + PlayerColor player; + CConnection *c; + CGameState* GS(CGameHandler *gh); + CPackForServer(): + player(PlayerColor::NEUTRAL), + c(nullptr) + { + } + + bool applyGh(CGameHandler *gh) //called after applying to gs + { + logGlobal->error("Should not happen... applying plain CPackForServer"); + return false; + } + +protected: + void throwNotAllowedAction(); + void throwOnWrongOwner(CGameHandler * gh, ObjectInstanceID id); + void throwOnWrongPlayer(CGameHandler * gh, PlayerColor player); + void throwAndCompain(CGameHandler * gh, std::string txt); + bool isPlayerOwns(CGameHandler * gh, ObjectInstanceID id); + +private: + void wrongPlayerMessage(CGameHandler * gh, PlayerColor expectedplayer); +}; struct DLL_LINKAGE MetaString { @@ -196,25 +239,104 @@ struct ArtifactLocation } }; -class CHealthInfo +///custom effect (resistance, reflection, etc) +struct CustomEffectInfo { -public: - CHealthInfo(): - stackId(0), delta(0), firstHPleft(0), fullUnits(0), resurrected(0) + CustomEffectInfo() + :effect(0), + sound(0), + stack(0) + { + } + /// WoG AC format + ui32 effect; + ui32 sound; + ui32 stack; + template void serialize(Handler & h, const int version) + { + h & effect; + h & sound; + h & stack; + } +}; + +class BattleChanges +{ +public: + enum class EOperation : si8 + { + ADD, + RESET_STATE, + UPDATE, + REMOVE + }; + + JsonNode data; + EOperation operation; + + BattleChanges() + : operation(EOperation::RESET_STATE), + data() + { + } + + BattleChanges(EOperation operation_) + : operation(operation_), + data() + { + } +}; + +class UnitChanges : public BattleChanges +{ +public: + uint32_t id; + int64_t healthDelta; + + UnitChanges() + : BattleChanges(EOperation::RESET_STATE), + id(0), + healthDelta(0) + { + } + + UnitChanges(uint32_t id_, EOperation operation_) + : BattleChanges(operation_), + id(id_), + healthDelta(0) { } - uint32_t stackId; - int32_t delta; - int32_t firstHPleft; - int32_t fullUnits; - int32_t resurrected; template void serialize(Handler & h, const int version) { - h & stackId; - h & delta; - h & firstHPleft; - h & fullUnits; - h & resurrected; + h & id; + h & healthDelta; + h & data; + h & operation; + } +}; + +class ObstacleChanges : public BattleChanges +{ +public: + uint32_t id; + + ObstacleChanges() + : BattleChanges(EOperation::RESET_STATE), + id(0) + { + } + + ObstacleChanges(uint32_t id_, EOperation operation_) + : BattleChanges(operation_), + id(id_) + { + } + + template void serialize(Handler & h, const int version) + { + h & id; + h & data; + h & operation; } }; diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 85a187bdf..bade8a3c6 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -32,11 +32,6 @@ #undef max -std::ostream & operator<<(std::ostream & out, const CPack * pack) -{ - return out << (pack? pack->toString() : ""); -} - DLL_LINKAGE void SetResources::applyGs(CGameState *gs) { assert(player < PlayerColor::PLAYER_LIMIT); @@ -1232,50 +1227,12 @@ DLL_LINKAGE void BattleStart::applyGs(CGameState *gs) DLL_LINKAGE void BattleNextRound::applyGs(CGameState *gs) { - for (int i = 0; i < 2; ++i) - { - gs->curB->sides[i].castSpellsCount = 0; - vstd::amax(--gs->curB->sides[i].enchanterCounter, 0); - } - - gs->curB->round = round; - - for(CStack *s : gs->curB->stacks) - { - s->state -= EBattleStackState::DEFENDING; - s->state -= EBattleStackState::WAITING; - s->state -= EBattleStackState::MOVED; - s->state -= EBattleStackState::HAD_MORALE; - s->state -= EBattleStackState::FEAR; - s->state -= EBattleStackState::DRAINED_MANA; - s->counterAttacks.reset(); - // new turn effects - s->updateBonuses(Bonus::NTurns); - - if(s->alive() && s->isClone()) - { - //cloned stack has special lifetime marker - //check it after bonuses updated in battleTurnPassed() - - if(!s->hasBonus(Selector::type(Bonus::NONE).And(Selector::source(Bonus::SPELL_EFFECT, SpellID::CLONE)))) - s->makeGhost(); - } - } - - for(auto &obst : gs->curB->obstacles) - obst->battleTurnPassed(); + gs->curB->nextRound(round); } DLL_LINKAGE void BattleSetActiveStack::applyGs(CGameState *gs) { - gs->curB->activeStack = stack; - CStack *st = gs->curB->getStack(stack); - - //remove bonuses that last until when stack gets new turn - st->popBonuses(Bonus::UntilGetsTurn); - - if(vstd::contains(st->state,EBattleStackState::MOVED)) //if stack is moving second time this turn it must had a high morale bonus - st->state.insert(EBattleStackState::HAD_MORALE); + gs->curB->nextTurn(stack); } DLL_LINKAGE void BattleTriggerEffect::applyGs(CGameState *gs) @@ -1286,15 +1243,14 @@ DLL_LINKAGE void BattleTriggerEffect::applyGs(CGameState *gs) { case Bonus::HP_REGENERATION: { - int32_t toHeal = val; - CHealth health = st->healthAfterHealed(toHeal, EHealLevel::HEAL, EHealPower::PERMANENT); - st->setHealth(health); + int64_t toHeal = val; + st->heal(toHeal, EHealLevel::HEAL, EHealPower::PERMANENT); break; } case Bonus::MANA_DRAIN: { CGHeroInstance * h = gs->getHero(ObjectInstanceID(additionalInfo)); - st->state.insert (EBattleStackState::DRAINED_MANA); + st->drainedMana = true; h->mana -= val; vstd::amax(h->mana, 0); break; @@ -1310,18 +1266,13 @@ DLL_LINKAGE void BattleTriggerEffect::applyGs(CGameState *gs) case Bonus::ENCHANTER: break; case Bonus::FEAR: - st->state.insert(EBattleStackState::FEAR); + st->fear = true; break; default: logNetwork->error("Unrecognized trigger effect type %d", effect); } } -DLL_LINKAGE void BattleObstaclePlaced::applyGs(CGameState *gs) -{ - gs->curB->obstacles.push_back(obstacle); -} - DLL_LINKAGE void BattleUpdateGateState::applyGs(CGameState *gs) { if(gs->curB) @@ -1330,15 +1281,6 @@ DLL_LINKAGE void BattleUpdateGateState::applyGs(CGameState *gs) void BattleResult::applyGs(CGameState *gs) { - for (CStack *s : gs->curB->stacks) - { - if (s->base && s->base->armyObj && vstd::contains(s->state, EBattleStackState::SUMMONED)) - { - //stack with SUMMONED flag but coming from garrison -> most likely resurrected, needs to be removed - assert(&s->base->armyObj->getStack(s->slot) == s->base); - const_cast(s->base->armyObj)->eraseStack(s->slot); - } - } for (auto & elem : gs->curB->stacks) delete elem; @@ -1373,90 +1315,24 @@ void BattleResult::applyGs(CGameState *gs) gs->curB.dellNull(); } -void BattleStackMoved::applyGs(CGameState *gs) +DLL_LINKAGE void BattleStackMoved::applyGs(CGameState *gs) { - CStack *s = gs->curB->getStack(stack); - assert(s); - BattleHex dest = tilesToMove.back(); - - //if unit ended movement on quicksands that were created by enemy, that quicksand patch becomes visible for owner - for(auto &oi : gs->curB->obstacles) - { - if(oi->obstacleType == CObstacleInstance::QUICKSAND - && vstd::contains(oi->getAffectedTiles(), tilesToMove.back())) - { - SpellCreatedObstacle *sands = dynamic_cast(oi.get()); - assert(sands); - if(sands->casterSide != s->side) - sands->visibleForAnotherSide = true; - } - } - s->position = dest; + applyBattle(gs->curB); } -DLL_LINKAGE void BattleStackAttacked::applyGs(CGameState *gs) +DLL_LINKAGE void BattleStackMoved::applyBattle(IBattleState * battleState) { - CStack * at = gs->curB->getStack(stackAttacked); - assert(at); - at->popBonuses(Bonus::UntilBeingAttacked); + battleState->moveUnit(stack, tilesToMove.back()); +} - if(willRebirth()) - at->health.reset();//kill stack first - else - at->setHealth(newHealth); +DLL_LINKAGE void BattleStackAttacked::applyGs(CGameState * gs) +{ + applyBattle(gs->curB); +} - if(killed()) - { - at->state -= EBattleStackState::ALIVE; - - if(at->cloneID >= 0) - { - //remove clone as well - CStack * clone = gs->curB->getStack(at->cloneID); - if(clone) - clone->makeGhost(); - - at->cloneID = -1; - } - } - //life drain handling - for(auto & elem : healedStacks) - elem.applyGs(gs); - - if(willRebirth()) - { - //TODO: handle rebirth with StacksHealedOrResurrected - at->casts.use(); - at->state.insert(EBattleStackState::ALIVE); - at->setHealth(newHealth); - - //removing all spells effects - auto selector = [](const Bonus * b) - { - //Special case: DISRUPTING_RAY is "immune" to dispell - //Other even PERMANENT effects can be removed - if(b->source == Bonus::SPELL_EFFECT) - return b->sid != SpellID::DISRUPTING_RAY; - else - return false; - }; - at->popBonuses(selector); - } - if(cloneKilled()) - { - //"hide" killed creatures instead so we keep info about it - at->makeGhost(); - - for(CStack * s : gs->curB->stacks) - { - if(s->cloneID == at->ID) - s->cloneID = -1; - } - } - - //killed summoned creature should be removed like clone - if(killed() && vstd::contains(at->state, EBattleStackState::SUMMONED)) - at->makeGhost(); +DLL_LINKAGE void BattleStackAttacked::applyBattle(IBattleState * battleState) +{ + battleState->setUnitState(newState.id, newState.data, newState.healthDelta); } DLL_LINKAGE void BattleAttack::applyGs(CGameState * gs) @@ -1464,11 +1340,7 @@ DLL_LINKAGE void BattleAttack::applyGs(CGameState * gs) CStack * attacker = gs->curB->getStack(stackAttacking); assert(attacker); - if(counter()) - attacker->counterAttacks.use(); - - if(shot()) - attacker->shots.use(); + attackerChanges.applyGs(gs); for(BattleStackAttacked & stackAttacked : bsa) stackAttacked.applyGs(gs); @@ -1480,7 +1352,7 @@ DLL_LINKAGE void StartAction::applyGs(CGameState *gs) { CStack *st = gs->curB->getStack(ba.stackNumber); - if(ba.actionType == Battle::END_TACTIC_PHASE) + if(ba.actionType == EActionType::END_TACTIC_PHASE) { gs->curB->tacticDistance = 0; return; @@ -1493,216 +1365,130 @@ DLL_LINKAGE void StartAction::applyGs(CGameState *gs) return; } - if(ba.actionType != Battle::HERO_SPELL) //don't check for stack if it's custom action by hero + if(ba.actionType != EActionType::HERO_SPELL) //don't check for stack if it's custom action by hero { assert(st); } else { - gs->curB->sides[ba.side].usedSpellsHistory.push_back(SpellID(ba.additionalInfo).toSpell()); + gs->curB->sides[ba.side].usedSpellsHistory.push_back(SpellID(ba.actionSubtype).toSpell()); } switch(ba.actionType) { - case Battle::DEFEND: - st->state -= EBattleStackState::DEFENDING_ANIM; - st->state.insert(EBattleStackState::DEFENDING); - st->state.insert(EBattleStackState::DEFENDING_ANIM); + case EActionType::DEFEND: + st->waiting = false; + st->defending = true; + st->defendingAnim = true; + break; + case EActionType::WAIT: + st->defendingAnim = false; + st->waiting = true; + break; + case EActionType::HERO_SPELL: //no change in current stack state break; - case Battle::WAIT: - st->state -= EBattleStackState::DEFENDING_ANIM; - st->state.insert(EBattleStackState::WAITING); - return; - case Battle::HERO_SPELL: //no change in current stack state - return; default: //any active stack action - attack, catapult, heal, spell... - st->state -= EBattleStackState::DEFENDING_ANIM; - st->state.insert(EBattleStackState::MOVED); + st->waiting = false; + st->defendingAnim = false; + st->movedThisRound = true; break; } - - if(st) - st->state -= EBattleStackState::WAITING; //if stack was waiting it has made move, so it won't be "waiting" anymore (if the action was WAIT, then we have returned) } DLL_LINKAGE void BattleSpellCast::applyGs(CGameState *gs) { assert(gs->curB); - const CSpell * spell = SpellID(id).toSpell(); - - spell->applyBattle(gs->curB, this); -} - -void actualizeEffect(CStack * s, const Bonus & ef) -{ - for(auto stackBonus : s->getBonusList()) //TODO: optimize + if(castByHero) { - if(stackBonus->source == Bonus::SPELL_EFFECT && stackBonus->type == ef.type && stackBonus->subtype == ef.subtype) + if(side < 2) { - stackBonus->turnsRemain = std::max(stackBonus->turnsRemain, ef.turnsRemain); + gs->curB->sides[side].castSpellsCount++; } } - CBonusSystemNode::treeHasChanged(); -} - -void actualizeEffect(CStack * s, const std::vector & ef) -{ - //actualizing features vector - - for(const Bonus &fromEffect : ef) - { - actualizeEffect(s, fromEffect); - } } DLL_LINKAGE void SetStackEffect::applyGs(CGameState *gs) { - if(effect.empty() && cumulativeEffects.empty()) - { - logGlobal->error("Trying to apply SetStackEffect with no effects"); - return; - } - - si32 spellid = effect.empty() ? cumulativeEffects.begin()->sid : effect.begin()->sid; //effects' source ID - - auto processEffect = [spellid, this](CStack * sta, const Bonus & effect, bool cumulative) - { - if(cumulative || !sta->hasBonus(Selector::source(Bonus::SPELL_EFFECT, spellid).And(Selector::typeSubtype(effect.type, effect.subtype)))) - { - //no such effect or cumulative - add new - logBonus->trace("%s receives a new bonus: %s", sta->nodeName(), effect.Description()); - sta->addNewBonus(std::make_shared(effect)); - } - else - { - logBonus->trace("%s updated bonus: %s", sta->nodeName(), effect.Description()); - actualizeEffect(sta, effect); - } - }; - - for(ui32 id : stacks) - { - CStack *s = gs->curB->getStack(id); - if(s) - { - for(const Bonus & fromEffect : effect) - processEffect(s, fromEffect, false); - for(const Bonus & fromEffect : cumulativeEffects) - processEffect(s, fromEffect, true); - } - else - logNetwork->error("Cannot find stack %d", id); - } - - for(auto & para : uniqueBonuses) - { - CStack *s = gs->curB->getStack(para.first); - if(s) - processEffect(s, para.second, false); - else - logNetwork->error("Cannot find stack %d", para.first); - } - - for(auto & para : cumulativeUniqueBonuses) - { - CStack *s = gs->curB->getStack(para.first); - if(s) - processEffect(s, para.second, true); - else - logNetwork->error("Cannot find stack %d", para.first); - } + applyBattle(gs->curB); } +DLL_LINKAGE void SetStackEffect::applyBattle(IBattleState * battleState) +{ + for(const auto & stackData : toRemove) + battleState->removeUnitBonus(stackData.first, stackData.second); + + for(const auto & stackData : toUpdate) + battleState->updateUnitBonus(stackData.first, stackData.second); + + for(const auto & stackData : toAdd) + battleState->addUnitBonus(stackData.first, stackData.second); +} + + DLL_LINKAGE void StacksInjured::applyGs(CGameState *gs) +{ + applyBattle(gs->curB); +} + +DLL_LINKAGE void StacksInjured::applyBattle(IBattleState * battleState) { for(BattleStackAttacked stackAttacked : stacks) - stackAttacked.applyGs(gs); + stackAttacked.applyBattle(battleState); } -DLL_LINKAGE void StacksHealedOrResurrected::applyGs(CGameState *gs) +DLL_LINKAGE void BattleUnitsChanged::applyGs(CGameState *gs) { - for(auto & elem : healedStacks) + applyBattle(gs->curB); +} + +DLL_LINKAGE void BattleUnitsChanged::applyBattle(IBattleState * battleState) +{ + for(auto & elem : changedStacks) { - CStack * changedStack = gs->curB->getStack(elem.stackId, false); - assert(changedStack); - - //checking if we resurrect a stack that is under a living stack - auto accessibility = gs->curB->getAccesibility(); - - if(!changedStack->alive() && !accessibility.accessible(changedStack->position, changedStack)) + switch(elem.operation) { - logNetwork->error("Cannot resurrect %s because hex %d is occupied!", changedStack->nodeName(), changedStack->position.hex); - return; //position is already occupied - } - - //applying changes - bool resurrected = !changedStack->alive(); //indicates if stack is resurrected or just healed - if(resurrected) - { - if(auto totalHealth = changedStack->health.available()) - logGlobal->warn("Dead stack %s with positive total HP %d", changedStack->nodeName(), totalHealth); - - changedStack->state.insert(EBattleStackState::ALIVE); - } - - changedStack->setHealth(elem); - - if(resurrected) - { - //removing all spells effects - auto selector = [](const Bonus * b) - { - //Special case: DISRUPTING_RAY is "immune" to dispell - //Other even PERMANENT effects can be removed - if(b->source == Bonus::SPELL_EFFECT) - return b->sid != SpellID::DISRUPTING_RAY; - else - return false; - }; - changedStack->popBonuses(selector); - } - else if(cure) - { - //removing all effects from negative spells - auto selector = [](const Bonus * b) - { - //Special case: DISRUPTING_RAY is "immune" to dispell - //Other even PERMANENT effects can be removed - if(b->source == Bonus::SPELL_EFFECT) - { - const CSpell * sourceSpell = SpellID(b->sid).toSpell(); - if(!sourceSpell) - return false; - return sourceSpell->id != SpellID::DISRUPTING_RAY && sourceSpell->isNegative(); - } - else - return false; - }; - changedStack->popBonuses(selector); + case BattleChanges::EOperation::RESET_STATE: + battleState->setUnitState(elem.id, elem.data, elem.healthDelta); + break; + case BattleChanges::EOperation::REMOVE: + battleState->removeUnit(elem.id); + break; + case BattleChanges::EOperation::ADD: + battleState->addUnit(elem.id, elem.data); + break; + default: + logNetwork->error("Unknown unit operation %d", (int)elem.operation); + break; } } } -DLL_LINKAGE void ObstaclesRemoved::applyGs(CGameState *gs) +DLL_LINKAGE void BattleObstaclesChanged::applyGs(CGameState * gs) { - if(gs->curB) //if there is a battle + if(gs->curB) + applyBattle(gs->curB); +} + +DLL_LINKAGE void BattleObstaclesChanged::applyBattle(IBattleState * battleState) +{ + for(const auto & change : changes) { - for(const si32 rem_obst :obstacles) + switch(change.operation) { - for(int i=0; icurB->obstacles.size(); ++i) - { - if(gs->curB->obstacles[i]->uniqueID == rem_obst) //remove this obstacle - { - gs->curB->obstacles.erase(gs->curB->obstacles.begin() + i); - break; - } - } + case BattleChanges::EOperation::REMOVE: + battleState->removeObstacle(change.id); + break; + case BattleChanges::EOperation::ADD: + battleState->addObstacle(change); + break; + default: + logNetwork->error("Unknown obstacle operation %d", (int)change.operation); + break; } } } - DLL_LINKAGE CatapultAttack::CatapultAttack() { attacker = -1; @@ -1712,99 +1498,26 @@ DLL_LINKAGE CatapultAttack::~CatapultAttack() { } -DLL_LINKAGE void CatapultAttack::applyGs(CGameState *gs) +DLL_LINKAGE void CatapultAttack::applyGs(CGameState * gs) { - if(gs->curB && gs->curB->town && gs->curB->town->fortLevel() != CGTownInstance::NONE) //if there is a battle and it's a siege - { - for(const auto &it :attackedParts) - { - gs->curB->si.wallState[it.attackedPart] = - SiegeInfo::applyDamage(EWallState::EWallState(gs->curB->si.wallState[it.attackedPart]), it.damageDealt); - } - } + if(gs->curB) + applyBattle(gs->curB); } -DLL_LINKAGE std::string CatapultAttack::AttackInfo::toString() const +DLL_LINKAGE void CatapultAttack::applyBattle(IBattleState * battleState) { - return boost::str(boost::format("{AttackInfo: destinationTile '%d', attackedPart '%d', damageDealt '%d'}") - % destinationTile % static_cast(attackedPart) % static_cast(damageDealt)); -} - -DLL_LINKAGE std::string CatapultAttack::toString() const -{ - std::ostringstream out; - out << "["; - for(auto it = attackedParts.begin(); it != attackedParts.end(); ++it) - { - out << it->toString(); - if(std::prev(attackedParts.end()) != it) out << ", "; - } - out << "]"; - - return boost::str(boost::format("{CatapultAttack: attackedParts '%s', attacker '%d'}") % out.str() % attacker); -} - -DLL_LINKAGE void BattleStacksRemoved::applyGs(CGameState *gs) -{ - if(!gs->curB) + auto town = battleState->getDefendedTown(); + if(!town) return; - while(!stackIDs.empty()) - { - ui32 rem_stack = *stackIDs.begin(); - - for(int b=0; bcurB->stacks.size(); ++b) //find it in vector of stacks - { - if(gs->curB->stacks[b]->ID == rem_stack) //if found - { - CStack * toRemove = gs->curB->stacks[b]; - - toRemove->state.erase(EBattleStackState::ALIVE); - toRemove->state.erase(EBattleStackState::GHOST_PENDING); - toRemove->state.insert(EBattleStackState::GHOST); - toRemove->detachFromAll();//TODO: may be some bonuses should remain - - //stack may be removed instantly (not being killed first) - //handle clone remove also here - if(toRemove->cloneID >= 0) - { - stackIDs.insert(toRemove->cloneID); - toRemove->cloneID = -1; - } - - //cleanup remaining clone links if any - for(CStack * s : gs->curB->stacks) - { - if(s->cloneID == toRemove->ID) - s->cloneID = -1; - } - - break; - } - } - - stackIDs.erase(rem_stack); - } -} - -DLL_LINKAGE void BattleStackAdded::applyGs(CGameState *gs) -{ - newStackID = 0; - if(!BattleHex(pos).isValid()) - { - logNetwork->warn("No place found for new stack!"); + if(town->fortLevel() == CGTownInstance::NONE) return; + + for(const auto & part : attackedParts) + { + auto newWallState = SiegeInfo::applyDamage(EWallState::EWallState(battleState->getWallState(part.attackedPart)), part.damageDealt); + battleState->setWallState(part.attackedPart, newWallState); } - - CStackBasicDescriptor csbd(creID, amount); - CStack * addedStack = gs->curB->generateNewStack(csbd, side, SlotID::SUMMONED_SLOT_PLACEHOLDER, pos); //TODO: netpacks? - if(summoned) - addedStack->state.insert(EBattleStackState::SUMMONED); - - addedStack->localInit(gs->curB.get()); - gs->curB->stacks.push_back(addedStack); - - newStackID = addedStack->ID; } DLL_LINKAGE void BattleSetStackProperty::applyGs(CGameState * gs) @@ -1837,7 +1550,7 @@ DLL_LINKAGE void BattleSetStackProperty::applyGs(CGameState * gs) } case CLONED: { - stack->state.insert(EBattleStackState::CLONED); + stack->cloned = true; break; } case HAS_CLONE: diff --git a/lib/VCMI_Lib.cpp b/lib/VCMI_Lib.cpp index 67fd7141c..e9c2bfe82 100644 --- a/lib/VCMI_Lib.cpp +++ b/lib/VCMI_Lib.cpp @@ -33,13 +33,13 @@ LibClasses * VLC = nullptr; -DLL_LINKAGE void preinitDLL(CConsoleHandler *Console) +DLL_LINKAGE void preinitDLL(CConsoleHandler * Console, bool onlyEssential) { console = Console; VLC = new LibClasses(); try { - VLC->loadFilesystem(); + VLC->loadFilesystem(onlyEssential); } catch(...) { @@ -48,9 +48,9 @@ DLL_LINKAGE void preinitDLL(CConsoleHandler *Console) } } -DLL_LINKAGE void loadDLLClasses() +DLL_LINKAGE void loadDLLClasses(bool onlyEssential) { - VLC->init(); + VLC->init(onlyEssential); } const IBonusTypeHandler * LibClasses::getBth() const @@ -58,7 +58,7 @@ const IBonusTypeHandler * LibClasses::getBth() const return bth; } -void LibClasses::loadFilesystem() +void LibClasses::loadFilesystem(bool onlyEssential) { CStopWatch totalTime; CStopWatch loadTime; @@ -72,7 +72,7 @@ void LibClasses::loadFilesystem() modh = new CModHandler(); logGlobal->info("\tMod handler: %d ms", loadTime.getDiff()); - modh->loadMods(); + modh->loadMods(onlyEssential); modh->loadModFilesystems(); logGlobal->info("\tMod filesystems: %d ms", loadTime.getDiff()); @@ -90,7 +90,7 @@ template void createHandler(Handler *&handler, const std::string logHandlerLoaded(name, timer); } -void LibClasses::init() +void LibClasses::init(bool onlyEssential) { CStopWatch pomtime, totalTime; @@ -124,7 +124,7 @@ void LibClasses::init() modh->load(); - modh->afterLoad(); + modh->afterLoad(onlyEssential); //FIXME: make sure that everything is ok after game restart //TODO: This should be done every time mod config changes diff --git a/lib/VCMI_Lib.h b/lib/VCMI_Lib.h index f2fb8c73c..35d958470 100644 --- a/lib/VCMI_Lib.h +++ b/lib/VCMI_Lib.h @@ -53,11 +53,11 @@ public: LibClasses(); //c-tor, loads .lods and NULLs handlers ~LibClasses(); - void init(); //uses standard config file + void init(bool onlyEssential); //uses standard config file void clear(); //deletes all handlers and its data - void loadFilesystem();// basic initialization. should be called before init() + void loadFilesystem(bool onlyEssential);// basic initialization. should be called before init() template void serialize(Handler &h, const int version) @@ -85,6 +85,6 @@ public: extern DLL_LINKAGE LibClasses * VLC; -DLL_LINKAGE void preinitDLL(CConsoleHandler *Console); -DLL_LINKAGE void loadDLLClasses(); +DLL_LINKAGE void preinitDLL(CConsoleHandler * Console, bool onlyEssential = false); +DLL_LINKAGE void loadDLLClasses(bool onlyEssential = false); diff --git a/lib/VCMI_lib.cbp b/lib/VCMI_lib.cbp index a6441d147..1d024e648 100644 --- a/lib/VCMI_lib.cbp +++ b/lib/VCMI_lib.cbp @@ -17,8 +17,10 @@