Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type inferring for Linked Forms and Linking of DeathItems #27

Merged
merged 10 commits into from
Mar 30, 2024
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
Loading