Skip to content

Commit

Permalink
Simplified outfit management, by not reverting anything.
Browse files Browse the repository at this point in the history
Distribution is persistent between saves within the same game session, so only actual replacements are tracked and saved in a co-save to be able to revert distributed outfits that are no longer valid in current distribution.
  • Loading branch information
adya committed Jul 30, 2024
1 parent 4e19ded commit dc64206
Show file tree
Hide file tree
Showing 3 changed files with 16 additions and 59 deletions.
16 changes: 1 addition & 15 deletions SPID/include/OutfitManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,6 @@ namespace Outfits
/// <returns>Result of the replacement.</returns>
ReplacementResult SetDefaultOutfit(RE::Actor*, RE::BGSOutfit*, bool allowOverwrites);

/// <summary>
/// Indicates that given actor didn't receive any distributed outfit and will be using the original one.
///
/// This method helps distinguish cases when there was no outfit distribution for the actor vs when we're reloading the save and replacements cache was cleared.
/// </summary>
void UseOriginalOutfit(RE::Actor*);

protected:
RE::BSEventNotifyControl ProcessEvent(const RE::TESFormDeleteEvent* a_event, RE::BSTEventSource<RE::TESFormDeleteEvent>*) override;

Expand All @@ -69,11 +62,6 @@ namespace Outfits
original(original), distributed(nullptr) {}
OutfitReplacement(RE::BGSOutfit* original, RE::BGSOutfit* distributed) :
original(original), distributed(distributed) {}

bool UsesOriginalOutfit() const
{
return original && !distributed;
}
};

friend fmt::formatter<Outfits::Manager::OutfitReplacement>;
Expand Down Expand Up @@ -107,9 +95,7 @@ struct fmt::formatter<Outfits::Manager::OutfitReplacement>
template <class FormatContext>
constexpr auto format(const Outfits::Manager::OutfitReplacement& replacement, FormatContext& a_ctx)
{
if (replacement.UsesOriginalOutfit()) {
return fmt::format_to(a_ctx.out(), "♻ {}", *replacement.original);
} else if (replacement.original && replacement.distributed) {
if (replacement.original && replacement.distributed) {
if (reverse) {
return fmt::format_to(a_ctx.out(), "{} 🔙 {}", *replacement.original, *replacement.distributed);
} else {
Expand Down
12 changes: 5 additions & 7 deletions SPID/src/Distribute.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,11 @@ namespace Distribute
},
accumulatedForms);

if (!for_first_form<RE::BGSOutfit>(
npcData, forms.outfits, input, [&](auto* a_outfit) {
return Outfits::Manager::GetSingleton()->SetDefaultOutfit(npcData.GetActor(), a_outfit, allowOverwrites) != Outfits::ReplacementResult::Skipped; // terminate as soon as valid outfit is confirmed.
},
accumulatedForms)) {
Outfits::Manager::GetSingleton()->UseOriginalOutfit(npcData.GetActor());
}
for_first_form<RE::BGSOutfit>(
npcData, forms.outfits, input, [&](auto* a_outfit) {
return Outfits::Manager::GetSingleton()->SetDefaultOutfit(npcData.GetActor(), a_outfit, allowOverwrites) != Outfits::ReplacementResult::Skipped; // terminate as soon as valid outfit is confirmed.
},
accumulatedForms);

for_first_form<RE::BGSOutfit>(
npcData, forms.sleepOutfits, input, [&](auto* a_outfit) {
Expand Down
47 changes: 10 additions & 37 deletions SPID/src/OutfitManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,8 @@ namespace Outfits
return false;
}

if (!id) { // If ID was 0 it means we don't have the outfit stored in this record.
output = nullptr;
return true;
if (!id) {
return false;
}

if (!interface->ResolveFormID(id, id)) {
Expand Down Expand Up @@ -113,7 +112,6 @@ namespace Outfits
serializationInterface->SetUniqueID(serializationKey);
serializationInterface->SetSaveCallback(Save);
serializationInterface->SetLoadCallback(Load);
serializationInterface->SetRevertCallback(Revert);

if (const auto scripts = RE::ScriptEventSourceHolder::GetSingleton()) {
scripts->AddEventSink<RE::TESFormDeleteEvent>(GetSingleton());
Expand Down Expand Up @@ -180,16 +178,6 @@ namespace Outfits
return ReplacementResult::Set;
}

void Manager::UseOriginalOutfit(RE::Actor* actor)
{
if (auto npc = actor->GetActorBase(); npc && npc->defaultOutfit) {
if (replacements.find(actor->formID) != replacements.end()) {
logger::warn("Overwriting replacement for {}", *actor);
}
replacements.try_emplace(actor->formID, npc->defaultOutfit);
}
}

void Manager::Load(SKSE::SerializationInterface* a_interface)
{
logger::info("{:*^30}", "LOADING");
Expand Down Expand Up @@ -230,24 +218,16 @@ namespace Outfits
for (const auto& it : loadedReplacements) {
const auto& actor = it.first;
const auto& replacement = it.second;

if (auto newIt = newReplacements.find(actor->formID); newIt != newReplacements.end()) {
if (newIt->second.UsesOriginalOutfit()) { // If new replacement uses original outfit
if (!replacement.UsesOriginalOutfit() && replacement.distributed == actor->GetActorBase()->defaultOutfit) { // but previous one doesn't and NPC still wears the distributed outfit
if (auto newIt = newReplacements.find(actor->formID); newIt != newReplacements.end()) { // If we have some new replacement for this actor
newIt->second.original = replacement.original; // we want to forward original outfit from the previous replacement to the new one. (so that a chain of outfits like this A->B->C becomes A->C and we'll be able to revert to the very first outfit)
} else if (replacement.distributed == actor->GetActorBase()->defaultOutfit) { // If there is no new replacement, and an actor is currently wearing the same outfit that was distributed to them last time, we want to revert whatever outfit was in previous replacement
#ifndef NDEBUG
logger::info("\tReverting Outfit Replacement for {}", *actor);
logger::info("\t\t{:R}", replacement);
logger::info("\tReverting Outfit Replacement for {}", *actor);
logger::info("\t\t{:R}", replacement);
#endif
if (actor->SetDefaultOutfit(replacement.original, false)) { // Having true here causes infinite loading. It seems that it works either way.
++revertedCount;
}
}
} else { // If new replacement
newIt->second.original = replacement.original; // if there was a previous distribution we want to forward original outfit from there to new distribution.
if (actor->SetDefaultOutfit(replacement.original, false)) { // Having true here causes infinite loading. It seems that it works either way.
++revertedCount;
}

} else { // If there is no new distribution, we want to keep the old one, assuming that whatever outfit is stored in this replacement is what NPC still wears in this save file
newReplacements[actor->formID] = replacement;
}
}

Expand Down Expand Up @@ -275,19 +255,12 @@ namespace Outfits
}
#ifndef NDEBUG
if (const auto actor = RE::TESForm::LookupByID<RE::Actor>(pair.first); actor) {
logger::info("\tSaved Outfit Replacement ({}) for actor {:F}", pair.second, *actor);
logger::info("\tSaved Outfit Replacement ({}) for actor {}", pair.second, *actor);
}
#endif
++savedCount;
}

logger::info("Saved {} names", savedCount);
}

void Manager::Revert(SKSE::SerializationInterface*)
{
logger::info("{:*^30}", "REVERTING");
/*Manager::GetSingleton()->replacements.clear();
logger::info("\tOutfit Replacements have been cleared.");*/
}
}

0 comments on commit dc64206

Please sign in to comment.