diff --git a/src/ArmorAddonOverrideService.cpp b/src/ArmorAddonOverrideService.cpp index 3104290..89865f4 100644 --- a/src/ArmorAddonOverrideService.cpp +++ b/src/ArmorAddonOverrideService.cpp @@ -4,595 +4,594 @@ //#include "skse64/GameRTTI.h" //#include "skse64/Serialization.h" -void _assertWrite(bool result, const char *err) { - if (!result) - throw ArmorAddonOverrideService::save_error(err); +void _assertWrite(bool result, const char* err) { + if (!result) + throw ArmorAddonOverrideService::save_error(err); } -void _assertRead(bool result, const char *err) { - if (!result) - throw ArmorAddonOverrideService::load_error(err); +void _assertRead(bool result, const char* err) { + if (!result) + throw ArmorAddonOverrideService::load_error(err); } -bool Outfit::conflictsWith(RE::TESObjectARMO *test) const { - if (!test) - return false; - const auto mask = static_cast(test->GetSlotMask()); - for (auto it = this->armors.cbegin(); it != this->armors.cend(); ++it) { - RE::TESObjectARMO *armor = *it; - if (armor) - if ((mask & static_cast(armor->GetSlotMask())) - != static_cast(RE::BGSBipedObjectForm::FirstPersonFlag::kNone)) - return true; - } - return false; +bool Outfit::conflictsWith(RE::TESObjectARMO* test) const { + if (!test) + return false; + const auto mask = static_cast(test->GetSlotMask()); + for (auto it = this->armors.cbegin(); it != this->armors.cend(); ++it) { + RE::TESObjectARMO* armor = *it; + if (armor) + if ((mask & static_cast(armor->GetSlotMask())) + != static_cast(RE::BGSBipedObjectForm::FirstPersonFlag::kNone)) + return true; + } + return false; } bool Outfit::hasShield() const { - auto &list = this->armors; - for (auto it = list.cbegin(); it != list.cend(); ++it) { - RE::TESObjectARMO *armor = *it; - if (armor) { - if ((armor->formFlags & RE::TESObjectARMO::RecordFlags::kShield) != 0) - return true; - } - } - return false; + auto& list = this->armors; + for (auto it = list.cbegin(); it != list.cend(); ++it) { + RE::TESObjectARMO* armor = *it; + if (armor) { + if ((armor->formFlags & RE::TESObjectARMO::RecordFlags::kShield) != 0) + return true; + } + } + return false; }; -std::unordered_set Outfit::computeDisplaySet(const std::unordered_set &equipped) { - std::unordered_set result; +std::unordered_set Outfit::computeDisplaySet(const std::unordered_set& equipped) { + std::unordered_set result; - // Handle whatever combination of flags we have for how passthrough and equip requirements work. - if (!allowsPassthrough && !requiresEquipped) { - // The usual behavior: Don't allow passthrough of any slot and unconditionally apply the outfit items. + // Handle whatever combination of flags we have for how passthrough and equip requirements work. + if (!allowsPassthrough && !requiresEquipped) { + // The usual behavior: Don't allow passthrough of any slot and unconditionally apply the outfit items. - // The result is just my own armors... - result = armors; - } else if (allowsPassthrough && !requiresEquipped) { - // The much requested behavior. Allow compatible armors to show through the outfit + // The result is just my own armors... + result = armors; + } else if (allowsPassthrough && !requiresEquipped) { + // The much requested behavior. Allow compatible armors to show through the outfit - // The result is just my own armors... - result = armors; + // The result is just my own armors... + result = armors; - // And now append equipped armors that *are compatible*. - for (auto candidate : equipped) { - if (!conflictsWith(candidate)) { - result.emplace(candidate); - } - } - } else if (!allowsPassthrough && requiresEquipped) { - // No passthrough, but do require that items are equipped to show their overrides. + // And now append equipped armors that *are compatible*. + for (auto candidate : equipped) { + if (!conflictsWith(candidate)) { + result.emplace(candidate); + } + } + } else if (!allowsPassthrough && requiresEquipped) { + // No passthrough, but do require that items are equipped to show their overrides. - // Get the effective mask of all equipped armors - std::uint32_t equippedMask = 0; - for (const auto armor : equipped) { - equippedMask |= static_cast(armor->GetSlotMask()); - } + // Get the effective mask of all equipped armors + std::uint32_t equippedMask = 0; + for (const auto armor : equipped) { + equippedMask |= static_cast(armor->GetSlotMask()); + } - // Only add my armors if they are covered by the equip mask - for (const auto armor : armors) { - std::uint32_t armorMask = static_cast(armor->GetSlotMask()); - if ((armorMask & equippedMask) == armorMask) { - result.emplace(armor); - } - } - } else if (allowsPassthrough && requiresEquipped) { - // Allow passthrough and also require that outfit items are equipped to show. + // Only add my armors if they are covered by the equip mask + for (const auto armor : armors) { + std::uint32_t armorMask = static_cast(armor->GetSlotMask()); + if ((armorMask & equippedMask) == armorMask) { + result.emplace(armor); + } + } + } else if (allowsPassthrough && requiresEquipped) { + // Allow passthrough and also require that outfit items are equipped to show. - // Get the effective mask of all equipped armors - std::uint32_t equippedMask = 0; - for (const auto armor : equipped) { - equippedMask |= static_cast(armor->GetSlotMask()); - } + // Get the effective mask of all equipped armors + std::uint32_t equippedMask = 0; + for (const auto armor : equipped) { + equippedMask |= static_cast(armor->GetSlotMask()); + } - // Only add my armors if they are covered by the equip mask - for (const auto armor : armors) { - std::uint32_t armorMask = static_cast(armor->GetSlotMask()); - if ((armorMask & equippedMask) == armorMask) { - result.emplace(armor); - } - } + // Only add my armors if they are covered by the equip mask + for (const auto armor : armors) { + std::uint32_t armorMask = static_cast(armor->GetSlotMask()); + if ((armorMask & equippedMask) == armorMask) { + result.emplace(armor); + } + } - // And now append equipped armors that *are compatible*. - for (auto candidate : equipped) { - if (!conflictsWith(candidate)) { - result.emplace(candidate); - } - } - } + // And now append equipped armors that *are compatible*. + for (auto candidate : equipped) { + if (!conflictsWith(candidate)) { + result.emplace(candidate); + } + } + } - // IN ALL CASES -- SPECIAL SHIELD HANDLING - // Check if the equipped and outfit have shields and save them. - std::optional equippedShield; - std::optional outfitShield; - for (const auto armor : equipped) { - if ((armor->formFlags & RE::TESObjectARMO::RecordFlags::kShield) != 0) { - equippedShield = armor; - break; - } - } - for (const auto armor : armors) { - if ((armor->formFlags & RE::TESObjectARMO::RecordFlags::kShield) != 0) { - outfitShield = armor; - break; - } - } + // IN ALL CASES -- SPECIAL SHIELD HANDLING + // Check if the equipped and outfit have shields and save them. + std::optional equippedShield; + std::optional outfitShield; + for (const auto armor : equipped) { + if ((armor->formFlags & RE::TESObjectARMO::RecordFlags::kShield) != 0) { + equippedShield = armor; + break; + } + } + for (const auto armor : armors) { + if ((armor->formFlags & RE::TESObjectARMO::RecordFlags::kShield) != 0) { + outfitShield = armor; + break; + } + } - // Unconditionally erase shields from the result, so that we can apply the correct one (if any) - for (auto it = result.begin(); it != result.end();) { - if (((*it)->formFlags & RE::TESObjectARMO::RecordFlags::kShield) != 0) { - it = result.erase(it); - } - else { - ++it; - } - } + // Unconditionally erase shields from the result, so that we can apply the correct one (if any) + for (auto it = result.begin(); it != result.end();) { + if (((*it)->formFlags & RE::TESObjectARMO::RecordFlags::kShield) != 0) { + it = result.erase(it); + } else { + ++it; + } + } - // Add the correct shield to the outfit. - if (!equippedShield.has_value()) { - // No equipped shield. No shield will be added. - } else if (equippedShield.has_value() && !outfitShield.has_value()) { - // Equipped shield, but nothing in the outfit. Use the equipped shield. - result.emplace(equippedShield.value()); - } else if (equippedShield.has_value() && outfitShield.has_value()) { - // Equipped shield, and outfit has shield. Use the outfit shield. - result.emplace(outfitShield.value()); - } - // END SHIELD HANDLING + // Add the correct shield to the outfit. + if (!equippedShield.has_value()) { + // No equipped shield. No shield will be added. + } else if (equippedShield.has_value() && !outfitShield.has_value()) { + // Equipped shield, but nothing in the outfit. Use the equipped shield. + result.emplace(equippedShield.value()); + } else if (equippedShield.has_value() && outfitShield.has_value()) { + // Equipped shield, and outfit has shield. Use the outfit shield. + result.emplace(outfitShield.value()); + } + // END SHIELD HANDLING - return result; + return result; }; -void Outfit::load_legacy(const SKSE::SerializationInterface *intfc, std::uint32_t version) { - std::uint32_t size = 0; - _assertRead(intfc->ReadRecordData(size), "Failed to read an outfit's armor count."); - for (std::uint32_t i = 0; i < size; i++) { - RE::FormID formID = 0; - _assertRead(intfc->ReadRecordData(formID), "Failed to read an outfit's armor."); - RE::FormID fixedID; - if (intfc->ResolveFormID(formID, fixedID)) { - auto armor = skyrim_cast(RE::TESForm::LookupByID(fixedID)); - if (armor) - this->armors.insert(armor); - } - } - if (version >= ArmorAddonOverrideService::kSaveVersionV1) { - _assertRead(intfc->ReadRecordData(isFavorite), "Failed to read an outfit's favorite status."); - } else { - this->isFavorite = false; - } +void Outfit::load_legacy(const SKSE::SerializationInterface* intfc, std::uint32_t version) { + std::uint32_t size = 0; + _assertRead(intfc->ReadRecordData(size), "Failed to read an outfit's armor count."); + for (std::uint32_t i = 0; i < size; i++) { + RE::FormID formID = 0; + _assertRead(intfc->ReadRecordData(formID), "Failed to read an outfit's armor."); + RE::FormID fixedID; + if (intfc->ResolveFormID(formID, fixedID)) { + auto armor = skyrim_cast(RE::TESForm::LookupByID(fixedID)); + if (armor) + this->armors.insert(armor); + } + } + if (version >= ArmorAddonOverrideService::kSaveVersionV1) { + _assertRead(intfc->ReadRecordData(isFavorite), "Failed to read an outfit's favorite status."); + } else { + this->isFavorite = false; + } } -void Outfit::load(const proto::Outfit &proto, const SKSE::SerializationInterface *intfc) { - this->name = proto.name(); - for (const auto &formID : proto.armors()) { - RE::FormID fixedID; - if (intfc->ResolveFormID(formID, fixedID)) { - auto armor = skyrim_cast(RE::TESForm::LookupByID(fixedID)); - if (armor) - this->armors.insert(armor); - } - } - this->isFavorite = proto.is_favorite(); - this->allowsPassthrough = proto.allows_passthrough(); - this->requiresEquipped = proto.requires_equipped(); +void Outfit::load(const proto::Outfit& proto, const SKSE::SerializationInterface* intfc) { + this->name = proto.name(); + for (const auto& formID : proto.armors()) { + RE::FormID fixedID; + if (intfc->ResolveFormID(formID, fixedID)) { + auto armor = skyrim_cast(RE::TESForm::LookupByID(fixedID)); + if (armor) + this->armors.insert(armor); + } + } + this->isFavorite = proto.is_favorite(); + this->allowsPassthrough = proto.allows_passthrough(); + this->requiresEquipped = proto.requires_equipped(); } proto::Outfit Outfit::save() const { - proto::Outfit out; - out.set_name(this->name); - for (const auto &armor : this->armors) { - if (armor) - out.add_armors(armor->formID); - } - out.set_is_favorite(this->isFavorite); - out.set_allows_passthrough(this->allowsPassthrough); - out.set_requires_equipped(this->requiresEquipped); - return out; + proto::Outfit out; + out.set_name(this->name); + for (const auto& armor : this->armors) { + if (armor) + out.add_armors(armor->formID); + } + out.set_is_favorite(this->isFavorite); + out.set_allows_passthrough(this->allowsPassthrough); + out.set_requires_equipped(this->requiresEquipped); + return out; } -void ArmorAddonOverrideService::_validateNameOrThrow(const char *outfitName) { - if (strcmp(outfitName, g_noOutfitName) == 0) - throw bad_name("Outfits can't use a blank name."); - if (strlen(outfitName) > ce_outfitNameMaxLength) - throw bad_name("The outfit's name is too long."); +void ArmorAddonOverrideService::_validateNameOrThrow(const char* outfitName) { + if (strcmp(outfitName, g_noOutfitName) == 0) + throw bad_name("Outfits can't use a blank name."); + if (strlen(outfitName) > ce_outfitNameMaxLength) + throw bad_name("The outfit's name is too long."); } // -Outfit &ArmorAddonOverrideService::getOutfit(const char *name) { - return this->outfits.at(name); +Outfit& ArmorAddonOverrideService::getOutfit(const char* name) { + return this->outfits.at(name); } -Outfit &ArmorAddonOverrideService::getOrCreateOutfit(const char *name) { - _validateNameOrThrow(name); - return this->outfits.emplace(name, name).first->second; +Outfit& ArmorAddonOverrideService::getOrCreateOutfit(const char* name) { + _validateNameOrThrow(name); + return this->outfits.emplace(name, name).first->second; } // -void ArmorAddonOverrideService::addOutfit(const char *name) { - _validateNameOrThrow(name); - this->outfits.emplace(name, name); +void ArmorAddonOverrideService::addOutfit(const char* name) { + _validateNameOrThrow(name); + this->outfits.emplace(name, name); } -void ArmorAddonOverrideService::addOutfit(const char *name, std::vector armors) { - _validateNameOrThrow(name); - auto &created = this->outfits.emplace(name, name).first->second; - for (auto it = armors.begin(); it != armors.end(); ++it) { - auto armor = *it; - if (armor) - created.armors.insert(armor); - } +void ArmorAddonOverrideService::addOutfit(const char* name, std::vector armors) { + _validateNameOrThrow(name); + auto& created = this->outfits.emplace(name, name).first->second; + for (auto it = armors.begin(); it != armors.end(); ++it) { + auto armor = *it; + if (armor) + created.armors.insert(armor); + } } -Outfit &ArmorAddonOverrideService::currentOutfit(RE::Actor *target) { - if (this->actorOutfitAssignments.count(target) == 0) - return g_noOutfit; - if (this->actorOutfitAssignments.at(target).currentOutfitName == g_noOutfitName) - return g_noOutfit; - try { - return this->outfits.at(this->actorOutfitAssignments.at(target).currentOutfitName); - } catch (std::out_of_range) { - return g_noOutfit; - } +Outfit& ArmorAddonOverrideService::currentOutfit(RE::Actor* target) { + if (this->actorOutfitAssignments.count(target) == 0) + return g_noOutfit; + if (this->actorOutfitAssignments.at(target).currentOutfitName == g_noOutfitName) + return g_noOutfit; + try { + return this->outfits.at(this->actorOutfitAssignments.at(target).currentOutfitName); + } catch (std::out_of_range) { + return g_noOutfit; + } }; -bool ArmorAddonOverrideService::hasOutfit(const char *name) const { - try { - static_cast(this->outfits.at(name)); - return true; - } catch (std::out_of_range) { - return false; - } +bool ArmorAddonOverrideService::hasOutfit(const char* name) const { + try { + static_cast(this->outfits.at(name)); + return true; + } catch (std::out_of_range) { + return false; + } } -void ArmorAddonOverrideService::deleteOutfit(const char *name) { - this->outfits.erase(name); - for (auto &assn : actorOutfitAssignments) { - if (assn.second.currentOutfitName == name) - assn.second.currentOutfitName = g_noOutfitName; - // If the outfit is assigned as a location outfit, remove it there as well. - for (auto it = assn.second.locationOutfits.begin(); it != assn.second.locationOutfits.end(); ++it) { - if (it->second == name) { - assn.second.locationOutfits.erase(it); - break; - } - } - } +void ArmorAddonOverrideService::deleteOutfit(const char* name) { + this->outfits.erase(name); + for (auto& assn : actorOutfitAssignments) { + if (assn.second.currentOutfitName == name) + assn.second.currentOutfitName = g_noOutfitName; + // If the outfit is assigned as a location outfit, remove it there as well. + for (auto it = assn.second.locationOutfits.begin(); it != assn.second.locationOutfits.end(); ++it) { + if (it->second == name) { + assn.second.locationOutfits.erase(it); + break; + } + } + } } -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::setFavorite(const char* name, bool favorite) { + auto outfit = this->outfits.find(name); + if (outfit != this->outfits.end()) + outfit->second.isFavorite = favorite; } -void ArmorAddonOverrideService::setOutfitPassthrough(const char *name, bool allowPassthrough) { - auto outfit = this->outfits.find(name); - if (outfit != this->outfits.end()) - outfit->second.allowsPassthrough = allowPassthrough; +void ArmorAddonOverrideService::setOutfitPassthrough(const char* name, bool allowPassthrough) { + auto outfit = this->outfits.find(name); + if (outfit != this->outfits.end()) + outfit->second.allowsPassthrough = allowPassthrough; } -void ArmorAddonOverrideService::setOutfitEquipRequired(const char *name, bool requiresEquipped) { - auto outfit = this->outfits.find(name); - if (outfit != this->outfits.end()) - outfit->second.requiresEquipped = requiresEquipped; +void ArmorAddonOverrideService::setOutfitEquipRequired(const char* name, bool requiresEquipped) { + auto outfit = this->outfits.find(name); + if (outfit != this->outfits.end()) + outfit->second.requiresEquipped = requiresEquipped; } -void ArmorAddonOverrideService::modifyOutfit(const char *name, - std::vector &add, - std::vector &remove, - bool createIfMissing) { - try { - Outfit &target = this->getOutfit(name); - for (auto it = add.begin(); it != add.end(); ++it) { - auto armor = *it; - if (armor) - target.armors.insert(armor); - } - for (auto it = remove.begin(); it != remove.end(); ++it) { - auto armor = *it; - if (armor) - target.armors.erase(armor); - } - } catch (std::out_of_range) { - if (createIfMissing) { - this->addOutfit(name); - this->modifyOutfit(name, add, remove); - } - } +void ArmorAddonOverrideService::modifyOutfit(const char* name, + std::vector& add, + std::vector& remove, + bool createIfMissing) { + try { + Outfit& target = this->getOutfit(name); + for (auto it = add.begin(); it != add.end(); ++it) { + auto armor = *it; + if (armor) + target.armors.insert(armor); + } + for (auto it = remove.begin(); it != remove.end(); ++it) { + auto armor = *it; + if (armor) + target.armors.erase(armor); + } + } catch (std::out_of_range) { + if (createIfMissing) { + this->addOutfit(name); + this->modifyOutfit(name, add, remove); + } + } } -void ArmorAddonOverrideService::renameOutfit(const char *oldName, const char *newName) { - _validateNameOrThrow(newName); - try { - static_cast(this->outfits.at(newName)); - throw name_conflict(""); - } catch (std::out_of_range) { - Outfit &renamed = (this->outfits[newName] = this->outfits - .at(oldName)); // don't try-catch this "at" call; let the caller catch the exception - renamed.name = newName; - this->outfits.erase(oldName); - for (auto &assn : actorOutfitAssignments) { - if (assn.second.currentOutfitName == oldName) - assn.second.currentOutfitName = newName; - // If the outfit is assigned as a location outfit, remove it there as well. - for (auto &locationOutfit : assn.second.locationOutfits) { - if (locationOutfit.second == oldName) { - assn.second.locationOutfits[locationOutfit.first] = newName; - break; - } - } - } - } +void ArmorAddonOverrideService::renameOutfit(const char* oldName, const char* newName) { + _validateNameOrThrow(newName); + try { + static_cast(this->outfits.at(newName)); + throw name_conflict(""); + } catch (std::out_of_range) { + Outfit& renamed = (this->outfits[newName] = this->outfits + .at(oldName)); // don't try-catch this "at" call; let the caller catch the exception + renamed.name = newName; + this->outfits.erase(oldName); + for (auto& assn : actorOutfitAssignments) { + if (assn.second.currentOutfitName == oldName) + assn.second.currentOutfitName = newName; + // If the outfit is assigned as a location outfit, remove it there as well. + for (auto& locationOutfit : assn.second.locationOutfits) { + if (locationOutfit.second == oldName) { + assn.second.locationOutfits[locationOutfit.first] = newName; + break; + } + } + } + } } -void ArmorAddonOverrideService::setOutfit(const char *name, RE::Actor *target) { - if (this->actorOutfitAssignments.count(target) == 0) - return; - if (strcmp(name, g_noOutfitName) == 0) { - this->actorOutfitAssignments.at(target).currentOutfitName = g_noOutfitName; - return; - } - try { - this->getOutfit(name); - this->actorOutfitAssignments.at(target).currentOutfitName = name; - } catch (std::out_of_range) { - LOG(info, - "ArmorAddonOverrideService: Tried to set non-existent outfit %s as active. Switching the system off for now.", - name); - this->actorOutfitAssignments.at(target).currentOutfitName = g_noOutfitName; - } +void ArmorAddonOverrideService::setOutfit(const char* name, RE::Actor* target) { + if (this->actorOutfitAssignments.count(target) == 0) + return; + if (strcmp(name, g_noOutfitName) == 0) { + this->actorOutfitAssignments.at(target).currentOutfitName = g_noOutfitName; + return; + } + try { + this->getOutfit(name); + this->actorOutfitAssignments.at(target).currentOutfitName = name; + } catch (std::out_of_range) { + LOG(info, + "ArmorAddonOverrideService: Tried to set non-existent outfit %s as active. Switching the system off for now.", + name); + this->actorOutfitAssignments.at(target).currentOutfitName = g_noOutfitName; + } } -void ArmorAddonOverrideService::addActor(RE::Actor *target) { - if (actorOutfitAssignments.count(target) == 0) - actorOutfitAssignments.emplace(target, ActorOutfitAssignments()); +void ArmorAddonOverrideService::addActor(RE::Actor* target) { + if (actorOutfitAssignments.count(target) == 0) + actorOutfitAssignments.emplace(target, ActorOutfitAssignments()); } -void ArmorAddonOverrideService::removeActor(RE::Actor *target) { - if (target == RE::PlayerCharacter::GetSingleton()) - return; - actorOutfitAssignments.erase(target); +void ArmorAddonOverrideService::removeActor(RE::Actor* target) { + if (target == RE::PlayerCharacter::GetSingleton()) + return; + actorOutfitAssignments.erase(target); } void ArmorAddonOverrideService::setLocationBasedAutoSwitchEnabled(bool newValue) noexcept { - locationBasedAutoSwitchEnabled = newValue; + locationBasedAutoSwitchEnabled = newValue; } -void ArmorAddonOverrideService::setOutfitUsingLocation(LocationType location, RE::Actor *target) { - if (this->actorOutfitAssignments.count(target) == 0) - return; - auto it = this->actorOutfitAssignments.at(target).locationOutfits.find(location); - if (it != this->actorOutfitAssignments.at(target).locationOutfits.end()) { - this->setOutfit(it->second.c_str(), target); - } +void ArmorAddonOverrideService::setOutfitUsingLocation(LocationType location, RE::Actor* target) { + if (this->actorOutfitAssignments.count(target) == 0) + return; + auto it = this->actorOutfitAssignments.at(target).locationOutfits.find(location); + if (it != this->actorOutfitAssignments.at(target).locationOutfits.end()) { + this->setOutfit(it->second.c_str(), target); + } } -void ArmorAddonOverrideService::setLocationOutfit(LocationType location, const char *name, RE::Actor *target) { - if (this->actorOutfitAssignments.count(target) == 0) - return; - if (!std::string(name).empty()) { // Can never set outfit to the "" outfit. Use unsetLocationOutfit instead. - this->actorOutfitAssignments.at(target).locationOutfits[location] = name; - } +void ArmorAddonOverrideService::setLocationOutfit(LocationType location, const char* name, RE::Actor* target) { + if (this->actorOutfitAssignments.count(target) == 0) + return; + if (!std::string(name).empty()) { // Can never set outfit to the "" outfit. Use unsetLocationOutfit instead. + this->actorOutfitAssignments.at(target).locationOutfits[location] = name; + } } -void ArmorAddonOverrideService::unsetLocationOutfit(LocationType location, RE::Actor *target) { - if (this->actorOutfitAssignments.count(target) == 0) - return; - this->actorOutfitAssignments.at(target).locationOutfits.erase(location); +void ArmorAddonOverrideService::unsetLocationOutfit(LocationType location, RE::Actor* target) { + if (this->actorOutfitAssignments.count(target) == 0) + return; + this->actorOutfitAssignments.at(target).locationOutfits.erase(location); } -std::optional ArmorAddonOverrideService::getLocationOutfit(LocationType location, RE::Actor *target) { - if (this->actorOutfitAssignments.count(target) == 0) - return std::optional();; - auto it = this->actorOutfitAssignments.at(target).locationOutfits.find(location); - if (it != this->actorOutfitAssignments.at(target).locationOutfits.end()) { - return std::optional(it->second); - } else { - return std::optional(); - } +std::optional ArmorAddonOverrideService::getLocationOutfit(LocationType location, RE::Actor* target) { + if (this->actorOutfitAssignments.count(target) == 0) + return std::optional();; + auto it = this->actorOutfitAssignments.at(target).locationOutfits.find(location); + if (it != this->actorOutfitAssignments.at(target).locationOutfits.end()) { + return std::optional(it->second); + } else { + return std::optional(); + } } #define CHECK_LOCATION(TYPE, CHECK_CODE) if (this->actorOutfitAssignments.at(target).locationOutfits.count(LocationType::TYPE) && (CHECK_CODE)) return std::optional(LocationType::TYPE); -std::optional ArmorAddonOverrideService::checkLocationType(const std::unordered_set &keywords, - const WeatherFlags &weather_flags, - RE::Actor *target) { - if (this->actorOutfitAssignments.count(target) == 0) - return std::optional(); +std::optional ArmorAddonOverrideService::checkLocationType(const std::unordered_set& keywords, + const WeatherFlags& weather_flags, + RE::Actor* target) { + if (this->actorOutfitAssignments.count(target) == 0) + return std::optional(); - CHECK_LOCATION(CitySnowy, keywords.count("LocTypeCity") && weather_flags.snowy); - CHECK_LOCATION(CityRainy, keywords.count("LocTypeCity") && weather_flags.rainy); - CHECK_LOCATION(City, keywords.count("LocTypeCity")); + CHECK_LOCATION(CitySnowy, keywords.count("LocTypeCity") && weather_flags.snowy); + CHECK_LOCATION(CityRainy, keywords.count("LocTypeCity") && weather_flags.rainy); + CHECK_LOCATION(City, keywords.count("LocTypeCity")); - // A city is considered a town, so it will use the town outfit unless a city one is selected. - CHECK_LOCATION(TownSnowy, keywords.count("LocTypeTown") + keywords.count("LocTypeCity") && weather_flags.snowy); - CHECK_LOCATION(TownRainy, keywords.count("LocTypeTown") + keywords.count("LocTypeCity") && weather_flags.rainy); - CHECK_LOCATION(Town, keywords.count("LocTypeTown") + keywords.count("LocTypeCity")); + // A city is considered a town, so it will use the town outfit unless a city one is selected. + CHECK_LOCATION(TownSnowy, keywords.count("LocTypeTown") + keywords.count("LocTypeCity") && weather_flags.snowy); + CHECK_LOCATION(TownRainy, keywords.count("LocTypeTown") + keywords.count("LocTypeCity") && weather_flags.rainy); + CHECK_LOCATION(Town, keywords.count("LocTypeTown") + keywords.count("LocTypeCity")); - CHECK_LOCATION(DungeonSnowy, keywords.count("LocTypeDungeon") && weather_flags.snowy); - CHECK_LOCATION(DungeonRainy, keywords.count("LocTypeDungeon") && weather_flags.rainy); - CHECK_LOCATION(Dungeon, keywords.count("LocTypeDungeon")); + CHECK_LOCATION(DungeonSnowy, keywords.count("LocTypeDungeon") && weather_flags.snowy); + CHECK_LOCATION(DungeonRainy, keywords.count("LocTypeDungeon") && weather_flags.rainy); + CHECK_LOCATION(Dungeon, keywords.count("LocTypeDungeon")); - CHECK_LOCATION(WorldSnowy, weather_flags.snowy); - CHECK_LOCATION(WorldRainy, weather_flags.rainy); - CHECK_LOCATION(World, true); + CHECK_LOCATION(WorldSnowy, weather_flags.snowy); + CHECK_LOCATION(WorldRainy, weather_flags.rainy); + CHECK_LOCATION(World, true); - return std::optional(); + return std::optional(); } -bool ArmorAddonOverrideService::shouldOverride(RE::Actor *target) const noexcept { - if (!this->enabled) - return false; - if (this->actorOutfitAssignments.count(target) == 0) - return false; - if (this->actorOutfitAssignments.at(target).currentOutfitName == g_noOutfitName) - return false; - return true; +bool ArmorAddonOverrideService::shouldOverride(RE::Actor* target) const noexcept { + if (!this->enabled) + return false; + if (this->actorOutfitAssignments.count(target) == 0) + return false; + if (this->actorOutfitAssignments.at(target).currentOutfitName == g_noOutfitName) + return false; + return true; } -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) - if (!favoritesOnly || it->second.isFavorite) - out.push_back(it->second.name); +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) + if (!favoritesOnly || it->second.isFavorite) + out.push_back(it->second.name); } void ArmorAddonOverrideService::setEnabled(bool flag) noexcept { - this->enabled = flag; + this->enabled = flag; } // void ArmorAddonOverrideService::reset() { - this->enabled = true; - this->actorOutfitAssignments.clear(); - this->actorOutfitAssignments[RE::PlayerCharacter::GetSingleton()] = ActorOutfitAssignments(); - this->outfits.clear(); - this->locationBasedAutoSwitchEnabled = false; + this->enabled = true; + this->actorOutfitAssignments.clear(); + this->actorOutfitAssignments[RE::PlayerCharacter::GetSingleton()] = ActorOutfitAssignments(); + this->outfits.clear(); + this->locationBasedAutoSwitchEnabled = false; } -void ArmorAddonOverrideService::load_legacy(const SKSE::SerializationInterface *intfc, std::uint32_t version) { - this->reset(); - // - std::string selectedOutfitName; - _assertWrite(intfc->ReadRecordData(this->enabled), "Failed to read the enable state."); - { // current outfit name - // - // we can't call WriteData directly on this->currentOutfitName because it's - // a cobb::istring, and SKSE only templated WriteData for std::string in - // specific; other basic_string classes break it. - // - std::uint32_t size = 0; - char buf[257]; - memset(buf, '\0', sizeof(buf)); - _assertRead(intfc->ReadRecordData(size), "Failed to read the selected outfit name."); - _assertRead(size < 257, "The selected outfit name is too long."); - if (size) { - _assertRead(intfc->ReadRecordData(buf, size), "Failed to read the selected outfit name."); - } - selectedOutfitName = buf; - } - std::uint32_t size; - _assertRead(intfc->ReadRecordData(size), "Failed to read the outfit count."); - for (std::uint32_t i = 0; i < size; i++) { - std::string name; - _assertRead(intfc->ReadRecordData(name), "Failed to read an outfit's name."); - auto &outfit = this->getOrCreateOutfit(name.c_str()); - outfit.load_legacy(intfc, version); - } - this->setOutfit(selectedOutfitName.c_str(), RE::PlayerCharacter::GetSingleton()); - 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()) - .locationOutfits.size()); - _assertRead(intfc->ReadRecordData(autoswitchSize), "Failed to read the number of autoswitch slots."); - for (std::uint32_t i = 0; i < autoswitchSize; i++) { - // get location outfit - // - // we can't call WriteData directly on this->currentOutfitName because it's - // a cobb::istring, and SKSE only templated WriteData for std::string in - // specific; other basic_string classes break it. - // - LocationType autoswitchSlot; - _assertRead(intfc->ReadRecordData(autoswitchSlot), "Failed to read the an autoswitch slot ID."); - std::uint32_t locationOutfitNameSize = 0; - char locationOutfitName[257]; - memset(locationOutfitName, '\0', sizeof(locationOutfitName)); - _assertRead(intfc->ReadRecordData(locationOutfitNameSize), "Failed to read the an autoswitch outfit name."); - _assertRead(locationOutfitNameSize < 257, "The autoswitch outfit name is too long."); - if (locationOutfitNameSize) { - _assertRead(intfc->ReadRecordData(locationOutfitName, locationOutfitNameSize), - "Failed to read the selected outfit name."); - this->actorOutfitAssignments.at(RE::PlayerCharacter::GetSingleton()).locationOutfits - .emplace(autoswitchSlot, locationOutfitName); - } - } - } else { - this->actorOutfitAssignments.at(RE::PlayerCharacter::GetSingleton()).locationOutfits = - std::map(); - } +void ArmorAddonOverrideService::load_legacy(const SKSE::SerializationInterface* intfc, std::uint32_t version) { + this->reset(); + // + std::string selectedOutfitName; + _assertWrite(intfc->ReadRecordData(this->enabled), "Failed to read the enable state."); + { // current outfit name + // + // we can't call WriteData directly on this->currentOutfitName because it's + // a cobb::istring, and SKSE only templated WriteData for std::string in + // specific; other basic_string classes break it. + // + std::uint32_t size = 0; + char buf[257]; + memset(buf, '\0', sizeof(buf)); + _assertRead(intfc->ReadRecordData(size), "Failed to read the selected outfit name."); + _assertRead(size < 257, "The selected outfit name is too long."); + if (size) { + _assertRead(intfc->ReadRecordData(buf, size), "Failed to read the selected outfit name."); + } + selectedOutfitName = buf; + } + std::uint32_t size; + _assertRead(intfc->ReadRecordData(size), "Failed to read the outfit count."); + for (std::uint32_t i = 0; i < size; i++) { + std::string name; + _assertRead(intfc->ReadRecordData(name), "Failed to read an outfit's name."); + auto& outfit = this->getOrCreateOutfit(name.c_str()); + outfit.load_legacy(intfc, version); + } + this->setOutfit(selectedOutfitName.c_str(), RE::PlayerCharacter::GetSingleton()); + 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()) + .locationOutfits.size()); + _assertRead(intfc->ReadRecordData(autoswitchSize), "Failed to read the number of autoswitch slots."); + for (std::uint32_t i = 0; i < autoswitchSize; i++) { + // get location outfit + // + // we can't call WriteData directly on this->currentOutfitName because it's + // a cobb::istring, and SKSE only templated WriteData for std::string in + // specific; other basic_string classes break it. + // + LocationType autoswitchSlot; + _assertRead(intfc->ReadRecordData(autoswitchSlot), "Failed to read the an autoswitch slot ID."); + std::uint32_t locationOutfitNameSize = 0; + char locationOutfitName[257]; + memset(locationOutfitName, '\0', sizeof(locationOutfitName)); + _assertRead(intfc->ReadRecordData(locationOutfitNameSize), "Failed to read the an autoswitch outfit name."); + _assertRead(locationOutfitNameSize < 257, "The autoswitch outfit name is too long."); + if (locationOutfitNameSize) { + _assertRead(intfc->ReadRecordData(locationOutfitName, locationOutfitNameSize), + "Failed to read the selected outfit name."); + this->actorOutfitAssignments.at(RE::PlayerCharacter::GetSingleton()).locationOutfits + .emplace(autoswitchSlot, locationOutfitName); + } + } + } else { + this->actorOutfitAssignments.at(RE::PlayerCharacter::GetSingleton()).locationOutfits = + std::map(); + } } -void ArmorAddonOverrideService::load(const SKSE::SerializationInterface *intfc, const proto::OutfitSystem &data) { - this->reset(); - // Extract data from the protobuf struct. - this->enabled = data.enabled(); - 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; +void ArmorAddonOverrideService::load(const SKSE::SerializationInterface* intfc, const proto::OutfitSystem& data) { + this->reset(); + // Extract data from the protobuf struct. + this->enabled = data.enabled(); + 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 = - cobb::istring(actorAssn.second.current_outfit_name().data(), actorAssn.second.current_outfit_name().size()); - for (const auto &locOutfitData : actorAssn.second.location_based_outfits()) { - assignments.locationOutfits.emplace(LocationType(locOutfitData.first), - cobb::istring(locOutfitData.second.data(), - locOutfitData.second.size())); - } - actorOutfitAssignmentsLocal[actor.get()] = assignments; - } - this->actorOutfitAssignments = actorOutfitAssignmentsLocal; - for (const auto &outfitData : data.outfits()) { - Outfit outfit; - outfit.load(outfitData, intfc); - this->outfits.emplace(cobb::istring(outfitData.name().data(), outfitData.name().size()), outfit); - } - this->locationBasedAutoSwitchEnabled = data.location_based_auto_switch_enabled(); + ActorOutfitAssignments assignments; + assignments.currentOutfitName = + cobb::istring(actorAssn.second.current_outfit_name().data(), actorAssn.second.current_outfit_name().size()); + for (const auto& locOutfitData : actorAssn.second.location_based_outfits()) { + assignments.locationOutfits.emplace(LocationType(locOutfitData.first), + cobb::istring(locOutfitData.second.data(), + locOutfitData.second.size())); + } + actorOutfitAssignmentsLocal[actor.get()] = assignments; + } + this->actorOutfitAssignments = actorOutfitAssignmentsLocal; + for (const auto& outfitData : data.outfits()) { + Outfit outfit; + outfit.load(outfitData, intfc); + this->outfits.emplace(cobb::istring(outfitData.name().data(), outfitData.name().size()), outfit); + } + this->locationBasedAutoSwitchEnabled = data.location_based_auto_switch_enabled(); - // 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 = - 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), - cobb::istring(locOutfitData.second.data(), locOutfitData.second.size())); - } - } + // 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 = + 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), + cobb::istring(locOutfitData.second.data(), locOutfitData.second.size())); + } + } } proto::OutfitSystem ArmorAddonOverrideService::save() { - proto::OutfitSystem out; - out.set_enabled(this->enabled); - 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; + proto::OutfitSystem out; + out.set_enabled(this->enabled); + 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; - proto::ActorOutfitAssignment assnOut; - assnOut.set_current_outfit_name(actorAssn.second.currentOutfitName.data(), - actorAssn.second.currentOutfitName.size()); - for (const auto &lbo : actorAssn.second.locationOutfits) { - assnOut.mutable_location_based_outfits() - ->insert({static_cast(lbo.first), std::string(lbo.second.data(), lbo.second.size())}); - } - out.mutable_actor_outfit_assignments()->insert({handle, assnOut}); - } - for (const auto &outfit : this->outfits) { - auto newOutfit = out.add_outfits(); - *newOutfit = outfit.second.save(); - } - out.set_location_based_auto_switch_enabled(this->locationBasedAutoSwitchEnabled); - return out; + proto::ActorOutfitAssignment assnOut; + assnOut.set_current_outfit_name(actorAssn.second.currentOutfitName.data(), + actorAssn.second.currentOutfitName.size()); + for (const auto& lbo : actorAssn.second.locationOutfits) { + assnOut.mutable_location_based_outfits() + ->insert({ static_cast(lbo.first), std::string(lbo.second.data(), lbo.second.size()) }); + } + out.mutable_actor_outfit_assignments()->insert({ handle, assnOut }); + } + for (const auto& outfit : this->outfits) { + auto newOutfit = out.add_outfits(); + *newOutfit = outfit.second.save(); + } + out.set_location_based_auto_switch_enabled(this->locationBasedAutoSwitchEnabled); + return out; } // void ArmorAddonOverrideService::dump() const { - LOG(info, "Dumping all state for ArmorAddonOverrideService..."); - LOG(info, "Enabled: %d", this->enabled); - LOG(info, "We have %d outfits. Enumerating...", this->outfits.size()); - for (auto it = this->outfits.begin(); it != this->outfits.end(); ++it) { - LOG(info, " - Key: %s", it->first.c_str()); - LOG(info, " - Name: %s", it->second.name.c_str()); - LOG(info, " - Armors:"); - auto &list = it->second.armors; - for (auto jt = list.begin(); jt != list.end(); ++jt) { - auto ptr = *jt; - if (ptr) { - LOG(info, " - (TESObjectARMO*){} == [ARMO:{}]", (void*) ptr, ptr->formID); - } else { - LOG(info, " - nullptr"); - } - } - } - LOG(info, "All state has been dumped."); + LOG(info, "Dumping all state for ArmorAddonOverrideService..."); + LOG(info, "Enabled: %d", this->enabled); + LOG(info, "We have %d outfits. Enumerating...", this->outfits.size()); + for (auto it = this->outfits.begin(); it != this->outfits.end(); ++it) { + LOG(info, " - Key: %s", it->first.c_str()); + LOG(info, " - Name: %s", it->second.name.c_str()); + LOG(info, " - Armors:"); + auto& list = it->second.armors; + for (auto jt = list.begin(); jt != list.end(); ++jt) { + auto ptr = *jt; + if (ptr) { + LOG(info, " - (TESObjectARMO*){} == [ARMO:{}]", (void*)ptr, ptr->formID); + } else { + LOG(info, " - nullptr"); + } + } + } + LOG(info, "All state has been dumped."); } diff --git a/src/OutfitSystem.cpp b/src/OutfitSystem.cpp index 3a1f6d8..301303b 100644 --- a/src/OutfitSystem.cpp +++ b/src/OutfitSystem.cpp @@ -27,1165 +27,1151 @@ } namespace OutfitSystem { - std::int32_t GetOutfitNameMaxLength(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *) { - return ArmorAddonOverrideService::ce_outfitNameMaxLength; - } - std::vector GetCarriedArmor(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *, - RE::Actor *target_skse) { - std::vector result; - auto target = (RE::Actor *) (target_skse); - if (target == nullptr) { - registry->TraceStack("Cannot retrieve data for a None RE::Actor.", - stackId, - RE::BSScript::IVirtualMachine::Severity::kError); - std::vector empty; - return empty; - } - // - class _Visitor : public RE::InventoryChanges::IItemChangeVisitor { - // - // If the player has a shield equipped, and if we're not overriding that - // shield, then we need to grab the equipped shield's worn-flags. - // - public: - virtual ReturnType Visit(RE::InventoryEntryData *data) override { - // Return true to continue, or else false to break. - const auto form = data->object; - if (form && form->formType == RE::FormType::Armor) - this->list.push_back(reinterpret_cast(form)); - return ReturnType::kContinue; - }; + std::int32_t GetOutfitNameMaxLength(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*) { + return ArmorAddonOverrideService::ce_outfitNameMaxLength; + } + std::vector GetCarriedArmor(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*, + RE::Actor* target_skse) { + std::vector result; + auto target = (RE::Actor*)(target_skse); + if (target == nullptr) { + registry->TraceStack("Cannot retrieve data for a None RE::Actor.", + stackId, + RE::BSScript::IVirtualMachine::Severity::kError); + std::vector empty; + return empty; + } + // + class _Visitor : public RE::InventoryChanges::IItemChangeVisitor { + // + // If the player has a shield equipped, and if we're not overriding that + // shield, then we need to grab the equipped shield's worn-flags. + // + public: + virtual ReturnType Visit(RE::InventoryEntryData* data) override { + // Return true to continue, or else false to break. + const auto form = data->object; + if (form && form->formType == RE::FormType::Armor) + this->list.push_back(reinterpret_cast(form)); + return ReturnType::kContinue; + }; - std::vector &list; - // - _Visitor(std::vector &l) : list(l) {}; - }; - auto inventory = target->GetInventoryChanges(); - if (inventory) { - _Visitor visitor(result); - inventory->ExecuteVisitor(&visitor); - } - std::vector converted_result; - converted_result.reserve(result.size()); - for (const auto ptr : result) { - converted_result.push_back((RE::TESObjectARMO *) ptr); - } - return converted_result; - } - std::vector GetWornItems( - RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *, - RE::Actor *target_skse) { - std::vector result; - auto target = (RE::Actor *) (target_skse); - if (target == nullptr) { - registry->TraceStack("Cannot retrieve data for a None RE::Actor.", stackId, RE::BSScript::IVirtualMachine::Severity::kError); - std::vector empty; - return empty; - } - // - class _Visitor : public RE::InventoryChanges::IItemChangeVisitor { - // - // If the player has a shield equipped, and if we're not overriding that - // shield, then we need to grab the equipped shield's worn-flags. - // - public: - virtual ReturnType Visit(RE::InventoryEntryData *data) override { - auto form = data->object; - if (form && form->formType == RE::FormType::Armor) - this->list.push_back(reinterpret_cast(form)); - return ReturnType::kContinue; - }; + std::vector& list; + // + _Visitor(std::vector& l) : list(l) {}; + }; + auto inventory = target->GetInventoryChanges(); + if (inventory) { + _Visitor visitor(result); + inventory->ExecuteVisitor(&visitor); + } + std::vector converted_result; + converted_result.reserve(result.size()); + for (const auto ptr : result) { + converted_result.push_back((RE::TESObjectARMO*)ptr); + } + return converted_result; + } + std::vector GetWornItems( + RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*, + RE::Actor* target_skse) { + std::vector result; + auto target = (RE::Actor*)(target_skse); + if (target == nullptr) { + registry->TraceStack("Cannot retrieve data for a None RE::Actor.", stackId, RE::BSScript::IVirtualMachine::Severity::kError); + std::vector empty; + return empty; + } + // + class _Visitor : public RE::InventoryChanges::IItemChangeVisitor { + // + // If the player has a shield equipped, and if we're not overriding that + // shield, then we need to grab the equipped shield's worn-flags. + // + public: + virtual ReturnType Visit(RE::InventoryEntryData* data) override { + auto form = data->object; + if (form && form->formType == RE::FormType::Armor) + this->list.push_back(reinterpret_cast(form)); + return ReturnType::kContinue; + }; - std::vector &list; - // - _Visitor(std::vector &l) : list(l) {}; - }; - auto inventory = target->GetInventoryChanges(); - if (inventory) { - _Visitor visitor(result); - inventory->ExecuteVisitorOnWorn(&visitor); - } - std::vector converted_result; - converted_result.reserve(result.size()); - for (const auto ptr : result) { - converted_result.push_back((RE::TESObjectARMO *) ptr); - } - return converted_result; - } - void RefreshArmorFor(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *, - RE::Actor *target_skse) { - auto target = (RE::Actor *) (target_skse); - ERROR_AND_RETURN_IF(target == nullptr, "Cannot refresh armor on a None RE::Actor.", registry, stackId); - auto pm = target->currentProcess; - if (pm) { - // - // "SetEquipFlag" tells the process manager that the RE::Actor's - // equipment has changed, and that their ArmorAddons should - // be updated. If you need to find it in Skyrim Special, you - // should see a call near the start of EquipManager's func- - // tion to equip an item. - // - // NOTE: AIProcess is also called as RE::ActorProcessManager - pm->SetEquipFlag(RE::AIProcess::Flag::kUnk01); - pm->UpdateEquipment(target); - } - } - std::vector ActorsNearPC(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *) { - std::vector result; - auto pc = RE::PlayerCharacter::GetSingleton(); - ERROR_AND_RETURN_EXPR_IF(pc == nullptr, "Could not get PC Singleton.", result, registry, stackId); - auto pcCell = pc->GetParentCell(); - ERROR_AND_RETURN_EXPR_IF(pcCell == nullptr, "Could not get cell of PC.", result, registry, stackId); - result.reserve(pcCell->references.size()); - for (const auto &ref : pcCell->references) { - RE::TESObjectREFR *objectRefPtr = ref.get(); - auto actorCastedPtr = skyrim_cast(objectRefPtr); - if (actorCastedPtr) - result.push_back(actorCastedPtr); - } - result.shrink_to_fit(); - return result; - } + std::vector& list; + // + _Visitor(std::vector& l) : list(l) {}; + }; + auto inventory = target->GetInventoryChanges(); + if (inventory) { + _Visitor visitor(result); + inventory->ExecuteVisitorOnWorn(&visitor); + } + std::vector converted_result; + converted_result.reserve(result.size()); + for (const auto ptr : result) { + converted_result.push_back((RE::TESObjectARMO*)ptr); + } + return converted_result; + } + void RefreshArmorFor(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*, + RE::Actor* target_skse) { + auto target = (RE::Actor*)(target_skse); + ERROR_AND_RETURN_IF(target == nullptr, "Cannot refresh armor on a None RE::Actor.", registry, stackId); + auto pm = target->currentProcess; + if (pm) { + // + // "SetEquipFlag" tells the process manager that the RE::Actor's + // equipment has changed, and that their ArmorAddons should + // be updated. If you need to find it in Skyrim Special, you + // should see a call near the start of EquipManager's func- + // tion to equip an item. + // + // NOTE: AIProcess is also called as RE::ActorProcessManager + pm->SetEquipFlag(RE::AIProcess::Flag::kUnk01); + pm->UpdateEquipment(target); + } + } + std::vector ActorsNearPC(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*) { + std::vector result; + auto pc = RE::PlayerCharacter::GetSingleton(); + ERROR_AND_RETURN_EXPR_IF(pc == nullptr, "Could not get PC Singleton.", result, registry, stackId); + auto pcCell = pc->GetParentCell(); + ERROR_AND_RETURN_EXPR_IF(pcCell == nullptr, "Could not get cell of PC.", result, registry, stackId); + result.reserve(pcCell->references.size()); + for (const auto& ref : pcCell->references) { + RE::TESObjectREFR* objectRefPtr = ref.get(); + auto actorCastedPtr = skyrim_cast(objectRefPtr); + if (actorCastedPtr) + result.push_back(actorCastedPtr); + } + result.shrink_to_fit(); + return result; + } - // - namespace ArmorFormSearchUtils { - static struct { - std::vector names; - std::vector armors; - // - void setup(std::string nameFilter, bool mustBePlayable) { - auto data = RE::TESDataHandler::GetSingleton(); - auto &list = data->GetFormArray(RE::FormType::Armor); - const auto size = list.size(); - this->names.reserve(size); - this->armors.reserve(size); - 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); - if (armor->templateArmor) // filter out predefined enchanted variants, to declutter the list - continue; - if (mustBePlayable && !!(armor->formFlags & RE::TESObjectARMO::RecordFlags::kNonPlayable)) - continue; - std::string armorName; - { // get name - auto tfn = skyrim_cast(armor); - if (tfn) - armorName = tfn->fullName.data(); - } - if (armorName.empty()) // skip nameless armor - continue; - if (!nameFilter.empty()) { - auto it = std::search( - armorName.begin(), armorName.end(), - nameFilter.begin(), nameFilter.end(), - [](char a, char b) { return toupper(a) == toupper(b); } - ); - if (it == armorName.end()) - continue; - } - this->armors.push_back(armor); - this->names.push_back(armorName.c_str()); - } - } - } - void clear() { - this->names.clear(); - this->armors.clear(); - } - } data; - // - // - void Prep(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *, - RE::BSFixedString filter, - bool mustBePlayable) { - data.setup(filter.data(), mustBePlayable); - } - std::vector GetForms(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *) { - std::vector result; - auto &list = data.armors; - for (auto it = list.begin(); it != list.end(); it++) - result.push_back(*it); - std::vector converted_result; - converted_result.reserve(result.size()); - for (const auto ptr : result) { - converted_result.push_back((RE::TESObjectARMO *) ptr); - } - return converted_result; - } - std::vector GetNames(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *) { - std::vector result; - auto &list = data.names; - for (auto it = list.begin(); it != list.end(); it++) - result.push_back(it->c_str()); - return result; - } - void Clear(RE::BSScript::IVirtualMachine *registry, std::uint32_t stackId, RE::StaticFunctionTag *) { - data.clear(); - } - } - namespace BodySlotListing { - enum { - kBodySlotMin = 30, - kBodySlotMax = 61, - }; - static struct { - std::vector bodySlots; - std::vector armorNames; - std::vector armors; - } data; - // - void Clear(RE::BSScript::IVirtualMachine *registry, std::uint32_t stackId, RE::StaticFunctionTag *) { - data.bodySlots.clear(); - data.armorNames.clear(); - data.armors.clear(); - } - void Prep(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *, - RE::BSFixedString name) { - data.bodySlots.clear(); - data.armorNames.clear(); - data.armors.clear(); - // - auto &service = ArmorAddonOverrideService::GetInstance(); - try { - auto &outfit = service.getOutfit(name.data()); - auto &armors = outfit.armors; - for (std::uint8_t i = kBodySlotMin; i <= kBodySlotMax; i++) { - std::uint32_t mask = 1 << (i - kBodySlotMin); - for (auto it = armors.begin(); it != armors.end(); it++) { - RE::TESObjectARMO *armor = *it; - if (armor && (static_cast(armor->GetSlotMask()) & mask)) { - data.bodySlots.push_back(i); - data.armors.push_back(armor); - { // name - // TESFullName* pFullName = DYNAMIC_CAST(armor, RE::TESObjectARMO, TESFullName); - auto pFullName = skyrim_cast(armor); - if (pFullName) - data.armorNames.emplace_back(pFullName->fullName.data()); - else - data.armorNames.emplace_back(""); - } - } - } - } - } - catch (std::out_of_range) { - registry->TraceStack("The specified outfit does not exist.", stackId, RE::BSScript::IVirtualMachine::Severity::kWarning); - } - } - std::vector GetArmorForms(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *) { - std::vector result; - auto &list = data.armors; - for (auto it = list.begin(); it != list.end(); it++) - result.push_back(*it); - std::vector converted_result; - converted_result.reserve(result.size()); - for (const auto ptr : result) { - converted_result.push_back((RE::TESObjectARMO *) ptr); - } - return converted_result; - } - std::vector GetArmorNames(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *) { - std::vector result; - auto &list = data.armorNames; - for (auto it = list.begin(); it != list.end(); it++) - result.push_back(it->c_str()); - return result; - } - std::vector GetSlotIndices(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *) { - std::vector result; - auto &list = data.bodySlots; - for (auto it = list.begin(); it != list.end(); it++) - result.push_back(*it); - return result; - } - } - namespace StringSorts { - std::vector NaturalSort_ASCII(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *, - std::vector arr, - bool descending) { - std::vector result = arr; - std::sort( - result.begin(), - result.end(), - [descending](const RE::BSFixedString &x, const RE::BSFixedString &y) { - std::string a(x.data()); - std::string b(y.data()); - if (descending) - std::swap(a, b); - return cobb::utf8::naturalcompare(a, b) > 0; - } - ); - return result; - } + // + namespace ArmorFormSearchUtils { + static struct { + std::vector names; + std::vector armors; + // + void setup(std::string nameFilter, bool mustBePlayable) { + auto data = RE::TESDataHandler::GetSingleton(); + auto& list = data->GetFormArray(RE::FormType::Armor); + const auto size = list.size(); + this->names.reserve(size); + this->armors.reserve(size); + 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); + if (armor->templateArmor) // filter out predefined enchanted variants, to declutter the list + continue; + if (mustBePlayable && !!(armor->formFlags & RE::TESObjectARMO::RecordFlags::kNonPlayable)) + continue; + std::string armorName; + { // get name + auto tfn = skyrim_cast(armor); + if (tfn) + armorName = tfn->fullName.data(); + } + if (armorName.empty()) // skip nameless armor + continue; + if (!nameFilter.empty()) { + auto it = std::search( + armorName.begin(), armorName.end(), + nameFilter.begin(), nameFilter.end(), + [](char a, char b) { return toupper(a) == toupper(b); } + ); + if (it == armorName.end()) + continue; + } + this->armors.push_back(armor); + this->names.push_back(armorName.c_str()); + } + } + } + void clear() { + this->names.clear(); + this->armors.clear(); + } + } data; + // + // + void Prep(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*, + RE::BSFixedString filter, + bool mustBePlayable) { + data.setup(filter.data(), mustBePlayable); + } + std::vector GetForms(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*) { + std::vector result; + auto& list = data.armors; + for (auto it = list.begin(); it != list.end(); it++) + result.push_back(*it); + std::vector converted_result; + converted_result.reserve(result.size()); + for (const auto ptr : result) { + converted_result.push_back((RE::TESObjectARMO*)ptr); + } + return converted_result; + } + std::vector GetNames(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*) { + std::vector result; + auto& list = data.names; + for (auto it = list.begin(); it != list.end(); it++) + result.push_back(it->c_str()); + return result; + } + void Clear(RE::BSScript::IVirtualMachine* registry, std::uint32_t stackId, RE::StaticFunctionTag*) { + data.clear(); + } + } + namespace BodySlotListing { + enum { + kBodySlotMin = 30, + kBodySlotMax = 61, + }; + static struct { + std::vector bodySlots; + std::vector armorNames; + std::vector armors; + } data; + // + void Clear(RE::BSScript::IVirtualMachine* registry, std::uint32_t stackId, RE::StaticFunctionTag*) { + data.bodySlots.clear(); + data.armorNames.clear(); + data.armors.clear(); + } + void Prep(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*, + RE::BSFixedString name) { + data.bodySlots.clear(); + data.armorNames.clear(); + data.armors.clear(); + // + auto& service = ArmorAddonOverrideService::GetInstance(); + try { + auto& outfit = service.getOutfit(name.data()); + auto& armors = outfit.armors; + for (std::uint8_t i = kBodySlotMin; i <= kBodySlotMax; i++) { + std::uint32_t mask = 1 << (i - kBodySlotMin); + for (auto it = armors.begin(); it != armors.end(); it++) { + RE::TESObjectARMO* armor = *it; + if (armor && (static_cast(armor->GetSlotMask()) & mask)) { + data.bodySlots.push_back(i); + data.armors.push_back(armor); + { // name + // TESFullName* pFullName = DYNAMIC_CAST(armor, RE::TESObjectARMO, TESFullName); + auto pFullName = skyrim_cast(armor); + if (pFullName) + data.armorNames.emplace_back(pFullName->fullName.data()); + else + data.armorNames.emplace_back(""); + } + } + } + } + } catch (std::out_of_range) { + registry->TraceStack("The specified outfit does not exist.", stackId, RE::BSScript::IVirtualMachine::Severity::kWarning); + } + } + std::vector GetArmorForms(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*) { + std::vector result; + auto& list = data.armors; + for (auto it = list.begin(); it != list.end(); it++) + result.push_back(*it); + std::vector converted_result; + converted_result.reserve(result.size()); + for (const auto ptr : result) { + converted_result.push_back((RE::TESObjectARMO*)ptr); + } + return converted_result; + } + std::vector GetArmorNames(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*) { + std::vector result; + auto& list = data.armorNames; + for (auto it = list.begin(); it != list.end(); it++) + result.push_back(it->c_str()); + return result; + } + std::vector GetSlotIndices(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*) { + std::vector result; + auto& list = data.bodySlots; + for (auto it = list.begin(); it != list.end(); it++) + result.push_back(*it); + return result; + } + } + namespace StringSorts { + std::vector NaturalSort_ASCII(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*, + std::vector arr, + bool descending) { + std::vector result = arr; + std::sort( + result.begin(), + result.end(), + [descending](const RE::BSFixedString& x, const RE::BSFixedString& y) { + std::string a(x.data()); + std::string b(y.data()); + if (descending) + std::swap(a, b); + return cobb::utf8::naturalcompare(a, b) > 0; + } + ); + return result; + } - // TODO: You need to change all the papyrus scripts that were assuming behavior here. - template std::vector NaturalSortPair_ASCII( - RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *, - std::vector arr, // Array of string - std::vector second, // Array of forms (T) - bool descending) { - std::size_t size = arr.size(); - if (size != second.size()) { - registry->TraceStack("The two arrays must be the same length.", stackId, RE::BSScript::IVirtualMachine::Severity::kError); - return second; - } - // - typedef std::pair _pair; - std::vector<_pair> pairs; - // - std::vector result; - { // Copy input array into output array - result.reserve(size); - for (std::uint32_t i = 0; i < size; i++) { - pairs.emplace_back(arr[i], second[i]); - } - } - std::sort( - pairs.begin(), - pairs.end(), - [descending](const _pair &x, const _pair &y) { - std::string a(x.first.data()); - std::string b(y.first.data()); - if (descending) - std::swap(a, b); - return cobb::utf8::naturalcompare(a, b) > 0; - } - ); - for (std::uint32_t i = 0; i < size; i++) { - result.push_back(pairs[i].first); - second[i] = pairs[i].second; - } - return second; - } - } - namespace Utility { - std::uint32_t HexToInt32(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *, - RE::BSFixedString str) { - const char *s = str.data(); - char *discard; - return strtoul(s, &discard, 16); - } - RE::BSFixedString ToHex(RE::BSScript::IVirtualMachine *registry, std::uint32_t stackId, RE::StaticFunctionTag *, + // TODO: You need to change all the papyrus scripts that were assuming behavior here. + template std::vector NaturalSortPair_ASCII( + RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*, + std::vector arr, // Array of string + std::vector second, // Array of forms (T) + bool descending) { + std::size_t size = arr.size(); + if (size != second.size()) { + registry->TraceStack("The two arrays must be the same length.", stackId, RE::BSScript::IVirtualMachine::Severity::kError); + return second; + } + // + typedef std::pair _pair; + std::vector<_pair> pairs; + // + std::vector result; + { // Copy input array into output array + result.reserve(size); + for (std::uint32_t i = 0; i < size; i++) { + pairs.emplace_back(arr[i], second[i]); + } + } + std::sort( + pairs.begin(), + pairs.end(), + [descending](const _pair& x, const _pair& y) { + std::string a(x.first.data()); + std::string b(y.first.data()); + if (descending) + std::swap(a, b); + return cobb::utf8::naturalcompare(a, b) > 0; + } + ); + for (std::uint32_t i = 0; i < size; i++) { + result.push_back(pairs[i].first); + second[i] = pairs[i].second; + } + return second; + } + } + namespace Utility { + std::uint32_t HexToInt32(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*, + RE::BSFixedString str) { + const char* s = str.data(); + char* discard; + return strtoul(s, &discard, 16); + } + RE::BSFixedString ToHex(RE::BSScript::IVirtualMachine* registry, std::uint32_t stackId, RE::StaticFunctionTag*, - std::uint32_t value, - std::int32_t length) { - if (length < 1) { - registry->TraceStack( - "Cannot format a hexadecimal valueinteger to a negative number of digits. Defaulting to eight.", - stackId, RE::BSScript::IVirtualMachine::Severity::kWarning); - length = 8; - } else if (length > 8) { - registry->TraceStack("Cannot format a hexadecimal integer longer than eight digits.", stackId, RE::BSScript::IVirtualMachine::Severity::kWarning); - length = 8; - } - char hex[9]; - memset(hex, '0', sizeof(hex)); - hex[length] = '\0'; - while (value > 0 && length--) { - std::uint8_t digit = value % 0x10; - value /= 0x10; - if (digit < 0xA) { - hex[length] = digit + '0'; - } else { - hex[length] = digit + 0x37; - } - } - return hex; // passes through RE::BSFixedString constructor, which I believe caches the string, so returning local vars should be fine - } - } - // - void AddArmorToOutfit(RE::BSScript::IVirtualMachine *registry, std::uint32_t stackId, RE::StaticFunctionTag *, + std::uint32_t value, + std::int32_t length) { + if (length < 1) { + registry->TraceStack( + "Cannot format a hexadecimal valueinteger to a negative number of digits. Defaulting to eight.", + stackId, RE::BSScript::IVirtualMachine::Severity::kWarning); + length = 8; + } else if (length > 8) { + registry->TraceStack("Cannot format a hexadecimal integer longer than eight digits.", stackId, RE::BSScript::IVirtualMachine::Severity::kWarning); + length = 8; + } + char hex[9]; + memset(hex, '0', sizeof(hex)); + hex[length] = '\0'; + while (value > 0 && length--) { + std::uint8_t digit = value % 0x10; + value /= 0x10; + if (digit < 0xA) { + hex[length] = digit + '0'; + } else { + hex[length] = digit + 0x37; + } + } + return hex; // passes through RE::BSFixedString constructor, which I believe caches the string, so returning local vars should be fine + } + } + // + void AddArmorToOutfit(RE::BSScript::IVirtualMachine* registry, std::uint32_t stackId, RE::StaticFunctionTag*, - RE::BSFixedString name, - RE::TESObjectARMO *armor_skse) { - auto armor = (RE::TESObjectARMO *) (armor_skse); - ERROR_AND_RETURN_IF(armor == nullptr, "Cannot add a None armor to an outfit.", registry, stackId); - auto &service = ArmorAddonOverrideService::GetInstance(); - try { - auto &outfit = service.getOutfit(name.data()); - outfit.armors.insert(armor); - } - catch (std::out_of_range) { - registry->TraceStack("The specified outfit does not exist.", stackId, RE::BSScript::IVirtualMachine::Severity::kWarning); - } - } - bool ArmorConflictsWithOutfit(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *, + RE::BSFixedString name, + RE::TESObjectARMO* armor_skse) { + auto armor = (RE::TESObjectARMO*)(armor_skse); + ERROR_AND_RETURN_IF(armor == nullptr, "Cannot add a None armor to an outfit.", registry, stackId); + auto& service = ArmorAddonOverrideService::GetInstance(); + try { + auto& outfit = service.getOutfit(name.data()); + outfit.armors.insert(armor); + } catch (std::out_of_range) { + registry->TraceStack("The specified outfit does not exist.", stackId, RE::BSScript::IVirtualMachine::Severity::kWarning); + } + } + bool ArmorConflictsWithOutfit(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*, - RE::TESObjectARMO *armor_skse, - RE::BSFixedString name) { - auto armor = (RE::TESObjectARMO *) (armor_skse); - if (armor == nullptr) { - registry->TraceStack("A None armor can't conflict with anything in an outfit.", stackId, RE::BSScript::IVirtualMachine::Severity::kWarning); - return false; - } - auto &service = ArmorAddonOverrideService::GetInstance(); - try { - auto &outfit = service.getOutfit(name.data()); - return outfit.conflictsWith(armor); - } - catch (std::out_of_range) { - registry->TraceStack("The specified outfit does not exist.", stackId, RE::BSScript::IVirtualMachine::Severity::kWarning); - return false; - } - } - void CreateOutfit(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *, - RE::BSFixedString name) { - auto &service = ArmorAddonOverrideService::GetInstance(); - try { - service.addOutfit(name.data()); - } - catch (ArmorAddonOverrideService::bad_name) { - registry->TraceStack("Invalid outfit name specified.", stackId, RE::BSScript::IVirtualMachine::Severity::kError); - return; - } - } - void DeleteOutfit(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *, - RE::BSFixedString name) { - auto &service = ArmorAddonOverrideService::GetInstance(); - service.deleteOutfit(name.data()); - } - std::vector GetOutfitContents(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *, + RE::TESObjectARMO* armor_skse, + RE::BSFixedString name) { + auto armor = (RE::TESObjectARMO*)(armor_skse); + if (armor == nullptr) { + registry->TraceStack("A None armor can't conflict with anything in an outfit.", stackId, RE::BSScript::IVirtualMachine::Severity::kWarning); + return false; + } + auto& service = ArmorAddonOverrideService::GetInstance(); + try { + auto& outfit = service.getOutfit(name.data()); + return outfit.conflictsWith(armor); + } catch (std::out_of_range) { + registry->TraceStack("The specified outfit does not exist.", stackId, RE::BSScript::IVirtualMachine::Severity::kWarning); + return false; + } + } + void CreateOutfit(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*, + RE::BSFixedString name) { + auto& service = ArmorAddonOverrideService::GetInstance(); + try { + service.addOutfit(name.data()); + } catch (ArmorAddonOverrideService::bad_name) { + registry->TraceStack("Invalid outfit name specified.", stackId, RE::BSScript::IVirtualMachine::Severity::kError); + return; + } + } + void DeleteOutfit(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*, + RE::BSFixedString name) { + auto& service = ArmorAddonOverrideService::GetInstance(); + service.deleteOutfit(name.data()); + } + std::vector GetOutfitContents(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*, - RE::BSFixedString name) { - std::vector result; - auto &service = ArmorAddonOverrideService::GetInstance(); - try { - auto &outfit = service.getOutfit(name.data()); - auto &armors = outfit.armors; - for (auto it = armors.begin(); it != armors.end(); ++it) - result.push_back(*it); - } - catch (std::out_of_range) { - registry->TraceStack("The specified outfit does not exist.", stackId, RE::BSScript::IVirtualMachine::Severity::kWarning); - } - std::vector converted_result; - converted_result.reserve(result.size()); - for (const auto ptr : result) { - converted_result.push_back((RE::TESObjectARMO *) ptr); - } - return converted_result; - } - bool GetOutfitFavoriteStatus(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *, + RE::BSFixedString name) { + std::vector result; + auto& service = ArmorAddonOverrideService::GetInstance(); + try { + auto& outfit = service.getOutfit(name.data()); + auto& armors = outfit.armors; + for (auto it = armors.begin(); it != armors.end(); ++it) + result.push_back(*it); + } catch (std::out_of_range) { + registry->TraceStack("The specified outfit does not exist.", stackId, RE::BSScript::IVirtualMachine::Severity::kWarning); + } + std::vector converted_result; + converted_result.reserve(result.size()); + for (const auto ptr : result) { + converted_result.push_back((RE::TESObjectARMO*)ptr); + } + return converted_result; + } + bool GetOutfitFavoriteStatus(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*, - RE::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->TraceStack("The specified outfit does not exist.", stackId, RE::BSScript::IVirtualMachine::Severity::kWarning); - } - return result; - } - bool GetOutfitPassthroughStatus(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *, + RE::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->TraceStack("The specified outfit does not exist.", stackId, RE::BSScript::IVirtualMachine::Severity::kWarning); + } + return result; + } + bool GetOutfitPassthroughStatus(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*, - RE::BSFixedString name) { - auto &service = ArmorAddonOverrideService::GetInstance(); - bool result = false; - try { - auto &outfit = service.getOutfit(name.data()); - result = outfit.allowsPassthrough; - } - catch (std::out_of_range) { - registry->TraceStack("The specified outfit does not exist.", stackId, RE::BSScript::IVirtualMachine::Severity::kWarning); - } - return result; - } - bool GetOutfitEquipRequiredStatus(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *, + RE::BSFixedString name) { + auto& service = ArmorAddonOverrideService::GetInstance(); + bool result = false; + try { + auto& outfit = service.getOutfit(name.data()); + result = outfit.allowsPassthrough; + } catch (std::out_of_range) { + registry->TraceStack("The specified outfit does not exist.", stackId, RE::BSScript::IVirtualMachine::Severity::kWarning); + } + return result; + } + bool GetOutfitEquipRequiredStatus(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*, - RE::BSFixedString name) { - auto &service = ArmorAddonOverrideService::GetInstance(); - bool result = false; - try { - auto &outfit = service.getOutfit(name.data()); - result = outfit.requiresEquipped; - } - catch (std::out_of_range) { - registry->TraceStack("The specified outfit does not exist.", stackId, RE::BSScript::IVirtualMachine::Severity::kWarning); - } - return result; - } - RE::BSFixedString GetSelectedOutfit(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *) { - auto &service = ArmorAddonOverrideService::GetInstance(); - return service.currentOutfit(RE::PlayerCharacter::GetSingleton()).name.c_str(); - } - bool IsEnabled(RE::BSScript::IVirtualMachine *registry, std::uint32_t stackId, RE::StaticFunctionTag *) { - auto &service = ArmorAddonOverrideService::GetInstance(); - return service.enabled; - } - std::vector ListOutfits(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *, + RE::BSFixedString name) { + auto& service = ArmorAddonOverrideService::GetInstance(); + bool result = false; + try { + auto& outfit = service.getOutfit(name.data()); + result = outfit.requiresEquipped; + } catch (std::out_of_range) { + registry->TraceStack("The specified outfit does not exist.", stackId, RE::BSScript::IVirtualMachine::Severity::kWarning); + } + return result; + } + RE::BSFixedString GetSelectedOutfit(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*) { + auto& service = ArmorAddonOverrideService::GetInstance(); + return service.currentOutfit(RE::PlayerCharacter::GetSingleton()).name.c_str(); + } + bool IsEnabled(RE::BSScript::IVirtualMachine* registry, std::uint32_t stackId, RE::StaticFunctionTag*) { + auto& service = ArmorAddonOverrideService::GetInstance(); + return service.enabled; + } + std::vector ListOutfits(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*, - bool favoritesOnly) { - auto &service = ArmorAddonOverrideService::GetInstance(); - std::vector result; - std::vector intermediate; - service.getOutfitNames(intermediate, favoritesOnly); - result.reserve(intermediate.size()); - for (auto it = intermediate.begin(); it != intermediate.end(); ++it) - result.push_back(it->c_str()); - return result; - } - void RemoveArmorFromOutfit(RE::BSScript::IVirtualMachine *registry, std::uint32_t stackId, RE::StaticFunctionTag *, + bool favoritesOnly) { + auto& service = ArmorAddonOverrideService::GetInstance(); + std::vector result; + std::vector intermediate; + service.getOutfitNames(intermediate, favoritesOnly); + result.reserve(intermediate.size()); + for (auto it = intermediate.begin(); it != intermediate.end(); ++it) + result.push_back(it->c_str()); + return result; + } + void RemoveArmorFromOutfit(RE::BSScript::IVirtualMachine* registry, std::uint32_t stackId, RE::StaticFunctionTag*, - RE::BSFixedString name, - RE::TESObjectARMO *armor_skse) { - auto armor = (RE::TESObjectARMO *) (armor_skse); - ERROR_AND_RETURN_IF(armor == nullptr, "Cannot remove a None armor from an outfit.", registry, stackId); - auto &service = ArmorAddonOverrideService::GetInstance(); - try { - auto &outfit = service.getOutfit(name.data()); - outfit.armors.erase(armor); - } - catch (std::out_of_range) { - registry->TraceStack("The specified outfit does not exist.", stackId, RE::BSScript::IVirtualMachine::Severity::kWarning); - } - } - void RemoveConflictingArmorsFrom(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *, + RE::BSFixedString name, + RE::TESObjectARMO* armor_skse) { + auto armor = (RE::TESObjectARMO*)(armor_skse); + ERROR_AND_RETURN_IF(armor == nullptr, "Cannot remove a None armor from an outfit.", registry, stackId); + auto& service = ArmorAddonOverrideService::GetInstance(); + try { + auto& outfit = service.getOutfit(name.data()); + outfit.armors.erase(armor); + } catch (std::out_of_range) { + registry->TraceStack("The specified outfit does not exist.", stackId, RE::BSScript::IVirtualMachine::Severity::kWarning); + } + } + void RemoveConflictingArmorsFrom(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*, - RE::TESObjectARMO *armor_skse, - RE::BSFixedString name) { - auto armor = (RE::TESObjectARMO *) (armor_skse); - ERROR_AND_RETURN_IF(armor == nullptr, - "A None armor can't conflict with anything in an outfit.", - registry, - stackId); - auto &service = ArmorAddonOverrideService::GetInstance(); - try { - auto &outfit = service.getOutfit(name.data()); - auto &armors = outfit.armors; - std::vector conflicts; - const auto candidateMask = armor->GetSlotMask(); - for (auto it = armors.begin(); it != armors.end(); ++it) { - RE::TESObjectARMO *existing = *it; - if (existing) { - const auto mask = existing->GetSlotMask(); - if ((static_cast(mask) & static_cast(candidateMask)) - != static_cast(RE::BGSBipedObjectForm::FirstPersonFlag::kNone)) - conflicts.push_back(existing); - } - } - for (auto it = conflicts.begin(); it != conflicts.end(); ++it) - armors.erase(*it); - } - catch (std::out_of_range) { - registry->TraceStack("The specified outfit does not exist.", stackId, RE::BSScript::IVirtualMachine::Severity::kError); - return; - } - } - bool RenameOutfit(RE::BSScript::IVirtualMachine *registry, std::uint32_t stackId, RE::StaticFunctionTag *, + RE::TESObjectARMO* armor_skse, + RE::BSFixedString name) { + auto armor = (RE::TESObjectARMO*)(armor_skse); + ERROR_AND_RETURN_IF(armor == nullptr, + "A None armor can't conflict with anything in an outfit.", + registry, + stackId); + auto& service = ArmorAddonOverrideService::GetInstance(); + try { + auto& outfit = service.getOutfit(name.data()); + auto& armors = outfit.armors; + std::vector conflicts; + const auto candidateMask = armor->GetSlotMask(); + for (auto it = armors.begin(); it != armors.end(); ++it) { + RE::TESObjectARMO* existing = *it; + if (existing) { + const auto mask = existing->GetSlotMask(); + if ((static_cast(mask) & static_cast(candidateMask)) + != static_cast(RE::BGSBipedObjectForm::FirstPersonFlag::kNone)) + conflicts.push_back(existing); + } + } + for (auto it = conflicts.begin(); it != conflicts.end(); ++it) + armors.erase(*it); + } catch (std::out_of_range) { + registry->TraceStack("The specified outfit does not exist.", stackId, RE::BSScript::IVirtualMachine::Severity::kError); + return; + } + } + bool RenameOutfit(RE::BSScript::IVirtualMachine* registry, std::uint32_t stackId, RE::StaticFunctionTag*, - RE::BSFixedString name, - RE::BSFixedString changeTo) { - auto &service = ArmorAddonOverrideService::GetInstance(); - try { - service.renameOutfit(name.data(), changeTo.data()); - } - catch (ArmorAddonOverrideService::bad_name) { - registry->TraceStack("The desired name is invalid.", stackId, RE::BSScript::IVirtualMachine::Severity::kError); - return false; - } - catch (ArmorAddonOverrideService::name_conflict) { - registry->TraceStack("The desired name is taken.", stackId, RE::BSScript::IVirtualMachine::Severity::kError); - return false; - } - catch (std::out_of_range) { - registry->TraceStack("The specified outfit does not exist.", stackId, RE::BSScript::IVirtualMachine::Severity::kError); - return false; - } - return true; - } - void SetOutfitFavoriteStatus(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *, + RE::BSFixedString name, + RE::BSFixedString changeTo) { + auto& service = ArmorAddonOverrideService::GetInstance(); + try { + service.renameOutfit(name.data(), changeTo.data()); + } catch (ArmorAddonOverrideService::bad_name) { + registry->TraceStack("The desired name is invalid.", stackId, RE::BSScript::IVirtualMachine::Severity::kError); + return false; + } catch (ArmorAddonOverrideService::name_conflict) { + registry->TraceStack("The desired name is taken.", stackId, RE::BSScript::IVirtualMachine::Severity::kError); + return false; + } catch (std::out_of_range) { + registry->TraceStack("The specified outfit does not exist.", stackId, RE::BSScript::IVirtualMachine::Severity::kError); + return false; + } + return true; + } + void SetOutfitFavoriteStatus(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*, - RE::BSFixedString name, - bool favorite) { - auto &service = ArmorAddonOverrideService::GetInstance(); - service.setFavorite(name.data(), favorite); - } - void SetOutfitPassthroughStatus(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *, + RE::BSFixedString name, + bool favorite) { + auto& service = ArmorAddonOverrideService::GetInstance(); + service.setFavorite(name.data(), favorite); + } + void SetOutfitPassthroughStatus(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*, - RE::BSFixedString name, - bool allowsPassthrough) { - auto &service = ArmorAddonOverrideService::GetInstance(); - service.setOutfitPassthrough(name.data(), allowsPassthrough); - } - void SetOutfitEquipRequiredStatus(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *, + RE::BSFixedString name, + bool allowsPassthrough) { + auto& service = ArmorAddonOverrideService::GetInstance(); + service.setOutfitPassthrough(name.data(), allowsPassthrough); + } + void SetOutfitEquipRequiredStatus(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*, - RE::BSFixedString name, - bool equipRequired) { - auto &service = ArmorAddonOverrideService::GetInstance(); - service.setOutfitEquipRequired(name.data(), equipRequired); - } - bool OutfitExists(RE::BSScript::IVirtualMachine *registry, std::uint32_t stackId, RE::StaticFunctionTag *, + RE::BSFixedString name, + bool equipRequired) { + auto& service = ArmorAddonOverrideService::GetInstance(); + service.setOutfitEquipRequired(name.data(), equipRequired); + } + bool OutfitExists(RE::BSScript::IVirtualMachine* registry, std::uint32_t stackId, RE::StaticFunctionTag*, - RE::BSFixedString name) { - auto &service = ArmorAddonOverrideService::GetInstance(); - return service.hasOutfit(name.data()); - } - void OverwriteOutfit(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *, - RE::BSFixedString name, - std::vector armors - ) { - auto &service = ArmorAddonOverrideService::GetInstance(); - try { - auto &outfit = service.getOrCreateOutfit(name.data()); - outfit.armors.clear(); - auto count = armors.size(); - for (std::uint32_t i = 0; i < count; i++) { - RE::TESObjectARMO *ptr = nullptr; - ptr = armors.at(i); - if (ptr) - outfit.armors.insert(ptr); - } - } - catch (ArmorAddonOverrideService::bad_name) { - registry->TraceStack("Invalid outfit name specified.", stackId, RE::BSScript::IVirtualMachine::Severity::kError); - return; - } - } - void SetEnabled(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *, - bool state) { - auto &service = ArmorAddonOverrideService::GetInstance(); - service.setEnabled(state); - } - void SetSelectedOutfit(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *, - RE::BSFixedString name) { - auto &service = ArmorAddonOverrideService::GetInstance(); - service.setOutfit(name.data(), RE::PlayerCharacter::GetSingleton()); - } - void AddActor(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *, - RE::Actor *target) { - auto &service = ArmorAddonOverrideService::GetInstance(); - service.addActor((RE::Actor *) target); - } - void RemoveActor(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *, - RE::Actor *target) { - auto &service = ArmorAddonOverrideService::GetInstance(); - service.removeActor((RE::Actor *) target); - } - void SetLocationBasedAutoSwitchEnabled(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *, + RE::BSFixedString name) { + auto& service = ArmorAddonOverrideService::GetInstance(); + return service.hasOutfit(name.data()); + } + void OverwriteOutfit(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*, + RE::BSFixedString name, + std::vector armors + ) { + auto& service = ArmorAddonOverrideService::GetInstance(); + try { + auto& outfit = service.getOrCreateOutfit(name.data()); + outfit.armors.clear(); + auto count = armors.size(); + for (std::uint32_t i = 0; i < count; i++) { + RE::TESObjectARMO* ptr = nullptr; + ptr = armors.at(i); + if (ptr) + outfit.armors.insert(ptr); + } + } catch (ArmorAddonOverrideService::bad_name) { + registry->TraceStack("Invalid outfit name specified.", stackId, RE::BSScript::IVirtualMachine::Severity::kError); + return; + } + } + void SetEnabled(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*, + bool state) { + auto& service = ArmorAddonOverrideService::GetInstance(); + service.setEnabled(state); + } + void SetSelectedOutfit(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*, + RE::BSFixedString name) { + auto& service = ArmorAddonOverrideService::GetInstance(); + service.setOutfit(name.data(), RE::PlayerCharacter::GetSingleton()); + } + void AddActor(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*, + RE::Actor* target) { + auto& service = ArmorAddonOverrideService::GetInstance(); + service.addActor((RE::Actor*)target); + } + void RemoveActor(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*, + RE::Actor* target) { + auto& service = ArmorAddonOverrideService::GetInstance(); + service.removeActor((RE::Actor*)target); + } + void SetLocationBasedAutoSwitchEnabled(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*, - bool value) { - ArmorAddonOverrideService::GetInstance().setLocationBasedAutoSwitchEnabled(value); - } - bool GetLocationBasedAutoSwitchEnabled(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *) { - return ArmorAddonOverrideService::GetInstance().locationBasedAutoSwitchEnabled; - } - std::vector GetAutoSwitchLocationArray(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *) { - std::vector result; - for (LocationType i : { - LocationType::World, - LocationType::WorldSnowy, - LocationType::WorldRainy, - LocationType::City, - LocationType::CitySnowy, - LocationType::CityRainy, - LocationType::Town, - LocationType::TownSnowy, - LocationType::TownRainy, - LocationType::Dungeon, - LocationType::DungeonSnowy, - LocationType::DungeonRainy - }) { - result.push_back(std::uint32_t(i)); - } - return result; - } - std::optional 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. - auto &service = ArmorAddonOverrideService::GetInstance(); + bool value) { + ArmorAddonOverrideService::GetInstance().setLocationBasedAutoSwitchEnabled(value); + } + bool GetLocationBasedAutoSwitchEnabled(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*) { + return ArmorAddonOverrideService::GetInstance().locationBasedAutoSwitchEnabled; + } + std::vector GetAutoSwitchLocationArray(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*) { + std::vector result; + for (LocationType i : { + LocationType::World, + LocationType::WorldSnowy, + LocationType::WorldRainy, + LocationType::City, + LocationType::CitySnowy, + LocationType::CityRainy, + LocationType::Town, + LocationType::TownSnowy, + LocationType::TownRainy, + LocationType::Dungeon, + LocationType::DungeonSnowy, + LocationType::DungeonRainy + }) { + result.push_back(std::uint32_t(i)); + } + return result; + } + std::optional 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. + auto& service = ArmorAddonOverrideService::GetInstance(); - // Collect weather information. - WeatherFlags weather_flags; - if (weather) { - weather_flags.snowy = weather->data.flags.any(RE::TESWeather::WeatherDataFlag::kSnow); - weather_flags.rainy = weather->data.flags.any(RE::TESWeather::WeatherDataFlag::kRainy); - } + // Collect weather information. + WeatherFlags weather_flags; + if (weather) { + weather_flags.snowy = weather->data.flags.any(RE::TESWeather::WeatherDataFlag::kSnow); + weather_flags.rainy = weather->data.flags.any(RE::TESWeather::WeatherDataFlag::kRainy); + } - // Collect location keywords - std::unordered_set keywords; - keywords.reserve(20); - while (location) { - std::uint32_t max = location->GetNumKeywords(); - for (std::uint32_t i = 0; i < max; i++) { - RE::BGSKeyword *keyword = location->GetKeywordAt(i).value(); - /* - char message[100]; - LOG(info, "SOS: Location has Keyword %s", keyword->GetFormEditorID()); - sprintf(message, "SOS: Location has keyword %s", keyword->GetFormEditorID()); - RE::DebugNotification(message, nullptr, false); - */ - keywords.emplace(keyword->GetFormEditorID()); - } - location = location->parentLoc; - } - return service.checkLocationType(keywords, weather_flags, RE::PlayerCharacter::GetSingleton()); - } - std::uint32_t IdentifyLocationType(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *, + // Collect location keywords + std::unordered_set keywords; + keywords.reserve(20); + while (location) { + std::uint32_t max = location->GetNumKeywords(); + for (std::uint32_t i = 0; i < max; i++) { + RE::BGSKeyword* keyword = location->GetKeywordAt(i).value(); + /* + char message[100]; + LOG(info, "SOS: Location has Keyword %s", keyword->GetFormEditorID()); + sprintf(message, "SOS: Location has keyword %s", keyword->GetFormEditorID()); + RE::DebugNotification(message, nullptr, false); + */ + keywords.emplace(keyword->GetFormEditorID()); + } + location = location->parentLoc; + } + return service.checkLocationType(keywords, weather_flags, RE::PlayerCharacter::GetSingleton()); + } + std::uint32_t IdentifyLocationType(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*, - RE::BGSLocation *location_skse, - RE::TESWeather *weather_skse) { - // NOTE: Identify the location for Papyrus. In the event no location is identified, we lie to Papyrus and say "World". - // Therefore, Papyrus cannot assume that locations returned have an outfit assigned, at least not for "World". - return static_cast(identifyLocation((RE::BGSLocation *) location_skse, - (RE::TESWeather *) weather_skse) - .value_or(LocationType::World)); - } - void SetOutfitUsingLocation(RE::BSScript::IVirtualMachine *registry, std::uint32_t stackId, RE::StaticFunctionTag *, + RE::BGSLocation* location_skse, + RE::TESWeather* weather_skse) { + // NOTE: Identify the location for Papyrus. In the event no location is identified, we lie to Papyrus and say "World". + // Therefore, Papyrus cannot assume that locations returned have an outfit assigned, at least not for "World". + return static_cast(identifyLocation((RE::BGSLocation*)location_skse, + (RE::TESWeather*)weather_skse) + .value_or(LocationType::World)); + } + void SetOutfitUsingLocation(RE::BSScript::IVirtualMachine* registry, std::uint32_t stackId, RE::StaticFunctionTag*, - RE::BGSLocation *location_skse, - RE::TESWeather *weather_skse) { - // NOTE: Location can be NULL. - auto &service = ArmorAddonOverrideService::GetInstance(); + RE::BGSLocation* location_skse, + RE::TESWeather* weather_skse) { + // NOTE: Location can be NULL. + auto& service = ArmorAddonOverrideService::GetInstance(); - if (service.locationBasedAutoSwitchEnabled) { - auto location = identifyLocation((RE::BGSLocation *) location_skse, (RE::TESWeather *) weather_skse); - // Debug notifications for location classification. - /* - const char* locationName = locationTypeStrings[static_cast(location)]; - char message[100]; - sprintf_s(message, "SOS: This location is a %s.", locationName); - RE::DebugNotification(message, nullptr, false); - */ - if (location.has_value()) { - service.setOutfitUsingLocation(location.value(), RE::PlayerCharacter::GetSingleton()); - } - } + if (service.locationBasedAutoSwitchEnabled) { + auto location = identifyLocation((RE::BGSLocation*)location_skse, (RE::TESWeather*)weather_skse); + // Debug notifications for location classification. + /* + const char* locationName = locationTypeStrings[static_cast(location)]; + char message[100]; + sprintf_s(message, "SOS: This location is a %s.", locationName); + RE::DebugNotification(message, nullptr, false); + */ + if (location.has_value()) { + service.setOutfitUsingLocation(location.value(), RE::PlayerCharacter::GetSingleton()); + } + } - } - void SetLocationOutfit(RE::BSScript::IVirtualMachine *registry, std::uint32_t stackId, RE::StaticFunctionTag *, + } + void SetLocationOutfit(RE::BSScript::IVirtualMachine* registry, std::uint32_t stackId, RE::StaticFunctionTag*, - std::uint32_t location, - RE::BSFixedString name) { - if (strcmp(name.data(), "") == 0) { - // Location outfit assignment is never allowed to be empty string. Use unset instead. - return; - } - return ArmorAddonOverrideService::GetInstance() - .setLocationOutfit(LocationType(location), name.data(), RE::PlayerCharacter::GetSingleton()); - } - void UnsetLocationOutfit(RE::BSScript::IVirtualMachine *registry, std::uint32_t stackId, RE::StaticFunctionTag *, + std::uint32_t location, + RE::BSFixedString name) { + if (strcmp(name.data(), "") == 0) { + // Location outfit assignment is never allowed to be empty string. Use unset instead. + return; + } + return ArmorAddonOverrideService::GetInstance() + .setLocationOutfit(LocationType(location), name.data(), RE::PlayerCharacter::GetSingleton()); + } + void UnsetLocationOutfit(RE::BSScript::IVirtualMachine* registry, std::uint32_t stackId, RE::StaticFunctionTag*, - std::uint32_t location) { - return ArmorAddonOverrideService::GetInstance() - .unsetLocationOutfit(LocationType(location), RE::PlayerCharacter::GetSingleton()); - } - RE::BSFixedString GetLocationOutfit(RE::BSScript::IVirtualMachine *registry, - std::uint32_t stackId, - RE::StaticFunctionTag *, + std::uint32_t location) { + return ArmorAddonOverrideService::GetInstance() + .unsetLocationOutfit(LocationType(location), RE::PlayerCharacter::GetSingleton()); + } + RE::BSFixedString GetLocationOutfit(RE::BSScript::IVirtualMachine* registry, + std::uint32_t stackId, + RE::StaticFunctionTag*, - std::uint32_t location) { - auto outfit = ArmorAddonOverrideService::GetInstance() - .getLocationOutfit(LocationType(location), RE::PlayerCharacter::GetSingleton()); - if (outfit.has_value()) { - return RE::BSFixedString(outfit.value().c_str()); - } else { - // Empty string means "no outfit assigned" for this location type. - return RE::BSFixedString(""); - } - } - bool ExportSettings(RE::BSScript::IVirtualMachine *registry, std::uint32_t stackId, RE::StaticFunctionTag *) { - std::string outputFile = GetRuntimeDirectory() + "Data\\SKSE\\Plugins\\OutfitSystemData.json"; - auto &service = ArmorAddonOverrideService::GetInstance(); - proto::OutfitSystem data = service.save(); - std::string output; - google::protobuf::util::JsonPrintOptions options; - options.add_whitespace = true; - google::protobuf::util::MessageToJsonString(data, &output, options); - std::ofstream file(outputFile); - if (file) { - file << output; - } else { - RE::DebugNotification("Failed to open config for writing", nullptr, false); - return false; - } - if (file.good()) { - std::string message = "Wrote JSON config to " + outputFile; - RE::DebugNotification(message.c_str(), nullptr, false); - return true; - } else { - RE::DebugNotification("Failed to write config", nullptr, false); - return false; - } - } - bool ImportSettings(RE::BSScript::IVirtualMachine *registry, std::uint32_t stackId, RE::StaticFunctionTag *) { - std::string inputFile = GetRuntimeDirectory() + "Data\\SKSE\\Plugins\\OutfitSystemData.json"; - std::ifstream file(inputFile); - if (!file) { - RE::DebugNotification("Failed to open config for reading", nullptr, false); - return false; - } - std::stringstream input; - input << file.rdbuf(); - if (!file.good()) { - RE::DebugNotification("Failed to read config data", nullptr, false); - return false; - } - proto::OutfitSystem data; - auto status = google::protobuf::util::JsonStringToMessage(input.str(), &data); - if (!status.ok()) { - RE::DebugNotification("Failed to parse config data. Invalid syntax.", nullptr, false); - return false; - } - auto &service = ArmorAddonOverrideService::GetInstance(); - service.load(SKSE::GetSerializationInterface(), data); - std::string message = "Read JSON config from " + inputFile; - RE::DebugNotification(message.c_str(), nullptr, false); - return true; - } + std::uint32_t location) { + auto outfit = ArmorAddonOverrideService::GetInstance() + .getLocationOutfit(LocationType(location), RE::PlayerCharacter::GetSingleton()); + if (outfit.has_value()) { + return RE::BSFixedString(outfit.value().c_str()); + } else { + // Empty string means "no outfit assigned" for this location type. + return RE::BSFixedString(""); + } + } + bool ExportSettings(RE::BSScript::IVirtualMachine* registry, std::uint32_t stackId, RE::StaticFunctionTag*) { + std::string outputFile = GetRuntimeDirectory() + "Data\\SKSE\\Plugins\\OutfitSystemData.json"; + auto& service = ArmorAddonOverrideService::GetInstance(); + proto::OutfitSystem data = service.save(); + std::string output; + google::protobuf::util::JsonPrintOptions options; + options.add_whitespace = true; + google::protobuf::util::MessageToJsonString(data, &output, options); + std::ofstream file(outputFile); + if (file) { + file << output; + } else { + RE::DebugNotification("Failed to open config for writing", nullptr, false); + return false; + } + if (file.good()) { + std::string message = "Wrote JSON config to " + outputFile; + RE::DebugNotification(message.c_str(), nullptr, false); + return true; + } else { + RE::DebugNotification("Failed to write config", nullptr, false); + return false; + } + } + bool ImportSettings(RE::BSScript::IVirtualMachine* registry, std::uint32_t stackId, RE::StaticFunctionTag*) { + std::string inputFile = GetRuntimeDirectory() + "Data\\SKSE\\Plugins\\OutfitSystemData.json"; + std::ifstream file(inputFile); + if (!file) { + RE::DebugNotification("Failed to open config for reading", nullptr, false); + return false; + } + std::stringstream input; + input << file.rdbuf(); + if (!file.good()) { + RE::DebugNotification("Failed to read config data", nullptr, false); + return false; + } + proto::OutfitSystem data; + auto status = google::protobuf::util::JsonStringToMessage(input.str(), &data); + if (!status.ok()) { + RE::DebugNotification("Failed to parse config data. Invalid syntax.", nullptr, false); + return false; + } + auto& service = ArmorAddonOverrideService::GetInstance(); + service.load(SKSE::GetSerializationInterface(), data); + std::string message = "Read JSON config from " + inputFile; + RE::DebugNotification(message.c_str(), nullptr, false); + return true; + } } -bool OutfitSystem::RegisterPapyrus(RE::BSScript::IVirtualMachine *registry) { - registry->RegisterFunction("GetOutfitNameMaxLength", - "SkyrimOutfitSystemNativeFuncs", - GetOutfitNameMaxLength); - registry->RegisterFunction( - "GetOutfitNameMaxLength", - "SkyrimOutfitSystemNativeFuncs", - GetOutfitNameMaxLength, - true - ); - registry->RegisterFunction( - "GetCarriedArmor", - "SkyrimOutfitSystemNativeFuncs", - GetCarriedArmor - ); - registry->RegisterFunction( - "GetWornItems", - "SkyrimOutfitSystemNativeFuncs", - GetWornItems - ); - registry->RegisterFunction( - "RefreshArmorFor", - "SkyrimOutfitSystemNativeFuncs", - RefreshArmorFor - ); - registry->RegisterFunction( - "ActorNearPC", - "SkyrimOutfitSystemNativeFuncs", - ActorsNearPC - ); - // - { // armor form search utils - registry->RegisterFunction( - "PrepArmorSearch", - "SkyrimOutfitSystemNativeFuncs", - ArmorFormSearchUtils::Prep - ); - registry->RegisterFunction( - "GetArmorSearchResultForms", - "SkyrimOutfitSystemNativeFuncs", - ArmorFormSearchUtils::GetForms - ); - registry->RegisterFunction( - "GetArmorSearchResultNames", - "SkyrimOutfitSystemNativeFuncs", - ArmorFormSearchUtils::GetNames - ); - registry->RegisterFunction( - "ClearArmorSearch", - "SkyrimOutfitSystemNativeFuncs", - ArmorFormSearchUtils::Clear - ); - } - { // body slot data - registry->RegisterFunction( - "PrepOutfitBodySlotListing", - "SkyrimOutfitSystemNativeFuncs", - BodySlotListing::Prep - ); - registry->RegisterFunction( - "GetOutfitBodySlotListingArmorForms", - "SkyrimOutfitSystemNativeFuncs", - BodySlotListing::GetArmorForms - ); - registry->RegisterFunction( - "GetOutfitBodySlotListingArmorNames", - "SkyrimOutfitSystemNativeFuncs", - BodySlotListing::GetArmorNames - ); - registry->RegisterFunction( - "GetOutfitBodySlotListingSlotIndices", - "SkyrimOutfitSystemNativeFuncs", - BodySlotListing::GetSlotIndices - ); - registry->RegisterFunction( - "ClearOutfitBodySlotListing", - "SkyrimOutfitSystemNativeFuncs", - BodySlotListing::Clear - ); - } - { // string sorts - registry->RegisterFunction( - "NaturalSort_ASCII", - "SkyrimOutfitSystemNativeFuncs", - StringSorts::NaturalSort_ASCII, - true - ); - registry->RegisterFunction( - "NaturalSortPairArmor_ASCII", - "SkyrimOutfitSystemNativeFuncs", - StringSorts::NaturalSortPair_ASCII, - true - ); - } - { // Utility - registry->RegisterFunction( - "HexToInt32", - "SkyrimOutfitSystemNativeFuncs", - Utility::HexToInt32, - true - ); - registry->RegisterFunction( - "ToHex", - "SkyrimOutfitSystemNativeFuncs", - Utility::ToHex, - true - ); - } - // - registry->RegisterFunction( - "AddArmorToOutfit", - "SkyrimOutfitSystemNativeFuncs", - AddArmorToOutfit - ); - registry->RegisterFunction( - "ArmorConflictsWithOutfit", - "SkyrimOutfitSystemNativeFuncs", - ArmorConflictsWithOutfit - ); - registry->RegisterFunction( - "CreateOutfit", - "SkyrimOutfitSystemNativeFuncs", - CreateOutfit - ); - registry->RegisterFunction( - "DeleteOutfit", - "SkyrimOutfitSystemNativeFuncs", - DeleteOutfit - ); - registry->RegisterFunction( - "GetOutfitContents", - "SkyrimOutfitSystemNativeFuncs", - GetOutfitContents - ); - registry->RegisterFunction( - "GetOutfitFavoriteStatus", - "SkyrimOutfitSystemNativeFuncs", - GetOutfitFavoriteStatus - ); - registry->RegisterFunction( - "GetOutfitPassthroughStatus", - "SkyrimOutfitSystemNativeFuncs", - GetOutfitPassthroughStatus - ); - registry->RegisterFunction( - "GetOutfitEquipRequiredStatus", - "SkyrimOutfitSystemNativeFuncs", - GetOutfitEquipRequiredStatus - ); - registry->RegisterFunction( - "SetOutfitFavoriteStatus", - "SkyrimOutfitSystemNativeFuncs", - SetOutfitFavoriteStatus - ); - registry->RegisterFunction( - "SetOutfitPassthroughStatus", - "SkyrimOutfitSystemNativeFuncs", - SetOutfitPassthroughStatus - ); - registry->RegisterFunction( - "SetOutfitEquipRequiredStatus", - "SkyrimOutfitSystemNativeFuncs", - SetOutfitEquipRequiredStatus - ); - registry->RegisterFunction( - "IsEnabled", - "SkyrimOutfitSystemNativeFuncs", - IsEnabled - ); - registry->RegisterFunction( - "GetSelectedOutfit", - "SkyrimOutfitSystemNativeFuncs", - GetSelectedOutfit - ); - registry->RegisterFunction( - "ListOutfits", - "SkyrimOutfitSystemNativeFuncs", - ListOutfits - ); - registry->RegisterFunction( - "RemoveArmorFromOutfit", - "SkyrimOutfitSystemNativeFuncs", - RemoveArmorFromOutfit - ); - registry->RegisterFunction( - "RemoveConflictingArmorsFrom", - "SkyrimOutfitSystemNativeFuncs", - RemoveConflictingArmorsFrom - ); - registry->RegisterFunction( - "RenameOutfit", - "SkyrimOutfitSystemNativeFuncs", - RenameOutfit - ); - registry->RegisterFunction( - "OutfitExists", - "SkyrimOutfitSystemNativeFuncs", - OutfitExists - ); - registry->RegisterFunction( - "OverwriteOutfit", - "SkyrimOutfitSystemNativeFuncs", - OverwriteOutfit - ); - registry->RegisterFunction( - "SetEnabled", - "SkyrimOutfitSystemNativeFuncs", - SetEnabled - ); - registry->RegisterFunction( - "SetSelectedOutfit", - "SkyrimOutfitSystemNativeFuncs", - SetSelectedOutfit - ); - registry->RegisterFunction( - "AddActor", - "SkyrimOutfitSystemNativeFuncs", - AddActor - ); - registry->RegisterFunction( - "RemoveActor", - "SkyrimOutfitSystemNativeFuncs", - RemoveActor - ); - registry->RegisterFunction( - "SetLocationBasedAutoSwitchEnabled", - "SkyrimOutfitSystemNativeFuncs", - SetLocationBasedAutoSwitchEnabled - ); - registry->RegisterFunction( - "GetLocationBasedAutoSwitchEnabled", - "SkyrimOutfitSystemNativeFuncs", - GetLocationBasedAutoSwitchEnabled - ); - registry->RegisterFunction( - "GetAutoSwitchLocationArray", - "SkyrimOutfitSystemNativeFuncs", - GetAutoSwitchLocationArray - ); - registry->RegisterFunction( - "IdentifyLocationType", - "SkyrimOutfitSystemNativeFuncs", - IdentifyLocationType - ); - registry->RegisterFunction( - "SetOutfitUsingLocation", - "SkyrimOutfitSystemNativeFuncs", - SetOutfitUsingLocation - ); - registry->RegisterFunction( - "SetLocationOutfit", - "SkyrimOutfitSystemNativeFuncs", - SetLocationOutfit - ); - registry->RegisterFunction( - "UnsetLocationOutfit", - "SkyrimOutfitSystemNativeFuncs", - UnsetLocationOutfit - ); - registry->RegisterFunction( - "GetLocationOutfit", - "SkyrimOutfitSystemNativeFuncs", - GetLocationOutfit - ); - registry->RegisterFunction( - "ExportSettings", - "SkyrimOutfitSystemNativeFuncs", - ExportSettings - ); - registry->RegisterFunction( - "ImportSettings", - "SkyrimOutfitSystemNativeFuncs", - ImportSettings - ); +bool OutfitSystem::RegisterPapyrus(RE::BSScript::IVirtualMachine* registry) { + registry->RegisterFunction("GetOutfitNameMaxLength", + "SkyrimOutfitSystemNativeFuncs", + GetOutfitNameMaxLength); + registry->RegisterFunction( + "GetOutfitNameMaxLength", + "SkyrimOutfitSystemNativeFuncs", + GetOutfitNameMaxLength, + true + ); + registry->RegisterFunction( + "GetCarriedArmor", + "SkyrimOutfitSystemNativeFuncs", + GetCarriedArmor + ); + registry->RegisterFunction( + "GetWornItems", + "SkyrimOutfitSystemNativeFuncs", + GetWornItems + ); + registry->RegisterFunction( + "RefreshArmorFor", + "SkyrimOutfitSystemNativeFuncs", + RefreshArmorFor + ); + registry->RegisterFunction( + "ActorNearPC", + "SkyrimOutfitSystemNativeFuncs", + ActorsNearPC + ); + // + { // armor form search utils + registry->RegisterFunction( + "PrepArmorSearch", + "SkyrimOutfitSystemNativeFuncs", + ArmorFormSearchUtils::Prep + ); + registry->RegisterFunction( + "GetArmorSearchResultForms", + "SkyrimOutfitSystemNativeFuncs", + ArmorFormSearchUtils::GetForms + ); + registry->RegisterFunction( + "GetArmorSearchResultNames", + "SkyrimOutfitSystemNativeFuncs", + ArmorFormSearchUtils::GetNames + ); + registry->RegisterFunction( + "ClearArmorSearch", + "SkyrimOutfitSystemNativeFuncs", + ArmorFormSearchUtils::Clear + ); + } + { // body slot data + registry->RegisterFunction( + "PrepOutfitBodySlotListing", + "SkyrimOutfitSystemNativeFuncs", + BodySlotListing::Prep + ); + registry->RegisterFunction( + "GetOutfitBodySlotListingArmorForms", + "SkyrimOutfitSystemNativeFuncs", + BodySlotListing::GetArmorForms + ); + registry->RegisterFunction( + "GetOutfitBodySlotListingArmorNames", + "SkyrimOutfitSystemNativeFuncs", + BodySlotListing::GetArmorNames + ); + registry->RegisterFunction( + "GetOutfitBodySlotListingSlotIndices", + "SkyrimOutfitSystemNativeFuncs", + BodySlotListing::GetSlotIndices + ); + registry->RegisterFunction( + "ClearOutfitBodySlotListing", + "SkyrimOutfitSystemNativeFuncs", + BodySlotListing::Clear + ); + } + { // string sorts + registry->RegisterFunction( + "NaturalSort_ASCII", + "SkyrimOutfitSystemNativeFuncs", + StringSorts::NaturalSort_ASCII, + true + ); + registry->RegisterFunction( + "NaturalSortPairArmor_ASCII", + "SkyrimOutfitSystemNativeFuncs", + StringSorts::NaturalSortPair_ASCII, + true + ); + } + { // Utility + registry->RegisterFunction( + "HexToInt32", + "SkyrimOutfitSystemNativeFuncs", + Utility::HexToInt32, + true + ); + registry->RegisterFunction( + "ToHex", + "SkyrimOutfitSystemNativeFuncs", + Utility::ToHex, + true + ); + } + // + registry->RegisterFunction( + "AddArmorToOutfit", + "SkyrimOutfitSystemNativeFuncs", + AddArmorToOutfit + ); + registry->RegisterFunction( + "ArmorConflictsWithOutfit", + "SkyrimOutfitSystemNativeFuncs", + ArmorConflictsWithOutfit + ); + registry->RegisterFunction( + "CreateOutfit", + "SkyrimOutfitSystemNativeFuncs", + CreateOutfit + ); + registry->RegisterFunction( + "DeleteOutfit", + "SkyrimOutfitSystemNativeFuncs", + DeleteOutfit + ); + registry->RegisterFunction( + "GetOutfitContents", + "SkyrimOutfitSystemNativeFuncs", + GetOutfitContents + ); + registry->RegisterFunction( + "GetOutfitFavoriteStatus", + "SkyrimOutfitSystemNativeFuncs", + GetOutfitFavoriteStatus + ); + registry->RegisterFunction( + "GetOutfitPassthroughStatus", + "SkyrimOutfitSystemNativeFuncs", + GetOutfitPassthroughStatus + ); + registry->RegisterFunction( + "GetOutfitEquipRequiredStatus", + "SkyrimOutfitSystemNativeFuncs", + GetOutfitEquipRequiredStatus + ); + registry->RegisterFunction( + "SetOutfitFavoriteStatus", + "SkyrimOutfitSystemNativeFuncs", + SetOutfitFavoriteStatus + ); + registry->RegisterFunction( + "SetOutfitPassthroughStatus", + "SkyrimOutfitSystemNativeFuncs", + SetOutfitPassthroughStatus + ); + registry->RegisterFunction( + "SetOutfitEquipRequiredStatus", + "SkyrimOutfitSystemNativeFuncs", + SetOutfitEquipRequiredStatus + ); + registry->RegisterFunction( + "IsEnabled", + "SkyrimOutfitSystemNativeFuncs", + IsEnabled + ); + registry->RegisterFunction( + "GetSelectedOutfit", + "SkyrimOutfitSystemNativeFuncs", + GetSelectedOutfit + ); + registry->RegisterFunction( + "ListOutfits", + "SkyrimOutfitSystemNativeFuncs", + ListOutfits + ); + registry->RegisterFunction( + "RemoveArmorFromOutfit", + "SkyrimOutfitSystemNativeFuncs", + RemoveArmorFromOutfit + ); + registry->RegisterFunction( + "RemoveConflictingArmorsFrom", + "SkyrimOutfitSystemNativeFuncs", + RemoveConflictingArmorsFrom + ); + registry->RegisterFunction( + "RenameOutfit", + "SkyrimOutfitSystemNativeFuncs", + RenameOutfit + ); + registry->RegisterFunction( + "OutfitExists", + "SkyrimOutfitSystemNativeFuncs", + OutfitExists + ); + registry->RegisterFunction( + "OverwriteOutfit", + "SkyrimOutfitSystemNativeFuncs", + OverwriteOutfit + ); + registry->RegisterFunction( + "SetEnabled", + "SkyrimOutfitSystemNativeFuncs", + SetEnabled + ); + registry->RegisterFunction( + "SetSelectedOutfit", + "SkyrimOutfitSystemNativeFuncs", + SetSelectedOutfit + ); + registry->RegisterFunction( + "AddActor", + "SkyrimOutfitSystemNativeFuncs", + AddActor + ); + registry->RegisterFunction( + "RemoveActor", + "SkyrimOutfitSystemNativeFuncs", + RemoveActor + ); + registry->RegisterFunction( + "SetLocationBasedAutoSwitchEnabled", + "SkyrimOutfitSystemNativeFuncs", + SetLocationBasedAutoSwitchEnabled + ); + registry->RegisterFunction( + "GetLocationBasedAutoSwitchEnabled", + "SkyrimOutfitSystemNativeFuncs", + GetLocationBasedAutoSwitchEnabled + ); + registry->RegisterFunction( + "GetAutoSwitchLocationArray", + "SkyrimOutfitSystemNativeFuncs", + GetAutoSwitchLocationArray + ); + registry->RegisterFunction( + "IdentifyLocationType", + "SkyrimOutfitSystemNativeFuncs", + IdentifyLocationType + ); + registry->RegisterFunction( + "SetOutfitUsingLocation", + "SkyrimOutfitSystemNativeFuncs", + SetOutfitUsingLocation + ); + registry->RegisterFunction( + "SetLocationOutfit", + "SkyrimOutfitSystemNativeFuncs", + SetLocationOutfit + ); + registry->RegisterFunction( + "UnsetLocationOutfit", + "SkyrimOutfitSystemNativeFuncs", + UnsetLocationOutfit + ); + registry->RegisterFunction( + "GetLocationOutfit", + "SkyrimOutfitSystemNativeFuncs", + GetLocationOutfit + ); + registry->RegisterFunction( + "ExportSettings", + "SkyrimOutfitSystemNativeFuncs", + ExportSettings + ); + registry->RegisterFunction( + "ImportSettings", + "SkyrimOutfitSystemNativeFuncs", + ImportSettings + ); - return true; + return true; } \ No newline at end of file diff --git a/src/PlayerSkinning.cpp b/src/PlayerSkinning.cpp index f1787ec..2b77bf0 100644 --- a/src/PlayerSkinning.cpp +++ b/src/PlayerSkinning.cpp @@ -3,517 +3,526 @@ #include namespace OutfitSystem { - SKSE::Trampoline *g_localTrampoline = nullptr; - SKSE::Trampoline *g_branchTrampoline = nullptr; + SKSE::Trampoline* g_localTrampoline = nullptr; + SKSE::Trampoline* g_branchTrampoline = nullptr; - bool ShouldOverrideSkinning(RE::TESObjectREFR *target) { - if (!ArmorAddonOverrideService::GetInstance().shouldOverride((RE::Actor *) target)) - return false; - return target == RE::PlayerCharacter::GetSingleton(); - } + bool ShouldOverrideSkinning(RE::TESObjectREFR* target) { + if (!ArmorAddonOverrideService::GetInstance().shouldOverride( + (RE::Actor*)target)) + return false; + return target == RE::PlayerCharacter::GetSingleton(); + } - class EquippedArmorVisitor : public RE::InventoryChanges::IItemChangeVisitor { - // - // If the player has a shield equipped, and if we're not overriding that - // shield, then we need to grab the equipped shield's worn-flags. - // - public: - virtual ReturnType Visit(RE::InventoryEntryData *data) override { - auto form = data->object; - if (form && form->formType == RE::FormType::Armor) { - auto armor = reinterpret_cast(form); - equipped.emplace(armor); - } - return ReturnType::kContinue; // Return true to "continue visiting". - }; + class EquippedArmorVisitor : public RE::InventoryChanges::IItemChangeVisitor { + // + // If the player has a shield equipped, and if we're not overriding that + // shield, then we need to grab the equipped shield's worn-flags. + // + public: + virtual ReturnType Visit(RE::InventoryEntryData* data) override { + auto form = data->object; + if (form && form->formType == RE::FormType::Armor) { + auto armor = reinterpret_cast(form); + equipped.emplace(armor); + } + return ReturnType::kContinue; // Return true to "continue visiting". + }; - std::unordered_set equipped; - }; + std::unordered_set equipped; + }; - REL::ID TESObjectARMO_ApplyArmorAddon(17792); // 0x00228AD0 in 1.5.73 + REL::ID TESObjectARMO_ApplyArmorAddon(17792); // 0x00228AD0 in 1.5.73 - namespace DontVanillaSkinPlayer { - bool _stdcall 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(info, "ShouldOverride: Failed to cast target to Actor."); - return true; - } - auto inventory = target->GetInventoryChanges(); - EquippedArmorVisitor visitor; - if (inventory) { - inventory->ExecuteVisitorOnWorn(&visitor); - } else { - LOG(info, "ShouldOverride: Unable to get target inventory."); - return true; - } - // Block the item (return true) if the item isn't in the display set. - return outfit.computeDisplaySet(visitor.equipped).count(armor) == 0; - } + namespace DontVanillaSkinPlayer { + bool _stdcall 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(info, "ShouldOverride: Failed to cast target to Actor."); + return true; + } + auto inventory = target->GetInventoryChanges(); + EquippedArmorVisitor visitor; + if (inventory) { + inventory->ExecuteVisitorOnWorn(&visitor); + } else { + LOG(info, "ShouldOverride: Unable to get target inventory."); + return true; + } + // Block the item (return true) if the item isn't in the display set. + return outfit.computeDisplaySet(visitor.equipped).count(armor) == 0; + } - REL::ID DontVanillaSkinPlayer_Hook_ID(24736); - std::uintptr_t DontVanillaSkinPlayer_Hook(DontVanillaSkinPlayer_Hook_ID.address() + 0x302); // 0x00364652 in 1.5.73 + REL::ID DontVanillaSkinPlayer_Hook_ID(24736); + std::uintptr_t DontVanillaSkinPlayer_Hook( + DontVanillaSkinPlayer_Hook_ID.address() + 0x302); // 0x00364652 in 1.5.73 - void Apply() { - LOG(info, "Patching vanilla player skinning"); - LOG(info, - "TESObjectARMO_ApplyArmorAddon = {:x}", - TESObjectARMO_ApplyArmorAddon.address() - REL::Module::get().base()); - LOG(info, "DontVanillaSkinPlayer_Hook = {:x}", DontVanillaSkinPlayer_Hook - REL::Module::get().base()); - { - struct DontVanillaSkinPlayer_Code : Xbyak::CodeGenerator { - DontVanillaSkinPlayer_Code() { - Xbyak::Label j_Out; - Xbyak::Label f_ApplyArmorAddon; - Xbyak::Label f_ShouldOverride; + void Apply() { + LOG(info, "Patching vanilla player skinning"); + LOG(info, "TESObjectARMO_ApplyArmorAddon = {:x}", + TESObjectARMO_ApplyArmorAddon.address() - REL::Module::get().base()); + LOG(info, "DontVanillaSkinPlayer_Hook = {:x}", + DontVanillaSkinPlayer_Hook - REL::Module::get().base()); + { + struct DontVanillaSkinPlayer_Code : Xbyak::CodeGenerator { + DontVanillaSkinPlayer_Code() { + Xbyak::Label j_Out; + Xbyak::Label f_ApplyArmorAddon; + Xbyak::Label f_ShouldOverride; - // armor in rcx, target in r13 - push(rcx); - push(rdx); - push(r9); - push(r8); - mov(rdx, r13); - sub(rsp, 0x40); - call(ptr[rip + f_ShouldOverride]); - add(rsp, 0x40); - pop(r8); - pop(r9); - pop(rdx); - pop(rcx); - test(al, al); - jnz(j_Out); - call(ptr[rip + f_ApplyArmorAddon]); - L(j_Out); - jmp(ptr[rip]); - dq(DontVanillaSkinPlayer_Hook + 0x5); + // armor in rcx, target in r13 + push(rcx); + push(rdx); + push(r9); + push(r8); + mov(rdx, r13); + sub(rsp, 0x40); + call(ptr[rip + f_ShouldOverride]); + add(rsp, 0x40); + pop(r8); + pop(r9); + pop(rdx); + pop(rcx); + test(al, al); + jnz(j_Out); + call(ptr[rip + f_ApplyArmorAddon]); + L(j_Out); + jmp(ptr[rip]); + dq(DontVanillaSkinPlayer_Hook + 0x5); - L(f_ApplyArmorAddon); - dq(TESObjectARMO_ApplyArmorAddon.address()); + L(f_ApplyArmorAddon); + dq(TESObjectARMO_ApplyArmorAddon.address()); - L(f_ShouldOverride); - dq(uintptr_t(ShouldOverride)); - } - }; - DontVanillaSkinPlayer_Code gen; - void *code = g_localTrampoline->allocate(gen); + L(f_ShouldOverride); + dq(uintptr_t(ShouldOverride)); + } + }; + DontVanillaSkinPlayer_Code gen; + void* code = g_localTrampoline->allocate(gen); - LOG(info, - "AVI: Patching vanilla player skinning at addr = {:x}. base = {:x}", - DontVanillaSkinPlayer_Hook, - REL::Module::get().base()); - g_branchTrampoline->write_branch<5>(DontVanillaSkinPlayer_Hook, code); - } - LOG(info, "Done"); - } - } + LOG(info, + "AVI: Patching vanilla player skinning at addr = {:x}. base = {:x}", + DontVanillaSkinPlayer_Hook, REL::Module::get().base()); + g_branchTrampoline->write_branch<5>(DontVanillaSkinPlayer_Hook, code); + } + LOG(info, "Done"); + } + } // namespace DontVanillaSkinPlayer - namespace ShimWornFlags { - std::uint32_t OverrideWornFlags(RE::InventoryChanges *inventory, RE::TESObjectREFR *target) { - std::uint32_t mask = 0; - // - auto &svc = ArmorAddonOverrideService::GetInstance(); - auto &outfit = svc.currentOutfit((RE::Actor *) target); - EquippedArmorVisitor visitor; - inventory->ExecuteVisitorOnWorn(&visitor); - auto displaySet = outfit.computeDisplaySet(visitor.equipped); - for (auto &armor : displaySet) { - mask |= static_cast(armor->GetSlotMask()); - } - return mask; - } + namespace ShimWornFlags { + std::uint32_t OverrideWornFlags(RE::InventoryChanges* inventory, + RE::TESObjectREFR* target) { + std::uint32_t mask = 0; + // + auto& svc = ArmorAddonOverrideService::GetInstance(); + auto& outfit = svc.currentOutfit((RE::Actor*)target); + EquippedArmorVisitor visitor; + inventory->ExecuteVisitorOnWorn(&visitor); + auto displaySet = outfit.computeDisplaySet(visitor.equipped); + for (auto& armor : displaySet) { + mask |= static_cast(armor->GetSlotMask()); + } + return mask; + } - REL::ID ShimWornFlags_Hook_ID(24724); - std::uintptr_t ShimWornFlags_Hook(ShimWornFlags_Hook_ID.address() + 0x80); // 0x00362F0C in 1.5.73 + REL::ID ShimWornFlags_Hook_ID(24724); + std::uintptr_t ShimWornFlags_Hook(ShimWornFlags_Hook_ID.address() + + 0x80); // 0x00362F0C in 1.5.73 - REL::ID InventoryChanges_GetWornMask(16044); // 0x001D9040 in 1.5.73 + REL::ID InventoryChanges_GetWornMask(16044); // 0x001D9040 in 1.5.73 - void Apply() { - LOG(info, "Patching shim worn flags"); - LOG(info, "ShimWornFlags_Hook = {:x}", ShimWornFlags_Hook - REL::Module::get().base()); - LOG(info, - "InventoryChanges_GetWornMask = {:x}", - InventoryChanges_GetWornMask.address() - REL::Module::get().base()); - { - struct ShimWornFlags_Code : Xbyak::CodeGenerator { - ShimWornFlags_Code() { - Xbyak::Label j_SuppressVanilla; - Xbyak::Label j_Out; - Xbyak::Label f_ShouldOverrideSkinning; - Xbyak::Label f_GetWornMask; - Xbyak::Label f_OverrideWornFlags; + void Apply() { + LOG(info, "Patching shim worn flags"); + LOG(info, "ShimWornFlags_Hook = {:x}", + ShimWornFlags_Hook - REL::Module::get().base()); + LOG(info, "InventoryChanges_GetWornMask = {:x}", + InventoryChanges_GetWornMask.address() - REL::Module::get().base()); + { + struct ShimWornFlags_Code : Xbyak::CodeGenerator { + ShimWornFlags_Code() { + Xbyak::Label j_SuppressVanilla; + Xbyak::Label j_Out; + Xbyak::Label f_ShouldOverrideSkinning; + Xbyak::Label f_GetWornMask; + Xbyak::Label f_OverrideWornFlags; - // target in rsi - push(rcx); - mov(rcx, rsi); - sub(rsp, 0x20); - call(ptr[rip + f_ShouldOverrideSkinning]); - add(rsp, 0x20); - pop(rcx); - test(al, al); - jnz(j_SuppressVanilla); - call(ptr[rip + f_GetWornMask]); - jmp(j_Out); + // target in rsi + push(rcx); + mov(rcx, rsi); + sub(rsp, 0x20); + call(ptr[rip + f_ShouldOverrideSkinning]); + add(rsp, 0x20); + pop(rcx); + test(al, al); + jnz(j_SuppressVanilla); + call(ptr[rip + f_GetWornMask]); + jmp(j_Out); - L(j_SuppressVanilla); - push(rdx); - mov(rdx, rsi); - sub(rsp, 0x20); - call(ptr[rip + f_OverrideWornFlags]); - add(rsp, 0x20); - pop(rdx); + L(j_SuppressVanilla); + push(rdx); + mov(rdx, rsi); + sub(rsp, 0x20); + call(ptr[rip + f_OverrideWornFlags]); + add(rsp, 0x20); + pop(rdx); - L(j_Out); - jmp(ptr[rip]); - dq(ShimWornFlags_Hook + 0x5); + L(j_Out); + jmp(ptr[rip]); + dq(ShimWornFlags_Hook + 0x5); - L(f_ShouldOverrideSkinning); - dq(uintptr_t(ShouldOverrideSkinning)); + L(f_ShouldOverrideSkinning); + dq(uintptr_t(ShouldOverrideSkinning)); - L(f_GetWornMask); - dq(InventoryChanges_GetWornMask.address()); + L(f_GetWornMask); + dq(InventoryChanges_GetWornMask.address()); - L(f_OverrideWornFlags); - dq(uintptr_t(OverrideWornFlags)); - } - }; - ShimWornFlags_Code gen; - void *code = g_localTrampoline->allocate(gen); + L(f_OverrideWornFlags); + dq(uintptr_t(OverrideWornFlags)); + } + }; + ShimWornFlags_Code gen; + void* code = g_localTrampoline->allocate(gen); - LOG(info, - "AVI: Patching shim worn flags at addr = {:x}. base = {:x}", - ShimWornFlags_Hook, - REL::Module::get().base()); - g_branchTrampoline->write_branch<5>(ShimWornFlags_Hook, code); - } - LOG(info, "Done"); - } - } + LOG(info, "AVI: Patching shim worn flags at addr = {:x}. base = {:x}", + ShimWornFlags_Hook, REL::Module::get().base()); + g_branchTrampoline->write_branch<5>(ShimWornFlags_Hook, code); + } + LOG(info, "Done"); + } + } // namespace ShimWornFlags - namespace CustomSkinPlayer { - void Custom(RE::Actor *target, RE::ActorWeightModel *actorWeightModel) { - if (!skyrim_cast(target)) { - // Actor failed to cast... - LOG(info, "Custom: Failed to cast target to Actor."); - return; - } - // Get basic actor information (race and sex) - if (!actorWeightModel) - return; - auto base = skyrim_cast(target->data.objectReference); - if (!base) - return; - auto race = base->race; - bool isFemale = base->IsFemale(); - // - auto &svc = ArmorAddonOverrideService::GetInstance(); - auto &outfit = svc.currentOutfit((RE::Actor *) target); + namespace CustomSkinPlayer { + void Custom(RE::Actor* target, RE::ActorWeightModel* actorWeightModel) { + if (!skyrim_cast(target)) { + // Actor failed to cast... + LOG(info, "Custom: Failed to cast target to Actor."); + return; + } + // Get basic actor information (race and sex) + if (!actorWeightModel) + return; + auto base = skyrim_cast(target->data.objectReference); + if (!base) + return; + auto race = base->race; + bool isFemale = base->IsFemale(); + // + auto& svc = ArmorAddonOverrideService::GetInstance(); + auto& outfit = svc.currentOutfit((RE::Actor*)target); - // Get actor inventory and equipped items - auto inventory = target->GetInventoryChanges(); - EquippedArmorVisitor visitor; - if (inventory) { - inventory->ExecuteVisitorOnWorn(&visitor); - } else { - LOG(info, "Custom: Unable to get target inventory."); - return; - } + // Get actor inventory and equipped items + auto inventory = target->GetInventoryChanges(); + EquippedArmorVisitor visitor; + if (inventory) { + inventory->ExecuteVisitorOnWorn(&visitor); + } else { + LOG(info, "Custom: Unable to get target inventory."); + return; + } - // Compute the display set. - auto displaySet = outfit.computeDisplaySet(visitor.equipped); + // Compute the display set. + auto displaySet = outfit.computeDisplaySet(visitor.equipped); - // Compute the remaining items to be applied to the player - // We assume that the DontVanillaSkinPlayer already passed through - // the equipped items that will be shown, so we only need to worry about the items that - // are not equipped but which are in the outfit or which are masked by the outfit. - std::unordered_set applySet; - for (const auto &item : displaySet) { - if (visitor.equipped.find(item) == visitor.equipped.end()) - applySet.insert(item); - } + // Compute the remaining items to be applied to the player + // We assume that the DontVanillaSkinPlayer already passed through + // the equipped items that will be shown, so we only need to worry about the + // items that are not equipped but which are in the outfit or which are masked + // by the outfit. + std::unordered_set applySet; + for (const auto& item : displaySet) { + if (visitor.equipped.find(item) == visitor.equipped.end()) + applySet.insert(item); + } - for (auto it = applySet.cbegin(); it != applySet.cend(); ++it) { - // TODO: [SlotPassthru] Also do the same iteration over the passthrough equipped items? - RE::TESObjectARMO *armor = *it; - if (armor) { - armor->ApplyArmorAddon(race, actorWeightModel, isFemale); - } - } - } + for (auto it = applySet.cbegin(); it != applySet.cend(); ++it) { + // TODO: [SlotPassthru] Also do the same iteration over the passthrough + // equipped items? + RE::TESObjectARMO* armor = *it; + if (armor) { + armor->ApplyArmorAddon(race, actorWeightModel, isFemale); + } + } + } - REL::ID CustomSkinPlayer_Hook_ID(24725); - std::uintptr_t CustomSkinPlayer_Hook(CustomSkinPlayer_Hook_ID.address() + 0x1EF); // 0x00364301 in 1.5.73 + REL::ID CustomSkinPlayer_Hook_ID(24725); + std::uintptr_t CustomSkinPlayer_Hook(CustomSkinPlayer_Hook_ID.address() + + 0x1EF); // 0x00364301 in 1.5.73 - REL::ID InventoryChanges_ExecuteVisitorOnWorn(16096); // 0x001E51D0 in 1.5.73 + REL::ID InventoryChanges_ExecuteVisitorOnWorn(16096); // 0x001E51D0 in 1.5.73 - void Apply() { - { - struct CustomSkinPlayer_Code : Xbyak::CodeGenerator { - CustomSkinPlayer_Code() { - Xbyak::Label j_Out; - Xbyak::Label f_Custom; - Xbyak::Label f_ExecuteVisitorOnWorn; - Xbyak::Label f_ShouldOverrideSkinning; + void Apply() { + { + struct CustomSkinPlayer_Code : Xbyak::CodeGenerator { + CustomSkinPlayer_Code() { + Xbyak::Label j_Out; + Xbyak::Label f_Custom; + Xbyak::Label f_ExecuteVisitorOnWorn; + Xbyak::Label f_ShouldOverrideSkinning; - // call original function - call(ptr[rip + f_ExecuteVisitorOnWorn]); + // call original function + call(ptr[rip + f_ExecuteVisitorOnWorn]); - push(rcx); - mov(rcx, rbx); - sub(rsp, 0x20); - call(ptr[rip + f_ShouldOverrideSkinning]); - add(rsp, 0x20); - pop(rcx); + push(rcx); + mov(rcx, rbx); + sub(rsp, 0x20); + call(ptr[rip + f_ShouldOverrideSkinning]); + add(rsp, 0x20); + pop(rcx); - test(al, al); - jz(j_Out); + test(al, al); + jz(j_Out); - push(rdx); - push(rcx); - mov(rcx, rbx); - mov(rdx, r15); - sub(rsp, 0x20); - call(ptr[rip + f_Custom]); - add(rsp, 0x20); - pop(rcx); - pop(rdx); + push(rdx); + push(rcx); + mov(rcx, rbx); + mov(rdx, r15); + sub(rsp, 0x20); + call(ptr[rip + f_Custom]); + add(rsp, 0x20); + pop(rcx); + pop(rdx); - L(j_Out); - jmp(ptr[rip]); - dq(CustomSkinPlayer_Hook + 0x5); + L(j_Out); + jmp(ptr[rip]); + dq(CustomSkinPlayer_Hook + 0x5); - L(f_Custom); - dq(uintptr_t(Custom)); + L(f_Custom); + dq(uintptr_t(Custom)); - L(f_ExecuteVisitorOnWorn); - dq(InventoryChanges_ExecuteVisitorOnWorn.address()); + L(f_ExecuteVisitorOnWorn); + dq(InventoryChanges_ExecuteVisitorOnWorn.address()); - L(f_ShouldOverrideSkinning); - dq(uintptr_t(ShouldOverrideSkinning)); - } - }; - CustomSkinPlayer_Code gen; - void *code = g_localTrampoline->allocate(gen); + L(f_ShouldOverrideSkinning); + dq(uintptr_t(ShouldOverrideSkinning)); + } + }; + CustomSkinPlayer_Code gen; + void* code = g_localTrampoline->allocate(gen); - LOG(info, - "AVI: Patching custom skin player at addr = {}. base = {}", - CustomSkinPlayer_Hook, - REL::Module::get().base()); - g_branchTrampoline->write_branch<5>(CustomSkinPlayer_Hook, code); - } - LOG(info, "Done"); - } - } + LOG(info, "AVI: Patching custom skin player at addr = {}. base = {}", + CustomSkinPlayer_Hook, REL::Module::get().base()); + g_branchTrampoline->write_branch<5>(CustomSkinPlayer_Hook, code); + } + LOG(info, "Done"); + } + } // namespace CustomSkinPlayer - namespace FixEquipConflictCheck { - // - // When you try to equip an item, the game loops over the armors in your ActorWeightModel - // rather than your other worn items. Because we're tampering with what goes into the AWM, - // this means that conflict checks are run against your outfit instead of your equipment, - // unless we patch in a fix. (For example, if your outfit doesn't include a helmet, then - // you'd be able to stack helmets endlessly without this patch.) - // - // The loop in question is performed in Actor::Unk_120, which is also generally responsible - // for equipping items at all. - // - class _Visitor : public RE::InventoryChanges::IItemChangeVisitor { - // - // Bethesda used a visitor to add armor-addons to the ActorWeightModel in the first - // place (see call stack for DontVanillaSkinPlayer patch), so why not use a similar - // visitor to check for conflicts? - // - public: - virtual ReturnType Visit(RE::InventoryEntryData *data) override { - // TODO: [SlotPassthru] We might be able to leave this as-is. - auto form = data->object; - if (form && form->formType == RE::FormType::Armor) { - auto armor = reinterpret_cast(form); - if (armor->TestBodyPartByIndex(this->conflictIndex)) { - auto em = RE::ActorEquipManager::GetSingleton(); - // - // TODO: The third argument to this call is meant to be a BaseExtraList*, - // and Bethesda supplies one when calling from Unk_120. Can we get away - // with a nullptr here, or do we have to find the BaseExtraList that - // contains an ExtraWorn? - // - // I'm not sure how to investigate this, but I did run one test, and that - // works properly: I gave myself ten Falmer Helmets and applied different - // enchantments to two of them (leaving the others unenchanted). In tests, - // I was unable to stack the helmets with each other or with other helmets, - // suggesting that the BaseExtraList may not be strictly necessary. - // - em->UnequipObject(this->target, form, nullptr, 1, nullptr, false, false, true, false, nullptr); - } - } - return ReturnType::kContinue; // True to continue visiting - }; + namespace FixEquipConflictCheck { + // + // When you try to equip an item, the game loops over the armors in your + // ActorWeightModel rather than your other worn items. Because we're tampering + // with what goes into the AWM, this means that conflict checks are run against + // your outfit instead of your equipment, unless we patch in a fix. (For + // example, if your outfit doesn't include a helmet, then you'd be able to stack + // helmets endlessly without this patch.) + // + // The loop in question is performed in Actor::Unk_120, which is also generally + // responsible for equipping items at all. + // + class _Visitor : public RE::InventoryChanges::IItemChangeVisitor { + // + // Bethesda used a visitor to add armor-addons to the ActorWeightModel in the + // first place (see call stack for DontVanillaSkinPlayer patch), so why not + // use a similar visitor to check for conflicts? + // + public: + virtual ReturnType Visit(RE::InventoryEntryData* data) override { + // TODO: [SlotPassthru] We might be able to leave this as-is. + auto form = data->object; + if (form && form->formType == RE::FormType::Armor) { + auto armor = reinterpret_cast(form); + if (armor->TestBodyPartByIndex(this->conflictIndex)) { + auto em = RE::ActorEquipManager::GetSingleton(); + // + // TODO: The third argument to this call is meant to be a + // BaseExtraList*, and Bethesda supplies one when calling from Unk_120. + // Can we get away with a nullptr here, or do we have to find the + // BaseExtraList that contains an ExtraWorn? + // + // I'm not sure how to investigate this, but I did run one test, and + // that works properly: I gave myself ten Falmer Helmets and applied + // different enchantments to two of them (leaving the others + // unenchanted). In tests, I was unable to stack the helmets with each + // other or with other helmets, suggesting that the BaseExtraList may + // not be strictly necessary. + // + em->UnequipObject(this->target, form, nullptr, 1, nullptr, false, false, + true, false, nullptr); + } + } + return ReturnType::kContinue; // True to continue visiting + }; - RE::Actor *target; - std::uint32_t conflictIndex = 0; - }; - void Inner(std::uint32_t bodyPartForNewItem, RE::Actor *target) { - auto inventory = target->GetInventoryChanges(); - if (inventory) { - _Visitor visitor; - visitor.conflictIndex = bodyPartForNewItem; - visitor.target = target; - inventory->ExecuteVisitorOnWorn(&visitor); - } else { - LOG(info, "OverridePlayerSkinning: Conflict check failed: no inventory!"); - } - } - bool ShouldOverride(RE::TESForm *item) { - // - // We only hijack equipping for armors, so I'd like for this patch to only - // apply to armors as well. It shouldn't really matter -- before I added - // this check, weapons and the like tested in-game with no issues, likely - // because they're handled very differently -- but I just wanna be sure. - // We should use vanilla code whenever we don't need to NOT use it. - // - return (item->formType == RE::FormType::Armor); - } + RE::Actor* target; + std::uint32_t conflictIndex = 0; + }; + void Inner(std::uint32_t bodyPartForNewItem, RE::Actor* target) { + auto inventory = target->GetInventoryChanges(); + if (inventory) { + _Visitor visitor; + visitor.conflictIndex = bodyPartForNewItem; + visitor.target = target; + inventory->ExecuteVisitorOnWorn(&visitor); + } else { + LOG(info, "OverridePlayerSkinning: Conflict check failed: no inventory!"); + } + } + bool ShouldOverride(RE::TESForm* item) { + // + // We only hijack equipping for armors, so I'd like for this patch to only + // apply to armors as well. It shouldn't really matter -- before I added + // this check, weapons and the like tested in-game with no issues, likely + // because they're handled very differently -- but I just wanna be sure. + // We should use vanilla code whenever we don't need to NOT use it. + // + return (item->formType == RE::FormType::Armor); + } - REL::ID FixEquipConflictCheck_Hook_ID(38004); - std::uintptr_t FixEquipConflictCheck_Hook(FixEquipConflictCheck_Hook_ID.address() + 0x97); // 0x0060CAC7 in 1.5.73 + REL::ID FixEquipConflictCheck_Hook_ID(38004); + std::uintptr_t FixEquipConflictCheck_Hook( + FixEquipConflictCheck_Hook_ID.address() + 0x97); // 0x0060CAC7 in 1.5.73 - REL::ID BGSBipedObjectForm_TestBodyPartByIndex(14119); // 0x001820A0 in 1.5.73 + REL::ID BGSBipedObjectForm_TestBodyPartByIndex(14119); // 0x001820A0 in 1.5.73 - void Apply() { - LOG(info, "Patching fix for equip conflict check"); - LOG(info, "FixEquipConflictCheck_Hook = {:x}", FixEquipConflictCheck_Hook - REL::Module::get().base()); - LOG(info, - "BGSBipedObjectForm_TestBodyPartByIndex = {:x}", - BGSBipedObjectForm_TestBodyPartByIndex.address() - REL::Module::get().base()); - { - struct FixEquipConflictCheck_Code : Xbyak::CodeGenerator { - FixEquipConflictCheck_Code() { - Xbyak::Label j_Out; - Xbyak::Label j_Exit; - Xbyak::Label f_Inner; - Xbyak::Label f_TestBodyPartByIndex; - Xbyak::Label f_ShouldOverride; + void Apply() { + LOG(info, "Patching fix for equip conflict check"); + LOG(info, "FixEquipConflictCheck_Hook = {:x}", + FixEquipConflictCheck_Hook - REL::Module::get().base()); + LOG(info, "BGSBipedObjectForm_TestBodyPartByIndex = {:x}", + BGSBipedObjectForm_TestBodyPartByIndex.address() - + REL::Module::get().base()); + { + struct FixEquipConflictCheck_Code : Xbyak::CodeGenerator { + FixEquipConflictCheck_Code() { + Xbyak::Label j_Out; + Xbyak::Label j_Exit; + Xbyak::Label f_Inner; + Xbyak::Label f_TestBodyPartByIndex; + Xbyak::Label f_ShouldOverride; - call(ptr[rip + f_TestBodyPartByIndex]); - test(al, al); - jz(j_Exit); + call(ptr[rip + f_TestBodyPartByIndex]); + test(al, al); + jz(j_Exit); - // rsp+0x10: item - // rdi: Actor - // rbx: Body Slot - push(rcx); - // Set RCX to the RDX argument of the patched function - // RSP + Argument offset rel to original entry + Offset from push above + Offset from pushes in original entry - mov(rcx, ptr[rsp + 0x10 + 0x08 + 0xC8]); - sub(rsp, 0x20); - call(ptr[rip + f_ShouldOverride]); - add(rsp, 0x20); - pop(rcx); - test(al, al); - mov(rax, 1); - jz(j_Out); - push(rcx); - push(rdx); - mov(rcx, rbx); - mov(rdx, rdi); - sub(rsp, 0x20); - call(ptr[rip + f_Inner]); - add(rsp, 0x20); - pop(rdx); - pop(rcx); + // rsp+0x10: item + // rdi: Actor + // rbx: Body Slot + push(rcx); + // Set RCX to the RDX argument of the patched function + // RSP + Argument offset rel to original entry + Offset from push above + // + Offset from pushes in original entry + mov(rcx, ptr[rsp + 0x10 + 0x08 + 0xC8]); + sub(rsp, 0x20); + call(ptr[rip + f_ShouldOverride]); + add(rsp, 0x20); + pop(rcx); + test(al, al); + mov(rax, 1); + jz(j_Out); + push(rcx); + push(rdx); + mov(rcx, rbx); + mov(rdx, rdi); + sub(rsp, 0x20); + call(ptr[rip + f_Inner]); + add(rsp, 0x20); + pop(rdx); + pop(rcx); - L(j_Exit); - xor_(al, al); + L(j_Exit); + xor_(al, al); - L(j_Out); - jmp(ptr[rip]); - dq(FixEquipConflictCheck_Hook + 0x5); + L(j_Out); + jmp(ptr[rip]); + dq(FixEquipConflictCheck_Hook + 0x5); - L(f_TestBodyPartByIndex); - dq(BGSBipedObjectForm_TestBodyPartByIndex.address()); + L(f_TestBodyPartByIndex); + dq(BGSBipedObjectForm_TestBodyPartByIndex.address()); - L(f_ShouldOverride); - dq(uintptr_t(ShouldOverride)); + L(f_ShouldOverride); + dq(uintptr_t(ShouldOverride)); - L(f_Inner); - dq(uintptr_t(Inner)); - } - }; - FixEquipConflictCheck_Code gen; - void *code = g_localTrampoline->allocate(gen); + L(f_Inner); + dq(uintptr_t(Inner)); + } + }; + FixEquipConflictCheck_Code gen; + void* code = g_localTrampoline->allocate(gen); - LOG(info, - "AVI: Patching fix equip conflict check at addr = {:x}. dst = {}. base = {:x}", - FixEquipConflictCheck_Hook, - code, - REL::Module::get().base()); - g_branchTrampoline->write_branch<5>(FixEquipConflictCheck_Hook, code); - } - LOG(info, "Done"); - } - } + LOG(info, + "AVI: Patching fix equip conflict check at addr = {:x}. dst = {}. base " + "= {:x}", + FixEquipConflictCheck_Hook, code, REL::Module::get().base()); + g_branchTrampoline->write_branch<5>(FixEquipConflictCheck_Hook, code); + } + LOG(info, "Done"); + } + } // namespace FixEquipConflictCheck - namespace RTTIPrinter { - void Print_RTTI(RE::InventoryChanges::IItemChangeVisitor *target) { - void *object = (void *) target; - void *vtable = *(void **) object; - void *info_block = ((void **) vtable)[-1]; - uintptr_t id = ((unsigned long *) info_block)[3]; - uintptr_t info = id + REL::Module::get().base(); - char *name = (char *) info + 16; - LOG(info, "vtable = {}, typeinfo = {}, typename = {}", vtable, info_block, name); - } + namespace RTTIPrinter { + void Print_RTTI(RE::InventoryChanges::IItemChangeVisitor* target) { + void* object = (void*)target; + void* vtable = *(void**)object; + void* info_block = ((void**)vtable)[-1]; + uintptr_t id = ((unsigned long*)info_block)[3]; + uintptr_t info = id + REL::Module::get().base(); + char* name = (char*)info + 16; + LOG(info, "vtable = {}, typeinfo = {}, typename = {}", vtable, info_block, + name); + } - REL::ID InventoryChanges_ExecuteVisitorOnWorn(16096); // 0x001E51D0 in 1.5.73 - std::uintptr_t InventoryChanges_ExecuteVisitorOnWorn_Hook - (InventoryChanges_ExecuteVisitorOnWorn.address() + 0x2A); // 0x00364301 in 1.5.73 + REL::ID InventoryChanges_ExecuteVisitorOnWorn(16096); // 0x001E51D0 in 1.5.73 + std::uintptr_t InventoryChanges_ExecuteVisitorOnWorn_Hook( + InventoryChanges_ExecuteVisitorOnWorn.address() + + 0x2A); // 0x00364301 in 1.5.73 - void Apply() { - LOG(info, "Patching RTTI print"); - LOG(info, - "InventoryChanges_ExecuteVisitorOnWorn_Hook = {:x}", - InventoryChanges_ExecuteVisitorOnWorn_Hook - REL::Module::get().base()); - { - struct RTTI_Code : Xbyak::CodeGenerator { - RTTI_Code() { - Xbyak::Label f_PrintRTTI; + void Apply() { + LOG(info, "Patching RTTI print"); + LOG(info, "InventoryChanges_ExecuteVisitorOnWorn_Hook = {:x}", + InventoryChanges_ExecuteVisitorOnWorn_Hook - REL::Module::get().base()); + { + struct RTTI_Code : Xbyak::CodeGenerator { + RTTI_Code() { + Xbyak::Label f_PrintRTTI; - push(rcx); - push(rdx); - push(rax); - mov(rcx, rdx); - sub(rsp, 0x20); - call(ptr[rip + f_PrintRTTI]); - add(rsp, 0x20); - pop(rax); - pop(rdx); - pop(rcx); + push(rcx); + push(rdx); + push(rax); + mov(rcx, rdx); + sub(rsp, 0x20); + call(ptr[rip + f_PrintRTTI]); + add(rsp, 0x20); + pop(rax); + pop(rdx); + pop(rcx); - jmp(ptr[rip]); - dq(InventoryChanges_ExecuteVisitorOnWorn_Hook + 0x6); + jmp(ptr[rip]); + dq(InventoryChanges_ExecuteVisitorOnWorn_Hook + 0x6); - L(f_PrintRTTI); - dq(uintptr_t(Print_RTTI)); - } - }; - RTTI_Code gen; - void *code = g_localTrampoline->allocate(gen); + L(f_PrintRTTI); + dq(uintptr_t(Print_RTTI)); + } + }; + RTTI_Code gen; + void* code = g_localTrampoline->allocate(gen); - LOG(info, - "AVI: Patching RTTI print at addr = {:x}. base = {:x}", - InventoryChanges_ExecuteVisitorOnWorn_Hook, - REL::Module::get().base()); - g_branchTrampoline->write_branch<6>(InventoryChanges_ExecuteVisitorOnWorn_Hook, code); - } - } - } + LOG(info, "AVI: Patching RTTI print at addr = {:x}. base = {:x}", + InventoryChanges_ExecuteVisitorOnWorn_Hook, REL::Module::get().base()); + g_branchTrampoline->write_branch<6>( + InventoryChanges_ExecuteVisitorOnWorn_Hook, code); + } + } + } // namespace RTTIPrinter - void ApplyPlayerSkinningHooks() { - DontVanillaSkinPlayer::Apply(); - ShimWornFlags::Apply(); - CustomSkinPlayer::Apply(); - FixEquipConflictCheck::Apply(); - } -} + void ApplyPlayerSkinningHooks() { + DontVanillaSkinPlayer::Apply(); + ShimWornFlags::Apply(); + CustomSkinPlayer::Apply(); + FixEquipConflictCheck::Apply(); + } +} // namespace OutfitSystem diff --git a/src/Utility.cpp b/src/Utility.cpp index c508c52..5090bf1 100644 --- a/src/Utility.cpp +++ b/src/Utility.cpp @@ -6,50 +6,44 @@ #include "SKSE/SKSE.h" -std::string GetRuntimePath() -{ - static char appPath[4096] = { 0 }; +std::string GetRuntimePath() { + static char appPath[4096] = { 0 }; - if(appPath[0]) - return appPath; + if (appPath[0]) + return appPath; - if (!SKSE::WinAPI::GetModuleFileName(SKSE::WinAPI::GetModuleHandle((const char*) nullptr), appPath, sizeof(appPath))) { - SKSE::stl::report_and_fail("Failed to get runtime path"); - } + if (!SKSE::WinAPI::GetModuleFileName(SKSE::WinAPI::GetModuleHandle((const char*) nullptr), appPath, sizeof(appPath))) { + SKSE::stl::report_and_fail("Failed to get runtime path"); + } - return appPath; + return appPath; } -std::string GetRuntimeName() -{ - std::string appPath = GetRuntimePath(); +std::string GetRuntimeName() { + std::string appPath = GetRuntimePath(); - std::string::size_type slashOffset = appPath.rfind('\\'); - if(slashOffset == std::string::npos) - return appPath; + std::string::size_type slashOffset = appPath.rfind('\\'); + if (slashOffset == std::string::npos) + return appPath; - return appPath.substr(slashOffset + 1); + return appPath.substr(slashOffset + 1); } -const std::string& GetRuntimeDirectory() -{ - static std::string s_runtimeDirectory; +const std::string& GetRuntimeDirectory() { + static std::string s_runtimeDirectory; - if(s_runtimeDirectory.empty()) - { - std::string runtimePath = GetRuntimePath(); + if (s_runtimeDirectory.empty()) { + std::string runtimePath = GetRuntimePath(); - // truncate at last slash - std::string::size_type lastSlash = runtimePath.rfind('\\'); - if(lastSlash != std::string::npos) // if we don't find a slash something is VERY WRONG - { - s_runtimeDirectory = runtimePath.substr(0, lastSlash + 1); - } - else - { - SKSE::log::critical("no slash in runtime path? (%s)", runtimePath.c_str()); - } - } + // truncate at last slash + std::string::size_type lastSlash = runtimePath.rfind('\\'); + if (lastSlash != std::string::npos) // if we don't find a slash something is VERY WRONG + { + s_runtimeDirectory = runtimePath.substr(0, lastSlash + 1); + } else { + SKSE::log::critical("no slash in runtime path? (%s)", runtimePath.c_str()); + } + } - return s_runtimeDirectory; + return s_runtimeDirectory; } diff --git a/src/cobb/utf8naturalsort.cpp b/src/cobb/utf8naturalsort.cpp index 1dbba67..e3c17cb 100644 --- a/src/cobb/utf8naturalsort.cpp +++ b/src/cobb/utf8naturalsort.cpp @@ -3,57 +3,57 @@ #include inline static bool isNumber(const char c) { - return c >= '0' && c <= '9'; + return c >= '0' && c <= '9'; } namespace cobb { - namespace utf8 { - int32_t naturalcompare(const std::string& a, const std::string& b) { - auto i_a = a.begin(); - auto i_b = b.begin(); - for (; i_a != a.end() && i_b != b.end(); ++i_a, ++i_b) { - unicodechar c_a = utf8::get(a, i_a); - unicodechar c_b = utf8::get(b, i_b); - if (!isNumber(c_a) || !isNumber(c_b)) { - // - // For non-numeric characters, just do a character code comparison. - // This won't internationalize -- it won't even handle accented - // characters in English. - // - c_a = std::towlower(c_a); // not sure about this; seems to use two-byte chars when unicode can be up to four bytes, no? - c_b = std::towlower(c_b); // then again, anything past the two-byte mark probably isn't even a "letter" as Westerners conceive of them... - if (c_a == c_b) - continue; - return c_b - c_a; - } - std::uint32_t n_a = c_a - '0'; - std::uint32_t n_b = c_b - '0'; - ++i_a; - ++i_b; - for (; i_a != a.end(); ++i_a) { - c_a = utf8::get(a, i_a); - if (!isNumber(c_a)) - break; - n_a = (n_a * 10) + (c_a - '0'); - } - for (; i_b != b.end(); ++i_b) { - c_b = utf8::get(b, i_b); - if (!isNumber(c_b)) - break; - n_b = (n_b * 10) + (c_b - '0'); - } - if (n_a != n_b) - return n_b - n_a; - } - // - // If we've reached this point, then we've reached the end of ONE OF the - // strings (and they don't end in digits). - // - if (i_a != a.end()) - return 1; - if (i_b != b.end()) - return -1; - return 0; - } - } + namespace utf8 { + int32_t naturalcompare(const std::string& a, const std::string& b) { + auto i_a = a.begin(); + auto i_b = b.begin(); + for (; i_a != a.end() && i_b != b.end(); ++i_a, ++i_b) { + unicodechar c_a = utf8::get(a, i_a); + unicodechar c_b = utf8::get(b, i_b); + if (!isNumber(c_a) || !isNumber(c_b)) { + // + // For non-numeric characters, just do a character code comparison. + // This won't internationalize -- it won't even handle accented + // characters in English. + // + c_a = std::towlower(c_a); // not sure about this; seems to use two-byte chars when unicode can be up to four bytes, no? + c_b = std::towlower(c_b); // then again, anything past the two-byte mark probably isn't even a "letter" as Westerners conceive of them... + if (c_a == c_b) + continue; + return c_b - c_a; + } + std::uint32_t n_a = c_a - '0'; + std::uint32_t n_b = c_b - '0'; + ++i_a; + ++i_b; + for (; i_a != a.end(); ++i_a) { + c_a = utf8::get(a, i_a); + if (!isNumber(c_a)) + break; + n_a = (n_a * 10) + (c_a - '0'); + } + for (; i_b != b.end(); ++i_b) { + c_b = utf8::get(b, i_b); + if (!isNumber(c_b)) + break; + n_b = (n_b * 10) + (c_b - '0'); + } + if (n_a != n_b) + return n_b - n_a; + } + // + // If we've reached this point, then we've reached the end of ONE OF the + // strings (and they don't end in digits). + // + if (i_a != a.end()) + return 1; + if (i_b != b.end()) + return -1; + return 0; + } + } } \ No newline at end of file diff --git a/src/cobb/utf8string.cpp b/src/cobb/utf8string.cpp index b1cfa8d..95ca435 100644 --- a/src/cobb/utf8string.cpp +++ b/src/cobb/utf8string.cpp @@ -1,194 +1,194 @@ #include "cobb/utf8string.h" namespace cobb { - namespace utf8 { - inline static bool _is_bytecount(char c) { - return c < 0xF8; - } - inline static bool _is_continuation(char c) { - return ((c & 0xC0) == 0x80); - } - // - void advance(const std::string& container, std::string::iterator& iterator) { - if (iterator == container.end()) - return; - unsigned char c; - // - c = iterator[0]; - ++iterator; - if (c < 0x80) // single-byte glyph - return; - if (!_is_bytecount(c)) // error - return; - // - c = iterator[0]; - ++iterator; - if (!_is_continuation(c)) // error - return; - // - c = iterator[0]; - ++iterator; - if (!_is_continuation(c)) // error - return; - // - c = iterator[0]; - ++iterator; - } - void append(std::string& target, const unicodechar c) { - if (c < 0x80) { - target.push_back(c); - return; - } - if (c < 0x800) { - target.push_back(0xE0 | ((c >> 6) & 0x1F)); - target.push_back(0x80 | (c & 0x3F)); - return; - } - if (c < 0x10000) { - target.push_back(0xE0 | ((c >> 12) & 0xF)); - target.push_back(0x80 | ((c >> 6) & 0x3F)); - target.push_back(0x80 | (c & 0x3F)); - return; - } - target.push_back(0xE0 | ((c >> 18) & 0x7)); - target.push_back(0x80 | ((c >> 12) & 0x3F)); - target.push_back(0x80 | ((c >> 6) & 0x3F)); - target.push_back(0x80 | (c & 0x1F)); - } - size_t count(std::string& container) { - return count_from(container, container.begin()); - } - size_t count_from(const std::string& container, const std::string::iterator& iterator) { - std::string::iterator working = iterator; - size_t count = 0; - while (working != container.end()) { - advance(container, working); - count++; - } - return count; - } - unicodechar get(const std::string& container, const std::string::const_iterator& iterator) { - if (iterator == container.end()) - return 0; - unsigned char a = iterator[0]; - if (a < 0x80) - return a; - if (!_is_bytecount(a)) - return invalid_glyph; - // - // Two bytes: - // - std::uint32_t result; - char b = iterator[1]; - if (!_is_continuation(b)) - return invalid_glyph; - if ((a & 0xE0) == 0xC0) { - result = (a & 0x1F) << 6; - result |= (b & 0x3F); - return result; - } - // - // Three bytes: - // - char c = iterator[2]; - if (!_is_continuation(c)) - return invalid_glyph; - if ((a & 0xF0) == 0xE0) { // three bytes - result = (a & 0xF) << 12; - result |= (b & 0x3F) << 6; - result |= (c & 0x3F); - if (result >= 0xD800 && result <= 0xDFFF) - return invalid_glyph; - return result; - } - // - // Four bytes: - // - char d = iterator[3]; - if (!_is_continuation(d)) - return invalid_glyph; - result = (a & 7) << 18; - result |= (b & 0x3F) << 12; - result |= (c & 0x3F) << 6; - result |= (d & 0x3F); - if (result > 0x10FFFF) - return invalid_glyph; - return result; - } - unicodechar get(const std::string& container, const std::string::iterator& iterator) { - if (iterator == container.end()) - return 0; - unsigned char a = iterator[0]; - if (a < 0x80) - return a; - if (!_is_bytecount(a)) - return invalid_glyph; - // - // Two bytes: - // - std::uint32_t result; - char b = iterator[1]; - if (!_is_continuation(b)) - return invalid_glyph; - if ((a & 0xE0) == 0xC0) { - result = (a & 0x1F) << 6; - result |= (b & 0x3F); - return result; - } - // - // Three bytes: - // - char c = iterator[2]; - if (!_is_continuation(c)) - return invalid_glyph; - if ((a & 0xF0) == 0xE0) { // three bytes - result = (a & 0xF) << 12; - result |= (b & 0x3F) << 6; - result |= (c & 0x3F); - if (result >= 0xD800 && result <= 0xDFFF) - return invalid_glyph; - return result; - } - // - // Four bytes: - // - char d = iterator[3]; - if (!_is_continuation(d)) - return invalid_glyph; - result = (a & 7) << 18; - result |= (b & 0x3F) << 12; - result |= (c & 0x3F) << 6; - result |= (d & 0x3F); - if (result > 0x10FFFF) - return invalid_glyph; - return result; - } - rawchar get_raw(const std::string& container, const std::string::iterator& iterator) { - if (iterator == container.end()) - return 0; - char a = iterator[0]; - char b = iterator[1]; - char c = iterator[2]; - char d = iterator[3]; - if (!_is_bytecount(a)) - return invalid_glyph; - { // 2 bytes - if (!_is_continuation(b)) - return invalid_glyph; - if ((a & 0xE0) == 0xC0) - return (a << 0x08) | b; - } - { // 3 bytes - if (!_is_continuation(c)) - return invalid_glyph; - if ((a & 0xF0) == 0xE0) - return (a << 0x10) | (b << 0x08) | c; - } - { // 4 bytes - if (!_is_continuation(d)) - return invalid_glyph; - return (a << 0x18) | (b << 0x10) | (c << 0x08) | d; - } - } - } + namespace utf8 { + inline static bool _is_bytecount(char c) { + return c < 0xF8; + } + inline static bool _is_continuation(char c) { + return ((c & 0xC0) == 0x80); + } + // + void advance(const std::string& container, std::string::iterator& iterator) { + if (iterator == container.end()) + return; + unsigned char c; + // + c = iterator[0]; + ++iterator; + if (c < 0x80) // single-byte glyph + return; + if (!_is_bytecount(c)) // error + return; + // + c = iterator[0]; + ++iterator; + if (!_is_continuation(c)) // error + return; + // + c = iterator[0]; + ++iterator; + if (!_is_continuation(c)) // error + return; + // + c = iterator[0]; + ++iterator; + } + void append(std::string& target, const unicodechar c) { + if (c < 0x80) { + target.push_back(c); + return; + } + if (c < 0x800) { + target.push_back(0xE0 | ((c >> 6) & 0x1F)); + target.push_back(0x80 | (c & 0x3F)); + return; + } + if (c < 0x10000) { + target.push_back(0xE0 | ((c >> 12) & 0xF)); + target.push_back(0x80 | ((c >> 6) & 0x3F)); + target.push_back(0x80 | (c & 0x3F)); + return; + } + target.push_back(0xE0 | ((c >> 18) & 0x7)); + target.push_back(0x80 | ((c >> 12) & 0x3F)); + target.push_back(0x80 | ((c >> 6) & 0x3F)); + target.push_back(0x80 | (c & 0x1F)); + } + size_t count(std::string& container) { + return count_from(container, container.begin()); + } + size_t count_from(const std::string& container, const std::string::iterator& iterator) { + std::string::iterator working = iterator; + size_t count = 0; + while (working != container.end()) { + advance(container, working); + count++; + } + return count; + } + unicodechar get(const std::string& container, const std::string::const_iterator& iterator) { + if (iterator == container.end()) + return 0; + unsigned char a = iterator[0]; + if (a < 0x80) + return a; + if (!_is_bytecount(a)) + return invalid_glyph; + // + // Two bytes: + // + std::uint32_t result; + char b = iterator[1]; + if (!_is_continuation(b)) + return invalid_glyph; + if ((a & 0xE0) == 0xC0) { + result = (a & 0x1F) << 6; + result |= (b & 0x3F); + return result; + } + // + // Three bytes: + // + char c = iterator[2]; + if (!_is_continuation(c)) + return invalid_glyph; + if ((a & 0xF0) == 0xE0) { // three bytes + result = (a & 0xF) << 12; + result |= (b & 0x3F) << 6; + result |= (c & 0x3F); + if (result >= 0xD800 && result <= 0xDFFF) + return invalid_glyph; + return result; + } + // + // Four bytes: + // + char d = iterator[3]; + if (!_is_continuation(d)) + return invalid_glyph; + result = (a & 7) << 18; + result |= (b & 0x3F) << 12; + result |= (c & 0x3F) << 6; + result |= (d & 0x3F); + if (result > 0x10FFFF) + return invalid_glyph; + return result; + } + unicodechar get(const std::string& container, const std::string::iterator& iterator) { + if (iterator == container.end()) + return 0; + unsigned char a = iterator[0]; + if (a < 0x80) + return a; + if (!_is_bytecount(a)) + return invalid_glyph; + // + // Two bytes: + // + std::uint32_t result; + char b = iterator[1]; + if (!_is_continuation(b)) + return invalid_glyph; + if ((a & 0xE0) == 0xC0) { + result = (a & 0x1F) << 6; + result |= (b & 0x3F); + return result; + } + // + // Three bytes: + // + char c = iterator[2]; + if (!_is_continuation(c)) + return invalid_glyph; + if ((a & 0xF0) == 0xE0) { // three bytes + result = (a & 0xF) << 12; + result |= (b & 0x3F) << 6; + result |= (c & 0x3F); + if (result >= 0xD800 && result <= 0xDFFF) + return invalid_glyph; + return result; + } + // + // Four bytes: + // + char d = iterator[3]; + if (!_is_continuation(d)) + return invalid_glyph; + result = (a & 7) << 18; + result |= (b & 0x3F) << 12; + result |= (c & 0x3F) << 6; + result |= (d & 0x3F); + if (result > 0x10FFFF) + return invalid_glyph; + return result; + } + rawchar get_raw(const std::string& container, const std::string::iterator& iterator) { + if (iterator == container.end()) + return 0; + char a = iterator[0]; + char b = iterator[1]; + char c = iterator[2]; + char d = iterator[3]; + if (!_is_bytecount(a)) + return invalid_glyph; + { // 2 bytes + if (!_is_continuation(b)) + return invalid_glyph; + if ((a & 0xE0) == 0xC0) + return (a << 0x08) | b; + } + { // 3 bytes + if (!_is_continuation(c)) + return invalid_glyph; + if ((a & 0xF0) == 0xE0) + return (a << 0x10) | (b << 0x08) | c; + } + { // 4 bytes + if (!_is_continuation(d)) + return invalid_glyph; + return (a << 0x18) | (b << 0x10) | (c << 0x08) | d; + } + } + } }; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index a394750..7669716 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,41 +6,41 @@ #include void WaitForDebugger(void) { - while (!IsDebuggerPresent()) { - Sleep(10); - } + while (!IsDebuggerPresent()) { + Sleep(10); + } - Sleep(1000 * 2); + Sleep(1000 * 2); } int ReportHook(int reportType, char* message, int* returnValue) { - // Got an error - util::report_and_fail(message); + // Got an error + util::report_and_fail(message); } namespace { - void InitializeLog() { - auto path = SKSE::log::log_directory(); - if (!path) { - util::report_and_fail("Failed to find standard logging directory"sv); - } + void InitializeLog() { + auto path = SKSE::log::log_directory(); + if (!path) { + util::report_and_fail("Failed to find standard logging directory"sv); + } - *path /= fmt::format("{}.log"sv, Plugin::NAME); - auto sink = std::make_shared(path->string(), true); + *path /= fmt::format("{}.log"sv, Plugin::NAME); + auto sink = std::make_shared(path->string(), true); #ifndef NDEBUG - const auto level = spdlog::level::trace; + const auto level = spdlog::level::trace; #else - const auto level = spdlog::level::info; + const auto level = spdlog::level::info; #endif - auto log = std::make_shared("global log"s, std::move(sink)); - log->set_level(level); - log->flush_on(level); + auto log = std::make_shared("global log"s, std::move(sink)); + log->set_level(level); + log->flush_on(level); - spdlog::set_default_logger(std::move(log)); - spdlog::set_pattern("%g(%#): [%^%l%$] %v"s); - } + spdlog::set_default_logger(std::move(log)); + spdlog::set_pattern("%g(%#): [%^%l%$] %v"s); + } } std::uint32_t g_pluginSerializationSignature = 'cOft'; @@ -50,168 +50,166 @@ void Callback_Serialization_Save(SKSE::SerializationInterface* intfc); void Callback_Serialization_Load(SKSE::SerializationInterface* intfc); extern "C" { -// Plugin Query for AE -DllExport constinit auto SKSEPlugin_Version = []() { - SKSE::PluginVersionData v; + // Plugin Query for AE + DllExport constinit auto SKSEPlugin_Version = []() { + SKSE::PluginVersionData v; - v.PluginVersion(Plugin::VERSION); - v.PluginName(Plugin::NAME); + v.PluginVersion(Plugin::VERSION); + v.PluginName(Plugin::NAME); - v.UsesAddressLibrary(true); - v.CompatibleVersions({SKSE::RUNTIME_LATEST}); + v.UsesAddressLibrary(true); + v.CompatibleVersions({ SKSE::RUNTIME_LATEST }); - return v; -}(); + return v; + }(); -// Plugin Query for SE -DllExport bool SKSEPlugin_Query(const SKSE::QueryInterface *a_skse, SKSE::PluginInfo *a_info) { - LOG(info, "Query: {} v{}", Plugin::NAME, Plugin::VERSION.string()); + // Plugin Query for SE + DllExport bool SKSEPlugin_Query(const SKSE::QueryInterface* a_skse, SKSE::PluginInfo* a_info) { + LOG(info, "Query: {} v{}", Plugin::NAME, Plugin::VERSION.string()); - a_info->infoVersion = SKSE::PluginInfo::kVersion; - a_info->name = "SkyrimOutfitSystemSE"; - a_info->version = 1; + a_info->infoVersion = SKSE::PluginInfo::kVersion; + a_info->name = "SkyrimOutfitSystemSE"; + a_info->version = 1; - if (a_skse->IsEditor()) { - LOG(critical, "[FATAL ERROR] Loaded in editor, marking as incompatible!\n"); - return false; - } + if (a_skse->IsEditor()) { + LOG(critical, "[FATAL ERROR] Loaded in editor, marking as incompatible!\n"); + return false; + } - return true; -} + return true; + } -// Entry point -DllExport bool SKSEPlugin_Load(const SKSE::LoadInterface *a_skse) { - InitializeLog(); - LOG(info, "Load: {} v{}", Plugin::NAME, Plugin::VERSION.string()); + // Entry point + DllExport bool SKSEPlugin_Load(const SKSE::LoadInterface* a_skse) { + InitializeLog(); + LOG(info, "Load: {} v{}", Plugin::NAME, Plugin::VERSION.string()); #ifdef _DEBUG - // Intercept Visual C++ exceptions, but only if we're developing. - _CrtSetReportHook(ReportHook); + // Intercept Visual C++ exceptions, but only if we're developing. + _CrtSetReportHook(ReportHook); #endif - SKSE::Init(a_skse); + SKSE::Init(a_skse); - // Check some interface versions - if (SKSE::GetMessagingInterface()->Version() < SKSE::MessagingInterface::kVersion) { - LOG(critical, "Messaging interface too old."); - return false; - } + // Check some interface versions + if (SKSE::GetMessagingInterface()->Version() < SKSE::MessagingInterface::kVersion) { + LOG(critical, "Messaging interface too old."); + return false; + } - if (SKSE::GetSerializationInterface()->Version() < SKSE::SerializationInterface::kVersion) { - LOG(critical, "Serialization interface too old."); - return false; - } + if (SKSE::GetSerializationInterface()->Version() < SKSE::SerializationInterface::kVersion) { + LOG(critical, "Serialization interface too old."); + return false; + } - // Create Trampolines - SKSE::AllocTrampoline(128, true); - OutfitSystem::g_branchTrampoline = &SKSE::GetTrampoline(); + // Create Trampolines + SKSE::AllocTrampoline(128, true); + OutfitSystem::g_branchTrampoline = &SKSE::GetTrampoline(); - OutfitSystem::g_localTrampoline = new SKSE::Trampoline("local"); - OutfitSystem::g_localTrampoline->create(1024 * 64); + OutfitSystem::g_localTrampoline = new SKSE::Trampoline("local"); + OutfitSystem::g_localTrampoline->create(1024 * 64); - // Actual plugin load - LOG(info, "Patching player skinning"); - OutfitSystem::ApplyPlayerSkinningHooks(); + // Actual plugin load + LOG(info, "Patching player skinning"); + OutfitSystem::ApplyPlayerSkinningHooks(); - // Messaging Callback - LOG(info, "Registering messaging callback"); - SKSE::GetMessagingInterface()->RegisterListener(Callback_Messaging_SKSE); + // Messaging Callback + LOG(info, "Registering messaging callback"); + SKSE::GetMessagingInterface()->RegisterListener(Callback_Messaging_SKSE); - // Serialization Callbacks - LOG(info, "Registering serialization callback"); - SKSE::GetSerializationInterface()->SetUniqueID(g_pluginSerializationSignature); - SKSE::GetSerializationInterface()->SetSaveCallback(Callback_Serialization_Save); - SKSE::GetSerializationInterface()->SetLoadCallback(Callback_Serialization_Load); + // Serialization Callbacks + LOG(info, "Registering serialization callback"); + SKSE::GetSerializationInterface()->SetUniqueID(g_pluginSerializationSignature); + SKSE::GetSerializationInterface()->SetSaveCallback(Callback_Serialization_Save); + SKSE::GetSerializationInterface()->SetLoadCallback(Callback_Serialization_Load); - // Papyrus Registrations - LOG(info, "Registering papyrus"); - SKSE::GetPapyrusInterface()->Register(OutfitSystem::RegisterPapyrus); + // Papyrus Registrations + LOG(info, "Registering papyrus"); + SKSE::GetPapyrusInterface()->Register(OutfitSystem::RegisterPapyrus); - return true; -} + return true; + } } -void Callback_Messaging_SKSE(SKSE::MessagingInterface::Message *message) { - if (message->type == SKSE::MessagingInterface::kPostLoad) { - } else if (message->type == SKSE::MessagingInterface::kPostPostLoad) { - } else if (message->type == SKSE::MessagingInterface::kDataLoaded) { - } else if (message->type == SKSE::MessagingInterface::kNewGame) { - ArmorAddonOverrideService::GetInstance().reset(); - } else if (message->type == SKSE::MessagingInterface::kPreLoadGame) { - ArmorAddonOverrideService::GetInstance() - .reset(); // AAOS::load resets as well, but this is needed in case the save we're about to load doesn't have any AAOS data. - } +void Callback_Messaging_SKSE(SKSE::MessagingInterface::Message* message) { + if (message->type == SKSE::MessagingInterface::kPostLoad) { + } else if (message->type == SKSE::MessagingInterface::kPostPostLoad) { + } else if (message->type == SKSE::MessagingInterface::kDataLoaded) { + } else if (message->type == SKSE::MessagingInterface::kNewGame) { + ArmorAddonOverrideService::GetInstance().reset(); + } else if (message->type == SKSE::MessagingInterface::kPreLoadGame) { + ArmorAddonOverrideService::GetInstance() + .reset(); // AAOS::load resets as well, but this is needed in case the save we're about to load doesn't have any AAOS data. + } } -void _assertWrite(bool result, const char *err); -void _assertRead(bool result, const char *err); +void _assertWrite(bool result, const char* err); +void _assertRead(bool result, const char* err); -void Callback_Serialization_Save(SKSE::SerializationInterface *intfc) { - LOG(info, "Writing savedata..."); - // - if (intfc->OpenRecord(ArmorAddonOverrideService::signature, ArmorAddonOverrideService::kSaveVersionV4)) { - try { - auto &service = ArmorAddonOverrideService::GetInstance(); - const auto &data = service.save(); - const auto &data_ser = data.SerializeAsString(); - _assertWrite(intfc->WriteRecordData(data_ser.data(), static_cast(data_ser.size())), - "Failed to write proto into SKSE record."); - } - catch (const ArmorAddonOverrideService::save_error &exception) { - LOG(info, "Save FAILED for ArmorAddonOverrideService."); - LOG(info, " - Exception string: %s", exception.what()); - } - } else - LOG(info, "Save FAILED for ArmorAddonOverrideService. Record didn't open."); - // - LOG(info, "Saving done!"); +void Callback_Serialization_Save(SKSE::SerializationInterface* intfc) { + LOG(info, "Writing savedata..."); + // + if (intfc->OpenRecord(ArmorAddonOverrideService::signature, ArmorAddonOverrideService::kSaveVersionV4)) { + try { + auto& service = ArmorAddonOverrideService::GetInstance(); + const auto& data = service.save(); + const auto& data_ser = data.SerializeAsString(); + _assertWrite(intfc->WriteRecordData(data_ser.data(), static_cast(data_ser.size())), + "Failed to write proto into SKSE record."); + } catch (const ArmorAddonOverrideService::save_error& exception) { + LOG(info, "Save FAILED for ArmorAddonOverrideService."); + LOG(info, " - Exception string: %s", exception.what()); + } + } else + LOG(info, "Save FAILED for ArmorAddonOverrideService. Record didn't open."); + // + LOG(info, "Saving done!"); } -void Callback_Serialization_Load(SKSE::SerializationInterface *intfc) { - LOG(info, "Loading savedata..."); - // - std::uint32_t - type; // This IS correct. A std::uint32_t and a four-character ASCII string have the same length (and can be read interchangeably, it seems). - std::uint32_t version; - std::uint32_t length; - bool error = false; - // - while (!error && intfc->GetNextRecordInfo(type, version, length)) { - switch (type) { - case ArmorAddonOverrideService::signature: - try { - auto &service = ArmorAddonOverrideService::GetInstance(); - if (version >= ArmorAddonOverrideService::kSaveVersionV4) { - // Read data from protobuf. - std::vector buf; - buf.insert(buf.begin(), length, 0); - _assertRead(intfc->ReadRecordData(buf.data(), length) == length, "Failed to load protobuf."); +void Callback_Serialization_Load(SKSE::SerializationInterface* intfc) { + LOG(info, "Loading savedata..."); + // + std::uint32_t + type; // This IS correct. A std::uint32_t and a four-character ASCII string have the same length (and can be read interchangeably, it seems). + std::uint32_t version; + std::uint32_t length; + bool error = false; + // + while (!error && intfc->GetNextRecordInfo(type, version, length)) { + switch (type) { + case ArmorAddonOverrideService::signature: + try { + auto& service = ArmorAddonOverrideService::GetInstance(); + if (version >= ArmorAddonOverrideService::kSaveVersionV4) { + // Read data from protobuf. + std::vector buf; + buf.insert(buf.begin(), length, 0); + _assertRead(intfc->ReadRecordData(buf.data(), length) == length, "Failed to load protobuf."); - // Parse data in protobuf. - proto::OutfitSystem data; - _assertRead(data.ParseFromArray(buf.data(), static_cast(buf.size())), - "Failed to parse protobuf."); + // Parse data in protobuf. + proto::OutfitSystem data; + _assertRead(data.ParseFromArray(buf.data(), static_cast(buf.size())), + "Failed to parse protobuf."); - // Load data from protobuf struct. - service.load(intfc, data); - } else { - service.load_legacy(intfc, version); - } - } - catch (const ArmorAddonOverrideService::load_error &exception) { - LOG(info, "Load FAILED for ArmorAddonOverrideService."); - LOG(info, " - Exception string: %s", exception.what()); - } - break; - default: - LOG(info, "Loading: Unhandled type %c%c%c%c", - (char) (type >> 0x18), - (char) (type >> 0x10), - (char) (type >> 0x8), - (char) type); - error = true; - break; - } - } - // - LOG(info, "Loading done!"); + // Load data from protobuf struct. + service.load(intfc, data); + } else { + service.load_legacy(intfc, version); + } + } catch (const ArmorAddonOverrideService::load_error& exception) { + LOG(info, "Load FAILED for ArmorAddonOverrideService."); + LOG(info, " - Exception string: %s", exception.what()); + } + break; + default: + LOG(info, "Loading: Unhandled type %c%c%c%c", + (char)(type >> 0x18), + (char)(type >> 0x10), + (char)(type >> 0x8), + (char)type); + error = true; + break; + } + } + // + LOG(info, "Loading done!"); } \ No newline at end of file