diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index ae0755066..e24125ce0 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -69,6 +69,7 @@ "vcmi.lobby.noPreview" : "no preview", "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.modsToEnable" : "{Following mods are required}", "vcmi.server.errors.modsToDisable" : "{Following mods must be disabled}", diff --git a/Mods/vcmi/config/vcmi/ukrainian.json b/Mods/vcmi/config/vcmi/ukrainian.json index be534d429..c22aebf6e 100644 --- a/Mods/vcmi/config/vcmi/ukrainian.json +++ b/Mods/vcmi/config/vcmi/ukrainian.json @@ -69,6 +69,7 @@ "vcmi.lobby.noPreview" : "огляд недоступний", "vcmi.lobby.noUnderground" : "немає підземелля", + "vcmi.client.errors.missingCampaigns" : "{Не вистачає файлів даних}\n\nФайли даних кампаній не знайдено! Можливо, ви використовуєте неповні або пошкоджені файли даних Heroes 3. Будь ласка, перевстановіть дані гри.", "vcmi.server.errors.existingProcess" : "Працює інший процес vcmiserver, будь ласка, спочатку завершіть його", "vcmi.server.errors.modsToEnable" : "{Потрібні модифікації для завантаження гри}", "vcmi.server.errors.modsToDisable" : "{Модифікації що мають бути вимкнені}", diff --git a/android/vcmi-app/build.gradle b/android/vcmi-app/build.gradle index f46b2f8e8..0dfea5a58 100644 --- a/android/vcmi-app/build.gradle +++ b/android/vcmi-app/build.gradle @@ -10,7 +10,7 @@ android { applicationId "is.xyz.vcmi" minSdk 19 targetSdk 33 - versionCode 1410 + versionCode 1412 versionName "1.4.1" setProperty("archivesBaseName", "vcmi") } diff --git a/client/CMT.cpp b/client/CMT.cpp index b6ca35859..9da5850bf 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -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("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("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) ); @@ -487,7 +487,8 @@ static void quitApplication() vstd::clear_pointer(CSH); 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()) GH.screenHandler().close(); @@ -501,10 +502,16 @@ static void quitApplication() std::cout << "Ending...\n"; - // this method is always called from event/network threads, which keep interface mutex locked - // unlock it here to avoid assertion failure on GH destruction in exit() - GH.interfaceMutex.unlock(); - exit(0); + // Perform quick exit without executing static destructors and let OS cleanup anything that we did not + // We generally don't care about them and this leads to numerous issues, e.g. + // destruction of locked mutexes (fails an assertion), even in third-party libraries (as well as native libs on Android) + // 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) diff --git a/client/adventureMap/CList.cpp b/client/adventureMap/CList.cpp index 49f3a383f..1be47bef6 100644 --- a/client/adventureMap/CList.cpp +++ b/client/adventureMap/CList.cpp @@ -218,8 +218,7 @@ CHeroList::CEmptyHeroItem::CEmptyHeroItem() CHeroList::CHeroItem::CHeroItem(CHeroList *parent, const CGHeroInstance * Hero) : CListItem(parent), - hero(Hero), - parentList(parent) + hero(Hero) { OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); movement = std::make_shared(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]; std::vector 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--) 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_SW, heroLower != nullptr, "altDown", "vcmi.radialWheel.moveDown", [this, heroPos](){ LOCPLINT->localState->swapWanderingHero(heroPos, heroPos + 1); parentList->updateWidget(); } }, - { RadialMenuConfig::ITEM_ALT_SS, heroLower != nullptr, "altDownBottom", "vcmi.radialWheel.moveBottom", [this, heroPos, heroes]() + { 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", [heroPos](){ LOCPLINT->localState->swapWanderingHero(heroPos, heroPos + 1); } }, + { RadialMenuConfig::ITEM_ALT_SS, heroLower != nullptr, "altDownBottom", "vcmi.radialWheel.moveBottom", [heroPos, heroes]() { for (int i = heroPos; i < heroes.size() - 1; i++) LOCPLINT->localState->swapWanderingHero(i, i + 1); - parentList->updateWidget(); } }, }; @@ -365,8 +362,7 @@ std::shared_ptr CTownList::createItem(size_t index) } CTownList::CTownItem::CTownItem(CTownList *parent, const CGTownInstance *Town): - CListItem(parent), - parentList(parent) + CListItem(parent) { const std::vector towns = LOCPLINT->localState->getOwnedTowns(); 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--) 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_SW, townLowerPos > -1, "altDown", "vcmi.radialWheel.moveDown", [this, townLowerPos](){ LOCPLINT->localState->swapOwnedTowns(townIndex, townLowerPos); 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); } }, { RadialMenuConfig::ITEM_ALT_SS, townLowerPos > -1, "altDownBottom", "vcmi.radialWheel.moveBottom", [this, towns]() { for (int i = townIndex; i < towns.size() - 1; i++) LOCPLINT->localState->swapOwnedTowns(i, i + 1); - parentList->updateWidget(); } }, }; diff --git a/client/adventureMap/CList.h b/client/adventureMap/CList.h index 01312e725..632abb941 100644 --- a/client/adventureMap/CList.h +++ b/client/adventureMap/CList.h @@ -117,7 +117,6 @@ class CHeroList : public CList std::shared_ptr movement; std::shared_ptr mana; std::shared_ptr portrait; - CHeroList *parentList; public: const CGHeroInstance * const hero; @@ -152,7 +151,6 @@ class CTownList : public CList class CTownItem : public CListItem { std::shared_ptr picture; - CTownList *parentList; public: int townIndex; diff --git a/client/battle/BattleInterface.cpp b/client/battle/BattleInterface.cpp index ab7cffcb4..fc64d5fdc 100644 --- a/client/battle/BattleInterface.cpp +++ b/client/battle/BattleInterface.cpp @@ -107,7 +107,8 @@ void BattleInterface::playIntroSoundAndUnlockInterface() { 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(); }; diff --git a/client/lobby/OptionsTab.cpp b/client/lobby/OptionsTab.cpp index e7698ba48..246104a76 100644 --- a/client/lobby/OptionsTab.cpp +++ b/client/lobby/OptionsTab.cpp @@ -503,7 +503,7 @@ void OptionsTab::SelectionWindow::recreate() int count = 0; for(auto & elem : allowedHeroes) { - CHero * type = VLC->heroh->objects[elem]; + const CHero * type = elem.toHeroType(); if(type->heroClass->faction == selectedFaction) { count++; diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index 6e9cfdada..6d982f697 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/client/mainmenu/CMainMenu.cpp @@ -382,12 +382,29 @@ void CMainMenu::openCampaignLobby(std::shared_ptr campaign) 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(CMainMenuConfig::get().getCampaigns(), name); + logGlobal->error("Unknown campaign set: %s", name); 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>(), PlayerColor(1)); + return; + } + + GH.windows().createAndPushWindow(config, name); } void CMainMenu::startTutorial() diff --git a/lib/GameSettings.cpp b/lib/GameSettings.cpp index 15a593e1b..6df29e210 100644 --- a/lib/GameSettings.cpp +++ b/lib/GameSettings.cpp @@ -118,11 +118,10 @@ void GameSettings::load(const JsonNode & input) const JsonNode & GameSettings::getValue(EGameSettings option) const { - assert(option < EGameSettings::OPTIONS_COUNT); auto index = static_cast(option); - assert(!gameSettings[index].isNull()); - return gameSettings[index]; + assert(!gameSettings.at(index).isNull()); + return gameSettings.at(index); } VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjectConstructors/CObjectClassesHandler.cpp b/lib/mapObjectConstructors/CObjectClassesHandler.cpp index 24d683498..11c79e733 100644 --- a/lib/mapObjectConstructors/CObjectClassesHandler.cpp +++ b/lib/mapObjectConstructors/CObjectClassesHandler.cpp @@ -97,11 +97,7 @@ CObjectClassesHandler::CObjectClassesHandler() #undef SET_HANDLER } -CObjectClassesHandler::~CObjectClassesHandler() -{ - for(auto * p : objects) - delete p; -} +CObjectClassesHandler::~CObjectClassesHandler() = default; std::vector CObjectClassesHandler::loadLegacyData() { @@ -225,6 +221,9 @@ TObjectTypeHandler CObjectClassesHandler::loadSubObjectFromJson(const std::strin return createdObject; } +ObjectClass::ObjectClass() = default; +ObjectClass::~ObjectClass() = default; + std::string ObjectClass::getJsonKey() const { return modScope + ':' + identifier; @@ -240,9 +239,9 @@ std::string ObjectClass::getNameTranslated() const 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 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(); obj->modScope = scope; obj->identifier = name; @@ -263,31 +262,31 @@ ObjectClass * CObjectClassesHandler::loadFromJson(const std::string & scope, con 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 ); 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 - 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) - generateExtraMonolithsForRMG(obj); + generateExtraMonolithsForRMG(obj.get()); return obj; } void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data) { - auto * object = loadFromJson(scope, data, name, objects.size()); - objects.push_back(object); - VLC->identifiersHandler->registerObject(scope, "object", name, object->id); + objects.push_back(loadFromJson(scope, data, name, objects.size())); + + VLC->identifiersHandler->registerObject(scope, "object", name, objects.back()->id); } void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) { - auto * object = loadFromJson(scope, data, name, index); - assert(objects[(si32)index] == nullptr); // ensure that this id was not loaded before - objects[static_cast(index)] = object; - VLC->identifiersHandler->registerObject(scope, "object", name, object->id); + assert(objects[index] == nullptr); // ensure that this id was not loaded before + + objects[index] = loadFromJson(scope, data, name, index); + VLC->identifiersHandler->registerObject(scope, "object", name, objects[index]->id); } 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); 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) @@ -335,7 +334,7 @@ TObjectTypeHandler CObjectClassesHandler::getHandlerFor(const std::string & scop std::optional id = VLC->identifiers()->getIdentifier(scope, "object", type); if(id) { - auto * object = objects[id.value()]; + const auto & object = objects[id.value()]; std::optional subID = VLC->identifiers()->getIdentifier(scope, object->getJsonKey(), subtype); if (subID) @@ -356,7 +355,7 @@ std::set CObjectClassesHandler::knownObjects() const { std::set ret; - for(auto * entry : objects) + for(auto & entry : objects) if (entry) ret.insert(entry->id); @@ -406,7 +405,7 @@ void CObjectClassesHandler::beforeValidate(JsonNode & object) void CObjectClassesHandler::afterLoadFinalization() { - for(auto * entry : objects) + for(auto & entry : objects) { if (!entry) continue; diff --git a/lib/mapObjectConstructors/CObjectClassesHandler.h b/lib/mapObjectConstructors/CObjectClassesHandler.h index 173fe8748..cb65b23eb 100644 --- a/lib/mapObjectConstructors/CObjectClassesHandler.h +++ b/lib/mapObjectConstructors/CObjectClassesHandler.h @@ -45,7 +45,7 @@ class CGObjectInstance; using TObjectTypeHandler = std::shared_ptr; /// Class responsible for creation of adventure map objects of specific type -class DLL_LINKAGE ObjectClass +class DLL_LINKAGE ObjectClass : boost::noncopyable { public: std::string modScope; @@ -57,7 +57,8 @@ public: JsonNode base; std::vector objects; - ObjectClass() = default; + ObjectClass(); + ~ObjectClass(); std::string getJsonKey() const; std::string getNameTextID() const; @@ -65,10 +66,10 @@ public: }; /// 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 - std::vector objects; + std::vector< std::unique_ptr > objects; /// map that is filled during contruction with all known handlers. Not serializeable due to usage of std::function std::map > 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, size_t index); - ObjectClass * loadFromJson(const std::string & scope, const JsonNode & json, const std::string & name, size_t index); + std::unique_ptr loadFromJson(const std::string & scope, const JsonNode & json, const std::string & name, size_t index); void generateExtraMonolithsForRMG(ObjectClass * container); diff --git a/lib/mapObjectConstructors/CommonConstructors.cpp b/lib/mapObjectConstructors/CommonConstructors.cpp index 6a558b96a..f8670ce31 100644 --- a/lib/mapObjectConstructors/CommonConstructors.cpp +++ b/lib/mapObjectConstructors/CommonConstructors.cpp @@ -77,7 +77,7 @@ void CTownInstanceConstructor::afterLoadFinalization() { filters[entry.first] = LogicalExpression(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)); }); } } diff --git a/lib/mapObjects/CArmedInstance.cpp b/lib/mapObjects/CArmedInstance.cpp index 6c588df7b..1420b24bd 100644 --- a/lib/mapObjects/CArmedInstance.cpp +++ b/lib/mapObjects/CArmedInstance.cpp @@ -16,6 +16,7 @@ #include "../CGeneralTextHandler.h" #include "../gameState/CGameState.h" #include "../CPlayerState.h" +#include "../MetaString.h" VCMI_LIB_NAMESPACE_BEGIN @@ -110,7 +111,12 @@ void CArmedInstance::updateMoraleBonusFromArmy() else if (!factions.empty()) // no bonus from empty garrison { b->val = 2 - static_cast(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 } diff --git a/lib/mapObjects/CGDwelling.cpp b/lib/mapObjects/CGDwelling.cpp index 4eeaad6e5..88f3578c5 100644 --- a/lib/mapObjects/CGDwelling.cpp +++ b/lib/mapObjects/CGDwelling.cpp @@ -160,7 +160,7 @@ void CGDwelling::pickRandomObject(CRandomGenerator & rand) if (subID == MapObjectSubID()) { 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); } diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 2d99c745f..605cf52aa 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -1145,42 +1145,47 @@ CGObjectInstance * CMapLoaderH3M::readWitchHut(const int3 & position, std::share auto * object = readGeneric(position, objectTemplate); auto * rewardable = dynamic_cast(object); - assert(rewardable); - // AB and later maps have allowed abilities defined in H3M if(features.levelAB) { std::set allowedAbilities; 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) - if(defaultAllowed.count(skillID)) - allowedAbilities.insert(SecondarySkill(skillID)); - } + for(int skillID = features.skillsCount; skillID < defaultAllowed.size(); ++skillID) + if(defaultAllowed.count(skillID)) + allowedAbilities.insert(SecondarySkill(skillID)); + } - JsonNode variable; - if (allowedAbilities.size() == 1) - { - variable.String() = VLC->skills()->getById(*allowedAbilities.begin())->getJsonKey(); + JsonNode variable; + if (allowedAbilities.size() == 1) + { + 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 { - JsonVector anyOfList; - for (auto const & skill : allowedAbilities) - { - JsonNode entry; - entry.String() = VLC->skills()->getById(skill)->getJsonKey(); - anyOfList.push_back(entry); - } - variable["anyOf"].Vector() = anyOfList; + logGlobal->warn("Failed to set allowed secondary skills to a Witch Hut! Object is not rewardable!"); } - - variable.setMeta(ModScope::scopeGame()); // list may include skills from all mods - rewardable->configuration.presetVariable("secondarySkill", "gainedSkill", variable); } return object; } @@ -1201,45 +1206,52 @@ CGObjectInstance * CMapLoaderH3M::readScholar(const int3 & position, std::shared auto bonusType = static_cast(bonusTypeRaw); auto bonusID = reader->readUInt8(); - switch (bonusType) + if (rewardable) { - case ScholarBonusType::PRIM_SKILL: + switch (bonusType) { - JsonNode variable; - JsonNode dice; - variable.String() = NPrimarySkill::names[bonusID]; - variable.setMeta(ModScope::scopeGame()); - dice.Integer() = 80; - rewardable->configuration.presetVariable("primarySkill", "gainedStat", variable); - rewardable->configuration.presetVariable("dice", "0", dice); - break; + case ScholarBonusType::PRIM_SKILL: + { + JsonNode variable; + JsonNode dice; + variable.String() = NPrimarySkill::names[bonusID]; + variable.setMeta(ModScope::scopeGame()); + dice.Integer() = 80; + 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: - { - 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); + } + else + { + logGlobal->warn("Failed to set reward parameters for a Scholar! Object is not rewardable!"); } reader->skipZero(6); @@ -1362,16 +1374,21 @@ CGObjectInstance * CMapLoaderH3M::readShrine(const int3 & position, std::shared_ auto * object = readGeneric(position, objectTemplate); auto * rewardable = dynamic_cast(object); - assert(rewardable); - SpellID spell = reader->readSpell32(); - if(spell != SpellID::NONE) + if (rewardable) { - JsonNode 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); + if(spell != SpellID::NONE) + { + JsonNode 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; } diff --git a/server/processors/TurnOrderProcessor.cpp b/server/processors/TurnOrderProcessor.cpp index db3969124..d41a9eb20 100644 --- a/server/processors/TurnOrderProcessor.cpp +++ b/server/processors/TurnOrderProcessor.cpp @@ -196,7 +196,10 @@ void TurnOrderProcessor::doStartNewDay() } if(!activePlayer) + { gameHandler->gameLobby()->setState(EServerState::GAMEPLAY_ENDED); + return; + } std::swap(actedPlayers, awaitingPlayers);