From beae3545364f191d08d4dfa3b4dd302593dbb5f9 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Sat, 17 Sep 2016 23:22:13 +0200 Subject: [PATCH 01/15] Correcting random amount of gold in gold piles In original H3 allowed amounts of gold in treasure piles are multipliers of 100. Before this fix gold amount can be any value from range 500-1000. --- lib/mapObjects/MiscObjects.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index d9cdc82eb..8da128efa 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -868,7 +868,7 @@ void CGResource::initObj(CRandomGenerator & rand) switch(subID) { case 6: - amount = rand.nextInt(500, 1000); + amount = rand.nextInt(5, 10) * 100; break; case 0: case 2: amount = rand.nextInt(6, 10); From f6df107a5550c4357d1531d4173ac6e4d940ad76 Mon Sep 17 00:00:00 2001 From: Arseniy Shestakov Date: Sun, 18 Sep 2016 00:53:37 +0300 Subject: [PATCH 02/15] Replace more magic subIDs with readable enums --- lib/mapObjects/CGTownInstance.cpp | 8 ++++---- lib/mapObjects/MiscObjects.cpp | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 3bb9b51df..61649d4a7 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -618,16 +618,16 @@ void CGTownInstance::initObj(CRandomGenerator & rand) switch (subID) { //add new visitable objects - case 0: + case ETownType::CASTLE: bonusingBuildings.push_back (new COPWBonus(BuildingID::STABLES, this)); break; - case 5: + case ETownType::DUNGEON: bonusingBuildings.push_back (new COPWBonus(BuildingID::MANA_VORTEX, this)); //fallthrough - case 2: case 3: case 6: + case ETownType::TOWER: case ETownType::INFERNO: case ETownType::STRONGHOLD: bonusingBuildings.push_back (new CTownBonus(BuildingID::SPECIAL_4, this)); break; - case 7: + case ETownType::FORTRESS: bonusingBuildings.push_back (new CTownBonus(BuildingID::SPECIAL_1, this)); break; } diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index 8da128efa..99f6da188 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -867,10 +867,10 @@ void CGResource::initObj(CRandomGenerator & rand) { switch(subID) { - case 6: + case Res::GOLD: amount = rand.nextInt(5, 10) * 100; break; - case 0: case 2: + case Res::WOOD: case Res::ORE: amount = rand.nextInt(6, 10); break; default: From 635c48f889d8d642ac1fb7517222356498bd0953 Mon Sep 17 00:00:00 2001 From: Arseniy Shestakov Date: Sun, 18 Sep 2016 10:01:09 +0300 Subject: [PATCH 03/15] CGHeroInstance::setType: fix to give proper subID to random heroes Hero class id is used to determine hero object appearance, but after than we use subID to store it's unique id. This change should fix issues 2127 and 2277 since random heroes not going to override others in heroesPool. --- lib/mapObjects/CGHeroInstance.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index c94e58834..a3cf0fb8c 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -255,7 +255,8 @@ void CGHeroInstance::setType(si32 ID, si32 subID) assert(ID == Obj::HERO); // just in case type = VLC->heroh->heroes[subID]; portrait = type->imageIndex; - CGObjectInstance::setType(ID, type->heroClass->id); + CGObjectInstance::setType(ID, type->heroClass->id); // to find object handler we must use heroClass->id + this->subID = subID; // after setType subID used to store unique hero identify id. Check issue 2277 for details randomizeArmy(type->heroClass->faction); } From 1d45d214e53c9744a19b901c7047504c498f03c6 Mon Sep 17 00:00:00 2001 From: Arseniy Shestakov Date: Sun, 18 Sep 2016 11:53:51 +0300 Subject: [PATCH 04/15] CGameHandler refactoring: when possible only use const data We only change gamestate via netpacks so there is absolutely no reason to use non-const pointers and functions in GH. --- server/CGameHandler.cpp | 106 ++++++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 5f62e954e..9c4abe3a2 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1356,7 +1356,7 @@ static bool evntCmp(const CMapEvent &a, const CMapEvent &b) void CGameHandler::setPortalDwelling(const CGTownInstance * town, bool forced=false, bool clear = false) {// bool forced = true - if creature should be replaced, if false - only if no creature was set - const PlayerState *p = gs->getPlayer(town->tempOwner); + const PlayerState * p = getPlayer(town->tempOwner); if(!p) { logGlobal->warn("There is no player owner of town %s at %s", town->name, town->pos()); @@ -1626,7 +1626,7 @@ void CGameHandler::newTurn() fw.mode = 1; fw.player = player; // find all hidden tiles - const auto & fow = gs->getPlayerTeam(player)->fogOfWarMap; + const auto & fow = getPlayerTeam(player)->fogOfWarMap; for (size_t i=0; ihasBonusOfType (Bonus::DARKNESS)) { - for (auto & player : gameState()->players) + for(auto & player : gs->players) { if (getPlayerStatus(player.first) == EPlayerStatus::INGAME && getPlayerRelations(player.first, t->tempOwner) == PlayerRelations::ENEMIES) @@ -1863,7 +1863,7 @@ void CGameHandler::setupBattle( int3 tile, const CArmedInstance *armies[2], cons { battleResult.set(nullptr); - const auto t = gs->getTile(tile); + const auto t = getTile(tile); ETerrainType terrain = t->terType; if(gs->map->isCoastalTile(tile)) //coastal tile is always ground terrain = ETerrainType::SAND; @@ -1972,7 +1972,7 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo return false; } - const TerrainTile t = *gs->getTile(hmpos); + const TerrainTile t = *getTile(hmpos); const int3 guardPos = gs->guardingCreaturePosition(hmpos); const bool embarking = !h->boat && !t.visitableObjects.empty() && t.visitableObjects.back()->ID == Obj::BOAT; @@ -2181,7 +2181,7 @@ void CGameHandler::setOwner(const CGObjectInstance * obj, PlayerColor owner) if (oldOwner < PlayerColor::PLAYER_LIMIT) //old owner is real player { - if (gs->getPlayer(oldOwner)->towns.empty())//previous player lost last last town + if(getPlayer(oldOwner)->towns.empty()) //previous player lost last last town { InfoWindow iw; iw.player = oldOwner; @@ -2192,11 +2192,11 @@ void CGameHandler::setOwner(const CGObjectInstance * obj, PlayerColor owner) } } - const PlayerState * p = gs->getPlayer(owner); + const PlayerState * p = getPlayer(owner); if((obj->ID == Obj::CREATURE_GENERATOR1 || obj->ID == Obj::CREATURE_GENERATOR4 ) && p && p->dwellings.size()==1)//first dwelling captured { - for(const CGTownInstance *t : gs->getPlayer(owner)->towns) + for(const CGTownInstance * t : getPlayer(owner)->towns) { if (t->hasBuilt(BuildingID::PORTAL_OF_SUMMON, ETownType::DUNGEON)) setPortalDwelling(t);//set initial creatures for all portals of summoning @@ -2226,7 +2226,7 @@ void CGameHandler::giveResource(PlayerColor player, Res::ERes which, int val) // SetResource sr; sr.player = player; sr.resid = which; - sr.val = gs->players.find(player)->second.resources.at(which) + val; + sr.val = getPlayer(player)->resources.at(which) + val; sendAndApply(&sr); } @@ -2505,7 +2505,7 @@ void CGameHandler::heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2) { auto h1 = getHero(hero1), h2 = getHero(hero2); - if( gameState()->getPlayerRelations(h1->getOwner(), h2->getOwner())) + if(getPlayerRelations(h1->getOwner(), h2->getOwner())) { auto exchange = std::make_shared(h1, h2); ExchangeDialog hex; @@ -2610,8 +2610,8 @@ void CGameHandler::close() bool CGameHandler::arrangeStacks( ObjectInstanceID id1, ObjectInstanceID id2, ui8 what, SlotID p1, SlotID p2, si32 val, PlayerColor player ) { - const CArmedInstance *s1 = static_cast(gs->getObjInstance(id1)), - *s2 = static_cast(gs->getObjInstance(id2)); + const CArmedInstance * s1 = static_cast(getObjInstance(id1)), + * s2 = static_cast(getObjInstance(id2)); const CCreatureSet &S1 = *s1, &S2 = *s2; StackLocation sl1(s1, p1), sl2(s2, p2); if(!sl1.slot.validSlot() || !sl2.slot.validSlot()) @@ -2727,7 +2727,7 @@ PlayerColor CGameHandler::getPlayerAt( CConnection *c ) const bool CGameHandler::disbandCreature( ObjectInstanceID id, SlotID pos ) { - CArmedInstance *s1 = static_cast(gs->getObjInstance(id)); + const CArmedInstance * s1 = static_cast(getObjInstance(id)); if(!vstd::contains(s1->stacks,pos)) { complain("Illegal call to disbandCreature - no such stack in army!"); @@ -2760,7 +2760,7 @@ bool CGameHandler::buildStructure( ObjectInstanceID tid, BuildingID requestedID, switch (requestedBuilding->mode) { case CBuilding::BUILD_NORMAL : - if (gs->canBuildStructure(t, requestedID) != EBuildingState::ALLOWED) + if(canBuildStructure(t, requestedID) != EBuildingState::ALLOWED) COMPLAIN_RET("Cannot build that building!"); break; @@ -2877,7 +2877,7 @@ bool CGameHandler::buildStructure( ObjectInstanceID tid, BuildingID requestedID, { SetResources sr; sr.player = t->tempOwner; - sr.res = gs->getPlayer(t->tempOwner)->resources - requestedBuilding->resources; + sr.res = getPlayer(t->tempOwner)->resources - requestedBuilding->resources; sendAndApply(&sr); } @@ -2935,7 +2935,7 @@ void CGameHandler::sendMessageToAll( const std::string &message ) bool CGameHandler::recruitCreatures(ObjectInstanceID objid, ObjectInstanceID dstid, CreatureID crid, ui32 cram, si32 fromLvl ) { - const CGDwelling *dw = static_cast(gs->getObj(objid)); + const CGDwelling * dw = static_cast(getObj(objid)); const CArmedInstance *dst = nullptr; const CCreature *c = VLC->creh->creatures.at(crid); bool warMachine = c->hasBonusOfType(Bonus::SIEGE_WEAPON); @@ -2970,7 +2970,7 @@ bool CGameHandler::recruitCreatures(ObjectInstanceID objid, ObjectInstanceID dst SlotID slot = dst->getSlotFor(crid); if( (!found && complain("Cannot recruit: no such creatures!")) - || (cram > VLC->creh->creatures.at(crid)->maxAmount(gs->getPlayer(dst->tempOwner)->resources) && complain("Cannot recruit: lack of resources!")) + || (cram > VLC->creh->creatures.at(crid)->maxAmount(getPlayer(dst->tempOwner)->resources) && complain("Cannot recruit: lack of resources!")) || (cram<=0 && complain("Cannot recruit: cram <= 0!")) || (!slot.validSlot() && !warMachine && complain("Cannot recruit: no available slot!"))) { @@ -2980,7 +2980,7 @@ bool CGameHandler::recruitCreatures(ObjectInstanceID objid, ObjectInstanceID dst //recruit SetResources sr; sr.player = dst->tempOwner; - sr.res = gs->getPlayer(dst->tempOwner)->resources - (c->cost * cram); + sr.res = getPlayer(dst->tempOwner)->resources - (c->cost * cram); SetAvailableCreatures sac; sac.tid = objid; @@ -3021,12 +3021,13 @@ bool CGameHandler::recruitCreatures(ObjectInstanceID objid, ObjectInstanceID dst bool CGameHandler::upgradeCreature( ObjectInstanceID objid, SlotID pos, CreatureID upgID ) { - CArmedInstance *obj = static_cast(gs->getObjInstance(objid)); + const CArmedInstance * obj = static_cast(getObjInstance(objid)); if (!obj->hasStackAtSlot(pos)) { COMPLAIN_RET("Cannot upgrade, no stack at slot " + boost::to_string(pos)); } - UpgradeInfo ui = gs->getUpgradeInfo(obj->getStack(pos)); + UpgradeInfo ui; + getUpgradeInfo(obj, pos, ui); PlayerColor player = obj->tempOwner; const PlayerState *p = getPlayer(player); int crQuantity = obj->stacks.at(pos)->count; @@ -3100,7 +3101,7 @@ void CGameHandler::moveArmy(const CArmedInstance *src, const CArmedInstance *dst bool CGameHandler::garrisonSwap( ObjectInstanceID tid ) { - CGTownInstance *town = gs->getTown(tid); + const CGTownInstance * town = getTown(tid); if(!town->garrisonHero && town->visitingHero) //visiting => garrison, merge armies: town army => hero army { @@ -3218,8 +3219,7 @@ bool CGameHandler::moveArtifact(const ArtifactLocation &al1, const ArtifactLocat */ bool CGameHandler::assembleArtifacts (ObjectInstanceID heroID, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo) { - - CGHeroInstance *hero = gs->getHero(heroID); + const CGHeroInstance * hero = getHero(heroID); const CArtifactInstance *destArtifact = hero->getArt(artifactSlot); if(!destArtifact) @@ -3253,8 +3253,8 @@ bool CGameHandler::assembleArtifacts (ObjectInstanceID heroID, ArtifactPosition bool CGameHandler::buyArtifact( ObjectInstanceID hid, ArtifactID aid ) { - CGHeroInstance *hero = gs->getHero(hid); - CGTownInstance *town = hero->visitedTown; + const CGHeroInstance * hero = getHero(hid); + const CGTownInstance * town = hero->visitedTown; if(aid==ArtifactID::SPELLBOOK) { if((!town->hasBuilt(BuildingID::MAGES_GUILD_1) && complain("Cannot buy a spellbook, no mage guild in the town!")) @@ -3274,7 +3274,7 @@ bool CGameHandler::buyArtifact( ObjectInstanceID hid, ArtifactID aid ) int price = VLC->arth->artifacts[aid]->price; if(( hero->getArt(ArtifactPosition(9+aid)) && complain("Hero already has this machine!")) - || (gs->getPlayer(hero->getOwner())->resources.at(Res::GOLD) < price && complain("Not enough gold!"))) + || (getPlayer(hero->getOwner())->resources.at(Res::GOLD) < price && complain("Not enough gold!"))) { return false; } @@ -3399,8 +3399,8 @@ bool CGameHandler::buySecSkill( const IMarket *m, const CGHeroInstance *h, Secon bool CGameHandler::tradeResources(const IMarket *market, ui32 val, PlayerColor player, ui32 id1, ui32 id2) { - int r1 = gs->getPlayer(player)->resources.at(id1), - r2 = gs->getPlayer(player)->resources.at(id2); + int r1 = getPlayer(player)->resources.at(id1), + r2 = getPlayer(player)->resources.at(id2); vstd::amin(val, r1); //can't trade more resources than have @@ -3490,15 +3490,15 @@ bool CGameHandler::transformInUndead(const IMarket *market, const CGHeroInstance bool CGameHandler::sendResources(ui32 val, PlayerColor player, Res::ERes r1, PlayerColor r2) { - const PlayerState *p2 = gs->getPlayer(r2, false); + const PlayerState *p2 = getPlayer(r2, false); if(!p2 || p2->status != EPlayerStatus::INGAME) { complain("Dest player must be in game!"); return false; } - si32 curRes1 = gs->getPlayer(player)->resources.at(r1), - curRes2 = gs->getPlayer(r2)->resources.at(r1); + si32 curRes1 = getPlayer(player)->resources.at(r1), + curRes2 = getPlayer(r2)->resources.at(r1); val = std::min(si32(val),curRes1); SetResource sr; @@ -3533,8 +3533,8 @@ bool CGameHandler::setFormation(ObjectInstanceID hid, ui8 formation) bool CGameHandler::hireHero(const CGObjectInstance *obj, ui8 hid, PlayerColor player) { - const PlayerState *p = gs->getPlayer(player); - const CGTownInstance *t = gs->getTown(obj->id); + const PlayerState * p = getPlayer(player); + const CGTownInstance * t = getTown(obj->id); //common preconditions // if( (p->resources.at(Res::GOLD)getHero(currObj); + const CGHeroInstance * h = getHero(currObj); if(!h && complain("Cannot realize cheat, no hero selected!")) return; sm.hid = h->id; @@ -4244,13 +4244,13 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message } else if (message == "vcmiarmenelos") //build all buildings in selected town { - CGHeroInstance *hero = gs->getHero(currObj); - CGTownInstance *town; + const CGHeroInstance * hero = getHero(currObj); + const CGTownInstance * town; if (hero) town = hero->visitedTown; else - town = gs->getTown(currObj); + town = getTown(currObj); if (town) { @@ -4267,7 +4267,7 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message } else if(message == "vcmiainur") //gives 5 archangels into each slot { - CGHeroInstance *hero = gs->getHero(currObj); + const CGHeroInstance * hero = getHero(currObj); const CCreature *archangel = VLC->creh->creatures.at(13); if(!hero) return; @@ -4277,7 +4277,7 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message } else if(message == "vcmiangband") //gives 10 black knight into each slot { - CGHeroInstance *hero = gs->getHero(currObj); + const CGHeroInstance * hero = getHero(currObj); const CCreature *blackKnight = VLC->creh->creatures.at(66); if(!hero) return; @@ -4287,7 +4287,7 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message } else if(message == "vcmiglaurung") //gives 5000 crystal dragons into each slot { - CGHeroInstance *hero = gs->getHero(currObj); + const CGHeroInstance * hero = getHero(currObj); const CCreature *crystalDragon = VLC->creh->creatures.at(133); if(!hero) return; @@ -4297,7 +4297,7 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message } else if(message == "vcminoldor") //all war machines { - CGHeroInstance *hero = gs->getHero(currObj); + const CGHeroInstance * hero = getHero(currObj); if(!hero) return; if(!hero->getArt(ArtifactPosition::MACH1)) @@ -4309,7 +4309,7 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message } else if (message == "vcmiforgeofnoldorking") //hero gets all artifacts except war machines, spell scrolls and spell book { - CGHeroInstance *hero = gs->getHero(currObj); + const CGHeroInstance *hero = gs->getHero(currObj); if(!hero) return; for (int g = 7; g < VLC->arth->artifacts.size(); ++g) //including artifacts from mods giveHeroNewArtifact(hero, VLC->arth->artifacts[g], ArtifactPosition::PRE_FIRST); @@ -4332,7 +4332,7 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message { SetResources sr; sr.player = player; - sr.res = gs->getPlayer(player)->resources; + sr.res = getPlayer(player)->resources; for(int i=0;igetPlayer(color, false); //do not output error if player does not exist + const PlayerState * pinfo = getPlayer(color, false); //do not output error if player does not exist if( pinfo //player exists && (ev.players & 1<tempOwner; CCastleEvent ev = town->events.front(); - PlayerState *pinfo = gs->getPlayer(player, false); + const PlayerState * pinfo = getPlayer(player, false); if( pinfo //player exists && (ev.players & 1<o->tempOwner; TResources boatCost; obj->getBoatCost(boatCost); - TResources aviable = gs->getPlayer(playerID)->resources; + TResources aviable = getPlayer(playerID)->resources; if (!aviable.canAfford(boatCost)) { @@ -4990,7 +4990,7 @@ void CGameHandler::checkVictoryLossConditions(const std::set & play { for(auto playerColor : playerColors) { - if(gs->getPlayer(playerColor, false)) + if(getPlayer(playerColor, false)) checkVictoryLossConditionsForPlayer(playerColor); } } @@ -5007,7 +5007,7 @@ void CGameHandler::checkVictoryLossConditionsForAll() void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player) { - const PlayerState *p = gs->getPlayer(player); + const PlayerState * p = getPlayer(player); if(p->status != EPlayerStatus::INGAME) return; auto victoryLossCheckResult = gs->checkForVictoryAndLoss(player); @@ -5028,10 +5028,10 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player) //one player won -> all enemies lost for (auto i = gs->players.cbegin(); i!=gs->players.cend(); i++) { - if(i->first != player && gs->getPlayer(i->first)->status == EPlayerStatus::INGAME) + if(i->first != player && getPlayer(i->first)->status == EPlayerStatus::INGAME) { peg.player = i->first; - peg.victoryLossCheckResult = gameState()->getPlayerRelations(player, i->first) == PlayerRelations::ALLIES ? + peg.victoryLossCheckResult = getPlayerRelations(player, i->first) == PlayerRelations::ALLIES ? victoryLossCheckResult : victoryLossCheckResult.invert(); // ally of winner InfoWindow iw; @@ -5120,7 +5120,7 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player) //notify all players for (auto pc : playerColors) { - if (gs->getPlayer(pc)->status == EPlayerStatus::INGAME) + if(getPlayer(pc)->status == EPlayerStatus::INGAME) { InfoWindow iw; getVictoryLossMessage(player, victoryLossCheckResult.invert(), iw); @@ -5131,7 +5131,7 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player) checkVictoryLossConditions(playerColors); } - auto playerInfo = gs->getPlayer(gs->currentPlayer, false); + auto playerInfo = getPlayer(gs->currentPlayer, false); // If we are called before the actual game start, there might be no current player if (playerInfo && playerInfo->status != EPlayerStatus::INGAME) { @@ -6019,7 +6019,7 @@ void CGameHandler::changeFogOfWar(int3 center, ui32 radius, PlayerColor player, if (hide) { std::unordered_set observedTiles; //do not hide tiles observed by heroes. May lead to disastrous AI problems - auto p = gs->getPlayer(player); + auto p = getPlayer(player); for (auto h : p->heroes) { getTilesInRange(observedTiles, h->getSightCenter(), h->getSightRadius(), h->tempOwner, -1); From 8b0c62e10ece7eae8b32a5f6cab2b7ec8a1369df Mon Sep 17 00:00:00 2001 From: Dydzio Date: Sun, 18 Sep 2016 22:05:05 +0200 Subject: [PATCH 05/15] Block surrender via escape tunnel. Fix issue 2389 --- lib/CBattleCallback.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/CBattleCallback.cpp b/lib/CBattleCallback.cpp index d2da9d392..21e468464 100644 --- a/lib/CBattleCallback.cpp +++ b/lib/CBattleCallback.cpp @@ -425,8 +425,10 @@ ui8 CBattleInfoEssentials::battleGetSiegeLevel() const bool CBattleInfoEssentials::battleCanSurrender(PlayerColor player) const { RETURN_IF_NOT_BATTLE(false); - //conditions like for fleeing + enemy must have a hero - return battleCanFlee(player) && battleHasHero(!playerToSide(player)); + ui8 mySide = playerToSide(player); + bool iAmSiegeDefender = ( mySide == BattleSide::DEFENDER && battleGetSiegeLevel() ); + //conditions like for fleeing (except escape tunnel presence) + enemy must have a hero + return battleCanFlee(player) && !iAmSiegeDefender && battleHasHero(!mySide); } bool CBattleInfoEssentials::battleHasHero(ui8 side) const From 58d11d60746298de8b02680c975873be9cadd67f Mon Sep 17 00:00:00 2001 From: Arseniy Shestakov Date: Sun, 18 Sep 2016 23:24:08 +0300 Subject: [PATCH 06/15] donotstartserver: ask for IP and port even for "host" player When "--donotstartserver" option specified client will show dialog to use any IP/port for host player. --- client/CPreGame.cpp | 22 ++++++++++++++-------- client/CPreGame.h | 4 ++-- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/client/CPreGame.cpp b/client/CPreGame.cpp index 3b629652a..c262c86b0 100644 --- a/client/CPreGame.cpp +++ b/client/CPreGame.cpp @@ -699,7 +699,10 @@ CSelectionScreen::CSelectionScreen(CMenuScreen::EState Type, CMenuScreen::EMulti if(isHost()) { assert(playerNames.size() == 1 && vstd::contains(playerNames, 1)); //TODO hot-seat/network combo - serv = sh->connectToServer(); + if(CServerHandler::DO_NOT_START_SERVER) + serv = CServerHandler::justConnectToServer(Address, Port); + else + serv = sh->connectToServer(); *serv << (ui8) 4; myNameID = 1; } @@ -3129,14 +3132,17 @@ void CMultiMode::hostTCP() Settings name = settings.write["general"]["playerName"]; name->String() = txt->text; GH.popIntTotally(this); - GH.pushInt(new CSelectionScreen(CMenuScreen::newGame, CMenuScreen::MULTI_NETWORK_HOST)); + if(CServerHandler::DO_NOT_START_SERVER) + GH.pushInt(new CSimpleJoinScreen(CMenuScreen::MULTI_NETWORK_HOST)); + else + GH.pushInt(new CSelectionScreen(CMenuScreen::newGame, CMenuScreen::MULTI_NETWORK_HOST)); } void CMultiMode::joinTCP() { Settings name = settings.write["general"]["playerName"]; name->String() = txt->text; - GH.pushInt(new CSimpleJoinScreen); + GH.pushInt(new CSimpleJoinScreen(CMenuScreen::MULTI_NETWORK_GUEST)); } CHotSeatPlayers::CHotSeatPlayers(const std::string &firstPlayer) @@ -4273,7 +4279,7 @@ void CPrologEpilogVideo::clickLeft( tribool down, bool previousState ) exitCb(); } -CSimpleJoinScreen::CSimpleJoinScreen() +CSimpleJoinScreen::CSimpleJoinScreen(CMenuScreen::EMultiMode mode) { OBJ_CONSTRUCTION_CAPTURING_ALL; bg = new CPicture("MUDIALOG.bmp"); // address background @@ -4289,7 +4295,7 @@ CSimpleJoinScreen::CSimpleJoinScreen() port->cb += std::bind(&CSimpleJoinScreen::onChange, this, _1); port->filters += std::bind(&CTextInput::numberFilter, _1, _2, 0, 65535); - ok = new CButton(Point( 26, 142), "MUBCHCK.DEF", CGI->generaltexth->zelp[560], std::bind(&CSimpleJoinScreen::enterSelectionScreen, this), SDLK_RETURN); + ok = new CButton(Point( 26, 142), "MUBCHCK.DEF", CGI->generaltexth->zelp[560], std::bind(&CSimpleJoinScreen::enterSelectionScreen, this, mode), SDLK_RETURN); cancel = new CButton(Point(142, 142), "MUBCANC.DEF", CGI->generaltexth->zelp[561], std::bind(&CGuiHandler::popIntTotally, std::ref(GH), this), SDLK_ESCAPE); bar = new CGStatusBar(new CPicture(Rect(7, 186, 218, 18), 0)); @@ -4298,15 +4304,15 @@ CSimpleJoinScreen::CSimpleJoinScreen() address->giveFocus(); } -void CSimpleJoinScreen::enterSelectionScreen() +void CSimpleJoinScreen::enterSelectionScreen(CMenuScreen::EMultiMode mode) { std::string textAddress = address->text; std::string textPort = port->text; GH.popIntTotally(this); - GH.pushInt(new CSelectionScreen(CMenuScreen::newGame, CMenuScreen::MULTI_NETWORK_GUEST, nullptr, textAddress, textPort)); -} + GH.pushInt(new CSelectionScreen(CMenuScreen::newGame, mode, nullptr, textAddress, textPort)); +} void CSimpleJoinScreen::onChange(const std::string & newText) { ok->block(address->text.empty() || port->text.empty()); diff --git a/client/CPreGame.h b/client/CPreGame.h index 486dd599a..c2931d7ef 100644 --- a/client/CPreGame.h +++ b/client/CPreGame.h @@ -642,10 +642,10 @@ class CSimpleJoinScreen : public CIntObject CTextInput * address; CTextInput * port; - void enterSelectionScreen(); + void enterSelectionScreen(CMenuScreen::EMultiMode mode); void onChange(const std::string & newText); public: - CSimpleJoinScreen(); + CSimpleJoinScreen(CMenuScreen::EMultiMode mode); }; extern ISelectionScreenInfo *SEL; From 75cffa7d0b6460e78238269cd0c27e3bfdb3b57a Mon Sep 17 00:00:00 2001 From: Arseniy Shestakov Date: Mon, 19 Sep 2016 03:20:44 +0300 Subject: [PATCH 07/15] CGameHandler::arrangeStacks: honour removableUnits of CGGarrison Now server properly check allowed actions for CGGarrison. Fix issue 2303 Server now allow all stack arrangement as long as troops stay inside garrison. It's possible to put more troops inside using swap/merge/split, but not take anything out if it. --- server/CGameHandler.cpp | 46 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 9c4abe3a2..ed19e1894 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2626,6 +2626,21 @@ bool CGameHandler::arrangeStacks( ObjectInstanceID id1, ObjectInstanceID id2, ui return false; } + // We can always put stacks into locked garrison, but not take them out of it + auto notRemovable = [&](const CArmedInstance * army) + { + if(id1 != id2) // Stack arrangement inside locked garrison is allowed + { + auto g = dynamic_cast(army); + if(g && !g->removableUnits) + { + complain("Stacks in this garrison are not removable!\n"); + return true; + } + } + return false; + }; + if(what==1) //swap { if ( ((s1->tempOwner != player && s1->tempOwner != PlayerColor::UNFLAGGABLE) && s1->getStackCount(p1)) @@ -2641,6 +2656,16 @@ bool CGameHandler::arrangeStacks( ObjectInstanceID id1, ObjectInstanceID id2, ui return false; } + if(!s1->slotEmpty(p1) && !s2->slotEmpty(p2)) + { + if(notRemovable(sl1.army) || notRemovable(sl2.army)) + return false; + } + if(s1->slotEmpty(p1) && notRemovable(sl2.army)) + return false; + else if(s2->slotEmpty(p2) && notRemovable(sl1.army)) + return false; + swapStacks(sl1, sl2); } else if(what==2)//merge @@ -2649,6 +2674,14 @@ bool CGameHandler::arrangeStacks( ObjectInstanceID id1, ObjectInstanceID id2, ui || (((s1->tempOwner != player && s1->tempOwner != PlayerColor::UNFLAGGABLE) && s2->getStackCount(p2)) && complain("Can't take troops from another player!"))) return false; + if(s1->slotEmpty(p1) || s2->slotEmpty(p2)) + { + complain("Cannot merge empty stack!"); + return false; + } + else if(notRemovable(sl1.army)) + return false; + moveStack(sl1, sl2); } else if(what==3) //split @@ -2681,6 +2714,17 @@ bool CGameHandler::arrangeStacks( ObjectInstanceID id1, ObjectInstanceID id2, ui return false; } + if(notRemovable(sl1.army)) + { + if(s1->getStackCount(p1) > countLeftOnSrc) + return false; + } + else if(notRemovable(sl2.army)) + { + if(s2->getStackCount(p1) < countLeftOnSrc) + return false; + } + moveStack(sl1, sl2, countToMove); //S2.slots[p2]->count = val; //S1.slots[p1]->count = total - val; @@ -2693,6 +2737,8 @@ bool CGameHandler::arrangeStacks( ObjectInstanceID id1, ObjectInstanceID id2, ui return false; } + if(notRemovable(sl1.army)) + return false; moveStack(sl1, sl2, val); } From 02a45007e7e9bc820c11d6cf492dbf6518bf7345 Mon Sep 17 00:00:00 2001 From: Arseniy Shestakov Date: Mon, 19 Sep 2016 03:30:55 +0300 Subject: [PATCH 08/15] VCAI::showGarrisonDialog: don't try to pick army from locked garrison --- AI/VCAI/VCAI.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 165658da3..668238881 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -697,7 +697,9 @@ void VCAI::showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *do //you can't request action from action-response thread requestActionASAP([=]() { - pickBestCreatures (down, up); + if(removableUnits) + pickBestCreatures(down, up); + answerQuery(queryID, 0); }); } From f721d22dfbe16817fc199ac0b36fc39dba2feac2 Mon Sep 17 00:00:00 2001 From: Arseniy Shestakov Date: Mon, 19 Sep 2016 04:56:20 +0300 Subject: [PATCH 09/15] CClient::loadGame: throw proper error if server save is missing --- client/Client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/Client.cpp b/client/Client.cpp index 6aa14d45d..99d4746ed 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -285,7 +285,7 @@ void CClient::loadGame(const std::string & fname, const bool server, const std:: if(clientSaveName.empty()) throw std::runtime_error("Cannot open client part of " + fname); - if(controlServerSaveName.empty()) + if(controlServerSaveName.empty() || !boost::filesystem::exists(controlServerSaveName)) throw std::runtime_error("Cannot open server part of " + fname); { From 31e5f7b800bbc0c947573492d474f7ff021eeebe Mon Sep 17 00:00:00 2001 From: Vadim Markovtsev Date: Mon, 19 Sep 2016 18:54:05 +0200 Subject: [PATCH 10/15] Fix morale widget update after dismissing a creature in garrison --- client/windows/CHeroWindow.cpp | 6 ++++++ client/windows/CHeroWindow.h | 1 + 2 files changed, 7 insertions(+) diff --git a/client/windows/CHeroWindow.cpp b/client/windows/CHeroWindow.cpp index ece7bd7f0..6761dfc30 100644 --- a/client/windows/CHeroWindow.cpp +++ b/client/windows/CHeroWindow.cpp @@ -344,6 +344,12 @@ void CHeroWindow::commanderWindow() } +void CHeroWindow::updateGarrisons() +{ + CWindowWithGarrison::updateGarrisons(); + morale->set(&heroWArt); +} + void CHeroWindow::showAll(SDL_Surface * to) { CIntObject::showAll(to); diff --git a/client/windows/CHeroWindow.h b/client/windows/CHeroWindow.h index 76455cef6..04f2baffc 100644 --- a/client/windows/CHeroWindow.h +++ b/client/windows/CHeroWindow.h @@ -91,6 +91,7 @@ public: void questlog(); //show quest log in hero window void commanderWindow(); void switchHero(); //changes displayed hero + virtual void updateGarrisons() override; //updates the morale widget and calls the parent //friends friend void CArtPlace::clickLeft(tribool down, bool previousState); From 563a5d53c0e48e98ee1ca9a5f0d5e3fafe3eb336 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Mon, 19 Sep 2016 20:05:57 +0200 Subject: [PATCH 11/15] Update secondary skill handling. Fix issue 2307 Also making secondary skill icon display behavior same as in H3 during popup message. --- lib/mapObjects/CGPandoraBox.cpp | 47 +++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/lib/mapObjects/CGPandoraBox.cpp b/lib/mapObjects/CGPandoraBox.cpp index 8568772d3..a5c3a4fd8 100644 --- a/lib/mapObjects/CGPandoraBox.cpp +++ b/lib/mapObjects/CGPandoraBox.cpp @@ -68,32 +68,51 @@ void CGPandoraBox::giveContentsUpToExp(const CGHeroInstance *h) const if(gainedExp || changesPrimSkill || abilities.size()) { + std::vector unpossessedAbilities; + std::vector unpossessedAbilityLevels; + int abilitiesRequiringSlot = 0; + + //filter out unnecessary secondary skills + for (int i = 0; i < abilities.size(); i++) + { + int curLev = h->getSecSkillLevel(abilities[i]); + bool abilityCanUseSlot = !curLev && ((h->secSkills.size() + abilitiesRequiringSlot) < GameConstants::SKILL_PER_HERO); //limit new abilities to number of slots + + if (abilityCanUseSlot) + abilitiesRequiringSlot++; + + if ( (curLev && curLev < abilityLevels[i]) || abilityCanUseSlot ) + { + unpossessedAbilities.push_back(abilities[i]); + unpossessedAbilityLevels.push_back(abilityLevels[i]); + } + } + TExpType expVal = h->calculateXp(gainedExp); //getText(iw,afterBattle,175,h); //wtf? - iw.text.addTxt(MetaString::ADVOB_TXT, 175); //%s learns something - iw.text.addReplacement(h->name); if(expVal) iw.components.push_back(Component(Component::EXPERIENCE,0,expVal,0)); + for(int i=0; ishowInfoDialog(&iw); + for(int i=0; i 0) //TODO:Create information that box was empty for now, and deliver to CGPandoraBox::giveContentsAfterExp + { + iw.text.addTxt(MetaString::ADVOB_TXT, 175); //%s learns something + iw.text.addReplacement(h->name); + cb->showInfoDialog(&iw); + } //give sec skills - for(int i=0; igetSecSkillLevel(abilities[i]); + for (int i = 0; ichangeSecSkill(h, unpossessedAbilities[i], unpossessedAbilityLevels[i],true); - if( (curLev && curLev < abilityLevels[i]) || (h->canLearnSkill() )) - { - cb->changeSecSkill(h,abilities[i],abilityLevels[i],true); - } - } + assert(h->secSkills.size() <= GameConstants::SKILL_PER_HERO); //give prim skills for(int i=0; i Date: Tue, 20 Sep 2016 10:49:29 +0300 Subject: [PATCH 12/15] New cheat vcmiungoliant: hide all tiles that out of sight radius --- server/CGameHandler.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index ed19e1894..20aa2a29b 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -4384,17 +4384,17 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message sr.res[Res::GOLD] += 100000; //100k sendAndApply(&sr); } - else if(message == "vcmieagles") //reveal FoW + else if(message == "vcmieagles" || message == "vcmiungoliant") //reveal or conceal FoW { FoWChange fc; - fc.mode = 1; + fc.mode = (message == "vcmieagles" ? 1 : 0); fc.player = player; auto hlp_tab = new int3[gs->map->width * gs->map->height * (gs->map->twoLevel ? 2 : 1)]; int lastUnc = 0; for(int i=0;imap->width;i++) for(int j=0;jmap->height;j++) for(int k = 0; k < (gs->map->twoLevel ? 2 : 1); k++) - if(!gs->getPlayerTeam(fc.player)->fogOfWarMap.at(i).at(j).at(k)) + if(!gs->getPlayerTeam(fc.player)->fogOfWarMap.at(i).at(j).at(k) || message == "vcmiungoliant") hlp_tab[lastUnc++] = int3(i,j,k); fc.tiles.insert(hlp_tab, hlp_tab + lastUnc); delete [] hlp_tab; From bd651ec5efe2362e903db33e1e458bcb4c6c490d Mon Sep 17 00:00:00 2001 From: Dydzio Date: Tue, 20 Sep 2016 12:40:58 +0200 Subject: [PATCH 13/15] Improve pandora box secondary skill handling --- lib/mapObjects/CGPandoraBox.cpp | 54 +++++++++++++++------------------ 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/lib/mapObjects/CGPandoraBox.cpp b/lib/mapObjects/CGPandoraBox.cpp index a5c3a4fd8..b10e818e3 100644 --- a/lib/mapObjects/CGPandoraBox.cpp +++ b/lib/mapObjects/CGPandoraBox.cpp @@ -65,32 +65,32 @@ void CGPandoraBox::giveContentsUpToExp(const CGHeroInstance *h) const break; } } + + std::vector> unpossessedAbilities; //ability + ability level + int abilitiesRequiringSlot = 0; - if(gainedExp || changesPrimSkill || abilities.size()) + //filter out unnecessary secondary skills + for (int i = 0; i < abilities.size(); i++) { - std::vector unpossessedAbilities; - std::vector unpossessedAbilityLevels; - int abilitiesRequiringSlot = 0; + int curLev = h->getSecSkillLevel(abilities[i]); + bool abilityCanUseSlot = !curLev && ((h->secSkills.size() + abilitiesRequiringSlot) < GameConstants::SKILL_PER_HERO); //limit new abilities to number of slots - //filter out unnecessary secondary skills - for (int i = 0; i < abilities.size(); i++) + if (abilityCanUseSlot) + abilitiesRequiringSlot++; + + if ((curLev && curLev < abilityLevels[i]) || abilityCanUseSlot) { - int curLev = h->getSecSkillLevel(abilities[i]); - bool abilityCanUseSlot = !curLev && ((h->secSkills.size() + abilitiesRequiringSlot) < GameConstants::SKILL_PER_HERO); //limit new abilities to number of slots - - if (abilityCanUseSlot) - abilitiesRequiringSlot++; - - if ( (curLev && curLev < abilityLevels[i]) || abilityCanUseSlot ) - { - unpossessedAbilities.push_back(abilities[i]); - unpossessedAbilityLevels.push_back(abilityLevels[i]); - } + unpossessedAbilities.push_back({ abilities[i], abilityLevels[i] }); } - + } + + if(gainedExp || changesPrimSkill || unpossessedAbilities.size()) + { TExpType expVal = h->calculateXp(gainedExp); //getText(iw,afterBattle,175,h); //wtf? - + iw.text.addTxt(MetaString::ADVOB_TXT, 175); //%s learns something + iw.text.addReplacement(h->name); + if(expVal) iw.components.push_back(Component(Component::EXPERIENCE,0,expVal,0)); @@ -98,19 +98,14 @@ void CGPandoraBox::giveContentsUpToExp(const CGHeroInstance *h) const if(primskills[i]) iw.components.push_back(Component(Component::PRIM_SKILL,i,primskills[i],0)); - for(int i=0; i 0) //TODO:Create information that box was empty for now, and deliver to CGPandoraBox::giveContentsAfterExp - { - iw.text.addTxt(MetaString::ADVOB_TXT, 175); //%s learns something - iw.text.addReplacement(h->name); - cb->showInfoDialog(&iw); - } + cb->showInfoDialog(&iw); //give sec skills - for (int i = 0; ichangeSecSkill(h, unpossessedAbilities[i], unpossessedAbilityLevels[i],true); + for (auto abilityData : unpossessedAbilities) + cb->changeSecSkill(h, abilityData.first, abilityData.second, true); assert(h->secSkills.size() <= GameConstants::SKILL_PER_HERO); @@ -125,6 +120,7 @@ void CGPandoraBox::giveContentsUpToExp(const CGHeroInstance *h) const if(expVal) cb->changePrimSkill(h, PrimarySkill::EXPERIENCE, expVal, false); } + //else { } //TODO:Create information that box was empty for now, and deliver to CGPandoraBox::giveContentsAfterExp or refactor if(!cb->isVisitCoveredByAnotherQuery(this, h)) giveContentsAfterExp(h); From dba58e5eb1a8c15e4e92fe2cf0642ec59c994c29 Mon Sep 17 00:00:00 2001 From: Arseniy Shestakov Date: Wed, 21 Sep 2016 19:09:02 +0300 Subject: [PATCH 14/15] Faerie Dragon: decrease Magic Mirror cast chance from 30 to 20 percent Povelitel find out it's work too often and FizMiG doc confirms this. --- config/creatures/neutral.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/creatures/neutral.json b/config/creatures/neutral.json index 7f66d6988..dd0a5e536 100644 --- a/config/creatures/neutral.json +++ b/config/creatures/neutral.json @@ -151,7 +151,7 @@ "mirror" : { "type" : "MAGIC_MIRROR", - "val" : 30 + "val" : 20 }, "casts" : { From 9963b9e02b238b74560fd26af0f12a9b4091cbc0 Mon Sep 17 00:00:00 2001 From: Arseniy Shestakov Date: Thu, 22 Sep 2016 03:43:30 +0300 Subject: [PATCH 15/15] New command line option for testing: testingsavefrequency Saving is slowest part of VCMI and for testing purposes like benchmarking it's helpful to create saves less often. When --testingsavefrequency=N is specified client going to save only once in N days. Option only active if other testing options are specified too. --- client/CMT.cpp | 4 +++- client/CPlayerInterface.cpp | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/client/CMT.cpp b/client/CMT.cpp index de916b0b8..a7dd4b867 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -248,7 +248,8 @@ int main(int argc, char** argv) ("loadserverip",po::value(),"IP for loaded game server") ("loadserverport",po::value(),"port for loaded game server") ("testingport",po::value(),"port for testing, override specified in config file") - ("testingfileprefix",po::value(),"prefix for auto save files"); + ("testingfileprefix",po::value(),"prefix for auto save files") + ("testingsavefrequency",po::value(),"how often auto save should be created"); if(argc > 1) { @@ -313,6 +314,7 @@ int main(int argc, char** argv) testingSettings["enabled"].Bool() = true; testingSettings["port"].String() = vm["testingport"].as(); testingSettings["prefix"].String() = vm["testingfileprefix"].as(); + testingSettings["savefrequency"].Float() = vm.count("testingsavefrequency") ? vm["testingsavefrequency"].as() : 1; } // Initialize logging based on settings diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index e5fae6d36..c67a3785a 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -178,7 +178,7 @@ void CPlayerInterface::yourTurn() } firstCall = 0; } - else + else if(settings["testing"].isNull() || cb->getDate() % static_cast(settings["testing"]["savefrequency"].Float()) == 0) { LOCPLINT->cb->save("Saves/" + prefix + "Autosave_" + boost::lexical_cast(autosaveCount++ + 1)); autosaveCount %= 5;