1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-03-17 20:58:07 +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:
build:
strategy:
fail-fast: false
matrix:
include:
- platform: linux-qt6

View File

@ -864,7 +864,7 @@ void AIGateway::makeTurn()
void AIGateway::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h)
{
LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->getNameTranslated() % obj->getObjectName() % obj->pos.toString());
LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->getNameTranslated() % obj->getObjectName() % obj->anchorPos().toString());
switch(obj->ID)
{
case Obj::TOWN:
@ -1454,8 +1454,8 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
void AIGateway::buildStructure(const CGTownInstance * t, BuildingID building)
{
auto name = t->town->buildings.at(building)->getNameTranslated();
logAi->debug("Player %d will build %s in town of %s at %s", ai->playerID, name, t->getNameTranslated(), t->pos.toString());
auto name = t->getTown()->buildings.at(building)->getNameTranslated();
logAi->debug("Player %d will build %s in town of %s at %s", ai->playerID, name, t->getNameTranslated(), t->anchorPos().toString());
cb->buildBuilding(t, building); //just do this;
}

View File

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

View File

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

View File

@ -71,7 +71,7 @@ float HeroManager::evaluateSecSkill(SecondarySkill skill, const CGHeroInstance *
float HeroManager::evaluateSpeciality(const CGHeroInstance * hero) const
{
auto heroSpecial = Selector::source(BonusSource::HERO_SPECIAL, BonusSourceID(hero->type->getId()));
auto heroSpecial = Selector::source(BonusSource::HERO_SPECIAL, BonusSourceID(hero->getHeroTypeID()));
auto secondarySkillBonus = Selector::targetSourceType()(BonusSource::SECONDARY_SKILL);
auto specialSecondarySkillBonuses = hero->getBonuses(heroSpecial.And(secondarySkillBonus));
auto secondarySkillBonuses = hero->getBonuses(Selector::sourceTypeSel(BonusSource::SECONDARY_SKILL));

View File

@ -1244,7 +1244,7 @@ public:
uint64_t RewardEvaluator::getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const
{
if(ai->buildAnalyzer->hasAnyBuilding(town->getFaction(), bi.id))
if(ai->buildAnalyzer->hasAnyBuilding(town->getFactionID(), bi.id))
return 0;
auto creaturesToUpgrade = ai->armyManager->getTotalCreaturesAvailable(bi.baseCreatureID);

View File

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

View File

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

View File

@ -56,7 +56,7 @@ TSubgoal BuildThis::whatToDoToAchieve()
case EBuildingState::ALLOWED:
case EBuildingState::NO_RESOURCES:
{
auto res = town->town->buildings.at(BuildingID(bid))->resources;
auto res = town->getTown()->buildings.at(BuildingID(bid))->resources;
return ai->ah->whatToDo(res, iAmElementar()); //realize immediately or gather resources
}
break;

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()));
else
return sptr(Explore());

View File

@ -88,13 +88,13 @@ TGoalVec GatherTroops::getAllPossibleSubgoals()
}
auto creature = VLC->creatures()->getByIndex(objid);
if(t->getFaction() == creature->getFaction()) //TODO: how to force AI to build unupgraded creatures? :O
if(t->getFactionID() == creature->getFactionID()) //TODO: how to force AI to build unupgraded creatures? :O
{
auto tryFindCreature = [&]() -> std::optional<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);
return make_optional(*itr);
}
@ -109,7 +109,7 @@ TGoalVec GatherTroops::getAllPossibleSubgoals()
if(upgradeNumber < 0)
continue;
BuildingID bid(BuildingID::DWELL_FIRST + creature->getLevel() - 1 + upgradeNumber * t->town->creatures.size());
BuildingID bid(BuildingID::DWELL_FIRST + creature->getLevel() - 1 + upgradeNumber * t->getTown()->creatures.size());
if(t->hasBuilt(bid) && ai->ah->freeResources().canAfford(creature->getFullRecruitCost())) //this assumes only creatures with dwellings are assigned to faction
{
solutions.push_back(sptr(BuyArmy(t, creature->getAIValue() * this->value).setobjid(objid)));

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
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)
{

View File

@ -1032,7 +1032,7 @@ void VCAI::mainLoop()
void VCAI::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h)
{
LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->getNameTranslated() % obj->getObjectName() % obj->pos.toString());
LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->getNameTranslated() % obj->getObjectName() % obj->anchorPos().toString());
switch(obj->ID)
{
case Obj::TOWN:
@ -1417,11 +1417,11 @@ void VCAI::wander(HeroPtr h)
//TODO pick the truly best
const CGTownInstance * t = *boost::max_element(townsNotReachable, compareReinforcements);
logAi->debug("%s can't reach any town, we'll try to make our way to %s at %s", h->getNameTranslated(), t->getNameTranslated(), t->visitablePos().toString());
int3 pos1 = h->pos;
int3 posBefore = h->visitablePos();
striveToGoal(sptr(Goals::ClearWayTo(t->visitablePos()).sethero(h))); //TODO: drop "strive", add to mainLoop
//if out hero is stuck, we may need to request another hero to clear the way we see
if(pos1 == h->pos && h == primaryHero()) //hero can't move
if(posBefore == h->visitablePos() && h == primaryHero()) //hero can't move
{
if(canRecruitAnyHero(t))
recruitHero(t);
@ -1471,7 +1471,7 @@ void VCAI::wander(HeroPtr h)
{
auto chosenObject = cb->getObjInstance(ObjectInstanceID(bestObjectGoal->objid));
if(chosenObject != nullptr)
logAi->debug("Of all %d destinations, object %s at pos=%s seems nice", dests.size(), chosenObject->getObjectName(), chosenObject->pos.toString());
logAi->debug("Of all %d destinations, object %s at pos=%s seems nice", dests.size(), chosenObject->getObjectName(), chosenObject->anchorPos().toString());
}
else
logAi->debug("Trying to realize goal of type %s as part of wandering.", bestObjectGoal->name());
@ -1994,8 +1994,8 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
void VCAI::buildStructure(const CGTownInstance * t, BuildingID building)
{
auto name = t->town->buildings.at(building)->getNameTranslated();
logAi->debug("Player %d will build %s in town of %s at %s", ai->playerID, name, t->getNameTranslated(), t->pos.toString());
auto name = t->getTown()->buildings.at(building)->getNameTranslated();
logAi->debug("Player %d will build %s in town of %s at %s", ai->playerID, name, t->getNameTranslated(), t->anchorPos().toString());
cb->buildBuilding(t, building); //just do this;
}
@ -2081,7 +2081,7 @@ void VCAI::tryRealize(Goals::BuildThis & g)
if (cb->canBuildStructure(t, b) == EBuildingState::ALLOWED)
{
logAi->debug("Player %d will build %s in town of %s at %s",
playerID, t->town->buildings.at(b)->getNameTranslated(), t->getNameTranslated(), t->pos.toString());
playerID, t->getTown()->buildings.at(b)->getNameTranslated(), t->getNameTranslated(), t->anchorPos().toString());
cb->buildBuilding(t, b);
throw goalFulfilledException(sptr(g));
}

View File

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

View File

@ -100,6 +100,7 @@ public:
virtual void assembleArtifacts(const ObjectInstanceID & heroID, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)=0;
virtual void eraseArtifactByClient(const ArtifactLocation & al)=0;
virtual bool dismissCreature(const CArmedInstance *obj, SlotID stackPos)=0;
virtual void saveLocalState(const JsonNode & data)=0;
virtual void endTurn()=0;
virtual void buyArtifact(const CGHeroInstance *hero, ArtifactID aid)=0; //used to buy artifacts in towns (including spell book in the guild and war machines in blacksmith)
virtual void setFormation(const CGHeroInstance * hero, EArmyFormation mode)=0;
@ -193,6 +194,7 @@ public:
void recruitCreatures(const CGDwelling * obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level=-1) override;
bool dismissCreature(const CArmedInstance *obj, SlotID stackPos) override;
bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE) override;
void saveLocalState(const JsonNode & data) override;
void endTurn() override;
void spellResearch(const CGTownInstance *town, SpellID spellAtSlot, bool accepted) override;
void swapGarrisonHero(const CGTownInstance *town) override;

View File

