mirror of
https://github.com/vcmi/vcmi.git
synced 2025-08-13 19:54:17 +02:00
@@ -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}",
|
||||
|
@@ -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" : "{Модифікації що мають бути вимкнені}",
|
||||
|
@@ -10,7 +10,7 @@ android {
|
||||
applicationId "is.xyz.vcmi"
|
||||
minSdk 19
|
||||
targetSdk 33
|
||||
versionCode 1410
|
||||
versionCode 1412
|
||||
versionName "1.4.1"
|
||||
setProperty("archivesBaseName", "vcmi")
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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<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];
|
||||
|
||||
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--)
|
||||
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<CIntObject> CTownList::createItem(size_t index)
|
||||
}
|
||||
|
||||
CTownList::CTownItem::CTownItem(CTownList *parent, const CGTownInstance *Town):
|
||||
CListItem(parent),
|
||||
parentList(parent)
|
||||
CListItem(parent)
|
||||
{
|
||||
const std::vector<const CGTownInstance *> 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();
|
||||
} },
|
||||
};
|
||||
|
||||
|
@@ -117,7 +117,6 @@ class CHeroList : public CList
|
||||
std::shared_ptr<CAnimImage> movement;
|
||||
std::shared_ptr<CAnimImage> mana;
|
||||
std::shared_ptr<CAnimImage> portrait;
|
||||
CHeroList *parentList;
|
||||
public:
|
||||
const CGHeroInstance * const hero;
|
||||
|
||||
@@ -152,7 +151,6 @@ class CTownList : public CList
|
||||
class CTownItem : public CListItem
|
||||
{
|
||||
std::shared_ptr<CAnimImage> picture;
|
||||
CTownList *parentList;
|
||||
public:
|
||||
int townIndex;
|
||||
|
||||
|
@@ -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();
|
||||
};
|
||||
|
||||
|
@@ -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++;
|
||||
|
@@ -382,12 +382,29 @@ void CMainMenu::openCampaignLobby(std::shared_ptr<CampaignState> 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<CCampaignScreen>(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<std::shared_ptr<CComponent>>(), PlayerColor(1));
|
||||
return;
|
||||
}
|
||||
|
||||
GH.windows().createAndPushWindow<CCampaignScreen>(config, name);
|
||||
}
|
||||
|
||||
void CMainMenu::startTutorial()
|
||||
|
@@ -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<size_t>(option);
|
||||
|
||||
assert(!gameSettings[index].isNull());
|
||||
return gameSettings[index];
|
||||
assert(!gameSettings.at(index).isNull());
|
||||
return gameSettings.at(index);
|
||||
}
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
@@ -97,11 +97,7 @@ CObjectClassesHandler::CObjectClassesHandler()
|
||||
#undef SET_HANDLER
|
||||
}
|
||||
|
||||
CObjectClassesHandler::~CObjectClassesHandler()
|
||||
{
|
||||
for(auto * p : objects)
|
||||
delete p;
|
||||
}
|
||||
CObjectClassesHandler::~CObjectClassesHandler() = default;
|
||||
|
||||
std::vector<JsonNode> 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<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->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<si32>(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<si32> id = VLC->identifiers()->getIdentifier(scope, "object", type);
|
||||
if(id)
|
||||
{
|
||||
auto * object = objects[id.value()];
|
||||
const auto & object = objects[id.value()];
|
||||
std::optional<si32> subID = VLC->identifiers()->getIdentifier(scope, object->getJsonKey(), subtype);
|
||||
|
||||
if (subID)
|
||||
@@ -356,7 +355,7 @@ std::set<MapObjectID> CObjectClassesHandler::knownObjects() const
|
||||
{
|
||||
std::set<MapObjectID> 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;
|
||||
|
@@ -45,7 +45,7 @@ class CGObjectInstance;
|
||||
using TObjectTypeHandler = std::shared_ptr<AObjectTypeHandler>;
|
||||
|
||||
/// 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<TObjectTypeHandler> 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<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
|
||||
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, 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);
|
||||
|
||||
|
@@ -77,7 +77,7 @@ void CTownInstanceConstructor::afterLoadFinalization()
|
||||
{
|
||||
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));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -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<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
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -1145,42 +1145,47 @@ CGObjectInstance * CMapLoaderH3M::readWitchHut(const int3 & position, std::share
|
||||
auto * object = readGeneric(position, objectTemplate);
|
||||
auto * rewardable = dynamic_cast<CRewardableObject*>(object);
|
||||
|
||||
assert(rewardable);
|
||||
|
||||
// AB and later maps have allowed abilities defined in H3M
|
||||
if(features.levelAB)
|
||||
{
|
||||
std::set<SecondarySkill> 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<ScholarBonusType>(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<CRewardableObject*>(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;
|
||||
}
|
||||
|
@@ -196,7 +196,10 @@ void TurnOrderProcessor::doStartNewDay()
|
||||
}
|
||||
|
||||
if(!activePlayer)
|
||||
{
|
||||
gameHandler->gameLobby()->setState(EServerState::GAMEPLAY_ENDED);
|
||||
return;
|
||||
}
|
||||
|
||||
std::swap(actedPlayers, awaitingPlayers);
|
||||
|
||||
|
Reference in New Issue
Block a user