Skip to content

Commit

Permalink
Merge pull request #21 from powerof3/feature/linked-items
Browse files Browse the repository at this point in the history
Linked Items
  • Loading branch information
adya authored Mar 25, 2024
2 parents 7a91d0b + 52ac89c commit 489af26
Show file tree
Hide file tree
Showing 13 changed files with 675 additions and 209 deletions.
1 change: 1 addition & 0 deletions SPID/cmake/headerlist.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions SPID/cmake/sourcelist.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
133 changes: 50 additions & 83 deletions SPID/include/Distribute.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ namespace Distribute
auto result = a_formData.filters.PassedFilters(a_npcData);

if (result != Filter::Result::kPass) {
if (result == Filter::Result::kFailRNG && hasLevelFilters) {
if (hasLevelFilters && result == Filter::Result::kFailRNG) {
pcLevelMultManager->InsertRejectedEntry(a_input, distributedFormID, index);
}
return false;
Expand Down Expand Up @@ -73,79 +73,86 @@ namespace Distribute
bool can_equip_outfit(const RE::TESNPC* a_npc, RE::BGSOutfit* a_outfit);
}

#pragma region Packages, Death Items
// old method (distributing one by one)
// for now, only packages/death items use this
template <class Form>
void for_each_form(
const NPCData& a_npcData,
Forms::Distributables<Form>& a_distributables,
Forms::DataVec<Form>& forms,
const PCLevelMult::Input& a_input,
std::function<bool(Form*, IndexOrCount)> a_callback)
std::function<bool(Form*, IndexOrCount)> a_callback,
std::set<RE::TESForm*>* accumulatedForms = nullptr)
{
auto& vec = a_distributables.GetForms(a_input.onlyPlayerLevelEntries);

for (auto& formData : vec) {
for (auto& formData : forms) {
if (!a_npcData.HasMutuallyExclusiveForm(formData.form) && detail::passed_filters(a_npcData, a_input, formData)) {
if (accumulatedForms) {
accumulatedForms->insert(formData.form);
}
a_callback(formData.form, formData.idxOrCount);
++formData.npcCount;
}
}
}
#pragma endregion

// outfits/sleep outfits
// skins
#pragma region Outfits, Sleep Outfits, Skins
template <class Form>
void for_each_form(
const NPCData& a_npcData,
Forms::Distributables<Form>& a_distributables,
const PCLevelMult::Input& a_input,
std::function<bool(Form*)> a_callback)
const NPCData& a_npcData,
Forms::DataVec<Form>& forms,
const PCLevelMult::Input& a_input,
std::function<bool(Form*)> a_callback,
std::set<RE::TESForm*>* accumulatedForms = nullptr)
{
auto& vec = a_distributables.GetForms(a_input.onlyPlayerLevelEntries);

for (auto& formData : vec) { // Vector is reversed in FinishLookupForms
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);
}
++formData.npcCount;
break;
}
}
}
#pragma endregion

// TODO: Is this unused?
// outfits/sleep outfits
template <class Form>
void for_each_form(
const NPCData& a_npcData,
Forms::Distributables<Form>& a_distributables,
std::function<bool(Form*)> a_callback)
std::function<bool(Form*)> a_callback,
std::set<RE::TESForm*>* 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);
}
++formData.npcCount;
break;
}
}
}

