diff --git a/client/CMT.cpp b/client/CMT.cpp index 21f84425a..a3ff7e266 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -205,7 +205,7 @@ int main(int argc, char * argv[]) logGlobal->info("The log file will be saved to %s", logPath); // Init filesystem and settings - preinitDLL(::console); + preinitDLL(::console, false); Settings session = settings.write["session"]; auto setSettingBool = [](std::string key, std::string arg) { diff --git a/client/Client.h b/client/Client.h index 32cbe1db2..7dbbc7a91 100644 --- a/client/Client.h +++ b/client/Client.h @@ -164,6 +164,7 @@ public: bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) override {return false;}; void createObject(const int3 & visitablePosition, const PlayerColor & initiator, MapObjectID type, MapObjectSubID subtype) override {}; void setOwner(const CGObjectInstance * obj, PlayerColor owner) override {}; + void giveExperience(const CGHeroInstance * hero, TExpType val) override {}; void changePrimSkill(const CGHeroInstance * hero, PrimarySkill which, si64 val, bool abs = false) override {}; void changeSecSkill(const CGHeroInstance * hero, SecondarySkill which, int val, bool abs = false) override {}; diff --git a/client/adventureMap/MapAudioPlayer.cpp b/client/adventureMap/MapAudioPlayer.cpp index 64218498a..81a7cf002 100644 --- a/client/adventureMap/MapAudioPlayer.cpp +++ b/client/adventureMap/MapAudioPlayer.cpp @@ -173,8 +173,10 @@ void MapAudioPlayer::updateMusic() { if(audioPlaying && playerMakingTurn && currentSelection) { - const auto * terrain = LOCPLINT->cb->getTile(currentSelection->visitablePos())->terType; - CCS->musich->playMusicFromSet("terrain", terrain->getJsonKey(), true, false); + const auto * tile = LOCPLINT->cb->getTile(currentSelection->visitablePos()); + + if (tile) + CCS->musich->playMusicFromSet("terrain", tile->terType->getJsonKey(), true, false); } if(audioPlaying && enemyMakingTurn) diff --git a/client/lobby/CSelectionBase.cpp b/client/lobby/CSelectionBase.cpp index 9a0be245e..2f602e023 100644 --- a/client/lobby/CSelectionBase.cpp +++ b/client/lobby/CSelectionBase.cpp @@ -69,7 +69,7 @@ int ISelectionScreenInfo::getCurrentDifficulty() PlayerInfo ISelectionScreenInfo::getPlayerInfo(PlayerColor color) { - return getMapInfo()->mapHeader->players[color.getNum()]; + return getMapInfo()->mapHeader->players.at(color.getNum()); } CSelectionBase::CSelectionBase(ESelectionScreen type) diff --git a/lib/CBonusTypeHandler.cpp b/lib/CBonusTypeHandler.cpp index b5cce121f..66f22cd61 100644 --- a/lib/CBonusTypeHandler.cpp +++ b/lib/CBonusTypeHandler.cpp @@ -76,10 +76,10 @@ std::string CBonusTypeHandler::bonusToString(const std::shared_ptr & bonu if (text.find("${val}") != std::string::npos) boost::algorithm::replace_all(text, "${val}", std::to_string(bearer->valOfBonuses(Selector::typeSubtype(bonus->type, bonus->subtype)))); - if (text.find("${subtype.creature}") != std::string::npos) + if (text.find("${subtype.creature}") != std::string::npos && bonus->subtype.as() != CreatureID::NONE) boost::algorithm::replace_all(text, "${subtype.creature}", bonus->subtype.as().toCreature()->getNamePluralTranslated()); - if (text.find("${subtype.spell}") != std::string::npos) + if (text.find("${subtype.spell}") != std::string::npos && bonus->subtype.as() != SpellID::NONE) boost::algorithm::replace_all(text, "${subtype.spell}", bonus->subtype.as().toSpell()->getNameTranslated()); return text; diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index 3ae6931a1..64892f944 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -654,14 +654,21 @@ void CHeroHandler::loadExperience() expPerLevel.push_back(24320); expPerLevel.push_back(28784); expPerLevel.push_back(34140); - while (expPerLevel[expPerLevel.size() - 1] > expPerLevel[expPerLevel.size() - 2]) + + for (;;) { auto i = expPerLevel.size() - 1; - auto diff = expPerLevel[i] - expPerLevel[i-1]; - diff += diff / 5; - expPerLevel.push_back (expPerLevel[i] + diff); + auto currExp = expPerLevel[i]; + auto prevExp = expPerLevel[i-1]; + auto prevDiff = currExp - prevExp; + auto nextDiff = prevDiff + prevDiff / 5; + auto maxExp = std::numeric_limits::max(); + + if (currExp > maxExp - nextDiff) + break; // overflow point reached + + expPerLevel.push_back (currExp + nextDiff); } - expPerLevel.pop_back();//last value is broken } /// convert h3-style ID (e.g. Gobin Wolf Rider) to vcmi (e.g. goblinWolfRider) @@ -741,12 +748,12 @@ void CHeroHandler::loadObject(std::string scope, std::string name, const JsonNod registerObject(scope, "hero", name, object->getIndex()); } -ui32 CHeroHandler::level (ui64 experience) const +ui32 CHeroHandler::level (TExpType experience) const { return static_cast(boost::range::upper_bound(expPerLevel, experience) - std::begin(expPerLevel)); } -ui64 CHeroHandler::reqExp (ui32 level) const +TExpType CHeroHandler::reqExp (ui32 level) const { if(!level) return 0; @@ -762,6 +769,11 @@ ui64 CHeroHandler::reqExp (ui32 level) const } } +ui32 CHeroHandler::maxSupportedLevel() const +{ + return expPerLevel.size(); +} + std::set CHeroHandler::getDefaultAllowed() const { std::set result; diff --git a/lib/CHeroHandler.h b/lib/CHeroHandler.h index 3103594bc..88ce86f5b 100644 --- a/lib/CHeroHandler.h +++ b/lib/CHeroHandler.h @@ -176,8 +176,8 @@ protected: class DLL_LINKAGE CHeroHandler : public CHandlerBase { /// expPerLEvel[i] is amount of exp needed to reach level i; - /// consists of 201 values. Any higher levels require experience larger that ui64 can hold - std::vector expPerLevel; + /// consists of 196 values. Any higher levels require experience larger that TExpType can hold + std::vector expPerLevel; /// helpers for loading to avoid huge load functions void loadHeroArmy(CHero * hero, const JsonNode & node) const; @@ -191,8 +191,9 @@ class DLL_LINKAGE CHeroHandler : public CHandlerBase loadLegacyData() override; diff --git a/lib/IGameCallback.h b/lib/IGameCallback.h index 076cfd804..9c6c4a0cc 100644 --- a/lib/IGameCallback.h +++ b/lib/IGameCallback.h @@ -84,6 +84,7 @@ public: virtual bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) = 0; virtual void createObject(const int3 & visitablePosition, const PlayerColor & initiator, MapObjectID type, MapObjectSubID subtype) = 0; virtual void setOwner(const CGObjectInstance * objid, PlayerColor owner)=0; + virtual void giveExperience(const CGHeroInstance * hero, TExpType val) =0; virtual void changePrimSkill(const CGHeroInstance * hero, PrimarySkill which, si64 val, bool abs=false)=0; virtual void changeSecSkill(const CGHeroInstance * hero, SecondarySkill which, int val, bool abs=false)=0; virtual void showBlockingDialog(BlockingDialog *iw) =0; diff --git a/lib/VCMI_Lib.cpp b/lib/VCMI_Lib.cpp index f297e5dff..94d3f57fd 100644 --- a/lib/VCMI_Lib.cpp +++ b/lib/VCMI_Lib.cpp @@ -47,14 +47,14 @@ VCMI_LIB_NAMESPACE_BEGIN LibClasses * VLC = nullptr; -DLL_LINKAGE void preinitDLL(CConsoleHandler * Console, bool onlyEssential, bool extractArchives) +DLL_LINKAGE void preinitDLL(CConsoleHandler * Console, bool extractArchives) { console = Console; VLC = new LibClasses(); VLC->loadFilesystem(extractArchives); settings.init("config/settings.json", "vcmi:settings"); persistentStorage.init("config/persistentStorage.json", ""); - VLC->loadModFilesystem(onlyEssential); + VLC->loadModFilesystem(); } @@ -182,12 +182,12 @@ void LibClasses::loadFilesystem(bool extractArchives) logGlobal->info("\tData loading: %d ms", loadTime.getDiff()); } -void LibClasses::loadModFilesystem(bool onlyEssential) +void LibClasses::loadModFilesystem() { CStopWatch loadTime; modh = new CModHandler(); identifiersHandler = new CIdentifierStorage(); - modh->loadMods(onlyEssential); + modh->loadMods(); logGlobal->info("\tMod handler: %d ms", loadTime.getDiff()); modh->loadModFilesystems(); diff --git a/lib/VCMI_Lib.h b/lib/VCMI_Lib.h index dc9912e3b..6622d9822 100644 --- a/lib/VCMI_Lib.h +++ b/lib/VCMI_Lib.h @@ -115,7 +115,7 @@ public: // basic initialization. should be called before init(). Can also extract original H3 archives void loadFilesystem(bool extractArchives); - void loadModFilesystem(bool onlyEssential); + void loadModFilesystem(); #if SCRIPTING_ENABLED void scriptsLoaded(); @@ -124,7 +124,7 @@ public: extern DLL_LINKAGE LibClasses * VLC; -DLL_LINKAGE void preinitDLL(CConsoleHandler * Console, bool onlyEssential = false, bool extractArchives = false); +DLL_LINKAGE void preinitDLL(CConsoleHandler * Console, bool extractArchives); DLL_LINKAGE void loadDLLClasses(bool onlyEssential = false); diff --git a/lib/constants/EntityIdentifiers.cpp b/lib/constants/EntityIdentifiers.cpp index 60cf811c2..597343cc2 100644 --- a/lib/constants/EntityIdentifiers.cpp +++ b/lib/constants/EntityIdentifiers.cpp @@ -292,7 +292,7 @@ const Skill * SecondarySkill::toEntity(const Services * services) const const CCreature * CreatureIDBase::toCreature() const { - return VLC->creh->objects.at(num); + return dynamic_cast(toEntity(VLC)); } const Creature * CreatureIDBase::toEntity(const Services * services) const @@ -324,12 +324,7 @@ std::string CreatureID::entityType() const CSpell * SpellIDBase::toSpell() const { - if(num < 0 || num >= VLC->spellh->objects.size()) - { - logGlobal->error("Unable to get spell of invalid ID %d", static_cast(num)); - return nullptr; - } - return VLC->spellh->objects[num]; + return dynamic_cast(toEntity(VLC)); } const spells::Spell * SpellIDBase::toEntity(const Services * services) const diff --git a/lib/mapObjectConstructors/CObjectClassesHandler.cpp b/lib/mapObjectConstructors/CObjectClassesHandler.cpp index f7fbcd46c..2c758b0f6 100644 --- a/lib/mapObjectConstructors/CObjectClassesHandler.cpp +++ b/lib/mapObjectConstructors/CObjectClassesHandler.cpp @@ -177,8 +177,10 @@ void CObjectClassesHandler::loadSubObject(const std::string & scope, const std:: auto object = loadSubObjectFromJson(scope, identifier, entry, obj, index); assert(object); - assert(obj->objects[index] == nullptr); // ensure that this id was not loaded before - obj->objects[index] = object; + if (obj->objects.at(index) != nullptr) + throw std::runtime_error("Attempt to load already loaded object:" + identifier); + + obj->objects.at(index) = object; registerObject(scope, obj->getJsonKey(), object->getSubTypeName(), object->subtype); for(const auto & compatID : entry["compatibilityIdentifiers"].Vector()) @@ -259,10 +261,16 @@ std::unique_ptr CObjectClassesHandler::loadFromJson(const std::stri { const std::string & subMeta = subData.second["index"].meta; - 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.get(), subIndex); + if ( subMeta == "core") + { + size_t subIndex = subData.second["index"].Integer(); + loadSubObject(subData.second.meta, subData.first, subData.second, obj.get(), subIndex); + } + else + { + logMod->error("Object %s:%s.%s - attempt to load object with preset index! This option is reserved for built-in mod", subMeta, name, subData.first ); + loadSubObject(subData.second.meta, subData.first, subData.second, obj.get()); + } } else loadSubObject(subData.second.meta, subData.first, subData.second, obj.get()); @@ -283,28 +291,28 @@ void CObjectClassesHandler::loadObject(std::string scope, std::string name, cons void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) { - assert(objects[index] == nullptr); // ensure that this id was not loaded before + assert(objects.at(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); + objects.at(index) = loadFromJson(scope, data, name, index); + VLC->identifiersHandler->registerObject(scope, "object", name, objects.at(index)->id); } void CObjectClassesHandler::loadSubObject(const std::string & identifier, JsonNode config, MapObjectID ID, MapObjectSubID subID) { config.setType(JsonNode::JsonType::DATA_STRUCT); // ensure that input is not NULL - assert(objects[ID.getNum()]); + assert(objects.at(ID.getNum())); - if ( subID.getNum() >= objects[ID.getNum()]->objects.size()) - objects[ID.getNum()]->objects.resize(subID.getNum()+1); + if ( subID.getNum() >= objects.at(ID.getNum())->objects.size()) + objects.at(ID.getNum())->objects.resize(subID.getNum()+1); JsonUtils::inherit(config, objects.at(ID.getNum())->base); - loadSubObject(config.meta, identifier, config, objects[ID.getNum()].get(), subID.getNum()); + loadSubObject(config.meta, identifier, config, objects.at(ID.getNum()).get(), subID.getNum()); } void CObjectClassesHandler::removeSubObject(MapObjectID ID, MapObjectSubID subID) { - assert(objects[ID.getNum()]); - objects[ID.getNum()]->objects[subID.getNum()] = nullptr; + assert(objects.at(ID.getNum())); + objects.at(ID.getNum())->objects.at(subID.getNum()) = nullptr; } TObjectTypeHandler CObjectClassesHandler::getHandlerFor(MapObjectID type, MapObjectSubID subtype) const @@ -337,11 +345,11 @@ TObjectTypeHandler CObjectClassesHandler::getHandlerFor(const std::string & scop std::optional id = VLC->identifiers()->getIdentifier(scope, "object", type); if(id) { - const auto & object = objects[id.value()]; + const auto & object = objects.at(id.value()); std::optional subID = VLC->identifiers()->getIdentifier(scope, object->getJsonKey(), subtype); if (subID) - return object->objects[subID.value()]; + return object->objects.at(subID.value()); } std::string errorString = "Failed to find object of type " + type + "::" + subtype; @@ -472,8 +480,8 @@ std::string CObjectClassesHandler::getObjectName(MapObjectID type, MapObjectSubI if (handler && handler->hasNameTextID()) return handler->getNameTranslated(); - if (objects[type.getNum()]) - return objects[type.getNum()]->getNameTranslated(); + if (objects.at(type.getNum())) + return objects.at(type.getNum())->getNameTranslated(); return objects.front()->getNameTranslated(); } @@ -487,7 +495,7 @@ SObjectSounds CObjectClassesHandler::getObjectSounds(MapObjectID type, MapObject if(type == Obj::PRISON || type == Obj::HERO || type == Obj::SPELL_SCROLL) subtype = 0; - if(objects[type.getNum()]) + if(objects.at(type.getNum())) return getHandlerFor(type, subtype)->getSounds(); else return objects.front()->objects.front()->getSounds(); diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index ac5fc907a..8440be51c 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -1439,7 +1439,7 @@ void CGHeroInstance::setPrimarySkill(PrimarySkill primarySkill, si64 value, ui8 bool CGHeroInstance::gainsLevel() const { - return exp >= static_cast(VLC->heroh->reqExp(level+1)); + return level < VLC->heroh->maxSupportedLevel() && exp >= static_cast(VLC->heroh->reqExp(level+1)); } void CGHeroInstance::levelUp(const std::vector & skills) diff --git a/lib/mapObjects/CGTownBuilding.cpp b/lib/mapObjects/CGTownBuilding.cpp index f00aa72f2..36eada924 100644 --- a/lib/mapObjects/CGTownBuilding.cpp +++ b/lib/mapObjects/CGTownBuilding.cpp @@ -249,8 +249,12 @@ void CTownBonus::onHeroVisit (const CGHeroInstance * h) const iw.player = cb->getOwner(heroID); iw.text.appendRawString(getVisitingBonusGreeting()); cb->showInfoDialog(&iw); - cb->changePrimSkill (cb->getHero(heroID), what, val); - town->addHeroToStructureVisitors(h, indexOnTV); + if (what == PrimarySkill::EXPERIENCE) + cb->giveExperience(cb->getHero(heroID), val); + else + cb->changePrimSkill(cb->getHero(heroID), what, val); + + town->addHeroToStructureVisitors(h, indexOnTV); } } } diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index 853466a14..36cda23e1 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -1138,7 +1138,7 @@ void CGSirens::onHeroVisit( const CGHeroInstance * h ) const xp = h->calculateXp(static_cast(xp)); iw.text.appendLocalString(EMetaText::ADVOB_TXT,132); iw.text.replaceNumber(static_cast(xp)); - cb->changePrimSkill(h, PrimarySkill::EXPERIENCE, xp, false); + cb->giveExperience(h, xp); } else { diff --git a/lib/modding/CModHandler.cpp b/lib/modding/CModHandler.cpp index 88e3f1851..1fe9d4b17 100644 --- a/lib/modding/CModHandler.cpp +++ b/lib/modding/CModHandler.cpp @@ -237,19 +237,12 @@ void CModHandler::loadOneMod(std::string modName, const std::string & parent, co } } -void CModHandler::loadMods(bool onlyEssential) +void CModHandler::loadMods() { JsonNode modConfig; - if(onlyEssential) - { - loadOneMod("vcmi", "", modConfig, true);//only vcmi and submods - } - else - { - modConfig = loadModSettings(JsonPath::builtin("config/modSettings.json")); - loadMods("", "", modConfig["activeMods"], true); - } + modConfig = loadModSettings(JsonPath::builtin("config/modSettings.json")); + loadMods("", "", modConfig["activeMods"], true); coreMod = std::make_unique(ModScope::scopeBuiltin(), modConfig[ModScope::scopeBuiltin()], JsonNode(JsonPath::builtin("config/gameConfig.json"))); } @@ -346,20 +339,25 @@ void CModHandler::loadModFilesystems() TModID CModHandler::findResourceOrigin(const ResourcePath & name) const { - for(const auto & modID : boost::adaptors::reverse(activeMods)) + try { - if(CResourceHandler::get(modID)->existsResource(name)) - return modID; + for(const auto & modID : boost::adaptors::reverse(activeMods)) + { + if(CResourceHandler::get(modID)->existsResource(name)) + return modID; + } + + if(CResourceHandler::get("core")->existsResource(name)) + return "core"; + + if(CResourceHandler::get("mapEditor")->existsResource(name)) + return "core"; // Workaround for loading maps via map editor } - - if(CResourceHandler::get("core")->existsResource(name)) - return "core"; - - if(CResourceHandler::get("mapEditor")->existsResource(name)) - return "core"; // Workaround for loading maps via map editor - - assert(0); - return ""; + catch( const std::out_of_range & e) + { + // no-op + } + throw std::runtime_error("Resource with name " + name.getName() + " and type " + EResTypeHelper::getEResTypeAsString(name.getType()) + " wasn't found."); } std::string CModHandler::getModLanguage(const TModID& modId) const diff --git a/lib/modding/CModHandler.h b/lib/modding/CModHandler.h index 4028ce6c2..30eea7a38 100644 --- a/lib/modding/CModHandler.h +++ b/lib/modding/CModHandler.h @@ -58,7 +58,7 @@ public: /// receives list of available mods and trying to load mod.json from all of them void initializeConfig(); - void loadMods(bool onlyEssential = false); + void loadMods(); void loadModFilesystems(); /// returns ID of mod that provides selected file resource diff --git a/lib/modding/ContentTypeHandler.cpp b/lib/modding/ContentTypeHandler.cpp index 977ad172b..4a61afa71 100644 --- a/lib/modding/ContentTypeHandler.cpp +++ b/lib/modding/ContentTypeHandler.cpp @@ -115,11 +115,13 @@ bool ContentTypeHandler::loadMod(const std::string & modName, bool validate) continue; } - if (vstd::contains(data.Struct(), "index") && !data["index"].isNull()) - { - if (modName != "core") - logMod->warn("Mod %s is attempting to load original data! This should be reserved for built-in mod.", modName); + bool hasIndex = vstd::contains(data.Struct(), "index") && !data["index"].isNull(); + if (hasIndex && modName != "core") + logMod->error("Mod %s is attempting to load original data! This option is reserved for built-in mod.", modName); + + if (hasIndex && modName == "core") + { // try to add H3 object data size_t index = static_cast(data["index"].Float()); diff --git a/lib/rewardable/Interface.cpp b/lib/rewardable/Interface.cpp index 534641f37..d7358efb9 100644 --- a/lib/rewardable/Interface.cpp +++ b/lib/rewardable/Interface.cpp @@ -117,7 +117,7 @@ void Rewardable::Interface::grantRewardBeforeLevelup(IGameCallback * cb, const R for(int i=0; i< info.reward.primary.size(); i++) cb->changePrimSkill(hero, static_cast(i), info.reward.primary[i], false); - si64 expToGive = 0; + TExpType expToGive = 0; if (info.reward.heroLevel > 0) expToGive += VLC->heroh->reqExp(hero->level+info.reward.heroLevel) - VLC->heroh->reqExp(hero->level); @@ -126,7 +126,7 @@ void Rewardable::Interface::grantRewardBeforeLevelup(IGameCallback * cb, const R expToGive += hero->calculateXp(info.reward.heroExperience); if(expToGive) - cb->changePrimSkill(hero, PrimarySkill::EXPERIENCE, expToGive); + cb->giveExperience(hero, expToGive); } void Rewardable::Interface::grantRewardAfterLevelup(IGameCallback * cb, const Rewardable::VisitInfo & info, const CArmedInstance * army, const CGHeroInstance * hero) const diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index aaf5b19f5..b9e4cb09f 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -177,7 +177,7 @@ MainWindow::MainWindow(QWidget* parent) : logGlobal->info("The log file will be saved to %s", logPath); //init - preinitDLL(::console, false, extractionOptions.extractArchives); + preinitDLL(::console, extractionOptions.extractArchives); // Initialize logging based on settings logConfig->configure(); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 3a943d2b8..1f4f84020 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -365,52 +365,60 @@ void CGameHandler::expGiven(const CGHeroInstance *hero) // levelUpHero(hero); } -void CGameHandler::changePrimSkill(const CGHeroInstance * hero, PrimarySkill which, si64 val, bool abs) +void CGameHandler::giveExperience(const CGHeroInstance * hero, TExpType amountToGain) { - if (which == PrimarySkill::EXPERIENCE) // Check if scenario limit reached - { - if (gs->map->levelLimit != 0) - { - TExpType expLimit = VLC->heroh->reqExp(gs->map->levelLimit); - TExpType resultingExp = abs ? val : hero->exp + val; - if (resultingExp > expLimit) - { - // set given experience to max possible, but don't decrease if hero already over top - abs = true; - val = std::max(expLimit, hero->exp); + TExpType maxExp = VLC->heroh->reqExp(VLC->heroh->maxSupportedLevel()); + TExpType currExp = hero->exp; - InfoWindow iw; - iw.player = hero->tempOwner; - iw.text.appendLocalString(EMetaText::GENERAL_TXT, 1); //can gain no more XP - iw.text.replaceRawString(hero->getNameTranslated()); - sendAndApply(&iw); - } - } + if (gs->map->levelLimit != 0) + maxExp = VLC->heroh->reqExp(gs->map->levelLimit); + + TExpType canGainExp = 0; + if (maxExp > currExp) + canGainExp = maxExp - currExp; + + if (amountToGain > canGainExp) + { + // set given experience to max possible, but don't decrease if hero already over top + amountToGain = canGainExp; + + InfoWindow iw; + iw.player = hero->tempOwner; + iw.text.appendLocalString(EMetaText::GENERAL_TXT, 1); //can gain no more XP + iw.text.replaceRawString(hero->getNameTranslated()); + sendAndApply(&iw); } + SetPrimSkill sps; + sps.id = hero->id; + sps.which = PrimarySkill::EXPERIENCE; + sps.abs = false; + sps.val = amountToGain; + sendAndApply(&sps); + + //hero may level up + if (hero->commander && hero->commander->alive) + { + //FIXME: trim experience according to map limit? + SetCommanderProperty scp; + scp.heroid = hero->id; + scp.which = SetCommanderProperty::EXPERIENCE; + scp.amount = amountToGain; + sendAndApply (&scp); + CBonusSystemNode::treeHasChanged(); + } + + expGiven(hero); +} + +void CGameHandler::changePrimSkill(const CGHeroInstance * hero, PrimarySkill which, si64 val, bool abs) +{ SetPrimSkill sps; sps.id = hero->id; sps.which = which; sps.abs = abs; sps.val = val; sendAndApply(&sps); - - //only for exp - hero may level up - if (which == PrimarySkill::EXPERIENCE) - { - if (hero->commander && hero->commander->alive) - { - //FIXME: trim experience according to map limit? - SetCommanderProperty scp; - scp.heroid = hero->id; - scp.which = SetCommanderProperty::EXPERIENCE; - scp.amount = val; - sendAndApply (&scp); - CBonusSystemNode::treeHasChanged(); - } - - expGiven(hero); - } } void CGameHandler::changeSecSkill(const CGHeroInstance * hero, SecondarySkill which, int val, bool abs) @@ -658,7 +666,7 @@ void CGameHandler::onNewTurn() { if (obj && obj->ID == Obj::PRISON) //give imprisoned hero 0 exp to level him up. easiest to do at this point { - changePrimSkill (getHero(obj->id), PrimarySkill::EXPERIENCE, 0); + giveExperience(getHero(obj->id), 0); } } } @@ -3708,7 +3716,7 @@ bool CGameHandler::sacrificeCreatures(const IMarket * market, const CGHeroInstan int expSum = 0; auto finish = [this, &hero, &expSum]() { - changePrimSkill(hero, PrimarySkill::EXPERIENCE, hero->calculateXp(expSum)); + giveExperience(hero, hero->calculateXp(expSum)); }; for(int i = 0; i < slot.size(); ++i) @@ -3749,7 +3757,7 @@ bool CGameHandler::sacrificeArtifact(const IMarket * m, const CGHeroInstance * h int expSum = 0; auto finish = [this, &hero, &expSum]() { - changePrimSkill(hero, PrimarySkill::EXPERIENCE, hero->calculateXp(expSum)); + giveExperience(hero, hero->calculateXp(expSum)); }; for(int i = 0; i < slot.size(); ++i) diff --git a/server/CGameHandler.h b/server/CGameHandler.h index 73603fba9..c82953185 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -102,6 +102,7 @@ public: bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) override; void createObject(const int3 & visitablePosition, const PlayerColor & initiator, MapObjectID type, MapObjectSubID subtype) override; void setOwner(const CGObjectInstance * obj, PlayerColor owner) override; + void giveExperience(const CGHeroInstance * hero, TExpType val) override; void changePrimSkill(const CGHeroInstance * hero, PrimarySkill which, si64 val, bool abs=false) override; void changeSecSkill(const CGHeroInstance * hero, SecondarySkill which, int val, bool abs=false) override; diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index f7d894f33..e19d78266 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -141,7 +141,11 @@ CVCMIServer::CVCMIServer(boost::program_options::variables_map & opts) if(cmdLineOptions.count("run-by-client")) { logNetwork->error("Port must be specified when run-by-client is used!!"); - exit(0); +#if (defined(__ANDROID_API__) && __ANDROID_API__ < 21) || (defined(__MINGW32__)) || defined(VCMI_APPLE) + ::exit(0); +#else + std::quick_exit(0); +#endif } acceptor = std::make_shared(*io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 0)); port = acceptor->local_endpoint().port(); @@ -1172,7 +1176,7 @@ int main(int argc, const char * argv[]) boost::program_options::variables_map opts; handleCommandOptions(argc, argv, opts); - preinitDLL(console); + preinitDLL(console, false); logConfig.configure(); loadDLLClasses(); diff --git a/server/TurnTimerHandler.cpp b/server/TurnTimerHandler.cpp index bb816a854..9aa3e5dc7 100644 --- a/server/TurnTimerHandler.cpp +++ b/server/TurnTimerHandler.cpp @@ -94,8 +94,14 @@ void TurnTimerHandler::update(int waitTime) if(gs->isPlayerMakingTurn(player)) onPlayerMakingTurn(player, waitTime); + // create copy for iterations - battle might end during onBattleLoop call + std::vector ongoingBattles; + for (auto & battle : gs->currentBattles) - onBattleLoop(battle->battleID, waitTime); + ongoingBattles.push_back(battle->battleID); + + for (auto & battleID : ongoingBattles) + onBattleLoop(battleID, waitTime); } } diff --git a/server/battles/BattleFlowProcessor.cpp b/server/battles/BattleFlowProcessor.cpp index 0fba8f63a..542803e62 100644 --- a/server/battles/BattleFlowProcessor.cpp +++ b/server/battles/BattleFlowProcessor.cpp @@ -712,6 +712,11 @@ void BattleFlowProcessor::stackTurnTrigger(const CBattleInfoCallback & battle, c } } BonusList bl = *(st->getBonuses(Selector::type()(BonusType::ENCHANTER))); + bl.remove_if([](const Bonus * b) + { + return b->subtype.as() == SpellID::NONE; + }); + int side = *battle.playerToSide(st->unitOwner()); if(st->canCast() && battle.battleGetEnchanterCounter(side) == 0) { diff --git a/server/battles/BattleResultProcessor.cpp b/server/battles/BattleResultProcessor.cpp index 52c4e7e6b..078725358 100644 --- a/server/battles/BattleResultProcessor.cpp +++ b/server/battles/BattleResultProcessor.cpp @@ -494,7 +494,7 @@ void BattleResultProcessor::endBattleConfirm(const CBattleInfoCallback & battle) } //give exp if(!finishingBattle->isDraw() && battleResult->exp[finishingBattle->winnerSide] && finishingBattle->winnerHero) - gameHandler->changePrimSkill(finishingBattle->winnerHero, PrimarySkill::EXPERIENCE, battleResult->exp[finishingBattle->winnerSide]); + gameHandler->giveExperience(finishingBattle->winnerHero, battleResult->exp[finishingBattle->winnerSide]); BattleResultAccepted raccepted; raccepted.battleID = battle.getBattle()->getBattleID(); diff --git a/server/processors/PlayerMessageProcessor.cpp b/server/processors/PlayerMessageProcessor.cpp index afb331148..6af453752 100644 --- a/server/processors/PlayerMessageProcessor.cpp +++ b/server/processors/PlayerMessageProcessor.cpp @@ -261,7 +261,7 @@ void PlayerMessageProcessor::cheatLevelup(PlayerColor player, const CGHeroInstan levelsToGain = 1; } - gameHandler->changePrimSkill(hero, PrimarySkill::EXPERIENCE, VLC->heroh->reqExp(hero->level + levelsToGain) - VLC->heroh->reqExp(hero->level)); + gameHandler->giveExperience(hero, VLC->heroh->reqExp(hero->level + levelsToGain) - VLC->heroh->reqExp(hero->level)); } void PlayerMessageProcessor::cheatExperience(PlayerColor player, const CGHeroInstance * hero, std::vector words) @@ -280,7 +280,7 @@ void PlayerMessageProcessor::cheatExperience(PlayerColor player, const CGHeroIns expAmountProcessed = 10000; } - gameHandler->changePrimSkill(hero, PrimarySkill::EXPERIENCE, expAmountProcessed); + gameHandler->giveExperience(hero, expAmountProcessed); } void PlayerMessageProcessor::cheatMovement(PlayerColor player, const CGHeroInstance * hero, std::vector words) diff --git a/test/mock/mock_IGameCallback.h b/test/mock/mock_IGameCallback.h index 48a2fb639..4ca0b0c39 100644 --- a/test/mock/mock_IGameCallback.h +++ b/test/mock/mock_IGameCallback.h @@ -44,6 +44,7 @@ public: bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) override {return false;} void createObject(const int3 & visitablePosition, const PlayerColor & initiator, MapObjectID type, MapObjectSubID subtype) override {}; void setOwner(const CGObjectInstance * objid, PlayerColor owner) override {} + void giveExperience(const CGHeroInstance * hero, TExpType val) override {} void changePrimSkill(const CGHeroInstance * hero, PrimarySkill which, si64 val, bool abs=false) override {} void changeSecSkill(const CGHeroInstance * hero, SecondarySkill which, int val, bool abs=false) override {} void showBlockingDialog(BlockingDialog *iw) override {}