Skip to content

Commit

Permalink
Reverted fix for spells not adding to all horkers.
Browse files Browse the repository at this point in the history
Removing SPID_Processed check caused other distributables to "stack" with every load of an actor.

For now only Outfits are allowed to do that.
  • Loading branch information
adya committed Jan 26, 2025
1 parent f49d9b1 commit f5c85e1
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 116 deletions.
229 changes: 115 additions & 114 deletions SPID/src/Distribute.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,145 +12,146 @@ namespace Distribute
const auto npc = npcData.GetNPC();
const auto actor = npcData.GetActor();

for_each_form<RE::BGSKeyword>(
npcData, forms.keywords, input, [&](const std::vector<RE::BGSKeyword*>& a_keywords) {
npc->AddKeywords(a_keywords);
},
accumulatedForms);
// SPID_Processed keyword prevents multiple distirbutions of the forms that do not support it during every ShouldBackgroundClone call on the same actor (when going in and out of a cell)
//
// This workaround can be removed when/if the per-actor item distribution manager is implemented (since it will take care of continuity).
// Only Distributables with support for isFinal trait can be safely distributed multiple times as they will be able "rotate".
// Eventually, this keyword won't be needed once SPID will keep track of all distributions in runtime.
if (!npc->HasKeyword(processed)) {
for_each_form<RE::BGSKeyword>(
npcData, forms.keywords, input, [&](const std::vector<RE::BGSKeyword*>& a_keywords) {
npc->AddKeywords(a_keywords);
},
accumulatedForms);

for_each_form<RE::TESFaction>(
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 });
}
},
accumulatedForms);
for_each_form<RE::TESFaction>(
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 });
}
},
accumulatedForms);

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

// Note: Abilities are persisted, so that once applied they stick on NPCs.
// Maybe one day we should add a system similar to outfits :)
// or at least implement RemoveSpell calls for all previous abilities.
for_each_form<RE::SpellItem>(
npcData, forms.spells, input, [&](const std::vector<RE::SpellItem*>& spells) {
for (auto& spell : spells) {
actor->AddSpell(spell); // Adding spells one by one to actor properly applies them. This solves On Death distribution issue #60
}
},
accumulatedForms);
// Note: Abilities are persisted, so that once applied they stick on NPCs.
// Maybe one day we should add a system similar to outfits :)
// or at least implement RemoveSpell calls for all previous abilities.
for_each_form<RE::SpellItem>(
npcData, forms.spells, input, [&](const std::vector<RE::SpellItem*>& spells) {
for (auto& spell : spells) {
actor->AddSpell(spell); // Adding spells one by one to actor properly applies them. This solves On Death distribution issue #60
}
},
accumulatedForms);

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

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

