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:
parent
8cbc95b5ef
commit
1b08956d10
6 changed files with 284 additions and 30 deletions
63
lib/chat.js
63
lib/chat.js
|
@ -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) {
|
||||
this.inChat = false
|
||||
const chat = this.shadowRoot.querySelector('#chat')
|
||||
const gameMenu = document.getElementById('pause-screen')
|
||||
const chatInput = this.shadowRoot.querySelector('#chatinput')
|
||||
|
||||
const chatHistory = []
|
||||
let chatHistoryPos = 0
|
||||
|
||||
renderer.domElement.requestPointerLock = renderer.domElement.requestPointerLock ||
|
||||
renderer.domElement.mozRequestPointerLock ||
|
||||
renderer.domElement.webkitRequestPointerLock
|
||||
|
@ -153,11 +178,11 @@ class ChatBox extends LitElement {
|
|||
if (e.code === 'Escape') {
|
||||
disableChat()
|
||||
} else if (e.keyCode === 38) {
|
||||
if (chatHistoryPos === 0) return
|
||||
chatInput.value = chatHistory[--chatHistoryPos] !== undefined ? chatHistory[chatHistoryPos] : ''
|
||||
if (this.chatHistoryPos === 0) return
|
||||
chatInput.value = this.chatHistory[--this.chatHistoryPos] !== undefined ? this.chatHistory[this.chatHistoryPos] : ''
|
||||
} else if (e.keyCode === 40) {
|
||||
if (chatHistoryPos === chatHistory.length) return
|
||||
chatInput.value = chatHistory[++chatHistoryPos] !== undefined ? chatHistory[chatHistoryPos] : ''
|
||||
if (this.chatHistoryPos === this.chatHistory.length) return
|
||||
chatInput.value = this.chatHistory[++this.chatHistoryPos] !== undefined ? this.chatHistory[this.chatHistoryPos] : ''
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -172,10 +197,10 @@ class ChatBox extends LitElement {
|
|||
if (e.code === km.key) {
|
||||
switch (km.defaultKey) {
|
||||
case 'KeyT':
|
||||
setTimeout(() => enableChat(false), 0)
|
||||
setTimeout(() => this.enableChat(false), 0)
|
||||
break
|
||||
case 'Slash':
|
||||
setTimeout(() => enableChat(true), 0)
|
||||
setTimeout(() => this.enableChat(true), 0)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -187,7 +212,7 @@ class ChatBox extends LitElement {
|
|||
if (!self.inChat) return
|
||||
e.stopPropagation()
|
||||
if (e.code === 'Enter') {
|
||||
chatHistory.push(chatInput.value)
|
||||
this.chatHistory.push(chatInput.value)
|
||||
client.write('chat', { message: chatInput.value })
|
||||
disableChat()
|
||||
}
|
||||
|
@ -204,24 +229,6 @@ class ChatBox extends LitElement {
|
|||
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 () {
|
||||
self.inChat = false
|
||||
|
|
|
@ -42,6 +42,12 @@ const commonCss = css`
|
|||
}
|
||||
`
|
||||
|
||||
/** @returns {boolean} */
|
||||
function isMobile () {
|
||||
const m = require('ismobilejs').default()
|
||||
return m.any
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} url
|
||||
*/
|
||||
|
@ -60,6 +66,7 @@ function displayScreen (prev, next) {
|
|||
|
||||
export {
|
||||
commonCss,
|
||||
isMobile,
|
||||
openURL,
|
||||
displayScreen
|
||||
}
|
||||
|
|
209
lib/menus/hud.js
209
lib/menus/hud.js
|
@ -1,4 +1,5 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
const { isMobile } = require('./components/common')
|
||||
|
||||
class Hud extends LitElement {
|
||||
static get styles () {
|
||||
|
@ -55,9 +56,142 @@ class Hud extends LitElement {
|
|||
background-size: 256px;
|
||||
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 {import('mineflayer').Bot} bot
|
||||
|
@ -73,6 +207,7 @@ class Hud extends LitElement {
|
|||
const hotbar = this.shadowRoot.querySelector('#hotbar')
|
||||
const xpLabel = this.shadowRoot.querySelector('#xp-label')
|
||||
|
||||
this.bot = bot
|
||||
hotbar.bot = bot
|
||||
debugMenu.bot = bot
|
||||
|
||||
|
@ -125,10 +260,84 @@ class Hud extends LitElement {
|
|||
foodbar.updateHunger(bot.food)
|
||||
// breathbar.updateOxygen(bot.oxygenLevel ?? 20)
|
||||
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 () {
|
||||
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-playerlist-overlay id="playerlist-overlay"></pmui-playerlist-overlay>
|
||||
<div class="crosshair"></div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
const { commonCss, displayScreen } = require('./components/common')
|
||||
const { commonCss, displayScreen, isMobile } = require('./components/common')
|
||||
|
||||
class OptionsScreen extends LitElement {
|
||||
static get styles () {
|
||||
|
@ -62,6 +62,7 @@ class OptionsScreen extends LitElement {
|
|||
this.renderDistance = getValue('renderDistance', 6, (v) => Number(v))
|
||||
this.fov = getValue('fov', 75, (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('--chatWidth', `${this.chatWidth}px`)
|
||||
|
@ -133,7 +134,20 @@ class OptionsScreen extends LitElement {
|
|||
}}></pmui-slider>
|
||||
</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>
|
||||
</main>
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
"dependencies": {
|
||||
"compression": "^1.7.4",
|
||||
"express": "^4.17.1",
|
||||
"ismobilejs": "^1.1.1",
|
||||
"lit": "^2.0.2",
|
||||
"net-browserify": "PrismarineJS/net-browserify",
|
||||
"querystring": "^0.2.1",
|
||||
|
|
16
styles.css
16
styles.css
|
@ -95,10 +95,26 @@ 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) {
|
||||
#ui-root {
|
||||
transform: scale(1);
|
||||
width: 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);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue