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-08-29 14:42:37 +02:00
commit aea05b3fb8
185 changed files with 2676 additions and 2462 deletions

View File

@ -183,6 +183,11 @@ jobs:
distribution: 'temurin'
java-version: '11'
# a hack to build ID for x64 build in order for Google Play to allow upload of both 32 and 64 bit builds
- name: Bump Android x64 build ID
if: ${{ matrix.platform == 'android-64' }}
run: perl -i -pe 's/versionCode (\d+)/$x=$1+1; "versionCode $x"/e' android/vcmi-app/build.gradle
- name: Build Number
run: |
source '${{github.workspace}}/CI/get_package_name.sh'

View File

@ -921,7 +921,7 @@ std::vector<const battle::Unit *> BattleExchangeEvaluator::getOneTurnReachableUn
ReachabilityInfo unitReachability = reachabilityIter != reachabilityCache.end() ? reachabilityIter->second : turnBattle.getReachability(unit);
bool reachable = unitReachability.distances[hex] <= radius;
bool reachable = unitReachability.distances.at(hex) <= radius;
if(!reachable && unitReachability.accessibility[hex] == EAccessibility::ALIVE_STACK)
{
@ -931,7 +931,7 @@ std::vector<const battle::Unit *> BattleExchangeEvaluator::getOneTurnReachableUn
{
for(BattleHex neighbor : hex.neighbouringTiles())
{
reachable = unitReachability.distances[neighbor] <= radius;
reachable = unitReachability.distances.at(neighbor) <= radius;
if(reachable) break;
}
@ -981,7 +981,7 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb
for(BattleHex hex = BattleHex::TOP_LEFT; hex.isValid(); hex = hex + 1)
{
bool enemyUnit = false;
bool reachable = unitReachability.distances[hex] <= unitSpeed;
bool reachable = unitReachability.distances.at(hex) <= unitSpeed;
if(!reachable && unitReachability.accessibility[hex] == EAccessibility::ALIVE_STACK)
{
@ -993,7 +993,7 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb
for(BattleHex neighbor : hex.neighbouringTiles())
{
reachable = unitReachability.distances[neighbor] <= unitSpeed;
reachable = unitReachability.distances.at(neighbor) <= unitSpeed;
if(reachable) break;
}

View File

@ -132,10 +132,10 @@ SlotID StackWithBonuses::unitSlot() const
}
TConstBonusListPtr StackWithBonuses::getAllBonuses(const CSelector & selector, const CSelector & limit,
const CBonusSystemNode * root, const std::string & cachingStr) const
const std::string & cachingStr) const
{
auto ret = std::make_shared<BonusList>();
TConstBonusListPtr originalList = origBearer->getAllBonuses(selector, limit, root, cachingStr);
TConstBonusListPtr originalList = origBearer->getAllBonuses(selector, limit, cachingStr);
vstd::copy_if(*originalList, std::back_inserter(*ret), [this](const std::shared_ptr<Bonus> & b)
{

View File

@ -91,7 +91,7 @@ public:
///IBonusBearer
TConstBonusListPtr getAllBonuses(const CSelector & selector, const CSelector & limit,
const CBonusSystemNode * root = nullptr, const std::string & cachingStr = "") const override;
const std::string & cachingStr = "") const override;
int64_t getTreeVersion() const override;

View File

@ -21,8 +21,6 @@
#include "../../lib/GameSettings.h"
#include "../../lib/gameState/CGameState.h"
#include "../../lib/serializer/CTypeList.h"
#include "../../lib/serializer/BinarySerializer.h"
#include "../../lib/serializer/BinaryDeserializer.h"
#include "../../lib/networkPacks/PacksForClient.h"
#include "../../lib/networkPacks/PacksForClientBattle.h"
#include "../../lib/networkPacks/PacksForServer.h"
@ -1491,7 +1489,7 @@ void AIGateway::tryRealize(Goals::Trade & g) //trade
//TODO trade only as much as needed
if (toGive) //don't try to sell 0 resources
{
cb->trade(m, EMarketMode::RESOURCE_RESOURCE, res, GameResID(g.resID), toGive);
cb->trade(m->getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, res, GameResID(g.resID), toGive);
acquiredResources = static_cast<int>(toGet * (it->resVal / toGive));
logAi->debug("Traded %d of %s for %d of %s at %s", toGive, res, acquiredResources, g.resID, obj->getObjectName());
}

View File

@ -334,9 +334,7 @@ void BuildAnalyzer::updateDailyIncome()
const CGMine* mine = dynamic_cast<const CGMine*>(obj);
if(mine)
{
dailyIncome[mine->producedResource.getNum()] += mine->getProducedQuantity();
}
dailyIncome += mine->dailyIncome();
}
for(const CGTownInstance* town : towns)

View File

@ -196,7 +196,7 @@ bool BuildingManager::getBuildingOptions(const CGTownInstance * t)
return true;
//workaround for mantis #2696 - build capitol with separate algorithm if it is available
if(vstd::contains(t->builtBuildings, BuildingID::CITY_HALL) && getMaxPossibleGoldBuilding(t) == BuildingID::CAPITOL)
if(t->hasBuilt(BuildingID::CITY_HALL) && getMaxPossibleGoldBuilding(t) == BuildingID::CAPITOL)
{
if(tryBuildNextStructure(t, capitolAndRequirements))
return true;

View File

@ -31,8 +31,6 @@
#include "../../lib/networkPacks/PacksForClientBattle.h"
#include "../../lib/networkPacks/PacksForServer.h"
#include "../../lib/serializer/CTypeList.h"
#include "../../lib/serializer/BinarySerializer.h"
#include "../../lib/serializer/BinaryDeserializer.h"
#include "AIhelper.h"
@ -2130,7 +2128,7 @@ void VCAI::tryRealize(Goals::Trade & g) //trade
//TODO trade only as much as needed
if (toGive) //don't try to sell 0 resources
{
cb->trade(m, EMarketMode::RESOURCE_RESOURCE, res, GameResID(g.resID), toGive);
cb->trade(m->getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, res, GameResID(g.resID), toGive);
acquiredResources = static_cast<int>(toGet * (it->resVal / toGive));
logAi->debug("Traded %d of %s for %d of %s at %s", toGive, res, acquiredResources, g.resID, obj->getObjectName());
}

View File

@ -266,15 +266,15 @@ void CCallback::buyArtifact(const CGHeroInstance *hero, ArtifactID aid)
sendRequest(&pack);
}
void CCallback::trade(const IMarket * market, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero)
void CCallback::trade(const ObjectInstanceID marketId, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero)
{
trade(market, mode, std::vector(1, id1), std::vector(1, id2), std::vector(1, val1), hero);
trade(marketId, mode, std::vector(1, id1), std::vector(1, id2), std::vector(1, val1), hero);
}
void CCallback::trade(const IMarket * market, EMarketMode mode, const std::vector<TradeItemSell> & id1, const std::vector<TradeItemBuy> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero)
void CCallback::trade(const ObjectInstanceID marketId, EMarketMode mode, const std::vector<TradeItemSell> & id1, const std::vector<TradeItemBuy> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero)
{
TradeOnMarketplace pack;
pack.marketId = dynamic_cast<const CGObjectInstance *>(market)->id;
pack.marketId = marketId;
pack.heroId = hero ? hero->id : ObjectInstanceID();
pack.mode = mode;
pack.r1 = id1;

View File

@ -34,7 +34,6 @@ class IBattleEventsReceiver;
class IGameEventsReceiver;
struct ArtifactLocation;
class BattleStateInfoForRetreat;
class IMarket;
VCMI_LIB_NAMESPACE_END
@ -81,8 +80,8 @@ public:
virtual bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE)=0; //if newID==-1 then best possible upgrade will be made
virtual void swapGarrisonHero(const CGTownInstance *town)=0;
virtual void trade(const IMarket * market, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero = nullptr)=0; //mode==0: sell val1 units of id1 resource for id2 resiurce
virtual void trade(const IMarket * market, EMarketMode mode, const std::vector<TradeItemSell> & id1, const std::vector<TradeItemBuy> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero = nullptr)=0;
virtual void trade(const ObjectInstanceID marketId, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero)=0; //mode==0: sell val1 units of id1 resource for id2 resiurce
virtual void trade(const ObjectInstanceID marketId, EMarketMode mode, const std::vector<TradeItemSell> & id1, const std::vector<TradeItemBuy> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero)=0;
virtual int selectionMade(int selection, QueryID queryID) =0;
virtual int sendQueryReply(std::optional<int32_t> reply, QueryID queryID) =0;
@ -190,8 +189,8 @@ public:
void endTurn() override;
void swapGarrisonHero(const CGTownInstance *town) override;
void buyArtifact(const CGHeroInstance *hero, ArtifactID aid) override;
void trade(const IMarket * market, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero = nullptr) override;
void trade(const IMarket * market, EMarketMode mode, const std::vector<TradeItemSell> & id1, const std::vector<TradeItemBuy> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero = nullptr) override;
void trade(const ObjectInstanceID marketId, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero = nullptr) override;
void trade(const ObjectInstanceID marketId, EMarketMode mode, const std::vector<TradeItemSell> & id1, const std::vector<TradeItemBuy> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero = nullptr) override;
void setFormation(const CGHeroInstance * hero, EArmyFormation mode) override;
void recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero, const HeroTypeID & nextHero=HeroTypeID::NONE) override;
void save(const std::string &fname) override;

View File

@ -403,7 +403,10 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR NOT WIN32)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT WIN32)
# For gcc 14+ we can use -fhardened instead
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_GLIBCXX_ASSERTIONS -fstack-protector-strong -fstack-clash-protection -fcf-protection=full")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_GLIBCXX_ASSERTIONS -fstack-protector-strong -fstack-clash-protection")
if (CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcf-protection=full")
endif()
endif()
endif()

View File

@ -70,6 +70,18 @@
* Added support for HotA bank building from Factory
* Added support for HotA-style 8th creature in town
# 1.5.6 -> 1.5.7
* Fixed game freeze if player is attacked in online multiplayer game by another player when he has unread dialogs, such as new week notification
* Fixed possible game crash after being attacked by enemy with artifact that blocks spellcasting
* Fixed heroes on map limit game setting not respected when moving hero from town garrison.
* Add workaround to fix possible crash on attempt to start previously generated random map that has players without owned heroes or towns
* Fixed crash on right-clicking spell icon when receiving unlearnable spells from Pandora
* Fixed possible text overflow in match information box in online lobby
* Fixed overlapping text in lobby login window
* Fixed excessive removal of open dialogs such as new week or map events on new turn
* Fixed objects like Mystical Gardens not resetting their state on new week correctly
# 1.5.5 -> 1.5.6
### Stability

View File

@ -660,5 +660,7 @@
"core.bonus.WATER_IMMUNITY.name": "Water immunity",
"core.bonus.WATER_IMMUNITY.description": "Immune to all spells from the school of Water magic",
"core.bonus.WIDE_BREATH.name": "Wide breath",
"core.bonus.WIDE_BREATH.description": "Wide breath attack (multiple hexes)"
"core.bonus.WIDE_BREATH.description": "Wide breath attack (multiple hexes)",
"core.bonus.DISINTEGRATE.name": "Disintegrate",
"core.bonus.DISINTEGRATE.description": "No corpse remains after death"
}

View File

@ -163,7 +163,7 @@
"vcmi.systemOptions.townsGroup" : "Stadt-Bildschirm",
"vcmi.statisticWindow.statistics" : "Statistik",
"vcmi.statisticWindow.tsvCopy" : "Daten in Zwischenabl.",
"vcmi.statisticWindow.tsvCopy" : "In Zwischenablage",
"vcmi.statisticWindow.selectView" : "Ansicht wählen",
"vcmi.statisticWindow.value" : "Wert",
"vcmi.statisticWindow.title.overview" : "Überblick",

View File

