diff --git a/SPID/cmake/headerlist.cmake b/SPID/cmake/headerlist.cmake index 9d8fa86..e233a89 100644 --- a/SPID/cmake/headerlist.cmake +++ b/SPID/cmake/headerlist.cmake @@ -8,6 +8,7 @@ set(headers ${headers} include/ExclusiveGroups.h include/FormData.h include/KeywordDependencies.h + include/LinkedDistribution.h include/LogBuffer.h include/LookupConfigs.h include/LookupFilters.h diff --git a/SPID/cmake/sourcelist.cmake b/SPID/cmake/sourcelist.cmake index fc75060..c78269e 100644 --- a/SPID/cmake/sourcelist.cmake +++ b/SPID/cmake/sourcelist.cmake @@ -6,6 +6,7 @@ set(sources ${sources} src/ExclusiveGroups.cpp src/FormData.cpp src/KeywordDependencies.cpp + src/LinkedDistribution.cpp src/LogBuffer.cpp src/LookupConfigs.cpp src/LookupFilters.cpp diff --git a/SPID/include/ExclusiveGroups.h b/SPID/include/ExclusiveGroups.h index 0fc97e8..4ea6043 100644 --- a/SPID/include/ExclusiveGroups.h +++ b/SPID/include/ExclusiveGroups.h @@ -15,15 +15,15 @@ namespace ExclusiveGroups /// /// As a result this method configures Manager with discovered valid exclusive groups. /// - /// - /// A raw exclusive group entries that should be processed/ + /// 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); /// /// Gets a set of all forms that are in the same exclusive group as the given form. /// Note that a form can appear in multiple exclusive groups, all of those groups are returned. /// - /// + /// A form for which mutually exclusive forms will be returned. /// A union of all groups that contain a given form. std::unordered_set MutuallyExclusiveFormsForForm(RE::TESForm* form) const; diff --git a/SPID/include/LinkedDistribution.h b/SPID/include/LinkedDistribution.h new file mode 100644 index 0000000..ba965c2 --- /dev/null +++ b/SPID/include/LinkedDistribution.h @@ -0,0 +1,79 @@ +#pragma once +#include "FormData.h" + +namespace LinkedDistribution +{ + namespace INI { + + struct RawLinkedItem + { + FormOrEditorID rawForm{}; + + /// Raw filters in RawLinkedItem only use MATCH, there is no meaning for ALL or NOT, so they are ignored. + Filters rawFormFilters{}; + + RandomCount count{ 1, 1 }; + Chance chance{ 100 }; + + std::string path{}; + }; + + using LinkedItemsVec = std::vector; + + inline LinkedItemsVec linkedItems{}; + + namespace Parser + { + bool TryParse(const std::string& a_key, const std::string& a_value, const std::string& a_path); + } + } + + template + using DataSet = std::set>; + + template + using LinkedForms = std::unordered_map>; + + class Manager : public ISingleton + { + private: + template + const DataSet
& LinkedFormsForForm(const RE::TESForm* form, const LinkedForms& linkedForms) const + { + if (const auto it = linkedForms.find(form); it != linkedForms.end()) { + return it->second; + } else { + static std::set empty{}; + return empty; + } + } + + public: + /// + /// Does a forms lookup similar to what Filters do. + /// + /// As a result this method configures Manager with discovered valid linked items. + /// + /// 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); + + + + private: + + LinkedForms spells{ RECORD::kSpell }; + LinkedForms perks{ RECORD::kPerk }; + LinkedForms items{ RECORD::kItem }; + LinkedForms shouts{ RECORD::kShout }; + LinkedForms levSpells{ RECORD::kLevSpell }; + LinkedForms packages{ RECORD::kPackage }; + LinkedForms outfits{ RECORD::kOutfit }; + LinkedForms keywords{ RECORD::kKeyword }; + LinkedForms deathItems{ RECORD::kDeathItem }; + LinkedForms factions{ RECORD::kFaction }; + LinkedForms sleepOutfits{ RECORD::kSleepOutfit }; + LinkedForms skins{ RECORD::kSkin }; + + }; +} diff --git a/SPID/src/ExclusiveGroups.cpp b/SPID/src/ExclusiveGroups.cpp index 6db5f4f..607c3c0 100644 --- a/SPID/src/ExclusiveGroups.cpp +++ b/SPID/src/ExclusiveGroups.cpp @@ -28,11 +28,7 @@ void ExclusiveGroups::Manager::LookupExclusiveGroups(RE::TESDataHandler* const d } // Remove empty groups - for (auto& [name, forms] : groups) { - if (forms.empty()) { - groups.erase(name); - } - } + std::erase_if(groups, [](const auto& pair) { return pair.second.empty(); }); for (auto& [name, forms] : groups) { for (auto& form : forms) { diff --git a/SPID/src/LinkedDistribution.cpp b/SPID/src/LinkedDistribution.cpp new file mode 100644 index 0000000..155d936 --- /dev/null +++ b/SPID/src/LinkedDistribution.cpp @@ -0,0 +1,82 @@ +#include "LinkedDistribution.h" +#include "FormData.h" + +#pragma region Parsing +bool LinkedDistribution::INI::Parser::TryParse(const std::string& a_key, const std::string& a_value, const std::string& a_path) +{ + if (a_key != "LinkedItem") { + return false; + } + + const auto sections = string::split(a_value, "|"); + const auto size = sections.size(); + + if (size < 2) { + logger::warn("IGNORED: LinkedItem must have a form and at least one filter name: {} = {}"sv, a_key, a_value); + return false; + } + + 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 false; + } + + LinkedDistribution::INI::RawLinkedItem item{}; + item.rawForm = sections[0]; + item.path = a_path; + + for (auto& IDs : split_IDs) { + item.rawFormFilters.MATCH.push_back(distribution::get_record(IDs)); + } +} +#pragma endregion + +#pragma region Lookup +void LinkedDistribution::Manager::LookupLinkedItems(RE::TESDataHandler* const dataHandler, INI::LinkedItemsVec& rawLinkedItems) +{ + using namespace Forms; + + // TODO: Figure out templates here. + + + for (auto& [rawForm, filterIDs, count, chance, path] : rawLinkedItems) { + try { + if (const auto form = Forms::detail::get_form(dataHandler, rawForm, path); form) { + /*auto& forms = linkedForms[form]; + FormVec match{}; + + if (Forms::detail::formID_to_form(dataHandler, filterIDs.MATCH, match, path, false, false)) { + for (const auto& form : match) { + if (std::holds_alternative(form)) { + forms.insert(std::get(form)); + } + } + }*/ + } + } 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 linked forms. + } + } + + // Remove empty linked forms + //std::erase_if(linkedForms, [](const auto& pair) { return pair.second.empty(); }); +} +#pragma endregion diff --git a/SPID/src/LookupConfigs.cpp b/SPID/src/LookupConfigs.cpp index f3c3a9f..f4b6ef1 100644 --- a/SPID/src/LookupConfigs.cpp +++ b/SPID/src/LookupConfigs.cpp @@ -1,4 +1,5 @@ #include "LookupConfigs.h" +#include "LinkedDistribution.h" namespace INI { @@ -55,13 +56,8 @@ namespace INI const auto sections = string::split(a_value, "|"); const auto size = sections.size(); - if (size == 0) { - logger::warn("IGNORED: ExclusiveGroup must have a name: {} = {}"sv, a_key, a_value); - return std::nullopt; - } - - if (size == 1) { - logger::warn("IGNORED: ExclusiveGroup must have at least one filter name: {} = {}"sv, a_key, a_value); + 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; } @@ -326,6 +322,10 @@ namespace INI continue; } + if (LinkedDistribution::INI::Parser::TryParse(key.pItem, entry, truncatedPath)) { + continue; + } + auto [data, sanitized_str] = detail::parse_ini(key.pItem, entry, truncatedPath); configs[key.pItem].emplace_back(data);