initial commit, lordsawar source, slightly modified
[lordsawar] / src / fight.cpp
diff --git a/src/fight.cpp b/src/fight.cpp
new file mode 100644 (file)
index 0000000..d7d213d
--- /dev/null
@@ -0,0 +1,712 @@
+// Copyright (C) 2001, 2002, 2003 Michael Bartl
+// Copyright (C) 2002, 2003, 2004, 2005, 2006 Ulf Lorenz
+// Copyright (C) 2004, 2006 Andrea Paternesi
+// Copyright (C) 2004 Bryan Duff
+// Copyright (C) 2006, 2007, 2008 Ben Asselstine
+// Copyright (C) 2008 Ole Laursen
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation; either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU Library General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program; if not, write to the Free Software
+//  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 
+//  02110-1301, USA.
+
+#include "fight.h"
+#include <algorithm>
+#include <assert.h>
+#include <stdlib.h>     // for random numbers
+#include <math.h>       // for has_hit()
+#include "army.h"
+#include "hero.h"
+#include "stacklist.h"
+#include "player.h"
+#include "playerlist.h"
+#include "Item.h"
+#include "GameMap.h"
+#include "citylist.h"
+#include "city.h"
+#include "stack.h"
+#include "Backpack.h"
+
+//#define debug(x) {cerr<<__FILE__<<": "<<__LINE__<<": "<<x<<endl<<flush;}
+#define debug(x)
+
+// Helper class; the single units participating in the fight are saved with
+// additional information. This should be a struct, but I don't know how to
+// forward the declaration properly.
+//! A particpant in a Fight.
+class Fighter
+{
+    public:
+        Fighter(Army* a, Vector<int> p);
+        
+        Army* army;
+        Vector<int> pos;       // location on the map (needed to calculate boni)
+        int terrain_strength;
+       //! needed for sorting
+        //bool operator() ( const Fighter* f1, const Fighter* f2 );
+
+};
+
+Fighter::Fighter(Army* a, Vector<int> p)
+    :army(a), pos(p)
+{
+}
+
+
+//take a list of stacks and create an ordered list of armies
+void Fight::orderArmies(std::list<Stack*> stacks, std::vector<Army*> &armies)
+{
+  std::list<Stack*>::iterator it;
+  if (stacks.empty())
+    return;
+  for (it = stacks.begin(); it != stacks.end(); it++)
+    for (Stack::iterator sit = (*it)->begin(); sit != (*it)->end(); sit++)
+      armies.push_back((*sit));
+
+  //okay now sort the army list according to the player's fight order
+  std::sort(armies.begin(), armies.end(), Stack::armyCompareFightOrder);
+
+  return;
+}
+
+Fight::Fight(Stack* attacker, Stack* defender, FightType type)
+    : d_turn(0), d_result(DRAW), d_type(type)
+{
+
+    debug("Fight between " <<attacker->getId() <<" and " <<defender->getId())
+    
+    // Duel case: two stacks fight each other; Nothing further to be done
+    // Important: We always assume that the attacking/defending stacks are
+    // the first in the list!!!
+    d_attackers.push_back(attacker);
+    d_defenders.push_back(defender);
+
+    // What we do here: In the setup, we need to find out all armies that
+    // participate in the fight.  If a city is being attacked then the
+    // defender gets any other stacks in the cities.
+    //
+
+    City *city = GameMap::getCity(defender->getPos());
+    Vector<int> p = defender->getPos();
+
+
+    if (city)
+      {
+       std::vector<Stack*> stacks = city->getDefenders();
+       for (std::vector<Stack*>::iterator it = stacks.begin(); 
+            it != stacks.end(); it++)
+         {
+           Stack *s = *it;
+           if (s == d_defenders.front())
+             continue;
+           d_defenders.push_back(s);
+         }
+      }
+       
+    std::list<Stack*>::iterator it;
+    
+    //setup fighters
+    it = d_defenders.begin();
+    std::vector<Army*> def;
+    orderArmies (d_defenders, def);
+    for (std::vector<Army*>::iterator ait = def.begin(); ait != def.end(); ait++)
+      {
+       Fighter* f = new Fighter((*ait), (*it)->getPos());
+       d_def_close.push_back(f);
+      }
+
+    it = d_attackers.begin();
+    std::vector<Army*> att;
+    orderArmies (d_attackers, att);
+    for (std::vector<Army*>::iterator ait = att.begin(); ait != att.end(); ait++)
+      {
+       Fighter* f = new Fighter((*ait), (*it)->getPos());
+       d_att_close.push_back(f);
+      }
+
+  fillInInitialHPs();
+
+  // Before the battle starts, calculate the bonuses
+  // bonuses remain even if the unit providing a stackwide bonus dies
+
+
+  calculateBonus();
+}
+
+Fight::Fight(std::list<Stack*> attackers, std::list<Stack*> defenders,
+             std::list<FightItem> history)
+{
+  d_attackers = attackers;
+  d_defenders = defenders;
+  d_actions = history;
+
+  fillInInitialHPs();
+}
+
+Fight::~Fight()
+{
+      
+  d_attackers.clear();
+  d_defenders.clear();
+
+  // clear all fighter items in all lists
+  while (!d_att_close.empty())
+    {
+      delete (*d_att_close.begin());
+      d_att_close.erase(d_att_close.begin());
+    }
+
+  while (!d_def_close.empty())
+    {
+      delete (*d_def_close.begin());
+      d_def_close.erase(d_def_close.begin());
+    }
+}
+
+void Fight::battle(bool intense)
+{
+  d_intense_combat = intense;
+
+  // first, fight until the fight is over
+  for (d_turn = 0; doRound(); d_turn++);
+
+  // Now we have to set the fight result.
+
+  // First, look if the attacker died; the attacking stack is the first
+  // one in the list
+  bool survivor = false;
+  Stack* s = d_attackers.front();
+  for (Stack::const_iterator it = s->begin(); it != s->end(); it++)
+    if ((*it)->getHP() > 0)
+      {
+       survivor = true;
+       break;
+      }
+
+  if (!survivor)
+      d_result = DEFENDER_WON;
+  else
+    {
+      // Now look if the defender died; also the first in the list
+      survivor = false;
+      s = d_defenders.front();
+      for (Stack::const_iterator it = s->begin(); it != s->end(); it++)
+       if ((*it)->getHP() > 0)
+         {
+           survivor = true;
+           break;
+         }
+
+      if (!survivor)
+       d_result = ATTACKER_WON;
+    }
+
+  if (d_type == FOR_KICKS)
+    {
+      //fixme: this will heal armies who happen to have a single hitpoint left.
+      std::list<Stack*>::iterator it;
+      //heal the attackers and defenders to full hit points
+      for (it = d_attackers.begin(); it != d_attackers.end(); it++)
+       for (Stack::iterator sit = (*it)->begin(); sit != (*it)->end(); sit++)
+         (*sit)->heal((*sit)->getStat(Army::HP));
+
+      for (it = d_defenders.begin(); it != d_defenders.end(); it++)
+       for (Stack::iterator sit = (*it)->begin(); sit != (*it)->end(); sit++)
+         (*sit)->heal((*sit)->getStat(Army::HP));
+    }
+}
+
+Army *findArmyById(const std::list<Stack *> &l, guint32 id)
+{
+  for (std::list<Stack *>::const_iterator i = l.begin(), end = l.end();
+       i != end; ++i) {
+    Army *a = (*i)->getArmyById(id);
+    if (a)
+      return a;
+  }
+
+  return 0;
+}
+
+Fight::Result Fight::battleFromHistory()
+{
+  for (std::list<FightItem>::iterator i = d_actions.begin(),
+         end = d_actions.end(); i != end; ++i) {
+    FightItem &f = *i;
+
+    Army *a = findArmyById(d_attackers, f.id);
+    if (!a)
+      a = findArmyById(d_defenders, f.id);
+
+    if (!a)
+      {
+       printf ("uh oh.  army id %d can't be found in the battle.\n", f.id);
+       printf ("do we have an army id of that in the stacklist anywhere?\n");
+      assert (false);
+      }
+    a->damage(f.damage);
+  }
+  //is there anybody alive in the attackers?
+  for (std::list<Stack*>::iterator it = d_attackers.begin(); it != d_attackers.end(); it++)
+    {
+      for (Stack::iterator i = (*it)->begin(); i != (*it)->end(); i++)
+       {
+         if ((*i)->getHP() > 0)
+           return Fight::ATTACKER_WON;
+       }
+    }
+
+  return Fight::DEFENDER_WON;
+}
+
+bool Fight::doRound()
+{
+  if (MAX_ROUNDS && d_turn >= MAX_ROUNDS)
+    return false;
+
+  debug ("Fight round #" <<d_turn);
+
+  //fight the first one in attackers with the first one in defenders
+  std::list<Fighter*>::iterator ffit = d_att_close.begin();
+  std::list<Fighter*>::iterator efit = d_def_close.begin();
+
+  //have the attacker and defender try to hit each other
+  fightArmies(*ffit, *efit);
+
+  if (*efit && (*efit)->army->getHP() <= 0)
+    remove((*efit));
+
+  if (*ffit && (*ffit)->army->getHP() <= 0)
+    remove((*ffit));
+
+  if (d_def_close.empty() || d_att_close.empty())
+    return false;
+
+  return true;
+}
+
+void Fight::calculateBaseStrength(std::list<Fighter*> fighters)
+{
+  std::list<Fighter*>::iterator fit;
+  for (fit = fighters.begin(); fit != fighters.end(); fit++)
+    {
+      if ((*fit)->army->getStat(Army::SHIP))
+       (*fit)->terrain_strength = 4;
+      else
+       (*fit)->terrain_strength = (*fit)->army->getStat(Army::STRENGTH);
+    }
+}
+
+void Fight::calculateTerrainModifiers(std::list<Fighter*> fighters)
+{ 
+  guint32 army_bonus;
+  GameMap *gm = GameMap::getInstance();
+  Maptile *mtile;
+  std::list<Fighter*>::iterator fit;
+  for (fit = d_att_close.begin(); fit != d_att_close.end(); fit++)
+    {
+      if ((*fit)->army->getStat(Army::SHIP))
+       continue;
+
+      mtile = gm->getTile((*fit)->pos);
+      army_bonus = (*fit)->army->getStat(Army::ARMY_BONUS);
+
+      if (army_bonus & Army::ADD1STRINOPEN && mtile->isOpenTerrain())
+       (*fit)->terrain_strength += 1;
+
+      if (army_bonus & Army::ADD1STRINFOREST && 
+         mtile->getType() == Tile::FOREST && !mtile->isCityTerrain())
+       (*fit)->terrain_strength += 1;
+
+      if (army_bonus & Army::ADD1STRINHILLS && mtile->isHillyTerrain())
+       (*fit)->terrain_strength += 1;
+
+      if (army_bonus & Army::ADD1STRINCITY && mtile->isCityTerrain())
+       (*fit)->terrain_strength += 1;
+
+      if (army_bonus & Army::ADD2STRINCITY && mtile->isCityTerrain())
+       (*fit)->terrain_strength += 2;
+
+      if (army_bonus & Army::ADD2STRINOPEN && mtile->isOpenTerrain())
+       (*fit)->terrain_strength += 2;
+
+      if ((*fit)->terrain_strength > 9) //terrain strength can't ever exceed 9
+       (*fit)->terrain_strength = 9;
+
+    }
+}
+
+void Fight::calculateModifiedStrengths (std::list<Fighter*>friendly, 
+                                       std::list<Fighter*>enemy, 
+                                       bool friendlyIsDefending,
+                                       Hero *strongestHero)
+{
+  guint32 army_bonus;
+  GameMap *gm = GameMap::getInstance();
+  Maptile *mtile;
+  std::list<Fighter*>::iterator fit;
+
+  //find highest non-hero bonus
+  guint32 highest_non_hero_bonus = 0;
+  for (fit = friendly.begin(); fit != friendly.end(); fit++)
+    {
+      guint32 non_hero_bonus = 0;
+      if ((*fit)->army->isHero())
+       continue;
+      mtile = gm->getTile((*fit)->pos);
+      army_bonus = (*fit)->army->getStat(Army::ARMY_BONUS);
+
+      if (army_bonus & Army::ADD1STACKINHILLS && mtile->isHillyTerrain())
+       non_hero_bonus += 1;
+
+      if (army_bonus & Army::ADD1STACK)
+       non_hero_bonus += 1;
+
+      if (army_bonus & Army::ADD2STACK)
+       non_hero_bonus += 2;
+
+      if (non_hero_bonus > highest_non_hero_bonus)
+       highest_non_hero_bonus = non_hero_bonus;
+    }
+
+  // does the defender cancel our non hero bonus?
+  for (fit = enemy.begin(); fit != enemy.end(); fit++)
+    {
+      army_bonus = (*fit)->army->getStat(Army::ARMY_BONUS);
+      if (army_bonus & Army::SUBALLNONHEROBONUS)
+       {
+         highest_non_hero_bonus = 0; //yes
+         break;
+       }
+    }
+
+  //find hero bonus of strongest hero
+  guint32 hero_bonus = 0;
+  if (strongestHero)
+    {
+      // first get command items from ALL heroes in the stack
+      for (fit = friendly.begin(); fit != friendly.end(); fit++)
+       {
+         if ((*fit)->army->isHero())
+           {
+             Hero *h = dynamic_cast<Hero*>((*fit)->army);
+             hero_bonus = h->getBackpack()->countStackStrengthBonuses();
+           }
+       }
+    }
+
+  //now add on the hero's natural command
+  if (strongestHero)
+    {
+      hero_bonus += strongestHero->calculateNaturalCommand();
+    }
+
+  // does the defender cancel our hero bonus?
+  for (fit = enemy.begin(); fit != enemy.end(); fit++)
+    {
+      army_bonus = (*fit)->army->getStat(Army::ARMY_BONUS);
+      if (army_bonus & Army::SUBALLHEROBONUS)
+       {
+         hero_bonus = 0; //yep
+         break;
+       }
+    }
+
+  guint32 fortify_bonus = 0;
+  for (fit = friendly.begin(); fit != friendly.end(); fit++)
+    {
+      army_bonus = (*fit)->army->getStat(Army::ARMY_BONUS);
+      if (army_bonus & Army::FORTIFY)
+       {
+         fortify_bonus = 1;
+         break;
+       }
+    }
+
+  guint32 city_bonus = 0;
+  if (friendlyIsDefending)
+    {
+      // calculate the city bonus
+      fit = friendly.begin();
+      mtile = gm->getTile((*fit)->pos);
+      City *c = Citylist::getInstance()->getNearestCity((*fit)->pos);
+      if (c && mtile->getBuilding() == Maptile::CITY)
+       {
+         if (c->isBurnt()) 
+           city_bonus = 0;
+         else
+           city_bonus = c->getDefenseLevel() - 1;
+       }
+      else
+       {
+         if (mtile->getBuilding() == Maptile::TEMPLE)
+           city_bonus = 2;
+         else if (mtile->getBuilding() == Maptile::RUIN)
+           city_bonus = 2;
+       }
+
+      // does the attacker cancel our city bonus?
+      for (fit = enemy.begin(); fit != enemy.end(); fit++)
+       {
+         army_bonus = (*fit)->army->getStat(Army::ARMY_BONUS);
+         if (army_bonus & Army::SUBALLCITYBONUS)
+           {
+             city_bonus = 0; //yep
+             break;
+           }
+       }
+    }
+
+  guint32 total_bonus = highest_non_hero_bonus + hero_bonus + fortify_bonus + 
+    city_bonus;
+
+  if (total_bonus > 5) //total bonus can't exceed 5
+    total_bonus = 5;
+
+  //add it to the terrain strength of each unit
+  for (fit = friendly.begin(); fit != friendly.end(); fit++)
+    {
+      (*fit)->terrain_strength += total_bonus;
+    }
+}
+
+void Fight::calculateFinalStrengths (std::list<Fighter*> friendly, std::list<Fighter*> enemy)
+{
+  guint32 army_bonus;
+  std::list<Fighter*>::iterator efit;
+  std::list<Fighter*>::iterator ffit;
+  for (efit = enemy.begin(); efit != enemy.end(); efit++)
+    {
+      if ((*efit)->army->getStat(Army::SHIP))
+       continue;
+      army_bonus = (*efit)->army->getStat(Army::ARMY_BONUS);
+      if (army_bonus & Army::SUB1ENEMYSTACK)
+       {
+         for (ffit = friendly.begin(); ffit != friendly.end(); ffit++)
+           {
+             (*ffit)->terrain_strength -= 1;
+             if ((*ffit)->terrain_strength <= 0)
+               (*ffit)->terrain_strength = 1;
+           }
+         break;
+       }
+    }
+}
+
+void Fight::calculateBonus()
+{
+  // If there is a hero, add a +1 strength bonus
+  std::list<Stack*>::const_iterator it;
+  Stack::const_iterator sit;
+  std::list<Fighter*>::iterator fit;
+
+  // go get the base strengths of all attackers
+  // this includes items with battle bonuses for the hero
+  // naval units always have strength = 4
+  calculateBaseStrength (d_att_close);
+  calculateBaseStrength (d_def_close);
+
+  // now determine the terrain strength by adding the terrain modifiers 
+  // to the base strength
+  // naval units always have a strength of 4
+  calculateTerrainModifiers (d_att_close);
+  calculateTerrainModifiers (d_def_close);
+
+  //calculate hero, non-hero, city, and fortify bonuses
+  it = d_attackers.begin();
+  Army *a = (*it)->getStrongestHero();
+  Hero *h = dynamic_cast<Hero*>(a);
+  calculateModifiedStrengths (d_att_close, d_def_close, false, h);
+  Hero *strongestHero = 0;
+  guint32 highest_strength = 0;
+  for (it = d_defenders.begin(); it != d_defenders.end(); it++)
+    {
+      a = (*it)->getStrongestHero();
+      if (!a)
+       continue;
+      h = dynamic_cast<Hero*>(a);
+      if (h->getStat(Army::STRENGTH) > highest_strength)
+       {
+         highest_strength = h->getStat(Army::STRENGTH);
+         strongestHero = h;
+       }
+    }
+  calculateModifiedStrengths (d_def_close, d_att_close, true, strongestHero);
+
+  calculateFinalStrengths (d_att_close, d_def_close);
+  calculateFinalStrengths (d_def_close, d_att_close);
+
+}
+
+void Fight::fightArmies(Fighter* attacker, Fighter* defender)
+{
+  static int misses_in_a_row;
+  guint32 sides = 0;
+
+  if (!attacker || !defender)
+    return;
+
+  Army *a = attacker->army;
+  Army *d = defender->army;
+
+  debug("Army " << a->getId() << " attacks " << d->getId())
+
+    if (d_intense_combat == true)
+      sides = 24;
+    else
+      sides = 20;
+
+  // factor used for some calculation regarding gaining medals
+  double xp_factor = a->getXpReward() / d->getXpReward();
+
+  // the clash has to be documented for later use in the fight dialog
+
+  // make a swing at the opponent
+  // take one hit point off, per hit.
+
+  FightItem item;
+  item.turn = d_turn;
+  int damage = 0;
+  item.id = d->getId();
+
+  while (damage == 0)
+    {
+      int attacker_roll = rand() % sides;
+      int defender_roll = rand() % sides;
+
+      if (attacker_roll <= attacker->terrain_strength &&
+         defender_roll > defender->terrain_strength)
+       {
+         //hit defender
+         if (d_type == FOR_KEEPS)
+           {
+             a->setNumberHasHit(a->getNumberHasHit() + (1/xp_factor));
+             d->setNumberHasBeenHit(d->getNumberHasBeenHit() + (1/xp_factor));
+           }
+         d->damage(1);
+         damage = 1;
+         item.id = d->getId();
+         misses_in_a_row = 0;
+       }
+      else if (defender_roll <= defender->terrain_strength &&
+              attacker_roll > attacker->terrain_strength)
+       {
+         //hit attacker
+         if (d_type == FOR_KEEPS)
+           {
+             d->setNumberHasHit(d->getNumberHasHit() + (1/xp_factor));
+             a->setNumberHasBeenHit(a->getNumberHasBeenHit() + (1/xp_factor));
+           }
+         a->damage(1);
+         damage = 1;
+         item.id = a->getId();
+         misses_in_a_row = 0;
+       }
+      else
+       {
+         misses_in_a_row++;
+         if (misses_in_a_row >= 10000)
+           {
+             //defender automatically wins
+             //hit attacker for however much it takes
+             if (d_type == FOR_KEEPS)
+               {
+                 d->setNumberHasHit(d->getNumberHasHit() + (1/xp_factor));
+                 a->setNumberHasBeenHit(a->getNumberHasBeenHit() + 
+                                        (1/xp_factor));
+               }
+             item.id = a->getId();
+             damage = a->getHP();
+             a->damage (damage);
+             misses_in_a_row = 0;
+           }
+       }
+    }
+  // continue documenting the engagement
+  item.damage = damage;
+  d_actions.push_back(item);
+
+}
+
+void Fight::remove(Fighter* f)
+{
+  std::list<Fighter*>::iterator it;
+
+  // is the fighter in the attacker lists?
+  for (it = d_att_close.begin(); it != d_att_close.end(); it++)
+    if ((*it) == f)
+      {
+       d_att_close.erase(it);
+       delete f;
+       return;
+      }
+
+  // or in the defender lists?
+  for (it = d_def_close.begin(); it != d_def_close.end(); it++)
+    if ((*it) == f)
+      {
+       d_def_close.erase(it);
+       delete f;
+       return;
+      }
+
+  // if the fighter wa sin no list, we are rather careful and don't do anything
+  debug("Fight: fighter without list!")
+}
+       
+guint32 Fight::getModifiedStrengthBonus(Army *a)
+{
+  std::list<Fighter*>::iterator it;
+  for (it = d_att_close.begin(); it != d_att_close.end(); it++)
+    if ((*it)->army == a)
+      return (*it)->terrain_strength;
+  for (it = d_def_close.begin(); it != d_def_close.end(); it++)
+    if ((*it)->army == a)
+      return (*it)->terrain_strength;
+  return 0;
+}
+
+void Fight::fillInInitialHPs()
+{
+  for (std::list<Stack *>::iterator i = d_attackers.begin();
+       i != d_attackers.end(); ++i)
+    for (Stack::iterator j = (*i)->begin(); j != (*i)->end(); ++j)
+      initial_hps[(*j)->getId()] = (*j)->getHP();
+  
+  for (std::list<Stack *>::iterator i = d_defenders.begin();
+       i != d_defenders.end(); ++i)
+    for (Stack::iterator j = (*i)->begin(); j != (*i)->end(); ++j)
+      initial_hps[(*j)->getId()] = (*j)->getHP();
+}
+       
+LocationBox Fight::calculateFightBox(Fight &fight)
+{
+  Citylist *cl = Citylist::getInstance();
+  Vector<int> dest = fight.getAttackers().front()->getPos();
+  if (cl->getObjectAt(dest) == NULL)
+    return LocationBox(dest);
+  Player *p = fight.getAttackers().front()->getOwner();
+  Stack *s = fight.getAttackers().front();
+  std::list<Vector<int> > tracks = p->getStackTrack(s);
+  if (tracks.size() >= 2)
+    {
+      std::list<Vector<int> >::iterator it = tracks.end();
+      it--; it--;
+      return LocationBox(*it, dest);
+    }
+  else
+    {
+      //this shouldn't be the case
+      return LocationBox(s->getPos(), dest);
+    }
+}