|
@@ -57,38 +57,26 @@ bool Catapult::applicable(Problem & problem, const Mechanics * m) const
|
|
|
return !attackableBattleHexes.empty() || m->adaptProblem(ESpellCastProblem::NO_APPROPRIATE_TARGET, problem);
|
|
|
}
|
|
|
|
|
|
-void Catapult::apply(ServerCallback * server, const Mechanics * m, const EffectTarget & /* eTarget */) const
|
|
|
+void Catapult::apply(ServerCallback * server, const Mechanics * m, const EffectTarget & eTarget) const
|
|
|
{
|
|
|
- //start with all destructible parts
|
|
|
- static const std::set<EWallPart> potentialTargets =
|
|
|
- {
|
|
|
- EWallPart::KEEP,
|
|
|
- EWallPart::BOTTOM_TOWER,
|
|
|
- EWallPart::BOTTOM_WALL,
|
|
|
- EWallPart::BELOW_GATE,
|
|
|
- EWallPart::OVER_GATE,
|
|
|
- EWallPart::UPPER_WALL,
|
|
|
- EWallPart::UPPER_TOWER,
|
|
|
- EWallPart::GATE
|
|
|
- };
|
|
|
-
|
|
|
- assert(potentialTargets.size() == size_t(EWallPart::PARTS_COUNT));
|
|
|
+ if(m->isMassive())
|
|
|
+ applyMassive(server, m); // Like earthquake
|
|
|
+ else
|
|
|
+ applyTargeted(server, m, eTarget); // Like catapult shots
|
|
|
+}
|
|
|
|
|
|
- std::set<EWallPart> allowedTargets;
|
|
|
|
|
|
- for (auto const & target : potentialTargets)
|
|
|
- {
|
|
|
- auto state = m->battle()->battleGetWallState(target);
|
|
|
+void Catapult::applyMassive(ServerCallback * server, const Mechanics * m) const
|
|
|
+{
|
|
|
+ //start with all destructible parts
|
|
|
+ std::vector<EWallPart> allowedTargets = getPotentialTargets(m, true, true);
|
|
|
|
|
|
- if(state != EWallState::DESTROYED && state != EWallState::NONE)
|
|
|
- allowedTargets.insert(target);
|
|
|
- }
|
|
|
assert(!allowedTargets.empty());
|
|
|
if (allowedTargets.empty())
|
|
|
return;
|
|
|
|
|
|
CatapultAttack ca;
|
|
|
- ca.attacker = -1;
|
|
|
+ ca.attacker = m->caster->getCasterUnitId();
|
|
|
|
|
|
for(int i = 0; i < targetsToAttack; i++)
|
|
|
{
|
|
@@ -97,7 +85,6 @@ void Catapult::apply(ServerCallback * server, const Mechanics * m, const EffectT
|
|
|
// Potential overshots (more hits on same targets than remaining HP) are allowed
|
|
|
EWallPart target = *RandomGeneratorUtil::nextItem(allowedTargets, *server->getRNG());
|
|
|
|
|
|
-
|
|
|
auto attackInfo = ca.attackedParts.begin();
|
|
|
for ( ; attackInfo != ca.attackedParts.end(); ++attackInfo)
|
|
|
if ( attackInfo->attackedPart == target )
|
|
@@ -105,8 +92,8 @@ void Catapult::apply(ServerCallback * server, const Mechanics * m, const EffectT
|
|
|
|
|
|
if (attackInfo == ca.attackedParts.end()) // new part
|
|
|
{
|
|
|
- CatapultAttack::AttackInfo newInfo{};
|
|
|
- newInfo.damageDealt = 1;
|
|
|
+ CatapultAttack::AttackInfo newInfo;
|
|
|
+ newInfo.damageDealt = getRandomDamage(server);
|
|
|
newInfo.attackedPart = target;
|
|
|
newInfo.destinationTile = m->battle()->wallPartToBattleHex(target);
|
|
|
ca.attackedParts.push_back(newInfo);
|
|
@@ -114,12 +101,96 @@ void Catapult::apply(ServerCallback * server, const Mechanics * m, const EffectT
|
|
|
}
|
|
|
else // already damaged before, update damage
|
|
|
{
|
|
|
- attackInfo->damageDealt += 1;
|
|
|
+ attackInfo->damageDealt += getRandomDamage(server);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
server->apply(&ca);
|
|
|
|
|
|
+ removeTowerShooters(server, m);
|
|
|
+}
|
|
|
+
|
|
|
+void Catapult::applyTargeted(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const
|
|
|
+{
|
|
|
+ assert(!target.empty());
|
|
|
+ auto destination = target.at(0).hexValue;
|
|
|
+ auto desiredTarget = m->battle()->battleHexToWallPart(destination);
|
|
|
+
|
|
|
+ for(int i = 0; i < targetsToAttack; i++)
|
|
|
+ {
|
|
|
+ auto actualTarget = EWallPart::INVALID;
|
|
|
+
|
|
|
+ if ( m->battle()->isWallPartAttackable(desiredTarget) &&
|
|
|
+ server->getRNG()->getInt64Range(0, 99)() < getCatapultHitChance(desiredTarget))
|
|
|
+ {
|
|
|
+ actualTarget = desiredTarget;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ std::vector<EWallPart> potentialTargets = getPotentialTargets(m, false, false);
|
|
|
+
|
|
|
+ if (potentialTargets.empty())
|
|
|
+ break; // everything is gone, can't attack anymore
|
|
|
+
|
|
|
+ actualTarget = *RandomGeneratorUtil::nextItem(potentialTargets, *server->getRNG());
|
|
|
+ }
|
|
|
+ assert(actualTarget != EWallPart::INVALID);
|
|
|
+
|
|
|
+ CatapultAttack::AttackInfo attack;
|
|
|
+ attack.attackedPart = actualTarget;
|
|
|
+ attack.destinationTile = m->battle()->wallPartToBattleHex(actualTarget);
|
|
|
+ attack.damageDealt = getRandomDamage(server);
|
|
|
+
|
|
|
+ CatapultAttack ca; //package for clients
|
|
|
+ ca.attacker = m->caster->getCasterUnitId();
|
|
|
+ ca.attackedParts.push_back(attack);
|
|
|
+ server->apply(&ca);
|
|
|
+ removeTowerShooters(server, m);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+int Catapult::getCatapultHitChance(EWallPart part) const
|
|
|
+{
|
|
|
+ switch(part)
|
|
|
+ {
|
|
|
+ case EWallPart::GATE:
|
|
|
+ return gate;
|
|
|
+ case EWallPart::KEEP:
|
|
|
+ return keep;
|
|
|
+ case EWallPart::BOTTOM_TOWER:
|
|
|
+ case EWallPart::UPPER_TOWER:
|
|
|
+ return tower;
|
|
|
+ case EWallPart::BOTTOM_WALL:
|
|
|
+ case EWallPart::BELOW_GATE:
|
|
|
+ case EWallPart::OVER_GATE:
|
|
|
+ case EWallPart::UPPER_WALL:
|
|
|
+ return wall;
|
|
|
+ default:
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+int Catapult::getRandomDamage (ServerCallback * server) const
|
|
|
+{
|
|
|
+ std::array<int, 3> damageChances = { noDmg, hit, crit }; //dmgChance[i] - chance for doing i dmg when hit is successful
|
|
|
+ int totalChance = std::accumulate(damageChances.begin(), damageChances.end(), 0);
|
|
|
+ int damageRandom = server->getRNG()->getInt64Range(0, totalChance - 1)();
|
|
|
+ int dealtDamage = 0;
|
|
|
+
|
|
|
+ //calculating dealt damage
|
|
|
+ for (int damage = 0; damage < damageChances.size(); ++damage)
|
|
|
+ {
|
|
|
+ if (damageRandom <= damageChances[damage])
|
|
|
+ {
|
|
|
+ dealtDamage = damage;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ damageRandom -= damageChances[damage];
|
|
|
+ }
|
|
|
+ return dealtDamage;
|
|
|
+}
|
|
|
+
|
|
|
+void Catapult::removeTowerShooters(ServerCallback * server, const Mechanics * m) const
|
|
|
+{
|
|
|
BattleUnitsChanged removeUnits;
|
|
|
|
|
|
for (auto const wallPart : { EWallPart::KEEP, EWallPart::BOTTOM_TOWER, EWallPart::UPPER_TOWER })
|
|
@@ -158,10 +229,51 @@ void Catapult::apply(ServerCallback * server, const Mechanics * m, const EffectT
|
|
|
server->apply(&removeUnits);
|
|
|
}
|
|
|
|
|
|
+std::vector<EWallPart> Catapult::getPotentialTargets(const Mechanics * m, bool bypassGateCheck, bool bypassTowerCheck) const
|
|
|
+{
|
|
|
+ std::vector<EWallPart> potentialTargets;
|
|
|
+ constexpr std::array<EWallPart, 4> walls = { EWallPart::BOTTOM_WALL, EWallPart::BELOW_GATE, EWallPart::OVER_GATE, EWallPart::UPPER_WALL };
|
|
|
+ constexpr std::array<EWallPart, 3> towers= { EWallPart::BOTTOM_TOWER, EWallPart::KEEP, EWallPart::UPPER_TOWER };
|
|
|
+ constexpr EWallPart gates = EWallPart::GATE;
|
|
|
+
|
|
|
+ // in H3, catapult under automatic control will attack objects in following order:
|
|
|
+ // walls, gates, towers
|
|
|
+ for (auto & part : walls)
|
|
|
+ if (m->battle()->isWallPartAttackable(part))
|
|
|
+ potentialTargets.push_back(part);
|
|
|
+
|
|
|
+ if ((potentialTargets.empty() || bypassGateCheck) && (m->battle()->isWallPartAttackable(gates)))
|
|
|
+ potentialTargets.push_back(gates);
|
|
|
+
|
|
|
+ if (potentialTargets.empty() || bypassTowerCheck)
|
|
|
+ for (auto & part : towers)
|
|
|
+ if (m->battle()->isWallPartAttackable(part))
|
|
|
+ potentialTargets.push_back(part);
|
|
|
+
|
|
|
+ return potentialTargets;
|
|
|
+}
|
|
|
+
|
|
|
+void Catapult::adjustHitChance()
|
|
|
+{
|
|
|
+ vstd::abetween(keep, 0, 100);
|
|
|
+ vstd::abetween(tower, 0, 100);
|
|
|
+ vstd::abetween(gate, 0, 100);
|
|
|
+ vstd::abetween(wall, 0, 100);
|
|
|
+ vstd::abetween(crit, 0, 100);
|
|
|
+ vstd::abetween(hit, 0, 100 - crit);
|
|
|
+ vstd::amin(noDmg, 100 - hit - crit);
|
|
|
+}
|
|
|
+
|
|
|
void Catapult::serializeJsonEffect(JsonSerializeFormat & handler)
|
|
|
{
|
|
|
- //TODO: add configuration unifying with Catapult ability
|
|
|
handler.serializeInt("targetsToAttack", targetsToAttack);
|
|
|
+ handler.serializeInt("chanceToHitKeep", keep);
|
|
|
+ handler.serializeInt("chanceToHitGate", gate);
|
|
|
+ handler.serializeInt("chanceToHitTower", tower);
|
|
|
+ handler.serializeInt("chanceToHitWall", wall);
|
|
|
+ handler.serializeInt("chanceToNormalHit", hit);
|
|
|
+ handler.serializeInt("chanceToCrit", crit);
|
|
|
+ adjustHitChance();
|
|
|
}
|
|
|
|
|
|
|