Skip to content

Commit

Permalink
Implemented Linked Distribution.
Browse files Browse the repository at this point in the history
Probably 😁 needs testing, and updating parsing and lookup to work with full entry format.
  • Loading branch information
adya committed Mar 19, 2024
1 parent 933ac13 commit 827b34b
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 103 deletions.
35 changes: 23 additions & 12 deletions SPID/include/FormData.h
Original file line number Diff line number Diff line change
Expand Up @@ -324,21 +324,32 @@ namespace Forms
/// 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{};
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>
Expand Down
41 changes: 26 additions & 15 deletions SPID/include/LinkedDistribution.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once
#include "FormData.h"
#include "LookupNPC.h"
#include "PCLevelMultManager.h"

namespace LinkedDistribution
{
Expand Down Expand Up @@ -29,25 +31,13 @@ namespace LinkedDistribution
}
}

template <class Form>
using DataSet = std::set<Forms::Data<Form>>;
using namespace Forms;

template <class T>
using LinkedForms = std::unordered_map<RE::TESForm*, DataSet<T>>;
template<class T>
using LinkedForms = std::unordered_map<RE::TESForm*, DataVec<T>>;

class Manager : public ISingleton<Manager>
{
private:
template <class Form>
const DataSet<Form>& LinkedFormsForForm(const RE::TESForm* form, const LinkedForms<Form>& linkedForms) const
{
if (const auto it = linkedForms.find(form); it != linkedForms.end()) {
return it->second;
} else {
static std::set<RE::TESForm*> empty{};
return empty;
}
}

public:
/// <summary>
Expand All @@ -59,7 +49,28 @@ namespace LinkedDistribution
/// <param name="rawLinkedDistribution">A raw linked item entries that should be processed.</param>
void LookupLinkedItems(RE::TESDataHandler* const dataHandler, INI::LinkedItemsVec& rawLinkedItems);


/// <summary>
/// Calculates DistributionSet for each linked form and calls a callback for each of them.
/// </summary>
/// <param name="linkedForms">A set of forms for which distribution sets should be calculated.
/// This is typically distributed forms accumulated during first distribution pass.</param>
/// <param name="callback">A callback to be called with each DistributionSet. This is supposed to do the actual distribution.</param>
void ForEachLinkedDistributionSet(const std::set<RE::TESForm*>& linkedForms, std::function<void(DistributionSet&)> callback);

private:

template <class Form>
DataVec<Form>& LinkedFormsForForm(RE::TESForm* form, LinkedForms<Form>& linkedForms) const
{
if (auto it = linkedForms.find(form); it != linkedForms.end()) {
return it->second;
} else {
static DataVec<Form> empty{};
return empty;
}
}

LinkedForms<RE::SpellItem> spells{ RECORD::kSpell };
LinkedForms<RE::BGSPerk> perks{ RECORD::kPerk };
LinkedForms<RE::TESBoundObject> items{ RECORD::kItem };
Expand Down
59 changes: 34 additions & 25 deletions SPID/src/Distribute.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "Distribute.h"

#include "DistributeManager.h"
#include "LinkedDistribution.h"

namespace Distribute
{
Expand Down Expand Up @@ -54,24 +55,24 @@ namespace Distribute
}

/// <summary>
/// Performs distribution of all configured forms to NPC described with a_npcData and a_input.
/// Performs distribution of all configured forms to NPC described with npcData and input.
/// </summary>
/// <param name="a_npcData">General information about NPC that is being processed.</param>
/// <param name="a_input">Leveling information about NPC that is being processed.</param>
/// <param name="npcData">General information about NPC that is being processed.</param>
/// <param name="input">Leveling information about NPC that is being processed.</param>
/// <param name="forms">A set of forms that should be distributed to NPC.</param>
/// <param name="accumulatedForms">An optional pointer to a set that will accumulate all distributed forms.</param>
void distribute(NPCData& a_npcData, const PCLevelMult::Input& a_input, Forms::DistributionSet& forms, std::set<RE::TESForm*>* accumulatedForms)
void distribute(NPCData& npcData, const PCLevelMult::Input& input, Forms::DistributionSet& forms, std::set<RE::TESForm*>* accumulatedForms)
{
const auto npc = a_npcData.GetNPC();
const auto npc = npcData.GetNPC();

for_each_form<RE::BGSKeyword>(
a_npcData, forms.keywords, a_input, [&](const std::vector<RE::BGSKeyword*>& a_keywords) {
npcData, forms.keywords, input, [&](const std::vector<RE::BGSKeyword*>& a_keywords) {
npc->AddKeywords(a_keywords);
},
accumulatedForms);

for_each_form<RE::TESFaction>(
a_npcData, forms.factions, a_input, [&](const std::vector<RE::TESFaction*>& a_factions) {
npcData, forms.factions, input, [&](const std::vector<RE::TESFaction*>& a_factions) {
npc->factions.reserve(static_cast<std::uint32_t>(a_factions.size()));
for (auto& faction : a_factions) {
npc->factions.emplace_back(RE::FACTION_RANK{ faction, 1 });
Expand All @@ -80,31 +81,31 @@ namespace Distribute
accumulatedForms);

for_each_form<RE::SpellItem>(
a_npcData, forms.spells, a_input, [&](const std::vector<RE::SpellItem*>& a_spells) {
npcData, forms.spells, input, [&](const std::vector<RE::SpellItem*>& a_spells) {
npc->GetSpellList()->AddSpells(a_spells);
},
accumulatedForms);

for_each_form<RE::TESLevSpell>(
a_npcData, forms.levSpells, a_input, [&](const std::vector<RE::TESLevSpell*>& a_levSpells) {
npcData, forms.levSpells, input, [&](const std::vector<RE::TESLevSpell*>& a_levSpells) {
npc->GetSpellList()->AddLevSpells(a_levSpells);
},
accumulatedForms);

for_each_form<RE::BGSPerk>(
a_npcData, forms.perks, a_input, [&](const std::vector<RE::BGSPerk*>& a_perks) {
npcData, forms.perks, input, [&](const std::vector<RE::BGSPerk*>& a_perks) {
npc->AddPerks(a_perks, 1);
},
accumulatedForms);

for_each_form<RE::TESShout>(
a_npcData, forms.shouts, a_input, [&](const std::vector<RE::TESShout*>& a_shouts) {
npcData, forms.shouts, input, [&](const std::vector<RE::TESShout*>& a_shouts) {
npc->GetSpellList()->AddShouts(a_shouts);
},
accumulatedForms);

for_each_form<RE::TESForm>(
a_npcData, forms.packages, a_input, [&](auto* a_packageOrList, [[maybe_unused]] IndexOrCount a_idx) {
npcData, forms.packages, input, [&](auto* a_packageOrList, [[maybe_unused]] IndexOrCount a_idx) {
auto packageIdx = std::get<Index>(a_idx);

if (a_packageOrList->Is(RE::FormType::Package)) {
Expand Down Expand Up @@ -162,7 +163,7 @@ namespace Distribute
accumulatedForms);

for_each_form<RE::TESObjectARMO>(
a_npcData, forms.skins, a_input, [&](auto* a_skin) {
npcData, forms.skins, input, [&](auto* a_skin) {
if (npc->skin != a_skin) {
npc->skin = a_skin;
return true;
Expand All @@ -172,7 +173,7 @@ namespace Distribute
accumulatedForms);

for_each_form<RE::BGSOutfit>(
a_npcData, forms.sleepOutfits, a_input, [&](auto* a_outfit) {
npcData, forms.sleepOutfits, input, [&](auto* a_outfit) {
if (npc->sleepOutfit != a_outfit) {
npc->sleepOutfit = a_outfit;
return true;
Expand All @@ -181,16 +182,14 @@ namespace Distribute
},
accumulatedForms);
}

}

// This only does one-level linking. So that linked entries won't trigger another level of distribution.
void DistributeLinkedEntries(NPCData& npcData, const PCLevelMult::Input& a_input, const std::set<RE::TESForm*>& forms)
void DistributeLinkedEntries(NPCData& npcData, const PCLevelMult::Input& input, const std::set<RE::TESForm*>& forms)
{
// TODO: Get linked entries and repeat distribution for them.

Forms::DistributionSet entries{};
detail::distribute(npcData, a_input, entries, nullptr);
LinkedDistribution::Manager::GetSingleton()->ForEachLinkedDistributionSet(forms, [&](Forms::DistributionSet& set) {
detail::distribute(npcData, input, set, nullptr);
});
}

void Distribute(NPCData& a_npcData, const PCLevelMult::Input& a_input)
Expand All @@ -202,13 +201,13 @@ namespace Distribute
Forms::DistributionSet entries{
Forms::spells.GetForms(a_input.onlyPlayerLevelEntries),
Forms::perks.GetForms(a_input.onlyPlayerLevelEntries),
{}, // items are processed separately
Forms::DistributionSet::empty<RE::TESBoundObject>(), // items are processed separately
Forms::shouts.GetForms(a_input.onlyPlayerLevelEntries),
Forms::levSpells.GetForms(a_input.onlyPlayerLevelEntries),
Forms::packages.GetForms(a_input.onlyPlayerLevelEntries),
{}, // outfits are processed along with items.
Forms::DistributionSet::empty<RE::BGSOutfit>(), // outfits are processed along with items.
Forms::keywords.GetForms(a_input.onlyPlayerLevelEntries),
{}, // deathItems are only processed on... well, death.
Forms::DistributionSet::empty<RE::TESBoundObject>(), // deathItems are only processed on... well, death.
Forms::factions.GetForms(a_input.onlyPlayerLevelEntries),
Forms::sleepOutfits.GetForms(a_input.onlyPlayerLevelEntries),
Forms::skins.GetForms(a_input.onlyPlayerLevelEntries)
Expand All @@ -233,6 +232,8 @@ namespace Distribute
const auto npc = a_npcData.GetNPC();
const auto actor = a_npcData.GetActor();

std::set<RE::TESForm*> distributedForms{};

for_each_form<RE::TESBoundObject>(a_npcData, Forms::items.GetForms(a_input.onlyPlayerLevelEntries), a_input, [&](std::map<RE::TESBoundObject*, Count>& a_objects, const bool a_hasLvlItem) {
if (npc->AddObjectsToContainer(a_objects, npc)) {
if (a_hasLvlItem) {
Expand All @@ -241,7 +242,8 @@ namespace Distribute
return true;
}
return false;
});
},
&distributedForms);

for_each_form<RE::BGSOutfit>(a_npcData, Forms::outfits.GetForms(a_input.onlyPlayerLevelEntries), a_input, [&](auto* a_outfit) {
if (detail::can_equip_outfit(npc, a_outfit)) {
Expand All @@ -251,7 +253,14 @@ namespace Distribute
return true;
}
return false;
});
},
&distributedForms);

// TODO: We can now log per-NPC distributed forms.

if (!distributedForms.empty()) {
DistributeLinkedEntries(a_npcData, a_input, distributedForms);
}
}

void Distribute(NPCData& a_npcData, bool a_onlyLeveledEntries, bool a_noItemOutfits)
Expand Down
5 changes: 5 additions & 0 deletions SPID/src/FormData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,8 @@ std::size_t Forms::GetTotalLeveledEntries()

return size;
}

bool Forms::DistributionSet::IsEmpty() const
{
return spells.empty() && perks.empty() && items.empty() && shouts.empty() && levSpells.empty() && packages.empty() && outfits.empty() && keywords.empty() && deathItems.empty() && factions.empty() && sleepOutfits.empty() && skins.empty();
}
Loading

0 comments on commit 827b34b

Please sign in to comment.