3 #include "AttackChoice.h"
4 #include "PartyLayout.h"
5 #include "TargetSelection.h"
6 #include "../common/Stats.h"
17 const PartyLayout *heroesLayout,
18 const PartyLayout *monstersLayout)
19 : heroesLayout(heroesLayout)
20 , monstersLayout(monstersLayout)
26 throw std::invalid_argument("construct battle without heroes layout");
28 if (!monstersLayout) {
29 throw std::invalid_argument("construct battle without monsters layout");
32 monsters.resize(monstersLayout->NumPositions());
36 void Battle::AddHero(const Hero &h) {
37 if (NumHeroes() == MaxHeroes()) {
38 throw std::overflow_error("max heroes breached");
41 heroes.back().GetAttackChoice() = AttackChoice(this);
44 void Battle::AddMonster(const Monster &m) {
45 for (int i = 0; i < MaxMonsters(); ++i) {
46 if (!MonsterPositionOccupied(i)) {
48 MonsterAt(i).GetAttackChoice() = AttackChoice(this);
52 throw std::overflow_error("all monster positions occupied");
55 void Battle::SetCapsule(const Capsule &c) {
60 int Battle::NumHeroes() const {
64 int Battle::MaxHeroes() const {
65 return heroesLayout->NumPositions() - 1;
68 int Battle::NumMonsters() const {
69 return monsters.size();
72 int Battle::MaxMonsters() const {
73 return monstersLayout->NumPositions();
76 bool Battle::HasCapsule() const {
77 return capsule.Active();
81 Hero &Battle::HeroAt(int index) {
82 assert(index >= 0 && index < NumHeroes() && "hero index out of bounds");
86 const Hero &Battle::HeroAt(int index) const {
87 assert(index >= 0 && index < NumHeroes() && "hero index out of bounds");
92 Monster &Battle::MonsterAt(int index) {
93 assert(index >= 0 && index < NumMonsters() && "monster index out of bounds");
94 return monsters[index];
97 const Monster &Battle::MonsterAt(int index) const {
98 assert(index >= 0 && index < NumMonsters() && "monster index out of bounds");
99 return monsters[index];
102 bool Battle::HeroPositionOccupied(int index) const {
103 return index >= 0 && index < NumHeroes();
106 bool Battle::HeroAlive(int index) const {
107 return HeroPositionOccupied(index) && HeroAt(index).Health() > 0;
111 bool Battle::MonsterPositionOccupied(int index) const {
112 return MonsterAlive(index);
115 bool Battle::MonsterAlive(int index) const {
116 return index >= 0 && index < NumMonsters() && MonsterAt(index).Health() > 0;
119 bool Battle::CapsuleAlive() const {
120 return capsule.Active() && capsule.Health() > 0;
124 void Battle::NextHero() {
126 while (activeHero < NumHeroes() && HeroAt(activeHero).Health() == 0) {
131 void Battle::PreviousHero() {
133 while (activeHero >= 0 && HeroAt(activeHero).Health() == 0) {
138 void Battle::SwapHeroes(int lhs, int rhs) {
139 if (lhs < 0 || lhs >= NumHeroes() || rhs < 0 || rhs >= NumHeroes() || lhs == rhs) return;
140 std::swap(HeroAt(lhs), HeroAt(rhs));
143 bool Battle::HasChosenAttackType() const {
144 return ActiveHero().GetAttackChoice().GetType() != AttackChoice::UNDECIDED;
147 bool Battle::AttackSelectionDone() const {
148 return activeHero >= NumHeroes();
154 OrderCompare(Battle *battle) : battle(battle) { }
155 bool operator ()(const Battle::Order &lhs, const Battle::Order &rhs) {
156 return lhs.GetStats(*battle).Agility() > rhs.GetStats(*battle).Agility();
163 void Battle::CalculateAttackOrder() {
164 attackOrder.reserve(NumMonsters() + NumHeroes() + 1);
165 for (int i(0); i < NumHeroes(); ++i) {
166 attackOrder.push_back(Order(Order::HERO, i));
168 for (std::vector<Monster>::size_type i(0), end(NumMonsters()); i < end; ++i) {
169 attackOrder.push_back(Order(Order::MONSTER, i));
170 MonsterAt(i).GetAttackChoice() = AttackChoice(this);
172 if (GetCapsule().Active() && GetCapsule().Health() > 0) {
173 attackOrder.push_back(Order(Order::CAPSULE));
175 std::sort(attackOrder.begin(), attackOrder.end(), OrderCompare(this));
178 void Battle::NextAttack() {
179 if (Victory() || Defeat()) {
180 attackCursor = attackOrder.size();
184 while (attackCursor < int(attackOrder.size())) {
185 if (attackOrder[attackCursor].IsMonster()) {
186 if (MonsterAlive(attackOrder[attackCursor].index)) break;
187 } else if (attackOrder[attackCursor].IsHero()) {
188 if (HeroAlive(attackOrder[attackCursor].index)) break;
190 if (CapsuleAlive()) break;
196 bool Battle::AttacksFinished() const {
197 return attackCursor >= int(attackOrder.size())
198 || Victory() || Defeat();
201 void Battle::CalculateDamage() {
202 if (CurrentAttack().IsMonster()) {
203 DecideMonsterAttack(MonsterAt(CurrentAttack().index));
204 } else if (CurrentAttack().IsCapsule()) {
205 DecideCapsuleAttack();
207 AttackChoice &ac = CurrentAttack().GetAttackChoice(*this);
208 if (ac.GetType() == AttackChoice::DEFEND) return;
209 TargetSelection &ts(ac.Selection());
211 const Stats &attackerStats = CurrentAttack().GetStats(*this);
212 CalculateDamage(attackerStats, ts);
215 AttackChoice &Battle::Order::GetAttackChoice(Battle &b) const {
218 return b.HeroAt(index).GetAttackChoice();
220 return b.GetCapsule().GetAttackChoice();
222 return b.MonsterAt(index).GetAttackChoice();
224 throw std::runtime_error("invalid case in Battle::Order::GetAttackChoice()");
228 Stats &Battle::Order::GetStats(Battle &b) const {
231 return b.HeroAt(index).GetStats();
233 return b.GetCapsule().GetStats();
235 return b.MonsterAt(index).GetStats();
237 throw std::runtime_error("invalid case in BttleStats::Order::GetAttackChoice()");
241 void Battle::ApplyDamage() {
242 if (attackCursor < 0) return;
243 AttackChoice &ac = CurrentAttack().GetAttackChoice(*this);
244 TargetSelection &ts(ac.Selection());
245 if (ts.TargetsMonsters()) {
246 for (int i(0); i < NumMonsters(); ++i) {
247 Monster &monster(MonsterAt(i));
249 monster.SubtractHealth(ts.GetAmount(i));
250 if (monster.Health() == 0) {
251 expReward += monster.ExpReward();
252 goldReward += monster.GoldReward();
257 for (int i(0); i < NumHeroes(); ++i) {
258 Hero &hero(HeroAt(i));
260 hero.SubtractHealth(ts.GetAmount(i));
266 const Battle::Order &Battle::CurrentAttack() const {
267 assert(attackCursor >= 0 && attackCursor < int(attackOrder.size()));
268 return attackOrder[attackCursor];
271 AttackChoice &Battle::CurrentAttackAttackChoice() {
272 return CurrentAttack().GetAttackChoice(*this);
275 void Battle::ClearAllAttacks() {
278 for (int i(0); i < NumHeroes(); ++i) {
279 HeroAt(i).GetAttackChoice() = AttackChoice(this);
281 for (int i(0); i < NumMonsters(); ++i) {
282 MonsterAt(i).GetAttackChoice() = AttackChoice(this);
284 GetCapsule().GetAttackChoice() = AttackChoice(this);
289 void Battle::DecideMonsterAttack(Monster &m) {
290 AttackChoice &ac(m.GetAttackChoice());
291 TargetSelection &ts(ac.Selection());
293 int target(rand() % NumHeroes());
294 while (!HeroAlive(target)) {
295 target = rand() % NumHeroes();
297 ac.SetType(AttackChoice::SWORD);
303 void Battle::DecideCapsuleAttack() {
304 AttackChoice &ac(GetCapsule().GetAttackChoice());
305 TargetSelection &ts(ac.Selection());
307 int target(rand() % NumMonsters());
308 while (!MonsterAlive(target)) {
309 target = rand() % NumMonsters();
311 ac.SetType(AttackChoice::SWORD);
317 void Battle::CalculateDamage(const Stats &attackerStats, TargetSelection &ts) const {
319 if (ts.TargetsMonsters()) {
320 for (int i(0); i < MaxMonsters(); ++i) {
321 if (ts.IsSelected(i)) {
322 if (MonsterAt(i).Health() > 0) {
323 const Stats &defenderStats(MonsterAt(i).GetStats());
324 Uint16 damage(CalculateDamage(attackerStats, defenderStats));
325 ts.SetBad(i, damage);
333 for (int i(0); i < MaxMonsters(); ++i) {
334 if (MonsterAt(i).Health() > 0) {
335 const Stats &defenderStats(MonsterAt(i).GetStats());
336 Uint16 damage(CalculateDamage(attackerStats, defenderStats));
337 ts.SetBad(i, damage);
342 for (int i(0); i < NumHeroes(); ++i) {
343 if (ts.IsSelected(i)) {
344 if (HeroAt(i).Health() > 0) {
345 const Stats &defenderStats(HeroAt(i).GetStats());
346 Uint16 damage(CalculateDamage(attackerStats, defenderStats));
347 ts.SetBad(i, damage);
355 for (int i(0); i < NumHeroes(); ++i) {
356 if (HeroAt(i).Health() > 0) {
357 const Stats &defenderStats(HeroAt(i).GetStats());
358 Uint16 damage(CalculateDamage(attackerStats, defenderStats));
359 ts.SetBad(i, damage);
366 Uint16 Battle::CalculateDamage(const Stats &attacker, const Stats &defender) const {
367 return attacker.Attack() / 2 - defender.Defense() / 4;
371 bool Battle::Victory() const {
372 for (int i(0); i < NumMonsters(); ++i) {
373 if (MonsterAlive(i)) return false;
378 bool Battle::Defeat() const {
379 for (int i(0); i < NumHeroes(); ++i) {
380 if (HeroAlive(i)) return false;