diff --git a/SPID/include/LinkedDistribution.h b/SPID/include/LinkedDistribution.h index d66c6a7..5fb6bf1 100644 --- a/SPID/include/LinkedDistribution.h +++ b/SPID/include/LinkedDistribution.h @@ -22,6 +22,22 @@ namespace LinkedDistribution kGlobal }; + /// + /// Type of the distribution that is being linked to. + /// + enum DistributionType : std::uint8_t + { + /// + /// Regular distribution that occurs during normal distribution pass. + /// + kRegular, + + /// + /// Distribution that occurs when an NPC dies. + /// + kDeath + }; + namespace INI { struct RawLinkedForm @@ -40,7 +56,7 @@ namespace LinkedDistribution }; using LinkedFormsVec = std::vector; - using LinkedFormsConfig = std::unordered_map; + using LinkedFormsConfig = std::unordered_map>; inline LinkedFormsConfig linkedForms{}; @@ -70,7 +86,7 @@ namespace LinkedDistribution friend Manager; // allow Manager to later modify forms directly. friend Form* detail::LookupLinkedForm(RE::TESDataHandler* const, INI::RawLinkedForm&); - using FormsMap = std::unordered_map>>; + using FormsMap = std::unordered_map>>>; LinkedForms(RECORD::TYPE type) : type(type) @@ -79,13 +95,13 @@ namespace LinkedDistribution RECORD::TYPE GetType() const { return type; } const FormsMap& GetForms() const { return forms; } - void LookupForms(RE::TESDataHandler* const dataHandler, INI::LinkedFormsVec& rawLinkedForms); + void LookupForms(RE::TESDataHandler* const, DistributionType, INI::LinkedFormsVec& rawLinkedForms); private: RECORD::TYPE type; FormsMap forms{}; - void Link(Form*, Scope, const FormVec& linkedForms, const IndexOrCount&, const PercentChance&, const Path&); + void Link(Form*, Scope, DistributionType, const FormVec& linkedForms, const IndexOrCount&, const PercentChance&, const Path&); }; class Manager : public ISingleton @@ -98,33 +114,29 @@ namespace LinkedDistribution /// /// A DataHandler that will perform the actual lookup. /// A raw linked form entries that should be processed. - void LookupLinkedForms(RE::TESDataHandler* const dataHandler, INI::LinkedFormsConfig& rawLinkedForms = INI::linkedForms); + void LookupLinkedForms(RE::TESDataHandler* const, INI::LinkedFormsConfig& rawLinkedForms = INI::linkedForms); void LogLinkedFormsLookup(); /// /// Calculates DistributionSet for each linked form and calls a callback for each of them. /// + /// Type of the distribution for which linked sets should be returned. /// A set of forms for which distribution sets should be calculated. /// This is typically distributed forms accumulated during first distribution pass. /// A callback to be called with each DistributionSet. This is supposed to do the actual distribution. - void ForEachLinkedDistributionSet(const DistributedForms& linkedForms, std::function distribute); + void ForEachLinkedDistributionSet(DistributionType, const DistributedForms& linkedForms, std::function distribute); - /// - /// Calculates DistributionSet with only DeathItems for each linked form and calls a callback for each of them. - /// This method is suitable for distributing items on death. - /// - /// A set of forms for which distribution sets should be calculated. - /// This is typically distributed forms accumulated during first distribution pass. - /// A callback to be called with each DistributionSet. This is supposed to do the actual distribution. - void ForEachLinkedDeathDistributionSet(const DistributedForms& linkedForms, std::function distribute); + bool IsEmpty(DistributionType) const; private: template - DataVec
& LinkedFormsForForm(const DistributedForm&, Scope, LinkedForms&) const; + DataVec& LinkedFormsForForm(DistributionType, const DistributedForm&, Scope, LinkedForms&) const; - void ForEachLinkedDistributionSet(const DistributedForms& linkedForms, Scope, std::function distribute); - void ForEachLinkedDeathDistributionSet(const DistributedForms& linkedForms, Scope, std::function distribute); + void LookupLinkedForms(RE::TESDataHandler* const, DistributionType, INI::LinkedFormsConfig& rawLinkedForms); + void LogLinkedFormsLookup(DistributionType); + + void ForEachLinkedDistributionSet(DistributionType, const DistributedForms& linkedForms, Scope, std::function distribute); LinkedForms spells{ RECORD::kSpell }; LinkedForms perks{ RECORD::kPerk }; @@ -138,18 +150,6 @@ namespace LinkedDistribution LinkedForms factions{ RECORD::kFaction }; LinkedForms skins{ RECORD::kSkin }; - LinkedForms deathSpells{ RECORD::kSpell }; - LinkedForms deathPerks{ RECORD::kPerk }; - LinkedForms deathItems{ RECORD::kItem }; - LinkedForms deathShouts{ RECORD::kShout }; - LinkedForms deathLevSpells{ RECORD::kLevSpell }; - LinkedForms deathPackages{ RECORD::kPackage }; - LinkedForms deathOutfits{ RECORD::kOutfit }; - LinkedForms deathSleepOutfits{ RECORD::kSleepOutfit }; - LinkedForms deathKeywords{ RECORD::kKeyword }; - LinkedForms deathFactions{ RECORD::kFaction }; - LinkedForms deathSkins{ RECORD::kSkin }; - /// /// Iterates over each type of LinkedForms and calls a callback with each of them. /// @@ -207,9 +207,10 @@ namespace LinkedDistribution } template - DataVec& Manager::LinkedFormsForForm(const DistributedForm& form, Scope scope, LinkedForms& linkedForms) const + DataVec& Manager::LinkedFormsForForm(DistributionType type, const DistributedForm& form, Scope scope, LinkedForms& linkedForms) const { - if (const auto formsIt = linkedForms.forms.find(scope == kLocal ? form.second : ""); formsIt != linkedForms.forms.end()) { + auto& forms = linkedForms.forms[type]; + if (const auto formsIt = forms.find(scope == kLocal ? form.second : ""); formsIt != forms.end()) { if (const auto linkedFormsIt = formsIt->second.find(form.first); linkedFormsIt != formsIt->second.end()) { return linkedFormsIt->second; } @@ -236,25 +237,25 @@ namespace LinkedDistribution } template - void LinkedForms::LookupForms(RE::TESDataHandler* const dataHandler, INI::LinkedFormsVec& rawLinkedForms) + void LinkedForms::LookupForms(RE::TESDataHandler* const dataHandler, DistributionType type, INI::LinkedFormsVec& rawLinkedForms) { for (auto& rawForm : rawLinkedForms) { if (auto form = detail::LookupLinkedForm(dataHandler, rawForm); form) { auto& [formID, scope, parentFormIDs, count, chance, path] = rawForm; FormVec parentForms{}; if (Forms::detail::formID_to_form(dataHandler, parentFormIDs.MATCH, parentForms, path, LookupOptions::kNone)) { - Link(form, scope, parentForms, count, chance, path); + Link(form, scope, type, parentForms, count, chance, path); } } } } template - void LinkedForms::Link(Form* form, Scope scope, const FormVec& linkedForms, const IndexOrCount& idxOrCount, const PercentChance& chance, const Path& path) + void LinkedForms::Link(Form* form, Scope scope, DistributionType type, const FormVec& linkedForms, const IndexOrCount& idxOrCount, const PercentChance& chance, const Path& path) { for (const auto& linkedForm : linkedForms) { if (std::holds_alternative(linkedForm)) { - auto& distributableFormsAtPath = forms[scope == kLocal ? path : ""]; // If item is global, we put it in a common map with no information about the path. + auto& distributableFormsAtPath = forms[type][scope == kLocal ? path : ""]; // If item is global, we put it in a common map with no information about the path. auto& distributableForms = distributableFormsAtPath[std::get(linkedForm)]; // Note that we don't use Data.index here, as these linked forms don't have any leveled filters // and as such do not to track their index. diff --git a/SPID/src/DeathDistribution.cpp b/SPID/src/DeathDistribution.cpp index 22d0762..0a84295 100644 --- a/SPID/src/DeathDistribution.cpp +++ b/SPID/src/DeathDistribution.cpp @@ -7,6 +7,7 @@ namespace DeathDistribution { #pragma region Parsing + namespace INI { enum Sections : std::uint32_t @@ -234,19 +235,11 @@ namespace DeathDistribution return true; } } + #pragma endregion - void Manager::Register() - { - if (INI::deathConfigs.empty()) { - return; - } - if (const auto scripts = RE::ScriptEventSourceHolder::GetSingleton()) { - scripts->AddEventSink(GetSingleton()); - logger::info("Registered for {}", typeid(RE::TESDeathEvent).name()); - } - } +#pragma region Lookup void Manager::LookupForms(RE::TESDataHandler* const dataHandler) { @@ -321,16 +314,16 @@ namespace DeathDistribution bool Manager::IsEmpty() { return spells.GetForms().empty() && - perks.GetForms().empty() && - items.GetForms().empty() && - shouts.GetForms().empty() && - levSpells.GetForms().empty() && - packages.GetForms().empty() && - outfits.GetForms().empty() && - keywords.GetForms().empty() && - factions.GetForms().empty() && - sleepOutfits.GetForms().empty() && - skins.GetForms().empty(); + perks.GetForms().empty() && + items.GetForms().empty() && + shouts.GetForms().empty() && + levSpells.GetForms().empty() && + packages.GetForms().empty() && + outfits.GetForms().empty() && + keywords.GetForms().empty() && + factions.GetForms().empty() && + sleepOutfits.GetForms().empty() && + skins.GetForms().empty(); } void Manager::LogFormsLookup() @@ -355,6 +348,21 @@ namespace DeathDistribution } }); } +#pragma endregion + +#pragma region Distribution + + void Manager::Register() + { + if (INI::deathConfigs.empty()) { + return; + } + + if (const auto scripts = RE::ScriptEventSourceHolder::GetSingleton()) { + scripts->AddEventSink(GetSingleton()); + logger::info("Registered for {}", typeid(RE::TESDeathEvent).name()); + } + } RE::BSEventNotifyControl Manager::ProcessEvent(const RE::TESDeathEvent* a_event, RE::BSTEventSource*) { @@ -389,7 +397,7 @@ namespace DeathDistribution // TODO: We can now log per-NPC distributed forms. if (!distributedForms.empty()) { - LinkedDistribution::Manager::GetSingleton()->ForEachLinkedDeathDistributionSet(distributedForms, [&](Forms::DistributionSet& set) { + LinkedDistribution::Manager::GetSingleton()->ForEachLinkedDistributionSet(LinkedDistribution::kDeath, distributedForms, [&](Forms::DistributionSet& set) { Distribute::Distribute(npcData, input, set, true, nullptr); // TODO: Accumulate forms here? to log what was distributed. }); } @@ -398,4 +406,5 @@ namespace DeathDistribution return RE::BSEventNotifyControl::kContinue; } +#pragma endregion } diff --git a/SPID/src/Distribute.cpp b/SPID/src/Distribute.cpp index d349eb5..9817e4d 100644 --- a/SPID/src/Distribute.cpp +++ b/SPID/src/Distribute.cpp @@ -170,7 +170,7 @@ namespace Distribute if (!distributedForms.empty()) { // TODO: This only does one-level linking. So that linked entries won't trigger another level of distribution. - LinkedDistribution::Manager::GetSingleton()->ForEachLinkedDistributionSet(distributedForms, [&](Forms::DistributionSet& set) { + LinkedDistribution::Manager::GetSingleton()->ForEachLinkedDistributionSet(LinkedDistribution::kRegular, distributedForms, [&](Forms::DistributionSet& set) { Distribute(npcData, input, set, true, nullptr); // TODO: Accumulate forms here? to log what was distributed. }); } diff --git a/SPID/src/LinkedDistribution.cpp b/SPID/src/LinkedDistribution.cpp index 032b7cc..ad1e5fa 100644 --- a/SPID/src/LinkedDistribution.cpp +++ b/SPID/src/LinkedDistribution.cpp @@ -27,6 +27,8 @@ namespace LinkedDistribution Scope scope = kLocal; + DistributionType distributionType = kRegular; + if (key.starts_with("Global"sv)) { scope = kGlobal; key.erase(0, 6); @@ -36,9 +38,14 @@ namespace LinkedDistribution return false; } - // TODO: Parse also Linked Death Forms + key.erase(0, 6); + + if (key.starts_with("Death"sv)) { + distributionType = kDeath; + key.erase(0, 5); + } - std::string rawType = key.substr(6); + std::string rawType = key; auto type = RECORD::GetType(rawType); if (type == RECORD::kTotal) { @@ -101,7 +108,7 @@ namespace LinkedDistribution } } - linkedForms[type].push_back(item); + linkedForms[distributionType][type].push_back(item); return true; } @@ -110,17 +117,17 @@ namespace LinkedDistribution #pragma region Lookup - void Manager::LookupLinkedForms(RE::TESDataHandler* dataHandler, INI::LinkedFormsConfig& rawLinkedForms) + void Manager::LookupLinkedForms(RE::TESDataHandler* const dataHandler, DistributionType distributionType, INI::LinkedFormsConfig& rawLinkedForms) { ForEachLinkedForms([&](LinkedForms& forms) { // If it's spells distributable we want to manually lookup forms to pick LevSpells that are added into the list. if constexpr (!std::is_same_v) { - forms.LookupForms(dataHandler, rawLinkedForms[forms.GetType()]); + forms.LookupForms(dataHandler, distributionType, rawLinkedForms[distributionType][forms.GetType()]); } }); // Sort out Spells and Leveled Spells into two separate lists. - auto& rawSpells = rawLinkedForms[RECORD::kSpell]; + auto& rawSpells = rawLinkedForms[distributionType][RECORD::kSpell]; for (auto& rawSpell : rawSpells) { if (auto form = detail::LookupLinkedForm(dataHandler, rawSpell); form) { @@ -130,14 +137,14 @@ namespace LinkedDistribution continue; } if (const auto spell = form->As(); spell) { - spells.Link(spell, scope, parentForms, idxOrCount, chance, path); + spells.Link(spell, scope, distributionType, parentForms, idxOrCount, chance, path); } else if (const auto levSpell = form->As(); levSpell) { - levSpells.Link(levSpell, scope, parentForms, idxOrCount, chance, path); + levSpells.Link(levSpell, scope, distributionType, parentForms, idxOrCount, chance, path); } } } - auto& genericForms = rawLinkedForms[RECORD::kForm]; + auto& genericForms = rawLinkedForms[distributionType][RECORD::kForm]; for (auto& rawForm : genericForms) { if (auto form = detail::LookupLinkedForm(dataHandler, rawForm); form) { auto& [formID, scope, parentFormIDs, idxOrCount, chance, path] = rawForm; @@ -147,21 +154,21 @@ namespace LinkedDistribution } // Add to appropriate list. (Note that type inferring doesn't recognize SleepOutfit, Skin or DeathItems) if (const auto keyword = form->As(); keyword) { - keywords.Link(keyword, scope, parentForms, idxOrCount, chance, path); + keywords.Link(keyword, scope, distributionType, parentForms, idxOrCount, chance, path); } else if (const auto spell = form->As(); spell) { - spells.Link(spell, scope, parentForms, idxOrCount, chance, path); + spells.Link(spell, scope, distributionType, parentForms, idxOrCount, chance, path); } else if (const auto levSpell = form->As(); levSpell) { - levSpells.Link(levSpell, scope, parentForms, idxOrCount, chance, path); + levSpells.Link(levSpell, scope, distributionType, parentForms, idxOrCount, chance, path); } else if (const auto perk = form->As(); perk) { - perks.Link(perk, scope, parentForms, idxOrCount, chance, path); + perks.Link(perk, scope, distributionType, parentForms, idxOrCount, chance, path); } else if (const auto shout = form->As(); shout) { - shouts.Link(shout, scope, parentForms, idxOrCount, chance, path); + shouts.Link(shout, scope, distributionType, parentForms, idxOrCount, chance, path); } else if (const auto item = form->As(); item) { - items.Link(item, scope, parentForms, idxOrCount, chance, path); + items.Link(item, scope, distributionType, parentForms, idxOrCount, chance, path); } else if (const auto outfit = form->As(); outfit) { - outfits.Link(outfit, scope, parentForms, idxOrCount, chance, path); + outfits.Link(outfit, scope, distributionType, parentForms, idxOrCount, chance, path); } else if (const auto faction = form->As(); faction) { - factions.Link(faction, scope, parentForms, idxOrCount, chance, path); + factions.Link(faction, scope, distributionType, parentForms, idxOrCount, chance, path); } else { auto type = form->GetFormType(); if (type == RE::FormType::Package || type == RE::FormType::FormList) { @@ -176,7 +183,7 @@ namespace LinkedDistribution } else { packageIndex = std::get(idxOrCount); } - packages.Link(form, scope, parentForms, packageIndex, chance, path); + packages.Link(form, scope, distributionType, parentForms, packageIndex, chance, path); } else { logger::warn("\t[{}] Unsupported Form type: {}", path, type); } @@ -186,8 +193,14 @@ namespace LinkedDistribution // Remove empty linked forms ForEachLinkedForms([&](LinkedForms& forms) { - std::erase_if(forms.forms, [](const auto& pair) { return pair.second.empty(); }); + std::erase_if(forms.forms[distributionType], [](const auto& pair) { return pair.second.empty(); }); }); + } + + void Manager::LookupLinkedForms(RE::TESDataHandler* dataHandler, INI::LinkedFormsConfig& rawLinkedForms) + { + LookupLinkedForms(dataHandler, kRegular, rawLinkedForms); + LookupLinkedForms(dataHandler, kDeath, rawLinkedForms); // Clear INI once lookup is done rawLinkedForms.clear(); @@ -196,19 +209,16 @@ namespace LinkedDistribution buffered_logger::clear(); } - void Manager::LogLinkedFormsLookup() + void Manager::LogLinkedFormsLookup(DistributionType type) { - logger::info("{:*^50}", "LINKED ITEMS"); - - ForEachLinkedForms([](LinkedForms& linkedForms) { - if (linkedForms.GetForms().empty()) { + ForEachLinkedForms([&](LinkedForms& linkedForms) { + if (linkedForms.forms[type].empty()) { return; } - std::unordered_map> map{}; // Iterate through the original map - for (const auto& pair : linkedForms.GetForms()) { + for (const auto& pair : linkedForms.forms[type]) { const auto& path = pair.first; const auto& formsMap = pair.second; @@ -237,69 +247,39 @@ namespace LinkedDistribution } }); } -#pragma endregion -#pragma region Distribution - void Manager::ForEachLinkedDistributionSet(const DistributedForms& targetForms, Scope scope, std::function performDistribution) + void Manager::LogLinkedFormsLookup() { - for (const auto& form : targetForms) { - auto& linkedSpells = LinkedFormsForForm(form, scope, spells); - auto& linkedPerks = LinkedFormsForForm(form, scope, perks); - auto& linkedItems = LinkedFormsForForm(form, scope, items); - auto& linkedShouts = LinkedFormsForForm(form, scope, shouts); - auto& linkedLevSpells = LinkedFormsForForm(form, scope, levSpells); - auto& linkedPackages = LinkedFormsForForm(form, scope, packages); - auto& linkedOutfits = LinkedFormsForForm(form, scope, outfits); - auto& linkedKeywords = LinkedFormsForForm(form, scope, keywords); - auto& linkedFactions = LinkedFormsForForm(form, scope, factions); - auto& linkedSleepOutfits = LinkedFormsForForm(form, scope, sleepOutfits); - auto& linkedSkins = LinkedFormsForForm(form, scope, skins); - - DistributionSet linkedEntries{ - linkedSpells, - linkedPerks, - linkedItems, - linkedShouts, - linkedLevSpells, - linkedPackages, - linkedOutfits, - linkedKeywords, - linkedFactions, - linkedSleepOutfits, - linkedSkins - }; - - if (linkedEntries.IsEmpty()) { - continue; - } + if (!IsEmpty(kRegular)) { + logger::info("{:*^50}", "LINKED FORMS"); - performDistribution(linkedEntries); + LogLinkedFormsLookup(kRegular); } - } + + if (!IsEmpty(kDeath)) { + logger::info("{:*^50}", "LINKED ON DEATH FORMS"); - void Manager::ForEachLinkedDistributionSet(const DistributedForms& targetForms, std::function performDistribution) - { - ForEachLinkedDistributionSet(targetForms, Scope::kLocal, performDistribution); - ForEachLinkedDistributionSet(targetForms, Scope::kGlobal, performDistribution); + LogLinkedFormsLookup(kDeath); + } } +#pragma endregion - void Manager::ForEachLinkedDeathDistributionSet(const DistributedForms& targetForms, Scope scope, std::function performDistribution) +#pragma region Distribution + void Manager::ForEachLinkedDistributionSet(DistributionType type, const DistributedForms& targetForms, Scope scope, std::function performDistribution) { for (const auto& form : targetForms) { - auto& linkedDeathItems = LinkedFormsForForm(form, scope, deathItems); - DistributionSet linkedEntries{ - DistributionSet::empty(), - DistributionSet::empty(), - DistributionSet::empty(), - DistributionSet::empty(), - DistributionSet::empty(), - DistributionSet::empty(), - DistributionSet::empty(), - DistributionSet::empty(), - DistributionSet::empty(), - DistributionSet::empty(), - DistributionSet::empty() + LinkedFormsForForm(type, form, scope, spells), + LinkedFormsForForm(type, form, scope, perks), + LinkedFormsForForm(type, form, scope, items), + LinkedFormsForForm(type, form, scope, shouts), + LinkedFormsForForm(type, form, scope, levSpells), + LinkedFormsForForm(type, form, scope, packages), + LinkedFormsForForm(type, form, scope, outfits), + LinkedFormsForForm(type, form, scope, keywords), + LinkedFormsForForm(type, form, scope, factions), + LinkedFormsForForm(type, form, scope, sleepOutfits), + LinkedFormsForForm(type, form, scope, skins) }; if (linkedEntries.IsEmpty()) { @@ -310,10 +290,15 @@ namespace LinkedDistribution } } - void Manager::ForEachLinkedDeathDistributionSet(const DistributedForms& targetForms, std::function performDistribution) + void Manager::ForEachLinkedDistributionSet(DistributionType type, const DistributedForms& targetForms, std::function performDistribution) + { + ForEachLinkedDistributionSet(type, targetForms, Scope::kLocal, performDistribution); + ForEachLinkedDistributionSet(type, targetForms, Scope::kGlobal, performDistribution); + } + + bool Manager::IsEmpty(DistributionType type) const { - ForEachLinkedDeathDistributionSet(targetForms, Scope::kLocal, performDistribution); - ForEachLinkedDeathDistributionSet(targetForms, Scope::kGlobal, performDistribution); + return false; } #pragma endregion }