Merge remote-tracking branch 'origin/main' into feature/autosave

# Conflicts:
#	src/dusk/imgui/ImGuiMenuEnhancements.cpp
#	src/dusk/settings.cpp
#	src/f_ap/f_ap_game.cpp
This commit is contained in:
MelonSpeedruns
2026-04-24 12:54:40 -04:00
77 changed files with 2163 additions and 929 deletions
+5 -5
View File
@@ -117,8 +117,8 @@ static Z2WolfHowlLine sNewSong3[9] = {
#if TARGET_PC
static Z2WolfHowlLine sHowlTimeSong[6] = {
{HOWL_LINE_MID, 20}, {HOWL_LINE_LOW, 20}, {HOWL_LINE_HIGH, 40},
{HOWL_LINE_MID, 20}, {HOWL_LINE_LOW, 20}, {HOWL_LINE_HIGH, 40},
{HOWL_LINE_MID, 15}, {HOWL_LINE_LOW, 15}, {HOWL_LINE_HIGH, 30},
{HOWL_LINE_MID, 15}, {HOWL_LINE_LOW, 15}, {HOWL_LINE_HIGH, 30},
};
#endif
@@ -368,9 +368,9 @@ void Z2WolfHowlMgr::setCorrectData(s8 curveID, Z2WolfHowlData* data) {
break;
#if TARGET_PC
case Z2WOLFHOWL_TIMESONG:
cPitchUp = 1.259906f;
cPitchCenter = 0.94387f;
cPitchDown = 0.840885f;
cPitchUp = 1.3348f;
cPitchCenter = 0.8909f;
cPitchDown = 0.7937f;
break;
#endif
default:
+8 -2
View File
@@ -41,7 +41,7 @@ void daAlink_c::handleWolfHowl() {
return;
}
bool canTransform = false;
bool canHowl = false;
if (mLinkAcch.ChkGroundHit() && !checkModeFlg(MODE_PLAYER_FLY) && !checkMagneBootsOn()) {
if (!checkForestOldCentury()) {
@@ -52,12 +52,17 @@ void daAlink_c::handleWolfHowl() {
(checkEventRun() || getMidnaActor()->checkMetamorphoseEnable()) &&
(checkModeFlg(4) || dComIfGp_checkPlayerStatus0(0, 0x10))))
{
canTransform = true;
canHowl = true;
}
}
}
}
if (!canHowl) {
Z2GetAudioMgr()->seStart(Z2SE_SYS_ERROR, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0);
return;
}
getWolfHowlMgrP()->setCorrectCurve(9);
procWolfHowlDemoInit();
}
@@ -154,6 +159,7 @@ bool daAlink_c::checkGyroAimContext() {
case PROC_BOW_SUBJECT:
case PROC_BOOMERANG_SUBJECT:
case PROC_COPY_ROD_SUBJECT:
case PROC_HAWK_SUBJECT:
case PROC_HOOKSHOT_SUBJECT:
case PROC_SWIM_HOOKSHOT_SUBJECT:
case PROC_HORSE_BOW_SUBJECT:
+54
View File
@@ -149,6 +149,23 @@ void daAlink_c::changeWolf() {
mpLinkModel = initModel(static_cast<J3DModelData*>(dComIfG_getObjectRes(l_wArcName, 14)), 0x20200);
#ifdef TARGET_PC
// Update Wolf Link's eye maxLOD to prevent the eyes from disappearing
{
J3DTexture* tex = mpLinkModel->getModelData()->getTexture();
JUTNameTab* nametable = mpLinkModel->getModelData()->getTextureName();
if (tex != nullptr && nametable != nullptr) {
for (u16 i = 0; i < tex->getNum(); i++) {
const char* tex_name = nametable->getName(i);
if (tex_name != NULL && strcmp(tex_name, "wl_eyeball") == 0) {
ResTIMG* timg = tex->getResTIMG(i);
timg->maxLOD = 0;
}
}
}
}
#endif
J3DModelData* chainModelData = static_cast<J3DModelData*>(dComIfG_getObjectRes(l_wArcName, 15));
for (u16 i = 0; i < 4; i++) {
mpWlChainModels[i] = initModel(chainModelData, 0);
@@ -162,6 +179,23 @@ void daAlink_c::changeWolf() {
mpWlMidnaHairModel =
initModelEnv(static_cast<J3DModelData*>(dComIfG_getObjectRes(l_wArcName, 11)), 0x1000000);
#ifdef TARGET_PC
// Update Midna's eye maxLOD to prevent the eyes from disappearing
{
J3DTexture* tex = mpWlMidnaModel->getModelData()->getTexture();
JUTNameTab* nametable = mpWlMidnaModel->getModelData()->getTextureName();
if (tex != nullptr && nametable != nullptr) {
for (u16 i = 0; i < tex->getNum(); i++) {
const char* tex_name = nametable->getName(i);
if (tex_name != NULL && strcmp(tex_name, "midona_eyeball") == 0) {
ResTIMG* timg = tex->getResTIMG(i);
timg->maxLOD = 0;
}
}
}
}
#endif
mpDMidnaBrk = static_cast<J3DAnmTevRegKey*>(dComIfG_getObjectRes(l_wArcName, 18));
mpDMidnaBrk->searchUpdateMaterialID(mpWlMidnaModel->getModelData());
mpWlMidnaModel->getModelData()->entryTevRegAnimator(mpDMidnaBrk);
@@ -342,6 +376,26 @@ void daAlink_c::changeLink(int param_0) {
initModel(static_cast<J3DModelData*>(dComIfG_getObjectRes(mArcName, "zl_face.bmd")), 0x20200);
}
#ifdef TARGET_PC
// Update Adult Link's eye maxLOD to prevent the eyes from disappearing
{
J3DTexture* tex = mpLinkFaceModel->getModelData()->getTexture();
JUTNameTab* nametable = mpLinkFaceModel->getModelData()->getTextureName();
if (tex != nullptr && nametable != nullptr) {
for (u16 i = 0; i < tex->getNum(); i++) {
const char* tex_name = nametable->getName(i);
if (tex_name != nullptr &&
(strcmp(tex_name, "al_eyeball") == 0 || strcmp(tex_name, "highlight02") == 0 ||
strcmp(tex_name, "eye_kage01") == 0))
{
ResTIMG* timg = tex->getResTIMG(i);
timg->maxLOD = 0;
}
}
}
}
#endif
modelData = static_cast<J3DModelData*>(dComIfG_getObjectRes(mArcName, "al_bootsH.bmd"));
u16 i;
for (i = 0; i < 2; i++) {
+9 -1
View File
@@ -2059,7 +2059,15 @@ static void demo_camera(b_bq_class* i_this) {
for (int i = 0; i < 5; i++) {
static u16 g_e_i[] = {0x83EB, 0x83EC, 0x83ED, 0x83EE, 0x83EF};
dComIfGp_particle_set(g_e_i[i], &pos, NULL, NULL);
#if TARGET_PC
if (i == 0) {
static const cXyz effWideScale = {mDoGph_gInf_c::getAspect(), 1.0f, 1.0f};
dComIfGp_particle_set(g_e_i[i], &pos, NULL, &effWideScale);
} else
#endif
{
dComIfGp_particle_set(g_e_i[i], &pos, NULL, NULL);
}
}
i_this->mSound.startCreatureSound(Z2SE_EN_BOSS_CONVERGE, 0, 0);
+10 -1
View File
@@ -2725,7 +2725,16 @@ static void demo_camera(b_ob_class* i_this) {
for (int i = 0; i < 5; i++) {
static u16 ex_eff[] = {dPa_RM(ID_ZI_S_OI_CONVERGE_FILTER), dPa_RM(ID_ZI_S_OI_CONVERGE_FILTEROUT), dPa_RM(ID_ZI_S_OI_CONVERGE_HIDE), dPa_RM(ID_ZI_S_OI_CONVERGE_POLYGON_A), dPa_RM(ID_ZI_S_OI_CONVERGE_POLYGON_B)};
dComIfGp_particle_set(ex_eff[i], &room_pos, NULL, &sc);
#if TARGET_PC
if (i == 0) {
static const cXyz effWideScale = {mDoGph_gInf_c::getAspect() * 10.0f, 10.0f, 10.0f};
dComIfGp_particle_set(ex_eff[i], &room_pos, NULL, &effWideScale);
} else
#endif
{
dComIfGp_particle_set(ex_eff[i], &room_pos, NULL, &sc);
}
}
i_this->mDemoCamEye.set(-4820.0f, -18600.0f, -510.0f);
+1
View File
@@ -19,6 +19,7 @@
#if TARGET_PC
#include <f_ap/f_ap_game.h>
#include <dusk/autosave.h>
#endif
char* daDoor20_c::getStopBmdName() {
+10 -1
View File
@@ -1677,7 +1677,16 @@ static void demo_camera(e_fm_class* i_this) {
cXyz spBC(0.0f, 0.0f, 0.0f);
for (int i = 0; i < 4; i++) {
static u16 g_e_i[] = {0x847B, 0x847C, 0x847D, 0x847E};
dComIfGp_particle_set(g_e_i[i], &spBC, NULL, NULL);
#if TARGET_PC
if (i == 0) {
static const cXyz effWideScale = {mDoGph_gInf_c::getAspect(), 1.0f, 1.0f};
dComIfGp_particle_set(g_e_i[i], &spBC, NULL, &effWideScale);
} else
#endif
{
dComIfGp_particle_set(g_e_i[i], &spBC, NULL, NULL);
}
}
i_this->mDemoCamFovy = 55.0f + NREG_F(10);
+10 -10
View File
@@ -761,6 +761,11 @@ static void koro2_game(fshop_class* i_this) {
sp5C.x = mDoCPd_c::getStickX3D(PAD_1);
sp5C.y = 0.0f;
sp5C.z = mDoCPd_c::getStickY(PAD_1);
#if TARGET_PC
if (dusk::getSettings().game.enableMirrorMode) {
sp5C.x = -sp5C.x;
}
#endif
MtxPosition(&sp5C, &sp68);
f32 reg_f31 = sp68.x;
@@ -782,20 +787,15 @@ static void koro2_game(fshop_class* i_this) {
reg_f30 = 0.0f;
}
s16 gyro_ax = 0;
s16 gyro_az = 0;
#if TARGET_PC
if (dusk::getSettings().game.enableGyroRollgoal) {
s16 rg_add_x;
s16 rg_add_z;
dusk::gyro::rollgoalTableOffset(rg_add_x, rg_add_z);
s16 tgt_x = static_cast<s16>(reg_f30 * (-6000.0f + JREG_F(7))) + rg_add_x;
s16 tgt_z = static_cast<s16>(reg_f31 * (-6000.0f + JREG_F(8))) + rg_add_z;
cLib_addCalcAngleS2(&i_this->field_0x4020.x, tgt_x, 4, 0x200);
cLib_addCalcAngleS2(&i_this->field_0x4020.z, tgt_z, 4, 0x200);
dusk::gyro::rollgoalTableOffset(gyro_ax, gyro_az);
}
#else
cLib_addCalcAngleS2(&i_this->field_0x4020.x, reg_f30 * (-6000.0f + JREG_F(7)), 4, 0x200);
cLib_addCalcAngleS2(&i_this->field_0x4020.z, reg_f31 * (-6000.0f + JREG_F(8)), 4, 0x200);
#endif
cLib_addCalcAngleS2(&i_this->field_0x4020.x, reg_f30 * (-6000.0f + JREG_F(7)) + gyro_ax, 4, 0x200);
cLib_addCalcAngleS2(&i_this->field_0x4020.z, reg_f31 * (-6000.0f + JREG_F(8)) + gyro_az, 4, 0x200);
}
#if TARGET_PC
if (i_this->field_0x4010 != 2) {
+4
View File
@@ -30,6 +30,10 @@ static char* l_arcName = "Mirror";
static char* l_arcName2 = "MR-Table";
dMirror_packet_c::dMirror_packet_c() {
#ifdef TARGET_PC
GXInitTexObj(&mTexObj, nullptr, 0, 0, static_cast<GXTexFmt>(-1), GX_MAX_TEXWRAPMODE,
GX_MAX_TEXWRAPMODE, GX_FALSE);
#endif
reset();
}
+12 -3
View File
@@ -2855,7 +2855,7 @@ void* daMP_Reader(void*) {
#endif
}
static u8 daMP_ReadThreadStack[0x2000];
static u8 daMP_ReadThreadStack[DUSK_IF_ELSE(8, 0x2000)];
#if TARGET_PC
static BOOL VideoThreadCancelled;
@@ -2880,7 +2880,7 @@ static BOOL daMP_CreateReadThread(s32 param_0) {
static OSThread daMP_VideoDecodeThread;
static u8 daMP_VideoDecodeThreadStack[0x64000];
static u8 daMP_VideoDecodeThreadStack[DUSK_IF_ELSE(8, 0x64000)];
static OSMessageQueue daMP_FreeTextureSetQueue;
@@ -3132,7 +3132,7 @@ static BOOL AudioThreadCancelled;
static OSThread daMP_AudioDecodeThread;
static u8 daMP_AudioDecodeThreadStack[0x64000];
static u8 daMP_AudioDecodeThreadStack[DUSK_IF_ELSE(8, 0x64000)];
static OSMessageQueue daMP_FreeAudioBufferQueue;
@@ -4580,3 +4580,12 @@ actor_process_profile_definition g_profile_MOVIE_PLAYER = {
};
AUDIO_INSTANCES;
#if TARGET_PC
void dusk::MoviePlayerShutdown() {
// We need to cleanly shut down the threads to avoid crashes on shutdown.
if (daMP_c::m_myObj) {
daMP_c::m_myObj->daMP_c_Finish();
}
}
#endif
+43 -1
View File
@@ -16,6 +16,10 @@
#include "f_op/f_op_camera_mng.h"
#include "m_Do/m_Do_mtx.h"
#if TARGET_PC
#include "dusk/frame_interpolation.h"
#endif
static f32 Reflect(cXyz* i_vec, cBgS_PolyInfo const& i_polyinfo, f32 i_scale) {
cM3dGPla plane;
@@ -31,6 +35,27 @@ static f32 Reflect(cXyz* i_vec, cBgS_PolyInfo const& i_polyinfo, f32 i_scale) {
return 0.0f;
}
#if TARGET_PC
static void d_a_obj_item_interp_callback(bool isSimFrame, void* pUserWork) {
daItem_c* item = static_cast<daItem_c*>(pUserWork);
if (item == NULL || item->mpModel == NULL || !item->chkDraw()) {
return;
}
item->setTevStr();
if (item->mpBrkAnm != NULL) {
s8 tevFrm = item->getTevFrm();
if (tevFrm != -1) {
item->mpBrkAnm->entry(item->mpModel->getModelData(), tevFrm);
} else {
item->mpBrkAnm->entry(item->mpModel->getModelData());
}
}
if (item->chkFlag(4)) {
fopAcM_setEffectMtx(item, item->mpModel->getModelData());
}
}
#endif
const daItemBase_data& daItemBase_c::getData() {
return m_data;
}
@@ -353,6 +378,10 @@ int daItem_c::_daItem_draw() {
return 1;
}
#if TARGET_PC
dusk::frame_interp::add_interpolation_callback(&d_a_obj_item_interp_callback, this);
#endif
if (chkDraw()) {
return DrawBase();
}
@@ -390,6 +419,9 @@ void daItem_c::procMainNormal() {
cLib_chaseF(&scale.z, mItemScale.z, step_z);
}
#if TARGET_PC
if (!dusk::getSettings().game.enableIndefiniteItemDrops) {
#endif
if (mWaitTimer == 0) {
if (mDisappearTimer == 0) {
deleteItem();
@@ -399,6 +431,9 @@ void daItem_c::procMainNormal() {
changeDraw();
}
}
#if TARGET_PC
}
#endif
mCcCyl.SetC(current.pos);
dComIfG_Ccsp()->Set(&mCcCyl);
@@ -1058,9 +1093,16 @@ int daItem_c::CountTimer() {
if (checkCountTimer()) {
if (mWaitTimer > 0) {
mWaitTimer--;
} else if (mDisappearTimer > 0) {
}
#if TARGET_PC
else if (!dusk::getSettings().game.enableIndefiniteItemDrops && mDisappearTimer > 0) {
mDisappearTimer--;
}
#else
else if (mDisappearTimer > 0) {
mDisappearTimer--;
}
#endif
}
cLib_calcTimer<u8>(&mBoomWindTgTimer);
+6 -9
View File
@@ -699,8 +699,8 @@ void dFlower_packet_c::draw() {
if (!cLib_checkBit<u8>(sp44->m_state, 4) && !cLib_checkBit<u8>(sp44->m_state, 0x40)) {
#ifdef TARGET_PC
Mtx flower_mtx;
if (dusk::frame_interp::lookup_replacement(reinterpret_cast<const void*>(&sp44->m_modelMtx), flower_mtx)) {
if (dusk::frame_interp::lookup_replacement(&sp44->m_modelMtx, flower_mtx)) {
cMtx_concat(j3dSys.getViewMtx(), flower_mtx, flower_mtx);
GXLoadPosMtxImm(flower_mtx, 0);
} else
#endif
@@ -854,21 +854,18 @@ void dFlower_packet_c::draw() {
if (!cLib_checkBit<u8>(sp34->m_state, 4) && cLib_checkBit<u8>(sp34->m_state, 0x40)) {
#ifdef TARGET_PC
Mtx flower_mtx;
if (dusk::frame_interp::lookup_replacement(reinterpret_cast<const void*>(&sp34->m_modelMtx), flower_mtx)) {
if (dusk::frame_interp::lookup_replacement(&sp34->m_modelMtx, flower_mtx)) {
cMtx_concat(j3dSys.getViewMtx(), flower_mtx, flower_mtx);
GXLoadPosMtxImm(flower_mtx, 0);
} else {
} else
#endif
{
GXLoadPosMtxImm(sp34->m_modelMtx, 0);
#ifdef TARGET_PC
}
#endif
GXLoadNrmMtxImm(j3dSys.getViewMtx(), 0);
#if TARGET_PC
GXLoadTexObj(&mTexObj_l_J_Ohana01_64128_0419TEX, GX_TEXMAP0);
#endif
if (!cLib_checkBit<u8>(sp34->m_state, 8)) {
if (!cLib_checkBit<u8>(sp34->m_state, 0x10)) {
GXCallDisplayList(mp_Jhana01DL, m_Jhana01DL_size);
@@ -995,7 +992,7 @@ void dFlower_packet_c::update() {
mDoMtx_stack_c::scaleM(temp_f31, temp_f31, temp_f31);
cMtx_concat(j3dSys.getViewMtx(), temp_r28, data_p->m_modelMtx);
#ifdef TARGET_PC
dusk::frame_interp::record_final_mtx(mDoMtx_stack_c::get(), data_p->m_modelMtx);
dusk::frame_interp::record_final_mtx(temp_r28, data_p->m_modelMtx);
#endif
}
}
+3 -7
View File
@@ -70,11 +70,7 @@ dFs_HIO_c::dFs_HIO_c() {
select_icon_appear_frames = 5;
appear_display_wait_frames = 15;
field_0x000d = 15;
#if TARGET_PC
card_wait_frames = 0;
#else
card_wait_frames = 90;
#endif
test_frame_counts[0] = 1.11f;
test_frame_counts[1] = 1.11f;
test_frame_counts[2] = 1.11f;
@@ -2367,7 +2363,7 @@ void dFile_select_c::CommandExec() {
break;
}
mWaitTimer = g_fsHIO.card_wait_frames;
mWaitTimer = IF_DUSK(dusk::getSettings().game.instantSaves ? 0 :) g_fsHIO.card_wait_frames;
}
void dFile_select_c::DataEraseWait() {
@@ -4759,7 +4755,7 @@ void dFile_select_c::MemCardFormatYesSel2Disp() {
bool isErrorTxtChange = errorTxtChangeAnm();
bool isYnMenuMove = yesnoMenuMoveAnm();
if (isErrorTxtChange == true && isYnMenuMove == true) {
mWaitTimer = g_fsHIO.card_wait_frames;
mWaitTimer = IF_DUSK(dusk::getSettings().game.instantSaves ? 0 :) g_fsHIO.card_wait_frames;
mDoMemCd_Format();
mCardCheckProc = MEMCARDCHECKPROC_FORMAT;
}
@@ -4830,7 +4826,7 @@ void dFile_select_c::MemCardMakeGameFileSelDisp() {
if (isErrorTxtChange == true && isYnMenuMove == true && isKetteiTxtDisp == true) {
if (field_0x0268 != 0) {
mWaitTimer = g_fsHIO.card_wait_frames;
mWaitTimer = IF_DUSK(dusk::getSettings().game.instantSaves ? 0 :) g_fsHIO.card_wait_frames;
setInitSaveData();
dataSave();
mCardCheckProc = MEMCARDCHECKPROC_MAKE_GAMEFILE;
+7
View File
@@ -237,6 +237,13 @@ void dDrawPath_c::rendering(dDrawPath_c::line_class const* p_line) {
if (isDrawType(p_line->field_0x0)) {
int width = getLineWidth(p_line->field_0x1);
#if TARGET_PC
f32 height = JUTVideo::getManager()->getRenderHeight() / 448.0f;
if (height > 1.0f) {
width /= 2;
}
#endif
if (width > 0 && p_line->mDataNum >= 2) {
GXSetLineWidth(width, GX_TO_ZERO);
GXSetTevColor(GX_TEVREG0, *getLineColor(p_line->field_0x0 & 0x3F, p_line->field_0x1));
+7
View File
@@ -2399,6 +2399,13 @@ void dMenu_Collect3D_c::_move(u8 param_0, u8 param_1) {
posZ = 550.0f;
}
toItem3Dpos(linkPos.x, posY, posZ, &itemPos);
#if TARGET_PC
if (dusk::getSettings().game.enableLinkDollRotation) {
const f32 angle = mDoCPd_c::getSubStickX3D(PAD_1) * 2048;
ANGLE_ADD(mLinkAngle, angle);
} else
#endif
if (param_0 == 0 && param_1 == 0) {
f32 temp = 450.0f;
ANGLE_ADD(mLinkAngle, temp);
+42 -1
View File
@@ -21,6 +21,7 @@
#include "d/d_msg_string.h"
#include "d/d_meter_haihai.h"
#include "d/d_menu_window.h"
#include "dusk/settings.h"
#include "f_op/f_op_msg_mng.h"
#include "m_Do/m_Do_graphic.h"
#include <cstring>
@@ -945,9 +946,15 @@ void dMenu_DmapBg_c::draw() {
mpMeterHaihai->drawHaihai(field_0xdda,
x1 + (local_224.x + local_218.x) / 2,
y1 + (local_224.y + local_218.y) / 2,
-35.0f + (local_224.x - local_218.x),
-35.0f + (local_224.x - local_218.x),
-35.0f + (local_224.y - local_218.y));
#if TARGET_PC
if (!dusk::getSettings().game.enableFrameInterpolation) {
field_0xdda = 0;
}
#else
field_0xdda = 0;
#endif
}
dMenu_Dmap_c::myclass->drawFloorScreenTop(mFloorScreen, field_0xd94, field_0xd98, grafContext);
@@ -984,7 +991,36 @@ void dMenu_DmapBg_c::update() {
JUT_ASSERT(2323, mpBackTexture != NULL);
void* spec = mpArchive->getResource("spec/spec.dat");
#if TARGET_PC
struct dmap_spec {
/* 0x00 */ BE(f32) field_0x0;
/* 0x04 */ BE(f32) field_0x4;
/* 0x08 */ BE(f32) field_0x8;
/* 0x0C */ u8 field_0xc;
/* 0x0D */ u8 field_0xd;
/* 0x0E */ u8 field_0xe;
/* 0x0F */ u8 field_0xf;
/* 0x10 */ u8 field_0x10;
/* 0x11 */ u8 field_0x11;
/* 0x12 */ u8 field_0x12;
/* 0x13 */ u8 field_0x13;
};
dmap_spec* dspec = (dmap_spec*)spec;
field_0xd80 = dspec->field_0x0;
field_0xd84 = dspec->field_0x4;
field_0xd88 = dspec->field_0x8;
field_0xd8c = dspec->field_0xc;
field_0xd8d = dspec->field_0xd;
field_0xd8e = dspec->field_0xe;
field_0xd8f = dspec->field_0xf;
field_0xd90 = dspec->field_0x10;
field_0xd91 = dspec->field_0x11;
field_0xd92 = dspec->field_0x12;
field_0xd93 = dspec->field_0x13;
#else
memcpy(&field_0xd80, spec, 20);
#endif
}
}
@@ -2545,6 +2581,11 @@ void dMenu_Dmap_c::zoomIn_proc() {
}
void dMenu_Dmap_c::zoomOut_init_proc() {
#if TARGET_PC
if (dusk::getSettings().game.enableFrameInterpolation) {
mpDrawBg->resetScrollArrowMask();
}
#endif
Z2GetAudioMgr()->seStart(Z2SE_SY_MAP_ZOOMOUT, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0);
mMapCtrl->initZoomOut(10);
mpDrawBg->iconScaleAnmInit(1.0f, 0.0f, 10);
+12 -7
View File
@@ -1769,14 +1769,19 @@ void dMenu_Fmap2DBack_c::calcBlink() {
t * (g_fmapHIO.mMapBlink[i + 1].mUnselectedRegion.mBlinkSpeed -
g_fmapHIO.mMapBlink[i].mUnselectedRegion.mBlinkSpeed);
field_0x1218++;
if (field_0x1218 >= selected_blink_speed) {
field_0x1218 = 0;
}
#if TARGET_PC
if (dusk::frame_interp::get_ui_tick_pending())
#endif
{
field_0x1218++;
if (field_0x1218 >= selected_blink_speed) {
field_0x1218 = 0;
}
field_0x121a++;
if (field_0x121a >= unselected_blink_speed) {
field_0x121a = 0;
field_0x121a++;
if (field_0x121a >= unselected_blink_speed) {
field_0x121a = 0;
}
}
f32 t_selected = 0.0f;
+75
View File
@@ -185,6 +185,17 @@ dMenu_Ring_c::dMenu_Ring_c(JKRExpHeap* i_heap, STControl* i_stick, CSTControl* i
field_0x682 = 0xc000;
break;
}
#if TARGET_PC
mCursorInterpPrevX = 0.0f;
mCursorInterpPrevY = 0.0f;
mCursorInterpCurrX = 0.0f;
mCursorInterpCurrY = 0.0f;
mCursorInterpPrevAngle = 0;
mCursorInterpCurrAngle = 0;
mCursorInterpPrevAngular = false;
mCursorInterpCurrAngular = false;
mCursorInterpInit = false;
#endif
for (int i = 0; i < 4; i++) {
field_0x674[i] = 0;
#if TARGET_PC
@@ -631,7 +642,71 @@ void dMenu_Ring_c::_draw() {
} else {
drawSelectItem();
drawItem2();
#if TARGET_PC
f32 simX = 0.0f;
f32 simY = 0.0f;
bool restoreSimPos = false;
if (dusk::frame_interp::is_enabled() && mAlphaRate >= 1.0f) {
simX = mpDrawCursor->getPositionX();
simY = mpDrawCursor->getPositionY();
const bool isAngular = (mStatus == STATUS_MOVE) && !mDirectSelectActive;
if (dusk::frame_interp::get_ui_tick_pending()) {
mCursorInterpPrevX = mCursorInterpCurrX;
mCursorInterpPrevY = mCursorInterpCurrY;
mCursorInterpPrevAngle = mCursorInterpCurrAngle;
mCursorInterpPrevAngular = mCursorInterpCurrAngular;
mCursorInterpCurrX = simX;
mCursorInterpCurrY = simY;
mCursorInterpCurrAngle = field_0x66e;
mCursorInterpCurrAngular = isAngular;
// reset prev = curr for first render pass or
// when angle modes prev/curr differ
// to prevent arrival jitter
if (!mCursorInterpInit ||
mCursorInterpPrevAngular != mCursorInterpCurrAngular) {
mCursorInterpPrevX = mCursorInterpCurrX;
mCursorInterpPrevY = mCursorInterpCurrY;
mCursorInterpPrevAngle = mCursorInterpCurrAngle;
mCursorInterpPrevAngular = mCursorInterpCurrAngular;
mCursorInterpInit = true;
}
}
if (mCursorInterpInit) {
const f32 step = dusk::frame_interp::get_interpolation_step();
if (mCursorInterpPrevAngular && mCursorInterpCurrAngular) {
const s16 delta = mCursorInterpCurrAngle - mCursorInterpPrevAngle;
const s16 lerpedAngle = mCursorInterpPrevAngle + (s16)(delta * step);
// yoinked from stick_move_proc()
const f32 x = g_ringHIO.mItemRingPosX + FB_WIDTH_BASE / 2 +
mRingRadiusH * cM_ssin(lerpedAngle);
const f32 y = g_ringHIO.mItemRingPosY + FB_HEIGHT_BASE / 2 +
mRingRadiusV * cM_scos(lerpedAngle);
mpDrawCursor->setPos(x, y);
} else {
mpDrawCursor->setPos(
mCursorInterpPrevX + (mCursorInterpCurrX - mCursorInterpPrevX) * step,
mCursorInterpPrevY + (mCursorInterpCurrY - mCursorInterpPrevY) * step
);
}
restoreSimPos = true;
}
} else {
mCursorInterpInit = false;
}
#endif
mpDrawCursor->draw();
#if TARGET_PC
// prevents offsetting at destination on the next frame
// since stick_wait_proc doesn't call setPos and we clobbered mPositionX/Y
if (restoreSimPos) {
mpDrawCursor->setPos(simX, simY);
}
#endif
mpItemExplain->trans(mCenterPosX, mCenterPosY);
mpItemExplain->draw((J2DOrthoGraph*)grafPort);
drawFlag0();
+37 -21
View File
@@ -18,6 +18,7 @@
#include "m_Do/m_Do_controller_pad.h"
#include "m_Do/m_Do_graphic.h"
#include "d/d_msg_scrn_explain.h"
#include "dusk/frame_interpolation.h"
#include "dusk/settings.h"
#include "JSystem/J2DGraph/J2DAnmLoader.h"
#include "f_op/f_op_msg_mng.h"
@@ -715,7 +716,9 @@ void dMenu_save_c::_move() {
}
(this->*MenuSaveProc[mMenuProc])();
#if !TARGET_PC
saveSelAnm();
#endif
if (mWarning != NULL) {
mWarning->_move();
@@ -732,36 +735,46 @@ void dMenu_save_c::saveSelAnm() {
}
void dMenu_save_c::selFileWakuAnm() {
mFileWakuAnmFrame += 2;
if (mFileWakuAnmFrame >= mpFileWakuAnm->getFrameMax()) {
mFileWakuAnmFrame -= mpFileWakuAnm->getFrameMax();
#if TARGET_PC
if (dusk::frame_interp::get_ui_tick_pending())
#endif
{
mFileWakuAnmFrame += 2;
if (mFileWakuAnmFrame >= mpFileWakuAnm->getFrameMax()) {
mFileWakuAnmFrame -= mpFileWakuAnm->getFrameMax();
}
mFileWakuRotAnmFrame += 2;
if (mFileWakuRotAnmFrame >= mpFileWakuRotAnm->getFrameMax()) {
mFileWakuRotAnmFrame -= mpFileWakuRotAnm->getFrameMax();
}
}
mpFileWakuAnm->setFrame(mFileWakuAnmFrame);
mFileWakuRotAnmFrame += 2;
if (mFileWakuRotAnmFrame >= mpFileWakuRotAnm->getFrameMax()) {
mFileWakuRotAnmFrame -= mpFileWakuRotAnm->getFrameMax();
}
mpFileWakuRotAnm->setFrame(mFileWakuRotAnmFrame);
}
void dMenu_save_c::bookIconAnm() {
field_0x154 += 2;
if (field_0x154 >= field_0x150->getFrameMax()) {
field_0x154 -= field_0x150->getFrameMax();
#if TARGET_PC
if (dusk::frame_interp::get_ui_tick_pending())
#endif
{
field_0x154 += 2;
if (field_0x154 >= field_0x150->getFrameMax()) {
field_0x154 -= field_0x150->getFrameMax();
}
field_0x15c += 2;
if (field_0x15c >= field_0x158->getFrameMax()) {
field_0x15c -= field_0x158->getFrameMax();
}
field_0x164 += 2;
if (field_0x164 >= field_0x160->getFrameMax()) {
field_0x164 -= field_0x160->getFrameMax();
}
}
field_0x150->setFrame(field_0x154);
field_0x15c += 2;
if (field_0x15c >= field_0x158->getFrameMax()) {
field_0x15c -= field_0x158->getFrameMax();
}
field_0x158->setFrame(field_0x15c);
field_0x164 += 2;
if (field_0x164 >= field_0x160->getFrameMax()) {
field_0x164 -= field_0x160->getFrameMax();
}
field_0x160->setFrame(field_0x164);
}
@@ -2809,6 +2822,9 @@ void dMenu_save_c::menuSaveWide() {
void dMenu_save_c::_draw2() {
if (field_0x21a1 == 0) {
#if TARGET_PC
saveSelAnm();
#endif
if (mpScrnExplain != NULL) {
dComIfGd_set2DOpa(&mMenuSaveExplain);
}
+7
View File
@@ -1987,6 +1987,13 @@ bool jmessage_tSequenceProcessor::do_isReady() {
}
#endif
#if TARGET_PC
if (dusk::getSettings().game.instantText && mDoCPd_c::getHoldB(0)) {
field_0xb2 = 1;
pReference->setSendTimer(0);
}
#endif
if (dComIfGp_checkMesgBgm()) {
bool isItemMusicPlaying = true;
if (mDoAud_checkPlayingSubBgmFlag() != Z2BGM_ITEM_GET &&
+1
View File
@@ -41,6 +41,7 @@
#if TARGET_PC
#include "dusk/memory.h"
#include <dusk/autosave.h>
#endif
#if DEBUG
+1
View File
@@ -28,6 +28,7 @@
#if TARGET_PC
#include "dusk/settings.h"
#include <f_ap/f_ap_game.h>
#include <dusk/autosave.h>
#endif
static u8 dSv_item_rename(u8 i_itemNo) {
+132 -10
View File
@@ -5,14 +5,15 @@
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstdio>
#include <span>
#include "Adpcm.hpp"
#include "freeverb/revmodel.hpp"
#include "JSystem/JAudio2/JASDriverIF.h"
#include "dusk/audio/DuskAudioSystem.h"
#include "dusk/endian.h"
#include "dusk/logging.h"
#include "global.h"
#include "tracy/Tracy.hpp"
@@ -95,6 +96,13 @@ static void RenderChannel(
ChannelAuxData& channelAux,
OutputSubframe& subframe);
static void RenderOutputChannel(
const JASDsp::TChannel& sourceChannel,
ChannelAuxData& aux,
OutputChannel outputChannel,
const std::span<f32> inputSamples,
OutputSubframe& fullOutputSubframe);
/**
* Converts a pitch value on a DSP channel to a sample rate.
*/
@@ -117,6 +125,8 @@ static void ResetChannel(JASDsp::TChannel& channel, ChannelAuxData& aux) {
aux.resamplePos = 0.0;
aux.resamplePrev = 0;
aux.oscPhase = 0;
aux.prev_lp_out = 0.0f;
aux.prev_lp_in = 0.0f;
@@ -141,6 +151,119 @@ static void MixSubframe(DspSubframe& dst, const DspSubframe& src) {
}
}
enum class OscType : u16 {
SQUARE_WAVE_PW_50 = 0,
SAW_WAVE = 1,
SQUARE_WAVE_PW_25 = 3,
TRIANGLE_WAVE = 4,
// idk what 5 and 6 are
SINE_WAVE = 7,
// idk what 8 and 9 are
SINE_WAVE_VAR_STEP = 10,
EVOLVING_HARMONIC = 11,
EVOLVING_RAMP = 12,
};
static s16 gEvolvingHarmonic[64];
static void GenerateEvolvingHarmonic() {
static bool initialized = false;
if (!initialized) {
gEvolvingHarmonic[62] = 8191;
gEvolvingHarmonic[63] = 16383;
initialized = true;
}
u32 prev2 = (u32)gEvolvingHarmonic[62];
u32 prev1 = (u32)gEvolvingHarmonic[63];
for (int i = 0; i < 64; i += 2) {
u32 cur = (u32)gEvolvingHarmonic[i];
gEvolvingHarmonic[i] = (s16)((s32)(prev2 * prev1 - (cur << 16)) >> 16);
prev2 = prev1;
prev1 = cur;
cur = (u32)gEvolvingHarmonic[i + 1];
gEvolvingHarmonic[i + 1] = (s16)((s32)(2u * (prev2 * prev1 + (cur << 16))) >> 16);
prev2 = prev1;
prev1 = cur;
}
}
static void RenderOscChannel(
JASDsp::TChannel& channel,
ChannelAuxData& channelAux,
OutputSubframe& subframe) {
if (channel.mResetFlag)
ResetChannel(channel, channelAux);
const u32 pitch = channel.mPitch;
DspSubframe buf = {};
const auto oscType = static_cast<OscType>(channel.mBytesPerBlock);
switch (oscType) {
case OscType::SQUARE_WAVE_PW_50: {
std::generate(buf.begin(), buf.end(), [&] {
f32 s = channelAux.oscPhase < 0x8000u ? 0.5f : -0.5f;
channelAux.oscPhase += pitch >> 1;
return s;
});
break;
}
case OscType::SQUARE_WAVE_PW_25: {
std::generate(buf.begin(), buf.end(), [&] {
f32 s = channelAux.oscPhase < 0x4000u ? 0.5f : -0.5f;
channelAux.oscPhase += pitch >> 1;
return s;
});
break;
}
case OscType::SAW_WAVE:
case OscType::EVOLVING_RAMP: {
std::generate(buf.begin(), buf.end(), [&] {
f32 s = (f32)(s16)channelAux.oscPhase / 32768.0f;
channelAux.oscPhase += pitch >> 1;
return s;
});
break;
}
case OscType::SINE_WAVE:
case OscType::SINE_WAVE_VAR_STEP: {
std::generate(buf.begin(), buf.end(), [&] {
f32 s = sinf((f32)channelAux.oscPhase * (2.0f * M_PI / 65536.0f)) * 0.5f;
channelAux.oscPhase += pitch >> 1;
return s;
});
break;
}
case OscType::TRIANGLE_WAVE: {
std::generate(buf.begin(), buf.end(), [&] {
f32 s = 0.5f - fabsf((f32)(s16)channelAux.oscPhase / 32768.0f);
channelAux.oscPhase += pitch >> 1;
return s;
});
break;
}
case OscType::EVOLVING_HARMONIC: {
std::generate(buf.begin(), buf.end(), [&] {
f32 s = gEvolvingHarmonic[channelAux.oscPhase >> 10] / 32768.0f;
channelAux.oscPhase += pitch >> 1;
return s;
});
break;
}
default:
DuskLog.error("RenderOscChannel: unimplemented oscillator type {}", channel.mBytesPerBlock);
break;
}
auto samples = std::span(buf).subspan(0, DSP_SUBFRAME_SIZE);
RenderOutputChannel(channel, channelAux, OutputChannel::LEFT, samples, subframe);
RenderOutputChannel(channel, channelAux, OutputChannel::RIGHT, samples, subframe);
}
void dusk::audio::DspRender(OutputSubframe& subframe) {
ZoneScoped;
if (DumpAudio != sDumpWasActive) {
@@ -152,6 +275,8 @@ void dusk::audio::DspRender(OutputSubframe& subframe) {
}
}
GenerateEvolvingHarmonic();
std::span channels(JASDsp::CH_BUF, DSP_CHANNELS);
DspSubframe reverbInputL = {};
@@ -174,17 +299,14 @@ void dusk::audio::DspRender(OutputSubframe& subframe) {
channel.mIsFinished = true;
continue;
}
else if (channel.mWaveAramAddress == 0) {
// I think these are oscillator channels? Not backed by audio.
// No idea how to implement these yet, so skip them.
channel.mIsFinished = true;
continue;
}
ValidateChannel(channel);
OutputSubframe channelSubframe = {};
RenderChannel(channel, channelAux, channelSubframe);
if (channel.mWaveAramAddress == 0) {
RenderOscChannel(channel, channelAux, channelSubframe);
} else {
ValidateChannel(channel);
RenderChannel(channel, channelAux, channelSubframe);
}
if (EnableReverb) {
// scale the input to the reverb rather than using wet/dry on the output.
+3
View File
@@ -53,6 +53,9 @@ namespace dusk::audio {
// last consumed sample from decodeBuf
s16 resamplePrev;
// phase of oscillator channels
u16 oscPhase;
// low pass previous state
f32 prev_lp_out; // out[n-1]
f32 prev_lp_in; // in[n-1]
+85
View File
@@ -0,0 +1,85 @@
#include "dusk/autosave.h"
#include "imgui/ImGuiConsole.hpp"
u8 mSaveBuffer[QUEST_LOG_SIZE * 3];
u8 mAutoSaveProc = 0;
int autoSaveWriteState = 0;
typedef void (*AutoSaveFuncs)();
static AutoSaveFuncs AutoSaveFuncsProc[] = {
noAutoSave, enterAutoSave, autoSaving, waitingForWrite, endAutoSave,
};
void noAutoSave() {}
void triggerAutoSave() {
if (dusk::getSettings().game.autoSave && mAutoSaveProc == 0 &&
strcmp(dComIfGp_getStartStageName(), "F_SP102") != 0 && dComIfGp_getStartStageLayer() != 0)
{
mAutoSaveProc = 1;
}
}
void updateAutoSave() {
(AutoSaveFuncsProc[mAutoSaveProc])();
}
void writeAutoSave() {
dComIfGs_setMemoryToCard(mSaveBuffer, dComIfGs_getDataNum());
mDoMemCdRWm_SetCheckSumGameData(mSaveBuffer, dComIfGs_getDataNum());
u8* save = mSaveBuffer;
for (int i = 0; i < 3; i++) {
mDoMemCdRWm_TestCheckSumGameData(save);
save += QUEST_LOG_SIZE;
}
g_mDoMemCd_control.save(mSaveBuffer, sizeof(mSaveBuffer), 0);
}
void autoSaving() {
int cardState = g_mDoMemCd_control.LoadSync(mSaveBuffer, sizeof(mSaveBuffer), 0);
if (cardState != 0) {
if (cardState == 2) {
mAutoSaveProc = 1;
} else if (cardState == 1) {
writeAutoSave();
mAutoSaveProc = 3;
}
}
}
void enterAutoSave() {
u32 cardStatus = g_mDoMemCd_control.getStatus(0);
if (cardStatus != 14) {
switch (cardStatus) {
case 2:
g_mDoMemCd_control.load();
mAutoSaveProc = 2;
break;
case 3:
case 4:
case 5:
break;
default:
mAutoSaveProc = 0;
break;
}
}
}
void waitingForWrite() {
autoSaveWriteState = g_mDoMemCd_control.SaveSync();
if (autoSaveWriteState == 2) {
mAutoSaveProc = 0;
} else if (autoSaveWriteState == 1) {
mAutoSaveProc = 4;
}
}
void endAutoSave() {
dusk::g_imguiConsole.ShowToast("Saving...", 2.0f);
mAutoSaveProc = 0;
}
+2 -2
View File
@@ -10,7 +10,7 @@
#include <limits>
#include <string>
#include "dusk/dusk.h"
#include "dusk/main.h"
using namespace dusk::config;
@@ -24,7 +24,7 @@ static absl::flat_hash_map<std::string_view, ConfigVarBase*> RegisteredConfigVar
static bool RegistrationDone = false;
static std::string GetConfigJsonPath() {
return fmt::format("{}{}", configPath, ConfigFileName);
return (dusk::ConfigPath / ConfigFileName).string();
}
ConfigVarBase::ConfigVarBase(const char* name, const ConfigImplBase* impl) : name(name), registered(false), layer(ConfigVarLayer::Default), impl(impl) {
+2 -1
View File
@@ -3,6 +3,7 @@
#include "dusk/app_info.hpp"
#include "dusk/dusk.h"
#include "dusk/logging.h"
#include "dusk/main.h"
#include "dusk/settings.h"
#include "version.h"
@@ -66,7 +67,7 @@ std::string GetReleaseName() {
}
std::filesystem::path GetSentryDatabasePath() {
return std::filesystem::path(configPath) / "sentry";
return dusk::ConfigPath / "sentry";
}
std::filesystem::path GetLogAttachmentPath() {
+21 -5
View File
@@ -3,6 +3,27 @@
#include <string.h>
#include <stdint.h>
#ifdef _MSC_VER
#include <intrin.h>
#endif
void __dcbz(void* addr, int offset) {
// Gekko cache lines are 32 bytes.
// dcbz usually requires addr to be 32-byte aligned.
memset((char*)addr + offset, 0, 32);
}
int __cntlzw(unsigned int val) {
if (val == 0) return 32; // PowerPC returns 32 if the input is 0
#ifdef _MSC_VER
unsigned long idx;
_BitScanReverse(&idx, val);
return 31 - (int)idx;
#else
return __builtin_clz(val);
#endif
}
#ifndef _MSC_VER
int stricmp(const char* str1, const char* str2) {
char a_var;
@@ -48,11 +69,6 @@ int strnicmp(const char* str1, const char* str2, int n) {
}
#endif
void *_memcpy(void* dest, void const* src, int n) {
return memcpy(dest, src, n);
}
void DCZeroRange(void* addr, uint32_t nBytes) {
#if defined(_MSC_VER) || TARGET_ANDROID
memset(addr, 0, nBytes);
-27
View File
@@ -1,27 +0,0 @@
// C++ Mangled version of extras.c
#include <cstring>
#include <cstdint>
#ifdef _MSC_VER
#include <intrin.h>
#endif
void *__memcpy(void* dest, void const* src, int n) {
return memcpy(dest, src, n);
}
void __dcbz(void* addr, int offset) {
// Gekko cache lines are 32 bytes.
// dcbz usually requires addr to be 32-byte aligned.
memset((char*)addr + offset, 0, 32);
}
int __cntlzw(unsigned int val) {
if (val == 0) return 32; // PowerPC returns 32 if the input is 0
#ifdef _MSC_VER
unsigned long idx;
_BitScanReverse(&idx, val);
return 31 - (int)idx;
#else
return __builtin_clz(val);
#endif
}
+12 -8
View File
@@ -127,14 +127,20 @@ void ensure_initialized() {
s_initialized = true;
}
void begin_sim_tick() {
ensure_initialized();
if (!g_enabled) {
return;
}
s_interpolationCallBackWork.clear();
s_cam_prev = std::move(s_cam_curr);
}
void begin_frame(bool enabled, bool is_sim_frame, float step) {
g_enabled = enabled;
g_is_sim_frame = is_sim_frame;
g_step = std::clamp(step, 0.0f, 1.0f);
if (is_sim_frame) {
s_interpolationCallBackWork.clear();
s_cam_prev = std::move(s_cam_curr);
}
}
bool is_enabled() {
@@ -408,10 +414,8 @@ void begin_presentation_camera() {
}
mDoLib_clipper::setup(view->fovy, view->aspect, view->near_, far_);
#if WIDESCREEN_SUPPORT
mDoGph_gInf_c::offWideZoom();
#endif
// FRAME INTERP NOTE: Removed the call to offWideZoom that was here, it causes problems with presentation during cutscenes.
s_presentation_depth = 1;
+44 -23
View File
@@ -5,62 +5,84 @@
#include <cmath>
#include <unordered_map>
namespace dusk {
namespace game_clock {
namespace dusk::game_clock {
using clock = std::chrono::steady_clock;
bool s_initialized = false;
clock::time_point s_previous_sample{};
float s_sim_accumulator = 0.0f;
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()));
constexpr clock::duration kAbnormalGapResetThreshold = std::chrono::milliseconds(250);
constexpr int kMaxSimTicksPerFrame = 2;
void ensure_initialized() {
if (s_initialized) {
return;
}
s_previous_sample = clock::now();
s_sim_accumulator = sim_pace();
s_current_snapshot_time = s_previous_sample;
s_initialized = true;
}
void reset_accumulator() {
ensure_initialized();
s_sim_accumulator = fmodf(s_sim_accumulator, sim_pace());
}
void reset_frame_timer() {
s_previous_sample = clock::now();
s_sim_accumulator = 0.0f;
s_current_snapshot_time = s_previous_sample - kSimPeriodDuration;
}
MainLoopPacer advance_main_loop() {
ensure_initialized();
const clock::time_point now = clock::now();
const float presentation_dt = std::chrono::duration<float>(now - s_previous_sample).count();
const clock::duration frame_gap = now - s_previous_sample;
const float presentation_dt = std::chrono::duration<float>(frame_gap).count();
s_previous_sample = now;
s_sim_accumulator += presentation_dt;
MainLoopPacer out{};
out.presentation_dt_seconds = presentation_dt;
const bool should_interpolate = dusk::getSettings().game.enableFrameInterpolation && !dusk::getTransientSettings().skipFrameRateLimit;
const bool should_interpolate = dusk::getSettings().game.enableFrameInterpolation &&
!dusk::getTransientSettings().skipFrameRateLimit;
out.is_interpolating = should_interpolate;
out.sim_pace = sim_pace();
if (!should_interpolate) {
s_sim_accumulator = 0.0f;
out.do_sim_tick = true;
out.interpolation_step = 0.0f;
return out;
} else {
out.do_sim_tick = s_sim_accumulator >= sim_pace();
out.interpolation_step = out.do_sim_tick ? 0.0f : s_sim_accumulator / sim_pace();
s_current_snapshot_time = now;
out.sim_ticks_to_run = 1;
return out;
}
if (frame_gap > kAbnormalGapResetThreshold) {
s_current_snapshot_time = now - kSimPeriodDuration;
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;
while (sim_ticks_to_run < kMaxSimTicksPerFrame && projected_snapshot_time < render_time) {
projected_snapshot_time += kSimPeriodDuration;
sim_ticks_to_run++;
}
out.sim_ticks_to_run = sim_ticks_to_run;
return out;
}
void commit_sim_tick() {
ensure_initialized();
s_current_snapshot_time += kSimPeriodDuration;
}
float sample_interpolation_step() {
ensure_initialized();
const float step =
std::chrono::duration<float>(clock::now() - s_current_snapshot_time).count() / sim_pace();
return std::clamp(step, 0.0f, 1.0f);
}
float consume_interval(const void* consumer) {
@@ -78,5 +100,4 @@ float consume_interval(const void* consumer) {
return dt;
}
} // namespace game_clock
} // namespace dusk
} // namespace dusk::game_clock
+106
View File
@@ -0,0 +1,106 @@
#include <cmath>
#include <SSystem/SComponent/c_xyz.h>
#include <d/d_com_inf_game.h>
#include <d/actor/d_a_player.h>
#include <d/actor/d_a_alink.h>
#include <dusk/gamepad_color.h>
cXyz currentGamepadColor = {0, 0, 0};
cXyz finalGamepadColor = {0, 0, 0};
cXyz additionalGamepadColor = {0, 0, 0};
float lerpSpeed = 0.0f;
const cXyz duskColor = {50, 50, -50};
const cXyz noColor = {0, 0, 0};
cXyz LerpColor(cXyz a, cXyz b, float t) {
return {std::lerp(a.x, b.x, t), std::lerp(a.y, b.y, t), std::lerp(a.z, b.z, t)};
}
void FadeLED(cXyz newColor, float speed) {
finalGamepadColor = newColor;
lerpSpeed = speed / 30.0f;
}
void SetLED(cXyz newColor) {
currentGamepadColor = newColor;
finalGamepadColor = newColor;
}
void SetGamepadAdditionalColor(cXyz addColor) {
additionalGamepadColor.x = addColor.x;
additionalGamepadColor.y = addColor.y;
additionalGamepadColor.z = addColor.z;
}
void handleGamepadColor() {
bool setColor = false;
fopAc_ac_c* zhint = dComIfGp_att_getZHint();
if (zhint != NULL) {
FadeLED({50, 50, 175}, 2.0f);
setColor = true;
}
daPy_py_c* player = daPy_getPlayerActorClass();
daAlink_c* link = daAlink_getAlinkActorClass();
if (link != nullptr && !setColor) {
if (link->checkWolf()) {
FadeLED({115, 115, 75}, 5.0f);
setColor = true;
} else {
switch (dComIfGs_getSelectEquipClothes()) {
case dItemNo_WEAR_KOKIRI_e:
FadeLED({0, 100, 0}, 5.0f);
setColor = true;
break;
case dItemNo_WEAR_ZORA_e:
FadeLED({0, 0, 100}, 5.0f);
setColor = true;
break;
case dItemNo_ARMOR_e:
if (link->checkMagicArmorHeavy()) {
FadeLED({5, 100, 100}, 5.0f);
} else {
FadeLED({100, 0, 5}, 5.0f);
}
setColor = true;
break;
case dItemNo_WEAR_CASUAL_e:
FadeLED({235, 230, 115}, 5.0f);
setColor = true;
break;
}
}
}
if (dKy_darkworld_check()) {
SetGamepadAdditionalColor(duskColor);
} else {
SetGamepadAdditionalColor(noColor);
}
f32 finalRed = finalGamepadColor.x + additionalGamepadColor.x;
f32 finalGreen = finalGamepadColor.y + additionalGamepadColor.y;
f32 finalBlue = finalGamepadColor.z + additionalGamepadColor.z;
if (finalRed > 255)
finalRed = 255;
if (finalRed < 0)
finalRed = 0;
if (finalGreen > 255)
finalGreen = 255;
if (finalGreen < 0)
finalGreen = 0;
if (finalBlue > 255)
finalBlue = 255;
if (finalBlue < 0)
finalBlue = 0;
currentGamepadColor = LerpColor(currentGamepadColor, cXyz{finalRed, finalGreen, finalBlue}, lerpSpeed);
PADSetColor(PAD_CHAN0, (u8)currentGamepadColor.x, (u8)currentGamepadColor.y, (u8)currentGamepadColor.z);
}
+85 -5
View File
@@ -1,16 +1,29 @@
#include "dusk/gyro.h"
#include "d/actor/d_a_alink.h"
#include <cmath>
namespace dusk::gyro {
namespace {
constexpr s32 kRollgoalTableMaxOffset = 12000;
constexpr s32 kRollgoalTableMaxOffset = 6500;
constexpr float kGyroEmaAlphaMin = 0.05f;
constexpr float kGyroEmaAlphaMax = 1.0f;
// Smooth gravity separately so the yaw/roll blend doesn't twitch with raw accel noise.
constexpr float kGravityEmaAlpha = 0.1f;
constexpr float kMinGravityProjection = 0.2f;
// Let roll contribute more strongly as the pad approaches an upright posture.
constexpr float kRollAimBoostMax = 2.0f;
bool s_sensor_enabled = false;
bool s_accel_enabled = false;
bool s_was_aiming = false;
bool s_have_gravity_baseline = false;
float s_smooth_gx = 0.0f;
float s_smooth_gy = 0.0f;
float s_smooth_gz = 0.0f;
float s_gravity_y = 0.0f;
float s_gravity_z = 0.0f;
float s_baseline_gravity_y = 0.0f;
float s_baseline_gravity_z = 0.0f;
float s_yaw_rad = 0.0f;
float s_pitch_rad = 0.0f;
float s_roll_rad = 0.0f;
@@ -19,6 +32,10 @@ s32 s_rollgoal_az = 0;
void reset_filter_state() {
s_smooth_gx = s_smooth_gy = s_smooth_gz = 0.0f;
s_gravity_y = s_gravity_z = 0.0f;
s_baseline_gravity_y = s_baseline_gravity_z = 0.0f;
s_was_aiming = false;
s_have_gravity_baseline = false;
s_yaw_rad = s_pitch_rad = s_roll_rad = 0.0f;
s_rollgoal_ax = s_rollgoal_az = 0;
}
@@ -49,15 +66,30 @@ bool queryGyroAimContext() {
}
void read(float dt) {
if (!s_sensor_keep_alive && !queryGyroAimContext()) {
const bool aim_active = queryGyroAimContext();
const bool aim_just_started = aim_active && !s_was_aiming;
const bool aim_just_ended = !aim_active && s_was_aiming;
s_was_aiming = aim_active;
if (!s_sensor_keep_alive && !aim_active) {
if (s_sensor_enabled) {
PADSetSensorEnabled(PAD_CHAN0, PAD_SENSOR_GYRO, FALSE);
s_sensor_enabled = false;
}
if (s_accel_enabled) {
PADSetSensorEnabled(PAD_CHAN0, PAD_SENSOR_ACCEL, FALSE);
s_accel_enabled = false;
}
reset_filter_state();
return;
}
if (aim_just_started || aim_just_ended) {
s_gravity_y = s_gravity_z = 0.0f;
s_baseline_gravity_y = s_baseline_gravity_z = 0.0f;
s_have_gravity_baseline = false;
}
if (!s_sensor_enabled) {
if (!PADHasSensor(PAD_CHAN0, PAD_SENSOR_GYRO)) {
return;
@@ -68,6 +100,13 @@ void read(float dt) {
s_sensor_enabled = true;
}
if (!s_accel_enabled && PADHasSensor(PAD_CHAN0, PAD_SENSOR_ACCEL) &&
PADSetSensorEnabled(PAD_CHAN0, PAD_SENSOR_ACCEL, TRUE))
{
// We only need accel for the gravity-aware yaw/roll mix.
s_accel_enabled = true;
}
f32 gyro[3];
if (!PADGetSensorData(PAD_CHAN0, PAD_SENSOR_GYRO, gyro, 3)) {
return;
@@ -80,9 +119,50 @@ void read(float dt) {
s_smooth_gy += smooth_alpha * (gyro[1] - s_smooth_gy);
s_smooth_gz += smooth_alpha * (gyro[2] - s_smooth_gz);
s_pitch_rad = -apply_deadband(s_smooth_gx, deadband) * dt * dusk::getSettings().game.gyroSensitivityX;
s_yaw_rad = apply_deadband(s_smooth_gy, deadband) * dt * dusk::getSettings().game.gyroSensitivityY;
s_roll_rad = apply_deadband(s_smooth_gz, deadband) * dt * dusk::getSettings().game.gyroSensitivityX; // GYRO NOTE: Exposing Z sensitivity seems unusual, so I'm just using X
const float pitch_rate = apply_deadband(s_smooth_gx, deadband);
const float yaw_rate = apply_deadband(s_smooth_gy, deadband);
const float roll_rate = apply_deadband(s_smooth_gz, deadband);
s_pitch_rad = -pitch_rate * dt * dusk::getSettings().game.gyroSensitivityX;
s_roll_rad = roll_rate * dt * dusk::getSettings().game.gyroSensitivityX; // GYRO NOTE: Exposing Z sensitivity seems unusual, so I'm just using X
float horizontal_rate = yaw_rate;
if (aim_active && s_accel_enabled) {
f32 accel[3];
if (PADGetSensorData(PAD_CHAN0, PAD_SENSOR_ACCEL, accel, 3)) {
if (!s_have_gravity_baseline) {
s_gravity_y = accel[1];
s_gravity_z = accel[2];
} else {
s_gravity_y += kGravityEmaAlpha * (accel[1] - s_gravity_y);
s_gravity_z += kGravityEmaAlpha * (accel[2] - s_gravity_z);
}
// Compare the current gravity projection against the gravity vector from
// aim start so the user's resting hold angle becomes the neutral baseline.
const float gravity_yz_len = std::sqrt((s_gravity_y * s_gravity_y) + (s_gravity_z * s_gravity_z));
if (gravity_yz_len >= kMinGravityProjection) {
const float current_gravity_y = s_gravity_y / gravity_yz_len;
const float current_gravity_z = s_gravity_z / gravity_yz_len;
if (!s_have_gravity_baseline) {
s_baseline_gravity_y = current_gravity_y;
s_baseline_gravity_z = current_gravity_z;
s_have_gravity_baseline = true;
}
const float yaw_weight =
(s_baseline_gravity_y * current_gravity_y) + (s_baseline_gravity_z * current_gravity_z);
const float roll_weight =
(s_baseline_gravity_y * current_gravity_z) - (s_baseline_gravity_z * current_gravity_y);
const float roll_mix = std::fabs(roll_weight);
const float roll_boost = 1.0f + (roll_mix * (kRollAimBoostMax - 1.0f));
horizontal_rate = (yaw_rate * yaw_weight) + (roll_rate * roll_weight * roll_boost);
}
}
}
s_yaw_rad = horizontal_rate * dt * dusk::getSettings().game.gyroSensitivityY;
s_pitch_rad = dusk::getSettings().game.gyroInvertPitch ? -s_pitch_rad : s_pitch_rad;
s_yaw_rad = dusk::getSettings().game.gyroInvertYaw ? -s_yaw_rad : s_yaw_rad;
+8 -1
View File
@@ -305,6 +305,10 @@ namespace dusk {
ImGuiMenuGame::ToggleFullscreen();
}
if (ImGui::IsKeyPressed(ImGuiKey_Escape) && getSettings().video.enableFullscreen) {
ImGuiMenuGame::ToggleFullscreen();
}
if (!dusk::IsGameLaunched) {
m_preLaunchWindow.draw();
}
@@ -319,9 +323,11 @@ namespace dusk {
}
}
// The menu bar renders with ImGuiCol_WindowBg behind it. We just want ImGuiCol_MenuBarBg,
// so make the window bg fully transparent temporarily
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
if (showMenu && ImGui::BeginMainMenuBar()) {
m_menuGame.draw();
m_menuEnhancements.draw();
m_menuTools.draw();
const auto fpsLabel =
@@ -336,6 +342,7 @@ namespace dusk {
ImGui::EndMainMenuBar();
}
ImGui::PopStyleColor();
if (!getSettings().backend.wasPresetChosen) {
m_firstRunPreset.draw();
-2
View File
@@ -9,7 +9,6 @@
#include <SDL3/SDL_touch.h>
#include "ImGuiFirstRunPreset.hpp"
#include "ImGuiMenuEnhancements.hpp"
#include "ImGuiMenuGame.hpp"
#include "ImGuiMenuTools.hpp"
#include "ImGuiPreLaunchWindow.hpp"
@@ -54,7 +53,6 @@ private:
ImGuiFirstRunPreset m_firstRunPreset;
ImGuiMenuGame m_menuGame;
ImGuiMenuEnhancements m_menuEnhancements;
ImGuiPreLaunchWindow m_preLaunchWindow;
// Keep always last
+2 -2
View File
@@ -126,7 +126,7 @@ void ImGuiEngine_Initialize(float scale) {
auto* colors = style.Colors;
colors[ImGuiCol_Text] = ImVec4(0.95f, 0.96f, 0.98f, 1.00f);
colors[ImGuiCol_TextDisabled] = ImVec4(0.36f, 0.42f, 0.47f, 1.00f);
colors[ImGuiCol_WindowBg] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f);
colors[ImGuiCol_WindowBg] = ImVec4(0.11f, 0.15f, 0.17f, 0.98f);
colors[ImGuiCol_ChildBg] = ImVec4(0.15f, 0.18f, 0.22f, 1.00f);
colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f);
colors[ImGuiCol_Border] = ImVec4(0.08f, 0.10f, 0.12f, 1.00f);
@@ -137,7 +137,7 @@ void ImGuiEngine_Initialize(float scale) {
colors[ImGuiCol_TitleBg] = ImVec4(0.09f, 0.12f, 0.14f, 0.65f);
colors[ImGuiCol_TitleBgActive] = ImVec4(0.08f, 0.10f, 0.12f, 1.00f);
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f);
colors[ImGuiCol_MenuBarBg] = ImVec4(0.15f, 0.18f, 0.22f, 1.00f);
colors[ImGuiCol_MenuBarBg] = ImVec4(0.15f, 0.18f, 0.22f, 0.80f);
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.39f);
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f);
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.18f, 0.22f, 0.25f, 1.00f);
-260
View File
@@ -1,260 +0,0 @@
#include "imgui.h"
#include "ImGuiMenuEnhancements.hpp"
#include "ImGuiConfig.hpp"
#include "dusk/settings.h"
namespace dusk {
ImGuiMenuEnhancements::ImGuiMenuEnhancements() {}
void ImGuiMenuEnhancements::draw() {
if (ImGui::BeginMenu("Enhancements")) {
if (ImGui::BeginMenu("Gameplay")) {
ImGui::SeparatorText("Preferences");
config::ImGuiCheckbox("Mirror Mode", getSettings().game.enableMirrorMode);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Mirrors the world horizontally, matching the Wii version of the game.");
}
config::ImGuiCheckbox("Disable Main HUD", getSettings().game.disableMainHUD);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Disables the main HUD of the game.\n"
"Useful for recording or a more immersive experience!");
}
ImGui::SeparatorText("Difficulty");
config::ImGuiSliderInt("Damage Multiplier", getSettings().game.damageMultiplier, 1, 8, "x%d");
config::ImGuiCheckbox("Instant Death", getSettings().game.instantDeath);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Any hit will instantly kill you.");
}
config::ImGuiCheckbox("No Heart Drops", getSettings().game.noHeartDrops);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Hearts will never drop from enemies,\n"
"pots and various other places.");
}
ImGui::SeparatorText("Quality of Life");
config::ImGuiCheckbox("Bigger Wallets", getSettings().game.biggerWallets);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Wallet sizes are like in the HD version. (500, 1000, 2000)");
}
config::ImGuiCheckbox("Disable Rupee Cutscenes", getSettings().game.disableRupeeCutscenes);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Rupees won't play cutscenes after you've collected them the first time.");
}
config::ImGuiCheckbox("Faster Climbing", getSettings().game.fastClimbing);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Quicker climbing on ladders and vines like the HD version.");
}
config::ImGuiCheckbox("Faster Tears of Light", getSettings().game.fastTears);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Tears of Light dropped by Shadow Insects pop out faster like the HD version.");
}
config::ImGuiCheckbox("Instant Saves", getSettings().game.instantSaves);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Skip the delay when writing to the Memory Card.");
}
config::ImGuiCheckbox("Autosave", getSettings().game.autoSave);
const bool autoSaveHovered = ImGui::IsItemHovered();
ImGui::SameLine();
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.72f, 0.2f, 1.0f));
ImGui::TextUnformatted("[EXPERIMENTAL]");
ImGui::PopStyleColor();
if (autoSaveHovered || ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"Automatically saves the game when going to new areas,\n"
"getting an item, or opening a dungeon door.");
}
config::ImGuiCheckbox("Hold B for Instant Text", getSettings().game.instantText);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Make text scroll immediately by holding B.");
}
config::ImGuiCheckbox("No Climbing Miss Animation", getSettings().game.noMissClimbing);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Prevents Link from playing a struggle animation\n"
"when grabbing ledges or climbing on vines.");
}
config::ImGuiCheckbox("No Rupee Returns", getSettings().game.noReturnRupees);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Always collect Rupees even if your Wallet is too full.");
}
config::ImGuiCheckbox("No Sword Recoil", getSettings().game.noSwordRecoil);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Link won't recoil when his sword hits walls.");
}
config::ImGuiCheckbox("Skip TV Settings Screen", getSettings().game.hideTvSettingsScreen);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Skip the TV calibration screen shown when loading a save.");
}
config::ImGuiCheckbox("Skip Warning Screen", getSettings().game.skipWarningScreen);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Skip the warning screen shown when starting the game.");
}
config::ImGuiCheckbox("Sun's Song (R+X)", getSettings().game.sunsSong);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Allows Wolf Link to howl and change the time of day.");
}
config::ImGuiCheckbox("Quick Transform (R+Y)", getSettings().game.enableQuickTransform);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Transform instantly by pressing R and Y simultaneously.");
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Graphics")) {
config::ImGuiSliderInt("Shadow Resolution", getSettings().game.shadowResolutionMultiplier, 1, 8, "x%d");
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Improves the shadow resolution, making them higher quality.");
}
config::ImGuiCheckbox("Unlock Framerate", getSettings().game.enableFrameInterpolation);
const bool frameInterpolationHovered = ImGui::IsItemHovered();
ImGui::SameLine();
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.72f, 0.2f, 1.0f));
ImGui::TextUnformatted("[EXPERIMENTAL]");
ImGui::PopStyleColor();
if (frameInterpolationHovered || ImGui::IsItemHovered()) {
ImGui::SetTooltip("Uses inter-frame interpolation to enable higher frame rates.\nVisual artifacts, animation glitches, or instability may occur.");
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Audio")) {
config::ImGuiCheckbox("No Low HP Sound", getSettings().game.noLowHpSound);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Disable the beeping sound when having low health.");
}
config::ImGuiCheckbox("Non-Stop Midna's Lament", getSettings().game.midnasLamentNonStop);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Prevents enemy music while Midna's Lament is playing.");
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Input")) {
config::ImGuiCheckbox("Invert Camera X Axis", getSettings().game.invertCameraXAxis);
ImGui::SeparatorText("Gyro");
config::ImGuiCheckbox("Gyro Aim", getSettings().game.enableGyroAim);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Enables the gyroscope on supported controllers\n"
"while in look mode (C-Up) and while aiming the\n"
"Slingshot, Gale Boomerang, Hero's Bow, Clawshot(s),\n"
"Ball and Chain, and Dominion Rod.");
}
config::ImGuiCheckbox("Gyro Rollgoal", getSettings().game.enableGyroRollgoal);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Enables the gyroscope on supported controllers to\n"
"tilt the Rollgoal table in Hena's Cabin.");
}
if (getSettings().game.enableGyroAim || getSettings().game.enableGyroRollgoal) {
config::ImGuiSliderFloat("Gyro Pitch Sensitivity", getSettings().game.gyroSensitivityY, 0.25f, 4.0f, "%.2f");
config::ImGuiSliderFloat("Gyro Yaw Sensitivity", getSettings().game.gyroSensitivityX, 0.25f, 4.0f, "%.2f");
if (getSettings().game.enableGyroRollgoal) {
config::ImGuiSliderFloat("Rollgoal Sensitivity", getSettings().game.gyroSensitivityRollgoal, 0.25f, 4.0f, "%.2f");
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Additional multiplier for scaling how strongly\n"
"the gyroscope affects the Rollgoal table.");
}
}
config::ImGuiSliderFloat("Gyro Deadband", getSettings().game.gyroDeadband, 0.0f, 0.5f, "%.3f");
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Angular rates below this magnitude are treated as zero,\n"
"reducing drift and jitter when the controller is still.");
}
config::ImGuiSliderFloat("Gyro Smoothing", getSettings().game.gyroSmoothing, 0.0f, 1.0f, "%.2f");
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Low values track raw gyro input more closely,\n"
"while higher values smooth out input over time.");
}
config::ImGuiCheckbox("Invert Gyro Pitch", getSettings().game.gyroInvertPitch);
config::ImGuiCheckbox("Invert Gyro Yaw", getSettings().game.gyroInvertYaw);
}
ImGui::SeparatorText("Tools");
config::ImGuiCheckbox("Turbo Key", getSettings().game.enableTurboKeybind);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Hold TAB to increase game speed by up to 4x.");
}
ImGui::EndMenu();
}
ImGui::Separator();
if (ImGui::BeginMenu("Cheats")) {
config::ImGuiCheckbox("Infinite Hearts", getSettings().game.infiniteHearts);
config::ImGuiCheckbox("Infinite Arrows", getSettings().game.infiniteArrows);
config::ImGuiCheckbox("Infinite Bombs", getSettings().game.infiniteBombs);
config::ImGuiCheckbox("Infinite Oil", getSettings().game.infiniteOil);
config::ImGuiCheckbox("Infinite Oxygen", getSettings().game.infiniteOxygen);
config::ImGuiCheckbox("Infinite Rupees", getSettings().game.infiniteRupees);
config::ImGuiCheckbox("Moon Jump (R+A)", getSettings().game.moonJump);
config::ImGuiCheckbox("Super Clawshot", getSettings().game.superClawshot);
config::ImGuiCheckbox("Always Greatspin", getSettings().game.alwaysGreatspin);
config::ImGuiCheckbox("Fast Iron Boots", getSettings().game.enableFastIronBoots);
config::ImGuiCheckbox("Can Transform Anywhere", getSettings().game.canTransformAnywhere);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Allows you to transform even if NPCs are looking.");
}
config::ImGuiCheckbox("Fast Spinner", getSettings().game.fastSpinner);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Speeds up Spinner movement when holding R.");
}
config::ImGuiCheckbox("Free Magic Armor", getSettings().game.freeMagicArmor);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Makes the magic armor work without rupees.");
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Technical")) {
config::ImGuiCheckbox("Restore Wii 1.0 Glitches", getSettings().game.restoreWiiGlitches);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Restores patched glitches from Wii USA 1.0,\n"
"the first released version.");
}
ImGui::EndMenu();
}
ImGui::EndMenu();
}
}
}
-18
View File
@@ -1,18 +0,0 @@
#ifndef DUSK_IMGUI_MENUENHANCEMENTS_HPP
#define DUSK_IMGUI_MENUENHANCEMENTS_HPP
#include <aurora/aurora.h>
#include <pad.h>
#include <string>
#include "imgui.h"
namespace dusk {
class ImGuiMenuEnhancements {
public:
ImGuiMenuEnhancements();
void draw();
};
}
#endif // DUSK_IMGUI_MENUENHANCEMENTS_HPP
+418 -171
View File
@@ -5,45 +5,18 @@
#include "ImGuiConsole.hpp"
#include "ImGuiMenuGame.hpp"
#include "ImGuiConfig.hpp"
#include <imgui_internal.h>
#include "JSystem/JUtility/JUTGamePad.h"
#include "dusk/audio/DuskAudioSystem.h"
#include "dusk/audio/DuskDsp.hpp"
#include "dusk/dusk.h"
#include "dusk/main.h"
#include "dusk/hotkeys.h"
#include "dusk/settings.h"
#include "m_Do/m_Do_controller_pad.h"
#include "m_Do/m_Do_graphic.h"
#include <aurora/gfx.h>
#include <aurora/lib/logging.hpp>
#include <SDL3/SDL_gamepad.h>
#include <SDL3/SDL_misc.h>
#include "dusk/main.h"
#if defined(__APPLE__)
#include <TargetConditionals.h>
#endif
#if defined(_WIN32) || (defined(__APPLE__) && !TARGET_OS_IOS && !TARGET_OS_MACCATALYST) || (defined(__linux__) && !defined(__ANDROID__))
#define DUSK_CAN_OPEN_DATA_FOLDER 1
namespace fs = std::filesystem;
static void OpenDataFolder() {
const std::string path = fs::absolute(fs::path(aurora::g_config.configPath)).generic_string();
#if defined(_WIN32)
const std::string url = std::string("file:///") + path;
#else
const std::string url = std::string("file://") + path;
#endif
(void)SDL_OpenURL(url.c_str());
}
#else
#define DUSK_CAN_OPEN_DATA_FOLDER 0
#endif
namespace {
constexpr int kInternalResolutionScaleMax = 12;
@@ -63,149 +36,13 @@ namespace dusk {
ImGuiMenuGame::ImGuiMenuGame() {}
void ImGuiMenuGame::draw() {
if (ImGui::BeginMenu("Game")) {
if (ImGui::BeginMenu("Graphics")) {
if (!IsMobile) {
if (ImGui::MenuItem("Toggle Fullscreen", hotkeys::TOGGLE_FULLSCREEN)) {
ToggleFullscreen();
}
if (ImGui::MenuItem("Default Window Size")) {
getSettings().video.enableFullscreen.setValue(false);
VISetWindowFullscreen(false);
VISetWindowSize(FB_WIDTH * 2, FB_HEIGHT * 2);
VICenterWindow();
}
}
bool vsync = getSettings().video.enableVsync;
if (ImGui::Checkbox("Enable Vsync", &vsync)) {
getSettings().video.enableVsync.setValue(vsync);
aurora_enable_vsync(vsync);
config::Save();
}
bool lockAspect = getSettings().video.lockAspectRatio;
if (ImGui::Checkbox("Force 4:3 Aspect Ratio", &lockAspect)) {
getSettings().video.lockAspectRatio.setValue(lockAspect);
if (lockAspect) {
AuroraSetViewportPolicy(AURORA_VIEWPORT_FIT);
} else {
AuroraSetViewportPolicy(AURORA_VIEWPORT_STRETCH);
}
config::Save();
}
u32 internalResolutionWidth = 0;
u32 internalResolutionHeight = 0;
AuroraGetRenderSize(&internalResolutionWidth, &internalResolutionHeight);
ImGui::TextDisabled("Current internal resolution: %ux%u", internalResolutionWidth,
internalResolutionHeight);
int scale = std::clamp(getSettings().game.internalResolutionScale.getValue(), 0,
kInternalResolutionScaleMax);
if (ImGui::SliderInt("Internal Resolution", &scale, 0, kInternalResolutionScaleMax,
scale == 0 ? "Auto" : "%dx"))
{
getSettings().game.internalResolutionScale.setValue(scale);
VISetFrameBufferScale(static_cast<float>(scale));
config::Save();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Auto renders at the native window resolution.\n"
"Higher values scale the game's internal framebuffer.");
}
constexpr const char* bloomModeNames[] = {"Off", "Classic", "Dusk"};
int bloomMode = static_cast<int>(getSettings().game.bloomMode.getValue());
if (ImGui::BeginCombo("Bloom", bloomModeNames[bloomMode])) {
for (int i = 0; i < IM_ARRAYSIZE(bloomModeNames); i++) {
const bool selected = bloomMode == i;
if (ImGui::Selectable(bloomModeNames[i], selected)) {
getSettings().game.bloomMode.setValue(static_cast<BloomMode>(i));
config::Save();
}
if (selected) {
ImGui::SetItemDefaultFocus();
}
}
ImGui::EndCombo();
}
bool bloomOff = bloomMode == static_cast<int>(BloomMode::Off);
if (bloomOff) ImGui::BeginDisabled();
float mult = getSettings().game.bloomMultiplier.getValue();
if (ImGui::SliderFloat("Bloom Brightness", &mult, 0.0f, 1.0f, "%.2f")) {
getSettings().game.bloomMultiplier.setValue(mult);
config::Save();
}
if (bloomOff) ImGui::EndDisabled();
ImGui::Checkbox("Enable LOD Bias", &aurora::gx::enableLodBias);
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Audio")) {
ImGui::Text("Master Volume");
if (config::ImGuiSliderInt("##masterVolume", getSettings().audio.masterVolume, 0, 100)) {
dusk::audio::SetMasterVolume(getSettings().audio.masterVolume / 100.0f);
}
if (config::ImGuiCheckbox("Enable Reverb", getSettings().audio.enableReverb)) {
dusk::audio::SetEnableReverb(getSettings().audio.enableReverb);
}
/*
// TODO: Implement additional settings
ImGui::Text("Main Music Volume");
ImGui::SliderFloat("##mainMusicVolume", &getSettings().audio.mainMusicVolume, 0, 100);
ImGui::Text("Sub Music Volume");
ImGui::SliderFloat("##subMusicVolume", &getSettings().audio.subMusicVolume, 0, 100);
ImGui::Text("Sound Effects Volume");
ImGui::SliderFloat("##soundEffectsVolume", &getSettings().audio.soundEffectsVolume, 0, 100);
ImGui::Text("Fanfare Volume");
ImGui::SliderFloat("##fanfareVolume", &getSettings().audio.fanfareVolume, 0, 100);
Z2AudioMgr* audioMgr = Z2AudioMgr::getInterface();
if (audioMgr != nullptr) {
}
*/
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Controller")) {
ImGui::MenuItem("Configure Controller", nullptr, &m_showControllerConfig);
ImGui::Checkbox("Show Input Viewer", &m_showInputViewer);
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Interface")) {
config::ImGuiCheckbox("Skip Pre-Launch UI", getSettings().backend.skipPreLaunchUI);
config::ImGuiCheckbox("Show Pipeline Compilation", getSettings().backend.showPipelineCompilation);
#if DUSK_ENABLE_SENTRY_NATIVE
config::ImGuiCheckbox("Enable Crash Reporting", getSettings().backend.enableCrashReporting);
#endif
if (!IsMobile) {
config::ImGuiCheckbox("Pause on Focus Lost", getSettings().game.pauseOnFocusLost);
}
ImGui::EndMenu();
}
ImGui::Separator();
#if DUSK_CAN_OPEN_DATA_FOLDER
if (ImGui::MenuItem("Open Data Folder")) {
OpenDataFolder();
}
#endif
if (ImGui::BeginMenu("Settings")) {
drawAudioMenu();
drawCheatsMenu();
drawGameplayMenu();
drawGraphicsMenu();
drawInputMenu();
drawInterfaceMenu();
ImGui::Separator();
@@ -221,6 +58,416 @@ namespace dusk {
}
}
void ImGuiMenuGame::drawGraphicsMenu() {
if (ImGui::BeginMenu("Graphics")) {
ImGui::SeparatorText("Display");
if (!IsMobile) {
if (ImGui::MenuItem("Toggle Fullscreen", hotkeys::TOGGLE_FULLSCREEN)) {
ToggleFullscreen();
}
if (ImGui::Button("Restore Default Window Size")) {
getSettings().video.enableFullscreen.setValue(false);
VISetWindowFullscreen(false);
VISetWindowSize(FB_WIDTH * 2, FB_HEIGHT * 2);
VICenterWindow();
}
}
ImGui::Separator();
bool vsync = getSettings().video.enableVsync;
if (ImGui::Checkbox("Enable VSync", &vsync)) {
getSettings().video.enableVsync.setValue(vsync);
aurora_enable_vsync(vsync);
config::Save();
}
bool lockAspect = getSettings().video.lockAspectRatio;
if (ImGui::Checkbox("Force 4:3 Aspect Ratio", &lockAspect)) {
getSettings().video.lockAspectRatio.setValue(lockAspect);
if (lockAspect) {
AuroraSetViewportPolicy(AURORA_VIEWPORT_FIT);
} else {
AuroraSetViewportPolicy(AURORA_VIEWPORT_STRETCH);
}
config::Save();
}
ImGui::SeparatorText("Resolution");
u32 internalResolutionWidth = 0;
u32 internalResolutionHeight = 0;
AuroraGetRenderSize(&internalResolutionWidth, &internalResolutionHeight);
ImGui::TextDisabled("Current internal resolution: %ux%u", internalResolutionWidth,
internalResolutionHeight);
int scale = std::clamp(getSettings().game.internalResolutionScale.getValue(), 0,
kInternalResolutionScaleMax);
if (ImGui::SliderInt("Internal Resolution", &scale, 0, kInternalResolutionScaleMax,
scale == 0 ? "Auto" : "%dx"))
{
getSettings().game.internalResolutionScale.setValue(scale);
VISetFrameBufferScale(static_cast<float>(scale));
config::Save();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Auto renders at the native window resolution.\n"
"Higher values scale the game's internal framebuffer.");
}
config::ImGuiSliderInt("Shadow Resolution", getSettings().game.shadowResolutionMultiplier, 1, 8, "x%d");
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Improves the shadow resolution, making them higher quality.");
}
ImGui::SeparatorText("Post-Processing");
constexpr const char* bloomModeNames[] = {"Off", "Classic", "Dusk"};
int bloomMode = static_cast<int>(getSettings().game.bloomMode.getValue());
if (ImGui::BeginCombo("Bloom", bloomModeNames[bloomMode])) {
for (int i = 0; i < IM_ARRAYSIZE(bloomModeNames); i++) {
const bool selected = bloomMode == i;
if (ImGui::Selectable(bloomModeNames[i], selected)) {
getSettings().game.bloomMode.setValue(static_cast<BloomMode>(i));
config::Save();
}
if (selected) {
ImGui::SetItemDefaultFocus();
}
}
ImGui::EndCombo();
}
bool bloomOff = bloomMode == static_cast<int>(BloomMode::Off);
if (bloomOff) ImGui::BeginDisabled();
float mult = getSettings().game.bloomMultiplier.getValue();
if (ImGui::SliderFloat("Bloom Brightness", &mult, 0.0f, 1.0f, "%.2f")) {
getSettings().game.bloomMultiplier.setValue(mult);
config::Save();
}
if (bloomOff) ImGui::EndDisabled();
ImGui::SeparatorText("Rendering");
config::ImGuiCheckbox("Unlock Framerate", getSettings().game.enableFrameInterpolation);
const bool frameInterpolationHovered = ImGui::IsItemHovered();
ImGui::SameLine();
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.72f, 0.2f, 1.0f));
ImGui::TextUnformatted("[EXPERIMENTAL]");
ImGui::PopStyleColor();
if (frameInterpolationHovered || ImGui::IsItemHovered()) {
ImGui::SetTooltip("Uses inter-frame interpolation to enable higher frame rates.\nVisual artifacts, animation glitches, or instability may occur.");
}
ImGui::Checkbox("Enable LOD Bias", &aurora::gx::enableLodBias);
config::ImGuiCheckbox("Enable Depth of Field", getSettings().game.enableDepthOfField);
ImGui::EndMenu();
}
}
void ImGuiMenuGame::drawGameplayMenu() {
if (ImGui::BeginMenu("Gameplay")) {
ImGui::SeparatorText("General");
config::ImGuiCheckbox("Mirror Mode", getSettings().game.enableMirrorMode);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Mirrors the world horizontally, matching the Wii version of the game.");
}
config::ImGuiCheckbox("Disable Main HUD", getSettings().game.disableMainHUD);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Disables the main HUD of the game.\n"
"Useful for recording or a more immersive experience!");
}
config::ImGuiCheckbox("Restore Wii 1.0 Glitches", getSettings().game.restoreWiiGlitches);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Restores patched glitches from Wii USA 1.0,\n"
"the first released version.");
}
config::ImGuiCheckbox("Enable Rotating Link Doll", getSettings().game.enableLinkDollRotation);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Enables rotating Link in the collection menu with the C-Stick");
}
ImGui::SeparatorText("Difficulty");
config::ImGuiSliderInt("Damage Multiplier", getSettings().game.damageMultiplier, 1, 8, "x%d");
config::ImGuiCheckbox("Instant Death", getSettings().game.instantDeath);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Any hit will instantly kill you.");
}
config::ImGuiCheckbox("No Heart Drops", getSettings().game.noHeartDrops);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Hearts will never drop from enemies,\n"
"pots and various other places.");
}
ImGui::SeparatorText("Quality of Life");
config::ImGuiCheckbox("Bigger Wallets", getSettings().game.biggerWallets);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Wallet sizes are like in the HD version. (500, 1000, 2000)");
}
config::ImGuiCheckbox("Disable Rupee Cutscenes", getSettings().game.disableRupeeCutscenes);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Rupees won't play cutscenes after you've collected them the first time.");
}
config::ImGuiCheckbox("Faster Climbing", getSettings().game.fastClimbing);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Quicker climbing on ladders and vines like the HD version.");
}
config::ImGuiCheckbox("Faster Tears of Light", getSettings().game.fastTears);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Tears of Light dropped by Shadow Insects pop out faster like the HD version.");
}
config::ImGuiCheckbox("Autosave", getSettings().game.autoSave);
const bool autoSaveHovered = ImGui::IsItemHovered();
ImGui::SameLine();
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.72f, 0.2f, 1.0f));
ImGui::TextUnformatted("[EXPERIMENTAL]");
ImGui::PopStyleColor();
if (autoSaveHovered || ImGui::IsItemHovered()) {
ImGui::SetTooltip("Autosaves the game when going to a new area,\n"
"opening a dungeon door, or getting a new item.");
}
config::ImGuiCheckbox("Instant Saves", getSettings().game.instantSaves);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Skip the delay when writing to the Memory Card.");
}
config::ImGuiCheckbox("Hold B for Instant Text", getSettings().game.instantText);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Make text scroll immediately by holding B.");
}
config::ImGuiCheckbox("No Climbing Miss Animation", getSettings().game.noMissClimbing);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Prevents Link from playing a struggle animation\n"
"when grabbing ledges or climbing on vines.");
}
config::ImGuiCheckbox("No Rupee Returns", getSettings().game.noReturnRupees);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Always collect Rupees even if your Wallet is too full.");
}
config::ImGuiCheckbox("No Sword Recoil", getSettings().game.noSwordRecoil);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Link won't recoil when his sword hits walls.");
}
config::ImGuiCheckbox("Skip TV Settings Screen", getSettings().game.hideTvSettingsScreen);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Skip the TV calibration screen shown when loading a save.");
}
config::ImGuiCheckbox("Skip Warning Screen", getSettings().game.skipWarningScreen);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Skip the warning screen shown when starting the game.");
}
config::ImGuiCheckbox("Sun's Song (R+X)", getSettings().game.sunsSong);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Allows Wolf Link to howl and change the time of day.");
}
config::ImGuiCheckbox("Quick Transform (R+Y)", getSettings().game.enableQuickTransform);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Transform instantly by pressing R and Y simultaneously.");
}
ImGui::EndMenu();
}
}
void ImGuiMenuGame::drawCheatsMenu() {
if (ImGui::BeginMenu("Cheats")) {
ImGui::SeparatorText("Resources");
config::ImGuiCheckbox("Infinite Hearts", getSettings().game.infiniteHearts);
config::ImGuiCheckbox("Infinite Arrows", getSettings().game.infiniteArrows);
config::ImGuiCheckbox("Infinite Bombs", getSettings().game.infiniteBombs);
config::ImGuiCheckbox("Infinite Oil", getSettings().game.infiniteOil);
config::ImGuiCheckbox("Infinite Oxygen", getSettings().game.infiniteOxygen);
config::ImGuiCheckbox("Infinite Rupees", getSettings().game.infiniteRupees);
config::ImGuiCheckbox("Items Don't Despawn", getSettings().game.enableIndefiniteItemDrops);
ImGui::SetItemTooltip("Items Don't Despawn Unless You Load A Different Room In Which Case They Do But Even Under Some Circumstances They Don't, It Is Quite Rare Though");
ImGui::SeparatorText("Abilities");
config::ImGuiCheckbox("Moon Jump (R+A)", getSettings().game.moonJump);
config::ImGuiCheckbox("Super Clawshot", getSettings().game.superClawshot);
config::ImGuiCheckbox("Always Greatspin", getSettings().game.alwaysGreatspin);
config::ImGuiCheckbox("Fast Iron Boots", getSettings().game.enableFastIronBoots);
config::ImGuiCheckbox("Can Transform Anywhere", getSettings().game.canTransformAnywhere);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Allows you to transform even if NPCs are looking.");
}
config::ImGuiCheckbox("Fast Spinner", getSettings().game.fastSpinner);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Speeds up Spinner movement when holding R.");
}
config::ImGuiCheckbox("Free Magic Armor", getSettings().game.freeMagicArmor);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Makes the magic armor work without rupees.");
}
ImGui::EndMenu();
}
}
void ImGuiMenuGame::drawAudioMenu() {
if (ImGui::BeginMenu("Audio")) {
ImGui::SeparatorText("Volume");
ImGui::Text("Master Volume");
if (config::ImGuiSliderInt("##masterVolume", getSettings().audio.masterVolume, 0, 100)) {
dusk::audio::SetMasterVolume(getSettings().audio.masterVolume / 100.0f);
}
/*
// TODO: Implement additional settings
ImGui::Text("Main Music Volume");
ImGui::SliderFloat("##mainMusicVolume", &getSettings().audio.mainMusicVolume, 0, 100);
ImGui::Text("Sub Music Volume");
ImGui::SliderFloat("##subMusicVolume", &getSettings().audio.subMusicVolume, 0, 100);
ImGui::Text("Sound Effects Volume");
ImGui::SliderFloat("##soundEffectsVolume", &getSettings().audio.soundEffectsVolume, 0, 100);
ImGui::Text("Fanfare Volume");
ImGui::SliderFloat("##fanfareVolume", &getSettings().audio.fanfareVolume, 0, 100);
Z2AudioMgr* audioMgr = Z2AudioMgr::getInterface();
if (audioMgr != nullptr) {
}
*/
ImGui::SeparatorText("Effects");
if (config::ImGuiCheckbox("Enable Reverb", getSettings().audio.enableReverb)) {
dusk::audio::SetEnableReverb(getSettings().audio.enableReverb);
}
ImGui::SeparatorText("Tweaks");
config::ImGuiCheckbox("No Low HP Sound", getSettings().game.noLowHpSound);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Disable the beeping sound when having low health.");
}
config::ImGuiCheckbox("Non-Stop Midna's Lament", getSettings().game.midnasLamentNonStop);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Prevents enemy music while Midna's Lament is playing.");
}
ImGui::EndMenu();
}
}
void ImGuiMenuGame::drawInputMenu() {
if (ImGui::BeginMenu("Input")) {
ImGui::SeparatorText("Controller");
if (ImGui::Button("Configure Controller")){
m_showControllerConfig = !m_showControllerConfig;
}
ImGui::SeparatorText("Camera");
config::ImGuiCheckbox("Invert Camera X Axis", getSettings().game.invertCameraXAxis);
ImGui::SeparatorText("Gyro");
config::ImGuiCheckbox("Gyro Aim", getSettings().game.enableGyroAim);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Enables the gyroscope on supported controllers\n"
"while in look mode (C-Up) and while aiming the\n"
"Slingshot, Gale Boomerang, Hero's Bow, Clawshot(s),\n"
"Ball and Chain, and Dominion Rod.");
}
config::ImGuiCheckbox("Gyro Rollgoal", getSettings().game.enableGyroRollgoal);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Enables the gyroscope on supported controllers to\n"
"tilt the Rollgoal table in Hena's Cabin.");
}
if (getSettings().game.enableGyroAim || getSettings().game.enableGyroRollgoal) {
config::ImGuiSliderFloat("Gyro Pitch Sensitivity", getSettings().game.gyroSensitivityY, 0.25f, 4.0f, "%.2f");
config::ImGuiSliderFloat("Gyro Yaw Sensitivity", getSettings().game.gyroSensitivityX, 0.25f, 4.0f, "%.2f");
if (getSettings().game.enableGyroRollgoal) {
config::ImGuiSliderFloat("Rollgoal Sensitivity", getSettings().game.gyroSensitivityRollgoal, 0.25f, 4.0f, "%.2f");
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Additional multiplier for scaling how strongly\n"
"the gyroscope affects the Rollgoal table.");
}
}
config::ImGuiSliderFloat("Gyro Deadband", getSettings().game.gyroDeadband, 0.0f, 0.5f, "%.3f");
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Angular rates below this magnitude are treated as zero,\n"
"reducing drift and jitter when the controller is still.");
}
config::ImGuiSliderFloat("Gyro Smoothing", getSettings().game.gyroSmoothing, 0.0f, 1.0f, "%.2f");
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Low values track raw gyro input more closely,\n"
"while higher values smooth out input over time.");
}
config::ImGuiCheckbox("Invert Gyro Pitch", getSettings().game.gyroInvertPitch);
config::ImGuiCheckbox("Invert Gyro Yaw", getSettings().game.gyroInvertYaw);
}
ImGui::SeparatorText("Tools");
config::ImGuiCheckbox("Turbo Key", getSettings().game.enableTurboKeybind);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Hold TAB to increase game speed by up to 4x.");
}
ImGui::Checkbox("Show Input Viewer", &m_showInputViewer);
ImGui::EndMenu();
}
}
void ImGuiMenuGame::drawInterfaceMenu() {
if (ImGui::BeginMenu("Interface")) {
config::ImGuiCheckbox("Skip Pre-Launch UI", getSettings().backend.skipPreLaunchUI);
config::ImGuiCheckbox("Show Pipeline Compilation", getSettings().backend.showPipelineCompilation);
#if DUSK_ENABLE_SENTRY_NATIVE
config::ImGuiCheckbox("Enable Crash Reporting", getSettings().backend.enableCrashReporting);
#endif
if (!IsMobile) {
config::ImGuiCheckbox("Pause on Focus Lost", getSettings().game.pauseOnFocusLost);
}
ImGui::EndMenu();
}
}
static void drawVirtualStick(const char* id, const ImVec2& stick) {
float scale = ImGuiScale();
ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPos().x + 45 * scale, ImGui::GetCursorPos().y + 10));
+7
View File
@@ -19,6 +19,13 @@ namespace dusk {
static void ToggleFullscreen();
private:
void drawAudioMenu();
void drawInputMenu();
void drawGraphicsMenu();
void drawGameplayMenu();
void drawCheatsMenu();
void drawInterfaceMenu();
struct {
int m_selectedPort = 0;
bool m_isReading = false;
+55 -8
View File
@@ -16,10 +16,58 @@
#include "dusk/main.h"
#include "m_Do/m_Do_main.h"
#include <aurora/lib/internal.hpp>
#include <SDL3/SDL_misc.h>
#if defined(__APPLE__)
#include <TargetConditionals.h>
#endif
#if defined(_WIN32) || (defined(__APPLE__) && !TARGET_OS_IOS && !TARGET_OS_MACCATALYST) || (defined(__linux__) && !defined(__ANDROID__))
#define DUSK_CAN_OPEN_DATA_FOLDER 1
namespace fs = std::filesystem;
static void OpenDataFolder() {
const std::string path = fs::absolute(dusk::ConfigPath).generic_string();
#if defined(_WIN32)
const std::string url = std::string("file:///") + path;
#else
const std::string url = std::string("file://") + path;
#endif
(void)SDL_OpenURL(url.c_str());
}
#else
#define DUSK_CAN_OPEN_DATA_FOLDER 0
#endif
namespace dusk {
ImGuiMenuTools::ImGuiMenuTools() {}
void ImGuiMenuTools::draw() {
if (ImGui::BeginMenu("Tools")) {
if (!dusk::IsGameLaunched) {
ImGui::BeginDisabled();
}
ImGui::MenuItem("Save Editor", hotkeys::SHOW_SAVE_EDITOR, &m_showSaveEditor);
ImGui::MenuItem("Map Loader", hotkeys::SHOW_MAP_LOADER, &m_showMapLoader);
ImGui::MenuItem("State Share", hotkeys::SHOW_STATE_SHARE, &m_showStateShare);
if (!dusk::IsGameLaunched) {
ImGui::EndDisabled();
}
#if DUSK_CAN_OPEN_DATA_FOLDER
ImGui::Separator();
if (ImGui::MenuItem("Open Data Folder")) {
OpenDataFolder();
}
#endif
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Debug")) {
bool developmentMode = mDoMain::developmentMode == 1;
if (ImGui::Checkbox("Development Mode", &developmentMode)) {
@@ -59,9 +107,6 @@ namespace dusk {
ImGui::MenuItem("Debug Overlay", hotkeys::SHOW_DEBUG_OVERLAY, &m_showDebugOverlay);
ImGui::MenuItem("Heap Viewer", hotkeys::SHOW_HEAP_VIEWER, &m_showHeapOverlay);
ImGui::MenuItem("Player Info", hotkeys::SHOW_PLAYER_INFO, &m_showPlayerInfo);
ImGui::MenuItem("Save Editor", hotkeys::SHOW_SAVE_EDITOR, &m_showSaveEditor);
ImGui::MenuItem("Map Loader", hotkeys::SHOW_MAP_LOADER, &m_showMapLoader);
ImGui::MenuItem("State Share", hotkeys::SHOW_STATE_SHARE, &m_showStateShare);
ImGui::MenuItem("Debug Camera", hotkeys::SHOW_DEBUG_CAMERA, &m_showCameraOverlay);
ImGui::MenuItem("Audio Debug", hotkeys::SHOW_AUDIO_DEBUG, &m_showAudioDebug);
ImGui::MenuItem("Bloom", nullptr, &m_showBloomWindow);
@@ -96,7 +141,9 @@ namespace dusk {
ImGui::SetNextWindowBgAlpha(0.65f);
if (ImGui::Begin("Debug Overlay", nullptr, windowFlags)) {
ImGuiStringViewText(fmt::format(FMT_STRING("FPS: {:.2f}\n"), io.Framerate));
ImGuiStringViewText(fmt::format(FMT_STRING("Frame usage: {:.1f}%\n"), frameUsagePct));
if (frameUsagePct > 0.f) {
ImGuiStringViewText(fmt::format(FMT_STRING("Frame usage: {:.1f}%\n"), frameUsagePct));
}
ImGui::Separator();
@@ -163,7 +210,7 @@ namespace dusk {
ImGui::Text("Link");
ImGuiStringViewText(
player != nullptr
? fmt::format("Position: {: .2f}, {: .2f}, {: .2f}\n", player->current.pos.x, player->current.pos.y, player->current.pos.z)
? fmt::format("Position: {: .4f}, {: .4f}, {: .4f}\n", player->current.pos.x, player->current.pos.y, player->current.pos.z)
: "Position: ?, ?, ?\n"
);
@@ -175,7 +222,7 @@ namespace dusk {
ImGuiStringViewText(
player != nullptr
? fmt::format("Speed: {0}\n", player->speedF)
? fmt::format("Speed: {: .4f}\n", player->speedF)
: "Speed: ?\n"
);
@@ -183,7 +230,7 @@ namespace dusk {
ImGui::Text("Epona");
ImGuiStringViewText(
horse != nullptr
? fmt::format("Position: {: .2f}, {: .2f}, {: .2f}\n", horse->current.pos.x, horse->current.pos.y, horse->current.pos.z)
? fmt::format("Position: {: .4f}, {: .4f}, {: .4f}\n", horse->current.pos.x, horse->current.pos.y, horse->current.pos.z)
: "Position: ?, ?, ?\n"
);
@@ -195,7 +242,7 @@ namespace dusk {
ImGuiStringViewText(
horse != nullptr
? fmt::format("Speed: {0}\n", horse->speedF)
? fmt::format("Speed: {: .4f}\n", horse->speedF)
: "Speed: ?\n"
);
+28
View File
@@ -46,6 +46,17 @@ static std::string ShowIsoInvalidError(const iso::ValidationError code) {
}
}
static std::string_view card_type_name(CARDFileType type) {
switch (type) {
case CARD_GCIFOLDER:
return "GCI Folder"sv;
case CARD_RAWIMAGE:
return "Card Image"sv;
default:
return ""sv;
}
}
void fileDialogCallback(void* userdata, const char* path, const char* error) {
auto* self = static_cast<ImGuiPreLaunchWindow*>(userdata);
if (error != nullptr) {
@@ -216,6 +227,23 @@ void ImGuiPreLaunchWindow::drawOptions() {
if (configuredBackendId != m_initialGraphicsBackend) {
ImGui::TextDisabled("Restart Required");
}
auto curFileType = (CARDFileType)getSettings().backend.cardFileType.getValue();
if (ImGui::BeginCombo("Save File Type", card_type_name(curFileType).data())) {
if (ImGui::Selectable("GCI Folder", curFileType == CARD_GCIFOLDER)) {
getSettings().backend.cardFileType.setValue(CARD_GCIFOLDER);
config::Save();
}
if (ImGui::Selectable("Card Image", curFileType == CARD_RAWIMAGE)) {
getSettings().backend.cardFileType.setValue(CARD_RAWIMAGE);
config::Save();
}
ImGui::EndCombo();
}
ImGui::EndChild();
}
+12 -10
View File
@@ -10,6 +10,7 @@
#include "d/d_item_data.h"
#include "d/d_meter2_info.h"
#include "d/d_save.h"
#include "d/actor/d_a_player.h"
#include <map>
@@ -579,20 +580,21 @@ namespace dusk {
if (ImGui::BeginCombo("Clothes", itemMap.find(statusA.mSelectEquip[0])->second.m_name.c_str())) {
if (ImGui::Selectable("None")) {
statusA.mSelectEquip[0] = dItemNo_NONE_e;
}
if (ImGui::Selectable("Ordon Clothes")) {
statusA.mSelectEquip[0] = dItemNo_WEAR_CASUAL_e;
dMeter2Info_setCloth(dItemNo_WEAR_CASUAL_e, false);
daPy_getPlayerActorClass()->setClothesChange(0);
}
if (ImGui::Selectable("Hero's Clothes")) {
statusA.mSelectEquip[0] = dItemNo_WEAR_KOKIRI_e;
dMeter2Info_setCloth(dItemNo_WEAR_KOKIRI_e, false);
daPy_getPlayerActorClass()->setClothesChange(0);
}
if (ImGui::Selectable("Zora Armor")) {
statusA.mSelectEquip[0] = dItemNo_WEAR_ZORA_e;
dMeter2Info_setCloth(dItemNo_WEAR_ZORA_e, false);
daPy_getPlayerActorClass()->setClothesChange(0);
}
if (ImGui::Selectable("Magic Armor")) {
statusA.mSelectEquip[0] = dItemNo_ARMOR_e;
dMeter2Info_setCloth(dItemNo_ARMOR_e, false);
daPy_getPlayerActorClass()->setClothesChange(0);
}
ImGui::EndCombo();
}
@@ -1489,11 +1491,11 @@ namespace dusk {
}
ImGui::TableNextColumn();
ImGui::Text(e.flagName.c_str());
ImGuiStringViewText(e.flagName);
ImGui::TableNextColumn();
ImGui::Text(e.location.c_str());
ImGuiStringViewText(e.location);
ImGui::TableNextColumn();
ImGui::Text(e.description.c_str());
ImGuiStringViewText(e.description);
}
ImGui::EndTable();
}
+323 -30
View File
@@ -5,14 +5,24 @@
#include "imgui.h"
#include "fmt/format.h"
#include "absl/strings/escaping.h"
#include "nlohmann/json.hpp"
#include "d/d_com_inf_game.h"
#include "dusk/main.h"
#include "dusk/io.hpp"
#include "dusk/logging.h"
#include "dusk/settings.h"
#include "f_op/f_op_overlap_mng.h"
#include "../file_select.hpp"
#include "aurora/lib/window.hpp"
#include <unordered_set>
#include <zstd.h>
namespace dusk {
using json = nlohmann::json;
#pragma pack(push, 1)
struct StateSharePacket {
char stageName[8];
@@ -23,9 +33,65 @@ struct StateSharePacket {
};
#pragma pack(pop)
static constexpr size_t PACKET_TOTAL = sizeof(StateSharePacket) + sizeof(dSv_info_c);
static constexpr size_t PACKET_TOTAL = sizeof(StateSharePacket) + sizeof(dSv_info_c);
static constexpr size_t PACKET_SAVE_ONLY = sizeof(StateSharePacket) + sizeof(dSv_save_c);
static constexpr auto STATES_FILENAME = "states.json";
void ImGuiStateShare::copyState() {
static bool ValidateEncodedState(const std::string&);
void ImGuiStateShare::onMergeFileSelected(void* userdata, const char* path, const char* /*error*/) {
auto* self = static_cast<ImGuiStateShare*>(userdata);
if (path != nullptr) {
self->m_pendingMergePath = path;
}
}
static std::string GetStatesFilePath() {
return (dusk::ConfigPath / STATES_FILENAME).string();
}
void ImGuiStateShare::loadStatesFile() {
m_loaded = true;
const std::filesystem::path filePath = dusk::ConfigPath / STATES_FILENAME;
if (!std::filesystem::exists(filePath)) {
return;
}
try {
const std::string pathStr = filePath.string();
auto data = io::FileStream::ReadAllBytes(pathStr.c_str());
auto j = json::parse(data);
if (!j.is_array()) {
return;
}
for (const auto& entry : j) {
if (!entry.contains("name") || !entry.contains("data")) {
continue;
}
SavedStateEntry s;
s.name = entry["name"].get<std::string>();
s.encoded = entry["data"].get<std::string>();
m_states.push_back(std::move(s));
}
} catch (const std::exception& e) {
m_statusMsg = fmt::format("Failed to load states: {}", e.what());
}
}
void ImGuiStateShare::saveStatesFile() {
json j = json::array();
for (const auto& s : m_states) {
j.push_back(json{{"name", s.name}, {"data", s.encoded}});
}
try {
io::FileStream::WriteAllText(GetStatesFilePath().c_str(), j.dump(2));
} catch (const std::exception& e) {
m_statusMsg = fmt::format("Failed to save states: {}", e.what());
}
}
std::string ImGuiStateShare::encodeCurrentState() {
StateSharePacket pkt = {};
strncpy(pkt.stageName, dComIfGp_getStartStageName(), 7);
pkt.roomNo = dComIfGp_getStartStageRoomNo();
@@ -40,26 +106,25 @@ void ImGuiStateShare::copyState() {
std::string compressed(bound, '\0');
compressed.resize(ZSTD_compress(compressed.data(), bound, raw.data(), raw.size(), 1));
std::string encoded = absl::Base64Escape(compressed);
ImGui::SetClipboardText(encoded.c_str());
m_statusMsg = "Copied to clipboard.";
return absl::Base64Escape(compressed);
}
bool ImGuiStateShare::pasteState() {
const char* clip = ImGui::GetClipboardText();
if (!clip || clip[0] == '\0') {
m_statusMsg = "Clipboard is empty.";
return false;
}
bool ImGuiStateShare::applyEncodedState(const std::string& encoded, const std::string& name) {
std::string decoded;
if (!absl::Base64Unescape(clip, &decoded)) {
if (!absl::Base64Unescape(encoded, &decoded)) {
m_statusMsg = "Invalid base64.";
return false;
}
unsigned long long dSize = ZSTD_getFrameContentSize(decoded.data(), decoded.size());
if (dSize == ZSTD_CONTENTSIZE_ERROR || dSize == ZSTD_CONTENTSIZE_UNKNOWN || dSize < PACKET_TOTAL) {
if (dSize == ZSTD_CONTENTSIZE_ERROR || dSize == ZSTD_CONTENTSIZE_UNKNOWN) {
m_statusMsg = "Not a valid state string.";
return false;
}
const bool isFull = (dSize == PACKET_TOTAL);
const bool isPartial = (dSize == PACKET_SAVE_ONLY);
if (!isFull && !isPartial) {
m_statusMsg = "Not a valid state string.";
return false;
}
@@ -75,45 +140,272 @@ bool ImGuiStateShare::pasteState() {
memcpy(&pkt, raw.data(), sizeof(pkt));
pkt.stageName[7] = '\0';
memcpy(&g_dComIfG_gameInfo.info, raw.data() + sizeof(pkt), sizeof(dSv_info_c));
if (isFull) {
memcpy(&g_dComIfG_gameInfo.info, raw.data() + sizeof(pkt), sizeof(dSv_info_c));
m_pendingInfo = g_dComIfG_gameInfo.info;
m_pendingSavedata.reset();
} else {
memcpy(&g_dComIfG_gameInfo.info.mSavedata, raw.data() + sizeof(pkt), sizeof(dSv_save_c));
m_pendingSavedata = g_dComIfG_gameInfo.info.mSavedata;
m_pendingInfo.reset();
}
s16 spawnPoint = pkt.startPoint == -4 ? -1 : pkt.startPoint;
if (spawnPoint == -1) {
dComIfGs_setRestartRoomParam(pkt.roomNo & 0x3F);
}
dComIfGp_setNextStage(pkt.stageName, spawnPoint, pkt.roomNo, pkt.layer);
m_pendingInfo = g_dComIfG_gameInfo.info;
dusk::getTransientSettings().stateShareLoadActive = true;
m_stateSharePeekSeen = false;
dComIfGp_setNextStage(pkt.stageName, spawnPoint, pkt.roomNo, pkt.layer, 0.0f, 0, 1, 0, 0, 1, 3);
m_statusMsg = fmt::format("Warping to {} room {} layer {}.", pkt.stageName, (int)pkt.roomNo, (int)pkt.layer);
if (name.empty()) {
m_statusMsg = fmt::format("{} room {} layer {}.", pkt.stageName, (int)pkt.roomNo, (int)pkt.layer);
} else {
m_statusMsg = fmt::format("{}: {} room {} layer {}.", name, pkt.stageName, (int)pkt.roomNo, (int)pkt.layer);
}
return true;
}
void ImGuiStateShare::tickPendingApply() {
if (!m_pendingInfo.has_value() || dComIfGp_isEnableNextStage())
if (!m_pendingInfo.has_value() && !m_pendingSavedata.has_value()) {
return;
g_dComIfG_gameInfo.info = *m_pendingInfo;
m_pendingInfo.reset();
}
if (dComIfGp_isEnableNextStage()) {
return;
}
if (m_pendingInfo.has_value()) {
g_dComIfG_gameInfo.info = *m_pendingInfo;
m_pendingInfo.reset();
} else {
g_dComIfG_gameInfo.info.mSavedata = *m_pendingSavedata;
m_pendingSavedata.reset();
}
dComIfGp_offOxygenShowFlag();
dComIfGp_setMaxOxygen(600);
dComIfGp_setOxygen(600);
}
static bool ValidateEncodedState(const std::string& encoded) {
std::string decoded;
if (!absl::Base64Unescape(encoded, &decoded)) {
return false;
}
unsigned long long dSize = ZSTD_getFrameContentSize(decoded.data(), decoded.size());
return dSize == PACKET_TOTAL || dSize == PACKET_SAVE_ONLY;
}
void ImGuiStateShare::mergeFromFile(const std::string& path) {
try {
auto data = io::FileStream::ReadAllBytes(path.c_str());
auto j = json::parse(data);
if (!j.is_array()) {
m_statusMsg = "File does not contain a JSON array.";
return;
}
std::unordered_set<std::string> existingNames;
for (const auto& s : m_states) {
existingNames.insert(s.name);
}
int added = 0;
int skipped = 0;
for (const auto& entry : j) {
if (!entry.contains("name") || !entry.contains("data")) {
++skipped;
continue;
}
const std::string name = entry["name"].get<std::string>();
const std::string encoded = entry["data"].get<std::string>();
if (!ValidateEncodedState(encoded)) {
++skipped;
continue;
}
if (existingNames.count(name)) {
++skipped;
continue;
}
SavedStateEntry s;
s.name = name;
s.encoded = encoded;
existingNames.insert(s.name);
m_states.push_back(std::move(s));
++added;
}
if (added > 0) {
saveStatesFile();
}
m_statusMsg = fmt::format("Merged: {} added, {} skipped.", added, skipped);
} catch (const std::exception& e) {
m_statusMsg = fmt::format("Failed to load file: {}", e.what());
}
}
void ImGuiStateShare::draw(bool& open) {
if (dusk::IsGameLaunched)
if (dusk::IsGameLaunched) {
tickPendingApply();
if (dusk::getTransientSettings().stateShareLoadActive) {
if (fopOvlpM_IsPeek()) {
m_stateSharePeekSeen = true;
} else if (m_stateSharePeekSeen) {
dusk::getTransientSettings().stateShareLoadActive = false;
m_stateSharePeekSeen = false;
}
}
}
if (!open)
if (!m_loaded) {
loadStatesFile();
}
if (!m_pendingMergePath.empty()) {
mergeFromFile(m_pendingMergePath);
m_pendingMergePath.clear();
}
if (!open) {
return;
}
if (!ImGui::Begin("State Share", &open, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)) {
ImGui::SetNextWindowSizeConstraints(ImVec2(400, 0), ImVec2(FLT_MAX, FLT_MAX));
if (!ImGui::Begin("State Manager", &open, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)) {
ImGui::End();
return;
}
if (!dusk::IsGameLaunched) ImGui::BeginDisabled();
if (ImGui::Button("Copy State")) copyState();
const bool gameRunning = dusk::IsGameLaunched;
const bool loadInProgress = dusk::getTransientSettings().stateShareLoadActive;
const float rowH = ImGui::GetTextLineHeightWithSpacing();
const float listH = rowH * 8 + ImGui::GetStyle().FramePadding.y * 2;
ImGui::BeginChild("##states", ImVec2(0, listH), true);
if (m_states.empty()) {
ImGui::TextDisabled("No saved states. Save or import one below.");
}
int toDelete = -1;
for (int i = 0; i < (int)m_states.size(); ++i) {
ImGui::PushID(i);
if (m_renamingIndex == i) {
ImGui::SetNextItemWidth(150);
bool done = ImGui::InputText("##rename", m_renameBuffer, sizeof(m_renameBuffer),
ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll);
if (done) {
if (m_renameBuffer[0] != '\0') {
m_states[i].name = m_renameBuffer;
}
m_renamingIndex = -1;
saveStatesFile();
} else if (ImGui::IsItemDeactivated()) {
m_renamingIndex = -1;
}
} else {
ImGui::Selectable(m_states[i].name.c_str(), false, ImGuiSelectableFlags_None, ImVec2(150, 0));
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Double-click to rename");
if (ImGui::IsMouseDoubleClicked(0)) {
m_renamingIndex = i;
strncpy(m_renameBuffer, m_states[i].name.c_str(), sizeof(m_renameBuffer) - 1);
m_renameBuffer[sizeof(m_renameBuffer) - 1] = '\0';
ImGui::SetKeyboardFocusHere(-1);
}
}
}
ImGui::SameLine();
if (!gameRunning || loadInProgress) { ImGui::BeginDisabled(); }
if (ImGui::Button("Load")) {
applyEncodedState(m_states[i].encoded, m_states[i].name);
}
if (!gameRunning || loadInProgress) { ImGui::EndDisabled(); }
ImGui::SameLine();
if (ImGui::Button("Copy")) {
ImGui::SetClipboardText(m_states[i].encoded.c_str());
m_statusMsg = fmt::format("'{}' copied to clipboard.", m_states[i].name);
}
ImGui::SameLine();
if (ImGui::Button("Del")) {
toDelete = i;
}
ImGui::PopID();
}
if (toDelete >= 0) {
if (m_renamingIndex == toDelete) { m_renamingIndex = -1; }
m_states.erase(m_states.begin() + toDelete);
saveStatesFile();
}
ImGui::EndChild();
// Toolbar
if (!gameRunning) { ImGui::BeginDisabled(); }
if (ImGui::Button("Save")) {
SavedStateEntry entry;
entry.name = fmt::format("State {}", m_states.size() + 1);
entry.encoded = encodeCurrentState();
m_states.push_back(std::move(entry));
saveStatesFile();
m_statusMsg = fmt::format("Saved as '{}'.", m_states.back().name);
}
if (!gameRunning) { ImGui::EndDisabled(); }
ImGui::SameLine();
if (ImGui::Button("Import State")) pasteState();
if (!dusk::IsGameLaunched) ImGui::EndDisabled();
if (ImGui::Button("Import Clipboard")) {
const char* clip = ImGui::GetClipboardText();
if (!clip || clip[0] == '\0') {
m_statusMsg = "Clipboard is empty.";
} else {
std::string clipStr = clip;
if (!ValidateEncodedState(clipStr)) {
m_statusMsg = "Clipboard does not contain a valid state.";
} else {
SavedStateEntry entry;
entry.name = fmt::format("Imported {}", m_states.size() + 1);
entry.encoded = std::move(clipStr);
m_states.push_back(std::move(entry));
saveStatesFile();
m_statusMsg = fmt::format("Imported as '{}'.", m_states.back().name);
}
}
}
ImGui::SameLine();
if (ImGui::Button("Load Pack")) {
static constexpr SDL_DialogFileFilter filter = {"State pack", "json"};
ShowFileSelect(&onMergeFileSelected, this, aurora::window::get_sdl_window(), &filter, 1, nullptr, false);
}
if (!m_states.empty()) {
ImGui::SameLine();
if (ImGui::Button("Clear All")) {
ImGui::OpenPopup("##clearall");
}
if (ImGui::BeginPopup("##clearall")) {
ImGui::Text("Delete all saved states?");
ImGui::Spacing();
if (ImGui::Button("Yes, clear all")) {
m_states.clear();
m_renamingIndex = -1;
saveStatesFile();
m_statusMsg = "All states cleared.";
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel")) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
if (!m_statusMsg.empty()) {
ImGui::Spacing();
@@ -125,8 +417,9 @@ void ImGuiStateShare::draw(bool& open) {
}
void ImGuiMenuTools::ShowStateShare() {
if (!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F8, m_showStateShare))
if (!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F8, m_showStateShare)) {
return;
}
m_stateShare.draw(m_showStateShare);
}
+29 -11
View File
@@ -4,21 +4,39 @@
#include "d/d_save.h"
#include <optional>
#include <string>
#include <vector>
namespace dusk {
class ImGuiStateShare {
public:
void draw(bool& open);
private:
void copyState();
bool pasteState();
void tickPendingApply();
struct SavedStateEntry {
std::string name;
std::string encoded;
};
class ImGuiStateShare {
public:
void draw(bool& open);
private:
std::string encodeCurrentState();
bool applyEncodedState(const std::string& encoded, const std::string& name = {});
void tickPendingApply();
void loadStatesFile();
void saveStatesFile();
void mergeFromFile(const std::string& path);
static void onMergeFileSelected(void* userdata, const char* path, const char* error);
std::vector<SavedStateEntry> m_states;
std::string m_statusMsg;
std::optional<dSv_info_c> m_pendingInfo;
std::optional<dSv_save_c> m_pendingSavedata;
int m_renamingIndex = -1;
char m_renameBuffer[128] = {};
bool m_loaded = false;
bool m_stateSharePeekSeen = false;
std::string m_pendingMergePath;
};
std::string m_statusMsg;
std::optional<dSv_info_c> m_pendingInfo;
};
}
#endif
+3 -3
View File
@@ -168,14 +168,14 @@ void aurora_log_callback(AuroraLogLevel level, const char* module, const char* m
aurora::Module DuskLog("dusk");
void dusk::InitializeFileLogging(const char* configDir, AuroraLogLevel logLevel) {
void dusk::InitializeFileLogging(const std::filesystem::path& configDir, AuroraLogLevel logLevel) {
std::lock_guard lock(g_logMutex);
if (g_logFile != nullptr || configDir == nullptr) {
if (g_logFile != nullptr || configDir.empty()) {
return;
}
std::error_code ec;
const std::filesystem::path logsDir = std::filesystem::path(configDir) / "logs";
const std::filesystem::path logsDir = configDir / "logs";
std::filesystem::create_directories(logsDir, ec);
if (ec) {
std::fprintf(stderr, "[WARNING | dusk] Failed to create log directory '%s': %s\n",
+17 -9
View File
@@ -44,6 +44,7 @@ UserSettings g_userSettings = {
.invertCameraXAxis {"game.invertCameraXAxis", false},
.disableMainHUD {"game.disableMainHUD", false},
.pauseOnFocusLost {"game.pauseOnFocusLost", false},
.enableLinkDollRotation = {"game.enableLinkDollRotation", false },
// Graphics
.bloomMode {"game.bloomMode", BloomMode::Classic},
@@ -52,6 +53,7 @@ UserSettings g_userSettings = {
.enableFrameInterpolation {"game.enableFrameInterpolation", false},
.internalResolutionScale {"game.internalResolutionScale", 0},
.shadowResolutionMultiplier {"game.shadowResolutionMultiplier", 1},
.enableDepthOfField {"game.enableDepthOfField", true},
// Audio
.noLowHpSound {"game.noLowHpSound", false},
@@ -70,14 +72,15 @@ UserSettings g_userSettings = {
// Cheats
.infiniteHearts {"game.infiniteHearts", false},
.infiniteArrows {"game.infiniteArrows", false},
.infiniteBombs {"game.infiniteBombs", false},
.infiniteOil {"game.infiniteOil", false},
.infiniteOxygen {"game.infiniteOxygen", false},
.infiniteRupees {"game.infiniteRupees", false},
.moonJump {"game.moonJump", false},
.superClawshot {"game.superClawshot", false},
.alwaysGreatspin {"game.alwaysGreatspin", false},
.infiniteArrows{"game.infiniteArrows", false},
.infiniteBombs{"game.infiniteBombs", false},
.infiniteOil{"game.infiniteOil", false},
.infiniteOxygen{"game.infiniteOxygen", false},
.infiniteRupees{"game.infiniteRupees", false},
.enableIndefiniteItemDrops {"game.enableIndefiniteItemDrops", false},
.moonJump{"game.moonJump", false},
.superClawshot{"game.superClawshot", false},
.alwaysGreatspin{"game.alwaysGreatspin", false},
.enableFastIronBoots {"game.enableFastIronBoots", false},
.canTransformAnywhere {"game.canTransformAnywhere", false},
.fastSpinner {"game.fastSpinner", false},
@@ -97,7 +100,8 @@ UserSettings g_userSettings = {
.showPipelineCompilation {"backend.showPipelineCompilation", false},
.wasPresetChosen {"backend.wasPresetChosen", false},
.enableCrashReporting {"backend.enableCrashReporting", true},
.duskMenuOpen {"backend.duskMenuOpen", false}
.duskMenuOpen {"backend.duskMenuOpen", false},
.cardFileType {"backend.cardFileType", static_cast<int>(CARD_GCIFOLDER)}
}
};
@@ -145,10 +149,12 @@ void registerSettings() {
Register(g_userSettings.game.disableWaterRefraction);
Register(g_userSettings.game.internalResolutionScale);
Register(g_userSettings.game.shadowResolutionMultiplier);
Register(g_userSettings.game.enableDepthOfField);
Register(g_userSettings.game.enableFastIronBoots);
Register(g_userSettings.game.canTransformAnywhere);
Register(g_userSettings.game.freeMagicArmor);
Register(g_userSettings.game.restoreWiiGlitches);
Register(g_userSettings.game.enableLinkDollRotation);
Register(g_userSettings.game.noMissClimbing);
Register(g_userSettings.game.noLowHpSound);
Register(g_userSettings.game.midnasLamentNonStop);
@@ -160,6 +166,7 @@ void registerSettings() {
Register(g_userSettings.game.infiniteOil);
Register(g_userSettings.game.infiniteOxygen);
Register(g_userSettings.game.infiniteRupees);
Register(g_userSettings.game.enableIndefiniteItemDrops);
Register(g_userSettings.game.moonJump);
Register(g_userSettings.game.superClawshot);
Register(g_userSettings.game.alwaysGreatspin);
@@ -181,6 +188,7 @@ void registerSettings() {
Register(g_userSettings.backend.wasPresetChosen);
Register(g_userSettings.backend.enableCrashReporting);
Register(g_userSettings.backend.duskMenuOpen);
Register(g_userSettings.backend.cardFileType);
}
// Transient settings
+3 -89
View File
@@ -25,9 +25,8 @@
#if TARGET_PC
#include "tracy/Tracy.hpp"
#include <dusk/imgui/ImGuiConsole.hpp>
#include <m_Do/m_Do_MemCardRWmng.h>
#include <m_Do/m_Do_MemCard.h>
#include <dusk/gamepad_color.h>
#include <dusk/autosave.h>
#endif
fapGm_HIO_c::fapGm_HIO_c() {
@@ -739,93 +738,8 @@ static void fapGm_AfterRecord() {
fapGm_After();
}
u8 mSaveBuffer[QUEST_LOG_SIZE * 3];
u8 mAutoSaveProc = 0;
int autoSaveWriteState = 0;
typedef void (*AutoSaveFuncs)();
static AutoSaveFuncs AutoSaveFuncsProc[] = {
noAutoSave,
enterAutoSave,
autoSaving,
waitingForWrite,
endAutoSave,
};
void noAutoSave() {}
void triggerAutoSave() {
if (dusk::getSettings().game.autoSave && mAutoSaveProc == 0 && strcmp(dComIfGp_getStartStageName(), "F_SP102") != 0 && dComIfGp_getStartStageLayer() != 0)
{
mAutoSaveProc = 1;
}
}
void updateAutoSave() {
(AutoSaveFuncsProc[mAutoSaveProc])();
}
void writeAutoSave() {
dComIfGs_setMemoryToCard(mSaveBuffer, dComIfGs_getDataNum());
mDoMemCdRWm_SetCheckSumGameData(mSaveBuffer, dComIfGs_getDataNum());
u8* save = mSaveBuffer;
for (int i = 0; i < 3; i++) {
mDoMemCdRWm_TestCheckSumGameData(save);
save += QUEST_LOG_SIZE;
}
g_mDoMemCd_control.save(mSaveBuffer, sizeof(mSaveBuffer), 0);
}
void autoSaving() {
int cardState = g_mDoMemCd_control.LoadSync(mSaveBuffer, sizeof(mSaveBuffer), 0);
if (cardState != 0) {
if (cardState == 2) {
mAutoSaveProc = 1;
} else if (cardState == 1) {
writeAutoSave();
mAutoSaveProc = 3;
}
}
}
void enterAutoSave() {
u32 cardStatus = g_mDoMemCd_control.getStatus(0);
if (cardStatus != 14) {
switch (cardStatus) {
case 2:
g_mDoMemCd_control.load();
mAutoSaveProc = 2;
break;
case 3:
case 4:
case 5:
break;
default:
mAutoSaveProc = 0;
break;
}
}
}
void waitingForWrite() {
autoSaveWriteState = g_mDoMemCd_control.SaveSync();
if (autoSaveWriteState == 2) {
mAutoSaveProc = 0;
} else if (autoSaveWriteState == 1) {
mAutoSaveProc = 4;
}
}
void endAutoSave() {
dusk::g_imguiConsole.ShowToast("Saving...", 2.0f);
mAutoSaveProc = 0;
}
static void duskExecute() {
handleGamepadColor();
updateAutoSave();
if (mDoCPd_c::getHoldR(PAD_1) && mDoCPd_c::getTrigX(PAD_1)) {
+2
View File
@@ -77,6 +77,8 @@ static OSThread MemCardThread;
void mDoMemCd_Ctrl_c::ThdInit() {
#if !PLATFORM_SHIELD
CARDSetLoadType((CARDFileType)dusk::getSettings().backend.cardFileType.getValue());
CARDInit(DUSK_GAME_NAME, DUSK_GAME_VERSION);
#endif
+6 -1
View File
@@ -351,8 +351,13 @@ void mDoExt_modelUpdateDL(J3DModel* i_model) {
void mDoExt_modelEntryDL(J3DModel* i_model) {
#if TARGET_PC
if (!dusk::frame_interp::is_sim_frame())
if (!dusk::frame_interp::is_sim_frame()) {
// FRAME INTERP NOTE: This fixes issue #355 where some lights would flicker.
// This is likely better solved by updating J3DMaterial::needsInterpCallBack,
// but it's unclear what exactly needs to be added.
i_model->diff();
return;
}
#endif
modelMtxErrorCheck(i_model);
+3 -7
View File
@@ -1029,15 +1029,8 @@ static void drawDepth2(view_class* param_0, view_port_class* param_1, int param_
GX_FALSE, 0);
}
#if TARGET_PC
// use full size for pc for higher quality background elements
u16 halfWidth = width;
u16 halfHeight = height;
#else
u16 halfWidth = width >> 1;
u16 halfHeight = height >> 1;
#endif
GXRenderModeObj* sp24 = JUTGetVideoManager()->getRenderMode();
GXSetCopyFilter(GX_FALSE, NULL, GX_TRUE, sp24->vfilter);
GXSetTexCopySrc(x_orig, y_orig_pos, width, height);
@@ -1155,6 +1148,9 @@ static void drawDepth2(view_class* param_0, view_port_class* param_1, int param_
GXSetProjection(ortho, GX_ORTHOGRAPHIC);
GXSetCurrentMtx(0);
#ifdef TARGET_PC
if (dusk::getSettings().game.enableDepthOfField)
#endif
if (l_tevColor0.a > -255 && sp8 == 1) {
GXBegin(GX_QUADS, GX_VTXFMT0, 4);
GXPosition3s16(x_orig, y_orig_pos, -5);
+55 -48
View File
@@ -66,13 +66,15 @@
#include "SDL3/SDL_filesystem.h"
#include "cxxopts.hpp"
#include "d/actor/d_a_movie_player.h"
#include "dusk/audio/DuskAudioSystem.h"
#include "dusk/config.hpp"
#include "dusk/settings.h"
#include "dusk/imgui/ImGuiConsole.hpp"
#include "dusk/settings.h"
#include "dusk/discord_presence.hpp"
#include "tracy/Tracy.hpp"
#include "f_pc/f_pc_draw.h"
#include "tracy/Tracy.hpp"
// --- GLOBALS ---
s8 mDoMain::developmentMode = -1;
@@ -96,6 +98,7 @@ bool dusk::IsRunning = true;
bool dusk::IsShuttingDown = false;
bool dusk::IsGameLaunched = false;
bool dusk::IsFocusPaused = false;
std::filesystem::path dusk::ConfigPath;
#endif
s32 LOAD_COPYDATE(void*) {
@@ -128,7 +131,6 @@ s32 LOAD_COPYDATE(void*) {
AuroraInfo auroraInfo;
AuroraStats dusk::lastFrameAuroraStats;
float dusk::frameUsagePct = 0.0f;
const char* configPath;
bool launchUILoop() {
while (dusk::IsRunning && !dusk::IsGameLaunched) {
@@ -240,8 +242,6 @@ void main01(void) {
continue;
}
const dusk::game_clock::MainLoopPacer pacing = dusk::game_clock::advance_main_loop();
VIWaitForRetrace();
dusk::lastFrameAuroraStats = *aurora_get_stats();
@@ -252,28 +252,33 @@ void main01(void) {
mDoGph_gInf_c::updateRenderSize();
dusk::frame_interp::begin_frame(pacing.is_interpolating, pacing.do_sim_tick, pacing.interpolation_step);
const auto pacing = dusk::game_clock::advance_main_loop();
if (pacing.is_interpolating) {
if (pacing.do_sim_tick) {
if (pacing.sim_ticks_to_run > 0) {
dusk::frame_interp::begin_frame(true, true, 0.0f);
dusk::frame_interp::set_ui_tick_pending(true);
mDoCPd_c::read();
DuskDebugPad();
dusk::gyro::read(pacing.sim_pace);
fapGm_Execute();
mDoAud_Execute();
dusk::game_clock::reset_accumulator();
for (int sim_tick = 0; sim_tick < pacing.sim_ticks_to_run; ++sim_tick) {
dusk::frame_interp::begin_sim_tick();
mDoCPd_c::read();
DuskDebugPad();
dusk::gyro::read(pacing.sim_pace);
fapGm_Execute();
mDoAud_Execute();
dusk::game_clock::commit_sim_tick();
}
}
dusk::frame_interp::begin_frame(true, false,
dusk::game_clock::sample_interpolation_step());
dusk::frame_interp::interpolate();
dusk::frame_interp::begin_presentation_camera();
if (!pacing.do_sim_tick) {
// run draw functions for anything specially marked to handle interp on non-sim
// ticks
fpcM_DrawIterater((fpcM_DrawIteraterFunc)fpcM_Draw);
}
// run draw functions for anything specially marked to handle interp
fpcM_DrawIterater((fpcM_DrawIteraterFunc)fpcM_Draw);
cAPIGph_Painter();
dusk::frame_interp::end_presentation_camera();
dusk::frame_interp::set_ui_tick_pending(false);
} else {
dusk::frame_interp::begin_frame(false, true, 0.0f);
dusk::frame_interp::set_ui_tick_pending(true);
// Game Inputs
@@ -377,7 +382,7 @@ static void ApplyCVarOverrides(const cxxopts::OptionValue& option) {
}
}
static const char* CalculateConfigPath() {
static std::filesystem::path CalculateConfigPath() {
const auto result = SDL_GetPrefPath(dusk::OrgName, dusk::AppName);
if (!result) {
DuskLog.fatal("Unable to get PrefPath: {}", SDL_GetError());
@@ -386,13 +391,12 @@ static const char* CalculateConfigPath() {
return result;
}
static void EnsureInitialPipelineCache(const char* configDir) {
if (configDir == nullptr) {
static void EnsureInitialPipelineCache(const std::filesystem::path& configDir) {
if (configDir.empty()) {
return;
}
const std::filesystem::path configPathFs(configDir);
const std::filesystem::path pipelineCachePath = configPathFs / "pipeline_cache.db";
const std::filesystem::path pipelineCachePath = configDir / "pipeline_cache.db";
if (std::filesystem::exists(pipelineCachePath)) {
return;
}
@@ -411,10 +415,10 @@ static void EnsureInitialPipelineCache(const char* configDir) {
}
std::error_code ec;
std::filesystem::create_directories(configPathFs, ec);
std::filesystem::create_directories(configDir, ec);
if (ec) {
DuskLog.warn("Failed to create config directory '{}' for pipeline cache: {}",
configPathFs.string(), ec.message());
configDir.string(), ec.message());
return;
}
@@ -505,37 +509,38 @@ int game_main(int argc, char* argv[]) {
exit(1);
}
configPath = CalculateConfigPath();
dusk::ConfigPath = CalculateConfigPath();
const auto startupLogLevel = static_cast<AuroraLogLevel>(parsed_arg_options["log-level"].as<uint8_t>());
dusk::InitializeFileLogging(configPath, startupLogLevel);
dusk::InitializeFileLogging(dusk::ConfigPath, startupLogLevel);
dusk::config::LoadFromUserPreferences();
ApplyCVarOverrides(parsed_arg_options["cvar"]);
dusk::InitializeCrashReporting();
EnsureInitialPipelineCache(configPath);
AuroraConfig config{};
config.appName = dusk::AppName;
config.configPath = configPath;
config.vsync = dusk::getSettings().video.enableVsync;
config.startFullscreen = dusk::getSettings().video.enableFullscreen;
config.windowPosX = -1;
config.windowPosY = -1;
config.windowWidth = defaultWindowWidth * 2;
config.windowHeight = defaultWindowHeight * 2;
config.desiredBackend = ResolveDesiredBackend(parsed_arg_options);
config.logCallback = &aurora_log_callback;
config.logLevel = startupLogLevel;
config.mem1Size = 256 * 1024 * 1024;
config.mem2Size = 24 * 1024 * 1024;
config.allowJoystickBackgroundEvents = true;
config.imGuiInitCallback = &aurora_imgui_init_callback;
config.allowTextureReplacements = true;
config.allowTextureDumps = false;
EnsureInitialPipelineCache(dusk::ConfigPath);
PADSetDefaultMapping(&defaultPadMapping);
auroraInfo = aurora_initialize(argc, argv, &config);
{
const auto configPathString = dusk::ConfigPath.string();
AuroraConfig config{};
config.appName = dusk::AppName;
config.configPath = configPathString.c_str();
config.vsync = dusk::getSettings().video.enableVsync;
config.startFullscreen = dusk::getSettings().video.enableFullscreen;
config.windowPosX = -1;
config.windowPosY = -1;
config.windowWidth = defaultWindowWidth * 2;
config.windowHeight = defaultWindowHeight * 2;
config.desiredBackend = ResolveDesiredBackend(parsed_arg_options);
config.logCallback = &aurora_log_callback;
config.logLevel = startupLogLevel;
config.mem1Size = 256 * 1024 * 1024;
config.mem2Size = 24 * 1024 * 1024;
config.allowJoystickBackgroundEvents = true;
config.imGuiInitCallback = &aurora_imgui_init_callback;
config.allowTextureReplacements = true;
config.allowTextureDumps = false;
auroraInfo = aurora_initialize(argc, argv, &config);
}
#ifdef DUSK_DISCORD_RPC
dusk::discord::Initialize();
@@ -614,6 +619,8 @@ int game_main(int argc, char* argv[]) {
main01();
dusk::MoviePlayerShutdown();
dusk::ShutdownCrashReporting();
dusk::ShutdownFileLogging();
fflush(stdout);