diff --git a/config.json b/config.json index 123d8f3fc..7c0ce254d 100644 --- a/config.json +++ b/config.json @@ -1,5 +1,6 @@ { - "defaultHost": "pjs.deptofcraft.com", + "version": 1, + "defaultHost": "", "defaultProxy": "zardoy.site:2344", "defaultVersion": "1.18.2", "mapsProvider": "zardoy.site/maps" diff --git a/prismarine-viewer/viewer/lib/entities.js b/prismarine-viewer/viewer/lib/entities.js index e46ce437a..3d9e994eb 100644 --- a/prismarine-viewer/viewer/lib/entities.js +++ b/prismarine-viewer/viewer/lib/entities.js @@ -30,7 +30,8 @@ function getUsernameTexture(username, { fontFamily = 'sans-serif' }) { function getEntityMesh (entity, scene, options) { if (entity.name) { try { - const e = new Entity('1.16.4', entity.name, scene) + // https://github.com/PrismarineJS/prismarine-viewer/pull/410 + const e = new Entity('1.16.4', entity.name.toLowerCase(), scene) if (entity.username !== undefined) { const canvas = getUsernameTexture(entity.username, options) diff --git a/prismarine-viewer/viewer/lib/worldrenderer.ts b/prismarine-viewer/viewer/lib/worldrenderer.ts index 3f022a732..3fc4b487e 100644 --- a/prismarine-viewer/viewer/lib/worldrenderer.ts +++ b/prismarine-viewer/viewer/lib/worldrenderer.ts @@ -33,10 +33,15 @@ export class WorldRenderer { downloadedTextureImage = undefined as any workers: any[] = [] viewerPosition?: Vec3 + lastCamUpdate = 0 + droppedFpsPercentage = 0 + initialChunksLoad = true texturesVersion?: string - constructor (public scene: THREE.Scene, numWorkers = 4) { + promisesQueue = [] as Promise[] + + constructor(public scene: THREE.Scene, numWorkers = 4) { // init workers for (let i = 0; i < numWorkers; i++) { // Node environment needs an absolute path, but browser needs the url of the file @@ -59,7 +64,21 @@ export class WorldRenderer { } const chunkCoords = data.key.split(',') - if (!this.loadedChunks[chunkCoords[0] + ',' + chunkCoords[2]] || !data.geometry.positions.length) return + if (!this.loadedChunks[chunkCoords[0] + ',' + chunkCoords[2]] || !data.geometry.positions.length || !this.active) return + + if (!this.initialChunksLoad) { + const newPromise = new Promise(resolve => { + if (this.droppedFpsPercentage > 0.5) { + setTimeout(resolve, 1000 / 50 * this.droppedFpsPercentage) + } else { + setTimeout(resolve) + } + }) + this.promisesQueue.push(newPromise) + for (const promise of this.promisesQueue) { + await promise + } + } const geometry = new THREE.BufferGeometry() geometry.setAttribute('position', new THREE.BufferAttribute(data.geometry.positions, 3)) @@ -156,7 +175,7 @@ export class WorldRenderer { for (const object of Object.values(this.sectionObjects)) { for (const child of object.children) { if (child.name === 'helper') { - child.visible = value; + child.visible = value } } } @@ -224,12 +243,13 @@ export class WorldRenderer { const [currentX, currentZ] = chunkPos(pos) return Object.fromEntries(Object.entries(this.sectionObjects).map(([key, o]) => { const [xRaw, yRaw, zRaw] = key.split(',').map(Number) - const [x, z] = chunkPos({x: xRaw, z: zRaw}) + const [x, z] = chunkPos({ x: xRaw, z: zRaw }) return [`${x - currentX},${z - currentZ}`, o] })) } addColumn (x, z, chunk) { + this.initialChunksLoad = false this.loadedChunks[`${x},${z}`] = true for (const worker of this.workers) { worker.postMessage({ type: 'chunk', x, z, chunk }) diff --git a/src/globalState.ts b/src/globalState.ts index 9815ca692..92bbb5c12 100644 --- a/src/globalState.ts +++ b/src/globalState.ts @@ -120,7 +120,12 @@ export const showContextmenu = (items: ContextMenuItem[], { clientX, clientY }) // --- -type AppConfig = { +export type AppConfig = { + defaultHost?: string + defaultHostSave?: string + defaultProxy?: string + defaultProxySave?: string + defaultVersion?: string mapsProvider?: string } diff --git a/src/index.ts b/src/index.ts index 948f4500f..90142e7af 100644 --- a/src/index.ts +++ b/src/index.ts @@ -126,6 +126,8 @@ let delta = 0 let lastTime = performance.now() let previousWindowWidth = window.innerWidth let previousWindowHeight = window.innerHeight +let max = 0 +let rendered = 0 const renderFrame = (time: DOMHighResTimeStamp) => { if (window.stopLoop) return window.requestAnimationFrame(renderFrame) @@ -149,10 +151,18 @@ const renderFrame = (time: DOMHighResTimeStamp) => { statsStart() viewer.update() renderer.render(viewer.scene, viewer.camera) + rendered++ postRenderFrameFn() statsEnd() } renderFrame(performance.now()) +setInterval(() => { + if (max > 0) { + viewer.world.droppedFpsPercentage = rendered / max + } + max = Math.max(rendered, max) + rendered = 0 +}, 1000) const resizeHandler = () => { const width = window.innerWidth @@ -564,6 +574,7 @@ async function connect (connectOptions: { // Bot position callback function botPosition () { + viewer.world.lastCamUpdate = Date.now() // this might cause lag, but not sure viewer.setFirstPersonCamera(bot.entity.position, bot.entity.yaw, bot.entity.pitch) void worldView.updatePosition(bot.entity.position) @@ -576,6 +587,7 @@ async function connect (connectOptions: { const maxPitch = 0.5 * Math.PI const minPitch = -0.5 * Math.PI mouseMovePostHandle = ({ x, y }) => { + viewer.world.lastCamUpdate = Date.now() bot.entity.pitch -= y bot.entity.pitch = Math.max(minPitch, Math.min(maxPitch, bot.entity.pitch)) bot.entity.yaw -= x diff --git a/src/menus/components/common.js b/src/menus/components/common.js index 593655d75..5ad74faf9 100644 --- a/src/menus/components/common.js +++ b/src/menus/components/common.js @@ -44,8 +44,12 @@ function isProbablyIphone () { /** * @param {string} url */ -function openURL (url) { - window.open(url, '_blank', 'noopener,noreferrer') +function openURL (url, newTab = true) { + if (newTab) { + window.open(url, '_blank', 'noopener,noreferrer') + } else { + window.open(url) + } } export { diff --git a/src/menus/components/edit_box.js b/src/menus/components/edit_box.js index 0f42527a6..c7210d438 100644 --- a/src/menus/components/edit_box.js +++ b/src/menus/components/edit_box.js @@ -114,6 +114,10 @@ class EditBox extends LitElement { type: Boolean, attribute: 'pmui-required' }, + placeholder: { + type: String, + attribute: 'pmui-placeholder' + }, state: { type: String, attribute: true @@ -144,6 +148,7 @@ class EditBox extends LitElement { autocomplete="off" autocapitalize="off" value="${this.value}" + placeholder=${ifDefined(this.placeholder || undefined)} list=${ifDefined(this.autocompleteValues ? `${this.id}-list` : undefined)} inputmode=${ifDefined(this.inputMode || undefined)} @input=${({ target: { value } }) => { this.value = this.inputMode === 'decimal' ? value.replaceAll(',', '.') : value }} diff --git a/src/menus/play_screen.js b/src/menus/play_screen.js index 7533ddb70..1deb7dc6a 100644 --- a/src/menus/play_screen.js +++ b/src/menus/play_screen.js @@ -70,8 +70,10 @@ class PlayScreen extends LitElement { static get properties () { return { server: { type: String }, + serverImplicit: { type: String }, serverport: { type: Number }, proxy: { type: String }, + proxyImplicit: { type: String }, proxyport: { type: Number }, username: { type: String }, password: { type: String }, @@ -84,11 +86,17 @@ class PlayScreen extends LitElement { this.version = '' this.serverport = '' this.proxyport = '' + this.server = '' + this.proxy = '' + this.username = '' + this.password = '' + this.serverImplicit = '' + this.proxyImplicit = '' // todo set them sooner add indicator void window.fetch('config.json').then(async res => res.json()).then(c => c, (error) => { - console.error('Failed to load config.json', error) + console.warn('Failed to load optional config.json', error) return {} - }).then(config => { + }).then(async (/** @type {import('../globalState').AppConfig} */config) => { miscUiState.appConfig = config const params = new URLSearchParams(window.location.search) @@ -100,9 +108,34 @@ class PlayScreen extends LitElement { return qsValue || window.localStorage.getItem(localStorageKey) } - this.server = getParam('server', 'ip') ?? config.defaultHost - this.proxy = getParam('proxy') ?? config.defaultProxy - this.version = getParam('version') || (window.localStorage.getItem('version') ?? config.defaultVersion) + if (config.defaultHost === '' || config.defaultHostSave === '') { + let proxy = config.defaultProxy || config.defaultProxySave || params.get('proxy') + const cleanUrl = url => url.replaceAll(/(https?:\/\/|\/$)/g, '') + if (proxy && cleanUrl(proxy) !== cleanUrl(location.origin + location.pathname)) { + if (!proxy.startsWith('http')) proxy = 'https://' + proxy + const proxyConfig = await fetch(proxy + '/config.json').then(async res => res.json()).then(c => c, (error) => { + console.warn(`Failed to load config.json from proxy ${proxy}`, error) + return {} + }) + if (config.defaultHost === '' && proxyConfig.defaultHost) { + config.defaultHost = proxyConfig.defaultHost + } else { + config.defaultHost = '' + } + if (config.defaultHostSave === '' && proxyConfig.defaultHostSave) { + config.defaultHostSave = proxyConfig.defaultHostSave + } else { + config.defaultHostSave = '' + } + } + this.server = this.serverImplicit + } + + this.serverImplicit = config.defaultHost ?? '' + this.proxyImplicit = config.defaultProxy ?? '' + this.server = getParam('server', 'ip') ?? config.defaultHostSave ?? '' + this.proxy = getParam('proxy') ?? config.defaultProxySave ?? '' + this.version = getParam('version') || (window.localStorage.getItem('version') ?? config.defaultVersion ?? '') this.username = getParam('username') || 'pviewer' + (Math.floor(Math.random() * 1000)) this.password = getParam('password') || '' if (process.env.NODE_ENV === 'development' && params.get('reconnect') && this.server && this.username) { @@ -125,7 +158,8 @@ class PlayScreen extends LitElement { pmui-id="serverip" pmui-value="${this.server}" pmui-type="url" - pmui-required="true" + pmui-required="${this.serverImplicit === ''}}" + pmui-placeholder="${this.serverImplicit}" .autocompleteValues=${JSON.parse(localStorage.getItem('serverHistory') || '[]')} @input=${e => { this.server = e.target.value }} > @@ -135,6 +169,7 @@ class PlayScreen extends LitElement { pmui-id="port" pmui-value="${this.serverport}" pmui-type="number" + pmui-placeholder="25565" @input=${e => { this.serverport = e.target.value }} > @@ -144,7 +179,8 @@ class PlayScreen extends LitElement { pmui-label="Proxy IP" pmui-id="proxy" pmui-value="${this.proxy}" - pmui-required=${/* TODO derive from config? */false} + pmui-required="${this.proxyImplicit === ''}}" + pmui-placeholder="${this.proxyImplicit}" pmui-type="url" @input=${e => { this.proxy = e.target.value }} > @@ -190,8 +226,8 @@ class PlayScreen extends LitElement { } onConnectPress () { - const server = `${this.server}${this.serverport && `:${this.serverport}`}` - const proxy = this.proxy && `${this.proxy}${this.proxyport && `:${this.proxyport}`}` + const server = this.server ? `${this.server}${this.serverport && `:${this.serverport}`}` : this.serverImplicit + const proxy = this.proxy ? `${this.proxy}${this.proxyport && `:${this.proxyport}`}` : this.proxyImplicit window.localStorage.setItem('username', this.username) window.localStorage.setItem('password', this.password) diff --git a/src/react/Button.tsx b/src/react/Button.tsx index fb9e3f04c..4d346b149 100644 --- a/src/react/Button.tsx +++ b/src/react/Button.tsx @@ -1,5 +1,5 @@ -import { forwardRef } from 'react' import classNames from 'classnames' +import { FC, Ref } from 'react' import { loadSound, playSound } from '../basicSounds' import buttonCss from './button.module.css' @@ -10,11 +10,12 @@ interface Props extends React.ComponentProps<'button'> { icon?: string children?: React.ReactNode inScreen?: boolean + rootRef?: Ref } void loadSound('button_click.mp3') -export default forwardRef(({ label, icon, children, inScreen, ...args }, ref) => { +export default (({ label, icon, children, inScreen, rootRef, ...args }) => { const onClick = (e) => { void playSound('button_click.mp3') args.onClick?.(e) @@ -28,9 +29,9 @@ export default forwardRef(({ label, icon, children, in args.style.width = 20 } - return -}) +}) satisfies FC diff --git a/src/react/ButtonWithTooltip.tsx b/src/react/ButtonWithTooltip.tsx index 390b074cb..842f5a2e7 100644 --- a/src/react/ButtonWithTooltip.tsx +++ b/src/react/ButtonWithTooltip.tsx @@ -51,20 +51,21 @@ export default ({ initialTooltip, ...args }: Props) => { }) return <> -