mirror of
https://github.com/HarbourMasters/Shipwright
synced 2026-04-23 08:14:31 +00:00
Merge 59d4754a35 into 18bf4b315f
This commit is contained in:
commit
49c4ed5c30
|
|
@ -107,37 +107,11 @@ static const std::map<int32_t, const char*> cosmeticsRandomizerModes = {
|
|||
{ RANDOMIZE_ON_FILE_LOAD_SEEDED, "On File Load (Seeded)" },
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const char* cvar;
|
||||
const char* valuesCvar;
|
||||
const char* rainbowCvar;
|
||||
const char* lockedCvar;
|
||||
const char* changedCvar;
|
||||
std::string label;
|
||||
CosmeticGroup group;
|
||||
ImVec4 currentColor;
|
||||
Color_RGBA8 defaultColor;
|
||||
bool supportsAlpha;
|
||||
bool supportsRainbow;
|
||||
bool advancedOption;
|
||||
} CosmeticOption;
|
||||
|
||||
Color_RGBA8 ColorRGBA8(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
|
||||
Color_RGBA8 color = { r, g, b, a };
|
||||
return color;
|
||||
}
|
||||
|
||||
#define COSMETIC_OPTION(id, label, group, defaultColor, supportsAlpha, supportsRainbow, advancedOption) \
|
||||
{ \
|
||||
id, { \
|
||||
CVAR_COSMETIC(id), CVAR_COSMETIC(id ".Value"), CVAR_COSMETIC(id ".Rainbow"), CVAR_COSMETIC(id ".Locked"), \
|
||||
CVAR_COSMETIC(id ".Changed"), label, group, \
|
||||
ImVec4(defaultColor.r / 255.0f, defaultColor.g / 255.0f, defaultColor.b / 255.0f, \
|
||||
defaultColor.a / 255.0f), \
|
||||
defaultColor, supportsAlpha, supportsRainbow, advancedOption \
|
||||
} \
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
/*
|
||||
So, you would like to add a new cosmetic option? BUCKLE UP
|
||||
|
|
@ -212,7 +186,7 @@ Color_RGBA8 ColorRGBA8(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
|
|||
in the moon cosmetic, where for the gDPSetEnvColor color we are halving the RGB values, to make them a bit darker similar to how the original
|
||||
colors were darker than the gDPSetPrimColor. You will see many more examples of this below in the `ApplyOrResetCustomGfxPatches` method
|
||||
*/
|
||||
static std::map<std::string, CosmeticOption> cosmeticOptions = {
|
||||
std::map<std::string, CosmeticOption> cosmeticOptions = {
|
||||
COSMETIC_OPTION("Link.KokiriTunic", "Kokiri Tunic", COSMETICS_GROUP_LINK, ColorRGBA8( 30, 105, 27, 255), false, true, false),
|
||||
COSMETIC_OPTION("Link.GoronTunic", "Goron Tunic", COSMETICS_GROUP_LINK, ColorRGBA8(100, 20, 0, 255), false, true, false),
|
||||
COSMETIC_OPTION("Link.ZoraTunic", "Zora Tunic", COSMETICS_GROUP_LINK, ColorRGBA8( 0, 60, 100, 255), false, true, false),
|
||||
|
|
@ -561,7 +535,10 @@ void CosmeticsUpdateTick() {
|
|||
index += static_cast<int>(60 * rainbowSpeed);
|
||||
}
|
||||
}
|
||||
UpdateCustomCosmeticsRainbow(hue, rainbowSpeed, index);
|
||||
|
||||
ApplyOrResetCustomGfxPatches(false);
|
||||
ApplyCustomCosmetics();
|
||||
hue++;
|
||||
if (hue >= (360 * rainbowSpeed)) {
|
||||
hue = 0;
|
||||
|
|
@ -2147,68 +2124,6 @@ void RandomizeColor(CosmeticOption& cosmeticOption, bool manual = true) {
|
|||
ApplySideEffects(cosmeticOption);
|
||||
}
|
||||
|
||||
void ResetColor(CosmeticOption& cosmeticOption) {
|
||||
Color_RGBA8 defaultColor = { cosmeticOption.defaultColor.r, cosmeticOption.defaultColor.g,
|
||||
cosmeticOption.defaultColor.b, cosmeticOption.defaultColor.a };
|
||||
cosmeticOption.currentColor.x = defaultColor.r / 255.0f;
|
||||
cosmeticOption.currentColor.y = defaultColor.g / 255.0f;
|
||||
cosmeticOption.currentColor.z = defaultColor.b / 255.0f;
|
||||
cosmeticOption.currentColor.w = defaultColor.a / 255.0f;
|
||||
|
||||
CVarClear(cosmeticOption.changedCvar);
|
||||
CVarClear(cosmeticOption.rainbowCvar);
|
||||
CVarClear(cosmeticOption.lockedCvar);
|
||||
CVarClear(cosmeticOption.valuesCvar);
|
||||
CVarClear((std::string(cosmeticOption.valuesCvar) + ".R").c_str());
|
||||
CVarClear((std::string(cosmeticOption.valuesCvar) + ".G").c_str());
|
||||
CVarClear((std::string(cosmeticOption.valuesCvar) + ".B").c_str());
|
||||
CVarClear((std::string(cosmeticOption.valuesCvar) + ".A").c_str());
|
||||
CVarClear((std::string(cosmeticOption.valuesCvar) + ".Type").c_str());
|
||||
|
||||
// This portion should match 1:1 the multiplied colors in `ApplySideEffect()`
|
||||
if (cosmeticOption.label == "Bow Body") {
|
||||
ResetColor(cosmeticOptions.at("Equipment.BowTips"));
|
||||
ResetColor(cosmeticOptions.at("Equipment.BowHandle"));
|
||||
} else if (cosmeticOption.label == "Idle Primary") {
|
||||
ResetColor(cosmeticOptions.at("Navi.IdleSecondary"));
|
||||
} else if (cosmeticOption.label == "Enemy Primary") {
|
||||
ResetColor(cosmeticOptions.at("Navi.EnemySecondary"));
|
||||
} else if (cosmeticOption.label == "NPC Primary") {
|
||||
ResetColor(cosmeticOptions.at("Navi.NPCSecondary"));
|
||||
} else if (cosmeticOption.label == "Props Primary") {
|
||||
ResetColor(cosmeticOptions.at("Navi.PropsSecondary"));
|
||||
} else if (cosmeticOption.label == "Level 1 Secondary") {
|
||||
ResetColor(cosmeticOptions.at("SpinAttack.Level1Primary"));
|
||||
} else if (cosmeticOption.label == "Level 2 Secondary") {
|
||||
ResetColor(cosmeticOptions.at("SpinAttack.Level2Primary"));
|
||||
} else if (cosmeticOption.label == "Item Select Color") {
|
||||
ResetColor(cosmeticOptions.at("Kaleido.ItemSelB"));
|
||||
ResetColor(cosmeticOptions.at("Kaleido.ItemSelC"));
|
||||
ResetColor(cosmeticOptions.at("Kaleido.ItemSelD"));
|
||||
} else if (cosmeticOption.label == "Equip Select Color") {
|
||||
ResetColor(cosmeticOptions.at("Kaleido.EquipSelB"));
|
||||
ResetColor(cosmeticOptions.at("Kaleido.EquipSelC"));
|
||||
ResetColor(cosmeticOptions.at("Kaleido.EquipSelD"));
|
||||
} else if (cosmeticOption.label == "Map Dungeon Color") {
|
||||
ResetColor(cosmeticOptions.at("Kaleido.MapSelDunB"));
|
||||
ResetColor(cosmeticOptions.at("Kaleido.MapSelDunC"));
|
||||
ResetColor(cosmeticOptions.at("Kaleido.MapSelDunD"));
|
||||
} else if (cosmeticOption.label == "Quest Status Color") {
|
||||
ResetColor(cosmeticOptions.at("Kaleido.QuestStatusB"));
|
||||
ResetColor(cosmeticOptions.at("Kaleido.QuestStatusC"));
|
||||
ResetColor(cosmeticOptions.at("Kaleido.QuestStatusD"));
|
||||
} else if (cosmeticOption.label == "Map Color") {
|
||||
ResetColor(cosmeticOptions.at("Kaleido.MapSelectB"));
|
||||
ResetColor(cosmeticOptions.at("Kaleido.MapSelectC"));
|
||||
ResetColor(cosmeticOptions.at("Kaleido.MapSelectD"));
|
||||
} else if (cosmeticOption.label == "Save Color") {
|
||||
ResetColor(cosmeticOptions.at("Kaleido.SaveB"));
|
||||
ResetColor(cosmeticOptions.at("Kaleido.SaveC"));
|
||||
ResetColor(cosmeticOptions.at("Kaleido.SaveD"));
|
||||
}
|
||||
ShipInit::Init(cosmeticOption.valuesCvar);
|
||||
}
|
||||
|
||||
void DrawCosmeticRow(CosmeticOption& cosmeticOption) {
|
||||
if (UIWidgets::CVarColorPicker(cosmeticOption.label.c_str(), cosmeticOption.cvar, cosmeticOption.defaultColor,
|
||||
cosmeticOption.supportsAlpha, 0, THEME_COLOR)) {
|
||||
|
|
@ -2508,6 +2423,14 @@ void CosmeticsEditorWindow::DrawElement() {
|
|||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (HasCustomCosmetics() && ImGui::BeginTabItem("Mods")) {
|
||||
|
||||
UIWidgets::Separator(true, true, 2.0f, 2.0f);
|
||||
|
||||
DrawCustomCosmetics();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (ImGui::BeginTabItem("Keys")) {
|
||||
|
||||
ImGui::BeginDisabled(CVarGetInteger(CVAR_SETTING("DisableChanges"), 0));
|
||||
|
|
@ -2622,8 +2545,10 @@ void CosmeticsEditorWindow::InitElement() {
|
|||
cosmeticOption.currentColor.w = cvarColor.a / 255.0f;
|
||||
}
|
||||
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
|
||||
ScanCustomCosmetics();
|
||||
ApplyOrResetCustomGfxPatches();
|
||||
ApplyAuthenticGfxPatches();
|
||||
ApplyCustomCosmetics();
|
||||
}
|
||||
|
||||
void CosmeticsEditor_RandomizeAll() {
|
||||
|
|
@ -2648,6 +2573,7 @@ void CosmeticsEditor_AutoRandomizeAll() {
|
|||
|
||||
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
|
||||
ApplyOrResetCustomGfxPatches();
|
||||
ApplyCustomCosmetics();
|
||||
}
|
||||
|
||||
void CosmeticsEditor_RandomizeGroup(CosmeticGroup group) {
|
||||
|
|
@ -2691,7 +2617,10 @@ void RegisterCosmeticHooks() {
|
|||
[]() { CosmeticsEditor_AutoRandomizeAll(); });
|
||||
|
||||
COND_HOOK(OnLoadGame, CVarGetInteger(CVAR_COSMETIC("RandomizeCosmeticsGenModes"), RANDOMIZE_OFF) == RANDOMIZE_OFF,
|
||||
[](s32 fileNum) { ApplyOrResetCustomGfxPatches(); });
|
||||
[](s32 fileNum) {
|
||||
ApplyOrResetCustomGfxPatches();
|
||||
ApplyCustomCosmetics();
|
||||
});
|
||||
|
||||
COND_HOOK(OnLoadGame,
|
||||
CVarGetInteger(CVAR_COSMETIC("RandomizeCosmeticsGenModes"), RANDOMIZE_OFF) == RANDOMIZE_ON_FILE_LOAD,
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ typedef enum {
|
|||
} CosmeticGroup;
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include "soh/SohGui/UIWidgets.hpp"
|
||||
extern "C" {
|
||||
#endif //__cplusplus
|
||||
|
||||
|
|
@ -38,6 +41,114 @@ Color_RGBA8 CosmeticsEditor_GetDefaultValue(const char* id);
|
|||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
#define COSMETIC_OPTION(id, label, group, defaultColor, supportsAlpha, supportsRainbow, advancedOption) \
|
||||
{ \
|
||||
id, { \
|
||||
CVAR_COSMETIC(id), CVAR_COSMETIC(id ".Value"), CVAR_COSMETIC(id ".Rainbow"), CVAR_COSMETIC(id ".Locked"), \
|
||||
CVAR_COSMETIC(id ".Changed"), label, group, \
|
||||
ImVec4(defaultColor.r / 255.0f, defaultColor.g / 255.0f, defaultColor.b / 255.0f, \
|
||||
defaultColor.a / 255.0f), \
|
||||
defaultColor, supportsAlpha, supportsRainbow, advancedOption \
|
||||
} \
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const char* cvar;
|
||||
const char* valuesCvar;
|
||||
const char* rainbowCvar;
|
||||
const char* lockedCvar;
|
||||
const char* changedCvar;
|
||||
std::string label;
|
||||
CosmeticGroup group;
|
||||
ImVec4 currentColor;
|
||||
Color_RGBA8 defaultColor;
|
||||
bool supportsAlpha;
|
||||
bool supportsRainbow;
|
||||
bool advancedOption;
|
||||
} CosmeticOption;
|
||||
|
||||
extern std::map<std::string, CosmeticOption> cosmeticOptions;
|
||||
|
||||
inline void ResetColor(CosmeticOption& cosmeticOption) {
|
||||
Color_RGBA8 defaultColor = { cosmeticOption.defaultColor.r, cosmeticOption.defaultColor.g,
|
||||
cosmeticOption.defaultColor.b, cosmeticOption.defaultColor.a };
|
||||
cosmeticOption.currentColor.x = defaultColor.r / 255.0f;
|
||||
cosmeticOption.currentColor.y = defaultColor.g / 255.0f;
|
||||
cosmeticOption.currentColor.z = defaultColor.b / 255.0f;
|
||||
cosmeticOption.currentColor.w = defaultColor.a / 255.0f;
|
||||
|
||||
CVarClear(cosmeticOption.changedCvar);
|
||||
CVarClear(cosmeticOption.rainbowCvar);
|
||||
CVarClear(cosmeticOption.lockedCvar);
|
||||
CVarClear(cosmeticOption.valuesCvar);
|
||||
CVarClear((std::string(cosmeticOption.valuesCvar) + ".R").c_str());
|
||||
CVarClear((std::string(cosmeticOption.valuesCvar) + ".G").c_str());
|
||||
CVarClear((std::string(cosmeticOption.valuesCvar) + ".B").c_str());
|
||||
CVarClear((std::string(cosmeticOption.valuesCvar) + ".A").c_str());
|
||||
CVarClear((std::string(cosmeticOption.valuesCvar) + ".Type").c_str());
|
||||
|
||||
if (cosmeticOption.label == "Bow Body") {
|
||||
ResetColor(cosmeticOptions.at("Equipment.BowTips"));
|
||||
ResetColor(cosmeticOptions.at("Equipment.BowHandle"));
|
||||
} else if (cosmeticOption.label == "Idle Primary") {
|
||||
ResetColor(cosmeticOptions.at("Navi.IdleSecondary"));
|
||||
} else if (cosmeticOption.label == "Enemy Primary") {
|
||||
ResetColor(cosmeticOptions.at("Navi.EnemySecondary"));
|
||||
} else if (cosmeticOption.label == "NPC Primary") {
|
||||
ResetColor(cosmeticOptions.at("Navi.NPCSecondary"));
|
||||
} else if (cosmeticOption.label == "Props Primary") {
|
||||
ResetColor(cosmeticOptions.at("Navi.PropsSecondary"));
|
||||
} else if (cosmeticOption.label == "Level 1 Secondary") {
|
||||
ResetColor(cosmeticOptions.at("SpinAttack.Level1Primary"));
|
||||
} else if (cosmeticOption.label == "Level 2 Secondary") {
|
||||
ResetColor(cosmeticOptions.at("SpinAttack.Level2Primary"));
|
||||
} else if (cosmeticOption.label == "Item Select Color") {
|
||||
ResetColor(cosmeticOptions.at("Kaleido.ItemSelB"));
|
||||
ResetColor(cosmeticOptions.at("Kaleido.ItemSelC"));
|
||||
ResetColor(cosmeticOptions.at("Kaleido.ItemSelD"));
|
||||
} else if (cosmeticOption.label == "Equip Select Color") {
|
||||
ResetColor(cosmeticOptions.at("Kaleido.EquipSelB"));
|
||||
ResetColor(cosmeticOptions.at("Kaleido.EquipSelC"));
|
||||
ResetColor(cosmeticOptions.at("Kaleido.EquipSelD"));
|
||||
} else if (cosmeticOption.label == "Map Dungeon Color") {
|
||||
ResetColor(cosmeticOptions.at("Kaleido.MapSelDunB"));
|
||||
ResetColor(cosmeticOptions.at("Kaleido.MapSelDunC"));
|
||||
ResetColor(cosmeticOptions.at("Kaleido.MapSelDunD"));
|
||||
} else if (cosmeticOption.label == "Quest Status Color") {
|
||||
ResetColor(cosmeticOptions.at("Kaleido.QuestStatusB"));
|
||||
ResetColor(cosmeticOptions.at("Kaleido.QuestStatusC"));
|
||||
ResetColor(cosmeticOptions.at("Kaleido.QuestStatusD"));
|
||||
} else if (cosmeticOption.label == "Map Color") {
|
||||
ResetColor(cosmeticOptions.at("Kaleido.MapSelectB"));
|
||||
ResetColor(cosmeticOptions.at("Kaleido.MapSelectC"));
|
||||
ResetColor(cosmeticOptions.at("Kaleido.MapSelectD"));
|
||||
} else if (cosmeticOption.label == "Save Color") {
|
||||
ResetColor(cosmeticOptions.at("Kaleido.SaveB"));
|
||||
ResetColor(cosmeticOptions.at("Kaleido.SaveC"));
|
||||
ResetColor(cosmeticOptions.at("Kaleido.SaveD"));
|
||||
}
|
||||
ShipInit::Init(cosmeticOption.valuesCvar);
|
||||
}
|
||||
|
||||
inline CosmeticOption MakeCosmeticOption(const char* cvar, const char* valuesCvar, const char* rainbowCvar,
|
||||
const char* lockedCvar, const char* changedCvar, const char* label,
|
||||
CosmeticGroup group, Color_RGBA8 defaultColor, bool supportsAlpha,
|
||||
bool supportsRainbow, bool advancedOption) {
|
||||
return CosmeticOption{ cvar,
|
||||
valuesCvar,
|
||||
rainbowCvar,
|
||||
lockedCvar,
|
||||
changedCvar,
|
||||
label,
|
||||
group,
|
||||
ImVec4(defaultColor.r / 255.0f, defaultColor.g / 255.0f, defaultColor.b / 255.0f,
|
||||
defaultColor.a / 255.0f),
|
||||
defaultColor,
|
||||
supportsAlpha,
|
||||
supportsRainbow,
|
||||
advancedOption };
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const std::string Name;
|
||||
const std::string ToolTip;
|
||||
|
|
@ -60,6 +171,11 @@ void CosmeticsEditor_RandomizeGroup(CosmeticGroup group);
|
|||
void CosmeticsEditor_ResetAll();
|
||||
void CosmeticsEditor_ResetGroup(CosmeticGroup group);
|
||||
void ApplyOrResetCustomGfxPatches(bool manualChange = true);
|
||||
void ScanCustomCosmetics();
|
||||
bool HasCustomCosmetics();
|
||||
void DrawCustomCosmetics();
|
||||
void ApplyCustomCosmetics();
|
||||
void UpdateCustomCosmeticsRainbow(int hue, float rainbowSpeed, int& index);
|
||||
|
||||
class CosmeticsEditorWindow final : public Ship::GuiWindow {
|
||||
public:
|
||||
|
|
|
|||
430
soh/soh/Enhancements/cosmetics/DynamicCosmeticsEditor.cpp
Normal file
430
soh/soh/Enhancements/cosmetics/DynamicCosmeticsEditor.cpp
Normal file
|
|
@ -0,0 +1,430 @@
|
|||
#include "CosmeticsEditor.h"
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <math.h>
|
||||
#include <tinyxml2.h>
|
||||
#include <fast/resource/type/DisplayList.h>
|
||||
#include <ship/resource/archive/Archive.h>
|
||||
|
||||
#include "soh/SohGui/UIWidgets.hpp"
|
||||
#include "soh/SohGui/SohGui.hpp"
|
||||
#include "soh/OTRGlobals.h"
|
||||
|
||||
extern "C" {
|
||||
#include "macros.h"
|
||||
#include "soh/cvar_prefixes.h"
|
||||
}
|
||||
|
||||
static constexpr const char* CUSTOM_COSMETIC_GROUP = "Custom";
|
||||
static constexpr const char* CUSTOM_CVAR_PREFIX = "gCosmetics.Custom.";
|
||||
|
||||
struct CustomCosmeticBinding {
|
||||
std::string materialPath;
|
||||
size_t commandIndex = 0;
|
||||
bool isPrimColor = true;
|
||||
uint8_t defaultA = 255;
|
||||
uint8_t primM = 0;
|
||||
uint8_t primL = 0;
|
||||
};
|
||||
|
||||
struct CustomCosmeticEntry {
|
||||
CosmeticOption option;
|
||||
std::string baseCvar;
|
||||
std::string valuesCvar;
|
||||
std::string rainbowCvar;
|
||||
std::string lockedCvar;
|
||||
std::string changedCvar;
|
||||
std::string category;
|
||||
std::vector<CustomCosmeticBinding> bindings;
|
||||
};
|
||||
|
||||
static std::vector<CustomCosmeticEntry> customCosmeticEntries;
|
||||
|
||||
static bool IsCustomArchive(const std::shared_ptr<Ship::Archive>& archive) {
|
||||
if (archive == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& archivePath = archive->GetPath();
|
||||
return archivePath.find("\\mods\\") != std::string::npos || archivePath.find("/mods/") != std::string::npos;
|
||||
}
|
||||
|
||||
static int GetCustomMaterialSortOrder(const std::string& materialPath) {
|
||||
if (materialPath.starts_with("objects/object_link_child/") ||
|
||||
materialPath.starts_with("__OTR__objects/object_link_child/")) {
|
||||
return 0;
|
||||
}
|
||||
if (materialPath.starts_with("objects/object_link_boy/") ||
|
||||
materialPath.starts_with("__OTR__objects/object_link_boy/")) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
static void SanitizeCustomKey(std::string& value) {
|
||||
for (auto it = value.begin(); it != value.end();) {
|
||||
if (!std::isalnum(static_cast<unsigned char>(*it))) {
|
||||
it = value.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool TryLoadCustomDisplayListXml(Ship::ArchiveManager* archiveManager, Ship::ResourceManager* resourceManager,
|
||||
const std::string& materialPath, tinyxml2::XMLDocument& document,
|
||||
std::shared_ptr<Fast::DisplayList>& material, tinyxml2::XMLElement*& root) {
|
||||
auto file = archiveManager->LoadFile(materialPath);
|
||||
if (file == nullptr || !file->IsLoaded || file->Buffer == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
document.Parse(file->Buffer->data(), file->Buffer->size());
|
||||
if (document.Error()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
root = document.FirstChildElement();
|
||||
if (root == nullptr || std::string(root->Name()) != "DisplayList") {
|
||||
return false;
|
||||
}
|
||||
|
||||
material = std::dynamic_pointer_cast<Fast::DisplayList>(resourceManager->LoadResource(materialPath));
|
||||
return material != nullptr;
|
||||
}
|
||||
|
||||
static size_t FindDisplayListInstructionIndex(const Fast::DisplayList& displayList, const Gfx& expected,
|
||||
size_t searchStart) {
|
||||
for (size_t i = searchStart; i < displayList.Instructions.size(); i++) {
|
||||
const Gfx& current = displayList.Instructions[i];
|
||||
if (current.words.w0 == expected.words.w0 && current.words.w1 == expected.words.w1) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
static Color_RGBA8 GetCustomCosmeticColor(const CustomCosmeticEntry& entry) {
|
||||
if (CVarGetInteger(entry.option.changedCvar, 0)) {
|
||||
return CVarGetColor(entry.option.valuesCvar, entry.option.defaultColor);
|
||||
}
|
||||
|
||||
return entry.option.defaultColor;
|
||||
}
|
||||
|
||||
void ApplyCustomCosmetics() {
|
||||
auto resourceManager = Ship::Context::GetInstance()->GetResourceManager();
|
||||
auto archiveManager = resourceManager->GetArchiveManager();
|
||||
|
||||
for (auto& entry : customCosmeticEntries) {
|
||||
Color_RGBA8 color = GetCustomCosmeticColor(entry);
|
||||
|
||||
for (const auto& binding : entry.bindings) {
|
||||
if (!IsCustomArchive(archiveManager->GetArchiveFromFile(binding.materialPath))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto material =
|
||||
std::dynamic_pointer_cast<Fast::DisplayList>(resourceManager->LoadResource(binding.materialPath));
|
||||
if (material == nullptr || binding.commandIndex >= material->Instructions.size()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (binding.isPrimColor) {
|
||||
material->Instructions[binding.commandIndex] =
|
||||
gsDPSetPrimColor(binding.primM, binding.primL, color.r, color.g, color.b, binding.defaultA);
|
||||
} else {
|
||||
material->Instructions[binding.commandIndex] =
|
||||
gsDPSetEnvColor(color.r, color.g, color.b, binding.defaultA);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void SetCustomCosmeticColor(const CustomCosmeticEntry& entry, Color_RGBA8 color) {
|
||||
CVarSetColor(entry.option.valuesCvar, color);
|
||||
CVarSetInteger(entry.option.rainbowCvar, 0);
|
||||
CVarSetInteger(entry.option.changedCvar, 1);
|
||||
ShipInit::Init(entry.option.valuesCvar);
|
||||
ShipInit::Init(entry.option.rainbowCvar);
|
||||
ShipInit::Init(entry.option.changedCvar);
|
||||
ApplyCustomCosmetics();
|
||||
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
|
||||
}
|
||||
|
||||
static void ResetCustomCosmeticColor(const CustomCosmeticEntry& entry) {
|
||||
ResetColor(const_cast<CosmeticOption&>(entry.option));
|
||||
ApplyCustomCosmetics();
|
||||
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
|
||||
}
|
||||
|
||||
static void RandomizeCustomCosmeticColor(const CustomCosmeticEntry& entry) {
|
||||
Color_RGBA8 color = { static_cast<uint8_t>(rand() % 256), static_cast<uint8_t>(rand() % 256),
|
||||
static_cast<uint8_t>(rand() % 256), 255 };
|
||||
SetCustomCosmeticColor(entry, color);
|
||||
}
|
||||
|
||||
static void DrawCustomCosmeticColorRow(const char* label, const char* cvar, Color_RGBA8 defaultColor,
|
||||
const char* rainbowCvar, const char* lockedCvar, const char* changedCvar,
|
||||
const std::function<void()>& onColorChanged,
|
||||
const std::function<void()>& onRandomize,
|
||||
const std::function<void()>& onRainbowToggle,
|
||||
const std::function<void()>& onReset) {
|
||||
if (UIWidgets::CVarColorPicker(label, cvar, defaultColor, false, 0, THEME_COLOR)) {
|
||||
onColorChanged();
|
||||
}
|
||||
|
||||
ImGui::SameLine((ImGui::CalcTextSize("Message Light Blue (None No Shadow)").x * 1.0f) + 60.0f);
|
||||
if (UIWidgets::Button(
|
||||
("Random##" + std::string(label)).c_str(),
|
||||
UIWidgets::ButtonOptions().Size(ImVec2(80, 31)).Padding(ImVec2(2.0f, 0.0f)).Color(THEME_COLOR))) {
|
||||
onRandomize();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (UIWidgets::CVarCheckbox(("Rainbow##" + std::string(label)).c_str(), rainbowCvar,
|
||||
UIWidgets::CheckboxOptions().Color(THEME_COLOR))) {
|
||||
onRainbowToggle();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
UIWidgets::CVarCheckbox(("Locked##" + std::string(label)).c_str(), lockedCvar,
|
||||
UIWidgets::CheckboxOptions().Color(THEME_COLOR));
|
||||
|
||||
if (CVarGetInteger(changedCvar, 0)) {
|
||||
ImGui::SameLine();
|
||||
if (UIWidgets::Button(("Reset##" + std::string(label)).c_str(),
|
||||
UIWidgets::ButtonOptions().Size(ImVec2(80, 31)).Padding(ImVec2(2.0f, 0.0f)))) {
|
||||
onReset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScanCustomCosmetics() {
|
||||
customCosmeticEntries.clear();
|
||||
|
||||
auto resourceManager = Ship::Context::GetInstance()->GetResourceManager();
|
||||
auto archiveManager = resourceManager->GetArchiveManager();
|
||||
auto materialPaths = archiveManager->ListFiles("*");
|
||||
std::unordered_map<std::string, size_t> entryIndicesByKey;
|
||||
|
||||
for (const auto& materialPath : *materialPaths) {
|
||||
if (!IsCustomArchive(archiveManager->GetArchiveFromFile(materialPath))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
tinyxml2::XMLDocument document;
|
||||
std::shared_ptr<Fast::DisplayList> material;
|
||||
tinyxml2::XMLElement* root = nullptr;
|
||||
if (!TryLoadCustomDisplayListXml(archiveManager.get(), resourceManager.get(), materialPath, document, material,
|
||||
root)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t searchStart = 0;
|
||||
for (auto* child = root->FirstChildElement(); child != nullptr; child = child->NextSiblingElement()) {
|
||||
std::string childName = child->Name();
|
||||
bool isPrimColor = childName == "SetPrimColor";
|
||||
if (!isPrimColor && childName != "SetEnvColor") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const char* cosmeticEntry = child->Attribute("CosmeticEntry");
|
||||
const char* cosmeticCategory = child->Attribute("CosmeticCategory");
|
||||
if (cosmeticEntry == nullptr || cosmeticEntry[0] == '\0') {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string key = cosmeticEntry;
|
||||
SanitizeCustomKey(key);
|
||||
if (key.empty()) {
|
||||
continue;
|
||||
}
|
||||
Gfx expectedInstruction;
|
||||
if (isPrimColor) {
|
||||
expectedInstruction =
|
||||
gsDPSetPrimColor(child->IntAttribute("M"), child->IntAttribute("L"), child->IntAttribute("R"),
|
||||
child->IntAttribute("G"), child->IntAttribute("B"), child->IntAttribute("A"));
|
||||
} else {
|
||||
expectedInstruction = gsDPSetEnvColor(child->IntAttribute("R"), child->IntAttribute("G"),
|
||||
child->IntAttribute("B"), child->IntAttribute("A"));
|
||||
}
|
||||
|
||||
size_t commandIndex = FindDisplayListInstructionIndex(*material, expectedInstruction, searchStart);
|
||||
if (commandIndex == SIZE_MAX) {
|
||||
continue;
|
||||
}
|
||||
searchStart = commandIndex + 1;
|
||||
|
||||
size_t entryIndex = 0;
|
||||
if (auto it = entryIndicesByKey.find(key); it != entryIndicesByKey.end()) {
|
||||
entryIndex = it->second;
|
||||
} else {
|
||||
entryIndex = customCosmeticEntries.size();
|
||||
entryIndicesByKey[key] = entryIndex;
|
||||
|
||||
CustomCosmeticEntry entry;
|
||||
entry.category = (cosmeticCategory != nullptr) ? cosmeticCategory : "";
|
||||
entry.baseCvar = std::string(CUSTOM_CVAR_PREFIX) + key;
|
||||
entry.valuesCvar = entry.baseCvar + ".Value";
|
||||
entry.rainbowCvar = entry.baseCvar + ".Rainbow";
|
||||
entry.lockedCvar = entry.baseCvar + ".Locked";
|
||||
entry.changedCvar = entry.baseCvar + ".Changed";
|
||||
const Color_RGBA8 defaultColor = { static_cast<uint8_t>(child->IntAttribute("R")),
|
||||
static_cast<uint8_t>(child->IntAttribute("G")),
|
||||
static_cast<uint8_t>(child->IntAttribute("B")),
|
||||
static_cast<uint8_t>(child->IntAttribute("A")) };
|
||||
entry.option =
|
||||
MakeCosmeticOption(entry.baseCvar.c_str(), entry.valuesCvar.c_str(), entry.rainbowCvar.c_str(),
|
||||
entry.lockedCvar.c_str(), entry.changedCvar.c_str(), cosmeticEntry,
|
||||
COSMETICS_GROUP_MAX, defaultColor, false, true, false);
|
||||
customCosmeticEntries.push_back(std::move(entry));
|
||||
}
|
||||
|
||||
CustomCosmeticBinding binding;
|
||||
binding.materialPath = materialPath;
|
||||
binding.commandIndex = commandIndex;
|
||||
binding.isPrimColor = isPrimColor;
|
||||
binding.defaultA = static_cast<uint8_t>(child->IntAttribute("A"));
|
||||
binding.primM = static_cast<uint8_t>(child->IntAttribute("M"));
|
||||
binding.primL = static_cast<uint8_t>(child->IntAttribute("L"));
|
||||
customCosmeticEntries[entryIndex].bindings.push_back(std::move(binding));
|
||||
}
|
||||
}
|
||||
|
||||
std::stable_sort(customCosmeticEntries.begin(), customCosmeticEntries.end(),
|
||||
[](const CustomCosmeticEntry& lhs, const CustomCosmeticEntry& rhs) {
|
||||
int lhsOrder = 2;
|
||||
int rhsOrder = 2;
|
||||
|
||||
for (const auto& binding : lhs.bindings) {
|
||||
lhsOrder = std::min(lhsOrder, GetCustomMaterialSortOrder(binding.materialPath));
|
||||
}
|
||||
for (const auto& binding : rhs.bindings) {
|
||||
rhsOrder = std::min(rhsOrder, GetCustomMaterialSortOrder(binding.materialPath));
|
||||
}
|
||||
|
||||
if (lhsOrder != rhsOrder) {
|
||||
return lhsOrder < rhsOrder;
|
||||
}
|
||||
|
||||
if (lhs.category.empty() != rhs.category.empty()) {
|
||||
return !lhs.category.empty();
|
||||
}
|
||||
|
||||
if (lhs.category != rhs.category) {
|
||||
return lhs.category < rhs.category;
|
||||
}
|
||||
|
||||
return lhs.option.label < rhs.option.label;
|
||||
});
|
||||
|
||||
ApplyCustomCosmetics();
|
||||
}
|
||||
|
||||
static void DrawCustomCosmeticRow(const CustomCosmeticEntry& entry) {
|
||||
const char* cvar = entry.option.cvar;
|
||||
|
||||
DrawCustomCosmeticColorRow(
|
||||
entry.option.label.c_str(), cvar, entry.option.defaultColor, entry.option.rainbowCvar, entry.option.lockedCvar,
|
||||
entry.option.changedCvar,
|
||||
[&entry]() {
|
||||
CVarSetInteger(entry.option.changedCvar, 1);
|
||||
ShipInit::Init(entry.option.changedCvar);
|
||||
ApplyCustomCosmetics();
|
||||
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
|
||||
},
|
||||
[&entry]() { RandomizeCustomCosmeticColor(entry); },
|
||||
[&entry]() {
|
||||
CVarSetInteger(entry.option.changedCvar, 1);
|
||||
ShipInit::Init(entry.option.changedCvar);
|
||||
ApplyCustomCosmetics();
|
||||
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
|
||||
},
|
||||
[&entry]() { ResetCustomCosmeticColor(entry); });
|
||||
}
|
||||
|
||||
static void DrawCustomCosmeticCategory(const char* label, const std::vector<const CustomCosmeticEntry*>& entries) {
|
||||
ImGui::Text("%s", label);
|
||||
ImGui::SameLine((ImGui::CalcTextSize("Message Light Blue (None No Shadow)").x * 1.0f) + 60.0f);
|
||||
if (UIWidgets::Button(
|
||||
("Random##" + std::string(label)).c_str(),
|
||||
UIWidgets::ButtonOptions().Size(ImVec2(80, 31)).Padding(ImVec2(2.0f, 0.0f)).Color(THEME_COLOR))) {
|
||||
for (const auto* entry : entries) {
|
||||
RandomizeCustomCosmeticColor(*entry);
|
||||
}
|
||||
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
|
||||
ApplyCustomCosmetics();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (UIWidgets::Button(("Reset##" + std::string(label)).c_str(),
|
||||
UIWidgets::ButtonOptions().Size(ImVec2(80, 31)).Padding(ImVec2(2.0f, 0.0f)))) {
|
||||
for (const auto* entry : entries) {
|
||||
ResetCustomCosmeticColor(*entry);
|
||||
}
|
||||
ApplyCustomCosmetics();
|
||||
}
|
||||
UIWidgets::Spacer();
|
||||
for (const auto* entry : entries) {
|
||||
DrawCustomCosmeticRow(*entry);
|
||||
}
|
||||
UIWidgets::Separator(true, true, 2.0f, 2.0f);
|
||||
}
|
||||
|
||||
bool HasCustomCosmetics() {
|
||||
return !customCosmeticEntries.empty();
|
||||
}
|
||||
|
||||
void DrawCustomCosmetics() {
|
||||
if (customCosmeticEntries.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<const CustomCosmeticEntry*> currentEntries;
|
||||
std::string currentCategory;
|
||||
|
||||
auto flushCategory = [&]() {
|
||||
if (currentEntries.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char* label = currentCategory.empty() ? CUSTOM_COSMETIC_GROUP : currentCategory.c_str();
|
||||
DrawCustomCosmeticCategory(label, currentEntries);
|
||||
currentEntries.clear();
|
||||
};
|
||||
|
||||
for (const auto& entry : customCosmeticEntries) {
|
||||
if (entry.category != currentCategory) {
|
||||
flushCategory();
|
||||
currentCategory = entry.category;
|
||||
}
|
||||
currentEntries.push_back(&entry);
|
||||
}
|
||||
|
||||
flushCategory();
|
||||
}
|
||||
|
||||
void UpdateCustomCosmeticsRainbow(int hue, float rainbowSpeed, int& index) {
|
||||
for (const auto& entry : customCosmeticEntries) {
|
||||
if (CVarGetInteger(entry.option.rainbowCvar, 0)) {
|
||||
double frequency = 2 * M_PI / (360 * rainbowSpeed);
|
||||
Color_RGBA8 newColor;
|
||||
newColor.r = static_cast<uint8_t>(sin(frequency * (hue + index) + 0) * 127) + 128;
|
||||
newColor.g = static_cast<uint8_t>(sin(frequency * (hue + index) + (2 * M_PI / 3)) * 127) + 128;
|
||||
newColor.b = static_cast<uint8_t>(sin(frequency * (hue + index) + (4 * M_PI / 3)) * 127) + 128;
|
||||
newColor.a = 255;
|
||||
CVarSetColor(entry.option.valuesCvar, newColor);
|
||||
}
|
||||
if (!CVarGetInteger(CVAR_COSMETIC("RainbowSync"), 0)) {
|
||||
index += static_cast<int>(60 * rainbowSpeed);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue