SkyrimOutfitSystemSE/include/ArmorAddonOverrideService.h
MetricExpansion 6d5ac0ea14 Format
2022-10-09 12:58:42 -07:00

153 lines
No EOL
6.5 KiB
C++

#pragma once
#include <set>
#include <unordered_map>
#include <vector>
//#include "skse64/PluginAPI.h"
#include "cobb/strings.h"
#include "outfit.pb.h"
namespace RE {
class TESObjectARMO;
}
enum class LocationType : std::uint32_t {
World = 0,
Town = 1,
Dungeon = 2,
City = 9,
WorldSnowy = 3,
TownSnowy = 4,
DungeonSnowy = 5,
CitySnowy = 10,
WorldRainy = 6,
TownRainy = 7,
DungeonRainy = 8,
CityRainy = 11
};
struct WeatherFlags {
bool snowy = false;
bool rainy = false;
};
struct Outfit {
Outfit(){};// we shouldn't need this, really, but std::unordered_map is a brat
Outfit(const char* n) : name(n), isFavorite(false), allowsPassthrough(false), requiresEquipped(false){};
Outfit(const Outfit& other) = default;
Outfit(const char* n, const Outfit& other) : name(n), isFavorite(false), allowsPassthrough(false), requiresEquipped(false) {
this->armors = other.armors;
}
std::string name;// can't be const; prevents assigning to Outfit vars
std::unordered_set<RE::TESObjectARMO*> armors;
bool isFavorite;
bool allowsPassthrough;
bool requiresEquipped;
bool conflictsWith(RE::TESObjectARMO*) const;
bool hasShield() const;
std::unordered_set<RE::TESObjectARMO*> computeDisplaySet(const std::unordered_set<RE::TESObjectARMO*>& equipped);
void load(const proto::Outfit& proto, const SKSE::SerializationInterface*);
void load_legacy(const SKSE::SerializationInterface* intfc, std::uint32_t version);// can throw ArmorAddonOverrideService::load_error
proto::Outfit save() const; // can throw ArmorAddonOverrideService::save_error
};
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:
typedef Outfit Outfit;
static constexpr std::uint32_t signature = 'AAOS';
enum {
kSaveVersionV1 = 1,
kSaveVersionV2 = 2,
kSaveVersionV3 = 3,
kSaveVersionV4 = 4// First version with protobuf
};
//
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
void addOutfit(const char* name, std::vector<RE::TESObjectARMO*> armors);// 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 setOutfitPassthrough(const char* name, bool allowPassthrough);
void setOutfitEquipRequired(const char* name, bool requiresEquipped);
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;
//
void refreshCurrentIfChanged(const char* testName);
//
void reset();
void load(const SKSE::SerializationInterface* intfc, const proto::OutfitSystem& data);// can throw load_error
void load_legacy(const SKSE::SerializationInterface* intfc, std::uint32_t version); // can throw load_error
proto::OutfitSystem save(); // can throw save_error
//
void dump() const;
};