Skip to content

Commit

Permalink
release (#228)
Browse files Browse the repository at this point in the history
  • Loading branch information
zardoy authored Nov 20, 2024
2 parents a2bd1bd + 4dac577 commit ce601ca
Show file tree
Hide file tree
Showing 32 changed files with 354 additions and 163 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,8 @@
"no-async-promise-executor": "off",
"no-bitwise": "off",
"unicorn/filename-case": "off",
"max-depth": "off"
"max-depth": "off",
"unicorn/no-typeof-undefined": "off"
},
"overrides": [
{
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ RUN pnpm i
# ENTRYPOINT ["pnpm", "run", "run-all"]

# only for prod
RUN pnpm run build
RUN GITHUB_REPOSITORY=zardoy/minecraft-web-client pnpm run build

# ---- Run Stage ----
FROM node:18-alpine
Expand Down
8 changes: 5 additions & 3 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@ Whatever offline mode you used (zip, folder, just single player), you can always

![docs-assets/singleplayer-future-city-1-10-2.jpg](./docs-assets/singleplayer-future-city-1-10-2.jpg)

### Servers
### Servers & Proxy

You can play almost on any Java server, vanilla servers are fully supported.
See the [Mineflayer](https://github.com/PrismarineJS/mineflayer) repo for the list of supported versions (should support majority of versions).
There is a builtin proxy, but you can also host your one! Just clone the repo, run `pnpm i` (following CONTRIBUTING.MD) and run `pnpm prod-start`, then you can specify `http://localhost:8080` in the proxy field.
There is a builtin proxy, but you can also host your one! Just clone the repo, run `pnpm i` (following CONTRIBUTING.MD) and run `pnpm prod-start`, then you can specify `http://localhost:8080` in the proxy field. Or you can deploy it to the cloud service:

[![Deploy to Koyeb](https://www.koyeb.com/static/images/deploy/button.svg)](https://app.koyeb.com/deploy?name=minecraft-web-client&type=git&repository=zardoy%2Fminecraft-web-client&branch=next&builder=dockerfile&env%5B%5D=&ports=8080%3Bhttp%3B%2F)

Proxy servers are used to connect to Minecraft servers which use TCP protocol. When you connect connect to a server with a proxy, websocket connection is created between you (browser client) and the proxy server located in Europe, then the proxy connects to the Minecraft server and sends the data to the client (you) without any packet deserialization to avoid any additional delays. That said all the Minecraft protocol packets are processed by the client, right in your browser.

Expand Down Expand Up @@ -139,7 +141,7 @@ Server specific:
Single player specific:

- `?loadSave=<save_name>` - Load the save on load with the specified folder name (not title)
- `?singleplayer=1` - Create empty world on load. Nothing will be saved
- `?singleplayer=1` or `?sp=1` - Create empty world on load. Nothing will be saved
- `?version=<version>` - Set the version for the singleplayer world (when used with `?singleplayer=1`)
- `?noSave=true` - Disable auto save on unload / disconnect / export whenever a world is loaded. Only manual save with `/save` command will work.
- `?serverSetting=<key>:<value>` - Set local server [options](https://github.com/zardoy/space-squid/tree/everything/src/modules.ts#L51). For example `?serverSetting=noInitialChunksSend:true` will disable initial chunks loading on the loading screen.
Expand Down
2 changes: 1 addition & 1 deletion README.NPM.MD
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Minecraft UI components for React extracted from [mcraft.fun](https://mcraft.fun
pnpm i minecraft-react
```

![demo](https://github-production-user-asset-6210df.s3.amazonaws.com/46503702/346295584-80f3ed4a-cab6-45d2-8896-5e20233cc9b1.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAVCODYLSA53PQK4ZA%2F20240706%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240706T195400Z&X-Amz-Expires=300&X-Amz-Signature=5b063823a57057c4042c15edd1db3edd107e00940fd0e66a2ba1df4e564a2809&X-Amz-SignedHeaders=host&actor_id=46503702&key_id=0&repo_id=432411890)
![demo](./docs-assets/npm-banner.jpeg)

## Usage

Expand Down
Binary file added docs-assets/npm-banner.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 14 additions & 3 deletions prismarine-viewer/viewer/lib/entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,27 @@ function getUsernameTexture (username: string, { fontFamily = 'sans-serif' }: an
const padding = 5
ctx.font = `${fontSize}px ${fontFamily}`

const textWidth = ctx.measureText(username).width + padding * 2
const lines = String(username).split('\n')

let textWidth = 0
for (const line of lines) {
const width = ctx.measureText(line).width + padding * 2
if (width > textWidth) textWidth = width
}

canvas.width = textWidth
canvas.height = fontSize + padding * 2
canvas.height = (fontSize + padding * 2) * lines.length

ctx.fillStyle = 'rgba(0, 0, 0, 0.3)'
ctx.fillRect(0, 0, canvas.width, canvas.height)

ctx.font = `${fontSize}px ${fontFamily}`
ctx.fillStyle = 'white'
ctx.fillText(username, padding, fontSize)
let i = 0
for (const line of lines) {
i++
ctx.fillText(line, padding + (textWidth - ctx.measureText(line).width) / 2, fontSize * i)
}

return canvas
}
Expand Down Expand Up @@ -454,6 +464,7 @@ export class Entities extends EventEmitter {
// ---
// not player
const displayText = entity.metadata?.[3] && this.parseEntityLabel(entity.metadata[2])
|| entity.metadata?.[23] && this.parseEntityLabel(entity.metadata[23]) // text displays
if (entity.name !== 'player' && displayText) {
addNametag({ ...entity, username: displayText }, this.entitiesOptions, this.entities[entity.id].children.find(c => c.name === 'mesh'))
}
Expand Down
4 changes: 2 additions & 2 deletions prismarine-viewer/viewer/lib/entity/EntityMesh.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,8 @@ export const knownNotHandled = [
'item_display', 'item_frame',
'lightning_bolt', 'marker',
'painting', 'spawner_minecart',
'spectral_arrow', 'text_display',
'tnt', 'trader_llama', 'zombie_horse'
'spectral_arrow', 'tnt',
'trader_llama', 'zombie_horse'
]

export const temporaryMap = {
Expand Down
4 changes: 4 additions & 0 deletions prismarine-viewer/viewer/lib/entity/entities.json
Original file line number Diff line number Diff line change
Expand Up @@ -16390,6 +16390,10 @@
},
"render_controllers": ["controller.render.strider"]
},
"text_display": {
"identifier": "minecraft:text_display",
"geometry": {}
},
"trident": {
"identifier": "minecraft:thrown_trident",
"textures": {
Expand Down
2 changes: 1 addition & 1 deletion prismarine-viewer/viewer/lib/mesher/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) {
}
}
if (invisibleBlocks.has(block.name)) continue
if (block.name.includes('_sign') || block.name === 'sign') {
if ((block.name.includes('_sign') || block.name === 'sign') && !world.config.disableSignsMapsSupport) {
const key = `${cursor.x},${cursor.y},${cursor.z}`
const props: any = block.getProperties()
const facingRotationMap = {
Expand Down
4 changes: 3 additions & 1 deletion prismarine-viewer/viewer/lib/mesher/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ export const defaultMesherConfig = {
smoothLighting: true,
outputFormat: 'threeJs' as 'threeJs' | 'webgpu',
textureSize: 1024, // for testing
debugModelVariant: undefined as undefined | number[]
debugModelVariant: undefined as undefined | number[],
clipWorldBelowY: undefined as undefined | number,
disableSignsMapsSupport: false
}

export type MesherConfig = typeof defaultMesherConfig
Expand Down
2 changes: 1 addition & 1 deletion prismarine-viewer/viewer/lib/viewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export class Viewer {

setBlockStateId (pos: Vec3, stateId: number) {
if (!this.world.loadedChunks[`${Math.floor(pos.x / 16) * 16},${Math.floor(pos.z / 16) * 16}`]) {
console.warn('[should be unreachable] setBlockStateId called for unloaded chunk', pos)
console.debug('[should be unreachable] setBlockStateId called for unloaded chunk', pos)
}
this.world.setBlockStateId(pos, stateId)
}
Expand Down
6 changes: 5 additions & 1 deletion prismarine-viewer/viewer/lib/worldrendererCommon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,10 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
console.log('texture loaded')
}

get worldMinYRender () {
return Math.floor(Math.max(this.worldConfig.minY, this.mesherConfig.clipWorldBelowY ?? -Infinity) / 16) * 16
}

addColumn (x: number, z: number, chunk: any, isLightUpdate: boolean) {
if (!this.active) return
if (this.workers.length === 0) throw new Error('workers not initialized yet')
Expand All @@ -330,7 +334,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
// todo optimize
worker.postMessage({ type: 'chunk', x, z, chunk })
}
for (let y = this.worldConfig.minY; y < this.worldConfig.worldHeight; y += 16) {
for (let y = this.worldMinYRender; y < this.worldConfig.worldHeight; y += 16) {
const loc = new Vec3(x, y, z)
this.setSectionDirty(loc)
if (this.neighborChunkUpdates && (!isLightUpdate || this.mesherConfig.smoothLighting)) {
Expand Down
1 change: 1 addition & 0 deletions scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ exports.getSwAdditionalEntries = () => {
'*.png',
'*.woff',
'mesher.js',
'manifest.json',
'worldSaveWorker.js',
`textures/entity/squid/squid.png`,
// everything but not .map
Expand Down
5 changes: 5 additions & 0 deletions scripts/dockerPrepare.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'
import { execSync } from 'child_process'

// write release tag
const commitShort = execSync('git rev-parse --short HEAD').toString().trim()
fs.writeFileSync('./assets/release.json', JSON.stringify({ latestTag: `${commitShort} (docker)` }), 'utf8')

const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'))
delete packageJson.optionalDependencies
Expand Down
20 changes: 20 additions & 0 deletions src/botUtils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { versionToNumber } from 'prismarine-viewer/viewer/prepare/utils'
import * as nbt from 'prismarine-nbt'

export const displayClientChat = (text: string) => {
const message = {
Expand All @@ -18,3 +19,22 @@ export const displayClientChat = (text: string) => {
sender: 'minecraft:chat'
})
}

export const parseFormattedMessagePacket = (arg) => {
if (typeof arg === 'object') {
try {
return {
formatted: nbt.simplify(arg),
plain: ''
}
} catch (err) {
console.warn('Failed to parse formatted message', arg, err)
return {
plain: JSON.stringify(arg)
}
}
}
return {
plain: String(arg)
}
}
9 changes: 5 additions & 4 deletions src/controls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const contro = new ControMax({
selectItem: ['KeyH'] // default will be removed
},
ui: {
toggleFullscreen: ['F11'],
back: [null/* 'Escape' */, 'B'],
leftClick: [null, 'A'],
rightClick: [null, 'Y'],
Expand Down Expand Up @@ -419,6 +420,10 @@ contro.on('trigger', ({ command }) => {
if (command === 'ui.pauseMenu') {
showModal({ reactType: 'pause-screen' })
}

if (command === 'ui.toggleFullscreen') {
void goFullscreen(true)
}
})

contro.on('release', ({ command }) => {
Expand Down Expand Up @@ -742,10 +747,6 @@ window.addEventListener('keydown', (e) => {

// #region experimental debug things
window.addEventListener('keydown', (e) => {
if (e.code === 'F11') {
e.preventDefault()
void goFullscreen(true)
}
if (e.code === 'KeyL' && e.altKey) {
console.clear()
}
Expand Down
14 changes: 14 additions & 0 deletions src/flyingSquidEvents.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { saveServer } from './flyingSquidUtils'
import { watchUnloadForCleanup } from './gameUnload'
import { showModal } from './globalState'
import { options } from './optionsStorage'
import { chatInputValueGlobal } from './react/Chat'
import { showNotification } from './react/NotificationProvider'

Expand All @@ -10,4 +13,15 @@ export default () => {
showModal({ reactType: 'chat' })
})
})

if (options.singleplayerAutoSave) {
const autoSaveInterval = setInterval(() => {
if (options.singleplayerAutoSave) {
void saveServer(true)
}
}, 2000)
watchUnloadForCleanup(() => {
clearInterval(autoSaveInterval)
})
}
}
18 changes: 7 additions & 11 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ import packetsPatcher from './packetsPatcher'
import { mainMenuState } from './react/MainMenuRenderApp'
import { ItemsRenderer } from 'mc-assets/dist/itemsRenderer'
import './mobileShim'
import { parseFormattedMessagePacket } from './botUtils'

window.debug = debug
window.THREE = THREE
Expand Down Expand Up @@ -402,7 +403,9 @@ async function connect (connectOptions: ConnectOptions) {
setLoadingScreenStatus(`Loading data for ${version}`)
if (!document.fonts.check('1em mojangles')) {
// todo instead re-render signs on load
await document.fonts.load('1em mojangles').catch(() => { })
await document.fonts.load('1em mojangles').catch(() => {
console.error('Failed to load font, signs wont be rendered correctly')
})
}
await window._MC_DATA_RESOLVER.promise // ensure data is loaded
await downloadSoundsIfNeeded()
Expand Down Expand Up @@ -457,7 +460,7 @@ async function connect (connectOptions: ConnectOptions) {
flyingSquidEvents()
}

if (connectOptions.authenticatedAccount) username = 'not-used'
if (connectOptions.authenticatedAccount) username = 'you'
let initialLoadingText: string
if (singleplayer) {
initialLoadingText = 'Local server is still starting'
Expand Down Expand Up @@ -637,14 +640,7 @@ async function connect (connectOptions: ConnectOptions) {

bot.on('kicked', (kickReason) => {
console.log('You were kicked!', kickReason)
let kickReasonString = typeof kickReason === 'string' ? kickReason : JSON.stringify(kickReason)
let kickReasonFormatted = undefined as undefined | Record<string, any>
if (typeof kickReason === 'object') {
try {
kickReasonFormatted = nbt.simplify(kickReason)
kickReasonString = ''
} catch {}
}
const { formatted: kickReasonFormatted, plain: kickReasonString } = parseFormattedMessagePacket(kickReason)
setLoadingScreenStatus(`The Minecraft server kicked you. Kick reason: ${kickReasonString}`, true, undefined, undefined, kickReasonFormatted)
destroyAll()
})
Expand Down Expand Up @@ -932,7 +928,7 @@ watchValue(miscUiState, async s => {
const qs = new URLSearchParams(window.location.search)
const moreServerOptions = {} as Record<string, any>
if (qs.has('version')) moreServerOptions.version = qs.get('version')
if (qs.get('singleplayer') === '1') {
if (qs.get('singleplayer') === '1' || qs.get('sp') === '1') {
loadSingleplayer({}, {
worldFolder: undefined,
...moreServerOptions
Expand Down
Loading

0 comments on commit ce601ca

Please sign in to comment.