@ -13,6 +13,8 @@
"vcmi.adventureMap.monsterThreat.levels.10" : "Dödlig",
"vcmi.adventureMap.monsterThreat.levels.11" : "Omöjlig",
"vcmi.adventureMap.monsterLevel" : "\n\nNivå: %LEVEL - Faktion: %TOWN",
"vcmi.adventureMap.monsterMeleeType" : "närstrid",
"vcmi.adventureMap.monsterRangedType" : "fjärrstrid",
"vcmi.adventureMap.confirmRestartGame" : "Är du säker på att du vill starta om spelet?",
"vcmi.adventureMap.noTownWithMarket" : "Det finns inga tillgängliga marknadsplatser!",
@ -21,7 +23,7 @@
"vcmi.adventureMap.playerAttacked" : "Spelare har blivit attackerad: %s",
"vcmi.adventureMap.moveCostDetails" : "Förflyttningspoängs-kostnad: %TURNS tur(er) + %POINTS poäng - Återstående poäng: %REMAINING",
"vcmi.adventureMap.moveCostDetailsNoTurns" : "Förflyttningspoängs-kostnad: %POINTS poäng - Återstående poäng: %REMAINING",
"vcmi.adventureMap.movementPointsHeroInfo" : "(Förflyttningspoäng: %REMAINING / %POINTS)",
"vcmi.adventureMap.movementPointsHeroInfo" : "(Förflyttningspoäng: %REMAINING / %POINTS)",
"vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Tyvärr, att spela om motståndarens tur är inte implementerat ännu!",
"vcmi.capitalColors.0" : "Röd",
@ -135,14 +137,15 @@
"vcmi.lobby.pvp.coin.hover" : "Mynt",
"vcmi.lobby.pvp.coin.help" : "Singla slant",
"vcmi.lobby.pvp.randomTown.hover" : "Slumpmässig stad",
"vcmi.lobby.pvp.randomTown.help" : "Skriv en slumpmässig stad i chatten",
"vcmi.lobby.pvp.randomTownVs.hover" : "Slumpmässig stad vs.",
"vcmi.lobby.pvp.randomTownVs.help" : "Skriv två slumpmässiga städer i chatten",
"vcmi.lobby.pvp.randomTown.help" : "Skriv en slumpad stad i chatten",
"vcmi.lobby.pvp.randomTownVs.hover" : "Slumpad stad vs.",
"vcmi.lobby.pvp.randomTownVs.help" : "Skriv två slumpade städer i chatten",
"vcmi.lobby.pvp.versus" : "vs.",
"vcmi.client.errors.invalidMap" : "{Ogiltig karta eller kampanj}\n\nStartade inte spelet! Vald karta eller kampanj kan vara ogiltig eller skadad. Orsak:\n%s",
"vcmi.client.errors.missingCampaigns" : "{Saknade datafiler}\n\nKampanjernas datafiler hittades inte! Du kanske använder ofullständiga eller skadade Heroes 3-datafiler. Vänligen installera om speldata.",
"vcmi.server.errors.disconnected" : "{Nätverksfel}\n\nAnslutningen till spelservern har förlorats!",
"vcmi.server.errors.playerLeft" : "{Spelare har lämnat}\n\n%s spelaren har kopplat bort sig från spelet!", //%s -> spelarens färg
"vcmi.server.errors.existingProcess" : "En annan VCMI-serverprocess är igång. Vänligen avsluta den innan du startar ett nytt spel.",
"vcmi.server.errors.modsToEnable" : "{Följande modd(ar) krävs}",
"vcmi.server.errors.modsToDisable" : "{Följande modd(ar) måste inaktiveras}",
@ -221,8 +224,8 @@
"vcmi.systemOptions.enableUiEnhancementsButton.help" : "{Gränssnittsförbättringar}\n\nVälj mellan olika förbättringar av användargränssnittet. Till exempel en lättåtkomlig ryggsäcksknapp med mera. Avaktivera för att få en mer klassisk spelupplevelse.",
"vcmi.systemOptions.enableLargeSpellbookButton.hover" : "Stor trollformelsbok",
"vcmi.systemOptions.enableLargeSpellbookButton.help" : "{Stor trollformelsbok}\n\nAktiverar en större trollformelsbok som rymmer fler trollformler per sida (animeringen av sidbyte i den större trollformelsboken fungerar inte).",
"vcmi.systemOptions.audioMuteFocus.hover" : "Stänger av ljudet vid inaktivitet",
"vcmi.systemOptions.audioMuteFocus.help" : "{Stäng av ljud vid inaktivitet}\n\nStänger av ljudet i spelet vid inaktivitet. Undantag är meddelanden i spelet och ljudet för ny tur/omgång.",
"vcmi.systemOptions.audioMuteFocus.hover" : "Tyst vid inaktivitet",
"vcmi.systemOptions.audioMuteFocus.help" : "{Tyst vid inaktivitet}\n\nStänger av ljudet i spelet vid inaktivitet. Undantag är meddelanden i spelet och ljudet för ny turomgång.",
"vcmi.adventureOptions.infoBarPick.hover" : "Visar textmeddelanden i infopanelen",
"vcmi.adventureOptions.infoBarPick.help" : "{Infopanelsmeddelanden}\n\nNär det är möjligt kommer spelmeddelanden från besökande kartobjekt att visas i infopanelen istället för att dyka upp i ett separat fönster.",
@ -234,11 +237,11 @@
"vcmi.adventureOptions.showGrid.help" : "{Visa rutnät}\n\nVisa rutnätsöverlägget som markerar gränserna mellan äventyrskartans brickor/rutor.",
"vcmi.adventureOptions.borderScroll.hover" : "Kantrullning",
"vcmi.adventureOptions.borderScroll.help" : "{Kantrullning}\n\nRullar äventyrskartan när markören är angränsande till fönsterkanten. Kan inaktiveras genom att hålla ned CTRL-tangenten.",
"vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Hantering av varelser i infopanelen i nedre högra hörnet",
"vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Hantera armén i nedre högra hörnet",
"vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Varelsehantering i infopanelen}\n\nTillåter omarrangering av varelser i infopanelen längst ner till höger på äventyrskartan istället för att bläddra mellan olika infopaneler.",
"vcmi.adventureOptions.leftButtonDrag.hover" : "Dra kartan med vänster musknapp",
"vcmi.adventureOptions.leftButtonDrag.hover" : "V.klicksdragning",
"vcmi.adventureOptions.leftButtonDrag.help" : "{Vänsterklicksdragning}\n\nVid aktivering kan äventyrskartans kartvy dras genom att flytta musen med vänster musknapp nedtryckt.",
"vcmi.adventureOptions.rightButtonDrag.hover" : "Dra kartan med höger musknapp",
"vcmi.adventureOptions.rightButtonDrag.hover" : "H.klicksdragning",
"vcmi.adventureOptions.rightButtonDrag.help" : "{Högerklicksdragning}\n\nVid aktivering kan äventyrskartans kartvy dras genom att flytta musen med höger musknapp nedtryckt.",
"vcmi.adventureOptions.smoothDragging.hover" : "Mjuk kartdragning",
"vcmi.adventureOptions.smoothDragging.help" : "{Mjuk kartdragning}\n\nVid aktivering så har kartdragningen en modern rullningseffekt.",
@ -268,16 +271,16 @@
"vcmi.battleOptions.animationsSpeed1.help" : "Ställ in animationshastigheten till mycket långsam.",
"vcmi.battleOptions.animationsSpeed5.help" : "Ställ in animationshastigheten till mycket snabb.",
"vcmi.battleOptions.animationsSpeed6.help" : "Ställ in animationshastigheten till omedelbar.",
"vcmi.battleOptions.movementHighlightOnHover.hover" : "Muspeka (hovra) för att avslöja förflyttningsräckvidd",
"vcmi.battleOptions.movementHighlightOnHover.help" : "{Muspeka för att avslöja förflyttningsräckvidd}\n\nVisar enheters potentiella förflyttningsräckvidd över slagfältet när du håller muspekaren över dem.",
"vcmi.battleOptions.movementHighlightOnHover.hover" : "Avslöja förflyttningsräckvidd",
"vcmi.battleOptions.movementHighlightOnHover.help" : "{Muspeka (hovra) för att avslöja förflyttningsräckvidd}\n\nVisar enheters potentiella förflyttningsräckvidd över slagfältet när du håller muspekaren över dem.",
"vcmi.battleOptions.rangeLimitHighlightOnHover.hover": "Avslöja skyttars räckvidd",
"vcmi.battleOptions.rangeLimitHighlightOnHover.help" : "{Muspeka för att avslöja skyttars räckvidd}\n\nVisar hur långt en enhets distansattack sträcker sig över slagfältet när du håller muspekaren över dem.",
"vcmi.battleOptions.showStickyHeroInfoWindows.hover" : "Visa fönster med hjältars primärförmågor",
"vcmi.battleOptions.showStickyHeroInfoWindows.help" : "{Visa fönster med hjältars primärförmågor}\n\nKommer alltid att visa ett fönster där du kan se dina hjältars primärförmågor (anfall, försvar, trollkonst, kunskap och trollformelpoäng).",
"vcmi.battleOptions.skipBattleIntroMusic.hover" : "Hoppa över intromusik",
"vcmi.battleOptions.skipBattleIntroMusic.help" : "{Hoppa över intromusik}\n\nTillåt åtgärder under intromusiken som spelas i början av varje strid.",
"vcmi.battleOptions.endWithAutocombat.hover" : "Slutför striden så fort som möjligt",
"vcmi.battleOptions.endWithAutocombat.help" : "{Slutför strid}\n\nAuto-strid spelar striden åt dig för att striden ska slutföras så fort som möjligt.",
"vcmi.battleOptions.endWithAutocombat.hover" : "Snabbstrid (AI)",
"vcmi.battleOptions.endWithAutocombat.help" : "{Slutför striden så fort som möjligt}\n\nAI för auto-strid spelar striden åt dig för att striden ska slutföras så fort som möjligt.",
"vcmi.battleOptions.showQuickSpell.hover" : "Snabb åtkomst till dina trollformler",
"vcmi.battleOptions.showQuickSpell.help" : "{Visa snabbtrollformels-panelen}\n\nVisar en snabbvalspanel vid sidan av stridsfönstret där du har snabb åtkomst till några av dina trollformler",
@ -365,15 +368,15 @@
"vcmi.optionsTab.turnOptions.help" : "Välj alternativ för turomgångs-timer och simultana turer",
"vcmi.optionsTab.chessFieldBase.hover" : "Bas-timern",
"vcmi.optionsTab.chessFieldTurn.hover" : "Turomgångs-timern",
"vcmi.optionsTab.chessFieldTurn.hover" : "Tur-timern",
"vcmi.optionsTab.chessFieldBattle.hover" : "Strids-timern",
"vcmi.optionsTab.chessFieldUnit.hover" : "Enhets-timern",
"vcmi.optionsTab.chessFieldBase.help" : "Används när {Turomgångs-timern} når '0'. Ställs in en gång i början av spelet. När den når '0' avslutas den aktuella turomgången (pågående strid avslutas med förlust).",
"vcmi.optionsTab.chessFieldBase.help" : "Används när {Tur-timern} når '0'. Ställs in en gång i början av spelet. När den når '0' avslutas den aktuella turomgången (pågående strid avslutas med förlust).",
"vcmi.optionsTab.chessFieldTurnAccumulate.help" : "Används utanför strid eller när {Strids-timern} tar slut. Återställs varje turomgång. Outnyttjad tid läggs till i {Bas-timern} till nästa turomgång.",
"vcmi.optionsTab.chessFieldTurnDiscard.help" : "Används utanför strid eller när {Strids-timern} tar slut. Återställs varje turomgång. Outnyttjad tid går förlorad.",
"vcmi.optionsTab.chessFieldBattle.help" : "Används i strider med AI eller i PVP-strid när {Enhets-timern} tar slut. Återställs i början av varje strid.",
"vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Används när du styr din enhet i PVP-strid. Outnyttjad tid läggs till i {Strids-timern} när enheten har avslutat sin turomgång.",
"vcmi.optionsTab.chessFieldUnitDiscard.help" : "Används när du styr din enhet i PVP-strid. Återställs i början av varje enhets turomgång. Outnyttjad tid går förlorad.",
"vcmi.optionsTab.chessFieldBattle.help" : "Används i strider med AI eller i PvP-strid när {Enhets-timern} tar slut. Återställs i början av varje strid.",
"vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Används när du styr din enhet i PvP-strid. Outnyttjad tid läggs till i {Strids-timern} när enheten har avslutat sin turomgång.",
"vcmi.optionsTab.chessFieldUnitDiscard.help" : "Används när du styr din enhet i PvP-strid. Återställs i början av varje enhets turomgång. Outnyttjad tid går förlorad.",
"vcmi.optionsTab.accumulate" : "Ackumulera",
@ -385,7 +388,7 @@
"vcmi.optionsTab.simturnsMax.help" : "Spela samtidigt som andra spelare under ett angivet antal dagar eller tills en tillräckligt nära kontakt inträffar med en annan spelare",
"vcmi.optionsTab.simturnsAI.help" : "{Simultana AI-turomgångar}\nExperimentellt alternativ. Tillåter AI-spelare att agera samtidigt som den mänskliga spelaren när simultana turomgångar är aktiverade.",
"vcmi.optionsTab.turnTime.select" : "Turtids-förinställningar",
"vcmi.optionsTab.turnTime.select" : "Timer-inställningar för turomgångar",
"vcmi.optionsTab.turnTime.unlimited" : "Obegränsat med tid",
"vcmi.optionsTab.turnTime.classic.1" : "Klassisk timer: 1 minut",
"vcmi.optionsTab.turnTime.classic.2" : "Klassisk timer: 2 minuter",
@ -393,22 +396,22 @@
"vcmi.optionsTab.turnTime.classic.10" : "Klassisk timer: 10 minuter",
"vcmi.optionsTab.turnTime.classic.20" : "Klassisk timer: 20 minuter",
"vcmi.optionsTab.turnTime.classic.30" : "Klassisk timer: 30 minuter",
"vcmi.optionsTab.turnTime.chess.20" : "Schack-timer: 20:00 + 10:00 + 02:00 + 00:00",
"vcmi.optionsTab.turnTime.chess.16" : "Schack-timer: 16:00 + 08:00 + 01:30 + 00:00",
"vcmi.optionsTab.turnTime.chess.8" : "Schack-timer: 08:00 + 04:00 + 01:00 + 00:00",
"vcmi.optionsTab.turnTime.chess.4" : "Schack-timer: 04:00 + 02:00 + 00:30 + 00:00",
"vcmi.optionsTab.turnTime.chess.2" : "Schack-timer: 02:00 + 01:00 + 00:15 + 00:00",
"vcmi.optionsTab.turnTime.chess.1" : "Schack-timer: 01:00 + 01:00 + 00:00 + 00:00",
"vcmi.optionsTab.turnTime.chess.20" : "Schack: 20:00 + 10:00 + 02:00 + 00:00",
"vcmi.optionsTab.turnTime.chess.16" : "Schack: 16:00 + 08:00 + 01:30 + 00:00",
"vcmi.optionsTab.turnTime.chess.8" : "Schack: 08:00 + 04:00 + 01:00 + 00:00",
"vcmi.optionsTab.turnTime.chess.4" : "Schack: 04:00 + 02:00 + 00:30 + 00:00",
"vcmi.optionsTab.turnTime.chess.2" : "Schack: 02:00 + 01:00 + 00:15 + 00:00",
"vcmi.optionsTab.turnTime.chess.1" : "Schack: 01:00 + 01:00 + 00:00 + 00:00",
"vcmi.optionsTab.simturns.select" : "Välj förinställning för simultana/samtidiga turer",
"vcmi.optionsTab.simturns.select" : "Simultana/samtidiga turomgångsinställningar",
"vcmi.optionsTab.simturns.none" : "Inga simultana/samtidiga turer",
"vcmi.optionsTab.simturns.tillContactMax" : "Simultantur: Fram till kontakt",
"vcmi.optionsTab.simturns.tillContact1" : "Simultantur: 1 vecka, bryt vid kontakt",
"vcmi.optionsTab.simturns.tillContact2" : "Simultantur: 2 veckor, bryt vid kontakt",
"vcmi.optionsTab.simturns.tillContact4" : "Simultantur: 1 månad, bryt vid kontakt",
"vcmi.optionsTab.simturns.blocked1" : "Simultantur: 1 vecka, kontakter blockerade",
"vcmi.optionsTab.simturns.blocked2" : "Simultantur: 2 veckor, kontakter blockerade",
"vcmi.optionsTab.simturns.blocked4" : "Simultantur: 1 månad, kontakter blockerade",
"vcmi.optionsTab.simturns.tillContactMax" : "Sam-tur: Fram till kontakt",
"vcmi.optionsTab.simturns.tillContact1" : "Sam-tur: 1 vecka, bryt vid kontakt",
"vcmi.optionsTab.simturns.tillContact2" : "Sam-tur: 2 veckor, bryt vid kontakt",
"vcmi.optionsTab.simturns.tillContact4" : "Sam-tur: 1 månad, bryt vid kontakt",
"vcmi.optionsTab.simturns.blocked1" : "Sam-tur: 1 vecka, kontakter blockerade",
"vcmi.optionsTab.simturns.blocked2" : "Sam-tur: 2 veckor, kontakter blockerade",
"vcmi.optionsTab.simturns.blocked4" : "Sam-tur: 1 månad, kontakter blockerade",
// Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language
// Using this information, VCMI will automatically select correct plural form for every possible amount
@ -525,10 +528,10 @@
"core.bonus.AIR_IMMUNITY.description" : "Immun mot alla trollformler från skolan för luftmagi",
"core.bonus.ATTACKS_ALL_ADJACENT.name" : "Attackera runtomkring",
"core.bonus.ATTACKS_ALL_ADJACENT.description" : "Attackerar alla angränsande fiender",
"core.bonus.BLOCKS_RETALIATION.name" : "Ingen motattack",
"core.bonus.BLOCKS_RETALIATION.name" : "Blockera närstrids-motattack",
"core.bonus.BLOCKS_RETALIATION.description" : "Fienden kan inte slå tillbaka/retaliera",
"core.bonus.BLOCKS_RANGED_RETALIATION.name" : "Ingen motattack på avstånd",
"core.bonus.BLOCKS_RANGED_RETALIATION.description" : "Fienden kan inte göra en motattack/retaliering på avstånd genom att använda en distansattack",
"core.bonus.BLOCKS_RANGED_RETALIATION.name" : "Blockera fjärrstrids-motattack",
"core.bonus.BLOCKS_RANGED_RETALIATION.description" : "Fienden kan inte retaliera på avstånd genom att använda en distansattack",
"core.bonus.CATAPULT.name" : "Katapult",
"core.bonus.CATAPULT.description" : "Attackerar belägringsmurar",
"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name" : "Minska trollformelkostnaden (${val})",
@ -538,21 +541,21 @@
"core.bonus.CHARGE_IMMUNITY.name" : "Galoppanfalls-immunitet",
"core.bonus.CHARGE_IMMUNITY.description" : "Immun mot ryttares och tornerares galopperande ridanfall",
"core.bonus.DARKNESS.name" : "I skydd av mörkret",
"core.bonus.DARKNESS.description" : "Skapar ett hölje av mörker med en ${val}-rutorsradie",
"core.bonus.DARKNESS.description" : "Skapar ett mörkerhölje med en rut-radie på ${val} som gäckar dina fiender",
"core.bonus.DEATH_STARE.name" : "Dödsblick (${val}%)",
"core.bonus.DEATH_STARE.description" : "Varje förbandsenhet med 'Dödsblick' har ${val}% chans att döda den översta enheten i ett fiendeförband",
"core.bonus.DEATH_STARE.description" : "Varje 'Dödsblick' har ${val}% chans att döda den översta fiendeenheten",
"core.bonus.DEFENSIVE_STANCE.name" : "Försvarshållning",
"core.bonus.DEFENSIVE_STANCE.description" : "Ger ytterligare +${val} till enhetens försvarsförmåga när du väljer att försvarar dig",
"core.bonus.DEFENSIVE_STANCE.description" : "När du väljer att försvara en enhet så får den +${val} extra försvar",
"core.bonus.DESTRUCTION.name" : "Förintelse",
"core.bonus.DESTRUCTION.description" : "Har ${val}% chans att döda extra enheter efter attack",
"core.bonus.DOUBLE_DAMAGE_CHANCE.name" : "Dödsstöt",
"core.bonus.DOUBLE_DAMAGE_CHANCE.description" : "Har ${val}% chans att ge dubbel basskada vid attack",
"core.bonus.DOUBLE_DAMAGE_CHANCE.description" : "Har ${val}% chans att utdela dubbel basskada vid attack",
"core.bonus.DRAGON_NATURE.name" : "Drake",
"core.bonus.DRAGON_NATURE.description" : "Varelsen har en draknatur",
"core.bonus.EARTH_IMMUNITY.name" : "Jord-immunitet",
"core.bonus.EARTH_IMMUNITY.description" : "Immun mot alla trollformler från skolan för jordmagi",
"core.bonus.ENCHANTER.name" : "Förtrollare",
"core.bonus.ENCHANTER.description" : "Kan kasta ${subtyp.spell} på alla varje tur/omgång",
"core.bonus.ENCHANTER.description" : "Kan kasta ${subtyp.spell} på alla varje turomgång",
"core.bonus.ENCHANTED.name" : "Förtrollad",
"core.bonus.ENCHANTED.description" : "Påverkas av permanent ${subtype.spell}",
"core.bonus.ENEMY_ATTACK_REDUCTION.name" : "Avfärda attack (${val}%)",
@ -578,15 +581,15 @@
"core.bonus.GARGOYLE.name" : "Stenfigur",
"core.bonus.GARGOYLE.description" : "Kan varken upplivas eller läkas",
"core.bonus.GENERAL_DAMAGE_REDUCTION.name" : "Minska skada (${val}%)",
"core.bonus.GENERAL_DAMAGE_REDUCTION.description" : "Reducerar fysisk skada från både distans- och närstridsattacker",
"core.bonus.GENERAL_DAMAGE_REDUCTION.description" : "Reducerar skadan från distans- och närstrids-attacker",
"core.bonus.HATE.name" : "Hatar ${subtyp.varelse}",
"core.bonus.HATE.description" : "Gör ${val}% mer skada mot ${subtyp.varelse}",
"core.bonus.HEALER.name" : "Helare",
"core.bonus.HEALER.description" : "Helar/läker allierade enheter",
"core.bonus.HP_REGENERATION.name" : "Självläkande",
"core.bonus.HP_REGENERATION.description" : "Får tillbaka ${val} träffpoäng (hälsa) varje runda",
"core.bonus.HP_REGENERATION.description" : "Får tillbaka ${val} hälsa (träffpoäng) varje runda",
"core.bonus.JOUSTING.name" : "Galopperande ridanfall",
"core.bonus.JOUSTING.description" : "Orsakar +${val}% extra skada för varje ruta som enheten förflyttas innan attack",
"core.bonus.JOUSTING.description" : "Orsakar +${val}% mer skada för varje ruta som förflyttas innan attack",
"core.bonus.KING.name" : "Kung",
"core.bonus.KING.description" : "Sårbar för 'Dräpar'-nivå ${val} eller högre",
"core.bonus.LEVEL_SPELL_IMMUNITY.name" : "Förtrollningsimmunitet 1-${val}",
@ -594,13 +597,13 @@
"core.bonus.LIMITED_SHOOTING_RANGE.name" : "Begränsad räckvidd för skjutning",
"core.bonus.LIMITED_SHOOTING_RANGE.description" : "Kan inte sikta på enheter längre bort än ${val} rutor",
"core.bonus.LIFE_DRAIN.name" : "Dränerar livskraft (${val}%)",
"core.bonus.LIFE_DRAIN.description" : "Dränerar ${val}% träffpoäng (hälsa) av utdelad skada",
"core.bonus.LIFE_DRAIN.description" : "Dränerar ${val}% hälsa (träffpoäng) av utdelad skada",
"core.bonus.MANA_CHANNELING.name" : "Kanalisera trollformelspoäng ${val}%",
"core.bonus.MANA_CHANNELING.description" : "Ger din hjälte ${val}% av den mängd trollformelspoäng som fienden spenderar per trollformel i strid",
"core.bonus.MANA_CHANNELING.description" : "Ger din hjälte ${val}% av fiendens spenderade trollformelspoäng i strid",
"core.bonus.MANA_DRAIN.name" : "Dränera trollformelspoäng",
"core.bonus.MANA_DRAIN.description" : "Dränerar ${val} trollformelspoäng varje tur",
"core.bonus.MAGIC_MIRROR.name" : "Magisk spegel (${val}%)",
"core.bonus.MAGIC_MIRROR.description" : "Har ${val}% chans att reflektera (omdirigera) en offensiv trollformel på en fiendeenhet",
"core.bonus.MAGIC_MIRROR.description" : "${val}% chans att reflektera en offensiv trollformel på en fiendeenhet",
"core.bonus.MAGIC_RESISTANCE.name" : "Magiskt motstånd (${val}%)",
"core.bonus.MAGIC_RESISTANCE.description" : "Har en ${val}% chans att motstå en skadlig trollformel",
"core.bonus.MIND_IMMUNITY.name" : "Immunitet mot sinnesförtrollningar",
@ -610,11 +613,11 @@
"core.bonus.NO_MELEE_PENALTY.name" : "Ingen närstridsbestraffning",
"core.bonus.NO_MELEE_PENALTY.description" : "Varelsen har ingen närstridsbestraffning",
"core.bonus.NO_MORALE.name" : "Ingen Moralpåverkan",
"core.bonus.NO_MORALE.description" : "Varelsen är immun mot moraliska effekter och har alltid neutral moral",
"core.bonus.NO_MORALE.description" : "Är immun mot moraliska effekter och har alltid neutral moral",
"core.bonus.NO_WALL_PENALTY.name" : "Ingen murbestraffning",
"core.bonus.NO_WALL_PENALTY.description" : "Orsakar full skada mot fiender bakom en mur",
"core.bonus.NON_LIVING.name" : "Icke levande",
"core.bonus.NON_LIVING.description" : "Immunitet mot många effekter som annars bara påverkar levande och odöda varelser",
"core.bonus.NON_LIVING.description" : "Påverkas inte av vissa effekter som levande/odöda gör",
"core.bonus.RANDOM_SPELLCASTER.name" : "Slumpmässig besvärjare",
"core.bonus.RANDOM_SPELLCASTER.description" : "Kan kasta trollformler som väljs slumpmässigt",
"core.bonus.RANGED_RETALIATION.name" : "Motattacker på avstånd",
@ -624,21 +627,21 @@
"core.bonus.REBIRTH.name" : "Återfödelse (${val}%)",
"core.bonus.REBIRTH.description" : "${val}% av enheterna kommer att återuppväckas efter döden",
"core.bonus.RETURN_AFTER_STRIKE.name" : "Återvänder efter närstrid",
"core.bonus.RETURN_AFTER_STRIKE.description" : "Efter att ha attackerat en fiendeenhet i närstrid återvänder enheten till rutan som den var placerad på innan den utförde sin närstridsattack",
"core.bonus.RETURN_AFTER_STRIKE.description" : "Efter närstridsattack återvänder den till sin ursprungliga ruta",
"core.bonus.REVENGE.name" : "Hämnd",
"core.bonus.REVENGE.description" : "Orsakar extra skada baserat på angriparens förlorade träffpoäng (hälsa) i strid",
"core.bonus.REVENGE.description" : "Orsakar mer skada om den själv blivit skadad",
"core.bonus.SHOOTER.name" : "Distans-attack",
"core.bonus.SHOOTER.description" : "Varelsen kan skjuta/attackera på avstånd",
"core.bonus.SHOOTS_ALL_ADJACENT.name" : "Skjuter alla i närheten",
"core.bonus.SHOOTS_ALL_ADJACENT.description" : "Denna varelses distans-attacker drabbar alla mål i ett litet område",
"core.bonus.SHOOTS_ALL_ADJACENT.description" : "Dess distans-attacker drabbar alla mål i ett litet område",
"core.bonus.SOUL_STEAL.name" : "Själtjuv",
"core.bonus.SOUL_STEAL.description" : "Återuppväcker ${val} av sina egna enheter för varje dödad fiendeenhet",
"core.bonus.SOUL_STEAL.description" : "Återuppväcker ${val} av sina egna för varje dödad fiendeenhet",
"core.bonus.SPELLCASTER.name" : "Besvärjare",
"core.bonus.SPELLCASTER.description" : "Kan kasta ${subtype.spell}",
"core.bonus.SPELL_AFTER_ATTACK.name" : "Besvärja efter attack",
"core.bonus.SPELL_AFTER_ATTACK.description" : "Har en ${val}% chans att kasta ${subtype.spell} efter att den har attackerat",
"core.bonus.SPELL_AFTER_ATTACK.description" : "Har ${val}% chans att kasta ${subtype.spell} efter anfall",
"core.bonus.SPELL_BEFORE_ATTACK.name" : "Besvärja före attack",
"core.bonus.SPELL_BEFORE_ATTACK.description" : "Har en ${val}% chans att kasta ${subtype.spell} innan den attackerar",
"core.bonus.SPELL_BEFORE_ATTACK.description" : "Har ${val}% chans att kasta ${subtype.spell} före anfall",
"core.bonus.SPELL_DAMAGE_REDUCTION.name" : "Trolldoms-resistens",
"core.bonus.SPELL_DAMAGE_REDUCTION.description" : "Skadan från trollformler är reducet med ${val}%.",
"core.bonus.SPELL_IMMUNITY.name" : "Trolldoms-immunitet",
@ -659,14 +662,14 @@
"core.bonus.TRANSMUTATION.description" : "${val}% chans att förvandla angripen enhet till en annan typ",
"core.bonus.UNDEAD.name" : "Odöd",
"core.bonus.UNDEAD.description" : "Varelsen är odöd",
"core.bonus.UNLIMITED_RETALIATIONS.name" : "Obegränsat antal motattacker",
"core.bonus.UNLIMITED_RETALIATIONS.description" : "Kan slå tillbaka mot ett obegränsat antal attacker varje omgång",
"core.bonus.UNLIMITED_RETALIATIONS.name" : "Slår tillbaka varje gång",
"core.bonus.UNLIMITED_RETALIATIONS.description" : "Obegränsat antal motattacker",
"core.bonus.WATER_IMMUNITY.name" : "Vatten-immunitet",
"core.bonus.WATER_IMMUNITY.description" : "Immun mot alla trollformler från vattenmagi-skolan",
"core.bonus.WATER_IMMUNITY.description" : "Immun mot alla trollformler från vattenmagiskolan",
"core.bonus.WIDE_BREATH.name" : "Bred dödlig andedräkt",
"core.bonus.WIDE_BREATH.description" : "Bred andningsattack (flera rutor)",
"core.bonus.DISINTEGRATE.name" : "Desintegrerar",
"core.bonus.DISINTEGRATE.description" : "Ingen fysisk kropp finns kvar efter att enheten blivit besegrad i strid",
"core.bonus.DISINTEGRATE.description" : "Ingen kropp lämnas kvar efter dödsögonblicket",
"core.bonus.INVINCIBLE.name" : "Oövervinnerlig",
"core.bonus.INVINCIBLE.description" : "Kan inte påverkas av någonting"
}

