1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Further changes for duel mode. It is possible to pass names of AIs to be used by command line. Moved things in battle AI.

This commit is contained in:
Michał W. Urbańczyk 2013-06-21 20:59:32 +00:00
parent 6737c270c9
commit 06dbdd234f
13 changed files with 509 additions and 338 deletions

View File

@ -13,50 +13,58 @@ CBattleCallback * cbc;
#define LOGL(text) print(text)
#define LOGFL(text, formattingEl) print(boost::str(boost::format(text) % formattingEl))
struct Priorities
{
double manaValue;
double generalResourceValueModifier;
std::vector<double> resourceTypeBaseValues;
std::function<double(const CStack *)> stackEvaluator;
class StackWithBonuses : public IBonusBearer
Priorities()
{
public:
const CStack *stack;
mutable std::vector<Bonus> bonusesToAdd;
virtual const TBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = NULL, const std::string &cachingStr = "") const OVERRIDE
{
TBonusListPtr ret = make_shared<BonusList>();
const TBonusListPtr originalList = stack->getAllBonuses(selector, limit, root, cachingStr);
boost::copy(*originalList, std::back_inserter(*ret));
BOOST_FOREACH(auto &bonus, bonusesToAdd)
{
if(selector(&bonus) && (!limit || !limit(&bonus)))
ret->push_back(&bonus);
manaValue = 0.;
generalResourceValueModifier = 1.;
range::copy(VLC->objh->resVals, std::back_inserter(resourceTypeBaseValues));
stackEvaluator = [](const CStack*){ return 1.0; };
}
} priorities;
//TODO limiters?
int distToNearestNeighbour(BattleHex hex, const ReachabilityInfo::TDistances& dists, BattleHex *chosenHex = NULL)
{
int ret = 1000000;
BOOST_FOREACH(BattleHex n, hex.neighbouringTiles())
{
if(dists[n] >= 0 && dists[n] < ret)
{
ret = dists[n];
if(chosenHex)
*chosenHex = n;
}
}
return ret;
}
};
struct Skirmish
bool isCloser(const EnemyInfo & ei1, const EnemyInfo & ei2, const ReachabilityInfo::TDistances & dists)
{
const CStack *attacker, *defender;
int retaliationDamage, dealtDamage;
Skirmish(const CStack *Attacker, const CStack *Defender)
:attacker(Attacker), defender(Defender)
{
TDmgRange retal, dmg = cbc->battleEstimateDamage(attacker, defender, &retal);
dealtDamage = (dmg.first + dmg.second) / 2;
retaliationDamage = (retal.first + retal.second) / 2;
if(attacker->hasBonusOfType(Bonus::ADDITIONAL_ATTACK))
dealtDamage *= 2;
if(attacker->hasBonusOfType(Bonus::BLOCKS_RETALIATION) || defender->hasBonusOfType(Bonus::NO_RETALIATION))
retaliationDamage = 0;
return distToNearestNeighbour(ei1.s->position, dists) < distToNearestNeighbour(ei2.s->position, dists);
}
};
template <typename Container, typename Pred>
auto sum(const Container & c, Pred p) -> decltype(p(*boost::begin(c)))
{
double ret = 0;
BOOST_FOREACH(const auto &element, c)
{
ret += p(element);
}
return ret;
}
CBattleAI::CBattleAI(void)
: side(-1), cb(NULL)
@ -89,261 +97,15 @@ void CBattleAI::init( CBattleCallback * CB )
CB->unlockGsWhenWaiting = false;
}
void CBattleAI::actionFinished(const BattleAction &action)
static bool thereRemainsEnemy()
{
print("actionFinished called");
return cbc->battleGetStacks(CBattleInfoEssentials::ONLY_ENEMY).size();
}
void CBattleAI::actionStarted(const BattleAction &action)
{
print("actionStarted called");
}
struct EnemyInfo
{
const CStack * s;
int adi, adr;
std::vector<BattleHex> attackFrom; //for melee fight
EnemyInfo(const CStack * _s) : s(_s)
{}
void calcDmg(const CStack * ourStack)
{
TDmgRange retal, dmg = cbc->battleEstimateDamage(ourStack, s, &retal);
adi = (dmg.first + dmg.second) / 2;
adr = (retal.first + retal.second) / 2;
}
bool operator==(const EnemyInfo& ei) const
{
return s == ei.s;
}
};
bool isMoreProfitable(const EnemyInfo &ei1, const EnemyInfo& ei2)
{
return (ei1.adi-ei1.adr) < (ei2.adi - ei2.adr);
}
int distToNearestNeighbour(BattleHex hex, const ReachabilityInfo::TDistances& dists, BattleHex *chosenHex = NULL)
{
int ret = 1000000;
BOOST_FOREACH(BattleHex n, hex.neighbouringTiles())
{
if(dists[n] >= 0 && dists[n] < ret)
{
ret = dists[n];
if(chosenHex)
*chosenHex = n;
}
}
return ret;
}
bool isCloser(const EnemyInfo & ei1, const EnemyInfo & ei2, const ReachabilityInfo::TDistances & dists)
{
return distToNearestNeighbour(ei1.s->position, dists) < distToNearestNeighbour(ei2.s->position, dists);
}
//FIXME: unused function
/*
static bool willSecondHexBlockMoreEnemyShooters(const BattleHex &h1, const BattleHex &h2)
{
int shooters[2] = {0}; //count of shooters on hexes
for(int i = 0; i < 2; i++)
BOOST_FOREACH(BattleHex neighbour, (i ? h2 : h1).neighbouringTiles())
if(const CStack *s = cbc->battleGetStackByPos(neighbour))
if(s->getCreature()->isShooting())
shooters[i]++;
return shooters[0] < shooters[1];
}
*/
template <typename Container, typename Pred>
auto sum(const Container & c, Pred p) -> decltype(p(*boost::begin(c)))
{
double ret = 0;
BOOST_FOREACH(const auto &element, c)
{
ret += p(element);
}
return ret;
}
struct ThreatMap
{
std::array<std::vector<BattleAttackInfo>, GameConstants::BFIELD_SIZE> threatMap; // [hexNr] -> enemies able to strike
const CStack *endangered;
std::array<int, GameConstants::BFIELD_SIZE> sufferedDamage;
ThreatMap(const CStack *Endangered)
: endangered(Endangered)
{
sufferedDamage.fill(0);
BOOST_FOREACH(const CStack *enemy, cbc->battleGetStacks())
{
//Consider only stacks of different owner
if(enemy->attackerOwned == endangered->attackerOwned)
continue;
//Look-up which tiles can be melee-attacked
std::array<bool, GameConstants::BFIELD_SIZE> meleeAttackable;
meleeAttackable.fill(false);
auto enemyReachability = cbc->getReachability(enemy);
for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
{
if(enemyReachability.isReachable(i))
{
meleeAttackable[i] = true;
BOOST_FOREACH(auto n, BattleHex(i).neighbouringTiles())
meleeAttackable[n] = true;
}
}
//Gather possible assaults
for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
{
if(cbc->battleCanShoot(enemy, i))
threatMap[i].push_back(BattleAttackInfo(enemy, endangered, true));
else if(meleeAttackable[i])
{
BattleAttackInfo bai(enemy, endangered, false);
bai.chargedFields = std::max(BattleHex::getDistance(enemy->position, i) - 1, 0); //TODO check real distance (BFS), not just metric
threatMap[i].push_back(BattleAttackInfo(bai));
}
}
}
for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
{
sufferedDamage[i] = sum(threatMap[i], [](const BattleAttackInfo &bai) -> int
{
auto dmg = cbc->calculateDmgRange(bai);
return (dmg.first + dmg.second)/2;
});
}
}
};
struct AttackPossibility
{
const CStack *enemy; //redundant (to attack.defender) but looks nice
BattleHex tile; //tile from which we attack
BattleAttackInfo attack;
int damageDealt;
int damageReceived; //usually by counter-attack
int damageDiff() const
{
return damageDealt - damageReceived;
}
int attackValue() const
{
//TODO consider tactical advantage
return damageDiff();
}
};
template<typename Key, typename Val>
const Val &getValOr(const std::map<Key, Val> &Map, const Key &key, const Val &defaultValue)
{
auto i = Map.find(key);
if(i != Map.end())
return i->second;
else
return defaultValue;
}
struct HypotheticChangesToBattleState
{
std::map<const CStack *, const IBonusBearer *> bonusesOfStacks;
};
struct PotentialTargets
{
std::vector<AttackPossibility> possibleAttacks;
std::vector<const CStack *> unreachableEnemies;
std::function<AttackPossibility(bool,BattleHex)> GenerateAttackInfo; //args: shooting, destHex
PotentialTargets()
{}
PotentialTargets(const CStack *attacker, const HypotheticChangesToBattleState &state = HypotheticChangesToBattleState())
{
auto dists = cbc->battleGetDistances(attacker);
std::vector<BattleHex> avHexes = cbc->battleGetAvailableHexes(attacker, false);
BOOST_FOREACH(const CStack *enemy, cbc->battleGetStacks())
{
//Consider only stacks of different owner
if(enemy->attackerOwned == attacker->attackerOwned)
continue;
GenerateAttackInfo = [&](bool shooting, BattleHex hex) -> AttackPossibility
{
auto bai = BattleAttackInfo(attacker, enemy, shooting);
bai.attackerBonuses = getValOr(state.bonusesOfStacks, bai.attacker, static_cast<const IBonusBearer *>(bai.attacker));
bai.defenderBonuses = getValOr(state.bonusesOfStacks, bai.defender, static_cast<const IBonusBearer *>(bai.defender));
AttackPossibility ap = {enemy, hex, bai, 0, 0};
if(hex.isValid())
{
assert(dists[hex] <= attacker->Speed());
ap.attack.chargedFields = dists[hex];
}
std::pair<ui32, ui32> retaliation;
auto attackDmg = cbc->battleEstimateDamage(ap.attack, &retaliation);
ap.damageDealt = (attackDmg.first + attackDmg.second) / 2;
ap.damageReceived = (retaliation.first + retaliation.second) / 2;
//TODO other damage related to attack (eg. fire shield and other abilities)
//TODO limit max damage by total stacks health (dealing 100000 dmg to single Pikineer is not that effective)
return ap;
};
if(cbc->battleCanShoot(attacker, enemy->position))
{
possibleAttacks.push_back(GenerateAttackInfo(true, BattleHex::INVALID));
}
else
{
BOOST_FOREACH(BattleHex hex, avHexes)
if(CStack::isMeleeAttackPossible(attacker, enemy, hex))
possibleAttacks.push_back(GenerateAttackInfo(false, hex));
if(!vstd::contains_if(possibleAttacks, [=](const AttackPossibility &pa) { return pa.enemy == enemy; }))
unreachableEnemies.push_back(enemy);
}
}
}
AttackPossibility bestAction() const
{
if(possibleAttacks.empty())
throw std::runtime_error("No best action, since we don't have any actions");
return *vstd::maxElementByFun(possibleAttacks, [](const AttackPossibility &ap) { return ap.damageDiff(); } );
}
int bestActionValue() const
{
if(possibleAttacks.empty())
return 0;
return bestAction().damageDiff();
}
};
BattleAction CBattleAI::activeStack( const CStack * stack )
{
LOG_TRACE_PARAMS(logAi, "stack: %s", stack->nodeName()) ;
cbc = cb; //TODO: make solid sure that AIs always use their callbacks (need to take care of event handlers too)
try
{
@ -354,6 +116,9 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
if(cb->battleCanCastSpell())
attemptCastingSpell();
if(!thereRemainsEnemy())
return BattleAction();
if(auto action = considerFleeingOrSurrendering())
return *action;
@ -364,6 +129,8 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
}
PotentialTargets targets(stack);
if(targets.possibleAttacks.size())
{
auto hlp = targets.bestAction();
@ -398,6 +165,16 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
return BattleAction::makeDefend(stack);
}
void CBattleAI::actionFinished(const BattleAction &action)
{
print("actionFinished called");
}
void CBattleAI::actionStarted(const BattleAction &action)
{
print("actionStarted called");
}
void CBattleAI::battleAttack(const BattleAttack *ba)
{
print("battleAttack called");
@ -591,6 +368,8 @@ struct CurrentOffensivePotential
return ourPotential - enemyPotential;
}
};
//
// //set has its own order, so remove_if won't work. TODO - reuse for map
// template<typename Elem, typename Predicate>
@ -764,3 +543,183 @@ boost::optional<BattleAction> CBattleAI::considerFleeingOrSurrendering()
}
return boost::none;
}
ThreatMap::ThreatMap(const CStack *Endangered) : endangered(Endangered)
{
sufferedDamage.fill(0);
BOOST_FOREACH(const CStack *enemy, cbc->battleGetStacks())
{
//Consider only stacks of different owner
if(enemy->attackerOwned == endangered->attackerOwned)
continue;
//Look-up which tiles can be melee-attacked
std::array<bool, GameConstants::BFIELD_SIZE> meleeAttackable;
meleeAttackable.fill(false);
auto enemyReachability = cbc->getReachability(enemy);
for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
{
if(enemyReachability.isReachable(i))
{
meleeAttackable[i] = true;
BOOST_FOREACH(auto n, BattleHex(i).neighbouringTiles())
meleeAttackable[n] = true;
}
}
//Gather possible assaults
for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
{
if(cbc->battleCanShoot(enemy, i))
threatMap[i].push_back(BattleAttackInfo(enemy, endangered, true));
else if(meleeAttackable[i])
{
BattleAttackInfo bai(enemy, endangered, false);
bai.chargedFields = std::max(BattleHex::getDistance(enemy->position, i) - 1, 0); //TODO check real distance (BFS), not just metric
threatMap[i].push_back(BattleAttackInfo(bai));
}
}
}
for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
{
sufferedDamage[i] = sum(threatMap[i], [](const BattleAttackInfo &bai) -> int
{
auto dmg = cbc->calculateDmgRange(bai);
return (dmg.first + dmg.second)/2;
});
}
}
const TBonusListPtr StackWithBonuses::getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root /*= NULL*/, const std::string &cachingStr /*= ""*/) const
{
TBonusListPtr ret = make_shared<BonusList>();
const TBonusListPtr originalList = stack->getAllBonuses(selector, limit, root, cachingStr);
boost::copy(*originalList, std::back_inserter(*ret));
BOOST_FOREACH(auto &bonus, bonusesToAdd)
{
if(selector(&bonus) && (!limit || !limit(&bonus)))
ret->push_back(&bonus);
}
//TODO limiters?
return ret;
}
int AttackPossibility::damageDiff() const
{
const auto dealtDmgValue = priorities.stackEvaluator(enemy) * damageDealt;
const auto receivedDmgValue = priorities.stackEvaluator(attack.attacker) * damageReceived;
return dealtDmgValue - receivedDmgValue;
}
int AttackPossibility::attackValue() const
{
return damageDiff() + tacticImpact;
}
AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo &AttackInfo, const HypotheticChangesToBattleState &state, BattleHex hex)
{
auto attacker = AttackInfo.attacker;
auto enemy = AttackInfo.defender;
const int remainingCounterAttacks = getValOr(state.counterAttacksLeft, enemy, enemy->counterAttacks);
const bool counterAttacksBlocked = attacker->hasBonusOfType(Bonus::BLOCKS_RETALIATION) || enemy->hasBonusOfType(Bonus::NO_RETALIATION);
const int totalAttacks = 1 + AttackInfo.attackerBonuses->getBonuses(Selector::type(Bonus::ADDITIONAL_ATTACK), (Selector::effectRange (Bonus::NO_LIMIT) || Selector::effectRange(Bonus::ONLY_MELEE_FIGHT)))->totalValue();
AttackPossibility ap = {enemy, hex, AttackInfo, 0, 0, 0};
auto curBai = AttackInfo; //we'll modify here the stack counts
for(int i = 0; i < totalAttacks; i++)
{
std::pair<ui32, ui32> retaliation(0,0);
auto attackDmg = cbc->battleEstimateDamage(curBai, &retaliation);
ap.damageDealt = (attackDmg.first + attackDmg.second) / 2;
ap.damageReceived = (retaliation.first + retaliation.second) / 2;
if(remainingCounterAttacks <= i || counterAttacksBlocked)
ap.damageReceived = 0;
curBai.attackerCount = attacker->count - attacker->countKilledByAttack(ap.damageReceived).first;
curBai.defenderCount = enemy->count - enemy->countKilledByAttack(ap.damageDealt).first;
if(!curBai.attackerCount)
break;
//TODO what about defender? should we break? but in pessimistic scenario defender might be alive
}
//TODO other damage related to attack (eg. fire shield and other abilities)
//Limit damages by total stack health
vstd::amin(ap.damageDealt, enemy->count * enemy->MaxHealth() - (enemy->MaxHealth() - enemy->firstHPleft));
vstd::amin(ap.damageReceived, attacker->count * attacker->MaxHealth() - (attacker->MaxHealth() - attacker->firstHPleft));
return ap;
}
PotentialTargets::PotentialTargets(const CStack *attacker, const HypotheticChangesToBattleState &state /*= HypotheticChangesToBattleState()*/)
{
auto dists = cbc->battleGetDistances(attacker);
auto avHexes = cbc->battleGetAvailableHexes(attacker, false);
BOOST_FOREACH(const CStack *enemy, cbc->battleGetStacks())
{
//Consider only stacks of different owner
if(enemy->attackerOwned == attacker->attackerOwned)
continue;
auto GenerateAttackInfo = [&](bool shooting, BattleHex hex) -> AttackPossibility
{
auto bai = BattleAttackInfo(attacker, enemy, shooting);
bai.attackerBonuses = getValOr(state.bonusesOfStacks, bai.attacker, bai.attacker);
bai.defenderBonuses = getValOr(state.bonusesOfStacks, bai.defender, bai.defender);
if(hex.isValid())
{
assert(dists[hex] <= attacker->Speed());
bai.chargedFields = dists[hex];
}
return AttackPossibility::evaluate(bai, state, hex);
};
if(cbc->battleCanShoot(attacker, enemy->position))
{
possibleAttacks.push_back(GenerateAttackInfo(true, BattleHex::INVALID));
}
else
{
BOOST_FOREACH(BattleHex hex, avHexes)
if(CStack::isMeleeAttackPossible(attacker, enemy, hex))
possibleAttacks.push_back(GenerateAttackInfo(false, hex));
if(!vstd::contains_if(possibleAttacks, [=](const AttackPossibility &pa) { return pa.enemy == enemy; }))
unreachableEnemies.push_back(enemy);
}
}
}
AttackPossibility PotentialTargets::bestAction() const
{
if(possibleAttacks.empty())
throw std::runtime_error("No best action, since we don't have any actions");
return *vstd::maxElementByFun(possibleAttacks, [](const AttackPossibility &ap) { return ap.attackValue(); } );
}
int PotentialTargets::bestActionValue() const
{
if(possibleAttacks.empty())
return 0;
return bestAction().attackValue();
}
void EnemyInfo::calcDmg(const CStack * ourStack)
{
TDmgRange retal, dmg = cbc->battleEstimateDamage(ourStack, s, &retal);
adi = (dmg.first + dmg.second) / 2;
adr = (retal.first + retal.second) / 2;
}

