271 lines
8.7 KiB
TypeScript
271 lines
8.7 KiB
TypeScript
import { CSSProperties, PointerEvent, useEffect, useRef } from 'react'
|
|
import { proxy, ref, useSnapshot } from 'valtio'
|
|
import { contro } from '../controls'
|
|
import { options } from '../optionsStorage'
|
|
import PixelartIcon from './PixelartIcon'
|
|
import Button from './Button'
|
|
|
|
export type ButtonName = 'action' | 'sneak' | 'break' | 'jump'
|
|
|
|
type ButtonsPositions = Record<ButtonName, [number, number]>
|
|
|
|
const getCurrentAppScaling = () => {
|
|
// body has css property --guiScale
|
|
const guiScale = getComputedStyle(document.body).getPropertyValue('--guiScale')
|
|
return parseFloat(guiScale)
|
|
}
|
|
|
|
export const joystickPointer = proxy({
|
|
pointer: null as { x: number, y: number, pointerId: number } | null,
|
|
joystickInner: null as HTMLDivElement | null,
|
|
})
|
|
|
|
export const handleMovementStickDelta = (e?: { clientX, clientY }) => {
|
|
const max = 32
|
|
let x = 0
|
|
let y = 0
|
|
if (e) {
|
|
const scale = getCurrentAppScaling()
|
|
x = e.clientX - joystickPointer.pointer!.x
|
|
y = e.clientY - joystickPointer.pointer!.y
|
|
x = Math.min(Math.max(x, -max), max) / scale
|
|
y = Math.min(Math.max(y, -max), max) / scale
|
|
}
|
|
|
|
joystickPointer.joystickInner!.style.transform = `translate(${x}px, ${y}px)`
|
|
const vector = {
|
|
x: x / max,
|
|
y: 0,
|
|
z: y / max,
|
|
}
|
|
void contro.emit('movementUpdate', {
|
|
vector,
|
|
soleVector: vector
|
|
})
|
|
}
|
|
|
|
type Props = {
|
|
setupActive: boolean
|
|
closeButtonsSetup: (newPositions?: ButtonsPositions) => void
|
|
foregroundGameActive: boolean
|
|
}
|
|
|
|
const Z_INDEX_INTERACTIBLE = 8
|
|
|
|
export default ({ setupActive, closeButtonsSetup, foregroundGameActive }: Props) => {
|
|
const bot = window.bot as typeof __type_bot | undefined
|
|
const { touchControlsPositions, touchMovementType, touchInteractionType } = useSnapshot(options)
|
|
const buttonsPositions = touchControlsPositions as ButtonsPositions
|
|
|
|
const joystickOuter = useRef<HTMLDivElement>(null)
|
|
const joystickInner = useRef<HTMLDivElement>(null)
|
|
|
|
const { pointer } = useSnapshot(joystickPointer)
|
|
const newButtonPositions = { ...buttonsPositions }
|
|
|
|
const buttonProps = (name: ButtonName) => {
|
|
let active = {
|
|
action: false,
|
|
sneak: bot?.getControlState('sneak'),
|
|
break: false,
|
|
jump: bot?.getControlState('jump'),
|
|
}[name]
|
|
const holdDown = {
|
|
action () {
|
|
if (!bot) return
|
|
document.dispatchEvent(new MouseEvent('mousedown', { button: 2 }))
|
|
bot.mouse.update()
|
|
},
|
|
sneak () {
|
|
void contro.emit('trigger', {
|
|
command: 'general.toggleSneakOrDown',
|
|
schema: null as any,
|
|
})
|
|
active = bot?.getControlState('sneak')
|
|
},
|
|
break () {
|
|
if (!bot) return
|
|
document.dispatchEvent(new MouseEvent('mousedown', { button: 0 }))
|
|
bot.mouse.update()
|
|
active = true
|
|
},
|
|
jump () {
|
|
void contro.emit('trigger', {
|
|
command: 'general.jump',
|
|
schema: null as any,
|
|
})
|
|
active = bot?.controlState.jump
|
|
}
|
|
}
|
|
const holdUp = {
|
|
action () {
|
|
document.dispatchEvent(new MouseEvent('mouseup', { button: 2 }))
|
|
},
|
|
sneak () {
|
|
void contro.emit('release', {
|
|
command: 'general.toggleSneakOrDown',
|
|
schema: null as any,
|
|
})
|
|
active = bot?.getControlState('sneak')
|
|
},
|
|
break () {
|
|
if (!bot) return
|
|
document.dispatchEvent(new MouseEvent('mouseup', { button: 0 }))
|
|
bot.mouse.update()
|
|
active = false
|
|
},
|
|
jump () {
|
|
void contro.emit('release', {
|
|
command: 'general.jump',
|
|
schema: null as any,
|
|
})
|
|
active = bot?.controlState.jump
|
|
}
|
|
}
|
|
|
|
type PType = PointerEvent<HTMLDivElement>
|
|
const pointerup = (e: PType) => {
|
|
const elem = e.currentTarget as HTMLElement
|
|
console.log(e.type, elem.hasPointerCapture(e.pointerId))
|
|
elem.releasePointerCapture(e.pointerId)
|
|
if (!setupActive) {
|
|
holdUp[name]()
|
|
pointerToggledUpdate(e)
|
|
}
|
|
}
|
|
const pointerToggledUpdate = (e) => {
|
|
e.currentTarget.style.background = active ? 'rgba(0, 0, 0, 0.8)' : 'rgba(0, 0, 0, 0.5)'
|
|
}
|
|
let setupPointer = null as { x, y } | null
|
|
return {
|
|
style: {
|
|
position: 'fixed',
|
|
left: `${buttonsPositions[name][0]}%`,
|
|
top: `${buttonsPositions[name][1]}%`,
|
|
borderRadius: '50%',
|
|
width: '32px',
|
|
height: '32px',
|
|
background: active ? 'rgba(0, 0, 0, 0.8)' : 'rgba(0, 0, 0, 0.5)',
|
|
display: 'flex',
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
transition: 'background 0.1s',
|
|
zIndex: Z_INDEX_INTERACTIBLE,
|
|
} satisfies CSSProperties,
|
|
onPointerDown (e: PType) {
|
|
const elem = e.currentTarget as HTMLElement
|
|
elem.setPointerCapture(e.pointerId)
|
|
if (setupActive) {
|
|
setupPointer = { x: e.clientX, y: e.clientY }
|
|
} else {
|
|
holdDown[name]()
|
|
pointerToggledUpdate(e)
|
|
}
|
|
},
|
|
onPointerMove (e: PType) {
|
|
if (setupPointer) {
|
|
const elem = e.currentTarget as HTMLElement
|
|
const size = 32
|
|
const scale = getCurrentAppScaling()
|
|
const xPerc = (e.clientX - (size * scale) / 2) / window.innerWidth * 100
|
|
const yPerc = (e.clientY - (size * scale) / 2) / window.innerHeight * 100
|
|
elem.style.left = `${xPerc}%`
|
|
elem.style.top = `${yPerc}%`
|
|
newButtonPositions[name] = [xPerc, yPerc]
|
|
}
|
|
},
|
|
onPointerUp: pointerup,
|
|
// onPointerCancel: pointerup,
|
|
onLostPointerCapture: pointerup,
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
joystickPointer.joystickInner = joystickInner.current && ref(joystickInner.current)
|
|
// todo antipattern
|
|
}, [foregroundGameActive])
|
|
|
|
if (!foregroundGameActive && !setupActive) return null
|
|
|
|
return <div>
|
|
{touchMovementType === 'modern' && (
|
|
<div
|
|
className='movement_joystick_outer'
|
|
ref={joystickOuter}
|
|
style={{
|
|
display: pointer ? 'flex' : 'none',
|
|
borderRadius: '50%',
|
|
width: 50,
|
|
height: 50,
|
|
border: '2px solid rgba(0, 0, 0, 0.5)',
|
|
backgroundColor: 'rgba(255, 255, div, 0.5)',
|
|
position: 'fixed',
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
translate: '-50% -50%',
|
|
...pointer ? {
|
|
left: `${pointer.x / window.innerWidth * 100}%`,
|
|
top: `${pointer.y / window.innerHeight * 100}%`
|
|
} : {}
|
|
}}
|
|
>
|
|
<div
|
|
className='movement_joystick_inner'
|
|
style={{
|
|
borderRadius: '50%',
|
|
width: 20,
|
|
height: 20,
|
|
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
position: 'absolute',
|
|
}}
|
|
ref={joystickInner}
|
|
/>
|
|
</div>
|
|
)}
|
|
{touchMovementType === 'modern' && (
|
|
<>
|
|
<div {...buttonProps('sneak')}>
|
|
<PixelartIcon iconName='arrow-down' />
|
|
</div>
|
|
<div {...buttonProps('jump')}>
|
|
<PixelartIcon iconName='arrow-up' />
|
|
</div>
|
|
</>
|
|
)}
|
|
{touchInteractionType === 'buttons' && (
|
|
<>
|
|
<div {...buttonProps('action')}>
|
|
<PixelartIcon iconName='circle' />
|
|
</div>
|
|
<div {...buttonProps('break')}>
|
|
<MineIcon />
|
|
</div>
|
|
</>
|
|
)}
|
|
{setupActive && <div style={{
|
|
position: 'fixed',
|
|
bottom: 0,
|
|
display: 'flex',
|
|
justifyContent: 'center',
|
|
gap: 3
|
|
}}
|
|
>
|
|
<Button onClick={() => {
|
|
closeButtonsSetup()
|
|
}}
|
|
>Cancel
|
|
</Button>
|
|
<Button onClick={() => {
|
|
closeButtonsSetup(newButtonPositions)
|
|
}}
|
|
>Apply
|
|
</Button>
|
|
</div>}
|
|
</div>
|
|
}
|
|
|
|
const MineIcon = () => {
|
|
return <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26" width={22} height={22}>
|
|
<path d="M 8 0 L 8 2 L 18 2 L 18 0 L 8 0 z M 18 2 L 18 4 L 20 4 L 20 6 L 22 6 L 22 8 L 24 8 L 24 2 L 22 2 L 18 2 z M 24 8 L 24 18 L 26 18 L 26 8 L 24 8 z M 24 18 L 22 18 L 22 20 L 24 20 L 24 18 z M 22 18 L 22 10 L 20 10 L 20 18 L 22 18 z M 20 10 L 20 8 L 18 8 L 18 10 L 20 10 z M 18 10 L 16 10 L 16 12 L 18 12 L 18 10 z M 16 12 L 14 12 L 14 14 L 16 14 L 16 12 z M 14 14 L 12 14 L 12 16 L 14 16 L 14 14 z M 12 16 L 10 16 L 10 18 L 12 18 L 12 16 z M 10 18 L 8 18 L 8 20 L 10 20 L 10 18 z M 8 20 L 6 20 L 6 22 L 8 22 L 8 20 z M 6 22 L 4 22 L 4 24 L 6 24 L 6 22 z M 4 24 L 2 24 L 2 22 L 0 22 L 0 24 L 0 26 L 2 26 L 4 26 L 4 24 z M 2 22 L 4 22 L 4 20 L 2 20 L 2 22 z M 4 20 L 6 20 L 6 18 L 4 18 L 4 20 z M 6 18 L 8 18 L 8 16 L 6 16 L 6 18 z M 8 16 L 10 16 L 10 14 L 8 14 L 8 16 z M 10 14 L 12 14 L 12 12 L 10 12 L 10 14 z M 12 12 L 14 12 L 14 10 L 12 10 L 12 12 z M 14 10 L 16 10 L 16 8 L 14 8 L 14 10 z M 16 8 L 18 8 L 18 6 L 16 6 L 16 8 z M 16 6 L 16 4 L 8 4 L 8 6 L 16 6 z M 8 4 L 8 2 L 6 2 L 6 4 L 8 4 z" stroke='white' />
|
|
</svg>
|
|
}
|