diff --git a/.github/workflows/github.yml b/.github/workflows/github.yml index 99dcbd8b7..fc40fd23a 100644 --- a/.github/workflows/github.yml +++ b/.github/workflows/github.yml @@ -17,6 +17,7 @@ env: jobs: build: strategy: + fail-fast: false matrix: include: - platform: linux-qt6 diff --git a/AI/Nullkiller/AIGateway.cpp b/AI/Nullkiller/AIGateway.cpp index 3609182df..99172b213 100644 --- a/AI/Nullkiller/AIGateway.cpp +++ b/AI/Nullkiller/AIGateway.cpp @@ -864,7 +864,7 @@ void AIGateway::makeTurn() void AIGateway::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h) { - LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->getNameTranslated() % obj->getObjectName() % obj->pos.toString()); + LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->getNameTranslated() % obj->getObjectName() % obj->anchorPos().toString()); switch(obj->ID) { case Obj::TOWN: @@ -1454,8 +1454,8 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h) void AIGateway::buildStructure(const CGTownInstance * t, BuildingID building) { - auto name = t->town->buildings.at(building)->getNameTranslated(); - logAi->debug("Player %d will build %s in town of %s at %s", ai->playerID, name, t->getNameTranslated(), t->pos.toString()); + auto name = t->getTown()->buildings.at(building)->getNameTranslated(); + logAi->debug("Player %d will build %s in town of %s at %s", ai->playerID, name, t->getNameTranslated(), t->anchorPos().toString()); cb->buildBuilding(t, building); //just do this; } diff --git a/AI/Nullkiller/Analyzers/ArmyManager.cpp b/AI/Nullkiller/Analyzers/ArmyManager.cpp index 9c70410b5..ccbdf1770 100644 --- a/AI/Nullkiller/Analyzers/ArmyManager.cpp +++ b/AI/Nullkiller/Analyzers/ArmyManager.cpp @@ -144,7 +144,7 @@ std::vector ArmyManager::getBestArmy(const IBonusBearer * armyCarrier, for(auto & slot : sortedSlots) { - alignmentMap[slot.creature->getFaction()] += slot.power; + alignmentMap[slot.creature->getFactionID()] += slot.power; } std::set allowedFactions; @@ -168,7 +168,7 @@ std::vector ArmyManager::getBestArmy(const IBonusBearer * armyCarrier, for(auto & slot : sortedSlots) { - if(vstd::contains(allowedFactions, slot.creature->getFaction())) + if(vstd::contains(allowedFactions, slot.creature->getFactionID())) { auto slotID = newArmyInstance.getSlotFor(slot.creature->getId()); diff --git a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp index ce9c00f36..01dfa0a82 100644 --- a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp @@ -17,7 +17,7 @@ namespace NKAI void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo) { - auto townInfo = developmentInfo.town->town; + auto townInfo = developmentInfo.town->getTown(); auto creatures = townInfo->creatures; auto buildings = townInfo->getAllBuildings(); @@ -31,7 +31,7 @@ void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo) } } - for(int level = 0; level < developmentInfo.town->town->creatures.size(); level++) + for(int level = 0; level < developmentInfo.town->getTown()->creatures.size(); level++) { logAi->trace("Checking dwelling level %d", level); BuildingInfo nextToBuild = BuildingInfo(); @@ -88,7 +88,7 @@ void BuildAnalyzer::updateOtherBuildings(TownDevelopmentInfo & developmentInfo) { for(auto & buildingID : buildingSet) { - if(!developmentInfo.town->hasBuilt(buildingID) && developmentInfo.town->town->buildings.count(buildingID)) + if(!developmentInfo.town->hasBuilt(buildingID) && developmentInfo.town->getTown()->buildings.count(buildingID)) { developmentInfo.addBuildingToBuild(getBuildingOrPrerequisite(developmentInfo.town, buildingID)); @@ -203,7 +203,7 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite( bool excludeDwellingDependencies) const { BuildingID building = toBuild; - auto townInfo = town->town; + auto townInfo = town->getTown(); const CBuilding * buildPtr = townInfo->buildings.at(building); const CCreature * creature = nullptr; @@ -346,7 +346,7 @@ bool BuildAnalyzer::hasAnyBuilding(int32_t alignment, BuildingID bid) const { for(auto tdi : developmentInfos) { - if(tdi.town->getFaction() == alignment && tdi.town->hasBuilt(bid)) + if(tdi.town->getFactionID() == alignment && tdi.town->hasBuilt(bid)) return true; } diff --git a/AI/Nullkiller/Analyzers/HeroManager.cpp b/AI/Nullkiller/Analyzers/HeroManager.cpp index e559bd264..b5c07d62e 100644 --- a/AI/Nullkiller/Analyzers/HeroManager.cpp +++ b/AI/Nullkiller/Analyzers/HeroManager.cpp @@ -71,7 +71,7 @@ float HeroManager::evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * float HeroManager::evaluateSpeciality(const CGHeroInstance * hero) const { - auto heroSpecial = Selector::source(BonusSource::HERO_SPECIAL, BonusSourceID(hero->type->getId())); + auto heroSpecial = Selector::source(BonusSource::HERO_SPECIAL, BonusSourceID(hero->getHeroTypeID())); auto secondarySkillBonus = Selector::targetSourceType()(BonusSource::SECONDARY_SKILL); auto specialSecondarySkillBonuses = hero->getBonuses(heroSpecial.And(secondarySkillBonus)); auto secondarySkillBonuses = hero->getBonuses(Selector::sourceTypeSel(BonusSource::SECONDARY_SKILL)); diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 9e23377f3..1ae243055 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -1244,7 +1244,7 @@ public: uint64_t RewardEvaluator::getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const { - if(ai->buildAnalyzer->hasAnyBuilding(town->getFaction(), bi.id)) + if(ai->buildAnalyzer->hasAnyBuilding(town->getFactionID(), bi.id)) return 0; auto creaturesToUpgrade = ai->armyManager->getTotalCreaturesAvailable(bi.baseCreatureID); diff --git a/AI/Nullkiller/Goals/BuildThis.cpp b/AI/Nullkiller/Goals/BuildThis.cpp index c9c89c0a9..414c8a03d 100644 --- a/AI/Nullkiller/Goals/BuildThis.cpp +++ b/AI/Nullkiller/Goals/BuildThis.cpp @@ -23,7 +23,7 @@ BuildThis::BuildThis(BuildingID Bid, const CGTownInstance * tid) : ElementarGoal(Goals::BUILD_STRUCTURE) { buildingInfo = BuildingInfo( - tid->town->buildings.at(Bid), + tid->getTown()->buildings.at(Bid), nullptr, CreatureID::NONE, tid, @@ -52,7 +52,7 @@ void BuildThis::accept(AIGateway * ai) if(cb->canBuildStructure(town, b) == EBuildingState::ALLOWED) { logAi->debug("Player %d will build %s in town of %s at %s", - ai->playerID, town->town->buildings.at(b)->getNameTranslated(), town->getNameTranslated(), town->pos.toString()); + ai->playerID, town->getTown()->buildings.at(b)->getNameTranslated(), town->getNameTranslated(), town->anchorPos().toString()); cb->buildBuilding(town, b); return; diff --git a/AI/VCAI/BuildingManager.cpp b/AI/VCAI/BuildingManager.cpp index 202661c33..60d971086 100644 --- a/AI/VCAI/BuildingManager.cpp +++ b/AI/VCAI/BuildingManager.cpp @@ -23,13 +23,13 @@ bool BuildingManager::tryBuildThisStructure(const CGTownInstance * t, BuildingID return false; } - if (!vstd::contains(t->town->buildings, building)) + if (!vstd::contains(t->getTown()->buildings, building)) return false; // no such building in town if (t->hasBuilt(building)) //Already built? Shouldn't happen in general return true; - const CBuilding * buildPtr = t->town->buildings.at(building); + const CBuilding * buildPtr = t->getTown()->buildings.at(building); auto toBuild = buildPtr->requirements.getFulfillmentCandidates([&](const BuildingID & buildID) { @@ -51,7 +51,7 @@ bool BuildingManager::tryBuildThisStructure(const CGTownInstance * t, BuildingID for (const auto & buildID : toBuild) { - const CBuilding * b = t->town->buildings.at(buildID); + const CBuilding * b = t->getTown()->buildings.at(buildID); EBuildingState canBuild = cb->canBuildStructure(t, buildID); if (canBuild == EBuildingState::ALLOWED) @@ -220,7 +220,7 @@ bool BuildingManager::getBuildingOptions(const CGTownInstance * t) //at the end, try to get and build any extra buildings with nonstandard slots (for example HotA 3rd level dwelling) std::vector extraBuildings; - for (auto buildingInfo : t->town->buildings) + for (auto buildingInfo : t->getTown()->buildings) { if (buildingInfo.first > BuildingID::DWELL_UP2_FIRST) extraBuildings.push_back(buildingInfo.first); diff --git a/AI/VCAI/Goals/BuildThis.cpp b/AI/VCAI/Goals/BuildThis.cpp index ee1e3d41a..62e3a1649 100644 --- a/AI/VCAI/Goals/BuildThis.cpp +++ b/AI/VCAI/Goals/BuildThis.cpp @@ -56,7 +56,7 @@ TSubgoal BuildThis::whatToDoToAchieve() case EBuildingState::ALLOWED: case EBuildingState::NO_RESOURCES: { - auto res = town->town->buildings.at(BuildingID(bid))->resources; + auto res = town->getTown()->buildings.at(BuildingID(bid))->resources; return ai->ah->whatToDo(res, iAmElementar()); //realize immediately or gather resources } break; diff --git a/AI/VCAI/Goals/FindObj.cpp b/AI/VCAI/Goals/FindObj.cpp index 189a5c44b..288474eaa 100644 --- a/AI/VCAI/Goals/FindObj.cpp +++ b/AI/VCAI/Goals/FindObj.cpp @@ -46,7 +46,7 @@ TSubgoal FindObj::whatToDoToAchieve() } } } - if(o && ai->isAccessible(o->pos)) //we don't use isAccessibleForHero as we don't know which hero it is + if(o && ai->isAccessible(o->visitablePos())) //we don't use isAccessibleForHero as we don't know which hero it is return sptr(VisitObj(o->id.getNum())); else return sptr(Explore()); diff --git a/AI/VCAI/Goals/GatherTroops.cpp b/AI/VCAI/Goals/GatherTroops.cpp index 275bef9f5..50b8fdce5 100644 --- a/AI/VCAI/Goals/GatherTroops.cpp +++ b/AI/VCAI/Goals/GatherTroops.cpp @@ -88,13 +88,13 @@ TGoalVec GatherTroops::getAllPossibleSubgoals() } auto creature = VLC->creatures()->getByIndex(objid); - if(t->getFaction() == creature->getFaction()) //TODO: how to force AI to build unupgraded creatures? :O + if(t->getFactionID() == creature->getFactionID()) //TODO: how to force AI to build unupgraded creatures? :O { auto tryFindCreature = [&]() -> std::optional> { - if(vstd::isValidIndex(t->town->creatures, creature->getLevel() - 1)) + if(vstd::isValidIndex(t->getTown()->creatures, creature->getLevel() - 1)) { - auto itr = t->town->creatures.begin(); + auto itr = t->getTown()->creatures.begin(); std::advance(itr, creature->getLevel() - 1); return make_optional(*itr); } @@ -109,7 +109,7 @@ TGoalVec GatherTroops::getAllPossibleSubgoals() if(upgradeNumber < 0) continue; - BuildingID bid(BuildingID::DWELL_FIRST + creature->getLevel() - 1 + upgradeNumber * t->town->creatures.size()); + BuildingID bid(BuildingID::DWELL_FIRST + creature->getLevel() - 1 + upgradeNumber * t->getTown()->creatures.size()); if(t->hasBuilt(bid) && ai->ah->freeResources().canAfford(creature->getFullRecruitCost())) //this assumes only creatures with dwellings are assigned to faction { solutions.push_back(sptr(BuyArmy(t, creature->getAIValue() * this->value).setobjid(objid))); diff --git a/AI/VCAI/MapObjectsEvaluator.cpp b/AI/VCAI/MapObjectsEvaluator.cpp index 5536d8fd9..e430c1f08 100644 --- a/AI/VCAI/MapObjectsEvaluator.cpp +++ b/AI/VCAI/MapObjectsEvaluator.cpp @@ -69,7 +69,7 @@ std::optional MapObjectsEvaluator::getObjectValue(const CGObjectInstance * { //special case handling: in-game heroes have hero ID as object subID, but when reading configs available hero object subID's are hero classes auto hero = dynamic_cast(obj); - return getObjectValue(obj->ID, hero->type->heroClass->getIndex()); + return getObjectValue(obj->ID, hero->getHeroClassID()); } else if(obj->ID == Obj::PRISON) { diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 3757c56d3..f9ebb1657 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -1032,7 +1032,7 @@ void VCAI::mainLoop() void VCAI::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h) { - LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->getNameTranslated() % obj->getObjectName() % obj->pos.toString()); + LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->getNameTranslated() % obj->getObjectName() % obj->anchorPos().toString()); switch(obj->ID) { case Obj::TOWN: @@ -1417,11 +1417,11 @@ void VCAI::wander(HeroPtr h) //TODO pick the truly best const CGTownInstance * t = *boost::max_element(townsNotReachable, compareReinforcements); logAi->debug("%s can't reach any town, we'll try to make our way to %s at %s", h->getNameTranslated(), t->getNameTranslated(), t->visitablePos().toString()); - int3 pos1 = h->pos; + int3 posBefore = h->visitablePos(); striveToGoal(sptr(Goals::ClearWayTo(t->visitablePos()).sethero(h))); //TODO: drop "strive", add to mainLoop //if out hero is stuck, we may need to request another hero to clear the way we see - if(pos1 == h->pos && h == primaryHero()) //hero can't move + if(posBefore == h->visitablePos() && h == primaryHero()) //hero can't move { if(canRecruitAnyHero(t)) recruitHero(t); @@ -1471,7 +1471,7 @@ void VCAI::wander(HeroPtr h) { auto chosenObject = cb->getObjInstance(ObjectInstanceID(bestObjectGoal->objid)); if(chosenObject != nullptr) - logAi->debug("Of all %d destinations, object %s at pos=%s seems nice", dests.size(), chosenObject->getObjectName(), chosenObject->pos.toString()); + logAi->debug("Of all %d destinations, object %s at pos=%s seems nice", dests.size(), chosenObject->getObjectName(), chosenObject->anchorPos().toString()); } else logAi->debug("Trying to realize goal of type %s as part of wandering.", bestObjectGoal->name()); @@ -1994,8 +1994,8 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) void VCAI::buildStructure(const CGTownInstance * t, BuildingID building) { - auto name = t->town->buildings.at(building)->getNameTranslated(); - logAi->debug("Player %d will build %s in town of %s at %s", ai->playerID, name, t->getNameTranslated(), t->pos.toString()); + auto name = t->getTown()->buildings.at(building)->getNameTranslated(); + logAi->debug("Player %d will build %s in town of %s at %s", ai->playerID, name, t->getNameTranslated(), t->anchorPos().toString()); cb->buildBuilding(t, building); //just do this; } @@ -2081,7 +2081,7 @@ void VCAI::tryRealize(Goals::BuildThis & g) if (cb->canBuildStructure(t, b) == EBuildingState::ALLOWED) { logAi->debug("Player %d will build %s in town of %s at %s", - playerID, t->town->buildings.at(b)->getNameTranslated(), t->getNameTranslated(), t->pos.toString()); + playerID, t->getTown()->buildings.at(b)->getNameTranslated(), t->getNameTranslated(), t->anchorPos().toString()); cb->buildBuilding(t, b); throw goalFulfilledException(sptr(g)); } diff --git a/CCallback.cpp b/CCallback.cpp index 02f6d7a2d..72b6e8b78 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -25,6 +25,7 @@ #include "lib/UnlockGuard.h" #include "lib/battle/BattleInfo.h" #include "lib/networkPacks/PacksForServer.h" +#include "lib/networkPacks/SaveLocalState.h" bool CCallback::teleportHero(const CGHeroInstance *who, const CGTownInstance *where) { @@ -318,11 +319,20 @@ void CCallback::recruitHero(const CGObjectInstance *townOrTavern, const CGHeroIn assert(townOrTavern); assert(hero); - HireHero pack(hero->getHeroType(), townOrTavern->id, nextHero); + HireHero pack(hero->getHeroTypeID(), townOrTavern->id, nextHero); pack.player = *player; sendRequest(pack); } +void CCallback::saveLocalState(const JsonNode & data) +{ + SaveLocalState state; + state.data = data; + state.player = *player; + + sendRequest(state); +} + void CCallback::save( const std::string &fname ) { cl->save(fname); diff --git a/CCallback.h b/CCallback.h index 39b9fc8ce..6e30299c6 100644 --- a/CCallback.h +++ b/CCallback.h @@ -100,6 +100,7 @@ public: virtual void assembleArtifacts(const ObjectInstanceID & heroID, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)=0; virtual void eraseArtifactByClient(const ArtifactLocation & al)=0; virtual bool dismissCreature(const CArmedInstance *obj, SlotID stackPos)=0; + virtual void saveLocalState(const JsonNode & data)=0; virtual void endTurn()=0; virtual void buyArtifact(const CGHeroInstance *hero, ArtifactID aid)=0; //used to buy artifacts in towns (including spell book in the guild and war machines in blacksmith) virtual void setFormation(const CGHeroInstance * hero, EArmyFormation mode)=0; @@ -193,6 +194,7 @@ public: void recruitCreatures(const CGDwelling * obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level=-1) override; bool dismissCreature(const CArmedInstance *obj, SlotID stackPos) override; bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE) override; + void saveLocalState(const JsonNode & data) override; void endTurn() override; void spellResearch(const CGTownInstance *town, SpellID spellAtSlot, bool accepted) override; void swapGarrisonHero(const CGTownInstance *town) override; diff --git a/Mods/vcmi/config/vcmi/swedish.json b/Mods/vcmi/config/vcmi/swedish.json index 8998f5700..57f0bae33 100644 --- a/Mods/vcmi/config/vcmi/swedish.json +++ b/Mods/vcmi/config/vcmi/swedish.json @@ -13,6 +13,8 @@ "vcmi.adventureMap.monsterThreat.levels.10" : "Dödlig", "vcmi.adventureMap.monsterThreat.levels.11" : "Omöjlig", "vcmi.adventureMap.monsterLevel" : "\n\nNivå: %LEVEL - Faktion: %TOWN", + "vcmi.adventureMap.monsterMeleeType" : "närstrid", + "vcmi.adventureMap.monsterRangedType" : "fjärrstrid", "vcmi.adventureMap.confirmRestartGame" : "Är du säker på att du vill starta om spelet?", "vcmi.adventureMap.noTownWithMarket" : "Det finns inga tillgängliga marknadsplatser!", @@ -21,7 +23,7 @@ "vcmi.adventureMap.playerAttacked" : "Spelare har blivit attackerad: %s", "vcmi.adventureMap.moveCostDetails" : "Förflyttningspoängs-kostnad: %TURNS tur(er) + %POINTS poäng - Återstående poäng: %REMAINING", "vcmi.adventureMap.moveCostDetailsNoTurns" : "Förflyttningspoängs-kostnad: %POINTS poäng - Återstående poäng: %REMAINING", - "vcmi.adventureMap.movementPointsHeroInfo" : "(Förflyttningspoäng: %REMAINING / %POINTS)", + "vcmi.adventureMap.movementPointsHeroInfo" : "(Förflyttningspoäng: %REMAINING / %POINTS)", "vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Tyvärr, att spela om motståndarens tur är inte implementerat ännu!", "vcmi.capitalColors.0" : "Röd", @@ -135,14 +137,15 @@ "vcmi.lobby.pvp.coin.hover" : "Mynt", "vcmi.lobby.pvp.coin.help" : "Singla slant", "vcmi.lobby.pvp.randomTown.hover" : "Slumpmässig stad", - "vcmi.lobby.pvp.randomTown.help" : "Skriv en slumpmässig stad i chatten", - "vcmi.lobby.pvp.randomTownVs.hover" : "Slumpmässig stad vs.", - "vcmi.lobby.pvp.randomTownVs.help" : "Skriv två slumpmässiga städer i chatten", + "vcmi.lobby.pvp.randomTown.help" : "Skriv en slumpad stad i chatten", + "vcmi.lobby.pvp.randomTownVs.hover" : "Slumpad stad vs.", + "vcmi.lobby.pvp.randomTownVs.help" : "Skriv två slumpade städer i chatten", "vcmi.lobby.pvp.versus" : "vs.", "vcmi.client.errors.invalidMap" : "{Ogiltig karta eller kampanj}\n\nStartade inte spelet! Vald karta eller kampanj kan vara ogiltig eller skadad. Orsak:\n%s", "vcmi.client.errors.missingCampaigns" : "{Saknade datafiler}\n\nKampanjernas datafiler hittades inte! Du kanske använder ofullständiga eller skadade Heroes 3-datafiler. Vänligen installera om speldata.", "vcmi.server.errors.disconnected" : "{Nätverksfel}\n\nAnslutningen till spelservern har förlorats!", + "vcmi.server.errors.playerLeft" : "{Spelare har lämnat}\n\n%s spelaren har kopplat bort sig från spelet!", //%s -> spelarens färg "vcmi.server.errors.existingProcess" : "En annan VCMI-serverprocess är igång. Vänligen avsluta den innan du startar ett nytt spel.", "vcmi.server.errors.modsToEnable" : "{Följande modd(ar) krävs}", "vcmi.server.errors.modsToDisable" : "{Följande modd(ar) måste inaktiveras}", @@ -221,8 +224,8 @@ "vcmi.systemOptions.enableUiEnhancementsButton.help" : "{Gränssnittsförbättringar}\n\nVälj mellan olika förbättringar av användargränssnittet. Till exempel en lättåtkomlig ryggsäcksknapp med mera. Avaktivera för att få en mer klassisk spelupplevelse.", "vcmi.systemOptions.enableLargeSpellbookButton.hover" : "Stor trollformelsbok", "vcmi.systemOptions.enableLargeSpellbookButton.help" : "{Stor trollformelsbok}\n\nAktiverar en större trollformelsbok som rymmer fler trollformler per sida (animeringen av sidbyte i den större trollformelsboken fungerar inte).", - "vcmi.systemOptions.audioMuteFocus.hover" : "Stänger av ljudet vid inaktivitet", - "vcmi.systemOptions.audioMuteFocus.help" : "{Stäng av ljud vid inaktivitet}\n\nStänger av ljudet i spelet vid inaktivitet. Undantag är meddelanden i spelet och ljudet för ny tur/omgång.", + "vcmi.systemOptions.audioMuteFocus.hover" : "Tyst vid inaktivitet", + "vcmi.systemOptions.audioMuteFocus.help" : "{Tyst vid inaktivitet}\n\nStänger av ljudet i spelet vid inaktivitet. Undantag är meddelanden i spelet och ljudet för ny turomgång.", "vcmi.adventureOptions.infoBarPick.hover" : "Visar textmeddelanden i infopanelen", "vcmi.adventureOptions.infoBarPick.help" : "{Infopanelsmeddelanden}\n\nNär det är möjligt kommer spelmeddelanden från besökande kartobjekt att visas i infopanelen istället för att dyka upp i ett separat fönster.", @@ -234,11 +237,11 @@ "vcmi.adventureOptions.showGrid.help" : "{Visa rutnät}\n\nVisa rutnätsöverlägget som markerar gränserna mellan äventyrskartans brickor/rutor.", "vcmi.adventureOptions.borderScroll.hover" : "Kantrullning", "vcmi.adventureOptions.borderScroll.help" : "{Kantrullning}\n\nRullar äventyrskartan när markören är angränsande till fönsterkanten. Kan inaktiveras genom att hålla ned CTRL-tangenten.", - "vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Hantering av varelser i infopanelen i nedre högra hörnet", + "vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Hantera armén i nedre högra hörnet", "vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Varelsehantering i infopanelen}\n\nTillåter omarrangering av varelser i infopanelen längst ner till höger på äventyrskartan istället för att bläddra mellan olika infopaneler.", - "vcmi.adventureOptions.leftButtonDrag.hover" : "Dra kartan med vänster musknapp", + "vcmi.adventureOptions.leftButtonDrag.hover" : "V.klicksdragning", "vcmi.adventureOptions.leftButtonDrag.help" : "{Vänsterklicksdragning}\n\nVid aktivering kan äventyrskartans kartvy dras genom att flytta musen med vänster musknapp nedtryckt.", - "vcmi.adventureOptions.rightButtonDrag.hover" : "Dra kartan med höger musknapp", + "vcmi.adventureOptions.rightButtonDrag.hover" : "H.klicksdragning", "vcmi.adventureOptions.rightButtonDrag.help" : "{Högerklicksdragning}\n\nVid aktivering kan äventyrskartans kartvy dras genom att flytta musen med höger musknapp nedtryckt.", "vcmi.adventureOptions.smoothDragging.hover" : "Mjuk kartdragning", "vcmi.adventureOptions.smoothDragging.help" : "{Mjuk kartdragning}\n\nVid aktivering så har kartdragningen en modern rullningseffekt.", @@ -268,16 +271,16 @@ "vcmi.battleOptions.animationsSpeed1.help" : "Ställ in animationshastigheten till mycket långsam.", "vcmi.battleOptions.animationsSpeed5.help" : "Ställ in animationshastigheten till mycket snabb.", "vcmi.battleOptions.animationsSpeed6.help" : "Ställ in animationshastigheten till omedelbar.", - "vcmi.battleOptions.movementHighlightOnHover.hover" : "Muspeka (hovra) för att avslöja förflyttningsräckvidd", - "vcmi.battleOptions.movementHighlightOnHover.help" : "{Muspeka för att avslöja förflyttningsräckvidd}\n\nVisar enheters potentiella förflyttningsräckvidd över slagfältet när du håller muspekaren över dem.", + "vcmi.battleOptions.movementHighlightOnHover.hover" : "Avslöja förflyttningsräckvidd", + "vcmi.battleOptions.movementHighlightOnHover.help" : "{Muspeka (hovra) för att avslöja förflyttningsräckvidd}\n\nVisar enheters potentiella förflyttningsräckvidd över slagfältet när du håller muspekaren över dem.", "vcmi.battleOptions.rangeLimitHighlightOnHover.hover": "Avslöja skyttars räckvidd", "vcmi.battleOptions.rangeLimitHighlightOnHover.help" : "{Muspeka för att avslöja skyttars räckvidd}\n\nVisar hur långt en enhets distansattack sträcker sig över slagfältet när du håller muspekaren över dem.", "vcmi.battleOptions.showStickyHeroInfoWindows.hover" : "Visa fönster med hjältars primärförmågor", "vcmi.battleOptions.showStickyHeroInfoWindows.help" : "{Visa fönster med hjältars primärförmågor}\n\nKommer alltid att visa ett fönster där du kan se dina hjältars primärförmågor (anfall, försvar, trollkonst, kunskap och trollformelpoäng).", "vcmi.battleOptions.skipBattleIntroMusic.hover" : "Hoppa över intromusik", "vcmi.battleOptions.skipBattleIntroMusic.help" : "{Hoppa över intromusik}\n\nTillåt åtgärder under intromusiken som spelas i början av varje strid.", - "vcmi.battleOptions.endWithAutocombat.hover" : "Slutför striden så fort som möjligt", - "vcmi.battleOptions.endWithAutocombat.help" : "{Slutför strid}\n\nAuto-strid spelar striden åt dig för att striden ska slutföras så fort som möjligt.", + "vcmi.battleOptions.endWithAutocombat.hover" : "Snabbstrid (AI)", + "vcmi.battleOptions.endWithAutocombat.help" : "{Slutför striden så fort som möjligt}\n\nAI för auto-strid spelar striden åt dig för att striden ska slutföras så fort som möjligt.", "vcmi.battleOptions.showQuickSpell.hover" : "Snabb åtkomst till dina trollformler", "vcmi.battleOptions.showQuickSpell.help" : "{Visa snabbtrollformels-panelen}\n\nVisar en snabbvalspanel vid sidan av stridsfönstret där du har snabb åtkomst till några av dina trollformler", @@ -365,15 +368,15 @@ "vcmi.optionsTab.turnOptions.help" : "Välj alternativ för turomgångs-timer och simultana turer", "vcmi.optionsTab.chessFieldBase.hover" : "Bas-timern", - "vcmi.optionsTab.chessFieldTurn.hover" : "Turomgångs-timern", + "vcmi.optionsTab.chessFieldTurn.hover" : "Tur-timern", "vcmi.optionsTab.chessFieldBattle.hover" : "Strids-timern", "vcmi.optionsTab.chessFieldUnit.hover" : "Enhets-timern", - "vcmi.optionsTab.chessFieldBase.help" : "Används när {Turomgångs-timern} når '0'. Ställs in en gång i början av spelet. När den når '0' avslutas den aktuella turomgången (pågående strid avslutas med förlust).", + "vcmi.optionsTab.chessFieldBase.help" : "Används när {Tur-timern} når '0'. Ställs in en gång i början av spelet. När den når '0' avslutas den aktuella turomgången (pågående strid avslutas med förlust).", "vcmi.optionsTab.chessFieldTurnAccumulate.help" : "Används utanför strid eller när {Strids-timern} tar slut. Återställs varje turomgång. Outnyttjad tid läggs till i {Bas-timern} till nästa turomgång.", "vcmi.optionsTab.chessFieldTurnDiscard.help" : "Används utanför strid eller när {Strids-timern} tar slut. Återställs varje turomgång. Outnyttjad tid går förlorad.", - "vcmi.optionsTab.chessFieldBattle.help" : "Används i strider med AI eller i PVP-strid när {Enhets-timern} tar slut. Återställs i början av varje strid.", - "vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Används när du styr din enhet i PVP-strid. Outnyttjad tid läggs till i {Strids-timern} när enheten har avslutat sin turomgång.", - "vcmi.optionsTab.chessFieldUnitDiscard.help" : "Används när du styr din enhet i PVP-strid. Återställs i början av varje enhets turomgång. Outnyttjad tid går förlorad.", + "vcmi.optionsTab.chessFieldBattle.help" : "Används i strider med AI eller i PvP-strid när {Enhets-timern} tar slut. Återställs i början av varje strid.", + "vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Används när du styr din enhet i PvP-strid. Outnyttjad tid läggs till i {Strids-timern} när enheten har avslutat sin turomgång.", + "vcmi.optionsTab.chessFieldUnitDiscard.help" : "Används när du styr din enhet i PvP-strid. Återställs i början av varje enhets turomgång. Outnyttjad tid går förlorad.", "vcmi.optionsTab.accumulate" : "Ackumulera", @@ -385,7 +388,7 @@ "vcmi.optionsTab.simturnsMax.help" : "Spela samtidigt som andra spelare under ett angivet antal dagar eller tills en tillräckligt nära kontakt inträffar med en annan spelare", "vcmi.optionsTab.simturnsAI.help" : "{Simultana AI-turomgångar}\nExperimentellt alternativ. Tillåter AI-spelare att agera samtidigt som den mänskliga spelaren när simultana turomgångar är aktiverade.", - "vcmi.optionsTab.turnTime.select" : "Turtids-förinställningar", + "vcmi.optionsTab.turnTime.select" : "Timer-inställningar för turomgångar", "vcmi.optionsTab.turnTime.unlimited" : "Obegränsat med tid", "vcmi.optionsTab.turnTime.classic.1" : "Klassisk timer: 1 minut", "vcmi.optionsTab.turnTime.classic.2" : "Klassisk timer: 2 minuter", @@ -393,22 +396,22 @@ "vcmi.optionsTab.turnTime.classic.10" : "Klassisk timer: 10 minuter", "vcmi.optionsTab.turnTime.classic.20" : "Klassisk timer: 20 minuter", "vcmi.optionsTab.turnTime.classic.30" : "Klassisk timer: 30 minuter", - "vcmi.optionsTab.turnTime.chess.20" : "Schack-timer: 20:00 + 10:00 + 02:00 + 00:00", - "vcmi.optionsTab.turnTime.chess.16" : "Schack-timer: 16:00 + 08:00 + 01:30 + 00:00", - "vcmi.optionsTab.turnTime.chess.8" : "Schack-timer: 08:00 + 04:00 + 01:00 + 00:00", - "vcmi.optionsTab.turnTime.chess.4" : "Schack-timer: 04:00 + 02:00 + 00:30 + 00:00", - "vcmi.optionsTab.turnTime.chess.2" : "Schack-timer: 02:00 + 01:00 + 00:15 + 00:00", - "vcmi.optionsTab.turnTime.chess.1" : "Schack-timer: 01:00 + 01:00 + 00:00 + 00:00", + "vcmi.optionsTab.turnTime.chess.20" : "Schack: 20:00 + 10:00 + 02:00 + 00:00", + "vcmi.optionsTab.turnTime.chess.16" : "Schack: 16:00 + 08:00 + 01:30 + 00:00", + "vcmi.optionsTab.turnTime.chess.8" : "Schack: 08:00 + 04:00 + 01:00 + 00:00", + "vcmi.optionsTab.turnTime.chess.4" : "Schack: 04:00 + 02:00 + 00:30 + 00:00", + "vcmi.optionsTab.turnTime.chess.2" : "Schack: 02:00 + 01:00 + 00:15 + 00:00", + "vcmi.optionsTab.turnTime.chess.1" : "Schack: 01:00 + 01:00 + 00:00 + 00:00", - "vcmi.optionsTab.simturns.select" : "Välj förinställning för simultana/samtidiga turer", + "vcmi.optionsTab.simturns.select" : "Simultana/samtidiga turomgångsinställningar", "vcmi.optionsTab.simturns.none" : "Inga simultana/samtidiga turer", - "vcmi.optionsTab.simturns.tillContactMax" : "Simultantur: Fram till kontakt", - "vcmi.optionsTab.simturns.tillContact1" : "Simultantur: 1 vecka, bryt vid kontakt", - "vcmi.optionsTab.simturns.tillContact2" : "Simultantur: 2 veckor, bryt vid kontakt", - "vcmi.optionsTab.simturns.tillContact4" : "Simultantur: 1 månad, bryt vid kontakt", - "vcmi.optionsTab.simturns.blocked1" : "Simultantur: 1 vecka, kontakter blockerade", - "vcmi.optionsTab.simturns.blocked2" : "Simultantur: 2 veckor, kontakter blockerade", - "vcmi.optionsTab.simturns.blocked4" : "Simultantur: 1 månad, kontakter blockerade", + "vcmi.optionsTab.simturns.tillContactMax" : "Sam-tur: Fram till kontakt", + "vcmi.optionsTab.simturns.tillContact1" : "Sam-tur: 1 vecka, bryt vid kontakt", + "vcmi.optionsTab.simturns.tillContact2" : "Sam-tur: 2 veckor, bryt vid kontakt", + "vcmi.optionsTab.simturns.tillContact4" : "Sam-tur: 1 månad, bryt vid kontakt", + "vcmi.optionsTab.simturns.blocked1" : "Sam-tur: 1 vecka, kontakter blockerade", + "vcmi.optionsTab.simturns.blocked2" : "Sam-tur: 2 veckor, kontakter blockerade", + "vcmi.optionsTab.simturns.blocked4" : "Sam-tur: 1 månad, kontakter blockerade", // Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language // Using this information, VCMI will automatically select correct plural form for every possible amount @@ -525,10 +528,10 @@ "core.bonus.AIR_IMMUNITY.description" : "Immun mot alla trollformler från skolan för luftmagi", "core.bonus.ATTACKS_ALL_ADJACENT.name" : "Attackera runtomkring", "core.bonus.ATTACKS_ALL_ADJACENT.description" : "Attackerar alla angränsande fiender", - "core.bonus.BLOCKS_RETALIATION.name" : "Ingen motattack", + "core.bonus.BLOCKS_RETALIATION.name" : "Blockera närstrids-motattack", "core.bonus.BLOCKS_RETALIATION.description" : "Fienden kan inte slå tillbaka/retaliera", - "core.bonus.BLOCKS_RANGED_RETALIATION.name" : "Ingen motattack på avstånd", - "core.bonus.BLOCKS_RANGED_RETALIATION.description" : "Fienden kan inte göra en motattack/retaliering på avstånd genom att använda en distansattack", + "core.bonus.BLOCKS_RANGED_RETALIATION.name" : "Blockera fjärrstrids-motattack", + "core.bonus.BLOCKS_RANGED_RETALIATION.description" : "Fienden kan inte retaliera på avstånd genom att använda en distansattack", "core.bonus.CATAPULT.name" : "Katapult", "core.bonus.CATAPULT.description" : "Attackerar belägringsmurar", "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name" : "Minska trollformelkostnaden (${val})", @@ -538,21 +541,21 @@ "core.bonus.CHARGE_IMMUNITY.name" : "Galoppanfalls-immunitet", "core.bonus.CHARGE_IMMUNITY.description" : "Immun mot ryttares och tornerares galopperande ridanfall", "core.bonus.DARKNESS.name" : "I skydd av mörkret", - "core.bonus.DARKNESS.description" : "Skapar ett hölje av mörker med en ${val}-rutorsradie", + "core.bonus.DARKNESS.description" : "Skapar ett mörkerhölje med en rut-radie på ${val} som gäckar dina fiender", "core.bonus.DEATH_STARE.name" : "Dödsblick (${val}%)", - "core.bonus.DEATH_STARE.description" : "Varje förbandsenhet med 'Dödsblick' har ${val}% chans att döda den översta enheten i ett fiendeförband", + "core.bonus.DEATH_STARE.description" : "Varje 'Dödsblick' har ${val}% chans att döda den översta fiendeenheten", "core.bonus.DEFENSIVE_STANCE.name" : "Försvarshållning", - "core.bonus.DEFENSIVE_STANCE.description" : "Ger ytterligare +${val} till enhetens försvarsförmåga när du väljer att försvarar dig", + "core.bonus.DEFENSIVE_STANCE.description" : "När du väljer att försvara en enhet så får den +${val} extra försvar", "core.bonus.DESTRUCTION.name" : "Förintelse", "core.bonus.DESTRUCTION.description" : "Har ${val}% chans att döda extra enheter efter attack", "core.bonus.DOUBLE_DAMAGE_CHANCE.name" : "Dödsstöt", - "core.bonus.DOUBLE_DAMAGE_CHANCE.description" : "Har ${val}% chans att ge dubbel basskada vid attack", + "core.bonus.DOUBLE_DAMAGE_CHANCE.description" : "Har ${val}% chans att utdela dubbel basskada vid attack", "core.bonus.DRAGON_NATURE.name" : "Drake", "core.bonus.DRAGON_NATURE.description" : "Varelsen har en draknatur", "core.bonus.EARTH_IMMUNITY.name" : "Jord-immunitet", "core.bonus.EARTH_IMMUNITY.description" : "Immun mot alla trollformler från skolan för jordmagi", "core.bonus.ENCHANTER.name" : "Förtrollare", - "core.bonus.ENCHANTER.description" : "Kan kasta ${subtyp.spell} på alla varje tur/omgång", + "core.bonus.ENCHANTER.description" : "Kan kasta ${subtyp.spell} på alla varje turomgång", "core.bonus.ENCHANTED.name" : "Förtrollad", "core.bonus.ENCHANTED.description" : "Påverkas av permanent ${subtype.spell}", "core.bonus.ENEMY_ATTACK_REDUCTION.name" : "Avfärda attack (${val}%)", @@ -578,15 +581,15 @@ "core.bonus.GARGOYLE.name" : "Stenfigur", "core.bonus.GARGOYLE.description" : "Kan varken upplivas eller läkas", "core.bonus.GENERAL_DAMAGE_REDUCTION.name" : "Minska skada (${val}%)", - "core.bonus.GENERAL_DAMAGE_REDUCTION.description" : "Reducerar fysisk skada från både distans- och närstridsattacker", + "core.bonus.GENERAL_DAMAGE_REDUCTION.description" : "Reducerar skadan från distans- och närstrids-attacker", "core.bonus.HATE.name" : "Hatar ${subtyp.varelse}", "core.bonus.HATE.description" : "Gör ${val}% mer skada mot ${subtyp.varelse}", "core.bonus.HEALER.name" : "Helare", "core.bonus.HEALER.description" : "Helar/läker allierade enheter", "core.bonus.HP_REGENERATION.name" : "Självläkande", - "core.bonus.HP_REGENERATION.description" : "Får tillbaka ${val} träffpoäng (hälsa) varje runda", + "core.bonus.HP_REGENERATION.description" : "Får tillbaka ${val} hälsa (träffpoäng) varje runda", "core.bonus.JOUSTING.name" : "Galopperande ridanfall", - "core.bonus.JOUSTING.description" : "Orsakar +${val}% extra skada för varje ruta som enheten förflyttas innan attack", + "core.bonus.JOUSTING.description" : "Orsakar +${val}% mer skada för varje ruta som förflyttas innan attack", "core.bonus.KING.name" : "Kung", "core.bonus.KING.description" : "Sårbar för 'Dräpar'-nivå ${val} eller högre", "core.bonus.LEVEL_SPELL_IMMUNITY.name" : "Förtrollningsimmunitet 1-${val}", @@ -594,13 +597,13 @@ "core.bonus.LIMITED_SHOOTING_RANGE.name" : "Begränsad räckvidd för skjutning", "core.bonus.LIMITED_SHOOTING_RANGE.description" : "Kan inte sikta på enheter längre bort än ${val} rutor", "core.bonus.LIFE_DRAIN.name" : "Dränerar livskraft (${val}%)", - "core.bonus.LIFE_DRAIN.description" : "Dränerar ${val}% träffpoäng (hälsa) av utdelad skada", + "core.bonus.LIFE_DRAIN.description" : "Dränerar ${val}% hälsa (träffpoäng) av utdelad skada", "core.bonus.MANA_CHANNELING.name" : "Kanalisera trollformelspoäng ${val}%", - "core.bonus.MANA_CHANNELING.description" : "Ger din hjälte ${val}% av den mängd trollformelspoäng som fienden spenderar per trollformel i strid", + "core.bonus.MANA_CHANNELING.description" : "Ger din hjälte ${val}% av fiendens spenderade trollformelspoäng i strid", "core.bonus.MANA_DRAIN.name" : "Dränera trollformelspoäng", "core.bonus.MANA_DRAIN.description" : "Dränerar ${val} trollformelspoäng varje tur", "core.bonus.MAGIC_MIRROR.name" : "Magisk spegel (${val}%)", - "core.bonus.MAGIC_MIRROR.description" : "Har ${val}% chans att reflektera (omdirigera) en offensiv trollformel på en fiendeenhet", + "core.bonus.MAGIC_MIRROR.description" : "${val}% chans att reflektera en offensiv trollformel på en fiendeenhet", "core.bonus.MAGIC_RESISTANCE.name" : "Magiskt motstånd (${val}%)", "core.bonus.MAGIC_RESISTANCE.description" : "Har en ${val}% chans att motstå en skadlig trollformel", "core.bonus.MIND_IMMUNITY.name" : "Immunitet mot sinnesförtrollningar", @@ -610,11 +613,11 @@ "core.bonus.NO_MELEE_PENALTY.name" : "Ingen närstridsbestraffning", "core.bonus.NO_MELEE_PENALTY.description" : "Varelsen har ingen närstridsbestraffning", "core.bonus.NO_MORALE.name" : "Ingen Moralpåverkan", - "core.bonus.NO_MORALE.description" : "Varelsen är immun mot moraliska effekter och har alltid neutral moral", + "core.bonus.NO_MORALE.description" : "Är immun mot moraliska effekter och har alltid neutral moral", "core.bonus.NO_WALL_PENALTY.name" : "Ingen murbestraffning", "core.bonus.NO_WALL_PENALTY.description" : "Orsakar full skada mot fiender bakom en mur", "core.bonus.NON_LIVING.name" : "Icke levande", - "core.bonus.NON_LIVING.description" : "Immunitet mot många effekter som annars bara påverkar levande och odöda varelser", + "core.bonus.NON_LIVING.description" : "Påverkas inte av vissa effekter som levande/odöda gör", "core.bonus.RANDOM_SPELLCASTER.name" : "Slumpmässig besvärjare", "core.bonus.RANDOM_SPELLCASTER.description" : "Kan kasta trollformler som väljs slumpmässigt", "core.bonus.RANGED_RETALIATION.name" : "Motattacker på avstånd", @@ -624,21 +627,21 @@ "core.bonus.REBIRTH.name" : "Återfödelse (${val}%)", "core.bonus.REBIRTH.description" : "${val}% av enheterna kommer att återuppväckas efter döden", "core.bonus.RETURN_AFTER_STRIKE.name" : "Återvänder efter närstrid", - "core.bonus.RETURN_AFTER_STRIKE.description" : "Efter att ha attackerat en fiendeenhet i närstrid återvänder enheten till rutan som den var placerad på innan den utförde sin närstridsattack", + "core.bonus.RETURN_AFTER_STRIKE.description" : "Efter närstridsattack återvänder den till sin ursprungliga ruta", "core.bonus.REVENGE.name" : "Hämnd", - "core.bonus.REVENGE.description" : "Orsakar extra skada baserat på angriparens förlorade träffpoäng (hälsa) i strid", + "core.bonus.REVENGE.description" : "Orsakar mer skada om den själv blivit skadad", "core.bonus.SHOOTER.name" : "Distans-attack", "core.bonus.SHOOTER.description" : "Varelsen kan skjuta/attackera på avstånd", "core.bonus.SHOOTS_ALL_ADJACENT.name" : "Skjuter alla i närheten", - "core.bonus.SHOOTS_ALL_ADJACENT.description" : "Denna varelses distans-attacker drabbar alla mål i ett litet område", + "core.bonus.SHOOTS_ALL_ADJACENT.description" : "Dess distans-attacker drabbar alla mål i ett litet område", "core.bonus.SOUL_STEAL.name" : "Själtjuv", - "core.bonus.SOUL_STEAL.description" : "Återuppväcker ${val} av sina egna enheter för varje dödad fiendeenhet", + "core.bonus.SOUL_STEAL.description" : "Återuppväcker ${val} av sina egna för varje dödad fiendeenhet", "core.bonus.SPELLCASTER.name" : "Besvärjare", "core.bonus.SPELLCASTER.description" : "Kan kasta ${subtype.spell}", "core.bonus.SPELL_AFTER_ATTACK.name" : "Besvärja efter attack", - "core.bonus.SPELL_AFTER_ATTACK.description" : "Har en ${val}% chans att kasta ${subtype.spell} efter att den har attackerat", + "core.bonus.SPELL_AFTER_ATTACK.description" : "Har ${val}% chans att kasta ${subtype.spell} efter anfall", "core.bonus.SPELL_BEFORE_ATTACK.name" : "Besvärja före attack", - "core.bonus.SPELL_BEFORE_ATTACK.description" : "Har en ${val}% chans att kasta ${subtype.spell} innan den attackerar", + "core.bonus.SPELL_BEFORE_ATTACK.description" : "Har ${val}% chans att kasta ${subtype.spell} före anfall", "core.bonus.SPELL_DAMAGE_REDUCTION.name" : "Trolldoms-resistens", "core.bonus.SPELL_DAMAGE_REDUCTION.description" : "Skadan från trollformler är reducet med ${val}%.", "core.bonus.SPELL_IMMUNITY.name" : "Trolldoms-immunitet", @@ -659,14 +662,14 @@ "core.bonus.TRANSMUTATION.description" : "${val}% chans att förvandla angripen enhet till en annan typ", "core.bonus.UNDEAD.name" : "Odöd", "core.bonus.UNDEAD.description" : "Varelsen är odöd", - "core.bonus.UNLIMITED_RETALIATIONS.name" : "Obegränsat antal motattacker", - "core.bonus.UNLIMITED_RETALIATIONS.description" : "Kan slå tillbaka mot ett obegränsat antal attacker varje omgång", + "core.bonus.UNLIMITED_RETALIATIONS.name" : "Slår tillbaka varje gång", + "core.bonus.UNLIMITED_RETALIATIONS.description" : "Obegränsat antal motattacker", "core.bonus.WATER_IMMUNITY.name" : "Vatten-immunitet", - "core.bonus.WATER_IMMUNITY.description" : "Immun mot alla trollformler från vattenmagi-skolan", + "core.bonus.WATER_IMMUNITY.description" : "Immun mot alla trollformler från vattenmagiskolan", "core.bonus.WIDE_BREATH.name" : "Bred dödlig andedräkt", "core.bonus.WIDE_BREATH.description" : "Bred andningsattack (flera rutor)", "core.bonus.DISINTEGRATE.name" : "Desintegrerar", - "core.bonus.DISINTEGRATE.description" : "Ingen fysisk kropp finns kvar efter att enheten blivit besegrad i strid", + "core.bonus.DISINTEGRATE.description" : "Ingen kropp lämnas kvar efter dödsögonblicket", "core.bonus.INVINCIBLE.name" : "Oövervinnerlig", "core.bonus.INVINCIBLE.description" : "Kan inte påverkas av någonting" } diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index c028b8aec..6133cdd64 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -1138,7 +1138,7 @@ void CPlayerInterface::showMapObjectSelectDialog(QueryID askID, const Component const CGTownInstance * t = dynamic_cast(cb->getObj(obj)); if(t) { - auto image = GH.renderHandler().loadImage(AnimationPath::builtin("ITPA"), t->town->clientInfo.icons[t->hasFort()][false] + 2, 0, EImageBlitMode::OPAQUE); + auto image = GH.renderHandler().loadImage(AnimationPath::builtin("ITPA"), t->getTown()->clientInfo.icons[t->hasFort()][false] + 2, 0, EImageBlitMode::OPAQUE); image->scaleTo(Point(35, 23)); images.push_back(image); } @@ -1333,6 +1333,8 @@ void CPlayerInterface::initializeHeroTownList() localState->addOwnedTown(town); } + localState->deserialize(*cb->getPlayerState(playerID)->playerLocalSettings); + if(adventureInt) adventureInt->onHeroChanged(nullptr); } diff --git a/client/ClientCommandManager.cpp b/client/ClientCommandManager.cpp index a2a7176ef..775c9b806 100644 --- a/client/ClientCommandManager.cpp +++ b/client/ClientCommandManager.cpp @@ -453,7 +453,7 @@ void ClientCommandManager::handleTellCommand(std::istringstream& singleWordBuffe if(what == "hs") { for(const CGHeroInstance* h : LOCPLINT->cb->getHeroesInfo()) - if(h->type->getIndex() == id1) + if(h->getHeroTypeID().getNum() == id1) if(const CArtifactInstance* a = h->getArt(ArtifactPosition(id2))) printCommandMessage(a->nodeName()); } diff --git a/client/HeroMovementController.cpp b/client/HeroMovementController.cpp index d402413e5..2ee599017 100644 --- a/client/HeroMovementController.cpp +++ b/client/HeroMovementController.cpp @@ -375,7 +375,7 @@ void HeroMovementController::sendMovementRequest(const CGHeroInstance * h, const { updateMovementSound(h, currNode.coord, nextNode.coord, nextNode.action); - assert(h->pos.z == nextNode.coord.z); // Z should change only if it's movement via teleporter and in this case this code shouldn't be executed at all + assert(h->anchorPos().z == nextNode.coord.z); // Z should change only if it's movement via teleporter and in this case this code shouldn't be executed at all logGlobal->trace("Requesting hero movement to %s", nextNode.coord.toString()); diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index d403120a7..91c8833b2 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -671,7 +671,7 @@ void ApplyClientNetPackVisitor::visitSetHeroesInTown(SetHeroesInTown & pack) void ApplyClientNetPackVisitor::visitHeroRecruited(HeroRecruited & pack) { CGHeroInstance *h = gs.map->heroesOnMap.back(); - if(h->getHeroType() != pack.hid) + if(h->getHeroTypeID() != pack.hid) { logNetwork->error("Something wrong with hero recruited!"); } diff --git a/client/PlayerLocalState.cpp b/client/PlayerLocalState.cpp index 541b9ff80..69e8b1b5e 100644 --- a/client/PlayerLocalState.cpp +++ b/client/PlayerLocalState.cpp @@ -11,6 +11,7 @@ #include "PlayerLocalState.h" #include "../CCallback.h" +#include "../lib/json/JsonNode.h" #include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/mapObjects/CGTownInstance.h" #include "../lib/pathfinder/CGPathNode.h" @@ -23,34 +24,20 @@ PlayerLocalState::PlayerLocalState(CPlayerInterface & owner) { } -void PlayerLocalState::saveHeroPaths(std::map & pathsMap) +const PlayerSpellbookSetting & PlayerLocalState::getSpellbookSettings() { - for(auto & p : paths) - { - if(p.second.nodes.size()) - pathsMap[p.first] = p.second.endPos(); - else - logGlobal->debug("%s has assigned an empty path! Ignoring it...", p.first->getNameTranslated()); - } + return spellbookSettings; } -void PlayerLocalState::loadHeroPaths(std::map & pathsMap) +void PlayerLocalState::setSpellbookSettings(const PlayerSpellbookSetting & newSettings) { - if(owner.cb) - { - for(auto & p : pathsMap) - { - CGPath path; - owner.cb->getPathsInfo(p.first)->getPath(path, p.second); - paths[p.first] = path; - logGlobal->trace("Restored path for hero %s leading to %s with %d nodes", p.first->nodeName(), p.second.toString(), path.nodes.size()); - } - } + spellbookSettings = newSettings; } void PlayerLocalState::setPath(const CGHeroInstance * h, const CGPath & path) { paths[h] = path; + syncronizeState(); } const CGPath & PlayerLocalState::getPath(const CGHeroInstance * h) const @@ -70,6 +57,7 @@ bool PlayerLocalState::setPath(const CGHeroInstance * h, const int3 & destinatio if(!owner.cb->getPathsInfo(h)->getPath(path, destination)) { paths.erase(h); //invalidate previously possible path if selected (before other hero blocked only path / fly spell expired) + syncronizeState(); return false; } @@ -93,6 +81,7 @@ void PlayerLocalState::erasePath(const CGHeroInstance * h) { paths.erase(h); adventureInt->onHeroChanged(h); + syncronizeState(); } void PlayerLocalState::verifyPath(const CGHeroInstance * h) @@ -170,6 +159,7 @@ void PlayerLocalState::setSelection(const CArmedInstance * selection) if (adventureInt && selection) adventureInt->onSelectionChanged(selection); + syncronizeState(); } bool PlayerLocalState::isHeroSleeping(const CGHeroInstance * hero) const @@ -184,6 +174,7 @@ void PlayerLocalState::setHeroAsleep(const CGHeroInstance * hero) assert(!vstd::contains(sleepingHeroes, hero)); sleepingHeroes.push_back(hero); + syncronizeState(); } void PlayerLocalState::setHeroAwaken(const CGHeroInstance * hero) @@ -193,6 +184,7 @@ void PlayerLocalState::setHeroAwaken(const CGHeroInstance * hero) assert(vstd::contains(sleepingHeroes, hero)); vstd::erase(sleepingHeroes, hero); + syncronizeState(); } const std::vector & PlayerLocalState::getWanderingHeroes() @@ -215,6 +207,8 @@ void PlayerLocalState::addWanderingHero(const CGHeroInstance * hero) if (currentSelection == nullptr) setSelection(hero); + + syncronizeState(); } void PlayerLocalState::removeWanderingHero(const CGHeroInstance * hero) @@ -236,6 +230,8 @@ void PlayerLocalState::removeWanderingHero(const CGHeroInstance * hero) if (currentSelection == nullptr && !ownedTowns.empty()) setSelection(ownedTowns.front()); + + syncronizeState(); } void PlayerLocalState::swapWanderingHero(size_t pos1, size_t pos2) @@ -244,6 +240,8 @@ void PlayerLocalState::swapWanderingHero(size_t pos1, size_t pos2) std::swap(wanderingHeroes.at(pos1), wanderingHeroes.at(pos2)); adventureInt->onHeroOrderChanged(); + + syncronizeState(); } const std::vector & PlayerLocalState::getOwnedTowns() @@ -266,6 +264,8 @@ void PlayerLocalState::addOwnedTown(const CGTownInstance * town) if (currentSelection == nullptr) setSelection(town); + + syncronizeState(); } void PlayerLocalState::removeOwnedTown(const CGTownInstance * town) @@ -282,6 +282,8 @@ void PlayerLocalState::removeOwnedTown(const CGTownInstance * town) if (currentSelection == nullptr && !ownedTowns.empty()) setSelection(ownedTowns.front()); + + syncronizeState(); } void PlayerLocalState::swapOwnedTowns(size_t pos1, size_t pos2) @@ -289,5 +291,119 @@ void PlayerLocalState::swapOwnedTowns(size_t pos1, size_t pos2) assert(ownedTowns[pos1] && ownedTowns[pos2]); std::swap(ownedTowns.at(pos1), ownedTowns.at(pos2)); + syncronizeState(); + adventureInt->onTownOrderChanged(); } + +void PlayerLocalState::syncronizeState() +{ + JsonNode data; + serialize(data); + owner.cb->saveLocalState(data); +} + +void PlayerLocalState::serialize(JsonNode & dest) const +{ + dest.clear(); + + for (auto const * town : ownedTowns) + { + JsonNode record; + record["id"].Integer() = town->id; + dest["towns"].Vector().push_back(record); + } + + for (auto const * hero : wanderingHeroes) + { + JsonNode record; + record["id"].Integer() = hero->id; + if (vstd::contains(sleepingHeroes, hero)) + record["sleeping"].Bool() = true; + + if (paths.count(hero)) + { + record["path"]["x"].Integer() = paths.at(hero).lastNode().coord.x; + record["path"]["y"].Integer() = paths.at(hero).lastNode().coord.y; + record["path"]["z"].Integer() = paths.at(hero).lastNode().coord.z; + } + dest["heroes"].Vector().push_back(record); + } + dest["spellbook"]["pageBattle"].Integer() = spellbookSettings.spellbookLastPageBattle; + dest["spellbook"]["pageAdvmap"].Integer() = spellbookSettings.spellbookLastPageAdvmap; + dest["spellbook"]["tabBattle"].Integer() = spellbookSettings.spellbookLastTabBattle; + dest["spellbook"]["tabAdvmap"].Integer() = spellbookSettings.spellbookLastTabAdvmap; + + dest["currentSelection"].Integer() = currentSelection->id; +} + +void PlayerLocalState::deserialize(const JsonNode & source) +{ + // this method must be called after player state has been initialized + assert(currentSelection != nullptr); + assert(!ownedTowns.empty() || wanderingHeroes.empty()); + + auto oldHeroes = wanderingHeroes; + auto oldTowns = ownedTowns; + + paths.clear(); + sleepingHeroes.clear(); + wanderingHeroes.clear(); + ownedTowns.clear(); + + for (auto const & town : source["towns"].Vector()) + { + ObjectInstanceID objID(town["id"].Integer()); + const CGTownInstance * townPtr = owner.cb->getTown(objID); + + if (!townPtr) + continue; + + if (!vstd::contains(oldTowns, townPtr)) + continue; + + ownedTowns.push_back(townPtr); + vstd::erase(oldTowns, townPtr); + } + + for (auto const & hero : source["heroes"].Vector()) + { + ObjectInstanceID objID(hero["id"].Integer()); + const CGHeroInstance * heroPtr = owner.cb->getHero(objID); + + if (!heroPtr) + continue; + + if (!vstd::contains(oldHeroes, heroPtr)) + continue; + + wanderingHeroes.push_back(heroPtr); + vstd::erase(oldHeroes, heroPtr); + + if (hero["sleeping"].Bool()) + sleepingHeroes.push_back(heroPtr); + + if (hero["path"]["x"].isNumber() && hero["path"]["y"].isNumber() && hero["path"]["z"].isNumber()) + { + int3 pathTarget(hero["path"]["x"].Integer(), hero["path"]["y"].Integer(), hero["path"]["z"].Integer()); + setPath(heroPtr, pathTarget); + } + } + + spellbookSettings.spellbookLastPageBattle = source["spellbook"]["pageBattle"].Integer(); + spellbookSettings.spellbookLastPageAdvmap = source["spellbook"]["pageAdvmap"].Integer(); + spellbookSettings.spellbookLastTabBattle = source["spellbook"]["tabBattle"].Integer(); + spellbookSettings.spellbookLastTabAdvmap = source["spellbook"]["tabAdvmap"].Integer(); + + // append any owned heroes / towns that were not present in loaded state + wanderingHeroes.insert(wanderingHeroes.end(), oldHeroes.begin(), oldHeroes.end()); + ownedTowns.insert(ownedTowns.end(), oldTowns.begin(), oldTowns.end()); + +//FIXME: broken, anything that is selected in here will be overwritten on NewTurn pack +// ObjectInstanceID selectedObjectID(source["currentSelection"].Integer()); +// const CGObjectInstance * objectPtr = owner.cb->getObjInstance(selectedObjectID); +// const CArmedInstance * armyPtr = dynamic_cast(objectPtr); +// +// if (armyPtr) +// setSelection(armyPtr); +} diff --git a/client/PlayerLocalState.h b/client/PlayerLocalState.h index c6029ff1b..0cfda1f7a 100644 --- a/client/PlayerLocalState.h +++ b/client/PlayerLocalState.h @@ -14,6 +14,7 @@ VCMI_LIB_NAMESPACE_BEGIN class CGHeroInstance; class CGTownInstance; class CArmedInstance; +class JsonNode; struct CGPath; class int3; @@ -21,6 +22,15 @@ VCMI_LIB_NAMESPACE_END class CPlayerInterface; +struct PlayerSpellbookSetting +{ + //on which page we left spellbook + int spellbookLastPageBattle = 0; + int spellbookLastPageAdvmap = 0; + int spellbookLastTabBattle = 4; + int spellbookLastTabAdvmap = 4; +}; + /// Class that contains potentially serializeable state of a local player class PlayerLocalState { @@ -34,18 +44,10 @@ class PlayerLocalState std::vector wanderingHeroes; //our heroes on the adventure map (not the garrisoned ones) std::vector ownedTowns; //our towns on the adventure map - void saveHeroPaths(std::map & paths); - void loadHeroPaths(std::map & paths); + PlayerSpellbookSetting spellbookSettings; + void syncronizeState(); public: - struct SpellbookLastSetting - { - //on which page we left spellbook - int spellbookLastPageBattle = 0; - int spellbookLastPageAdvmap = 0; - int spellbookLastTabBattle = 4; - int spellbookLastTabAdvmap = 4; - } spellbookSettings; explicit PlayerLocalState(CPlayerInterface & owner); @@ -53,6 +55,9 @@ public: void setHeroAsleep(const CGHeroInstance * hero); void setHeroAwaken(const CGHeroInstance * hero); + const PlayerSpellbookSetting & getSpellbookSettings(); + void setSpellbookSettings(const PlayerSpellbookSetting & newSettings); + const std::vector & getOwnedTowns(); const CGTownInstance * getOwnedTown(size_t index); void addOwnedTown(const CGTownInstance * hero); @@ -81,6 +86,9 @@ public: const CGTownInstance * getCurrentTown() const; const CArmedInstance * getCurrentArmy() const; + void serialize(JsonNode & dest) const; + void deserialize(const JsonNode & source); + /// Changes currently selected object void setSelection(const CArmedInstance *sel); }; diff --git a/client/adventureMap/CList.cpp b/client/adventureMap/CList.cpp index 5c46a510a..e730224b4 100644 --- a/client/adventureMap/CList.cpp +++ b/client/adventureMap/CList.cpp @@ -432,7 +432,7 @@ std::shared_ptr CTownList::CTownItem::genSelection() void CTownList::CTownItem::update() { - size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->built >= LOCPLINT->cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)]; + size_t iconIndex = town->getTown()->clientInfo.icons[town->hasFort()][town->built >= LOCPLINT->cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)]; picture->setFrame(iconIndex + 2); redraw(); diff --git a/client/adventureMap/MapAudioPlayer.cpp b/client/adventureMap/MapAudioPlayer.cpp index bac234103..356b5577b 100644 --- a/client/adventureMap/MapAudioPlayer.cpp +++ b/client/adventureMap/MapAudioPlayer.cpp @@ -81,9 +81,9 @@ void MapAudioPlayer::addObject(const CGObjectInstance * obj) { for(int fy = 0; fy < obj->getHeight(); ++fy) { - int3 currTile(obj->pos.x - fx, obj->pos.y - fy, obj->pos.z); + int3 currTile(obj->anchorPos().x - fx, obj->anchorPos().y - fy, obj->anchorPos().z); - if(LOCPLINT->cb->isInTheMap(currTile) && obj->coveringAt(currTile.x, currTile.y)) + if(LOCPLINT->cb->isInTheMap(currTile) && obj->coveringAt(currTile)) objects[currTile.z][currTile.x][currTile.y].push_back(obj->id); } } @@ -108,7 +108,7 @@ void MapAudioPlayer::addObject(const CGObjectInstance * obj) for(const auto & tile : tiles) { - int3 currTile = obj->pos + tile; + int3 currTile = obj->anchorPos() + tile; if(LOCPLINT->cb->isInTheMap(currTile)) objects[currTile.z][currTile.x][currTile.y].push_back(obj->id); diff --git a/client/battle/BattleInterfaceClasses.cpp b/client/battle/BattleInterfaceClasses.cpp index 919118407..5cf6aa340 100644 --- a/client/battle/BattleInterfaceClasses.cpp +++ b/client/battle/BattleInterfaceClasses.cpp @@ -389,13 +389,13 @@ BattleHero::BattleHero(const BattleInterface & owner, const CGHeroInstance * her { AnimationPath animationPath; - if(!hero->type->battleImage.empty()) - animationPath = hero->type->battleImage; + if(!hero->getHeroType()->battleImage.empty()) + animationPath = hero->getHeroType()->battleImage; else if(hero->gender == EHeroGender::FEMALE) - animationPath = hero->type->heroClass->imageBattleFemale; + animationPath = hero->getHeroClass()->imageBattleFemale; else - animationPath = hero->type->heroClass->imageBattleMale; + animationPath = hero->getHeroClass()->imageBattleMale; animation = GH.renderHandler().loadAnimation(animationPath, EImageBlitMode::ALPHA); @@ -1027,7 +1027,7 @@ void StackQueue::update() int32_t StackQueue::getSiegeShooterIconID() { - return owner.siegeController->getSiegedTown()->town->faction->getIndex(); + return owner.siegeController->getSiegedTown()->getFactionID().getNum(); } std::optional StackQueue::getHoveredUnitIdIfAny() const diff --git a/client/battle/BattleSiegeController.cpp b/client/battle/BattleSiegeController.cpp index f64bc8ac4..2f3c4df5e 100644 --- a/client/battle/BattleSiegeController.cpp +++ b/client/battle/BattleSiegeController.cpp @@ -58,14 +58,14 @@ ImagePath BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisual }; }; - const std::string & prefix = town->town->clientInfo.siegePrefix; + const std::string & prefix = town->getTown()->clientInfo.siegePrefix; std::string addit = std::to_string(getImageIndex()); switch(what) { case EWallVisual::BACKGROUND_WALL: { - auto faction = town->town->faction->getIndex(); + auto faction = town->getFactionID(); if (faction == ETownType::RAMPART || faction == ETownType::NECROPOLIS || faction == ETownType::DUNGEON || faction == ETownType::STRONGHOLD) return ImagePath::builtinTODO(prefix + "TPW1.BMP"); @@ -111,7 +111,7 @@ ImagePath BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisual void BattleSiegeController::showWallPiece(Canvas & canvas, EWallVisual::EWallVisual what) { - auto & ci = town->town->clientInfo; + auto & ci = town->getTown()->clientInfo; auto const & pos = ci.siegePositions[what]; if ( wallPieceImages[what] && pos.isValid()) @@ -120,7 +120,7 @@ void BattleSiegeController::showWallPiece(Canvas & canvas, EWallVisual::EWallVis ImagePath BattleSiegeController::getBattleBackgroundName() const { - const std::string & prefix = town->town->clientInfo.siegePrefix; + const std::string & prefix = town->getTown()->clientInfo.siegePrefix; return ImagePath::builtinTODO(prefix + "BACK.BMP"); } @@ -130,8 +130,8 @@ bool BattleSiegeController::getWallPieceExistence(EWallVisual::EWallVisual what) switch (what) { - case EWallVisual::MOAT: return fortifications.hasMoat && town->town->clientInfo.siegePositions.at(EWallVisual::MOAT).isValid(); - case EWallVisual::MOAT_BANK: return fortifications.hasMoat && town->town->clientInfo.siegePositions.at(EWallVisual::MOAT_BANK).isValid(); + case EWallVisual::MOAT: return fortifications.hasMoat && town->getTown()->clientInfo.siegePositions.at(EWallVisual::MOAT).isValid(); + case EWallVisual::MOAT_BANK: return fortifications.hasMoat && town->getTown()->clientInfo.siegePositions.at(EWallVisual::MOAT_BANK).isValid(); case EWallVisual::KEEP_BATTLEMENT: return fortifications.citadelHealth > 0 && owner.getBattle()->battleGetWallState(EWallPart::KEEP) != EWallState::DESTROYED; case EWallVisual::UPPER_BATTLEMENT: return fortifications.upperTowerHealth > 0 && owner.getBattle()->battleGetWallState(EWallPart::UPPER_TOWER) != EWallState::DESTROYED; case EWallVisual::BOTTOM_BATTLEMENT: return fortifications.lowerTowerHealth > 0 && owner.getBattle()->battleGetWallState(EWallPart::BOTTOM_TOWER) != EWallState::DESTROYED; @@ -218,8 +218,8 @@ Point BattleSiegeController::getTurretCreaturePosition( BattleHex position ) con if (posID != 0) { return { - town->town->clientInfo.siegePositions[posID].x, - town->town->clientInfo.siegePositions[posID].y + town->getTown()->clientInfo.siegePositions[posID].x, + town->getTown()->clientInfo.siegePositions[posID].y }; } diff --git a/client/gui/CGuiHandler.cpp b/client/gui/CGuiHandler.cpp index ebacdc1b4..6608ca44d 100644 --- a/client/gui/CGuiHandler.cpp +++ b/client/gui/CGuiHandler.cpp @@ -250,8 +250,8 @@ void CGuiHandler::setStatusbar(std::shared_ptr newStatusBar) void CGuiHandler::onScreenResize(bool resolutionChanged) { if(resolutionChanged) - { screenHandler().onScreenResize(); - } + windows().onScreenResize(); + CCS->curh->onScreenResize(); } diff --git a/client/gui/CursorHandler.cpp b/client/gui/CursorHandler.cpp index eaa8cfd98..fbc5922f8 100644 --- a/client/gui/CursorHandler.cpp +++ b/client/gui/CursorHandler.cpp @@ -312,3 +312,8 @@ void CursorHandler::changeCursor(Cursor::ShowType newShowType) break; } } + +void CursorHandler::onScreenResize() +{ + cursor->setImage(getCurrentImage(), getPivotOffset()); +} diff --git a/client/gui/CursorHandler.h b/client/gui/CursorHandler.h index 539c577bd..acaccaaa8 100644 --- a/client/gui/CursorHandler.h +++ b/client/gui/CursorHandler.h @@ -182,6 +182,7 @@ public: void hide(); void show(); + void onScreenResize(); /// change cursor's positions to (x, y) void cursorMove(const int & x, const int & y); diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index 75b8181ee..71604a58d 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -480,11 +480,11 @@ void SelectionTab::filter(int size, bool selectFirst) if((elem->mapHeader && (!size || elem->mapHeader->width == size)) || tabType == ESelectionScreen::campaignList) { if(showRandom) - curFolder = "RANDOMMAPS/"; + curFolder = "RandomMaps/"; auto [folderName, baseFolder, parentExists, fileInFolder] = checkSubfolder(elem->originalFileURI); - if((showRandom && baseFolder != "RANDOMMAPS") || (!showRandom && baseFolder == "RANDOMMAPS")) + if((showRandom && baseFolder != "RandomMaps") || (!showRandom && baseFolder == "RandomMaps")) continue; if(parentExists && !showRandom) @@ -715,7 +715,7 @@ void SelectionTab::selectFileName(std::string fname) selectAbs(-1); if(tabType == ESelectionScreen::saveGame && inputName->getText().empty()) - inputName->setText("NEWGAME"); + inputName->setText(CGI->generaltexth->translate("core.genrltxt.11")); } void SelectionTab::selectNewestFile() @@ -808,7 +808,8 @@ void SelectionTab::parseMaps(const std::unordered_set & files) try { auto mapInfo = std::make_shared(); - mapInfo->mapInit(file.getName()); + mapInfo->mapInit(file.getOriginalName()); + mapInfo->name = mapInfo->getNameForList(); if (isMapSupported(*mapInfo)) allItems.push_back(mapInfo); @@ -873,7 +874,7 @@ void SelectionTab::parseCampaigns(const std::unordered_set & files { auto info = std::make_shared(); //allItems[i].date = std::asctime(std::localtime(&files[i].date)); - info->fileURI = file.getName(); + info->fileURI = file.getOriginalName(); info->campaignInit(); if(info->campaign) allItems.push_back(info); @@ -988,6 +989,6 @@ void SelectionTab::ListItem::updateItem(std::shared_ptr info, bool iconLossCondition->setFrame(info->mapHeader->defeatIconIndex, 0); labelName->setMaxWidth(185); } - labelName->setText(info->getNameForList()); + labelName->setText(info->name); labelName->setColor(color); } diff --git a/client/lobby/SelectionTab.h b/client/lobby/SelectionTab.h index 67e39047c..86c625a58 100644 --- a/client/lobby/SelectionTab.h +++ b/client/lobby/SelectionTab.h @@ -33,6 +33,7 @@ public: ElementInfo() : CMapInfo() { } ~ElementInfo() { } std::string folderName = ""; + std::string name = ""; bool isFolder = false; }; diff --git a/client/mapView/MapRenderer.cpp b/client/mapView/MapRenderer.cpp index 6688812a6..a274fe5ec 100644 --- a/client/mapView/MapRenderer.cpp +++ b/client/mapView/MapRenderer.cpp @@ -591,8 +591,8 @@ void MapRendererOverlay::renderTile(IMapRendererContext & context, Canvas & targ if(context.objectTransparency(objectID, coordinates) > 0 && !context.isActiveHero(object)) { - visitable |= object->visitableAt(coordinates.x, coordinates.y); - blocking |= object->blockingAt(coordinates.x, coordinates.y); + visitable |= object->visitableAt(coordinates); + blocking |= object->blockingAt(coordinates); } } diff --git a/client/mapView/MapRendererContext.cpp b/client/mapView/MapRendererContext.cpp index 239541ae5..291322f31 100644 --- a/client/mapView/MapRendererContext.cpp +++ b/client/mapView/MapRendererContext.cpp @@ -120,7 +120,7 @@ size_t MapRendererBaseContext::objectGroupIndex(ObjectInstanceID objectID) const Point MapRendererBaseContext::objectImageOffset(ObjectInstanceID objectID, const int3 & coordinates) const { const CGObjectInstance * object = getObject(objectID); - int3 offsetTiles(object->getPosition() - coordinates); + int3 offsetTiles(object->anchorPos() - coordinates); return Point(offsetTiles) * Point(32, 32); } @@ -498,7 +498,7 @@ size_t MapRendererWorldViewContext::overlayImageIndex(const int3 & coordinates) { const auto * object = getObject(objectID); - if(!object->visitableAt(coordinates.x, coordinates.y)) + if(!object->visitableAt(coordinates)) continue; ObjectPosInfo info(object); diff --git a/client/mapView/MapRendererContextState.cpp b/client/mapView/MapRendererContextState.cpp index aa1a6ab0a..bb2632ce3 100644 --- a/client/mapView/MapRendererContextState.cpp +++ b/client/mapView/MapRendererContextState.cpp @@ -49,9 +49,9 @@ void MapRendererContextState::addObject(const CGObjectInstance * obj) { for(int fy = 0; fy < obj->getHeight(); ++fy) { - int3 currTile(obj->pos.x - fx, obj->pos.y - fy, obj->pos.z); + int3 currTile(obj->anchorPos().x - fx, obj->anchorPos().y - fy, obj->anchorPos().z); - if(LOCPLINT->cb->isInTheMap(currTile) && obj->coveringAt(currTile.x, currTile.y)) + if(LOCPLINT->cb->isInTheMap(currTile) && obj->coveringAt(currTile)) { auto & container = objects[currTile.z][currTile.x][currTile.y]; @@ -73,7 +73,7 @@ void MapRendererContextState::addMovingObject(const CGObjectInstance * object, c { for(int y = yFrom; y <= yDest; ++y) { - int3 currTile(x, y, object->pos.z); + int3 currTile(x, y, object->anchorPos().z); if(LOCPLINT->cb->isInTheMap(currTile)) { diff --git a/client/mapView/MapViewController.cpp b/client/mapView/MapViewController.cpp index ec33d6f5b..fa653725b 100644 --- a/client/mapView/MapViewController.cpp +++ b/client/mapView/MapViewController.cpp @@ -317,7 +317,7 @@ bool MapViewController::isEventVisible(const CGObjectInstance * obj, const Playe if(obj->isVisitable()) return context->isVisible(obj->visitablePos()); else - return context->isVisible(obj->pos); + return context->isVisible(obj->anchorPos()); } bool MapViewController::isEventVisible(const CGHeroInstance * obj, const int3 & from, const int3 & dest) diff --git a/client/mapView/mapHandler.cpp b/client/mapView/mapHandler.cpp index bd19e421a..ede4b3ea5 100644 --- a/client/mapView/mapHandler.cpp +++ b/client/mapView/mapHandler.cpp @@ -59,7 +59,7 @@ std::string CMapHandler::getTerrainDescr(const int3 & pos, bool rightClick) cons for(const auto & object : map->objects) { - if(object && object->coveringAt(pos.x, pos.y) && object->pos.z == pos.z && object->isTile2Terrain()) + if(object && object->coveringAt(pos) && object->isTile2Terrain()) { result = object->getObjectName(); break; @@ -103,15 +103,15 @@ bool CMapHandler::compareObjectBlitOrder(const CGObjectInstance * a, const CGObj for(const auto & aOffset : a->getBlockedOffsets()) { - int3 testTarget = a->pos + aOffset + int3(0, 1, 0); - if(b->blockingAt(testTarget.x, testTarget.y)) + int3 testTarget = a->anchorPos() + aOffset + int3(0, 1, 0); + if(b->blockingAt(testTarget)) bBlocksA += 1; } for(const auto & bOffset : b->getBlockedOffsets()) { - int3 testTarget = b->pos + bOffset + int3(0, 1, 0); - if(a->blockingAt(testTarget.x, testTarget.y)) + int3 testTarget = b->anchorPos() + bOffset + int3(0, 1, 0); + if(a->blockingAt(testTarget)) aBlocksB += 1; } @@ -126,8 +126,8 @@ bool CMapHandler::compareObjectBlitOrder(const CGObjectInstance * a, const CGObj return aBlocksB < bBlocksA; // object that don't have clear priority via tile blocking will appear based on their row - if(a->pos.y != b->pos.y) - return a->pos.y < b->pos.y; + if(a->anchorPos().y != b->anchorPos().y) + return a->anchorPos().y < b->anchorPos().y; // heroes should appear on top of objects on the same tile if(b->ID==Obj::HERO && a->ID!=Obj::HERO) diff --git a/client/media/CVideoHandler.cpp b/client/media/CVideoHandler.cpp index 9428c7428..b13c2dfc0 100644 --- a/client/media/CVideoHandler.cpp +++ b/client/media/CVideoHandler.cpp @@ -608,9 +608,8 @@ std::pair, si64> CAudioInstance::extractAudio(const Vide bool CVideoPlayer::openAndPlayVideoImpl(const VideoPath & name, const Point & position, bool useOverlay, bool stopOnKey) { CVideoInstance instance; - CAudioInstance audio; - auto extractedAudio = audio.extractAudio(name); + auto extractedAudio = getAudio(name); int audioHandle = CCS->soundh->playSound(extractedAudio); if (!instance.openInput(name)) @@ -684,6 +683,15 @@ std::unique_ptr CVideoPlayer::open(const VideoPath & name, float std::pair, si64> CVideoPlayer::getAudio(const VideoPath & videoToOpen) { + AudioPath audioPath = videoToOpen.toType(); + AudioPath audioPathVideoDir = audioPath.addPrefix("VIDEO/"); + + if(CResourceHandler::get()->existsResource(audioPath)) + return CResourceHandler::get()->load(audioPath)->readAll(); + + if(CResourceHandler::get()->existsResource(audioPathVideoDir)) + return CResourceHandler::get()->load(audioPathVideoDir)->readAll(); + CAudioInstance audio; return audio.extractAudio(videoToOpen); } diff --git a/client/render/IScreenHandler.h b/client/render/IScreenHandler.h index 32abf7711..f3b6d5eee 100644 --- a/client/render/IScreenHandler.h +++ b/client/render/IScreenHandler.h @@ -44,6 +44,8 @@ public: /// Dimensions of logical output. Can be different if scaling is used virtual Point getLogicalResolution() const = 0; + virtual int getInterfaceScalingPercentage() const = 0; + virtual int getScalingFactor() const = 0; /// Window has focus diff --git a/client/renderSDL/CursorHardware.cpp b/client/renderSDL/CursorHardware.cpp index cbcfaeac2..7c9daea4d 100644 --- a/client/renderSDL/CursorHardware.cpp +++ b/client/renderSDL/CursorHardware.cpp @@ -11,11 +11,14 @@ #include "StdInc.h" #include "CursorHardware.h" +#include "SDL_Extensions.h" + #include "../gui/CGuiHandler.h" #include "../render/IScreenHandler.h" #include "../render/Colors.h" #include "../render/IImage.h" -#include "SDL_Extensions.h" + +#include "../../lib/CConfigHandler.h" #include #include @@ -45,19 +48,28 @@ void CursorHardware::setVisible(bool on) void CursorHardware::setImage(std::shared_ptr image, const Point & pivotOffset) { - auto cursorSurface = CSDL_Ext::newSurface(image->dimensions() * GH.screenHandler().getScalingFactor()); + int videoScalingSettings = GH.screenHandler().getInterfaceScalingPercentage(); + float cursorScalingSettings = settings["video"]["cursorScalingFactor"].Float(); + int cursorScalingPercent = videoScalingSettings * cursorScalingSettings; + Point cursorDimensions = image->dimensions() * GH.screenHandler().getScalingFactor(); + Point cursorDimensionsScaled = image->dimensions() * cursorScalingPercent / 100; + Point pivotOffsetScaled = pivotOffset * cursorScalingPercent / 100 / GH.screenHandler().getScalingFactor(); + + auto cursorSurface = CSDL_Ext::newSurface(cursorDimensions); CSDL_Ext::fillSurface(cursorSurface, CSDL_Ext::toSDL(Colors::TRANSPARENCY)); image->draw(cursorSurface, Point(0,0)); + auto cursorSurfaceScaled = CSDL_Ext::scaleSurface(cursorSurface, cursorDimensionsScaled.x, cursorDimensionsScaled.y ); auto oldCursor = cursor; - cursor = SDL_CreateColorCursor(cursorSurface, pivotOffset.x, pivotOffset.y); + cursor = SDL_CreateColorCursor(cursorSurfaceScaled, pivotOffsetScaled.x, pivotOffsetScaled.y); if (!cursor) logGlobal->error("Failed to set cursor! SDL says %s", SDL_GetError()); SDL_FreeSurface(cursorSurface); + SDL_FreeSurface(cursorSurfaceScaled); GH.dispatchMainThread([this, oldCursor](){ SDL_SetCursor(cursor); diff --git a/client/renderSDL/ScreenHandler.cpp b/client/renderSDL/ScreenHandler.cpp index c8d99e6fa..7614335e8 100644 --- a/client/renderSDL/ScreenHandler.cpp +++ b/client/renderSDL/ScreenHandler.cpp @@ -84,19 +84,39 @@ Rect ScreenHandler::convertLogicalPointsToWindow(const Rect & input) const return result; } +int ScreenHandler::getInterfaceScalingPercentage() const +{ + auto [minimalScaling, maximalScaling] = getSupportedScalingRange(); + + int userScaling = settings["video"]["resolution"]["scaling"].Integer(); + + if (userScaling == 0) // autodetection + { +#ifdef VCMI_MOBILE + // for mobiles - stay at maximum scaling unless we have large screen + // might be better to check screen DPI / physical dimensions, but way more complex, and may result in different edge cases, e.g. chromebooks / tv's + int preferredMinimalScaling = 200; +#else + // for PC - avoid downscaling if possible + int preferredMinimalScaling = 100; +#endif + // prefer a little below maximum - to give space for extended UI + int preferredMaximalScaling = maximalScaling * 10 / 12; + userScaling = std::max(std::min(maximalScaling, preferredMinimalScaling), preferredMaximalScaling); + } + + int scaling = std::clamp(userScaling, minimalScaling, maximalScaling); + return scaling; +} + Point ScreenHandler::getPreferredLogicalResolution() const { Point renderResolution = getRenderResolution(); double reservedAreaWidth = settings["video"]["reservedWidth"].Float(); + + int scaling = getInterfaceScalingPercentage(); Point availableResolution = Point(renderResolution.x * (1 - reservedAreaWidth), renderResolution.y); - - auto [minimalScaling, maximalScaling] = getSupportedScalingRange(); - - int userScaling = settings["video"]["resolution"]["scaling"].Integer(); - int scaling = std::clamp(userScaling, minimalScaling, maximalScaling); - Point logicalResolution = availableResolution * 100.0 / scaling; - return logicalResolution; } @@ -335,25 +355,22 @@ EUpscalingFilter ScreenHandler::loadUpscalingFilter() const if (filter != EUpscalingFilter::AUTO) return filter; - // for now - always fallback to no filter - return EUpscalingFilter::NONE; - // else - autoselect -// Point outputResolution = getRenderResolution(); -// Point logicalResolution = getPreferredLogicalResolution(); -// -// float scaleX = static_cast(outputResolution.x) / logicalResolution.x; -// float scaleY = static_cast(outputResolution.x) / logicalResolution.x; -// float scaling = std::min(scaleX, scaleY); -// -// if (scaling <= 1.0f) -// return EUpscalingFilter::NONE; -// if (scaling <= 2.0f) -// return EUpscalingFilter::XBRZ_2; -// if (scaling <= 3.0f) -// return EUpscalingFilter::XBRZ_3; -// -// return EUpscalingFilter::XBRZ_4; + Point outputResolution = getRenderResolution(); + Point logicalResolution = getPreferredLogicalResolution(); + + float scaleX = static_cast(outputResolution.x) / logicalResolution.x; + float scaleY = static_cast(outputResolution.x) / logicalResolution.x; + float scaling = std::min(scaleX, scaleY); + + if (scaling <= 1.001f) + return EUpscalingFilter::NONE; // running at original resolution or even lower than that - no need for xbrz + if (scaling <= 2.001f) + return EUpscalingFilter::XBRZ_2; // resolutions below 1200p (including 1080p / FullHD) + if (scaling <= 3.001f) + return EUpscalingFilter::XBRZ_3; // resolutions below 2400p (including 1440p and 2160p / 4K) + + return EUpscalingFilter::XBRZ_4; // Only for massive displays, e.g. 8K } void ScreenHandler::selectUpscalingFilter() diff --git a/client/renderSDL/ScreenHandler.h b/client/renderSDL/ScreenHandler.h index e15958b55..6a9026d7b 100644 --- a/client/renderSDL/ScreenHandler.h +++ b/client/renderSDL/ScreenHandler.h @@ -112,6 +112,8 @@ public: int getScalingFactor() const final; + int getInterfaceScalingPercentage() const final; + std::vector getSupportedResolutions() const final; std::vector getSupportedResolutions(int displayIndex) const; std::tuple getSupportedScalingRange() const final; diff --git a/client/widgets/MiscWidgets.cpp b/client/widgets/MiscWidgets.cpp index e7e7c655d..14cc7b14d 100644 --- a/client/widgets/MiscWidgets.cpp +++ b/client/widgets/MiscWidgets.cpp @@ -468,8 +468,8 @@ void CInteractableTownTooltip::init(const CGTownInstance * town) LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE); } }, [town]{ - if(!town->town->faction->getDescriptionTranslated().empty()) - CRClickPopup::createAndPush(town->town->faction->getDescriptionTranslated()); + if(!town->getFaction()->getDescriptionTranslated().empty()) + CRClickPopup::createAndPush(town->getFaction()->getDescriptionTranslated()); }); fastMarket = std::make_shared(Rect(143, 31, 30, 34), []() { @@ -532,8 +532,7 @@ CreatureTooltip::CreatureTooltip(Point pos, const CGCreature * creature) { OBJECT_CONSTRUCTION; - auto creatureID = creature->getCreature(); - int32_t creatureIconIndex = CGI->creatures()->getById(creatureID)->getIconIndex(); + int32_t creatureIconIndex = creature->getCreature()->getIconIndex(); creatureImage = std::make_shared(AnimationPath::builtin("TWCRPORT"), creatureIconIndex); creatureImage->center(Point(parent->pos.x + parent->pos.w / 2, parent->pos.y + creatureImage->pos.h / 2 + 11)); @@ -633,7 +632,7 @@ CCreaturePic::CCreaturePic(int x, int y, const CCreature * cre, bool Big, bool A pos.x+=x; pos.y+=y; - auto faction = cre->getFaction(); + auto faction = cre->getFactionID(); assert(CGI->townh->size() > faction); diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 9729bd73c..aea486688 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -82,7 +82,7 @@ CBuildingRect::CBuildingRect(CCastleBuildings * Par, const CGTownInstance * Town // special animation frame manipulation for castle shipyard with and without ship // done due to .def used in special way, not to animate building - first image is for shipyard without citadel moat, 2nd image is for including moat - if(Town->town->faction->getId() == FactionID::CASTLE && Str->building && + if(Town->getFactionID() == FactionID::CASTLE && Str->building && (Str->building->bid == BuildingID::SHIPYARD || Str->building->bid == BuildingID::SHIP)) { if(Town->hasBuilt(BuildingID::CITADEL)) @@ -107,7 +107,7 @@ const CBuilding * CBuildingRect::getBuilding() return nullptr; if (str->hiddenUpgrade) // hidden upgrades, e.g. hordes - return base (dwelling for hordes) - return town->town->buildings.at(str->building->getBase()); + return town->getTown()->buildings.at(str->building->getBase()); return str->building; } @@ -156,7 +156,7 @@ void CBuildingRect::showPopupWindow(const Point & cursorPosition) return; BuildingID bid = getBuilding()->bid; - const CBuilding *bld = town->town->buildings.at(bid); + const CBuilding *bld = town->getTown()->buildings.at(bid); if (bid < BuildingID::DWELL_FIRST) { CRClickPopup::createAndPush(CInfoWindow::genText(bld->getNameTranslated(), bld->getDescriptionTranslated()), @@ -235,10 +235,10 @@ std::string CBuildingRect::getSubtitle()//hover text for building int bid = getBuilding()->bid; if (bid<30)//non-dwellings - only building name - return town->town->buildings.at(getBuilding()->bid)->getNameTranslated(); + return town->getTown()->buildings.at(getBuilding()->bid)->getNameTranslated(); else//dwellings - recruit %creature% { - auto & availableCreatures = town->creatures[(bid-30)%town->town->creatures.size()].second; + auto & availableCreatures = town->creatures[(bid-30)%town->getTown()->creatures.size()].second; if(availableCreatures.size()) { int creaID = availableCreatures.back();//taking last of available creatures @@ -566,7 +566,7 @@ CCastleBuildings::CCastleBuildings(const CGTownInstance* Town): { OBJECT_CONSTRUCTION; - background = std::make_shared(town->town->clientInfo.townBackground); + background = std::make_shared(town->getTown()->clientInfo.townBackground); background->needRefresh = true; background->getSurface()->setBlitMode(EImageBlitMode::OPAQUE); pos.w = background->pos.w; @@ -602,7 +602,7 @@ void CCastleBuildings::recreate() } } - for(const CStructure * structure : town->town->clientInfo.structures) + for(const CStructure * structure : town->getTown()->clientInfo.structures) { if(!structure->building) { @@ -617,7 +617,7 @@ void CCastleBuildings::recreate() for(auto & entry : groups) { - const CBuilding * build = town->town->buildings.at(entry.first); + const CBuilding * build = town->getTown()->buildings.at(entry.first); const CStructure * toAdd = *boost::max_element(entry.second, [=](const CStructure * a, const CStructure * b) { @@ -648,7 +648,7 @@ void CCastleBuildings::recreate() void CCastleBuildings::addBuilding(BuildingID building) { //FIXME: implement faster method without complete recreation of town - BuildingID base = town->town->buildings.at(building)->getBase(); + BuildingID base = town->getTown()->buildings.at(building)->getBase(); recreate(); @@ -687,7 +687,7 @@ void CCastleBuildings::buildingClicked(BuildingID building) BuildingID buildingToEnter = building; for(;;) { - const CBuilding *b = town->town->buildings.find(buildingToEnter)->second; + const CBuilding *b = town->getTown()->buildings.find(buildingToEnter)->second; if (buildingTryActivateCustomUI(buildingToEnter, building)) return; @@ -705,7 +705,7 @@ void CCastleBuildings::buildingClicked(BuildingID building) bool CCastleBuildings::buildingTryActivateCustomUI(BuildingID buildingToTest, BuildingID buildingTarget) { logGlobal->trace("You've clicked on %d", (int)buildingToTest.toEnum()); - const CBuilding *b = town->town->buildings.at(buildingToTest); + const CBuilding *b = town->getTown()->buildings.at(buildingToTest); if (town->getWarMachineInBuilding(buildingToTest).hasValue()) { @@ -744,7 +744,7 @@ bool CCastleBuildings::buildingTryActivateCustomUI(BuildingID buildingToTest, Bu } } - if (town->rewardableBuildings.count(buildingToTest) && town->town->buildings.at(buildingToTest)->manualHeroVisit) + if (town->rewardableBuildings.count(buildingToTest) && town->getTown()->buildings.at(buildingToTest)->manualHeroVisit) { enterRewardable(buildingToTest); return true; @@ -820,10 +820,10 @@ bool CCastleBuildings::buildingTryActivateCustomUI(BuildingID buildingToTest, Bu return false; case BuildingSubID::PORTAL_OF_SUMMONING: - if (town->creatures[town->town->creatures.size()].second.empty())//No creatures + if (town->creatures[town->getTown()->creatures.size()].second.empty())//No creatures LOCPLINT->showInfoDialog(CGI->generaltexth->tcommands[30]); else - enterDwelling(town->town->creatures.size()); + enterDwelling(town->getTown()->creatures.size()); return true; case BuildingSubID::BANK: @@ -850,7 +850,7 @@ void CCastleBuildings::enterRewardable(BuildingID building) { MetaString message; message.appendTextID("core.genrltxt.273"); // only visiting heroes may visit %s - message.replaceTextID(town->town->buildings.at(building)->getNameTextID()); + message.replaceTextID(town->getTown()->buildings.at(building)->getNameTextID()); LOCPLINT->showInfoDialog(message.toString()); } @@ -868,7 +868,7 @@ void CCastleBuildings::enterBlacksmith(BuildingID building, ArtifactID artifactI const CGHeroInstance *hero = town->visitingHero; if(!hero) { - LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[273]) % town->town->buildings.find(building)->second->getNameTranslated())); + LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[273]) % town->getTown()->buildings.find(building)->second->getNameTranslated())); return; } auto art = artifactID.toArtifact(); @@ -897,8 +897,8 @@ void CCastleBuildings::enterBlacksmith(BuildingID building, ArtifactID artifactI void CCastleBuildings::enterBuilding(BuildingID building) { - std::vector> comps(1, std::make_shared(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFaction(), building))); - LOCPLINT->showInfoDialog( town->town->buildings.find(building)->second->getDescriptionTranslated(), comps); + std::vector> comps(1, std::make_shared(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFactionID(), building))); + LOCPLINT->showInfoDialog( town->getTown()->buildings.find(building)->second->getDescriptionTranslated(), comps); } void CCastleBuildings::enterCastleGate() @@ -915,20 +915,20 @@ void CCastleBuildings::enterCastleGate() { const CGTownInstance *t = Town; if (t->id != this->town->id && t->visitingHero == nullptr && //another town, empty and this is - t->town->faction->getId() == town->town->faction->getId() && //the town of the same faction + t->getFactionID() == town->getFactionID() && //the town of the same faction t->hasBuilt(BuildingSubID::CASTLE_GATE)) //and the town has a castle gate { availableTowns.push_back(t->id.getNum());//add to the list if(settings["general"]["enableUiEnhancements"].Bool()) { - auto image = GH.renderHandler().loadImage(AnimationPath::builtin("ITPA"), t->town->clientInfo.icons[t->hasFort()][false] + 2, 0, EImageBlitMode::OPAQUE); + auto image = GH.renderHandler().loadImage(AnimationPath::builtin("ITPA"), t->getTown()->clientInfo.icons[t->hasFort()][false] + 2, 0, EImageBlitMode::OPAQUE); image->scaleTo(Point(35, 23)); images.push_back(image); } } } - auto gateIcon = std::make_shared(town->town->clientInfo.buildingsIcons, BuildingID::CASTLE_GATE);//will be deleted by selection window + auto gateIcon = std::make_shared(town->getTown()->clientInfo.buildingsIcons, BuildingID::CASTLE_GATE);//will be deleted by selection window auto wnd = std::make_shared(availableTowns, gateIcon, CGI->generaltexth->jktexts[40], CGI->generaltexth->jktexts[41], std::bind (&CCastleInterface::castleTeleport, LOCPLINT->castleInt, _1), 0, images); wnd->onPopup = [availableTowns](int index) { CRClickPopup::createAndPush(LOCPLINT->cb->getObjInstance(ObjectInstanceID(availableTowns[index])), GH.getCursorPosition()); }; @@ -940,7 +940,7 @@ void CCastleBuildings::enterDwelling(int level) if (level < 0 || level >= town->creatures.size() || town->creatures[level].second.empty()) { assert(0); - logGlobal->error("Attempt to enter into invalid dwelling of level %d in town %s (%s)", level, town->getNameTranslated(), town->town->faction->getNameTranslated()); + logGlobal->error("Attempt to enter into invalid dwelling of level %d in town %s (%s)", level, town->getNameTranslated(), town->getFaction()->getNameTranslated()); return; } @@ -954,8 +954,8 @@ void CCastleBuildings::enterDwelling(int level) void CCastleBuildings::enterToTheQuickRecruitmentWindow() { const auto beginIt = town->creatures.cbegin(); - const auto afterLastIt = town->creatures.size() > town->town->creatures.size() - ? std::next(beginIt, town->town->creatures.size()) + const auto afterLastIt = town->creatures.size() > town->getTown()->creatures.size() + ? std::next(beginIt, town->getTown()->creatures.size()) : town->creatures.cend(); const auto hasSomeoneToRecruit = std::any_of(beginIt, afterLastIt, [](const auto & creatureInfo) { return creatureInfo.first > 0; }); @@ -967,8 +967,8 @@ void CCastleBuildings::enterToTheQuickRecruitmentWindow() void CCastleBuildings::enterFountain(const BuildingID & building, BuildingSubID::EBuildingSubID subID, BuildingID upgrades) { - std::vector> comps(1, std::make_shared(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFaction(), building))); - std::string descr = town->town->buildings.find(building)->second->getDescriptionTranslated(); + std::vector> comps(1, std::make_shared(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFactionID(), building))); + std::string descr = town->getTown()->buildings.find(building)->second->getDescriptionTranslated(); std::string hasNotProduced; std::string hasProduced; @@ -977,10 +977,10 @@ void CCastleBuildings::enterFountain(const BuildingID & building, BuildingSubID: bool isMysticPondOrItsUpgrade = subID == BuildingSubID::MYSTIC_POND || (upgrades != BuildingID::NONE - && town->town->buildings.find(BuildingID(upgrades))->second->subId == BuildingSubID::MYSTIC_POND); + && town->getTown()->buildings.find(BuildingID(upgrades))->second->subId == BuildingSubID::MYSTIC_POND); if(upgrades != BuildingID::NONE) - descr += "\n\n"+town->town->buildings.find(BuildingID(upgrades))->second->getDescriptionTranslated(); + descr += "\n\n"+town->getTown()->buildings.find(BuildingID(upgrades))->second->getDescriptionTranslated(); if(isMysticPondOrItsUpgrade) //for vanila Rampart like towns { @@ -1056,7 +1056,7 @@ void CCastleBuildings::enterTownHall() void CCastleBuildings::openMagesGuild() { - auto mageGuildBackground = LOCPLINT->castleInt->town->town->clientInfo.guildBackground; + auto mageGuildBackground = LOCPLINT->castleInt->town->getTown()->clientInfo.guildBackground; GH.windows().createAndPushWindow(LOCPLINT->castleInt, mageGuildBackground); } @@ -1247,7 +1247,7 @@ CTownInfo::CTownInfo(int posX, int posY, const CGTownInstance * Town, bool townH return;//FIXME: suspicious statement, fix or comment picture = std::make_shared(AnimationPath::builtin("ITMCL.DEF"), town->fortLevel()-1); } - building = town->town->buildings.at(BuildingID(buildID)); + building = town->getTown()->buildings.at(BuildingID(buildID)); pos = picture->pos; } @@ -1322,7 +1322,7 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town, const CGTownInst recreateIcons(); if (!from) adventureInt->onAudioPaused(); - CCS->musich->playMusicFromSet("faction", town->town->faction->getJsonKey(), true, false); + CCS->musich->playMusicFromSet("faction", town->getFaction()->getJsonKey(), true, false); } CCastleInterface::~CCastleInterface() @@ -1403,7 +1403,7 @@ void CCastleInterface::removeBuilding(BuildingID bid) void CCastleInterface::recreateIcons() { OBJECT_CONSTRUCTION; - size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->built >= LOCPLINT->cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)]; + size_t iconIndex = town->getTown()->clientInfo.icons[town->hasFort()][town->built >= LOCPLINT->cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)]; icon->setFrame(iconIndex); TResources townIncome = town->dailyIncome(); @@ -1425,8 +1425,8 @@ void CCastleInterface::recreateIcons() if(town->hasBuilt(BuildingID::TAVERN)) LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE); }, [this]{ - if(!town->town->faction->getDescriptionTranslated().empty()) - CRClickPopup::createAndPush(town->town->faction->getDescriptionTranslated()); + if(!town->getFaction()->getDescriptionTranslated().empty()) + CRClickPopup::createAndPush(town->getFaction()->getDescriptionTranslated()); }); creainfo.clear(); @@ -1527,7 +1527,7 @@ CHallInterface::CBuildingBox::CBuildingBox(int x, int y, const CGTownInstance * -1, -1, -1, 0, 0, 1, 2, -1, 1, 1, -1, -1 }; - icon = std::make_shared(town->town->clientInfo.buildingsIcons, building->bid, 0, 2, 2); + icon = std::make_shared(town->getTown()->clientInfo.buildingsIcons, building->bid, 0, 2, 2); header = std::make_shared(AnimationPath::builtin("TPTHBAR"), panelIndex[static_cast(state)], 0, 1, 73); if(iconIndex[static_cast(state)] >=0) mark = std::make_shared(AnimationPath::builtin("TPTHCHK"), iconIndex[static_cast(state)], 0, 136, 56); @@ -1569,7 +1569,7 @@ void CHallInterface::CBuildingBox::showPopupWindow(const Point & cursorPosition) } CHallInterface::CHallInterface(const CGTownInstance * Town): - CWindowObject(PLAYER_COLORED | BORDERED, Town->town->clientInfo.hallBackground), + CWindowObject(PLAYER_COLORED | BORDERED, Town->getTown()->clientInfo.hallBackground), town(Town) { OBJECT_CONSTRUCTION; @@ -1581,10 +1581,10 @@ CHallInterface::CHallInterface(const CGTownInstance * Town): auto statusbarBackground = std::make_shared(background->getSurface(), barRect, 5, 556); statusbar = CGStatusBar::create(statusbarBackground); - title = std::make_shared(399, 12, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, town->town->buildings.at(BuildingID(town->hallLevel()+BuildingID::VILLAGE_HALL))->getNameTranslated()); + title = std::make_shared(399, 12, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, town->getTown()->buildings.at(BuildingID(town->hallLevel()+BuildingID::VILLAGE_HALL))->getNameTranslated()); exit = std::make_shared(Point(748, 556), AnimationPath::builtin("TPMAGE1.DEF"), CButton::tooltip(CGI->generaltexth->hcommands[8]), [&](){close();}, EShortcut::GLOBAL_RETURN); - auto & boxList = town->town->clientInfo.hallSlots; + auto & boxList = town->getTown()->clientInfo.hallSlots; boxes.resize(boxList.size()); for(size_t row=0; rowwarn("Invalid building ID found in hallSlots of town '%s'", town->town->faction->getJsonKey() ); + logMod->warn("Invalid building ID found in hallSlots of town '%s'", town->getFaction()->getJsonKey() ); continue; } - const CBuilding * current = town->town->buildings.at(buildingID); + const CBuilding * current = town->getTown()->buildings.at(buildingID); if(town->hasBuilt(buildingID)) { building = current; @@ -1629,7 +1629,7 @@ CBuildWindow::CBuildWindow(const CGTownInstance *Town, const CBuilding * Buildin { OBJECT_CONSTRUCTION; - icon = std::make_shared(town->town->clientInfo.buildingsIcons, building->bid, 0, 125, 50); + icon = std::make_shared(town->getTown()->clientInfo.buildingsIcons, building->bid, 0, 125, 50); auto statusbarBackground = std::make_shared(background->getSurface(), Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26); statusbar = CGStatusBar::create(statusbarBackground); @@ -1711,7 +1711,7 @@ std::string CBuildWindow::getTextForState(EBuildingState state) { auto toStr = [&](const BuildingID build) -> std::string { - return town->town->buildings.at(build)->getNameTranslated(); + return town->getTown()->buildings.at(build)->getNameTranslated(); }; ret = CGI->generaltexth->allTexts[52]; @@ -1721,7 +1721,7 @@ std::string CBuildWindow::getTextForState(EBuildingState state) case EBuildingState::MISSING_BASE: { std::string msg = CGI->generaltexth->translate("vcmi.townHall.missingBase"); - ret = boost::str(boost::format(msg) % town->town->buildings.at(building->upgrade)->getNameTranslated()); + ret = boost::str(boost::format(msg) % town->getTown()->buildings.at(building->upgrade)->getNameTranslated()); break; } } @@ -1780,11 +1780,11 @@ CFortScreen::CFortScreen(const CGTownInstance * town): { OBJECT_CONSTRUCTION; ui32 fortSize = static_cast(town->creatures.size()); - if(fortSize > town->town->creatures.size() && town->creatures.back().second.empty()) + if(fortSize > town->getTown()->creatures.size() && town->creatures.back().second.empty()) fortSize--; fortSize = std::min(fortSize, static_cast(GameConstants::CREATURES_PER_TOWN)); // for 8 creatures + portal of summoning - const CBuilding * fortBuilding = town->town->buildings.at(BuildingID(town->fortLevel()+6)); + const CBuilding * fortBuilding = town->getTown()->buildings.at(BuildingID(town->fortLevel()+6)); title = std::make_shared(400, 12, FONT_BIG, ETextAlignment::CENTER, Colors::WHITE, fortBuilding->getNameTranslated()); std::string text = boost::str(boost::format(CGI->generaltexth->fcommands[6]) % fortBuilding->getNameTranslated()); @@ -1810,7 +1810,7 @@ CFortScreen::CFortScreen(const CGTownInstance * town): for(ui32 i=0; itown->creatures.size()) + if(fortSize == town->getTown()->creatures.size()) { BuildingID dwelling = BuildingID::getDwellingFromLevel(i, 1); @@ -1839,7 +1839,7 @@ CFortScreen::CFortScreen(const CGTownInstance * town): ImagePath CFortScreen::getBgName(const CGTownInstance * town) { ui32 fortSize = static_cast(town->creatures.size()); - if(fortSize > town->town->creatures.size() && town->creatures.back().second.empty()) + if(fortSize > town->getTown()->creatures.size() && town->creatures.back().second.empty()) fortSize--; fortSize = std::min(fortSize, static_cast(GameConstants::CREATURES_PER_TOWN)); // for 8 creatures + portal of summoning @@ -1877,7 +1877,7 @@ CFortScreen::RecruitArea::RecruitArea(int posX, int posY, const CGTownInstance * if(getMyBuilding() != nullptr) { - buildingIcon = std::make_shared(town->town->clientInfo.buildingsIcons, getMyBuilding()->bid, 0, 4, 21); + buildingIcon = std::make_shared(town->getTown()->clientInfo.buildingsIcons, getMyBuilding()->bid, 0, 4, 21); buildingName = std::make_shared(78, 101, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, getMyBuilding()->getNameTranslated(), 152); if(town->hasBuilt(getMyBuilding()->bid)) @@ -1913,8 +1913,8 @@ const CCreature * CFortScreen::RecruitArea::getMyCreature() { if(!town->creatures.at(level).second.empty()) // built return town->creatures.at(level).second.back().toCreature(); - if(!town->town->creatures.at(level).empty()) // there are creatures on this level - return town->town->creatures.at(level).front().toCreature(); + if(!town->getTown()->creatures.at(level).empty()) // there are creatures on this level + return town->getTown()->creatures.at(level).front().toCreature(); return nullptr; } @@ -1922,17 +1922,17 @@ const CBuilding * CFortScreen::RecruitArea::getMyBuilding() { BuildingID myID = BuildingID(BuildingID::getDwellingFromLevel(level, 0)); - if (level == town->town->creatures.size()) - return town->town->getSpecialBuilding(BuildingSubID::PORTAL_OF_SUMMONING); + if (level == town->getTown()->creatures.size()) + return town->getTown()->getSpecialBuilding(BuildingSubID::PORTAL_OF_SUMMONING); - if (!town->town->buildings.count(myID)) + if (!town->getTown()->buildings.count(myID)) return nullptr; - const CBuilding * build = town->town->buildings.at(myID); - while (town->town->buildings.count(myID)) + const CBuilding * build = town->getTown()->buildings.at(myID); + while (town->getTown()->buildings.count(myID)) { if (town->hasBuilt(myID)) - build = town->town->buildings.at(myID); + build = town->getTown()->buildings.at(myID); BuildingID::advanceDwelling(myID); } @@ -1972,7 +1972,7 @@ CMageGuildScreen::CMageGuildScreen(CCastleInterface * owner, const ImagePath & i { OBJECT_CONSTRUCTION; - window = std::make_shared(owner->town->town->clientInfo.guildWindow, 332, 76); + window = std::make_shared(owner->town->getTown()->clientInfo.guildWindow, 332, 76); resdatabar = std::make_shared(); resdatabar->moveBy(pos.topLeft(), true); @@ -2007,7 +2007,7 @@ void CMageGuildScreen::updateSpells(ObjectInstanceID tID) const CGTownInstance * town = LOCPLINT->cb->getTown(townId); - for(size_t i=0; itown->mageLevel; i++) + for(size_t i=0; igetTown()->mageLevel; i++) { size_t spellCount = town->spellsAtLevel((int)i+1,false); //spell at level with -1 hmmm? for(size_t j=0; jsecSkills.size(); ++m) secSkillIcons[leftRight].push_back(std::make_shared(AnimationPath::builtin("SECSK32"), 0, 0, 32 + 36 * m + 454 * leftRight, qeLayout ? 83 : 88)); - specImages[leftRight] = std::make_shared(AnimationPath::builtin("UN32"), hero->type->imageIndex, 0, 67 + 490 * leftRight, qeLayout ? 41 : 45); + specImages[leftRight] = std::make_shared(AnimationPath::builtin("UN32"), hero->getHeroType()->imageIndex, 0, 67 + 490 * leftRight, qeLayout ? 41 : 45); expImages[leftRight] = std::make_shared(AnimationPath::builtin("PSKIL32"), 4, 0, 103 + 490 * leftRight, qeLayout ? 41 : 45); expValues[leftRight] = std::make_shared(119 + 490 * leftRight, qeLayout ? 66 : 71, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE); @@ -151,7 +151,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, specialtyAreas[b] = std::make_shared(); specialtyAreas[b]->pos = Rect(Point(pos.x + 69 + 490 * b, pos.y + (qeLayout ? 41 : 45)), Point(32, 32)); specialtyAreas[b]->hoverText = CGI->generaltexth->heroscrn[27]; - specialtyAreas[b]->text = hero->type->getSpecialtyDescriptionTranslated(); + specialtyAreas[b]->text = hero->getHeroType()->getSpecialtyDescriptionTranslated(); experienceAreas[b] = std::make_shared(); experienceAreas[b]->pos = Rect(Point(pos.x + 105 + 490 * b, pos.y + (qeLayout ? 41 : 45)), Point(32, 32)); diff --git a/client/windows/CHeroWindow.cpp b/client/windows/CHeroWindow.cpp index 36dd52d80..809f937f0 100644 --- a/client/windows/CHeroWindow.cpp +++ b/client/windows/CHeroWindow.cpp @@ -184,9 +184,9 @@ void CHeroWindow::update() name->setText(curHero->getNameTranslated()); title->setText((boost::format(CGI->generaltexth->allTexts[342]) % curHero->level % curHero->getClassNameTranslated()).str()); - specArea->text = curHero->type->getSpecialtyDescriptionTranslated(); - specImage->setFrame(curHero->type->imageIndex); - specName->setText(curHero->type->getSpecialtyNameTranslated()); + specArea->text = curHero->getHeroType()->getSpecialtyDescriptionTranslated(); + specImage->setFrame(curHero->getHeroType()->imageIndex); + specName->setText(curHero->getHeroType()->getSpecialtyNameTranslated()); tacticsButton = std::make_shared(Point(539, 483), AnimationPath::builtin("hsbtns8.def"), std::make_pair(heroscrn[26], heroscrn[31]), 0, EShortcut::HERO_TOGGLE_TACTICS); tacticsButton->addHoverText(EButtonState::HIGHLIGHTED, CGI->generaltexth->heroscrn[25]); diff --git a/client/windows/CKingdomInterface.cpp b/client/windows/CKingdomInterface.cpp index 9363cd2aa..6c99bbca6 100644 --- a/client/windows/CKingdomInterface.cpp +++ b/client/windows/CKingdomInterface.cpp @@ -300,7 +300,7 @@ int InfoBoxHeroData::getSubID() else return 0; case HERO_SPECIAL: - return hero->type->getIndex(); + return hero->getHeroTypeID().getNum(); case HERO_MANA: case HERO_EXPERIENCE: return 0; @@ -800,7 +800,7 @@ CTownItem::CTownItem(const CGTownInstance * Town) garr = std::make_shared(Point(313, 3), 4, Point(232,0), town->getUpperArmy(), town->visitingHero, true, true, CGarrisonInt::ESlotsLayout::TWO_ROWS); heroes = std::make_shared(town, Point(244,6), Point(475,6), garr, false); - size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->built >= LOCPLINT->cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)]; + size_t iconIndex = town->getTown()->clientInfo.icons[town->hasFort()][town->built >= LOCPLINT->cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)]; picture = std::make_shared(AnimationPath::builtin("ITPT"), iconIndex, 0, 5, 6); openTown = std::make_shared(Rect(5, 6, 58, 64), town); @@ -823,8 +823,8 @@ CTownItem::CTownItem(const CGTownInstance * Town) if(town->hasBuilt(BuildingID::TAVERN)) LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE); }, [&]{ - if(!town->town->faction->getDescriptionTranslated().empty()) - CRClickPopup::createAndPush(town->town->faction->getDescriptionTranslated()); + if(!town->getTown()->faction->getDescriptionTranslated().empty()) + CRClickPopup::createAndPush(town->getFaction()->getDescriptionTranslated()); }); fastMarket = std::make_shared(Rect(153, 6, 65, 64), []() { diff --git a/client/windows/CMarketWindow.cpp b/client/windows/CMarketWindow.cpp index bf7661696..02b167575 100644 --- a/client/windows/CMarketWindow.cpp +++ b/client/windows/CMarketWindow.cpp @@ -192,7 +192,7 @@ std::string CMarketWindow::getMarketTitle(const ObjectInstanceID marketId, const { for(const auto & buildingId : town->getBuildings()) { - if(const auto building = town->town->buildings.at(buildingId); vstd::contains(building->marketModes, mode)) + if(const auto building = town->getTown()->buildings.at(buildingId); vstd::contains(building->marketModes, mode)) return building->getNameTranslated(); } } diff --git a/client/windows/CQuestLog.cpp b/client/windows/CQuestLog.cpp index 2e18ef424..dbc39d439 100644 --- a/client/windows/CQuestLog.cpp +++ b/client/windows/CQuestLog.cpp @@ -78,7 +78,7 @@ void CQuestMinimap::addQuestMarks (const QuestInfo * q) int3 tile; if (q->obj) - tile = q->obj->pos; + tile = q->obj->visitablePos(); else tile = q->tile; @@ -104,7 +104,7 @@ void CQuestMinimap::update() void CQuestMinimap::iconClicked() { if(currentQuest->obj) - adventureInt->centerOnTile(currentQuest->obj->pos); + adventureInt->centerOnTile(currentQuest->obj->visitablePos()); //moveAdvMapSelection(); } diff --git a/client/windows/CSpellWindow.cpp b/client/windows/CSpellWindow.cpp index 778b7db6c..6f810a2fa 100644 --- a/client/windows/CSpellWindow.cpp +++ b/client/windows/CSpellWindow.cpp @@ -205,9 +205,9 @@ CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _m } } - selectedTab = battleSpellsOnly ? myInt->localState->spellbookSettings.spellbookLastTabBattle : myInt->localState->spellbookSettings.spellbookLastTabAdvmap; + selectedTab = battleSpellsOnly ? myInt->localState->getSpellbookSettings().spellbookLastTabBattle : myInt->localState->getSpellbookSettings().spellbookLastTabAdvmap; schoolTab->setFrame(selectedTab, 0); - int cp = battleSpellsOnly ? myInt->localState->spellbookSettings.spellbookLastPageBattle : myInt->localState->spellbookSettings.spellbookLastPageAdvmap; + int cp = battleSpellsOnly ? myInt->localState->getSpellbookSettings().spellbookLastPageBattle : myInt->localState->getSpellbookSettings().spellbookLastPageAdvmap; // spellbook last page battle index is not reset after battle, so this needs to stay here vstd::abetween(cp, 0, std::max(0, pagesWithinCurrentTab() - 1)); setCurrentPage(cp); @@ -313,8 +313,18 @@ void CSpellWindow::processSpells() void CSpellWindow::fexitb() { - (myInt->battleInt ? myInt->localState->spellbookSettings.spellbookLastTabBattle : myInt->localState->spellbookSettings.spellbookLastTabAdvmap) = selectedTab; - (myInt->battleInt ? myInt->localState->spellbookSettings.spellbookLastPageBattle : myInt->localState->spellbookSettings.spellbookLastPageAdvmap) = currentPage; + auto spellBookState = myInt->localState->getSpellbookSettings(); + if(myInt->battleInt) + { + spellBookState.spellbookLastTabBattle = selectedTab; + spellBookState.spellbookLastPageBattle = currentPage; + } + else + { + spellBookState.spellbookLastTabAdvmap = selectedTab; + spellBookState.spellbookLastPageAdvmap = currentPage; + } + myInt->localState->setSpellbookSettings(spellBookState); if(onSpellSelect) onSpellSelect(SpellID::NONE); @@ -619,8 +629,10 @@ void CSpellWindow::SpellArea::clickPressed(const Point & cursorPosition) auto guard = vstd::makeScopeGuard([this]() { - owner->myInt->localState->spellbookSettings.spellbookLastTabAdvmap = owner->selectedTab; - owner->myInt->localState->spellbookSettings.spellbookLastPageAdvmap = owner->currentPage; + auto spellBookState = owner->myInt->localState->getSpellbookSettings(); + spellBookState.spellbookLastTabAdvmap = owner->selectedTab; + spellBookState.spellbookLastPageAdvmap = owner->currentPage; + owner->myInt->localState->setSpellbookSettings(spellBookState); }); spells::detail::ProblemImpl problem; diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index e014d36a8..46406d4d4 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -522,9 +522,9 @@ CTavernWindow::CTavernWindow(const CGObjectInstance * TavernObj, const std::func recruit->block(true); } if(LOCPLINT->castleInt) - videoPlayer = std::make_shared(Point(70, 56), LOCPLINT->castleInt->town->town->clientInfo.tavernVideo, false); + videoPlayer = std::make_shared(Point(70, 56), LOCPLINT->castleInt->town->getTown()->clientInfo.tavernVideo, false); else if(const auto * townObj = dynamic_cast(TavernObj)) - videoPlayer = std::make_shared(Point(70, 56), townObj->town->clientInfo.tavernVideo, false); + videoPlayer = std::make_shared(Point(70, 56), townObj->getTown()->clientInfo.tavernVideo, false); else videoPlayer = std::make_shared(Point(70, 56), VideoPath::builtin("TAVERN.BIK"), false); @@ -548,7 +548,7 @@ void CTavernWindow::addInvite() if(!inviteableHeroes.empty()) { - int imageIndex = heroToInvite ? (*CGI->heroh)[heroToInvite->getHeroType()]->imageIndex : 156; // 156 => special id for random + int imageIndex = heroToInvite ? heroToInvite->getIconIndex() : 156; // 156 => special id for random if(!heroToInvite) heroToInvite = (*RandomGeneratorUtil::nextItem(inviteableHeroes, CRandomGenerator::getDefault())).second; @@ -563,7 +563,7 @@ void CTavernWindow::recruitb() const CGHeroInstance *toBuy = (selected ? h2 : h1)->h; const CGObjectInstance *obj = tavernObj; - LOCPLINT->cb->recruitHero(obj, toBuy, heroToInvite ? heroToInvite->getHeroType() : HeroTypeID::NONE); + LOCPLINT->cb->recruitHero(obj, toBuy, heroToInvite ? heroToInvite->getHeroTypeID() : HeroTypeID::NONE); close(); } @@ -963,7 +963,7 @@ CUniversityWindow::CUniversityWindow(const CGHeroInstance * _hero, BuildingID bu if(auto town = dynamic_cast(_market)) { - auto faction = town->town->faction->getId(); + auto faction = town->getTown()->faction->getId(); titlePic = std::make_shared((*CGI->townh)[faction]->town->clientInfo.buildingsIcons, building); } else if(auto uni = dynamic_cast(_market); uni->appearance) diff --git a/client/windows/QuickRecruitmentWindow.cpp b/client/windows/QuickRecruitmentWindow.cpp index 49f9be79d..cdbb80121 100644 --- a/client/windows/QuickRecruitmentWindow.cpp +++ b/client/windows/QuickRecruitmentWindow.cpp @@ -51,9 +51,9 @@ void QuickRecruitmentWindow::setCreaturePurchaseCards() { int availableAmount = getAvailableCreatures(); Point position = Point((pos.w - 100*availableAmount - 8*(availableAmount-1))/2,64); - for (int i = 0; i < town->town->creatures.size(); i++) + for (int i = 0; i < town->getTown()->creatures.size(); i++) { - if(!town->town->creatures.at(i).empty() && !town->creatures.at(i).second.empty() && town->creatures[i].first) + if(!town->getTown()->creatures.at(i).empty() && !town->creatures.at(i).second.empty() && town->creatures[i].first) { cards.push_back(std::make_shared(town->creatures[i].second, position, town->creatures[i].first, this)); position.x += 108; @@ -108,7 +108,7 @@ void QuickRecruitmentWindow::purchaseUnits() { int level = 0; int i = 0; - for(auto c : town->town->creatures) + for(auto c : town->getTown()->creatures) { for(auto c2 : c) if(c2 == selected->creatureOnTheCard->getId()) @@ -129,8 +129,8 @@ void QuickRecruitmentWindow::purchaseUnits() int QuickRecruitmentWindow::getAvailableCreatures() { int creaturesAmount = 0; - for (int i=0; i< town->town->creatures.size(); i++) - if(!town->town->creatures.at(i).empty() && !town->creatures.at(i).second.empty() && town->creatures[i].first) + for (int i=0; i< town->getTown()->creatures.size(); i++) + if(!town->getTown()->creatures.at(i).empty() && !town->creatures.at(i).second.empty() && town->creatures[i].first) creaturesAmount++; return creaturesAmount; } diff --git a/client/windows/settings/GeneralOptionsTab.cpp b/client/windows/settings/GeneralOptionsTab.cpp index d8ead20f8..6571a3422 100644 --- a/client/windows/settings/GeneralOptionsTab.cpp +++ b/client/windows/settings/GeneralOptionsTab.cpp @@ -194,10 +194,8 @@ GeneralOptionsTab::GeneralOptionsTab() build(config); - const auto & currentResolution = settings["video"]["resolution"]; - std::shared_ptr scalingLabel = widget("scalingLabel"); - scalingLabel->setText(scalingToLabelString(currentResolution["scaling"].Integer())); + scalingLabel->setText(scalingToLabelString(GH.screenHandler().getInterfaceScalingPercentage())); std::shared_ptr longTouchLabel = widget("longTouchLabel"); if (longTouchLabel) diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 276903873..93ae2038f 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -184,6 +184,7 @@ "targetfps", "vsync", "fontsType", + "cursorScalingFactor", "fontScalingFactor", "upscalingFilter", "fontUpscalingFilter", @@ -195,22 +196,19 @@ "additionalProperties" : false, "required" : [ "width", "height", "scaling" ], "properties" : { - "width" : { "type" : "number" }, - "height" : { "type" : "number" }, - "scaling" : { "type" : "number" } - }, - "defaultIOS" : {"width" : 800, "height" : 600, "scaling" : 200 }, - "defaultAndroid" : {"width" : 800, "height" : 600, "scaling" : 200 }, - "default" : {"width" : 800, "height" : 600, "scaling" : 100 } + "width" : { "type" : "number", "default" : 1280 }, + "height" : { "type" : "number", "default" : 720 }, + "scaling" : { "type" : "number", "default" : 0 } + } }, "reservedWidth" : { "type" : "number", - "defaultIOS" : 0.1, // iOS camera cutout / notch is excluded from available area by SDL + "defaultIOS" : 0.1, // iOS camera cutout / notch is not excluded from available area by SDL, handle it this way "default" : 0 }, "fullscreen" : { "type" : "boolean", - "default" : false + "default" : true }, "realFullscreen" : { "type" : "boolean", @@ -256,6 +254,10 @@ "enum" : [ "auto", "original", "scalable" ], "default" : "auto" }, + "cursorScalingFactor" : { + "type" : "number", + "default" : 1 + }, "fontScalingFactor" : { "type" : "number", "default" : 1 diff --git a/include/vcmi/Entity.h b/include/vcmi/Entity.h index 406a879b5..b00a61525 100644 --- a/include/vcmi/Entity.h +++ b/include/vcmi/Entity.h @@ -26,7 +26,7 @@ class DLL_LINKAGE INativeTerrainProvider { public: virtual TerrainId getNativeTerrain() const = 0; - virtual FactionID getFaction() const = 0; + virtual FactionID getFactionID() const = 0; virtual bool isNativeTerrain(TerrainId terrain) const; }; diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index e2229ba19..a56893838 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -202,6 +202,10 @@ elseif(NOT APPLE_IOS) target_link_libraries(vcmilauncher SDL2::SDL2) endif() +if(ENABLE_STATIC_LIBS OR NOT (ENABLE_EDITOR AND ENABLE_LAUNCHER)) + target_compile_definitions(vcmilauncher PRIVATE VCMIQT_STATIC) +endif() + target_link_libraries(vcmilauncher vcmi vcmiqt Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network) target_include_directories(vcmilauncher PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} diff --git a/launcher/settingsView/csettingsview_moc.cpp b/launcher/settingsView/csettingsview_moc.cpp index 91788e7b6..94ca8fc6c 100644 --- a/launcher/settingsView/csettingsview_moc.cpp +++ b/launcher/settingsView/csettingsview_moc.cpp @@ -127,7 +127,12 @@ void CSettingsView::loadSettings() #endif fillValidScalingRange(); - ui->spinBoxInterfaceScaling->setValue(settings["video"]["resolution"]["scaling"].Float()); + ui->buttonScalingAuto->setChecked(settings["video"]["resolution"]["scaling"].Integer() == 0); + if (settings["video"]["resolution"]["scaling"].Integer() == 0) + ui->spinBoxInterfaceScaling->setValue(100); + else + ui->spinBoxInterfaceScaling->setValue(settings["video"]["resolution"]["scaling"].Float()); + ui->spinBoxFramerateLimit->setValue(settings["video"]["targetfps"].Float()); ui->spinBoxFramerateLimit->setDisabled(settings["video"]["vsync"].Bool()); ui->sliderReservedArea->setValue(std::round(settings["video"]["reservedWidth"].Float() * 100)); @@ -174,6 +179,7 @@ void CSettingsView::loadSettings() ui->sliderControllerSticksAcceleration->setValue(settings["input"]["controllerAxisScale"].Float() * 100); ui->lineEditGameLobbyHost->setText(QString::fromStdString(settings["lobby"]["hostname"].String())); ui->spinBoxNetworkPortLobby->setValue(settings["lobby"]["port"].Integer()); + ui->buttonVSync->setChecked(settings["video"]["vsync"].Bool()); if (settings["video"]["fontsType"].String() == "auto") ui->buttonFontAuto->setChecked(true); @@ -195,7 +201,6 @@ void CSettingsView::loadSettings() void CSettingsView::loadToggleButtonSettings() { setCheckbuttonState(ui->buttonShowIntro, settings["video"]["showIntro"].Bool()); - setCheckbuttonState(ui->buttonVSync, settings["video"]["vsync"].Bool()); setCheckbuttonState(ui->buttonAutoCheck, settings["launcher"]["autoCheckRepositories"].Bool()); setCheckbuttonState(ui->buttonRepositoryDefault, settings["launcher"]["defaultRepositoryEnabled"].Bool()); @@ -212,10 +217,15 @@ void CSettingsView::loadToggleButtonSettings() std::string cursorType = settings["video"]["cursor"].String(); int cursorTypeIndex = vstd::find_pos(cursorTypesList, cursorType); setCheckbuttonState(ui->buttonCursorType, cursorTypeIndex); + ui->sliderScalingCursor->setDisabled(cursorType == "software"); // Not supported + ui->labelScalingCursorValue->setDisabled(cursorType == "software"); // Not supported int fontScalingPercentage = settings["video"]["fontScalingFactor"].Float() * 100; ui->sliderScalingFont->setValue(fontScalingPercentage / 5); + int cursorScalingPercentage = settings["video"]["cursorScalingFactor"].Float() * 100; + ui->sliderScalingCursor->setValue(cursorScalingPercentage / 5); + } void CSettingsView::fillValidResolutions() @@ -494,6 +504,8 @@ void CSettingsView::on_buttonCursorType_toggled(bool value) Settings node = settings.write["video"]["cursor"]; node->String() = cursorTypesList[value ? 1 : 0]; updateCheckbuttonText(ui->buttonCursorType); + ui->sliderScalingCursor->setDisabled(value == 1); // Not supported + ui->labelScalingCursorValue->setDisabled(value == 1); // Not supported } void CSettingsView::loadTranslation() @@ -627,7 +639,6 @@ void CSettingsView::on_buttonVSync_toggled(bool value) Settings node = settings.write["video"]["vsync"]; node->Bool() = value; ui->spinBoxFramerateLimit->setDisabled(settings["video"]["vsync"].Bool()); - updateCheckbuttonText(ui->buttonVSync); } void CSettingsView::on_comboBoxEnemyPlayerAI_currentTextChanged(const QString &arg1) @@ -816,3 +827,21 @@ void CSettingsView::on_buttonValidationFull_clicked(bool checked) Settings node = settings.write["mods"]["validation"]; node->String() = "full"; } + +void CSettingsView::on_sliderScalingCursor_valueChanged(int value) +{ + int actualValuePercentage = value * 5; + ui->labelScalingCursorValue->setText(QString("%1%").arg(actualValuePercentage)); + Settings node = settings.write["video"]["cursorScalingFactor"]; + node->Float() = actualValuePercentage / 100.0; +} + +void CSettingsView::on_buttonScalingAuto_toggled(bool checked) +{ + ui->spinBoxInterfaceScaling->setDisabled(checked); + ui->spinBoxInterfaceScaling->setValue(100); + + Settings node = settings.write["video"]["resolution"]["scaling"]; + node->Integer() = checked ? 0 : 100; +} + diff --git a/launcher/settingsView/csettingsview_moc.h b/launcher/settingsView/csettingsview_moc.h index 512762d0c..d05e7eb1e 100644 --- a/launcher/settingsView/csettingsview_moc.h +++ b/launcher/settingsView/csettingsview_moc.h @@ -97,6 +97,10 @@ private slots: void on_buttonValidationFull_clicked(bool checked); + void on_sliderScalingCursor_valueChanged(int value); + + void on_buttonScalingAuto_toggled(bool checked); + private: Ui::CSettingsView * ui; diff --git a/launcher/settingsView/csettingsview_moc.ui b/launcher/settingsView/csettingsview_moc.ui index c0082edd8..a974b5209 100644 --- a/launcher/settingsView/csettingsview_moc.ui +++ b/launcher/settingsView/csettingsview_moc.ui @@ -47,209 +47,119 @@ 0 - -800 + -126 729 - 1506 + 1503 - - + + + + + + 0 + 0 + + + + + + + true + + + + + + + Mods Validation + + + + + + + + Automatic + + + + + None + + + + + xBRZ x2 + + + + + xBRZ x3 + + + + + xBRZ x4 + + + + + + + + 500 + + + 2500 + + + 25 + + + 250 + + + 500 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + 250 + + + + + + + Reset + + + + - - + + - 0 - - - 50 - - - 1 - - - 10 - - - 0 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - 10 - - - - - - - Upscaling Filter - - - - - - - 100 - - 500 - - 10 - - - 100 - - - 100 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - 50 - - - - - - - false - - - BattleAI - - - - BattleAI - - - - - StupidAI - - - - - - - - Additional repository - - - - - - - Ignore SSL errors - - - - - - - - Nearest - - - - - Linear - - - - - Automatic (Linear) - - - - - - - - VCMI Language - - - - - - - - 0 - 0 - - - - Automatic - - - true - - - true - - - buttonGroupFonts - - - - - - - - true - - - - Video - - - 5 - - - - - - - Show intro - - - - - - - Heroes III Translation - - - - - - - 0 - - 50 + 2000 - 1 + 250 - 10 - - - 0 + 250 Qt::Horizontal @@ -258,169 +168,11 @@ QSlider::TicksAbove - 10 + 250 - - - - - 0 - 0 - - - - Scalable - - - true - - - true - - - buttonGroupFonts - - - - - - - Touch Tap Tolerance - - - - - - - - - - - - - - 100 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - 10 - - - - - - - - - - Renderer - - - - - - - - true - - - - Artificial Intelligence - - - 5 - - - - - - - Neutral AI in battles - - - - - - - VSync - - - - - - - Framerate Limit - - - - - - - empty = map name prefix - - - - - - - - true - - - - Input - Touchscreen - - - 5 - - - - - - - 0 - - - 50 - - - 1 - - - 10 - - - 0 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - 10 - - - - - - - Relative Pointer Speed - - - - - - - + true @@ -442,8 +194,56 @@ - - + + + + Reserved screen area + + + + + + + + true + + + + General + + + 5 + + + + + + + Autosave + + + + + + + 100 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + 10 + + + + + + + true + 0 @@ -456,26 +256,341 @@ true - - - - - - Long Touch Duration + + false - - - - - + + + + true + + + + 0 + 0 + + - Check on startup + Basic + + + true + + + false + + + buttonGroupValidation + + + + + + + Sound Volume - + + + + Haptic Feedback + + + + + + + Touch Tap Tolerance + + + + + + + + + + + + + 1024 + + + 65535 + + + 3030 + + + + + + + true + + + + 0 + 0 + + + + Full + + + true + + + false + + + buttonGroupValidation + + + + + + + + true + + + + Input - Controller + + + 5 + + + + + + + 10 + + + 30 + + + 1 + + + 2 + + + 20 + + + 20 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + 2 + + + + + + + Software Cursor + + + + + + + VCAI + + + + VCAI + + + + + Nullkiller + + + + + + + + + + + true + + + + + + + VCAI + + + + VCAI + + + + + Nullkiller + + + + + + + + Heroes III Translation + + + + + + + + true + + + + Artificial Intelligence + + + 5 + + + + + + + Default repository + + + + + + + + + + % + + + 50 + + + 400 + + + 10 + + + + + + + Relative Pointer Speed + + + + + + + Downscaling Filter + + + + + + + true + + + + 0 + 0 + + + + + + + true + + + false + + + + + + + 1024 + + + 65535 + + + 3030 + + + + + + + Show intro + + + + + + + + 0 + 0 + + + + Automatic + + + true + + + true + + + buttonGroupFonts + + + + + + + 100 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + 10 + + + + @@ -494,38 +609,28 @@ - - - - - true - - + + - Input - Controller - - - 5 + Refresh now - - - - - 0 - 0 - - + + - - - - true + Adventure Map Allies - + + + + Neutral AI in battles + + + + 10 @@ -556,14 +661,547 @@ - - + + - Downscaling Filter + - + + + + Display index + + + + + + + Use Relative Pointer Mode + + + + + + + + + + Autocombat AI in battles + + + + + + + true + + + + 0 + 0 + + + + Off + + + true + + + false + + + buttonGroupValidation + + + + + + + + true + + + + Input - Mouse + + + 5 + + + + + + + Network port + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + true + + + + Audio + + + 5 + + + + + + + Additional repository + + + + + + + + 0 + 0 + + + + VSync + + + true + + + + + + + Adventure Map Enemies + + + + + + + Framerate Limit + + + + + + + Use scalable fonts + + + + + + + Renderer + + + + + + + + true + + + + Input - Touchscreen + + + 5 + + + + + + + 25 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + 5 + + + + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + VCMI Language + + + + + + + Online Lobby address + + + + + + + Online Lobby port + + + + + + + Controller Click Tolerance + + + + + + + BattleAI + + + + BattleAI + + + + + StupidAI + + + + + + + + + Nearest + + + + + Linear + + + + + Automatic (Linear) + + + + + + + + Ignore SSL errors + + + + + + + + + + Show Tutorial again + + + + + + + Sticks Acceleration + + + + + + + + true + + + + Video + + + 5 + + + + + + + false + + + BattleAI + + + + BattleAI + + + + + StupidAI + + + + + + + + + + + + + + + Fullscreen + + + + + + + Mouse Click Tolerance + + + + + + + Enemy AI in battles + + + + + + + Autosave limit (0 = off) + + + + + + + 0 + + + 50 + + + 1 + + + 10 + + + 0 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + 10 + + + + + + + Interface Scaling + + + + + + + Music Volume + + + + + + + false + + + BattleAI + + + + BattleAI + + + + + StupidAI + + + + + + + + + + + + + + + Cursor Scaling + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + 0 + 0 + + + + Scalable + + + true + + + true + + + buttonGroupFonts + + + + + + + + true + + + + Network + + + 5 + + + + + + + + true + + + + Miscellaneous + + + 5 + + + + + + + 100 + + + 500 + + + 10 + + + 100 + + + 100 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + 50 + + + + Select display mode for game @@ -594,14 +1232,155 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use - - - - Use scalable fonts + + + + 20 + + + 1000 + + + 10 - + + + + Long Touch Duration + + + + + + + Check on startup + + + + + + + 0 + + + 50 + + + 1 + + + 10 + + + 0 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + 10 + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + 100 + + + 300 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + 25 + + + + + + + Font Scaling (experimental) + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + 0 + + + 50 + + + 1 + + + 10 + + + 0 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + 10 + + + + + + + empty = map name prefix + + + + @@ -623,47 +1402,8 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use - - - - - - - - Automatic - - - - - None - - - - - xBRZ x2 - - - - - xBRZ x3 - - - - - xBRZ x4 - - - - - - - - Online Lobby port - - - - - + + 0 @@ -671,283 +1411,27 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use - + Automatic true - - - - VCAI - - - - VCAI - - - - - Nullkiller - - - - - - - - - true - - + + - Network - - - 5 + Sticks Sensitivity - - + + - Refresh now + Upscaling Filter - - - - 500 - - - 2500 - - - 25 - - - 250 - - - 500 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - 250 - - - - - - - Display index - - - - - - - 500 - - - 2000 - - - 250 - - - 250 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - 250 - - - - - - - false - - - BattleAI - - - - BattleAI - - - - - StupidAI - - - - - - - - Adventure Map Enemies - - - - - - - - - - true - - - - - - - Reset - - - - - - - Show Tutorial again - - - - - - - Fullscreen - - - - - - - Use Relative Pointer Mode - - - - - - - - - - - - - - Sticks Acceleration - - - - - - - Autosave limit (0 = off) - - - - - - - Mouse Click Tolerance - - - - - - - - true - - - - General - - - 5 - - - - - - - Online Lobby address - - - - - - - - 0 - 0 - - - - - - - true - - - - - - - Interface Scaling - - - - - - - Default repository - - - - - - - - 0 - 0 - - - - - - - true - - - - - - - Haptic Feedback - - - - - - @@ -955,449 +1439,15 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use - - - - - true - - - - Input - Mouse - - - 5 - - - - - - - 25 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - 5 - - - - - - - 20 - - - 1000 - - - 10 - - - - - - - 1024 - - - 65535 - - - 3030 - - - - - - - 100 - - - 300 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - 25 - - - - - - - Enemy AI in battles - - - - - - Reserved screen area - - - - - - - % - - - 50 - - - 400 - - - 10 - - - - - - - Music Volume - - - - - - - BattleAI - - - - BattleAI - - - - - StupidAI - - - - - - - - Autosave - - - - - - - 100 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - 10 - - - - - - - Network port - - - - - - - true - - - - 0 - 0 - - - - - - - true - - - false - - - - Resolution - - - - - 0 - 0 - - - - - - - true - - - - - - - Sound Volume - - - - - - - Controller Click Tolerance - - - - - - - Adventure Map Allies - - - - - - - - true - - - - Miscellaneous - - - 5 - - - - - - - 1024 - - - 65535 - - - 3030 - - - - - - - VCAI - - - - VCAI - - - - - Nullkiller - - - - - - - - - true - - - - Audio - - - 5 - - - - - - - - 0 - 0 - - - - - - - true - - - - - - - Software Cursor - - - - - - - Autocombat AI in battles - - - - - - - - - - - - - - - - - true - - - - 0 - 0 - - - - - - - true - - - false - - - - - - - Font Scaling (experimental) - - - - - - - Sticks Sensitivity - - - - - - - Mods Validation - - - - - - - true - - - - 0 - 0 - - - - Off - - - true - - - false - - - buttonGroupValidation - - - - - - - true - - - - 0 - 0 - - - - Basic - - - true - - - false - - - buttonGroupValidation - - - - - - - true - - - - 0 - 0 - - - - Full - - - true - - - false - - - buttonGroupValidation - - + + diff --git a/lib/BasicTypes.cpp b/lib/BasicTypes.cpp index a294a14a2..fecc59c7d 100644 --- a/lib/BasicTypes.cpp +++ b/lib/BasicTypes.cpp @@ -38,7 +38,7 @@ TerrainId AFactionMember::getNativeTerrain() const //this code is used in the CreatureTerrainLimiter::limit to setup battle bonuses //and in the CGHeroInstance::getNativeTerrain() to setup movement bonuses or/and penalties. return getBonusBearer()->hasBonus(selectorNoTerrainPenalty, cachingStringNoTerrainPenalty) - ? TerrainId::ANY_TERRAIN : VLC->factions()->getById(getFaction())->getNativeTerrain(); + ? TerrainId::ANY_TERRAIN : getFactionID().toEntity(VLC)->getNativeTerrain(); } int32_t AFactionMember::magicResistance() const diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index f34608fd2..2eed440ed 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -117,7 +117,7 @@ int32_t CCreature::getHorde() const return hordeGrowth; } -FactionID CCreature::getFaction() const +FactionID CCreature::getFactionID() const { return FactionID(faction); } diff --git a/lib/CCreatureHandler.h b/lib/CCreatureHandler.h index 7b09b8d65..0e6c8011b 100644 --- a/lib/CCreatureHandler.h +++ b/lib/CCreatureHandler.h @@ -127,7 +127,7 @@ public: std::string getNamePluralTextID() const override; std::string getNameSingularTextID() const override; - FactionID getFaction() const override; + FactionID getFactionID() const override; int32_t getIndex() const override; int32_t getIconIndex() const override; std::string getJsonKey() const override; diff --git a/lib/CCreatureSet.cpp b/lib/CCreatureSet.cpp index eb12d2db2..d11c6c527 100644 --- a/lib/CCreatureSet.cpp +++ b/lib/CCreatureSet.cpp @@ -926,10 +926,10 @@ void CStackInstance::serializeJson(JsonSerializeFormat & handler) } } -FactionID CStackInstance::getFaction() const +FactionID CStackInstance::getFactionID() const { if(type) - return type->getFaction(); + return type->getFactionID(); return FactionID::NEUTRAL; } diff --git a/lib/CCreatureSet.h b/lib/CCreatureSet.h index 51b2c8e92..ba5f708bb 100644 --- a/lib/CCreatureSet.h +++ b/lib/CCreatureSet.h @@ -106,7 +106,7 @@ public: //IConstBonusProvider const IBonusBearer* getBonusBearer() const override; //INativeTerrainProvider - FactionID getFaction() const override; + FactionID getFactionID() const override; virtual ui64 getPower() const; /// Returns total market value of resources needed to recruit this unit diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index 8e404e34c..b6017df9d 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -381,7 +381,7 @@ bool CGameInfoCallback::getHeroInfo(const CGObjectInstance * hero, InfoAboutHero for(const auto & creature : VLC->creh->objects) { - if(creature->getFaction() == factionIndex && static_cast(creature->getAIValue()) > maxAIValue) + if(creature->getFactionID() == factionIndex && static_cast(creature->getAIValue()) > maxAIValue) { maxAIValue = creature->getAIValue(); mostStrong = creature.get(); @@ -539,7 +539,7 @@ EDiggingStatus CGameInfoCallback::getTileDigStatus(int3 tile, bool verbose) cons for(const auto & object : gs->map->objects) { - if(object && object->ID == Obj::HOLE && object->pos == tile) + if(object && object->ID == Obj::HOLE && object->anchorPos() == tile) return EDiggingStatus::TILE_OCCUPIED; } return getTile(tile)->getDiggingStatus(); @@ -575,10 +575,10 @@ EBuildingState CGameInfoCallback::canBuildStructure( const CGTownInstance *t, Bu { ERROR_RET_VAL_IF(!canGetFullInfo(t), "Town is not owned!", EBuildingState::TOWN_NOT_OWNED); - if(!t->town->buildings.count(ID)) + if(!t->getTown()->buildings.count(ID)) return EBuildingState::BUILDING_ERROR; - const CBuilding * building = t->town->buildings.at(ID); + const CBuilding * building = t->getTown()->buildings.at(ID); if(t->hasBuilt(ID)) //already built diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 7bfb5225c..37cb28106 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -559,6 +559,7 @@ set(lib_MAIN_HEADERS networkPacks/PacksForServer.h networkPacks/SetRewardableConfiguration.h networkPacks/SetStackEffect.h + networkPacks/SaveLocalState.h networkPacks/StackLocation.h networkPacks/TradeItem.h diff --git a/lib/CPlayerState.cpp b/lib/CPlayerState.cpp index 2242d5381..2660377c6 100644 --- a/lib/CPlayerState.cpp +++ b/lib/CPlayerState.cpp @@ -10,6 +10,7 @@ #include "StdInc.h" #include "CPlayerState.h" +#include "json/JsonNode.h" #include "mapObjects/CGDwelling.h" #include "mapObjects/CGTownInstance.h" #include "mapObjects/CGHeroInstance.h" @@ -20,8 +21,13 @@ VCMI_LIB_NAMESPACE_BEGIN PlayerState::PlayerState() - : color(-1), human(false), cheated(false), enteredWinningCheatCode(false), - enteredLosingCheatCode(false), status(EPlayerStatus::INGAME) + : color(-1) + , playerLocalSettings(std::make_unique()) + , human(false) + , cheated(false) + , enteredWinningCheatCode(false) + , enteredLosingCheatCode(false) + , status(EPlayerStatus::INGAME) { setNodeType(PLAYER); } diff --git a/lib/CPlayerState.h b/lib/CPlayerState.h index 96a0ba790..411b0945f 100644 --- a/lib/CPlayerState.h +++ b/lib/CPlayerState.h @@ -16,7 +16,6 @@ #include "bonuses/CBonusSystemNode.h" #include "ResourceSet.h" #include "TurnTimerInfo.h" -#include "ConstTransitivePtr.h" VCMI_LIB_NAMESPACE_BEGIN @@ -66,6 +65,7 @@ public: std::vector quests; //store info about all received quests std::vector battleBonuses; //additional bonuses to be added during battle with neutrals std::map> costumesArtifacts; + std::unique_ptr playerLocalSettings; // Json with client-defined data, such as order of heroes or current hero paths. Not used by client/lib bool cheated; bool enteredWinningCheatCode, enteredLosingCheatCode; //if true, this player has entered cheat codes for loss / victory @@ -116,6 +116,9 @@ public: h & status; h & turnTimer; + if (h.version >= Handler::Version::LOCAL_PLAYER_STATE_DATA) + h & *playerLocalSettings; + if (h.version >= Handler::Version::PLAYER_STATE_OWNED_OBJECTS) { h & ownedObjects; diff --git a/lib/battle/CUnitState.cpp b/lib/battle/CUnitState.cpp index 131fc2a23..737a9813f 100644 --- a/lib/battle/CUnitState.cpp +++ b/lib/battle/CUnitState.cpp @@ -416,9 +416,9 @@ int32_t CUnitState::creatureIconIndex() const return unitType()->getIconIndex(); } -FactionID CUnitState::getFaction() const +FactionID CUnitState::getFactionID() const { - return unitType()->getFaction(); + return unitType()->getFactionID(); } int32_t CUnitState::getCasterUnitId() const diff --git a/lib/battle/CUnitState.h b/lib/battle/CUnitState.h index b5451ba89..9bb9570a1 100644 --- a/lib/battle/CUnitState.h +++ b/lib/battle/CUnitState.h @@ -253,7 +253,7 @@ public: void localInit(const IUnitEnvironment * env_); void serializeJson(JsonSerializeFormat & handler); - FactionID getFaction() const override; + FactionID getFactionID() const override; void afterAttack(bool ranged, bool counter); diff --git a/lib/bonuses/Limiters.cpp b/lib/bonuses/Limiters.cpp index 4f1786a20..914081139 100644 --- a/lib/bonuses/Limiters.cpp +++ b/lib/bonuses/Limiters.cpp @@ -300,15 +300,15 @@ ILimiter::EDecision FactionLimiter::limit(const BonusLimitationContext &context) if(bearer) { if(faction != FactionID::DEFAULT) - return bearer->getFaction() == faction ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD; + return bearer->getFactionID() == faction ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD; switch(context.b.source) { case BonusSource::CREATURE_ABILITY: - return bearer->getFaction() == context.b.sid.as().toCreature()->getFaction() ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD; + return bearer->getFactionID() == context.b.sid.as().toCreature()->getFactionID() ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD; case BonusSource::TOWN_STRUCTURE: - return bearer->getFaction() == context.b.sid.as().getFaction() ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD; + return bearer->getFactionID() == context.b.sid.as().getFaction() ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD; //TODO: other sources of bonuses } diff --git a/lib/campaign/CampaignState.cpp b/lib/campaign/CampaignState.cpp index 37ae497ef..5dc6a2e10 100644 --- a/lib/campaign/CampaignState.cpp +++ b/lib/campaign/CampaignState.cpp @@ -351,14 +351,14 @@ void CampaignState::setCurrentMapAsConquered(std::vector heroe { JsonNode node = CampaignState::crossoverSerialize(hero); - if (reservedHeroes.count(hero->getHeroType())) + if (reservedHeroes.count(hero->getHeroTypeID())) { - logGlobal->info("Hero crossover: %d (%s) exported to global pool", hero->getHeroType(), hero->getNameTranslated()); - globalHeroPool[hero->getHeroType()] = node; + logGlobal->info("Hero crossover: %d (%s) exported to global pool", hero->getHeroTypeID(), hero->getNameTranslated()); + globalHeroPool[hero->getHeroTypeID()] = node; } else { - logGlobal->info("Hero crossover: %d (%s) exported to scenario pool", hero->getHeroType(), hero->getNameTranslated()); + logGlobal->info("Hero crossover: %d (%s) exported to scenario pool", hero->getHeroTypeID(), hero->getNameTranslated()); scenarioHeroPool[*currentMap].push_back(node); } } diff --git a/lib/entities/faction/CFaction.cpp b/lib/entities/faction/CFaction.cpp index 4ed5d1ad2..3ef88afae 100644 --- a/lib/entities/faction/CFaction.cpp +++ b/lib/entities/faction/CFaction.cpp @@ -92,7 +92,7 @@ FactionID CFaction::getId() const return FactionID(index); } -FactionID CFaction::getFaction() const +FactionID CFaction::getFactionID() const { return FactionID(index); } diff --git a/lib/entities/faction/CFaction.h b/lib/entities/faction/CFaction.h index 294a77529..27d6a2239 100644 --- a/lib/entities/faction/CFaction.h +++ b/lib/entities/faction/CFaction.h @@ -39,7 +39,7 @@ class DLL_LINKAGE CFaction : public Faction FactionID index = FactionID::NEUTRAL; - FactionID getFaction() const override; //This function should not be used + FactionID getFactionID() const override; //This function should not be used public: TerrainId nativeTerrain; diff --git a/lib/entities/faction/CTownHandler.cpp b/lib/entities/faction/CTownHandler.cpp index 3ed640afc..09a99ed71 100644 --- a/lib/entities/faction/CTownHandler.cpp +++ b/lib/entities/faction/CTownHandler.cpp @@ -883,8 +883,8 @@ void CTownHandler::beforeValidate(JsonNode & object) if (building.second.Struct().count("onVisitBonuses")) { building.second["configuration"]["visitMode"] = JsonNode("bonus"); - building.second["configuration"]["visitMode"]["rewards"][0]["message"] = building.second["description"]; - building.second["configuration"]["visitMode"]["rewards"][0]["bonuses"] = building.second["onVisitBonuses"]; + building.second["configuration"]["rewards"][0]["message"] = building.second["description"]; + building.second["configuration"]["rewards"][0]["bonuses"] = building.second["onVisitBonuses"]; } } } diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index e35432cef..7bba1770d 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -449,7 +449,7 @@ void CGameState::initGrailPosition() //remove tiles with holes for(auto & elem : map->objects) if(elem && elem->ID == Obj::HOLE) - allowedPos -= elem->pos; + allowedPos -= elem->anchorPos(); if(!allowedPos.empty()) { @@ -495,7 +495,7 @@ void CGameState::randomizeMapObjects() { for (int j = 0; j < object->getHeight() ; j++) { - int3 pos = object->pos - int3(i,j,0); + int3 pos = object->anchorPos() - int3(i,j,0); if(map->isInTheMap(pos)) map->getTile(pos).extTileFlags |= 128; } } @@ -530,7 +530,7 @@ void CGameState::placeStartingHero(const PlayerColor & playerColor, const HeroTy { for(auto town : map->towns) { - if(town->getPosition() == townPos) + if(town->anchorPos() == townPos) { townPos = town->visitablePos(); break; @@ -545,8 +545,7 @@ void CGameState::placeStartingHero(const PlayerColor & playerColor, const HeroTy hero->setHeroType(heroTypeId); hero->tempOwner = playerColor; - hero->pos = townPos; - hero->pos += hero->getVisitableOffset(); + hero->setAnchorPos(townPos + hero->getVisitableOffset()); map->getEditManager()->insertObject(hero); } @@ -600,7 +599,7 @@ void CGameState::initHeroes() } hero->initHero(getRandomGenerator()); - map->allHeroes[hero->getHeroType().getNum()] = hero; + map->allHeroes[hero->getHeroTypeID().getNum()] = hero; } // generate boats for all heroes on water @@ -614,7 +613,7 @@ void CGameState::initHeroes() auto boat = dynamic_cast(handler->create(callback, nullptr)); handler->configureObject(boat, gs->getRandomGenerator()); - boat->pos = hero->pos; + boat->setAnchorPos(hero->anchorPos()); boat->appearance = handler->getTemplates().front(); boat->id = ObjectInstanceID(static_cast(gs->map->objects.size())); @@ -630,20 +629,20 @@ void CGameState::initHeroes() { auto * hero = dynamic_cast(obj.get()); hero->initHero(getRandomGenerator()); - map->allHeroes[hero->getHeroType().getNum()] = hero; + map->allHeroes[hero->getHeroTypeID().getNum()] = hero; } } std::set heroesToCreate = getUnusedAllowedHeroes(); //ids of heroes to be created and put into the pool for(auto ph : map->predefinedHeroes) { - if(!vstd::contains(heroesToCreate, ph->getHeroType())) + if(!vstd::contains(heroesToCreate, ph->getHeroTypeID())) continue; ph->initHero(getRandomGenerator()); heroesPool->addHeroToPool(ph); - heroesToCreate.erase(ph->type->getId()); + heroesToCreate.erase(ph->getHeroTypeID()); - map->allHeroes[ph->getHeroType().getNum()] = ph; + map->allHeroes[ph->getHeroTypeID().getNum()] = ph; } for(const HeroTypeID & htype : heroesToCreate) //all not used allowed heroes go with default state into the pool @@ -757,12 +756,12 @@ void CGameState::initTownNames() for(auto & vti : map->towns) { - assert(vti->town); + assert(vti->getTown()); if(!vti->getNameTextID().empty()) continue; - FactionID faction = vti->getFaction(); + FactionID faction = vti->getFactionID(); if(availableNames.empty()) { @@ -799,8 +798,8 @@ void CGameState::initTowns() for (auto & vti : map->towns) { - assert(vti->town); - assert(vti->town->creatures.size() <= GameConstants::CREATURES_PER_TOWN); + assert(vti->getTown()); + assert(vti->getTown()->creatures.size() <= GameConstants::CREATURES_PER_TOWN); constexpr std::array basicDwellings = { BuildingID::DWELL_FIRST, BuildingID::DWELL_LVL_2, BuildingID::DWELL_LVL_3, BuildingID::DWELL_LVL_4, BuildingID::DWELL_LVL_5, BuildingID::DWELL_LVL_6, BuildingID::DWELL_LVL_7, BuildingID::DWELL_LVL_8 }; constexpr std::array upgradedDwellings = { BuildingID::DWELL_UP_FIRST, BuildingID::DWELL_LVL_2_UP, BuildingID::DWELL_LVL_3_UP, BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_UP, BuildingID::DWELL_LVL_7_UP, BuildingID::DWELL_LVL_8_UP }; @@ -829,7 +828,7 @@ void CGameState::initTowns() vti->addBuilding(BuildingID::VILLAGE_HALL); //init hordes - for (int i = 0; i < vti->town->creatures.size(); i++) + for (int i = 0; i < vti->getTown()->creatures.size(); i++) { if(vti->hasBuilt(hordes[i])) //if we have horde for this level { @@ -895,7 +894,7 @@ void CGameState::initTowns() int sel = -1; for(ui32 ps=0;pspossibleSpells.size();ps++) - total += vti->possibleSpells[ps].toSpell()->getProbability(vti->getFaction()); + total += vti->possibleSpells[ps].toSpell()->getProbability(vti->getFactionID()); if (total == 0) // remaining spells have 0 probability break; @@ -903,7 +902,7 @@ void CGameState::initTowns() auto r = getRandomGenerator().nextInt(total - 1); for(ui32 ps=0; pspossibleSpells.size();ps++) { - r -= vti->possibleSpells[ps].toSpell()->getProbability(vti->getFaction()); + r -= vti->possibleSpells[ps].toSpell()->getProbability(vti->getFactionID()); if(r<0) { sel = ps; @@ -964,22 +963,18 @@ void CGameState::placeHeroesInTowns() { for(CGTownInstance * t : player.second.getTowns()) { - if(h->visitablePos().z != t->visitablePos().z) - continue; - - bool heroOnTownBlockableTile = t->blockingAt(h->visitablePos().x, h->visitablePos().y); + bool heroOnTownBlockableTile = t->blockingAt(h->visitablePos()); // current hero position is at one of blocking tiles of current town // assume that this hero should be visiting the town (H3M format quirk) and move hero to correct position if (heroOnTownBlockableTile) { - int3 correctedPos = h->convertFromVisitablePos(t->visitablePos()); - map->removeBlockVisTiles(h); - h->pos = correctedPos; + int3 correctedPos = h->convertFromVisitablePos(t->visitablePos()); + h->setAnchorPos(correctedPos); map->addBlockVisTiles(h); - assert(t->visitableAt(h->visitablePos().x, h->visitablePos().y)); + assert(t->visitableAt(h->visitablePos())); } } } @@ -1001,7 +996,7 @@ void CGameState::initVisitingAndGarrisonedHeroes() if(h->visitablePos().z != t->visitablePos().z) continue; - if (t->visitableAt(h->visitablePos().x, h->visitablePos().y)) + if (t->visitableAt(h->visitablePos())) { assert(t->visitingHero == nullptr); t->setVisitingHero(h); @@ -1066,7 +1061,7 @@ BattleField CGameState::battleGetBattlefieldType(int3 tile, vstd::RNG & rand) for(auto &obj : map->objects) { //look only for objects covering given tile - if( !obj || obj->pos.z != tile.z || !obj->coveringAt(tile.x, tile.y)) + if( !obj || !obj->coveringAt(tile)) continue; auto customBattlefield = obj->getBattlefield(); @@ -1250,10 +1245,10 @@ bool CGameState::isVisible(const CGObjectInstance * obj, const std::optionalgetWidth(); ++fx) { - int3 pos = obj->pos + int3(-fx, -fy, 0); + int3 pos = obj->anchorPos() + int3(-fx, -fy, 0); if ( map->isInTheMap(pos) && - obj->coveringAt(pos.x, pos.y) && + obj->coveringAt(pos) && isVisible(pos, *player)) return true; } @@ -1660,18 +1655,13 @@ std::set CGameState::getUnusedAllowedHeroes(bool alsoIncludeNotAllow } for(auto hero : map->heroesOnMap) //heroes instances initialization - { - if(hero->type) - ret -= hero->type->getId(); - else - ret -= hero->getHeroType(); - } + ret -= hero->getHeroTypeID(); for(auto obj : map->objects) //prisons { auto * hero = dynamic_cast(obj.get()); if(hero && hero->ID == Obj::PRISON) - ret -= hero->getHeroType(); + ret -= hero->getHeroTypeID(); } return ret; @@ -1695,7 +1685,7 @@ CGHeroInstance * CGameState::getUsedHero(const HeroTypeID & hid) const auto * hero = dynamic_cast(obj.get()); assert(hero); - if (hero->getHeroType() == hid) + if (hero->getHeroTypeID() == hid) return hero; } diff --git a/lib/gameState/CGameStateCampaign.cpp b/lib/gameState/CGameStateCampaign.cpp index 703f97667..2178da654 100644 --- a/lib/gameState/CGameStateCampaign.cpp +++ b/lib/gameState/CGameStateCampaign.cpp @@ -86,7 +86,7 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(const CampaignTravel & tr .And(Selector::subtype()(BonusSubtypeID(g))) .And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL)); - hero.hero->getLocalBonus(sel)->val = hero.hero->type->heroClass->primarySkillInitial[g.getNum()]; + hero.hero->getLocalBonus(sel)->val = hero.hero->getHeroClass()->primarySkillInitial[g.getNum()]; } } } @@ -96,7 +96,7 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(const CampaignTravel & tr //trimming sec skills for(auto & hero : campaignHeroReplacements) { - hero.hero->secSkills = hero.hero->type->secSkillsInit; + hero.hero->secSkills = hero.hero->getHeroType()->secSkillsInit; hero.hero->recreateSecondarySkillsBonuses(); } } @@ -240,7 +240,7 @@ void CGameStateCampaign::placeCampaignHeroes() for(auto & replacement : campaignHeroReplacements) if (replacement.heroPlaceholderId.hasValue()) - heroesToRemove.insert(replacement.hero->getHeroType()); + heroesToRemove.insert(replacement.hero->getHeroTypeID()); for(auto & heroID : heroesToRemove) { @@ -368,9 +368,9 @@ void CGameStateCampaign::replaceHeroesPlaceholders() heroToPlace->id = campaignHeroReplacement.heroPlaceholderId; if(heroPlaceholder->tempOwner.isValidPlayer()) heroToPlace->tempOwner = heroPlaceholder->tempOwner; - heroToPlace->pos = heroPlaceholder->pos; - heroToPlace->type = heroToPlace->getHeroType().toHeroType(); - heroToPlace->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, heroToPlace->type->heroClass->getIndex())->getTemplates().front(); + heroToPlace->setAnchorPos(heroPlaceholder->anchorPos()); + heroToPlace->setHeroType(heroToPlace->getHeroTypeID()); + heroToPlace->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, heroToPlace->getHeroTypeID())->getTemplates().front(); gameState->map->removeBlockVisTiles(heroPlaceholder, true); gameState->map->objects[heroPlaceholder->id.getNum()] = nullptr; @@ -563,7 +563,7 @@ void CGameStateCampaign::initHeroes() { for (auto & hero : heroes) { - if (hero->getHeroType().getNum() == chosenBonus->info1) + if (hero->getHeroTypeID().getNum() == chosenBonus->info1) { giveCampaignBonusToHero(hero); break; @@ -655,14 +655,14 @@ void CGameStateCampaign::initTowns() if (!owner->human) continue; - if (town->pos != pi.posOfMainTown) + if (town->anchorPos() != pi.posOfMainTown) continue; BuildingID newBuilding; if(gameState->scenarioOps->campState->formatVCMI()) newBuilding = BuildingID(chosenBonus->info1); else - newBuilding = CBuildingHandler::campToERMU(chosenBonus->info1, town->getFaction(), town->getBuildings()); + newBuilding = CBuildingHandler::campToERMU(chosenBonus->info1, town->getFactionID(), town->getBuildings()); // Build granted building & all prerequisites - e.g. Mages Guild Lvl 3 should also give Mages Guild Lvl 1 & 2 while(true) @@ -675,7 +675,7 @@ void CGameStateCampaign::initTowns() town->addBuilding(newBuilding); - auto building = town->town->buildings.at(newBuilding); + auto building = town->getTown()->buildings.at(newBuilding); newBuilding = building->upgrade; } break; diff --git a/lib/gameState/GameStatistics.cpp b/lib/gameState/GameStatistics.cpp index 237da8f54..6e1c95e85 100644 --- a/lib/gameState/GameStatistics.cpp +++ b/lib/gameState/GameStatistics.cpp @@ -381,7 +381,7 @@ float Statistic::getTownBuiltRatio(const PlayerState * ps) for(const auto & t : ps->getTowns()) { built += t->getBuildings().size(); - for(const auto & b : t->town->buildings) + for(const auto & b : t->getTown()->buildings) if(!t->forbiddenBuildings.count(b.first)) total += 1; } diff --git a/lib/gameState/InfoAboutArmy.cpp b/lib/gameState/InfoAboutArmy.cpp index cc3bdc205..cb6ad5e95 100644 --- a/lib/gameState/InfoAboutArmy.cpp +++ b/lib/gameState/InfoAboutArmy.cpp @@ -115,7 +115,7 @@ void InfoAboutHero::initFromHero(const CGHeroInstance *h, InfoAboutHero::EInfoLe initFromArmy(h, detailed); - hclass = h->type->heroClass; + hclass = h->getHeroClass(); name = h->getNameTranslated(); portraitSource = h->getPortraitSource(); diff --git a/lib/gameState/TavernHeroesPool.cpp b/lib/gameState/TavernHeroesPool.cpp index bafaf28b0..44d085c13 100644 --- a/lib/gameState/TavernHeroesPool.cpp +++ b/lib/gameState/TavernHeroesPool.cpp @@ -25,7 +25,7 @@ std::map TavernHeroesPool::unusedHeroesFromPool() c { std::map pool = heroesPool; for(const auto & slot : currentTavern) - pool.erase(slot.hero->getHeroType()); + pool.erase(slot.hero->getHeroTypeID()); return pool; } @@ -34,7 +34,7 @@ TavernSlotRole TavernHeroesPool::getSlotRole(HeroTypeID hero) const { for (auto const & slot : currentTavern) { - if (slot.hero->getHeroType() == hero) + if (slot.hero->getHeroTypeID() == hero) return slot.role; } return TavernSlotRole::NONE; @@ -106,7 +106,7 @@ CGHeroInstance * TavernHeroesPool::takeHeroFromPool(HeroTypeID hero) heroesPool.erase(hero); vstd::erase_if(currentTavern, [&](const TavernSlot & entry){ - return entry.hero->type->getId() == hero; + return entry.hero->getHeroTypeID() == hero; }); assert(result); @@ -138,7 +138,7 @@ void TavernHeroesPool::onNewDay() void TavernHeroesPool::addHeroToPool(CGHeroInstance * hero) { - heroesPool[hero->getHeroType()] = hero; + heroesPool[hero->getHeroTypeID()] = hero; } void TavernHeroesPool::setAvailability(HeroTypeID hero, std::set mask) diff --git a/lib/mapObjectConstructors/CObjectClassesHandler.cpp b/lib/mapObjectConstructors/CObjectClassesHandler.cpp index de28a4a0f..f8939be58 100644 --- a/lib/mapObjectConstructors/CObjectClassesHandler.cpp +++ b/lib/mapObjectConstructors/CObjectClassesHandler.cpp @@ -207,8 +207,13 @@ TObjectTypeHandler CObjectClassesHandler::loadSubObjectFromJson(const std::strin // Compatibility with 1.5 mods for 1.6. To be removed in 1.7 // Detect banks that use old format and load them using old bank hander - if (baseObject->id == Obj::CREATURE_BANK && entry.Struct().count("levels") && !entry.Struct().count("rewards")) - handler = "bank"; + if (baseObject->id == Obj::CREATURE_BANK) + { + if (entry.Struct().count("levels") && !entry.Struct().count("rewards")) + handler = "bank"; + else + handler = "configurable"; + } auto createdObject = handlerConstructors.at(handler)(); diff --git a/lib/mapObjectConstructors/CommonConstructors.cpp b/lib/mapObjectConstructors/CommonConstructors.cpp index d90ac8ef2..421d2565f 100644 --- a/lib/mapObjectConstructors/CommonConstructors.cpp +++ b/lib/mapObjectConstructors/CommonConstructors.cpp @@ -96,7 +96,6 @@ bool CTownInstanceConstructor::objectFilter(const CGObjectInstance * object, std void CTownInstanceConstructor::initializeObject(CGTownInstance * obj) const { - obj->town = faction->town; obj->tempOwner = PlayerColor::NEUTRAL; } @@ -144,7 +143,7 @@ bool CHeroInstanceConstructor::objectFilter(const CGObjectInstance * object, std auto heroTest = [&](const HeroTypeID & id) { - return hero->type->getId() == id; + return hero->getHeroTypeID() == id; }; if(filters.count(templ->stringID)) @@ -154,11 +153,6 @@ bool CHeroInstanceConstructor::objectFilter(const CGObjectInstance * object, std return false; } -void CHeroInstanceConstructor::initializeObject(CGHeroInstance * obj) const -{ - obj->type = nullptr; //FIXME: set to valid value. somehow. -} - void CHeroInstanceConstructor::randomizeObject(CGHeroInstance * object, vstd::RNG & rng) const { diff --git a/lib/mapObjectConstructors/CommonConstructors.h b/lib/mapObjectConstructors/CommonConstructors.h index d7ce61f5e..2089acdf7 100644 --- a/lib/mapObjectConstructors/CommonConstructors.h +++ b/lib/mapObjectConstructors/CommonConstructors.h @@ -81,7 +81,6 @@ public: const CHeroClass * heroClass = nullptr; std::map> filters; - void initializeObject(CGHeroInstance * object) const override; void randomizeObject(CGHeroInstance * object, vstd::RNG & rng) const override; void afterLoadFinalization() override; diff --git a/lib/mapObjects/CArmedInstance.cpp b/lib/mapObjects/CArmedInstance.cpp index 5a8b0cae8..23d026cd9 100644 --- a/lib/mapObjects/CArmedInstance.cpp +++ b/lib/mapObjects/CArmedInstance.cpp @@ -78,7 +78,7 @@ void CArmedInstance::updateMoraleBonusFromArmy() const CStackInstance * inst = slot.second; const auto * creature = inst->getCreatureID().toEntity(VLC); - factions.insert(creature->getFaction()); + factions.insert(creature->getFactionID()); // Check for undead flag instead of faction (undead mummies are neutral) if (!hasUndead) { diff --git a/lib/mapObjects/CGCreature.cpp b/lib/mapObjects/CGCreature.cpp index ffa7bf5a9..2d595bd5f 100644 --- a/lib/mapObjects/CGCreature.cpp +++ b/lib/mapObjects/CGCreature.cpp @@ -33,7 +33,7 @@ std::string CGCreature::getHoverText(PlayerColor player) const if(stacks.empty()) { //should not happen... - logGlobal->error("Invalid stack at tile %s: subID=%d; id=%d", pos.toString(), getCreature(), id.getNum()); + logGlobal->error("Invalid stack at tile %s: subID=%d; id=%d", anchorPos().toString(), getCreature(), id.getNum()); return "INVALID_STACK"; } @@ -45,7 +45,7 @@ std::string CGCreature::getHoverText(PlayerColor player) const else ms.appendLocalString(EMetaText::ARRAY_TXT, quantityTextIndex); ms.appendRawString(" "); - ms.appendNamePlural(getCreature()); + ms.appendNamePlural(getCreatureID()); return ms.toString(); } @@ -57,7 +57,7 @@ std::string CGCreature::getHoverText(const CGHeroInstance * hero) const MetaString ms; ms.appendNumber(stacks.begin()->second->count); ms.appendRawString(" "); - ms.appendName(getCreature(), stacks.begin()->second->count); + ms.appendName(getCreatureID(), stacks.begin()->second->count); return ms.toString(); } else @@ -69,11 +69,11 @@ std::string CGCreature::getHoverText(const CGHeroInstance * hero) const std::string CGCreature::getMonsterLevelText() const { std::string monsterLevel = VLC->generaltexth->translate("vcmi.adventureMap.monsterLevel"); - bool isRanged = VLC->creatures()->getById(getCreature())->getBonusBearer()->hasBonusOfType(BonusType::SHOOTER); + bool isRanged = getCreature()->getBonusBearer()->hasBonusOfType(BonusType::SHOOTER); std::string attackTypeKey = isRanged ? "vcmi.adventureMap.monsterRangedType" : "vcmi.adventureMap.monsterMeleeType"; std::string attackType = VLC->generaltexth->translate(attackTypeKey); - boost::replace_first(monsterLevel, "%TOWN", (*VLC->townh)[VLC->creatures()->getById(getCreature())->getFaction()]->getNameTranslated()); - boost::replace_first(monsterLevel, "%LEVEL", std::to_string(VLC->creatures()->getById(getCreature())->getLevel())); + boost::replace_first(monsterLevel, "%TOWN", getCreature()->getFactionID().toEntity(VLC)->getNameTranslated()); + boost::replace_first(monsterLevel, "%LEVEL", std::to_string(getCreature()->getLevel())); boost::replace_first(monsterLevel, "%ATTACK_TYPE", attackType); return monsterLevel; } @@ -150,7 +150,7 @@ std::string CGCreature::getPopupText(PlayerColor player) const std::vector CGCreature::getPopupComponents(PlayerColor player) const { return { - Component(ComponentType::CREATURE, getCreature()) + Component(ComponentType::CREATURE, getCreatureID()) }; } @@ -182,7 +182,7 @@ void CGCreature::onHeroVisit( const CGHeroInstance * h ) const BlockingDialog ynd(true,false); ynd.player = h->tempOwner; ynd.text.appendLocalString(EMetaText::ADVOB_TXT, 86); - ynd.text.replaceName(getCreature(), getStackCount(SlotID(0))); + ynd.text.replaceName(getCreatureID(), getStackCount(SlotID(0))); cb->showBlockingDialog(this, &ynd); break; } @@ -197,7 +197,7 @@ void CGCreature::onHeroVisit( const CGHeroInstance * h ) const std::string tmp = VLC->generaltexth->advobtxt[90]; boost::algorithm::replace_first(tmp, "%d", std::to_string(getStackCount(SlotID(0)))); boost::algorithm::replace_first(tmp, "%d", std::to_string(action)); - boost::algorithm::replace_first(tmp,"%s",VLC->creatures()->getById(getCreature())->getNamePluralTranslated()); + boost::algorithm::replace_first(tmp,"%s",getCreature()->getNamePluralTranslated()); ynd.text.appendRawString(tmp); cb->showBlockingDialog(this, &ynd); break; @@ -205,11 +205,16 @@ void CGCreature::onHeroVisit( const CGHeroInstance * h ) const } } -CreatureID CGCreature::getCreature() const +CreatureID CGCreature::getCreatureID() const { return CreatureID(getObjTypeIndex().getNum()); } +const CCreature * CGCreature::getCreature() const +{ + return getCreatureID().toCreature(); +} + void CGCreature::pickRandomObject(vstd::RNG & rand) { switch(ID.toEnum()) @@ -279,7 +284,7 @@ void CGCreature::initObj(vstd::RNG & rand) stacks[SlotID(0)]->setType(getCreature()); TQuantity &amount = stacks[SlotID(0)]->count; - const Creature * c = VLC->creatures()->getById(getCreature()); + const Creature * c = getCreature(); if(amount == 0) { amount = rand.nextInt(c->getAdvMapAmountMin(), c->getAdvMapAmountMax()); @@ -353,8 +358,8 @@ int CGCreature::takenAction(const CGHeroInstance *h, bool allowJoin) const for(const auto & elem : h->Slots()) { - bool isOurUpgrade = vstd::contains(getCreature().toCreature()->upgrades, elem.second->getCreatureID()); - bool isOurDowngrade = vstd::contains(elem.second->type->upgrades, getCreature()); + bool isOurUpgrade = vstd::contains(getCreature()->upgrades, elem.second->getCreatureID()); + bool isOurDowngrade = vstd::contains(elem.second->type->upgrades, getCreatureID()); if(isOurUpgrade || isOurDowngrade) count += elem.second->count; @@ -380,7 +385,7 @@ int CGCreature::takenAction(const CGHeroInstance *h, bool allowJoin) const if(diplomacy * 2 + sympathy + 1 >= character) { - int32_t recruitCost = VLC->creatures()->getById(getCreature())->getRecruitCost(EGameResID::GOLD); + int32_t recruitCost = getCreature()->getRecruitCost(EGameResID::GOLD); int32_t stackCount = getStackCount(SlotID(0)); return recruitCost * stackCount; //join for gold } @@ -493,7 +498,7 @@ void CGCreature::flee( const CGHeroInstance * h ) const BlockingDialog ynd(true,false); ynd.player = h->tempOwner; ynd.text.appendLocalString(EMetaText::ADVOB_TXT,91); - ynd.text.replaceName(getCreature(), getStackCount(SlotID(0))); + ynd.text.replaceName(getCreatureID(), getStackCount(SlotID(0))); cb->showBlockingDialog(this, &ynd); } @@ -513,7 +518,7 @@ void CGCreature::battleFinished(const CGHeroInstance *hero, const BattleResult & { //merge stacks into one TSlots::const_iterator i; - const CCreature * cre = getCreature().toCreature(); + const CCreature * cre = getCreature(); for(i = stacks.begin(); i != stacks.end(); i++) { if(cre->isMyUpgrade(i->second->type)) @@ -562,7 +567,7 @@ bool CGCreature::containsUpgradedStack() const float c = 5325.181015f; float d = 32788.727920f; - int val = static_cast(std::floor(a * pos.x + b * pos.y + c * pos.z + d)); + int val = static_cast(std::floor(a * visitablePos().x + b * visitablePos().y + c * visitablePos().z + d)); return ((val % 32768) % 100) < 50; } @@ -591,7 +596,7 @@ int CGCreature::getNumberOfStacks(const CGHeroInstance *hero) const ui32 c = 1943276003u; ui32 d = 3174620878u; - ui32 R1 = a * static_cast(pos.x) + b * static_cast(pos.y) + c * static_cast(pos.z) + d; + ui32 R1 = a * static_cast(visitablePos().x) + b * static_cast(visitablePos().y) + c * static_cast(visitablePos().z) + d; ui32 R2 = (R1 >> 16) & 0x7fff; int R4 = R2 % 100 + 1; diff --git a/lib/mapObjects/CGCreature.h b/lib/mapObjects/CGCreature.h index a8c32fb8b..daa554a2e 100644 --- a/lib/mapObjects/CGCreature.h +++ b/lib/mapObjects/CGCreature.h @@ -49,7 +49,8 @@ public: void newTurn(vstd::RNG & rand) const override; void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; void blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const override; - CreatureID getCreature() const; + CreatureID getCreatureID() const; + const CCreature * getCreature() const; //stack formation depends on position, bool containsUpgradedStack() const; diff --git a/lib/mapObjects/CGDwelling.cpp b/lib/mapObjects/CGDwelling.cpp index 51dd897e3..638480ed2 100644 --- a/lib/mapObjects/CGDwelling.cpp +++ b/lib/mapObjects/CGDwelling.cpp @@ -93,7 +93,7 @@ FactionID CGDwelling::randomizeFaction(vstd::RNG & rand) assert(linkedTown->ID == Obj::TOWN); if(linkedTown->ID==Obj::TOWN) - return linkedTown->getFaction(); + return linkedTown->getFactionID(); } if(!randomizationInfo->allowedFactions.empty()) diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index ddf3ca0b5..8d5d4b63f 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -116,9 +116,9 @@ ui32 CGHeroInstance::getTileMovementCost(const TerrainTile & dest, const Terrain return static_cast(ret); } -FactionID CGHeroInstance::getFaction() const +FactionID CGHeroInstance::getFactionID() const { - return FactionID(type->heroClass->faction); + return getHeroClass()->faction; } const IBonusBearer* CGHeroInstance::getBonusBearer() const @@ -229,10 +229,10 @@ bool CGHeroInstance::canLearnSkill(const SecondarySkill & which) const if (getSecSkillLevel(which) > 0) return false; - if (type->heroClass->secSkillProbability.count(which) == 0) + if (getHeroClass()->secSkillProbability.count(which) == 0) return false; - if (type->heroClass->secSkillProbability.at(which) == 0) + if (getHeroClass()->secSkillProbability.at(which) == 0) return false; return true; @@ -282,7 +282,6 @@ int CGHeroInstance::movementPointsLimitCached(bool onLand, const TurnInfo * ti) CGHeroInstance::CGHeroInstance(IGameCallback * cb) : CArmedInstance(cb), - type(nullptr), tacticFormationEnabled(false), inTownGarrison(false), moveDir(4), @@ -303,14 +302,30 @@ PlayerColor CGHeroInstance::getOwner() const return tempOwner; } -HeroTypeID CGHeroInstance::getHeroType() const +const CHeroClass * CGHeroInstance::getHeroClass() const { + return getHeroType()->heroClass; +} + +HeroClassID CGHeroInstance::getHeroClassID() const +{ + return getHeroType()->heroClass->getId(); +} + +const CHero * CGHeroInstance::getHeroType() const +{ + return getHeroTypeID().toHeroType(); +} + +HeroTypeID CGHeroInstance::getHeroTypeID() const +{ + if (ID == Obj::RANDOM_HERO) + return HeroTypeID::NONE; return HeroTypeID(getObjTypeIndex().getNum()); } void CGHeroInstance::setHeroType(HeroTypeID heroType) { - assert(type == nullptr); subID = heroType; } @@ -323,16 +338,14 @@ void CGHeroInstance::initHero(vstd::RNG & rand, const HeroTypeID & SUBID) void CGHeroInstance::initHero(vstd::RNG & rand) { assert(validTypes(true)); - if(!type) - type = getHeroType().toHeroType(); - + if (ID == Obj::HERO) - appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->getIndex())->getTemplates().front(); + appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, getHeroClass()->getIndex())->getTemplates().front(); if(!vstd::contains(spells, SpellID::PRESET)) { // hero starts with default spells - for(const auto & spellID : type->spells) + for(const auto & spellID : getHeroType()->spells) spells.insert(spellID); } else //remove placeholder @@ -341,7 +354,7 @@ void CGHeroInstance::initHero(vstd::RNG & rand) if(!vstd::contains(spells, SpellID::SPELLBOOK_PRESET)) { // hero starts with default spellbook presence status - if(!getArt(ArtifactPosition::SPELLBOOK) && type->haveSpellBook) + if(!getArt(ArtifactPosition::SPELLBOOK) && getHeroType()->haveSpellBook) { auto artifact = ArtifactUtils::createArtifact(ArtifactID::SPELLBOOK); putArtifact(ArtifactPosition::SPELLBOOK, artifact); @@ -360,14 +373,14 @@ void CGHeroInstance::initHero(vstd::RNG & rand) { for(int g=0; g(g), type->heroClass->primarySkillInitial[g]); + pushPrimSkill(static_cast(g), getHeroClass()->primarySkillInitial[g]); } } if(secSkills.size() == 1 && secSkills[0] == std::pair(SecondarySkill::NONE, -1)) //set secondary skills to default - secSkills = type->secSkillsInit; + secSkills = getHeroType()->secSkillsInit; if (gender == EHeroGender::DEFAULT) - gender = type->gender; + gender = getHeroType()->gender; setFormation(EArmyFormation::LOOSE); if (!stacksCount()) //standard army//initial army @@ -403,9 +416,9 @@ void CGHeroInstance::initHero(vstd::RNG & rand) addNewBonus(bonus); } - if (cb->getSettings().getBoolean(EGameSettings::MODULE_COMMANDERS) && !commander && type->heroClass->commander.hasValue()) + if (cb->getSettings().getBoolean(EGameSettings::MODULE_COMMANDERS) && !commander && getHeroClass()->commander.hasValue()) { - commander = new CCommanderInstance(type->heroClass->commander); + commander = new CCommanderInstance(getHeroClass()->commander); commander->setArmyObj (castToArmyObj()); //TODO: separate function for setting commanders commander->giveStackExp (exp); //after our exp is set } @@ -413,7 +426,7 @@ void CGHeroInstance::initHero(vstd::RNG & rand) skillsInfo = SecondarySkillsInfo(); //copy active (probably growing) bonuses from hero prototype to hero object - for(const std::shared_ptr & b : type->specialty) + for(const std::shared_ptr & b : getHeroType()->specialty) addNewBonus(b); //initialize bonuses @@ -433,14 +446,14 @@ void CGHeroInstance::initArmy(vstd::RNG & rand, IArmyDescriptor * dst) auto stacksCountChances = cb->getSettings().getVector(EGameSettings::HEROES_STARTING_STACKS_CHANCES); int stacksCountInitRandomNumber = rand.nextInt(1, 100); - size_t maxStacksCount = std::min(stacksCountChances.size(), type->initialArmy.size()); + size_t maxStacksCount = std::min(stacksCountChances.size(), getHeroType()->initialArmy.size()); for(int stackNo=0; stackNo < maxStacksCount; stackNo++) { if (stacksCountInitRandomNumber > stacksCountChances[stackNo]) continue; - auto & stack = type->initialArmy[stackNo]; + auto & stack = getHeroType()->initialArmy[stackNo]; int count = rand.nextInt(stack.minAmount, stack.maxAmount); @@ -588,11 +601,11 @@ std::string CGHeroInstance::getMovementPointsTextIfOwner(PlayerColor player) con ui8 CGHeroInstance::maxlevelsToMagicSchool() const { - return type->heroClass->isMagicHero() ? 3 : 4; + return getHeroClass()->isMagicHero() ? 3 : 4; } ui8 CGHeroInstance::maxlevelsToWisdom() const { - return type->heroClass->isMagicHero() ? 3 : 6; + return getHeroClass()->isMagicHero() ? 3 : 6; } CGHeroInstance::SecondarySkillsInfo::SecondarySkillsInfo(): @@ -617,11 +630,8 @@ void CGHeroInstance::pickRandomObject(vstd::RNG & rand) { ID = Obj::HERO; subID = cb->gameState()->pickNextHeroType(getOwner()); - type = getHeroType().toHeroType(); - randomizeArmy(type->heroClass->faction); + randomizeArmy(getHeroClass()->faction); } - else - type = getHeroType().toHeroType(); auto oldSubID = subID; @@ -629,7 +639,7 @@ void CGHeroInstance::pickRandomObject(vstd::RNG & rand) // after setType subID used to store unique hero identify id. Check issue 2277 for details // exclude prisons which should use appearance as set in map, via map editor or RMG if (ID != Obj::PRISON) - setType(ID, type->heroClass->getIndex()); + setType(ID, getHeroClass()->getIndex()); this->subID = oldSubID; } @@ -1064,7 +1074,7 @@ si32 CGHeroInstance::getManaNewTurn() const BoatId CGHeroInstance::getBoatType() const { - return BoatId(VLC->townh->getById(type->heroClass->faction)->getBoatType()); + return BoatId(VLC->townh->getById(getHeroClass()->faction)->getBoatType()); } void CGHeroInstance::getOutOffsets(std::vector &offsets) const @@ -1103,7 +1113,7 @@ void CGHeroInstance::pushPrimSkill( PrimarySkill which, int val ) EAlignment CGHeroInstance::getAlignment() const { - return type->heroClass->getAlignment(); + return getHeroClass()->getAlignment(); } void CGHeroInstance::initExp(vstd::RNG & rand) @@ -1127,12 +1137,12 @@ HeroTypeID CGHeroInstance::getPortraitSource() const if (customPortraitSource.isValid()) return customPortraitSource; else - return getHeroType(); + return getHeroTypeID(); } int32_t CGHeroInstance::getIconIndex() const { - return VLC->heroTypes()->getById(getPortraitSource())->getIconIndex(); + return getPortraitSource().toEntity(VLC)->getIconIndex(); } std::string CGHeroInstance::getNameTranslated() const @@ -1149,15 +1159,15 @@ std::string CGHeroInstance::getClassNameTextID() const { if (isCampaignGem()) return "core.genrltxt.735"; - return type->heroClass->getNameTextID(); + return getHeroClass()->getNameTextID(); } std::string CGHeroInstance::getNameTextID() const { if (!nameCustomTextId.empty()) return nameCustomTextId; - if (type) - return type->getNameTextID(); + if (getHeroTypeID().hasValue()) + return getHeroType()->getNameTextID(); // FIXME: called by logging from some specialties (mods?) before type is set on deserialization // assert(0); @@ -1173,8 +1183,8 @@ std::string CGHeroInstance::getBiographyTextID() const { if (!biographyCustomTextId.empty()) return biographyCustomTextId; - if (type) - return type->getBiographyTextID(); + if (getHeroTypeID().hasValue()) + return getHeroType()->getBiographyTextID(); return ""; //for random hero } @@ -1372,11 +1382,11 @@ std::vector CGHeroInstance::getLevelUpProposedSecondarySkills(vs SecondarySkill selection; if (selectWisdom) - selection = type->heroClass->chooseSecSkill(intersect(options, wisdomList), rand); + selection = getHeroClass()->chooseSecSkill(intersect(options, wisdomList), rand); else if (selectSchool) - selection = type->heroClass->chooseSecSkill(intersect(options, schoolList), rand); + selection = getHeroClass()->chooseSecSkill(intersect(options, schoolList), rand); else - selection = type->heroClass->chooseSecSkill(options, rand); + selection = getHeroClass()->chooseSecSkill(options, rand); skills.push_back(selection); options.erase(selection); @@ -1407,7 +1417,7 @@ PrimarySkill CGHeroInstance::nextPrimarySkill(vstd::RNG & rand) const { assert(gainsLevel()); const auto isLowLevelHero = level < GameConstants::HERO_HIGH_LEVEL; - const auto & skillChances = isLowLevelHero ? type->heroClass->primarySkillLowLevel : type->heroClass->primarySkillHighLevel; + const auto & skillChances = isLowLevelHero ? getHeroClass()->primarySkillLowLevel : getHeroClass()->primarySkillHighLevel; if (isCampaignYog()) { @@ -1537,35 +1547,25 @@ bool CGHeroInstance::hasVisions(const CGObjectInstance * target, BonusSubtypeID if (visionsMultiplier > 0) vstd::amax(visionsRange, 3); //minimum range is 3 tiles, but only if VISIONS bonus present - const int distance = static_cast(target->pos.dist2d(visitablePos())); + const int distance = static_cast(target->anchorPos().dist2d(visitablePos())); //logGlobal->debug(boost::str(boost::format("Visions: dist %d, mult %d, range %d") % distance % visionsMultiplier % visionsRange)); - return (distance < visionsRange) && (target->pos.z == pos.z); + return (distance < visionsRange) && (target->anchorPos().z == anchorPos().z); } std::string CGHeroInstance::getHeroTypeName() const { if(ID == Obj::HERO || ID == Obj::PRISON) - { - if(type) - { - return type->getJsonKey(); - } - else - { - return getHeroType().toEntity(VLC)->getJsonKey(); - } - } + return getHeroType()->getJsonKey(); + return ""; } void CGHeroInstance::afterAddToMap(CMap * map) { if(ID != Obj::PRISON) - { map->heroesOnMap.emplace_back(this); - } } void CGHeroInstance::afterRemoveFromMap(CMap* map) { @@ -1752,8 +1752,7 @@ void CGHeroInstance::serializeJsonOptions(JsonSerializeFormat & handler) if(!appearance) { // crossoverDeserialize - type = getHeroType().toHeroType(); - appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->getIndex())->getTemplates().front(); + appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, getHeroClassID())->getTemplates().front(); } } @@ -1840,7 +1839,7 @@ bool CGHeroInstance::isCampaignYog() const if (!boost::starts_with(campaign, "DATA/YOG")) // "Birth of a Barbarian" return false; - if (getHeroType() != HeroTypeID::SOLMYR) // Yog (based on Solmyr) + if (getHeroTypeID() != HeroTypeID::SOLMYR) // Yog (based on Solmyr) return false; return true; @@ -1858,7 +1857,7 @@ bool CGHeroInstance::isCampaignGem() const if (!boost::starts_with(campaign, "DATA/GEM") && !boost::starts_with(campaign, "DATA/FINAL")) // "New Beginning" and "Unholy Alliance" return false; - if (getHeroType() != HeroTypeID::GEM) // Yog (based on Solmyr) + if (getHeroTypeID() != HeroTypeID::GEM) // Yog (based on Solmyr) return false; return true; diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index b84549d6e..b127eb508 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -72,7 +72,6 @@ public: ////////////////////////////////////////////////////////////////////////// - const CHero * type; TExpType exp; //experience points ui32 level; //current level of hero @@ -171,7 +170,7 @@ public: const IOwnableObject * asOwnable() const final; //INativeTerrainProvider - FactionID getFaction() const override; + FactionID getFactionID() const override; TerrainId getNativeTerrain() const override; int getLowestCreatureSpeed() const; si32 manaRegain() const; //how many points of mana can hero regain "naturally" in one day @@ -237,7 +236,11 @@ public: ////////////////////////////////////////////////////////////////////////// - HeroTypeID getHeroType() const; + const CHeroClass * getHeroClass() const; + HeroClassID getHeroClassID() const; + + const CHero * getHeroType() const; + HeroTypeID getHeroTypeID() const; void setHeroType(HeroTypeID type); void initHero(vstd::RNG & rand); @@ -354,7 +357,11 @@ public: h & skillsInfo; h & visitedTown; h & boat; - h & type; + if (h.version < Handler::Version::REMOVE_TOWN_PTR) + { + CHero * type = nullptr; + h & type; + } h & commander; h & visitedObjects; BONUS_TREE_DESERIALIZATION_FIX diff --git a/lib/mapObjects/CGObjectInstance.cpp b/lib/mapObjects/CGObjectInstance.cpp index 5cee078fe..c9d7b3eea 100644 --- a/lib/mapObjects/CGObjectInstance.cpp +++ b/lib/mapObjects/CGObjectInstance.cpp @@ -54,14 +54,14 @@ MapObjectSubID CGObjectInstance::getObjTypeIndex() const return subID; } -int3 CGObjectInstance::getPosition() const +int3 CGObjectInstance::anchorPos() const { return pos; } int3 CGObjectInstance::getTopVisiblePos() const { - return pos - appearance->getTopVisibleOffset(); + return anchorPos() - appearance->getTopVisibleOffset(); } void CGObjectInstance::setOwner(const PlayerColor & ow) @@ -69,6 +69,11 @@ void CGObjectInstance::setOwner(const PlayerColor & ow) tempOwner = ow; } +void CGObjectInstance::setAnchorPos(int3 newPos) +{ + pos = newPos; +} + int CGObjectInstance::getWidth() const { return appearance->getWidth(); @@ -79,32 +84,19 @@ int CGObjectInstance::getHeight() const return appearance->getHeight(); } -bool CGObjectInstance::visitableAt(int x, int y) const -{ - return appearance->isVisitableAt(pos.x - x, pos.y - y); -} -bool CGObjectInstance::blockingAt(int x, int y) const -{ - return appearance->isBlockedAt(pos.x - x, pos.y - y); -} - -bool CGObjectInstance::coveringAt(int x, int y) const -{ - return appearance->isVisibleAt(pos.x - x, pos.y - y); -} - bool CGObjectInstance::visitableAt(const int3 & testPos) const { - return pos.z == testPos.z && appearance->isVisitableAt(pos.x - testPos.x, pos.y - testPos.y); + return anchorPos().z == testPos.z && appearance->isVisitableAt(anchorPos().x - testPos.x, anchorPos().y - testPos.y); } + bool CGObjectInstance::blockingAt(const int3 & testPos) const { - return pos.z == testPos.z && appearance->isBlockedAt(pos.x - testPos.x, pos.y - testPos.y); + return anchorPos().z == testPos.z && appearance->isBlockedAt(anchorPos().x - testPos.x, anchorPos().y - testPos.y); } bool CGObjectInstance::coveringAt(const int3 & testPos) const { - return pos.z == testPos.z && appearance->isVisibleAt(pos.x - testPos.x, pos.y - testPos.y); + return anchorPos().z == testPos.z && appearance->isVisibleAt(anchorPos().x - testPos.x, anchorPos().y - testPos.y); } std::set CGObjectInstance::getBlockedPos() const @@ -115,7 +107,7 @@ std::set CGObjectInstance::getBlockedPos() const for(int h=0; hisBlockedAt(w, h)) - ret.insert(int3(pos.x - w, pos.y - h, pos.z)); + ret.insert(int3(anchorPos().x - w, anchorPos().y - h, anchorPos().z)); } } return ret; @@ -215,6 +207,8 @@ int CGObjectInstance::getSightRadius() const int3 CGObjectInstance::getVisitableOffset() const { + if (!isVisitable()) + throw std::runtime_error("Attempt to access visitable offset of a non-visitable object!"); return appearance->getVisitableOffset(); } @@ -313,6 +307,9 @@ void CGObjectInstance::onHeroVisit( const CGHeroInstance * h ) const int3 CGObjectInstance::visitablePos() const { + if (!isVisitable()) + throw std::runtime_error("Attempt to access visitable position on a non-visitable object!"); + return pos - getVisitableOffset(); } diff --git a/lib/mapObjects/CGObjectInstance.h b/lib/mapObjects/CGObjectInstance.h index 130077ae1..b5fdd7142 100644 --- a/lib/mapObjects/CGObjectInstance.h +++ b/lib/mapObjects/CGObjectInstance.h @@ -28,8 +28,6 @@ using TObjectTypeHandler = std::shared_ptr; class DLL_LINKAGE CGObjectInstance : public IObjectInterface { public: - /// Position of bottom-right corner of object on map - int3 pos; /// Type of object, e.g. town, hero, creature. MapObjectID ID; /// Subtype of object, depends on type @@ -41,6 +39,9 @@ public: /// Defines appearance of object on map (animation, blocked tiles, blit order, etc) std::shared_ptr appearance; + /// Position of bottom-right corner of object on map + int3 pos; + std::string instanceName; std::string typeName; std::string subTypeName; @@ -62,21 +63,19 @@ public: return this->tempOwner; } void setOwner(const PlayerColor & ow); + void setAnchorPos(int3 pos); /** APPEARANCE ACCESSORS **/ int getWidth() const; //returns width of object graphic in tiles int getHeight() const; //returns height of object graphic in tiles int3 visitablePos() const override; - int3 getPosition() const override; + int3 anchorPos() const override; int3 getTopVisiblePos() const; - bool visitableAt(int x, int y) const; //returns true if object is visitable at location (x, y) (h3m pos) - bool blockingAt(int x, int y) const; //returns true if object is blocking location (x, y) (h3m pos) - bool coveringAt(int x, int y) const; //returns true if object covers with picture location (x, y) (h3m pos) - bool visitableAt(const int3 & pos) const; //returns true if object is visitable at location (x, y) (h3m pos) - bool blockingAt (const int3 & pos) const; //returns true if object is blocking location (x, y) (h3m pos) - bool coveringAt (const int3 & pos) const; //returns true if object covers with picture location (x, y) (h3m pos) + bool visitableAt(const int3 & pos) const; //returns true if object is visitable at location + bool blockingAt (const int3 & pos) const; //returns true if object is blocking location + bool coveringAt (const int3 & pos) const; //returns true if object covers with picture location std::set getBlockedPos() const; //returns set of positions blocked by this object const std::set & getBlockedOffsets() const; //returns set of relative positions blocked by this object diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 9d28aee82..50d9b8b35 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -45,7 +45,7 @@ int CGTownInstance::getSightRadius() const //returns sight distance for(const auto & bid : builtBuildings) { - auto height = town->buildings.at(bid)->height; + auto height = getTown()->buildings.at(bid)->height; if(ret < height) ret = height; } @@ -115,7 +115,7 @@ int CGTownInstance::mageGuildLevel() const int CGTownInstance::getHordeLevel(const int & HID) const//HID - 0 or 1; returns creature level or -1 if that horde structure is not present { - return town->hordeLvl.at(HID); + return getTown()->hordeLvl.at(HID); } int CGTownInstance::creatureGrowth(const int & level) const @@ -127,7 +127,7 @@ GrowthInfo CGTownInstance::getGrowthInfo(int level) const { GrowthInfo ret; - if (level<0 || level >=town->creatures.size()) + if (level<0 || level >=getTown()->creatures.size()) return ret; if (creatures[level].second.empty()) return ret; //no dwelling @@ -151,11 +151,11 @@ GrowthInfo CGTownInstance::getGrowthInfo(int level) const else if (hasBuilt(BuildingID::CITADEL)) ret.entries.emplace_back(subID, BuildingID::CITADEL, castleBonus = base / 2); - if(town->hordeLvl.at(0) == level)//horde 1 + if(getTown()->hordeLvl.at(0) == level)//horde 1 if(hasBuilt(BuildingID::HORDE_1)) ret.entries.emplace_back(subID, BuildingID::HORDE_1, creature->getHorde()); - if(town->hordeLvl.at(1) == level)//horde 2 + if(getTown()->hordeLvl.at(1) == level)//horde 2 if(hasBuilt(BuildingID::HORDE_2)) ret.entries.emplace_back(subID, BuildingID::HORDE_2, creature->getHorde()); @@ -209,11 +209,11 @@ int CGTownInstance::getDwellingBonus(const std::vector& creatureIds, TResources CGTownInstance::dailyIncome() const { TResources ret; - for(const auto & p : town->buildings) + for(const auto & p : getTown()->buildings) { BuildingID buildingUpgrade; - for(const auto & p2 : town->buildings) + for(const auto & p2 : getTown()->buildings) { if (p2.second->upgrade == p.first) { @@ -251,10 +251,10 @@ bool CGTownInstance::hasCapitol() const TownFortifications CGTownInstance::fortificationsLevel() const { - auto result = town->fortifications; + auto result = getTown()->fortifications; for (auto const & buildingID : builtBuildings) - result += town->buildings.at(buildingID)->fortifications; + result += getTown()->buildings.at(buildingID)->fortifications; if (result.wallsHealth == 0) return TownFortifications(); @@ -264,7 +264,6 @@ TownFortifications CGTownInstance::fortificationsLevel() const CGTownInstance::CGTownInstance(IGameCallback *cb): CGDwelling(cb), - town(nullptr), built(0), destroyed(0), identifier(0), @@ -379,17 +378,17 @@ void CGTownInstance::onHeroLeave(const CGHeroInstance * h) const std::string CGTownInstance::getObjectName() const { - return getNameTranslated() + ", " + town->faction->getNameTranslated(); + return getNameTranslated() + ", " + getTown()->faction->getNameTranslated(); } bool CGTownInstance::townEnvisagesBuilding(BuildingSubID::EBuildingSubID subId) const { - return town->getBuildingType(subId) != BuildingID::NONE; + return getTown()->getBuildingType(subId) != BuildingID::NONE; } void CGTownInstance::initializeConfigurableBuildings(vstd::RNG & rand) { - for(const auto & kvp : town->buildings) + for(const auto & kvp : getTown()->buildings) { if(!kvp.second->rewardableObjectInfo.getParameters().isNull()) rewardableBuildings[kvp.first] = new TownRewardableBuildingInstance(this, kvp.second->bid, rand); @@ -457,8 +456,7 @@ void CGTownInstance::pickRandomObject(vstd::RNG & rand) assert(ID == Obj::TOWN); // just in case setType(ID, subID); - town = (*VLC->townh)[getFaction()]->town; - randomizeArmy(getFaction()); + randomizeArmy(getFactionID()); updateAppearance(); } @@ -467,19 +465,19 @@ void CGTownInstance::initObj(vstd::RNG & rand) ///initialize town structures blockVisit = true; if(townEnvisagesBuilding(BuildingSubID::PORTAL_OF_SUMMONING)) //Dungeon for example - creatures.resize(town->creatures.size() + 1); + creatures.resize(getTown()->creatures.size() + 1); else - creatures.resize(town->creatures.size()); + creatures.resize(getTown()->creatures.size()); - for (int level = 0; level < town->creatures.size(); level++) + for (int level = 0; level < getTown()->creatures.size(); level++) { BuildingID buildID = BuildingID(BuildingID::getDwellingFromLevel(level, 0)); int upgradeNum = 0; - for (; town->buildings.count(buildID); upgradeNum++, BuildingID::advanceDwelling(buildID)) + for (; getTown()->buildings.count(buildID); upgradeNum++, BuildingID::advanceDwelling(buildID)) { - if (hasBuilt(buildID) && town->creatures.at(level).size() > upgradeNum) - creatures[level].second.push_back(town->creatures[level][upgradeNum]); + if (hasBuilt(buildID) && getTown()->creatures.at(level).size() > upgradeNum) + creatures[level].second.push_back(getTown()->creatures[level][upgradeNum]); } } initializeConfigurableBuildings(rand); @@ -623,9 +621,9 @@ void CGTownInstance::removeCapitols(const PlayerColor & owner) const if (hasCapitol()) // search if there's an older capitol { PlayerState* state = cb->gameState()->getPlayerState(owner); //get all towns owned by player - for (const auto & town : state->getTowns()) + for (const auto & otherTown : state->getTowns()) { - if (town != this && town->hasCapitol()) + if (otherTown != this && otherTown->hasCapitol()) { RazeStructures rs; rs.tid = id; @@ -648,7 +646,7 @@ void CGTownInstance::clearArmy() const BoatId CGTownInstance::getBoatType() const { - return town->faction->boatType; + return getTown()->faction->boatType; } int CGTownInstance::getMarketEfficiency() const @@ -703,7 +701,7 @@ void CGTownInstance::updateAppearance() std::string CGTownInstance::nodeName() const { - return "Town (" + (town ? town->faction->getNameTranslated() : "unknown") + ") of " + getNameTranslated(); + return "Town (" + getTown()->faction->getNameTranslated() + ") of " + getNameTranslated(); } void CGTownInstance::deserializationFix() @@ -752,7 +750,7 @@ void CGTownInstance::recreateBuildingsBonuses() for(const auto & upgradeID : builtBuildings) { - const auto & upgrade = town->buildings.at(upgradeID); + const auto & upgrade = getTown()->buildings.at(upgradeID); if (upgrade->getBase() == bid && upgrade->upgradeReplacesBonuses) bonusesReplacedByUpgrade = true; } @@ -761,7 +759,7 @@ void CGTownInstance::recreateBuildingsBonuses() if (bonusesReplacedByUpgrade) continue; - auto building = town->buildings.at(bid); + auto building = getTown()->buildings.at(bid); if(building->buildingBonuses.empty()) continue; @@ -828,21 +826,6 @@ bool CGTownInstance::armedGarrison() const return !stacks.empty() || garrisonHero; } -const CTown * CGTownInstance::getTown() const -{ - if(ID == Obj::RANDOM_TOWN) - return VLC->townh->randomTown; - else - { - if(nullptr == town) - { - return (*VLC->townh)[getFaction()]->town; - } - else - return town; - } -} - int CGTownInstance::getTownLevel() const { // count all buildings that are not upgrades @@ -850,7 +833,7 @@ int CGTownInstance::getTownLevel() const for(const auto & bid : builtBuildings) { - if(town->buildings.at(bid)->upgrade == BuildingID::NONE) + if(getTown()->buildings.at(bid)->upgrade == BuildingID::NONE) level++; } return level; @@ -892,7 +875,7 @@ bool CGTownInstance::hasBuilt(BuildingSubID::EBuildingSubID buildingID) const { for(const auto & bid : builtBuildings) { - if(town->buildings.at(bid)->subId == buildingID) + if(getTown()->buildings.at(bid)->subId == buildingID) return true; } return false; @@ -905,7 +888,7 @@ bool CGTownInstance::hasBuilt(const BuildingID & buildingID) const bool CGTownInstance::hasBuilt(const BuildingID & buildingID, FactionID townID) const { - if (townID == town->faction->getId() || townID == FactionID::ANY) + if (townID == getTown()->faction->getId() || townID == FactionID::ANY) return hasBuilt(buildingID); return false; } @@ -923,7 +906,7 @@ std::set CGTownInstance::availableModes() const std::set result; for (const auto & buildingID : builtBuildings) { - const auto * buildingPtr = town->buildings.at(buildingID).get(); + const auto * buildingPtr = getTown()->buildings.at(buildingID).get(); result.insert(buildingPtr->marketModes.begin(), buildingPtr->marketModes.end()); } @@ -950,11 +933,11 @@ std::set CGTownInstance::getBuildings() const TResources CGTownInstance::getBuildingCost(const BuildingID & buildingID) const { - if (vstd::contains(town->buildings, buildingID)) - return town->buildings.at(buildingID)->resources; + if (vstd::contains(getTown()->buildings, buildingID)) + return getTown()->buildings.at(buildingID)->resources; else { - logGlobal->error("Town %s at %s has no possible building %d!", getNameTranslated(), pos.toString(), buildingID.toEnum()); + logGlobal->error("Town %s at %s has no possible building %d!", getNameTranslated(), anchorPos().toString(), buildingID.toEnum()); return TResources(); } @@ -962,7 +945,7 @@ TResources CGTownInstance::getBuildingCost(const BuildingID & buildingID) const CBuilding::TRequired CGTownInstance::genBuildingRequirements(const BuildingID & buildID, bool deep) const { - const CBuilding * building = town->buildings.at(buildID); + const CBuilding * building = getTown()->buildings.at(buildID); //TODO: find better solution to prevent infinite loops std::set processed; @@ -970,13 +953,13 @@ CBuilding::TRequired CGTownInstance::genBuildingRequirements(const BuildingID & std::function dependTest = [&](const BuildingID & id) -> CBuilding::TRequired::Variant { - if (town->buildings.count(id) == 0) + if (getTown()->buildings.count(id) == 0) { logMod->error("Invalid building ID %d in building dependencies!", id.getNum()); return CBuilding::TRequired::OperatorAll(); } - const CBuilding * build = town->buildings.at(id); + const CBuilding * build = getTown()->buildings.at(id); CBuilding::TRequired::OperatorAll requirements; if (!hasBuilt(id)) @@ -1001,7 +984,7 @@ CBuilding::TRequired CGTownInstance::genBuildingRequirements(const BuildingID & CBuilding::TRequired::OperatorAll requirements; if (building->upgrade != BuildingID::NONE) { - const CBuilding * upgr = town->buildings.at(building->upgrade); + const CBuilding * upgr = getTown()->buildings.at(building->upgrade); requirements.expressions.push_back(dependTest(upgr->bid)); processed.clear(); @@ -1151,14 +1134,27 @@ void CGTownInstance::serializeJsonOptions(JsonSerializeFormat & handler) } } -FactionID CGTownInstance::getFaction() const +const CFaction * CGTownInstance::getFaction() const { - return FactionID(subID.getNum()); + return getFactionID().toFaction(); +} + +const CTown * CGTownInstance::getTown() const +{ + if(ID == Obj::RANDOM_TOWN) + return VLC->townh->randomTown; + + return getFaction()->town; +} + +FactionID CGTownInstance::getFactionID() const +{ + return FactionID(subID.getNum()); } TerrainId CGTownInstance::getNativeTerrain() const { - return town->faction->getNativeTerrain(); + return getTown()->faction->getNativeTerrain(); } ArtifactID CGTownInstance::getWarMachineInBuilding(BuildingID building) const @@ -1166,21 +1162,21 @@ ArtifactID CGTownInstance::getWarMachineInBuilding(BuildingID building) const if (builtBuildings.count(building) == 0) return ArtifactID::NONE; - if (building == BuildingID::BLACKSMITH && town->warMachineDeprecated.hasValue()) - return town->warMachineDeprecated.toCreature()->warMachine; + if (building == BuildingID::BLACKSMITH && getTown()->warMachineDeprecated.hasValue()) + return getTown()->warMachineDeprecated.toCreature()->warMachine; - return town->buildings.at(building)->warMachine; + return getTown()->buildings.at(building)->warMachine; } bool CGTownInstance::isWarMachineAvailable(ArtifactID warMachine) const { for (auto const & buildingID : builtBuildings) - if (town->buildings.at(buildingID)->warMachine == warMachine) + if (getTown()->buildings.at(buildingID)->warMachine == warMachine) return true; if (builtBuildings.count(BuildingID::BLACKSMITH) && - town->warMachineDeprecated.hasValue() && - town->warMachineDeprecated.toCreature()->warMachine == warMachine) + getTown()->warMachineDeprecated.hasValue() && + getTown()->warMachineDeprecated.toCreature()->warMachine == warMachine) return true; return false; @@ -1200,7 +1196,7 @@ GrowthInfo::Entry::Entry(int subID, const BuildingID & building, int _count): co { MetaString formatter; formatter.appendRawString("%s %+d"); - formatter.replaceRawString((*VLC->townh)[subID]->town->buildings.at(building)->getNameTranslated()); + formatter.replaceRawString(FactionID(subID).toFaction()->town->buildings.at(building)->getNameTranslated()); formatter.replacePositiveNumber(count); description = formatter.toString(); diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index fe143c6fd..636f2bc5c 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -50,18 +50,16 @@ struct DLL_LINKAGE GrowthInfo class DLL_LINKAGE CGTownInstance : public CGDwelling, public IShipyard, public IMarket, public INativeTerrainProvider, public ICreatureUpgrader { + friend class CTownInstanceConstructor; std::string nameTextId; // name of town std::map convertOldBuildings(std::vector oldVector); std::set builtBuildings; public: - using CGDwelling::getPosition; - enum EFortLevel {NONE = 0, FORT = 1, CITADEL = 2, CASTLE = 3}; CTownAndVisitingHero townAndVis; - const CTown * town; si32 built; //how many buildings has been built this turn si32 destroyed; //how many buildings has been destroyed this turn ConstTransitivePtr garrisonHero, visitingHero; @@ -114,16 +112,21 @@ public: rewardableBuildings = convertOldBuildings(oldVector); } - if (h.saving) + if (h.version < Handler::Version::REMOVE_TOWN_PTR) { - CFaction * faction = town ? town->faction : nullptr; - h & faction; - } - else - { - CFaction * faction = nullptr; - h & faction; - town = faction ? faction->town : nullptr; + CTown * town = nullptr; + + if (h.saving) + { + CFaction * faction = town ? town->faction : nullptr; + h & faction; + } + else + { + CFaction * faction = nullptr; + h & faction; + town = faction ? faction->town : nullptr; + } } h & townAndVis; @@ -215,9 +218,10 @@ public: DamageRange getKeepDamageRange() const; const CTown * getTown() const; + const CFaction * getFaction() const; /// INativeTerrainProvider - FactionID getFaction() const override; + FactionID getFactionID() const override; TerrainId getNativeTerrain() const override; /// Returns ID of war machine that is produced by specified building or NONE if this is not built or if building does not produce war machines diff --git a/lib/mapObjects/CQuest.cpp b/lib/mapObjects/CQuest.cpp index 7e1e00298..051349282 100644 --- a/lib/mapObjects/CQuest.cpp +++ b/lib/mapObjects/CQuest.cpp @@ -431,7 +431,7 @@ void CGSeerHut::setObjToKill() if(getCreatureToKill(true)) { - quest->stackToKill = getCreatureToKill(false)->getCreature(); + quest->stackToKill = getCreatureToKill(false)->getCreatureID(); assert(quest->stackToKill != CreatureID::NONE); quest->stackDirection = checkDirection(); } @@ -614,7 +614,7 @@ void CGSeerHut::onHeroVisit(const CGHeroInstance * h) const int CGSeerHut::checkDirection() const { - int3 cord = getCreatureToKill(false)->pos; + int3 cord = getCreatureToKill(false)->visitablePos(); if(static_cast(cord.x) / static_cast(cb->getMapSize().x) < 0.34) //north { if(static_cast(cord.y) / static_cast(cb->getMapSize().y) < 0.34) //northwest diff --git a/lib/mapObjects/IObjectInterface.cpp b/lib/mapObjects/IObjectInterface.cpp index 432b9e35d..80cbec7e4 100644 --- a/lib/mapObjects/IObjectInterface.cpp +++ b/lib/mapObjects/IObjectInterface.cpp @@ -145,7 +145,7 @@ void IBoatGenerator::getProblemText(MetaString &out, const CGHeroInstance *visit out.appendLocalString(EMetaText::ADVOB_TXT, 189); break; case NO_WATER: - logGlobal->error("Shipyard without water at tile %s! ", getObject()->getPosition().toString()); + logGlobal->error("Shipyard without water at tile %s! ", getObject()->anchorPos().toString()); return; } } diff --git a/lib/mapObjects/IObjectInterface.h b/lib/mapObjects/IObjectInterface.h index 9ccda2c7c..4398c5a11 100644 --- a/lib/mapObjects/IObjectInterface.h +++ b/lib/mapObjects/IObjectInterface.h @@ -47,7 +47,7 @@ public: virtual PlayerColor getOwner() const = 0; virtual int3 visitablePos() const = 0; - virtual int3 getPosition() const = 0; + virtual int3 anchorPos() const = 0; virtual void onHeroVisit(const CGHeroInstance * h) const; virtual void onHeroLeave(const CGHeroInstance * h) const; diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index 4a8c4143d..b8b95994d 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -111,7 +111,7 @@ void CGMine::initObj(vstd::RNG & rand) } else { - logGlobal->error("Abandoned mine at (%s) has no valid resource candidates!", pos.toString()); + logGlobal->error("Abandoned mine at (%s) has no valid resource candidates!", anchorPos().toString()); producedResource = GameResID::GOLD; } } @@ -510,11 +510,11 @@ void CGMonolith::onHeroVisit( const CGHeroInstance * h ) const if(cb->isTeleportChannelImpassable(channel)) { - logGlobal->debug("Cannot find corresponding exit monolith for %d at %s", id.getNum(), pos.toString()); + logGlobal->debug("Cannot find corresponding exit monolith for %d at %s", id.getNum(), anchorPos().toString()); td.impassable = true; } else if(getRandomExit(h) == ObjectInstanceID()) - logGlobal->debug("All exits blocked for monolith %d at %s", id.getNum(), pos.toString()); + logGlobal->debug("All exits blocked for monolith %d at %s", id.getNum(), anchorPos().toString()); } else h->showInfoDialog(70); @@ -574,7 +574,7 @@ void CGSubterraneanGate::onHeroVisit( const CGHeroInstance * h ) const if(cb->isTeleportChannelImpassable(channel)) { h->showInfoDialog(153);//Just inside the entrance you find a large pile of rubble blocking the tunnel. You leave discouraged. - logGlobal->debug("Cannot find exit subterranean gate for %d at %s", id.getNum(), pos.toString()); + logGlobal->debug("Cannot find exit subterranean gate for %d at %s", id.getNum(), anchorPos().toString()); td.impassable = true; } else @@ -657,11 +657,11 @@ void CGWhirlpool::onHeroVisit( const CGHeroInstance * h ) const TeleportDialog td(h->id, channel); if(cb->isTeleportChannelImpassable(channel)) { - logGlobal->debug("Cannot find exit whirlpool for %d at %s", id.getNum(), pos.toString()); + logGlobal->debug("Cannot find exit whirlpool for %d at %s", id.getNum(), anchorPos().toString()); td.impassable = true; } else if(getRandomExit(h) == ObjectInstanceID()) - logGlobal->debug("All exits are blocked for whirlpool %d at %s", id.getNum(), pos.toString()); + logGlobal->debug("All exits are blocked for whirlpool %d at %s", id.getNum(), anchorPos().toString()); if(!isProtected(h)) { @@ -1086,9 +1086,9 @@ void CGMagi::onHeroVisit(const CGHeroInstance * h) const for(const auto & eye : eyes) { - cb->getTilesInRange (fw.tiles, eye->pos, 10, ETileVisibility::HIDDEN, h->tempOwner); + cb->getTilesInRange (fw.tiles, eye->visitablePos(), 10, ETileVisibility::HIDDEN, h->tempOwner); cb->sendAndApply(fw); - cv.pos = eye->pos; + cv.pos = eye->visitablePos(); cb->sendAndApply(cv); } diff --git a/lib/mapObjects/TownBuildingInstance.cpp b/lib/mapObjects/TownBuildingInstance.cpp index 04d2dfbe5..2b485ef6d 100644 --- a/lib/mapObjects/TownBuildingInstance.cpp +++ b/lib/mapObjects/TownBuildingInstance.cpp @@ -56,9 +56,9 @@ int3 TownBuildingInstance::visitablePos() const return town->visitablePos(); } -int3 TownBuildingInstance::getPosition() const +int3 TownBuildingInstance::anchorPos() const { - return town->getPosition(); + return town->anchorPos(); } TownRewardableBuildingInstance::TownRewardableBuildingInstance(IGameCallback *cb) @@ -73,14 +73,14 @@ TownRewardableBuildingInstance::TownRewardableBuildingInstance(CGTownInstance * void TownRewardableBuildingInstance::initObj(vstd::RNG & rand) { - assert(town && town->town); + assert(town && town->getTown()); configuration = generateConfiguration(rand); } Rewardable::Configuration TownRewardableBuildingInstance::generateConfiguration(vstd::RNG & rand) const { Rewardable::Configuration result; - auto building = town->town->buildings.at(getBuildingType()); + auto building = town->getTown()->buildings.at(getBuildingType()); building->rewardableObjectInfo.configureObject(result, rand, cb); for(auto & rewardInfo : result.info) diff --git a/lib/mapObjects/TownBuildingInstance.h b/lib/mapObjects/TownBuildingInstance.h index 8874793cf..015421448 100644 --- a/lib/mapObjects/TownBuildingInstance.h +++ b/lib/mapObjects/TownBuildingInstance.h @@ -38,7 +38,7 @@ public: const IOwnableObject * asOwnable() const override; int3 visitablePos() const override; - int3 getPosition() const override; + int3 anchorPos() const override; template void serialize(Handler &h) { diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index 77af471b0..57e3a4c24 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -232,22 +232,22 @@ CMap::~CMap() void CMap::removeBlockVisTiles(CGObjectInstance * obj, bool total) { - const int zVal = obj->pos.z; + const int zVal = obj->anchorPos().z; for(int fx = 0; fx < obj->getWidth(); ++fx) { - int xVal = obj->pos.x - fx; + int xVal = obj->anchorPos().x - fx; for(int fy = 0; fy < obj->getHeight(); ++fy) { - int yVal = obj->pos.y - fy; + int yVal = obj->anchorPos().y - fy; if(xVal>=0 && xVal < width && yVal>=0 && yVal < height) { TerrainTile & curt = terrain[zVal][xVal][yVal]; - if(total || obj->visitableAt(xVal, yVal)) + if(total || obj->visitableAt(int3(xVal, yVal, zVal))) { curt.visitableObjects -= obj; curt.visitable = curt.visitableObjects.size(); } - if(total || obj->blockingAt(xVal, yVal)) + if(total || obj->blockingAt(int3(xVal, yVal, zVal))) { curt.blockingObjects -= obj; curt.blocked = curt.blockingObjects.size(); @@ -259,22 +259,22 @@ void CMap::removeBlockVisTiles(CGObjectInstance * obj, bool total) void CMap::addBlockVisTiles(CGObjectInstance * obj) { - const int zVal = obj->pos.z; + const int zVal = obj->anchorPos().z; for(int fx = 0; fx < obj->getWidth(); ++fx) { - int xVal = obj->pos.x - fx; + int xVal = obj->anchorPos().x - fx; for(int fy = 0; fy < obj->getHeight(); ++fy) { - int yVal = obj->pos.y - fy; + int yVal = obj->anchorPos().y - fy; if(xVal>=0 && xVal < width && yVal >= 0 && yVal < height) { TerrainTile & curt = terrain[zVal][xVal][yVal]; - if(obj->visitableAt(xVal, yVal)) + if(obj->visitableAt(int3(xVal, yVal, zVal))) { curt.visitableObjects.push_back(obj); curt.visitable = true; } - if(obj->blockingAt(xVal, yVal)) + if(obj->blockingAt(int3(xVal, yVal, zVal))) { curt.blockingObjects.push_back(obj); curt.blocked = true; @@ -302,7 +302,7 @@ void CMap::calculateGuardingGreaturePositions() CGHeroInstance * CMap::getHero(HeroTypeID heroID) { for(auto & elem : heroesOnMap) - if(elem->getHeroType() == heroID) + if(elem->getHeroTypeID() == heroID) return elem; return nullptr; } @@ -444,14 +444,14 @@ const CGObjectInstance * CMap::getObjectiveObjectFrom(const int3 & pos, Obj type bestMatch = object; else { - if (object->pos.dist2dSQ(pos) < bestMatch->pos.dist2dSQ(pos)) + if (object->anchorPos().dist2dSQ(pos) < bestMatch->anchorPos().dist2dSQ(pos)) bestMatch = object;// closer than one we already found } } } assert(bestMatch != nullptr); // if this happens - victory conditions or map itself is very, very broken - logGlobal->error("Will use %s from %s", bestMatch->getObjectName(), bestMatch->pos.toString()); + logGlobal->error("Will use %s from %s", bestMatch->getObjectName(), bestMatch->anchorPos().toString()); return bestMatch; } @@ -635,7 +635,7 @@ void CMap::addNewObject(CGObjectInstance * obj) void CMap::moveObject(CGObjectInstance * obj, const int3 & pos) { removeBlockVisTiles(obj); - obj->pos = pos; + obj->setAnchorPos(pos); addBlockVisTiles(obj); } @@ -803,7 +803,7 @@ void CMap::reindexObjects() if (lhs->isRemovable() && !rhs->isRemovable()) return false; - return lhs->pos.y < rhs->pos.y; + return lhs->anchorPos().y < rhs->anchorPos().y; }); // instanceNames don't change diff --git a/lib/mapping/CMapOperation.cpp b/lib/mapping/CMapOperation.cpp index 7aaf268fc..f6c8deb22 100644 --- a/lib/mapping/CMapOperation.cpp +++ b/lib/mapping/CMapOperation.cpp @@ -615,7 +615,7 @@ std::string CInsertObjectOperation::getLabel() const CMoveObjectOperation::CMoveObjectOperation(CMap* map, CGObjectInstance* obj, const int3& targetPosition) : CMapOperation(map), obj(obj), - initialPos(obj->pos), + initialPos(obj->anchorPos()), targetPos(targetPosition) { } diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 1826d34ea..1fe7da29f 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -896,7 +896,7 @@ void CMapLoaderH3M::readPredefinedHeroes() } map->predefinedHeroes.emplace_back(hero); - logGlobal->debug("Map '%s': Hero predefined in map: %s", mapName, VLC->heroh->getById(hero->getHeroType())->getJsonKey()); + logGlobal->debug("Map '%s': Hero predefined in map: %s", mapName, hero->getHeroType()->getJsonKey()); } } @@ -913,7 +913,7 @@ void CMapLoaderH3M::loadArtifactsOfHero(CGHeroInstance * hero) if(!hero->artifactsWorn.empty() || !hero->artifactsInBackpack.empty()) { - logGlobal->debug("Hero %d at %s has set artifacts twice (in map properties and on adventure map instance). Using the latter set...", hero->getHeroType().getNum(), hero->pos.toString()); + logGlobal->debug("Hero %d at %s has set artifacts twice (in map properties and on adventure map instance). Using the latter set...", hero->getHeroTypeID().getNum(), hero->anchorPos().toString()); hero->artifactsInBackpack.clear(); while(!hero->artifactsWorn.empty()) @@ -1651,7 +1651,7 @@ void CMapLoaderH3M::readObjects() if(!newObject) continue; - newObject->pos = mapPosition; + newObject->setAnchorPos(mapPosition); newObject->ID = objectTemplate->id; newObject->id = objectInstanceID; if(newObject->ID != Obj::HERO && newObject->ID != Obj::HERO_PLACEHOLDER && newObject->ID != Obj::PRISON) @@ -1778,7 +1778,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(const int3 & mapPosition, const Objec for(auto & elem : map->disposedHeroes) { - if(elem.heroId == object->getHeroType()) + if(elem.heroId == object->getHeroTypeID()) { object->nameCustomTextId = elem.name; object->customPortraitSource = elem.portrait; @@ -1788,7 +1788,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(const int3 & mapPosition, const Objec bool hasName = reader->readBool(); if(hasName) - object->nameCustomTextId = readLocalizedString(TextIdentifier("heroes", object->getHeroType().getNum(), "name")); + object->nameCustomTextId = readLocalizedString(TextIdentifier("heroes", object->getHeroTypeID().getNum(), "name")); if(features.levelSOD) { @@ -1891,7 +1891,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(const int3 & mapPosition, const Objec auto ps = object->getAllBonuses(Selector::type()(BonusType::PRIMARY_SKILL).And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL)), nullptr); if(ps->size()) { - logGlobal->debug("Hero %s has set primary skills twice (in map properties and on adventure map instance). Using the latter set...", object->getHeroType().getNum() ); + logGlobal->debug("Hero %s has set primary skills twice (in map properties and on adventure map instance). Using the latter set...", object->getHeroTypeID().getNum() ); for(const auto & b : *ps) object->removeBonus(b); } @@ -1904,7 +1904,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(const int3 & mapPosition, const Objec } if (object->subID != MapObjectSubID()) - logGlobal->debug("Map '%s': Hero on map: %s at %s, owned by %s", mapName, VLC->heroh->getById(object->getHeroType())->getJsonKey(), mapPosition.toString(), object->getOwner().toString()); + logGlobal->debug("Map '%s': Hero on map: %s at %s, owned by %s", mapName, object->getHeroType()->getJsonKey(), mapPosition.toString(), object->getOwner().toString()); else logGlobal->debug("Map '%s': Hero on map: (random) at %s, owned by %s", mapName, mapPosition.toString(), object->getOwner().toString()); diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index 753e235c7..0777defed 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -436,10 +436,8 @@ void CMapFormatJson::serializePlayerInfo(JsonSerializeFormat & handler) if(hero->ID == Obj::HERO) { std::string temp; - if(hero->type) - temp = hero->type->getJsonKey(); - else - temp = hero->getHeroType().toEntity(VLC)->getJsonKey(); + if(hero->getHeroTypeID().hasValue()) + temp = hero->getHeroType()->getJsonKey(); handler.serializeString("type", temp); } @@ -1072,7 +1070,7 @@ void CMapLoaderJson::MapObjectLoader::construct() instance->id = ObjectInstanceID(static_cast(owner->map->objects.size())); instance->instanceName = jsonKey; - instance->pos = pos; + instance->setAnchorPos(pos); owner->map->addNewObject(instance); } @@ -1154,10 +1152,10 @@ void CMapLoaderJson::readObjects() auto * hero = dynamic_cast(object.get()); - if (debugHeroesOnMap.count(hero->getHeroType())) + if (debugHeroesOnMap.count(hero->getHeroTypeID())) logGlobal->error("Hero is already on the map at %s", hero->visitablePos().toString()); - debugHeroesOnMap.insert(hero->getHeroType()); + debugHeroesOnMap.insert(hero->getHeroTypeID()); } } diff --git a/lib/networkPacks/NetPackVisitor.h b/lib/networkPacks/NetPackVisitor.h index 007f30fa6..09a67e354 100644 --- a/lib/networkPacks/NetPackVisitor.h +++ b/lib/networkPacks/NetPackVisitor.h @@ -13,6 +13,7 @@ #include "PacksForClientBattle.h" #include "PacksForServer.h" #include "PacksForLobby.h" +#include "SaveLocalState.h" #include "SetRewardableConfiguration.h" #include "SetStackEffect.h" @@ -177,6 +178,7 @@ public: virtual void visitLobbyForceSetPlayer(LobbyForceSetPlayer & pack) {} virtual void visitLobbyShowMessage(LobbyShowMessage & pack) {} virtual void visitLobbyPvPAction(LobbyPvPAction & pack) {} + virtual void visitSaveLocalState(SaveLocalState & pack) {} }; VCMI_LIB_NAMESPACE_END diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index 6e7229f98..6ea83ab8f 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -12,6 +12,7 @@ #include "PacksForClient.h" #include "PacksForClientBattle.h" #include "PacksForServer.h" +#include "SaveLocalState.h" #include "SetRewardableConfiguration.h" #include "StackLocation.h" #include "PacksForLobby.h" @@ -92,6 +93,12 @@ bool CLobbyPackToServer::isForServer() const return true; } +void SaveLocalState::visitTyped(ICPackVisitor & visitor) +{ + visitor.visitSaveLocalState(*this); +} + + void PackageApplied::visitTyped(ICPackVisitor & visitor) { visitor.visitPackageApplied(*this); @@ -1047,7 +1054,7 @@ void ChangeObjPos::applyGs(CGameState *gs) return; } gs->map->removeBlockVisTiles(obj); - obj->pos = nPos + obj->getVisitableOffset(); + obj->setAnchorPos(nPos + obj->getVisitableOffset()); gs->map->addBlockVisTiles(obj); } @@ -1348,7 +1355,7 @@ void NewStructures::applyGs(CGameState *gs) for(const auto & id : bid) { - assert(t->town->buildings.at(id) != nullptr); + assert(t->getTown()->buildings.at(id) != nullptr); t->addBuilding(id); } t->updateAppearance(); @@ -1463,11 +1470,11 @@ void GiveHero::applyGs(CGameState *gs) auto oldVisitablePos = h->visitablePos(); gs->map->removeBlockVisTiles(h,true); - h->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, h->type->heroClass->getIndex())->getTemplates().front(); + h->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, h->getHeroClassID().getNum())->getTemplates().front(); h->setOwner(player); h->setMovementPoints(h->movementPointsLimit(true)); - h->pos = h->convertFromVisitablePos(oldVisitablePos); + h->setAnchorPos(h->convertFromVisitablePos(oldVisitablePos)); gs->map->heroesOnMap.emplace_back(h); gs->getPlayerState(h->getOwner())->addOwnedObject(h); diff --git a/lib/networkPacks/SaveLocalState.h b/lib/networkPacks/SaveLocalState.h new file mode 100644 index 000000000..b6106fa47 --- /dev/null +++ b/lib/networkPacks/SaveLocalState.h @@ -0,0 +1,31 @@ +/* + * SaveLocalState.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "NetPacksBase.h" + +#include "../json/JsonNode.h" + +VCMI_LIB_NAMESPACE_BEGIN + +struct DLL_LINKAGE SaveLocalState : public CPackForServer +{ + JsonNode data; + + void visitTyped(ICPackVisitor & visitor) override; + + template void serialize(Handler & h) + { + h & static_cast(*this); + h & data; + } +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/pathfinder/CPathfinder.cpp b/lib/pathfinder/CPathfinder.cpp index 41b7e7ee5..28f8fa178 100644 --- a/lib/pathfinder/CPathfinder.cpp +++ b/lib/pathfinder/CPathfinder.cpp @@ -292,7 +292,7 @@ TeleporterTilesVector CPathfinderHelper::getTeleportExits(const PathNodeInfo & s { auto * town = dynamic_cast(source.nodeObject); assert(town); - if (town && town->getFaction() == FactionID::INFERNO) + if (town && town->getFactionID() == FactionID::INFERNO) { /// TODO: Find way to reuse CPlayerSpecificInfoCallback::getTownsInfo /// This may be handy if we allow to use teleportation to friendly towns diff --git a/lib/rewardable/Limiter.cpp b/lib/rewardable/Limiter.cpp index 563db49e1..384ffea92 100644 --- a/lib/rewardable/Limiter.cpp +++ b/lib/rewardable/Limiter.cpp @@ -173,10 +173,10 @@ bool Rewardable::Limiter::heroAllowed(const CGHeroInstance * hero) const if(!players.empty() && !vstd::contains(players, hero->getOwner())) return false; - if(!heroes.empty() && !vstd::contains(heroes, hero->type->getId())) + if(!heroes.empty() && !vstd::contains(heroes, hero->getHeroTypeID())) return false; - if(!heroClasses.empty() && !vstd::contains(heroClasses, hero->type->heroClass->getId())) + if(!heroClasses.empty() && !vstd::contains(heroClasses, hero->getHeroClassID())) return false; diff --git a/lib/rmg/RmgObject.cpp b/lib/rmg/RmgObject.cpp index 1004072cb..691bb23b8 100644 --- a/lib/rmg/RmgObject.cpp +++ b/lib/rmg/RmgObject.cpp @@ -87,7 +87,7 @@ const rmg::Area & Object::Instance::getAccessibleArea() const void Object::Instance::setPosition(const int3 & position) { dPosition = position; - dObject.pos = dPosition + dParent.getPosition(); + dObject.setAnchorPos(dPosition + dParent.getPosition()); dBlockedAreaCache.clear(); dAccessibleAreaCache.clear(); @@ -96,21 +96,21 @@ void Object::Instance::setPosition(const int3 & position) void Object::Instance::setPositionRaw(const int3 & position) { - if(!dObject.pos.valid()) + if(!dObject.anchorPos().valid()) { - dObject.pos = dPosition + dParent.getPosition(); + dObject.setAnchorPos(dPosition + dParent.getPosition()); dBlockedAreaCache.clear(); dAccessibleAreaCache.clear(); dParent.clearCachedArea(); } - auto shift = position + dParent.getPosition() - dObject.pos; + auto shift = position + dParent.getPosition() - dObject.anchorPos(); dAccessibleAreaCache.translate(shift); dBlockedAreaCache.translate(shift); dPosition = position; - dObject.pos = dPosition + dParent.getPosition(); + dObject.setAnchorPos(dPosition + dParent.getPosition()); } void Object::Instance::setAnyTemplate(vstd::RNG & rng) @@ -497,12 +497,12 @@ void Object::Instance::finalize(RmgMap & map, vstd::RNG & rng) } if (dObject.isVisitable() && !map.isOnMap(dObject.visitablePos())) - throw rmgException(boost::str(boost::format("Visitable tile %s of object %d at %s is outside the map") % dObject.visitablePos().toString() % dObject.id % dObject.pos.toString())); + throw rmgException(boost::str(boost::format("Visitable tile %s of object %d at %s is outside the map") % dObject.visitablePos().toString() % dObject.id % dObject.anchorPos().toString())); for(const auto & tile : dObject.getBlockedPos()) { if(!map.isOnMap(tile)) - throw rmgException(boost::str(boost::format("Tile %s of object %d at %s is outside the map") % tile.toString() % dObject.id % dObject.pos.toString())); + throw rmgException(boost::str(boost::format("Tile %s of object %d at %s is outside the map") % tile.toString() % dObject.id % dObject.anchorPos().toString())); } for(const auto & tile : getBlockedArea().getTilesVector()) diff --git a/lib/rmg/modificators/ObjectManager.cpp b/lib/rmg/modificators/ObjectManager.cpp index a15ef0937..c256a85c1 100644 --- a/lib/rmg/modificators/ObjectManager.cpp +++ b/lib/rmg/modificators/ObjectManager.cpp @@ -733,7 +733,7 @@ CGCreature * ObjectManager::chooseGuard(si32 strength, bool zoneGuard) continue; if(!cre->getAIValue()) //bug #2681 continue; - if(!vstd::contains(zone.getMonsterTypes(), cre->getFaction())) + if(!vstd::contains(zone.getMonsterTypes(), cre->getFactionID())) continue; if((static_cast(cre->getAIValue() * (cre->ammMin + cre->ammMax) / 2) < strength) && (strength < static_cast(cre->getAIValue()) * 100)) //at least one full monster. size between average size of given stack and 100 { diff --git a/lib/rmg/modificators/QuestArtifactPlacer.cpp b/lib/rmg/modificators/QuestArtifactPlacer.cpp index 9117ab34c..31d9e5c05 100644 --- a/lib/rmg/modificators/QuestArtifactPlacer.cpp +++ b/lib/rmg/modificators/QuestArtifactPlacer.cpp @@ -112,7 +112,7 @@ void QuestArtifactPlacer::placeQuestArtifacts(vstd::RNG & rand) logGlobal->trace("Replacing %s at %s with the quest artifact %s", objectToReplace->getObjectName(), - objectToReplace->getPosition().toString(), + objectToReplace->anchorPos().toString(), VLC->artifacts()->getById(artifactToPlace)->getNameTranslated()); //Update appearance. Terrain is irrelevant. @@ -121,7 +121,7 @@ void QuestArtifactPlacer::placeQuestArtifacts(vstd::RNG & rand) auto templates = handler->getTemplates(); //artifactToReplace->appearance = templates.front(); newObj->appearance = templates.front(); - newObj->pos = objectToReplace->pos; + newObj->setAnchorPos(objectToReplace->anchorPos()); mapProxy->insertObject(newObj); mapProxy->removeObject(objectToReplace); break; diff --git a/lib/rmg/modificators/TownPlacer.cpp b/lib/rmg/modificators/TownPlacer.cpp index 0a3e412c4..1ab68779d 100644 --- a/lib/rmg/modificators/TownPlacer.cpp +++ b/lib/rmg/modificators/TownPlacer.cpp @@ -90,7 +90,7 @@ void TownPlacer::placeTowns(ObjectManager & manager) totalTowns++; //register MAIN town of zone only - map.registerZone(town->getFaction()); + map.registerZone(town->getFactionID()); if(player.isValidPlayer()) //configure info for owning player { @@ -213,7 +213,7 @@ void TownPlacer::addNewTowns(int count, bool hasFort, const PlayerColor & player { //FIXME: discovered bug with small zones - getPos is close to map boarder and we have outOfMap exception //register MAIN town of zone - map.registerZone(town->getFaction()); + map.registerZone(town->getFactionID()); //first town in zone goes in the middle placeMainTown(manager, *town); } diff --git a/lib/rmg/modificators/TreasurePlacer.cpp b/lib/rmg/modificators/TreasurePlacer.cpp index eb1b3c60f..781458d43 100644 --- a/lib/rmg/modificators/TreasurePlacer.cpp +++ b/lib/rmg/modificators/TreasurePlacer.cpp @@ -51,7 +51,7 @@ void TreasurePlacer::process() // Add all native creatures for(auto const & cre : VLC->creh->objects) { - if(!cre->special && cre->getFaction() == zone.getTownType()) + if(!cre->special && cre->getFactionID() == zone.getTownType()) { creatures.push_back(cre.get()); } @@ -192,7 +192,7 @@ void TreasurePlacer::addPrisons() { // Hero can be used again auto* hero = dynamic_cast(obj); - prisonHeroPlacer->restoreDrawnHero(hero->getHeroType()); + prisonHeroPlacer->restoreDrawnHero(hero->getHeroTypeID()); }; oi.setTemplates(Obj::PRISON, 0, zone.getTerrainType()); @@ -236,9 +236,9 @@ void TreasurePlacer::addDwellings() continue; const auto * cre = creatures.front(); - if(cre->getFaction() == zone.getTownType()) + if(cre->getFactionID() == zone.getTownType()) { - auto nativeZonesCount = static_cast(map.getZoneCount(cre->getFaction())); + auto nativeZonesCount = static_cast(map.getZoneCount(cre->getFactionID())); ObjectInfo oi(dwellingType, secondaryID); setBasicProperties(oi, CompoundMapObjectID(dwellingType, secondaryID)); @@ -375,7 +375,7 @@ void TreasurePlacer::addPandoraBoxesWithCreatures() return obj; }; oi.setTemplates(Obj::PANDORAS_BOX, 0, zone.getTerrainType()); - oi.value = static_cast(creature->getAIValue() * creaturesAmount * (1 + static_cast(map.getZoneCount(creature->getFaction())) / map.getTotalZoneCount())); + oi.value = static_cast(creature->getAIValue() * creaturesAmount * (1 + static_cast(map.getZoneCount(creature->getFactionID())) / map.getTotalZoneCount())); oi.probability = 3; if(!oi.templates.empty()) addObjectToRandomPool(oi); @@ -555,7 +555,7 @@ void TreasurePlacer::addSeerHuts() oi.destroyObject = destroyObject; oi.probability = 3; oi.setTemplates(Obj::SEER_HUT, randomAppearance, zone.getTerrainType()); - oi.value = static_cast(((2 * (creature->getAIValue()) * creaturesAmount * (1 + static_cast(map.getZoneCount(creature->getFaction())) / map.getTotalZoneCount())) - 4000) / 3); + oi.value = static_cast(((2 * (creature->getAIValue()) * creaturesAmount * (1 + static_cast(map.getZoneCount(creature->getFactionID())) / map.getTotalZoneCount())) - 4000) / 3); if (oi.value > zone.getMaxTreasureValue()) { continue; diff --git a/lib/serializer/CSerializer.cpp b/lib/serializer/CSerializer.cpp index 23de9965e..f8311a95b 100644 --- a/lib/serializer/CSerializer.cpp +++ b/lib/serializer/CSerializer.cpp @@ -26,7 +26,7 @@ void CSerializer::addStdVecItems(CGameState *gs, LibClasses *lib) registerVectoredType(&gs->map->objects, [](const CGObjectInstance &obj){ return obj.id; }); registerVectoredType(&gs->map->allHeroes, - [](const CGHeroInstance &h){ return h.type->getId(); }); + [](const CGHeroInstance &h){ return h.getHeroType()->getId(); }); registerVectoredType(&gs->map->artInstances, [](const CArtifactInstance &artInst){ return artInst.getId(); }); registerVectoredType(&gs->map->quests, diff --git a/lib/serializer/ESerializationVersion.h b/lib/serializer/ESerializationVersion.h index b4be54223..d0fe579e4 100644 --- a/lib/serializer/ESerializationVersion.h +++ b/lib/serializer/ESerializationVersion.h @@ -62,6 +62,8 @@ enum class ESerializationVersion : int32_t REWARDABLE_BANKS, // 863 - team state contains list of scouted objects, coast visitable rewardable objects REGION_LABEL, // 864 - labels for campaign regions SPELL_RESEARCH, // 865 - spell research + LOCAL_PLAYER_STATE_DATA, // 866 - player state contains arbitrary client-side data + REMOVE_TOWN_PTR, // 867 - removed pointer to CTown from CGTownInstance - CURRENT = SPELL_RESEARCH + CURRENT = REMOVE_TOWN_PTR }; diff --git a/lib/serializer/RegisterTypes.h b/lib/serializer/RegisterTypes.h index f716c7be4..492d0e371 100644 --- a/lib/serializer/RegisterTypes.h +++ b/lib/serializer/RegisterTypes.h @@ -34,6 +34,7 @@ #include "../networkPacks/PacksForClientBattle.h" #include "../networkPacks/PacksForLobby.h" #include "../networkPacks/PacksForServer.h" +#include "../networkPacks/SaveLocalState.h" #include "../networkPacks/SetRewardableConfiguration.h" #include "../networkPacks/SetStackEffect.h" @@ -290,6 +291,7 @@ void registerTypes(Serializer &s) s.template registerType(240); s.template registerType(241); s.template registerType(242); + s.template registerType(243); } VCMI_LIB_NAMESPACE_END diff --git a/lib/spells/AdventureSpellMechanics.cpp b/lib/spells/AdventureSpellMechanics.cpp index decfa7140..089c691b2 100644 --- a/lib/spells/AdventureSpellMechanics.cpp +++ b/lib/spells/AdventureSpellMechanics.cpp @@ -209,7 +209,7 @@ ESpellCastResult SummonBoatMechanics::applyAdventureEffects(SpellCastEnvironment if(b->hero || b->layer != EPathfindingLayer::SAIL) continue; //we're looking for unoccupied boat - double nDist = b->pos.dist2d(parameters.caster->getHeroCaster()->visitablePos()); + double nDist = b->visitablePos().dist2d(parameters.caster->getHeroCaster()->visitablePos()); if(!nearest || nDist < dist) //it's first boat or closer than previous { nearest = b; @@ -669,11 +669,11 @@ const CGTownInstance * TownPortalMechanics::findNearestTown(SpellCastEnvironment return nullptr; auto nearest = pool.cbegin(); //nearest town's iterator - si32 dist = (*nearest)->pos.dist2dSQ(parameters.caster->getHeroCaster()->pos); + si32 dist = (*nearest)->visitablePos().dist2dSQ(parameters.caster->getHeroCaster()->visitablePos()); for(auto i = nearest + 1; i != pool.cend(); ++i) { - si32 curDist = (*i)->pos.dist2dSQ(parameters.caster->getHeroCaster()->pos); + si32 curDist = (*i)->visitablePos().dist2dSQ(parameters.caster->getHeroCaster()->visitablePos()); if(curDist < dist) { diff --git a/lib/spells/effects/Moat.cpp b/lib/spells/effects/Moat.cpp index 5be7c92fa..889644b88 100644 --- a/lib/spells/effects/Moat.cpp +++ b/lib/spells/effects/Moat.cpp @@ -88,7 +88,7 @@ void Moat::convertBonus(const Mechanics * m, std::vector & converted) con if(m->battle()->battleGetDefendedTown() && m->battle()->battleGetFortifications().hasMoat) { - nb.sid = BonusSourceID(m->battle()->battleGetDefendedTown()->town->buildings.at(BuildingID::CITADEL)->getUniqueTypeID()); + nb.sid = BonusSourceID(m->battle()->battleGetDefendedTown()->getTown()->buildings.at(BuildingID::CITADEL)->getUniqueTypeID()); nb.source = BonusSource::TOWN_STRUCTURE; } else diff --git a/lib/spells/effects/Summon.cpp b/lib/spells/effects/Summon.cpp index 5f9c4d34b..e46cdc6da 100644 --- a/lib/spells/effects/Summon.cpp +++ b/lib/spells/effects/Summon.cpp @@ -79,7 +79,7 @@ bool Summon::applicable(Problem & problem, const Mechanics * m) const text.replaceNamePlural(elemental->creatureId()); - if(caster->type->gender == EHeroGender::FEMALE) + if(caster->gender == EHeroGender::FEMALE) text.replaceLocalString(EMetaText::GENERAL_TXT, 540); else text.replaceLocalString(EMetaText::GENERAL_TXT, 539); diff --git a/mapeditor/CMakeLists.txt b/mapeditor/CMakeLists.txt index 33f6f6e67..e9f7ad6cf 100644 --- a/mapeditor/CMakeLists.txt +++ b/mapeditor/CMakeLists.txt @@ -217,6 +217,10 @@ if(APPLE) set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER vcmieditor) endif() +if(ENABLE_STATIC_LIBS OR NOT (ENABLE_EDITOR AND ENABLE_LAUNCHER)) + target_compile_definitions(vcmieditor PRIVATE VCMIQT_STATIC) +endif() + target_link_libraries(vcmieditor vcmi vcmiqt Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network) target_include_directories(vcmieditor PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index 30aa744a3..0fb5859ec 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -138,25 +138,11 @@ void Initializer::initialize(CGHeroInstance * o) o->subID = 0; o->tempOwner = PlayerColor::NEUTRAL; } - - if(o->ID == Obj::HERO) + + if(o->getHeroTypeID().hasValue()) { - for(auto const & t : VLC->heroh->objects) - { - if(t->heroClass->getId() == HeroClassID(o->subID)) - { - o->type = t.get(); - break; - } - } - } - - if(o->type) - { - // o->type = VLC->heroh->objects.at(o->subID); - - o->gender = o->type->gender; - o->randomizeArmy(o->type->heroClass->faction); + o->gender = o->getHeroType()->gender; + o->randomizeArmy(o->getFactionID()); } } @@ -304,7 +290,7 @@ void Inspector::updateProperties(CGHeroInstance * o) auto isPrison = o->ID == Obj::PRISON; addProperty("Owner", o->tempOwner, new OwnerDelegate(controller, isPrison), isPrison); //field is not editable for prison addProperty("Experience", o->exp, false); - addProperty("Hero class", o->type ? o->type->heroClass->getNameTranslated() : "", true); + addProperty("Hero class", o->getHeroClassID().hasValue() ? o->getHeroClass()->getNameTranslated() : "", true); { //Gender auto * delegate = new InspectorDelegate; @@ -319,20 +305,20 @@ void Inspector::updateProperties(CGHeroInstance * o) addProperty("Skills", PropertyEditorPlaceholder(), delegate, false); addProperty("Spells", PropertyEditorPlaceholder(), new HeroSpellDelegate(*o), false); - if(o->type || o->ID == Obj::PRISON) + if(o->getHeroTypeID().hasValue() || o->ID == Obj::PRISON) { //Hero type auto * delegate = new InspectorDelegate; - for(int i = 0; i < VLC->heroh->objects.size(); ++i) + for(const auto & heroPtr : VLC->heroh->objects) { - if(controller.map()->allowedHeroes.count(HeroTypeID(i)) != 0) + if(controller.map()->allowedHeroes.count(heroPtr->getId()) != 0) { - if(o->ID == Obj::PRISON || (o->type && VLC->heroh->objects[i]->heroClass->getIndex() == o->type->heroClass->getIndex())) + if(o->ID == Obj::PRISON || heroPtr->heroClass->getIndex() == o->getHeroClassID()) { - delegate->options.push_back({QObject::tr(VLC->heroh->objects[i]->getNameTranslated().c_str()), QVariant::fromValue(VLC->heroh->objects[i]->getId().getNum())}); + delegate->options.push_back({QObject::tr(heroPtr->getNameTranslated().c_str()), QVariant::fromValue(heroPtr->getIndex())}); } } } - addProperty("Hero type", o->type ? o->type->getNameTranslated() : "", delegate, false); + addProperty("Hero type", o->getHeroTypeID().hasValue() ? o->getHeroType()->getNameTranslated() : "", delegate, false); } } @@ -706,13 +692,10 @@ void Inspector::setProperty(CGHeroInstance * o, const QString & key, const QVari for(auto const & t : VLC->heroh->objects) { if(t->getId() == value.toInt()) - { o->subID = value.toInt(); - o->type = t.get(); - } } - o->gender = o->type->gender; - o->randomizeArmy(o->type->heroClass->faction); + o->gender = o->getHeroType()->gender; + o->randomizeArmy(o->getHeroType()->heroClass->faction); updateProperties(); //updating other properties after change } } diff --git a/mapeditor/inspector/townbuildingswidget.cpp b/mapeditor/inspector/townbuildingswidget.cpp index a90bd654a..4bf95c5c8 100644 --- a/mapeditor/inspector/townbuildingswidget.cpp +++ b/mapeditor/inspector/townbuildingswidget.cpp @@ -331,7 +331,7 @@ void TownBuildingsDelegate::setEditorData(QWidget *editor, const QModelIndex &in { if(auto * ed = qobject_cast(editor)) { - auto * ctown = town.town; + auto * ctown = town.getTown(); if(!ctown) ctown = VLC->townh->randomTown; if(!ctown) diff --git a/mapeditor/inspector/towneventdialog.cpp b/mapeditor/inspector/towneventdialog.cpp index e56af191e..475c42edc 100644 --- a/mapeditor/inspector/towneventdialog.cpp +++ b/mapeditor/inspector/towneventdialog.cpp @@ -100,7 +100,7 @@ void TownEventDialog::initResources() void TownEventDialog::initBuildings() { - auto * ctown = town.town; + auto * ctown = town.getTown(); if (!ctown) ctown = VLC->townh->randomTown; if (!ctown) @@ -156,7 +156,7 @@ QStandardItem * TownEventDialog::addBuilding(const CTown& ctown, BuildingID buil void TownEventDialog::initCreatures() { auto creatures = params.value("creatures").toList(); - auto * ctown = town.town; + auto * ctown = town.getTown(); if (!ctown) ui->creaturesTable->setRowCount(GameConstants::CREATURES_PER_TOWN); else diff --git a/mapeditor/mapcontroller.cpp b/mapeditor/mapcontroller.cpp index 5fc0c25ec..a30a90aa8 100644 --- a/mapeditor/mapcontroller.cpp +++ b/mapeditor/mapcontroller.cpp @@ -138,7 +138,7 @@ void MapController::repairMap(CMap * map) const // FIXME: How about custom scenarios where defeated hero cannot be hired again? - map->allowedHeroes.insert(nih->getHeroType()); + map->allowedHeroes.insert(nih->getHeroTypeID()); auto const & type = VLC->heroh->objects[nih->subID]; assert(type->heroClass); @@ -154,10 +154,6 @@ void MapController::repairMap(CMap * map) const nih->subTypeName = "prison"; nih->subID = 0; } - - if(obj->ID != Obj::RANDOM_HERO) - nih->type = type.get(); - if(nih->ID == Obj::HERO) //not prison nih->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->getIndex())->getTemplates().front(); //fix spellbook diff --git a/mapeditor/maphandler.cpp b/mapeditor/maphandler.cpp index e42b84b78..c9b18ac32 100644 --- a/mapeditor/maphandler.cpp +++ b/mapeditor/maphandler.cpp @@ -379,7 +379,7 @@ void MapHandler::drawObjects(QPainter & painter, int x, int y, int z, const std: if(objData.objBitmap) { - auto pos = obj->getPosition(); + auto pos = obj->anchorPos(); painter.drawImage(QPoint(x * tileSize, y * tileSize), *objData.objBitmap, object.rect, Qt::AutoColor | Qt::NoOpaqueDetection); diff --git a/mapeditor/mapsettings/abstractsettings.cpp b/mapeditor/mapsettings/abstractsettings.cpp index a1f4afc43..f75a77685 100644 --- a/mapeditor/mapsettings/abstractsettings.cpp +++ b/mapeditor/mapsettings/abstractsettings.cpp @@ -115,7 +115,7 @@ std::string AbstractSettings::getMonsterName(const CMap & map, int objectIdx) std::string name; if(auto monster = dynamic_cast(map.objects[objectIdx].get())) { - name = boost::str(boost::format("%1% at %2%") % monster->getObjectName() % monster->getPosition().toString()); + name = boost::str(boost::format("%1% at %2%") % monster->getObjectName() % monster->anchorPos().toString()); } return name; } diff --git a/mapeditor/playerparams.cpp b/mapeditor/playerparams.cpp index dc4cfee94..985c01d47 100644 --- a/mapeditor/playerparams.cpp +++ b/mapeditor/playerparams.cpp @@ -77,12 +77,10 @@ PlayerParams::PlayerParams(MapController & ctrl, int playerId, QWidget *parent) { if(auto town = dynamic_cast(controller.map()->objects[i].get())) { - auto * ctown = town->town; + auto * ctown = town->getTown(); if(!ctown) - { ctown = VLC->townh->randomTown; - town->town = ctown; - } + if(ctown && town->getOwner().getNum() == playerColor) { if(playerInfo.hasMainTown && playerInfo.posOfMainTown == town->pos) diff --git a/mapeditor/scenelayer.cpp b/mapeditor/scenelayer.cpp index 32713ebef..628fb932c 100644 --- a/mapeditor/scenelayer.cpp +++ b/mapeditor/scenelayer.cpp @@ -425,7 +425,7 @@ void ObjectsLayer::setDirty(const CGObjectInstance * object) { for(int i = 0; i < object->getWidth(); ++i) { - setDirty(object->getPosition().x - i, object->getPosition().y - j); + setDirty(object->anchorPos().x - i, object->anchorPos().y - j); } } } @@ -479,7 +479,7 @@ void SelectionObjectsLayer::draw() { if(obj != newObject) { - QRect bbox(obj->getPosition().x, obj->getPosition().y, 1, 1); + QRect bbox(obj->anchorPos().x, obj->anchorPos().y, 1, 1); for(auto & t : obj->getBlockedPos()) { QPoint topLeft(std::min(t.x, bbox.topLeft().x()), std::min(t.y, bbox.topLeft().y())); @@ -496,7 +496,7 @@ void SelectionObjectsLayer::draw() if(selectionMode == SelectionMode::MOVEMENT && (shift.x() || shift.y())) { painter.setOpacity(0.7); - auto newPos = QPoint(obj->getPosition().x, obj->getPosition().y) + shift; + auto newPos = QPoint(obj->anchorPos().x, obj->anchorPos().y) + shift; handler->drawObjectAt(painter, obj, newPos.x(), newPos.y()); } } @@ -517,7 +517,7 @@ CGObjectInstance * SelectionObjectsLayer::selectObjectAt(int x, int y, const CGO if(!object.obj || object.obj == ignore || lockedObjects.count(object.obj)) continue; - if(object.obj->visitableAt(x, y)) + if(object.obj->visitableAt(int3(x, y, scene->level))) { return const_cast(object.obj); } @@ -529,7 +529,7 @@ CGObjectInstance * SelectionObjectsLayer::selectObjectAt(int x, int y, const CGO if(!object.obj || object.obj == ignore || lockedObjects.count(object.obj)) continue; - if(object.obj->blockingAt(x, y)) + if(object.obj->blockingAt(int3(x, y, scene->level))) { return const_cast(object.obj); } @@ -541,7 +541,7 @@ CGObjectInstance * SelectionObjectsLayer::selectObjectAt(int x, int y, const CGO if(!object.obj || object.obj == ignore || lockedObjects.count(object.obj)) continue; - if(object.obj->coveringAt(x, y)) + if(object.obj->coveringAt(int3(x, y, scene->level))) { return const_cast(object.obj); } diff --git a/mapeditor/validator.cpp b/mapeditor/validator.cpp index fdab680b9..b4a98df30 100644 --- a/mapeditor/validator.cpp +++ b/mapeditor/validator.cpp @@ -122,13 +122,13 @@ std::set Validator::validate(const CMap * map) ++amountOfHeroes[ins->getOwner()]; } - if(ins->type) + if(ins->getHeroTypeID().hasValue()) { - if(map->allowedHeroes.count(ins->getHeroType()) == 0) - issues.insert({ tr("Hero %1 is prohibited by map settings").arg(ins->type->getNameTranslated().c_str()), false }); + if(map->allowedHeroes.count(ins->getHeroTypeID()) == 0) + issues.insert({ tr("Hero %1 is prohibited by map settings").arg(ins->getHeroType()->getNameTranslated().c_str()), false }); - if(!allHeroesOnMap.insert(ins->type).second) - issues.insert({ tr("Hero %1 has duplicate on map").arg(ins->type->getNameTranslated().c_str()), false }); + if(!allHeroesOnMap.insert(ins->getHeroType()).second) + issues.insert({ tr("Hero %1 has duplicate on map").arg(ins->getHeroType()->getNameTranslated().c_str()), false }); } else if(ins->ID != Obj::RANDOM_HERO) issues.insert({ tr("Hero %1 has an empty type and must be removed").arg(ins->instanceName.c_str()), true }); diff --git a/scripting/lua/api/Creature.cpp b/scripting/lua/api/Creature.cpp index 64c13c38b..5fca4601b 100644 --- a/scripting/lua/api/Creature.cpp +++ b/scripting/lua/api/Creature.cpp @@ -46,7 +46,7 @@ const std::vector CreatureProxy::REGISTER_CUSTOM = {"getLevel", LuaMethodWrapper::invoke, false}, {"getGrowth", LuaMethodWrapper::invoke, false}, {"getHorde", LuaMethodWrapper::invoke, false}, - {"getFaction", LuaMethodWrapper::invoke, false}, + {"getFactionID", LuaMethodWrapper::invoke, false}, {"getBaseAttack", LuaMethodWrapper::invoke, false}, {"getBaseDefense", LuaMethodWrapper::invoke, false}, diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 7a4f0a797..663107a3b 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -162,7 +162,7 @@ void CGameHandler::levelUpHero(const CGHeroInstance * hero) hlu.player = hero->tempOwner; hlu.heroId = hero->id; hlu.primskill = primarySkill; - hlu.skills = hero->getLevelUpProposedSecondarySkills(heroPool->getHeroSkillsRandomGenerator(hero->getHeroType())); + hlu.skills = hero->getLevelUpProposedSecondarySkills(heroPool->getHeroSkillsRandomGenerator(hero->getHeroTypeID())); if (hlu.skills.size() == 0) { @@ -235,7 +235,8 @@ void CGameHandler::levelUpCommander (const CCommanderInstance * c, int skill) scp.accumulatedBonus.type = BonusType::STACKS_SPEED; break; case ECommander::SPELL_POWER: - scp.accumulatedBonus.type = BonusType::MAGIC_RESISTANCE; + scp.accumulatedBonus.type = BonusType::SPELL_DAMAGE_REDUCTION; + scp.accumulatedBonus.subtype = BonusSubtypeID(SpellSchool::ANY); scp.accumulatedBonus.val = difference (VLC->creh->skillLevels, c->secondarySkills, ECommander::RESISTANCE); sendAndApply(scp); //additional pack scp.accumulatedBonus.type = BonusType::CREATURE_SPELL_POWER; @@ -552,7 +553,7 @@ void CGameHandler::init(StartInfo *si, Load::ProgressAccumulator & progressTrack for (auto & elem : gs->map->allHeroes) { if(elem) - heroPool->getHeroSkillsRandomGenerator(elem->getHeroType()); // init RMG seed + heroPool->getHeroSkillsRandomGenerator(elem->getHeroTypeID()); // init RMG seed } reinitScripting(); @@ -568,12 +569,12 @@ void CGameHandler::setPortalDwelling(const CGTownInstance * town, bool forced=fa return; } - if (forced || town->creatures.at(town->town->creatures.size()).second.empty())//we need to change creature + if (forced || town->creatures.at(town->getTown()->creatures.size()).second.empty())//we need to change creature { SetAvailableCreatures ssi; ssi.tid = town->id; ssi.creatures = town->creatures; - ssi.creatures[town->town->creatures.size()].second.clear();//remove old one + ssi.creatures[town->getTown()->creatures.size()].second.clear();//remove old one std::set availableCreatures; for (const auto & dwelling : p->getOwnedObjects()) @@ -589,13 +590,13 @@ void CGameHandler::setPortalDwelling(const CGTownInstance * town, bool forced=fa if (clear) { - ssi.creatures[town->town->creatures.size()].first = std::max(1, (creatureId.toEntity(VLC)->getGrowth())/2); + ssi.creatures[town->getTown()->creatures.size()].first = std::max(1, (creatureId.toEntity(VLC)->getGrowth())/2); } else { - ssi.creatures[town->town->creatures.size()].first = creatureId.toEntity(VLC)->getGrowth(); + ssi.creatures[town->getTown()->creatures.size()].first = creatureId.toEntity(VLC)->getGrowth(); } - ssi.creatures[town->town->creatures.size()].second.push_back(creatureId); + ssi.creatures[town->getTown()->creatures.size()].second.push_back(creatureId); sendAndApply(ssi); } } @@ -656,7 +657,7 @@ void CGameHandler::onNewTurn() PlayerColor player = t->tempOwner; if(t->hasBuilt(BuildingID::GRAIL) - && t->town->buildings.at(BuildingID::GRAIL)->height == CBuilding::HEIGHT_SKYSHIP) + && t->getTown()->buildings.at(BuildingID::GRAIL)->height == CBuilding::HEIGHT_SKYSHIP) { // Skyship, probably easier to handle same as Veil of darkness // do it every new day before veils @@ -803,7 +804,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveme return false; } - logGlobal->trace("Player %d (%s) wants to move hero %s from %s to %s", asker, asker.toString(), h->getNameTranslated(), h->pos.toString(), dst.toString()); + logGlobal->trace("Player %d (%s) wants to move hero %d from %s to %s", asker, asker.toString(), hid.getNum(), h->anchorPos().toString(), dst.toString()); const int3 hmpos = h->convertToVisitablePos(dst); if (!gs->map->isInTheMap(hmpos)) @@ -902,7 +903,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveme // should be called if hero changes tile but before applying TryMoveHero package auto leaveTile = [&]() { - for (CGObjectInstance *obj : gs->map->getTile(int3(h->pos.x-1, h->pos.y, h->pos.z)).visitableObjects) + for (CGObjectInstance *obj : gs->map->getTile(h->visitablePos()).visitableObjects) { obj->onHeroLeave(h); } @@ -1047,7 +1048,7 @@ bool CGameHandler::teleportHero(ObjectInstanceID hid, ObjectInstanceID dstid, ui if (((h->getOwner() != t->getOwner()) && complain("Cannot teleport hero to another player")) - || (from->town->faction->getId() != t->town->faction->getId() + || (from->getFactionID() != t->getFactionID() && complain("Source town and destination town should belong to the same faction")) || ((!from || !from->hasBuilt(BuildingSubID::CASTLE_GATE)) @@ -1211,7 +1212,7 @@ void CGameHandler::visitCastleObjects(const CGTownInstance * t, std::vectorrewardableBuildings) { - if (!t->town->buildings.at(building.first)->manualHeroVisit && t->hasBuilt(building.first)) + if (!t->getTown()->buildings.at(building.first)->manualHeroVisit && t->hasBuilt(building.first)) buildingsToVisit.push_back(building.first); } @@ -2035,12 +2036,12 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID, const CGTownInstance * t = getTown(tid); if(!t) COMPLAIN_RETF("No such town (ID=%s)!", tid); - if(!t->town->buildings.count(requestedID)) - COMPLAIN_RETF("Town of faction %s does not have info about building ID=%s!", t->town->faction->getNameTranslated() % requestedID); + if(!t->getTown()->buildings.count(requestedID)) + COMPLAIN_RETF("Town of faction %s does not have info about building ID=%s!", t->getFaction()->getNameTranslated() % requestedID); if(t->hasBuilt(requestedID)) - COMPLAIN_RETF("Building %s is already built in %s", t->town->buildings.at(requestedID)->getNameTranslated() % t->getNameTranslated()); + COMPLAIN_RETF("Building %s is already built in %s", t->getTown()->buildings.at(requestedID)->getNameTranslated() % t->getNameTranslated()); - const CBuilding * requestedBuilding = t->town->buildings.at(requestedID); + const CBuilding * requestedBuilding = t->getTown()->buildings.at(requestedID); //Vector with future list of built building and buildings in auto-mode that are not yet built. std::vector remainingAutoBuildings; @@ -2080,7 +2081,7 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID, int level = BuildingID::getLevelFromDwelling(buildingID); int upgradeNumber = BuildingID::getUpgradedFromDwelling(buildingID); - if(upgradeNumber >= t->town->creatures.at(level).size()) + if(upgradeNumber >= t->getTown()->creatures.at(level).size()) { complain(boost::str(boost::format("Error encountered when building dwelling (bid=%s):" "no creature found (upgrade number %d, level %d!") @@ -2088,7 +2089,7 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID, return; } - const CCreature * crea = t->town->creatures.at(level).at(upgradeNumber).toCreature(); + const CCreature * crea = t->getTown()->creatures.at(level).at(upgradeNumber).toCreature(); SetAvailableCreatures ssi; ssi.tid = t->id; @@ -2098,7 +2099,7 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID, ssi.creatures[level].second.push_back(crea->getId()); sendAndApply(ssi); } - if(t->town->buildings.at(buildingID)->subId == BuildingSubID::PORTAL_OF_SUMMONING) + if(t->getTown()->buildings.at(buildingID)->subId == BuildingSubID::PORTAL_OF_SUMMONING) { setPortalDwelling(t); } @@ -2109,9 +2110,9 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID, { auto isMageGuild = (buildingID <= BuildingID::MAGES_GUILD_5 && buildingID >= BuildingID::MAGES_GUILD_1); auto isLibrary = isMageGuild ? false - : t->town->buildings.at(buildingID)->subId == BuildingSubID::EBuildingSubID::LIBRARY; + : t->getTown()->buildings.at(buildingID)->subId == BuildingSubID::EBuildingSubID::LIBRARY; - if(isMageGuild || isLibrary || (t->getFaction() == ETownType::CONFLUX && buildingID == BuildingID::GRAIL)) + if(isMageGuild || isLibrary || (t->getFactionID() == ETownType::CONFLUX && buildingID == BuildingID::GRAIL)) { if(t->visitingHero) giveSpells(t,t->visitingHero); @@ -2127,7 +2128,7 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID, }; //Init the vectors - for(auto & build : t->town->buildings) + for(auto & build : t->getTown()->buildings) { if(t->hasBuilt(build.first)) { @@ -2211,7 +2212,7 @@ bool CGameHandler::visitTownBuilding(ObjectInstanceID tid, BuildingID bid) if(!t->hasBuilt(bid)) return false; - auto subID = t->town->buildings.at(bid)->subId; + auto subID = t->getTown()->buildings.at(bid)->subId; if(subID == BuildingSubID::EBuildingSubID::BANK) { @@ -2223,7 +2224,7 @@ bool CGameHandler::visitTownBuilding(ObjectInstanceID tid, BuildingID bid) return true; } - if (t->rewardableBuildings.count(bid) && t->visitingHero && t->town->buildings.at(bid)->manualHeroVisit) + if (t->rewardableBuildings.count(bid) && t->visitingHero && t->getTown()->buildings.at(bid)->manualHeroVisit) { std::vector buildingsToVisit; std::vector visitors; @@ -4012,6 +4013,9 @@ bool CGameHandler::isBlockedByQueries(const CPackForServer *pack, PlayerColor pl if (dynamic_cast(pack) != nullptr) return false; + if (dynamic_cast(pack) != nullptr) + return false; + auto query = queries->topQuery(player); if (query && query->blocksPack(pack)) { @@ -4222,8 +4226,11 @@ CGObjectInstance * CGameHandler::createNewObject(const int3 & visitablePosition, else o->appearance = handler->getTemplates().front(); + if (o->isVisitable()) + o->setAnchorPos(visitablePosition + o->getVisitableOffset()); + else + o->setAnchorPos(visitablePosition); - o->pos = visitablePosition + o->getVisitableOffset(); return o; } diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index fe4607786..ee23bcc9e 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -19,6 +19,7 @@ #include "queries/MapQueries.h" #include "../lib/IGameCallback.h" +#include "../lib/CPlayerState.h" #include "../lib/mapObjects/CGTownInstance.h" #include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/gameState/CGameState.h" @@ -283,7 +284,7 @@ void ApplyGhNetPackVisitor::visitTradeOnMarketplace(TradeOnMarketplace & pack) gh.throwAndComplain(&pack, "Can not trade - no hero!"); // TODO: check that object is actually being visited (e.g. Query exists) - if (!object->visitableAt(hero->visitablePos().x, hero->visitablePos().y)) + if (!object->visitableAt(hero->visitablePos())) gh.throwAndComplain(&pack, "Can not trade - object not visited!"); if (object->getOwner().isValidPlayer() && gh.getPlayerRelations(object->getOwner(), hero->getOwner()) == PlayerRelations::ENEMIES) @@ -389,6 +390,13 @@ void ApplyGhNetPackVisitor::visitQueryReply(QueryReply & pack) result = gh.queryReply(pack.qid, pack.reply, pack.player); } +void ApplyGhNetPackVisitor::visitSaveLocalState(SaveLocalState & pack) +{ + gh.throwIfWrongPlayer(&pack); + *gh.gameState()->getPlayerState(pack.player)->playerLocalSettings = pack.data; + result = true; +} + void ApplyGhNetPackVisitor::visitMakeAction(MakeAction & pack) { gh.throwIfWrongPlayer(&pack); diff --git a/server/ServerNetPackVisitors.h b/server/ServerNetPackVisitors.h index ba94157cb..73feeec8f 100644 --- a/server/ServerNetPackVisitors.h +++ b/server/ServerNetPackVisitors.h @@ -62,4 +62,5 @@ public: void visitDigWithHero(DigWithHero & pack) override; void visitCastAdvSpell(CastAdvSpell & pack) override; void visitPlayerMessage(PlayerMessage & pack) override; + void visitSaveLocalState(SaveLocalState & pack) override; }; diff --git a/server/processors/HeroPoolProcessor.cpp b/server/processors/HeroPoolProcessor.cpp index fd26db5f5..44a50884a 100644 --- a/server/processors/HeroPoolProcessor.cpp +++ b/server/processors/HeroPoolProcessor.cpp @@ -47,8 +47,8 @@ TavernHeroSlot HeroPoolProcessor::selectSlotForRole(const PlayerColor & player, // try to find "better" slot to overwrite // we want to avoid overwriting retreated heroes when tavern still has slot with random hero // as well as avoid overwriting surrendered heroes if we can overwrite retreated hero - auto roleLeft = heroesPool->getSlotRole(heroes[0]->getHeroType()); - auto roleRight = heroesPool->getSlotRole(heroes[1]->getHeroType()); + auto roleLeft = heroesPool->getSlotRole(heroes[0]->getHeroTypeID()); + auto roleRight = heroesPool->getSlotRole(heroes[1]->getHeroTypeID()); if (roleLeft > roleRight) return TavernHeroSlot::RANDOM; @@ -70,7 +70,7 @@ void HeroPoolProcessor::onHeroSurrendered(const PlayerColor & color, const CGHer sah.slotID = selectSlotForRole(color, sah.roleID); sah.player = color; - sah.hid = hero->getHeroType(); + sah.hid = hero->getHeroTypeID(); sah.replenishPoints = false; gameHandler->sendAndApply(sah); } @@ -82,9 +82,9 @@ void HeroPoolProcessor::onHeroEscaped(const PlayerColor & color, const CGHeroIns sah.slotID = selectSlotForRole(color, sah.roleID); sah.player = color; - sah.hid = hero->getHeroType(); + sah.hid = hero->getHeroTypeID(); sah.army.clearSlots(); - sah.army.setCreature(SlotID(0), hero->type->initialArmy.at(0).creature, 1); + sah.army.setCreature(SlotID(0), hero->getHeroType()->initialArmy.at(0).creature, 1); sah.replenishPoints = false; gameHandler->sendAndApply(sah); @@ -112,7 +112,7 @@ void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHe if (newHero) { - sah.hid = newHero->getHeroType(); + sah.hid = newHero->getHeroTypeID(); if (giveArmy) { @@ -123,7 +123,7 @@ void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHe { sah.roleID = TavernSlotRole::SINGLE_UNIT; sah.army.clearSlots(); - sah.army.setCreature(SlotID(0), newHero->type->initialArmy[0].creature, 1); + sah.army.setCreature(SlotID(0), newHero->getHeroType()->initialArmy[0].creature, 1); } } else @@ -208,7 +208,7 @@ bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTy for(const auto & hero : recruitableHeroes) { - if(hero->getHeroType() == heroToRecruit) + if(hero->getHeroTypeID() == heroToRecruit) recruitedHero = hero; } @@ -221,7 +221,7 @@ bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTy HeroRecruited hr; hr.tid = mapObject->id; - hr.hid = recruitedHero->getHeroType(); + hr.hid = recruitedHero->getHeroTypeID(); hr.player = player; hr.tile = recruitedHero->convertFromVisitablePos(targetPos ); if(gameHandler->getTile(targetPos)->isWater() && !recruitedHero->boat) @@ -258,14 +258,14 @@ std::vector HeroPoolProcessor::findAvailableClassesFor(const for(const auto & elem : heroesPool->unusedHeroesFromPool()) { - if (vstd::contains(result, elem.second->type->heroClass)) + if (vstd::contains(result, elem.second->getHeroClass())) continue; bool heroAvailable = heroesPool->isHeroAvailableFor(elem.first, player); - bool heroClassBanned = elem.second->type->heroClass->tavernProbability(factionID) == 0; + bool heroClassBanned = elem.second->getHeroClass()->tavernProbability(factionID) == 0; if(heroAvailable && !heroClassBanned) - result.push_back(elem.second->type->heroClass); + result.push_back(elem.second->getHeroClass()); } return result; @@ -282,7 +282,7 @@ std::vector HeroPoolProcessor::findAvailableHeroesFor(const Pl assert(!vstd::contains(result, elem.second)); bool heroAvailable = heroesPool->isHeroAvailableFor(elem.first, player); - bool heroClassMatches = elem.second->type->heroClass == heroClass; + bool heroClassMatches = elem.second->getHeroClass() == heroClass; if(heroAvailable && heroClassMatches) result.push_back(elem.second); @@ -318,7 +318,7 @@ const CHeroClass * HeroPoolProcessor::pickClassFor(bool isNative, const PlayerCo continue; bool hasSameClass = vstd::contains_if(currentTavern, [&](const CGHeroInstance * hero){ - return hero->type->heroClass == heroClass; + return hero->getHeroClass() == heroClass; }); if (hasSameClass) diff --git a/server/processors/NewTurnProcessor.cpp b/server/processors/NewTurnProcessor.cpp index 104a27761..bb5d4e3f7 100644 --- a/server/processors/NewTurnProcessor.cpp +++ b/server/processors/NewTurnProcessor.cpp @@ -95,10 +95,10 @@ void NewTurnProcessor::handleTownEvents(const CGTownInstance * town) // 1. Building exists in town (don't attempt to build Lvl 5 guild in Fortress // 2. Building was not built yet // othervice, silently ignore / skip it - if (town->town->buildings.count(i) && !town->hasBuilt(i)) + if (town->getTown()->buildings.count(i) && !town->hasBuilt(i)) { gameHandler->buildStructure(town->id, i, true); - iw.components.emplace_back(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFaction(), i)); + iw.components.emplace_back(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFactionID(), i)); } } @@ -292,7 +292,7 @@ SetAvailableCreatures NewTurnProcessor::generateTownGrowth(const CGTownInstance sac.tid = t->id; sac.creatures = t->creatures; - for (int k=0; k < t->town->creatures.size(); k++) + for (int k=0; k < t->getTown()->creatures.size(); k++) { if (t->creatures.at(k).second.empty()) continue; @@ -391,7 +391,7 @@ void NewTurnProcessor::updateNeutralTownGarrison(const CGTownInstance * t, int c { const auto * creature = slot.second->type; - if (creature->getFaction() != t->getFaction()) + if (creature->getFactionID() != t->getFactionID()) continue; if (creature->getLevel() != tierToGrow) @@ -564,7 +564,7 @@ std::tuple NewTurnProcessor::pickWeekType(bool newMonth) { newMonster.second = VLC->creh->pickRandomMonster(gameHandler->getRandomGenerator()); } while (VLC->creh->objects[newMonster.second] && - (*VLC->townh)[VLC->creatures()->getById(newMonster.second)->getFaction()]->town == nullptr); // find first non neutral creature + (*VLC->townh)[VLC->creatures()->getById(newMonster.second)->getFactionID()]->town == nullptr); // find first non neutral creature return { EWeekType::BONUS_GROWTH, newMonster.second}; } diff --git a/server/processors/PlayerMessageProcessor.cpp b/server/processors/PlayerMessageProcessor.cpp index 83183d478..6ebc78375 100644 --- a/server/processors/PlayerMessageProcessor.cpp +++ b/server/processors/PlayerMessageProcessor.cpp @@ -370,7 +370,7 @@ void PlayerMessageProcessor::cheatBuildTown(PlayerColor player, const CGTownInst if (!town) return; - for (auto & build : town->town->buildings) + for (auto & build : town->getTown()->buildings) { if (!town->hasBuilt(build.first) && !build.second->getNameTranslated().empty() diff --git a/test/entity/CCreatureTest.cpp b/test/entity/CCreatureTest.cpp index 590754e73..3d963df09 100644 --- a/test/entity/CCreatureTest.cpp +++ b/test/entity/CCreatureTest.cpp @@ -100,7 +100,7 @@ TEST_F(CCreatureTest, DISABLED_JsonUpdate) EXPECT_EQ(subject->getFightValue(), 2420); EXPECT_EQ(subject->getLevel(), 6); - EXPECT_EQ(subject->getFaction().getNum(), 55); + EXPECT_EQ(subject->getFactionID().getNum(), 55); EXPECT_TRUE(subject->isDoubleWide()); } diff --git a/test/mock/mock_Creature.h b/test/mock/mock_Creature.h index 6dacd4815..cb51a595a 100644 --- a/test/mock/mock_Creature.h +++ b/test/mock/mock_Creature.h @@ -44,7 +44,7 @@ public: MOCK_CONST_METHOD0(getLevel, int32_t()); MOCK_CONST_METHOD0(getGrowth, int32_t()); MOCK_CONST_METHOD0(getHorde, int32_t()); - MOCK_CONST_METHOD0(getFaction, FactionID()); + MOCK_CONST_METHOD0(getFactionID, FactionID()); MOCK_CONST_METHOD0(getBaseAttack, int32_t()); MOCK_CONST_METHOD0(getBaseDefense, int32_t()); diff --git a/test/mock/mock_battle_Unit.h b/test/mock/mock_battle_Unit.h index 5f2b03bf0..7560bfdc8 100644 --- a/test/mock/mock_battle_Unit.h +++ b/test/mock/mock_battle_Unit.h @@ -82,7 +82,7 @@ public: MOCK_CONST_METHOD1(willMove, bool(int)); MOCK_CONST_METHOD1(waited, bool(int)); - MOCK_CONST_METHOD0(getFaction, FactionID()); + MOCK_CONST_METHOD0(getFactionID, FactionID()); MOCK_CONST_METHOD1(battleQueuePhase, battle::BattlePhases::Type(int)); diff --git a/vcmiqt/vcmiqt.h b/vcmiqt/vcmiqt.h index a3cf5f8c0..4783eaaae 100644 --- a/vcmiqt/vcmiqt.h +++ b/vcmiqt/vcmiqt.h @@ -10,7 +10,7 @@ #include -#if VCMIQT_STATIC +#ifdef VCMIQT_STATIC # define VCMIQT_LINKAGE #elif defined(VCMIQT_SHARED) # define VCMIQT_LINKAGE Q_DECL_EXPORT