Skip to content

Commit

Permalink
Added support for linked death items.
Browse files Browse the repository at this point in the history
  • Loading branch information
adya committed Mar 26, 2024
1 parent de12de1 commit 30d8fa7
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 43 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);
}
13 changes: 11 additions & 2 deletions SPID/include/LinkedDistribution.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,15 @@ namespace LinkedDistribution
/// <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);

/// <summary>
/// Calculates DistributionSet with only DeathItems for each linked form and calls a callback for each of them.
/// This method is suitable for distributing items on death.
/// </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 ForEachLinkedDeathDistributionSet(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;
Expand All @@ -103,7 +112,7 @@ namespace LinkedDistribution
LinkedForms<RE::TESLevSpell> levSpells{ RECORD::kLevSpell };
LinkedForms<RE::TESForm> packages{ RECORD::kPackage };
LinkedForms<RE::BGSOutfit> outfits{ RECORD::kOutfit };
LinkedForms<RE::BGSOutfit> sleepingOutfits{ RECORD::kSleepOutfit };
LinkedForms<RE::BGSOutfit> sleepOutfits{ RECORD::kSleepOutfit };
LinkedForms<RE::BGSKeyword> keywords{ RECORD::kKeyword };
LinkedForms<RE::TESFaction> factions{ RECORD::kFaction };
LinkedForms<RE::TESObjectARMO> skins{ RECORD::kSkin };
Expand Down Expand Up @@ -186,7 +195,7 @@ namespace LinkedDistribution
func(items, std::forward<Args>(args)...);
func(deathItems, std::forward<Args>(args)...);
func(outfits, std::forward<Args>(args)...);
func(sleepingOutfits, std::forward<Args>(args)...);
func(sleepOutfits, std::forward<Args>(args)...);
func(factions, std::forward<Args>(args)...);
func(packages, std::forward<Args>(args)...);
func(skins, std::forward<Args>(args)...);
Expand Down
87 changes: 60 additions & 27 deletions SPID/src/Distribute.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,52 +158,85 @@ namespace Distribute
return false;
},
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& input, const std::set<RE::TESForm*>& forms)
{
LinkedDistribution::Manager::GetSingleton()->ForEachLinkedDistributionSet(forms, [&](Forms::DistributionSet& set) {
detail::distribute(npcData, input, set, nullptr); // TODO: Accumulate forms here?
});
for_each_form<RE::TESBoundObject>(
npcData, forms.deathItems, input, [&](auto* deathItem, IndexOrCount idxOrCount) {
auto count = std::get<RandomCount>(idxOrCount);

detail::add_item(npcData.GetActor(), deathItem, count.GetRandom());
return true;
},
accumulatedForms);
}
}

void Distribute(NPCData& a_npcData, const PCLevelMult::Input& a_input)
void Distribute(NPCData& npcData, const PCLevelMult::Input& input)
{
if (a_input.onlyPlayerLevelEntries && PCLevelMult::Manager::GetSingleton()->HasHitLevelCap(a_input)) {
if (input.onlyPlayerLevelEntries && PCLevelMult::Manager::GetSingleton()->HasHitLevelCap(input)) {
return;
}

// TODO: Figure out how to distribute only death items perhaps?
Forms::DistributionSet entries{
Forms::spells.GetForms(a_input.onlyPlayerLevelEntries),
Forms::perks.GetForms(a_input.onlyPlayerLevelEntries),
Forms::items.GetForms(a_input.onlyPlayerLevelEntries),
Forms::shouts.GetForms(a_input.onlyPlayerLevelEntries),
Forms::levSpells.GetForms(a_input.onlyPlayerLevelEntries),
Forms::packages.GetForms(a_input.onlyPlayerLevelEntries),
Forms::outfits.GetForms(a_input.onlyPlayerLevelEntries),
Forms::keywords.GetForms(a_input.onlyPlayerLevelEntries),
Forms::spells.GetForms(input.onlyPlayerLevelEntries),
Forms::perks.GetForms(input.onlyPlayerLevelEntries),
Forms::items.GetForms(input.onlyPlayerLevelEntries),
Forms::shouts.GetForms(input.onlyPlayerLevelEntries),
Forms::levSpells.GetForms(input.onlyPlayerLevelEntries),
Forms::packages.GetForms(input.onlyPlayerLevelEntries),
Forms::outfits.GetForms(input.onlyPlayerLevelEntries),
Forms::keywords.GetForms(input.onlyPlayerLevelEntries),
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)
Forms::factions.GetForms(input.onlyPlayerLevelEntries),
Forms::sleepOutfits.GetForms(input.onlyPlayerLevelEntries),
Forms::skins.GetForms(input.onlyPlayerLevelEntries)
};

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

detail::distribute(a_npcData, a_input, entries, &distributedForms);
detail::distribute(npcData, input, entries, &distributedForms);
// TODO: We can now log per-NPC distributed forms.

if (!distributedForms.empty()) {
DistributeLinkedEntries(a_npcData, a_input, distributedForms);
// This only does one-level linking. So that linked entries won't trigger another level of distribution.
LinkedDistribution::Manager::GetSingleton()->ForEachLinkedDistributionSet(distributedForms, [&](Forms::DistributionSet& set) {
detail::distribute(npcData, input, set, nullptr); // TODO: Accumulate forms here? to log what was distributed.
});
}
}

void Distribute(NPCData& a_npcData, bool a_onlyLeveledEntries)
void Distribute(NPCData& npcData, bool onlyLeveledEntries)
{
const auto input = PCLevelMult::Input{ npcData.GetActor(), npcData.GetNPC(), onlyLeveledEntries };
Distribute(npcData, input);
}

void DistributeDeathItems(NPCData& npcData, const PCLevelMult::Input& input)
{
const auto input = PCLevelMult::Input{ a_npcData.GetActor(), a_npcData.GetNPC(), a_onlyLeveledEntries };
Distribute(a_npcData, input);
std::set<RE::TESForm*> distributedForms{};

Forms::DistributionSet entries{
Forms::DistributionSet::empty<RE::SpellItem>(),
Forms::DistributionSet::empty<RE::BGSPerk>(),
Forms::DistributionSet::empty<RE::TESBoundObject>(),
Forms::DistributionSet::empty<RE::TESShout>(),
Forms::DistributionSet::empty<RE::TESLevSpell>(),
Forms::DistributionSet::empty<RE::TESForm>(),
Forms::DistributionSet::empty<RE::BGSOutfit>(),
Forms::DistributionSet::empty<RE::BGSKeyword>(),
Forms::deathItems.GetForms(input.onlyPlayerLevelEntries),
Forms::DistributionSet::empty<RE::TESFaction>(),
Forms::DistributionSet::empty<RE::BGSOutfit>(),
Forms::DistributionSet::empty<RE::TESObjectARMO>()
};

detail::distribute(npcData, input, entries, &distributedForms);
// TODO: We can now log per-NPC distributed forms.

if (!distributedForms.empty()) {

LinkedDistribution::Manager::GetSingleton()->ForEachLinkedDeathDistributionSet(distributedForms, [&](Forms::DistributionSet& set) {
detail::distribute(npcData, input, set, nullptr); // TODO: Accumulate forms here? to log what was distributed.
});
}
}
}
9 changes: 2 additions & 7 deletions SPID/src/DistributeManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,15 +189,10 @@ namespace Distribute::Event
const auto actor = a_event->actorDying->As<RE::Actor>();
const auto npc = actor ? actor->GetActorBase() : nullptr;
if (actor && npc) {
const auto npcData = NPCData(actor, npc);
auto npcData = NPCData(actor, npc);
const auto input = PCLevelMult::Input{ actor, npc, false };

for_each_form<RE::TESBoundObject>(npcData, Forms::deathItems.GetForms(input.onlyPlayerLevelEntries), input, [&](auto* deathItem, IndexOrCount idxOrCount) {
auto count = std::get<RandomCount>(idxOrCount);

detail::add_item(actor, deathItem, count.GetRandom());
return true;
});
DistributeDeathItems(npcData, input);
}
}

