From aff026ad3f4b3d94b85b733b7c02185709fccb1b Mon Sep 17 00:00:00 2001 From: MetricExpansion <> Date: Fri, 13 Nov 2020 22:59:58 -0800 Subject: [PATCH] Starting work to detect snowy weather. - Allow weather to be passed into location checks. - Allow reordering of autoswitch menu items. --- include/ArmorAddonOverrideService.h | 11 ++-- .../skyrimoutfitsystem_english.txt | Bin 13928 -> 14014 bytes src/ArmorAddonOverrideService.cpp | 15 ++++-- src/OutfitSystem.cpp | 48 +++++++++++------- src/papyrus/skyoutsysautoswitchtrigger.psc | 2 +- src/papyrus/skyoutsysmcm.psc | 7 +-- src/papyrus/skyoutsysquicksloteffect.psc | 5 +- src/papyrus/skyrimoutfitsystemnativefuncs.psc | 6 +-- 8 files changed, 57 insertions(+), 37 deletions(-) diff --git a/include/ArmorAddonOverrideService.h b/include/ArmorAddonOverrideService.h index 1f51e2b..6b59d2d 100644 --- a/include/ArmorAddonOverrideService.h +++ b/include/ArmorAddonOverrideService.h @@ -14,9 +14,14 @@ namespace RE { enum class LocationType: std::uint32_t { World, Town, - Dungeon + Dungeon, + WorldSnowy +}; +static const char * locationTypeStrings[] = { "overworld", "town", "dungeon", "world (snowy)" }; + +struct WeatherFlags { + bool snowy = false; }; -static const char * locationTypeStrings[] = { "overworld", "town", "dungeon" }; struct Outfit { Outfit() {}; // we shouldn't need this, really, but std::unordered_map is a brat @@ -103,7 +108,7 @@ class ArmorAddonOverrideService { void setLocationOutfit(LocationType location, const char* name); void unsetLocationOutfit(LocationType location); std::optional getLocationOutfit(LocationType location); - static std::optional checkLocationType(const std::set& keywords); + LocationType checkLocationType(const std::unordered_set& keywords, const WeatherFlags& weather_flags); // bool shouldOverride() const noexcept; void getOutfitNames(std::vector& out, bool favoritesOnly = false) const; diff --git a/mod_files/interface/translations/skyrimoutfitsystem_english.txt b/mod_files/interface/translations/skyrimoutfitsystem_english.txt index 4bd98a634a7aba94d24ff7f91ee6c45a0a48debd..448110c67e01a7d5d63e95ac87bbd475db403b98 100644 GIT binary patch delta 48 zcmaEnvoCi;0{7%|TyoOJ44e$%4EYR23^@!b3 ArmorAddonOverrideService::getLocationOutfit(Locati } } -std::optional ArmorAddonOverrideService::checkLocationType(const std::set& keywords) { +LocationType ArmorAddonOverrideService::checkLocationType(const std::unordered_set& keywords, const WeatherFlags& weather_flags) { if (keywords.count("LocTypeHabitation")) { - return std::optional(LocationType::Town); + return LocationType::Town; } else if (keywords.count("LocTypeDungeon")) { - return std::optional(LocationType::Dungeon); + return LocationType::Dungeon; } - return std::optional(); + // By now, we know we're in no location more specific than "World" + // + // We will now perform weather checks and return the value *only if* that + // weather has an outfit assigned. + if (weather_flags.snowy && locationOutfits.count(LocationType::WorldSnowy)) { + return LocationType::WorldSnowy; + } + return LocationType::World; } bool ArmorAddonOverrideService::shouldOverride() const noexcept { diff --git a/src/OutfitSystem.cpp b/src/OutfitSystem.cpp index decd584..6bd0201 100644 --- a/src/OutfitSystem.cpp +++ b/src/OutfitSystem.cpp @@ -23,6 +23,7 @@ #include "RE/Inventory/InventoryChanges.h" #include "RE/Inventory/InventoryEntryData.h" #include "RE/FormComponents/TESForm/BGSLocation.h" +#include "RE/FormComponents/TESForm/TESWeather.h" #include "RE/FormComponents/TESForm/BGSKeyword/BGSKeyword.h" #include "RE/Misc/Misc.h" #pragma warning( pop ) @@ -588,15 +589,28 @@ extern SKSESerializationInterface* g_Serialization; bool GetLocationBasedAutoSwitchEnabled(VMClassRegistry* registry, UInt32 stackId, StaticFunctionTag*) { return ArmorAddonOverrideService::GetInstance().locationBasedAutoSwitchEnabled; } - UInt32 GetAutoSwitchLocationCount(VMClassRegistry* registry, UInt32 stackId, StaticFunctionTag*) { - return 3; + VMResultArray GetAutoSwitchLocationArray(VMClassRegistry* registry, UInt32 stackId, StaticFunctionTag*) { + VMResultArray result; + for (UInt32 i : {0, 3, 1, 2}) { + result.push_back(i); + } + return result; } - LocationType identifyLocation(RE::BGSLocation* location) { + LocationType identifyLocation(RE::BGSLocation* location, RE::TESWeather* weather) { // Just a helper function to classify a location. // TODO: Think of a better place than this since we're not exposing it to Papyrus. - std::optional classifiedLocation; + auto& service = ArmorAddonOverrideService::GetInstance(); + + // Collect weather information. + WeatherFlags weather_flags; + if (weather) { + weather_flags.snowy = weather->data.flags.any(RE::TESWeather::WeatherDataFlag::kSnow); + } + + // Collect location keywords + std::unordered_set keywords; + keywords.reserve(20); while (location) { - std::set keywords; std::uint32_t max = location->GetNumKeywords(); for (std::uint32_t i = 0; i < max; i++) { RE::BGSKeyword* keyword = location->GetKeywordAt(i).value(); @@ -608,23 +622,19 @@ extern SKSESerializationInterface* g_Serialization; */ keywords.emplace(keyword->GetFormEditorID()); } - classifiedLocation = ArmorAddonOverrideService::checkLocationType(keywords); - if (classifiedLocation.has_value()) { - break; - } location = location->parentLoc; } - return classifiedLocation.value_or(LocationType::World); + return service.checkLocationType(keywords, weather_flags); } - UInt32 IdentifyLocationType(VMClassRegistry* registry, UInt32 stackId, StaticFunctionTag*, BGSLocation* location_skse) { - return static_cast(identifyLocation((RE::BGSLocation*) location_skse)); + UInt32 IdentifyLocationType(VMClassRegistry* registry, UInt32 stackId, StaticFunctionTag*, BGSLocation* location_skse, TESWeather* weather_skse) { + return static_cast(identifyLocation((RE::BGSLocation*) location_skse, (RE::TESWeather*) weather_skse)); } - void SetOutfitUsingLocation(VMClassRegistry* registry, UInt32 stackId, StaticFunctionTag*, BGSLocation* location_skse) { + void SetOutfitUsingLocation(VMClassRegistry* registry, UInt32 stackId, StaticFunctionTag*, BGSLocation* location_skse, TESWeather* weather_skse) { // NOTE: Location can be NULL. auto& service = ArmorAddonOverrideService::GetInstance(); if (service.locationBasedAutoSwitchEnabled) { - auto location = identifyLocation((RE::BGSLocation*) location_skse); + auto location = identifyLocation((RE::BGSLocation*) location_skse, (RE::TESWeather*) weather_skse); // Debug notifications for location classification. /* const char* locationName = locationTypeStrings[static_cast(location)]; @@ -939,19 +949,19 @@ bool OutfitSystem::RegisterPapyrus(VMClassRegistry* registry) { GetLocationBasedAutoSwitchEnabled, registry )); - registry->RegisterFunction(new NativeFunction0( - "GetAutoSwitchLocationCount", + registry->RegisterFunction(new NativeFunction0>( + "GetAutoSwitchLocationArray", "SkyrimOutfitSystemNativeFuncs", - GetAutoSwitchLocationCount, + GetAutoSwitchLocationArray, registry )); - registry->RegisterFunction(new NativeFunction1( + registry->RegisterFunction(new NativeFunction2( "IdentifyLocationType", "SkyrimOutfitSystemNativeFuncs", IdentifyLocationType, registry )); - registry->RegisterFunction(new NativeFunction1( + registry->RegisterFunction(new NativeFunction2( "SetOutfitUsingLocation", "SkyrimOutfitSystemNativeFuncs", SetOutfitUsingLocation, diff --git a/src/papyrus/skyoutsysautoswitchtrigger.psc b/src/papyrus/skyoutsysautoswitchtrigger.psc index 0eb1161..d038f8c 100644 --- a/src/papyrus/skyoutsysautoswitchtrigger.psc +++ b/src/papyrus/skyoutsysautoswitchtrigger.psc @@ -5,7 +5,7 @@ Event OnLocationChange(Location akOldLoc, Location akNewLoc) ; Debug.Trace("SOS: Running OnLocationChange") GoToState("Waiting") Utility.Wait(10.0) - SkyrimOutfitSystemNativeFuncs.SetOutfitUsingLocation(Game.GetPlayer().GetCurrentLocation()) + SkyrimOutfitSystemNativeFuncs.SetOutfitUsingLocation(Game.GetPlayer().GetCurrentLocation(), Weather.GetCurrentWeather()) SkyrimOutfitSystemNativeFuncs.RefreshArmorFor(Game.GetPlayer()) GoToState("Listening") endEvent diff --git a/src/papyrus/skyoutsysmcm.psc b/src/papyrus/skyoutsysmcm.psc index b22bb8e..1830bc5 100644 --- a/src/papyrus/skyoutsysmcm.psc +++ b/src/papyrus/skyoutsysmcm.psc @@ -252,16 +252,17 @@ EndFunction ;/Block/; ; Right column SetCursorPosition(1) AddHeaderOption("$SkyOutSys_MCMHeader_Autoswitch") - Int iCount = SkyrimOutfitSystemNativeFuncs.GetAutoSwitchLocationCount() + Int[] iIndices = SkyrimOutfitSystemNativeFuncs.GetAutoSwitchLocationArray() + Int iCount = iIndices.Length AddToggleOptionST("OPT_AutoswitchEnabled", "$SkyOutSys_Text_EnableAutoswitch", SkyrimOutfitSystemNativeFuncs.GetLocationBasedAutoSwitchEnabled()) If SkyrimOutfitSystemNativeFuncs.GetLocationBasedAutoSwitchEnabled() Int iIterator = 0 While iIterator < iCount - String sLocationOutfit = SkyrimOutfitSystemNativeFuncs.GetLocationOutfit(iIterator) + String sLocationOutfit = SkyrimOutfitSystemNativeFuncs.GetLocationOutfit(iIndices[iIterator]) If sLocationOutfit == "" sLocationOutfit = "$SkyOutSys_AutoswitchEdit_None" EndIf - AddMenuOptionST("OPT_AutoswitchEntry" + iIterator, "$SkyOutSys_Text_Autoswitch" + iIterator, sLocationOutfit) + AddMenuOptionST("OPT_AutoswitchEntry" + iIndices[iIterator], "$SkyOutSys_Text_Autoswitch" + iIndices[iIterator], sLocationOutfit) iIterator = iIterator + 1 EndWhile EndIf diff --git a/src/papyrus/skyoutsysquicksloteffect.psc b/src/papyrus/skyoutsysquicksloteffect.psc index acd54fd..259c287 100644 --- a/src/papyrus/skyoutsysquicksloteffect.psc +++ b/src/papyrus/skyoutsysquicksloteffect.psc @@ -1,9 +1,6 @@ Scriptname SkyOutSysQuickslotEffect extends activemagiceffect Event OnEffectStart(Actor akCaster, Actor akTarget) - - SkyrimOutfitSystemNativeFuncs.SetOutfitUsingLocation(akCaster.GetCurrentLocation()) - String[] sLMenuItems = SkyrimOutfitSystemNativeFuncs.ListOutfits(favoritesOnly = true) sLMenuItems = SkyrimOutfitSystemNativeFuncs.NaturalSort_ASCII(sLMenuItems) UIListMenu menu = UIExtensions.GetMenu("UIListMenu") as UIListMenu @@ -29,7 +26,7 @@ Event OnEffectStart(Actor akCaster, Actor akTarget) ; 1) autoswitching is enabled, ; 2) the current location has an outfit assigned already, and ; 3) if we have an outfit selected in this menu - Int playerLocationType = SkyrimOutfitSystemNativeFuncs.IdentifyLocationType(Game.GetPlayer().GetCurrentLocation()) + Int playerLocationType = SkyrimOutfitSystemNativeFuncs.IdentifyLocationType(Game.GetPlayer().GetCurrentLocation(), Weather.GetCurrentWeather()) If SkyrimOutfitSystemNativeFuncs.GetLocationBasedAutoSwitchEnabled() && SkyrimOutfitSystemNativeFuncs.GetLocationOutfit(playerLocationType) != "" && result != "" SkyrimOutfitSystemNativeFuncs.SetLocationOutfit(playerLocationType, result) Debug.Notification("This outfit will be remembered for this location type.") diff --git a/src/papyrus/skyrimoutfitsystemnativefuncs.psc b/src/papyrus/skyrimoutfitsystemnativefuncs.psc index 9886ed2..0f9c90e 100644 --- a/src/papyrus/skyrimoutfitsystemnativefuncs.psc +++ b/src/papyrus/skyrimoutfitsystemnativefuncs.psc @@ -79,9 +79,9 @@ Bool Function OutfitExists (String asOutfitName) Global Native Function SetLocationBasedAutoSwitchEnabled (Bool abEnabled) Global Native Bool Function GetLocationBasedAutoSwitchEnabled () Global Native -Int Function GetAutoSwitchLocationCount () Global Native -Int Function IdentifyLocationType (Location alLocation) Global Native - Function SetOutfitUsingLocation (Location alLocation) Global Native +Int[] Function GetAutoSwitchLocationArray () Global Native +Int Function IdentifyLocationType (Location alLocation, Weather awWeather) Global Native + Function SetOutfitUsingLocation (Location alLocation, Weather awWeather) Global Native Function SetLocationOutfit (Int aiLocationType, String asOutfitName) Global Native Function UnsetLocationOutfit (Int aiLocationType) Global Native String Function GetLocationOutfit (Int aiLocationType) Global Native