View File

@ -1138,7 +1138,7 @@ void CPlayerInterface::showMapObjectSelectDialog(QueryID askID, const Component
const CGTownInstance * t = dynamic_cast<const CGTownInstance *>(cb->getObj(obj));
if(t)
{
auto image = GH.renderHandler().loadImage(AnimationPath::builtin("ITPA"), t->town->clientInfo.icons[t->hasFort()][false] + 2, 0, EImageBlitMode::OPAQUE);
auto image = GH.renderHandler().loadImage(AnimationPath::builtin("ITPA"), t->getTown()->clientInfo.icons[t->hasFort()][false] + 2, 0, EImageBlitMode::OPAQUE);
image->scaleTo(Point(35, 23));
images.push_back(image);
}
@ -1333,6 +1333,8 @@ void CPlayerInterface::initializeHeroTownList()
localState->addOwnedTown(town);
}
localState->deserialize(*cb->getPlayerState(playerID)->playerLocalSettings);
if(adventureInt)
adventureInt->onHeroChanged(nullptr);
}

View File

@ -453,7 +453,7 @@ void ClientCommandManager::handleTellCommand(std::istringstream& singleWordBuffe
if(what == "hs")
{
for(const CGHeroInstance* h : LOCPLINT->cb->getHeroesInfo())
if(h->type->getIndex() == id1)
if(h->getHeroTypeID().getNum() == id1)
if(const CArtifactInstance* a = h->getArt(ArtifactPosition(id2)))
printCommandMessage(a->nodeName());
}

View File

@ -375,7 +375,7 @@ void HeroMovementController::sendMovementRequest(const CGHeroInstance * h, const
{
updateMovementSound(h, currNode.coord, nextNode.coord, nextNode.action);
assert(h->pos.z == nextNode.coord.z); // Z should change only if it's movement via teleporter and in this case this code shouldn't be executed at all
assert(h->anchorPos().z == nextNode.coord.z); // Z should change only if it's movement via teleporter and in this case this code shouldn't be executed at all
logGlobal->trace("Requesting hero movement to %s", nextNode.coord.toString());

View File

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

View File

@ -11,6 +11,7 @@
#include "PlayerLocalState.h"
#include "../CCallback.h"
#include "../lib/json/JsonNode.h"
#include "../lib/mapObjects/CGHeroInstance.h"
#include "../lib/mapObjects/CGTownInstance.h"
#include "../lib/pathfinder/CGPathNode.h"
@ -23,34 +24,20 @@ PlayerLocalState::PlayerLocalState(CPlayerInterface & owner)
{
}
void PlayerLocalState::saveHeroPaths(std::map<const CGHeroInstance *, int3> & pathsMap)
const PlayerSpellbookSetting & PlayerLocalState::getSpellbookSettings()
{
for(auto & p : paths)
{
if(p.second.nodes.size())
pathsMap[p.first] = p.second.endPos();
else
logGlobal->debug("%s has assigned an empty path! Ignoring it...", p.first->getNameTranslated());
}
return spellbookSettings;
}
void PlayerLocalState::loadHeroPaths(std::map<const CGHeroInstance *, int3> & pathsMap)
void PlayerLocalState::setSpellbookSettings(const PlayerSpellbookSetting & newSettings)
{
if(owner.cb)
{
for(auto & p : pathsMap)
{
CGPath path;
owner.cb->getPathsInfo(p.first)->getPath(path, p.second);
paths[p.first] = path;
logGlobal->trace("Restored path for hero %s leading to %s with %d nodes", p.first->nodeName(), p.second.toString(), path.nodes.size());
}
}
spellbookSettings = newSettings;
}
void PlayerLocalState::setPath(const CGHeroInstance * h, const CGPath & path)
{
paths[h] = path;
syncronizeState();
}
const CGPath & PlayerLocalState::getPath(const CGHeroInstance * h) const
@ -70,6 +57,7 @@ bool PlayerLocalState::setPath(const CGHeroInstance * h, const int3 & destinatio
if(!owner.cb->getPathsInfo(h)->getPath(path, destination))
{
paths.erase(h); //invalidate previously possible path if selected (before other hero blocked only path / fly spell expired)
syncronizeState();
return false;
}
@ -93,6 +81,7 @@ void PlayerLocalState::erasePath(const CGHeroInstance * h)
{
paths.erase(h);
adventureInt->onHeroChanged(h);
syncronizeState();
}
void PlayerLocalState::verifyPath(const CGHeroInstance * h)
@ -170,6 +159,7 @@ void PlayerLocalState::setSelection(const CArmedInstance * selection)
if (adventureInt && selection)
adventureInt->onSelectionChanged(selection);
syncronizeState();
}
bool PlayerLocalState::isHeroSleeping(const CGHeroInstance * hero) const
@ -184,6 +174,7 @@ void PlayerLocalState::setHeroAsleep(const CGHeroInstance * hero)
assert(!vstd::contains(sleepingHeroes, hero));
sleepingHeroes.push_back(hero);
syncronizeState();
}
void PlayerLocalState::setHeroAwaken(const CGHeroInstance * hero)
@ -193,6 +184,7 @@ void PlayerLocalState::setHeroAwaken(const CGHeroInstance * hero)
assert(vstd::contains(sleepingHeroes, hero));
vstd::erase(sleepingHeroes, hero);
syncronizeState();
}
const std::vector<const CGHeroInstance *> & PlayerLocalState::getWanderingHeroes()
@ -215,6 +207,8 @@ void PlayerLocalState::addWanderingHero(const CGHeroInstance * hero)
if (currentSelection == nullptr)
setSelection(hero);
syncronizeState();
}
void PlayerLocalState::removeWanderingHero(const CGHeroInstance * hero)
@ -236,6 +230,8 @@ void PlayerLocalState::removeWanderingHero(const CGHeroInstance * hero)
if (currentSelection == nullptr && !ownedTowns.empty())
setSelection(ownedTowns.front());
syncronizeState();
}
void PlayerLocalState::swapWanderingHero(size_t pos1, size_t pos2)
@ -244,6 +240,8 @@ void PlayerLocalState::swapWanderingHero(size_t pos1, size_t pos2)
std::swap(wanderingHeroes.at(pos1), wanderingHeroes.at(pos2));
adventureInt->onHeroOrderChanged();
syncronizeState();
}
const std::vector<const CGTownInstance *> & PlayerLocalState::getOwnedTowns()
@ -266,6 +264,8 @@ void PlayerLocalState::addOwnedTown(const CGTownInstance * town)
if (currentSelection == nullptr)
setSelection(town);
syncronizeState();
}
void PlayerLocalState::removeOwnedTown(const CGTownInstance * town)
@ -282,6 +282,8 @@ void PlayerLocalState::removeOwnedTown(const CGTownInstance * town)
if (currentSelection == nullptr && !ownedTowns.empty())
setSelection(ownedTowns.front());
syncronizeState();
}
void PlayerLocalState::swapOwnedTowns(size_t pos1, size_t pos2)
@ -289,5 +291,119 @@ void PlayerLocalState::swapOwnedTowns(size_t pos1, size_t pos2)
assert(ownedTowns[pos1] && ownedTowns[pos2]);
std::swap(ownedTowns.at(pos1), ownedTowns.at(pos2));
syncronizeState();
adventureInt->onTownOrderChanged();
}
void PlayerLocalState::syncronizeState()
{
JsonNode data;
serialize(data);
owner.cb->saveLocalState(data);
}
void PlayerLocalState::serialize(JsonNode & dest) const
{
dest.clear();
for (auto const * town : ownedTowns)
{
JsonNode record;
record["id"].Integer() = town->id;
dest["towns"].Vector().push_back(record);
}
for (auto const * hero : wanderingHeroes)
{
JsonNode record;
record["id"].Integer() = hero->id;
if (vstd::contains(sleepingHeroes, hero))
record["sleeping"].Bool() = true;
if (paths.count(hero))
{
record["path"]["x"].Integer() = paths.at(hero).lastNode().coord.x;
record["path"]["y"].Integer() = paths.at(hero).lastNode().coord.y;
record["path"]["z"].Integer() = paths.at(hero).lastNode().coord.z;
}
dest["heroes"].Vector().push_back(record);
}
dest["spellbook"]["pageBattle"].Integer() = spellbookSettings.spellbookLastPageBattle;
dest["spellbook"]["pageAdvmap"].Integer() = spellbookSettings.spellbookLastPageAdvmap;
dest["spellbook"]["tabBattle"].Integer() = spellbookSettings.spellbookLastTabBattle;
dest["spellbook"]["tabAdvmap"].Integer() = spellbookSettings.spellbookLastTabAdvmap;
dest["currentSelection"].Integer() = currentSelection->id;
}
void PlayerLocalState::deserialize(const JsonNode & source)
{
// this method must be called after player state has been initialized
assert(currentSelection != nullptr);
assert(!ownedTowns.empty() || wanderingHeroes.empty());
auto oldHeroes = wanderingHeroes;
auto oldTowns = ownedTowns;
paths.clear();
sleepingHeroes.clear();
wanderingHeroes.clear();
ownedTowns.clear();
for (auto const & town : source["towns"].Vector())
{
ObjectInstanceID objID(town["id"].Integer());
const CGTownInstance * townPtr = owner.cb->getTown(objID);
if (!townPtr)
continue;
if (!vstd::contains(oldTowns, townPtr))
continue;
ownedTowns.push_back(townPtr);
vstd::erase(oldTowns, townPtr);
}
for (auto const & hero : source["heroes"].Vector())
{
ObjectInstanceID objID(hero["id"].Integer());
const CGHeroInstance * heroPtr = owner.cb->getHero(objID);
if (!heroPtr)
continue;
if (!vstd::contains(oldHeroes, heroPtr))
continue;
wanderingHeroes.push_back(heroPtr);
vstd::erase(oldHeroes, heroPtr);
if (hero["sleeping"].Bool())
sleepingHeroes.push_back(heroPtr);
if (hero["path"]["x"].isNumber() && hero["path"]["y"].isNumber() && hero["path"]["z"].isNumber())
{
int3 pathTarget(hero["path"]["x"].Integer(), hero["path"]["y"].Integer(), hero["path"]["z"].Integer());
setPath(heroPtr, pathTarget);
}
}
spellbookSettings.spellbookLastPageBattle = source["spellbook"]["pageBattle"].Integer();
spellbookSettings.spellbookLastPageAdvmap = source["spellbook"]["pageAdvmap"].Integer();
spellbookSettings.spellbookLastTabBattle = source["spellbook"]["tabBattle"].Integer();
spellbookSettings.spellbookLastTabAdvmap = source["spellbook"]["tabAdvmap"].Integer();
// append any owned heroes / towns that were not present in loaded state
wanderingHeroes.insert(wanderingHeroes.end(), oldHeroes.begin(), oldHeroes.end());
ownedTowns.insert(ownedTowns.end(), oldTowns.begin(), oldTowns.end());
//FIXME: broken, anything that is selected in here will be overwritten on NewTurn pack
// ObjectInstanceID selectedObjectID(source["currentSelection"].Integer());
// const CGObjectInstance * objectPtr = owner.cb->getObjInstance(selectedObjectID);
// const CArmedInstance * armyPtr = dynamic_cast<const CArmedInstance*>(objectPtr);
//
// if (armyPtr)
// setSelection(armyPtr);
}

