Merge remote-tracking branch 'origin/main' into tphd

This commit is contained in:
Lurs
2026-06-17 19:17:52 +02:00
101 changed files with 8779 additions and 688 deletions
+2 -2
View File
@@ -291,13 +291,13 @@ message(STATUS "dusklight: Fetching cxxopts")
FetchContent_Declare(cxxopts
URL https://github.com/jarro2783/cxxopts/archive/refs/tags/v3.3.1.tar.gz
URL_HASH SHA256=3bfc70542c521d4b55a46429d808178916a579b28d048bd8c727ee76c39e2072
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
DOWNLOAD_EXTRACT_TIMESTAMP FALSE
)
message(STATUS "dusklight: Fetching nlohmann/json")
FetchContent_Declare(json
URL https://github.com/nlohmann/json/releases/download/v3.12.0/json.tar.xz
URL_HASH SHA256=42f6e95cad6ec532fd372391373363b62a14af6d771056dbfc86160e6dfff7aa
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
DOWNLOAD_EXTRACT_TIMESTAMP FALSE
)
FetchContent_MakeAvailable(cxxopts json)
+5 -1
View File
@@ -158,7 +158,11 @@
"cacheVariables": {
"CMAKE_C_COMPILER": "cl",
"CMAKE_CXX_COMPILER": "cl",
"CMAKE_INSTALL_PREFIX": "${sourceDir}/build/install"
"CMAKE_INSTALL_PREFIX": "${sourceDir}/build/install",
"CMAKE_DISABLE_FIND_PACKAGE_PkgConfig": {
"type": "BOOL",
"value": true
}
},
"vendor": {
"microsoft.com/VisualStudioSettings/CMake/1.0": {
+1 -1
+16
View File
@@ -1418,7 +1418,11 @@ set(DUSK_FILES
include/dusk/scope_guard.hpp
src/dusk/dvd_asset.cpp
src/d/actor/d_a_alink_dusk.cpp
src/dusk/android_frame_rate.hpp
src/dusk/android_frame_rate.cpp
src/dusk/asserts.cpp
src/dusk/batch.cpp
src/dusk/batch.hpp
src/dusk/config.cpp
src/dusk/crash_handler.cpp
src/dusk/crash_reporting.cpp
@@ -1432,6 +1436,8 @@ set(DUSK_FILES
src/dusk/game_clock.cpp
src/dusk/globals.cpp
src/dusk/gyro.cpp
include/dusk/menu_pointer.h
src/dusk/menu_pointer.cpp
src/dusk/mouse.cpp
src/dusk/gamepad_color.cpp
src/dusk/autosave.cpp
@@ -1445,6 +1451,7 @@ set(DUSK_FILES
src/dusk/stubs.cpp
include/dusk/texture_replacements.hpp
src/dusk/texture_replacements.cpp
src/dusk/touch_camera.cpp
src/dusk/update_check.cpp
src/dusk/update_check.hpp
#src/dusk/m_Do_ext_dusk.cpp
@@ -1474,6 +1481,7 @@ set(DUSK_FILES
src/dusk/ui/button.hpp
src/dusk/ui/component.cpp
src/dusk/ui/component.hpp
src/dusk/ui/controls.hpp
src/dusk/ui/controller_config.cpp
src/dusk/ui/controller_config.hpp
src/dusk/ui/document.cpp
@@ -1486,6 +1494,8 @@ set(DUSK_FILES
src/dusk/ui/graphics_tuner.hpp
src/dusk/ui/input.cpp
src/dusk/ui/input.hpp
src/dusk/ui/icon_provider.cpp
src/dusk/ui/icon_provider.hpp
src/dusk/ui/modal.cpp
src/dusk/ui/modal.hpp
src/dusk/ui/nav_types.hpp
@@ -1511,6 +1521,12 @@ set(DUSK_FILES
src/dusk/ui/string_button.hpp
src/dusk/ui/tab_bar.cpp
src/dusk/ui/tab_bar.hpp
src/dusk/ui/touch_controls_common.cpp
src/dusk/ui/touch_controls_common.hpp
src/dusk/ui/touch_controls.cpp
src/dusk/ui/touch_controls.hpp
src/dusk/ui/touch_controls_editor.cpp
src/dusk/ui/touch_controls_editor.hpp
src/dusk/ui/ui.cpp
src/dusk/ui/ui.hpp
src/dusk/ui/warp.cpp
+2 -2
View File
@@ -138,7 +138,7 @@
NOD_PREBUILT = nod;
CXXOPTS = pkgs.cxxopts.src;
JSON = pkgs.nlohmann_json.src;
XXHASH = pkgs.xxHash.src;
XXHASH = pkgs.xxhash.src;
ZSTD = pkgs.zstd.src;
FMT = pkgs.fetchzip {
url = "https://github.com/fmtlib/fmt/archive/refs/tags/11.1.4.tar.gz";
@@ -194,7 +194,7 @@
pkgs.zstd
pkgs.cxxopts
pkgs.nlohmann_json
pkgs.xxHash
pkgs.xxhash
pkgs.abseil-cpp
pkgs.zlib
pkgs.libpng
+1
View File
@@ -4556,6 +4556,7 @@ public:
void handleWolfHowl();
void handleQuickTransform();
bool checkAimContext();
bool checkAimInputContext();
void onIronBallChainInterpCallback();
+10
View File
@@ -4,6 +4,10 @@
#include "JSystem/J3DGraphBase/J3DPacket.h"
#include "SSystem/SComponent/c_xyz.h"
#if TARGET_PC
#include "dusk/batch.hpp"
#endif
class cCcD_Obj;
class dCcMassS_HitInf;
class fopAc_ac_c;
@@ -107,6 +111,12 @@ public:
#if TARGET_PC
TGXTexObj mTexObj_l_J_Ohana00_64TEX;
TGXTexObj mTexObj_l_J_Ohana01_64128_0419TEX;
dusk::batch::LeafTemplate mTplHana00; // l_J_hana00DL
dusk::batch::LeafTemplate mTplHana00Cut; // l_J_hana00_cDL
dusk::batch::LeafTemplate mTplHana01; // l_J_hana01DL
dusk::batch::LeafTemplate mTplHana01Cut00; // l_J_hana01_c_00DL
dusk::batch::LeafTemplate mTplHana01Cut; // l_J_hana01_c_01DL
#endif
}; // Size: 0x12A54
+8
View File
@@ -4,6 +4,10 @@
#include "JSystem/J3DGraphBase/J3DPacket.h"
#include "SSystem/SComponent/c_xyz.h"
#if TARGET_PC
#include "../../../src/dusk/batch.hpp"
#endif
class cCcD_Obj;
class csXyz;
class dCcMassS_HitInf;
@@ -110,6 +114,10 @@ public:
#if TARGET_PC
TGXTexObj mTexObj_l_M_Hijiki00TEX;
TGXTexObj mTexObj_l_M_kusa05_RGBATEX;
dusk::batch::LeafTemplate mTplKusa9q; // l_M_Kusa_9qDL
dusk::batch::LeafTemplate mTplKusa9qCut; // l_M_Kusa_9q_cDL
dusk::batch::LeafTemplate mTplTengusa; // l_M_TenGusaDL
#endif
}; // Size: 0x1D718
+1 -1
View File
@@ -1037,7 +1037,7 @@ public:
bool test1Camera(s32);
bool test2Camera(s32);
#if TARGET_PC
static bool canUseFreeCam();
static bool isAimActive();
bool freeCamera();
bool executeDebugFlyCam();
void deactivateDebugFlyCam();
+13
View File
@@ -287,6 +287,11 @@ public:
MEMCARDCHECKPROC_ERR_YESNO_CURSOR_MOVE_ANM,
MEMCARDCHECKPROC_SAVEDATA_CLEAR,
#if TARGET_PC
MEMCARDCHECKPROC_AUTO_MAKE_GAMEFILE,
MEMCARDCHECKPROC_AUTO_MAKE_GAMEFILE_ERR_WAIT,
#endif
#if PLATFORM_WII || PLATFORM_SHIELD
MEMCARDCHECKPROC_NAND_STAT_CHECK,
MEMCARDCHECKPROC_GAMEFILE_INIT_SEL,
@@ -411,6 +416,10 @@ public:
bool yesnoWakuAlpahAnm(u8);
#if TARGET_PC
void fileSelectWide();
bool pointerDataSelect();
bool pointerMenuSelect();
bool pointerCopyDataToSelect();
bool pointerYesNoSelect(bool errorSelect);
#endif
void _draw();
void errorMoveAnmInitSet(int, int);
@@ -445,6 +454,10 @@ public:
void MemCardMakeGameFile();
void MemCardMakeGameFileWait();
void MemCardMakeGameFileCheck();
#if TARGET_PC
void MemCardAutoMakeGameFile();
void MemCardAutoMakeGameFileErrWait();
#endif
void MemCardMsgWindowInitOpen();
void MemCardMsgWindowOpen();
void MemCardMsgWindowClose();
+3
View File
@@ -157,6 +157,9 @@ public:
int getDispType() const;
void _move(f32, f32, int, f32);
void _draw();
#if TARGET_PC
bool refreshTextureSize();
#endif
virtual ~dMap_c() {
#if DEBUG
+2
View File
@@ -74,6 +74,8 @@ public:
#if TARGET_PC
void menuCollectWide();
bool pointerWait();
void pointerActivateCurrent();
#endif
void _create();
+4
View File
@@ -51,6 +51,10 @@ public:
void setBButtonString(u16);
void setHIO(bool);
#if TARGET_PC
bool pointerWait();
#endif
virtual void draw() { _draw(); }
virtual ~dMenu_Insect_c();
+4
View File
@@ -55,6 +55,10 @@ public:
u8 getLetterNum();
void setHIO(bool);
#if TARGET_PC
bool pointerWait();
#endif
virtual void draw() { _draw(); }
virtual ~dMenu_Letter_c();
+3
View File
@@ -80,6 +80,9 @@ public:
void setBButtonString(u16);
bool isRumbleSupported();
bool dpdMenuMove();
#if TARGET_PC
bool pointerConfirmSelect();
#endif
void paneResize(u64);
void initialize();
void yesnoMenuMoveAnmInitSet(int, int);
+3
View File
@@ -74,6 +74,9 @@ public:
void clacEllipsePlotAverage(int, f32, f32);
bool dpdMove();
u8 openExplain(u8);
#if TARGET_PC
bool pointerMove();
#endif
virtual void draw() { _draw(); }
virtual ~dMenu_Ring_c();
+2
View File
@@ -266,6 +266,8 @@ public:
#if TARGET_PC
void menuSaveWide();
bool pointerSaveSelect();
bool pointerYesNoSelect(bool errorSelect, u8 errParam = 0, u8 soundParam = 0);
#endif
void _draw2();
+4
View File
@@ -49,6 +49,10 @@ public:
u8 getSkillNum();
void setHIO(bool);
#if TARGET_PC
bool pointerWait();
#endif
virtual void draw() { _draw(); }
virtual ~dMenu_Skill_c();
+4
View File
@@ -49,6 +49,10 @@ public:
void selectScale();
void selectTrans();
void selectAnimeTransform(int);
#if TARGET_PC
bool pointerMove();
bool consumePointerClick();
#endif
void setOffsetX(f32 i_offsetX) { mOffsetX = i_offsetX; }
bool isAnimeUpdate(int param_0) { return (field_0x114 & (u8)(1 << param_0)) ? TRUE : FALSE; }
+8
View File
@@ -9,6 +9,8 @@ namespace dusk {
enum class ActionBinds {
FIRST_PERSON_CAMERA,
CALL_MIDNA,
OPEN_MAP_SCREEN,
TOGGLE_MINIMAP,
OPEN_DUSKLIGHT_MENU,
TURBO_SPEED_BUTTON,
COUNT,
@@ -32,6 +34,12 @@ bool isActionBound(ActionBinds action, u32 port);
void updateActionBindings();
void setVirtualActionBind(ActionBinds action, u32 port, bool pressed, bool available = true);
void clearVirtualActionBind(ActionBinds action, u32 port);
void clearAllVirtualActionBinds();
bool getActionBindTrig(ActionBinds action, u32 port);
bool getActionBindHold(ActionBinds action, u32 port);
+9 -2
View File
@@ -4,6 +4,7 @@
#include "dolphin/types.h"
#include <type_traits>
#include <cstdlib>
#include <limits>
#include <string>
/**
@@ -139,11 +140,16 @@ concept ConfigValueInteger =
|| std::is_same_v<T, s64>
|| std::is_same_v<T, u64>;
template <typename T>
struct ConfigValueTraits {
static constexpr bool enabled = false;
};
/**
* \brief Concept that defines the legal set of types that can be used for CVar values.
*
* Valid types cannot be cv-qualified and must be basic primitive types (int, float, bool),
* strings, or enums of the basic primitives.
* strings, enums of the basic primitives, or explicitly-enabled structured settings.
*/
template <typename T>
concept ConfigValue =
@@ -154,7 +160,8 @@ concept ConfigValue =
|| std::is_same_v<T, f32>
|| std::is_same_v<T, f64>
|| std::is_same_v<T, std::string>
|| (std::is_enum_v<T> && ConfigValueInteger<std::underlying_type_t<T>>));
|| (std::is_enum_v<T> && ConfigValueInteger<std::underlying_type_t<T>>)
|| ConfigValueTraits<T>::enabled);
template <ConfigValue T>
const ConfigImplBase* GetConfigImpl();
+60
View File
@@ -0,0 +1,60 @@
#pragma once
#include "dolphin/types.h"
class CPaneMgr;
namespace dusk::menu_pointer {
enum class Context {
None,
FileSelect,
Save,
ItemWheel,
Collection,
Options,
Dialog,
};
enum class Phase {
Move,
Press,
Release,
Cancel,
};
struct State {
f32 x = 0.0f;
f32 y = 0.0f;
bool valid = false;
bool down = false;
bool pressed = false;
bool released = false;
bool clicked = false;
bool touch = false;
};
void begin_game_frame() noexcept;
void end_game_frame() noexcept;
void begin_context(Context context) noexcept;
bool handle_fallthrough_pointer(f32 x, f32 y, Phase phase, bool touch, s32 mouseButton = -1) noexcept;
bool active() noexcept;
bool enabled() noexcept;
bool mouse_capture_active() noexcept;
const State& state() noexcept;
bool consume_click() noexcept;
void set_dialog_choice(u8 choice, bool clicked) noexcept;
bool get_dialog_choice(u8& choice) noexcept;
bool consume_dialog_click(u8& choice) noexcept;
void defer_activation(Context context, u8 target) noexcept;
bool consume_deferred_activation(Context context, u8 target) noexcept;
void clear_deferred_activation(Context context) noexcept;
u32 suppressed_pad_buttons(u32 port) noexcept;
void finish_pad_suppression_read(u32 port) noexcept;
bool hit_rect(f32 left, f32 top, f32 right, f32 bottom, f32 padding = 0.0f) noexcept;
bool hit_pane(CPaneMgr* pane, f32 padding = 0.0f) noexcept;
bool hit_pane(J2DPane* pane, f32 padding = 0.0f) noexcept;
} // namespace dusk::menu_pointer
+18 -16
View File
@@ -1,10 +1,10 @@
#ifndef DUSK_CONFIG_H
#define DUSK_CONFIG_H
#pragma once
#include <array>
#include <filesystem>
#include "dusk/config_var.hpp"
#include "dusk/ui/controls.hpp"
namespace dusk {
@@ -41,11 +41,6 @@ enum class DiscVerificationState : u8 {
HashMismatch,
};
enum class GyroMode : u8 {
Sensor = 0,
Mouse = 1,
};
enum class FrameInterpMode : u8 {
Off = 0,
Capped = 1,
@@ -97,12 +92,6 @@ struct ConfigEnumRange<DiscVerificationState> {
static constexpr auto max = DiscVerificationState::HashMismatch;
};
template <>
struct ConfigEnumRange<GyroMode> {
static constexpr auto min = GyroMode::Sensor;
static constexpr auto max = GyroMode::Mouse;
};
template <>
struct ConfigEnumRange<FrameInterpMode> {
static constexpr auto min = FrameInterpMode::Off;
@@ -120,6 +109,11 @@ struct ConfigEnumRange<MagicArmorMode> {
static constexpr auto min = MagicArmorMode::NORMAL;
static constexpr auto max = MagicArmorMode::COSMETIC;
};
template <>
struct ConfigValueTraits<ui::ControlLayout> {
static constexpr bool enabled = true;
};
} // namespace config
// Persistent user settings
@@ -135,6 +129,9 @@ struct UserSettings {
ConfigVar<bool> enableFpsOverlay;
ConfigVar<int> fpsOverlayCorner;
ConfigVar<int> maxFrameRate;
ConfigVar<bool> rememberWindowSize;
ConfigVar<int> lastWindowWidth;
ConfigVar<int> lastWindowHeight;
} video;
struct {
@@ -219,6 +216,9 @@ struct UserSettings {
ConfigVar<float> mouseCameraSensitivity;
ConfigVar<bool> invertMouseY;
ConfigVar<bool> freeCamera;
ConfigVar<bool> enableTouchControls;
ConfigVar<bool> enableMenuPointer;
ConfigVar<ui::ControlLayout> touchControlsLayout;
ConfigVar<bool> invertCameraXAxis;
ConfigVar<bool> invertCameraYAxis;
ConfigVar<bool> invertFirstPersonXAxis;
@@ -227,6 +227,8 @@ struct UserSettings {
ConfigVar<bool> invertAirSwimY;
ConfigVar<float> freeCameraXSensitivity;
ConfigVar<float> freeCameraYSensitivity;
ConfigVar<float> touchCameraXSensitivity;
ConfigVar<float> touchCameraYSensitivity;
ConfigVar<bool> debugFlyCam;
ConfigVar<bool> debugFlyCamLockEvents;
ConfigVar<bool> allowBackgroundInput;
@@ -288,6 +290,8 @@ struct UserSettings {
struct {
std::array<ActionBindConfigVar, 4> firstPersonCamera;
std::array<ActionBindConfigVar, 4> callMidna;
std::array<ActionBindConfigVar, 4> openMapScreen;
std::array<ActionBindConfigVar, 4> toggleMinimap;
std::array<ActionBindConfigVar, 4> openDusklightMenu;
std::array<ActionBindConfigVar, 4> turboSpeedButton;
} actionBindings;
@@ -322,6 +326,4 @@ struct TransientSettings {
TransientSettings& getTransientSettings();
}
#endif // DUSK_CONFIG_H
} // namespace dusk
+12
View File
@@ -0,0 +1,12 @@
#pragma once
namespace dusk::touch_camera {
constexpr float YAW_DEGREES_PER_DP = 0.34f;
constexpr float PITCH_DEGREES_PER_DP = 0.22f;
void add_delta(float yaw_dp, float pitch_dp) noexcept;
bool consume_delta(float& yaw_dp, float& pitch_dp) noexcept;
void clear() noexcept;
} // namespace dusk::touch_camera
@@ -212,6 +212,9 @@ public:
void setCornerColor(JUtility::TColor c0) {
setCornerColor(c0, c0, c0, c0);
}
#if TARGET_PC
JUtility::TColor corner(size_t index) const { return mCornerColor[index]; }
#endif
protected:
/* 0x100 */ JUTTexture* mTexture[2];
@@ -3,6 +3,20 @@
#include <gx.h>
#if TARGET_PC
#include <mtx.h>
struct ParticleDrawCtx {
bool batch; // off = immediate mode
bool useTexMtx; // UVs transformed by texMtx
bool useClr0; // prm color in GX_VA_CLR0
bool useClr1; // env color in GX_VA_CLR1
Mtx texMtx;
GXColor clr0;
GXColor clr1;
};
#endif
struct JPAEmitterWorkData;
class JPABaseParticle;
class JKRHeap;
@@ -75,6 +89,9 @@ public:
const GXTevColorArg* getTevColorArg() const { return st_ca[(pBsd->mFlags >> 0x0F) & 0x07]; }
const GXTevAlphaArg* getTevAlphaArg() const { return st_aa[(pBsd->mFlags >> 0x12) & 0x01]; }
#if TARGET_PC
u32 getTevColorArgSel() const { return (pBsd->mFlags >> 0x0F) & 0x07; }
#endif
u32 getType() const { return (pBsd->mFlags >> 0) & 0x0F; }
u32 getDirType() const { return (pBsd->mFlags >> 4) & 0x07; }
@@ -186,26 +203,34 @@ void JPARegistPrm(JPAEmitterWorkData*);
void JPARegistEnv(JPAEmitterWorkData*);
void JPARegistPrmEnv(JPAEmitterWorkData*);
void JPADrawPoint(JPAEmitterWorkData*, JPABaseParticle*);
void JPADrawLine(JPAEmitterWorkData*, JPABaseParticle*);
void JPADrawRotBillboard(JPAEmitterWorkData*, JPABaseParticle*);
void JPADrawBillboard(JPAEmitterWorkData*, JPABaseParticle*);
void JPADrawRotDirection(JPAEmitterWorkData*, JPABaseParticle*);
void JPADrawDirection(JPAEmitterWorkData*, JPABaseParticle*);
void JPADrawRotation(JPAEmitterWorkData*, JPABaseParticle*);
void JPADrawDBillboard(JPAEmitterWorkData*, JPABaseParticle*);
void JPADrawRotYBillboard(JPAEmitterWorkData*, JPABaseParticle*);
void JPADrawYBillboard(JPAEmitterWorkData*, JPABaseParticle*);
void JPADrawParticleCallBack(JPAEmitterWorkData*, JPABaseParticle*);
void JPALoadTexAnm(JPAEmitterWorkData*, JPABaseParticle*);
void JPASetPointSize(JPAEmitterWorkData*, JPABaseParticle*);
void JPASetLineWidth(JPAEmitterWorkData*, JPABaseParticle*);
void JPALoadCalcTexCrdMtxAnm(JPAEmitterWorkData*, JPABaseParticle*);
void JPARegistAlpha(JPAEmitterWorkData*, JPABaseParticle*);
void JPARegistEnv(JPAEmitterWorkData*, JPABaseParticle*);
void JPARegistAlphaEnv(JPAEmitterWorkData*, JPABaseParticle*);
void JPARegistPrmAlpha(JPAEmitterWorkData*, JPABaseParticle*);
void JPARegistPrmAlphaEnv(JPAEmitterWorkData*, JPABaseParticle*);
#if TARGET_PC
#define JPA_DRAW_PARTICLE_ARGS JPAEmitterWorkData*, JPABaseParticle*, ParticleDrawCtx*
#else
#define JPA_DRAW_PARTICLE_ARGS JPAEmitterWorkData*, JPABaseParticle*
#endif
void JPADrawPoint(JPA_DRAW_PARTICLE_ARGS);
void JPADrawLine(JPA_DRAW_PARTICLE_ARGS);
void JPADrawRotBillboard(JPA_DRAW_PARTICLE_ARGS);
void JPADrawBillboard(JPA_DRAW_PARTICLE_ARGS);
void JPADrawRotDirection(JPA_DRAW_PARTICLE_ARGS);
void JPADrawDirection(JPA_DRAW_PARTICLE_ARGS);
void JPADrawRotation(JPA_DRAW_PARTICLE_ARGS);
void JPADrawDBillboard(JPA_DRAW_PARTICLE_ARGS);
void JPADrawRotYBillboard(JPA_DRAW_PARTICLE_ARGS);
void JPADrawYBillboard(JPA_DRAW_PARTICLE_ARGS);
void JPADrawParticleCallBack(JPA_DRAW_PARTICLE_ARGS);
void JPALoadTexAnm(JPA_DRAW_PARTICLE_ARGS);
void JPASetPointSize(JPA_DRAW_PARTICLE_ARGS);
void JPASetLineWidth(JPA_DRAW_PARTICLE_ARGS);
void JPALoadCalcTexCrdMtxAnm(JPA_DRAW_PARTICLE_ARGS);
void JPARegistAlpha(JPA_DRAW_PARTICLE_ARGS);
void JPARegistEnv(JPA_DRAW_PARTICLE_ARGS);
void JPARegistAlphaEnv(JPA_DRAW_PARTICLE_ARGS);
void JPARegistPrmAlpha(JPA_DRAW_PARTICLE_ARGS);
void JPARegistPrmAlphaEnv(JPA_DRAW_PARTICLE_ARGS);
#undef JPA_DRAW_PARTICLE_ARGS
#if TARGET_PC
void JPAInterpBillboard(JPAEmitterWorkData*, JPABaseParticle*);
@@ -17,6 +17,10 @@ class JPADynamicsBlock;
class JPAFieldBlock;
class JPAKeyBlock;
#if TARGET_PC
struct ParticleDrawCtx;
#endif
/**
* @ingroup jsystem-jparticle
*
@@ -50,13 +54,19 @@ public:
public:
typedef void (*EmitterFunc)(JPAEmitterWorkData*);
typedef void (*ParticleFunc)(JPAEmitterWorkData*, JPABaseParticle*);
#if TARGET_PC
typedef void (*DrawParticleFunc)(JPAEmitterWorkData*, JPABaseParticle*,
ParticleDrawCtx*);
#else
typedef ParticleFunc DrawParticleFunc;
#endif
/* 0x00 */ EmitterFunc* mpCalcEmitterFuncList;
/* 0x04 */ EmitterFunc* mpDrawEmitterFuncList;
/* 0x08 */ EmitterFunc* mpDrawEmitterChildFuncList;
/* 0x0C */ ParticleFunc* mpCalcParticleFuncList;
/* 0x10 */ ParticleFunc* mpDrawParticleFuncList;
/* 0x10 */ DrawParticleFunc* mpDrawParticleFuncList;
/* 0x14 */ ParticleFunc* mpCalcParticleChildFuncList;
/* 0x18 */ ParticleFunc* mpDrawParticleChildFuncList;
/* 0x18 */ DrawParticleFunc* mpDrawParticleChildFuncList;
/* 0x1C */ JPABaseShape* pBsp;
/* 0x20 */ JPAExtraShape* pEsp;
@@ -77,6 +87,20 @@ public:
/* 0x45 */ u8 mpDrawParticleFuncListNum;
/* 0x46 */ u8 mpCalcParticleChildFuncListNum;
/* 0x47 */ u8 mpDrawParticleChildFuncListNum;
#if TARGET_PC
struct BatchInfo {
f32 vtxPos[8][3];
f32 vtxUv[8][2];
u8 vtxCount; // 4 (quad) or 8 (cross)
bool supported; // draw func list contains only batchable funcs
bool hasPtclColor; // per-particle JPARegist* func is present
bool hasPtclTexMtx; // JPALoadCalcTexCrdMtxAnm is present
};
BatchInfo mBatchInfo;
void initBatchInfo();
#endif
};
#endif /* JPARESOURCE_H */
+2 -2
View File
@@ -136,8 +136,8 @@ void J3DLoadCPCmd(u8 addr, u32 val) {
#if TARGET_PC
static void J3DLoadArrayBasePtr(GXAttr attr, void* data, u32 size, bool le) {
u32 idx = (attr == GX_VA_NBT) ? 1 : (attr - GX_VA_POS);
GXCmd1u8(GX_LOAD_AURORA);
GXCmd1u16(GX_LOAD_AURORA_ARRAYBASE | idx);
GXCmd1u8(GX_AURORA);
GXCmd1u16(GX_AURORA_LOAD_ARRAYBASE | idx);
GXCmd1u64((u64)data);
GXCmd1u32(size);
GXCmd1u8(le ? 1 : 0);
+55 -293
View File
@@ -7,265 +7,11 @@
#include "JSystem/JKernel/JKRHeap.h"
#if TARGET_PC
#include <algorithm>
#include <aurora/dl.hpp>
#include <tracy/Tracy.hpp>
#include <vector>
#include "dusk/logging.h"
namespace {
u16 read_be16(const u8* data) {
return (u16(data[0]) << 8) | data[1];
}
void append_be16(std::vector<u8>& out, u16 value) {
out.push_back(value >> 8);
out.push_back(value & 0xFF);
}
void append_bytes(std::vector<u8>& out, const u8* data, u32 size) {
out.insert(out.end(), data, data + size);
}
bool is_matrix_idx_attr(GXAttr attr) {
return attr >= GX_VA_PNMTXIDX && attr <= GX_VA_TEX7MTXIDX;
}
bool is_draw_opcode(u8 opcode) {
return opcode == GX_QUADS || opcode == GX_TRIANGLES || opcode == GX_TRIANGLESTRIP ||
opcode == GX_TRIANGLEFAN || opcode == GX_LINES || opcode == GX_LINESTRIP ||
opcode == GX_POINTS;
}
bool is_mergeable_draw_opcode(u8 opcode) {
return opcode == GX_QUADS || opcode == GX_TRIANGLES || opcode == GX_TRIANGLESTRIP ||
opcode == GX_TRIANGLEFAN;
}
bool calc_vtx_stride(const GXVtxDescList* vtxDesc, u32& stride) {
stride = 0;
for (; vtxDesc->attr != GX_VA_NULL; vtxDesc++) {
switch (vtxDesc->type) {
case GX_NONE:
break;
case GX_DIRECT:
if (!is_matrix_idx_attr(vtxDesc->attr)) {
return false;
}
stride += 1;
break;
case GX_INDEX8:
stride += 1;
break;
case GX_INDEX16:
stride += 2;
break;
default:
return false;
}
}
return stride != 0;
}
bool get_command_size(const u8* dlStart, u32 dlSize, u32 offset, u32 stride, u32& cmdSize) {
if (offset >= dlSize) {
return false;
}
const u8 cmd = dlStart[offset];
const u8 opcode = cmd & GX_OPCODE_MASK;
switch (opcode) {
case GX_NOP:
case GX_CMD_INVL_VC:
cmdSize = 1;
return true;
case (GX_LOAD_BP_REG & GX_OPCODE_MASK):
cmdSize = 5;
return offset + cmdSize <= dlSize;
case GX_LOAD_CP_REG:
cmdSize = 6;
return offset + cmdSize <= dlSize;
case GX_LOAD_XF_REG: {
if (offset + 5 > dlSize) {
return false;
}
const u16 count = read_be16(dlStart + offset + 1) + 1;
cmdSize = 5 + count * 4;
return offset + cmdSize <= dlSize;
}
case GX_LOAD_INDX_A:
case GX_LOAD_INDX_B:
case GX_LOAD_INDX_C:
case GX_LOAD_INDX_D:
cmdSize = 5;
return offset + cmdSize <= dlSize;
case GX_CMD_CALL_DL:
cmdSize = 9;
return offset + cmdSize <= dlSize;
default:
if (is_draw_opcode(opcode)) {
if (offset + 3 > dlSize) {
return false;
}
const u16 vtxCount = read_be16(dlStart + offset + 1);
cmdSize = 3 + vtxCount * stride;
return offset + cmdSize <= dlSize;
}
return false;
}
}
struct MergeRun {
u8 cmd = 0;
u16 vtxCount = 0;
std::vector<u8> vertices;
};
void flush_merge_run(std::vector<u8>& out, MergeRun& run) {
if (run.vtxCount == 0) {
return;
}
out.push_back(run.cmd);
append_be16(out, run.vtxCount);
append_bytes(out, run.vertices.data(), run.vertices.size());
run.vertices.clear();
run.vtxCount = 0;
}
void append_vertex(std::vector<u8>& out, const u8* vertices, u32 stride, u16 idx) {
append_bytes(out, vertices + idx * stride, stride);
}
bool triangulate_draw(
std::vector<u8>& out, u8 opcode, const u8* vertices, u32 stride, u16 vtxCount) {
switch (opcode) {
case GX_TRIANGLES:
append_bytes(out, vertices, vtxCount * stride);
return true;
case GX_TRIANGLEFAN:
if (vtxCount < 3) {
return false;
}
for (u16 v = 2; v < vtxCount; v++) {
append_vertex(out, vertices, stride, 0);
append_vertex(out, vertices, stride, v - 1);
append_vertex(out, vertices, stride, v);
}
return true;
case GX_TRIANGLESTRIP:
if (vtxCount < 3) {
return false;
}
for (u16 v = 2; v < vtxCount; v++) {
if ((v & 1) == 0) {
append_vertex(out, vertices, stride, v - 2);
append_vertex(out, vertices, stride, v - 1);
} else {
append_vertex(out, vertices, stride, v - 1);
append_vertex(out, vertices, stride, v - 2);
}
append_vertex(out, vertices, stride, v);
}
return true;
case GX_QUADS:
if ((vtxCount & 3) != 0) {
return false;
}
for (u16 v = 0; v < vtxCount; v += 4) {
append_vertex(out, vertices, stride, v);
append_vertex(out, vertices, stride, v + 1);
append_vertex(out, vertices, stride, v + 2);
append_vertex(out, vertices, stride, v + 2);
append_vertex(out, vertices, stride, v + 3);
append_vertex(out, vertices, stride, v);
}
return true;
default:
return false;
}
}
void append_triangles_to_run(
std::vector<u8>& out, MergeRun& run, u8 cmd, const std::vector<u8>& vertices, u32 stride) {
u32 offset = 0;
u32 remaining = vertices.size() / stride;
while (remaining != 0) {
if (run.vtxCount != 0 && run.cmd != cmd) {
flush_merge_run(out, run);
}
if (run.vtxCount == 0) {
run.cmd = cmd;
}
u32 available = 0xFFFF - run.vtxCount;
if (available == 0) {
flush_merge_run(out, run);
continue;
}
u32 toCopy = std::min(remaining, available);
append_bytes(run.vertices, vertices.data() + offset * stride, toCopy * stride);
run.vtxCount += toCopy;
offset += toCopy;
remaining -= toCopy;
if (run.vtxCount == 0xFFFF) {
flush_merge_run(out, run);
}
}
}
bool optimize_display_list(const u8* dlStart, u32 dlSize, u32 stride, std::vector<u8>& out) {
MergeRun run;
out.reserve(dlSize);
for (u32 offset = 0; offset < dlSize;) {
u32 cmdSize = 0;
if (!get_command_size(dlStart, dlSize, offset, stride, cmdSize)) {
return false;
}
const u8 cmd = dlStart[offset];
const u8 opcode = cmd & GX_OPCODE_MASK;
if (opcode == GX_NOP) {
offset += cmdSize;
continue;
}
if (!is_draw_opcode(opcode)) {
flush_merge_run(out, run);
append_bytes(out, dlStart + offset, cmdSize);
offset += cmdSize;
continue;
}
if (!is_mergeable_draw_opcode(opcode)) {
flush_merge_run(out, run);
append_bytes(out, dlStart + offset, cmdSize);
offset += cmdSize;
continue;
}
const u16 vtxCount = read_be16(dlStart + offset + 1);
const u8* vertices = dlStart + offset + 3;
std::vector<u8> triangles;
if (!triangulate_draw(triangles, opcode, vertices, stride, vtxCount)) {
flush_merge_run(out, run);
append_bytes(out, dlStart + offset, cmdSize);
offset += cmdSize;
continue;
}
append_triangles_to_run(out, run, (GX_TRIANGLES | (cmd & GX_VAT_MASK)), triangles, stride);
offset += cmdSize;
}
flush_merge_run(out, run);
return true;
}
void set_display_list_copy(void*& displayList, u32& displayListSize, const u8* data, u32 size) {
const u32 alignedSize = ALIGN_NEXT(size, 0x20);
u8* newDL = JKR_NEW_ARRAY_ARGS(u8, alignedSize, 0x20);
@@ -289,20 +35,11 @@ u32 J3DShapeDraw::countVertex(u32 stride) {
u8* dlStart = (u8*)getDisplayList();
#if TARGET_PC
for (u32 offset = 0; offset < getDisplayListSize();) {
u8 cmd = dlStart[offset];
u8 opcode = cmd & GX_OPCODE_MASK;
u32 cmdSize = 0;
if (!get_command_size(dlStart, getDisplayListSize(), offset, stride, cmdSize)) {
break;
aurora::gx::dl::Reader reader{dlStart, getDisplayListSize(), static_cast<u8>(stride)};
while (const auto cmd = reader.next()) {
if (cmd->kind != aurora::gx::dl::Command::Kind::Passthrough) {
count += cmd->draw.vtxCount;
}
if (!is_draw_opcode(opcode)) {
offset += cmdSize;
continue;
}
int vtxNum = be16(*reinterpret_cast<u16*>(dlStart + offset + 1));
count += vtxNum;
offset += 3 + stride * vtxNum;
}
#else
for (u8* dl = dlStart; (dl - dlStart) < getDisplayListSize();) {
@@ -320,6 +57,53 @@ u32 J3DShapeDraw::countVertex(u32 stride) {
return count;
}
#if TARGET_PC
void J3DShapeDraw::addTexMtxIndexInDL(u32 stride, u32 attrOffs, u32 valueBase) {
u32 byteNum = countVertex(stride);
u32 oldSize = mDisplayListSize;
u32 newSize = ALIGN_NEXT(oldSize + byteNum, 0x20);
u8* newDLStart = JKR_NEW_ARRAY_ARGS(u8, newSize, 0x20);
u8* oldDLStart = (u8*)mDisplayList;
u8* newDL = newDLStart;
aurora::gx::dl::Reader reader{oldDLStart, mDisplayListSize, static_cast<u8>(stride)};
while (const auto cmd = reader.next()) {
if (cmd->kind == aurora::gx::dl::Command::Kind::Passthrough) {
std::memcpy(newDL, cmd->data, cmd->size);
newDL += cmd->size;
continue;
}
const auto& draw = cmd->draw;
const u32 headerSize = draw.vertices - cmd->data;
std::memcpy(newDL, cmd->data, headerSize);
newDL += headerSize;
for (u32 i = 0; i < draw.vtxCount; i++) {
const u8* oldVtx = draw.vertices + stride * i;
u8 pnmtxidx = oldVtx[0];
std::memcpy(newDL, oldVtx, attrOffs);
newDL += attrOffs;
*newDL++ = valueBase + pnmtxidx;
std::memcpy(newDL, oldVtx + attrOffs, stride - attrOffs);
newDL += stride - attrOffs;
}
}
if (reader.failed()) {
// preserve the remainder untouched
std::memcpy(newDL, oldDLStart + reader.pos(), mDisplayListSize - reader.pos());
newDL += mDisplayListSize - reader.pos();
}
u32 realSize = ALIGN_NEXT((uintptr_t)newDL - (uintptr_t)newDLStart, 0x20);
for (; (newDL - newDLStart) < newSize; newDL++)
*newDL = 0;
mDisplayListSize = realSize;
mDisplayList = newDLStart;
DCStoreRange(newDLStart, mDisplayListSize);
}
#else
void J3DShapeDraw::addTexMtxIndexInDL(u32 stride, u32 attrOffs, u32 valueBase) {
u32 byteNum = countVertex(stride);
u32 oldSize = mDisplayListSize;
@@ -330,32 +114,13 @@ void J3DShapeDraw::addTexMtxIndexInDL(u32 stride, u32 attrOffs, u32 valueBase) {
u8* newDL = newDLStart;
for (; (oldDL - oldDLStart) < mDisplayListSize;) {
#if TARGET_PC
u32 oldOffset = oldDL - oldDLStart;
u32 cmdSize = 0;
if (!get_command_size(oldDLStart, mDisplayListSize, oldOffset, stride, cmdSize)) {
memcpy(newDL, oldDL, mDisplayListSize - oldOffset);
newDL += mDisplayListSize - oldOffset;
break;
}
#endif
// Copy command
u8 cmd = *(u8*)oldDL;
oldDL++;
*newDL++ = cmd;
#if TARGET_PC
u8 opcode = cmd & GX_OPCODE_MASK;
if (!is_draw_opcode(opcode)) {
memcpy(newDL, oldDL, cmdSize - 1);
oldDL += cmdSize - 1;
newDL += cmdSize - 1;
continue;
}
#else
if (cmd != GX_TRIANGLEFAN && cmd != GX_TRIANGLESTRIP)
break;
#endif
// Copy count
int vtxNum = *(u16*)oldDL;
@@ -384,6 +149,7 @@ void J3DShapeDraw::addTexMtxIndexInDL(u32 stride, u32 attrOffs, u32 valueBase) {
mDisplayList = newDLStart;
DCStoreRange(newDLStart, mDisplayListSize);
}
#endif
J3DShapeDraw::J3DShapeDraw(const u8* displayList, u32 displayListSize) {
#if TARGET_PC
@@ -397,12 +163,8 @@ J3DShapeDraw::J3DShapeDraw(const u8* displayList, u32 displayListSize) {
#if TARGET_PC
J3DShapeDraw::J3DShapeDraw(
const u8* displayList, u32 displayListSize, const GXVtxDescList* vtxDesc) {
u32 stride = 0;
std::vector<u8> optimized;
if (calc_vtx_stride(vtxDesc, stride) &&
optimize_display_list(displayList, displayListSize, stride, optimized))
{
set_display_list_copy(mDisplayList, mDisplayListSize, optimized.data(), optimized.size());
if (const auto optimized = aurora::gx::dl::optimize(displayList, displayListSize, vtxDesc)) {
set_display_list_copy(mDisplayList, mDisplayListSize, optimized->data(), optimized->size());
} else {
set_display_list_copy(mDisplayList, mDisplayListSize, displayList, displayListSize);
}
+269 -30
View File
@@ -14,6 +14,33 @@
#endif
#include "tracy/Tracy.hpp"
#if TARGET_PC
#define JPA_DRAW_CTX_PARAM , ParticleDrawCtx* ctx
namespace {
GXColor emitter_prm_color(JPAEmitterWorkData* work) {
JPABaseEmitter* emtr = work->mpEmtr;
GXColor prm = emtr->mPrmClr;
prm.r = COLOR_MULTI(prm.r, emtr->mGlobalPrmClr.r);
prm.g = COLOR_MULTI(prm.g, emtr->mGlobalPrmClr.g);
prm.b = COLOR_MULTI(prm.b, emtr->mGlobalPrmClr.b);
prm.a = COLOR_MULTI(prm.a, emtr->mGlobalPrmClr.a);
return prm;
}
GXColor emitter_env_color(JPAEmitterWorkData* work) {
JPABaseEmitter* emtr = work->mpEmtr;
GXColor env = emtr->mEnvClr;
env.r = COLOR_MULTI(env.r, emtr->mGlobalEnvClr.r);
env.g = COLOR_MULTI(env.g, emtr->mGlobalEnvClr.g);
env.b = COLOR_MULTI(env.b, emtr->mGlobalEnvClr.b);
return env;
}
} // namespace
#else
#define JPA_DRAW_CTX_PARAM
#endif
void JPASetPointSize(JPAEmitterWorkData* work) {
GXSetPointSize((u8)(25.0f * work->mGlobalPtclScl.x), GX_TO_ONE);
}
@@ -22,15 +49,16 @@ void JPASetLineWidth(JPAEmitterWorkData* work) {
GXSetLineWidth((u8)(25.0f * work->mGlobalPtclScl.x), GX_TO_ONE);
}
void JPASetPointSize(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
void JPASetPointSize(JPAEmitterWorkData* work, JPABaseParticle* ptcl JPA_DRAW_CTX_PARAM) {
GXSetPointSize((u8)(ptcl->mParticleScaleX * (25.0f * work->mGlobalPtclScl.x)), GX_TO_ONE);
}
void JPASetLineWidth(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
void JPASetLineWidth(JPAEmitterWorkData* work, JPABaseParticle* ptcl JPA_DRAW_CTX_PARAM) {
GXSetLineWidth((u8)(ptcl->mParticleScaleX * (25.0f * work->mGlobalPtclScl.x)), GX_TO_ONE);
}
void JPARegistPrm(JPAEmitterWorkData* work) {
ZoneScoped;
JPABaseEmitter* emtr = work->mpEmtr;
GXColor prm = emtr->mPrmClr;
prm.r = COLOR_MULTI(prm.r, emtr->mGlobalPrmClr.r);
@@ -41,6 +69,7 @@ void JPARegistPrm(JPAEmitterWorkData* work) {
}
void JPARegistEnv(JPAEmitterWorkData* work) {
ZoneScoped;
JPABaseEmitter* emtr = work->mpEmtr;
GXColor env = emtr->mEnvClr;
env.r = COLOR_MULTI(env.r, emtr->mGlobalEnvClr.r);
@@ -50,6 +79,7 @@ void JPARegistEnv(JPAEmitterWorkData* work) {
}
void JPARegistPrmEnv(JPAEmitterWorkData* work) {
ZoneScoped;
JPABaseEmitter* emtr = work->mpEmtr;
GXColor prm = emtr->mPrmClr;
GXColor env = emtr->mEnvClr;
@@ -64,7 +94,8 @@ void JPARegistPrmEnv(JPAEmitterWorkData* work) {
GXSetTevColor(GX_TEVREG1, env);
}
void JPARegistAlpha(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
void JPARegistAlpha(JPAEmitterWorkData* work, JPABaseParticle* ptcl JPA_DRAW_CTX_PARAM) {
ZoneScoped;
JPABaseEmitter* emtr = work->mpEmtr;
GXColor prm = emtr->mPrmClr;
prm.r = COLOR_MULTI(prm.r, emtr->mGlobalPrmClr.r);
@@ -72,10 +103,19 @@ void JPARegistAlpha(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
prm.b = COLOR_MULTI(prm.b, emtr->mGlobalPrmClr.b);
prm.a = COLOR_MULTI(prm.a, emtr->mGlobalPrmClr.a);
prm.a = COLOR_MULTI(prm.a, ptcl->mPrmColorAlphaAnm);
#if TARGET_PC
if (ctx->batch) {
ctx->clr0 = prm;
if (ctx->useClr1) {
ctx->clr1 = emitter_env_color(work);
}
return;
}
#endif
GXSetTevColor(GX_TEVREG0, prm);
}
void JPARegistPrmAlpha(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
void JPARegistPrmAlpha(JPAEmitterWorkData* work, JPABaseParticle* ptcl JPA_DRAW_CTX_PARAM) {
ZoneScoped;
JPABaseEmitter* emtr = work->mpEmtr;
GXColor prm = ptcl->mPrmClr;
@@ -84,10 +124,19 @@ void JPARegistPrmAlpha(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
prm.b = COLOR_MULTI(prm.b, emtr->mGlobalPrmClr.b);
prm.a = COLOR_MULTI(prm.a, emtr->mGlobalPrmClr.a);
prm.a = COLOR_MULTI(prm.a, ptcl->mPrmColorAlphaAnm);
#if TARGET_PC
if (ctx->batch) {
ctx->clr0 = prm;
if (ctx->useClr1) {
ctx->clr1 = emitter_env_color(work);
}
return;
}
#endif
GXSetTevColor(GX_TEVREG0, prm);
}
void JPARegistPrmAlphaEnv(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
void JPARegistPrmAlphaEnv(JPAEmitterWorkData* work, JPABaseParticle* ptcl JPA_DRAW_CTX_PARAM) {
ZoneScoped;
JPABaseEmitter* emtr = work->mpEmtr;
GXColor prm = ptcl->mPrmClr;
@@ -100,11 +149,19 @@ void JPARegistPrmAlphaEnv(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
env.r = COLOR_MULTI(env.r, emtr->mGlobalEnvClr.r);
env.g = COLOR_MULTI(env.g, emtr->mGlobalEnvClr.g);
env.b = COLOR_MULTI(env.b, emtr->mGlobalEnvClr.b);
#if TARGET_PC
if (ctx->batch) {
ctx->clr0 = prm;
ctx->clr1 = env;
return;
}
#endif
GXSetTevColor(GX_TEVREG0, prm);
GXSetTevColor(GX_TEVREG1, env);
}
void JPARegistAlphaEnv(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
void JPARegistAlphaEnv(JPAEmitterWorkData* work, JPABaseParticle* ptcl JPA_DRAW_CTX_PARAM) {
ZoneScoped;
JPABaseEmitter* emtr = work->mpEmtr;
GXColor prm = emtr->mPrmClr;
GXColor env = ptcl->mEnvClr;
@@ -116,16 +173,31 @@ void JPARegistAlphaEnv(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
env.r = COLOR_MULTI(env.r, emtr->mGlobalEnvClr.r);
env.g = COLOR_MULTI(env.g, emtr->mGlobalEnvClr.g);
env.b = COLOR_MULTI(env.b, emtr->mGlobalEnvClr.b);
#if TARGET_PC
if (ctx->batch) {
ctx->clr0 = prm;
ctx->clr1 = env;
return;
}
#endif
GXSetTevColor(GX_TEVREG0, prm);
GXSetTevColor(GX_TEVREG1, env);
}
void JPARegistEnv(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
void JPARegistEnv(JPAEmitterWorkData* work, JPABaseParticle* ptcl JPA_DRAW_CTX_PARAM) {
ZoneScoped;
JPABaseEmitter* emtr = work->mpEmtr;
GXColor env = ptcl->mEnvClr;
env.r = COLOR_MULTI(env.r, emtr->mGlobalEnvClr.r);
env.g = COLOR_MULTI(env.g, emtr->mGlobalEnvClr.g);
env.b = COLOR_MULTI(env.b, emtr->mGlobalEnvClr.b);
#if TARGET_PC
if (ctx->batch) {
ctx->clr0 = emitter_prm_color(work);
ctx->clr1 = env;
return;
}
#endif
GXSetTevColor(GX_TEVREG1, env);
}
@@ -258,7 +330,7 @@ void JPAGenCalcTexCrdMtxAnm(JPAEmitterWorkData* work) {
GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_TEXMTX0);
}
void JPALoadCalcTexCrdMtxAnm(JPAEmitterWorkData* work, JPABaseParticle* param_1) {
void JPALoadCalcTexCrdMtxAnm(JPAEmitterWorkData* work, JPABaseParticle* param_1 JPA_DRAW_CTX_PARAM) {
ZoneScoped;
JPABaseShape* shape = work->mpRes->getBsp();
f32 dVar16 = param_1->mAge;
@@ -286,6 +358,12 @@ void JPALoadCalcTexCrdMtxAnm(JPAEmitterWorkData* work, JPABaseParticle* param_1)
local_108[2][1] = 0.0f;
local_108[2][2] = 1.0f;
local_108[2][3] = 0.0f;
#if TARGET_PC
if (ctx->batch) {
MTXCopy(local_108, ctx->texMtx);
return;
}
#endif
GXLoadTexMtxImm(local_108, 0x1e, GX_MTX2x4);
}
@@ -299,7 +377,7 @@ void JPALoadTexAnm(JPAEmitterWorkData* work) {
work->mpResMgr->load(work->mpRes->getTexIdx(work->mpEmtr->mTexAnmIdx), GX_TEXMAP0);
}
void JPALoadTexAnm(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
void JPALoadTexAnm(JPAEmitterWorkData* work, JPABaseParticle* ptcl JPA_DRAW_CTX_PARAM) {
ZoneScoped;
work->mpResMgr->load(work->mpRes->getTexIdx(ptcl->mTexAnmIdx), GX_TEXMAP0);
}
@@ -429,6 +507,47 @@ static projectionFunc p_prj[3] = {
};
#if TARGET_PC
static void emit_batch_quad(JPAEmitterWorkData* work, const ParticleDrawCtx* ctx,
const Mtx posMtx) {
const JPAResource::BatchInfo& info = work->mpRes->mBatchInfo;
for (int i = 0; i < info.vtxCount; i++) {
Vec localPos = {info.vtxPos[i][0], info.vtxPos[i][1], info.vtxPos[i][2]};
Vec drawPos;
MTXMultVec(posMtx, &localPos, &drawPos);
f32 texS = info.vtxUv[i][0];
f32 texT = info.vtxUv[i][1];
if (ctx->useTexMtx) {
f32 srcS = texS;
f32 srcT = texT;
texS = ctx->texMtx[0][0] * srcS + ctx->texMtx[0][1] * srcT + ctx->texMtx[0][3];
texT = ctx->texMtx[1][0] * srcS + ctx->texMtx[1][1] * srcT + ctx->texMtx[1][3];
}
GXPosition3f32(drawPos.x, drawPos.y, drawPos.z);
if (ctx->useClr0) {
GXColor4u8(ctx->clr0.r, ctx->clr0.g, ctx->clr0.b, ctx->clr0.a);
}
if (ctx->useClr1) {
GXColor4u8(ctx->clr1.r, ctx->clr1.g, ctx->clr1.b, ctx->clr1.a);
}
GXTexCoord2f32(texS, texT);
}
}
static void submit_particle_quad(
JPAEmitterWorkData* work, ParticleDrawCtx* ctx, const Mtx posMtx, const u8* dl, u32 dlSize) {
if (ctx->batch) {
emit_batch_quad(work, ctx, posMtx);
return;
}
GXLoadPosMtxImm(posMtx, GX_PNMTX0);
p_prj[work->mPrjType](work, posMtx);
GXCallDisplayList(dl, dlSize);
}
void JPAInterpBillboard(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
Mtx ptclPosMtx;
MTXTrans(ptclPosMtx, ptcl->mPosition.x, ptcl->mPosition.y, ptcl->mPosition.z);
@@ -448,7 +567,7 @@ void JPAInterpRotBillboard(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
}
#endif
void JPADrawBillboard(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
void JPADrawBillboard(JPAEmitterWorkData* work, JPABaseParticle* ptcl JPA_DRAW_CTX_PARAM) {
if (ptcl->checkStatus(JPAPtclStts_Invisible)) {
return;
}
@@ -473,12 +592,16 @@ void JPADrawBillboard(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
posMtx[2][2] = 1.0f;
posMtx[2][3] = pos.z;
posMtx[0][1] = posMtx[0][2] = posMtx[1][0] = posMtx[1][2] = posMtx[2][0] = posMtx[2][1] = 0.0f;
#if TARGET_PC
submit_particle_quad(work, ctx, posMtx, jpa_dl, sizeof(jpa_dl));
#else
GXLoadPosMtxImm(posMtx, GX_PNMTX0);
p_prj[work->mPrjType](work, posMtx);
GXCallDisplayList(jpa_dl, sizeof(jpa_dl));
#endif
}
void JPADrawRotBillboard(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
void JPADrawRotBillboard(JPAEmitterWorkData* work, JPABaseParticle* ptcl JPA_DRAW_CTX_PARAM) {
if (ptcl->checkStatus(JPAPtclStts_Invisible)) {
return;
}
@@ -517,12 +640,16 @@ void JPADrawRotBillboard(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
posMtx[2][2] = 1.0f;
posMtx[2][3] = pos.z;
posMtx[0][2] = posMtx[1][2] = posMtx[2][0] = posMtx[2][1] = 0.0f;
#if TARGET_PC
submit_particle_quad(work, ctx, posMtx, jpa_dl, sizeof(jpa_dl));
#else
GXLoadPosMtxImm(posMtx, GX_PNMTX0);
p_prj[work->mPrjType](work, posMtx);
GXCallDisplayList(jpa_dl, sizeof(jpa_dl));
#endif
}
void JPADrawYBillboard(JPAEmitterWorkData* work, JPABaseParticle* param_1) {
void JPADrawYBillboard(JPAEmitterWorkData* work, JPABaseParticle* param_1 JPA_DRAW_CTX_PARAM) {
if (param_1->checkStatus(JPAPtclStts_Invisible)) {
return;
}
@@ -542,12 +669,16 @@ void JPADrawYBillboard(JPAEmitterWorkData* work, JPABaseParticle* param_1) {
local_38[2][2] = work->mYBBCamMtx[2][2];
local_38[2][3] = local_48.z;
local_38[0][1] = local_38[0][2] = local_38[1][0] = local_38[2][0] = 0.0f;
#if TARGET_PC
submit_particle_quad(work, ctx, local_38, jpa_dl, sizeof(jpa_dl));
#else
GXLoadPosMtxImm(local_38, GX_PNMTX0);
p_prj[work->mPrjType](work, local_38);
GXCallDisplayList(jpa_dl, sizeof(jpa_dl));
#endif
}
void JPADrawRotYBillboard(JPAEmitterWorkData* work, JPABaseParticle* param_1) {
void JPADrawRotYBillboard(JPAEmitterWorkData* work, JPABaseParticle* param_1 JPA_DRAW_CTX_PARAM) {
if (param_1->checkStatus(JPAPtclStts_Invisible)) {
return;
}
@@ -576,9 +707,13 @@ void JPADrawRotYBillboard(JPAEmitterWorkData* work, JPABaseParticle* param_1) {
local_38[2][1] = local_94 * fVar1;
local_38[2][2] = local_90;
local_38[2][3] = local_48.z;
#if TARGET_PC
submit_particle_quad(work, ctx, local_38, jpa_dl, sizeof(jpa_dl));
#else
GXLoadPosMtxImm(local_38, GX_PNMTX0);
p_prj[work->mPrjType](work, local_38);
GXCallDisplayList(jpa_dl, sizeof(jpa_dl));
#endif
}
void dirTypeVel(JPAEmitterWorkData const* work, JPABaseParticle const* param_1,
@@ -741,6 +876,88 @@ static u8* p_dl[2] = {
};
#if TARGET_PC
static bool make_direction_mtx(JPAEmitterWorkData* work, JPABaseParticle* ptcl, Mtx posMtx) {
JGeometry::TVec3<f32> axisY;
JGeometry::TVec3<f32> axisZ;
JGeometry::TVec3<f32> baseAxis(ptcl->mBaseAxis);
p_direction[work->mDirType](work, ptcl, &axisY);
if (axisY.isZero()) {
return false;
}
axisY.normalize();
axisZ.cross(baseAxis, axisY);
if (axisZ.isZero()) {
return false;
}
axisZ.normalize();
baseAxis.cross(axisY, axisZ);
baseAxis.normalize();
ptcl->mBaseAxis.set(baseAxis);
f32 scaleX = work->mGlobalPtclScl.x * ptcl->mParticleScaleX;
f32 scaleY = work->mGlobalPtclScl.y * ptcl->mParticleScaleY;
posMtx[0][0] = baseAxis.x;
posMtx[0][1] = axisY.x;
posMtx[0][2] = axisZ.x;
posMtx[0][3] = ptcl->mPosition.x;
posMtx[1][0] = baseAxis.y;
posMtx[1][1] = axisY.y;
posMtx[1][2] = axisZ.y;
posMtx[1][3] = ptcl->mPosition.y;
posMtx[2][0] = baseAxis.z;
posMtx[2][1] = axisY.z;
posMtx[2][2] = axisZ.z;
posMtx[2][3] = ptcl->mPosition.z;
p_plane[work->mPlaneType](posMtx, scaleX, scaleY);
return true;
}
static bool make_rot_direction_mtx(JPAEmitterWorkData* work, JPABaseParticle* ptcl, Mtx posMtx) {
f32 sinRot = JMASSin(ptcl->mRotateAngle);
f32 cosRot = JMASCos(ptcl->mRotateAngle);
JGeometry::TVec3<f32> axisY;
JGeometry::TVec3<f32> axisZ;
JGeometry::TVec3<f32> baseAxis(ptcl->mBaseAxis);
p_direction[work->mDirType](work, ptcl, &axisY);
if (axisY.isZero()) {
return false;
}
axisY.normalize();
axisZ.cross(baseAxis, axisY);
if (axisZ.isZero()) {
return false;
}
axisZ.normalize();
baseAxis.cross(axisY, axisZ);
baseAxis.normalize();
ptcl->mBaseAxis.set(baseAxis);
f32 scaleX = work->mGlobalPtclScl.x * ptcl->mParticleScaleX;
f32 scaleY = work->mGlobalPtclScl.y * ptcl->mParticleScaleY;
Mtx rotMtx;
Mtx dirMtx;
p_rot[work->mRotType](sinRot, cosRot, rotMtx);
p_plane[work->mPlaneType](rotMtx, scaleX, scaleY);
dirMtx[0][0] = baseAxis.x;
dirMtx[0][1] = axisY.x;
dirMtx[0][2] = axisZ.x;
dirMtx[0][3] = ptcl->mPosition.x;
dirMtx[1][0] = baseAxis.y;
dirMtx[1][1] = axisY.y;
dirMtx[1][2] = axisZ.y;
dirMtx[1][3] = ptcl->mPosition.y;
dirMtx[2][0] = baseAxis.z;
dirMtx[2][1] = axisY.z;
dirMtx[2][2] = axisZ.z;
dirMtx[2][3] = ptcl->mPosition.z;
MTXConcat(dirMtx, rotMtx, posMtx);
return true;
}
void JPAInterpDirection(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
JGeometry::TVec3<f32> axisY;
JGeometry::TVec3<f32> axisZ;
@@ -823,7 +1040,7 @@ void JPAInterpRotDirection(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
}
#endif
void JPADrawDirection(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
void JPADrawDirection(JPAEmitterWorkData* work, JPABaseParticle* ptcl JPA_DRAW_CTX_PARAM) {
if (ptcl->checkStatus(JPAPtclStts_Invisible)) {
return;
}
@@ -832,8 +1049,12 @@ void JPADrawDirection(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
Mtx posMtx;
#if TARGET_PC
if (!dusk::frame_interp::lookup_replacement(ptcl, posMtx))
#endif
if (!dusk::frame_interp::lookup_replacement(ptcl, posMtx) &&
!make_direction_mtx(work, ptcl, posMtx))
{
return;
}
#else
{
JGeometry::TVec3<f32> axisY;
JGeometry::TVec3<f32> axisZ;
@@ -869,14 +1090,19 @@ void JPADrawDirection(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
posMtx[2][3] = ptcl->mPosition.z;
p_plane[work->mPlaneType](posMtx, scaleX, scaleY);
}
#endif
MTXConcat(work->mPosCamMtx, posMtx, posMtx);
#if TARGET_PC
submit_particle_quad(work, ctx, posMtx, p_dl[work->mDLType], sizeof(jpa_dl));
#else
GXLoadPosMtxImm(posMtx, GX_PNMTX0);
p_prj[work->mPrjType](work, posMtx);
GXCallDisplayList(p_dl[work->mDLType], sizeof(jpa_dl));
#endif
}
void JPADrawRotDirection(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
void JPADrawRotDirection(JPAEmitterWorkData* work, JPABaseParticle* ptcl JPA_DRAW_CTX_PARAM) {
if (ptcl->checkStatus(JPAPtclStts_Invisible)) {
return;
}
@@ -886,8 +1112,12 @@ void JPADrawRotDirection(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
Mtx mtx1;
Mtx mtx2;
#if TARGET_PC
if (!dusk::frame_interp::lookup_replacement(ptcl, mtx1))
#endif
if (!dusk::frame_interp::lookup_replacement(ptcl, mtx1) &&
!make_rot_direction_mtx(work, ptcl, mtx1))
{
return;
}
#else
{
f32 sinRot = JMASSin(ptcl->mRotateAngle);
f32 cosRot = JMASCos(ptcl->mRotateAngle);
@@ -927,13 +1157,18 @@ void JPADrawRotDirection(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
mtx2[2][3] = ptcl->mPosition.z;
MTXConcat(mtx2, mtx1, mtx1);
}
#endif
MTXConcat(work->mPosCamMtx, mtx1, mtx2);
#if TARGET_PC
submit_particle_quad(work, ctx, mtx2, p_dl[work->mDLType], sizeof(jpa_dl));
#else
GXLoadPosMtxImm(mtx2, GX_PNMTX0);
p_prj[work->mPrjType](work, mtx2);
GXCallDisplayList(p_dl[work->mDLType], sizeof(jpa_dl));
#endif
}
void JPADrawDBillboard(JPAEmitterWorkData* param_0, JPABaseParticle* param_1) {
void JPADrawDBillboard(JPAEmitterWorkData* param_0, JPABaseParticle* param_1 JPA_DRAW_CTX_PARAM) {
if (param_1->checkStatus(JPAPtclStts_Invisible)) {
return;
}
@@ -970,7 +1205,7 @@ void JPADrawDBillboard(JPAEmitterWorkData* param_0, JPABaseParticle* param_1) {
GXCallDisplayList(jpa_dl, sizeof(jpa_dl));
}
void JPADrawRotation(JPAEmitterWorkData* param_0, JPABaseParticle* param_1) {
void JPADrawRotation(JPAEmitterWorkData* param_0, JPABaseParticle* param_1 JPA_DRAW_CTX_PARAM) {
if (param_1->checkStatus(JPAPtclStts_Invisible)) {
return;
}
@@ -988,12 +1223,16 @@ void JPADrawRotation(JPAEmitterWorkData* param_0, JPABaseParticle* param_1) {
auStack_88[1][3] = param_1->mPosition.y;
auStack_88[2][3] = param_1->mPosition.z;
MTXConcat(param_0->mPosCamMtx, auStack_88, auStack_88);
#if TARGET_PC
submit_particle_quad(param_0, ctx, auStack_88, p_dl[param_0->mDLType], sizeof(jpa_dl));
#else
GXLoadPosMtxImm(auStack_88, 0);
p_prj[param_0->mPrjType](param_0, auStack_88);
GXCallDisplayList(p_dl[param_0->mDLType], sizeof(jpa_dl));
#endif
}
void JPADrawPoint(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
void JPADrawPoint(JPAEmitterWorkData* work, JPABaseParticle* ptcl JPA_DRAW_CTX_PARAM) {
if (ptcl->checkStatus(JPAPtclStts_Invisible)) {
return;
}
@@ -1010,7 +1249,7 @@ void JPADrawPoint(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
GXSetVtxDesc(GX_VA_TEX0, GX_INDEX8);
}
void JPADrawLine(JPAEmitterWorkData* param_0, JPABaseParticle* param_1) {
void JPADrawLine(JPAEmitterWorkData* param_0, JPABaseParticle* param_1 JPA_DRAW_CTX_PARAM) {
if (param_1->checkStatus(JPAPtclStts_Invisible)) {
return;
}
@@ -1086,7 +1325,7 @@ void JPADrawStripe(JPAEmitterWorkData* param_0) {
GXSetVtxDesc(GX_VA_POS, GX_DIRECT);
GXSetVtxDesc(GX_VA_TEX0, GX_DIRECT);
GXBegin(GX_TRIANGLESTRIP, GX_VTXFMT1, ptcl_num << 1);
for (JPANode<JPABaseParticle>* node = startNode; node != param_0->mpAlivePtcl->getEnd();
for (JPANode<JPABaseParticle>* node = startNode; node != param_0->mpAlivePtcl->getEnd();
node = node_func(node), coord += step) {
param_0->mpCurNode = node;
JPABaseParticle* particle = node->getObject();
@@ -1111,7 +1350,7 @@ void JPADrawStripe(JPAEmitterWorkData* param_0) {
}
particle->mBaseAxis.cross(local_f8, local_104);
particle->mBaseAxis.normalize();
local_c8[0][0] = local_104.x;
local_c8[0][1] = local_f8.x;
local_c8[0][2] = particle->mBaseAxis.x;
@@ -1177,7 +1416,7 @@ void JPADrawStripeX(JPAEmitterWorkData* param_0) {
GXSetVtxDesc(GX_VA_POS, GX_DIRECT);
GXSetVtxDesc(GX_VA_TEX0, GX_DIRECT);
GXBegin(GX_TRIANGLESTRIP, GX_VTXFMT1, ptcl_num << 1);
for (JPANode<JPABaseParticle>* node = startNode; node != param_0->mpAlivePtcl->getEnd();
for (JPANode<JPABaseParticle>* node = startNode; node != param_0->mpAlivePtcl->getEnd();
node = node_func(node), coord += step) {
param_0->mpCurNode = node;
JPABaseParticle* particle = node->getObject();
@@ -1202,7 +1441,7 @@ void JPADrawStripeX(JPAEmitterWorkData* param_0) {
}
particle->mBaseAxis.cross(local_c0, local_cc);
particle->mBaseAxis.normalize();
local_90[0][0] = local_cc.x;
local_90[0][1] = local_c0.x;
local_90[0][2] = particle->mBaseAxis.x;
@@ -1227,7 +1466,7 @@ void JPADrawStripeX(JPAEmitterWorkData* param_0) {
coord = start_coord;
GXBegin(GX_TRIANGLESTRIP, GX_VTXFMT1, ptcl_num << 1);
for (JPANode<JPABaseParticle>* node = startNode; node != param_0->mpAlivePtcl->getEnd();
for (JPANode<JPABaseParticle>* node = startNode; node != param_0->mpAlivePtcl->getEnd();
node = node_func(node), coord += step) {
param_0->mpCurNode = node;
JPABaseParticle* particle = node->getObject();
@@ -1252,7 +1491,7 @@ void JPADrawStripeX(JPAEmitterWorkData* param_0) {
}
particle->mBaseAxis.cross(local_c0, local_cc);
particle->mBaseAxis.normalize();
local_90[0][0] = local_cc.x;
local_90[0][1] = local_c0.x;
local_90[0][2] = particle->mBaseAxis.x;
@@ -1289,7 +1528,7 @@ void JPADrawEmitterCallBackB(JPAEmitterWorkData* work) {
emtr->mpEmtrCallBack->draw(emtr);
}
void JPADrawParticleCallBack(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
void JPADrawParticleCallBack(JPAEmitterWorkData* work, JPABaseParticle* ptcl JPA_DRAW_CTX_PARAM) {
JPABaseEmitter* emtr = work->mpEmtr;
if (emtr->mpPtclCallBack == NULL) {
return;
+275 -6
View File
@@ -18,9 +18,21 @@
#include "global.h"
#include "tracy/Tracy.hpp"
#if TARGET_PC
#define JPA_DRAW_CTX_ARG , &ctx
#else
#define JPA_DRAW_CTX_ARG
#endif
JPAResource::JPAResource() {
mpCalcEmitterFuncList = mpDrawEmitterFuncList = mpDrawEmitterChildFuncList = NULL;
#if TARGET_PC
mpCalcParticleFuncList = mpCalcParticleChildFuncList = NULL;
mpDrawParticleFuncList = mpDrawParticleChildFuncList = NULL;
mBatchInfo = {};
#else
mpCalcParticleFuncList = mpDrawParticleFuncList = mpCalcParticleChildFuncList = mpDrawParticleChildFuncList = NULL;
#endif
pBsp = NULL;
pEsp = NULL;
pCsp = NULL;
@@ -61,6 +73,60 @@ static u8 jpa_crd[32] ATTRIBUTE_ALIGN(32) = {
0x00, 0x00, 0x01, 0x00, 0x01, 0x02, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x02, 0x02, 0x00, 0x02,
};
#if TARGET_PC
void JPAResource::initBatchInfo() {
mBatchInfo = {};
bool hasDrawFunc = false;
for (int i = 0; i < mpDrawParticleFuncListNum; i++) {
DrawParticleFunc func = mpDrawParticleFuncList[i];
if (func == JPADrawBillboard || func == JPADrawRotBillboard ||
func == JPADrawYBillboard || func == JPADrawRotYBillboard ||
func == JPADrawDirection || func == JPADrawRotDirection || func == JPADrawRotation)
{
hasDrawFunc = true;
} else if (func == JPADrawParticleCallBack) {
// Batchable only for emitters without a particle callback; checked per draw
} else if (func == JPALoadCalcTexCrdMtxAnm) {
mBatchInfo.hasPtclTexMtx = true;
} else if (func == JPARegistAlpha || func == JPARegistPrmAlpha ||
func == JPARegistPrmAlphaEnv || func == JPARegistAlphaEnv ||
func == static_cast<DrawParticleFunc>(JPARegistEnv)) // overloaded
{
mBatchInfo.hasPtclColor = true;
} else {
// JPADrawPoint, JPADrawLine, JPADrawDBillboard, JPALoadTexAnm,
// JPASetPointSize, JPASetLineWidth
return;
}
}
if (!hasDrawFunc) {
return;
}
// Template array offsets, same math as setPTev
int base_plane_type = (pBsp->getType() == 3 || pBsp->getType() == 7) ?
pBsp->getBasePlaneType() : 0;
int center_offset = pEsp != nullptr ? (pEsp->getScaleCenterX() + 3 * pEsp->getScaleCenterY()) * 0xC : 0x30;
const s8* pos = reinterpret_cast<const s8*>(jpa_pos) + center_offset + base_plane_type * 0x6C;
const s8* crd = reinterpret_cast<const s8*>(jpa_crd) + (pBsp->getTilingS() + 2 * pBsp->getTilingT()) * 8;
bool cross = pBsp->getType() == 4 || pBsp->getType() == 8;
mBatchInfo.vtxCount = cross ? 8 : 4;
for (int i = 0; i < mBatchInfo.vtxCount; i++) {
int posIdx = i < 4 ? i : 72 + (i - 4);
int crdIdx = i & 3;
mBatchInfo.vtxPos[i][0] = pos[posIdx * 3 + 0];
mBatchInfo.vtxPos[i][1] = pos[posIdx * 3 + 1];
mBatchInfo.vtxPos[i][2] = pos[posIdx * 3 + 2];
mBatchInfo.vtxUv[i][0] = crd[crdIdx * 2 + 0];
mBatchInfo.vtxUv[i][1] = crd[crdIdx * 2 + 1];
}
mBatchInfo.supported = true;
}
#endif
void JPAResource::init(JKRHeap* heap) {
BOOL is_glbl_clr_anm = pBsp->isGlblClrAnm();
BOOL is_glbl_tex_anm = pBsp->isGlblTexAnm();
@@ -525,7 +591,10 @@ void JPAResource::init(JKRHeap* heap) {
if (mpDrawParticleFuncListNum != 0) {
mpDrawParticleFuncList =
(ParticleFunc*)JKRAllocFromHeap(heap, mpDrawParticleFuncListNum * sizeof(ParticleFunc), alignof(ParticleFunc));
(DrawParticleFunc*)JKRAllocFromHeap(
heap,
mpDrawParticleFuncListNum * sizeof(DrawParticleFunc),
alignof(DrawParticleFunc));
}
func_no = 0;
@@ -635,7 +704,10 @@ void JPAResource::init(JKRHeap* heap) {
if (mpDrawParticleChildFuncListNum != 0) {
mpDrawParticleChildFuncList =
(ParticleFunc*)JKRAllocFromHeap(heap, mpDrawParticleChildFuncListNum * sizeof(ParticleFunc), sizeof(EmitterFunc));
(DrawParticleFunc*)JKRAllocFromHeap(
heap,
mpDrawParticleChildFuncListNum * sizeof(DrawParticleFunc),
alignof(DrawParticleFunc));
}
func_no = 0;
@@ -699,6 +771,10 @@ void JPAResource::init(JKRHeap* heap) {
mpDrawParticleChildFuncList[func_no] = &JPARegistPrmAlphaEnv;
func_no++;
}
#if TARGET_PC
initBatchInfo();
#endif
}
bool JPAResource::calc(JPAEmitterWorkData* work, JPABaseEmitter* emtr) {
@@ -808,6 +884,183 @@ void JPAResource::draw(JPAEmitterWorkData* work, JPABaseEmitter* emtr) {
}
}
#if TARGET_PC
static GXTevAlphaArg to_vtx_alpha_arg(GXTevAlphaArg arg) {
return arg == GX_CA_A0 ? GX_CA_RASA : arg;
}
static void batch_set_tev_op(GXTevStageID stage) {
GXSetTevColorOp(stage, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV);
GXSetTevAlphaOp(stage, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV);
}
static void batch_setup_tev(JPAEmitterWorkData* work, bool useClr1) {
JPABaseShape* shape = work->mpRes->getBsp();
JPAExTexShape* ets = work->mpRes->getEts();
bool useIndirect = ets != nullptr && ets->isUseIndirect();
// JPAEmitterManager::draw configures both channels to pass vertex color through
GXSetNumChans(useClr1 ? 2 : 1);
const GXTevAlphaArg* alphaArg = shape->getTevAlphaArg();
GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
GXSetTevAlphaIn(GX_TEVSTAGE0, to_vtx_alpha_arg(alphaArg[0]), to_vtx_alpha_arg(alphaArg[1]),
to_vtx_alpha_arg(alphaArg[2]), to_vtx_alpha_arg(alphaArg[3]));
batch_set_tev_op(GX_TEVSTAGE0);
if (!useIndirect) {
GXSetTevDirect(GX_TEVSTAGE0);
}
GXTevStageID nextStage = GX_TEVSTAGE1;
switch (shape->getTevColorArgSel()) {
case 0: // TEXC
GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_ONE, GX_CC_ZERO);
break;
case 1: // C0 * TEXC
GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_RASC, GX_CC_TEXC, GX_CC_ZERO);
break;
case 2: // lerp(C0, 1, TEXC)
GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_RASC, GX_CC_ONE, GX_CC_TEXC, GX_CC_ZERO);
break;
case 3: // lerp(C1, C0, TEXC) = C0 * TEXC (stage 0) + C1 * (1 - TEXC) (stage 1)
GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_RASC, GX_CC_TEXC, GX_CC_ZERO);
GXSetTevOrder(nextStage, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR1A1);
GXSetTevColorIn(nextStage, GX_CC_RASC, GX_CC_ZERO, GX_CC_TEXC, GX_CC_CPREV);
GXSetTevAlphaIn(nextStage, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_APREV);
batch_set_tev_op(nextStage);
GXSetTevDirect(nextStage);
nextStage = static_cast<GXTevStageID>(nextStage + 1);
break;
case 4: // TEXC * C0 + C1: C0 * TEXC (stage 0), + C1 (stage 1)
GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_RASC, GX_CC_TEXC, GX_CC_ZERO);
GXSetTevOrder(nextStage, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR1A1);
GXSetTevColorIn(nextStage, GX_CC_CPREV, GX_CC_ZERO, GX_CC_ZERO, GX_CC_RASC);
GXSetTevAlphaIn(nextStage, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_APREV);
batch_set_tev_op(nextStage);
GXSetTevDirect(nextStage);
nextStage = static_cast<GXTevStageID>(nextStage + 1);
break;
case 5: // C0
GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_RASC);
break;
}
if (ets != nullptr && ets->isUseSecTex()) {
// Mirrors setPTev's secondary texture stage, at the next free stage
GXTexCoordID texCoord = useIndirect ? GX_TEXCOORD2 : GX_TEXCOORD1;
GXSetTevOrder(nextStage, texCoord, GX_TEXMAP3, GX_COLOR_NULL);
GXSetTevColorIn(nextStage, GX_CC_ZERO, GX_CC_TEXC, GX_CC_CPREV, GX_CC_ZERO);
GXSetTevAlphaIn(nextStage, GX_CA_ZERO, GX_CA_TEXA, GX_CA_APREV, GX_CA_ZERO);
batch_set_tev_op(nextStage);
GXSetTevDirect(nextStage);
nextStage = static_cast<GXTevStageID>(nextStage + 1);
}
GXSetNumTevStages(nextStage);
}
static void batch_setup_vtx_desc(bool useClr0, bool useClr1) {
static Mtx identityMtx = {
{1.0f, 0.0f, 0.0f, 0.0f},
{0.0f, 1.0f, 0.0f, 0.0f},
{0.0f, 0.0f, 1.0f, 0.0f},
};
GXLoadPosMtxImm(identityMtx, GX_PNMTX0);
GXSetCurrentMtx(GX_PNMTX0);
GXClearVtxDesc();
GXSetVtxDesc(GX_VA_POS, GX_DIRECT);
if (useClr0) {
GXSetVtxDesc(GX_VA_CLR0, GX_DIRECT);
}
if (useClr1) {
GXSetVtxDesc(GX_VA_CLR1, GX_DIRECT);
}
GXSetVtxDesc(GX_VA_TEX0, GX_DIRECT);
GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
if (useClr0) {
GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
}
if (useClr1) {
GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_CLR1, GX_CLR_RGBA, GX_RGBA8, 0);
}
GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);
}
static void batch_restore_gx(JPAEmitterWorkData* work, bool changedTev, bool changedTexMtx) {
GXClearVtxDesc();
GXSetVtxDesc(GX_VA_POS, GX_INDEX8);
GXSetVtxDesc(GX_VA_TEX0, GX_INDEX8);
GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_S8, 0);
GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_S8, 0);
GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);
GXSetCurrentMtx(GX_PNMTX0);
if (changedTexMtx) {
GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_TEXMTX0);
}
if (changedTev) {
GXSetNumChans(0);
work->mpRes->getBsp()->setGX(work);
work->mpRes->setPTev();
}
}
static bool draw_particle_batch(JPAEmitterWorkData* work) {
ZoneScoped;
JPAResource* res = work->mpRes;
const JPAResource::BatchInfo& info = res->mBatchInfo;
if (!info.supported || work->mPrjType != 0 || work->mpEmtr->mpPtclCallBack != nullptr) {
return false;
}
bool useClr0 = false;
bool useClr1 = false;
if (info.hasPtclColor) {
u32 colorSel = res->getBsp()->getTevColorArgSel();
if (colorSel >= 6) {
return false;
}
useClr0 = true;
useClr1 = colorSel == 3 || colorSel == 4;
batch_setup_tev(work, useClr1);
}
if (info.hasPtclTexMtx) {
// UVs are CPU-transformed; drop the texgen
GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);
}
batch_setup_vtx_desc(useClr0, useClr1);
ParticleDrawCtx ctx{};
ctx.batch = true;
ctx.useTexMtx = info.hasPtclTexMtx;
ctx.useClr0 = useClr0;
ctx.useClr1 = useClr1;
bool fwdAhead = res->getBsp()->isDrawFwdAhead();
JPANode<JPABaseParticle>* node = fwdAhead ? work->mpEmtr->mAlivePtclBase.getLast() :
work->mpEmtr->mAlivePtclBase.getFirst();
GXBegin(GX_QUADS, GX_VTXFMT1, GX_AUTO);
while (node != work->mpEmtr->mAlivePtclBase.getEnd()) {
work->mpCurNode = node;
for (int i = res->mpDrawParticleFuncListNum - 1; i >= 0; i--) {
(*res->mpDrawParticleFuncList[i])(work, node->getObject(), &ctx);
}
node = fwdAhead ? node->getPrev() : node->getNext();
}
GXEnd();
batch_restore_gx(work, useClr0, info.hasPtclTexMtx);
return true;
}
#endif
void JPAResource::drawP(JPAEmitterWorkData* work) {
ZoneScoped;
work->mpEmtr->clearStatus(0x80);
@@ -842,13 +1095,25 @@ void JPAResource::drawP(JPAEmitterWorkData* work) {
(*mpDrawEmitterFuncList[i])(work);
}
#if TARGET_PC
if (draw_particle_batch(work)) {
GXSetMisc(GX_MT_XF_FLUSH, 0);
if (work->mpEmtr->mpEmtrCallBack != nullptr) {
work->mpEmtr->mpEmtrCallBack->drawAfter(work->mpEmtr);
}
return;
}
ParticleDrawCtx ctx{}; // immediate mode
#endif
if (pBsp->isDrawFwdAhead()) {
JPANode<JPABaseParticle>* node = work->mpEmtr->mAlivePtclBase.getLast();
for (; node != work->mpEmtr->mAlivePtclBase.getEnd(); node = node->getPrev()) {
work->mpCurNode = node;
if (mpDrawParticleFuncList != NULL) {
for (int i = mpDrawParticleFuncListNum - 1; i >= 0; i--) {
(*mpDrawParticleFuncList[i])(work, node->getObject());
(*mpDrawParticleFuncList[i])(work, node->getObject() JPA_DRAW_CTX_ARG);
}
}
}
@@ -858,7 +1123,7 @@ void JPAResource::drawP(JPAEmitterWorkData* work) {
work->mpCurNode = node;
if (mpDrawParticleFuncList != NULL) {
for (int i = mpDrawParticleFuncListNum - 1; i >= 0; i--) {
(*mpDrawParticleFuncList[i])(work, node->getObject());
(*mpDrawParticleFuncList[i])(work, node->getObject() JPA_DRAW_CTX_ARG);
}
}
}
@@ -905,13 +1170,17 @@ void JPAResource::drawC(JPAEmitterWorkData* work) {
(*mpDrawEmitterChildFuncList[i])(work);
}
#if TARGET_PC
ParticleDrawCtx ctx{}; // immediate mode
#endif
if (pBsp->isDrawFwdAhead()) {
JPANode<JPABaseParticle>* node = work->mpEmtr->mAlivePtclChld.getLast();
for (; node != work->mpEmtr->mAlivePtclChld.getEnd(); node = node->getPrev()) {
work->mpCurNode = node;
if (mpDrawParticleChildFuncList != NULL) {
for (int i = mpDrawParticleChildFuncListNum - 1; i >= 0; i--) {
(*mpDrawParticleChildFuncList[i])(work, node->getObject());
(*mpDrawParticleChildFuncList[i])(work, node->getObject() JPA_DRAW_CTX_ARG);
}
}
}
@@ -921,7 +1190,7 @@ void JPAResource::drawC(JPAEmitterWorkData* work) {
work->mpCurNode = node;
if (mpDrawParticleChildFuncList != NULL) {
for (int i = mpDrawParticleChildFuncListNum - 1; i >= 0; i--) {
(*mpDrawParticleChildFuncList[i])(work, node->getObject());
(*mpDrawParticleChildFuncList[i])(work, node->getObject() JPA_DRAW_CTX_ARG);
}
}
}
@@ -4,6 +4,7 @@ import android.app.ActionBar;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
@@ -14,12 +15,16 @@ import android.provider.DocumentsContract;
import android.provider.OpenableColumns;
import android.provider.Settings;
import android.util.Log;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.View;
import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowInsetsController;
import org.libsdl.app.SDLActivity;
import org.libsdl.app.SDLSurface;
import java.io.File;
import java.util.ArrayList;
@@ -27,6 +32,7 @@ import java.util.List;
public class DuskActivity extends SDLActivity {
private static final String TAG = "DuskActivity";
private static final float DEFAULT_SURFACE_FRAME_RATE = 60.0f;
private static final int FOLDER_DIALOG_REQUEST_CODE = 0x4455;
private static final int MANAGE_STORAGE_REQUEST_CODE = 0x4456;
private static final String EXTERNAL_STORAGE_AUTHORITY =
@@ -88,6 +94,11 @@ public class DuskActivity extends SDLActivity {
hideSystemBars();
}
@Override
protected SDLSurface createSDLSurface(Context context) {
return new DuskSurface(context);
}
@Override
protected void onResume() {
super.onResume();
@@ -139,6 +150,77 @@ public class DuskActivity extends SDLActivity {
};
}
public void setPreferredSurfaceFrameRate(float frameRate) {
runOnUiThread(() -> {
if (mSurface instanceof DuskSurface) {
((DuskSurface)mSurface).setPreferredFrameRate(frameRate);
}
});
}
private static final class DuskSurface extends SDLSurface {
private float preferredFrameRate = DEFAULT_SURFACE_FRAME_RATE;
DuskSurface(Context context) {
super(context);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
super.surfaceChanged(holder, format, width, height);
setTargetFrameRate(holder);
}
void setPreferredFrameRate(float frameRate) {
preferredFrameRate = frameRate;
setTargetFrameRate(getHolder());
}
private void setTargetFrameRate(SurfaceHolder holder) {
if (!mIsSurfaceReady || Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
return;
}
Surface surface = holder != null ? holder.getSurface() : getHolder().getSurface();
if (surface == null || !surface.isValid()) {
return;
}
float targetFrameRate = getMaxSupportedFrameRate();
if (preferredFrameRate > 0.0f) {
targetFrameRate = preferredFrameRate;
}
if (targetFrameRate <= 0.0f) {
return;
}
try {
surface.setFrameRate(
targetFrameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
Log.v(TAG, "Requested surface frame rate " + targetFrameRate + " fps");
} catch (RuntimeException e) {
Log.w(TAG, "Failed to request surface frame rate", e);
}
}
private float getMaxSupportedFrameRate() {
if (mDisplay == null) {
return 0.0f;
}
float maxFrameRate = mDisplay.getRefreshRate();
Display.Mode[] modes = mDisplay.getSupportedModes();
if (modes == null) {
return maxFrameRate;
}
for (Display.Mode mode : modes) {
maxFrameRate = Math.max(maxFrameRate, mode.getRefreshRate());
}
return maxFrameRate;
}
}
@Override
protected String[] getArguments() {
Intent intent = getIntent();
@@ -19,9 +19,13 @@ import android.os.*;
import java.lang.Runnable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.UUID;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDevice {
private static final String TAG = "hidapi";
@@ -33,10 +37,19 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
private boolean mIsConnected = false;
private boolean mIsChromebook = false;
private boolean mIsReconnecting = false;
private boolean mHasEnabledNotifications = false;
private boolean mHasSeenInputUpdate = false;
private boolean mFrozen = false;
private LinkedList<GattOperation> mOperations;
GattOperation mCurrentOperation = null;
private Handler mHandler;
private int mProductId = -1;
private int mReportId = 0;
private UUID mInputCharacteristic;
private static final int D0G_BLE2_PID = 0x1106;
private static final int TRITON_BLE_PID = 0x1303;
private static final int TRANSPORT_AUTO = 0;
private static final int TRANSPORT_BREDR = 1;
@@ -45,10 +58,14 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
private static final int CHROMEBOOK_CONNECTION_CHECK_INTERVAL = 10000;
static final UUID steamControllerService = UUID.fromString("100F6C32-1735-4313-B402-38567131E5F3");
static final UUID inputCharacteristic = UUID.fromString("100F6C33-1735-4313-B402-38567131E5F3");
static final UUID inputCharacteristicD0G = UUID.fromString("100F6C33-1735-4313-B402-38567131E5F3");
static final UUID inputCharacteristicTriton_0x45 = UUID.fromString("100F6C7A-1735-4313-B402-38567131E5F3");
static final UUID inputCharacteristicTriton_0x47 = UUID.fromString("100F6C7C-1735-4313-B402-38567131E5F3");
static final UUID reportCharacteristic = UUID.fromString("100F6C34-1735-4313-B402-38567131E5F3");
static private final byte[] enterValveMode = new byte[] { (byte)0xC0, (byte)0x87, 0x03, 0x08, 0x07, 0x00 };
private HashMap<Integer, BluetoothGattCharacteristic> mOutputReportChars = new HashMap<Integer, BluetoothGattCharacteristic>();
static class GattOperation {
private enum Operation {
CHR_READ,
@@ -61,6 +78,7 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
byte[] mValue;
BluetoothGatt mGatt;
boolean mResult = true;
int mDelayMs = 0;
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid) {
mGatt = gatt;
@@ -68,6 +86,13 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
mUuid = uuid;
}
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, int delayMs) {
mGatt = gatt;
mOp = operation;
mUuid = uuid;
mDelayMs = delayMs;
}
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value) {
mGatt = gatt;
mOp = operation;
@@ -75,6 +100,14 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
mValue = value;
}
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value, int delayMs) {
mGatt = gatt;
mOp = operation;
mUuid = uuid;
mValue = value;
mDelayMs = delayMs;
}
public void run() {
// This is executed in main thread
BluetoothGattCharacteristic chr;
@@ -136,6 +169,8 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
return mResult;
}
public int getDelayMs() { return mDelayMs; }
private BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
BluetoothGattService valveService = mGatt.getService(steamControllerService);
if (valveService == null)
@@ -154,6 +189,10 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid) {
return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid);
}
static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid, int delayMs) {
return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid, delayMs);
}
}
HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) {
@@ -166,6 +205,8 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
mHandler = new Handler(Looper.getMainLooper());
mGatt = connectGatt();
mHasEnabledNotifications = false;
mHasSeenInputUpdate = false;
// final HIDDeviceBLESteamController finalThis = this;
// mHandler.postDelayed(new Runnable() {
// @Override
@@ -314,8 +355,45 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
Log.v(TAG, "Found Valve steam controller service " + service.getUuid());
for (BluetoothGattCharacteristic chr : service.getCharacteristics()) {
if (chr.getUuid().equals(inputCharacteristic)) {
Log.v(TAG, "Found input characteristic");
if (chr.getUuid().equals(inputCharacteristicTriton_0x45)) {
Log.v(TAG, "Found Triton input characteristic 0x45");
mProductId = TRITON_BLE_PID;
mReportId = 0x45;
mInputCharacteristic = chr.getUuid();
} else if (chr.getUuid().equals(inputCharacteristicTriton_0x47)) {
Log.v(TAG, "Found Triton input characteristic 0x47");
mProductId = TRITON_BLE_PID;
mReportId = 0x47;
mInputCharacteristic = chr.getUuid();
} else if (chr.getUuid().equals(inputCharacteristicD0G)) {
Log.v(TAG, "Found D0G input characteristic");
mProductId = D0G_BLE2_PID;
mReportId = 0x03;
mInputCharacteristic = chr.getUuid();
} else {
Pattern reportPattern = Pattern.compile("100F6C([0-9A-Z]{2})", Pattern.CASE_INSENSITIVE);
Matcher matcher = reportPattern.matcher(chr.getUuid().toString());
if (matcher.find()) {
try {
int reportId = Integer.parseInt(matcher.group(1), 16);
reportId -= 0x35;
if (reportId >= 0x80) {
// This is a Triton output report characteristic that we need to care about.
Log.v(TAG, "Found Triton output report 0x" + Integer.toString(reportId, 16));
mOutputReportChars.put(reportId, chr);
}
}
catch (NumberFormatException nfe) {
Log.w(TAG, "Could not parse report characteristic " + chr.getUuid().toString() + ": " + nfe.toString());
}
}
}
}
for (BluetoothGattCharacteristic chr : service.getCharacteristics()) {
if (chr.getUuid().equals(mInputCharacteristic)) {
// Start notifications
BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
if (cccd != null) {
@@ -372,21 +450,30 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
mCurrentOperation = mOperations.removeFirst();
}
// Run in main thread
mHandler.post(new Runnable() {
@Override
public void run() {
synchronized (mOperations) {
if (mCurrentOperation == null) {
Log.e(TAG, "Current operation null in executor?");
return;
}
Runnable gattOperationRunnable = new Runnable() {
@Override
public void run() {
synchronized (mOperations) {
if (mCurrentOperation == null) {
Log.e(TAG, "Current operation null in executor?");
return;
}
mCurrentOperation.run();
// now wait for the GATT callback and when it comes, finish this operation
mCurrentOperation.run();
// now wait for the GATT callback and when it comes, finish this operation
}
}
}
});
};
if (mCurrentOperation.getDelayMs() == 0) {
// Run in main thread
mHandler.post(gattOperationRunnable);
}
else {
// If we have a delay on this operation, wait before we post it.
mHandler.postDelayed(gattOperationRunnable, mCurrentOperation.getDelayMs());
}
}
private void queueGattOperation(GattOperation op) {
@@ -397,8 +484,39 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
}
private void enableNotification(UUID chrUuid) {
GattOperation op = HIDDeviceBLESteamController.GattOperation.enableNotification(mGatt, chrUuid);
// Add a 500ms delay to notification write for Amazon Fire TV devices, as otherwise if we do this too quickly after connecting
// it will return success and then silently drop the operation on the floor.
GattOperation op = HIDDeviceBLESteamController.GattOperation.enableNotification(mGatt, chrUuid, 500);
queueGattOperation(op);
// Amazon Fire devices can also silently timeout on writeDescriptor, so
// set up a little delayed check that will attempt to write a second time.
//
// While this only seems to be needed on Amazon Fire TV devices at present, it
// doesn't hurt to have a retry on other devices as well.
//
final HIDDeviceBLESteamController finalThis = this;
final UUID finalUuid = chrUuid;
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (!finalThis.mHasEnabledNotifications) {
if (finalThis.mHasSeenInputUpdate) {
// Amazon Five devices may have enabled notifications on the input characteristic and not given us a callback. If we've seen
// input reports, though, somewhat by definition notifications are enabled.
Log.w(TAG, "WriteDescriptor has never returned, but we've seen input reports. Moving on with controller initialization.");
finalThis.mHasEnabledNotifications = true;
finalThis.enableValveMode();
return;
}
// Give one more try.
GattOperation retry = HIDDeviceBLESteamController.GattOperation.enableNotification(finalThis.mGatt, finalUuid, 500);
finalThis.queueGattOperation(retry);
}
}
}, 1000);
}
void writeCharacteristic(UUID uuid, byte[] value) {
@@ -448,8 +566,16 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
mIsConnected = false;
gatt.disconnect();
mGatt = connectGatt(false);
}
else {
} else {
if (getProductId() == TRITON_BLE_PID) {
// Android will not properly play well with Data Length Extensions without manually requesting a large MTU,
// and Triton controllers require DLE support.
//
// 517 is basically a "magic number" as far as Android's bluetooth code is concerned, so do not change
// this value. It is functionally "please enable data length extensions" on some Android builds.
mGatt.requestMtu(517);
}
probeService(this);
}
}
@@ -474,7 +600,7 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
// Only register controller with the native side once it has been fully configured
if (!isRegistered()) {
Log.v(TAG, "Registering Steam Controller with ID: " + getId());
mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0, true);
mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0, true, mReportId);
setRegistered();
}
}
@@ -487,7 +613,8 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
// Enable this for verbose logging of controller input reports
//Log.v(TAG, "onCharacteristicChanged uuid=" + characteristic.getUuid() + " data=" + HexDump.dumpHexString(characteristic.getValue()));
if (characteristic.getUuid().equals(inputCharacteristic) && !mFrozen) {
if (characteristic.getUuid().equals(mInputCharacteristic) && !mFrozen) {
mHasSeenInputUpdate = true;
mManager.HIDDeviceInputReport(getId(), characteristic.getValue());
}
}
@@ -497,19 +624,36 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
//Log.v(TAG, "onDescriptorRead status=" + status);
}
private void enableValveMode()
{
BluetoothGattService valveService = mGatt.getService(steamControllerService);
if (valveService == null)
return;
BluetoothGattCharacteristic reportChr = valveService.getCharacteristic(reportCharacteristic);
if (reportChr != null) {
if (getProductId() == TRITON_BLE_PID) {
// For Triton we just mark things registered.
Log.v(TAG, "Registering Triton Steam Controller with ID: " + getId());
mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0, true, mReportId);
setRegistered();
} else {
// For the original controller, we need to manually enter Valve mode.
Log.v(TAG, "Writing report characteristic to enter valve mode");
reportChr.setValue(enterValveMode);
mGatt.writeCharacteristic(reportChr);
}
}
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
BluetoothGattCharacteristic chr = descriptor.getCharacteristic();
//Log.v(TAG, "onDescriptorWrite status=" + status + " uuid=" + chr.getUuid() + " descriptor=" + descriptor.getUuid());
if (chr.getUuid().equals(inputCharacteristic)) {
boolean hasWrittenInputDescriptor = true;
BluetoothGattCharacteristic reportChr = chr.getService().getCharacteristic(reportCharacteristic);
if (reportChr != null) {
Log.v(TAG, "Writing report characteristic to enter valve mode");
reportChr.setValue(enterValveMode);
gatt.writeCharacteristic(reportChr);
}
if (chr.getUuid().equals(mInputCharacteristic)) {
mHasEnabledNotifications = true;
enableValveMode();
}
finishCurrentGattOperation();
@@ -548,9 +692,20 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
@Override
public int getProductId() {
// We don't have an easy way to query from the Bluetooth device, but we know what it is
final int D0G_BLE2_PID = 0x1106;
return D0G_BLE2_PID;
if (mProductId > 0) {
// We've already set a product ID.
return mProductId;
}
if (mDevice.getName().startsWith("Steam Ctrl")) {
// We're a newer Triton device
mProductId = TRITON_BLE_PID;
} else {
// We're an OG Steam Controller
mProductId = D0G_BLE2_PID;
}
return mProductId;
}
@Override
@@ -601,10 +756,29 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
writeCharacteristic(reportCharacteristic, actual_report);
return report.length;
} else {
//Log.v(TAG, "writeOutputReport " + HexDump.dumpHexString(report));
writeCharacteristic(reportCharacteristic, report);
return report.length;
// If we're an original-recipe Steam Controller we just write to the characteristic directly.
if (getProductId() == D0G_BLE2_PID) {
//Log.v(TAG, "writeOutputReport " + HexDump.dumpHexString(report));
writeCharacteristic(reportCharacteristic, report);
return report.length;
}
// If we're a Triton, we need to find the correct report characteristic.
if (report.length > 0) {
int reportId = report[0] & 0xFF;
BluetoothGattCharacteristic targetedReportCharacteristic = mOutputReportChars.get(reportId);
if (targetedReportCharacteristic != null) {
byte[] actual_report = Arrays.copyOfRange(report, 1, report.length - 1);
//Log.v(TAG, "writeOutputReport 0x" + Integer.toString(reportId, 16) + " " + HexDump.dumpHexString(report));
writeCharacteristic(targetedReportCharacteristic.getUuid(), actual_report);
return report.length;
} else {
Log.w(TAG, "Got report write request for unknown report type 0x" + Integer.toString(reportId, 16));
}
}
}
return -1;
}
@Override
@@ -256,6 +256,7 @@ public class HIDDeviceManager {
0x24c6, // PowerA
0x2c22, // Qanba
0x2dc8, // 8BitDo
0x3537, // GameSir
0x37d7, // Flydigi
0x9886, // ASTRO Gaming
};
@@ -360,7 +361,7 @@ public class HIDDeviceManager {
HIDDeviceUSB device = new HIDDeviceUSB(this, usbDevice, interface_index);
int id = device.getId();
mDevicesById.put(id, device);
HIDDeviceConnected(id, device.getIdentifier(), device.getVendorId(), device.getProductId(), device.getSerialNumber(), device.getVersion(), device.getManufacturerName(), device.getProductName(), usbInterface.getId(), usbInterface.getInterfaceClass(), usbInterface.getInterfaceSubclass(), usbInterface.getInterfaceProtocol(), false);
HIDDeviceConnected(id, device.getIdentifier(), device.getVendorId(), device.getProductId(), device.getSerialNumber(), device.getVersion(), device.getManufacturerName(), device.getProductName(), usbInterface.getId(), usbInterface.getInterfaceClass(), usbInterface.getInterfaceSubclass(), usbInterface.getInterfaceProtocol(), false, 0);
}
}
}
@@ -529,7 +530,13 @@ public class HIDDeviceManager {
return false;
}
return bluetoothDevice.getName().equals("SteamController") && ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) != 0);
// Steam Controllers will always support Bluetooth Low Energy
if ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) == 0) {
return false;
}
// Match on the name either the original Steam Controller or the new second-generation one advertise with.
return bluetoothDevice.getName().equals("SteamController") || bluetoothDevice.getName().startsWith("Steam Ctrl");
}
private void close() {
@@ -681,7 +688,7 @@ public class HIDDeviceManager {
private native void HIDDeviceRegisterCallback();
private native void HIDDeviceReleaseCallback();
native void HIDDeviceConnected(int deviceID, String identifier, int vendorId, int productId, String serial_number, int release_number, String manufacturer_string, String product_string, int interface_number, int interface_class, int interface_subclass, int interface_protocol, boolean bBluetooth);
native void HIDDeviceConnected(int deviceID, String identifier, int vendorId, int productId, String serial_number, int release_number, String manufacturer_string, String product_string, int interface_number, int interface_class, int interface_subclass, int interface_protocol, boolean bBluetooth, int reportID);
native void HIDDeviceOpenPending(int deviceID);
native void HIDDeviceOpenResult(int deviceID, boolean opened);
native void HIDDeviceDisconnected(int deviceID);
@@ -21,6 +21,7 @@ class HIDDeviceUSB implements HIDDevice {
protected InputThread mInputThread;
protected boolean mRunning;
protected boolean mFrozen;
protected boolean mClaimed;
public HIDDeviceUSB(HIDDeviceManager manager, UsbDevice usbDevice, int interface_index) {
mManager = manager;
@@ -29,6 +30,7 @@ class HIDDeviceUSB implements HIDDevice {
mInterface = mDevice.getInterface(mInterfaceIndex).getId();
mDeviceId = manager.getDeviceIDForIdentifier(getIdentifier());
mRunning = false;
mClaimed = false;
}
String getIdentifier() {
@@ -114,6 +116,7 @@ class HIDDeviceUSB implements HIDDevice {
close();
return false;
}
mClaimed = true;
// Find the endpoints
for (int j = 0; j < iface.getEndpointCount(); j++) {
@@ -132,9 +135,12 @@ class HIDDeviceUSB implements HIDDevice {
}
}
// Make sure the required endpoints were present
if (mInputEndpoint == null || mOutputEndpoint == null) {
// Make sure the required endpoints were present. The original Steam Controller and the wireless dongle for it do NOT
// actually have -- or require -- output endpoints, so we need to accept only an input one for them or else we'll fall
// back to the Android system gamepad functionality (and lose our paddles et al).
if (mInputEndpoint == null) {
Log.w(TAG, "Missing required endpoint on USB device " + getDeviceName());
mConnection.releaseInterface(iface);
close();
return false;
}
@@ -154,6 +160,11 @@ class HIDDeviceUSB implements HIDDevice {
return -1;
}
if (!mClaimed) {
Log.w(TAG, "writeReport() called but some other process currently owns the USB device");
return -1;
}
if (feature) {
int res = -1;
int offset = 0;
@@ -185,6 +196,11 @@ class HIDDeviceUSB implements HIDDevice {
}
return length;
} else {
if (mOutputEndpoint == null)
{
Log.e(TAG, "Tried to write an output report to an interface with no output endpoint!");
return -1;
}
int res = mConnection.bulkTransfer(mOutputEndpoint, report, report.length, 1000);
if (res != report.length) {
Log.w(TAG, "writeOutputReport() returned " + res + " on device " + getDeviceName());
@@ -205,6 +221,12 @@ class HIDDeviceUSB implements HIDDevice {
Log.w(TAG, "readReport() called with no device connection");
return false;
}
if (!mClaimed) {
if (feature) {
return false;
}
return true;
}
if (report_number == 0x0) {
/* Offset the return buffer by 1, so that the report ID
@@ -258,10 +280,13 @@ class HIDDeviceUSB implements HIDDevice {
mInputThread = null;
}
if (mConnection != null) {
UsbInterface iface = mDevice.getInterface(mInterfaceIndex);
mConnection.releaseInterface(iface);
if (mClaimed) {
UsbInterface iface = mDevice.getInterface(mInterfaceIndex);
mConnection.releaseInterface(iface);
}
mConnection.close();
mConnection = null;
mClaimed = false;
}
}
@@ -274,6 +299,22 @@ class HIDDeviceUSB implements HIDDevice {
@Override
public void setFrozen(boolean frozen) {
mFrozen = frozen;
/* If we have a valid device connection and the claim state doesn't match what we want, try to correct that. */
if (mConnection != null && mClaimed == mFrozen) {
UsbInterface iface = mDevice.getInterface(mInterfaceIndex);
if (frozen) {
mClaimed = !mConnection.releaseInterface(iface);
if (mClaimed) {
Log.e(TAG, "Tried to release claim on USB device, but failed!");
}
} else {
mClaimed = mConnection.claimInterface(iface, true);
if (!mClaimed) {
Log.e(TAG, "Tried to regain claim on USB device, but failed!");
}
}
}
}
protected class InputThread extends Thread {
@@ -61,7 +61,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
private static final String TAG = "SDL";
private static final int SDL_MAJOR_VERSION = 3;
private static final int SDL_MINOR_VERSION = 4;
private static final int SDL_MICRO_VERSION = 8;
private static final int SDL_MICRO_VERSION = 10;
/*
// Display InputType.SOURCE/CLASS of events and devices
//
@@ -530,7 +530,8 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
if (mHIDDeviceManager != null) {
mHIDDeviceManager.setFrozen(true);
}
}
if (!mHasMultiWindow) {
pauseNativeThread();
}
@@ -543,7 +544,8 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
if (mHIDDeviceManager != null) {
mHIDDeviceManager.setFrozen(false);
}
}
if (!mHasMultiWindow) {
resumeNativeThread();
}
@@ -616,6 +618,14 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
super.onWindowFocusChanged(hasFocus);
Log.v(TAG, "onWindowFocusChanged(): " + hasFocus);
// If we are gaining focus, we can always try to restore our USB devices. If we are losing focus,
// only try to relinquish them if we don't have background events allowed (for multi-window Android setups).
if (hasFocus || !SDLActivity.nativeGetHintBoolean("SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS", false)) {
if (mHIDDeviceManager != null) {
mHIDDeviceManager.setFrozen(!hasFocus);
}
}
if (SDLActivity.mBrokenLibraries) {
return;
}
@@ -1481,11 +1491,11 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
if (SDLControllerManager.isDeviceSDLJoystick(deviceId)) {
// Note that we process events with specific key codes here
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (SDLControllerManager.onNativePadDown(deviceId, keyCode)) {
if (SDLControllerManager.onNativePadDown(deviceId, keyCode, event.getScanCode())) {
return true;
}
} else if (event.getAction() == KeyEvent.ACTION_UP) {
if (SDLControllerManager.onNativePadUp(deviceId, keyCode)) {
if (SDLControllerManager.onNativePadUp(deviceId, keyCode, event.getScanCode())) {
return true;
}
}
@@ -1963,7 +1973,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
int flags = Intent.FLAG_ACTIVITY_NO_HISTORY
int flags = Intent.FLAG_ACTIVITY_NO_HISTORY
| Intent.FLAG_ACTIVITY_MULTIPLE_TASK
| Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
i.addFlags(flags);
@@ -2227,3 +2237,4 @@ class SDLClipboardHandler implements
SDLActivity.onNativeClipboardChanged();
}
}
@@ -10,6 +10,10 @@ import android.hardware.lights.Light;
import android.hardware.lights.LightsRequest;
import android.hardware.lights.LightsManager;
import android.hardware.lights.LightState;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.graphics.Color;
import android.os.Build;
import android.os.VibrationEffect;
@@ -30,16 +34,18 @@ public class SDLControllerManager
static native void nativeAddJoystick(int device_id, String name, String desc,
int vendor_id, int product_id,
int button_mask,
int naxes, int axis_mask, int nhats, boolean can_rumble, boolean has_rgb_led);
int naxes, int axis_mask, int nhats, boolean can_rumble, boolean has_rgb_led,
boolean has_accelerometer, boolean has_gyroscope);
static native void nativeRemoveJoystick(int device_id);
static native void nativeAddHaptic(int device_id, String name);
static native void nativeRemoveHaptic(int device_id);
static public native boolean onNativePadDown(int device_id, int keycode);
static public native boolean onNativePadUp(int device_id, int keycode);
static public native boolean onNativePadDown(int device_id, int keycode, int scancode);
static public native boolean onNativePadUp(int device_id, int keycode, int scancode);
static native void onNativeJoy(int device_id, int axis,
float value);
static native void onNativeHat(int device_id, int hat_id,
int x, int y);
static native void onNativeJoySensor(int device_id, int sensor_type, long sensor_timestamp, float x, float y, float z);
protected static SDLJoystickHandler mJoystickHandler;
protected static SDLHapticHandler mHapticHandler;
@@ -81,6 +87,13 @@ public class SDLControllerManager
mJoystickHandler.setLED(device_id, red, green, blue);
}
/**
* This method is called by SDL using JNI.
*/
static void joystickSetSensorsEnabled(int device_id, boolean enabled) {
mJoystickHandler.setSensorsEnabled(device_id, enabled);
}
/**
* This method is called by SDL using JNI.
*/
@@ -153,6 +166,10 @@ class SDLJoystickHandler {
ArrayList<InputDevice.MotionRange> hats;
ArrayList<Light> lights;
LightsManager.LightsSession lightsSession;
SensorManager sensorManager;
SDLJoySensorListener sensorListener;
Sensor accelerometerSensor;
Sensor gyroscopeSensor;
}
static class RangeComparator implements Comparator<InputDevice.MotionRange> {
@Override
@@ -225,12 +242,13 @@ class SDLJoystickHandler {
joystick.desc = getJoystickDescriptor(joystickDevice);
joystick.axes = new ArrayList<InputDevice.MotionRange>();
joystick.hats = new ArrayList<InputDevice.MotionRange>();
java.util.Set<Integer> axisStrsSet = new java.util.HashSet<Integer>();
joystick.lights = new ArrayList<Light>();
List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges();
Collections.sort(ranges, new RangeComparator());
for (InputDevice.MotionRange range : ranges) {
if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
if (((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) && axisStrsSet.add(range.getAxis())) {
if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {
joystick.hats.add(range);
} else {
@@ -241,6 +259,8 @@ class SDLJoystickHandler {
boolean can_rumble = false;
boolean has_rgb_led = false;
boolean has_accelerometer = false;
boolean has_gyroscope = false;
if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) {
VibratorManager vibratorManager = joystickDevice.getVibratorManager();
int[] vibrators = vibratorManager.getVibratorIds();
@@ -258,12 +278,26 @@ class SDLJoystickHandler {
joystick.lightsSession = lightsManager.openSession();
has_rgb_led = true;
}
SensorManager sensorManager = joystickDevice.getSensorManager();
if (sensorManager != null) {
joystick.sensorManager = sensorManager;
joystick.sensorListener = new SDLJoySensorListener(joystick.device_id);
joystick.accelerometerSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
if (joystick.accelerometerSensor != null) {
has_accelerometer = true;
}
joystick.gyroscopeSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
if (joystick.gyroscopeSensor != null) {
has_gyroscope = true;
}
}
}
mJoysticks.add(joystick);
SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc,
getVendorId(joystickDevice), getProductId(joystickDevice),
getButtonMask(joystickDevice), joystick.axes.size(), getAxisMask(joystick.axes), joystick.hats.size()/2, can_rumble, has_rgb_led);
getButtonMask(joystickDevice), joystick.axes.size(), getAxisMask(joystick.axes), joystick.hats.size()/2, can_rumble, has_rgb_led,
has_accelerometer, has_gyroscope);
}
}
}
@@ -508,6 +542,31 @@ class SDLJoystickHandler {
}
joystick.lightsSession.requestLights(lightsRequest.build());
}
void setSensorsEnabled(int device_id, boolean enabled) {
if (Build.VERSION.SDK_INT < 31 /* Android 12.0 (S) */) {
return;
}
SDLJoystick joystick = getJoystick(device_id);
if (joystick == null || joystick.sensorManager == null) {
return;
}
if (enabled) {
if (joystick.accelerometerSensor != null) {
SDLSensorManager.registerListener(joystick.sensorManager, joystick.sensorListener, joystick.accelerometerSensor, SensorManager.SENSOR_DELAY_GAME);
}
if (joystick.gyroscopeSensor != null) {
SDLSensorManager.registerListener(joystick.sensorManager, joystick.sensorListener, joystick.gyroscopeSensor, SensorManager.SENSOR_DELAY_GAME);
}
} else {
if (joystick.accelerometerSensor != null) {
SDLSensorManager.unregisterListener(joystick.sensorManager, joystick.sensorListener, joystick.accelerometerSensor);
}
if (joystick.gyroscopeSensor != null) {
SDLSensorManager.unregisterListener(joystick.sensorManager, joystick.sensorListener, joystick.gyroscopeSensor);
}
}
}
}
class SDLHapticHandler_API31 extends SDLHapticHandler {
@@ -933,3 +992,19 @@ class SDLGenericMotionListener_API29 extends SDLGenericMotionListener_API26 {
return penDevice.isExternal() ? SDL_PEN_DEVICE_TYPE_INDIRECT : SDL_PEN_DEVICE_TYPE_DIRECT;
}
}
class SDLJoySensorListener implements SensorEventListener {
int device_id;
public SDLJoySensorListener(int device_id) {
this.device_id = device_id;
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {}
@Override
public void onSensorChanged(SensorEvent event) {
SDLControllerManager.onNativeJoySensor(device_id, event.sensor.getType(), event.timestamp, event.values[0], event.values[1], event.values[2]);
}
}
@@ -0,0 +1,32 @@
package org.libsdl.app;
import android.hardware.Sensor;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
// This class coordinates synchronized access to sensor manager registration
//
// This prevents a java.util.ConcurrentModificationException exception on
// Android 16, specifically on the Samsung Tab S9 Ultra.
class SDLSensorManager
{
static private SDLSensorManager mManager = new SDLSensorManager();
public static void registerListener(SensorManager manager, SensorEventListener listener, Sensor sensor, int samplingPeriodUs) {
mManager.RegisterListener(manager, listener, sensor, samplingPeriodUs);
}
public static void unregisterListener(SensorManager manager, SensorEventListener listener, Sensor sensor) {
mManager.UnregisterListener(manager, listener, sensor);
}
private synchronized void RegisterListener(SensorManager manager, SensorEventListener listener, Sensor sensor, int samplingPeriodUs) {
manager.registerListener(listener, sensor, samplingPeriodUs, null);
}
private synchronized void UnregisterListener(SensorManager manager, SensorEventListener listener, Sensor sensor) {
manager.unregisterListener(listener, sensor);
}
}
@@ -47,6 +47,9 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
// Is SurfaceView ready for rendering
protected boolean mIsSurfaceReady;
// Is on-screen keyboard visible
protected boolean mKeyboardVisible;
// Pinch events
private final ScaleGestureDetector scaleGestureDetector;
@@ -213,6 +216,18 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
WindowInsets.Type.displayCutout());
SDLActivity.onNativeInsetsChanged(combined.left, combined.right, combined.top, combined.bottom);
if (insets.isVisible(WindowInsets.Type.ime())) {
if (!mKeyboardVisible) {
mKeyboardVisible = true;
SDLActivity.onNativeScreenKeyboardShown();
}
} else {
if (mKeyboardVisible) {
mKeyboardVisible = false;
SDLActivity.onNativeScreenKeyboardHidden();
}
}
}
// Pass these to any child views in case they need them
@@ -318,11 +333,11 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
protected void enableSensor(int sensortype, boolean enabled) {
// TODO: This uses getDefaultSensor - what if we have >1 accels?
if (enabled) {
mSensorManager.registerListener(this,
SDLSensorManager.registerListener(mSensorManager, this,
mSensorManager.getDefaultSensor(sensortype),
SensorManager.SENSOR_DELAY_GAME, null);
SensorManager.SENSOR_DELAY_GAME);
} else {
mSensorManager.unregisterListener(this,
SDLSensorManager.unregisterListener(mSensorManager, this,
mSensorManager.getDefaultSensor(sensortype));
}
}
+1 -1
View File
@@ -16,7 +16,7 @@ body {
flex-direction: column;
justify-content: flex-end;
align-items: stretch;
z-index: 1;
z-index: 2;
pointer-events: none;
}
+339
View File
@@ -0,0 +1,339 @@
*, *:before, *:after {
box-sizing: border-box;
}
body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
font-family: "Fira Sans Condensed";
font-weight: bold;
color: rgba(248, 244, 232, 90%);
z-index: 1;
filter: opacity(0);
transition: filter 0.2s linear-in-out;
}
body[open] {
filter: opacity(1);
}
body:not([open]) {
pointer-events: none;
}
button {
display: flex;
align-items: center;
justify-content: center;
decorator: none;
padding: 0;
border: 1dp rgba(255, 255, 255, 22%);
background-color: rgba(22, 24, 28, 48%);
color: rgba(248, 244, 232, 90%);
text-align: center;
/* backdrop-filter: blur(7dp); */
/* box-shadow: 0 6dp 18dp rgba(0, 0, 0, 28%); */
transform-origin: center;
transition: background-color border-color filter transform 0.08s linear-in-out,
opacity 0.2s linear-in-out;
}
button.pressed,
button.active {
background-color: rgba(63, 78, 90, 68%);
border-color: rgba(255, 255, 255, 48%);
filter: brightness(1.18);
}
button:hidden {
opacity: 0;
pointer-events: none;
}
button span {
display: block;
line-height: 1;
}
button icon {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
button icon glyph {
display: block;
font-family: "Material Symbols Rounded";
font-weight: normal;
font-size: 24dp;
line-height: 1;
}
.midna-icon,
.item-icon,
.item-count,
.oil-meter {
display: none;
}
.midna-icon.visible,
.item-icon.visible,
.item-count.visible,
.oil-meter.visible {
display: block;
}
.control {
position: absolute;
}
.trigger-l.active {
background-color: rgba(57, 116, 133, 74%);
border-color: rgba(128, 222, 234, 72%);
}
.trigger,
.skip {
border-radius: 23dp;
}
.trigger {
font-size: 22dp;
}
.button-z {
background-color: rgba(118, 79, 158, 58%);
border-color: rgba(203, 170, 255, 36%);
}
.midna-icon {
position: absolute;
left: 9dp;
top: -1dp;
height: 48dp;
}
.button-z.has-icon span,
.face.has-item span {
position: absolute;
font-size: 13dp;
line-height: 1;
}
.button-z.has-icon span {
right: 9dp;
bottom: 7dp;
}
.button-z.pressed {
background-color: rgba(139, 91, 187, 82%);
border-color: rgba(220, 194, 255, 70%);
}
action-bar {
position: absolute;
display: flex;
align-items: center;
border: 1dp rgba(255, 255, 255, 22%);
border-radius: 23dp;
background-color: rgba(22, 24, 28, 48%);
/* backdrop-filter: blur(7dp); */
/* box-shadow: 0 -6dp 18dp rgba(0, 0, 0, 28%); */
overflow: hidden;
opacity: 1;
transform-origin: center;
transition: opacity 0.2s linear-in-out;
}
action-bar:hidden,
action-bar:hidden button,
action-bar:hidden separator {
opacity: 0;
pointer-events: none;
}
.utility {
position: relative;
flex: 1 1 auto;
width: 56dp;
height: 44dp;
margin: 0;
border-width: 0dp;
border-radius: 0;
background-color: transparent;
box-shadow: none;
}
.utility,
.skip {
opacity: 0.55;
}
.utility.pressed {
background-color: rgba(63, 78, 90, 68%);
}
.utility.pressed,
.skip.pressed {
opacity: 1;
}
.skip {
z-index: 1;
border-color: rgba(255, 255, 255, 36%);
}
separator {
display: block;
flex: 0 0 1dp;
width: 1dp;
height: 24dp;
background-color: rgba(255, 255, 255, 18%);
opacity: 1;
transition: opacity 0.2s linear-in-out;
}
.face {
position: absolute;
border-radius: 29dp;
font-size: 24dp;
overflow: visible;
}
.item-icon {
width: auto;
height: auto;
max-width: 76%;
max-height: 76%;
}
.item-count {
position: absolute;
left: 6dp;
bottom: 5dp;
min-width: 17dp;
height: 15dp;
padding: 1dp 3dp;
border-radius: 7dp;
background-color: rgba(0, 0, 0, 52%);
color: rgba(255, 255, 255, 92%);
font-size: 12dp;
line-height: 13dp;
text-align: center;
}
.oil-meter {
position: absolute;
left: 12dp;
bottom: -5dp;
width: 34dp;
height: 8dp;
padding: 2dp;
border: 1dp rgba(42, 32, 18, 82%);
border-radius: 4dp;
background-color: rgba(18, 14, 10, 70%);
/* box-shadow: 0 2dp 6dp rgba(0, 0, 0, 35%); */
}
oil-fill {
display: block;
width: 0%;
height: 100%;
border-radius: 2dp;
background-color: rgb(255, 232, 74);
}
.face.has-item span {
right: 6dp;
bottom: 6dp;
color: rgba(255, 255, 255, 88%);
}
.face.a {
border-radius: 37dp;
font-size: 31dp;
background-color: rgba(34, 112, 123, 62%);
}
.face.b {
background-color: rgba(161, 61, 66, 58%);
}
.face.x {
background-color: rgba(83, 115, 151, 56%);
}
.face.y {
background-color: rgba(113, 91, 150, 54%);
}
button.control.docked-top,
action-bar.docked-top {
border-top-width: 0dp;
border-top-left-radius: 0dp;
border-top-right-radius: 0dp;
}
button.control.docked-bottom,
action-bar.docked-bottom {
border-bottom-width: 0dp;
border-bottom-left-radius: 0dp;
border-bottom-right-radius: 0dp;
}
button.control.docked-left,
action-bar.docked-left {
border-left-width: 0dp;
border-top-left-radius: 0dp;
border-bottom-left-radius: 0dp;
}
button.control.docked-right,
action-bar.docked-right {
border-right-width: 0dp;
border-top-right-radius: 0dp;
border-bottom-right-radius: 0dp;
}
touch-stick {
display: block;
position: absolute;
width: 124dp;
height: 124dp;
border-radius: 62dp;
background-color: rgba(18, 20, 24, 35%);
border: 1dp rgba(255, 255, 255, 20%);
/* backdrop-filter: blur(7dp); */
/* box-shadow: 0 8dp 24dp rgba(0, 0, 0, 24%); */
opacity: 0;
pointer-events: none;
transition: opacity 0.18s linear-in-out;
}
touch-stick.active {
opacity: 1;
}
stick-ring {
position: absolute;
left: 18dp;
top: 18dp;
width: 88dp;
height: 88dp;
border-radius: 44dp;
border: 1dp rgba(255, 255, 255, 18%);
}
stick-knob {
position: absolute;
width: 48dp;
height: 48dp;
border-radius: 24dp;
background-color: rgba(238, 236, 226, 55%);
border: 1dp rgba(255, 255, 255, 45%);
}
+138
View File
@@ -0,0 +1,138 @@
body.touch-editor {
background-color: rgba(4, 6, 8, 34%);
z-index: 8;
}
body.touch-editor .control,
body.touch-editor action-bar {
opacity: 0.88;
cursor: move;
pointer-events: auto;
}
body.touch-editor .control:hover,
body.touch-editor action-bar:hover,
body.touch-editor .control.editor-selected,
body.touch-editor action-bar.editor-selected {
border-color: rgba(255, 232, 128, 80%);
filter: brightness(1.15);
}
body.touch-editor action-bar button,
body.touch-editor action-bar separator {
pointer-events: none;
}
selection-frame {
display: none;
position: absolute;
z-index: 20;
border: 2dp rgba(255, 232, 128, 88%);
background-color: rgba(255, 232, 128, 7%);
pointer-events: none;
}
selection-frame.visible {
display: block;
}
resize-handle {
display: block;
position: absolute;
width: 22dp;
height: 22dp;
border: 2dp rgba(255, 244, 190, 96%);
border-radius: 11dp;
background-color: rgba(34, 37, 42, 86%);
pointer-events: auto;
}
resize-handle.left {
left: -12dp;
}
resize-handle.right {
right: -12dp;
}
resize-handle.top {
top: -12dp;
}
resize-handle.bottom {
bottom: -12dp;
}
resize-handle.horizontal {
top: 50%;
margin-top: -11dp;
}
resize-handle.vertical {
left: 50%;
margin-left: -11dp;
}
resize-handle.corner.left {
left: -12dp;
}
resize-handle.corner.right {
right: -12dp;
}
resize-handle.corner.top {
top: -12dp;
}
resize-handle.corner.bottom {
bottom: -12dp;
}
editor-toolbar {
display: flex;
position: absolute;
left: 24dp;
right: 24dp;
top: 50%;
z-index: 30;
height: 48dp;
margin-top: -24dp;
gap: 8dp;
justify-content: center;
pointer-events: auto;
}
editor-toolbar button.editor-command {
flex: 0 1 150dp;
min-width: 96dp;
height: 48dp;
padding: 0 14dp;
border-radius: 8dp;
border: 1dp rgba(255, 255, 255, 26%);
background-color: rgba(17, 19, 24, 88%);
color: rgba(255, 250, 232, 94%);
font-family: "Fira Sans";
font-size: 18dp;
line-height: 48dp;
opacity: 1;
cursor: pointer;
}
editor-toolbar button.editor-command span {
display: block;
width: 100%;
line-height: 48dp;
text-align: center;
}
editor-toolbar button.editor-command.primary {
border-color: rgba(255, 232, 128, 70%);
background-color: rgba(96, 82, 38, 90%);
}
editor-toolbar button.editor-command:hover,
editor-toolbar button.editor-command:focus-visible {
border-color: rgba(255, 244, 190, 92%);
background-color: rgba(78, 85, 96, 92%);
}
+10
View File
@@ -181,3 +181,13 @@ bool daAlink_c::checkAimContext() {
return false;
}
}
bool daAlink_c::checkAimInputContext() {
switch (mProcID) {
case PROC_HOOKSHOT_ROOF_WAIT:
case PROC_HOOKSHOT_WALL_WAIT:
return false;
default:
return checkAimContext();
}
}
+29 -2
View File
@@ -14,6 +14,7 @@
#include "dusk/action_bindings.h"
#include "dusk/gyro.h"
#include "dusk/mouse.h"
#include "dusk/touch_camera.h"
#endif
bool daAlink_c::checkNoSubjectModeCamera() {
@@ -122,7 +123,7 @@ BOOL daAlink_c::setBodyAngleToCamera() {
}
#if TARGET_PC
if (dusk::getSettings().game.enableMouseAim && checkAimContext()) {
if (dusk::getSettings().game.enableMouseAim && checkAimInputContext()) {
sp8 = mBodyAngle.x;
} else
#endif
@@ -141,7 +142,7 @@ BOOL daAlink_c::setBodyAngleToCamera() {
#if TARGET_PC
if ((dusk::getSettings().game.enableGyroAim ||
dusk::getSettings().game.enableMouseAim) &&
checkAimContext())
checkAimInputContext())
{
f32 gyro_scale = 1.0f;
if (checkWolfEyeUp()) {
@@ -172,6 +173,32 @@ BOOL daAlink_c::setBodyAngleToCamera() {
sp8 = mBodyAngle.x;
}
}
if (dusk::getSettings().game.enableTouchControls && checkAimInputContext()) {
f32 touchYawDp = 0.0f;
f32 touchPitchDp = 0.0f;
if (dusk::touch_camera::consume_delta(touchYawDp, touchPitchDp)) {
f32 scale = 1.0f;
if (checkWolfEyeUp()) {
scale *= 0.6f;
}
if (dComIfGp_checkPlayerStatus0(0, 0x200000)) {
scale /= dComIfGp_getCameraZoomScale(field_0x317c);
}
const f32 yawDeg = -touchYawDp * dusk::touch_camera::YAW_DEGREES_PER_DP * scale *
dusk::getSettings().game.touchCameraXSensitivity;
const f32 pitchDeg = touchPitchDp * dusk::touch_camera::PITCH_DEGREES_PER_DP *
scale * dusk::getSettings().game.touchCameraYSensitivity;
shape_angle.y = shape_angle.y + cM_deg2s(yawDeg);
sp8 = sp8 + cM_deg2s(pitchDeg);
if (checkNotItemSinkLimit() && sp8 > 0 && sp8 > mBodyAngle.x) {
sp8 = mBodyAngle.x;
}
}
}
#endif
if (checkNotItemSinkLimit() && sp8 > 0) {
+10
View File
@@ -6882,6 +6882,16 @@ static int daNpc_Ks_Delete(npc_ks_class* i_this) {
i_this->model->stopZelAnime();
}
#if TARGET_PC
if (leader == i_this) {
leader = NULL;
}
if (saru_p[i_this->set_id] == i_this) {
saru_p[i_this->set_id] = NULL;
}
#endif
return 1;
}
+378
View File
@@ -15,6 +15,12 @@ const u16 l_J_Ohana00_64TEX__height = 63;
using GameVersion = dusk::version::GameVersion;
static u8* l_J_Ohana00_64TEX_get() { static u8 buf[0x800]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x9060}, {GameVersion::GcnPal, 0x9060}}, 0x800), true); return buf; }
#define l_J_Ohana00_64TEX (l_J_Ohana00_64TEX_get())
// from d_grass.inc
static MtxP get_model_mtx(Mtx modelMtx, Mtx storage);
static void transform_positions(
const dusk::batch::LeafTemplate& tpl, const Vec* posArray, const Mtx mtx, Vec* xfPos);
static void split_batch(u32& emitted, u32 vtxCount);
#else
#include "assets/l_J_Ohana00_64TEX.h"
#endif
@@ -588,6 +594,12 @@ dFlower_packet_c::dFlower_packet_c() {
GXInitTexObj(&mTexObj_l_J_Ohana01_64128_0419TEX, l_J_Ohana01_64128_0419TEX,
l_J_Ohana01_64128_0419TEX__width + 1, l_J_Ohana01_64128_0419TEX__height + 1, GX_TF_CMPR, GX_MIRROR, GX_MIRROR, GX_FALSE
);
dusk::batch::decode_leaf_template(l_J_hana00DL, 0x140, mTplHana00);
dusk::batch::decode_leaf_template(l_J_hana00_cDL, 0xC0, mTplHana00Cut);
dusk::batch::decode_leaf_template(l_J_hana01DL, 0x120, mTplHana01);
dusk::batch::decode_leaf_template(l_J_hana01_c_00DL, 0xC0, mTplHana01Cut00);
dusk::batch::decode_leaf_template(l_J_hana01_c_01DL, 0x120, mTplHana01Cut);
#endif
m_deleteRoom = &dFlower_packet_c::deleteRoom;
@@ -597,6 +609,371 @@ dFlower_packet_c::dFlower_packet_c() {
#endif
}
#if TARGET_PC
static void batch_setup_tev(u32 lightMask) {
GXSetCullMode(GX_CULL_NONE);
GXSetNumChans(2);
GXSetChanCtrl(GX_COLOR0, GX_FALSE, GX_SRC_REG, GX_SRC_VTX, 0, GX_DF_NONE, GX_AF_NONE);
GXSetChanCtrl(GX_COLOR1, GX_TRUE, GX_SRC_VTX, GX_SRC_REG, lightMask, GX_DF_CLAMP, GX_AF_SPOT);
GXSetNumTevStages(3);
GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR1A1);
GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_RASC);
GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV);
GXSetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO);
GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV);
GXSetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0);
GXSetTevColorIn(GX_TEVSTAGE1, GX_CC_ZERO, GX_CC_CPREV, GX_CC_RASC, GX_CC_ZERO);
GXSetTevColorOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV);
GXSetTevAlphaIn(GX_TEVSTAGE1, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO);
GXSetTevAlphaOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV);
GXSetTevOrder(GX_TEVSTAGE2, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL);
GXSetTevColorIn(GX_TEVSTAGE2, GX_CC_ZERO, GX_CC_TEXC, GX_CC_CPREV, GX_CC_C0);
GXSetTevColorOp(GX_TEVSTAGE2, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_4, GX_TRUE, GX_TEVPREV);
GXSetTevAlphaIn(GX_TEVSTAGE2, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_TEXA);
GXSetTevAlphaOp(GX_TEVSTAGE2, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV);
}
static GXColor hana00_amb_color(const dFlower_data_c* flower, const dKy_tevstr_c* tevstr) {
GXColor amb = {0, 0, 0, 0xFF};
if (DEBUG && g_kankyoHIO.navy.grass_adjust_ON != 0) {
amb.r = g_kankyoHIO.navy.grass_ambcol.r * 2;
amb.g = g_kankyoHIO.navy.grass_ambcol.g * 2;
amb.b = g_kankyoHIO.navy.grass_ambcol.b * 2;
} else {
amb.r = (flower->field_0x04 & 0x1F) * 2;
amb.g = ((flower->field_0x04 >> 5) & 0x1F) * 2;
amb.b = ((flower->field_0x04 >> 0xA) & 0x1F) * 2;
}
if (daPy_py_c::checkNowWolfPowerUp()) {
f32 ambRate = g_env_light.bg_amb_col[0].r / 255.0f;
f32 col = (((flower->field_0x04 & 0x1F) * 2 + 0x10));
amb.r = col * (ambRate * 4.0f);
ambRate = g_env_light.bg_amb_col[0].g / 255.0f;
f32 col2 = (((flower->field_0x04 >> 5) & 0x1F) * 2 + 0x10);
amb.g = col2 * (4.0f * ambRate);
ambRate = g_env_light.bg_amb_col[0].b / 255.0f;
f32 col3 = (((flower->field_0x04 >> 10) & 0x1F) * 2 + 0x10);
amb.b = col3 * (4.0f * ambRate);
}
if (amb.r == 0x3E) {
amb.r = tevstr->AmbCol.r;
}
if (amb.g == 0x3E) {
amb.g = tevstr->AmbCol.g;
}
if (amb.b == 0x3E) {
amb.b = tevstr->AmbCol.b;
}
return amb;
}
static GXColor hana01_amb_color(int idx, const dKy_tevstr_c* tevstr) {
f32 rRate = tevstr->AmbCol.r * 0.03125f;
if (rRate > 1.0f) {
rRate = 1.0f;
}
f32 gRate = tevstr->AmbCol.g * 0.03125f;
if (gRate > 1.0f) {
gRate = 1.0f;
}
f32 bRate = tevstr->AmbCol.b * 0.03125f;
if (bRate > 1.0f) {
bRate = 1.0f;
}
GXColor amb = {1, 1, 1, 1};
GXColor sub;
sub.r = -0.4f * tevstr->AmbCol.r * rRate;
sub.g = -0.4f * tevstr->AmbCol.g * gRate;
sub.b = -0.4f * tevstr->AmbCol.b * bRate;
switch (idx & 7) {
case 0:
amb.r = tevstr->AmbCol.r + sub.r;
amb.g = tevstr->AmbCol.g;
amb.b = tevstr->AmbCol.b;
break;
case 1:
amb.r = tevstr->AmbCol.r;
amb.g = tevstr->AmbCol.g + sub.g;
amb.b = tevstr->AmbCol.b;
break;
case 2:
amb.r = tevstr->AmbCol.r;
amb.g = tevstr->AmbCol.g;
amb.b = tevstr->AmbCol.b + sub.b;
break;
case 3:
amb.r = tevstr->AmbCol.r + sub.r;
amb.g = tevstr->AmbCol.g + sub.g;
amb.b = tevstr->AmbCol.b;
break;
case 4:
amb.r = tevstr->AmbCol.r;
amb.g = tevstr->AmbCol.g + sub.g;
amb.b = tevstr->AmbCol.b + sub.b;
break;
case 5:
amb.r = tevstr->AmbCol.r + sub.r;
amb.g = tevstr->AmbCol.g;
amb.b = tevstr->AmbCol.b + sub.b;
break;
case 6:
amb.r = tevstr->AmbCol.r + sub.r;
amb.g = tevstr->AmbCol.g + sub.g;
amb.b = tevstr->AmbCol.b + sub.b;
break;
case 7:
break;
}
if (daPy_py_c::checkNowWolfPowerUp()) {
f32 ambRate = g_env_light.bg_amb_col[0].r / 255.0f;
amb.r = (amb.r + 8) * (6.0f * ambRate);
ambRate = g_env_light.bg_amb_col[0].g / 255.0f;
amb.g = (amb.g + 8) * (6.0f * ambRate);
ambRate = g_env_light.bg_amb_col[0].b / 255.0f;
amb.b = (amb.b + 8) * (6.0f * ambRate);
}
amb.a = 0xFF;
return amb;
}
static void flower_emit(const dusk::batch::LeafTemplate& tpl, const Vec* xformedPos, GXColor amb) {
for (u32 i = 0; i < tpl.vtxCount; i++) {
const dusk::batch::LeafTemplate::Vtx& v = tpl.vtx[i];
const Vec& p = xformedPos[v.pos];
GXPosition3f32(p.x, p.y, p.z);
GXNormal1x8(v.nrm);
GXColor1x8(v.clr);
GXColor4u8(amb.r, amb.g, amb.b, amb.a);
GXTexCoord1x8(v.tex);
}
}
void dFlower_packet_c::draw() {
ZoneScoped;
dScnKy_env_light_c* kankyo = dKy_getEnvlight();
j3dSys.reinitGX();
GXSetNumIndStages(0);
dKy_setLight_again();
GXClearVtxDesc();
GXSetVtxDesc(GX_VA_POS, GX_INDEX8);
GXSetVtxDesc(GX_VA_NRM, GX_INDEX8);
GXSetVtxDesc(GX_VA_CLR0, GX_INDEX8);
GXSetVtxDesc(GX_VA_TEX0, GX_INDEX8);
GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_NRM, GX_NRM_XYZ, GX_F32, 0);
GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);
GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_NRM, GX_NRM_XYZ, GX_F32, 0);
GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_CLR1, GX_CLR_RGBA, GX_RGBA8, 0);
GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);
GXSETARRAY(GX_VA_POS, &l_flowerPos, sizeof(l_flowerPos), sizeof(Vec), true);
GXSETARRAY(GX_VA_NRM, &l_flowerNormal, sizeof(l_flowerNormal), sizeof(Vec), true);
GXSETARRAY(GX_VA_CLR0, &l_flowerColor, sizeof(l_flowerColor), sizeof(GXColor), true);
GXSETARRAY(GX_VA_TEX0, &l_flowerTexCoord, sizeof(l_flowerTexCoord), 8, true);
static GXVtxDescList vtxDescList[] = {
{GX_VA_POS, GX_DIRECT},
{GX_VA_NRM, GX_INDEX8},
{GX_VA_CLR0, GX_INDEX8},
{GX_VA_CLR1, GX_DIRECT},
{GX_VA_TEX0, GX_INDEX8},
{GX_VA_NULL, GX_NONE},
};
static Vec xfPos[256];
Mtx identity;
MTXIdentity(identity);
// --- hana00 ---
for (int i = 0; i < 64; i++) {
dFlower_data_c* first = m_room[i].getData();
if (first == nullptr || !dComIfGp_roomControl_checkStatusFlag(i, 0x10)) {
continue;
}
dKy_tevstr_c* tevstr = dComIfGp_roomControl_getTevStr(i);
int lightCount = 6;
if (dComIfGp_roomControl_getStatusRoomDt(i) != nullptr) {
lightCount = dComIfGp_roomControl_getStatusRoomDt(i)->getLightVecInfoNum();
}
if (dKy_SunMoon_Light_Check() && lightCount < 2) {
lightCount = 2;
}
for (int j = 0; j < 6; j++) {
if (kankyo->field_0x0c18[j].field_0x26 == 1) {
lightCount++;
}
}
if (lightCount <= 2) {
GXCallDisplayList(l_matLight4DL, 0x80);
} else {
GXCallDisplayList(l_matDL, 0x80);
}
GXSetTevColorS10(GX_TEVREG0, {0, 0, 0, 0});
dKy_Global_amb_set(tevstr);
dKy_GxFog_tevstr_set(tevstr);
dKy_setLight_nowroom_grass(tevstr->room_no, 1.0f);
GXLoadTexObj(&mTexObj_l_J_Ohana00_64TEX, GX_TEXMAP0);
batch_setup_tev(lightCount <= 2 ? (GX_LIGHT1 | GX_LIGHT2 | GX_LIGHT3 | GX_LIGHT4) :
(GX_LIGHT1 | GX_LIGHT2 | GX_LIGHT3 | GX_LIGHT4 |
GX_LIGHT5 | GX_LIGHT6 | GX_LIGHT7));
GXSetVtxDescv(vtxDescList);
GXLoadPosMtxImm(identity, GX_PNMTX0);
GXLoadNrmMtxImm(j3dSys.getViewMtx(), 0);
for (int bucket = 0; bucket < 2; bucket++) {
const bool cut = bucket != 0;
const dusk::batch::LeafTemplate& tpl = cut ? mTplHana00Cut : mTplHana00;
bool open = false;
u32 emitted = 0;
for (dFlower_data_c* flower = first; flower != nullptr; flower = flower->mp_next) {
if (cLib_checkBit<u8>(flower->m_state, 4) ||
cLib_checkBit<u8>(flower->m_state, 0x40))
{
continue;
}
if ((cLib_checkBit<u8>(flower->m_state, 8) != 0) != cut) {
continue;
}
if (!open) {
GXBegin(GX_TRIANGLES, GX_VTXFMT1, GX_AUTO);
open = true;
}
split_batch(emitted, tpl.vtxCount);
Mtx interpMtx;
MtxP mtx = get_model_mtx(flower->m_modelMtx, interpMtx);
transform_positions(tpl, reinterpret_cast<Vec*>(l_flowerPos), mtx, xfPos);
flower_emit(tpl, xfPos, hana00_amb_color(flower, tevstr));
}
if (open) {
GXEnd();
}
}
}
// --- hana01 ---
GXSETARRAY(GX_VA_POS, mp_pos, sizeof(l_flowerPos2), sizeof(Vec), true);
GXSETARRAY(GX_VA_NRM, &l_flowerNormal2, sizeof(l_flowerNormal2), sizeof(Vec), true);
GXSETARRAY(GX_VA_CLR0, mp_colors, sizeof(l_flowerColor2), sizeof(GXColor), true);
GXSETARRAY(GX_VA_TEX0, mp_texCoords, sizeof(l_flowerTexCoord2), 8, true);
for (int i = 0; i < 64; i++) {
dFlower_data_c* first = m_room[i].getData();
if (first == NULL) {
continue;
}
dKy_tevstr_c* tevstr = dComIfGp_roomControl_getTevStr(i);
int lightCount = 6;
if (dComIfGp_roomControl_getStatusRoomDt(i) != NULL) {
lightCount = dComIfGp_roomControl_getStatusRoomDt(i)->getLightVecInfoNum();
}
#if DEBUG
if (g_kankyoHIO.light.m_HOSTIO_setting != 0) {
lightCount = g_kankyoHIO.dungeonLight.usedLights;
}
#endif
if (dKy_SunMoon_Light_Check() == TRUE && lightCount < 2) {
lightCount = 2;
}
if (lightCount <= 2) {
GXCallDisplayList(mp_mat2Light4DL, m_mat2Light4DL_size);
} else {
GXCallDisplayList(mp_mat2DL, m_mat2DL_size);
}
GXSetTevColorS10(GX_TEVREG0, {0, 0, 0, 0});
dKy_Global_amb_set(tevstr);
dKy_GxFog_tevstr_set(tevstr);
dKy_setLight_nowroom_grass(tevstr->room_no, 1.0f);
GXLoadTexObj(&mTexObj_l_J_Ohana01_64128_0419TEX, GX_TEXMAP0);
batch_setup_tev(lightCount <= 2 ? (GX_LIGHT1 | GX_LIGHT2 | GX_LIGHT3 | GX_LIGHT4) :
(GX_LIGHT1 | GX_LIGHT2 | GX_LIGHT3 | GX_LIGHT4 |
GX_LIGHT5 | GX_LIGHT6 | GX_LIGHT7));
GXSetVtxDescv(vtxDescList);
GXLoadPosMtxImm(identity, GX_PNMTX0);
GXLoadNrmMtxImm(j3dSys.getViewMtx(), 0);
const dusk::batch::LeafTemplate* const buckets[3] = {
&mTplHana01, &mTplHana01Cut00, &mTplHana01Cut};
for (int bucket = 0; bucket < 3; bucket++) {
const dusk::batch::LeafTemplate& tpl = *buckets[bucket];
bool open = false;
u32 emitted = 0;
int idx = 0;
for (dFlower_data_c* flower = first; flower != NULL; flower = flower->mp_next, idx++) {
if (cLib_checkBit<u8>(flower->m_state, 4) ||
!cLib_checkBit<u8>(flower->m_state, 0x40))
{
continue;
}
const int flowerBucket = cLib_checkBit<u8>(flower->m_state, 8) ? 2 :
cLib_checkBit<u8>(flower->m_state, 0x10) ? 1 :
0;
if (flowerBucket != bucket) {
continue;
}
if (!open) {
GXBegin(GX_TRIANGLES, GX_VTXFMT1, GX_AUTO);
open = true;
}
split_batch(emitted, tpl.vtxCount);
Mtx interpMtx;
MtxP mtx = get_model_mtx(flower->m_modelMtx, interpMtx);
transform_positions(tpl, mp_pos, mtx, xfPos);
flower_emit(tpl, xfPos, hana01_amb_color(idx, tevstr));
}
if (open) {
GXEnd();
}
}
}
GXSetNumTevStages(1);
GXSetNumChans(1);
J3DShape::resetVcdVatCache();
}
#else
void dFlower_packet_c::draw() {
ZoneScoped;
dScnKy_env_light_c* kankyo = dKy_getEnvlight();
@@ -886,6 +1263,7 @@ void dFlower_packet_c::draw() {
J3DShape::resetVcdVatCache();
}
#endif
void dFlower_packet_c::calc() {
dFlower_anm_c* anm_p = getAnm();
+356
View File
@@ -512,11 +512,366 @@ dGrass_packet_c::dGrass_packet_c() {
m_Mkusa_9q_cDL_size = 0xC0;
field_0x1d714 = 0;
#if TARGET_PC
dusk::batch::decode_leaf_template(mp_Mkusa_9q_DL, m_Mkusa_9q_DL_size, mTplKusa9q);
dusk::batch::decode_leaf_template(mp_Mkusa_9q_cDL, m_Mkusa_9q_cDL_size, mTplKusa9qCut);
dusk::batch::decode_leaf_template(l_M_TenGusaDL, 0xC0, mTplTengusa);
#endif
OS_REPORT("草群メモリ=%f\n", 117.7734375f);
m_deleteRoom = &dGrass_packet_c::deleteRoom;
}
#if TARGET_PC
static MtxP get_model_mtx(Mtx modelMtx, Mtx storage) {
if (dusk::frame_interp::lookup_replacement(modelMtx, storage)) {
cMtx_concat(j3dSys.getViewMtx(), storage, storage);
return storage;
}
return modelMtx;
}
static void transform_positions(
const dusk::batch::LeafTemplate& tpl, const Vec* posArray, const Mtx mtx, Vec* xfPos) {
for (u32 i = 0; i < tpl.posRefCount; i++) {
const u8 idx = tpl.posRefs[i];
MTXMultVec(mtx, &posArray[idx], &xfPos[idx]);
}
}
static void split_batch(u32& emitted, u32 vtxCount) {
if (emitted + vtxCount > 0xFFFF) {
GXEnd();
GXBegin(GX_TRIANGLES, GX_VTXFMT1, GX_AUTO);
emitted = 0;
}
emitted += vtxCount;
}
static GXColor blade_amb_color(const dGrass_data_c* blade, const dKy_tevstr_c* tevstr) {
GXColor amb;
amb.a = 0;
#if DEBUG
if (g_kankyoHIO.navy.grass_adjust_ON) {
amb.r = g_kankyoHIO.navy.grass_ambcol.r * 2;
amb.g = g_kankyoHIO.navy.grass_ambcol.g * 2;
amb.b = g_kankyoHIO.navy.grass_ambcol.b * 2;
return amb;
}
#endif
amb.r = (blade->m_addCol & 0x1F) * 2;
amb.g = ((blade->m_addCol >> 5) & 0x1F) * 2;
amb.b = ((blade->m_addCol >> 0xA) & 0x1F) * 2;
if (daPy_py_c::checkNowWolfPowerUp()) {
f32 ambRate = g_env_light.bg_amb_col[0].r / 255.0f;
f32 col = (((blade->m_addCol & 0x1F) * 2 + 0x10));
amb.r = col * (ambRate * 4.0f);
ambRate = g_env_light.bg_amb_col[0].g / 255.0f;
f32 col2 = (((blade->m_addCol >> 5) & 0x1F) * 2 + 0x10);
amb.g = col2 * (4.0f * ambRate);
ambRate = g_env_light.bg_amb_col[0].b / 255.0f;
f32 col3 = (((blade->m_addCol >> 10) & 0x1F) * 2 + 0x10);
amb.b = col3 * (4.0f * ambRate);
}
f32 roomAmbScale = 1.0f - (static_cast<int>(blade->m_pos.x) & 0xFF) * 0.001953125f;
f32 colScale = 1.1f - (static_cast<u8>(static_cast<int>(blade->m_pos.x)) & 0xFF) / 2000.0f;
colScale -= (static_cast<int>(blade->m_pos.z) & 0xFF) / 2000.0f;
if (colScale > 1.0f) {
colScale = 1.0f;
}
if (amb.r == 0x3E) {
amb.r = tevstr->AmbCol.r * roomAmbScale;
} else {
amb.r = amb.r * colScale;
}
if (amb.g == 0x3E) {
amb.g = tevstr->AmbCol.g * roomAmbScale;
} else {
amb.g = amb.g * colScale;
}
if (amb.b == 0x3E) {
amb.b = tevstr->AmbCol.b * roomAmbScale;
} else {
amb.b = amb.b * colScale;
}
return amb;
}
static void blade_emit(const dusk::batch::LeafTemplate& tpl, const Vec* xformedPos,
const GXColor* colors, GXColor amb) {
for (u32 i = 0; i < tpl.vtxCount; i++) {
const dusk::batch::LeafTemplate::Vtx& v = tpl.vtx[i];
const Vec& p = xformedPos[v.pos];
GXPosition3f32(p.x, p.y, p.z);
GXNormal1x8(v.nrm);
GXColor4u8(amb.r, amb.g, amb.b, colors[v.clr].a);
GXTexCoord1x8(v.tex);
}
}
void dGrass_packet_c::draw() {
ZoneScoped;
dScnKy_env_light_c* kankyo = dKy_getEnvlight();
j3dSys.reinitGX();
GXSetNumIndStages(0);
dKy_setLight_again();
GXClearVtxDesc();
static GXVtxDescList l_vtxDescList[] = {
{GX_VA_POS, GX_INDEX8},
{GX_VA_NRM, GX_INDEX8},
{GX_VA_CLR0, GX_INDEX8},
{GX_VA_TEX0, GX_INDEX8},
{GX_VA_NULL, GX_NONE},
};
static GXVtxDescList l_batchVtxDescList[] = {
{GX_VA_POS, GX_DIRECT},
{GX_VA_NRM, GX_INDEX8},
{GX_VA_CLR0, GX_DIRECT},
{GX_VA_TEX0, GX_INDEX8},
{GX_VA_NULL, GX_NONE},
};
GXSetVtxDescv(l_vtxDescList);
GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_NRM, GX_NRM_XYZ, GX_F32, 0);
GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);
GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_NRM, GX_NRM_XYZ, GX_F32, 0);
GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);
GXSETARRAY(GX_VA_POS, mp_pos, sizeof(l_pos), sizeof(Vec), true);
GXSETARRAY(GX_VA_NRM, mp_normal, sizeof(l_normal), sizeof(Vec), true);
GXSETARRAY(GX_VA_CLR0, mp_colors, sizeof(l_color), sizeof(GXColor), true);
GXSETARRAY(GX_VA_TEX0, mp_texCoords, sizeof(l_texCoord), 8, true);
GXColorS10 reg1 = {0, 0, 0, 0};
// daytime "shine" alpha curve (TEVREG1 alpha)
f32 daytime = g_env_light.getDaytime();
f32 ratio;
f32 shine;
if (daytime >= 90.0f && daytime < 135.0f) {
ratio = 1.0f - (0.022222223f * (135.0f - daytime));
shine = 100.0f - (18.0f * ratio);
} else if (daytime >= 135.0f && daytime < 225.0f) {
ratio = 1.0f - (0.011111111f * (225.0f - daytime));
shine = 82.0f - (25.0f * ratio);
} else if (daytime >= 225.0f && daytime < 270.0f) {
ratio = 1.0f - (0.022222223f * (270.0f - daytime));
shine = 57.0f - (-25.0f * ratio);
} else if (daytime >= 270.0f && daytime < 315.0f) {
ratio = (1.0f - (0.022222223f * (315.0f - daytime)));
shine = 82.0f - (-18.0f * ratio);
} else {
shine = 100.0f;
}
#if DEBUG
if (g_kankyoHIO.navy.grass_shine_value != 0.0f) {
shine = g_kankyoHIO.navy.grass_shine_value;
}
#endif
static Vec xfPos[256];
Mtx identity;
PSMTXIdentity(identity);
for (int i = 0; i < 64; i++) {
dGrass_data_c* first = m_room[i].getData();
if (first == NULL || !dComIfGp_roomControl_checkStatusFlag(i, 0x10)) {
continue;
}
int lightCount = 6;
dKy_tevstr_c* tevstr = dComIfGp_roomControl_getTevStr(i);
f32 lightInf = g_env_light.grass_light_inf_rate * g_env_light.bg_light_influence;
lightInf += 0.5f * (1.0f - lightInf);
J3DLightInfo* lightInfo = tevstr->mLights[0].getLightInfo();
reg1.r = lightInfo->mColor.r * lightInf;
reg1.g = lightInfo->mColor.g * lightInf;
reg1.b = lightInfo->mColor.b * lightInf;
reg1.a = shine;
if (memcmp(dComIfGp_getStartStageName(), "D_MN01", 6) == 0) {
reg1.r = 0;
reg1.g = 0x1E;
reg1.b = 5;
reg1.a = 0x50;
}
GFSetTevColorS10(GX_TEVREG1, reg1);
if (dComIfGp_roomControl_getStatusRoomDt(i) != nullptr) {
lightCount = dComIfGp_roomControl_getStatusRoomDt(i)->getLightVecInfoNum();
}
#if DEBUG
if (g_kankyoHIO.light.m_HOSTIO_setting != 0) {
lightCount = g_kankyoHIO.dungeonLight.usedLights;
}
#endif
if (dKy_SunMoon_Light_Check() == TRUE && lightCount < 2) {
lightCount = 2;
}
for (int j = 0; j < 6; j++) {
if (kankyo->field_0x0c18[j].field_0x26 == 1) {
lightCount++;
}
}
// room-level setup
if (first->field_0x05 <= 3 || first->field_0x05 >= 10) {
GXLoadTexObj(&mTexObj_l_M_kusa05_RGBATEX, GX_TEXMAP0);
if (lightCount <= 3) {
GXCallDisplayList(mp_kusa9q_14_DL, m_kusa9q_DL_14_size);
} else {
GXCallDisplayList(mp_kusa9q_DL, m_kusa9q_DL_size);
}
} else {
GXLoadTexObj(&mTexObj_l_M_Hijiki00TEX, GX_TEXMAP0);
GXCallDisplayList(l_Tengusa_matDL, 0xA0);
}
GFSetTevColorS10(GX_TEVREG2, {0, 0, 0, 0});
dKy_Global_amb_set(tevstr);
dKy_GfFog_tevstr_set(tevstr);
dKy_setLight_nowroom_grass(tevstr->room_no, 0.0f);
GXSetVtxDescv(l_batchVtxDescList);
GXLoadPosMtxImm(identity, GX_PNMTX0);
GXLoadNrmMtxImm(j3dSys.getViewMtx(), 0);
// buckets: (kusa05 vs tengusa) x (standing vs cut)
bool hasRegrowing = false;
for (int bucket = 0; bucket < 4; bucket++) {
const bool kusaTex = bucket < 2;
const bool cut = (bucket & 1) != 0;
const dusk::batch::LeafTemplate& tpl =
cut ? mTplKusa9qCut : (kusaTex ? mTplKusa9q : mTplTengusa);
bool open = false;
u32 emitted = 0;
for (dGrass_data_c* blade = first; blade != NULL; blade = blade->mp_next) {
if (cLib_checkBit<u8>(blade->field_0x01, 2)) {
continue; // clipped
}
if (blade->field_0x02 < -1) {
hasRegrowing = true;
continue;
}
const bool bladeKusaTex = blade->field_0x05 <= 3 || blade->field_0x05 >= 10;
if (bladeKusaTex != kusaTex || (blade->field_0x02 < 0) != cut) {
continue;
}
if (!open) {
if (kusaTex) {
GXLoadTexObj(&mTexObj_l_M_kusa05_RGBATEX, GX_TEXMAP0);
if (lightCount <= 2) {
GXCallDisplayList(mp_kusa9q_14_DL, m_kusa9q_DL_14_size);
} else {
GXCallDisplayList(mp_kusa9q_DL, m_kusa9q_DL_size);
}
} else {
GXLoadTexObj(&mTexObj_l_M_Hijiki00TEX, GX_TEXMAP0);
GXCallDisplayList(l_Tengusa_matDL, 0xA0);
}
// change amb_src to GX_SRC_VTX
const u32 lightMask =
(kusaTex && lightCount <= 2)
? (GX_LIGHT1 | GX_LIGHT2 | GX_LIGHT3 | GX_LIGHT4)
: (GX_LIGHT1 | GX_LIGHT2 | GX_LIGHT3 | GX_LIGHT4 | GX_LIGHT5 |
GX_LIGHT6 | GX_LIGHT7);
GXSetChanCtrl(GX_COLOR0, GX_TRUE, GX_SRC_VTX, GX_SRC_REG, lightMask,
GX_DF_CLAMP, GX_AF_SPOT);
reg1.a = cut ? 0 : shine;
GFSetTevColorS10(GX_TEVREG1, reg1);
GXBegin(GX_TRIANGLES, GX_VTXFMT1, GX_AUTO);
open = true;
}
split_batch(emitted, tpl.vtxCount);
Mtx interpMtx;
MtxP mtx = get_model_mtx(blade->m_modelMtx, interpMtx);
transform_positions(tpl, mp_pos, mtx, xfPos);
blade_emit(tpl, xfPos, mp_colors, blade_amb_color(blade, tevstr));
}
if (open) {
GXEnd();
}
}
// regrowing blades have per-blade TEVREG2 alpha
// draw them with the original immediate path
if (hasRegrowing) {
GXSetVtxDescv(l_vtxDescList);
for (dGrass_data_c* blade = first; blade != NULL; blade = blade->mp_next) {
if (blade->field_0x02 >= -1 || cLib_checkBit<u8>(blade->field_0x01, 2)) {
continue;
}
const bool kusaTex = blade->field_0x05 <= 3 || blade->field_0x05 >= 10;
if (kusaTex) {
GXLoadTexObj(&mTexObj_l_M_kusa05_RGBATEX, GX_TEXMAP0);
if (lightCount <= 2) {
GXCallDisplayList(mp_kusa9q_14_DL, m_kusa9q_DL_14_size);
} else {
GXCallDisplayList(mp_kusa9q_DL, m_kusa9q_DL_size);
}
} else {
GXLoadTexObj(&mTexObj_l_M_Hijiki00TEX, GX_TEXMAP0);
GXCallDisplayList(l_Tengusa_matDL, 0xA0);
}
reg1.a = 0;
GFSetTevColorS10(GX_TEVREG1, reg1);
GXSetChanAmbColor(GX_COLOR0A0, blade_amb_color(blade, tevstr));
Mtx modelMtx;
GXLoadPosMtxImm(get_model_mtx(blade->m_modelMtx, modelMtx), GX_PNMTX0);
GXLoadNrmMtxImm(j3dSys.getViewMtx(), 0);
GFSetTevColorS10(GX_TEVREG2,
{0, 0, 0, static_cast<s16>(-0x100 - (blade->field_0x02 << 8) / 40)});
if (blade->field_0x02 != -2) {
if (kusaTex) {
GXCallDisplayList(mp_Mkusa_9q_DL, m_Mkusa_9q_DL_size);
} else {
GXCallDisplayList(l_M_TenGusaDL, 0xC0);
}
} else {
GXCallDisplayList(mp_Mkusa_9q_cDL, m_Mkusa_9q_cDL_size);
}
GFSetTevColorS10(GX_TEVREG2, {0, 0, 0, 0});
}
}
}
J3DShape::resetVcdVatCache();
}
#else
void dGrass_packet_c::draw() {
ZoneScoped;
dScnKy_env_light_c* kankyo = dKy_getEnvlight();
@@ -811,6 +1166,7 @@ void dGrass_packet_c::draw() {
J3DShape::resetVcdVatCache();
}
#endif
void dGrass_packet_c::calc() {
cXyz* temp_r29 = dKyw_get_wind_vec();
+42 -7
View File
@@ -34,6 +34,7 @@
#include "dusk/action_bindings.h"
#include "dusk/mouse.h"
#include "dusk/settings.h"
#include "dusk/touch_camera.h"
#include "imgui.h"
#endif
@@ -7499,6 +7500,15 @@ static constexpr s16 FLYCAM_ROLL_SPEED = 256;
static ImVec2 sFlyCamLastMousePos = {-1.f, -1.f};
#if TARGET_PC
static constexpr f32 TOUCH_CAMERA_CSTICK_EXIT_THRESHOLD = 0.05f;
static bool sTouchFreeCameraActive = false;
bool dCamera_c::isAimActive() {
auto* link = daAlink_getAlinkActorClass();
return link != nullptr && link->checkAimInputContext() &&
dComIfGp_checkCameraAttentionStatus(link->field_0x317c, 0x10);
}
bool dCamera_c::executeDebugFlyCam() {
if (!dusk::getSettings().game.debugFlyCam) {
if (mDebugFlyCam.initialized) {
@@ -7640,16 +7650,30 @@ void dCamera_c::deactivateDebugFlyCam() {
mDebugFlyCam.initialized = false;
}
bool dCamera_c::canUseFreeCam() {
return dusk::getSettings().game.freeCamera || dusk::getSettings().game.enableMouseCamera;
}
bool dCamera_c::freeCamera() {
if (canUseFreeCam() && mGear == 1) {
f32 touchYawDp = 0.0f;
f32 touchPitchDp = 0.0f;
bool touchCameraMoved = false;
const bool touchControlsEnabled = dusk::getSettings().game.enableTouchControls;
if (touchControlsEnabled && !isAimActive()) {
touchCameraMoved = dusk::touch_camera::consume_delta(touchYawDp, touchPitchDp);
}
if (!touchControlsEnabled ||
mPadInfo.mCStick.mLastValue > TOUCH_CAMERA_CSTICK_EXIT_THRESHOLD)
{
sTouchFreeCameraActive = false;
}
if (touchCameraMoved) {
sTouchFreeCameraActive = true;
}
const bool useFreeCamera = dusk::getSettings().game.freeCamera ||
dusk::getSettings().game.enableMouseCamera || sTouchFreeCameraActive;
if (useFreeCamera && mGear == 1) {
mGear = 0;
}
if (!canUseFreeCam() || mCamStyle == 70)
if (!useFreeCamera || mCamStyle == 70)
{
mCamParam.mManualMode = 0;
return false;
@@ -7660,6 +7684,17 @@ bool dCamera_c::freeCamera() {
mCamParam.freeYAngle = mViewCache.mDirection.mInclination.Degree();
}
if (touchCameraMoved) {
mCamParam.mManualMode = 1;
const f32 yawInput = dusk::getSettings().game.invertCameraXAxis ? -touchYawDp : touchYawDp;
const f32 pitchInput =
touchPitchDp * (dusk::getSettings().game.invertCameraYAxis ? -1.0f : 1.0f);
mCamParam.freeXAngle += yawInput * dusk::getSettings().game.touchCameraXSensitivity *
dusk::touch_camera::YAW_DEGREES_PER_DP;
mCamParam.freeYAngle += pitchInput * dusk::getSettings().game.touchCameraYSensitivity *
dusk::touch_camera::PITCH_DEGREES_PER_DP;
}
cXyz camMovement = {mPadInfo.mCStick.mLastPosX, mPadInfo.mCStick.mLastPosY, 0.0f};
f32 magnitude = sqrt(mPadInfo.mCStick.mLastPosX * mPadInfo.mCStick.mLastPosX + mPadInfo.mCStick.mLastPosY * mPadInfo.mCStick.mLastPosY);
@@ -11359,7 +11394,7 @@ static int camera_execute(camera_process_class* i_this) {
const auto target = get_target_trim_height(i_this);
const auto step = dusk::frame_interp::get_interpolation_step();
const auto cur = camera->TrimHeight();
const auto prev = (4.0f * cur - target) / 3.0f;
const auto prev = (4.0f * cur - target) / 3.0f;
const auto trim_height = prev + (cur - prev) * step;
widezoom_correction(i_this, trim_height);
+314
View File
@@ -23,8 +23,22 @@
#include "m_Do/m_Do_graphic.h"
#include <cstring>
#if TARGET_PC
#include "dusk/menu_pointer.h"
#include "dusk/string.hpp"
namespace {
constexpr u8 pointer_target(u8 group, u8 index) noexcept {
return static_cast<u8>((group << 4) | (index & 0x0F));
}
constexpr u8 s_pointerDataSelectTarget = 0;
constexpr u8 s_pointerMenuSelectTarget = 1;
constexpr u8 s_pointerCopySelectTarget = 2;
constexpr u8 s_pointerYesNoSelectTarget = 3;
} // namespace
#endif
static s32 SelStartFrameTbl[3] = {
59,
99,
@@ -756,8 +770,143 @@ void dFile_select_c::dataSelectInit() {
}
}
#if TARGET_PC
bool dFile_select_c::pointerDataSelect() {
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::FileSelect);
for (u8 i = 0; i < 3; ++i) {
if (!dusk::menu_pointer::hit_pane(mSelFilePanes[i], 8.0f)) {
continue;
}
const bool clicked = dusk::menu_pointer::consume_click();
if (mSelectNum != i) {
mDoAud_seStart(Z2SE_FILE_SELECT_CURSOR, NULL, 0, 0);
mLastSelectNum = mSelectNum;
mSelectNum = i;
if (clicked) {
dusk::menu_pointer::defer_activation(
dusk::menu_pointer::Context::FileSelect,
pointer_target(s_pointerDataSelectTarget, i));
}
dataSelectAnmSet();
mDataSelProc = DATASELPROC_DATA_SELECT_MOVE_ANIME;
return true;
}
if (clicked) {
dataSelectStart();
return true;
}
}
return false;
}
bool dFile_select_c::pointerMenuSelect() {
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::FileSelect);
for (u8 i = 0; i < 3; ++i) {
if (!dusk::menu_pointer::hit_pane(m3mSelPane[i], 8.0f)) {
continue;
}
const bool clicked = dusk::menu_pointer::consume_click();
if (!mIsDataNew[mSelectNum] && mSelectMenuNum != i) {
mDoAud_seStart(Z2SE_SY_MENU_CURSOR_COMMON, NULL, 0, 0);
mLastSelectMenuNum = mSelectMenuNum;
mSelectMenuNum = i;
if (clicked) {
dusk::menu_pointer::defer_activation(
dusk::menu_pointer::Context::FileSelect,
pointer_target(s_pointerMenuSelectTarget, i));
}
menuSelectAnmSet();
mDataSelProc = DATASELPROC_MENU_SELECT_MOVE_ANM;
return true;
}
if (clicked) {
menuSelectStart();
return true;
}
}
return false;
}
bool dFile_select_c::pointerCopyDataToSelect() {
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::FileSelect);
for (u8 i = 0; i < 2; ++i) {
if (!dusk::menu_pointer::hit_pane(mCpSelPane[i], 8.0f)) {
continue;
}
const bool clicked = dusk::menu_pointer::consume_click();
if (field_0x026b != i) {
mDoAud_seStart(Z2SE_FILE_SELECT_CURSOR, NULL, 0, 0);
field_0x026c = field_0x026b;
field_0x026b = i;
if (clicked) {
dusk::menu_pointer::defer_activation(
dusk::menu_pointer::Context::FileSelect,
pointer_target(s_pointerCopySelectTarget, i));
}
copyDataToSelectMoveAnmSet();
mDataSelProc = DATASELPROC_COPY_DATA_TO_SELECT_MOVE_ANM;
return true;
}
if (clicked) {
copyDataToSelectStart();
return true;
}
}
return false;
}
bool dFile_select_c::pointerYesNoSelect(bool errorSelect) {
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::FileSelect);
for (u8 i = 0; i < 2; ++i) {
if (!dusk::menu_pointer::hit_pane(mYnSelPane[i], 8.0f)) {
continue;
}
const bool clicked =
(!errorSelect || field_0x0268 == i) && dusk::menu_pointer::consume_click();
if (field_0x0268 != i) {
field_0x0269 = field_0x0268;
field_0x0268 = i;
if (errorSelect) {
errCurMove(0);
return false;
} else {
mDoAud_seStart(Z2SE_SY_MENU_CURSOR_COMMON, NULL, 0, 0);
if (clicked) {
dusk::menu_pointer::defer_activation(
dusk::menu_pointer::Context::FileSelect,
pointer_target(s_pointerYesNoSelectTarget, i));
}
yesnoSelectAnmSet();
mDataSelProc = DATASELPROC_YES_NO_CURSOR_MOVE_ANM;
return true;
}
}
if (clicked) {
if (errorSelect) {
if (field_0x0268 != 0) {
mDoAud_seStart(Z2SE_SY_CURSOR_OK, 0, 0, 0);
} else {
mDoAud_seStart(Z2SE_SY_CURSOR_CANCEL, 0, 0, 0);
}
mSelIcon->setAlphaRate(0.0f);
} else {
yesNoSelectStart();
}
return true;
}
}
return false;
}
#endif
// handles switching between quest logs
void dFile_select_c::dataSelect() {
#if TARGET_PC
if (pointerDataSelect()) {
return;
}
#endif
stick->checkTrigger();
// If A or Start was pressed
@@ -801,6 +950,9 @@ static u16 msgTbl[3] = {
};
void dFile_select_c::dataSelectStart() {
#if TARGET_PC
dusk::menu_pointer::clear_deferred_activation(dusk::menu_pointer::Context::FileSelect);
#endif
mSelIcon->setAlphaRate(0.0f);
if (mIsNoData[mSelectNum]) {
@@ -949,6 +1101,16 @@ void dFile_select_c::dataSelectAnmSet() {
}
void dFile_select_c::dataSelectMoveAnime() {
#if TARGET_PC
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::FileSelect);
if (mSelectNum != 0xFF && dusk::menu_pointer::hit_pane(mSelFilePanes[mSelectNum], 8.0f) &&
dusk::menu_pointer::consume_click())
{
dusk::menu_pointer::defer_activation(
dusk::menu_pointer::Context::FileSelect,
pointer_target(s_pointerDataSelectTarget, mSelectNum));
}
#endif
bool iVar7 = true;
bool iVar6 = true;
bool bVar1 = true;
@@ -997,6 +1159,14 @@ void dFile_select_c::dataSelectMoveAnime() {
mSelFilePanes[mLastSelectNum]->getPanePtr()->setAnimation((J2DAnmTransform*)NULL);
}
#if TARGET_PC
if (dusk::menu_pointer::consume_deferred_activation(
dusk::menu_pointer::Context::FileSelect,
pointer_target(s_pointerDataSelectTarget, mSelectNum))) {
dataSelectStart();
return;
}
#endif
mDataSelProc = DATASELPROC_DATA_SELECT;
}
}
@@ -1161,6 +1331,12 @@ void dFile_select_c::selectDataOpenEraseMove() {
// Handles selecting between copy / start / delete menus in quest log
void dFile_select_c::menuSelect() {
#if TARGET_PC
if (pointerMenuSelect()) {
return;
}
#endif
stick->checkTrigger();
// if a was pressed, do the menu selection process
@@ -1191,6 +1367,9 @@ void dFile_select_c::menuSelect() {
// Handles copy / start / delete actions depending on which menu is selected from menuSelect
void dFile_select_c::menuSelectStart() {
#if TARGET_PC
dusk::menu_pointer::clear_deferred_activation(dusk::menu_pointer::Context::FileSelect);
#endif
#if TARGET_PC
if (!dusk::getSettings().game.hideTvSettingsScreen || mSelectMenuNum != 1) {
mDoAud_seStart(Z2SE_SY_CURSOR_OK, NULL, 0, 0);
@@ -1312,6 +1491,17 @@ void dFile_select_c::menuSelectAnmSet() {
}
void dFile_select_c::menuSelectMoveAnm() {
#if TARGET_PC
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::FileSelect);
if (mSelectMenuNum != 0xFF &&
dusk::menu_pointer::hit_pane(m3mSelPane[mSelectMenuNum], 8.0f) &&
dusk::menu_pointer::consume_click())
{
dusk::menu_pointer::defer_activation(
dusk::menu_pointer::Context::FileSelect,
pointer_target(s_pointerMenuSelectTarget, mSelectMenuNum));
}
#endif
bool tmp1 = true;
if (mSelectMenuNum != 0xFF &&
@@ -1369,6 +1559,14 @@ void dFile_select_c::menuSelectMoveAnm() {
m3mSelPane[mLastSelectMenuNum]->getPanePtr()->setAnimation((J2DAnmTransform*)NULL);
}
#if TARGET_PC
if (dusk::menu_pointer::consume_deferred_activation(
dusk::menu_pointer::Context::FileSelect,
pointer_target(s_pointerMenuSelectTarget, mSelectMenuNum))) {
menuSelectStart();
return;
}
#endif
mDataSelProc = DATASELPROC_MENU_SELECT;
}
}
@@ -1698,6 +1896,12 @@ void dFile_select_c::setSaveDataForCopySel() {
}
void dFile_select_c::copyDataToSelect() {
#if TARGET_PC
if (pointerCopyDataToSelect()) {
return;
}
#endif
stick->checkTrigger();
if (mDoCPd_c::getTrigA(PAD_1)) {
@@ -1722,6 +1926,9 @@ void dFile_select_c::copyDataToSelect() {
}
void dFile_select_c::copyDataToSelectStart() {
#if TARGET_PC
dusk::menu_pointer::clear_deferred_activation(dusk::menu_pointer::Context::FileSelect);
#endif
mDoAud_seStart(Z2SE_SY_CURSOR_OK, NULL, 0, 0);
mCpDataToNum = getCptoNum(field_0x026b);
@@ -1787,6 +1994,17 @@ void dFile_select_c::copyDataToSelectMoveAnmSet() {
}
void dFile_select_c::copyDataToSelectMoveAnm() {
#if TARGET_PC
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::FileSelect);
if (field_0x026b != 0xFF &&
dusk::menu_pointer::hit_pane(mCpSelPane[field_0x026b], 8.0f) &&
dusk::menu_pointer::consume_click())
{
dusk::menu_pointer::defer_activation(
dusk::menu_pointer::Context::FileSelect,
pointer_target(s_pointerCopySelectTarget, field_0x026b));
}
#endif
bool iVar7 = true;
bool iVar6 = true;
bool bVar1 = true;
@@ -1836,6 +2054,14 @@ void dFile_select_c::copyDataToSelectMoveAnm() {
mSelIcon2->setAlphaRate(1.0f);
}
#if TARGET_PC
if (dusk::menu_pointer::consume_deferred_activation(
dusk::menu_pointer::Context::FileSelect,
pointer_target(s_pointerCopySelectTarget, field_0x026b))) {
copyDataToSelectStart();
return;
}
#endif
mDataSelProc = DATASELPROC_COPY_DATA_TO_SELECT;
}
}
@@ -2105,6 +2331,12 @@ void dFile_select_c::yesnoCursorShow() {
}
void dFile_select_c::YesNoSelect() {
#if TARGET_PC
if (pointerYesNoSelect(false)) {
return;
}
#endif
stick->checkTrigger();
if (mDoCPd_c::getTrigA(PAD_1)) {
@@ -2129,6 +2361,9 @@ void dFile_select_c::YesNoSelect() {
}
void dFile_select_c::yesNoSelectStart() {
#if TARGET_PC
dusk::menu_pointer::clear_deferred_activation(dusk::menu_pointer::Context::FileSelect);
#endif
if (field_0x0268 != 0) {
mDoAud_seStart(Z2SE_SY_CURSOR_OK, NULL, 0, 0);
field_0x03b1 = 1;
@@ -2284,10 +2519,29 @@ void dFile_select_c::YesNoCancelMove() {
}
void dFile_select_c::yesNoCursorMoveAnm() {
#if TARGET_PC
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::FileSelect);
if (field_0x0268 != 0xFF &&
dusk::menu_pointer::hit_pane(mYnSelPane[field_0x0268], 8.0f) &&
dusk::menu_pointer::consume_click())
{
dusk::menu_pointer::defer_activation(
dusk::menu_pointer::Context::FileSelect,
pointer_target(s_pointerYesNoSelectTarget, field_0x0268));
}
#endif
bool isYnSelMove = yesnoSelectMoveAnm();
bool isYnWakuAlpha = yesnoWakuAlpahAnm(field_0x0269);
if (isYnSelMove == true && isYnWakuAlpha == true) {
yesnoCursorShow();
#if TARGET_PC
if (dusk::menu_pointer::consume_deferred_activation(
dusk::menu_pointer::Context::FileSelect,
pointer_target(s_pointerYesNoSelectTarget, field_0x0268))) {
yesNoSelectStart();
return;
}
#endif
mDataSelProc = DATASELPROC_YES_NO_SELECT;
}
}
@@ -4238,6 +4492,11 @@ static MemCardCheckFuncT MemCardCheckProc[] = {
&dFile_select_c::MemCardErrYesNoCursorMoveAnm,
&dFile_select_c::MemCardSaveDataClear,
#if TARGET_PC
&dFile_select_c::MemCardAutoMakeGameFile,
&dFile_select_c::MemCardAutoMakeGameFileErrWait,
#endif
#if PLATFORM_WII || PLATFORM_SHIELD
&dFile_select_c::nandStatCheck,
&dFile_select_c::gameFileInitSel,
@@ -4321,11 +4580,33 @@ void dFile_select_c::MemCardStatCheck() {
mDoMemCd_Load();
mCardCheckProc = MEMCARDCHECKPROC_LOAD_WAIT;
break;
#if TARGET_PC
case 1: { // no save file
if (dusk::getSettings().game.instantSaves) {
field_0x03b1 = 1;
setInitSaveData();
dataSave();
mCardCheckProc = MEMCARDCHECKPROC_AUTO_MAKE_GAMEFILE;
} else {
errDispInitSet(22, 0);
field_0x0280 = true;
mNextCardCheckProc = MEMCARDCHECKPROC_MAKE_GAMEFILE_SEL;
}
break;
}
case 4: // card is writing
if (dusk::getSettings().game.instantSaves) {
field_0x03b1 = 1;
mCardCheckProc = MEMCARDCHECKPROC_AUTO_MAKE_GAMEFILE;
}
break;
#else
case 1:
errDispInitSet(22, 0);
field_0x0280 = true;
mNextCardCheckProc = MEMCARDCHECKPROC_MAKE_GAMEFILE_SEL;
break;
#endif
}
#else
switch (status) {
@@ -5031,6 +5312,33 @@ void dFile_select_c::MemCardMakeGameFileCheck() {
}
}
#if TARGET_PC
void dFile_select_c::MemCardAutoMakeGameFile() {
field_0x03b4 = mDoMemCd_SaveSync();
if (field_0x03b4 == 0) {
return;
}
field_0x03b1 = 0;
if (field_0x03b4 == 1) {
mDoMemCd_Load();
mCardCheckProc = MEMCARDCHECKPROC_LOAD_WAIT;
} else {
errDispInitSet(0x1A, 0);
field_0x0280 = false;
mWindowCloseMsgDispCb = NULL;
mKeyWaitMsgDispCb = NULL;
mNextCardCheckProc = MEMCARDCHECKPROC_AUTO_MAKE_GAMEFILE_ERR_WAIT;
}
}
void dFile_select_c::MemCardAutoMakeGameFileErrWait() {
mNextCardCheckProc = MEMCARDCHECKPROC_STAT_CHECK;
mKeyWaitCardCheckProc = MEMCARDCHECKPROC_MSG_WINDOW_CLOSE;
mCardCheckProc = MEMCARDCHECKPROC_ERRMSG_WAIT_KEY;
}
#endif
#if PLATFORM_WII || PLATFORM_SHIELD
void dFile_select_c::gameFileInitSel() {
if (errYesNoSelect() != 0) {
@@ -5184,6 +5492,12 @@ void dFile_select_c::MemCardMsgWindowClose() {
bool dFile_select_c::errYesNoSelect() {
bool rv = false;
#if TARGET_PC
if (pointerYesNoSelect(true)) {
return true;
}
#endif
stick->checkTrigger();
if (mDoCPd_c::getTrigA(PAD_1)) {
+22
View File
@@ -1213,6 +1213,10 @@ void dMap_c::changeTextureSize(int param_1, int param_2, int param_3) {
JUT_ASSERT(2672, mImage_p != NULL);
JUT_ASSERT(2673, mResTIMG != NULL);
#if TARGET_PC
GXDestroyCopyTex(mImage_p);
#endif
mTexSizeX = param_1 >> param_3;
mTexSizeY = param_2 >> param_3;
@@ -1226,6 +1230,24 @@ void dMap_c::changeTextureSize(int param_1, int param_2, int param_3) {
}
#endif
#if TARGET_PC
bool dMap_c::refreshTextureSize() {
JUT_ASSERT(2688, mImage_p != NULL);
JUT_ASSERT(2689, mResTIMG != NULL);
const u16 oldWidth = mResTIMG->width;
const u16 oldHeight = mResTIMG->height;
makeResTIMG(mResTIMG, mTexSizeX, mTexSizeY, mImage_p, (u8*)m_res, 0x33);
if (mResTIMG->width == oldWidth && mResTIMG->height == oldHeight) {
return false;
}
GXDestroyCopyTex(mImage_p);
return true;
}
#endif
void dMap_c::_remove() {
if (mImage_p != NULL) {
#if TARGET_PC
+46 -33
View File
@@ -15,32 +15,49 @@
#include <cstring>
#ifdef TARGET_PC
#include <span>
#include <numbers>
#include <array>
#include "dusk/settings.h"
#include "m_Do/m_Do_graphic.h"
#include <dolphin/gx/GXAurora.h>
#include <aurora/math.hpp>
constexpr u16 kPreferredMapResolutionMultiplier = 4;
constexpr u32 kMaxMapRenderPixels = 4096 * 4096;
constexpr u16 kMapImageSide = 16 * kPreferredMapResolutionMultiplier;
#include <algorithm>
#include <array>
#include <cmath>
#include <functional>
#include <limits>
#include <numbers>
#include <span>
constexpr u16 kMapIconResolutionMultiplier = 4;
constexpr u16 kMapImageSide = 16 * kMapIconResolutionMultiplier;
constexpr u32 kMapImageTotalPixels = kMapImageSide * kMapImageSide;
typedef std::function<u8(size_t, size_t)> PaintI8Fn;
u16 map_resolution_multiplier(u16 width, u16 height) {
const u32 basePixels = static_cast<u32>(width) * height;
if (basePixels == 0) {
return 1;
u16 scaled_map_axis(u16 value, f32 scale) {
const auto scaledValue =
static_cast<u32>(std::max(1.0f, std::round(static_cast<f32>(value) * scale)));
return static_cast<u16>(std::min<u32>(scaledValue, std::numeric_limits<u16>::max()));
}
aurora::Vec2<u16> map_render_size_for(u16 width, u16 height) {
if (width == 0 || height == 0) {
return {width, height};
}
u16 scale = kPreferredMapResolutionMultiplier;
while (scale > 1) {
const u32 scalePixels = static_cast<u32>(scale) * scale;
if (basePixels <= kMaxMapRenderPixels / scalePixels) {
break;
}
scale--;
}
return scale;
u32 renderWidth = 0;
u32 renderHeight = 0;
AuroraGetRenderSize(&renderWidth, &renderHeight);
const f32 logicalWidth = std::max(mDoGph_gInf_c::getWidthF(), 1.0f);
const f32 logicalHeight = std::max(mDoGph_gInf_c::getHeightF(), 1.0f);
const f32 irScaleX = renderWidth > 0 ? static_cast<f32>(renderWidth) / logicalWidth : 1.0f;
const f32 irScaleY = renderHeight > 0 ? static_cast<f32>(renderHeight) / logicalHeight : 1.0f;
const f32 hudScale = std::clamp(dusk::getSettings().game.hudScale.getValue(), 0.5f, 2.0f);
return {
scaled_map_axis(width, irScaleX * hudScale),
scaled_map_axis(height, irScaleY * hudScale),
};
}
void paint_i8(std::span<u8> dst, size_t width, PaintI8Fn paint) {
@@ -496,9 +513,9 @@ void dRenderingMap_c::makeResTIMG(ResTIMG* p_image, u16 width, u16 height, u8* p
p_image->format = GX_TF_C8;
p_image->alphaEnabled = 2;
#ifdef TARGET_PC
const u16 scale = map_resolution_multiplier(width, height);
p_image->width = width * scale;
p_image->height = height * scale;
const auto [rw, rh] = map_render_size_for(width, height);
p_image->width = rw;
p_image->height = rh;
#else
p_image->width = width;
p_image->height = height;
@@ -581,16 +598,14 @@ void dRenderingFDAmap_c::drawBack() const {
void dRenderingFDAmap_c::preRenderingMap() {
#ifdef TARGET_PC
const u16 scale = map_resolution_multiplier(mTexWidth, mTexHeight);
const u16 w = mTexWidth * scale;
const u16 h = mTexHeight * scale;
GXCreateFrameBuffer(w, h);
const auto [rw, rh] = map_render_size_for(mTexWidth, mTexHeight);
GXCreateFrameBuffer(rw, rh);
// Set logical viewport dimensions
GXSetViewport(0.0f, 0.0f, mTexWidth, mTexHeight, 0.0f, 1.0f);
GXSetScissor(0, 0, mTexWidth, mTexHeight);
// Set render viewport dimensions
GXSetViewportRender(0.0f, 0.0f, w, h, 0.0f, 1.0f);
GXSetScissorRender(0, 0, w, h);
GXSetViewportRender(0.0f, 0.0f, rw, rh, 0.0f, 1.0f);
GXSetScissorRender(0, 0, rw, rh);
#else
GXSetViewport(0.0f, 0.0f, mTexWidth, mTexHeight, 0.0f, 1.0f);
GXSetScissor(0, 0, mTexWidth, mTexHeight);
@@ -628,11 +643,9 @@ void dRenderingFDAmap_c::preRenderingMap() {
void dRenderingFDAmap_c::postRenderingMap() {
GXSetCopyFilter(GX_FALSE, NULL, GX_FALSE, NULL);
#ifdef TARGET_PC
const u16 scale = map_resolution_multiplier(mTexWidth, mTexHeight);
const u16 w = mTexWidth * scale;
const u16 h = mTexHeight * scale;
GXSetTexCopySrc(0, 0, w, h);
GXSetTexCopyDst(w, h, GX_CTF_R8, GX_FALSE);
const auto [rw, rh] = map_render_size_for(mTexWidth, mTexHeight);
GXSetTexCopySrc(0, 0, rw, rh);
GXSetTexCopyDst(rw, rh, GX_CTF_R8, GX_FALSE);
GXCopyTex(field_0x4, GX_TRUE);
GXRestoreFrameBuffer();
#else
+91
View File
@@ -36,6 +36,10 @@
#include "d/d_menu_window.h"
#include "JSystem/J3DGraphBase/J3DMaterial.h"
#if TARGET_PC
#include "dusk/menu_pointer.h"
#endif
typedef void (dMenu_Collect2D_c::*initFunc)();
static DUSK_CONSTEXPR initFunc init[] = {
&dMenu_Collect2D_c::wait_init, &dMenu_Collect2D_c::save_open_init,
@@ -1788,6 +1792,12 @@ void dMenu_Collect2D_c::wait_init() {
}
void dMenu_Collect2D_c::wait_proc() {
#if TARGET_PC
if (pointerWait()) {
return;
}
#endif
if (dMw_A_TRIGGER()) {
if (mCursorX == 0 && mCursorY == 5) {
if (mDoGph_gInf_c::getFader()->mStatus == 1) {
@@ -1889,6 +1899,87 @@ void dMenu_Collect2D_c::wait_proc() {
}
}
#if TARGET_PC
void dMenu_Collect2D_c::pointerActivateCurrent() {
if (mCursorX == 0 && mCursorY == 5) {
if (mDoGph_gInf_c::getFader()->mStatus == 1) {
mSubWindowOpenCheck = 1;
Z2GetAudioMgr()->seStart(Z2SE_SY_MENU_CHANGE_WINDOW, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
-1.0f, 0);
dMeter2Info_set2DVibrationM();
}
} else if (mCursorX == 1 && mCursorY == 5) {
if (mDoGph_gInf_c::getFader()->mStatus == 1) {
mSubWindowOpenCheck = 2;
Z2GetAudioMgr()->seStart(Z2SE_SY_MENU_CHANGE_WINDOW, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
-1.0f, 0);
dMeter2Info_set2DVibrationM();
}
} else if (mCursorX == 3 && mCursorY == 4) {
if (field_0x22d[3][4] != 0 && mDoGph_gInf_c::getFader()->mStatus == 1) {
mSubWindowOpenCheck = 3;
dMeter2Info_set2DVibration();
}
} else if (mCursorX == 2 && mCursorY == 4) {
if (isFishIconVisible() && mDoGph_gInf_c::getFader()->mStatus == 1) {
mSubWindowOpenCheck = 4;
dMeter2Info_set2DVibration();
}
} else if (mCursorX == 3 && mCursorY == 3) {
if (isSkillIconVisible() && mDoGph_gInf_c::getFader()->mStatus == 1) {
mSubWindowOpenCheck = 5;
dMeter2Info_set2DVibration();
}
} else if (mCursorX == 2 && mCursorY == 3) {
if (isInsectIconVisible() && mDoGph_gInf_c::getFader()->mStatus == 1) {
mSubWindowOpenCheck = 6;
dMeter2Info_set2DVibration();
}
} else if (field_0x22d[mCursorX][mCursorY] != 0 && !mIsWolf) {
if ((mCursorX >= 3 && mCursorX <= 4) || (mCursorX == 5 && mCursorY == 2)) {
u8 cursorY = mCursorY;
if (cursorY == 0) {
if (daPy_getPlayerActorClass()->getSwordChangeWaitTimer() == 0) {
changeSword();
}
} else if (cursorY == 1) {
if (daPy_getPlayerActorClass()->getShieldChangeWaitTimer() == 0) {
changeShield();
}
} else if (cursorY == 2 &&
daPy_getPlayerActorClass()->getClothesChangeWaitTimer() == 0)
{
changeClothe();
}
}
}
}
bool dMenu_Collect2D_c::pointerWait() {
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::Collection);
for (u8 y = 0; y < 6; ++y) {
for (u8 x = 0; x < 7; ++x) {
if (getItemTag(x, y, true) == 0 || !dusk::menu_pointer::hit_pane(mpSelPm[x][y], 8.0f)) {
continue;
}
if (mCursorX != x || mCursorY != y) {
mDoAud_seStart(Z2SE_SY_MENU_CURSOR_COMMON, NULL, 0, 0);
mCursorX = x;
mCursorY = y;
cursorPosSet();
setItemNameString(mCursorX, mCursorY);
}
if (dusk::menu_pointer::consume_click()) {
pointerActivateCurrent();
return true;
}
return false;
}
}
return false;
}
#endif
void dMenu_Collect2D_c::save_open_init() {
JKRHeap* heap = mDoExt_setCurrentHeap(mpSubHeap);
+13
View File
@@ -17,7 +17,10 @@
#include "d/d_msg_scrn_explain.h"
#include "m_Do/m_Do_graphic.h"
#include "d/actor/d_a_midna.h"
#if TARGET_PC
#include "dusk/frame_interpolation.h"
#include "dusk/ui/touch_controls.hpp"
#endif
#include <cstring>
#if TARGET_PC
@@ -2509,6 +2512,10 @@ dMenu_Fmap2DTop_c::dMenu_Fmap2DTop_c(JKRExpHeap* i_heap, STControl* i_stick) {
}
dMenu_Fmap2DTop_c::~dMenu_Fmap2DTop_c() {
#if TARGET_PC
dusk::ui::set_control_override(dusk::ui::Control::Z, dusk::ui::ControlOverride::Default);
#endif
deleteExplain();
JKR_DELETE(mpTitleScreen);
mpTitleScreen = NULL;
@@ -2782,6 +2789,12 @@ void dMenu_Fmap2DTop_c::setZButtonString(u32 param_0, u8 i_alpha) {
param_0 = 0x533;
}
#if TARGET_PC
dusk::ui::set_control_override(dusk::ui::Control::Z,
param_0 != 0 && isWarpAccept() ? dusk::ui::ControlOverride::Action :
dusk::ui::ControlOverride::Default);
#endif
#if VERSION == VERSION_GCN_JPN
static const u64 cont_zt[5] = {MULTI_CHAR('cont_zt'), MULTI_CHAR('cont_zt1'), MULTI_CHAR('cont_zt2'), MULTI_CHAR('cont_zt3'), MULTI_CHAR('cont_zt4')};
#define setZButtonString_font_zt cont_zt
+43
View File
@@ -22,6 +22,10 @@
#include <cstdio>
#include <cstring>
#if TARGET_PC
#include "dusk/menu_pointer.h"
#endif
typedef void (dMenu_Insect_c::*initFunc)();
static initFunc map_init_process[] = {
&dMenu_Insect_c::wait_init, &dMenu_Insect_c::explain_open_init,
@@ -280,6 +284,12 @@ void dMenu_Insect_c::wait_init() {
void dMenu_Insect_c::wait_move() {
if (mDoGph_gInf_c::getFader()->getStatus() == 1) {
#if TARGET_PC
if (pointerWait()) {
return;
}
#endif
if (mDoCPd_c::getTrigB(PAD_1) || field_0xf7 == 0) {
if (mDoCPd_c::getTrigB(PAD_1) && field_0xf6 == 1) {
dMeter2Info_setInsectSelectType(0);
@@ -301,6 +311,39 @@ void dMenu_Insect_c::wait_move() {
}
}
#if TARGET_PC
bool dMenu_Insect_c::pointerWait() {
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::Collection);
for (u8 y = 0; y < 4; ++y) {
for (u8 x = 0; x < 6; ++x) {
const int index = x + y * 6;
if (!isGetInsect(x, y) || !dusk::menu_pointer::hit_pane(mpINSParent[index], 8.0f)) {
continue;
}
if (field_0xf4 != x || field_0xf5 != y) {
field_0xf4 = x;
field_0xf5 = y;
setCursorPos();
setAButtonString(0x368);
Z2GetAudioMgr()->seStart(Z2SE_SY_CURSOR_ITEM, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
-1.0f, 0);
}
if (dusk::menu_pointer::consume_click()) {
field_0xf3 = 1;
Z2GetAudioMgr()->seStart(Z2SE_SY_EXP_WIN_OPEN, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
-1.0f, 0);
dMeter2Info_set2DVibration();
return true;
}
return false;
}
}
return false;
}
#endif
void dMenu_Insect_c::explain_open_init() {
char local_78[32];
char local_98[32];
+67
View File
@@ -19,6 +19,15 @@
#ifdef TARGET_PC
#include "dusk/achievements.h"
#include "dusk/menu_pointer.h"
#include "dusk/ui/touch_controls.hpp"
static void enable_turn_page_controls(bool enabled) {
const auto controlOverride =
enabled ? dusk::ui::ControlOverride::Action : dusk::ui::ControlOverride::Default;
dusk::ui::set_control_override(dusk::ui::Control::L, controlOverride);
dusk::ui::set_control_override(dusk::ui::Control::R, controlOverride);
}
#endif
#if VERSION == VERSION_GCN_JPN
@@ -82,6 +91,10 @@ dMenu_Letter_c::dMenu_Letter_c(JKRExpHeap* i_heap, STControl* i_stick, CSTContro
dMenu_Letter_c::~dMenu_Letter_c() {
#if TARGET_PC
enable_turn_page_controls(false);
#endif
JKR_DELETE(mpDrawCursor);
mpDrawCursor = NULL;
@@ -357,6 +370,10 @@ int dMenu_Letter_c::_open() {
}
int dMenu_Letter_c::_close() {
#if TARGET_PC
enable_turn_page_controls(false);
#endif
s16 closeWindowFrame =
g_drawHIO.mLetterSelectScreen.mCloseFrame[dMeter_drawLetterHIO_c::WINDOW_FRAME];
field_0x368 = 0;
@@ -386,6 +403,10 @@ int dMenu_Letter_c::_close() {
}
void dMenu_Letter_c::wait_init() {
#if TARGET_PC
enable_turn_page_controls(field_0x374 > 1);
#endif
setAButtonString(0x40c);
setBButtonString(0x3f9);
}
@@ -393,6 +414,12 @@ void dMenu_Letter_c::wait_init() {
void dMenu_Letter_c::wait_move() {
u8 oldIndex = mIndex;
if (mDoGph_gInf_c::getFader()->getStatus() == 1) {
#if TARGET_PC
if (pointerWait()) {
return;
}
#endif
if (mDoCPd_c::getTrigB(PAD_1) != 0) {
mpDrawCursor->offPlayAnime(0);
mStatus = 3;
@@ -448,8 +475,40 @@ void dMenu_Letter_c::wait_move() {
}
}
#if TARGET_PC
bool dMenu_Letter_c::pointerWait() {
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::Collection);
for (u8 i = 0; i < field_0x373; ++i) {
if (!dusk::menu_pointer::hit_pane(mpLetterParent[i], 8.0f)) {
continue;
}
if (mIndex != i) {
mIndex = i;
changeActiveColor();
Z2GetAudioMgr()->seStart(Z2SE_SY_CURSOR_ITEM, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
-1.0f, 0);
}
if (dusk::menu_pointer::consume_click()) {
mProcess = 3;
Z2GetAudioMgr()->seStart(Z2SE_SY_LETTER_OPEN, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
-1.0f, 0);
dMeter2Info_set2DVibration();
return true;
}
return false;
}
return false;
}
#endif
void dMenu_Letter_c::slide_right_init() {
#if TARGET_PC
enable_turn_page_controls(false);
#endif
field_0x358 = -field_0x1ec->getWidth() * mDoGph_gInf_c::getInvScale();
field_0x35c = field_0x1ec->getWidth() IF_NOT_DUSK(* mDoGph_gInf_c::getInvScale());
changePageLight();
@@ -467,6 +526,10 @@ void dMenu_Letter_c::slide_right_move() {
}
void dMenu_Letter_c::slide_left_init() {
#if TARGET_PC
enable_turn_page_controls(false);
#endif
field_0x358 = field_0x1ec->getWidth() * mDoGph_gInf_c::getInvScale();
field_0x35c = -field_0x1ec->getWidth() IF_NOT_DUSK(* mDoGph_gInf_c::getInvScale());
changePageLight();
@@ -484,6 +547,10 @@ void dMenu_Letter_c::slide_left_move() {
}
void dMenu_Letter_c::read_open_init() {
#if TARGET_PC
enable_turn_page_controls(false);
#endif
field_0x36a = 0;
u8 idx = field_0x3ac[field_0x36f * 6 + mIndex] - 1;
field_0x3e3 = 1;
+132
View File
@@ -26,6 +26,11 @@
#include "JSystem/JAudio2/JASDriverIF.h"
#if TARGET_PC
#include "dusk/menu_pointer.h"
#include "dusk/ui/touch_controls.hpp"
#endif
typedef void (dMenu_Option_c::*initFunc)();
static initFunc init[] = {
&dMenu_Option_c::atten_init,
@@ -293,6 +298,10 @@ void dMenu_Option_c::_create() {
}
void dMenu_Option_c::_delete() {
#if TARGET_PC
dusk::ui::set_control_override(dusk::ui::Control::Z, dusk::ui::ControlOverride::Default);
#endif
JKR_DELETE(mpString);
mpString = NULL;
@@ -518,6 +527,15 @@ void dMenu_Option_c::_move() {
(this->*init[field_0x3ef])();
}
}
#if TARGET_PC
if (field_0x3f4 == 5 && field_0x3ef != SelectType3 && field_0x3f3 == 5 &&
field_0x3ef != SelectType4 && field_0x3ef != SelectType5 && field_0x3ef != SelectType6 &&
field_0x3ef != SelectType7 && pointerConfirmSelect())
{
goto skip;
}
#endif
}
skip:
u8 oldValue = field_0x3ef;
@@ -1074,6 +1092,34 @@ void dMenu_Option_c::confirm_move_move() {
bool leftTrigger = checkLeftTrigger();
bool rightTrigger = checkRightTrigger();
#if TARGET_PC
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::Options);
for (u8 i = 0; i < 2; ++i) {
if (!dusk::menu_pointer::hit_pane(mpYesNoSelBase_c[i], 8.0f)) {
continue;
}
if (field_0x3f9 != i) {
Z2GetAudioMgr()->seStart(Z2SE_SY_MENU_CURSOR_COMMON, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
-1.0f, 0);
field_0x3fa = field_0x3f9;
field_0x3f9 = i;
yesnoSelectAnmSet();
field_0x3ef = SelectType6;
mpWarning->_move();
setAnimation();
return;
}
if (dusk::menu_pointer::consume_click()) {
yesNoSelectStart();
field_0x3ef = SelectType7;
dMeter2Info_set2DVibrationM();
mpWarning->_move();
setAnimation();
return;
}
}
#endif
if (mDoCPd_c::getTrigA(PAD_1) != 0) {
yesNoSelectStart();
field_0x3ef = SelectType7;
@@ -2063,6 +2109,11 @@ void dMenu_Option_c::cursorAnime(f32 i_cursorValue) {
}
void dMenu_Option_c::setZButtonString(u16 i_stringID) {
#if TARGET_PC
dusk::ui::set_control_override(dusk::ui::Control::Z,
i_stringID != 0 ? dusk::ui::ControlOverride::Action : dusk::ui::ControlOverride::Default);
#endif
if (i_stringID == 0) {
for (int i = 0; i < 3; i++) {
if (mpZButtonText[i] != NULL) {
@@ -2142,7 +2193,88 @@ bool dMenu_Option_c::isRumbleSupported() {
return JUTGamePad::sRumbleSupported >> 0x1f;
}
#if TARGET_PC
bool dMenu_Option_c::pointerConfirmSelect() {
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::Options);
if (!dusk::menu_pointer::state().clicked) {
return false;
}
for (u8 i = 0; i < SelectType3; ++i) {
if (dusk::menu_pointer::hit_pane(mpMenuPane[i], 8.0f)) {
return false;
}
}
if (!dusk::menu_pointer::consume_click()) {
return false;
}
field_0x3f7 = 1;
field_0x3f5 = field_0x3ef;
field_0x3ef = SelectType4;
dMeter2Info_set2DVibration();
(this->*init[field_0x3ef])();
return true;
}
#endif
bool dMenu_Option_c::dpdMenuMove() {
#if TARGET_PC
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::Options);
for (u8 i = 0; i < SelectType3; ++i) {
if (!dusk::menu_pointer::hit_pane(mpMenuPane[i], 8.0f)) {
continue;
}
if (getSelectType() != i) {
field_0x3ef = i;
setCursorPos(i);
Z2GetAudioMgr()->seStart(Z2SE_SY_CURSOR_OPTION, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
-1.0f, 0);
}
if (!dusk::menu_pointer::consume_click()) {
return true;
}
switch (i) {
case SelectType0:
field_0x3e4 ^= 1;
field_0x3da = 5;
field_0x3ef = SelectType3;
field_0x3f5 = SelectType0;
Z2GetAudioMgr()->seStart(Z2SE_SY_OPTION_SWITCH, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
-1.0f, 0);
return true;
case SelectType1:
if (isRumbleSupported()) {
field_0x3ea ^= 1;
if (field_0x3ea != 0) {
mDoCPd_c::startMotorWave(0, &field_0x3e0, JUTGamePad::CRumble::VAL_0, 0x3c);
}
field_0x3da = 5;
field_0x3ef = SelectType3;
field_0x3f5 = SelectType1;
Z2GetAudioMgr()->seStart(Z2SE_SY_OPTION_SWITCH, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
-1.0f, 0);
}
return true;
case SelectType2:
if (field_0x3e9 == 0) {
field_0x3e9 = 2;
} else {
field_0x3e9--;
}
field_0x3da = 5;
mDoAud_setOutputMode(dMo_soundMode[field_0x3e9]);
setSoundMode(dMo_soundMode[field_0x3e9]);
field_0x3ef = SelectType3;
field_0x3f5 = SelectType2;
Z2GetAudioMgr()->seStart(Z2SE_SY_OPTION_SWITCH, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
-1.0f, 0);
return true;
}
}
#endif
return false;
}
+81
View File
@@ -31,7 +31,9 @@
#if TARGET_PC
#include "dusk/game_clock.h"
#include "dusk/menu_pointer.h"
#include "dusk/settings.h"
#include "dusk/ui/touch_controls.hpp"
#endif
typedef void (dMenu_Ring_c::*initFunc)();
@@ -614,6 +616,9 @@ void dMenu_Ring_c::_delete() {
* initializes a new process if mStatus changes
*/
void dMenu_Ring_c::_move() {
#if TARGET_PC
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::ItemWheel);
#endif
mRingRadiusH = g_ringHIO.mRingRadiusH;
mRingRadiusV = g_ringHIO.mRingRadiusV;
mOldStatus = mStatus; // Save current status for check
@@ -1517,6 +1522,11 @@ void dMenu_Ring_c::stick_wait_proc() {
setDoStatus(0);
return;
}
#if TARGET_PC
if (pointerMove()) {
return;
}
#endif
if (dMw_A_TRIGGER() && !dMeter2Info_isTouchKeyCheck(0xe)) {
Z2GetAudioMgr()->seStart(Z2SE_SYS_ERROR, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0);
}
@@ -1528,6 +1538,49 @@ void dMenu_Ring_c::stick_wait_proc() {
}
}
#if TARGET_PC
bool dMenu_Ring_c::pointerMove() {
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::ItemWheel);
const auto& pointer = dusk::menu_pointer::state();
if (!pointer.valid || mItemsTotal == 0) {
return false;
}
int hoveredSlot = -1;
f32 bestDistance = 42.0f;
for (u8 i = 0; i < mItemsTotal; ++i) {
const f32 x = mItemSlotPosX[i] + mCenterPosX;
const f32 y = mItemSlotPosY[i] + mCenterPosY;
const f32 distance = calcDistance(pointer.x, pointer.y, x, y);
if (distance < bestDistance) {
bestDistance = distance;
hoveredSlot = i;
}
}
if (hoveredSlot < 0) {
return false;
}
if (mCurrentSlot != hoveredSlot) {
mDirectSelectCursorPos.x = mItemSlotPosX[mCurrentSlot];
mDirectSelectCursorPos.z = mItemSlotPosY[mCurrentSlot];
mCurrentSlot = hoveredSlot;
mDirectSelectActive = true;
field_0x670 = field_0x63e[mCurrentSlot];
setStatus(STATUS_MOVE);
field_0x6b2 = 0;
return true;
}
if (dusk::menu_pointer::consume_click()) {
return true;
}
return false;
}
#endif
void dMenu_Ring_c::stick_move_init() {
if (mCursorSpeed == 0) {
mCursorSpeed = g_ringHIO.mCursorInitSpeed;
@@ -1672,12 +1725,40 @@ void dMenu_Ring_c::drawSelectItem() {
#else
if (field_0x674[i] < 10) {
#endif
#if TARGET_PC
f32 initSizeX;
f32 initSizeY;
f32 initScaleX;
f32 initScaleY;
Vec pos;
dusk::ui::EquipTarget touchTarget;
if (dusk::ui::get_equip_target(i, touchTarget)) {
initSizeX = touchTarget.width;
initSizeY = touchTarget.height;
initScaleX = 1.0f;
initScaleY = 1.0f;
pos.x = touchTarget.left;
pos.y = touchTarget.top;
pos.z = 0.0f;
} else {
CPaneMgr* meterItemPane = dMeter2Info_getMeterItemPanePtr(i);
if (meterItemPane == NULL) {
continue;
}
initSizeX = meterItemPane->getInitSizeX() * 1.7f;
initSizeY = meterItemPane->getInitSizeY() * 1.7f;
initScaleX = meterItemPane->getInitScaleX();
initScaleY = meterItemPane->getInitScaleY();
pos = meterItemPane->getGlobalVtxCenter(meterItemPane->mPane, true, 0);
}
#else
f32 initSizeX = dMeter2Info_getMeterItemPanePtr(i)->getInitSizeX() * 1.7f;
f32 initSizeY = dMeter2Info_getMeterItemPanePtr(i)->getInitSizeY() * 1.7f;
f32 initScaleX = dMeter2Info_getMeterItemPanePtr(i)->getInitScaleX();
f32 initScaleY = dMeter2Info_getMeterItemPanePtr(i)->getInitScaleY();
Vec pos = dMeter2Info_getMeterItemPanePtr(i)->getGlobalVtxCenter(
dMeter2Info_getMeterItemPanePtr(i)->mPane, true, 0);
#endif
#if TARGET_PC
f32 fVar14 = 0.1f + 0.8f * u;
+153 -2
View File
@@ -18,11 +18,15 @@
#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"
#if TARGET_PC
#include "dusk/frame_interpolation.h"
#include "dusk/menu_pointer.h"
#include "dusk/settings.h"
#endif
static int SelStartFrameTbl[3] = {
59,
99,
@@ -54,6 +58,17 @@ static int YnSelStartFrameTbl[2][2] = {
static int YnSelEndFrameTbl[2][2] = {{2138, 3171}, {2150, 3181}};
#if TARGET_PC
namespace {
constexpr u8 pointer_target(u8 group, u8 index) noexcept {
return static_cast<u8>((group << 4) | (index & 0x0F));
}
constexpr u8 s_pointerSaveSelectTarget = 0;
constexpr u8 s_pointerYesNoSelectTarget = 1;
} // namespace
#endif
static dMs_HIO_c g_msHIO;
dMs_HIO_c::dMs_HIO_c() {
@@ -1766,6 +1781,12 @@ void dMenu_save_c::openSaveSelect3() {
void dMenu_save_c::saveSelect() {
if (!mDoRst::isReset()) {
#if TARGET_PC
if (pointerSaveSelect()) {
return;
}
#endif
stick->checkTrigger();
if (mDoCPd_c::getTrigA(PAD_1)) {
@@ -1792,7 +1813,84 @@ void dMenu_save_c::saveSelect() {
}
}
#if TARGET_PC
bool dMenu_save_c::pointerSaveSelect() {
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::Save);
for (u8 i = 0; i < 3; ++i) {
if (!dusk::menu_pointer::hit_pane(mpSelData[i], 8.0f)) {
continue;
}
const bool clicked = dusk::menu_pointer::consume_click();
if (mSelectedFile != i) {
mDoAud_seStart(Z2SE_FILE_SELECT_CURSOR, NULL, 0, 0);
mLastSelFile = mSelectedFile;
mSelectedFile = i;
if (clicked) {
dusk::menu_pointer::defer_activation(
dusk::menu_pointer::Context::Save,
pointer_target(s_pointerSaveSelectTarget, i));
}
dataSelectAnmSet();
mMenuProc = PROC_SAVE_SELECT_MOVE_ANM;
return true;
}
if (clicked) {
saveSelectStart();
return true;
}
}
return false;
}
bool dMenu_save_c::pointerYesNoSelect(bool errorSelect, u8 errParam, u8 soundParam) {
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::Save);
for (u8 i = 0; i < 2; ++i) {
if (!dusk::menu_pointer::hit_pane(mpNoYes[i], 8.0f)) {
continue;
}
const bool clicked =
(!errorSelect || mYesNoCursor == i) && dusk::menu_pointer::consume_click();
if (mYesNoCursor != i) {
if (errorSelect) {
errCurMove(errParam, soundParam);
return false;
}
mDoAud_seStart(Z2SE_SY_MENU_CURSOR_COMMON, NULL, 0, 0);
mYesNoPrevCursor = mYesNoCursor;
mYesNoCursor = i;
if (clicked) {
dusk::menu_pointer::defer_activation(
dusk::menu_pointer::Context::Save,
pointer_target(s_pointerYesNoSelectTarget, i));
}
yesnoSelectAnmSet(0);
mMenuProc = PROC_YES_NO_CURSOR_MOVE_ANM;
return true;
}
if (clicked) {
if (errorSelect) {
if (mYesNoCursor != CURSOR_NO) {
if (soundParam == 0) {
mDoAud_seStart(Z2SE_SY_CURSOR_OK, NULL, 0, 0);
}
} else if (soundParam == 0) {
mDoAud_seStart(Z2SE_SY_CURSOR_CANCEL, NULL, 0, 0);
}
mSelIcon->setAlphaRate(0.0f);
} else {
yesnoSelectStart();
}
return true;
}
}
return false;
}
#endif
void dMenu_save_c::saveSelectStart() {
#if TARGET_PC
dusk::menu_pointer::clear_deferred_activation(dusk::menu_pointer::Context::Save);
#endif
mDoAud_seStart(Z2SE_SY_CURSOR_OK, NULL, 0, 0);
selectDataMoveAnmInitSet(SelOpenStartFrameTbl[mSelectedFile],
SelOpenEndFrameTbl[mSelectedFile]);
@@ -1851,6 +1949,17 @@ void dMenu_save_c::dataSelectAnmSet() {
}
void dMenu_save_c::saveSelectMoveAnime() {
#if TARGET_PC
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::Save);
if (mSelectedFile != 0xFF &&
dusk::menu_pointer::hit_pane(mpSelData[mSelectedFile], 8.0f) &&
dusk::menu_pointer::consume_click())
{
dusk::menu_pointer::defer_activation(
dusk::menu_pointer::Context::Save,
pointer_target(s_pointerSaveSelectTarget, mSelectedFile));
}
#endif
bool bookWakuAnmComplete = true;
bool selWakuAnmComplete = true;
bool var_r29 = true;
@@ -1900,12 +2009,26 @@ void dMenu_save_c::saveSelectMoveAnime() {
if (mLastSelFile != 0xFF) {
mpSelData[mLastSelFile]->getPanePtr()->setAnimation((J2DAnmTransformKey*)NULL);
}
#if TARGET_PC
if (dusk::menu_pointer::consume_deferred_activation(
dusk::menu_pointer::Context::Save,
pointer_target(s_pointerSaveSelectTarget, mSelectedFile))) {
saveSelectStart();
return;
}
#endif
mMenuProc = PROC_SAVE_SELECT;
}
}
void dMenu_save_c::saveYesNoSelect() {
if (!mDoRst::isReset()) {
#if TARGET_PC
if (pointerYesNoSelect(false)) {
return;
}
#endif
stick->checkTrigger();
if (mDoCPd_c::getTrigA(PAD_1)) {
@@ -1933,6 +2056,9 @@ void dMenu_save_c::saveYesNoSelect() {
}
void dMenu_save_c::yesnoSelectStart() {
#if TARGET_PC
dusk::menu_pointer::clear_deferred_activation(dusk::menu_pointer::Context::Save);
#endif
if (mYesNoCursor != CURSOR_NO) {
mDoAud_seStart(Z2SE_SY_CURSOR_OK, NULL, 0, 0);
mSelIcon->setAlphaRate(0.0f);
@@ -2001,11 +2127,30 @@ void dMenu_save_c::yesnoSelectAnmSet(u8 param_0) {
}
void dMenu_save_c::yesNoCursorMoveAnm() {
#if TARGET_PC
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::Save);
if (mYesNoCursor != 0xFF &&
dusk::menu_pointer::hit_pane(mpNoYes[mYesNoCursor], 8.0f) &&
dusk::menu_pointer::consume_click())
{
dusk::menu_pointer::defer_activation(
dusk::menu_pointer::Context::Save,
pointer_target(s_pointerYesNoSelectTarget, mYesNoCursor));
}
#endif
bool selAnmComplete = yesnoSelectMoveAnm(0);
bool wakuAnmComplete = yesnoWakuAlpahAnm(mYesNoPrevCursor);
if (selAnmComplete == true && wakuAnmComplete == true) {
yesnoCursorShow();
#if TARGET_PC
if (dusk::menu_pointer::consume_deferred_activation(
dusk::menu_pointer::Context::Save,
pointer_target(s_pointerYesNoSelectTarget, mYesNoCursor))) {
yesnoSelectStart();
return;
}
#endif
mMenuProc = PROC_SAVE_YES_NO_SELECT;
}
}
@@ -2181,6 +2326,12 @@ bool dMenu_save_c::errYesNoSelect(u8 param_0, u8 param_1) {
return false;
}
#if TARGET_PC
if (pointerYesNoSelect(true, param_0, param_1)) {
return true;
}
#endif
stick->checkTrigger();
if (mDoCPd_c::getTrigA(PAD_1)) {
+38
View File
@@ -18,6 +18,10 @@
#include "m_Do/m_Do_graphic.h"
#include <cstring>
#if TARGET_PC
#include "dusk/menu_pointer.h"
#endif
typedef void (dMenu_Skill_c::*initFunc)();
static initFunc map_init_process[] = {
&dMenu_Skill_c::wait_init,
@@ -275,6 +279,12 @@ void dMenu_Skill_c::wait_init() {
void dMenu_Skill_c::wait_move() {
u8 oldIndex = mIndex;
if (mDoGph_gInf_c::getFader()->getStatus() == 1) {
#if TARGET_PC
if (pointerWait()) {
return;
}
#endif
if (mDoCPd_c::getTrigB(PAD_1) != 0) {
mpDrawCursor->offPlayAnime(0);
mStatus = 3;
@@ -299,6 +309,34 @@ void dMenu_Skill_c::wait_move() {
}
}
#if TARGET_PC
bool dMenu_Skill_c::pointerWait() {
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::Collection);
for (u8 i = 0; i < mSkillNum; ++i) {
if (!dusk::menu_pointer::hit_pane(mpLetterParent[i], 8.0f)) {
continue;
}
if (mIndex != i) {
mIndex = i;
changeActiveColor();
Z2GetAudioMgr()->seStart(Z2SE_SY_CURSOR_ITEM, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
-1.0f, 0);
}
if (dusk::menu_pointer::consume_click()) {
mProcess = PROC_WAIT_MOVE;
Z2GetAudioMgr()->seStart(Z2SE_SY_EXP_WIN_OPEN, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
-1.0f, 0);
dMeter2Info_set2DVibration();
return true;
}
return false;
}
return false;
}
#endif
void dMenu_Skill_c::read_open_init() {
static const u32 i_id[7] = {
1716, 1715, 1717, 1718, 1719, 1720, 1721,
+17 -2
View File
@@ -663,8 +663,15 @@ void dMeter2_c::moveLife() {
draw_life = true;
}
if (mLifeGaugeScale != g_drawHIO.mLifeParentScale) {
mLifeGaugeScale = g_drawHIO.mLifeParentScale;
#if TARGET_PC
const f32 lifeGaugeScale =
g_drawHIO.mLifeParentScale *
std::clamp(dusk::getSettings().game.hudScale.getValue(), 0.5f, 2.0f);
#else
const f32 lifeGaugeScale = g_drawHIO.mLifeParentScale;
#endif
if (mLifeGaugeScale != lifeGaugeScale) {
mLifeGaugeScale = lifeGaugeScale;
draw_life = true;
}
@@ -2966,7 +2973,15 @@ void dMeter2_c::alphaAnimeButtonCross() {
field_0x190++;
}
} else {
#if TARGET_PC
if (dusk::getSettings().game.enableTouchControls) {
mpMeterDraw->setAlphaButtonCrossAnimeMin();
} else {
mpMeterDraw->setAlphaButtonCrossAnimeMax();
}
#else
mpMeterDraw->setAlphaButtonCrossAnimeMax();
#endif
if (field_0x190 < 5) {
field_0x190++;
+35
View File
@@ -25,6 +25,7 @@
#if TARGET_PC
#include "dusk/settings.h"
#include "dusk/ui/icon_provider.hpp"
#include <algorithm>
namespace {
@@ -653,10 +654,22 @@ void dMeter2Draw_c::draw() {
J2DGrafContext* graf_ctx = dComIfGp_getCurrentGrafPort();
graf_ctx->setup2D();
#if TARGET_PC
const bool touchControlsEnabled = dusk::getSettings().game.enableTouchControls;
if (touchControlsEnabled) {
mpButtonParent->hide();
} else {
mpButtonParent->show();
}
#endif
mpScreen->draw(0.0f, 0.0f, graf_ctx);
drawKanteraScreen(1);
drawKanteraScreen(2);
#if TARGET_PC
if (!touchControlsEnabled) {
#endif
for (int i = 0; i < 2; i++) {
if (mpItemXY[i] != NULL) {
for (int j = 0; j < 3; j++) {
@@ -705,6 +718,9 @@ void dMeter2Draw_c::draw() {
}
}
}
#if TARGET_PC
}
#endif
if (mpLightDropParent->getAlphaRate() != 0.0f) {
f32 var_f28 = g_drawHIO.mLightDrop.mPikariScaleNormal;
@@ -788,7 +804,11 @@ void dMeter2Draw_c::draw() {
}
}
#if TARGET_PC
if (!touchControlsEnabled && field_0x738 > 0.0f) {
#else
if (field_0x738 > 0.0f) {
#endif
drawPikari(mpButtonMidona, &field_0x738, g_drawHIO.mMidnaIconPikariScale,
g_drawHIO.mMidnaIconPikariFrontOuter, g_drawHIO.mMidnaIconPikariFrontInner,
g_drawHIO.mMidnaIconPikariBackOuter, g_drawHIO.mMidnaIconPikariBackInner,
@@ -2480,6 +2500,11 @@ void dMeter2Draw_c::drawButtonB(u8 i_action, bool param_1, f32 i_posX, f32 i_pos
SAFE_STRCPY(static_cast<J2DTextBox*>(mpBText[i]->getPanePtr())->getStringPtr(), mp_string);
}
#if TARGET_PC
if (dusk::getSettings().game.enableTouchControls) {
mpScreen->search(MULTI_CHAR('item_b_n'))->hide();
} else
#endif
if (i_action == 0x26 || i_action == 0x2E) {
mpScreen->search(MULTI_CHAR('item_b_n'))->show();
var_r31 = 1;
@@ -2757,6 +2782,12 @@ void dMeter2Draw_c::drawButtonXY(int i_no, u8 i_itemNo, u8 i_action, bool param_
mpTextXY[i_no]->scale(g_drawHIO.mButtonXYTextScale, g_drawHIO.mButtonXYTextScale);
mpTextXY[i_no]->paneTrans(g_drawHIO.mButtonXYTextPosX, g_drawHIO.mButtonXYTextPosY);
}
#if TARGET_PC
if (dusk::getSettings().game.enableTouchControls) {
mpScreen->search(tag[i_no])->hide();
}
#endif
}
}
@@ -3322,6 +3353,10 @@ void dMeter2Draw_c::setButtonIconMidonaAlpha(u32 param_0) {
}
mpButtonXY[2]->setAlpha(255.0f * field_0x724 * temp_f30_2);
#if TARGET_PC
dusk::ui::update_midna_icon_texture(mpButtonMidona != NULL ? mpButtonMidona->getPanePtr() : NULL);
#endif
}
void dMeter2Draw_c::setButtonIconAlpha(int i_no, u8 unused0, u32 unused1, bool unused2) {
+57 -2
View File
@@ -22,6 +22,10 @@
#endif
#include <cstring>
#if TARGET_PC
#include "dusk/action_bindings.h"
#endif
#if (PLATFORM_WII || PLATFORM_SHIELD)
dMeter_map_HIO_c g_meter_mapHIO;
#endif
@@ -539,6 +543,12 @@ void dMeterMap_c::_move(u32 param_0) {
}
#endif
#if TARGET_PC
if (mMap->refreshTextureSize()) {
mMapJ2DPicture->changeTexture(mMap->getResTIMGPointer(), 0);
}
#endif
int stayNo = dComIfGp_roomControl_getStayNo();
field_0x14 = param_0;
@@ -732,7 +742,38 @@ void dMeterMap_c::ctrlShowMap() {
}
}
} else if (!mDoCPd_c::getTrigUp(PAD_1) && !mDoCPd_c::getTrigDown(PAD_1)) {
}
#if TARGET_PC
else if (!isEventRunCheck() &&
(dMeter2Info_getMapStatus() == 0 || dMeter2Info_getMapStatus() == 1) &&
!dMeter2Info_isSub2DStatus(1) && (isFmapScreen() || isDmapScreen()) &&
dusk::getActionBindTrig(dusk::ActionBinds::OPEN_MAP_SCREEN, PAD_1))
{
dMeter2Info_setMapStatus(2);
dMeter2Info_setMapKeyDirection(0x400);
Z2GetAudioMgr()->seStart(Z2SE_SY_MAP_OPEN_S, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
-1.0f, 0);
dMeter2Info_set2DVibration();
} else if (!isEventRunCheck() &&
(dMeter2Info_getMapStatus() == 0 || dMeter2Info_getMapStatus() == 1) &&
isEnableDispMapAndMapDispSizeTypeNo() &&
dusk::getActionBindTrig(dusk::ActionBinds::TOGGLE_MINIMAP, PAD_1))
{
if (isDispPosInsideFlg()) {
setDispPosOutsideFlg_SE_On();
Z2GetAudioMgr()->seStart(Z2SE_SY_MAP_CLOSE_S, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
-1.0f, 0);
dMeter2Info_setMapStatus(0);
} else {
setDispPosInsideFlg_SE_On();
Z2GetAudioMgr()->seStart(Z2SE_SY_MAP_OPEN_S, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
-1.0f, 0);
dMeter2Info_set2DVibration();
dMeter2Info_setMapStatus(1);
}
}
#endif
else if (!mDoCPd_c::getTrigUp(PAD_1) && !mDoCPd_c::getTrigDown(PAD_1)) {
keyCheck();
}
@@ -827,7 +868,21 @@ void dMeterMap_c::meter_map_move(u32 param_0) {
dMeter2Info_set2DVibration();
}
dMeter2Info_resetPauseStatus();
} else if (
}
#if TARGET_PC
else if (!dComIfGp_event_runCheck() && !dMsgObject_isTalkNowCheck() &&
(dMeter2Info_getMapStatus() == 0 || dMeter2Info_getMapStatus() == 1) &&
!dMeter2Info_isSub2DStatus(1) && (isFmapScreen() || isDmapScreen()) &&
dusk::getActionBindTrig(dusk::ActionBinds::OPEN_MAP_SCREEN, PAD_1))
{
dMeter2Info_setMapStatus(2);
dMeter2Info_setMapKeyDirection(0x400);
Z2GetAudioMgr()->seStart(Z2SE_SY_MAP_OPEN_S, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
-1.0f, 0);
dMeter2Info_set2DVibration();
}
#endif
else if (
#if DEBUG
dMw_RIGHT_TRIGGER() &&
#else
+15
View File
@@ -13,6 +13,7 @@
#include "JSystem/JUtility/JUTFont.h"
#if TARGET_PC
#include "dusk/menu_pointer.h"
#include "dusk/scope_guard.hpp"
#endif
@@ -575,6 +576,20 @@ void jmessage_tReference::pageSend() {
void jmessage_tReference::selectMessage() {
if (mSelectNum != 0) {
#if TARGET_PC
u8 pointerChoice = 0xFF;
if (dusk::menu_pointer::get_dialog_choice(pointerChoice) && pointerChoice < mSelectNum &&
pointerChoice != mSelectPos)
{
mSelectPos = pointerChoice;
if (mSelectType != 0) {
getObjectPtr()->getSequenceProcessor()->calcStringLength();
}
Z2GetAudioMgr()->seStart(Z2SE_SY_TALK_CURSOR, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
-1.0f, 0);
}
#endif
mpStick->checkTrigger();
if (mSelectType == 0) {
+18 -2
View File
@@ -26,12 +26,13 @@
#include <cstring>
#include "JSystem/JKernel/JKRExpHeap.h"
#include "dusk/version.hpp"
#include "m_Do/m_Do_controller_pad.h"
#include "m_Do/m_Do_lib.h"
#if TARGET_PC
#include "dusk/menu_pointer.h"
#include "dusk/settings.h"
#include "dusk/version.hpp"
#include <vector>
#include <array>
#include <algorithm>
@@ -1130,7 +1131,20 @@ void dMsgObject_c::selectProc() {
dComIfGp_setAStatusForce(0x2a, 0);
}
}
if (mDoCPd_c::getTrigA(0)) {
#if TARGET_PC
jmessage_tReference* pRef = (jmessage_tReference*)mpRenProc->getReference();
u8 pointerChoice = 0xFF;
bool pointerConfirm = dusk::menu_pointer::consume_dialog_click(pointerChoice) &&
pointerChoice < pRef->getSelectNum();
if (pointerConfirm) {
pRef->setSelectPos(pointerChoice);
}
#endif
if (mDoCPd_c::getTrigA(0)
#if TARGET_PC
|| pointerConfirm
#endif
) {
if (getSelectCursorPosLocal() != 0xff) {
field_0x1a3 = 1;
}
@@ -1152,7 +1166,9 @@ void dMsgObject_c::selectProc() {
}
field_0x1a3 = 2;
}
#ifndef TARGET_PC
jmessage_tReference* pRef = (jmessage_tReference*)mpRenProc->getReference();
#endif
if (getStatusLocal() == 8) {
if (isMidonaMessage() && field_0x1a3 != 0) {
if (field_0x1a3 == 2 && getSelectCancelPos() == 3) {
+55
View File
@@ -16,6 +16,17 @@
#include "d/d_msg_object.h"
#include "d/d_pane_class.h"
#if TARGET_PC
#include "dusk/menu_pointer.h"
namespace {
bool hit_choice_pane(CPaneMgr* pane, f32 padding) {
return pane != NULL && pane->getPanePtr() != NULL && pane->getPanePtr()->isVisible() &&
dusk::menu_pointer::hit_pane(pane, padding);
}
} // namespace
#endif
typedef void (dMsgScrn3Select_c::*processFn)();
processFn process[] = {
&dMsgScrn3Select_c::open1Proc, &dMsgScrn3Select_c::open2Proc, &dMsgScrn3Select_c::waitProc,
@@ -470,6 +481,9 @@ bool dMsgScrn3Select_c::selAnimeMove(u8 i_selNum, u8 param_1, bool param_2) {
mSelNum = i_selNum;
field_0x114 = 0;
field_0x108 = param_2;
#if TARGET_PC
pointerMove();
#endif
(this->*process[mProcess])();
@@ -518,6 +532,47 @@ bool dMsgScrn3Select_c::selAnimeMove(u8 i_selNum, u8 param_1, bool param_2) {
return mProcess == PROC_SELECT_e ? TRUE : FALSE;
}
#if TARGET_PC
bool dMsgScrn3Select_c::pointerMove() {
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::Dialog);
mDPDPoint = 0xFF;
const u8 firstPane = mSelNum == 2 ? 1 : 0;
for (u8 choice = 0; choice < mSelNum; ++choice) {
const u8 paneIndex = firstPane + choice;
if (paneIndex >= 3) {
continue;
}
// TODO: this sucks and should be replaced with Wii mpTouchArea
bool hit = hit_choice_pane(mpSel_c[paneIndex], 8.0f) ||
hit_choice_pane(mpTmSel_c[paneIndex], 24.0f) ||
hit_choice_pane(mpTmrSel_c[paneIndex], 24.0f) ||
hit_choice_pane(mpKahen_c[paneIndex], 8.0f) ||
hit_choice_pane(mpCursor_c[paneIndex], 8.0f);
for (int i = 0; i < 5 && !hit; ++i) {
hit = hit_choice_pane(mpSelCldw_c[i][paneIndex], 8.0f);
}
if (!hit) {
continue;
}
mDPDPoint = choice;
field_0x110 = paneIndex;
dusk::menu_pointer::set_dialog_choice(choice, dusk::menu_pointer::state().clicked);
return true;
}
return false;
}
bool dMsgScrn3Select_c::consumePointerClick() {
u8 choice = 0xFF;
return dusk::menu_pointer::consume_dialog_click(choice);
}
#endif
bool dMsgScrn3Select_c::selAnimeEnd() {
if (mProcess == PROC_MAX_e) {
return true;
+4
View File
@@ -643,6 +643,10 @@ f32 dMsgScrnExplain_c::getAlphaRatio() {
bool dMsgScrnExplain_c::checkTriggerA() {
if (mDoCPd_c::getTrigA(PAD_1)) {
return true;
#if TARGET_PC
} else if (mpSelect_c != NULL && mpSelect_c->consumePointerClick()) {
return true;
#endif
} else {
return false;
}
+15 -13
View File
@@ -181,20 +181,22 @@ void OSWaitCond(OSCond* cond, OSMutex* mutex) {
mutex->count = 0;
mutex->thread = nullptr;
// Unlock the recursive mutex the same number of times it was locked
for (s32 i = 0; i < savedCount; i++) {
mutexData.nativeMutex.unlock();
}
// Wait on the condition variable
{
std::unique_lock<std::recursive_mutex> lock(mutexData.nativeMutex);
// Keep one recursion level held so cv.wait() is what releases the mutex;
// fully unlocking before the wait opens a window where a signal is lost.
if (savedCount >= 1) {
for (s32 i = 1; i < savedCount; i++) {
mutexData.nativeMutex.unlock();
}
std::unique_lock lock(mutexData.nativeMutex, std::adopt_lock);
condData.cv.wait(lock);
lock.release();
for (s32 i = 1; i < savedCount; i++) {
mutexData.nativeMutex.lock();
}
} else {
// Mutex wasn't held on entry (contract violation); wait anyway.
std::unique_lock lock(mutexData.nativeMutex);
condData.cv.wait(lock);
}
// Re-lock the recursive mutex the same number of times
for (s32 i = 0; i < savedCount; i++) {
mutexData.nativeMutex.lock();
}
// Restore GC mutex state
+61 -20
View File
@@ -8,10 +8,19 @@ namespace dusk {
static std::array<std::array<ActionBindPressData, static_cast<int>(ActionBinds::COUNT)>, PAD_CHANMAX> actionPressData{};
struct VirtualActionBindData {
bool pressed = false;
bool available = false;
};
static std::array<std::array<VirtualActionBindData, static_cast<int>(ActionBinds::COUNT)>, PAD_CHANMAX> virtualActionData{};
ActionBindsMap& getActionBinds() {
static ActionBindsMap actionBinds = {
{ActionBinds::FIRST_PERSON_CAMERA, {&getSettings().actionBindings.firstPersonCamera, "First Person Camera"}},
{ActionBinds::CALL_MIDNA, {&getSettings().actionBindings.callMidna, "Call Midna"}},
{ActionBinds::OPEN_MAP_SCREEN, {&getSettings().actionBindings.openMapScreen, "Open Map Screen"}},
{ActionBinds::TOGGLE_MINIMAP, {&getSettings().actionBindings.toggleMinimap, "Toggle Minimap"}},
{ActionBinds::OPEN_DUSKLIGHT_MENU, {&getSettings().actionBindings.openDusklightMenu, "Open Dusklight Menu"}},
{ActionBinds::TURBO_SPEED_BUTTON, {&getSettings().actionBindings.turboSpeedButton, "Turbo Speed Button"}},
};
@@ -25,6 +34,10 @@ bool isActionBound(ActionBinds action, u32 port) {
return false;
}
if (port < PAD_CHANMAX && virtualActionData[port][static_cast<int>(action)].available) {
return true;
}
return getActionBindButton(action, port) != PAD_NATIVE_BUTTON_INVALID;
}
@@ -41,43 +54,71 @@ void updateActionBindings() {
// If the action isn't bound, or if documents are visible and the action isn't
// opening the dusklight menu, don't update. Otherwise, we may accidentally
// perform actions while the dusklight menu is open.
if (!isActionBound(action, port) ||
const int button = boundAction.configVars->at(port);
const bool virtualAvailable = virtualActionData[port][static_cast<int>(action)].available;
if ((button == PAD_NATIVE_BUTTON_INVALID && !virtualAvailable) ||
(ui::any_document_visible() && action != ActionBinds::OPEN_DUSKLIGHT_MENU)) {
continue;
}
int button = boundAction.configVars->at(port);
// If keyboard is active for this port
u32 count = 0;
if (PADGetKeyButtonBindings(port, &count) != nullptr) {
int numKeys = 0;
const bool* kbState = SDL_GetKeyboardState(&numKeys);
if (kbState[button]) {
actionPressData[port][static_cast<int>(action)].pressedCurFrame = true;
}
} else {
// If controller is active
auto controller = aurora::input::get_controller_for_player(port);
if (controller) {
if (SDL_GetGamepadButton(controller->m_controller, static_cast<SDL_GamepadButton>(button))) {
if (button != PAD_NATIVE_BUTTON_INVALID) {
// If keyboard is active for this port
u32 count = 0;
if (PADGetKeyButtonBindings(port, &count) != nullptr) {
int numKeys = 0;
const bool* kbState = SDL_GetKeyboardState(&numKeys);
if (kbState[button]) {
actionPressData[port][static_cast<int>(action)].pressedCurFrame = true;
}
} else {
// If controller is active
auto controller = aurora::input::get_controller_for_player(port);
if (controller) {
if (SDL_GetGamepadButton(controller->m_controller, static_cast<SDL_GamepadButton>(button))) {
actionPressData[port][static_cast<int>(action)].pressedCurFrame = true;
}
}
}
}
}
for (auto& [action, _] : getActionBinds()) {
const auto& virtualAction = virtualActionData[port][static_cast<int>(action)];
if (virtualAction.available && virtualAction.pressed && !ui::any_document_visible()) {
actionPressData[port][static_cast<int>(action)].pressedCurFrame = true;
}
}
}
}
void setVirtualActionBind(ActionBinds action, u32 port, bool pressed, bool available) {
if (port >= PAD_CHANMAX) {
return;
}
virtualActionData[port][static_cast<int>(action)] = {
.pressed = pressed,
.available = available,
};
}
void clearVirtualActionBind(ActionBinds action, u32 port) {
if (port >= PAD_CHANMAX) {
return;
}
virtualActionData[port][static_cast<int>(action)] = {};
}
void clearAllVirtualActionBinds() {
virtualActionData = {};
}
bool getActionBindTrig(ActionBinds action, u32 port) {
return isActionBound(action, port) &&
actionPressData[port][static_cast<int>(action)].pressedCurFrame &&
return actionPressData[port][static_cast<int>(action)].pressedCurFrame &&
!actionPressData[port][static_cast<int>(action)].pressedPrevFrame;
}
bool getActionBindHold(ActionBinds action, u32 port) {
return isActionBound(action, port) &&
actionPressData[port][static_cast<int>(action)].pressedCurFrame &&
return actionPressData[port][static_cast<int>(action)].pressedCurFrame &&
actionPressData[port][static_cast<int>(action)].pressedPrevFrame;
}
+74
View File
@@ -0,0 +1,74 @@
#include "dusk/android_frame_rate.hpp"
#if defined(TARGET_ANDROID) || defined(__ANDROID__) || defined(ANDROID)
#include "dusk/settings.h"
#include <SDL3/SDL_system.h>
#include <jni.h>
namespace dusk::android {
namespace {
float preferred_surface_frame_rate() {
switch (getSettings().game.enableFrameInterpolation.getValue()) {
case FrameInterpMode::Off:
return 30.0f;
case FrameInterpMode::Unlimited:
default:
return 0.0f;
case FrameInterpMode::Capped:
return static_cast<float>(getSettings().video.maxFrameRate.getValue());
}
}
bool clear_pending_exception(JNIEnv* env) {
if (env == nullptr || !env->ExceptionCheck()) {
return false;
}
env->ExceptionClear();
return true;
}
} // namespace
void update_surface_frame_rate() {
auto* env = static_cast<JNIEnv*>(SDL_GetAndroidJNIEnv());
if (env == nullptr) {
return;
}
jobject activity = static_cast<jobject>(SDL_GetAndroidActivity());
if (activity == nullptr || clear_pending_exception(env)) {
if (activity != nullptr) {
env->DeleteLocalRef(activity);
}
return;
}
jclass activityClass = env->GetObjectClass(activity);
if (activityClass == nullptr || clear_pending_exception(env)) {
env->DeleteLocalRef(activity);
return;
}
jmethodID setPreferredFrameRate =
env->GetMethodID(activityClass, "setPreferredSurfaceFrameRate", "(F)V");
env->DeleteLocalRef(activityClass);
if (setPreferredFrameRate == nullptr || clear_pending_exception(env)) {
env->DeleteLocalRef(activity);
return;
}
jvalue args[1]{};
args[0].f = preferred_surface_frame_rate();
env->CallVoidMethodA(activity, setPreferredFrameRate, args);
env->DeleteLocalRef(activity);
clear_pending_exception(env);
}
} // namespace dusk::android
#else
namespace dusk::android {
void update_surface_frame_rate() {}
} // namespace dusk::android
#endif
+7
View File
@@ -0,0 +1,7 @@
#pragma once
namespace dusk::android {
void update_surface_frame_rate();
} // namespace dusk::android
+72
View File
@@ -0,0 +1,72 @@
#include "dusk/batch.hpp"
#include "dusk/logging.h"
#include <aurora/dl.hpp>
#include <dolphin/gx/GXEnum.h>
namespace dusk::batch {
void decode_leaf_template(const u8* dl, u32 size, LeafTemplate& out) {
out.vtxCount = 0;
out.posRefCount = 0;
bool posSeen[256] = {};
static constexpr GXVtxDescList kLeafDesc[] = {
{GX_VA_POS, GX_INDEX8},
{GX_VA_NRM, GX_INDEX8},
{GX_VA_CLR0, GX_INDEX8},
{GX_VA_TEX0, GX_INDEX8},
{GX_VA_NULL, GX_NONE},
};
aurora::gx::dl::Reader reader{dl, size, kLeafDesc};
while (const auto cmd = reader.next()) {
if (cmd->kind == aurora::gx::dl::Command::Kind::Passthrough) {
if (cmd->data[0] != GX_NOP) {
DuskLog.fatal("decode_leaf_template: unexpected opcode {:#x}", cmd->data[0]);
}
continue;
}
if (cmd->kind != aurora::gx::dl::Command::Kind::Draw) {
DuskLog.fatal("decode_leaf_template: unexpected pre-optimized draw");
}
const auto& draw = cmd->draw;
bool overflow = false;
const bool expanded =
aurora::gx::dl::expand_triangles(draw.prim, draw.vtxCount, [&](u16 i0, u16 i1, u16 i2) {
if (overflow || out.vtxCount + 3 > LeafTemplate::kMaxVtx) {
overflow = true;
return;
}
for (const u16 elem : {i0, i1, i2}) {
LeafTemplate::Vtx& v = out.vtx[out.vtxCount++];
v.pos = draw.attr_idx(elem, GX_VA_POS);
v.nrm = draw.attr_idx(elem, GX_VA_NRM);
v.clr = draw.attr_idx(elem, GX_VA_CLR0);
v.tex = draw.attr_idx(elem, GX_VA_TEX0);
if (!posSeen[v.pos]) {
posSeen[v.pos] = true;
if (out.posRefCount >= LeafTemplate::kMaxPosRefs) {
overflow = true;
return;
}
out.posRefs[out.posRefCount++] = v.pos;
}
}
});
if (!expanded) {
DuskLog.fatal("decode_leaf_template: untriangulable draw (prim {:#x}, {} verts)",
static_cast<u32>(draw.prim), draw.vtxCount);
}
if (overflow) {
DuskLog.fatal("decode_leaf_template: template overflow ({} verts, {} positions)",
out.vtxCount, out.posRefCount);
}
}
if (reader.failed()) {
DuskLog.fatal("decode_leaf_template: failed to walk display list");
}
}
} // namespace dusk::batch
+25
View File
@@ -0,0 +1,25 @@
#pragma once
#include <dolphin/types.h>
namespace dusk::batch {
struct LeafTemplate {
static constexpr u32 kMaxVtx = 192;
static constexpr u32 kMaxPosRefs = 64;
struct Vtx {
u8 pos;
u8 nrm;
u8 clr;
u8 tex;
};
Vtx vtx[kMaxVtx];
u16 vtxCount = 0;
u8 posRefs[kMaxPosRefs];
u8 posRefCount = 0;
};
void decode_leaf_template(const u8* dl, u32 size, LeafTemplate& out);
} // namespace dusk
+223 -51
View File
@@ -1,19 +1,23 @@
#include "dusk/config.hpp"
#include "absl/container/flat_hash_map.h"
#include "fmt/format.h"
#include "nlohmann/json.hpp"
#include "absl/container/flat_hash_map.h"
#include "aurora/lib/logging.hpp"
#include "dusk/io.hpp"
#include "dusk/settings.h"
#include <limits>
#include <cmath>
#include <filesystem>
#include <system_error>
#include <limits>
#include <optional>
#include <string>
#include <string_view>
#include <system_error>
#include <utility>
#include "dusk/main.h"
#include "dusk/action_bindings.h"
#include "dusk/main.h"
using namespace dusk::config;
@@ -26,6 +30,104 @@ aurora::Module DuskConfigLog("dusk::config");
static absl::flat_hash_map<std::string_view, ConfigVarBase*> RegisteredConfigVars;
static bool RegistrationDone = false;
static std::optional<dusk::ui::ControlAnchor> parse_control_anchor(std::string_view value) {
if (value == "none") {
return dusk::ui::ControlAnchor::None;
}
if (value == "top") {
return dusk::ui::ControlAnchor::Top;
}
if (value == "left") {
return dusk::ui::ControlAnchor::Left;
}
if (value == "bottom") {
return dusk::ui::ControlAnchor::Bottom;
}
if (value == "right") {
return dusk::ui::ControlAnchor::Right;
}
if (value == "topLeft") {
return dusk::ui::ControlAnchor::TopLeft;
}
if (value == "topRight") {
return dusk::ui::ControlAnchor::TopRight;
}
if (value == "bottomLeft") {
return dusk::ui::ControlAnchor::BottomLeft;
}
if (value == "bottomRight") {
return dusk::ui::ControlAnchor::BottomRight;
}
return std::nullopt;
}
static const char* control_anchor_value(dusk::ui::ControlAnchor anchor) {
switch (anchor) {
case dusk::ui::ControlAnchor::None:
return "none";
case dusk::ui::ControlAnchor::Top:
return "top";
case dusk::ui::ControlAnchor::Left:
return "left";
case dusk::ui::ControlAnchor::Bottom:
return "bottom";
case dusk::ui::ControlAnchor::Right:
return "right";
case dusk::ui::ControlAnchor::TopLeft:
return "topLeft";
case dusk::ui::ControlAnchor::TopRight:
return "topRight";
case dusk::ui::ControlAnchor::BottomLeft:
return "bottomLeft";
case dusk::ui::ControlAnchor::BottomRight:
return "bottomRight";
}
return "none";
}
static std::optional<float> json_finite_float(const json& object, const char* key) {
const auto iter = object.find(key);
if (iter == object.end() || !iter->is_number()) {
return std::nullopt;
}
const float value = iter->get<float>();
if (!std::isfinite(value)) {
return std::nullopt;
}
return value;
}
static std::optional<dusk::ui::ControlProps> parse_control_props(const json& value) {
if (!value.is_object()) {
return std::nullopt;
}
const auto x = json_finite_float(value, "x");
const auto y = json_finite_float(value, "y");
const auto w = json_finite_float(value, "w");
const auto h = json_finite_float(value, "h");
const auto scale = json_finite_float(value, "scale");
const auto anchorIter = value.find("anchor");
if (!x || !y || !w || !h || !scale || anchorIter == value.end() || !anchorIter->is_string()) {
return std::nullopt;
}
const auto anchor = parse_control_anchor(anchorIter->get<std::string>());
if (!anchor || *w <= 0.0f || *h <= 0.0f || *scale <= 0.0f) {
return std::nullopt;
}
return dusk::ui::ControlProps{
.x = *x,
.y = *y,
.w = *w,
.h = *h,
.scale = *scale,
.anchor = *anchor,
};
}
static std::filesystem::path GetConfigJsonPath() {
return dusk::ConfigPath / ConfigFileName;
}
@@ -46,8 +148,8 @@ static void ReplaceFile(const std::filesystem::path& source, const std::filesyst
}
}
ConfigVarBase::ConfigVarBase(const char* name, const ConfigImplBase* impl) : name(name), registered(false), layer(ConfigVarLayer::Default), impl(impl) {
}
ConfigVarBase::ConfigVarBase(const char* name, const ConfigImplBase* impl)
: name(name), registered(false), layer(ConfigVarLayer::Default), impl(impl) {}
const char* ConfigVarBase::getName() const noexcept {
return name;
@@ -72,11 +174,13 @@ static T sanitizeEnumValue(const ConfigVar<T>& cVar, T value) {
return value;
}
template<ConfigValue T>
template <ConfigValue T>
void ConfigImpl<T>::loadFromJson(ConfigVar<T>& cVar, const json& jsonValue) {
if constexpr (std::is_enum_v<T>) {
if (jsonValue.is_boolean()) {
DuskConfigLog.error("Doing default migration of CVar {} from bool, enum values may not be what is expected!", cVar.getName());
DuskConfigLog.error("Doing default migration of CVar {} from bool, enum values may not "
"be what is expected!",
cVar.getName());
using Underlying = std::underlying_type_t<T>;
const bool b = jsonValue.get<bool>();
@@ -91,13 +195,14 @@ void ConfigImpl<T>::loadFromJson(ConfigVar<T>& cVar, const json& jsonValue) {
cVar.setValue(sanitizeEnumValue(cVar, jsonValue.get<T>()), false);
}
template<ConfigValue T>
template <ConfigValue T>
nlohmann::json ConfigImpl<T>::dumpToJson(const ConfigVar<T>& cVar) {
return cVar.getValueForSave();
}
template<ConfigValue T> requires std::is_integral_v<T> && std::is_signed_v<T>
static void loadFromArgImpl(ConfigVar<T>& cVar, const std::string_view stringValue) {
template <ConfigValue T>
requires std::is_integral_v<T>&& std::is_signed_v<T> static void loadFromArgImpl(
ConfigVar<T>& cVar, const std::string_view stringValue) {
const std::string str(stringValue);
const auto result = std::stoll(str);
if (result >= std::numeric_limits<T>::min() && result <= std::numeric_limits<T>::max()) {
@@ -107,8 +212,9 @@ static void loadFromArgImpl(ConfigVar<T>& cVar, const std::string_view stringVal
}
}
template<ConfigValue T> requires std::is_integral_v<T> && std::is_unsigned_v<T>
static void loadFromArgImpl(ConfigVar<T>& cVar, const std::string_view stringValue) {
template <ConfigValue T>
requires std::is_integral_v<T>&& std::is_unsigned_v<T> static void loadFromArgImpl(
ConfigVar<T>& cVar, const std::string_view stringValue) {
const std::string str(stringValue);
const auto result = std::stoull(str);
if (result <= std::numeric_limits<T>::max()) {
@@ -134,14 +240,17 @@ static void loadFromArgImpl(ConfigVar<std::string>& cVar, const std::string_view
cVar.setOverrideValue(std::string(stringValue));
}
template<ConfigValue T> requires std::is_enum_v<T>
static void loadFromArgImpl(ConfigVar<T>& cVar, const std::string_view stringValue) {
template <ConfigValue T>
requires std::is_enum_v<T> static void loadFromArgImpl(
ConfigVar<T>& cVar, const std::string_view stringValue) {
using Underlying = std::underlying_type_t<T>;
const std::string str(stringValue);
if constexpr (std::is_signed_v<Underlying>) {
const auto result = std::stoll(str);
if (result >= std::numeric_limits<Underlying>::min() && result <= std::numeric_limits<Underlying>::max()) {
if (result >= std::numeric_limits<Underlying>::min() &&
result <= std::numeric_limits<Underlying>::max())
{
cVar.setOverrideValue(sanitizeEnumValue(cVar, static_cast<T>(result)));
} else {
throw std::out_of_range("Value is too large");
@@ -156,16 +265,20 @@ static void loadFromArgImpl(ConfigVar<T>& cVar, const std::string_view stringVal
}
}
template<ConfigValue T>
template <ConfigValue T>
void ConfigImpl<T>::loadFromArg(ConfigVar<T>& cVar, const std::string_view stringValue) {
loadFromArgImpl(cVar, stringValue);
}
template<>
template <>
void ConfigImpl<bool>::loadFromArg(ConfigVar<bool>& cVar, const std::string_view stringValue) {
if (stringValue == "1" || stringValue == "TRUE" || stringValue == "true" || stringValue == "True") {
if (stringValue == "1" || stringValue == "TRUE" || stringValue == "true" ||
stringValue == "True")
{
cVar.setOverrideValue(true);
} else if (stringValue == "0" || stringValue == "FALSE" || stringValue == "false" || stringValue == "False") {
} else if (stringValue == "0" || stringValue == "FALSE" || stringValue == "false" ||
stringValue == "False")
{
cVar.setOverrideValue(false);
} else {
throw InvalidConfigError("Value cannot be parsed as boolean");
@@ -174,42 +287,103 @@ void ConfigImpl<bool>::loadFromArg(ConfigVar<bool>& cVar, const std::string_view
// My IDE is convinced this namespace is necessary. It shouldn't be AFAICT?
namespace dusk::config {
template class ConfigImpl<bool>;
template class ConfigImpl<s8>;
template class ConfigImpl<u8>;
template class ConfigImpl<s16>;
template class ConfigImpl<u16>;
template class ConfigImpl<s32>;
template class ConfigImpl<u32>;
template class ConfigImpl<s64>;
template class ConfigImpl<u64>;
template class ConfigImpl<f32>;
template class ConfigImpl<f64>;
template class ConfigImpl<std::string>;
template class ConfigImpl<dusk::BloomMode>;
template class ConfigImpl<dusk::DepthOfFieldMode>;
template class ConfigImpl<dusk::DiscVerificationState>;
template class ConfigImpl<dusk::GameLanguage>;
template class ConfigImpl<dusk::GyroMode>;
template class ConfigImpl<bool>;
template class ConfigImpl<s8>;
template class ConfigImpl<u8>;
template class ConfigImpl<s16>;
template class ConfigImpl<u16>;
template class ConfigImpl<s32>;
template class ConfigImpl<u32>;
template class ConfigImpl<s64>;
template class ConfigImpl<u64>;
template class ConfigImpl<f32>;
template class ConfigImpl<f64>;
template class ConfigImpl<std::string>;
template class ConfigImpl<dusk::BloomMode>;
template class ConfigImpl<dusk::DepthOfFieldMode>;
template class ConfigImpl<dusk::DiscVerificationState>;
template class ConfigImpl<dusk::GameLanguage>;
template<> void ConfigImpl<FrameInterpMode>::loadFromJson(ConfigVar<FrameInterpMode>& cVar, const json& jsonValue) {
if (jsonValue.is_boolean()) {
const bool b = jsonValue.get<bool>();
template <>
void ConfigImpl<FrameInterpMode>::loadFromJson(
ConfigVar<FrameInterpMode>& cVar, const json& jsonValue) {
if (jsonValue.is_boolean()) {
const bool b = jsonValue.get<bool>();
const FrameInterpMode mode = b ? FrameInterpMode::Unlimited : FrameInterpMode::Off;
const FrameInterpMode mode = b ? FrameInterpMode::Unlimited : FrameInterpMode::Off;
cVar.setValue(sanitizeEnumValue(cVar, mode), false);
return;
cVar.setValue(sanitizeEnumValue(cVar, mode), false);
return;
}
cVar.setValue(sanitizeEnumValue(cVar, jsonValue.get<FrameInterpMode>()), false);
}
template <>
void ConfigImpl<ui::ControlLayout>::loadFromJson(
ConfigVar<ui::ControlLayout>& cVar, const json& jsonValue) {
if (!jsonValue.is_object()) {
return;
}
const int version = jsonValue.value("version", 0);
if (version != ui::ControlLayout::Version) {
return;
}
const auto controlsIter = jsonValue.find("controls");
if (controlsIter == jsonValue.end() || !controlsIter->is_object()) {
return;
}
ui::ControlLayout layout{.version = version};
for (const auto& control : controlsIter->items()) {
if (!ui::is_control_layout_id(control.key())) {
continue;
}
cVar.setValue(sanitizeEnumValue(cVar, jsonValue.get<FrameInterpMode>()), false);
if (const auto props = parse_control_props(control.value())) {
layout.controls[control.key()] = *props;
}
}
template class ConfigImpl<dusk::FrameInterpMode>;
template class ConfigImpl<dusk::MenuScaling>;
template class ConfigImpl<dusk::Resampler>;
template class ConfigImpl<dusk::MagicArmorMode>;
cVar.setValue(std::move(layout), false);
}
template <>
void ConfigImpl<ui::ControlLayout>::loadFromArg(
ConfigVar<ui::ControlLayout>&, const std::string_view) {
throw InvalidConfigError("Touch control layout cannot be parsed from launch arguments");
}
template <>
nlohmann::json ConfigImpl<ui::ControlLayout>::dumpToJson(const ConfigVar<ui::ControlLayout>& cVar) {
const auto& layout = cVar.getValueForSave();
json controls = json::object();
for (const auto& [id, props] : layout.controls) {
controls[id] = {
{"x", props.x},
{"y", props.y},
{"w", props.w},
{"h", props.h},
{"scale", props.scale},
{"anchor", control_anchor_value(props.anchor)},
};
}
return {
{"version", ui::ControlLayout::Version},
{"controls", std::move(controls)},
};
}
template class ConfigImpl<dusk::FrameInterpMode>;
template class ConfigImpl<dusk::MenuScaling>;
template class ConfigImpl<dusk::Resampler>;
template class ConfigImpl<dusk::MagicArmorMode>;
template class ConfigImpl<dusk::ui::ControlLayout>;
} // namespace dusk::config
void dusk::config::Register(ConfigVarBase& configVar) {
const auto& name = configVar.getName();
if (RegistrationDone) {
@@ -298,9 +472,7 @@ void dusk::config::Save() {
}
const auto configPathString = io::fs_path_to_string(configJsonPath);
DuskConfigLog.info(
"Saving config to '{}'",
configPathString);
DuskConfigLog.info("Saving config to '{}'", configPathString);
json j;
+1 -10
View File
@@ -70,16 +70,7 @@ bool rollgoal_gyro_enabled() {
}
bool queryGyroAimContext() {
if (!static_cast<bool>(getSettings().game.enableGyroAim)) {
return false;
}
daAlink_c* link = daAlink_getAlinkActorClass();
if (link == nullptr) {
return false;
}
return link->checkAimContext() && dComIfGp_checkCameraAttentionStatus(link->field_0x317c, 0x10);
return getSettings().game.enableGyroAim.getValue() && dCamera_c::isAimActive();
}
void read(float dt) {
+386
View File
@@ -0,0 +1,386 @@
#include "dusk/menu_pointer.h"
#include "m_Do/m_Do_graphic.h"
#include "d/d_pane_class.h"
#include "dusk/settings.h"
#include <aurora/rmlui.hpp>
#include <dolphin/pad.h>
#include <algorithm>
namespace dusk::menu_pointer {
namespace {
State s_state;
bool s_clickConsumed = false;
Context s_lastContext = Context::None;
Context s_currentContext = Context::None;
u8 s_lastDialogChoice = 0xFF;
u8 s_currentDialogChoice = 0xFF;
bool s_lastDialogChoiceValid = false;
bool s_currentDialogChoiceValid = false;
bool s_lastDialogClicked = false;
bool s_currentDialogClicked = false;
bool s_mouseActive = false;
bool s_mouseButtonCaptured = false;
s32 s_mouseButton = -1;
u32 s_suppressedPadHoldMask = 0;
u32 s_suppressedPadNextReadMask = 0;
Context s_deferredActivationContext = Context::None;
u8 s_deferredActivationTarget = 0xFF;
s32 scancode_from_rml_button(s32 button) noexcept {
switch (button) {
case 0:
return PAD_KEY_MOUSE_LEFT;
case 1:
return PAD_KEY_MOUSE_RIGHT;
case 2:
return PAD_KEY_MOUSE_MIDDLE;
default:
return PAD_KEY_INVALID;
}
}
bool is_mouse_scancode(s32 scancode) noexcept {
return scancode >= PAD_KEY_MOUSE_X2 && scancode <= PAD_KEY_MOUSE_LEFT;
}
PADButton pad_button_for_scancode(u32 port, s32 scancode) noexcept {
u32 count = 0;
PADKeyButtonBinding* bindings = PADGetKeyButtonBindings(port, &count);
if (bindings == nullptr) {
return 0;
}
for (u32 i = 0; i < count; ++i) {
if (bindings[i].scancode == scancode) {
return bindings[i].padButton;
}
}
return 0;
}
s32 menu_confirm_mouse_scancode() noexcept {
constexpr u32 port = PAD_CHAN0;
u32 count = 0;
PADKeyButtonBinding* bindings = PADGetKeyButtonBindings(port, &count);
if (bindings == nullptr) {
return PAD_KEY_MOUSE_LEFT;
}
for (u32 i = 0; i < count; ++i) {
if (bindings[i].padButton == PAD_BUTTON_A && is_mouse_scancode(bindings[i].scancode)) {
return bindings[i].scancode;
}
}
return pad_button_for_scancode(port, PAD_KEY_MOUSE_LEFT) != 0 ? PAD_KEY_INVALID :
PAD_KEY_MOUSE_LEFT;
}
bool mouse_button_is_menu_confirm(s32 button) noexcept {
const s32 scancode = scancode_from_rml_button(button);
return scancode != PAD_KEY_INVALID && scancode == menu_confirm_mouse_scancode();
}
void suppress_pad_for_mouse_button(s32 button, bool held) noexcept {
const s32 scancode = scancode_from_rml_button(button);
if (scancode == PAD_KEY_INVALID) {
return;
}
const PADButton padButton = pad_button_for_scancode(PAD_CHAN0, scancode);
if (padButton == 0) {
return;
}
s_suppressedPadNextReadMask |= padButton;
if (held) {
s_suppressedPadHoldMask |= padButton;
} else {
s_suppressedPadHoldMask &= ~padButton;
}
}
void set_position_from_rml(f32 x, f32 y) noexcept {
auto* context = aurora::rmlui::get_context();
if (context == nullptr) {
return;
}
const auto dimensions = context->GetDimensions();
const f32 width = std::max(static_cast<f32>(dimensions.x), 1.0f);
const f32 height = std::max(static_cast<f32>(dimensions.y), 1.0f);
s_state.x = mDoGph_gInf_c::getMinXF() + x / width * mDoGph_gInf_c::getWidthF();
s_state.y = mDoGph_gInf_c::getMinYF() + y / height * mDoGph_gInf_c::getHeightF();
s_state.valid = true;
}
void clear_input_state() noexcept {
s_state = {};
s_clickConsumed = false;
s_lastDialogChoice = 0xFF;
s_currentDialogChoice = 0xFF;
s_lastDialogChoiceValid = false;
s_currentDialogChoiceValid = false;
s_lastDialogClicked = false;
s_currentDialogClicked = false;
s_mouseActive = false;
s_mouseButtonCaptured = false;
s_mouseButton = -1;
s_suppressedPadHoldMask = 0;
s_suppressedPadNextReadMask = 0;
s_deferredActivationContext = Context::None;
s_deferredActivationTarget = 0xFF;
}
} // namespace
bool handle_fallthrough_pointer(f32 x, f32 y, Phase phase, bool touch, s32 mouseButton) noexcept {
if (!enabled()) {
return false;
}
s_clickConsumed = false;
if (!touch) {
if (phase == Phase::Press) {
if (!mouse_button_is_menu_confirm(mouseButton)) {
return false;
}
s_mouseButtonCaptured = true;
s_mouseButton = mouseButton;
suppress_pad_for_mouse_button(mouseButton, true);
} else if (phase == Phase::Release) {
if (!s_mouseButtonCaptured || s_mouseButton != mouseButton) {
return false;
}
suppress_pad_for_mouse_button(mouseButton, false);
s_mouseButtonCaptured = false;
s_mouseButton = -1;
} else if (phase == Phase::Cancel) {
if (s_mouseButtonCaptured) {
suppress_pad_for_mouse_button(s_mouseButton, false);
s_mouseButtonCaptured = false;
s_mouseButton = -1;
} else if (!s_mouseActive) {
return false;
}
}
s_mouseActive = true;
}
if (phase != Phase::Cancel) {
set_position_from_rml(x, y);
}
s_state.touch = touch;
switch (phase) {
case Phase::Press:
s_state.down = true;
s_state.pressed = true;
break;
case Phase::Release:
s_state.down = false;
s_state.released = true;
s_state.clicked = true;
break;
case Phase::Cancel:
s_state.down = false;
break;
case Phase::Move:
default:
break;
}
return true;
}
void begin_game_frame() noexcept {
s_currentContext = Context::None;
s_currentDialogChoice = 0xFF;
s_currentDialogChoiceValid = false;
s_currentDialogClicked = false;
s_clickConsumed = false;
if (!enabled()) {
clear_input_state();
}
}
void end_game_frame() noexcept {
s_lastContext = s_currentContext;
s_lastDialogChoice = s_currentDialogChoice;
s_lastDialogChoiceValid = s_currentDialogChoiceValid;
s_lastDialogClicked = s_currentDialogClicked;
s_state.pressed = false;
s_state.released = false;
s_state.clicked = false;
if (!s_state.down) {
s_state.valid = false;
}
s_clickConsumed = false;
}
void begin_context(Context context) noexcept {
if (context == Context::None) {
return;
}
if (s_lastContext == Context::None && s_currentContext == Context::None) {
s_state = {};
s_mouseActive = false;
s_mouseButtonCaptured = false;
s_mouseButton = -1;
s_suppressedPadHoldMask = 0;
s_suppressedPadNextReadMask = 0;
s_deferredActivationContext = Context::None;
s_deferredActivationTarget = 0xFF;
}
s_currentContext = context;
}
bool active() noexcept {
return s_currentContext != Context::None || s_lastContext != Context::None;
}
bool enabled() noexcept {
return getSettings().game.enableMenuPointer.getValue();
}
bool mouse_capture_active() noexcept {
return enabled() && s_mouseButtonCaptured;
}
const State& state() noexcept {
return s_state;
}
bool consume_click() noexcept {
if (!s_state.clicked || s_clickConsumed) {
return false;
}
s_clickConsumed = true;
return true;
}
void set_dialog_choice(u8 choice, bool clicked) noexcept {
s_currentDialogChoice = choice;
s_currentDialogChoiceValid = true;
s_currentDialogClicked = clicked;
}
bool get_dialog_choice(u8& choice) noexcept {
if (s_currentDialogChoiceValid) {
choice = s_currentDialogChoice;
return true;
}
if (s_lastDialogChoiceValid) {
choice = s_lastDialogChoice;
return true;
}
return false;
}
bool consume_dialog_click(u8& choice) noexcept {
if (s_currentDialogChoiceValid && s_currentDialogClicked) {
choice = s_currentDialogChoice;
s_currentDialogClicked = false;
return true;
}
if (s_lastDialogChoiceValid && s_lastDialogClicked) {
choice = s_lastDialogChoice;
s_lastDialogClicked = false;
return true;
}
return false;
}
void defer_activation(Context context, u8 target) noexcept {
s_deferredActivationContext = context;
s_deferredActivationTarget = target;
}
bool consume_deferred_activation(Context context, u8 target) noexcept {
if (s_deferredActivationContext != context || s_deferredActivationTarget != target) {
return false;
}
s_deferredActivationContext = Context::None;
s_deferredActivationTarget = 0xFF;
return true;
}
void clear_deferred_activation(Context context) noexcept {
if (s_deferredActivationContext != context) {
return;
}
s_deferredActivationContext = Context::None;
s_deferredActivationTarget = 0xFF;
}
u32 suppressed_pad_buttons(u32 port) noexcept {
if (port != PAD_CHAN0) {
return 0;
}
return s_suppressedPadHoldMask | s_suppressedPadNextReadMask;
}
void finish_pad_suppression_read(u32 port) noexcept {
if (port != PAD_CHAN0) {
return;
}
s_suppressedPadNextReadMask = 0;
}
bool hit_rect(f32 left, f32 top, f32 right, f32 bottom, f32 padding) noexcept {
const auto& state = menu_pointer::state();
if (!state.valid) {
return false;
}
if (left > right) {
std::swap(left, right);
}
if (top > bottom) {
std::swap(top, bottom);
}
return state.x >= left - padding && state.x <= right + padding && state.y >= top - padding &&
state.y <= bottom + padding;
}
bool hit_pane(CPaneMgr* pane, f32 padding) noexcept {
if (pane == nullptr || pane->getPanePtr() == nullptr) {
return false;
}
Mtx mtx;
Vec v0 = pane->getGlobalVtx(&mtx, 0, false, 0);
Vec v1 = pane->getGlobalVtx(&mtx, 1, false, 0);
Vec v2 = pane->getGlobalVtx(&mtx, 2, false, 0);
Vec v3 = pane->getGlobalVtx(&mtx, 3, false, 0);
const f32 left = std::min({v0.x, v1.x, v2.x, v3.x});
const f32 right = std::max({v0.x, v1.x, v2.x, v3.x});
const f32 top = std::min({v0.y, v1.y, v2.y, v3.y});
const f32 bottom = std::max({v0.y, v1.y, v2.y, v3.y});
return hit_rect(left, top, right, bottom, padding);
}
bool hit_pane(J2DPane* pane, f32 padding) noexcept {
if (pane == nullptr || !pane->isVisible()) {
return false;
}
const JGeometry::TBox2<f32>& bounds = pane->getBounds();
return hit_rect(bounds.i.x, bounds.i.y, bounds.f.x, bounds.f.y, padding);
}
} // namespace dusk::menu_pointer
+3 -11
View File
@@ -1,4 +1,5 @@
#include "dusk/mouse.h"
#include "dusk/menu_pointer.h"
#include "dusk/settings.h"
#include "dusk/ui/ui.hpp"
#include "d/actor/d_a_alink.h"
@@ -26,16 +27,7 @@ void reset_deltas() {
}
bool queryMouseAimContext() {
if (!getSettings().game.enableMouseAim) {
return false;
}
daAlink_c* link = daAlink_getAlinkActorClass();
if (link == nullptr) {
return false;
}
return link->checkAimContext() && dComIfGp_checkCameraAttentionStatus(link->field_0x317c, 0x10);
return getSettings().game.enableMouseAim.getValue() && dCamera_c::isAimActive();
}
bool wantMouseCapture() {
@@ -50,7 +42,7 @@ bool isWindowFocused(SDL_Window* window) {
}
bool shouldCaptureMouse(SDL_Window* window) {
if (window == nullptr || ui::any_document_visible()) {
if (window == nullptr || ui::any_document_visible() || menu_pointer::active()) {
return false;
}
return wantMouseCapture() && isWindowFocused(window);
+36
View File
@@ -14,6 +14,9 @@ UserSettings g_userSettings = {
.enableFpsOverlay {"game.enableFpsOverlay", false},
.fpsOverlayCorner {"game.fpsOverlayCorner", 0},
.maxFrameRate {"video.maxFrameRate", 240},
.rememberWindowSize {"video.rememberWindowSize", false},
.lastWindowWidth {"video.lastWindowWidth", 0},
.lastWindowHeight {"video.lastWindowHeight", 0},
},
.audio = {
@@ -95,6 +98,9 @@ UserSettings g_userSettings = {
.mouseCameraSensitivity {"game.mouseCameraSensitivity", 1.0f},
.invertMouseY {"game.invertMouseY", false},
.freeCamera {"game.freeCamera", false},
.enableTouchControls {"game.enableTouchControls", false},
.enableMenuPointer {"game.enableMenuPointer", true},
.touchControlsLayout {"game.touchControlsLayout", ui::ControlLayout{}},
.invertCameraXAxis {"game.invertCameraXAxis", false},
.invertCameraYAxis {"game.invertCameraYAxis", false},
.invertFirstPersonXAxis {"game.invertFirstPersonXAxis", false},
@@ -103,6 +109,8 @@ UserSettings g_userSettings = {
.invertAirSwimY {"game.invertAirSwimY", false},
.freeCameraXSensitivity {"game.freeCameraXSensitivity", 1.0f},
.freeCameraYSensitivity {"game.freeCameraYSensitivity", 1.0f},
.touchCameraXSensitivity {"game.touchCameraXSensitivity", 1.0f},
.touchCameraYSensitivity {"game.touchCameraYSensitivity", 1.0f},
.debugFlyCam {"game.debugFlyCam", false},
.debugFlyCamLockEvents {"game.debugFlyCamLockEvents", true},
.allowBackgroundInput {"game.allowBackgroundInput", true},
@@ -179,6 +187,18 @@ UserSettings g_userSettings = {
ActionBindConfigVar{"actionBindings.callMidna_port2", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.callMidna_port3", PAD_NATIVE_BUTTON_INVALID},
},
.openMapScreen {
ActionBindConfigVar{"actionBindings.openMapScreen_port0", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.openMapScreen_port1", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.openMapScreen_port2", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.openMapScreen_port3", PAD_NATIVE_BUTTON_INVALID},
},
.toggleMinimap {
ActionBindConfigVar{"actionBindings.toggleMinimap_port0", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.toggleMinimap_port1", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.toggleMinimap_port2", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.toggleMinimap_port3", PAD_NATIVE_BUTTON_INVALID},
},
.openDusklightMenu {
ActionBindConfigVar{"actionBindings.openDusklightMenu_port0", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.openDusklightMenu_port1", PAD_NATIVE_BUTTON_INVALID},
@@ -228,6 +248,9 @@ void registerSettings() {
Register(g_userSettings.video.enableFpsOverlay);
Register(g_userSettings.video.fpsOverlayCorner);
Register(g_userSettings.video.maxFrameRate);
Register(g_userSettings.video.rememberWindowSize);
Register(g_userSettings.video.lastWindowWidth);
Register(g_userSettings.video.lastWindowHeight);
// Audio
Register(g_userSettings.audio.masterVolume);
@@ -268,6 +291,8 @@ void registerSettings() {
Register(g_userSettings.game.invertAirSwimY);
Register(g_userSettings.game.freeCameraXSensitivity);
Register(g_userSettings.game.freeCameraYSensitivity);
Register(g_userSettings.game.touchCameraXSensitivity);
Register(g_userSettings.game.touchCameraYSensitivity);
Register(g_userSettings.game.minimalHUD);
Register(g_userSettings.game.hudScale);
Register(g_userSettings.game.pauseOnFocusLost);
@@ -332,6 +357,9 @@ void registerSettings() {
Register(g_userSettings.game.mouseCameraSensitivity);
Register(g_userSettings.game.invertMouseY);
Register(g_userSettings.game.freeCamera);
Register(g_userSettings.game.enableTouchControls);
Register(g_userSettings.game.enableMenuPointer);
Register(g_userSettings.game.touchControlsLayout);
Register(g_userSettings.game.debugFlyCam);
Register(g_userSettings.game.debugFlyCamLockEvents);
Register(g_userSettings.game.allowBackgroundInput);
@@ -362,6 +390,14 @@ void registerSettings() {
Register(g_userSettings.actionBindings.callMidna[1]);
Register(g_userSettings.actionBindings.callMidna[2]);
Register(g_userSettings.actionBindings.callMidna[3]);
Register(g_userSettings.actionBindings.openMapScreen[0]);
Register(g_userSettings.actionBindings.openMapScreen[1]);
Register(g_userSettings.actionBindings.openMapScreen[2]);
Register(g_userSettings.actionBindings.openMapScreen[3]);
Register(g_userSettings.actionBindings.toggleMinimap[0]);
Register(g_userSettings.actionBindings.toggleMinimap[1]);
Register(g_userSettings.actionBindings.toggleMinimap[2]);
Register(g_userSettings.actionBindings.toggleMinimap[3]);
Register(g_userSettings.actionBindings.openDusklightMenu[0]);
Register(g_userSettings.actionBindings.openDusklightMenu[1]);
Register(g_userSettings.actionBindings.openDusklightMenu[2]);
+26
View File
@@ -0,0 +1,26 @@
#include "dusk/touch_camera.h"
namespace dusk::touch_camera {
namespace {
float s_yaw_dp = 0.0f;
float s_pitch_dp = 0.0f;
} // namespace
void add_delta(float yaw_dp, float pitch_dp) noexcept {
s_yaw_dp += yaw_dp;
s_pitch_dp += pitch_dp;
}
bool consume_delta(float& yaw_dp, float& pitch_dp) noexcept {
yaw_dp = s_yaw_dp;
pitch_dp = s_pitch_dp;
clear();
return yaw_dp != 0.0f || pitch_dp != 0.0f;
}
void clear() noexcept {
s_yaw_dp = 0.0f;
s_pitch_dp = 0.0f;
}
} // namespace dusk::touch_camera
-2
View File
@@ -19,8 +19,6 @@ public:
void set_text(const Rml::String& text);
Button& on_pressed(ButtonCallback callback);
const Rml::String& get_text() const { return mProps.text; }
private:
void update_props(Props props);
+14
View File
@@ -861,6 +861,20 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
break;
}
case Page::Rumble: {
if (PADCanForceDeviceRumble(static_cast<u32>(port))) {
pane.add_child<BoolButton>(BoolButton::Props{
.key = "Use Device Haptics",
.getValue = [port] { return PADGetForceDeviceRumble(static_cast<u32>(port)); },
.setValue =
[port](bool value) {
PADSetForceDeviceRumble(static_cast<u32>(port), value ? TRUE : FALSE);
PADSerializeMappings();
},
.isDisabled = [this] { return mRumbleTestActive; },
});
pane.add_text("Use native device haptics instead of controller rumble. "
"Useful for devices with built-in gamepads.");
}
auto& rumbleTest = pane.add_select_button({
.key = "Test Rumble",
.getValue =
+192
View File
@@ -0,0 +1,192 @@
#pragma once
#include <map>
namespace dusk::ui {
struct EquipTarget {
float left = 0.0f;
float top = 0.0f;
float width = 0.0f;
float height = 0.0f;
bool valid = false;
};
enum class Control {
A,
B,
X,
Y,
Z,
L,
R,
FIRST_PERSON,
ITEMS,
COLLECTIONS,
MAP,
SKIP,
DPAD_UP,
DPAD_DOWN,
DPAD_LEFT,
DPAD_RIGHT,
COUNT,
};
enum class ControlAnchor : u8 {
None,
Top,
Left,
Bottom,
Right,
TopLeft,
TopRight,
BottomLeft,
BottomRight,
};
struct ControlProps {
float x = 0.0f;
float y = 0.0f;
float w = 0.0f;
float h = 0.0f;
float scale = 1.0f;
ControlAnchor anchor = ControlAnchor::None;
};
struct ControlRect {
float l = 0.0f;
float t = 0.0f;
float w = 0.0f;
float h = 0.0f;
};
struct ResolvedControlLayout {
ControlRect visual;
ControlRect box;
float scale = 1.0f;
};
struct ControlLayoutSize {
float w = 0.0f;
float h = 0.0f;
};
struct ControlLayout {
static constexpr int Version = 1;
int version = Version;
std::map<std::string, ControlProps, std::less<> > controls;
};
constexpr std::array<std::string_view, 9> kControlLayoutIds = {
"actionBar",
"buttonA",
"buttonB",
"buttonX",
"buttonY",
"buttonZ",
"skip",
"triggerL",
"triggerR",
};
constexpr bool is_control_layout_id(std::string_view id) noexcept {
for (const auto knownId : kControlLayoutIds) {
if (id == knownId) {
return true;
}
}
return false;
}
constexpr ControlRect resolve_anchored_rect(
ControlAnchor anchor, float x, float y, float w, float h, ControlLayoutSize docSize) noexcept {
switch (anchor) {
case ControlAnchor::None:
return {x * docSize.w - w * 0.5f, y * docSize.h - h * 0.5f, w, h};
case ControlAnchor::Top:
return {x * docSize.w - w * 0.5f, y, w, h};
case ControlAnchor::Bottom:
return {x * docSize.w - w * 0.5f, docSize.h - y - h, w, h};
case ControlAnchor::Left:
return {x, y * docSize.h - h * 0.5f, w, h};
case ControlAnchor::Right:
return {docSize.w - x - w, y * docSize.h - h * 0.5f, w, h};
case ControlAnchor::TopLeft:
return {x, y, w, h};
case ControlAnchor::TopRight:
return {docSize.w - x - w, y, w, h};
case ControlAnchor::BottomLeft:
return {x, docSize.h - y - h, w, h};
case ControlAnchor::BottomRight:
return {docSize.w - x - w, docSize.h - y - h, w, h};
}
return {};
}
constexpr ResolvedControlLayout resolve_control_layout(
ControlProps props, ControlLayoutSize docSize) noexcept {
const float visualW = props.w * props.scale;
const float visualH = props.h * props.scale;
const ControlRect visual =
resolve_anchored_rect(props.anchor, props.x, props.y, visualW, visualH, docSize);
const ControlRect box = {
visual.l + (visual.w - props.w) * 0.5f,
visual.t + (visual.h - props.h) * 0.5f,
props.w,
props.h,
};
return {
.visual = visual,
.box = box,
.scale = props.scale,
};
}
constexpr ControlProps encode_control_props(ControlRect visual, ControlLayoutSize docSize,
ControlProps props, ControlAnchor anchor) noexcept {
props.anchor = anchor;
switch (anchor) {
case ControlAnchor::None:
props.x = (visual.l + visual.w * 0.5f) / docSize.w;
props.y = (visual.t + visual.h * 0.5f) / docSize.h;
break;
case ControlAnchor::Top:
props.x = (visual.l + visual.w * 0.5f) / docSize.w;
props.y = visual.t;
break;
case ControlAnchor::Bottom:
props.x = (visual.l + visual.w * 0.5f) / docSize.w;
props.y = docSize.h - visual.t - visual.h;
break;
case ControlAnchor::Left:
props.x = visual.l;
props.y = (visual.t + visual.h * 0.5f) / docSize.h;
break;
case ControlAnchor::Right:
props.x = docSize.w - visual.l - visual.w;
props.y = (visual.t + visual.h * 0.5f) / docSize.h;
break;
case ControlAnchor::TopLeft:
props.x = visual.l;
props.y = visual.t;
break;
case ControlAnchor::TopRight:
props.x = docSize.w - visual.l - visual.w;
props.y = visual.t;
break;
case ControlAnchor::BottomLeft:
props.x = visual.l;
props.y = docSize.h - visual.t - visual.h;
break;
case ControlAnchor::BottomRight:
props.x = docSize.w - visual.l - visual.w;
props.y = docSize.h - visual.t - visual.h;
break;
}
return props;
}
} // namespace dusk::ui
+44 -11
View File
@@ -3,7 +3,6 @@
#include "aurora/rmlui.hpp"
#include "ui.hpp"
#include "Z2AudioLib/Z2SeMgr.h"
#include "m_Do/m_Do_audio.h"
namespace dusk::ui {
@@ -19,32 +18,39 @@ Rml::ElementDocument* load_document(const Rml::String& source) {
} // namespace
Document::Document(const Rml::String& source) : mDocument(load_document(source)) {
Document::Document(const Rml::String& source, bool passive)
: mDocument(load_document(source)), mPassive(passive) {
// Block events while hidden (except for Menu command); play nav sounds when visible
listen(
Rml::EventId::Keydown,
[this](Rml::Event& event) {
if (mPassive) {
return;
}
const auto cmd = map_nav_event(event);
if (cmd != NavCommand::Menu && !visible()) {
if (cmd != NavCommand::Menu && (!visible() || !active())) {
event.StopImmediatePropagation();
}
},
true);
const auto blockUnlessVisible = [this](Rml::Event& event) {
if (!visible()) {
const auto blockUnlessActive = [this](Rml::Event& event) {
if (!visible() || !active()) {
event.StopImmediatePropagation();
}
};
listen(Rml::EventId::Mouseover, blockUnlessVisible, true);
listen(Rml::EventId::Click, blockUnlessVisible, true);
listen(Rml::EventId::Scroll, blockUnlessVisible, true);
listen(Rml::EventId::Mouseover, blockUnlessActive, true);
listen(Rml::EventId::Click, blockUnlessActive, true);
listen(Rml::EventId::Scroll, blockUnlessActive, true);
listen(Rml::EventId::Keydown, [this](Rml::Event& event) {
const auto cmd = map_nav_event(event);
if (cmd == NavCommand::None) {
if (mPassive) {
auto* doc = top_document();
if (doc != nullptr && doc->handle_nav_event(event)) {
event.StopPropagation();
}
return;
}
if (handle_nav_command(event, cmd)) {
if (handle_nav_event(event)) {
event.StopPropagation();
}
});
@@ -97,6 +103,18 @@ void Document::listen(Rml::Element* element, Rml::EventId event,
std::make_unique<ScopedEventListener>(element, event, std::move(callback), capture));
}
void Document::listen(Rml::Element* element, const Rml::String& event,
ScopedEventListener::Callback callback, bool capture) {
if (element == nullptr) {
element = mDocument;
}
if (element == nullptr || event.empty() || !callback) {
return;
}
mListeners.emplace_back(
std::make_unique<ScopedEventListener>(element, event, std::move(callback), capture));
}
bool Document::visible() const {
if (mDocument == nullptr) {
return false;
@@ -104,6 +122,21 @@ bool Document::visible() const {
return *mDocument->GetProperty(Rml::PropertyId::Visibility) == Rml::Style::Visibility::Visible;
}
bool Document::active() const {
return !mClosed && !mPendingClose;
}
bool Document::handle_nav_event(Rml::Event& event) {
if (!active()) {
return false;
}
const auto cmd = map_nav_event(event);
if (cmd == NavCommand::None || (cmd != NavCommand::Menu && !visible())) {
return false;
}
return handle_nav_command(event, cmd);
}
bool Document::handle_nav_command(Rml::Event& event, NavCommand cmd) {
if (cmd == NavCommand::Menu) {
mDoAud_seStartMenu(visible() ? kSoundMenuClose : kSoundMenuOpen);
+13 -4
View File
@@ -7,7 +7,7 @@ namespace dusk::ui {
class Document {
public:
Document(const Rml::String& source);
explicit Document(const Rml::String& source, bool passive = false);
virtual ~Document();
Document(const Document&) = delete;
@@ -18,12 +18,19 @@ public:
virtual void update();
virtual bool focus();
virtual bool visible() const;
virtual bool active() const;
void listen(Rml::Element* element, Rml::EventId event, ScopedEventListener::Callback callback,
bool capture = false);
void listen(Rml::Element* element, const Rml::String& event,
ScopedEventListener::Callback callback, bool capture = false);
void listen(Rml::EventId event, ScopedEventListener::Callback callback, bool capture = false) {
listen(mDocument, event, std::move(callback), capture);
}
void listen(
const Rml::String& event, ScopedEventListener::Callback callback, bool capture = false) {
listen(mDocument, event, std::move(callback), capture);
}
void toggle() {
if (visible()) {
hide(false);
@@ -35,14 +42,15 @@ public:
push_document(std::move(document));
hide(false);
}
void pop() {
void pop(bool show = true) {
hide(true);
show_top_document();
focus_top_document(show);
}
bool pending_close() const { return mPendingClose; }
bool closed() const { return mClosed; }
bool handle_nav_event(Rml::Event& event);
protected:
virtual bool handle_nav_command(Rml::Event& event, NavCommand cmd);
@@ -50,6 +58,7 @@ protected:
std::vector<std::unique_ptr<ScopedEventListener> > mListeners;
bool mPendingClose = false;
bool mClosed = false;
bool mPassive = false;
};
} // namespace dusk::ui
+899
View File
@@ -0,0 +1,899 @@
#include "icon_provider.hpp"
#include "d/dolzel.h" // IWYU pragma: keep
#ifdef AURORA_ENABLE_RMLUI
#include <SDL3/SDL_surface.h>
#include <aurora/lib/gfx/texture_convert.hpp>
#include <aurora/rmlui.hpp>
#include <fmt/format.h>
#include <algorithm>
#include <array>
#include <charconv>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#include <memory>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
#include "JSystem/J2DGraph/J2DPicture.h"
#include "JSystem/JUtility/JUTTexture.h"
#include "d/actor/d_a_alink.h"
#include "d/d_com_inf_game.h"
#include "d/d_item_data.h"
#include "d/d_meter2_info.h"
#include "d/d_pane_class.h"
namespace dusk::ui {
namespace {
constexpr std::string_view kScheme = "item";
constexpr std::string_view kSourcePrefix = "item://";
constexpr std::string_view kMeterScheme = "meter";
constexpr std::string_view kMeterSourcePrefix = "meter://";
constexpr size_t kItemTextureBufferSize = 0xC00;
constexpr size_t kMaxCachedIcons = 128;
constexpr uint64_t kMeterTextureSourceSlots = 8;
constexpr uint32_t kMinRenderedPaneIconSize = 128;
constexpr uint32_t kMaxRenderedPaneIconSize = 1024;
struct alignas(32) ItemTextureBuffer {
std::array<std::byte, kItemTextureBufferSize> bytes{};
std::byte* data() noexcept { return bytes.data(); }
const std::byte* data() const noexcept { return bytes.data(); }
};
struct CachedIcon {
std::vector<uint8_t> pixels;
uint32_t width = 0;
uint32_t height = 0;
};
struct RuntimeIconState {
CachedIcon icon;
uint64_t revision = 0;
bool valid = false;
};
struct LayerColors {
JUtility::TColor black;
JUtility::TColor white;
std::array<JUtility::TColor, 4> corner;
};
struct RectF {
float left = std::numeric_limits<float>::max();
float top = std::numeric_limits<float>::max();
float right = std::numeric_limits<float>::lowest();
float bottom = std::numeric_limits<float>::lowest();
bool valid() const noexcept { return left < right && top < bottom; }
float width() const noexcept { return right - left; }
float height() const noexcept { return bottom - top; }
void include(float x, float y) noexcept {
if (!std::isfinite(x) || !std::isfinite(y)) {
return;
}
left = std::min(left, x);
top = std::min(top, y);
right = std::max(right, x);
bottom = std::max(bottom, y);
}
void include(const RectF& rect) noexcept {
if (!rect.valid()) {
return;
}
include(rect.left, rect.top);
include(rect.right, rect.bottom);
}
};
struct PictureLayer {
J2DPicture* picture = nullptr;
RectF rect;
uint8_t alpha = 0;
};
struct SurfaceDeleter {
void operator()(SDL_Surface* surface) const noexcept { SDL_DestroySurface(surface); }
};
using SurfacePtr = std::unique_ptr<SDL_Surface, SurfaceDeleter>;
std::unordered_map<std::string, CachedIcon>& icon_cache() {
static auto* cache = new std::unordered_map<std::string, CachedIcon>();
return *cache;
}
RuntimeIconState& midna_icon_state() {
static auto* state = new RuntimeIconState();
return *state;
}
std::string_view strip_query(std::string_view path) noexcept {
const auto queryPos = path.find_first_of("?#");
if (queryPos != std::string_view::npos) {
path = path.substr(0, queryPos);
}
return path;
}
std::optional<u8> parse_item_no(std::string_view text) noexcept {
if (text.starts_with("0x") || text.starts_with("0X")) {
text.remove_prefix(2);
}
unsigned value = 0;
const auto* first = text.data();
const auto* last = text.data() + text.size();
const auto [ptr, ec] = std::from_chars(first, last, value, 16);
if (ec != std::errc() || ptr != last || value > 0xFF) {
return std::nullopt;
}
return static_cast<u8>(value);
}
bool is_valid_icon_item(u8 itemNo) noexcept {
return itemNo != 0 && itemNo != dItemNo_NONE_e;
}
u8 item_icon_texture_item(u8 itemNo) noexcept {
if (itemNo == dItemNo_LIGHT_ARROW_e) {
return dItemNo_BOW_e;
}
return itemNo;
}
std::optional<u8> selected_slot_item(int slot) noexcept {
const u8 itemNo = dComIfGp_getSelectItem(slot);
if (!is_valid_icon_item(itemNo)) {
return std::nullopt;
}
return item_icon_texture_item(itemNo);
}
bool is_sword_item(u8 itemNo) noexcept {
switch (itemNo) {
case dItemNo_WOOD_STICK_e:
case dItemNo_SWORD_e:
case dItemNo_MASTER_SWORD_e:
case dItemNo_LIGHT_SWORD_e:
return true;
default:
return false;
}
}
std::optional<u8> b_button_item() noexcept {
const u8 action = dComIfGp_getAStatus();
if (action == 0x26 || action == 0x2E) {
const u8 sword = dComIfGs_getSelectEquipSword();
if (is_sword_item(sword)) {
return sword;
}
return std::nullopt;
}
if (action == 0x4F) {
return dItemNo_LURE_ROD_e;
}
return std::nullopt;
}
std::optional<u8> item_for_source(std::string_view source) noexcept {
if (!source.starts_with(kSourcePrefix)) {
return std::nullopt;
}
std::string_view path = strip_query(source.substr(kSourcePrefix.size()));
if (path.starts_with("item/")) {
path.remove_prefix(5);
const auto itemNo = parse_item_no(path);
if (itemNo && is_valid_icon_item(*itemNo)) {
return item_icon_texture_item(*itemNo);
}
return std::nullopt;
}
if (path == "slot/x") {
return selected_slot_item(0);
}
if (path == "slot/y") {
return selected_slot_item(1);
}
if (path == "button/b") {
return b_button_item();
}
return std::nullopt;
}
uint32_t item_revision(u8 itemNo) noexcept {
uint32_t revision = itemNo;
revision = revision * 131u + g_meter2_info.getItemType(itemNo);
if (itemNo == dItemNo_KANTERA_e || itemNo == dItemNo_KANTERA2_e) {
revision = revision * 131u + (dComIfGs_getOil() == 0 ? 0u : 1u);
}
if (itemNo == dItemNo_COPY_ROD_e) {
auto* player = daPy_getPlayerActorClass();
revision = revision * 131u + (player != nullptr && player->checkCopyRodTopUse() ? 1u : 0u);
}
return revision;
}
std::string item_source_for_item(u8 itemNo) {
itemNo = item_icon_texture_item(itemNo);
return fmt::format("{}://item/{:02x}?rev={:08x}", kScheme, itemNo, item_revision(itemNo));
}
std::optional<int> selected_slot_count(int slot) noexcept {
const u8 itemNo = dComIfGp_getSelectItem(slot);
if (!is_valid_icon_item(itemNo)) {
return std::nullopt;
}
if (item_icon_texture_item(itemNo) == dItemNo_KANTERA_e ||
item_icon_texture_item(itemNo) == dItemNo_KANTERA2_e)
{
return std::nullopt;
}
int count = 0;
int max = 0;
switch (itemNo) {
case dItemNo_BOW_e:
case dItemNo_LIGHT_ARROW_e:
case dItemNo_ARROW_LV1_e:
case dItemNo_ARROW_LV2_e:
case dItemNo_ARROW_LV3_e:
case dItemNo_HAWK_ARROW_e:
count = dComIfGs_getArrowNum();
max = dComIfGs_getArrowMax();
break;
case dItemNo_BOMB_ARROW_e:
count = std::min<int>(dComIfGp_getSelectItemNum(slot), dComIfGs_getArrowNum());
max = std::max<int>(dComIfGp_getSelectItemMaxNum(slot), dComIfGs_getArrowMax());
break;
default:
count = dComIfGp_getSelectItemNum(slot);
max = dComIfGp_getSelectItemMaxNum(slot);
break;
}
if (max <= 0) {
return std::nullopt;
}
return std::clamp(count, 0, max);
}
aurora::gfx::ConvertedTexture decode_timg(const ResTIMG* image) {
if (image == nullptr || image->width.host() == 0 || image->height.host() == 0) {
return {};
}
const auto* base = reinterpret_cast<const uint8_t*>(image);
const auto width = image->width.host();
const auto height = image->height.host();
const uint32_t textureSize = GXGetTexBufferSize(width, height, image->format, GX_FALSE, 0);
const auto* textureData = base + static_cast<int32_t>(image->imageOffset);
if (image->indexTexture != 0) {
const auto* paletteData = base + static_cast<int32_t>(image->paletteOffset);
return aurora::gfx::convert_texture_palette(image->format, width, height, 1,
aurora::ArrayRef{textureData, textureSize}, static_cast<GXTlutFmt>(image->colorFormat),
image->numColors,
aurora::ArrayRef{paletteData, static_cast<size_t>(image->numColors) * 2});
}
return aurora::gfx::convert_texture(
image->format, width, height, 1, aurora::ArrayRef{textureData, textureSize});
}
uint8_t lerp_u8(uint8_t a, uint8_t b, uint32_t t) noexcept {
return static_cast<uint8_t>(
(static_cast<uint32_t>(a) * (255u - t) + static_cast<uint32_t>(b) * t) / 255u);
}
JUtility::TColor lerp_color(
const JUtility::TColor& a, const JUtility::TColor& b, uint32_t t) noexcept {
return {
lerp_u8(a.r, b.r, t),
lerp_u8(a.g, b.g, t),
lerp_u8(a.b, b.b, t),
lerp_u8(a.a, b.a, t),
};
}
JUtility::TColor bilerp_corner(
const LayerColors& colors, uint32_t x, uint32_t y, uint32_t width, uint32_t height) noexcept {
const uint32_t u = width > 1 ? (x * 255u) / (width - 1u) : 0u;
const uint32_t v = height > 1 ? (y * 255u) / (height - 1u) : 0u;
const JUtility::TColor top = lerp_color(colors.corner[0], colors.corner[1], u);
const JUtility::TColor bottom = lerp_color(colors.corner[2], colors.corner[3], u);
return lerp_color(top, bottom, v);
}
std::array<uint8_t, 4> apply_layer_colors(std::span<const uint8_t, 4> src,
const LayerColors& colors, uint32_t x, uint32_t y, uint32_t width, uint32_t height) noexcept {
std::array out{
lerp_u8(colors.black.r, colors.white.r, src[0]),
lerp_u8(colors.black.g, colors.white.g, src[1]),
lerp_u8(colors.black.b, colors.white.b, src[2]),
src[3],
};
const auto corner = bilerp_corner(colors, x, y, width, height);
out[0] = static_cast<uint8_t>((static_cast<uint32_t>(out[0]) * corner.r) / 255u);
out[1] = static_cast<uint8_t>((static_cast<uint32_t>(out[1]) * corner.g) / 255u);
out[2] = static_cast<uint8_t>((static_cast<uint32_t>(out[2]) * corner.b) / 255u);
out[3] = static_cast<uint8_t>((static_cast<uint32_t>(out[3]) * corner.a) / 255u);
return out;
}
void blend_premultiplied(uint8_t* dst, const std::array<uint8_t, 4>& src) noexcept {
const uint32_t srcAlpha = src[3];
const uint32_t invAlpha = 255u - srcAlpha;
const uint32_t srcR = (static_cast<uint32_t>(src[0]) * srcAlpha) / 255u;
const uint32_t srcG = (static_cast<uint32_t>(src[1]) * srcAlpha) / 255u;
const uint32_t srcB = (static_cast<uint32_t>(src[2]) * srcAlpha) / 255u;
dst[0] = static_cast<uint8_t>(
std::min(255u, srcR + (static_cast<uint32_t>(dst[0]) * invAlpha) / 255u));
dst[1] = static_cast<uint8_t>(
std::min(255u, srcG + (static_cast<uint32_t>(dst[1]) * invAlpha) / 255u));
dst[2] = static_cast<uint8_t>(
std::min(255u, srcB + (static_cast<uint32_t>(dst[2]) * invAlpha) / 255u));
dst[3] = static_cast<uint8_t>(
std::min(255u, srcAlpha + (static_cast<uint32_t>(dst[3]) * invAlpha) / 255u));
}
LayerColors layer_colors(const J2DPicture& picture) noexcept {
return {
.black = picture.getBlack(),
.white = picture.getWhite(),
.corner = {picture.corner(0), picture.corner(1), picture.corner(2), picture.corner(3)},
};
}
LayerColors layer_colors(J2DPicture& picture, uint8_t alpha) noexcept {
std::array<JUtility::TColor, 4> corners{};
picture.getNewColor(corners.data());
for (auto& corner : corners) {
corner.a = static_cast<uint8_t>((static_cast<uint32_t>(corner.a) * alpha) / 255u);
}
return {
.black = picture.getBlack(),
.white = picture.getWhite(),
.corner = corners,
};
}
std::optional<CachedIcon> render_item_icon(u8 itemNo) {
std::array<ItemTextureBuffer, 4> buffers{};
std::array<J2DPicture, 4> pictures{};
const int textureCount =
dMeter2Info_readItemTexture(itemNo, buffers[0].data(), &pictures[0], buffers[1].data(),
&pictures[1], buffers[2].data(), &pictures[2], buffers[3].data(), &pictures[3], -1);
if (textureCount <= 0) {
return std::nullopt;
}
std::array<aurora::gfx::ConvertedTexture, 4> decodedLayers{};
std::array<LayerColors, 4> colors{};
int decodedCount = 0;
for (int i = 0; i < textureCount && i < static_cast<int>(decodedLayers.size()); ++i) {
auto decoded = decode_timg(reinterpret_cast<const ResTIMG*>(buffers[i].data()));
if (decoded.data.empty()) {
continue;
}
colors[decodedCount] = layer_colors(pictures[i]);
decodedLayers[decodedCount] = std::move(decoded);
++decodedCount;
}
if (decodedCount == 0) {
return std::nullopt;
}
CachedIcon icon{
.width = decodedLayers[0].width,
.height = decodedLayers[0].height,
};
icon.pixels.assign(static_cast<size_t>(icon.width) * static_cast<size_t>(icon.height) * 4, 0);
for (int layer = 0; layer < decodedCount; ++layer) {
const auto& decoded = decodedLayers[layer];
for (uint32_t y = 0; y < icon.height; ++y) {
const uint32_t sourceY = decoded.height > 0 ? (y * decoded.height) / icon.height : 0;
for (uint32_t x = 0; x < icon.width; ++x) {
const uint32_t sourceX = decoded.width > 0 ? (x * decoded.width) / icon.width : 0;
const size_t sourceOffset =
(static_cast<size_t>(sourceY) * decoded.width + static_cast<size_t>(sourceX)) *
4;
if (sourceOffset + 3 >= decoded.data.size()) {
continue;
}
const std::span<const uint8_t, 4> sourcePixel(
decoded.data.data() + sourceOffset, 4);
const auto pixel =
apply_layer_colors(sourcePixel, colors[layer], x, y, icon.width, icon.height);
uint8_t* destination =
icon.pixels.data() +
(static_cast<size_t>(y) * icon.width + static_cast<size_t>(x)) * 4;
blend_premultiplied(destination, pixel);
}
}
}
return icon;
}
SurfacePtr create_rgba_surface(uint32_t width, uint32_t height) {
if (width == 0 || height == 0 ||
width > static_cast<uint32_t>(std::numeric_limits<int>::max()) ||
height > static_cast<uint32_t>(std::numeric_limits<int>::max()))
{
return {};
}
return SurfacePtr{SDL_CreateSurface(
static_cast<int>(width), static_cast<int>(height), SDL_PIXELFORMAT_RGBA32)};
}
bool lock_surface(SDL_Surface* surface) noexcept {
return surface != nullptr && (!SDL_MUSTLOCK(surface) || SDL_LockSurface(surface));
}
void unlock_surface(SDL_Surface* surface) noexcept {
if (surface != nullptr && SDL_MUSTLOCK(surface)) {
SDL_UnlockSurface(surface);
}
}
SurfacePtr create_layer_surface(
const aurora::gfx::ConvertedTexture& decoded, const LayerColors& colors) {
if (decoded.width == 0 || decoded.height == 0 || decoded.data.empty()) {
return {};
}
auto surface = create_rgba_surface(decoded.width, decoded.height);
if (!surface || !lock_surface(surface.get())) {
return {};
}
for (uint32_t y = 0; y < decoded.height; ++y) {
auto* destination = static_cast<uint8_t*>(surface->pixels) +
static_cast<size_t>(y) * static_cast<size_t>(surface->pitch);
for (uint32_t x = 0; x < decoded.width; ++x) {
const size_t sourceOffset =
(static_cast<size_t>(y) * decoded.width + static_cast<size_t>(x)) * 4;
if (sourceOffset + 3 >= decoded.data.size()) {
continue;
}
const std::span<const uint8_t, 4> sourcePixel(decoded.data.data() + sourceOffset, 4);
const auto pixel =
apply_layer_colors(sourcePixel, colors, x, y, decoded.width, decoded.height);
std::memcpy(destination + static_cast<size_t>(x) * 4, pixel.data(), pixel.size());
}
}
unlock_surface(surface.get());
SDL_SetSurfaceBlendMode(surface.get(), SDL_BLENDMODE_BLEND);
return surface;
}
std::optional<CachedIcon> icon_from_surface(SDL_Surface* surface) {
if (surface == nullptr || surface->w <= 0 || surface->h <= 0) {
return std::nullopt;
}
CachedIcon icon{
.width = static_cast<uint32_t>(surface->w),
.height = static_cast<uint32_t>(surface->h),
};
const size_t rowSize = static_cast<size_t>(icon.width) * 4u;
icon.pixels.resize(rowSize * static_cast<size_t>(icon.height));
if (!lock_surface(surface)) {
return std::nullopt;
}
for (uint32_t y = 0; y < icon.height; ++y) {
const auto* source = static_cast<const uint8_t*>(surface->pixels) +
static_cast<size_t>(y) * static_cast<size_t>(surface->pitch);
auto* destination = icon.pixels.data() + static_cast<size_t>(y) * rowSize;
std::memcpy(destination, source, rowSize);
}
unlock_surface(surface);
return icon;
}
RectF pane_global_rect(J2DPane* pane) noexcept {
RectF rect;
CPaneMgr paneMgr;
Mtx m;
for (u8 i = 0; i < 4; ++i) {
const Vec vertex = paneMgr.getGlobalVtx(pane, &m, i, false, 0);
rect.include(vertex.x, vertex.y);
}
return rect;
}
uint8_t effective_pane_alpha(J2DPane& pane, uint8_t parentAlpha) noexcept {
uint32_t alpha = pane.getAlpha();
if (pane.isInfluencedAlpha()) {
alpha = alpha * parentAlpha / 255u;
}
return static_cast<uint8_t>(alpha);
}
void collect_picture_layers(
J2DPane* pane, std::vector<PictureLayer>& layers, uint8_t parentAlpha = 255) noexcept {
if (pane == nullptr || !pane->isVisible()) {
return;
}
const uint8_t paneAlpha = effective_pane_alpha(*pane, parentAlpha);
if (paneAlpha == 0) {
return;
}
if (pane->getKind() == MULTI_CHAR('PIC1') || pane->getKind() == MULTI_CHAR('PIC2')) {
auto* picture = static_cast<J2DPicture*>(pane);
if (picture->getTexture(0) != nullptr) {
RectF rect = pane_global_rect(pane);
if (rect.valid()) {
layers.push_back({
.picture = picture,
.rect = rect,
.alpha = paneAlpha,
});
}
}
}
for (J2DPane* child = pane->getFirstChildPane(); child != nullptr;
child = child->getNextChildPane())
{
collect_picture_layers(child, layers, paneAlpha);
}
}
std::optional<uint32_t> icon_dimension(float value) noexcept {
if (!std::isfinite(value) || value <= 0.0f) {
return std::nullopt;
}
const auto dimension = static_cast<uint32_t>(std::ceil(value));
if (dimension == 0 || dimension > kMaxRenderedPaneIconSize) {
return std::nullopt;
}
return dimension;
}
float pane_icon_render_scale(const std::vector<PictureLayer>& layers, const RectF& canvas) {
float scale = 1.0f;
for (const auto& layer : layers) {
if (layer.picture == nullptr || !layer.rect.valid() || layer.rect.width() <= 0.0f ||
layer.rect.height() <= 0.0f)
{
continue;
}
auto* texture = layer.picture->getTexture(0);
const ResTIMG* image = texture != nullptr ? texture->getTexInfo() : nullptr;
if (image == nullptr || image->width.host() == 0 || image->height.host() == 0) {
continue;
}
scale = std::max(scale, static_cast<float>(image->width) / layer.rect.width());
scale = std::max(scale, static_cast<float>(image->height) / layer.rect.height());
}
const float canvasMax = std::max(canvas.width(), canvas.height());
if (canvasMax <= 0.0f) {
return scale;
}
const float minScale = static_cast<float>(kMinRenderedPaneIconSize) / canvasMax;
const float maxScale = static_cast<float>(kMaxRenderedPaneIconSize) / canvasMax;
return std::clamp(std::max(scale, minScale), 1.0f, maxScale);
}
void composite_picture_layer(
SDL_Surface& icon, const RectF& canvas, const PictureLayer& layer, float renderScale) {
if (layer.picture == nullptr || !layer.rect.valid()) {
return;
}
auto* texture = layer.picture->getTexture(0);
if (texture == nullptr) {
return;
}
auto decoded = decode_timg(texture->getTexInfo());
if (decoded.data.empty() || decoded.width == 0 || decoded.height == 0) {
return;
}
const auto colors = layer_colors(*layer.picture, layer.alpha);
auto layerSurface = create_layer_surface(decoded, colors);
if (!layerSurface) {
return;
}
const float dstLeft = (layer.rect.left - canvas.left) * renderScale;
const float dstTop = (layer.rect.top - canvas.top) * renderScale;
const float dstRight = (layer.rect.right - canvas.left) * renderScale;
const float dstBottom = (layer.rect.bottom - canvas.top) * renderScale;
const float dstWidth = dstRight - dstLeft;
const float dstHeight = dstBottom - dstTop;
if (dstWidth <= 0.0f || dstHeight <= 0.0f) {
return;
}
const int x0 = std::clamp(static_cast<int>(std::floor(dstLeft)), 0, icon.w);
const int y0 = std::clamp(static_cast<int>(std::floor(dstTop)), 0, icon.h);
const int x1 = std::clamp(static_cast<int>(std::ceil(dstRight)), 0, icon.w);
const int y1 = std::clamp(static_cast<int>(std::ceil(dstBottom)), 0, icon.h);
if (x0 >= x1 || y0 >= y1) {
return;
}
SDL_Rect destinationRect{
.x = x0,
.y = y0,
.w = x1 - x0,
.h = y1 - y0,
};
SDL_BlitSurfaceScaled(
layerSurface.get(), nullptr, &icon, &destinationRect, SDL_SCALEMODE_LINEAR);
}
std::optional<CachedIcon> render_j2d_pane_icon(J2DPane* pane) {
std::vector<PictureLayer> layers;
collect_picture_layers(pane, layers);
if (layers.empty()) {
return std::nullopt;
}
RectF canvas;
for (const auto& layer : layers) {
canvas.include(layer.rect);
}
if (!canvas.valid()) {
return std::nullopt;
}
const float renderScale = pane_icon_render_scale(layers, canvas);
auto width = icon_dimension(canvas.width() * renderScale);
auto height = icon_dimension(canvas.height() * renderScale);
if (!width || !height) {
return std::nullopt;
}
auto surface = create_rgba_surface(*width, *height);
if (!surface) {
return std::nullopt;
}
for (const auto& layer : layers) {
composite_picture_layer(*surface, canvas, layer, renderScale);
}
return icon_from_surface(surface.get());
}
std::optional<aurora::rmlui::RuntimeTexture> icon_provider(std::string_view source) {
const auto itemNo = item_for_source(source);
if (!itemNo) {
return std::nullopt;
}
auto& cache = icon_cache();
const std::string key(source);
auto it = cache.find(key);
if (it == cache.end()) {
auto icon = render_item_icon(*itemNo);
if (!icon) {
return std::nullopt;
}
if (cache.size() >= kMaxCachedIcons) {
cache.erase(cache.begin());
}
it = cache.emplace(key, std::move(*icon)).first;
}
const auto& icon = it->second;
return aurora::rmlui::RuntimeTexture{
.width = icon.width,
.height = icon.height,
.rgba8 =
std::span(reinterpret_cast<const std::byte*>(icon.pixels.data()), icon.pixels.size()),
.premultipliedAlpha = true,
};
}
std::optional<aurora::rmlui::RuntimeTexture> meter_texture_provider(std::string_view source) {
if (!source.starts_with(kMeterSourcePrefix)) {
return std::nullopt;
}
const std::string name(strip_query(source.substr(kMeterSourcePrefix.size())));
if (name != "midna") {
return std::nullopt;
}
const auto& state = midna_icon_state();
if (!state.valid) {
return std::nullopt;
}
return aurora::rmlui::RuntimeTexture{
.width = state.icon.width,
.height = state.icon.height,
.rgba8 = std::span(
reinterpret_cast<const std::byte*>(state.icon.pixels.data()), state.icon.pixels.size()),
.premultipliedAlpha = true,
};
}
} // namespace
void register_icon_texture_provider() noexcept {
aurora::rmlui::register_texture_provider(std::string(kScheme), icon_provider);
aurora::rmlui::register_texture_provider(std::string(kMeterScheme), meter_texture_provider);
}
void unregister_icon_texture_provider() noexcept {
aurora::rmlui::unregister_texture_provider(kScheme);
aurora::rmlui::unregister_texture_provider(kMeterScheme);
icon_cache().clear();
midna_icon_state() = {};
}
void update_midna_icon_texture(J2DPane* pane) noexcept {
auto& state = midna_icon_state();
if (pane == nullptr || !pane->isVisible()) {
if (state.valid) {
state.valid = false;
state.icon = {};
state.revision++;
}
return;
}
auto icon = render_j2d_pane_icon(pane);
if (!icon) {
if (state.valid) {
state.valid = false;
state.icon = {};
state.revision++;
}
return;
}
if (!state.valid || state.icon.width != icon->width || state.icon.height != icon->height ||
state.icon.pixels != icon->pixels)
{
state.icon = std::move(*icon);
state.valid = true;
state.revision++;
}
}
std::string midna_icon_source() {
const auto& state = midna_icon_state();
if (!state.valid) {
return "";
}
return fmt::format(
"{}://midna?slot={}", kMeterScheme, state.revision % kMeterTextureSourceSlots);
}
uint64_t midna_icon_revision() noexcept {
const auto& state = midna_icon_state();
return state.valid ? state.revision : 0;
}
std::string item_icon_source_for_button(Control control) {
std::optional<u8> itemNo;
switch (control) {
case Control::X:
itemNo = selected_slot_item(0);
break;
case Control::Y:
itemNo = selected_slot_item(1);
break;
case Control::B:
itemNo = b_button_item();
break;
default:
break;
}
if (!itemNo) {
return {};
}
return item_source_for_item(*itemNo);
}
std::string item_count_label_for_button(Control control) {
std::optional<int> count;
switch (control) {
case Control::X:
count = selected_slot_count(0);
break;
case Control::Y:
count = selected_slot_count(1);
break;
default:
break;
}
if (!count) {
return {};
}
return fmt::format("{}", *count);
}
std::optional<float> item_oil_fill_for_button(Control control) noexcept {
std::optional<u8> itemNo;
switch (control) {
case Control::X:
itemNo = selected_slot_item(0);
break;
case Control::Y:
itemNo = selected_slot_item(1);
break;
default:
break;
}
if (!itemNo || (*itemNo != dItemNo_KANTERA_e && *itemNo != dItemNo_KANTERA2_e)) {
return std::nullopt;
}
const int maxOil = dComIfGs_getMaxOil();
if (maxOil <= 0) {
return std::nullopt;
}
return std::clamp(
static_cast<float>(dComIfGs_getOil()) / static_cast<float>(maxOil), 0.0f, 1.0f);
}
} // namespace dusk::ui
#else
namespace dusk::ui {
void register_icon_texture_provider() noexcept {}
void unregister_icon_texture_provider() noexcept {}
void update_midna_icon_texture(J2DPane*) noexcept {}
std::string midna_icon_source() {
return {};
}
uint64_t midna_icon_revision() noexcept {
return 0;
}
std::string item_icon_source_for_button(Control) {
return {};
}
std::string item_count_label_for_button(Control) {
return {};
}
std::optional<float> item_oil_fill_for_button(Control) noexcept {
return std::nullopt;
}
} // namespace dusk::ui
#endif
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include "controls.hpp"
#include <cstdint>
#include <optional>
#include <string>
class J2DPane;
namespace dusk::ui {
void register_icon_texture_provider() noexcept;
void unregister_icon_texture_provider() noexcept;
void update_midna_icon_texture(J2DPane* pane) noexcept;
std::string midna_icon_source();
uint64_t midna_icon_revision() noexcept;
std::string item_icon_source_for_button(Control control);
std::string item_count_label_for_button(Control control);
std::optional<float> item_oil_fill_for_button(Control control) noexcept;
} // namespace dusk::ui
+5 -43
View File
@@ -5,6 +5,7 @@
#include "dusk/action_bindings.h"
#include "controller_config.hpp"
#include "dusk/livesplit.h"
#include "dusk/settings.h"
#include "dusk/speedrun.h"
#include "fmt/format.h"
#include "magic_enum.hpp"
@@ -13,6 +14,7 @@
#include <SDL3/SDL_gamepad.h>
#include <SDL3/SDL_timer.h>
#include <algorithm>
#include <aurora/gfx.h>
#include <dolphin/pad.h>
#include <m_Do/m_Do_main.h>
@@ -187,50 +189,13 @@ void remove_element(Rml::Element*& elem) noexcept {
} // namespace
// https://vplesko.com/posts/how_to_implement_an_fps_counter.html
void Overlay::advance_fps_counter(float& outFps, Uint64 perfFreq) {
if (perfFreq == 0) {
outFps = 0.f;
return;
}
const Uint64 curr = SDL_GetPerformanceCounter();
if (!mFpsHavePrevCounter) {
mFpsPrevCounter = curr;
mFpsHavePrevCounter = true;
outFps = 0.f;
return;
}
const Uint64 processingTicks = curr - mFpsPrevCounter;
mFpsPrevCounter = curr;
mFpsFrameEvents.push_back({curr, processingTicks});
mFpsSumTicks += processingTicks;
while (!mFpsFrameEvents.empty() && mFpsFrameEvents.front().endCounter + perfFreq < curr) {
mFpsSumTicks -= mFpsFrameEvents.front().processingTicks;
mFpsFrameEvents.pop_front();
}
const auto n = mFpsFrameEvents.size();
if (n == 0 || mFpsSumTicks == 0) {
outFps = 0.f;
return;
}
const double avgSeconds =
static_cast<double>(mFpsSumTicks) / static_cast<double>(n) / static_cast<double>(perfFreq);
outFps = static_cast<float>(1.0 / avgSeconds);
}
static std::string FormatTime(OSTime ticks) {
OSCalendarTime t;
OSTicksToCalendarTime(ticks, &t);
return fmt::format("{0:02}:{1:02}:{2:02}.{3:03}", t.hour, t.min, t.sec, t.msec);
}
Overlay::Overlay() : Document(kDocumentSource) {
Overlay::Overlay() : Document(kDocumentSource, true) {
mFpsCounter = mDocument->GetElementById("fps");
mSpeedrunTimer = mDocument->GetElementById("speedrun-timer");
mSpeedrunRta = mDocument->GetElementById("speedrun-rta");
@@ -276,8 +241,7 @@ void Overlay::update() {
mFpsCounter->SetAttribute("corner", kFpsCorners[idx]);
const Uint64 perfFreq = SDL_GetPerformanceFrequency();
float fps = 0.f;
advance_fps_counter(fps, perfFreq);
float fps = aurora_get_fps();
const Uint64 now = SDL_GetPerformanceCounter();
// Limit updates to twice per second
@@ -290,9 +254,6 @@ void Overlay::update() {
}
} else {
mFpsCounter->RemoveAttribute("open");
mFpsFrameEvents.clear();
mFpsSumTicks = 0;
mFpsHavePrevCounter = false;
mFpsLastUpdate = 0;
}
}
@@ -357,6 +318,7 @@ void Overlay::update() {
u32 count = 0;
const bool showControllerWarning = PADGetIndexForPort(PAD_CHAN0) < 0 &&
PADGetKeyButtonBindings(PAD_CHAN0, &count) == nullptr &&
!getSettings().game.enableTouchControls &&
dynamic_cast<Window*>(top_document()) == nullptr &&
dynamic_cast<WindowSmall*>(top_document()) == nullptr;
if (showControllerWarning && mControllerWarning == nullptr) {
-13
View File
@@ -3,7 +3,6 @@
#include "document.hpp"
#include <chrono>
#include <deque>
namespace dusk::ui {
@@ -26,19 +25,7 @@ protected:
Rml::Element* mSpeedrunIgt = nullptr;
clock::time_point mCurrentToastStartTime;
clock::time_point mMenuNotificationStartTime;
struct FpsFrameEvent {
Uint64 endCounter;
Uint64 processingTicks;
};
std::deque<FpsFrameEvent> mFpsFrameEvents;
Uint64 mFpsSumTicks = 0;
bool mFpsHavePrevCounter = false;
Uint64 mFpsPrevCounter = 0;
Uint64 mFpsLastUpdate = 0;
void advance_fps_counter(float& outFps, Uint64 perfFreq);
};
} // namespace dusk::ui
+1 -1
View File
@@ -739,7 +739,7 @@ Prelaunch::Prelaunch() : Document(kDocumentSource), mRoot(mDocument->GetElementB
}
IsGameLaunched = true;
hide(true);
pop(false);
});
apply_intro_animation(mMenuButtons.back()->root(), "delay-1");
+2
View File
@@ -21,6 +21,7 @@ void applyPresetClassic() {
s.game.shadowResolutionMultiplier.setValue(1);
s.game.hideTvSettingsScreen.setValue(false);
s.game.menuScalingMode.setValue(MenuScaling::GameCube);
s.game.enableMenuPointer.setValue(false);
AuroraSetViewportPolicy(AURORA_VIEWPORT_FIT);
}
@@ -53,6 +54,7 @@ void applyPresetDusk() {
s.game.autoSave.setValue(true);
s.game.menuScalingMode.setValue(MenuScaling::Dusklight);
s.game.enhancedMapMenus.setValue(true);
s.game.enableMenuPointer.setValue(true);
}
} // namespace
+66 -4
View File
@@ -6,6 +6,7 @@
#include "dusk/app_info.hpp"
#include "dusk/audio/DuskAudioSystem.h"
#include "dusk/audio/DuskDsp.hpp"
#include "dusk/android_frame_rate.hpp"
#include "dusk/config.hpp"
#include "dusk/hotkeys.h"
#include "dusk/data.hpp"
@@ -22,6 +23,7 @@
#include "menu_bar.hpp"
#include "pane.hpp"
#include "prelaunch.hpp"
#include "touch_controls_editor.hpp"
#include "ui.hpp"
#include <aurora/lib/window.hpp>
@@ -35,6 +37,17 @@
#include <algorithm>
#include <filesystem>
#if defined(__APPLE__)
#include <TargetConditionals.h>
#endif
#if defined(TARGET_ANDROID) || defined(__ANDROID__) || \
(defined(__APPLE__) && TARGET_OS_IOS && !TARGET_OS_MACCATALYST)
#define TOUCH_CONTROLS_AVAILABLE true
#else
#define TOUCH_CONTROLS_AVAILABLE false
#endif
namespace dusk::ui {
namespace {
@@ -482,14 +495,19 @@ SelectButton& config_percent_select(Pane& leftPane, Pane& rightPane, ConfigVar<f
SelectButton& config_int_select(Pane& leftPane, Pane& rightPane, ConfigVar<int>& var,
Rml::String key, Rml::String helpText, int min, int max, int step = 5,
std::function<bool()> isDisabled = {}, std::string suffix = "") {
std::function<bool()> isDisabled = {}, std::function<void(int)> onChange = {},
std::string suffix = "") {
auto& button = leftPane.add_child<NumberButton>(NumberButton::Props{
.key = std::move(key),
.getValue = [&var] { return var; },
.setValue =
[&var, min, max](int value) {
var.setValue(std::clamp(value, min, max));
[&var, min, max, callback = std::move(onChange)](int value) {
const int clampedValue = std::clamp(value, min, max);
var.setValue(clampedValue);
config::Save();
if (callback) {
callback(clampedValue);
}
},
.isDisabled = std::move(isDisabled),
.isModified = [&var] { return var.getValue() != var.getDefaultValue(); },
@@ -871,6 +889,21 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
pane.add_rml(
"<br/>Display the current framerate in a corner of the screen while playing.");
});
config_bool_select(leftPane, rightPane, getSettings().video.rememberWindowSize,
{
.key = "Remember Window Size",
.helpText = "Save and restore the previous session's window size when opening Dusklight.",
.onChange =
[](bool value) {
if (value && !dusk::getSettings().video.enableFullscreen) {
const auto windowSize = aurora::window::get_window_size();
dusk::getSettings().video.lastWindowWidth.setValue(windowSize.width);
dusk::getSettings().video.lastWindowHeight.setValue(windowSize.height);
dusk::config::Save();
}
},
.isDisabled = [] { return IsMobile; },
});
leftPane.add_section("Resolution");
graphics_tuner_control(*this, leftPane, rightPane,
getSettings().game.internalResolutionScale,
@@ -971,6 +1004,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
.on_pressed([i] {
mDoAud_seStartMenu(kSoundItemChange);
getSettings().game.enableFrameInterpolation.setValue(static_cast<FrameInterpMode>(i));
android::update_surface_frame_rate();
config::Save();
});
}
@@ -978,7 +1012,8 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
});
config_int_select(leftPane, rightPane, getSettings().video.maxFrameRate,
"Framerate Cap", "Limit the framerate to the specified value.", 30, 540, 1,
[] { return getSettings().game.enableFrameInterpolation.getValue() != FrameInterpMode::Capped; });
[] { return getSettings().game.enableFrameInterpolation.getValue() != FrameInterpMode::Capped; },
[](int) { android::update_surface_frame_rate(); });
config_bool_select(leftPane, rightPane, getSettings().game.enableMapBackground,
{
.key = "Enable Mini-Map Shadows",
@@ -1022,6 +1057,31 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
.onChange = [](bool value) { aurora_set_background_input(value); },
});
#if TOUCH_CONTROLS_AVAILABLE
leftPane.add_section("Touch");
addOption("Touch Controls", getSettings().game.enableTouchControls,
"Enables controls overlay for touch screens.<br/><br/>Press and drag on the left side "
"of the screen to move, and on the right side of the screen to control the camera.");
auto& customizeTouchLayout = leftPane.add_button(ControlledButton::Props{
.text = "Customize Layout",
.isDisabled = [] { return !getSettings().game.enableTouchControls; },
});
leftPane.register_control(customizeTouchLayout.on_pressed(
[this] { push(std::make_unique<TouchControlsEditor>()); }),
rightPane, [](Pane& pane) {
pane.clear();
pane.add_text("Open the touch controls layout editor.");
});
config_percent_select(leftPane, rightPane, getSettings().game.touchCameraXSensitivity,
"Touch Camera X Sensitivity",
"Adjusts touch camera horizontal sensitivity.<br/><br/>Applies to touch input only.",
25, 400, 5, [] { return !getSettings().game.enableTouchControls; });
config_percent_select(leftPane, rightPane, getSettings().game.touchCameraYSensitivity,
"Touch Camera Y Sensitivity",
"Adjusts touch camera vertical sensitivity.<br/><br/>Applies to touch input only.", 25,
400, 5, [] { return !getSettings().game.enableTouchControls; });
#endif
leftPane.add_section("Camera");
addOption("Free Camera", getSettings().game.freeCamera,
"Enables free camera control, letting you control the camera fully with the C-Stick.");
@@ -1089,6 +1149,8 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
[] { return !getSettings().game.enableMouseAim || !getSettings().game.enableMouseCamera; });
leftPane.add_section("Gameplay");
addOption("Mouse/Touch in Menus", getSettings().game.enableMenuPointer,
"Enables mouse and touch input for supported in-game menus.");
addOption("Invert Air/Swim X Axis", getSettings().game.invertAirSwimX,
"Invert horizontal movement while flying or swimming.");
addOption("Invert Air/Swim Y Axis", getSettings().game.invertAirSwimY,
File diff suppressed because it is too large Load Diff
+133
View File
@@ -0,0 +1,133 @@
#pragma once
#include "controls.hpp"
#include "document.hpp"
#include "dusk/action_bindings.h"
#include "dusk/menu_pointer.h"
#include "dusk/ui/controls.hpp"
#include <array>
#include <bitset>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <string>
namespace dusk::ui {
enum class ControlOverride {
Default,
Action,
};
bool get_equip_target(int slot, EquipTarget& target) noexcept;
void set_control_override(Control control, ControlOverride override) noexcept;
void sync_virtual_input() noexcept;
class TouchControls final : public Document {
public:
TouchControls();
~TouchControls() override;
void show() override;
void hide(bool close) override;
void update() override;
void sync_virtual_input() noexcept;
private:
struct StickTouch {
SDL_FingerID id = 0;
Rml::Vector2f start;
Rml::Vector2f current;
bool active = false;
};
struct ControlTouch {
SDL_FingerID id = 0;
clock::time_point startTime{};
bool active = false;
bool longPressFired = false;
};
struct LayoutState {
std::optional<ControlRect> visualRect;
std::optional<ControlRect> appliedBox;
float layoutScale = 1.0f;
std::optional<float> appliedTransform;
};
struct ControlElements {
Rml::Element* root = nullptr;
Rml::Element* icon = nullptr;
Rml::Element* oil = nullptr;
Rml::Element* oilFill = nullptr;
Rml::Element* count = nullptr;
LayoutState layout;
};
enum class ControlAction {
Tap,
Hold,
};
void set_control_pressed(Control control, bool pressed);
void release_control(Control control) noexcept;
void sync_control_button_mask() noexcept;
bool fire_control_action(Control control, ControlAction action) noexcept;
bool start_control_touch(SDL_FingerID id, Control control) noexcept;
void set_control_visual(Control control, bool pressed) noexcept;
void sync_l_lock_state() noexcept;
void clear_motion_touch_input() noexcept;
void clear_control_input() noexcept;
void clear_virtual_input() noexcept;
void sync_touch_state() noexcept;
void sync_visibility() noexcept;
void sync_safe_area() noexcept;
void sync_control_layouts() noexcept;
void sync_visual_state() noexcept;
void sync_action_bar_state() noexcept;
void sync_control_displays() noexcept;
void apply_control_transform(Control control) noexcept;
void handle_touch_down(Rml::Event& event) noexcept;
void handle_touch_motion(Rml::Event& event) noexcept;
void handle_touch_up(Rml::Event& event) noexcept;
void handle_touch_cancel(Rml::Event& event) noexcept;
void handle_mouse_move(Rml::Event& event) noexcept;
void handle_mouse_down(Rml::Event& event) noexcept;
void handle_mouse_up(Rml::Event& event) noexcept;
void sync_control_long_presses() noexcept;
bool release_control_touch(SDL_FingerID id, bool cancelled) noexcept;
bool handle_menu_event(Rml::Event& event, menu_pointer::Phase phase) noexcept;
Rml::Element* mRoot = nullptr;
Rml::Element* mControlStick = nullptr;
Rml::Element* mControlKnob = nullptr;
Rml::Element* mActionBar = nullptr;
std::array<ControlElements, static_cast<std::size_t>(Control::COUNT)> mControlElements{};
std::string mButtonBIconSource;
std::string mButtonXIconSource;
std::string mButtonYIconSource;
std::string mZTriggerIconSource;
uint64_t mZTriggerIconRevision = 0;
std::string mButtonXCountLabel;
std::string mButtonYCountLabel;
StickTouch mMoveTouch;
StickTouch mCameraTouch;
SDL_FingerID mMenuPointerTouch = 0;
int mMenuPointerMouseSuppressions = 0;
std::array<ControlTouch, static_cast<std::size_t>(Control::COUNT)> mControlTouches{};
std::array<bool, static_cast<std::size_t>(Control::COUNT)> mControlVisualPressed{};
std::bitset<static_cast<std::size_t>(ActionBinds::COUNT)> mQueuedActions;
LayoutState mActionBarLayout;
Insets mSafeInsets;
u16 mButtonMask = 0;
bool mLPressed = false;
bool mLLatched = false;
bool mManualLLatched = false;
bool mLReleasePending = false;
bool mRTriggerHeld = false;
bool mWantsVirtualPad = false;
bool mWasSuppressed = true;
bool mMenuPointerTouchActive = false;
clock::time_point mLPressStartTime{};
clock::time_point mLastLTapTime{};
};
} // namespace dusk::ui
+359
View File
@@ -0,0 +1,359 @@
#include "touch_controls_common.hpp"
#include <aurora/rmlui.hpp>
#include <algorithm>
#include <array>
#include <cmath>
namespace dusk::ui {
namespace {
constexpr std::array<TouchLayoutControlInfo, kTouchLayoutControlCount> kLayoutControls = {{
{
.layoutId = "triggerL",
.elementId = "trigger-l",
.props =
{
.x = 24.f,
.y = 18.f,
.w = 78.f,
.h = 46.f,
.scale = 1.f,
.anchor = ControlAnchor::TopLeft,
},
.control = Control::L,
.hasControl = true,
},
{
.layoutId = "triggerR",
.elementId = "trigger-r",
.props =
{
.x = 24.f,
.y = 18.f,
.w = 78.f,
.h = 46.f,
.scale = 1.f,
.anchor = ControlAnchor::TopRight,
},
.control = Control::R,
.hasControl = true,
},
{
.layoutId = "buttonZ",
.elementId = "button-z",
.props =
{
.x = 24.f,
.y = 72.f,
.w = 78.f,
.h = 46.f,
.scale = 1.f,
.anchor = ControlAnchor::TopRight,
},
.control = Control::Z,
.hasControl = true,
},
{
.layoutId = "actionBar",
.elementId = "action-bar",
.props =
{
.x = 56.f,
.y = 0.f,
.w = 230.f,
.h = 46.f,
.scale = 1.f,
.anchor = ControlAnchor::BottomLeft,
},
},
{
.layoutId = "skip",
.elementId = "skip",
.props =
{
.x = 24.f,
.y = 18.f,
.w = 64.f,
.h = 46.f,
.scale = 1.f,
.anchor = ControlAnchor::TopRight,
},
.control = Control::SKIP,
.hasControl = true,
},
{
.layoutId = "buttonY",
.elementId = "button-y",
.props =
{
.x = 124.f,
.y = 138.f,
.w = 58.f,
.h = 58.f,
.scale = 1.f,
.anchor = ControlAnchor::BottomRight,
},
.control = Control::Y,
.hasControl = true,
},
{
.layoutId = "buttonX",
.elementId = "button-x",
.props =
{
.x = 28.f,
.y = 144.f,
.w = 58.f,
.h = 58.f,
.scale = 1.f,
.anchor = ControlAnchor::BottomRight,
},
.control = Control::X,
.hasControl = true,
},
{
.layoutId = "buttonB",
.elementId = "button-b",
.props =
{
.x = 158.f,
.y = 48.f,
.w = 58.f,
.h = 58.f,
.scale = 1.f,
.anchor = ControlAnchor::BottomRight,
},
.control = Control::B,
.hasControl = true,
},
{
.layoutId = "buttonA",
.elementId = "button-a",
.props =
{
.x = 62.f,
.y = 64.f,
.w = 74.f,
.h = 74.f,
.scale = 1.f,
.anchor = ControlAnchor::BottomRight,
},
.control = Control::A,
.hasControl = true,
},
}};
constexpr std::string_view kTouchControlsRmlFragment = R"RML(
<button id="trigger-l" class="control trigger trigger-l"><span>L</span></button>
<action-bar id="action-bar" class="control">
<button id="items" class="utility items"><icon><glyph>&#xeb0e;</glyph></icon></button>
<separator />
<button id="first-person" class="utility first-person"><icon><glyph>&#xf4c9;</glyph></icon></button>
<separator />
<button id="map" class="utility map"><icon><glyph>&#xe55b;</glyph></icon></button>
<separator />
<button id="collections" class="utility collections"><icon><glyph>&#xe034;</glyph></icon></button>
</action-bar>
<button id="skip" class="control skip"><icon><glyph>&#xe044;</glyph></icon></button>
<button id="trigger-r" class="control trigger trigger-r"><span>R</span></button>
<button id="button-z" class="control trigger button-z midna"><img id="z-midna-icon" class="midna-icon" /><span>Z</span></button>
<button id="button-y" class="control face y"><img id="button-y-icon" class="item-icon" /><oil-meter id="button-y-oil" class="oil-meter"><oil-fill id="button-y-oil-fill" /></oil-meter><count id="button-y-count" class="item-count"></count><span>Y</span></button>
<button id="button-x" class="control face x"><img id="button-x-icon" class="item-icon" /><oil-meter id="button-x-oil" class="oil-meter"><oil-fill id="button-x-oil-fill" /></oil-meter><count id="button-x-count" class="item-count"></count><span>X</span></button>
<button id="button-b" class="control face b"><img id="button-b-icon" class="item-icon" /><span>B</span></button>
<button id="button-a" class="control face a"><span>A</span></button>
)RML";
} // namespace
std::string_view touch_controls_rml_fragment() noexcept {
return kTouchControlsRmlFragment;
}
std::span<const TouchLayoutControlInfo> touch_layout_controls() noexcept {
return kLayoutControls;
}
const TouchLayoutControlInfo* find_touch_layout_control(std::string_view layoutId) noexcept {
for (const auto& info : kLayoutControls) {
if (info.layoutId == layoutId) {
return &info;
}
}
return nullptr;
}
const TouchLayoutControlInfo* find_touch_layout_control(Control control) noexcept {
for (const auto& info : kLayoutControls) {
if (info.hasControl && info.control == control) {
return &info;
}
}
return nullptr;
}
SDL_FingerID touch_event_id(const Rml::Event& event) noexcept {
return event.GetParameter<SDL_FingerID>("finger_id", 0);
}
Rml::Vector2f touch_event_position(const Rml::Event& event) noexcept {
return {
event.GetParameter("x", 0.f),
event.GetParameter("y", 0.f),
};
}
Rml::Vector2f mouse_event_position(const Rml::Event& event) noexcept {
return {
event.GetParameter("mouse_x", 0.f),
event.GetParameter("mouse_y", 0.f),
};
}
float touch_dp_scale(Rml::Context* context) noexcept {
if (context == nullptr) {
context = aurora::rmlui::get_context();
}
if (context == nullptr) {
return 1.f;
}
return std::max(context->GetDensityIndependentPixelRatio(), 1.f);
}
ControlLayoutSize touch_document_size_dp(Rml::Context* context) noexcept {
if (context == nullptr) {
return {};
}
const auto dimensions = context->GetDimensions();
const float scale = touch_dp_scale(context);
return {
.w = static_cast<float>(dimensions.x) / scale,
.h = static_cast<float>(dimensions.y) / scale,
};
}
ControlAnchor touch_control_dock_anchor(ControlRect visual, ControlLayoutSize docSize) noexcept {
if (docSize.w <= 0.f || docSize.h <= 0.f || visual.w <= 0.f || visual.h <= 0.f) {
return ControlAnchor::None;
}
const bool top = control_float_near(visual.t, 0.f);
const bool bottom = control_float_near(visual.t + visual.h, docSize.h);
const bool left = control_float_near(visual.l, 0.f);
const bool right = control_float_near(visual.l + visual.w, docSize.w);
if (top && left && !right) {
return ControlAnchor::TopLeft;
}
if (top && right && !left) {
return ControlAnchor::TopRight;
}
if (bottom && left && !right) {
return ControlAnchor::BottomLeft;
}
if (bottom && right && !left) {
return ControlAnchor::BottomRight;
}
if (top) {
return ControlAnchor::Top;
}
if (bottom) {
return ControlAnchor::Bottom;
}
if (left) {
return ControlAnchor::Left;
}
if (right) {
return ControlAnchor::Right;
}
return ControlAnchor::None;
}
bool control_float_near(float a, float b) noexcept {
return std::abs(a - b) <= 0.01f;
}
bool control_rect_near(ControlRect a, ControlRect b) noexcept {
return control_float_near(a.l, b.l) && control_float_near(a.t, b.t) &&
control_float_near(a.w, b.w) && control_float_near(a.h, b.h);
}
void apply_control_box_if_changed(
Rml::Element* element, std::optional<ControlRect>& appliedBox, ControlRect box) noexcept {
if (element == nullptr || (appliedBox && control_rect_near(*appliedBox, box))) {
return;
}
element->SetProperty(Rml::PropertyId::Left, Rml::Property(box.l, Rml::Unit::DP));
element->SetProperty(Rml::PropertyId::Top, Rml::Property(box.t, Rml::Unit::DP));
element->SetProperty(Rml::PropertyId::Width, Rml::Property(box.w, Rml::Unit::DP));
element->SetProperty(Rml::PropertyId::Height, Rml::Property(box.h, Rml::Unit::DP));
appliedBox = box;
}
void apply_control_transform_if_changed(
Rml::Element* element, std::optional<float>& appliedTransform, float scale) noexcept {
if (element == nullptr || (appliedTransform && control_float_near(*appliedTransform, scale))) {
return;
}
element->SetProperty(Rml::PropertyId::Transform,
Rml::Transform::MakeProperty({Rml::Transforms::Scale2D{scale}}));
appliedTransform = scale;
}
void apply_control_dock_classes(Rml::Element* element, ControlAnchor anchor) noexcept {
if (element == nullptr) {
return;
}
bool top = false;
bool bottom = false;
bool left = false;
bool right = false;
switch (anchor) {
case ControlAnchor::Top:
top = true;
break;
case ControlAnchor::Bottom:
bottom = true;
break;
case ControlAnchor::Left:
left = true;
break;
case ControlAnchor::Right:
right = true;
break;
case ControlAnchor::TopLeft:
top = true;
left = true;
break;
case ControlAnchor::TopRight:
top = true;
right = true;
break;
case ControlAnchor::BottomLeft:
bottom = true;
left = true;
break;
case ControlAnchor::BottomRight:
bottom = true;
right = true;
break;
case ControlAnchor::None:
break;
}
element->SetClass("docked", top || bottom || left || right);
element->SetClass("docked-top", top);
element->SetClass("docked-bottom", bottom);
element->SetClass("docked-left", left);
element->SetClass("docked-right", right);
}
} // namespace dusk::ui
+45
View File
@@ -0,0 +1,45 @@
#pragma once
#include "controls.hpp"
#include <RmlUi/Core.h>
#include <SDL3/SDL_touch.h>
#include <cstddef>
#include <optional>
#include <span>
#include <string_view>
namespace dusk::ui {
constexpr std::size_t kTouchLayoutControlCount = 9;
struct TouchLayoutControlInfo {
std::string_view layoutId;
const char* elementId = nullptr;
ControlProps props;
Control control = Control::COUNT;
bool hasControl = false;
};
std::string_view touch_controls_rml_fragment() noexcept;
std::span<const TouchLayoutControlInfo> touch_layout_controls() noexcept;
const TouchLayoutControlInfo* find_touch_layout_control(std::string_view layoutId) noexcept;
const TouchLayoutControlInfo* find_touch_layout_control(Control control) noexcept;
SDL_FingerID touch_event_id(const Rml::Event& event) noexcept;
Rml::Vector2f touch_event_position(const Rml::Event& event) noexcept;
Rml::Vector2f mouse_event_position(const Rml::Event& event) noexcept;
float touch_dp_scale(Rml::Context* context = nullptr) noexcept;
ControlLayoutSize touch_document_size_dp(Rml::Context* context) noexcept;
ControlAnchor touch_control_dock_anchor(ControlRect visual, ControlLayoutSize docSize) noexcept;
bool control_float_near(float a, float b) noexcept;
bool control_rect_near(ControlRect a, ControlRect b) noexcept;
void apply_control_box_if_changed(
Rml::Element* element, std::optional<ControlRect>& appliedBox, ControlRect box) noexcept;
void apply_control_transform_if_changed(
Rml::Element* element, std::optional<float>& appliedTransform, float scale) noexcept;
void apply_control_dock_classes(Rml::Element* element, ControlAnchor anchor) noexcept;
} // namespace dusk::ui
+630
View File
@@ -0,0 +1,630 @@
#include "touch_controls_editor.hpp"
#include "modal.hpp"
#include "Z2AudioLib/Z2SeMgr.h"
#include "dusk/config.hpp"
#include "dusk/settings.h"
#include "m_Do/m_Do_audio.h"
#include <aurora/rmlui.hpp>
#include <algorithm>
#include <cmath>
#include <memory>
#include <string>
namespace dusk::ui {
namespace {
constexpr float kDragThresholdDp = 6.f;
constexpr float kMinControlDp = 36.f;
constexpr float kMinTriggerWidthDp = 44.f;
constexpr float kMinTriggerHeightDp = 32.f;
constexpr float kMinActionBarWidthDp = 112.f;
constexpr float kMinActionBarHeightDp = 36.f;
constexpr float kMinScale = 0.25f;
struct HandleBinding {
const char* id = nullptr;
TouchControlsEditor::EditHandle handle = TouchControlsEditor::EditHandle::Move;
};
constexpr std::array kHandleBindings = {
HandleBinding{"editor-handle-left", TouchControlsEditor::EditHandle::Left},
HandleBinding{"editor-handle-right", TouchControlsEditor::EditHandle::Right},
HandleBinding{"editor-handle-top", TouchControlsEditor::EditHandle::Top},
HandleBinding{"editor-handle-bottom", TouchControlsEditor::EditHandle::Bottom},
HandleBinding{"editor-handle-top-left", TouchControlsEditor::EditHandle::TopLeft},
HandleBinding{"editor-handle-top-right", TouchControlsEditor::EditHandle::TopRight},
HandleBinding{"editor-handle-bottom-left", TouchControlsEditor::EditHandle::BottomLeft},
HandleBinding{"editor-handle-bottom-right", TouchControlsEditor::EditHandle::BottomRight},
};
Rml::String touch_controls_editor_document_source() {
const auto fragment = touch_controls_rml_fragment();
return Rml::String{R"RML(
<rml>
<head>
<link type="text/rcss" href="res/rml/touch_controls.rcss" />
<link type="text/rcss" href="res/rml/touch_controls_editor.rcss" />
</head>
<body id="root" class="touch-editor">
)RML"} + Rml::String{fragment.data(), fragment.size()} + Rml::String{R"RML(
<selection-frame id="editor-selection-frame">
<resize-handle id="editor-handle-left" class="edge horizontal left" />
<resize-handle id="editor-handle-right" class="edge horizontal right" />
<resize-handle id="editor-handle-top" class="edge vertical top" />
<resize-handle id="editor-handle-bottom" class="edge vertical bottom" />
<resize-handle id="editor-handle-top-left" class="corner top left" />
<resize-handle id="editor-handle-top-right" class="corner top right" />
<resize-handle id="editor-handle-bottom-left" class="corner bottom left" />
<resize-handle id="editor-handle-bottom-right" class="corner bottom right" />
</selection-frame>
<editor-toolbar id="editor-toolbar">
<button id="editor-save" class="editor-command primary"><span>Save</span></button>
<button id="editor-reset" class="editor-command"><span>Reset</span></button>
<button id="editor-cancel" class="editor-command"><span>Cancel</span></button>
</editor-toolbar>
</body>
</rml>
)RML"};
}
bool is_corner(TouchControlsEditor::EditHandle handle) noexcept {
using EditHandle = TouchControlsEditor::EditHandle;
return handle == EditHandle::TopLeft || handle == EditHandle::TopRight ||
handle == EditHandle::BottomLeft || handle == EditHandle::BottomRight;
}
bool is_horizontal_edge(TouchControlsEditor::EditHandle handle) noexcept {
using EditHandle = TouchControlsEditor::EditHandle;
return handle == EditHandle::Left || handle == EditHandle::Right;
}
bool is_vertical_edge(TouchControlsEditor::EditHandle handle) noexcept {
using EditHandle = TouchControlsEditor::EditHandle;
return handle == EditHandle::Top || handle == EditHandle::Bottom;
}
bool control_valid(std::size_t index) noexcept {
return index < touch_layout_controls().size();
}
float squared_distance(Rml::Vector2f a, Rml::Vector2f b) noexcept {
const auto delta = a - b;
return delta.x * delta.x + delta.y * delta.y;
}
} // namespace
TouchControlsEditor::TouchControlsEditor()
: Document(touch_controls_editor_document_source()),
mRoot(mDocument != nullptr ? mDocument->GetElementById("root") : nullptr),
mSelectionFrame(
mDocument != nullptr ? mDocument->GetElementById("editor-selection-frame") : nullptr),
mSaveButton(mDocument != nullptr ? mDocument->GetElementById("editor-save") : nullptr),
mResetButton(mDocument != nullptr ? mDocument->GetElementById("editor-reset") : nullptr),
mCancelButton(mDocument != nullptr ? mDocument->GetElementById("editor-cancel") : nullptr),
mWorkingLayout(getSettings().game.touchControlsLayout.getValue()) {
mWorkingLayout.version = ControlLayout::Version;
const auto controls = touch_layout_controls();
for (std::size_t i = 0; i < controls.size() && i < mElements.size(); ++i) {
mElements[i].root =
mDocument != nullptr ? mDocument->GetElementById(controls[i].elementId) : nullptr;
}
bind_control_events();
bind_handle_events();
bind_toolbar_events();
listen(mRoot, aurora::rmlui::TouchStartEvent, [this](Rml::Event& event) {
if (event.GetTargetElement() != mRoot) {
return;
}
clear_selected_control();
event.StopPropagation();
});
listen(mRoot, Rml::EventId::Mousedown, [this](Rml::Event& event) {
const s32 button = event.GetParameter("button", -1);
if (button != 0 || event.GetTargetElement() != mRoot) {
return;
}
clear_selected_control();
event.StopPropagation();
});
listen(mRoot, aurora::rmlui::TouchMoveEvent, [this](Rml::Event& event) {
if (continue_edit(touch_event_position(event))) {
event.StopPropagation();
}
});
listen(mRoot, aurora::rmlui::TouchEndEvent, [this](Rml::Event& event) {
if (end_edit(true, touch_event_id(event), false)) {
event.StopPropagation();
}
});
listen(mRoot, aurora::rmlui::TouchCancelEvent, [this](Rml::Event& event) {
if (end_edit(true, touch_event_id(event), true)) {
event.StopPropagation();
}
});
listen(mRoot, Rml::EventId::Mousemove, [this](Rml::Event& event) {
if (continue_edit(mouse_event_position(event))) {
event.StopPropagation();
}
});
listen(mRoot, Rml::EventId::Mouseup, [this](Rml::Event& event) {
if (end_edit(false, 0, false)) {
event.StopPropagation();
}
});
listen(mRoot, Rml::EventId::Transitionend, [this](Rml::Event& event) {
if (event.GetTargetElement() == mRoot && !mRoot->HasAttribute("open") &&
Document::visible())
{
Document::hide(mPendingClose);
}
});
}
void TouchControlsEditor::show() {
Document::show();
if (mRoot != nullptr) {
mRoot->SetAttribute("open", "");
}
}
void TouchControlsEditor::hide(bool close) {
if (mRoot != nullptr) {
mRoot->RemoveAttribute("open");
mPendingClose = close;
} else {
Document::hide(close);
}
}
void TouchControlsEditor::update() {
sync_control_layouts();
sync_selection_frame();
Document::update();
}
bool TouchControlsEditor::focus() {
return mSaveButton != nullptr && mSaveButton->Focus(true);
}
void TouchControlsEditor::bind_control_events() noexcept {
const auto controls = touch_layout_controls();
for (std::size_t i = 0; i < controls.size() && i < mElements.size(); ++i) {
auto* element = mElements[i].root;
if (element == nullptr) {
continue;
}
listen(element, aurora::rmlui::TouchStartEvent, [this, i](Rml::Event& event) {
if (begin_edit(i, EditHandle::Move, touch_event_position(event), true,
touch_event_id(event)))
{
event.StopPropagation();
}
});
listen(element, Rml::EventId::Mousedown, [this, i](Rml::Event& event) {
const s32 button = event.GetParameter("button", -1);
if (button != 0) {
return;
}
if (begin_edit(i, EditHandle::Move, mouse_event_position(event), false)) {
event.StopPropagation();
}
});
}
}
void TouchControlsEditor::bind_handle_events() noexcept {
for (const auto& binding : kHandleBindings) {
auto* element = mDocument != nullptr ? mDocument->GetElementById(binding.id) : nullptr;
if (element == nullptr) {
continue;
}
listen(element, aurora::rmlui::TouchStartEvent, [this, handle = binding.handle](
Rml::Event& event) {
if (!control_valid(mSelectedIndex)) {
return;
}
if (begin_edit(mSelectedIndex, handle, touch_event_position(event), true,
touch_event_id(event)))
{
event.StopPropagation();
}
});
listen(element, Rml::EventId::Mousedown, [this, handle = binding.handle](Rml::Event& event) {
const s32 button = event.GetParameter("button", -1);
if (button != 0 || !control_valid(mSelectedIndex)) {
return;
}
if (begin_edit(mSelectedIndex, handle, mouse_event_position(event), false)) {
event.StopPropagation();
}
});
}
}
void TouchControlsEditor::bind_toolbar_events() noexcept {
bind_button_command(mSaveButton, &TouchControlsEditor::save_layout);
bind_button_command(mResetButton, &TouchControlsEditor::request_reset);
bind_button_command(mCancelButton, &TouchControlsEditor::cancel_edit);
}
void TouchControlsEditor::bind_button_command(
Rml::Element* element, void (TouchControlsEditor::*callback)()) noexcept {
if (element == nullptr) {
return;
}
listen(element, Rml::EventId::Click, [this, callback](Rml::Event& event) {
(this->*callback)();
event.StopPropagation();
});
listen(element, Rml::EventId::Keydown, [this, callback](Rml::Event& event) {
if (map_nav_event(event) != NavCommand::Confirm) {
return;
}
(this->*callback)();
event.StopPropagation();
});
}
void TouchControlsEditor::sync_control_layouts() noexcept {
auto* context = mDocument != nullptr ? mDocument->GetContext() : nullptr;
const auto docSize = touch_document_size_dp(context);
if (docSize.w <= 0.f || docSize.h <= 0.f || context == nullptr) {
return;
}
const auto controls = touch_layout_controls();
for (std::size_t i = 0; i < controls.size() && i < mElements.size(); ++i) {
const auto layout = resolve_control_layout(props_for(i), docSize);
auto& element = mElements[i];
element.layout.visualRect = layout.visual;
element.layout.layoutScale = layout.scale;
if (element.root != nullptr) {
element.root->SetPseudoClass("hidden", false);
}
apply_control_box_if_changed(element.root, element.layout.appliedBox, layout.box);
apply_control_dock_classes(
element.root, touch_control_dock_anchor(layout.visual, docSize));
apply_control_transform_if_changed(
element.root, element.layout.appliedTransform, element.layout.layoutScale);
}
}
void TouchControlsEditor::sync_selection_frame() noexcept {
const bool hasSelection =
control_valid(mSelectedIndex) && mElements[mSelectedIndex].layout.visualRect;
if (mSelectionFrame == nullptr) {
return;
}
mSelectionFrame->SetClass("visible", hasSelection);
for (std::size_t i = 0; i < mElements.size(); ++i) {
if (mElements[i].root != nullptr) {
mElements[i].root->SetClass("editor-selected", hasSelection && i == mSelectedIndex);
}
}
if (!hasSelection) {
mAppliedSelectionFrame = std::nullopt;
return;
}
apply_control_box_if_changed(
mSelectionFrame, mAppliedSelectionFrame, *mElements[mSelectedIndex].layout.visualRect);
}
void TouchControlsEditor::set_selected_control(std::size_t index) noexcept {
if (!control_valid(index)) {
clear_selected_control();
return;
}
mSelectedIndex = index;
sync_selection_frame();
}
void TouchControlsEditor::clear_selected_control() noexcept {
mSelectedIndex = kTouchLayoutControlCount;
sync_selection_frame();
}
ControlProps TouchControlsEditor::props_for(std::size_t index) const {
const auto controls = touch_layout_controls();
if (!control_valid(index)) {
return {};
}
const auto& info = controls[index];
if (const auto iter = mWorkingLayout.controls.find(info.layoutId);
iter != mWorkingLayout.controls.end())
{
return iter->second;
}
return info.props;
}
void TouchControlsEditor::store_props(
std::size_t index, ControlRect visual, ControlProps props) noexcept {
if (!control_valid(index)) {
return;
}
auto* context = mDocument != nullptr ? mDocument->GetContext() : nullptr;
const auto docSize = touch_document_size_dp(context);
if (docSize.w <= 0.f || docSize.h <= 0.f) {
return;
}
props.w = std::max(props.w, 1.f);
props.h = std::max(props.h, 1.f);
props.scale = std::max(props.scale, kMinScale);
props = encode_control_props(visual, docSize, props, touch_control_dock_anchor(visual, docSize));
mWorkingLayout.version = ControlLayout::Version;
mWorkingLayout.controls[std::string{touch_layout_controls()[index].layoutId}] = props;
sync_control_layouts();
sync_selection_frame();
}
void TouchControlsEditor::restore_active_control() noexcept {
if (!control_valid(mPointerEdit.index)) {
return;
}
auto& controls = mWorkingLayout.controls;
const auto key = std::string{touch_layout_controls()[mPointerEdit.index].layoutId};
if (mPointerEdit.storedProps) {
controls[key] = *mPointerEdit.storedProps;
} else {
controls.erase(key);
}
sync_control_layouts();
sync_selection_frame();
}
bool TouchControlsEditor::begin_edit(
std::size_t index, EditHandle handle, Rml::Vector2f positionPx, bool touch,
SDL_FingerID touchId) noexcept {
if (!control_valid(index) || mPointerEdit.active) {
return false;
}
auto* context = mDocument != nullptr ? mDocument->GetContext() : nullptr;
const auto docSize = touch_document_size_dp(context);
if (docSize.w <= 0.f || docSize.h <= 0.f) {
return false;
}
const auto props = props_for(index);
const auto layout = resolve_control_layout(props, docSize);
std::optional<ControlProps> storedProps;
if (const auto iter = mWorkingLayout.controls.find(touch_layout_controls()[index].layoutId);
iter != mWorkingLayout.controls.end())
{
storedProps = iter->second;
}
mPointerEdit = {
.index = index,
.touchId = touchId,
.startPointerDp = pointer_position_dp(positionPx),
.startVisual = layout.visual,
.startProps = props,
.storedProps = storedProps,
.handle = handle,
.active = true,
.touch = touch,
};
set_selected_control(index);
return true;
}
bool TouchControlsEditor::continue_edit(Rml::Vector2f positionPx) noexcept {
if (!mPointerEdit.active) {
return false;
}
const auto pointerDp = pointer_position_dp(positionPx);
if (!mPointerEdit.dragging) {
if (squared_distance(pointerDp, mPointerEdit.startPointerDp) <
kDragThresholdDp * kDragThresholdDp)
{
return true;
}
mPointerEdit.dragging = true;
}
auto props = mPointerEdit.startProps;
auto rect = rect_for_edit(pointerDp, props);
rect = clamp_visual_rect(mPointerEdit.index, rect);
if (is_corner(mPointerEdit.handle)) {
props.scale = std::max(rect.w / std::max(mPointerEdit.startProps.w, 1.f), kMinScale);
} else if (is_horizontal_edge(mPointerEdit.handle)) {
props.w = rect.w / std::max(props.scale, kMinScale);
} else if (is_vertical_edge(mPointerEdit.handle)) {
props.h = rect.h / std::max(props.scale, kMinScale);
}
store_props(mPointerEdit.index, rect, props);
return true;
}
bool TouchControlsEditor::end_edit(bool touch, SDL_FingerID touchId, bool cancelled) noexcept {
if (!mPointerEdit.active || mPointerEdit.touch != touch ||
(touch && mPointerEdit.touchId != touchId))
{
return false;
}
if (cancelled && mPointerEdit.dragging) {
restore_active_control();
}
mPointerEdit = {};
return true;
}
Rml::Vector2f TouchControlsEditor::pointer_position_dp(Rml::Vector2f positionPx) const noexcept {
auto* context = mDocument != nullptr ? mDocument->GetContext() : nullptr;
return positionPx / touch_dp_scale(context);
}
ControlRect TouchControlsEditor::rect_for_edit(
Rml::Vector2f pointerDp, ControlProps& props) const noexcept {
const auto& edit = mPointerEdit;
auto rect = edit.startVisual;
const auto delta = pointerDp - edit.startPointerDp;
switch (edit.handle) {
case EditHandle::Move:
rect.l += delta.x;
rect.t += delta.y;
return rect;
case EditHandle::Left: {
const float right = edit.startVisual.l + edit.startVisual.w;
rect.l = pointerDp.x;
rect.w = right - rect.l;
return rect;
}
case EditHandle::Right:
rect.w = pointerDp.x - edit.startVisual.l;
return rect;
case EditHandle::Top: {
const float bottom = edit.startVisual.t + edit.startVisual.h;
rect.t = pointerDp.y;
rect.h = bottom - rect.t;
return rect;
}
case EditHandle::Bottom:
rect.h = pointerDp.y - edit.startVisual.t;
return rect;
case EditHandle::TopLeft:
case EditHandle::TopRight:
case EditHandle::BottomLeft:
case EditHandle::BottomRight:
break;
}
auto* context = mDocument != nullptr ? mDocument->GetContext() : nullptr;
const auto docSize = touch_document_size_dp(context);
const bool left = edit.handle == EditHandle::TopLeft || edit.handle == EditHandle::BottomLeft;
const bool top = edit.handle == EditHandle::TopLeft || edit.handle == EditHandle::TopRight;
const Rml::Vector2f fixed{
left ? edit.startVisual.l + edit.startVisual.w : edit.startVisual.l,
top ? edit.startVisual.t + edit.startVisual.h : edit.startVisual.t,
};
const float desiredW = left ? fixed.x - pointerDp.x : pointerDp.x - fixed.x;
const float desiredH = top ? fixed.y - pointerDp.y : pointerDp.y - fixed.y;
const auto minSize = min_visual_size(edit.index);
const float minRatio =
std::max(minSize.x / std::max(edit.startVisual.w, 1.f),
minSize.y / std::max(edit.startVisual.h, 1.f));
const float maxW = left ? fixed.x : docSize.w - fixed.x;
const float maxH = top ? fixed.y : docSize.h - fixed.y;
const float maxRatio =
std::max(minRatio, std::min(maxW / std::max(edit.startVisual.w, 1.f),
maxH / std::max(edit.startVisual.h, 1.f)));
const float ratio =
std::clamp(std::max(desiredW / std::max(edit.startVisual.w, 1.f),
desiredH / std::max(edit.startVisual.h, 1.f)),
minRatio, maxRatio);
rect.w = edit.startVisual.w * ratio;
rect.h = edit.startVisual.h * ratio;
rect.l = left ? fixed.x - rect.w : fixed.x;
rect.t = top ? fixed.y - rect.h : fixed.y;
props.scale = std::max(edit.startProps.scale * ratio, kMinScale);
return rect;
}
ControlRect TouchControlsEditor::clamp_visual_rect(std::size_t index, ControlRect rect) const noexcept {
auto* context = mDocument != nullptr ? mDocument->GetContext() : nullptr;
const auto docSize = touch_document_size_dp(context);
if (docSize.w <= 0.f || docSize.h <= 0.f || !control_valid(index)) {
return rect;
}
const auto minSize = min_visual_size(index);
const float minW = std::min(minSize.x, docSize.w);
const float minH = std::min(minSize.y, docSize.h);
rect.w = std::clamp(rect.w, minW, docSize.w);
rect.h = std::clamp(rect.h, minH, docSize.h);
rect.l = std::clamp(rect.l, 0.f, std::max(0.f, docSize.w - rect.w));
rect.t = std::clamp(rect.t, 0.f, std::max(0.f, docSize.h - rect.h));
return rect;
}
Rml::Vector2f TouchControlsEditor::min_visual_size(std::size_t index) const noexcept {
if (!control_valid(index)) {
return {kMinControlDp, kMinControlDp};
}
const auto id = touch_layout_controls()[index].layoutId;
if (id == "actionBar") {
return {kMinActionBarWidthDp, kMinActionBarHeightDp};
}
if (id == "triggerL" || id == "triggerR" || id == "buttonZ" || id == "skip") {
return {kMinTriggerWidthDp, kMinTriggerHeightDp};
}
return {kMinControlDp, kMinControlDp};
}
bool TouchControlsEditor::handle_nav_command(Rml::Event& event, NavCommand cmd) {
if (cmd == NavCommand::Cancel || cmd == NavCommand::Menu) {
cancel_edit();
return true;
}
return Document::handle_nav_command(event, cmd);
}
void TouchControlsEditor::save_layout() {
mWorkingLayout.version = ControlLayout::Version;
getSettings().game.touchControlsLayout.setValue(mWorkingLayout);
config::Save();
mDoAud_seStartMenu(kSoundItemChange);
pop();
}
void TouchControlsEditor::request_reset() {
auto dismiss = [](Modal& modal) { modal.pop(); };
push(std::make_unique<Modal>(Modal::Props{
.title = "Reset Touch Layout?",
.bodyRml = "Reset controls to their default layout. This will not be saved until you press Save.",
.actions =
{
ModalAction{
.label = "Reset",
.onPressed =
[this, dismiss](Modal& modal) {
reset_working_layout();
mDoAud_seStartMenu(kSoundItemChange);
dismiss(modal);
},
},
ModalAction{
.label = "Cancel",
.onPressed = dismiss,
},
},
}));
}
void TouchControlsEditor::reset_working_layout() noexcept {
mWorkingLayout = ControlLayout{};
mWorkingLayout.version = ControlLayout::Version;
mPointerEdit = {};
sync_control_layouts();
sync_selection_frame();
}
void TouchControlsEditor::cancel_edit() {
mDoAud_seStartMenu(kSoundWindowClose);
pop();
}
} // namespace dusk::ui
+98
View File
@@ -0,0 +1,98 @@
#pragma once
#include "controls.hpp"
#include "document.hpp"
#include "touch_controls_common.hpp"
#include <array>
#include <cstddef>
#include <optional>
namespace dusk::ui {
class TouchControlsEditor final : public Document {
public:
TouchControlsEditor();
void show() override;
void hide(bool close) override;
void update() override;
bool focus() override;
enum class EditHandle {
Move,
Left,
Right,
Top,
Bottom,
TopLeft,
TopRight,
BottomLeft,
BottomRight,
};
private:
struct LayoutState {
std::optional<ControlRect> visualRect;
std::optional<ControlRect> appliedBox;
float layoutScale = 1.0f;
std::optional<float> appliedTransform;
};
struct EditElement {
Rml::Element* root = nullptr;
LayoutState layout;
};
struct PointerEdit {
std::size_t index = kTouchLayoutControlCount;
SDL_FingerID touchId = 0;
Rml::Vector2f startPointerDp;
ControlRect startVisual;
ControlProps startProps;
std::optional<ControlProps> storedProps;
EditHandle handle = EditHandle::Move;
bool active = false;
bool touch = false;
bool dragging = false;
};
void bind_control_events() noexcept;
void bind_handle_events() noexcept;
void bind_toolbar_events() noexcept;
void bind_button_command(
Rml::Element* element, void (TouchControlsEditor::*callback)()) noexcept;
void sync_control_layouts() noexcept;
void sync_selection_frame() noexcept;
void set_selected_control(std::size_t index) noexcept;
void clear_selected_control() noexcept;
ControlProps props_for(std::size_t index) const;
void store_props(std::size_t index, ControlRect visual, ControlProps props) noexcept;
void restore_active_control() noexcept;
bool begin_edit(std::size_t index, EditHandle handle, Rml::Vector2f positionPx, bool touch,
SDL_FingerID touchId = 0) noexcept;
bool continue_edit(Rml::Vector2f positionPx) noexcept;
bool end_edit(bool touch, SDL_FingerID touchId, bool cancelled) noexcept;
Rml::Vector2f pointer_position_dp(Rml::Vector2f positionPx) const noexcept;
ControlRect rect_for_edit(Rml::Vector2f pointerDp, ControlProps& props) const noexcept;
ControlRect clamp_visual_rect(std::size_t index, ControlRect rect) const noexcept;
Rml::Vector2f min_visual_size(std::size_t index) const noexcept;
bool handle_nav_command(Rml::Event& event, NavCommand cmd) override;
void save_layout();
void request_reset();
void reset_working_layout() noexcept;
void cancel_edit();
Rml::Element* mRoot = nullptr;
Rml::Element* mSelectionFrame = nullptr;
Rml::Element* mSaveButton = nullptr;
Rml::Element* mResetButton = nullptr;
Rml::Element* mCancelButton = nullptr;
std::array<EditElement, kTouchLayoutControlCount> mElements{};
ControlLayout mWorkingLayout;
PointerEdit mPointerEdit;
std::optional<ControlRect> mAppliedSelectionFrame;
std::size_t mSelectedIndex = kTouchLayoutControlCount;
};
} // namespace dusk::ui
+18 -7
View File
@@ -1,7 +1,11 @@
#include "ui.hpp"
#include <RmlUi/Core.h>
#include <SDL3/SDL_filesystem.h>
#include <SDL3/SDL_events.h>
#include <SDL3/SDL_gamepad.h>
#include <SDL3/SDL_joystick.h>
#include <SDL3/SDL_power.h>
#include <SDL3/SDL_video.h>
#include <absl/container/flat_hash_set.h>
#include <aurora/rmlui.hpp>
#include <fmt/format.h>
@@ -11,11 +15,12 @@
#include <ranges>
#include "aurora/lib/window.hpp"
#include "dusk/config.hpp"
#include "dusk/io.hpp"
#include "input.hpp"
#include "icon_provider.hpp"
#include "prelaunch.hpp"
#include "window.hpp"
#include "dusk/config.hpp"
namespace dusk::ui {
namespace {
@@ -56,11 +61,13 @@ bool initialize() noexcept {
load_font("MaterialSymbolsRounded-Regular.ttf");
load_font("NotoMono-Regular.ttf");
register_icon_texture_provider();
sInitialized = true;
return true;
}
void shutdown() noexcept {
unregister_icon_texture_provider();
sDocumentStack.clear();
sPassiveDocuments.clear();
sConnectedGamepads.clear();
@@ -188,9 +195,13 @@ Document& push_document(std::unique_ptr<Document> doc, bool show, bool passive)
return ret;
}
void show_top_document() noexcept {
void focus_top_document(bool show) noexcept {
if (auto* doc = top_document()) {
doc->show();
if (show) {
doc->show();
} else {
doc->focus();
}
}
input::sync_input_block();
}
@@ -203,13 +214,13 @@ bool any_document_visible() noexcept {
bool is_prelaunch_open() noexcept {
return std::any_of(sDocumentStack.begin(), sDocumentStack.end(), [](const auto& doc) {
const auto* prelaunch = dynamic_cast<const Prelaunch*>(doc.get());
return prelaunch != nullptr && !prelaunch->pending_close() && !prelaunch->closed();
return prelaunch != nullptr && prelaunch->active();
});
}
Document* top_document() noexcept {
for (auto& doc : std::views::reverse(sDocumentStack)) {
if (!doc->closed() && !doc->pending_close()) {
if (doc->active()) {
return doc.get();
}
}
@@ -252,7 +263,7 @@ void update() noexcept {
context->GetFocusElement() == context->GetRootElement()))
{
for (auto& doc : std::views::reverse(sDocumentStack)) {
if (!doc->closed() && !doc->pending_close() && doc->focus()) {
if (doc->active() && doc->focus()) {
break;
}
}
+1 -1
View File
@@ -74,7 +74,7 @@ void update() noexcept;
Document& push_document(
std::unique_ptr<Document> doc, bool show = true, bool passive = false) noexcept;
void show_top_document() noexcept;
void focus_top_document(bool show) noexcept;
bool any_document_visible() noexcept;
bool is_prelaunch_open() noexcept;
Document* top_document() noexcept;
+3
View File
@@ -29,6 +29,7 @@
#include "tracy/Tracy.hpp"
#include <dusk/gamepad_color.h>
#include <dusk/autosave.h>
#include "dusk/menu_pointer.h"
#endif
fapGm_HIO_c::fapGm_HIO_c() {
@@ -743,6 +744,7 @@ static void fapGm_AfterRecord() {
BOOL isRecording = false;
static void duskExecute() {
dusk::menu_pointer::begin_game_frame();
dusk::input::handleGamepadColor();
updateAutoSave();
@@ -842,6 +844,7 @@ void fapGm_Execute() {
#ifdef TARGET_PC
dusk::speedrun::onGameFrame();
dusk::AchievementSystem::get().tick();
dusk::menu_pointer::end_game_frame();
#endif
}
+14
View File
@@ -12,6 +12,11 @@
#include "m_Do/m_Do_main.h"
#include "tracy/Tracy.hpp"
#if TARGET_PC
#include "dusk/menu_pointer.h"
#include "dusk/ui/touch_controls.hpp"
#endif
JUTGamePad* mDoCPd_c::m_gamePad[4];
interface_of_controller_pad mDoCPd_c::m_cpadInfo[4];
@@ -58,6 +63,9 @@ void mDoCPd_c::create() {
void mDoCPd_c::read() {
ZoneScoped;
#if TARGET_PC
dusk::ui::sync_virtual_input();
#endif
JUTGamePad::read();
if (!mDoRst::isReset() && mDoRst::is3ButtonReset()) {
@@ -88,6 +96,12 @@ void mDoCPd_c::read() {
cLib_memSet(interface, 0, sizeof(interface_of_controller_pad));
} else {
convert(interface, *pad);
#if TARGET_PC
const u32 suppressedButtons = dusk::menu_pointer::suppressed_pad_buttons(i);
interface->mButtonFlags &= ~suppressedButtons;
interface->mPressedButtonFlags &= ~suppressedButtons;
dusk::menu_pointer::finish_pad_suppression_read(i);
#endif
LRlockCheck(interface);
}
#if DEBUG

Some files were not shown because too many files have changed in this diff Show More