Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

release #228

Merged
merged 13 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -94,7 +94,7 @@
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}

function addCube(attr, boneId, bone, cube, texWidth = 64, texHeight = 64) {

Check warning on line 97 in prismarine-viewer/viewer/lib/entity/EntityMesh.js

View workflow job for this annotation

GitHub Actions / build-and-deploy

Function 'addCube' has too many parameters (6). Maximum allowed is 4
const cubeRotation = new THREE.Euler(0, 0, 0)
if (cube.rotation) {
cubeRotation.x = -cube.rotation[0] * Math.PI / 180
Expand Down Expand Up @@ -224,8 +224,8 @@
'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 @@ -126,7 +126,7 @@
}))
}

function renderLiquid (world: World, cursor: Vec3, texture: any | undefined, type: number, biome: string, water: boolean, attr: Record<string, any>) {

Check warning on line 129 in prismarine-viewer/viewer/lib/mesher/models.ts

View workflow job for this annotation

GitHub Actions / build-and-deploy

Function 'renderLiquid' has too many parameters (7). Maximum allowed is 4
const heights: number[] = []
for (let z = -1; z <= 1; z++) {
for (let x = -1; x <= 1; x++) {
Expand Down Expand Up @@ -238,7 +238,7 @@

let needSectionRecomputeOnChange = false

function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO: boolean, attr: MesherGeometryOutput, globalMatrix: any, globalShift: any, block: Block, biome: string) {

Check warning on line 241 in prismarine-viewer/viewer/lib/mesher/models.ts

View workflow job for this annotation

GitHub Actions / build-and-deploy

Function 'renderElement' has too many parameters (9). Maximum allowed is 4
const position = cursor
// const key = `${position.x},${position.y},${position.z}`
// if (!globalThis.allowedBlocks.includes(key)) return
Expand Down Expand Up @@ -484,7 +484,7 @@
}
}
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
Loading