mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-07-02 18:46:00 -04:00
Merge remote-tracking branch 'origin/randomizer' into rando-archi
# Conflicts: # include/dusk/settings.h # src/dusk/settings.cpp
This commit is contained in:
@@ -20,31 +20,17 @@ It aims to be as accurate as possible to the original while also providing new o
|
||||
> Dusklight does *not* provide any copyrighted assets. You must provide your own copy of the original game.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> At a minimum, Dusklight requires a GPU with support for either D3D12, Vulkan, or Metal. Your experience with specific hardware, operating systems, and drivers may vary. In particular, older Intel iGPUs have a high likelihood of incompatibility. We are also aware of a number of issues on devices with Adreno GPUs and are working to resolve them.
|
||||
> At a minimum, Dusklight requires a GPU with support for D3D12, Vulkan 1.1+, or Metal. For older devices, best-effort support is provided for D3D11 and OpenGL ES (Android), but will not achieve full accuracy or performance. Your experience with specific hardware, operating systems, and drivers may vary.
|
||||
|
||||
### 1. Dump your game
|
||||
|
||||
You must dump your own copy of the game, please see [this article](https://wiki.dolphin-emu.org/index.php?title=Ripping_Games) for instructions. After dumping, you can use a program like [Dolphin](https://dolphin-emu.org/) or [nodtool](https://github.com/encounter/nod/releases) to convert the `.iso` to a `.rvz` to save space.
|
||||
You must dump your own copy of the game. Please see [this article](https://wiki.dolphin-emu.org/index.php?title=Ripping_Games) for instructions. After dumping, you can use a program like [Dolphin](https://dolphin-emu.org/) or [nodtool](https://github.com/encounter/nod/releases) to convert the `.iso` to `.rvz` to save space.
|
||||
|
||||
Currently, only the GameCube USA and EUR releases are supported. Support for other versions of the game is planned in the future.
|
||||
|
||||
### 2. Download [Dusklight](https://github.com/TwilitRealm/dusklight/releases)
|
||||
### 2. Install Dusklight
|
||||
|
||||
### 3. Setup the game
|
||||
**Windows / macOS / Linux**
|
||||
- Extract the .zip file
|
||||
- Launch Dusklight
|
||||
- Press **Select Disc Image** and provide the path to your supported game dump
|
||||
- Press **Play**!
|
||||
|
||||
**iOS**
|
||||
- Follow the [iOS setup guide](docs/ios-install-altstore.md)
|
||||
|
||||
**Android**
|
||||
- Install the Dusklight APK
|
||||
- Launch Dusklight
|
||||
- Press **Select Disc Image** and provide the path to your supported game dump
|
||||
- Press **Play**!
|
||||
Visit the [official installation guide](https://twilitrealm.dev/install/) for full instructions.
|
||||
|
||||
# Building
|
||||
|
||||
|
||||
Vendored
+1
-1
Submodule extern/aurora updated: e145b9ec20...9087a409da
@@ -1487,6 +1487,8 @@ set(DUSK_FILES
|
||||
src/dusk/ui/controls.hpp
|
||||
src/dusk/ui/controller_config.cpp
|
||||
src/dusk/ui/controller_config.hpp
|
||||
src/dusk/ui/cosmetics.hpp
|
||||
src/dusk/ui/cosmetics.cpp
|
||||
src/dusk/ui/document.cpp
|
||||
src/dusk/ui/document.hpp
|
||||
src/dusk/ui/editor.cpp
|
||||
@@ -1555,6 +1557,10 @@ set(DUSK_FILES
|
||||
src/dusk/discord_presence.cpp
|
||||
src/dusk/version.cpp
|
||||
src/dusk/action_bindings.cpp
|
||||
src/dusk/cosmetics/color_utils.hpp
|
||||
src/dusk/cosmetics/color_utils.cpp
|
||||
src/dusk/cosmetics/texture_utils.hpp
|
||||
src/dusk/cosmetics/texture_utils.cpp
|
||||
# Randomizer files
|
||||
src/dusk/randomizer/game/flags.cpp
|
||||
src/dusk/randomizer/game/flags.h
|
||||
|
||||
@@ -269,6 +269,12 @@
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
postFixup = lib.optionalString (!isDarwin) ''
|
||||
patchelf \
|
||||
--add-needed "${pkgs.vulkan-loader}/lib/libvulkan.so" \
|
||||
$out/bin/dusklight
|
||||
'';
|
||||
|
||||
dontStrip = true;
|
||||
|
||||
meta = {
|
||||
|
||||
@@ -6397,7 +6397,8 @@ public:
|
||||
|
||||
class daAlinkHIO_huLight_c0 {
|
||||
public:
|
||||
static daAlinkHIO_huLight_c1 const m;
|
||||
static daAlinkHIO_huLight_c1 IF_NOT_DUSK(const) m;
|
||||
IF_DUSK(static daAlinkHIO_huLight_c1 const original;)
|
||||
};
|
||||
|
||||
class daAlinkHIO_wlLight_c1 {
|
||||
@@ -6471,7 +6472,8 @@ public:
|
||||
|
||||
class daAlinkHIO_kandelaar_c0 {
|
||||
public:
|
||||
static daAlinkHIO_kandelaar_c1 const m;
|
||||
static daAlinkHIO_kandelaar_c1 IF_NOT_DUSK(const) m;
|
||||
IF_DUSK(static daAlinkHIO_kandelaar_c1 const original;)
|
||||
};
|
||||
|
||||
class daAlinkHIO_kandelaar_c : public daAlinkHIO_data_c {
|
||||
|
||||
@@ -88,9 +88,14 @@ public:
|
||||
/* 0x396A */ u8 field_0x396A[0x399E - 0x396A];
|
||||
/* 0x399E */ s16 field_0x399e;
|
||||
/* 0x39A0 */ u8 field_0x39A0[0x39A4 - 0x39A0];
|
||||
|
||||
#if TARGET_PC
|
||||
/* 0x39A4 */ cM_rnd_c mMantRng;
|
||||
#endif
|
||||
};
|
||||
|
||||
#if TARGET_PC
|
||||
STATIC_ASSERT(sizeof(mant_class) == 0x39ac);
|
||||
#else
|
||||
STATIC_ASSERT(sizeof(mant_class) == 0x39a4);
|
||||
#endif
|
||||
|
||||
#endif /* D_A_MANT_H */
|
||||
|
||||
@@ -360,7 +360,12 @@ inline void dMsgObject_demoMessageGroup() {
|
||||
}
|
||||
|
||||
inline bool dMsgObject_isTalkNowCheck() {
|
||||
#if TARGET_PC
|
||||
dMsgObject_c* msgObject = dMsgObject_getMsgObjectClass();
|
||||
return msgObject != NULL && msgObject->getStatus() != 1;
|
||||
#else
|
||||
return dMsgObject_getMsgObjectClass()->getStatus() == 1 ? false : true;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool dMsgObject_isKillMessageFlag() {
|
||||
@@ -497,7 +502,12 @@ inline void dMsgObject_onMsgSend() {
|
||||
}
|
||||
|
||||
inline bool dMsgObject_isFukidashiCheck() {
|
||||
#if TARGET_PC
|
||||
dMsgObject_c* msgObject = dMsgObject_getMsgObjectClass();
|
||||
return msgObject != NULL && msgObject->getScrnDrawPtr() != NULL;
|
||||
#else
|
||||
return dMsgObject_getMsgObjectClass()->getScrnDrawPtr() == NULL ? false : true;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void* dMsgObject_getTalkHeap() {
|
||||
|
||||
@@ -46,6 +46,12 @@ enum class FrameInterpMode : u8 {
|
||||
Unlimited = 2,
|
||||
};
|
||||
|
||||
enum class TouchTargeting : u8 {
|
||||
Hybrid = 0,
|
||||
Hold = 1,
|
||||
Switch = 2,
|
||||
};
|
||||
|
||||
enum class MenuScaling : u8 {
|
||||
GameCube = 0,
|
||||
Wii = 1,
|
||||
@@ -97,6 +103,12 @@ struct ConfigEnumRange<FrameInterpMode> {
|
||||
static constexpr auto max = FrameInterpMode::Unlimited;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ConfigEnumRange<TouchTargeting> {
|
||||
static constexpr auto min = TouchTargeting::Hybrid;
|
||||
static constexpr auto max = TouchTargeting::Switch;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ConfigEnumRange<MenuScaling> {
|
||||
static constexpr auto min = MenuScaling::GameCube;
|
||||
@@ -216,6 +228,7 @@ struct UserSettings {
|
||||
ConfigVar<bool> invertMouseY;
|
||||
ConfigVar<bool> freeCamera;
|
||||
ConfigVar<bool> enableTouchControls;
|
||||
ConfigVar<TouchTargeting> touchTargeting;
|
||||
ConfigVar<bool> enableMenuPointer;
|
||||
ConfigVar<ui::ControlLayout> touchControlsLayout;
|
||||
ConfigVar<bool> invertCameraXAxis;
|
||||
@@ -304,6 +317,29 @@ struct UserSettings {
|
||||
ConfigVar<std::string> serverPass;
|
||||
ConfigVar<std::string> slotName;
|
||||
} archipelago;
|
||||
|
||||
// Cosmetics
|
||||
struct {
|
||||
ConfigVar<std::string> herosTunicCapColor;
|
||||
ConfigVar<std::string> herosTunicTorsoColor;
|
||||
ConfigVar<std::string> herosTunicSkirtColor;
|
||||
ConfigVar<std::string> zoraArmorCapColor;
|
||||
ConfigVar<std::string> zoraArmorHelmetColor;
|
||||
ConfigVar<std::string> zoraArmorTorsoColor;
|
||||
ConfigVar<std::string> zoraArmorScalesColor;
|
||||
ConfigVar<std::string> zoraArmorFlippersColor;
|
||||
ConfigVar<std::string> lanternGlowColor;
|
||||
ConfigVar<std::string> woodenSwordColor;
|
||||
ConfigVar<std::string> msBladeColor;
|
||||
ConfigVar<std::string> msHandleColor;
|
||||
ConfigVar<std::string> lightSwordGlowColor;
|
||||
ConfigVar<std::string> boomerangColor;
|
||||
ConfigVar<std::string> ironBootsColor;
|
||||
ConfigVar<std::string> spinnerColor;
|
||||
ConfigVar<std::string> linkHairColor;
|
||||
ConfigVar<std::string> wolfLinkColor;
|
||||
ConfigVar<std::string> eponaColor;
|
||||
} cosmetics;
|
||||
};
|
||||
|
||||
UserSettings& getSettings();
|
||||
|
||||
@@ -67,6 +67,10 @@ public:
|
||||
|
||||
JKRMemArchive* getArchive() const { return mArchive; }
|
||||
JKRHeap* getHeap() const { return mHeap; }
|
||||
#if TARGET_PC
|
||||
s32 getEntryNumber() const { return mEntryNumber; }
|
||||
#endif
|
||||
|
||||
|
||||
private:
|
||||
/* 0x14 */ u8 mMountDirection;
|
||||
|
||||
@@ -58,12 +58,9 @@
|
||||
#include "res/Object/Alink.h"
|
||||
#include <cstring>
|
||||
#include <dusk/string.hpp>
|
||||
#endif
|
||||
|
||||
#include "dusk/cosmetics/color_utils.hpp"
|
||||
#include "dusk/randomizer/game/flags.h"
|
||||
#include "dusk/randomizer/game/stages.h"
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/randomizer/game/tools.h"
|
||||
#endif
|
||||
|
||||
@@ -4238,6 +4235,25 @@ int daAlink_c::createHeap() {
|
||||
if (mpHIO == NULL) {
|
||||
return 0;
|
||||
}
|
||||
#if TARGET_PC
|
||||
const auto& lanternColor = dusk::getSettings().cosmetics.lanternGlowColor.getValue();
|
||||
if (dusk::cosmetics::is_valid_hex_color_str(lanternColor)) {
|
||||
u8 r = std::stoi(lanternColor.substr(0, 2), nullptr, 16);
|
||||
u8 g = std::stoi(lanternColor.substr(2, 2), nullptr, 16);
|
||||
u8 b = std::stoi(lanternColor.substr(4, 2), nullptr, 16);
|
||||
auto& lanternAmbience = mpHIO->mItem.mLanternPL.m;
|
||||
auto& lanternSphere = mpHIO->mItem.mLantern.m;
|
||||
lanternAmbience.mColorR = r;
|
||||
lanternAmbience.mColorG = g;
|
||||
lanternAmbience.mColorB = b;
|
||||
lanternSphere.mColorReg1R = r;
|
||||
lanternSphere.mColorReg1G = g;
|
||||
lanternSphere.mColorReg1B = b;
|
||||
lanternSphere.mColorReg2R = r;
|
||||
lanternSphere.mColorReg2G = g;
|
||||
lanternSphere.mColorReg2B = b;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!(mpWlChangeModel = initModel(dRes_ID_ALINK_BMD_WL_CHANGE_e, 0))) {
|
||||
return 0;
|
||||
|
||||
@@ -1624,7 +1624,7 @@ const daAlinkHIO_bomb_c1 daAlinkHIO_bomb_c0::m = {
|
||||
|
||||
#pragma push
|
||||
#pragma force_active on
|
||||
const daAlinkHIO_huLight_c1 daAlinkHIO_huLight_c0::m = {
|
||||
IF_NOT_DUSK(const) daAlinkHIO_huLight_c1 daAlinkHIO_huLight_c0::m = {
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
@@ -1638,8 +1638,25 @@ const daAlinkHIO_huLight_c1 daAlinkHIO_huLight_c0::m = {
|
||||
0.0f,
|
||||
};
|
||||
#pragma pop
|
||||
#if TARGET_PC
|
||||
// Save original lantern colors incase player reverts to default
|
||||
const daAlinkHIO_huLight_c1 daAlinkHIO_huLight_c0::original = {
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
181,
|
||||
112,
|
||||
40,
|
||||
-70,
|
||||
1.0f,
|
||||
50.0f,
|
||||
350.0f,
|
||||
0.0f,
|
||||
};
|
||||
#endif
|
||||
|
||||
const daAlinkHIO_kandelaar_c1 daAlinkHIO_kandelaar_c0::m = {
|
||||
|
||||
IF_NOT_DUSK(const) daAlinkHIO_kandelaar_c1 daAlinkHIO_kandelaar_c0::m = {
|
||||
{
|
||||
30,
|
||||
1.1f,
|
||||
@@ -1672,6 +1689,42 @@ const daAlinkHIO_kandelaar_c1 daAlinkHIO_kandelaar_c0::m = {
|
||||
0.5f,
|
||||
};
|
||||
|
||||
#if TARGET_PC
|
||||
// Save original lantern colors incase player reverts to default
|
||||
const daAlinkHIO_kandelaar_c1 daAlinkHIO_kandelaar_c0::original = {
|
||||
{
|
||||
30,
|
||||
1.1f,
|
||||
2.0f,
|
||||
3.0f,
|
||||
17.0f,
|
||||
},
|
||||
{
|
||||
11,
|
||||
1.0f,
|
||||
0.0f,
|
||||
3.0f,
|
||||
12.0f,
|
||||
},
|
||||
{
|
||||
17,
|
||||
1.0f,
|
||||
0.0f,
|
||||
3.0f,
|
||||
18.0f,
|
||||
},
|
||||
80,
|
||||
40,
|
||||
20,
|
||||
40,
|
||||
30,
|
||||
10,
|
||||
3,
|
||||
200,
|
||||
0.5f,
|
||||
};
|
||||
#endif
|
||||
|
||||
const daAlinkHIO_fmChain_c1 daAlinkHIO_fmChain_c0::m = {
|
||||
{
|
||||
20,
|
||||
|
||||
@@ -1191,6 +1191,19 @@ void daAlink_c::setLightningSwordEffect() {
|
||||
emitter = setEmitter(&field_0x327c[i], effName[i], ¤t.pos, &shape_angle);
|
||||
if (emitter != NULL) {
|
||||
emitter->setGlobalRTMatrix(mSwordModel->getBaseTRMtx());
|
||||
#if TARGET_PC
|
||||
// Apply custom light sword glow if applicable
|
||||
const auto& lightSwordGlowColor = dusk::getSettings().cosmetics.lightSwordGlowColor.getValue();
|
||||
if (dusk::cosmetics::is_valid_hex_color_str(lightSwordGlowColor)) {
|
||||
GXColor color = dusk::cosmetics::hex_color_str_to_gx_color(lightSwordGlowColor);
|
||||
emitter->setGlobalEnvColor(color.r, color.g, color.b);
|
||||
emitter->setGlobalPrmColor(color.r, color.g, color.b);
|
||||
} else if (lightSwordGlowColor == "Rainbow") {
|
||||
GXColor color = dusk::cosmetics::get_rainbow_rgb(127.5f);
|
||||
emitter->setGlobalEnvColor(color.r, color.g, color.b);
|
||||
emitter->setGlobalPrmColor(color.r, color.g, color.b);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "d/d_com_inf_game.h"
|
||||
|
||||
#if TARGET_PC
|
||||
#include <aurora/texture.hpp>
|
||||
#include "dusk/dvd_asset.hpp"
|
||||
#include "dusk/frame_interpolation.h"
|
||||
|
||||
@@ -40,6 +41,8 @@ static f32* l_texCoord_get() { alignas(32) static f32 buf[338]; static bool _
|
||||
//#define l_pos (l_pos_get())
|
||||
#define l_normal (l_normal_get())
|
||||
#define l_texCoord (l_texCoord_get())
|
||||
|
||||
static bool l_Egnd_mantTEX_hasReplacement = false;
|
||||
#else
|
||||
#include "assets/l_Egnd_mantTEX.h"
|
||||
|
||||
@@ -223,6 +226,7 @@ void daMant_packet_c::draw() {
|
||||
GXInitTexObjCI(
|
||||
&undersideTexObj, l_Egnd_mantTEX_U, 0x80, 0x80, GX_TF_C8, GX_CLAMP, GX_CLAMP, 0, 0);
|
||||
GXInitTexObjLOD(&undersideTexObj, GX_LINEAR, GX_LINEAR, 0.0, 0.0, 0.0, 0, 0, GX_ANISO_1);
|
||||
l_Egnd_mantTEX_hasReplacement = aurora::texture::has_replacement(&mainTexObj, &tlutObj);
|
||||
textureObjsInitialized = true;
|
||||
}
|
||||
#else
|
||||
@@ -636,7 +640,11 @@ static int daMant_Execute(mant_class* i_this) {
|
||||
iVar8 = 0;
|
||||
|
||||
if (i_this->field_0x3967 != 0) {
|
||||
#if TARGET_PC
|
||||
mant_cut_type = l_Egnd_mantTEX_hasReplacement ? 1 : i_this->field_0x3967;
|
||||
#else
|
||||
mant_cut_type = i_this->field_0x3967;
|
||||
#endif
|
||||
|
||||
if (i_this->field_0x3968 < 15) {
|
||||
i_this->field_0x3968++;
|
||||
@@ -648,9 +656,18 @@ static int daMant_Execute(mant_class* i_this) {
|
||||
iVar8 = 20;
|
||||
}
|
||||
|
||||
unaff_r29 = cM_rndF(65536.0f);
|
||||
var_f31 = cM_rndFX(32.0f);
|
||||
var_f30 = cM_rndFX(32.0f);
|
||||
#if TARGET_PC
|
||||
if (l_Egnd_mantTEX_hasReplacement) {
|
||||
unaff_r29 = i_this->mMantRng.getF(65536.0f);
|
||||
var_f31 = i_this->mMantRng.getFX(32.0f);
|
||||
var_f30 = i_this->mMantRng.getFX(32.0f);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
unaff_r29 = cM_rndF(65536.0f);
|
||||
var_f31 = cM_rndFX(32.0f);
|
||||
var_f30 = cM_rndFX(32.0f);
|
||||
}
|
||||
}
|
||||
|
||||
i_this->field_0x3967 = 0;
|
||||
@@ -760,6 +777,8 @@ static int daMant_Create(fopAc_ac_c* i_this) {
|
||||
if(textureObjsInitialized) {
|
||||
GXInitTlutObjData(&tlutObj, l_Egnd_mantPAL); // make sure the cached textures are updated
|
||||
}
|
||||
|
||||
m_this->mMantRng.init(66, 16983, 855);
|
||||
#endif
|
||||
|
||||
lbl_277_bss_0 = 0;
|
||||
|
||||
@@ -7602,6 +7602,10 @@ bool dCamera_c::executeDebugFlyCam() {
|
||||
sFlyCamLastMousePos = mouseValid ? io.MousePos : ImVec2{-1.0f, -1.0f};
|
||||
}
|
||||
|
||||
if (dusk::getSettings().game.enableMirrorMode) {
|
||||
stickX *= -1.0f;
|
||||
}
|
||||
|
||||
f32 verticalDisp = 0.0f;
|
||||
if (trigR >= FLYCAM_TRIGGER_DEADZONE) {
|
||||
verticalDisp += trigR;
|
||||
|
||||
@@ -884,7 +884,7 @@ dItem_fieldItemResource dItem_data::field_item_res_randomizer[] = {
|
||||
/* 0x2E */ {"F_gD_rupy", 0x0004,-0x0001,-0x0001, 0xFF, 0x1000},
|
||||
/* 0x2F */ {"F_gD_rupy", 0x0004,-0x0001,-0x0001, 0xFF, 0x1000},
|
||||
/* 0x30 */ {"O_gD_marm", 0x0003,-0x0001,-0x0001, 0xFF, 0x1000},
|
||||
/* 0x31 */ {"O_gD_ZORA", 0x0003,-0x0001,-0x0001, 0xFF, 0x1000},
|
||||
/* 0x31 */ {"O_gD_zora", 0x0003,-0x0001,-0x0001, 0xFF, 0x1000},
|
||||
/* 0x32 */ {"O_gD_Injy", 0x0003,-0x0001,-0x0001, 0xFF, 0x1000},
|
||||
/* 0x33 */ {"O_gD_TKS", 0x0008,-0x0001,-0x0001, 0xFF, 0x1000},
|
||||
/* 0x34 */ {"O_gD_puL2", 0x0003,-0x0001,-0x0001, 0xFF, 0x1000},
|
||||
|
||||
@@ -7,6 +7,11 @@
|
||||
#include "d/d_meter_HIO.h"
|
||||
#include "d/d_pane_class.h"
|
||||
|
||||
#if TARGET_PC
|
||||
#include "d/actor/d_a_alink.h"
|
||||
#include "dusk/cosmetics/color_utils.hpp"
|
||||
#endif
|
||||
|
||||
dKantera_icon_c::dKantera_icon_c() {
|
||||
initiate();
|
||||
}
|
||||
@@ -50,6 +55,23 @@ void dKantera_icon_c::setScale(f32 h, f32 v) {
|
||||
|
||||
void dKantera_icon_c::setNowGauge(u16 h, u16 v) {
|
||||
mpGauge->scale((f32)v / (f32)h, 1.0f);
|
||||
#if TARGET_PC
|
||||
// Apply custom lantern glow if necessary
|
||||
const auto& lanternColorStr = dusk::getSettings().cosmetics.lanternGlowColor.getValue();
|
||||
if (dusk::cosmetics::is_valid_hex_color_str(lanternColorStr)) {
|
||||
auto color = dusk::cosmetics::hex_color_str_to_gx_color(lanternColorStr);
|
||||
mpGauge->setBlackWhite(JUtility::TColor(color.r, color.g, color.b, 255),
|
||||
JUtility::TColor(color.r, color.g, color.b, 255));
|
||||
} else if (lanternColorStr == "Rainbow") {
|
||||
auto lv = &daAlink_getAlinkActorClass()->mpHIO->mItem.mLantern.m;
|
||||
mpGauge->setBlackWhite(JUtility::TColor(lv->mColorReg1R, lv->mColorReg1G, lv->mColorReg1B, 255),
|
||||
JUtility::TColor(lv->mColorReg1R, lv->mColorReg1G, lv->mColorReg1B, 255));
|
||||
} else {
|
||||
// Smaller gauge is just pure yellow
|
||||
mpGauge->setBlackWhite(JUtility::TColor(255, 255, 0, 255),
|
||||
JUtility::TColor(255, 255, 0, 255));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void dDlst_KanteraIcon_c::draw() {
|
||||
|
||||
+20
-12
@@ -426,7 +426,15 @@ void dMenu_Fmap2DBack_c::draw() {
|
||||
}
|
||||
|
||||
mpPointParent->setAlphaRate(mArrowAlpha * mSpotTextureFadeAlpha);
|
||||
mpPointParent->translate(mArrowPos2DX + mTransX, mArrowPos2DY + mTransZ);
|
||||
|
||||
f32 drawX = mArrowPos2DX + mTransX;
|
||||
#ifdef TARGET_PC
|
||||
if (dusk::getSettings().game.enableMirrorMode) {
|
||||
drawX = getMirrorPosX(drawX, 0.0f);
|
||||
}
|
||||
#endif
|
||||
|
||||
mpPointParent->translate(drawX, mArrowPos2DY + mTransZ);
|
||||
mpPointScreen->draw(0.0f, 0.0f, grafPort);
|
||||
}
|
||||
|
||||
@@ -745,7 +753,7 @@ void dMenu_Fmap2DBack_c::zoomMapCalc(f32 i_zoom) {
|
||||
|
||||
f32 tmp2 = (dVar12 + (i_zoom * (centerX - dVar12)));
|
||||
f32 tmp2_ = (dVar11 + (i_zoom * (centerY - dVar11)));
|
||||
|
||||
|
||||
field_0xf0c[mRegionCursor] =
|
||||
((tmp2 + (tmp3 * mZoom)) - mRegionMapSizeX[mRegionCursor] * mZoom * 0.5f) -
|
||||
mRegionMinMapX[mRegionCursor];
|
||||
@@ -1005,6 +1013,11 @@ void dMenu_Fmap2DBack_c::allmap_move2(STControl* param_0) {
|
||||
f32 stickValue = param_0->getValueStick();
|
||||
if (stickValue >= spC) {
|
||||
s16 angle = param_0->getAngleStick();
|
||||
#ifdef TARGET_PC
|
||||
if (dusk::getSettings().game.enableMirrorMode) {
|
||||
angle = -angle;
|
||||
}
|
||||
#endif
|
||||
f32 local_68 = (mTexMaxX - mTexMinX);
|
||||
f32 zoomRate = local_68 / getAllMapZoomRate();
|
||||
f32 sp24;
|
||||
@@ -1046,11 +1059,6 @@ void dMenu_Fmap2DBack_c::allmap_move2(STControl* param_0) {
|
||||
calcAllMapPos2D((mArrowPos3DX + control_xpos) - mStageTransX,
|
||||
(mArrowPos3DZ + control_ypos) - mStageTransZ, &sp14, &sp10);
|
||||
|
||||
#if TARGET_PC
|
||||
if (dusk::getSettings().game.enableMirrorMode) {
|
||||
sp14 = getMirrorPosX(sp14, 0.0f);
|
||||
}
|
||||
#endif
|
||||
|
||||
mSelectRegion = 0xff;
|
||||
for (int i = 7; i >= 0; i--) {
|
||||
@@ -1907,6 +1915,11 @@ void dMenu_Fmap2DBack_c::regionMapMove(STControl* i_stick) {
|
||||
f32 stick_value = i_stick->getValueStick();
|
||||
if (stick_value >= slow_bound) {
|
||||
s16 angle = i_stick->getAngleStick();
|
||||
#ifdef TARGET_PC
|
||||
if (dusk::getSettings().game.enableMirrorMode) {
|
||||
angle = -angle;
|
||||
}
|
||||
#endif
|
||||
f32 local_68 = mTexMaxX - mTexMinX;
|
||||
f32 spot_zoom = getSpotMapZoomRate();
|
||||
f32 region_zoom = getRegionMapZoomRate(mRegionCursor);
|
||||
@@ -1946,11 +1959,6 @@ void dMenu_Fmap2DBack_c::regionMapMove(STControl* i_stick) {
|
||||
calcAllMapPos2D(mArrowPos3DX + control_xpos - mStageTransX,
|
||||
mArrowPos3DZ + control_ypos - mStageTransZ, &pos_x, &pos_y);
|
||||
|
||||
#if TARGET_PC
|
||||
if (dusk::getSettings().game.enableMirrorMode) {
|
||||
pos_x = getMirrorPosX(pos_x, 0.0f);
|
||||
}
|
||||
#endif
|
||||
|
||||
mSelectRegion = 0xff;
|
||||
int region = mRegionCursor;
|
||||
|
||||
@@ -437,7 +437,12 @@ void dMeter2_c::checkStatus() {
|
||||
|
||||
field_0x128 = daPy_py_c::checkNowWolf();
|
||||
|
||||
#if TARGET_PC
|
||||
dMsgObject_c* msgObject = dMsgObject_getMsgObjectClass();
|
||||
if (!dComIfGp_2dShowCheck() || (msgObject != NULL && msgObject->isPlaceMessage())) {
|
||||
#else
|
||||
if (!dComIfGp_2dShowCheck() || dMsgObject_getMsgObjectClass()->isPlaceMessage()) {
|
||||
#endif
|
||||
mStatus |= 0x4000;
|
||||
} else if (dComIfGp_checkPlayerStatus1(0, 1) && dComIfGp_getAStatus() == 0x12) {
|
||||
mStatus |= 0x200000;
|
||||
@@ -2870,8 +2875,14 @@ void dMeter2_c::alphaAnimeButton() {
|
||||
u8 var_31;
|
||||
var_31 = 0;
|
||||
|
||||
#if TARGET_PC
|
||||
dMsgObject_c* msgObject = dMsgObject_getMsgObjectClass();
|
||||
if ((mStatus & 0x4000) ||
|
||||
((mStatus & 0x100) && (msgObject != NULL && msgObject->isAutoMessageFlag())) ||
|
||||
#else
|
||||
if ((mStatus & 0x4000) ||
|
||||
((mStatus & 0x100) && dMsgObject_getMsgObjectClass()->isAutoMessageFlag()) ||
|
||||
#endif
|
||||
((mStatus & 0x40000000) && !(mStatus & 0x100)) || (mStatus & 0x80000000) || (mStatus & 8) ||
|
||||
(mStatus & 0x10) || (mStatus & 0x20) || (mStatus & 0x04000000) || (mStatus & 0x10000000))
|
||||
{
|
||||
|
||||
+35
-3
@@ -20,10 +20,11 @@
|
||||
#include "d/d_msg_class.h"
|
||||
#include "d/d_msg_object.h"
|
||||
#include "d/d_pane_class.h"
|
||||
#include "dusk/frame_interpolation.h"
|
||||
#include <cstring>
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/cosmetics/color_utils.hpp"
|
||||
#include "dusk/frame_interpolation.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "dusk/ui/icon_provider.hpp"
|
||||
#include <algorithm>
|
||||
@@ -1676,8 +1677,39 @@ void dMeter2Draw_c::drawKanteraScreen(u8 i_meterType) {
|
||||
mpMagicMeter->setBlackWhite(black, mpMagicMeter->getInitWhite());
|
||||
setAlphaMagicChange(true);
|
||||
} else if (i_meterType == 1) {
|
||||
mpMagicMeter->setBlackWhite(JUtility::TColor(255, 255, 140, 255),
|
||||
JUtility::TColor(230, 170, 0, 255));
|
||||
#if TARGET_PC
|
||||
// Apply custom lantern glow if necessary
|
||||
const auto& lanternColorStr = dusk::getSettings().cosmetics.lanternGlowColor.getValue();
|
||||
auto lv = &daAlink_getAlinkActorClass()->mpHIO->mItem.mLantern.m;
|
||||
auto hlv = &daAlink_getAlinkActorClass()->mpHIO->mItem.mLanternPL.m;
|
||||
if (dusk::cosmetics::is_valid_hex_color_str(lanternColorStr)) {
|
||||
auto color = dusk::cosmetics::hex_color_str_to_gx_color(lanternColorStr);
|
||||
mpMagicMeter->setBlackWhite(JUtility::TColor(color.r, color.g, color.b, 255),
|
||||
JUtility::TColor(color.r, color.g, color.b, 255));
|
||||
} else if (lanternColorStr == "Rainbow") {
|
||||
GXColor color = dusk::cosmetics::get_rainbow_rgb(127.5f);
|
||||
lv->mColorReg1R = color.r / 2;
|
||||
lv->mColorReg1G = color.g / 2;
|
||||
lv->mColorReg1B = color.b / 2;
|
||||
lv->mColorReg2R = color.r / 2;
|
||||
lv->mColorReg2G = color.g / 2;
|
||||
lv->mColorReg2B = color.b / 2;
|
||||
hlv->mColorR = color.r / 2;
|
||||
hlv->mColorG = color.g / 2;
|
||||
hlv->mColorB = color.b / 2;
|
||||
|
||||
mpMagicMeter->setBlackWhite(JUtility::TColor(color.r/2, color.g/2, color.b/2, 255),
|
||||
JUtility::TColor(color.r/2, color.g/2, color.b/2, 255));
|
||||
} else {
|
||||
// Set back original colors if no valid cosmetic choice
|
||||
*lv = daAlink_getAlinkActorClass()->mpHIO->mItem.mLantern.original;
|
||||
*hlv = daAlink_getAlinkActorClass()->mpHIO->mItem.mLanternPL.original;
|
||||
#endif
|
||||
mpMagicMeter->setBlackWhite(JUtility::TColor(255, 255, 140, 255),
|
||||
JUtility::TColor(230, 170, 0, 255));
|
||||
#if TARGET_PC
|
||||
}
|
||||
#endif
|
||||
setAlphaKanteraChange(true);
|
||||
} else if (i_meterType == 2) {
|
||||
f32 oxygen_percent = (f32)dComIfGp_getOxygen() / (f32)dComIfGp_getMaxOxygen();
|
||||
|
||||
@@ -378,6 +378,7 @@ nlohmann::json ConfigImpl<ui::ControlLayout>::dumpToJson(const ConfigVar<ui::Con
|
||||
}
|
||||
|
||||
template class ConfigImpl<dusk::FrameInterpMode>;
|
||||
template class ConfigImpl<dusk::TouchTargeting>;
|
||||
template class ConfigImpl<dusk::MenuScaling>;
|
||||
template class ConfigImpl<dusk::Resampler>;
|
||||
template class ConfigImpl<dusk::MagicArmorMode>;
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
#include "color_utils.hpp"
|
||||
|
||||
namespace dusk::cosmetics {
|
||||
uint8_t desaturate_rgb_565(uint16_t rgb565Val)
|
||||
{
|
||||
const uint32_t r = (rgb565Val & 0xf800) >> 11;
|
||||
const uint32_t g = (rgb565Val & 0x7e0) >> 5;
|
||||
const uint32_t b = rgb565Val & 0x1f;
|
||||
|
||||
// Here we are doing a quicker (0.22 * r + 0.72 * g + 0.06 * b) which
|
||||
// uses multiplies and shifts rather than division.
|
||||
const uint32_t combined = 30480413 * r + 49085341 * g + 8312839 * b;
|
||||
uint8_t shifted = (combined >> 24) & 0xff;
|
||||
|
||||
// Check if should round up shifted value.
|
||||
if (shifted < 0xff && combined & 0x00800000)
|
||||
{
|
||||
shifted += 1;
|
||||
}
|
||||
|
||||
return shifted;
|
||||
}
|
||||
|
||||
uint16_t blend_overlay_rgb_565(uint8_t grayVal, GXColor color)
|
||||
{
|
||||
uint32_t rTimes255, gTimes255, bTimes255;
|
||||
|
||||
if (grayVal <= 0x7f)
|
||||
{
|
||||
const uint32_t grayTimesTwo = 2 * grayVal;
|
||||
|
||||
rTimes255 = grayTimesTwo * color.r;
|
||||
gTimes255 = grayTimesTwo * color.g;
|
||||
bTimes255 = grayTimesTwo * color.b;
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint32_t multiplier = 2 * (255 - grayVal);
|
||||
|
||||
rTimes255 = 255 * 255 - multiplier * (255 - color.r);
|
||||
gTimes255 = 255 * 255 - multiplier * (255 - color.g);
|
||||
bTimes255 = 255 * 255 - multiplier * (255 - color.b);
|
||||
}
|
||||
|
||||
// Divide each by 255
|
||||
const uint32_t r = (rTimes255 + 1 + (rTimes255 >> 8)) >> 8;
|
||||
const uint32_t g = (gTimes255 + 1 + (gTimes255 >> 8)) >> 8;
|
||||
const uint32_t b = (bTimes255 + 1 + (bTimes255 >> 8)) >> 8;
|
||||
|
||||
return ((r & 0xf8) << 8) | ((g & 0xfc) << 3) | ((b & 0xf8) >> 3);
|
||||
}
|
||||
|
||||
bool is_valid_hex_color_str(std::string_view hexStr) {
|
||||
return hexStr.find_first_not_of("0123456789ABCDEFabcdef") == std::string_view::npos && hexStr.length() == 6;
|
||||
}
|
||||
|
||||
GXColor hex_color_str_to_gx_color(const std::string& hexColorStr) {
|
||||
u8 r = std::stoi(hexColorStr.substr(0, 2), nullptr, 16);
|
||||
u8 g = std::stoi(hexColorStr.substr(2, 2), nullptr, 16);
|
||||
u8 b = std::stoi(hexColorStr.substr(4, 2), nullptr, 16);
|
||||
return GXColor{r, g, b};
|
||||
}
|
||||
|
||||
GXColor get_rainbow_rgb(f32 amplitude) {
|
||||
static f32 rainbowPhaseAngle = 0.f;
|
||||
f32 angleIncrement = 1.0f; // Degrees per frame (Adjust for speed)
|
||||
rainbowPhaseAngle += angleIncrement;
|
||||
if (rainbowPhaseAngle >= 360.0f) {
|
||||
rainbowPhaseAngle -= 360.0f;
|
||||
}
|
||||
f32 phase_rad = rainbowPhaseAngle * M_PI / 180.0f;
|
||||
|
||||
u8 r_val = (u8)(amplitude * (sinf(phase_rad) + 1.0f) + 0.5f);
|
||||
u8 g_val = (u8)(amplitude * (sinf(phase_rad + 2.0f * M_PI / 3.0f) + 1.0f) + 0.5f);
|
||||
u8 b_val = (u8)(amplitude * (sinf(phase_rad + 4.0f * M_PI / 3.0f) + 1.0f));
|
||||
GXColor rgbColor;
|
||||
rgbColor.r = r_val;
|
||||
rgbColor.g = g_val;
|
||||
rgbColor.b = b_val;
|
||||
rgbColor.a = 0xff;
|
||||
return rgbColor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* File originally copied from console TPR with permission from isaac
|
||||
* https://github.com/zsrtp/libtp_rel/blob/master/include/util/color_utils.h
|
||||
*/
|
||||
|
||||
#include "dolphin/gx/GXStruct.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
namespace dusk::cosmetics
|
||||
{
|
||||
// Desaturates an RGB565 color to a u8 gray value (0xFF being white and 0x00
|
||||
// being black).
|
||||
uint8_t desaturate_rgb_565(uint16_t rgb565Val);
|
||||
|
||||
// Performs an "Overlay" blend of a u8 gray value and a pointer to a u8
|
||||
// array of {r,g,b}. Returns the result as an RGB565.
|
||||
uint16_t blend_overlay_rgb_565(uint8_t grayVal, GXColor color);
|
||||
|
||||
bool is_valid_hex_color_str(std::string_view hexStr);
|
||||
|
||||
GXColor hex_color_str_to_gx_color(const std::string& hexColorStr);
|
||||
|
||||
GXColor get_rainbow_rgb(f32 amplitude);
|
||||
} // namespace dusk::cosmetics
|
||||
@@ -0,0 +1,301 @@
|
||||
#include "texture_utils.hpp"
|
||||
#include "color_utils.hpp"
|
||||
|
||||
#include "JSystem/J3DGraphLoader/J3DModelLoader.h"
|
||||
#include "JSystem/JKernel/JKRMemArchive.h"
|
||||
#include "JSystem/JSupport/JSupport.h"
|
||||
#include "JSystem/JUtility/JUTNameTab.h"
|
||||
#include "JSystem/JUtility/JUTTexture.h"
|
||||
#include "d/actor/d_a_alink.h"
|
||||
#include "d/actor/d_a_player.h"
|
||||
#include "global.h"
|
||||
#include "gx/GXEnum.h"
|
||||
#include "m_Do/m_Do_dvd_thread.h"
|
||||
|
||||
namespace dusk::cosmetics {
|
||||
ResTIMG* find_tex_header_in_tex_1_section(J3DTextureBlock* tex1Ptr, const char* textureName) {
|
||||
if (tex1Ptr == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto strTable = JSUConvertOffsetToPtr<ResNTAB>(tex1Ptr, tex1Ptr->mpNameTable);
|
||||
for (size_t i = 0; i < strTable->mEntryNum && i < tex1Ptr->mTextureNum; i++) {
|
||||
const char* str = strTable->getName(i);
|
||||
|
||||
if (strcmp(str, textureName) == 0) {
|
||||
return &JSUConvertOffsetToPtr<ResTIMG>(tex1Ptr, tex1Ptr->mpTextureRes)[i];
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// When left is greater than right
|
||||
// 0b00 points to the left color
|
||||
// 0b01 points to the right color
|
||||
// 0b10 is closer to left color
|
||||
// 0b11 is closer to right color
|
||||
|
||||
// When left is not greater than right
|
||||
// 0b00 points to the left color
|
||||
// 0b01 points to the right color
|
||||
// 0b10 is midway between the colors
|
||||
// 0b11 is transparent
|
||||
|
||||
// That means when maintaining the relative order, if we have to swap the colors:
|
||||
|
||||
// in the case of left being greater than right:
|
||||
// 0b00 will swap to 0b01
|
||||
// 0b01 will swap to 0b00
|
||||
// 0b10 will swap to 0b11
|
||||
// 0b11 will swap to 0b10
|
||||
// So the left bit stays the same, and the right bit changes
|
||||
// Can do xor (^) like 0b01010101 or 0x55 for each u16
|
||||
|
||||
// in the case of left not being greater than right:
|
||||
// 0b00 will swap to 0b01
|
||||
// 0b01 will swap to 0b00
|
||||
// 0b10 will stay the same
|
||||
// 0b11 will stay the same
|
||||
// so if the left bit is a 0, the right bit will change
|
||||
uint32_t swap_index_bits(bool leftIsGreater, uint32_t bits) {
|
||||
if (leftIsGreater) {
|
||||
return bits ^ 0x55555555;
|
||||
}
|
||||
|
||||
const uint32_t mask = ((bits >> 1) & 0x55555555) ^ 0x55555555;
|
||||
return bits ^ mask;
|
||||
}
|
||||
|
||||
void recolor_cmpr_texture(J3DTextureBlock* tex1Ptr, const char* textureName, GXColor color)
|
||||
{
|
||||
ResTIMG* texHeaderPtr = find_tex_header_in_tex_1_section(tex1Ptr, textureName);
|
||||
if (texHeaderPtr == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (texHeaderPtr->format != GX_VA_TEX1) {
|
||||
// Texture is not CMPR
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t recolors[0x100];
|
||||
for (int32_t i = 0; i < 0x100; i++) {
|
||||
recolors[i] = blend_overlay_rgb_565(i, color);
|
||||
}
|
||||
|
||||
constexpr int32_t blockWidth = 8;
|
||||
constexpr int32_t blockHeight = 8;
|
||||
|
||||
const int32_t roundedWidth = texHeaderPtr->width + ((blockWidth - (texHeaderPtr->width % blockWidth)) % blockWidth);
|
||||
const int32_t roundedHeight = texHeaderPtr->height + ((blockHeight - (texHeaderPtr->height % blockHeight)) % blockHeight);
|
||||
|
||||
const int32_t numBlocks = roundedWidth / blockWidth * roundedHeight / blockHeight;
|
||||
|
||||
const int32_t iterations = numBlocks * 4;
|
||||
|
||||
uint8_t* currentAddr = JSUConvertOffsetToPtr<u8>(texHeaderPtr, texHeaderPtr->imageOffset);
|
||||
for (int32_t i = 0; i < iterations; i++) {
|
||||
auto* rgb565Ptr = reinterpret_cast<BE<uint16_t>*>(currentAddr);
|
||||
|
||||
auto leftRgb565 = rgb565Ptr[0];
|
||||
auto rightRgb565 = rgb565Ptr[1];
|
||||
const bool leftIsGreater = leftRgb565 > rightRgb565;
|
||||
|
||||
const uint32_t leftGrayVal = desaturate_rgb_565(leftRgb565);
|
||||
const uint32_t rightGrayVal = desaturate_rgb_565(rightRgb565);
|
||||
|
||||
uint16_t leftNewRgb565 = recolors[leftGrayVal];
|
||||
uint16_t rightNewRgb565 = recolors[rightGrayVal];
|
||||
|
||||
bool needsBitSwap = false;
|
||||
|
||||
if (leftIsGreater) {
|
||||
if (leftNewRgb565 == rightNewRgb565) {
|
||||
// Need to make sure that subtracting 1 does not mess
|
||||
// everything up. For example, 0x1000 - 1 => 0x0fff which is
|
||||
// a completely different color.
|
||||
if ((leftNewRgb565 & 0x1f) == 0)
|
||||
{
|
||||
// If left value has 0 blue, we change its blue to 1.
|
||||
leftNewRgb565 += 1;
|
||||
}
|
||||
rightNewRgb565 = leftNewRgb565 - 1;
|
||||
}
|
||||
else if (leftNewRgb565 < rightNewRgb565) {
|
||||
needsBitSwap = true;
|
||||
}
|
||||
}
|
||||
else if (leftNewRgb565 > rightNewRgb565) {
|
||||
needsBitSwap = true;
|
||||
}
|
||||
|
||||
if (needsBitSwap) {
|
||||
// The left and right colors are swapping so that their values
|
||||
// are relative in the same way. We need to update the bits
|
||||
// referencing the palette entries to handle the swap.
|
||||
|
||||
const uint16_t temp = leftNewRgb565;
|
||||
leftNewRgb565 = rightNewRgb565;
|
||||
rightNewRgb565 = temp;
|
||||
|
||||
auto wordPtr = reinterpret_cast<BE<uint32_t>*>(currentAddr);
|
||||
const uint32_t bits = wordPtr[1];
|
||||
|
||||
const uint32_t newBits = swap_index_bits(leftIsGreater, bits);
|
||||
wordPtr[1] = newBits;
|
||||
}
|
||||
|
||||
rgb565Ptr[0] = leftNewRgb565;
|
||||
rgb565Ptr[1] = rightNewRgb565;
|
||||
|
||||
currentAddr += 8;
|
||||
}
|
||||
}
|
||||
|
||||
J3DTextureBlock* find_tex_1_in_bmd(J3DModelFileData* bmdPtr)
|
||||
{
|
||||
if (bmdPtr == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (bmdPtr->mMagic1 != MULTI_CHAR('J3D2')) {
|
||||
// Model was not a BMD or BDL!
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (bmdPtr->mMagic2 != MULTI_CHAR('bmd3') && bmdPtr->mMagic2 != MULTI_CHAR('bdl4')) {
|
||||
// Model was not a BMD or BDL!
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
J3DModelBlock* curBlock = bmdPtr->mBlocks;
|
||||
for (int32_t i = 0; i < bmdPtr->mBlockNum; i++) {
|
||||
if (curBlock->mBlockType == MULTI_CHAR('TEX1')) {
|
||||
return static_cast<J3DTextureBlock*>(curBlock);
|
||||
}
|
||||
|
||||
// Line taken from J3DModelLoader.cpp
|
||||
curBlock = (J3DModelBlock*)((uintptr_t)curBlock + curBlock->mBlockSize);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct CosmeticOverride {
|
||||
std::list<std::string_view> textures{};
|
||||
ConfigVar<std::string>* hexColor{nullptr};
|
||||
};
|
||||
|
||||
auto& get_cosmetic_overrides() {
|
||||
static std::unordered_map<s32, std::unordered_map<std::string_view, std::list<CosmeticOverride>>> cosmeticOverrides{};
|
||||
if (cosmeticOverrides.empty()) {
|
||||
auto& cosmetics = getSettings().cosmetics;
|
||||
// Main Link Model
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Kmdl.arc")]["bmwr/al_head.bmd"] = {
|
||||
{.textures = {"al_cap"}, .hexColor = &cosmetics.herosTunicCapColor},
|
||||
{.textures = {"al_hair"}, .hexColor = &cosmetics.linkHairColor},
|
||||
};
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Kmdl.arc")]["bmwr/al.bmd"] = {
|
||||
{.textures = {"al_upbody"}, .hexColor = &cosmetics.herosTunicTorsoColor},
|
||||
{.textures = {"al_lowbody"}, .hexColor = &cosmetics.herosTunicSkirtColor},
|
||||
};
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Kmdl.arc")]["bmwr/al_bootsh.bmd"] = {
|
||||
{.textures = {"al_bootsH"}, .hexColor = &cosmetics.ironBootsColor},
|
||||
};
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Kmdl.arc")]["bmwr/al_swb.bmd"] = {
|
||||
{.textures = {"al_SWB"}, .hexColor = &cosmetics.woodenSwordColor},
|
||||
};
|
||||
// Zora Armor Link Model
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Zmdl.arc")]["bmwr/zl_head.bmd"] = {
|
||||
{.textures = {"zl_cap"}, .hexColor = &cosmetics.zoraArmorCapColor},
|
||||
{.textures = {"zl_helmet"}, .hexColor = &cosmetics.zoraArmorHelmetColor},
|
||||
};
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Zmdl.arc")]["bmwr/zl.bmd"] = {
|
||||
{.textures = {"zl_armor", "zl_armL"}, .hexColor = &cosmetics.zoraArmorTorsoColor},
|
||||
{.textures = {"zl_body"}, .hexColor = &cosmetics.zoraArmorScalesColor},
|
||||
{.textures = {"zl_boots"}, .hexColor = &cosmetics.zoraArmorFlippersColor},
|
||||
};
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Zmdl.arc")]["bmwr/al_bootsh.bmd"] = {
|
||||
{.textures = {"al_bootsH"}, .hexColor = &cosmetics.ironBootsColor},
|
||||
};
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Zmdl.arc")]["bmwr/al_swb.bmd"] = {
|
||||
{.textures = {"al_SWB"}, .hexColor = &cosmetics.woodenSwordColor},
|
||||
};
|
||||
// Zora Armor field model
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/O_gD_zora.arc")]["bmdr/o_gd_al_zora.bmd"] = {
|
||||
{.textures = {"zl_armor"}, .hexColor = &cosmetics.zoraArmorTorsoColor},
|
||||
{.textures = {"zl_body"}, .hexColor = &cosmetics.zoraArmorScalesColor},
|
||||
{.textures = {"zl_helmet"}, .hexColor = &cosmetics.zoraArmorHelmetColor},
|
||||
{.textures = {"zl_cap"}, .hexColor = &cosmetics.zoraArmorCapColor},
|
||||
};
|
||||
// Magic Armor Model
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Mmdl.arc")]["bmwr/al_bootsh.bmd"] = {
|
||||
{.textures = {"al_bootsH"}, .hexColor = &cosmetics.ironBootsColor},
|
||||
};
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Mmdl.arc")]["bmwr/al_swb.bmd"] = {
|
||||
{.textures = {"al_SWB"}, .hexColor = &cosmetics.woodenSwordColor},
|
||||
};
|
||||
// Master Sword Colors
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Alink.arc")]["bmwe/al_swm.bmd"] = {
|
||||
{.textures = {"al_SWM"}, .hexColor = &cosmetics.msBladeColor},
|
||||
{.textures = {"al_SWgripM"}, .hexColor = &cosmetics.msHandleColor},
|
||||
};
|
||||
// Boomerang Color
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Alink.arc")]["bmdr/al_boom.bmd"] = {
|
||||
{.textures = {"L_al_boom00"}, .hexColor = &cosmetics.boomerangColor},
|
||||
};
|
||||
// Spinner Color
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Alink.arc")]["bmdr/al_sp.bmd"] = {
|
||||
{.textures = {"al_SP"}, .hexColor = &cosmetics.spinnerColor},
|
||||
};
|
||||
// Epona Color
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Horse.arc")]["bmdr/hs.bmd"] = {
|
||||
{.textures = {"hs_body"}, .hexColor = &cosmetics.eponaColor},
|
||||
};
|
||||
// Wolf Link Color
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Wmdl.arc")]["bmwr/wl.bmd"] = {
|
||||
{.textures = {"wl_body"}, .hexColor = &cosmetics.wolfLinkColor},
|
||||
};
|
||||
}
|
||||
return cosmeticOverrides;
|
||||
}
|
||||
|
||||
void handle_texture_overrides_on_load(mDoDvdThd_mountArchive_c* mountArchive) {
|
||||
|
||||
auto entryNum = mountArchive->getEntryNumber();
|
||||
auto& cosmeticOverrides = get_cosmetic_overrides();
|
||||
if (!cosmeticOverrides.contains(entryNum)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& [resName, overrides] : cosmeticOverrides[entryNum]) {
|
||||
|
||||
auto* archive = mountArchive->getArchive();
|
||||
auto* entry = archive->findFsResource(resName.data(), 0);
|
||||
if (!entry) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* tex1Addr = find_tex_1_in_bmd(static_cast<J3DModelFileData*>(archive->fetchResource(entry, NULL)));
|
||||
if (!tex1Addr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& cosmeticOverride : overrides) {
|
||||
const auto& [textures, hexColorVar] = cosmeticOverride;
|
||||
const auto& hexColorStr = hexColorVar->getValue();
|
||||
if (!is_valid_hex_color_str(hexColorStr)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto color = hex_color_str_to_gx_color(hexColorStr);
|
||||
if (tex1Addr) {
|
||||
for (const auto& textureName : textures) {
|
||||
recolor_cmpr_texture(tex1Addr, textureName.data(), color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* File originally copied from console TPR with permission from isaac
|
||||
* https://github.com/zsrtp/libtp_rel/blob/master/include/util/texture_utils.h
|
||||
*/
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
struct ResTIMG;
|
||||
struct J3DTextureBlock;
|
||||
class J3DModelFileData;
|
||||
class mDoDvdThd_mountArchive_c;
|
||||
namespace dusk::cosmetics
|
||||
{
|
||||
ResTIMG* find_tex_header_in_tex_1_section(J3DTextureBlock* tex1Ptr, const char* textureName);
|
||||
|
||||
uint32_t swap_index_bits(bool leftIsGreater, uint32_t bits);
|
||||
|
||||
void recolor_cmpr_texture(J3DTextureBlock* tex1Ptr, const char* textureName, const uint8_t* rgb);
|
||||
|
||||
J3DTextureBlock* find_tex_1_in_bmd(J3DModelFileData* bmdPtr);
|
||||
|
||||
void handle_texture_overrides_on_load(mDoDvdThd_mountArchive_c* mountArchive);
|
||||
} // namespace dusk::cosmetics
|
||||
@@ -96,6 +96,7 @@ UserSettings g_userSettings = {
|
||||
.invertMouseY {"game.invertMouseY", false},
|
||||
.freeCamera {"game.freeCamera", false},
|
||||
.enableTouchControls {"game.enableTouchControls", false},
|
||||
.touchTargeting {"game.touchTargeting", TouchTargeting::Hybrid},
|
||||
.enableMenuPointer {"game.enableMenuPointer", true},
|
||||
.touchControlsLayout {"game.touchControlsLayout", ui::ControlLayout{}},
|
||||
.invertCameraXAxis {"game.invertCameraXAxis", false},
|
||||
@@ -216,6 +217,27 @@ UserSettings g_userSettings = {
|
||||
.serverIP {"archipelago.serverIP", "archipelago.gg"},
|
||||
.serverPass {"archipelago.serverPass", ""},
|
||||
.slotName {"archipelago.slotName", ""},
|
||||
|
||||
.cosmetics = {
|
||||
.herosTunicCapColor = {"cosmetics.hatColor", ""},
|
||||
.herosTunicTorsoColor = {"cosmetics.tunicBodyColor", ""},
|
||||
.herosTunicSkirtColor = {"cosmetics.tunicSkirtColor", ""},
|
||||
.zoraArmorCapColor = {"cosmetics.zoraArmorCapColor", ""},
|
||||
.zoraArmorHelmetColor = {"cosmetics.zoraArmorHelmetColor", ""},
|
||||
.zoraArmorTorsoColor = {"cosmetics.zoraArmorTorsoColor", ""},
|
||||
.zoraArmorScalesColor = {"cosmetics.zoraArmorScalesColor", ""},
|
||||
.zoraArmorFlippersColor = {"cosmetics.zoraArmorFlippersColor", ""},
|
||||
.lanternGlowColor = {"cosmetics.lanternGlowColor", ""},
|
||||
.woodenSwordColor = {"cosmetics.woodenSwordColor", ""},
|
||||
.msBladeColor = {"cosmetics.msBladeColor", ""},
|
||||
.msHandleColor = {"cosmetics.msHandleColor", ""},
|
||||
.lightSwordGlowColor = {"cosmetics.lightSwordGlowColor", ""},
|
||||
.boomerangColor = {"cosmetics.boomerangColor", ""},
|
||||
.ironBootsColor = {"cosmetics.ironBootsColor", ""},
|
||||
.spinnerColor = {"cosmetics.spinnerColor", ""},
|
||||
.linkHairColor = {"cosmetics.linkHairColor", ""},
|
||||
.wolfLinkColor = {"cosmetics.wolfLinkColor", ""},
|
||||
.eponaColor = {"cosmetics.eponaColor", ""},
|
||||
}
|
||||
};
|
||||
|
||||
@@ -341,6 +363,7 @@ void registerSettings() {
|
||||
Register(g_userSettings.game.invertMouseY);
|
||||
Register(g_userSettings.game.freeCamera);
|
||||
Register(g_userSettings.game.enableTouchControls);
|
||||
Register(g_userSettings.game.touchTargeting);
|
||||
Register(g_userSettings.game.enableMenuPointer);
|
||||
Register(g_userSettings.game.touchControlsLayout);
|
||||
Register(g_userSettings.game.debugFlyCam);
|
||||
@@ -393,6 +416,26 @@ void registerSettings() {
|
||||
Register(g_userSettings.archipelago.serverIP);
|
||||
Register(g_userSettings.archipelago.serverPass);
|
||||
Register(g_userSettings.archipelago.slotName);
|
||||
|
||||
Register(g_userSettings.cosmetics.herosTunicCapColor);
|
||||
Register(g_userSettings.cosmetics.herosTunicTorsoColor);
|
||||
Register(g_userSettings.cosmetics.herosTunicSkirtColor);
|
||||
Register(g_userSettings.cosmetics.zoraArmorCapColor);
|
||||
Register(g_userSettings.cosmetics.zoraArmorHelmetColor);
|
||||
Register(g_userSettings.cosmetics.zoraArmorTorsoColor);
|
||||
Register(g_userSettings.cosmetics.zoraArmorScalesColor);
|
||||
Register(g_userSettings.cosmetics.zoraArmorFlippersColor);
|
||||
Register(g_userSettings.cosmetics.lanternGlowColor);
|
||||
Register(g_userSettings.cosmetics.woodenSwordColor);
|
||||
Register(g_userSettings.cosmetics.msBladeColor);
|
||||
Register(g_userSettings.cosmetics.msHandleColor);
|
||||
Register(g_userSettings.cosmetics.lightSwordGlowColor);
|
||||
Register(g_userSettings.cosmetics.boomerangColor);
|
||||
Register(g_userSettings.cosmetics.ironBootsColor);
|
||||
Register(g_userSettings.cosmetics.spinnerColor);
|
||||
Register(g_userSettings.cosmetics.linkHairColor);
|
||||
Register(g_userSettings.cosmetics.wolfLinkColor);
|
||||
Register(g_userSettings.cosmetics.eponaColor);
|
||||
}
|
||||
|
||||
// Transient settings
|
||||
|
||||
@@ -7,6 +7,9 @@ float s_pitch_dp = 0.0f;
|
||||
} // namespace
|
||||
|
||||
void add_delta(float yaw_dp, float pitch_dp) noexcept {
|
||||
if (getSettings().game.enableMirrorMode) {
|
||||
yaw_dp *= -1.0;
|
||||
}
|
||||
s_yaw_dp += yaw_dp;
|
||||
s_pitch_dp += pitch_dp;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
#include "cosmetics.hpp"
|
||||
|
||||
#include "dusk/config.hpp"
|
||||
#include "dusk/randomizer/generator/utility/string.hpp"
|
||||
#include "pane.hpp"
|
||||
#include "string_button.hpp"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <random>
|
||||
|
||||
namespace dusk::ui {
|
||||
|
||||
static const auto defaultHexColors = std::unordered_map<std::string, std::string>({
|
||||
{"ab706e", "Red"},
|
||||
{"6382a0", "Blue"},
|
||||
{"94749a", "Purple"},
|
||||
{"ec8644", "Orange"},
|
||||
{"b9ab00", "Yellow"},
|
||||
{"ec9fc8", "Pink"},
|
||||
{"505154", "Black"},
|
||||
{"f8f7f4", "White"},
|
||||
{"91723e", "Brown"},
|
||||
});
|
||||
|
||||
static const auto defaultGlowColors = std::unordered_map<std::string, std::string>({
|
||||
{"ff0000", "Red"},
|
||||
{"f68821", "Orange"},
|
||||
{"f6f321", "Yellow"},
|
||||
{"00ff00", "Green"},
|
||||
{"0000ff", "Blue"},
|
||||
{"8000ff", "Purple"},
|
||||
{"a0a0a0", "White"},
|
||||
{"Rainbow", "Rainbow"},
|
||||
});
|
||||
|
||||
static const auto masterSwordColors = std::unordered_map<std::string, std::string>({
|
||||
{"ff0000", "Red"},
|
||||
{"f68821", "Orange"},
|
||||
{"f6f321", "Yellow"},
|
||||
{"00ff00", "Green"},
|
||||
{"0000ff", "Blue"},
|
||||
{"8000ff", "Purple"},
|
||||
{"a0a0a0", "White"},
|
||||
{"30d0d0", "Cyan"},
|
||||
});
|
||||
|
||||
void add_cosmetic_option(Pane& leftPane, Pane& rightPane, const char* key, ConfigVar<std::string>& option,
|
||||
const std::unordered_map<std::string, std::string>& colorPresets = defaultHexColors) {
|
||||
leftPane.register_control(leftPane.add_select_button({
|
||||
.key = key,
|
||||
.getValue = [&option, &colorPresets] {
|
||||
const auto& curHexStr = option.getValue();
|
||||
if (curHexStr.empty()) {
|
||||
return Rml::String("Default");
|
||||
}
|
||||
if (colorPresets.contains(curHexStr)) {
|
||||
return colorPresets.at(curHexStr);
|
||||
}
|
||||
return curHexStr;
|
||||
},
|
||||
}),
|
||||
rightPane, [key, &option, &colorPresets](Pane& pane) {
|
||||
pane.clear();
|
||||
pane.add_rml(fmt::format("Choose {}. Leave blank for default value. A reload or reboot may be required to see color changes ingame.", key));
|
||||
|
||||
pane.add_child<StringButton>(StringButton::Props{
|
||||
.key = "Edit Hex Color",
|
||||
.getValue = [&option] {
|
||||
return option;
|
||||
},
|
||||
.setValue = [&option](Rml::String str) {
|
||||
// Make lowercase
|
||||
for (char& c : str) {
|
||||
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
}
|
||||
|
||||
option.setValue(str);
|
||||
config::Save();
|
||||
},
|
||||
.maxLength = 6,
|
||||
});
|
||||
|
||||
pane.add_button(ControlledButton::Props{
|
||||
.text = "Default",
|
||||
.isSelected = [&option] {
|
||||
return option.getValue().empty();
|
||||
}
|
||||
}).on_pressed([&option] {
|
||||
option.setValue("");
|
||||
config::Save();
|
||||
});
|
||||
|
||||
pane.add_button(ControlledButton::Props{
|
||||
.text = "Random Color",
|
||||
}).on_pressed([&option] {
|
||||
std::random_device rd{};
|
||||
std::uniform_int_distribution dist(0, 0xFFFFFF);
|
||||
std::string hexStr = randomizer::utility::str::intToHex(dist(rd), false);
|
||||
option.setValue(hexStr);
|
||||
config::Save();
|
||||
});
|
||||
|
||||
for (const auto& [hexStr, color] : colorPresets) {
|
||||
pane.add_button(ControlledButton::Props{
|
||||
.text = color,
|
||||
.isSelected = [hexStr, &option] {
|
||||
return option.getValue() == hexStr;
|
||||
},
|
||||
}).on_pressed([hexStr, &option] {
|
||||
option.setValue(hexStr);
|
||||
config::Save();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
CosmeticsWindow::CosmeticsWindow() {
|
||||
|
||||
auto& cosmetics = getSettings().cosmetics;
|
||||
|
||||
add_tab("Equipment Colors", [this, &cosmetics](Rml::Element* content) {
|
||||
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
|
||||
auto& rightPane = add_child<Pane>(content, Pane::Type::Controlled);
|
||||
|
||||
add_cosmetic_option(leftPane, rightPane, "Hero's Tunic Cap Color", cosmetics.herosTunicCapColor);
|
||||
add_cosmetic_option(leftPane, rightPane, "Hero's Tunic Body Color", cosmetics.herosTunicTorsoColor);
|
||||
add_cosmetic_option(leftPane, rightPane, "Hero's Tunic Skirt Color", cosmetics.herosTunicSkirtColor);
|
||||
add_cosmetic_option(leftPane, rightPane, "Zora Armor Cap Color", cosmetics.zoraArmorCapColor);
|
||||
add_cosmetic_option(leftPane, rightPane, "Zora Armor Helmet Color", cosmetics.zoraArmorHelmetColor);
|
||||
add_cosmetic_option(leftPane, rightPane, "Zora Armor Torso Color", cosmetics.zoraArmorTorsoColor);
|
||||
add_cosmetic_option(leftPane, rightPane, "Zora Armor Scales Color", cosmetics.zoraArmorScalesColor);
|
||||
add_cosmetic_option(leftPane, rightPane, "Zora Armor Flippers Color", cosmetics.zoraArmorFlippersColor);
|
||||
add_cosmetic_option(leftPane, rightPane, "Lantern Glow Color", cosmetics.lanternGlowColor, defaultGlowColors);
|
||||
add_cosmetic_option(leftPane, rightPane, "Wooden Sword Color", cosmetics.woodenSwordColor);
|
||||
add_cosmetic_option(leftPane, rightPane, "Master Sword Blade Color", cosmetics.msBladeColor, masterSwordColors);
|
||||
add_cosmetic_option(leftPane, rightPane, "Master Sword Handle Color", cosmetics.msHandleColor, masterSwordColors);
|
||||
add_cosmetic_option(leftPane, rightPane, "Light Sword Glow Color", cosmetics.lightSwordGlowColor, defaultGlowColors);
|
||||
add_cosmetic_option(leftPane, rightPane, "Boomerang Color", cosmetics.boomerangColor);
|
||||
add_cosmetic_option(leftPane, rightPane, "Iron Boots Color", cosmetics.ironBootsColor);
|
||||
add_cosmetic_option(leftPane, rightPane, "Spinner Color", cosmetics.spinnerColor);
|
||||
|
||||
});
|
||||
|
||||
add_tab("Misc. Colors", [this, &cosmetics](Rml::Element* content) {
|
||||
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
|
||||
auto& rightPane = add_child<Pane>(content, Pane::Type::Controlled);
|
||||
|
||||
add_cosmetic_option(leftPane, rightPane, "Link's Hair Color", cosmetics.linkHairColor);
|
||||
add_cosmetic_option(leftPane, rightPane, "Wolf Link Color", cosmetics.wolfLinkColor);
|
||||
add_cosmetic_option(leftPane, rightPane, "Epona Color", cosmetics.eponaColor);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "window.hpp"
|
||||
|
||||
namespace dusk::ui {
|
||||
class CosmeticsWindow : public Window {
|
||||
public:
|
||||
CosmeticsWindow();
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,15 +7,17 @@
|
||||
|
||||
#include "achievements.hpp"
|
||||
#include "aurora/rmlui.hpp"
|
||||
#include "dusk/speedrun.h"
|
||||
#include "cosmetics.hpp"
|
||||
#include "dusk/livesplit.h"
|
||||
#include "dusk/main.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "dusk/speedrun.h"
|
||||
#include "editor.hpp"
|
||||
#include "f_pc/f_pc_manager.h"
|
||||
#include "f_pc/f_pc_name.h"
|
||||
#include "imgui.h"
|
||||
#include "modal.hpp"
|
||||
#include "rando_config.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "ui.hpp"
|
||||
#include "warp.hpp"
|
||||
@@ -24,7 +26,6 @@
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
|
||||
#include "rando_config.hpp"
|
||||
|
||||
namespace dusk::ui {
|
||||
namespace {
|
||||
@@ -63,6 +64,8 @@ MenuBar::MenuBar() : Document(kDocumentSource), mRoot(mDocument->GetElementById(
|
||||
|
||||
mTabBar->add_tab("Randomizer", [this] { push(std::make_unique<RandomizerWindow>()); });
|
||||
|
||||
mTabBar->add_tab("Cosmetics", [this] {push(std::make_unique<CosmeticsWindow>());});
|
||||
|
||||
mTabBar->add_tab("Reset", [this] {
|
||||
mTabBar->set_active_tab(-1);
|
||||
const auto dismiss = [](Modal& modal) { modal.pop(); };
|
||||
|
||||
@@ -77,6 +77,18 @@ constexpr std::array kInterpolationModes = {
|
||||
"Unlimited",
|
||||
};
|
||||
|
||||
constexpr std::array kTouchTargetingLabels = {
|
||||
"Hybrid",
|
||||
"Hold",
|
||||
"Switch",
|
||||
};
|
||||
|
||||
constexpr std::array kTouchTargetingDescriptions = {
|
||||
"Tap once to lock on when a target is found. Double-tap when none is found to hold L.",
|
||||
"L stays held only while your finger is on the button.",
|
||||
"Tap L to keep it held. Tap again to release it.",
|
||||
};
|
||||
|
||||
constexpr std::array kGyroInputModeLabels = {
|
||||
"Sensor",
|
||||
"Mouse",
|
||||
@@ -407,6 +419,14 @@ bool gyro_enabled() {
|
||||
return getSettings().game.enableGyroAim || getSettings().game.enableGyroRollgoal;
|
||||
}
|
||||
|
||||
Rml::String touch_targeting_label(TouchTargeting targeting) {
|
||||
const auto index = static_cast<std::size_t>(targeting);
|
||||
if (index >= kTouchTargetingLabels.size()) {
|
||||
return "Unknown";
|
||||
}
|
||||
return kTouchTargetingLabels[index];
|
||||
}
|
||||
|
||||
struct ConfigBoolProps {
|
||||
Rml::String key;
|
||||
Rml::String icon;
|
||||
@@ -1003,6 +1023,45 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
pane.clear();
|
||||
pane.add_text("Open the touch controls layout editor.");
|
||||
});
|
||||
leftPane.register_control(leftPane.add_select_button({
|
||||
.key = "Touch Targeting",
|
||||
.getValue =
|
||||
[] {
|
||||
return touch_targeting_label(
|
||||
getSettings().game.touchTargeting.getValue());
|
||||
},
|
||||
.isDisabled =
|
||||
[] { return !getSettings().game.enableTouchControls; },
|
||||
.isModified =
|
||||
[] {
|
||||
const auto& targeting =
|
||||
getSettings().game.touchTargeting;
|
||||
return targeting.getValue() !=
|
||||
targeting.getDefaultValue();
|
||||
},
|
||||
}),
|
||||
rightPane, [](Pane& pane) {
|
||||
pane.clear();
|
||||
for (int i = 0; i < static_cast<int>(kTouchTargetingLabels.size()); ++i) {
|
||||
pane.add_button({
|
||||
.text = kTouchTargetingLabels[i],
|
||||
.isSelected =
|
||||
[i] {
|
||||
return getSettings().game.touchTargeting.getValue() ==
|
||||
static_cast<TouchTargeting>(i);
|
||||
},
|
||||
})
|
||||
.on_pressed([i] {
|
||||
mDoAud_seStartMenu(kSoundItemChange);
|
||||
getSettings().game.touchTargeting.setValue(
|
||||
static_cast<TouchTargeting>(i));
|
||||
config::Save();
|
||||
});
|
||||
}
|
||||
pane.add_rml(fmt::format("<br/>Hybrid: {}<br/>Hold: {}<br/>Switch: {}",
|
||||
kTouchTargetingDescriptions[0], kTouchTargetingDescriptions[1],
|
||||
kTouchTargetingDescriptions[2]));
|
||||
});
|
||||
config_percent_select(leftPane, rightPane, getSettings().game.touchCameraXSensitivity,
|
||||
"Touch Camera X Sensitivity",
|
||||
"Adjusts touch camera horizontal sensitivity.<br/><br/>Applies to touch input only.",
|
||||
|
||||
@@ -485,43 +485,71 @@ void TouchControls::set_control_pressed(Control control, bool pressed) {
|
||||
mLastLTapTime = {};
|
||||
break;
|
||||
}
|
||||
if (pressed && (mLLatched || mManualLLatched)) {
|
||||
switch (getSettings().game.touchTargeting.getValue()) {
|
||||
case TouchTargeting::Hold:
|
||||
mLPressed = pressed;
|
||||
mLLatched = false;
|
||||
mManualLLatched = false;
|
||||
mLPressed = false;
|
||||
mLReleasePending = true;
|
||||
mLReleasePending = false;
|
||||
mLPressStartTime = {};
|
||||
mLastLTapTime = {};
|
||||
set_control_visual(control, false);
|
||||
} else if (pressed) {
|
||||
const auto now = clock::now();
|
||||
if (!player_attention_locked() && mLastLTapTime != clock::time_point{} &&
|
||||
now - mLastLTapTime <= kLDoubleTapWindow)
|
||||
{
|
||||
mManualLLatched = true;
|
||||
break;
|
||||
case TouchTargeting::Switch:
|
||||
if (pressed) {
|
||||
const bool wasLatched = mLPressed || mLLatched || mManualLLatched;
|
||||
mLPressed = false;
|
||||
mLLatched = false;
|
||||
mManualLLatched = !wasLatched;
|
||||
mLReleasePending = true;
|
||||
} else {
|
||||
mLPressed = false;
|
||||
mLLatched = false;
|
||||
mLReleasePending = false;
|
||||
}
|
||||
mLPressStartTime = {};
|
||||
mLastLTapTime = {};
|
||||
break;
|
||||
case TouchTargeting::Hybrid:
|
||||
default:
|
||||
if (pressed && (mLLatched || mManualLLatched)) {
|
||||
mLLatched = false;
|
||||
mManualLLatched = false;
|
||||
mLPressed = false;
|
||||
mLReleasePending = true;
|
||||
mLPressStartTime = {};
|
||||
mLastLTapTime = {};
|
||||
set_control_visual(control, false);
|
||||
} else if (pressed) {
|
||||
const auto now = clock::now();
|
||||
if (!player_attention_locked() && mLastLTapTime != clock::time_point{} &&
|
||||
now - mLastLTapTime <= kLDoubleTapWindow)
|
||||
{
|
||||
mManualLLatched = true;
|
||||
mLPressed = false;
|
||||
mLReleasePending = true;
|
||||
mLPressStartTime = {};
|
||||
mLastLTapTime = {};
|
||||
} else if (!mLReleasePending) {
|
||||
mLPressed = true;
|
||||
mLPressStartTime = now;
|
||||
}
|
||||
} else if (!mLReleasePending) {
|
||||
mLPressed = true;
|
||||
mLPressStartTime = now;
|
||||
mLPressed = false;
|
||||
}
|
||||
} else if (!mLReleasePending) {
|
||||
mLPressed = false;
|
||||
}
|
||||
if (!pressed) {
|
||||
const auto now = clock::now();
|
||||
if (!mLReleasePending) {
|
||||
const bool wasQuickTap = mLPressStartTime != clock::time_point{} &&
|
||||
now - mLPressStartTime <= kLDoubleTapWindow;
|
||||
mLastLTapTime = wasQuickTap ? now : clock::time_point{};
|
||||
if (!pressed) {
|
||||
const auto now = clock::now();
|
||||
if (!mLReleasePending) {
|
||||
const bool wasQuickTap = mLPressStartTime != clock::time_point{} &&
|
||||
now - mLPressStartTime <= kLDoubleTapWindow;
|
||||
mLastLTapTime = wasQuickTap ? now : clock::time_point{};
|
||||
}
|
||||
mLPressStartTime = {};
|
||||
mLReleasePending = false;
|
||||
}
|
||||
mLPressStartTime = {};
|
||||
mLReleasePending = false;
|
||||
}
|
||||
if (!pressed && !player_attention_locked()) {
|
||||
mLLatched = false;
|
||||
if (!pressed && !player_attention_locked()) {
|
||||
mLLatched = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Control::R:
|
||||
@@ -635,6 +663,17 @@ void TouchControls::apply_control_transform(Control control) noexcept {
|
||||
}
|
||||
|
||||
void TouchControls::sync_l_lock_state() noexcept {
|
||||
const auto targeting = getSettings().game.touchTargeting.getValue();
|
||||
if (targeting == TouchTargeting::Hold) {
|
||||
mLLatched = false;
|
||||
mManualLLatched = false;
|
||||
return;
|
||||
}
|
||||
if (targeting == TouchTargeting::Switch) {
|
||||
mLLatched = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (player_attention_locked()) {
|
||||
if (mLPressed) {
|
||||
mLLatched = true;
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
#include "m_Do/m_Do_ext.h"
|
||||
#include "os_report.h"
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/cosmetics/texture_utils.hpp"
|
||||
#endif
|
||||
|
||||
s32 mDoDvdThd::main(void* param_0) {
|
||||
JKRThread(OSGetCurrentThread(), 0);
|
||||
#if TARGET_PC
|
||||
@@ -314,6 +318,9 @@ s32 mDoDvdThd_mountArchive_c::execute() {
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#if TARGET_PC
|
||||
dusk::cosmetics::handle_texture_overrides_on_load(this);
|
||||
#endif
|
||||
mIsDone = true;
|
||||
return mArchive != NULL;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
"""
|
||||
Loads d_a_mant.rel from CWD, applies tears using seed (66, 16983, 855) and cut type 1, writes PNGs to tear_textures/
|
||||
Requires pillow and xxhash.
|
||||
"""
|
||||
|
||||
import math
|
||||
import struct
|
||||
import xxhash
|
||||
from pathlib import Path
|
||||
from PIL import Image
|
||||
|
||||
def yaz0_decompress(data):
|
||||
expand_size = struct.unpack_from(">I", data, 4)[0]
|
||||
out = bytearray(expand_size)
|
||||
src_pos = 0x10
|
||||
dst_pos = 0
|
||||
chunk_bits_left = 0
|
||||
chunk_bits = 0
|
||||
|
||||
while dst_pos < expand_size:
|
||||
if chunk_bits_left == 0:
|
||||
chunk_bits = data[src_pos]
|
||||
src_pos += 1
|
||||
chunk_bits_left = 8
|
||||
|
||||
if chunk_bits & 0x80:
|
||||
out[dst_pos] = data[src_pos]
|
||||
src_pos += 1
|
||||
dst_pos += 1
|
||||
else:
|
||||
b0 = data[src_pos]
|
||||
b1 = data[src_pos + 1]
|
||||
src_pos += 2
|
||||
dist = ((b0 & 0x0F) << 8) | b1
|
||||
count = b0 >> 4
|
||||
if count == 0:
|
||||
count = data[src_pos] + 0x12
|
||||
src_pos += 1
|
||||
else:
|
||||
count += 2
|
||||
|
||||
copy_pos = dst_pos - dist - 1
|
||||
for _ in range(count):
|
||||
out[dst_pos] = out[copy_pos]
|
||||
dst_pos += 1
|
||||
copy_pos += 1
|
||||
|
||||
chunk_bits <<= 1
|
||||
chunk_bits_left -= 1
|
||||
|
||||
return bytes(out)
|
||||
|
||||
SINCOS_TABLE = tuple(
|
||||
(
|
||||
math.sin((i * math.tau) / (1 << 13)),
|
||||
math.cos((i * math.tau) / (1 << 13)),
|
||||
)
|
||||
for i in range(1 << 13)
|
||||
)
|
||||
|
||||
def rnd(rng):
|
||||
rng[0] = (rng[0] * 171) % 30269
|
||||
rng[1] = (rng[1] * 172) % 30307
|
||||
rng[2] = (rng[2] * 170) % 30323
|
||||
value = rng[0] / 30269.0 + rng[1] / 30307.0 + rng[2] / 30323.0
|
||||
return abs(value % 1.0)
|
||||
|
||||
def rnd_f(rng, max_value):
|
||||
return rnd(rng) * max_value
|
||||
|
||||
def rnd_fx(rng, max_value):
|
||||
return max_value * (rnd(rng) - 0.5) * 2.0
|
||||
|
||||
def linear_index_to_swizzled(linear_index):
|
||||
within_tile_x = linear_index & 0x7
|
||||
tile_row_offset = (linear_index & 0x78) * 4
|
||||
tile_column_offset = (linear_index >> 4) & 0x18
|
||||
macro_row_offset = linear_index & 0x3E00
|
||||
|
||||
return within_tile_x + tile_row_offset + tile_column_offset + macro_row_offset
|
||||
|
||||
SWIZZLED_TO_XY = [(0, 0)] * 0x4000
|
||||
for linear_index in range(0x4000):
|
||||
x = linear_index & 0x7F
|
||||
y = linear_index >> 7
|
||||
swizzled_index = linear_index_to_swizzled(linear_index)
|
||||
SWIZZLED_TO_XY[swizzled_index] = (x, y)
|
||||
|
||||
NEIGHBOR_OFFSETS = (0, 1, 0x80, 0x81, 2, 0x82, 0x102, 0x101, 0x100)
|
||||
|
||||
def write_c8_texture(c8_data, stage, output_dir, palette, palette_data):
|
||||
min_index = min(c8_data)
|
||||
max_index = max(c8_data)
|
||||
tlut_offset = 2 * min_index
|
||||
tlut_size = 2 * (max_index + 1 - min_index)
|
||||
tlut_end = tlut_offset + tlut_size
|
||||
path = output_dir / (
|
||||
f"[{stage:02d}] tex1_{0x80}x{0x80}_"
|
||||
f"{xxhash.xxh64(c8_data, seed=0).intdigest():016x}_"
|
||||
f"{xxhash.xxh64(palette_data[tlut_offset:tlut_end], seed=0).intdigest():016x}_"
|
||||
f"{0x9}.png"
|
||||
)
|
||||
|
||||
rgba = Image.new("RGBA", (0x80, 0x80))
|
||||
pixels = rgba.load()
|
||||
for swizzled_index, palette_index in enumerate(c8_data):
|
||||
x, y = SWIZZLED_TO_XY[swizzled_index]
|
||||
pixels[x, y] = palette[palette_index]
|
||||
rgba.save(path)
|
||||
return path
|
||||
|
||||
def write_stage(tex, tex_u, pal, stage, output_dir, palette):
|
||||
return [
|
||||
write_c8_texture(bytes(tex), stage, output_dir, palette, pal),
|
||||
write_c8_texture(bytes(tex_u), stage, output_dir, palette, pal),
|
||||
]
|
||||
|
||||
def export_mant_tears():
|
||||
rel = yaz0_decompress(Path("d_a_mant.rel").read_bytes())
|
||||
tex = bytearray(rel[0x1C00 : 0x1C00 + 0x4000])
|
||||
tex_u = bytearray([6] * 0x4000)
|
||||
pal = bytes(rel[0x9C00 : 0x9C00 + 0x60])
|
||||
rng = [int(66), int(16983), int(855)]
|
||||
|
||||
Path("tear_textures").mkdir(parents=True, exist_ok=True)
|
||||
written = []
|
||||
|
||||
rgba_palette = []
|
||||
for offset in range(0, len(pal), 2):
|
||||
color16 = struct.unpack_from(">H", pal, offset)[0]
|
||||
if color16 & 0x8000:
|
||||
r = (color16 >> 7) & 0xF8
|
||||
r |= r >> 5
|
||||
g = (color16 >> 2) & 0xF8
|
||||
g |= g >> 5
|
||||
b = (color16 << 3) & 0xF8
|
||||
b |= b >> 5
|
||||
a = 255
|
||||
else:
|
||||
r = (color16 >> 4) & 0xF0
|
||||
r |= r >> 4
|
||||
g = color16 & 0xF0
|
||||
g |= g >> 4
|
||||
b = (color16 << 4) & 0xF0
|
||||
b |= b >> 4
|
||||
a = (color16 >> 7) & 0xE0
|
||||
a |= (a >> 3) | (a >> 6)
|
||||
rgba_palette.append((r, g, b, a))
|
||||
while len(rgba_palette) < 256:
|
||||
rgba_palette.append((0, 0, 0, 0))
|
||||
|
||||
written.extend(write_stage(tex, tex_u, pal, 0, Path("tear_textures"), rgba_palette))
|
||||
|
||||
cut_step = 0
|
||||
for _ in range(15):
|
||||
cut_step += 1
|
||||
|
||||
angle = int(rnd_f(rng, 65536.0)) & 0xFFFF
|
||||
if angle >= 0x8000:
|
||||
angle -= 0x10000
|
||||
|
||||
x = rnd_fx(rng, 32.0)
|
||||
y = rnd_fx(rng, 32.0)
|
||||
sincos_index = (angle & 0xFFFF) >> (16 - 13)
|
||||
sin_v, cos_v = SINCOS_TABLE[sincos_index]
|
||||
|
||||
for i, texel_count in enumerate(
|
||||
tuple(
|
||||
1 if i <= 3 or i >= 26 else (9 if 12 <= i <= 18 else 4)
|
||||
for i in range(30)
|
||||
)
|
||||
):
|
||||
x += sin_v
|
||||
y -= cos_v
|
||||
|
||||
packed = int(x + 64.0) | (int(y + 64.0) << 7)
|
||||
for j in range(texel_count):
|
||||
u_var1 = packed + NEIGHBOR_OFFSETS[j]
|
||||
if 0 <= u_var1 < 0x4000:
|
||||
i_var5 = linear_index_to_swizzled(u_var1)
|
||||
tex[i_var5] = 0
|
||||
tex_u[i_var5] = 0
|
||||
|
||||
written.extend(write_stage(tex, tex_u, pal, cut_step, Path("tear_textures"), rgba_palette))
|
||||
|
||||
return written
|
||||
|
||||
if __name__ == "__main__":
|
||||
export_mant_tears()
|
||||
Reference in New Issue
Block a user