1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-08-15 20:03:15 +02:00

Merge pull request #3335 from vcmi/master

Merge master -> beta
This commit is contained in:
Ivan Savenko
2023-12-17 19:29:01 +02:00
committed by GitHub
17 changed files with 172 additions and 128 deletions

View File

@@ -69,6 +69,7 @@
"vcmi.lobby.noPreview" : "no preview", "vcmi.lobby.noPreview" : "no preview",
"vcmi.lobby.noUnderground" : "no underground", "vcmi.lobby.noUnderground" : "no underground",
"vcmi.client.errors.missingCampaigns" : "{Missing data files}\n\nCampaigns data files were not found! You may be using incomplete or corrupted Heroes 3 data files. Please reinstall game data.",
"vcmi.server.errors.existingProcess" : "Another VCMI server process is running. Please terminate it before starting a new game.", "vcmi.server.errors.existingProcess" : "Another VCMI server process is running. Please terminate it before starting a new game.",
"vcmi.server.errors.modsToEnable" : "{Following mods are required}", "vcmi.server.errors.modsToEnable" : "{Following mods are required}",
"vcmi.server.errors.modsToDisable" : "{Following mods must be disabled}", "vcmi.server.errors.modsToDisable" : "{Following mods must be disabled}",

View File

@@ -69,6 +69,7 @@
"vcmi.lobby.noPreview" : "огляд недоступний", "vcmi.lobby.noPreview" : "огляд недоступний",
"vcmi.lobby.noUnderground" : "немає підземелля", "vcmi.lobby.noUnderground" : "немає підземелля",
"vcmi.client.errors.missingCampaigns" : "{Не вистачає файлів даних}\n\nФайли даних кампаній не знайдено! Можливо, ви використовуєте неповні або пошкоджені файли даних Heroes 3. Будь ласка, перевстановіть дані гри.",
"vcmi.server.errors.existingProcess" : "Працює інший процес vcmiserver, будь ласка, спочатку завершіть його", "vcmi.server.errors.existingProcess" : "Працює інший процес vcmiserver, будь ласка, спочатку завершіть його",
"vcmi.server.errors.modsToEnable" : "{Потрібні модифікації для завантаження гри}", "vcmi.server.errors.modsToEnable" : "{Потрібні модифікації для завантаження гри}",
"vcmi.server.errors.modsToDisable" : "{Модифікації що мають бути вимкнені}", "vcmi.server.errors.modsToDisable" : "{Модифікації що мають бути вимкнені}",

View File

@@ -10,7 +10,7 @@ android {
applicationId "is.xyz.vcmi" applicationId "is.xyz.vcmi"
minSdk 19 minSdk 19
targetSdk 33 targetSdk 33
versionCode 1410 versionCode 1412
versionName "1.4.1" versionName "1.4.1"
setProperty("archivesBaseName", "vcmi") setProperty("archivesBaseName", "vcmi")
} }

View File

@@ -257,10 +257,10 @@ int main(int argc, char * argv[])
}; };
testFile("DATA/HELP.TXT", "VCMI requires Heroes III: Shadow of Death or Heroes III: Complete data files to run!"); testFile("DATA/HELP.TXT", "VCMI requires Heroes III: Shadow of Death or Heroes III: Complete data files to run!");
testFile("MODS/VCMI/MOD.JSON", "VCMI installation is corrupted! Built-in mod was not found!");
testFile("DATA/PLAYERS.PAL", "Heroes III data files are missing or corruped! Please reinstall them.");
testFile("SPRITES/DEFAULT.DEF", "Heroes III data files are missing or corruped! Please reinstall them.");
testFile("DATA/TENTCOLR.TXT", "Heroes III: Restoration of Erathia (including HD Edition) data files are not supported!"); testFile("DATA/TENTCOLR.TXT", "Heroes III: Restoration of Erathia (including HD Edition) data files are not supported!");
testFile("MODS/VCMI/MOD.JSON", "VCMI installation is corrupted! Built-in mod was not found!");
testFile("DATA/PLAYERS.PAL", "Heroes III data files (Data/H3Bitmap.lod) are incomplete or corruped! Please reinstall them.");
testFile("SPRITES/DEFAULT.DEF", "Heroes III data files (Data/H3Sprite.lod) are incomplete or corruped! Please reinstall them.");
srand ( (unsigned int)time(nullptr) ); srand ( (unsigned int)time(nullptr) );
@@ -487,7 +487,8 @@ static void quitApplication()
vstd::clear_pointer(CSH); vstd::clear_pointer(CSH);
vstd::clear_pointer(VLC); vstd::clear_pointer(VLC);
vstd::clear_pointer(console);// should be removed after everything else since used by logging // sometimes leads to a hang. TODO: investigate
//vstd::clear_pointer(console);// should be removed after everything else since used by logging
if(!settings["session"]["headless"].Bool()) if(!settings["session"]["headless"].Bool())
GH.screenHandler().close(); GH.screenHandler().close();
@@ -501,10 +502,16 @@ static void quitApplication()
std::cout << "Ending...\n"; std::cout << "Ending...\n";
// this method is always called from event/network threads, which keep interface mutex locked // Perform quick exit without executing static destructors and let OS cleanup anything that we did not
// unlock it here to avoid assertion failure on GH destruction in exit() // We generally don't care about them and this leads to numerous issues, e.g.
GH.interfaceMutex.unlock(); // destruction of locked mutexes (fails an assertion), even in third-party libraries (as well as native libs on Android)
exit(0); // Android - std::quick_exit is available only starting from API level 21
// Mingw, macOS and iOS - std::quick_exit is unavailable (at least in current version of CI)
#if (defined(__ANDROID_API__) && __ANDROID_API__ < 21) || (defined(__MINGW32__)) || defined(VCMI_APPLE)
::exit(0);
#else
std::quick_exit(0);
#endif
} }
void handleQuit(bool ask) void handleQuit(bool ask)

View File

