initial commit, lordsawar source, slightly modified
[lordsawar] / src / fight.cpp
1 // Copyright (C) 2001, 2002, 2003 Michael Bartl
2 // Copyright (C) 2002, 2003, 2004, 2005, 2006 Ulf Lorenz
3 // Copyright (C) 2004, 2006 Andrea Paternesi
4 // Copyright (C) 2004 Bryan Duff
5 // Copyright (C) 2006, 2007, 2008 Ben Asselstine
6 // Copyright (C) 2008 Ole Laursen
7 //
8 //  This program is free software; you can redistribute it and/or modify
9 //  it under the terms of the GNU General Public License as published by
10 //  the Free Software Foundation; either version 3 of the License, or
11 //  (at your option) any later version.
12 //
13 //  This program is distributed in the hope that it will be useful,
14 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 //  GNU Library General Public License for more details.
17 //
18 //  You should have received a copy of the GNU General Public License
19 //  along with this program; if not, write to the Free Software
20 //  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 
21 //  02110-1301, USA.
22
23 #include "fight.h"
24 #include <algorithm>
25 #include <assert.h>
26 #include <stdlib.h>     // for random numbers
27 #include <math.h>       // for has_hit()
28 #include "army.h"
29 #include "hero.h"
30 #include "stacklist.h"
31 #include "player.h"
32 #include "playerlist.h"
33 #include "Item.h"
34 #include "GameMap.h"
35 #include "citylist.h"
36 #include "city.h"
37 #include "stack.h"
38 #include "Backpack.h"
39
40 //#define debug(x) {cerr<<__FILE__<<": "<<__LINE__<<": "<<x<<endl<<flush;}
41 #define debug(x)
42
43 // Helper class; the single units participating in the fight are saved with
44 // additional information. This should be a struct, but I don't know how to
45 // forward the declaration properly.
46 //! A particpant in a Fight.
47 class Fighter
48 {
49     public:
50         Fighter(Army* a, Vector<int> p);
51         
52         Army* army;
53         Vector<int> pos;       // location on the map (needed to calculate boni)
54         int terrain_strength;
55         //! needed for sorting
56         //bool operator() ( const Fighter* f1, const Fighter* f2 );
57
58 };
59
60 Fighter::Fighter(Army* a, Vector<int> p)
61     :army(a), pos(p)
62 {
63 }
64
65
66 //take a list of stacks and create an ordered list of armies
67 void Fight::orderArmies(std::list<Stack*> stacks, std::vector<Army*> &armies)
68 {
69   std::list<Stack*>::iterator it;
70   if (stacks.empty())
71     return;
72   for (it = stacks.begin(); it != stacks.end(); it++)
73     for (Stack::iterator sit = (*it)->begin(); sit != (*it)->end(); sit++)
74       armies.push_back((*sit));
75
76   //okay now sort the army list according to the player's fight order
77   std::sort(armies.begin(), armies.end(), Stack::armyCompareFightOrder);
78
79   return;
80 }
81
82 Fight::Fight(Stack* attacker, Stack* defender, FightType type)
83     : d_turn(0), d_result(DRAW), d_type(type)
84 {
85
86     debug("Fight between " <<attacker->getId() <<" and " <<defender->getId())
87     
88     // Duel case: two stacks fight each other; Nothing further to be done
89     // Important: We always assume that the attacking/defending stacks are
90     // the first in the list!!!
91     d_attackers.push_back(attacker);
92     d_defenders.push_back(defender);
93
94     // What we do here: In the setup, we need to find out all armies that
95     // participate in the fight.  If a city is being attacked then the
96     // defender gets any other stacks in the cities.
97     //
98
99     City *city = GameMap::getCity(defender->getPos());
100     Vector<int> p = defender->getPos();
101
102
103     if (city)
104       {
105         std::vector<Stack*> stacks = city->getDefenders();
106         for (std::vector<Stack*>::iterator it = stacks.begin(); 
107              it != stacks.end(); it++)
108           {
109             Stack *s = *it;
110             if (s == d_defenders.front())
111               continue;
112             d_defenders.push_back(s);
113           }
114       }
115         
116     std::list<Stack*>::iterator it;
117     
118     //setup fighters
119     it = d_defenders.begin();
120     std::vector<Army*> def;
121     orderArmies (d_defenders, def);
122     for (std::vector<Army*>::iterator ait = def.begin(); ait != def.end(); ait++)
123       {
124         Fighter* f = new Fighter((*ait), (*it)->getPos());
125         d_def_close.push_back(f);
126       }
127
128     it = d_attackers.begin();
129     std::vector<Army*> att;
130     orderArmies (d_attackers, att);
131     for (std::vector<Army*>::iterator ait = att.begin(); ait != att.end(); ait++)
132       {
133         Fighter* f = new Fighter((*ait), (*it)->getPos());
134         d_att_close.push_back(f);
135       }
136
137   fillInInitialHPs();
138
139   // Before the battle starts, calculate the bonuses
140   // bonuses remain even if the unit providing a stackwide bonus dies
141
142
143   calculateBonus();
144 }
145
146 Fight::Fight(std::list<Stack*> attackers, std::list<Stack*> defenders,
147              std::list<FightItem> history)
148 {
149   d_attackers = attackers;
150   d_defenders = defenders;
151   d_actions = history;
152
153   fillInInitialHPs();
154 }
155
156 Fight::~Fight()
157 {
158       
159   d_attackers.clear();
160   d_defenders.clear();
161
162   // clear all fighter items in all lists
163   while (!d_att_close.empty())
164     {
165       delete (*d_att_close.begin());
166       d_att_close.erase(d_att_close.begin());
167     }
168
169   while (!d_def_close.empty())
170     {
171       delete (*d_def_close.begin());
172       d_def_close.erase(d_def_close.begin());
173     }
174 }
175
176 void Fight::battle(bool intense)
177 {
178   d_intense_combat = intense;
179
180   // first, fight until the fight is over
181   for (d_turn = 0; doRound(); d_turn++);
182
183   // Now we have to set the fight result.
184
185   // First, look if the attacker died; the attacking stack is the first
186   // one in the list
187   bool survivor = false;
188   Stack* s = d_attackers.front();
189   for (Stack::const_iterator it = s->begin(); it != s->end(); it++)
190     if ((*it)->getHP() > 0)
191       {
192         survivor = true;
193         break;
194       }
195
196   if (!survivor)
197       d_result = DEFENDER_WON;
198   else
199     {
200       // Now look if the defender died; also the first in the list
201       survivor = false;
202       s = d_defenders.front();
203       for (Stack::const_iterator it = s->begin(); it != s->end(); it++)
204         if ((*it)->getHP() > 0)
205           {
206             survivor = true;
207             break;
208           }
209
210       if (!survivor)
211         d_result = ATTACKER_WON;
212     }
213
214   if (d_type == FOR_KICKS)
215     {
216       //fixme: this will heal armies who happen to have a single hitpoint left.
217       std::list<Stack*>::iterator it;
218       //heal the attackers and defenders to full hit points
219       for (it = d_attackers.begin(); it != d_attackers.end(); it++)
220         for (Stack::iterator sit = (*it)->begin(); sit != (*it)->end(); sit++)
221           (*sit)->heal((*sit)->getStat(Army::HP));
222
223       for (it = d_defenders.begin(); it != d_defenders.end(); it++)
224         for (Stack::iterator sit = (*it)->begin(); sit != (*it)->end(); sit++)
225           (*sit)->heal((*sit)->getStat(Army::HP));
226     }
227 }
228
229 Army *findArmyById(const std::list<Stack *> &l, guint32 id)
230 {
231   for (std::list<Stack *>::const_iterator i = l.begin(), end = l.end();
232        i != end; ++i) {
233     Army *a = (*i)->getArmyById(id);
234     if (a)
235       return a;
236   }
237
238   return 0;
239 }
240
241 Fight::Result Fight::battleFromHistory()
242 {
243   for (std::list<FightItem>::iterator i = d_actions.begin(),
244          end = d_actions.end(); i != end; ++i) {
245     FightItem &f = *i;
246
247     Army *a = findArmyById(d_attackers, f.id);
248     if (!a)
249       a = findArmyById(d_defenders, f.id);
250
251     if (!a)
252       {
253         printf ("uh oh.  army id %d can't be found in the battle.\n", f.id);
254         printf ("do we have an army id of that in the stacklist anywhere?\n");
255       assert (false);
256       }
257     a->damage(f.damage);
258   }
259   //is there anybody alive in the attackers?
260   for (std::list<Stack*>::iterator it = d_attackers.begin(); it != d_attackers.end(); it++)
261     {
262       for (Stack::iterator i = (*it)->begin(); i != (*it)->end(); i++)
263         {
264           if ((*i)->getHP() > 0)
265             return Fight::ATTACKER_WON;
266         }
267     }
268
269   return Fight::DEFENDER_WON;
270 }
271
272 bool Fight::doRound()
273 {
274   if (MAX_ROUNDS && d_turn >= MAX_ROUNDS)
275     return false;
276
277   debug ("Fight round #" <<d_turn);
278
279   //fight the first one in attackers with the first one in defenders
280   std::list<Fighter*>::iterator ffit = d_att_close.begin();
281   std::list<Fighter*>::iterator efit = d_def_close.begin();
282
283   //have the attacker and defender try to hit each other
284   fightArmies(*ffit, *efit);
285
286   if (*efit && (*efit)->army->getHP() <= 0)
287     remove((*efit));
288
289   if (*ffit && (*ffit)->army->getHP() <= 0)
290     remove((*ffit));
291
292   if (d_def_close.empty() || d_att_close.empty())
293     return false;
294
295   return true;
296 }
297
298 void Fight::calculateBaseStrength(std::list<Fighter*> fighters)
299 {
300   std::list<Fighter*>::iterator fit;
301   for (fit = fighters.begin(); fit != fighters.end(); fit++)
302     {
303       if ((*fit)->army->getStat(Army::SHIP))
304         (*fit)->terrain_strength = 4;
305       else
306         (*fit)->terrain_strength = (*fit)->army->getStat(Army::STRENGTH);
307     }
308 }
309
310 void Fight::calculateTerrainModifiers(std::list<Fighter*> fighters)
311
312   guint32 army_bonus;
313   GameMap *gm = GameMap::getInstance();
314   Maptile *mtile;
315   std::list<Fighter*>::iterator fit;
316   for (fit = d_att_close.begin(); fit != d_att_close.end(); fit++)
317     {
318       if ((*fit)->army->getStat(Army::SHIP))
319         continue;
320
321       mtile = gm->getTile((*fit)->pos);
322       army_bonus = (*fit)->army->getStat(Army::ARMY_BONUS);
323
324       if (army_bonus & Army::ADD1STRINOPEN && mtile->isOpenTerrain())
325         (*fit)->terrain_strength += 1;
326
327       if (army_bonus & Army::ADD1STRINFOREST && 
328           mtile->getType() == Tile::FOREST && !mtile->isCityTerrain())
329         (*fit)->terrain_strength += 1;
330
331       if (army_bonus & Army::ADD1STRINHILLS && mtile->isHillyTerrain())
332         (*fit)->terrain_strength += 1;
333
334       if (army_bonus & Army::ADD1STRINCITY && mtile->isCityTerrain())
335         (*fit)->terrain_strength += 1;
336
337       if (army_bonus & Army::ADD2STRINCITY && mtile->isCityTerrain())
338         (*fit)->terrain_strength += 2;
339
340       if (army_bonus & Army::ADD2STRINOPEN && mtile->isOpenTerrain())
341         (*fit)->terrain_strength += 2;
342
343       if ((*fit)->terrain_strength > 9) //terrain strength can't ever exceed 9
344         (*fit)->terrain_strength = 9;
345
346     }
347 }
348
349 void Fight::calculateModifiedStrengths (std::list<Fighter*>friendly, 
350                                         std::list<Fighter*>enemy, 
351                                         bool friendlyIsDefending,
352                                         Hero *strongestHero)
353 {
354   guint32 army_bonus;
355   GameMap *gm = GameMap::getInstance();
356   Maptile *mtile;
357   std::list<Fighter*>::iterator fit;
358
359   //find highest non-hero bonus
360   guint32 highest_non_hero_bonus = 0;
361   for (fit = friendly.begin(); fit != friendly.end(); fit++)
362     {
363       guint32 non_hero_bonus = 0;
364       if ((*fit)->army->isHero())
365         continue;
366       mtile = gm->getTile((*fit)->pos);
367       army_bonus = (*fit)->army->getStat(Army::ARMY_BONUS);
368
369       if (army_bonus & Army::ADD1STACKINHILLS && mtile->isHillyTerrain())
370         non_hero_bonus += 1;
371
372       if (army_bonus & Army::ADD1STACK)
373         non_hero_bonus += 1;
374
375       if (army_bonus & Army::ADD2STACK)
376         non_hero_bonus += 2;
377
378       if (non_hero_bonus > highest_non_hero_bonus)
379         highest_non_hero_bonus = non_hero_bonus;
380     }
381
382   // does the defender cancel our non hero bonus?
383   for (fit = enemy.begin(); fit != enemy.end(); fit++)
384     {
385       army_bonus = (*fit)->army->getStat(Army::ARMY_BONUS);
386       if (army_bonus & Army::SUBALLNONHEROBONUS)
387         {
388           highest_non_hero_bonus = 0; //yes
389           break;
390         }
391     }
392
393   //find hero bonus of strongest hero
394   guint32 hero_bonus = 0;
395   if (strongestHero)
396     {
397       // first get command items from ALL heroes in the stack
398       for (fit = friendly.begin(); fit != friendly.end(); fit++)
399         {
400           if ((*fit)->army->isHero())
401             {
402               Hero *h = dynamic_cast<Hero*>((*fit)->army);
403               hero_bonus = h->getBackpack()->countStackStrengthBonuses();
404             }
405         }
406     }
407
408   //now add on the hero's natural command
409   if (strongestHero)
410     {
411       hero_bonus += strongestHero->calculateNaturalCommand();
412     }
413
414   // does the defender cancel our hero bonus?
415   for (fit = enemy.begin(); fit != enemy.end(); fit++)
416     {
417       army_bonus = (*fit)->army->getStat(Army::ARMY_BONUS);
418       if (army_bonus & Army::SUBALLHEROBONUS)
419         {
420           hero_bonus = 0; //yep
421           break;
422         }
423     }
424
425   guint32 fortify_bonus = 0;
426   for (fit = friendly.begin(); fit != friendly.end(); fit++)
427     {
428       army_bonus = (*fit)->army->getStat(Army::ARMY_BONUS);
429       if (army_bonus & Army::FORTIFY)
430         {
431           fortify_bonus = 1;
432           break;
433         }
434     }
435
436   guint32 city_bonus = 0;
437   if (friendlyIsDefending)
438     {
439       // calculate the city bonus
440       fit = friendly.begin();
441       mtile = gm->getTile((*fit)->pos);
442       City *c = Citylist::getInstance()->getNearestCity((*fit)->pos);
443       if (c && mtile->getBuilding() == Maptile::CITY)
444         {
445           if (c->isBurnt()) 
446             city_bonus = 0;
447           else
448             city_bonus = c->getDefenseLevel() - 1;
449         }
450       else
451         {
452           if (mtile->getBuilding() == Maptile::TEMPLE)
453             city_bonus = 2;
454           else if (mtile->getBuilding() == Maptile::RUIN)
455             city_bonus = 2;
456         }
457
458       // does the attacker cancel our city bonus?
459       for (fit = enemy.begin(); fit != enemy.end(); fit++)
460         {
461           army_bonus = (*fit)->army->getStat(Army::ARMY_BONUS);
462           if (army_bonus & Army::SUBALLCITYBONUS)
463             {
464               city_bonus = 0; //yep
465               break;
466             }
467         }
468     }
469
470   guint32 total_bonus = highest_non_hero_bonus + hero_bonus + fortify_bonus + 
471     city_bonus;
472
473   if (total_bonus > 5) //total bonus can't exceed 5
474     total_bonus = 5;
475
476   //add it to the terrain strength of each unit
477   for (fit = friendly.begin(); fit != friendly.end(); fit++)
478     {
479       (*fit)->terrain_strength += total_bonus;
480     }
481 }
482
483 void Fight::calculateFinalStrengths (std::list<Fighter*> friendly, std::list<Fighter*> enemy)
484 {
485   guint32 army_bonus;
486   std::list<Fighter*>::iterator efit;
487   std::list<Fighter*>::iterator ffit;
488   for (efit = enemy.begin(); efit != enemy.end(); efit++)
489     {
490       if ((*efit)->army->getStat(Army::SHIP))
491         continue;
492       army_bonus = (*efit)->army->getStat(Army::ARMY_BONUS);
493       if (army_bonus & Army::SUB1ENEMYSTACK)
494         {
495           for (ffit = friendly.begin(); ffit != friendly.end(); ffit++)
496             {
497               (*ffit)->terrain_strength -= 1;
498               if ((*ffit)->terrain_strength <= 0)
499                 (*ffit)->terrain_strength = 1;
500             }
501           break;
502         }
503     }
504 }
505
506 void Fight::calculateBonus()
507 {
508   // If there is a hero, add a +1 strength bonus
509   std::list<Stack*>::const_iterator it;
510   Stack::const_iterator sit;
511   std::list<Fighter*>::iterator fit;
512
513   // go get the base strengths of all attackers
514   // this includes items with battle bonuses for the hero
515   // naval units always have strength = 4
516   calculateBaseStrength (d_att_close);
517   calculateBaseStrength (d_def_close);
518
519   // now determine the terrain strength by adding the terrain modifiers 
520   // to the base strength
521   // naval units always have a strength of 4
522   calculateTerrainModifiers (d_att_close);
523   calculateTerrainModifiers (d_def_close);
524
525   //calculate hero, non-hero, city, and fortify bonuses
526   it = d_attackers.begin();
527   Army *a = (*it)->getStrongestHero();
528   Hero *h = dynamic_cast<Hero*>(a);
529   calculateModifiedStrengths (d_att_close, d_def_close, false, h);
530   Hero *strongestHero = 0;
531   guint32 highest_strength = 0;
532   for (it = d_defenders.begin(); it != d_defenders.end(); it++)
533     {
534       a = (*it)->getStrongestHero();
535       if (!a)
536         continue;
537       h = dynamic_cast<Hero*>(a);
538       if (h->getStat(Army::STRENGTH) > highest_strength)
539         {
540           highest_strength = h->getStat(Army::STRENGTH);
541           strongestHero = h;
542         }
543     }
544   calculateModifiedStrengths (d_def_close, d_att_close, true, strongestHero);
545
546   calculateFinalStrengths (d_att_close, d_def_close);
547   calculateFinalStrengths (d_def_close, d_att_close);
548
549 }
550
551 void Fight::fightArmies(Fighter* attacker, Fighter* defender)
552 {
553   static int misses_in_a_row;
554   guint32 sides = 0;
555
556   if (!attacker || !defender)
557     return;
558
559   Army *a = attacker->army;
560   Army *d = defender->army;
561
562   debug("Army " << a->getId() << " attacks " << d->getId())
563
564     if (d_intense_combat == true)
565       sides = 24;
566     else
567       sides = 20;
568
569   // factor used for some calculation regarding gaining medals
570   double xp_factor = a->getXpReward() / d->getXpReward();
571
572   // the clash has to be documented for later use in the fight dialog
573
574   // make a swing at the opponent
575   // take one hit point off, per hit.
576
577   FightItem item;
578   item.turn = d_turn;
579   int damage = 0;
580   item.id = d->getId();
581
582   while (damage == 0)
583     {
584       int attacker_roll = rand() % sides;
585       int defender_roll = rand() % sides;
586
587       if (attacker_roll <= attacker->terrain_strength &&
588           defender_roll > defender->terrain_strength)
589         {
590           //hit defender
591           if (d_type == FOR_KEEPS)
592             {
593               a->setNumberHasHit(a->getNumberHasHit() + (1/xp_factor));
594               d->setNumberHasBeenHit(d->getNumberHasBeenHit() + (1/xp_factor));
595             }
596           d->damage(1);
597           damage = 1;
598           item.id = d->getId();
599           misses_in_a_row = 0;
600         }
601       else if (defender_roll <= defender->terrain_strength &&
602                attacker_roll > attacker->terrain_strength)
603         {
604           //hit attacker
605           if (d_type == FOR_KEEPS)
606             {
607               d->setNumberHasHit(d->getNumberHasHit() + (1/xp_factor));
608               a->setNumberHasBeenHit(a->getNumberHasBeenHit() + (1/xp_factor));
609             }
610           a->damage(1);
611           damage = 1;
612           item.id = a->getId();
613           misses_in_a_row = 0;
614         }
615       else
616         {
617           misses_in_a_row++;
618           if (misses_in_a_row >= 10000)
619             {
620               //defender automatically wins
621               //hit attacker for however much it takes
622               if (d_type == FOR_KEEPS)
623                 {
624                   d->setNumberHasHit(d->getNumberHasHit() + (1/xp_factor));
625                   a->setNumberHasBeenHit(a->getNumberHasBeenHit() + 
626                                          (1/xp_factor));
627                 }
628               item.id = a->getId();
629               damage = a->getHP();
630               a->damage (damage);
631               misses_in_a_row = 0;
632             }
633         }
634     }
635   // continue documenting the engagement
636   item.damage = damage;
637   d_actions.push_back(item);
638
639 }
640
641 void Fight::remove(Fighter* f)
642 {
643   std::list<Fighter*>::iterator it;
644
645   // is the fighter in the attacker lists?
646   for (it = d_att_close.begin(); it != d_att_close.end(); it++)
647     if ((*it) == f)
648       {
649         d_att_close.erase(it);
650         delete f;
651         return;
652       }
653
654   // or in the defender lists?
655   for (it = d_def_close.begin(); it != d_def_close.end(); it++)
656     if ((*it) == f)
657       {
658         d_def_close.erase(it);
659         delete f;
660         return;
661       }
662
663   // if the fighter wa sin no list, we are rather careful and don't do anything
664   debug("Fight: fighter without list!")
665 }
666         
667 guint32 Fight::getModifiedStrengthBonus(Army *a)
668 {
669   std::list<Fighter*>::iterator it;
670   for (it = d_att_close.begin(); it != d_att_close.end(); it++)
671     if ((*it)->army == a)
672       return (*it)->terrain_strength;
673   for (it = d_def_close.begin(); it != d_def_close.end(); it++)
674     if ((*it)->army == a)
675       return (*it)->terrain_strength;
676   return 0;
677 }
678
679 void Fight::fillInInitialHPs()
680 {
681   for (std::list<Stack *>::iterator i = d_attackers.begin();
682        i != d_attackers.end(); ++i)
683     for (Stack::iterator j = (*i)->begin(); j != (*i)->end(); ++j)
684       initial_hps[(*j)->getId()] = (*j)->getHP();
685   
686   for (std::list<Stack *>::iterator i = d_defenders.begin();
687        i != d_defenders.end(); ++i)
688     for (Stack::iterator j = (*i)->begin(); j != (*i)->end(); ++j)
689       initial_hps[(*j)->getId()] = (*j)->getHP();
690 }
691         
692 LocationBox Fight::calculateFightBox(Fight &fight)
693 {
694   Citylist *cl = Citylist::getInstance();
695   Vector<int> dest = fight.getAttackers().front()->getPos();
696   if (cl->getObjectAt(dest) == NULL)
697     return LocationBox(dest);
698   Player *p = fight.getAttackers().front()->getOwner();
699   Stack *s = fight.getAttackers().front();
700   std::list<Vector<int> > tracks = p->getStackTrack(s);
701   if (tracks.size() >= 2)
702     {
703       std::list<Vector<int> >::iterator it = tracks.end();
704       it--; it--;
705       return LocationBox(*it, dest);
706     }
707   else
708     {
709       //this shouldn't be the case
710       return LocationBox(s->getPos(), dest);
711     }
712 }