It's all done, but not tested.

This commit is contained in:
MetricExpansion 2022-10-23 23:05:15 -07:00
parent 939d10a985
commit d9469bdd42
6 changed files with 53 additions and 578 deletions

View file

@ -72,37 +72,22 @@ namespace SlotPolicy {
Selection select(Mode policy, bool hasEquipped, bool hasOutfit);
}// namespace SlotPolicy
struct Outfit {
Outfit(const proto::Outfit& proto, const SKSE::SerializationInterface* intfc);
Outfit(const char* n) : m_name(n), m_favorited(false), m_blanketSlotPolicy(SlotPolicy::Mode::XXOO){};
Outfit(const Outfit& other) = default;
Outfit(const char* n, const Outfit& other) : m_name(n), m_favorited(false) {
m_armors = other.m_armors;
m_slotPolicies = other.m_slotPolicies;
m_blanketSlotPolicy = other.m_blanketSlotPolicy;
}
std::string m_name;// can't be const; prevents assigning to Outfit vars
std::unordered_set<RE::TESObjectARMO*> m_armors;
bool m_favorited;
std::map<RE::BIPED_OBJECT, SlotPolicy::Mode> m_slotPolicies;
SlotPolicy::Mode m_blanketSlotPolicy;
OutfitService& GetRustInstance();
bool conflictsWith(RE::TESObjectARMO*) const;
std::unordered_set<RE::TESObjectARMO*> computeDisplaySet(const std::unordered_set<RE::TESObjectARMO*>& equippedSet);
void setSlotPolicy(RE::BIPED_OBJECT slot, std::optional<SlotPolicy::Mode> policy);
void setBlanketSlotPolicy(SlotPolicy::Mode policy);
void setDefaultSlotPolicy();
proto::Outfit save() const;// can throw ArmorAddonOverrideService::save_error
struct bad_name: public std::runtime_error {
explicit bad_name(const std::string& what_arg) : runtime_error(what_arg){};
};
struct load_error: public std::runtime_error {
explicit load_error(const std::string& what_arg) : runtime_error(what_arg){};
};
struct name_conflict: public std::runtime_error {
explicit name_conflict(const std::string& what_arg) : runtime_error(what_arg){};
};
struct save_error: public std::runtime_error {
explicit save_error(const std::string& what_arg) : runtime_error(what_arg){};
};
const constexpr char* g_noOutfitName = "";
static Outfit g_noOutfit(g_noOutfitName);// can't be const; prevents us from assigning it to Outfit&s
class ArmorAddonOverrideService {
public:
ArmorAddonOverrideService(){};
ArmorAddonOverrideService(const proto::OutfitSystem& data, const SKSE::SerializationInterface* intfc);// can throw load_error
typedef Outfit Outfit;
namespace Peristence {
static constexpr std::uint32_t signature = 'AAOS';
enum {
kSaveVersionV1 = 1,// Unsupported handwritten binary format
@ -111,80 +96,4 @@ public:
kSaveVersionV4 = 4,// First version with protobuf
kSaveVersionV5 = 5,// First version with Slot Control System
};
//
static constexpr std::uint32_t ce_outfitNameMaxLength = 256;// SKSE caps serialized std::strings and const char*s to 256 bytes.
//
static void _validateNameOrThrow(const char* outfitName);
//
struct bad_name: public std::runtime_error {
explicit bad_name(const std::string& what_arg) : runtime_error(what_arg){};
};
struct load_error: public std::runtime_error {
explicit load_error(const std::string& what_arg) : runtime_error(what_arg){};
};
struct name_conflict: public std::runtime_error {
explicit name_conflict(const std::string& what_arg) : runtime_error(what_arg){};
};
struct save_error: public std::runtime_error {
explicit save_error(const std::string& what_arg) : runtime_error(what_arg){};
};
//
private:
struct OutfitReferenceByName: public cobb::istring {
OutfitReferenceByName(const value_type* s) : cobb::istring(s){};
OutfitReferenceByName(const basic_string& other) : cobb::istring(other){};
//
operator Outfit&() {
return ArmorAddonOverrideService::GetInstance().outfits.at(*this);
}
};
public:
struct ActorOutfitAssignments {
cobb::istring currentOutfitName = g_noOutfitName;
std::map<LocationType, cobb::istring> locationOutfits;
};
bool enabled = true;
std::map<cobb::istring, Outfit> outfits;
// TODO: You probably shouldn't use an Actor pointer to refer to actors. It works for the PlayerCharacter, but likely not for NPCs.
std::map<RE::RawActorHandle, ActorOutfitAssignments> actorOutfitAssignments;
// Location-based switching
bool locationBasedAutoSwitchEnabled = false;
//
static ArmorAddonOverrideService& GetInstance() {
static ArmorAddonOverrideService instance;
return instance;
};
//
Outfit& getOutfit(const char* name); // throws std::out_of_range if not found
Outfit& getOrCreateOutfit(const char* name);// can throw bad_name
//
void addOutfit(const char* name); // can throw bad_name
Outfit& currentOutfit(RE::RawActorHandle target);
bool hasOutfit(const char* name) const;
void deleteOutfit(const char* name);
void setFavorite(const char* name, bool favorite);
void modifyOutfit(const char* name, std::vector<RE::TESObjectARMO*>& add, std::vector<RE::TESObjectARMO*>& remove, bool createIfMissing = false);// can throw bad_name if (createIfMissing)
void renameOutfit(const char* oldName, const char* newName); // throws name_conflict if the new name is already taken; can throw bad_name; throws std::out_of_range if the oldName doesn't exist
void setOutfit(const char* name, RE::RawActorHandle target);
void addActor(RE::RawActorHandle target);
void removeActor(RE::RawActorHandle target);
std::unordered_set<RE::RawActorHandle> listActors();
//
void setLocationBasedAutoSwitchEnabled(bool) noexcept;
void setOutfitUsingLocation(LocationType location, RE::RawActorHandle target);
void setLocationOutfit(LocationType location, const char* name, RE::RawActorHandle target);
void unsetLocationOutfit(LocationType location, RE::RawActorHandle target);
std::optional<cobb::istring> getLocationOutfit(LocationType location, RE::RawActorHandle target);
std::optional<LocationType> checkLocationType(const std::unordered_set<std::string>& keywords, const WeatherFlags& weather_flags, RE::RawActorHandle target);
//
bool shouldOverride(RE::RawActorHandle target) const noexcept;
void getOutfitNames(std::vector<std::string>& out, bool favoritesOnly = false) const;
void setEnabled(bool) noexcept;
//
proto::OutfitSystem save();// can throw save_error
//
void dump() const;
};
OutfitService& GetRustInstance();
}

View file

@ -4,459 +4,11 @@
void _assertWrite(bool result, const char* err) {
if (!result)
throw ArmorAddonOverrideService::save_error(err);
throw save_error(err);
}
void _assertRead(bool result, const char* err) {
if (!result)
throw ArmorAddonOverrideService::load_error(err);
}
Outfit::Outfit(const proto::Outfit& proto, const SKSE::SerializationInterface* intfc) {
m_name = proto.name();
for (const auto& formID : proto.armors()) {
RE::FormID fixedID;
if (intfc->ResolveFormID(formID, fixedID)) {
auto armor = skyrim_cast<RE::TESObjectARMO*>(RE::TESForm::LookupByID(fixedID));
if (armor)
m_armors.insert(armor);
}
}
m_favorited = proto.is_favorite();
for (const auto& pair : proto.slot_policies()) {
auto slot = static_cast<RE::BIPED_OBJECT>(pair.first);
auto policy = static_cast<SlotPolicy::Mode>(pair.second);
if (slot >= RE::BIPED_OBJECTS_META::kNumSlots) {
LOG(err, "Invalid slot {}.", static_cast<std::uint32_t>(slot));
continue;
}
if (policy >= SlotPolicy::Mode::kNumModes) {
LOG(err, "Invalid slot preference {}", policy);
continue;
}
m_slotPolicies[slot] = policy;
}
m_blanketSlotPolicy = static_cast<SlotPolicy::Mode>(proto.slot_policy());
}
bool Outfit::conflictsWith(RE::TESObjectARMO* test) const {
if (!test)
return false;
const auto mask = static_cast<uint32_t>(test->GetSlotMask());
for (auto it = m_armors.cbegin(); it != m_armors.cend(); ++it) {
RE::TESObjectARMO* armor = *it;
if (armor)
if ((mask & static_cast<uint32_t>(armor->GetSlotMask()))
!= static_cast<uint32_t>(RE::BGSBipedObjectForm::FirstPersonFlag::kNone))
return true;
}
return false;
}
void Outfit::setDefaultSlotPolicy() {
m_slotPolicies.clear();
m_slotPolicies[RE::BIPED_OBJECTS::kShield] = SlotPolicy::Mode::XEXO;
m_blanketSlotPolicy = SlotPolicy::Mode::XXOO;
}
void Outfit::setSlotPolicy(RE::BIPED_OBJECT slot, std::optional<SlotPolicy::Mode> policy) {
if (slot >= RE::BIPED_OBJECTS_META::kNumSlots) {
LOG(err, "Invalid slot {}.", static_cast<std::uint32_t>(slot));
return;
}
if (policy.has_value()) {
if (policy.value() >= SlotPolicy::Mode::kNumModes) {
LOG(err, "Invalid slot preference {}.", policy.value());
return;
}
m_slotPolicies[slot] = policy.value();
} else {
m_slotPolicies.erase(slot);
}
}
void Outfit::setBlanketSlotPolicy(SlotPolicy::Mode policy) {
if (policy >= SlotPolicy::Mode::kNumModes) {
LOG(err, "Invalid slot preference {}.", policy);
return;
}
m_blanketSlotPolicy = policy;
}
std::unordered_set<RE::TESObjectARMO*> Outfit::computeDisplaySet(const std::unordered_set<RE::TESObjectARMO*>& equippedSet) {
// Helper function that assigns the armor's to the positions in an array that match the armor's slot mask.
auto assignToMatchingSlots = [](std::array<RE::TESObjectARMO*, RE::BIPED_OBJECTS_META::kNumSlots>& dest, RE::TESObjectARMO* armor) {
auto mask = static_cast<uint32_t>(armor->GetSlotMask());
for (auto slot = RE::BIPED_OBJECTS_META::kFirstSlot; slot < RE::BIPED_OBJECTS_META::kNumSlots; slot++) {
if (mask & (1 << slot)) dest[slot] = armor;
}
};
// Get the list with the equipped armor in each slot
std::array<RE::TESObjectARMO*, RE::BIPED_OBJECTS_META::kNumSlots> equipped{nullptr};
for (auto armor : equippedSet) {
if (!armor) continue;
assignToMatchingSlots(equipped, armor);
}
// Get the list with the outfit armor in each slot
std::array<RE::TESObjectARMO*, RE::BIPED_OBJECTS_META::kNumSlots> outfit{nullptr};
for (auto armor : m_armors) {
if (!armor) continue;
assignToMatchingSlots(outfit, armor);
}
// Go pairwise through the two lists and select what goes in the results slot
std::unordered_set<RE::TESObjectARMO*> result;
std::uint32_t occupiedMask = 0;
for (auto slot = RE::BIPED_OBJECTS_META::kFirstSlot; slot < RE::BIPED_OBJECTS_META::kNumSlots; slot++) {
// Someone before us already got this slot.
if (occupiedMask & (1 << slot)) continue;
// Select the slot's policy, falling back to the outfit's policy if none.
SlotPolicy::Mode preference = m_blanketSlotPolicy;
auto slotSpecificPolicy = m_slotPolicies.find(static_cast<RE::BIPED_OBJECT>(slot));
if (slotSpecificPolicy != m_slotPolicies.end()) preference = slotSpecificPolicy->second;
auto selection = SlotPolicy::select(preference, equipped[slot], outfit[slot]);
RE::TESObjectARMO* selectedArmor = nullptr;
switch (selection) {
case SlotPolicy::Selection::EQUIPPED:
selectedArmor = equipped[slot];
break;
case SlotPolicy::Selection::OUTFIT:
selectedArmor = outfit[slot];
break;
case SlotPolicy::Selection::EMPTY:
break;
}
if (!selectedArmor) continue;
occupiedMask |= static_cast<uint32_t>(selectedArmor->GetSlotMask());
result.emplace(selectedArmor);
}
return result;
};
proto::Outfit Outfit::save() const {
proto::Outfit out;
out.set_name(m_name);
for (const auto& armor : m_armors) {
if (armor)
out.add_armors(armor->formID);
}
out.set_is_favorite(m_favorited);
for (const auto& policy : m_slotPolicies) {
out.mutable_slot_policies()->emplace(static_cast<std::uint32_t>(policy.first), static_cast<std::uint32_t>(policy.second));
}
out.set_slot_policy(static_cast<std::uint32_t>(m_blanketSlotPolicy));
return out;
}
ArmorAddonOverrideService::ArmorAddonOverrideService(const proto::OutfitSystem& data, const SKSE::SerializationInterface* intfc) {
// Extract data from the protobuf struct.
enabled = data.enabled();
std::map<RE::RawActorHandle, ActorOutfitAssignments> actorOutfitAssignmentsLocal;
for (const auto& actorAssn : data.actor_outfit_assignments()) {
// Lookup the actor
std::uint64_t handle;
RE::NiPointer<RE::Actor> actor;
if (!intfc->ResolveHandle(actorAssn.first, handle))
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[static_cast<RE::RawActorHandle>(handle)] = assignments;
}
actorOutfitAssignments = actorOutfitAssignmentsLocal;
for (const auto& outfitData : data.outfits()) {
outfits.emplace(std::piecewise_construct,
std::forward_as_tuple(cobb::istring(outfitData.name().data(), outfitData.name().size())),
std::forward_as_tuple(outfitData, intfc));
}
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()) {
actorOutfitAssignments[RE::PlayerCharacter::GetSingleton()->GetHandle().native_handle()].currentOutfitName =
cobb::istring(data.obsolete_current_outfit_name().data(), data.obsolete_current_outfit_name().size());
for (const auto& locOutfitData : data.obsolete_location_based_outfits()) {
actorOutfitAssignments[RE::PlayerCharacter::GetSingleton()->GetHandle().native_handle()].locationOutfits.emplace(LocationType(locOutfitData.first),
cobb::istring(locOutfitData.second.data(), locOutfitData.second.size()));
}
}
}
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 outfits.at(name);
}
Outfit& ArmorAddonOverrideService::getOrCreateOutfit(const char* name) {
_validateNameOrThrow(name);
auto created = outfits.emplace(name, name);
if (created.second) created.first->second.setDefaultSlotPolicy();
return created.first->second;
}
//
void ArmorAddonOverrideService::addOutfit(const char* name) {
_validateNameOrThrow(name);
auto created = outfits.emplace(name, name);
if (created.second) created.first->second.setDefaultSlotPolicy();
}
Outfit& ArmorAddonOverrideService::currentOutfit(RE::RawActorHandle target) {
if (!actorOutfitAssignments.contains(target)) return g_noOutfit;
if (actorOutfitAssignments.at(target).currentOutfitName == g_noOutfitName) return g_noOutfit;
auto outfit = outfits.find(actorOutfitAssignments.at(target).currentOutfitName);
if (outfit == outfits.end()) return g_noOutfit;
return outfit->second;
};
bool ArmorAddonOverrideService::hasOutfit(const char* name) const {
return outfits.contains(name);
}
void ArmorAddonOverrideService::deleteOutfit(const char* name) {
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 = outfits.find(name);
if (outfit != outfits.end())
outfit->second.m_favorited = favorite;
}
void ArmorAddonOverrideService::modifyOutfit(const char* name,
std::vector<RE::TESObjectARMO*>& add,
std::vector<RE::TESObjectARMO*>& remove,
bool createIfMissing) {
try {
Outfit& target = getOutfit(name);
for (auto it = add.begin(); it != add.end(); ++it) {
auto armor = *it;
if (armor)
target.m_armors.insert(armor);
}
for (auto it = remove.begin(); it != remove.end(); ++it) {
auto armor = *it;
if (armor)
target.m_armors.erase(armor);
}
} catch (std::out_of_range) {
if (createIfMissing) {
addOutfit(name);
modifyOutfit(name, add, remove);
}
}
}
void ArmorAddonOverrideService::renameOutfit(const char* oldName, const char* newName) {
_validateNameOrThrow(newName);
if (outfits.contains(newName)) throw name_conflict("");
auto outfitNode = outfits.extract(oldName);
if (outfitNode.empty()) throw std::out_of_range("");
outfitNode.key() = newName;
outfitNode.mapped().m_name = newName;
outfits.insert(std::move(outfitNode));
for (auto& assignment : actorOutfitAssignments) {
if (assignment.second.currentOutfitName == oldName)
assignment.second.currentOutfitName = newName;
// If the outfit is assigned as a location outfit, remove it there as well.
for (auto& locationOutfit : assignment.second.locationOutfits) {
if (locationOutfit.second == oldName) {
assignment.second.locationOutfits[locationOutfit.first] = newName;
break;
}
}
}
}
void ArmorAddonOverrideService::setOutfit(const char* name, RE::RawActorHandle target) {
if (!actorOutfitAssignments.contains(target)) return;
if (strcmp(name, g_noOutfitName) == 0) {
actorOutfitAssignments.at(target).currentOutfitName = g_noOutfitName;
return;
}
try {
getOutfit(name);
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);
actorOutfitAssignments.at(target).currentOutfitName = g_noOutfitName;
}
}
void ArmorAddonOverrideService::addActor(RE::RawActorHandle target) {
if (actorOutfitAssignments.count(target) == 0)
actorOutfitAssignments.emplace(target, ActorOutfitAssignments());
}
void ArmorAddonOverrideService::removeActor(RE::RawActorHandle target) {
if (target == RE::PlayerCharacter::GetSingleton()->GetHandle().native_handle())
return;
actorOutfitAssignments.erase(target);
}
std::unordered_set<RE::RawActorHandle> ArmorAddonOverrideService::listActors() {
std::unordered_set<RE::RawActorHandle> actors;
for (auto& assignment : actorOutfitAssignments) {
actors.insert(assignment.first);
}
return actors;
}
void ArmorAddonOverrideService::setLocationBasedAutoSwitchEnabled(bool newValue) noexcept {
locationBasedAutoSwitchEnabled = newValue;
}
void ArmorAddonOverrideService::setOutfitUsingLocation(LocationType location, RE::RawActorHandle target) {
if (actorOutfitAssignments.count(target) == 0)
return;
auto it = actorOutfitAssignments.at(target).locationOutfits.find(location);
if (it != actorOutfitAssignments.at(target).locationOutfits.end()) {
setOutfit(it->second.c_str(), target);
}
}
void ArmorAddonOverrideService::setLocationOutfit(LocationType location, const char* name, RE::RawActorHandle target) {
if (actorOutfitAssignments.count(target) == 0)
return;
if (!std::string(name).empty()) {// Can never set outfit to the "" outfit. Use unsetLocationOutfit instead.
actorOutfitAssignments.at(target).locationOutfits[location] = name;
}
}
void ArmorAddonOverrideService::unsetLocationOutfit(LocationType location, RE::RawActorHandle target) {
if (actorOutfitAssignments.count(target) == 0)
return;
actorOutfitAssignments.at(target).locationOutfits.erase(location);
}
std::optional<cobb::istring> ArmorAddonOverrideService::getLocationOutfit(LocationType location, RE::RawActorHandle target) {
if (actorOutfitAssignments.count(target) == 0)
return std::optional<cobb::istring>();
;
auto it = actorOutfitAssignments.at(target).locationOutfits.find(location);
if (it != actorOutfitAssignments.at(target).locationOutfits.end()) {
return std::optional<cobb::istring>(it->second);
} else {
return std::optional<cobb::istring>();
}
}
#define CHECK_LOCATION(TYPE, CHECK_CODE) \
if (actorOutfitAssignments.at(target).locationOutfits.count(LocationType::TYPE) && (CHECK_CODE)) \
return std::optional<LocationType>(LocationType::TYPE);
std::optional<LocationType> ArmorAddonOverrideService::checkLocationType(const std::unordered_set<std::string>& keywords,
const WeatherFlags& weather_flags,
RE::RawActorHandle target) {
if (actorOutfitAssignments.count(target) == 0)
return std::optional<LocationType>();
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"));
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);
return std::optional<LocationType>();
}
bool ArmorAddonOverrideService::shouldOverride(RE::RawActorHandle target) const noexcept {
if (!enabled)
return false;
if (actorOutfitAssignments.count(target) == 0)
return false;
if (actorOutfitAssignments.at(target).currentOutfitName == g_noOutfitName)
return false;
return true;
}
void ArmorAddonOverrideService::getOutfitNames(std::vector<std::string>& out, bool favoritesOnly) const {
out.clear();
auto& list = outfits;
out.reserve(list.size());
for (auto it = list.cbegin(); it != list.cend(); ++it)
if (!favoritesOnly || it->second.m_favorited)
out.push_back(it->second.m_name);
}
void ArmorAddonOverrideService::setEnabled(bool flag) noexcept {
enabled = flag;
}
proto::OutfitSystem ArmorAddonOverrideService::save() {
proto::OutfitSystem out;
out.set_enabled(enabled);
for (const auto& actorAssn : actorOutfitAssignments) {
// Store a reference to the actor
std::uint64_t handle;
handle = actorAssn.first;
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<std::uint32_t>(lbo.first), std::string(lbo.second.data(), lbo.second.size())});
}
out.mutable_actor_outfit_assignments()->insert({handle, assnOut});
}
for (const auto& outfit : outfits) {
auto newOutfit = out.add_outfits();
*newOutfit = outfit.second.save();
}
out.set_location_based_auto_switch_enabled(locationBasedAutoSwitchEnabled);
return out;
}
//
void ArmorAddonOverrideService::dump() const {
LOG(info, "Dumping all state for ArmorAddonOverrideService...");
LOG(info, "Enabled: %d", enabled);
LOG(info, "We have %d outfits. Enumerating...", outfits.size());
for (auto it = outfits.begin(); it != outfits.end(); ++it) {
LOG(info, " - Key: %s", it->first.c_str());
LOG(info, " - Name: %s", it->second.m_name.c_str());
LOG(info, " - Armors:");
auto& list = it->second.m_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.");
throw load_error(err);
}
namespace SlotPolicy {
@ -502,6 +54,7 @@ namespace SlotPolicy {
}
}// namespace SlotPolicy
OutfitService& GetRustInstance() {
static rust::Box<OutfitService> instance = outfit_service_create();
return *instance;

View file

@ -31,7 +31,7 @@ namespace OutfitSystem {
std::uint32_t stackId,
RE::StaticFunctionTag*) {
LogExit exitPrint("GetOutfitNameMaxLength"sv);
return ArmorAddonOverrideService::ce_outfitNameMaxLength;
return GetRustInstance().max_outfit_name_len();
}
std::vector<RE::TESObjectARMO*> GetCarriedArmor(RE::BSScript::IVirtualMachine* registry,
std::uint32_t stackId,
@ -618,7 +618,7 @@ namespace OutfitSystem {
auto& service = GetRustInstance();
try {
service.add_outfit(name.data());
} catch (ArmorAddonOverrideService::bad_name) {
} catch (bad_name) {
registry->TraceStack("Invalid outfit name specified.", stackId, RE::BSScript::IVirtualMachine::Severity::kError);
return;
}
@ -808,7 +808,7 @@ namespace OutfitSystem {
if (ptr)
outfit->insert_armor(ptr);
}
} catch (ArmorAddonOverrideService::bad_name) {
} catch (bad_name) {
registry->TraceStack("Invalid outfit name specified.", stackId, RE::BSScript::IVirtualMachine::Severity::kError);
return;
}

View file

@ -150,10 +150,10 @@ void Callback_Messaging_SKSE(SKSE::MessagingInterface::Message* message) {
} else if (message->type == SKSE::MessagingInterface::kPostPostLoad) {
} else if (message->type == SKSE::MessagingInterface::kDataLoaded) {
} else if (message->type == SKSE::MessagingInterface::kNewGame) {
ArmorAddonOverrideService::GetInstance() = ArmorAddonOverrideService();
GetRustInstance().replace_with_new();
} else if (message->type == SKSE::MessagingInterface::kPreLoadGame) {
// AAOS::load resets as well, but this is needed in case the save we're about to load doesn't have any AAOS data.
ArmorAddonOverrideService::GetInstance() = ArmorAddonOverrideService();
GetRustInstance().replace_with_new();
}
}
@ -163,14 +163,14 @@ void _assertRead(bool result, const char* err);
void Callback_Serialization_Save(SKSE::SerializationInterface* intfc) {
LOG(info, "Writing savedata...");
//
if (intfc->OpenRecord(ArmorAddonOverrideService::signature, ArmorAddonOverrideService::kSaveVersionV5)) {
if (intfc->OpenRecord(Peristence::signature, Peristence::kSaveVersionV5)) {
try {
auto& service = ArmorAddonOverrideService::GetInstance();
const auto& data = service.save();
const auto& data_ser = data.SerializeAsString();
_assertWrite(intfc->WriteRecordData(data_ser.data(), static_cast<std::uint32_t>(data_ser.size())),
auto& service = GetRustInstance();
const auto data = service.save_proto_c();
if (data.empty()) throw save_error("Proto was zero size");
_assertWrite(intfc->WriteRecordData(data.data(), static_cast<std::uint32_t>(data.size())),
"Failed to write proto into SKSE record.");
} catch (const ArmorAddonOverrideService::save_error& exception) {
} catch (const save_error& exception) {
LOG(info, "Save FAILED for ArmorAddonOverrideService.");
LOG(info, " - Exception string: %s", exception.what());
}
@ -190,10 +190,10 @@ void Callback_Serialization_Load(SKSE::SerializationInterface* intfc) {
//
while (!error && intfc->GetNextRecordInfo(type, version, length)) {
switch (type) {
case ArmorAddonOverrideService::signature:
case Peristence::signature:
try {
auto& service = ArmorAddonOverrideService::GetInstance();
if (version >= ArmorAddonOverrideService::kSaveVersionV4) {
auto& service = GetRustInstance();
if (version >= Peristence::kSaveVersionV4) {
// Read data from protobuf.
std::vector<uint8_t> buf;
buf.insert(buf.begin(), length, 0);
@ -204,23 +204,20 @@ void Callback_Serialization_Load(SKSE::SerializationInterface* intfc) {
_assertRead(data.ParseFromArray(buf.data(), static_cast<int>(buf.size())),
"Failed to parse protobuf.");
// Load data from protobuf struct.
service = ArmorAddonOverrideService(data, intfc);
// Initialize the Rust AAOS
rust::Slice<const uint8_t> slice {buf.data(), buf.size()};
GetRustInstance().replace_with_proto_data_ptr(slice, intfc);
if (!service.replace_with_proto_data_ptr(slice, intfc)) {
throw load_error("failed to load from proto");
};
if (version == ArmorAddonOverrideService::kSaveVersionV4) {
if (version == Peristence::kSaveVersionV4) {
LOG(info, "Migrating outfit slot settings");
for (auto& outfit : service.outfits) {
outfit.second.setDefaultSlotPolicy();
}
service.reset_all_outfits_to_default_slot_policy();
}
} else {
LOG(err, "Legacy format not supported. Try upgrading through v0.4.0 first.");
}
} catch (const ArmorAddonOverrideService::load_error& exception) {
} catch (const load_error& exception) {
LOG(info, "Load FAILED for ArmorAddonOverrideService.");
LOG(info, " - Exception string: %s", exception.what());
}

View file

@ -81,8 +81,10 @@ mod ffi {
extern "Rust" {
type OutfitService;
fn outfit_service_create() -> Box<OutfitService>;
fn replace_with_new(self: &mut OutfitService);
unsafe fn replace_with_proto_data_ptr(self: &mut OutfitService, data: &[u8], intfc: *const SerializationInterface) -> bool;
unsafe fn replace_with_json_data(self: &mut OutfitService, data: &str, intfc: *const SerializationInterface) -> bool;
fn max_outfit_name_len(self: &OutfitService) -> u32;
fn get_outfit_ptr(self: &mut OutfitService, name: &str) -> *mut Outfit;
fn get_or_create_mut_outfit_ptr(self: &mut OutfitService, name: &str) -> *mut Outfit;
fn add_outfit(self: &mut OutfitService, name: &str);
@ -107,6 +109,7 @@ mod ffi {
fn get_outfit_names(self: &OutfitService, favorites_only: bool) -> Vec<String>;
fn set_enabled(self: &mut OutfitService, option: bool);
fn enabled_c(self: &OutfitService) -> bool;
fn reset_all_outfits_to_default_slot_policy(self: &mut OutfitService);
fn save_json_c(self: &mut OutfitService) -> String;
fn save_proto_c(self: &mut OutfitService) -> Vec<u8>;

View file

@ -217,6 +217,13 @@ impl OutfitService {
}
}
pub fn max_outfit_name_len(&self) -> u32 {
256
}
pub fn replace_with_new(&mut self) {
*self = Self::new();
}
pub unsafe fn replace_with_proto_data_ptr(self: &mut OutfitService,
data: &[u8],
intfc: *const SerializationInterface) -> bool {
@ -541,6 +548,12 @@ impl OutfitService {
out.location_based_auto_switch_enabled = self.location_switching_enabled;
out
}
pub fn reset_all_outfits_to_default_slot_policy(&mut self) {
for outfit in self.outfits.values_mut() {
outfit.reset_to_default_slot_policy();
}
}
}
pub mod slot_policy {