1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-08-13 19:54:17 +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.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}",

View File

@@ -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" : "{Модифікації що мають бути вимкнені}",

View File

@@ -10,7 +10,7 @@ android {
applicationId "is.xyz.vcmi"
minSdk 19
targetSdk 33
versionCode 1410
versionCode 1412
versionName "1.4.1"
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("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)

View File

@@ -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();
} },
};

View File

@@ -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;

View File

@@ -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();
};

View File

@@ -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++;

View File

@@ -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()

View File

@@ -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

View File

@@ -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;

View File

@@ -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);

View File

@@ -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));
});
}
}

View File

@@ -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
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

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