koin.js 1.0.16 → 1.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +22 -6
- package/dist/index.d.ts +22 -6
- package/dist/index.js +1041 -543
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1042 -544
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +64 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -334,9 +334,11 @@ var en = {
|
|
|
334
334
|
cheats: "Cheats",
|
|
335
335
|
retroAchievements: "RetroAchievements",
|
|
336
336
|
shortcuts: "Shortcuts",
|
|
337
|
-
exit: "Exit",
|
|
337
|
+
exit: "Exit Game",
|
|
338
338
|
language: "Language",
|
|
339
|
-
selectLanguage: "Select Language"
|
|
339
|
+
selectLanguage: "Select Language",
|
|
340
|
+
haptics: "Haptic Feedback",
|
|
341
|
+
enableHaptics: "Vibration"
|
|
340
342
|
},
|
|
341
343
|
overlay: {
|
|
342
344
|
play: "PLAY",
|
|
@@ -413,13 +415,16 @@ var en = {
|
|
|
413
415
|
},
|
|
414
416
|
cheats: {
|
|
415
417
|
title: "Cheats",
|
|
416
|
-
addCheat: "Add Cheat",
|
|
417
|
-
available: "
|
|
418
|
-
emptyTitle: "No
|
|
419
|
-
emptyDesc: "
|
|
420
|
-
copy: "Copy
|
|
421
|
-
active: "
|
|
422
|
-
toggleHint: "Click
|
|
418
|
+
addCheat: "Add Manual Cheat",
|
|
419
|
+
available: "Available Cheats",
|
|
420
|
+
emptyTitle: "No Cheats Available",
|
|
421
|
+
emptyDesc: "This game does not have any known cheats.",
|
|
422
|
+
copy: "Copy",
|
|
423
|
+
active: "Active",
|
|
424
|
+
toggleHint: "Click to toggle",
|
|
425
|
+
codePlaceholder: "Enter cheat code (e.g. 00C-048-E6E)",
|
|
426
|
+
descPlaceholder: "Cheat description (optional)",
|
|
427
|
+
add: "Add Cheat"
|
|
423
428
|
},
|
|
424
429
|
saveSlots: {
|
|
425
430
|
title: "Save States",
|
|
@@ -2592,7 +2597,8 @@ function useTouchHandlers({
|
|
|
2592
2597
|
onPress,
|
|
2593
2598
|
onPressDown,
|
|
2594
2599
|
onRelease,
|
|
2595
|
-
onPositionChange
|
|
2600
|
+
onPositionChange,
|
|
2601
|
+
hapticsEnabled = true
|
|
2596
2602
|
}) {
|
|
2597
2603
|
const isDraggingRef = React2.useRef(false);
|
|
2598
2604
|
const drag = useDrag({
|
|
@@ -2618,7 +2624,7 @@ function useTouchHandlers({
|
|
|
2618
2624
|
const touch = e.touches[0];
|
|
2619
2625
|
e.preventDefault();
|
|
2620
2626
|
e.stopPropagation();
|
|
2621
|
-
if (navigator.vibrate) {
|
|
2627
|
+
if (hapticsEnabled && navigator.vibrate) {
|
|
2622
2628
|
navigator.vibrate(8);
|
|
2623
2629
|
}
|
|
2624
2630
|
if (isSystemButton) {
|
|
@@ -2855,7 +2861,11 @@ var VirtualButton = React2__default.default.memo(function VirtualButton2({
|
|
|
2855
2861
|
customPosition,
|
|
2856
2862
|
onPositionChange,
|
|
2857
2863
|
isLandscape = false,
|
|
2858
|
-
console: console2 = ""
|
|
2864
|
+
console: console2 = "",
|
|
2865
|
+
hapticsEnabled = true,
|
|
2866
|
+
mode = "normal",
|
|
2867
|
+
isHeld = false,
|
|
2868
|
+
isInTurbo = false
|
|
2859
2869
|
}) {
|
|
2860
2870
|
const t = useKoinTranslation();
|
|
2861
2871
|
const buttonRef = React2.useRef(null);
|
|
@@ -2882,7 +2892,9 @@ var VirtualButton = React2__default.default.memo(function VirtualButton2({
|
|
|
2882
2892
|
onPress,
|
|
2883
2893
|
onPressDown,
|
|
2884
2894
|
onRelease,
|
|
2885
|
-
onPositionChange
|
|
2895
|
+
onPositionChange,
|
|
2896
|
+
hapticsEnabled
|
|
2897
|
+
// Pass haptics setting
|
|
2886
2898
|
});
|
|
2887
2899
|
useTouchEvents(buttonRef, {
|
|
2888
2900
|
onTouchStart: handleTouchStart,
|
|
@@ -2910,7 +2922,7 @@ var VirtualButton = React2__default.default.memo(function VirtualButton2({
|
|
|
2910
2922
|
width = `${config.size * 2}px`;
|
|
2911
2923
|
}
|
|
2912
2924
|
}
|
|
2913
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
2925
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2914
2926
|
"button",
|
|
2915
2927
|
{
|
|
2916
2928
|
ref: buttonRef,
|
|
@@ -2950,48 +2962,364 @@ var VirtualButton = React2__default.default.memo(function VirtualButton2({
|
|
|
2950
2962
|
},
|
|
2951
2963
|
"aria-label": label,
|
|
2952
2964
|
onContextMenu: (e) => e.preventDefault(),
|
|
2953
|
-
children:
|
|
2965
|
+
children: [
|
|
2966
|
+
(mode !== "normal" || isHeld || isInTurbo) && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2967
|
+
"span",
|
|
2968
|
+
{
|
|
2969
|
+
className: "absolute -top-1 -right-1 rounded-full flex items-center justify-center",
|
|
2970
|
+
style: {
|
|
2971
|
+
width: "16px",
|
|
2972
|
+
height: "16px",
|
|
2973
|
+
backgroundColor: isHeld ? "#22c55e" : isInTurbo ? "#fbbf24" : "rgba(255,255,255,0.2)",
|
|
2974
|
+
animation: isInTurbo ? "pulse 0.5s infinite" : "none"
|
|
2975
|
+
},
|
|
2976
|
+
children: isHeld ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Hand, { size: 10, color: "white", strokeWidth: 3 }) : isInTurbo ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Zap, { size: 10, color: "white", fill: "white" }) : mode === "hold" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Hand, { size: 10, color: "white" }) : mode === "turbo" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Zap, { size: 10, color: "white" }) : null
|
|
2977
|
+
}
|
|
2978
|
+
),
|
|
2979
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "drop-shadow-md", children: label })
|
|
2980
|
+
]
|
|
2954
2981
|
}
|
|
2955
2982
|
);
|
|
2956
2983
|
});
|
|
2957
2984
|
var VirtualButton_default = VirtualButton;
|
|
2985
|
+
var CENTER_TOUCH_RADIUS = 0.35;
|
|
2986
|
+
var Dpad = React2__default.default.memo(function Dpad2({
|
|
2987
|
+
size = 180,
|
|
2988
|
+
x,
|
|
2989
|
+
y,
|
|
2990
|
+
containerWidth,
|
|
2991
|
+
containerHeight,
|
|
2992
|
+
systemColor = "#00FF41",
|
|
2993
|
+
isLandscape = false,
|
|
2994
|
+
customPosition,
|
|
2995
|
+
onPositionChange,
|
|
2996
|
+
hapticsEnabled = true,
|
|
2997
|
+
onButtonDown,
|
|
2998
|
+
onButtonUp
|
|
2999
|
+
}) {
|
|
3000
|
+
const dpadRef = React2.useRef(null);
|
|
3001
|
+
const activeTouchRef = React2.useRef(null);
|
|
3002
|
+
const activeDirectionsRef = React2.useRef(/* @__PURE__ */ new Set());
|
|
3003
|
+
const upPathRef = React2.useRef(null);
|
|
3004
|
+
const downPathRef = React2.useRef(null);
|
|
3005
|
+
const leftPathRef = React2.useRef(null);
|
|
3006
|
+
const rightPathRef = React2.useRef(null);
|
|
3007
|
+
const centerCircleRef = React2.useRef(null);
|
|
3008
|
+
const displayX = customPosition ? customPosition.x : x;
|
|
3009
|
+
const displayY = customPosition ? customPosition.y : y;
|
|
3010
|
+
const releaseAllDirections = React2.useCallback(() => {
|
|
3011
|
+
activeDirectionsRef.current.forEach((dir) => onButtonUp(dir));
|
|
3012
|
+
activeDirectionsRef.current = /* @__PURE__ */ new Set();
|
|
3013
|
+
}, [onButtonUp]);
|
|
3014
|
+
const drag = useDrag({
|
|
3015
|
+
elementSize: size,
|
|
3016
|
+
displayX,
|
|
3017
|
+
displayY,
|
|
3018
|
+
containerWidth,
|
|
3019
|
+
containerHeight,
|
|
3020
|
+
onPositionChange,
|
|
3021
|
+
centerThreshold: CENTER_TOUCH_RADIUS,
|
|
3022
|
+
onDragStart: () => {
|
|
3023
|
+
releaseAllDirections();
|
|
3024
|
+
updateVisuals(/* @__PURE__ */ new Set());
|
|
3025
|
+
}
|
|
3026
|
+
});
|
|
3027
|
+
const getDirectionsFromTouch = React2.useCallback((touchX, touchY, rect) => {
|
|
3028
|
+
const centerX = rect.left + rect.width / 2;
|
|
3029
|
+
const centerY = rect.top + rect.height / 2;
|
|
3030
|
+
const dx = touchX - centerX;
|
|
3031
|
+
const dy = touchY - centerY;
|
|
3032
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
3033
|
+
const deadZone = rect.width / 2 * 0.15;
|
|
3034
|
+
if (distance < deadZone) return /* @__PURE__ */ new Set();
|
|
3035
|
+
const directions = /* @__PURE__ */ new Set();
|
|
3036
|
+
const angle = Math.atan2(dy, dx) * (180 / Math.PI);
|
|
3037
|
+
if (angle >= -150 && angle <= -30) directions.add("up");
|
|
3038
|
+
if (angle >= 30 && angle <= 150) directions.add("down");
|
|
3039
|
+
if (angle >= 120 || angle <= -120) directions.add("left");
|
|
3040
|
+
if (angle >= -60 && angle <= 60) directions.add("right");
|
|
3041
|
+
return directions;
|
|
3042
|
+
}, []);
|
|
3043
|
+
const updateVisuals = React2.useCallback((directions) => {
|
|
3044
|
+
const activeFill = `${systemColor}80`;
|
|
3045
|
+
const inactiveFill = "rgba(255, 255, 255, 0.05)";
|
|
3046
|
+
const activeStroke = systemColor;
|
|
3047
|
+
const inactiveStroke = "rgba(255, 255, 255, 0.2)";
|
|
3048
|
+
const glow = `0 0 15px ${systemColor}`;
|
|
3049
|
+
const updatePart = (ref, isActive) => {
|
|
3050
|
+
if (ref.current) {
|
|
3051
|
+
ref.current.style.fill = isActive ? activeFill : inactiveFill;
|
|
3052
|
+
ref.current.style.stroke = isActive ? activeStroke : inactiveStroke;
|
|
3053
|
+
ref.current.style.filter = isActive ? `drop-shadow(${glow})` : "none";
|
|
3054
|
+
ref.current.style.transform = isActive ? "scale(0.98)" : "scale(1)";
|
|
3055
|
+
ref.current.style.transformOrigin = "center";
|
|
3056
|
+
}
|
|
3057
|
+
};
|
|
3058
|
+
updatePart(upPathRef, directions.has("up"));
|
|
3059
|
+
updatePart(downPathRef, directions.has("down"));
|
|
3060
|
+
updatePart(leftPathRef, directions.has("left"));
|
|
3061
|
+
updatePart(rightPathRef, directions.has("right"));
|
|
3062
|
+
if (centerCircleRef.current) {
|
|
3063
|
+
const isAny = directions.size > 0;
|
|
3064
|
+
centerCircleRef.current.style.fill = isAny ? systemColor : "rgba(0,0,0,0.5)";
|
|
3065
|
+
centerCircleRef.current.style.stroke = isAny ? "#fff" : "rgba(255,255,255,0.3)";
|
|
3066
|
+
}
|
|
3067
|
+
}, [systemColor]);
|
|
3068
|
+
const updateDirections = React2.useCallback((newDirections) => {
|
|
3069
|
+
const prev = activeDirectionsRef.current;
|
|
3070
|
+
prev.forEach((dir) => {
|
|
3071
|
+
if (!newDirections.has(dir)) {
|
|
3072
|
+
onButtonUp(dir);
|
|
3073
|
+
}
|
|
3074
|
+
});
|
|
3075
|
+
newDirections.forEach((dir) => {
|
|
3076
|
+
if (!prev.has(dir)) {
|
|
3077
|
+
if (hapticsEnabled && navigator.vibrate) navigator.vibrate(5);
|
|
3078
|
+
onButtonDown(dir);
|
|
3079
|
+
}
|
|
3080
|
+
});
|
|
3081
|
+
activeDirectionsRef.current = newDirections;
|
|
3082
|
+
updateVisuals(newDirections);
|
|
3083
|
+
}, [updateVisuals, onButtonDown, onButtonUp, hapticsEnabled]);
|
|
3084
|
+
const handleTouchStart = React2.useCallback((e) => {
|
|
3085
|
+
e.preventDefault();
|
|
3086
|
+
if (activeTouchRef.current !== null) return;
|
|
3087
|
+
const touch = e.changedTouches[0];
|
|
3088
|
+
activeTouchRef.current = touch.identifier;
|
|
3089
|
+
const rect = dpadRef.current?.getBoundingClientRect();
|
|
3090
|
+
if (!rect) return;
|
|
3091
|
+
if (onPositionChange) {
|
|
3092
|
+
drag.checkDragStart(touch.clientX, touch.clientY, rect);
|
|
3093
|
+
}
|
|
3094
|
+
if (!drag.isDragging) {
|
|
3095
|
+
updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
|
|
3096
|
+
}
|
|
3097
|
+
}, [getDirectionsFromTouch, updateDirections, onPositionChange, drag]);
|
|
3098
|
+
const handleTouchMove = React2.useCallback((e) => {
|
|
3099
|
+
e.preventDefault();
|
|
3100
|
+
let touch = null;
|
|
3101
|
+
for (let i = 0; i < e.changedTouches.length; i++) {
|
|
3102
|
+
if (e.changedTouches[i].identifier === activeTouchRef.current) {
|
|
3103
|
+
touch = e.changedTouches[i];
|
|
3104
|
+
break;
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
if (!touch) return;
|
|
3108
|
+
if (drag.isDragging) {
|
|
3109
|
+
drag.handleDragMove(touch.clientX, touch.clientY);
|
|
3110
|
+
} else if (onPositionChange) {
|
|
3111
|
+
const startedDrag = drag.checkMoveThreshold(touch.clientX, touch.clientY);
|
|
3112
|
+
if (!startedDrag) {
|
|
3113
|
+
drag.clearDragTimer();
|
|
3114
|
+
const rect = dpadRef.current?.getBoundingClientRect();
|
|
3115
|
+
if (rect) {
|
|
3116
|
+
updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
} else {
|
|
3120
|
+
const rect = dpadRef.current?.getBoundingClientRect();
|
|
3121
|
+
if (rect) {
|
|
3122
|
+
updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
}, [drag, getDirectionsFromTouch, updateDirections, onPositionChange]);
|
|
3126
|
+
const handleTouchEnd = React2.useCallback((e) => {
|
|
3127
|
+
e.preventDefault();
|
|
3128
|
+
drag.clearDragTimer();
|
|
3129
|
+
let touchEnded = false;
|
|
3130
|
+
for (let i = 0; i < e.changedTouches.length; i++) {
|
|
3131
|
+
if (e.changedTouches[i].identifier === activeTouchRef.current) {
|
|
3132
|
+
touchEnded = true;
|
|
3133
|
+
break;
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3136
|
+
if (touchEnded) {
|
|
3137
|
+
activeTouchRef.current = null;
|
|
3138
|
+
if (drag.isDragging) {
|
|
3139
|
+
drag.handleDragEnd();
|
|
3140
|
+
} else {
|
|
3141
|
+
activeDirectionsRef.current.forEach((dir) => onButtonUp(dir));
|
|
3142
|
+
activeDirectionsRef.current = /* @__PURE__ */ new Set();
|
|
3143
|
+
updateVisuals(/* @__PURE__ */ new Set());
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
}, [updateVisuals, drag, onButtonUp]);
|
|
3147
|
+
useTouchEvents(dpadRef, {
|
|
3148
|
+
onTouchStart: handleTouchStart,
|
|
3149
|
+
onTouchMove: handleTouchMove,
|
|
3150
|
+
onTouchEnd: handleTouchEnd,
|
|
3151
|
+
onTouchCancel: handleTouchEnd
|
|
3152
|
+
}, { cleanup: drag.clearDragTimer });
|
|
3153
|
+
const leftPx = displayX / 100 * containerWidth - size / 2;
|
|
3154
|
+
const topPx = displayY / 100 * containerHeight - size / 2;
|
|
3155
|
+
const dUp = "M 35,5 L 65,5 L 65,35 L 50,50 L 35,35 Z";
|
|
3156
|
+
const dRight = "M 65,35 L 95,35 L 95,65 L 65,65 L 50,50 Z";
|
|
3157
|
+
const dDown = "M 65,65 L 65,95 L 35,95 L 35,65 L 50,50 Z";
|
|
3158
|
+
const dLeft = "M 35,65 L 5,65 L 5,35 L 35,35 L 50,50 Z";
|
|
3159
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3160
|
+
"div",
|
|
3161
|
+
{
|
|
3162
|
+
ref: dpadRef,
|
|
3163
|
+
className: `absolute pointer-events-auto touch-manipulation select-none ${drag.isDragging ? "opacity-60" : ""}`,
|
|
3164
|
+
style: {
|
|
3165
|
+
top: 0,
|
|
3166
|
+
left: 0,
|
|
3167
|
+
transform: `translate3d(${leftPx}px, ${topPx}px, 0)${drag.isDragging ? " scale(1.05)" : ""}`,
|
|
3168
|
+
width: size,
|
|
3169
|
+
height: size,
|
|
3170
|
+
opacity: isLandscape ? 0.75 : 0.9,
|
|
3171
|
+
WebkitTouchCallout: "none",
|
|
3172
|
+
WebkitUserSelect: "none",
|
|
3173
|
+
touchAction: "none",
|
|
3174
|
+
transition: drag.isDragging ? "none" : "transform 0.1s ease-out"
|
|
3175
|
+
},
|
|
3176
|
+
children: [
|
|
3177
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: `absolute inset-0 rounded-full bg-black/40 backdrop-blur-md border shadow-lg ${drag.isDragging ? "border-white/50 ring-2 ring-white/30" : "border-white/10"}` }),
|
|
3178
|
+
/* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "100%", height: "100%", viewBox: "0 0 100 100", className: "drop-shadow-xl relative z-10", children: [
|
|
3179
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { ref: upPathRef, d: dUp, fill: "rgba(255,255,255,0.05)", stroke: "rgba(255,255,255,0.2)", strokeWidth: "1", className: "transition-all duration-75" }),
|
|
3180
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { ref: rightPathRef, d: dRight, fill: "rgba(255,255,255,0.05)", stroke: "rgba(255,255,255,0.2)", strokeWidth: "1", className: "transition-all duration-75" }),
|
|
3181
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { ref: downPathRef, d: dDown, fill: "rgba(255,255,255,0.05)", stroke: "rgba(255,255,255,0.2)", strokeWidth: "1", className: "transition-all duration-75" }),
|
|
3182
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { ref: leftPathRef, d: dLeft, fill: "rgba(255,255,255,0.05)", stroke: "rgba(255,255,255,0.2)", strokeWidth: "1", className: "transition-all duration-75" }),
|
|
3183
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3184
|
+
"circle",
|
|
3185
|
+
{
|
|
3186
|
+
ref: centerCircleRef,
|
|
3187
|
+
cx: "50",
|
|
3188
|
+
cy: "50",
|
|
3189
|
+
r: "12",
|
|
3190
|
+
fill: drag.isDragging ? systemColor : "rgba(0,0,0,0.5)",
|
|
3191
|
+
stroke: drag.isDragging ? "#fff" : "rgba(255,255,255,0.3)",
|
|
3192
|
+
strokeWidth: drag.isDragging ? 2 : 1
|
|
3193
|
+
}
|
|
3194
|
+
),
|
|
3195
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M 50,15 L 50,25 M 45,20 L 50,15 L 55,20", stroke: "white", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", fill: "none", opacity: "0.8", pointerEvents: "none" }),
|
|
3196
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M 50,85 L 50,75 M 45,80 L 50,85 L 55,80", stroke: "white", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", fill: "none", opacity: "0.8", pointerEvents: "none" }),
|
|
3197
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M 15,50 L 25,50 M 20,45 L 15,50 L 20,55", stroke: "white", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", fill: "none", opacity: "0.8", pointerEvents: "none" }),
|
|
3198
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M 85,50 L 75,50 M 80,45 L 85,50 L 80,55", stroke: "white", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", fill: "none", opacity: "0.8", pointerEvents: "none" })
|
|
3199
|
+
] })
|
|
3200
|
+
]
|
|
3201
|
+
}
|
|
3202
|
+
);
|
|
3203
|
+
});
|
|
3204
|
+
var Dpad_default = Dpad;
|
|
2958
3205
|
|
|
2959
|
-
// src/
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
3206
|
+
// src/components/VirtualController/positioning.ts
|
|
3207
|
+
function adjustButtonPosition(config, context) {
|
|
3208
|
+
const sizeMultiplier = context.isFullscreen ? 1.1 : 1;
|
|
3209
|
+
return {
|
|
3210
|
+
...config,
|
|
3211
|
+
size: Math.floor(config.size * sizeMultiplier)
|
|
3212
|
+
};
|
|
3213
|
+
}
|
|
3214
|
+
var STORAGE_KEY = "virtual-button-positions";
|
|
3215
|
+
function useButtonPositions() {
|
|
3216
|
+
const [landscapePositions, setLandscapePositions] = React2.useState({});
|
|
3217
|
+
const [portraitPositions, setPortraitPositions] = React2.useState({});
|
|
3218
|
+
React2.useEffect(() => {
|
|
3219
|
+
try {
|
|
3220
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
3221
|
+
if (stored) {
|
|
3222
|
+
const parsed = JSON.parse(stored);
|
|
3223
|
+
if (parsed.landscape && parsed.portrait) {
|
|
3224
|
+
setLandscapePositions(parsed.landscape);
|
|
3225
|
+
setPortraitPositions(parsed.portrait);
|
|
3226
|
+
} else {
|
|
3227
|
+
setLandscapePositions(parsed);
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
} catch (e) {
|
|
3231
|
+
console.error("Failed to load button positions:", e);
|
|
3232
|
+
}
|
|
3233
|
+
}, []);
|
|
3234
|
+
const savePosition = React2.useCallback((buttonType, x, y, isLandscape) => {
|
|
3235
|
+
if (isLandscape) {
|
|
3236
|
+
setLandscapePositions((prev) => {
|
|
3237
|
+
const updated = { ...prev, [buttonType]: { x, y } };
|
|
3238
|
+
try {
|
|
3239
|
+
const stored = {
|
|
3240
|
+
landscape: updated,
|
|
3241
|
+
portrait: portraitPositions
|
|
3242
|
+
};
|
|
3243
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(stored));
|
|
3244
|
+
} catch (e) {
|
|
3245
|
+
console.error("Failed to save button position:", e);
|
|
3246
|
+
}
|
|
3247
|
+
return updated;
|
|
3248
|
+
});
|
|
3249
|
+
} else {
|
|
3250
|
+
setPortraitPositions((prev) => {
|
|
3251
|
+
const updated = { ...prev, [buttonType]: { x, y } };
|
|
3252
|
+
try {
|
|
3253
|
+
const stored = {
|
|
3254
|
+
landscape: landscapePositions,
|
|
3255
|
+
portrait: updated
|
|
3256
|
+
};
|
|
3257
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(stored));
|
|
3258
|
+
} catch (e) {
|
|
3259
|
+
console.error("Failed to save button position:", e);
|
|
3260
|
+
}
|
|
3261
|
+
return updated;
|
|
3262
|
+
});
|
|
3263
|
+
}
|
|
3264
|
+
}, [landscapePositions, portraitPositions]);
|
|
3265
|
+
const getPosition = React2.useCallback((buttonType, isLandscape) => {
|
|
3266
|
+
const positions = isLandscape ? landscapePositions : portraitPositions;
|
|
3267
|
+
return positions[buttonType] || null;
|
|
3268
|
+
}, [landscapePositions, portraitPositions]);
|
|
3269
|
+
const resetPositions = React2.useCallback(() => {
|
|
3270
|
+
setLandscapePositions({});
|
|
3271
|
+
setPortraitPositions({});
|
|
3272
|
+
try {
|
|
3273
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
3274
|
+
} catch (e) {
|
|
3275
|
+
console.error("Failed to reset button positions:", e);
|
|
3276
|
+
}
|
|
3277
|
+
}, []);
|
|
3278
|
+
return {
|
|
3279
|
+
landscapePositions,
|
|
3280
|
+
portraitPositions,
|
|
3281
|
+
savePosition,
|
|
3282
|
+
getPosition,
|
|
3283
|
+
resetPositions
|
|
3284
|
+
};
|
|
3285
|
+
}
|
|
3286
|
+
|
|
3287
|
+
// src/lib/controls/types.ts
|
|
3288
|
+
var DPAD_BUTTONS2 = ["up", "down", "left", "right"];
|
|
3289
|
+
var FACE_BUTTONS = ["a", "b", "x", "y"];
|
|
3290
|
+
var SHOULDER_BUTTONS = ["l", "r"];
|
|
3291
|
+
var TRIGGER_BUTTONS = ["l2", "r2"];
|
|
3292
|
+
var STICK_BUTTONS = ["l3", "r3"];
|
|
3293
|
+
var SYSTEM_BUTTONS = ["start", "select"];
|
|
3294
|
+
var ALL_BUTTONS = [
|
|
3295
|
+
...DPAD_BUTTONS2,
|
|
3296
|
+
...FACE_BUTTONS,
|
|
3297
|
+
...SHOULDER_BUTTONS,
|
|
3298
|
+
...TRIGGER_BUTTONS,
|
|
3299
|
+
...STICK_BUTTONS,
|
|
3300
|
+
...SYSTEM_BUTTONS
|
|
3301
|
+
];
|
|
3302
|
+
|
|
3303
|
+
// src/lib/controls/defaults.ts
|
|
3304
|
+
var STANDARD_GAMEPAD_BUTTONS = {
|
|
3305
|
+
// Face buttons (Xbox/PlayStation layout)
|
|
3306
|
+
a: 0,
|
|
3307
|
+
// A / Cross (bottom)
|
|
3308
|
+
b: 1,
|
|
3309
|
+
// B / Circle (right)
|
|
3310
|
+
x: 2,
|
|
3311
|
+
// X / Square (left)
|
|
3312
|
+
y: 3,
|
|
3313
|
+
// Y / Triangle (top)
|
|
3314
|
+
// Shoulders
|
|
3315
|
+
l: 4,
|
|
3316
|
+
// LB / L1
|
|
3317
|
+
r: 5,
|
|
3318
|
+
// RB / R1
|
|
3319
|
+
// Triggers
|
|
3320
|
+
l2: 6,
|
|
3321
|
+
// LT / L2
|
|
3322
|
+
r2: 7,
|
|
2995
3323
|
// RT / R2
|
|
2996
3324
|
// System
|
|
2997
3325
|
select: 8,
|
|
@@ -3541,351 +3869,6 @@ function getKeyboardCode(buttonType, controls) {
|
|
|
3541
3869
|
}
|
|
3542
3870
|
return DEFAULT_CONTROLS[key] ?? null;
|
|
3543
3871
|
}
|
|
3544
|
-
function getKeyName(code) {
|
|
3545
|
-
if (code.startsWith("Key")) return code.slice(3).toLowerCase();
|
|
3546
|
-
if (code.startsWith("Arrow")) return code.slice(5);
|
|
3547
|
-
if (code === "Enter") return "Enter";
|
|
3548
|
-
if (code === "ShiftRight" || code === "ShiftLeft") return "Shift";
|
|
3549
|
-
return code;
|
|
3550
|
-
}
|
|
3551
|
-
function getCanvas() {
|
|
3552
|
-
return document.querySelector(".game-canvas-container canvas") || document.querySelector("canvas");
|
|
3553
|
-
}
|
|
3554
|
-
function dispatchKeyboardEvent(type, code) {
|
|
3555
|
-
const canvas = getCanvas();
|
|
3556
|
-
if (!canvas) {
|
|
3557
|
-
return false;
|
|
3558
|
-
}
|
|
3559
|
-
const event = new KeyboardEvent(type, {
|
|
3560
|
-
code,
|
|
3561
|
-
key: getKeyName(code),
|
|
3562
|
-
bubbles: true,
|
|
3563
|
-
cancelable: true
|
|
3564
|
-
});
|
|
3565
|
-
canvas.dispatchEvent(event);
|
|
3566
|
-
return true;
|
|
3567
|
-
}
|
|
3568
|
-
var CENTER_TOUCH_RADIUS = 0.35;
|
|
3569
|
-
var Dpad = React2__default.default.memo(function Dpad2({
|
|
3570
|
-
size = 180,
|
|
3571
|
-
x,
|
|
3572
|
-
y,
|
|
3573
|
-
containerWidth,
|
|
3574
|
-
containerHeight,
|
|
3575
|
-
controls,
|
|
3576
|
-
systemColor = "#00FF41",
|
|
3577
|
-
isLandscape = false,
|
|
3578
|
-
customPosition,
|
|
3579
|
-
onPositionChange
|
|
3580
|
-
}) {
|
|
3581
|
-
const dpadRef = React2.useRef(null);
|
|
3582
|
-
const activeTouchRef = React2.useRef(null);
|
|
3583
|
-
const activeDirectionsRef = React2.useRef(/* @__PURE__ */ new Set());
|
|
3584
|
-
const upPathRef = React2.useRef(null);
|
|
3585
|
-
const downPathRef = React2.useRef(null);
|
|
3586
|
-
const leftPathRef = React2.useRef(null);
|
|
3587
|
-
const rightPathRef = React2.useRef(null);
|
|
3588
|
-
const centerCircleRef = React2.useRef(null);
|
|
3589
|
-
const displayX = customPosition ? customPosition.x : x;
|
|
3590
|
-
const displayY = customPosition ? customPosition.y : y;
|
|
3591
|
-
const releaseAllDirections = React2.useCallback((getKeyCode2) => {
|
|
3592
|
-
activeDirectionsRef.current.forEach((dir) => {
|
|
3593
|
-
const keyCode = getKeyCode2(dir);
|
|
3594
|
-
if (keyCode) dispatchKeyboardEvent("keyup", keyCode);
|
|
3595
|
-
});
|
|
3596
|
-
activeDirectionsRef.current = /* @__PURE__ */ new Set();
|
|
3597
|
-
}, []);
|
|
3598
|
-
const getKeyCode = React2.useCallback((direction) => {
|
|
3599
|
-
if (!controls) {
|
|
3600
|
-
const defaults = {
|
|
3601
|
-
up: "ArrowUp",
|
|
3602
|
-
down: "ArrowDown",
|
|
3603
|
-
left: "ArrowLeft",
|
|
3604
|
-
right: "ArrowRight"
|
|
3605
|
-
};
|
|
3606
|
-
return defaults[direction];
|
|
3607
|
-
}
|
|
3608
|
-
return controls[direction] || "";
|
|
3609
|
-
}, [controls]);
|
|
3610
|
-
const drag = useDrag({
|
|
3611
|
-
elementSize: size,
|
|
3612
|
-
displayX,
|
|
3613
|
-
displayY,
|
|
3614
|
-
containerWidth,
|
|
3615
|
-
containerHeight,
|
|
3616
|
-
onPositionChange,
|
|
3617
|
-
centerThreshold: CENTER_TOUCH_RADIUS,
|
|
3618
|
-
onDragStart: () => {
|
|
3619
|
-
releaseAllDirections(getKeyCode);
|
|
3620
|
-
updateVisuals(/* @__PURE__ */ new Set());
|
|
3621
|
-
}
|
|
3622
|
-
});
|
|
3623
|
-
const getDirectionsFromTouch = React2.useCallback((touchX, touchY, rect) => {
|
|
3624
|
-
const centerX = rect.left + rect.width / 2;
|
|
3625
|
-
const centerY = rect.top + rect.height / 2;
|
|
3626
|
-
const dx = touchX - centerX;
|
|
3627
|
-
const dy = touchY - centerY;
|
|
3628
|
-
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
3629
|
-
const deadZone = rect.width / 2 * 0.15;
|
|
3630
|
-
if (distance < deadZone) return /* @__PURE__ */ new Set();
|
|
3631
|
-
const directions = /* @__PURE__ */ new Set();
|
|
3632
|
-
const angle = Math.atan2(dy, dx) * (180 / Math.PI);
|
|
3633
|
-
if (angle >= -150 && angle <= -30) directions.add("up");
|
|
3634
|
-
if (angle >= 30 && angle <= 150) directions.add("down");
|
|
3635
|
-
if (angle >= 120 || angle <= -120) directions.add("left");
|
|
3636
|
-
if (angle >= -60 && angle <= 60) directions.add("right");
|
|
3637
|
-
return directions;
|
|
3638
|
-
}, []);
|
|
3639
|
-
const updateVisuals = React2.useCallback((directions) => {
|
|
3640
|
-
const activeFill = `${systemColor}80`;
|
|
3641
|
-
const inactiveFill = "rgba(255, 255, 255, 0.05)";
|
|
3642
|
-
const activeStroke = systemColor;
|
|
3643
|
-
const inactiveStroke = "rgba(255, 255, 255, 0.2)";
|
|
3644
|
-
const glow = `0 0 15px ${systemColor}`;
|
|
3645
|
-
const updatePart = (ref, isActive) => {
|
|
3646
|
-
if (ref.current) {
|
|
3647
|
-
ref.current.style.fill = isActive ? activeFill : inactiveFill;
|
|
3648
|
-
ref.current.style.stroke = isActive ? activeStroke : inactiveStroke;
|
|
3649
|
-
ref.current.style.filter = isActive ? `drop-shadow(${glow})` : "none";
|
|
3650
|
-
ref.current.style.transform = isActive ? "scale(0.98)" : "scale(1)";
|
|
3651
|
-
ref.current.style.transformOrigin = "center";
|
|
3652
|
-
}
|
|
3653
|
-
};
|
|
3654
|
-
updatePart(upPathRef, directions.has("up"));
|
|
3655
|
-
updatePart(downPathRef, directions.has("down"));
|
|
3656
|
-
updatePart(leftPathRef, directions.has("left"));
|
|
3657
|
-
updatePart(rightPathRef, directions.has("right"));
|
|
3658
|
-
if (centerCircleRef.current) {
|
|
3659
|
-
const isAny = directions.size > 0;
|
|
3660
|
-
centerCircleRef.current.style.fill = isAny ? systemColor : "rgba(0,0,0,0.5)";
|
|
3661
|
-
centerCircleRef.current.style.stroke = isAny ? "#fff" : "rgba(255,255,255,0.3)";
|
|
3662
|
-
}
|
|
3663
|
-
}, [systemColor]);
|
|
3664
|
-
const updateDirections = React2.useCallback((newDirections) => {
|
|
3665
|
-
const prev = activeDirectionsRef.current;
|
|
3666
|
-
prev.forEach((dir) => {
|
|
3667
|
-
if (!newDirections.has(dir)) {
|
|
3668
|
-
const keyCode = getKeyCode(dir);
|
|
3669
|
-
if (keyCode) dispatchKeyboardEvent("keyup", keyCode);
|
|
3670
|
-
}
|
|
3671
|
-
});
|
|
3672
|
-
newDirections.forEach((dir) => {
|
|
3673
|
-
if (!prev.has(dir)) {
|
|
3674
|
-
const keyCode = getKeyCode(dir);
|
|
3675
|
-
if (keyCode) {
|
|
3676
|
-
if (navigator.vibrate) navigator.vibrate(5);
|
|
3677
|
-
dispatchKeyboardEvent("keydown", keyCode);
|
|
3678
|
-
}
|
|
3679
|
-
}
|
|
3680
|
-
});
|
|
3681
|
-
activeDirectionsRef.current = newDirections;
|
|
3682
|
-
updateVisuals(newDirections);
|
|
3683
|
-
}, [getKeyCode, updateVisuals]);
|
|
3684
|
-
const handleTouchStart = React2.useCallback((e) => {
|
|
3685
|
-
e.preventDefault();
|
|
3686
|
-
if (activeTouchRef.current !== null) return;
|
|
3687
|
-
const touch = e.changedTouches[0];
|
|
3688
|
-
activeTouchRef.current = touch.identifier;
|
|
3689
|
-
const rect = dpadRef.current?.getBoundingClientRect();
|
|
3690
|
-
if (!rect) return;
|
|
3691
|
-
if (onPositionChange) {
|
|
3692
|
-
drag.checkDragStart(touch.clientX, touch.clientY, rect);
|
|
3693
|
-
}
|
|
3694
|
-
if (!drag.isDragging) {
|
|
3695
|
-
updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
|
|
3696
|
-
}
|
|
3697
|
-
}, [getDirectionsFromTouch, updateDirections, onPositionChange, drag]);
|
|
3698
|
-
const handleTouchMove = React2.useCallback((e) => {
|
|
3699
|
-
e.preventDefault();
|
|
3700
|
-
let touch = null;
|
|
3701
|
-
for (let i = 0; i < e.changedTouches.length; i++) {
|
|
3702
|
-
if (e.changedTouches[i].identifier === activeTouchRef.current) {
|
|
3703
|
-
touch = e.changedTouches[i];
|
|
3704
|
-
break;
|
|
3705
|
-
}
|
|
3706
|
-
}
|
|
3707
|
-
if (!touch) return;
|
|
3708
|
-
if (drag.isDragging) {
|
|
3709
|
-
drag.handleDragMove(touch.clientX, touch.clientY);
|
|
3710
|
-
} else if (onPositionChange) {
|
|
3711
|
-
const startedDrag = drag.checkMoveThreshold(touch.clientX, touch.clientY);
|
|
3712
|
-
if (!startedDrag) {
|
|
3713
|
-
drag.clearDragTimer();
|
|
3714
|
-
const rect = dpadRef.current?.getBoundingClientRect();
|
|
3715
|
-
if (rect) {
|
|
3716
|
-
updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
|
|
3717
|
-
}
|
|
3718
|
-
}
|
|
3719
|
-
} else {
|
|
3720
|
-
const rect = dpadRef.current?.getBoundingClientRect();
|
|
3721
|
-
if (rect) {
|
|
3722
|
-
updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
|
|
3723
|
-
}
|
|
3724
|
-
}
|
|
3725
|
-
}, [drag, getDirectionsFromTouch, updateDirections, onPositionChange]);
|
|
3726
|
-
const handleTouchEnd = React2.useCallback((e) => {
|
|
3727
|
-
e.preventDefault();
|
|
3728
|
-
drag.clearDragTimer();
|
|
3729
|
-
let touchEnded = false;
|
|
3730
|
-
for (let i = 0; i < e.changedTouches.length; i++) {
|
|
3731
|
-
if (e.changedTouches[i].identifier === activeTouchRef.current) {
|
|
3732
|
-
touchEnded = true;
|
|
3733
|
-
break;
|
|
3734
|
-
}
|
|
3735
|
-
}
|
|
3736
|
-
if (touchEnded) {
|
|
3737
|
-
activeTouchRef.current = null;
|
|
3738
|
-
if (drag.isDragging) {
|
|
3739
|
-
drag.handleDragEnd();
|
|
3740
|
-
} else {
|
|
3741
|
-
activeDirectionsRef.current.forEach((dir) => {
|
|
3742
|
-
const keyCode = getKeyCode(dir);
|
|
3743
|
-
if (keyCode) dispatchKeyboardEvent("keyup", keyCode);
|
|
3744
|
-
});
|
|
3745
|
-
activeDirectionsRef.current = /* @__PURE__ */ new Set();
|
|
3746
|
-
updateVisuals(/* @__PURE__ */ new Set());
|
|
3747
|
-
}
|
|
3748
|
-
}
|
|
3749
|
-
}, [getKeyCode, updateVisuals, drag]);
|
|
3750
|
-
useTouchEvents(dpadRef, {
|
|
3751
|
-
onTouchStart: handleTouchStart,
|
|
3752
|
-
onTouchMove: handleTouchMove,
|
|
3753
|
-
onTouchEnd: handleTouchEnd,
|
|
3754
|
-
onTouchCancel: handleTouchEnd
|
|
3755
|
-
}, { cleanup: drag.clearDragTimer });
|
|
3756
|
-
const leftPx = displayX / 100 * containerWidth - size / 2;
|
|
3757
|
-
const topPx = displayY / 100 * containerHeight - size / 2;
|
|
3758
|
-
const dUp = "M 35,5 L 65,5 L 65,35 L 50,50 L 35,35 Z";
|
|
3759
|
-
const dRight = "M 65,35 L 95,35 L 95,65 L 65,65 L 50,50 Z";
|
|
3760
|
-
const dDown = "M 65,65 L 65,95 L 35,95 L 35,65 L 50,50 Z";
|
|
3761
|
-
const dLeft = "M 35,65 L 5,65 L 5,35 L 35,35 L 50,50 Z";
|
|
3762
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3763
|
-
"div",
|
|
3764
|
-
{
|
|
3765
|
-
ref: dpadRef,
|
|
3766
|
-
className: `absolute pointer-events-auto touch-manipulation select-none ${drag.isDragging ? "opacity-60" : ""}`,
|
|
3767
|
-
style: {
|
|
3768
|
-
top: 0,
|
|
3769
|
-
left: 0,
|
|
3770
|
-
transform: `translate3d(${leftPx}px, ${topPx}px, 0)${drag.isDragging ? " scale(1.05)" : ""}`,
|
|
3771
|
-
width: size,
|
|
3772
|
-
height: size,
|
|
3773
|
-
opacity: isLandscape ? 0.75 : 0.9,
|
|
3774
|
-
WebkitTouchCallout: "none",
|
|
3775
|
-
WebkitUserSelect: "none",
|
|
3776
|
-
touchAction: "none",
|
|
3777
|
-
transition: drag.isDragging ? "none" : "transform 0.1s ease-out"
|
|
3778
|
-
},
|
|
3779
|
-
children: [
|
|
3780
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: `absolute inset-0 rounded-full bg-black/40 backdrop-blur-md border shadow-lg ${drag.isDragging ? "border-white/50 ring-2 ring-white/30" : "border-white/10"}` }),
|
|
3781
|
-
/* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "100%", height: "100%", viewBox: "0 0 100 100", className: "drop-shadow-xl relative z-10", children: [
|
|
3782
|
-
/* @__PURE__ */ jsxRuntime.jsx("path", { ref: upPathRef, d: dUp, fill: "rgba(255,255,255,0.05)", stroke: "rgba(255,255,255,0.2)", strokeWidth: "1", className: "transition-all duration-75" }),
|
|
3783
|
-
/* @__PURE__ */ jsxRuntime.jsx("path", { ref: rightPathRef, d: dRight, fill: "rgba(255,255,255,0.05)", stroke: "rgba(255,255,255,0.2)", strokeWidth: "1", className: "transition-all duration-75" }),
|
|
3784
|
-
/* @__PURE__ */ jsxRuntime.jsx("path", { ref: downPathRef, d: dDown, fill: "rgba(255,255,255,0.05)", stroke: "rgba(255,255,255,0.2)", strokeWidth: "1", className: "transition-all duration-75" }),
|
|
3785
|
-
/* @__PURE__ */ jsxRuntime.jsx("path", { ref: leftPathRef, d: dLeft, fill: "rgba(255,255,255,0.05)", stroke: "rgba(255,255,255,0.2)", strokeWidth: "1", className: "transition-all duration-75" }),
|
|
3786
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3787
|
-
"circle",
|
|
3788
|
-
{
|
|
3789
|
-
ref: centerCircleRef,
|
|
3790
|
-
cx: "50",
|
|
3791
|
-
cy: "50",
|
|
3792
|
-
r: "12",
|
|
3793
|
-
fill: drag.isDragging ? systemColor : "rgba(0,0,0,0.5)",
|
|
3794
|
-
stroke: drag.isDragging ? "#fff" : "rgba(255,255,255,0.3)",
|
|
3795
|
-
strokeWidth: drag.isDragging ? 2 : 1
|
|
3796
|
-
}
|
|
3797
|
-
),
|
|
3798
|
-
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M 50,15 L 50,25 M 45,20 L 50,15 L 55,20", stroke: "white", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", fill: "none", opacity: "0.8", pointerEvents: "none" }),
|
|
3799
|
-
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M 50,85 L 50,75 M 45,80 L 50,85 L 55,80", stroke: "white", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", fill: "none", opacity: "0.8", pointerEvents: "none" }),
|
|
3800
|
-
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M 15,50 L 25,50 M 20,45 L 15,50 L 20,55", stroke: "white", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", fill: "none", opacity: "0.8", pointerEvents: "none" }),
|
|
3801
|
-
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M 85,50 L 75,50 M 80,45 L 85,50 L 80,55", stroke: "white", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", fill: "none", opacity: "0.8", pointerEvents: "none" })
|
|
3802
|
-
] })
|
|
3803
|
-
]
|
|
3804
|
-
}
|
|
3805
|
-
);
|
|
3806
|
-
});
|
|
3807
|
-
var Dpad_default = Dpad;
|
|
3808
|
-
|
|
3809
|
-
// src/components/VirtualController/positioning.ts
|
|
3810
|
-
function adjustButtonPosition(config, context) {
|
|
3811
|
-
const sizeMultiplier = context.isFullscreen ? 1.1 : 1;
|
|
3812
|
-
return {
|
|
3813
|
-
...config,
|
|
3814
|
-
size: Math.floor(config.size * sizeMultiplier)
|
|
3815
|
-
};
|
|
3816
|
-
}
|
|
3817
|
-
var STORAGE_KEY = "virtual-button-positions";
|
|
3818
|
-
function useButtonPositions() {
|
|
3819
|
-
const [landscapePositions, setLandscapePositions] = React2.useState({});
|
|
3820
|
-
const [portraitPositions, setPortraitPositions] = React2.useState({});
|
|
3821
|
-
React2.useEffect(() => {
|
|
3822
|
-
try {
|
|
3823
|
-
const stored = localStorage.getItem(STORAGE_KEY);
|
|
3824
|
-
if (stored) {
|
|
3825
|
-
const parsed = JSON.parse(stored);
|
|
3826
|
-
if (parsed.landscape && parsed.portrait) {
|
|
3827
|
-
setLandscapePositions(parsed.landscape);
|
|
3828
|
-
setPortraitPositions(parsed.portrait);
|
|
3829
|
-
} else {
|
|
3830
|
-
setLandscapePositions(parsed);
|
|
3831
|
-
}
|
|
3832
|
-
}
|
|
3833
|
-
} catch (e) {
|
|
3834
|
-
console.error("Failed to load button positions:", e);
|
|
3835
|
-
}
|
|
3836
|
-
}, []);
|
|
3837
|
-
const savePosition = React2.useCallback((buttonType, x, y, isLandscape) => {
|
|
3838
|
-
if (isLandscape) {
|
|
3839
|
-
setLandscapePositions((prev) => {
|
|
3840
|
-
const updated = { ...prev, [buttonType]: { x, y } };
|
|
3841
|
-
try {
|
|
3842
|
-
const stored = {
|
|
3843
|
-
landscape: updated,
|
|
3844
|
-
portrait: portraitPositions
|
|
3845
|
-
};
|
|
3846
|
-
localStorage.setItem(STORAGE_KEY, JSON.stringify(stored));
|
|
3847
|
-
} catch (e) {
|
|
3848
|
-
console.error("Failed to save button position:", e);
|
|
3849
|
-
}
|
|
3850
|
-
return updated;
|
|
3851
|
-
});
|
|
3852
|
-
} else {
|
|
3853
|
-
setPortraitPositions((prev) => {
|
|
3854
|
-
const updated = { ...prev, [buttonType]: { x, y } };
|
|
3855
|
-
try {
|
|
3856
|
-
const stored = {
|
|
3857
|
-
landscape: landscapePositions,
|
|
3858
|
-
portrait: updated
|
|
3859
|
-
};
|
|
3860
|
-
localStorage.setItem(STORAGE_KEY, JSON.stringify(stored));
|
|
3861
|
-
} catch (e) {
|
|
3862
|
-
console.error("Failed to save button position:", e);
|
|
3863
|
-
}
|
|
3864
|
-
return updated;
|
|
3865
|
-
});
|
|
3866
|
-
}
|
|
3867
|
-
}, [landscapePositions, portraitPositions]);
|
|
3868
|
-
const getPosition = React2.useCallback((buttonType, isLandscape) => {
|
|
3869
|
-
const positions = isLandscape ? landscapePositions : portraitPositions;
|
|
3870
|
-
return positions[buttonType] || null;
|
|
3871
|
-
}, [landscapePositions, portraitPositions]);
|
|
3872
|
-
const resetPositions = React2.useCallback(() => {
|
|
3873
|
-
setLandscapePositions({});
|
|
3874
|
-
setPortraitPositions({});
|
|
3875
|
-
try {
|
|
3876
|
-
localStorage.removeItem(STORAGE_KEY);
|
|
3877
|
-
} catch (e) {
|
|
3878
|
-
console.error("Failed to reset button positions:", e);
|
|
3879
|
-
}
|
|
3880
|
-
}, []);
|
|
3881
|
-
return {
|
|
3882
|
-
landscapePositions,
|
|
3883
|
-
portraitPositions,
|
|
3884
|
-
savePosition,
|
|
3885
|
-
getPosition,
|
|
3886
|
-
resetPositions
|
|
3887
|
-
};
|
|
3888
|
-
}
|
|
3889
3872
|
var STORAGE_KEY2 = "koin-controls-hint-shown";
|
|
3890
3873
|
function ControlsHint({ isVisible }) {
|
|
3891
3874
|
const [show, setShow] = React2.useState(false);
|
|
@@ -3970,7 +3953,7 @@ function LockButton({
|
|
|
3970
3953
|
"button",
|
|
3971
3954
|
{
|
|
3972
3955
|
onClick: onToggle,
|
|
3973
|
-
className: "
|
|
3956
|
+
className: "pointer-events-auto p-2 rounded-full backdrop-blur-sm transition-all active:scale-95",
|
|
3974
3957
|
style: {
|
|
3975
3958
|
backgroundColor: isLocked ? "rgba(0,0,0,0.6)" : `${systemColor}20`,
|
|
3976
3959
|
border: `1px solid ${isLocked ? "rgba(255,255,255,0.2)" : systemColor}`
|
|
@@ -3984,18 +3967,164 @@ function LockButton({
|
|
|
3984
3967
|
}
|
|
3985
3968
|
)
|
|
3986
3969
|
}
|
|
3987
|
-
);
|
|
3970
|
+
);
|
|
3971
|
+
}
|
|
3972
|
+
function HoldButton({
|
|
3973
|
+
isActive,
|
|
3974
|
+
onToggle,
|
|
3975
|
+
systemColor = "#00FF41"
|
|
3976
|
+
}) {
|
|
3977
|
+
const Icon = isActive ? lucideReact.StopCircle : lucideReact.Hand;
|
|
3978
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3979
|
+
"button",
|
|
3980
|
+
{
|
|
3981
|
+
onClick: onToggle,
|
|
3982
|
+
className: "pointer-events-auto p-2 rounded-full backdrop-blur-sm transition-all active:scale-95",
|
|
3983
|
+
style: {
|
|
3984
|
+
backgroundColor: isActive ? "rgba(0,0,0,0.6)" : `${systemColor}20`,
|
|
3985
|
+
border: `1px solid ${isActive ? "rgba(255,255,255,0.4)" : systemColor}`
|
|
3986
|
+
},
|
|
3987
|
+
"aria-label": isActive ? "Disable Hold Mode" : "Enable Button Hold Mode",
|
|
3988
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3989
|
+
Icon,
|
|
3990
|
+
{
|
|
3991
|
+
size: 18,
|
|
3992
|
+
style: { color: isActive ? "#fff" : systemColor }
|
|
3993
|
+
}
|
|
3994
|
+
)
|
|
3995
|
+
}
|
|
3996
|
+
);
|
|
3997
|
+
}
|
|
3998
|
+
function TurboButton({
|
|
3999
|
+
isActive,
|
|
4000
|
+
onToggle,
|
|
4001
|
+
systemColor = "#00FF41"
|
|
4002
|
+
}) {
|
|
4003
|
+
const Icon = isActive ? lucideReact.ZapOff : lucideReact.Zap;
|
|
4004
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4005
|
+
"button",
|
|
4006
|
+
{
|
|
4007
|
+
onClick: onToggle,
|
|
4008
|
+
className: "pointer-events-auto p-2 rounded-full backdrop-blur-sm transition-all active:scale-95",
|
|
4009
|
+
style: {
|
|
4010
|
+
backgroundColor: isActive ? "rgba(255,200,0,0.3)" : `${systemColor}20`,
|
|
4011
|
+
border: `1px solid ${isActive ? "rgba(255,200,0,0.6)" : systemColor}`
|
|
4012
|
+
},
|
|
4013
|
+
"aria-label": isActive ? "Disable Turbo Mode" : "Enable Turbo Fire Mode",
|
|
4014
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4015
|
+
Icon,
|
|
4016
|
+
{
|
|
4017
|
+
size: 18,
|
|
4018
|
+
style: { color: isActive ? "#FFC800" : systemColor }
|
|
4019
|
+
}
|
|
4020
|
+
)
|
|
4021
|
+
}
|
|
4022
|
+
);
|
|
4023
|
+
}
|
|
4024
|
+
var MODE_CONFIG = {
|
|
4025
|
+
hold: {
|
|
4026
|
+
Icon: lucideReact.Hand,
|
|
4027
|
+
title: "Hold Mode",
|
|
4028
|
+
instruction: "Tap a button to hold it",
|
|
4029
|
+
buttonIcon: lucideReact.Hand,
|
|
4030
|
+
buttonColor: "#22c55e"
|
|
4031
|
+
// green
|
|
4032
|
+
},
|
|
4033
|
+
turbo: {
|
|
4034
|
+
Icon: lucideReact.Zap,
|
|
4035
|
+
title: "Turbo Mode",
|
|
4036
|
+
instruction: "Tap a button for rapid fire",
|
|
4037
|
+
buttonIcon: lucideReact.Zap,
|
|
4038
|
+
buttonColor: "#fbbf24"
|
|
4039
|
+
// yellow
|
|
4040
|
+
}
|
|
4041
|
+
};
|
|
4042
|
+
function ModeOverlay({
|
|
4043
|
+
mode,
|
|
4044
|
+
heldButtons,
|
|
4045
|
+
turboButtons,
|
|
4046
|
+
systemColor = "#00FF41",
|
|
4047
|
+
onExit
|
|
4048
|
+
}) {
|
|
4049
|
+
if (!mode) return null;
|
|
4050
|
+
const config = MODE_CONFIG[mode];
|
|
4051
|
+
const buttons = mode === "hold" ? heldButtons : turboButtons;
|
|
4052
|
+
const buttonArray = Array.from(buttons);
|
|
4053
|
+
const { Icon, buttonIcon: ButtonIcon } = config;
|
|
4054
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed top-0 left-0 right-0 z-40 flex justify-center pt-4 pointer-events-none", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4055
|
+
"div",
|
|
4056
|
+
{
|
|
4057
|
+
className: "relative px-5 py-3 rounded-2xl backdrop-blur-md border pointer-events-auto",
|
|
4058
|
+
style: {
|
|
4059
|
+
backgroundColor: "rgba(0,0,0,0.85)",
|
|
4060
|
+
borderColor: `${systemColor}60`,
|
|
4061
|
+
boxShadow: `0 4px 20px ${systemColor}30`
|
|
4062
|
+
},
|
|
4063
|
+
children: [
|
|
4064
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4065
|
+
"button",
|
|
4066
|
+
{
|
|
4067
|
+
onClick: onExit,
|
|
4068
|
+
className: "absolute -top-2 -right-2 w-7 h-7 rounded-full flex items-center justify-center transition-transform active:scale-90",
|
|
4069
|
+
style: {
|
|
4070
|
+
backgroundColor: "#ef4444",
|
|
4071
|
+
border: "2px solid rgba(255,255,255,0.3)"
|
|
4072
|
+
},
|
|
4073
|
+
"aria-label": "Exit mode",
|
|
4074
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 14, color: "white", strokeWidth: 3 })
|
|
4075
|
+
}
|
|
4076
|
+
),
|
|
4077
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
4078
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4079
|
+
"div",
|
|
4080
|
+
{
|
|
4081
|
+
className: "w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0",
|
|
4082
|
+
style: { backgroundColor: `${systemColor}30` },
|
|
4083
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { size: 20, style: { color: systemColor } })
|
|
4084
|
+
}
|
|
4085
|
+
),
|
|
4086
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-left", children: [
|
|
4087
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-white font-bold text-sm", children: config.title }),
|
|
4088
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-white/60 text-xs", children: config.instruction })
|
|
4089
|
+
] })
|
|
4090
|
+
] }),
|
|
4091
|
+
buttonArray.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1.5 justify-center mt-2 pt-2 border-t border-white/10", children: buttonArray.map((button) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4092
|
+
"span",
|
|
4093
|
+
{
|
|
4094
|
+
className: "px-2 py-0.5 rounded-full text-[10px] font-bold uppercase flex items-center gap-1",
|
|
4095
|
+
style: {
|
|
4096
|
+
backgroundColor: `${config.buttonColor}25`,
|
|
4097
|
+
color: config.buttonColor
|
|
4098
|
+
},
|
|
4099
|
+
children: [
|
|
4100
|
+
/* @__PURE__ */ jsxRuntime.jsx(ButtonIcon, { size: 10 }),
|
|
4101
|
+
button
|
|
4102
|
+
]
|
|
4103
|
+
},
|
|
4104
|
+
button
|
|
4105
|
+
)) })
|
|
4106
|
+
]
|
|
4107
|
+
}
|
|
4108
|
+
) });
|
|
3988
4109
|
}
|
|
3989
4110
|
var LOCK_KEY = "koin-controls-locked";
|
|
3990
4111
|
function VirtualController({
|
|
3991
4112
|
system,
|
|
3992
4113
|
isRunning,
|
|
3993
4114
|
controls,
|
|
3994
|
-
systemColor = "#00FF41"
|
|
4115
|
+
systemColor = "#00FF41",
|
|
3995
4116
|
// Default retro green
|
|
4117
|
+
hapticsEnabled = true,
|
|
4118
|
+
onButtonDown,
|
|
4119
|
+
onButtonUp,
|
|
4120
|
+
onPause,
|
|
4121
|
+
onResume
|
|
3996
4122
|
}) {
|
|
3997
4123
|
const { isMobile, isLandscape, isPortrait } = useMobile();
|
|
3998
4124
|
const [pressedButtons, setPressedButtons] = React2.useState(/* @__PURE__ */ new Set());
|
|
4125
|
+
const pressedButtonsRef = React2.useRef(/* @__PURE__ */ new Set());
|
|
4126
|
+
const [heldButtons, setHeldButtons] = React2.useState(/* @__PURE__ */ new Set());
|
|
4127
|
+
const [isHoldMode, setIsHoldMode] = React2.useState(false);
|
|
3999
4128
|
const [containerSize, setContainerSize] = React2.useState({ width: 0, height: 0 });
|
|
4000
4129
|
const [isFullscreenState, setIsFullscreenState] = React2.useState(false);
|
|
4001
4130
|
const [isLocked, setIsLocked] = React2.useState(true);
|
|
@@ -4013,6 +4142,28 @@ function VirtualController({
|
|
|
4013
4142
|
return newValue;
|
|
4014
4143
|
});
|
|
4015
4144
|
}, []);
|
|
4145
|
+
const toggleHoldMode = React2.useCallback(() => {
|
|
4146
|
+
setIsHoldMode((prev) => {
|
|
4147
|
+
if (prev) {
|
|
4148
|
+
onResume();
|
|
4149
|
+
} else {
|
|
4150
|
+
onPause();
|
|
4151
|
+
}
|
|
4152
|
+
return !prev;
|
|
4153
|
+
});
|
|
4154
|
+
}, [onPause, onResume]);
|
|
4155
|
+
const [isTurboMode, setIsTurboMode] = React2.useState(false);
|
|
4156
|
+
const [turboButtons, setTurboButtons] = React2.useState(/* @__PURE__ */ new Set());
|
|
4157
|
+
const toggleTurboMode = React2.useCallback(() => {
|
|
4158
|
+
setIsTurboMode((prev) => {
|
|
4159
|
+
if (prev) {
|
|
4160
|
+
onResume();
|
|
4161
|
+
} else {
|
|
4162
|
+
onPause();
|
|
4163
|
+
}
|
|
4164
|
+
return !prev;
|
|
4165
|
+
});
|
|
4166
|
+
}, [onPause, onResume]);
|
|
4016
4167
|
const layout = getLayoutForSystem(system);
|
|
4017
4168
|
const visibleButtons = layout.buttons.filter((btn) => {
|
|
4018
4169
|
if (isPortrait) {
|
|
@@ -4082,9 +4233,9 @@ function VirtualController({
|
|
|
4082
4233
|
return;
|
|
4083
4234
|
}
|
|
4084
4235
|
setPressedButtons((prev) => new Set(prev).add(buttonType));
|
|
4085
|
-
|
|
4236
|
+
onButtonDown(buttonType);
|
|
4086
4237
|
setTimeout(() => {
|
|
4087
|
-
|
|
4238
|
+
onButtonUp(buttonType);
|
|
4088
4239
|
setPressedButtons((prev) => {
|
|
4089
4240
|
const next = new Set(prev);
|
|
4090
4241
|
next.delete(buttonType);
|
|
@@ -4092,7 +4243,7 @@ function VirtualController({
|
|
|
4092
4243
|
});
|
|
4093
4244
|
}, 100);
|
|
4094
4245
|
},
|
|
4095
|
-
[isRunning, getButtonKeyboardCode]
|
|
4246
|
+
[isRunning, getButtonKeyboardCode, onButtonDown, onButtonUp]
|
|
4096
4247
|
);
|
|
4097
4248
|
const handlePressDown = React2.useCallback(
|
|
4098
4249
|
(buttonType) => {
|
|
@@ -4101,15 +4252,70 @@ function VirtualController({
|
|
|
4101
4252
|
if (isSystemButton) return;
|
|
4102
4253
|
const keyboardCode = getButtonKeyboardCode(buttonType);
|
|
4103
4254
|
if (!keyboardCode) return;
|
|
4255
|
+
if (isHoldMode) {
|
|
4256
|
+
const isHeld = heldButtons.has(buttonType);
|
|
4257
|
+
if (isHeld) {
|
|
4258
|
+
setHeldButtons((prev) => {
|
|
4259
|
+
const next = new Set(prev);
|
|
4260
|
+
next.delete(buttonType);
|
|
4261
|
+
return next;
|
|
4262
|
+
});
|
|
4263
|
+
onButtonUp(buttonType);
|
|
4264
|
+
if (hapticsEnabled && navigator.vibrate) navigator.vibrate(10);
|
|
4265
|
+
} else {
|
|
4266
|
+
setHeldButtons((prev) => {
|
|
4267
|
+
setTurboButtons((turboPrev) => {
|
|
4268
|
+
if (turboPrev.has(buttonType)) {
|
|
4269
|
+
const nextTurbo = new Set(turboPrev);
|
|
4270
|
+
nextTurbo.delete(buttonType);
|
|
4271
|
+
return nextTurbo;
|
|
4272
|
+
}
|
|
4273
|
+
return turboPrev;
|
|
4274
|
+
});
|
|
4275
|
+
return new Set(prev).add(buttonType);
|
|
4276
|
+
});
|
|
4277
|
+
onButtonDown(buttonType);
|
|
4278
|
+
if (hapticsEnabled && navigator.vibrate) navigator.vibrate([10, 30, 10]);
|
|
4279
|
+
}
|
|
4280
|
+
return;
|
|
4281
|
+
}
|
|
4282
|
+
if (isTurboMode) {
|
|
4283
|
+
const isTurbo = turboButtons.has(buttonType);
|
|
4284
|
+
if (isTurbo) {
|
|
4285
|
+
setTurboButtons((prev) => {
|
|
4286
|
+
const next = new Set(prev);
|
|
4287
|
+
next.delete(buttonType);
|
|
4288
|
+
return next;
|
|
4289
|
+
});
|
|
4290
|
+
if (hapticsEnabled && navigator.vibrate) navigator.vibrate(10);
|
|
4291
|
+
} else {
|
|
4292
|
+
setTurboButtons((prev) => {
|
|
4293
|
+
setHeldButtons((holdPrev) => {
|
|
4294
|
+
if (holdPrev.has(buttonType)) {
|
|
4295
|
+
const nextHold = new Set(holdPrev);
|
|
4296
|
+
nextHold.delete(buttonType);
|
|
4297
|
+
onButtonUp(buttonType);
|
|
4298
|
+
return nextHold;
|
|
4299
|
+
}
|
|
4300
|
+
return holdPrev;
|
|
4301
|
+
});
|
|
4302
|
+
return new Set(prev).add(buttonType);
|
|
4303
|
+
});
|
|
4304
|
+
if (hapticsEnabled && navigator.vibrate) navigator.vibrate([5, 10, 5]);
|
|
4305
|
+
}
|
|
4306
|
+
return;
|
|
4307
|
+
}
|
|
4104
4308
|
setPressedButtons((prev) => {
|
|
4105
4309
|
if (prev.has(buttonType)) return prev;
|
|
4106
4310
|
const next = new Set(prev);
|
|
4107
4311
|
next.add(buttonType);
|
|
4108
4312
|
return next;
|
|
4109
4313
|
});
|
|
4110
|
-
|
|
4314
|
+
if (!heldButtons.has(buttonType)) {
|
|
4315
|
+
onButtonDown(buttonType);
|
|
4316
|
+
}
|
|
4111
4317
|
},
|
|
4112
|
-
[isRunning, getButtonKeyboardCode]
|
|
4318
|
+
[isRunning, getButtonKeyboardCode, isHoldMode, isTurboMode, heldButtons, hapticsEnabled, onButtonDown, onButtonUp]
|
|
4113
4319
|
);
|
|
4114
4320
|
const handleRelease = React2.useCallback(
|
|
4115
4321
|
(buttonType) => {
|
|
@@ -4117,15 +4323,29 @@ function VirtualController({
|
|
|
4117
4323
|
if (isSystemButton) return;
|
|
4118
4324
|
const keyboardCode = getButtonKeyboardCode(buttonType);
|
|
4119
4325
|
if (!keyboardCode) return;
|
|
4326
|
+
if (isHoldMode) return;
|
|
4327
|
+
if (heldButtons.has(buttonType)) {
|
|
4328
|
+
if (!isHoldMode) {
|
|
4329
|
+
setHeldButtons((prev) => {
|
|
4330
|
+
const next = new Set(prev);
|
|
4331
|
+
next.delete(buttonType);
|
|
4332
|
+
return next;
|
|
4333
|
+
});
|
|
4334
|
+
onButtonUp(buttonType);
|
|
4335
|
+
if (hapticsEnabled && navigator.vibrate) navigator.vibrate(10);
|
|
4336
|
+
}
|
|
4337
|
+
return;
|
|
4338
|
+
}
|
|
4339
|
+
if (isTurboMode) return;
|
|
4120
4340
|
setPressedButtons((prev) => {
|
|
4121
4341
|
if (!prev.has(buttonType)) return prev;
|
|
4122
4342
|
const next = new Set(prev);
|
|
4123
4343
|
next.delete(buttonType);
|
|
4124
4344
|
return next;
|
|
4125
4345
|
});
|
|
4126
|
-
|
|
4346
|
+
onButtonUp(buttonType);
|
|
4127
4347
|
},
|
|
4128
|
-
[getButtonKeyboardCode]
|
|
4348
|
+
[getButtonKeyboardCode, isHoldMode, isTurboMode, heldButtons, hapticsEnabled, onButtonUp]
|
|
4129
4349
|
);
|
|
4130
4350
|
React2.useEffect(() => {
|
|
4131
4351
|
if (!isRunning && pressedButtons.size > 0) {
|
|
@@ -4137,6 +4357,22 @@ function VirtualController({
|
|
|
4137
4357
|
setPressedButtons(/* @__PURE__ */ new Set());
|
|
4138
4358
|
}
|
|
4139
4359
|
}, [isRunning, pressedButtons, handleRelease]);
|
|
4360
|
+
const TURBO_RATE = 15;
|
|
4361
|
+
React2.useEffect(() => {
|
|
4362
|
+
pressedButtonsRef.current = pressedButtons;
|
|
4363
|
+
}, [pressedButtons]);
|
|
4364
|
+
React2.useEffect(() => {
|
|
4365
|
+
if (isTurboMode || turboButtons.size === 0) return;
|
|
4366
|
+
const interval = setInterval(() => {
|
|
4367
|
+
turboButtons.forEach((buttonType) => {
|
|
4368
|
+
if (pressedButtonsRef.current.has(buttonType)) {
|
|
4369
|
+
onButtonDown(buttonType);
|
|
4370
|
+
setTimeout(() => onButtonUp(buttonType), 25);
|
|
4371
|
+
}
|
|
4372
|
+
});
|
|
4373
|
+
}, 1e3 / TURBO_RATE);
|
|
4374
|
+
return () => clearInterval(interval);
|
|
4375
|
+
}, [isTurboMode, turboButtons, onButtonDown, onButtonUp]);
|
|
4140
4376
|
const memoizedButtonElements = React2.useMemo(() => {
|
|
4141
4377
|
const width = containerSize.width || (typeof window !== "undefined" ? window.innerWidth : 0);
|
|
4142
4378
|
const height = containerSize.height || (typeof window !== "undefined" ? window.innerHeight : 0);
|
|
@@ -4167,14 +4403,32 @@ function VirtualController({
|
|
|
4167
4403
|
className: "fixed inset-0 z-30 pointer-events-none",
|
|
4168
4404
|
style: { touchAction: "none" },
|
|
4169
4405
|
children: [
|
|
4170
|
-
/* @__PURE__ */ jsxRuntime.
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4406
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fixed top-4 left-1/2 -translate-x-1/2 z-40 flex gap-4", children: [
|
|
4407
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4408
|
+
LockButton,
|
|
4409
|
+
{
|
|
4410
|
+
isLocked,
|
|
4411
|
+
onToggle: toggleLock,
|
|
4412
|
+
systemColor
|
|
4413
|
+
}
|
|
4414
|
+
),
|
|
4415
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4416
|
+
HoldButton,
|
|
4417
|
+
{
|
|
4418
|
+
isActive: isHoldMode,
|
|
4419
|
+
onToggle: toggleHoldMode,
|
|
4420
|
+
systemColor
|
|
4421
|
+
}
|
|
4422
|
+
),
|
|
4423
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4424
|
+
TurboButton,
|
|
4425
|
+
{
|
|
4426
|
+
isActive: isTurboMode,
|
|
4427
|
+
onToggle: toggleTurboMode,
|
|
4428
|
+
systemColor
|
|
4429
|
+
}
|
|
4430
|
+
)
|
|
4431
|
+
] }),
|
|
4178
4432
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4179
4433
|
Dpad_default,
|
|
4180
4434
|
{
|
|
@@ -4183,18 +4437,20 @@ function VirtualController({
|
|
|
4183
4437
|
y: finalDpadY,
|
|
4184
4438
|
containerWidth: containerSize.width || window.innerWidth,
|
|
4185
4439
|
containerHeight: containerSize.height || window.innerHeight,
|
|
4186
|
-
controls,
|
|
4187
4440
|
systemColor,
|
|
4188
4441
|
isLandscape,
|
|
4189
4442
|
customPosition: getPosition("up", isLandscape),
|
|
4190
|
-
onPositionChange: isLocked ? void 0 : (x, y) => savePosition("up", x, y, isLandscape)
|
|
4443
|
+
onPositionChange: isLocked ? void 0 : (x, y) => savePosition("up", x, y, isLandscape),
|
|
4444
|
+
hapticsEnabled,
|
|
4445
|
+
onButtonDown,
|
|
4446
|
+
onButtonUp
|
|
4191
4447
|
}
|
|
4192
4448
|
),
|
|
4193
4449
|
memoizedButtonElements.filter(({ buttonConfig }) => !DPAD_TYPES.includes(buttonConfig.type)).map(({ buttonConfig, adjustedConfig, customPosition, width, height }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
4194
4450
|
VirtualButton_default,
|
|
4195
4451
|
{
|
|
4196
4452
|
config: adjustedConfig,
|
|
4197
|
-
isPressed: pressedButtons.has(buttonConfig.type),
|
|
4453
|
+
isPressed: pressedButtons.has(buttonConfig.type) || heldButtons.has(buttonConfig.type),
|
|
4198
4454
|
onPress: handlePress,
|
|
4199
4455
|
onPressDown: handlePressDown,
|
|
4200
4456
|
onRelease: handleRelease,
|
|
@@ -4203,10 +4459,24 @@ function VirtualController({
|
|
|
4203
4459
|
customPosition,
|
|
4204
4460
|
onPositionChange: isLocked ? void 0 : (x, y) => savePosition(buttonConfig.type, x, y, isLandscape),
|
|
4205
4461
|
isLandscape,
|
|
4206
|
-
console: layout.console
|
|
4462
|
+
console: layout.console,
|
|
4463
|
+
hapticsEnabled,
|
|
4464
|
+
mode: isHoldMode ? "hold" : isTurboMode ? "turbo" : "normal",
|
|
4465
|
+
isHeld: heldButtons.has(buttonConfig.type),
|
|
4466
|
+
isInTurbo: turboButtons.has(buttonConfig.type)
|
|
4207
4467
|
},
|
|
4208
4468
|
buttonConfig.type
|
|
4209
4469
|
)),
|
|
4470
|
+
(isHoldMode || isTurboMode) && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4471
|
+
ModeOverlay,
|
|
4472
|
+
{
|
|
4473
|
+
mode: isHoldMode ? "hold" : "turbo",
|
|
4474
|
+
heldButtons,
|
|
4475
|
+
turboButtons,
|
|
4476
|
+
systemColor,
|
|
4477
|
+
onExit: isHoldMode ? toggleHoldMode : toggleTurboMode
|
|
4478
|
+
}
|
|
4479
|
+
),
|
|
4210
4480
|
/* @__PURE__ */ jsxRuntime.jsx(ControlsHint, { isVisible: isRunning })
|
|
4211
4481
|
]
|
|
4212
4482
|
}
|
|
@@ -4893,7 +5163,8 @@ function CheatModal({
|
|
|
4893
5163
|
cheats,
|
|
4894
5164
|
activeCheats,
|
|
4895
5165
|
onToggle,
|
|
4896
|
-
onClose
|
|
5166
|
+
onClose,
|
|
5167
|
+
onAddManualCheat
|
|
4897
5168
|
}) {
|
|
4898
5169
|
const t = useKoinTranslation();
|
|
4899
5170
|
const [copiedId, setCopiedId] = React2__default.default.useState(null);
|
|
@@ -4911,54 +5182,111 @@ function CheatModal({
|
|
|
4911
5182
|
subtitle: t.modals.cheats.available.replace("{{count}}", cheats.length.toString()),
|
|
4912
5183
|
icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Code, { size: 24, className: "text-purple-400" }),
|
|
4913
5184
|
footer: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 text-center w-full", children: activeCheats.size > 0 ? t.modals.cheats.active.replace("{{count}}", activeCheats.size.toString()) : t.modals.cheats.toggleHint }),
|
|
4914
|
-
children: /* @__PURE__ */ jsxRuntime.
|
|
4915
|
-
/* @__PURE__ */ jsxRuntime.
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
flex-shrink-0 w-6 h-6 rounded border-2 flex items-center justify-center transition-all
|
|
4934
|
-
${isActive ? "border-purple-500 bg-purple-500" : "border-gray-600 group-hover:border-gray-400"}
|
|
4935
|
-
`,
|
|
4936
|
-
children: isActive && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { size: 14, className: "text-white" })
|
|
5185
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 space-y-4 max-h-[400px] overflow-y-auto", children: [
|
|
5186
|
+
onAddManualCheat && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-3 bg-white/5 rounded-lg border border-white/10 space-y-3", children: [
|
|
5187
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-2", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
5188
|
+
"input",
|
|
5189
|
+
{
|
|
5190
|
+
type: "text",
|
|
5191
|
+
placeholder: t.modals.cheats.codePlaceholder || "Enter cheat code",
|
|
5192
|
+
className: "flex-1 bg-black/50 border border-white/20 rounded px-3 py-2 text-sm text-white placeholder-gray-500 focus:outline-none focus:border-purple-500",
|
|
5193
|
+
onKeyDown: (e) => {
|
|
5194
|
+
if (e.key === "Enter") {
|
|
5195
|
+
const input = e.currentTarget;
|
|
5196
|
+
const code = input.value.trim();
|
|
5197
|
+
if (code) {
|
|
5198
|
+
const descInput = input.parentElement?.nextElementSibling?.querySelector("input");
|
|
5199
|
+
const desc = descInput?.value.trim() || "Custom Cheat";
|
|
5200
|
+
onAddManualCheat(code, desc);
|
|
5201
|
+
input.value = "";
|
|
5202
|
+
if (descInput) descInput.value = "";
|
|
5203
|
+
}
|
|
4937
5204
|
}
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
5205
|
+
}
|
|
5206
|
+
}
|
|
5207
|
+
) }),
|
|
5208
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
5209
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5210
|
+
"input",
|
|
5211
|
+
{
|
|
5212
|
+
type: "text",
|
|
5213
|
+
placeholder: t.modals.cheats.descPlaceholder || "Description (optional)",
|
|
5214
|
+
className: "flex-1 bg-black/50 border border-white/20 rounded px-3 py-2 text-sm text-white placeholder-gray-500 focus:outline-none focus:border-purple-500"
|
|
5215
|
+
}
|
|
5216
|
+
),
|
|
5217
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5218
|
+
"button",
|
|
5219
|
+
{
|
|
5220
|
+
onClick: (e) => {
|
|
5221
|
+
const descInput = e.currentTarget.previousElementSibling;
|
|
5222
|
+
const codeInput = e.currentTarget.parentElement?.previousElementSibling?.querySelector("input");
|
|
5223
|
+
const code = codeInput?.value.trim();
|
|
5224
|
+
const desc = descInput?.value.trim() || "Custom Cheat";
|
|
5225
|
+
if (code) {
|
|
5226
|
+
onAddManualCheat(code, desc);
|
|
5227
|
+
codeInput.value = "";
|
|
5228
|
+
descInput.value = "";
|
|
5229
|
+
}
|
|
5230
|
+
},
|
|
5231
|
+
className: "px-3 py-2 bg-purple-600 hover:bg-purple-500 text-white text-sm rounded transition-colors",
|
|
5232
|
+
children: t.modals.cheats.add || "Add"
|
|
5233
|
+
}
|
|
5234
|
+
)
|
|
5235
|
+
] })
|
|
5236
|
+
] }),
|
|
5237
|
+
cheats.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-12 text-gray-500", children: [
|
|
5238
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Code, { size: 48, className: "mx-auto mb-3 opacity-50" }),
|
|
5239
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium", children: t.modals.cheats.emptyTitle }),
|
|
5240
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm mt-1", children: t.modals.cheats.emptyDesc })
|
|
5241
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: cheats.map((cheat) => {
|
|
5242
|
+
const isActive = activeCheats.has(cheat.id);
|
|
5243
|
+
const isManual = cheat.source === "manual";
|
|
5244
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5245
|
+
"div",
|
|
5246
|
+
{
|
|
5247
|
+
className: `
|
|
5248
|
+
group flex items-start gap-4 p-4 rounded-lg border transition-all cursor-pointer
|
|
5249
|
+
${isActive ? "border-purple-500/50 bg-purple-500/10" : "border-white/10 bg-white/5 hover:border-white/20 hover:bg-white/10"}
|
|
5250
|
+
`,
|
|
5251
|
+
onClick: () => onToggle(cheat.id),
|
|
5252
|
+
children: [
|
|
5253
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5254
|
+
"div",
|
|
5255
|
+
{
|
|
5256
|
+
className: `
|
|
5257
|
+
flex-shrink-0 w-6 h-6 rounded border-2 flex items-center justify-center transition-all
|
|
5258
|
+
${isActive ? "border-purple-500 bg-purple-500" : "border-gray-600 group-hover:border-gray-400"}
|
|
5259
|
+
`,
|
|
5260
|
+
children: isActive && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { size: 14, className: "text-white" })
|
|
5261
|
+
}
|
|
5262
|
+
),
|
|
5263
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
5264
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
5265
|
+
isManual && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs bg-purple-500/20 text-purple-300 px-1.5 py-0.5 rounded border border-purple-500/30", children: "USER" }),
|
|
5266
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: `font-medium ${isActive ? "text-purple-300" : "text-white"}`, children: cheat.description })
|
|
5267
|
+
] }),
|
|
5268
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mt-2", children: [
|
|
5269
|
+
/* @__PURE__ */ jsxRuntime.jsx("code", { className: "px-2 py-1 bg-black/50 rounded text-xs font-mono text-gray-400 truncate max-w-[200px]", children: cheat.code }),
|
|
5270
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5271
|
+
"button",
|
|
5272
|
+
{
|
|
5273
|
+
onClick: (e) => {
|
|
5274
|
+
e.stopPropagation();
|
|
5275
|
+
handleCopy(cheat.code, cheat.id);
|
|
5276
|
+
},
|
|
5277
|
+
className: "p-1.5 rounded hover:bg-white/10 text-gray-500 hover:text-white transition-colors",
|
|
5278
|
+
title: t.modals.cheats.copy,
|
|
5279
|
+
children: copiedId === cheat.id ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { size: 14, className: "text-green-400" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Copy, { size: 14 })
|
|
5280
|
+
}
|
|
5281
|
+
)
|
|
5282
|
+
] })
|
|
4955
5283
|
] })
|
|
4956
|
-
]
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
)
|
|
4961
|
-
|
|
5284
|
+
]
|
|
5285
|
+
},
|
|
5286
|
+
cheat.id
|
|
5287
|
+
);
|
|
5288
|
+
}) })
|
|
5289
|
+
] })
|
|
4962
5290
|
}
|
|
4963
5291
|
);
|
|
4964
5292
|
}
|
|
@@ -5290,7 +5618,9 @@ function SettingsModal({
|
|
|
5290
5618
|
onClose,
|
|
5291
5619
|
currentLanguage,
|
|
5292
5620
|
onLanguageChange,
|
|
5293
|
-
systemColor = "#00FF41"
|
|
5621
|
+
systemColor = "#00FF41",
|
|
5622
|
+
hapticsEnabled,
|
|
5623
|
+
onToggleHaptics
|
|
5294
5624
|
}) {
|
|
5295
5625
|
const t = useKoinTranslation();
|
|
5296
5626
|
const languages = [
|
|
@@ -5314,30 +5644,57 @@ function SettingsModal({
|
|
|
5314
5644
|
children: t.modals.shortcuts.pressEsc
|
|
5315
5645
|
}
|
|
5316
5646
|
),
|
|
5317
|
-
children: /* @__PURE__ */ jsxRuntime.
|
|
5318
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "
|
|
5319
|
-
/* @__PURE__ */ jsxRuntime.
|
|
5320
|
-
|
|
5647
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-6 space-y-6", children: [
|
|
5648
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
|
|
5649
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm font-medium text-gray-400", children: [
|
|
5650
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Globe, { size: 16 }),
|
|
5651
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: t.settings.language })
|
|
5652
|
+
] }),
|
|
5653
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid gap-2", children: languages.map((lang) => {
|
|
5654
|
+
const isActive = currentLanguage === lang.code;
|
|
5655
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5656
|
+
"button",
|
|
5657
|
+
{
|
|
5658
|
+
onClick: () => onLanguageChange(lang.code),
|
|
5659
|
+
className: `
|
|
5660
|
+
flex items-center justify-between px-4 py-3 rounded-lg border transition-all
|
|
5661
|
+
${isActive ? "bg-white/10 border-white/20 text-white" : "bg-black/20 border-transparent text-gray-400 hover:bg-white/5 hover:text-white"}
|
|
5662
|
+
`,
|
|
5663
|
+
children: [
|
|
5664
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: lang.name }),
|
|
5665
|
+
isActive && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { size: 16, style: { color: systemColor } })
|
|
5666
|
+
]
|
|
5667
|
+
},
|
|
5668
|
+
lang.code
|
|
5669
|
+
);
|
|
5670
|
+
}) })
|
|
5321
5671
|
] }),
|
|
5322
|
-
/* @__PURE__ */ jsxRuntime.
|
|
5323
|
-
|
|
5324
|
-
|
|
5672
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
|
|
5673
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm font-medium text-gray-400", children: [
|
|
5674
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Zap, { size: 16 }),
|
|
5675
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: t.settings.haptics })
|
|
5676
|
+
] }),
|
|
5677
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
5325
5678
|
"button",
|
|
5326
5679
|
{
|
|
5327
|
-
onClick:
|
|
5680
|
+
onClick: onToggleHaptics,
|
|
5328
5681
|
className: `
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5682
|
+
w-full flex items-center justify-between px-4 py-3 rounded-lg border transition-all
|
|
5683
|
+
${hapticsEnabled ? "bg-white/10 border-white/20 text-white" : "bg-black/20 border-transparent text-gray-400 hover:bg-white/5 hover:text-white"}
|
|
5684
|
+
`,
|
|
5332
5685
|
children: [
|
|
5333
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children:
|
|
5334
|
-
|
|
5686
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: t.settings.enableHaptics }),
|
|
5687
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: `w-10 h-6 rounded-full p-1 transition-colors ${hapticsEnabled ? "bg-[#00FF41]" : "bg-gray-700"}`, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
5688
|
+
"div",
|
|
5689
|
+
{
|
|
5690
|
+
className: `w-4 h-4 rounded-full bg-white transition-transform ${hapticsEnabled ? "translate-x-4" : "translate-x-0"}`
|
|
5691
|
+
}
|
|
5692
|
+
) })
|
|
5335
5693
|
]
|
|
5336
|
-
}
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
] }) })
|
|
5694
|
+
}
|
|
5695
|
+
)
|
|
5696
|
+
] })
|
|
5697
|
+
] })
|
|
5341
5698
|
}
|
|
5342
5699
|
);
|
|
5343
5700
|
}
|
|
@@ -5357,6 +5714,7 @@ function GameModals({
|
|
|
5357
5714
|
cheats,
|
|
5358
5715
|
activeCheats,
|
|
5359
5716
|
onToggleCheat,
|
|
5717
|
+
onAddManualCheat,
|
|
5360
5718
|
saveModalOpen,
|
|
5361
5719
|
setSaveModalOpen,
|
|
5362
5720
|
saveModalMode,
|
|
@@ -5376,7 +5734,9 @@ function GameModals({
|
|
|
5376
5734
|
settingsModalOpen,
|
|
5377
5735
|
setSettingsModalOpen,
|
|
5378
5736
|
currentLanguage,
|
|
5379
|
-
onLanguageChange
|
|
5737
|
+
onLanguageChange,
|
|
5738
|
+
hapticsEnabled,
|
|
5739
|
+
onToggleHaptics
|
|
5380
5740
|
}) {
|
|
5381
5741
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
5382
5742
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -5411,6 +5771,7 @@ function GameModals({
|
|
|
5411
5771
|
cheats,
|
|
5412
5772
|
activeCheats,
|
|
5413
5773
|
onToggle: onToggleCheat,
|
|
5774
|
+
onAddManualCheat,
|
|
5414
5775
|
onClose: () => {
|
|
5415
5776
|
setCheatsModalOpen(false);
|
|
5416
5777
|
onResume();
|
|
@@ -5463,7 +5824,9 @@ function GameModals({
|
|
|
5463
5824
|
},
|
|
5464
5825
|
currentLanguage,
|
|
5465
5826
|
onLanguageChange,
|
|
5466
|
-
systemColor
|
|
5827
|
+
systemColor,
|
|
5828
|
+
hapticsEnabled,
|
|
5829
|
+
onToggleHaptics
|
|
5467
5830
|
}
|
|
5468
5831
|
)
|
|
5469
5832
|
] });
|
|
@@ -6758,6 +7121,11 @@ function useEmulatorCore({
|
|
|
6758
7121
|
input_volume_up: "add",
|
|
6759
7122
|
input_volume_down: "subtract",
|
|
6760
7123
|
input_audio_mute: "f9",
|
|
7124
|
+
// Cheat hotkeys
|
|
7125
|
+
quick_menu_show_cheats: true,
|
|
7126
|
+
input_cheat_index_plus: "y",
|
|
7127
|
+
input_cheat_index_minus: "t",
|
|
7128
|
+
input_cheat_toggle: "u",
|
|
6761
7129
|
...inputConfig,
|
|
6762
7130
|
...specificConfig,
|
|
6763
7131
|
// Apply system-specific optimizations
|
|
@@ -6851,18 +7219,15 @@ function useEmulatorCore({
|
|
|
6851
7219
|
const restart = React2.useCallback(async () => {
|
|
6852
7220
|
if (nostalgistRef.current) {
|
|
6853
7221
|
try {
|
|
6854
|
-
|
|
6855
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
6856
|
-
nostalgistRef.current.resume();
|
|
6857
|
-
setIsPaused(false);
|
|
6858
|
-
setStatus("running");
|
|
6859
|
-
} catch (err) {
|
|
6860
|
-
console.error("[Nostalgist] Restart error:", err);
|
|
7222
|
+
console.log("[Nostalgist] Full restart - stopping, re-preparing with fresh config, starting");
|
|
6861
7223
|
stop();
|
|
7224
|
+
await prepare();
|
|
6862
7225
|
await start();
|
|
7226
|
+
} catch (err) {
|
|
7227
|
+
console.error("[Nostalgist] Restart error:", err);
|
|
6863
7228
|
}
|
|
6864
7229
|
}
|
|
6865
|
-
}, [stop, start]);
|
|
7230
|
+
}, [stop, prepare, start]);
|
|
6866
7231
|
const pause = React2.useCallback(() => {
|
|
6867
7232
|
if (nostalgistRef.current && !isPaused && status === "running") {
|
|
6868
7233
|
try {
|
|
@@ -6955,9 +7320,6 @@ function useEmulatorCore({
|
|
|
6955
7320
|
console.error("[Nostalgist] Resize error:", err);
|
|
6956
7321
|
}
|
|
6957
7322
|
}, []);
|
|
6958
|
-
React2.useCallback(() => {
|
|
6959
|
-
return nostalgistRef.current;
|
|
6960
|
-
}, []);
|
|
6961
7323
|
return {
|
|
6962
7324
|
status,
|
|
6963
7325
|
setStatus,
|
|
@@ -7084,8 +7446,26 @@ function useEmulatorInput({ nostalgistRef }) {
|
|
|
7084
7446
|
console.error("[Nostalgist] Press key error:", err);
|
|
7085
7447
|
}
|
|
7086
7448
|
}, [nostalgistRef]);
|
|
7449
|
+
const pressDown = React2.useCallback((button) => {
|
|
7450
|
+
if (!nostalgistRef.current) return;
|
|
7451
|
+
try {
|
|
7452
|
+
nostalgistRef.current.pressDown(button);
|
|
7453
|
+
} catch (err) {
|
|
7454
|
+
console.error("[Nostalgist] Press down error:", err);
|
|
7455
|
+
}
|
|
7456
|
+
}, [nostalgistRef]);
|
|
7457
|
+
const pressUp = React2.useCallback((button) => {
|
|
7458
|
+
if (!nostalgistRef.current) return;
|
|
7459
|
+
try {
|
|
7460
|
+
nostalgistRef.current.pressUp(button);
|
|
7461
|
+
} catch (err) {
|
|
7462
|
+
console.error("[Nostalgist] Press up error:", err);
|
|
7463
|
+
}
|
|
7464
|
+
}, [nostalgistRef]);
|
|
7087
7465
|
return {
|
|
7088
|
-
pressKey
|
|
7466
|
+
pressKey,
|
|
7467
|
+
pressDown,
|
|
7468
|
+
pressUp
|
|
7089
7469
|
};
|
|
7090
7470
|
}
|
|
7091
7471
|
var MIN_SAVE_INTERVAL = 100;
|
|
@@ -7352,26 +7732,53 @@ function useEmulatorSaves({ nostalgistRef, isPaused, setIsPaused, setStatus, rew
|
|
|
7352
7732
|
stopRewindCapture
|
|
7353
7733
|
};
|
|
7354
7734
|
}
|
|
7355
|
-
function useEmulatorCheats({
|
|
7356
|
-
|
|
7735
|
+
function useEmulatorCheats({
|
|
7736
|
+
nostalgistRef
|
|
7737
|
+
}) {
|
|
7738
|
+
const allocatedPointersRef = React2.useRef([]);
|
|
7739
|
+
const injectCheats = React2.useCallback((cheats) => {
|
|
7357
7740
|
if (!nostalgistRef.current) return;
|
|
7358
|
-
|
|
7359
|
-
|
|
7360
|
-
|
|
7361
|
-
|
|
7741
|
+
const module = nostalgistRef.current.getEmscriptenModule();
|
|
7742
|
+
if (!module) return;
|
|
7743
|
+
if (module._free && allocatedPointersRef.current.length > 0) {
|
|
7744
|
+
allocatedPointersRef.current.forEach((ptr) => {
|
|
7745
|
+
try {
|
|
7746
|
+
module._free(ptr);
|
|
7747
|
+
} catch (e) {
|
|
7748
|
+
}
|
|
7749
|
+
});
|
|
7750
|
+
allocatedPointersRef.current = [];
|
|
7362
7751
|
}
|
|
7363
|
-
|
|
7364
|
-
|
|
7365
|
-
|
|
7366
|
-
|
|
7367
|
-
|
|
7368
|
-
}
|
|
7369
|
-
|
|
7752
|
+
if (module._cmd_cheat_realloc) {
|
|
7753
|
+
module._cmd_cheat_realloc(0);
|
|
7754
|
+
if (cheats.length > 0) {
|
|
7755
|
+
module._cmd_cheat_realloc(cheats.length);
|
|
7756
|
+
}
|
|
7757
|
+
}
|
|
7758
|
+
if (cheats.length > 0 && module.stringToNewUTF8 && module._cmd_cheat_set_code) {
|
|
7759
|
+
cheats.forEach((cheat, index) => {
|
|
7760
|
+
try {
|
|
7761
|
+
const ptr = module.stringToNewUTF8(cheat.code);
|
|
7762
|
+
allocatedPointersRef.current.push(ptr);
|
|
7763
|
+
module._cmd_cheat_set_code(index, ptr);
|
|
7764
|
+
if (module._cmd_cheat_toggle_index) {
|
|
7765
|
+
module._cmd_cheat_toggle_index(index, true);
|
|
7766
|
+
}
|
|
7767
|
+
} catch (err) {
|
|
7768
|
+
console.error("[Cheats] Failed to inject cheat:", index, err);
|
|
7769
|
+
}
|
|
7770
|
+
});
|
|
7771
|
+
}
|
|
7772
|
+
if (module._cmd_cheat_apply_cheats) {
|
|
7773
|
+
module._cmd_cheat_apply_cheats();
|
|
7370
7774
|
}
|
|
7371
7775
|
}, [nostalgistRef]);
|
|
7776
|
+
const clearCheats = React2.useCallback(() => {
|
|
7777
|
+
injectCheats([]);
|
|
7778
|
+
}, [injectCheats]);
|
|
7372
7779
|
return {
|
|
7373
|
-
|
|
7374
|
-
|
|
7780
|
+
injectCheats,
|
|
7781
|
+
clearCheats
|
|
7375
7782
|
};
|
|
7376
7783
|
}
|
|
7377
7784
|
|
|
@@ -7443,7 +7850,9 @@ var useNostalgist = ({
|
|
|
7443
7850
|
initialVolume
|
|
7444
7851
|
});
|
|
7445
7852
|
const {
|
|
7446
|
-
pressKey
|
|
7853
|
+
pressKey,
|
|
7854
|
+
pressDown,
|
|
7855
|
+
pressUp
|
|
7447
7856
|
} = useEmulatorInput({
|
|
7448
7857
|
nostalgistRef
|
|
7449
7858
|
});
|
|
@@ -7466,8 +7875,8 @@ var useNostalgist = ({
|
|
|
7466
7875
|
// Disable manual rewind loop for heavy systems
|
|
7467
7876
|
});
|
|
7468
7877
|
const {
|
|
7469
|
-
|
|
7470
|
-
|
|
7878
|
+
injectCheats,
|
|
7879
|
+
clearCheats
|
|
7471
7880
|
} = useEmulatorCheats({
|
|
7472
7881
|
nostalgistRef
|
|
7473
7882
|
});
|
|
@@ -7510,9 +7919,11 @@ var useNostalgist = ({
|
|
|
7510
7919
|
toggleMute,
|
|
7511
7920
|
screenshot,
|
|
7512
7921
|
pressKey,
|
|
7922
|
+
pressDown,
|
|
7923
|
+
pressUp,
|
|
7513
7924
|
resize,
|
|
7514
|
-
|
|
7515
|
-
|
|
7925
|
+
injectCheats,
|
|
7926
|
+
clearCheats,
|
|
7516
7927
|
getNostalgistInstance,
|
|
7517
7928
|
isPerformanceMode
|
|
7518
7929
|
}), [
|
|
@@ -7542,9 +7953,11 @@ var useNostalgist = ({
|
|
|
7542
7953
|
toggleMute,
|
|
7543
7954
|
screenshot,
|
|
7544
7955
|
pressKey,
|
|
7956
|
+
pressDown,
|
|
7957
|
+
pressUp,
|
|
7545
7958
|
resize,
|
|
7546
|
-
|
|
7547
|
-
|
|
7959
|
+
injectCheats,
|
|
7960
|
+
clearCheats,
|
|
7548
7961
|
getNostalgistInstance,
|
|
7549
7962
|
isPerformanceMode
|
|
7550
7963
|
]);
|
|
@@ -8275,39 +8688,95 @@ function useGameSaves({
|
|
|
8275
8688
|
handleAutoSaveToggle
|
|
8276
8689
|
};
|
|
8277
8690
|
}
|
|
8691
|
+
var generateManualId = () => `manual-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
8278
8692
|
function useGameCheats({
|
|
8279
8693
|
nostalgist,
|
|
8280
8694
|
cheats = [],
|
|
8281
|
-
onToggleCheat
|
|
8695
|
+
onToggleCheat,
|
|
8696
|
+
showToast,
|
|
8697
|
+
romId
|
|
8282
8698
|
}) {
|
|
8283
8699
|
const [cheatsModalOpen, setCheatsModalOpen] = React2.useState(false);
|
|
8284
8700
|
const [activeCheats, setActiveCheats] = React2.useState(/* @__PURE__ */ new Set());
|
|
8701
|
+
const [manualCheatsInternal, setManualCheatsInternal] = React2.useState([]);
|
|
8702
|
+
const [isLoaded, setIsLoaded] = React2.useState(false);
|
|
8703
|
+
const cheatStorageKey = romId ? `koin_cheats_${romId}` : null;
|
|
8704
|
+
React2.useEffect(() => {
|
|
8705
|
+
if (!cheatStorageKey) return;
|
|
8706
|
+
setIsLoaded(false);
|
|
8707
|
+
try {
|
|
8708
|
+
const stored = localStorage.getItem(cheatStorageKey);
|
|
8709
|
+
if (stored) {
|
|
8710
|
+
const parsed = JSON.parse(stored);
|
|
8711
|
+
if (Array.isArray(parsed)) {
|
|
8712
|
+
setManualCheatsInternal(parsed);
|
|
8713
|
+
}
|
|
8714
|
+
}
|
|
8715
|
+
} catch (e) {
|
|
8716
|
+
console.error("[Cheats] Failed to load cheats:", e);
|
|
8717
|
+
} finally {
|
|
8718
|
+
setIsLoaded(true);
|
|
8719
|
+
}
|
|
8720
|
+
}, [cheatStorageKey]);
|
|
8721
|
+
React2.useEffect(() => {
|
|
8722
|
+
if (!cheatStorageKey || !isLoaded) return;
|
|
8723
|
+
localStorage.setItem(cheatStorageKey, JSON.stringify(manualCheatsInternal));
|
|
8724
|
+
}, [manualCheatsInternal, cheatStorageKey, isLoaded]);
|
|
8725
|
+
const allCheats = React2.useMemo(() => {
|
|
8726
|
+
const normalizedExternal = cheats.map((c) => ({
|
|
8727
|
+
id: typeof c.id === "number" ? `db-${c.id}` : c.id,
|
|
8728
|
+
code: c.code,
|
|
8729
|
+
description: c.description,
|
|
8730
|
+
source: "database"
|
|
8731
|
+
}));
|
|
8732
|
+
return [...normalizedExternal, ...manualCheatsInternal];
|
|
8733
|
+
}, [cheats, manualCheatsInternal]);
|
|
8734
|
+
const handleAddManualCheat = (code, description) => {
|
|
8735
|
+
if (!nostalgist) return;
|
|
8736
|
+
const newCheat = {
|
|
8737
|
+
id: generateManualId(),
|
|
8738
|
+
code,
|
|
8739
|
+
description,
|
|
8740
|
+
source: "manual"
|
|
8741
|
+
};
|
|
8742
|
+
setManualCheatsInternal((prev) => [...prev, newCheat]);
|
|
8743
|
+
setActiveCheats((prev) => new Set(prev).add(newCheat.id));
|
|
8744
|
+
setCheatsModalOpen(false);
|
|
8745
|
+
const currentActiveCheats = [...activeCheats, newCheat.id];
|
|
8746
|
+
const cheatsToInject = allCheats.filter((c) => currentActiveCheats.includes(c.id) || c.id === newCheat.id).concat([newCheat]);
|
|
8747
|
+
nostalgist.injectCheats(cheatsToInject.map((c) => ({ code: c.code })));
|
|
8748
|
+
nostalgist.resume();
|
|
8749
|
+
showToast?.("Cheat added!", "success");
|
|
8750
|
+
};
|
|
8285
8751
|
const handleToggleCheat = (cheatId) => {
|
|
8286
8752
|
if (!nostalgist) return;
|
|
8287
8753
|
const newActiveCheats = new Set(activeCheats);
|
|
8288
8754
|
const isActive = newActiveCheats.has(cheatId);
|
|
8289
8755
|
if (isActive) {
|
|
8290
8756
|
newActiveCheats.delete(cheatId);
|
|
8757
|
+
showToast?.("Cheat Disabled");
|
|
8291
8758
|
} else {
|
|
8292
8759
|
newActiveCheats.add(cheatId);
|
|
8760
|
+
showToast?.("Cheat Enabled", "success");
|
|
8293
8761
|
}
|
|
8294
8762
|
setActiveCheats(newActiveCheats);
|
|
8295
|
-
onToggleCheat
|
|
8296
|
-
|
|
8297
|
-
|
|
8298
|
-
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
});
|
|
8304
|
-
}, 50);
|
|
8763
|
+
if (onToggleCheat) {
|
|
8764
|
+
const numericId = cheatId.startsWith("db-") ? parseInt(cheatId.slice(3), 10) : void 0;
|
|
8765
|
+
if (numericId !== void 0) {
|
|
8766
|
+
onToggleCheat(numericId, !isActive);
|
|
8767
|
+
}
|
|
8768
|
+
}
|
|
8769
|
+
const cheatsToInject = allCheats.filter((c) => newActiveCheats.has(c.id)).map((c) => ({ code: c.code }));
|
|
8770
|
+
nostalgist.injectCheats(cheatsToInject);
|
|
8305
8771
|
};
|
|
8306
8772
|
return {
|
|
8307
8773
|
cheatsModalOpen,
|
|
8308
8774
|
setCheatsModalOpen,
|
|
8309
8775
|
activeCheats,
|
|
8310
|
-
|
|
8776
|
+
allCheats,
|
|
8777
|
+
// Unified list - replaces separate cheats + manualCheats
|
|
8778
|
+
handleToggleCheat,
|
|
8779
|
+
handleAddManualCheat
|
|
8311
8780
|
};
|
|
8312
8781
|
}
|
|
8313
8782
|
function useGameRecording({
|
|
@@ -8484,10 +8953,13 @@ function useGamePlayer(props) {
|
|
|
8484
8953
|
cheatsModalOpen,
|
|
8485
8954
|
setCheatsModalOpen,
|
|
8486
8955
|
activeCheats,
|
|
8487
|
-
|
|
8956
|
+
allCheats,
|
|
8957
|
+
handleToggleCheat,
|
|
8958
|
+
handleAddManualCheat
|
|
8488
8959
|
} = useGameCheats({
|
|
8489
8960
|
...props,
|
|
8490
|
-
nostalgist
|
|
8961
|
+
nostalgist,
|
|
8962
|
+
showToast
|
|
8491
8963
|
});
|
|
8492
8964
|
const {
|
|
8493
8965
|
isRecording,
|
|
@@ -8543,7 +9015,9 @@ function useGamePlayer(props) {
|
|
|
8543
9015
|
hardcoreRestrictions,
|
|
8544
9016
|
// Cheats
|
|
8545
9017
|
activeCheats,
|
|
9018
|
+
allCheats,
|
|
8546
9019
|
handleToggleCheat,
|
|
9020
|
+
handleAddManualCheat,
|
|
8547
9021
|
// Emulator
|
|
8548
9022
|
nostalgist,
|
|
8549
9023
|
volumeState,
|
|
@@ -8571,7 +9045,8 @@ var DEFAULT_SETTINGS = {
|
|
|
8571
9045
|
muted: false,
|
|
8572
9046
|
shader: "",
|
|
8573
9047
|
showPerformanceOverlay: false,
|
|
8574
|
-
showInputDisplay: false
|
|
9048
|
+
showInputDisplay: false,
|
|
9049
|
+
hapticsEnabled: true
|
|
8575
9050
|
};
|
|
8576
9051
|
function usePlayerPersistence(onSettingsChange) {
|
|
8577
9052
|
const [settings, setSettings] = React2.useState(DEFAULT_SETTINGS);
|
|
@@ -8678,7 +9153,9 @@ var es = {
|
|
|
8678
9153
|
shortcuts: "Atajos",
|
|
8679
9154
|
exit: "Salir",
|
|
8680
9155
|
language: "Idioma",
|
|
8681
|
-
selectLanguage: "Seleccionar idioma"
|
|
9156
|
+
selectLanguage: "Seleccionar idioma",
|
|
9157
|
+
haptics: "Vibraci\xF3n",
|
|
9158
|
+
enableHaptics: "Habilitar vibraci\xF3n"
|
|
8682
9159
|
},
|
|
8683
9160
|
overlay: {
|
|
8684
9161
|
play: "JUGAR",
|
|
@@ -8761,7 +9238,10 @@ var es = {
|
|
|
8761
9238
|
emptyDesc: "No se encontraron c\xF3digos para este juego",
|
|
8762
9239
|
copy: "Copiar c\xF3digo",
|
|
8763
9240
|
active: "{{count}} truco{{s}} activo(s)",
|
|
8764
|
-
toggleHint: "Haz clic para activar/desactivar"
|
|
9241
|
+
toggleHint: "Haz clic para activar/desactivar",
|
|
9242
|
+
codePlaceholder: "Introduce c\xF3digo (ej: 00C-048-E6E)",
|
|
9243
|
+
descPlaceholder: "Descripci\xF3n (opcional)",
|
|
9244
|
+
add: "A\xF1adir truco"
|
|
8765
9245
|
},
|
|
8766
9246
|
saveSlots: {
|
|
8767
9247
|
title: "Guardar partida",
|
|
@@ -8906,7 +9386,9 @@ var fr = {
|
|
|
8906
9386
|
shortcuts: "Raccourcis",
|
|
8907
9387
|
exit: "Quitter",
|
|
8908
9388
|
language: "Langue",
|
|
8909
|
-
selectLanguage: "Choisir la langue"
|
|
9389
|
+
selectLanguage: "Choisir la langue",
|
|
9390
|
+
haptics: "Vibration",
|
|
9391
|
+
enableHaptics: "Activer la vibration"
|
|
8910
9392
|
},
|
|
8911
9393
|
overlay: {
|
|
8912
9394
|
play: "JOUER",
|
|
@@ -8989,7 +9471,10 @@ var fr = {
|
|
|
8989
9471
|
emptyDesc: "Aucun code trouv\xE9 pour ce jeu",
|
|
8990
9472
|
copy: "Copier",
|
|
8991
9473
|
active: "{{count}} actif(s)",
|
|
8992
|
-
toggleHint: "Cliquez pour activer/d\xE9sactiver"
|
|
9474
|
+
toggleHint: "Cliquez pour activer/d\xE9sactiver",
|
|
9475
|
+
codePlaceholder: "Entrez le code (ex: 00C-048-E6E)",
|
|
9476
|
+
descPlaceholder: "Description (optionnel)",
|
|
9477
|
+
add: "Ajouter"
|
|
8993
9478
|
},
|
|
8994
9479
|
saveSlots: {
|
|
8995
9480
|
title: "Sauvegardes",
|
|
@@ -9162,7 +9647,9 @@ var GamePlayerInner = React2.memo(function GamePlayerInner2(props) {
|
|
|
9162
9647
|
hardcoreRestrictions,
|
|
9163
9648
|
// Cheats
|
|
9164
9649
|
activeCheats,
|
|
9650
|
+
allCheats,
|
|
9165
9651
|
handleToggleCheat,
|
|
9652
|
+
handleAddManualCheat,
|
|
9166
9653
|
// Emulator Instance & State
|
|
9167
9654
|
nostalgist,
|
|
9168
9655
|
// Contains start, restart, etc.
|
|
@@ -9227,7 +9714,7 @@ var GamePlayerInner = React2.memo(function GamePlayerInner2(props) {
|
|
|
9227
9714
|
if (muted !== settings.muted) toggleMute();
|
|
9228
9715
|
}
|
|
9229
9716
|
}, [settingsLoaded]);
|
|
9230
|
-
const { system, systemColor = "#00FF41",
|
|
9717
|
+
const { system, systemColor = "#00FF41", onExit } = props;
|
|
9231
9718
|
const handlePauseToggle = React2.useCallback(() => {
|
|
9232
9719
|
status === "ready" ? start() : togglePause();
|
|
9233
9720
|
}, [status, start, togglePause]);
|
|
@@ -9283,6 +9770,9 @@ var GamePlayerInner = React2.memo(function GamePlayerInner2(props) {
|
|
|
9283
9770
|
const handleToggleInputDisplay = React2.useCallback(() => {
|
|
9284
9771
|
updateSettings({ showInputDisplay: !settings.showInputDisplay });
|
|
9285
9772
|
}, [updateSettings, settings.showInputDisplay]);
|
|
9773
|
+
const handleToggleHaptics = React2.useCallback(() => {
|
|
9774
|
+
updateSettings({ hapticsEnabled: !settings.hapticsEnabled });
|
|
9775
|
+
}, [updateSettings, settings.hapticsEnabled]);
|
|
9286
9776
|
const handleToggleRecording = React2.useCallback(async () => {
|
|
9287
9777
|
if (!recordingSupported) {
|
|
9288
9778
|
console.warn("[Recording] Not supported in this browser");
|
|
@@ -9369,7 +9859,12 @@ var GamePlayerInner = React2.memo(function GamePlayerInner2(props) {
|
|
|
9369
9859
|
system,
|
|
9370
9860
|
isRunning: status === "running" || status === "paused",
|
|
9371
9861
|
controls,
|
|
9372
|
-
systemColor
|
|
9862
|
+
systemColor,
|
|
9863
|
+
hapticsEnabled: settings.hapticsEnabled,
|
|
9864
|
+
onButtonDown: nostalgist.pressDown,
|
|
9865
|
+
onButtonUp: nostalgist.pressUp,
|
|
9866
|
+
onPause: nostalgist.pause,
|
|
9867
|
+
onResume: nostalgist.resume
|
|
9373
9868
|
}
|
|
9374
9869
|
),
|
|
9375
9870
|
!isFullscreen2 && isMobile && /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -9524,9 +10019,10 @@ var GamePlayerInner = React2.memo(function GamePlayerInner2(props) {
|
|
|
9524
10019
|
systemColor,
|
|
9525
10020
|
cheatsModalOpen,
|
|
9526
10021
|
setCheatsModalOpen,
|
|
9527
|
-
cheats,
|
|
10022
|
+
cheats: allCheats,
|
|
9528
10023
|
activeCheats,
|
|
9529
10024
|
onToggleCheat: handleToggleCheat,
|
|
10025
|
+
onAddManualCheat: handleAddManualCheat,
|
|
9530
10026
|
saveModalOpen,
|
|
9531
10027
|
setSaveModalOpen,
|
|
9532
10028
|
saveModalMode,
|
|
@@ -9546,7 +10042,9 @@ var GamePlayerInner = React2.memo(function GamePlayerInner2(props) {
|
|
|
9546
10042
|
settingsModalOpen,
|
|
9547
10043
|
setSettingsModalOpen,
|
|
9548
10044
|
currentLanguage: props.currentLanguage,
|
|
9549
|
-
onLanguageChange: props.onLanguageChange
|
|
10045
|
+
onLanguageChange: props.onLanguageChange,
|
|
10046
|
+
hapticsEnabled: settings.hapticsEnabled,
|
|
10047
|
+
onToggleHaptics: handleToggleHaptics
|
|
9550
10048
|
}
|
|
9551
10049
|
),
|
|
9552
10050
|
!isMobile && /* @__PURE__ */ jsxRuntime.jsx(
|