// items
#pragma region Items
// countable items
template <class Form>
void for_each_form(
const NPCData& a_npcData,
Forms::Distributables<Form>& a_distributables,
Forms::DataVec<Form>& forms,
const PCLevelMult::Input& a_input,
std::function<bool(std::map<Form*, Count>&, bool)> a_callback)
std::function<bool(std::map<Form*, Count>&, bool)> a_callback,
std::set<RE::TESForm*>* accumulatedForms = nullptr)
{
auto& vec = a_distributables.GetForms(a_input.onlyPlayerLevelEntries);

if (vec.empty()) {
return;
}

std::map<Form*, Count> collectedForms{};
bool hasLeveledItems = false;

for (auto& formData : vec) {
for (auto& formData : forms) {
if (!a_npcData.HasMutuallyExclusiveForm(formData.form) && detail::passed_filters(a_npcData, a_input, formData)) {
if (formData.form->Is(RE::FormType::LeveledItem)) {
hasLeveledItems = true;
Expand All @@ -156,36 +163,36 @@ namespace Distribute
}

if (!collectedForms.empty()) {
if (accumulatedForms) {
std::ranges::copy(collectedForms | std::views::keys, std::inserter(*accumulatedForms, accumulatedForms->end()));
}
a_callback(collectedForms, hasLeveledItems);
}
}
#pragma endregion

#pragma region Spells, Perks, Shouts, Keywords
// spells, perks, shouts, keywords
// forms that can be added to
template <class Form>
void for_each_form(
NPCData& a_npcData,
Forms::Distributables<Form>& a_distributables,
Forms::DataVec<Form>& forms,
const PCLevelMult::Input& a_input,
std::function<void(const std::vector<Form*>&)> a_callback)
std::function<void(const std::vector<Form*>&)> a_callback,
std::set<RE::TESForm*>* accumulatedForms = nullptr)
{
auto& vec = a_distributables.GetForms(a_input.onlyPlayerLevelEntries);

if (vec.empty()) {
return;
}

const auto npc = a_npcData.GetNPC();

std::vector<Form*> collectedForms{};
Set<RE::FormID> collectedFormIDs{};
Set<RE::FormID> collectedLeveledFormIDs{};

collectedForms.reserve(vec.size());
collectedFormIDs.reserve(vec.size());
collectedLeveledFormIDs.reserve(vec.size());
collectedForms.reserve(forms.size());
collectedFormIDs.reserve(forms.size());
collectedLeveledFormIDs.reserve(forms.size());

for (auto& formData : vec) {
for (auto& formData : forms) {
auto form = formData.form;
auto formID = form->GetFormID();
if (collectedFormIDs.contains(formID)) {
Expand All @@ -212,60 +219,20 @@ namespace Distribute
}

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);
}
}
}

template <class Form>
void for_each_form(
NPCData& a_npcData,
Forms::Distributables<Form>& a_distributables,
std::function<void(const std::vector<Form*>&)> a_callback)
{
const auto& vec = a_distributables.GetForms(false);

if (vec.empty()) {
return;
}

const auto npc = a_npcData.GetNPC();

std::vector<Form*> collectedForms{};
Set<RE::FormID> collectedFormIDs{};

collectedForms.reserve(vec.size());
collectedFormIDs.reserve(vec.size());

for (auto& formData : vec) {
auto form = formData.form;
auto formID = form->GetFormID();
if (collectedFormIDs.contains(formID)) {
continue;
}
if constexpr (std::is_same_v<RE::BGSKeyword, Form>) {
if (!a_npcData.HasMutuallyExclusiveForm(form) && detail::passed_filters(a_npcData, formData) && a_npcData.InsertKeyword(form->GetFormEditorID())) {
collectedForms.emplace_back(form);
collectedFormIDs.emplace(formID);
++formData.npcCount;
}
} else {
if (!a_npcData.HasMutuallyExclusiveForm(form) && detail::passed_filters(a_npcData, formData) && !detail::has_form(npc, form) && collectedFormIDs.emplace(formID).second) {
collectedForms.emplace_back(form);
++formData.npcCount;
}
}
}

if (!collectedForms.empty()) {
a_callback(collectedForms);
}
}
#pragma endregion

void Distribute(NPCData& a_npcData, const PCLevelMult::Input& a_input);
void DistributeItemOutfits(NPCData& a_npcData, const PCLevelMult::Input& a_input);

void Distribute(NPCData& a_npcData, bool a_onlyLeveledEntries, bool a_noItemOutfits = false);

}
6 changes: 3 additions & 3 deletions SPID/include/ExclusiveGroups.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ namespace ExclusiveGroups
///
/// As a result this method configures Manager with discovered valid exclusive groups.
/// </summary>
/// <param name=""></param>
/// <param name="rawExclusiveGroups">A raw exclusive group entries that should be processed/</param>
/// <param name="dataHandler">A DataHandler that will perform the actual lookup.</param>
/// <param name="rawExclusiveGroups">A raw exclusive group entries that should be processed.</param>
void LookupExclusiveGroups(RE::TESDataHandler* const dataHandler, INI::ExclusiveGroupsVec& rawExclusiveGroups);