@@ -218,8 +218,7 @@ CHeroList::CEmptyHeroItem::CEmptyHeroItem()
CHeroList::CHeroItem::CHeroItem(CHeroList *parent, const CGHeroInstance * Hero) CHeroList::CHeroItem::CHeroItem(CHeroList *parent, const CGHeroInstance * Hero)
: CListItem(parent), : CListItem(parent),
hero(Hero), hero(Hero)
parentList(parent)
{ {
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
movement = std::make_shared<CAnimImage>(AnimationPath::builtin("IMOBIL"), 0, 0, 0, 1); movement = std::make_shared<CAnimImage>(AnimationPath::builtin("IMOBIL"), 0, 0, 0, 1);
@@ -285,19 +284,17 @@ void CHeroList::CHeroItem::gesture(bool on, const Point & initialPosition, const
const CGHeroInstance * heroLower = (heroPos > heroes.size() - 2) ? nullptr : heroes[heroPos + 1]; const CGHeroInstance * heroLower = (heroPos > heroes.size() - 2) ? nullptr : heroes[heroPos + 1];
std::vector<RadialMenuConfig> menuElements = { std::vector<RadialMenuConfig> menuElements = {
{ RadialMenuConfig::ITEM_ALT_NN, heroUpper != nullptr, "altUpTop", "vcmi.radialWheel.moveTop", [this, heroPos]() { RadialMenuConfig::ITEM_ALT_NN, heroUpper != nullptr, "altUpTop", "vcmi.radialWheel.moveTop", [heroPos]()
{ {
for (int i = heroPos; i > 0; i--) for (int i = heroPos; i > 0; i--)
LOCPLINT->localState->swapWanderingHero(i, i - 1); LOCPLINT->localState->swapWanderingHero(i, i - 1);
parentList->updateWidget();
} }, } },
{ RadialMenuConfig::ITEM_ALT_NW, heroUpper != nullptr, "altUp", "vcmi.radialWheel.moveUp", [this, heroPos](){LOCPLINT->localState->swapWanderingHero(heroPos, heroPos - 1); parentList->updateWidget(); } }, { RadialMenuConfig::ITEM_ALT_NW, heroUpper != nullptr, "altUp", "vcmi.radialWheel.moveUp", [heroPos](){LOCPLINT->localState->swapWanderingHero(heroPos, heroPos - 1); } },
{ RadialMenuConfig::ITEM_ALT_SW, heroLower != nullptr, "altDown", "vcmi.radialWheel.moveDown", [this, heroPos](){ LOCPLINT->localState->swapWanderingHero(heroPos, heroPos + 1); parentList->updateWidget(); } }, { RadialMenuConfig::ITEM_ALT_SW, heroLower != nullptr, "altDown", "vcmi.radialWheel.moveDown", [heroPos](){ LOCPLINT->localState->swapWanderingHero(heroPos, heroPos + 1); } },
{ RadialMenuConfig::ITEM_ALT_SS, heroLower != nullptr, "altDownBottom", "vcmi.radialWheel.moveBottom", [this, heroPos, heroes]() { RadialMenuConfig::ITEM_ALT_SS, heroLower != nullptr, "altDownBottom", "vcmi.radialWheel.moveBottom", [heroPos, heroes]()
{ {
for (int i = heroPos; i < heroes.size() - 1; i++) for (int i = heroPos; i < heroes.size() - 1; i++)
LOCPLINT->localState->swapWanderingHero(i, i + 1); LOCPLINT->localState->swapWanderingHero(i, i + 1);
parentList->updateWidget();
} }, } },
}; };
@@ -365,8 +362,7 @@ std::shared_ptr<CIntObject> CTownList::createItem(size_t index)
} }
CTownList::CTownItem::CTownItem(CTownList *parent, const CGTownInstance *Town): CTownList::CTownItem::CTownItem(CTownList *parent, const CGTownInstance *Town):
CListItem(parent), CListItem(parent)
parentList(parent)
{ {
const std::vector<const CGTownInstance *> towns = LOCPLINT->localState->getOwnedTowns(); const std::vector<const CGTownInstance *> towns = LOCPLINT->localState->getOwnedTowns();
townIndex = std::distance(towns.begin(), std::find(towns.begin(), towns.end(), Town)); townIndex = std::distance(towns.begin(), std::find(towns.begin(), towns.end(), Town));
@@ -430,15 +426,13 @@ void CTownList::CTownItem::gesture(bool on, const Point & initialPosition, const
{ {
for (int i = townIndex; i > 0; i--) for (int i = townIndex; i > 0; i--)
LOCPLINT->localState->swapOwnedTowns(i, i - 1); LOCPLINT->localState->swapOwnedTowns(i, i - 1);
parentList->updateWidget();
} }, } },
{ RadialMenuConfig::ITEM_ALT_NW, townUpperPos > -1, "altUp", "vcmi.radialWheel.moveUp", [this, townUpperPos](){LOCPLINT->localState->swapOwnedTowns(townIndex, townUpperPos); parentList->updateWidget(); } }, { RadialMenuConfig::ITEM_ALT_NW, townUpperPos > -1, "altUp", "vcmi.radialWheel.moveUp", [this, townUpperPos](){LOCPLINT->localState->swapOwnedTowns(townIndex, townUpperPos); } },
{ RadialMenuConfig::ITEM_ALT_SW, townLowerPos > -1, "altDown", "vcmi.radialWheel.moveDown", [this, townLowerPos](){ LOCPLINT->localState->swapOwnedTowns(townIndex, townLowerPos); parentList->updateWidget(); } }, { RadialMenuConfig::ITEM_ALT_SW, townLowerPos > -1, "altDown", "vcmi.radialWheel.moveDown", [this, townLowerPos](){ LOCPLINT->localState->swapOwnedTowns(townIndex, townLowerPos); } },
{ RadialMenuConfig::ITEM_ALT_SS, townLowerPos > -1, "altDownBottom", "vcmi.radialWheel.moveBottom", [this, towns]() { RadialMenuConfig::ITEM_ALT_SS, townLowerPos > -1, "altDownBottom", "vcmi.radialWheel.moveBottom", [this, towns]()
{ {
for (int i = townIndex; i < towns.size() - 1; i++) for (int i = townIndex; i < towns.size() - 1; i++)
LOCPLINT->localState->swapOwnedTowns(i, i + 1); LOCPLINT->localState->swapOwnedTowns(i, i + 1);
parentList->updateWidget();
} }, } },
}; };

View File

