Android build v1.1.6
This commit is contained in:
Binary file not shown.
@@ -7,8 +7,8 @@ android {
|
|||||||
applicationId "com.warren.iwanttoheal"
|
applicationId "com.warren.iwanttoheal"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 84
|
versionCode 85
|
||||||
versionName "1.1.5"
|
versionName "1.1.6"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
aaptOptions {
|
aaptOptions {
|
||||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ public abstract class ControllerBridgeActivity extends BridgeActivity {
|
|||||||
public static final String EXTRA_INITIAL_URL = "com.warren.iwanttoheal.INITIAL_URL";
|
public static final String EXTRA_INITIAL_URL = "com.warren.iwanttoheal.INITIAL_URL";
|
||||||
private static final String PREFS_NAME = "com.warren.iwanttoheal.performance";
|
private static final String PREFS_NAME = "com.warren.iwanttoheal.performance";
|
||||||
private static final String CACHE_CLEARED_VERSION_KEY = "webview_cache_cleared_version";
|
private static final String CACHE_CLEARED_VERSION_KEY = "webview_cache_cleared_version";
|
||||||
private static final long DPAD_THROTTLE_MS = 220;
|
private static final long DPAD_THROTTLE_MS = 55;
|
||||||
private long lastDpadDispatchAt = 0;
|
private final long[] lastDpadDispatchAt = new long[16];
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
@@ -139,7 +139,7 @@ public abstract class ControllerBridgeActivity extends BridgeActivity {
|
|||||||
|
|
||||||
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||||
boolean repeat = event.getRepeatCount() > 0;
|
boolean repeat = event.getRepeatCount() > 0;
|
||||||
if (isDpadToken(token) && shouldThrottleDpad()) return true;
|
if (isDpadToken(token) && shouldThrottleDpad(token)) return true;
|
||||||
String script =
|
String script =
|
||||||
"window.dispatchEvent(new CustomEvent('ashen-halls-native-controller',"
|
"window.dispatchEvent(new CustomEvent('ashen-halls-native-controller',"
|
||||||
+ "{detail:{token:'" + token + "',repeat:" + repeat + "}}));";
|
+ "{detail:{token:'" + token + "',repeat:" + repeat + "}}));";
|
||||||
@@ -153,10 +153,11 @@ public abstract class ControllerBridgeActivity extends BridgeActivity {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldThrottleDpad() {
|
private boolean shouldThrottleDpad(String token) {
|
||||||
|
int buttonIndex = Integer.parseInt(token.substring("Button".length()));
|
||||||
long now = SystemClock.uptimeMillis();
|
long now = SystemClock.uptimeMillis();
|
||||||
if (now - lastDpadDispatchAt < DPAD_THROTTLE_MS) return true;
|
if (now - lastDpadDispatchAt[buttonIndex] < DPAD_THROTTLE_MS) return true;
|
||||||
lastDpadDispatchAt = now;
|
lastDpadDispatchAt[buttonIndex] = now;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,12 @@ import { createRoot } from 'react-dom/client'
|
|||||||
import './index.css'
|
import './index.css'
|
||||||
import './App.css'
|
import './App.css'
|
||||||
import { AdminScreen } from './components/AdminScreen'
|
import { AdminScreen } from './components/AdminScreen'
|
||||||
|
import { InputProvider } from './input'
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById('root')!).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
|
<InputProvider>
|
||||||
<AdminScreen onBack={() => window.close()} />
|
<AdminScreen onBack={() => window.close()} />
|
||||||
|
</InputProvider>
|
||||||
</StrictMode>,
|
</StrictMode>,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1509,6 +1509,7 @@ export function PvPRoguelikeScreen({
|
|||||||
bindings={activeBindings}
|
bindings={activeBindings}
|
||||||
canCast={status === 'playing' && !playerDone && playerAlive}
|
canCast={status === 'playing' && !playerDone && playerAlive}
|
||||||
className="pvp-bottom-spell-bar"
|
className="pvp-bottom-spell-bar"
|
||||||
|
focusable={false}
|
||||||
iconStyle={controllerIconStyle}
|
iconStyle={controllerIconStyle}
|
||||||
onCast={castPlayerSpell}
|
onCast={castPlayerSpell}
|
||||||
resource={playerSide.resource}
|
resource={playerSide.resource}
|
||||||
|
|||||||
@@ -1289,6 +1289,7 @@ export function PvpStadiumScreen({
|
|||||||
bindings={activeBindings}
|
bindings={activeBindings}
|
||||||
canCast={status === 'playing' && playerAlive}
|
canCast={status === 'playing' && playerAlive}
|
||||||
className="pvp-bottom-spell-bar"
|
className="pvp-bottom-spell-bar"
|
||||||
|
focusable={false}
|
||||||
iconStyle={controllerIconStyle}
|
iconStyle={controllerIconStyle}
|
||||||
onCast={castPlayerSpell}
|
onCast={castPlayerSpell}
|
||||||
resource={playerSide.resource}
|
resource={playerSide.resource}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export const SpellButton = memo(function SpellButton({
|
|||||||
iconStyle,
|
iconStyle,
|
||||||
resourceName,
|
resourceName,
|
||||||
disabled,
|
disabled,
|
||||||
|
focusable = true,
|
||||||
onCast,
|
onCast,
|
||||||
emptyKeyPrefix = 'empty',
|
emptyKeyPrefix = 'empty',
|
||||||
}: {
|
}: {
|
||||||
@@ -48,6 +49,7 @@ export const SpellButton = memo(function SpellButton({
|
|||||||
iconStyle: ControllerIconStyle
|
iconStyle: ControllerIconStyle
|
||||||
resourceName: string
|
resourceName: string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
focusable?: boolean
|
||||||
onCast: (spell: Spell) => void
|
onCast: (spell: Spell) => void
|
||||||
emptyKeyPrefix?: string
|
emptyKeyPrefix?: string
|
||||||
}) {
|
}) {
|
||||||
@@ -61,9 +63,11 @@ export const SpellButton = memo(function SpellButton({
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className="spell"
|
className="spell"
|
||||||
|
data-controller-nav={focusable ? undefined : 'skip'}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
key={spell.id}
|
key={spell.id}
|
||||||
onClick={() => onCast(spell)}
|
onClick={() => onCast(spell)}
|
||||||
|
tabIndex={focusable ? undefined : -1}
|
||||||
title={spell.description}
|
title={spell.description}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -95,6 +99,7 @@ export const SpellBar = memo(function SpellBar({
|
|||||||
canCast,
|
canCast,
|
||||||
onCast,
|
onCast,
|
||||||
className = 'spell-bar six-slots vertical-spell-bar',
|
className = 'spell-bar six-slots vertical-spell-bar',
|
||||||
|
focusable = true,
|
||||||
}: {
|
}: {
|
||||||
spells: SpellSlot[]
|
spells: SpellSlot[]
|
||||||
bindings: Record<string, string>
|
bindings: Record<string, string>
|
||||||
@@ -104,6 +109,7 @@ export const SpellBar = memo(function SpellBar({
|
|||||||
canCast: boolean
|
canCast: boolean
|
||||||
onCast: (spell: Spell) => void
|
onCast: (spell: Spell) => void
|
||||||
className?: string
|
className?: string
|
||||||
|
focusable?: boolean
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
@@ -119,6 +125,7 @@ export const SpellBar = memo(function SpellBar({
|
|||||||
<SpellButton
|
<SpellButton
|
||||||
binding={bindings[`ability${slotIndex + 1}`]}
|
binding={bindings[`ability${slotIndex + 1}`]}
|
||||||
disabled={!canCast || resource < spell.cost || spell.remaining > 0}
|
disabled={!canCast || resource < spell.cost || spell.remaining > 0}
|
||||||
|
focusable={focusable}
|
||||||
iconStyle={iconStyle}
|
iconStyle={iconStyle}
|
||||||
key={spell.id}
|
key={spell.id}
|
||||||
onCast={onCast}
|
onCast={onCast}
|
||||||
|
|||||||
+12
-1
@@ -19,6 +19,7 @@ import {
|
|||||||
dispatchExternalGameAction,
|
dispatchExternalGameAction,
|
||||||
type ControllerIconStyle,
|
type ControllerIconStyle,
|
||||||
type InputAction,
|
type InputAction,
|
||||||
|
useGameAction,
|
||||||
} from './input'
|
} from './input'
|
||||||
import { ControllerBindingLabel } from './components/ControllerIcons'
|
import { ControllerBindingLabel } from './components/ControllerIcons'
|
||||||
import { PartyMemberFrame } from './components/PartyFrames'
|
import { PartyMemberFrame } from './components/PartyFrames'
|
||||||
@@ -402,6 +403,12 @@ export function DualScreenBottomDisplay() {
|
|||||||
channel?.close()
|
channel?.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useGameAction((action) => {
|
||||||
|
if (!state || state.opponentParty) return
|
||||||
|
if (action === 'confirm' || action === 'back' || action === 'toggleTouchLock') return
|
||||||
|
sendAction(action)
|
||||||
|
})
|
||||||
|
|
||||||
if (!state && workshopState) {
|
if (!state && workshopState) {
|
||||||
return (
|
return (
|
||||||
<main className="dual-bottom-display workshop-bottom-display">
|
<main className="dual-bottom-display workshop-bottom-display">
|
||||||
@@ -449,7 +456,10 @@ export function DualScreenBottomDisplay() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className={`dual-bottom-display ${state.opponentParty ? 'pvp-opponent-bottom-display' : ''}`}>
|
<main
|
||||||
|
className={`dual-bottom-display ${state.opponentParty ? 'pvp-opponent-bottom-display' : ''}`}
|
||||||
|
data-combat-active={!state.opponentParty && state.status === 'playing' && !state.paused ? 'true' : 'false'}
|
||||||
|
>
|
||||||
<header className="dual-controls-header">
|
<header className="dual-controls-header">
|
||||||
<div>
|
<div>
|
||||||
<p className="eyebrow">{state.opponentParty ? 'Opponent View' : `${state.difficultyName} ${state.contentName}`}</p>
|
<p className="eyebrow">{state.opponentParty ? 'Opponent View' : `${state.difficultyName} ${state.contentName}`}</p>
|
||||||
@@ -649,6 +659,7 @@ export function DualScreenTopCombat({
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className="dual-top-spell"
|
className="dual-top-spell"
|
||||||
|
data-controller-nav={state.opponentParty ? 'skip' : undefined}
|
||||||
disabled={
|
disabled={
|
||||||
!state.playerIsAlive
|
!state.playerIsAlive
|
||||||
|| state.resource < spell.cost
|
|| state.resource < spell.cost
|
||||||
|
|||||||
+110
-26
@@ -130,10 +130,20 @@ const STORAGE_KEY = 'ashen-halls-input-bindings-v1'
|
|||||||
const PREFERENCES_STORAGE_KEY = 'ashen-halls-input-preferences-v1'
|
const PREFERENCES_STORAGE_KEY = 'ashen-halls-input-preferences-v1'
|
||||||
const GAME_ACTION_EVENT = 'ashen-halls-game-action'
|
const GAME_ACTION_EVENT = 'ashen-halls-game-action'
|
||||||
const NATIVE_CONTROLLER_EVENT = 'ashen-halls-native-controller'
|
const NATIVE_CONTROLLER_EVENT = 'ashen-halls-native-controller'
|
||||||
const FOCUSABLE_SELECTOR = 'button:not(:disabled), input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex="-1"])'
|
const FOCUSABLE_SELECTOR = 'button:not(:disabled):not([data-controller-nav="skip"]), input:not(:disabled):not([data-controller-nav="skip"]), select:not(:disabled):not([data-controller-nav="skip"]), textarea:not(:disabled):not([data-controller-nav="skip"]), [tabindex]:not([tabindex="-1"]):not([data-controller-nav="skip"])'
|
||||||
|
const MAIN_CONTENT_SELECTOR = '.menu-screen, .content-screen, .dungeon-run-screen'
|
||||||
|
const HEADER_CONTENT_SELECTOR = '.app-header'
|
||||||
const GAMEPAD_COMBAT_POLL_MS = 1000 / 60
|
const GAMEPAD_COMBAT_POLL_MS = 1000 / 60
|
||||||
const GAMEPAD_MENU_POLL_MS = 1000 / 30
|
const GAMEPAD_MENU_POLL_MS = 1000 / 30
|
||||||
const GAMEPAD_BROWSER_DISCONNECTED_POLL_MS = 250
|
const GAMEPAD_BROWSER_DISCONNECTED_POLL_MS = 250
|
||||||
|
const CONTROLLER_REPEAT_INITIAL_MS = 260
|
||||||
|
const CONTROLLER_REPEAT_MS = 85
|
||||||
|
const DPAD_NAV_ACTIONS: Partial<Record<string, InputAction>> = {
|
||||||
|
Button12: 'navigateUp',
|
||||||
|
Button13: 'navigateDown',
|
||||||
|
Button14: 'navigateLeft',
|
||||||
|
Button15: 'navigateRight',
|
||||||
|
}
|
||||||
|
|
||||||
let lastControllerFocus: HTMLElement | null = null
|
let lastControllerFocus: HTMLElement | null = null
|
||||||
|
|
||||||
@@ -256,6 +266,16 @@ function isVisible(element: HTMLElement) {
|
|||||||
return element.getClientRects().length > 0
|
return element.getClientRects().length > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function focusableDescendants(scope: ParentNode) {
|
||||||
|
return Array.from(
|
||||||
|
scope.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR),
|
||||||
|
).filter(isVisible)
|
||||||
|
}
|
||||||
|
|
||||||
|
function uniqueElements(elements: HTMLElement[]) {
|
||||||
|
return elements.filter((element, index) => elements.indexOf(element) === index)
|
||||||
|
}
|
||||||
|
|
||||||
function focusableElements() {
|
function focusableElements() {
|
||||||
const keyboard = document.querySelector<HTMLElement>('.controller-keyboard')
|
const keyboard = document.querySelector<HTMLElement>('.controller-keyboard')
|
||||||
const pauseMenu = document.querySelector<HTMLElement>('.pause-screen')
|
const pauseMenu = document.querySelector<HTMLElement>('.pause-screen')
|
||||||
@@ -264,10 +284,22 @@ function focusableElements() {
|
|||||||
'.result-screen, .binding-capture, .dual-startup-prompt',
|
'.result-screen, .binding-capture, .dual-startup-prompt',
|
||||||
),
|
),
|
||||||
).find(isVisible)
|
).find(isVisible)
|
||||||
const scope: ParentNode = keyboard ?? pauseMenu ?? dialog ?? document
|
const mainContent = Array.from(
|
||||||
return Array.from(
|
document.querySelectorAll<HTMLElement>(MAIN_CONTENT_SELECTOR),
|
||||||
scope.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR),
|
).find(isVisible)
|
||||||
|
const overlay = keyboard ?? pauseMenu ?? dialog
|
||||||
|
if (overlay) return focusableDescendants(overlay)
|
||||||
|
const visibleHeaders = Array.from(
|
||||||
|
document.querySelectorAll<HTMLElement>(HEADER_CONTENT_SELECTOR),
|
||||||
).filter(isVisible)
|
).filter(isVisible)
|
||||||
|
if (!mainContent) return visibleHeaders.length > 0 ? [] : focusableDescendants(document)
|
||||||
|
const headerControls = visibleHeaders.flatMap(
|
||||||
|
(header) => focusableDescendants(header),
|
||||||
|
)
|
||||||
|
return uniqueElements([
|
||||||
|
...focusableDescendants(mainContent),
|
||||||
|
...headerControls,
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
function rememberFocusableControl(element: HTMLElement) {
|
function rememberFocusableControl(element: HTMLElement) {
|
||||||
@@ -291,6 +323,23 @@ function currentFocusableControl(candidates = focusableElements()) {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function rangeDistance(startA: number, endA: number, startB: number, endB: number) {
|
||||||
|
if (endA < startB) return startB - endA
|
||||||
|
if (endB < startA) return startA - endB
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeSelectOption(select: HTMLSelectElement, direction: -1 | 1) {
|
||||||
|
const options = Array.from(select.options).filter((option) => !option.disabled)
|
||||||
|
const currentIndex = options.findIndex((option) => option.index === select.selectedIndex)
|
||||||
|
const nextOption = options[currentIndex + direction]
|
||||||
|
if (!nextOption) return false
|
||||||
|
select.selectedIndex = nextOption.index
|
||||||
|
select.dispatchEvent(new Event('input', { bubbles: true }))
|
||||||
|
select.dispatchEvent(new Event('change', { bubbles: true }))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
export function focusFirstControl() {
|
export function focusFirstControl() {
|
||||||
const first = focusableElements()[0]
|
const first = focusableElements()[0]
|
||||||
if (first) focusControl(first)
|
if (first) focusControl(first)
|
||||||
@@ -311,21 +360,48 @@ function moveFocus(action: InputAction) {
|
|||||||
const currentY = currentRect.top + currentRect.height / 2
|
const currentY = currentRect.top + currentRect.height / 2
|
||||||
const vertical = action === 'navigateUp' || action === 'navigateDown'
|
const vertical = action === 'navigateUp' || action === 'navigateDown'
|
||||||
const direction = action === 'navigateUp' || action === 'navigateLeft' ? -1 : 1
|
const direction = action === 'navigateUp' || action === 'navigateLeft' ? -1 : 1
|
||||||
|
const currentIndex = candidates.indexOf(current)
|
||||||
|
|
||||||
|
const adjacent = candidates[currentIndex + direction]
|
||||||
|
const fallbackToAdjacent = () => {
|
||||||
|
if (adjacent) focusControl(adjacent)
|
||||||
|
}
|
||||||
|
|
||||||
const ranked = candidates
|
const ranked = candidates
|
||||||
.filter((candidate) => candidate !== current)
|
.filter((candidate) => candidate !== current)
|
||||||
.map((candidate) => {
|
.map((candidate, index) => {
|
||||||
const rect = candidate.getBoundingClientRect()
|
const rect = candidate.getBoundingClientRect()
|
||||||
const x = rect.left + rect.width / 2
|
const x = rect.left + rect.width / 2
|
||||||
const y = rect.top + rect.height / 2
|
const y = rect.top + rect.height / 2
|
||||||
const primary = vertical ? y - currentY : x - currentX
|
const primary = vertical
|
||||||
const secondary = vertical ? Math.abs(x - currentX) : Math.abs(y - currentY)
|
? direction > 0
|
||||||
return { candidate, primary, score: Math.abs(primary) + secondary * 2.5 }
|
? rect.top - currentRect.bottom
|
||||||
|
: currentRect.top - rect.bottom
|
||||||
|
: direction > 0
|
||||||
|
? rect.left - currentRect.right
|
||||||
|
: currentRect.left - rect.right
|
||||||
|
const secondary = vertical
|
||||||
|
? rangeDistance(currentRect.left, currentRect.right, rect.left, rect.right)
|
||||||
|
: rangeDistance(currentRect.top, currentRect.bottom, rect.top, rect.bottom)
|
||||||
|
return {
|
||||||
|
candidate,
|
||||||
|
index,
|
||||||
|
primary,
|
||||||
|
secondary,
|
||||||
|
score: Math.max(0, primary) + secondary * 2.5 + (vertical ? Math.abs(x - currentX) : Math.abs(y - currentY)) * 0.1,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.filter(({ primary }) => Math.sign(primary) === direction)
|
.filter(({ primary }) => primary >= -4)
|
||||||
.sort((a, b) => a.score - b.score)
|
const sameRowHorizontal = !vertical
|
||||||
|
? ranked.filter(({ secondary }) => secondary === 0)
|
||||||
|
: ranked
|
||||||
|
if (sameRowHorizontal.length === 0) {
|
||||||
|
fallbackToAdjacent()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sameRowHorizontal.sort((a, b) => a.score - b.score || a.secondary - b.secondary || a.index - b.index)
|
||||||
|
|
||||||
const next = ranked[0]?.candidate
|
const next = sameRowHorizontal[0]?.candidate
|
||||||
if (!next) return
|
if (!next) return
|
||||||
focusControl(next)
|
focusControl(next)
|
||||||
}
|
}
|
||||||
@@ -524,12 +600,26 @@ export function InputProvider({ children }: { children: ReactNode }) {
|
|||||||
setTouchLockMessage('')
|
setTouchLockMessage('')
|
||||||
}, 1400)
|
}, 1400)
|
||||||
} else if (action.startsWith('navigate')) {
|
} else if (action.startsWith('navigate')) {
|
||||||
if (uiOverlay || !combatActive) moveFocus(action)
|
if (uiOverlay || !combatActive) {
|
||||||
|
const active = currentFocusableControl()
|
||||||
|
if (
|
||||||
|
active instanceof HTMLSelectElement
|
||||||
|
&& (action === 'navigateUp' || action === 'navigateDown')
|
||||||
|
&& changeSelectOption(active, action === 'navigateUp' ? -1 : 1)
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
moveFocus(action)
|
||||||
|
}
|
||||||
} else if (action === 'confirm') {
|
} else if (action === 'confirm') {
|
||||||
const active = currentFocusableControl()
|
const active = currentFocusableControl()
|
||||||
if (isTextInput(active)) {
|
if (isTextInput(active)) {
|
||||||
setKeyboardInput(active)
|
setKeyboardInput(active)
|
||||||
window.requestAnimationFrame(() => focusFirstControl())
|
window.requestAnimationFrame(() => focusFirstControl())
|
||||||
|
} else if (active instanceof HTMLSelectElement) {
|
||||||
|
const select = active as HTMLSelectElement & { showPicker?: () => void }
|
||||||
|
if (select.showPicker) select.showPicker()
|
||||||
|
else active.click()
|
||||||
} else if (
|
} else if (
|
||||||
active
|
active
|
||||||
&& active.matches('button:not(:disabled), [role="button"]')
|
&& active.matches('button:not(:disabled), [role="button"]')
|
||||||
@@ -566,12 +656,6 @@ export function InputProvider({ children }: { children: ReactNode }) {
|
|||||||
document.querySelector('[data-combat-active="true"]'),
|
document.querySelector('[data-combat-active="true"]'),
|
||||||
)
|
)
|
||||||
const uiOverlay = hasUiOverlay()
|
const uiOverlay = hasUiOverlay()
|
||||||
const menuDpadActions: Partial<Record<string, InputAction>> = {
|
|
||||||
Button12: 'navigateUp',
|
|
||||||
Button13: 'navigateDown',
|
|
||||||
Button14: 'navigateLeft',
|
|
||||||
Button15: 'navigateRight',
|
|
||||||
}
|
|
||||||
const uiPriority = [
|
const uiPriority = [
|
||||||
'navigateUp',
|
'navigateUp',
|
||||||
'navigateDown',
|
'navigateDown',
|
||||||
@@ -608,18 +692,18 @@ export function InputProvider({ children }: { children: ReactNode }) {
|
|||||||
'navigateLeft',
|
'navigateLeft',
|
||||||
'navigateRight',
|
'navigateRight',
|
||||||
] satisfies InputAction[]
|
] satisfies InputAction[]
|
||||||
const action = menuDpadActions[token] && (!combatActive || uiOverlay)
|
const action = DPAD_NAV_ACTIONS[token] && (!combatActive || uiOverlay)
|
||||||
? menuDpadActions[token]
|
? DPAD_NAV_ACTIONS[token]
|
||||||
: uiOverlay
|
: uiOverlay
|
||||||
? uiPriority.find((candidate) => bindingsRef.current.controller[candidate] === token)
|
? uiPriority.find((candidate) => bindingsRef.current.controller[candidate] === token)
|
||||||
: combatActive && preferencesRef.current.directPartyTargeting
|
: combatActive && preferencesRef.current.directPartyTargeting
|
||||||
? [...directTargetActions, ...combatPriority].find(
|
? [...directTargetActions, ...combatPriority].find(
|
||||||
(candidate) => bindingsRef.current.controller[candidate] === token,
|
(candidate) => bindingsRef.current.controller[candidate] === token,
|
||||||
)
|
)
|
||||||
: combatActive && menuDpadActions[token]
|
: combatActive && DPAD_NAV_ACTIONS[token]
|
||||||
? menuDpadActions[token]
|
? DPAD_NAV_ACTIONS[token]
|
||||||
: !combatActive && menuDpadActions[token]
|
: !combatActive && DPAD_NAV_ACTIONS[token]
|
||||||
? menuDpadActions[token]
|
? DPAD_NAV_ACTIONS[token]
|
||||||
: (combatActive ? combatPriority : INPUT_ACTIONS).find(
|
: (combatActive ? combatPriority : INPUT_ACTIONS).find(
|
||||||
(candidate) => bindingsRef.current.controller[candidate] === token,
|
(candidate) => bindingsRef.current.controller[candidate] === token,
|
||||||
)
|
)
|
||||||
@@ -768,11 +852,11 @@ export function InputProvider({ children }: { children: ReactNode }) {
|
|||||||
const action = INPUT_ACTIONS.find(
|
const action = INPUT_ACTIONS.find(
|
||||||
(candidate) => bindingsRef.current.controller[candidate] === token,
|
(candidate) => bindingsRef.current.controller[candidate] === token,
|
||||||
)
|
)
|
||||||
const canRepeat = action?.startsWith('navigate') ?? false
|
const canRepeat = Boolean(action?.startsWith('navigate') || DPAD_NAV_ACTIONS[token])
|
||||||
const nextRepeat = repeatRef.current[token] ?? 0
|
const nextRepeat = repeatRef.current[token] ?? 0
|
||||||
if (pressed || (canRepeat && time >= nextRepeat)) {
|
if (pressed || (canRepeat && time >= nextRepeat)) {
|
||||||
dispatchControllerToken(token, !pressed)
|
dispatchControllerToken(token, !pressed)
|
||||||
repeatRef.current[token] = time + (pressed ? 360 : 125)
|
repeatRef.current[token] = time + (pressed ? CONTROLLER_REPEAT_INITIAL_MS : CONTROLLER_REPEAT_MS)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ import { DualScreenBottomDisplay, DualScreenProvider, DualScreenStartupPrompt }
|
|||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById('root')!).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
{new URLSearchParams(window.location.search).get('display') === 'bottom' ? (
|
{new URLSearchParams(window.location.search).get('display') === 'bottom' ? (
|
||||||
|
<InputProvider>
|
||||||
<DualScreenBottomDisplay />
|
<DualScreenBottomDisplay />
|
||||||
|
</InputProvider>
|
||||||
) : (
|
) : (
|
||||||
<DualScreenProvider>
|
<DualScreenProvider>
|
||||||
<DualScreenStartupPrompt />
|
<DualScreenStartupPrompt />
|
||||||
|
|||||||
Reference in New Issue
Block a user