Compare commits

...
Sign in to create a new pull request.

50 commits

Author SHA1 Message Date
Vitaly Turovsky
4127bf7169 add test fullscreen map 2024-07-07 17:50:06 +03:00
Vitaly
606f7a2a9e
Merge branch 'next' into minimap 2024-07-07 13:34:52 +03:00
gguio
1bd8534650 map rotation. Fixes required: drawing has flaws + parts of world 2024-07-04 00:52:05 +04:00
gguio
5f4a572347 map rotation 2024-07-04 00:23:02 +04:00
gguio
d35db9a9a5 fixed drawing direction 2024-07-03 13:12:18 +04:00
gguio
b357ea2e6e mirror x axis 2024-07-02 22:10:01 +04:00
gguio
f7475d0646 parts of the worlds letters 2024-07-02 15:49:53 +04:00
gguio
08e5d22d86 world parts drawing 2024-07-02 14:52:24 +04:00
gguio
2d343e62d7 things on the border of the map are not clipped 2024-07-02 14:22:51 +04:00
Vitaly
1abc6d53d9 hotfix: hotbar display was broken 2024-07-02 13:03:28 +04:00
gguio
65eae1583e open map on click 2024-06-28 14:38:12 +04:00
gguio
a61fc535e2 localServer warps are in provider 2024-06-28 14:27:19 +04:00
gguio
32a0f4c220 fix delay for warp on border 2024-06-28 14:20:51 +04:00
gguio
2fc14ec420 delay for warp on border 2024-06-28 14:03:22 +04:00
gguio
46ef555b56 warps drawing 2024-06-28 13:50:59 +04:00
Vitaly
78ba42f5dd
Merge branch 'next' into minimap 2024-06-27 15:04:30 +03:00
gguio
58cb23d2d8 saving warps in local server 2024-06-27 14:41:07 +04:00
gguio
f49c529442 ui for set warp 2024-06-27 13:04:31 +04:00
gguio
c4282c9ab7 storybook for map + fields to set warp. Styles are not done 2024-06-26 20:36:35 +04:00
gguio
d960d9a55a storybook for minimap. Toggle full map doesnt work yet 2024-06-26 16:16:46 +04:00
gguio
9e7299c2b0 storybook doesnt worl 2024-06-26 14:45:51 +04:00
gguio
d61adb0a5a screen to set up warp 2024-06-26 14:35:32 +04:00
gguio
3c63c0b240 add new warp logic in MinimapDrawer 2024-06-26 13:39:54 +04:00
gguio
a6880be14c x and y should be otherwise 2024-06-26 13:18:14 +04:00
gguio
e8864447d2 click to map pos fix 2024-06-26 12:46:17 +04:00
gguio
4ef31616c2 mouse click in world coords doesnt count correctly 2024-06-25 15:52:30 +04:00
gguio
03d8e3100f pointer lock 2024-06-22 17:46:39 +04:00
gguio
0dfff262f4 useState used for adapter 2024-06-22 17:00:55 +04:00
gguio
fb84af6105 clean up 2024-06-21 15:17:54 +04:00
gguio
7901a3a041 adapter implementation 2024-06-21 15:10:36 +04:00
gguio
4121fadafa small clean up 2024-06-21 13:49:41 +04:00
gguio
33302c06aa toggle full map 2024-06-21 13:48:27 +04:00
gguio
3b91a17f90 toggle command 2024-06-21 12:09:52 +04:00
gguio
79be8359d8 fix for weired bug with undo button in Keybindings screen 2024-06-20 19:16:56 +04:00
gguio
6ebe049ad1 full map container 2024-06-20 18:24:12 +04:00
gguio
326ef49c7e fix cache 2024-06-20 17:48:36 +04:00
Vitaly Turovsky
ce951fe6dd update squid, fix lint 2024-06-18 19:55:58 +03:00
Vitaly Turovsky
f6194e4628 fix and cleanup code 2024-06-18 15:22:00 +03:00
gguio
817c2d71e3 Merge branch 'next' into minimap 2024-06-18 13:34:04 +04:00
gguio
dfd1cb0028 build doesnt work right 2024-06-18 13:33:23 +04:00
gguio
55ce44585f cacha added. Didnt change anything, refactoring required 2024-06-17 19:59:40 +04:00
gguio
73cf00b1b4 a little faster drawing. Still lags 2024-06-17 13:37:27 +04:00
gguio
362d87c320 stable 2024-06-17 12:20:43 +04:00
gguio
d455b83a6e Merge branch 'next' into minimap 2024-06-15 20:29:11 +04:00
gguio
0f78c74146 map draws and updates. Not optimized 2024-06-15 13:37:35 +04:00
gguio
d42548ea1f fixed eslint in MinimapDrawer 2024-06-14 12:45:37 +04:00
gguio
74f1894867 better scaling 2024-06-13 23:21:41 +04:00
gguio
2696a1e345 drawing map by pixel ex 2024-06-13 23:13:26 +04:00
gguio
d8aabaf99d simple class for map drawing 2024-06-13 22:10:06 +04:00
gguio
e84dc5924e minimap storybook 2024-06-13 21:29:02 +04:00
9 changed files with 829 additions and 0 deletions

View file

@ -51,6 +51,7 @@ export const contro = new ControMax({
},
ui: {
back: [null/* 'Escape' */, 'B'],
toggleMap: ['KeyM'],
leftClick: [null, 'A'],
rightClick: [null, 'Y'],
speedupCursor: [null, 'Left Stick'],

View 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
View 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())

View file

@ -49,6 +49,7 @@
.undo-keyboard,
.undo-gamepad {
aspect-ratio: 1;
min-width: 20px;
}
.button {

View 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
View 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
View 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)
}
}

View 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>
}

View file

@ -19,6 +19,7 @@ import ScoreboardProvider from './react/ScoreboardProvider'
import SignEditorProvider from './react/SignEditorProvider'
import IndicatorEffectsProvider from './react/IndicatorEffectsProvider'
import PlayerListOverlayProvider from './react/PlayerListOverlayProvider'
import MinimapProvider from './react/MinimapProvider'
import HudBarsProvider from './react/HudBarsProvider'
import XPBarProvider from './react/XPBarProvider'
import DebugOverlay from './react/DebugOverlay'
@ -116,6 +117,7 @@ const InGameUi = () => {
<ScoreboardProvider />
<IndicatorEffectsProvider />
<Crosshair />
<MinimapProvider />
</div>
<PauseScreen />