diff --git a/client/BattleInterface/CBattleInterfaceClasses.cpp b/client/BattleInterface/CBattleInterfaceClasses.cpp index 5106f2b72..345d0216c 100644 --- a/client/BattleInterface/CBattleInterfaceClasses.cpp +++ b/client/BattleInterface/CBattleInterfaceClasses.cpp @@ -480,7 +480,11 @@ void CBattleResultWindow::bExitf() { if(LOCPLINT->cb->getStartInfo()->mode == StartInfo::DUEL) { - std::exit(0); + SDL_Event ev; + ev.type = SDL_QUIT; + ev.user.code = 0; + SDL_PushEvent(&ev); + return; } CPlayerInterface * intTmp = owner->curInt; diff --git a/client/Client.cpp b/client/Client.cpp index d88fea1bf..cf877086e 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -393,9 +393,11 @@ void CClient::newGame( CConnection *con, StartInfo *si ) if(si->mode == StartInfo::DUEL) { + boost::unique_lock un(*LOCPLINT->pim); CPlayerInterface *p = new CPlayerInterface(-1); p->observerInDuelMode = true; battleints[254] = playerint[254] = p; + privilagedBattleEventReceivers.push_back(p); GH.curInt = p; p->init(new CCallback(gs, -1, this)); battleStarted(gs->curB); diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 09267f2b3..f3bff4d28 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -848,56 +848,8 @@ void CGameState::init(StartInfo * si) } break; case StartInfo::DUEL: - { - DuelParameters dp; - try - { - CLoadFile lf(scenarioOps->mapname); - lf >> dp; - } - catch(...) - {} - - - const CArmedInstance *armies[2] = {0}; - const CGHeroInstance *heroes[2] = {0}; - CGTownInstance *town = NULL; - - for(int i = 0; i < 2; i++) - { - CArmedInstance *obj = NULL; - if(dp.sides[i].heroId >= 0) - { - CGHeroInstance *h = new CGHeroInstance(); - armies[i] = heroes[i] = h; - obj = h; - h->subID = dp.sides[i].heroId; - h->initHero(h->subID); - } - else - { - CGCreature *c = new CGCreature(); - armies[i] = obj = c; - c->subID = 34; - - } - - obj->initObj(); - obj->setOwner(i); - - for(int j = 0; j < ARRAY_COUNT(dp.sides[i].stacks); j++) - { - int cre = dp.sides[i].stacks[j].type, count = dp.sides[i].stacks[j].count; - if(count || obj->hasStackAtSlot(j)) - obj->setCreature(j, cre, count); - } - } - - curB = BattleInfo::setupBattle(int3(), dp.bfieldType, dp.terType, armies, heroes, false, town); - curB->localInit(); - return; - } - break; + initDuel(); + return; default: tlog1 << "Wrong mode: " << (int)scenarioOps->mode << std::endl; return; @@ -1527,6 +1479,108 @@ void CGameState::init(StartInfo * si) } } +void CGameState::initDuel() +{ + DuelParameters dp; + try //CLoadFile likes throwing + { + if(boost::algorithm::ends_with(scenarioOps->mapname, ".json")) + { + tlog0 << "Loading duel settings from JSON file: " << scenarioOps->mapname << std::endl; + dp = DuelParameters::fromJSON(scenarioOps->mapname); + tlog0 << "JSON file has been successfully read!\n"; + } + else + { + CLoadFile lf(scenarioOps->mapname); + lf >> dp; + } + } + catch(...) + { + tlog1 << "Cannot load duel settings from " << scenarioOps->mapname << std::endl; + throw; + } + + const CArmedInstance *armies[2] = {0}; + const CGHeroInstance *heroes[2] = {0}; + CGTownInstance *town = NULL; + + for(int i = 0; i < 2; i++) + { + CArmedInstance *obj = NULL; + if(dp.sides[i].heroId >= 0) + { + const DuelParameters::SideSettings &ss = dp.sides[i]; + CGHeroInstance *h = new CGHeroInstance(); + armies[i] = heroes[i] = h; + obj = h; + h->subID = ss.heroId; + for(int i = 0; i < ss.heroPrimSkills.size(); i++) + h->pushPrimSkill(i, ss.heroPrimSkills[i]); + + if(ss.spells.size()) + { + h->putArtifact(ArtifactPosition::SPELLBOOK, CArtifactInstance::createNewArtifactInstance(0)); + boost::copy(ss.spells, std::inserter(h->spells, h->spells.begin())); + } + + BOOST_FOREACH(auto &parka, ss.artifacts) + { + h->putArtifact(parka.first, parka.second); + } + + typedef const std::pair &TSecSKill; + BOOST_FOREACH(TSecSKill secSkill, ss.heroSecSkills) + h->setSecSkillLevel((CGHeroInstance::SecondarySkill)secSkill.first, secSkill.second, 1); + + h->initHero(h->subID); + obj->initObj(); + } + else + { + CGCreature *c = new CGCreature(); + armies[i] = obj = c; + //c->subID = 34; + } + + obj->setOwner(i); + + for(int j = 0; j < ARRAY_COUNT(dp.sides[i].stacks); j++) + { + TCreature cre = dp.sides[i].stacks[j].type; + TQuantity count = dp.sides[i].stacks[j].count; + if(count || obj->hasStackAtSlot(j)) + obj->setCreature(j, cre, count); + } + + BOOST_FOREACH(const DuelParameters::CusomCreature &cc, dp.creatures) + { + CCreature *c = VLC->creh->creatures[cc.id]; + if(cc.attack >= 0) + c->getBonus(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK))->val = cc.attack; + if(cc.defense >= 0) + c->getBonus(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE))->val = cc.defense; + if(cc.speed >= 0) + c->getBonus(Selector::type(Bonus::STACKS_SPEED))->val = cc.speed; + if(cc.HP >= 0) + c->getBonus(Selector::type(Bonus::STACK_HEALTH))->val = cc.HP; + if(cc.dmg >= 0) + { + c->getBonus(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 1))->val = cc.dmg; + c->getBonus(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 2))->val = cc.dmg; + } + if(cc.shoots >= 0) + c->getBonus(Selector::type(Bonus::SHOTS))->val = cc.shoots; + } + } + + curB = BattleInfo::setupBattle(int3(), dp.bfieldType, dp.terType, armies, heroes, false, town); + curB->obstacles = dp.obstacles; + curB->localInit(); + return; +} + int CGameState::battleGetBattlefieldType(int3 tile) const { if(tile==int3() && curB) @@ -2723,6 +2777,88 @@ DuelParameters::DuelParameters() bfieldType = 15; } +DuelParameters DuelParameters::fromJSON(const std::string &fname) +{ + DuelParameters ret; + + const JsonNode duelData(fname); + ret.terType = duelData["terType"].Float(); + ret.bfieldType = duelData["bfieldType"].Float(); + BOOST_FOREACH(const JsonNode &n, duelData["sides"].Vector()) + { + SideSettings &ss = ret.sides[(int)n["side"].Float()]; + int i = 0; + BOOST_FOREACH(const JsonNode &stackNode, n["army"].Vector()) + { + ss.stacks[i].type = stackNode.Vector()[0].Float(); + ss.stacks[i].count = stackNode.Vector()[1].Float(); + i++; + } + + if(n["heroid"].getType() == JsonNode::DATA_FLOAT) + ss.heroId = n["heroid"].Float(); + else + ss.heroId = -1; + + BOOST_FOREACH(const JsonNode &n, n["heroPrimSkills"].Vector()) + ss.heroPrimSkills.push_back(n.Float()); + + BOOST_FOREACH(const JsonNode &skillNode, n["heroSecSkills"].Vector()) + { + std::pair secSkill; + secSkill.first = skillNode.Vector()[0].Float(); + secSkill.second = skillNode.Vector()[1].Float(); + ss.heroSecSkills.push_back(secSkill); + } + + assert(ss.heroPrimSkills.empty() || ss.heroPrimSkills.size() == GameConstants::PRIMARY_SKILLS); + + if(ss.heroId != -1) + BOOST_FOREACH(const JsonNode &spell, n["spells"].Vector()) + ss.spells.insert(spell.Float()); + } + + BOOST_FOREACH(const JsonNode &n, duelData["obstacles"].Vector()) + { + auto oi = make_shared(); + if(n.getType() == JsonNode::DATA_VECTOR) + { + oi->ID = n.Vector()[0].Float(); + oi->pos = n.Vector()[1].Float(); + } + else + { + assert(n.getType() == JsonNode::DATA_FLOAT); + oi->ID = 21; + oi->pos = n.Float(); + } + oi->uniqueID = ret.obstacles.size(); + ret.obstacles.push_back(oi); + } + + BOOST_FOREACH(const JsonNode &n, duelData["creatures"].Vector()) + { + CusomCreature cc; + cc.id = n["id"].Float(); + +#define retreive(name) \ + if(n[ #name ].getType() == JsonNode::DATA_FLOAT)\ + cc.name = n[ #name ].Float(); \ + else \ + cc.name = -1; + + retreive(attack); + retreive(defense); + retreive(HP); + retreive(dmg); + retreive(shoots); + retreive(speed); + ret.creatures.push_back(cc); + } + + return ret; +} + TeamState::TeamState() { setNodeType(TEAM); diff --git a/lib/CGameState.h b/lib/CGameState.h index 6c33a1a95..50a64b65e 100644 --- a/lib/CGameState.h +++ b/lib/CGameState.h @@ -284,12 +284,12 @@ struct DLL_LINKAGE CPathsInfo ~CPathsInfo(); }; -struct DLL_LINKAGE DuelParameters +struct DLL_EXPORT DuelParameters { si32 terType, bfieldType; - struct SideSettings + struct DLL_EXPORT SideSettings { - struct StackSettings + struct DLL_EXPORT StackSettings { si32 type; si32 count; @@ -300,23 +300,46 @@ struct DLL_LINKAGE DuelParameters StackSettings(); StackSettings(si32 Type, si32 Count); - }; - StackSettings stacks[GameConstants::ARMY_SIZE]; + } stacks[GameConstants::ARMY_SIZE]; si32 heroId; //-1 if none + std::vector heroPrimSkills; //may be empty + std::map artifacts; + std::vector > heroSecSkills; //may be empty; pairs , level [0-3] std::set spells; SideSettings(); template void serialize(Handler &h, const int version) { - h & stacks & heroId & spells; + h & stacks & heroId & heroPrimSkills & artifacts & heroSecSkills & spells; } } sides[2]; + std::vector > obstacles; + + static DuelParameters fromJSON(const std::string &fname); + + struct CusomCreature + { + int id; + int attack, defense, dmg, HP, speed, shoots; + + CusomCreature() + { + id = attack = defense = dmg = HP = speed = shoots = -1; + } + template void serialize(Handler &h, const int version) + { + h & id & attack & defense & dmg & HP & speed & shoots; + } + }; + + std::vector creatures; + DuelParameters(); template void serialize(Handler &h, const int version) { - h & terType & bfieldType & sides; + h & terType & bfieldType & sides & obstacles & creatures; } }; @@ -387,6 +410,8 @@ public: boost::shared_mutex *mx; void init(StartInfo * si); + + void initDuel(); void loadTownDInfos(); void randomizeObject(CGObjectInstance *cur); std::pair pickObject(CGObjectInstance *obj); //chooses type of object to be randomized, returns diff --git a/lib/IGameCallback.cpp b/lib/IGameCallback.cpp index 458133c59..a1e0ca0ab 100644 --- a/lib/IGameCallback.cpp +++ b/lib/IGameCallback.cpp @@ -56,6 +56,8 @@ si8 CBattleInfoCallback::battleCanTeleportTo(const CStack * stack, BattleHex des std::vector CBattleInfoCallback::battleGetDistances(const CStack * stack, BattleHex hex /*= BattleHex::INVALID*/, BattleHex * predecessors /*= NULL*/) { + // FIXME - This method is broken, hex argument is not used. However AI depends on that wrong behaviour. + if(!hex.isValid()) hex = stack->position;