View File

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

View File

@ -432,7 +432,7 @@ std::shared_ptr<CIntObject> CTownList::CTownItem::genSelection()
void CTownList::CTownItem::update()
{
size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->built >= LOCPLINT->cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)];
size_t iconIndex = town->getTown()->clientInfo.icons[town->hasFort()][town->built >= LOCPLINT->cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)];
picture->setFrame(iconIndex + 2);
redraw();

View File

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

View File

@ -389,13 +389,13 @@ BattleHero::BattleHero(const BattleInterface & owner, const CGHeroInstance * her
{
AnimationPath animationPath;
if(!hero->type->battleImage.empty())
animationPath = hero->type->battleImage;
if(!hero->getHeroType()->battleImage.empty())
animationPath = hero->getHeroType()->battleImage;
else
if(hero->gender == EHeroGender::FEMALE)
animationPath = hero->type->heroClass->imageBattleFemale;
animationPath = hero->getHeroClass()->imageBattleFemale;
else
animationPath = hero->type->heroClass->imageBattleMale;
animationPath = hero->getHeroClass()->imageBattleMale;
animation = GH.renderHandler().loadAnimation(animationPath, EImageBlitMode::ALPHA);
@ -1027,7 +1027,7 @@ void StackQueue::update()
int32_t StackQueue::getSiegeShooterIconID()
{
return owner.siegeController->getSiegedTown()->town->faction->getIndex();
return owner.siegeController->getSiegedTown()->getFactionID().getNum();
}
std::optional<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());
switch(what)
{
case EWallVisual::BACKGROUND_WALL:
{
auto faction = town->town->faction->getIndex();
auto faction = town->getFactionID();
if (faction == ETownType::RAMPART || faction == ETownType::NECROPOLIS || faction == ETownType::DUNGEON || faction == ETownType::STRONGHOLD)
return ImagePath::builtinTODO(prefix + "TPW1.BMP");
@ -111,7 +111,7 @@ ImagePath BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisual
void BattleSiegeController::showWallPiece(Canvas & canvas, EWallVisual::EWallVisual what)
{
auto & ci = town->town->clientInfo;
auto & ci = town->getTown()->clientInfo;
auto const & pos = ci.siegePositions[what];
if ( wallPieceImages[what] && pos.isValid())
@ -120,7 +120,7 @@ void BattleSiegeController::showWallPiece(Canvas & canvas, EWallVisual::EWallVis
ImagePath BattleSiegeController::getBattleBackgroundName() const
{
const std::string & prefix = town->town->clientInfo.siegePrefix;
const std::string & prefix = town->getTown()->clientInfo.siegePrefix;
return ImagePath::builtinTODO(prefix + "BACK.BMP");
}
@ -130,8 +130,8 @@ bool BattleSiegeController::getWallPieceExistence(EWallVisual::EWallVisual what)
switch (what)
{
case EWallVisual::MOAT: return fortifications.hasMoat && town->town->clientInfo.siegePositions.at(EWallVisual::MOAT).isValid();
case EWallVisual::MOAT_BANK: return fortifications.hasMoat && town->town->clientInfo.siegePositions.at(EWallVisual::MOAT_BANK).isValid();
case EWallVisual::MOAT: return fortifications.hasMoat && town->getTown()->clientInfo.siegePositions.at(EWallVisual::MOAT).isValid();
case EWallVisual::MOAT_BANK: return fortifications.hasMoat && town->getTown()->clientInfo.siegePositions.at(EWallVisual::MOAT_BANK).isValid();
case EWallVisual::KEEP_BATTLEMENT: return fortifications.citadelHealth > 0 && owner.getBattle()->battleGetWallState(EWallPart::KEEP) != EWallState::DESTROYED;
case EWallVisual::UPPER_BATTLEMENT: return fortifications.upperTowerHealth > 0 && owner.getBattle()->battleGetWallState(EWallPart::UPPER_TOWER) != EWallState::DESTROYED;
case EWallVisual::BOTTOM_BATTLEMENT: return fortifications.lowerTowerHealth > 0 && owner.getBattle()->battleGetWallState(EWallPart::BOTTOM_TOWER) != EWallState::DESTROYED;
@ -218,8 +218,8 @@ Point BattleSiegeController::getTurretCreaturePosition( BattleHex position ) con
if (posID != 0)
{
return {
town->town->clientInfo.siegePositions[posID].x,
town->town->clientInfo.siegePositions[posID].y
town->getTown()->clientInfo.siegePositions[posID].x,
town->getTown()->clientInfo.siegePositions[posID].y
};
}

View File

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

View File

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

View File

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

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

View File

@ -33,6 +33,7 @@ public:
ElementInfo() : CMapInfo() { }
~ElementInfo() { }
std::string folderName = "";
std::string name = "";
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))
{
visitable |= object->visitableAt(coordinates.x, coordinates.y);
blocking |= object->blockingAt(coordinates.x, coordinates.y);
visitable |= object->visitableAt(coordinates);
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
{
const CGObjectInstance * object = getObject(objectID);
int3 offsetTiles(object->getPosition() - coordinates);
int3 offsetTiles(object->anchorPos() - coordinates);
return Point(offsetTiles) * Point(32, 32);
}
@ -498,7 +498,7 @@ size_t MapRendererWorldViewContext::overlayImageIndex(const int3 & coordinates)
{
const auto * object = getObject(objectID);
if(!object->visitableAt(coordinates.x, coordinates.y))
if(!object->visitableAt(coordinates))
continue;
ObjectPosInfo info(object);

View File

@ -49,9 +49,9 @@ void MapRendererContextState::addObject(const CGObjectInstance * obj)
{
for(int fy = 0; fy < obj->getHeight(); ++fy)
{
int3 currTile(obj->pos.x - fx, obj->pos.y - fy, obj->pos.z);
int3 currTile(obj->anchorPos().x - fx, obj->anchorPos().y - fy, obj->anchorPos().z);
if(LOCPLINT->cb->isInTheMap(currTile) && obj->coveringAt(currTile.x, currTile.y))
if(LOCPLINT->cb->isInTheMap(currTile) && obj->coveringAt(currTile))
{
auto & container = objects[currTile.z][currTile.x][currTile.y];
@ -73,7 +73,7 @@ void MapRendererContextState::addMovingObject(const CGObjectInstance * object, c
{
for(int y = yFrom; y <= yDest; ++y)
{
int3 currTile(x, y, object->pos.z);
int3 currTile(x, y, object->anchorPos().z);
if(LOCPLINT->cb->isInTheMap(currTile))
{

View File

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

View File

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

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)
{
CVideoInstance instance;
CAudioInstance audio;
auto extractedAudio = audio.extractAudio(name);
auto extractedAudio = getAudio(name);
int audioHandle = CCS->soundh->playSound(extractedAudio);
if (!instance.openInput(name))
@ -684,6 +683,15 @@ std::unique_ptr<IVideoInstance> CVideoPlayer::open(const VideoPath & name, float
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;
return audio.extractAudio(videoToOpen);
}

View File

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

View File

@ -11,11 +11,14 @@
#include "StdInc.h"
#include "CursorHardware.h"
#include "SDL_Extensions.h"
#include "../gui/CGuiHandler.h"
#include "../render/IScreenHandler.h"
#include "../render/Colors.h"
#include "../render/IImage.h"
#include "SDL_Extensions.h"
#include "../../lib/CConfigHandler.h"
#include <SDL_render.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)
{
auto cursorSurface = CSDL_Ext::newSurface(image->dimensions() * GH.screenHandler().getScalingFactor());
int videoScalingSettings = GH.screenHandler().getInterfaceScalingPercentage();
float cursorScalingSettings = settings["video"]["cursorScalingFactor"].Float();
int cursorScalingPercent = videoScalingSettings * cursorScalingSettings;
Point cursorDimensions = image->dimensions() * GH.screenHandler().getScalingFactor();
Point cursorDimensionsScaled = image->dimensions() * cursorScalingPercent / 100;
Point pivotOffsetScaled = pivotOffset * cursorScalingPercent / 100 / GH.screenHandler().getScalingFactor();
auto cursorSurface = CSDL_Ext::newSurface(cursorDimensions);
CSDL_Ext::fillSurface(cursorSurface, CSDL_Ext::toSDL(Colors::TRANSPARENCY));
image->draw(cursorSurface, Point(0,0));
auto cursorSurfaceScaled = CSDL_Ext::scaleSurface(cursorSurface, cursorDimensionsScaled.x, cursorDimensionsScaled.y );
auto oldCursor = cursor;
cursor = SDL_CreateColorCursor(cursorSurface, pivotOffset.x, pivotOffset.y);
cursor = SDL_CreateColorCursor(cursorSurfaceScaled, pivotOffsetScaled.x, pivotOffsetScaled.y);
if (!cursor)
logGlobal->error("Failed to set cursor! SDL says %s", SDL_GetError());
SDL_FreeSurface(cursorSurface);
SDL_FreeSurface(cursorSurfaceScaled);
GH.dispatchMainThread([this, oldCursor](){
SDL_SetCursor(cursor);

View File

@ -84,19 +84,39 @@ Rect ScreenHandler::convertLogicalPointsToWindow(const Rect & input) const
return result;
}
int ScreenHandler::getInterfaceScalingPercentage() const
{
auto [minimalScaling, maximalScaling] = getSupportedScalingRange();
int userScaling = settings["video"]["resolution"]["scaling"].Integer();
if (userScaling == 0) // autodetection
{
#ifdef VCMI_MOBILE
// for mobiles - stay at maximum scaling unless we have large screen
// might be better to check screen DPI / physical dimensions, but way more complex, and may result in different edge cases, e.g. chromebooks / tv's
int preferredMinimalScaling = 200;
#else
// for PC - avoid downscaling if possible
int preferredMinimalScaling = 100;
#endif
// prefer a little below maximum - to give space for extended UI
int preferredMaximalScaling = maximalScaling * 10 / 12;
userScaling = std::max(std::min(maximalScaling, preferredMinimalScaling), preferredMaximalScaling);
}
int scaling = std::clamp(userScaling, minimalScaling, maximalScaling);
return scaling;
}
Point ScreenHandler::getPreferredLogicalResolution() const
{
Point renderResolution = getRenderResolution();
double reservedAreaWidth = settings["video"]["reservedWidth"].Float();
int scaling = getInterfaceScalingPercentage();
Point availableResolution = Point(renderResolution.x * (1 - reservedAreaWidth), renderResolution.y);
auto [minimalScaling, maximalScaling] = getSupportedScalingRange();
int userScaling = settings["video"]["resolution"]["scaling"].Integer();
int scaling = std::clamp(userScaling, minimalScaling, maximalScaling);
Point logicalResolution = availableResolution * 100.0 / scaling;
return logicalResolution;
}
@ -335,25 +355,22 @@ EUpscalingFilter ScreenHandler::loadUpscalingFilter() const
if (filter != EUpscalingFilter::AUTO)
return filter;
// for now - always fallback to no filter
return EUpscalingFilter::NONE;
// else - autoselect
// Point outputResolution = getRenderResolution();
// Point logicalResolution = getPreferredLogicalResolution();
//
// float scaleX = static_cast<float>(outputResolution.x) / logicalResolution.x;
// float scaleY = static_cast<float>(outputResolution.x) / logicalResolution.x;
// float scaling = std::min(scaleX, scaleY);
//
// if (scaling <= 1.0f)
// return EUpscalingFilter::NONE;
// if (scaling <= 2.0f)
// return EUpscalingFilter::XBRZ_2;
// if (scaling <= 3.0f)
// return EUpscalingFilter::XBRZ_3;
//
// return EUpscalingFilter::XBRZ_4;
Point outputResolution = getRenderResolution();
Point logicalResolution = getPreferredLogicalResolution();
float scaleX = static_cast<float>(outputResolution.x) / logicalResolution.x;
float scaleY = static_cast<float>(outputResolution.x) / logicalResolution.x;
float scaling = std::min(scaleX, scaleY);
if (scaling <= 1.001f)
return EUpscalingFilter::NONE; // running at original resolution or even lower than that - no need for xbrz
if (scaling <= 2.001f)
return EUpscalingFilter::XBRZ_2; // resolutions below 1200p (including 1080p / FullHD)
if (scaling <= 3.001f)
return EUpscalingFilter::XBRZ_3; // resolutions below 2400p (including 1440p and 2160p / 4K)
return EUpscalingFilter::XBRZ_4; // Only for massive displays, e.g. 8K
}
void ScreenHandler::selectUpscalingFilter()

View File

@ -112,6 +112,8 @@ public:
int getScalingFactor() const final;
int getInterfaceScalingPercentage() const final;
std::vector<Point> getSupportedResolutions() const final;
std::vector<Point> getSupportedResolutions(int displayIndex) const;
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);
}
}, [town]{
if(!town->town->faction->getDescriptionTranslated().empty())
CRClickPopup::createAndPush(town->town->faction->getDescriptionTranslated());
if(!town->getFaction()->getDescriptionTranslated().empty())
CRClickPopup::createAndPush(town->getFaction()->getDescriptionTranslated());
});
fastMarket = std::make_shared<LRClickableArea>(Rect(143, 31, 30, 34), []()
{
@ -532,8 +532,7 @@ CreatureTooltip::CreatureTooltip(Point pos, const CGCreature * creature)
{
OBJECT_CONSTRUCTION;
auto creatureID = creature->getCreature();
int32_t creatureIconIndex = CGI->creatures()->getById(creatureID)->getIconIndex();
int32_t creatureIconIndex = creature->getCreature()->getIconIndex();
creatureImage = std::make_shared<CAnimImage>(AnimationPath::builtin("TWCRPORT"), creatureIconIndex);
creatureImage->center(Point(parent->pos.x + parent->pos.w / 2, parent->pos.y + creatureImage->pos.h / 2 + 11));
@ -633,7 +632,7 @@ CCreaturePic::CCreaturePic(int x, int y, const CCreature * cre, bool Big, bool A
pos.x+=x;
pos.y+=y;
auto faction = cre->getFaction();
auto faction = cre->getFactionID();
assert(CGI->townh->size() > faction);

View File

@ -82,7 +82,7 @@ CBuildingRect::CBuildingRect(CCastleBuildings * Par, const CGTownInstance * Town
// special animation frame manipulation for castle shipyard with and without ship
// done due to .def used in special way, not to animate building - first image is for shipyard without citadel moat, 2nd image is for including moat
if(Town->town->faction->getId() == FactionID::CASTLE && Str->building &&
if(Town->getFactionID() == FactionID::CASTLE && Str->building &&
(Str->building->bid == BuildingID::SHIPYARD || Str->building->bid == BuildingID::SHIP))
{
if(Town->hasBuilt(BuildingID::CITADEL))
@ -107,7 +107,7 @@ const CBuilding * CBuildingRect::getBuilding()
return nullptr;
if (str->hiddenUpgrade) // hidden upgrades, e.g. hordes - return base (dwelling for hordes)
return town->town->buildings.at(str->building->getBase());
return town->getTown()->buildings.at(str->building->getBase());
return str->building;
}
@ -156,7 +156,7 @@ void CBuildingRect::showPopupWindow(const Point & cursorPosition)
return;
BuildingID bid = getBuilding()->bid;
const CBuilding *bld = town->town->buildings.at(bid);
const CBuilding *bld = town->getTown()->buildings.at(bid);
if (bid < BuildingID::DWELL_FIRST)
{
CRClickPopup::createAndPush(CInfoWindow::genText(bld->getNameTranslated(), bld->getDescriptionTranslated()),
@ -235,10 +235,10 @@ std::string CBuildingRect::getSubtitle()//hover text for building
int bid = getBuilding()->bid;
if (bid<30)//non-dwellings - only building name
return town->town->buildings.at(getBuilding()->bid)->getNameTranslated();
return town->getTown()->buildings.at(getBuilding()->bid)->getNameTranslated();
else//dwellings - recruit %creature%
{
auto & availableCreatures = town->creatures[(bid-30)%town->town->creatures.size()].second;
auto & availableCreatures = town->creatures[(bid-30)%town->getTown()->creatures.size()].second;
if(availableCreatures.size())
{
int creaID = availableCreatures.back();//taking last of available creatures
@ -566,7 +566,7 @@ CCastleBuildings::CCastleBuildings(const CGTownInstance* Town):
{
OBJECT_CONSTRUCTION;
background = std::make_shared<CPicture>(town->town->clientInfo.townBackground);
background = std::make_shared<CPicture>(town->getTown()->clientInfo.townBackground);
background->needRefresh = true;
background->getSurface()->setBlitMode(EImageBlitMode::OPAQUE);
pos.w = background->pos.w;
@ -602,7 +602,7 @@ void CCastleBuildings::recreate()
}
}
for(const CStructure * structure : town->town->clientInfo.structures)
for(const CStructure * structure : town->getTown()->clientInfo.structures)
{
if(!structure->building)
{
@ -617,7 +617,7 @@ void CCastleBuildings::recreate()
for(auto & entry : groups)
{
const CBuilding * build = town->town->buildings.at(entry.first);
const CBuilding * build = town->getTown()->buildings.at(entry.first);
const CStructure * toAdd = *boost::max_element(entry.second, [=](const CStructure * a, const CStructure * b)
{
@ -648,7 +648,7 @@ void CCastleBuildings::recreate()
void CCastleBuildings::addBuilding(BuildingID building)
{
//FIXME: implement faster method without complete recreation of town
BuildingID base = town->town->buildings.at(building)->getBase();
BuildingID base = town->getTown()->buildings.at(building)->getBase();
recreate();
@ -687,7 +687,7 @@ void CCastleBuildings::buildingClicked(BuildingID building)
BuildingID buildingToEnter = building;
for(;;)
{
const CBuilding *b = town->town->buildings.find(buildingToEnter)->second;
const CBuilding *b = town->getTown()->buildings.find(buildingToEnter)->second;
if (buildingTryActivateCustomUI(buildingToEnter, building))
return;
@ -705,7 +705,7 @@ void CCastleBuildings::buildingClicked(BuildingID building)
bool CCastleBuildings::buildingTryActivateCustomUI(BuildingID buildingToTest, BuildingID buildingTarget)
{
logGlobal->trace("You've clicked on %d", (int)buildingToTest.toEnum());
const CBuilding *b = town->town->buildings.at(buildingToTest);
const CBuilding *b = town->getTown()->buildings.at(buildingToTest);
if (town->getWarMachineInBuilding(buildingToTest).hasValue())
{
@ -744,7 +744,7 @@ bool CCastleBuildings::buildingTryActivateCustomUI(BuildingID buildingToTest, Bu
}
}
if (town->rewardableBuildings.count(buildingToTest) && town->town->buildings.at(buildingToTest)->manualHeroVisit)
if (town->rewardableBuildings.count(buildingToTest) && town->getTown()->buildings.at(buildingToTest)->manualHeroVisit)
{
enterRewardable(buildingToTest);
return true;
@ -820,10 +820,10 @@ bool CCastleBuildings::buildingTryActivateCustomUI(BuildingID buildingToTest, Bu
return false;
case BuildingSubID::PORTAL_OF_SUMMONING:
if (town->creatures[town->town->creatures.size()].second.empty())//No creatures
if (town->creatures[town->getTown()->creatures.size()].second.empty())//No creatures
LOCPLINT->showInfoDialog(CGI->generaltexth->tcommands[30]);
else
enterDwelling(town->town->creatures.size());
enterDwelling(town->getTown()->creatures.size());
return true;
case BuildingSubID::BANK:
@ -850,7 +850,7 @@ void CCastleBuildings::enterRewardable(BuildingID building)
{
MetaString message;
message.appendTextID("core.genrltxt.273"); // only visiting heroes may visit %s
message.replaceTextID(town->town->buildings.at(building)->getNameTextID());
message.replaceTextID(town->getTown()->buildings.at(building)->getNameTextID());
LOCPLINT->showInfoDialog(message.toString());
}
@ -868,7 +868,7 @@ void CCastleBuildings::enterBlacksmith(BuildingID building, ArtifactID artifactI
const CGHeroInstance *hero = town->visitingHero;
if(!hero)
{
LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[273]) % town->town->buildings.find(building)->second->getNameTranslated()));
LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[273]) % town->getTown()->buildings.find(building)->second->getNameTranslated()));
return;
}
auto art = artifactID.toArtifact();
@ -897,8 +897,8 @@ void CCastleBuildings::enterBlacksmith(BuildingID building, ArtifactID artifactI
void CCastleBuildings::enterBuilding(BuildingID building)
{
std::vector<std::shared_ptr<CComponent>> comps(1, std::make_shared<CComponent>(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFaction(), building)));
LOCPLINT->showInfoDialog( town->town->buildings.find(building)->second->getDescriptionTranslated(), comps);
std::vector<std::shared_ptr<CComponent>> comps(1, std::make_shared<CComponent>(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFactionID(), building)));
LOCPLINT->showInfoDialog( town->getTown()->buildings.find(building)->second->getDescriptionTranslated(), comps);
}
void CCastleBuildings::enterCastleGate()
@ -915,20 +915,20 @@ void CCastleBuildings::enterCastleGate()
{
const CGTownInstance *t = Town;
if (t->id != this->town->id && t->visitingHero == nullptr && //another town, empty and this is
t->town->faction->getId() == town->town->faction->getId() && //the town of the same faction
t->getFactionID() == town->getFactionID() && //the town of the same faction
t->hasBuilt(BuildingSubID::CASTLE_GATE)) //and the town has a castle gate
{
availableTowns.push_back(t->id.getNum());//add to the list
if(settings["general"]["enableUiEnhancements"].Bool())
{
auto image = GH.renderHandler().loadImage(AnimationPath::builtin("ITPA"), t->town->clientInfo.icons[t->hasFort()][false] + 2, 0, EImageBlitMode::OPAQUE);
auto image = GH.renderHandler().loadImage(AnimationPath::builtin("ITPA"), t->getTown()->clientInfo.icons[t->hasFort()][false] + 2, 0, EImageBlitMode::OPAQUE);
image->scaleTo(Point(35, 23));
images.push_back(image);
}
}
}
auto gateIcon = std::make_shared<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],
CGI->generaltexth->jktexts[41], std::bind (&CCastleInterface::castleTeleport, LOCPLINT->castleInt, _1), 0, images);
wnd->onPopup = [availableTowns](int index) { CRClickPopup::createAndPush(LOCPLINT->cb->getObjInstance(ObjectInstanceID(availableTowns[index])), GH.getCursorPosition()); };
@ -940,7 +940,7 @@ void CCastleBuildings::enterDwelling(int level)
if (level < 0 || level >= town->creatures.size() || town->creatures[level].second.empty())
{
assert(0);
logGlobal->error("Attempt to enter into invalid dwelling of level %d in town %s (%s)", level, town->getNameTranslated(), town->town->faction->getNameTranslated());
logGlobal->error("Attempt to enter into invalid dwelling of level %d in town %s (%s)", level, town->getNameTranslated(), town->getFaction()->getNameTranslated());
return;
}
@ -954,8 +954,8 @@ void CCastleBuildings::enterDwelling(int level)
void CCastleBuildings::enterToTheQuickRecruitmentWindow()
{
const auto beginIt = town->creatures.cbegin();
const auto afterLastIt = town->creatures.size() > town->town->creatures.size()
? std::next(beginIt, town->town->creatures.size())
const auto afterLastIt = town->creatures.size() > town->getTown()->creatures.size()
? std::next(beginIt, town->getTown()->creatures.size())
: town->creatures.cend();
const auto hasSomeoneToRecruit = std::any_of(beginIt, afterLastIt,
[](const auto & creatureInfo) { return creatureInfo.first > 0; });
@ -967,8 +967,8 @@ void CCastleBuildings::enterToTheQuickRecruitmentWindow()
void CCastleBuildings::enterFountain(const BuildingID & building, BuildingSubID::EBuildingSubID subID, BuildingID upgrades)
{
std::vector<std::shared_ptr<CComponent>> comps(1, std::make_shared<CComponent>(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFaction(), building)));
std::string descr = town->town->buildings.find(building)->second->getDescriptionTranslated();
std::vector<std::shared_ptr<CComponent>> comps(1, std::make_shared<CComponent>(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFactionID(), building)));
std::string descr = town->getTown()->buildings.find(building)->second->getDescriptionTranslated();
std::string hasNotProduced;
std::string hasProduced;
@ -977,10 +977,10 @@ void CCastleBuildings::enterFountain(const BuildingID & building, BuildingSubID:
bool isMysticPondOrItsUpgrade = subID == BuildingSubID::MYSTIC_POND
|| (upgrades != BuildingID::NONE
&& town->town->buildings.find(BuildingID(upgrades))->second->subId == BuildingSubID::MYSTIC_POND);
&& town->getTown()->buildings.find(BuildingID(upgrades))->second->subId == BuildingSubID::MYSTIC_POND);
if(upgrades != BuildingID::NONE)
descr += "\n\n"+town->town->buildings.find(BuildingID(upgrades))->second->getDescriptionTranslated();
descr += "\n\n"+town->getTown()->buildings.find(BuildingID(upgrades))->second->getDescriptionTranslated();
if(isMysticPondOrItsUpgrade) //for vanila Rampart like towns
{
@ -1056,7 +1056,7 @@ void CCastleBuildings::enterTownHall()
void CCastleBuildings::openMagesGuild()
{
auto mageGuildBackground = LOCPLINT->castleInt->town->town->clientInfo.guildBackground;
auto mageGuildBackground = LOCPLINT->castleInt->town->getTown()->clientInfo.guildBackground;
GH.windows().createAndPushWindow<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
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;
}
@ -1322,7 +1322,7 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town, const CGTownInst
recreateIcons();
if (!from)
adventureInt->onAudioPaused();
CCS->musich->playMusicFromSet("faction", town->town->faction->getJsonKey(), true, false);
CCS->musich->playMusicFromSet("faction", town->getFaction()->getJsonKey(), true, false);
}
CCastleInterface::~CCastleInterface()
@ -1403,7 +1403,7 @@ void CCastleInterface::removeBuilding(BuildingID bid)
void CCastleInterface::recreateIcons()
{
OBJECT_CONSTRUCTION;
size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->built >= LOCPLINT->cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)];
size_t iconIndex = town->getTown()->clientInfo.icons[town->hasFort()][town->built >= LOCPLINT->cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)];
icon->setFrame(iconIndex);
TResources townIncome = town->dailyIncome();
@ -1425,8 +1425,8 @@ void CCastleInterface::recreateIcons()
if(town->hasBuilt(BuildingID::TAVERN))
LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE);
}, [this]{
if(!town->town->faction->getDescriptionTranslated().empty())
CRClickPopup::createAndPush(town->town->faction->getDescriptionTranslated());
if(!town->getFaction()->getDescriptionTranslated().empty())
CRClickPopup::createAndPush(town->getFaction()->getDescriptionTranslated());
});
creainfo.clear();
@ -1527,7 +1527,7 @@ CHallInterface::CBuildingBox::CBuildingBox(int x, int y, const CGTownInstance *
-1, -1, -1, 0, 0, 1, 2, -1, 1, 1, -1, -1
};
icon = std::make_shared<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);
if(iconIndex[static_cast<int>(state)] >=0)
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):
CWindowObject(PLAYER_COLORED | BORDERED, Town->town->clientInfo.hallBackground),
CWindowObject(PLAYER_COLORED | BORDERED, Town->getTown()->clientInfo.hallBackground),
town(Town)
{
OBJECT_CONSTRUCTION;
@ -1581,10 +1581,10 @@ CHallInterface::CHallInterface(const CGTownInstance * Town):
auto statusbarBackground = std::make_shared<CPicture>(background->getSurface(), barRect, 5, 556);
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);
auto & boxList = town->town->clientInfo.hallSlots;
auto & boxList = town->getTown()->clientInfo.hallSlots;
boxes.resize(boxList.size());
for(size_t row=0; row<boxList.size(); row++) //for each row
{
@ -1595,11 +1595,11 @@ CHallInterface::CHallInterface(const CGTownInstance * Town):
{
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;
}
const CBuilding * current = town->town->buildings.at(buildingID);
const CBuilding * current = town->getTown()->buildings.at(buildingID);
if(town->hasBuilt(buildingID))
{
building = current;
@ -1629,7 +1629,7 @@ CBuildWindow::CBuildWindow(const CGTownInstance *Town, const CBuilding * Buildin
{
OBJECT_CONSTRUCTION;
icon = std::make_shared<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);
statusbar = CGStatusBar::create(statusbarBackground);
@ -1711,7 +1711,7 @@ std::string CBuildWindow::getTextForState(EBuildingState state)
{
auto toStr = [&](const BuildingID build) -> std::string
{
return town->town->buildings.at(build)->getNameTranslated();
return town->getTown()->buildings.at(build)->getNameTranslated();
};
ret = CGI->generaltexth->allTexts[52];
@ -1721,7 +1721,7 @@ std::string CBuildWindow::getTextForState(EBuildingState state)
case EBuildingState::MISSING_BASE:
{
std::string msg = CGI->generaltexth->translate("vcmi.townHall.missingBase");
ret = boost::str(boost::format(msg) % town->town->buildings.at(building->upgrade)->getNameTranslated());
ret = boost::str(boost::format(msg) % town->getTown()->buildings.at(building->upgrade)->getNameTranslated());
break;
}
}
@ -1780,11 +1780,11 @@ CFortScreen::CFortScreen(const CGTownInstance * town):
{
OBJECT_CONSTRUCTION;
ui32 fortSize = static_cast<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 = 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());
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++)
{
BuildingID buildingID;
if(fortSize == town->town->creatures.size())
if(fortSize == town->getTown()->creatures.size())
{
BuildingID dwelling = BuildingID::getDwellingFromLevel(i, 1);
@ -1839,7 +1839,7 @@ CFortScreen::CFortScreen(const CGTownInstance * town):
ImagePath CFortScreen::getBgName(const CGTownInstance * town)
{
ui32 fortSize = static_cast<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 = 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)
{
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);
if(town->hasBuilt(getMyBuilding()->bid))
@ -1913,8 +1913,8 @@ const CCreature * CFortScreen::RecruitArea::getMyCreature()
{
if(!town->creatures.at(level).second.empty()) // built
return town->creatures.at(level).second.back().toCreature();
if(!town->town->creatures.at(level).empty()) // there are creatures on this level
return town->town->creatures.at(level).front().toCreature();
if(!town->getTown()->creatures.at(level).empty()) // there are creatures on this level
return town->getTown()->creatures.at(level).front().toCreature();
return nullptr;
}
@ -1922,17 +1922,17 @@ const CBuilding * CFortScreen::RecruitArea::getMyBuilding()
{
BuildingID myID = BuildingID(BuildingID::getDwellingFromLevel(level, 0));
if (level == town->town->creatures.size())
return town->town->getSpecialBuilding(BuildingSubID::PORTAL_OF_SUMMONING);
if (level == town->getTown()->creatures.size())
return town->getTown()->getSpecialBuilding(BuildingSubID::PORTAL_OF_SUMMONING);
if (!town->town->buildings.count(myID))
if (!town->getTown()->buildings.count(myID))
return nullptr;
const CBuilding * build = town->town->buildings.at(myID);
while (town->town->buildings.count(myID))
const CBuilding * build = town->getTown()->buildings.at(myID);
while (town->getTown()->buildings.count(myID))
{
if (town->hasBuilt(myID))
build = town->town->buildings.at(myID);
build = town->getTown()->buildings.at(myID);
BuildingID::advanceDwelling(myID);
}
@ -1972,7 +1972,7 @@ CMageGuildScreen::CMageGuildScreen(CCastleInterface * owner, const ImagePath & i
{
OBJECT_CONSTRUCTION;
window = std::make_shared<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->moveBy(pos.topLeft(), true);
@ -2007,7 +2007,7 @@ void CMageGuildScreen::updateSpells(ObjectInstanceID tID)
const CGTownInstance * town = LOCPLINT->cb->getTown(townId);
for(size_t i=0; 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?
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)
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);
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]->pos = Rect(Point(pos.x + 69 + 490 * b, pos.y + (qeLayout ? 41 : 45)), Point(32, 32));
specialtyAreas[b]->hoverText = CGI->generaltexth->heroscrn[27];
specialtyAreas[b]->text = hero->type->getSpecialtyDescriptionTranslated();
specialtyAreas[b]->text = hero->getHeroType()->getSpecialtyDescriptionTranslated();
experienceAreas[b] = std::make_shared<LRClickableAreaWText>();
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());
title->setText((boost::format(CGI->generaltexth->allTexts[342]) % curHero->level % curHero->getClassNameTranslated()).str());
specArea->text = curHero->type->getSpecialtyDescriptionTranslated();
specImage->setFrame(curHero->type->imageIndex);
specName->setText(curHero->type->getSpecialtyNameTranslated());
specArea->text = curHero->getHeroType()->getSpecialtyDescriptionTranslated();
specImage->setFrame(curHero->getHeroType()->imageIndex);
specName->setText(curHero->getHeroType()->getSpecialtyNameTranslated());
tacticsButton = std::make_shared<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]);

View File

@ -300,7 +300,7 @@ int InfoBoxHeroData::getSubID()
else
return 0;
case HERO_SPECIAL:
return hero->type->getIndex();
return hero->getHeroTypeID().getNum();
case HERO_MANA:
case HERO_EXPERIENCE:
return 0;
@ -800,7 +800,7 @@ CTownItem::CTownItem(const CGTownInstance * Town)
garr = std::make_shared<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);
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);
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))
LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE);
}, [&]{
if(!town->town->faction->getDescriptionTranslated().empty())
CRClickPopup::createAndPush(town->town->faction->getDescriptionTranslated());
if(!town->getTown()->faction->getDescriptionTranslated().empty())
CRClickPopup::createAndPush(town->getFaction()->getDescriptionTranslated());
});
fastMarket = std::make_shared<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())
{
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();
}
}

View File

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

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

View File

@ -522,9 +522,9 @@ CTavernWindow::CTavernWindow(const CGObjectInstance * TavernObj, const std::func
recruit->block(true);
}
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))
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
videoPlayer = std::make_shared<VideoWidget>(Point(70, 56), VideoPath::builtin("TAVERN.BIK"), false);
@ -548,7 +548,7 @@ void CTavernWindow::addInvite()
if(!inviteableHeroes.empty())
{
int imageIndex = heroToInvite ? (*CGI->heroh)[heroToInvite->getHeroType()]->imageIndex : 156; // 156 => special id for random
int imageIndex = heroToInvite ? heroToInvite->getIconIndex() : 156; // 156 => special id for random
if(!heroToInvite)
heroToInvite = (*RandomGeneratorUtil::nextItem(inviteableHeroes, CRandomGenerator::getDefault())).second;
@ -563,7 +563,7 @@ void CTavernWindow::recruitb()
const CGHeroInstance *toBuy = (selected ? h2 : h1)->h;
const CGObjectInstance *obj = tavernObj;
LOCPLINT->cb->recruitHero(obj, toBuy, heroToInvite ? heroToInvite->getHeroType() : HeroTypeID::NONE);
LOCPLINT->cb->recruitHero(obj, toBuy, heroToInvite ? heroToInvite->getHeroTypeID() : HeroTypeID::NONE);
close();
}
@ -963,7 +963,7 @@ CUniversityWindow::CUniversityWindow(const CGHeroInstance * _hero, BuildingID bu
if(auto town = dynamic_cast<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);
}
else if(auto uni = dynamic_cast<const CGUniversity *>(_market); uni->appearance)

View File

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

View File

@ -194,10 +194,8 @@ GeneralOptionsTab::GeneralOptionsTab()
build(config);
const auto & currentResolution = settings["video"]["resolution"];
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");
if (longTouchLabel)

View File

@ -184,6 +184,7 @@
"targetfps",
"vsync",
"fontsType",
"cursorScalingFactor",
"fontScalingFactor",
"upscalingFilter",
"fontUpscalingFilter",
@ -195,22 +196,19 @@
"additionalProperties" : false,
"required" : [ "width", "height", "scaling" ],
"properties" : {
"width" : { "type" : "number" },
"height" : { "type" : "number" },
"scaling" : { "type" : "number" }
},
"defaultIOS" : {"width" : 800, "height" : 600, "scaling" : 200 },
"defaultAndroid" : {"width" : 800, "height" : 600, "scaling" : 200 },
"default" : {"width" : 800, "height" : 600, "scaling" : 100 }
"width" : { "type" : "number", "default" : 1280 },
"height" : { "type" : "number", "default" : 720 },
"scaling" : { "type" : "number", "default" : 0 }
}
},
"reservedWidth" : {
"type" : "number",
"defaultIOS" : 0.1, // iOS camera cutout / notch is excluded from available area by SDL
"defaultIOS" : 0.1, // iOS camera cutout / notch is not excluded from available area by SDL, handle it this way
"default" : 0
},
"fullscreen" : {
"type" : "boolean",
"default" : false
"default" : true
},
"realFullscreen" : {
"type" : "boolean",
@ -256,6 +254,10 @@
"enum" : [ "auto", "original", "scalable" ],
"default" : "auto"
},
"cursorScalingFactor" : {
"type" : "number",
"default" : 1
},
"fontScalingFactor" : {
"type" : "number",
"default" : 1

View File

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

View File

@ -202,6 +202,10 @@ elseif(NOT APPLE_IOS)
target_link_libraries(vcmilauncher SDL2::SDL2)
endif()
if(ENABLE_STATIC_LIBS OR NOT (ENABLE_EDITOR AND ENABLE_LAUNCHER))
target_compile_definitions(vcmilauncher PRIVATE VCMIQT_STATIC)
endif()
target_link_libraries(vcmilauncher vcmi vcmiqt Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network)
target_include_directories(vcmilauncher
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}

View File

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

View File

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

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
//and in the CGHeroInstance::getNativeTerrain() to setup movement bonuses or/and penalties.
return getBonusBearer()->hasBonus(selectorNoTerrainPenalty, cachingStringNoTerrainPenalty)
? TerrainId::ANY_TERRAIN : VLC->factions()->getById(getFaction())->getNativeTerrain();
? TerrainId::ANY_TERRAIN : getFactionID().toEntity(VLC)->getNativeTerrain();
}
int32_t AFactionMember::magicResistance() const

View File

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

View File

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

View File

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

View File

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

View File

@ -381,7 +381,7 @@ bool CGameInfoCallback::getHeroInfo(const CGObjectInstance * hero, InfoAboutHero
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();
mostStrong = creature.get();
@ -539,7 +539,7 @@ EDiggingStatus CGameInfoCallback::getTileDigStatus(int3 tile, bool verbose) cons
for(const auto & object : gs->map->objects)
{
if(object && object->ID == Obj::HOLE && object->pos == tile)
if(object && object->ID == Obj::HOLE && object->anchorPos() == tile)
return EDiggingStatus::TILE_OCCUPIED;
}
return getTile(tile)->getDiggingStatus();
@ -575,10 +575,10 @@ EBuildingState CGameInfoCallback::canBuildStructure( const CGTownInstance *t, Bu
{
ERROR_RET_VAL_IF(!canGetFullInfo(t), "Town is not owned!", EBuildingState::TOWN_NOT_OWNED);
if(!t->town->buildings.count(ID))
if(!t->getTown()->buildings.count(ID))
return EBuildingState::BUILDING_ERROR;
const CBuilding * building = t->town->buildings.at(ID);
const CBuilding * building = t->getTown()->buildings.at(ID);
if(t->hasBuilt(ID)) //already built

View File

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

View File

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

View File

@ -16,7 +16,6 @@
#include "bonuses/CBonusSystemNode.h"
#include "ResourceSet.h"
#include "TurnTimerInfo.h"
#include "ConstTransitivePtr.h"
VCMI_LIB_NAMESPACE_BEGIN
@ -66,6 +65,7 @@ public:
std::vector<QuestInfo> quests; //store info about all received quests
std::vector<Bonus> battleBonuses; //additional bonuses to be added during battle with neutrals
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 enteredWinningCheatCode, enteredLosingCheatCode; //if true, this player has entered cheat codes for loss / victory
@ -116,6 +116,9 @@ public:
h & status;
h & turnTimer;
if (h.version >= Handler::Version::LOCAL_PLAYER_STATE_DATA)
h & *playerLocalSettings;
if (h.version >= Handler::Version::PLAYER_STATE_OWNED_OBJECTS)
{
h & ownedObjects;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -39,7 +39,7 @@ class DLL_LINKAGE CFaction : public Faction
FactionID index = FactionID::NEUTRAL;
FactionID getFaction() const override; //This function should not be used
FactionID getFactionID() const override; //This function should not be used
public:
TerrainId nativeTerrain;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -25,7 +25,7 @@ std::map<HeroTypeID, CGHeroInstance*> TavernHeroesPool::unusedHeroesFromPool() c
{
std::map<HeroTypeID, CGHeroInstance*> pool = heroesPool;
for(const auto & slot : currentTavern)
pool.erase(slot.hero->getHeroType());
pool.erase(slot.hero->getHeroTypeID());
return pool;
}
@ -34,7 +34,7 @@ TavernSlotRole TavernHeroesPool::getSlotRole(HeroTypeID hero) const
{
for (auto const & slot : currentTavern)
{
if (slot.hero->getHeroType() == hero)
if (slot.hero->getHeroTypeID() == hero)
return slot.role;
}
return TavernSlotRole::NONE;
@ -106,7 +106,7 @@ CGHeroInstance * TavernHeroesPool::takeHeroFromPool(HeroTypeID hero)
heroesPool.erase(hero);
vstd::erase_if(currentTavern, [&](const TavernSlot & entry){
return entry.hero->type->getId() == hero;
return entry.hero->getHeroTypeID() == hero;
});
assert(result);
@ -138,7 +138,7 @@ void TavernHeroesPool::onNewDay()
void TavernHeroesPool::addHeroToPool(CGHeroInstance * hero)
{
heroesPool[hero->getHeroType()] = hero;
heroesPool[hero->getHeroTypeID()] = hero;
}
void TavernHeroesPool::setAvailability(HeroTypeID hero, std::set<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
// Detect banks that use old format and load them using old bank hander
if (baseObject->id == Obj::CREATURE_BANK && entry.Struct().count("levels") && !entry.Struct().count("rewards"))
handler = "bank";
if (baseObject->id == Obj::CREATURE_BANK)
{
if (entry.Struct().count("levels") && !entry.Struct().count("rewards"))
handler = "bank";
else
handler = "configurable";
}
auto createdObject = handlerConstructors.at(handler)();

View File

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

View File

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

View File

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

View File

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

View File

@ -49,7 +49,8 @@ public:
void newTurn(vstd::RNG & rand) const override;
void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
void blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const override;
CreatureID getCreature() const;
CreatureID getCreatureID() const;
const CCreature * getCreature() const;
//stack formation depends on position,
bool containsUpgradedStack() const;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -431,7 +431,7 @@ void CGSeerHut::setObjToKill()
if(getCreatureToKill(true))
{
quest->stackToKill = getCreatureToKill(false)->getCreature();
quest->stackToKill = getCreatureToKill(false)->getCreatureID();
assert(quest->stackToKill != CreatureID::NONE);
quest->stackDirection = checkDirection();
}
@ -614,7 +614,7 @@ void CGSeerHut::onHeroVisit(const CGHeroInstance * h) const
int CGSeerHut::checkDirection() const
{
int3 cord = getCreatureToKill(false)->pos;
int3 cord = getCreatureToKill(false)->visitablePos();
if(static_cast<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

View File

@ -145,7 +145,7 @@ void IBoatGenerator::getProblemText(MetaString &out, const CGHeroInstance *visit
out.appendLocalString(EMetaText::ADVOB_TXT, 189);
break;
case NO_WATER:
logGlobal->error("Shipyard without water at tile %s! ", getObject()->getPosition().toString());
logGlobal->error("Shipyard without water at tile %s! ", getObject()->anchorPos().toString());
return;
}
}

View File

@ -47,7 +47,7 @@ public:
virtual PlayerColor getOwner() const = 0;
virtual int3 visitablePos() const = 0;
virtual int3 getPosition() const = 0;
virtual int3 anchorPos() const = 0;
virtual void onHeroVisit(const CGHeroInstance * h) const;
virtual void onHeroLeave(const CGHeroInstance * h) const;

View File

@ -111,7 +111,7 @@ void CGMine::initObj(vstd::RNG & rand)
}
else
{
logGlobal->error("Abandoned mine at (%s) has no valid resource candidates!", pos.toString());
logGlobal->error("Abandoned mine at (%s) has no valid resource candidates!", anchorPos().toString());
producedResource = GameResID::GOLD;
}
}
@ -510,11 +510,11 @@ void CGMonolith::onHeroVisit( const CGHeroInstance * h ) const
if(cb->isTeleportChannelImpassable(channel))
{
logGlobal->debug("Cannot find corresponding exit monolith for %d at %s", id.getNum(), pos.toString());
logGlobal->debug("Cannot find corresponding exit monolith for %d at %s", id.getNum(), anchorPos().toString());
td.impassable = true;
}
else if(getRandomExit(h) == ObjectInstanceID())
logGlobal->debug("All exits blocked for monolith %d at %s", id.getNum(), pos.toString());
logGlobal->debug("All exits blocked for monolith %d at %s", id.getNum(), anchorPos().toString());
}
else
h->showInfoDialog(70);
@ -574,7 +574,7 @@ void CGSubterraneanGate::onHeroVisit( const CGHeroInstance * h ) const
if(cb->isTeleportChannelImpassable(channel))
{
h->showInfoDialog(153);//Just inside the entrance you find a large pile of rubble blocking the tunnel. You leave discouraged.
logGlobal->debug("Cannot find exit subterranean gate for %d at %s", id.getNum(), pos.toString());
logGlobal->debug("Cannot find exit subterranean gate for %d at %s", id.getNum(), anchorPos().toString());
td.impassable = true;
}
else
@ -657,11 +657,11 @@ void CGWhirlpool::onHeroVisit( const CGHeroInstance * h ) const
TeleportDialog td(h->id, channel);
if(cb->isTeleportChannelImpassable(channel))
{
logGlobal->debug("Cannot find exit whirlpool for %d at %s", id.getNum(), pos.toString());
logGlobal->debug("Cannot find exit whirlpool for %d at %s", id.getNum(), anchorPos().toString());
td.impassable = true;
}
else if(getRandomExit(h) == ObjectInstanceID())
logGlobal->debug("All exits are blocked for whirlpool %d at %s", id.getNum(), pos.toString());
logGlobal->debug("All exits are blocked for whirlpool %d at %s", id.getNum(), anchorPos().toString());
if(!isProtected(h))
{
@ -1086,9 +1086,9 @@ void CGMagi::onHeroVisit(const CGHeroInstance * h) const
for(const auto & eye : eyes)
{
cb->getTilesInRange (fw.tiles, eye->pos, 10, ETileVisibility::HIDDEN, h->tempOwner);
cb->getTilesInRange (fw.tiles, eye->visitablePos(), 10, ETileVisibility::HIDDEN, h->tempOwner);
cb->sendAndApply(fw);
cv.pos = eye->pos;
cv.pos = eye->visitablePos();
cb->sendAndApply(cv);
}

View File

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

View File

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

View File

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

View File

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

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