View File

@ -1,9 +1,111 @@
#pragma once
#include "../../lib/BattleHex.h"
#include "../../lib/HeroBonus.h"
#include "../../lib/CBattleCallback.h"
class CSpell;
class StackWithBonuses : public IBonusBearer
{
public:
const CStack *stack;
mutable std::vector<Bonus> bonusesToAdd;
virtual const TBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = NULL, const std::string &cachingStr = "") const OVERRIDE;
};
struct EnemyInfo
{
const CStack * s;
int adi, adr;
std::vector<BattleHex> attackFrom; //for melee fight
EnemyInfo(const CStack * _s) : s(_s)
{}
void calcDmg(const CStack * ourStack);
bool operator==(const EnemyInfo& ei) const
{
return s == ei.s;
}
};
//FIXME: unused function
/*
static bool willSecondHexBlockMoreEnemyShooters(const BattleHex &h1, const BattleHex &h2)
{
int shooters[2] = {0}; //count of shooters on hexes
for(int i = 0; i < 2; i++)
BOOST_FOREACH(BattleHex neighbour, (i ? h2 : h1).neighbouringTiles())
if(const CStack *s = cbc->battleGetStackByPos(neighbour))
if(s->getCreature()->isShooting())
shooters[i]++;
return shooters[0] < shooters[1];
}
*/
struct ThreatMap
{
std::array<std::vector<BattleAttackInfo>, GameConstants::BFIELD_SIZE> threatMap; // [hexNr] -> enemies able to strike
const CStack *endangered;
std::array<int, GameConstants::BFIELD_SIZE> sufferedDamage;
ThreatMap(const CStack *Endangered);
};
struct HypotheticChangesToBattleState
{
std::map<const CStack *, const IBonusBearer *> bonusesOfStacks;
std::map<const CStack *, int> counterAttacksLeft;
};
struct AttackPossibility
{
const CStack *enemy; //redundant (to attack.defender) but looks nice
BattleHex tile; //tile from which we attack
BattleAttackInfo attack;
int damageDealt;
int damageReceived; //usually by counter-attack
int tacticImpact;
int damageDiff() const;
int attackValue() const;
static AttackPossibility evaluate(const BattleAttackInfo &AttackInfo, const HypotheticChangesToBattleState &state, BattleHex hex);
};
template<typename Key, typename Val, typename Val2>
const Val &getValOr(const std::map<Key, Val> &Map, const Key &key, const Val2 &defaultValue)
{
auto i = Map.find(key);
if(i != Map.end())
return i->second;
else
return defaultValue;
}
struct PotentialTargets
{
std::vector<AttackPossibility> possibleAttacks;
std::vector<const CStack *> unreachableEnemies;
//std::function<AttackPossibility(bool,BattleHex)> GenerateAttackInfo; //args: shooting, destHex
PotentialTargets(){};
PotentialTargets(const CStack *attacker, const HypotheticChangesToBattleState &state = HypotheticChangesToBattleState());
AttackPossibility bestAction() const;
int bestActionValue() const;
};
class CBattleAI : public CBattleGameInterface
{
int side;

View File

@ -78,7 +78,8 @@ std::queue<SDL_Event> events;
boost::mutex eventsM;
bool gNoGUI = false;
static bool gOnlyAI = false;
static po::variables_map vm;
//static bool setResolution = false; //set by event handling thread after resolution is adjusted
static bool ermInteractiveMode = false; //structurize when time is right
@ -226,12 +227,12 @@ int main(int argc, char** argv)
("start", po::value<std::string>(), "starts game from saved StartInfo file")
("onlyAI", "runs without human player, all players will be default AI")
("noGUI", "runs without GUI, implies --onlyAI")
("ai", po::value<std::vector<std::string>>(), "AI to be used for the player, can be specified several times for the consecutive players")
("oneGoodAI", "puts one default AI and the rest will be EmptyAI")
("autoSkip", "automatically skip turns in GUI")
("disable-video", "disable video player")
("nointro,i", "skips intro movies");
po::variables_map vm;
if(argc > 1)
{
try
@ -258,7 +259,7 @@ int main(int argc, char** argv)
if(vm.count("noGUI"))
{
gNoGUI = true;
vm["onlyAI"];
vm.insert(std::pair<std::string, po::variable_value>("onlyAI", po::variable_value()));
}
//Set environment vars to make window centered. Sometimes work, sometimes not. :/
@ -375,7 +376,6 @@ int main(int argc, char** argv)
if(!vm.count("battle"))
{
gOnlyAI = vm.count("onlyAI");
Settings session = settings.write["session"];
session["autoSkip"].Bool() = vm.count("autoSkip");
session["oneGoodAI"].Bool() = vm.count("oneGoodAI");
@ -566,7 +566,7 @@ void processCommand(const std::string &message)
}
else if(cn == "onlyai")
{
gOnlyAI = true;
vm.insert(std::pair<std::string, po::variable_value>("onlyAI", po::variable_value()));
}
else if (cn == "ai")
{
@ -917,11 +917,18 @@ static void listenForEvents()
void startGame(StartInfo * options, CConnection *serv/* = NULL*/)
{
if(gOnlyAI)
if(vm.count("onlyAI"))
{
auto ais = vm["ai"].as<std::vector<std::string>>();
int i = 0;
for(auto it = options->playerInfos.begin(); it != options->playerInfos.end(); ++it)
{
it->second.playerID = PlayerSettings::PLAYER_AI;
if(i < ais.size())
it->second.name = ais[i++];
}
}

View File

@ -410,10 +410,18 @@ void CClient::newGame( CConnection *con, StartInfo *si )
{
auto cbc = make_shared<CBattleCallback>(gs, color, this);
battleCallbacks[color] = cbc;
std::string AItoGive = it->second.name;
if(AItoGive.empty())
{
if(color == PlayerColor(0))
battleints[color] = CDynLibHandler::getNewBattleAI(settings["server"]["neutralAI"].String());
else
battleints[color] = CDynLibHandler::getNewBattleAI("StupidAI");
}
battleints[color] = CDynLibHandler::getNewBattleAI(AItoGive);
battleints[color]->init(cbc.get());
}
}
@ -802,7 +810,7 @@ void CServerHandler::callServer()
{
setThreadName("CServerHandler::callServer");
std::string logName = VCMIDirs::get().localPath() + "/server_log.txt";
std::string comm = VCMIDirs::get().serverPath() + " " + port + " > " + logName;
std::string comm = VCMIDirs::get().serverPath() + " --port=" + port + " > " + logName;
int result = std::system(comm.c_str());
if (result == 0)
logNetwork->infoStream() << "Server closed correctly";

View File

@ -1129,33 +1129,55 @@ std::string CStack::nodeName() const
return oss.str();
}
void CStack::prepareAttacked(BattleStackAttacked &bsa) const
std::pair<int,int> CStack::countKilledByAttack(int damageReceived) const
{
bsa.killedAmount = bsa.damageAmount / MaxHealth();
unsigned damageFirst = bsa.damageAmount % MaxHealth();
int killedCount = 0;
int newRemainingHP = 0;
killedCount = damageReceived / MaxHealth();
unsigned damageFirst = damageReceived % MaxHealth();
if (damageReceived && vstd::contains(state, EBattleStackState::CLONED)) // block ability should not kill clone (0 damage)
{
killedCount = count;
}
else
{
if( firstHPleft <= damageFirst )
{
killedCount++;
newRemainingHP = firstHPleft + MaxHealth() - damageFirst;
}
else
{
newRemainingHP = firstHPleft - damageFirst;
}
}
return std::make_pair(killedCount, newRemainingHP);
}
void CStack::prepareAttacked(BattleStackAttacked &bsa, boost::optional<int> customCount /*= boost::none*/) const
{
auto afterAttack = countKilledByAttack(bsa.damageAmount);
bsa.killedAmount = afterAttack.first;
bsa.newHP = afterAttack.second;
if (bsa.damageAmount && vstd::contains(state, EBattleStackState::CLONED)) // block ability should not kill clone (0 damage)
{
bsa.killedAmount = count;
bsa.flags |= BattleStackAttacked::CLONE_KILLED;
return; // no rebirth I believe
}
if( firstHPleft <= damageFirst )
{
bsa.killedAmount++;
bsa.newHP = firstHPleft + MaxHealth() - damageFirst;
}
else
{
bsa.newHP = firstHPleft - damageFirst;
}
const int countToUse = customCount ? *customCount : count;
if(count <= bsa.killedAmount) //stack killed
if(countToUse <= bsa.killedAmount) //stack killed
{
bsa.newAmount = 0;
bsa.flags |= BattleStackAttacked::KILLED;
bsa.killedAmount = count; //we cannot kill more creatures than we have
bsa.killedAmount = countToUse; //we cannot kill more creatures than we have
int resurrectFactor = valOfBonuses(Bonus::REBIRTH);
if (resurrectFactor > 0 && casts) //there must be casts left
@ -1177,7 +1199,7 @@ void CStack::prepareAttacked(BattleStackAttacked &bsa) const
}
else
{
bsa.newAmount = count - bsa.killedAmount;
bsa.newAmount = countToUse - bsa.killedAmount;
}
}

View File

@ -193,7 +193,8 @@ public:
bool coversPos(BattleHex position) const; //checks also if unit is double-wide
std::vector<BattleHex> getSurroundingHexes(BattleHex attackerPos = BattleHex::INVALID) const; // get six or 8 surrounding hexes depending on creature size
void prepareAttacked(BattleStackAttacked &bsa) const; //requires bsa.damageAmout filled
std::pair<int,int> countKilledByAttack(int damageReceived) const; //returns pair<killed count, new left HP>
void prepareAttacked(BattleStackAttacked &bsa, boost::optional<int> customCount = boost::none) const; //requires bsa.damageAmout filled
template <typename Handler> void serialize(Handler &h, const int version)
{

View File

@ -1017,7 +1017,7 @@ std::pair<ui32, ui32> CBattleInfoCallback::battleEstimateDamage(const BattleAtta
{
BattleStackAttacked bsa;
bsa.damageAmount = ret.*pairElems[i];
bai.defender->prepareAttacked(bsa);
bai.defender->prepareAttacked(bsa, bai.defenderCount);
auto retaliationAttack = bai.reverse();
retaliationAttack.attackerCount = bsa.newAmount;
@ -2329,6 +2329,8 @@ BattleAttackInfo::BattleAttackInfo(const CStack *Attacker, const CStack *Defende
defenderPosition = Defender->position;
attackerCount = Attacker->count;
defenderCount = Defender->count;
shooting = Shooting;
chargedFields = 0;
@ -2343,8 +2345,8 @@ BattleAttackInfo BattleAttackInfo::reverse() const
std::swap(ret.attacker, ret.defender);
std::swap(ret.attackerBonuses, ret.defenderBonuses);
std::swap(ret.attackerPosition, ret.defenderPosition);
std::swap(ret.attackerCount, ret.defenderCount);
ret.attackerCount = ret.attacker->count;
ret.shooting = false;
ret.chargedFields = 0;
ret.luckyHit = ret.ballistaDoubleDamage = ret.deathBlow = false;

View File

@ -198,7 +198,7 @@ struct DLL_LINKAGE BattleAttackInfo
const CStack *attacker, *defender;
BattleHex attackerPosition, defenderPosition;
int attackerCount;
int attackerCount, defenderCount;
bool shooting;
int chargedFields;

View File

@ -93,6 +93,10 @@ struct StartInfo
logGlobal->errorStream() << "Cannot find info about player " << no <<". Throwing...";
throw std::runtime_error("Cannot find info about player");
}
const PlayerSettings & getIthPlayersSettings(PlayerColor no) const
{
return const_cast<StartInfo&>(*this).getIthPlayersSettings(no);
}
PlayerSettings *getPlayersSettings(const ui8 nameID)
{

View File

@ -24,6 +24,7 @@
#include "../lib/VCMIDirs.h"
#include "../client/CSoundBase.h"
#include "CGameHandler.h"
#include "CVCMIServer.h"
#include "../lib/CCreatureSet.h"
#include "../lib/CThreadHelper.h"
#include "../lib/GameConstants.h"
@ -455,6 +456,15 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer
CasualtiesAfterBattle cab1(bEndArmy1, gs->curB), cab2(bEndArmy2, gs->curB); //calculate casualties before deleting battle
if(finishingBattle->duel)
{
duelFinished();
sendAndApply(battleResult.data); //after this point casualties objects are destroyed
return;
}
ChangeSpells cs; //for Eagle Eye
if(finishingBattle->winnerHero)
@ -596,15 +606,6 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer
sendAndApply(&cs);
}
if(finishingBattle->duel)
{
BattleResultsApplied resultsApplied;
resultsApplied.player1 = finishingBattle->victor;
resultsApplied.player2 = finishingBattle->loser;
sendAndApply(&resultsApplied);
return;
}
cab1.takeFromArmy(this); cab2.takeFromArmy(this); //take casualties after battle is deleted
//if one hero has lost we will erase him
@ -665,13 +666,6 @@ void CGameHandler::battleAfterLevelUp( const BattleResult &result )
setBattle(nullptr);
if(finishingBattle->duel)
{
CSaveFile resultFile("result.vdrst");
resultFile << *battleResult.data;
return;
}
if(visitObjectAfterVictory && result.winner==0)
{
logGlobal->traceStream() << "post-victory visit";
@ -6187,6 +6181,49 @@ bool CGameHandler::isVisitCoveredByAnotherQuery(const CGObjectInstance *obj, con
return true;
}
void CGameHandler::duelFinished()
{
auto si = getStartInfo();
auto getName = [&](int i){ return si->getIthPlayersSettings(gs->curB->sides[i]).name; };
int casualtiesPoints = 0;
logGlobal->debugStream() << boost::format("Winner side %d\nWinner casualties:")
% (int)battleResult.data->winner;
for(auto i = battleResult.data->casualties[battleResult.data->winner].begin(); i != battleResult.data->casualties[battleResult.data->winner].end(); i++)
{
const CCreature *c = VLC->creh->creatures[i->first];
logGlobal->debugStream() << boost::format("\t* %d of %s") % i->second % c->namePl;
casualtiesPoints += c->AIValue * i->second;
}
logGlobal->debugStream() << boost::format("Total casualties points: %d") % casualtiesPoints;
time_t timeNow;
time(&timeNow);
std::ofstream out(cmdLineOptions["resultsFile"].as<std::string>(), std::ios::app);
if(out)
{
out << boost::format("%s\t%s\t%s\t%d\t%d\t%d\t%s\n") % si->mapname % getName(0) % getName(1)
% battleResult.data->winner % battleResult.data->result % casualtiesPoints
% asctime(localtime(&timeNow));
}
else
{
logGlobal->errorStream() << "Cannot open to write " << cmdLineOptions["resultsFile"].as<std::string>();
}
CSaveFile resultFile("result.vdrst");
resultFile << *battleResult.data;
BattleResultsApplied resultsApplied;
resultsApplied.player1 = finishingBattle->victor;
resultsApplied.player2 = finishingBattle->loser;
sendAndApply(&resultsApplied);
return;
}
CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance *army, BattleInfo *bat)
{
heroWithDeadCommander = ObjectInstanceID();

View File

@ -121,6 +121,7 @@ public:
void checkForBattleEnd();
void setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance *heroes[2], bool creatureBank, const CGTownInstance *town);
void setBattleResult(BattleResult::EResult resultType, int victoriusSide);
void duelFinished();
CGameHandler(void);
~CGameHandler(void);

View File

@ -43,6 +43,8 @@ namespace intpr = boost::interprocess;
bool end2 = false;
int port = 3030;
boost::program_options::variables_map cmdLineOptions;
/*
* CVCMIServer.cpp, part of VCMI engine
*
@ -508,21 +510,45 @@ void CVCMIServer::loadGame()
gh.run(true);
}
static void handleCommandOptions(int argc, char *argv[])
{
namespace po = boost::program_options;
po::options_description opts("Allowed options");
opts.add_options()
("help,h", "display help and exit")
("version,v", "display version information and exit")
("port", po::value<int>()->default_value(3030), "port at which server will listen to connections from client")
("resultsFile", po::value<std::string>()->default_value("./results.txt"), "file to which the battle result will be appended. Used only in the DUEL mode.");
if(argc > 1)
{
try
{
po::store(po::parse_command_line(argc, argv, opts), cmdLineOptions);
}
catch(std::exception &e)
{
std::cerr << "Failure during parsing command-line options:\n" << e.what() << std::endl;
}
}
po::notify(cmdLineOptions);
}
int main(int argc, char** argv)
{
console = new CConsoleHandler;
CBasicLogConfigurator logConfig(VCMIDirs::get().localPath() + "/VCMI_Server_log.txt", console);
logConfig.configureDefault();
if(argc > 1)
{
port = boost::lexical_cast<int>(argv[1]);
}
preinitDLL(console);
settings.init();
logConfig.configure();
handleCommandOptions(argc, argv);
port = cmdLineOptions["port"].as<int>();
logNetwork->infoStream() << "Port " << port << " will be used.";
loadDLLClasses();
srand ( (ui32)time(NULL) );
try

View File

@ -96,3 +96,5 @@ public:
void sendPack(CConnection * pc, const CPackForSelectionScreen & pack);
void startListeningThread(CConnection * pc);
};
extern boost::program_options::variables_map cmdLineOptions;