diff --git a/include/ArmorAddonOverrideService.h b/include/ArmorAddonOverrideService.h index c7f12b1..89cdca9 100644 --- a/include/ArmorAddonOverrideService.h +++ b/include/ArmorAddonOverrideService.h @@ -12,13 +12,14 @@ namespace RE { struct Outfit { Outfit() {}; // we shouldn't need this, really, but std::unordered_map is a brat - Outfit(const char* n) : name(n) {}; + Outfit(const char* n) : name(n), isFavorite(false) {}; Outfit(const Outfit& other) = default; - Outfit(const char* n, const Outfit& other) : name(n) { + Outfit(const char* n, const Outfit& other) : name(n), isFavorite(false) { this->armors = other.armors; } std::string name; // can't be const; prevents assigning to Outfit vars std::set armors; + bool isFavorite; bool conflictsWith(RE::TESObjectARMO*) const; bool hasShield() const; @@ -33,7 +34,7 @@ class ArmorAddonOverrideService { public: typedef Outfit Outfit; static constexpr UInt32 signature = 'AAOS'; - enum { kSaveVersion = 1 }; + enum { kSaveVersionV1 = 1, kSaveVersionV2 = 2 }; // static constexpr UInt32 ce_outfitNameMaxLength = 256; // SKSE caps serialized std::strings and const char*s to 256 bytes. // @@ -79,12 +80,13 @@ class ArmorAddonOverrideService { Outfit& currentOutfit(); bool hasOutfit(const char* name) const; void deleteOutfit(const char* name); + void setFavorite(const char* name, bool favorite); 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); // bool shouldOverride() const noexcept; - void getOutfitNames(std::vector& out) const; + void getOutfitNames(std::vector& out, bool favoritesOnly = false) const; void setEnabled(bool) noexcept; // void refreshCurrentIfChanged(const char* testName); diff --git a/mod_files/interface/translations/skyrimoutfitsystem_english.txt b/mod_files/interface/translations/skyrimoutfitsystem_english.txt index 83374d9..99be5c7 100644 Binary files a/mod_files/interface/translations/skyrimoutfitsystem_english.txt and b/mod_files/interface/translations/skyrimoutfitsystem_english.txt differ diff --git a/src/ArmorAddonOverrideService.cpp b/src/ArmorAddonOverrideService.cpp index 5a8d317..4b49bdb 100644 --- a/src/ArmorAddonOverrideService.cpp +++ b/src/ArmorAddonOverrideService.cpp @@ -52,6 +52,11 @@ void Outfit::load(SKSESerializationInterface* intfc, UInt32 version) { this->armors.insert(armor); } } + if (version >= ArmorAddonOverrideService::kSaveVersionV1) { + _assertRead(ReadData(intfc, &isFavorite), "Failed to read an outfit's favorite status."); + } else { + this->isFavorite = false; + } } void Outfit::save(SKSESerializationInterface* intfc) const { using namespace Serialization; @@ -65,6 +70,7 @@ void Outfit::save(SKSESerializationInterface* intfc) const { formID = armor->formID; _assertWrite(WriteData(intfc, &formID), "Failed to write an outfit's armors."); } + _assertWrite(WriteData(intfc, &isFavorite), "Failed to write an outfit's favorite status."); } void ArmorAddonOverrideService::_validateNameOrThrow(const char* outfitName) { @@ -117,6 +123,13 @@ void ArmorAddonOverrideService::deleteOutfit(const char* name) { if (this->currentOutfitName == name) this->currentOutfitName = g_noOutfitName; } + +void ArmorAddonOverrideService::setFavorite(const char* name, bool favorite) { + auto outfit = this->outfits.find(name); + if (outfit != this->outfits.end()) + outfit->second.isFavorite = favorite; +} + void ArmorAddonOverrideService::modifyOutfit(const char* name, std::vector& add, std::vector& remove, bool createIfMissing) { try { Outfit& target = this->getOutfit(name); @@ -170,12 +183,12 @@ bool ArmorAddonOverrideService::shouldOverride() const noexcept { return false; return true; } -void ArmorAddonOverrideService::getOutfitNames(std::vector& out) const { +void ArmorAddonOverrideService::getOutfitNames(std::vector& out, bool favoritesOnly) const { out.clear(); auto& list = this->outfits; out.reserve(list.size()); for (auto it = list.cbegin(); it != list.cend(); ++it) - out.push_back(it->second.name); + if (!favoritesOnly || it->second.isFavorite) out.push_back(it->second.name); } void ArmorAddonOverrideService::setEnabled(bool flag) noexcept { this->enabled = flag; diff --git a/src/OutfitSystem.cpp b/src/OutfitSystem.cpp index ed3fa49..d923a20 100644 --- a/src/OutfitSystem.cpp +++ b/src/OutfitSystem.cpp @@ -438,6 +438,18 @@ } return converted_result; } + bool GetOutfitFavoriteStatus(VMClassRegistry* registry, UInt32 stackId, StaticFunctionTag*, BSFixedString name) { + auto& service = ArmorAddonOverrideService::GetInstance(); + bool result = false; + try { + auto& outfit = service.getOutfit(name.data); + result = outfit.isFavorite; + } + catch (std::out_of_range) { + registry->LogWarning("The specified outfit does not exist.", stackId); + } + return result; + } BSFixedString GetSelectedOutfit(VMClassRegistry* registry, UInt32 stackId, StaticFunctionTag*) { auto& service = ArmorAddonOverrideService::GetInstance(); return service.currentOutfit().name.c_str(); @@ -446,11 +458,11 @@ auto& service = ArmorAddonOverrideService::GetInstance(); return service.enabled; } - VMResultArray ListOutfits(VMClassRegistry* registry, UInt32 stackId, StaticFunctionTag*) { + VMResultArray ListOutfits(VMClassRegistry* registry, UInt32 stackId, StaticFunctionTag*, bool favoritesOnly) { auto& service = ArmorAddonOverrideService::GetInstance(); VMResultArray result; std::vector intermediate; - service.getOutfitNames(intermediate); + service.getOutfitNames(intermediate, favoritesOnly); result.reserve(intermediate.size()); for (auto it = intermediate.begin(); it != intermediate.end(); ++it) result.push_back(it->c_str()); @@ -512,6 +524,10 @@ } return true; } + void SetOutfitFavoriteStatus(VMClassRegistry* registry, UInt32 stackId, StaticFunctionTag*, BSFixedString name, bool favorite) { + auto& service = ArmorAddonOverrideService::GetInstance(); + service.setFavorite(name.data, favorite); + } bool OutfitExists(VMClassRegistry* registry, UInt32 stackId, StaticFunctionTag*, BSFixedString name) { auto& service = ArmorAddonOverrideService::GetInstance(); return service.hasOutfit(name.data); @@ -702,6 +718,18 @@ bool OutfitSystem::RegisterPapyrus(VMClassRegistry* registry) { GetOutfitContents, registry )); + registry->RegisterFunction(new NativeFunction1( + "GetOutfitFavoriteStatus", + "SkyrimOutfitSystemNativeFuncs", + GetOutfitFavoriteStatus, + registry + )); + registry->RegisterFunction(new NativeFunction2( + "SetOutfitFavoriteStatus", + "SkyrimOutfitSystemNativeFuncs", + SetOutfitFavoriteStatus, + registry + )); registry->RegisterFunction(new NativeFunction0( "IsEnabled", "SkyrimOutfitSystemNativeFuncs", @@ -714,7 +742,7 @@ bool OutfitSystem::RegisterPapyrus(VMClassRegistry* registry) { GetSelectedOutfit, registry )); - registry->RegisterFunction(new NativeFunction0>( + registry->RegisterFunction(new NativeFunction1, bool>( "ListOutfits", "SkyrimOutfitSystemNativeFuncs", ListOutfits, diff --git a/src/main.cpp b/src/main.cpp index ecbab84..3bce6c5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -137,7 +137,7 @@ void Callback_Messaging_SKSE(SKSEMessagingInterface::Message* message) { void Callback_Serialization_Save(SKSESerializationInterface* intfc) { _MESSAGE("Writing savedata..."); // - if (intfc->OpenRecord(ArmorAddonOverrideService::signature, ArmorAddonOverrideService::kSaveVersion)) { + if (intfc->OpenRecord(ArmorAddonOverrideService::signature, ArmorAddonOverrideService::kSaveVersionV2)) { try { auto& service = ArmorAddonOverrideService::GetInstance(); service.save(intfc); diff --git a/src/papyrus/skyoutsysmcm.psc b/src/papyrus/skyoutsysmcm.psc index 59ca3bb..44da736 100644 --- a/src/papyrus/skyoutsysmcm.psc +++ b/src/papyrus/skyoutsysmcm.psc @@ -232,14 +232,7 @@ EndFunction ; SkyOutSysQuickslotManager kQM = GetQuickslotManager() AddHeaderOption("$SkyOutSys_MCMHeader_Quickslots") - Int iCount = kQM.GetQuickslotCount() AddToggleOptionST("OPT_QuickslotsEnabled", "$SkyOutSys_Text_EnableQuickslots", kQM.GetEnabled()) - Int iIterator = 0 - While iIterator < iCount - String sQuickslotted = kQM.GetQuickslot(iIterator) - AddMenuOptionST("OPT_QuickslotEntry" + iIterator, "$SkyOutSys_Text_Quickslot{" + (iIterator + 1) + "}", sQuickslotted) - iIterator = iIterator + 1 - EndWhile EndFunction ; State OPT_Enabled @@ -344,7 +337,6 @@ EndFunction AddHeaderOption("$SkyOutSys_MCMHeader_GeneralActions") AddInputOptionST("OutfitContext_New", "$SkyOutSys_OContext_New", "") AddInputOptionST("OutfitContext_NewFromWorn", "$SkyOutSys_OContext_NewFromWorn", "") - AddEmptyOption() ; Int iContextFlags = OPTION_FLAG_HIDDEN If _sOutfitShowingContextMenu @@ -356,6 +348,11 @@ EndFunction Else AddTextOptionST("OutfitContext_Toggle", "$SkyOutSys_OContext_ToggleOn", "", iContextFlags) EndIf + If SkyrimOutfitSystemNativeFuncs.GetOutfitFavoriteStatus(_sOutfitShowingContextMenu) + AddTextOptionST("OutfitContext_Favorite", "$SkyOutSys_OContext_ToggleFavoriteOff", "", iContextFlags) + Else + AddTextOptionST("OutfitContext_Favorite", "$SkyOutSys_OContext_ToggleFavoriteOn", "", iContextFlags) + EndIf AddTextOptionST ("OutfitContext_Edit", "$SkyOutSys_OContext_Edit", "", iContextFlags) AddInputOptionST("OutfitContext_Rename", "$SkyOutSys_OContext_Rename", "", iContextFlags) AddTextOptionST ("OutfitContext_Delete", "$SkyOutSys_OContext_Delete", "", iContextFlags) @@ -427,6 +424,17 @@ EndFunction ForcePageReset() EndEvent EndState + State OutfitContext_Favorite + Event OnSelectST() + If SkyrimOutfitSystemNativeFuncs.GetOutfitFavoriteStatus(_sOutfitShowingContextMenu) + SkyrimOutfitSystemNativeFuncs.SetOutfitFavoriteStatus(_sOutfitShowingContextMenu, false) + Else + SkyrimOutfitSystemNativeFuncs.SetOutfitFavoriteStatus(_sOutfitShowingContextMenu, true) + EndIf + RefreshCache() + ForcePageReset() + EndEvent + EndState State OutfitContext_Edit Event OnSelectST() StartEditingOutfit(_sOutfitShowingContextMenu) diff --git a/src/papyrus/skyoutsysquicksloteffect.psc b/src/papyrus/skyoutsysquicksloteffect.psc index e781582..35bf4a2 100644 --- a/src/papyrus/skyoutsysquicksloteffect.psc +++ b/src/papyrus/skyoutsysquicksloteffect.psc @@ -1,25 +1,24 @@ Scriptname SkyOutSysQuickslotEffect extends activemagiceffect -Message Property SkyrimOutfitSystemQuickslotMenuMessage Auto - Event OnEffectStart(Actor akCaster, Actor akTarget) - Int iSelection = SkyrimOutfitSystemQuickslotMenuMessage.Show() - ; - ; The first option is "Cancel." - ; - If iSelection - iSelection = iSelection - 1 - SkyOutSysQuickslotManager kManager = Quest.GetQuest("SkyrimOutfitSystemQuickslotManager") as SkyOutSysQuickslotManager - Int iCount = kManager.GetQuickslotCount() - If iSelection < iCount - String sOutfit = kManager.GetQuickslot(iSelection) - SkyrimOutfitSystemNativeFuncs.SetSelectedOutfit(sOutfit) - Else - ; - ; The option after the last outfit is "No Outfit." - ; - SkyrimOutfitSystemNativeFuncs.SetSelectedOutfit("") - EndIf + String[] sLMenuItems = SkyrimOutfitSystemNativeFuncs.ListOutfits(favoritesOnly = true) + sLMenuItems = SkyrimOutfitSystemNativeFuncs.NaturalSort_ASCII(sLMenuItems) + UIListMenu menu = UIExtensions.GetMenu("UIListMenu") as UIListMenu + Int iIndex = 0 + menu.AddEntryItem("[DISMISS]") + menu.AddEntryItem("[NO OUTFIT]") + While iIndex < sLMenuItems.Length + menu.AddEntryItem(sLMenuItems[iIndex]) + iIndex = iIndex + 1 + EndWhile + UIExtensions.OpenMenu("UIListMenu") + String result = menu.GetResultString() + Debug.Trace("User selected outfit: " + result) + If result == "[NO OUTFIT]" + result = "" + Endif + If result != "[DISMISS]" + SkyrimOutfitSystemNativeFuncs.SetSelectedOutfit(result) SkyrimOutfitSystemNativeFuncs.RefreshArmorFor(Game.GetPlayer()) - EndIf + Endif EndEvent diff --git a/src/papyrus/skyrimoutfitsystemnativefuncs.psc b/src/papyrus/skyrimoutfitsystemnativefuncs.psc index 6503998..c0afbf2 100644 --- a/src/papyrus/skyrimoutfitsystemnativefuncs.psc +++ b/src/papyrus/skyrimoutfitsystemnativefuncs.psc @@ -64,9 +64,11 @@ Bool Function ArmorConflictsWithOutfit (Armor akTest, String asOutfitName) G Function CreateOutfit (String asOutfitName) Global Native Function DeleteOutfit (String asOutfitName) Global Native Armor[] Function GetOutfitContents (String asOutfitName) Global Native +Bool Function GetOutfitFavoriteStatus(String asOutfitName) Global Native + Function SetOutfitFavoriteStatus(String asOutfitName, Bool abFavorite) Global Native String Function GetSelectedOutfit () Global Native Bool Function IsEnabled () Global Native -String[] Function ListOutfits () Global Native +String[] Function ListOutfits (Bool favoritesOnly = False) Global Native Function RemoveArmorFromOutfit (String asOutfitName, Armor akArmor) Global Native Function RemoveConflictingArmorsFrom (Armor akTest, String asOutfitName) Global Native Bool Function RenameOutfit (String asOutfitName, String asRenameTo) Global Native