for_each_form<RE::TESForm>(
npcData, forms.packages, input, [&](auto* a_packageOrList, [[maybe_unused]] IndexOrCount a_idx) {
auto packageIdx = std::get<Index>(a_idx);
for_each_form<RE::TESForm>(
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)) {
auto package = a_packageOrList->As<RE::TESPackage>();
if (a_packageOrList->Is(RE::FormType::Package)) {
auto package = a_packageOrList->As<RE::TESPackage>();

if (packageIdx > 0) {
--packageIdx; //get actual position we want to insert at
}
if (packageIdx > 0) {
--packageIdx; //get actual position we want to insert at
}

auto& packageList = npc->aiPackages.packages;
if (std::ranges::find(packageList, package) == packageList.end()) {
if (packageList.empty() || packageIdx == 0) {
packageList.push_front(package);
} else {
auto idxIt = packageList.begin();
for (idxIt; idxIt != packageList.end(); ++idxIt) {
auto idx = std::distance(packageList.begin(), idxIt);
if (packageIdx == idx) {
break;
auto& packageList = npc->aiPackages.packages;
if (std::ranges::find(packageList, package) == packageList.end()) {
if (packageList.empty() || packageIdx == 0) {
packageList.push_front(package);
} else {
auto idxIt = packageList.begin();
for (idxIt; idxIt != packageList.end(); ++idxIt) {
auto idx = std::distance(packageList.begin(), idxIt);
if (packageIdx == idx) {
break;
}
}
if (idxIt != packageList.end()) {
packageList.insert_after(idxIt, package);
}
}
if (idxIt != packageList.end()) {
packageList.insert_after(idxIt, package);
}
}
} else if (a_packageOrList->Is(RE::FormType::FormList)) {
auto packageList = a_packageOrList->As<RE::BGSListForm>();

switch (packageIdx) {
case 0:
npc->defaultPackList = packageList;
break;
case 1:
npc->spectatorOverRidePackList = packageList;
break;
case 2:
npc->observeCorpseOverRidePackList = packageList;
break;
case 3:
npc->guardWarnOverRidePackList = packageList;
break;
case 4:
npc->enterCombatOverRidePackList = packageList;
break;
default:
break;
}
}
} else if (a_packageOrList->Is(RE::FormType::FormList)) {
auto packageList = a_packageOrList->As<RE::BGSListForm>();

switch (packageIdx) {
case 0:
npc->defaultPackList = packageList;
break;
case 1:
npc->spectatorOverRidePackList = packageList;
break;
case 2:
npc->observeCorpseOverRidePackList = packageList;
break;
case 3:
npc->guardWarnOverRidePackList = packageList;
break;
case 4:
npc->enterCombatOverRidePackList = packageList;
break;
default:
break;
}
}
},
accumulatedForms);

for_first_form<RE::BGSOutfit>(
npcData, forms.outfits, input, [&](auto* outfit, bool isFinal) {
return distributeOutfit(npcData, outfit, isFinal); // terminate as soon as valid outfit is confirmed.
},
accumulatedForms);

for_first_form<RE::BGSOutfit>(
npcData, forms.sleepOutfits, input, [&](auto* a_outfit, bool isFinal) {
if (npc->sleepOutfit != a_outfit) {
npc->sleepOutfit = a_outfit;
return true;
}
return false;
},
accumulatedForms);
},
accumulatedForms);

// This is a quick fix for items being added multiple times during every ShouldBackgroundClone call on the same actor (when going in and out of a cell)
// SPID_Processed keyword does not block the entire distribution, but only extra items.
// All other things are not additive, so they won't stack.
// This workaround can be removed when/if the per-actor item distribution manager is implemented (since it will take care of continuity).
if (!npc->HasKeyword(processed)) {
for_each_form<RE::TESBoundObject>(
npcData, forms.items, input, [&](std::map<RE::TESBoundObject*, Count>& a_objects) {
// TODO: Per-actor item distribution. Would require similar manager as in outfits :) but would be cool, right?
// adding objects to actors directly put them in extra data changes, so these items are baked into the save.
// to mitiage it, we would need
// adding objects to actors directly put them in inventory changes, so these items are baked into the save.
// to mitiage it, we would need to remove such items whenever a new distribution is triggered.
/*for (auto object : a_objects) {
actor->AddObjectToContainer(object.first, nullptr, object.second, actor);
}
return true;*/
return npc->AddObjectsToContainer(a_objects, npc);
},
accumulatedForms);

for_first_form<RE::TESObjectARMO>(
npcData, forms.skins, input, [&](auto* a_skin, bool isFinal) {
if (npc->skin != a_skin) {
npc->skin = a_skin;
return true;
}
return false;
},
accumulatedForms);

for_first_form<RE::BGSOutfit>(
npcData, forms.sleepOutfits, input, [&](auto* a_outfit, bool isFinal) {
if (npc->sleepOutfit != a_outfit) {
npc->sleepOutfit = a_outfit;
return true;
}
return false;
},
accumulatedForms);
}
for_first_form<RE::TESObjectARMO>(
npcData, forms.skins, input, [&](auto* a_skin, bool isFinal) {
if (npc->skin != a_skin) {
npc->skin = a_skin;
return true;
}
return false;
for_first_form<RE::BGSOutfit>(
npcData, forms.outfits, input, [&](auto* outfit, bool isFinal) {
return distributeOutfit(npcData, outfit, isFinal); // terminate as soon as valid outfit is confirmed.
},
accumulatedForms);
}
Expand Down
7 changes: 7 additions & 0 deletions SPID/src/OutfitManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -598,10 +598,17 @@ namespace Outfits
stl::install_hook<Resurrect>();
stl::install_hook<ResetReference>();
stl::install_hook<SetOutfitActor>();
#ifndef NDEBUG
stl::install_hook<EquipObject>();
stl::install_hook<UnequipObject>();
#endif
}
break;
#ifndef NDEBUG
case SKSE::MessagingInterface::kPostPostLoad:
LOG_HEADER("OUTFITS"); // This is where TESNPCs start initializing, so we give it a nice header.
break;
#endif
case SKSE::MessagingInterface::kPreLoadGame:
isLoadingGame = true;
break;
Expand Down
2 changes: 0 additions & 2 deletions SPID/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ void MessageHandler(SKSE::MessagingInterface::Message* a_message)
} else {
logger::info("INFO - MergeMapper not detected");
}

LOG_HEADER("OUTFITS");
}
break;
case SKSE::MessagingInterface::kDataLoaded:
Expand Down

0 comments on commit f5c85e1

Please sign in to comment.