UI reorganized and options (#257)
* added readme pt-pt * ui reorganized, keybinds, foodbar, panorama, etc
|
@ -4,8 +4,8 @@
|
|||
[](https://discord.gg/GsEFRM8)
|
||||
[](https://gitpod.io/#https://github.com/PrismarineJS/prismarine-web-client)
|
||||
|
||||
| 🇺🇸 [English](README.md) | 🇷🇺 [Russian](README_RU.md) |
|
||||
| ----------------------- | -------------------------- |
|
||||
| 🇺🇸 [English](README.md) | 🇷🇺 [Russian](README_RU.md) | 🇵🇹 [Portuguese](README_PT.md) |
|
||||
| ----------------------- | -------------------------- | ---------------------------- |
|
||||
|
||||
A Minecraft client running in a web page. **Live demo at https://webclient.prismarine.js.org/**
|
||||
|
||||
|
|
104
README_PT.md
Normal file
|
@ -0,0 +1,104 @@
|
|||
# prismarine-web-client
|
||||
[](http://npmjs.com/package/prismarine-web-client)
|
||||
[](https://github.com/PrismarineJS/prismarine-web-client/actions?query=workflow%3A%22CI%22)
|
||||
[](https://discord.gg/GsEFRM8)
|
||||
[](https://gitpod.io/#https://github.com/PrismarineJS/prismarine-web-client)
|
||||
|
||||
| 🇺🇸 [English](README.md) | 🇷🇺 [Russian](README_RU.md) | 🇵🇹 [Portuguese](README_PT.md) |
|
||||
| ----------------------- | -------------------------- | ---------------------------- |
|
||||
|
||||
Um cliente de Minecraft a funcionar numa página web. **Demostração em https://webclient.prismarine.js.org/**
|
||||
|
||||
## Como functiona
|
||||
prismarine-web-client executa mineflayer e prismarine-viewer no teu navegador, que se conecta por WebSocket a uma proxy
|
||||
que traduz o conexão do WebSocket em TCP para poderes conectar-te a servidores normais do Minecraft. Prismarine-web-client é basiado em:
|
||||
* [prismarine-viewer](https://github.com/PrismarineJS/prismarine-viewer) para renderizar o mundo
|
||||
* [mineflayer](https://github.com/PrismarineJS/mineflayer) um API incrível do cliente de Minecraft
|
||||
|
||||
Da uma olhada nestes módulos se quiseres entender mais sobre como isto funciona e poderes contribuir!
|
||||
|
||||
## Captura de tela
|
||||

|
||||
|
||||
## Demostração ao vivo
|
||||
Clica neste endereço para o abrires no navegador, sem instalação necessária: https://webclient.prismarine.js.org/
|
||||
|
||||
*Testado no Chrome & Firefox para plataformas desktop.*
|
||||
|
||||
## Uso
|
||||
Para hospedá-lo por si próprio, execute estes comandos no bash:
|
||||
```bash
|
||||
$ npm install -g prismarine-web-client
|
||||
$ prismarine-web-client
|
||||
```
|
||||
Finalmente, abra `http://localhost:8080` no seu navigador.
|
||||
|
||||
## Conteúdos
|
||||
|
||||
* Mostra criaturas e os jogadores
|
||||
* Mostra os blocos
|
||||
* Movimento (podes mover-te, e também ver entidades a mover-se em tempo real)
|
||||
* Colocar e destruir blocos
|
||||
|
||||
## Planeamentos
|
||||
* Containers (inventário, baús, etc.)
|
||||
* Sons
|
||||
* Mais interações no mundo (atacar entidades, etc.)
|
||||
* Renderizar cosméticos (ciclo dia-noite, nevoeiro, etc.)
|
||||
|
||||
## Desenvolvimentos
|
||||
|
||||
Se estiveres a contribuir/fazer alterações, precisas intalá-lo de outra forma.
|
||||
|
||||
Primeiro, clona o repositório.
|
||||
|
||||
Depois, defina o seu diretório de trabalho para o do repositório. Por examplo:
|
||||
```bash
|
||||
$ cd ~/prismarine-web-client/
|
||||
```
|
||||
|
||||
Finalmente, execute
|
||||
|
||||
```bash
|
||||
$ npm install
|
||||
$ npm start
|
||||
```
|
||||
|
||||
Isto vai começar o express e webpack no modo de desenvolvimento; quando salvares um arquivo, o build vai ser executado de novo (demora 5s),
|
||||
e podes atualizar a página para veres o novo resultado.
|
||||
|
||||
Conecta em http://localhost:8080 no teu navegador.
|
||||
|
||||
Poderás ter que desativar o auto salvar no teu IDE para evitar estar constantemente a reconstruir; see https://webpack.js.org/guides/development/#adjusting-your-text-editor.
|
||||
|
||||
Para conferir a build de produção (vai demorar alguns minutos para terminar), podes executar `npm run build-start`.
|
||||
|
||||
Se estiveres interessado em contribuir, podes dar uma vista de olhos nos projetos em https://github.com/PrismarineJS/prismarine-web-client/projects.
|
||||
|
||||
Algumas variáveis estão expostas no objeto global ``window`` para depuração:
|
||||
* ``bot``
|
||||
* ``viewer``
|
||||
* ``mcData``
|
||||
* ``worldView``
|
||||
* ``Vec3``
|
||||
* ``pathfinder``
|
||||
* ``debugMenu``
|
||||
|
||||
### Adicionar coisas no debugMenu
|
||||
|
||||
debugMenu.customEntries['myKey'] = 'myValue'
|
||||
delete debugMenu.customEntries['myKey']
|
||||
|
||||
### Alguns exemplos de depuração
|
||||
|
||||
Na devtools do chrome:
|
||||
|
||||
* `bot.chat('test')` permite usar o chat
|
||||
* `bot.chat(JSON.stringify(Object.values(bot.players).map(({username, ping}) => ({username, ping}))))` display the ping of everyone
|
||||
* `window.bot.entity.position.y += 5` saltar
|
||||
* `bot.chat(JSON.stringify(bot.findBlock({matching:(block) => block.name==='diamond_ore', maxDistance:256}).position))` encontrar a posição de um bloco de diamante
|
||||
* `bot.physics.stepHeight = 2` permite andar sobre os blocos
|
||||
* `bot.physics.sprintSpeed = 5` andar mais rápido
|
||||
* `bot.loadPlugin(pathfinder.pathfinder)` em seguida `bot.pathfinder.goto(new pathfinder.goals.GoalXZ(100, 100))` para ir para a posição 100, 100
|
||||
|
||||
Para mais ideas de depuração, leia o documento [mineflayer](https://github.com/PrismarineJS/mineflayer).
|
|
@ -4,8 +4,8 @@
|
|||
[](https://discord.gg/GsEFRM8)
|
||||
[](https://gitpod.io/#https://github.com/PrismarineJS/prismarine-web-client)
|
||||
|
||||
| 🇺🇸 [English](README.md) | 🇷🇺 [Russian](README_RU.md) |
|
||||
| ----------------------- | -------------------------- |
|
||||
| 🇺🇸 [English](README.md) | 🇷🇺 [Russian](README_RU.md) | 🇵🇹 [Portuguese](README_PT.md) |
|
||||
| ----------------------- | -------------------------- | ---------------------------- |
|
||||
|
||||
Клиент Minecraft, запущенный на веб-странице. **Демонстрация на https://webclient.prismarine.js.org/**
|
||||
|
||||
|
|
BIN
assets/click_stereo.ogg
Normal file
BIN
extra-textures/background/panorama_0.png
Normal file
After Width: | Height: | Size: 859 KiB |
BIN
extra-textures/background/panorama_1.png
Normal file
After Width: | Height: | Size: 952 KiB |
BIN
extra-textures/background/panorama_2.png
Normal file
After Width: | Height: | Size: 704 KiB |
BIN
extra-textures/background/panorama_3.png
Normal file
After Width: | Height: | Size: 684 KiB |
BIN
extra-textures/background/panorama_4.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
extra-textures/background/panorama_5.png
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
extra-textures/edition.png
Normal file
After Width: | Height: | Size: 433 B |
BIN
extra-textures/widgets.png
Normal file
After Width: | Height: | Size: 15 KiB |
18
index.html
|
@ -17,14 +17,14 @@
|
|||
<link rel="manifest" href="manifest.json" crossorigin="use-credentials">
|
||||
</head>
|
||||
<body>
|
||||
<game-menu id="game-menu" style="display: none;"></game-menu>
|
||||
<debug-menu id="debugmenu" style="display: none;"></debug-menu>
|
||||
<player-list id="playerlist" style="display: none;"></player-list>
|
||||
<cross-hair id="crosshair" style="display: none;"></cross-hair>
|
||||
<chat-box id="chatbox" style="display: none;"></chat-box>
|
||||
<health-bar id="healthbar" style="display: none;"></health-bar>
|
||||
<hot-bar id="hotbar" style="display: none;"></hot-bar>
|
||||
<loading-screen id="loading-background" style="display: none;"></loading-screen>
|
||||
<prismarine-menu id="prismarine-menu"></prismarine-menu>
|
||||
<div id="ui-root">
|
||||
<pmui-hud id="hud" style="display: none;"></pmui-hud>
|
||||
<pmui-pausescreen id="pause-screen" style="display: none;"></pmui-pausescreen>
|
||||
<pmui-loadingscreen id="loading-screen" style="display: none;"></pmui-loadingscreen>
|
||||
<pmui-playscreen id="play-screen" style="display: none;"></pmui-playscreen>
|
||||
<pmui-keybindsscreen id="keybinds-screen" style="display: none;"></pmui-keybindsscreen>
|
||||
<pmui-optionsscreen id="options-screen" style="display: none;"></pmui-optionsscreen>
|
||||
<pmui-titlescreen id="title-screen" style="display: block;"></pmui-titlescreen>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
257
index.js
|
@ -1,13 +1,22 @@
|
|||
/* global THREE */
|
||||
require('./lib/menu')
|
||||
require('./lib/loading_screen')
|
||||
require('./lib/healthbar')
|
||||
require('./lib/hotbar')
|
||||
require('./lib/gameMenu')
|
||||
require('./lib/chat')
|
||||
require('./lib/crosshair')
|
||||
require('./lib/playerlist')
|
||||
require('./lib/debugmenu')
|
||||
|
||||
require('./lib/menus/components/button')
|
||||
require('./lib/menus/components/edit_box')
|
||||
require('./lib/menus/components/slider')
|
||||
require('./lib/menus/components/hotbar')
|
||||
require('./lib/menus/components/health_bar')
|
||||
require('./lib/menus/components/food_bar')
|
||||
require('./lib/menus/components/breath_bar')
|
||||
require('./lib/menus/components/debug_overlay')
|
||||
require('./lib/menus/components/playerlist_overlay')
|
||||
require('./lib/menus/hud')
|
||||
require('./lib/menus/play_screen')
|
||||
require('./lib/menus/pause_screen')
|
||||
require('./lib/menus/loading_screen')
|
||||
require('./lib/menus/keybinds_screen')
|
||||
require('./lib/menus/options_screen')
|
||||
require('./lib/menus/title_screen')
|
||||
|
||||
const net = require('net')
|
||||
const Cursor = require('./lib/cursor')
|
||||
|
@ -45,43 +54,60 @@ document.body.appendChild(renderer.domElement)
|
|||
const viewer = new Viewer(renderer)
|
||||
|
||||
// Menu panorama background
|
||||
function getPanoramaMesh () {
|
||||
const geometry = new THREE.SphereGeometry(500, 60, 40)
|
||||
geometry.scale(-1, 1, 1)
|
||||
const texture = new THREE.TextureLoader().load('title_blured.jpg')
|
||||
const material = new THREE.MeshBasicMaterial({ map: texture })
|
||||
const mesh = new THREE.Mesh(geometry, material)
|
||||
mesh.rotation.y = Math.PI
|
||||
mesh.onBeforeRender = () => {
|
||||
mesh.rotation.y += 0.0005
|
||||
mesh.rotation.x = -Math.sin(mesh.rotation.y * 3) * 0.3
|
||||
function addPanoramaCubeMap () {
|
||||
let time = 0
|
||||
viewer.camera = new THREE.PerspectiveCamera(85, window.innerWidth / window.innerHeight, 0.05, 1000)
|
||||
viewer.camera.updateProjectionMatrix()
|
||||
viewer.camera.position.set(0, 0, 0)
|
||||
viewer.camera.rotation.set(0, 0, 0)
|
||||
const panorGeo = new THREE.BoxGeometry(1000, 1000, 1000)
|
||||
|
||||
const loader = new THREE.TextureLoader()
|
||||
const panorMaterials = [
|
||||
new THREE.MeshBasicMaterial({ map: loader.load('extra-textures/background/panorama_1.png'), transparent: true, side: THREE.DoubleSide }), // WS
|
||||
new THREE.MeshBasicMaterial({ map: loader.load('extra-textures/background/panorama_3.png'), transparent: true, side: THREE.DoubleSide }), // ES
|
||||
new THREE.MeshBasicMaterial({ map: loader.load('extra-textures/background/panorama_4.png'), transparent: true, side: THREE.DoubleSide }), // Up
|
||||
new THREE.MeshBasicMaterial({ map: loader.load('extra-textures/background/panorama_5.png'), transparent: true, side: THREE.DoubleSide }), // Down
|
||||
new THREE.MeshBasicMaterial({ map: loader.load('extra-textures/background/panorama_0.png'), transparent: true, side: THREE.DoubleSide }), // NS
|
||||
new THREE.MeshBasicMaterial({ map: loader.load('extra-textures/background/panorama_2.png'), transparent: true, side: THREE.DoubleSide }) // SS
|
||||
]
|
||||
|
||||
const panoramaBox = new THREE.Mesh(panorGeo, panorMaterials)
|
||||
|
||||
panoramaBox.onBeforeRender = () => {
|
||||
time += 0.01
|
||||
panoramaBox.rotation.y = Math.PI + time * 0.01
|
||||
panoramaBox.rotation.z = Math.sin(-time * 0.001) * 0.001
|
||||
}
|
||||
|
||||
const group = new THREE.Object3D()
|
||||
group.add(mesh)
|
||||
group.add(panoramaBox)
|
||||
|
||||
const Entity = require('prismarine-viewer/viewer/lib/entity/Entity')
|
||||
for (let i = 0; i < 42; i++) {
|
||||
const m = new Entity('1.16.4', 'squid').mesh
|
||||
m.position.set(Math.random() * 20 - 10, Math.random() * 20 - 10, Math.random() * 20 - 30)
|
||||
m.position.set(Math.random() * 30 - 15, Math.random() * 20 - 10, Math.random() * 10 - 17)
|
||||
m.rotation.set(0, Math.PI + Math.random(), -Math.PI / 4, 'ZYX')
|
||||
const v = Math.random() * 0.01
|
||||
m.children[0].onBeforeRender = () => {
|
||||
m.rotation.y += v
|
||||
m.rotation.z = Math.cos(mesh.rotation.y * 3) * Math.PI / 4 - Math.PI / 2
|
||||
m.rotation.z = Math.cos(panoramaBox.rotation.y * 3) * Math.PI / 4 - Math.PI / 2
|
||||
}
|
||||
group.add(m)
|
||||
}
|
||||
|
||||
viewer.scene.add(group)
|
||||
return group
|
||||
}
|
||||
|
||||
const panoramaCubeMap = addPanoramaCubeMap()
|
||||
|
||||
function removePanorama () {
|
||||
viewer.scene.remove(panoramaMesh)
|
||||
panoramaMesh = null
|
||||
viewer.camera = new THREE.PerspectiveCamera(document.getElementById('options-screen').fov, window.innerWidth / window.innerHeight, 0.1, 1000)
|
||||
viewer.camera.updateProjectionMatrix()
|
||||
viewer.scene.remove(panoramaCubeMap)
|
||||
}
|
||||
|
||||
let panoramaMesh = getPanoramaMesh()
|
||||
viewer.scene.add(panoramaMesh)
|
||||
|
||||
// Browser animation loop
|
||||
let animate = () => {
|
||||
window.requestAnimationFrame(animate)
|
||||
viewer.update()
|
||||
|
@ -89,59 +115,36 @@ let animate = () => {
|
|||
}
|
||||
animate()
|
||||
|
||||
const calcGuiScale = (guiScaleIn) => {
|
||||
let i
|
||||
for (i = 1; i !== guiScaleIn && i < window.innerWidth && i < (window.innerHeight) && window.innerWidth / (i + 1) >= 320 && (window.innerHeight) / (i + 1) >= 240; ++i);
|
||||
return i
|
||||
}
|
||||
|
||||
const setScaleFactor = (value) => {
|
||||
const i = calcGuiScale(value)
|
||||
document.documentElement.style.setProperty('--guiScaleFactor', i)
|
||||
console.log(`Scale: ${i}`)
|
||||
}
|
||||
|
||||
window.setScaleFactor = (value) => {
|
||||
setScaleFactor(value)
|
||||
}
|
||||
|
||||
setScaleFactor(3)
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
viewer.camera.aspect = window.innerWidth / window.innerHeight
|
||||
viewer.camera.updateProjectionMatrix()
|
||||
renderer.setSize(window.innerWidth, window.innerHeight)
|
||||
setScaleFactor(3)
|
||||
})
|
||||
|
||||
const showEl = (str) => { document.getElementById(str).style = 'display:block' }
|
||||
async function main () {
|
||||
const showEl = (str) => { document.getElementById(str).style = 'display:block' }
|
||||
const menu = document.getElementById('prismarine-menu')
|
||||
const menu = document.getElementById('play-screen')
|
||||
|
||||
menu.addEventListener('connect', e => {
|
||||
const options = e.detail
|
||||
menu.style = 'display: none;'
|
||||
showEl('healthbar')
|
||||
showEl('hotbar')
|
||||
showEl('crosshair')
|
||||
showEl('chatbox')
|
||||
showEl('loading-background')
|
||||
showEl('playerlist')
|
||||
showEl('debugmenu')
|
||||
showEl('loading-screen')
|
||||
removePanorama()
|
||||
connect(options)
|
||||
})
|
||||
}
|
||||
|
||||
async function connect (options) {
|
||||
const loadingScreen = document.getElementById('loading-background')
|
||||
const healthbar = document.getElementById('healthbar')
|
||||
const hotbar = document.getElementById('hotbar')
|
||||
const chat = document.getElementById('chatbox')
|
||||
const playerList = document.getElementById('playerlist')
|
||||
const debugMenu = document.getElementById('debugmenu')
|
||||
const gameMenu = document.getElementById('game-menu')
|
||||
const loadingScreen = document.getElementById('loading-screen')
|
||||
|
||||
const viewDistance = 6
|
||||
const hud = document.getElementById('hud')
|
||||
const chat = hud.shadowRoot.querySelector('#chat')
|
||||
const debugMenu = hud.shadowRoot.querySelector('#debug-overlay')
|
||||
const optionsScrn = document.getElementById('options-screen')
|
||||
const keyBindScrn = document.getElementById('keybinds-screen')
|
||||
const gameMenu = document.getElementById('pause-screen')
|
||||
|
||||
const viewDistance = optionsScrn.renderDistance
|
||||
const hostprompt = options.server
|
||||
const proxyprompt = options.proxy
|
||||
const username = options.username
|
||||
|
@ -183,26 +186,25 @@ async function connect (options) {
|
|||
closeTimeout: 240 * 1000
|
||||
})
|
||||
|
||||
healthbar.bot = bot
|
||||
hotbar.bot = bot
|
||||
debugMenu.bot = bot
|
||||
|
||||
bot.on('error', (err) => {
|
||||
console.log('Encountered error!', err)
|
||||
loadingScreen.status = `Error encountered. Error message: ${err}. Please reload the page`
|
||||
loadingScreen.style = 'display: block;'
|
||||
loadingScreen.hasError = true
|
||||
})
|
||||
|
||||
bot.on('kicked', (kickReason) => {
|
||||
console.log('User was kicked!', kickReason)
|
||||
loadingScreen.status = `The Minecraft server kicked you. Kick reason: ${kickReason}. Please reload the page to rejoin`
|
||||
loadingScreen.style = 'display: block;'
|
||||
loadingScreen.hasError = true
|
||||
})
|
||||
|
||||
bot.on('end', (endReason) => {
|
||||
console.log('disconnected for', endReason)
|
||||
loadingScreen.status = `You have been disconnected from the server. End reason: ${endReason}. Please reload the page to rejoin`
|
||||
loadingScreen.style = 'display: block;'
|
||||
loadingScreen.hasError = true
|
||||
})
|
||||
|
||||
bot.once('login', () => {
|
||||
|
@ -212,7 +214,7 @@ async function connect (options) {
|
|||
bot.once('spawn', () => {
|
||||
const mcData = require('minecraft-data')(bot.version)
|
||||
|
||||
loadingScreen.status = 'Placing blocks (starting viewer)...'
|
||||
loadingScreen.status = 'Placing blocks (starting viewer)'
|
||||
|
||||
console.log('bot spawned - starting viewer')
|
||||
|
||||
|
@ -220,15 +222,18 @@ async function connect (options) {
|
|||
|
||||
const center = bot.entity.position
|
||||
|
||||
console.log(viewDistance)
|
||||
const worldView = new WorldView(bot.world, viewDistance, center)
|
||||
|
||||
chat.init(bot._client, renderer)
|
||||
gameMenu.init(renderer)
|
||||
playerList.init(bot)
|
||||
optionsScrn.isInsideWorld = true
|
||||
optionsScrn.addEventListener('fov_changed', (e) => {
|
||||
viewer.camera.fov = e.detail.fov
|
||||
viewer.camera.updateProjectionMatrix()
|
||||
})
|
||||
|
||||
viewer.setVersion(version)
|
||||
|
||||
hotbar.viewerVersion = viewer.version
|
||||
window.worldView = worldView
|
||||
window.bot = bot
|
||||
window.mcData = mcData
|
||||
|
@ -236,15 +241,8 @@ async function connect (options) {
|
|||
window.Vec3 = Vec3
|
||||
window.pathfinder = pathfinder
|
||||
window.debugMenu = debugMenu
|
||||
window.settings = optionsScrn
|
||||
window.renderer = renderer
|
||||
window.settings = {
|
||||
mouseSensXValue: window.localStorage.getItem('mouseSensX') ?? 0.005,
|
||||
mouseSensYValue: window.localStorage.getItem('mouseSensY') ?? 0.005,
|
||||
set mouseSensX (v) { this.mouseSensXValue = v; window.localStorage.setItem('mouseSensX', v) },
|
||||
set mouseSensY (v) { this.mouseSensYValue = v; window.localStorage.setItem('mouseSensY', v) },
|
||||
get mouseSensX () { return this.mouseSensXValue },
|
||||
get mouseSensY () { return this.mouseSensYValue }
|
||||
}
|
||||
|
||||
initVR(bot, renderer, viewer)
|
||||
|
||||
|
@ -270,12 +268,12 @@ async function connect (options) {
|
|||
bot.on('move', botPosition)
|
||||
botPosition()
|
||||
|
||||
loadingScreen.status = 'Setting callbacks...'
|
||||
loadingScreen.status = 'Setting callbacks'
|
||||
|
||||
function moveCallback (e) {
|
||||
bot.entity.pitch -= e.movementY * window.settings.mouseSensYValue
|
||||
bot.entity.pitch -= e.movementY * optionsScrn.mouseSensitivityY * 0.0001
|
||||
bot.entity.pitch = Math.max(minPitch, Math.min(maxPitch, bot.entity.pitch))
|
||||
bot.entity.yaw -= e.movementX * window.settings.mouseSensXValue
|
||||
bot.entity.yaw -= e.movementX * optionsScrn.mouseSensitivityX * 0.0001
|
||||
|
||||
viewer.setFirstPersonCamera(null, bot.entity.yaw, bot.entity.pitch)
|
||||
}
|
||||
|
@ -320,16 +318,6 @@ async function connect (options) {
|
|||
|
||||
document.addEventListener('contextmenu', (e) => e.preventDefault(), false)
|
||||
|
||||
const codes = {
|
||||
KeyW: 'forward',
|
||||
KeyS: 'back',
|
||||
KeyA: 'right',
|
||||
KeyD: 'left',
|
||||
Space: 'jump',
|
||||
ShiftLeft: 'sneak',
|
||||
ControlLeft: 'sprint'
|
||||
}
|
||||
|
||||
window.addEventListener('blur', (e) => {
|
||||
bot.clearControlStates()
|
||||
}, false)
|
||||
|
@ -337,42 +325,79 @@ async function connect (options) {
|
|||
document.addEventListener('keydown', (e) => {
|
||||
if (chat.inChat) return
|
||||
if (gameMenu.inMenu) return
|
||||
console.log(e.code)
|
||||
if (e.code in codes) {
|
||||
bot.setControlState(codes[e.code], true)
|
||||
}
|
||||
if (e.code.startsWith('Digit')) {
|
||||
const numPressed = e.code.substr(5)
|
||||
if (numPressed < 1 || numPressed > 9) return
|
||||
hotbar.reloadHotbarSelected(numPressed - 1)
|
||||
}
|
||||
if (e.code === 'KeyQ') {
|
||||
if (bot.heldItem) bot.tossStack(bot.heldItem)
|
||||
}
|
||||
|
||||
keyBindScrn.keymaps.forEach(km => {
|
||||
if (e.code === km.key) {
|
||||
switch (km.defaultKey) {
|
||||
case 'KeyQ':
|
||||
if (bot.heldItem) bot.tossStack(bot.heldItem)
|
||||
break
|
||||
case 'ControlLeft':
|
||||
bot.setControlState('sprint', true)
|
||||
break
|
||||
case 'ShiftLeft':
|
||||
bot.setControlState('sneak', true)
|
||||
break
|
||||
case 'Space':
|
||||
bot.setControlState('jump', true)
|
||||
break
|
||||
case 'KeyD':
|
||||
bot.setControlState('left', true)
|
||||
break
|
||||
case 'KeyA':
|
||||
bot.setControlState('right', true)
|
||||
break
|
||||
case 'KeyS':
|
||||
bot.setControlState('back', true)
|
||||
break
|
||||
case 'KeyW':
|
||||
bot.setControlState('forward', true)
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}, false)
|
||||
|
||||
document.addEventListener('keyup', (e) => {
|
||||
if (e.code in codes) {
|
||||
bot.setControlState(codes[e.code], false)
|
||||
}
|
||||
keyBindScrn.keymaps.forEach(km => {
|
||||
if (e.code === km.key) {
|
||||
switch (km.defaultKey) {
|
||||
case 'ControlLeft':
|
||||
bot.setControlState('sprint', false)
|
||||
break
|
||||
case 'ShiftLeft':
|
||||
bot.setControlState('sneak', false)
|
||||
break
|
||||
case 'Space':
|
||||
bot.setControlState('jump', false)
|
||||
break
|
||||
case 'KeyD':
|
||||
bot.setControlState('left', false)
|
||||
break
|
||||
case 'KeyA':
|
||||
bot.setControlState('right', false)
|
||||
break
|
||||
case 'KeyS':
|
||||
bot.setControlState('back', false)
|
||||
break
|
||||
case 'KeyW':
|
||||
bot.setControlState('forward', false)
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}, false)
|
||||
|
||||
loadingScreen.status = 'Done!'
|
||||
console.log(loadingScreen.status) // only do that because it's read in index.html and npm run fix complains.
|
||||
|
||||
hud.init(renderer, bot, host)
|
||||
hud.style.display = 'block'
|
||||
|
||||
setTimeout(function () {
|
||||
// remove loading screen, wait a second to make sure a frame has properly rendered
|
||||
loadingScreen.style = 'display: none;'
|
||||
}, 2500)
|
||||
|
||||
// TODO: Remove after #85 is done
|
||||
debugMenu.customEntries.food = bot.food
|
||||
debugMenu.customEntries.saturation = bot.foodSaturation
|
||||
|
||||
bot.on('health', () => {
|
||||
debugMenu.customEntries.food = bot.food
|
||||
debugMenu.customEntries.saturation = bot.foodSaturation
|
||||
})
|
||||
})
|
||||
}
|
||||
main()
|
||||
|
|
74
lib/chat.js
|
@ -40,19 +40,22 @@ class ChatBox extends LitElement {
|
|||
return css`
|
||||
.chat-wrapper {
|
||||
position: fixed;
|
||||
z-index:10;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.chat-display-wrapper {
|
||||
bottom: calc(8px * 16);
|
||||
bottom: 40px;
|
||||
padding: 4px;
|
||||
max-height: calc(90px * 8);
|
||||
width: calc(320px * 4);
|
||||
padding-left: 0;
|
||||
max-height: var(--chatHeight);
|
||||
width: var(--chatWidth);
|
||||
}
|
||||
|
||||
.chat-input-wrapper {
|
||||
bottom: calc(2px * 16);
|
||||
width: 100%;
|
||||
bottom: 2px;
|
||||
width: calc(100% - 2px);
|
||||
position: relative;
|
||||
left: 1px;
|
||||
overflow: hidden;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
@ -60,22 +63,29 @@ class ChatBox extends LitElement {
|
|||
.chat {
|
||||
overflow: hidden;
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
font-size: 10px;
|
||||
margin: 0px;
|
||||
line-height: 100%;
|
||||
text-shadow: 2px 2px 0px #3f3f3f;
|
||||
text-shadow: 1px 1px 0px #3f3f3f;
|
||||
font-family: mojangles, minecraft, monospace;
|
||||
width: 100%;
|
||||
max-height: calc(90px * 8);
|
||||
max-height: var(--chatHeight);
|
||||
}
|
||||
|
||||
input[type=text], #chatinput {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
border: 1px solid rgba(0, 0, 0, 0.5);
|
||||
display: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#chatinput:focus {
|
||||
border-color: white;
|
||||
}
|
||||
|
||||
.chat-message {
|
||||
display: block;
|
||||
padding-left: 4px;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
|
@ -102,13 +112,13 @@ class ChatBox extends LitElement {
|
|||
render () {
|
||||
return html`
|
||||
<div id="chat-wrapper" class="chat-wrapper chat-display-wrapper">
|
||||
<div class="chat" id="chat">
|
||||
<li class="chat-message chat-message-fade chat-message-faded">Welcome to prismarine-web-client! Chat appears here.</li>
|
||||
</div>
|
||||
<div class="chat" id="chat">
|
||||
<li class="chat-message chat-message-fade chat-message-faded">Welcome to prismarine-web-client! Chat appears here.</li>
|
||||
</div>
|
||||
</div>
|
||||
<div id="chat-wrapper2" class="chat-wrapper chat-input-wrapper">
|
||||
<div class="chat" id="chat-input">
|
||||
<input type="text" class="chat" id="chatinput"></input>
|
||||
<input type="text" class="chat" id="chatinput" spellcheck="false" autocomplete="off"></input>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
@ -117,7 +127,7 @@ class ChatBox extends LitElement {
|
|||
init (client, renderer) {
|
||||
this.inChat = false
|
||||
const chat = this.shadowRoot.querySelector('#chat')
|
||||
const gameMenu = document.getElementById('game-menu')
|
||||
const gameMenu = document.getElementById('pause-screen')
|
||||
const chatInput = this.shadowRoot.querySelector('#chatinput')
|
||||
|
||||
const chatHistory = []
|
||||
|
@ -132,12 +142,12 @@ class ChatBox extends LitElement {
|
|||
|
||||
const self = this
|
||||
|
||||
// Esc event - Doesnt work with onkeypress?!
|
||||
// Esc event - Doesnt work with onkeypress?! - keypressed is deprecated uk
|
||||
document.addEventListener('keydown', e => {
|
||||
if (gameMenu.inMenu) return
|
||||
if (!self.inChat) return
|
||||
e = e || window.event
|
||||
if (e.keyCode === 27 || e.key === 'Escape' || e.key === 'Esc') {
|
||||
if (e.code === 'Escape') {
|
||||
disableChat()
|
||||
} else if (e.keyCode === 38) {
|
||||
if (chatHistoryPos === 0) return
|
||||
|
@ -148,18 +158,26 @@ class ChatBox extends LitElement {
|
|||
}
|
||||
})
|
||||
|
||||
const keyBindScrn = document.getElementById('keybinds-screen')
|
||||
|
||||
// Chat events
|
||||
document.addEventListener('keypress', e => {
|
||||
if (gameMenu.inMenu) return
|
||||
e = e || window.event
|
||||
if (self.inChat === false) {
|
||||
if (e.code === 'KeyT') {
|
||||
setTimeout(() => enableChat(false), 0)
|
||||
}
|
||||
keyBindScrn.keymaps.forEach(km => {
|
||||
if (e.code === km.key) {
|
||||
switch (km.defaultKey) {
|
||||
case 'KeyT':
|
||||
setTimeout(() => enableChat(false), 0)
|
||||
break
|
||||
case 'Slash':
|
||||
setTimeout(() => enableChat(true), 0)
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (e.code === 'Slash') {
|
||||
setTimeout(() => enableChat(true), 0)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -191,7 +209,7 @@ class ChatBox extends LitElement {
|
|||
// Show chat input
|
||||
chatInput.style.display = 'block'
|
||||
// Show extended chat history
|
||||
chat.style.maxHeight = 'calc(90px * 8)'
|
||||
chat.style.maxHeight = 'calc(var(--chatHeight) * var(--chatScale))'
|
||||
chat.scrollTop = chat.scrollHeight // Stay bottom of the list
|
||||
if (isCommand) { // handle commands
|
||||
chatInput.value = '/'
|
||||
|
@ -199,16 +217,12 @@ class ChatBox extends LitElement {
|
|||
// Focus element
|
||||
chatInput.focus()
|
||||
chatHistoryPos = chatHistory.length
|
||||
document.querySelector('#chatbox').shadowRoot.querySelectorAll('.chat-message').forEach(e => e.classList.add('chat-message-chat-opened'))
|
||||
document.querySelector('#hud').shadowRoot.querySelector('#chat').shadowRoot.querySelectorAll('.chat-message').forEach(e => e.classList.add('chat-message-chat-opened'))
|
||||
}
|
||||
|
||||
function disableChat () {
|
||||
// Set inChat value
|
||||
self.inChat = false
|
||||
|
||||
// Hide chat
|
||||
hideChat()
|
||||
|
||||
renderer.domElement.requestPointerLock()
|
||||
}
|
||||
|
||||
|
@ -220,9 +234,9 @@ class ChatBox extends LitElement {
|
|||
// Hide it
|
||||
chatInput.style.display = 'none'
|
||||
// Hide extended chat history
|
||||
chat.style.maxHeight = 'calc(90px * 4)'
|
||||
chat.style.maxHeight = 'var(--chatHeight)'
|
||||
chat.scrollTop = chat.scrollHeight // Stay bottom of the list
|
||||
document.querySelector('#chatbox').shadowRoot.querySelectorAll('.chat-message').forEach(e => e.classList.remove('chat-message-chat-opened'))
|
||||
document.querySelector('#hud').shadowRoot.querySelector('#chat').shadowRoot.querySelectorAll('.chat-message').forEach(e => e.classList.remove('chat-message-chat-opened'))
|
||||
}
|
||||
|
||||
function readExtra (extra) {
|
||||
|
|
|
@ -1,112 +0,0 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
|
||||
export class LegacyButton extends LitElement {
|
||||
static get styles () {
|
||||
return css`
|
||||
:host {
|
||||
--guiScale: var(--guiScaleFactor, 3);
|
||||
}
|
||||
|
||||
.legacy-btn {
|
||||
--textColor: white;
|
||||
image-rendering: crisp-edges;
|
||||
image-rendering: pixelated;
|
||||
text-decoration: none;
|
||||
|
||||
cursor: default;
|
||||
border: none;
|
||||
background: none;
|
||||
outline: none;
|
||||
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
display: grid;
|
||||
width: 100%;
|
||||
height: calc(20px * var(--guiScale));
|
||||
|
||||
font-family: mojangles, minecraft, monospace;
|
||||
font-size: calc(10px * var(--guiScale));
|
||||
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
color: var(--textColor);
|
||||
text-shadow: calc(1px * var(--guiScale)) calc(1px * var(--guiScale)) black;
|
||||
}
|
||||
|
||||
.legacy-btn:disabled {
|
||||
--textColor: rgb(160, 160, 160);
|
||||
}
|
||||
|
||||
.legacy-btn::after,
|
||||
.legacy-btn::before {
|
||||
--yPos: -66px;
|
||||
content: '';
|
||||
display: block;
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
|
||||
background-image: url('textures/1.16.4/gui/widgets.png');
|
||||
background-size: calc(256px * var(--guiScale));
|
||||
background-position-y: calc(var(--yPos) * var(--guiScale));
|
||||
}
|
||||
|
||||
.legacy-btn::after {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.legacy-btn::before {
|
||||
left: 50%;
|
||||
background-position-x: calc(-200px * var(--guiScale) + 100%);
|
||||
}
|
||||
|
||||
.legacy-btn:hover::after,
|
||||
.legacy-btn:hover::before,
|
||||
.legacy-btn:focus::after,
|
||||
.legacy-btn:focus::before {
|
||||
--yPos: -86px;
|
||||
}
|
||||
|
||||
.legacy-btn:disabled::after,
|
||||
.legacy-btn:disabled::before {
|
||||
--yPos: -46px;
|
||||
--textColor: rgb(160, 160, 160);
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
static get properties () {
|
||||
return {
|
||||
size: {
|
||||
type: String,
|
||||
attribute: 'btn-width'
|
||||
},
|
||||
scaleFactor: {
|
||||
type: Number,
|
||||
attribute: 'scale-factor'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor () {
|
||||
super()
|
||||
this.scaleFactor = 3
|
||||
|
||||
this.size = '100%'
|
||||
this.offset = [0, 0]
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`
|
||||
<button class="legacy-btn" style="width: calc(${this.size.endsWith('%') ? this.size : this.size + ' * var(--guiScale)'});"><slot></slot></button>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('legacy-button', LegacyButton)
|
|
@ -1,30 +0,0 @@
|
|||
const { html } = require('lit')
|
||||
const { LegacyButton } = require('./button')
|
||||
|
||||
class LegacyButtonLink extends LegacyButton {
|
||||
constructor () {
|
||||
super()
|
||||
this.href = ''
|
||||
}
|
||||
|
||||
static get properties () {
|
||||
return {
|
||||
size: {
|
||||
type: String,
|
||||
attribute: 'btn-width'
|
||||
},
|
||||
href: {
|
||||
type: String,
|
||||
attribute: 'go-to'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`
|
||||
<a class="legacy-btn" href=${this.href} target="_blank" style="width: calc(${this.size} * var(--guiScale));"><slot></slot></a>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('legacy-button-link', LegacyButtonLink)
|
|
@ -1,106 +0,0 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
|
||||
class LegacyTextField extends LitElement {
|
||||
constructor () {
|
||||
super()
|
||||
this.size = '200px'
|
||||
this.id = ''
|
||||
this.value = ''
|
||||
this.label = ''
|
||||
}
|
||||
|
||||
static get properties () {
|
||||
return {
|
||||
size: {
|
||||
type: String,
|
||||
attribute: 'field-width'
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
attribute: 'field-label'
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
attribute: 'field-id'
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
attribute: 'field-value'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static get styles () {
|
||||
return css`
|
||||
:host {
|
||||
--guiScale: var(--guiScaleFactor, 3);
|
||||
}
|
||||
|
||||
.text-field-div {
|
||||
--borderColor: grey;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
width: 100%;
|
||||
height: calc(22px * var(--guiScale));
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
background: black;
|
||||
border: calc(1px * var(--guiScale)) solid var(--borderColor);
|
||||
|
||||
padding: calc(4px * var(--guiScale));
|
||||
}
|
||||
|
||||
.text-field-div:focus-within {
|
||||
--borderColor: white;
|
||||
}
|
||||
|
||||
.text-field-div label {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
|
||||
bottom: calc(21px * var(--guiScale));
|
||||
left: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
font-family: mojangles, minecraft, monospace;
|
||||
font-size: calc(10px * var(--guiScale));
|
||||
|
||||
color: rgb(206, 206, 206);
|
||||
text-shadow: calc(1px * var(--guiScale)) calc(1px * var(--guiScale)) black;
|
||||
}
|
||||
|
||||
.legacy-text-field {
|
||||
outline: none;
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
font-family: mojangles, minecraft, monospace;
|
||||
font-size: calc(8px * var(--guiScale));
|
||||
|
||||
color: white;
|
||||
text-shadow: calc(1px * var(--guiScale)) calc(1px * var(--guiScale)) black;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`
|
||||
<div class="text-field-div" style="width: calc(${this.size.endsWith('%') ? this.size : this.size + ' * var(--guiScale)'});">
|
||||
<label for="${this.id}">${this.label}</label>
|
||||
<input id="${this.id}" type="text" name="" spellcheck="false" required="" autocomplete="off" value="${this.value}" @input=${(e) => { this.value = e.target.value }} class="legacy-text-field">
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('legacy-text-field', LegacyTextField)
|
|
@ -1,29 +0,0 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
|
||||
class CrossHair extends LitElement {
|
||||
static get styles () {
|
||||
return css`
|
||||
#crosshair {
|
||||
image-rendering: optimizeSpeed;
|
||||
image-rendering: -moz-crisp-edges;
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
image-rendering: -o-crisp-edges;
|
||||
image-rendering: pixelated;
|
||||
-ms-interpolation-mode: nearest-neighbor;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
height: calc(256px * 4);
|
||||
width: calc(256px * 4);
|
||||
transform: translate(calc(-50% + 120px * 4), calc(-50% + 120px * 4));
|
||||
clip-path: inset(0px calc(240px * 4) calc(240px * 4) 0px);
|
||||
z-index:10;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`<img id="crosshair" src="extra-textures/icons.png">`
|
||||
}
|
||||
}
|
||||
window.customElements.define('cross-hair', CrossHair)
|
|
@ -67,14 +67,14 @@ class Cursor {
|
|||
this.breakStartTime = performance.now()
|
||||
try {
|
||||
bot.dig(cursorBlock, 'ignore')
|
||||
} catch {} // we don't care if its aborted
|
||||
} catch (e) {} // we don't care if its aborted
|
||||
}
|
||||
|
||||
// Stop break
|
||||
if (!this.buttons[0] && this.lastButtons[0]) {
|
||||
try {
|
||||
bot.stopDigging() // this shouldnt throw anything...
|
||||
} catch {} // to be reworked in mineflayer, then remove the try here
|
||||
} catch (e) {} // to be reworked in mineflayer, then remove the try here
|
||||
}
|
||||
|
||||
// Show break animation
|
||||
|
|
118
lib/debugmenu.js
|
@ -1,118 +0,0 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
|
||||
class DebugMenu extends LitElement {
|
||||
constructor () {
|
||||
super()
|
||||
|
||||
this.isOpen = false
|
||||
this.customEntries = {}
|
||||
}
|
||||
|
||||
firstUpdated () {
|
||||
document.addEventListener('keydown', e => {
|
||||
e ??= window.event
|
||||
if (e.key === 'F3') {
|
||||
this.isOpen = !this.isOpen
|
||||
e.preventDefault()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
static get styles () {
|
||||
return css`
|
||||
.debugmenu-wrapper {
|
||||
position: fixed;
|
||||
z-index:25;
|
||||
}
|
||||
|
||||
.debugmenu {
|
||||
overflow: hidden;
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
margin: 0px;
|
||||
line-height: 100%;
|
||||
text-shadow: 2px 2px 0px #3f3f3f;
|
||||
font-family: mojangles, minecraft, monospace;
|
||||
width: calc(320px * 4);
|
||||
max-height: calc(90px * 8);
|
||||
top: calc(8px * 16);
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.debugmenu p {
|
||||
margin: 0px;
|
||||
padding: 1px;
|
||||
width: fit-content;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
static get properties () {
|
||||
return {
|
||||
isOpen: { type: Boolean },
|
||||
cursorBlock: { type: Object },
|
||||
bot: { type: Object },
|
||||
customEntries: { type: Object }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
updated (changedProperties) {
|
||||
if (changedProperties.has('bot')) {
|
||||
this.bot.on('move', () => {
|
||||
this.requestUpdate()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
if (!this.isOpen) {
|
||||
return html``
|
||||
}
|
||||
|
||||
const target = this.cursorBlock
|
||||
const targetDiggable = (target && this.bot.canDigBlock(target))
|
||||
|
||||
const pos = this.bot.entity.position
|
||||
const rot = [this.bot.entity.yaw, this.bot.entity.pitch]
|
||||
|
||||
const viewDegToMinecraft = (yaw) => {
|
||||
return yaw % 360 - 180 * (yaw < 0 ? -1 : 1)
|
||||
}
|
||||
|
||||
const quadsDescription = [
|
||||
'north (towards negative Z)',
|
||||
'east (towards positive X)',
|
||||
'south (towards positive Z)',
|
||||
'west (towards negative X)'
|
||||
]
|
||||
|
||||
const minecraftYaw = viewDegToMinecraft(rot[0] * -180 / Math.PI)
|
||||
const minecraftQuad = Math.floor(((minecraftYaw + 180) / 90 + 0.5) % 4)
|
||||
|
||||
const renderProp = (name, value, nextItem) => {
|
||||
return html`${name}: ${typeof value === 'boolean'
|
||||
? html`<span style="color: ${value ? 'lightgreen' : 'red'}">${value}</span>`
|
||||
: value}${nextItem ? ' / ' : ''}`
|
||||
}
|
||||
|
||||
return html`
|
||||
<div id="debugmenu-wrapper" class="debugmenu-wrapper">
|
||||
<div class="debugmenu" id="debugmenu">
|
||||
<p id="debug-entry-renderer">Renderer: three.js r${global.THREE.REVISION}</p>
|
||||
</br>
|
||||
<p>XYZ: ${pos.x.toFixed(3)} / ${pos.y.toFixed(3)} / ${pos.z.toFixed(3)}</p>
|
||||
<p>Chunk: ${Math.floor(pos.x % 16)} ~ ${Math.floor(pos.z % 16)} in ${Math.floor(pos.x / 16)} ~ ${Math.floor(pos.z / 16)}</p>
|
||||
<p>Facing (viewer): ${rot[0].toFixed(3)} ${rot[1].toFixed(3)}</p>
|
||||
<p>Facing (minecraft): ${quadsDescription[minecraftQuad]} (${minecraftYaw.toFixed(1)} ${(rot[1] * -180 / Math.PI).toFixed(1)})</p>
|
||||
${targetDiggable ? html`<p>Looking at: ${target.position.x} ${target.position.y} ${target.position.z}</p>` : ''}
|
||||
${targetDiggable ? html`<p>${target.name}${Object.entries(target.getProperties()).length > 0 ? ' | ' : ''}${Object.entries(target.getProperties()).map(([n, p], idx, arr) => renderProp(n, p, arr[idx + 1]))}</p>` : ''}<br>
|
||||
${Object.entries(this.customEntries).map(([name, value]) => html`<p>${name}: ${value}</p>`)}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('debug-menu', DebugMenu)
|
129
lib/gameMenu.js
|
@ -1,129 +0,0 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
require('./github_link')
|
||||
require('./components/button')
|
||||
require('./components/buttonlink')
|
||||
require('./components/textfield')
|
||||
|
||||
class GameMenu extends LitElement {
|
||||
constructor () {
|
||||
super()
|
||||
this.inMenu = false
|
||||
}
|
||||
|
||||
disableGameMenu (renderer = false) {
|
||||
this.inMenu = false
|
||||
this.style.display = 'none'
|
||||
if (renderer) {
|
||||
renderer.domElement.requestPointerLock()
|
||||
}
|
||||
}
|
||||
|
||||
enableGameMenu () {
|
||||
this.inMenu = true
|
||||
document.exitPointerLock()
|
||||
this.style.display = 'block'
|
||||
this.focus()
|
||||
}
|
||||
|
||||
static get styles () {
|
||||
return css`
|
||||
:host {
|
||||
--guiScale: var(--guiScaleFactor, 3);
|
||||
}
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin:0;
|
||||
padding:0;
|
||||
font-family: sans-serif;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.menu-box {
|
||||
position: fixed;
|
||||
z-index: 11;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: calc(180px * var(--guiScale));
|
||||
padding: calc(10px * var(--guiScale));
|
||||
transform: translate(-50%, -50%);
|
||||
box-sizing: border-box;
|
||||
border-radius: 10px;
|
||||
background: rgba(0, 0, 0, 0.8)
|
||||
}
|
||||
|
||||
.link-buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: calc(4px * var(--guiScale));
|
||||
}
|
||||
|
||||
.title, .subtitle {
|
||||
text-align: center;
|
||||
|
||||
font-family: mojangles, minecraft, monospace;
|
||||
font-size: calc(10px * var(--guiScale));
|
||||
font-weight: normal;
|
||||
|
||||
color: white;
|
||||
margin-top: 0;
|
||||
text-shadow: calc(1px * var(--guiScale)) calc(1px * var(--guiScale)) black;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: calc(7.5px * var(--guiScale));
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: calc(6px * var(--guiScale));
|
||||
}
|
||||
|
||||
.spacev {
|
||||
height: calc(6px * var(--guiScale));
|
||||
}
|
||||
|
||||
.field-spacev {
|
||||
height: calc(14px * var(--guiScale));
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`
|
||||
<github-link></github-link>
|
||||
<div class="menu-box">
|
||||
<h2 class="title">Game Menu</h2>
|
||||
<div class="spacev"></div>
|
||||
<legacy-button btn-width="100%" @click=${() => { this.disableGameMenu() }}>Back to Game</legacy-button>
|
||||
<div class="spacev"></div>
|
||||
<legacy-button btn-width="100%">Options</legacy-button>
|
||||
<div class="spacev"></div>
|
||||
<legacy-button btn-width="100%" onClick="window.location.reload();">Disconnect</legacy-button>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
init (renderer) {
|
||||
const chat = document.getElementById('chatbox')
|
||||
const self = this
|
||||
|
||||
document.addEventListener('keydown', e => {
|
||||
if (chat.inChat) return
|
||||
e = e || window.event
|
||||
if (e.keyCode === 27 || e.key === 'Escape' || e.key === 'Esc') {
|
||||
if (self.inMenu) {
|
||||
self.disableGameMenu(renderer)
|
||||
} else {
|
||||
self.enableGameMenu()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('game-menu', GameMenu)
|
|
@ -1,58 +0,0 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
|
||||
class GithubLink extends LitElement {
|
||||
static get styles () {
|
||||
return css`
|
||||
.github-corner:hover .octo-arm {
|
||||
animation: octocat-wave 560ms ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes octocat-wave {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
20% {
|
||||
transform: rotate(-25deg);
|
||||
}
|
||||
|
||||
40% {
|
||||
transform: rotate(10deg);
|
||||
}
|
||||
|
||||
60% {
|
||||
transform: rotate(-25deg);
|
||||
}
|
||||
|
||||
80% {
|
||||
transform: rotate(10deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
.github-corner:hover .octo-arm {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.github-corner .octo-arm {
|
||||
animation: octocat-wave 560ms ease-in-out;
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`<a href="https://github.com/PrismarineJS/prismarine-web-client" class="github-corner" aria-label="View source on GitHub">
|
||||
<svg width="80" height="80" viewBox="0 0 250 250" style="fill:#151513; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true">
|
||||
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
|
||||
<path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path>
|
||||
<path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path>
|
||||
</svg>
|
||||
</a>`
|
||||
}
|
||||
}
|
||||
window.customElements.define('github-link', GithubLink)
|
224
lib/healthbar.js
|
@ -1,224 +0,0 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
|
||||
function getEffectClass (effect) {
|
||||
switch (effect.id) {
|
||||
case 19: return 'poisoned'
|
||||
case 20: return 'withered'
|
||||
case 22: return 'absorption'
|
||||
default: return ''
|
||||
}
|
||||
}
|
||||
|
||||
class HealthBar extends LitElement {
|
||||
updated (changedProperties) {
|
||||
if (changedProperties.has('bot')) {
|
||||
this.bot.once('spawn', () => this.init())
|
||||
}
|
||||
}
|
||||
|
||||
init () {
|
||||
this.bot.on('entityHurt', (entity) => {
|
||||
if (entity !== this.bot.entity) return
|
||||
this.onDamage()
|
||||
})
|
||||
this.bot.on('entityEffect', (entity, effect) => {
|
||||
if (entity !== this.bot.entity) return
|
||||
this.shadowRoot.firstElementChild.classList.add(getEffectClass(effect))
|
||||
})
|
||||
this.bot.on('entityEffectEnd', (entity, effect) => {
|
||||
if (entity !== this.bot.entity) return
|
||||
this.shadowRoot.firstElementChild.classList.remove(getEffectClass(effect))
|
||||
})
|
||||
this.bot.on('game', () => this.onGameUpdate())
|
||||
this.bot.on('health', () => this.updateHealth())
|
||||
this.onGameUpdate()
|
||||
this.updateHealth()
|
||||
}
|
||||
|
||||
onGameUpdate () {
|
||||
this.shadowRoot.firstElementChild.classList.toggle('creative', this.bot.player.gamemode === 1)
|
||||
this.shadowRoot.firstElementChild.classList.toggle('hardcore', this.bot.game.hardcore)
|
||||
}
|
||||
|
||||
onDamage () {
|
||||
this.shadowRoot.firstElementChild.classList.add('damaged')
|
||||
if (this.hurtTimeout) clearTimeout(this.hurtTimeout)
|
||||
this.hurtTimeout = setTimeout(() => {
|
||||
this.shadowRoot.firstElementChild.classList.remove('damaged')
|
||||
this.hurtTimeout = null
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
updateHealth () {
|
||||
const wrapper = this.shadowRoot.firstElementChild
|
||||
const health = wrapper.firstElementChild
|
||||
const absorption = wrapper.lastElementChild
|
||||
health.dataset.value = Math.min(this.bot.health, 20)
|
||||
absorption.dataset.value = Math.max(this.bot.health - 20, 0)
|
||||
health.classList.toggle('low', this.bot.health <= 4)
|
||||
}
|
||||
|
||||
static get properties () {
|
||||
return {
|
||||
bot: { type: Object }
|
||||
}
|
||||
}
|
||||
|
||||
static get styles () {
|
||||
return css`
|
||||
#healthbar {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translate(calc(-182px * 2), calc(-22px * 7));
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
--lightened: 0;
|
||||
--hardcore: 0;
|
||||
--kind: 1;
|
||||
--background-x: calc(-3 * (16px + var(--lightened) * 9px));
|
||||
--background-y: calc(-3 * (var(--hardcore) * 5 * 9px));
|
||||
--offset: calc(-3 * (16px + 9px * (var(--kind) * 4 + var(--lightened) * 2)));
|
||||
--health: attr(data-value number);
|
||||
}
|
||||
#healthbar.creative { display: none; }
|
||||
#healthbar.hardcore { --hardcore: 1; }
|
||||
#healthbar.poisoned { --kind: 2; }
|
||||
#healthbar.withered { --kind: 3; }
|
||||
.health.absorption { --kind: 4; }
|
||||
.health.absorption > * { visibility: hidden; }
|
||||
|
||||
.health > * , .food > * {
|
||||
display: inline-block;
|
||||
image-rendering: optimizeSpeed;
|
||||
image-rendering: -moz-crisp-edges;
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
image-rendering: -o-crisp-edges;
|
||||
image-rendering: pixelated;
|
||||
-ms-interpolation-mode: nearest-neighbor;
|
||||
width: calc(3 * 9px);
|
||||
height: calc(3 * 9px);
|
||||
margin: 0;
|
||||
margin-right: -3px;
|
||||
background-image: url(extra-textures/icons.png), url(extra-textures/icons.png);
|
||||
background-repeat: no-repeat, no-repeat;
|
||||
background-size: calc(3 * 256px) auto, calc(3 * 256px) auto;
|
||||
background-position: var(--background-x) var(--background-y), var(--background-x) var(--background-y);
|
||||
}
|
||||
|
||||
/* Damage flashing animation */
|
||||
.health.damaged {
|
||||
animation: damaged 0.3s steps(2, end) 2;
|
||||
}
|
||||
@media (prefers-reduced-motion) {
|
||||
.health.damaged {
|
||||
animation: none;
|
||||
--lightened: 1;
|
||||
}
|
||||
}
|
||||
@keyframes damaged {
|
||||
to { --lightened: 1; }
|
||||
}
|
||||
|
||||
/* Low health shaking animation */
|
||||
.health.low > * {
|
||||
animation: lowhealth 0.2s steps(2, end) infinite;
|
||||
}
|
||||
.health.low > *:nth-child(2n) {
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.health.low > *:nth-child(3n) {
|
||||
animation-duration: 0.1s;
|
||||
}
|
||||
@media (prefers-reduced-motion) {
|
||||
.health.low > * {
|
||||
animation-duration: 0.5s !important;
|
||||
}
|
||||
}
|
||||
@keyframes lowhealth {
|
||||
to {
|
||||
transform: translateY(3px);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* 1 - 2-20 */
|
||||
.health[data-value*="2"] > :nth-child(1),
|
||||
.health[data-value*="3"] > :nth-child(1),
|
||||
.health[data-value*="4"] > :nth-child(1),
|
||||
.health[data-value*="5"] > :nth-child(1),
|
||||
.health[data-value*="6"] > :nth-child(1),
|
||||
.health[data-value*="7"] > :nth-child(1),
|
||||
.health[data-value*="8"] > :nth-child(1),
|
||||
.health[data-value*="9"] > :nth-child(1),
|
||||
.health[data-value^="1"]:not([data-value="1"]) > :nth-child(1),
|
||||
/* 2 - 4-20 */
|
||||
.health[data-value*="4"] > :nth-child(2),
|
||||
.health[data-value*="5"] > :nth-child(2),
|
||||
.health[data-value*="6"] > :nth-child(2),
|
||||
.health[data-value*="7"] > :nth-child(2),
|
||||
.health[data-value*="8"] > :nth-child(2),
|
||||
.health[data-value*="9"] > :nth-child(2),
|
||||
.health[data-value^="1"]:not([data-value="1"]) > :nth-child(2),
|
||||
.health[data-value="20"] > :nth-child(2),
|
||||
/* 3 - 6-20 */
|
||||
.health[data-value*="6"] > :nth-child(3),
|
||||
.health[data-value*="7"] > :nth-child(3),
|
||||
.health[data-value*="8"] > :nth-child(3),
|
||||
.health[data-value*="9"] > :nth-child(3),
|
||||
.health[data-value^="1"]:not([data-value="1"]) > :nth-child(3),
|
||||
.health[data-value="20"] > :nth-child(3),
|
||||
/* 4 - 8-20 */
|
||||
.health[data-value*="8"] > :nth-child(4),
|
||||
.health[data-value*="9"] > :nth-child(4),
|
||||
.health[data-value^="1"]:not([data-value="1"]) > :nth-child(4),
|
||||
.health[data-value="20"] > :nth-child(4),
|
||||
/* 5 - 10-20 */
|
||||
.health[data-value^="1"]:not([data-value="1"]) > :nth-child(5),
|
||||
.health[data-value="20"] > :nth-child(5),
|
||||
/* 6 - 12-20 */
|
||||
.health[data-value^="1"]:not([data-value$="1"]):not([data-value$="0"]) > :nth-child(6),
|
||||
.health[data-value="20"] > :nth-child(6),
|
||||
/* 7 - 14-20 */
|
||||
.health[data-value^="1"]:not([data-value$="0"]):not([data-value$="1"]):not([data-value$="2"]):not([data-value$="3"]) > :nth-child(7),
|
||||
.health[data-value="20"] > :nth-child(7),
|
||||
/* 8 - 16-20 */
|
||||
.health[data-value="16"] > :nth-child(8),
|
||||
.health[data-value="17"] > :nth-child(8),
|
||||
.health[data-value="18"] > :nth-child(8),
|
||||
.health[data-value="19"] > :nth-child(8),
|
||||
.health[data-value="20"] > :nth-child(8),
|
||||
/* 9 - 18-20 */
|
||||
.health[data-value="18"] > :nth-child(9),
|
||||
.health[data-value="19"] > :nth-child(9),
|
||||
.health[data-value="20"] > :nth-child(9),
|
||||
/* 10 - 20 */
|
||||
.health[data-value="20"] > :nth-child(10),
|
||||
.health > .full {
|
||||
visibility: visible;
|
||||
background-position: var(--offset) var(--background-y), var(--background-x) var(--background-y);
|
||||
}
|
||||
.health[data-value="1"] > :nth-child(1),
|
||||
.health[data-value="3"] > :nth-child(2),
|
||||
.health[data-value="5"] > :nth-child(3),
|
||||
.health[data-value="7"] > :nth-child(4),
|
||||
.health[data-value="9"] > :nth-child(5),
|
||||
.health[data-value="11"] > :nth-child(6),
|
||||
.health[data-value="13"] > :nth-child(7),
|
||||
.health[data-value="15"] > :nth-child(8),
|
||||
.health[data-value="17"] > :nth-child(9),
|
||||
.health[data-value="19"] > :nth-child(10),
|
||||
.health > .half {
|
||||
visibility: visible;
|
||||
background-position: calc((-3 * 9px) + var(--offset)) var(--background-y), var(--background-x) var(--background-y);
|
||||
}`
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`<div id="healthbar">
|
||||
<div class="health" data-value="20"><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p></div>
|
||||
<div class="health absorption" data-value="0"><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p></div>
|
||||
</div>`
|
||||
}
|
||||
}
|
||||
window.customElements.define('health-bar', HealthBar)
|
172
lib/hotbar.js
|
@ -1,172 +0,0 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
const invsprite = require('./invsprite.json')
|
||||
|
||||
class HotBar extends LitElement {
|
||||
updated (changedProperties) {
|
||||
if (changedProperties.has('bot')) {
|
||||
// inventory listener
|
||||
this.bot.once('spawn', () => {
|
||||
this.init()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
init () {
|
||||
this.reloadHotbar()
|
||||
this.reloadHotbarSelected(0)
|
||||
document.addEventListener('wheel', (e) => {
|
||||
const newSlot = ((this.bot.quickBarSlot + Math.sign(e.deltaY)) % 9 + 9) % 9
|
||||
this.reloadHotbarSelected(newSlot)
|
||||
})
|
||||
|
||||
this.bot.inventory.on('updateSlot', (slot, oldItem, newItem) => {
|
||||
if (slot >= this.bot.inventory.hotbarStart + 9) return
|
||||
if (slot < this.bot.inventory.hotbarStart) return
|
||||
|
||||
const sprite = newItem ? invsprite[newItem.name] : invsprite.air
|
||||
const slotImage = this.shadowRoot.getElementById('hotbar-' + (slot - this.bot.inventory.hotbarStart))
|
||||
slotImage.style['background-position-x'] = `-${sprite.x * 2}px`
|
||||
slotImage.style['background-position-y'] = `-${sprite.y * 2}px`
|
||||
slotImage.innerHTML = newItem?.count > 1 ? newItem.count : ''
|
||||
})
|
||||
}
|
||||
|
||||
async reloadHotbar () {
|
||||
for (let i = 0; i < 9; i++) {
|
||||
const item = this.bot.inventory.slots[this.bot.inventory.hotbarStart + i]
|
||||
const sprite = item ? invsprite[item.name] : invsprite.air
|
||||
const slotImage = this.shadowRoot.getElementById('hotbar-' + i)
|
||||
slotImage.style['background-position-x'] = `-${sprite.x * 2}px`
|
||||
slotImage.style['background-position-y'] = `-${sprite.y * 2}px`
|
||||
slotImage.innerHTML = item?.count > 1 ? item.count : ''
|
||||
}
|
||||
}
|
||||
|
||||
async reloadHotbarSelected (slot) {
|
||||
const item = this.bot.inventory.slots[this.bot.inventory.hotbarStart + slot]
|
||||
const planned = (20 * 4 * slot) + 'px'
|
||||
this.shadowRoot.getElementById('hotbar-highlight').style.marginLeft = planned
|
||||
this.bot.setQuickBarSlot(slot)
|
||||
this.activeItemName = item?.displayName
|
||||
const name = this.shadowRoot.getElementById('hotbar-item-name')
|
||||
name.classList.remove('hotbar-item-name-fader')
|
||||
setTimeout(() => name.classList.add('hotbar-item-name-fader'), 10)
|
||||
}
|
||||
|
||||
static get properties () {
|
||||
return {
|
||||
bot: { type: Object },
|
||||
viewerVersion: { type: String },
|
||||
activeItemName: { type: String }
|
||||
}
|
||||
}
|
||||
|
||||
static get styles () {
|
||||
return css`
|
||||
#hotbar-wrapper {
|
||||
image-rendering: optimizeSpeed;
|
||||
image-rendering: -moz-crisp-edges;
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
image-rendering: -o-crisp-edges;
|
||||
image-rendering: pixelated;
|
||||
-ms-interpolation-mode: nearest-neighbor;
|
||||
}
|
||||
|
||||
#hotbar-image {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
height: calc(256px * 4);
|
||||
width: calc(256px * 4);
|
||||
transform: translate(calc(-182px * 2), calc(-22px * 4));
|
||||
clip-path: inset(0px calc(74px * 4) calc(234px * 4) 0px);
|
||||
}
|
||||
|
||||
#hotbar-items-wrapper {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
height: calc(256px * 4);
|
||||
width: calc(256px * 4);
|
||||
transform: translate(calc(-182px * 2), calc(-22px * 4));
|
||||
clip-path: inset(0px calc(74px * 4) calc(234px * 4) 0px);
|
||||
}
|
||||
|
||||
#hotbar-highlight {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
height: calc(256px * 4);
|
||||
width: calc(256px * 4);
|
||||
margin-left: calc(20px * 4 * 4); /* EDIT THIS TO CHANGE WHICH SLOT IS SELECTED */
|
||||
transform: translate(calc((-24px * 2) - (20px * 4 * 4) ), calc((-22px * 4) + (-24px * 4) + 4px)); /* first need to translate up to account for clipping, then account for size of image, then 1px to center vertically over the image*/
|
||||
clip-path: inset(calc(22px * 4) calc(232px * 4) calc(210px * 4) 0px);
|
||||
}
|
||||
|
||||
.hotbar-item {
|
||||
display: inline-block;
|
||||
height: 32px;
|
||||
width: 64px;
|
||||
margin-top: 12px;
|
||||
margin-left: 12px;
|
||||
margin-right: 4.25px;
|
||||
background-image: url('invsprite.png');
|
||||
background-size: 2048px auto;
|
||||
text-align: right;
|
||||
font-size: 32px;
|
||||
vertical-align: top;
|
||||
padding-top: 32px;
|
||||
color: #ffffff;
|
||||
text-shadow: 2px 2px 0px #3f3f3f;
|
||||
font-family: mojangles, minecraft, monospace;
|
||||
}
|
||||
|
||||
#hotbar-item-name {
|
||||
color: white;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, calc(-170px));
|
||||
margin-top: 0px;
|
||||
text-shadow: rgb(63, 63, 63) 2px 2px 0px;
|
||||
font-family: mojangles, minecraft, monospace;
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hotbar-item-name-fader {
|
||||
opacity: 0;
|
||||
transition: visibility 0s, opacity 2s linear;
|
||||
transition-delay: 2s;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
constructor () {
|
||||
super()
|
||||
this.activeItemName = ''
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`
|
||||
<div id="hotbar-wrapper">
|
||||
<p id="hotbar-item-name">${this.activeItemName}</p>
|
||||
<img id="hotbar-image" src="textures/1.16.4/gui/widgets.png">
|
||||
<img id="hotbar-highlight" src="textures/1.16.4/gui/widgets.png">
|
||||
<div id="hotbar-items-wrapper">
|
||||
<div class="hotbar-item" id="hotbar-0"></div
|
||||
><div class="hotbar-item" id="hotbar-1"></div
|
||||
><div class="hotbar-item" id="hotbar-2"></div
|
||||
><div class="hotbar-item" id="hotbar-3"></div
|
||||
><div class="hotbar-item" id="hotbar-4"></div
|
||||
><div class="hotbar-item" id="hotbar-5"></div
|
||||
><div class="hotbar-item" id="hotbar-6"></div
|
||||
><div class="hotbar-item" id="hotbar-7"></div
|
||||
><div class="hotbar-item" id="hotbar-8"></div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('hot-bar', HotBar)
|
|
@ -1,84 +0,0 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
|
||||
class LoadingScreen extends LitElement {
|
||||
constructor () {
|
||||
super()
|
||||
this.status = 'Waiting for JS load'
|
||||
}
|
||||
|
||||
firstUpdated () {
|
||||
this.statusRunner()
|
||||
}
|
||||
|
||||
static get properties () {
|
||||
return {
|
||||
status: { type: String },
|
||||
loadingText: { type: String }
|
||||
}
|
||||
}
|
||||
|
||||
async statusRunner () {
|
||||
const array = ['.', '..', '...', '']
|
||||
// eslint-disable-next-line promise/param-names
|
||||
const timer = ms => new Promise(res => setTimeout(res, ms))
|
||||
|
||||
const load = async () => {
|
||||
for (let i = 0; true; i = ((i + 1) % array.length)) {
|
||||
this.loadingText = this.status + array[i]
|
||||
await timer(500)
|
||||
}
|
||||
}
|
||||
|
||||
load()
|
||||
}
|
||||
|
||||
static get styles () {
|
||||
return css`
|
||||
h1 {
|
||||
font-family: mojangles, minecraft, monospace;
|
||||
}
|
||||
.loader {
|
||||
display: initial;
|
||||
}
|
||||
#loading-image {
|
||||
height: 75%;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
position: absolute;
|
||||
transform: translate(-50%, -50%);
|
||||
image-rendering: crisp-edges;
|
||||
image-rendering: -webkit-crisp-edges;
|
||||
}
|
||||
|
||||
#loading-background {
|
||||
background-color: #60a490;
|
||||
z-index: 100;
|
||||
height: 100% !important;
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
#loading-text {
|
||||
color: #29594b;
|
||||
z-index: 200;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 12rem);
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`
|
||||
<div id="loading-background" class="loader">
|
||||
<img src="extra-textures/loading.png" id="loading-image">
|
||||
<div id="loading" class="loader">
|
||||
<h1 class="middle" id="loading-text">${this.loadingText}</h1>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('loading-screen', LoadingScreen)
|
175
lib/menu.js
|
@ -1,175 +0,0 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
require('./github_link')
|
||||
require('./components/button')
|
||||
require('./components/buttonlink')
|
||||
require('./components/textfield')
|
||||
|
||||
/* global fetch */
|
||||
class PrismarineMenu extends LitElement {
|
||||
constructor () {
|
||||
super()
|
||||
|
||||
this.server = ''
|
||||
this.serverport = 25565
|
||||
this.proxy = ''
|
||||
this.proxyport = ''
|
||||
this.username = window.localStorage.getItem('username') ?? 'pviewer' + (Math.floor(Math.random() * 1000))
|
||||
this.password = ''
|
||||
fetch('config.json').then(res => res.json()).then(config => {
|
||||
this.server = config.defaultHost
|
||||
this.serverport = config.defaultHostPort ?? 25565
|
||||
this.proxy = config.defaultProxy
|
||||
this.proxyport = !config.defaultProxy && !config.defaultProxyPort ? '' : config.defaultProxyPort ?? 443
|
||||
})
|
||||
}
|
||||
|
||||
static get properties () {
|
||||
return {
|
||||
server: { type: String },
|
||||
serverport: { type: Number },
|
||||
proxy: { type: String },
|
||||
proxyport: { type: Number },
|
||||
username: { type: String },
|
||||
password: { type: String }
|
||||
}
|
||||
}
|
||||
|
||||
static get styles () {
|
||||
return css`
|
||||
:host {
|
||||
--guiScale: var(--guiScaleFactor, 3);
|
||||
}
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin:0;
|
||||
padding:0;
|
||||
font-family: sans-serif;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.login-box {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: calc(180px * var(--guiScale));
|
||||
padding: calc(10px * var(--guiScale));
|
||||
transform: translate(-50%, -50%);
|
||||
box-sizing: border-box;
|
||||
border-radius: 10px;
|
||||
background: rgba(0, 0, 0, 0.5)
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
flex-direction: column
|
||||
}
|
||||
|
||||
.bottom-links {
|
||||
margin-top: calc(6px * var(--guiScale));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bottom-links span {
|
||||
text-align: center;
|
||||
color: rgb(175, 175, 175);
|
||||
padding: calc(1px * var(--guiScale));
|
||||
font-family: mojangles, minecraft, monospace;
|
||||
font-size: calc(10px * var(--guiScale));
|
||||
text-shadow: calc(1px * var(--guiScale)) calc(1px * var(--guiScale)) black;
|
||||
}
|
||||
|
||||
.link-buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: calc(4px * var(--guiScale));
|
||||
}
|
||||
|
||||
.title, .subtitle {
|
||||
text-align: center;
|
||||
|
||||
font-family: mojangles, minecraft, monospace;
|
||||
font-size: calc(10px * var(--guiScale));
|
||||
font-weight: normal;
|
||||
|
||||
color: white;
|
||||
margin-top: 0;
|
||||
text-shadow: calc(1px * var(--guiScale)) calc(1px * var(--guiScale)) black;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: calc(7.5px * var(--guiScale));
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: calc(6px * var(--guiScale));
|
||||
}
|
||||
|
||||
.spacev {
|
||||
height: calc(6px * var(--guiScale));
|
||||
}
|
||||
|
||||
.field-spacev {
|
||||
height: calc(14px * var(--guiScale));
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
dispatchConnect () {
|
||||
window.localStorage.setItem('username', this.username)
|
||||
this.dispatchEvent(new window.CustomEvent('connect', {
|
||||
detail: {
|
||||
server: `${this.server}:${this.serverport}`,
|
||||
proxy: `${this.proxy}${this.proxy !== '' ? `:${this.proxyport}` : ''}`,
|
||||
username: this.username,
|
||||
password: this.password
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`
|
||||
<github-link></github-link>
|
||||
<div class="login-box">
|
||||
<h2 class="title">Prismarine Web Client</h2>
|
||||
<h3 class="subtitle" style="color: rgb(175, 175, 175)">A minecraft client in the browser!</h3>
|
||||
<form>
|
||||
<div class="field-spacev"></div>
|
||||
<div class="wrapper">
|
||||
<legacy-text-field field-width="100%" field-label="Server IP" field-id="serverip" field-value="${this.server}" @input=${e => { this.server = e.target.value }}></legacy-text-field>
|
||||
<legacy-text-field field-width="100%" field-label="Server Port" field-id="port" field-value="${this.serverport}" @input=${e => { this.serverport = e.target.value }}></legacy-text-field>
|
||||
</div>
|
||||
<div class="field-spacev"></div>
|
||||
<div class="wrapper">
|
||||
<legacy-text-field field-width="100%" field-label="Proxy" field-id="proxy" field-value="${this.proxy}" @input=${e => { this.proxy = e.target.value }}></legacy-text-field>
|
||||
<legacy-text-field field-width="100%" field-label="Port" field-id="port" field-value="${this.proxyport}" @input=${e => { this.proxyport = e.target.value }}></legacy-text-field>
|
||||
</div>
|
||||
<div class="field-spacev"></div>
|
||||
<legacy-text-field field-width="100%" field-label="Username" field-id="username" field-value="${this.username}" @input=${e => { this.username = e.target.value }}></legacy-text-field>
|
||||
<div class="spacev"></div>
|
||||
<legacy-button btn-width="100%" @click=${() => { this.dispatchConnect() }}>Play</legacy-button>
|
||||
<div class="bottom-links">
|
||||
<span> Want to contribute?</span>
|
||||
<div class="link-buttons">
|
||||
<legacy-button-link btn-width="78px" go-to="https://github.com/PrismarineJS/prismarine-web-client">Github</legacy-button-link>
|
||||
<legacy-button-link btn-width="78px" go-to="https://discord.gg/4Ucm684Fq3">Discord</legacy-button-link>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('prismarine-menu', PrismarineMenu)
|
88
lib/menus/components/breath_bar.js
Normal file
|
@ -0,0 +1,88 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
|
||||
class BreathBar extends LitElement {
|
||||
static get styles () {
|
||||
return css`
|
||||
.breathbar {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
left: calc(50% + 91px);
|
||||
transform: translate(-100%);
|
||||
bottom: 40px;
|
||||
--offset: calc(-1 * 16px);
|
||||
--bg-x: calc(-1 * 16px);
|
||||
--bg-y: calc(-1 * 18px);
|
||||
}
|
||||
|
||||
.breath {
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
margin-left: -1px;
|
||||
}
|
||||
|
||||
.breath.full {
|
||||
background-image: url('textures/1.17.1/gui/icons.png');
|
||||
background-size: 256px;
|
||||
background-position: var(--offset) var(--bg-y);
|
||||
}
|
||||
|
||||
.breath.half {
|
||||
background-image: url('textures/1.17.1/gui/icons.png');
|
||||
background-size: 256px;
|
||||
background-position: calc(var(--offset) - 9) var(--bg-y);
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
gameModeChanged (gamemode) {
|
||||
this.shadowRoot.querySelector('#breathbar').classList.toggle('creative', gamemode === 1)
|
||||
}
|
||||
|
||||
updateOxygen (hValue) {
|
||||
const breathbar = this.shadowRoot.querySelector('#breathbar')
|
||||
breathbar.style.display = 'block'
|
||||
|
||||
const breaths = breathbar.children
|
||||
|
||||
for (let i = 0; i < breaths.length; i++) {
|
||||
breaths[i].classList.remove('full')
|
||||
breaths[i].classList.remove('half')
|
||||
}
|
||||
|
||||
for (let i = 0; i < Math.ceil(hValue / 2); i++) {
|
||||
if (i >= breaths.length) break
|
||||
|
||||
if (hValue % 2 !== 0 && Math.ceil(hValue / 2) === i + 1) {
|
||||
breaths[i].classList.add('half')
|
||||
} else {
|
||||
breaths[i].classList.add('full')
|
||||
}
|
||||
}
|
||||
|
||||
// if (hValue === 20) {
|
||||
// setTimeout(() => {
|
||||
// breathbar.style.display = 'none'
|
||||
// }, 1000)
|
||||
// }
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`
|
||||
<div id="breathbar" class="breathbar">
|
||||
<div class="breath"></div>
|
||||
<div class="breath"></div>
|
||||
<div class="breath"></div>
|
||||
<div class="breath"></div>
|
||||
<div class="breath"></div>
|
||||
<div class="breath"></div>
|
||||
<div class="breath"></div>
|
||||
<div class="breath"></div>
|
||||
<div class="breath"></div>
|
||||
<div class="breath"></div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('pmui-breathbar', BreathBar)
|
140
lib/menus/components/button.js
Normal file
|
@ -0,0 +1,140 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
|
||||
const audioContext = new window.AudioContext()
|
||||
const sounds = {}
|
||||
|
||||
async function playSound (path) {
|
||||
let volume = 1
|
||||
const options = document.getElementById('options-screen')
|
||||
if (options) {
|
||||
volume = options.sound / 100
|
||||
}
|
||||
|
||||
let soundBuffer = sounds[path]
|
||||
|
||||
if (!soundBuffer) {
|
||||
const res = await window.fetch(path)
|
||||
const data = await res.arrayBuffer()
|
||||
|
||||
soundBuffer = await audioContext.decodeAudioData(data)
|
||||
sounds[path] = soundBuffer
|
||||
}
|
||||
|
||||
const gainNode = audioContext.createGain()
|
||||
const source = audioContext.createBufferSource()
|
||||
source.buffer = soundBuffer
|
||||
source.connect(gainNode)
|
||||
gainNode.connect(audioContext.destination)
|
||||
gainNode.gain.value = volume
|
||||
source.start(0)
|
||||
}
|
||||
|
||||
class Button extends LitElement {
|
||||
static get styles () {
|
||||
return css`
|
||||
.button {
|
||||
--txrV: 66px;
|
||||
position: relative;
|
||||
width: 200px;
|
||||
height: 20px;
|
||||
font-family: minecraft, mojangles, monospace;
|
||||
font-size: 10px;
|
||||
color: white;
|
||||
text-shadow: 1px 1px #222;
|
||||
border: none;
|
||||
z-index: 1;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.button:hover,
|
||||
.button:focus-visible {
|
||||
--txrV: 86px;
|
||||
}
|
||||
|
||||
.button:disabled {
|
||||
--txrV: 46px;
|
||||
color: #A0A0A0;
|
||||
text-shadow: 1px 1px #111;
|
||||
}
|
||||
|
||||
.button::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 50%;
|
||||
height: 20px;
|
||||
background: url('textures/1.17.1/gui/widgets.png');
|
||||
background-size: 256px;
|
||||
background-position-y: calc(var(--txrV) * -1);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.button::before {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
width: 50%;
|
||||
height: 20px;
|
||||
background: url('textures/1.17.1/gui/widgets.png');
|
||||
background-size: 256px;
|
||||
background-position-x: calc(-200px + 100%);
|
||||
background-position-y: calc(var(--txrV) * -1);
|
||||
z-index: -1;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
static get properties () {
|
||||
return {
|
||||
label: {
|
||||
type: String,
|
||||
attribute: 'pmui-label'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
attribute: 'pmui-width'
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
attribute: 'pmui-disabled'
|
||||
},
|
||||
onPress: {
|
||||
type: Function,
|
||||
attribute: 'pmui-click'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor () {
|
||||
super()
|
||||
this.label = ''
|
||||
this.disabled = false
|
||||
this.width = '200px'
|
||||
this.onPress = () => {}
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`
|
||||
<button
|
||||
class="button"
|
||||
?disabled=${this.disabled}
|
||||
@click=${this.onBtnClick}
|
||||
style="width: ${this.width};"
|
||||
>
|
||||
${this.label}
|
||||
</button>`
|
||||
}
|
||||
|
||||
onBtnClick () {
|
||||
playSound('click_stereo.ogg')
|
||||
this.dispatchEvent(new window.CustomEvent('pmui-click'))
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('pmui-button', Button)
|
||||
const _playSound = playSound
|
||||
export { _playSound as playSound }
|
65
lib/menus/components/common.js
Normal file
|
@ -0,0 +1,65 @@
|
|||
import { css } from 'lit'
|
||||
|
||||
const commonCss = css`
|
||||
.dirt-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: url('textures/1.17.1/gui/options_background.png'), rgba(0, 0, 0, 0.75);
|
||||
background-size: 16px;
|
||||
background-repeat: repeat;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transform-origin: top left;
|
||||
transform: scale(2);
|
||||
background-blend-mode: overlay;
|
||||
}
|
||||
|
||||
.bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: rgba(0, 0, 0, 0.75);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.title {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translate(-50%);
|
||||
font-size: 10px;
|
||||
color: white;
|
||||
text-align: center;
|
||||
text-shadow: 1px 1px #222;
|
||||
}
|
||||
|
||||
.text {
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
text-shadow: 1px 1px #222;
|
||||
}
|
||||
`
|
||||
|
||||
/**
|
||||
* @param {string} url
|
||||
*/
|
||||
function openURL (url) {
|
||||
window.open(url, '_blank', 'noopener,noreferrer')
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} prev
|
||||
* @param {HTMLElement} next
|
||||
*/
|
||||
function displayScreen (prev, next) {
|
||||
prev.style.display = 'none'
|
||||
next.style.display = 'block'
|
||||
}
|
||||
|
||||
export {
|
||||
commonCss,
|
||||
openURL,
|
||||
displayScreen
|
||||
}
|
146
lib/menus/components/debug_overlay.js
Normal file
|
@ -0,0 +1,146 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
|
||||
class DebugOverlay extends LitElement {
|
||||
static get styles () {
|
||||
return css`
|
||||
.debug-left-side,
|
||||
.debug-right-side {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 40;
|
||||
}
|
||||
|
||||
.debug-left-side {
|
||||
top: 1px;
|
||||
left: 1px;
|
||||
}
|
||||
|
||||
.debug-right-side {
|
||||
top: 1px;
|
||||
right: 1px;
|
||||
}
|
||||
|
||||
p {
|
||||
display: block;
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
width: fit-content;
|
||||
height: 9px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
padding-bottom: 1px;
|
||||
background: rgba(110, 110, 110, 0.5);
|
||||
}
|
||||
|
||||
.debug-right-side p {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.empty {
|
||||
display: block;
|
||||
height: 9px;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
static get properties () {
|
||||
return {
|
||||
showOverlay: { type: Boolean },
|
||||
cursorBlock: { type: Object },
|
||||
bot: { type: Object },
|
||||
customEntries: { type: Object }
|
||||
}
|
||||
}
|
||||
|
||||
constructor () {
|
||||
super()
|
||||
this.showOverlay = false
|
||||
this.customEntries = {}
|
||||
}
|
||||
|
||||
firstUpdated () {
|
||||
document.addEventListener('keydown', e => {
|
||||
e ??= window.event
|
||||
if (e.key === 'F3') {
|
||||
this.showOverlay = !this.showOverlay
|
||||
e.preventDefault()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
updated (changedProperties) {
|
||||
if (changedProperties.has('bot')) {
|
||||
this.bot.on('move', () => {
|
||||
this.requestUpdate()
|
||||
})
|
||||
this.bot.on('time', () => {
|
||||
this.requestUpdate()
|
||||
})
|
||||
this.bot.on('entitySpawn', () => {
|
||||
this.requestUpdate()
|
||||
})
|
||||
this.bot.on('entityGone', () => {
|
||||
this.requestUpdate()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
if (!this.showOverlay) {
|
||||
return html``
|
||||
}
|
||||
|
||||
const target = this.cursorBlock
|
||||
const targetDiggable = (target && this.bot.canDigBlock(target))
|
||||
|
||||
const pos = this.bot.entity.position
|
||||
const rot = [this.bot.entity.yaw, this.bot.entity.pitch]
|
||||
|
||||
const viewDegToMinecraft = (yaw) => yaw % 360 - 180 * (yaw < 0 ? -1 : 1)
|
||||
|
||||
const quadsDescription = [
|
||||
'north (towards negative Z)',
|
||||
'east (towards positive X)',
|
||||
'south (towards positive Z)',
|
||||
'west (towards negative X)'
|
||||
]
|
||||
|
||||
const minecraftYaw = viewDegToMinecraft(rot[0] * -180 / Math.PI)
|
||||
const minecraftQuad = Math.floor(((minecraftYaw + 180) / 90 + 0.5) % 4)
|
||||
|
||||
const renderProp = (name, value) => {
|
||||
return html`<p>${name}: ${typeof value === 'boolean' ? html`<span style="color: ${value ? 'lightgreen' : 'red'}">${value}</span>` : value}</p>`
|
||||
}
|
||||
|
||||
const skyL = this.bot.world.getSkyLight(this.bot.entity.position)
|
||||
const biomeId = this.bot.world.getBiome(this.bot.entity.position)
|
||||
|
||||
return html`
|
||||
<div class="debug-left-side">
|
||||
<p>Prismarine Web Client (${this.bot.version})</p>
|
||||
<p>E: ${Object.values(this.bot.entities).length}</p>
|
||||
<p>${this.bot.game.dimension}</p>
|
||||
<div class="empty"></div>
|
||||
<p>XYZ: ${pos.x.toFixed(3)} / ${pos.y.toFixed(3)} / ${pos.z.toFixed(3)}</p>
|
||||
<p>Chunk: ${Math.floor(pos.x % 16)} ~ ${Math.floor(pos.z % 16)} in ${Math.floor(pos.x / 16)} ~ ${Math.floor(pos.z / 16)}</p>
|
||||
<p>Facing (viewer): ${rot[0].toFixed(3)} ${rot[1].toFixed(3)}</p>
|
||||
<p>Facing (minecraft): ${quadsDescription[minecraftQuad]} (${minecraftYaw.toFixed(1)} ${(rot[1] * -180 / Math.PI).toFixed(1)})</p>
|
||||
<p>Light: ${skyL} (${skyL} sky)</p>
|
||||
<p>Biome: minecraft:${window.mcData.biomesArray[biomeId].name}</p>
|
||||
<p>Day: ${this.bot.time.day}</p>
|
||||
<div class="empty"></div>
|
||||
${Object.entries(this.customEntries).map(([name, value]) => html`<p>${name}: ${value}</p>`)}
|
||||
</div>
|
||||
|
||||
<div class="debug-right-side">
|
||||
<p>Renderer: three.js r${global.THREE.REVISION}</p>
|
||||
<div class="empty"></div>
|
||||
${targetDiggable ? html`<p>${target.name}</p>${Object.entries(target.getProperties()).map(([n, p], idx, arr) => renderProp(n, p, arr[idx + 1]))}` : ''}
|
||||
${targetDiggable ? html`<p>Looking at: ${target.position.x} ${target.position.y} ${target.position.z}</p>` : ''}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('pmui-debug-overlay', DebugOverlay)
|
97
lib/menus/components/edit_box.js
Normal file
|
@ -0,0 +1,97 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
|
||||
class EditBox extends LitElement {
|
||||
static get styles () {
|
||||
return css`
|
||||
.edit-container {
|
||||
position: relative;
|
||||
width: 200px;
|
||||
height: 20px;
|
||||
background: black;
|
||||
border: 1px solid grey;
|
||||
}
|
||||
|
||||
.edit-container:hover,
|
||||
.edit-container:focus-within {
|
||||
border-color: white;
|
||||
}
|
||||
|
||||
.edit-container label {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
bottom: 21px;
|
||||
left: 0;
|
||||
font-size: 10px;
|
||||
color: rgb(206, 206, 206);
|
||||
text-shadow: 1px 1px black;
|
||||
}
|
||||
|
||||
.edit-box {
|
||||
position: relative;
|
||||
outline: none;
|
||||
border: none;
|
||||
background: none;
|
||||
left: 1px;
|
||||
width: calc(100% - 2px);
|
||||
height: 100%;
|
||||
font-family: minecraft, mojangles, monospace;
|
||||
font-size: 10px;
|
||||
color: white;
|
||||
text-shadow: 1px 1px #222;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
constructor () {
|
||||
super()
|
||||
this.width = '200px'
|
||||
this.id = ''
|
||||
this.value = ''
|
||||
this.label = ''
|
||||
}
|
||||
|
||||
static get properties () {
|
||||
return {
|
||||
width: {
|
||||
type: String,
|
||||
attribute: 'pmui-width'
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
attribute: 'pmui-id'
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
attribute: 'pmui-label'
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
attribute: 'pmui-value'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`
|
||||
<div
|
||||
class="edit-container"
|
||||
style="width: ${this.width};"
|
||||
>
|
||||
<label for="${this.id}">${this.label}</label>
|
||||
<input
|
||||
id="${this.id}"
|
||||
type="text"
|
||||
name=""
|
||||
spellcheck="false"
|
||||
required=""
|
||||
autocomplete="off"
|
||||
value="${this.value}"
|
||||
@input=${(e) => { this.value = e.target.value }}
|
||||
class="edit-box">
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('pmui-editbox', EditBox)
|
117
lib/menus/components/food_bar.js
Normal file
|
@ -0,0 +1,117 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
|
||||
class FoodBar extends LitElement {
|
||||
static get styles () {
|
||||
return css`
|
||||
.foodbar {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
left: calc(50% + 91px);
|
||||
transform: translate(-100%);
|
||||
bottom: 30px;
|
||||
--lightened: 0;
|
||||
--offset: calc(-1 * (52px));
|
||||
--bg-x: calc(-1 * (16px + 9px * var(--lightened)));
|
||||
--bg-y: calc(-1 * 27px);
|
||||
}
|
||||
|
||||
.food {
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
background-image: url('textures/1.17.1/gui/icons.png'), url('textures/1.17.1/gui/icons.png');
|
||||
background-size: 256px, 256px;
|
||||
background-position: var(--bg-x) var(--bg-y), var(--bg-x) var(--bg-y);
|
||||
margin-left: -1px;
|
||||
}
|
||||
|
||||
.food.full {
|
||||
background-position: var(--offset) var(--bg-y), var(--bg-x) var(--bg-y);
|
||||
}
|
||||
|
||||
.food.half {
|
||||
background-position: calc(var(--offset) - 9px) var(--bg-y), var(--bg-x) var(--bg-y);
|
||||
}
|
||||
|
||||
.foodbar.low .food {
|
||||
animation: lowHungerAnim 0.2s steps(2, end) infinite;
|
||||
}
|
||||
|
||||
.foodbar.low .food:nth-of-type(2n) {
|
||||
animation-direction: reverse;
|
||||
}
|
||||
|
||||
.foodbar.low .food:nth-of-type(3n) {
|
||||
animation-duration: 0.1s;
|
||||
}
|
||||
|
||||
.foodbar.updated {
|
||||
animation: updatedAnim 0.3s steps(2, end) 2;
|
||||
}
|
||||
|
||||
@keyframes lowHungerAnim {
|
||||
to { transform: translateY(1px); }
|
||||
}
|
||||
|
||||
@keyframes updatedAnim {
|
||||
to { --lightened: 1; }
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
gameModeChanged (gamemode) {
|
||||
this.shadowRoot.querySelector('#foodbar').classList.toggle('creative', gamemode === 1)
|
||||
}
|
||||
|
||||
onHungerUpdate () {
|
||||
this.shadowRoot.querySelector('#foodbar').classList.toggle('updated', true)
|
||||
if (this.hungerTimeout) clearTimeout(this.hungerTimeout)
|
||||
this.hungerTimeout = setTimeout(() => {
|
||||
this.shadowRoot.querySelector('#foodbar').classList.toggle('updated', false)
|
||||
this.hungerTimeout = null
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
updateHunger (hValue, d) {
|
||||
const foodbar = this.shadowRoot.querySelector('#foodbar')
|
||||
foodbar.classList.toggle('low', hValue <= 5)
|
||||
|
||||
const foods = foodbar.children
|
||||
|
||||
for (let i = 0; i < foods.length; i++) {
|
||||
foods[i].classList.remove('full')
|
||||
foods[i].classList.remove('half')
|
||||
}
|
||||
|
||||
// if (d) this.onHungerUpdate()
|
||||
|
||||
for (let i = 0; i < Math.ceil(hValue / 2); i++) {
|
||||
if (i >= foods.length) break
|
||||
|
||||
if (hValue % 2 !== 0 && Math.ceil(hValue / 2) === i + 1) {
|
||||
foods[i].classList.add('half')
|
||||
} else {
|
||||
foods[i].classList.add('full')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`
|
||||
<div id="foodbar" class="foodbar" data-value="4">
|
||||
<div class="food"></div>
|
||||
<div class="food"></div>
|
||||
<div class="food"></div>
|
||||
<div class="food"></div>
|
||||
<div class="food"></div>
|
||||
<div class="food"></div>
|
||||
<div class="food"></div>
|
||||
<div class="food"></div>
|
||||
<div class="food"></div>
|
||||
<div class="food"></div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('pmui-foodbar', FoodBar)
|
158
lib/menus/components/health_bar.js
Normal file
|
@ -0,0 +1,158 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
|
||||
function getEffectClass (effect) {
|
||||
switch (effect.id) {
|
||||
case 19: return 'poisoned'
|
||||
case 20: return 'withered'
|
||||
case 22: return 'absorption'
|
||||
default: return ''
|
||||
}
|
||||
}
|
||||
|
||||
class HealthBar extends LitElement {
|
||||
static get styles () {
|
||||
return css`
|
||||
.health {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
left: calc(50% - 91px);
|
||||
bottom: 30px;
|
||||
--hardcore: 0;
|
||||
--kind: 0;
|
||||
--lightened: 0;
|
||||
--offset: calc(-1 * (52px + (9px * (4 * var(--kind) + var(--lightened) * 2)) ));
|
||||
--bg-x: calc(-1 * (16px + 9px * var(--lightened)));
|
||||
--bg-y: calc(-1 * var(--hardcore) * 45px);
|
||||
}
|
||||
|
||||
.health.creative {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.health.hardcore {
|
||||
--hardcore: 1;
|
||||
}
|
||||
|
||||
.health.poisoned {
|
||||
--kind: 1;
|
||||
}
|
||||
|
||||
.health.withered {
|
||||
--kind: 2;
|
||||
}
|
||||
|
||||
.health.absorption {
|
||||
--kind: 3;
|
||||
}
|
||||
|
||||
.heart {
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
background-image: url('textures/1.17.1/gui/icons.png'), url('textures/1.17.1/gui/icons.png');
|
||||
background-size: 256px, 256px;
|
||||
background-position: var(--bg-x) var(--bg-y), var(--bg-x) var(--bg-y);
|
||||
margin-left: -1px;
|
||||
}
|
||||
|
||||
.heart.full {
|
||||
background-position: var(--offset) var(--bg-y), var(--bg-x) var(--bg-y);
|
||||
}
|
||||
|
||||
.heart.half {
|
||||
background-position: calc(var(--offset) - 9px) var(--bg-y), var(--bg-x) var(--bg-y);
|
||||
}
|
||||
|
||||
.health.low .heart {
|
||||
animation: lowHealthAnim 0.2s steps(2, end) infinite;
|
||||
}
|
||||
|
||||
.health.low .heart:nth-of-type(2n) {
|
||||
animation-direction: reverse;
|
||||
}
|
||||
|
||||
.health.low .heart:nth-of-type(3n) {
|
||||
animation-duration: 0.1s;
|
||||
}
|
||||
|
||||
.health.damaged {
|
||||
animation: damagedAnim 0.3s steps(2, end) 2;
|
||||
}
|
||||
|
||||
@keyframes lowHealthAnim {
|
||||
to {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes damagedAnim {
|
||||
to { --lightened: 1; }
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
effectAdded (effect) {
|
||||
this.shadowRoot.querySelector('#health').classList.add(getEffectClass(effect))
|
||||
}
|
||||
|
||||
effectEnded (effect) {
|
||||
this.shadowRoot.querySelector('#health').classList.remove(getEffectClass(effect))
|
||||
}
|
||||
|
||||
onDamage () {
|
||||
this.shadowRoot.querySelector('#health').classList.toggle('damaged', true)
|
||||
if (this.hurtTimeout) clearTimeout(this.hurtTimeout)
|
||||
this.hurtTimeout = setTimeout(() => {
|
||||
this.shadowRoot.querySelector('#health').classList.toggle('damaged', false)
|
||||
this.hurtTimeout = null
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
gameModeChanged (gamemode, hardcore) {
|
||||
this.shadowRoot.querySelector('#health').classList.toggle('creative', gamemode === 1)
|
||||
this.shadowRoot.querySelector('#health').classList.toggle('hardcore', hardcore)
|
||||
}
|
||||
|
||||
updateHealth (hValue, d) {
|
||||
const health = this.shadowRoot.querySelector('#health')
|
||||
health.classList.toggle('low', hValue <= 4)
|
||||
|
||||
const hearts = health.children
|
||||
|
||||
for (let i = 0; i < hearts.length; i++) {
|
||||
hearts[i].classList.remove('full')
|
||||
hearts[i].classList.remove('half')
|
||||
}
|
||||
|
||||
if (d) this.onDamage()
|
||||
|
||||
for (let i = 0; i < Math.ceil(hValue / 2); i++) {
|
||||
if (i >= hearts.length) break
|
||||
|
||||
if (hValue % 2 !== 0 && Math.ceil(hValue / 2) === i + 1) {
|
||||
hearts[i].classList.add('half')
|
||||
} else {
|
||||
hearts[i].classList.add('full')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`
|
||||
<div id="health" class="health">
|
||||
<div class="heart"></div>
|
||||
<div class="heart"></div>
|
||||
<div class="heart"></div>
|
||||
<div class="heart"></div>
|
||||
<div class="heart"></div>
|
||||
<div class="heart"></div>
|
||||
<div class="heart"></div>
|
||||
<div class="heart"></div>
|
||||
<div class="heart"></div>
|
||||
<div class="heart"></div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('pmui-healthbar', HealthBar)
|
210
lib/menus/components/hotbar.js
Normal file
|
@ -0,0 +1,210 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
const invsprite = require('../../invsprite.json')
|
||||
|
||||
class Hotbar extends LitElement {
|
||||
static get styles () {
|
||||
return css`
|
||||
.hotbar {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translate(-50%);
|
||||
width: 182px;
|
||||
height: 22px;
|
||||
background: url("textures/1.16.4/gui/widgets.png");
|
||||
background-size: 256px;
|
||||
}
|
||||
|
||||
#hotbar-selected {
|
||||
position: absolute;
|
||||
left: -1px;
|
||||
top: -1px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: url("textures/1.16.4/gui/widgets.png");
|
||||
background-size: 256px;
|
||||
background-position-y: -22px;
|
||||
}
|
||||
|
||||
#hotbar-items-wrapper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 1px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 22px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.hotbar-item {
|
||||
position: relative;
|
||||
width: 20px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
top: 3px;
|
||||
left: 2px;
|
||||
position: absolute;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
transform-origin: top left;
|
||||
transform: scale(0.5);
|
||||
background-image: url('invsprite.png');
|
||||
background-size: 1024px auto;
|
||||
}
|
||||
|
||||
.item-stack {
|
||||
position: absolute;
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
text-shadow: 1px 1px 0 rgb(63, 63, 63);
|
||||
right: 1px;
|
||||
bottom: 1px;
|
||||
}
|
||||
|
||||
#hotbar-item-name {
|
||||
color: white;
|
||||
position: absolute;
|
||||
bottom: 51px;
|
||||
left: 50%;
|
||||
transform: translate(-50%);
|
||||
text-shadow: rgb(63, 63, 63) 1px 1px 0px;
|
||||
font-family: mojangles, minecraft, monospace;
|
||||
font-size: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hotbar-item-name-fader {
|
||||
opacity: 0;
|
||||
transition: visibility 0s, opacity 1s linear;
|
||||
transition-delay: 2s;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
static get properties () {
|
||||
return {
|
||||
activeItemName: { type: String },
|
||||
bot: { type: Object },
|
||||
viewerVersion: { type: String }
|
||||
}
|
||||
}
|
||||
|
||||
constructor () {
|
||||
super()
|
||||
this.activeItemName = ''
|
||||
}
|
||||
|
||||
updated (changedProperties) {
|
||||
if (changedProperties.has('bot')) {
|
||||
// inventory listener
|
||||
this.bot.once('spawn', () => {
|
||||
this.init()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
init () {
|
||||
this.reloadHotbar()
|
||||
this.reloadHotbarSelected(0)
|
||||
|
||||
document.addEventListener('wheel', (e) => {
|
||||
const newSlot = ((this.bot.quickBarSlot + Math.sign(e.deltaY)) % 9 + 9) % 9
|
||||
this.reloadHotbarSelected(newSlot)
|
||||
})
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
const numPressed = e.code.substr(5)
|
||||
if (numPressed < 1 || numPressed > 9) return
|
||||
this.reloadHotbarSelected(numPressed - 1)
|
||||
})
|
||||
|
||||
this.bot.inventory.on('updateSlot', (slot, oldItem, newItem) => {
|
||||
if (slot >= this.bot.inventory.hotbarStart + 9) return
|
||||
if (slot < this.bot.inventory.hotbarStart) return
|
||||
|
||||
const sprite = newItem ? invsprite[newItem.name] : invsprite.air
|
||||
const slotEl = this.shadowRoot.getElementById('hotbar-' + (slot - this.bot.inventory.hotbarStart))
|
||||
const slotIcon = slotEl.children[0]
|
||||
const slotStack = slotEl.children[1]
|
||||
slotIcon.style['background-position-x'] = `-${sprite.x}px`
|
||||
slotIcon.style['background-position-y'] = `-${sprite.y}px`
|
||||
slotStack.innerHTML = newItem?.count > 1 ? newItem.count : ''
|
||||
})
|
||||
}
|
||||
|
||||
async reloadHotbar () {
|
||||
for (let i = 0; i < 9; i++) {
|
||||
const item = this.bot.inventory.slots[this.bot.inventory.hotbarStart + i]
|
||||
const sprite = item ? invsprite[item.name] : invsprite.air
|
||||
const slotEl = this.shadowRoot.getElementById('hotbar-' + i)
|
||||
const slotIcon = slotEl.children[0]
|
||||
const slotStack = slotEl.children[1]
|
||||
slotIcon.style['background-position-x'] = `-${sprite.x}px`
|
||||
slotIcon.style['background-position-y'] = `-${sprite.y}px`
|
||||
slotStack.innerHTML = item?.count > 1 ? item.count : ''
|
||||
}
|
||||
}
|
||||
|
||||
async reloadHotbarSelected (slot) {
|
||||
const item = this.bot.inventory.slots[this.bot.inventory.hotbarStart + slot]
|
||||
const newLeftPos = (-1 + 20 * slot) + 'px'
|
||||
this.shadowRoot.getElementById('hotbar-selected').style.left = newLeftPos
|
||||
this.bot.setQuickBarSlot(slot)
|
||||
this.activeItemName = item?.displayName ?? ''
|
||||
const name = this.shadowRoot.getElementById('hotbar-item-name')
|
||||
name.classList.remove('hotbar-item-name-fader')
|
||||
setTimeout(() => name.classList.add('hotbar-item-name-fader'), 10)
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`
|
||||
<div class="hotbar">
|
||||
<p id="hotbar-item-name">${this.activeItemName}</p>
|
||||
<div id="hotbar-selected"></div>
|
||||
<div id="hotbar-items-wrapper">
|
||||
<div class="hotbar-item" id="hotbar-0">
|
||||
<div class="item-icon"></div>
|
||||
<span class="item-stack"></span>
|
||||
</div>
|
||||
<div class="hotbar-item" id="hotbar-1">
|
||||
<div class="item-icon"></div>
|
||||
<span class="item-stack"></span>
|
||||
</div>
|
||||
<div class="hotbar-item" id="hotbar-2">
|
||||
<div class="item-icon"></div>
|
||||
<span class="item-stack"></span>
|
||||
</div>
|
||||
<div class="hotbar-item" id="hotbar-3">
|
||||
<div class="item-icon"></div>
|
||||
<span class="item-stack"></span>
|
||||
</div>
|
||||
<div class="hotbar-item" id="hotbar-4">
|
||||
<div class="item-icon"></div>
|
||||
<span class="item-stack"></span>
|
||||
</div>
|
||||
<div class="hotbar-item" id="hotbar-5">
|
||||
<div class="item-icon"></div>
|
||||
<span class="item-stack"></span>
|
||||
</div>
|
||||
<div class="hotbar-item" id="hotbar-6">
|
||||
<div class="item-icon"></div>
|
||||
<span class="item-stack"></span>
|
||||
</div>
|
||||
<div class="hotbar-item" id="hotbar-7">
|
||||
<div class="item-icon"></div>
|
||||
<span class="item-stack"></span>
|
||||
</div>
|
||||
<div class="hotbar-item" id="hotbar-8">
|
||||
<div class="item-icon"></div>
|
||||
<span class="item-stack"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('pmui-hotbar', Hotbar)
|
179
lib/menus/components/playerlist_overlay.js
Normal file
|
@ -0,0 +1,179 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
|
||||
const MAX_ROWS_PER_COL = 10
|
||||
|
||||
class PlayerListOverlay extends LitElement {
|
||||
static get styles () {
|
||||
return css`
|
||||
.playerlist-container {
|
||||
position: absolute;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
top: 9px;
|
||||
left: 50%;
|
||||
transform: translate(-50%);
|
||||
width: fit-content;
|
||||
padding: 1px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1px 0;
|
||||
place-items: center;
|
||||
z-index: 30;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: white;
|
||||
text-shadow: 1px 1px 0px #3f3f3f;
|
||||
font-size: 10px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.playerlist-entry {
|
||||
overflow: hidden;
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
margin: 0px;
|
||||
line-height: calc(100% - 1px);
|
||||
text-shadow: 1px 1px 0px #3f3f3f;
|
||||
font-family: mojangles, minecraft, monospace;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.active-player {
|
||||
color: rgb(42, 204, 237);
|
||||
text-shadow: 1px 1px 0px rgb(4, 44, 67);
|
||||
}
|
||||
|
||||
.playerlist-ping {
|
||||
text-align: right;
|
||||
float: right;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.playerlist-ping-value {
|
||||
color: rgb(114, 255, 114);
|
||||
text-shadow: 1px 1px 0px rgb(28, 105, 28);
|
||||
float: left;
|
||||
margin: 0;
|
||||
margin-right: 1px;
|
||||
}
|
||||
|
||||
.playerlist-ping-label {
|
||||
text-shadow: 1px 1px 0px #3f3f3f;
|
||||
color: white;
|
||||
float: right;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.player-lists {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
place-items: center;
|
||||
place-content: center;
|
||||
gap: 0 4px;
|
||||
}
|
||||
|
||||
.player-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1px 0;
|
||||
min-width: 80px;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
static get properties () {
|
||||
return {
|
||||
serverIP: { type: String },
|
||||
clientId: { type: String },
|
||||
players: { type: Object }
|
||||
}
|
||||
}
|
||||
|
||||
constructor () {
|
||||
super()
|
||||
this.serverIP = ''
|
||||
this.clientId = ''
|
||||
this.players = {}
|
||||
}
|
||||
|
||||
init (bot, ip) {
|
||||
const playerList = this.shadowRoot.querySelector('#playerlist-container')
|
||||
|
||||
this.isOpen = false
|
||||
this.players = bot.players
|
||||
this.clientId = bot.player.uuid
|
||||
this.serverIP = ip
|
||||
|
||||
this.requestUpdate()
|
||||
|
||||
const showList = (shouldShow = true) => {
|
||||
playerList.style.display = shouldShow ? 'block' : 'none'
|
||||
this.isOpen = shouldShow
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', e => {
|
||||
e ??= window.event
|
||||
if (e.key === 'Tab') {
|
||||
showList(true)
|
||||
e.preventDefault()
|
||||
}
|
||||
})
|
||||
|
||||
document.addEventListener('keyup', e => {
|
||||
if (!this.isOpen) return
|
||||
e ??= window.event
|
||||
if (e.key === 'Tab') {
|
||||
showList(false)
|
||||
e.preventDefault()
|
||||
}
|
||||
})
|
||||
|
||||
bot.on('playerUpdated', () => this.requestUpdate()) // LitElement seems to be batching requests, so it should be fine?
|
||||
bot.on('playerJoined', () => this.requestUpdate())
|
||||
bot.on('playerLeft', () => this.requestUpdate())
|
||||
}
|
||||
|
||||
render () {
|
||||
const lists = []
|
||||
const players = Object.values(this.players).sort((a, b) => {
|
||||
if (a.username > b.username) return 1
|
||||
if (a.username < b.username) return -1
|
||||
return 0
|
||||
})
|
||||
|
||||
let tempList = []
|
||||
for (let i = 0; i < players.length; i++) {
|
||||
tempList.push(players[i])
|
||||
|
||||
if ((i + 1) / MAX_ROWS_PER_COL === 1 || i + 1 === players.length) {
|
||||
lists.push([...tempList])
|
||||
tempList = []
|
||||
}
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="playerlist-container" id="playerlist-container" style="display: none;">
|
||||
<span class="title">Server IP: ${this.serverIP}</span>
|
||||
<div class="player-lists">
|
||||
${lists.map(list => html`
|
||||
<div class="player-list">
|
||||
${list.map(player => html`
|
||||
<div class="playerlist-entry${this.clientId === player.uuid ? ' active-player' : ''}" id="plist-player-${player.uuid}">
|
||||
${player.username}
|
||||
<div class="playerlist-ping">
|
||||
<p class="playerlist-ping-value">${player.ping}</p>
|
||||
<p class="playerlist-ping-label">ms</p>
|
||||
</div>
|
||||
</div>
|
||||
`)}
|
||||
</div>
|
||||
`)}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('pmui-playerlist-overlay', PlayerListOverlay)
|
176
lib/menus/components/slider.js
Normal file
|
@ -0,0 +1,176 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
|
||||
class Slider extends LitElement {
|
||||
static get styles () {
|
||||
return css`
|
||||
.slider-container {
|
||||
--txrV: -46px;
|
||||
position: relative;
|
||||
width: 150px;
|
||||
height: 20px;
|
||||
font-family: minecraft, mojangles, monospace;
|
||||
font-size: 10px;
|
||||
color: white;
|
||||
text-shadow: 1px 1px #220;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.slider-thumb {
|
||||
--txrV: -66px;
|
||||
pointer-events: none;
|
||||
width: 8px;
|
||||
height: 20px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.slider-container:hover .slider-thumb {
|
||||
--txrV: -86px;
|
||||
}
|
||||
|
||||
.slider-container::after,
|
||||
.slider-thumb::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 50%;
|
||||
height: 20px;
|
||||
background: url('textures/1.17.1/gui/widgets.png');
|
||||
background-size: 256px;
|
||||
background-position-y: var(--txrV);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.slider-container::before,
|
||||
.slider-thumb::before {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
width: 50%;
|
||||
height: 20px;
|
||||
background: url('textures/1.17.1/gui/widgets.png');
|
||||
background-size: 256px;
|
||||
background-position-x: calc(-200px + 100%);
|
||||
background-position-y: var(--txrV);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.slider {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background: none;
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
position: relative;
|
||||
appearance: none;
|
||||
width: 8px;
|
||||
height: 20px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.slider::-moz-range-thumb {
|
||||
width: 8px;
|
||||
height: 20px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
label {
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 6;
|
||||
width: max-content;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
constructor () {
|
||||
super()
|
||||
this.label = ''
|
||||
this.type = '%'
|
||||
this.width = '150px'
|
||||
this.value = '50'
|
||||
this.min = '0'
|
||||
this.max = '100'
|
||||
this.ratio = (Number(this.value) - Number(this.min)) / (Number(this.max) - Number(this.min))
|
||||
}
|
||||
|
||||
updated () {
|
||||
this.ratio = (Number(this.value) - Number(this.min)) / (Number(this.max) - Number(this.min))
|
||||
}
|
||||
|
||||
static get properties () {
|
||||
return {
|
||||
label: {
|
||||
type: String,
|
||||
attribute: 'pmui-label'
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
attribute: 'pmui-type'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
attribute: 'pmui-width'
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
attribute: 'pmui-value'
|
||||
},
|
||||
min: {
|
||||
type: String,
|
||||
attribute: 'pmui-min'
|
||||
},
|
||||
max: {
|
||||
type: String,
|
||||
attribute: 'pmui-max'
|
||||
},
|
||||
ratio: { type: Number }
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`
|
||||
<div
|
||||
class="slider-container"
|
||||
style="width: ${this.width};"
|
||||
>
|
||||
<input
|
||||
type="range"
|
||||
class="slider"
|
||||
min="${this.min}"
|
||||
max="${this.max}"
|
||||
value="${this.value}"
|
||||
@input=${(e) => {
|
||||
const range = e.target
|
||||
this.ratio = (range.value - range.min) / (range.max - range.min)
|
||||
this.value = range.value
|
||||
}}>
|
||||
<div
|
||||
class="slider-thumb"
|
||||
style="left: calc((100% * ${this.ratio}) - (8px * ${this.ratio}));"
|
||||
></div>
|
||||
<label>${this.label}: ${this.value}${this.type}</label>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('pmui-slider', Slider)
|
148
lib/menus/hud.js
Normal file
|
@ -0,0 +1,148 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
|
||||
class Hud extends LitElement {
|
||||
static get styles () {
|
||||
return css`
|
||||
:host {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: -2;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.crosshair {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: url('textures/1.17.1/gui/icons.png');
|
||||
background-size: 256px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
#xp-label {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
left: 50%;
|
||||
transform: translate(-50%);
|
||||
font-size: 10px;
|
||||
font-family: minecraft, mojangles, monospace;
|
||||
color: rgb(30, 250, 30);
|
||||
text-shadow: 0px -1px #000, 0px 1px #000, 1px 0px #000, -1px 0px #000;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
#xp-bar-bg {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 24px;
|
||||
transform: translate(-50%);
|
||||
width: 182px;
|
||||
height: 5px;
|
||||
background-image: url('textures/1.16.4/gui/icons.png');
|
||||
background-size: 256px;
|
||||
background-position-y: -64px;
|
||||
}
|
||||
|
||||
.xp-bar {
|
||||
width: 182px;
|
||||
height: 5px;
|
||||
background-image: url('textures/1.17.1/gui/icons.png');
|
||||
background-size: 256px;
|
||||
background-position-y: -69px;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {globalThis.THREE.Renderer} renderer
|
||||
* @param {import('mineflayer').Bot} bot
|
||||
* @param {string} host
|
||||
*/
|
||||
init (renderer, bot, host) {
|
||||
const debugMenu = this.shadowRoot.querySelector('#debug-overlay')
|
||||
const playerList = this.shadowRoot.querySelector('#playerlist-overlay')
|
||||
const healthbar = this.shadowRoot.querySelector('#health-bar')
|
||||
const foodbar = this.shadowRoot.querySelector('#food-bar')
|
||||
// const breathbar = this.shadowRoot.querySelector('#breath-bar')
|
||||
const chat = this.shadowRoot.querySelector('#chat')
|
||||
const hotbar = this.shadowRoot.querySelector('#hotbar')
|
||||
const xpLabel = this.shadowRoot.querySelector('#xp-label')
|
||||
|
||||
hotbar.bot = bot
|
||||
debugMenu.bot = bot
|
||||
|
||||
chat.init(bot._client, renderer)
|
||||
playerList.init(bot, host)
|
||||
|
||||
bot.on('entityHurt', (entity) => {
|
||||
if (entity !== bot.entity) return
|
||||
healthbar.onDamage()
|
||||
})
|
||||
|
||||
bot.on('entityEffect', (entity, effect) => {
|
||||
if (entity !== bot.entity) return
|
||||
healthbar.effectAdded(effect)
|
||||
})
|
||||
|
||||
bot.on('entityEffectEnd', (entity, effect) => {
|
||||
if (entity !== bot.entity) return
|
||||
healthbar.effectEnded(effect)
|
||||
})
|
||||
|
||||
bot.on('game', () => {
|
||||
healthbar.gameModeChanged(bot.player.gamemode, bot.game.hardcore)
|
||||
foodbar.gameModeChanged(bot.player.gamemode)
|
||||
// breathbar.gameModeChanged(bot.player.gamemode)
|
||||
this.shadowRoot.querySelector('#xp-bar-bg').style.display = bot.player.gamemode === 1 ? 'none' : 'block'
|
||||
})
|
||||
|
||||
bot.on('health', () => {
|
||||
healthbar.updateHealth(bot.health, true)
|
||||
foodbar.updateHunger(bot.food, true)
|
||||
})
|
||||
|
||||
bot.on('experience', () => {
|
||||
this.shadowRoot.querySelector('#xp-bar-bg').firstElementChild.style.width = `${182 * bot.experience.progress}px`
|
||||
xpLabel.innerHTML = bot.experience.level
|
||||
xpLabel.style.display = bot.experience.level > 0 ? 'block' : 'none'
|
||||
})
|
||||
|
||||
// bot.on('breath', () => {
|
||||
// breathbar.updateOxygen(bot.oxygenLevel)
|
||||
// })
|
||||
|
||||
this.shadowRoot.querySelector('#xp-bar-bg').style.display = bot.player.gamemode === 1 ? 'none' : 'block'
|
||||
this.shadowRoot.querySelector('#xp-bar-bg').firstElementChild.style.width = `${182 * bot.experience.progress}px`
|
||||
xpLabel.innerHTML = bot.experience.level
|
||||
xpLabel.style.display = bot.experience.level > 0 ? 'block' : 'none'
|
||||
healthbar.gameModeChanged(bot.player.gamemode, bot.game.hardcore)
|
||||
healthbar.updateHealth(bot.health)
|
||||
foodbar.updateHunger(bot.food)
|
||||
// breathbar.updateOxygen(bot.oxygenLevel ?? 20)
|
||||
hotbar.init()
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`
|
||||
<pmui-debug-overlay id="debug-overlay"></pmui-debug-overlay>
|
||||
<pmui-playerlist-overlay id="playerlist-overlay"></pmui-playerlist-overlay>
|
||||
<div class="crosshair"></div>
|
||||
<chat-box id="chat"></chat-box>
|
||||
<!--<pmui-breathbar id="breath-bar"></pmui-breathbar>-->
|
||||
<pmui-healthbar id="health-bar"></pmui-healthbar>
|
||||
<pmui-foodbar id="food-bar"></pmui-foodbar>
|
||||
<div id="xp-bar-bg">
|
||||
<div class="xp-bar"></div>
|
||||
<span id="xp-label"></span>
|
||||
</div>
|
||||
<pmui-hotbar id="hotbar"></pmui-hotbar>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('pmui-hud', Hud)
|
172
lib/menus/keybinds_screen.js
Normal file
|
@ -0,0 +1,172 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
const { commonCss, displayScreen } = require('./components/common')
|
||||
|
||||
class KeyBindsScreen extends LitElement {
|
||||
static get styles () {
|
||||
return css`
|
||||
${commonCss}
|
||||
.title {
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
left: 50%;
|
||||
transform: translate(-50%);
|
||||
width: 100%;
|
||||
height: calc(100% - 64px);
|
||||
place-items: center;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
box-shadow: inset 0 3px 6px rgba(0, 0, 0, 0.7), inset 0 -3px 6px rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
.keymap-list {
|
||||
width: 288px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 4px 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.keymap-list::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.keymap-list::-webkit-scrollbar-track {
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.keymap-list::-webkit-scrollbar-thumb {
|
||||
background: #ccc;
|
||||
box-shadow: inset -1px -1px 0 #4f4f4f;
|
||||
}
|
||||
|
||||
.keymap-entry {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
place-content: center;
|
||||
place-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
span {
|
||||
color: white;
|
||||
text-shadow: 1px 1px 0 rgb(63, 63, 63);
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.keymap-entry-btns {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.bottom-btns {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 310px;
|
||||
height: 20px;
|
||||
justify-content: space-between;
|
||||
position: absolute;
|
||||
bottom: 9px;
|
||||
left: 50%;
|
||||
transform: translate(-50%);
|
||||
}
|
||||
|
||||
`
|
||||
}
|
||||
|
||||
static get properties () {
|
||||
return {
|
||||
keymaps: { type: Object },
|
||||
selected: { type: Number }
|
||||
}
|
||||
}
|
||||
|
||||
constructor () {
|
||||
super()
|
||||
this.selected = -1
|
||||
this.keymaps = [
|
||||
{ defaultKey: 'KeyW', key: 'KeyW', name: 'Walk Forwards' },
|
||||
{ defaultKey: 'KeyS', key: 'KeyS', name: 'Walk Backwards' },
|
||||
{ defaultKey: 'KeyA', key: 'KeyA', name: 'Strafe Left' },
|
||||
{ defaultKey: 'KeyD', key: 'KeyD', name: 'Strafe Right' },
|
||||
{ defaultKey: 'Space', key: 'Space', name: 'Jump' },
|
||||
{ defaultKey: 'ShiftLeft', key: 'ShiftLeft', name: 'Sneak' },
|
||||
{ defaultKey: 'ControlLeft', key: 'ControlLeft', name: 'Sprint' },
|
||||
{ defaultKey: 'KeyT', key: 'KeyT', name: 'Open Chat' },
|
||||
{ defaultKey: 'Slash', key: 'Slash', name: 'Open Command' },
|
||||
// { defaultKey: '0', key: '0', name: 'Attack/Destroy' },
|
||||
// { defaultKey: '1', key: '1', name: 'Place Block' },
|
||||
{ defaultKey: 'KeyQ', key: 'KeyQ', name: 'Drop Item' }
|
||||
// { defaultKey: 'Digit1', key: 'Digit1', name: 'Hotbar Slot 1' },
|
||||
// { defaultKey: 'Digit2', key: 'Digit2', name: 'Hotbar Slot 2' },
|
||||
// { defaultKey: 'Digit3', key: 'Digit3', name: 'Hotbar Slot 3' },
|
||||
// { defaultKey: 'Digit4', key: 'Digit4', name: 'Hotbar Slot 4' },
|
||||
// { defaultKey: 'Digit5', key: 'Digit5', name: 'Hotbar Slot 5' },
|
||||
// { defaultKey: 'Digit6', key: 'Digit6', name: 'Hotbar Slot 6' },
|
||||
// { defaultKey: 'Digit7', key: 'Digit7', name: 'Hotbar Slot 7' },
|
||||
// { defaultKey: 'Digit8', key: 'Digit8', name: 'Hotbar Slot 8' },
|
||||
// { defaultKey: 'Digit9', key: 'Digit9', name: 'Hotbar Slot 9' },
|
||||
// { defaultKey: 'KeyE', key: 'KeyE', name: 'Open Inventory' }
|
||||
]
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (this.selected !== -1) {
|
||||
this.keymaps[this.selected].key = e.code
|
||||
this.selected = -1
|
||||
this.requestUpdate()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`
|
||||
<div class="dirt-bg"></div>
|
||||
|
||||
<p class="title">Key Binds</p>
|
||||
|
||||
<main>
|
||||
<div class="keymap-list">
|
||||
${this.keymaps.map((m, i) => html`
|
||||
<div class="keymap-entry">
|
||||
<span>${m.name}</span>
|
||||
|
||||
<div class="keymap-entry-btns">
|
||||
<pmui-button pmui-width="72px" pmui-label="${this.selected === i ? `> ${m.key} <` : m.key}" @pmui-click=${e => {
|
||||
e.target.setAttribute('pmui-label', `> ${m.key} <`)
|
||||
this.selected = i
|
||||
this.requestUpdate()
|
||||
}}></pmui-button>
|
||||
<pmui-button pmui-width="50px" ?pmui-disabled=${m.key === m.defaultKey} pmui-label="Reset" @pmui-click=${() => {
|
||||
this.keymaps[i].key = this.keymaps[i].defaultKey
|
||||
this.requestUpdate()
|
||||
this.selected = -1
|
||||
}}></pmui-button>
|
||||
</div>
|
||||
</div>
|
||||
`)}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div class="bottom-btns">
|
||||
<pmui-button pmui-width="150px" pmui-label="Reset All Keys" ?pmui-disabled=${!this.keymaps.some(v => v.key !== v.defaultKey)} @pmui-click=${this.onResetAllPress}></pmui-button>
|
||||
<pmui-button pmui-width="150px" pmui-label="Done" @pmui-click=${() => displayScreen(this, document.getElementById('options-screen'))}></pmui-button>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
onResetAllPress () {
|
||||
for (let i = 0; i < this.keymaps.length; i++) {
|
||||
this.keymaps[i].key = this.keymaps[i].defaultKey
|
||||
}
|
||||
this.requestUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('pmui-keybindsscreen', KeyBindsScreen)
|
67
lib/menus/loading_screen.js
Normal file
|
@ -0,0 +1,67 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
const { commonCss } = require('./components/common')
|
||||
|
||||
class LoadingScreen extends LitElement {
|
||||
static get styles () {
|
||||
return css`
|
||||
${commonCss}
|
||||
.title {
|
||||
top: 30px;
|
||||
}
|
||||
|
||||
#cancel-btn {
|
||||
position: absolute;
|
||||
top: calc(20% + 50px);
|
||||
left: 50%;
|
||||
transform: translate(-50%);
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
static get properties () {
|
||||
return {
|
||||
status: { type: String },
|
||||
loadingText: { type: String },
|
||||
hasError: { type: Number }
|
||||
}
|
||||
}
|
||||
|
||||
constructor () {
|
||||
super()
|
||||
this.hasError = false
|
||||
this.status = 'Waiting for JS load'
|
||||
}
|
||||
|
||||
firstUpdated () {
|
||||
this.statusRunner()
|
||||
}
|
||||
|
||||
async statusRunner () {
|
||||
const array = ['.', '..', '...', '']
|
||||
const timer = ms => new Promise((resolve) => setTimeout(resolve, ms))
|
||||
|
||||
const load = async () => {
|
||||
for (let i = 0; true; i = ((i + 1) % array.length)) {
|
||||
this.loadingText = this.status + array[i]
|
||||
await timer(500)
|
||||
}
|
||||
}
|
||||
|
||||
load()
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`
|
||||
<div class="dirt-bg"></div>
|
||||
|
||||
<p class="title">${this.hasError ? this.status : this.loadingText}</p>
|
||||
|
||||
${this.hasError
|
||||
? html`<pmui-button id="cancel-btn" pmui-width="200px" pmui-label="Cancel" @pmui-click=${() => window.location.reload()}></pmui-button>`
|
||||
: ''
|
||||
}
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('pmui-loadingscreen', LoadingScreen)
|
144
lib/menus/options_screen.js
Normal file
|
@ -0,0 +1,144 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
const { commonCss, displayScreen } = require('./components/common')
|
||||
|
||||
class OptionsScreen extends LitElement {
|
||||
static get styles () {
|
||||
return css`
|
||||
${commonCss}
|
||||
.title {
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
top: calc(100% / 6 - 6px);
|
||||
left: 50%;
|
||||
width: 310px;
|
||||
gap: 4px 0;
|
||||
place-items: center;
|
||||
place-content: center;
|
||||
transform: translate(-50%);
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
gap: 0 10px;
|
||||
height: 20px;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
static get properties () {
|
||||
return {
|
||||
isInsideWorld: { type: Boolean },
|
||||
mouseSensitivityX: { type: Number },
|
||||
mouseSensitivityY: { type: Number },
|
||||
chatWidth: { type: Number },
|
||||
chatHeight: { type: Number },
|
||||
chatScale: { type: Number },
|
||||
sound: { type: Number },
|
||||
renderDistance: { type: Number },
|
||||
fov: { type: Number },
|
||||
guiScale: { type: Number }
|
||||
}
|
||||
}
|
||||
|
||||
constructor () {
|
||||
super()
|
||||
this.isInsideWorld = false
|
||||
|
||||
const getValue = (item, defaultValue, convertFn) => window.localStorage.getItem(item) ? convertFn(window.localStorage.getItem(item)) : defaultValue
|
||||
|
||||
this.mouseSensitivityX = getValue('mouseSensX', 50, (v) => Math.floor(Number(v) * 10000))
|
||||
this.mouseSensitivityY = getValue('mouseSensY', 50, (v) => Math.floor(Number(v) * 10000))
|
||||
this.chatWidth = getValue('chatWidth', 320, (v) => Number(v))
|
||||
this.chatHeight = getValue('chatHeight', 180, (v) => Number(v))
|
||||
this.chatScale = getValue('chatScale', 100, (v) => Number(v))
|
||||
this.sound = getValue('sound', 50, (v) => Number(v))
|
||||
this.renderDistance = getValue('renderDistance', 6, (v) => Number(v))
|
||||
this.fov = getValue('fov', 75, (v) => Number(v))
|
||||
this.guiScale = getValue('guiScale', 3, (v) => Number(v))
|
||||
|
||||
document.documentElement.style.setProperty('--chatScale', `${this.chatScale}`)
|
||||
document.documentElement.style.setProperty('--chatWidth', `${this.chatWidth}px`)
|
||||
document.documentElement.style.setProperty('--chatHeight', `${this.chatHeight}px`)
|
||||
document.documentElement.style.setProperty('--guiScale', `${this.guiScale}`)
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`
|
||||
<div class="${this.isInsideWorld ? 'bg' : 'dirt-bg'}"></div>
|
||||
|
||||
<p class="title">Options</p>
|
||||
|
||||
<main>
|
||||
<div class="wrapper">
|
||||
<pmui-slider pmui-label="Mouse Sensitivity X" pmui-value="${this.mouseSensitivityX}" pmui-min="1" pmui-max="100" @input=${(e) => {
|
||||
this.mouseSensitivityX = Number(e.target.value)
|
||||
window.localStorage.setItem('mouseSensX', this.mouseSensitivityX * 0.0001)
|
||||
}}></pmui-slider>
|
||||
<pmui-slider pmui-label="Mouse Sensitivity Y" pmui-value="${this.mouseSensitivityY}" pmui-min="1" pmui-max="100" @input=${(e) => {
|
||||
this.mouseSensitivityY = Number(e.target.value)
|
||||
window.localStorage.setItem('mouseSensY', this.mouseSensitivityY * 0.0001)
|
||||
}}></pmui-slider>
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<pmui-slider pmui-label="Chat Width" pmui-value="${this.chatWidth}" pmui-min="0" pmui-max="320" pmui-type="px" @input=${(e) => {
|
||||
this.chatWidth = Number(e.target.value)
|
||||
window.localStorage.setItem('chatWidth', `${this.chatWidth}`)
|
||||
document.documentElement.style.setProperty('--chatWidth', `${this.chatWidth}px`)
|
||||
}}></pmui-slider>
|
||||
<pmui-slider pmui-label="Chat Height" pmui-value="${this.chatHeight}" pmui-min="0" pmui-max="180" pmui-type="px" @input=${(e) => {
|
||||
this.chatHeight = Number(e.target.value)
|
||||
window.localStorage.setItem('chatHeight', `${this.chatHeight}`)
|
||||
document.documentElement.style.setProperty('--chatHeight', `${this.chatHeight}px`)
|
||||
}}></pmui-slider>
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<pmui-slider pmui-label="Chat Scale" pmui-value="${this.chatScale}" pmui-min="0" pmui-max="100" @input=${(e) => {
|
||||
this.chatScale = Number(e.target.value)
|
||||
window.localStorage.setItem('chatScale', `${this.chatScale}`)
|
||||
document.documentElement.style.setProperty('--chatScale', `${this.chatScale}`)
|
||||
}}></pmui-slider>
|
||||
<pmui-slider pmui-label="Sound Volume" pmui-value="${this.sound}" pmui-min="0" pmui-max="100" @input=${(e) => {
|
||||
this.sound = Number(e.target.value)
|
||||
window.localStorage.setItem('sound', `${this.sound}`)
|
||||
}}></pmui-slider>
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<pmui-button pmui-width="150px" pmui-label="Key Binds" @pmui-click=${() => displayScreen(this, document.getElementById('keybinds-screen'))}></pmui-button>
|
||||
<pmui-slider pmui-label="Gui Scale" pmui-value="${this.guiScale}" pmui-min="1" pmui-max="4" pmui-type="" @input=${(e) => {
|
||||
this.guiScale = Number(e.target.value)
|
||||
window.localStorage.setItem('guiScale', `${this.guiScale}`)
|
||||
document.documentElement.style.setProperty('--guiScale', `${this.guiScale}`)
|
||||
}}></pmui-slider>
|
||||
</div>
|
||||
${this.isInsideWorld
|
||||
? ''
|
||||
: html`
|
||||
<div class="wrapper">
|
||||
<pmui-slider pmui-label="Render Distance" pmui-value="${this.renderDistance}" pmui-min="2" pmui-max="6" pmui-type=" chunks" @input=${(e) => {
|
||||
this.renderDistance = Number(e.target.value)
|
||||
window.localStorage.setItem('renderDistance', `${this.renderDistance}`)
|
||||
}}></pmui-slider>
|
||||
<pmui-slider pmui-label="Field of View" pmui-value="${this.fov}" pmui-min="30" pmui-max="110" pmui-type="" @input=${(e) => {
|
||||
this.fov = Number(e.target.value)
|
||||
window.localStorage.setItem('fov', `${this.fov}`)
|
||||
|
||||
this.dispatchEvent(new window.CustomEvent('fov_changed', { fov: this.fov }))
|
||||
}}></pmui-slider>
|
||||
</div>
|
||||
`}
|
||||
<br>
|
||||
|
||||
<pmui-button pmui-width="200px" pmui-label="Done" @pmui-click=${() => displayScreen(this, document.getElementById(this.isInsideWorld ? 'pause-screen' : 'title-screen'))}></pmui-button>
|
||||
</main>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('pmui-optionsscreen', OptionsScreen)
|
110
lib/menus/pause_screen.js
Normal file
|
@ -0,0 +1,110 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
const { openURL, displayScreen } = require('./components/common')
|
||||
|
||||
class PauseScreen extends LitElement {
|
||||
static get styles () {
|
||||
return css`
|
||||
.bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: rgba(0, 0, 0, 0.75);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.title {
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: 50%;
|
||||
transform: translate(-50%);
|
||||
font-size: 10px;
|
||||
color: white;
|
||||
text-shadow: 1px 1px #222;
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px 0;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
width: 204px;
|
||||
top: calc(25% + 48px - 16px);
|
||||
transform: translate(-50%);
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
constructor () {
|
||||
super()
|
||||
this.inMenu = false
|
||||
}
|
||||
|
||||
init (renderer) {
|
||||
const chat = document.getElementById('hud').shadowRoot.querySelector('#chat')
|
||||
const self = this
|
||||
|
||||
document.addEventListener('keydown', e => {
|
||||
if (chat.inChat) return
|
||||
e = e || window.event
|
||||
if (e.keyCode === 27 || e.key === 'Escape' || e.key === 'Esc') {
|
||||
if (self.inMenu) {
|
||||
self.disableGameMenu(renderer)
|
||||
} else {
|
||||
self.enableGameMenu()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`
|
||||
<div class="bg"></div>
|
||||
|
||||
<p class="title">Game Menu</p>
|
||||
|
||||
<main>
|
||||
<pmui-button pmui-width="204px" pmui-label="Back to Game" @pmui-click=${this.onReturnPress}></pmui-button>
|
||||
<div class="row">
|
||||
<pmui-button pmui-width="98px" pmui-label="Github" @pmui-click=${() => openURL('https://github.com/PrismarineJS/prismarine-web-client')}></pmui-button>
|
||||
<pmui-button pmui-width="98px" pmui-label="Discord" @pmui-click=${() => openURL('https://discord.gg/4Ucm684Fq3')}></pmui-button>
|
||||
</div>
|
||||
<pmui-button pmui-width="204px" pmui-label="Options" @pmui-click=${() => displayScreen(this, document.getElementById('options-screen'))}></pmui-button>
|
||||
<pmui-button pmui-width="204px" pmui-label="Disconnect" @pmui-click=${() => window.location.reload()}></pmui-button>
|
||||
</main>
|
||||
`
|
||||
}
|
||||
|
||||
disableGameMenu (renderer = false) {
|
||||
this.inMenu = false
|
||||
this.style.display = 'none'
|
||||
if (renderer) {
|
||||
renderer.domElement.requestPointerLock()
|
||||
}
|
||||
}
|
||||
|
||||
enableGameMenu () {
|
||||
this.inMenu = true
|
||||
document.exitPointerLock()
|
||||
this.style.display = 'block'
|
||||
this.focus()
|
||||
}
|
||||
|
||||
onReturnPress (renderer = false) {
|
||||
this.inMenu = false
|
||||
this.style.display = 'none'
|
||||
if (renderer) {
|
||||
renderer.domElement.requestPointerLock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('pmui-pausescreen', PauseScreen)
|
142
lib/menus/play_screen.js
Normal file
|
@ -0,0 +1,142 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
const { commonCss, displayScreen } = require('./components/common')
|
||||
|
||||
class PlayScreen extends LitElement {
|
||||
static get styles () {
|
||||
return css`
|
||||
${commonCss}
|
||||
.title {
|
||||
top: 12px;
|
||||
}
|
||||
|
||||
.edit-boxes {
|
||||
position: absolute;
|
||||
top: 59px;
|
||||
left: 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px 0;
|
||||
transform: translate(-50%);
|
||||
width: 310px;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0 4px;
|
||||
}
|
||||
|
||||
.button-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0 4px;
|
||||
position: absolute;
|
||||
bottom: 9px;
|
||||
left: 50%;
|
||||
transform: translate(-50%);
|
||||
width: 310px;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
static get properties () {
|
||||
return {
|
||||
server: { type: String },
|
||||
serverport: { type: Number },
|
||||
proxy: { type: String },
|
||||
proxyport: { type: Number },
|
||||
username: { type: String },
|
||||
password: { type: String }
|
||||
}
|
||||
}
|
||||
|
||||
constructor () {
|
||||
super()
|
||||
this.server = ''
|
||||
this.serverport = 25565
|
||||
this.proxy = ''
|
||||
this.proxyport = ''
|
||||
this.username = window.localStorage.getItem('username') ?? 'pviewer' + (Math.floor(Math.random() * 1000))
|
||||
this.password = ''
|
||||
|
||||
window.fetch('config.json').then(res => res.json()).then(config => {
|
||||
this.server = config.defaultHost
|
||||
this.serverport = config.defaultHostPort ?? 25565
|
||||
this.proxy = config.defaultProxy
|
||||
this.proxyport = !config.defaultProxy && !config.defaultProxyPort ? '' : config.defaultProxyPort ?? 443
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`
|
||||
<div class="dirt-bg"></div>
|
||||
|
||||
<p class="title">Join a Server</p>
|
||||
|
||||
<main class="edit-boxes">
|
||||
<div class="wrapper">
|
||||
<pmui-editbox
|
||||
pmui-width="150px"
|
||||
pmui-label="Server IP"
|
||||
pmui-id="serverip"
|
||||
pmui-value="${this.server}"
|
||||
@input=${e => { this.server = e.target.value }}
|
||||
></pmui-editbox>
|
||||
<pmui-editbox
|
||||
pmui-width="150px"
|
||||
pmui-label="Server Port"
|
||||
pmui-id="port"
|
||||
pmui-value="${this.serverport}"
|
||||
@input=${e => { this.serverport = e.target.value }}
|
||||
></pmui-editbox>
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<pmui-editbox
|
||||
pmui-width="150px"
|
||||
pmui-label="Proxy"
|
||||
pmui-id="proxy"
|
||||
pmui-value="${this.proxy}"
|
||||
@input=${e => { this.proxy = e.target.value }}
|
||||
></pmui-editbox>
|
||||
<pmui-editbox
|
||||
pmui-width="150px"
|
||||
pmui-label="Port"
|
||||
pmui-id="port"
|
||||
pmui-value="${this.proxyport}"
|
||||
@input=${e => { this.proxyport = e.target.value }}
|
||||
></pmui-editbox>
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<pmui-editbox
|
||||
pmui-width="150px"
|
||||
pmui-label="Username"
|
||||
pmui-id="username"
|
||||
pmui-value="${this.username}"
|
||||
@input=${e => { this.username = e.target.value }}
|
||||
></pmui-editbox>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div class="button-wrapper">
|
||||
<pmui-button pmui-width="150px" pmui-label="Connect" @pmui-click=${this.onConnectPress}></pmui-button>
|
||||
<pmui-button pmui-width="150px" pmui-label="Cancel" @pmui-click=${() => displayScreen(this, document.getElementById('title-screen'))}></pmui-button>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
onConnectPress () {
|
||||
window.localStorage.setItem('username', this.username)
|
||||
|
||||
this.dispatchEvent(new window.CustomEvent('connect', {
|
||||
detail: {
|
||||
server: `${this.server}:${this.serverport}`,
|
||||
proxy: `${this.proxy}${this.proxy !== '' ? `:${this.proxyport}` : ''}`,
|
||||
username: this.username,
|
||||
password: this.password
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('pmui-playscreen', PlayScreen)
|
124
lib/menus/title_screen.js
Normal file
|
@ -0,0 +1,124 @@
|
|||
const { openURL, displayScreen } = require('./components/common')
|
||||
const { LitElement, html, css } = require('lit')
|
||||
|
||||
class TitleScreen extends LitElement {
|
||||
static get styles () {
|
||||
return css`
|
||||
.minecraft {
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
left: calc(50% - 137px);
|
||||
}
|
||||
|
||||
.minecraft .minec {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-image: url('textures/1.17.1/gui/title/minecraft.png');
|
||||
background-size: 256px;
|
||||
width: 155px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.minecraft .raft {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 155px;
|
||||
background-image: url('textures/1.17.1/gui/title/minecraft.png');
|
||||
background-size: 256px;
|
||||
width: 155px;
|
||||
height: 44px;
|
||||
background-position-y: -45px;
|
||||
}
|
||||
|
||||
.minecraft .edition {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 37px;
|
||||
left: calc(88px + 5px);
|
||||
background-image: url('extra-textures/edition.png');
|
||||
background-size: 128px;
|
||||
width: 88px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.splash {
|
||||
position: absolute;
|
||||
top: 32px;
|
||||
left: 227px;
|
||||
color: #ff0;
|
||||
transform: translate(-50%, -50%) rotateZ(-20deg) scale(1);
|
||||
width: max-content;
|
||||
text-shadow: 1px 1px #220;
|
||||
font-size: 10px;
|
||||
animation: splashAnim 400ms infinite alternate linear;
|
||||
}
|
||||
|
||||
@keyframes splashAnim {
|
||||
to {
|
||||
transform: translate(-50%, -50%) rotateZ(-20deg) scale(1.07);
|
||||
}
|
||||
}
|
||||
|
||||
.menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px 0;
|
||||
position: absolute;
|
||||
top: calc(25% + 48px);
|
||||
left: 50%;
|
||||
width: 200px;
|
||||
transform: translate(-50%);
|
||||
}
|
||||
|
||||
.menu-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0 4px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bottom-info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
position: absolute;
|
||||
bottom: -1px;
|
||||
left: 1px;
|
||||
width: calc(100% - 2px);
|
||||
color: white;
|
||||
text-shadow: 1px 1px #222;
|
||||
font-size: 10px;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`
|
||||
<div class="minecraft">
|
||||
<div class="minec"></div>
|
||||
<div class="raft"></div>
|
||||
<div class="edition"></div>
|
||||
<span class="splash">Prismarine is a beautiful block</span>
|
||||
</div>
|
||||
|
||||
<div class="menu">
|
||||
<pmui-button pmui-width="200px" pmui-label="Play" @pmui-click=${() => displayScreen(this, document.getElementById('play-screen'))}></pmui-button>
|
||||
<pmui-button pmui-width="200px" pmui-label="Options" @pmui-click=${() => displayScreen(this, document.getElementById('options-screen'))}></pmui-button>
|
||||
<div class="menu-row">
|
||||
<pmui-button pmui-width="98px" pmui-label="Github" @pmui-click=${() => openURL('https://github.com/PrismarineJS/prismarine-web-client')}></pmui-button>
|
||||
<pmui-button pmui-width="98px" pmui-label="Discord" @pmui-click=${() => openURL('https://discord.gg/4Ucm684Fq3')}></pmui-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bottom-info">
|
||||
<span>Prismarine Web Client</span>
|
||||
<span>A minecraft client in the browser!</span>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('pmui-titlescreen', TitleScreen)
|
|
@ -1,118 +0,0 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
|
||||
class PlayerList extends LitElement {
|
||||
static get styles () {
|
||||
return css`
|
||||
.playerlist-container {
|
||||
position: absolute;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
left: 50%;
|
||||
top: 1%;
|
||||
transform: translateX(-50%);
|
||||
border: 2px solid rgba(0, 0, 0, 0.8);
|
||||
padding: 5px;
|
||||
min-width: 8%;
|
||||
}
|
||||
|
||||
.playerlist-entry {
|
||||
overflow: hidden;
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
margin: 0px;
|
||||
line-height: 100%;
|
||||
text-shadow: 2px 2px 0px #3f3f3f;
|
||||
font-family: mojangles, minecraft, monospace;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.plist-active-player {
|
||||
color: rgb(42, 204, 237);
|
||||
}
|
||||
|
||||
.plist-ping-container {
|
||||
text-align: right;
|
||||
float: right;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.plist-ping-value {
|
||||
color: rgb(114, 255, 114);
|
||||
float: left;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.plist-ping-label {
|
||||
color: white;
|
||||
float: right;
|
||||
margin: 0px;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
static get properties () {
|
||||
return {
|
||||
clientId: { type: String },
|
||||
players: { type: Object }
|
||||
}
|
||||
}
|
||||
|
||||
constructor () {
|
||||
super()
|
||||
this.clientId = ''
|
||||
this.players = {}
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`
|
||||
<div id="playerlist-container" class="playerlist-container" style="display: none;">
|
||||
${Object.values(this.players).map(player => html`
|
||||
<div class="playerlist-entry${this.clientId === player.uuid ? ' plist-active-player' : ''}" id="plist-player-${player.uuid}">
|
||||
${player.username}
|
||||
<div class="plist-ping-container">
|
||||
<p class="plist-ping-value">${player.ping}</p>
|
||||
<p class="plist-ping-label">ms</p>
|
||||
</div>
|
||||
</div>
|
||||
`)}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
init (bot) {
|
||||
const playerList = this.shadowRoot.querySelector('#playerlist-container')
|
||||
|
||||
this.isOpen = false
|
||||
this.players = bot.players
|
||||
this.clientId = bot.player.uuid
|
||||
|
||||
this.requestUpdate()
|
||||
|
||||
const showList = (shouldShow = true) => {
|
||||
playerList.style.display = shouldShow ? 'block' : 'none'
|
||||
this.isOpen = shouldShow
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', e => {
|
||||
e ??= window.event
|
||||
if (e.key === 'Tab') {
|
||||
showList(true)
|
||||
e.preventDefault()
|
||||
}
|
||||
})
|
||||
|
||||
document.addEventListener('keyup', e => {
|
||||
if (!this.isOpen) return
|
||||
e ??= window.event
|
||||
if (e.key === 'Tab') {
|
||||
showList(false)
|
||||
e.preventDefault()
|
||||
}
|
||||
})
|
||||
|
||||
bot.on('playerUpdated', () => this.requestUpdate()) // LitElement seems to be batching requests, so it should be fine?
|
||||
bot.on('playerJoined', () => this.requestUpdate())
|
||||
bot.on('playerLeft', () => this.requestUpdate())
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('player-list', PlayerList)
|
|
@ -6,7 +6,7 @@
|
|||
"scripts": {
|
||||
"build": "webpack --config webpack.prod.js",
|
||||
"build-dev": "webpack --config webpack.dev.js",
|
||||
"start": "node server.js 8080 dev",
|
||||
"start": "node --max-old-space-size=8192 server.js 8080 dev",
|
||||
"prod-start": "node server.js",
|
||||
"build-dev-start": "npm run build-dev && npm run prod-start",
|
||||
"build-start": "npm run build && npm run prod-start",
|
||||
|
|
52
styles.css
|
@ -1,12 +1,36 @@
|
|||
:root {
|
||||
--guiScaleFactor: 3;
|
||||
--guiScale: 3;
|
||||
--chatWidth: 320px;
|
||||
--chatHeight: 180px;
|
||||
--chatScale: 1;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dirt-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: url('textures/1.17.1/gui/options_background.png'), rgba(0, 0, 0, 0.7);
|
||||
background-size: 16px;
|
||||
background-repeat: repeat;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transform-origin: top left;
|
||||
transform: scale(2);
|
||||
background-blend-mode: overlay;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: minecraft;
|
||||
src: url(minecraftia.woff);
|
||||
|
@ -16,13 +40,16 @@ html {
|
|||
font-family: mojangles;
|
||||
src: url(mojangles.ttf);
|
||||
}
|
||||
|
||||
body {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
margin:0;
|
||||
padding:0;
|
||||
height: 100vh;
|
||||
font-family: sans-serif;
|
||||
background: linear-gradient(#141e30, #243b55);
|
||||
background: #333;
|
||||
/* background: linear-gradient(#141e30, #243b55); */
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
|
@ -32,9 +59,30 @@ body {
|
|||
}
|
||||
|
||||
canvas {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
font-size: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#ui-root {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
transform-origin: top left;
|
||||
transform: scale(var(--guiScale));
|
||||
width: calc(100% / var(--guiScale));
|
||||
height: calc(100% / var(--guiScale));
|
||||
z-index: 10;
|
||||
image-rendering: optimizeSpeed;
|
||||
image-rendering: -moz-crisp-edges;
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
image-rendering: -o-crisp-edges;
|
||||
image-rendering: pixelated;
|
||||
-ms-interpolation-mode: nearest-neighbor;
|
||||
font-family: minecraft, mojangles, monospace;
|
||||
}
|