Mobile controls (#262)

* added readme pt-pt

* added url/querystring deps and fix chat pos/scale

url and querystring were missing in node_modules.
chat scale option wasn't implemented and chat input was on top instead of bottom.

* added bot version text field and guiScale for small screens

text field to choose bot version. gui scale changes on small screens (slider takes no effect then). Removed unused images.

* added mobile controls

* fixed bot and chat

* mobile controls only appear on mobile or if forced

* lint fix
This commit is contained in:
KalmeMarq 2021-12-25 21:50:40 +00:00 committed by GitHub
parent 8cbc95b5ef
commit 1b08956d10
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 284 additions and 30 deletions

View file

@ -127,15 +127,40 @@ class ChatBox extends LitElement {
` `
} }
constructor () {
super()
this.chatHistoryPos = 0
this.chatHistory = []
}
enableChat (isCommand) {
const chat = this.shadowRoot.querySelector('#chat')
const chatInput = this.shadowRoot.querySelector('#chatinput')
// Set inChat value
this.inChat = true
// Exit the pointer lock
document.exitPointerLock()
// Show chat input
chatInput.style.display = 'block'
// Show extended chat history
chat.style.maxHeight = 'var(--chatHeight)'
chat.scrollTop = chat.scrollHeight // Stay bottom of the list
if (isCommand) { // handle commands
chatInput.value = '/'
}
// Focus element
chatInput.focus()
this.chatHistoryPos = this.chatHistory.length
document.querySelector('#hud').shadowRoot.querySelector('#chat').shadowRoot.querySelectorAll('.chat-message').forEach(e => e.classList.add('chat-message-chat-opened'))
}
init (client, renderer) { init (client, renderer) {
this.inChat = false this.inChat = false
const chat = this.shadowRoot.querySelector('#chat') const chat = this.shadowRoot.querySelector('#chat')
const gameMenu = document.getElementById('pause-screen') const gameMenu = document.getElementById('pause-screen')
const chatInput = this.shadowRoot.querySelector('#chatinput') const chatInput = this.shadowRoot.querySelector('#chatinput')
const chatHistory = []
let chatHistoryPos = 0
renderer.domElement.requestPointerLock = renderer.domElement.requestPointerLock || renderer.domElement.requestPointerLock = renderer.domElement.requestPointerLock ||
renderer.domElement.mozRequestPointerLock || renderer.domElement.mozRequestPointerLock ||
renderer.domElement.webkitRequestPointerLock renderer.domElement.webkitRequestPointerLock
@ -153,11 +178,11 @@ class ChatBox extends LitElement {
if (e.code === 'Escape') { if (e.code === 'Escape') {
disableChat() disableChat()
} else if (e.keyCode === 38) { } else if (e.keyCode === 38) {
if (chatHistoryPos === 0) return if (this.chatHistoryPos === 0) return
chatInput.value = chatHistory[--chatHistoryPos] !== undefined ? chatHistory[chatHistoryPos] : '' chatInput.value = this.chatHistory[--this.chatHistoryPos] !== undefined ? this.chatHistory[this.chatHistoryPos] : ''
} else if (e.keyCode === 40) { } else if (e.keyCode === 40) {
if (chatHistoryPos === chatHistory.length) return if (this.chatHistoryPos === this.chatHistory.length) return
chatInput.value = chatHistory[++chatHistoryPos] !== undefined ? chatHistory[chatHistoryPos] : '' chatInput.value = this.chatHistory[++this.chatHistoryPos] !== undefined ? this.chatHistory[this.chatHistoryPos] : ''
} }
}) })
@ -172,10 +197,10 @@ class ChatBox extends LitElement {
if (e.code === km.key) { if (e.code === km.key) {
switch (km.defaultKey) { switch (km.defaultKey) {
case 'KeyT': case 'KeyT':
setTimeout(() => enableChat(false), 0) setTimeout(() => this.enableChat(false), 0)
break break
case 'Slash': case 'Slash':
setTimeout(() => enableChat(true), 0) setTimeout(() => this.enableChat(true), 0)
break break
} }
} }
@ -187,7 +212,7 @@ class ChatBox extends LitElement {
if (!self.inChat) return if (!self.inChat) return
e.stopPropagation() e.stopPropagation()
if (e.code === 'Enter') { if (e.code === 'Enter') {
chatHistory.push(chatInput.value) this.chatHistory.push(chatInput.value)
client.write('chat', { message: chatInput.value }) client.write('chat', { message: chatInput.value })
disableChat() disableChat()
} }
@ -204,24 +229,6 @@ class ChatBox extends LitElement {
hideChat(); hideChat();
} }
}); */ }); */
function enableChat (isCommand) {
// Set inChat value
self.inChat = true
// Exit the pointer lock
document.exitPointerLock()
// Show chat input
chatInput.style.display = 'block'
// Show extended chat history
chat.style.maxHeight = 'calc(var(--chatHeight) * var(--chatScale))'
chat.scrollTop = chat.scrollHeight // Stay bottom of the list
if (isCommand) { // handle commands
chatInput.value = '/'
}
// Focus element
chatInput.focus()
chatHistoryPos = chatHistory.length
document.querySelector('#hud').shadowRoot.querySelector('#chat').shadowRoot.querySelectorAll('.chat-message').forEach(e => e.classList.add('chat-message-chat-opened'))
}
function disableChat () { function disableChat () {
self.inChat = false self.inChat = false

View file

@ -42,6 +42,12 @@ const commonCss = css`
} }
` `
/** @returns {boolean} */
function isMobile () {
const m = require('ismobilejs').default()
return m.any
}
/** /**
* @param {string} url * @param {string} url
*/ */
@ -60,6 +66,7 @@ function displayScreen (prev, next) {
export { export {
commonCss, commonCss,
isMobile,
openURL, openURL,
displayScreen displayScreen
} }

View file

@ -1,4 +1,5 @@
const { LitElement, html, css } = require('lit') const { LitElement, html, css } = require('lit')
const { isMobile } = require('./components/common')
class Hud extends LitElement { class Hud extends LitElement {
static get styles () { static get styles () {
@ -55,9 +56,142 @@ class Hud extends LitElement {
background-size: 256px; background-size: 256px;
background-position-y: -69px; background-position-y: -69px;
} }
.mobile-top-btns {
display: none;
flex-direction: row;
position: absolute;
top: 0;
left: 50%;
transform: translate(-50%);
gap: 0 1px;
z-index: 20;
}
.pause-btn,
.chat-btn {
border: none;
outline: none;
width: 18px;
height: 18px;
background-image: url('extra-textures/gui.png');
background-size: 256px;
background-position-x: -200px;
background-position-y: -64px;
}
.chat-btn {
background-position-y: -82px;
}
.mobile-control-forward,
.mobile-control-back,
.mobile-control-left,
.mobile-control-sneak,
.mobile-control-jump,
.mobile-control-right {
position: absolute;
width: 22px;
height: 22px;
background-image: url('extra-textures/gui.png');
background-size: 256px;
border: none;
outline: none;
}
.mobile-control-forward:active,
.mobile-control-back:active,
.mobile-control-sneak:active,
.mobile-control-left:active,
.mobile-control-jump:active,
.mobile-control-right:active {
filter: brightness(85%);
}
.mobile-control-forward {
top: 0;
left: 50%;
transform: translate(-50%);
background-position: -2px -109px;
}
.mobile-control-back {
bottom: 0;
left: 50%;
transform: translate(-50%);
background-position: -54px -109px;
}
.mobile-control-left {
top: 50%;
left: 0;
transform: translate(0, -50%);
background-position: -28px -109px;
}
.mobile-control-right {
top: 50%;
right: 0;
transform: translate(0, -50%);
background-position: -80px -109px;
}
.mobile-control-jump {
position: relative;
width: 18px;
height: 18px;
background-position: -108px -111px;
}
.mobile-control-sneak {
width: 18px;
height: 18px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-position: -218px -82px;
}
.mobile-control-sneak.is-down {
background-position: -218px -64px;
}
.mobile-controls-left {
display: none;
position: absolute;
left: 8px;
bottom: 0;
width: 70px;
height: 70px;
z-index: 30;
opacity: 0.65;
transform-origin: bottom left;
transform: scale(1.5);
}
.mobile-controls-right {
display: none;
flex-direction: column;
place-items: center;
place-content: center;
position: absolute;
right: 20px;
bottom: 0;
width: 22px;
height: 70px;
z-index: 30;
opacity: 0.65;
transform-origin: bottom right;
transform: scale(1.5);
}
` `
} }
static get properties () {
return {
bot: { type: Object }
}
}
/** /**
* @param {globalThis.THREE.Renderer} renderer * @param {globalThis.THREE.Renderer} renderer
* @param {import('mineflayer').Bot} bot * @param {import('mineflayer').Bot} bot
@ -73,6 +207,7 @@ class Hud extends LitElement {
const hotbar = this.shadowRoot.querySelector('#hotbar') const hotbar = this.shadowRoot.querySelector('#hotbar')
const xpLabel = this.shadowRoot.querySelector('#xp-label') const xpLabel = this.shadowRoot.querySelector('#xp-label')
this.bot = bot
hotbar.bot = bot hotbar.bot = bot
debugMenu.bot = bot debugMenu.bot = bot
@ -125,10 +260,84 @@ class Hud extends LitElement {
foodbar.updateHunger(bot.food) foodbar.updateHunger(bot.food)
// breathbar.updateOxygen(bot.oxygenLevel ?? 20) // breathbar.updateOxygen(bot.oxygenLevel ?? 20)
hotbar.init() hotbar.init()
if (document.getElementById('options-screen').forceMobileControls || isMobile()) {
this.showMobileControls(true)
} else {
this.showMobileControls(false)
}
}
/** @param {boolean} bl */
showMobileControls (bl) {
this.shadowRoot.querySelector('#mobile-top').style.display = bl ? 'flex' : 'none'
this.shadowRoot.querySelector('#mobile-left').style.display = bl ? 'block' : 'none'
this.shadowRoot.querySelector('#mobile-right').style.display = bl ? 'flex' : 'none'
}
/**
* @param {string} id
* @param {boolean} action
*/
mobileControl (e, id, action) {
e.stopPropagation()
this.bot.setControlState(id, action)
} }
render () { render () {
return html` return html`
<div class="mobile-top-btns" id="mobile-top">
<button class="chat-btn" @click=${(e) => {
e.stopPropagation()
this.shadowRoot.querySelector('#chat').enableChat(false)
}}></button>
<button class="pause-btn" @click=${(e) => {
e.stopPropagation()
document.getElementById('pause-screen').enableGameMenu()
}}></button>
</div>
<div class="mobile-controls-left" id="mobile-left">
<button
class="mobile-control-forward"
@touchstart=${(e) => this.mobileControl(e, 'forward', true)}
@touchend=${(e) => this.mobileControl(e, 'forward', false)}
@mousedown=${(e) => this.mobileControl(e, 'forward', true)}
@mouseup=${(e) => this.mobileControl(e, 'forward', false)}
></button>
<button
class="mobile-control-back"
@touchstart=${(e) => this.mobileControl(e, 'back', true)}
@touchend=${(e) => this.mobileControl(e, 'back', false)}
@mousedown=${(e) => this.mobileControl(e, 'back', true)}
@mouseup=${(e) => this.mobileControl(e, 'back', false)}
></button>
<button class="mobile-control-left"
@touchstart=${(e) => this.mobileControl(e, 'right', true)}
@touchend=${(e) => this.mobileControl(e, 'right', false)}
@mousedown=${(e) => this.mobileControl(e, 'right', true)}
@mouseup=${(e) => this.mobileControl(e, 'right', false)}
></button>
<button class="mobile-control-right"
@touchstart=${(e) => this.mobileControl(e, 'left', true)}
@touchend=${(e) => this.mobileControl(e, 'left', false)}
@mousedown=${(e) => this.mobileControl(e, 'left', true)}
@mouseup=${(e) => this.mobileControl(e, 'left', false)}
></button>
<button class="mobile-control-sneak" @dblclick=${(e) => {
e.stopPropagation()
const b = e.target.classList.toggle('is-down')
this.bot.setControlState('sneak', b)
}}></button>
</div>
<div class="mobile-controls-right" id="mobile-right">
<button class="mobile-control-jump"
@touchstart=${(e) => this.mobileControl(e, 'jump', true)}
@touchend=${(e) => this.mobileControl(e, 'jump', false)}
@mousedown=${(e) => this.mobileControl(e, 'jump', true)}
@mouseup=${(e) => this.mobileControl(e, 'jump', false)}
></button>
</div>
<pmui-debug-overlay id="debug-overlay"></pmui-debug-overlay> <pmui-debug-overlay id="debug-overlay"></pmui-debug-overlay>
<pmui-playerlist-overlay id="playerlist-overlay"></pmui-playerlist-overlay> <pmui-playerlist-overlay id="playerlist-overlay"></pmui-playerlist-overlay>
<div class="crosshair"></div> <div class="crosshair"></div>

View file

@ -1,5 +1,5 @@
const { LitElement, html, css } = require('lit') const { LitElement, html, css } = require('lit')
const { commonCss, displayScreen } = require('./components/common') const { commonCss, displayScreen, isMobile } = require('./components/common')
class OptionsScreen extends LitElement { class OptionsScreen extends LitElement {
static get styles () { static get styles () {
@ -62,6 +62,7 @@ class OptionsScreen extends LitElement {
this.renderDistance = getValue('renderDistance', 6, (v) => Number(v)) this.renderDistance = getValue('renderDistance', 6, (v) => Number(v))
this.fov = getValue('fov', 75, (v) => Number(v)) this.fov = getValue('fov', 75, (v) => Number(v))
this.guiScale = getValue('guiScale', 3, (v) => Number(v)) this.guiScale = getValue('guiScale', 3, (v) => Number(v))
this.forceMobileControls = getValue('forceMobileControls', false, (v) => v === 'true')
document.documentElement.style.setProperty('--chatScale', `${this.chatScale / 100}`) document.documentElement.style.setProperty('--chatScale', `${this.chatScale / 100}`)
document.documentElement.style.setProperty('--chatWidth', `${this.chatWidth}px`) document.documentElement.style.setProperty('--chatWidth', `${this.chatWidth}px`)
@ -133,7 +134,20 @@ class OptionsScreen extends LitElement {
}}></pmui-slider> }}></pmui-slider>
</div> </div>
`} `}
<br>
<div class="wrapper">
<pmui-button pmui-width="150px" pmui-label=${'Force Mobile Controls: ' + (this.forceMobileControls ? 'ON' : 'OFF')} @pmui-click=${() => {
this.forceMobileControls = !this.forceMobileControls
window.localStorage.setItem('forceMobileControls', `${this.forceMobileControls}`)
if (this.forceMobileControls || isMobile()) {
document.getElementById('hud').showMobileControls(true)
} else {
document.getElementById('hud').showMobileControls(false)
}
this.requestUpdate()
}
}></pmui-button>
</div>
<pmui-button pmui-width="200px" pmui-label="Done" @pmui-click=${() => displayScreen(this, document.getElementById(this.isInsideWorld ? 'pause-screen' : 'title-screen'))}></pmui-button> <pmui-button pmui-width="200px" pmui-label="Done" @pmui-click=${() => displayScreen(this, document.getElementById(this.isInsideWorld ? 'pause-screen' : 'title-screen'))}></pmui-button>
</main> </main>

View file

@ -36,6 +36,7 @@
"dependencies": { "dependencies": {
"compression": "^1.7.4", "compression": "^1.7.4",
"express": "^4.17.1", "express": "^4.17.1",
"ismobilejs": "^1.1.1",
"lit": "^2.0.2", "lit": "^2.0.2",
"net-browserify": "PrismarineJS/net-browserify", "net-browserify": "PrismarineJS/net-browserify",
"querystring": "^0.2.1", "querystring": "^0.2.1",

View file

@ -95,6 +95,14 @@ canvas {
} }
} }
@media only screen and (max-height: 670px) {
#ui-root {
transform: scale(2);
width: calc(100% / 2);
height: calc(100% / 2);
}
}
@media only screen and (max-width: 561px) { @media only screen and (max-width: 561px) {
#ui-root { #ui-root {
transform: scale(1); transform: scale(1);
@ -102,3 +110,11 @@ canvas {
height: calc(100% / 1); height: calc(100% / 1);
} }
} }
@media only screen and (max-height: 430px) {
#ui-root {
transform: scale(1);
width: calc(100% / 1);
height: calc(100% / 1);
}
}