Compare commits
50 commits
next
...
fullscreen
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4127bf7169 | ||
|
|
606f7a2a9e |
||
|
|
1bd8534650 | ||
|
|
5f4a572347 | ||
|
|
d35db9a9a5 | ||
|
|
b357ea2e6e | ||
|
|
f7475d0646 | ||
|
|
08e5d22d86 | ||
|
|
2d343e62d7 | ||
|
|
1abc6d53d9 | ||
|
|
65eae1583e | ||
|
|
a61fc535e2 | ||
|
|
32a0f4c220 | ||
|
|
2fc14ec420 | ||
|
|
46ef555b56 | ||
|
|
78ba42f5dd |
||
|
|
58cb23d2d8 | ||
|
|
f49c529442 | ||
|
|
c4282c9ab7 | ||
|
|
d960d9a55a | ||
|
|
9e7299c2b0 | ||
|
|
d61adb0a5a | ||
|
|
3c63c0b240 | ||
|
|
a6880be14c | ||
|
|
e8864447d2 | ||
|
|
4ef31616c2 | ||
|
|
03d8e3100f | ||
|
|
0dfff262f4 | ||
|
|
fb84af6105 | ||
|
|
7901a3a041 | ||
|
|
4121fadafa | ||
|
|
33302c06aa | ||
|
|
3b91a17f90 | ||
|
|
79be8359d8 | ||
|
|
6ebe049ad1 | ||
|
|
326ef49c7e | ||
|
|
ce951fe6dd | ||
|
|
f6194e4628 | ||
|
|
817c2d71e3 | ||
|
|
dfd1cb0028 | ||
|
|
55ce44585f | ||
|
|
73cf00b1b4 | ||
|
|
362d87c320 | ||
|
|
d455b83a6e | ||
|
|
0f78c74146 | ||
|
|
d42548ea1f | ||
|
|
74f1894867 | ||
|
|
2696a1e345 | ||
|
|
d8aabaf99d | ||
|
|
e84dc5924e |
9 changed files with 829 additions and 0 deletions
|
|
@ -51,6 +51,7 @@ export const contro = new ControMax({
|
||||||
},
|
},
|
||||||
ui: {
|
ui: {
|
||||||
back: [null/* 'Escape' */, 'B'],
|
back: [null/* 'Escape' */, 'B'],
|
||||||
|
toggleMap: ['KeyM'],
|
||||||
leftClick: [null, 'A'],
|
leftClick: [null, 'A'],
|
||||||
rightClick: [null, 'Y'],
|
rightClick: [null, 'Y'],
|
||||||
speedupCursor: [null, 'Left Stick'],
|
speedupCursor: [null, 'Left Stick'],
|
||||||
|
|
|
||||||
18
src/react/FullScreenMap.stories.tsx
Normal file
18
src/react/FullScreenMap.stories.tsx
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import type { Meta, StoryObj } from '@storybook/react'
|
||||||
|
|
||||||
|
import FullScreenMap from './FullScreenMap'
|
||||||
|
|
||||||
|
const meta: Meta<typeof FullScreenMap> = {
|
||||||
|
component: FullScreenMap
|
||||||
|
}
|
||||||
|
|
||||||
|
export default meta
|
||||||
|
type Story = StoryObj<typeof FullScreenMap>
|
||||||
|
|
||||||
|
export const Primary: Story = {
|
||||||
|
args: {
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
noScaling: true
|
||||||
|
},
|
||||||
|
}
|
||||||
132
src/react/FullScreenMap.tsx
Normal file
132
src/react/FullScreenMap.tsx
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
import { useEffect, useRef } from 'react'
|
||||||
|
import * as THREE from 'three'
|
||||||
|
import { DragGesture, WheelGesture, ScrollGesture, MoveGesture, PinchGesture } from '@use-gesture/vanilla'
|
||||||
|
import Gesto from 'gesto'
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const ref = useRef<HTMLCanvasElement>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const canvas = ref.current!
|
||||||
|
const { scene, camera, renderer, addCube, onDestroy } = initScene(canvas)
|
||||||
|
|
||||||
|
// const size = 16 * 4 * 1
|
||||||
|
const size = 10
|
||||||
|
for (let x = -size / 2; x < size / 2; x++) {
|
||||||
|
for (let z = -size / 2; z < size / 2; z++) {
|
||||||
|
addCube(x, z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
renderer.dispose()
|
||||||
|
onDestroy()
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return <canvas
|
||||||
|
style={{
|
||||||
|
position: 'fixed',
|
||||||
|
inset: 0,
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
touchAction: 'none',
|
||||||
|
}}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
const initScene = (canvas: HTMLCanvasElement) => {
|
||||||
|
const abortController = new AbortController()
|
||||||
|
|
||||||
|
const renderer = new THREE.WebGLRenderer({ canvas })
|
||||||
|
renderer.setSize(window.innerWidth, window.innerHeight)
|
||||||
|
renderer.setPixelRatio(window.devicePixelRatio || 1)
|
||||||
|
renderer.setClearColor(0x000000, 1)
|
||||||
|
|
||||||
|
const scene = new THREE.Scene()
|
||||||
|
const camera = new THREE.PerspectiveCamera(75, canvas.width / canvas.height, 0.1, 1000)
|
||||||
|
|
||||||
|
camera.position.set(0, 80, 0)
|
||||||
|
// look down
|
||||||
|
camera.rotation.set(-Math.PI / 2, 0, 0, 'ZYX')
|
||||||
|
|
||||||
|
|
||||||
|
const onResize = () => {
|
||||||
|
renderer.setSize(window.innerWidth, window.innerHeight)
|
||||||
|
camera.aspect = window.innerWidth / window.innerHeight
|
||||||
|
camera.updateProjectionMatrix()
|
||||||
|
}
|
||||||
|
window.addEventListener('resize', onResize, { signal: abortController.signal })
|
||||||
|
onResize()
|
||||||
|
|
||||||
|
let debugText = 'test'
|
||||||
|
const debugTextEl = document.createElement('div')
|
||||||
|
debugTextEl.style.position = 'fixed'
|
||||||
|
debugTextEl.style.top = '0'
|
||||||
|
debugTextEl.style.left = '0'
|
||||||
|
debugTextEl.style.background = 'rgba(0, 0, 0, 0.5)'
|
||||||
|
debugTextEl.style.color = 'white'
|
||||||
|
debugTextEl.style.fontSize = '10px'
|
||||||
|
debugTextEl.style.padding = '5px'
|
||||||
|
document.body.appendChild(debugTextEl)
|
||||||
|
|
||||||
|
renderer.setAnimationLoop(() => {
|
||||||
|
renderer.render(scene, camera)
|
||||||
|
debugTextEl.innerText = debugText
|
||||||
|
})
|
||||||
|
|
||||||
|
// register controls
|
||||||
|
|
||||||
|
const gestures = [] as { destroy: () => void }[]
|
||||||
|
const gesture = new DragGesture(canvas, ({ movement: [mx, my] }) => {
|
||||||
|
camera.position.x -= mx * 0.001
|
||||||
|
camera.position.z -= my * 0.001
|
||||||
|
})
|
||||||
|
const wheel = new WheelGesture(canvas, ({ delta: [dx, dy] }) => {
|
||||||
|
camera.position.y += dy * 0.01
|
||||||
|
})
|
||||||
|
const pinch = new PinchGesture(canvas, ({ delta, movement: [ox, oy], pinching, origin }) => {
|
||||||
|
console.log([ox, oy], delta, pinching, origin)
|
||||||
|
})
|
||||||
|
gestures.push(wheel)
|
||||||
|
gestures.push(gesture)
|
||||||
|
|
||||||
|
let scale = 1
|
||||||
|
// const gesto = new Gesto(canvas, {
|
||||||
|
// container: window,
|
||||||
|
// pinchOutside: true,
|
||||||
|
// }).on('drag', ({ deltaX, deltaY }) => {
|
||||||
|
// camera.position.x -= deltaX * 0.01
|
||||||
|
// camera.position.z -= deltaY * 0.01
|
||||||
|
// }).on('pinchStart', (e) => {
|
||||||
|
// e.datas.scale = scale
|
||||||
|
// }).on('pinch', ({ datas: { scale: newScale } }) => {
|
||||||
|
// scale = newScale
|
||||||
|
// console.log(scale)
|
||||||
|
// camera.position.y += newScale * 0.01
|
||||||
|
// })
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
scene,
|
||||||
|
camera,
|
||||||
|
renderer,
|
||||||
|
onDestroy: () => {
|
||||||
|
abortController.abort()
|
||||||
|
for (const gesture of gestures) {
|
||||||
|
gesture.destroy()
|
||||||
|
}
|
||||||
|
// gesto.unset()
|
||||||
|
},
|
||||||
|
addCube (x, z) {
|
||||||
|
const geometry = new THREE.BoxGeometry(1, 1, 1)
|
||||||
|
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 })
|
||||||
|
const cube = new THREE.Mesh(geometry, material)
|
||||||
|
cube.position.set(x, 0, z)
|
||||||
|
scene.add(cube)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('gesturestart', (e) => e.preventDefault())
|
||||||
|
document.addEventListener('gesturechange', (e) => e.preventDefault())
|
||||||
|
|
@ -49,6 +49,7 @@
|
||||||
.undo-keyboard,
|
.undo-keyboard,
|
||||||
.undo-gamepad {
|
.undo-gamepad {
|
||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
|
min-width: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
|
|
|
||||||
66
src/react/Minimap.stories.tsx
Normal file
66
src/react/Minimap.stories.tsx
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { Vec3 } from 'vec3'
|
||||||
|
import type { Meta, StoryObj } from '@storybook/react'
|
||||||
|
import { WorldWarp } from 'flying-squid/dist/lib/modules/warps'
|
||||||
|
import { TypedEventEmitter } from 'contro-max/build/typedEventEmitter'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
|
import { cleanData } from 'cypress/types/jquery'
|
||||||
|
import Minimap from './Minimap'
|
||||||
|
import { DrawerAdapter, MapUpdates } from './MinimapDrawer'
|
||||||
|
|
||||||
|
const meta: Meta<typeof Minimap> = {
|
||||||
|
component: Minimap,
|
||||||
|
decorators: [
|
||||||
|
(Story, context) => {
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('map updated')
|
||||||
|
adapter.emit('updateMap')
|
||||||
|
|
||||||
|
}, [context.args['fullMap']])
|
||||||
|
|
||||||
|
return <div> <Story /> </div>
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default meta
|
||||||
|
type Story = StoryObj<typeof Minimap>;
|
||||||
|
|
||||||
|
|
||||||
|
class DrawerAdapterImpl extends TypedEventEmitter<MapUpdates> implements DrawerAdapter {
|
||||||
|
playerPosition: Vec3
|
||||||
|
yaw: number
|
||||||
|
warps: WorldWarp[]
|
||||||
|
|
||||||
|
constructor (pos?: Vec3, warps?: WorldWarp[]) {
|
||||||
|
super()
|
||||||
|
this.playerPosition = pos ?? new Vec3(0, 0, 0)
|
||||||
|
this.warps = warps ?? [] as WorldWarp[]
|
||||||
|
}
|
||||||
|
|
||||||
|
getHighestBlockColor (x: number, z:number) {
|
||||||
|
console.log('got color')
|
||||||
|
return 'green'
|
||||||
|
}
|
||||||
|
|
||||||
|
setWarp (name: string, pos: Vec3, color: string, disabled: boolean, world?: string): void {
|
||||||
|
const warp: WorldWarp = { name, x: pos.x, y: pos.y, z: pos.z, world, color, disabled }
|
||||||
|
const index = this.warps.findIndex(w => w.name === name)
|
||||||
|
if (index === -1) {
|
||||||
|
this.warps.push(warp)
|
||||||
|
} else {
|
||||||
|
this.warps[index] = warp
|
||||||
|
}
|
||||||
|
this.emit('updateWarps')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const adapter = new DrawerAdapterImpl()
|
||||||
|
|
||||||
|
export const Primary: Story = {
|
||||||
|
args: {
|
||||||
|
adapter,
|
||||||
|
fullMap: false
|
||||||
|
},
|
||||||
|
}
|
||||||
264
src/react/Minimap.tsx
Normal file
264
src/react/Minimap.tsx
Normal file
|
|
@ -0,0 +1,264 @@
|
||||||
|
import { useRef, useEffect, useState, CSSProperties, Dispatch, SetStateAction } from 'react'
|
||||||
|
import { Vec3 } from 'vec3'
|
||||||
|
import { WorldWarp } from 'flying-squid/dist/lib/modules/warps'
|
||||||
|
import { showModal, hideModal } from '../globalState'
|
||||||
|
import { useIsModalActive } from './utilsApp'
|
||||||
|
import { MinimapDrawer, DrawerAdapter } from './MinimapDrawer'
|
||||||
|
import Input from './Input'
|
||||||
|
import Button from './Button'
|
||||||
|
|
||||||
|
|
||||||
|
export default ({ adapter, fullMap }: { adapter: DrawerAdapter, fullMap?: boolean }) => {
|
||||||
|
const fullMapOpened = useIsModalActive('full-map')
|
||||||
|
const [isWarpInfoOpened, setIsWarpInfoOpened] = useState(false)
|
||||||
|
const canvasTick = useRef(0)
|
||||||
|
const canvasRef = useRef<HTMLCanvasElement>(null)
|
||||||
|
const drawerRef = useRef<MinimapDrawer | null>(null)
|
||||||
|
|
||||||
|
function updateMap () {
|
||||||
|
if (drawerRef.current && canvasTick.current % 2 === 0) {
|
||||||
|
drawerRef.current.draw(adapter.playerPosition)
|
||||||
|
if (canvasTick.current % 300 === 0) {
|
||||||
|
drawerRef.current.deleteOldWorldColors(adapter.playerPosition.x, adapter.playerPosition.z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
canvasTick.current += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleFullMap = () => {
|
||||||
|
if (fullMapOpened) {
|
||||||
|
hideModal({ reactType: 'full-map' })
|
||||||
|
} else {
|
||||||
|
showModal({ reactType: 'full-map' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClickOnMap = (e: MouseEvent) => {
|
||||||
|
drawerRef.current?.setWarpPosOnClick(e, adapter.playerPosition)
|
||||||
|
setIsWarpInfoOpened(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateWarps = () => {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (canvasRef.current && !drawerRef.current) {
|
||||||
|
drawerRef.current = new MinimapDrawer(canvasRef.current, adapter)
|
||||||
|
} else if (canvasRef.current && drawerRef.current) {
|
||||||
|
drawerRef.current.canvas = canvasRef.current
|
||||||
|
}
|
||||||
|
}, [canvasRef.current, fullMapOpened, fullMap])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if ((fullMapOpened || fullMap) && canvasRef.current) {
|
||||||
|
canvasRef.current.addEventListener('click', handleClickOnMap)
|
||||||
|
} else if (!fullMapOpened || !fullMap) {
|
||||||
|
setIsWarpInfoOpened(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
canvasRef.current?.removeEventListener('click', handleClickOnMap)
|
||||||
|
}
|
||||||
|
}, [fullMapOpened, fullMap])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
adapter.on('updateMap', updateMap)
|
||||||
|
adapter.on('toggleFullMap', toggleFullMap)
|
||||||
|
adapter.on('updateWaprs', updateWarps)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
adapter.off('updateMap', updateMap)
|
||||||
|
adapter.off('toggleFullMap', toggleFullMap)
|
||||||
|
adapter.off('updateWaprs', updateWarps)
|
||||||
|
}
|
||||||
|
}, [adapter])
|
||||||
|
|
||||||
|
return fullMapOpened || fullMap ? <div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
isolation: 'isolate',
|
||||||
|
inset: '0px',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
border: '2px solid red',
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.4)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
zIndex: '-1'
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
toggleFullMap()
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<canvas
|
||||||
|
style={{
|
||||||
|
width: '35%',
|
||||||
|
}}
|
||||||
|
width={150}
|
||||||
|
height={150}
|
||||||
|
ref={canvasRef}
|
||||||
|
></canvas>
|
||||||
|
{isWarpInfoOpened && <WarpInfo adapter={adapter} drawer={drawerRef.current} setIsWarpInfoOpened={setIsWarpInfoOpened} />}
|
||||||
|
</div> : <div
|
||||||
|
className='minimap'
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
right: '0px',
|
||||||
|
top: '0px',
|
||||||
|
padding: '5px 5px 0px 0px',
|
||||||
|
border: '2px solid red'
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
toggleFullMap()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<canvas width={80} height={80} ref={canvasRef}></canvas>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
const WarpInfo = (
|
||||||
|
{ adapter, drawer, setIsWarpInfoOpened }
|
||||||
|
:
|
||||||
|
{ adapter: DrawerAdapter, drawer: MinimapDrawer | null, setIsWarpInfoOpened: Dispatch<SetStateAction<boolean>> }
|
||||||
|
) => {
|
||||||
|
const [warp, setWarp] = useState<WorldWarp>({
|
||||||
|
name: '',
|
||||||
|
x: drawer?.lastWarpPos.x ?? 100,
|
||||||
|
y: drawer?.lastWarpPos.y ?? 100,
|
||||||
|
z: drawer?.lastWarpPos.z ?? 100,
|
||||||
|
color: '#d3d3d3',
|
||||||
|
disabled: false,
|
||||||
|
world: adapter.world
|
||||||
|
})
|
||||||
|
|
||||||
|
const posInputStyle: CSSProperties = {
|
||||||
|
flexGrow: '1',
|
||||||
|
}
|
||||||
|
const fieldCont: CSSProperties = {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '5px'
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
inset: '0px',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexDirection: 'column',
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
|
fontSize: '0.8em'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '10px',
|
||||||
|
width: '40%',
|
||||||
|
minWidth: '300px',
|
||||||
|
maxWidth: '400px',
|
||||||
|
padding: '20px',
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
|
border: '2px solid black'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={fieldCont}>
|
||||||
|
<div>
|
||||||
|
Name:
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
onChange={(e) => {
|
||||||
|
if (!e.target) return
|
||||||
|
setWarp(prev => { return { ...prev, name: e.target.value } })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={fieldCont}>
|
||||||
|
<div>
|
||||||
|
X:
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
rootStyles={posInputStyle}
|
||||||
|
defaultValue={drawer?.lastWarpPos.x ?? 100}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (!e.target) return
|
||||||
|
setWarp(prev => { return { ...prev, x: Number(e.target.value) } })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
Y:
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
rootStyles={posInputStyle}
|
||||||
|
defaultValue={drawer?.lastWarpPos.y ?? 100}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (!e.target) return
|
||||||
|
setWarp(prev => { return { ...prev, y: Number(e.target.value) } })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
Z:
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
rootStyles={posInputStyle}
|
||||||
|
defaultValue={drawer?.lastWarpPos.z ?? 100}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (!e.target) return
|
||||||
|
setWarp(prev => { return { ...prev, z: Number(e.target.value) } })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={fieldCont}>
|
||||||
|
<div>Color:</div>
|
||||||
|
<Input
|
||||||
|
placeholder={'#232323 or rgb(0, 0, 0)'}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (!e.target) return
|
||||||
|
setWarp(prev => { return { ...prev, color: e.target.value } })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={fieldCont} >
|
||||||
|
<div>Disabled:</div>
|
||||||
|
<input
|
||||||
|
type={'checkbox'}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (!e.target) return
|
||||||
|
setWarp(prev => { return { ...prev, disabled: e.target.checked } })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={fieldCont}>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
adapter.setWarp(
|
||||||
|
warp.name,
|
||||||
|
new Vec3(warp.x, warp.y, warp.z),
|
||||||
|
warp.color ?? '#d3d3d3',
|
||||||
|
warp.disabled ?? false,
|
||||||
|
warp.world ?? 'overworld'
|
||||||
|
)
|
||||||
|
console.log(adapter.warps)
|
||||||
|
setIsWarpInfoOpened(false)
|
||||||
|
}}
|
||||||
|
>Add</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setIsWarpInfoOpened(false)
|
||||||
|
}}
|
||||||
|
>Cancel</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
268
src/react/MinimapDrawer.ts
Normal file
268
src/react/MinimapDrawer.ts
Normal file
|
|
@ -0,0 +1,268 @@
|
||||||
|
import { Vec3 } from 'vec3'
|
||||||
|
import { TypedEventEmitter } from 'contro-max/build/typedEventEmitter'
|
||||||
|
import { WorldWarp } from 'flying-squid/dist/lib/modules/warps'
|
||||||
|
|
||||||
|
type BotType = Omit<import('mineflayer').Bot, 'world' | '_client'> & {
|
||||||
|
world: Omit<import('prismarine-world').world.WorldSync, 'getBlock'> & {
|
||||||
|
getBlock: (pos: import('vec3').Vec3) => import('prismarine-block').Block | null
|
||||||
|
}
|
||||||
|
_client: Omit<import('minecraft-protocol').Client, 'on'> & {
|
||||||
|
write: typeof import('../generatedClientPackets').clientWrite
|
||||||
|
on: typeof import('../generatedServerPackets').clientOn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MapUpdates = {
|
||||||
|
updateBlockColor: (pos: Vec3) => void
|
||||||
|
updatePlayerPosition: () => void
|
||||||
|
updateWarps: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DrawerAdapter extends TypedEventEmitter<MapUpdates> {
|
||||||
|
getHighestBlockColor: (x: number, z: number) => string
|
||||||
|
playerPosition: Vec3
|
||||||
|
warps: WorldWarp[]
|
||||||
|
world?: string
|
||||||
|
yaw: number
|
||||||
|
setWarp: (name: string, pos: Vec3, color: string, disabled: boolean, world?: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MinimapDrawer {
|
||||||
|
centerX: number
|
||||||
|
centerY: number
|
||||||
|
_mapSize: number
|
||||||
|
radius: number
|
||||||
|
ctx: CanvasRenderingContext2D
|
||||||
|
_canvas: HTMLCanvasElement
|
||||||
|
worldColors: { [key: string]: string } = {}
|
||||||
|
lastBotPos: Vec3
|
||||||
|
lastWarpPos: Vec3
|
||||||
|
mapPixel: number
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
canvas: HTMLCanvasElement,
|
||||||
|
public adapter: DrawerAdapter
|
||||||
|
) {
|
||||||
|
this.canvas = canvas
|
||||||
|
this.adapter = adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
get canvas () {
|
||||||
|
return this._canvas
|
||||||
|
}
|
||||||
|
|
||||||
|
set canvas (canvas: HTMLCanvasElement) {
|
||||||
|
this.ctx = canvas.getContext('2d', { willReadFrequently: true })!
|
||||||
|
this.ctx.imageSmoothingEnabled = false
|
||||||
|
this.radius = Math.floor(Math.min(canvas.width, canvas.height) / 2.2)
|
||||||
|
this._mapSize = this.radius * 2
|
||||||
|
this.mapPixel = Math.floor(this.radius * 2 / this.mapSize)
|
||||||
|
this.centerX = canvas.width / 2
|
||||||
|
this.centerY = canvas.height / 2
|
||||||
|
this._canvas = canvas
|
||||||
|
}
|
||||||
|
|
||||||
|
get mapSize () {
|
||||||
|
return this._mapSize
|
||||||
|
}
|
||||||
|
|
||||||
|
set mapSize (mapSize: number) {
|
||||||
|
this._mapSize = mapSize
|
||||||
|
this.mapPixel = Math.floor(this.radius * 2 / this.mapSize)
|
||||||
|
this.draw(this.lastBotPos)
|
||||||
|
}
|
||||||
|
|
||||||
|
draw (
|
||||||
|
botPos: Vec3,
|
||||||
|
getHighestBlockColor?: DrawerAdapter['getHighestBlockColor'],
|
||||||
|
) {
|
||||||
|
this.ctx.clearRect(
|
||||||
|
this.centerX - this.radius,
|
||||||
|
this.centerY - this.radius,
|
||||||
|
this.radius * 2,
|
||||||
|
this.radius * 2
|
||||||
|
)
|
||||||
|
|
||||||
|
this.lastBotPos = botPos
|
||||||
|
this.updateWorldColors(getHighestBlockColor ?? this.adapter.getHighestBlockColor, botPos.x, botPos.z)
|
||||||
|
this.drawWarps()
|
||||||
|
this.rotateMap()
|
||||||
|
this.drawPartsOfWorld()
|
||||||
|
}
|
||||||
|
|
||||||
|
updateWorldColors (
|
||||||
|
getHighestBlockColor: DrawerAdapter['getHighestBlockColor'],
|
||||||
|
x: number,
|
||||||
|
z: number
|
||||||
|
) {
|
||||||
|
const left = this.centerX - this.radius
|
||||||
|
const top = this.centerY - this.radius
|
||||||
|
|
||||||
|
this.ctx.save()
|
||||||
|
|
||||||
|
this.ctx.beginPath()
|
||||||
|
this.ctx.arc(this.centerX, this.centerY, this.radius, 0, Math.PI * 2, true)
|
||||||
|
this.ctx.clip()
|
||||||
|
|
||||||
|
for (let row = 0; row < this.mapSize; row += 1) {
|
||||||
|
for (let col = 0; col < this.mapSize; col += 1) {
|
||||||
|
this.ctx.fillStyle = this.getHighestBlockColorCached(
|
||||||
|
getHighestBlockColor,
|
||||||
|
x - this.mapSize / 2 + col,
|
||||||
|
z - this.mapSize / 2 + row
|
||||||
|
)
|
||||||
|
this.ctx.fillRect(
|
||||||
|
left + this.mapPixel * col,
|
||||||
|
top + this.mapPixel * row,
|
||||||
|
this.mapPixel,
|
||||||
|
this.mapPixel
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const clippedImage = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height)
|
||||||
|
this.ctx.restore()
|
||||||
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
|
||||||
|
this.ctx.putImageData(clippedImage, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
getHighestBlockColorCached (
|
||||||
|
getHighestBlockColor: DrawerAdapter['getHighestBlockColor'],
|
||||||
|
x: number,
|
||||||
|
z: number
|
||||||
|
) {
|
||||||
|
const roundX = Math.floor(x)
|
||||||
|
const roundZ = Math.floor(z)
|
||||||
|
const key = `${roundX},${roundZ}`
|
||||||
|
if (this.worldColors[key]) {
|
||||||
|
return this.worldColors[key]
|
||||||
|
}
|
||||||
|
const color = getHighestBlockColor(x, z)
|
||||||
|
if (color !== 'white') this.worldColors[key] = color
|
||||||
|
return color
|
||||||
|
}
|
||||||
|
|
||||||
|
getDistance (x1: number, z1: number, x2: number, z2: number): number {
|
||||||
|
return Math.hypot((x2 - x1), (z2 - z1))
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteOldWorldColors (currX: number, currZ: number) {
|
||||||
|
for (const key of Object.keys(this.worldColors)) {
|
||||||
|
const [x, z] = key.split(',').map(Number)
|
||||||
|
if (this.getDistance(x, z, currX, currZ) > this.radius * 5) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||||
|
delete this.worldColors[`${x},${z}`]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setWarpPosOnClick (e: MouseEvent, botPos: Vec3) {
|
||||||
|
if (!e.target) return
|
||||||
|
const rect = (e.target as HTMLCanvasElement).getBoundingClientRect()
|
||||||
|
const z = (e.pageY - rect.top) * this.canvas.width / rect.width
|
||||||
|
const x = (e.pageX - rect.left) * this.canvas.height / rect.height
|
||||||
|
const worldX = x - this.mapSize / 2
|
||||||
|
const worldZ = z - this.mapSize / 2
|
||||||
|
|
||||||
|
// console.log([(botPos.x + worldX).toFixed(0), (botPos.z + worldZ).toFixed(0)])
|
||||||
|
this.lastWarpPos = new Vec3(Math.floor(botPos.x + worldX), botPos.y, Math.floor(botPos.z + worldZ))
|
||||||
|
}
|
||||||
|
|
||||||
|
drawWarps () {
|
||||||
|
for (const warp of this.adapter.warps) {
|
||||||
|
const distance = this.getDistance(
|
||||||
|
this.adapter.playerPosition.x,
|
||||||
|
this.adapter.playerPosition.z,
|
||||||
|
warp.x,
|
||||||
|
warp.z
|
||||||
|
)
|
||||||
|
if (distance > this.mapSize) continue
|
||||||
|
const z = Math.floor((this.mapSize / 2 - this.adapter.playerPosition.z + warp.z))
|
||||||
|
const x = Math.floor((this.mapSize / 2 - this.adapter.playerPosition.x + warp.x))
|
||||||
|
const dz = z - this.centerX
|
||||||
|
const dx = x - this.centerY
|
||||||
|
const circleDist = Math.hypot(dx, dz)
|
||||||
|
|
||||||
|
const angle = Math.atan2(dz, dx)
|
||||||
|
const circleZ = circleDist > this.mapSize / 2 ? this.centerX + this.mapSize / 2 * Math.sin(angle) : z
|
||||||
|
const circleX = circleDist > this.mapSize / 2 ? this.centerY + this.mapSize / 2 * Math.cos(angle) : x
|
||||||
|
this.ctx.beginPath()
|
||||||
|
this.ctx.arc(circleX, circleZ, circleDist > this.mapSize / 2 ? 1.5 : 2, 0, Math.PI * 2, false)
|
||||||
|
this.ctx.strokeStyle = 'black'
|
||||||
|
this.ctx.lineWidth = 1
|
||||||
|
this.ctx.stroke()
|
||||||
|
this.ctx.fillStyle = warp.disabled ? 'rgba(255, 255, 255, 0.4)' : warp.color ?? '#d3d3d3'
|
||||||
|
this.ctx.fill()
|
||||||
|
this.ctx.closePath()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drawPartsOfWorld () {
|
||||||
|
this.ctx.fillStyle = 'white'
|
||||||
|
this.ctx.shadowOffsetX = 1
|
||||||
|
this.ctx.shadowOffsetY = 1
|
||||||
|
this.ctx.shadowColor = 'black'
|
||||||
|
this.ctx.font = `${this.radius / 4}px serif`
|
||||||
|
this.ctx.textAlign = 'center'
|
||||||
|
this.ctx.textBaseline = 'middle'
|
||||||
|
this.ctx.strokeStyle = 'black'
|
||||||
|
this.ctx.lineWidth = 1
|
||||||
|
|
||||||
|
const angle = this.adapter.yaw % Math.PI
|
||||||
|
const angleS = angle + Math.PI
|
||||||
|
const angleW = angle + Math.PI * 3 / 2
|
||||||
|
const angleE = angle + Math.PI / 2
|
||||||
|
|
||||||
|
this.ctx.strokeText(
|
||||||
|
'N',
|
||||||
|
this.centerX + this.radius * Math.cos(angle),
|
||||||
|
this.centerY + this.radius * Math.sin(angle)
|
||||||
|
)
|
||||||
|
this.ctx.strokeText(
|
||||||
|
'S',
|
||||||
|
this.centerX + this.radius * Math.cos(angleS),
|
||||||
|
this.centerY + this.radius * Math.sin(angleS)
|
||||||
|
)
|
||||||
|
this.ctx.strokeText(
|
||||||
|
'W',
|
||||||
|
this.centerX + this.radius * Math.cos(angleW),
|
||||||
|
this.centerY + this.radius * Math.sin(angleW)
|
||||||
|
)
|
||||||
|
this.ctx.strokeText(
|
||||||
|
'E',
|
||||||
|
this.centerX + this.radius * Math.cos(angleE),
|
||||||
|
this.centerY + this.radius * Math.sin(angleE)
|
||||||
|
)
|
||||||
|
this.ctx.fillText(
|
||||||
|
'N',
|
||||||
|
this.centerX + this.radius * Math.cos(angle),
|
||||||
|
this.centerY + this.radius * Math.sin(angle)
|
||||||
|
)
|
||||||
|
this.ctx.fillText(
|
||||||
|
'S',
|
||||||
|
this.centerX + this.radius * Math.cos(angleS),
|
||||||
|
this.centerY + this.radius * Math.sin(angleS)
|
||||||
|
)
|
||||||
|
this.ctx.fillText(
|
||||||
|
'W',
|
||||||
|
this.centerX + this.radius * Math.cos(angleW),
|
||||||
|
this.centerY + this.radius * Math.sin(angleW)
|
||||||
|
)
|
||||||
|
this.ctx.fillText(
|
||||||
|
'E',
|
||||||
|
this.centerX + this.radius * Math.cos(angleE),
|
||||||
|
this.centerY + this.radius * Math.sin(angleE)
|
||||||
|
)
|
||||||
|
|
||||||
|
this.ctx.shadowOffsetX = 0
|
||||||
|
this.ctx.shadowOffsetY = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
rotateMap () {
|
||||||
|
this.ctx.setTransform(1, 0, 0, 1, 0, 0)
|
||||||
|
const angle = this.adapter.yaw % Math.PI
|
||||||
|
this.ctx.translate(this.centerX, this.centerY)
|
||||||
|
this.ctx.rotate(angle)
|
||||||
|
this.ctx.translate(-this.centerX, -this.centerY)
|
||||||
|
}
|
||||||
|
}
|
||||||
77
src/react/MinimapProvider.tsx
Normal file
77
src/react/MinimapProvider.tsx
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { Vec3 } from 'vec3'
|
||||||
|
import { WorldWarp } from 'flying-squid/dist/lib/modules/warps'
|
||||||
|
import { TypedEventEmitter } from 'contro-max/build/typedEventEmitter'
|
||||||
|
import BlockData from '../../prismarine-viewer/viewer/lib/moreBlockDataGenerated.json'
|
||||||
|
import { contro } from '../controls'
|
||||||
|
import Minimap from './Minimap'
|
||||||
|
import { DrawerAdapter, MapUpdates } from './MinimapDrawer'
|
||||||
|
|
||||||
|
export class DrawerAdapterImpl extends TypedEventEmitter<MapUpdates> implements DrawerAdapter {
|
||||||
|
playerPosition: Vec3
|
||||||
|
yaw: number
|
||||||
|
warps: WorldWarp[]
|
||||||
|
world: string
|
||||||
|
|
||||||
|
constructor (pos?: Vec3, warps?: WorldWarp[]) {
|
||||||
|
super()
|
||||||
|
this.playerPosition = pos ?? new Vec3(0, 0, 0)
|
||||||
|
this.warps = warps ?? [] as WorldWarp[]
|
||||||
|
}
|
||||||
|
|
||||||
|
getHighestBlockColor (x: number, z:number) {
|
||||||
|
let block = null as import('prismarine-block').Block | null
|
||||||
|
let { height } = (bot.game as any)
|
||||||
|
const airBlocks = new Set(['air', 'cave_air', 'void_air'])
|
||||||
|
do {
|
||||||
|
block = bot.world.getBlock(new Vec3(x, height, z))
|
||||||
|
height -= 1
|
||||||
|
} while (airBlocks.has(block?.name ?? ''))
|
||||||
|
const color = BlockData.colors[block?.name ?? ''] ?? 'white'
|
||||||
|
return color
|
||||||
|
}
|
||||||
|
|
||||||
|
setWarp (name: string, pos: Vec3, color: string, disabled: boolean, world?: string): void {
|
||||||
|
this.world = bot.game.dimension
|
||||||
|
const warp: WorldWarp = { name, x: pos.x, y: pos.y, z: pos.z, world: world ?? this.world, color, disabled }
|
||||||
|
const index = this.warps.findIndex(w => w.name === name)
|
||||||
|
if (index === -1) {
|
||||||
|
this.warps.push(warp)
|
||||||
|
} else {
|
||||||
|
this.warps[index] = warp
|
||||||
|
}
|
||||||
|
// if (localServer) void localServer.setWarp(warp)
|
||||||
|
this.emit('updateWarps')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const [adapter] = useState(() => new DrawerAdapterImpl(bot.entity.position, localServer?.warps))
|
||||||
|
|
||||||
|
const updateMap = () => {
|
||||||
|
if (!adapter) return
|
||||||
|
adapter.playerPosition = bot.entity.position
|
||||||
|
adapter.yaw = bot.entity.yaw
|
||||||
|
adapter.emit('updateMap')
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleFullMap = ({ command }) => {
|
||||||
|
if (!adapter) return
|
||||||
|
if (command === 'ui.toggleMap') adapter.emit('toggleFullMap')
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
bot.on('move', updateMap)
|
||||||
|
|
||||||
|
contro.on('trigger', toggleFullMap)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
bot.off('move', updateMap)
|
||||||
|
contro.off('trigger', toggleFullMap)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return <div>
|
||||||
|
<Minimap adapter={adapter} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,7 @@ import ScoreboardProvider from './react/ScoreboardProvider'
|
||||||
import SignEditorProvider from './react/SignEditorProvider'
|
import SignEditorProvider from './react/SignEditorProvider'
|
||||||
import IndicatorEffectsProvider from './react/IndicatorEffectsProvider'
|
import IndicatorEffectsProvider from './react/IndicatorEffectsProvider'
|
||||||
import PlayerListOverlayProvider from './react/PlayerListOverlayProvider'
|
import PlayerListOverlayProvider from './react/PlayerListOverlayProvider'
|
||||||
|
import MinimapProvider from './react/MinimapProvider'
|
||||||
import HudBarsProvider from './react/HudBarsProvider'
|
import HudBarsProvider from './react/HudBarsProvider'
|
||||||
import XPBarProvider from './react/XPBarProvider'
|
import XPBarProvider from './react/XPBarProvider'
|
||||||
import DebugOverlay from './react/DebugOverlay'
|
import DebugOverlay from './react/DebugOverlay'
|
||||||
|
|
@ -116,6 +117,7 @@ const InGameUi = () => {
|
||||||
<ScoreboardProvider />
|
<ScoreboardProvider />
|
||||||
<IndicatorEffectsProvider />
|
<IndicatorEffectsProvider />
|
||||||
<Crosshair />
|
<Crosshair />
|
||||||
|
<MinimapProvider />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PauseScreen />
|
<PauseScreen />
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue