diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fd2b75d..c12fb41 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,6 +15,8 @@ jobs: uses: adya/pack-skse-mod/.github/workflows/pack.yml@main with: CMAKE_PROJECT_DIR: "SPID" + CMAKE_VR_CONFIG_PRESET: '' + CMAKE_VR_BUILD_PRESET: '' AE_353_BRANCH: master-1.6.353 FOMOD_INCLUDE_PDB: true FOMOD_MOD_NAME: "Spell Perk Item Distributor" diff --git a/SPID/include/Defs.h b/SPID/include/Defs.h index 8f044e8..ba09ba0 100644 --- a/SPID/include/Defs.h +++ b/SPID/include/Defs.h @@ -51,11 +51,6 @@ namespace INI }; using Values = std::vector>; - - inline bool is_mod_name(const std::string& a_str) - { - return a_str.rfind(".esp") != std::string::npos || a_str.rfind(".esl") != std::string::npos || a_str.rfind(".esm") != std::string::npos; - } } namespace DATA diff --git a/SPID/include/LookupConfigs.h b/SPID/include/LookupConfigs.h index 9360164..6f7a40d 100644 --- a/SPID/include/LookupConfigs.h +++ b/SPID/include/LookupConfigs.h @@ -197,5 +197,5 @@ namespace INI return std::make_tuple(data, std::nullopt); } - bool Read(); + std::pair Read(); } diff --git a/SPID/include/LookupForms.h b/SPID/include/LookupForms.h index e17e229..6d1c64c 100644 --- a/SPID/include/LookupForms.h +++ b/SPID/include/LookupForms.h @@ -162,12 +162,12 @@ namespace Lookup } } if (!form) { - logger::error(" [{}] [0x{:X}] ({}) FAIL - formID doesn't exist", path, *formID, modName.value_or("")); + logger::error("\t\t[{}] [0x{:X}] ({}) FAIL - formID doesn't exist", path, *formID, modName.value_or("")); } else { if constexpr (std::is_same_v) { if (string::is_empty(form->GetFormEditorID())) { form = nullptr; - logger::error(" [{}] [0x{:X}] ({}) FAIL - keyword does not have a valid editorID", path, *formID, modName.value_or("")); + logger::error("\t\t[{}] [0x{:X}] ({}) FAIL - keyword does not have a valid editorID", path, *formID, modName.value_or("")); } } } @@ -181,7 +181,7 @@ namespace Lookup auto& keywordArray = a_dataHandler->GetFormArray(); auto result = std::find_if(keywordArray.begin(), keywordArray.end(), [&](const auto& keyword) { - return keyword && string::iequals(keyword->formEditorID, keywordName); + return keyword && keyword->formEditorID == keywordName.c_str(); }); if (result != keywordArray.end()) { diff --git a/SPID/src/DistributePCLevelMult.cpp b/SPID/src/DistributePCLevelMult.cpp index 18720aa..fa72b77 100644 --- a/SPID/src/DistributePCLevelMult.cpp +++ b/SPID/src/DistributePCLevelMult.cpp @@ -4,140 +4,140 @@ namespace Distribute::PlayerLeveledActor { - struct HandleUpdatePlayerLevel - { - static void thunk(RE::Actor* a_actor) - { - if (const auto npc = a_actor->GetActorBase(); npc && npc->HasPCLevelMult()) { - const auto input = npc->IsDynamicForm() ? PCLevelMult::Input{ a_actor, npc, true, false } : // use character formID for permanent storage - PCLevelMult::Input{ npc, true, false }; - Distribute(npc, input); - } + struct HandleUpdatePlayerLevel + { + static void thunk(RE::Actor* a_actor) + { + if (const auto npc = a_actor->GetActorBase()) { + const auto input = npc->IsDynamicForm() ? PCLevelMult::Input{ a_actor, npc, true, false } : // use character formID for permanent storage + PCLevelMult::Input{ npc, true, false }; + Distribute(npc, input); + } - func(a_actor); - } - static inline REL::Relocation func; - }; + func(a_actor); + } + static inline REL::Relocation func; + }; - struct LoadGame - { - static void thunk(RE::Character* a_this, std::uintptr_t a_buf) - { - if (const auto actorbase = a_this->GetActorBase(); actorbase && actorbase->HasPCLevelMult()) { - const auto input = actorbase->IsDynamicForm() ? PCLevelMult::Input{ a_this, actorbase, true, false } : // use character formID for permanent storage - PCLevelMult::Input{ actorbase, true, false }; + struct LoadGame + { + static void thunk(RE::Character* a_this, std::uintptr_t a_buf) + { + if (const auto actorbase = a_this->GetActorBase(); actorbase && actorbase->HasPCLevelMult()) { + const auto input = actorbase->IsDynamicForm() ? PCLevelMult::Input{ a_this, actorbase, true, false } : // use character formID for permanent storage + PCLevelMult::Input{ actorbase, true, false }; - if (const auto pcLevelMultManager = PCLevelMult::Manager::GetSingleton(); !pcLevelMultManager->FindDistributedEntry(input)) { - //start distribution for first time - Distribute(actorbase, input); - } else { - //handle redistribution and removal - pcLevelMultManager->ForEachDistributedEntry(input, [&](RE::TESForm& a_form, [[maybe_unused]] IdxOrCount a_count, bool a_isBelowLevel) { - switch (a_form.GetFormType()) { - case RE::FormType::Keyword: - { - auto keyword = a_form.As(); - if (a_isBelowLevel) { - actorbase->RemoveKeyword(keyword); - } else { - actorbase->AddKeyword(keyword); - } - } - break; - case RE::FormType::Faction: - { - auto faction = a_form.As(); - auto it = std::ranges::find_if(actorbase->factions, [&](const auto& factionRank) { - return factionRank.faction == faction; - }); - if (it != actorbase->factions.end()) { - if (a_isBelowLevel) { - (*it).rank = -1; - } else { - (*it).rank = 1; - } - } - } - break; - case RE::FormType::Perk: - { - auto perk = a_form.As(); - if (a_isBelowLevel) { - actorbase->RemovePerk(perk); - } else { - actorbase->AddPerk(perk, 1); - } - } - break; - case RE::FormType::Spell: - { - auto spell = a_form.As(); - if (auto actorEffects = actorbase->GetSpellList()) { - if (a_isBelowLevel) { - actorEffects->RemoveSpell(spell); - } else if (!actorEffects->GetIndex(spell)) { - actorEffects->AddSpell(spell); - } - } - } - break; - case RE::FormType::LeveledSpell: - { - auto spell = a_form.As(); - if (auto actorEffects = actorbase->GetSpellList()) { - if (a_isBelowLevel) { - actorEffects->RemoveLevSpell(spell); - } else { - actorEffects->AddLevSpell(spell); - } - } - } - break; - case RE::FormType::Shout: - { - auto shout = a_form.As(); - if (auto actorEffects = actorbase->GetSpellList()) { - if (a_isBelowLevel) { - actorEffects->RemoveShout(shout); - } else { - actorEffects->AddShout(shout); - } - } - } - break; - default: - { - if (a_form.IsInventoryObject()) { - auto boundObject = static_cast(&a_form); - if (a_isBelowLevel) { - actorbase->RemoveObjectFromContainer(boundObject, a_count); - } else if (actorbase->CountObjectsInContainer(boundObject) < a_count) { - actorbase->AddObjectToContainer(boundObject, a_count, a_this); - } - } - } - break; - } - }); - } - } + if (const auto pcLevelMultManager = PCLevelMult::Manager::GetSingleton(); !pcLevelMultManager->FindDistributedEntry(input)) { + //start distribution for first time + Distribute(actorbase, input); + } else { + //handle redistribution and removal + pcLevelMultManager->ForEachDistributedEntry(input, [&](RE::TESForm& a_form, [[maybe_unused]] IdxOrCount a_count, bool a_isBelowLevel) { + switch (a_form.GetFormType()) { + case RE::FormType::Keyword: + { + const auto keyword = a_form.As(); + if (a_isBelowLevel) { + actorbase->RemoveKeyword(keyword); + } else { + actorbase->AddKeyword(keyword); + } + } + break; + case RE::FormType::Faction: + { + auto faction = a_form.As(); + const auto it = std::ranges::find_if(actorbase->factions, [&](const auto& factionRank) { + return factionRank.faction == faction; + }); + if (it != actorbase->factions.end()) { + if (a_isBelowLevel) { + (*it).rank = -1; + } else { + (*it).rank = 1; + } + } + } + break; + case RE::FormType::Perk: + { + const auto perk = a_form.As(); + if (a_isBelowLevel) { + actorbase->RemovePerk(perk); + } else { + actorbase->AddPerk(perk, 1); + } + } + break; + case RE::FormType::Spell: + { + const auto spell = a_form.As(); + if (const auto actorEffects = actorbase->GetSpellList()) { + if (a_isBelowLevel) { + actorEffects->RemoveSpell(spell); + } else if (!actorEffects->GetIndex(spell)) { + actorEffects->AddSpell(spell); + } + } + } + break; + case RE::FormType::LeveledSpell: + { + const auto spell = a_form.As(); + if (const auto actorEffects = actorbase->GetSpellList()) { + if (a_isBelowLevel) { + actorEffects->RemoveLevSpell(spell); + } else { + actorEffects->AddLevSpell(spell); + } + } + } + break; + case RE::FormType::Shout: + { + const auto shout = a_form.As(); + if (const auto actorEffects = actorbase->GetSpellList()) { + if (a_isBelowLevel) { + actorEffects->RemoveShout(shout); + } else { + actorEffects->AddShout(shout); + } + } + } + break; + default: + { + if (a_form.IsInventoryObject()) { + const auto boundObject = static_cast(&a_form); + if (a_isBelowLevel) { + actorbase->RemoveObjectFromContainer(boundObject, a_count); + } else if (actorbase->CountObjectsInContainer(boundObject) < a_count) { + actorbase->AddObjectToContainer(boundObject, a_count, a_this); + } + } + } + break; + } + }); + } + } - func(a_this, a_buf); - } - static inline REL::Relocation func; + func(a_this, a_buf); + } + static inline REL::Relocation func; - static inline size_t index{ 0 }; - static inline size_t size{ 0xF }; - }; + static inline size_t index{ 0 }; + static inline size_t size{ 0xF }; + }; - void Install() - { - // ProcessLists::HandleUpdate - // inlined into SetLevel in AE - REL::Relocation target{ RELOCATION_ID(40575, 41567), OFFSET(0x86, 0x137) }; - stl::write_thunk_call(target.address()); + void Install() + { + // ProcessLists::HandlePlayerLevelUpdate + // inlined into SetLevel in AE + REL::Relocation target{ RELOCATION_ID(40575, 41567), OFFSET(0x97, 0x137) }; + stl::write_thunk_call(target.address()); - stl::write_vfunc(); - logger::info(" Hooked npc load save"); - } + stl::write_vfunc(); + logger::info(" Hooked npc load save"); + } } diff --git a/SPID/src/LookupConfigs.cpp b/SPID/src/LookupConfigs.cpp index 0010b0b..62d1001 100644 --- a/SPID/src/LookupConfigs.cpp +++ b/SPID/src/LookupConfigs.cpp @@ -1,6 +1,6 @@ #include "LookupConfigs.h" -bool INI::Read() +std::pair INI::Read() { logger::info("{:*^50}", "INI"); @@ -8,7 +8,7 @@ bool INI::Read() if (files.empty()) { logger::warn(" No .ini files with _DISTR suffix were found within the Data folder, aborting..."); - return false; + return { false, false }; } logger::info("\t{} matching inis found", files.size()); @@ -18,6 +18,8 @@ bool INI::Read() configs[RECORD::add[i]] = INIDataVec{}; } + bool shouldLogErrors{ false }; + for (auto& path : files) { logger::info("\tINI : {}", path); @@ -30,6 +32,8 @@ bool INI::Read() continue; } + string::replace_first_instance(path, "Data\\", ""); + if (auto values = ini.GetSection(""); values && !values->empty()) { std::multimap, CSimpleIniA::Entry::LoadOrder> oldFormatMap; for (auto& [key, entry] : *values) { @@ -42,6 +46,7 @@ bool INI::Read() } } catch (...) { logger::warn("\t\tFailed to parse entry [{} = {}]", key.pItem, entry); + shouldLogErrors = true; } } @@ -59,5 +64,5 @@ bool INI::Read() } } - return true; + return { true, shouldLogErrors }; } diff --git a/SPID/src/PCLevelMultManager.cpp b/SPID/src/PCLevelMultManager.cpp index 6afd8df..2467811 100644 --- a/SPID/src/PCLevelMultManager.cpp +++ b/SPID/src/PCLevelMultManager.cpp @@ -145,7 +145,7 @@ namespace PCLevelMult for (const auto& [level, cachedData] : levelMap) { const bool is_below_level = a_input.npcLevel < level; for (auto& [formid, idx] : cachedData.distributedEntries) { - if (auto form = RE::TESForm::LookupByID(formid)) { + if (const auto form = RE::TESForm::LookupByID(formid)) { a_fn(*form, idx, is_below_level); } } @@ -171,7 +171,7 @@ namespace PCLevelMult } // For spawned actors with FF reference IDs - void Manager::DeleteNPC(RE::FormID a_characterID) + void Manager::DeleteNPC(RE::FormID a_characterID) { auto& currentCache = cache[GetSingleton()->GetCurrentPlayerID()]; if (const auto it = currentCache.find(a_characterID); it != currentCache.end()) { diff --git a/SPID/src/main.cpp b/SPID/src/main.cpp index 34f2be7..fcb1c47 100644 --- a/SPID/src/main.cpp +++ b/SPID/src/main.cpp @@ -8,6 +8,7 @@ HMODULE kid{ nullptr }; HMODULE tweaks{ nullptr }; bool shouldLookupForms{ false }; +bool shouldLogErrors{ false }; bool shouldDistribute{ false }; bool DoDistribute() @@ -70,7 +71,7 @@ void MessageHandler(SKSE::MessagingInterface::Message* a_message) tweaks = GetModuleHandle(L"po3_Tweaks"); logger::info("powerofthree's Tweaks (po3_tweaks) detected : {}", tweaks != nullptr); - if (shouldLookupForms = INI::Read(); shouldLookupForms) { + if (std::tie(shouldLookupForms, shouldLogErrors) = INI::Read(); shouldLookupForms) { logger::info("{:*^50}", "HOOKS"); Distribute::LeveledActor::Install(); Distribute::PlayerLeveledActor::Install(); @@ -102,6 +103,10 @@ void MessageHandler(SKSE::MessagingInterface::Message* a_message) PCLevelMult::Manager::GetSingleton()->Register(); logger::info("{:*^50}", "SAVES"); } + if (shouldLogErrors) { + const auto error = fmt::format("[SPID] Errors found when reading configs. Check {}.log in {} for more info\n", Version::PROJECT, SKSE::log::log_directory()->string()); + RE::ConsoleLog::GetSingleton()->Print(error.c_str()); + } } break; case SKSE::MessagingInterface::kNewGame: diff --git a/cmake/ports/clib-util/portfile.cmake b/cmake/ports/clib-util/portfile.cmake index 3deb117..7328181 100644 --- a/cmake/ports/clib-util/portfile.cmake +++ b/cmake/ports/clib-util/portfile.cmake @@ -2,8 +2,8 @@ vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO powerof3/CLibUtil - REF ae6b7fde091baf106c9394cb219db81b3827f3a8 - SHA512 d0a0b5b7f270e1733472e296cf061d8364368e2168cb883348a887bac38b5048f4922bb632660646e92334385dc68a4170030ed7a81d14a88d4ef609e2908538 + REF ee0c71260aa25a6e4686aad1ce2d55bde2e5a808 + SHA512 beff7dacb50dc8963bd65db0528c9e74b542e24f3e6218e77e5b43ab134a9cc531c7f81fd67c699072c53af39ddf9b8cd7d6467ae74305a2e75a4905a25d14d0 HEAD_REF master ) diff --git a/cmake/ports/clib-util/vcpkg.json b/cmake/ports/clib-util/vcpkg.json index d86ae7b..19466bd 100644 --- a/cmake/ports/clib-util/vcpkg.json +++ b/cmake/ports/clib-util/vcpkg.json @@ -1,6 +1,6 @@ { "name": "clib-util", - "version-string": "1.0.0", + "version-string": "1.1.1", "port-version": 1, "description": "", "homepage": "https://github.com/powerof3/CLibUtil"