3 #include "AttackChoice.h"
4 #include "PartyLayout.h"
5 #include "TargetSelection.h"
6 #include "../common/GameState.h"
7 #include "../common/Stats.h"
8 #include "../common/Upgrade.h"
13 using common::GameState;
15 using common::Upgrade;
22 const PartyLayout *heroesLayout,
23 const PartyLayout *monstersLayout)
24 : heroesLayout(heroesLayout)
25 , monstersLayout(monstersLayout)
31 throw std::invalid_argument("construct battle without heroes layout");
33 if (!monstersLayout) {
34 throw std::invalid_argument("construct battle without monsters layout");
37 monsters.resize(monstersLayout->NumPositions());
41 void Battle::AddHero(const Hero &h) {
42 if (NumHeroes() == MaxHeroes()) {
43 throw std::overflow_error("max heroes breached");
46 heroes.back().GetAttackChoice() = AttackChoice(this);
49 void Battle::AddMonster(const Monster &m) {
50 for (int i = 0; i < MaxMonsters(); ++i) {
51 if (!MonsterPositionOccupied(i)) {
53 MonsterAt(i).GetAttackChoice() = AttackChoice(this);
57 throw std::overflow_error("all monster positions occupied");
60 void Battle::SetCapsule(const Capsule &c) {
65 int Battle::NumHeroes() const {
69 int Battle::MaxHeroes() const {
70 return heroesLayout->NumPositions() - 1;
73 int Battle::NumMonsters() const {
74 return monsters.size();
77 int Battle::MaxMonsters() const {
78 return monstersLayout->NumPositions();
81 bool Battle::HasCapsule() const {
82 return capsule.Active();
86 Hero &Battle::HeroAt(int index) {
87 assert(index >= 0 && index < NumHeroes() && "hero index out of bounds");
91 const Hero &Battle::HeroAt(int index) const {
92 assert(index >= 0 && index < NumHeroes() && "hero index out of bounds");
97 Monster &Battle::MonsterAt(int index) {
98 assert(index >= 0 && index < NumMonsters() && "monster index out of bounds");
99 return monsters[index];
102 const Monster &Battle::MonsterAt(int index) const {
103 assert(index >= 0 && index < NumMonsters() && "monster index out of bounds");
104 return monsters[index];
107 bool Battle::HeroPositionOccupied(int index) const {
108 return index >= 0 && index < NumHeroes();
111 bool Battle::HeroAlive(int index) const {
112 return HeroPositionOccupied(index) && HeroAt(index).Health() > 0;
116 bool Battle::MonsterPositionOccupied(int index) const {
117 return MonsterAlive(index);
120 bool Battle::MonsterAlive(int index) const {
121 return index >= 0 && index < NumMonsters() && MonsterAt(index).Health() > 0;
124 bool Battle::CapsuleAlive() const {
125 return capsule.Active() && capsule.Health() > 0;
129 void Battle::NextHero() {
131 while (activeHero < NumHeroes() && HeroAt(activeHero).Health() == 0) {
136 void Battle::PreviousHero() {
138 while (activeHero >= 0 && HeroAt(activeHero).Health() == 0) {
143 void Battle::SwapHeroes(int lhs, int rhs) {
144 if (lhs < 0 || lhs >= NumHeroes() || rhs < 0 || rhs >= NumHeroes() || lhs == rhs) return;
145 std::swap(HeroAt(lhs), HeroAt(rhs));
148 bool Battle::HasChosenAttackType() const {
149 return ActiveHero().GetAttackChoice().GetType() != AttackChoice::UNDECIDED;
152 bool Battle::AttackSelectionDone() const {
153 return activeHero >= NumHeroes();
159 OrderCompare(Battle *battle) : battle(battle) { }
160 bool operator ()(const Battle::Order &lhs, const Battle::Order &rhs) {
161 return lhs.GetStats(*battle).Agility() > rhs.GetStats(*battle).Agility();
168 void Battle::CalculateAttackOrder() {
169 attackOrder.reserve(NumMonsters() + NumHeroes() + 1);
170 for (int i(0); i < NumHeroes(); ++i) {
171 attackOrder.push_back(Order(Order::HERO, i));
173 for (std::vector<Monster>::size_type i(0), end(NumMonsters()); i < end; ++i) {
174 attackOrder.push_back(Order(Order::MONSTER, i));
175 MonsterAt(i).GetAttackChoice() = AttackChoice(this);
177 if (GetCapsule().Active() && GetCapsule().Health() > 0) {
178 attackOrder.push_back(Order(Order::CAPSULE));
180 std::sort(attackOrder.begin(), attackOrder.end(), OrderCompare(this));
183 void Battle::NextAttack() {
184 if (Victory() || Defeat()) {
185 attackCursor = attackOrder.size();
189 while (attackCursor < int(attackOrder.size())) {
190 if (attackOrder[attackCursor].IsMonster()) {
191 if (MonsterAlive(attackOrder[attackCursor].index)) break;
192 } else if (attackOrder[attackCursor].IsHero()) {
193 if (HeroAlive(attackOrder[attackCursor].index)) break;
195 if (CapsuleAlive()) break;
201 bool Battle::AttacksFinished() const {
202 return attackCursor >= int(attackOrder.size())
203 || Victory() || Defeat();
206 void Battle::CalculateDamage() {
207 if (CurrentAttack().IsMonster()) {
208 DecideMonsterAttack(MonsterAt(CurrentAttack().index));
209 } else if (CurrentAttack().IsCapsule()) {
210 DecideCapsuleAttack();
212 AttackChoice &ac = CurrentAttack().GetAttackChoice(*this);
213 if (ac.GetType() == AttackChoice::DEFEND) return;
214 TargetSelection &ts(ac.Selection());
216 const Stats &attackerStats = CurrentAttack().GetStats(*this);
217 CalculateDamage(attackerStats, ts);
220 AttackChoice &Battle::Order::GetAttackChoice(Battle &b) const {
223 return b.HeroAt(index).GetAttackChoice();
225 return b.GetCapsule().GetAttackChoice();
227 return b.MonsterAt(index).GetAttackChoice();
229 throw std::runtime_error("invalid case in Battle::Order::GetAttackChoice()");
233 Stats &Battle::Order::GetStats(Battle &b) const {
236 return b.HeroAt(index).GetStats();
238 return b.GetCapsule().GetStats();
240 return b.MonsterAt(index).GetStats();
242 throw std::runtime_error("invalid case in BttleStats::Order::GetAttackChoice()");
246 void Battle::ApplyDamage() {
247 if (attackCursor < 0) return;
248 AttackChoice &ac = CurrentAttack().GetAttackChoice(*this);
249 TargetSelection &ts(ac.Selection());
250 if (ts.TargetsMonsters()) {
251 for (int i(0); i < NumMonsters(); ++i) {
252 Monster &monster(MonsterAt(i));
254 monster.SubtractHealth(ts.GetAmount(i));
255 if (monster.Health() == 0) {
256 expReward += monster.ExpReward();
257 goldReward += monster.GoldReward();
262 for (int i(0); i < NumHeroes(); ++i) {
263 Hero &hero(HeroAt(i));
265 hero.SubtractHealth(ts.GetAmount(i));
271 const Battle::Order &Battle::CurrentAttack() const {
272 assert(attackCursor >= 0 && attackCursor < int(attackOrder.size()));
273 return attackOrder[attackCursor];
276 AttackChoice &Battle::CurrentAttackAttackChoice() {
277 return CurrentAttack().GetAttackChoice(*this);
280 void Battle::ClearAllAttacks() {
283 for (int i(0); i < NumHeroes(); ++i) {
284 HeroAt(i).GetAttackChoice() = AttackChoice(this);
286 for (int i(0); i < NumMonsters(); ++i) {
287 MonsterAt(i).GetAttackChoice() = AttackChoice(this);
289 GetCapsule().GetAttackChoice() = AttackChoice(this);
294 void Battle::DecideMonsterAttack(Monster &m) {
295 AttackChoice &ac(m.GetAttackChoice());
296 TargetSelection &ts(ac.Selection());
298 int target(rand() % NumHeroes());
299 while (!HeroAlive(target)) {
300 target = rand() % NumHeroes();
302 ac.SetType(AttackChoice::SWORD);
308 void Battle::DecideCapsuleAttack() {
309 AttackChoice &ac(GetCapsule().GetAttackChoice());
310 TargetSelection &ts(ac.Selection());
312 int target(rand() % NumMonsters());
313 while (!MonsterAlive(target)) {
314 target = rand() % NumMonsters();
316 ac.SetType(AttackChoice::SWORD);
322 void Battle::CalculateDamage(const Stats &attackerStats, TargetSelection &ts) const {
324 if (ts.TargetsMonsters()) {
325 for (int i(0); i < MaxMonsters(); ++i) {
326 if (ts.IsSelected(i)) {
327 if (MonsterAt(i).Health() > 0) {
328 const Stats &defenderStats(MonsterAt(i).GetStats());
329 Uint16 damage(CalculateDamage(attackerStats, defenderStats));
330 ts.SetBad(i, damage);
338 for (int i(0); i < MaxMonsters(); ++i) {
339 if (MonsterAt(i).Health() > 0) {
340 const Stats &defenderStats(MonsterAt(i).GetStats());
341 Uint16 damage(CalculateDamage(attackerStats, defenderStats));
342 ts.SetBad(i, damage);
347 for (int i(0); i < NumHeroes(); ++i) {
348 if (ts.IsSelected(i)) {
349 if (HeroAt(i).Health() > 0) {
350 const Stats &defenderStats(HeroAt(i).GetStats());
351 Uint16 damage(CalculateDamage(attackerStats, defenderStats));
352 ts.SetBad(i, damage);
360 for (int i(0); i < NumHeroes(); ++i) {
361 if (HeroAt(i).Health() > 0) {
362 const Stats &defenderStats(HeroAt(i).GetStats());
363 Uint16 damage(CalculateDamage(attackerStats, defenderStats));
364 ts.SetBad(i, damage);
371 Uint16 Battle::CalculateDamage(const Stats &attacker, const Stats &defender) const {
372 return attacker.Attack() / 2 - defender.Defense() / 4;
376 bool Battle::Victory() const {
377 for (int i(0); i < NumMonsters(); ++i) {
378 if (MonsterAlive(i)) return false;
383 bool Battle::Defeat() const {
384 for (int i(0); i < NumHeroes(); ++i) {
385 if (HeroAlive(i)) return false;
391 void Battle::ApplyRewards(
393 vector<Upgrade> &info) {
394 for (vector<Hero>::iterator i(HeroesBegin()), end(HeroesEnd());
396 if (i->Health() <= 0) continue;
397 i->Master().AddExperience(expReward, info);
399 if (capsule.Health() > 0) {
400 capsule.Master().AddExperience(expReward, info);
402 state.money += goldReward;