mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-07-05 19:43:41 -04:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ae54f024cd | |||
| 9aa391c5bf | |||
| ec48249934 | |||
| 6a8f3516f9 | |||
| 79d4835784 | |||
| b1a4783e38 | |||
| 0038afa392 | |||
| 5fcffa0b4f | |||
| 9e9d11ae89 | |||
| 97bd84725c | |||
| 19c86b1b73 | |||
| ca247095da | |||
| ac3d3314c4 | |||
| c4d01b82a6 | |||
| 42e8d9ab9d | |||
| 9c562ff740 | |||
| 1787de517c | |||
| 832e567620 |
Vendored
+1
-1
Submodule extern/aurora updated: 6d69b7822e...63550a8375
@@ -103,6 +103,10 @@ public:
|
|||||||
field_0xd98 = param_1;
|
field_0xd98 = param_1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if TARGET_PC
|
||||||
|
void resetScrollArrowMask() { field_0xdda = 0; }
|
||||||
|
#endif
|
||||||
|
|
||||||
/* 0xC98 */ JKRExpHeap* mpHeap;
|
/* 0xC98 */ JKRExpHeap* mpHeap;
|
||||||
/* 0xC9C */ JKRExpHeap* mpTalkHeap;
|
/* 0xC9C */ JKRExpHeap* mpTalkHeap;
|
||||||
/* 0xCA0 */ STControl* mpStick;
|
/* 0xCA0 */ STControl* mpStick;
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ struct UserSettings {
|
|||||||
ConfigVar<bool> infiniteOil;
|
ConfigVar<bool> infiniteOil;
|
||||||
ConfigVar<bool> infiniteOxygen;
|
ConfigVar<bool> infiniteOxygen;
|
||||||
ConfigVar<bool> infiniteRupees;
|
ConfigVar<bool> infiniteRupees;
|
||||||
|
ConfigVar<bool> enableIndefiniteItemDrops;
|
||||||
ConfigVar<bool> moonJump;
|
ConfigVar<bool> moonJump;
|
||||||
ConfigVar<bool> superClawshot;
|
ConfigVar<bool> superClawshot;
|
||||||
ConfigVar<bool> alwaysGreatspin;
|
ConfigVar<bool> alwaysGreatspin;
|
||||||
|
|||||||
+25
-25
@@ -1,9 +1,10 @@
|
|||||||
#ifndef DUSK_TIME_H
|
#ifndef DUSK_TIME_H
|
||||||
#define DUSK_TIME_H
|
#define DUSK_TIME_H
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <numeric>
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <numeric>
|
||||||
|
|
||||||
|
#include "SDL3/SDL_timer.h"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#ifndef WIN32_LEAN_AND_MEAN
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
@@ -15,28 +16,26 @@
|
|||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
#include <shellapi.h>
|
#include <shellapi.h>
|
||||||
#include <intrin.h>
|
#include <intrin.h>
|
||||||
#else
|
|
||||||
#include "SDL3/SDL_timer.h"
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class Limiter {
|
class Limiter {
|
||||||
using delta_clock = std::chrono::high_resolution_clock;
|
|
||||||
using duration_t = std::chrono::nanoseconds;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void Reset() { m_oldTime = delta_clock::now(); }
|
using duration_t = Uint64;
|
||||||
|
|
||||||
|
void Reset() { m_oldTime = SDL_GetTicksNS(); }
|
||||||
|
|
||||||
void Sleep(duration_t targetFrameTime) {
|
void Sleep(duration_t targetFrameTime) {
|
||||||
if (targetFrameTime.count() == 0) {
|
if (targetFrameTime == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto start = delta_clock::now();
|
const Uint64 start = SDL_GetTicksNS();
|
||||||
duration_t adjustedSleepTime = SleepTime(targetFrameTime);
|
duration_t adjustedSleepTime = SleepTime(targetFrameTime);
|
||||||
if (adjustedSleepTime.count() > 0) {
|
if (adjustedSleepTime > 0) {
|
||||||
NanoSleep(adjustedSleepTime);
|
NanoSleep(adjustedSleepTime);
|
||||||
duration_t overslept = TimeSince(start) - adjustedSleepTime;
|
const duration_t elapsed = TimeSince(start);
|
||||||
if (overslept < duration_t{targetFrameTime}) {
|
const duration_t overslept = elapsed > adjustedSleepTime ? elapsed - adjustedSleepTime : 0;
|
||||||
|
if (overslept < targetFrameTime) {
|
||||||
m_overheadTimes[m_overheadTimeIdx] = overslept;
|
m_overheadTimes[m_overheadTimeIdx] = overslept;
|
||||||
m_overheadTimeIdx = (m_overheadTimeIdx + 1) % m_overheadTimes.size();
|
m_overheadTimeIdx = (m_overheadTimeIdx + 1) % m_overheadTimes.size();
|
||||||
}
|
}
|
||||||
@@ -45,23 +44,23 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
duration_t SleepTime(duration_t targetFrameTime) {
|
duration_t SleepTime(duration_t targetFrameTime) {
|
||||||
const auto sleepTime = duration_t{targetFrameTime} - TimeSince(m_oldTime);
|
const duration_t elapsed = TimeSince(m_oldTime);
|
||||||
m_overhead = std::accumulate(m_overheadTimes.begin(), m_overheadTimes.end(), duration_t{}) / m_overheadTimes.size();
|
const duration_t sleepTime = elapsed < targetFrameTime ? targetFrameTime - elapsed : 0;
|
||||||
|
m_overhead = std::accumulate(m_overheadTimes.begin(), m_overheadTimes.end(), duration_t{0}) /
|
||||||
|
m_overheadTimes.size();
|
||||||
if (sleepTime > m_overhead) {
|
if (sleepTime > m_overhead) {
|
||||||
return sleepTime - m_overhead;
|
return sleepTime - m_overhead;
|
||||||
}
|
}
|
||||||
return duration_t{0};
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
delta_clock::time_point m_oldTime;
|
Uint64 m_oldTime = 0;
|
||||||
std::array<duration_t, 4> m_overheadTimes{};
|
std::array<duration_t, 4> m_overheadTimes{};
|
||||||
size_t m_overheadTimeIdx = 0;
|
size_t m_overheadTimeIdx = 0;
|
||||||
duration_t m_overhead = duration_t{0};
|
duration_t m_overhead = 0;
|
||||||
|
|
||||||
duration_t TimeSince(delta_clock::time_point start) {
|
duration_t TimeSince(Uint64 start) const { return SDL_GetTicksNS() - start; }
|
||||||
return std::chrono::duration_cast<duration_t>(delta_clock::now() - start);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if _WIN32
|
#if _WIN32
|
||||||
void NanoSleep(const duration_t duration) {
|
void NanoSleep(const duration_t duration) {
|
||||||
@@ -85,9 +84,10 @@ private:
|
|||||||
|
|
||||||
LARGE_INTEGER start, current;
|
LARGE_INTEGER start, current;
|
||||||
QueryPerformanceCounter(&start);
|
QueryPerformanceCounter(&start);
|
||||||
LONGLONG ticksToWait = static_cast<LONGLONG>(duration.count() * countPerNs);
|
const LONGLONG ticksToWait = static_cast<LONGLONG>(duration * countPerNs);
|
||||||
if (DWORD ms = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count(); ms > 1) {
|
const Uint64 ms = duration / 1'000'000ULL;
|
||||||
::Sleep(ms - 1);
|
if (ms > 1) {
|
||||||
|
::Sleep(static_cast<DWORD>(ms - 1));
|
||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
QueryPerformanceCounter(¤t);
|
QueryPerformanceCounter(¤t);
|
||||||
@@ -99,7 +99,7 @@ private:
|
|||||||
} while (current.QuadPart - start.QuadPart < ticksToWait);
|
} while (current.QuadPart - start.QuadPart < ticksToWait);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
void NanoSleep(const duration_t duration) { SDL_DelayPrecise(duration.count()); }
|
void NanoSleep(const duration_t duration) { SDL_DelayPrecise(duration); }
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -368,11 +368,11 @@ constexpr auto FRAME_PERIOD = std::chrono::duration_cast<std::chrono::nanosecond
|
|||||||
std::chrono::duration<double>(1001.0 / 30000.0));
|
std::chrono::duration<double>(1001.0 / 30000.0));
|
||||||
constexpr auto RETRACE_PERIOD = FRAME_PERIOD / 2;
|
constexpr auto RETRACE_PERIOD = FRAME_PERIOD / 2;
|
||||||
|
|
||||||
static void waitPrecise(Limiter& limiter, Uint64 targetNs) {
|
static void waitPrecise(Limiter& limiter, Limiter::duration_t targetNs) {
|
||||||
const auto sleepTime = limiter.SleepTime(std::chrono::nanoseconds(targetNs));
|
const auto sleepTime = limiter.SleepTime(targetNs);
|
||||||
dusk::frameUsagePct =
|
dusk::frameUsagePct =
|
||||||
100.0f * (1.0f - static_cast<float>(sleepTime.count()) / static_cast<float>(targetNs));
|
100.0f * (1.0f - static_cast<float>(sleepTime) / static_cast<float>(targetNs));
|
||||||
limiter.Sleep(std::chrono::nanoseconds(targetNs));
|
limiter.Sleep(targetNs);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -2059,7 +2059,15 @@ static void demo_camera(b_bq_class* i_this) {
|
|||||||
for (int i = 0; i < 5; i++) {
|
for (int i = 0; i < 5; i++) {
|
||||||
static u16 g_e_i[] = {0x83EB, 0x83EC, 0x83ED, 0x83EE, 0x83EF};
|
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);
|
i_this->mSound.startCreatureSound(Z2SE_EN_BOSS_CONVERGE, 0, 0);
|
||||||
|
|||||||
@@ -2725,7 +2725,16 @@ static void demo_camera(b_ob_class* i_this) {
|
|||||||
|
|
||||||
for (int i = 0; i < 5; i++) {
|
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)};
|
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);
|
i_this->mDemoCamEye.set(-4820.0f, -18600.0f, -510.0f);
|
||||||
|
|||||||
@@ -1677,7 +1677,16 @@ static void demo_camera(e_fm_class* i_this) {
|
|||||||
cXyz spBC(0.0f, 0.0f, 0.0f);
|
cXyz spBC(0.0f, 0.0f, 0.0f);
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
static u16 g_e_i[] = {0x847B, 0x847C, 0x847D, 0x847E};
|
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);
|
i_this->mDemoCamFovy = 55.0f + NREG_F(10);
|
||||||
|
|||||||
@@ -390,6 +390,9 @@ void daItem_c::procMainNormal() {
|
|||||||
cLib_chaseF(&scale.z, mItemScale.z, step_z);
|
cLib_chaseF(&scale.z, mItemScale.z, step_z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if TARGET_PC
|
||||||
|
if (!dusk::getSettings().game.enableIndefiniteItemDrops) {
|
||||||
|
#endif
|
||||||
if (mWaitTimer == 0) {
|
if (mWaitTimer == 0) {
|
||||||
if (mDisappearTimer == 0) {
|
if (mDisappearTimer == 0) {
|
||||||
deleteItem();
|
deleteItem();
|
||||||
@@ -399,6 +402,9 @@ void daItem_c::procMainNormal() {
|
|||||||
changeDraw();
|
changeDraw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#if TARGET_PC
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
mCcCyl.SetC(current.pos);
|
mCcCyl.SetC(current.pos);
|
||||||
dComIfG_Ccsp()->Set(&mCcCyl);
|
dComIfG_Ccsp()->Set(&mCcCyl);
|
||||||
@@ -1058,9 +1064,16 @@ int daItem_c::CountTimer() {
|
|||||||
if (checkCountTimer()) {
|
if (checkCountTimer()) {
|
||||||
if (mWaitTimer > 0) {
|
if (mWaitTimer > 0) {
|
||||||
mWaitTimer--;
|
mWaitTimer--;
|
||||||
} else if (mDisappearTimer > 0) {
|
}
|
||||||
|
#if TARGET_PC
|
||||||
|
else if (!dusk::getSettings().game.enableIndefiniteItemDrops && mDisappearTimer > 0) {
|
||||||
mDisappearTimer--;
|
mDisappearTimer--;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
else if (mDisappearTimer > 0) {
|
||||||
|
mDisappearTimer--;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
cLib_calcTimer<u8>(&mBoomWindTgTimer);
|
cLib_calcTimer<u8>(&mBoomWindTgTimer);
|
||||||
|
|||||||
+13
-1
@@ -21,6 +21,7 @@
|
|||||||
#include "d/d_msg_string.h"
|
#include "d/d_msg_string.h"
|
||||||
#include "d/d_meter_haihai.h"
|
#include "d/d_meter_haihai.h"
|
||||||
#include "d/d_menu_window.h"
|
#include "d/d_menu_window.h"
|
||||||
|
#include "dusk/settings.h"
|
||||||
#include "f_op/f_op_msg_mng.h"
|
#include "f_op/f_op_msg_mng.h"
|
||||||
#include "m_Do/m_Do_graphic.h"
|
#include "m_Do/m_Do_graphic.h"
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@@ -945,9 +946,15 @@ void dMenu_DmapBg_c::draw() {
|
|||||||
mpMeterHaihai->drawHaihai(field_0xdda,
|
mpMeterHaihai->drawHaihai(field_0xdda,
|
||||||
x1 + (local_224.x + local_218.x) / 2,
|
x1 + (local_224.x + local_218.x) / 2,
|
||||||
y1 + (local_224.y + local_218.y) / 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));
|
-35.0f + (local_224.y - local_218.y));
|
||||||
|
#if TARGET_PC
|
||||||
|
if (!dusk::getSettings().game.enableFrameInterpolation) {
|
||||||
|
field_0xdda = 0;
|
||||||
|
}
|
||||||
|
#else
|
||||||
field_0xdda = 0;
|
field_0xdda = 0;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
dMenu_Dmap_c::myclass->drawFloorScreenTop(mFloorScreen, field_0xd94, field_0xd98, grafContext);
|
dMenu_Dmap_c::myclass->drawFloorScreenTop(mFloorScreen, field_0xd94, field_0xd98, grafContext);
|
||||||
@@ -2574,6 +2581,11 @@ void dMenu_Dmap_c::zoomIn_proc() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void dMenu_Dmap_c::zoomOut_init_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);
|
Z2GetAudioMgr()->seStart(Z2SE_SY_MAP_ZOOMOUT, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0);
|
||||||
mMapCtrl->initZoomOut(10);
|
mMapCtrl->initZoomOut(10);
|
||||||
mpDrawBg->iconScaleAnmInit(1.0f, 0.0f, 10);
|
mpDrawBg->iconScaleAnmInit(1.0f, 0.0f, 10);
|
||||||
|
|||||||
+84
-4
@@ -1,16 +1,29 @@
|
|||||||
#include "dusk/gyro.h"
|
#include "dusk/gyro.h"
|
||||||
#include "d/actor/d_a_alink.h"
|
#include "d/actor/d_a_alink.h"
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
namespace dusk::gyro {
|
namespace dusk::gyro {
|
||||||
namespace {
|
namespace {
|
||||||
constexpr s32 kRollgoalTableMaxOffset = 6500;
|
constexpr s32 kRollgoalTableMaxOffset = 6500;
|
||||||
constexpr float kGyroEmaAlphaMin = 0.05f;
|
constexpr float kGyroEmaAlphaMin = 0.05f;
|
||||||
constexpr float kGyroEmaAlphaMax = 1.0f;
|
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_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_gx = 0.0f;
|
||||||
float s_smooth_gy = 0.0f;
|
float s_smooth_gy = 0.0f;
|
||||||
float s_smooth_gz = 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_yaw_rad = 0.0f;
|
||||||
float s_pitch_rad = 0.0f;
|
float s_pitch_rad = 0.0f;
|
||||||
float s_roll_rad = 0.0f;
|
float s_roll_rad = 0.0f;
|
||||||
@@ -19,6 +32,10 @@ s32 s_rollgoal_az = 0;
|
|||||||
|
|
||||||
void reset_filter_state() {
|
void reset_filter_state() {
|
||||||
s_smooth_gx = s_smooth_gy = s_smooth_gz = 0.0f;
|
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_yaw_rad = s_pitch_rad = s_roll_rad = 0.0f;
|
||||||
s_rollgoal_ax = s_rollgoal_az = 0;
|
s_rollgoal_ax = s_rollgoal_az = 0;
|
||||||
}
|
}
|
||||||
@@ -49,15 +66,30 @@ bool queryGyroAimContext() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void read(float dt) {
|
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) {
|
if (s_sensor_enabled) {
|
||||||
PADSetSensorEnabled(PAD_CHAN0, PAD_SENSOR_GYRO, FALSE);
|
PADSetSensorEnabled(PAD_CHAN0, PAD_SENSOR_GYRO, FALSE);
|
||||||
s_sensor_enabled = false;
|
s_sensor_enabled = false;
|
||||||
}
|
}
|
||||||
|
if (s_accel_enabled) {
|
||||||
|
PADSetSensorEnabled(PAD_CHAN0, PAD_SENSOR_ACCEL, FALSE);
|
||||||
|
s_accel_enabled = false;
|
||||||
|
}
|
||||||
reset_filter_state();
|
reset_filter_state();
|
||||||
return;
|
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 (!s_sensor_enabled) {
|
||||||
if (!PADHasSensor(PAD_CHAN0, PAD_SENSOR_GYRO)) {
|
if (!PADHasSensor(PAD_CHAN0, PAD_SENSOR_GYRO)) {
|
||||||
return;
|
return;
|
||||||
@@ -68,6 +100,13 @@ void read(float dt) {
|
|||||||
s_sensor_enabled = true;
|
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];
|
f32 gyro[3];
|
||||||
if (!PADGetSensorData(PAD_CHAN0, PAD_SENSOR_GYRO, gyro, 3)) {
|
if (!PADGetSensorData(PAD_CHAN0, PAD_SENSOR_GYRO, gyro, 3)) {
|
||||||
return;
|
return;
|
||||||
@@ -80,9 +119,50 @@ void read(float dt) {
|
|||||||
s_smooth_gy += smooth_alpha * (gyro[1] - s_smooth_gy);
|
s_smooth_gy += smooth_alpha * (gyro[1] - s_smooth_gy);
|
||||||
s_smooth_gz += smooth_alpha * (gyro[2] - s_smooth_gz);
|
s_smooth_gz += smooth_alpha * (gyro[2] - s_smooth_gz);
|
||||||
|
|
||||||
s_pitch_rad = -apply_deadband(s_smooth_gx, deadband) * dt * dusk::getSettings().game.gyroSensitivityX;
|
const float pitch_rate = apply_deadband(s_smooth_gx, deadband);
|
||||||
s_yaw_rad = apply_deadband(s_smooth_gy, deadband) * dt * dusk::getSettings().game.gyroSensitivityY;
|
const float yaw_rate = apply_deadband(s_smooth_gy, deadband);
|
||||||
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 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_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;
|
s_yaw_rad = dusk::getSettings().game.gyroInvertYaw ? -s_yaw_rad : s_yaw_rad;
|
||||||
|
|||||||
@@ -305,6 +305,10 @@ namespace dusk {
|
|||||||
ImGuiMenuGame::ToggleFullscreen();
|
ImGuiMenuGame::ToggleFullscreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ImGui::IsKeyPressed(ImGuiKey_Escape) && getSettings().video.enableFullscreen) {
|
||||||
|
ImGuiMenuGame::ToggleFullscreen();
|
||||||
|
}
|
||||||
|
|
||||||
if (!dusk::IsGameLaunched) {
|
if (!dusk::IsGameLaunched) {
|
||||||
m_preLaunchWindow.draw();
|
m_preLaunchWindow.draw();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -288,6 +288,8 @@ namespace dusk {
|
|||||||
config::ImGuiCheckbox("Infinite Oil", getSettings().game.infiniteOil);
|
config::ImGuiCheckbox("Infinite Oil", getSettings().game.infiniteOil);
|
||||||
config::ImGuiCheckbox("Infinite Oxygen", getSettings().game.infiniteOxygen);
|
config::ImGuiCheckbox("Infinite Oxygen", getSettings().game.infiniteOxygen);
|
||||||
config::ImGuiCheckbox("Infinite Rupees", getSettings().game.infiniteRupees);
|
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");
|
ImGui::SeparatorText("Abilities");
|
||||||
config::ImGuiCheckbox("Moon Jump (R+A)", getSettings().game.moonJump);
|
config::ImGuiCheckbox("Moon Jump (R+A)", getSettings().game.moonJump);
|
||||||
|
|||||||
@@ -5,14 +5,22 @@
|
|||||||
#include "imgui.h"
|
#include "imgui.h"
|
||||||
#include "fmt/format.h"
|
#include "fmt/format.h"
|
||||||
#include "absl/strings/escaping.h"
|
#include "absl/strings/escaping.h"
|
||||||
|
#include "nlohmann/json.hpp"
|
||||||
|
|
||||||
#include "d/d_com_inf_game.h"
|
#include "d/d_com_inf_game.h"
|
||||||
#include "dusk/main.h"
|
#include "dusk/main.h"
|
||||||
|
#include "dusk/io.hpp"
|
||||||
|
#include "dusk/logging.h"
|
||||||
|
#include "../file_select.hpp"
|
||||||
|
#include "aurora/lib/window.hpp"
|
||||||
|
|
||||||
|
#include <unordered_set>
|
||||||
#include <zstd.h>
|
#include <zstd.h>
|
||||||
|
|
||||||
namespace dusk {
|
namespace dusk {
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
#pragma pack(push, 1)
|
||||||
struct StateSharePacket {
|
struct StateSharePacket {
|
||||||
char stageName[8];
|
char stageName[8];
|
||||||
@@ -23,9 +31,65 @@ struct StateSharePacket {
|
|||||||
};
|
};
|
||||||
#pragma pack(pop)
|
#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 = {};
|
StateSharePacket pkt = {};
|
||||||
strncpy(pkt.stageName, dComIfGp_getStartStageName(), 7);
|
strncpy(pkt.stageName, dComIfGp_getStartStageName(), 7);
|
||||||
pkt.roomNo = dComIfGp_getStartStageRoomNo();
|
pkt.roomNo = dComIfGp_getStartStageRoomNo();
|
||||||
@@ -40,26 +104,25 @@ void ImGuiStateShare::copyState() {
|
|||||||
std::string compressed(bound, '\0');
|
std::string compressed(bound, '\0');
|
||||||
compressed.resize(ZSTD_compress(compressed.data(), bound, raw.data(), raw.size(), 1));
|
compressed.resize(ZSTD_compress(compressed.data(), bound, raw.data(), raw.size(), 1));
|
||||||
|
|
||||||
std::string encoded = absl::Base64Escape(compressed);
|
return absl::Base64Escape(compressed);
|
||||||
ImGui::SetClipboardText(encoded.c_str());
|
|
||||||
m_statusMsg = "Copied to clipboard.";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ImGuiStateShare::pasteState() {
|
bool ImGuiStateShare::applyEncodedState(const std::string& encoded, const std::string& name) {
|
||||||
const char* clip = ImGui::GetClipboardText();
|
|
||||||
if (!clip || clip[0] == '\0') {
|
|
||||||
m_statusMsg = "Clipboard is empty.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string decoded;
|
std::string decoded;
|
||||||
if (!absl::Base64Unescape(clip, &decoded)) {
|
if (!absl::Base64Unescape(encoded, &decoded)) {
|
||||||
m_statusMsg = "Invalid base64.";
|
m_statusMsg = "Invalid base64.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned long long dSize = ZSTD_getFrameContentSize(decoded.data(), decoded.size());
|
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.";
|
m_statusMsg = "Not a valid state string.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -75,45 +138,261 @@ bool ImGuiStateShare::pasteState() {
|
|||||||
memcpy(&pkt, raw.data(), sizeof(pkt));
|
memcpy(&pkt, raw.data(), sizeof(pkt));
|
||||||
pkt.stageName[7] = '\0';
|
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;
|
s16 spawnPoint = pkt.startPoint == -4 ? -1 : pkt.startPoint;
|
||||||
|
|
||||||
if (spawnPoint == -1) {
|
if (spawnPoint == -1) {
|
||||||
dComIfGs_setRestartRoomParam(pkt.roomNo & 0x3F);
|
dComIfGs_setRestartRoomParam(pkt.roomNo & 0x3F);
|
||||||
}
|
}
|
||||||
|
|
||||||
dComIfGp_setNextStage(pkt.stageName, spawnPoint, pkt.roomNo, pkt.layer);
|
dComIfGp_setNextStage(pkt.stageName, spawnPoint, pkt.roomNo, pkt.layer);
|
||||||
m_pendingInfo = g_dComIfG_gameInfo.info;
|
|
||||||
|
|
||||||
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImGuiStateShare::tickPendingApply() {
|
void ImGuiStateShare::tickPendingApply() {
|
||||||
if (!m_pendingInfo.has_value() || dComIfGp_isEnableNextStage())
|
if (!m_pendingInfo.has_value() && !m_pendingSavedata.has_value()) {
|
||||||
return;
|
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) {
|
void ImGuiStateShare::draw(bool& open) {
|
||||||
if (dusk::IsGameLaunched)
|
if (dusk::IsGameLaunched) {
|
||||||
tickPendingApply();
|
tickPendingApply();
|
||||||
|
}
|
||||||
|
|
||||||
if (!open)
|
if (!m_loaded) {
|
||||||
|
loadStatesFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_pendingMergePath.empty()) {
|
||||||
|
mergeFromFile(m_pendingMergePath);
|
||||||
|
m_pendingMergePath.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!open) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SetNextWindowSizeConstraints(ImVec2(400, 0), ImVec2(FLT_MAX, FLT_MAX));
|
||||||
if (!ImGui::Begin("State Share", &open, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)) {
|
if (!ImGui::Begin("State Share", &open, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)) {
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!dusk::IsGameLaunched) ImGui::BeginDisabled();
|
const bool gameRunning = dusk::IsGameLaunched;
|
||||||
if (ImGui::Button("Copy State")) copyState();
|
|
||||||
|
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) { ImGui::BeginDisabled(); }
|
||||||
|
if (ImGui::Button("Load")) {
|
||||||
|
applyEncodedState(m_states[i].encoded, m_states[i].name);
|
||||||
|
}
|
||||||
|
if (!gameRunning) { 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();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button("Import State")) pasteState();
|
if (ImGui::Button("Import Clipboard")) {
|
||||||
if (!dusk::IsGameLaunched) ImGui::EndDisabled();
|
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()) {
|
if (!m_statusMsg.empty()) {
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
@@ -125,8 +404,9 @@ void ImGuiStateShare::draw(bool& open) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ImGuiMenuTools::ShowStateShare() {
|
void ImGuiMenuTools::ShowStateShare() {
|
||||||
if (!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F8, m_showStateShare))
|
if (!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F8, m_showStateShare)) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
m_stateShare.draw(m_showStateShare);
|
m_stateShare.draw(m_showStateShare);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,21 +4,38 @@
|
|||||||
#include "d/d_save.h"
|
#include "d/d_save.h"
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace dusk {
|
namespace dusk {
|
||||||
class ImGuiStateShare {
|
|
||||||
public:
|
|
||||||
void draw(bool& open);
|
|
||||||
|
|
||||||
private:
|
struct SavedStateEntry {
|
||||||
void copyState();
|
std::string name;
|
||||||
bool pasteState();
|
std::string encoded;
|
||||||
void tickPendingApply();
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
std::string m_pendingMergePath;
|
||||||
|
};
|
||||||
|
|
||||||
std::string m_statusMsg;
|
|
||||||
std::optional<dSv_info_c> m_pendingInfo;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -75,6 +75,7 @@ UserSettings g_userSettings = {
|
|||||||
.infiniteOil{"game.infiniteOil", false},
|
.infiniteOil{"game.infiniteOil", false},
|
||||||
.infiniteOxygen{"game.infiniteOxygen", false},
|
.infiniteOxygen{"game.infiniteOxygen", false},
|
||||||
.infiniteRupees{"game.infiniteRupees", false},
|
.infiniteRupees{"game.infiniteRupees", false},
|
||||||
|
.enableIndefiniteItemDrops {"game.enableIndefiniteItemDrops", false},
|
||||||
.moonJump{"game.moonJump", false},
|
.moonJump{"game.moonJump", false},
|
||||||
.superClawshot{"game.superClawshot", false},
|
.superClawshot{"game.superClawshot", false},
|
||||||
.alwaysGreatspin{"game.alwaysGreatspin", false},
|
.alwaysGreatspin{"game.alwaysGreatspin", false},
|
||||||
@@ -160,6 +161,7 @@ void registerSettings() {
|
|||||||
Register(g_userSettings.game.infiniteOil);
|
Register(g_userSettings.game.infiniteOil);
|
||||||
Register(g_userSettings.game.infiniteOxygen);
|
Register(g_userSettings.game.infiniteOxygen);
|
||||||
Register(g_userSettings.game.infiniteRupees);
|
Register(g_userSettings.game.infiniteRupees);
|
||||||
|
Register(g_userSettings.game.enableIndefiniteItemDrops);
|
||||||
Register(g_userSettings.game.moonJump);
|
Register(g_userSettings.game.moonJump);
|
||||||
Register(g_userSettings.game.superClawshot);
|
Register(g_userSettings.game.superClawshot);
|
||||||
Register(g_userSettings.game.alwaysGreatspin);
|
Register(g_userSettings.game.alwaysGreatspin);
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
"""
|
||||||
|
Convert a folder of TPGZ saves to a states.json
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python saves_to_states_json.py path/to/saves [prefix]
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
pip install zstandard
|
||||||
|
"""
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
import zstandard
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
SAVE_C_SIZE = 0x958
|
||||||
|
|
||||||
|
PACKET_FORMAT = "<8sbbh"
|
||||||
|
|
||||||
|
RETURN_PLACE_OFF = 0x058
|
||||||
|
NAME_OFF = RETURN_PLACE_OFF + 0x00
|
||||||
|
ROOM_OFF = RETURN_PLACE_OFF + 0x09
|
||||||
|
SPAWN_POINT_OFF = RETURN_PLACE_OFF + 0x08
|
||||||
|
|
||||||
|
folder = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(__file__).parent
|
||||||
|
out_path = folder / "states.json"
|
||||||
|
|
||||||
|
if len(sys.argv) > 2:
|
||||||
|
prefix = sys.argv[2]
|
||||||
|
else:
|
||||||
|
prefix = None
|
||||||
|
|
||||||
|
cctx = zstandard.ZstdCompressor(level=1)
|
||||||
|
states = []
|
||||||
|
|
||||||
|
for bin_path in sorted(folder.glob("*.bin")):
|
||||||
|
raw = bin_path.read_bytes()
|
||||||
|
save_c = raw[:SAVE_C_SIZE]
|
||||||
|
if len(save_c) < SAVE_C_SIZE:
|
||||||
|
print(f" skip {bin_path.name}: too small ({len(save_c)} bytes)")
|
||||||
|
continue
|
||||||
|
|
||||||
|
stage_name = save_c[NAME_OFF:NAME_OFF + 8]
|
||||||
|
room_no = struct.unpack_from("b", save_c, ROOM_OFF)[0]
|
||||||
|
spawn_point = struct.unpack_from("B", save_c, SPAWN_POINT_OFF)[0]
|
||||||
|
|
||||||
|
pkt = struct.pack(PACKET_FORMAT, stage_name, room_no, -1, spawn_point)
|
||||||
|
payload = pkt + save_c
|
||||||
|
encoded = base64.b64encode(cctx.compress(payload)).decode("ascii")
|
||||||
|
|
||||||
|
stage_str = stage_name.rstrip(b"\x00").decode("ascii", errors="replace")
|
||||||
|
print(f" {bin_path.stem:30s} stage={stage_str!r} room={room_no} point={spawn_point}")
|
||||||
|
states.append({"name": f"({prefix}) {bin_path.stem}" if prefix else bin_path.stem, "data": encoded})
|
||||||
|
|
||||||
|
out_path.write_text(json.dumps(states, indent=2))
|
||||||
|
print(f"\nWrote {len(states)} states to {out_path}")
|
||||||
Reference in New Issue
Block a user