@@ -117,7 +117,6 @@ class CHeroList : public CList
std::shared_ptr<CAnimImage> movement; std::shared_ptr<CAnimImage> movement;
std::shared_ptr<CAnimImage> mana; std::shared_ptr<CAnimImage> mana;
std::shared_ptr<CAnimImage> portrait; std::shared_ptr<CAnimImage> portrait;
CHeroList *parentList;
public: public:
const CGHeroInstance * const hero; const CGHeroInstance * const hero;
@@ -152,7 +151,6 @@ class CTownList : public CList
class CTownItem : public CListItem class CTownItem : public CListItem
{ {
std::shared_ptr<CAnimImage> picture; std::shared_ptr<CAnimImage> picture;
CTownList *parentList;
public: public:
int townIndex; int townIndex;

View File

@@ -107,7 +107,8 @@ void BattleInterface::playIntroSoundAndUnlockInterface()
{ {
auto onIntroPlayed = [this]() auto onIntroPlayed = [this]()
{ {
if(LOCPLINT->battleInt) // Make sure that battle have not ended while intro was playing AND that a different one has not started
if(LOCPLINT->battleInt.get() == this)
onIntroSoundPlayed(); onIntroSoundPlayed();
}; };

View File

@@ -503,7 +503,7 @@ void OptionsTab::SelectionWindow::recreate()
int count = 0; int count = 0;
for(auto & elem : allowedHeroes) for(auto & elem : allowedHeroes)
{ {
CHero * type = VLC->heroh->objects[elem]; const CHero * type = elem.toHeroType();
if(type->heroClass->faction == selectedFaction) if(type->heroClass->faction == selectedFaction)
{ {
count++; count++;

View File

@@ -382,12 +382,29 @@ void CMainMenu::openCampaignLobby(std::shared_ptr<CampaignState> campaign)
void CMainMenu::openCampaignScreen(std::string name) void CMainMenu::openCampaignScreen(std::string name)
{ {
if(vstd::contains(CMainMenuConfig::get().getCampaigns().Struct(), name)) auto const & config = CMainMenuConfig::get().getCampaigns();
if(!vstd::contains(config.Struct(), name))
{ {
GH.windows().createAndPushWindow<CCampaignScreen>(CMainMenuConfig::get().getCampaigns(), name); logGlobal->error("Unknown campaign set: %s", name);
return; return;
} }
logGlobal->error("Unknown campaign set: %s", name);
bool campaignsFound = true;
for (auto const & entry : config[name]["items"].Vector())
{
ResourcePath resourceID(entry["file"].String(), EResType::CAMPAIGN);
if (!CResourceHandler::get()->existsResource(resourceID))
campaignsFound = false;
}
if (!campaignsFound)
{
CInfoWindow::showInfoDialog(CGI->generaltexth->translate("vcmi.client.errors.missingCampaigns"), std::vector<std::shared_ptr<CComponent>>(), PlayerColor(1));
return;
}
GH.windows().createAndPushWindow<CCampaignScreen>(config, name);
} }
void CMainMenu::startTutorial() void CMainMenu::startTutorial()

View File

@@ -118,11 +118,10 @@ void GameSettings::load(const JsonNode & input)
const JsonNode & GameSettings::getValue(EGameSettings option) const const JsonNode & GameSettings::getValue(EGameSettings option) const
{ {
assert(option < EGameSettings::OPTIONS_COUNT);
auto index = static_cast<size_t>(option); auto index = static_cast<size_t>(option);
assert(!gameSettings[index].isNull()); assert(!gameSettings.at(index).isNull());
return gameSettings[index]; return gameSettings.at(index);
} }
VCMI_LIB_NAMESPACE_END VCMI_LIB_NAMESPACE_END

View File

@@ -97,11 +97,7 @@ CObjectClassesHandler::CObjectClassesHandler()
#undef SET_HANDLER #undef SET_HANDLER
} }
CObjectClassesHandler::~CObjectClassesHandler() CObjectClassesHandler::~CObjectClassesHandler() = default;
{
for(auto * p : objects)
delete p;
}
std::vector<JsonNode> CObjectClassesHandler::loadLegacyData() std::vector<JsonNode> CObjectClassesHandler::loadLegacyData()
{ {
@@ -225,6 +221,9 @@ TObjectTypeHandler CObjectClassesHandler::loadSubObjectFromJson(const std::strin
return createdObject; return createdObject;
} }
ObjectClass::ObjectClass() = default;
ObjectClass::~ObjectClass() = default;
std::string ObjectClass::getJsonKey() const std::string ObjectClass::getJsonKey() const
{ {
return modScope + ':' + identifier; return modScope + ':' + identifier;
@@ -240,9 +239,9 @@ std::string ObjectClass::getNameTranslated() const
return VLC->generaltexth->translate(getNameTextID()); return VLC->generaltexth->translate(getNameTextID());
} }
ObjectClass * CObjectClassesHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & name, size_t index) std::unique_ptr<ObjectClass> CObjectClassesHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & name, size_t index)
{ {
auto * obj = new ObjectClass(); auto obj = std::make_unique<ObjectClass>();
obj->modScope = scope; obj->modScope = scope;
obj->identifier = name; obj->identifier = name;
@@ -263,31 +262,31 @@ ObjectClass * CObjectClassesHandler::loadFromJson(const std::string & scope, con
if ( subMeta != "core") if ( subMeta != "core")
logMod->warn("Object %s:%s.%s - attempt to load object with preset index! This option is reserved for built-in mod", subMeta, name, subData.first ); logMod->warn("Object %s:%s.%s - attempt to load object with preset index! This option is reserved for built-in mod", subMeta, name, subData.first );
size_t subIndex = subData.second["index"].Integer(); size_t subIndex = subData.second["index"].Integer();
loadSubObject(subData.second.meta, subData.first, subData.second, obj, subIndex); loadSubObject(subData.second.meta, subData.first, subData.second, obj.get(), subIndex);
} }
else else
loadSubObject(subData.second.meta, subData.first, subData.second, obj); loadSubObject(subData.second.meta, subData.first, subData.second, obj.get());
} }
if (obj->id == MapObjectID::MONOLITH_TWO_WAY) if (obj->id == MapObjectID::MONOLITH_TWO_WAY)
generateExtraMonolithsForRMG(obj); generateExtraMonolithsForRMG(obj.get());
return obj; return obj;
} }
void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data) void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
{ {
auto * object = loadFromJson(scope, data, name, objects.size()); objects.push_back(loadFromJson(scope, data, name, objects.size()));
objects.push_back(object);
VLC->identifiersHandler->registerObject(scope, "object", name, object->id); VLC->identifiersHandler->registerObject(scope, "object", name, objects.back()->id);
} }
void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
{ {
auto * object = loadFromJson(scope, data, name, index); assert(objects[index] == nullptr); // ensure that this id was not loaded before
assert(objects[(si32)index] == nullptr); // ensure that this id was not loaded before
objects[static_cast<si32>(index)] = object; objects[index] = loadFromJson(scope, data, name, index);
VLC->identifiersHandler->registerObject(scope, "object", name, object->id); VLC->identifiersHandler->registerObject(scope, "object", name, objects[index]->id);
} }
void CObjectClassesHandler::loadSubObject(const std::string & identifier, JsonNode config, MapObjectID ID, MapObjectSubID subID) void CObjectClassesHandler::loadSubObject(const std::string & identifier, JsonNode config, MapObjectID ID, MapObjectSubID subID)
@@ -299,7 +298,7 @@ void CObjectClassesHandler::loadSubObject(const std::string & identifier, JsonNo
objects[ID.getNum()]->objects.resize(subID.getNum()+1); objects[ID.getNum()]->objects.resize(subID.getNum()+1);
JsonUtils::inherit(config, objects.at(ID.getNum())->base); JsonUtils::inherit(config, objects.at(ID.getNum())->base);
loadSubObject(config.meta, identifier, config, objects[ID.getNum()], subID.getNum()); loadSubObject(config.meta, identifier, config, objects[ID.getNum()].get(), subID.getNum());
} }
void CObjectClassesHandler::removeSubObject(MapObjectID ID, MapObjectSubID subID) void CObjectClassesHandler::removeSubObject(MapObjectID ID, MapObjectSubID subID)
@@ -335,7 +334,7 @@ TObjectTypeHandler CObjectClassesHandler::getHandlerFor(const std::string & scop
std::optional<si32> id = VLC->identifiers()->getIdentifier(scope, "object", type); std::optional<si32> id = VLC->identifiers()->getIdentifier(scope, "object", type);
if(id) if(id)
{ {
auto * object = objects[id.value()]; const auto & object = objects[id.value()];
std::optional<si32> subID = VLC->identifiers()->getIdentifier(scope, object->getJsonKey(), subtype); std::optional<si32> subID = VLC->identifiers()->getIdentifier(scope, object->getJsonKey(), subtype);
if (subID) if (subID)
@@ -356,7 +355,7 @@ std::set<MapObjectID> CObjectClassesHandler::knownObjects() const
{ {
std::set<MapObjectID> ret; std::set<MapObjectID> ret;
for(auto * entry : objects) for(auto & entry : objects)
if (entry) if (entry)
ret.insert(entry->id); ret.insert(entry->id);
@@ -406,7 +405,7 @@ void CObjectClassesHandler::beforeValidate(JsonNode & object)
void CObjectClassesHandler::afterLoadFinalization() void CObjectClassesHandler::afterLoadFinalization()
{ {
for(auto * entry : objects) for(auto & entry : objects)
{ {
if (!entry) if (!entry)
continue; continue;

View File

@@ -45,7 +45,7 @@ class CGObjectInstance;
using TObjectTypeHandler = std::shared_ptr<AObjectTypeHandler>; using TObjectTypeHandler = std::shared_ptr<AObjectTypeHandler>;
/// Class responsible for creation of adventure map objects of specific type /// Class responsible for creation of adventure map objects of specific type
class DLL_LINKAGE ObjectClass class DLL_LINKAGE ObjectClass : boost::noncopyable
{ {
public: public:
std::string modScope; std::string modScope;
@@ -57,7 +57,8 @@ public:
JsonNode base; JsonNode base;
std::vector<TObjectTypeHandler> objects; std::vector<TObjectTypeHandler> objects;
ObjectClass() = default; ObjectClass();
~ObjectClass();
std::string getJsonKey() const; std::string getJsonKey() const;
std::string getNameTextID() const; std::string getNameTextID() const;
@@ -65,10 +66,10 @@ public:
}; };
/// Main class responsible for creation of all adventure map objects /// Main class responsible for creation of all adventure map objects
class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase, boost::noncopyable
{ {
/// list of object handlers, each of them handles only one type /// list of object handlers, each of them handles only one type
std::vector<ObjectClass * > objects; std::vector< std::unique_ptr<ObjectClass> > objects;
/// map that is filled during contruction with all known handlers. Not serializeable due to usage of std::function /// map that is filled during contruction with all known handlers. Not serializeable due to usage of std::function
std::map<std::string, std::function<TObjectTypeHandler()> > handlerConstructors; std::map<std::string, std::function<TObjectTypeHandler()> > handlerConstructors;
@@ -82,7 +83,7 @@ class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase
void loadSubObject(const std::string & scope, const std::string & identifier, const JsonNode & entry, ObjectClass * obj); void loadSubObject(const std::string & scope, const std::string & identifier, const JsonNode & entry, ObjectClass * obj);
void loadSubObject(const std::string & scope, const std::string & identifier, const JsonNode & entry, ObjectClass * obj, size_t index); void loadSubObject(const std::string & scope, const std::string & identifier, const JsonNode & entry, ObjectClass * obj, size_t index);
ObjectClass * loadFromJson(const std::string & scope, const JsonNode & json, const std::string & name, size_t index); std::unique_ptr<ObjectClass> loadFromJson(const std::string & scope, const JsonNode & json, const std::string & name, size_t index);
void generateExtraMonolithsForRMG(ObjectClass * container); void generateExtraMonolithsForRMG(ObjectClass * container);

View File

@@ -77,7 +77,7 @@ void CTownInstanceConstructor::afterLoadFinalization()
{ {
filters[entry.first] = LogicalExpression<BuildingID>(entry.second, [this](const JsonNode & node) filters[entry.first] = LogicalExpression<BuildingID>(entry.second, [this](const JsonNode & node)
{ {
return BuildingID(VLC->identifiers()->getIdentifier("building." + faction->getJsonKey(), node.Vector()[0]).value()); return BuildingID(VLC->identifiers()->getIdentifier("building." + faction->getJsonKey(), node.Vector()[0]).value_or(-1));
}); });
} }
} }

View File

@@ -16,6 +16,7 @@
#include "../CGeneralTextHandler.h" #include "../CGeneralTextHandler.h"
#include "../gameState/CGameState.h" #include "../gameState/CGameState.h"
#include "../CPlayerState.h" #include "../CPlayerState.h"
#include "../MetaString.h"
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
@@ -110,7 +111,12 @@ void CArmedInstance::updateMoraleBonusFromArmy()
else if (!factions.empty()) // no bonus from empty garrison else if (!factions.empty()) // no bonus from empty garrison
{ {
b->val = 2 - static_cast<si32>(factionsInArmy); b->val = 2 - static_cast<si32>(factionsInArmy);
description = boost::str(boost::format(VLC->generaltexth->arraytxt[114]) % factionsInArmy % b->val); //Troops of %d alignments %d MetaString formatter;
formatter.appendTextID("core.arraytxt.114"); //Troops of %d alignments %d
formatter.replaceNumber(factionsInArmy);
formatter.replaceNumber(b->val);
description = formatter.toString();
description = description.substr(0, description.size()-3);//trim value description = description.substr(0, description.size()-3);//trim value
} }

View File

@@ -160,7 +160,7 @@ void CGDwelling::pickRandomObject(CRandomGenerator & rand)
if (subID == MapObjectSubID()) if (subID == MapObjectSubID())
{ {
logGlobal->error("Error: failed to find dwelling for %s of level %d", (*VLC->townh)[faction]->getNameTranslated(), int(level)); logGlobal->error("Error: failed to find dwelling for %s of level %d", (*VLC->townh)[faction]->getNameTranslated(), int(level));
ID = Obj::CREATURE_GENERATOR4; ID = Obj::CREATURE_GENERATOR1;
subID = *RandomGeneratorUtil::nextItem(VLC->objtypeh->knownSubObjects(Obj::CREATURE_GENERATOR1), rand); subID = *RandomGeneratorUtil::nextItem(VLC->objtypeh->knownSubObjects(Obj::CREATURE_GENERATOR1), rand);
} }

View File

@@ -1145,42 +1145,47 @@ CGObjectInstance * CMapLoaderH3M::readWitchHut(const int3 & position, std::share
auto * object = readGeneric(position, objectTemplate); auto * object = readGeneric(position, objectTemplate);
auto * rewardable = dynamic_cast<CRewardableObject*>(object); auto * rewardable = dynamic_cast<CRewardableObject*>(object);
assert(rewardable);
// AB and later maps have allowed abilities defined in H3M // AB and later maps have allowed abilities defined in H3M
if(features.levelAB) if(features.levelAB)
{ {
std::set<SecondarySkill> allowedAbilities; std::set<SecondarySkill> allowedAbilities;
reader->readBitmaskSkills(allowedAbilities, false); reader->readBitmaskSkills(allowedAbilities, false);
if(allowedAbilities.size() != 1) if (rewardable)
{ {
auto defaultAllowed = VLC->skillh->getDefaultAllowed(); if(allowedAbilities.size() != 1)
{
auto defaultAllowed = VLC->skillh->getDefaultAllowed();
for(int skillID = features.skillsCount; skillID < defaultAllowed.size(); ++skillID) for(int skillID = features.skillsCount; skillID < defaultAllowed.size(); ++skillID)
if(defaultAllowed.count(skillID)) if(defaultAllowed.count(skillID))
allowedAbilities.insert(SecondarySkill(skillID)); allowedAbilities.insert(SecondarySkill(skillID));
} }
JsonNode variable; JsonNode variable;
if (allowedAbilities.size() == 1) if (allowedAbilities.size() == 1)
{ {
variable.String() = VLC->skills()->getById(*allowedAbilities.begin())->getJsonKey(); variable.String() = VLC->skills()->getById(*allowedAbilities.begin())->getJsonKey();
}
else
{
JsonVector anyOfList;
for (auto const & skill : allowedAbilities)
{
JsonNode entry;
entry.String() = VLC->skills()->getById(skill)->getJsonKey();
anyOfList.push_back(entry);
}
variable["anyOf"].Vector() = anyOfList;
}
variable.setMeta(ModScope::scopeGame()); // list may include skills from all mods
rewardable->configuration.presetVariable("secondarySkill", "gainedSkill", variable);
} }
else else
{ {
JsonVector anyOfList; logGlobal->warn("Failed to set allowed secondary skills to a Witch Hut! Object is not rewardable!");
for (auto const & skill : allowedAbilities)
{
JsonNode entry;
entry.String() = VLC->skills()->getById(skill)->getJsonKey();
anyOfList.push_back(entry);
}
variable["anyOf"].Vector() = anyOfList;
} }
variable.setMeta(ModScope::scopeGame()); // list may include skills from all mods
rewardable->configuration.presetVariable("secondarySkill", "gainedSkill", variable);
} }
return object; return object;
} }
@@ -1201,45 +1206,52 @@ CGObjectInstance * CMapLoaderH3M::readScholar(const int3 & position, std::shared
auto bonusType = static_cast<ScholarBonusType>(bonusTypeRaw); auto bonusType = static_cast<ScholarBonusType>(bonusTypeRaw);
auto bonusID = reader->readUInt8(); auto bonusID = reader->readUInt8();
switch (bonusType) if (rewardable)
{ {
case ScholarBonusType::PRIM_SKILL: switch (bonusType)
{ {
JsonNode variable; case ScholarBonusType::PRIM_SKILL:
JsonNode dice; {
variable.String() = NPrimarySkill::names[bonusID]; JsonNode variable;
variable.setMeta(ModScope::scopeGame()); JsonNode dice;
dice.Integer() = 80; variable.String() = NPrimarySkill::names[bonusID];
rewardable->configuration.presetVariable("primarySkill", "gainedStat", variable); variable.setMeta(ModScope::scopeGame());
rewardable->configuration.presetVariable("dice", "0", dice); dice.Integer() = 80;
break; rewardable->configuration.presetVariable("primarySkill", "gainedStat", variable);
rewardable->configuration.presetVariable("dice", "0", dice);
break;
}
case ScholarBonusType::SECONDARY_SKILL:
{
JsonNode variable;
JsonNode dice;
variable.String() = VLC->skills()->getByIndex(bonusID)->getJsonKey();
variable.setMeta(ModScope::scopeGame());
dice.Integer() = 50;
rewardable->configuration.presetVariable("secondarySkill", "gainedSkill", variable);
rewardable->configuration.presetVariable("dice", "0", dice);
break;
}
case ScholarBonusType::SPELL:
{
JsonNode variable;
JsonNode dice;
variable.String() = VLC->spells()->getByIndex(bonusID)->getJsonKey();
variable.setMeta(ModScope::scopeGame());
dice.Integer() = 20;
rewardable->configuration.presetVariable("spell", "gainedSpell", variable);
rewardable->configuration.presetVariable("dice", "0", dice);
break;
}
case ScholarBonusType::RANDOM:
break;// No-op
default:
logGlobal->warn("Map '%s': Invalid Scholar settings! Ignoring...", mapName);
} }
case ScholarBonusType::SECONDARY_SKILL: }
{ else
JsonNode variable; {
JsonNode dice; logGlobal->warn("Failed to set reward parameters for a Scholar! Object is not rewardable!");
variable.String() = VLC->skills()->getByIndex(bonusID)->getJsonKey();
variable.setMeta(ModScope::scopeGame());
dice.Integer() = 50;
rewardable->configuration.presetVariable("secondarySkill", "gainedSkill", variable);
rewardable->configuration.presetVariable("dice", "0", dice);
break;
}
case ScholarBonusType::SPELL:
{
JsonNode variable;
JsonNode dice;
variable.String() = VLC->spells()->getByIndex(bonusID)->getJsonKey();
variable.setMeta(ModScope::scopeGame());
dice.Integer() = 20;
rewardable->configuration.presetVariable("spell", "gainedSpell", variable);
rewardable->configuration.presetVariable("dice", "0", dice);
break;
}
case ScholarBonusType::RANDOM:
break;// No-op
default:
logGlobal->warn("Map '%s': Invalid Scholar settings! Ignoring...", mapName);
} }
reader->skipZero(6); reader->skipZero(6);
@@ -1362,16 +1374,21 @@ CGObjectInstance * CMapLoaderH3M::readShrine(const int3 & position, std::shared_
auto * object = readGeneric(position, objectTemplate); auto * object = readGeneric(position, objectTemplate);
auto * rewardable = dynamic_cast<CRewardableObject*>(object); auto * rewardable = dynamic_cast<CRewardableObject*>(object);
assert(rewardable);
SpellID spell = reader->readSpell32(); SpellID spell = reader->readSpell32();
if(spell != SpellID::NONE) if (rewardable)
{ {
JsonNode variable; if(spell != SpellID::NONE)
variable.String() = VLC->spells()->getById(spell)->getJsonKey(); {
variable.setMeta(ModScope::scopeGame()); // list may include spells from all mods JsonNode variable;
rewardable->configuration.presetVariable("spell", "gainedSpell", variable); variable.String() = VLC->spells()->getById(spell)->getJsonKey();
variable.setMeta(ModScope::scopeGame()); // list may include spells from all mods
rewardable->configuration.presetVariable("spell", "gainedSpell", variable);
}
}
else
{
logGlobal->warn("Failed to set selected spell to a Shrine!. Object is not rewardable!");
} }
return object; return object;
} }

View File

@@ -196,7 +196,10 @@ void TurnOrderProcessor::doStartNewDay()
} }
if(!activePlayer) if(!activePlayer)
{
gameHandler->gameLobby()->setState(EServerState::GAMEPLAY_ENDED); gameHandler->gameLobby()->setState(EServerState::GAMEPLAY_ENDED);
return;
}
std::swap(actedPlayers, awaitingPlayers); std::swap(actedPlayers, awaitingPlayers);