mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-08-12 11:22:00 -04:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 75fd5ad1c4 | |||
| bf4c727d04 | |||
| ea1f50fe27 | |||
| 99180d02f7 | |||
| 5b64e4fd2c | |||
| 692c04e936 | |||
| e17d0f21fc | |||
| 91e77b1051 | |||
| 0b1a2c10b6 | |||
| e855e6471f | |||
| 848f635798 |
@@ -519,7 +519,6 @@ if (DUSK_ENABLE_CODE_MODS AND CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR
|
||||
add_subdirectory(mods/ao_mod)
|
||||
add_subdirectory(mods/shadow_mod)
|
||||
add_subdirectory(mods/window_demo)
|
||||
add_subdirectory(mods/basic_cosmetics_mod)
|
||||
endif ()
|
||||
|
||||
if (APPLE)
|
||||
|
||||
+12
-5
@@ -244,7 +244,7 @@ set(DOLZEL_FILES
|
||||
src/CaptureScreen.cpp
|
||||
)
|
||||
if(DEBUG)
|
||||
list(APPEND DOLZEL_FILES src/d/d_event_debug.cpp)
|
||||
list(APPEND DOLZEL_FILES src/d/d_event_debug.cpp)
|
||||
endif(DEBUG)
|
||||
|
||||
set(Z2AUDIOLIB_FILES
|
||||
@@ -1404,10 +1404,10 @@ set(REL_FILES
|
||||
)
|
||||
|
||||
set(DOLPHIN_FILES
|
||||
libs/dolphin/src/gf/GFGeometry.cpp
|
||||
libs/dolphin/src/gf/GFLight.cpp
|
||||
libs/dolphin/src/gf/GFPixel.cpp
|
||||
libs/dolphin/src/gf/GFTev.cpp
|
||||
libs/dolphin/src/gf/GFGeometry.cpp
|
||||
libs/dolphin/src/gf/GFLight.cpp
|
||||
libs/dolphin/src/gf/GFPixel.cpp
|
||||
libs/dolphin/src/gf/GFTev.cpp
|
||||
)
|
||||
|
||||
set(DUSK_FILES
|
||||
@@ -1432,10 +1432,15 @@ set(DUSK_FILES
|
||||
src/dusk/dvd_asset.hpp
|
||||
src/dusk/extras.c
|
||||
src/dusk/frame_interpolation.cpp
|
||||
src/dusk/commands.cpp
|
||||
src/dusk/commands.hpp
|
||||
src/dusk/game_clock.cpp
|
||||
src/dusk/gamepad_color.cpp
|
||||
src/dusk/globals.cpp
|
||||
src/dusk/gyro.cpp
|
||||
src/dusk/game_combos.cpp
|
||||
src/dusk/d_trigger_view.cpp
|
||||
#src/dusk/m_Do_ext_dusk.cpp
|
||||
src/dusk/imgui/ImGuiActorSpawner.cpp
|
||||
src/dusk/imgui/ImGuiBloomWindow.cpp
|
||||
src/dusk/imgui/ImGuiBloomWindow.hpp
|
||||
@@ -1509,6 +1514,8 @@ set(DUSK_FILES
|
||||
src/dusk/touch_camera.cpp
|
||||
src/dusk/ui/achievements.cpp
|
||||
src/dusk/ui/achievements.hpp
|
||||
src/dusk/ui/command_console.cpp
|
||||
src/dusk/ui/command_console.hpp
|
||||
src/dusk/ui/bool_button.cpp
|
||||
src/dusk/ui/bool_button.hpp
|
||||
src/dusk/ui/button.cpp
|
||||
|
||||
@@ -423,7 +423,7 @@ public:
|
||||
static DUSK_GAME_DATA daMidna_texData_s const m_texDataTable[21];
|
||||
static DUSK_GAME_DATA daMidna_anmData_s const m_anmDataTable[53];
|
||||
|
||||
// private:
|
||||
private:
|
||||
/* 0x568 */ request_of_phase_process_class mPhase;
|
||||
/* 0x570 */ J3DModel* mpModel;
|
||||
/* 0x574 */ J3DModel* mpShadowModel;
|
||||
|
||||
@@ -37,7 +37,7 @@ public:
|
||||
CPaneMgrAlpha* getNoDatBase() { return mNoDatBase; }
|
||||
void draw() { _draw(); }
|
||||
|
||||
// private:
|
||||
private:
|
||||
/* 0x04 */ JKRArchive* mArchive;
|
||||
/* 0x08 */ dDlst_FileInfo_c mFileInfo;
|
||||
/* 0x1C */ u8 field_0x1c[4];
|
||||
|
||||
@@ -13,8 +13,36 @@ class dFile_info_c;
|
||||
class J2DPicture;
|
||||
|
||||
#if TARGET_PC
|
||||
static PaneCache mSelDtPanes[] = {
|
||||
{MULTI_CHAR('tate_n0'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('tate_n1'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('ken_n0'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('ken_n1'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('fuku_n0'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('fuku_n1'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('fuku_n2'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('gray_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('b_base'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('b_base1'), 0.0f, 0.0f, false},
|
||||
};
|
||||
|
||||
|
||||
static PaneCache fileSelPanes[] = {
|
||||
{MULTI_CHAR('w_uzu00'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('w_uzu01'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('w_uzu02'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('w_uzu03'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('w_uzu04'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('w_uzu05'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('w_uzu06'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('w_uzu07'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('w_uzu08'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('w_uzu09'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('w_er_msg'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('w_er_msE'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('w_er_msR'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('er_for0'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('er_for1'), 0.0f, 0.0f, false},
|
||||
};
|
||||
#endif
|
||||
|
||||
class dDlst_FileSel_c : public dDlst_base_c {
|
||||
|
||||
@@ -30,7 +30,7 @@ public:
|
||||
|
||||
void drawSelf() { mpKanteraIcon->draw(); }
|
||||
|
||||
// private:
|
||||
private:
|
||||
/* 0x04 */ dDlst_KanteraIcon_c* mpKanteraIcon;
|
||||
/* 0x08 */ CPaneMgr* mpParent;
|
||||
/* 0x0C */ CPaneMgr* mpGauge;
|
||||
|
||||
@@ -15,6 +15,49 @@ class dMenu_Fishing_c;
|
||||
class dMenu_Skill_c;
|
||||
class dMenu_Insect_c;
|
||||
class dSelect_cursor_c;
|
||||
|
||||
#if TARGET_PC
|
||||
struct PaneCache {
|
||||
u64 tag;
|
||||
f32 origTransX;
|
||||
f32 origTransY;
|
||||
bool cached;
|
||||
};
|
||||
|
||||
static PaneCache mpScreenPanes[] = {
|
||||
{MULTI_CHAR('sa_tex_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('op_tex_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('heart_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('wolf_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('item_0_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('item_1_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('item_2_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('fish_3_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('lett_4_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('maki_5_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('fuku_n0'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('fuku_n1'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('fuku_n2'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('tate_n0'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('tate_n1'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('ken_n0'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('ken_n1'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('kabu_6n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('t_t00'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('f_t00'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('itemn_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('infotxtn'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('sa_op_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('title_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('menu_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('w_er_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('center_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('info_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('lavel_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('modelbgn'), 0.0f, 0.0f, false},
|
||||
};
|
||||
#endif
|
||||
|
||||
class dMenu_Collect2D_c;
|
||||
class dMenu_Collect2DTop_c : public dDlst_base_c {
|
||||
public:
|
||||
@@ -129,7 +172,7 @@ public:
|
||||
J2DPicture* getBlackTex() { return mpBlackTex; }
|
||||
u8 getSubWindowOpenCheck() { return mSubWindowOpenCheck; }
|
||||
|
||||
// private:
|
||||
private:
|
||||
/* 0x004 */ JKRExpHeap* mpHeap;
|
||||
/* 0x008 */ JKRExpHeap* mpSubHeap;
|
||||
/* 0x00C */ void* field_0xc;
|
||||
|
||||
@@ -43,7 +43,7 @@ public:
|
||||
|
||||
u8 getStatus() { return mStatus; }
|
||||
|
||||
// private:
|
||||
private:
|
||||
/* 0x004 */ JKRExpHeap* mpHeap;
|
||||
/* 0x008 */ JKRArchive* mpArchive;
|
||||
/* 0x00C */ STControl* mpStick;
|
||||
|
||||
@@ -270,7 +270,7 @@ public:
|
||||
|
||||
static DUSK_GAME_DATA dMenu_Fmap_c* MyClass;
|
||||
|
||||
// private:
|
||||
private:
|
||||
/* 0x004 */ JKRExpHeap* mpHeap;
|
||||
/* 0x008 */ JKRExpHeap* mpTalkHeap;
|
||||
/* 0x00C */ STControl* mpStick;
|
||||
|
||||
@@ -60,7 +60,7 @@ public:
|
||||
|
||||
u8 getStatus() { return mStatus; }
|
||||
|
||||
// private:
|
||||
private:
|
||||
/* 0x04 */ JKRExpHeap* mpHeap;
|
||||
/* 0x08 */ JKRArchive* mpArchive;
|
||||
/* 0x0C */ STControl* mpStick;
|
||||
|
||||
@@ -64,7 +64,7 @@ public:
|
||||
|
||||
u8 getStatus() { return mStatus; }
|
||||
|
||||
// private:
|
||||
private:
|
||||
/* 0x004 */ JKRExpHeap* mpHeap;
|
||||
/* 0x008 */ JKRArchive* mpArchive;
|
||||
/* 0x00C */ STControl* mpStick;
|
||||
|
||||
@@ -117,7 +117,7 @@ public:
|
||||
return ((u8)mUseFlag & (u8)i_flag) != 0 ? true : false;
|
||||
}
|
||||
|
||||
// private:
|
||||
private:
|
||||
/* 0x004 */ J2DScreen* mpBackScreen;
|
||||
/* 0x008 */ J2DScreen* mpScreen;
|
||||
/* 0x00C */ J2DScreen* mpClipScreen;
|
||||
|
||||
@@ -84,7 +84,7 @@ public:
|
||||
void drawFlag0() { mDrawFlag = 0; }
|
||||
void setStatus(u8 i_status) { mStatus = i_status; }
|
||||
|
||||
// private:
|
||||
private:
|
||||
/* 0x004 */ JKRExpHeap* mpHeap;
|
||||
/* 0x008 */ STControl* mpStick;
|
||||
/* 0x00C */ CSTControl* mpCStick;
|
||||
|
||||
@@ -278,7 +278,7 @@ public:
|
||||
u8 getEndStatus() { return mEndStatus; }
|
||||
void setUseType(u8 type) { mUseType = type; }
|
||||
|
||||
// private:
|
||||
private:
|
||||
/* 0x0004 */ JKRArchive* mpArchive;
|
||||
/* 0x0008 */ mDoDvdThd_mountArchive_c* mpMount;
|
||||
/* 0x000C */ STControl* stick;
|
||||
|
||||
@@ -58,7 +58,7 @@ public:
|
||||
|
||||
u8 getStatus() { return mStatus; }
|
||||
|
||||
// private:
|
||||
private:
|
||||
/* 0x004 */ JKRExpHeap* mpHeap;
|
||||
/* 0x008 */ JKRArchive* mpArchive;
|
||||
/* 0x00C */ STControl* mpStick;
|
||||
|
||||
@@ -154,7 +154,7 @@ public:
|
||||
}
|
||||
#endif
|
||||
|
||||
// private:
|
||||
private:
|
||||
/* 0x004 */ item_params mItemParams[4];
|
||||
/* 0x074 */ JKRExpHeap* heap;
|
||||
/* 0x078 */ J2DScreen* mpScreen;
|
||||
|
||||
@@ -25,7 +25,7 @@ public:
|
||||
void setTextBoxPtr(J2DTextBox* p_textBox) { mpTextBoxPtr = p_textBox; }
|
||||
void resetType() { mType = 0x47; }
|
||||
|
||||
// private:
|
||||
private:
|
||||
/* 0x04 */ J2DTextBox* mpTextBoxPtr;
|
||||
/* 0x08 */ f32 mPosX;
|
||||
/* 0x0C */ f32 mPosY;
|
||||
@@ -53,7 +53,7 @@ public:
|
||||
|
||||
void setRupeeColor(u8 color) { mRupeeColor = color; }
|
||||
|
||||
// private:
|
||||
private:
|
||||
/* 0x004 */ COutFontSet_c* mpOfs[35];
|
||||
/* 0x090 */ J2DPicture* mpPane[70];
|
||||
/* 0x1A8 */ f32 mAlphaRatio;
|
||||
|
||||
@@ -12,6 +12,34 @@ class J2DTextBox;
|
||||
class JUTFont;
|
||||
class STControl;
|
||||
|
||||
#if TARGET_PC
|
||||
struct PaneCache {
|
||||
u64 tag;
|
||||
f32 origTransX;
|
||||
f32 origTransY;
|
||||
bool cached;
|
||||
};
|
||||
|
||||
static PaneCache l_tagName[] = {
|
||||
{MULTI_CHAR('m_00_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_00_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_00_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_00_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_00_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_01_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_01_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_01_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_01_3'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('m_01_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_02_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_02_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_02_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_02_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_02_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m03_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m03_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m03_2'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('m03_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m03_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_04_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_04_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_04_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_04_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_04_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_05_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_05_1'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('m_05_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_05_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_05_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_06_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_06_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_06_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_06_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_06_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_07_0'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('m_07_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_07_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_07_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_07_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_08_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_08_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_08_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_08_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_08_4'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('m_09_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_09_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_09_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_09_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_09_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_10_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_10_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_10_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_10_3'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('m_10_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_11_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_11_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_11_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_11_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_11_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m12_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m12_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m12_2'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('m12_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m12_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('p_end_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('p_end_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('p_end_0'), 0.0f, 0.0f, false},
|
||||
};
|
||||
|
||||
static PaneCache l_nameTagName[] = {
|
||||
{MULTI_CHAR('name_00'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_01'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_02'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_03'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_04'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_05'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_06'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_07'), 0.0f, 0.0f, false},
|
||||
};
|
||||
|
||||
static PaneCache l_nameCurTagName[] = {
|
||||
{MULTI_CHAR('s__n_00'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_01'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_02'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_03'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_04'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_05'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_06'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_07'), 0.0f, 0.0f, false},
|
||||
};
|
||||
#endif
|
||||
|
||||
class dNm_HIO_c {
|
||||
public:
|
||||
dNm_HIO_c();
|
||||
|
||||
@@ -1351,6 +1351,9 @@ enum dStage_SaveTbl {
|
||||
|
||||
const char* dStage_getName2(s16, s8);
|
||||
dStage_objectNameInf* dStage_searchName(const char*);
|
||||
#if TARGET_PC
|
||||
dStage_objectNameInf* dStage_searchNameCI(const char*);
|
||||
#endif
|
||||
static int dStage_stageKeepTresureInit(dStage_dt_c*, void*, int, void*);
|
||||
static int dStage_filiInfo2Init(dStage_dt_c*, void*, int, void*);
|
||||
static int dStage_mapPathInitCommonLayer(dStage_dt_c*, void*, int, void*);
|
||||
|
||||
@@ -68,7 +68,7 @@ public:
|
||||
JKRMemArchive* getArchive() const { return mArchive; }
|
||||
JKRHeap* getHeap() const { return mHeap; }
|
||||
|
||||
// private:
|
||||
private:
|
||||
/* 0x14 */ u8 mMountDirection;
|
||||
/* 0x18 */ s32 mEntryNumber;
|
||||
/* 0x1C */ JKRMemArchive* mArchive;
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.25)
|
||||
project(basic_cosmetics_mod CXX)
|
||||
|
||||
if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
||||
set(DUSK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../.." CACHE PATH "Path to dusk source root")
|
||||
option(DUSK_MOD_USE_FULL_TREE "Use full build instead of the minimal mod SDK" OFF)
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
if (DUSK_MOD_USE_FULL_TREE)
|
||||
add_subdirectory("${DUSK_DIR}" dusk EXCLUDE_FROM_ALL)
|
||||
else ()
|
||||
add_subdirectory("${DUSK_DIR}/sdk" dusk-sdk EXCLUDE_FROM_ALL)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
set(COSMETIC_SOURCES src/color_utils.cpp src/midna_hair_color.cpp src/texture_utils.cpp src/hooks.cpp)
|
||||
|
||||
add_mod(basic_cosmetics_mod
|
||||
FEATURES game fmt
|
||||
SOURCES src/mod.cpp ${COSMETIC_SOURCES}
|
||||
MOD_JSON mod.json
|
||||
RES_DIR res
|
||||
)
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"id": "dev.twilitrealm.basic_cosmetics",
|
||||
"name": "Basic Cosmetics",
|
||||
"version": "1.0.0",
|
||||
"author": "Twilit Realm",
|
||||
"description": "Change Basic Cosmetic Colors"
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
#include "color_utils.hpp"
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// Helper function to perform overlay blending on a single 8-bit color channel
|
||||
uint8_t blend_overlay_channel(uint8_t base, uint8_t blend)
|
||||
{
|
||||
if (base < 128) {
|
||||
return static_cast<uint8_t>((2 * base * blend) / 255);
|
||||
}
|
||||
|
||||
return static_cast<uint8_t>(255 - (2 * (255 - base) * (255 - blend)) / 255);
|
||||
}
|
||||
|
||||
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};
|
||||
}
|
||||
|
||||
static f32 rainbowPhaseAngle = 0.f;
|
||||
|
||||
GXColor get_rainbow_rgb(f32 amplitude) {
|
||||
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;
|
||||
}
|
||||
|
||||
void update_rainbow_rgb(f32 increment) {
|
||||
rainbowPhaseAngle += increment;
|
||||
if (rainbowPhaseAngle >= 360.0f) {
|
||||
rainbowPhaseAngle -= 360.0f;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
#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 <gx.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
// 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);
|
||||
|
||||
// Perform overlay blending on a single 8-bit color channel
|
||||
uint8_t blend_overlay_channel(uint8_t base, uint8_t blend);
|
||||
|
||||
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);
|
||||
|
||||
void update_rainbow_rgb(f32 increment);
|
||||
@@ -1,694 +0,0 @@
|
||||
#include "hooks.hpp"
|
||||
|
||||
#include "mod.hpp"
|
||||
#include "color_utils.hpp"
|
||||
#include "midna_hair_color.hpp"
|
||||
#include "texture_utils.hpp"
|
||||
#include "types.h"
|
||||
|
||||
#include "mods/svc/hook.hpp"
|
||||
#include "mods/svc/log.hpp"
|
||||
|
||||
#include "d/actor/d_a_alink.h"
|
||||
#include "d/actor/d_a_midna.h"
|
||||
#include "d/d_a_item_static.h"
|
||||
#include "d/d_bright_check.h"
|
||||
#include "d/d_file_sel_info.h"
|
||||
#include "d/d_file_select.h"
|
||||
#include "d/d_kankyo.h"
|
||||
#include "d/d_kantera_icon_meter.h"
|
||||
#include "d/d_menu_collect.h"
|
||||
#include "d/d_menu_fishing.h"
|
||||
#include "d/d_menu_fmap2D.h"
|
||||
#include "d/d_menu_insect.h"
|
||||
#include "d/d_menu_letter.h"
|
||||
#include "d/d_menu_option.h"
|
||||
#include "d/d_menu_ring.h"
|
||||
#include "d/d_menu_save.h"
|
||||
#include "d/d_menu_skill.h"
|
||||
#include "d/d_meter_button.h"
|
||||
#include "d/d_meter_hakusha.h"
|
||||
#include "d/d_meter2.h"
|
||||
#include "d/d_meter2_draw.h"
|
||||
#include "d/d_meter2_info.h"
|
||||
#include "d/d_msg_object.h"
|
||||
#include "d/d_msg_out_font.h"
|
||||
#include "d/d_msg_scrn_base.h"
|
||||
#include "d/d_pane_class.h"
|
||||
#include "m_Do/m_Do_dvd_thread.h"
|
||||
#include "SSystem/SComponent/c_phase.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
// Main hook for recoloring textures on load
|
||||
DEFINE_HOOK_SYMBOL(
|
||||
"mDoDvdThd_mountArchive_c::execute", s32(mDoDvdThd_mountArchive_c*), MountArchiveExecute);
|
||||
void mount_archive_execute_post(ModContext*, void* args, void* retval, void* userdata) {
|
||||
auto archive = mods::arg<mDoDvdThd_mountArchive_c*>(args, 0);
|
||||
handle_texture_overrides_on_load(archive);
|
||||
}
|
||||
|
||||
// Helper for getting configVar color
|
||||
static std::optional<GXColor> get_config_var_color(ConfigVarHandle handle, bool allowRainbow = false) {
|
||||
auto colorStr = get_str_option(handle, "");
|
||||
|
||||
// Convert to lowercase
|
||||
for (auto& c : colorStr) {
|
||||
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
}
|
||||
|
||||
if (is_valid_hex_color_str(colorStr) || (colorStr == "rainbow" && allowRainbow)) {
|
||||
if (is_valid_hex_color_str(colorStr)) {
|
||||
return hex_color_str_to_gx_color(colorStr);
|
||||
}
|
||||
|
||||
if (allowRainbow) {
|
||||
// Assume rainbow if not a valid hex str
|
||||
auto color = get_rainbow_rgb(127.5f);
|
||||
color.r /= 2;
|
||||
color.g /= 2;
|
||||
color.b /= 2;
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Lantern ambience color
|
||||
DEFINE_HOOK(&dKy_WolfEyeLight_set, WolfEyeLightSet);
|
||||
void wolf_eye_light_set_post(ModContext*, void* args, void* retval, void* userdata) {
|
||||
auto maybeLanternColor = get_config_var_color(get_cvars().lanternGlowColor, true);
|
||||
if (maybeLanternColor.has_value()) {
|
||||
auto lanternColor = maybeLanternColor.value();
|
||||
|
||||
dScnKy_env_light_c* kankyo = dKy_getEnvlight();
|
||||
kankyo->field_0x0c18[0].mColor.r = lanternColor.r;
|
||||
kankyo->field_0x0c18[0].mColor.g = lanternColor.g;
|
||||
kankyo->field_0x0c18[0].mColor.b = lanternColor.b;
|
||||
}
|
||||
}
|
||||
|
||||
// Lantern Sphere color
|
||||
DEFINE_HOOK(&daAlink_c::preKandelaarDraw, PreKandelaarDraw);
|
||||
void pre_kandelaar_draw_post(ModContext*, void* args, void* retval, void* userdata) {
|
||||
auto maybeLanternColor = get_config_var_color(get_cvars().lanternGlowColor, true);
|
||||
if (maybeLanternColor.has_value()) {
|
||||
auto lanternColor = maybeLanternColor.value();
|
||||
|
||||
J3DMaterial* mat_p = daAlink_getAlinkActorClass()->mpKanteraGlowModel->getModelData()->getMaterialNodePointer(0);
|
||||
|
||||
J3DGXColorS10 color;
|
||||
color.r = lanternColor.r;
|
||||
color.g = lanternColor.g;
|
||||
color.b = lanternColor.b;
|
||||
color.a = 255;
|
||||
mat_p->setTevColor(1, &color);
|
||||
|
||||
color.r = lanternColor.r;
|
||||
color.g = lanternColor.g;
|
||||
color.b = lanternColor.b;
|
||||
mat_p->setTevColor(2, &color);
|
||||
}
|
||||
}
|
||||
|
||||
// Main Lantern Meter Color
|
||||
DEFINE_HOOK(&CPaneMgr::setBlackWhite, CPaneMgrSetBlackWhite);
|
||||
HookAction cpane_mgr_set_black_white_pre(ModContext*, void* args, void* retval, void* userdata) {
|
||||
// Check for magic meter
|
||||
auto pane = mods::arg<CPaneMgr*>(args, 0);
|
||||
if (dMeter2Info_getMeterClass() == NULL || pane != dMeter2Info_getMeterClass()->getMeterDrawPtr()->mpMagicMeter) {
|
||||
return HOOK_CONTINUE;
|
||||
}
|
||||
|
||||
// If magic meter, check to see we're setting lantern colors
|
||||
auto& black = mods::arg_ref<JUtility::TColor>(args, 1);
|
||||
auto& white = mods::arg_ref<JUtility::TColor>(args, 2);
|
||||
if (black != JUtility::TColor(255, 255, 140, 255) &&
|
||||
white != JUtility::TColor(230, 170, 0, 255)) {
|
||||
return HOOK_CONTINUE;
|
||||
}
|
||||
|
||||
auto maybeLanternColor = get_config_var_color(get_cvars().lanternGlowColor, true);
|
||||
if (maybeLanternColor.has_value()) {
|
||||
auto lanternColor = maybeLanternColor.value();
|
||||
black = JUtility::TColor(lanternColor.r, lanternColor.g, lanternColor.b, 255);
|
||||
white = JUtility::TColor(lanternColor.r, lanternColor.g, lanternColor.b, 255);
|
||||
}
|
||||
|
||||
return HOOK_CONTINUE;
|
||||
}
|
||||
|
||||
// Lantern Icon Meter Color
|
||||
DEFINE_HOOK(&dKantera_icon_c::setNowGauge, KanteraIconSetNowGauge);
|
||||
void kantera_icon_set_now_gauge_post(ModContext*, void* args, void* retval, void* userdata) {
|
||||
auto maybeLanternColor = get_config_var_color(get_cvars().lanternGlowColor, true);
|
||||
if (maybeLanternColor.has_value()) {
|
||||
auto lanternColor = maybeLanternColor.value();
|
||||
|
||||
auto kanteraIcon = mods::arg<dKantera_icon_c*>(args, 0);
|
||||
kanteraIcon->mpGauge->setBlackWhite(
|
||||
JUtility::TColor(lanternColor.r, lanternColor.g, lanternColor.b, 255),
|
||||
JUtility::TColor(lanternColor.r, lanternColor.g, lanternColor.b, 255));
|
||||
}
|
||||
}
|
||||
|
||||
// Light Sword Effect Color
|
||||
DEFINE_HOOK(&daAlink_c::setLightningSwordEffect, SetLightningSwordEffect);
|
||||
void set_lightning_sword_effect_post(ModContext*, void* args, void* retval, void* userdata) {
|
||||
auto maybeGlowColor = get_config_var_color(get_cvars().lightSwordGlowColor, true);
|
||||
if (maybeGlowColor.has_value()) {
|
||||
auto glowColor = maybeGlowColor.value();
|
||||
|
||||
auto link = mods::arg<daAlink_c*>(args, 0);
|
||||
// Check copied from inside the hooked function
|
||||
if (link->mEquipItem == 0x103 && link->checkNoResetFlg3(daPy_py_c::FLG3_UNK_100000)) {
|
||||
for (size_t i = 0; i < 3; i++) {
|
||||
auto emitter = dComIfGp_particle_getEmitter(link->field_0x327c[i]);
|
||||
if (emitter != NULL) {
|
||||
emitter->setGlobalEnvColor(glowColor.r, glowColor.g, glowColor.b);
|
||||
emitter->setGlobalPrmColor(glowColor.r, glowColor.g, glowColor.b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void recolor_ui_button(ConfigVarHandle option, u64 tag, J2DScreen* screen) {
|
||||
auto buttonColorStr = get_str_option(option, "");
|
||||
if (is_valid_hex_color_str(buttonColorStr)) {
|
||||
auto color = hex_color_str_to_gx_color(buttonColorStr);
|
||||
auto element = static_cast<J2DPicture*>(screen->search(tag));
|
||||
if (element != nullptr) {
|
||||
element->setBlackWhite(JUtility::TColor(0, 0, 0, 0),
|
||||
JUtility::TColor(color.r, color.g, color.b, 0xFF));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Main gameplay UI. Top left hearts and top right buttons
|
||||
DEFINE_HOOK(&dMeter2Draw_c::init, dMeter2Init);
|
||||
void d_meter_2_init_post(ModContext*, void* args, void*, void*) {
|
||||
auto dMeter2Draw = mods::arg<dMeter2Draw_c*>(args, 0);
|
||||
auto screen = dMeter2Draw->getMainScreenPtr();
|
||||
|
||||
// Heart tags on main UI
|
||||
static constexpr std::array kHeartTags = {
|
||||
MULTI_CHAR('hear_00'), MULTI_CHAR('hear_01'), MULTI_CHAR('hear_02'), MULTI_CHAR('hear_03'), MULTI_CHAR('hear_04'), MULTI_CHAR('hear_05'), MULTI_CHAR('hear_06'),
|
||||
MULTI_CHAR('hear_07'), MULTI_CHAR('hear_08'), MULTI_CHAR('hear_09'), MULTI_CHAR('hear_10'), MULTI_CHAR('hear_11'), MULTI_CHAR('hear_12'), MULTI_CHAR('hear_13'),
|
||||
MULTI_CHAR('hear_14'), MULTI_CHAR('hear_15'), MULTI_CHAR('hear_16'), MULTI_CHAR('hear_17'), MULTI_CHAR('hear_18'), MULTI_CHAR('hear_19'), MULTI_CHAR('bigh_00'),
|
||||
MULTI_CHAR('bigh_01'), MULTI_CHAR('bigh_02'), MULTI_CHAR('bigh_03')
|
||||
};
|
||||
|
||||
auto maybeHeartColor = get_config_var_color(get_cvars().heartColor);
|
||||
if (maybeHeartColor.has_value()) {
|
||||
auto heartColor = maybeHeartColor.value();
|
||||
for (auto tag : kHeartTags) {
|
||||
auto element = static_cast<J2DPicture*>(screen->search(tag));
|
||||
if (element != nullptr) {
|
||||
element->setBlackWhite(heartColor, JUtility::TColor(200, 200, 200, 255));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen);
|
||||
recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen);
|
||||
recolor_ui_button(get_cvars().xButtonColor, MULTI_CHAR('x_btn'), screen);
|
||||
recolor_ui_button(get_cvars().yButtonColor, MULTI_CHAR('y_btn'), screen);
|
||||
recolor_ui_button(get_cvars().zButtonColor, MULTI_CHAR('zbtn'), screen);
|
||||
}
|
||||
|
||||
// Hearts on the file select screen
|
||||
DEFINE_HOOK(&dFile_info_c::setHeartCnt, FileInfoSetHeartCount);
|
||||
void file_info_set_heart_count_post(ModContext*, void* args, void*, void*) {
|
||||
auto fileInfo = mods::arg<dFile_info_c*>(args, 0);
|
||||
|
||||
// Heart tags on file select
|
||||
static constexpr std::array kFileSelectHeartTags {
|
||||
MULTI_CHAR('hear_20'), MULTI_CHAR('hear_21'), MULTI_CHAR('hear_22'), MULTI_CHAR('hear_23'), MULTI_CHAR('hear_24'), MULTI_CHAR('hear_25'), MULTI_CHAR('hear_26'),
|
||||
MULTI_CHAR('hear_27'), MULTI_CHAR('hear_28'), MULTI_CHAR('hear_29'), MULTI_CHAR('hear_30'), MULTI_CHAR('hear_31'), MULTI_CHAR('hear_32'), MULTI_CHAR('hear_33'),
|
||||
MULTI_CHAR('hear_34'), MULTI_CHAR('hear_35'), MULTI_CHAR('hear_36'), MULTI_CHAR('hear_37'), MULTI_CHAR('hear_38'), MULTI_CHAR('hear_39'),
|
||||
};
|
||||
|
||||
auto maybeHeartColor = get_config_var_color(get_cvars().heartColor);
|
||||
if (maybeHeartColor.has_value()) {
|
||||
auto heartColor = maybeHeartColor.value();
|
||||
for (auto tag : kFileSelectHeartTags) {
|
||||
auto element = static_cast<J2DPicture*>(fileInfo->mFileInfo.Scr->search(tag));
|
||||
if (element != nullptr) {
|
||||
element->setBlackWhite(heartColor, JUtility::TColor(200, 200, 200, 255));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Buttons that appear contextually at the bottom of the screen during gameplay
|
||||
// (Are X, Y ever used there?)
|
||||
DEFINE_HOOK(&dMeterButton_c::screenInitButton, ScreenInitButton);
|
||||
void screen_init_button_post(ModContext*, void* args, void*, void*) {
|
||||
auto meterButton = mods::arg<dMeterButton_c*>(args, 0);
|
||||
auto screen = meterButton->mpButtonScreen;
|
||||
|
||||
recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn1'), screen);
|
||||
recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen);
|
||||
recolor_ui_button(get_cvars().xButtonColor, MULTI_CHAR('x_btn'), screen);
|
||||
recolor_ui_button(get_cvars().yButtonColor, MULTI_CHAR('y_btn'), screen);
|
||||
recolor_ui_button(get_cvars().zButtonColor, MULTI_CHAR('zbtn'), screen);
|
||||
}
|
||||
|
||||
// A and B buttons when saving a file
|
||||
DEFINE_HOOK(&dMenu_save_c::screenSet, MenuSaveScreenSet);
|
||||
void menu_save_screen_set_post(ModContext*, void* args, void*, void*) {
|
||||
auto menuSave = mods::arg<dMenu_save_c*>(args, 0);
|
||||
auto screen = menuSave->mSaveSel.Scr;
|
||||
|
||||
recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('wabtn'), screen);
|
||||
recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('wbbtn'), screen);
|
||||
}
|
||||
|
||||
// A and B buttons when selecting a file
|
||||
DEFINE_HOOK(&dFile_select_c::screenSet, FileSelectScreenSet);
|
||||
void file_select_screen_set_post(ModContext*, void* args, void*, void*) {
|
||||
auto fileSelect = mods::arg<dFile_select_c*>(args, 0);
|
||||
auto screen = fileSelect->fileSel.Scr;
|
||||
|
||||
recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('wabtn'), screen);
|
||||
recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('wbbtn'), screen);
|
||||
}
|
||||
|
||||
// A button on brightness check screen
|
||||
DEFINE_HOOK(&dBrightCheck_c::screenSet, BrightCheckScreenSet);
|
||||
void bright_check_screen_set_post(ModContext*, void* args, void*, void*) {
|
||||
auto brightCheck = mods::arg<dBrightCheck_c*>(args, 0);
|
||||
auto screen = brightCheck->mBrightCheck.Scr;
|
||||
|
||||
recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn1'), screen);
|
||||
}
|
||||
|
||||
// X and Y buttons on the item wheel screen
|
||||
DEFINE_HOOK(&dMenu_Ring_c::_create, MenuRingCreate);
|
||||
void menu_ring_create_post(ModContext*, void* args, void*, void*) {
|
||||
auto menuRing = mods::arg<dMenu_Ring_c*>(args, 0);
|
||||
auto screen = menuRing->mpScreen;
|
||||
|
||||
recolor_ui_button(get_cvars().xButtonColor, MULTI_CHAR('xbtn'), screen);
|
||||
recolor_ui_button(get_cvars().yButtonColor, MULTI_CHAR('ybtn'), screen);
|
||||
}
|
||||
|
||||
// A and B buttons on the main pause screen
|
||||
DEFINE_HOOK(&dMenu_Collect2D_c::_create, MenuCollect2DCreate);
|
||||
void menu_collect_2D_create_post(ModContext*, void* args, void*, void*) {
|
||||
auto menuCollect2D = mods::arg<dMenu_Collect2D_c*>(args, 0);
|
||||
auto screen = menuCollect2D->mpScreenIcon;
|
||||
|
||||
recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen);
|
||||
recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen);
|
||||
}
|
||||
|
||||
// A and B buttons on the fishing journal screen
|
||||
DEFINE_HOOK(&dMenu_Fishing_c::screenSetDoIcon, MenuFishingScreenSetDoIcon);
|
||||
void menu_fishing_screen_set_do_icon_post(ModContext*, void* args, void*, void*) {
|
||||
auto menuFishing = mods::arg<dMenu_Fishing_c*>(args, 0);
|
||||
auto screen = menuFishing->mpIconScreen;
|
||||
|
||||
recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen);
|
||||
recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen);
|
||||
}
|
||||
|
||||
// A and B buttons on the insect collection screen
|
||||
DEFINE_HOOK(&dMenu_Insect_c::screenSetDoIcon, MenuInsectScreenSetDoIcon);
|
||||
void menu_insect_screen_set_do_icon_post(ModContext*, void* args, void*, void*) {
|
||||
auto menuInsect = mods::arg<dMenu_Insect_c*>(args, 0);
|
||||
auto screen = menuInsect->mpIconScreen;
|
||||
|
||||
recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen);
|
||||
recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen);
|
||||
}
|
||||
|
||||
// A and B buttons on the letters screen
|
||||
DEFINE_HOOK(&dMenu_Letter_c::screenSetDoIcon, MenuLetterScreenSetDoIcon);
|
||||
void menu_letter_screen_set_do_icon_post(ModContext*, void* args, void*, void*) {
|
||||
auto menuLetter = mods::arg<dMenu_Letter_c*>(args, 0);
|
||||
auto screen = menuLetter->mpIconScreen;
|
||||
|
||||
recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen);
|
||||
recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen);
|
||||
}
|
||||
|
||||
// A, B, and Z buttons on option screen
|
||||
DEFINE_HOOK(&dMenu_Option_c::_create, MenuOptionCreate);
|
||||
void menu_option_create_post(ModContext*, void* args, void*, void*) {
|
||||
auto menuOption = mods::arg<dMenu_Option_c*>(args, 0);
|
||||
auto screen = menuOption->mpScreenIcon;
|
||||
auto backScreen = menuOption->mpBackScreen;
|
||||
auto tvScreen = menuOption->mpTVScreen;
|
||||
|
||||
recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen);
|
||||
recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen);
|
||||
recolor_ui_button(get_cvars().zButtonColor, MULTI_CHAR('g_zbtn'), backScreen);
|
||||
|
||||
// A Button on option's brightness check screen
|
||||
recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn1'), tvScreen);
|
||||
}
|
||||
|
||||
// A and B buttons on the hidden skills screen
|
||||
DEFINE_HOOK(&dMenu_Skill_c::screenSetDoIcon, MenuSkillScreenSetDoIcon);
|
||||
void menu_skill_screen_set_do_icon_post(ModContext*, void* args, void*, void*) {
|
||||
auto menuSkill = mods::arg<dMenu_Skill_c*>(args, 0);
|
||||
auto screen = menuSkill->mpIconScreen;
|
||||
|
||||
recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen);
|
||||
recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen);
|
||||
}
|
||||
|
||||
// A, B, and Z buttons on the map screen
|
||||
DEFINE_HOOK(&dMenu_Fmap_c::_create, MenuFMapCreate);
|
||||
void menu_fmap_create_post(ModContext*, void* args, void*, void*) {
|
||||
auto menuFMap = mods::arg<dMenu_Fmap_c*>(args, 0);
|
||||
auto screen = menuFMap->mpDraw2DTop->mpTitleScreen;
|
||||
|
||||
recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen);
|
||||
recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn1'), screen);
|
||||
recolor_ui_button(get_cvars().zButtonColor, MULTI_CHAR('zbtn'), screen);
|
||||
}
|
||||
|
||||
// A button on the howling screen
|
||||
DEFINE_HOOK(&dMsgObject_c::talkStartInit, MsgObjectTalkStartInit);
|
||||
void msg_object_talk_start_init_post(ModContext*, void* args, void*, void*) {
|
||||
auto msgObject = mods::arg<dMsgObject_c*>(args, 0);
|
||||
|
||||
// If textbox kind is howling
|
||||
if (msgObject->mFukiKind == 17) {
|
||||
auto screen = msgObject->mpScrnDraw->mpScreen;
|
||||
|
||||
recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('abtn'), screen);
|
||||
}
|
||||
}
|
||||
|
||||
// A button when on Epona
|
||||
DEFINE_HOOK(&dMeterHakusha_c::_create, MeterHakushaCreate);
|
||||
void meter_hakusha_create_post(ModContext*, void* args, void*, void*) {
|
||||
auto meterHakusha = mods::arg<dMeterHakusha_c*>(args, 0);
|
||||
auto screen = meterHakusha->mpButtonScreen;
|
||||
|
||||
recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn1'), screen);
|
||||
}
|
||||
|
||||
// A, B, X, and Y button icons in text
|
||||
DEFINE_HOOK(&COutFont_c::createPane, OutFontCreatePane);
|
||||
void out_font_create_pane_post(ModContext*, void* args, void*, void*) {
|
||||
auto outFont = mods::arg<COutFont_c*>(args, 0);
|
||||
auto paneArr = outFont->mpPane;
|
||||
|
||||
auto maybeAButtonColor = get_config_var_color(get_cvars().aButtonColor);
|
||||
if (maybeAButtonColor.has_value()) {
|
||||
auto aButtonColor = maybeAButtonColor.value();
|
||||
paneArr[0]->setBlackWhite(
|
||||
JUtility::TColor(255, 255, 255, 0),
|
||||
JUtility::TColor(aButtonColor.r,aButtonColor.g,aButtonColor.b, 255));
|
||||
}
|
||||
|
||||
auto maybeBButtonColor = get_config_var_color(get_cvars().bButtonColor);
|
||||
if (maybeBButtonColor.has_value()) {
|
||||
auto bButtonColor = maybeBButtonColor.value();
|
||||
paneArr[1]->setBlackWhite(
|
||||
JUtility::TColor(255, 255, 255, 0),
|
||||
JUtility::TColor(bButtonColor.r,bButtonColor.g,bButtonColor.b, 255));
|
||||
}
|
||||
|
||||
// Condition copied from hooked function
|
||||
auto xyBlack = JUtility::TColor(255, 255, 255, 0);
|
||||
if (outFont->field_0x242 == 1) {
|
||||
xyBlack = JUtility::TColor(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
auto maybeXButtonColor = get_config_var_color(get_cvars().xButtonColor);
|
||||
if (maybeXButtonColor.has_value()) {
|
||||
auto xButtonColor = maybeXButtonColor.value();
|
||||
paneArr[5]->setBlackWhite(xyBlack, JUtility::TColor(xButtonColor.r,xButtonColor.g,xButtonColor.b, 255));
|
||||
}
|
||||
|
||||
auto maybeYButtonColor = get_config_var_color(get_cvars().yButtonColor);
|
||||
if (maybeYButtonColor.has_value()) {
|
||||
auto yButtonColor = maybeYButtonColor.value();
|
||||
paneArr[6]->setBlackWhite(xyBlack, JUtility::TColor(yButtonColor.r,yButtonColor.g,yButtonColor.b, 255));
|
||||
}
|
||||
}
|
||||
|
||||
// Heart icon in text
|
||||
DEFINE_HOOK(&COutFontSet_c::drawFont, OutFontSetDrawFont);
|
||||
void out_font_set_draw_font_post(ModContext*, void* args, void*, void*) {
|
||||
auto outFontSet = mods::arg<COutFontSet_c*>(args, 0);
|
||||
|
||||
auto maybeHeartColor = get_config_var_color(get_cvars().heartColor);
|
||||
if (maybeHeartColor.has_value()) {
|
||||
auto heartColor = maybeHeartColor.value();
|
||||
u32 heartColor_u32 = heartColor.r << 24 | heartColor.g << 16 | heartColor.b << 8 | 0xFF;
|
||||
if (outFontSet->getType() == 0x1B) { // Heart icon type
|
||||
outFontSet->mColor = heartColor_u32;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Heart Drop/Piece of Heart/Heart Container model color
|
||||
DEFINE_HOOK(&daItemBase_c::CreateItemHeap, ItemBaseCreateItemHeap);
|
||||
void item_base_create_item_heap_post(ModContext*, void* args, void*, void*) {
|
||||
auto itemBase = mods::arg<daItemBase_c*>(args, 0);
|
||||
if (itemBase == NULL) {
|
||||
return;
|
||||
}
|
||||
auto itemNo = itemBase->m_itemNo;
|
||||
if (itemNo == dItemNo_HEART_e || itemNo == dItemNo_UTAWA_HEART_e || itemNo == dItemNo_KAKERA_HEART_e) {
|
||||
auto maybeHeartColor = get_config_var_color(get_cvars().heartColor);
|
||||
if (maybeHeartColor.has_value()) {
|
||||
auto heartColor = maybeHeartColor.value();
|
||||
auto heartColorS10 = GXColorS10(heartColor.r, heartColor.g, heartColor.b, heartColor.a);
|
||||
|
||||
// Edit inner heart material color for Piece of Heart / Heart Container
|
||||
if (itemNo == dItemNo_UTAWA_HEART_e || itemNo == dItemNo_KAKERA_HEART_e) {
|
||||
*itemBase->mpModel->getModelData()->getMaterialNodePointer(3)->getTevKColor(1) = heartColor;
|
||||
*itemBase->mpModel->getModelData()->getMaterialNodePointer(3)->getTevColor(1) = heartColorS10;
|
||||
}
|
||||
|
||||
const u8 heartColorRGB[3] = {heartColor.r,heartColor.g,heartColor.b};
|
||||
u8** cRegTable = reinterpret_cast<u8**>(&itemBase->mpBrkAnm->getBrkAnm()->mAnmCRegDataR);
|
||||
u8** kRegTable = reinterpret_cast<u8**>(&itemBase->mpBrkAnm->getBrkAnm()->mAnmKRegDataR);
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
u8* cReg = cRegTable[i];
|
||||
u8* kReg = kRegTable[i];
|
||||
|
||||
auto curColor = heartColorRGB[i];
|
||||
|
||||
// Set heart drop inner heart color
|
||||
cReg[0x3] = curColor;
|
||||
cReg[0xB] = curColor;
|
||||
|
||||
// Set heart drop outer heart color
|
||||
kReg[0x3] = curColor;
|
||||
kReg[0xB] = curColor;
|
||||
|
||||
if (itemNo == dItemNo_KAKERA_HEART_e) {
|
||||
cReg[0x13] = curColor;
|
||||
cReg[0x1B] = curColor;
|
||||
kReg[0x13] = curColor;
|
||||
kReg[0x1B] = curColor;
|
||||
|
||||
}
|
||||
if (itemNo == dItemNo_UTAWA_HEART_e) {
|
||||
cReg[0x13] = curColor;
|
||||
kReg[0x13] = curColor;
|
||||
kReg[0x1B] = curColor;
|
||||
kReg[0x23] = curColor;
|
||||
kReg[0x2B] = curColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Midna Hair Color
|
||||
DEFINE_HOOK(&daMidna_c::create, MidnaCreate);
|
||||
void midna_create_post(ModContext*, void* args, void* retval, void*) {
|
||||
auto step = reinterpret_cast<int*>(retval);
|
||||
// Don't set colors if midna isn't done loading
|
||||
if (*step != cPhs_COMPLEATE_e) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto midna = mods::arg<daMidna_c*>(args, 0);
|
||||
midna->field_0x6e0 = *get_midna_hair_normalColor();
|
||||
if (dKy_darkworld_check()) {
|
||||
midna->field_0x6e8 = *get_midna_hair_normalKColor();
|
||||
midna->field_0x6ec = *get_midna_hair_normalKColor2();
|
||||
} else {
|
||||
midna->field_0x6e8 = *get_midna_hair_lNormalKColor();
|
||||
midna->field_0x6ec = *get_midna_hair_lNormalKColor2();
|
||||
}
|
||||
}
|
||||
|
||||
// Override Midna Hair Color Part 2
|
||||
DEFINE_HOOK(&daMidna_c::setBodyPartMatrix, MidnaSetBodyPartMatrix);
|
||||
|
||||
static GXColorS10 midnaField0x6e0{};
|
||||
static GXColor midnaField0x6e8{};
|
||||
static GXColor midnaField0x6ec{};
|
||||
|
||||
// Copy the original values before setBodyPartMatrix runs
|
||||
HookAction midna_set_body_part_matrix_pre(ModContext*, void* args, void* retval, void* userdata) {
|
||||
auto midna = mods::arg<daMidna_c*>(args, 0);
|
||||
|
||||
midnaField0x6e0 = midna->field_0x6e0;
|
||||
midnaField0x6e8 = midna->field_0x6e8;
|
||||
midnaField0x6ec = midna->field_0x6ec;
|
||||
|
||||
return HOOK_CONTINUE;
|
||||
}
|
||||
|
||||
void midna_set_body_part_matrix_post(ModContext*, void* args, void* retval, void* userdata) {
|
||||
auto midna = mods::arg<daMidna_c*>(args, 0);
|
||||
if (midna->mpHairhandBmd != NULL) {
|
||||
// Restore the original values from before setBodyPartMatrix ran (this undoes the chase)
|
||||
midna->field_0x6e0 = midnaField0x6e0;
|
||||
midna->field_0x6e8 = midnaField0x6e8;
|
||||
midna->field_0x6ec = midnaField0x6ec;
|
||||
|
||||
// Statement copied from inside function to determine colors
|
||||
bool bigColors = midna->checkStateFlg0(daMidna_c::FLG0_UNK_10000000) ||
|
||||
midna->mBckHeap[2].getIdx() == daMidna_c::m_anmDataTable[daMidna_c::ANM_HAIR].mResID ||
|
||||
midna->mBckHeap[2].getIdx() == daMidna_c::m_anmDataTable[daMidna_c::ANM_S_TAKES].mResID ||
|
||||
midna->mBckHeap[2].getIdx() == daMidna_c::m_anmDataTable[daMidna_c::ANM_S_WAITS].mResID ||
|
||||
midna->mBckHeap[2].getIdx() == daMidna_c::m_anmDataTable[daMidna_c::ANM_S_PACKAWAY].mResID ||
|
||||
midna->mBckHeap[2].getIdx() == daMidna_c::m_anmDataTable[daMidna_c::ANM_GRABST].mResID ||
|
||||
midna->checkEndResetStateFlg0(daMidna_c::ERFLG0_UNK_40) ||
|
||||
dComIfGp_checkPlayerStatus1(0, 0x800000);
|
||||
|
||||
GXColorS10 color{};
|
||||
GXColor kcolor1{};
|
||||
GXColor kcolor2{};
|
||||
|
||||
// Set our own colors
|
||||
if (bigColors) {
|
||||
kcolor1 = *get_midna_hair_bigKColor();
|
||||
if (dKy_darkworld_check()) {
|
||||
color = *get_midna_hair_bigColor();
|
||||
kcolor2 = *get_midna_hair_normalKColor2();
|
||||
} else {
|
||||
color = *get_midna_hair_lBigColor();
|
||||
kcolor2 = *get_midna_hair_lBigKColor2();
|
||||
}
|
||||
} else {
|
||||
color = *get_midna_hair_normalColor();
|
||||
if (dKy_darkworld_check()) {
|
||||
kcolor1 = *get_midna_hair_normalKColor();
|
||||
kcolor2 = *get_midna_hair_normalKColor2();
|
||||
} else {
|
||||
kcolor1 = *get_midna_hair_lNormalKColor();
|
||||
kcolor2 = *get_midna_hair_lNormalKColor2();
|
||||
}
|
||||
}
|
||||
|
||||
// Reapply the chase that happens in the function
|
||||
cLib_chaseS(&midna->field_0x6e0.r, color.r, 10);
|
||||
cLib_chaseS(&midna->field_0x6e0.g, color.g, 10);
|
||||
cLib_chaseS(&midna->field_0x6e0.b, color.b, 10);
|
||||
cLib_chaseUC(&midna->field_0x6e8.r, kcolor1.r, 10);
|
||||
cLib_chaseUC(&midna->field_0x6e8.g, kcolor1.g, 10);
|
||||
cLib_chaseUC(&midna->field_0x6e8.b, kcolor1.b, 10);
|
||||
cLib_chaseUC(&midna->field_0x6ec.r, kcolor2.r, 10);
|
||||
cLib_chaseUC(&midna->field_0x6ec.g, kcolor2.g, 10);
|
||||
cLib_chaseUC(&midna->field_0x6ec.b, kcolor2.b, 10);
|
||||
}
|
||||
}
|
||||
|
||||
// Midna Charge Ring Color
|
||||
DEFINE_HOOK(&daAlink_c::setWolfLockDomeModel, SetWolfLockDomeModel);
|
||||
void wolf_lock_dome_model_post(ModContext*, void* args, void* retval, void* userdata) {
|
||||
auto domeRingColorStr = get_str_option(get_cvars().midnaChargeRingColor, "");
|
||||
if (is_valid_hex_color_str(domeRingColorStr)) {
|
||||
auto domeRingColor = hex_color_str_to_gx_color(domeRingColorStr);
|
||||
const u8 domeWave1RGBA[3] = {domeRingColor.r, domeRingColor.g, domeRingColor.b};
|
||||
const u8 domeWave2RGBA[3] = {domeRingColor.r, domeRingColor.g, domeRingColor.b};
|
||||
u8** chromaRegisterTable = reinterpret_cast<u8**>(&daAlink_getAlinkActorClass()->field_0x0724->mAnmCRegDataR);
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
u8* currentTable = chromaRegisterTable[i];
|
||||
const u8 currentWave1Color = domeWave1RGBA[i];
|
||||
const u8 currentWave2Color = domeWave2RGBA[i];
|
||||
const u8 currentBaseColor = (currentWave1Color + currentWave2Color) / 2;
|
||||
|
||||
currentTable[0x3] = currentBaseColor; // Set Alpha for the ring base
|
||||
currentTable[0x13] = currentWave1Color; // Set Alpha for ring wave 1
|
||||
currentTable[0x23] = currentWave2Color; // Set Alpha for ring wave 2
|
||||
currentTable[0xB] = currentBaseColor; // Set Alpha for darkworld ring base
|
||||
currentTable[0x1B] = currentWave1Color; // Set Alpha for darkworld ring wave 1
|
||||
currentTable[0x2B] = currentWave2Color; // Set Alpha for darkworld ring wave 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define ADD_POST_HOOK(defined_hook, function, original) \
|
||||
result = mods::hook::add_post<defined_hook>(function); \
|
||||
if (result != MOD_OK) { \
|
||||
mods::log::debug("failed to add post hook to" #original ", Result {}", static_cast<int>(result)); \
|
||||
return result; \
|
||||
}
|
||||
|
||||
#define ADD_PRE_HOOK(defined_hook, function, original) \
|
||||
result = mods::hook::add_pre<defined_hook>(function); \
|
||||
if (result != MOD_OK) { \
|
||||
mods::log::debug("failed to add pre hook to" #original ", Result {}", static_cast<int>(result)); \
|
||||
return result; \
|
||||
}
|
||||
|
||||
ModResult add_all_hooks() {
|
||||
ModResult result{};
|
||||
|
||||
// Main hook for texture recoloring
|
||||
ADD_POST_HOOK(MountArchiveExecute, mount_archive_execute_post, mountArchive_execute)
|
||||
|
||||
// Hooks for lantern glow
|
||||
ADD_POST_HOOK(WolfEyeLightSet, wolf_eye_light_set_post, dKy_WolfEyeLight_set)
|
||||
ADD_POST_HOOK(PreKandelaarDraw, pre_kandelaar_draw_post, daAlink_c::preKandelaarDraw)
|
||||
|
||||
// Hooks for lantern meter color
|
||||
ADD_PRE_HOOK(CPaneMgrSetBlackWhite, cpane_mgr_set_black_white_pre, CPaneMgr::setBlackWhite)
|
||||
ADD_POST_HOOK(KanteraIconSetNowGauge, kantera_icon_set_now_gauge_post, dKantera_icon_c::setNowGauge)
|
||||
|
||||
// Hook for midna charge ring
|
||||
ADD_POST_HOOK(SetWolfLockDomeModel, wolf_lock_dome_model_post, daAlink_c::setWolfLockDomeModel)
|
||||
|
||||
// Hook for light sword glow
|
||||
ADD_POST_HOOK(SetLightningSwordEffect, set_lightning_sword_effect_post, daAlink_c::setLightningSwordEffect)
|
||||
|
||||
// Hooks for Midna Hair Color
|
||||
ADD_POST_HOOK(MidnaCreate, midna_create_post, daMidna_c::create)
|
||||
ADD_PRE_HOOK(MidnaSetBodyPartMatrix, midna_set_body_part_matrix_pre, daMidna_c::setBodyPartMatrix)
|
||||
ADD_POST_HOOK(MidnaSetBodyPartMatrix, midna_set_body_part_matrix_post, daMidna_c::setBodyPartMatrix)
|
||||
|
||||
// Hooks for UI colors
|
||||
ADD_POST_HOOK(dMeter2Init, d_meter_2_init_post, dMeter2Draw_c::init)
|
||||
ADD_POST_HOOK(FileInfoSetHeartCount, file_info_set_heart_count_post, dFile_info_c::setHeartCnt)
|
||||
ADD_POST_HOOK(ScreenInitButton, screen_init_button_post, dMeterButton_c::screenInitButton)
|
||||
ADD_POST_HOOK(MenuSaveScreenSet, menu_save_screen_set_post, dMenu_save_c::screenSet)
|
||||
ADD_POST_HOOK(FileSelectScreenSet, file_select_screen_set_post, dFile_select_c::screenSet)
|
||||
ADD_POST_HOOK(BrightCheckScreenSet, bright_check_screen_set_post, dBrightCheck_c::screenSet)
|
||||
ADD_POST_HOOK(MenuRingCreate, menu_ring_create_post, dMenu_Ring_c::_create)
|
||||
ADD_POST_HOOK(MenuCollect2DCreate, menu_collect_2D_create_post, dMenu_Collect2D_c::_create)
|
||||
ADD_POST_HOOK(MenuFishingScreenSetDoIcon, menu_fishing_screen_set_do_icon_post, dMenu_Fishing_c::screenSetDoIcon)
|
||||
ADD_POST_HOOK(MenuInsectScreenSetDoIcon, menu_insect_screen_set_do_icon_post, dMenu_Insect_c::screenSetDoIcon)
|
||||
ADD_POST_HOOK(MenuLetterScreenSetDoIcon, menu_letter_screen_set_do_icon_post, dMenu_Letter_c::screenSetDoIcon)
|
||||
ADD_POST_HOOK(MenuOptionCreate, menu_option_create_post, dMenu_Option_c::_create)
|
||||
ADD_POST_HOOK(MenuSkillScreenSetDoIcon, menu_skill_screen_set_do_icon_post, dMenu_Skill_c::screenSetDoIcon)
|
||||
ADD_POST_HOOK(OutFontCreatePane, out_font_create_pane_post, COutFont_c::createPane)
|
||||
ADD_POST_HOOK(OutFontSetDrawFont, out_font_set_draw_font_post, COutFontSet_c::drawFont)
|
||||
ADD_POST_HOOK(MenuFMapCreate, menu_fmap_create_post, dMenu_Fmap_c::_create)
|
||||
ADD_POST_HOOK(MsgObjectTalkStartInit, msg_object_talk_start_init_post, dMsgObject_c::talkStartInit)
|
||||
ADD_POST_HOOK(MeterHakushaCreate, meter_hakusha_create_post, dMeterHakusha_c::_create)
|
||||
|
||||
// Heart Model Color
|
||||
ADD_POST_HOOK(ItemBaseCreateItemHeap, item_base_create_item_heap_post, daItemBase_c::CreateItemHeap)
|
||||
|
||||
return MOD_OK;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "mods/service.hpp"
|
||||
|
||||
ModResult add_all_hooks();
|
||||
@@ -1,141 +0,0 @@
|
||||
#include "midna_hair_color.hpp"
|
||||
#include "mod.hpp"
|
||||
|
||||
#include "mods/svc/log.hpp"
|
||||
|
||||
#include <array>
|
||||
|
||||
static constexpr std::array<std::array<GXColor, 9>, 10> midnaHairColors = {{
|
||||
// Default
|
||||
{{
|
||||
/*l_lNormalKColor*/ /*l_normalKColor*/ /*l_normalColor*/
|
||||
{0xFF, 0xDC, 0x00}, {0xB4, 0x87, 0x00}, {0x50, 0x00, 0x00},
|
||||
/*l_bigKColor*/ /*l_lBigColor*/ /*l_bigColor*/
|
||||
{0x50, 0x00, 0x00}, {0xFF, 0x78, 0x00}, {0xFF, 0x64, 0x78},
|
||||
/*l_lNormalKColor2*//*l_normalKColor2*/ /*l_lBigKColor2*/
|
||||
{0x00, 0xC3, 0xEB}, {0xC3, 0xC3, 0x00}, {0xAA, 0xFF, 0xC3}
|
||||
}},
|
||||
// Pink
|
||||
{{
|
||||
{0xF5, 0xCF, 0xF3}, {0xAD, 0x7F, 0x7F}, {0x1B, 0x00, 0x20},
|
||||
{0x3C, 0x02, 0x58}, {0xE3, 0x72, 0xF2}, {0xE3, 0x5F, 0xF8},
|
||||
{0xDD, 0x00, 0xEB}, {0xDD, 0x00, 0xC3}, {0xF6, 0x4C, 0xFF}
|
||||
}},
|
||||
// Red
|
||||
{{
|
||||
{0xE4, 0x65, 0x41}, {0xA1, 0x3E, 0x22}, {0x21, 0x00, 0x00},
|
||||
{0x4F, 0x02, 0x01}, {0xF0, 0x3A, 0x25}, {0xF0, 0x30, 0x8C},
|
||||
{0xEB, 0x00, 0x00}, {0xEB, 0x00, 0x00}, {0xFF, 0x4F, 0x3A}
|
||||
}},
|
||||
// Yellow
|
||||
{{
|
||||
{0x91, 0x83, 0x0E}, {0x66, 0x50, 0x07}, {0x0E, 0x0B, 0x00},
|
||||
{0x24, 0x25, 0x02}, {0xCB, 0xB7, 0x00}, {0xCB, 0x99, 0x78},
|
||||
{0xEB, 0xDE, 0x00}, {0xEB, 0xDE, 0x00}, {0xFF, 0xF8, 0xBF}
|
||||
}},
|
||||
// Green
|
||||
{{
|
||||
{0x35, 0x79, 0x53}, {0x25, 0x4A, 0x2B}, {0x00, 0x0E, 0x05},
|
||||
{0x0A, 0x29, 0x10}, {0x00, 0xB6, 0x6F}, {0x00, 0x98, 0xB3},
|
||||
{0x1F, 0xEB, 0x00}, {0x1F, 0xEB, 0x00}, {0x9A, 0xFF, 0x81}
|
||||
}},
|
||||
// Blue
|
||||
{{
|
||||
{0x00, 0x72, 0xFF}, {0x00, 0x46, 0x85}, {0x00, 0x08, 0x28},
|
||||
{0x16, 0x1B, 0x5D}, {0x00, 0x60, 0xFF}, {0x00, 0x50, 0xFF},
|
||||
{0x00, 0x48, 0xEB}, {0x00, 0x48, 0xC3}, {0x3A, 0x66, 0xFF}
|
||||
}},
|
||||
// Purple
|
||||
{{
|
||||
{0x6F, 0x34, 0xFF}, {0x4E, 0x20, 0x85}, {0x0D, 0x00, 0x34},
|
||||
{0x15, 0x08, 0x79}, {0x62, 0x00, 0xFF}, {0x62, 0x00, 0xFF},
|
||||
{0x7B, 0x00, 0xEB}, {0x7B, 0x00, 0xC3}, {0x94, 0x3E, 0xFF}
|
||||
}},
|
||||
// Brown
|
||||
{{
|
||||
{0x00, 0x00, 0x00}, {0x00, 0x00, 0x00}, {0x1A, 0x05, 0x00},
|
||||
{0x3C, 0x19, 0x0E}, {0x3F, 0x1D, 0x0B}, {0x3F, 0x18, 0x7E},
|
||||
{0x3F, 0x1D, 0x0B}, {0x3F, 0x1D, 0x09}, {0x59, 0x32, 0x1E}
|
||||
}},
|
||||
// White
|
||||
{{
|
||||
{0xF0, 0xF1, 0xF1}, {0xA9, 0x94, 0x7E}, {0x09, 0x0B, 0x0C},
|
||||
{0x22, 0x24, 0x24}, {0xFF, 0xFF, 0xFF}, {0xFF, 0xD5, 0xFF},
|
||||
{0xEA, 0xEA, 0xEA}, {0xEA, 0xEA, 0xC2}, {0xF3, 0xF3, 0xF3}
|
||||
}},
|
||||
// Black
|
||||
{{
|
||||
{0x00, 0x00, 0x00}, {0x00, 0x00, 0x00}, {0x0B, 0x0B, 0x0B},
|
||||
{0x23, 0x23, 0x23}, {0x00, 0x00, 0x00}, {0x00, 0x00, 0x78},
|
||||
{0x00, 0x00, 0x00}, {0x00, 0x00, 0x00}, {0x00, 0x00, 0x00}
|
||||
}}
|
||||
}};
|
||||
|
||||
static struct {
|
||||
GXColorS10 l_normalColor = { 0x50, 0x00, 0x00, 0x00 };
|
||||
GXColor l_normalKColor = { 0xB4, 0x87, 0x00, 0x00 };
|
||||
GXColor l_normalKColor2 = { 0x00, 0xC3, 0xC3, 0x00 };
|
||||
GXColorS10 l_bigColor = { 0xFF, 0x64, 0x78, 0x00 };
|
||||
GXColor l_bigKColor = { 0x1E, 0x00, 0x00, 0x00 };
|
||||
GXColor l_lNormalKColor = { 0xFF, 0xDC, 0x00, 0x00 };
|
||||
GXColor l_lNormalKColor2 = { 0x00, 0xC3, 0xEB, 0x00 };
|
||||
GXColorS10 l_lBigColor = { 0xFF, 0x78, 0x00, 0x00 };
|
||||
GXColor l_lBigKColor2 = { 0xAA, 0xFF, 0xC3, 0x00 };
|
||||
} currentMidnaHairColors;
|
||||
|
||||
void set_all_midna_hair_colors() {
|
||||
auto& g_cvars = get_cvars();
|
||||
auto hairBaseColor = get_int_option(g_cvars.midnaHairBaseColor, 0);
|
||||
auto hairTipsColor = get_int_option(g_cvars.midnaHairTipsColor, 0);
|
||||
|
||||
// Colors we have to convert to GXColorS10
|
||||
auto& normalColor = midnaHairColors.at(hairBaseColor)[2];
|
||||
auto& bigColor = midnaHairColors.at(hairBaseColor)[5];
|
||||
auto& lBigColor = midnaHairColors.at(hairBaseColor)[4];
|
||||
|
||||
currentMidnaHairColors.l_normalColor = GXColorS10{normalColor.r, normalColor.g, normalColor.b};
|
||||
currentMidnaHairColors.l_normalKColor = midnaHairColors.at(hairBaseColor)[1];
|
||||
currentMidnaHairColors.l_normalKColor2 = midnaHairColors.at(hairTipsColor)[7];
|
||||
currentMidnaHairColors.l_bigColor = GXColorS10{bigColor.r, bigColor.g, bigColor.b};
|
||||
currentMidnaHairColors.l_bigKColor = midnaHairColors.at(hairBaseColor)[3];
|
||||
currentMidnaHairColors.l_lNormalKColor = midnaHairColors.at(hairBaseColor)[0];
|
||||
currentMidnaHairColors.l_lNormalKColor2 = midnaHairColors.at(hairTipsColor)[6];
|
||||
currentMidnaHairColors.l_lBigColor = GXColorS10{lBigColor.r, lBigColor.g, lBigColor.b};
|
||||
currentMidnaHairColors.l_lBigKColor2 = midnaHairColors.at(hairTipsColor)[8];
|
||||
}
|
||||
|
||||
const GXColorS10* get_midna_hair_normalColor() {
|
||||
return ¤tMidnaHairColors.l_normalColor;
|
||||
}
|
||||
|
||||
const GXColor* get_midna_hair_normalKColor() {
|
||||
return ¤tMidnaHairColors.l_normalKColor;
|
||||
}
|
||||
|
||||
const GXColor* get_midna_hair_normalKColor2() {
|
||||
return ¤tMidnaHairColors.l_normalKColor2;
|
||||
}
|
||||
|
||||
const GXColorS10* get_midna_hair_bigColor() {
|
||||
return ¤tMidnaHairColors.l_bigColor;
|
||||
}
|
||||
|
||||
const GXColor* get_midna_hair_bigKColor() {
|
||||
return ¤tMidnaHairColors.l_bigKColor;
|
||||
}
|
||||
|
||||
const GXColor* get_midna_hair_lNormalKColor() {
|
||||
return ¤tMidnaHairColors.l_lNormalKColor;
|
||||
}
|
||||
|
||||
const GXColor* get_midna_hair_lNormalKColor2() {
|
||||
return ¤tMidnaHairColors.l_lNormalKColor2;
|
||||
}
|
||||
|
||||
const GXColorS10* get_midna_hair_lBigColor() {
|
||||
return ¤tMidnaHairColors.l_lBigColor;
|
||||
}
|
||||
|
||||
const GXColor* get_midna_hair_lBigKColor2() {
|
||||
return ¤tMidnaHairColors.l_lBigKColor2;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <gx.h>
|
||||
|
||||
void set_all_midna_hair_colors();
|
||||
|
||||
const GXColorS10* get_midna_hair_normalColor();
|
||||
const GXColor* get_midna_hair_normalKColor();
|
||||
const GXColor* get_midna_hair_normalKColor2();
|
||||
const GXColorS10* get_midna_hair_bigColor();
|
||||
const GXColor* get_midna_hair_bigKColor();
|
||||
const GXColor* get_midna_hair_lNormalKColor();
|
||||
const GXColor* get_midna_hair_lNormalKColor2();
|
||||
const GXColorS10* get_midna_hair_lBigColor();
|
||||
const GXColor* get_midna_hair_lBigKColor2();
|
||||
@@ -1,282 +0,0 @@
|
||||
#include "mod.hpp"
|
||||
#include "hooks.hpp"
|
||||
#include "color_utils.hpp"
|
||||
#include "midna_hair_color.hpp"
|
||||
#include "option_descriptions.hpp"
|
||||
|
||||
#include "mods/service.hpp"
|
||||
#include "mods/svc/config.h"
|
||||
#include "mods/svc/hook.hpp"
|
||||
#include "mods/svc/log.h"
|
||||
#include "mods/svc/log.hpp"
|
||||
#include "mods/svc/ui.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
DEFINE_MOD();
|
||||
IMPORT_SERVICE(LogService, svc_log);
|
||||
IMPORT_SERVICE(ConfigService, svc_config);
|
||||
IMPORT_SERVICE(HookService, svc_hook);
|
||||
IMPORT_SERVICE(UiService, svc_ui);
|
||||
|
||||
static cvars g_cvars;
|
||||
|
||||
cvars& get_cvars() {
|
||||
return g_cvars;
|
||||
}
|
||||
|
||||
std::string get_str_option(ConfigVarHandle handle, const std::string& fallback) {
|
||||
std::string value{};
|
||||
size_t outLength{};
|
||||
svc_config->get_string(mod_ctx, handle, NULL, 0, &outLength);
|
||||
value.resize(outLength);
|
||||
if (handle == 0 || svc_config->get_string(mod_ctx, handle, value.data(), value.size() + 1, &outLength) != MOD_OK) {
|
||||
return fallback;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
int64_t get_int_option(ConfigVarHandle handle, int64_t fallback) {
|
||||
int64_t value = fallback;
|
||||
if (handle == 0 || svc_config->get_int(mod_ctx, handle, &value) != MOD_OK) {
|
||||
return fallback;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
namespace {
|
||||
UiWindowHandle g_cosmeticsWindow = 0;
|
||||
|
||||
ModResult register_str_option(
|
||||
const char* name, const char* defaultValue, ConfigVarHandle& outHandle, ModError* error) {
|
||||
ConfigVarDesc cvarDesc = CONFIG_VAR_DESC_INIT;
|
||||
cvarDesc.name = name;
|
||||
cvarDesc.type = CONFIG_VAR_STRING;
|
||||
cvarDesc.default_string = defaultValue;
|
||||
if (svc_config->register_var(mod_ctx, &cvarDesc, &outHandle) != MOD_OK) {
|
||||
return mods::set_error(error, MOD_ERROR, "failed to register cosmetics option");
|
||||
}
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
ModResult register_int_option(
|
||||
const char* name, int64_t defaultValue, ConfigVarHandle& outHandle, ModError* error) {
|
||||
ConfigVarDesc cvarDesc = CONFIG_VAR_DESC_INIT;
|
||||
cvarDesc.name = name;
|
||||
cvarDesc.type = CONFIG_VAR_INT;
|
||||
cvarDesc.default_int = defaultValue;
|
||||
if (svc_config->register_var(mod_ctx, &cvarDesc, &outHandle) != MOD_OK) {
|
||||
return mods::set_error(error, MOD_ERROR, "failed to register cosmetics option");
|
||||
}
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
void add_control(UiElementHandle pane, const UiControlDesc& desc) {
|
||||
auto result = svc_ui->pane_add_control(mod_ctx, pane, &desc, nullptr);
|
||||
if (result != MOD_OK) {
|
||||
mods::log::debug("pane_add_control failed {}", static_cast<int>(result));
|
||||
}
|
||||
}
|
||||
|
||||
void add_cosmetic_option(UiElementHandle left, ConfigVarHandle cvar, const char* name, const char* helpRml) {
|
||||
UiControlDesc control = UI_CONTROL_DESC_INIT;
|
||||
control.kind = UI_CONTROL_STRING;
|
||||
control.label = name;
|
||||
control.help_rml = helpRml;
|
||||
control.binding = UI_BINDING_CONFIG_VAR;
|
||||
control.config_var = cvar;
|
||||
control.max_length = 6;
|
||||
add_control(left, control);
|
||||
}
|
||||
|
||||
void add_cosmetic_option_with_rainbow(UiElementHandle left, ConfigVarHandle cvar, const char* name, const char* helpRml) {
|
||||
UiControlDesc control = UI_CONTROL_DESC_INIT;
|
||||
control.kind = UI_CONTROL_STRING;
|
||||
control.label = name;
|
||||
control.help_rml = helpRml;
|
||||
control.binding = UI_BINDING_CONFIG_VAR;
|
||||
control.config_var = cvar;
|
||||
control.max_length = 7;
|
||||
add_control(left, control);
|
||||
}
|
||||
|
||||
void add_midna_hair_option(UiElementHandle left, ConfigVarHandle cvar, const std::string& name) {
|
||||
static const char* kMidnaHairOptions[] = {
|
||||
"Default", "Pink", "Red", "Yellow", "Green", "Blue", "Purple", "Brown", "White", "Black"
|
||||
};
|
||||
UiControlDesc control = UI_CONTROL_DESC_INIT;
|
||||
control.kind = UI_CONTROL_SELECT;
|
||||
control.label = name.c_str();
|
||||
control.help_rml = "Choose Midna's hair color.";
|
||||
control.binding = UI_BINDING_CONFIG_VAR;
|
||||
control.config_var = cvar;
|
||||
control.options = kMidnaHairOptions;
|
||||
control.option_count = 10;
|
||||
add_control(left, control);
|
||||
}
|
||||
|
||||
ModResult build_equipment_colors_tab(
|
||||
ModContext*, UiWindowHandle, UiElementHandle left, UiElementHandle right, void*, ModError*) {
|
||||
(void)right;
|
||||
|
||||
add_cosmetic_option(left, g_cvars.herosTunicCapColor, "Hero's Tunic Cap Color", kLinksCapColorsExplanation);
|
||||
add_cosmetic_option(left, g_cvars.herosTunicTorsoColor, "Hero's Tunic Body Color", kLinksShirtColorsExplanation);
|
||||
add_cosmetic_option(left, g_cvars.herosTunicSkirtColor, "Hero's Tunic Skirt Color", kLinksSkirtColorsExplanation);
|
||||
add_cosmetic_option(left, g_cvars.zoraArmorCapColor, "Zora Armor Cap Color", kZoraCapColorsExplanation);
|
||||
add_cosmetic_option(left, g_cvars.zoraArmorHelmetColor, "Zora Armor Helmet Color", kZoraHelmetColorsExplanation);
|
||||
add_cosmetic_option(left, g_cvars.zoraArmorTorsoColor, "Zora Armor Torso Color", kZoraTorsoColorsExplanation);
|
||||
add_cosmetic_option(left, g_cvars.zoraArmorScalesColor, "Zora Armor Scales Color", kZoraScalesColorsExplanation);
|
||||
add_cosmetic_option(left, g_cvars.zoraArmorFlippersColor, "Zora Armor Flippers Color", kZoraFlippersColorsExplanation);
|
||||
add_cosmetic_option_with_rainbow(left, g_cvars.lanternGlowColor, "Lantern Glow Color", kLanternGlowColorsExplanation);
|
||||
add_cosmetic_option(left, g_cvars.woodenSwordColor, "Wooden Sword Color", kWoodenSwordColorsExplanation);
|
||||
add_cosmetic_option(left, g_cvars.ordonSwordBladeColor, "Ordon Sword Blade Color", kOrdonBladeColorsExplanation);
|
||||
add_cosmetic_option(left, g_cvars.ordonSwordHandleColor, "Ordon Sword Handle Color", kOrdonHandleColorsExplanation);
|
||||
add_cosmetic_option(left, g_cvars.msBladeColor, "Master Sword Blade Color", kMSBladeColorsExplanation);
|
||||
add_cosmetic_option(left, g_cvars.msHandleColor, "Master Sword Handle Color", kMSHandleColorsExplanation);
|
||||
add_cosmetic_option_with_rainbow(left, g_cvars.lightSwordGlowColor, "Light Sword Glow Color", kSwordGlowColorsExplanation);
|
||||
add_cosmetic_option(left, g_cvars.boomerangColor, "Boomerang Color", kBoomerangColorsExplanation);
|
||||
add_cosmetic_option(left, g_cvars.ironBootsColor, "Iron Boots Color", kIronBootsColorsExplanation);
|
||||
add_cosmetic_option(left, g_cvars.spinnerColor, "Spinner Color", kSpinnerColorsExplanation);
|
||||
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
ModResult build_ui_colors_tab(
|
||||
ModContext*, UiWindowHandle, UiElementHandle left, UiElementHandle right, void*, ModError*) {
|
||||
(void)right;
|
||||
|
||||
add_cosmetic_option(left, g_cvars.aButtonColor, "A Button Color", kAButtonColorsExplanation);
|
||||
add_cosmetic_option(left, g_cvars.bButtonColor, "B Button Color", kBButtonColorsExplanation);
|
||||
add_cosmetic_option(left, g_cvars.xButtonColor, "X Button Color", kXButtonColorsExplanation);
|
||||
add_cosmetic_option(left, g_cvars.yButtonColor, "Y Button Color", kYButtonColorsExplanation);
|
||||
add_cosmetic_option(left, g_cvars.zButtonColor, "Z Button Color", kZButtonColorsExplanation);
|
||||
add_cosmetic_option(left, g_cvars.heartColor, "Heart Color", kHeartColorsExplanation);
|
||||
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
ModResult build_misc_colors_tab(
|
||||
ModContext*, UiWindowHandle, UiElementHandle left, UiElementHandle right, void*, ModError*) {
|
||||
(void)right;
|
||||
|
||||
add_midna_hair_option(left, g_cvars.midnaHairBaseColor, "Midna's Hair Base Color");
|
||||
add_midna_hair_option(left, g_cvars.midnaHairTipsColor, "Midna's Hair Tips Color");
|
||||
add_cosmetic_option(left, g_cvars.midnaChargeRingColor, "Midna Charge Ring Color", kChargeRingColorsExplanation);
|
||||
add_cosmetic_option(left, g_cvars.linkHairColor, "Link's Hair Color", kLinksHairColorsExplanation);
|
||||
add_cosmetic_option(left, g_cvars.wolfLinkColor, "Wolf Link Color", kWolfLinkColorsExplanation);
|
||||
add_cosmetic_option(left, g_cvars.eponaColor, "Epona Color", kEponaColorsExplanation);
|
||||
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
void on_cosmetics_menu_window_closed(ModContext*, UiWindowHandle, void*) {
|
||||
g_cosmeticsWindow = 0;
|
||||
}
|
||||
|
||||
void on_open_cosmetics_menu(ModContext*, void*) {
|
||||
if (g_cosmeticsWindow != 0) {
|
||||
return;
|
||||
}
|
||||
UiTabDesc tabs[] = {UI_TAB_DESC_INIT, UI_TAB_DESC_INIT, UI_TAB_DESC_INIT};
|
||||
tabs[0].title = "Equipment Colors";
|
||||
tabs[0].build = build_equipment_colors_tab;
|
||||
tabs[1].title = "UI Colors";
|
||||
tabs[1].build = build_ui_colors_tab;
|
||||
tabs[2].title = "Misc Colors";
|
||||
tabs[2].build = build_misc_colors_tab;
|
||||
UiWindowDesc desc = UI_WINDOW_DESC_INIT;
|
||||
desc.tabs = tabs;
|
||||
desc.tab_count = ARRAY_SIZE(tabs);
|
||||
desc.on_closed = on_cosmetics_menu_window_closed;
|
||||
if (svc_ui->window_push(mod_ctx, &desc, &g_cosmeticsWindow) != MOD_OK) {
|
||||
svc_log->error(mod_ctx, "failed to open basic cosmetics window");
|
||||
}
|
||||
}
|
||||
|
||||
ModResult build_panel(ModContext*, UiElementHandle panel, void*, ModError*) {
|
||||
UiControlDesc control = UI_CONTROL_DESC_INIT;
|
||||
control.kind = UI_CONTROL_BUTTON;
|
||||
control.label = "Open Cosmetics Menu";
|
||||
control.on_pressed = on_open_cosmetics_menu;
|
||||
add_control(panel, control);
|
||||
return MOD_OK;
|
||||
}
|
||||
}
|
||||
|
||||
#define REGISTER_COSMETIC_OPTION(option) \
|
||||
result = register_str_option(#option, NULL, g_cvars.option, error); \
|
||||
if (result != MOD_OK) { \
|
||||
return result; \
|
||||
} \
|
||||
|
||||
extern "C" {
|
||||
MOD_EXPORT ModResult mod_initialize(ModError* error) {
|
||||
svc_log->info(mod_ctx, "basic_cosmetics_mod initialized");
|
||||
|
||||
ModResult result{};
|
||||
|
||||
REGISTER_COSMETIC_OPTION(herosTunicCapColor)
|
||||
REGISTER_COSMETIC_OPTION(herosTunicTorsoColor)
|
||||
REGISTER_COSMETIC_OPTION(herosTunicSkirtColor)
|
||||
REGISTER_COSMETIC_OPTION(zoraArmorCapColor)
|
||||
REGISTER_COSMETIC_OPTION(zoraArmorHelmetColor)
|
||||
REGISTER_COSMETIC_OPTION(zoraArmorTorsoColor)
|
||||
REGISTER_COSMETIC_OPTION(zoraArmorScalesColor)
|
||||
REGISTER_COSMETIC_OPTION(zoraArmorFlippersColor)
|
||||
REGISTER_COSMETIC_OPTION(lanternGlowColor)
|
||||
REGISTER_COSMETIC_OPTION(woodenSwordColor)
|
||||
REGISTER_COSMETIC_OPTION(ordonSwordBladeColor)
|
||||
REGISTER_COSMETIC_OPTION(ordonSwordHandleColor)
|
||||
REGISTER_COSMETIC_OPTION(msBladeColor)
|
||||
REGISTER_COSMETIC_OPTION(msHandleColor)
|
||||
REGISTER_COSMETIC_OPTION(lightSwordGlowColor)
|
||||
REGISTER_COSMETIC_OPTION(boomerangColor)
|
||||
REGISTER_COSMETIC_OPTION(ironBootsColor)
|
||||
REGISTER_COSMETIC_OPTION(spinnerColor)
|
||||
REGISTER_COSMETIC_OPTION(aButtonColor)
|
||||
REGISTER_COSMETIC_OPTION(bButtonColor)
|
||||
REGISTER_COSMETIC_OPTION(xButtonColor)
|
||||
REGISTER_COSMETIC_OPTION(yButtonColor)
|
||||
REGISTER_COSMETIC_OPTION(zButtonColor)
|
||||
REGISTER_COSMETIC_OPTION(heartColor)
|
||||
|
||||
result = register_int_option("midnaHairBaseColor", 0, g_cvars.midnaHairBaseColor, error);
|
||||
if (result != MOD_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = register_int_option("midnaHairTipsColor", 0, g_cvars.midnaHairTipsColor, error);
|
||||
if (result != MOD_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
REGISTER_COSMETIC_OPTION(midnaChargeRingColor)
|
||||
REGISTER_COSMETIC_OPTION(linkHairColor)
|
||||
REGISTER_COSMETIC_OPTION(wolfLinkColor)
|
||||
REGISTER_COSMETIC_OPTION(eponaColor)
|
||||
|
||||
UiModsPanelDesc panelDesc = UI_MODS_PANEL_DESC_INIT;
|
||||
panelDesc.build = build_panel;
|
||||
svc_ui->register_mods_panel(mod_ctx, &panelDesc);
|
||||
|
||||
// Add all our hooks
|
||||
result = add_all_hooks();
|
||||
if (result != MOD_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
MOD_EXPORT ModResult mod_update(ModError*) {
|
||||
update_rainbow_rgb(1.0f);
|
||||
set_all_midna_hair_colors();
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
MOD_EXPORT ModResult mod_shutdown(ModError*) {
|
||||
svc_log->info(mod_ctx, "basic_cosmetics_mod unloaded");
|
||||
g_cosmeticsWindow = 0;
|
||||
return MOD_OK;
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "mods/svc/config.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
struct cvars {
|
||||
ConfigVarHandle herosTunicCapColor = 0;
|
||||
ConfigVarHandle herosTunicTorsoColor = 0;
|
||||
ConfigVarHandle herosTunicSkirtColor = 0;
|
||||
ConfigVarHandle zoraArmorCapColor = 0;
|
||||
ConfigVarHandle zoraArmorHelmetColor = 0;
|
||||
ConfigVarHandle zoraArmorTorsoColor = 0;
|
||||
ConfigVarHandle zoraArmorScalesColor = 0;
|
||||
ConfigVarHandle zoraArmorFlippersColor = 0;
|
||||
ConfigVarHandle lanternGlowColor = 0;
|
||||
ConfigVarHandle woodenSwordColor = 0;
|
||||
ConfigVarHandle ordonSwordBladeColor = 0;
|
||||
ConfigVarHandle ordonSwordHandleColor = 0;
|
||||
ConfigVarHandle msBladeColor = 0;
|
||||
ConfigVarHandle msHandleColor = 0;
|
||||
ConfigVarHandle lightSwordGlowColor = 0;
|
||||
ConfigVarHandle boomerangColor = 0;
|
||||
ConfigVarHandle ironBootsColor = 0;
|
||||
ConfigVarHandle spinnerColor = 0;
|
||||
ConfigVarHandle heartColor = 0;
|
||||
ConfigVarHandle aButtonColor = 0;
|
||||
ConfigVarHandle bButtonColor = 0;
|
||||
ConfigVarHandle xButtonColor = 0;
|
||||
ConfigVarHandle yButtonColor = 0;
|
||||
ConfigVarHandle zButtonColor = 0;
|
||||
ConfigVarHandle midnaHairBaseColor = 0;
|
||||
ConfigVarHandle midnaHairTipsColor = 0;
|
||||
ConfigVarHandle midnaChargeRingColor = 0;
|
||||
ConfigVarHandle linkHairColor = 0;
|
||||
ConfigVarHandle wolfLinkColor = 0;
|
||||
ConfigVarHandle eponaColor = 0;
|
||||
};
|
||||
|
||||
cvars& get_cvars();
|
||||
|
||||
std::string get_str_option(ConfigVarHandle handle, const std::string& fallback);
|
||||
|
||||
int64_t get_int_option(ConfigVarHandle handle, int64_t fallback);
|
||||
@@ -1,354 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
constexpr const char* kLinksCapColorsExplanation =
|
||||
"Set the color of the Hero's Tunic cap with any 6 digit hex code. Loading a new area, changing forms, or changing tunics is required to see changes.<br/>"
|
||||
"Common colors:<br/>"
|
||||
"- Red: ab706e<br/>"
|
||||
"- Blue: 6382a0<br/>"
|
||||
"- Purple: 94749a<br/>"
|
||||
"- Orange: ec8644<br/>"
|
||||
"- Yellow: b9ab00<br/>"
|
||||
"- Pink: ec9fc8<br/>"
|
||||
"- Black: 505154<br/>"
|
||||
"- White: f8f7f4<br/>"
|
||||
"- Brown: 91723e<br/>";
|
||||
|
||||
constexpr const char* kLinksShirtColorsExplanation =
|
||||
"Set the color of the Hero's Tunic shirt with any 6 digit hex code. Loading a new area, changing forms, or changing tunics is required to see changes.<br/>"
|
||||
"Common colors:<br/>"
|
||||
"- Red: ab706e<br/>"
|
||||
"- Blue: 6382a0<br/>"
|
||||
"- Purple: 94749a<br/>"
|
||||
"- Orange: ec8644<br/>"
|
||||
"- Yellow: b9ab00<br/>"
|
||||
"- Pink: ec9fc8<br/>"
|
||||
"- Black: 505154<br/>"
|
||||
"- White: f8f7f4<br/>"
|
||||
"- Brown: 91723e<br/>";
|
||||
|
||||
constexpr const char* kLinksSkirtColorsExplanation =
|
||||
"Set the color of the Hero's Tunic skirt with any 6 digit hex code. Loading a new area, changing forms, or changing tunics is required to see changes.<br/>"
|
||||
"Common colors:<br/>"
|
||||
"- Red: ab706e<br/>"
|
||||
"- Blue: 6382a0<br/>"
|
||||
"- Purple: 94749a<br/>"
|
||||
"- Orange: ec8644<br/>"
|
||||
"- Yellow: b9ab00<br/>"
|
||||
"- Pink: ec9fc8<br/>"
|
||||
"- Black: 505154<br/>"
|
||||
"- White: f8f7f4<br/>"
|
||||
"- Brown: 91723e<br/>";
|
||||
|
||||
constexpr const char* kZoraCapColorsExplanation =
|
||||
"Set the color of the Zora Armor cap with any 6 digit hex code. Loading a new area, changing forms, or changing tunics is required to see changes.<br/>"
|
||||
"Common colors:<br/>"
|
||||
"- Red: ab706e<br/>"
|
||||
"- Blue: 6382a0<br/>"
|
||||
"- Purple: 94749a<br/>"
|
||||
"- Orange: ec8644<br/>"
|
||||
"- Yellow: b9ab00<br/>"
|
||||
"- Pink: ec9fc8<br/>"
|
||||
"- Black: 505154<br/>"
|
||||
"- White: f8f7f4<br/>"
|
||||
"- Brown: 91723e<br/>";
|
||||
|
||||
constexpr const char* kZoraHelmetColorsExplanation =
|
||||
"Set the color of the Zora Armor helmet with any 6 digit hex code. Loading a new area, changing forms, or changing tunics is required to see changes.<br/>"
|
||||
"Common colors:<br/>"
|
||||
"- Red: ab706e<br/>"
|
||||
"- Blue: 6382a0<br/>"
|
||||
"- Purple: 94749a<br/>"
|
||||
"- Orange: ec8644<br/>"
|
||||
"- Yellow: b9ab00<br/>"
|
||||
"- Pink: ec9fc8<br/>"
|
||||
"- Black: 505154<br/>"
|
||||
"- White: f8f7f4<br/>"
|
||||
"- Brown: 91723e<br/>";
|
||||
|
||||
constexpr const char* kZoraTorsoColorsExplanation =
|
||||
"Set the color of the Zora Armor torso with any 6 digit hex code. Loading a new area, changing forms, or changing tunics is required to see changes.<br/>"
|
||||
"Common colors:<br/>"
|
||||
"- Red: ab706e<br/>"
|
||||
"- Blue: 6382a0<br/>"
|
||||
"- Purple: 94749a<br/>"
|
||||
"- Orange: ec8644<br/>"
|
||||
"- Yellow: b9ab00<br/>"
|
||||
"- Pink: ec9fc8<br/>"
|
||||
"- Black: 505154<br/>"
|
||||
"- White: f8f7f4<br/>"
|
||||
"- Brown: 91723e<br/>";
|
||||
|
||||
constexpr const char* kZoraScalesColorsExplanation =
|
||||
"Set the color of the Zora Armor scales with any 6 digit hex code. Loading a new area, changing forms, or changing tunics is required to see changes.<br/>"
|
||||
"Common colors:<br/>"
|
||||
"- Red: ab706e<br/>"
|
||||
"- Blue: 6382a0<br/>"
|
||||
"- Purple: 94749a<br/>"
|
||||
"- Orange: ec8644<br/>"
|
||||
"- Yellow: b9ab00<br/>"
|
||||
"- Pink: ec9fc8<br/>"
|
||||
"- Black: 505154<br/>"
|
||||
"- White: f8f7f4<br/>"
|
||||
"- Brown: 91723e<br/>";
|
||||
|
||||
constexpr const char* kZoraFlippersColorsExplanation =
|
||||
"Set the color of the Zora Armor flippers with any 6 digit hex code. Loading a new area, changing forms, or changing tunics is required to see changes.<br/>"
|
||||
"Common colors:<br/>"
|
||||
"- Red: ab706e<br/>"
|
||||
"- Blue: 6382a0<br/>"
|
||||
"- Purple: 94749a<br/>"
|
||||
"- Orange: ec8644<br/>"
|
||||
"- Yellow: b9ab00<br/>"
|
||||
"- Pink: ec9fc8<br/>"
|
||||
"- Black: 505154<br/>"
|
||||
"- White: f8f7f4<br/>"
|
||||
"- Brown: 91723e<br/>";
|
||||
|
||||
constexpr const char* kWoodenSwordColorsExplanation =
|
||||
"Set the color with any 6 digit hex code. Loading a new area, changing forms, or changing tunics is required to see changes.<br/>"
|
||||
"Common colors:<br/>"
|
||||
"- Red: ab706e<br/>"
|
||||
"- Blue: 6382a0<br/>"
|
||||
"- Purple: 94749a<br/>"
|
||||
"- Orange: ec8644<br/>"
|
||||
"- Yellow: b9ab00<br/>"
|
||||
"- Pink: ec9fc8<br/>"
|
||||
"- Black: 505154<br/>"
|
||||
"- White: f8f7f4<br/>"
|
||||
"- Brown: 91723e<br/>";
|
||||
|
||||
constexpr const char* kLanternGlowColorsExplanation =
|
||||
"Set the color of the lantern light with any 6 digit hex code, or with \"rainbow\" for a rainbow effect.<br/>"
|
||||
"Common colors:<br/>"
|
||||
"- Red: ff0000<br/>"
|
||||
"- Orange: f68821<br/>"
|
||||
"- Yellow: f6f321<br/>"
|
||||
"- Green: 00ff00<br/>"
|
||||
"- Blue: 0000ff<br/>"
|
||||
"- Purple: 8000ff<br/>"
|
||||
"- White: a0a0a0<br/>";
|
||||
|
||||
constexpr const char* kOrdonBladeColorsExplanation =
|
||||
"Set the color of the Ordon Sword's blade with any 6 digit hex code. Closing and re-opening Dusklight will be required to see changes.<br/>"
|
||||
"Common colors:<br/>"
|
||||
"- Red: ff0000<br/>"
|
||||
"- Orange: f68821<br/>"
|
||||
"- Yellow: f6f321<br/>"
|
||||
"- Green: 00ff00<br/>"
|
||||
"- Blue: 0000ff<br/>"
|
||||
"- Purple: 8000ff<br/>"
|
||||
"- White: a0a0a0<br/>"
|
||||
"- Cyan: 30d0d0<br/>";
|
||||
|
||||
constexpr const char* kOrdonHandleColorsExplanation =
|
||||
"Set the color of the Ordon Sword's handle with any 6 digit hex code. Closing and re-opening Dusklight will be required to see changes.<br/>"
|
||||
"Common colors:<br/>"
|
||||
"- Red: ff0000<br/>"
|
||||
"- Orange: f68821<br/>"
|
||||
"- Yellow: f6f321<br/>"
|
||||
"- Green: 00ff00<br/>"
|
||||
"- Blue: 0000ff<br/>"
|
||||
"- Purple: 8000ff<br/>"
|
||||
"- White: a0a0a0<br/>"
|
||||
"- Cyan: 30d0d0<br/>";
|
||||
|
||||
constexpr const char* kMSBladeColorsExplanation =
|
||||
"Set the color of the Master Sword's blade with any 6 digit hex code. Closing and re-opening Dusklight will be required to see changes.<br/>"
|
||||
"Common colors:<br/>"
|
||||
"- Red: ff0000<br/>"
|
||||
"- Orange: f68821<br/>"
|
||||
"- Yellow: f6f321<br/>"
|
||||
"- Green: 00ff00<br/>"
|
||||
"- Blue: 0000ff<br/>"
|
||||
"- Purple: 8000ff<br/>"
|
||||
"- White: a0a0a0<br/>"
|
||||
"- Cyan: 30d0d0<br/>";
|
||||
|
||||
constexpr const char* kMSHandleColorsExplanation =
|
||||
"Set the color of the Master Sword's handle with any 6 digit hex code. Closing and re-opening Dusklight will be required to see changes.<br/>"
|
||||
"Common colors:<br/>"
|
||||
"- Red: ff0000<br/>"
|
||||
"- Orange: f68821<br/>"
|
||||
"- Yellow: f6f321<br/>"
|
||||
"- Green: 00ff00<br/>"
|
||||
"- Blue: 0000ff<br/>"
|
||||
"- Purple: 8000ff<br/>"
|
||||
"- White: a0a0a0<br/>"
|
||||
"- Cyan: 30d0d0<br/>";
|
||||
|
||||
constexpr const char* kSwordGlowColorsExplanation =
|
||||
"Set the color of the light sword glow with any 6 digit hex code, or with \"rainbow\" for a rainbow effect.<br/>"
|
||||
"Common colors:<br/>"
|
||||
"- Red: ff0000<br/>"
|
||||
"- Orange: f68821<br/>"
|
||||
"- Yellow: f6f321<br/>"
|
||||
"- Green: 00ff00<br/>"
|
||||
"- Blue: 0000ff<br/>"
|
||||
"- Purple: 8000ff<br/>"
|
||||
"- White: a0a0a0<br/>";
|
||||
|
||||
constexpr const char* kBoomerangColorsExplanation =
|
||||
"Set the color of the Gale Boomerang with any 6 digit hex code. Closing and re-opening Dusklight will be required to see changes.<br/>"
|
||||
"Common colors:<br/>"
|
||||
"- Red: ab706e<br/>"
|
||||
"- Blue: 6382a0<br/>"
|
||||
"- Purple: 94749a<br/>"
|
||||
"- Orange: ec8644<br/>"
|
||||
"- Yellow: b9ab00<br/>"
|
||||
"- Pink: ec9fc8<br/>"
|
||||
"- Black: 505154<br/>"
|
||||
"- White: f8f7f4<br/>"
|
||||
"- Brown: 91723e<br/>";
|
||||
|
||||
constexpr const char* kIronBootsColorsExplanation =
|
||||
"Set the color of the Iron Boots with any 6 digit hex code. Closing and re-opening Dusklight will be required to see changes.<br/>"
|
||||
"Common colors:<br/>"
|
||||
"- Red: ab706e<br/>"
|
||||
"- Blue: 6382a0<br/>"
|
||||
"- Purple: 94749a<br/>"
|
||||
"- Orange: ec8644<br/>"
|
||||
"- Yellow: b9ab00<br/>"
|
||||
"- Pink: ec9fc8<br/>"
|
||||
"- Black: 505154<br/>"
|
||||
"- White: f8f7f4<br/>"
|
||||
"- Brown: 91723e<br/>";
|
||||
|
||||
constexpr const char* kSpinnerColorsExplanation =
|
||||
"Set the color of the Spinner with any 6 digit hex code. Closing and re-opening Dusklight will be required to see changes.<br/>"
|
||||
"Common colors:<br/>"
|
||||
"- Red: ab706e<br/>"
|
||||
"- Blue: 6382a0<br/>"
|
||||
"- Purple: 94749a<br/>"
|
||||
"- Orange: ec8644<br/>"
|
||||
"- Yellow: b9ab00<br/>"
|
||||
"- Pink: ec9fc8<br/>"
|
||||
"- Black: 505154<br/>"
|
||||
"- White: f8f7f4<br/>"
|
||||
"- Brown: 91723e<br/>";
|
||||
|
||||
|
||||
constexpr const char* kAButtonColorsExplanation =
|
||||
"Set the color of the A button with any 6 digit hex code. A reload may be necessary to see changes.<br/>"
|
||||
"Common colors:<br/>"
|
||||
"- Red: ff0000<br/>"
|
||||
"- Orange: ff5000<br/>"
|
||||
"- Yellow: ffaf00<br/>"
|
||||
"- Dark Green: 0080ff<br/>"
|
||||
"- Blue: 0000ff<br/>"
|
||||
"- Purple: 8000ff<br/>"
|
||||
"- Grey: 5555ff<br/>"
|
||||
"- Pink: ff20ff<br/>";
|
||||
|
||||
constexpr const char* kBButtonColorsExplanation =
|
||||
"Set the color of the B button with any 6 digit hex code. A reload may be necessary to see changes.<br/>"
|
||||
"Common colors:<br/>"
|
||||
"- Orange: ffff40<br/>"
|
||||
"- Pink: ffa0ff<br/>"
|
||||
"- Green: 00e87b<br/>"
|
||||
"- Blue: 00aaff<br/>"
|
||||
"- Purple: 6078ff<br/>"
|
||||
"- Black: 000000<br/>"
|
||||
"- Teal: 00f3ff<br/>";
|
||||
|
||||
constexpr const char* kXButtonColorsExplanation =
|
||||
"Set the color of the X button with any 6 digit hex code. A reload may be necessary to see changes.<br/>"
|
||||
"Common colors:<br/>"
|
||||
"- Red: ff0000<br/>"
|
||||
"- Orange: ff8200<br/>"
|
||||
"- Yellow: f7df00<br/>"
|
||||
"- Lime Green: 70ff00<br/>"
|
||||
"- Dark Green: 00bd11<br/>"
|
||||
"- Blue: 0000ff<br/>"
|
||||
"- Purple: 800088<br/>"
|
||||
"- Black: 000000<br/>"
|
||||
"- Pink: ff00aa<br/>"
|
||||
"- Cyan: 00ffff<br/>";
|
||||
|
||||
constexpr const char* kYButtonColorsExplanation =
|
||||
"Set the color of the Y button with any 6 digit hex code. A reload may be necessary to see changes.<br/>"
|
||||
"Common colors:<br/>"
|
||||
"- Red: ff0000<br/>"
|
||||
"- Orange: ff8200<br/>"
|
||||
"- Yellow: f7df00<br/>"
|
||||
"- Lime Green: 70ff00<br/>"
|
||||
"- Dark Green: 00bd11<br/>"
|
||||
"- Blue: 0000ff<br/>"
|
||||
"- Purple: 800088<br/>"
|
||||
"- Black: 000000<br/>"
|
||||
"- Pink: ff00aa<br/>"
|
||||
"- Cyan: 00ffff<br/>";
|
||||
|
||||
constexpr const char* kZButtonColorsExplanation =
|
||||
"Set the Z button color with any 6 digit hex code. A reload may be necessary to see changes.<br/>"
|
||||
"Common colors:<br/>"
|
||||
"- Red: ff0000<br/>"
|
||||
"- Orange: ff8200<br/>"
|
||||
"- Yellow: f7df00<br/>"
|
||||
"- Lime Green: 70ff00<br/>"
|
||||
"- Dark Green: 00bd11<br/>"
|
||||
"- Purple: 800088<br/>"
|
||||
"- Black: 000000<br/>"
|
||||
"- Light Blue: 00ffff<br/>";
|
||||
|
||||
constexpr const char* kHeartColorsExplanation =
|
||||
"Set the heart color with any 6 digit hex code. This will also change the color of heart drops. A reload may be necessary to see changes.<br/>"
|
||||
"Common colors:<br/>"
|
||||
"- Orange: ffff40<br/>"
|
||||
"- Pink: ffa0ff<br/>"
|
||||
"- Green: 00e87b<br/>"
|
||||
"- Blue: 00aaff<br/>"
|
||||
"- Purple: 6078ff<br/>"
|
||||
"- Black: 000000<br/>"
|
||||
"- Teal: 00f3ff<br/>";
|
||||
|
||||
constexpr const char* kChargeRingColorsExplanation =
|
||||
"Set the color of Midna's charge ring with any 6 digit hex code.<br/>"
|
||||
"Common colors:<br/>"
|
||||
"- Pink: ff9f9f<br/>"
|
||||
"- Red: ff0000<br/>"
|
||||
"- Yellow: ffff00<br/>"
|
||||
"- Green: 00ff00<br/>"
|
||||
"- Blue: 0000ff<br/>"
|
||||
"- Purple: ff00ff<br/>"
|
||||
"- Brown: 331900<br/>"
|
||||
"- White: feffff<br/>"
|
||||
"- Black: 000000<br/>";
|
||||
|
||||
constexpr const char* kLinksHairColorsExplanation =
|
||||
"Set the color of Link's hair with any 6 digit hex code. Loading a new area, changing forms, or changing tunics is required to see changes.<br/>"
|
||||
"Common colors:<br/>"
|
||||
"- Red: ab706e<br/>"
|
||||
"- Blue: 6382a0<br/>"
|
||||
"- Purple: 94749a<br/>"
|
||||
"- Orange: ec8644<br/>"
|
||||
"- Yellow: b9ab00<br/>"
|
||||
"- Pink: ec9fc8<br/>"
|
||||
"- Black: 505154<br/>"
|
||||
"- White: f8f7f4<br/>"
|
||||
"- Brown: 91723e<br/>";
|
||||
|
||||
constexpr const char* kWolfLinkColorsExplanation =
|
||||
"Set the color of Wolf Link with any 6 digit hex code. Loading a new area or changing forms is required to see changes.<br/>"
|
||||
"Common colors:<br/>"
|
||||
"- Red: ab706e<br/>"
|
||||
"- Blue: 6382a0<br/>"
|
||||
"- Purple: 94749a<br/>"
|
||||
"- Orange: ec8644<br/>"
|
||||
"- Yellow: b9ab00<br/>"
|
||||
"- Pink: ec9fc8<br/>"
|
||||
"- Black: 505154<br/>"
|
||||
"- White: f8f7f4<br/>"
|
||||
"- Brown: 91723e<br/>";
|
||||
|
||||
constexpr const char* kEponaColorsExplanation =
|
||||
"Set the color of Wolf Link with any 6 digit hex code. Loading a new area while *not* riding Epona is required to see changes.<br/>"
|
||||
"Common colors:<br/>"
|
||||
"- Red: ab706e<br/>"
|
||||
"- Blue: 6382a0<br/>"
|
||||
"- Purple: 94749a<br/>"
|
||||
"- Orange: ec8644<br/>"
|
||||
"- Yellow: b9ab00<br/>"
|
||||
"- Pink: ec9fc8<br/>"
|
||||
"- Black: 505154<br/>"
|
||||
"- White: f8f7f4<br/>"
|
||||
"- Brown: 91723e<br/>";
|
||||
@@ -1,549 +0,0 @@
|
||||
#include "texture_utils.hpp"
|
||||
#include "color_utils.hpp"
|
||||
#include "mod.hpp"
|
||||
|
||||
#include "mods/svc/config.h"
|
||||
#include "mods/svc/log.hpp"
|
||||
|
||||
// Forward declaration needed for J3DModelLoader to be happy
|
||||
class J3DVertexData;
|
||||
#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 "global.h"
|
||||
#include "gx/GXEnum.h"
|
||||
#include "m_Do/m_Do_dvd_thread.h"
|
||||
|
||||
#include <list>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void recolor_rgb5a3_texture(ResTIMG* texHeaderPtr, GXColor color)
|
||||
{
|
||||
// Precompute lookup tables for both RGB555 (opaque) and RGB444 (translucent) modes
|
||||
uint16_t recolors_rgb555[0x100];
|
||||
uint16_t recolors_rgb444[0x100];
|
||||
|
||||
for (int32_t i = 0; i < 0x100; i++) {
|
||||
const uint8_t r = blend_overlay_channel(i, color.r);
|
||||
const uint8_t g = blend_overlay_channel(i, color.g);
|
||||
const uint8_t b = blend_overlay_channel(i, color.b);
|
||||
|
||||
// Pack as RGB555: Bit 15 set to 1 + 5 bits R, G, B
|
||||
recolors_rgb555[i] = 0x8000 | ((r >> 3) << 10) | ((g >> 3) << 5) | (b >> 3);
|
||||
|
||||
// Pack as RGB444: 4 bits R, G, B (Bit 15 remains 0)
|
||||
recolors_rgb444[i] = ((r >> 4) << 8) | ((g >> 4) << 4) | (b >> 4);
|
||||
}
|
||||
|
||||
constexpr int32_t blockWidth = 4;
|
||||
constexpr int32_t blockHeight = 4;
|
||||
|
||||
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 totalPixels = roundedWidth * roundedHeight;
|
||||
|
||||
auto* pixelPtr = reinterpret_cast<BE<uint16_t>*>(JSUConvertOffsetToPtr<u8>(texHeaderPtr, texHeaderPtr->imageOffset));
|
||||
|
||||
for (int32_t i = 0; i < totalPixels; i++) {
|
||||
const uint16_t rawPixel = pixelPtr[i];
|
||||
|
||||
// MSB determines if pixel is opaque or translucent
|
||||
if (rawPixel & 0x8000) {
|
||||
// Pixel is opaque
|
||||
const uint8_t r5 = (rawPixel >> 10) & 0x1F;
|
||||
const uint8_t g5 = (rawPixel >> 5) & 0x1F;
|
||||
const uint8_t b5 = rawPixel & 0x1F;
|
||||
|
||||
// Expand 5-bit to 8-bit
|
||||
const uint8_t r8 = (r5 << 3) | (r5 >> 2);
|
||||
const uint8_t g8 = (g5 << 3) | (g5 >> 2);
|
||||
const uint8_t b8 = (b5 << 3) | (b5 >> 2);
|
||||
|
||||
const uint8_t grayVal = static_cast<uint8_t>((r8 * 77 + g8 * 150 + b8 * 29) >> 8);
|
||||
|
||||
pixelPtr[i] = recolors_rgb555[grayVal];
|
||||
} else {
|
||||
// Pixel is translucent
|
||||
const uint16_t alpha3 = rawPixel & 0x7000;
|
||||
|
||||
const uint8_t r4 = (rawPixel >> 8) & 0x0F;
|
||||
const uint8_t g4 = (rawPixel >> 4) & 0x0F;
|
||||
const uint8_t b4 = rawPixel & 0x0F;
|
||||
|
||||
// Expand 4-bit to 8-bit
|
||||
const uint8_t r8 = (r4 << 4) | r4;
|
||||
const uint8_t g8 = (g4 << 4) | g4;
|
||||
const uint8_t b8 = (b4 << 4) | b4;
|
||||
|
||||
const uint8_t grayVal = static_cast<uint8_t>((r8 * 77 + g8 * 150 + b8 * 29) >> 8);
|
||||
|
||||
// Combine original alpha with recolored RGB444
|
||||
pixelPtr[i] = alpha3 | recolors_rgb444[grayVal];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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(ResTIMG* texHeaderPtr, GXColor color)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to encode a single 4x4 sub-block (16 pixels) into an 8-byte CMPR block
|
||||
static void encode_cmpr_sub_block(uint8_t* dst, const uint8_t pixels[16]) {
|
||||
uint8_t min_val = 255;
|
||||
uint8_t max_val = 0;
|
||||
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
if (pixels[i] < min_val) min_val = pixels[i];
|
||||
if (pixels[i] > max_val) max_val = pixels[i];
|
||||
}
|
||||
|
||||
auto intensity_to_rgb565 = [](uint8_t val) -> uint16_t {
|
||||
uint16_t r5 = val >> 3;
|
||||
uint16_t g6 = val >> 2;
|
||||
uint16_t b5 = val >> 3;
|
||||
return static_cast<uint16_t>((r5 << 11) | (g6 << 5) | b5);
|
||||
};
|
||||
|
||||
uint16_t c0_565 = intensity_to_rgb565(max_val);
|
||||
uint16_t c1_565 = intensity_to_rgb565(min_val);
|
||||
uint32_t indices = 0;
|
||||
|
||||
if (max_val > min_val) {
|
||||
// Enforce c0_565 > c1_565 in unsigned 16-bit representation to use 4-color mode
|
||||
if (c0_565 == c1_565) {
|
||||
if ((c0_565 & 0x001F) < 0x001F) {
|
||||
c0_565 += 1;
|
||||
} else {
|
||||
c1_565 -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Interpolated 8-bit intensity values for quantization
|
||||
const int c0 = max_val;
|
||||
const int c1 = min_val;
|
||||
const int c2 = (2 * max_val + min_val) / 3;
|
||||
const int c3 = (max_val + 2 * min_val) / 3;
|
||||
|
||||
// Map each pixel to the nearest palette entry
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
const int p = pixels[i];
|
||||
const int d0 = std::abs(p - c0);
|
||||
const int d1 = std::abs(p - c1);
|
||||
const int d2 = std::abs(p - c2);
|
||||
const int d3 = std::abs(p - c3);
|
||||
|
||||
uint32_t best_idx = 0;
|
||||
int min_d = d0;
|
||||
|
||||
if (d1 < min_d) { min_d = d1; best_idx = 1; }
|
||||
if (d2 < min_d) { min_d = d2; best_idx = 2; }
|
||||
if (d3 < min_d) { min_d = d3; best_idx = 3; }
|
||||
|
||||
indices |= (best_idx << (30 - (2 * i)));
|
||||
}
|
||||
}
|
||||
|
||||
// Account for big endian data expectation
|
||||
dst[0] = static_cast<uint8_t>(c0_565 >> 8);
|
||||
dst[1] = static_cast<uint8_t>(c0_565 & 0xFF);
|
||||
dst[2] = static_cast<uint8_t>(c1_565 >> 8);
|
||||
dst[3] = static_cast<uint8_t>(c1_565 & 0xFF);
|
||||
dst[4] = static_cast<uint8_t>(indices >> 24);
|
||||
dst[5] = static_cast<uint8_t>((indices >> 16) & 0xFF);
|
||||
dst[6] = static_cast<uint8_t>((indices >> 8) & 0xFF);
|
||||
dst[7] = static_cast<uint8_t>(indices & 0xFF);
|
||||
}
|
||||
|
||||
bool convert_i8_to_cmpr(ResTIMG* texHeaderPtr) {
|
||||
|
||||
const uint16_t width = texHeaderPtr->width;
|
||||
const uint16_t height = texHeaderPtr->height;
|
||||
|
||||
if (width % 8 != 0 || height % 8 != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t tilesX = width / 8;
|
||||
const uint32_t blocksY_CMPR = height / 8;
|
||||
|
||||
auto* imageData = JSUConvertOffsetToPtr<uint8_t>(texHeaderPtr, texHeaderPtr->imageOffset);
|
||||
uint8_t* writePtr = imageData;
|
||||
|
||||
// Process image in 8x8 blocks
|
||||
for (uint32_t by = 0; by < blocksY_CMPR; ++by) {
|
||||
for (uint32_t bx = 0; bx < tilesX; ++bx) {
|
||||
// Locate the two stacked 8x4 I8 tiles (32 bytes each) making up the 8x8 block
|
||||
const uint32_t topTileIdx = (2 * by) * tilesX + bx;
|
||||
const uint32_t bottomTileIdx = (2 * by + 1) * tilesX + bx;
|
||||
|
||||
const uint8_t* topTileData = imageData + (topTileIdx * 32);
|
||||
const uint8_t* bottomTileData = imageData + (bottomTileIdx * 32);
|
||||
|
||||
uint8_t subBlockPixels[4][16];
|
||||
|
||||
// Extract Sub-block 0 (Top-Left) and Sub-block 1 (Top-Right)
|
||||
for (int row = 0; row < 4; ++row) {
|
||||
for (int col = 0; col < 4; ++col) {
|
||||
subBlockPixels[0][row * 4 + col] = topTileData[row * 8 + col];
|
||||
subBlockPixels[1][row * 4 + col] = topTileData[row * 8 + col + 4];
|
||||
}
|
||||
}
|
||||
|
||||
// Extract Sub-block 2 (Bottom-Left) and Sub-block 3 (Bottom-Right)
|
||||
for (int row = 0; row < 4; ++row) {
|
||||
for (int col = 0; col < 4; ++col) {
|
||||
subBlockPixels[2][row * 4 + col] = bottomTileData[row * 8 + col];
|
||||
subBlockPixels[3][row * 4 + col] = bottomTileData[row * 8 + col + 4];
|
||||
}
|
||||
}
|
||||
|
||||
// Encode the 4 sub-blocks in CMPR order (32 bytes per 8x8 block)
|
||||
for (const auto& subBlockPixel : subBlockPixels) {
|
||||
encode_cmpr_sub_block(writePtr, subBlockPixel);
|
||||
writePtr += 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update ResTIMG header metadata
|
||||
texHeaderPtr->format = GX_TF_CMPR; // 0x0E
|
||||
texHeaderPtr->colorFormat = 0;
|
||||
texHeaderPtr->numColors = 0;
|
||||
texHeaderPtr->paletteOffset = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void recolor_texture(J3DTextureBlock* tex1Ptr, const char* textureName, GXColor color) {
|
||||
ResTIMG* texHeaderPtr = find_tex_header_in_tex_1_section(tex1Ptr, textureName);
|
||||
if (texHeaderPtr == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (texHeaderPtr->format) {
|
||||
case GX_TF_CMPR:
|
||||
recolor_cmpr_texture(texHeaderPtr, color);
|
||||
break;
|
||||
case GX_TF_RGB5A3:
|
||||
recolor_rgb5a3_texture(texHeaderPtr, color);
|
||||
break;
|
||||
case GX_TF_I8:
|
||||
if (convert_i8_to_cmpr(texHeaderPtr)) {
|
||||
recolor_cmpr_texture(texHeaderPtr, color);
|
||||
} else {
|
||||
mods::log::debug("Could not convert {} from i8 to cmpr", textureName);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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{};
|
||||
ConfigVarHandle hexColor{0};
|
||||
};
|
||||
|
||||
auto& get_cosmetic_overrides() {
|
||||
static std::unordered_map<s32, std::unordered_map<std::string_view, std::list<CosmeticOverride>>> cosmeticOverrides{};
|
||||
if (cosmeticOverrides.empty()) {
|
||||
auto& g_cvars = get_cvars();
|
||||
// Ordon Clothes Link Model
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Bmdl.arc")]["bmwr/al_swb.bmd"] = {
|
||||
{.textures = {"al_SWB"}, .hexColor = g_cvars.woodenSwordColor},
|
||||
};
|
||||
// Main Link Model
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Kmdl.arc")]["bmwr/al_head.bmd"] = {
|
||||
{.textures = {"al_cap"}, .hexColor = g_cvars.herosTunicCapColor},
|
||||
{.textures = {"al_hair"}, .hexColor = g_cvars.linkHairColor},
|
||||
};
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Kmdl.arc")]["bmwr/al.bmd"] = {
|
||||
{.textures = {"al_upbody"}, .hexColor = g_cvars.herosTunicTorsoColor},
|
||||
{.textures = {"al_lowbody"}, .hexColor = g_cvars.herosTunicSkirtColor},
|
||||
};
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Kmdl.arc")]["bmwr/al_bootsh.bmd"] = {
|
||||
{.textures = {"al_bootsH"}, .hexColor = g_cvars.ironBootsColor},
|
||||
};
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Kmdl.arc")]["bmwr/al_swb.bmd"] = {
|
||||
{.textures = {"al_SWB"}, .hexColor = g_cvars.woodenSwordColor},
|
||||
};
|
||||
// Zora Armor Link Model
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Zmdl.arc")]["bmwr/zl_head.bmd"] = {
|
||||
{.textures = {"zl_cap"}, .hexColor = g_cvars.zoraArmorCapColor},
|
||||
{.textures = {"zl_helmet"}, .hexColor = g_cvars.zoraArmorHelmetColor},
|
||||
};
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Zmdl.arc")]["bmwr/zl.bmd"] = {
|
||||
{.textures = {"zl_armor", "zl_armL"}, .hexColor = g_cvars.zoraArmorTorsoColor},
|
||||
{.textures = {"zl_body"}, .hexColor = g_cvars.zoraArmorScalesColor},
|
||||
{.textures = {"zl_boots"}, .hexColor = g_cvars.zoraArmorFlippersColor},
|
||||
};
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Zmdl.arc")]["bmwr/al_bootsh.bmd"] = {
|
||||
{.textures = {"al_bootsH"}, .hexColor = g_cvars.ironBootsColor},
|
||||
};
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Zmdl.arc")]["bmwr/al_swb.bmd"] = {
|
||||
{.textures = {"al_SWB"}, .hexColor = g_cvars.woodenSwordColor},
|
||||
};
|
||||
// Zora Armor field model
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/O_gD_zora.arc")]["bmdr/o_gd_al_zora.bmd"] = {
|
||||
{.textures = {"zl_armor"}, .hexColor = g_cvars.zoraArmorTorsoColor},
|
||||
{.textures = {"zl_body"}, .hexColor = g_cvars.zoraArmorScalesColor},
|
||||
{.textures = {"zl_helmet"}, .hexColor = g_cvars.zoraArmorHelmetColor},
|
||||
{.textures = {"zl_cap"}, .hexColor = g_cvars.zoraArmorCapColor},
|
||||
};
|
||||
// Magic Armor Model
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Mmdl.arc")]["bmwr/al_bootsh.bmd"] = {
|
||||
{.textures = {"al_bootsH"}, .hexColor = g_cvars.ironBootsColor},
|
||||
};
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Mmdl.arc")]["bmwr/al_swb.bmd"] = {
|
||||
{.textures = {"al_SWB"}, .hexColor = g_cvars.woodenSwordColor},
|
||||
};
|
||||
// Ordon Sword Colors
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Alink.arc")]["bmwr/al_swa.bmd"] = {
|
||||
{.textures = {"al_SWA", "hilight01"}, .hexColor = g_cvars.ordonSwordBladeColor},
|
||||
{.textures = {"al_SWgripA"}, .hexColor = g_cvars.ordonSwordHandleColor},
|
||||
};
|
||||
// Master Sword Colors
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Alink.arc")]["bmwe/al_swm.bmd"] = {
|
||||
{.textures = {"al_SWM"}, .hexColor = g_cvars.msBladeColor},
|
||||
{.textures = {"al_SWgripM"}, .hexColor = g_cvars.msHandleColor},
|
||||
};
|
||||
// Boomerang Color
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Alink.arc")]["bmdr/al_boom.bmd"] = {
|
||||
{.textures = {"L_al_boom00"}, .hexColor = g_cvars.boomerangColor},
|
||||
};
|
||||
// Spinner Color
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Alink.arc")]["bmdr/al_sp.bmd"] = {
|
||||
{.textures = {"al_SP"}, .hexColor = g_cvars.spinnerColor},
|
||||
};
|
||||
// Epona Color
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Horse.arc")]["bmdr/hs.bmd"] = {
|
||||
{.textures = {"hs_body", "hs_eye.1", "hs_eye.2", "hs_eye.3"}, .hexColor = g_cvars.eponaColor},
|
||||
};
|
||||
// Wolf Link Color
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/Wmdl.arc")]["bmwr/wl.bmd"] = {
|
||||
{.textures = {"wl_body", "wl_eye.1", "wl_eye.2", "wl_eye.3", "wl_eye.4", "wl_eye.5"}, .hexColor = g_cvars.wolfLinkColor},
|
||||
};
|
||||
// Wooden Sword Item Model
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/O_gD_SWB.arc")]["bmdr/o_gd_al_swb.bmd"] = {
|
||||
{.textures = {"al_SWB"}, .hexColor = g_cvars.woodenSwordColor},
|
||||
};
|
||||
// Boomerang Item Model
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/O_gD_boom.arc")]["bmdr/o_gd_boom.bmd"] = {
|
||||
{.textures = {"L_al_boom00"}, .hexColor = g_cvars.boomerangColor},
|
||||
};
|
||||
// Iron Boots Item Model
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/O_gD_boot.arc")]["bmwr/o_gd_al_bootsh.bmd"] = {
|
||||
{.textures = {"al_bootsH"}, .hexColor = g_cvars.ironBootsColor},
|
||||
};
|
||||
// Spinner Item Model
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/O_gD_SP.arc")]["bmdr/o_gd_al_sp.bmd"] = {
|
||||
{.textures = {"al_SP"}, .hexColor = g_cvars.spinnerColor},
|
||||
};
|
||||
// Gale Boomerang for Ook
|
||||
cosmeticOverrides[DVDConvertPathToEntrynum("/res/Object/E_mk.arc")]["bmdr/bm.bmd"] = {
|
||||
{.textures = {"L_al_boom00", "bm_boom"}, .hexColor = g_cvars.boomerangColor},
|
||||
};
|
||||
}
|
||||
return cosmeticOverrides;
|
||||
}
|
||||
|
||||
s32 get_entry_number(mDoDvdThd_mountArchive_c* mountArchive) {
|
||||
return mountArchive->mEntryNumber;
|
||||
}
|
||||
|
||||
void handle_texture_overrides_on_load(mDoDvdThd_mountArchive_c* mountArchive) {
|
||||
|
||||
auto entryNum = get_entry_number(mountArchive);
|
||||
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 = get_str_option(hexColorVar, "");
|
||||
if (!is_valid_hex_color_str(hexColorStr)) {
|
||||
mods::log::debug("Invalid Hex Str {}", hexColorStr);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto color = hex_color_str_to_gx_color(hexColorStr);
|
||||
if (tex1Addr) {
|
||||
for (const auto& textureName : textures) {
|
||||
recolor_texture(tex1Addr, textureName.data(), color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
#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;
|
||||
|
||||
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);
|
||||
@@ -0,0 +1,80 @@
|
||||
*, *:before, *:after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: visible;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
console {
|
||||
position: absolute;
|
||||
bottom: 10dp;
|
||||
left: 10dp;
|
||||
width: 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: rgba(0, 0, 0, 60%);
|
||||
pointer-events: auto;
|
||||
font-family: "Noto Mono";
|
||||
font-size: 14dp;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
output {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
max-height: 480dp;
|
||||
padding: 4dp 8dp;
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
output[open] {
|
||||
height: 480dp;
|
||||
max-height: 480dp;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
console:not([open]) {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
line {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
line.cmd {
|
||||
color: #FFD966;
|
||||
}
|
||||
|
||||
line.copyable {
|
||||
color: #80C8FF;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
line.copyable:hover {
|
||||
color: #FFFFFF;
|
||||
background-color: rgba(255, 255, 255, 12%);
|
||||
}
|
||||
|
||||
console input {
|
||||
display: none;
|
||||
width: 100%;
|
||||
background-color: rgba(0, 0, 0, 40%);
|
||||
border: 0dp;
|
||||
border-top: 1dp rgba(255, 255, 255, 20%);
|
||||
color: #FFFFFF;
|
||||
font-family: "Noto Mono";
|
||||
font-size: 14dp;
|
||||
padding: 4dp 8dp;
|
||||
}
|
||||
|
||||
console[open] input {
|
||||
display: block;
|
||||
}
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/camera_operators.hpp"
|
||||
#include "dusk/commands.hpp"
|
||||
#include "dusk/frame_interpolation.h"
|
||||
#include "dusk/logging.h"
|
||||
#include "dusk/action_bindings.h"
|
||||
@@ -1054,6 +1055,11 @@ void dCamera_c::debugDrawInit() {
|
||||
bool dCamera_c::Run() {
|
||||
#if TARGET_PC
|
||||
ResetView();
|
||||
if (dusk::isCameraDetached()) {
|
||||
mFrameCounter++;
|
||||
mTicks++;
|
||||
return true;
|
||||
}
|
||||
if (executeDebugFlyCam() || dusk::mods::camera_run_operators(this)) {
|
||||
mFrameCounter++;
|
||||
mTicks++;
|
||||
@@ -11123,6 +11129,9 @@ camera_class* dCam_getCamera() {
|
||||
|
||||
dCamera_c* dCam_getBody() {
|
||||
camera_process_class* camera = (camera_process_class*)dCam_getCamera();
|
||||
#if TARGET_PC
|
||||
if (camera == nullptr) { return nullptr; }
|
||||
#endif
|
||||
return &camera->mCamera;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
#include <typeindex>
|
||||
#include "JSystem/JKernel/JKRHeap.h"
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "client/TracyScoped.hpp"
|
||||
#include "tracy/Tracy.hpp"
|
||||
#include "dusk/frame_interpolation.h"
|
||||
#include "helpers/gx_helper.h"
|
||||
#include "dusk/logging.h"
|
||||
|
||||
+7
-40
@@ -23,7 +23,6 @@
|
||||
#include "m_Do/m_Do_graphic.h"
|
||||
#include <cstring>
|
||||
|
||||
#include "dusk/utilities.hpp"
|
||||
#include "dusk/version.hpp"
|
||||
|
||||
#if TARGET_PC
|
||||
@@ -4111,43 +4110,11 @@ bool dFile_select_c::yesnoWakuAlpahAnm(u8 param_1) {
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
|
||||
static dusk::utils::PaneCache mSelDtPanes[] = {
|
||||
{MULTI_CHAR('tate_n0'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('tate_n1'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('ken_n0'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('ken_n1'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('fuku_n0'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('fuku_n1'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('fuku_n2'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('gray_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('b_base'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('b_base1'), 0.0f, 0.0f, false},
|
||||
};
|
||||
|
||||
static dusk::utils::PaneCache fileSelPanes[] = {
|
||||
{MULTI_CHAR('w_uzu00'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('w_uzu01'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('w_uzu02'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('w_uzu03'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('w_uzu04'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('w_uzu05'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('w_uzu06'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('w_uzu07'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('w_uzu08'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('w_uzu09'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('w_er_msg'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('w_er_msE'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('w_er_msR'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('er_for0'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('er_for1'), 0.0f, 0.0f, false},
|
||||
};
|
||||
|
||||
void dFile_select_c::fileSelectWide() {
|
||||
static bool cachedPanes = false;
|
||||
// Get pre-scale values for each pane
|
||||
if (!cachedPanes) {
|
||||
for (dusk::utils::PaneCache& entry : mSelDtPanes) {
|
||||
for (PaneCache& entry : mSelDtPanes) {
|
||||
J2DPane* pane = mSelDt.ScrDt->search(entry.tag);
|
||||
if (!entry.cached) {
|
||||
entry.origTransX = pane->getTranslateX();
|
||||
@@ -4155,7 +4122,7 @@ void dFile_select_c::fileSelectWide() {
|
||||
entry.cached = true;
|
||||
}
|
||||
}
|
||||
for (dusk::utils::PaneCache& entry : fileSelPanes) {
|
||||
for (PaneCache& entry : fileSelPanes) {
|
||||
J2DPane* pane = fileSel.Scr->search(entry.tag);
|
||||
if (!entry.cached) {
|
||||
entry.origTransX = pane->getTranslateX();
|
||||
@@ -4169,13 +4136,13 @@ void dFile_select_c::fileSelectWide() {
|
||||
// Reset all panes
|
||||
mSelDt.ScrDt->scale(1.0f, 1.0f);
|
||||
mSelDt.ScrDt->translate(0.0f, 0.0f);
|
||||
for (dusk::utils::PaneCache& entry : mSelDtPanes) {
|
||||
for (PaneCache& entry : mSelDtPanes) {
|
||||
J2DPane* pane = mSelDt.ScrDt->search(entry.tag);
|
||||
pane->setBasePosition(J2DBasePosition_4);
|
||||
pane->scale(1.0f, 1.0f);
|
||||
pane->translate(entry.origTransX, entry.origTransY);
|
||||
}
|
||||
for (dusk::utils::PaneCache& entry : fileSelPanes) {
|
||||
for (PaneCache& entry : fileSelPanes) {
|
||||
J2DPane* pane = fileSel.Scr->search(entry.tag);
|
||||
pane->setBasePosition(J2DBasePosition_4);
|
||||
pane->scale(1.0f, 1.0f);
|
||||
@@ -4247,7 +4214,7 @@ void dFile_select_c::fileSelectWide() {
|
||||
mSelDt.ScrDt->search(MULTI_CHAR('fuku_n2'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
|
||||
|
||||
// Spirals & Memory Card Text
|
||||
for (dusk::utils::PaneCache& entry : fileSelPanes) {
|
||||
for (PaneCache& entry : fileSelPanes) {
|
||||
J2DPane* pane = fileSel.Scr->search(entry.tag);
|
||||
pane->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
|
||||
}
|
||||
@@ -4273,7 +4240,7 @@ void dFile_select_c::fileSelectWide() {
|
||||
const f32 wideShiftFactor = mSelDt.ScrDt->search(MULTI_CHAR('gray_n'))->getTranslateX() * (wideScaleFactor - mDoGph_gInf_c::hudAspectScaleDown);
|
||||
const f32 ultraShiftFactor = mSelDt.ScrDt->search(MULTI_CHAR('gray_n'))->getTranslateX() * (ultraScaleFactor - mDoGph_gInf_c::hudAspectScaleDown);
|
||||
|
||||
for (dusk::utils::PaneCache& entry : mSelDtPanes) {
|
||||
for (PaneCache& entry : mSelDtPanes) {
|
||||
const size_t index = &entry - mSelDtPanes;
|
||||
J2DPane* pane = mSelDt.ScrDt->search(entry.tag);
|
||||
pane->setBasePosition(J2DBasePosition_0);
|
||||
@@ -4341,7 +4308,7 @@ void dFile_select_c::fileSelectWide() {
|
||||
}
|
||||
|
||||
// Spirals & Memory Card Text
|
||||
for (dusk::utils::PaneCache& entry : fileSelPanes) {
|
||||
for (PaneCache& entry : fileSelPanes) {
|
||||
J2DPane* pane = fileSel.Scr->search(entry.tag);
|
||||
pane->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
|
||||
}
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/menu_pointer.h"
|
||||
#include "dusk/utilities.hpp"
|
||||
#endif
|
||||
|
||||
typedef void (dMenu_Collect2D_c::*initFunc)();
|
||||
@@ -102,44 +101,11 @@ dMenu_Collect2D_c::~dMenu_Collect2D_c() {
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
static dusk::utils::PaneCache mpScreenPanes[] = {
|
||||
{MULTI_CHAR('sa_tex_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('op_tex_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('heart_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('wolf_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('item_0_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('item_1_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('item_2_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('fish_3_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('lett_4_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('maki_5_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('fuku_n0'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('fuku_n1'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('fuku_n2'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('tate_n0'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('tate_n1'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('ken_n0'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('ken_n1'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('kabu_6n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('t_t00'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('f_t00'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('itemn_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('infotxtn'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('sa_op_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('title_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('menu_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('w_er_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('center_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('info_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('lavel_n'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('modelbgn'), 0.0f, 0.0f, false},
|
||||
};
|
||||
|
||||
void dMenu_Collect2D_c::menuCollectWide() {
|
||||
static bool cachedPanes = false;
|
||||
// Get pre-scale values for each pane
|
||||
if (!cachedPanes) {
|
||||
for (dusk::utils::PaneCache& entry : mpScreenPanes) {
|
||||
for (PaneCache& entry : mpScreenPanes) {
|
||||
J2DPane* pane = mpScreen->search(entry.tag);
|
||||
if (!entry.cached) {
|
||||
entry.origTransX = pane->getTranslateX();
|
||||
@@ -153,7 +119,7 @@ void dMenu_Collect2D_c::menuCollectWide() {
|
||||
// Reset all panes
|
||||
mpScreen->scale(1.0f, 1.0f);
|
||||
mpScreen->translate(0.0f, 0.0f);
|
||||
for (dusk::utils::PaneCache& entry : mpScreenPanes) {
|
||||
for (PaneCache& entry : mpScreenPanes) {
|
||||
J2DPane* pane = mpScreen->search(entry.tag);
|
||||
pane->scale(1.0f, 1.0f);
|
||||
pane->translate(entry.origTransX, entry.origTransY);
|
||||
|
||||
+9
-30
@@ -10,7 +10,6 @@
|
||||
#include <cstring>
|
||||
|
||||
#include "JSystem/J2DGraph/J2DAnmLoader.h"
|
||||
#include "dusk/utilities.hpp"
|
||||
#include "dusk/version.hpp"
|
||||
#include "f_op/f_op_msg_mng.h"
|
||||
|
||||
@@ -1513,31 +1512,11 @@ void dName_c::selectCursorPosSet(int row) {
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
|
||||
static dusk::utils::PaneCache l_tagName[] = {
|
||||
{MULTI_CHAR('m_00_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_00_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_00_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_00_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_00_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_01_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_01_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_01_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_01_3'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('m_01_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_02_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_02_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_02_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_02_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_02_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m03_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m03_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m03_2'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('m03_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m03_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_04_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_04_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_04_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_04_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_04_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_05_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_05_1'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('m_05_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_05_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_05_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_06_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_06_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_06_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_06_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_06_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_07_0'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('m_07_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_07_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_07_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_07_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_08_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_08_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_08_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_08_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_08_4'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('m_09_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_09_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_09_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_09_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_09_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_10_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_10_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_10_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_10_3'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('m_10_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_11_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_11_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_11_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_11_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_11_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m12_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m12_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m12_2'), 0.0f, 0.0f, false},
|
||||
{MULTI_CHAR('m12_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m12_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('p_end_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('p_end_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('p_end_0'), 0.0f, 0.0f, false},
|
||||
};
|
||||
|
||||
static dusk::utils::PaneCache l_nameTagName[] = {
|
||||
{MULTI_CHAR('name_00'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_01'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_02'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_03'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_04'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_05'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_06'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_07'), 0.0f, 0.0f, false},
|
||||
};
|
||||
|
||||
static dusk::utils::PaneCache l_nameCurTagName[] = {
|
||||
{MULTI_CHAR('s__n_00'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_01'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_02'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_03'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_04'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_05'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_06'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_07'), 0.0f, 0.0f, false},
|
||||
};
|
||||
|
||||
void dName_c::nameWide() {
|
||||
static bool cachedPanes = false;
|
||||
// Get pre-scale values for each pane
|
||||
if (!cachedPanes) {
|
||||
for (dusk::utils::PaneCache& entry : l_tagName) {
|
||||
for (PaneCache& entry : l_tagName) {
|
||||
J2DPane* pane = nameIn.NameInScr->search(entry.tag);
|
||||
if (!entry.cached) {
|
||||
entry.origTransX = pane->getTranslateX();
|
||||
@@ -1545,7 +1524,7 @@ void dName_c::nameWide() {
|
||||
entry.cached = true;
|
||||
}
|
||||
}
|
||||
for (dusk::utils::PaneCache& entry : l_nameTagName) {
|
||||
for (PaneCache& entry : l_nameTagName) {
|
||||
J2DPane* pane = nameIn.NameInScr->search(entry.tag);
|
||||
if (!entry.cached) {
|
||||
entry.origTransX = pane->getTranslateX();
|
||||
@@ -1553,7 +1532,7 @@ void dName_c::nameWide() {
|
||||
entry.cached = true;
|
||||
}
|
||||
}
|
||||
for (dusk::utils::PaneCache& entry : l_nameCurTagName) {
|
||||
for (PaneCache& entry : l_nameCurTagName) {
|
||||
J2DPane* pane = nameIn.NameInScr->search(entry.tag);
|
||||
if (!entry.cached) {
|
||||
entry.origTransX = pane->getTranslateX();
|
||||
@@ -1567,19 +1546,19 @@ void dName_c::nameWide() {
|
||||
// Reset all panes
|
||||
nameIn.NameInScr->scale(1.0f, 1.0f);
|
||||
nameIn.NameInScr->translate(0.0f, 0.0f);
|
||||
for (dusk::utils::PaneCache& entry : l_tagName) {
|
||||
for (PaneCache& entry : l_tagName) {
|
||||
J2DPane* pane = nameIn.NameInScr->search(entry.tag);
|
||||
pane->setBasePosition(J2DBasePosition_4);
|
||||
pane->scale(1.0f, 1.0f);
|
||||
pane->translate(entry.origTransX, entry.origTransY);
|
||||
}
|
||||
for (dusk::utils::PaneCache& entry : l_nameTagName) {
|
||||
for (PaneCache& entry : l_nameTagName) {
|
||||
J2DPane* pane = nameIn.NameInScr->search(entry.tag);
|
||||
pane->setBasePosition(J2DBasePosition_4);
|
||||
pane->scale(1.0f, 1.0f);
|
||||
pane->translate(entry.origTransX, entry.origTransY);
|
||||
}
|
||||
for (dusk::utils::PaneCache& entry : l_nameCurTagName) {
|
||||
for (PaneCache& entry : l_nameCurTagName) {
|
||||
J2DPane* pane = nameIn.NameInScr->search(entry.tag);
|
||||
pane->setBasePosition(J2DBasePosition_4);
|
||||
pane->scale(1.0f, 1.0f);
|
||||
@@ -1595,17 +1574,17 @@ void dName_c::nameWide() {
|
||||
break;
|
||||
default: // Wii and Dusklight
|
||||
// List of Characters Box
|
||||
for (dusk::utils::PaneCache& entry : l_tagName) {
|
||||
for (PaneCache& entry : l_tagName) {
|
||||
J2DPane* pane = nameIn.NameInScr->search(entry.tag);
|
||||
pane->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
|
||||
}
|
||||
// Letters being typed
|
||||
for (dusk::utils::PaneCache& entry : l_nameTagName) {
|
||||
for (PaneCache& entry : l_nameTagName) {
|
||||
J2DPane* pane = nameIn.NameInScr->search(entry.tag);
|
||||
pane->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
|
||||
}
|
||||
// Underscores when typing below letters
|
||||
for (dusk::utils::PaneCache& entry : l_nameCurTagName) {
|
||||
for (PaneCache& entry : l_nameCurTagName) {
|
||||
J2DPane* pane = nameIn.NameInScr->search(entry.tag);
|
||||
pane->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/autosave.h"
|
||||
#include "dusk/d_trigger_view.h"
|
||||
#include "dusk/memory.h"
|
||||
#include "dusk/ui/ui.hpp"
|
||||
#endif
|
||||
@@ -675,6 +676,10 @@ static int dScnPly_Draw(dScnPly_c* i_this) {
|
||||
dComIfG_Ccsp()->Draw();
|
||||
dComIfG_Bgsp().Draw();
|
||||
|
||||
#if TARGET_PC
|
||||
dusk::TriggerView::execute();
|
||||
#endif
|
||||
|
||||
#if DEBUG
|
||||
dPath_Draw();
|
||||
#endif
|
||||
|
||||
@@ -1537,6 +1537,27 @@ dStage_objectNameInf* dStage_searchName(char const* objName) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
dStage_objectNameInf* dStage_searchNameCI(char const* objName) {
|
||||
dStage_objectNameInf* obj = l_objectName;
|
||||
|
||||
for (u32 i = 0; i < ARRAY_SIZEU(l_objectName); i++) {
|
||||
const char* a = obj->name;
|
||||
const char* b = objName;
|
||||
while (*a && *b && tolower((unsigned char)*a) == tolower((unsigned char)*b)) {
|
||||
++a;
|
||||
++b;
|
||||
}
|
||||
if (*a == '\0' && *b == '\0') {
|
||||
return obj;
|
||||
}
|
||||
obj++;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
const char* dStage_getName(s16 procName, s8 argument) {
|
||||
static char tmp_name[dStage_NAME_LENGTH];
|
||||
|
||||
|
||||
@@ -0,0 +1,880 @@
|
||||
#include "commands.hpp"
|
||||
|
||||
#include "JSystem/JUtility/JUTGamePad.h"
|
||||
#include "SSystem/SComponent/c_math.h"
|
||||
#include "SSystem/SComponent/c_sxyz.h"
|
||||
#include "SSystem/SComponent/c_xyz.h"
|
||||
#include "c/c_damagereaction.h"
|
||||
#include "d/actor/d_a_alink.h"
|
||||
#include "d/d_com_inf_actor.h"
|
||||
#include "d/d_com_inf_game.h"
|
||||
#include "d/d_camera.h"
|
||||
#include "d/d_kankyo.h"
|
||||
#include "d/d_stage.h"
|
||||
#include "dusk/game_clock.h"
|
||||
#include "f_op/f_op_actor_mng.h"
|
||||
#include "f_pc/f_pc_layer.h"
|
||||
#include "f_pc/f_pc_layer_iter.h"
|
||||
#include "f_pc/f_pc_manager.h"
|
||||
#include "f_pc/f_pc_node.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "fmt/format.h"
|
||||
|
||||
namespace dusk {
|
||||
namespace {
|
||||
|
||||
static constexpr int kMaxHistory = 64;
|
||||
|
||||
static std::vector<std::string> SplitArgs(std::string_view input) {
|
||||
std::vector<std::string> args;
|
||||
std::string cur;
|
||||
for (char c : input) {
|
||||
if (c == ' ' || c == '\t') {
|
||||
if (!cur.empty()) {
|
||||
args.push_back(std::move(cur));
|
||||
cur.clear();
|
||||
}
|
||||
} else {
|
||||
cur += c;
|
||||
}
|
||||
}
|
||||
if (!cur.empty()) {
|
||||
args.push_back(std::move(cur));
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
static std::optional<long> ParseLong(const std::string& s) {
|
||||
if (s.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
char* end = nullptr;
|
||||
const long v = std::strtol(s.c_str(), &end, 0);
|
||||
return (end != s.c_str() && *end == '\0') ? std::optional<long>{v} : std::nullopt;
|
||||
}
|
||||
|
||||
static std::optional<float> ParseFloat(const std::string& s) {
|
||||
if (s.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
char* end = nullptr;
|
||||
const float v = std::strtof(s.c_str(), &end);
|
||||
return (end != s.c_str() && *end == '\0') ? std::optional<float>{v} : std::nullopt;
|
||||
}
|
||||
|
||||
static const char* ActorShortName(s16 profname) {
|
||||
const char* n = dStage_getName(profname, -1);
|
||||
return n ? n : "?";
|
||||
}
|
||||
|
||||
// Resolves @found / @link / @<id> to a process pointer, outputting an error on failure
|
||||
static base_process_class* ParseProcArg(
|
||||
const std::string& s, unsigned int foundProcId, const CommandOutput& output) {
|
||||
if (s.empty() || s[0] != '@') {
|
||||
output("Error: proc reference must start with @");
|
||||
return nullptr;
|
||||
}
|
||||
const std::string inner = s.substr(1);
|
||||
unsigned int id;
|
||||
if (inner == "found") {
|
||||
if (foundProcId == 0) {
|
||||
output("Error: @found is not set");
|
||||
return nullptr;
|
||||
}
|
||||
id = foundProcId;
|
||||
} else if (inner == "link") {
|
||||
auto* player = dComIfGp_getPlayer(0);
|
||||
if (player == nullptr) {
|
||||
output("Error: player not available");
|
||||
return nullptr;
|
||||
}
|
||||
id = (unsigned int)fpcM_GetID(player);
|
||||
} else {
|
||||
const auto v = ParseLong(inner);
|
||||
if (!v) {
|
||||
output("Error: invalid proc ID");
|
||||
return nullptr;
|
||||
}
|
||||
id = (unsigned int)*v;
|
||||
}
|
||||
auto* proc = fpcM_SearchByID(id);
|
||||
if (proc == nullptr) {
|
||||
output(fmt::format(FMT_STRING("Error: proc {} not found"), id));
|
||||
return nullptr;
|
||||
}
|
||||
return proc;
|
||||
}
|
||||
|
||||
// Like ParseProcArg but ensures the proc is an actor
|
||||
static fopAc_ac_c* ParseActorArg(
|
||||
const std::string& s, unsigned int foundProcId, const CommandOutput& output) {
|
||||
auto* proc = ParseProcArg(s, foundProcId, output);
|
||||
if (proc == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!fopAcM_IsActor(proc)) {
|
||||
output("Error: proc is not an actor");
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<fopAc_ac_c*>(proc);
|
||||
}
|
||||
|
||||
static std::optional<s16> ParseActorId(const std::string& s) {
|
||||
if (const auto v = ParseLong(s)) {
|
||||
return (s16)*v;
|
||||
}
|
||||
const auto* entry = dStage_searchNameCI(s.c_str());
|
||||
return entry ? std::optional<s16>{entry->procname} : std::nullopt;
|
||||
}
|
||||
|
||||
static std::optional<cXyz> ParseXYZ(const std::vector<std::string>& args, size_t i) {
|
||||
const auto x = ParseFloat(args[i]), y = ParseFloat(args[i + 1]), z = ParseFloat(args[i + 2]);
|
||||
return (x && y && z) ? std::optional<cXyz>{cXyz(*x, *y, *z)} : std::nullopt;
|
||||
}
|
||||
|
||||
static bool TryAngle(
|
||||
s16& out, const std::vector<std::string>& args, size_t i, const CommandOutput& output) {
|
||||
if (args.size() <= i) {
|
||||
return true;
|
||||
}
|
||||
const auto a = ParseLong(args[i]);
|
||||
if (!a) {
|
||||
output("Error: invalid angle");
|
||||
return false;
|
||||
}
|
||||
out = (s16)*a;
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::string actorLine(const base_process_class* proc) {
|
||||
const auto* ac = static_cast<const fopAc_ac_c*>(proc);
|
||||
return fmt::format(FMT_STRING("procId={} 0x{:04X} ({}) @ ({:.2f}, {:.2f}, {:.2f}) room={}"),
|
||||
(unsigned int)proc->id, (unsigned int)(u16)proc->profname, ActorShortName(proc->profname),
|
||||
ac->current.pos.x, ac->current.pos.y, ac->current.pos.z, (int)ac->current.roomNo);
|
||||
}
|
||||
|
||||
static void recurseLayer(void* p, int (*callback)(void*, void*), void* ctx) {
|
||||
auto* proc = static_cast<base_process_class*>(p);
|
||||
if (fpcBs_Is_JustOfType(g_fpcNd_type, proc->subtype)) {
|
||||
fpcLyIt_OnlyHere(&static_cast<process_node_class*>(p)->layer, callback, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
struct ListContext {
|
||||
s16 targetId;
|
||||
std::vector<std::string>* output;
|
||||
};
|
||||
struct FindContext {
|
||||
s16 targetId;
|
||||
std::vector<base_process_class*> matches;
|
||||
};
|
||||
|
||||
static int ListActorCallback(void* p, void* ctx) {
|
||||
auto* proc = static_cast<base_process_class*>(p);
|
||||
auto* context = static_cast<ListContext*>(ctx);
|
||||
if (fopAcM_IsActor(proc) && (context->targetId < 0 || proc->profname == context->targetId)) {
|
||||
context->output->push_back(" " + actorLine(proc));
|
||||
}
|
||||
recurseLayer(p, ListActorCallback, ctx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int FindActorCallback(void* p, void* ctx) {
|
||||
auto* proc = static_cast<base_process_class*>(p);
|
||||
auto* context = static_cast<FindContext*>(ctx);
|
||||
if (fopAcM_IsActor(proc) && proc->profname == context->targetId) {
|
||||
context->matches.push_back(proc);
|
||||
}
|
||||
recurseLayer(p, FindActorCallback, ctx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct CameraFlyState {
|
||||
bool active = false;
|
||||
cXyz startEye;
|
||||
cXyz startCenter;
|
||||
cXyz endEye;
|
||||
cXyz endCenter;
|
||||
float duration = 0.0f;
|
||||
float elapsed = 0.0f;
|
||||
};
|
||||
CameraFlyState s_cameraFly;
|
||||
|
||||
static dCamera_c* getCamera() { return dCam_getBody(); }
|
||||
|
||||
static cXyz anglesToDir(s16 h, s16 v) {
|
||||
return cXyz(cM_scos(v) * cM_ssin(h), cM_ssin(v), cM_scos(v) * cM_scos(h));
|
||||
}
|
||||
|
||||
static void cameraToAngles(dCamera_c* cam, s16& h, s16& v) {
|
||||
const cXyz eye = cam->iEye();
|
||||
const cXyz ctr = cam->iCenter();
|
||||
const float dx = ctr.x - eye.x;
|
||||
const float dy = ctr.y - eye.y;
|
||||
const float dz = ctr.z - eye.z;
|
||||
const float hDist = sqrtf(dx * dx + dz * dz);
|
||||
h = cM_atan2s(dx, dz);
|
||||
v = cM_atan2s(dy, hDist);
|
||||
}
|
||||
|
||||
static float smoothstep(float t) {
|
||||
t = std::clamp(t, 0.0f, 1.0f);
|
||||
return t * t * (3.0f - 2.0f * t);
|
||||
}
|
||||
|
||||
static constexpr float kCamTargetDist = 100.0f;
|
||||
|
||||
} // namespace
|
||||
|
||||
bool isCameraDetached() { return s_cameraFly.active; }
|
||||
|
||||
void processCameraCommands() {
|
||||
if (!s_cameraFly.active) { return; }
|
||||
dCamera_c* cam = getCamera();
|
||||
if (cam == nullptr) { s_cameraFly.active = false; return; }
|
||||
|
||||
cXyz eye, center;
|
||||
|
||||
if (s_cameraFly.duration <= 0.0f) {
|
||||
eye = s_cameraFly.endEye;
|
||||
center = s_cameraFly.endCenter;
|
||||
} else {
|
||||
s_cameraFly.elapsed += dusk::game_clock::sim_pace();
|
||||
const float t = smoothstep(s_cameraFly.elapsed / s_cameraFly.duration);
|
||||
eye = s_cameraFly.startEye + (s_cameraFly.endEye - s_cameraFly.startEye) * t;
|
||||
center = s_cameraFly.startCenter + (s_cameraFly.endCenter - s_cameraFly.startCenter) * t;
|
||||
|
||||
if (s_cameraFly.elapsed >= s_cameraFly.duration) {
|
||||
eye = s_cameraFly.endEye;
|
||||
center = s_cameraFly.endCenter;
|
||||
s_cameraFly.startEye = s_cameraFly.endEye;
|
||||
s_cameraFly.startCenter = s_cameraFly.endCenter;
|
||||
s_cameraFly.duration = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
cam->Reset(center, eye);
|
||||
cam->mDebugFlyCam.initialized = false;
|
||||
}
|
||||
|
||||
void runCommand(std::string_view cmdLine, CommandState& state, const CommandOutput& output) {
|
||||
if (state.echoEnabled) {
|
||||
output(fmt::format(FMT_STRING("> {}"), cmdLine));
|
||||
}
|
||||
|
||||
if (!cmdLine.empty()) {
|
||||
if (state.history.empty() || state.history.back() != cmdLine) {
|
||||
state.history.push_back(std::string(cmdLine));
|
||||
if ((int)state.history.size() > kMaxHistory) {
|
||||
state.history.erase(state.history.begin());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto args = SplitArgs(cmdLine);
|
||||
if (args.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto& cmd = args[0];
|
||||
|
||||
auto requirePlayer = [&]() -> daAlink_c* {
|
||||
auto* p = (daAlink_c*)dComIfGp_getPlayer(0);
|
||||
if (p == nullptr) {
|
||||
output("Error: player not available");
|
||||
}
|
||||
return p;
|
||||
};
|
||||
|
||||
if (cmd == "tp") {
|
||||
auto* player = requirePlayer();
|
||||
if (player == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.size() >= 2 && args[1].starts_with('@')) {
|
||||
auto* ac = ParseActorArg(args[1], state.foundProcId, output);
|
||||
if (ac == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.size() >= 3 && args[2].starts_with('@')) {
|
||||
auto* destAc = ParseActorArg(args[2], state.foundProcId, output);
|
||||
if (destAc == nullptr) {
|
||||
return;
|
||||
}
|
||||
const cXyz destPos = destAc->current.pos;
|
||||
ac->current.pos = destPos;
|
||||
output(fmt::format(FMT_STRING("Moved actor {} to ({:.2f}, {:.2f}, {:.2f})"), ac->id,
|
||||
destPos.x, destPos.y, destPos.z));
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.size() >= 5) {
|
||||
const auto pos = ParseXYZ(args, 2);
|
||||
if (!pos) {
|
||||
output("Error: invalid coordinates");
|
||||
return;
|
||||
}
|
||||
ac->current.pos = *pos;
|
||||
if (!TryAngle(ac->shape_angle.y, args, 5, output)) {
|
||||
return;
|
||||
}
|
||||
output(fmt::format(FMT_STRING("Moved actor {} to ({:.2f}, {:.2f}, {:.2f})"), ac->id,
|
||||
pos->x, pos->y, pos->z));
|
||||
return;
|
||||
}
|
||||
|
||||
player->current.pos = ac->current.pos;
|
||||
output(fmt::format(FMT_STRING("Teleported to actor {} ({:.2f}, {:.2f}, {:.2f})"),
|
||||
ac->id, ac->current.pos.x, ac->current.pos.y, ac->current.pos.z));
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.size() < 4) {
|
||||
output("Usage: tp <x> <y> <z> [angle] | tp @<procId> [<x> <y> <z> [angle] | @link]");
|
||||
return;
|
||||
}
|
||||
const auto pos = ParseXYZ(args, 1);
|
||||
if (!pos) {
|
||||
output("Error: invalid coordinates");
|
||||
return;
|
||||
}
|
||||
player->current.pos = *pos;
|
||||
if (!TryAngle(player->shape_angle.y, args, 4, output)) {
|
||||
return;
|
||||
}
|
||||
output(fmt::format(
|
||||
FMT_STRING("Teleported to ({:.2f}, {:.2f}, {:.2f})"), pos->x, pos->y, pos->z));
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == "spawn") {
|
||||
if (args.size() < 2) {
|
||||
output("Usage: spawn <actorId> [params] [x y z] [angle]");
|
||||
return;
|
||||
}
|
||||
auto* player = requirePlayer();
|
||||
if (player == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto actorId = ParseActorId(args[1]);
|
||||
if (!actorId) {
|
||||
output("Error: unknown actor ID or name");
|
||||
return;
|
||||
}
|
||||
|
||||
long paramsL = -1;
|
||||
if (args.size() >= 3) {
|
||||
const auto p = ParseLong(args[2]);
|
||||
if (!p) {
|
||||
output("Error: invalid params");
|
||||
return;
|
||||
}
|
||||
paramsL = *p;
|
||||
}
|
||||
|
||||
cXyz pos = player->current.pos;
|
||||
if (args.size() >= 6) {
|
||||
const auto spawnPos = ParseXYZ(args, 3);
|
||||
if (!spawnPos) {
|
||||
output("Error: invalid spawn coordinates");
|
||||
return;
|
||||
}
|
||||
pos = *spawnPos;
|
||||
}
|
||||
|
||||
s16 angleY = 0;
|
||||
if (!TryAngle(angleY, args, 6, output)) {
|
||||
return;
|
||||
}
|
||||
csXyz angle;
|
||||
angle.set(0, angleY, 0);
|
||||
cXyz scale(1.0f, 1.0f, 1.0f);
|
||||
|
||||
layer_class* savedLayer = fpcLy_CurrentLayer();
|
||||
base_process_class* playScene = fpcM_SearchByName(fpcNm_PLAY_SCENE_e);
|
||||
if (playScene != nullptr) {
|
||||
fpcLy_SetCurrentLayer(&((process_node_class*)playScene)->layer);
|
||||
}
|
||||
unsigned int result = fopAcM_create(
|
||||
*actorId, (u32)paramsL, &pos, player->current.roomNo, &angle, &scale, (s8)-1);
|
||||
fpcLy_SetCurrentLayer(savedLayer);
|
||||
|
||||
output(result != 0 ? fmt::format(FMT_STRING("Spawned actorId=0x{:04X} procId={}"),
|
||||
(unsigned int)(u16)*actorId, result) :
|
||||
fmt::format(FMT_STRING("Failed to spawn actorId=0x{:04X}"),
|
||||
(unsigned int)(u16)*actorId));
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == "reset") {
|
||||
JUTGamePad::C3ButtonReset::sResetSwitchPushing = true;
|
||||
output("Soft reset triggered");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == "warp") {
|
||||
if (args.size() < 4) {
|
||||
output("Usage: warp <stageName> <point> <roomNo> [layer=-1]");
|
||||
output(" e.g. warp F_SP121 0 0");
|
||||
return;
|
||||
}
|
||||
const auto pointL = ParseLong(args[2]), roomL = ParseLong(args[3]);
|
||||
if (!pointL || !roomL) {
|
||||
output("Error: invalid point or room number");
|
||||
return;
|
||||
}
|
||||
long layerL = -1;
|
||||
if (args.size() >= 5) {
|
||||
const auto l = ParseLong(args[4]);
|
||||
if (!l) {
|
||||
output("Error: invalid layer");
|
||||
return;
|
||||
}
|
||||
layerL = *l;
|
||||
}
|
||||
state.lastWarpStage = args[1];
|
||||
dComIfGp_setNextStage(state.lastWarpStage.c_str(), (s16)*pointL, (s8)*roomL, (s8)layerL);
|
||||
output(fmt::format(FMT_STRING("Warping to {} point={} room={} layer={}"), args[1],
|
||||
(int)(s16)*pointL, (int)(s8)*roomL, (int)(s8)layerL));
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == "list") {
|
||||
s16 targetId = -1;
|
||||
if (args.size() >= 2) {
|
||||
const auto id = ParseActorId(args[1]);
|
||||
if (!id) {
|
||||
output("Error: unknown actor ID or name");
|
||||
return;
|
||||
}
|
||||
targetId = *id;
|
||||
}
|
||||
std::vector<std::string> results;
|
||||
ListContext ctx{targetId, &results};
|
||||
fpcLyIt_OnlyHere(fpcLy_RootLayer(), ListActorCallback, &ctx);
|
||||
output(results.empty() ? "No matching actors found" :
|
||||
fmt::format(FMT_STRING("Found {} actor(s):"), results.size()));
|
||||
for (const auto& r : results) {
|
||||
output(r);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == "killall") {
|
||||
if (args.size() < 2) {
|
||||
output("Usage: killall <actorId|name>");
|
||||
return;
|
||||
}
|
||||
const auto targetId = ParseActorId(args[1]);
|
||||
if (!targetId) {
|
||||
output("Error: unknown actor ID or name");
|
||||
return;
|
||||
}
|
||||
FindContext ctx{*targetId, {}};
|
||||
fpcLyIt_OnlyHere(fpcLy_RootLayer(), FindActorCallback, &ctx);
|
||||
for (auto* proc : ctx.matches) {
|
||||
fpcM_Delete(proc);
|
||||
}
|
||||
output(fmt::format(FMT_STRING("Deleted {} actor(s) of type {} ({})"),
|
||||
(int)ctx.matches.size(), (unsigned int)(u16)*targetId, ActorShortName(*targetId)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == "heal") {
|
||||
auto* player = requirePlayer();
|
||||
if (player == nullptr) {
|
||||
return;
|
||||
}
|
||||
const u16 maxLife = dComIfGs_getMaxLife() / 5 * 4;
|
||||
u16 newLife = maxLife;
|
||||
if (args.size() >= 2) {
|
||||
const auto amount = ParseLong(args[1]);
|
||||
if (!amount) {
|
||||
output("Error: invalid amount");
|
||||
return;
|
||||
}
|
||||
newLife =
|
||||
(u16)std::max(0L, std::min((long)maxLife, (long)dComIfGs_getLife() + *amount));
|
||||
}
|
||||
dComIfGs_setLife(newLife);
|
||||
output(fmt::format(FMT_STRING("Health: {}/{}"), (int)newLife, (int)maxLife));
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == "kill") {
|
||||
if (args.size() >= 2) {
|
||||
auto* proc = ParseProcArg(args[1], state.foundProcId, output);
|
||||
if (proc == nullptr) {
|
||||
return;
|
||||
}
|
||||
fpcM_Delete(proc);
|
||||
output(fmt::format(FMT_STRING("Deleted proc {}"), proc->id));
|
||||
} else {
|
||||
auto* player = requirePlayer();
|
||||
if (player == nullptr) {
|
||||
return;
|
||||
}
|
||||
dComIfGs_setLife(0);
|
||||
output("Set Link's health to 0");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == "freeze" || cmd == "unfreeze") {
|
||||
const bool doFreeze = (cmd == "freeze");
|
||||
if (args.size() < 2) {
|
||||
g_dComIfAc_gameInfo.mPause = doFreeze;
|
||||
output(doFreeze ? "Global freeze on" : "Global freeze off");
|
||||
return;
|
||||
}
|
||||
auto* ac = ParseActorArg(args[1], state.foundProcId, output);
|
||||
if (ac == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (doFreeze) {
|
||||
fpcM_PauseEnable(ac, 1);
|
||||
output(fmt::format(FMT_STRING("Froze actor {}"), (unsigned int)ac->id));
|
||||
} else {
|
||||
fpcM_PauseDisable(ac, 1);
|
||||
output(fmt::format(FMT_STRING("Unfroze actor {}"), (unsigned int)ac->id));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == "rate") {
|
||||
if (args.size() >= 2) {
|
||||
const auto hz = ParseLong(args[1]);
|
||||
if (!hz || *hz <= 0) {
|
||||
output("Error: rate must be a positive integer");
|
||||
return;
|
||||
}
|
||||
dusk::game_clock::set_sim_rate((float)*hz);
|
||||
}
|
||||
output(fmt::format(FMT_STRING("Sim rate: {} hz"), (int)dusk::game_clock::get_sim_rate()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == "ebf") {
|
||||
if (args.size() >= 2) {
|
||||
const auto val = ParseLong(args[1]);
|
||||
if (!val || *val < 0 || *val > 255) {
|
||||
output("Error: value must be 0-255");
|
||||
return;
|
||||
}
|
||||
cDmr_SkipInfo = (u8)*val;
|
||||
}
|
||||
output(fmt::format(FMT_STRING("EBF = {}"), (int)cDmr_SkipInfo));
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == "time") {
|
||||
if (args.size() >= 2) {
|
||||
const auto t = ParseFloat(args[1]);
|
||||
if (!t || *t < 0.0f || *t > 360.0f) {
|
||||
output("Error: time must be a float between 0 and 360");
|
||||
return;
|
||||
}
|
||||
dKy_instant_timechg(*t);
|
||||
}
|
||||
output(fmt::format(FMT_STRING("Time: {:.2f} ({}:{:02d})"), dComIfGs_getTime(),
|
||||
dKy_getdaytime_hour(), dKy_getdaytime_minute()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == "rupees") {
|
||||
const u16 maxRupees = dComIfGs_getRupeeMax();
|
||||
if (args.size() >= 2) {
|
||||
const auto amount = ParseLong(args[1]);
|
||||
if (!amount) {
|
||||
output("Error: invalid amount");
|
||||
return;
|
||||
}
|
||||
dComIfGs_setRupee((u16)std::max(0L, std::min((long)maxRupees, *amount)));
|
||||
}
|
||||
output(fmt::format(FMT_STRING("Rupees: {}/{}"), (int)dComIfGs_getRupee(), (int)maxRupees));
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == "find") {
|
||||
if (args.size() < 2) {
|
||||
if (state.foundProcId == 0) {
|
||||
output("@found is not set");
|
||||
return;
|
||||
}
|
||||
auto* proc = fpcM_SearchByID(state.foundProcId);
|
||||
output(proc != nullptr && fopAcM_IsActor(proc) ?
|
||||
"@found = " + actorLine(proc) :
|
||||
fmt::format(FMT_STRING("@found = {} (no longer exists)"),
|
||||
(unsigned int)state.foundProcId));
|
||||
return;
|
||||
}
|
||||
|
||||
const auto targetId = ParseActorId(args[1]);
|
||||
if (!targetId) {
|
||||
output("Error: unknown actor ID or name");
|
||||
return;
|
||||
}
|
||||
|
||||
int targetN = 1;
|
||||
if (args.size() >= 3) {
|
||||
const auto n = ParseLong(args[2]);
|
||||
if (!n || *n < 1) {
|
||||
output("Error: index must be >= 1");
|
||||
return;
|
||||
}
|
||||
targetN = (int)*n;
|
||||
}
|
||||
|
||||
FindContext ctx{*targetId, {}};
|
||||
fpcLyIt_OnlyHere(fpcLy_RootLayer(), FindActorCallback, &ctx);
|
||||
|
||||
if (ctx.matches.empty()) {
|
||||
output(fmt::format(FMT_STRING("No actors found for '{}'"), args[1]));
|
||||
return;
|
||||
}
|
||||
|
||||
std::sort(ctx.matches.begin(), ctx.matches.end(),
|
||||
[](const base_process_class* a, const base_process_class* b) { return a->id < b->id; });
|
||||
|
||||
if (targetN > (int)ctx.matches.size()) {
|
||||
output(fmt::format(
|
||||
FMT_STRING("Error: only {} actor(s) of that type exist"), (int)ctx.matches.size()));
|
||||
return;
|
||||
}
|
||||
|
||||
auto* picked = ctx.matches[(size_t)(targetN - 1)];
|
||||
state.foundProcId = picked->id;
|
||||
output(fmt::format(
|
||||
FMT_STRING("@found [{}/{}] {}"), targetN, (int)ctx.matches.size(), actorLine(picked)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == "camera") {
|
||||
const std::string sub = args.size() >= 2 ? args[1] : "";
|
||||
|
||||
if (sub == "attach") {
|
||||
s_cameraFly.active = false;
|
||||
output("Camera attached");
|
||||
return;
|
||||
}
|
||||
|
||||
auto* cam = getCamera();
|
||||
if (cam == nullptr) { output("Error: camera not available"); return; }
|
||||
|
||||
if (sub == "detach") {
|
||||
s_cameraFly.active = true;
|
||||
s_cameraFly.duration = 0.0f;
|
||||
s_cameraFly.startEye = s_cameraFly.endEye = cam->iEye();
|
||||
s_cameraFly.startCenter = s_cameraFly.endCenter = cam->iCenter();
|
||||
output("Camera detached");
|
||||
return;
|
||||
}
|
||||
|
||||
if (sub == "tp") {
|
||||
// camera tp @<ref>
|
||||
if (args.size() >= 3 && !args[2].empty() && args[2][0] == '@') {
|
||||
auto* ac = ParseActorArg(args[2], state.foundProcId, output);
|
||||
if (ac == nullptr) { return; }
|
||||
const cXyz actorPos = ac->current.pos;
|
||||
const cXyz dir = anglesToDir(ac->shape_angle.y, 0);
|
||||
const cXyz newEye = cXyz(actorPos.x - dir.x * kCamTargetDist,
|
||||
actorPos.y - dir.y * kCamTargetDist + 50.0f,
|
||||
actorPos.z - dir.z * kCamTargetDist);
|
||||
s_cameraFly.active = true; s_cameraFly.duration = 0.0f;
|
||||
s_cameraFly.startEye = s_cameraFly.endEye = newEye;
|
||||
s_cameraFly.startCenter = s_cameraFly.endCenter = actorPos;
|
||||
output(fmt::format(FMT_STRING("Camera moved to actor {}"), ac->id));
|
||||
return;
|
||||
}
|
||||
// camera tp x y z [h] [v]
|
||||
if (args.size() < 5) {
|
||||
output("Usage: camera tp <x> <y> <z> [h_angle] [v_angle] | camera tp @<ref>");
|
||||
return;
|
||||
}
|
||||
const auto pos = ParseXYZ(args, 2);
|
||||
if (!pos) { output("Error: invalid coordinates"); return; }
|
||||
s16 h = 0, v = 0;
|
||||
cameraToAngles(cam, h, v);
|
||||
if (args.size() >= 6) {
|
||||
const auto hv = ParseLong(args[5]);
|
||||
if (!hv) { output("Error: invalid h_angle"); return; }
|
||||
h = (s16)*hv;
|
||||
}
|
||||
if (args.size() >= 7) {
|
||||
const auto vv = ParseLong(args[6]);
|
||||
if (!vv) { output("Error: invalid v_angle"); return; }
|
||||
v = (s16)*vv;
|
||||
}
|
||||
const cXyz dir = anglesToDir(h, v);
|
||||
const cXyz center = cXyz(pos->x + dir.x * kCamTargetDist,
|
||||
pos->y + dir.y * kCamTargetDist,
|
||||
pos->z + dir.z * kCamTargetDist);
|
||||
s_cameraFly.active = true; s_cameraFly.duration = 0.0f;
|
||||
s_cameraFly.startEye = s_cameraFly.endEye = *pos;
|
||||
s_cameraFly.startCenter = s_cameraFly.endCenter = center;
|
||||
output(fmt::format(FMT_STRING("Camera teleported to ({:.2f}, {:.2f}, {:.2f})"),
|
||||
pos->x, pos->y, pos->z));
|
||||
return;
|
||||
}
|
||||
|
||||
if (sub == "pos") {
|
||||
s16 h = 0, v = 0;
|
||||
cameraToAngles(cam, h, v);
|
||||
const cXyz eye = cam->iEye();
|
||||
output(fmt::format(FMT_STRING("Camera: ({:.2f}, {:.2f}, {:.2f}) h={} v={}"),
|
||||
eye.x, eye.y, eye.z, (int)h, (int)v));
|
||||
return;
|
||||
}
|
||||
|
||||
if (sub == "fly") {
|
||||
// camera fly <time> <x> <y> <z> [h] [v] | camera fly <time> @<ref>
|
||||
if (args.size() < 4) {
|
||||
output("Usage: camera fly <time> <x> <y> <z> [h_angle] [v_angle] | camera fly <time> @<ref>");
|
||||
return;
|
||||
}
|
||||
const auto t = ParseFloat(args[2]);
|
||||
if (!t || *t <= 0) { output("Error: invalid time"); return; }
|
||||
const float flyTime = *t;
|
||||
|
||||
cXyz targetPos;
|
||||
s16 h = 0, v = 0;
|
||||
|
||||
if (!args[3].empty() && args[3][0] == '@') {
|
||||
auto* ac = ParseActorArg(args[3], state.foundProcId, output);
|
||||
if (ac == nullptr) { return; }
|
||||
targetPos = ac->current.pos;
|
||||
h = ac->shape_angle.y;
|
||||
v = 0;
|
||||
} else {
|
||||
if (args.size() < 6) {
|
||||
output("Usage: camera fly <time> <x> <y> <z> [h_angle] [v_angle] | camera fly <time> @<ref>");
|
||||
return;
|
||||
}
|
||||
const auto pos = ParseXYZ(args, 3);
|
||||
if (!pos) { output("Error: invalid coordinates"); return; }
|
||||
targetPos = *pos;
|
||||
cameraToAngles(cam, h, v);
|
||||
if (args.size() >= 7) {
|
||||
const auto hv = ParseLong(args[6]); if (!hv) { output("Error: invalid h_angle"); return; } h = (s16)*hv;
|
||||
}
|
||||
if (args.size() >= 8) {
|
||||
const auto vv = ParseLong(args[7]); if (!vv) { output("Error: invalid v_angle"); return; } v = (s16)*vv;
|
||||
}
|
||||
}
|
||||
|
||||
const cXyz dir = anglesToDir(h, v);
|
||||
const cXyz endCenter = cXyz(targetPos.x + dir.x * kCamTargetDist,
|
||||
targetPos.y + dir.y * kCamTargetDist,
|
||||
targetPos.z + dir.z * kCamTargetDist);
|
||||
|
||||
s_cameraFly.active = true;
|
||||
s_cameraFly.startEye = cam->iEye();
|
||||
s_cameraFly.startCenter = cam->iCenter();
|
||||
s_cameraFly.endEye = targetPos;
|
||||
s_cameraFly.endCenter = endCenter;
|
||||
s_cameraFly.duration = flyTime;
|
||||
s_cameraFly.elapsed = 0.0f;
|
||||
output(fmt::format(FMT_STRING("Camera flying to ({:.2f}, {:.2f}, {:.2f}) over {:.1f}s"),
|
||||
targetPos.x, targetPos.y, targetPos.z, flyTime));
|
||||
return;
|
||||
}
|
||||
|
||||
output("Usage: camera detach | attach | tp ... | fly ... | pos");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == "echo") {
|
||||
if (args.size() >= 2 && args[1] == "off") {
|
||||
state.echoEnabled = false;
|
||||
output("Echo disabled");
|
||||
} else if (args.size() >= 2 && args[1] == "on") {
|
||||
state.echoEnabled = true;
|
||||
output("Echo enabled");
|
||||
} else {
|
||||
output(state.echoEnabled ? "Echo: on" : "Echo: off");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == "transform") {
|
||||
auto* player = requirePlayer();
|
||||
if (player == nullptr) { return; }
|
||||
player->procCoMetamorphoseInit();
|
||||
output("Transforming");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == "angle") {
|
||||
auto* player = requirePlayer();
|
||||
if (player == nullptr) { return; }
|
||||
if (args.size() >= 2) {
|
||||
const auto a = ParseLong(args[1]);
|
||||
if (!a) { output("Error: invalid angle"); return; }
|
||||
player->shape_angle.y = (s16)*a;
|
||||
output(fmt::format(FMT_STRING("Angle set to {}"), (int)(s16)*a));
|
||||
} else {
|
||||
output(fmt::format(FMT_STRING("Angle: {}"), (int)player->shape_angle.y));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == "pos") {
|
||||
auto* player = requirePlayer();
|
||||
if (player == nullptr) {
|
||||
return;
|
||||
}
|
||||
output(fmt::format(FMT_STRING("pos: {:.4f} {:.4f} {:.4f}"), player->current.pos.x,
|
||||
player->current.pos.y, player->current.pos.z));
|
||||
output(
|
||||
fmt::format(FMT_STRING("stage: {} room: {} entry: {}"), dComIfGp_getStartStageName(),
|
||||
(int)player->current.roomNo, (int)dComIfGp_getStartStagePoint()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == "help") {
|
||||
output("@<ref> = @<procId> | @found | @link");
|
||||
output("");
|
||||
output("angle [value] Get or set Link's facing angle");
|
||||
output("camera detach | attach Detach or reattach camera");
|
||||
output("camera pos Print camera position and angles");
|
||||
output("camera tp <x> <y> <z> [h] [v] Teleport camera (h/v are s16 angles)");
|
||||
output("camera tp @<ref> Teleport camera to actor");
|
||||
output("camera fly <t> <x> <y> <z> [h] [v] Fly camera over t seconds (h/v are s16 angles)");
|
||||
output("camera fly <t> @<ref> Fly camera to actor");
|
||||
output("ebf [0-255] Get or set cDmr_SkipInfo");
|
||||
output("echo on | off Enable or disable command echo");
|
||||
output("find <id|name> [n=1] Store nth actor as @found");
|
||||
output("freeze [@<ref>] Freeze globally, or freeze actor");
|
||||
output("heal [amount] Heal to max, or by relative amount");
|
||||
output("kill Set Link health to 0");
|
||||
output("kill @<ref> Delete proc");
|
||||
output("killall <id|name> Delete all actors of a type");
|
||||
output("list [id|name] List actors in scene");
|
||||
output("pos Print player position and stage");
|
||||
output("rate [hz] Get or set sim rate (1-1000, default 30)");
|
||||
output("reset Soft reset");
|
||||
output("rupees [amount] Get or set rupee count");
|
||||
output("spawn <id|name> [params] [x y z] [angle] Spawn actor");
|
||||
output("time [0-360] Get or set time of day");
|
||||
output("tp <x> <y> <z> [angle] Teleport Link to coords");
|
||||
output("tp @<ref> Teleport Link to actor");
|
||||
output("tp @<ref> <x> <y> <z> [angle] Move actor to coords");
|
||||
output("tp @<ref> @<ref> Move actor to actor");
|
||||
output("transform Force transform");
|
||||
output("unfreeze [@<ref>] Unfreeze globally, or unfreeze actor");
|
||||
output("warp <stage> <point> <room> [layer] Warp to stage");
|
||||
return;
|
||||
}
|
||||
|
||||
output(fmt::format(FMT_STRING("Unknown command '{}' (try 'help')"), cmd));
|
||||
}
|
||||
|
||||
} // namespace dusk
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace dusk {
|
||||
|
||||
using CommandOutput = std::function<void(std::string)>;
|
||||
|
||||
struct CommandState {
|
||||
std::vector<std::string> history;
|
||||
unsigned int foundProcId = 0;
|
||||
std::string lastWarpStage;
|
||||
bool echoEnabled = true;
|
||||
};
|
||||
|
||||
// Execute a single command line. Calls output() for every line of response.
|
||||
// Manages history internally; callers need only hold a CommandState.
|
||||
void runCommand(std::string_view cmdLine, CommandState& state, const CommandOutput& output);
|
||||
|
||||
// Per-sim-tick update for camera fly animation.
|
||||
void processCameraCommands();
|
||||
|
||||
// Returns true when the dev camera is detached (suppresses the game's camera update).
|
||||
bool isCameraDetached();
|
||||
|
||||
} // namespace dusk
|
||||
@@ -0,0 +1,417 @@
|
||||
#include "dusk/d_trigger_view.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "d/d_debug_viewer.h"
|
||||
#include "d/d_com_inf_game.h"
|
||||
#include "d/d_stage.h"
|
||||
#include "d/d_path.h"
|
||||
#include "d/d_attention.h"
|
||||
#include "d/actor/d_a_alink.h"
|
||||
#include "d/actor/d_a_kytag08.h"
|
||||
#include "d/actor/d_a_e_rb.h"
|
||||
#include "d/actor/d_a_e_rd.h"
|
||||
#include "f_op/f_op_actor_mng.h"
|
||||
#include "f_op/f_op_actor_tag.h"
|
||||
#include "f_pc/f_pc_name.h"
|
||||
#include "m_Do/m_Do_mtx.h"
|
||||
#include "SSystem/SComponent/c_tag.h"
|
||||
#include "SSystem/SComponent/c_list.h"
|
||||
#include "JSystem/JMath/JMath.h"
|
||||
|
||||
namespace dusk {
|
||||
namespace TriggerView {
|
||||
|
||||
static u8 s_opacity;
|
||||
|
||||
typedef void (*DrawCallback)(fopAc_ac_c*);
|
||||
|
||||
static void searchActorForCallback(s16 actorName, DrawCallback callback) {
|
||||
node_class* node = g_fopAcTg_Queue.mpHead;
|
||||
|
||||
for (int i = 0; i < g_fopAcTg_Queue.mSize; i++) {
|
||||
if (node != NULL) {
|
||||
create_tag_class* tag = (create_tag_class*)node;
|
||||
fopAc_ac_c* actorData = (fopAc_ac_c*)tag->mpTagData;
|
||||
|
||||
bool checkAll = actorName == -1;
|
||||
if (actorData != NULL && (fopAcM_GetName(actorData) == actorName || checkAll)) {
|
||||
callback(actorData);
|
||||
}
|
||||
node = node->mpNextNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void drawSceneExit(fopAc_ac_c* actor) {
|
||||
struct daScex_c : public fopAc_ac_c {
|
||||
/* 0x568 */ Mtx mMatrix;
|
||||
};
|
||||
daScex_c* scex = (daScex_c*)actor;
|
||||
|
||||
cXyz points[8];
|
||||
points[0].set(-actor->scale.x, actor->scale.y, -actor->scale.z);
|
||||
points[1].set(actor->scale.x, actor->scale.y, -actor->scale.z);
|
||||
points[2].set(-actor->scale.x, actor->scale.y, actor->scale.z);
|
||||
points[3].set(actor->scale.x, actor->scale.y, actor->scale.z);
|
||||
points[4].set(-actor->scale.x, 0.0f, -actor->scale.z);
|
||||
points[5].set(actor->scale.x, 0.0f, -actor->scale.z);
|
||||
points[6].set(-actor->scale.x, 0.0f, actor->scale.z);
|
||||
points[7].set(actor->scale.x, 0.0f, actor->scale.z);
|
||||
|
||||
mDoMtx_inverse(scex->mMatrix, mDoMtx_stack_c::get());
|
||||
mDoMtx_multVecArray(mDoMtx_stack_c::get(), points, points, 8);
|
||||
|
||||
GXColor color = {0xFF, 0x00, 0xFF, s_opacity};
|
||||
dDbVw_drawCube8pXlu(points, color);
|
||||
}
|
||||
|
||||
static void drawMidnaStop(fopAc_ac_c* actor) {
|
||||
struct daMidnaStop_c : public fopAc_ac_c {
|
||||
/* 0x568 */ u8 data[0x5C0 - 0x568];
|
||||
/* 0x5C0 */ f32 mRangeXZ;
|
||||
/* 0x5C4 */ f32 mRangeY;
|
||||
};
|
||||
daMidnaStop_c* mstop = (daMidnaStop_c*)actor;
|
||||
|
||||
GXColor color = {0x4A, 0x36, 0xBA, s_opacity};
|
||||
dDbVw_drawCylinderXlu(mstop->current.pos, mstop->scale.x * 100.0f, mstop->scale.y, color, 1);
|
||||
}
|
||||
|
||||
static void drawPlumTag(fopAc_ac_c* actor) {
|
||||
GXColor color = {0x00, 0xFF, 0x00, s_opacity};
|
||||
dDbVw_drawCylinderXlu(actor->current.pos, actor->scale.x * 100.0f, 1000000.0f, color, 1);
|
||||
}
|
||||
|
||||
static void drawPlumSearch(fopAc_ac_c* actor) {
|
||||
GXColor color = {0xFF, 0x00, 0x00, s_opacity};
|
||||
const f32 search_dist = 500.0f;
|
||||
dDbVw_drawCircleXlu(actor->attention_info.position, search_dist + 160.0f, color, 1, 12);
|
||||
}
|
||||
|
||||
static void drawSwitchArea(fopAc_ac_c* actor) {
|
||||
struct daSwc00_c : public fopAc_ac_c {
|
||||
/* 0x568 */ cXyz mStart;
|
||||
/* 0x574 */ cXyz mEnd;
|
||||
/* 0x580 */ u16 mEventID;
|
||||
/* 0x582 */ u8 mAction;
|
||||
};
|
||||
daSwc00_c* swc = (daSwc00_c*)actor;
|
||||
int shape_type = (fopAcM_GetParam(actor) >> 0x12) & 3;
|
||||
|
||||
GXColor color = {0x00, 0x00, 0xFF, s_opacity};
|
||||
if (shape_type == 3) {
|
||||
dDbVw_drawCylinderXlu(swc->current.pos, JMAFastSqrt(swc->scale.x) - 30.0f, swc->scale.y, color, 1);
|
||||
} else if (shape_type == 0) {
|
||||
cXyz size = swc->mEnd - swc->mStart;
|
||||
size *= 0.5f;
|
||||
cXyz pos = swc->mStart + size;
|
||||
csXyz angle(swc->current.angle.x, swc->current.angle.y, swc->current.angle.z);
|
||||
dDbVw_drawCubeXlu(pos, size, angle, color);
|
||||
}
|
||||
}
|
||||
|
||||
static void drawEventArea(fopAc_ac_c* actor) {
|
||||
u8 type = (actor->shape_angle.z & 0xFF);
|
||||
if (type == 0xFF) {
|
||||
type = 0;
|
||||
}
|
||||
|
||||
if (type == 15 || type == 16) {
|
||||
GXColor color = {0xFF, 0xFF, 0x00, s_opacity};
|
||||
cXyz points[8];
|
||||
points[0].set(-actor->scale.x, actor->scale.y, -actor->scale.z);
|
||||
points[1].set(actor->scale.x, actor->scale.y, -actor->scale.z);
|
||||
points[2].set(-actor->scale.x, actor->scale.y, actor->scale.z);
|
||||
points[3].set(actor->scale.x, actor->scale.y, actor->scale.z);
|
||||
points[4].set(-actor->scale.x, 0.0f, -actor->scale.z);
|
||||
points[5].set(actor->scale.x, 0.0f, -actor->scale.z);
|
||||
points[6].set(-actor->scale.x, 0.0f, actor->scale.z);
|
||||
points[7].set(actor->scale.x, 0.0f, actor->scale.z);
|
||||
|
||||
mDoMtx_stack_c::transS(actor->home.pos.x, actor->home.pos.y, actor->home.pos.z);
|
||||
mDoMtx_stack_c::YrotS(actor->current.angle.y);
|
||||
mDoMtx_multVecArray(mDoMtx_stack_c::get(), points, points, 8);
|
||||
|
||||
dDbVw_drawCube8pXlu(points, color);
|
||||
} else {
|
||||
GXColor outer_color = {0xFF, 0x00, 0x00, s_opacity};
|
||||
GXColor inner_color = {0x00, 0xFF, 0x00, s_opacity};
|
||||
cXyz pos = actor->current.pos;
|
||||
|
||||
daAlink_c* player = (daAlink_c*)dComIfGp_getPlayer(0);
|
||||
if (player != NULL && pos.y < player->mLinkAcch.GetGroundH()) {
|
||||
pos.y = player->mLinkAcch.GetGroundH() + 100.0f;
|
||||
}
|
||||
|
||||
const f32 inner_scale = 0.83f;
|
||||
dDbVw_drawCircleXlu(pos, actor->scale.x * inner_scale, inner_color, 1, 20);
|
||||
dDbVw_drawCircleXlu(pos, actor->scale.x, outer_color, 1, 20);
|
||||
}
|
||||
}
|
||||
|
||||
static void drawEventTag(fopAc_ac_c* actor) {
|
||||
GXColor color = {0x00, 0xC8, 0xFF, s_opacity};
|
||||
u16 area_type = actor->home.angle.x & 0x8000;
|
||||
|
||||
if (area_type == 0x8000) {
|
||||
cXyz start(actor->current.pos.x - (actor->scale.x * 0.5f), actor->current.pos.y,
|
||||
actor->current.pos.z - (actor->scale.z * 0.5f));
|
||||
cXyz end(actor->current.pos.x + (actor->scale.x * 0.5f),
|
||||
actor->current.pos.y + actor->scale.y,
|
||||
actor->current.pos.z + (actor->scale.z * 0.5f));
|
||||
|
||||
cXyz points[8];
|
||||
points[0].set(start.x, start.y, start.z);
|
||||
points[1].set(start.x, start.y, end.z);
|
||||
points[2].set(end.x, start.y, end.z);
|
||||
points[3].set(end.x, start.y, start.z);
|
||||
points[4].set(start.x, end.y, start.z);
|
||||
points[5].set(start.x, end.y, end.z);
|
||||
points[6].set(end.x, end.y, end.z);
|
||||
points[7].set(end.x, end.y, start.z);
|
||||
|
||||
dDbVw_drawCube8pXlu(points, color);
|
||||
} else {
|
||||
cXyz pos = actor->current.pos;
|
||||
pos.y -= actor->scale.y;
|
||||
dDbVw_drawCylinderXlu(pos, actor->scale.x, actor->scale.y * 2, color, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void drawTWGate(fopAc_ac_c* actor) {
|
||||
GXColor color = {0xFF, 0xFF, 0xFF, s_opacity};
|
||||
dDbVw_drawCylinderXlu(actor->current.pos, actor->scale.x * 100.0f, actor->scale.y * 100.0f, color, 1);
|
||||
}
|
||||
|
||||
static u8 s_pathColorIndex = 0;
|
||||
|
||||
static void drawPaths(dStage_dPath_c* paths) {
|
||||
static const GXColor colors[8] = {
|
||||
{0xFF, 0xFF, 0xFF}, {0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00}, {0x00, 0xFF, 0x00},
|
||||
{0x00, 0x00, 0xFF}, {0xFF, 0xFF, 0x00}, {0xFF, 0x00, 0xFF}, {0x00, 0xFF, 0xFF},
|
||||
};
|
||||
|
||||
cXyz cubeSize = {30.0f, 30.0f, 30.0f};
|
||||
csXyz cubeAngle = {0, 0, 0};
|
||||
|
||||
for (int i = 0; i < (int)paths->num; i++) {
|
||||
dPath* path = &paths->m_path[i];
|
||||
GXColor color = colors[(s_pathColorIndex++) & 7];
|
||||
color.a = s_opacity;
|
||||
cXyz a, b;
|
||||
|
||||
if (dPath_ChkClose(path) && path->m_num > 2) {
|
||||
a = (Vec)path->m_points[0].m_position;
|
||||
b = (Vec)path->m_points[(int)path->m_num - 1].m_position;
|
||||
dDbVw_drawLineXlu(a, b, color, 1, 10);
|
||||
}
|
||||
|
||||
for (int j = 0; j < (int)path->m_num - 1; j++) {
|
||||
a = (Vec)path->m_points[j].m_position;
|
||||
b = (Vec)path->m_points[j + 1].m_position;
|
||||
dDbVw_drawLineXlu(a, b, color, 1, 10);
|
||||
dDbVw_drawCubeXlu(a, cubeSize, cubeAngle, color);
|
||||
}
|
||||
dDbVw_drawCubeXlu(b, cubeSize, cubeAngle, color);
|
||||
}
|
||||
}
|
||||
|
||||
static void drawStagePaths() {
|
||||
dStage_dPath_c* stagePaths = g_dComIfG_gameInfo.play.getStage().getPath2Inf();
|
||||
if (stagePaths != nullptr) {
|
||||
drawPaths(stagePaths);
|
||||
}
|
||||
}
|
||||
|
||||
static void drawCurrentRoomPaths() {
|
||||
fopAc_ac_c* player = dComIfGp_getPlayer(0);
|
||||
if (player == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
s32 roomNo = fopAcM_GetRoomNo(player);
|
||||
if (roomNo < 0 || roomNo >= 64) {
|
||||
return;
|
||||
}
|
||||
|
||||
dStage_dPath_c* roomPaths = dStage_roomControl_c::mStatus[roomNo].mRoomDt.getPath2Inf();
|
||||
if (roomPaths != nullptr) {
|
||||
drawPaths(roomPaths);
|
||||
}
|
||||
}
|
||||
|
||||
static void drawCheckpointTag(fopAc_ac_c* actor) {
|
||||
struct daTagChgRestart_c : public fopAc_ac_c {
|
||||
/* 0x568 */ cXyz mVertices[4];
|
||||
};
|
||||
daTagChgRestart_c* chk = (daTagChgRestart_c*)actor;
|
||||
|
||||
GXColor color = {0x29, 0xF0, 0xFF, s_opacity};
|
||||
cXyz points[8];
|
||||
|
||||
mDoMtx_stack_c::transS(actor->current.pos.x, actor->current.pos.y, actor->current.pos.z);
|
||||
mDoMtx_stack_c::YrotM(actor->current.angle.y);
|
||||
|
||||
points[0] = chk->mVertices[0];
|
||||
points[1] = chk->mVertices[1];
|
||||
points[2] = chk->mVertices[3];
|
||||
points[3] = chk->mVertices[2];
|
||||
points[4] = chk->mVertices[0];
|
||||
points[5] = chk->mVertices[1];
|
||||
points[6] = chk->mVertices[3];
|
||||
points[7] = chk->mVertices[2];
|
||||
|
||||
mDoMtx_multVecArray(mDoMtx_stack_c::get(), points, points, 8);
|
||||
|
||||
fopAc_ac_c* player = dComIfGp_getPlayer(0);
|
||||
if (player != nullptr) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
points[i].y = player->current.pos.y;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
points[i].y += 1000.0f;
|
||||
}
|
||||
|
||||
dDbVw_drawCube8pXlu(points, color);
|
||||
}
|
||||
|
||||
static void drawTransformDists(fopAc_ac_c* actor) {
|
||||
if (fopAcM_GetGroup(actor) == 4 && !(actor->actor_status & fopAcStts_UNK_0x8000000_e)) {
|
||||
GXColor near_color = {0x00, 0xFF, 0x00, s_opacity};
|
||||
GXColor far_color = {0xFF, 0x00, 0x00, s_opacity};
|
||||
|
||||
const f32 near_dist = 400.0f;
|
||||
const f32 far_dist = 5000.0f;
|
||||
|
||||
dDbVw_drawCircleXlu(actor->eyePos, near_dist, near_color, 1, 20);
|
||||
dDbVw_drawCircleXlu(actor->eyePos, far_dist, far_color, 1, 20);
|
||||
|
||||
const s16 view_range = 0x4000;
|
||||
cXyz offset(0.0f, 0.0f, far_dist);
|
||||
cXyz endpos;
|
||||
|
||||
mDoMtx_stack_c::transS(actor->eyePos.x, actor->eyePos.y, actor->eyePos.z);
|
||||
mDoMtx_stack_c::YrotM(actor->shape_angle.y);
|
||||
mDoMtx_stack_c::YrotM(-view_range);
|
||||
mDoMtx_stack_c::multVec(&offset, &endpos);
|
||||
dDbVw_drawLineXlu(actor->eyePos, endpos, far_color, 1, 10);
|
||||
|
||||
mDoMtx_stack_c::transS(actor->eyePos.x, actor->eyePos.y, actor->eyePos.z);
|
||||
mDoMtx_stack_c::YrotM(actor->shape_angle.y);
|
||||
mDoMtx_stack_c::YrotM(view_range);
|
||||
mDoMtx_stack_c::multVec(&offset, &endpos);
|
||||
dDbVw_drawLineXlu(actor->eyePos, endpos, far_color, 1, 10);
|
||||
|
||||
mDoMtx_stack_c::transS(actor->eyePos.x, actor->eyePos.y, actor->eyePos.z);
|
||||
mDoMtx_stack_c::YrotM(actor->shape_angle.y);
|
||||
mDoMtx_stack_c::multVec(&offset, &endpos);
|
||||
dDbVw_drawLineXlu(actor->eyePos, endpos, far_color, 1, 10);
|
||||
}
|
||||
}
|
||||
|
||||
static void drawAttentionDists(fopAc_ac_c* actor) {
|
||||
if (fopAcM_GetGroup(actor) != 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
GXColor lock_color = {0x00, 0x00, 0xFF, s_opacity};
|
||||
GXColor talk_color = {0x00, 0xFF, 0x00, s_opacity};
|
||||
|
||||
dist_entry& lock_inf = dAttention_c::getDistTable(actor->attention_info.distances[fopAc_attn_LOCK_e]);
|
||||
dist_entry& talk_inf = dAttention_c::getDistTable(actor->attention_info.distances[fopAc_attn_TALK_e]);
|
||||
|
||||
dDbVw_drawCircleXlu(actor->attention_info.position, lock_inf.mDistMax, lock_color, 1, 20);
|
||||
dDbVw_drawCircleXlu(actor->attention_info.position, talk_inf.mDistMax, talk_color, 1, 20);
|
||||
}
|
||||
|
||||
static void drawPurpleMistAvoid(fopAc_ac_c* actor) {
|
||||
kytag08_class* tag = (kytag08_class*)actor;
|
||||
|
||||
GXColor avoidColor = {0x00, 0xFF, 0x00, s_opacity};
|
||||
GXColor targetColor = {0xFF, 0x00, 0xFF, s_opacity};
|
||||
|
||||
dDbVw_drawCircleXlu(tag->mAvoidPos, tag->mSize.x * 45.0f * tag->mSizeScale, avoidColor, 1, 20);
|
||||
|
||||
cXyz cubeSize(10.0f, 10.0f, 10.0f);
|
||||
csXyz cubeAngle(0, 0, 0);
|
||||
dDbVw_drawCubeXlu(tag->mAvoidPos, cubeSize, cubeAngle, avoidColor);
|
||||
dDbVw_drawCubeXlu(tag->mTargetAvoidPos, cubeSize, cubeAngle, targetColor);
|
||||
}
|
||||
|
||||
static void drawLeeverData(fopAc_ac_c* actor) {
|
||||
e_rb_class* leever = (e_rb_class*)actor;
|
||||
|
||||
if (leever->isChild) {
|
||||
return;
|
||||
}
|
||||
|
||||
GXColor color = {0xFF, 0x00, 0x00, s_opacity};
|
||||
GXColor color2 = {0x00, 0x00, 0xFF, s_opacity};
|
||||
|
||||
cXyz pos = actor->current.pos;
|
||||
daAlink_c* player = (daAlink_c*)dComIfGp_getPlayer(0);
|
||||
if (player != nullptr && pos.y < player->mLinkAcch.GetGroundH()) {
|
||||
pos.y = player->mLinkAcch.GetGroundH() + 100.0f;
|
||||
}
|
||||
|
||||
dDbVw_drawCircleXlu(pos, leever->appearRange * 100.0f, color, 1, 20);
|
||||
dDbVw_drawCircleXlu(pos, leever->field_0xa69 * 100.0f, color2, 1, 20);
|
||||
}
|
||||
|
||||
void execute() {
|
||||
const auto& settings = getTransientSettings().triggerView;
|
||||
s_opacity = (u8)(255.0f * (settings.opacity / 100.0f));
|
||||
|
||||
if (settings.loadZones) {
|
||||
searchActorForCallback(fpcNm_SCENE_EXIT_e, drawSceneExit);
|
||||
}
|
||||
|
||||
if (settings.midnaStops) {
|
||||
searchActorForCallback(fpcNm_Tag_Mstop_e, drawMidnaStop);
|
||||
}
|
||||
|
||||
if (settings.switchAreas) {
|
||||
searchActorForCallback(fpcNm_SWC00_e, drawSwitchArea);
|
||||
}
|
||||
|
||||
if (settings.eventAreas) {
|
||||
searchActorForCallback(fpcNm_TAG_EVENT_e, drawEventTag);
|
||||
searchActorForCallback(fpcNm_TAG_EVTAREA_e, drawEventArea);
|
||||
searchActorForCallback(fpcNm_TAG_MYNA2_e, drawPlumTag);
|
||||
searchActorForCallback(fpcNm_MYNA2_e, drawPlumSearch);
|
||||
}
|
||||
|
||||
if (settings.twilightGates) {
|
||||
searchActorForCallback(fpcNm_Tag_TWGate_e, drawTWGate);
|
||||
}
|
||||
|
||||
if (settings.paths) {
|
||||
s_pathColorIndex = 0;
|
||||
drawStagePaths();
|
||||
drawCurrentRoomPaths();
|
||||
}
|
||||
|
||||
if (settings.checkpoints) {
|
||||
searchActorForCallback(fpcNm_Tag_ChgRestart_e, drawCheckpointTag);
|
||||
}
|
||||
|
||||
if (settings.transformDists) {
|
||||
searchActorForCallback(-1, drawTransformDists);
|
||||
}
|
||||
|
||||
if (settings.attentionDists) {
|
||||
searchActorForCallback(-1, drawAttentionDists);
|
||||
}
|
||||
|
||||
if (settings.purpleMistAvoid) {
|
||||
searchActorForCallback(fpcNm_KYTAG08_e, drawPurpleMistAvoid);
|
||||
}
|
||||
|
||||
if (settings.leevers) {
|
||||
searchActorForCallback(fpcNm_E_RB_e, drawLeeverData);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace TriggerView
|
||||
} // namespace dusk
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace dusk {
|
||||
namespace TriggerView {
|
||||
void execute();
|
||||
}
|
||||
}
|
||||
+20
-9
@@ -16,8 +16,9 @@ clock::time_point s_current_snapshot_time{};
|
||||
|
||||
std::unordered_map<uintptr_t, clock::time_point> s_interval_last_sample;
|
||||
|
||||
constexpr clock::duration kSimPeriodDuration =
|
||||
std::chrono::duration_cast<clock::duration>(std::chrono::duration<float>(sim_pace()));
|
||||
float s_sim_rate_hz = 30.0f;
|
||||
clock::duration s_sim_period_duration = std::chrono::duration_cast<clock::duration>(std::chrono::duration<float>(sim_pace()));
|
||||
|
||||
constexpr clock::duration kAbnormalGapResetThreshold = std::chrono::milliseconds(250);
|
||||
constexpr int kMaxSimTicksPerFrame = 2;
|
||||
|
||||
@@ -32,7 +33,17 @@ void ensure_initialized() {
|
||||
|
||||
void reset_frame_timer() {
|
||||
s_previous_sample = clock::now();
|
||||
s_current_snapshot_time = s_previous_sample - kSimPeriodDuration;
|
||||
s_current_snapshot_time = s_previous_sample - s_sim_period_duration;
|
||||
}
|
||||
|
||||
void set_sim_rate(float hz) {
|
||||
s_sim_rate_hz = std::max(1.0f, std::min(hz, 1000.0f));
|
||||
s_sim_period_duration = std::chrono::duration_cast<clock::duration>(std::chrono::duration<float>(1.0f / s_sim_rate_hz));
|
||||
reset_frame_timer();
|
||||
}
|
||||
|
||||
float get_sim_rate() {
|
||||
return s_sim_rate_hz;
|
||||
}
|
||||
|
||||
MainLoopPacer advance_main_loop() {
|
||||
@@ -45,12 +56,12 @@ MainLoopPacer advance_main_loop() {
|
||||
|
||||
MainLoopPacer out{};
|
||||
out.presentation_dt_seconds = presentation_dt;
|
||||
out.sim_pace = 1.0f / s_sim_rate_hz;
|
||||
|
||||
const bool should_interpolate = dusk::getSettings().game.enableFrameInterpolation.getValue() !=
|
||||
dusk::FrameInterpMode::Off &&
|
||||
!dusk::getTransientSettings().skipFrameRateLimit;
|
||||
out.is_interpolating = should_interpolate;
|
||||
out.sim_pace = sim_pace();
|
||||
|
||||
if (!should_interpolate) {
|
||||
s_current_snapshot_time = now;
|
||||
@@ -59,16 +70,16 @@ MainLoopPacer advance_main_loop() {
|
||||
}
|
||||
|
||||
if (frame_gap > kAbnormalGapResetThreshold) {
|
||||
s_current_snapshot_time = now - kSimPeriodDuration;
|
||||
s_current_snapshot_time = now - s_sim_period_duration;
|
||||
out.sim_ticks_to_run = 0;
|
||||
return out;
|
||||
}
|
||||
|
||||
int sim_ticks_to_run = 0;
|
||||
clock::time_point projected_snapshot_time = s_current_snapshot_time;
|
||||
const clock::time_point render_time = now - kSimPeriodDuration;
|
||||
const clock::time_point render_time = now - s_sim_period_duration;
|
||||
while (sim_ticks_to_run < kMaxSimTicksPerFrame && projected_snapshot_time < render_time) {
|
||||
projected_snapshot_time += kSimPeriodDuration;
|
||||
projected_snapshot_time += s_sim_period_duration;
|
||||
sim_ticks_to_run++;
|
||||
}
|
||||
out.sim_ticks_to_run = sim_ticks_to_run;
|
||||
@@ -77,13 +88,13 @@ MainLoopPacer advance_main_loop() {
|
||||
|
||||
void commit_sim_tick() {
|
||||
ensure_initialized();
|
||||
s_current_snapshot_time += kSimPeriodDuration;
|
||||
s_current_snapshot_time += s_sim_period_duration;
|
||||
}
|
||||
|
||||
float sample_interpolation_step() {
|
||||
ensure_initialized();
|
||||
const float step =
|
||||
std::chrono::duration<float>(clock::now() - s_current_snapshot_time).count() / sim_pace();
|
||||
std::chrono::duration<float>(clock::now() - s_current_snapshot_time).count() / (1.0f / s_sim_rate_hz);
|
||||
return std::clamp(step, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,4 +23,8 @@ float sample_interpolation_step();
|
||||
|
||||
float consume_interval(const void* consumer);
|
||||
|
||||
// Runtime sim rate override (default 30 hz). Resets the frame timer.
|
||||
void set_sim_rate(float hz);
|
||||
float get_sim_rate();
|
||||
|
||||
} // namespace dusk::game_clock
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
#include "dusk/game_combos.h"
|
||||
|
||||
#include "SSystem/SComponent/c_API_controller_pad.h"
|
||||
#include "SSystem/SComponent/c_xyz.h"
|
||||
#include "d/actor/d_a_alink.h"
|
||||
#include "d/d_com_inf_game.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "m_Do/m_Do_controller_pad.h"
|
||||
|
||||
namespace dusk {
|
||||
namespace {
|
||||
|
||||
cXyz s_savedTeleportPos{};
|
||||
s16 s_savedTeleportAngle = 0;
|
||||
bool s_hasTeleportPos = false;
|
||||
|
||||
static daAlink_c* getPlayer() {
|
||||
return (daAlink_c*)dComIfGp_getPlayer(0);
|
||||
}
|
||||
|
||||
static void consumeButtons(u32 mask) {
|
||||
mDoCPd_c::getCpadInfo(PAD_1).mPressedButtonFlags &= ~mask;
|
||||
mDoCPd_c::getCpadInfo(PAD_1).mButtonFlags &= ~mask;
|
||||
}
|
||||
|
||||
// Table: holdMask, trigMask, strict, condition, action, consumeMask, exclusive
|
||||
static const GameCombo kCombos[] = {
|
||||
// Move Link (L+R+Y), pass-through, non-exclusive
|
||||
{
|
||||
PAD_TRIGGER_R | PAD_TRIGGER_L,
|
||||
PAD_BUTTON_Y,
|
||||
false,
|
||||
[] { return (bool)getSettings().game.enableMoveLinkCombo; },
|
||||
[] { getTransientSettings().moveLinkActive = !getTransientSettings().moveLinkActive; },
|
||||
0,
|
||||
false,
|
||||
},
|
||||
// Quick Transform (R+Y, strictly only R held)
|
||||
{
|
||||
PAD_TRIGGER_R,
|
||||
PAD_BUTTON_Y,
|
||||
true,
|
||||
[] { return getPlayer() != nullptr; },
|
||||
[] { getPlayer()->handleQuickTransform(); },
|
||||
PAD_BUTTON_Y,
|
||||
false,
|
||||
},
|
||||
// Wolf Howl (R+X)
|
||||
{
|
||||
PAD_TRIGGER_R,
|
||||
PAD_BUTTON_X,
|
||||
false,
|
||||
[] { return getPlayer() != nullptr; },
|
||||
[] { getPlayer()->handleWolfHowl(); },
|
||||
PAD_BUTTON_X,
|
||||
false,
|
||||
},
|
||||
// Teleport save (R+D-pad Up), consumes D-pad Up, exclusive
|
||||
{
|
||||
PAD_TRIGGER_R,
|
||||
PAD_BUTTON_UP,
|
||||
false,
|
||||
[] { return getSettings().game.enableTeleportCombo && getPlayer() != nullptr; },
|
||||
[] {
|
||||
auto* p = getPlayer();
|
||||
s_savedTeleportPos = p->current.pos;
|
||||
s_savedTeleportAngle = p->shape_angle.y;
|
||||
s_hasTeleportPos = true;
|
||||
},
|
||||
PAD_BUTTON_UP,
|
||||
true,
|
||||
},
|
||||
// Teleport load (R+D-pad Down), consumes D-pad Down, exclusive
|
||||
{
|
||||
PAD_TRIGGER_R,
|
||||
PAD_BUTTON_DOWN,
|
||||
false,
|
||||
[] {
|
||||
return getSettings().game.enableTeleportCombo && s_hasTeleportPos &&
|
||||
getPlayer() != nullptr;
|
||||
},
|
||||
[] {
|
||||
auto* p = getPlayer();
|
||||
p->current.pos = s_savedTeleportPos;
|
||||
p->shape_angle.y = s_savedTeleportAngle;
|
||||
p->mNormalSpeed = 0.0f;
|
||||
},
|
||||
PAD_BUTTON_DOWN,
|
||||
true,
|
||||
},
|
||||
// Moon Jump (R+A, hold), continuous, pass-through, non-exclusive
|
||||
{
|
||||
PAD_TRIGGER_R | PAD_BUTTON_A,
|
||||
0,
|
||||
false,
|
||||
[] { return getSettings().game.moonJump && getPlayer() != nullptr; },
|
||||
[] { getPlayer()->speed.y = 56.0f; },
|
||||
0,
|
||||
false,
|
||||
},
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void processGameCombos() {
|
||||
if (!getSettings().game.enableMoveLinkCombo) {
|
||||
getTransientSettings().moveLinkActive = false;
|
||||
}
|
||||
|
||||
const u32 held = mDoCPd_c::getHold(PAD_1);
|
||||
const u32 trig = mDoCPd_c::getTrig(PAD_1);
|
||||
|
||||
for (const auto& combo : kCombos) {
|
||||
if ((held & combo.holdMask) != combo.holdMask) {
|
||||
continue;
|
||||
}
|
||||
if (combo.strict && (held & ~combo.trigMask) != combo.holdMask) {
|
||||
continue;
|
||||
}
|
||||
if (combo.trigMask != 0 && !(trig & combo.trigMask)) {
|
||||
continue;
|
||||
}
|
||||
if (combo.condition != nullptr && !combo.condition()) {
|
||||
continue;
|
||||
}
|
||||
combo.action();
|
||||
if (combo.consumeMask != 0) {
|
||||
consumeButtons(combo.consumeMask);
|
||||
}
|
||||
if (combo.exclusive) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dusk
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <dolphin/types.h>
|
||||
|
||||
namespace dusk {
|
||||
|
||||
struct GameCombo {
|
||||
using ConditionFn = bool(*)();
|
||||
using ActionFn = void(*)();
|
||||
|
||||
u32 holdMask; // all of these must be held (getHold)
|
||||
u32 trigMask; // at least one must be newly triggered (getTrig); 0 = fires every frame while holdMask is held
|
||||
bool strict; // if true: (held & ~trigMask) must equal holdMask exactly, no extra buttons held
|
||||
ConditionFn condition; // extra game-state guard; nullptr = no extra check
|
||||
ActionFn action; // runs when the combo is fired
|
||||
u32 consumeMask; // buttons to clear after firing; 0 = pass-through
|
||||
bool exclusive; // stop evaluating future combos if this fires
|
||||
};
|
||||
|
||||
void processGameCombos();
|
||||
|
||||
} // namespace dusk
|
||||
@@ -238,12 +238,6 @@ namespace dusk {
|
||||
getTransientSettings().skipFrameRateLimit = getSettings().game.enableTurboKeybind &&
|
||||
(ImGui::IsKeyDown(ImGuiKey_Tab) || getActionBindHoldAnyPort(ActionBinds::TURBO_SPEED_BUTTON));
|
||||
|
||||
if (dusk::frame_interp::get_ui_tick_pending() && mDoMain::developmentMode == 1 && (mDoCPd_c::getHold(PAD_1) & (PAD_TRIGGER_R | PAD_TRIGGER_L)) == (PAD_TRIGGER_R | PAD_TRIGGER_L) && mDoCPd_c::getTrigY(PAD_1)) {
|
||||
getTransientSettings().moveLinkActive = !getTransientSettings().moveLinkActive;
|
||||
}
|
||||
if (mDoMain::developmentMode != 1) {
|
||||
getTransientSettings().moveLinkActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ImGuiConsole::PreDraw() {
|
||||
|
||||
@@ -93,6 +93,26 @@ namespace dusk {
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
auto& triggerView = getTransientSettings().triggerView;
|
||||
if (ImGui::BeginMenu("Trigger View")) {
|
||||
ImGui::Checkbox("Load Zones", &triggerView.loadZones);
|
||||
ImGui::Checkbox("Event Areas", &triggerView.eventAreas);
|
||||
ImGui::Checkbox("Event Tags", &triggerView.eventTags);
|
||||
ImGui::Checkbox("Switch Areas", &triggerView.switchAreas);
|
||||
ImGui::Checkbox("Midna Stops", &triggerView.midnaStops);
|
||||
ImGui::Checkbox("Twilight Gates", &triggerView.twilightGates);
|
||||
ImGui::Checkbox("Checkpoints", &triggerView.checkpoints);
|
||||
ImGui::Checkbox("Paths", &triggerView.paths);
|
||||
ImGui::Separator();
|
||||
ImGui::Checkbox("Transform Distances", &triggerView.transformDists);
|
||||
ImGui::Checkbox("Attention Distances", &triggerView.attentionDists);
|
||||
ImGui::Checkbox("Purple Mist Avoid", &triggerView.purpleMistAvoid);
|
||||
ImGui::Checkbox("Leever Ranges", &triggerView.leevers);
|
||||
ImGui::Separator();
|
||||
ImGui::SliderFloat("Opacity##triggers", &triggerView.opacity, 0.0f, 100.0f);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (!dusk::IsGameLaunched) {
|
||||
ImGui::BeginDisabled();
|
||||
}
|
||||
|
||||
+21
-2
@@ -154,7 +154,9 @@ UserSettings g_userSettings = {
|
||||
.recordingMode {"game.recordingMode", false},
|
||||
.removeQuestMapMarkers {"game.removeQuestMapMarkers", false},
|
||||
.showInputViewer {"game.showInputViewer", false},
|
||||
.showInputViewerGyro {"game.showInputViewerGyro", false}
|
||||
.showInputViewerGyro {"game.showInputViewerGyro", false},
|
||||
.enableMoveLinkCombo {"game.enableMoveLinkCombo", false},
|
||||
.enableTeleportCombo {"game.enableTeleportCombo", false}
|
||||
},
|
||||
|
||||
.backend = {
|
||||
@@ -302,6 +304,8 @@ void registerSettings() {
|
||||
Register(g_userSettings.game.removeQuestMapMarkers);
|
||||
Register(g_userSettings.game.showInputViewer);
|
||||
Register(g_userSettings.game.showInputViewerGyro);
|
||||
Register(g_userSettings.game.enableMoveLinkCombo);
|
||||
Register(g_userSettings.game.enableTeleportCombo);
|
||||
Register(g_userSettings.game.fastSpinner);
|
||||
Register(g_userSettings.game.infiniteHearts);
|
||||
Register(g_userSettings.game.infiniteArrows);
|
||||
@@ -392,7 +396,22 @@ static TransientSettings g_transientSettings = {
|
||||
.colliderViewOpacity = 50.0f,
|
||||
.drawRange = 100.0f,
|
||||
},
|
||||
.skipFrameRateLimit = false,
|
||||
.triggerView = {
|
||||
.loadZones = false,
|
||||
.eventAreas = false,
|
||||
.switchAreas = false,
|
||||
.eventTags = false,
|
||||
.midnaStops = false,
|
||||
.twilightGates = false,
|
||||
.checkpoints = false,
|
||||
.paths = false,
|
||||
.transformDists = false,
|
||||
.attentionDists = false,
|
||||
.purpleMistAvoid = false,
|
||||
.leevers = false,
|
||||
.opacity = 75.0f,
|
||||
},
|
||||
.skipFrameRateLimit = false
|
||||
};
|
||||
|
||||
TransientSettings& getTransientSettings() {
|
||||
|
||||
@@ -282,6 +282,8 @@ struct UserSettings {
|
||||
ConfigVar<bool> removeQuestMapMarkers;
|
||||
ConfigVar<bool> showInputViewer;
|
||||
ConfigVar<bool> showInputViewerGyro;
|
||||
ConfigVar<bool> enableMoveLinkCombo;
|
||||
ConfigVar<bool> enableTeleportCombo;
|
||||
} game;
|
||||
|
||||
struct {
|
||||
@@ -323,8 +325,25 @@ struct CollisionViewSettings {
|
||||
float drawRange;
|
||||
};
|
||||
|
||||
struct TriggerViewSettings {
|
||||
bool loadZones;
|
||||
bool eventAreas;
|
||||
bool switchAreas;
|
||||
bool eventTags;
|
||||
bool midnaStops;
|
||||
bool twilightGates;
|
||||
bool checkpoints;
|
||||
bool paths;
|
||||
bool transformDists;
|
||||
bool attentionDists;
|
||||
bool purpleMistAvoid;
|
||||
bool leevers;
|
||||
float opacity;
|
||||
};
|
||||
|
||||
struct TransientSettings {
|
||||
CollisionViewSettings collisionView;
|
||||
TriggerViewSettings triggerView;
|
||||
bool skipFrameRateLimit;
|
||||
bool moveLinkActive;
|
||||
bool stateShareLoadActive;
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
#include "command_console.hpp"
|
||||
|
||||
#include <RmlUi/Core.h>
|
||||
#include <RmlUi/Core/Elements/ElementFormControlInput.h>
|
||||
#include <SDL3/SDL_keyboard.h>
|
||||
#include <aurora/rmlui.hpp>
|
||||
#include <imgui.h>
|
||||
|
||||
#include "dusk/settings.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "fmt/format.h"
|
||||
#include "ui.hpp"
|
||||
|
||||
namespace dusk::ui {
|
||||
namespace {
|
||||
|
||||
static const Rml::String kDocumentSource = R"RML(
|
||||
<rml>
|
||||
<head>
|
||||
<link type="text/rcss" href="res/rml/command_console.rcss" />
|
||||
</head>
|
||||
<body>
|
||||
<console id="console">
|
||||
<output id="console-output"></output>
|
||||
<input id="console-input" type="text" maxlength="255" />
|
||||
</console>
|
||||
</body>
|
||||
</rml>
|
||||
)RML";
|
||||
|
||||
static bool isCommand(std::string_view text) {
|
||||
return text.size() >= 2 && text[0] == '>' && text[1] == ' ';
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
CommandConsole::CommandConsole() : Document(kDocumentSource) {
|
||||
mConsole = mDocument ? mDocument->GetElementById("console") : nullptr;
|
||||
mOutput = mDocument ? mDocument->GetElementById("console-output") : nullptr;
|
||||
auto* rawInput = mDocument ? mDocument->GetElementById("console-input") : nullptr;
|
||||
mInput = rmlui_dynamic_cast<Rml::ElementFormControlInput*>(rawInput);
|
||||
|
||||
listen(
|
||||
Rml::EventId::Keydown,
|
||||
[this](Rml::Event& event) {
|
||||
if (!mInputActive) {
|
||||
return;
|
||||
}
|
||||
const auto key = static_cast<Rml::Input::KeyIdentifier>(event.GetParameter<int>("key_identifier", Rml::Input::KI_UNKNOWN));
|
||||
if (key == Rml::Input::KI_RETURN) {
|
||||
executeFromInput();
|
||||
event.StopImmediatePropagation();
|
||||
} else if (key == Rml::Input::KI_ESCAPE) {
|
||||
hide(true);
|
||||
event.StopImmediatePropagation();
|
||||
} else if (key == Rml::Input::KI_UP) {
|
||||
navigateHistory(-1);
|
||||
event.StopImmediatePropagation();
|
||||
} else if (key == Rml::Input::KI_DOWN) {
|
||||
navigateHistory(+1);
|
||||
event.StopImmediatePropagation();
|
||||
}
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
bool CommandConsole::handle_nav_command(Rml::Event&, NavCommand) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void CommandConsole::update() {
|
||||
if (!getSettings().backend.enableAdvancedSettings) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float dt = std::max(ImGui::GetIO().DeltaTime, 0.0f);
|
||||
for (auto& line : mOutputLines) {
|
||||
line.remain -= dt;
|
||||
}
|
||||
const auto [first, last] =
|
||||
std::ranges::remove_if(mOutputLines, [](const OutputLine& l) { return l.remain <= 0.0f; });
|
||||
mOutputLines.erase(first, last);
|
||||
|
||||
if (mOutputLines.empty() && !mInputActive) {
|
||||
Document::hide(mPendingClose);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mOutput == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
Rml::String html;
|
||||
if (mInputActive) {
|
||||
for (const auto& msg : mMsgHistory) {
|
||||
html += isCommand(msg) ? "<line class=\"cmd\">" + escape(msg) + "</line>" : "<line>" + escape(msg) + "</line>";
|
||||
}
|
||||
mOutput->SetAttribute("open", "");
|
||||
} else {
|
||||
const int total = (int)mOutputLines.size();
|
||||
const int startIdx = std::max(0, total - kMaxVisibleLines);
|
||||
for (int i = startIdx; i < total; ++i) {
|
||||
const auto& line = mOutputLines[i];
|
||||
const float alpha = line.remain < kFadeSeconds ? line.remain / kFadeSeconds : 1.0f;
|
||||
const Rml::String cls = isCommand(line.text) ? " class=\"cmd\"" : "";
|
||||
html += fmt::format(FMT_STRING("<line{} style=\"opacity: {:.3f}\">{}</line>"), cls,
|
||||
alpha, escape(line.text));
|
||||
}
|
||||
mOutput->RemoveAttribute("open");
|
||||
}
|
||||
|
||||
mOutput->SetInnerRML(html);
|
||||
|
||||
if (mScrollToBottom && mInputActive) {
|
||||
mOutput->SetScrollTop(1e9f);
|
||||
mScrollToBottom = false;
|
||||
}
|
||||
}
|
||||
|
||||
void CommandConsole::show() {
|
||||
if (mDocument != nullptr) {
|
||||
mDocument->Show(Rml::ModalFlag::None, Rml::FocusFlag::None, Rml::ScrollFlag::None);
|
||||
}
|
||||
mInputActive = true;
|
||||
mScrollToBottom = true;
|
||||
if (mConsole != nullptr) {
|
||||
mConsole->SetAttribute("open", "");
|
||||
}
|
||||
focus();
|
||||
}
|
||||
|
||||
bool CommandConsole::focus() {
|
||||
if (mInput != nullptr) {
|
||||
mInput->SetValue("");
|
||||
aurora::rmlui::set_input_type(aurora::rmlui::InputType::Text);
|
||||
return mInput->Focus(true);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CommandConsole::hide(bool close) {
|
||||
mInputActive = false;
|
||||
mHistoryPos = -1;
|
||||
if (mConsole != nullptr) {
|
||||
mConsole->RemoveAttribute("open");
|
||||
}
|
||||
if (mInput != nullptr) {
|
||||
mInput->SetValue("");
|
||||
}
|
||||
mPendingClose = close;
|
||||
// Immediately refocus
|
||||
if (auto* doc = top_document()) {
|
||||
doc->focus();
|
||||
}
|
||||
}
|
||||
|
||||
void CommandConsole::executeFromInput() {
|
||||
if (mInput == nullptr) {
|
||||
return;
|
||||
}
|
||||
const Rml::String value = mInput->GetValue();
|
||||
hide(true);
|
||||
if (!value.empty()) {
|
||||
runCommand(value, mState, [this](std::string text) { ConsolePrint(std::move(text)); });
|
||||
}
|
||||
mScrollToBottom = true;
|
||||
}
|
||||
|
||||
void CommandConsole::navigateHistory(int dir) {
|
||||
if (mState.history.empty() || mInput == nullptr) {
|
||||
return;
|
||||
}
|
||||
const int prev = mHistoryPos;
|
||||
if (dir < 0) {
|
||||
if (mHistoryPos == -1) {
|
||||
mHistoryPos = (int)mState.history.size() - 1;
|
||||
} else if (mHistoryPos > 0) {
|
||||
--mHistoryPos;
|
||||
}
|
||||
} else {
|
||||
if (mHistoryPos != -1 && ++mHistoryPos >= (int)mState.history.size()) {
|
||||
mHistoryPos = -1;
|
||||
}
|
||||
}
|
||||
if (prev != mHistoryPos) {
|
||||
const char* str = mHistoryPos >= 0 ? mState.history[mHistoryPos].c_str() : "";
|
||||
mInput->SetValue(str);
|
||||
const int end = static_cast<int>(Rml::StringUtilities::LengthUTF8(mInput->GetValue()));
|
||||
mInput->SetSelectionRange(end, end);
|
||||
}
|
||||
}
|
||||
|
||||
void CommandConsole::ConsolePrint(std::string text) {
|
||||
mMsgHistory.push_back(text);
|
||||
if ((int)mMsgHistory.size() > kMaxMsgHistory) {
|
||||
mMsgHistory.erase(mMsgHistory.begin());
|
||||
}
|
||||
mOutputLines.push_back({std::move(text), kDurationNormal});
|
||||
mScrollToBottom = true;
|
||||
if ((int)mOutputLines.size() > kMaxStoredLines) {
|
||||
mOutputLines.erase(mOutputLines.begin());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dusk::ui
|
||||
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include "document.hpp"
|
||||
#include "dusk/commands.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Rml {
|
||||
class ElementFormControlInput;
|
||||
}
|
||||
|
||||
namespace dusk::ui {
|
||||
|
||||
class CommandConsole : public Document {
|
||||
public:
|
||||
CommandConsole();
|
||||
|
||||
void update() override;
|
||||
void show() override;
|
||||
bool focus() override;
|
||||
void hide(bool close) override;
|
||||
|
||||
private:
|
||||
struct OutputLine {
|
||||
std::string text;
|
||||
float remain;
|
||||
};
|
||||
|
||||
static constexpr float kDurationNormal = 6.0f;
|
||||
static constexpr float kFadeSeconds = 0.8f;
|
||||
static constexpr int kMaxStoredLines = 64;
|
||||
static constexpr int kMaxVisibleLines = 24;
|
||||
static constexpr int kMaxMsgHistory = 500;
|
||||
|
||||
Rml::Element* mConsole = nullptr;
|
||||
Rml::Element* mOutput = nullptr;
|
||||
Rml::ElementFormControlInput* mInput = nullptr;
|
||||
|
||||
std::vector<OutputLine> mOutputLines;
|
||||
std::vector<std::string> mMsgHistory;
|
||||
int mHistoryPos = -1;
|
||||
bool mInputActive = false;
|
||||
bool mScrollToBottom = false;
|
||||
|
||||
CommandState mState;
|
||||
|
||||
bool handle_nav_command(Rml::Event& event, NavCommand cmd) override;
|
||||
|
||||
void ConsolePrint(std::string text);
|
||||
void executeFromInput();
|
||||
void navigateHistory(int dir);
|
||||
};
|
||||
|
||||
} // namespace dusk::ui
|
||||
@@ -84,6 +84,7 @@ void Document::show() {
|
||||
focus();
|
||||
}
|
||||
}
|
||||
mPendingClose = false;
|
||||
}
|
||||
|
||||
void Document::hide(bool close) {
|
||||
|
||||
@@ -65,6 +65,7 @@ public:
|
||||
uncover_top_document();
|
||||
}
|
||||
|
||||
bool pending_close() const { return mPendingClose; }
|
||||
bool closed() const { return mClosed; }
|
||||
|
||||
bool handle_nav_event(Rml::Event& event);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "dusk/settings.h"
|
||||
#include "dusk/speedrun.h"
|
||||
#include "editor.hpp"
|
||||
#include "f_op/f_op_scene_mng.h"
|
||||
#include "f_pc/f_pc_manager.h"
|
||||
#include "f_pc/f_pc_name.h"
|
||||
#include "imgui.h"
|
||||
@@ -142,6 +143,10 @@ MenuBar::MenuBar()
|
||||
if (getSettings().game.liveSplitEnabled) {
|
||||
dusk::speedrun::reset();
|
||||
}
|
||||
auto* playScene = fpcM_SearchByName(fpcNm_PLAY_SCENE_e);
|
||||
if (playScene != nullptr) {
|
||||
fopScnM_ChangeReq((scene_class*)playScene, fpcNm_NAME_SCENE_e, 0x7FFF, 0);
|
||||
}
|
||||
hide(false);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1536,6 +1536,20 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
"Recording Mode",
|
||||
"Disables the game HUD and all background music.<br/><br/>Useful for recording footage.");
|
||||
});
|
||||
|
||||
add_tab("Tools", [this](Rml::Element* content) {
|
||||
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
|
||||
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
|
||||
|
||||
leftPane.add_section("Link");
|
||||
add_speedrun_disabled_option(leftPane, rightPane, getSettings().game.enableMoveLinkCombo,
|
||||
"Move Link (L+R+Y)",
|
||||
"Enables the L+R+Y button combo to toggle freely repositioning Link.");
|
||||
add_speedrun_disabled_option(leftPane, rightPane, getSettings().game.enableTeleportCombo,
|
||||
"Teleport (R+D-pad Up/Down)",
|
||||
"R+D-pad Up stores Link's current position.<br/>"
|
||||
"R+D-pad Down teleports Link back to it.");
|
||||
});
|
||||
}
|
||||
|
||||
void SettingsWindow::update() {
|
||||
|
||||
+15
-1
@@ -18,6 +18,7 @@
|
||||
#include "dusk/config.hpp"
|
||||
#include "dusk/io.hpp"
|
||||
#include <borealis/io.hpp>
|
||||
#include "command_console.hpp"
|
||||
#include "icon_provider.hpp"
|
||||
#include "input.hpp"
|
||||
#include "mod_texture_provider.hpp"
|
||||
@@ -216,6 +217,19 @@ void handle_event(const SDL_Event& event) noexcept {
|
||||
sConnectedGamepads.erase(event.gdevice.which);
|
||||
}
|
||||
input::handle_event(event);
|
||||
// TODO: don't overlap with PAD bindings?
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_SLASH) {
|
||||
bool found = false;
|
||||
for (auto& doc : sDocumentStack) {
|
||||
if (auto* console = dynamic_cast<CommandConsole*>(doc.get())) {
|
||||
console->show();
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
push_document(std::make_unique<CommandConsole>(), true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool register_scoped_styles(DocumentScope scope, std::string id, const std::string& rcss) noexcept {
|
||||
@@ -269,7 +283,7 @@ void uncover_top_document() noexcept {
|
||||
|
||||
bool any_document_visible() noexcept {
|
||||
return std::any_of(sDocumentStack.begin(), sDocumentStack.end(),
|
||||
[](const auto& doc) { return doc && doc->visible(); });
|
||||
[](const auto& doc) { return doc && doc->visible() && !doc->pending_close(); });
|
||||
}
|
||||
|
||||
bool is_prelaunch_open() noexcept {
|
||||
|
||||
@@ -14,11 +14,4 @@ namespace dusk::utils {
|
||||
std::string base64_encode(const std::vector<uint8_t>& data);
|
||||
bool base64_decode(const std::string& text, std::vector<uint8_t>& out);
|
||||
uint32_t crc32(const void* data, size_t size);
|
||||
|
||||
struct PaneCache {
|
||||
uint64_t tag;
|
||||
float origTransX;
|
||||
float origTransY;
|
||||
bool cached;
|
||||
};
|
||||
} // namespace dusk::utils
|
||||
|
||||
@@ -759,23 +759,7 @@ static void duskExecute() {
|
||||
isRecording = false;
|
||||
}
|
||||
|
||||
if (mDoCPd_c::getHoldR(PAD_1) && mDoCPd_c::getTrigX(PAD_1)) {
|
||||
if (const auto link = g_dComIfG_gameInfo.play.getPlayer(0)) {
|
||||
dynamic_cast<daAlink_c*>(link)->handleWolfHowl();
|
||||
}
|
||||
}
|
||||
|
||||
if ((mDoCPd_c::getHold(PAD_1) & (PAD_TRIGGER_R | PAD_TRIGGER_L)) == PAD_TRIGGER_R && mDoCPd_c::getTrigY(PAD_1)) {
|
||||
if (const auto link = g_dComIfG_gameInfo.play.getPlayer(0)) {
|
||||
dynamic_cast<daAlink_c*>(link)->handleQuickTransform();
|
||||
}
|
||||
}
|
||||
|
||||
if (dusk::getSettings().game.moonJump && (mDoCPd_c::getHoldR(PAD_1) && mDoCPd_c::getHoldA(PAD_1))) {
|
||||
if (const auto link = g_dComIfG_gameInfo.play.getPlayer(0)) {
|
||||
link->speed.y = 56.0f;
|
||||
}
|
||||
}
|
||||
|
||||
if (dusk::getSettings().game.fastSpinner && mDoCPd_c::getHoldR(PAD_1)) {
|
||||
if (const auto link = g_dComIfG_gameInfo.play.getPlayer(0)) {
|
||||
|
||||
+19
-3
@@ -60,6 +60,8 @@
|
||||
#include "dusk/frame_interpolation.h"
|
||||
#include "dusk/game_clock.h"
|
||||
#include "dusk/gyro.h"
|
||||
#include "dusk/commands.hpp"
|
||||
#include "dusk/game_combos.h"
|
||||
#include "dusk/imgui/ImGuiConsole.hpp"
|
||||
#include "dusk/imgui/ImGuiEngine.hpp"
|
||||
#include "dusk/iso_validate.hpp"
|
||||
@@ -70,6 +72,7 @@
|
||||
#include "dusk/mouse.h"
|
||||
#include "dusk/os.h"
|
||||
#include "dusk/presentation.hpp"
|
||||
#include "dusk/ui/command_console.hpp"
|
||||
#include "dusk/ui/menu_bar.hpp"
|
||||
#include "dusk/ui/overlay.hpp"
|
||||
#include "dusk/ui/prelaunch.hpp"
|
||||
@@ -300,7 +303,9 @@ void main01(void) {
|
||||
mDoCPd_c::read();
|
||||
dusk::mouse::read();
|
||||
dusk::gyro::read(pacing.sim_pace);
|
||||
dusk::processGameCombos();
|
||||
fapGm_Execute();
|
||||
dusk::processCameraCommands();
|
||||
mDoAud_Execute();
|
||||
dusk::game_clock::commit_sim_tick();
|
||||
}
|
||||
@@ -316,13 +321,15 @@ void main01(void) {
|
||||
dusk::frame_interp::end_presentation_camera();
|
||||
dusk::frame_interp::set_ui_tick_pending(false);
|
||||
} else {
|
||||
dusk::frame_interp::begin_frame(dusk::FrameInterpMode::Off, true, 0.0f);
|
||||
dusk::frame_interp::set_ui_tick_pending(true);
|
||||
|
||||
// Game Inputs
|
||||
mDoCPd_c::read();
|
||||
dusk::mouse::read();
|
||||
dusk::gyro::read(pacing.presentation_dt_seconds);
|
||||
dusk::processGameCombos();
|
||||
dusk::processCameraCommands();
|
||||
|
||||
dusk::frame_interp::begin_frame(dusk::FrameInterpMode::Off, true, 0.0f);
|
||||
dusk::frame_interp::set_ui_tick_pending(true);
|
||||
|
||||
// EXECUTE GAME LOGIC & RENDER
|
||||
// This calls mDoGph_Painter -> JFWDisplay -> GX Functions
|
||||
@@ -354,6 +361,15 @@ void main01(void) {
|
||||
|
||||
Limiter::duration_t sleepTime = main_loop_limiter.Sleep(target_ns);
|
||||
dusk::frameUsagePct = 100.0f * (1.0f - static_cast<float>(sleepTime) / static_cast<float>(target_ns));
|
||||
} else if (!pacing.is_interpolating && !dusk::getTransientSettings().skipFrameRateLimit) {
|
||||
// Non-interp: throttle display rate to the configured sim rate so /rate works
|
||||
const double sim_fps = static_cast<double>(dusk::game_clock::get_sim_rate());
|
||||
const Limiter::duration_t sim_target_ns = static_cast<Limiter::duration_t>(1'000'000'000.0 / sim_fps);
|
||||
main_loop_limiter.Sleep(sim_target_ns);
|
||||
} else if (dusk::getTransientSettings().skipFrameRateLimit) {
|
||||
// Turbo: cap at 120 hz rather than running fully unlimited
|
||||
constexpr Limiter::duration_t kTurboTargetNs = 1'000'000'000LL / 120LL;
|
||||
main_loop_limiter.Sleep(kTurboTargetNs);
|
||||
} else {
|
||||
main_loop_limiter.Reset();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user