diff --git a/SPID/include/Defs.h b/SPID/include/Defs.h index 7691604..57c23f5 100644 --- a/SPID/include/Defs.h +++ b/SPID/include/Defs.h @@ -54,6 +54,16 @@ struct Range return value >= min && value <= max; } + [[nodiscard]] bool IsExact() const + { + return min == max; + } + + [[nodiscard]] T GetRandom() const + { + return IsExact() ? min : RNG().generate(min, max); + } + // members T min{ std::numeric_limits::min() }; T max{ std::numeric_limits::max() }; @@ -83,7 +93,10 @@ struct Traits std::optional teammate{}; }; -using IdxOrCount = std::int32_t; +using Index = std::int32_t; +using Count = std::int32_t; +using RandomCount = Range; +using IndexOrCount = std::variant; using Chance = std::uint32_t; /// A standardized way of converting any object to string. diff --git a/SPID/include/Distribute.h b/SPID/include/Distribute.h index 787931b..f0fd2f2 100644 --- a/SPID/include/Distribute.h +++ b/SPID/include/Distribute.h @@ -77,10 +77,10 @@ namespace Distribute // for now, only packages/death items use this template void for_each_form( - const NPCData& a_npcData, - Forms::Distributables
& a_distributables, - const PCLevelMult::Input& a_input, - std::function a_callback) + const NPCData& a_npcData, + Forms::Distributables& a_distributables, + const PCLevelMult::Input& a_input, + std::function a_callback) { auto& vec = a_distributables.GetForms(a_input.onlyPlayerLevelEntries); @@ -131,10 +131,10 @@ namespace Distribute // items template void for_each_form( - const NPCData& a_npcData, - Forms::Distributables& a_distributables, - const PCLevelMult::Input& a_input, - std::function&, bool)> a_callback) + const NPCData& a_npcData, + Forms::Distributables& a_distributables, + const PCLevelMult::Input& a_input, + std::function&, bool)> a_callback) { auto& vec = a_distributables.GetForms(a_input.onlyPlayerLevelEntries); @@ -142,15 +142,15 @@ namespace Distribute return; } - std::map collectedForms{}; - bool hasLeveledItems = false; + std::map collectedForms{}; + bool hasLeveledItems = false; for (auto& formData : vec) { if (!a_npcData.HasMutuallyExclusiveForm(formData.form) && detail::passed_filters(a_npcData, a_input, formData)) { if (formData.form->Is(RE::FormType::LeveledItem)) { hasLeveledItems = true; } - collectedForms.emplace(formData.form, formData.idxOrCount); + collectedForms.emplace(formData.form, std::get(formData.idxOrCount).GetRandom()); ++formData.npcCount; } } diff --git a/SPID/include/FormData.h b/SPID/include/FormData.h index 093acb9..9c165b0 100644 --- a/SPID/include/FormData.h +++ b/SPID/include/FormData.h @@ -307,9 +307,9 @@ namespace Forms { std::uint32_t index{ 0 }; - Form* form{ nullptr }; - IdxOrCount idxOrCount{ 1 }; - FilterData filters{}; + Form* form{ nullptr }; + IndexOrCount idxOrCount{ RandomCount(1, 1) }; + FilterData filters{}; std::string path{}; std::uint32_t npcCount{ 0 }; diff --git a/SPID/include/LookupConfigs.h b/SPID/include/LookupConfigs.h index 063c2ed..07e9181 100644 --- a/SPID/include/LookupConfigs.h +++ b/SPID/include/LookupConfigs.h @@ -46,7 +46,7 @@ namespace INI Filters rawFormFilters{}; LevelFilters levelFilters{}; Traits traits{}; - IdxOrCount idxOrCount{ 1 }; + IndexOrCount idxOrCount{ RandomCount(1, 1) }; Chance chance{ 100 }; std::string path{}; }; diff --git a/SPID/src/Distribute.cpp b/SPID/src/Distribute.cpp index 2e40cee..0f2a559 100644 --- a/SPID/src/Distribute.cpp +++ b/SPID/src/Distribute.cpp @@ -89,8 +89,8 @@ namespace Distribute npc->GetSpellList()->AddShouts(a_shouts); }); - for_each_form(a_npcData, Forms::packages, a_input, [&](auto* a_packageOrList, [[maybe_unused]] IdxOrCount a_idx) { - auto packageIdx = a_idx; + for_each_form(a_npcData, Forms::packages, a_input, [&](auto* a_packageOrList, [[maybe_unused]] IndexOrCount a_idx) { + auto packageIdx = std::get(a_idx); if (a_packageOrList->Is(RE::FormType::Package)) { auto package = a_packageOrList->As(); @@ -172,7 +172,7 @@ namespace Distribute const auto npc = a_npcData.GetNPC(); const auto actor = a_npcData.GetActor(); - for_each_form(a_npcData, Forms::items, a_input, [&](std::map& a_objects, const bool a_hasLvlItem) { + for_each_form(a_npcData, Forms::items, a_input, [&](std::map& a_objects, const bool a_hasLvlItem) { if (npc->AddObjectsToContainer(a_objects, npc)) { if (a_hasLvlItem) { detail::init_leveled_items(actor); diff --git a/SPID/src/DistributeManager.cpp b/SPID/src/DistributeManager.cpp index be7e6a3..63b8414 100644 --- a/SPID/src/DistributeManager.cpp +++ b/SPID/src/DistributeManager.cpp @@ -222,8 +222,10 @@ namespace Distribute::Event const auto npcData = NPCData(actor, npc); const auto input = PCLevelMult::Input{ actor, npc, false }; - for_each_form(npcData, Forms::deathItems, input, [&](auto* a_deathItem, IdxOrCount a_count) { - detail::add_item(actor, a_deathItem, a_count); + for_each_form(npcData, Forms::deathItems, input, [&](auto* deathItem, IndexOrCount idxOrCount) { + auto count = std::get(idxOrCount); + + detail::add_item(actor, deathItem, count.GetRandom()); return true; }); } diff --git a/SPID/src/LookupConfigs.cpp b/SPID/src/LookupConfigs.cpp index 07df2e0..f3c3a9f 100644 --- a/SPID/src/LookupConfigs.cpp +++ b/SPID/src/LookupConfigs.cpp @@ -249,9 +249,25 @@ namespace INI if (a_key == "Package") { // reuse item count for package stack index data.idxOrCount = 0; } + if (kIdxOrCount < size) { - if (const auto& str = sections[kIdxOrCount]; distribution::is_valid_entry(str)) { - data.idxOrCount = string::to_num(str); + if (a_key == "Package") { // If it's a package, then we only expect a single number. + if (const auto& str = sections[kIdxOrCount]; distribution::is_valid_entry(str)) { + data.idxOrCount = string::to_num(str); + } + } else { + if (const auto& str = sections[kIdxOrCount]; distribution::is_valid_entry(str)) { + if (auto countPair = string::split(str, "-"); countPair.size() > 1) { + auto minCount = string::to_num(countPair[0]); + auto maxCount = string::to_num(countPair[1]); + + data.idxOrCount = RandomCount(minCount, maxCount); + } else { + auto count = string::to_num(str); + + data.idxOrCount = RandomCount(count, count); // create the exact match range. + } + } } }