/// <summary>
/// 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.
/// </summary>
/// <param name="form"></param>
/// <param name="form">A form for which mutually exclusive forms will be returned.</param>
/// <returns>A union of all groups that contain a given form.</returns>
std::unordered_set<RE::TESForm*> MutuallyExclusiveFormsForForm(RE::TESForm* form) const;

Expand Down
46 changes: 42 additions & 4 deletions SPID/include/FormData.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ namespace Forms
}

template <class Form = RE::TESForm>
std::variant<Form*, const RE::TESFile*> get_form_or_mod(RE::TESDataHandler* dataHandler, const FormOrEditorID& formOrEditorID, const std::string& path, bool whitelistedOnly = false)
std::variant<Form*, const RE::TESFile*> get_form_or_mod(RE::TESDataHandler* const dataHandler, const FormOrEditorID& formOrEditorID, const std::string& path, bool whitelistedOnly = false)
{
Form* form = nullptr;
const RE::TESFile* mod = nullptr;
Expand Down Expand Up @@ -235,7 +235,7 @@ namespace Forms
return form;
}

inline const RE::TESFile* get_file(RE::TESDataHandler* dataHandler, const FormOrEditorID& formOrEditorID, const std::string& path)
inline const RE::TESFile* get_file(RE::TESDataHandler* const dataHandler, const FormOrEditorID& formOrEditorID, const std::string& path)
{
auto formOrMod = get_form_or_mod(dataHandler, formOrEditorID, path);

Expand All @@ -247,7 +247,7 @@ namespace Forms
}

template <class Form = RE::TESForm>
Form* get_form(RE::TESDataHandler* dataHandler, const FormOrEditorID& formOrEditorID, const std::string& path, bool whitelistedOnly = false)
Form* get_form(RE::TESDataHandler* const dataHandler, const FormOrEditorID& formOrEditorID, const std::string& path, bool whitelistedOnly = false)
{
auto formOrMod = get_form_or_mod<Form>(dataHandler, formOrEditorID, path, whitelistedOnly);

Expand All @@ -258,7 +258,7 @@ namespace Forms
return nullptr;
}

inline bool formID_to_form(RE::TESDataHandler* 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 std::string& a_path, bool a_all = false, bool whitelistedOnly = true)
{
if (a_rawFormVec.empty()) {
return true;
Expand Down Expand Up @@ -320,6 +320,44 @@ namespace Forms
template <class Form>
using DataVec = std::vector<Data<Form>>;

/// <summary>
/// A set of distributable forms that should be processed.
///
/// DistributionSet is used to conveniently pack all distributable forms into one structure.
/// Note that all entries store references so they are not owned by this structure.
/// If you want to omit certain type of entries, you can use static empty() method to get a reference to an empty container.
/// </summary>
struct DistributionSet
{
DataVec<RE::SpellItem>& spells;
DataVec<RE::BGSPerk>& perks;
DataVec<RE::TESBoundObject>& items;
DataVec<RE::TESShout>& shouts;
DataVec<RE::TESLevSpell>& levSpells;
DataVec<RE::TESForm>& packages;
DataVec<RE::BGSOutfit>& outfits;
DataVec<RE::BGSKeyword>& keywords;
DataVec<RE::TESBoundObject>& deathItems;
DataVec<RE::TESFaction>& factions;
DataVec<RE::BGSOutfit>& sleepOutfits;
DataVec<RE::TESObjectARMO>& skins;

bool IsEmpty() const;

template <typename Form>
static DataVec<Form>& empty()
{
static DataVec<Form> empty{};
return empty;
}
};

/// <summary>
/// A container that holds distributable entries for a single form type.
///
/// Note that this container tracks separately leveled (those using level in their filters) entries.
/// </summary>
/// <typeparam name="Form">Type of the forms to store.</typeparam>
template <class Form>
struct Distributables
{
Expand Down
Loading

0 comments on commit 489af26

Please sign in to comment.