1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-03-19 21:10:12 +02:00

Merge remote-tracking branch 'upstream/develop' into develop

This commit is contained in:
Xilmi 2024-10-10 17:23:39 +02:00
commit 61fba1fedd
139 changed files with 2236 additions and 1902 deletions

View File

@ -17,6 +17,7 @@ env:
jobs: jobs:
build: build:
strategy: strategy:
fail-fast: false
matrix: matrix:
include: include:
- platform: linux-qt6 - platform: linux-qt6

View File

@ -864,7 +864,7 @@ void AIGateway::makeTurn()
void AIGateway::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h) 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) switch(obj->ID)
{ {
case Obj::TOWN: case Obj::TOWN:
@ -1454,8 +1454,8 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
void AIGateway::buildStructure(const CGTownInstance * t, BuildingID building) void AIGateway::buildStructure(const CGTownInstance * t, BuildingID building)
{ {
auto name = t->town->buildings.at(building)->getNameTranslated(); 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->pos.toString()); 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; cb->buildBuilding(t, building); //just do this;
} }

View File

@ -144,7 +144,7 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
for(auto & slot : sortedSlots) for(auto & slot : sortedSlots)
{ {
alignmentMap[slot.creature->getFaction()] += slot.power; alignmentMap[slot.creature->getFactionID()] += slot.power;
} }
std::set<FactionID> allowedFactions; std::set<FactionID> allowedFactions;
@ -168,7 +168,7 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
for(auto & slot : sortedSlots) 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()); auto slotID = newArmyInstance.getSlotFor(slot.creature->getId());

View File

@ -17,7 +17,7 @@ namespace NKAI
void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo) void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo)
{ {
auto townInfo = developmentInfo.town->town; auto townInfo = developmentInfo.town->getTown();
auto creatures = townInfo->creatures; auto creatures = townInfo->creatures;
auto buildings = townInfo->getAllBuildings(); 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); logAi->trace("Checking dwelling level %d", level);
BuildingInfo nextToBuild = BuildingInfo(); BuildingInfo nextToBuild = BuildingInfo();
@ -88,7 +88,7 @@ void BuildAnalyzer::updateOtherBuildings(TownDevelopmentInfo & developmentInfo)
{ {
for(auto & buildingID : buildingSet) 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)); developmentInfo.addBuildingToBuild(getBuildingOrPrerequisite(developmentInfo.town, buildingID));
@ -203,7 +203,7 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
bool excludeDwellingDependencies) const bool excludeDwellingDependencies) const
{ {
BuildingID building = toBuild; BuildingID building = toBuild;
auto townInfo = town->town; auto townInfo = town->getTown();
const CBuilding * buildPtr = townInfo->buildings.at(building); const CBuilding * buildPtr = townInfo->buildings.at(building);
const CCreature * creature = nullptr; const CCreature * creature = nullptr;
@ -346,7 +346,7 @@ bool BuildAnalyzer::hasAnyBuilding(int32_t alignment, BuildingID bid) const
{ {
for(auto tdi : developmentInfos) for(auto tdi : developmentInfos)
{ {
if(tdi.town->getFaction() == alignment && tdi.town->hasBuilt(bid)) if(tdi.town->getFactionID() == alignment && tdi.town->hasBuilt(bid))
return true; return true;
} }

View File

@ -71,7 +71,7 @@ float HeroManager::evaluateSecSkill(SecondarySkill skill, const CGHeroInstance *
float HeroManager::evaluateSpeciality(const CGHeroInstance * hero) const 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 secondarySkillBonus = Selector::targetSourceType()(BonusSource::SECONDARY_SKILL);
auto specialSecondarySkillBonuses = hero->getBonuses(heroSpecial.And(secondarySkillBonus)); auto specialSecondarySkillBonuses = hero->getBonuses(heroSpecial.And(secondarySkillBonus));
auto secondarySkillBonuses = hero->getBonuses(Selector::sourceTypeSel(BonusSource::SECONDARY_SKILL)); auto secondarySkillBonuses = hero->getBonuses(Selector::sourceTypeSel(BonusSource::SECONDARY_SKILL));

View File

@ -1244,7 +1244,7 @@ public:
uint64_t RewardEvaluator::getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const 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; return 0;
auto creaturesToUpgrade = ai->armyManager->getTotalCreaturesAvailable(bi.baseCreatureID); auto creaturesToUpgrade = ai->armyManager->getTotalCreaturesAvailable(bi.baseCreatureID);

View File

@ -23,7 +23,7 @@ BuildThis::BuildThis(BuildingID Bid, const CGTownInstance * tid)
: ElementarGoal(Goals::BUILD_STRUCTURE) : ElementarGoal(Goals::BUILD_STRUCTURE)
{ {
buildingInfo = BuildingInfo( buildingInfo = BuildingInfo(
tid->town->buildings.at(Bid), tid->getTown()->buildings.at(Bid),
nullptr, nullptr,
CreatureID::NONE, CreatureID::NONE,
tid, tid,
@ -52,7 +52,7 @@ void BuildThis::accept(AIGateway * ai)
if(cb->canBuildStructure(town, b) == EBuildingState::ALLOWED) if(cb->canBuildStructure(town, b) == EBuildingState::ALLOWED)
{ {
logAi->debug("Player %d will build %s in town of %s at %s", 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); cb->buildBuilding(town, b);
return; return;

View File

@ -23,13 +23,13 @@ bool BuildingManager::tryBuildThisStructure(const CGTownInstance * t, BuildingID
return false; return false;
} }
if (!vstd::contains(t->town->buildings, building)) if (!vstd::contains(t->getTown()->buildings, building))
return false; // no such building in town return false; // no such building in town
if (t->hasBuilt(building)) //Already built? Shouldn't happen in general if (t->hasBuilt(building)) //Already built? Shouldn't happen in general
return true; 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) auto toBuild = buildPtr->requirements.getFulfillmentCandidates([&](const BuildingID & buildID)
{ {
@ -51,7 +51,7 @@ bool BuildingManager::tryBuildThisStructure(const CGTownInstance * t, BuildingID
for (const auto & buildID : toBuild) 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); EBuildingState canBuild = cb->canBuildStructure(t, buildID);
if (canBuild == EBuildingState::ALLOWED) 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) //at the end, try to get and build any extra buildings with nonstandard slots (for example HotA 3rd level dwelling)
std::vector<BuildingID> extraBuildings; std::vector<BuildingID> extraBuildings;
for (auto buildingInfo : t->town->buildings) for (auto buildingInfo : t->getTown()->buildings)
{ {
if (buildingInfo.first > BuildingID::DWELL_UP2_FIRST) if (buildingInfo.first > BuildingID::DWELL_UP2_FIRST)
extraBuildings.push_back(buildingInfo.first); extraBuildings.push_back(buildingInfo.first);

View File

@ -56,7 +56,7 @@ TSubgoal BuildThis::whatToDoToAchieve()
case EBuildingState::ALLOWED: case EBuildingState::ALLOWED:
case EBuildingState::NO_RESOURCES: 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 return ai->ah->whatToDo(res, iAmElementar()); //realize immediately or gather resources
} }
break; break;

View File

@ -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())); return sptr(VisitObj(o->id.getNum()));
else else
return sptr(Explore()); return sptr(Explore());

View File

@ -88,13 +88,13 @@ TGoalVec GatherTroops::getAllPossibleSubgoals()
} }
auto creature = VLC->creatures()->getByIndex(objid); 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<std::vector<CreatureID>> auto tryFindCreature = [&]() -> std::optional<std::vector<CreatureID>>
{ {
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); std::advance(itr, creature->getLevel() - 1);
return make_optional(*itr); return make_optional(*itr);
} }
@ -109,7 +109,7 @@ TGoalVec GatherTroops::getAllPossibleSubgoals()
if(upgradeNumber < 0) if(upgradeNumber < 0)
continue; 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 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))); solutions.push_back(sptr(BuyArmy(t, creature->getAIValue() * this->value).setobjid(objid)));

View File

@ -69,7 +69,7 @@ std::optional<int> 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 //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<const CGHeroInstance*>(obj); auto hero = dynamic_cast<const CGHeroInstance*>(obj);
return getObjectValue(obj->ID, hero->type->heroClass->getIndex()); return getObjectValue(obj->ID, hero->getHeroClassID());
} }
else if(obj->ID == Obj::PRISON) else if(obj->ID == Obj::PRISON)
{ {

View File

@ -1032,7 +1032,7 @@ void VCAI::mainLoop()
void VCAI::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h) 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) switch(obj->ID)
{ {
case Obj::TOWN: case Obj::TOWN:
@ -1417,11 +1417,11 @@ void VCAI::wander(HeroPtr h)
//TODO pick the truly best //TODO pick the truly best
const CGTownInstance * t = *boost::max_element(townsNotReachable, compareReinforcements); 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()); 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 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 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)) if(canRecruitAnyHero(t))
recruitHero(t); recruitHero(t);
@ -1471,7 +1471,7 @@ void VCAI::wander(HeroPtr h)
{ {
auto chosenObject = cb->getObjInstance(ObjectInstanceID(bestObjectGoal->objid)); auto chosenObject = cb->getObjInstance(ObjectInstanceID(bestObjectGoal->objid));
if(chosenObject != nullptr) 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 else
logAi->debug("Trying to realize goal of type %s as part of wandering.", bestObjectGoal->name()); 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) void VCAI::buildStructure(const CGTownInstance * t, BuildingID building)
{ {
auto name = t->town->buildings.at(building)->getNameTranslated(); 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->pos.toString()); 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; cb->buildBuilding(t, building); //just do this;
} }
@ -2081,7 +2081,7 @@ void VCAI::tryRealize(Goals::BuildThis & g)
if (cb->canBuildStructure(t, b) == EBuildingState::ALLOWED) if (cb->canBuildStructure(t, b) == EBuildingState::ALLOWED)
{ {
logAi->debug("Player %d will build %s in town of %s at %s", 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); cb->buildBuilding(t, b);
throw goalFulfilledException(sptr(g)); throw goalFulfilledException(sptr(g));
} }

View File

@ -25,6 +25,7 @@
#include "lib/UnlockGuard.h" #include "lib/UnlockGuard.h"
#include "lib/battle/BattleInfo.h" #include "lib/battle/BattleInfo.h"
#include "lib/networkPacks/PacksForServer.h" #include "lib/networkPacks/PacksForServer.h"
#include "lib/networkPacks/SaveLocalState.h"
bool CCallback::teleportHero(const CGHeroInstance *who, const CGTownInstance *where) bool CCallback::teleportHero(const CGHeroInstance *who, const CGTownInstance *where)
{ {
@ -318,11 +319,20 @@ void CCallback::recruitHero(const CGObjectInstance *townOrTavern, const CGHeroIn
assert(townOrTavern); assert(townOrTavern);
assert(hero); assert(hero);
HireHero pack(hero->getHeroType(), townOrTavern->id, nextHero); HireHero pack(hero->getHeroTypeID(), townOrTavern->id, nextHero);
pack.player = *player; pack.player = *player;
sendRequest(pack); 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 ) void CCallback::save( const std::string &fname )
{ {
cl->save(fname); cl->save(fname);

View File

@ -100,6 +100,7 @@ public:
virtual void assembleArtifacts(const ObjectInstanceID & heroID, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)=0; virtual void assembleArtifacts(const ObjectInstanceID & heroID, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)=0;
virtual void eraseArtifactByClient(const ArtifactLocation & al)=0; virtual void eraseArtifactByClient(const ArtifactLocation & al)=0;
virtual bool dismissCreature(const CArmedInstance *obj, SlotID stackPos)=0; virtual bool dismissCreature(const CArmedInstance *obj, SlotID stackPos)=0;
virtual void saveLocalState(const JsonNode & data)=0;
virtual void endTurn()=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 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; 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; 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 dismissCreature(const CArmedInstance *obj, SlotID stackPos) override;
bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE) override; bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE) override;
void saveLocalState(const JsonNode & data) override;
void endTurn() override; void endTurn() override;
void spellResearch(const CGTownInstance *town, SpellID spellAtSlot, bool accepted) override; void spellResearch(const CGTownInstance *town, SpellID spellAtSlot, bool accepted) override;
void swapGarrisonHero(const CGTownInstance *town) override; void swapGarrisonHero(const CGTownInstance *town) override;

View File

@ -13,6 +13,8 @@
"vcmi.adventureMap.monsterThreat.levels.10" : "Dödlig", "vcmi.adventureMap.monsterThreat.levels.10" : "Dödlig",
"vcmi.adventureMap.monsterThreat.levels.11" : "Omöjlig", "vcmi.adventureMap.monsterThreat.levels.11" : "Omöjlig",
"vcmi.adventureMap.monsterLevel" : "\n\nNivå: %LEVEL - Faktion: %TOWN", "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.confirmRestartGame" : "Är du säker på att du vill starta om spelet?",
"vcmi.adventureMap.noTownWithMarket" : "Det finns inga tillgängliga marknadsplatser!", "vcmi.adventureMap.noTownWithMarket" : "Det finns inga tillgängliga marknadsplatser!",
@ -21,7 +23,7 @@
"vcmi.adventureMap.playerAttacked" : "Spelare har blivit attackerad: %s", "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.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.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.adventureMap.replayOpponentTurnNotImplemented" : "Tyvärr, att spela om motståndarens tur är inte implementerat ännu!",
"vcmi.capitalColors.0" : "Röd", "vcmi.capitalColors.0" : "Röd",
@ -135,14 +137,15 @@
"vcmi.lobby.pvp.coin.hover" : "Mynt", "vcmi.lobby.pvp.coin.hover" : "Mynt",
"vcmi.lobby.pvp.coin.help" : "Singla slant", "vcmi.lobby.pvp.coin.help" : "Singla slant",
"vcmi.lobby.pvp.randomTown.hover" : "Slumpmässig stad", "vcmi.lobby.pvp.randomTown.hover" : "Slumpmässig stad",
"vcmi.lobby.pvp.randomTown.help" : "Skriv en slumpmässig stad i chatten", "vcmi.lobby.pvp.randomTown.help" : "Skriv en slumpad stad i chatten",
"vcmi.lobby.pvp.randomTownVs.hover" : "Slumpmässig stad vs.", "vcmi.lobby.pvp.randomTownVs.hover" : "Slumpad stad vs.",
"vcmi.lobby.pvp.randomTownVs.help" : "Skriv två slumpmässiga städer i chatten", "vcmi.lobby.pvp.randomTownVs.help" : "Skriv två slumpade städer i chatten",
"vcmi.lobby.pvp.versus" : "vs.", "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.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.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.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.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.modsToEnable" : "{Följande modd(ar) krävs}",
"vcmi.server.errors.modsToDisable" : "{Följande modd(ar) måste inaktiveras}", "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.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.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.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.hover" : "Tyst 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.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.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.", "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.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.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.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.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.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.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.hover" : "Mjuk kartdragning",
"vcmi.adventureOptions.smoothDragging.help" : "{Mjuk kartdragning}\n\nVid aktivering så har kartdragningen en modern rullningseffekt.", "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.animationsSpeed1.help" : "Ställ in animationshastigheten till mycket långsam.",
"vcmi.battleOptions.animationsSpeed5.help" : "Ställ in animationshastigheten till mycket snabb.", "vcmi.battleOptions.animationsSpeed5.help" : "Ställ in animationshastigheten till mycket snabb.",
"vcmi.battleOptions.animationsSpeed6.help" : "Ställ in animationshastigheten till omedelbar.", "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.hover" : "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.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.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.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.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.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.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.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.hover" : "Snabbstrid (AI)",
"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.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.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", "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.turnOptions.help" : "Välj alternativ för turomgångs-timer och simultana turer",
"vcmi.optionsTab.chessFieldBase.hover" : "Bas-timern", "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.chessFieldBattle.hover" : "Strids-timern",
"vcmi.optionsTab.chessFieldUnit.hover" : "Enhets-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.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.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.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.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.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", "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.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.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.unlimited" : "Obegränsat med tid",
"vcmi.optionsTab.turnTime.classic.1" : "Klassisk timer: 1 minut", "vcmi.optionsTab.turnTime.classic.1" : "Klassisk timer: 1 minut",
"vcmi.optionsTab.turnTime.classic.2" : "Klassisk timer: 2 minuter", "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.10" : "Klassisk timer: 10 minuter",
"vcmi.optionsTab.turnTime.classic.20" : "Klassisk timer: 20 minuter", "vcmi.optionsTab.turnTime.classic.20" : "Klassisk timer: 20 minuter",
"vcmi.optionsTab.turnTime.classic.30" : "Klassisk timer: 30 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.20" : "Schack: 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.16" : "Schack: 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.8" : "Schack: 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.4" : "Schack: 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.2" : "Schack: 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.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.none" : "Inga simultana/samtidiga turer",
"vcmi.optionsTab.simturns.tillContactMax" : "Simultantur: Fram till kontakt", "vcmi.optionsTab.simturns.tillContactMax" : "Sam-tur: Fram till kontakt",
"vcmi.optionsTab.simturns.tillContact1" : "Simultantur: 1 vecka, bryt vid kontakt", "vcmi.optionsTab.simturns.tillContact1" : "Sam-tur: 1 vecka, bryt vid kontakt",
"vcmi.optionsTab.simturns.tillContact2" : "Simultantur: 2 veckor, bryt vid kontakt", "vcmi.optionsTab.simturns.tillContact2" : "Sam-tur: 2 veckor, bryt vid kontakt",
"vcmi.optionsTab.simturns.tillContact4" : "Simultantur: 1 månad, bryt vid kontakt", "vcmi.optionsTab.simturns.tillContact4" : "Sam-tur: 1 månad, bryt vid kontakt",
"vcmi.optionsTab.simturns.blocked1" : "Simultantur: 1 vecka, kontakter blockerade", "vcmi.optionsTab.simturns.blocked1" : "Sam-tur: 1 vecka, kontakter blockerade",
"vcmi.optionsTab.simturns.blocked2" : "Simultantur: 2 veckor, kontakter blockerade", "vcmi.optionsTab.simturns.blocked2" : "Sam-tur: 2 veckor, kontakter blockerade",
"vcmi.optionsTab.simturns.blocked4" : "Simultantur: 1 månad, 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 // 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 // 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.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.name" : "Attackera runtomkring",
"core.bonus.ATTACKS_ALL_ADJACENT.description" : "Attackerar alla angränsande fiender", "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_RETALIATION.description" : "Fienden kan inte slå tillbaka/retaliera",
"core.bonus.BLOCKS_RANGED_RETALIATION.name" : "Ingen motattack på avstånd", "core.bonus.BLOCKS_RANGED_RETALIATION.name" : "Blockera fjärrstrids-motattack",
"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.description" : "Fienden kan inte retaliera på avstånd genom att använda en distansattack",
"core.bonus.CATAPULT.name" : "Katapult", "core.bonus.CATAPULT.name" : "Katapult",
"core.bonus.CATAPULT.description" : "Attackerar belägringsmurar", "core.bonus.CATAPULT.description" : "Attackerar belägringsmurar",
"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name" : "Minska trollformelkostnaden (${val})", "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.name" : "Galoppanfalls-immunitet",
"core.bonus.CHARGE_IMMUNITY.description" : "Immun mot ryttares och tornerares galopperande ridanfall", "core.bonus.CHARGE_IMMUNITY.description" : "Immun mot ryttares och tornerares galopperande ridanfall",
"core.bonus.DARKNESS.name" : "I skydd av mörkret", "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.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.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.name" : "Förintelse",
"core.bonus.DESTRUCTION.description" : "Har ${val}% chans att döda extra enheter efter attack", "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.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.name" : "Drake",
"core.bonus.DRAGON_NATURE.description" : "Varelsen har en draknatur", "core.bonus.DRAGON_NATURE.description" : "Varelsen har en draknatur",
"core.bonus.EARTH_IMMUNITY.name" : "Jord-immunitet", "core.bonus.EARTH_IMMUNITY.name" : "Jord-immunitet",
"core.bonus.EARTH_IMMUNITY.description" : "Immun mot alla trollformler från skolan för jordmagi", "core.bonus.EARTH_IMMUNITY.description" : "Immun mot alla trollformler från skolan för jordmagi",
"core.bonus.ENCHANTER.name" : "Förtrollare", "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.name" : "Förtrollad",
"core.bonus.ENCHANTED.description" : "Påverkas av permanent ${subtype.spell}", "core.bonus.ENCHANTED.description" : "Påverkas av permanent ${subtype.spell}",
"core.bonus.ENEMY_ATTACK_REDUCTION.name" : "Avfärda attack (${val}%)", "core.bonus.ENEMY_ATTACK_REDUCTION.name" : "Avfärda attack (${val}%)",
@ -578,15 +581,15 @@
"core.bonus.GARGOYLE.name" : "Stenfigur", "core.bonus.GARGOYLE.name" : "Stenfigur",
"core.bonus.GARGOYLE.description" : "Kan varken upplivas eller läkas", "core.bonus.GARGOYLE.description" : "Kan varken upplivas eller läkas",
"core.bonus.GENERAL_DAMAGE_REDUCTION.name" : "Minska skada (${val}%)", "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.name" : "Hatar ${subtyp.varelse}",
"core.bonus.HATE.description" : "Gör ${val}% mer skada mot ${subtyp.varelse}", "core.bonus.HATE.description" : "Gör ${val}% mer skada mot ${subtyp.varelse}",
"core.bonus.HEALER.name" : "Helare", "core.bonus.HEALER.name" : "Helare",
"core.bonus.HEALER.description" : "Helar/läker allierade enheter", "core.bonus.HEALER.description" : "Helar/läker allierade enheter",
"core.bonus.HP_REGENERATION.name" : "Självläkande", "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.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.name" : "Kung",
"core.bonus.KING.description" : "Sårbar för 'Dräpar'-nivå ${val} eller högre", "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}", "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.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.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.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.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.name" : "Dränera trollformelspoäng",
"core.bonus.MANA_DRAIN.description" : "Dränerar ${val} trollformelspoäng varje tur", "core.bonus.MANA_DRAIN.description" : "Dränerar ${val} trollformelspoäng varje tur",
"core.bonus.MAGIC_MIRROR.name" : "Magisk spegel (${val}%)", "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.name" : "Magiskt motstånd (${val}%)",
"core.bonus.MAGIC_RESISTANCE.description" : "Har en ${val}% chans att motstå en skadlig trollformel", "core.bonus.MAGIC_RESISTANCE.description" : "Har en ${val}% chans att motstå en skadlig trollformel",
"core.bonus.MIND_IMMUNITY.name" : "Immunitet mot sinnesförtrollningar", "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.name" : "Ingen närstridsbestraffning",
"core.bonus.NO_MELEE_PENALTY.description" : "Varelsen har 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.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.name" : "Ingen murbestraffning",
"core.bonus.NO_WALL_PENALTY.description" : "Orsakar full skada mot fiender bakom en mur", "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.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.name" : "Slumpmässig besvärjare",
"core.bonus.RANDOM_SPELLCASTER.description" : "Kan kasta trollformler som väljs slumpmässigt", "core.bonus.RANDOM_SPELLCASTER.description" : "Kan kasta trollformler som väljs slumpmässigt",
"core.bonus.RANGED_RETALIATION.name" : "Motattacker på avstånd", "core.bonus.RANGED_RETALIATION.name" : "Motattacker på avstånd",
@ -624,21 +627,21 @@
"core.bonus.REBIRTH.name" : "Återfödelse (${val}%)", "core.bonus.REBIRTH.name" : "Återfödelse (${val}%)",
"core.bonus.REBIRTH.description" : "${val}% av enheterna kommer att återuppväckas efter döden", "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.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.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.name" : "Distans-attack",
"core.bonus.SHOOTER.description" : "Varelsen kan skjuta/attackera på avstånd", "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.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.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.name" : "Besvärjare",
"core.bonus.SPELLCASTER.description" : "Kan kasta ${subtype.spell}", "core.bonus.SPELLCASTER.description" : "Kan kasta ${subtype.spell}",
"core.bonus.SPELL_AFTER_ATTACK.name" : "Besvärja efter attack", "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.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.name" : "Trolldoms-resistens",
"core.bonus.SPELL_DAMAGE_REDUCTION.description" : "Skadan från trollformler är reducet med ${val}%.", "core.bonus.SPELL_DAMAGE_REDUCTION.description" : "Skadan från trollformler är reducet med ${val}%.",
"core.bonus.SPELL_IMMUNITY.name" : "Trolldoms-immunitet", "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.TRANSMUTATION.description" : "${val}% chans att förvandla angripen enhet till en annan typ",
"core.bonus.UNDEAD.name" : "Odöd", "core.bonus.UNDEAD.name" : "Odöd",
"core.bonus.UNDEAD.description" : "Varelsen är odöd", "core.bonus.UNDEAD.description" : "Varelsen är odöd",
"core.bonus.UNLIMITED_RETALIATIONS.name" : "Obegränsat antal motattacker", "core.bonus.UNLIMITED_RETALIATIONS.name" : "Slår tillbaka varje gång",
"core.bonus.UNLIMITED_RETALIATIONS.description" : "Kan slå tillbaka mot ett obegränsat antal attacker varje omgång", "core.bonus.UNLIMITED_RETALIATIONS.description" : "Obegränsat antal motattacker",
"core.bonus.WATER_IMMUNITY.name" : "Vatten-immunitet", "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.name" : "Bred dödlig andedräkt",
"core.bonus.WIDE_BREATH.description" : "Bred andningsattack (flera rutor)", "core.bonus.WIDE_BREATH.description" : "Bred andningsattack (flera rutor)",
"core.bonus.DISINTEGRATE.name" : "Desintegrerar", "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.name" : "Oövervinnerlig",
"core.bonus.INVINCIBLE.description" : "Kan inte påverkas av någonting" "core.bonus.INVINCIBLE.description" : "Kan inte påverkas av någonting"
} }

