Skip to content

Commit

Permalink
Merge pull request #27 from powerof3/feature/22-allow-specifying-form…
Browse files Browse the repository at this point in the history
…-type-in-linkedf

Type inferring for Linked Forms and Linking of DeathItems
  • Loading branch information
adya authored Mar 30, 2024
2 parents 88e2eba + 7747d4b commit a91a1f0
Show file tree
Hide file tree
Showing 9 changed files with 545 additions and 200 deletions.
6 changes: 4 additions & 2 deletions SPID/include/Distribute.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
187 changes: 144 additions & 43 deletions SPID/include/FormData.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,25 @@ namespace Forms
{}
};

/// <summary>
/// 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.
/// </summary>
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;
Expand Down Expand Up @@ -68,6 +87,9 @@ namespace Forms
{}
};

/// <summary>
/// An exception thrown when actual form's type is not in the whitelist.
/// </summary>
struct InvalidFormTypeException : std::exception
{
const RE::FormType formType;
Expand Down Expand Up @@ -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<Form, RE::BGSKeyword>) {
if (string::is_empty(form->GetFormEditorID())) {
// Keywords with empty EditorIDs cause game to crash.
Expand All @@ -207,8 +236,12 @@ namespace Forms
if constexpr (std::is_same_v<Form, RE::BGSKeyword>) {
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, RE::TESForm>) {
form = find_or_create_keyword(editorID);
} else {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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<Form>& GetForms(bool a_onlyLevelEntries);
DataVec<Form>& 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();
Expand All @@ -384,6 +430,12 @@ namespace Forms
RECORD::TYPE type;
DataVec<Form> forms{};
DataVec<Form> 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<RE::SpellItem> spells{ RECORD::kSpell };
Expand Down Expand Up @@ -418,6 +470,17 @@ namespace Forms
a_func(packages, std::forward<Args>(args)...);
a_func(skins, std::forward<Args>(args)...);
}

/// <summary>
/// Performs lookup for a single entry.
/// It's up to the callee to add the form to the appropriate distributable container.
/// </summary>
/// <typeparam name="Form">Type of the form to lookup. This type can be defaulted to accept any TESForm.</typeparam>
/// <param name="dataHandler"></param>
/// <param name="rawForm">A raw form entry that needs to be looked up.</param>
/// <param name="callback">A callback to be called with validated data after successful lookup.</param>
template <class Form = RE::TESForm*>
void LookupGenericForm(RE::TESDataHandler* const dataHandler, INI::Data& rawForm, std::function<void(bool isValid, Form*, const IndexOrCount&, const FilterData&, const std::string& path)> callback);
}

template <class Form>
Expand Down Expand Up @@ -447,6 +510,12 @@ std::size_t Forms::Distributables<Form>::GetLeveledSize() const
return formsWithLevels.size();
}

template <class Form>
std::size_t Forms::Distributables<Form>::GetLookupCount() const
{
return lookupCount;
}

template <class Form>
RECORD::TYPE Forms::Distributables<Form>::GetType() const
{
Expand All @@ -469,7 +538,15 @@ Forms::DataVec<Form>& Forms::Distributables<Form>::GetForms(bool a_onlyLevelEntr
}

template <class Form>
void Forms::Distributables<Form>::LookupForms(RE::TESDataHandler* a_dataHandler, std::string_view a_type, INI::DataVec& a_INIDataVec)
void Forms::Distributables<Form>::LookupForm(RE::TESDataHandler* dataHandler, INI::Data& rawForm)
{
Forms::LookupGenericForm<Form>(dataHandler, rawForm, [&](bool isValid, Form* form, const auto& idxOrCount, const auto& filters, const auto& path) {
EmplaceForm(isValid, form, idxOrCount, filters, path);
});
}

template <class Form>
void Forms::Distributables<Form>::LookupForms(RE::TESDataHandler* dataHandler, std::string_view a_type, INI::DataVec& a_INIDataVec)
{
if (a_INIDataVec.empty()) {
return;
Expand All @@ -478,46 +555,19 @@ void Forms::Distributables<Form>::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<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 <class Form>
void Forms::Distributables<Form>::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 <class Form>
Expand All @@ -542,3 +592,54 @@ void Forms::Distributables<Form>::FinishLookupForms()
std::back_inserter(formsWithLevels),
[](const auto& formData) { return formData.filters.HasLevelFilters(); });
}

template <class Form>
void Forms::LookupGenericForm(RE::TESDataHandler* const dataHandler, INI::Data& rawForm, std::function<void(bool isValid, Form*, const IndexOrCount&, const FilterData&, const std::string& path)> callback)
{
auto& [formOrEditorID, strings, filterIDs, level, traits, idxOrCount, chance, path] = rawForm;

try {
if (auto form = detail::get_form<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.
}
}
Loading

0 comments on commit a91a1f0

Please sign in to comment.