diff --git a/SPID/include/Distribute.h b/SPID/include/Distribute.h index 9c2c2ed..f840a3e 100644 --- a/SPID/include/Distribute.h +++ b/SPID/include/Distribute.h @@ -235,6 +235,8 @@ namespace Distribute } #pragma endregion - void Distribute(NPCData& a_npcData, const PCLevelMult::Input& a_input); - void Distribute(NPCData& a_npcData, bool a_onlyLeveledEntries); + void Distribute(NPCData& npcData, const PCLevelMult::Input& input); + void Distribute(NPCData& npcData, bool onlyLeveledEntries); + + void DistributeDeathItems(NPCData& npcData, const PCLevelMult::Input& input); } diff --git a/SPID/include/FormData.h b/SPID/include/FormData.h index 780725e..3d46c05 100644 --- a/SPID/include/FormData.h +++ b/SPID/include/FormData.h @@ -31,6 +31,25 @@ namespace Forms {} }; + /// + /// An exception thrown when actual form's type does not match the form type excplicilty defined in the config. + /// E.g. Spell = 0x12345, but the 0x12345 form is actually a Perk. + /// + struct MismatchingFormTypeException : std::exception + { + const RE::FormType expectedFformType; + const RE::FormType actualFormType; + const FormOrEditorID formOrEditorID; + const std::string path; + + MismatchingFormTypeException(RE::FormType expectedFformType, RE::FormType actualFormType, const FormOrEditorID& formOrEditorID, const std::string& path) : + expectedFformType(expectedFformType), + actualFormType(actualFormType), + formOrEditorID(formOrEditorID), + path(path) + {} + }; + struct InvalidKeywordException : std::exception { const RE::FormID formID; @@ -68,6 +87,9 @@ namespace Forms {} }; + /// + /// An exception thrown when actual form's type is not in the whitelist. + /// struct InvalidFormTypeException : std::exception { const RE::FormType formType; @@ -182,15 +204,22 @@ namespace Forms // Either 0x1235 or 0x1235~MyPlugin.esp if (formID) { + RE::TESForm* anyForm; if (modName) { - form = as_form(dataHandler->LookupForm(*formID, *modName)); + anyForm = dataHandler->LookupForm(*formID, *modName); } else { - form = as_form(RE::TESForm::LookupByID(*formID)); + anyForm = RE::TESForm::LookupByID(*formID); } - if (!form) { + + if (!anyForm) { throw UnknownFormIDException(*formID, path, modName); } + form = as_form(anyForm); + if (!form) { + throw MismatchingFormTypeException(anyForm->GetFormType(), Form::FORMTYPE, FormModPair{ *formID, modName }, path); + } + if constexpr (std::is_same_v) { if (string::is_empty(form->GetFormEditorID())) { // Keywords with empty EditorIDs cause game to crash. @@ -207,8 +236,12 @@ namespace Forms if constexpr (std::is_same_v) { form = find_or_create_keyword(editorID); } else { - form = as_form(RE::TESForm::LookupByEditorID(editorID)); - if (!form) { + if (const auto anyForm = RE::TESForm::LookupByEditorID(editorID); anyForm) { + form = as_form(anyForm); + if (!form) { + throw MismatchingFormTypeException(anyForm->GetFormType(), Form::FORMTYPE, editorID, path); + } + } else { if constexpr (std::is_same_v) { form = find_or_create_keyword(editorID); } else { @@ -285,6 +318,16 @@ namespace Forms buffered_logger::error("\t\t[{}] Filter ({}) SKIP - editorID doesn't exist", e.path, e.editorID); } catch (const MalformedEditorIDException& e) { buffered_logger::error("\t\t[{}] Filter (\"\") SKIP - malformed editorID", e.path); + } catch (const Lookup::MismatchingFormTypeException& e) { + std::visit(overload{ + [&](const FormModPair& formMod) { + auto& [formID, modName] = formMod; + buffered_logger::error("\t\t[{}] Filter[0x{:X}] ({}) FAIL - mismatching form type (expected: {}, actual: {})", e.path, *formID, modName.value_or(""), RE::FormTypeToString(e.expectedFformType), RE::FormTypeToString(e.actualFormType)); + }, + [&](std::string editorID) { + buffered_logger::error("\t\t[{}] Filter ({}) FAIL - mismatching form type (expected: {}, actual: {})", e.path, editorID, RE::FormTypeToString(e.expectedFformType), RE::FormTypeToString(e.actualFormType)); + } }, + e.formOrEditorID); } catch (const InvalidFormTypeException& e) { std::visit(overload{ [&](const FormModPair& formMod) { @@ -370,12 +413,15 @@ namespace Forms std::size_t GetSize() const; std::size_t GetLeveledSize() const; + std::size_t GetLookupCount() const; + RECORD::TYPE GetType() const; DataVec
& GetForms(bool a_onlyLevelEntries); DataVec& GetForms(); void LookupForms(RE::TESDataHandler* a_dataHandler, std::string_view a_type, INI::DataVec& a_INIDataVec); + void EmplaceForm(bool isValid, Form*, const IndexOrCount&, const FilterData&, const std::string& path); // Init formsWithLevels and formsNoLevels void FinishLookupForms(); @@ -384,6 +430,12 @@ namespace Forms RECORD::TYPE type; DataVec forms{}; DataVec formsWithLevels{}; + + /// Total number of entries that were matched to this Distributable, including invalid. + /// This counter is used for logging purposes. + std::size_t lookupCount{ 0 }; + + void LookupForm(RE::TESDataHandler* a_dataHandler, INI::Data& rawForm); }; inline Distributables spells{ RECORD::kSpell }; @@ -418,6 +470,17 @@ namespace Forms a_func(packages, std::forward(args)...); a_func(skins, std::forward(args)...); } + + /// + /// Performs lookup for a single entry. + /// It's up to the callee to add the form to the appropriate distributable container. + /// + /// Type of the form to lookup. This type can be defaulted to accept any TESForm. + /// + /// A raw form entry that needs to be looked up. + /// A callback to be called with validated data after successful lookup. + template + void LookupGenericForm(RE::TESDataHandler* const dataHandler, INI::Data& rawForm, std::function callback); } template @@ -447,6 +510,12 @@ std::size_t Forms::Distributables::GetLeveledSize() const return formsWithLevels.size(); } +template +std::size_t Forms::Distributables::GetLookupCount() const +{ + return lookupCount; +} + template RECORD::TYPE Forms::Distributables::GetType() const { @@ -469,7 +538,15 @@ Forms::DataVec& Forms::Distributables::GetForms(bool a_onlyLevelEntr } template -void Forms::Distributables::LookupForms(RE::TESDataHandler* a_dataHandler, std::string_view a_type, INI::DataVec& a_INIDataVec) +void Forms::Distributables::LookupForm(RE::TESDataHandler* dataHandler, INI::Data& rawForm) +{ + Forms::LookupGenericForm(dataHandler, rawForm, [&](bool isValid, Form* form, const auto& idxOrCount, const auto& filters, const auto& path) { + EmplaceForm(isValid, form, idxOrCount, filters, path); + }); +} + +template +void Forms::Distributables::LookupForms(RE::TESDataHandler* dataHandler, std::string_view a_type, INI::DataVec& a_INIDataVec) { if (a_INIDataVec.empty()) { return; @@ -478,46 +555,19 @@ void Forms::Distributables::LookupForms(RE::TESDataHandler* a_dataHandler, logger::info("{}", a_type); forms.reserve(a_INIDataVec.size()); - std::uint32_t index = 0; - - for (auto& [formOrEditorID, strings, filterIDs, level, traits, idxOrCount, chance, path] : a_INIDataVec) { - try { - if (auto form = detail::get_form(a_dataHandler, formOrEditorID, path); form) { - FormFilters filterForms{}; - bool validEntry = detail::formID_to_form(a_dataHandler, filterIDs.ALL, filterForms.ALL, path, true); - if (validEntry) { - validEntry = detail::formID_to_form(a_dataHandler, filterIDs.NOT, filterForms.NOT, path); - } - if (validEntry) { - validEntry = detail::formID_to_form(a_dataHandler, filterIDs.MATCH, filterForms.MATCH, path); - } + for (auto& rawForm : a_INIDataVec) { + LookupForm(dataHandler, rawForm); + } +} - if (validEntry) { - forms.emplace_back(index, form, idxOrCount, FilterData(strings, filterForms, level, traits, chance), path); - index++; - } - } - } catch (const Lookup::UnknownFormIDException& e) { - buffered_logger::error("\t[{}] [0x{:X}] ({}) FAIL - formID doesn't exist", e.path, e.formID, e.modName.value_or("")); - } catch (const Lookup::InvalidKeywordException& e) { - buffered_logger::error("\t[{}] [0x{:X}] ({}) FAIL - keyword does not have a valid editorID", e.path, e.formID, e.modName.value_or("")); - } catch (const Lookup::KeywordNotFoundException& e) { - if (e.isDynamic) { - buffered_logger::critical("\t[{}] {} FAIL - couldn't create keyword", e.path, e.editorID); - } else { - buffered_logger::critical("\t[{}] {} FAIL - couldn't get existing keyword", e.path, e.editorID); - } - } catch (const Lookup::UnknownEditorIDException& e) { - buffered_logger::error("\t[{}] ({}) FAIL - editorID doesn't exist", e.path, e.editorID); - } catch (const Lookup::MalformedEditorIDException& e) { - buffered_logger::error("\t[{}] FAIL - editorID can't be empty", e.path); - } catch (const Lookup::InvalidFormTypeException& e) { - // Whitelisting is disabled, so this should not occur - } catch (const Lookup::UnknownPluginException& e) { - // Likewise, we don't expect plugin names in distributable forms. - } +template +void Forms::Distributables::EmplaceForm(bool isValid, Form* form, const IndexOrCount& idxOrCount, const FilterData& filters, const std::string& path) +{ + if (isValid) { + forms.emplace_back(forms.size(), form, idxOrCount, filters, path); } + lookupCount++; } template @@ -542,3 +592,54 @@ void Forms::Distributables::FinishLookupForms() std::back_inserter(formsWithLevels), [](const auto& formData) { return formData.filters.HasLevelFilters(); }); } + +template +void Forms::LookupGenericForm(RE::TESDataHandler* const dataHandler, INI::Data& rawForm, std::function callback) +{ + auto& [formOrEditorID, strings, filterIDs, level, traits, idxOrCount, chance, path] = rawForm; + + try { + if (auto form = detail::get_form(dataHandler, formOrEditorID, path); form) { + FormFilters filterForms{}; + + bool validEntry = detail::formID_to_form(dataHandler, filterIDs.ALL, filterForms.ALL, path, true); + if (validEntry) { + validEntry = detail::formID_to_form(dataHandler, filterIDs.NOT, filterForms.NOT, path); + } + if (validEntry) { + validEntry = detail::formID_to_form(dataHandler, filterIDs.MATCH, filterForms.MATCH, path); + } + + FilterData filters{ strings, filterForms, level, traits, chance }; + callback(validEntry, form, idxOrCount, filters, path); + } + } catch (const Lookup::UnknownFormIDException& e) { + buffered_logger::error("\t[{}] [0x{:X}] ({}) FAIL - formID doesn't exist", e.path, e.formID, e.modName.value_or("")); + } catch (const Lookup::InvalidKeywordException& e) { + buffered_logger::error("\t[{}] [0x{:X}] ({}) FAIL - keyword does not have a valid editorID", e.path, e.formID, e.modName.value_or("")); + } catch (const Lookup::KeywordNotFoundException& e) { + if (e.isDynamic) { + buffered_logger::critical("\t[{}] {} FAIL - couldn't create keyword", e.path, e.editorID); + } else { + buffered_logger::critical("\t[{}] {} FAIL - couldn't get existing keyword", e.path, e.editorID); + } + } catch (const Lookup::UnknownEditorIDException& e) { + buffered_logger::error("\t[{}] ({}) FAIL - editorID doesn't exist", e.path, e.editorID); + } catch (const Lookup::MalformedEditorIDException& e) { + buffered_logger::error("\t[{}] FAIL - editorID can't be empty", e.path); + } catch (const Lookup::MismatchingFormTypeException& e) { + std::visit(overload{ + [&](const FormModPair& formMod) { + auto& [formID, modName] = formMod; + buffered_logger::error("\t\t[{}] [0x{:X}] ({}) FAIL - mismatching form type (expected: {}, actual: {})", e.path, *formID, modName.value_or(""), RE::FormTypeToString(e.expectedFformType), RE::FormTypeToString(e.actualFormType)); + }, + [&](std::string editorID) { + buffered_logger::error("\t\t[{}] ({}) FAIL - mismatching form type (expected: {}, actual: {})", e.path, editorID, RE::FormTypeToString(e.expectedFformType), RE::FormTypeToString(e.actualFormType)); + } }, + e.formOrEditorID); + } catch (const Lookup::InvalidFormTypeException& e) { + // Whitelisting is disabled, so this should not occur + } catch (const Lookup::UnknownPluginException& e) { + // Likewise, we don't expect plugin names in distributable forms. + } +} diff --git a/SPID/include/LinkedDistribution.h b/SPID/include/LinkedDistribution.h index ee56f73..e862e62 100644 --- a/SPID/include/LinkedDistribution.h +++ b/SPID/include/LinkedDistribution.h @@ -7,23 +7,28 @@ namespace LinkedDistribution { namespace INI { - struct RawLinkedItem + struct RawLinkedForm { - FormOrEditorID rawForm{}; + FormOrEditorID formOrEditorID{}; - /// Raw filters in RawLinkedItem only use MATCH, there is no meaning for ALL or NOT, so they are ignored. - Filters rawFormFilters{}; + /// Raw filters in RawLinkedForm only use MATCH, there is no meaning for ALL or NOT, so they are ignored. + Filters formIDs{}; - RandomCount count{ 1, 1 }; - Chance chance{ 100 }; + IndexOrCount idxOrCount{ RandomCount(1, 1) }; + Chance chance{ 100 }; std::string path{}; }; - using LinkedItemsVec = std::vector; + using LinkedFormsVec = std::vector; + using LinkedFormsConfig = std::unordered_map; - inline LinkedItemsVec linkedItems{}; + inline LinkedFormsConfig linkedForms{}; + /// + /// Checks whether given entry is a linked form and attempts to parse it. + /// + /// true if given entry was a linked form. Note that returned value doesn't represent whether or parsing was successful. bool TryParse(const std::string& a_key, const std::string& a_value, const std::string& a_path); } @@ -31,25 +36,37 @@ namespace LinkedDistribution class Manager; + template + struct LinkedForms; + + namespace detail + { + template + Form* LookupLinkedForm(RE::TESDataHandler* const dataHandler, INI::RawLinkedForm& rawForm); + } + template struct LinkedForms { friend Manager; // allow Manager to later modify forms directly. + friend Form* detail::LookupLinkedForm(RE::TESDataHandler* const, INI::RawLinkedForm&); - using Map = std::unordered_map>; + using FormsMap = std::unordered_map>; LinkedForms(RECORD::TYPE type) : type(type) {} - RECORD::TYPE GetType() const { return type; } - const Map& GetForms() const { return forms; } + RECORD::TYPE GetType() const { return type; } + const FormsMap& GetForms() const { return forms; } + + void LookupForms(RE::TESDataHandler* const dataHandler, INI::LinkedFormsVec& rawLinkedForms); private: RECORD::TYPE type; - Map forms{}; + FormsMap forms{}; - void Link(Form* form, const FormVec& linkedForms, const RandomCount& count, const Chance& chance, const std::string& path); + void Link(Form* form, const FormVec& linkedForms, const IndexOrCount& idxOrCount, const Chance& chance, const std::string& path); }; class Manager : public ISingleton @@ -58,13 +75,13 @@ namespace LinkedDistribution /// /// Does a forms lookup similar to what Filters do. /// - /// As a result this method configures Manager with discovered valid linked items. + /// As a result this method configures Manager with discovered valid linked forms. /// /// A DataHandler that will perform the actual lookup. - /// A raw linked item entries that should be processed. - void LookupLinkedItems(RE::TESDataHandler* const dataHandler, INI::LinkedItemsVec& rawLinkedItems = INI::linkedItems); + /// A raw linked form entries that should be processed. + void LookupLinkedForms(RE::TESDataHandler* const dataHandler, INI::LinkedFormsConfig& rawLinkedForms = INI::linkedForms); - void LogLinkedItemsLookup(); + void LogLinkedFormsLookup(); /// /// Calculates DistributionSet for each linked form and calls a callback for each of them. @@ -74,6 +91,15 @@ namespace LinkedDistribution /// A callback to be called with each DistributionSet. This is supposed to do the actual distribution. void ForEachLinkedDistributionSet(const std::set& linkedForms, std::function callback); + /// + /// 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 std::set& linkedForms, std::function callback); + private: template DataVec& LinkedFormsForForm(RE::TESForm* form, LinkedForms& linkedForms) const; @@ -81,10 +107,12 @@ namespace LinkedDistribution LinkedForms spells{ RECORD::kSpell }; LinkedForms perks{ RECORD::kPerk }; LinkedForms items{ RECORD::kItem }; + LinkedForms deathItems{ RECORD::kDeathItem }; LinkedForms shouts{ RECORD::kShout }; LinkedForms levSpells{ RECORD::kLevSpell }; LinkedForms packages{ RECORD::kPackage }; LinkedForms outfits{ RECORD::kOutfit }; + LinkedForms sleepOutfits{ RECORD::kSleepOutfit }; LinkedForms keywords{ RECORD::kKeyword }; LinkedForms factions{ RECORD::kFaction }; LinkedForms skins{ RECORD::kSkin }; @@ -93,10 +121,58 @@ namespace LinkedDistribution /// Iterates over each type of LinkedForms and calls a callback with each of them. /// template - void ForEachLinkedForms(Func&& func, const Args&&... args); + void ForEachLinkedForms(Func&& func, Args&&... args); }; #pragma region Implementation + + template + Form* detail::LookupLinkedForm(RE::TESDataHandler* const dataHandler, INI::RawLinkedForm& rawForm) + { + using namespace Forms::Lookup; + + try { + return Forms::detail::get_form(dataHandler, rawForm.formOrEditorID, rawForm.path); + } catch (const UnknownFormIDException& e) { + buffered_logger::error("\t\t[{}] LinkedForm [0x{:X}] ({}) SKIP - formID doesn't exist", e.path, e.formID, e.modName.value_or("")); + } catch (const UnknownPluginException& e) { + buffered_logger::error("\t\t[{}] LinkedForm ({}) SKIP - mod cannot be found", e.path, e.modName); + } catch (const InvalidKeywordException& e) { + buffered_logger::error("\t\t[{}] LinkedForm [0x{:X}] ({}) SKIP - keyword does not have a valid editorID", e.path, e.formID, e.modName.value_or("")); + } catch (const KeywordNotFoundException& e) { + if (e.isDynamic) { + buffered_logger::critical("\t\t[{}] LinkedForm {} FAIL - couldn't create keyword", e.path, e.editorID); + } else { + buffered_logger::critical("\t\t[{}] LinkedForm {} FAIL - couldn't get existing keyword", e.path, e.editorID); + } + } catch (const UnknownEditorIDException& e) { + buffered_logger::error("\t\t[{}] LinkedForm ({}) SKIP - editorID doesn't exist", e.path, e.editorID); + } catch (const MalformedEditorIDException& e) { + buffered_logger::error("\t\t[{}] LinkedForm (\"\") SKIP - malformed editorID", e.path); + } catch (const MismatchingFormTypeException& e) { + std::visit(overload{ + [&](const FormModPair& formMod) { + auto& [formID, modName] = formMod; + buffered_logger::error("\t\t[{}] LinkedForm [0x{:X}] ({}) SKIP - mismatching form type (expected: {}, actual: {})", e.path, *formID, modName.value_or(""), RE::FormTypeToString(e.expectedFformType), RE::FormTypeToString(e.actualFormType)); + }, + [&](std::string editorID) { + buffered_logger::error("\t\t[{}] LinkedForm ({}) SKIP - mismatching form type (expected: {}, actual: {})", e.path, editorID, RE::FormTypeToString(e.expectedFformType), RE::FormTypeToString(e.actualFormType)); + } }, + e.formOrEditorID); + } catch (const InvalidFormTypeException& e) { + std::visit(overload{ + [&](const FormModPair& formMod) { + auto& [formID, modName] = formMod; + buffered_logger::error("\t\t[{}] LinkedForm [0x{:X}] ({}) SKIP - unsupported form type ({})", e.path, *formID, modName.value_or(""), RE::FormTypeToString(e.formType)); + }, + [&](std::string editorID) { + buffered_logger::error("\t\t[{}] LinkedForm ({}) SKIP - unsupported form type ({})", e.path, editorID, RE::FormTypeToString(e.formType)); + } }, + e.formOrEditorID); + } + return nullptr; + } + template DataVec& Manager::LinkedFormsForForm(RE::TESForm* form, LinkedForms& linkedForms) const { @@ -109,7 +185,7 @@ namespace LinkedDistribution } template - void Manager::ForEachLinkedForms(Func&& func, const Args&&... args) + void Manager::ForEachLinkedForms(Func&& func, Args&&... args) { func(keywords, std::forward(args)...); func(spells, std::forward(args)...); @@ -117,21 +193,36 @@ namespace LinkedDistribution func(perks, std::forward(args)...); func(shouts, std::forward(args)...); func(items, std::forward(args)...); + func(deathItems, std::forward(args)...); func(outfits, std::forward(args)...); + func(sleepOutfits, std::forward(args)...); func(factions, std::forward(args)...); func(packages, std::forward(args)...); func(skins, std::forward(args)...); } template - void LinkedForms::Link(Form* form, const FormVec& linkedForms, const RandomCount& count, const Chance& chance, const std::string& path) + void LinkedForms::LookupForms(RE::TESDataHandler* const dataHandler, INI::LinkedFormsVec& rawLinkedForms) + { + for (auto& rawForm : rawLinkedForms) { + auto form = detail::LookupLinkedForm(dataHandler, rawForm); + auto& [formID, parentFormIDs, count, chance, path] = rawForm; + FormVec parentForms{}; + if (Forms::detail::formID_to_form(dataHandler, parentFormIDs.MATCH, parentForms, path, false, false)) { + Link(form, parentForms, count, chance, path); + } + } + } + + template + void LinkedForms::Link(Form* form, const FormVec& linkedForms, const IndexOrCount& idxOrCount, const Chance& chance, const std::string& path) { for (const auto& linkedForm : linkedForms) { if (std::holds_alternative(linkedForm)) { auto& distributableForms = forms[std::get(linkedForm)]; - // Note that we don't use Data.index here, as these linked items doesn't have any leveled filters + // 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. - distributableForms.emplace_back(0, form, count, FilterData({}, {}, {}, {}, chance), path); + distributableForms.emplace_back(0, form, idxOrCount, FilterData({}, {}, {}, {}, chance), path); } } } diff --git a/SPID/include/LookupConfigs.h b/SPID/include/LookupConfigs.h index 07e9181..d2d7013 100644 --- a/SPID/include/LookupConfigs.h +++ b/SPID/include/LookupConfigs.h @@ -4,7 +4,12 @@ namespace RECORD { enum TYPE { - kSpell = 0, + /// + /// A generic form type that requries type inference. + /// + kForm = 0, + + kSpell, kPerk, kItem, kShout, @@ -20,8 +25,47 @@ namespace RECORD kTotal }; - inline constexpr std::array add{ "Spell"sv, "Perk"sv, "Item"sv, "Shout"sv, "LevSpell"sv, "Package"sv, "Outfit"sv, "Keyword"sv, "DeathItem"sv, "Faction"sv, "SleepOutfit"sv, "Skin"sv }; - inline constexpr std::array remove{ "-Spell"sv, "-Perk"sv, "-Item"sv, "-Shout"sv, "-LevSpell"sv, "-Package"sv, "-Outfit"sv, "-Keyword"sv, "-DeathItem"sv, "-Faction"sv, "-SleepOutfit"sv, "-Skin"sv }; + namespace detail + { + inline static constexpr std::array add{ + "Form"sv, + "Spell"sv, + "Perk"sv, + "Item"sv, + "Shout"sv, + "LevSpell"sv, + "Package"sv, + "Outfit"sv, + "Keyword"sv, + "DeathItem"sv, + "Faction"sv, + "SleepOutfit"sv, + "Skin"sv + }; + } + + inline constexpr std::string_view GetTypeName(const TYPE aType) + { + return detail::add.at(aType); + } + + inline constexpr TYPE GetType(const std::string& aType) + { + using namespace detail; + return static_cast(std::distance(add.begin(), std::find(add.begin(), add.end(), aType))); + } + + inline constexpr TYPE GetType(const std::string_view& aType) + { + using namespace detail; + return static_cast(std::distance(add.begin(), std::find(add.begin(), add.end(), aType))); + } + + inline constexpr TYPE GetType(const char* aType) + { + using namespace detail; + return static_cast(std::distance(add.begin(), std::find(add.begin(), add.end(), aType))); + } } namespace INI @@ -63,7 +107,7 @@ namespace INI using DataVec = std::vector; using ExclusiveGroupsVec = std::vector; - inline StringMap configs{}; + inline Map configs{}; /// /// A list of RawExclusiveGroups that will be processed along with configs. diff --git a/SPID/src/Distribute.cpp b/SPID/src/Distribute.cpp index 944fe0d..366cde2 100644 --- a/SPID/src/Distribute.cpp +++ b/SPID/src/Distribute.cpp @@ -158,52 +158,84 @@ namespace Distribute return false; }, accumulatedForms); - } - } - // This only does one-level linking. So that linked entries won't trigger another level of distribution. - void DistributeLinkedEntries(NPCData& npcData, const PCLevelMult::Input& input, const std::set& forms) - { - LinkedDistribution::Manager::GetSingleton()->ForEachLinkedDistributionSet(forms, [&](Forms::DistributionSet& set) { - detail::distribute(npcData, input, set, nullptr); // TODO: Accumulate forms here? - }); + for_each_form( + npcData, forms.deathItems, input, [&](auto* deathItem, IndexOrCount idxOrCount) { + auto count = std::get(idxOrCount); + + detail::add_item(npcData.GetActor(), deathItem, count.GetRandom()); + return true; + }, + accumulatedForms); + } } - void Distribute(NPCData& a_npcData, const PCLevelMult::Input& a_input) + void Distribute(NPCData& npcData, const PCLevelMult::Input& input) { - if (a_input.onlyPlayerLevelEntries && PCLevelMult::Manager::GetSingleton()->HasHitLevelCap(a_input)) { + if (input.onlyPlayerLevelEntries && PCLevelMult::Manager::GetSingleton()->HasHitLevelCap(input)) { return; } - // TODO: Figure out how to distribute only death items perhaps? Forms::DistributionSet entries{ - Forms::spells.GetForms(a_input.onlyPlayerLevelEntries), - Forms::perks.GetForms(a_input.onlyPlayerLevelEntries), - Forms::items.GetForms(a_input.onlyPlayerLevelEntries), - Forms::shouts.GetForms(a_input.onlyPlayerLevelEntries), - Forms::levSpells.GetForms(a_input.onlyPlayerLevelEntries), - Forms::packages.GetForms(a_input.onlyPlayerLevelEntries), - Forms::outfits.GetForms(a_input.onlyPlayerLevelEntries), - Forms::keywords.GetForms(a_input.onlyPlayerLevelEntries), + Forms::spells.GetForms(input.onlyPlayerLevelEntries), + Forms::perks.GetForms(input.onlyPlayerLevelEntries), + Forms::items.GetForms(input.onlyPlayerLevelEntries), + Forms::shouts.GetForms(input.onlyPlayerLevelEntries), + Forms::levSpells.GetForms(input.onlyPlayerLevelEntries), + Forms::packages.GetForms(input.onlyPlayerLevelEntries), + Forms::outfits.GetForms(input.onlyPlayerLevelEntries), + Forms::keywords.GetForms(input.onlyPlayerLevelEntries), Forms::DistributionSet::empty(), // deathItems are only processed on... well, death. - Forms::factions.GetForms(a_input.onlyPlayerLevelEntries), - Forms::sleepOutfits.GetForms(a_input.onlyPlayerLevelEntries), - Forms::skins.GetForms(a_input.onlyPlayerLevelEntries) + Forms::factions.GetForms(input.onlyPlayerLevelEntries), + Forms::sleepOutfits.GetForms(input.onlyPlayerLevelEntries), + Forms::skins.GetForms(input.onlyPlayerLevelEntries) }; std::set distributedForms{}; - detail::distribute(a_npcData, a_input, entries, &distributedForms); + detail::distribute(npcData, input, entries, &distributedForms); // TODO: We can now log per-NPC distributed forms. if (!distributedForms.empty()) { - DistributeLinkedEntries(a_npcData, a_input, distributedForms); + // 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) { + detail::distribute(npcData, input, set, nullptr); // TODO: Accumulate forms here? to log what was distributed. + }); } } - void Distribute(NPCData& a_npcData, bool a_onlyLeveledEntries) + void Distribute(NPCData& npcData, bool onlyLeveledEntries) + { + const auto input = PCLevelMult::Input{ npcData.GetActor(), npcData.GetNPC(), onlyLeveledEntries }; + Distribute(npcData, input); + } + + void DistributeDeathItems(NPCData& npcData, const PCLevelMult::Input& input) { - const auto input = PCLevelMult::Input{ a_npcData.GetActor(), a_npcData.GetNPC(), a_onlyLeveledEntries }; - Distribute(a_npcData, input); + std::set distributedForms{}; + + Forms::DistributionSet entries{ + Forms::DistributionSet::empty(), + Forms::DistributionSet::empty(), + Forms::DistributionSet::empty(), + Forms::DistributionSet::empty(), + Forms::DistributionSet::empty(), + Forms::DistributionSet::empty(), + Forms::DistributionSet::empty(), + Forms::DistributionSet::empty(), + Forms::deathItems.GetForms(input.onlyPlayerLevelEntries), + Forms::DistributionSet::empty(), + Forms::DistributionSet::empty(), + Forms::DistributionSet::empty() + }; + + detail::distribute(npcData, input, entries, &distributedForms); + // TODO: We can now log per-NPC distributed forms. + + if (!distributedForms.empty()) { + LinkedDistribution::Manager::GetSingleton()->ForEachLinkedDeathDistributionSet(distributedForms, [&](Forms::DistributionSet& set) { + detail::distribute(npcData, input, set, nullptr); // TODO: Accumulate forms here? to log what was distributed. + }); + } } } diff --git a/SPID/src/DistributeManager.cpp b/SPID/src/DistributeManager.cpp index bc8dad2..3b9e91f 100644 --- a/SPID/src/DistributeManager.cpp +++ b/SPID/src/DistributeManager.cpp @@ -130,7 +130,7 @@ namespace Distribute ForEachDistributable([&](Distributables& a_distributable) { if (a_distributable && a_distributable.GetType() != RECORD::kDeathItem) { - logger::info("{}", RECORD::add[a_distributable.GetType()]); + logger::info("{}", RECORD::GetTypeName(a_distributable.GetType())); auto& forms = a_distributable.GetForms(); @@ -189,15 +189,10 @@ namespace Distribute::Event const auto actor = a_event->actorDying->As(); const auto npc = actor ? actor->GetActorBase() : nullptr; if (actor && npc) { - const auto npcData = NPCData(actor, npc); + auto npcData = NPCData(actor, npc); const auto input = PCLevelMult::Input{ actor, npc, false }; - for_each_form(npcData, Forms::deathItems.GetForms(input.onlyPlayerLevelEntries), input, [&](auto* deathItem, IndexOrCount idxOrCount) { - auto count = std::get(idxOrCount); - - detail::add_item(actor, deathItem, count.GetRandom()); - return true; - }); + DistributeDeathItems(npcData, input); } } diff --git a/SPID/src/LinkedDistribution.cpp b/SPID/src/LinkedDistribution.cpp index a27cc9e..bcdf193 100644 --- a/SPID/src/LinkedDistribution.cpp +++ b/SPID/src/LinkedDistribution.cpp @@ -1,5 +1,6 @@ #include "LinkedDistribution.h" #include "FormData.h" +#include "LookupConfigs.h" namespace LinkedDistribution { @@ -13,7 +14,7 @@ namespace LinkedDistribution { kForm = 0, kLinkedForms, - kCount, + kIdxOrCount, kChance, // Minimum required sections @@ -22,44 +23,61 @@ namespace LinkedDistribution bool TryParse(const std::string& a_key, const std::string& a_value, const std::string& a_path) { - if (a_key != "LinkedItem") { + if (!a_key.starts_with("Linked"sv)) { return false; } + std::string rawType = a_key.substr(6); + auto type = RECORD::GetType(rawType); + if (type == RECORD::kTotal) { + logger::warn("IGNORED: Invalid Linked Form type: {}"sv, rawType); + return true; + } + const auto sections = string::split(a_value, "|"); const auto size = sections.size(); if (size <= kRequired) { logger::warn("IGNORED: LinkedItem must have a form and at least one Form Filter: {} = {}"sv, a_key, a_value); - return false; + return true; } auto split_IDs = distribution::split_entry(sections[kLinkedForms]); if (split_IDs.empty()) { logger::warn("IGNORED: LinkedItem must have at least one Form Filter : {} = {}"sv, a_key, a_value); - return false; + return true; } - INI::RawLinkedItem item{}; - item.rawForm = distribution::get_record(sections[kForm]); + INI::RawLinkedForm item{}; + item.formOrEditorID = distribution::get_record(sections[kForm]); item.path = a_path; for (auto& IDs : split_IDs) { - item.rawFormFilters.MATCH.push_back(distribution::get_record(IDs)); + item.formIDs.MATCH.push_back(distribution::get_record(IDs)); } - if (kCount < size) { - if (const auto& str = sections[kCount]; 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]); + if (type == RECORD::kPackage) { // reuse item count for package stack index + item.idxOrCount = 0; + } - item.count = RandomCount(minCount, maxCount); - } else { - auto count = string::to_num(str); + if (kIdxOrCount < size) { + if (type == RECORD::kPackage) { + if (const auto& str = sections[kIdxOrCount]; distribution::is_valid_entry(str)) { + item.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]); - item.count = RandomCount(count, count); // create the exact match range. + item.idxOrCount = RandomCount(minCount, maxCount); + } else { + auto count = string::to_num(str); + + item.idxOrCount = RandomCount(count, count); // create the exact match range. + } } } } @@ -70,7 +88,7 @@ namespace LinkedDistribution } } - linkedItems.push_back(item); + linkedForms[type].push_back(item); return true; } @@ -79,65 +97,54 @@ namespace LinkedDistribution #pragma region Lookup - void Manager::LookupLinkedItems(RE::TESDataHandler* const dataHandler, INI::LinkedItemsVec& rawLinkedItems) + void Manager::LookupLinkedForms(RE::TESDataHandler* dataHandler, INI::LinkedFormsConfig& rawLinkedForms) { - using namespace Forms::Lookup; + ForEachLinkedForms([&](LinkedForms& forms) { + forms.LookupForms(dataHandler, rawLinkedForms[forms.GetType()]); + }); - for (auto& [formOrEditorID, linkedIDs, count, chance, path] : rawLinkedItems) { - try { - auto form = detail::get_form(dataHandler, formOrEditorID, path); - FormVec match{}; - if (!detail::formID_to_form(dataHandler, linkedIDs.MATCH, match, path, false, false)) { + auto& genericForms = rawLinkedForms[RECORD::kForm]; + for (auto& rawForm : genericForms) { + if (auto form = detail::LookupLinkedForm(dataHandler, rawForm); form) { + auto& [formID, parentFormIDs, idxOrCount, chance, path] = rawForm; + FormVec parentForms{}; + if (!Forms::detail::formID_to_form(dataHandler, parentFormIDs.MATCH, parentForms, path, false, false)) { continue; } - // Add to appropriate list. + // Add to appropriate list. (Note that type inferring doesn't recognize SleepOutfit or DeathItems) if (const auto keyword = form->As(); keyword) { - keywords.Link(keyword, match, count, chance, path); + keywords.Link(keyword, parentForms, idxOrCount, chance, path); } else if (const auto spell = form->As(); spell) { - spells.Link(spell, match, count, chance, path); + spells.Link(spell, parentForms, idxOrCount, chance, path); } else if (const auto perk = form->As(); perk) { - perks.Link(perk, match, count, chance, path); + perks.Link(perk, parentForms, idxOrCount, chance, path); } else if (const auto shout = form->As(); shout) { - shouts.Link(shout, match, count, chance, path); + shouts.Link(shout, parentForms, idxOrCount, chance, path); } else if (const auto item = form->As(); item) { - items.Link(item, match, count, chance, path); + items.Link(item, parentForms, idxOrCount, chance, path); } else if (const auto outfit = form->As(); outfit) { - outfits.Link(outfit, match, count, chance, path); + outfits.Link(outfit, parentForms, idxOrCount, chance, path); } else if (const auto faction = form->As(); faction) { - factions.Link(faction, match, count, chance, path); + factions.Link(faction, parentForms, idxOrCount, chance, path); } else if (const auto skin = form->As(); skin) { - skins.Link(skin, match, count, chance, path); - } else if (const auto package = form->As(); package) { - auto type = package->GetFormType(); - if (type == RE::FormType::Package || type == RE::FormType::FormList) - packages.Link(package, match, count, chance, path); - } - } catch (const UnknownFormIDException& e) { - buffered_logger::error("\t\t[{}] LinkedItem [0x{:X}] ({}) SKIP - formID doesn't exist", e.path, e.formID, e.modName.value_or("")); - } catch (const UnknownPluginException& e) { - buffered_logger::error("\t\t[{}] LinkedItem ({}) SKIP - mod cannot be found", e.path, e.modName); - } catch (const InvalidKeywordException& e) { - buffered_logger::error("\t\t[{}] LinkedItem [0x{:X}] ({}) SKIP - keyword does not have a valid editorID", e.path, e.formID, e.modName.value_or("")); - } catch (const KeywordNotFoundException& e) { - if (e.isDynamic) { - buffered_logger::critical("\t\t[{}] LinkedItem {} FAIL - couldn't create keyword", e.path, e.editorID); + skins.Link(skin, parentForms, idxOrCount, chance, path); } else { - buffered_logger::critical("\t\t[{}] LinkedItem {} FAIL - couldn't get existing keyword", e.path, e.editorID); + auto type = form->GetFormType(); + if (type == RE::FormType::Package || type == RE::FormType::FormList) { + // With generic Form entries we default to RandomCount, so we need to properly convert it to Index if it turned out to be a package. + Index packageIndex = 1; + if (std::holds_alternative(idxOrCount)) { + auto& count = std::get(idxOrCount); + if (!count.IsExact()) { + logger::warn("Inferred Form is a Package, but specifies a random count instead of index. Min value ({}) of the range will be used as an index.", count.min); + } + packageIndex = count.min; + } else { + packageIndex = std::get(idxOrCount); + } + packages.Link(form, parentForms, packageIndex, chance, path); + } } - } catch (const UnknownEditorIDException& e) { - buffered_logger::error("\t\t[{}] LinkedItem ({}) SKIP - editorID doesn't exist", e.path, e.editorID); - } catch (const MalformedEditorIDException& e) { - buffered_logger::error("\t\t[{}] LinkedItem (\"\") SKIP - malformed editorID", e.path); - } catch (const InvalidFormTypeException& e) { - std::visit(overload{ - [&](const FormModPair& formMod) { - auto& [formID, modName] = formMod; - buffered_logger::error("\t\t[{}] LinkedItem [0x{:X}] ({}) SKIP - unsupported form type ({})", e.path, *formID, modName.value_or(""), RE::FormTypeToString(e.formType)); - }, - [&](std::string editorID) { - buffered_logger::error("\t\t[{}] LinkedItem ({}) SKIP - unsupported form type ({})", e.path, editorID, RE::FormTypeToString(e.formType)); - } }, - e.formOrEditorID); } } @@ -147,17 +154,17 @@ namespace LinkedDistribution }); // Clear INI once lookup is done - rawLinkedItems.clear(); + rawLinkedForms.clear(); // Clear logger's buffer to free some memory :) buffered_logger::clear(); } - void Manager::LogLinkedItemsLookup() + void Manager::LogLinkedFormsLookup() { logger::info("{:*^50}", "LINKED ITEMS"); - ForEachLinkedForms([](const LinkedForms& linkedForms) { + ForEachLinkedForms([](LinkedForms& linkedForms) { if (linkedForms.GetForms().empty()) { return; } @@ -174,18 +181,18 @@ namespace LinkedDistribution } } - const auto& recordName = RECORD::add[linkedForms.GetType()]; + const auto& recordName = RECORD::GetTypeName(linkedForms.GetType()); logger::info("Linked {}s: ", recordName); - for (const auto& [form, linkedItems] : map) { + for (const auto& [form, linkedForms] : map) { logger::info("\t{}", describe(form)); - const auto lastItemIndex = linkedItems.size() - 1; + const auto lastItemIndex = linkedForms.size() - 1; for (int i = 0; i < lastItemIndex; ++i) { - const auto& linkedItem = linkedItems[i]; + const auto& linkedItem = linkedForms[i]; logger::info("\t├─── {}", describe(linkedItem)); } - const auto& lastLinkedItem = linkedItems[lastItemIndex]; + const auto& lastLinkedItem = linkedForms[lastItemIndex]; logger::info("\t└─── {}", describe(lastLinkedItem)); } }); @@ -203,6 +210,7 @@ namespace LinkedDistribution auto& linkedOutfits = LinkedFormsForForm(form, outfits); auto& linkedKeywords = LinkedFormsForForm(form, keywords); auto& linkedFactions = LinkedFormsForForm(form, factions); + auto& linkedSleepOutfits = LinkedFormsForForm(form, sleepOutfits); auto& linkedSkins = LinkedFormsForForm(form, skins); DistributionSet linkedEntries{ @@ -214,9 +222,9 @@ namespace LinkedDistribution linkedPackages, linkedOutfits, linkedKeywords, - DistributionSet::empty(), // deathItems can't be linked at the moment (only makes sense on death) + DistributionSet::empty(), // deathItems are distributed only on death :) as such, linked items are also distributed only on death. linkedFactions, - DistributionSet::empty(), // sleeping outfits are not supported for now due to lack of support in config's syntax. + linkedSleepOutfits, linkedSkins }; @@ -228,5 +236,33 @@ namespace LinkedDistribution } } + void Manager::ForEachLinkedDeathDistributionSet(const std::set& targetForms, std::function performDistribution) + { + for (const auto form : targetForms) { + auto& linkedDeathItems = LinkedFormsForForm(form, deathItems); + + DistributionSet linkedEntries{ + DistributionSet::empty(), + DistributionSet::empty(), + DistributionSet::empty(), + DistributionSet::empty(), + DistributionSet::empty(), + DistributionSet::empty(), + DistributionSet::empty(), + DistributionSet::empty(), + linkedDeathItems, + DistributionSet::empty(), + DistributionSet::empty(), + DistributionSet::empty() + }; + + if (linkedEntries.IsEmpty()) { + continue; + } + + performDistribution(linkedEntries); + } + } + #pragma endregion } diff --git a/SPID/src/LookupConfigs.cpp b/SPID/src/LookupConfigs.cpp index 0c7f572..a6539ae 100644 --- a/SPID/src/LookupConfigs.cpp +++ b/SPID/src/LookupConfigs.cpp @@ -84,7 +84,7 @@ namespace INI return group; } - std::pair> parse_ini(const std::string& a_key, const std::string& a_value, const std::string& a_path) + std::pair> parse_ini(const RECORD::TYPE& typeHint, const std::string& a_value, const std::string& a_path) { Data data{}; @@ -242,12 +242,12 @@ namespace INI } //ITEMCOUNT/INDEX - if (a_key == "Package") { // reuse item count for package stack index + if (typeHint == RECORD::kPackage) { // reuse item count for package stack index data.idxOrCount = 0; } if (kIdxOrCount < size) { - if (a_key == "Package") { // If it's a package, then we only expect a single number. + if (typeHint == RECORD::kPackage) { // 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); } @@ -326,8 +326,10 @@ namespace INI continue; } - auto [data, sanitized_str] = detail::parse_ini(key.pItem, entry, truncatedPath); - configs[key.pItem].emplace_back(data); + auto type = RECORD::GetType(key.pItem); + auto [data, sanitized_str] = detail::parse_ini(type, entry, truncatedPath); + + configs[type].emplace_back(data); if (sanitized_str) { oldFormatMap.emplace(key, std::make_pair(entry, *sanitized_str)); diff --git a/SPID/src/LookupForms.cpp b/SPID/src/LookupForms.cpp index c15ea1e..bf64d33 100644 --- a/SPID/src/LookupForms.cpp +++ b/SPID/src/LookupForms.cpp @@ -8,17 +8,59 @@ bool LookupDistributables(RE::TESDataHandler* const dataHandler) { using namespace Forms; + ForEachDistributable([&](Distributables& a_distributable) { + const auto& recordName = RECORD::GetTypeName(a_distributable.GetType()); + + a_distributable.LookupForms(dataHandler, recordName, INI::configs[a_distributable.GetType()]); + }); + + auto& genericForms = INI::configs[RECORD::kForm]; + + for (auto& rawForm : genericForms) { + // Add to appropriate list. (Note that type inferring doesn't recognize SleepOutfit or DeathItems) + LookupGenericForm(dataHandler, rawForm, [&](bool isValid, auto form, const auto& idxOrCount, const auto& filters, const auto& path) { + if (const auto keyword = form->As(); keyword) { + keywords.EmplaceForm(isValid, keyword, idxOrCount, filters, path); + } else if (const auto spell = form->As(); spell) { + spells.EmplaceForm(isValid, spell, idxOrCount, filters, path); + } else if (const auto perk = form->As(); perk) { + perks.EmplaceForm(isValid, perk, idxOrCount, filters, path); + } else if (const auto shout = form->As(); shout) { + shouts.EmplaceForm(isValid, shout, idxOrCount, filters, path); + } else if (const auto item = form->As(); item) { + items.EmplaceForm(isValid, item, idxOrCount, filters, path); + } else if (const auto outfit = form->As(); outfit) { + outfits.EmplaceForm(isValid, outfit, idxOrCount, filters, path); + } else if (const auto faction = form->As(); faction) { + factions.EmplaceForm(isValid, faction, idxOrCount, filters, path); + } else if (const auto skin = form->As(); skin) { + skins.EmplaceForm(isValid, skin, idxOrCount, filters, path); + } else { + auto type = form->GetFormType(); + if (type == RE::FormType::Package || type == RE::FormType::FormList) { + // With generic Form entries we default to RandomCount, so we need to properly convert it to Index if it turned out to be a package. + Index packageIndex = 1; + if (std::holds_alternative(idxOrCount)) { + auto& count = std::get(idxOrCount); + if (!count.IsExact()) { + logger::warn("Inferred Form is a Package, but specifies a random count instead of index. Min value ({}) of the range will be used as an index.", count.min); + } + packageIndex = count.min; + } else { + packageIndex = std::get(idxOrCount); + } + packages.EmplaceForm(isValid, form, packageIndex, filters, path); + } + } + }); + } + + Dependencies::ResolveKeywords(); + bool valid = false; ForEachDistributable([&](Distributables& a_distributable) { - const auto& recordName = RECORD::add[a_distributable.GetType()]; - - a_distributable.LookupForms(dataHandler, recordName, INI::configs[recordName]); - if constexpr (std::is_same_v) { - Dependencies::ResolveKeywords(); - } a_distributable.FinishLookupForms(); - if (a_distributable) { valid = true; } @@ -33,15 +75,15 @@ void LogDistributablesLookup() logger::info("{:*^50}", "PROCESSING"); - ForEachDistributable([](const Distributables& a_distributable) { - const auto& recordName = RECORD::add[a_distributable.GetType()]; + ForEachDistributable([](Distributables& a_distributable) { + const auto& recordName = RECORD::GetTypeName(a_distributable.GetType()); - const auto all = INI::configs[recordName].size(); const auto added = a_distributable.GetSize(); + const auto all = a_distributable.GetLookupCount(); // Only log entries that are actually present in INIs. if (all > 0) { - logger::info("Adding {}/{} {}s", added, all, recordName); + logger::info("Registered {}/{} {}s", added, all, recordName); } }); @@ -78,14 +120,14 @@ void LogExclusiveGroupsLookup() } } -void LookupLinkedItems(RE::TESDataHandler* const dataHandler) +void LookupLinkedForms(RE::TESDataHandler* const dataHandler) { - LinkedDistribution::Manager::GetSingleton()->LookupLinkedItems(dataHandler); + LinkedDistribution::Manager::GetSingleton()->LookupLinkedForms(dataHandler); } -void LogLinkedItemsLookup() +void LogLinkedFormsLookup() { - LinkedDistribution::Manager::GetSingleton()->LogLinkedItemsLookup(); + LinkedDistribution::Manager::GetSingleton()->LogLinkedFormsLookup(); } bool Lookup::LookupForms() @@ -104,8 +146,8 @@ bool Lookup::LookupForms() logger::info("Lookup took {}μs / {}ms", timer.duration_μs(), timer.duration_ms()); } - LookupLinkedItems(dataHandler); - LogLinkedItemsLookup(); + LookupLinkedForms(dataHandler); + LogLinkedFormsLookup(); LookupExclusiveGroups(dataHandler); LogExclusiveGroupsLookup();