View File

@ -1138,7 +1138,7 @@ void CPlayerInterface::showMapObjectSelectDialog(QueryID askID, const Component
const CGTownInstance * t = dynamic_cast<const CGTownInstance *>(cb->getObj(obj)); const CGTownInstance * t = dynamic_cast<const CGTownInstance *>(cb->getObj(obj));
if(t) 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)); image->scaleTo(Point(35, 23));
images.push_back(image); images.push_back(image);
} }
@ -1333,6 +1333,8 @@ void CPlayerInterface::initializeHeroTownList()
localState->addOwnedTown(town); localState->addOwnedTown(town);
} }
localState->deserialize(*cb->getPlayerState(playerID)->playerLocalSettings);
if(adventureInt) if(adventureInt)
adventureInt->onHeroChanged(nullptr); adventureInt->onHeroChanged(nullptr);
} }

View File

@ -453,7 +453,7 @@ void ClientCommandManager::handleTellCommand(std::istringstream& singleWordBuffe
if(what == "hs") if(what == "hs")
{ {
for(const CGHeroInstance* h : LOCPLINT->cb->getHeroesInfo()) 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))) if(const CArtifactInstance* a = h->getArt(ArtifactPosition(id2)))
printCommandMessage(a->nodeName()); printCommandMessage(a->nodeName());
} }

View File

@ -375,7 +375,7 @@ void HeroMovementController::sendMovementRequest(const CGHeroInstance * h, const
{ {
updateMovementSound(h, currNode.coord, nextNode.coord, nextNode.action); 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()); logGlobal->trace("Requesting hero movement to %s", nextNode.coord.toString());

View File

@ -671,7 +671,7 @@ void ApplyClientNetPackVisitor::visitSetHeroesInTown(SetHeroesInTown & pack)
void ApplyClientNetPackVisitor::visitHeroRecruited(HeroRecruited & pack) void ApplyClientNetPackVisitor::visitHeroRecruited(HeroRecruited & pack)
{ {
CGHeroInstance *h = gs.map->heroesOnMap.back(); CGHeroInstance *h = gs.map->heroesOnMap.back();
if(h->getHeroType() != pack.hid) if(h->getHeroTypeID() != pack.hid)
{ {
logNetwork->error("Something wrong with hero recruited!"); logNetwork->error("Something wrong with hero recruited!");
} }

View File

@ -11,6 +11,7 @@
#include "PlayerLocalState.h" #include "PlayerLocalState.h"
#include "../CCallback.h" #include "../CCallback.h"
#include "../lib/json/JsonNode.h"
#include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/mapObjects/CGHeroInstance.h"
#include "../lib/mapObjects/CGTownInstance.h" #include "../lib/mapObjects/CGTownInstance.h"
#include "../lib/pathfinder/CGPathNode.h" #include "../lib/pathfinder/CGPathNode.h"
@ -23,34 +24,20 @@ PlayerLocalState::PlayerLocalState(CPlayerInterface & owner)
{ {
} }
void PlayerLocalState::saveHeroPaths(std::map<const CGHeroInstance *, int3> & pathsMap) const PlayerSpellbookSetting & PlayerLocalState::getSpellbookSettings()
{ {
for(auto & p : paths) return spellbookSettings;
{
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());
}
} }
void PlayerLocalState::loadHeroPaths(std::map<const CGHeroInstance *, int3> & pathsMap) void PlayerLocalState::setSpellbookSettings(const PlayerSpellbookSetting & newSettings)
{ {
if(owner.cb) spellbookSettings = newSettings;
{
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());
}
}
} }
void PlayerLocalState::setPath(const CGHeroInstance * h, const CGPath & path) void PlayerLocalState::setPath(const CGHeroInstance * h, const CGPath & path)
{ {
paths[h] = path; paths[h] = path;
syncronizeState();
} }
const CGPath & PlayerLocalState::getPath(const CGHeroInstance * h) const 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)) 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) paths.erase(h); //invalidate previously possible path if selected (before other hero blocked only path / fly spell expired)
syncronizeState();
return false; return false;
} }
@ -93,6 +81,7 @@ void PlayerLocalState::erasePath(const CGHeroInstance * h)
{ {
paths.erase(h); paths.erase(h);
adventureInt->onHeroChanged(h); adventureInt->onHeroChanged(h);
syncronizeState();
} }
void PlayerLocalState::verifyPath(const CGHeroInstance * h) void PlayerLocalState::verifyPath(const CGHeroInstance * h)
@ -170,6 +159,7 @@ void PlayerLocalState::setSelection(const CArmedInstance * selection)
if (adventureInt && selection) if (adventureInt && selection)
adventureInt->onSelectionChanged(selection); adventureInt->onSelectionChanged(selection);
syncronizeState();
} }
bool PlayerLocalState::isHeroSleeping(const CGHeroInstance * hero) const bool PlayerLocalState::isHeroSleeping(const CGHeroInstance * hero) const
@ -184,6 +174,7 @@ void PlayerLocalState::setHeroAsleep(const CGHeroInstance * hero)
assert(!vstd::contains(sleepingHeroes, hero)); assert(!vstd::contains(sleepingHeroes, hero));
sleepingHeroes.push_back(hero); sleepingHeroes.push_back(hero);
syncronizeState();
} }
void PlayerLocalState::setHeroAwaken(const CGHeroInstance * hero) void PlayerLocalState::setHeroAwaken(const CGHeroInstance * hero)
@ -193,6 +184,7 @@ void PlayerLocalState::setHeroAwaken(const CGHeroInstance * hero)
assert(vstd::contains(sleepingHeroes, hero)); assert(vstd::contains(sleepingHeroes, hero));
vstd::erase(sleepingHeroes, hero); vstd::erase(sleepingHeroes, hero);
syncronizeState();
} }
const std::vector<const CGHeroInstance *> & PlayerLocalState::getWanderingHeroes() const std::vector<const CGHeroInstance *> & PlayerLocalState::getWanderingHeroes()
@ -215,6 +207,8 @@ void PlayerLocalState::addWanderingHero(const CGHeroInstance * hero)
if (currentSelection == nullptr) if (currentSelection == nullptr)
setSelection(hero); setSelection(hero);
syncronizeState();
} }
void PlayerLocalState::removeWanderingHero(const CGHeroInstance * hero) void PlayerLocalState::removeWanderingHero(const CGHeroInstance * hero)
@ -236,6 +230,8 @@ void PlayerLocalState::removeWanderingHero(const CGHeroInstance * hero)
if (currentSelection == nullptr && !ownedTowns.empty()) if (currentSelection == nullptr && !ownedTowns.empty())
setSelection(ownedTowns.front()); setSelection(ownedTowns.front());
syncronizeState();
} }
void PlayerLocalState::swapWanderingHero(size_t pos1, size_t pos2) 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)); std::swap(wanderingHeroes.at(pos1), wanderingHeroes.at(pos2));
adventureInt->onHeroOrderChanged(); adventureInt->onHeroOrderChanged();
syncronizeState();
} }
const std::vector<const CGTownInstance *> & PlayerLocalState::getOwnedTowns() const std::vector<const CGTownInstance *> & PlayerLocalState::getOwnedTowns()
@ -266,6 +264,8 @@ void PlayerLocalState::addOwnedTown(const CGTownInstance * town)
if (currentSelection == nullptr) if (currentSelection == nullptr)
setSelection(town); setSelection(town);
syncronizeState();
} }
void PlayerLocalState::removeOwnedTown(const CGTownInstance * town) void PlayerLocalState::removeOwnedTown(const CGTownInstance * town)
@ -282,6 +282,8 @@ void PlayerLocalState::removeOwnedTown(const CGTownInstance * town)
if (currentSelection == nullptr && !ownedTowns.empty()) if (currentSelection == nullptr && !ownedTowns.empty())
setSelection(ownedTowns.front()); setSelection(ownedTowns.front());
syncronizeState();
} }
void PlayerLocalState::swapOwnedTowns(size_t pos1, size_t pos2) 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]); assert(ownedTowns[pos1] && ownedTowns[pos2]);
std::swap(ownedTowns.at(pos1), ownedTowns.at(pos2)); std::swap(ownedTowns.at(pos1), ownedTowns.at(pos2));
syncronizeState();
adventureInt->onTownOrderChanged(); 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<const CArmedInstance*>(objectPtr);
//
// if (armyPtr)
// setSelection(armyPtr);
}

View File

@ -14,6 +14,7 @@ VCMI_LIB_NAMESPACE_BEGIN
class CGHeroInstance; class CGHeroInstance;
class CGTownInstance; class CGTownInstance;
class CArmedInstance; class CArmedInstance;
class JsonNode;
struct CGPath; struct CGPath;
class int3; class int3;
@ -21,6 +22,15 @@ VCMI_LIB_NAMESPACE_END
class CPlayerInterface; 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 that contains potentially serializeable state of a local player
class PlayerLocalState class PlayerLocalState
{ {
@ -34,18 +44,10 @@ class PlayerLocalState
std::vector<const CGHeroInstance *> wanderingHeroes; //our heroes on the adventure map (not the garrisoned ones) std::vector<const CGHeroInstance *> wanderingHeroes; //our heroes on the adventure map (not the garrisoned ones)
std::vector<const CGTownInstance *> ownedTowns; //our towns on the adventure map std::vector<const CGTownInstance *> ownedTowns; //our towns on the adventure map
void saveHeroPaths(std::map<const CGHeroInstance *, int3> & paths); PlayerSpellbookSetting spellbookSettings;
void loadHeroPaths(std::map<const CGHeroInstance *, int3> & paths);
void syncronizeState();
public: 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); explicit PlayerLocalState(CPlayerInterface & owner);
@ -53,6 +55,9 @@ public:
void setHeroAsleep(const CGHeroInstance * hero); void setHeroAsleep(const CGHeroInstance * hero);
void setHeroAwaken(const CGHeroInstance * hero); void setHeroAwaken(const CGHeroInstance * hero);
const PlayerSpellbookSetting & getSpellbookSettings();
void setSpellbookSettings(const PlayerSpellbookSetting & newSettings);
const std::vector<const CGTownInstance *> & getOwnedTowns(); const std::vector<const CGTownInstance *> & getOwnedTowns();
const CGTownInstance * getOwnedTown(size_t index); const CGTownInstance * getOwnedTown(size_t index);
void addOwnedTown(const CGTownInstance * hero); void addOwnedTown(const CGTownInstance * hero);
@ -81,6 +86,9 @@ public:
const CGTownInstance * getCurrentTown() const; const CGTownInstance * getCurrentTown() const;
const CArmedInstance * getCurrentArmy() const; const CArmedInstance * getCurrentArmy() const;
void serialize(JsonNode & dest) const;
void deserialize(const JsonNode & source);
/// Changes currently selected object /// Changes currently selected object
void setSelection(const CArmedInstance *sel); void setSelection(const CArmedInstance *sel);
}; };

View File

@ -432,7 +432,7 @@ std::shared_ptr<CIntObject> CTownList::CTownItem::genSelection()
void CTownList::CTownItem::update() 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); picture->setFrame(iconIndex + 2);
redraw(); redraw();

View File

@ -81,9 +81,9 @@ void MapAudioPlayer::addObject(const CGObjectInstance * obj)
{ {
for(int fy = 0; fy < obj->getHeight(); ++fy) 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); 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) for(const auto & tile : tiles)
{ {
int3 currTile = obj->pos + tile; int3 currTile = obj->anchorPos() + tile;
if(LOCPLINT->cb->isInTheMap(currTile)) if(LOCPLINT->cb->isInTheMap(currTile))
objects[currTile.z][currTile.x][currTile.y].push_back(obj->id); objects[currTile.z][currTile.x][currTile.y].push_back(obj->id);

View File

@ -389,13 +389,13 @@ BattleHero::BattleHero(const BattleInterface & owner, const CGHeroInstance * her
{ {
AnimationPath animationPath; AnimationPath animationPath;
if(!hero->type->battleImage.empty()) if(!hero->getHeroType()->battleImage.empty())
animationPath = hero->type->battleImage; animationPath = hero->getHeroType()->battleImage;
else else
if(hero->gender == EHeroGender::FEMALE) if(hero->gender == EHeroGender::FEMALE)
animationPath = hero->type->heroClass->imageBattleFemale; animationPath = hero->getHeroClass()->imageBattleFemale;
else else
animationPath = hero->type->heroClass->imageBattleMale; animationPath = hero->getHeroClass()->imageBattleMale;
animation = GH.renderHandler().loadAnimation(animationPath, EImageBlitMode::ALPHA); animation = GH.renderHandler().loadAnimation(animationPath, EImageBlitMode::ALPHA);
@ -1027,7 +1027,7 @@ void StackQueue::update()
int32_t StackQueue::getSiegeShooterIconID() int32_t StackQueue::getSiegeShooterIconID()
{ {
return owner.siegeController->getSiegedTown()->town->faction->getIndex(); return owner.siegeController->getSiegedTown()->getFactionID().getNum();
} }
std::optional<uint32_t> StackQueue::getHoveredUnitIdIfAny() const std::optional<uint32_t> StackQueue::getHoveredUnitIdIfAny() const

View File

@ -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()); std::string addit = std::to_string(getImageIndex());
switch(what) switch(what)
{ {
case EWallVisual::BACKGROUND_WALL: 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) if (faction == ETownType::RAMPART || faction == ETownType::NECROPOLIS || faction == ETownType::DUNGEON || faction == ETownType::STRONGHOLD)
return ImagePath::builtinTODO(prefix + "TPW1.BMP"); return ImagePath::builtinTODO(prefix + "TPW1.BMP");
@ -111,7 +111,7 @@ ImagePath BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisual
void BattleSiegeController::showWallPiece(Canvas & canvas, EWallVisual::EWallVisual what) void BattleSiegeController::showWallPiece(Canvas & canvas, EWallVisual::EWallVisual what)
{ {
auto & ci = town->town->clientInfo; auto & ci = town->getTown()->clientInfo;
auto const & pos = ci.siegePositions[what]; auto const & pos = ci.siegePositions[what];
if ( wallPieceImages[what] && pos.isValid()) if ( wallPieceImages[what] && pos.isValid())
@ -120,7 +120,7 @@ void BattleSiegeController::showWallPiece(Canvas & canvas, EWallVisual::EWallVis
ImagePath BattleSiegeController::getBattleBackgroundName() const 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"); return ImagePath::builtinTODO(prefix + "BACK.BMP");
} }
@ -130,8 +130,8 @@ bool BattleSiegeController::getWallPieceExistence(EWallVisual::EWallVisual what)
switch (what) switch (what)
{ {
case EWallVisual::MOAT: return fortifications.hasMoat && town->town->clientInfo.siegePositions.at(EWallVisual::MOAT).isValid(); case EWallVisual::MOAT: return fortifications.hasMoat && town->getTown()->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_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::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::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; 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) if (posID != 0)
{ {
return { return {
town->town->clientInfo.siegePositions[posID].x, town->getTown()->clientInfo.siegePositions[posID].x,
town->town->clientInfo.siegePositions[posID].y town->getTown()->clientInfo.siegePositions[posID].y
}; };
} }

View File

@ -250,8 +250,8 @@ void CGuiHandler::setStatusbar(std::shared_ptr<IStatusBar> newStatusBar)
void CGuiHandler::onScreenResize(bool resolutionChanged) void CGuiHandler::onScreenResize(bool resolutionChanged)
{ {
if(resolutionChanged) if(resolutionChanged)
{
screenHandler().onScreenResize(); screenHandler().onScreenResize();
}
windows().onScreenResize(); windows().onScreenResize();
CCS->curh->onScreenResize();
} }

View File

@ -312,3 +312,8 @@ void CursorHandler::changeCursor(Cursor::ShowType newShowType)
break; break;
} }
} }
void CursorHandler::onScreenResize()
{
cursor->setImage(getCurrentImage(), getPivotOffset());
}

View File

@ -182,6 +182,7 @@ public:
void hide(); void hide();
void show(); void show();
void onScreenResize();
/// change cursor's positions to (x, y) /// change cursor's positions to (x, y)
void cursorMove(const int & x, const int & y); void cursorMove(const int & x, const int & y);

View File

@ -480,11 +480,11 @@ void SelectionTab::filter(int size, bool selectFirst)
if((elem->mapHeader && (!size || elem->mapHeader->width == size)) || tabType == ESelectionScreen::campaignList) if((elem->mapHeader && (!size || elem->mapHeader->width == size)) || tabType == ESelectionScreen::campaignList)
{ {
if(showRandom) if(showRandom)
curFolder = "RANDOMMAPS/"; curFolder = "RandomMaps/";
auto [folderName, baseFolder, parentExists, fileInFolder] = checkSubfolder(elem->originalFileURI); auto [folderName, baseFolder, parentExists, fileInFolder] = checkSubfolder(elem->originalFileURI);
if((showRandom && baseFolder != "RANDOMMAPS") || (!showRandom && baseFolder == "RANDOMMAPS")) if((showRandom && baseFolder != "RandomMaps") || (!showRandom && baseFolder == "RandomMaps"))
continue; continue;
if(parentExists && !showRandom) if(parentExists && !showRandom)
@ -715,7 +715,7 @@ void SelectionTab::selectFileName(std::string fname)
selectAbs(-1); selectAbs(-1);
if(tabType == ESelectionScreen::saveGame && inputName->getText().empty()) if(tabType == ESelectionScreen::saveGame && inputName->getText().empty())
inputName->setText("NEWGAME"); inputName->setText(CGI->generaltexth->translate("core.genrltxt.11"));
} }
void SelectionTab::selectNewestFile() void SelectionTab::selectNewestFile()
@ -808,7 +808,8 @@ void SelectionTab::parseMaps(const std::unordered_set<ResourcePath> & files)
try try
{ {
auto mapInfo = std::make_shared<ElementInfo>(); auto mapInfo = std::make_shared<ElementInfo>();
mapInfo->mapInit(file.getName()); mapInfo->mapInit(file.getOriginalName());
mapInfo->name = mapInfo->getNameForList();
if (isMapSupported(*mapInfo)) if (isMapSupported(*mapInfo))
allItems.push_back(mapInfo); allItems.push_back(mapInfo);
@ -873,7 +874,7 @@ void SelectionTab::parseCampaigns(const std::unordered_set<ResourcePath> & files
{ {
auto info = std::make_shared<ElementInfo>(); auto info = std::make_shared<ElementInfo>();
//allItems[i].date = std::asctime(std::localtime(&files[i].date)); //allItems[i].date = std::asctime(std::localtime(&files[i].date));
info->fileURI = file.getName(); info->fileURI = file.getOriginalName();
info->campaignInit(); info->campaignInit();
if(info->campaign) if(info->campaign)
allItems.push_back(info); allItems.push_back(info);
@ -988,6 +989,6 @@ void SelectionTab::ListItem::updateItem(std::shared_ptr<ElementInfo> info, bool
iconLossCondition->setFrame(info->mapHeader->defeatIconIndex, 0); iconLossCondition->setFrame(info->mapHeader->defeatIconIndex, 0);
labelName->setMaxWidth(185); labelName->setMaxWidth(185);
} }
labelName->setText(info->getNameForList()); labelName->setText(info->name);
labelName->setColor(color); labelName->setColor(color);
} }

View File

@ -33,6 +33,7 @@ public:
ElementInfo() : CMapInfo() { } ElementInfo() : CMapInfo() { }
~ElementInfo() { } ~ElementInfo() { }
std::string folderName = ""; std::string folderName = "";
std::string name = "";
bool isFolder = false; bool isFolder = false;
}; };

View File

@ -591,8 +591,8 @@ void MapRendererOverlay::renderTile(IMapRendererContext & context, Canvas & targ
if(context.objectTransparency(objectID, coordinates) > 0 && !context.isActiveHero(object)) if(context.objectTransparency(objectID, coordinates) > 0 && !context.isActiveHero(object))
{ {
visitable |= object->visitableAt(coordinates.x, coordinates.y); visitable |= object->visitableAt(coordinates);
blocking |= object->blockingAt(coordinates.x, coordinates.y); blocking |= object->blockingAt(coordinates);
} }
} }

View File

@ -120,7 +120,7 @@ size_t MapRendererBaseContext::objectGroupIndex(ObjectInstanceID objectID) const
Point MapRendererBaseContext::objectImageOffset(ObjectInstanceID objectID, const int3 & coordinates) const Point MapRendererBaseContext::objectImageOffset(ObjectInstanceID objectID, const int3 & coordinates) const
{ {
const CGObjectInstance * object = getObject(objectID); const CGObjectInstance * object = getObject(objectID);
int3 offsetTiles(object->getPosition() - coordinates); int3 offsetTiles(object->anchorPos() - coordinates);
return Point(offsetTiles) * Point(32, 32); return Point(offsetTiles) * Point(32, 32);
} }
@ -498,7 +498,7 @@ size_t MapRendererWorldViewContext::overlayImageIndex(const int3 & coordinates)
{ {
const auto * object = getObject(objectID); const auto * object = getObject(objectID);
if(!object->visitableAt(coordinates.x, coordinates.y)) if(!object->visitableAt(coordinates))
continue; continue;
ObjectPosInfo info(object); ObjectPosInfo info(object);

View File

@ -49,9 +49,9 @@ void MapRendererContextState::addObject(const CGObjectInstance * obj)
{ {
for(int fy = 0; fy < obj->getHeight(); ++fy) 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]; 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) 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)) if(LOCPLINT->cb->isInTheMap(currTile))
{ {

View File

@ -317,7 +317,7 @@ bool MapViewController::isEventVisible(const CGObjectInstance * obj, const Playe
if(obj->isVisitable()) if(obj->isVisitable())
return context->isVisible(obj->visitablePos()); return context->isVisible(obj->visitablePos());
else else
return context->isVisible(obj->pos); return context->isVisible(obj->anchorPos());
} }
bool MapViewController::isEventVisible(const CGHeroInstance * obj, const int3 & from, const int3 & dest) bool MapViewController::isEventVisible(const CGHeroInstance * obj, const int3 & from, const int3 & dest)

View File

@ -59,7 +59,7 @@ std::string CMapHandler::getTerrainDescr(const int3 & pos, bool rightClick) cons
for(const auto & object : map->objects) 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(); result = object->getObjectName();
break; break;
@ -103,15 +103,15 @@ bool CMapHandler::compareObjectBlitOrder(const CGObjectInstance * a, const CGObj
for(const auto & aOffset : a->getBlockedOffsets()) for(const auto & aOffset : a->getBlockedOffsets())
{ {
int3 testTarget = a->pos + aOffset + int3(0, 1, 0); int3 testTarget = a->anchorPos() + aOffset + int3(0, 1, 0);
if(b->blockingAt(testTarget.x, testTarget.y)) if(b->blockingAt(testTarget))
bBlocksA += 1; bBlocksA += 1;
} }
for(const auto & bOffset : b->getBlockedOffsets()) for(const auto & bOffset : b->getBlockedOffsets())
{ {
int3 testTarget = b->pos + bOffset + int3(0, 1, 0); int3 testTarget = b->anchorPos() + bOffset + int3(0, 1, 0);
if(a->blockingAt(testTarget.x, testTarget.y)) if(a->blockingAt(testTarget))
aBlocksB += 1; aBlocksB += 1;
} }
@ -126,8 +126,8 @@ bool CMapHandler::compareObjectBlitOrder(const CGObjectInstance * a, const CGObj
return aBlocksB < bBlocksA; return aBlocksB < bBlocksA;
// object that don't have clear priority via tile blocking will appear based on their row // object that don't have clear priority via tile blocking will appear based on their row
if(a->pos.y != b->pos.y) if(a->anchorPos().y != b->anchorPos().y)
return a->pos.y < b->pos.y; return a->anchorPos().y < b->anchorPos().y;
// heroes should appear on top of objects on the same tile // heroes should appear on top of objects on the same tile
if(b->ID==Obj::HERO && a->ID!=Obj::HERO) if(b->ID==Obj::HERO && a->ID!=Obj::HERO)

View File

@ -608,9 +608,8 @@ std::pair<std::unique_ptr<ui8 []>, si64> CAudioInstance::extractAudio(const Vide
bool CVideoPlayer::openAndPlayVideoImpl(const VideoPath & name, const Point & position, bool useOverlay, bool stopOnKey) bool CVideoPlayer::openAndPlayVideoImpl(const VideoPath & name, const Point & position, bool useOverlay, bool stopOnKey)
{ {
CVideoInstance instance; CVideoInstance instance;
CAudioInstance audio;
auto extractedAudio = audio.extractAudio(name); auto extractedAudio = getAudio(name);
int audioHandle = CCS->soundh->playSound(extractedAudio); int audioHandle = CCS->soundh->playSound(extractedAudio);
if (!instance.openInput(name)) if (!instance.openInput(name))
@ -684,6 +683,15 @@ std::unique_ptr<IVideoInstance> CVideoPlayer::open(const VideoPath & name, float
std::pair<std::unique_ptr<ui8[]>, si64> CVideoPlayer::getAudio(const VideoPath & videoToOpen) std::pair<std::unique_ptr<ui8[]>, si64> CVideoPlayer::getAudio(const VideoPath & videoToOpen)
{ {
AudioPath audioPath = videoToOpen.toType<EResType::SOUND>();
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; CAudioInstance audio;
return audio.extractAudio(videoToOpen); return audio.extractAudio(videoToOpen);
} }

View File

@ -44,6 +44,8 @@ public:
/// Dimensions of logical output. Can be different if scaling is used /// Dimensions of logical output. Can be different if scaling is used
virtual Point getLogicalResolution() const = 0; virtual Point getLogicalResolution() const = 0;
virtual int getInterfaceScalingPercentage() const = 0;
virtual int getScalingFactor() const = 0; virtual int getScalingFactor() const = 0;
/// Window has focus /// Window has focus

View File

@ -11,11 +11,14 @@
#include "StdInc.h" #include "StdInc.h"
#include "CursorHardware.h" #include "CursorHardware.h"
#include "SDL_Extensions.h"
#include "../gui/CGuiHandler.h" #include "../gui/CGuiHandler.h"
#include "../render/IScreenHandler.h" #include "../render/IScreenHandler.h"
#include "../render/Colors.h" #include "../render/Colors.h"
#include "../render/IImage.h" #include "../render/IImage.h"
#include "SDL_Extensions.h"
#include "../../lib/CConfigHandler.h"
#include <SDL_render.h> #include <SDL_render.h>
#include <SDL_events.h> #include <SDL_events.h>
@ -45,19 +48,28 @@ void CursorHardware::setVisible(bool on)
void CursorHardware::setImage(std::shared_ptr<IImage> image, const Point & pivotOffset) void CursorHardware::setImage(std::shared_ptr<IImage> 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)); CSDL_Ext::fillSurface(cursorSurface, CSDL_Ext::toSDL(Colors::TRANSPARENCY));
image->draw(cursorSurface, Point(0,0)); image->draw(cursorSurface, Point(0,0));
auto cursorSurfaceScaled = CSDL_Ext::scaleSurface(cursorSurface, cursorDimensionsScaled.x, cursorDimensionsScaled.y );
auto oldCursor = cursor; auto oldCursor = cursor;
cursor = SDL_CreateColorCursor(cursorSurface, pivotOffset.x, pivotOffset.y); cursor = SDL_CreateColorCursor(cursorSurfaceScaled, pivotOffsetScaled.x, pivotOffsetScaled.y);
if (!cursor) if (!cursor)
logGlobal->error("Failed to set cursor! SDL says %s", SDL_GetError()); logGlobal->error("Failed to set cursor! SDL says %s", SDL_GetError());
SDL_FreeSurface(cursorSurface); SDL_FreeSurface(cursorSurface);
SDL_FreeSurface(cursorSurfaceScaled);
GH.dispatchMainThread([this, oldCursor](){ GH.dispatchMainThread([this, oldCursor](){
SDL_SetCursor(cursor); SDL_SetCursor(cursor);

View File

@ -84,19 +84,39 @@ Rect ScreenHandler::convertLogicalPointsToWindow(const Rect & input) const
return result; 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 ScreenHandler::getPreferredLogicalResolution() const
{ {
Point renderResolution = getRenderResolution(); Point renderResolution = getRenderResolution();
double reservedAreaWidth = settings["video"]["reservedWidth"].Float(); double reservedAreaWidth = settings["video"]["reservedWidth"].Float();
int scaling = getInterfaceScalingPercentage();
Point availableResolution = Point(renderResolution.x * (1 - reservedAreaWidth), renderResolution.y); 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; Point logicalResolution = availableResolution * 100.0 / scaling;
return logicalResolution; return logicalResolution;
} }
@ -335,25 +355,22 @@ EUpscalingFilter ScreenHandler::loadUpscalingFilter() const
if (filter != EUpscalingFilter::AUTO) if (filter != EUpscalingFilter::AUTO)
return filter; return filter;
// for now - always fallback to no filter
return EUpscalingFilter::NONE;
// else - autoselect // else - autoselect
// Point outputResolution = getRenderResolution(); Point outputResolution = getRenderResolution();
// Point logicalResolution = getPreferredLogicalResolution(); Point logicalResolution = getPreferredLogicalResolution();
//
// float scaleX = static_cast<float>(outputResolution.x) / logicalResolution.x; float scaleX = static_cast<float>(outputResolution.x) / logicalResolution.x;
// float scaleY = static_cast<float>(outputResolution.x) / logicalResolution.x; float scaleY = static_cast<float>(outputResolution.x) / logicalResolution.x;
// float scaling = std::min(scaleX, scaleY); float scaling = std::min(scaleX, scaleY);
//
// if (scaling <= 1.0f) if (scaling <= 1.001f)
// return EUpscalingFilter::NONE; return EUpscalingFilter::NONE; // running at original resolution or even lower than that - no need for xbrz
// if (scaling <= 2.0f) if (scaling <= 2.001f)
// return EUpscalingFilter::XBRZ_2; return EUpscalingFilter::XBRZ_2; // resolutions below 1200p (including 1080p / FullHD)
// if (scaling <= 3.0f) if (scaling <= 3.001f)
// return EUpscalingFilter::XBRZ_3; return EUpscalingFilter::XBRZ_3; // resolutions below 2400p (including 1440p and 2160p / 4K)
//
// return EUpscalingFilter::XBRZ_4; return EUpscalingFilter::XBRZ_4; // Only for massive displays, e.g. 8K
} }
void ScreenHandler::selectUpscalingFilter() void ScreenHandler::selectUpscalingFilter()

View File

@ -112,6 +112,8 @@ public:
int getScalingFactor() const final; int getScalingFactor() const final;
int getInterfaceScalingPercentage() const final;
std::vector<Point> getSupportedResolutions() const final; std::vector<Point> getSupportedResolutions() const final;
std::vector<Point> getSupportedResolutions(int displayIndex) const; std::vector<Point> getSupportedResolutions(int displayIndex) const;
std::tuple<int, int> getSupportedScalingRange() const final; std::tuple<int, int> getSupportedScalingRange() const final;

View File

@ -468,8 +468,8 @@ void CInteractableTownTooltip::init(const CGTownInstance * town)
LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE); LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE);
} }
}, [town]{ }, [town]{
if(!town->town->faction->getDescriptionTranslated().empty()) if(!town->getFaction()->getDescriptionTranslated().empty())
CRClickPopup::createAndPush(town->town->faction->getDescriptionTranslated()); CRClickPopup::createAndPush(town->getFaction()->getDescriptionTranslated());
}); });
fastMarket = std::make_shared<LRClickableArea>(Rect(143, 31, 30, 34), []() fastMarket = std::make_shared<LRClickableArea>(Rect(143, 31, 30, 34), []()
{ {
@ -532,8 +532,7 @@ CreatureTooltip::CreatureTooltip(Point pos, const CGCreature * creature)
{ {
OBJECT_CONSTRUCTION; OBJECT_CONSTRUCTION;
auto creatureID = creature->getCreature(); int32_t creatureIconIndex = creature->getCreature()->getIconIndex();
int32_t creatureIconIndex = CGI->creatures()->getById(creatureID)->getIconIndex();
creatureImage = std::make_shared<CAnimImage>(AnimationPath::builtin("TWCRPORT"), creatureIconIndex); creatureImage = std::make_shared<CAnimImage>(AnimationPath::builtin("TWCRPORT"), creatureIconIndex);
creatureImage->center(Point(parent->pos.x + parent->pos.w / 2, parent->pos.y + creatureImage->pos.h / 2 + 11)); 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.x+=x;
pos.y+=y; pos.y+=y;
auto faction = cre->getFaction(); auto faction = cre->getFactionID();
assert(CGI->townh->size() > faction); assert(CGI->townh->size() > faction);

View File

@ -82,7 +82,7 @@ CBuildingRect::CBuildingRect(CCastleBuildings * Par, const CGTownInstance * Town
// special animation frame manipulation for castle shipyard with and without ship // 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 // 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)) (Str->building->bid == BuildingID::SHIPYARD || Str->building->bid == BuildingID::SHIP))
{ {
if(Town->hasBuilt(BuildingID::CITADEL)) if(Town->hasBuilt(BuildingID::CITADEL))
@ -107,7 +107,7 @@ const CBuilding * CBuildingRect::getBuilding()
return nullptr; return nullptr;
if (str->hiddenUpgrade) // hidden upgrades, e.g. hordes - return base (dwelling for hordes) 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; return str->building;
} }
@ -156,7 +156,7 @@ void CBuildingRect::showPopupWindow(const Point & cursorPosition)
return; return;
BuildingID bid = getBuilding()->bid; 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) if (bid < BuildingID::DWELL_FIRST)
{ {
CRClickPopup::createAndPush(CInfoWindow::genText(bld->getNameTranslated(), bld->getDescriptionTranslated()), CRClickPopup::createAndPush(CInfoWindow::genText(bld->getNameTranslated(), bld->getDescriptionTranslated()),
@ -235,10 +235,10 @@ std::string CBuildingRect::getSubtitle()//hover text for building
int bid = getBuilding()->bid; int bid = getBuilding()->bid;
if (bid<30)//non-dwellings - only building name 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% 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()) if(availableCreatures.size())
{ {
int creaID = availableCreatures.back();//taking last of available creatures int creaID = availableCreatures.back();//taking last of available creatures
@ -566,7 +566,7 @@ CCastleBuildings::CCastleBuildings(const CGTownInstance* Town):
{ {
OBJECT_CONSTRUCTION; OBJECT_CONSTRUCTION;
background = std::make_shared<CPicture>(town->town->clientInfo.townBackground); background = std::make_shared<CPicture>(town->getTown()->clientInfo.townBackground);
background->needRefresh = true; background->needRefresh = true;
background->getSurface()->setBlitMode(EImageBlitMode::OPAQUE); background->getSurface()->setBlitMode(EImageBlitMode::OPAQUE);
pos.w = background->pos.w; 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) if(!structure->building)
{ {
@ -617,7 +617,7 @@ void CCastleBuildings::recreate()
for(auto & entry : groups) 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) 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) void CCastleBuildings::addBuilding(BuildingID building)
{ {
//FIXME: implement faster method without complete recreation of town //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(); recreate();
@ -687,7 +687,7 @@ void CCastleBuildings::buildingClicked(BuildingID building)
BuildingID buildingToEnter = building; BuildingID buildingToEnter = building;
for(;;) for(;;)
{ {
const CBuilding *b = town->town->buildings.find(buildingToEnter)->second; const CBuilding *b = town->getTown()->buildings.find(buildingToEnter)->second;
if (buildingTryActivateCustomUI(buildingToEnter, building)) if (buildingTryActivateCustomUI(buildingToEnter, building))
return; return;
@ -705,7 +705,7 @@ void CCastleBuildings::buildingClicked(BuildingID building)
bool CCastleBuildings::buildingTryActivateCustomUI(BuildingID buildingToTest, BuildingID buildingTarget) bool CCastleBuildings::buildingTryActivateCustomUI(BuildingID buildingToTest, BuildingID buildingTarget)
{ {
logGlobal->trace("You've clicked on %d", (int)buildingToTest.toEnum()); 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()) 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); enterRewardable(buildingToTest);
return true; return true;
@ -820,10 +820,10 @@ bool CCastleBuildings::buildingTryActivateCustomUI(BuildingID buildingToTest, Bu
return false; return false;
case BuildingSubID::PORTAL_OF_SUMMONING: 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]); LOCPLINT->showInfoDialog(CGI->generaltexth->tcommands[30]);
else else
enterDwelling(town->town->creatures.size()); enterDwelling(town->getTown()->creatures.size());
return true; return true;
case BuildingSubID::BANK: case BuildingSubID::BANK:
@ -850,7 +850,7 @@ void CCastleBuildings::enterRewardable(BuildingID building)
{ {
MetaString message; MetaString message;
message.appendTextID("core.genrltxt.273"); // only visiting heroes may visit %s 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()); LOCPLINT->showInfoDialog(message.toString());
} }
@ -868,7 +868,7 @@ void CCastleBuildings::enterBlacksmith(BuildingID building, ArtifactID artifactI
const CGHeroInstance *hero = town->visitingHero; const CGHeroInstance *hero = town->visitingHero;
if(!hero) 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; return;
} }
auto art = artifactID.toArtifact(); auto art = artifactID.toArtifact();
@ -897,8 +897,8 @@ void CCastleBuildings::enterBlacksmith(BuildingID building, ArtifactID artifactI
void CCastleBuildings::enterBuilding(BuildingID building) void CCastleBuildings::enterBuilding(BuildingID building)
{ {
std::vector<std::shared_ptr<CComponent>> comps(1, std::make_shared<CComponent>(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFaction(), building))); std::vector<std::shared_ptr<CComponent>> comps(1, std::make_shared<CComponent>(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFactionID(), building)));
LOCPLINT->showInfoDialog( town->town->buildings.find(building)->second->getDescriptionTranslated(), comps); LOCPLINT->showInfoDialog( town->getTown()->buildings.find(building)->second->getDescriptionTranslated(), comps);
} }
void CCastleBuildings::enterCastleGate() void CCastleBuildings::enterCastleGate()
@ -915,20 +915,20 @@ void CCastleBuildings::enterCastleGate()
{ {
const CGTownInstance *t = Town; const CGTownInstance *t = Town;
if (t->id != this->town->id && t->visitingHero == nullptr && //another town, empty and this is 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 t->hasBuilt(BuildingSubID::CASTLE_GATE)) //and the town has a castle gate
{ {
availableTowns.push_back(t->id.getNum());//add to the list availableTowns.push_back(t->id.getNum());//add to the list
if(settings["general"]["enableUiEnhancements"].Bool()) 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)); image->scaleTo(Point(35, 23));
images.push_back(image); images.push_back(image);
} }
} }
} }
auto gateIcon = std::make_shared<CAnimImage>(town->town->clientInfo.buildingsIcons, BuildingID::CASTLE_GATE);//will be deleted by selection window auto gateIcon = std::make_shared<CAnimImage>(town->getTown()->clientInfo.buildingsIcons, BuildingID::CASTLE_GATE);//will be deleted by selection window
auto wnd = std::make_shared<CObjectListWindow>(availableTowns, gateIcon, CGI->generaltexth->jktexts[40], auto wnd = std::make_shared<CObjectListWindow>(availableTowns, gateIcon, CGI->generaltexth->jktexts[40],
CGI->generaltexth->jktexts[41], std::bind (&CCastleInterface::castleTeleport, LOCPLINT->castleInt, _1), 0, images); 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()); }; 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()) if (level < 0 || level >= town->creatures.size() || town->creatures[level].second.empty())
{ {
assert(0); 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; return;
} }
@ -954,8 +954,8 @@ void CCastleBuildings::enterDwelling(int level)
void CCastleBuildings::enterToTheQuickRecruitmentWindow() void CCastleBuildings::enterToTheQuickRecruitmentWindow()
{ {
const auto beginIt = town->creatures.cbegin(); const auto beginIt = town->creatures.cbegin();
const auto afterLastIt = town->creatures.size() > town->town->creatures.size() const auto afterLastIt = town->creatures.size() > town->getTown()->creatures.size()
? std::next(beginIt, town->town->creatures.size()) ? std::next(beginIt, town->getTown()->creatures.size())
: town->creatures.cend(); : town->creatures.cend();
const auto hasSomeoneToRecruit = std::any_of(beginIt, afterLastIt, const auto hasSomeoneToRecruit = std::any_of(beginIt, afterLastIt,
[](const auto & creatureInfo) { return creatureInfo.first > 0; }); [](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) void CCastleBuildings::enterFountain(const BuildingID & building, BuildingSubID::EBuildingSubID subID, BuildingID upgrades)
{ {
std::vector<std::shared_ptr<CComponent>> comps(1, std::make_shared<CComponent>(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFaction(), building))); std::vector<std::shared_ptr<CComponent>> comps(1, std::make_shared<CComponent>(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFactionID(), building)));
std::string descr = town->town->buildings.find(building)->second->getDescriptionTranslated(); std::string descr = town->getTown()->buildings.find(building)->second->getDescriptionTranslated();
std::string hasNotProduced; std::string hasNotProduced;
std::string hasProduced; std::string hasProduced;
@ -977,10 +977,10 @@ void CCastleBuildings::enterFountain(const BuildingID & building, BuildingSubID:
bool isMysticPondOrItsUpgrade = subID == BuildingSubID::MYSTIC_POND bool isMysticPondOrItsUpgrade = subID == BuildingSubID::MYSTIC_POND
|| (upgrades != BuildingID::NONE || (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) 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 if(isMysticPondOrItsUpgrade) //for vanila Rampart like towns
{ {
@ -1056,7 +1056,7 @@ void CCastleBuildings::enterTownHall()
void CCastleBuildings::openMagesGuild() void CCastleBuildings::openMagesGuild()
{ {
auto mageGuildBackground = LOCPLINT->castleInt->town->town->clientInfo.guildBackground; auto mageGuildBackground = LOCPLINT->castleInt->town->getTown()->clientInfo.guildBackground;
GH.windows().createAndPushWindow<CMageGuildScreen>(LOCPLINT->castleInt, mageGuildBackground); GH.windows().createAndPushWindow<CMageGuildScreen>(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 return;//FIXME: suspicious statement, fix or comment
picture = std::make_shared<CAnimImage>(AnimationPath::builtin("ITMCL.DEF"), town->fortLevel()-1); picture = std::make_shared<CAnimImage>(AnimationPath::builtin("ITMCL.DEF"), town->fortLevel()-1);
} }
building = town->town->buildings.at(BuildingID(buildID)); building = town->getTown()->buildings.at(BuildingID(buildID));
pos = picture->pos; pos = picture->pos;
} }
@ -1322,7 +1322,7 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town, const CGTownInst
recreateIcons(); recreateIcons();
if (!from) if (!from)
adventureInt->onAudioPaused(); adventureInt->onAudioPaused();
CCS->musich->playMusicFromSet("faction", town->town->faction->getJsonKey(), true, false); CCS->musich->playMusicFromSet("faction", town->getFaction()->getJsonKey(), true, false);
} }
CCastleInterface::~CCastleInterface() CCastleInterface::~CCastleInterface()
@ -1403,7 +1403,7 @@ void CCastleInterface::removeBuilding(BuildingID bid)
void CCastleInterface::recreateIcons() void CCastleInterface::recreateIcons()
{ {
OBJECT_CONSTRUCTION; 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); icon->setFrame(iconIndex);
TResources townIncome = town->dailyIncome(); TResources townIncome = town->dailyIncome();
@ -1425,8 +1425,8 @@ void CCastleInterface::recreateIcons()
if(town->hasBuilt(BuildingID::TAVERN)) if(town->hasBuilt(BuildingID::TAVERN))
LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE); LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE);
}, [this]{ }, [this]{
if(!town->town->faction->getDescriptionTranslated().empty()) if(!town->getFaction()->getDescriptionTranslated().empty())
CRClickPopup::createAndPush(town->town->faction->getDescriptionTranslated()); CRClickPopup::createAndPush(town->getFaction()->getDescriptionTranslated());
}); });
creainfo.clear(); 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 -1, -1, -1, 0, 0, 1, 2, -1, 1, 1, -1, -1
}; };
icon = std::make_shared<CAnimImage>(town->town->clientInfo.buildingsIcons, building->bid, 0, 2, 2); icon = std::make_shared<CAnimImage>(town->getTown()->clientInfo.buildingsIcons, building->bid, 0, 2, 2);
header = std::make_shared<CAnimImage>(AnimationPath::builtin("TPTHBAR"), panelIndex[static_cast<int>(state)], 0, 1, 73); header = std::make_shared<CAnimImage>(AnimationPath::builtin("TPTHBAR"), panelIndex[static_cast<int>(state)], 0, 1, 73);
if(iconIndex[static_cast<int>(state)] >=0) if(iconIndex[static_cast<int>(state)] >=0)
mark = std::make_shared<CAnimImage>(AnimationPath::builtin("TPTHCHK"), iconIndex[static_cast<int>(state)], 0, 136, 56); mark = std::make_shared<CAnimImage>(AnimationPath::builtin("TPTHCHK"), iconIndex[static_cast<int>(state)], 0, 136, 56);
@ -1569,7 +1569,7 @@ void CHallInterface::CBuildingBox::showPopupWindow(const Point & cursorPosition)
} }
CHallInterface::CHallInterface(const CGTownInstance * Town): CHallInterface::CHallInterface(const CGTownInstance * Town):
CWindowObject(PLAYER_COLORED | BORDERED, Town->town->clientInfo.hallBackground), CWindowObject(PLAYER_COLORED | BORDERED, Town->getTown()->clientInfo.hallBackground),
town(Town) town(Town)
{ {
OBJECT_CONSTRUCTION; OBJECT_CONSTRUCTION;
@ -1581,10 +1581,10 @@ CHallInterface::CHallInterface(const CGTownInstance * Town):
auto statusbarBackground = std::make_shared<CPicture>(background->getSurface(), barRect, 5, 556); auto statusbarBackground = std::make_shared<CPicture>(background->getSurface(), barRect, 5, 556);
statusbar = CGStatusBar::create(statusbarBackground); statusbar = CGStatusBar::create(statusbarBackground);
title = std::make_shared<CLabel>(399, 12, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, town->town->buildings.at(BuildingID(town->hallLevel()+BuildingID::VILLAGE_HALL))->getNameTranslated()); title = std::make_shared<CLabel>(399, 12, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, town->getTown()->buildings.at(BuildingID(town->hallLevel()+BuildingID::VILLAGE_HALL))->getNameTranslated());
exit = std::make_shared<CButton>(Point(748, 556), AnimationPath::builtin("TPMAGE1.DEF"), CButton::tooltip(CGI->generaltexth->hcommands[8]), [&](){close();}, EShortcut::GLOBAL_RETURN); exit = std::make_shared<CButton>(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()); boxes.resize(boxList.size());
for(size_t row=0; row<boxList.size(); row++) //for each row for(size_t row=0; row<boxList.size(); row++) //for each row
{ {
@ -1595,11 +1595,11 @@ CHallInterface::CHallInterface(const CGTownInstance * Town):
{ {
if (!buildingID.hasValue()) if (!buildingID.hasValue())
{ {
logMod->warn("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; continue;
} }
const CBuilding * current = town->town->buildings.at(buildingID); const CBuilding * current = town->getTown()->buildings.at(buildingID);
if(town->hasBuilt(buildingID)) if(town->hasBuilt(buildingID))
{ {
building = current; building = current;
@ -1629,7 +1629,7 @@ CBuildWindow::CBuildWindow(const CGTownInstance *Town, const CBuilding * Buildin
{ {
OBJECT_CONSTRUCTION; OBJECT_CONSTRUCTION;
icon = std::make_shared<CAnimImage>(town->town->clientInfo.buildingsIcons, building->bid, 0, 125, 50); icon = std::make_shared<CAnimImage>(town->getTown()->clientInfo.buildingsIcons, building->bid, 0, 125, 50);
auto statusbarBackground = std::make_shared<CPicture>(background->getSurface(), Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26); auto statusbarBackground = std::make_shared<CPicture>(background->getSurface(), Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26);
statusbar = CGStatusBar::create(statusbarBackground); statusbar = CGStatusBar::create(statusbarBackground);
@ -1711,7 +1711,7 @@ std::string CBuildWindow::getTextForState(EBuildingState state)
{ {
auto toStr = [&](const BuildingID build) -> std::string 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]; ret = CGI->generaltexth->allTexts[52];
@ -1721,7 +1721,7 @@ std::string CBuildWindow::getTextForState(EBuildingState state)
case EBuildingState::MISSING_BASE: case EBuildingState::MISSING_BASE:
{ {
std::string msg = CGI->generaltexth->translate("vcmi.townHall.missingBase"); 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; break;
} }
} }
@ -1780,11 +1780,11 @@ CFortScreen::CFortScreen(const CGTownInstance * town):
{ {
OBJECT_CONSTRUCTION; OBJECT_CONSTRUCTION;
ui32 fortSize = static_cast<ui32>(town->creatures.size()); ui32 fortSize = static_cast<ui32>(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--;
fortSize = std::min(fortSize, static_cast<ui32>(GameConstants::CREATURES_PER_TOWN)); // for 8 creatures + portal of summoning fortSize = std::min(fortSize, static_cast<ui32>(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<CLabel>(400, 12, FONT_BIG, ETextAlignment::CENTER, Colors::WHITE, fortBuilding->getNameTranslated()); title = std::make_shared<CLabel>(400, 12, FONT_BIG, ETextAlignment::CENTER, Colors::WHITE, fortBuilding->getNameTranslated());
std::string text = boost::str(boost::format(CGI->generaltexth->fcommands[6]) % 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; i<fortSize; i++) for(ui32 i=0; i<fortSize; i++)
{ {
BuildingID buildingID; BuildingID buildingID;
if(fortSize == town->town->creatures.size()) if(fortSize == town->getTown()->creatures.size())
{ {
BuildingID dwelling = BuildingID::getDwellingFromLevel(i, 1); BuildingID dwelling = BuildingID::getDwellingFromLevel(i, 1);
@ -1839,7 +1839,7 @@ CFortScreen::CFortScreen(const CGTownInstance * town):
ImagePath CFortScreen::getBgName(const CGTownInstance * town) ImagePath CFortScreen::getBgName(const CGTownInstance * town)
{ {
ui32 fortSize = static_cast<ui32>(town->creatures.size()); ui32 fortSize = static_cast<ui32>(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--;
fortSize = std::min(fortSize, static_cast<ui32>(GameConstants::CREATURES_PER_TOWN)); // for 8 creatures + portal of summoning fortSize = std::min(fortSize, static_cast<ui32>(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) if(getMyBuilding() != nullptr)
{ {
buildingIcon = std::make_shared<CAnimImage>(town->town->clientInfo.buildingsIcons, getMyBuilding()->bid, 0, 4, 21); buildingIcon = std::make_shared<CAnimImage>(town->getTown()->clientInfo.buildingsIcons, getMyBuilding()->bid, 0, 4, 21);
buildingName = std::make_shared<CLabel>(78, 101, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, getMyBuilding()->getNameTranslated(), 152); buildingName = std::make_shared<CLabel>(78, 101, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, getMyBuilding()->getNameTranslated(), 152);
if(town->hasBuilt(getMyBuilding()->bid)) if(town->hasBuilt(getMyBuilding()->bid))
@ -1913,8 +1913,8 @@ const CCreature * CFortScreen::RecruitArea::getMyCreature()
{ {
if(!town->creatures.at(level).second.empty()) // built if(!town->creatures.at(level).second.empty()) // built
return town->creatures.at(level).second.back().toCreature(); return town->creatures.at(level).second.back().toCreature();
if(!town->town->creatures.at(level).empty()) // there are creatures on this level if(!town->getTown()->creatures.at(level).empty()) // there are creatures on this level
return town->town->creatures.at(level).front().toCreature(); return town->getTown()->creatures.at(level).front().toCreature();
return nullptr; return nullptr;
} }
@ -1922,17 +1922,17 @@ const CBuilding * CFortScreen::RecruitArea::getMyBuilding()
{ {
BuildingID myID = BuildingID(BuildingID::getDwellingFromLevel(level, 0)); BuildingID myID = BuildingID(BuildingID::getDwellingFromLevel(level, 0));
if (level == town->town->creatures.size()) if (level == town->getTown()->creatures.size())
return town->town->getSpecialBuilding(BuildingSubID::PORTAL_OF_SUMMONING); return town->getTown()->getSpecialBuilding(BuildingSubID::PORTAL_OF_SUMMONING);
if (!town->town->buildings.count(myID)) if (!town->getTown()->buildings.count(myID))
return nullptr; return nullptr;
const CBuilding * build = town->town->buildings.at(myID); const CBuilding * build = town->getTown()->buildings.at(myID);
while (town->town->buildings.count(myID)) while (town->getTown()->buildings.count(myID))
{ {
if (town->hasBuilt(myID)) if (town->hasBuilt(myID))
build = town->town->buildings.at(myID); build = town->getTown()->buildings.at(myID);
BuildingID::advanceDwelling(myID); BuildingID::advanceDwelling(myID);
} }
@ -1972,7 +1972,7 @@ CMageGuildScreen::CMageGuildScreen(CCastleInterface * owner, const ImagePath & i
{ {
OBJECT_CONSTRUCTION; OBJECT_CONSTRUCTION;
window = std::make_shared<CPicture>(owner->town->town->clientInfo.guildWindow, 332, 76); window = std::make_shared<CPicture>(owner->town->getTown()->clientInfo.guildWindow, 332, 76);
resdatabar = std::make_shared<CMinorResDataBar>(); resdatabar = std::make_shared<CMinorResDataBar>();
resdatabar->moveBy(pos.topLeft(), true); resdatabar->moveBy(pos.topLeft(), true);
@ -2007,7 +2007,7 @@ void CMageGuildScreen::updateSpells(ObjectInstanceID tID)
const CGTownInstance * town = LOCPLINT->cb->getTown(townId); const CGTownInstance * town = LOCPLINT->cb->getTown(townId);
for(size_t i=0; i<town->town->mageLevel; i++) for(size_t i=0; i<town->getTown()->mageLevel; i++)
{ {
size_t spellCount = town->spellsAtLevel((int)i+1,false); //spell at level with -1 hmmm? size_t spellCount = town->spellsAtLevel((int)i+1,false); //spell at level with -1 hmmm?
for(size_t j=0; j<spellCount; j++) for(size_t j=0; j<spellCount; j++)

View File

@ -84,7 +84,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
for(int m=0; m < hero->secSkills.size(); ++m) for(int m=0; m < hero->secSkills.size(); ++m)
secSkillIcons[leftRight].push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SECSK32"), 0, 0, 32 + 36 * m + 454 * leftRight, qeLayout ? 83 : 88)); secSkillIcons[leftRight].push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SECSK32"), 0, 0, 32 + 36 * m + 454 * leftRight, qeLayout ? 83 : 88));
specImages[leftRight] = std::make_shared<CAnimImage>(AnimationPath::builtin("UN32"), hero->type->imageIndex, 0, 67 + 490 * leftRight, qeLayout ? 41 : 45); specImages[leftRight] = std::make_shared<CAnimImage>(AnimationPath::builtin("UN32"), hero->getHeroType()->imageIndex, 0, 67 + 490 * leftRight, qeLayout ? 41 : 45);
expImages[leftRight] = std::make_shared<CAnimImage>(AnimationPath::builtin("PSKIL32"), 4, 0, 103 + 490 * leftRight, qeLayout ? 41 : 45); expImages[leftRight] = std::make_shared<CAnimImage>(AnimationPath::builtin("PSKIL32"), 4, 0, 103 + 490 * leftRight, qeLayout ? 41 : 45);
expValues[leftRight] = std::make_shared<CLabel>(119 + 490 * leftRight, qeLayout ? 66 : 71, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE); expValues[leftRight] = std::make_shared<CLabel>(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<LRClickableAreaWText>(); specialtyAreas[b] = std::make_shared<LRClickableAreaWText>();
specialtyAreas[b]->pos = Rect(Point(pos.x + 69 + 490 * b, pos.y + (qeLayout ? 41 : 45)), Point(32, 32)); 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]->hoverText = CGI->generaltexth->heroscrn[27];
specialtyAreas[b]->text = hero->type->getSpecialtyDescriptionTranslated(); specialtyAreas[b]->text = hero->getHeroType()->getSpecialtyDescriptionTranslated();
experienceAreas[b] = std::make_shared<LRClickableAreaWText>(); experienceAreas[b] = std::make_shared<LRClickableAreaWText>();
experienceAreas[b]->pos = Rect(Point(pos.x + 105 + 490 * b, pos.y + (qeLayout ? 41 : 45)), Point(32, 32)); experienceAreas[b]->pos = Rect(Point(pos.x + 105 + 490 * b, pos.y + (qeLayout ? 41 : 45)), Point(32, 32));

View File

@ -184,9 +184,9 @@ void CHeroWindow::update()
name->setText(curHero->getNameTranslated()); name->setText(curHero->getNameTranslated());
title->setText((boost::format(CGI->generaltexth->allTexts[342]) % curHero->level % curHero->getClassNameTranslated()).str()); title->setText((boost::format(CGI->generaltexth->allTexts[342]) % curHero->level % curHero->getClassNameTranslated()).str());
specArea->text = curHero->type->getSpecialtyDescriptionTranslated(); specArea->text = curHero->getHeroType()->getSpecialtyDescriptionTranslated();
specImage->setFrame(curHero->type->imageIndex); specImage->setFrame(curHero->getHeroType()->imageIndex);
specName->setText(curHero->type->getSpecialtyNameTranslated()); specName->setText(curHero->getHeroType()->getSpecialtyNameTranslated());
tacticsButton = std::make_shared<CToggleButton>(Point(539, 483), AnimationPath::builtin("hsbtns8.def"), std::make_pair(heroscrn[26], heroscrn[31]), 0, EShortcut::HERO_TOGGLE_TACTICS); tacticsButton = std::make_shared<CToggleButton>(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]); tacticsButton->addHoverText(EButtonState::HIGHLIGHTED, CGI->generaltexth->heroscrn[25]);

View File

@ -300,7 +300,7 @@ int InfoBoxHeroData::getSubID()
else else
return 0; return 0;
case HERO_SPECIAL: case HERO_SPECIAL:
return hero->type->getIndex(); return hero->getHeroTypeID().getNum();
case HERO_MANA: case HERO_MANA:
case HERO_EXPERIENCE: case HERO_EXPERIENCE:
return 0; return 0;
@ -800,7 +800,7 @@ CTownItem::CTownItem(const CGTownInstance * Town)
garr = std::make_shared<CGarrisonInt>(Point(313, 3), 4, Point(232,0), town->getUpperArmy(), town->visitingHero, true, true, CGarrisonInt::ESlotsLayout::TWO_ROWS); garr = std::make_shared<CGarrisonInt>(Point(313, 3), 4, Point(232,0), town->getUpperArmy(), town->visitingHero, true, true, CGarrisonInt::ESlotsLayout::TWO_ROWS);
heroes = std::make_shared<HeroSlots>(town, Point(244,6), Point(475,6), garr, false); heroes = std::make_shared<HeroSlots>(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<CAnimImage>(AnimationPath::builtin("ITPT"), iconIndex, 0, 5, 6); picture = std::make_shared<CAnimImage>(AnimationPath::builtin("ITPT"), iconIndex, 0, 5, 6);
openTown = std::make_shared<LRClickableAreaOpenTown>(Rect(5, 6, 58, 64), town); openTown = std::make_shared<LRClickableAreaOpenTown>(Rect(5, 6, 58, 64), town);
@ -823,8 +823,8 @@ CTownItem::CTownItem(const CGTownInstance * Town)
if(town->hasBuilt(BuildingID::TAVERN)) if(town->hasBuilt(BuildingID::TAVERN))
LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE); LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE);
}, [&]{ }, [&]{
if(!town->town->faction->getDescriptionTranslated().empty()) if(!town->getTown()->faction->getDescriptionTranslated().empty())
CRClickPopup::createAndPush(town->town->faction->getDescriptionTranslated()); CRClickPopup::createAndPush(town->getFaction()->getDescriptionTranslated());
}); });
fastMarket = std::make_shared<LRClickableArea>(Rect(153, 6, 65, 64), []() fastMarket = std::make_shared<LRClickableArea>(Rect(153, 6, 65, 64), []()
{ {

View File

@ -192,7 +192,7 @@ std::string CMarketWindow::getMarketTitle(const ObjectInstanceID marketId, const
{ {
for(const auto & buildingId : town->getBuildings()) 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(); return building->getNameTranslated();
} }
} }

View File

@ -78,7 +78,7 @@ void CQuestMinimap::addQuestMarks (const QuestInfo * q)
int3 tile; int3 tile;
if (q->obj) if (q->obj)
tile = q->obj->pos; tile = q->obj->visitablePos();
else else
tile = q->tile; tile = q->tile;
@ -104,7 +104,7 @@ void CQuestMinimap::update()
void CQuestMinimap::iconClicked() void CQuestMinimap::iconClicked()
{ {
if(currentQuest->obj) if(currentQuest->obj)
adventureInt->centerOnTile(currentQuest->obj->pos); adventureInt->centerOnTile(currentQuest->obj->visitablePos());
//moveAdvMapSelection(); //moveAdvMapSelection();
} }

View File

@ -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); 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 // 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)); vstd::abetween(cp, 0, std::max(0, pagesWithinCurrentTab() - 1));
setCurrentPage(cp); setCurrentPage(cp);
@ -313,8 +313,18 @@ void CSpellWindow::processSpells()
void CSpellWindow::fexitb() void CSpellWindow::fexitb()
{ {
(myInt->battleInt ? myInt->localState->spellbookSettings.spellbookLastTabBattle : myInt->localState->spellbookSettings.spellbookLastTabAdvmap) = selectedTab; auto spellBookState = myInt->localState->getSpellbookSettings();
(myInt->battleInt ? myInt->localState->spellbookSettings.spellbookLastPageBattle : myInt->localState->spellbookSettings.spellbookLastPageAdvmap) = currentPage; if(myInt->battleInt)
{
spellBookState.spellbookLastTabBattle = selectedTab;
spellBookState.spellbookLastPageBattle = currentPage;
}
else
{
spellBookState.spellbookLastTabAdvmap = selectedTab;
spellBookState.spellbookLastPageAdvmap = currentPage;
}
myInt->localState->setSpellbookSettings(spellBookState);
if(onSpellSelect) if(onSpellSelect)
onSpellSelect(SpellID::NONE); onSpellSelect(SpellID::NONE);
@ -619,8 +629,10 @@ void CSpellWindow::SpellArea::clickPressed(const Point & cursorPosition)
auto guard = vstd::makeScopeGuard([this]() auto guard = vstd::makeScopeGuard([this]()
{ {
owner->myInt->localState->spellbookSettings.spellbookLastTabAdvmap = owner->selectedTab; auto spellBookState = owner->myInt->localState->getSpellbookSettings();
owner->myInt->localState->spellbookSettings.spellbookLastPageAdvmap = owner->currentPage; spellBookState.spellbookLastTabAdvmap = owner->selectedTab;
spellBookState.spellbookLastPageAdvmap = owner->currentPage;
owner->myInt->localState->setSpellbookSettings(spellBookState);
}); });
spells::detail::ProblemImpl problem; spells::detail::ProblemImpl problem;

View File

@ -522,9 +522,9 @@ CTavernWindow::CTavernWindow(const CGObjectInstance * TavernObj, const std::func
recruit->block(true); recruit->block(true);
} }
if(LOCPLINT->castleInt) if(LOCPLINT->castleInt)
videoPlayer = std::make_shared<VideoWidget>(Point(70, 56), LOCPLINT->castleInt->town->town->clientInfo.tavernVideo, false); videoPlayer = std::make_shared<VideoWidget>(Point(70, 56), LOCPLINT->castleInt->town->getTown()->clientInfo.tavernVideo, false);
else if(const auto * townObj = dynamic_cast<const CGTownInstance *>(TavernObj)) else if(const auto * townObj = dynamic_cast<const CGTownInstance *>(TavernObj))
videoPlayer = std::make_shared<VideoWidget>(Point(70, 56), townObj->town->clientInfo.tavernVideo, false); videoPlayer = std::make_shared<VideoWidget>(Point(70, 56), townObj->getTown()->clientInfo.tavernVideo, false);
else else
videoPlayer = std::make_shared<VideoWidget>(Point(70, 56), VideoPath::builtin("TAVERN.BIK"), false); videoPlayer = std::make_shared<VideoWidget>(Point(70, 56), VideoPath::builtin("TAVERN.BIK"), false);
@ -548,7 +548,7 @@ void CTavernWindow::addInvite()
if(!inviteableHeroes.empty()) 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) if(!heroToInvite)
heroToInvite = (*RandomGeneratorUtil::nextItem(inviteableHeroes, CRandomGenerator::getDefault())).second; heroToInvite = (*RandomGeneratorUtil::nextItem(inviteableHeroes, CRandomGenerator::getDefault())).second;
@ -563,7 +563,7 @@ void CTavernWindow::recruitb()
const CGHeroInstance *toBuy = (selected ? h2 : h1)->h; const CGHeroInstance *toBuy = (selected ? h2 : h1)->h;
const CGObjectInstance *obj = tavernObj; 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(); close();
} }
@ -963,7 +963,7 @@ CUniversityWindow::CUniversityWindow(const CGHeroInstance * _hero, BuildingID bu
if(auto town = dynamic_cast<const CGTownInstance *>(_market)) if(auto town = dynamic_cast<const CGTownInstance *>(_market))
{ {
auto faction = town->town->faction->getId(); auto faction = town->getTown()->faction->getId();
titlePic = std::make_shared<CAnimImage>((*CGI->townh)[faction]->town->clientInfo.buildingsIcons, building); titlePic = std::make_shared<CAnimImage>((*CGI->townh)[faction]->town->clientInfo.buildingsIcons, building);
} }
else if(auto uni = dynamic_cast<const CGUniversity *>(_market); uni->appearance) else if(auto uni = dynamic_cast<const CGUniversity *>(_market); uni->appearance)

View File

@ -51,9 +51,9 @@ void QuickRecruitmentWindow::setCreaturePurchaseCards()
{ {
int availableAmount = getAvailableCreatures(); int availableAmount = getAvailableCreatures();
Point position = Point((pos.w - 100*availableAmount - 8*(availableAmount-1))/2,64); 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<CreaturePurchaseCard>(town->creatures[i].second, position, town->creatures[i].first, this)); cards.push_back(std::make_shared<CreaturePurchaseCard>(town->creatures[i].second, position, town->creatures[i].first, this));
position.x += 108; position.x += 108;
@ -108,7 +108,7 @@ void QuickRecruitmentWindow::purchaseUnits()
{ {
int level = 0; int level = 0;
int i = 0; int i = 0;
for(auto c : town->town->creatures) for(auto c : town->getTown()->creatures)
{ {
for(auto c2 : c) for(auto c2 : c)
if(c2 == selected->creatureOnTheCard->getId()) if(c2 == selected->creatureOnTheCard->getId())
@ -129,8 +129,8 @@ void QuickRecruitmentWindow::purchaseUnits()
int QuickRecruitmentWindow::getAvailableCreatures() int QuickRecruitmentWindow::getAvailableCreatures()
{ {
int creaturesAmount = 0; int creaturesAmount = 0;
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)
creaturesAmount++; creaturesAmount++;
return creaturesAmount; return creaturesAmount;
} }

View File

@ -194,10 +194,8 @@ GeneralOptionsTab::GeneralOptionsTab()
build(config); build(config);
const auto & currentResolution = settings["video"]["resolution"];
std::shared_ptr<CLabel> scalingLabel = widget<CLabel>("scalingLabel"); std::shared_ptr<CLabel> scalingLabel = widget<CLabel>("scalingLabel");
scalingLabel->setText(scalingToLabelString(currentResolution["scaling"].Integer())); scalingLabel->setText(scalingToLabelString(GH.screenHandler().getInterfaceScalingPercentage()));
std::shared_ptr<CLabel> longTouchLabel = widget<CLabel>("longTouchLabel"); std::shared_ptr<CLabel> longTouchLabel = widget<CLabel>("longTouchLabel");
if (longTouchLabel) if (longTouchLabel)

View File

@ -184,6 +184,7 @@
"targetfps", "targetfps",
"vsync", "vsync",
"fontsType", "fontsType",
"cursorScalingFactor",
"fontScalingFactor", "fontScalingFactor",
"upscalingFilter", "upscalingFilter",
"fontUpscalingFilter", "fontUpscalingFilter",
@ -195,22 +196,19 @@
"additionalProperties" : false, "additionalProperties" : false,
"required" : [ "width", "height", "scaling" ], "required" : [ "width", "height", "scaling" ],
"properties" : { "properties" : {
"width" : { "type" : "number" }, "width" : { "type" : "number", "default" : 1280 },
"height" : { "type" : "number" }, "height" : { "type" : "number", "default" : 720 },
"scaling" : { "type" : "number" } "scaling" : { "type" : "number", "default" : 0 }
}, }
"defaultIOS" : {"width" : 800, "height" : 600, "scaling" : 200 },
"defaultAndroid" : {"width" : 800, "height" : 600, "scaling" : 200 },
"default" : {"width" : 800, "height" : 600, "scaling" : 100 }
}, },
"reservedWidth" : { "reservedWidth" : {
"type" : "number", "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 "default" : 0
}, },
"fullscreen" : { "fullscreen" : {
"type" : "boolean", "type" : "boolean",
"default" : false "default" : true
}, },
"realFullscreen" : { "realFullscreen" : {
"type" : "boolean", "type" : "boolean",
@ -256,6 +254,10 @@
"enum" : [ "auto", "original", "scalable" ], "enum" : [ "auto", "original", "scalable" ],
"default" : "auto" "default" : "auto"
}, },
"cursorScalingFactor" : {
"type" : "number",
"default" : 1
},
"fontScalingFactor" : { "fontScalingFactor" : {
"type" : "number", "type" : "number",
"default" : 1 "default" : 1

View File

@ -26,7 +26,7 @@ class DLL_LINKAGE INativeTerrainProvider
{ {
public: public:
virtual TerrainId getNativeTerrain() const = 0; virtual TerrainId getNativeTerrain() const = 0;
virtual FactionID getFaction() const = 0; virtual FactionID getFactionID() const = 0;
virtual bool isNativeTerrain(TerrainId terrain) const; virtual bool isNativeTerrain(TerrainId terrain) const;
}; };

View File

@ -202,6 +202,10 @@ elseif(NOT APPLE_IOS)
target_link_libraries(vcmilauncher SDL2::SDL2) target_link_libraries(vcmilauncher SDL2::SDL2)
endif() 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_link_libraries(vcmilauncher vcmi vcmiqt Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network)
target_include_directories(vcmilauncher target_include_directories(vcmilauncher
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}

View File

@ -127,7 +127,12 @@ void CSettingsView::loadSettings()
#endif #endif
fillValidScalingRange(); 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->setValue(settings["video"]["targetfps"].Float());
ui->spinBoxFramerateLimit->setDisabled(settings["video"]["vsync"].Bool()); ui->spinBoxFramerateLimit->setDisabled(settings["video"]["vsync"].Bool());
ui->sliderReservedArea->setValue(std::round(settings["video"]["reservedWidth"].Float() * 100)); 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->sliderControllerSticksAcceleration->setValue(settings["input"]["controllerAxisScale"].Float() * 100);
ui->lineEditGameLobbyHost->setText(QString::fromStdString(settings["lobby"]["hostname"].String())); ui->lineEditGameLobbyHost->setText(QString::fromStdString(settings["lobby"]["hostname"].String()));
ui->spinBoxNetworkPortLobby->setValue(settings["lobby"]["port"].Integer()); ui->spinBoxNetworkPortLobby->setValue(settings["lobby"]["port"].Integer());
ui->buttonVSync->setChecked(settings["video"]["vsync"].Bool());
if (settings["video"]["fontsType"].String() == "auto") if (settings["video"]["fontsType"].String() == "auto")
ui->buttonFontAuto->setChecked(true); ui->buttonFontAuto->setChecked(true);
@ -195,7 +201,6 @@ void CSettingsView::loadSettings()
void CSettingsView::loadToggleButtonSettings() void CSettingsView::loadToggleButtonSettings()
{ {
setCheckbuttonState(ui->buttonShowIntro, settings["video"]["showIntro"].Bool()); setCheckbuttonState(ui->buttonShowIntro, settings["video"]["showIntro"].Bool());
setCheckbuttonState(ui->buttonVSync, settings["video"]["vsync"].Bool());
setCheckbuttonState(ui->buttonAutoCheck, settings["launcher"]["autoCheckRepositories"].Bool()); setCheckbuttonState(ui->buttonAutoCheck, settings["launcher"]["autoCheckRepositories"].Bool());
setCheckbuttonState(ui->buttonRepositoryDefault, settings["launcher"]["defaultRepositoryEnabled"].Bool()); setCheckbuttonState(ui->buttonRepositoryDefault, settings["launcher"]["defaultRepositoryEnabled"].Bool());
@ -212,10 +217,15 @@ void CSettingsView::loadToggleButtonSettings()
std::string cursorType = settings["video"]["cursor"].String(); std::string cursorType = settings["video"]["cursor"].String();
int cursorTypeIndex = vstd::find_pos(cursorTypesList, cursorType); int cursorTypeIndex = vstd::find_pos(cursorTypesList, cursorType);
setCheckbuttonState(ui->buttonCursorType, cursorTypeIndex); 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; int fontScalingPercentage = settings["video"]["fontScalingFactor"].Float() * 100;
ui->sliderScalingFont->setValue(fontScalingPercentage / 5); ui->sliderScalingFont->setValue(fontScalingPercentage / 5);
int cursorScalingPercentage = settings["video"]["cursorScalingFactor"].Float() * 100;
ui->sliderScalingCursor->setValue(cursorScalingPercentage / 5);
} }
void CSettingsView::fillValidResolutions() void CSettingsView::fillValidResolutions()
@ -494,6 +504,8 @@ void CSettingsView::on_buttonCursorType_toggled(bool value)
Settings node = settings.write["video"]["cursor"]; Settings node = settings.write["video"]["cursor"];
node->String() = cursorTypesList[value ? 1 : 0]; node->String() = cursorTypesList[value ? 1 : 0];
updateCheckbuttonText(ui->buttonCursorType); updateCheckbuttonText(ui->buttonCursorType);
ui->sliderScalingCursor->setDisabled(value == 1); // Not supported
ui->labelScalingCursorValue->setDisabled(value == 1); // Not supported
} }
void CSettingsView::loadTranslation() void CSettingsView::loadTranslation()
@ -627,7 +639,6 @@ void CSettingsView::on_buttonVSync_toggled(bool value)
Settings node = settings.write["video"]["vsync"]; Settings node = settings.write["video"]["vsync"];
node->Bool() = value; node->Bool() = value;
ui->spinBoxFramerateLimit->setDisabled(settings["video"]["vsync"].Bool()); ui->spinBoxFramerateLimit->setDisabled(settings["video"]["vsync"].Bool());
updateCheckbuttonText(ui->buttonVSync);
} }
void CSettingsView::on_comboBoxEnemyPlayerAI_currentTextChanged(const QString &arg1) 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"]; Settings node = settings.write["mods"]["validation"];
node->String() = "full"; 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;
}

View File

@ -97,6 +97,10 @@ private slots:
void on_buttonValidationFull_clicked(bool checked); void on_buttonValidationFull_clicked(bool checked);
void on_sliderScalingCursor_valueChanged(int value);
void on_buttonScalingAuto_toggled(bool checked);
private: private:
Ui::CSettingsView * ui; Ui::CSettingsView * ui;

File diff suppressed because it is too large Load Diff

View File

@ -38,7 +38,7 @@ TerrainId AFactionMember::getNativeTerrain() const
//this code is used in the CreatureTerrainLimiter::limit to setup battle bonuses //this code is used in the CreatureTerrainLimiter::limit to setup battle bonuses
//and in the CGHeroInstance::getNativeTerrain() to setup movement bonuses or/and penalties. //and in the CGHeroInstance::getNativeTerrain() to setup movement bonuses or/and penalties.
return getBonusBearer()->hasBonus(selectorNoTerrainPenalty, cachingStringNoTerrainPenalty) 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 int32_t AFactionMember::magicResistance() const

View File

@ -117,7 +117,7 @@ int32_t CCreature::getHorde() const
return hordeGrowth; return hordeGrowth;
} }
FactionID CCreature::getFaction() const FactionID CCreature::getFactionID() const
{ {
return FactionID(faction); return FactionID(faction);
} }

View File

@ -127,7 +127,7 @@ public:
std::string getNamePluralTextID() const override; std::string getNamePluralTextID() const override;
std::string getNameSingularTextID() const override; std::string getNameSingularTextID() const override;
FactionID getFaction() const override; FactionID getFactionID() const override;
int32_t getIndex() const override; int32_t getIndex() const override;
int32_t getIconIndex() const override; int32_t getIconIndex() const override;
std::string getJsonKey() const override; std::string getJsonKey() const override;

View File

@ -926,10 +926,10 @@ void CStackInstance::serializeJson(JsonSerializeFormat & handler)
} }
} }
FactionID CStackInstance::getFaction() const FactionID CStackInstance::getFactionID() const
{ {
if(type) if(type)
return type->getFaction(); return type->getFactionID();
return FactionID::NEUTRAL; return FactionID::NEUTRAL;
} }

View File

@ -106,7 +106,7 @@ public:
//IConstBonusProvider //IConstBonusProvider
const IBonusBearer* getBonusBearer() const override; const IBonusBearer* getBonusBearer() const override;
//INativeTerrainProvider //INativeTerrainProvider
FactionID getFaction() const override; FactionID getFactionID() const override;
virtual ui64 getPower() const; virtual ui64 getPower() const;
/// Returns total market value of resources needed to recruit this unit /// Returns total market value of resources needed to recruit this unit

View File

@ -381,7 +381,7 @@ bool CGameInfoCallback::getHeroInfo(const CGObjectInstance * hero, InfoAboutHero
for(const auto & creature : VLC->creh->objects) for(const auto & creature : VLC->creh->objects)
{ {
if(creature->getFaction() == factionIndex && static_cast<int>(creature->getAIValue()) > maxAIValue) if(creature->getFactionID() == factionIndex && static_cast<int>(creature->getAIValue()) > maxAIValue)
{ {
maxAIValue = creature->getAIValue(); maxAIValue = creature->getAIValue();
mostStrong = creature.get(); mostStrong = creature.get();
@ -539,7 +539,7 @@ EDiggingStatus CGameInfoCallback::getTileDigStatus(int3 tile, bool verbose) cons
for(const auto & object : gs->map->objects) 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 EDiggingStatus::TILE_OCCUPIED;
} }
return getTile(tile)->getDiggingStatus(); 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); 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; 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 if(t->hasBuilt(ID)) //already built

View File

@ -559,6 +559,7 @@ set(lib_MAIN_HEADERS
networkPacks/PacksForServer.h networkPacks/PacksForServer.h
networkPacks/SetRewardableConfiguration.h networkPacks/SetRewardableConfiguration.h
networkPacks/SetStackEffect.h networkPacks/SetStackEffect.h
networkPacks/SaveLocalState.h
networkPacks/StackLocation.h networkPacks/StackLocation.h
networkPacks/TradeItem.h networkPacks/TradeItem.h

View File

@ -10,6 +10,7 @@
#include "StdInc.h" #include "StdInc.h"
#include "CPlayerState.h" #include "CPlayerState.h"
#include "json/JsonNode.h"
#include "mapObjects/CGDwelling.h" #include "mapObjects/CGDwelling.h"
#include "mapObjects/CGTownInstance.h" #include "mapObjects/CGTownInstance.h"
#include "mapObjects/CGHeroInstance.h" #include "mapObjects/CGHeroInstance.h"
@ -20,8 +21,13 @@
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
PlayerState::PlayerState() PlayerState::PlayerState()
: color(-1), human(false), cheated(false), enteredWinningCheatCode(false), : color(-1)
enteredLosingCheatCode(false), status(EPlayerStatus::INGAME) , playerLocalSettings(std::make_unique<JsonNode>())
, human(false)
, cheated(false)
, enteredWinningCheatCode(false)
, enteredLosingCheatCode(false)
, status(EPlayerStatus::INGAME)
{ {
setNodeType(PLAYER); setNodeType(PLAYER);
} }

View File

@ -16,7 +16,6 @@
#include "bonuses/CBonusSystemNode.h" #include "bonuses/CBonusSystemNode.h"
#include "ResourceSet.h" #include "ResourceSet.h"
#include "TurnTimerInfo.h" #include "TurnTimerInfo.h"
#include "ConstTransitivePtr.h"
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
@ -66,6 +65,7 @@ public:
std::vector<QuestInfo> quests; //store info about all received quests std::vector<QuestInfo> quests; //store info about all received quests
std::vector<Bonus> battleBonuses; //additional bonuses to be added during battle with neutrals std::vector<Bonus> battleBonuses; //additional bonuses to be added during battle with neutrals
std::map<uint32_t, std::map<ArtifactPosition, ArtifactID>> costumesArtifacts; std::map<uint32_t, std::map<ArtifactPosition, ArtifactID>> costumesArtifacts;
std::unique_ptr<JsonNode> playerLocalSettings; // Json with client-defined data, such as order of heroes or current hero paths. Not used by client/lib
bool cheated; bool cheated;
bool enteredWinningCheatCode, enteredLosingCheatCode; //if true, this player has entered cheat codes for loss / victory bool enteredWinningCheatCode, enteredLosingCheatCode; //if true, this player has entered cheat codes for loss / victory
@ -116,6 +116,9 @@ public:
h & status; h & status;
h & turnTimer; h & turnTimer;
if (h.version >= Handler::Version::LOCAL_PLAYER_STATE_DATA)
h & *playerLocalSettings;
if (h.version >= Handler::Version::PLAYER_STATE_OWNED_OBJECTS) if (h.version >= Handler::Version::PLAYER_STATE_OWNED_OBJECTS)
{ {
h & ownedObjects; h & ownedObjects;

View File

@ -416,9 +416,9 @@ int32_t CUnitState::creatureIconIndex() const
return unitType()->getIconIndex(); return unitType()->getIconIndex();
} }
FactionID CUnitState::getFaction() const FactionID CUnitState::getFactionID() const
{ {
return unitType()->getFaction(); return unitType()->getFactionID();
} }
int32_t CUnitState::getCasterUnitId() const int32_t CUnitState::getCasterUnitId() const

View File

@ -253,7 +253,7 @@ public:
void localInit(const IUnitEnvironment * env_); void localInit(const IUnitEnvironment * env_);
void serializeJson(JsonSerializeFormat & handler); void serializeJson(JsonSerializeFormat & handler);
FactionID getFaction() const override; FactionID getFactionID() const override;
void afterAttack(bool ranged, bool counter); void afterAttack(bool ranged, bool counter);

View File

@ -300,15 +300,15 @@ ILimiter::EDecision FactionLimiter::limit(const BonusLimitationContext &context)
if(bearer) if(bearer)
{ {
if(faction != FactionID::DEFAULT) 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) switch(context.b.source)
{ {
case BonusSource::CREATURE_ABILITY: case BonusSource::CREATURE_ABILITY:
return bearer->getFaction() == context.b.sid.as<CreatureID>().toCreature()->getFaction() ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD; return bearer->getFactionID() == context.b.sid.as<CreatureID>().toCreature()->getFactionID() ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
case BonusSource::TOWN_STRUCTURE: case BonusSource::TOWN_STRUCTURE:
return bearer->getFaction() == context.b.sid.as<BuildingTypeUniqueID>().getFaction() ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD; return bearer->getFactionID() == context.b.sid.as<BuildingTypeUniqueID>().getFaction() ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
//TODO: other sources of bonuses //TODO: other sources of bonuses
} }

View File

@ -351,14 +351,14 @@ void CampaignState::setCurrentMapAsConquered(std::vector<CGHeroInstance *> heroe
{ {
JsonNode node = CampaignState::crossoverSerialize(hero); 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()); logGlobal->info("Hero crossover: %d (%s) exported to global pool", hero->getHeroTypeID(), hero->getNameTranslated());
globalHeroPool[hero->getHeroType()] = node; globalHeroPool[hero->getHeroTypeID()] = node;
} }
else 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); scenarioHeroPool[*currentMap].push_back(node);
} }
} }

View File

@ -92,7 +92,7 @@ FactionID CFaction::getId() const
return FactionID(index); return FactionID(index);
} }
FactionID CFaction::getFaction() const FactionID CFaction::getFactionID() const
{ {
return FactionID(index); return FactionID(index);
} }

View File

@ -39,7 +39,7 @@ class DLL_LINKAGE CFaction : public Faction
FactionID index = FactionID::NEUTRAL; 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: public:
TerrainId nativeTerrain; TerrainId nativeTerrain;

View File

@ -883,8 +883,8 @@ void CTownHandler::beforeValidate(JsonNode & object)
if (building.second.Struct().count("onVisitBonuses")) if (building.second.Struct().count("onVisitBonuses"))
{ {
building.second["configuration"]["visitMode"] = JsonNode("bonus"); building.second["configuration"]["visitMode"] = JsonNode("bonus");
building.second["configuration"]["visitMode"]["rewards"][0]["message"] = building.second["description"]; building.second["configuration"]["rewards"][0]["message"] = building.second["description"];
building.second["configuration"]["visitMode"]["rewards"][0]["bonuses"] = building.second["onVisitBonuses"]; building.second["configuration"]["rewards"][0]["bonuses"] = building.second["onVisitBonuses"];
} }
} }
} }

View File

@ -449,7 +449,7 @@ void CGameState::initGrailPosition()
//remove tiles with holes //remove tiles with holes
for(auto & elem : map->objects) for(auto & elem : map->objects)
if(elem && elem->ID == Obj::HOLE) if(elem && elem->ID == Obj::HOLE)
allowedPos -= elem->pos; allowedPos -= elem->anchorPos();
if(!allowedPos.empty()) if(!allowedPos.empty())
{ {
@ -495,7 +495,7 @@ void CGameState::randomizeMapObjects()
{ {
for (int j = 0; j < object->getHeight() ; j++) 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; 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) for(auto town : map->towns)
{ {
if(town->getPosition() == townPos) if(town->anchorPos() == townPos)
{ {
townPos = town->visitablePos(); townPos = town->visitablePos();
break; break;
@ -545,8 +545,7 @@ void CGameState::placeStartingHero(const PlayerColor & playerColor, const HeroTy
hero->setHeroType(heroTypeId); hero->setHeroType(heroTypeId);
hero->tempOwner = playerColor; hero->tempOwner = playerColor;
hero->pos = townPos; hero->setAnchorPos(townPos + hero->getVisitableOffset());
hero->pos += hero->getVisitableOffset();
map->getEditManager()->insertObject(hero); map->getEditManager()->insertObject(hero);
} }
@ -600,7 +599,7 @@ void CGameState::initHeroes()
} }
hero->initHero(getRandomGenerator()); hero->initHero(getRandomGenerator());
map->allHeroes[hero->getHeroType().getNum()] = hero; map->allHeroes[hero->getHeroTypeID().getNum()] = hero;
} }
// generate boats for all heroes on water // generate boats for all heroes on water
@ -614,7 +613,7 @@ void CGameState::initHeroes()
auto boat = dynamic_cast<CGBoat*>(handler->create(callback, nullptr)); auto boat = dynamic_cast<CGBoat*>(handler->create(callback, nullptr));
handler->configureObject(boat, gs->getRandomGenerator()); handler->configureObject(boat, gs->getRandomGenerator());
boat->pos = hero->pos; boat->setAnchorPos(hero->anchorPos());
boat->appearance = handler->getTemplates().front(); boat->appearance = handler->getTemplates().front();
boat->id = ObjectInstanceID(static_cast<si32>(gs->map->objects.size())); boat->id = ObjectInstanceID(static_cast<si32>(gs->map->objects.size()));
@ -630,20 +629,20 @@ void CGameState::initHeroes()
{ {
auto * hero = dynamic_cast<CGHeroInstance*>(obj.get()); auto * hero = dynamic_cast<CGHeroInstance*>(obj.get());
hero->initHero(getRandomGenerator()); hero->initHero(getRandomGenerator());
map->allHeroes[hero->getHeroType().getNum()] = hero; map->allHeroes[hero->getHeroTypeID().getNum()] = hero;
} }
} }
std::set<HeroTypeID> heroesToCreate = getUnusedAllowedHeroes(); //ids of heroes to be created and put into the pool std::set<HeroTypeID> heroesToCreate = getUnusedAllowedHeroes(); //ids of heroes to be created and put into the pool
for(auto ph : map->predefinedHeroes) for(auto ph : map->predefinedHeroes)
{ {
if(!vstd::contains(heroesToCreate, ph->getHeroType())) if(!vstd::contains(heroesToCreate, ph->getHeroTypeID()))
continue; continue;
ph->initHero(getRandomGenerator()); ph->initHero(getRandomGenerator());
heroesPool->addHeroToPool(ph); 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 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) for(auto & vti : map->towns)
{ {
assert(vti->town); assert(vti->getTown());
if(!vti->getNameTextID().empty()) if(!vti->getNameTextID().empty())
continue; continue;
FactionID faction = vti->getFaction(); FactionID faction = vti->getFactionID();
if(availableNames.empty()) if(availableNames.empty())
{ {
@ -799,8 +798,8 @@ void CGameState::initTowns()
for (auto & vti : map->towns) for (auto & vti : map->towns)
{ {
assert(vti->town); assert(vti->getTown());
assert(vti->town->creatures.size() <= GameConstants::CREATURES_PER_TOWN); 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 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 }; 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); vti->addBuilding(BuildingID::VILLAGE_HALL);
//init hordes //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 if(vti->hasBuilt(hordes[i])) //if we have horde for this level
{ {
@ -895,7 +894,7 @@ void CGameState::initTowns()
int sel = -1; int sel = -1;
for(ui32 ps=0;ps<vti->possibleSpells.size();ps++) for(ui32 ps=0;ps<vti->possibleSpells.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 if (total == 0) // remaining spells have 0 probability
break; break;
@ -903,7 +902,7 @@ void CGameState::initTowns()
auto r = getRandomGenerator().nextInt(total - 1); auto r = getRandomGenerator().nextInt(total - 1);
for(ui32 ps=0; ps<vti->possibleSpells.size();ps++) for(ui32 ps=0; ps<vti->possibleSpells.size();ps++)
{ {
r -= vti->possibleSpells[ps].toSpell()->getProbability(vti->getFaction()); r -= vti->possibleSpells[ps].toSpell()->getProbability(vti->getFactionID());
if(r<0) if(r<0)
{ {
sel = ps; sel = ps;
@ -964,22 +963,18 @@ void CGameState::placeHeroesInTowns()
{ {
for(CGTownInstance * t : player.second.getTowns()) for(CGTownInstance * t : player.second.getTowns())
{ {
if(h->visitablePos().z != t->visitablePos().z) bool heroOnTownBlockableTile = t->blockingAt(h->visitablePos());
continue;
bool heroOnTownBlockableTile = t->blockingAt(h->visitablePos().x, h->visitablePos().y);
// current hero position is at one of blocking tiles of current town // 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 // assume that this hero should be visiting the town (H3M format quirk) and move hero to correct position
if (heroOnTownBlockableTile) if (heroOnTownBlockableTile)
{ {
int3 correctedPos = h->convertFromVisitablePos(t->visitablePos());
map->removeBlockVisTiles(h); map->removeBlockVisTiles(h);
h->pos = correctedPos; int3 correctedPos = h->convertFromVisitablePos(t->visitablePos());
h->setAnchorPos(correctedPos);
map->addBlockVisTiles(h); 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) if(h->visitablePos().z != t->visitablePos().z)
continue; continue;
if (t->visitableAt(h->visitablePos().x, h->visitablePos().y)) if (t->visitableAt(h->visitablePos()))
{ {
assert(t->visitingHero == nullptr); assert(t->visitingHero == nullptr);
t->setVisitingHero(h); t->setVisitingHero(h);
@ -1066,7 +1061,7 @@ BattleField CGameState::battleGetBattlefieldType(int3 tile, vstd::RNG & rand)
for(auto &obj : map->objects) for(auto &obj : map->objects)
{ {
//look only for objects covering given tile //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; continue;
auto customBattlefield = obj->getBattlefield(); auto customBattlefield = obj->getBattlefield();
@ -1250,10 +1245,10 @@ bool CGameState::isVisible(const CGObjectInstance * obj, const std::optional<Pla
{ {
for(int fx=0; fx < obj->getWidth(); ++fx) for(int fx=0; fx < obj->getWidth(); ++fx)
{ {
int3 pos = obj->pos + int3(-fx, -fy, 0); int3 pos = obj->anchorPos() + int3(-fx, -fy, 0);
if ( map->isInTheMap(pos) && if ( map->isInTheMap(pos) &&
obj->coveringAt(pos.x, pos.y) && obj->coveringAt(pos) &&
isVisible(pos, *player)) isVisible(pos, *player))
return true; return true;
} }
@ -1660,18 +1655,13 @@ std::set<HeroTypeID> CGameState::getUnusedAllowedHeroes(bool alsoIncludeNotAllow
} }
for(auto hero : map->heroesOnMap) //heroes instances initialization for(auto hero : map->heroesOnMap) //heroes instances initialization
{ ret -= hero->getHeroTypeID();
if(hero->type)
ret -= hero->type->getId();
else
ret -= hero->getHeroType();
}
for(auto obj : map->objects) //prisons for(auto obj : map->objects) //prisons
{ {
auto * hero = dynamic_cast<const CGHeroInstance *>(obj.get()); auto * hero = dynamic_cast<const CGHeroInstance *>(obj.get());
if(hero && hero->ID == Obj::PRISON) if(hero && hero->ID == Obj::PRISON)
ret -= hero->getHeroType(); ret -= hero->getHeroTypeID();
} }
return ret; return ret;
@ -1695,7 +1685,7 @@ CGHeroInstance * CGameState::getUsedHero(const HeroTypeID & hid) const
auto * hero = dynamic_cast<CGHeroInstance *>(obj.get()); auto * hero = dynamic_cast<CGHeroInstance *>(obj.get());
assert(hero); assert(hero);
if (hero->getHeroType() == hid) if (hero->getHeroTypeID() == hid)
return hero; return hero;
} }

View File

@ -86,7 +86,7 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(const CampaignTravel & tr
.And(Selector::subtype()(BonusSubtypeID(g))) .And(Selector::subtype()(BonusSubtypeID(g)))
.And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL)); .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 //trimming sec skills
for(auto & hero : campaignHeroReplacements) for(auto & hero : campaignHeroReplacements)
{ {
hero.hero->secSkills = hero.hero->type->secSkillsInit; hero.hero->secSkills = hero.hero->getHeroType()->secSkillsInit;
hero.hero->recreateSecondarySkillsBonuses(); hero.hero->recreateSecondarySkillsBonuses();
} }
} }
@ -240,7 +240,7 @@ void CGameStateCampaign::placeCampaignHeroes()
for(auto & replacement : campaignHeroReplacements) for(auto & replacement : campaignHeroReplacements)
if (replacement.heroPlaceholderId.hasValue()) if (replacement.heroPlaceholderId.hasValue())
heroesToRemove.insert(replacement.hero->getHeroType()); heroesToRemove.insert(replacement.hero->getHeroTypeID());
for(auto & heroID : heroesToRemove) for(auto & heroID : heroesToRemove)
{ {
@ -368,9 +368,9 @@ void CGameStateCampaign::replaceHeroesPlaceholders()
heroToPlace->id = campaignHeroReplacement.heroPlaceholderId; heroToPlace->id = campaignHeroReplacement.heroPlaceholderId;
if(heroPlaceholder->tempOwner.isValidPlayer()) if(heroPlaceholder->tempOwner.isValidPlayer())
heroToPlace->tempOwner = heroPlaceholder->tempOwner; heroToPlace->tempOwner = heroPlaceholder->tempOwner;
heroToPlace->pos = heroPlaceholder->pos; heroToPlace->setAnchorPos(heroPlaceholder->anchorPos());
heroToPlace->type = heroToPlace->getHeroType().toHeroType(); heroToPlace->setHeroType(heroToPlace->getHeroTypeID());
heroToPlace->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, heroToPlace->type->heroClass->getIndex())->getTemplates().front(); heroToPlace->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, heroToPlace->getHeroTypeID())->getTemplates().front();
gameState->map->removeBlockVisTiles(heroPlaceholder, true); gameState->map->removeBlockVisTiles(heroPlaceholder, true);
gameState->map->objects[heroPlaceholder->id.getNum()] = nullptr; gameState->map->objects[heroPlaceholder->id.getNum()] = nullptr;
@ -563,7 +563,7 @@ void CGameStateCampaign::initHeroes()
{ {
for (auto & hero : heroes) for (auto & hero : heroes)
{ {
if (hero->getHeroType().getNum() == chosenBonus->info1) if (hero->getHeroTypeID().getNum() == chosenBonus->info1)
{ {
giveCampaignBonusToHero(hero); giveCampaignBonusToHero(hero);
break; break;
@ -655,14 +655,14 @@ void CGameStateCampaign::initTowns()
if (!owner->human) if (!owner->human)
continue; continue;
if (town->pos != pi.posOfMainTown) if (town->anchorPos() != pi.posOfMainTown)
continue; continue;
BuildingID newBuilding; BuildingID newBuilding;
if(gameState->scenarioOps->campState->formatVCMI()) if(gameState->scenarioOps->campState->formatVCMI())
newBuilding = BuildingID(chosenBonus->info1); newBuilding = BuildingID(chosenBonus->info1);
else 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 // Build granted building & all prerequisites - e.g. Mages Guild Lvl 3 should also give Mages Guild Lvl 1 & 2
while(true) while(true)
@ -675,7 +675,7 @@ void CGameStateCampaign::initTowns()
town->addBuilding(newBuilding); town->addBuilding(newBuilding);
auto building = town->town->buildings.at(newBuilding); auto building = town->getTown()->buildings.at(newBuilding);
newBuilding = building->upgrade; newBuilding = building->upgrade;
} }
break; break;

View File

@ -381,7 +381,7 @@ float Statistic::getTownBuiltRatio(const PlayerState * ps)
for(const auto & t : ps->getTowns()) for(const auto & t : ps->getTowns())
{ {
built += t->getBuildings().size(); built += t->getBuildings().size();
for(const auto & b : t->town->buildings) for(const auto & b : t->getTown()->buildings)
if(!t->forbiddenBuildings.count(b.first)) if(!t->forbiddenBuildings.count(b.first))
total += 1; total += 1;
} }

View File

@ -115,7 +115,7 @@ void InfoAboutHero::initFromHero(const CGHeroInstance *h, InfoAboutHero::EInfoLe
initFromArmy(h, detailed); initFromArmy(h, detailed);
hclass = h->type->heroClass; hclass = h->getHeroClass();
name = h->getNameTranslated(); name = h->getNameTranslated();
portraitSource = h->getPortraitSource(); portraitSource = h->getPortraitSource();

View File

@ -25,7 +25,7 @@ std::map<HeroTypeID, CGHeroInstance*> TavernHeroesPool::unusedHeroesFromPool() c
{ {
std::map<HeroTypeID, CGHeroInstance*> pool = heroesPool; std::map<HeroTypeID, CGHeroInstance*> pool = heroesPool;
for(const auto & slot : currentTavern) for(const auto & slot : currentTavern)
pool.erase(slot.hero->getHeroType()); pool.erase(slot.hero->getHeroTypeID());
return pool; return pool;
} }
@ -34,7 +34,7 @@ TavernSlotRole TavernHeroesPool::getSlotRole(HeroTypeID hero) const
{ {
for (auto const & slot : currentTavern) for (auto const & slot : currentTavern)
{ {
if (slot.hero->getHeroType() == hero) if (slot.hero->getHeroTypeID() == hero)
return slot.role; return slot.role;
} }
return TavernSlotRole::NONE; return TavernSlotRole::NONE;
@ -106,7 +106,7 @@ CGHeroInstance * TavernHeroesPool::takeHeroFromPool(HeroTypeID hero)
heroesPool.erase(hero); heroesPool.erase(hero);
vstd::erase_if(currentTavern, [&](const TavernSlot & entry){ vstd::erase_if(currentTavern, [&](const TavernSlot & entry){
return entry.hero->type->getId() == hero; return entry.hero->getHeroTypeID() == hero;
}); });
assert(result); assert(result);
@ -138,7 +138,7 @@ void TavernHeroesPool::onNewDay()
void TavernHeroesPool::addHeroToPool(CGHeroInstance * hero) void TavernHeroesPool::addHeroToPool(CGHeroInstance * hero)
{ {
heroesPool[hero->getHeroType()] = hero; heroesPool[hero->getHeroTypeID()] = hero;
} }
void TavernHeroesPool::setAvailability(HeroTypeID hero, std::set<PlayerColor> mask) void TavernHeroesPool::setAvailability(HeroTypeID hero, std::set<PlayerColor> mask)

View File

@ -207,8 +207,13 @@ TObjectTypeHandler CObjectClassesHandler::loadSubObjectFromJson(const std::strin
// Compatibility with 1.5 mods for 1.6. To be removed in 1.7 // 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 // 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")) if (baseObject->id == Obj::CREATURE_BANK)
handler = "bank"; {
if (entry.Struct().count("levels") && !entry.Struct().count("rewards"))
handler = "bank";
else
handler = "configurable";
}
auto createdObject = handlerConstructors.at(handler)(); auto createdObject = handlerConstructors.at(handler)();

View File

@ -96,7 +96,6 @@ bool CTownInstanceConstructor::objectFilter(const CGObjectInstance * object, std
void CTownInstanceConstructor::initializeObject(CGTownInstance * obj) const void CTownInstanceConstructor::initializeObject(CGTownInstance * obj) const
{ {
obj->town = faction->town;
obj->tempOwner = PlayerColor::NEUTRAL; obj->tempOwner = PlayerColor::NEUTRAL;
} }
@ -144,7 +143,7 @@ bool CHeroInstanceConstructor::objectFilter(const CGObjectInstance * object, std
auto heroTest = [&](const HeroTypeID & id) auto heroTest = [&](const HeroTypeID & id)
{ {
return hero->type->getId() == id; return hero->getHeroTypeID() == id;
}; };
if(filters.count(templ->stringID)) if(filters.count(templ->stringID))
@ -154,11 +153,6 @@ bool CHeroInstanceConstructor::objectFilter(const CGObjectInstance * object, std
return false; 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 void CHeroInstanceConstructor::randomizeObject(CGHeroInstance * object, vstd::RNG & rng) const
{ {

View File

@ -81,7 +81,6 @@ public:
const CHeroClass * heroClass = nullptr; const CHeroClass * heroClass = nullptr;
std::map<std::string, LogicalExpression<HeroTypeID>> filters; std::map<std::string, LogicalExpression<HeroTypeID>> filters;
void initializeObject(CGHeroInstance * object) const override;
void randomizeObject(CGHeroInstance * object, vstd::RNG & rng) const override; void randomizeObject(CGHeroInstance * object, vstd::RNG & rng) const override;
void afterLoadFinalization() override; void afterLoadFinalization() override;

View File

@ -78,7 +78,7 @@ void CArmedInstance::updateMoraleBonusFromArmy()
const CStackInstance * inst = slot.second; const CStackInstance * inst = slot.second;
const auto * creature = inst->getCreatureID().toEntity(VLC); 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) // Check for undead flag instead of faction (undead mummies are neutral)
if (!hasUndead) if (!hasUndead)
{ {

View File

@ -33,7 +33,7 @@ std::string CGCreature::getHoverText(PlayerColor player) const
if(stacks.empty()) if(stacks.empty())
{ {
//should not happen... //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"; return "INVALID_STACK";
} }
@ -45,7 +45,7 @@ std::string CGCreature::getHoverText(PlayerColor player) const
else else
ms.appendLocalString(EMetaText::ARRAY_TXT, quantityTextIndex); ms.appendLocalString(EMetaText::ARRAY_TXT, quantityTextIndex);
ms.appendRawString(" "); ms.appendRawString(" ");
ms.appendNamePlural(getCreature()); ms.appendNamePlural(getCreatureID());
return ms.toString(); return ms.toString();
} }
@ -57,7 +57,7 @@ std::string CGCreature::getHoverText(const CGHeroInstance * hero) const
MetaString ms; MetaString ms;
ms.appendNumber(stacks.begin()->second->count); ms.appendNumber(stacks.begin()->second->count);
ms.appendRawString(" "); ms.appendRawString(" ");
ms.appendName(getCreature(), stacks.begin()->second->count); ms.appendName(getCreatureID(), stacks.begin()->second->count);
return ms.toString(); return ms.toString();
} }
else else
@ -69,11 +69,11 @@ std::string CGCreature::getHoverText(const CGHeroInstance * hero) const
std::string CGCreature::getMonsterLevelText() const std::string CGCreature::getMonsterLevelText() const
{ {
std::string monsterLevel = VLC->generaltexth->translate("vcmi.adventureMap.monsterLevel"); 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 attackTypeKey = isRanged ? "vcmi.adventureMap.monsterRangedType" : "vcmi.adventureMap.monsterMeleeType";
std::string attackType = VLC->generaltexth->translate(attackTypeKey); std::string attackType = VLC->generaltexth->translate(attackTypeKey);
boost::replace_first(monsterLevel, "%TOWN", (*VLC->townh)[VLC->creatures()->getById(getCreature())->getFaction()]->getNameTranslated()); boost::replace_first(monsterLevel, "%TOWN", getCreature()->getFactionID().toEntity(VLC)->getNameTranslated());
boost::replace_first(monsterLevel, "%LEVEL", std::to_string(VLC->creatures()->getById(getCreature())->getLevel())); boost::replace_first(monsterLevel, "%LEVEL", std::to_string(getCreature()->getLevel()));
boost::replace_first(monsterLevel, "%ATTACK_TYPE", attackType); boost::replace_first(monsterLevel, "%ATTACK_TYPE", attackType);
return monsterLevel; return monsterLevel;
} }
@ -150,7 +150,7 @@ std::string CGCreature::getPopupText(PlayerColor player) const
std::vector<Component> CGCreature::getPopupComponents(PlayerColor player) const std::vector<Component> CGCreature::getPopupComponents(PlayerColor player) const
{ {
return { return {
Component(ComponentType::CREATURE, getCreature()) Component(ComponentType::CREATURE, getCreatureID())
}; };
} }
@ -182,7 +182,7 @@ void CGCreature::onHeroVisit( const CGHeroInstance * h ) const
BlockingDialog ynd(true,false); BlockingDialog ynd(true,false);
ynd.player = h->tempOwner; ynd.player = h->tempOwner;
ynd.text.appendLocalString(EMetaText::ADVOB_TXT, 86); 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); cb->showBlockingDialog(this, &ynd);
break; break;
} }
@ -197,7 +197,7 @@ void CGCreature::onHeroVisit( const CGHeroInstance * h ) const
std::string tmp = VLC->generaltexth->advobtxt[90]; 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(getStackCount(SlotID(0))));
boost::algorithm::replace_first(tmp, "%d", std::to_string(action)); 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); ynd.text.appendRawString(tmp);
cb->showBlockingDialog(this, &ynd); cb->showBlockingDialog(this, &ynd);
break; break;
@ -205,11 +205,16 @@ void CGCreature::onHeroVisit( const CGHeroInstance * h ) const
} }
} }
CreatureID CGCreature::getCreature() const CreatureID CGCreature::getCreatureID() const
{ {
return CreatureID(getObjTypeIndex().getNum()); return CreatureID(getObjTypeIndex().getNum());
} }
const CCreature * CGCreature::getCreature() const
{
return getCreatureID().toCreature();
}
void CGCreature::pickRandomObject(vstd::RNG & rand) void CGCreature::pickRandomObject(vstd::RNG & rand)
{ {
switch(ID.toEnum()) switch(ID.toEnum())
@ -279,7 +284,7 @@ void CGCreature::initObj(vstd::RNG & rand)
stacks[SlotID(0)]->setType(getCreature()); stacks[SlotID(0)]->setType(getCreature());
TQuantity &amount = stacks[SlotID(0)]->count; TQuantity &amount = stacks[SlotID(0)]->count;
const Creature * c = VLC->creatures()->getById(getCreature()); const Creature * c = getCreature();
if(amount == 0) if(amount == 0)
{ {
amount = rand.nextInt(c->getAdvMapAmountMin(), c->getAdvMapAmountMax()); 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()) for(const auto & elem : h->Slots())
{ {
bool isOurUpgrade = vstd::contains(getCreature().toCreature()->upgrades, elem.second->getCreatureID()); bool isOurUpgrade = vstd::contains(getCreature()->upgrades, elem.second->getCreatureID());
bool isOurDowngrade = vstd::contains(elem.second->type->upgrades, getCreature()); bool isOurDowngrade = vstd::contains(elem.second->type->upgrades, getCreatureID());
if(isOurUpgrade || isOurDowngrade) if(isOurUpgrade || isOurDowngrade)
count += elem.second->count; count += elem.second->count;
@ -380,7 +385,7 @@ int CGCreature::takenAction(const CGHeroInstance *h, bool allowJoin) const
if(diplomacy * 2 + sympathy + 1 >= character) 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)); int32_t stackCount = getStackCount(SlotID(0));
return recruitCost * stackCount; //join for gold return recruitCost * stackCount; //join for gold
} }
@ -493,7 +498,7 @@ void CGCreature::flee( const CGHeroInstance * h ) const
BlockingDialog ynd(true,false); BlockingDialog ynd(true,false);
ynd.player = h->tempOwner; ynd.player = h->tempOwner;
ynd.text.appendLocalString(EMetaText::ADVOB_TXT,91); 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); cb->showBlockingDialog(this, &ynd);
} }
@ -513,7 +518,7 @@ void CGCreature::battleFinished(const CGHeroInstance *hero, const BattleResult &
{ {
//merge stacks into one //merge stacks into one
TSlots::const_iterator i; TSlots::const_iterator i;
const CCreature * cre = getCreature().toCreature(); const CCreature * cre = getCreature();
for(i = stacks.begin(); i != stacks.end(); i++) for(i = stacks.begin(); i != stacks.end(); i++)
{ {
if(cre->isMyUpgrade(i->second->type)) if(cre->isMyUpgrade(i->second->type))
@ -562,7 +567,7 @@ bool CGCreature::containsUpgradedStack() const
float c = 5325.181015f; float c = 5325.181015f;
float d = 32788.727920f; float d = 32788.727920f;
int val = static_cast<int>(std::floor(a * pos.x + b * pos.y + c * pos.z + d)); int val = static_cast<int>(std::floor(a * visitablePos().x + b * visitablePos().y + c * visitablePos().z + d));
return ((val % 32768) % 100) < 50; return ((val % 32768) % 100) < 50;
} }
@ -591,7 +596,7 @@ int CGCreature::getNumberOfStacks(const CGHeroInstance *hero) const
ui32 c = 1943276003u; ui32 c = 1943276003u;
ui32 d = 3174620878u; ui32 d = 3174620878u;
ui32 R1 = a * static_cast<ui32>(pos.x) + b * static_cast<ui32>(pos.y) + c * static_cast<ui32>(pos.z) + d; ui32 R1 = a * static_cast<ui32>(visitablePos().x) + b * static_cast<ui32>(visitablePos().y) + c * static_cast<ui32>(visitablePos().z) + d;
ui32 R2 = (R1 >> 16) & 0x7fff; ui32 R2 = (R1 >> 16) & 0x7fff;
int R4 = R2 % 100 + 1; int R4 = R2 % 100 + 1;

View File

@ -49,7 +49,8 @@ public:
void newTurn(vstd::RNG & rand) const override; void newTurn(vstd::RNG & rand) const override;
void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
void blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) 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, //stack formation depends on position,
bool containsUpgradedStack() const; bool containsUpgradedStack() const;

View File

@ -93,7 +93,7 @@ FactionID CGDwelling::randomizeFaction(vstd::RNG & rand)
assert(linkedTown->ID == Obj::TOWN); assert(linkedTown->ID == Obj::TOWN);
if(linkedTown->ID==Obj::TOWN) if(linkedTown->ID==Obj::TOWN)
return linkedTown->getFaction(); return linkedTown->getFactionID();
} }
if(!randomizationInfo->allowedFactions.empty()) if(!randomizationInfo->allowedFactions.empty())

View File

@ -116,9 +116,9 @@ ui32 CGHeroInstance::getTileMovementCost(const TerrainTile & dest, const Terrain
return static_cast<ui32>(ret); return static_cast<ui32>(ret);
} }
FactionID CGHeroInstance::getFaction() const FactionID CGHeroInstance::getFactionID() const
{ {
return FactionID(type->heroClass->faction); return getHeroClass()->faction;
} }
const IBonusBearer* CGHeroInstance::getBonusBearer() const const IBonusBearer* CGHeroInstance::getBonusBearer() const
@ -229,10 +229,10 @@ bool CGHeroInstance::canLearnSkill(const SecondarySkill & which) const
if (getSecSkillLevel(which) > 0) if (getSecSkillLevel(which) > 0)
return false; return false;
if (type->heroClass->secSkillProbability.count(which) == 0) if (getHeroClass()->secSkillProbability.count(which) == 0)
return false; return false;
if (type->heroClass->secSkillProbability.at(which) == 0) if (getHeroClass()->secSkillProbability.at(which) == 0)
return false; return false;
return true; return true;
@ -282,7 +282,6 @@ int CGHeroInstance::movementPointsLimitCached(bool onLand, const TurnInfo * ti)
CGHeroInstance::CGHeroInstance(IGameCallback * cb) CGHeroInstance::CGHeroInstance(IGameCallback * cb)
: CArmedInstance(cb), : CArmedInstance(cb),
type(nullptr),
tacticFormationEnabled(false), tacticFormationEnabled(false),
inTownGarrison(false), inTownGarrison(false),
moveDir(4), moveDir(4),
@ -303,14 +302,30 @@ PlayerColor CGHeroInstance::getOwner() const
return tempOwner; 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()); return HeroTypeID(getObjTypeIndex().getNum());
} }
void CGHeroInstance::setHeroType(HeroTypeID heroType) void CGHeroInstance::setHeroType(HeroTypeID heroType)
{ {
assert(type == nullptr);
subID = heroType; subID = heroType;
} }
@ -323,16 +338,14 @@ void CGHeroInstance::initHero(vstd::RNG & rand, const HeroTypeID & SUBID)
void CGHeroInstance::initHero(vstd::RNG & rand) void CGHeroInstance::initHero(vstd::RNG & rand)
{ {
assert(validTypes(true)); assert(validTypes(true));
if(!type)
type = getHeroType().toHeroType();
if (ID == Obj::HERO) 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)) if(!vstd::contains(spells, SpellID::PRESET))
{ {
// hero starts with default spells // hero starts with default spells
for(const auto & spellID : type->spells) for(const auto & spellID : getHeroType()->spells)
spells.insert(spellID); spells.insert(spellID);
} }
else //remove placeholder else //remove placeholder
@ -341,7 +354,7 @@ void CGHeroInstance::initHero(vstd::RNG & rand)
if(!vstd::contains(spells, SpellID::SPELLBOOK_PRESET)) if(!vstd::contains(spells, SpellID::SPELLBOOK_PRESET))
{ {
// hero starts with default spellbook presence status // 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); auto artifact = ArtifactUtils::createArtifact(ArtifactID::SPELLBOOK);
putArtifact(ArtifactPosition::SPELLBOOK, artifact); putArtifact(ArtifactPosition::SPELLBOOK, artifact);
@ -360,14 +373,14 @@ void CGHeroInstance::initHero(vstd::RNG & rand)
{ {
for(int g=0; g<GameConstants::PRIMARY_SKILLS; ++g) for(int g=0; g<GameConstants::PRIMARY_SKILLS; ++g)
{ {
pushPrimSkill(static_cast<PrimarySkill>(g), type->heroClass->primarySkillInitial[g]); pushPrimSkill(static_cast<PrimarySkill>(g), getHeroClass()->primarySkillInitial[g]);
} }
} }
if(secSkills.size() == 1 && secSkills[0] == std::pair<SecondarySkill,ui8>(SecondarySkill::NONE, -1)) //set secondary skills to default if(secSkills.size() == 1 && secSkills[0] == std::pair<SecondarySkill,ui8>(SecondarySkill::NONE, -1)) //set secondary skills to default
secSkills = type->secSkillsInit; secSkills = getHeroType()->secSkillsInit;
if (gender == EHeroGender::DEFAULT) if (gender == EHeroGender::DEFAULT)
gender = type->gender; gender = getHeroType()->gender;
setFormation(EArmyFormation::LOOSE); setFormation(EArmyFormation::LOOSE);
if (!stacksCount()) //standard army//initial army if (!stacksCount()) //standard army//initial army
@ -403,9 +416,9 @@ void CGHeroInstance::initHero(vstd::RNG & rand)
addNewBonus(bonus); 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->setArmyObj (castToArmyObj()); //TODO: separate function for setting commanders
commander->giveStackExp (exp); //after our exp is set commander->giveStackExp (exp); //after our exp is set
} }
@ -413,7 +426,7 @@ void CGHeroInstance::initHero(vstd::RNG & rand)
skillsInfo = SecondarySkillsInfo(); skillsInfo = SecondarySkillsInfo();
//copy active (probably growing) bonuses from hero prototype to hero object //copy active (probably growing) bonuses from hero prototype to hero object
for(const std::shared_ptr<Bonus> & b : type->specialty) for(const std::shared_ptr<Bonus> & b : getHeroType()->specialty)
addNewBonus(b); addNewBonus(b);
//initialize bonuses //initialize bonuses
@ -433,14 +446,14 @@ void CGHeroInstance::initArmy(vstd::RNG & rand, IArmyDescriptor * dst)
auto stacksCountChances = cb->getSettings().getVector(EGameSettings::HEROES_STARTING_STACKS_CHANCES); auto stacksCountChances = cb->getSettings().getVector(EGameSettings::HEROES_STARTING_STACKS_CHANCES);
int stacksCountInitRandomNumber = rand.nextInt(1, 100); 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++) for(int stackNo=0; stackNo < maxStacksCount; stackNo++)
{ {
if (stacksCountInitRandomNumber > stacksCountChances[stackNo]) if (stacksCountInitRandomNumber > stacksCountChances[stackNo])
continue; continue;
auto & stack = type->initialArmy[stackNo]; auto & stack = getHeroType()->initialArmy[stackNo];
int count = rand.nextInt(stack.minAmount, stack.maxAmount); int count = rand.nextInt(stack.minAmount, stack.maxAmount);
@ -588,11 +601,11 @@ std::string CGHeroInstance::getMovementPointsTextIfOwner(PlayerColor player) con
ui8 CGHeroInstance::maxlevelsToMagicSchool() const ui8 CGHeroInstance::maxlevelsToMagicSchool() const
{ {
return type->heroClass->isMagicHero() ? 3 : 4; return getHeroClass()->isMagicHero() ? 3 : 4;
} }
ui8 CGHeroInstance::maxlevelsToWisdom() const ui8 CGHeroInstance::maxlevelsToWisdom() const
{ {
return type->heroClass->isMagicHero() ? 3 : 6; return getHeroClass()->isMagicHero() ? 3 : 6;
} }
CGHeroInstance::SecondarySkillsInfo::SecondarySkillsInfo(): CGHeroInstance::SecondarySkillsInfo::SecondarySkillsInfo():
@ -617,11 +630,8 @@ void CGHeroInstance::pickRandomObject(vstd::RNG & rand)
{ {
ID = Obj::HERO; ID = Obj::HERO;
subID = cb->gameState()->pickNextHeroType(getOwner()); subID = cb->gameState()->pickNextHeroType(getOwner());
type = getHeroType().toHeroType(); randomizeArmy(getHeroClass()->faction);
randomizeArmy(type->heroClass->faction);
} }
else
type = getHeroType().toHeroType();
auto oldSubID = subID; 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 // 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 // exclude prisons which should use appearance as set in map, via map editor or RMG
if (ID != Obj::PRISON) if (ID != Obj::PRISON)
setType(ID, type->heroClass->getIndex()); setType(ID, getHeroClass()->getIndex());
this->subID = oldSubID; this->subID = oldSubID;
} }
@ -1064,7 +1074,7 @@ si32 CGHeroInstance::getManaNewTurn() const
BoatId CGHeroInstance::getBoatType() 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<int3> &offsets) const void CGHeroInstance::getOutOffsets(std::vector<int3> &offsets) const
@ -1103,7 +1113,7 @@ void CGHeroInstance::pushPrimSkill( PrimarySkill which, int val )
EAlignment CGHeroInstance::getAlignment() const EAlignment CGHeroInstance::getAlignment() const
{ {
return type->heroClass->getAlignment(); return getHeroClass()->getAlignment();
} }
void CGHeroInstance::initExp(vstd::RNG & rand) void CGHeroInstance::initExp(vstd::RNG & rand)
@ -1127,12 +1137,12 @@ HeroTypeID CGHeroInstance::getPortraitSource() const
if (customPortraitSource.isValid()) if (customPortraitSource.isValid())
return customPortraitSource; return customPortraitSource;
else else
return getHeroType(); return getHeroTypeID();
} }
int32_t CGHeroInstance::getIconIndex() const int32_t CGHeroInstance::getIconIndex() const
{ {
return VLC->heroTypes()->getById(getPortraitSource())->getIconIndex(); return getPortraitSource().toEntity(VLC)->getIconIndex();
} }
std::string CGHeroInstance::getNameTranslated() const std::string CGHeroInstance::getNameTranslated() const
@ -1149,15 +1159,15 @@ std::string CGHeroInstance::getClassNameTextID() const
{ {
if (isCampaignGem()) if (isCampaignGem())
return "core.genrltxt.735"; return "core.genrltxt.735";
return type->heroClass->getNameTextID(); return getHeroClass()->getNameTextID();
} }
std::string CGHeroInstance::getNameTextID() const std::string CGHeroInstance::getNameTextID() const
{ {
if (!nameCustomTextId.empty()) if (!nameCustomTextId.empty())
return nameCustomTextId; return nameCustomTextId;
if (type) if (getHeroTypeID().hasValue())
return type->getNameTextID(); return getHeroType()->getNameTextID();
// FIXME: called by logging from some specialties (mods?) before type is set on deserialization // FIXME: called by logging from some specialties (mods?) before type is set on deserialization
// assert(0); // assert(0);
@ -1173,8 +1183,8 @@ std::string CGHeroInstance::getBiographyTextID() const
{ {
if (!biographyCustomTextId.empty()) if (!biographyCustomTextId.empty())
return biographyCustomTextId; return biographyCustomTextId;
if (type) if (getHeroTypeID().hasValue())
return type->getBiographyTextID(); return getHeroType()->getBiographyTextID();
return ""; //for random hero return ""; //for random hero
} }
@ -1372,11 +1382,11 @@ std::vector<SecondarySkill> CGHeroInstance::getLevelUpProposedSecondarySkills(vs
SecondarySkill selection; SecondarySkill selection;
if (selectWisdom) if (selectWisdom)
selection = type->heroClass->chooseSecSkill(intersect(options, wisdomList), rand); selection = getHeroClass()->chooseSecSkill(intersect(options, wisdomList), rand);
else if (selectSchool) else if (selectSchool)
selection = type->heroClass->chooseSecSkill(intersect(options, schoolList), rand); selection = getHeroClass()->chooseSecSkill(intersect(options, schoolList), rand);
else else
selection = type->heroClass->chooseSecSkill(options, rand); selection = getHeroClass()->chooseSecSkill(options, rand);
skills.push_back(selection); skills.push_back(selection);
options.erase(selection); options.erase(selection);
@ -1407,7 +1417,7 @@ PrimarySkill CGHeroInstance::nextPrimarySkill(vstd::RNG & rand) const
{ {
assert(gainsLevel()); assert(gainsLevel());
const auto isLowLevelHero = level < GameConstants::HERO_HIGH_LEVEL; 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()) if (isCampaignYog())
{ {
@ -1537,35 +1547,25 @@ bool CGHeroInstance::hasVisions(const CGObjectInstance * target, BonusSubtypeID
if (visionsMultiplier > 0) if (visionsMultiplier > 0)
vstd::amax(visionsRange, 3); //minimum range is 3 tiles, but only if VISIONS bonus present vstd::amax(visionsRange, 3); //minimum range is 3 tiles, but only if VISIONS bonus present
const int distance = static_cast<int>(target->pos.dist2d(visitablePos())); const int distance = static_cast<int>(target->anchorPos().dist2d(visitablePos()));
//logGlobal->debug(boost::str(boost::format("Visions: dist %d, mult %d, range %d") % distance % visionsMultiplier % visionsRange)); //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 std::string CGHeroInstance::getHeroTypeName() const
{ {
if(ID == Obj::HERO || ID == Obj::PRISON) if(ID == Obj::HERO || ID == Obj::PRISON)
{ return getHeroType()->getJsonKey();
if(type)
{
return type->getJsonKey();
}
else
{
return getHeroType().toEntity(VLC)->getJsonKey();
}
}
return ""; return "";
} }
void CGHeroInstance::afterAddToMap(CMap * map) void CGHeroInstance::afterAddToMap(CMap * map)
{ {
if(ID != Obj::PRISON) if(ID != Obj::PRISON)
{
map->heroesOnMap.emplace_back(this); map->heroesOnMap.emplace_back(this);
}
} }
void CGHeroInstance::afterRemoveFromMap(CMap* map) void CGHeroInstance::afterRemoveFromMap(CMap* map)
{ {
@ -1752,8 +1752,7 @@ void CGHeroInstance::serializeJsonOptions(JsonSerializeFormat & handler)
if(!appearance) if(!appearance)
{ {
// crossoverDeserialize // crossoverDeserialize
type = getHeroType().toHeroType(); appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, getHeroClassID())->getTemplates().front();
appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->getIndex())->getTemplates().front();
} }
} }
@ -1840,7 +1839,7 @@ bool CGHeroInstance::isCampaignYog() const
if (!boost::starts_with(campaign, "DATA/YOG")) // "Birth of a Barbarian" if (!boost::starts_with(campaign, "DATA/YOG")) // "Birth of a Barbarian"
return false; return false;
if (getHeroType() != HeroTypeID::SOLMYR) // Yog (based on Solmyr) if (getHeroTypeID() != HeroTypeID::SOLMYR) // Yog (based on Solmyr)
return false; return false;
return true; 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" if (!boost::starts_with(campaign, "DATA/GEM") && !boost::starts_with(campaign, "DATA/FINAL")) // "New Beginning" and "Unholy Alliance"
return false; return false;
if (getHeroType() != HeroTypeID::GEM) // Yog (based on Solmyr) if (getHeroTypeID() != HeroTypeID::GEM) // Yog (based on Solmyr)
return false; return false;
return true; return true;

View File

@ -72,7 +72,6 @@ public:
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
const CHero * type;
TExpType exp; //experience points TExpType exp; //experience points
ui32 level; //current level of hero ui32 level; //current level of hero
@ -171,7 +170,7 @@ public:
const IOwnableObject * asOwnable() const final; const IOwnableObject * asOwnable() const final;
//INativeTerrainProvider //INativeTerrainProvider
FactionID getFaction() const override; FactionID getFactionID() const override;
TerrainId getNativeTerrain() const override; TerrainId getNativeTerrain() const override;
int getLowestCreatureSpeed() const; int getLowestCreatureSpeed() const;
si32 manaRegain() const; //how many points of mana can hero regain "naturally" in one day 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 setHeroType(HeroTypeID type);
void initHero(vstd::RNG & rand); void initHero(vstd::RNG & rand);
@ -354,7 +357,11 @@ public:
h & skillsInfo; h & skillsInfo;
h & visitedTown; h & visitedTown;
h & boat; h & boat;
h & type; if (h.version < Handler::Version::REMOVE_TOWN_PTR)
{
CHero * type = nullptr;
h & type;
}
h & commander; h & commander;
h & visitedObjects; h & visitedObjects;
BONUS_TREE_DESERIALIZATION_FIX BONUS_TREE_DESERIALIZATION_FIX

View File

@ -54,14 +54,14 @@ MapObjectSubID CGObjectInstance::getObjTypeIndex() const
return subID; return subID;
} }
int3 CGObjectInstance::getPosition() const int3 CGObjectInstance::anchorPos() const
{ {
return pos; return pos;
} }
int3 CGObjectInstance::getTopVisiblePos() const int3 CGObjectInstance::getTopVisiblePos() const
{ {
return pos - appearance->getTopVisibleOffset(); return anchorPos() - appearance->getTopVisibleOffset();
} }
void CGObjectInstance::setOwner(const PlayerColor & ow) void CGObjectInstance::setOwner(const PlayerColor & ow)
@ -69,6 +69,11 @@ void CGObjectInstance::setOwner(const PlayerColor & ow)
tempOwner = ow; tempOwner = ow;
} }
void CGObjectInstance::setAnchorPos(int3 newPos)
{
pos = newPos;
}
int CGObjectInstance::getWidth() const int CGObjectInstance::getWidth() const
{ {
return appearance->getWidth(); return appearance->getWidth();
@ -79,32 +84,19 @@ int CGObjectInstance::getHeight() const
return appearance->getHeight(); 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 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 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 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<int3> CGObjectInstance::getBlockedPos() const std::set<int3> CGObjectInstance::getBlockedPos() const
@ -115,7 +107,7 @@ std::set<int3> CGObjectInstance::getBlockedPos() const
for(int h=0; h<getHeight(); ++h) for(int h=0; h<getHeight(); ++h)
{ {
if(appearance->isBlockedAt(w, h)) if(appearance->isBlockedAt(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; return ret;
@ -215,6 +207,8 @@ int CGObjectInstance::getSightRadius() const
int3 CGObjectInstance::getVisitableOffset() const int3 CGObjectInstance::getVisitableOffset() const
{ {
if (!isVisitable())
throw std::runtime_error("Attempt to access visitable offset of a non-visitable object!");
return appearance->getVisitableOffset(); return appearance->getVisitableOffset();
} }
@ -313,6 +307,9 @@ void CGObjectInstance::onHeroVisit( const CGHeroInstance * h ) const
int3 CGObjectInstance::visitablePos() const int3 CGObjectInstance::visitablePos() const
{ {
if (!isVisitable())
throw std::runtime_error("Attempt to access visitable position on a non-visitable object!");
return pos - getVisitableOffset(); return pos - getVisitableOffset();
} }

View File

@ -28,8 +28,6 @@ using TObjectTypeHandler = std::shared_ptr<AObjectTypeHandler>;
class DLL_LINKAGE CGObjectInstance : public IObjectInterface class DLL_LINKAGE CGObjectInstance : public IObjectInterface
{ {
public: public:
/// Position of bottom-right corner of object on map
int3 pos;
/// Type of object, e.g. town, hero, creature. /// Type of object, e.g. town, hero, creature.
MapObjectID ID; MapObjectID ID;
/// Subtype of object, depends on type /// Subtype of object, depends on type
@ -41,6 +39,9 @@ public:
/// Defines appearance of object on map (animation, blocked tiles, blit order, etc) /// Defines appearance of object on map (animation, blocked tiles, blit order, etc)
std::shared_ptr<const ObjectTemplate> appearance; std::shared_ptr<const ObjectTemplate> appearance;
/// Position of bottom-right corner of object on map
int3 pos;
std::string instanceName; std::string instanceName;
std::string typeName; std::string typeName;
std::string subTypeName; std::string subTypeName;
@ -62,21 +63,19 @@ public:
return this->tempOwner; return this->tempOwner;
} }
void setOwner(const PlayerColor & ow); void setOwner(const PlayerColor & ow);
void setAnchorPos(int3 pos);
/** APPEARANCE ACCESSORS **/ /** APPEARANCE ACCESSORS **/
int getWidth() const; //returns width of object graphic in tiles int getWidth() const; //returns width of object graphic in tiles
int getHeight() const; //returns height of object graphic in tiles int getHeight() const; //returns height of object graphic in tiles
int3 visitablePos() const override; int3 visitablePos() const override;
int3 getPosition() const override; int3 anchorPos() const override;
int3 getTopVisiblePos() const; 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 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 (x, y) (h3m pos) 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 (x, y) (h3m pos) bool coveringAt (const int3 & pos) const; //returns true if object covers with picture location
std::set<int3> getBlockedPos() const; //returns set of positions blocked by this object std::set<int3> getBlockedPos() const; //returns set of positions blocked by this object
const std::set<int3> & getBlockedOffsets() const; //returns set of relative positions blocked by this object const std::set<int3> & getBlockedOffsets() const; //returns set of relative positions blocked by this object

View File

@ -45,7 +45,7 @@ int CGTownInstance::getSightRadius() const //returns sight distance
for(const auto & bid : builtBuildings) for(const auto & bid : builtBuildings)
{ {
auto height = town->buildings.at(bid)->height; auto height = getTown()->buildings.at(bid)->height;
if(ret < height) if(ret < height)
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 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 int CGTownInstance::creatureGrowth(const int & level) const
@ -127,7 +127,7 @@ GrowthInfo CGTownInstance::getGrowthInfo(int level) const
{ {
GrowthInfo ret; GrowthInfo ret;
if (level<0 || level >=town->creatures.size()) if (level<0 || level >=getTown()->creatures.size())
return ret; return ret;
if (creatures[level].second.empty()) if (creatures[level].second.empty())
return ret; //no dwelling return ret; //no dwelling
@ -151,11 +151,11 @@ GrowthInfo CGTownInstance::getGrowthInfo(int level) const
else if (hasBuilt(BuildingID::CITADEL)) else if (hasBuilt(BuildingID::CITADEL))
ret.entries.emplace_back(subID, BuildingID::CITADEL, castleBonus = base / 2); 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)) if(hasBuilt(BuildingID::HORDE_1))
ret.entries.emplace_back(subID, BuildingID::HORDE_1, creature->getHorde()); 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)) if(hasBuilt(BuildingID::HORDE_2))
ret.entries.emplace_back(subID, BuildingID::HORDE_2, creature->getHorde()); ret.entries.emplace_back(subID, BuildingID::HORDE_2, creature->getHorde());
@ -209,11 +209,11 @@ int CGTownInstance::getDwellingBonus(const std::vector<CreatureID>& creatureIds,
TResources CGTownInstance::dailyIncome() const TResources CGTownInstance::dailyIncome() const
{ {
TResources ret; TResources ret;
for(const auto & p : town->buildings) for(const auto & p : getTown()->buildings)
{ {
BuildingID buildingUpgrade; BuildingID buildingUpgrade;
for(const auto & p2 : town->buildings) for(const auto & p2 : getTown()->buildings)
{ {
if (p2.second->upgrade == p.first) if (p2.second->upgrade == p.first)
{ {
@ -251,10 +251,10 @@ bool CGTownInstance::hasCapitol() const
TownFortifications CGTownInstance::fortificationsLevel() const TownFortifications CGTownInstance::fortificationsLevel() const
{ {
auto result = town->fortifications; auto result = getTown()->fortifications;
for (auto const & buildingID : builtBuildings) for (auto const & buildingID : builtBuildings)
result += town->buildings.at(buildingID)->fortifications; result += getTown()->buildings.at(buildingID)->fortifications;
if (result.wallsHealth == 0) if (result.wallsHealth == 0)
return TownFortifications(); return TownFortifications();
@ -264,7 +264,6 @@ TownFortifications CGTownInstance::fortificationsLevel() const
CGTownInstance::CGTownInstance(IGameCallback *cb): CGTownInstance::CGTownInstance(IGameCallback *cb):
CGDwelling(cb), CGDwelling(cb),
town(nullptr),
built(0), built(0),
destroyed(0), destroyed(0),
identifier(0), identifier(0),
@ -379,17 +378,17 @@ void CGTownInstance::onHeroLeave(const CGHeroInstance * h) const
std::string CGTownInstance::getObjectName() const std::string CGTownInstance::getObjectName() const
{ {
return getNameTranslated() + ", " + town->faction->getNameTranslated(); return getNameTranslated() + ", " + getTown()->faction->getNameTranslated();
} }
bool CGTownInstance::townEnvisagesBuilding(BuildingSubID::EBuildingSubID subId) const 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) void CGTownInstance::initializeConfigurableBuildings(vstd::RNG & rand)
{ {
for(const auto & kvp : town->buildings) for(const auto & kvp : getTown()->buildings)
{ {
if(!kvp.second->rewardableObjectInfo.getParameters().isNull()) if(!kvp.second->rewardableObjectInfo.getParameters().isNull())
rewardableBuildings[kvp.first] = new TownRewardableBuildingInstance(this, kvp.second->bid, rand); 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 assert(ID == Obj::TOWN); // just in case
setType(ID, subID); setType(ID, subID);
town = (*VLC->townh)[getFaction()]->town; randomizeArmy(getFactionID());
randomizeArmy(getFaction());
updateAppearance(); updateAppearance();
} }
@ -467,19 +465,19 @@ void CGTownInstance::initObj(vstd::RNG & rand) ///initialize town structures
blockVisit = true; blockVisit = true;
if(townEnvisagesBuilding(BuildingSubID::PORTAL_OF_SUMMONING)) //Dungeon for example if(townEnvisagesBuilding(BuildingSubID::PORTAL_OF_SUMMONING)) //Dungeon for example
creatures.resize(town->creatures.size() + 1); creatures.resize(getTown()->creatures.size() + 1);
else 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)); BuildingID buildID = BuildingID(BuildingID::getDwellingFromLevel(level, 0));
int upgradeNum = 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) if (hasBuilt(buildID) && getTown()->creatures.at(level).size() > upgradeNum)
creatures[level].second.push_back(town->creatures[level][upgradeNum]); creatures[level].second.push_back(getTown()->creatures[level][upgradeNum]);
} }
} }
initializeConfigurableBuildings(rand); initializeConfigurableBuildings(rand);
@ -623,9 +621,9 @@ void CGTownInstance::removeCapitols(const PlayerColor & owner) const
if (hasCapitol()) // search if there's an older capitol if (hasCapitol()) // search if there's an older capitol
{ {
PlayerState* state = cb->gameState()->getPlayerState(owner); //get all towns owned by player 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; RazeStructures rs;
rs.tid = id; rs.tid = id;
@ -648,7 +646,7 @@ void CGTownInstance::clearArmy() const
BoatId CGTownInstance::getBoatType() const BoatId CGTownInstance::getBoatType() const
{ {
return town->faction->boatType; return getTown()->faction->boatType;
} }
int CGTownInstance::getMarketEfficiency() const int CGTownInstance::getMarketEfficiency() const
@ -703,7 +701,7 @@ void CGTownInstance::updateAppearance()
std::string CGTownInstance::nodeName() const std::string CGTownInstance::nodeName() const
{ {
return "Town (" + (town ? town->faction->getNameTranslated() : "unknown") + ") of " + getNameTranslated(); return "Town (" + getTown()->faction->getNameTranslated() + ") of " + getNameTranslated();
} }
void CGTownInstance::deserializationFix() void CGTownInstance::deserializationFix()
@ -752,7 +750,7 @@ void CGTownInstance::recreateBuildingsBonuses()
for(const auto & upgradeID : builtBuildings) 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) if (upgrade->getBase() == bid && upgrade->upgradeReplacesBonuses)
bonusesReplacedByUpgrade = true; bonusesReplacedByUpgrade = true;
} }
@ -761,7 +759,7 @@ void CGTownInstance::recreateBuildingsBonuses()
if (bonusesReplacedByUpgrade) if (bonusesReplacedByUpgrade)
continue; continue;
auto building = town->buildings.at(bid); auto building = getTown()->buildings.at(bid);
if(building->buildingBonuses.empty()) if(building->buildingBonuses.empty())
continue; continue;
@ -828,21 +826,6 @@ bool CGTownInstance::armedGarrison() const
return !stacks.empty() || garrisonHero; 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 int CGTownInstance::getTownLevel() const
{ {
// count all buildings that are not upgrades // count all buildings that are not upgrades
@ -850,7 +833,7 @@ int CGTownInstance::getTownLevel() const
for(const auto & bid : builtBuildings) for(const auto & bid : builtBuildings)
{ {
if(town->buildings.at(bid)->upgrade == BuildingID::NONE) if(getTown()->buildings.at(bid)->upgrade == BuildingID::NONE)
level++; level++;
} }
return level; return level;
@ -892,7 +875,7 @@ bool CGTownInstance::hasBuilt(BuildingSubID::EBuildingSubID buildingID) const
{ {
for(const auto & bid : builtBuildings) for(const auto & bid : builtBuildings)
{ {
if(town->buildings.at(bid)->subId == buildingID) if(getTown()->buildings.at(bid)->subId == buildingID)
return true; return true;
} }
return false; return false;
@ -905,7 +888,7 @@ bool CGTownInstance::hasBuilt(const BuildingID & buildingID) const
bool CGTownInstance::hasBuilt(const BuildingID & buildingID, FactionID townID) 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 hasBuilt(buildingID);
return false; return false;
} }
@ -923,7 +906,7 @@ std::set<EMarketMode> CGTownInstance::availableModes() const
std::set<EMarketMode> result; std::set<EMarketMode> result;
for (const auto & buildingID : builtBuildings) 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()); result.insert(buildingPtr->marketModes.begin(), buildingPtr->marketModes.end());
} }
@ -950,11 +933,11 @@ std::set<BuildingID> CGTownInstance::getBuildings() const
TResources CGTownInstance::getBuildingCost(const BuildingID & buildingID) const TResources CGTownInstance::getBuildingCost(const BuildingID & buildingID) const
{ {
if (vstd::contains(town->buildings, buildingID)) if (vstd::contains(getTown()->buildings, buildingID))
return town->buildings.at(buildingID)->resources; return getTown()->buildings.at(buildingID)->resources;
else 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(); return TResources();
} }
@ -962,7 +945,7 @@ TResources CGTownInstance::getBuildingCost(const BuildingID & buildingID) const
CBuilding::TRequired CGTownInstance::genBuildingRequirements(const BuildingID & buildID, bool deep) 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 //TODO: find better solution to prevent infinite loops
std::set<BuildingID> processed; std::set<BuildingID> processed;
@ -970,13 +953,13 @@ CBuilding::TRequired CGTownInstance::genBuildingRequirements(const BuildingID &
std::function<CBuilding::TRequired::Variant(const BuildingID &)> dependTest = std::function<CBuilding::TRequired::Variant(const BuildingID &)> dependTest =
[&](const BuildingID & id) -> CBuilding::TRequired::Variant [&](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()); logMod->error("Invalid building ID %d in building dependencies!", id.getNum());
return CBuilding::TRequired::OperatorAll(); return CBuilding::TRequired::OperatorAll();
} }
const CBuilding * build = town->buildings.at(id); const CBuilding * build = getTown()->buildings.at(id);
CBuilding::TRequired::OperatorAll requirements; CBuilding::TRequired::OperatorAll requirements;
if (!hasBuilt(id)) if (!hasBuilt(id))
@ -1001,7 +984,7 @@ CBuilding::TRequired CGTownInstance::genBuildingRequirements(const BuildingID &
CBuilding::TRequired::OperatorAll requirements; CBuilding::TRequired::OperatorAll requirements;
if (building->upgrade != BuildingID::NONE) 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)); requirements.expressions.push_back(dependTest(upgr->bid));
processed.clear(); 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 TerrainId CGTownInstance::getNativeTerrain() const
{ {
return town->faction->getNativeTerrain(); return getTown()->faction->getNativeTerrain();
} }
ArtifactID CGTownInstance::getWarMachineInBuilding(BuildingID building) const ArtifactID CGTownInstance::getWarMachineInBuilding(BuildingID building) const
@ -1166,21 +1162,21 @@ ArtifactID CGTownInstance::getWarMachineInBuilding(BuildingID building) const
if (builtBuildings.count(building) == 0) if (builtBuildings.count(building) == 0)
return ArtifactID::NONE; return ArtifactID::NONE;
if (building == BuildingID::BLACKSMITH && town->warMachineDeprecated.hasValue()) if (building == BuildingID::BLACKSMITH && getTown()->warMachineDeprecated.hasValue())
return town->warMachineDeprecated.toCreature()->warMachine; return getTown()->warMachineDeprecated.toCreature()->warMachine;
return town->buildings.at(building)->warMachine; return getTown()->buildings.at(building)->warMachine;
} }
bool CGTownInstance::isWarMachineAvailable(ArtifactID warMachine) const bool CGTownInstance::isWarMachineAvailable(ArtifactID warMachine) const
{ {
for (auto const & buildingID : builtBuildings) for (auto const & buildingID : builtBuildings)
if (town->buildings.at(buildingID)->warMachine == warMachine) if (getTown()->buildings.at(buildingID)->warMachine == warMachine)
return true; return true;
if (builtBuildings.count(BuildingID::BLACKSMITH) && if (builtBuildings.count(BuildingID::BLACKSMITH) &&
town->warMachineDeprecated.hasValue() && getTown()->warMachineDeprecated.hasValue() &&
town->warMachineDeprecated.toCreature()->warMachine == warMachine) getTown()->warMachineDeprecated.toCreature()->warMachine == warMachine)
return true; return true;
return false; return false;
@ -1200,7 +1196,7 @@ GrowthInfo::Entry::Entry(int subID, const BuildingID & building, int _count): co
{ {
MetaString formatter; MetaString formatter;
formatter.appendRawString("%s %+d"); 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); formatter.replacePositiveNumber(count);
description = formatter.toString(); description = formatter.toString();

View File

@ -50,18 +50,16 @@ struct DLL_LINKAGE GrowthInfo
class DLL_LINKAGE CGTownInstance : public CGDwelling, public IShipyard, public IMarket, public INativeTerrainProvider, public ICreatureUpgrader class DLL_LINKAGE CGTownInstance : public CGDwelling, public IShipyard, public IMarket, public INativeTerrainProvider, public ICreatureUpgrader
{ {
friend class CTownInstanceConstructor;
std::string nameTextId; // name of town std::string nameTextId; // name of town
std::map<BuildingID, TownRewardableBuildingInstance*> convertOldBuildings(std::vector<TownRewardableBuildingInstance*> oldVector); std::map<BuildingID, TownRewardableBuildingInstance*> convertOldBuildings(std::vector<TownRewardableBuildingInstance*> oldVector);
std::set<BuildingID> builtBuildings; std::set<BuildingID> builtBuildings;
public: public:
using CGDwelling::getPosition;
enum EFortLevel {NONE = 0, FORT = 1, CITADEL = 2, CASTLE = 3}; enum EFortLevel {NONE = 0, FORT = 1, CITADEL = 2, CASTLE = 3};
CTownAndVisitingHero townAndVis; CTownAndVisitingHero townAndVis;
const CTown * town;
si32 built; //how many buildings has been built this turn si32 built; //how many buildings has been built this turn
si32 destroyed; //how many buildings has been destroyed this turn si32 destroyed; //how many buildings has been destroyed this turn
ConstTransitivePtr<CGHeroInstance> garrisonHero, visitingHero; ConstTransitivePtr<CGHeroInstance> garrisonHero, visitingHero;
@ -114,16 +112,21 @@ public:
rewardableBuildings = convertOldBuildings(oldVector); rewardableBuildings = convertOldBuildings(oldVector);
} }
if (h.saving) if (h.version < Handler::Version::REMOVE_TOWN_PTR)
{ {
CFaction * faction = town ? town->faction : nullptr; CTown * town = nullptr;
h & faction;
} if (h.saving)
else {
{ CFaction * faction = town ? town->faction : nullptr;
CFaction * faction = nullptr; h & faction;
h & faction; }
town = faction ? faction->town : nullptr; else
{
CFaction * faction = nullptr;
h & faction;
town = faction ? faction->town : nullptr;
}
} }
h & townAndVis; h & townAndVis;
@ -215,9 +218,10 @@ public:
DamageRange getKeepDamageRange() const; DamageRange getKeepDamageRange() const;
const CTown * getTown() const; const CTown * getTown() const;
const CFaction * getFaction() const;
/// INativeTerrainProvider /// INativeTerrainProvider
FactionID getFaction() const override; FactionID getFactionID() const override;
TerrainId getNativeTerrain() 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 /// 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

View File

@ -431,7 +431,7 @@ void CGSeerHut::setObjToKill()
if(getCreatureToKill(true)) if(getCreatureToKill(true))
{ {
quest->stackToKill = getCreatureToKill(false)->getCreature(); quest->stackToKill = getCreatureToKill(false)->getCreatureID();
assert(quest->stackToKill != CreatureID::NONE); assert(quest->stackToKill != CreatureID::NONE);
quest->stackDirection = checkDirection(); quest->stackDirection = checkDirection();
} }
@ -614,7 +614,7 @@ void CGSeerHut::onHeroVisit(const CGHeroInstance * h) const
int CGSeerHut::checkDirection() const int CGSeerHut::checkDirection() const
{ {
int3 cord = getCreatureToKill(false)->pos; int3 cord = getCreatureToKill(false)->visitablePos();
if(static_cast<double>(cord.x) / static_cast<double>(cb->getMapSize().x) < 0.34) //north if(static_cast<double>(cord.x) / static_cast<double>(cb->getMapSize().x) < 0.34) //north
{ {
if(static_cast<double>(cord.y) / static_cast<double>(cb->getMapSize().y) < 0.34) //northwest if(static_cast<double>(cord.y) / static_cast<double>(cb->getMapSize().y) < 0.34) //northwest

View File

@ -145,7 +145,7 @@ void IBoatGenerator::getProblemText(MetaString &out, const CGHeroInstance *visit
out.appendLocalString(EMetaText::ADVOB_TXT, 189); out.appendLocalString(EMetaText::ADVOB_TXT, 189);
break; break;
case NO_WATER: 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; return;
} }
} }

View File

@ -47,7 +47,7 @@ public:
virtual PlayerColor getOwner() const = 0; virtual PlayerColor getOwner() const = 0;
virtual int3 visitablePos() 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 onHeroVisit(const CGHeroInstance * h) const;
virtual void onHeroLeave(const CGHeroInstance * h) const; virtual void onHeroLeave(const CGHeroInstance * h) const;

View File

@ -111,7 +111,7 @@ void CGMine::initObj(vstd::RNG & rand)
} }
else 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; producedResource = GameResID::GOLD;
} }
} }
@ -510,11 +510,11 @@ void CGMonolith::onHeroVisit( const CGHeroInstance * h ) const
if(cb->isTeleportChannelImpassable(channel)) 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; td.impassable = true;
} }
else if(getRandomExit(h) == ObjectInstanceID()) 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 else
h->showInfoDialog(70); h->showInfoDialog(70);
@ -574,7 +574,7 @@ void CGSubterraneanGate::onHeroVisit( const CGHeroInstance * h ) const
if(cb->isTeleportChannelImpassable(channel)) if(cb->isTeleportChannelImpassable(channel))
{ {
h->showInfoDialog(153);//Just inside the entrance you find a large pile of rubble blocking the tunnel. You leave discouraged. 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; td.impassable = true;
} }
else else
@ -657,11 +657,11 @@ void CGWhirlpool::onHeroVisit( const CGHeroInstance * h ) const
TeleportDialog td(h->id, channel); TeleportDialog td(h->id, channel);
if(cb->isTeleportChannelImpassable(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; td.impassable = true;
} }
else if(getRandomExit(h) == ObjectInstanceID()) 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)) if(!isProtected(h))
{ {
@ -1086,9 +1086,9 @@ void CGMagi::onHeroVisit(const CGHeroInstance * h) const
for(const auto & eye : eyes) 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); cb->sendAndApply(fw);
cv.pos = eye->pos; cv.pos = eye->visitablePos();
cb->sendAndApply(cv); cb->sendAndApply(cv);
} }

View File

@ -56,9 +56,9 @@ int3 TownBuildingInstance::visitablePos() const
return town->visitablePos(); return town->visitablePos();
} }
int3 TownBuildingInstance::getPosition() const int3 TownBuildingInstance::anchorPos() const
{ {
return town->getPosition(); return town->anchorPos();
} }
TownRewardableBuildingInstance::TownRewardableBuildingInstance(IGameCallback *cb) TownRewardableBuildingInstance::TownRewardableBuildingInstance(IGameCallback *cb)
@ -73,14 +73,14 @@ TownRewardableBuildingInstance::TownRewardableBuildingInstance(CGTownInstance *
void TownRewardableBuildingInstance::initObj(vstd::RNG & rand) void TownRewardableBuildingInstance::initObj(vstd::RNG & rand)
{ {
assert(town && town->town); assert(town && town->getTown());
configuration = generateConfiguration(rand); configuration = generateConfiguration(rand);
} }
Rewardable::Configuration TownRewardableBuildingInstance::generateConfiguration(vstd::RNG & rand) const Rewardable::Configuration TownRewardableBuildingInstance::generateConfiguration(vstd::RNG & rand) const
{ {
Rewardable::Configuration result; Rewardable::Configuration result;
auto building = town->town->buildings.at(getBuildingType()); auto building = town->getTown()->buildings.at(getBuildingType());
building->rewardableObjectInfo.configureObject(result, rand, cb); building->rewardableObjectInfo.configureObject(result, rand, cb);
for(auto & rewardInfo : result.info) for(auto & rewardInfo : result.info)

View File

@ -38,7 +38,7 @@ public:
const IOwnableObject * asOwnable() const override; const IOwnableObject * asOwnable() const override;
int3 visitablePos() const override; int3 visitablePos() const override;
int3 getPosition() const override; int3 anchorPos() const override;
template <typename Handler> void serialize(Handler &h) template <typename Handler> void serialize(Handler &h)
{ {

View File

@ -232,22 +232,22 @@ CMap::~CMap()
void CMap::removeBlockVisTiles(CGObjectInstance * obj, bool total) 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) 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) 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) if(xVal>=0 && xVal < width && yVal>=0 && yVal < height)
{ {
TerrainTile & curt = terrain[zVal][xVal][yVal]; TerrainTile & curt = terrain[zVal][xVal][yVal];
if(total || obj->visitableAt(xVal, yVal)) if(total || obj->visitableAt(int3(xVal, yVal, zVal)))
{ {
curt.visitableObjects -= obj; curt.visitableObjects -= obj;
curt.visitable = curt.visitableObjects.size(); curt.visitable = curt.visitableObjects.size();
} }
if(total || obj->blockingAt(xVal, yVal)) if(total || obj->blockingAt(int3(xVal, yVal, zVal)))
{ {
curt.blockingObjects -= obj; curt.blockingObjects -= obj;
curt.blocked = curt.blockingObjects.size(); curt.blocked = curt.blockingObjects.size();
@ -259,22 +259,22 @@ void CMap::removeBlockVisTiles(CGObjectInstance * obj, bool total)
void CMap::addBlockVisTiles(CGObjectInstance * obj) 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) 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) 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) if(xVal>=0 && xVal < width && yVal >= 0 && yVal < height)
{ {
TerrainTile & curt = terrain[zVal][xVal][yVal]; TerrainTile & curt = terrain[zVal][xVal][yVal];
if(obj->visitableAt(xVal, yVal)) if(obj->visitableAt(int3(xVal, yVal, zVal)))
{ {
curt.visitableObjects.push_back(obj); curt.visitableObjects.push_back(obj);
curt.visitable = true; curt.visitable = true;
} }
if(obj->blockingAt(xVal, yVal)) if(obj->blockingAt(int3(xVal, yVal, zVal)))
{ {
curt.blockingObjects.push_back(obj); curt.blockingObjects.push_back(obj);
curt.blocked = true; curt.blocked = true;
@ -302,7 +302,7 @@ void CMap::calculateGuardingGreaturePositions()
CGHeroInstance * CMap::getHero(HeroTypeID heroID) CGHeroInstance * CMap::getHero(HeroTypeID heroID)
{ {
for(auto & elem : heroesOnMap) for(auto & elem : heroesOnMap)
if(elem->getHeroType() == heroID) if(elem->getHeroTypeID() == heroID)
return elem; return elem;
return nullptr; return nullptr;
} }
@ -444,14 +444,14 @@ const CGObjectInstance * CMap::getObjectiveObjectFrom(const int3 & pos, Obj type
bestMatch = object; bestMatch = object;
else 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 bestMatch = object;// closer than one we already found
} }
} }
} }
assert(bestMatch != nullptr); // if this happens - victory conditions or map itself is very, very broken 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; return bestMatch;
} }
@ -635,7 +635,7 @@ void CMap::addNewObject(CGObjectInstance * obj)
void CMap::moveObject(CGObjectInstance * obj, const int3 & pos) void CMap::moveObject(CGObjectInstance * obj, const int3 & pos)
{ {
removeBlockVisTiles(obj); removeBlockVisTiles(obj);
obj->pos = pos; obj->setAnchorPos(pos);
addBlockVisTiles(obj); addBlockVisTiles(obj);
} }
@ -803,7 +803,7 @@ void CMap::reindexObjects()
if (lhs->isRemovable() && !rhs->isRemovable()) if (lhs->isRemovable() && !rhs->isRemovable())
return false; return false;
return lhs->pos.y < rhs->pos.y; return lhs->anchorPos().y < rhs->anchorPos().y;
}); });
// instanceNames don't change // instanceNames don't change

View File

@ -615,7 +615,7 @@ std::string CInsertObjectOperation::getLabel() const
CMoveObjectOperation::CMoveObjectOperation(CMap* map, CGObjectInstance* obj, const int3& targetPosition) CMoveObjectOperation::CMoveObjectOperation(CMap* map, CGObjectInstance* obj, const int3& targetPosition)
: CMapOperation(map), : CMapOperation(map),
obj(obj), obj(obj),
initialPos(obj->pos), initialPos(obj->anchorPos()),
targetPos(targetPosition) targetPos(targetPosition)
{ {
} }

Some files were not shown because too many files have changed in this diff Show More