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:
parent
6737c270c9
commit
06dbdd234f
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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++];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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";
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -198,7 +198,7 @@ struct DLL_LINKAGE BattleAttackInfo
|
||||
const CStack *attacker, *defender;
|
||||
BattleHex attackerPosition, defenderPosition;
|
||||
|
||||
int attackerCount;
|
||||
int attackerCount, defenderCount;
|
||||
bool shooting;
|
||||
int chargedFields;
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -96,3 +96,5 @@ public:
|
||||
void sendPack(CConnection * pc, const CPackForSelectionScreen & pack);
|
||||
void startListeningThread(CConnection * pc);
|
||||
};
|
||||
|
||||
extern boost::program_options::variables_map cmdLineOptions;
|
Loading…
Reference in New Issue
Block a user