diff --git a/include/ArmorAddonOverrideService.h b/include/ArmorAddonOverrideService.h index f81242f..c83534b 100644 --- a/include/ArmorAddonOverrideService.h +++ b/include/ArmorAddonOverrideService.h @@ -103,7 +103,7 @@ class ArmorAddonOverrideService { bool enabled = true; std::map outfits; // TODO: You probably shouldn't use an Actor pointer to refer to actors. It works for the PlayerCharacter, but likely not for NPCs. - std::map actorOutfitAssignments; + std::map actorOutfitAssignments; // Location-based switching bool locationBasedAutoSwitchEnabled = false; // @@ -117,7 +117,7 @@ class ArmorAddonOverrideService { // void addOutfit(const char* name); // can throw bad_name void addOutfit(const char* name, std::vector armors); // can throw bad_name - Outfit& currentOutfit(RE::Actor* target); + Outfit& currentOutfit(RE::RawActorHandle target); bool hasOutfit(const char* name) const; void deleteOutfit(const char* name); void setFavorite(const char* name, bool favorite); @@ -125,19 +125,19 @@ class ArmorAddonOverrideService { void setOutfitEquipRequired(const char* name, bool requiresEquipped); void modifyOutfit(const char* name, std::vector& add, std::vector& remove, bool createIfMissing = false); // can throw bad_name if (createIfMissing) void renameOutfit(const char* oldName, const char* newName); // throws name_conflict if the new name is already taken; can throw bad_name; throws std::out_of_range if the oldName doesn't exist - void setOutfit(const char* name, RE::Actor* target); - void addActor(RE::Actor* target); - void removeActor(RE::Actor* target); - std::unordered_set listActors(); + void setOutfit(const char* name, RE::RawActorHandle target); + void addActor(RE::RawActorHandle target); + void removeActor(RE::RawActorHandle target); + std::unordered_set listActors(); // void setLocationBasedAutoSwitchEnabled(bool) noexcept; - void setOutfitUsingLocation(LocationType location, RE::Actor* target); - void setLocationOutfit(LocationType location, const char* name, RE::Actor* target); - void unsetLocationOutfit(LocationType location, RE::Actor* target); - std::optional getLocationOutfit(LocationType location, RE::Actor* target); - std::optional checkLocationType(const std::unordered_set& keywords, const WeatherFlags& weather_flags, RE::Actor* target); + void setOutfitUsingLocation(LocationType location, RE::RawActorHandle target); + void setLocationOutfit(LocationType location, const char* name, RE::RawActorHandle target); + void unsetLocationOutfit(LocationType location, RE::RawActorHandle target); + std::optional getLocationOutfit(LocationType location, RE::RawActorHandle target); + std::optional checkLocationType(const std::unordered_set& keywords, const WeatherFlags& weather_flags, RE::RawActorHandle target); // - bool shouldOverride(RE::Actor* target) const noexcept; + bool shouldOverride(RE::RawActorHandle target) const noexcept; void getOutfitNames(std::vector& out, bool favoritesOnly = false) const; void setEnabled(bool) noexcept; // diff --git a/include/SOS_PCH.h b/include/SOS_PCH.h index 1be9d9b..7f04420 100644 --- a/include/SOS_PCH.h +++ b/include/SOS_PCH.h @@ -78,4 +78,8 @@ namespace Plugin { inline constexpr auto NAME = "SkyrimOutfitSystemSE"sv; } +namespace RE { + using RawActorHandle = RE::ActorHandle::native_handle_type; +} + #endif //SKYRIMOUTFITSYSTEMSE_SOS_PCH_H diff --git a/src/ArmorAddonOverrideService.cpp b/src/ArmorAddonOverrideService.cpp index b61d830..518d65d 100644 --- a/src/ArmorAddonOverrideService.cpp +++ b/src/ArmorAddonOverrideService.cpp @@ -217,7 +217,7 @@ void ArmorAddonOverrideService::addOutfit(const char* name, std::vectoractorOutfitAssignments.count(target) == 0) return g_noOutfit; if (this->actorOutfitAssignments.at(target).currentOutfitName == g_noOutfitName) @@ -315,7 +315,7 @@ void ArmorAddonOverrideService::renameOutfit(const char* oldName, const char* ne } } } -void ArmorAddonOverrideService::setOutfit(const char* name, RE::Actor* target) { +void ArmorAddonOverrideService::setOutfit(const char* name, RE::RawActorHandle target) { if (this->actorOutfitAssignments.count(target) == 0) return; if (strcmp(name, g_noOutfitName) == 0) { @@ -333,19 +333,19 @@ void ArmorAddonOverrideService::setOutfit(const char* name, RE::Actor* target) { } } -void ArmorAddonOverrideService::addActor(RE::Actor* target) { +void ArmorAddonOverrideService::addActor(RE::RawActorHandle target) { if (actorOutfitAssignments.count(target) == 0) actorOutfitAssignments.emplace(target, ActorOutfitAssignments()); } -void ArmorAddonOverrideService::removeActor(RE::Actor* target) { - if (target == RE::PlayerCharacter::GetSingleton()) +void ArmorAddonOverrideService::removeActor(RE::RawActorHandle target) { + if (target == RE::PlayerCharacter::GetSingleton()->GetHandle().native_handle()) return; actorOutfitAssignments.erase(target); } -std::unordered_set ArmorAddonOverrideService::listActors() { - std::unordered_set actors; +std::unordered_set ArmorAddonOverrideService::listActors() { + std::unordered_set actors; for (auto& assignment : actorOutfitAssignments) { actors.insert(assignment.first); } @@ -356,7 +356,7 @@ void ArmorAddonOverrideService::setLocationBasedAutoSwitchEnabled(bool newValue) locationBasedAutoSwitchEnabled = newValue; } -void ArmorAddonOverrideService::setOutfitUsingLocation(LocationType location, RE::Actor* target) { +void ArmorAddonOverrideService::setOutfitUsingLocation(LocationType location, RE::RawActorHandle target) { if (this->actorOutfitAssignments.count(target) == 0) return; auto it = this->actorOutfitAssignments.at(target).locationOutfits.find(location); @@ -365,7 +365,7 @@ void ArmorAddonOverrideService::setOutfitUsingLocation(LocationType location, RE } } -void ArmorAddonOverrideService::setLocationOutfit(LocationType location, const char* name, RE::Actor* target) { +void ArmorAddonOverrideService::setLocationOutfit(LocationType location, const char* name, RE::RawActorHandle target) { if (this->actorOutfitAssignments.count(target) == 0) return; if (!std::string(name).empty()) {// Can never set outfit to the "" outfit. Use unsetLocationOutfit instead. @@ -373,13 +373,13 @@ void ArmorAddonOverrideService::setLocationOutfit(LocationType location, const c } } -void ArmorAddonOverrideService::unsetLocationOutfit(LocationType location, RE::Actor* target) { +void ArmorAddonOverrideService::unsetLocationOutfit(LocationType location, RE::RawActorHandle target) { if (this->actorOutfitAssignments.count(target) == 0) return; this->actorOutfitAssignments.at(target).locationOutfits.erase(location); } -std::optional ArmorAddonOverrideService::getLocationOutfit(LocationType location, RE::Actor* target) { +std::optional ArmorAddonOverrideService::getLocationOutfit(LocationType location, RE::RawActorHandle target) { if (this->actorOutfitAssignments.count(target) == 0) return std::optional(); ; @@ -397,7 +397,7 @@ std::optional ArmorAddonOverrideService::getLocationOutfit(Locati std::optional ArmorAddonOverrideService::checkLocationType(const std::unordered_set& keywords, const WeatherFlags& weather_flags, - RE::Actor* target) { + RE::RawActorHandle target) { if (this->actorOutfitAssignments.count(target) == 0) return std::optional(); @@ -421,7 +421,7 @@ std::optional ArmorAddonOverrideService::checkLocationType(const s return std::optional(); } -bool ArmorAddonOverrideService::shouldOverride(RE::Actor* target) const noexcept { +bool ArmorAddonOverrideService::shouldOverride(RE::RawActorHandle target) const noexcept { if (!this->enabled) return false; if (this->actorOutfitAssignments.count(target) == 0) @@ -445,7 +445,7 @@ void ArmorAddonOverrideService::setEnabled(bool flag) noexcept { void ArmorAddonOverrideService::reset() { this->enabled = true; this->actorOutfitAssignments.clear(); - this->actorOutfitAssignments[RE::PlayerCharacter::GetSingleton()] = ActorOutfitAssignments(); + this->actorOutfitAssignments[RE::PlayerCharacter::GetSingleton()->GetHandle().native_handle()] = ActorOutfitAssignments(); this->outfits.clear(); this->locationBasedAutoSwitchEnabled = false; } @@ -479,12 +479,12 @@ void ArmorAddonOverrideService::load_legacy(const SKSE::SerializationInterface* auto& outfit = this->getOrCreateOutfit(name.c_str()); outfit.load_legacy(intfc, version); } - this->setOutfit(selectedOutfitName.c_str(), RE::PlayerCharacter::GetSingleton()); + this->setOutfit(selectedOutfitName.c_str(), RE::PlayerCharacter::GetSingleton()->GetHandle().native_handle()); if (version >= ArmorAddonOverrideService::kSaveVersionV3) { _assertWrite(intfc->ReadRecordData(this->locationBasedAutoSwitchEnabled), "Failed to read the autoswitch enable state."); std::uint32_t autoswitchSize = - static_cast(this->actorOutfitAssignments.at(RE::PlayerCharacter::GetSingleton()) + static_cast(this->actorOutfitAssignments.at(RE::PlayerCharacter::GetSingleton()->GetHandle().native_handle()) .locationOutfits.size()); _assertRead(intfc->ReadRecordData(autoswitchSize), "Failed to read the number of autoswitch slots."); for (std::uint32_t i = 0; i < autoswitchSize; i++) { @@ -504,11 +504,11 @@ void ArmorAddonOverrideService::load_legacy(const SKSE::SerializationInterface* if (locationOutfitNameSize) { _assertRead(intfc->ReadRecordData(locationOutfitName, locationOutfitNameSize), "Failed to read the selected outfit name."); - this->actorOutfitAssignments.at(RE::PlayerCharacter::GetSingleton()).locationOutfits.emplace(autoswitchSlot, locationOutfitName); + this->actorOutfitAssignments.at(RE::PlayerCharacter::GetSingleton()->GetHandle().native_handle()).locationOutfits.emplace(autoswitchSlot, locationOutfitName); } } } else { - this->actorOutfitAssignments.at(RE::PlayerCharacter::GetSingleton()).locationOutfits = + this->actorOutfitAssignments.at(RE::PlayerCharacter::GetSingleton()->GetHandle().native_handle()).locationOutfits = std::map(); } } @@ -517,15 +517,13 @@ void ArmorAddonOverrideService::load(const SKSE::SerializationInterface* intfc, this->reset(); // Extract data from the protobuf struct. this->enabled = data.enabled(); - std::map actorOutfitAssignmentsLocal; + std::map actorOutfitAssignmentsLocal; for (const auto& actorAssn : data.actor_outfit_assignments()) { // Lookup the actor std::uint64_t handle; RE::NiPointer actor; if (!intfc->ResolveHandle(actorAssn.first, handle)) continue; - if (!RE::LookupReferenceByHandle(static_cast(handle), actor)) - continue; ActorOutfitAssignments assignments; assignments.currentOutfitName = @@ -535,7 +533,7 @@ void ArmorAddonOverrideService::load(const SKSE::SerializationInterface* intfc, cobb::istring(locOutfitData.second.data(), locOutfitData.second.size())); } - actorOutfitAssignmentsLocal[actor.get()] = assignments; + actorOutfitAssignmentsLocal[static_cast(handle)] = assignments; } this->actorOutfitAssignments = actorOutfitAssignmentsLocal; for (const auto& outfitData : data.outfits()) { @@ -547,10 +545,10 @@ void ArmorAddonOverrideService::load(const SKSE::SerializationInterface* intfc, // If the loaded actor assignments are empty, then we know we loaded the old format... convert from that instead. if (actorOutfitAssignmentsLocal.empty()) { - this->actorOutfitAssignments[RE::PlayerCharacter::GetSingleton()].currentOutfitName = + this->actorOutfitAssignments[RE::PlayerCharacter::GetSingleton()->GetHandle().native_handle()].currentOutfitName = cobb::istring(data.obsolete_current_outfit_name().data(), data.obsolete_current_outfit_name().size()); for (const auto& locOutfitData : data.obsolete_location_based_outfits()) { - this->actorOutfitAssignments[RE::PlayerCharacter::GetSingleton()].locationOutfits.emplace(LocationType(locOutfitData.first), + this->actorOutfitAssignments[RE::PlayerCharacter::GetSingleton()->GetHandle().native_handle()].locationOutfits.emplace(LocationType(locOutfitData.first), cobb::istring(locOutfitData.second.data(), locOutfitData.second.size())); } } @@ -562,9 +560,7 @@ proto::OutfitSystem ArmorAddonOverrideService::save() { for (const auto& actorAssn : actorOutfitAssignments) { // Store a reference to the actor std::uint64_t handle; - RE::RefHandle handleTmp; - RE::CreateRefHandle(handleTmp, actorAssn.first); - handle = handleTmp; + handle = actorAssn.first; proto::ActorOutfitAssignment assnOut; assnOut.set_current_outfit_name(actorAssn.second.currentOutfitName.data(), diff --git a/src/OutfitSystem.cpp b/src/OutfitSystem.cpp index d51390f..1eab2f9 100644 --- a/src/OutfitSystem.cpp +++ b/src/OutfitSystem.cpp @@ -144,7 +144,8 @@ namespace OutfitSystem { RE::StaticFunctionTag*) { auto& service = ArmorAddonOverrideService::GetInstance(); auto actors = service.listActors(); - for (auto& actor : actors) { + for (auto& actorRef : actors) { + auto actor = RE::Actor::LookupByHandle(actorRef); if (!actor) continue; auto pm = actor->currentProcess; @@ -158,7 +159,7 @@ namespace OutfitSystem { // // NOTE: AIProcess is also called as RE::ActorProcessManager pm->SetEquipFlag(RE::AIProcess::Flag::kUnk01); - pm->UpdateEquipment(actor); + pm->UpdateEquipment(actor.get()); } } } @@ -197,7 +198,8 @@ namespace OutfitSystem { for (std::uint32_t i = 0; i < size; i++) { const auto form = list[i]; if (form && form->formType == RE::FormType::Armor) { - auto armor = static_cast(form); + auto armor = skyrim_cast(form); + if (!armor) continue; if (armor->templateArmor)// filter out predefined enchanted variants, to declutter the list continue; if (mustBePlayable && !!(armor->formFlags & RE::TESObjectARMO::RecordFlags::kNonPlayable)) @@ -574,7 +576,7 @@ namespace OutfitSystem { if (!actor) return RE::BSFixedString(""); auto& service = ArmorAddonOverrideService::GetInstance(); - return service.currentOutfit(actor).name.c_str(); + return service.currentOutfit(actor->GetHandle().native_handle()).name.c_str(); } bool IsEnabled(RE::BSScript::IVirtualMachine* registry, std::uint32_t stackId, RE::StaticFunctionTag*) { auto& service = ArmorAddonOverrideService::GetInstance(); @@ -729,21 +731,21 @@ namespace OutfitSystem { if (!actor) return; auto& service = ArmorAddonOverrideService::GetInstance(); - service.setOutfit(name.data(), actor); + service.setOutfit(name.data(), actor->GetHandle().native_handle()); } void AddActor(RE::BSScript::IVirtualMachine* registry, std::uint32_t stackId, RE::StaticFunctionTag*, RE::Actor* target) { auto& service = ArmorAddonOverrideService::GetInstance(); - service.addActor((RE::Actor*) target); + service.addActor(target->GetHandle().native_handle()); } void RemoveActor(RE::BSScript::IVirtualMachine* registry, std::uint32_t stackId, RE::StaticFunctionTag*, RE::Actor* target) { auto& service = ArmorAddonOverrideService::GetInstance(); - service.removeActor((RE::Actor*) target); + service.removeActor(target->GetHandle().native_handle()); } std::vector ListActors(RE::BSScript::IVirtualMachine* registry, std::uint32_t stackId, @@ -751,8 +753,16 @@ namespace OutfitSystem { auto& service = ArmorAddonOverrideService::GetInstance(); auto actors = service.listActors(); std::vector actorVec; - for (auto& actor : actors) { - actorVec.push_back(actor); + for (auto& actorRef : actors) { + auto actor = RE::Actor::LookupByHandle(actorRef); + if (!actor) continue; +#if _DEBUG + LOG(debug, "INNER: Actor {} has refcount {}", actor->GetDisplayFullName(), actor->QRefCount()); +#endif + if (actor->QRefCount() == 1) { + LOG(warn, "ListActors will return an actor {} with refcount of 1. This may crash.", actor->GetDisplayFullName()); + } + actorVec.push_back(actor.get()); } std::sort( actorVec.begin(), @@ -760,6 +770,11 @@ namespace OutfitSystem { [](const RE::Actor* x, const RE::Actor* y) { return x < y; }); +#if _DEBUG + for (const auto& actor : actorVec) { + LOG(debug, "Actor {} has refcount {}", actor->GetDisplayFullName(), actor->QRefCount()); + } +#endif return actorVec; } void SetLocationBasedAutoSwitchEnabled(RE::BSScript::IVirtualMachine* registry, @@ -824,7 +839,7 @@ namespace OutfitSystem { } location = location->parentLoc; } - return service.checkLocationType(keywords, weather_flags, RE::PlayerCharacter::GetSingleton()); + return service.checkLocationType(keywords, weather_flags, RE::PlayerCharacter::GetSingleton()->CreateRefHandle().native_handle()); } std::uint32_t IdentifyLocationType(RE::BSScript::IVirtualMachine* registry, std::uint32_t stackId, @@ -856,7 +871,7 @@ namespace OutfitSystem { RE::DebugNotification(message, nullptr, false); */ if (location.has_value()) { - service.setOutfitUsingLocation(location.value(), actor); + service.setOutfitUsingLocation(location.value(), actor->GetHandle().native_handle()); } } } @@ -871,7 +886,7 @@ namespace OutfitSystem { return; } return ArmorAddonOverrideService::GetInstance() - .setLocationOutfit(LocationType(location), name.data(), actor); + .setLocationOutfit(LocationType(location), name.data(), actor->GetHandle().native_handle()); } void UnsetLocationOutfit(RE::BSScript::IVirtualMachine* registry, std::uint32_t stackId, RE::StaticFunctionTag*, RE::Actor* actor, @@ -879,7 +894,7 @@ namespace OutfitSystem { if (!actor) return; return ArmorAddonOverrideService::GetInstance() - .unsetLocationOutfit(LocationType(location), actor); + .unsetLocationOutfit(LocationType(location), actor->GetHandle().native_handle()); } RE::BSFixedString GetLocationOutfit(RE::BSScript::IVirtualMachine* registry, std::uint32_t stackId, @@ -889,7 +904,7 @@ namespace OutfitSystem { if (!actor) return RE::BSFixedString(""); auto outfit = ArmorAddonOverrideService::GetInstance() - .getLocationOutfit(LocationType(location), actor); + .getLocationOutfit(LocationType(location), actor->GetHandle().native_handle()); if (outfit.has_value()) { return RE::BSFixedString(outfit.value().c_str()); } else { diff --git a/src/hooking/Patches.cpp b/src/hooking/Patches.cpp index 21a723b..74f4565 100644 --- a/src/hooking/Patches.cpp +++ b/src/hooking/Patches.cpp @@ -20,7 +20,7 @@ namespace Hooking { LOG(warn, "Target failed to cast to RE::Actor"); return false; } - if (!ArmorAddonOverrideService::GetInstance().shouldOverride(actor)) return false; + if (!ArmorAddonOverrideService::GetInstance().shouldOverride(actor->GetHandle().native_handle())) return false; return true; } @@ -64,13 +64,13 @@ namespace Hooking { bool ShouldOverride(RE::TESObjectARMO* armor, RE::TESObjectREFR* target) { if (!ShouldOverrideSkinning(target)) { return false; } auto& svc = ArmorAddonOverrideService::GetInstance(); - auto& outfit = svc.currentOutfit((RE::Actor*) target); auto actor = skyrim_cast(target); if (!actor) { // Actor failed to cast... LOG(warn, "ShouldOverride: Failed to cast target to Actor."); return true; } + auto& outfit = svc.currentOutfit(actor->GetHandle().native_handle()); auto inventory = target->GetInventoryChanges(); EquippedArmorVisitor visitor; if (inventory) { @@ -90,7 +90,7 @@ namespace Hooking { auto actor = skyrim_cast(target); if (!actor) return mask; auto& svc = ArmorAddonOverrideService::GetInstance(); - auto& outfit = svc.currentOutfit(actor); + auto& outfit = svc.currentOutfit(actor->GetHandle().native_handle()); EquippedArmorVisitor visitor; inventory->ExecuteVisitorOnWorn(&visitor); auto displaySet = outfit.computeDisplaySet(visitor.equipped); @@ -103,7 +103,8 @@ namespace Hooking { namespace CustomSkinPlayer { void Custom(RE::Actor* target, RE::ActorWeightModel* actorWeightModel) { - if (!skyrim_cast(target)) { + auto actor = skyrim_cast(target); + if (!actor) { // Actor failed to cast... LOG(info, "Custom: Failed to cast target to Actor."); return; @@ -118,7 +119,7 @@ namespace Hooking { bool isFemale = base->IsFemale(); // auto& svc = ArmorAddonOverrideService::GetInstance(); - auto& outfit = svc.currentOutfit((RE::Actor*) target); + auto& outfit = svc.currentOutfit(actor->GetHandle().native_handle()); // Get actor inventory and equipped items auto inventory = target->GetInventoryChanges();