@ -100,9 +100,8 @@
#include "../lib/pathfinder/CGPathNode.h"
#include "../lib/serializer/BinaryDeserializer.h"
#include "../lib/serializer/BinarySerializer.h"
#include "../lib/serializer/CTypeList.h"
#include "../lib/serializer/ESerializationVersion.h"
#include "../lib/spells/CSpellHandler.h"
@ -203,6 +202,11 @@ void CPlayerInterface::playerEndsTurn(PlayerColor player)
{
makingTurn = false;
closeAllDialogs();
// remove all pending dialogs that do not expect query answer
vstd::erase_if(dialogs, [](const std::shared_ptr<CInfoWindow> & window){
return window->ID == QueryID::NONE;
});
}
}
@ -619,9 +623,7 @@ void CPlayerInterface::battleStartBefore(const BattleID & battleID, const CCreat
{
movementController->onBattleStarted();
//Don't wait for dialogs when we are non-active hot-seat player
if (LOCPLINT == this)
waitForAllDialogs();
waitForAllDialogs();
}
void CPlayerInterface::battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, BattleSide side, bool replayAllowed)
@ -644,9 +646,7 @@ void CPlayerInterface::battleStart(const BattleID & battleID, const CCreatureSet
cb->registerBattleInterface(autofightingAI);
}
//Don't wait for dialogs when we are non-active hot-seat player
if (LOCPLINT == this)
waitForAllDialogs();
waitForAllDialogs();
BATTLE_EVENT_POSSIBLE_RETURN;
}
@ -1632,14 +1632,14 @@ void CPlayerInterface::battleNewRoundFirst(const BattleID & battleID)
battleInt->newRoundFirst();
}
void CPlayerInterface::showMarketWindow(const IMarket *market, const CGHeroInstance *visitor, QueryID queryID)
void CPlayerInterface::showMarketWindow(const IMarket * market, const CGHeroInstance * visitor, QueryID queryID)
{
EVENT_HANDLER_CALLED_BY_CLIENT;
auto onWindowClosed = [this, queryID](){
cb->selectionMade(0, queryID);
};
if (market->allowsTrade(EMarketMode::ARTIFACT_EXP) && dynamic_cast<const CGArtifactsAltar*>(market) == nullptr)
if (market->allowsTrade(EMarketMode::ARTIFACT_EXP) && market->getArtifactsStorage() == nullptr)
{
// compatibility check, safe to remove for 1.6
// 1.4 saves loaded in 1.5 will not be able to visit Altar of Sacrifice due to Altar now requiring different map object class
@ -1654,8 +1654,17 @@ void CPlayerInterface::showMarketWindow(const IMarket *market, const CGHeroInsta
GH.windows().createAndPushWindow<CMarketWindow>(market, visitor, onWindowClosed, EMarketMode::CREATURE_EXP);
else if(market->allowsTrade(EMarketMode::CREATURE_UNDEAD))
GH.windows().createAndPushWindow<CTransformerWindow>(market, visitor, onWindowClosed);
else if(!market->availableModes().empty())
GH.windows().createAndPushWindow<CMarketWindow>(market, visitor, onWindowClosed, market->availableModes().front());
else if (!market->availableModes().empty())
for(auto mode = EMarketMode::RESOURCE_RESOURCE; mode != EMarketMode::MARKET_AFTER_LAST_PLACEHOLDER; mode = vstd::next(mode, 1))
{
if(vstd::contains(market->availableModes(), mode))
{
GH.windows().createAndPushWindow<CMarketWindow>(market, visitor, onWindowClosed, mode);
break;
}
}
else
onWindowClosed();
}
void CPlayerInterface::showUniversityWindow(const IMarket *market, const CGHeroInstance *visitor, QueryID queryID)
@ -1664,7 +1673,7 @@ void CPlayerInterface::showUniversityWindow(const IMarket *market, const CGHeroI
auto onWindowClosed = [this, queryID](){
cb->selectionMade(0, queryID);
};
GH.windows().createAndPushWindow<CUniversityWindow>(visitor, market, onWindowClosed);
GH.windows().createAndPushWindow<CUniversityWindow>(visitor, BuildingID::NONE, market, onWindowClosed);
}
void CPlayerInterface::showHillFortWindow(const CGObjectInstance *object, const CGHeroInstance *visitor)
@ -1760,6 +1769,9 @@ void CPlayerInterface::artifactDisassembled(const ArtifactLocation &al)
void CPlayerInterface::waitForAllDialogs()
{
if (!makingTurn)
return;
while(!dialogs.empty())
{
auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex);

View File

@ -120,7 +120,7 @@ protected: // Call-ins from server, should not be called directly, but only via
void showTeleportDialog(const CGHeroInstance * hero, TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override;
void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) override;
void showMapObjectSelectDialog(QueryID askID, const Component & icon, const MetaString & title, const MetaString & description, const std::vector<ObjectInstanceID> & objects) override;
void showMarketWindow(const IMarket *market, const CGHeroInstance *visitor, QueryID queryID) override;
void showMarketWindow(const IMarket * market, const CGHeroInstance * visitor, QueryID queryID) override;
void showUniversityWindow(const IMarket *market, const CGHeroInstance *visitor, QueryID queryID) override;
void showHillFortWindow(const CGObjectInstance *object, const CGHeroInstance *visitor) override;
void advmapSpellCast(const CGHeroInstance * caster, SpellID spellID) override; //called when a hero casts a spell

View File

@ -35,6 +35,7 @@
#include "../lib/TurnTimerInfo.h"
#include "../lib/VCMIDirs.h"
#include "../lib/campaign/CampaignState.h"
#include "../lib/gameState/CGameState.h"
#include "../lib/gameState/HighScore.h"
#include "../lib/CPlayerState.h"
#include "../lib/mapping/CMapInfo.h"
@ -44,7 +45,6 @@
#include "../lib/rmg/CMapGenOptions.h"
#include "../lib/serializer/Connection.h"
#include "../lib/filesystem/Filesystem.h"
#include "../lib/registerTypes/RegisterTypesLobbyPacks.h"
#include "../lib/serializer/CMemorySerializer.h"
#include "../lib/UnlockGuard.h"
@ -56,61 +56,6 @@
#include <vcmi/events/EventBus.h>
template<typename T> class CApplyOnLobby;
class CBaseForLobbyApply
{
public:
virtual bool applyOnLobbyHandler(CServerHandler * handler, CPackForLobby & pack) const = 0;
virtual void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, CPackForLobby & pack) const = 0;
virtual ~CBaseForLobbyApply(){};
template<typename U> static CBaseForLobbyApply * getApplier(const U * t = nullptr)
{
return new CApplyOnLobby<U>();
}
};
template<typename T> class CApplyOnLobby : public CBaseForLobbyApply
{
public:
bool applyOnLobbyHandler(CServerHandler * handler, CPackForLobby & pack) const override
{
auto & ref = static_cast<T&>(pack);
ApplyOnLobbyHandlerNetPackVisitor visitor(*handler);
logNetwork->trace("\tImmediately apply on lobby: %s", typeid(ref).name());
ref.visit(visitor);
return visitor.getResult();
}
void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, CPackForLobby & pack) const override
{
auto & ref = static_cast<T &>(pack);
ApplyOnLobbyScreenNetPackVisitor visitor(*handler, lobby);
logNetwork->trace("\tApply on lobby from queue: %s", typeid(ref).name());
ref.visit(visitor);
}
};
template<> class CApplyOnLobby<CPack>: public CBaseForLobbyApply
{
public:
bool applyOnLobbyHandler(CServerHandler * handler, CPackForLobby & pack) const override
{
logGlobal->error("Cannot apply plain CPack!");
assert(0);
return false;
}
void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, CPackForLobby & pack) const override
{
logGlobal->error("Cannot apply plain CPack!");
assert(0);
}
};
CServerHandler::~CServerHandler()
{
if (serverRunner)
@ -148,7 +93,6 @@ CServerHandler::CServerHandler()
: networkHandler(INetworkHandler::createHandler())
, lobbyClient(std::make_unique<GlobalLobbyClient>())
, gameChat(std::make_unique<GameChatHandler>())
, applier(std::make_unique<CApplier<CBaseForLobbyApply>>())
, threadNetwork(&CServerHandler::threadRunNetwork, this)
, state(EClientState::NONE)
, serverPort(0)
@ -159,7 +103,6 @@ CServerHandler::CServerHandler()
, client(nullptr)
{
uuid = boost::uuids::to_string(boost::uuids::random_generator()());
registerTypesLobbyPacks(*applier);
}
void CServerHandler::threadRunNetwork()
@ -320,8 +263,8 @@ void CServerHandler::onConnectionEstablished(const NetworkConnectionPtr & netCon
void CServerHandler::applyPackOnLobbyScreen(CPackForLobby & pack)
{
const CBaseForLobbyApply * apply = applier->getApplier(CTypeList::getInstance().getTypeID(&pack)); //find the applier
apply->applyOnLobbyScreen(dynamic_cast<CLobbyScreen *>(SEL), this, pack);
ApplyOnLobbyScreenNetPackVisitor visitor(*this, dynamic_cast<CLobbyScreen *>(SEL));
pack.visit(visitor);
GH.windows().totalRedraw();
}
@ -960,7 +903,10 @@ void CServerHandler::waitForServerShutdown()
void CServerHandler::visitForLobby(CPackForLobby & lobbyPack)
{
if(applier->getApplier(CTypeList::getInstance().getTypeID(&lobbyPack))->applyOnLobbyHandler(this, lobbyPack))
ApplyOnLobbyHandlerNetPackVisitor visitor(*this);
lobbyPack.visit(visitor);
if(visitor.getResult())
{
if(!settings["session"]["headless"].Bool())
applyPackOnLobbyScreen(lobbyPack);

View File

@ -31,8 +31,6 @@ struct CPackForClient;
class HighScoreParameter;
template<typename T> class CApplier;
VCMI_LIB_NAMESPACE_END
class CClient;
@ -102,7 +100,6 @@ class CServerHandler final : public IServerAPI, public LobbyInfo, public INetwor
std::shared_ptr<INetworkConnection> networkConnection;
std::unique_ptr<GlobalLobbyClient> lobbyClient;
std::unique_ptr<GameChatHandler> gameChat;
std::unique_ptr<CApplier<CBaseForLobbyApply>> applier;
std::unique_ptr<IServerRunner> serverRunner;
std::shared_ptr<CMapInfo> mapToStart;
std::vector<std::string> localPlayerNames;

View File

@ -29,13 +29,10 @@
#include "../lib/VCMIDirs.h"
#include "../lib/UnlockGuard.h"
#include "../lib/battle/BattleInfo.h"
#include "../lib/serializer/BinaryDeserializer.h"
#include "../lib/serializer/BinarySerializer.h"
#include "../lib/serializer/Connection.h"
#include "../lib/mapping/CMapService.h"
#include "../lib/pathfinder/CGPathNode.h"
#include "../lib/filesystem/Filesystem.h"
#include "../lib/registerTypes/RegisterTypesClientPacks.h"
#include <memory>
#include <vcmi/events/EventBus.h>
@ -50,53 +47,6 @@
ThreadSafeVector<int> CClient::waitingRequest;
template<typename T> class CApplyOnCL;
class CBaseForCLApply
{
public:
virtual void applyOnClAfter(CClient * cl, CPack * pack) const =0;
virtual void applyOnClBefore(CClient * cl, CPack * pack) const =0;
virtual ~CBaseForCLApply(){}
template<typename U> static CBaseForCLApply * getApplier(const U * t = nullptr)
{
return new CApplyOnCL<U>();
}
};
template<typename T> class CApplyOnCL : public CBaseForCLApply
{
public:
void applyOnClAfter(CClient * cl, CPack * pack) const override
{
T * ptr = static_cast<T *>(pack);
ApplyClientNetPackVisitor visitor(*cl, *cl->gameState());
ptr->visit(visitor);
}
void applyOnClBefore(CClient * cl, CPack * pack) const override
{
T * ptr = static_cast<T *>(pack);
ApplyFirstClientNetPackVisitor visitor(*cl, *cl->gameState());
ptr->visit(visitor);
}
};
template<> class CApplyOnCL<CPack>: public CBaseForCLApply
{
public:
void applyOnClAfter(CClient * cl, CPack * pack) const override
{
logGlobal->error("Cannot apply on CL plain CPack!");
assert(0);
}
void applyOnClBefore(CClient * cl, CPack * pack) const override
{
logGlobal->error("Cannot apply on CL plain CPack!");
assert(0);
}
};
CPlayerEnvironment::CPlayerEnvironment(PlayerColor player_, CClient * cl_, std::shared_ptr<CCallback> mainCallback_)
: player(player_),
cl(cl_),
@ -130,12 +80,9 @@ const CPlayerEnvironment::GameCb * CPlayerEnvironment::game() const
return mainCallback.get();
}
CClient::CClient()
{
waitingRequest.clear();
applier = std::make_shared<CApplier<CBaseForCLApply>>();
registerTypesClientPacks(*applier);
gs = nullptr;
}
@ -400,25 +347,21 @@ void CClient::installNewBattleInterface(std::shared_ptr<CBattleGameInterface> ba
}
}
void CClient::handlePack(CPack * pack)
void CClient::handlePack(CPackForClient * pack)
{
CBaseForCLApply * apply = applier->getApplier(CTypeList::getInstance().getTypeID(pack)); //find the applier
if(apply)
ApplyClientNetPackVisitor afterVisitor(*this, *gameState());
ApplyFirstClientNetPackVisitor beforeVisitor(*this, *gameState());
pack->visit(beforeVisitor);
logNetwork->trace("\tMade first apply on cl: %s", typeid(*pack).name());
{
apply->applyOnClBefore(this, pack);
logNetwork->trace("\tMade first apply on cl: %s", typeid(*pack).name());
{
boost::unique_lock lock(CGameState::mutex);
gs->apply(pack);
}
logNetwork->trace("\tApplied on gs: %s", typeid(*pack).name());
apply->applyOnClAfter(this, pack);
logNetwork->trace("\tMade second apply on cl: %s", typeid(*pack).name());
}
else
{
logNetwork->error("Message %s cannot be applied, cannot find applier!", typeid(*pack).name());
boost::unique_lock lock(CGameState::mutex);
gs->apply(pack);
}
logNetwork->trace("\tApplied on gs: %s", typeid(*pack).name());
pack->visit(afterVisitor);
logNetwork->trace("\tMade second apply on cl: %s", typeid(*pack).name());
delete pack;
}

View File

@ -21,14 +21,10 @@ struct CPackForServer;
class IBattleEventsReceiver;
class CBattleGameInterface;
class CGameInterface;
class BinaryDeserializer;
class BinarySerializer;
class BattleAction;
class BattleInfo;
struct BankConfig;
template<typename T> class CApplier;
#if SCRIPTING_ENABLED
namespace scripting
{
@ -147,7 +143,7 @@ public:
static ThreadSafeVector<int> waitingRequest; //FIXME: make this normal field (need to join all threads before client destruction)
void handlePack(CPack * pack); //applies the given pack and deletes it
void handlePack(CPackForClient * pack); //applies the given pack and deletes it
int sendRequest(const CPackForServer * request, PlayerColor player); //returns ID given to that request
void battleStarted(const BattleInfo * info);
@ -211,7 +207,7 @@ public:
void castSpell(const spells::Caster * caster, SpellID spellID, const int3 &pos) override {};
void changeFogOfWar(int3 center, ui32 radius, PlayerColor player, ETileVisibility mode) override {}
void changeFogOfWar(std::unordered_set<int3> & tiles, PlayerColor player, ETileVisibility mode) override {}
void changeFogOfWar(const std::unordered_set<int3> & tiles, PlayerColor player, ETileVisibility mode) override {}
void setObjPropertyValue(ObjectInstanceID objid, ObjProperty prop, int32_t value) override {};
void setObjPropertyID(ObjectInstanceID objid, ObjProperty prop, ObjPropertyID identifier) override {};
@ -237,8 +233,6 @@ private:
#endif
std::unique_ptr<events::EventBus> clientEventBus;
std::shared_ptr<CApplier<CBaseForCLApply>> applier;
mutable boost::mutex pathCacheMutex;
std::map<const CGHeroInstance *, std::shared_ptr<CPathsInfo>> pathCache;

View File

@ -29,7 +29,6 @@
#include "../CCallback.h"
#include "../lib/filesystem/Filesystem.h"
#include "../lib/filesystem/FileInfo.h"
#include "../lib/serializer/BinarySerializer.h"
#include "../lib/serializer/Connection.h"
#include "../lib/texts/CGeneralTextHandler.h"
#include "../lib/CHeroHandler.h"
@ -362,6 +361,14 @@ void ApplyClientNetPackVisitor::visitHeroVisit(HeroVisit & pack)
void ApplyClientNetPackVisitor::visitNewTurn(NewTurn & pack)
{
cl.invalidatePaths();
if (pack.newWeekNotification)
{
const auto & newWeek = *pack.newWeekNotification;
std::string str = newWeek.text.toString();
callAllInterfaces(cl, &CGameInterface::showInfoDialog, newWeek.type, str, newWeek.components,(soundBase::soundID)newWeek.soundID);
}
}
void ApplyClientNetPackVisitor::visitGiveBonus(GiveBonus & pack)
@ -999,7 +1006,7 @@ void ApplyClientNetPackVisitor::visitOpenWindow(OpenWindow & pack)
case EOpenWindowMode::UNIVERSITY_WINDOW:
{
//displays University window (when hero enters University on adventure map)
const auto * market = dynamic_cast<const IMarket*>(cl.getObj(ObjectInstanceID(pack.object)));
const auto * market = cl.getMarket(ObjectInstanceID(pack.object));
const CGHeroInstance *hero = cl.getHero(ObjectInstanceID(pack.visitor));
callInterfaceIfPresent(cl, hero->tempOwner, &IGameEventsReceiver::showUniversityWindow, market, hero, pack.queryID);
}
@ -1009,7 +1016,7 @@ void ApplyClientNetPackVisitor::visitOpenWindow(OpenWindow & pack)
//displays Thieves' Guild window (when hero enters Den of Thieves)
const CGObjectInstance *obj = cl.getObj(ObjectInstanceID(pack.object));
const CGHeroInstance *hero = cl.getHero(ObjectInstanceID(pack.visitor));
const auto *market = dynamic_cast<const IMarket*>(obj);
const auto market = cl.getMarket(pack.object);
callInterfaceIfPresent(cl, cl.getTile(obj->visitablePos())->visitableObjects.back()->tempOwner, &IGameEventsReceiver::showMarketWindow, market, hero, pack.queryID);
}
break;

View File

@ -68,7 +68,10 @@ GlobalLobbyLoginWindow::GlobalLobbyLoginWindow()
onLoginModeChanged(0); // call it manually to disable widgets - toggleMode will not emit this call if this is currently selected option
}
else
{
toggleMode->setSelected(1);
onLoginModeChanged(1);
}
filledBackground->setPlayerColor(PlayerColor(1));
inputUsername->setCallback([this](const std::string & text)

View File

@ -286,5 +286,5 @@ GlobalLobbyMatchCard::GlobalLobbyMatchCard(GlobalLobbyWindow * window, const Glo
opponentDescription.replaceNumber(matchDescription.participants.size());
}
labelMatchOpponent = std::make_shared<CLabel>(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, opponentDescription.toString());
labelMatchOpponent = std::make_shared<CLabel>(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, opponentDescription.toString(), 120);
}

View File

@ -47,7 +47,6 @@
#include "../../lib/texts/CGeneralTextHandler.h"
#include "../../lib/campaign/CampaignHandler.h"
#include "../../lib/serializer/CTypeList.h"
#include "../../lib/filesystem/Filesystem.h"
#include "../../lib/filesystem/CCompressedStream.h"
#include "../../lib/mapping/CMapInfo.h"

View File

@ -130,13 +130,9 @@ TIcons CStatisticScreen::extractIcons() const
std::sort(tmpData.begin(), tmpData.end(), [](const StatisticDataSetEntry & v1, const StatisticDataSetEntry & v2){ return v1.player == v2.player ? v1.day < v2.day : v1.player < v2.player; });
auto imageTown = GH.renderHandler().loadImage(AnimationPath::builtin("cradvntr"), 3, 0, EImageBlitMode::COLORKEY);
imageTown->scaleTo(Point(CHART_ICON_SIZE, CHART_ICON_SIZE));
auto imageBattle = GH.renderHandler().loadImage(AnimationPath::builtin("cradvntr"), 5, 0, EImageBlitMode::COLORKEY);
imageBattle->scaleTo(Point(CHART_ICON_SIZE, CHART_ICON_SIZE));
auto imageDefeated = GH.renderHandler().loadImage(AnimationPath::builtin("tpthchk"), 1, 0, EImageBlitMode::COLORKEY);
imageDefeated->scaleTo(Point(CHART_ICON_SIZE, CHART_ICON_SIZE));
auto imageDefeated = GH.renderHandler().loadImage(AnimationPath::builtin("crcombat"), 0, 0, EImageBlitMode::COLORKEY);
auto imageGrail = GH.renderHandler().loadImage(AnimationPath::builtin("vwsymbol"), 2, 0, EImageBlitMode::COLORKEY);
imageGrail->scaleTo(Point(CHART_ICON_SIZE, CHART_ICON_SIZE));
std::map<PlayerColor, bool> foundDefeated;
std::map<PlayerColor, bool> foundGrail;
@ -273,7 +269,11 @@ OverviewPanel::OverviewPanel(Rect position, std::string title, const StatisticDa
},
{
CGI->generaltexth->translate("vcmi.statisticWindow.param.daysSurvived"), [this](PlayerColor color){
return CStatisticScreen::getDay(playerDataFilter(color).size());
auto playerData = playerDataFilter(color);
for(int i = 0; i < playerData.size(); i++)
if(playerData[i].status == EPlayerStatus::LOSER)
return CStatisticScreen::getDay(i + 1);
return CStatisticScreen::getDay(playerData.size());
}
},
{
@ -424,12 +424,25 @@ void OverviewPanel::update(int to)
}
}
int computeGridStep(int maxAmount, int linesLimit)
{
for (int lineInterval = 1;;lineInterval *= 10)
{
for (int factor : { 1, 2, 5 } )
{
int lineIntervalToTest = lineInterval * factor;
if (maxAmount / lineIntervalToTest <= linesLimit)
return lineIntervalToTest;
}
}
}
LineChart::LineChart(Rect position, std::string title, TData data, TIcons icons, float maxY)
: CIntObject(), maxVal(0), maxDay(0)
{
OBJECT_CONSTRUCTION;
addUsedEvents(LCLICK | MOVE);
addUsedEvents(LCLICK | MOVE | GESTURE);
pos = position + pos.topLeft();
@ -455,15 +468,48 @@ LineChart::LineChart(Rect position, std::string title, TData data, TIcons icons,
maxDay = line.second.size();
}
//calculate nice maxVal
int gridLineCount = 10;
int gridStep = computeGridStep(maxVal, gridLineCount);
niceMaxVal = gridStep * std::ceil(maxVal / gridStep);
niceMaxVal = std::max(1, niceMaxVal); // avoid zero size Y axis (if all values are 0)
// calculate points in chart
auto getPoint = [this](int i, std::vector<float> data){
float x = (static_cast<float>(chartArea.w) / static_cast<float>(maxDay - 1)) * static_cast<float>(i);
float y = static_cast<float>(chartArea.h) - (static_cast<float>(chartArea.h) / niceMaxVal) * data[i];
return Point(x, y);
};
// draw grid (vertical lines)
int dayGridInterval = maxDay < 700 ? 7 : 28;
for(const auto & line : data)
{
for(int i = 0; i < line.second.size(); i += dayGridInterval)
{
Point p = getPoint(i, line.second) + chartArea.topLeft();
canvas->addLine(Point(p.x, chartArea.topLeft().y), Point(p.x, chartArea.topLeft().y + chartArea.h), ColorRGBA(70, 70, 70));
}
}
// draw grid (horizontal lines)
if(maxVal > 0)
{
int gridStepPx = int((static_cast<float>(chartArea.h) / niceMaxVal) * gridStep);
for(int i = 0; i < std::ceil(maxVal / gridStep) + 1; i++)
{
canvas->addLine(chartArea.topLeft() + Point(0, chartArea.h - gridStepPx * i), chartArea.topLeft() + Point(chartArea.w, chartArea.h - gridStepPx * i), ColorRGBA(70, 70, 70));
layout.emplace_back(std::make_shared<CLabel>(chartArea.topLeft().x - 5, chartArea.topLeft().y + 10 + chartArea.h - gridStepPx * i, FONT_SMALL, ETextAlignment::CENTERRIGHT, Colors::WHITE, TextOperations::formatMetric(i * gridStep, 5)));
}
}
// draw
for(const auto & line : data)
{
Point lastPoint(-1, -1);
for(int i = 0; i < line.second.size(); i++)
{
float x = (static_cast<float>(chartArea.w) / static_cast<float>(maxDay - 1)) * static_cast<float>(i);
float y = static_cast<float>(chartArea.h) - (static_cast<float>(chartArea.h) / maxVal) * line.second[i];
Point p = Point(x, y) + chartArea.topLeft();
Point p = getPoint(i, line.second) + chartArea.topLeft();
if(lastPoint.x != -1)
canvas->addLine(lastPoint, p, line.first);
@ -472,7 +518,7 @@ LineChart::LineChart(Rect position, std::string title, TData data, TIcons icons,
for(auto & icon : icons)
if(std::get<0>(icon) == line.first && std::get<1>(icon) == i + 1) // color && day
{
pictures.emplace_back(std::make_shared<CPicture>(std::get<2>(icon), Point(x - (CHART_ICON_SIZE / 2), y - (CHART_ICON_SIZE / 2)) + chartArea.topLeft()));
pictures.emplace_back(std::make_shared<CPicture>(std::get<2>(icon), Point(p.x - (std::get<2>(icon)->width() / 2), p.y - (std::get<2>(icon)->height() / 2))));
pictures.back()->addRClickCallback([icon](){ CRClickPopup::createAndPush(std::get<3>(icon)); });
}
@ -484,12 +530,8 @@ LineChart::LineChart(Rect position, std::string title, TData data, TIcons icons,
canvas->addLine(chartArea.topLeft() + Point(0, -10), chartArea.topLeft() + Point(0, chartArea.h + 10), Colors::WHITE);
canvas->addLine(chartArea.topLeft() + Point(-10, chartArea.h), chartArea.topLeft() + Point(chartArea.w + 10, chartArea.h), Colors::WHITE);
Point p = chartArea.topLeft() + Point(-5, chartArea.h + 10);
layout.emplace_back(std::make_shared<CLabel>(p.x, p.y, FONT_SMALL, ETextAlignment::CENTERRIGHT, Colors::WHITE, "0"));
p = chartArea.topLeft() + Point(chartArea.w + 10, chartArea.h + 10);
Point p = chartArea.topLeft() + Point(chartArea.w + 10, chartArea.h + 10);
layout.emplace_back(std::make_shared<CLabel>(p.x, p.y, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CStatisticScreen::getDay(maxDay)));
p = chartArea.topLeft() + Point(-5, -10);
layout.emplace_back(std::make_shared<CLabel>(p.x, p.y, FONT_SMALL, ETextAlignment::CENTERRIGHT, Colors::WHITE, std::to_string(static_cast<int>(maxVal))));
p = chartArea.bottomLeft() + Point(chartArea.w / 2, + 20);
layout.emplace_back(std::make_shared<CLabel>(p.x, p.y, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.64")));
}
@ -502,8 +544,8 @@ void LineChart::updateStatusBar(const Point & cursorPosition)
statusBar->setEnabled(r.isInside(cursorPosition));
if(r.isInside(cursorPosition))
{
float x = (static_cast<float>(maxDay) / static_cast<float>(chartArea.w)) * (static_cast<float>(cursorPosition.x) - static_cast<float>(r.x)) + 1.0f;
float y = maxVal - (maxVal / static_cast<float>(chartArea.h)) * (static_cast<float>(cursorPosition.y) - static_cast<float>(r.y));
float x = (static_cast<float>(maxDay - 1) / static_cast<float>(chartArea.w)) * (static_cast<float>(cursorPosition.x) - static_cast<float>(r.x)) + 1.0f;
float y = niceMaxVal - (niceMaxVal / static_cast<float>(chartArea.h)) * (static_cast<float>(cursorPosition.y) - static_cast<float>(r.y));
statusBar->write(CGI->generaltexth->translate("core.genrltxt.64") + ": " + CStatisticScreen::getDay(x) + " " + CGI->generaltexth->translate("vcmi.statisticWindow.value") + ": " + (static_cast<int>(y) > 0 ? std::to_string(static_cast<int>(y)) : std::to_string(y)));
}
setRedrawParent(true);
@ -515,7 +557,7 @@ void LineChart::mouseMoved(const Point & cursorPosition, const Point & lastUpdat
updateStatusBar(cursorPosition);
}
void LineChart::clickPressed(const Point & cursorPosition)
void LineChart::gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance)
{
updateStatusBar(cursorPosition);
updateStatusBar(currentPosition);
}

View File

@ -24,8 +24,6 @@ class CPicture;
using TData = std::vector<std::pair<ColorRGBA, std::vector<float>>>;
using TIcons = std::vector<std::tuple<ColorRGBA, int, std::shared_ptr<IImage>, std::string>>; // Color, Day, Image, Helptext
const int CHART_ICON_SIZE = 32;
class CStatisticScreen : public CWindowObject
{
enum Content {
@ -123,6 +121,7 @@ class LineChart : public CIntObject
Rect chartArea;
float maxVal;
int niceMaxVal;
int maxDay;
void updateStatusBar(const Point & cursorPosition);
@ -130,5 +129,5 @@ public:
LineChart(Rect position, std::string title, TData data, TIcons icons, float maxY);
void mouseMoved(const Point & cursorPosition, const Point & lastUpdateDistance) override;
void clickPressed(const Point & cursorPosition) override;
void gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance) override;
};

View File

@ -56,12 +56,8 @@ bool ImageLocator::operator<(const ImageLocator & other) const
return scalingFactor < other.scalingFactor;
if(playerColored != other.playerColored)
return playerColored < other.playerColored;
if(layerShadow != other.layerShadow)
return layerShadow < other.layerShadow;
if(layerBody != other.layerBody)
return layerBody < other.layerBody;
if (layerOverlay != other.layerOverlay)
return layerOverlay < other.layerOverlay;
if(layer != other.layer)
return layer < other.layer;
return false;
}

View File

@ -12,6 +12,15 @@
#include "../../lib/filesystem/ResourcePath.h"
#include "../../lib/constants/EntityIdentifiers.h"
enum class EImageLayer
{
ALL,
BODY,
SHADOW,
OVERLAY,
};
struct ImageLocator
{
std::optional<ImagePath> image;
@ -19,13 +28,12 @@ struct ImageLocator
int defFrame = -1;
int defGroup = -1;
PlayerColor playerColored = PlayerColor::CANNOT_DETERMINE;
bool verticalFlip = false;
bool horizontalFlip = false;
int8_t scalingFactor = 1;
PlayerColor playerColored = PlayerColor::CANNOT_DETERMINE;
bool layerShadow = false;
bool layerBody = true;
bool layerOverlay = false;
EImageLayer layer = EImageLayer::ALL;
ImageLocator() = default;
ImageLocator(const AnimationPath & path, int frame, int group);

View File

@ -30,6 +30,8 @@ ImageScaled::ImageScaled(const ImageLocator & inputLocator, const std::shared_pt
{
locator.scalingFactor = GH.screenHandler().getScalingFactor();
setBodyEnabled(true);
if (mode == EImageBlitMode::ALPHA)
setShadowEnabled(true);
}
std::shared_ptr<ISharedImage> ImageScaled::getSharedImage() const
@ -45,7 +47,7 @@ void ImageScaled::scaleInteger(int factor)
void ImageScaled::scaleTo(const Point & size)
{
if (body)
body = body->scaleTo(size, nullptr); // FIXME: adjust for scaling
body = body->scaleTo(size * GH.screenHandler().getScalingFactor(), nullptr);
}
void ImageScaled::exportBitmap(const boost::filesystem::path &path) const
@ -107,11 +109,10 @@ void ImageScaled::adjustPalette(const ColorFilter &shifter, uint32_t colorsToSki
void ImageScaled::setShadowEnabled(bool on)
{
assert(blitMode == EImageBlitMode::ALPHA);
if (on)
{
locator.layerBody = false;
locator.layerShadow = true;
locator.layerOverlay = false;
locator.layer = EImageLayer::SHADOW;
locator.playerColored = PlayerColor::CANNOT_DETERMINE;
shadow = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage();
}
@ -123,9 +124,7 @@ void ImageScaled::setBodyEnabled(bool on)
{
if (on)
{
locator.layerBody = true;
locator.layerShadow = false;
locator.layerOverlay = false;
locator.layer = blitMode == EImageBlitMode::ALPHA ? EImageLayer::BODY : EImageLayer::ALL;
locator.playerColored = playerColor;
body = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage();
}
@ -136,11 +135,10 @@ void ImageScaled::setBodyEnabled(bool on)
void ImageScaled::setOverlayEnabled(bool on)
{
assert(blitMode == EImageBlitMode::ALPHA);
if (on)
{
locator.layerBody = false;
locator.layerShadow = false;
locator.layerOverlay = true;
locator.layer = EImageLayer::OVERLAY;
locator.playerColored = PlayerColor::CANNOT_DETERMINE;
overlay = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage();
}

View File

@ -228,16 +228,14 @@ std::shared_ptr<ISharedImage> RenderHandler::scaleImage(const ImageLocator & loc
if (imageFiles.count(locator))
return imageFiles.at(locator);
auto handle = image->createImageReference(EImageBlitMode::OPAQUE);
auto handle = image->createImageReference(locator.layer == EImageLayer::ALL ? EImageBlitMode::OPAQUE : EImageBlitMode::ALPHA);
assert(locator.scalingFactor != 1); // should be filtered-out before
handle->setOverlayEnabled(locator.layerOverlay);
handle->setBodyEnabled(locator.layerBody);
handle->setShadowEnabled(locator.layerShadow);
if (locator.layerBody && locator.playerColored != PlayerColor::CANNOT_DETERMINE)
handle->setOverlayEnabled(locator.layer == EImageLayer::ALL || locator.layer == EImageLayer::OVERLAY);
handle->setBodyEnabled(locator.layer == EImageLayer::ALL || locator.layer == EImageLayer::BODY);
handle->setShadowEnabled(locator.layer == EImageLayer::ALL || locator.layer == EImageLayer::SHADOW);
if (locator.layer == EImageLayer::ALL && locator.playerColored != PlayerColor::CANNOT_DETERMINE)
handle->playerColored(locator.playerColored);
handle->scaleInteger(locator.scalingFactor);

View File

@ -472,13 +472,11 @@ void SDLImageIndexed::setShadowTransparency(float factor)
};
// seems to be used unconditionally
colorsSDL[0] = CSDL_Ext::toSDL(Colors::TRANSPARENCY);
colorsSDL[1] = CSDL_Ext::toSDL(shadow25);
colorsSDL[4] = CSDL_Ext::toSDL(shadow50);
// seems to be used only if color matches
if (colorsSimilar(originalPalette->colors[0], sourcePalette[0]))
colorsSDL[0] = CSDL_Ext::toSDL(Colors::TRANSPARENCY);
if (colorsSimilar(originalPalette->colors[2], sourcePalette[2]))
colorsSDL[2] = CSDL_Ext::toSDL(shadow25);
@ -502,6 +500,9 @@ void SDLImageIndexed::setShadowEnabled(bool on)
if (on)
setShadowTransparency(1.0);
if (!on && blitMode == EImageBlitMode::ALPHA)
setShadowTransparency(0.0);
shadowEnabled = on;
}

View File

@ -233,7 +233,7 @@ std::string CComponent::getDescription() const
return description;
}
case ComponentType::SPELL:
return CGI->spells()->getById(data.subType.as<SpellID>())->getDescriptionTranslated(data.value.value_or(0));
return CGI->spells()->getById(data.subType.as<SpellID>())->getDescriptionTranslated(std::max(0, data.value.value_or(0)));
case ComponentType::MORALE:
return CGI->generaltexth->heroscrn[ 4 - (data.value.value_or(0)>0) + (data.value.value_or(0)<0)];
case ComponentType::LUCK:
@ -293,7 +293,7 @@ std::string CComponent::getSubtitle() const
return CGI->artifacts()->getById(data.subType.as<ArtifactID>())->getNameTranslated();
case ComponentType::SPELL_SCROLL:
case ComponentType::SPELL:
if (data.value < 0)
if (data.value.value_or(0) < 0)
return "{#A9A9A9|" + CGI->spells()->getById(data.subType.as<SpellID>())->getNameTranslated() + "}";
else
return CGI->spells()->getById(data.subType.as<SpellID>())->getNameTranslated();

View File

@ -464,7 +464,7 @@ void CInteractableTownTooltip::init(const CGTownInstance * town)
std::vector<const CGTownInstance*> towns = LOCPLINT->cb->getTownsInfo(true);
for(auto & town : towns)
{
if(town->id == townId && town->builtBuildings.count(BuildingID::TAVERN))
if(town->id == townId && town->hasBuilt(BuildingID::TAVERN))
LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE);
}
}, [town]{
@ -476,7 +476,7 @@ void CInteractableTownTooltip::init(const CGTownInstance * town)
std::vector<const CGTownInstance*> towns = LOCPLINT->cb->getTownsInfo(true);
for(auto & town : towns)
{
if(town->builtBuildings.count(BuildingID::MARKETPLACE))
if(town->hasBuilt(BuildingID::MARKETPLACE))
{
GH.windows().createAndPushWindow<CMarketWindow>(town, nullptr, nullptr, EMarketMode::RESOURCE_RESOURCE);
return;

View File

@ -24,16 +24,15 @@
#include "../../../lib/networkPacks/ArtifactLocation.h"
#include "../../../lib/texts/CGeneralTextHandler.h"
#include "../../../lib/mapObjects/CGHeroInstance.h"
#include "../../../lib/mapObjects/CGMarket.h"
#include "../../../lib/mapObjects/IMarket.h"
CAltarArtifacts::CAltarArtifacts(const IMarket * market, const CGHeroInstance * hero)
: CMarketBase(market, hero)
{
OBJECT_CONSTRUCTION;
assert(dynamic_cast<const CGArtifactsAltar*>(market));
auto altarObj = dynamic_cast<const CGArtifactsAltar*>(market);
altarArtifacts = altarObj;
assert(market->getArtifactsStorage());
altarArtifactsStorage = market->getArtifactsStorage();
deal = std::make_shared<CButton>(Point(269, 520), AnimationPath::builtin("ALTSACR.DEF"),
CGI->generaltexth->zelp[585], [this]() {CAltarArtifacts::makeDeal(); }, EShortcut::MARKET_DEAL);
@ -51,7 +50,7 @@ CAltarArtifacts::CAltarArtifacts(const IMarket * market, const CGHeroInstance *
// Hero's artifacts
heroArts = std::make_shared<CArtifactsOfHeroAltar>(Point(-365, -11));
heroArts->setHero(hero);
heroArts->altarId = altarObj->id;
heroArts->altarId = market->getObjInstanceID();
// Altar
offerTradePanel = std::make_shared<ArtifactsAltarPanel>([this](const std::shared_ptr<CTradeableItem> & altarSlot)
@ -104,7 +103,7 @@ void CAltarArtifacts::makeDeal()
{
positions.push_back(artInst->getId());
}
LOCPLINT->cb->trade(market, EMarketMode::ARTIFACT_EXP, positions, std::vector<TradeItemBuy>(), std::vector<ui32>(), hero);
LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::ARTIFACT_EXP, positions, std::vector<TradeItemBuy>(), std::vector<ui32>(), hero);
deselect();
}
@ -125,7 +124,7 @@ std::shared_ptr<CArtifactsOfHeroAltar> CAltarArtifacts::getAOHset() const
void CAltarArtifacts::updateAltarSlots()
{
assert(altarArtifacts->artifactsInBackpack.size() <= GameConstants::ALTAR_ARTIFACTS_SLOTS);
assert(altarArtifactsStorage->artifactsInBackpack.size() <= GameConstants::ALTAR_ARTIFACTS_SLOTS);
assert(tradeSlotsMap.size() <= GameConstants::ALTAR_ARTIFACTS_SLOTS);
auto tradeSlotsMapNewArts = tradeSlotsMap;
@ -146,12 +145,12 @@ void CAltarArtifacts::updateAltarSlots()
for(auto & tradeSlot : tradeSlotsMapNewArts)
{
assert(tradeSlot.first->id == -1);
assert(altarArtifacts->getArtPos(tradeSlot.second) != ArtifactPosition::PRE_FIRST);
assert(altarArtifactsStorage->getArtPos(tradeSlot.second) != ArtifactPosition::PRE_FIRST);
tradeSlot.first->setID(tradeSlot.second->getTypeId().num);
tradeSlot.first->subtitle->setText(std::to_string(calcExpCost(tradeSlot.second->getTypeId())));
}
auto newArtsFromBulkMove = altarArtifacts->artifactsInBackpack;
auto newArtsFromBulkMove = altarArtifactsStorage->artifactsInBackpack;
for(const auto & [altarSlot, art] : tradeSlotsMap)
{
newArtsFromBulkMove.erase(std::remove_if(newArtsFromBulkMove.begin(), newArtsFromBulkMove.end(), [artForRemove = art](auto & slotInfo)
@ -179,7 +178,7 @@ void CAltarArtifacts::putBackArtifacts()
{
// TODO: If the backpack capacity limit is enabled, artifacts may remain on the altar.
// Perhaps should be erased in CGameHandler::objectVisitEnded if id of visited object will be available
if(!altarArtifacts->artifactsInBackpack.empty())
if(!altarArtifactsStorage->artifactsInBackpack.empty())
LOCPLINT->cb->bulkMoveArtifacts(heroArts->altarId, heroArts->getHero()->id, false, true, true);
}
@ -200,7 +199,7 @@ void CAltarArtifacts::onSlotClickPressed(const std::shared_ptr<CTradeableItem> &
if(const auto pickedArtInst = heroArts->getPickedArtifact())
{
if(pickedArtInst->canBePutAt(altarArtifacts))
if(pickedArtInst->canBePutAt(altarArtifactsStorage))
{
if(pickedArtInst->artType->isTradable())
{
@ -221,7 +220,7 @@ void CAltarArtifacts::onSlotClickPressed(const std::shared_ptr<CTradeableItem> &
else if(altarSlot->id != -1)
{
assert(tradeSlotsMap.at(altarSlot));
const auto slot = altarArtifacts->getArtPos(tradeSlotsMap.at(altarSlot));
const auto slot = altarArtifactsStorage->getArtPos(tradeSlotsMap.at(altarSlot));
assert(slot != ArtifactPosition::PRE_FIRST);
LOCPLINT->cb->swapArtifacts(ArtifactLocation(heroArts->altarId, slot),
ArtifactLocation(hero->id, GH.isKeyboardCtrlDown() ? ArtifactPosition::FIRST_AVAILABLE : ArtifactPosition::TRANSITION_POS));

View File

@ -26,7 +26,7 @@ public:
void putBackArtifacts();
private:
const CArtifactSet * altarArtifacts;
const CArtifactSet * altarArtifactsStorage;
std::shared_ptr<CButton> sacrificeBackpackButton;
std::shared_ptr<CArtifactsOfHeroAltar> heroArts;
std::map<std::shared_ptr<CTradeableItem>, const CArtifactInstance*> tradeSlotsMap;

View File

@ -23,7 +23,7 @@
#include "../../../lib/texts/CGeneralTextHandler.h"
#include "../../../lib/mapObjects/CGHeroInstance.h"
#include "../../../lib/mapObjects/CGMarket.h"
#include "../../../lib/mapObjects/IMarket.h"
CAltarCreatures::CAltarCreatures(const IMarket * market, const CGHeroInstance * hero)
: CMarketBase(market, hero)
@ -157,7 +157,7 @@ void CAltarCreatures::makeDeal()
}
}
LOCPLINT->cb->trade(market, EMarketMode::CREATURE_EXP, ids, {}, toSacrifice, hero);
LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::CREATURE_EXP, ids, {}, toSacrifice, hero);
for(int & units : unitsOnAltar)
units = 0;

View File

@ -11,7 +11,6 @@
#include "StdInc.h"
#include "CArtifactsBuying.h"
#include "../../gui/CGuiHandler.h"
#include "../../gui/Shortcut.h"
#include "../../widgets/Buttons.h"
#include "../../widgets/TextControls.h"
@ -21,24 +20,16 @@
#include "../../../CCallback.h"
#include "../../../lib/entities/building/CBuilding.h"
#include "../../../lib/entities/faction/CTownHandler.h"
#include "../../../lib/mapObjects/CGHeroInstance.h"
#include "../../../lib/mapObjects/CGMarket.h"
#include "../../../lib/mapObjects/CGTownInstance.h"
#include "../../../lib/mapObjects/IMarket.h"
#include "../../../lib/texts/CGeneralTextHandler.h"
CArtifactsBuying::CArtifactsBuying(const IMarket * market, const CGHeroInstance * hero)
CArtifactsBuying::CArtifactsBuying(const IMarket * market, const CGHeroInstance * hero, const std::string & title)
: CMarketBase(market, hero)
, CResourcesSelling([this](const std::shared_ptr<CTradeableItem> & heroSlot){CArtifactsBuying::onSlotClickPressed(heroSlot, bidTradePanel);})
{
OBJECT_CONSTRUCTION;
std::string title;
if(auto townMarket = dynamic_cast<const CGTownInstance*>(market))
title = (*CGI->townh)[townMarket->getFaction()]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->getNameTranslated();
else
title = CGI->generaltexth->allTexts[349];
labels.emplace_back(std::make_shared<CLabel>(titlePos.x, titlePos.y, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, title));
deal = std::make_shared<CButton>(dealButtonPos, AnimationPath::builtin("TPMRKB.DEF"),
CGI->generaltexth->zelp[595], [this](){CArtifactsBuying::makeDeal();}, EShortcut::MARKET_DEAL);
@ -77,7 +68,7 @@ void CArtifactsBuying::makeDeal()
{
if(ArtifactID(offerTradePanel->getSelectedItemId()).toArtifact()->canBePutAt(hero))
{
LOCPLINT->cb->trade(market, EMarketMode::RESOURCE_ARTIFACT, GameResID(bidTradePanel->getSelectedItemId()),
LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_ARTIFACT, GameResID(bidTradePanel->getSelectedItemId()),
ArtifactID(offerTradePanel->getSelectedItemId()), offerQty, hero);
CMarketTraderText::makeDeal();
deselect();

View File

@ -14,7 +14,7 @@
class CArtifactsBuying : public CResourcesSelling, public CMarketTraderText
{
public:
CArtifactsBuying(const IMarket * market, const CGHeroInstance * hero);
CArtifactsBuying(const IMarket * market, const CGHeroInstance * hero, const std::string & title);
void deselect() override;
void makeDeal() override;

View File

@ -22,14 +22,11 @@
#include "../../../CCallback.h"
#include "../../../lib/CArtifactInstance.h"
#include "../../../lib/entities/building/CBuilding.h"
#include "../../../lib/entities/faction/CTownHandler.h"
#include "../../../lib/mapObjects/CGHeroInstance.h"
#include "../../../lib/mapObjects/CGMarket.h"
#include "../../../lib/mapObjects/CGTownInstance.h"
#include "../../../lib/mapObjects/IMarket.h"
#include "../../../lib/texts/CGeneralTextHandler.h"
CArtifactsSelling::CArtifactsSelling(const IMarket * market, const CGHeroInstance * hero)
CArtifactsSelling::CArtifactsSelling(const IMarket * market, const CGHeroInstance * hero, const std::string & title)
: CMarketBase(market, hero)
, CResourcesBuying(
[this](const std::shared_ptr<CTradeableItem> & resSlot){CArtifactsSelling::onSlotClickPressed(resSlot, offerTradePanel);},
@ -37,12 +34,6 @@ CArtifactsSelling::CArtifactsSelling(const IMarket * market, const CGHeroInstanc
{
OBJECT_CONSTRUCTION;
std::string title;
if(const auto townMarket = dynamic_cast<const CGTownInstance*>(market))
title = (*CGI->townh)[townMarket->getFaction()]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->getNameTranslated();
else if(const auto mapMarket = dynamic_cast<const CGMarket*>(market))
title = mapMarket->title;
labels.emplace_back(std::make_shared<CLabel>(titlePos.x, titlePos.y, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, title));
labels.push_back(std::make_shared<CLabel>(155, 56, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, boost::str(boost::format(CGI->generaltexth->allTexts[271]) % hero->getNameTranslated())));
deal = std::make_shared<CButton>(dealButtonPos, AnimationPath::builtin("TPMRKB.DEF"),
@ -87,7 +78,8 @@ void CArtifactsSelling::makeDeal()
{
const auto art = hero->getArt(selectedHeroSlot);
assert(art);
LOCPLINT->cb->trade(market, EMarketMode::ARTIFACT_RESOURCE, art->getId(), GameResID(offerTradePanel->getSelectedItemId()), offerQty, hero);
LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::ARTIFACT_RESOURCE, art->getId(),
GameResID(offerTradePanel->getSelectedItemId()), offerQty, hero);
CMarketTraderText::makeDeal();
}

View File

@ -15,7 +15,7 @@
class CArtifactsSelling : public CResourcesBuying, public CMarketTraderText
{
public:
CArtifactsSelling(const IMarket * market, const CGHeroInstance * hero);
CArtifactsSelling(const IMarket * market, const CGHeroInstance * hero, const std::string & title);
void deselect() override;
void makeDeal() override;
void updateShowcases() override;

View File

@ -23,7 +23,7 @@
#include "../../../lib/texts/CGeneralTextHandler.h"
#include "../../../lib/mapObjects/CGHeroInstance.h"
#include "../../../lib/mapObjects/CGMarket.h"
#include "../../../lib/mapObjects/IMarket.h"
CFreelancerGuild::CFreelancerGuild(const IMarket * market, const CGHeroInstance * hero)
: CMarketBase(market, hero)
@ -69,7 +69,7 @@ void CFreelancerGuild::makeDeal()
{
if(auto toTrade = offerSlider->getValue(); toTrade != 0)
{
LOCPLINT->cb->trade(market, EMarketMode::CREATURE_RESOURCE, SlotID(bidTradePanel->highlightedSlot->serial), GameResID(offerTradePanel->getSelectedItemId()), bidQty * toTrade, hero);
LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::CREATURE_RESOURCE, SlotID(bidTradePanel->highlightedSlot->serial), GameResID(offerTradePanel->getSelectedItemId()), bidQty * toTrade, hero);
CMarketTraderText::makeDeal();
deselect();
}

View File

@ -22,7 +22,7 @@
#include "../../../CCallback.h"
#include "../../../lib/texts/CGeneralTextHandler.h"
#include "../../../lib/mapObjects/CGMarket.h"
#include "../../../lib/mapObjects/IMarket.h"
CMarketResources::CMarketResources(const IMarket * market, const CGHeroInstance * hero)
: CMarketBase(market, hero)
@ -60,7 +60,7 @@ void CMarketResources::makeDeal()
{
if(auto toTrade = offerSlider->getValue(); toTrade != 0)
{
LOCPLINT->cb->trade(market, EMarketMode::RESOURCE_RESOURCE, GameResID(bidTradePanel->getSelectedItemId()),
LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, GameResID(bidTradePanel->getSelectedItemId()),
GameResID(offerTradePanel->highlightedSlot->id), bidQty * toTrade, hero);
CMarketTraderText::makeDeal();
deselect();

View File

@ -22,6 +22,7 @@
#include "../../../CCallback.h"
#include "../../../lib/texts/CGeneralTextHandler.h"
#include "../../../lib/mapObjects/IMarket.h"
#include "../../../lib/texts/MetaString.h"
CTransferResources::CTransferResources(const IMarket * market, const CGHeroInstance * hero)
@ -63,7 +64,7 @@ void CTransferResources::makeDeal()
{
if(auto toTrade = offerSlider->getValue(); toTrade != 0)
{
LOCPLINT->cb->trade(market, EMarketMode::RESOURCE_PLAYER, GameResID(bidTradePanel->getSelectedItemId()),
LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_PLAYER, GameResID(bidTradePanel->getSelectedItemId()),
PlayerColor(offerTradePanel->getSelectedItemId()), toTrade, hero);
CMarketTraderText::makeDeal();
deselect();

View File

@ -145,7 +145,7 @@ void CBuildingRect::clickPressed(const Point & cursorPosition)
if(getBuilding() && area && (parent->selectedBuilding==this))
{
auto building = getBuilding();
parent->buildingClicked(building->bid, building->subId, building->upgrade);
parent->buildingClicked(building->bid);
}
}
@ -586,9 +586,9 @@ void CCastleBuildings::recreate()
//Generate buildings list
auto buildingsCopy = town->builtBuildings;// a bit modified copy of built buildings
auto buildingsCopy = town->getBuildings();// a bit modified copy of built buildings
if(vstd::contains(town->builtBuildings, BuildingID::SHIPYARD))
if(town->hasBuilt(BuildingID::SHIPYARD))
{
auto bayPos = town->bestLocation();
if(!bayPos.valid())
@ -681,18 +681,76 @@ const CGHeroInstance * CCastleBuildings::getHero()
return town->garrisonHero;
}
void CCastleBuildings::buildingClicked(BuildingID building, BuildingSubID::EBuildingSubID subID, BuildingID upgrades)
void CCastleBuildings::buildingClicked(BuildingID building)
{
logGlobal->trace("You've clicked on %d", (int)building.toEnum());
const CBuilding *b = town->town->buildings.find(building)->second;
if (building >= BuildingID::DWELL_FIRST)
BuildingID buildingToEnter = building;
for(;;)
{
enterDwelling((BuildingID::getLevelFromDwelling(building)));
const CBuilding *b = town->town->buildings.find(buildingToEnter)->second;
if (buildingTryActivateCustomUI(buildingToEnter, building))
return;
if (!b->upgrade.hasValue())
{
enterBuilding(building);
return;
}
buildingToEnter = b->upgrade;
}
}
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);
if (town->getWarMachineInBuilding(buildingToTest).hasValue())
{
enterBlacksmith(buildingTarget, town->getWarMachineInBuilding(buildingToTest));
return true;
}
if (!b->marketModes.empty())
{
switch (*b->marketModes.begin())
{
case EMarketMode::CREATURE_UNDEAD:
GH.windows().createAndPushWindow<CTransformerWindow>(town, getHero(), nullptr);
return true;
case EMarketMode::RESOURCE_SKILL:
if (getHero())
GH.windows().createAndPushWindow<CUniversityWindow>(getHero(), buildingTarget, town, nullptr);
return true;
case EMarketMode::RESOURCE_RESOURCE:
// can't use allied marketplace
if (town->getOwner() == LOCPLINT->playerID)
{
GH.windows().createAndPushWindow<CMarketWindow>(town, getHero(), nullptr, *b->marketModes.begin());
return true;
}
else
return false;
default:
if(getHero())
GH.windows().createAndPushWindow<CMarketWindow>(town, getHero(), nullptr, *b->marketModes.begin());
else
LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[273]) % b->getNameTranslated())); //Only visiting heroes may use the %s.
return true;
}
}
if (buildingToTest >= BuildingID::DWELL_FIRST)
{
enterDwelling((BuildingID::getLevelFromDwelling(buildingToTest)));
return true;
}
else
{
switch(building)
switch(buildingToTest)
{
case BuildingID::MAGES_GUILD_1:
case BuildingID::MAGES_GUILD_2:
@ -700,139 +758,91 @@ void CCastleBuildings::buildingClicked(BuildingID building, BuildingSubID::EBuil
case BuildingID::MAGES_GUILD_4:
case BuildingID::MAGES_GUILD_5:
enterMagesGuild();
break;
return true;
case BuildingID::TAVERN:
LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE);
break;
return true;
case BuildingID::SHIPYARD:
if(town->shipyardStatus() == IBoatGenerator::GOOD)
{
LOCPLINT->showShipyardDialog(town);
return true;
}
else if(town->shipyardStatus() == IBoatGenerator::BOAT_ALREADY_BUILT)
{
LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[51]);
break;
return true;
}
return false;
case BuildingID::FORT:
case BuildingID::CITADEL:
case BuildingID::CASTLE:
GH.windows().createAndPushWindow<CFortScreen>(town);
break;
return true;
case BuildingID::VILLAGE_HALL:
case BuildingID::CITY_HALL:
case BuildingID::TOWN_HALL:
case BuildingID::CAPITOL:
enterTownHall();
break;
case BuildingID::MARKETPLACE:
// can't use allied marketplace
if (town->getOwner() == LOCPLINT->playerID)
GH.windows().createAndPushWindow<CMarketWindow>(town, town->visitingHero, nullptr, EMarketMode::RESOURCE_RESOURCE);
else
enterBuilding(building);
break;
case BuildingID::BLACKSMITH:
enterBlacksmith(town->town->warMachine);
break;
return true;
case BuildingID::SHIP:
LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[51]); //Cannot build another boat
break;
return true;
case BuildingID::SPECIAL_1:
case BuildingID::SPECIAL_2:
case BuildingID::SPECIAL_3:
case BuildingID::SPECIAL_4:
switch (subID)
switch (b->subId)
{
case BuildingSubID::NONE:
enterBuilding(building);
break;
case BuildingSubID::MYSTIC_POND:
enterFountain(building, subID, upgrades);
break;
case BuildingSubID::ARTIFACT_MERCHANT:
if(town->visitingHero)
GH.windows().createAndPushWindow<CMarketWindow>(town, town->visitingHero, nullptr, EMarketMode::RESOURCE_ARTIFACT);
else
LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[273]) % b->getNameTranslated())); //Only visiting heroes may use the %s.
break;
case BuildingSubID::FOUNTAIN_OF_FORTUNE:
enterFountain(building, subID, upgrades);
break;
case BuildingSubID::FREELANCERS_GUILD:
if(getHero())
GH.windows().createAndPushWindow<CMarketWindow>(town, getHero(), nullptr, EMarketMode::CREATURE_RESOURCE);
else
LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[273]) % b->getNameTranslated())); //Only visiting heroes may use the %s.
break;
case BuildingSubID::MAGIC_UNIVERSITY:
if (getHero())
GH.windows().createAndPushWindow<CUniversityWindow>(getHero(), town, nullptr);
else
enterBuilding(building);
break;
enterFountain(buildingToTest, b->subId, buildingTarget);
return true;
case BuildingSubID::CASTLE_GATE:
if (LOCPLINT->makingTurn)
{
enterCastleGate();
else
enterBuilding(building);
break;
case BuildingSubID::CREATURE_TRANSFORMER: //Skeleton Transformer
GH.windows().createAndPushWindow<CTransformerWindow>(town, getHero(), nullptr);
break;
return true;
}
return false;
case BuildingSubID::PORTAL_OF_SUMMONING:
if (town->creatures[town->town->creatures.size()].second.empty())//No creatures
LOCPLINT->showInfoDialog(CGI->generaltexth->tcommands[30]);
else
enterDwelling(town->town->creatures.size());
break;
case BuildingSubID::BALLISTA_YARD:
enterBlacksmith(ArtifactID::BALLISTA);
break;
case BuildingSubID::THIEVES_GUILD:
enterAnyThievesGuild();
break;
return true;
case BuildingSubID::BANK:
enterBank();
break;
default:
if(upgrades == BuildingID::TAVERN)
LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE);
else
enterBuilding(building);
break;
return true;
}
break;
default:
enterBuilding(building);
break;
}
}
for (auto const & bonus : b->buildingBonuses)
{
if (bonus->type == BonusType::THIEVES_GUILD_ACCESS)
{
enterAnyThievesGuild();
return true;
}
}
return false;
}
void CCastleBuildings::enterBlacksmith(ArtifactID artifactID)
void CCastleBuildings::enterBlacksmith(BuildingID building, ArtifactID artifactID)
{
const CGHeroInstance *hero = town->visitingHero;
if(!hero)
{
LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[273]) % town->town->buildings.find(BuildingID::BLACKSMITH)->second->getNameTranslated()));
LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[273]) % town->town->buildings.find(building)->second->getNameTranslated()));
return;
}
auto art = artifactID.toArtifact();
@ -843,7 +853,7 @@ void CCastleBuildings::enterBlacksmith(ArtifactID artifactID)
{
for(auto slot : art->getPossibleSlots().at(ArtBearer::HERO))
{
if(hero->getArt(slot) == nullptr)
if(hero->getArt(slot) == nullptr || hero->getArt(slot)->getTypeId() != artifactID)
{
possible = true;
break;
@ -854,8 +864,9 @@ void CCastleBuildings::enterBlacksmith(ArtifactID artifactID)
}
}
}
CreatureID cre = art->getWarMachine();
GH.windows().createAndPushWindow<CBlacksmithDialog>(possible, cre, artifactID, hero->id);
CreatureID creatureID = artifactID.toArtifact()->getWarMachine();
GH.windows().createAndPushWindow<CBlacksmithDialog>(possible, creatureID, artifactID, hero->id);
}
void CCastleBuildings::enterBuilding(BuildingID building)
@ -996,7 +1007,7 @@ void CCastleBuildings::enterMagesGuild()
void CCastleBuildings::enterTownHall()
{
if(town->visitingHero && town->visitingHero->hasArt(ArtifactID::GRAIL) &&
!vstd::contains(town->builtBuildings, BuildingID::GRAIL)) //hero has grail, but town does not have it
!town->hasBuilt(BuildingID::GRAIL)) //hero has grail, but town does not have it
{
if(!vstd::contains(town->forbiddenBuildings, BuildingID::GRAIL))
{
@ -1033,7 +1044,7 @@ void CCastleBuildings::enterAnyThievesGuild()
std::vector<const CGTownInstance*> towns = LOCPLINT->cb->getTownsInfo(true);
for(auto & ownedTown : towns)
{
if(ownedTown->builtBuildings.count(BuildingID::TAVERN))
if(ownedTown->hasBuilt(BuildingID::TAVERN))
{
LOCPLINT->showThievesGuildWindow(ownedTown);
return;
@ -1059,7 +1070,7 @@ void CCastleBuildings::enterBank()
void CCastleBuildings::enterAnyMarket()
{
if(town->builtBuildings.count(BuildingID::MARKETPLACE))
if(town->hasBuilt(BuildingID::MARKETPLACE))
{
GH.windows().createAndPushWindow<CMarketWindow>(town, nullptr, nullptr, EMarketMode::RESOURCE_RESOURCE);
return;
@ -1068,7 +1079,7 @@ void CCastleBuildings::enterAnyMarket()
std::vector<const CGTownInstance*> towns = LOCPLINT->cb->getTownsInfo(true);
for(auto & town : towns)
{
if(town->builtBuildings.count(BuildingID::MARKETPLACE))
if(town->hasBuilt(BuildingID::MARKETPLACE))
{
GH.windows().createAndPushWindow<CMarketWindow>(town, nullptr, nullptr, EMarketMode::RESOURCE_RESOURCE);
return;
@ -1385,7 +1396,7 @@ void CCastleInterface::recreateIcons()
fastMarket = std::make_shared<LRClickableArea>(Rect(163, 410, 64, 42), [this]() { builds->enterAnyMarket(); });
fastTavern = std::make_shared<LRClickableArea>(Rect(15, 387, 58, 64), [&]()
{
if(town->builtBuildings.count(BuildingID::TAVERN))
if(town->hasBuilt(BuildingID::TAVERN))
LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE);
}, [this]{
if(!town->town->faction->getDescriptionTranslated().empty())
@ -1563,7 +1574,7 @@ CHallInterface::CHallInterface(const CGTownInstance * Town):
}
const CBuilding * current = town->town->buildings.at(buildingID);
if(vstd::contains(town->builtBuildings, buildingID))
if(town->hasBuilt(buildingID))
{
building = current;
}
@ -1776,7 +1787,7 @@ CFortScreen::CFortScreen(const CGTownInstance * town):
{
BuildingID dwelling = BuildingID::getDwellingFromLevel(i, 1);
if(vstd::contains(town->builtBuildings, dwelling))
if(town->hasBuilt(dwelling))
buildingID = BuildingID(BuildingID::getDwellingFromLevel(i, 1));
else
buildingID = BuildingID(BuildingID::getDwellingFromLevel(i, 0));
@ -1841,7 +1852,7 @@ CFortScreen::RecruitArea::RecruitArea(int posX, int posY, const CGTownInstance *
buildingIcon = std::make_shared<CAnimImage>(town->town->clientInfo.buildingsIcons, getMyBuilding()->bid, 0, 4, 21);
buildingName = std::make_shared<CLabel>(78, 101, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, getMyBuilding()->getNameTranslated(), 152);
if(vstd::contains(town->builtBuildings, getMyBuilding()->bid))
if(town->hasBuilt(getMyBuilding()->bid))
{
ui32 available = town->creatures[level].first;
std::string availableText = CGI->generaltexth->allTexts[217]+ std::to_string(available);

View File

@ -150,7 +150,7 @@ class CCastleBuildings : public CIntObject
const CGHeroInstance* getHero();//Select hero for buildings usage
void enterBlacksmith(ArtifactID artifactID);//support for blacksmith + ballista yard
void enterBlacksmith(BuildingID building, ArtifactID artifactID);//support for blacksmith + ballista yard
void enterBuilding(BuildingID building);//for buildings with simple description + pic left-click messages
void enterCastleGate();
void enterFountain(const BuildingID & building, BuildingSubID::EBuildingSubID subID, BuildingID upgrades);//Rampart's fountains
@ -173,7 +173,8 @@ public:
void enterBank();
void enterToTheQuickRecruitmentWindow();
void buildingClicked(BuildingID building, BuildingSubID::EBuildingSubID subID = BuildingSubID::NONE, BuildingID upgrades = BuildingID::NONE);
bool buildingTryActivateCustomUI(BuildingID buildingToTest, BuildingID buildingTarget);
void buildingClicked(BuildingID building);
void addBuilding(BuildingID building);
void removeBuilding(BuildingID building);//FIXME: not tested!!!
};

View File

@ -619,6 +619,7 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s
parent->stackArtifactIcon = std::make_shared<CAnimImage>(AnimationPath::builtin("ARTIFACT"), art->artType->getIconIndex(), 0, pos.x, pos.y);
parent->stackArtifactHelp = std::make_shared<LRClickableAreaWTextComp>(Rect(pos, Point(44, 44)), ComponentType::ARTIFACT);
parent->stackArtifactHelp->component.subType = art->artType->getId();
parent->stackArtifactHelp->text = art->getDescription();
if(parent->info->owner)
{

View File

@ -585,9 +585,7 @@ void CKingdomInterface::generateMinesList(const std::vector<const CGObjectInstan
const CGMine * mine = dynamic_cast<const CGMine *>(object);
assert(mine);
minesCount[mine->producedResource]++;
if (mine->producedResource == EGameResID::GOLD)
totalIncome += mine->getProducedQuantity();
totalIncome += mine->dailyIncome()[EGameResID::GOLD];
}
}
@ -596,7 +594,7 @@ void CKingdomInterface::generateMinesList(const std::vector<const CGObjectInstan
auto * playerSettings = LOCPLINT->cb->getPlayerSettings(LOCPLINT->playerID);
for(auto & hero : heroes)
{
totalIncome += hero->valOfBonuses(Selector::typeSubtype(BonusType::GENERATE_RESOURCE, BonusSubtypeID(GameResID(EGameResID::GOLD)))) * playerSettings->handicap.percentIncome / 100;
totalIncome += hero->dailyIncome()[EGameResID::GOLD];
}
//Add town income of all towns
@ -822,7 +820,7 @@ CTownItem::CTownItem(const CGTownInstance * Town)
fastTavern = std::make_shared<LRClickableArea>(Rect(5, 6, 58, 64), [&]()
{
if(town->builtBuildings.count(BuildingID::TAVERN))
if(town->hasBuilt(BuildingID::TAVERN))
LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE);
}, [&]{
if(!town->town->faction->getDescriptionTranslated().empty())
@ -833,7 +831,7 @@ CTownItem::CTownItem(const CGTownInstance * Town)
std::vector<const CGTownInstance*> towns = LOCPLINT->cb->getTownsInfo(true);
for(auto & town : towns)
{
if(town->builtBuildings.count(BuildingID::MARKETPLACE))
if(town->hasBuilt(BuildingID::MARKETPLACE))
{
GH.windows().createAndPushWindow<CMarketWindow>(town, nullptr, nullptr, EMarketMode::RESOURCE_RESOURCE);
return;

View File

@ -27,11 +27,14 @@
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../../lib/entities/building/CBuilding.h"
#include "../../lib/texts/CGeneralTextHandler.h"
#include "../../lib/mapObjects/CGTownInstance.h"
#include "../../lib/mapObjects/CGMarket.h"
#include "../../lib/mapObjects/CGHeroInstance.h"
#include "../../CCallback.h"
CMarketWindow::CMarketWindow(const IMarket * market, const CGHeroInstance * hero, const std::function<void()> & onWindowClosed, EMarketMode mode)
: CWindowObject(PLAYER_COLORED)
, windowClosedCallback(onWindowClosed)
@ -111,6 +114,11 @@ void CMarketWindow::createChangeModeButtons(EMarketMode currentMode, const IMark
if(!market->allowsTrade(modeButton))
return false;
if(currentMode == EMarketMode::ARTIFACT_EXP && modeButton != EMarketMode::CREATURE_EXP)
return false;
if(currentMode == EMarketMode::CREATURE_EXP && modeButton != EMarketMode::ARTIFACT_EXP)
return false;
if(modeButton == EMarketMode::RESOURCE_RESOURCE || modeButton == EMarketMode::RESOURCE_PLAYER)
{
if(const auto town = dynamic_cast<const CGTownInstance*>(market))
@ -175,12 +183,28 @@ void CMarketWindow::initWidgetInternals(const EMarketMode mode, const std::pair<
redraw();
}
std::string CMarketWindow::getMarketTitle(const ObjectInstanceID marketId, const EMarketMode mode) const
{
assert(LOCPLINT->cb->getMarket(marketId));
assert(vstd::contains(LOCPLINT->cb->getMarket(marketId)->availableModes(), mode));
if(const auto town = LOCPLINT->cb->getTown(marketId))
{
for(const auto & buildingId : town->getBuildings())
{
if(const auto building = town->town->buildings.at(buildingId); vstd::contains(building->marketModes, mode))
return building->getNameTranslated();
}
}
return LOCPLINT->cb->getObj(marketId)->getObjectName();
}
void CMarketWindow::createArtifactsBuying(const IMarket * market, const CGHeroInstance * hero)
{
OBJECT_CONSTRUCTION;
background = createBg(ImagePath::builtin("TPMRKABS.bmp"), PLAYER_COLORED);
marketWidget = std::make_shared<CArtifactsBuying>(market, hero);
marketWidget = std::make_shared<CArtifactsBuying>(market, hero, getMarketTitle(market->getObjInstanceID(), EMarketMode::RESOURCE_ARTIFACT));
initWidgetInternals(EMarketMode::RESOURCE_ARTIFACT, CGI->generaltexth->zelp[600]);
}
@ -192,13 +216,13 @@ void CMarketWindow::createArtifactsSelling(const IMarket * market, const CGHeroI
// Create image that copies part of background containing slot MISC_1 into position of slot MISC_5
artSlotBack = std::make_shared<CPicture>(background->getSurface(), Rect(20, 187, 47, 47), 0, 0);
artSlotBack->moveTo(pos.topLeft() + Point(18, 339));
auto artsSellingMarket = std::make_shared<CArtifactsSelling>(market, hero);
auto artsSellingMarket = std::make_shared<CArtifactsSelling>(market, hero, getMarketTitle(market->getObjInstanceID(), EMarketMode::ARTIFACT_RESOURCE));
artSets.clear();
const auto heroArts = artsSellingMarket->getAOHset();
heroArts->showPopupCallback = [this, heroArts](CArtPlace & artPlace, const Point & cursorPosition){showArifactInfo(*heroArts, artPlace, cursorPosition);};
addSet(heroArts);
marketWidget = artsSellingMarket;
initWidgetInternals(EMarketMode::ARTIFACT_RESOURCE, CGI->generaltexth->zelp[600]);
initWidgetInternals(EMarketMode::ARTIFACT_RESOURCE, CGI->generaltexth->zelp[600]);
}
void CMarketWindow::createMarketResources(const IMarket * market, const CGHeroInstance * hero)
@ -233,10 +257,10 @@ void CMarketWindow::createAltarArtifacts(const IMarket * market, const CGHeroIns
OBJECT_CONSTRUCTION;
background = createBg(ImagePath::builtin("ALTRART2.bmp"), PLAYER_COLORED);
auto altarArtifacts = std::make_shared<CAltarArtifacts>(market, hero);
marketWidget = altarArtifacts;
auto altarArtifactsStorage = std::make_shared<CAltarArtifacts>(market, hero);
marketWidget = altarArtifactsStorage;
artSets.clear();
const auto heroArts = altarArtifacts->getAOHset();
const auto heroArts = altarArtifactsStorage->getAOHset();
heroArts->clickPressedCallback = [this, heroArts](const CArtPlace & artPlace, const Point & cursorPosition)
{
clickPressedOnArtPlace(heroArts->getHero(), artPlace.slot, true, true, false);
@ -252,7 +276,7 @@ void CMarketWindow::createAltarArtifacts(const IMarket * market, const CGHeroIns
addSet(heroArts);
initWidgetInternals(EMarketMode::ARTIFACT_EXP, CGI->generaltexth->zelp[568]);
updateExperience();
quitButton->addCallback([altarArtifacts](){altarArtifacts->putBackArtifacts();});
quitButton->addCallback([altarArtifactsStorage](){altarArtifactsStorage->putBackArtifacts();});
}
void CMarketWindow::createAltarCreatures(const IMarket * market, const CGHeroInstance * hero)

View File

@ -27,6 +27,7 @@ public:
private:
void createChangeModeButtons(EMarketMode currentMode, const IMarket * market, const CGHeroInstance * hero);
void initWidgetInternals(const EMarketMode mode, const std::pair<std::string, std::string> & quitButtonHelpContainer);
std::string getMarketTitle(const ObjectInstanceID marketId, const EMarketMode mode) const;
void createArtifactsBuying(const IMarket * market, const CGHeroInstance * hero);
void createArtifactsSelling(const IMarket * market, const CGHeroInstance * hero);

View File

@ -819,7 +819,7 @@ void CTransformerWindow::makeDeal()
for(auto & elem : items)
{
if(!elem->left)
LOCPLINT->cb->trade(market, EMarketMode::CREATURE_UNDEAD, SlotID(elem->id), {}, {}, hero);
LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::CREATURE_UNDEAD, SlotID(elem->id), {}, {}, hero);
}
}
@ -946,7 +946,7 @@ void CUniversityWindow::CItem::hover(bool on)
GH.statusbar()->clear();
}
CUniversityWindow::CUniversityWindow(const CGHeroInstance * _hero, const IMarket * _market, const std::function<void()> & onWindowClosed)
CUniversityWindow::CUniversityWindow(const CGHeroInstance * _hero, BuildingID building, const IMarket * _market, const std::function<void()> & onWindowClosed)
: CWindowObject(PLAYER_COLORED, ImagePath::builtin("UNIVERS1")),
hero(_hero),
onWindowClosed(onWindowClosed),
@ -961,8 +961,7 @@ CUniversityWindow::CUniversityWindow(const CGHeroInstance * _hero, const IMarket
if(auto town = dynamic_cast<const CGTownInstance *>(_market))
{
auto faction = town->town->faction->getId();
auto bid = town->town->getSpecialBuilding(BuildingSubID::MAGIC_UNIVERSITY)->bid;
titlePic = std::make_shared<CAnimImage>((*CGI->townh)[faction]->town->clientInfo.buildingsIcons, bid);
titlePic = std::make_shared<CAnimImage>((*CGI->townh)[faction]->town->clientInfo.buildingsIcons, building);
}
else if(auto uni = dynamic_cast<const CGUniversity *>(_market); uni->appearance)
{
@ -1005,7 +1004,7 @@ void CUniversityWindow::updateSecondarySkills()
void CUniversityWindow::makeDeal(SecondarySkill skill)
{
LOCPLINT->cb->trade(market, EMarketMode::RESOURCE_SKILL, GameResID(GameResID::GOLD), skill, 1, hero);
LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_SKILL, GameResID(GameResID::GOLD), skill, 1, hero);
}
CUnivConfirmWindow::CUnivConfirmWindow(CUniversityWindow * owner_, SecondarySkill SKILL, bool available)

View File

@ -391,7 +391,7 @@ class CUniversityWindow final : public CStatusbarWindow, public IMarketHolder
std::function<void()> onWindowClosed;
public:
CUniversityWindow(const CGHeroInstance * _hero, const IMarket * _market, const std::function<void()> & onWindowClosed);
CUniversityWindow(const CGHeroInstance * _hero, BuildingID building, const IMarket * _market, const std::function<void()> & onWindowClosed);
void makeDeal(SecondarySkill skill);
void close() override;

View File

@ -591,6 +591,15 @@
{
"icon": "zvs/Lib1.res/MEGABREATH"
}
},
"DISINTEGRATE":
{
"graphics":
{
"icon": "zvs/Lib1.res/DISINTEGRATE"
}
}
}

View File

@ -10,6 +10,11 @@
{
"type": "MORALE",
"val": 1
},
{
"propagator": "PLAYER_PROPAGATOR",
"type": "THIEVES_GUILD_ACCESS",
"val": 1
}
]
},
@ -43,7 +48,10 @@
"produce": { "gold": 4000 }
},
"marketplace": { "id" : 14 },
"marketplace": {
"id" : 14,
"marketModes" : ["resource-resource", "resource-player"]
},
"resourceSilo": { "id" : 15, "requires" : [ "marketplace" ] },
"blacksmith": { "id" : 16 },
@ -198,5 +206,39 @@
}
]
}
},
// Section 3 - markets
"artifactMerchant" : {
"requires" : [ "marketplace" ],
"marketModes" : ["resource-artifact", "artifact-resource"]
},
"freelancersGuild" : {
"requires" : [ "marketplace" ],
"marketModes" : ["creature-resource"]
},
"magicUniversity" : {
"marketModes" : ["resource-skill"]
},
"creatureTransformer" : {
"marketModes" : ["creature-undead"]
},
// Section 4 - buildings that now have dedicated mechanics
"ballistaYard": {
"blacksmith" : "ballista"
},
"thievesGuild" : {
"bonuses": [
{
"propagator": "PLAYER_PROPAGATOR",
"type": "THIEVES_GUILD_ACCESS",
"val": 2
}
]
}
}

View File

@ -147,7 +147,6 @@
],
"horde" : [ 2, -1 ],
"mageGuild" : 4,
"warMachine" : "ballista",
"moatAbility" : "castleMoat",
// primaryResource not specified so town get both Wood and Ore for resource bonus
@ -168,7 +167,7 @@
"capitol": { },
"marketplace": { },
"resourceSilo": { "produce": { "ore": 1, "wood": 1 } },
"blacksmith": { },
"blacksmith": { "warMachine" : "ballista" },
"special1": {
"bonuses": [

View File

@ -152,7 +152,6 @@
"horde" : [ 0, -1 ],
"mageGuild" : 5,
"primaryResource" : "mercury",
"warMachine" : "ballista",
"moatAbility" : "castleMoat",
"buildings" :
@ -173,13 +172,13 @@
"capitol": { },
"marketplace": { },
"resourceSilo": { "produce": { "mercury": 1 } },
"blacksmith": { },
"blacksmith": { "warMachine" : "ballista" },
"special1": { "type" : "artifactMerchant", "requires" : [ "marketplace" ] },
"special1": { "requires" : [ "marketplace" ], "marketModes" : ["resource-artifact", "artifact-resource"] },
"horde1": { "id" : 18, "upgrades" : "dwellingLvl1" },
"horde1Upgr": { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" },
"ship": { "id" : 20, "upgrades" : "shipyard" },
"special2": { "type" : "magicUniversity", "requires" : [ "mageGuild1" ] },
"special2": { "requires" : [ "mageGuild1" ], "marketModes" : ["resource-skill"] },
"grail": { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }},
"extraTownHall": { "id" : 27, "requires" : [ "townHall" ], "mode" : "auto" },
"extraCityHall": { "id" : 28, "requires" : [ "cityHall" ], "mode" : "auto" },

View File

@ -148,10 +148,8 @@
"horde" : [ 0, -1 ],
"mageGuild" : 5,
"primaryResource" : "sulfur",
"warMachine" : "ballista",
"moatAbility" : "dungeonMoat",
"buildings" :
{
"mageGuild1": { },
@ -169,9 +167,9 @@
"capitol": { },
"marketplace": { },
"resourceSilo": { "produce": { "sulfur": 1 } },
"blacksmith": { },
"blacksmith": { "warMachine" : "ballista" },
"special1": { "type" : "artifactMerchant", "requires" : [ "marketplace" ] },
"special1": { "requires" : [ "marketplace" ], "marketModes" : ["resource-artifact", "artifact-resource"] },
"horde1": { "id" : 18, "upgrades" : "dwellingLvl1" },
"horde1Upgr": { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" },
"special2": {

View File

@ -147,7 +147,6 @@
],
"horde" : [ 0, -1 ],
"mageGuild" : 3,
"warMachine" : "firstAidTent",
"moatAbility" : "fortressMoat",
// primaryResource not specified so town get both Wood and Ore for resource bonus
@ -167,7 +166,7 @@
"capitol": { },
"marketplace": { },
"resourceSilo": { "produce": { "wood": 1, "ore": 1 } },
"blacksmith": { },
"blacksmith": { "warMachine" : "firstAidTent" },
"special1": {
"requires" : [ "allOf", [ "townHall" ], [ "special2" ] ],

View File

@ -149,7 +149,6 @@
"horde" : [ 0, 2 ],
"mageGuild" : 5,
"primaryResource" : "mercury",
"warMachine" : "ammoCart",
"moatAbility" : "infernoMoat",
"buildings" :
@ -169,7 +168,7 @@
"capitol": { },
"marketplace": { },
"resourceSilo": { "produce": { "mercury": 1 } },
"blacksmith": { },
"blacksmith": { "warMachine" : "ammoCart" },
"horde1": { "id" : 18, "upgrades" : "dwellingLvl1" },
"horde1Upgr": { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" },

View File

@ -152,7 +152,6 @@
],
"horde" : [ 0, -1 ],
"mageGuild" : 5,
"warMachine" : "firstAidTent",
"moatAbility" : "necropolisMoat",
// primaryResource not specified so town get both Wood and Ore for resource bonus
@ -174,7 +173,7 @@
"capitol": { },
"marketplace": { },
"resourceSilo": { "produce": { "ore": 1, "wood": 1 } },
"blacksmith": { },
"blacksmith": { "warMachine" : "firstAidTent" },
"special1": { "requires" : [ "fort" ], "bonuses": [ { "type": "DARKNESS", "val": 20 } ] },
"horde1": { "id" : 18, "upgrades" : "dwellingLvl1", "requires" : [ "special3" ] },
@ -182,7 +181,7 @@
"ship": { "id" : 20, "upgrades" : "shipyard" },
"special2": { "requires" : [ "mageGuild1" ],
"bonuses": [ { "type": "UNDEAD_RAISE_PERCENTAGE", "val": 10, "propagator": "PLAYER_PROPAGATOR" } ] },
"special3": { "type" : "creatureTransformer", "requires" : [ "dwellingLvl1" ] },
"special3": { "requires" : [ "dwellingLvl1" ], "marketModes" : ["creature-undead"] },
"grail": { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 },
"bonuses": [ { "type": "UNDEAD_RAISE_PERCENTAGE", "val": 20, "propagator": "PLAYER_PROPAGATOR" } ] },

View File

@ -152,7 +152,6 @@
"horde" : [ 1, 4 ],
"mageGuild" : 5,
"primaryResource" : "crystal",
"warMachine" : "firstAidTent",
"moatAbility" : "rampartMoat",
"buildings" :
@ -172,7 +171,7 @@
"capitol": { },
"marketplace": { },
"resourceSilo": { "produce": { "crystal": 1 } },
"blacksmith": { },
"blacksmith": { "warMachine" : "firstAidTent" },
"special1": { "type" : "mysticPond" },
"horde1": { "id" : 18, "upgrades" : "dwellingLvl2" },

View File

@ -145,7 +145,6 @@
],
"horde" : [ 0, -1 ],
"mageGuild" : 3,
"warMachine" : "ammoCart",
"moatAbility" : "strongholdMoat",
// primaryResource not specified so town get both Wood and Ore for resource bonus
@ -164,13 +163,13 @@
"capitol": { },
"marketplace": { },
"resourceSilo": { "produce": { "ore": 1, "wood": 1 } },
"blacksmith": { },
"blacksmith": { "warMachine" : "ammoCart" },
"special1": { "type" : "escapeTunnel", "requires" : [ "fort" ] },
"horde1": { "id" : 18, "upgrades" : "dwellingLvl1" },
"horde1Upgr": { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" },
"special2": { "type" : "freelancersGuild", "requires" : [ "marketplace" ] },
"special3": { "type" : "ballistaYard", "requires" : [ "blacksmith" ] },
"special2": { "requires" : [ "marketplace" ], "marketModes" : ["creature-resource"] },
"special3": { "warMachine" : "ballista", "requires" : [ "blacksmith" ] },
"special4": {
"requires" : [ "fort" ],
"configuration" : {

View File

@ -147,7 +147,6 @@
"horde" : [ 1, -1 ],
"primaryResource" : "gems",
"mageGuild" : 5,
"warMachine" : "ammoCart",
"moatAbility" : "towerMoat",
"buildings" :
@ -167,9 +166,9 @@
"capitol": { },
"marketplace": { },
"resourceSilo": { "produce" : { "gems": 1 } },
"blacksmith": { },
"blacksmith": { "warMachine" : "ammoCart" },
"special1": { "type" : "artifactMerchant", "requires" : [ "marketplace" ] },
"special1": { "requires" : [ "marketplace" ], "marketModes" : ["resource-artifact", "artifact-resource"] },
"horde1": { "id" : 18, "upgrades" : "dwellingLvl2" },
"horde1Upgr": { "id" : 19, "upgrades" : "dwellingUpLvl2", "requires" : [ "horde1" ], "mode" : "auto" },
"special2": { "height" : "high", "requires" : [ "fort" ] },

View File

@ -89,7 +89,7 @@
"additionalProperties" : false,
"required" : [
"mapObject", "buildingsIcons", "buildings", "creatures", "guildWindow", "names",
"hallBackground", "hallSlots", "horde", "mageGuild", "moatAbility", "defaultTavern", "tavernVideo", "guildBackground", "musicTheme", "siege", "structures", "townBackground", "warMachine"
"hallBackground", "hallSlots", "horde", "mageGuild", "moatAbility", "defaultTavern", "tavernVideo", "guildBackground", "musicTheme", "siege", "structures", "townBackground"
],
"description" : "town",
"properties" : {
@ -133,10 +133,6 @@
"type" : "string",
"description" : "Primary resource for this town. Produced by Silo and offered as starting bonus"
},
"warMachine" : {
"type" : "string",
"description" : "Identifier of war machine produced by blacksmith in town"
},
"horde" : {
"type" : "array",
"maxItems" : 2,

View File

@ -36,7 +36,7 @@
},
"type" : {
"type" : "string",
"enum" : [ "mysticPond", "artifactMerchant", "freelancersGuild", "magicUniversity", "castleGate", "creatureTransformer", "portalOfSummoning", "ballistaYard", "library", "escapeTunnel", "treasury", "thievesGuild", "bank" ],
"enum" : [ "mysticPond", "castleGate", "portalOfSummoning", "library", "escapeTunnel", "treasury", "bank" ],
"description" : "Subtype for some special buildings"
},
"mode" : {
@ -93,10 +93,22 @@
"gems" : { "type" : "number"}
}
},
"warMachine" : {
"type" : "string",
"description" : "Artifact ID of a war machine that can be purchased in this building, if any"
},
"bonuses" : {
"type" : "array",
"description" : "Bonuses that are provided by this building in any town where this building has been built. Only affects town itself (including siege), to propagate effect to player or team please use bonus propagators",
"items" : { "$ref" : "bonus.json" }
},
"marketModes" : {
"type" : "array",
"items" : {
"type" : "string",
"enum" : [ "resource-resource", "resource-player", "creature-resource", "resource-artifact", "artifact-resource", "artifact-experience", "creature-experience", "creature-undead", "resource-skill"],
},
"description" : "List of modes available in this market"
}
}
}

6
debian/changelog vendored
View File

@ -4,6 +4,12 @@ vcmi (1.6.0) jammy; urgency=medium
-- Ivan Savenko <saven.ivan@gmail.com> Fri, 30 Aug 2024 12:00:00 +0200
vcmi (1.5.7) jammy; urgency=medium
* New upstream release
-- Ivan Savenko <saven.ivan@gmail.com> Mon, 26 Aug 2024 12:00:00 +0200
vcmi (1.5.6) jammy; urgency=medium
* New upstream release

View File

@ -1,7 +1,7 @@
[![VCMI](https://github.com/vcmi/vcmi/actions/workflows/github.yml/badge.svg?branch=develop&event=push)](https://github.com/vcmi/vcmi/actions/workflows/github.yml?query=branch%3Adevelop+event%3Apush)
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.0/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.0)
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.5/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.5)
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.6/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.6)
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.7/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.7)
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/total)](https://github.com/vcmi/vcmi/releases)
# VCMI Project

View File

@ -1010,11 +1010,16 @@ Dummy bonus that acts as marker for Dendroid's Bind ability
Dummy skill for alternative upgrades mod
### TOWN_MAGIC_WELL
### THIEVES_GUILD_ACCESS
Internal bonus, do not use
Increases amount of information available in affected thieves guild (in town or in adventure map tavern). Does not affects adventure map object "Den of Thieves". You may want to use PLAYER_PROPAGATOR with this bonus to make its effect player wide.
- val: additional number of 'levels' of information to grant access to
### LEVEL_COUNTER
Internal bonus, do not use
### DISINTEGRATE
After death of unit no corpse remains

View File

@ -247,9 +247,6 @@ Each town requires a set of buildings (Around 30-45 buildings)
// maximum level of mage guild
"mageGuild" : 4,
// war machine produced in town
"warMachine" : "ballista"
// Identifier of spell that will create effects for town moat during siege
"moatAbility" : "castleMoat"
}

View File

@ -136,6 +136,9 @@ These are just a couple of examples of what can be done in VCMI. See vcmi config
"gold" : 10000
},
// Artifact ID of a war machine produced in this town building, if any
"warMachine" : "ballista",
// Allows to define additional functionality of this building, usually using logic of one of original H3 town building
// Generally only needs to be specified for "special" buildings
// See 'List of unique town buildings' section below for detailed description of this field
@ -171,6 +174,9 @@ These are just a couple of examples of what can be done in VCMI. See vcmi config
// If set to true, this building will replace all bonuses from base building, leaving only bonuses defined by this building"
"upgradeReplacesBonuses" : false,
// If the building is a market, it requires market mode.
"marketModes" : [ "resource-resource", "resource-player" ],
}
```
@ -204,20 +210,17 @@ Following Heroes III buildings can be used as unique buildings for a town. Their
- `castleGate`
- `creatureTransformer`
- `portalOfSummoning`
- `ballistaYard`
- `library`
- `escapeTunnel`
- `treasury`
#### Buildings from other Heroes III mods
Following HotA buildings can be used as unique building for a town. Functionality should match corresponding HotA building:
- `thievesGuild`
- `bank`
#### Custom buildings
In addition to above, it is possible to use same format as [Rewardable](../Map_Objects/Rewardable.md) map objects for town buildings. In order to do that, configuration of a rewardable object must be placed into `configuration` json node in building config.
```
### Town Structure node
@ -248,3 +251,17 @@ In addition to above, it is possible to use same format as [Rewardable](../Map_O
"hidden" : false
}
```
#### Markets in towns
Market buildings require list of available [modes](../Map_Objects/Market.md)
##### Marketplace
```jsonc
"marketplace": { "marketModes" : ["resource-resource", "resource-player"] },
```
##### Artifact merchant
```jsonc
"special1": { "type" : "artifactMerchant", "requires" : [ "marketplace" ], "marketModes" : ["resource-artifact", "artifact-resource"] },
```

View File

@ -91,6 +91,7 @@
<launchable type="desktop-id">vcmilauncher.desktop</launchable>
<releases>
<release version="1.6.0" date="2024-08-30" type="development"/>
<release version="1.5.7" date="2024-08-26" type="stable"/>
<release version="1.5.6" date="2024-08-04" type="stable"/>
<release version="1.5.5" date="2024-07-17" type="stable"/>
<release version="1.5.4" date="2024-07-12" type="stable"/>

View File

@ -460,7 +460,7 @@ std::shared_ptr<CArtifact> CArtHandler::loadFromJson(const std::string & scope,
}
const JsonNode & warMachine = node["warMachine"];
if(warMachine.getType() == JsonNode::JsonType::DATA_STRING && !warMachine.String().empty())
if(!warMachine.isNull())
{
VLC->identifiers()->requestIdentifier("creature", warMachine, [=](si32 id)
{

View File

@ -174,6 +174,15 @@ const CGTownInstance* CGameInfoCallback::getTown(ObjectInstanceID objid) const
return nullptr;
}
const IMarket * CGameInfoCallback::getMarket(ObjectInstanceID objid) const
{
const CGObjectInstance * obj = getObj(objid, false);
if(obj)
return dynamic_cast<const IMarket*>(obj);
else
return nullptr;
}
void CGameInfoCallback::fillUpgradeInfo(const CArmedInstance *obj, SlotID stackPos, UpgradeInfo &out) const
{
//boost::shared_lock<boost::shared_mutex> lock(*gs->mx);
@ -227,15 +236,7 @@ void CGameInfoCallback::getThievesGuildInfo(SThievesGuildInfo & thi, const CGObj
if(obj->ID == Obj::TOWN || obj->ID == Obj::TAVERN)
{
int taverns = 0;
for(auto town : gs->players[*getPlayerID()].towns)
{
if(town->hasBuilt(BuildingID::TAVERN))
taverns++;
if(town->hasBuilt(BuildingSubID::THIEVES_GUILD))
taverns += 2;
}
int taverns = gs->players[*getPlayerID()].valOfBonuses(BonusType::THIEVES_GUILD_ACCESS);
gs->obtainPlayersStats(thi, taverns);
}
else if(obj->ID == Obj::DEN_OF_THIEVES)
@ -247,7 +248,7 @@ void CGameInfoCallback::getThievesGuildInfo(SThievesGuildInfo & thi, const CGObj
int CGameInfoCallback::howManyTowns(PlayerColor Player) const
{
ERROR_RET_VAL_IF(!hasAccess(Player), "Access forbidden!", -1);
return static_cast<int>(gs->players[Player].towns.size());
return static_cast<int>(gs->players[Player].getTowns().size());
}
bool CGameInfoCallback::getTownInfo(const CGObjectInstance * town, InfoAboutTown & dest, const CGObjectInstance * selectedObject) const
@ -600,7 +601,7 @@ EBuildingState CGameInfoCallback::canBuildStructure( const CGTownInstance *t, Bu
const PlayerState *ps = getPlayerState(t->tempOwner, false);
if(ps)
{
for(const CGTownInstance *town : ps->towns)
for(const CGTownInstance *town : ps->getTowns())
{
if(town->hasBuilt(BuildingID::CAPITOL))
{
@ -702,9 +703,9 @@ int CGameInfoCallback::getHeroCount( PlayerColor player, bool includeGarrisoned
ERROR_RET_VAL_IF(!p, "No such player!", -1);
if(includeGarrisoned)
return static_cast<int>(p->heroes.size());
return static_cast<int>(p->getHeroes().size());
else
for(const auto & elem : p->heroes)
for(const auto & elem : p->getHeroes())
if(!elem->inTownGarrison)
ret++;
return ret;
@ -748,7 +749,7 @@ std::vector < const CGTownInstance *> CPlayerSpecificInfoCallback::getTownsInfo(
auto ret = std::vector < const CGTownInstance *>();
for(const auto & i : gs->players)
{
for(const auto & town : i.second.towns)
for(const auto & town : i.second.getTowns())
{
if(i.first == getPlayerID() || (!onlyOur && isVisible(town, getPlayerID())))
{
@ -780,7 +781,7 @@ int CPlayerSpecificInfoCallback::getHeroSerial(const CGHeroInstance * hero, bool
return -1;
size_t index = 0;
auto & heroes = gs->players[*getPlayerID()].heroes;
const auto & heroes = gs->players[*getPlayerID()].getHeroes();
for (auto & possibleHero : heroes)
{
@ -813,34 +814,12 @@ int3 CPlayerSpecificInfoCallback::getGrailPos( double *outKnownRatio )
std::vector < const CGObjectInstance * > CPlayerSpecificInfoCallback::getMyObjects() const
{
std::vector < const CGObjectInstance * > ret;
for(const CGObjectInstance * obj : gs->map->objects)
{
if(obj && obj->tempOwner == getPlayerID())
ret.push_back(obj);
}
return ret;
}
std::vector < const CGDwelling * > CPlayerSpecificInfoCallback::getMyDwellings() const
{
ASSERT_IF_CALLED_WITH_PLAYER
std::vector < const CGDwelling * > ret;
for(CGDwelling * dw : gs->getPlayerState(*getPlayerID())->dwellings)
{
ret.push_back(dw);
}
return ret;
return gs->getPlayerState(*getPlayerID())->getOwnedObjects();
}
std::vector <QuestInfo> CPlayerSpecificInfoCallback::getMyQuests() const
{
std::vector <QuestInfo> ret;
for(const auto & quest : gs->getPlayerState(*getPlayerID())->quests)
{
ret.push_back (quest);
}
return ret;
return gs->getPlayerState(*getPlayerID())->quests;
}
int CPlayerSpecificInfoCallback::howManyHeroes(bool includeGarrisoned) const
@ -858,12 +837,12 @@ const CGHeroInstance* CPlayerSpecificInfoCallback::getHeroBySerial(int serialId,
if (!includeGarrisoned)
{
for(ui32 i = 0; i < p->heroes.size() && static_cast<int>(i) <= serialId; i++)
if(p->heroes[i]->inTownGarrison)
for(ui32 i = 0; i < p->getHeroes().size() && static_cast<int>(i) <= serialId; i++)
if(p->getHeroes()[i]->inTownGarrison)
serialId++;
}
ERROR_RET_VAL_IF(serialId < 0 || serialId >= p->heroes.size(), "No player info", nullptr);
return p->heroes[serialId];
ERROR_RET_VAL_IF(serialId < 0 || serialId >= p->getHeroes().size(), "No player info", nullptr);
return p->getHeroes()[serialId];
}
const CGTownInstance* CPlayerSpecificInfoCallback::getTownBySerial(int serialId) const
@ -871,8 +850,8 @@ const CGTownInstance* CPlayerSpecificInfoCallback::getTownBySerial(int serialId)
ASSERT_IF_CALLED_WITH_PLAYER
const PlayerState *p = getPlayerState(*getPlayerID());
ERROR_RET_VAL_IF(!p, "No player info", nullptr);
ERROR_RET_VAL_IF(serialId < 0 || serialId >= p->towns.size(), "No player info", nullptr);
return p->towns[serialId];
ERROR_RET_VAL_IF(serialId < 0 || serialId >= p->getTowns().size(), "No player info", nullptr);
return p->getTowns()[serialId];
}
int CPlayerSpecificInfoCallback::getResourceAmount(GameResID type) const

View File

@ -23,7 +23,7 @@ struct InfoWindow;
struct PlayerSettings;
struct CPackForClient;
struct TerrainTile;
struct PlayerState;
class PlayerState;
class CTown;
struct StartInfo;
struct CPathsInfo;
@ -48,6 +48,7 @@ class CGHeroInstance;
class CGDwelling;
class CGTeleport;
class CGTownInstance;
class IMarket;
class DLL_LINKAGE IGameInfoCallback : boost::noncopyable
{
@ -189,6 +190,7 @@ public:
virtual std::vector <const CGObjectInstance * > getFlaggableObjects(int3 pos) const;
virtual const CGObjectInstance * getTopObj (int3 pos) const;
virtual PlayerColor getOwner(ObjectInstanceID heroID) const;
virtual const IMarket * getMarket(ObjectInstanceID objid) const;
//map
virtual int3 guardingCreaturePosition (int3 pos) const;
@ -245,7 +247,6 @@ public:
virtual const CGTownInstance* getTownBySerial(int serialId) const; // serial id is [0, number of towns)
virtual const CGHeroInstance* getHeroBySerial(int serialId, bool includeGarrisoned=true) const; // serial id is [0, number of heroes)
virtual std::vector <const CGHeroInstance *> getHeroesInfo(bool onlyOur = true) const; //true -> only owned; false -> all visible
virtual std::vector <const CGDwelling *> getMyDwellings() const; //returns all dwellings that belong to player
virtual std::vector <const CGObjectInstance * > getMyObjects() const; //returns all objects flagged by belonging player
virtual std::vector <QuestInfo> getMyQuests() const;

View File

@ -13,9 +13,6 @@
#include "CStack.h"
#include "VCMIDirs.h"
#include "serializer/BinaryDeserializer.h"
#include "serializer/BinarySerializer.h"
#ifdef STATIC_AI
# include "AI/VCAI/VCAI.h"
# include "AI/Nullkiller/AIGateway.h"

View File

@ -53,8 +53,6 @@ class CStack;
class CCreature;
class CLoadFile;
class CSaveFile;
class BinaryDeserializer;
class BinarySerializer;
class BattleStateInfo;
struct ArtifactLocation;
class BattleStateInfoForRetreat;

View File

@ -217,6 +217,7 @@ set(lib_MAIN_SRCS
serializer/JsonSerializeFormat.cpp
serializer/JsonSerializer.cpp
serializer/JsonUpdater.cpp
serializer/SerializerReflection.cpp
spells/AbilityCaster.cpp
spells/AdventureSpellMechanics.cpp
@ -503,6 +504,7 @@ set(lib_MAIN_HEADERS
mapObjects/CRewardableObject.h
mapObjects/IMarket.h
mapObjects/IObjectInterface.h
mapObjects/IOwnableObject.h
mapObjects/MapObjects.h
mapObjects/MiscObjects.h
mapObjects/ObjectTemplate.h
@ -563,12 +565,6 @@ set(lib_MAIN_HEADERS
pathfinder/PathfindingRules.h
pathfinder/TurnInfo.h
registerTypes/RegisterTypes.h
registerTypes/RegisterTypesClientPacks.h
registerTypes/RegisterTypesLobbyPacks.h
registerTypes/RegisterTypesMapObjects.h
registerTypes/RegisterTypesServerPacks.h
rewardable/Configuration.h
rewardable/Info.h
rewardable/Interface.h
@ -625,7 +621,9 @@ set(lib_MAIN_HEADERS
serializer/JsonUpdater.h
serializer/Cast.h
serializer/ESerializationVersion.h
serializer/RegisterTypes.h
serializer/Serializeable.h
serializer/SerializerReflection.h
spells/AbilityCaster.h
spells/AdventureSpellMechanics.h

View File

@ -10,6 +10,9 @@
#include "StdInc.h"
#include "CPlayerState.h"
#include "mapObjects/CGDwelling.h"
#include "mapObjects/CGTownInstance.h"
#include "mapObjects/CGHeroInstance.h"
#include "gameState/QuestInfo.h"
#include "texts/CGeneralTextHandler.h"
#include "VCMI_Lib.h"
@ -90,4 +93,54 @@ int PlayerState::getResourceAmount(int type) const
return vstd::atOrDefault(resources, static_cast<size_t>(type), 0);
}
template<typename T>
std::vector<T> PlayerState::getObjectsOfType() const
{
std::vector<T> result;
for (auto const & object : ownedObjects)
{
auto casted = dynamic_cast<T>(object);
if (casted)
result.push_back(casted);
}
return result;
}
std::vector<const CGHeroInstance *> PlayerState::getHeroes() const
{
return getObjectsOfType<const CGHeroInstance *>();
}
std::vector<const CGTownInstance *> PlayerState::getTowns() const
{
return getObjectsOfType<const CGTownInstance *>();
}
std::vector<CGHeroInstance *> PlayerState::getHeroes()
{
return getObjectsOfType<CGHeroInstance *>();
}
std::vector<CGTownInstance *> PlayerState::getTowns()
{
return getObjectsOfType<CGTownInstance *>();
}
std::vector<const CGObjectInstance *> PlayerState::getOwnedObjects() const
{
return {ownedObjects.begin(), ownedObjects.end()};
}
void PlayerState::addOwnedObject(CGObjectInstance * object)
{
assert(object->asOwnable() != nullptr);
ownedObjects.push_back(object);
}
void PlayerState::removeOwnedObject(CGObjectInstance * object)
{
vstd::erase(ownedObjects, object);
}
VCMI_LIB_NAMESPACE_END

View File

@ -20,12 +20,13 @@
VCMI_LIB_NAMESPACE_BEGIN
class CGObjectInstance;
class CGHeroInstance;
class CGTownInstance;
class CGDwelling;
struct QuestInfo;
struct DLL_LINKAGE PlayerState : public CBonusSystemNode, public Player
class DLL_LINKAGE PlayerState : public CBonusSystemNode, public Player
{
struct VisitedObjectGlobal
{
@ -47,6 +48,11 @@ struct DLL_LINKAGE PlayerState : public CBonusSystemNode, public Player
}
};
std::vector<CGObjectInstance*> ownedObjects;
template<typename T>
std::vector<T> getObjectsOfType() const;
public:
PlayerColor color;
bool human; //true if human controlled player, false for AI
@ -55,12 +61,8 @@ public:
/// list of objects that were "destroyed" by player, either via simple pick-up (e.g. resources) or defeated heroes or wandering monsters
std::set<ObjectInstanceID> destroyedObjects;
std::set<ObjectInstanceID> visitedObjects; // as a std::set, since most accesses here will be from visited status checks
std::set<VisitedObjectGlobal> visitedObjectsGlobal;
std::vector<ConstTransitivePtr<CGHeroInstance> > heroes;
std::vector<ConstTransitivePtr<CGTownInstance> > towns;
std::vector<ConstTransitivePtr<CGDwelling> > dwellings; //used for town growth
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;
@ -90,9 +92,19 @@ public:
std::string getNameTextID() const override;
void registerIcons(const IconRegistar & cb) const override;
std::vector<const CGHeroInstance* > getHeroes() const;
std::vector<const CGTownInstance* > getTowns() const;
std::vector<CGHeroInstance* > getHeroes();
std::vector<CGTownInstance* > getTowns();
std::vector<const CGObjectInstance* > getOwnedObjects() const;
void addOwnedObject(CGObjectInstance * object);
void removeOwnedObject(CGObjectInstance * object);
bool checkVanquished() const
{
return heroes.empty() && towns.empty();
return ownedObjects.empty();
}
template <typename Handler> void serialize(Handler &h)
@ -103,9 +115,21 @@ public:
h & resources;
h & status;
h & turnTimer;
h & heroes;
h & towns;
h & dwellings;
if (h.version >= Handler::Version::PLAYER_STATE_OWNED_OBJECTS)
{
h & ownedObjects;
}
else
{
std::vector<const CGObjectInstance* > heroes;
std::vector<const CGObjectInstance* > towns;
std::vector<const CGObjectInstance* > dwellings;
h & heroes;
h & towns;
h & dwellings;
}
h & quests;
h & visitedObjects;
h & visitedObjectsGlobal;

View File

@ -180,8 +180,7 @@ CGameState * CPrivilegedInfoCallback::gameState()
return gs;
}
template<typename Loader>
void CPrivilegedInfoCallback::loadCommonState(Loader & in)
void CPrivilegedInfoCallback::loadCommonState(CLoadFile & in)
{
logGlobal->info("Loading lib part of game...");
in.checkMagicBytes(SAVEGAME_MAGIC);
@ -203,8 +202,7 @@ void CPrivilegedInfoCallback::loadCommonState(Loader & in)
in.serializer & gs;
}
template<typename Saver>
void CPrivilegedInfoCallback::saveCommonState(Saver & out) const
void CPrivilegedInfoCallback::saveCommonState(CSaveFile & out) const
{
ActiveModsInSaveList activeMods;
@ -220,10 +218,6 @@ void CPrivilegedInfoCallback::saveCommonState(Saver & out) const
out.serializer & gs;
}
// hardly memory usage for `-gdwarf-4` flag
template DLL_LINKAGE void CPrivilegedInfoCallback::loadCommonState<CLoadFile>(CLoadFile &);
template DLL_LINKAGE void CPrivilegedInfoCallback::saveCommonState<CSaveFile>(CSaveFile &) const;
TerrainTile * CNonConstInfoCallback::getTile(const int3 & pos)
{
if(!gs->map->isInTheMap(pos))
@ -287,18 +281,16 @@ CArtifactSet * CNonConstInfoCallback::getArtSet(const ArtifactLocation & loc)
return hero;
}
}
else if(auto market = getMarket(loc.artHolder))
{
if(auto artSet = market->getArtifactsStorage())
return artSet;
}
else if(auto army = getArmyInstance(loc.artHolder))
{
return army->getStackPtr(loc.creature.value());
}
else if(auto market = dynamic_cast<CGArtifactsAltar*>(getObjInstance(loc.artHolder)))
{
return market;
}
else
{
return nullptr;
}
return nullptr;
}
bool IGameCallback::isVisitCoveredByAnotherQuery(const CGObjectInstance *obj, const CGHeroInstance *hero)

View File

@ -31,6 +31,8 @@ struct BankConfig;
class CCreatureSet;
class CStackBasicDescriptor;
class CGCreature;
class CSaveFile;
class CLoadFile;
enum class EOpenWindowMode : uint8_t;
namespace spells
@ -74,11 +76,8 @@ public:
void pickAllowedArtsSet(std::vector<const CArtifact *> & out, vstd::RNG & rand);
void getAllowedSpells(std::vector<SpellID> &out, std::optional<ui16> level = std::nullopt);
template<typename Saver>
void saveCommonState(Saver &out) const; //stores GS and VLC
template<typename Loader>
void loadCommonState(Loader &in); //loads GS and VLC
void saveCommonState(CSaveFile &out) const; //stores GS and VLC
void loadCommonState(CLoadFile &in); //loads GS and VLC
};
class DLL_LINKAGE IGameEventCallback
@ -141,7 +140,7 @@ public:
virtual void sendAndApply(CPackForClient * pack) = 0;
virtual void heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2)=0; //when two heroes meet on adventure map
virtual void changeFogOfWar(int3 center, ui32 radius, PlayerColor player, ETileVisibility mode) = 0;
virtual void changeFogOfWar(std::unordered_set<int3> &tiles, PlayerColor player, ETileVisibility mode) = 0;
virtual void changeFogOfWar(const std::unordered_set<int3> &tiles, PlayerColor player, ETileVisibility mode) = 0;
virtual void castSpell(const spells::Caster * caster, SpellID spellID, const int3 &pos) = 0;

View File

@ -109,7 +109,7 @@ public:
virtual void showPuzzleMap(){};
virtual void viewWorldMap(){};
virtual void showMarketWindow(const IMarket *market, const CGHeroInstance *visitor, QueryID queryID){};
virtual void showMarketWindow(const IMarket * market, const CGHeroInstance * visitor, QueryID queryID){};
virtual void showUniversityWindow(const IMarket *market, const CGHeroInstance *visitor, QueryID queryID){};
virtual void showHillFortWindow(const CGObjectInstance *object, const CGHeroInstance *visitor){};
virtual void showThievesGuildWindow (const CGObjectInstance * obj){};

View File

@ -64,6 +64,12 @@ void ResourceSet::positive()
vstd::amax(elem, 0);
}
void ResourceSet::applyHandicap(int percentage)
{
for(auto & elem : *this)
elem = vstd::divideAndCeil(elem * percentage, 100);
}
static bool canAfford(const ResourceSet &res, const ResourceSet &price)
{
assert(res.size() == price.size() && price.size() == GameConstants::RESOURCE_QUANTITY);

View File

@ -210,6 +210,7 @@ public:
DLL_LINKAGE void amax(const TResourceCap &val); //performs vstd::amax on each element
DLL_LINKAGE void amin(const TResourceCap &val); //performs vstd::amin on each element
DLL_LINKAGE void positive(); //values below 0 are set to 0 - upgrade cost can't be negative, for example
DLL_LINKAGE void applyHandicap(int percentage);
DLL_LINKAGE bool nonZero() const; //returns true if at least one value is non-zero;
DLL_LINKAGE bool canAfford(const ResourceSet &price) const;
DLL_LINKAGE bool canBeAfforded(const ResourceSet &res) const;

View File

@ -1051,7 +1051,7 @@ ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo &accessibi
if(isInObstacle(curHex, obstacles, checkParams))
continue;
const int costToNeighbour = ret.distances[curHex.hex] + 1;
const int costToNeighbour = ret.distances.at(curHex.hex) + 1;
for(BattleHex neighbour : BattleHex::neighbouringTilesCache[curHex.hex])
{

View File

@ -831,7 +831,8 @@ void CUnitState::damage(int64_t & amount)
health.damage(amount);
}
if(health.available() <= 0 && (cloned || summoned))
bool disintegrate = hasBonusOfType(BonusType::DISINTEGRATE);
if(health.available() <= 0 && (cloned || summoned || disintegrate))
ghostPending = true;
}
@ -900,9 +901,9 @@ CUnitStateDetached::CUnitStateDetached(const IUnitInfo * unit_, const IBonusBear
{
}
TConstBonusListPtr CUnitStateDetached::getAllBonuses(const CSelector & selector, const CSelector & limit, const CBonusSystemNode * root, const std::string & cachingStr) const
TConstBonusListPtr CUnitStateDetached::getAllBonuses(const CSelector & selector, const CSelector & limit, const std::string & cachingStr) const
{
return bonus->getAllBonuses(selector, limit, root, cachingStr);
return bonus->getAllBonuses(selector, limit, cachingStr);
}
int64_t CUnitStateDetached::getTreeVersion() const

View File

@ -282,7 +282,7 @@ public:
explicit CUnitStateDetached(const IUnitInfo * unit_, const IBonusBearer * bonus_);
TConstBonusListPtr getAllBonuses(const CSelector & selector, const CSelector & limit,
const CBonusSystemNode * root = nullptr, const std::string & cachingStr = "") const override;
const std::string & cachingStr = "") const override;
int64_t getTreeVersion() const override;

View File

@ -150,7 +150,7 @@ class JsonNode;
BONUS_NAME(GARGOYLE) /* gargoyle is special than NON_LIVING, cannot be rised or healed */ \
BONUS_NAME(SPECIAL_ADD_VALUE_ENCHANT) /*specialty spell like Aenin has, increased effect of spell, additionalInfo = value to add*/\
BONUS_NAME(SPECIAL_FIXED_VALUE_ENCHANT) /*specialty spell like Melody has, constant spell effect (i.e. 3 luck), additionalInfo = value to fix.*/\
BONUS_NAME(TOWN_MAGIC_WELL) /*one-time pseudo-bonus to implement Magic Well in the town*/\
BONUS_NAME(THIEVES_GUILD_ACCESS) \
BONUS_NAME(LIMITED_SHOOTING_RANGE) /*limits range of shooting creatures, doesn't adjust any other mechanics (half vs full damage etc). val - range in hexes, additional info - optional new range for broken arrow mechanic */\
BONUS_NAME(LEARN_BATTLE_SPELL_CHANCE) /*skill-agnostic eagle eye chance. subtype = 0 - from enemy, 1 - TODO: from entire battlefield*/\
BONUS_NAME(LEARN_BATTLE_SPELL_LEVEL_LIMIT) /*skill-agnostic eagle eye limit, subtype - school (-1 for all), others TODO*/\
@ -178,6 +178,7 @@ class JsonNode;
BONUS_NAME(REVENGE) /*additional damage based on how many units in stack died - formula: sqrt((number of creatures at battle start + 1) * creature health) / (total health now + 1 creature health) - 1) * 100% */ \
BONUS_NAME(RESOURCES_CONSTANT_BOOST) /*Bonus that does not account for propagation and gives extra resources per day. val - resource amount, subtype - resource type*/ \
BONUS_NAME(RESOURCES_TOWN_MULTIPLYING_BOOST) /*Bonus that does not account for propagation and gives extra resources per day with amount multiplied by number of owned towns. val - base resource amount to be multiplied times number of owned towns, subtype - resource type*/ \
BONUS_NAME(DISINTEGRATE) /* after death no corpse remains */ \
/* end of list */

View File

@ -107,10 +107,9 @@ void CBonusSystemNode::getAllBonusesRec(BonusList &out, const CSelector & select
}
}
TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root, const std::string &cachingStr) const
TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr) const
{
bool limitOnUs = (!root || root == this); //caching won't work when we want to limit bonuses against an external node
if (CBonusSystemNode::cachingEnabled && limitOnUs)
if (CBonusSystemNode::cachingEnabled)
{
// Exclusive access for one thread
boost::lock_guard<boost::mutex> lock(sync);
@ -157,11 +156,11 @@ TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, co
}
else
{
return getAllBonusesWithoutCaching(selector, limit, root);
return getAllBonusesWithoutCaching(selector, limit);
}
}
TConstBonusListPtr CBonusSystemNode::getAllBonusesWithoutCaching(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root) const
TConstBonusListPtr CBonusSystemNode::getAllBonusesWithoutCaching(const CSelector &selector, const CSelector &limit) const
{
auto ret = std::make_shared<BonusList>();
@ -169,29 +168,7 @@ TConstBonusListPtr CBonusSystemNode::getAllBonusesWithoutCaching(const CSelector
BonusList beforeLimiting;
BonusList afterLimiting;
getAllBonusesRec(beforeLimiting, selector);
if(!root || root == this)
{
limitBonuses(beforeLimiting, afterLimiting);
}
else if(root)
{
//We want to limit our query against an external node. We get all its bonuses,
// add the ones we're considering and see if they're cut out by limiters
BonusList rootBonuses;
BonusList limitedRootBonuses;
getAllBonusesRec(rootBonuses, selector);
for(const auto & b : beforeLimiting)
rootBonuses.push_back(b);
root->limitBonuses(rootBonuses, limitedRootBonuses);
for(const auto & b : beforeLimiting)
if(vstd::contains(limitedRootBonuses, b))
afterLimiting.push_back(b);
}
limitBonuses(beforeLimiting, afterLimiting);
afterLimiting.getBonuses(*ret, selector, limit);
ret->stackBonuses();
return ret;

View File

@ -53,7 +53,7 @@ private:
mutable boost::mutex sync;
void getAllBonusesRec(BonusList &out, const CSelector & selector) const;
TConstBonusListPtr getAllBonusesWithoutCaching(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = nullptr) const;
TConstBonusListPtr getAllBonusesWithoutCaching(const CSelector &selector, const CSelector &limit) const;
std::shared_ptr<Bonus> getUpdatedBonus(const std::shared_ptr<Bonus> & b, const TUpdaterPtr & updater) const;
void getRedParents(TCNodes &out) const; //retrieves list of red parent nodes (nodes bonuses propagate from)
@ -86,7 +86,7 @@ public:
void limitBonuses(const BonusList &allBonuses, BonusList &out) const; //out will bo populed with bonuses that are not limited here
TBonusListPtr limitBonuses(const BonusList &allBonuses) const; //same as above, returns out by val for convenience
TConstBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = nullptr, const std::string &cachingStr = "") const override;
TConstBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr = "") const override;
void getParents(TCNodes &out) const; //retrieves list of parent nodes (nodes to inherit bonuses from),
/// Returns first bonus matching selector

View File

@ -17,7 +17,7 @@ VCMI_LIB_NAMESPACE_BEGIN
int IBonusBearer::valOfBonuses(const CSelector &selector, const std::string &cachingStr) const
{
TConstBonusListPtr hlp = getAllBonuses(selector, nullptr, nullptr, cachingStr);
TConstBonusListPtr hlp = getAllBonuses(selector, nullptr, cachingStr);
return hlp->totalValue();
}
@ -34,12 +34,12 @@ bool IBonusBearer::hasBonus(const CSelector &selector, const CSelector &limit, c
TConstBonusListPtr IBonusBearer::getBonuses(const CSelector &selector, const std::string &cachingStr) const
{
return getAllBonuses(selector, nullptr, nullptr, cachingStr);
return getAllBonuses(selector, nullptr, cachingStr);
}
TConstBonusListPtr IBonusBearer::getBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr) const
{
return getAllBonuses(selector, limit, nullptr, cachingStr);
return getAllBonuses(selector, limit, cachingStr);
}
int IBonusBearer::valOfBonuses(BonusType type) const

View File

@ -22,7 +22,7 @@ public:
//interface
IBonusBearer() = default;
virtual ~IBonusBearer() = default;
virtual TConstBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = nullptr, const std::string &cachingStr = "") const = 0;
virtual TConstBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr = "") const = 0;
int valOfBonuses(const CSelector &selector, const std::string &cachingStr = "") const;
bool hasBonus(const CSelector &selector, const std::string &cachingStr = "") const;
bool hasBonus(const CSelector &selector, const CSelector &limit, const std::string &cachingStr = "") const;

View File

@ -26,18 +26,11 @@ namespace BuildingSubID
DEFAULT = -50,
NONE = -1,
CASTLE_GATE,
CREATURE_TRANSFORMER,
MYSTIC_POND,
FOUNTAIN_OF_FORTUNE,
ARTIFACT_MERCHANT,
LIBRARY,
PORTAL_OF_SUMMONING,
ESCAPE_TUNNEL,
FREELANCERS_GUILD,
BALLISTA_YARD,
MAGIC_UNIVERSITY,
TREASURY,
THIEVES_GUILD,
BANK
};
}
@ -256,4 +249,14 @@ enum class EMapLevel : int8_t
UNDERGROUND = 1
};
enum class EWeekType : int8_t
{
FIRST_WEEK,
NORMAL,
DOUBLE_GROWTH,
BONUS_GROWTH,
DEITYOFFIRE,
PLAGUE
};
VCMI_LIB_NAMESPACE_END

View File

@ -178,18 +178,11 @@ namespace MappedKeys
static const std::map<std::string, BuildingSubID::EBuildingSubID> SPECIAL_BUILDINGS =
{
{ "mysticPond", BuildingSubID::MYSTIC_POND },
{ "artifactMerchant", BuildingSubID::ARTIFACT_MERCHANT },
{ "freelancersGuild", BuildingSubID::FREELANCERS_GUILD },
{ "magicUniversity", BuildingSubID::MAGIC_UNIVERSITY },
{ "castleGate", BuildingSubID::CASTLE_GATE },
{ "creatureTransformer", BuildingSubID::CREATURE_TRANSFORMER },//only skeleton transformer yet
{ "portalOfSummoning", BuildingSubID::PORTAL_OF_SUMMONING },
{ "ballistaYard", BuildingSubID::BALLISTA_YARD },
{ "library", BuildingSubID::LIBRARY },
{ "fountainOfFortune", BuildingSubID::FOUNTAIN_OF_FORTUNE },//luck garrison bonus
{ "escapeTunnel", BuildingSubID::ESCAPE_TUNNEL },
{ "treasury", BuildingSubID::TREASURY },
{ "thievesGuild", BuildingSubID::THIEVES_GUILD },
{ "bank", BuildingSubID::BANK }
};

View File

@ -34,6 +34,8 @@ public:
TResources resources;
TResources produce;
TRequired requirements;
ArtifactID warMachine;
std::set<EMarketMode> marketModes;
BuildingID bid; //structure ID
BuildingID upgrade; /// indicates that building "upgrade" can be improved by this, -1 = empty
@ -85,7 +87,7 @@ public:
STRONG_INLINE
bool IsTradeBuilding() const
{
return bid == BuildingID::MARKETPLACE || subId == BuildingSubID::ARTIFACT_MERCHANT || subId == BuildingSubID::FREELANCERS_GUILD;
return !marketModes.empty();
}
void addNewBonus(const std::shared_ptr<Bonus> & b, BonusList & bonusList) const;

View File

@ -69,7 +69,7 @@ public:
std::map<int,int> hordeLvl; //[0] - first horde building creature level; [1] - second horde building (-1 if not present)
ui32 mageLevel; //max available mage guild level
GameResID primaryRes;
ArtifactID warMachine;
CreatureID warMachineDeprecated;
SpellID moatAbility;
// default chance for hero of specific class to appear in tavern, if field "tavern" was not set

View File

@ -324,6 +324,14 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons
}
loadBuildingRequirements(ret, source["requires"], requirementsToLoad);
if (!source["warMachine"].isNull())
{
VLC->identifiers()->requestIdentifier("artifact", source["warMachine"], [=](si32 identifier)
{
ret->warMachine = ArtifactID(identifier);
});
}
if (!source["upgrades"].isNull())
{
// building id and upgrades can't be the same
@ -342,6 +350,11 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons
ret->upgrade = BuildingID::NONE;
ret->town->buildings[ret->bid] = ret;
for(const auto & element : source["marketModes"].Vector())
{
if(MappedKeys::MARKET_NAMES_TO_TYPES.count(element.String()))
ret->marketModes.insert(MappedKeys::MARKET_NAMES_TO_TYPES.at(element.String()));
}
registerObject(source.getModScope(), ret->town->getBuildingScope(), ret->identifier, ret->bid.getNum());
}
@ -547,7 +560,13 @@ void CTownHandler::loadTown(CTown * town, const JsonNode & source)
else
town->primaryRes = GameResID(resIter - std::begin(GameConstants::RESOURCE_NAMES));
warMachinesToLoad[town] = source["warMachine"];
if (!source["warMachine"].isNull())
{
VLC->identifiers()->requestIdentifier( "creature", source["warMachine"], [=](si32 creatureID)
{
town->warMachineDeprecated = creatureID;
});
}
town->mageLevel = static_cast<ui32>(source["mageGuild"].Float());
@ -843,7 +862,6 @@ void CTownHandler::beforeValidate(JsonNode & object)
void CTownHandler::afterLoadFinalization()
{
initializeRequirements();
initializeWarMachines();
}
void CTownHandler::initializeRequirements()
@ -873,27 +891,6 @@ void CTownHandler::initializeRequirements()
requirementsToLoad.clear();
}
void CTownHandler::initializeWarMachines()
{
// must be done separately after all objects are loaded
for(auto & p : warMachinesToLoad)
{
CTown * t = p.first;
JsonNode creatureKey = p.second;
auto ret = VLC->identifiers()->getIdentifier("creature", creatureKey, false);
if(ret)
{
const CCreature * creature = CreatureID(*ret).toCreature();
t->warMachine = creature->warMachine;
}
}
warMachinesToLoad.clear();
}
std::set<FactionID> CTownHandler::getDefaultAllowed() const
{
std::set<FactionID> allowedFactions;

View File

@ -34,14 +34,12 @@ class DLL_LINKAGE CTownHandler : public CHandlerBase<FactionID, Faction, CFactio
CTown * town;
};
std::map<CTown *, JsonNode> warMachinesToLoad;
std::vector<BuildingRequirementsHelper> requirementsToLoad;
std::vector<BuildingRequirementsHelper> overriddenBidsToLoad; //list of buildings, which bonuses should be overridden.
static const TPropagatorPtr & emptyPropagator();
void initializeRequirements();
void initializeWarMachines();
/// loads CBuilding's into town
void loadBuildingRequirements(CBuilding * building, const JsonNode & source, std::vector<BuildingRequirementsHelper> & bidsToLoad) const;

View File

@ -45,12 +45,11 @@
#include "../mapping/CMapService.h"
#include "../modding/IdentifierStorage.h"
#include "../modding/ModScope.h"
#include "../networkPacks/NetPacksBase.h"
#include "../pathfinder/CPathfinder.h"
#include "../pathfinder/PathfinderOptions.h"
#include "../registerTypes/RegisterTypesClientPacks.h"
#include "../rmg/CMapGenerator.h"
#include "../serializer/CMemorySerializer.h"
#include "../serializer/CTypeList.h"
#include "../spells/CSpellHandler.h"
#include <vstd/RNG.h>
@ -59,29 +58,6 @@ VCMI_LIB_NAMESPACE_BEGIN
boost::shared_mutex CGameState::mutex;
template <typename T> class CApplyOnGS;
class CBaseForGSApply
{
public:
virtual void applyOnGS(CGameState *gs, CPack * pack) const =0;
virtual ~CBaseForGSApply() = default;
template<typename U> static CBaseForGSApply *getApplier(const U * t=nullptr)
{
return new CApplyOnGS<U>();
}
};
template <typename T> class CApplyOnGS : public CBaseForGSApply
{
public:
void applyOnGS(CGameState *gs, CPack * pack) const override
{
T *ptr = static_cast<T*>(pack);
ptr->applyGs(gs);
}
};
HeroTypeID CGameState::pickNextHeroType(const PlayerColor & owner)
{
const PlayerSettings &ps = scenarioOps->getIthPlayersSettings(owner);
@ -165,8 +141,6 @@ CGameState::CGameState()
{
gs = this;
heroesPool = std::make_unique<TavernHeroesPool>();
applier = std::make_shared<CApplier<CBaseForGSApply>>();
registerTypesClientPacks(*applier);
globalEffects.setNodeType(CBonusSystemNode::GLOBAL_EFFECTS);
}
@ -303,6 +277,27 @@ void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRan
std::unique_ptr<CMap> randomMap = mapGenerator.generate();
progressTracking.exclude(mapGenerator);
// Update starting options
for(int i = 0; i < randomMap->players.size(); ++i)
{
const auto & playerInfo = randomMap->players[i];
if(playerInfo.canAnyonePlay())
{
PlayerSettings & playerSettings = scenarioOps->playerInfos[PlayerColor(i)];
playerSettings.compOnly = !playerInfo.canHumanPlay;
playerSettings.castle = playerInfo.defaultCastle();
if(playerSettings.isControlledByAI() && playerSettings.name.empty())
{
playerSettings.name = VLC->generaltexth->allTexts[468];
}
playerSettings.color = PlayerColor(i);
}
else
{
scenarioOps->playerInfos.erase(PlayerColor(i));
}
}
if(allowSavingRandomMap)
{
try
@ -332,26 +327,6 @@ void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRan
}
map = randomMap.release();
// Update starting options
for(int i = 0; i < map->players.size(); ++i)
{
const auto & playerInfo = map->players[i];
if(playerInfo.canAnyonePlay())
{
PlayerSettings & playerSettings = scenarioOps->playerInfos[PlayerColor(i)];
playerSettings.compOnly = !playerInfo.canHumanPlay;
playerSettings.castle = playerInfo.defaultCastle();
if(playerSettings.isControlledByAI() && playerSettings.name.empty())
{
playerSettings.name = VLC->generaltexth->allTexts[468];
}
playerSettings.color = PlayerColor(i);
}
else
{
scenarioOps->playerInfos.erase(PlayerColor(i));
}
}
logGlobal->info("Generated random map in %i ms.", sw.getDiff());
}
@ -369,6 +344,15 @@ void CGameState::initCampaign()
map = campaign->getCurrentMap().release();
}
void CGameState::generateOwnedObjectsAfterDeserialize()
{
for (auto & object : map->objects)
{
if (object && object->asOwnable() && object->getOwner().isValidPlayer())
players.at(object->getOwner()).addOwnedObject(object.get());
}
}
void CGameState::initGlobalBonuses()
{
const JsonNode & baseBonuses = VLC->settings()->getValue(EGameSettings::BONUSES_GLOBAL);
@ -508,6 +492,9 @@ void CGameState::randomizeMapObjects()
}
}
}
if (object->getOwner().isValidPlayer())
getPlayerState(object->getOwner())->addOwnedObject(object);
}
}
@ -599,7 +586,6 @@ void CGameState::initHeroes()
}
hero->initHero(getRandomGenerator());
getPlayerState(hero->getOwner())->heroes.push_back(hero);
map->allHeroes[hero->getHeroType().getNum()] = hero;
}
@ -724,14 +710,14 @@ void CGameState::initStartingBonus()
}
case PlayerStartingBonus::ARTIFACT:
{
if(elem.second.heroes.empty())
if(elem.second.getHeroes().empty())
{
logGlobal->error("Cannot give starting artifact - no heroes!");
break;
}
const Artifact * toGive = pickRandomArtifact(getRandomGenerator(), CArtifact::ART_TREASURE).toEntity(VLC);
CGHeroInstance *hero = elem.second.heroes[0];
CGHeroInstance *hero = elem.second.getHeroes()[0];
if(!giveHeroArtifact(hero, toGive->getId()))
logGlobal->error("Cannot give starting artifact - no free slots!");
}
@ -807,12 +793,12 @@ void CGameState::initTowns()
constexpr std::array hordes = { BuildingID::HORDE_PLACEHOLDER1, BuildingID::HORDE_PLACEHOLDER2, BuildingID::HORDE_PLACEHOLDER3, BuildingID::HORDE_PLACEHOLDER4, BuildingID::HORDE_PLACEHOLDER5, BuildingID::HORDE_PLACEHOLDER6, BuildingID::HORDE_PLACEHOLDER7, BuildingID::HORDE_PLACEHOLDER8 };
//init buildings
if(vstd::contains(vti->builtBuildings, BuildingID::DEFAULT)) //give standard set of buildings
if(vti->hasBuilt(BuildingID::DEFAULT)) //give standard set of buildings
{
vti->builtBuildings.erase(BuildingID::DEFAULT);
vti->builtBuildings.insert(BuildingID::VILLAGE_HALL);
vti->removeBuilding(BuildingID::DEFAULT);
vti->addBuilding(BuildingID::VILLAGE_HALL);
if(vti->tempOwner != PlayerColor::NEUTRAL)
vti->builtBuildings.insert(BuildingID::TAVERN);
vti->addBuilding(BuildingID::TAVERN);
auto definesBuildingsChances = VLC->settings()->getVector(EGameSettings::TOWNS_STARTING_DWELLING_CHANCES);
@ -820,48 +806,49 @@ void CGameState::initTowns()
{
if((getRandomGenerator().nextInt(1,100) <= definesBuildingsChances[i]))
{
vti->builtBuildings.insert(basicDwellings[i]);
vti->addBuilding(basicDwellings[i]);
}
}
}
// village hall must always exist
vti->builtBuildings.insert(BuildingID::VILLAGE_HALL);
vti->addBuilding(BuildingID::VILLAGE_HALL);
//init hordes
for (int i = 0; i < vti->town->creatures.size(); i++)
{
if (vstd::contains(vti->builtBuildings, hordes[i])) //if we have horde for this level
if(vti->hasBuilt(hordes[i])) //if we have horde for this level
{
vti->builtBuildings.erase(hordes[i]);//remove old ID
vti->removeBuilding(hordes[i]);//remove old ID
if (vti->getTown()->hordeLvl.at(0) == i)//if town first horde is this one
{
vti->builtBuildings.insert(BuildingID::HORDE_1);//add it
vti->addBuilding(BuildingID::HORDE_1);//add it
//if we have upgraded dwelling as well
if (vstd::contains(vti->builtBuildings, upgradedDwellings[i]))
vti->builtBuildings.insert(BuildingID::HORDE_1_UPGR);//add it as well
if(vti->hasBuilt(upgradedDwellings[i]))
vti->addBuilding(BuildingID::HORDE_1_UPGR);//add it as well
}
if (vti->getTown()->hordeLvl.at(1) == i)//if town second horde is this one
{
vti->builtBuildings.insert(BuildingID::HORDE_2);
if (vstd::contains(vti->builtBuildings, upgradedDwellings[i]))
vti->builtBuildings.insert(BuildingID::HORDE_2_UPGR);
vti->addBuilding(BuildingID::HORDE_2);
if(vti->hasBuilt(upgradedDwellings[i]))
vti->addBuilding(BuildingID::HORDE_2_UPGR);
}
}
}
//#1444 - remove entries that don't have buildings defined (like some unused extra town hall buildings)
//But DO NOT remove horde placeholders before they are replaced
vstd::erase_if(vti->builtBuildings, [vti](const BuildingID & bid)
{
return !vti->getTown()->buildings.count(bid) || !vti->getTown()->buildings.at(bid);
});
for(const auto & building : vti->getBuildings())
{
if(!vti->getTown()->buildings.count(building) || !vti->getTown()->buildings.at(building))
vti->removeBuilding(building);
}
if (vstd::contains(vti->builtBuildings, BuildingID::SHIPYARD) && vti->shipyardStatus()==IBoatGenerator::TILE_BLOCKED)
vti->builtBuildings.erase(BuildingID::SHIPYARD);//if we have harbor without water - erase it (this is H3 behaviour)
if(vti->hasBuilt(BuildingID::SHIPYARD) && vti->shipyardStatus()==IBoatGenerator::TILE_BLOCKED)
vti->removeBuilding(BuildingID::SHIPYARD);//if we have harbor without water - erase it (this is H3 behaviour)
//Early check for #1444-like problems
for([[maybe_unused]] const auto & building : vti->builtBuildings)
for([[maybe_unused]] const auto & building : vti->getBuildings())
{
assert(vti->getTown()->buildings.at(building) != nullptr);
}
@ -917,8 +904,6 @@ void CGameState::initTowns()
vti->possibleSpells -= s->id;
}
vti->possibleSpells.clear();
if(vti->getOwner() != PlayerColor::NEUTRAL)
getPlayerState(vti->getOwner())->towns.emplace_back(vti);
}
}
@ -961,9 +946,9 @@ void CGameState::placeHeroesInTowns()
if(player.first == PlayerColor::NEUTRAL)
continue;
for(CGHeroInstance * h : player.second.heroes)
for(CGHeroInstance * h : player.second.getHeroes())
{
for(CGTownInstance * t : player.second.towns)
for(CGTownInstance * t : player.second.getTowns())
{
if(h->visitablePos().z != t->visitablePos().z)
continue;
@ -995,9 +980,9 @@ void CGameState::initVisitingAndGarrisonedHeroes()
continue;
//init visiting and garrisoned heroes
for(CGHeroInstance * h : player.second.heroes)
for(CGHeroInstance * h : player.second.getHeroes())
{
for(CGTownInstance * t : player.second.towns)
for(CGTownInstance * t : player.second.getTowns())
{
if(h->visitablePos().z != t->visitablePos().z)
continue;
@ -1144,10 +1129,9 @@ PlayerRelations CGameState::getPlayerRelations( PlayerColor color1, PlayerColor
return PlayerRelations::ENEMIES;
}
void CGameState::apply(CPack *pack)
void CGameState::apply(CPackForClient *pack)
{
ui16 typ = CTypeList::getInstance().getTypeID(pack);
applier->getApplier(typ)->applyOnGS(this, pack);
pack->applyGs(this);
}
void CGameState::calculatePaths(const CGHeroInstance *hero, CPathsInfo &out)
@ -1222,82 +1206,6 @@ int3 CGameState::guardingCreaturePosition (int3 pos) const
return gs->map->guardingCreaturePositions[pos.z][pos.x][pos.y];
}
RumorState CGameState::pickNewRumor()
{
RumorState newRumor;
static const std::vector<RumorState::ERumorType> rumorTypes = {RumorState::TYPE_MAP, RumorState::TYPE_SPECIAL, RumorState::TYPE_RAND, RumorState::TYPE_RAND};
std::vector<RumorState::ERumorTypeSpecial> sRumorTypes = {
RumorState::RUMOR_OBELISKS, RumorState::RUMOR_ARTIFACTS, RumorState::RUMOR_ARMY, RumorState::RUMOR_INCOME};
if(map->grailPos.valid()) // Grail should always be on map, but I had related crash I didn't manage to reproduce
sRumorTypes.push_back(RumorState::RUMOR_GRAIL);
int rumorId = -1;
int rumorExtra = -1;
auto & rand = getRandomGenerator();
newRumor.type = *RandomGeneratorUtil::nextItem(rumorTypes, rand);
do
{
switch(newRumor.type)
{
case RumorState::TYPE_SPECIAL:
{
SThievesGuildInfo tgi;
obtainPlayersStats(tgi, 20);
rumorId = *RandomGeneratorUtil::nextItem(sRumorTypes, rand);
if(rumorId == RumorState::RUMOR_GRAIL)
{
rumorExtra = getTile(map->grailPos)->terType->getIndex();
break;
}
std::vector<PlayerColor> players = {};
switch(rumorId)
{
case RumorState::RUMOR_OBELISKS:
players = tgi.obelisks[0];
break;
case RumorState::RUMOR_ARTIFACTS:
players = tgi.artifacts[0];
break;
case RumorState::RUMOR_ARMY:
players = tgi.army[0];
break;
case RumorState::RUMOR_INCOME:
players = tgi.income[0];
break;
}
rumorExtra = RandomGeneratorUtil::nextItem(players, rand)->getNum();
break;
}
case RumorState::TYPE_MAP:
// Makes sure that map rumors only used if there enough rumors too choose from
if(!map->rumors.empty() && (map->rumors.size() > 1 || !currentRumor.last.count(RumorState::TYPE_MAP)))
{
rumorId = rand.nextInt((int)map->rumors.size() - 1);
break;
}
else
newRumor.type = RumorState::TYPE_RAND;
[[fallthrough]];
case RumorState::TYPE_RAND:
auto vector = VLC->generaltexth->findStringsWithPrefix("core.randtvrn");
rumorId = rand.nextInt((int)vector.size() - 1);
break;
}
}
while(!newRumor.update(rumorId, rumorExtra));
return newRumor;
}
bool CGameState::isVisible(int3 pos, const std::optional<PlayerColor> & player) const
{
if (!map->isInTheMap(pos))
@ -1396,7 +1304,7 @@ bool CGameState::checkForVictory(const PlayerColor & player, const EventConditio
}
case EventCondition::HAVE_ARTIFACT: //check if any hero has winning artifact
{
for(const auto & elem : p->heroes)
for(const auto & elem : p->getHeroes())
if(elem->hasArt(condition.objectType.as<ArtifactID>()))
return true;
return false;
@ -1430,7 +1338,7 @@ bool CGameState::checkForVictory(const PlayerColor & player, const EventConditio
}
else // any town
{
for (const CGTownInstance * t : p->towns)
for (const CGTownInstance * t : p->getTowns())
{
if (t->hasBuilt(condition.objectType.as<BuildingID>()))
return true;
@ -1575,9 +1483,9 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level)
if(level >= 0) //num of towns & num of heroes
{
//num of towns
FILL_FIELD(numOfTowns, g->second.towns.size())
FILL_FIELD(numOfTowns, g->second.getTowns().size())
//num of heroes
FILL_FIELD(numOfHeroes, g->second.heroes.size())
FILL_FIELD(numOfHeroes, g->second.getHeroes().size())
}
if(level >= 1) //best hero's portrait
{
@ -1649,7 +1557,7 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level)
if(playerInactive(player.second.color)) //do nothing for neutral player
continue;
CreatureID bestCre; //best creature's ID
for(const auto & elem : player.second.heroes)
for(const auto & elem : player.second.getHeroes())
{
for(const auto & it : elem->Slots())
{

View File

@ -38,9 +38,6 @@ class TavernHeroesPool;
struct SThievesGuildInfo;
class CRandomGenerator;
template<typename T> class CApplier;
class CBaseForGSApply;
struct UpgradeInfo
{
CreatureID oldID; //creature to be upgraded
@ -101,7 +98,7 @@ public:
/// picks next free hero type of the H3 hero init sequence -> chosen starting hero, then unused hero type randomly
HeroTypeID pickNextHeroType(const PlayerColor & owner);
void apply(CPack *pack);
void apply(CPackForClient *pack);
BattleField battleGetBattlefieldType(int3 tile, vstd::RNG & rand);
void fillUpgradeInfo(const CArmedInstance *obj, SlotID stackPos, UpgradeInfo &out) const override;
@ -111,7 +108,6 @@ public:
void calculatePaths(const std::shared_ptr<PathfinderConfig> & config) override;
int3 guardingCreaturePosition (int3 pos) const override;
std::vector<CGObjectInstance*> guardingCreatures (int3 pos) const;
RumorState pickNewRumor();
/// Gets a artifact ID randomly and removes the selected artifact from this handler.
ArtifactID pickRandomArtifact(vstd::RNG & rand, int flags);
@ -160,6 +156,8 @@ public:
h & day;
h & map;
h & players;
if (h.version < Handler::Version::PLAYER_STATE_OWNED_OBJECTS)
generateOwnedObjectsAfterDeserialize();
h & teams;
h & heroesPool;
h & globalEffects;
@ -199,6 +197,8 @@ private:
void initVisitingAndGarrisonedHeroes();
void initCampaign();
void generateOwnedObjectsAfterDeserialize();
// ----- bonus system handling -----
void buildBonusSystemTree();
@ -215,7 +215,6 @@ private:
UpgradeInfo fillUpgradeInfo(const CStackInstance &stack) const;
// ---- data -----
std::shared_ptr<CApplier<CBaseForGSApply>> applier;
Services * services;
/// Pointer to campaign state manager. Nullptr for single scenarios

View File

@ -536,7 +536,7 @@ void CGameStateCampaign::initHeroes()
}
assert(humanPlayer != PlayerColor::NEUTRAL);
std::vector<ConstTransitivePtr<CGHeroInstance> > & heroes = gameState->players[humanPlayer].heroes;
const auto & heroes = gameState->players[humanPlayer].getHeroes();
if (chosenBonus->info1 == 0xFFFD) //most powerful
{
@ -656,7 +656,7 @@ void CGameStateCampaign::initTowns()
if(gameState->scenarioOps->campState->formatVCMI())
newBuilding = BuildingID(chosenBonus->info1);
else
newBuilding = CBuildingHandler::campToERMU(chosenBonus->info1, town->getFaction(), town->builtBuildings);
newBuilding = CBuildingHandler::campToERMU(chosenBonus->info1, town->getFaction(), town->getBuildings());
// Build granted building & all prerequisites - e.g. Mages Guild Lvl 3 should also give Mages Guild Lvl 1 & 2
while(true)
@ -664,10 +664,10 @@ void CGameStateCampaign::initTowns()
if (newBuilding == BuildingID::NONE)
break;
if (town->builtBuildings.count(newBuilding) != 0)
if(town->hasBuilt(newBuilding))
break;
town->builtBuildings.insert(newBuilding);
town->addBuilding(newBuilding);
auto building = town->town->buildings.at(newBuilding);
newBuilding = building->upgrade;

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