pages235/src/utils.ts

248 lines
7.2 KiB
TypeScript

import { activeModalStack, hideModal, miscUiState, showModal } from './globalState'
import { notification } from './menus/notification'
import * as crypto from 'crypto'
import UUID from 'uuid-1345'
import { options } from './optionsStorage'
import { saveWorld } from './builtinCommands'
import { openWorldZip } from './browserfs'
import { installTexturePack } from './texturePack'
export const goFullscreen = async (doToggle = false) => {
if (!document.fullscreenElement) {
// todo display a message or repeat?
await document.documentElement.requestFullscreen().catch(() => { })
// request full keyboard access
//@ts-ignore
navigator.keyboard?.lock?.(['Escape', 'KeyW'])
} else if (doToggle) {
await document.exitFullscreen().catch(() => { })
}
}
export const toNumber = (val) => {
const num = Number(val)
return isNaN(num) ? undefined : num
}
export const pointerLock = {
get hasPointerLock() {
return document.pointerLockElement
},
justHitEscape: false,
async requestPointerLock() {
if (document.getElementById('hud').style.display === 'none' || activeModalStack.length || !document.documentElement.requestPointerLock || miscUiState.currentTouch) {
return
}
if (options.autoFullScreen) {
void goFullscreen()
}
const displayBrowserProblem = () => {
notification.show = true
notification.message = navigator['keyboard'] ? 'Browser Limitation: Click on screen, enable Auto Fullscreen or F11' : 'Browser Limitation: Click on screen or use fullscreen in Chrome'
}
if (!(document.fullscreenElement && navigator['keyboard']) && this.justHitEscape) {
displayBrowserProblem()
} else {
//@ts-ignore
const promise: any = document.documentElement.requestPointerLock({
unadjustedMovement: options.mouseRawInput
})
promise?.catch((error) => {
if (error.name === "NotSupportedError") {
// Some platforms may not support unadjusted movement, request again a regular pointer lock.
document.documentElement.requestPointerLock()
} else if (error.name === 'SecurityError') {
// cause: https://discourse.threejs.org/t/how-to-avoid-pointerlockcontrols-error/33017/4
displayBrowserProblem()
} else {
console.error(error)
}
})
}
this.justHitEscape = false
}
}
window.getScreenRefreshRate = getScreenRefreshRate
/**
* Allows to obtain the estimated Hz of the primary monitor in the system.
*/
export function getScreenRefreshRate(): Promise<number> {
let requestId = null
let callbackTriggered = false
let resolve
const DOMHighResTimeStampCollection = []
const triggerAnimation = (DOMHighResTimeStamp) => {
DOMHighResTimeStampCollection.unshift(DOMHighResTimeStamp)
if (DOMHighResTimeStampCollection.length > 10) {
let t0 = DOMHighResTimeStampCollection.pop()
let fps = Math.floor(1000 * 10 / (DOMHighResTimeStamp - t0))
if (!callbackTriggered) {
resolve(fps/* , DOMHighResTimeStampCollection */)
}
callbackTriggered = true
}
requestId = window.requestAnimationFrame(triggerAnimation)
}
window.requestAnimationFrame(triggerAnimation)
window.setTimeout(() => {
window.cancelAnimationFrame(requestId)
requestId = null
}, 500)
return new Promise(_resolve => {
resolve = _resolve
})
}
export const getGamemodeNumber = (bot) => {
switch (bot.game.gameMode) {
case 'survival': return 0
case 'creative': return 1
case 'adventure': return 2
case 'spectator': return 3
default: return -1
}
}
export const isCypress = () => {
return localStorage.cypress === 'true'
}
// https://github.com/PrismarineJS/node-minecraft-protocol/blob/cf1f67117d586b5e6e21f0d9602da12e9fcf46b6/src/server/login.js#L170
function javaUUID(s: string) {
const hash = crypto.createHash('md5')
hash.update(s, 'utf8')
const buffer = hash.digest()
buffer[6] = (buffer[6] & 0x0f) | 0x30
buffer[8] = (buffer[8] & 0x3f) | 0x80
return buffer
}
export function nameToMcOfflineUUID(name) {
return (new UUID(javaUUID('OfflinePlayer:' + name))).toString()
}
export const setLoadingScreenStatus = function (status: string | undefined, isError = false, hideDots = false) {
const loadingScreen = document.getElementById('loading-error-screen')
if (status === undefined) {
loadingScreen.status = ''
hideModal({ elem: loadingScreen, }, null, { force: true })
return
}
// todo update in component instead
showModal(loadingScreen)
if (loadingScreen.hasError) {
miscUiState.gameLoaded = false
return
}
loadingScreen.hideDots = hideDots
loadingScreen.hasError = isError
loadingScreen.status = isError && loadingScreen.status ? status + `\nLast status: ${loadingScreen.status}` : status
}
export const disconnect = async () => {
if (window.localServer) {
await saveWorld()
localServer.quit()
} else {
// workaround bot.end doesn't end the socket and emit end event
bot.end()
bot._client.socket.end()
}
bot._client.emit('end', 'You left the server')
miscUiState.gameLoaded = false
}
export const loadScript = async function (scriptSrc: string) {
if (document.querySelector(`script[src="${scriptSrc}"]`)) {
return
}
return new Promise((resolve, reject) => {
const scriptElement = document.createElement('script')
scriptElement.src = scriptSrc
scriptElement.async = true
scriptElement.onload = () => {
resolve(scriptElement)
}
scriptElement.onerror = (error) => {
reject(error)
}
document.head.appendChild(scriptElement)
})
}
// doesn't support snapshots
export const toMajorVersion = (version) => {
const [a, b] = (version + '').split('.')
return `${a}.${b}`
}
let prevRenderDistance = options.renderDistance
export const setRenderDistance = () => {
worldView.viewDistance = options.renderDistance
if (localServer) {
localServer.options['view-distance'] = options.renderDistance
localServer.players[0].emit('playerChangeRenderDistance', options.renderDistance)
}
prevRenderDistance = options.renderDistance
}
export const reloadChunks = () => {
if (!worldView) return
setRenderDistance()
worldView.updatePosition(bot.entity.position, true)
}
export const openFilePicker = (specificCase?: 'resourcepack') => {
// create and show input picker
let picker: HTMLInputElement = document.body.querySelector('input#file-zip-picker')
if (!picker) {
picker = document.createElement('input')
picker.type = 'file'
picker.accept = '.zip'
picker.addEventListener('change', () => {
const file = picker.files[0]
picker.value = ''
if (!file) return
if (!file.name.endsWith('.zip')) {
const doContinue = confirm(`Are you sure ${file.name.slice(-20)} is .zip file? Only .zip files are supported. Continue?`)
if (!doContinue) return
}
if (specificCase === 'resourcepack') {
installTexturePack(file)
} else {
openWorldZip(file)
}
})
picker.hidden = true
document.body.appendChild(picker)
}
picker.click()
}
export const resolveTimeout = (promise, timeout = 10000) => {
return new Promise((resolve, reject) => {
promise.then(resolve, reject)
setTimeout(() => {
reject(new Error('timeout'))
}, timeout)
})
}