diff --git a/SPID/include/Defs.h b/SPID/include/Defs.h index f379e20..fd2d450 100644 --- a/SPID/include/Defs.h +++ b/SPID/include/Defs.h @@ -93,6 +93,8 @@ struct Traits std::optional teammate{}; }; +using Path = std::string; + using Index = std::int32_t; using Count = std::int32_t; using RandomCount = Range; diff --git a/SPID/include/Distribute.h b/SPID/include/Distribute.h index f840a3e..f222af3 100644 --- a/SPID/include/Distribute.h +++ b/SPID/include/Distribute.h @@ -70,6 +70,8 @@ namespace Distribute void add_item(RE::Actor* a_actor, RE::TESBoundObject* a_item, std::uint32_t a_itemCount); } + using namespace Forms; + #pragma region Packages, Death Items // old method (distributing one by one) // for now, only packages/death items use this @@ -79,12 +81,12 @@ namespace Distribute Forms::DataVec
& forms, const PCLevelMult::Input& a_input, std::function a_callback, - std::set* accumulatedForms = nullptr) + DistributedForms* accumulatedForms = nullptr) { for (auto& formData : forms) { if (!a_npcData.HasMutuallyExclusiveForm(formData.form) && detail::passed_filters(a_npcData, a_input, formData)) { if (accumulatedForms) { - accumulatedForms->insert(formData.form); + accumulatedForms->insert({ formData.form, formData.path }); } a_callback(formData.form, formData.idxOrCount); ++formData.npcCount; @@ -100,12 +102,12 @@ namespace Distribute Forms::DataVec& forms, const PCLevelMult::Input& a_input, std::function a_callback, - std::set* accumulatedForms = nullptr) + DistributedForms* accumulatedForms = nullptr) { for (auto& formData : forms) { // Vector is reversed in FinishLookupForms if (!a_npcData.HasMutuallyExclusiveForm(formData.form) && detail::passed_filters(a_npcData, a_input, formData) && a_callback(formData.form)) { if (accumulatedForms) { - accumulatedForms->insert(formData.form); + accumulatedForms->insert({ formData.form, formData.path }); } ++formData.npcCount; break; @@ -121,14 +123,14 @@ namespace Distribute const NPCData& a_npcData, Forms::Distributables& a_distributables, std::function a_callback, - std::set* accumulatedForms = nullptr) + DistributedForms* accumulatedForms = nullptr) { auto& vec = a_distributables.GetForms(false); for (auto& formData : vec) { // Vector is reversed in FinishLookupForms if (!a_npcData.HasMutuallyExclusiveForm(formData.form) && detail::passed_filters(a_npcData, formData) && a_callback(formData.form)) { if (accumulatedForms) { - accumulatedForms->insert(formData.form); + accumulatedForms->insert({ formData.form, formData.path }); } ++formData.npcCount; break; @@ -144,7 +146,7 @@ namespace Distribute Forms::DataVec& forms, const PCLevelMult::Input& a_input, std::function&)> a_callback, - std::set* accumulatedForms = nullptr) + DistributedForms* accumulatedForms = nullptr) { std::map collectedForms{}; @@ -159,18 +161,21 @@ namespace Distribute leveledItem->CalculateCurrentFormList(level, count, calcedObjects, 0, true); for (auto& calcObj : calcedObjects) { collectedForms[static_cast(calcObj.form)] += calcObj.count; + if (accumulatedForms) { + accumulatedForms->insert({ calcObj.form, formData.path }); + } } } else { collectedForms[formData.form] += count; + if (accumulatedForms) { + accumulatedForms->insert({ formData.form, formData.path }); + } } ++formData.npcCount; } } if (!collectedForms.empty()) { - if (accumulatedForms) { - std::ranges::copy(collectedForms | std::views::keys, std::inserter(*accumulatedForms, accumulatedForms->end())); - } a_callback(collectedForms); } } @@ -185,7 +190,7 @@ namespace Distribute Forms::DataVec& forms, const PCLevelMult::Input& a_input, std::function&)> a_callback, - std::set* accumulatedForms = nullptr) + DistributedForms* accumulatedForms = nullptr) { const auto npc = a_npcData.GetNPC(); @@ -210,6 +215,9 @@ namespace Distribute if (formData.filters.HasLevelFilters()) { collectedLeveledFormIDs.emplace(formID); } + if (accumulatedForms) { + accumulatedForms->insert({ form, formData.path }); + } ++formData.npcCount; } } else { @@ -218,15 +226,15 @@ namespace Distribute if (formData.filters.HasLevelFilters()) { collectedLeveledFormIDs.emplace(formID); } + if (accumulatedForms) { + accumulatedForms->insert({ form, formData.path }); + } ++formData.npcCount; } } } if (!collectedForms.empty()) { - if (accumulatedForms) { - accumulatedForms->insert(collectedForms.begin(), collectedForms.end()); - } a_callback(collectedForms); if (!collectedLeveledFormIDs.empty()) { PCLevelMult::Manager::GetSingleton()->InsertDistributedEntry(a_input, Form::FORMTYPE, collectedLeveledFormIDs); diff --git a/SPID/include/ExclusiveGroups.h b/SPID/include/ExclusiveGroups.h index 4ea6043..0fb37ea 100644 --- a/SPID/include/ExclusiveGroups.h +++ b/SPID/include/ExclusiveGroups.h @@ -3,6 +3,27 @@ namespace ExclusiveGroups { + namespace INI + { + struct RawExclusiveGroup + { + std::string name{}; + + /// Raw filters in RawExclusiveGroup only use NOT and MATCH, there is no meaning for ALL, so it's ignored. + Filters formIDs{}; + Path path{}; + }; + + using ExclusiveGroupsVec = std::vector; + + /// + /// A list of RawExclusiveGroups that will be processed along with configs. + /// + inline ExclusiveGroupsVec exclusiveGroups{}; + + bool TryParse(const std::string& a_key, const std::string& a_value, const Path& a_path); + } + using GroupName = std::string; using LinkedGroups = std::unordered_map>; using Groups = std::unordered_map>; @@ -17,7 +38,9 @@ namespace ExclusiveGroups /// /// A DataHandler that will perform the actual lookup. /// A raw exclusive group entries that should be processed. - void LookupExclusiveGroups(RE::TESDataHandler* const dataHandler, INI::ExclusiveGroupsVec& rawExclusiveGroups); + void LookupExclusiveGroups(RE::TESDataHandler* const dataHandler, INI::ExclusiveGroupsVec& rawExclusiveGroups = INI::exclusiveGroups); + + void LogExclusiveGroupsLookup(); /// /// Gets a set of all forms that are in the same exclusive group as the given form. diff --git a/SPID/include/FormData.h b/SPID/include/FormData.h index 3d46c05..1591bb4 100644 --- a/SPID/include/FormData.h +++ b/SPID/include/FormData.h @@ -10,9 +10,9 @@ namespace Forms struct UnknownPluginException : std::exception { const std::string modName; - const std::string path; + const Path path; - UnknownPluginException(const std::string& modName, const std::string& path) : + UnknownPluginException(const std::string& modName, const Path& path) : modName(modName), path(path) {} @@ -22,9 +22,9 @@ namespace Forms { const RE::FormID formID; const std::optional modName; - const std::string path; + const Path path; - UnknownFormIDException(RE::FormID formID, const std::string& path, std::optional modName = std::nullopt) : + UnknownFormIDException(RE::FormID formID, const Path& path, std::optional modName = std::nullopt) : formID(formID), path(path), modName(modName) @@ -37,13 +37,13 @@ namespace Forms /// struct MismatchingFormTypeException : std::exception { - const RE::FormType expectedFformType; + const RE::FormType expectedFormType; const RE::FormType actualFormType; const FormOrEditorID formOrEditorID; - const std::string path; + const Path path; - MismatchingFormTypeException(RE::FormType expectedFformType, RE::FormType actualFormType, const FormOrEditorID& formOrEditorID, const std::string& path) : - expectedFformType(expectedFformType), + MismatchingFormTypeException(RE::FormType expectedFormType, RE::FormType actualFormType, const FormOrEditorID& formOrEditorID, const Path& path) : + expectedFormType(expectedFormType), actualFormType(actualFormType), formOrEditorID(formOrEditorID), path(path) @@ -54,9 +54,9 @@ namespace Forms { const RE::FormID formID; const std::optional modName; - const std::string path; + const Path path; - InvalidKeywordException(RE::FormID formID, const std::string& path, std::optional modName = std::nullopt) : + InvalidKeywordException(RE::FormID formID, const Path& path, std::optional modName = std::nullopt) : formID(formID), modName(modName), path(path) @@ -67,9 +67,9 @@ namespace Forms { const std::string editorID; const bool isDynamic; - const std::string path; + const Path path; - KeywordNotFoundException(const std::string& editorID, bool isDynamic, const std::string& path) : + KeywordNotFoundException(const std::string& editorID, bool isDynamic, const Path& path) : editorID(editorID), isDynamic(isDynamic), path(path) @@ -79,9 +79,9 @@ namespace Forms struct UnknownEditorIDException : std::exception { const std::string editorID; - const std::string path; + const Path path; - UnknownEditorIDException(const std::string& editorID, const std::string& path) : + UnknownEditorIDException(const std::string& editorID, const Path& path) : editorID(editorID), path(path) {} @@ -94,9 +94,9 @@ namespace Forms { const RE::FormType formType; const FormOrEditorID formOrEditorID; - const std::string path; + const Path path; - InvalidFormTypeException(RE::FormType formType, const FormOrEditorID& formOrEditorID, const std::string& path) : + InvalidFormTypeException(RE::FormType formType, const FormOrEditorID& formOrEditorID, const Path& path) : formType(formType), formOrEditorID(formOrEditorID), path(path) @@ -105,9 +105,9 @@ namespace Forms struct MalformedEditorIDException : std::exception { - const std::string path; + const Path path; - MalformedEditorIDException(const std::string& path) : + MalformedEditorIDException(const Path& path) : path(path) {} }; @@ -140,7 +140,7 @@ namespace Forms } template - std::variant get_form_or_mod(RE::TESDataHandler* const dataHandler, const FormOrEditorID& formOrEditorID, const std::string& path, bool whitelistedOnly = false) + std::variant get_form_or_mod(RE::TESDataHandler* const dataHandler, const FormOrEditorID& formOrEditorID, const Path& path, bool whitelistedOnly = false) { Form* form = nullptr; const RE::TESFile* mod = nullptr; @@ -217,7 +217,7 @@ namespace Forms form = as_form(anyForm); if (!form) { - throw MismatchingFormTypeException(anyForm->GetFormType(), Form::FORMTYPE, FormModPair{ *formID, modName }, path); + throw MismatchingFormTypeException(Form::FORMTYPE, anyForm->GetFormType(), FormModPair{ *formID, modName }, path); } if constexpr (std::is_same_v) { @@ -232,7 +232,6 @@ namespace Forms if (editorID.empty()) { throw MalformedEditorIDException(path); } - // if constexpr (std::is_same_v) { form = find_or_create_keyword(editorID); } else { @@ -268,7 +267,7 @@ namespace Forms return form; } - inline const RE::TESFile* get_file(RE::TESDataHandler* const dataHandler, const FormOrEditorID& formOrEditorID, const std::string& path) + inline const RE::TESFile* get_file(RE::TESDataHandler* const dataHandler, const FormOrEditorID& formOrEditorID, const Path& path) { auto formOrMod = get_form_or_mod(dataHandler, formOrEditorID, path); @@ -280,7 +279,7 @@ namespace Forms } template - Form* get_form(RE::TESDataHandler* const dataHandler, const FormOrEditorID& formOrEditorID, const std::string& path, bool whitelistedOnly = false) + Form* get_form(RE::TESDataHandler* const dataHandler, const FormOrEditorID& formOrEditorID, const Path& path, bool whitelistedOnly = false) { auto formOrMod = get_form_or_mod(dataHandler, formOrEditorID, path, whitelistedOnly); @@ -291,7 +290,7 @@ namespace Forms return nullptr; } - inline bool formID_to_form(RE::TESDataHandler* const a_dataHandler, RawFormVec& a_rawFormVec, FormVec& a_formVec, const std::string& a_path, bool a_all = false, bool whitelistedOnly = true) + inline bool formID_to_form(RE::TESDataHandler* const a_dataHandler, RawFormVec& a_rawFormVec, FormVec& a_formVec, const Path& a_path, bool a_all = false, bool whitelistedOnly = true) { if (a_rawFormVec.empty()) { return true; @@ -322,10 +321,10 @@ namespace Forms 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)); + buffered_logger::error("\t\t[{}] Filter[0x{:X}] ({}) FAIL - mismatching form type (expected: {}, actual: {})", e.path, *formID, modName.value_or(""), RE::FormTypeToString(e.expectedFormType), 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)); + buffered_logger::error("\t\t[{}] Filter ({}) FAIL - mismatching form type (expected: {}, actual: {})", e.path, editorID, RE::FormTypeToString(e.expectedFormType), RE::FormTypeToString(e.actualFormType)); } }, e.formOrEditorID); } catch (const InvalidFormTypeException& e) { @@ -354,7 +353,7 @@ namespace Forms IndexOrCount idxOrCount{ RandomCount(1, 1) }; FilterData filters{}; - std::string path{}; + Path path{}; std::uint32_t npcCount{ 0 }; bool operator==(const Data& a_rhs) const; @@ -363,6 +362,9 @@ namespace Forms template using DataVec = std::vector>; + using DistributedForm = std::pair; + using DistributedForms = std::set; + /// /// A set of distributable forms that should be processed. /// @@ -420,8 +422,8 @@ namespace Forms 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); + void LookupForms(RE::TESDataHandler*, std::string_view a_type, INI::DataVec&); + void EmplaceForm(bool isValid, Form*, const IndexOrCount&, const FilterData&, const Path&); // Init formsWithLevels and formsNoLevels void FinishLookupForms(); @@ -435,7 +437,7 @@ namespace Forms /// This counter is used for logging purposes. std::size_t lookupCount{ 0 }; - void LookupForm(RE::TESDataHandler* a_dataHandler, INI::Data& rawForm); + void LookupForm(RE::TESDataHandler*, INI::Data&); }; inline Distributables spells{ RECORD::kSpell }; @@ -480,7 +482,7 @@ namespace Forms /// 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); + void LookupGenericForm(RE::TESDataHandler* const dataHandler, INI::Data& rawForm, std::function callback); } template @@ -562,7 +564,7 @@ void Forms::Distributables::LookupForms(RE::TESDataHandler* dataHandler, s } template -void Forms::Distributables::EmplaceForm(bool isValid, Form* form, const IndexOrCount& idxOrCount, const FilterData& filters, const std::string& path) +void Forms::Distributables::EmplaceForm(bool isValid, Form* form, const IndexOrCount& idxOrCount, const FilterData& filters, const Path& path) { if (isValid) { forms.emplace_back(forms.size(), form, idxOrCount, filters, path); @@ -594,7 +596,7 @@ void Forms::Distributables::FinishLookupForms() } template -void Forms::LookupGenericForm(RE::TESDataHandler* const dataHandler, INI::Data& rawForm, std::function callback) +void Forms::LookupGenericForm(RE::TESDataHandler* const dataHandler, INI::Data& rawForm, std::function callback) { auto& [formOrEditorID, strings, filterIDs, level, traits, idxOrCount, chance, path] = rawForm; @@ -631,10 +633,10 @@ void Forms::LookupGenericForm(RE::TESDataHandler* const dataHandler, INI::Data& 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)); + buffered_logger::error("\t\t[{}] [0x{:X}] ({}) FAIL - mismatching form type (expected: {}, actual: {})", e.path, *formID, modName.value_or(""), RE::FormTypeToString(e.expectedFormType), 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)); + buffered_logger::error("\t\t[{}] ({}) FAIL - mismatching form type (expected: {}, actual: {})", e.path, editorID, RE::FormTypeToString(e.expectedFormType), RE::FormTypeToString(e.actualFormType)); } }, e.formOrEditorID); } catch (const Lookup::InvalidFormTypeException& e) { @@ -643,3 +645,14 @@ void Forms::LookupGenericForm(RE::TESDataHandler* const dataHandler, INI::Data& // Likewise, we don't expect plugin names in distributable forms. } } + +inline std::ostream& operator<<(std::ostream& os, Forms::DistributedForm form) +{ + os << form.first; + + if (!form.second.empty()) { + os << " @" << form.second; + } + + return os; +} diff --git a/SPID/include/LinkedDistribution.h b/SPID/include/LinkedDistribution.h index 537b1fd..560e7e0 100644 --- a/SPID/include/LinkedDistribution.h +++ b/SPID/include/LinkedDistribution.h @@ -5,19 +5,38 @@ namespace LinkedDistribution { + + /// + /// Scope of a linked form determines which distributions can trigger linked forms. + /// + enum Scope : std::uint8_t + { + /// + /// Local scope links forms only to distributions defined in the same configuration file. + /// + kLocal = 0, + + /// + /// Global scope links forms to all distributions in all loaded configuration files. + /// + kGlobal + }; + namespace INI { struct RawLinkedForm { FormOrEditorID formOrEditorID{}; + Scope scope{ kLocal }; + /// Raw filters in RawLinkedForm only use MATCH, there is no meaning for ALL or NOT, so they are ignored. Filters formIDs{}; IndexOrCount idxOrCount{ RandomCount(1, 1) }; PercentChance chance{ 100 }; - std::string path{}; + Path path{}; }; using LinkedFormsVec = std::vector; @@ -29,7 +48,7 @@ namespace LinkedDistribution /// 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); + bool TryParse(const std::string& key, const std::string& value, const Path&); } using namespace Forms; @@ -51,7 +70,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) @@ -66,7 +85,7 @@ namespace LinkedDistribution RECORD::TYPE type; FormsMap forms{}; - void Link(Form* form, const FormVec& linkedForms, const IndexOrCount& idxOrCount, const PercentChance& chance, const std::string& path); + void Link(Form*, Scope, const FormVec& linkedForms, const IndexOrCount&, const PercentChance&, const Path&); }; class Manager : public ISingleton @@ -88,8 +107,8 @@ namespace LinkedDistribution /// /// 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 std::set& linkedForms, std::function callback); + /// A callback to be called with each DistributionSet. This is supposed to do the actual distribution. + void ForEachLinkedDistributionSet(const DistributedForms& linkedForms, std::function distribute); /// /// Calculates DistributionSet with only DeathItems for each linked form and calls a callback for each of them. @@ -97,12 +116,15 @@ namespace LinkedDistribution /// /// 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); + /// A callback to be called with each DistributionSet. This is supposed to do the actual distribution. + void ForEachLinkedDeathDistributionSet(const DistributedForms& linkedForms, std::function distribute); private: template - DataVec& LinkedFormsForForm(RE::TESForm* form, LinkedForms& linkedForms) const; + DataVec& LinkedFormsForForm(const DistributedForm&, Scope, LinkedForms&) const; + + void ForEachLinkedDistributionSet(const DistributedForms& linkedForms, Scope, std::function distribute); + void ForEachLinkedDeathDistributionSet(const DistributedForms& linkedForms, Scope, std::function distribute); LinkedForms spells{ RECORD::kSpell }; LinkedForms perks{ RECORD::kPerk }; @@ -153,10 +175,10 @@ namespace LinkedDistribution 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)); + buffered_logger::error("\t\t[{}] LinkedForm [0x{:X}] ({}) SKIP - mismatching form type (expected: {}, actual: {})", e.path, *formID, modName.value_or(""), RE::FormTypeToString(e.expectedFormType), 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)); + buffered_logger::error("\t\t[{}] LinkedForm ({}) SKIP - mismatching form type (expected: {}, actual: {})", e.path, editorID, RE::FormTypeToString(e.expectedFormType), RE::FormTypeToString(e.actualFormType)); } }, e.formOrEditorID); } catch (const InvalidFormTypeException& e) { @@ -174,14 +196,16 @@ namespace LinkedDistribution } template - DataVec& Manager::LinkedFormsForForm(RE::TESForm* form, LinkedForms& linkedForms) const + DataVec& Manager::LinkedFormsForForm(const DistributedForm& form, Scope scope, LinkedForms& linkedForms) const { - if (auto it = linkedForms.forms.find(form); it != linkedForms.forms.end()) { - return it->second; - } else { - static DataVec empty{}; - return empty; + if (const auto formsIt = linkedForms.forms.find(scope == kLocal ? form.second : ""); formsIt != linkedForms.forms.end()) { + if (const auto linkedFormsIt = formsIt->second.find(form.first); linkedFormsIt != formsIt->second.end()) { + return linkedFormsIt->second; + } } + + static DataVec empty{}; + return empty; } template @@ -205,21 +229,23 @@ namespace LinkedDistribution 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); + 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, false, false)) { + Link(form, scope, parentForms, count, chance, path); + } } } } template - void LinkedForms::Link(Form* form, const FormVec& linkedForms, const IndexOrCount& idxOrCount, const PercentChance& chance, const std::string& path) + void LinkedForms::Link(Form* form, Scope scope, const FormVec& linkedForms, const IndexOrCount& idxOrCount, const PercentChance& chance, const Path& path) { for (const auto& linkedForm : linkedForms) { if (std::holds_alternative(linkedForm)) { - auto& distributableForms = forms[std::get(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& 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. distributableForms.emplace_back(0, form, idxOrCount, FilterData({}, {}, {}, {}, chance), path); diff --git a/SPID/include/LookupConfigs.h b/SPID/include/LookupConfigs.h index d4c10fe..7431303 100644 --- a/SPID/include/LookupConfigs.h +++ b/SPID/include/LookupConfigs.h @@ -27,7 +27,7 @@ namespace RECORD namespace detail { - inline static constexpr std::array add{ + inline static constexpr std::array names{ "Form"sv, "Spell"sv, "Perk"sv, @@ -46,25 +46,14 @@ namespace RECORD inline constexpr std::string_view GetTypeName(const TYPE aType) { - return detail::add.at(aType); + return detail::names.at(aType); } - inline constexpr TYPE GetType(const std::string& aType) + template + constexpr TYPE GetType(const T& 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))); + return static_cast(std::distance(names.begin(), std::find(names.begin(), names.end(), aType))); } } @@ -95,24 +84,9 @@ namespace INI std::string path{}; }; - struct RawExclusiveGroup - { - std::string name{}; - - /// Raw filters in RawExclusiveGroup only use NOT and MATCH, there is no meaning for ALL, so it's ignored. - Filters rawFormFilters{}; - std::string path{}; - }; - using DataVec = std::vector; - using ExclusiveGroupsVec = std::vector; inline Map configs{}; - /// - /// A list of RawExclusiveGroups that will be processed along with configs. - /// - inline ExclusiveGroupsVec exclusiveGroups{}; - std::pair GetConfigs(); } diff --git a/SPID/src/Distribute.cpp b/SPID/src/Distribute.cpp index 366cde2..6a6c6eb 100644 --- a/SPID/src/Distribute.cpp +++ b/SPID/src/Distribute.cpp @@ -21,7 +21,7 @@ namespace Distribute /// Leveling information about NPC that is being processed. /// A set of forms that should be distributed to NPC. /// An optional pointer to a set that will accumulate all distributed forms. - void distribute(NPCData& npcData, const PCLevelMult::Input& input, Forms::DistributionSet& forms, std::set* accumulatedForms) + void distribute(NPCData& npcData, const PCLevelMult::Input& input, Forms::DistributionSet& forms, DistributedForms* accumulatedForms) { const auto npc = npcData.GetNPC(); @@ -191,7 +191,7 @@ namespace Distribute Forms::skins.GetForms(input.onlyPlayerLevelEntries) }; - std::set distributedForms{}; + DistributedForms distributedForms{}; detail::distribute(npcData, input, entries, &distributedForms); // TODO: We can now log per-NPC distributed forms. @@ -212,7 +212,7 @@ namespace Distribute void DistributeDeathItems(NPCData& npcData, const PCLevelMult::Input& input) { - std::set distributedForms{}; + DistributedForms distributedForms{}; Forms::DistributionSet entries{ Forms::DistributionSet::empty(), diff --git a/SPID/src/ExclusiveGroups.cpp b/SPID/src/ExclusiveGroups.cpp index 607c3c0..277b46e 100644 --- a/SPID/src/ExclusiveGroups.cpp +++ b/SPID/src/ExclusiveGroups.cpp @@ -1,59 +1,117 @@ #include "ExclusiveGroups.h" #include "FormData.h" -void ExclusiveGroups::Manager::LookupExclusiveGroups(RE::TESDataHandler* const dataHandler, INI::ExclusiveGroupsVec& exclusiveGroups) +namespace ExclusiveGroups { - groups.clear(); - linkedGroups.clear(); - - for (auto& [name, filterIDs, path] : exclusiveGroups) { - auto& forms = groups[name]; - FormVec match{}; - FormVec formsNot{}; - - if (Forms::detail::formID_to_form(dataHandler, filterIDs.MATCH, match, path, false, false) && - Forms::detail::formID_to_form(dataHandler, filterIDs.NOT, formsNot, path, false, false)) { - for (const auto& form : match) { - if (std::holds_alternative(form)) { - forms.insert(std::get(form)); - } + bool INI::TryParse(const std::string& a_key, const std::string& a_value, const Path& a_path) + { + if (a_key != "ExclusiveGroup") { + return false; + } + + const auto sections = string::split(a_value, "|"); + const auto size = sections.size(); + + if (size < 2) { + logger::warn("IGNORED: ExclusiveGroup must have a name and at least one Form Filter: {} = {}"sv, a_key, a_value); + return true; + } + + auto split_IDs = distribution::split_entry(sections[1]); + + if (split_IDs.empty()) { + logger::warn("ExclusiveGroup must have at least one Form Filter : {} = {}"sv, a_key, a_value); + return true; + } + + RawExclusiveGroup group{}; + group.name = sections[0]; + group.path = a_path; + + for (auto& IDs : split_IDs) { + if (IDs.at(0) == '-') { + IDs.erase(0, 1); + group.formIDs.NOT.push_back(distribution::get_record(IDs)); + } else { + group.formIDs.MATCH.push_back(distribution::get_record(IDs)); } + } + + exclusiveGroups.emplace_back(group); + + return true; + } + + void Manager::LookupExclusiveGroups(RE::TESDataHandler* const dataHandler, INI::ExclusiveGroupsVec& exclusiveGroups) + { + groups.clear(); + linkedGroups.clear(); - for (auto& form : formsNot) { - if (std::holds_alternative(form)) { - forms.erase(std::get(form)); + for (auto& [name, filterIDs, path] : exclusiveGroups) { + auto& forms = groups[name]; + FormVec match{}; + FormVec formsNot{}; + + if (Forms::detail::formID_to_form(dataHandler, filterIDs.MATCH, match, path, false, false) && + Forms::detail::formID_to_form(dataHandler, filterIDs.NOT, formsNot, path, false, false)) { + for (const auto& form : match) { + if (std::holds_alternative(form)) { + forms.insert(std::get(form)); + } + } + + for (auto& form : formsNot) { + if (std::holds_alternative(form)) { + forms.erase(std::get(form)); + } } } } - } - // Remove empty groups - std::erase_if(groups, [](const auto& pair) { return pair.second.empty(); }); + // Remove empty groups + std::erase_if(groups, [](const auto& pair) { return pair.second.empty(); }); - for (auto& [name, forms] : groups) { - for (auto& form : forms) { - linkedGroups[form].insert(name); + for (auto& [name, forms] : groups) { + for (auto& form : forms) { + linkedGroups[form].insert(name); + } } } -} -std::unordered_set ExclusiveGroups::Manager::MutuallyExclusiveFormsForForm(RE::TESForm* form) const -{ - std::unordered_set forms{}; - if (auto it = linkedGroups.find(form); it != linkedGroups.end()) { - std::ranges::for_each(it->second, [&](const GroupName& name) { - const auto& group = groups.at(name); - forms.insert(group.begin(), group.end()); - }); + void Manager::LogExclusiveGroupsLookup() + { + if (groups.empty()) { + return; + } + + logger::info("{:*^50}", "EXCLUSIVE GROUPS"); + + for (const auto& [group, forms] : groups) { + logger::info("Adding '{}' exclusive group", group); + for (const auto& form : forms) { + logger::info(" {}", describe(form)); + } + } } - // Remove self from the list. - forms.erase(form); + std::unordered_set Manager::MutuallyExclusiveFormsForForm(RE::TESForm* form) const + { + std::unordered_set forms{}; + if (auto it = linkedGroups.find(form); it != linkedGroups.end()) { + std::ranges::for_each(it->second, [&](const GroupName& name) { + const auto& group = groups.at(name); + forms.insert(group.begin(), group.end()); + }); + } - return forms; -} + // Remove self from the list. + forms.erase(form); -const ExclusiveGroups::Groups& ExclusiveGroups::Manager::GetGroups() const -{ - return groups; + return forms; + } + + const Groups& ExclusiveGroups::Manager::GetGroups() const + { + return groups; + } } diff --git a/SPID/src/LinkedDistribution.cpp b/SPID/src/LinkedDistribution.cpp index b0b7886..df6414f 100644 --- a/SPID/src/LinkedDistribution.cpp +++ b/SPID/src/LinkedDistribution.cpp @@ -21,37 +21,48 @@ namespace LinkedDistribution kRequired = kLinkedForms }; - bool TryParse(const std::string& a_key, const std::string& a_value, const std::string& a_path) + bool TryParse(const std::string& originalKey, const std::string& value, const Path& path) { - if (!a_key.starts_with("Linked"sv)) { + std::string key = originalKey; + + Scope scope = kLocal; + + if (key.starts_with("Global"sv)) { + scope = kGlobal; + key.erase(0, 6); + } + + if (!key.starts_with("Linked"sv)) { return false; } - std::string rawType = a_key.substr(6); + std::string rawType = key.substr(6); auto type = RECORD::GetType(rawType); + if (type == RECORD::kTotal) { - logger::warn("IGNORED: Invalid Linked Form type: {}"sv, rawType); + logger::warn("IGNORED: Unsupported Linked Form type: {}"sv, rawType); return true; } - const auto sections = string::split(a_value, "|"); + const auto sections = string::split(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); + logger::warn("IGNORED: LinkedItem must have a form and at least one Form Filter: {} = {}"sv, key, value); 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); + logger::warn("IGNORED: LinkedItem must have at least one Form Filter : {} = {}"sv, key, value); return true; } INI::RawLinkedForm item{}; item.formOrEditorID = distribution::get_record(sections[kForm]); - item.path = a_path; + item.scope = scope; + item.path = path; for (auto& IDs : split_IDs) { item.formIDs.MATCH.push_back(distribution::get_record(IDs)); @@ -106,28 +117,28 @@ namespace LinkedDistribution auto& genericForms = rawLinkedForms[RECORD::kForm]; for (auto& rawForm : genericForms) { if (auto form = detail::LookupLinkedForm(dataHandler, rawForm); form) { - auto& [formID, parentFormIDs, idxOrCount, chance, path] = rawForm; + auto& [formID, scope, 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. (Note that type inferring doesn't recognize SleepOutfit or DeathItems) if (const auto keyword = form->As(); keyword) { - keywords.Link(keyword, parentForms, idxOrCount, chance, path); + keywords.Link(keyword, scope, parentForms, idxOrCount, chance, path); } else if (const auto spell = form->As(); spell) { - spells.Link(spell, parentForms, idxOrCount, chance, path); + spells.Link(spell, scope, parentForms, idxOrCount, chance, path); } else if (const auto perk = form->As(); perk) { - perks.Link(perk, parentForms, idxOrCount, chance, path); + perks.Link(perk, scope, parentForms, idxOrCount, chance, path); } else if (const auto shout = form->As(); shout) { - shouts.Link(shout, parentForms, idxOrCount, chance, path); + shouts.Link(shout, scope, parentForms, idxOrCount, chance, path); } else if (const auto item = form->As(); item) { - items.Link(item, parentForms, idxOrCount, chance, path); + items.Link(item, scope, parentForms, idxOrCount, chance, path); } else if (const auto outfit = form->As(); outfit) { - outfits.Link(outfit, parentForms, idxOrCount, chance, path); + outfits.Link(outfit, scope, parentForms, idxOrCount, chance, path); } else if (const auto faction = form->As(); faction) { - factions.Link(faction, parentForms, idxOrCount, chance, path); + factions.Link(faction, scope, parentForms, idxOrCount, chance, path); } else if (const auto skin = form->As(); skin) { - skins.Link(skin, parentForms, idxOrCount, chance, path); + skins.Link(skin, scope, parentForms, idxOrCount, chance, path); } else { auto type = form->GetFormType(); if (type == RE::FormType::Package || type == RE::FormType::FormList) { @@ -136,13 +147,15 @@ namespace LinkedDistribution 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); + logger::warn("\t[{}] Inferred Form is a Package, but specifies a random count instead of index. Min value ({}) of the range will be used as an index.", path, count.min); } packageIndex = count.min; } else { packageIndex = std::get(idxOrCount); } - packages.Link(form, parentForms, packageIndex, chance, path); + packages.Link(form, scope, parentForms, packageIndex, chance, path); + } else { + logger::warn("\t[{}] Unsupported Form type: {}", path, RE::FormTypeToString(type)); } } } @@ -169,15 +182,19 @@ namespace LinkedDistribution return; } - std::unordered_map> map{}; + std::unordered_map> map{}; // Iterate through the original map for (const auto& pair : linkedForms.GetForms()) { - const auto key = pair.first; - const DataVec& values = pair.second; - - for (const auto& value : values) { - map[value.form].push_back(key); + const auto& path = pair.first; + const auto& formsMap = pair.second; + + for (const auto& pair : formsMap) { + const auto key = pair.first; + const auto& values = pair.second; + for (const auto& value : values) { + map[value.form].emplace_back(key, path); + } } } @@ -197,21 +214,23 @@ namespace LinkedDistribution } }); } +#pragma endregion - void Manager::ForEachLinkedDistributionSet(const std::set& targetForms, std::function performDistribution) +#pragma region Distribution + void Manager::ForEachLinkedDistributionSet(const DistributedForms& targetForms, Scope scope, std::function performDistribution) { - for (const auto form : targetForms) { - auto& linkedSpells = LinkedFormsForForm(form, spells); - auto& linkedPerks = LinkedFormsForForm(form, perks); - auto& linkedItems = LinkedFormsForForm(form, items); - auto& linkedShouts = LinkedFormsForForm(form, shouts); - auto& linkedLevSpells = LinkedFormsForForm(form, levSpells); - auto& linkedPackages = LinkedFormsForForm(form, packages); - 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); + 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, @@ -236,10 +255,16 @@ namespace LinkedDistribution } } - void Manager::ForEachLinkedDeathDistributionSet(const std::set& targetForms, std::function performDistribution) + void Manager::ForEachLinkedDistributionSet(const DistributedForms& targetForms, std::function performDistribution) + { + ForEachLinkedDistributionSet(targetForms, Scope::kLocal, performDistribution); + ForEachLinkedDistributionSet(targetForms, Scope::kGlobal, performDistribution); + } + + void Manager::ForEachLinkedDeathDistributionSet(const DistributedForms& targetForms, Scope scope, std::function performDistribution) { - for (const auto form : targetForms) { - auto& linkedDeathItems = LinkedFormsForForm(form, deathItems); + for (const auto& form : targetForms) { + auto& linkedDeathItems = LinkedFormsForForm(form, scope, deathItems); DistributionSet linkedEntries{ DistributionSet::empty(), @@ -264,5 +289,10 @@ namespace LinkedDistribution } } + void Manager::ForEachLinkedDeathDistributionSet(const DistributedForms& targetForms, std::function performDistribution) + { + ForEachLinkedDeathDistributionSet(targetForms, Scope::kLocal, performDistribution); + ForEachLinkedDeathDistributionSet(targetForms, Scope::kGlobal, performDistribution); + } #pragma endregion } diff --git a/SPID/src/LookupConfigs.cpp b/SPID/src/LookupConfigs.cpp index 8d4f869..11d30e6 100644 --- a/SPID/src/LookupConfigs.cpp +++ b/SPID/src/LookupConfigs.cpp @@ -1,4 +1,5 @@ #include "LookupConfigs.h" +#include "ExclusiveGroups.h" #include "LinkedDistribution.h" namespace INI @@ -47,44 +48,7 @@ namespace INI return newValue; } - std::optional parse_exclusive_group(const std::string& a_key, const std::string& a_value, const std::string& a_path) - { - if (a_key != "ExclusiveGroup") { - return std::nullopt; - } - - const auto sections = string::split(a_value, "|"); - const auto size = sections.size(); - - if (size < 2) { - logger::warn("IGNORED: ExclusiveGroup must have a name and at least one Form Filter: {} = {}"sv, a_key, a_value); - return std::nullopt; - } - - auto split_IDs = distribution::split_entry(sections[1]); - - if (split_IDs.empty()) { - logger::warn("ExclusiveGroup must have at least one Form Filter : {} = {}"sv, a_key, a_value); - return std::nullopt; - } - - RawExclusiveGroup group{}; - group.name = sections[0]; - group.path = a_path; - - for (auto& IDs : split_IDs) { - if (IDs.at(0) == '-') { - IDs.erase(0, 1); - group.rawFormFilters.NOT.push_back(distribution::get_record(IDs)); - } else { - group.rawFormFilters.MATCH.push_back(distribution::get_record(IDs)); - } - } - - return group; - } - - std::pair> parse_ini(const RECORD::TYPE& typeHint, const std::string& a_value, const std::string& a_path) + std::pair> parse_ini(const RECORD::TYPE& typeHint, const std::string& a_value, const Path& a_path) { Data data{}; @@ -317,8 +281,7 @@ namespace INI for (auto& [key, entry] : *values) { try { - if (const auto group = detail::parse_exclusive_group(key.pItem, entry, truncatedPath); group) { - exclusiveGroups.emplace_back(*group); + if (ExclusiveGroups::INI::TryParse(key.pItem, entry, truncatedPath)) { continue; } @@ -327,6 +290,10 @@ namespace INI } auto type = RECORD::GetType(key.pItem); + if (type == RECORD::kTotal) { + logger::warn("\t\tUnsupported Form type: {}"sv, key.pItem); + continue; + } auto [data, sanitized_str] = detail::parse_ini(type, entry, truncatedPath); configs[type].emplace_back(data); @@ -335,7 +302,7 @@ namespace INI oldFormatMap.emplace(key, std::make_pair(entry, *sanitized_str)); } } catch (...) { - logger::warn("\t\tFailed to parse entry [{} = {}]", key.pItem, entry); + logger::warn("\t\tFailed to parse entry [{} = {}]"sv, key.pItem, entry); shouldLogErrors = true; } } diff --git a/SPID/src/LookupForms.cpp b/SPID/src/LookupForms.cpp index bf64d33..86bdc49 100644 --- a/SPID/src/LookupForms.cpp +++ b/SPID/src/LookupForms.cpp @@ -43,13 +43,15 @@ bool LookupDistributables(RE::TESDataHandler* const dataHandler) 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); + logger::warn("\t[{}] Inferred Form is a Package, but specifies a random count instead of index. Min value ({}) of the range will be used as an index.", path, count.min); } packageIndex = count.min; } else { packageIndex = std::get(idxOrCount); } packages.EmplaceForm(isValid, form, packageIndex, filters, path); + } else { + logger::warn("\t[{}] Unsupported Form type: {}", path, RE::FormTypeToString(type)); } } }); @@ -99,25 +101,12 @@ void LogDistributablesLookup() // instead of quering data handler for the same raw FormOrEditorID. void LookupExclusiveGroups(RE::TESDataHandler* const dataHandler) { - ExclusiveGroups::Manager::GetSingleton()->LookupExclusiveGroups(dataHandler, INI::exclusiveGroups); + ExclusiveGroups::Manager::GetSingleton()->LookupExclusiveGroups(dataHandler); } void LogExclusiveGroupsLookup() { - if (const auto manager = ExclusiveGroups::Manager::GetSingleton(); manager) { - const auto& groups = manager->GetGroups(); - - if (!groups.empty()) { - logger::info("{:*^50}", "EXCLUSIVE GROUPS"); - - for (const auto& [group, forms] : groups) { - logger::info("Adding '{}' exclusive group", group); - for (const auto& form : forms) { - logger::info(" {}", describe(form)); - } - } - } - } + ExclusiveGroups::Manager::GetSingleton()->LogExclusiveGroupsLookup(); } void LookupLinkedForms(RE::TESDataHandler* const dataHandler)