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.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React2, { memo, useState, useEffect, useRef, createContext, useMemo, useCallback, useContext } from 'react';
|
|
2
|
-
import { Gauge, Play, Pause, RotateCcw, Rewind, Save, Download, Camera, Video, Circle, Monitor, ChevronDown, RefreshCw, HelpCircle, Maximize, Gamepad2, Joystick, Code, Settings, Power, Square, Keyboard, ChevronUp, VolumeX, Volume1, Volume2, Loader2, Trophy, X, AlertTriangle, Minimize2, List, PauseCircle, Check, Clock, Lock, Unlock, Move, User, Copy,
|
|
2
|
+
import { Gauge, Play, Pause, RotateCcw, Rewind, Save, Download, Camera, Video, Circle, Monitor, ChevronDown, RefreshCw, HelpCircle, Maximize, Gamepad2, Joystick, Code, Settings, Power, Square, Keyboard, Hand, Zap, ChevronUp, VolumeX, Volume1, Volume2, Loader2, Trophy, X, AlertTriangle, Minimize2, List, PauseCircle, Check, Clock, Lock, Unlock, StopCircle, ZapOff, Move, User, Copy, HardDrive, Trash2, Cpu, AlertCircle, FileCode, Globe, ExternalLink, EyeOff, Eye, Shield, CheckCircle, LogOut, Info, XCircle } from 'lucide-react';
|
|
3
3
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
4
4
|
import { createPortal } from 'react-dom';
|
|
5
5
|
import { Nostalgist } from 'nostalgist';
|
|
@@ -328,9 +328,11 @@ var en = {
|
|
|
328
328
|
cheats: "Cheats",
|
|
329
329
|
retroAchievements: "RetroAchievements",
|
|
330
330
|
shortcuts: "Shortcuts",
|
|
331
|
-
exit: "Exit",
|
|
331
|
+
exit: "Exit Game",
|
|
332
332
|
language: "Language",
|
|
333
|
-
selectLanguage: "Select Language"
|
|
333
|
+
selectLanguage: "Select Language",
|
|
334
|
+
haptics: "Haptic Feedback",
|
|
335
|
+
enableHaptics: "Vibration"
|
|
334
336
|
},
|
|
335
337
|
overlay: {
|
|
336
338
|
play: "PLAY",
|
|
@@ -407,13 +409,16 @@ var en = {
|
|
|
407
409
|
},
|
|
408
410
|
cheats: {
|
|
409
411
|
title: "Cheats",
|
|
410
|
-
addCheat: "Add Cheat",
|
|
411
|
-
available: "
|
|
412
|
-
emptyTitle: "No
|
|
413
|
-
emptyDesc: "
|
|
414
|
-
copy: "Copy
|
|
415
|
-
active: "
|
|
416
|
-
toggleHint: "Click
|
|
412
|
+
addCheat: "Add Manual Cheat",
|
|
413
|
+
available: "Available Cheats",
|
|
414
|
+
emptyTitle: "No Cheats Available",
|
|
415
|
+
emptyDesc: "This game does not have any known cheats.",
|
|
416
|
+
copy: "Copy",
|
|
417
|
+
active: "Active",
|
|
418
|
+
toggleHint: "Click to toggle",
|
|
419
|
+
codePlaceholder: "Enter cheat code (e.g. 00C-048-E6E)",
|
|
420
|
+
descPlaceholder: "Cheat description (optional)",
|
|
421
|
+
add: "Add Cheat"
|
|
417
422
|
},
|
|
418
423
|
saveSlots: {
|
|
419
424
|
title: "Save States",
|
|
@@ -2586,7 +2591,8 @@ function useTouchHandlers({
|
|
|
2586
2591
|
onPress,
|
|
2587
2592
|
onPressDown,
|
|
2588
2593
|
onRelease,
|
|
2589
|
-
onPositionChange
|
|
2594
|
+
onPositionChange,
|
|
2595
|
+
hapticsEnabled = true
|
|
2590
2596
|
}) {
|
|
2591
2597
|
const isDraggingRef = useRef(false);
|
|
2592
2598
|
const drag = useDrag({
|
|
@@ -2612,7 +2618,7 @@ function useTouchHandlers({
|
|
|
2612
2618
|
const touch = e.touches[0];
|
|
2613
2619
|
e.preventDefault();
|
|
2614
2620
|
e.stopPropagation();
|
|
2615
|
-
if (navigator.vibrate) {
|
|
2621
|
+
if (hapticsEnabled && navigator.vibrate) {
|
|
2616
2622
|
navigator.vibrate(8);
|
|
2617
2623
|
}
|
|
2618
2624
|
if (isSystemButton) {
|
|
@@ -2849,7 +2855,11 @@ var VirtualButton = React2.memo(function VirtualButton2({
|
|
|
2849
2855
|
customPosition,
|
|
2850
2856
|
onPositionChange,
|
|
2851
2857
|
isLandscape = false,
|
|
2852
|
-
console: console2 = ""
|
|
2858
|
+
console: console2 = "",
|
|
2859
|
+
hapticsEnabled = true,
|
|
2860
|
+
mode = "normal",
|
|
2861
|
+
isHeld = false,
|
|
2862
|
+
isInTurbo = false
|
|
2853
2863
|
}) {
|
|
2854
2864
|
const t = useKoinTranslation();
|
|
2855
2865
|
const buttonRef = useRef(null);
|
|
@@ -2876,7 +2886,9 @@ var VirtualButton = React2.memo(function VirtualButton2({
|
|
|
2876
2886
|
onPress,
|
|
2877
2887
|
onPressDown,
|
|
2878
2888
|
onRelease,
|
|
2879
|
-
onPositionChange
|
|
2889
|
+
onPositionChange,
|
|
2890
|
+
hapticsEnabled
|
|
2891
|
+
// Pass haptics setting
|
|
2880
2892
|
});
|
|
2881
2893
|
useTouchEvents(buttonRef, {
|
|
2882
2894
|
onTouchStart: handleTouchStart,
|
|
@@ -2904,7 +2916,7 @@ var VirtualButton = React2.memo(function VirtualButton2({
|
|
|
2904
2916
|
width = `${config.size * 2}px`;
|
|
2905
2917
|
}
|
|
2906
2918
|
}
|
|
2907
|
-
return /* @__PURE__ */
|
|
2919
|
+
return /* @__PURE__ */ jsxs(
|
|
2908
2920
|
"button",
|
|
2909
2921
|
{
|
|
2910
2922
|
ref: buttonRef,
|
|
@@ -2944,48 +2956,364 @@ var VirtualButton = React2.memo(function VirtualButton2({
|
|
|
2944
2956
|
},
|
|
2945
2957
|
"aria-label": label,
|
|
2946
2958
|
onContextMenu: (e) => e.preventDefault(),
|
|
2947
|
-
children:
|
|
2959
|
+
children: [
|
|
2960
|
+
(mode !== "normal" || isHeld || isInTurbo) && /* @__PURE__ */ jsx(
|
|
2961
|
+
"span",
|
|
2962
|
+
{
|
|
2963
|
+
className: "absolute -top-1 -right-1 rounded-full flex items-center justify-center",
|
|
2964
|
+
style: {
|
|
2965
|
+
width: "16px",
|
|
2966
|
+
height: "16px",
|
|
2967
|
+
backgroundColor: isHeld ? "#22c55e" : isInTurbo ? "#fbbf24" : "rgba(255,255,255,0.2)",
|
|
2968
|
+
animation: isInTurbo ? "pulse 0.5s infinite" : "none"
|
|
2969
|
+
},
|
|
2970
|
+
children: isHeld ? /* @__PURE__ */ jsx(Hand, { size: 10, color: "white", strokeWidth: 3 }) : isInTurbo ? /* @__PURE__ */ jsx(Zap, { size: 10, color: "white", fill: "white" }) : mode === "hold" ? /* @__PURE__ */ jsx(Hand, { size: 10, color: "white" }) : mode === "turbo" ? /* @__PURE__ */ jsx(Zap, { size: 10, color: "white" }) : null
|
|
2971
|
+
}
|
|
2972
|
+
),
|
|
2973
|
+
/* @__PURE__ */ jsx("span", { className: "drop-shadow-md", children: label })
|
|
2974
|
+
]
|
|
2948
2975
|
}
|
|
2949
2976
|
);
|
|
2950
2977
|
});
|
|
2951
2978
|
var VirtualButton_default = VirtualButton;
|
|
2979
|
+
var CENTER_TOUCH_RADIUS = 0.35;
|
|
2980
|
+
var Dpad = React2.memo(function Dpad2({
|
|
2981
|
+
size = 180,
|
|
2982
|
+
x,
|
|
2983
|
+
y,
|
|
2984
|
+
containerWidth,
|
|
2985
|
+
containerHeight,
|
|
2986
|
+
systemColor = "#00FF41",
|
|
2987
|
+
isLandscape = false,
|
|
2988
|
+
customPosition,
|
|
2989
|
+
onPositionChange,
|
|
2990
|
+
hapticsEnabled = true,
|
|
2991
|
+
onButtonDown,
|
|
2992
|
+
onButtonUp
|
|
2993
|
+
}) {
|
|
2994
|
+
const dpadRef = useRef(null);
|
|
2995
|
+
const activeTouchRef = useRef(null);
|
|
2996
|
+
const activeDirectionsRef = useRef(/* @__PURE__ */ new Set());
|
|
2997
|
+
const upPathRef = useRef(null);
|
|
2998
|
+
const downPathRef = useRef(null);
|
|
2999
|
+
const leftPathRef = useRef(null);
|
|
3000
|
+
const rightPathRef = useRef(null);
|
|
3001
|
+
const centerCircleRef = useRef(null);
|
|
3002
|
+
const displayX = customPosition ? customPosition.x : x;
|
|
3003
|
+
const displayY = customPosition ? customPosition.y : y;
|
|
3004
|
+
const releaseAllDirections = useCallback(() => {
|
|
3005
|
+
activeDirectionsRef.current.forEach((dir) => onButtonUp(dir));
|
|
3006
|
+
activeDirectionsRef.current = /* @__PURE__ */ new Set();
|
|
3007
|
+
}, [onButtonUp]);
|
|
3008
|
+
const drag = useDrag({
|
|
3009
|
+
elementSize: size,
|
|
3010
|
+
displayX,
|
|
3011
|
+
displayY,
|
|
3012
|
+
containerWidth,
|
|
3013
|
+
containerHeight,
|
|
3014
|
+
onPositionChange,
|
|
3015
|
+
centerThreshold: CENTER_TOUCH_RADIUS,
|
|
3016
|
+
onDragStart: () => {
|
|
3017
|
+
releaseAllDirections();
|
|
3018
|
+
updateVisuals(/* @__PURE__ */ new Set());
|
|
3019
|
+
}
|
|
3020
|
+
});
|
|
3021
|
+
const getDirectionsFromTouch = useCallback((touchX, touchY, rect) => {
|
|
3022
|
+
const centerX = rect.left + rect.width / 2;
|
|
3023
|
+
const centerY = rect.top + rect.height / 2;
|
|
3024
|
+
const dx = touchX - centerX;
|
|
3025
|
+
const dy = touchY - centerY;
|
|
3026
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
3027
|
+
const deadZone = rect.width / 2 * 0.15;
|
|
3028
|
+
if (distance < deadZone) return /* @__PURE__ */ new Set();
|
|
3029
|
+
const directions = /* @__PURE__ */ new Set();
|
|
3030
|
+
const angle = Math.atan2(dy, dx) * (180 / Math.PI);
|
|
3031
|
+
if (angle >= -150 && angle <= -30) directions.add("up");
|
|
3032
|
+
if (angle >= 30 && angle <= 150) directions.add("down");
|
|
3033
|
+
if (angle >= 120 || angle <= -120) directions.add("left");
|
|
3034
|
+
if (angle >= -60 && angle <= 60) directions.add("right");
|
|
3035
|
+
return directions;
|
|
3036
|
+
}, []);
|
|
3037
|
+
const updateVisuals = useCallback((directions) => {
|
|
3038
|
+
const activeFill = `${systemColor}80`;
|
|
3039
|
+
const inactiveFill = "rgba(255, 255, 255, 0.05)";
|
|
3040
|
+
const activeStroke = systemColor;
|
|
3041
|
+
const inactiveStroke = "rgba(255, 255, 255, 0.2)";
|
|
3042
|
+
const glow = `0 0 15px ${systemColor}`;
|
|
3043
|
+
const updatePart = (ref, isActive) => {
|
|
3044
|
+
if (ref.current) {
|
|
3045
|
+
ref.current.style.fill = isActive ? activeFill : inactiveFill;
|
|
3046
|
+
ref.current.style.stroke = isActive ? activeStroke : inactiveStroke;
|
|
3047
|
+
ref.current.style.filter = isActive ? `drop-shadow(${glow})` : "none";
|
|
3048
|
+
ref.current.style.transform = isActive ? "scale(0.98)" : "scale(1)";
|
|
3049
|
+
ref.current.style.transformOrigin = "center";
|
|
3050
|
+
}
|
|
3051
|
+
};
|
|
3052
|
+
updatePart(upPathRef, directions.has("up"));
|
|
3053
|
+
updatePart(downPathRef, directions.has("down"));
|
|
3054
|
+
updatePart(leftPathRef, directions.has("left"));
|
|
3055
|
+
updatePart(rightPathRef, directions.has("right"));
|
|
3056
|
+
if (centerCircleRef.current) {
|
|
3057
|
+
const isAny = directions.size > 0;
|
|
3058
|
+
centerCircleRef.current.style.fill = isAny ? systemColor : "rgba(0,0,0,0.5)";
|
|
3059
|
+
centerCircleRef.current.style.stroke = isAny ? "#fff" : "rgba(255,255,255,0.3)";
|
|
3060
|
+
}
|
|
3061
|
+
}, [systemColor]);
|
|
3062
|
+
const updateDirections = useCallback((newDirections) => {
|
|
3063
|
+
const prev = activeDirectionsRef.current;
|
|
3064
|
+
prev.forEach((dir) => {
|
|
3065
|
+
if (!newDirections.has(dir)) {
|
|
3066
|
+
onButtonUp(dir);
|
|
3067
|
+
}
|
|
3068
|
+
});
|
|
3069
|
+
newDirections.forEach((dir) => {
|
|
3070
|
+
if (!prev.has(dir)) {
|
|
3071
|
+
if (hapticsEnabled && navigator.vibrate) navigator.vibrate(5);
|
|
3072
|
+
onButtonDown(dir);
|
|
3073
|
+
}
|
|
3074
|
+
});
|
|
3075
|
+
activeDirectionsRef.current = newDirections;
|
|
3076
|
+
updateVisuals(newDirections);
|
|
3077
|
+
}, [updateVisuals, onButtonDown, onButtonUp, hapticsEnabled]);
|
|
3078
|
+
const handleTouchStart = useCallback((e) => {
|
|
3079
|
+
e.preventDefault();
|
|
3080
|
+
if (activeTouchRef.current !== null) return;
|
|
3081
|
+
const touch = e.changedTouches[0];
|
|
3082
|
+
activeTouchRef.current = touch.identifier;
|
|
3083
|
+
const rect = dpadRef.current?.getBoundingClientRect();
|
|
3084
|
+
if (!rect) return;
|
|
3085
|
+
if (onPositionChange) {
|
|
3086
|
+
drag.checkDragStart(touch.clientX, touch.clientY, rect);
|
|
3087
|
+
}
|
|
3088
|
+
if (!drag.isDragging) {
|
|
3089
|
+
updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
|
|
3090
|
+
}
|
|
3091
|
+
}, [getDirectionsFromTouch, updateDirections, onPositionChange, drag]);
|
|
3092
|
+
const handleTouchMove = useCallback((e) => {
|
|
3093
|
+
e.preventDefault();
|
|
3094
|
+
let touch = null;
|
|
3095
|
+
for (let i = 0; i < e.changedTouches.length; i++) {
|
|
3096
|
+
if (e.changedTouches[i].identifier === activeTouchRef.current) {
|
|
3097
|
+
touch = e.changedTouches[i];
|
|
3098
|
+
break;
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
if (!touch) return;
|
|
3102
|
+
if (drag.isDragging) {
|
|
3103
|
+
drag.handleDragMove(touch.clientX, touch.clientY);
|
|
3104
|
+
} else if (onPositionChange) {
|
|
3105
|
+
const startedDrag = drag.checkMoveThreshold(touch.clientX, touch.clientY);
|
|
3106
|
+
if (!startedDrag) {
|
|
3107
|
+
drag.clearDragTimer();
|
|
3108
|
+
const rect = dpadRef.current?.getBoundingClientRect();
|
|
3109
|
+
if (rect) {
|
|
3110
|
+
updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
} else {
|
|
3114
|
+
const rect = dpadRef.current?.getBoundingClientRect();
|
|
3115
|
+
if (rect) {
|
|
3116
|
+
updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
}, [drag, getDirectionsFromTouch, updateDirections, onPositionChange]);
|
|
3120
|
+
const handleTouchEnd = useCallback((e) => {
|
|
3121
|
+
e.preventDefault();
|
|
3122
|
+
drag.clearDragTimer();
|
|
3123
|
+
let touchEnded = false;
|
|
3124
|
+
for (let i = 0; i < e.changedTouches.length; i++) {
|
|
3125
|
+
if (e.changedTouches[i].identifier === activeTouchRef.current) {
|
|
3126
|
+
touchEnded = true;
|
|
3127
|
+
break;
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
if (touchEnded) {
|
|
3131
|
+
activeTouchRef.current = null;
|
|
3132
|
+
if (drag.isDragging) {
|
|
3133
|
+
drag.handleDragEnd();
|
|
3134
|
+
} else {
|
|
3135
|
+
activeDirectionsRef.current.forEach((dir) => onButtonUp(dir));
|
|
3136
|
+
activeDirectionsRef.current = /* @__PURE__ */ new Set();
|
|
3137
|
+
updateVisuals(/* @__PURE__ */ new Set());
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
}, [updateVisuals, drag, onButtonUp]);
|
|
3141
|
+
useTouchEvents(dpadRef, {
|
|
3142
|
+
onTouchStart: handleTouchStart,
|
|
3143
|
+
onTouchMove: handleTouchMove,
|
|
3144
|
+
onTouchEnd: handleTouchEnd,
|
|
3145
|
+
onTouchCancel: handleTouchEnd
|
|
3146
|
+
}, { cleanup: drag.clearDragTimer });
|
|
3147
|
+
const leftPx = displayX / 100 * containerWidth - size / 2;
|
|
3148
|
+
const topPx = displayY / 100 * containerHeight - size / 2;
|
|
3149
|
+
const dUp = "M 35,5 L 65,5 L 65,35 L 50,50 L 35,35 Z";
|
|
3150
|
+
const dRight = "M 65,35 L 95,35 L 95,65 L 65,65 L 50,50 Z";
|
|
3151
|
+
const dDown = "M 65,65 L 65,95 L 35,95 L 35,65 L 50,50 Z";
|
|
3152
|
+
const dLeft = "M 35,65 L 5,65 L 5,35 L 35,35 L 50,50 Z";
|
|
3153
|
+
return /* @__PURE__ */ jsxs(
|
|
3154
|
+
"div",
|
|
3155
|
+
{
|
|
3156
|
+
ref: dpadRef,
|
|
3157
|
+
className: `absolute pointer-events-auto touch-manipulation select-none ${drag.isDragging ? "opacity-60" : ""}`,
|
|
3158
|
+
style: {
|
|
3159
|
+
top: 0,
|
|
3160
|
+
left: 0,
|
|
3161
|
+
transform: `translate3d(${leftPx}px, ${topPx}px, 0)${drag.isDragging ? " scale(1.05)" : ""}`,
|
|
3162
|
+
width: size,
|
|
3163
|
+
height: size,
|
|
3164
|
+
opacity: isLandscape ? 0.75 : 0.9,
|
|
3165
|
+
WebkitTouchCallout: "none",
|
|
3166
|
+
WebkitUserSelect: "none",
|
|
3167
|
+
touchAction: "none",
|
|
3168
|
+
transition: drag.isDragging ? "none" : "transform 0.1s ease-out"
|
|
3169
|
+
},
|
|
3170
|
+
children: [
|
|
3171
|
+
/* @__PURE__ */ 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"}` }),
|
|
3172
|
+
/* @__PURE__ */ jsxs("svg", { width: "100%", height: "100%", viewBox: "0 0 100 100", className: "drop-shadow-xl relative z-10", children: [
|
|
3173
|
+
/* @__PURE__ */ 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" }),
|
|
3174
|
+
/* @__PURE__ */ 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" }),
|
|
3175
|
+
/* @__PURE__ */ 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" }),
|
|
3176
|
+
/* @__PURE__ */ 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" }),
|
|
3177
|
+
/* @__PURE__ */ jsx(
|
|
3178
|
+
"circle",
|
|
3179
|
+
{
|
|
3180
|
+
ref: centerCircleRef,
|
|
3181
|
+
cx: "50",
|
|
3182
|
+
cy: "50",
|
|
3183
|
+
r: "12",
|
|
3184
|
+
fill: drag.isDragging ? systemColor : "rgba(0,0,0,0.5)",
|
|
3185
|
+
stroke: drag.isDragging ? "#fff" : "rgba(255,255,255,0.3)",
|
|
3186
|
+
strokeWidth: drag.isDragging ? 2 : 1
|
|
3187
|
+
}
|
|
3188
|
+
),
|
|
3189
|
+
/* @__PURE__ */ 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" }),
|
|
3190
|
+
/* @__PURE__ */ 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" }),
|
|
3191
|
+
/* @__PURE__ */ 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" }),
|
|
3192
|
+
/* @__PURE__ */ 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" })
|
|
3193
|
+
] })
|
|
3194
|
+
]
|
|
3195
|
+
}
|
|
3196
|
+
);
|
|
3197
|
+
});
|
|
3198
|
+
var Dpad_default = Dpad;
|
|
2952
3199
|
|
|
2953
|
-
// src/
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
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
|
-
|
|
3200
|
+
// src/components/VirtualController/positioning.ts
|
|
3201
|
+
function adjustButtonPosition(config, context) {
|
|
3202
|
+
const sizeMultiplier = context.isFullscreen ? 1.1 : 1;
|
|
3203
|
+
return {
|
|
3204
|
+
...config,
|
|
3205
|
+
size: Math.floor(config.size * sizeMultiplier)
|
|
3206
|
+
};
|
|
3207
|
+
}
|
|
3208
|
+
var STORAGE_KEY = "virtual-button-positions";
|
|
3209
|
+
function useButtonPositions() {
|
|
3210
|
+
const [landscapePositions, setLandscapePositions] = useState({});
|
|
3211
|
+
const [portraitPositions, setPortraitPositions] = useState({});
|
|
3212
|
+
useEffect(() => {
|
|
3213
|
+
try {
|
|
3214
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
3215
|
+
if (stored) {
|
|
3216
|
+
const parsed = JSON.parse(stored);
|
|
3217
|
+
if (parsed.landscape && parsed.portrait) {
|
|
3218
|
+
setLandscapePositions(parsed.landscape);
|
|
3219
|
+
setPortraitPositions(parsed.portrait);
|
|
3220
|
+
} else {
|
|
3221
|
+
setLandscapePositions(parsed);
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
} catch (e) {
|
|
3225
|
+
console.error("Failed to load button positions:", e);
|
|
3226
|
+
}
|
|
3227
|
+
}, []);
|
|
3228
|
+
const savePosition = useCallback((buttonType, x, y, isLandscape) => {
|
|
3229
|
+
if (isLandscape) {
|
|
3230
|
+
setLandscapePositions((prev) => {
|
|
3231
|
+
const updated = { ...prev, [buttonType]: { x, y } };
|
|
3232
|
+
try {
|
|
3233
|
+
const stored = {
|
|
3234
|
+
landscape: updated,
|
|
3235
|
+
portrait: portraitPositions
|
|
3236
|
+
};
|
|
3237
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(stored));
|
|
3238
|
+
} catch (e) {
|
|
3239
|
+
console.error("Failed to save button position:", e);
|
|
3240
|
+
}
|
|
3241
|
+
return updated;
|
|
3242
|
+
});
|
|
3243
|
+
} else {
|
|
3244
|
+
setPortraitPositions((prev) => {
|
|
3245
|
+
const updated = { ...prev, [buttonType]: { x, y } };
|
|
3246
|
+
try {
|
|
3247
|
+
const stored = {
|
|
3248
|
+
landscape: landscapePositions,
|
|
3249
|
+
portrait: updated
|
|
3250
|
+
};
|
|
3251
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(stored));
|
|
3252
|
+
} catch (e) {
|
|
3253
|
+
console.error("Failed to save button position:", e);
|
|
3254
|
+
}
|
|
3255
|
+
return updated;
|
|
3256
|
+
});
|
|
3257
|
+
}
|
|
3258
|
+
}, [landscapePositions, portraitPositions]);
|
|
3259
|
+
const getPosition = useCallback((buttonType, isLandscape) => {
|
|
3260
|
+
const positions = isLandscape ? landscapePositions : portraitPositions;
|
|
3261
|
+
return positions[buttonType] || null;
|
|
3262
|
+
}, [landscapePositions, portraitPositions]);
|
|
3263
|
+
const resetPositions = useCallback(() => {
|
|
3264
|
+
setLandscapePositions({});
|
|
3265
|
+
setPortraitPositions({});
|
|
3266
|
+
try {
|
|
3267
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
3268
|
+
} catch (e) {
|
|
3269
|
+
console.error("Failed to reset button positions:", e);
|
|
3270
|
+
}
|
|
3271
|
+
}, []);
|
|
3272
|
+
return {
|
|
3273
|
+
landscapePositions,
|
|
3274
|
+
portraitPositions,
|
|
3275
|
+
savePosition,
|
|
3276
|
+
getPosition,
|
|
3277
|
+
resetPositions
|
|
3278
|
+
};
|
|
3279
|
+
}
|
|
3280
|
+
|
|
3281
|
+
// src/lib/controls/types.ts
|
|
3282
|
+
var DPAD_BUTTONS2 = ["up", "down", "left", "right"];
|
|
3283
|
+
var FACE_BUTTONS = ["a", "b", "x", "y"];
|
|
3284
|
+
var SHOULDER_BUTTONS = ["l", "r"];
|
|
3285
|
+
var TRIGGER_BUTTONS = ["l2", "r2"];
|
|
3286
|
+
var STICK_BUTTONS = ["l3", "r3"];
|
|
3287
|
+
var SYSTEM_BUTTONS = ["start", "select"];
|
|
3288
|
+
var ALL_BUTTONS = [
|
|
3289
|
+
...DPAD_BUTTONS2,
|
|
3290
|
+
...FACE_BUTTONS,
|
|
3291
|
+
...SHOULDER_BUTTONS,
|
|
3292
|
+
...TRIGGER_BUTTONS,
|
|
3293
|
+
...STICK_BUTTONS,
|
|
3294
|
+
...SYSTEM_BUTTONS
|
|
3295
|
+
];
|
|
3296
|
+
|
|
3297
|
+
// src/lib/controls/defaults.ts
|
|
3298
|
+
var STANDARD_GAMEPAD_BUTTONS = {
|
|
3299
|
+
// Face buttons (Xbox/PlayStation layout)
|
|
3300
|
+
a: 0,
|
|
3301
|
+
// A / Cross (bottom)
|
|
3302
|
+
b: 1,
|
|
3303
|
+
// B / Circle (right)
|
|
3304
|
+
x: 2,
|
|
3305
|
+
// X / Square (left)
|
|
3306
|
+
y: 3,
|
|
3307
|
+
// Y / Triangle (top)
|
|
3308
|
+
// Shoulders
|
|
3309
|
+
l: 4,
|
|
3310
|
+
// LB / L1
|
|
3311
|
+
r: 5,
|
|
3312
|
+
// RB / R1
|
|
3313
|
+
// Triggers
|
|
3314
|
+
l2: 6,
|
|
3315
|
+
// LT / L2
|
|
3316
|
+
r2: 7,
|
|
2989
3317
|
// RT / R2
|
|
2990
3318
|
// System
|
|
2991
3319
|
select: 8,
|
|
@@ -3535,351 +3863,6 @@ function getKeyboardCode(buttonType, controls) {
|
|
|
3535
3863
|
}
|
|
3536
3864
|
return DEFAULT_CONTROLS[key] ?? null;
|
|
3537
3865
|
}
|
|
3538
|
-
function getKeyName(code) {
|
|
3539
|
-
if (code.startsWith("Key")) return code.slice(3).toLowerCase();
|
|
3540
|
-
if (code.startsWith("Arrow")) return code.slice(5);
|
|
3541
|
-
if (code === "Enter") return "Enter";
|
|
3542
|
-
if (code === "ShiftRight" || code === "ShiftLeft") return "Shift";
|
|
3543
|
-
return code;
|
|
3544
|
-
}
|
|
3545
|
-
function getCanvas() {
|
|
3546
|
-
return document.querySelector(".game-canvas-container canvas") || document.querySelector("canvas");
|
|
3547
|
-
}
|
|
3548
|
-
function dispatchKeyboardEvent(type, code) {
|
|
3549
|
-
const canvas = getCanvas();
|
|
3550
|
-
if (!canvas) {
|
|
3551
|
-
return false;
|
|
3552
|
-
}
|
|
3553
|
-
const event = new KeyboardEvent(type, {
|
|
3554
|
-
code,
|
|
3555
|
-
key: getKeyName(code),
|
|
3556
|
-
bubbles: true,
|
|
3557
|
-
cancelable: true
|
|
3558
|
-
});
|
|
3559
|
-
canvas.dispatchEvent(event);
|
|
3560
|
-
return true;
|
|
3561
|
-
}
|
|
3562
|
-
var CENTER_TOUCH_RADIUS = 0.35;
|
|
3563
|
-
var Dpad = React2.memo(function Dpad2({
|
|
3564
|
-
size = 180,
|
|
3565
|
-
x,
|
|
3566
|
-
y,
|
|
3567
|
-
containerWidth,
|
|
3568
|
-
containerHeight,
|
|
3569
|
-
controls,
|
|
3570
|
-
systemColor = "#00FF41",
|
|
3571
|
-
isLandscape = false,
|
|
3572
|
-
customPosition,
|
|
3573
|
-
onPositionChange
|
|
3574
|
-
}) {
|
|
3575
|
-
const dpadRef = useRef(null);
|
|
3576
|
-
const activeTouchRef = useRef(null);
|
|
3577
|
-
const activeDirectionsRef = useRef(/* @__PURE__ */ new Set());
|
|
3578
|
-
const upPathRef = useRef(null);
|
|
3579
|
-
const downPathRef = useRef(null);
|
|
3580
|
-
const leftPathRef = useRef(null);
|
|
3581
|
-
const rightPathRef = useRef(null);
|
|
3582
|
-
const centerCircleRef = useRef(null);
|
|
3583
|
-
const displayX = customPosition ? customPosition.x : x;
|
|
3584
|
-
const displayY = customPosition ? customPosition.y : y;
|
|
3585
|
-
const releaseAllDirections = useCallback((getKeyCode2) => {
|
|
3586
|
-
activeDirectionsRef.current.forEach((dir) => {
|
|
3587
|
-
const keyCode = getKeyCode2(dir);
|
|
3588
|
-
if (keyCode) dispatchKeyboardEvent("keyup", keyCode);
|
|
3589
|
-
});
|
|
3590
|
-
activeDirectionsRef.current = /* @__PURE__ */ new Set();
|
|
3591
|
-
}, []);
|
|
3592
|
-
const getKeyCode = useCallback((direction) => {
|
|
3593
|
-
if (!controls) {
|
|
3594
|
-
const defaults = {
|
|
3595
|
-
up: "ArrowUp",
|
|
3596
|
-
down: "ArrowDown",
|
|
3597
|
-
left: "ArrowLeft",
|
|
3598
|
-
right: "ArrowRight"
|
|
3599
|
-
};
|
|
3600
|
-
return defaults[direction];
|
|
3601
|
-
}
|
|
3602
|
-
return controls[direction] || "";
|
|
3603
|
-
}, [controls]);
|
|
3604
|
-
const drag = useDrag({
|
|
3605
|
-
elementSize: size,
|
|
3606
|
-
displayX,
|
|
3607
|
-
displayY,
|
|
3608
|
-
containerWidth,
|
|
3609
|
-
containerHeight,
|
|
3610
|
-
onPositionChange,
|
|
3611
|
-
centerThreshold: CENTER_TOUCH_RADIUS,
|
|
3612
|
-
onDragStart: () => {
|
|
3613
|
-
releaseAllDirections(getKeyCode);
|
|
3614
|
-
updateVisuals(/* @__PURE__ */ new Set());
|
|
3615
|
-
}
|
|
3616
|
-
});
|
|
3617
|
-
const getDirectionsFromTouch = useCallback((touchX, touchY, rect) => {
|
|
3618
|
-
const centerX = rect.left + rect.width / 2;
|
|
3619
|
-
const centerY = rect.top + rect.height / 2;
|
|
3620
|
-
const dx = touchX - centerX;
|
|
3621
|
-
const dy = touchY - centerY;
|
|
3622
|
-
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
3623
|
-
const deadZone = rect.width / 2 * 0.15;
|
|
3624
|
-
if (distance < deadZone) return /* @__PURE__ */ new Set();
|
|
3625
|
-
const directions = /* @__PURE__ */ new Set();
|
|
3626
|
-
const angle = Math.atan2(dy, dx) * (180 / Math.PI);
|
|
3627
|
-
if (angle >= -150 && angle <= -30) directions.add("up");
|
|
3628
|
-
if (angle >= 30 && angle <= 150) directions.add("down");
|
|
3629
|
-
if (angle >= 120 || angle <= -120) directions.add("left");
|
|
3630
|
-
if (angle >= -60 && angle <= 60) directions.add("right");
|
|
3631
|
-
return directions;
|
|
3632
|
-
}, []);
|
|
3633
|
-
const updateVisuals = useCallback((directions) => {
|
|
3634
|
-
const activeFill = `${systemColor}80`;
|
|
3635
|
-
const inactiveFill = "rgba(255, 255, 255, 0.05)";
|
|
3636
|
-
const activeStroke = systemColor;
|
|
3637
|
-
const inactiveStroke = "rgba(255, 255, 255, 0.2)";
|
|
3638
|
-
const glow = `0 0 15px ${systemColor}`;
|
|
3639
|
-
const updatePart = (ref, isActive) => {
|
|
3640
|
-
if (ref.current) {
|
|
3641
|
-
ref.current.style.fill = isActive ? activeFill : inactiveFill;
|
|
3642
|
-
ref.current.style.stroke = isActive ? activeStroke : inactiveStroke;
|
|
3643
|
-
ref.current.style.filter = isActive ? `drop-shadow(${glow})` : "none";
|
|
3644
|
-
ref.current.style.transform = isActive ? "scale(0.98)" : "scale(1)";
|
|
3645
|
-
ref.current.style.transformOrigin = "center";
|
|
3646
|
-
}
|
|
3647
|
-
};
|
|
3648
|
-
updatePart(upPathRef, directions.has("up"));
|
|
3649
|
-
updatePart(downPathRef, directions.has("down"));
|
|
3650
|
-
updatePart(leftPathRef, directions.has("left"));
|
|
3651
|
-
updatePart(rightPathRef, directions.has("right"));
|
|
3652
|
-
if (centerCircleRef.current) {
|
|
3653
|
-
const isAny = directions.size > 0;
|
|
3654
|
-
centerCircleRef.current.style.fill = isAny ? systemColor : "rgba(0,0,0,0.5)";
|
|
3655
|
-
centerCircleRef.current.style.stroke = isAny ? "#fff" : "rgba(255,255,255,0.3)";
|
|
3656
|
-
}
|
|
3657
|
-
}, [systemColor]);
|
|
3658
|
-
const updateDirections = useCallback((newDirections) => {
|
|
3659
|
-
const prev = activeDirectionsRef.current;
|
|
3660
|
-
prev.forEach((dir) => {
|
|
3661
|
-
if (!newDirections.has(dir)) {
|
|
3662
|
-
const keyCode = getKeyCode(dir);
|
|
3663
|
-
if (keyCode) dispatchKeyboardEvent("keyup", keyCode);
|
|
3664
|
-
}
|
|
3665
|
-
});
|
|
3666
|
-
newDirections.forEach((dir) => {
|
|
3667
|
-
if (!prev.has(dir)) {
|
|
3668
|
-
const keyCode = getKeyCode(dir);
|
|
3669
|
-
if (keyCode) {
|
|
3670
|
-
if (navigator.vibrate) navigator.vibrate(5);
|
|
3671
|
-
dispatchKeyboardEvent("keydown", keyCode);
|
|
3672
|
-
}
|
|
3673
|
-
}
|
|
3674
|
-
});
|
|
3675
|
-
activeDirectionsRef.current = newDirections;
|
|
3676
|
-
updateVisuals(newDirections);
|
|
3677
|
-
}, [getKeyCode, updateVisuals]);
|
|
3678
|
-
const handleTouchStart = useCallback((e) => {
|
|
3679
|
-
e.preventDefault();
|
|
3680
|
-
if (activeTouchRef.current !== null) return;
|
|
3681
|
-
const touch = e.changedTouches[0];
|
|
3682
|
-
activeTouchRef.current = touch.identifier;
|
|
3683
|
-
const rect = dpadRef.current?.getBoundingClientRect();
|
|
3684
|
-
if (!rect) return;
|
|
3685
|
-
if (onPositionChange) {
|
|
3686
|
-
drag.checkDragStart(touch.clientX, touch.clientY, rect);
|
|
3687
|
-
}
|
|
3688
|
-
if (!drag.isDragging) {
|
|
3689
|
-
updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
|
|
3690
|
-
}
|
|
3691
|
-
}, [getDirectionsFromTouch, updateDirections, onPositionChange, drag]);
|
|
3692
|
-
const handleTouchMove = useCallback((e) => {
|
|
3693
|
-
e.preventDefault();
|
|
3694
|
-
let touch = null;
|
|
3695
|
-
for (let i = 0; i < e.changedTouches.length; i++) {
|
|
3696
|
-
if (e.changedTouches[i].identifier === activeTouchRef.current) {
|
|
3697
|
-
touch = e.changedTouches[i];
|
|
3698
|
-
break;
|
|
3699
|
-
}
|
|
3700
|
-
}
|
|
3701
|
-
if (!touch) return;
|
|
3702
|
-
if (drag.isDragging) {
|
|
3703
|
-
drag.handleDragMove(touch.clientX, touch.clientY);
|
|
3704
|
-
} else if (onPositionChange) {
|
|
3705
|
-
const startedDrag = drag.checkMoveThreshold(touch.clientX, touch.clientY);
|
|
3706
|
-
if (!startedDrag) {
|
|
3707
|
-
drag.clearDragTimer();
|
|
3708
|
-
const rect = dpadRef.current?.getBoundingClientRect();
|
|
3709
|
-
if (rect) {
|
|
3710
|
-
updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
|
|
3711
|
-
}
|
|
3712
|
-
}
|
|
3713
|
-
} else {
|
|
3714
|
-
const rect = dpadRef.current?.getBoundingClientRect();
|
|
3715
|
-
if (rect) {
|
|
3716
|
-
updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
|
|
3717
|
-
}
|
|
3718
|
-
}
|
|
3719
|
-
}, [drag, getDirectionsFromTouch, updateDirections, onPositionChange]);
|
|
3720
|
-
const handleTouchEnd = useCallback((e) => {
|
|
3721
|
-
e.preventDefault();
|
|
3722
|
-
drag.clearDragTimer();
|
|
3723
|
-
let touchEnded = false;
|
|
3724
|
-
for (let i = 0; i < e.changedTouches.length; i++) {
|
|
3725
|
-
if (e.changedTouches[i].identifier === activeTouchRef.current) {
|
|
3726
|
-
touchEnded = true;
|
|
3727
|
-
break;
|
|
3728
|
-
}
|
|
3729
|
-
}
|
|
3730
|
-
if (touchEnded) {
|
|
3731
|
-
activeTouchRef.current = null;
|
|
3732
|
-
if (drag.isDragging) {
|
|
3733
|
-
drag.handleDragEnd();
|
|
3734
|
-
} else {
|
|
3735
|
-
activeDirectionsRef.current.forEach((dir) => {
|
|
3736
|
-
const keyCode = getKeyCode(dir);
|
|
3737
|
-
if (keyCode) dispatchKeyboardEvent("keyup", keyCode);
|
|
3738
|
-
});
|
|
3739
|
-
activeDirectionsRef.current = /* @__PURE__ */ new Set();
|
|
3740
|
-
updateVisuals(/* @__PURE__ */ new Set());
|
|
3741
|
-
}
|
|
3742
|
-
}
|
|
3743
|
-
}, [getKeyCode, updateVisuals, drag]);
|
|
3744
|
-
useTouchEvents(dpadRef, {
|
|
3745
|
-
onTouchStart: handleTouchStart,
|
|
3746
|
-
onTouchMove: handleTouchMove,
|
|
3747
|
-
onTouchEnd: handleTouchEnd,
|
|
3748
|
-
onTouchCancel: handleTouchEnd
|
|
3749
|
-
}, { cleanup: drag.clearDragTimer });
|
|
3750
|
-
const leftPx = displayX / 100 * containerWidth - size / 2;
|
|
3751
|
-
const topPx = displayY / 100 * containerHeight - size / 2;
|
|
3752
|
-
const dUp = "M 35,5 L 65,5 L 65,35 L 50,50 L 35,35 Z";
|
|
3753
|
-
const dRight = "M 65,35 L 95,35 L 95,65 L 65,65 L 50,50 Z";
|
|
3754
|
-
const dDown = "M 65,65 L 65,95 L 35,95 L 35,65 L 50,50 Z";
|
|
3755
|
-
const dLeft = "M 35,65 L 5,65 L 5,35 L 35,35 L 50,50 Z";
|
|
3756
|
-
return /* @__PURE__ */ jsxs(
|
|
3757
|
-
"div",
|
|
3758
|
-
{
|
|
3759
|
-
ref: dpadRef,
|
|
3760
|
-
className: `absolute pointer-events-auto touch-manipulation select-none ${drag.isDragging ? "opacity-60" : ""}`,
|
|
3761
|
-
style: {
|
|
3762
|
-
top: 0,
|
|
3763
|
-
left: 0,
|
|
3764
|
-
transform: `translate3d(${leftPx}px, ${topPx}px, 0)${drag.isDragging ? " scale(1.05)" : ""}`,
|
|
3765
|
-
width: size,
|
|
3766
|
-
height: size,
|
|
3767
|
-
opacity: isLandscape ? 0.75 : 0.9,
|
|
3768
|
-
WebkitTouchCallout: "none",
|
|
3769
|
-
WebkitUserSelect: "none",
|
|
3770
|
-
touchAction: "none",
|
|
3771
|
-
transition: drag.isDragging ? "none" : "transform 0.1s ease-out"
|
|
3772
|
-
},
|
|
3773
|
-
children: [
|
|
3774
|
-
/* @__PURE__ */ 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"}` }),
|
|
3775
|
-
/* @__PURE__ */ jsxs("svg", { width: "100%", height: "100%", viewBox: "0 0 100 100", className: "drop-shadow-xl relative z-10", children: [
|
|
3776
|
-
/* @__PURE__ */ 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" }),
|
|
3777
|
-
/* @__PURE__ */ 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" }),
|
|
3778
|
-
/* @__PURE__ */ 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" }),
|
|
3779
|
-
/* @__PURE__ */ 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" }),
|
|
3780
|
-
/* @__PURE__ */ jsx(
|
|
3781
|
-
"circle",
|
|
3782
|
-
{
|
|
3783
|
-
ref: centerCircleRef,
|
|
3784
|
-
cx: "50",
|
|
3785
|
-
cy: "50",
|
|
3786
|
-
r: "12",
|
|
3787
|
-
fill: drag.isDragging ? systemColor : "rgba(0,0,0,0.5)",
|
|
3788
|
-
stroke: drag.isDragging ? "#fff" : "rgba(255,255,255,0.3)",
|
|
3789
|
-
strokeWidth: drag.isDragging ? 2 : 1
|
|
3790
|
-
}
|
|
3791
|
-
),
|
|
3792
|
-
/* @__PURE__ */ 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" }),
|
|
3793
|
-
/* @__PURE__ */ 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" }),
|
|
3794
|
-
/* @__PURE__ */ 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" }),
|
|
3795
|
-
/* @__PURE__ */ 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" })
|
|
3796
|
-
] })
|
|
3797
|
-
]
|
|
3798
|
-
}
|
|
3799
|
-
);
|
|
3800
|
-
});
|
|
3801
|
-
var Dpad_default = Dpad;
|
|
3802
|
-
|
|
3803
|
-
// src/components/VirtualController/positioning.ts
|
|
3804
|
-
function adjustButtonPosition(config, context) {
|
|
3805
|
-
const sizeMultiplier = context.isFullscreen ? 1.1 : 1;
|
|
3806
|
-
return {
|
|
3807
|
-
...config,
|
|
3808
|
-
size: Math.floor(config.size * sizeMultiplier)
|
|
3809
|
-
};
|
|
3810
|
-
}
|
|
3811
|
-
var STORAGE_KEY = "virtual-button-positions";
|
|
3812
|
-
function useButtonPositions() {
|
|
3813
|
-
const [landscapePositions, setLandscapePositions] = useState({});
|
|
3814
|
-
const [portraitPositions, setPortraitPositions] = useState({});
|
|
3815
|
-
useEffect(() => {
|
|
3816
|
-
try {
|
|
3817
|
-
const stored = localStorage.getItem(STORAGE_KEY);
|
|
3818
|
-
if (stored) {
|
|
3819
|
-
const parsed = JSON.parse(stored);
|
|
3820
|
-
if (parsed.landscape && parsed.portrait) {
|
|
3821
|
-
setLandscapePositions(parsed.landscape);
|
|
3822
|
-
setPortraitPositions(parsed.portrait);
|
|
3823
|
-
} else {
|
|
3824
|
-
setLandscapePositions(parsed);
|
|
3825
|
-
}
|
|
3826
|
-
}
|
|
3827
|
-
} catch (e) {
|
|
3828
|
-
console.error("Failed to load button positions:", e);
|
|
3829
|
-
}
|
|
3830
|
-
}, []);
|
|
3831
|
-
const savePosition = useCallback((buttonType, x, y, isLandscape) => {
|
|
3832
|
-
if (isLandscape) {
|
|
3833
|
-
setLandscapePositions((prev) => {
|
|
3834
|
-
const updated = { ...prev, [buttonType]: { x, y } };
|
|
3835
|
-
try {
|
|
3836
|
-
const stored = {
|
|
3837
|
-
landscape: updated,
|
|
3838
|
-
portrait: portraitPositions
|
|
3839
|
-
};
|
|
3840
|
-
localStorage.setItem(STORAGE_KEY, JSON.stringify(stored));
|
|
3841
|
-
} catch (e) {
|
|
3842
|
-
console.error("Failed to save button position:", e);
|
|
3843
|
-
}
|
|
3844
|
-
return updated;
|
|
3845
|
-
});
|
|
3846
|
-
} else {
|
|
3847
|
-
setPortraitPositions((prev) => {
|
|
3848
|
-
const updated = { ...prev, [buttonType]: { x, y } };
|
|
3849
|
-
try {
|
|
3850
|
-
const stored = {
|
|
3851
|
-
landscape: landscapePositions,
|
|
3852
|
-
portrait: updated
|
|
3853
|
-
};
|
|
3854
|
-
localStorage.setItem(STORAGE_KEY, JSON.stringify(stored));
|
|
3855
|
-
} catch (e) {
|
|
3856
|
-
console.error("Failed to save button position:", e);
|
|
3857
|
-
}
|
|
3858
|
-
return updated;
|
|
3859
|
-
});
|
|
3860
|
-
}
|
|
3861
|
-
}, [landscapePositions, portraitPositions]);
|
|
3862
|
-
const getPosition = useCallback((buttonType, isLandscape) => {
|
|
3863
|
-
const positions = isLandscape ? landscapePositions : portraitPositions;
|
|
3864
|
-
return positions[buttonType] || null;
|
|
3865
|
-
}, [landscapePositions, portraitPositions]);
|
|
3866
|
-
const resetPositions = useCallback(() => {
|
|
3867
|
-
setLandscapePositions({});
|
|
3868
|
-
setPortraitPositions({});
|
|
3869
|
-
try {
|
|
3870
|
-
localStorage.removeItem(STORAGE_KEY);
|
|
3871
|
-
} catch (e) {
|
|
3872
|
-
console.error("Failed to reset button positions:", e);
|
|
3873
|
-
}
|
|
3874
|
-
}, []);
|
|
3875
|
-
return {
|
|
3876
|
-
landscapePositions,
|
|
3877
|
-
portraitPositions,
|
|
3878
|
-
savePosition,
|
|
3879
|
-
getPosition,
|
|
3880
|
-
resetPositions
|
|
3881
|
-
};
|
|
3882
|
-
}
|
|
3883
3866
|
var STORAGE_KEY2 = "koin-controls-hint-shown";
|
|
3884
3867
|
function ControlsHint({ isVisible }) {
|
|
3885
3868
|
const [show, setShow] = useState(false);
|
|
@@ -3964,7 +3947,7 @@ function LockButton({
|
|
|
3964
3947
|
"button",
|
|
3965
3948
|
{
|
|
3966
3949
|
onClick: onToggle,
|
|
3967
|
-
className: "
|
|
3950
|
+
className: "pointer-events-auto p-2 rounded-full backdrop-blur-sm transition-all active:scale-95",
|
|
3968
3951
|
style: {
|
|
3969
3952
|
backgroundColor: isLocked ? "rgba(0,0,0,0.6)" : `${systemColor}20`,
|
|
3970
3953
|
border: `1px solid ${isLocked ? "rgba(255,255,255,0.2)" : systemColor}`
|
|
@@ -3978,18 +3961,164 @@ function LockButton({
|
|
|
3978
3961
|
}
|
|
3979
3962
|
)
|
|
3980
3963
|
}
|
|
3981
|
-
);
|
|
3964
|
+
);
|
|
3965
|
+
}
|
|
3966
|
+
function HoldButton({
|
|
3967
|
+
isActive,
|
|
3968
|
+
onToggle,
|
|
3969
|
+
systemColor = "#00FF41"
|
|
3970
|
+
}) {
|
|
3971
|
+
const Icon = isActive ? StopCircle : Hand;
|
|
3972
|
+
return /* @__PURE__ */ jsx(
|
|
3973
|
+
"button",
|
|
3974
|
+
{
|
|
3975
|
+
onClick: onToggle,
|
|
3976
|
+
className: "pointer-events-auto p-2 rounded-full backdrop-blur-sm transition-all active:scale-95",
|
|
3977
|
+
style: {
|
|
3978
|
+
backgroundColor: isActive ? "rgba(0,0,0,0.6)" : `${systemColor}20`,
|
|
3979
|
+
border: `1px solid ${isActive ? "rgba(255,255,255,0.4)" : systemColor}`
|
|
3980
|
+
},
|
|
3981
|
+
"aria-label": isActive ? "Disable Hold Mode" : "Enable Button Hold Mode",
|
|
3982
|
+
children: /* @__PURE__ */ jsx(
|
|
3983
|
+
Icon,
|
|
3984
|
+
{
|
|
3985
|
+
size: 18,
|
|
3986
|
+
style: { color: isActive ? "#fff" : systemColor }
|
|
3987
|
+
}
|
|
3988
|
+
)
|
|
3989
|
+
}
|
|
3990
|
+
);
|
|
3991
|
+
}
|
|
3992
|
+
function TurboButton({
|
|
3993
|
+
isActive,
|
|
3994
|
+
onToggle,
|
|
3995
|
+
systemColor = "#00FF41"
|
|
3996
|
+
}) {
|
|
3997
|
+
const Icon = isActive ? ZapOff : Zap;
|
|
3998
|
+
return /* @__PURE__ */ jsx(
|
|
3999
|
+
"button",
|
|
4000
|
+
{
|
|
4001
|
+
onClick: onToggle,
|
|
4002
|
+
className: "pointer-events-auto p-2 rounded-full backdrop-blur-sm transition-all active:scale-95",
|
|
4003
|
+
style: {
|
|
4004
|
+
backgroundColor: isActive ? "rgba(255,200,0,0.3)" : `${systemColor}20`,
|
|
4005
|
+
border: `1px solid ${isActive ? "rgba(255,200,0,0.6)" : systemColor}`
|
|
4006
|
+
},
|
|
4007
|
+
"aria-label": isActive ? "Disable Turbo Mode" : "Enable Turbo Fire Mode",
|
|
4008
|
+
children: /* @__PURE__ */ jsx(
|
|
4009
|
+
Icon,
|
|
4010
|
+
{
|
|
4011
|
+
size: 18,
|
|
4012
|
+
style: { color: isActive ? "#FFC800" : systemColor }
|
|
4013
|
+
}
|
|
4014
|
+
)
|
|
4015
|
+
}
|
|
4016
|
+
);
|
|
4017
|
+
}
|
|
4018
|
+
var MODE_CONFIG = {
|
|
4019
|
+
hold: {
|
|
4020
|
+
Icon: Hand,
|
|
4021
|
+
title: "Hold Mode",
|
|
4022
|
+
instruction: "Tap a button to hold it",
|
|
4023
|
+
buttonIcon: Hand,
|
|
4024
|
+
buttonColor: "#22c55e"
|
|
4025
|
+
// green
|
|
4026
|
+
},
|
|
4027
|
+
turbo: {
|
|
4028
|
+
Icon: Zap,
|
|
4029
|
+
title: "Turbo Mode",
|
|
4030
|
+
instruction: "Tap a button for rapid fire",
|
|
4031
|
+
buttonIcon: Zap,
|
|
4032
|
+
buttonColor: "#fbbf24"
|
|
4033
|
+
// yellow
|
|
4034
|
+
}
|
|
4035
|
+
};
|
|
4036
|
+
function ModeOverlay({
|
|
4037
|
+
mode,
|
|
4038
|
+
heldButtons,
|
|
4039
|
+
turboButtons,
|
|
4040
|
+
systemColor = "#00FF41",
|
|
4041
|
+
onExit
|
|
4042
|
+
}) {
|
|
4043
|
+
if (!mode) return null;
|
|
4044
|
+
const config = MODE_CONFIG[mode];
|
|
4045
|
+
const buttons = mode === "hold" ? heldButtons : turboButtons;
|
|
4046
|
+
const buttonArray = Array.from(buttons);
|
|
4047
|
+
const { Icon, buttonIcon: ButtonIcon } = config;
|
|
4048
|
+
return /* @__PURE__ */ jsx("div", { className: "fixed top-0 left-0 right-0 z-40 flex justify-center pt-4 pointer-events-none", children: /* @__PURE__ */ jsxs(
|
|
4049
|
+
"div",
|
|
4050
|
+
{
|
|
4051
|
+
className: "relative px-5 py-3 rounded-2xl backdrop-blur-md border pointer-events-auto",
|
|
4052
|
+
style: {
|
|
4053
|
+
backgroundColor: "rgba(0,0,0,0.85)",
|
|
4054
|
+
borderColor: `${systemColor}60`,
|
|
4055
|
+
boxShadow: `0 4px 20px ${systemColor}30`
|
|
4056
|
+
},
|
|
4057
|
+
children: [
|
|
4058
|
+
/* @__PURE__ */ jsx(
|
|
4059
|
+
"button",
|
|
4060
|
+
{
|
|
4061
|
+
onClick: onExit,
|
|
4062
|
+
className: "absolute -top-2 -right-2 w-7 h-7 rounded-full flex items-center justify-center transition-transform active:scale-90",
|
|
4063
|
+
style: {
|
|
4064
|
+
backgroundColor: "#ef4444",
|
|
4065
|
+
border: "2px solid rgba(255,255,255,0.3)"
|
|
4066
|
+
},
|
|
4067
|
+
"aria-label": "Exit mode",
|
|
4068
|
+
children: /* @__PURE__ */ jsx(X, { size: 14, color: "white", strokeWidth: 3 })
|
|
4069
|
+
}
|
|
4070
|
+
),
|
|
4071
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
4072
|
+
/* @__PURE__ */ jsx(
|
|
4073
|
+
"div",
|
|
4074
|
+
{
|
|
4075
|
+
className: "w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0",
|
|
4076
|
+
style: { backgroundColor: `${systemColor}30` },
|
|
4077
|
+
children: /* @__PURE__ */ jsx(Icon, { size: 20, style: { color: systemColor } })
|
|
4078
|
+
}
|
|
4079
|
+
),
|
|
4080
|
+
/* @__PURE__ */ jsxs("div", { className: "text-left", children: [
|
|
4081
|
+
/* @__PURE__ */ jsx("h3", { className: "text-white font-bold text-sm", children: config.title }),
|
|
4082
|
+
/* @__PURE__ */ jsx("p", { className: "text-white/60 text-xs", children: config.instruction })
|
|
4083
|
+
] })
|
|
4084
|
+
] }),
|
|
4085
|
+
buttonArray.length > 0 && /* @__PURE__ */ 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__ */ jsxs(
|
|
4086
|
+
"span",
|
|
4087
|
+
{
|
|
4088
|
+
className: "px-2 py-0.5 rounded-full text-[10px] font-bold uppercase flex items-center gap-1",
|
|
4089
|
+
style: {
|
|
4090
|
+
backgroundColor: `${config.buttonColor}25`,
|
|
4091
|
+
color: config.buttonColor
|
|
4092
|
+
},
|
|
4093
|
+
children: [
|
|
4094
|
+
/* @__PURE__ */ jsx(ButtonIcon, { size: 10 }),
|
|
4095
|
+
button
|
|
4096
|
+
]
|
|
4097
|
+
},
|
|
4098
|
+
button
|
|
4099
|
+
)) })
|
|
4100
|
+
]
|
|
4101
|
+
}
|
|
4102
|
+
) });
|
|
3982
4103
|
}
|
|
3983
4104
|
var LOCK_KEY = "koin-controls-locked";
|
|
3984
4105
|
function VirtualController({
|
|
3985
4106
|
system,
|
|
3986
4107
|
isRunning,
|
|
3987
4108
|
controls,
|
|
3988
|
-
systemColor = "#00FF41"
|
|
4109
|
+
systemColor = "#00FF41",
|
|
3989
4110
|
// Default retro green
|
|
4111
|
+
hapticsEnabled = true,
|
|
4112
|
+
onButtonDown,
|
|
4113
|
+
onButtonUp,
|
|
4114
|
+
onPause,
|
|
4115
|
+
onResume
|
|
3990
4116
|
}) {
|
|
3991
4117
|
const { isMobile, isLandscape, isPortrait } = useMobile();
|
|
3992
4118
|
const [pressedButtons, setPressedButtons] = useState(/* @__PURE__ */ new Set());
|
|
4119
|
+
const pressedButtonsRef = useRef(/* @__PURE__ */ new Set());
|
|
4120
|
+
const [heldButtons, setHeldButtons] = useState(/* @__PURE__ */ new Set());
|
|
4121
|
+
const [isHoldMode, setIsHoldMode] = useState(false);
|
|
3993
4122
|
const [containerSize, setContainerSize] = useState({ width: 0, height: 0 });
|
|
3994
4123
|
const [isFullscreenState, setIsFullscreenState] = useState(false);
|
|
3995
4124
|
const [isLocked, setIsLocked] = useState(true);
|
|
@@ -4007,6 +4136,28 @@ function VirtualController({
|
|
|
4007
4136
|
return newValue;
|
|
4008
4137
|
});
|
|
4009
4138
|
}, []);
|
|
4139
|
+
const toggleHoldMode = useCallback(() => {
|
|
4140
|
+
setIsHoldMode((prev) => {
|
|
4141
|
+
if (prev) {
|
|
4142
|
+
onResume();
|
|
4143
|
+
} else {
|
|
4144
|
+
onPause();
|
|
4145
|
+
}
|
|
4146
|
+
return !prev;
|
|
4147
|
+
});
|
|
4148
|
+
}, [onPause, onResume]);
|
|
4149
|
+
const [isTurboMode, setIsTurboMode] = useState(false);
|
|
4150
|
+
const [turboButtons, setTurboButtons] = useState(/* @__PURE__ */ new Set());
|
|
4151
|
+
const toggleTurboMode = useCallback(() => {
|
|
4152
|
+
setIsTurboMode((prev) => {
|
|
4153
|
+
if (prev) {
|
|
4154
|
+
onResume();
|
|
4155
|
+
} else {
|
|
4156
|
+
onPause();
|
|
4157
|
+
}
|
|
4158
|
+
return !prev;
|
|
4159
|
+
});
|
|
4160
|
+
}, [onPause, onResume]);
|
|
4010
4161
|
const layout = getLayoutForSystem(system);
|
|
4011
4162
|
const visibleButtons = layout.buttons.filter((btn) => {
|
|
4012
4163
|
if (isPortrait) {
|
|
@@ -4076,9 +4227,9 @@ function VirtualController({
|
|
|
4076
4227
|
return;
|
|
4077
4228
|
}
|
|
4078
4229
|
setPressedButtons((prev) => new Set(prev).add(buttonType));
|
|
4079
|
-
|
|
4230
|
+
onButtonDown(buttonType);
|
|
4080
4231
|
setTimeout(() => {
|
|
4081
|
-
|
|
4232
|
+
onButtonUp(buttonType);
|
|
4082
4233
|
setPressedButtons((prev) => {
|
|
4083
4234
|
const next = new Set(prev);
|
|
4084
4235
|
next.delete(buttonType);
|
|
@@ -4086,7 +4237,7 @@ function VirtualController({
|
|
|
4086
4237
|
});
|
|
4087
4238
|
}, 100);
|
|
4088
4239
|
},
|
|
4089
|
-
[isRunning, getButtonKeyboardCode]
|
|
4240
|
+
[isRunning, getButtonKeyboardCode, onButtonDown, onButtonUp]
|
|
4090
4241
|
);
|
|
4091
4242
|
const handlePressDown = useCallback(
|
|
4092
4243
|
(buttonType) => {
|
|
@@ -4095,15 +4246,70 @@ function VirtualController({
|
|
|
4095
4246
|
if (isSystemButton) return;
|
|
4096
4247
|
const keyboardCode = getButtonKeyboardCode(buttonType);
|
|
4097
4248
|
if (!keyboardCode) return;
|
|
4249
|
+
if (isHoldMode) {
|
|
4250
|
+
const isHeld = heldButtons.has(buttonType);
|
|
4251
|
+
if (isHeld) {
|
|
4252
|
+
setHeldButtons((prev) => {
|
|
4253
|
+
const next = new Set(prev);
|
|
4254
|
+
next.delete(buttonType);
|
|
4255
|
+
return next;
|
|
4256
|
+
});
|
|
4257
|
+
onButtonUp(buttonType);
|
|
4258
|
+
if (hapticsEnabled && navigator.vibrate) navigator.vibrate(10);
|
|
4259
|
+
} else {
|
|
4260
|
+
setHeldButtons((prev) => {
|
|
4261
|
+
setTurboButtons((turboPrev) => {
|
|
4262
|
+
if (turboPrev.has(buttonType)) {
|
|
4263
|
+
const nextTurbo = new Set(turboPrev);
|
|
4264
|
+
nextTurbo.delete(buttonType);
|
|
4265
|
+
return nextTurbo;
|
|
4266
|
+
}
|
|
4267
|
+
return turboPrev;
|
|
4268
|
+
});
|
|
4269
|
+
return new Set(prev).add(buttonType);
|
|
4270
|
+
});
|
|
4271
|
+
onButtonDown(buttonType);
|
|
4272
|
+
if (hapticsEnabled && navigator.vibrate) navigator.vibrate([10, 30, 10]);
|
|
4273
|
+
}
|
|
4274
|
+
return;
|
|
4275
|
+
}
|
|
4276
|
+
if (isTurboMode) {
|
|
4277
|
+
const isTurbo = turboButtons.has(buttonType);
|
|
4278
|
+
if (isTurbo) {
|
|
4279
|
+
setTurboButtons((prev) => {
|
|
4280
|
+
const next = new Set(prev);
|
|
4281
|
+
next.delete(buttonType);
|
|
4282
|
+
return next;
|
|
4283
|
+
});
|
|
4284
|
+
if (hapticsEnabled && navigator.vibrate) navigator.vibrate(10);
|
|
4285
|
+
} else {
|
|
4286
|
+
setTurboButtons((prev) => {
|
|
4287
|
+
setHeldButtons((holdPrev) => {
|
|
4288
|
+
if (holdPrev.has(buttonType)) {
|
|
4289
|
+
const nextHold = new Set(holdPrev);
|
|
4290
|
+
nextHold.delete(buttonType);
|
|
4291
|
+
onButtonUp(buttonType);
|
|
4292
|
+
return nextHold;
|
|
4293
|
+
}
|
|
4294
|
+
return holdPrev;
|
|
4295
|
+
});
|
|
4296
|
+
return new Set(prev).add(buttonType);
|
|
4297
|
+
});
|
|
4298
|
+
if (hapticsEnabled && navigator.vibrate) navigator.vibrate([5, 10, 5]);
|
|
4299
|
+
}
|
|
4300
|
+
return;
|
|
4301
|
+
}
|
|
4098
4302
|
setPressedButtons((prev) => {
|
|
4099
4303
|
if (prev.has(buttonType)) return prev;
|
|
4100
4304
|
const next = new Set(prev);
|
|
4101
4305
|
next.add(buttonType);
|
|
4102
4306
|
return next;
|
|
4103
4307
|
});
|
|
4104
|
-
|
|
4308
|
+
if (!heldButtons.has(buttonType)) {
|
|
4309
|
+
onButtonDown(buttonType);
|
|
4310
|
+
}
|
|
4105
4311
|
},
|
|
4106
|
-
[isRunning, getButtonKeyboardCode]
|
|
4312
|
+
[isRunning, getButtonKeyboardCode, isHoldMode, isTurboMode, heldButtons, hapticsEnabled, onButtonDown, onButtonUp]
|
|
4107
4313
|
);
|
|
4108
4314
|
const handleRelease = useCallback(
|
|
4109
4315
|
(buttonType) => {
|
|
@@ -4111,15 +4317,29 @@ function VirtualController({
|
|
|
4111
4317
|
if (isSystemButton) return;
|
|
4112
4318
|
const keyboardCode = getButtonKeyboardCode(buttonType);
|
|
4113
4319
|
if (!keyboardCode) return;
|
|
4320
|
+
if (isHoldMode) return;
|
|
4321
|
+
if (heldButtons.has(buttonType)) {
|
|
4322
|
+
if (!isHoldMode) {
|
|
4323
|
+
setHeldButtons((prev) => {
|
|
4324
|
+
const next = new Set(prev);
|
|
4325
|
+
next.delete(buttonType);
|
|
4326
|
+
return next;
|
|
4327
|
+
});
|
|
4328
|
+
onButtonUp(buttonType);
|
|
4329
|
+
if (hapticsEnabled && navigator.vibrate) navigator.vibrate(10);
|
|
4330
|
+
}
|
|
4331
|
+
return;
|
|
4332
|
+
}
|
|
4333
|
+
if (isTurboMode) return;
|
|
4114
4334
|
setPressedButtons((prev) => {
|
|
4115
4335
|
if (!prev.has(buttonType)) return prev;
|
|
4116
4336
|
const next = new Set(prev);
|
|
4117
4337
|
next.delete(buttonType);
|
|
4118
4338
|
return next;
|
|
4119
4339
|
});
|
|
4120
|
-
|
|
4340
|
+
onButtonUp(buttonType);
|
|
4121
4341
|
},
|
|
4122
|
-
[getButtonKeyboardCode]
|
|
4342
|
+
[getButtonKeyboardCode, isHoldMode, isTurboMode, heldButtons, hapticsEnabled, onButtonUp]
|
|
4123
4343
|
);
|
|
4124
4344
|
useEffect(() => {
|
|
4125
4345
|
if (!isRunning && pressedButtons.size > 0) {
|
|
@@ -4131,6 +4351,22 @@ function VirtualController({
|
|
|
4131
4351
|
setPressedButtons(/* @__PURE__ */ new Set());
|
|
4132
4352
|
}
|
|
4133
4353
|
}, [isRunning, pressedButtons, handleRelease]);
|
|
4354
|
+
const TURBO_RATE = 15;
|
|
4355
|
+
useEffect(() => {
|
|
4356
|
+
pressedButtonsRef.current = pressedButtons;
|
|
4357
|
+
}, [pressedButtons]);
|
|
4358
|
+
useEffect(() => {
|
|
4359
|
+
if (isTurboMode || turboButtons.size === 0) return;
|
|
4360
|
+
const interval = setInterval(() => {
|
|
4361
|
+
turboButtons.forEach((buttonType) => {
|
|
4362
|
+
if (pressedButtonsRef.current.has(buttonType)) {
|
|
4363
|
+
onButtonDown(buttonType);
|
|
4364
|
+
setTimeout(() => onButtonUp(buttonType), 25);
|
|
4365
|
+
}
|
|
4366
|
+
});
|
|
4367
|
+
}, 1e3 / TURBO_RATE);
|
|
4368
|
+
return () => clearInterval(interval);
|
|
4369
|
+
}, [isTurboMode, turboButtons, onButtonDown, onButtonUp]);
|
|
4134
4370
|
const memoizedButtonElements = useMemo(() => {
|
|
4135
4371
|
const width = containerSize.width || (typeof window !== "undefined" ? window.innerWidth : 0);
|
|
4136
4372
|
const height = containerSize.height || (typeof window !== "undefined" ? window.innerHeight : 0);
|
|
@@ -4161,14 +4397,32 @@ function VirtualController({
|
|
|
4161
4397
|
className: "fixed inset-0 z-30 pointer-events-none",
|
|
4162
4398
|
style: { touchAction: "none" },
|
|
4163
4399
|
children: [
|
|
4164
|
-
/* @__PURE__ */
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4400
|
+
/* @__PURE__ */ jsxs("div", { className: "fixed top-4 left-1/2 -translate-x-1/2 z-40 flex gap-4", children: [
|
|
4401
|
+
/* @__PURE__ */ jsx(
|
|
4402
|
+
LockButton,
|
|
4403
|
+
{
|
|
4404
|
+
isLocked,
|
|
4405
|
+
onToggle: toggleLock,
|
|
4406
|
+
systemColor
|
|
4407
|
+
}
|
|
4408
|
+
),
|
|
4409
|
+
/* @__PURE__ */ jsx(
|
|
4410
|
+
HoldButton,
|
|
4411
|
+
{
|
|
4412
|
+
isActive: isHoldMode,
|
|
4413
|
+
onToggle: toggleHoldMode,
|
|
4414
|
+
systemColor
|
|
4415
|
+
}
|
|
4416
|
+
),
|
|
4417
|
+
/* @__PURE__ */ jsx(
|
|
4418
|
+
TurboButton,
|
|
4419
|
+
{
|
|
4420
|
+
isActive: isTurboMode,
|
|
4421
|
+
onToggle: toggleTurboMode,
|
|
4422
|
+
systemColor
|
|
4423
|
+
}
|
|
4424
|
+
)
|
|
4425
|
+
] }),
|
|
4172
4426
|
/* @__PURE__ */ jsx(
|
|
4173
4427
|
Dpad_default,
|
|
4174
4428
|
{
|
|
@@ -4177,18 +4431,20 @@ function VirtualController({
|
|
|
4177
4431
|
y: finalDpadY,
|
|
4178
4432
|
containerWidth: containerSize.width || window.innerWidth,
|
|
4179
4433
|
containerHeight: containerSize.height || window.innerHeight,
|
|
4180
|
-
controls,
|
|
4181
4434
|
systemColor,
|
|
4182
4435
|
isLandscape,
|
|
4183
4436
|
customPosition: getPosition("up", isLandscape),
|
|
4184
|
-
onPositionChange: isLocked ? void 0 : (x, y) => savePosition("up", x, y, isLandscape)
|
|
4437
|
+
onPositionChange: isLocked ? void 0 : (x, y) => savePosition("up", x, y, isLandscape),
|
|
4438
|
+
hapticsEnabled,
|
|
4439
|
+
onButtonDown,
|
|
4440
|
+
onButtonUp
|
|
4185
4441
|
}
|
|
4186
4442
|
),
|
|
4187
4443
|
memoizedButtonElements.filter(({ buttonConfig }) => !DPAD_TYPES.includes(buttonConfig.type)).map(({ buttonConfig, adjustedConfig, customPosition, width, height }) => /* @__PURE__ */ jsx(
|
|
4188
4444
|
VirtualButton_default,
|
|
4189
4445
|
{
|
|
4190
4446
|
config: adjustedConfig,
|
|
4191
|
-
isPressed: pressedButtons.has(buttonConfig.type),
|
|
4447
|
+
isPressed: pressedButtons.has(buttonConfig.type) || heldButtons.has(buttonConfig.type),
|
|
4192
4448
|
onPress: handlePress,
|
|
4193
4449
|
onPressDown: handlePressDown,
|
|
4194
4450
|
onRelease: handleRelease,
|
|
@@ -4197,10 +4453,24 @@ function VirtualController({
|
|
|
4197
4453
|
customPosition,
|
|
4198
4454
|
onPositionChange: isLocked ? void 0 : (x, y) => savePosition(buttonConfig.type, x, y, isLandscape),
|
|
4199
4455
|
isLandscape,
|
|
4200
|
-
console: layout.console
|
|
4456
|
+
console: layout.console,
|
|
4457
|
+
hapticsEnabled,
|
|
4458
|
+
mode: isHoldMode ? "hold" : isTurboMode ? "turbo" : "normal",
|
|
4459
|
+
isHeld: heldButtons.has(buttonConfig.type),
|
|
4460
|
+
isInTurbo: turboButtons.has(buttonConfig.type)
|
|
4201
4461
|
},
|
|
4202
4462
|
buttonConfig.type
|
|
4203
4463
|
)),
|
|
4464
|
+
(isHoldMode || isTurboMode) && /* @__PURE__ */ jsx(
|
|
4465
|
+
ModeOverlay,
|
|
4466
|
+
{
|
|
4467
|
+
mode: isHoldMode ? "hold" : "turbo",
|
|
4468
|
+
heldButtons,
|
|
4469
|
+
turboButtons,
|
|
4470
|
+
systemColor,
|
|
4471
|
+
onExit: isHoldMode ? toggleHoldMode : toggleTurboMode
|
|
4472
|
+
}
|
|
4473
|
+
),
|
|
4204
4474
|
/* @__PURE__ */ jsx(ControlsHint, { isVisible: isRunning })
|
|
4205
4475
|
]
|
|
4206
4476
|
}
|
|
@@ -4887,7 +5157,8 @@ function CheatModal({
|
|
|
4887
5157
|
cheats,
|
|
4888
5158
|
activeCheats,
|
|
4889
5159
|
onToggle,
|
|
4890
|
-
onClose
|
|
5160
|
+
onClose,
|
|
5161
|
+
onAddManualCheat
|
|
4891
5162
|
}) {
|
|
4892
5163
|
const t = useKoinTranslation();
|
|
4893
5164
|
const [copiedId, setCopiedId] = React2.useState(null);
|
|
@@ -4905,54 +5176,111 @@ function CheatModal({
|
|
|
4905
5176
|
subtitle: t.modals.cheats.available.replace("{{count}}", cheats.length.toString()),
|
|
4906
5177
|
icon: /* @__PURE__ */ jsx(Code, { size: 24, className: "text-purple-400" }),
|
|
4907
5178
|
footer: /* @__PURE__ */ 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 }),
|
|
4908
|
-
children: /* @__PURE__ */
|
|
4909
|
-
/* @__PURE__ */
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
flex-shrink-0 w-6 h-6 rounded border-2 flex items-center justify-center transition-all
|
|
4928
|
-
${isActive ? "border-purple-500 bg-purple-500" : "border-gray-600 group-hover:border-gray-400"}
|
|
4929
|
-
`,
|
|
4930
|
-
children: isActive && /* @__PURE__ */ jsx(Check, { size: 14, className: "text-white" })
|
|
5179
|
+
children: /* @__PURE__ */ jsxs("div", { className: "p-4 space-y-4 max-h-[400px] overflow-y-auto", children: [
|
|
5180
|
+
onAddManualCheat && /* @__PURE__ */ jsxs("div", { className: "p-3 bg-white/5 rounded-lg border border-white/10 space-y-3", children: [
|
|
5181
|
+
/* @__PURE__ */ jsx("div", { className: "flex gap-2", children: /* @__PURE__ */ jsx(
|
|
5182
|
+
"input",
|
|
5183
|
+
{
|
|
5184
|
+
type: "text",
|
|
5185
|
+
placeholder: t.modals.cheats.codePlaceholder || "Enter cheat code",
|
|
5186
|
+
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",
|
|
5187
|
+
onKeyDown: (e) => {
|
|
5188
|
+
if (e.key === "Enter") {
|
|
5189
|
+
const input = e.currentTarget;
|
|
5190
|
+
const code = input.value.trim();
|
|
5191
|
+
if (code) {
|
|
5192
|
+
const descInput = input.parentElement?.nextElementSibling?.querySelector("input");
|
|
5193
|
+
const desc = descInput?.value.trim() || "Custom Cheat";
|
|
5194
|
+
onAddManualCheat(code, desc);
|
|
5195
|
+
input.value = "";
|
|
5196
|
+
if (descInput) descInput.value = "";
|
|
5197
|
+
}
|
|
4931
5198
|
}
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
5199
|
+
}
|
|
5200
|
+
}
|
|
5201
|
+
) }),
|
|
5202
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
5203
|
+
/* @__PURE__ */ jsx(
|
|
5204
|
+
"input",
|
|
5205
|
+
{
|
|
5206
|
+
type: "text",
|
|
5207
|
+
placeholder: t.modals.cheats.descPlaceholder || "Description (optional)",
|
|
5208
|
+
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"
|
|
5209
|
+
}
|
|
5210
|
+
),
|
|
5211
|
+
/* @__PURE__ */ jsx(
|
|
5212
|
+
"button",
|
|
5213
|
+
{
|
|
5214
|
+
onClick: (e) => {
|
|
5215
|
+
const descInput = e.currentTarget.previousElementSibling;
|
|
5216
|
+
const codeInput = e.currentTarget.parentElement?.previousElementSibling?.querySelector("input");
|
|
5217
|
+
const code = codeInput?.value.trim();
|
|
5218
|
+
const desc = descInput?.value.trim() || "Custom Cheat";
|
|
5219
|
+
if (code) {
|
|
5220
|
+
onAddManualCheat(code, desc);
|
|
5221
|
+
codeInput.value = "";
|
|
5222
|
+
descInput.value = "";
|
|
5223
|
+
}
|
|
5224
|
+
},
|
|
5225
|
+
className: "px-3 py-2 bg-purple-600 hover:bg-purple-500 text-white text-sm rounded transition-colors",
|
|
5226
|
+
children: t.modals.cheats.add || "Add"
|
|
5227
|
+
}
|
|
5228
|
+
)
|
|
5229
|
+
] })
|
|
5230
|
+
] }),
|
|
5231
|
+
cheats.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "text-center py-12 text-gray-500", children: [
|
|
5232
|
+
/* @__PURE__ */ jsx(Code, { size: 48, className: "mx-auto mb-3 opacity-50" }),
|
|
5233
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium", children: t.modals.cheats.emptyTitle }),
|
|
5234
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm mt-1", children: t.modals.cheats.emptyDesc })
|
|
5235
|
+
] }) : /* @__PURE__ */ jsx("div", { className: "space-y-2", children: cheats.map((cheat) => {
|
|
5236
|
+
const isActive = activeCheats.has(cheat.id);
|
|
5237
|
+
const isManual = cheat.source === "manual";
|
|
5238
|
+
return /* @__PURE__ */ jsxs(
|
|
5239
|
+
"div",
|
|
5240
|
+
{
|
|
5241
|
+
className: `
|
|
5242
|
+
group flex items-start gap-4 p-4 rounded-lg border transition-all cursor-pointer
|
|
5243
|
+
${isActive ? "border-purple-500/50 bg-purple-500/10" : "border-white/10 bg-white/5 hover:border-white/20 hover:bg-white/10"}
|
|
5244
|
+
`,
|
|
5245
|
+
onClick: () => onToggle(cheat.id),
|
|
5246
|
+
children: [
|
|
5247
|
+
/* @__PURE__ */ jsx(
|
|
5248
|
+
"div",
|
|
5249
|
+
{
|
|
5250
|
+
className: `
|
|
5251
|
+
flex-shrink-0 w-6 h-6 rounded border-2 flex items-center justify-center transition-all
|
|
5252
|
+
${isActive ? "border-purple-500 bg-purple-500" : "border-gray-600 group-hover:border-gray-400"}
|
|
5253
|
+
`,
|
|
5254
|
+
children: isActive && /* @__PURE__ */ jsx(Check, { size: 14, className: "text-white" })
|
|
5255
|
+
}
|
|
5256
|
+
),
|
|
5257
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
5258
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
5259
|
+
isManual && /* @__PURE__ */ 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" }),
|
|
5260
|
+
/* @__PURE__ */ jsx("p", { className: `font-medium ${isActive ? "text-purple-300" : "text-white"}`, children: cheat.description })
|
|
5261
|
+
] }),
|
|
5262
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-2", children: [
|
|
5263
|
+
/* @__PURE__ */ 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 }),
|
|
5264
|
+
/* @__PURE__ */ jsx(
|
|
5265
|
+
"button",
|
|
5266
|
+
{
|
|
5267
|
+
onClick: (e) => {
|
|
5268
|
+
e.stopPropagation();
|
|
5269
|
+
handleCopy(cheat.code, cheat.id);
|
|
5270
|
+
},
|
|
5271
|
+
className: "p-1.5 rounded hover:bg-white/10 text-gray-500 hover:text-white transition-colors",
|
|
5272
|
+
title: t.modals.cheats.copy,
|
|
5273
|
+
children: copiedId === cheat.id ? /* @__PURE__ */ jsx(Check, { size: 14, className: "text-green-400" }) : /* @__PURE__ */ jsx(Copy, { size: 14 })
|
|
5274
|
+
}
|
|
5275
|
+
)
|
|
5276
|
+
] })
|
|
4949
5277
|
] })
|
|
4950
|
-
]
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
)
|
|
4955
|
-
|
|
5278
|
+
]
|
|
5279
|
+
},
|
|
5280
|
+
cheat.id
|
|
5281
|
+
);
|
|
5282
|
+
}) })
|
|
5283
|
+
] })
|
|
4956
5284
|
}
|
|
4957
5285
|
);
|
|
4958
5286
|
}
|
|
@@ -5284,7 +5612,9 @@ function SettingsModal({
|
|
|
5284
5612
|
onClose,
|
|
5285
5613
|
currentLanguage,
|
|
5286
5614
|
onLanguageChange,
|
|
5287
|
-
systemColor = "#00FF41"
|
|
5615
|
+
systemColor = "#00FF41",
|
|
5616
|
+
hapticsEnabled,
|
|
5617
|
+
onToggleHaptics
|
|
5288
5618
|
}) {
|
|
5289
5619
|
const t = useKoinTranslation();
|
|
5290
5620
|
const languages = [
|
|
@@ -5308,30 +5638,57 @@ function SettingsModal({
|
|
|
5308
5638
|
children: t.modals.shortcuts.pressEsc
|
|
5309
5639
|
}
|
|
5310
5640
|
),
|
|
5311
|
-
children: /* @__PURE__ */
|
|
5312
|
-
/* @__PURE__ */ jsxs("div", { className: "
|
|
5313
|
-
/* @__PURE__ */
|
|
5314
|
-
|
|
5641
|
+
children: /* @__PURE__ */ jsxs("div", { className: "p-6 space-y-6", children: [
|
|
5642
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
5643
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm font-medium text-gray-400", children: [
|
|
5644
|
+
/* @__PURE__ */ jsx(Globe, { size: 16 }),
|
|
5645
|
+
/* @__PURE__ */ jsx("span", { children: t.settings.language })
|
|
5646
|
+
] }),
|
|
5647
|
+
/* @__PURE__ */ jsx("div", { className: "grid gap-2", children: languages.map((lang) => {
|
|
5648
|
+
const isActive = currentLanguage === lang.code;
|
|
5649
|
+
return /* @__PURE__ */ jsxs(
|
|
5650
|
+
"button",
|
|
5651
|
+
{
|
|
5652
|
+
onClick: () => onLanguageChange(lang.code),
|
|
5653
|
+
className: `
|
|
5654
|
+
flex items-center justify-between px-4 py-3 rounded-lg border transition-all
|
|
5655
|
+
${isActive ? "bg-white/10 border-white/20 text-white" : "bg-black/20 border-transparent text-gray-400 hover:bg-white/5 hover:text-white"}
|
|
5656
|
+
`,
|
|
5657
|
+
children: [
|
|
5658
|
+
/* @__PURE__ */ jsx("span", { children: lang.name }),
|
|
5659
|
+
isActive && /* @__PURE__ */ jsx(Check, { size: 16, style: { color: systemColor } })
|
|
5660
|
+
]
|
|
5661
|
+
},
|
|
5662
|
+
lang.code
|
|
5663
|
+
);
|
|
5664
|
+
}) })
|
|
5315
5665
|
] }),
|
|
5316
|
-
/* @__PURE__ */
|
|
5317
|
-
|
|
5318
|
-
|
|
5666
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
5667
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm font-medium text-gray-400", children: [
|
|
5668
|
+
/* @__PURE__ */ jsx(Zap, { size: 16 }),
|
|
5669
|
+
/* @__PURE__ */ jsx("span", { children: t.settings.haptics })
|
|
5670
|
+
] }),
|
|
5671
|
+
/* @__PURE__ */ jsxs(
|
|
5319
5672
|
"button",
|
|
5320
5673
|
{
|
|
5321
|
-
onClick:
|
|
5674
|
+
onClick: onToggleHaptics,
|
|
5322
5675
|
className: `
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5676
|
+
w-full flex items-center justify-between px-4 py-3 rounded-lg border transition-all
|
|
5677
|
+
${hapticsEnabled ? "bg-white/10 border-white/20 text-white" : "bg-black/20 border-transparent text-gray-400 hover:bg-white/5 hover:text-white"}
|
|
5678
|
+
`,
|
|
5326
5679
|
children: [
|
|
5327
|
-
/* @__PURE__ */ jsx("span", { children:
|
|
5328
|
-
|
|
5680
|
+
/* @__PURE__ */ jsx("span", { children: t.settings.enableHaptics }),
|
|
5681
|
+
/* @__PURE__ */ jsx("div", { className: `w-10 h-6 rounded-full p-1 transition-colors ${hapticsEnabled ? "bg-[#00FF41]" : "bg-gray-700"}`, children: /* @__PURE__ */ jsx(
|
|
5682
|
+
"div",
|
|
5683
|
+
{
|
|
5684
|
+
className: `w-4 h-4 rounded-full bg-white transition-transform ${hapticsEnabled ? "translate-x-4" : "translate-x-0"}`
|
|
5685
|
+
}
|
|
5686
|
+
) })
|
|
5329
5687
|
]
|
|
5330
|
-
}
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
] }) })
|
|
5688
|
+
}
|
|
5689
|
+
)
|
|
5690
|
+
] })
|
|
5691
|
+
] })
|
|
5335
5692
|
}
|
|
5336
5693
|
);
|
|
5337
5694
|
}
|
|
@@ -5351,6 +5708,7 @@ function GameModals({
|
|
|
5351
5708
|
cheats,
|
|
5352
5709
|
activeCheats,
|
|
5353
5710
|
onToggleCheat,
|
|
5711
|
+
onAddManualCheat,
|
|
5354
5712
|
saveModalOpen,
|
|
5355
5713
|
setSaveModalOpen,
|
|
5356
5714
|
saveModalMode,
|
|
@@ -5370,7 +5728,9 @@ function GameModals({
|
|
|
5370
5728
|
settingsModalOpen,
|
|
5371
5729
|
setSettingsModalOpen,
|
|
5372
5730
|
currentLanguage,
|
|
5373
|
-
onLanguageChange
|
|
5731
|
+
onLanguageChange,
|
|
5732
|
+
hapticsEnabled,
|
|
5733
|
+
onToggleHaptics
|
|
5374
5734
|
}) {
|
|
5375
5735
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5376
5736
|
/* @__PURE__ */ jsx(
|
|
@@ -5405,6 +5765,7 @@ function GameModals({
|
|
|
5405
5765
|
cheats,
|
|
5406
5766
|
activeCheats,
|
|
5407
5767
|
onToggle: onToggleCheat,
|
|
5768
|
+
onAddManualCheat,
|
|
5408
5769
|
onClose: () => {
|
|
5409
5770
|
setCheatsModalOpen(false);
|
|
5410
5771
|
onResume();
|
|
@@ -5457,7 +5818,9 @@ function GameModals({
|
|
|
5457
5818
|
},
|
|
5458
5819
|
currentLanguage,
|
|
5459
5820
|
onLanguageChange,
|
|
5460
|
-
systemColor
|
|
5821
|
+
systemColor,
|
|
5822
|
+
hapticsEnabled,
|
|
5823
|
+
onToggleHaptics
|
|
5461
5824
|
}
|
|
5462
5825
|
)
|
|
5463
5826
|
] });
|
|
@@ -6752,6 +7115,11 @@ function useEmulatorCore({
|
|
|
6752
7115
|
input_volume_up: "add",
|
|
6753
7116
|
input_volume_down: "subtract",
|
|
6754
7117
|
input_audio_mute: "f9",
|
|
7118
|
+
// Cheat hotkeys
|
|
7119
|
+
quick_menu_show_cheats: true,
|
|
7120
|
+
input_cheat_index_plus: "y",
|
|
7121
|
+
input_cheat_index_minus: "t",
|
|
7122
|
+
input_cheat_toggle: "u",
|
|
6755
7123
|
...inputConfig,
|
|
6756
7124
|
...specificConfig,
|
|
6757
7125
|
// Apply system-specific optimizations
|
|
@@ -6845,18 +7213,15 @@ function useEmulatorCore({
|
|
|
6845
7213
|
const restart = useCallback(async () => {
|
|
6846
7214
|
if (nostalgistRef.current) {
|
|
6847
7215
|
try {
|
|
6848
|
-
|
|
6849
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
6850
|
-
nostalgistRef.current.resume();
|
|
6851
|
-
setIsPaused(false);
|
|
6852
|
-
setStatus("running");
|
|
6853
|
-
} catch (err) {
|
|
6854
|
-
console.error("[Nostalgist] Restart error:", err);
|
|
7216
|
+
console.log("[Nostalgist] Full restart - stopping, re-preparing with fresh config, starting");
|
|
6855
7217
|
stop();
|
|
7218
|
+
await prepare();
|
|
6856
7219
|
await start();
|
|
7220
|
+
} catch (err) {
|
|
7221
|
+
console.error("[Nostalgist] Restart error:", err);
|
|
6857
7222
|
}
|
|
6858
7223
|
}
|
|
6859
|
-
}, [stop, start]);
|
|
7224
|
+
}, [stop, prepare, start]);
|
|
6860
7225
|
const pause = useCallback(() => {
|
|
6861
7226
|
if (nostalgistRef.current && !isPaused && status === "running") {
|
|
6862
7227
|
try {
|
|
@@ -6949,9 +7314,6 @@ function useEmulatorCore({
|
|
|
6949
7314
|
console.error("[Nostalgist] Resize error:", err);
|
|
6950
7315
|
}
|
|
6951
7316
|
}, []);
|
|
6952
|
-
useCallback(() => {
|
|
6953
|
-
return nostalgistRef.current;
|
|
6954
|
-
}, []);
|
|
6955
7317
|
return {
|
|
6956
7318
|
status,
|
|
6957
7319
|
setStatus,
|
|
@@ -7078,8 +7440,26 @@ function useEmulatorInput({ nostalgistRef }) {
|
|
|
7078
7440
|
console.error("[Nostalgist] Press key error:", err);
|
|
7079
7441
|
}
|
|
7080
7442
|
}, [nostalgistRef]);
|
|
7443
|
+
const pressDown = useCallback((button) => {
|
|
7444
|
+
if (!nostalgistRef.current) return;
|
|
7445
|
+
try {
|
|
7446
|
+
nostalgistRef.current.pressDown(button);
|
|
7447
|
+
} catch (err) {
|
|
7448
|
+
console.error("[Nostalgist] Press down error:", err);
|
|
7449
|
+
}
|
|
7450
|
+
}, [nostalgistRef]);
|
|
7451
|
+
const pressUp = useCallback((button) => {
|
|
7452
|
+
if (!nostalgistRef.current) return;
|
|
7453
|
+
try {
|
|
7454
|
+
nostalgistRef.current.pressUp(button);
|
|
7455
|
+
} catch (err) {
|
|
7456
|
+
console.error("[Nostalgist] Press up error:", err);
|
|
7457
|
+
}
|
|
7458
|
+
}, [nostalgistRef]);
|
|
7081
7459
|
return {
|
|
7082
|
-
pressKey
|
|
7460
|
+
pressKey,
|
|
7461
|
+
pressDown,
|
|
7462
|
+
pressUp
|
|
7083
7463
|
};
|
|
7084
7464
|
}
|
|
7085
7465
|
var MIN_SAVE_INTERVAL = 100;
|
|
@@ -7346,26 +7726,53 @@ function useEmulatorSaves({ nostalgistRef, isPaused, setIsPaused, setStatus, rew
|
|
|
7346
7726
|
stopRewindCapture
|
|
7347
7727
|
};
|
|
7348
7728
|
}
|
|
7349
|
-
function useEmulatorCheats({
|
|
7350
|
-
|
|
7729
|
+
function useEmulatorCheats({
|
|
7730
|
+
nostalgistRef
|
|
7731
|
+
}) {
|
|
7732
|
+
const allocatedPointersRef = useRef([]);
|
|
7733
|
+
const injectCheats = useCallback((cheats) => {
|
|
7351
7734
|
if (!nostalgistRef.current) return;
|
|
7352
|
-
|
|
7353
|
-
|
|
7354
|
-
|
|
7355
|
-
|
|
7735
|
+
const module = nostalgistRef.current.getEmscriptenModule();
|
|
7736
|
+
if (!module) return;
|
|
7737
|
+
if (module._free && allocatedPointersRef.current.length > 0) {
|
|
7738
|
+
allocatedPointersRef.current.forEach((ptr) => {
|
|
7739
|
+
try {
|
|
7740
|
+
module._free(ptr);
|
|
7741
|
+
} catch (e) {
|
|
7742
|
+
}
|
|
7743
|
+
});
|
|
7744
|
+
allocatedPointersRef.current = [];
|
|
7356
7745
|
}
|
|
7357
|
-
|
|
7358
|
-
|
|
7359
|
-
|
|
7360
|
-
|
|
7361
|
-
|
|
7362
|
-
}
|
|
7363
|
-
|
|
7746
|
+
if (module._cmd_cheat_realloc) {
|
|
7747
|
+
module._cmd_cheat_realloc(0);
|
|
7748
|
+
if (cheats.length > 0) {
|
|
7749
|
+
module._cmd_cheat_realloc(cheats.length);
|
|
7750
|
+
}
|
|
7751
|
+
}
|
|
7752
|
+
if (cheats.length > 0 && module.stringToNewUTF8 && module._cmd_cheat_set_code) {
|
|
7753
|
+
cheats.forEach((cheat, index) => {
|
|
7754
|
+
try {
|
|
7755
|
+
const ptr = module.stringToNewUTF8(cheat.code);
|
|
7756
|
+
allocatedPointersRef.current.push(ptr);
|
|
7757
|
+
module._cmd_cheat_set_code(index, ptr);
|
|
7758
|
+
if (module._cmd_cheat_toggle_index) {
|
|
7759
|
+
module._cmd_cheat_toggle_index(index, true);
|
|
7760
|
+
}
|
|
7761
|
+
} catch (err) {
|
|
7762
|
+
console.error("[Cheats] Failed to inject cheat:", index, err);
|
|
7763
|
+
}
|
|
7764
|
+
});
|
|
7765
|
+
}
|
|
7766
|
+
if (module._cmd_cheat_apply_cheats) {
|
|
7767
|
+
module._cmd_cheat_apply_cheats();
|
|
7364
7768
|
}
|
|
7365
7769
|
}, [nostalgistRef]);
|
|
7770
|
+
const clearCheats = useCallback(() => {
|
|
7771
|
+
injectCheats([]);
|
|
7772
|
+
}, [injectCheats]);
|
|
7366
7773
|
return {
|
|
7367
|
-
|
|
7368
|
-
|
|
7774
|
+
injectCheats,
|
|
7775
|
+
clearCheats
|
|
7369
7776
|
};
|
|
7370
7777
|
}
|
|
7371
7778
|
|
|
@@ -7437,7 +7844,9 @@ var useNostalgist = ({
|
|
|
7437
7844
|
initialVolume
|
|
7438
7845
|
});
|
|
7439
7846
|
const {
|
|
7440
|
-
pressKey
|
|
7847
|
+
pressKey,
|
|
7848
|
+
pressDown,
|
|
7849
|
+
pressUp
|
|
7441
7850
|
} = useEmulatorInput({
|
|
7442
7851
|
nostalgistRef
|
|
7443
7852
|
});
|
|
@@ -7460,8 +7869,8 @@ var useNostalgist = ({
|
|
|
7460
7869
|
// Disable manual rewind loop for heavy systems
|
|
7461
7870
|
});
|
|
7462
7871
|
const {
|
|
7463
|
-
|
|
7464
|
-
|
|
7872
|
+
injectCheats,
|
|
7873
|
+
clearCheats
|
|
7465
7874
|
} = useEmulatorCheats({
|
|
7466
7875
|
nostalgistRef
|
|
7467
7876
|
});
|
|
@@ -7504,9 +7913,11 @@ var useNostalgist = ({
|
|
|
7504
7913
|
toggleMute,
|
|
7505
7914
|
screenshot,
|
|
7506
7915
|
pressKey,
|
|
7916
|
+
pressDown,
|
|
7917
|
+
pressUp,
|
|
7507
7918
|
resize,
|
|
7508
|
-
|
|
7509
|
-
|
|
7919
|
+
injectCheats,
|
|
7920
|
+
clearCheats,
|
|
7510
7921
|
getNostalgistInstance,
|
|
7511
7922
|
isPerformanceMode
|
|
7512
7923
|
}), [
|
|
@@ -7536,9 +7947,11 @@ var useNostalgist = ({
|
|
|
7536
7947
|
toggleMute,
|
|
7537
7948
|
screenshot,
|
|
7538
7949
|
pressKey,
|
|
7950
|
+
pressDown,
|
|
7951
|
+
pressUp,
|
|
7539
7952
|
resize,
|
|
7540
|
-
|
|
7541
|
-
|
|
7953
|
+
injectCheats,
|
|
7954
|
+
clearCheats,
|
|
7542
7955
|
getNostalgistInstance,
|
|
7543
7956
|
isPerformanceMode
|
|
7544
7957
|
]);
|
|
@@ -8269,39 +8682,95 @@ function useGameSaves({
|
|
|
8269
8682
|
handleAutoSaveToggle
|
|
8270
8683
|
};
|
|
8271
8684
|
}
|
|
8685
|
+
var generateManualId = () => `manual-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
8272
8686
|
function useGameCheats({
|
|
8273
8687
|
nostalgist,
|
|
8274
8688
|
cheats = [],
|
|
8275
|
-
onToggleCheat
|
|
8689
|
+
onToggleCheat,
|
|
8690
|
+
showToast,
|
|
8691
|
+
romId
|
|
8276
8692
|
}) {
|
|
8277
8693
|
const [cheatsModalOpen, setCheatsModalOpen] = useState(false);
|
|
8278
8694
|
const [activeCheats, setActiveCheats] = useState(/* @__PURE__ */ new Set());
|
|
8695
|
+
const [manualCheatsInternal, setManualCheatsInternal] = useState([]);
|
|
8696
|
+
const [isLoaded, setIsLoaded] = useState(false);
|
|
8697
|
+
const cheatStorageKey = romId ? `koin_cheats_${romId}` : null;
|
|
8698
|
+
useEffect(() => {
|
|
8699
|
+
if (!cheatStorageKey) return;
|
|
8700
|
+
setIsLoaded(false);
|
|
8701
|
+
try {
|
|
8702
|
+
const stored = localStorage.getItem(cheatStorageKey);
|
|
8703
|
+
if (stored) {
|
|
8704
|
+
const parsed = JSON.parse(stored);
|
|
8705
|
+
if (Array.isArray(parsed)) {
|
|
8706
|
+
setManualCheatsInternal(parsed);
|
|
8707
|
+
}
|
|
8708
|
+
}
|
|
8709
|
+
} catch (e) {
|
|
8710
|
+
console.error("[Cheats] Failed to load cheats:", e);
|
|
8711
|
+
} finally {
|
|
8712
|
+
setIsLoaded(true);
|
|
8713
|
+
}
|
|
8714
|
+
}, [cheatStorageKey]);
|
|
8715
|
+
useEffect(() => {
|
|
8716
|
+
if (!cheatStorageKey || !isLoaded) return;
|
|
8717
|
+
localStorage.setItem(cheatStorageKey, JSON.stringify(manualCheatsInternal));
|
|
8718
|
+
}, [manualCheatsInternal, cheatStorageKey, isLoaded]);
|
|
8719
|
+
const allCheats = useMemo(() => {
|
|
8720
|
+
const normalizedExternal = cheats.map((c) => ({
|
|
8721
|
+
id: typeof c.id === "number" ? `db-${c.id}` : c.id,
|
|
8722
|
+
code: c.code,
|
|
8723
|
+
description: c.description,
|
|
8724
|
+
source: "database"
|
|
8725
|
+
}));
|
|
8726
|
+
return [...normalizedExternal, ...manualCheatsInternal];
|
|
8727
|
+
}, [cheats, manualCheatsInternal]);
|
|
8728
|
+
const handleAddManualCheat = (code, description) => {
|
|
8729
|
+
if (!nostalgist) return;
|
|
8730
|
+
const newCheat = {
|
|
8731
|
+
id: generateManualId(),
|
|
8732
|
+
code,
|
|
8733
|
+
description,
|
|
8734
|
+
source: "manual"
|
|
8735
|
+
};
|
|
8736
|
+
setManualCheatsInternal((prev) => [...prev, newCheat]);
|
|
8737
|
+
setActiveCheats((prev) => new Set(prev).add(newCheat.id));
|
|
8738
|
+
setCheatsModalOpen(false);
|
|
8739
|
+
const currentActiveCheats = [...activeCheats, newCheat.id];
|
|
8740
|
+
const cheatsToInject = allCheats.filter((c) => currentActiveCheats.includes(c.id) || c.id === newCheat.id).concat([newCheat]);
|
|
8741
|
+
nostalgist.injectCheats(cheatsToInject.map((c) => ({ code: c.code })));
|
|
8742
|
+
nostalgist.resume();
|
|
8743
|
+
showToast?.("Cheat added!", "success");
|
|
8744
|
+
};
|
|
8279
8745
|
const handleToggleCheat = (cheatId) => {
|
|
8280
8746
|
if (!nostalgist) return;
|
|
8281
8747
|
const newActiveCheats = new Set(activeCheats);
|
|
8282
8748
|
const isActive = newActiveCheats.has(cheatId);
|
|
8283
8749
|
if (isActive) {
|
|
8284
8750
|
newActiveCheats.delete(cheatId);
|
|
8751
|
+
showToast?.("Cheat Disabled");
|
|
8285
8752
|
} else {
|
|
8286
8753
|
newActiveCheats.add(cheatId);
|
|
8754
|
+
showToast?.("Cheat Enabled", "success");
|
|
8287
8755
|
}
|
|
8288
8756
|
setActiveCheats(newActiveCheats);
|
|
8289
|
-
onToggleCheat
|
|
8290
|
-
|
|
8291
|
-
|
|
8292
|
-
|
|
8293
|
-
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
|
|
8297
|
-
});
|
|
8298
|
-
}, 50);
|
|
8757
|
+
if (onToggleCheat) {
|
|
8758
|
+
const numericId = cheatId.startsWith("db-") ? parseInt(cheatId.slice(3), 10) : void 0;
|
|
8759
|
+
if (numericId !== void 0) {
|
|
8760
|
+
onToggleCheat(numericId, !isActive);
|
|
8761
|
+
}
|
|
8762
|
+
}
|
|
8763
|
+
const cheatsToInject = allCheats.filter((c) => newActiveCheats.has(c.id)).map((c) => ({ code: c.code }));
|
|
8764
|
+
nostalgist.injectCheats(cheatsToInject);
|
|
8299
8765
|
};
|
|
8300
8766
|
return {
|
|
8301
8767
|
cheatsModalOpen,
|
|
8302
8768
|
setCheatsModalOpen,
|
|
8303
8769
|
activeCheats,
|
|
8304
|
-
|
|
8770
|
+
allCheats,
|
|
8771
|
+
// Unified list - replaces separate cheats + manualCheats
|
|
8772
|
+
handleToggleCheat,
|
|
8773
|
+
handleAddManualCheat
|
|
8305
8774
|
};
|
|
8306
8775
|
}
|
|
8307
8776
|
function useGameRecording({
|
|
@@ -8478,10 +8947,13 @@ function useGamePlayer(props) {
|
|
|
8478
8947
|
cheatsModalOpen,
|
|
8479
8948
|
setCheatsModalOpen,
|
|
8480
8949
|
activeCheats,
|
|
8481
|
-
|
|
8950
|
+
allCheats,
|
|
8951
|
+
handleToggleCheat,
|
|
8952
|
+
handleAddManualCheat
|
|
8482
8953
|
} = useGameCheats({
|
|
8483
8954
|
...props,
|
|
8484
|
-
nostalgist
|
|
8955
|
+
nostalgist,
|
|
8956
|
+
showToast
|
|
8485
8957
|
});
|
|
8486
8958
|
const {
|
|
8487
8959
|
isRecording,
|
|
@@ -8537,7 +9009,9 @@ function useGamePlayer(props) {
|
|
|
8537
9009
|
hardcoreRestrictions,
|
|
8538
9010
|
// Cheats
|
|
8539
9011
|
activeCheats,
|
|
9012
|
+
allCheats,
|
|
8540
9013
|
handleToggleCheat,
|
|
9014
|
+
handleAddManualCheat,
|
|
8541
9015
|
// Emulator
|
|
8542
9016
|
nostalgist,
|
|
8543
9017
|
volumeState,
|
|
@@ -8565,7 +9039,8 @@ var DEFAULT_SETTINGS = {
|
|
|
8565
9039
|
muted: false,
|
|
8566
9040
|
shader: "",
|
|
8567
9041
|
showPerformanceOverlay: false,
|
|
8568
|
-
showInputDisplay: false
|
|
9042
|
+
showInputDisplay: false,
|
|
9043
|
+
hapticsEnabled: true
|
|
8569
9044
|
};
|
|
8570
9045
|
function usePlayerPersistence(onSettingsChange) {
|
|
8571
9046
|
const [settings, setSettings] = useState(DEFAULT_SETTINGS);
|
|
@@ -8672,7 +9147,9 @@ var es = {
|
|
|
8672
9147
|
shortcuts: "Atajos",
|
|
8673
9148
|
exit: "Salir",
|
|
8674
9149
|
language: "Idioma",
|
|
8675
|
-
selectLanguage: "Seleccionar idioma"
|
|
9150
|
+
selectLanguage: "Seleccionar idioma",
|
|
9151
|
+
haptics: "Vibraci\xF3n",
|
|
9152
|
+
enableHaptics: "Habilitar vibraci\xF3n"
|
|
8676
9153
|
},
|
|
8677
9154
|
overlay: {
|
|
8678
9155
|
play: "JUGAR",
|
|
@@ -8755,7 +9232,10 @@ var es = {
|
|
|
8755
9232
|
emptyDesc: "No se encontraron c\xF3digos para este juego",
|
|
8756
9233
|
copy: "Copiar c\xF3digo",
|
|
8757
9234
|
active: "{{count}} truco{{s}} activo(s)",
|
|
8758
|
-
toggleHint: "Haz clic para activar/desactivar"
|
|
9235
|
+
toggleHint: "Haz clic para activar/desactivar",
|
|
9236
|
+
codePlaceholder: "Introduce c\xF3digo (ej: 00C-048-E6E)",
|
|
9237
|
+
descPlaceholder: "Descripci\xF3n (opcional)",
|
|
9238
|
+
add: "A\xF1adir truco"
|
|
8759
9239
|
},
|
|
8760
9240
|
saveSlots: {
|
|
8761
9241
|
title: "Guardar partida",
|
|
@@ -8900,7 +9380,9 @@ var fr = {
|
|
|
8900
9380
|
shortcuts: "Raccourcis",
|
|
8901
9381
|
exit: "Quitter",
|
|
8902
9382
|
language: "Langue",
|
|
8903
|
-
selectLanguage: "Choisir la langue"
|
|
9383
|
+
selectLanguage: "Choisir la langue",
|
|
9384
|
+
haptics: "Vibration",
|
|
9385
|
+
enableHaptics: "Activer la vibration"
|
|
8904
9386
|
},
|
|
8905
9387
|
overlay: {
|
|
8906
9388
|
play: "JOUER",
|
|
@@ -8983,7 +9465,10 @@ var fr = {
|
|
|
8983
9465
|
emptyDesc: "Aucun code trouv\xE9 pour ce jeu",
|
|
8984
9466
|
copy: "Copier",
|
|
8985
9467
|
active: "{{count}} actif(s)",
|
|
8986
|
-
toggleHint: "Cliquez pour activer/d\xE9sactiver"
|
|
9468
|
+
toggleHint: "Cliquez pour activer/d\xE9sactiver",
|
|
9469
|
+
codePlaceholder: "Entrez le code (ex: 00C-048-E6E)",
|
|
9470
|
+
descPlaceholder: "Description (optionnel)",
|
|
9471
|
+
add: "Ajouter"
|
|
8987
9472
|
},
|
|
8988
9473
|
saveSlots: {
|
|
8989
9474
|
title: "Sauvegardes",
|
|
@@ -9156,7 +9641,9 @@ var GamePlayerInner = memo(function GamePlayerInner2(props) {
|
|
|
9156
9641
|
hardcoreRestrictions,
|
|
9157
9642
|
// Cheats
|
|
9158
9643
|
activeCheats,
|
|
9644
|
+
allCheats,
|
|
9159
9645
|
handleToggleCheat,
|
|
9646
|
+
handleAddManualCheat,
|
|
9160
9647
|
// Emulator Instance & State
|
|
9161
9648
|
nostalgist,
|
|
9162
9649
|
// Contains start, restart, etc.
|
|
@@ -9221,7 +9708,7 @@ var GamePlayerInner = memo(function GamePlayerInner2(props) {
|
|
|
9221
9708
|
if (muted !== settings.muted) toggleMute();
|
|
9222
9709
|
}
|
|
9223
9710
|
}, [settingsLoaded]);
|
|
9224
|
-
const { system, systemColor = "#00FF41",
|
|
9711
|
+
const { system, systemColor = "#00FF41", onExit } = props;
|
|
9225
9712
|
const handlePauseToggle = useCallback(() => {
|
|
9226
9713
|
status === "ready" ? start() : togglePause();
|
|
9227
9714
|
}, [status, start, togglePause]);
|
|
@@ -9277,6 +9764,9 @@ var GamePlayerInner = memo(function GamePlayerInner2(props) {
|
|
|
9277
9764
|
const handleToggleInputDisplay = useCallback(() => {
|
|
9278
9765
|
updateSettings({ showInputDisplay: !settings.showInputDisplay });
|
|
9279
9766
|
}, [updateSettings, settings.showInputDisplay]);
|
|
9767
|
+
const handleToggleHaptics = useCallback(() => {
|
|
9768
|
+
updateSettings({ hapticsEnabled: !settings.hapticsEnabled });
|
|
9769
|
+
}, [updateSettings, settings.hapticsEnabled]);
|
|
9280
9770
|
const handleToggleRecording = useCallback(async () => {
|
|
9281
9771
|
if (!recordingSupported) {
|
|
9282
9772
|
console.warn("[Recording] Not supported in this browser");
|
|
@@ -9363,7 +9853,12 @@ var GamePlayerInner = memo(function GamePlayerInner2(props) {
|
|
|
9363
9853
|
system,
|
|
9364
9854
|
isRunning: status === "running" || status === "paused",
|
|
9365
9855
|
controls,
|
|
9366
|
-
systemColor
|
|
9856
|
+
systemColor,
|
|
9857
|
+
hapticsEnabled: settings.hapticsEnabled,
|
|
9858
|
+
onButtonDown: nostalgist.pressDown,
|
|
9859
|
+
onButtonUp: nostalgist.pressUp,
|
|
9860
|
+
onPause: nostalgist.pause,
|
|
9861
|
+
onResume: nostalgist.resume
|
|
9367
9862
|
}
|
|
9368
9863
|
),
|
|
9369
9864
|
!isFullscreen2 && isMobile && /* @__PURE__ */ jsx(
|
|
@@ -9518,9 +10013,10 @@ var GamePlayerInner = memo(function GamePlayerInner2(props) {
|
|
|
9518
10013
|
systemColor,
|
|
9519
10014
|
cheatsModalOpen,
|
|
9520
10015
|
setCheatsModalOpen,
|
|
9521
|
-
cheats,
|
|
10016
|
+
cheats: allCheats,
|
|
9522
10017
|
activeCheats,
|
|
9523
10018
|
onToggleCheat: handleToggleCheat,
|
|
10019
|
+
onAddManualCheat: handleAddManualCheat,
|
|
9524
10020
|
saveModalOpen,
|
|
9525
10021
|
setSaveModalOpen,
|
|
9526
10022
|
saveModalMode,
|
|
@@ -9540,7 +10036,9 @@ var GamePlayerInner = memo(function GamePlayerInner2(props) {
|
|
|
9540
10036
|
settingsModalOpen,
|
|
9541
10037
|
setSettingsModalOpen,
|
|
9542
10038
|
currentLanguage: props.currentLanguage,
|
|
9543
|
-
onLanguageChange: props.onLanguageChange
|
|
10039
|
+
onLanguageChange: props.onLanguageChange,
|
|
10040
|
+
hapticsEnabled: settings.hapticsEnabled,
|
|
10041
|
+
onToggleHaptics: handleToggleHaptics
|
|
9544
10042
|
}
|
|
9545
10043
|
),
|
|
9546
10044
|
!isMobile && /* @__PURE__ */ jsx(
|