Expand Down
39 changes: 34 additions & 5 deletions SPID/src/LinkedDistribution.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ namespace LinkedDistribution
if (!Forms::detail::formID_to_form(dataHandler, parentFormIDs.MATCH, parentForms, path, false, false)) {
continue;
}
// Add to appropriate list. (Note that type inferring doesn't recognize SleepingOutfit or DeathItems)
// Add to appropriate list. (Note that type inferring doesn't recognize SleepOutfit or DeathItems)
if (const auto keyword = form->As<RE::BGSKeyword>(); keyword) {
keywords.Link(keyword, parentForms, idxOrCount, chance, path);
} else if (const auto spell = form->As<RE::SpellItem>(); spell) {
Expand All @@ -131,12 +131,12 @@ namespace LinkedDistribution
} else if (const auto package = form->As<RE::TESForm>(); package) {
auto type = package->GetFormType();
if (type == RE::FormType::Package || type == RE::FormType::FormList) {
// During type inferring we'll default to RandomCount, so we need to properly convert it to Index if it's a package.
// With generic Form entries we default to RandomCount, so we need to properly convert it to Index if it turned out to be a package.
Index packageIndex = 1;
if (std::holds_alternative<RandomCount>(idxOrCount)) {
auto& count = std::get<RandomCount>(idxOrCount);
if (!count.IsExact()) {
logger::warn("Inferred Form is a package, but specifies a random count instead of index. Min value ({}) of the range will be used as an index.", count.min);
logger::warn("Inferred Form is a Package, but specifies a random count instead of index. Min value ({}) of the range will be used as an index.", count.min);
}
packageIndex = count.min;
} else {
Expand Down Expand Up @@ -210,6 +210,7 @@ namespace LinkedDistribution
auto& linkedOutfits = LinkedFormsForForm(form, outfits);
auto& linkedKeywords = LinkedFormsForForm(form, keywords);
auto& linkedFactions = LinkedFormsForForm(form, factions);
auto& linkedSleepOutfits = LinkedFormsForForm(form, sleepOutfits);
auto& linkedSkins = LinkedFormsForForm(form, skins);

DistributionSet linkedEntries{
Expand All @@ -221,9 +222,9 @@ namespace LinkedDistribution
linkedPackages,
linkedOutfits,
linkedKeywords,
DistributionSet::empty<RE::TESBoundObject>(), // deathItems can't be linked at the moment (only makes sense on death)
DistributionSet::empty<RE::TESBoundObject>(), // deathItems are distributed only on death :) as such, linked items are also distributed only on death.
linkedFactions,
DistributionSet::empty<RE::BGSOutfit>(), // sleeping outfits are not supported for now due to lack of support in config's syntax.
linkedSleepOutfits,
linkedSkins
};

Expand All @@ -235,5 +236,33 @@ namespace LinkedDistribution
}
}

void Manager::ForEachLinkedDeathDistributionSet(const std::set<RE::TESForm*>& targetForms, std::function<void(DistributionSet&)> performDistribution)
{
for (const auto form : targetForms) {
auto& linkedDeathItems = LinkedFormsForForm(form, deathItems);

DistributionSet linkedEntries{
DistributionSet::empty<RE::SpellItem>(),
DistributionSet::empty<RE::BGSPerk>(),
DistributionSet::empty<RE::TESBoundObject>(),
DistributionSet::empty<RE::TESShout>(),
DistributionSet::empty<RE::TESLevSpell>(),
DistributionSet::empty<RE::TESForm>(),
DistributionSet::empty<RE::BGSOutfit>(),
DistributionSet::empty<RE::BGSKeyword>(),
linkedDeathItems,
DistributionSet::empty<RE::TESFaction>(),
DistributionSet::empty<RE::BGSOutfit>(),
DistributionSet::empty<RE::TESObjectARMO>()
};

if (linkedEntries.IsEmpty()) {
continue;
}

performDistribution(linkedEntries);
}
}

#pragma endregion
}

0 comments on commit 30d8fa7

Please sign in to comment.