It's all done, but not tested.
This commit is contained in:
parent
939d10a985
commit
d9469bdd42
6 changed files with 53 additions and 578 deletions
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
37
src/main.cpp
37
src/main.cpp
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue