a decent release (#225)

This commit is contained in:
Vitaly 2024-11-05 07:08:47 +03:00 committed by GitHub
commit c8416261c6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 421 additions and 187 deletions

View file

@ -20,6 +20,9 @@ jobs:
run: npm install --global vercel pnpm@9.0.4
- name: Pull Vercel Environment Information
run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
- name: Write Release Info
run: |
echo "{\"latestTag\": \"$(git rev-parse --short $GITHUB_SHA)\"}" > assets/release.json
- name: Build Project Artifacts
run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
- run: pnpm build-storybook

View file

@ -20,6 +20,13 @@ jobs:
- run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
- run: node scripts/replaceFavicon.mjs ${{ secrets.FAVICON_MAIN }}
# will install + build to .vercel/output/static
- name: Get Release Info
run: pnpx zardoy-release empty --skip-github
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
id: releaseInfo
- name: Write Release Info
run: echo '${{ toJson(steps.releaseInfo.outputs) }}' > assets/release.json
- run: vercel build --token=${{ secrets.VERCEL_TOKEN }} --prod
- run: pnpm build-storybook
- name: Copy playground files
@ -47,6 +54,10 @@ jobs:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: .vercel/output/static
force_orphan: true
- name: Set publishing config
run: pnpm config set '//registry.npmjs.org/:_authToken' "${NODE_AUTH_TOKEN}"
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- run: pnpm tsx scripts/buildNpmReact.ts ${{ steps.release.outputs.tag }}
if: steps.release.outputs.tag
env:

View file

@ -4,6 +4,15 @@
<meta name="darkreader-lock">
<script>
window.startLoad = Date.now()
// g663 fix: forbid change of string prototype
Object.defineProperty(String.prototype, 'format', {
writable: false,
configurable: false
});
Object.defineProperty(String.prototype, 'replaceAll', {
writable: false,
configurable: false
});
</script>
<!-- // #region initial loader -->
<script async>

View file

@ -67,7 +67,7 @@
"esbuild-plugin-polyfill-node": "^0.3.0",
"express": "^4.18.2",
"filesize": "^10.0.12",
"flying-squid": "npm:@zardoy/flying-squid@^0.0.44",
"flying-squid": "npm:@zardoy/flying-squid@^0.0.47",
"fs-extra": "^11.1.1",
"google-drive-browserfs": "github:zardoy/browserfs#google-drive",
"jszip": "^3.10.1",

30
pnpm-lock.yaml generated
View file

@ -119,8 +119,8 @@ importers:
specifier: ^10.0.12
version: 10.0.12
flying-squid:
specifier: npm:@zardoy/flying-squid@^0.0.44
version: '@zardoy/flying-squid@0.0.44(encoding@0.1.13)'
specifier: npm:@zardoy/flying-squid@^0.0.47
version: '@zardoy/flying-squid@0.0.47(encoding@0.1.13)'
fs-extra:
specifier: ^11.1.1
version: 11.1.1
@ -350,7 +350,7 @@ importers:
version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/75e940a4cd50d89e0ba03db3733d5d704917a3c8(@types/react@18.2.20)(react@18.2.0)
mineflayer:
specifier: github:zardoy/mineflayer
version: https://codeload.github.com/zardoy/mineflayer/tar.gz/519acba455d3387414b81fcaa12e550dcdc4f88e(encoding@0.1.13)
version: https://codeload.github.com/zardoy/mineflayer/tar.gz/ece6755d94931116924874d9f55bc024998cc1ae(encoding@0.1.13)
mineflayer-pathfinder:
specifier: ^2.4.4
version: 2.4.4
@ -3395,8 +3395,8 @@ packages:
resolution: {integrity: sha512-6xm38yGVIa6mKm/DUCF2zFFJhERh/QWp1ufm4cNUvxsONBmfPg8uZ9pZBdOmF6qFGr/HlT6ABBkCSx/dlEtvWg==}
engines: {node: '>=12 <14 || 14.2 - 14.9 || >14.10.0'}
'@zardoy/flying-squid@0.0.44':
resolution: {integrity: sha512-Co1fmBU1FkDD3WxiMzIZ4z2pYKFVeLGfdWJT2qQg0eQ9kP064NteOlI5sNNBjMqedNsZSlQdHzT+jkgcFnaGsQ==}
'@zardoy/flying-squid@0.0.47':
resolution: {integrity: sha512-VUtOqPGZ/20tQEjRLFpbz0taoTMi0GgoUM7002wn8RjuVmowg0pMUjdy0YwcFPGla8z1sOwjsF9cOtU4hQ8pUg==}
engines: {node: '>=8'}
hasBin: true
@ -4052,6 +4052,10 @@ packages:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
chalk@5.3.0:
resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==}
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
change-case@4.1.2:
resolution: {integrity: sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==}
@ -6799,8 +6803,8 @@ packages:
resolution: {integrity: sha512-wSchhS59hK+oPs8tFg847H82YEvxU7zYKdDKj4e5FVo3CxJ74eXJVT+JcFwEvoqFO7kXiQlhJITxEvO13GOSKA==}
engines: {node: '>=18'}
mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/519acba455d3387414b81fcaa12e550dcdc4f88e:
resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/519acba455d3387414b81fcaa12e550dcdc4f88e}
mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/ece6755d94931116924874d9f55bc024998cc1ae:
resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/ece6755d94931116924874d9f55bc024998cc1ae}
version: 4.23.0
engines: {node: '>=18'}
@ -7517,7 +7521,7 @@ packages:
prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/23849d4d24af91f45a5bd38781a6f82d40316c05:
resolution: {tarball: https://codeload.github.com/zardoy/prismarine-block/tar.gz/23849d4d24af91f45a5bd38781a6f82d40316c05}
version: 1.17.1
version: 1.19.0
prismarine-chat@1.10.1:
resolution: {integrity: sha512-XukYcuueuhDxzEXG7r8BZyt6jOObrPPB4JESCgb+/XenB9nExoSHF8eTQWWj8faKPLqm1dRQaYwFJlNBlJZJUw==}
@ -13415,11 +13419,11 @@ snapshots:
'@types/emscripten': 1.39.8
tslib: 1.14.1
'@zardoy/flying-squid@0.0.44(encoding@0.1.13)':
'@zardoy/flying-squid@0.0.47(encoding@0.1.13)':
dependencies:
'@tootallnate/once': 2.0.0
chalk: 5.3.0
change-case: 4.1.2
colors: 1.4.0
diamond-square: https://codeload.github.com/zardoy/diamond-square/tar.gz/cfaad2d1d5909fdfa63c8cc7bc05fb5e87782d71
emit-then: 2.0.0
exit-hook: 2.2.1
@ -13525,7 +13529,7 @@ snapshots:
agent-base@7.1.0:
dependencies:
debug: 4.3.4(supports-color@8.1.1)
debug: 4.3.7
transitivePeerDependencies:
- supports-color
@ -14268,6 +14272,8 @@ snapshots:
ansi-styles: 4.3.0
supports-color: 7.2.0
chalk@5.3.0: {}
change-case@4.1.2:
dependencies:
camel-case: 4.1.2
@ -17821,7 +17827,7 @@ snapshots:
- encoding
- supports-color
mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/519acba455d3387414b81fcaa12e550dcdc4f88e(encoding@0.1.13):
mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/ece6755d94931116924874d9f55bc024998cc1ae(encoding@0.1.13):
dependencies:
minecraft-data: 3.78.0
minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/590dc33fed2100e77ef58e7db716dfc45eb61159(patch_hash=7sh5krubuk2vjuogjioaktvwzi)(encoding@0.1.13)

View file

@ -281,9 +281,14 @@ export class BasePlaygroundScene {
addKeyboardShortcuts () {
document.addEventListener('keydown', (e) => {
if (e.code === 'KeyR' && !e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey) {
this.controls?.reset()
this.resetCamera()
if (!e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey) {
if (e.code === 'KeyR') {
this.controls?.reset()
this.resetCamera()
}
if (e.code === 'KeyE') {
worldView?.setBlockStateId(this.targetPos, this.world.getBlockStateId(this.targetPos))
}
}
})
document.addEventListener('visibilitychange', () => {
@ -297,6 +302,9 @@ export class BasePlaygroundScene {
})
const updateKeys = () => {
if (pressedKeys.has('ControlLeft') || pressedKeys.has('MetaLeft')) {
return
}
// if (typeof viewer === 'undefined') return
// Create a vector that points in the direction the camera is looking
const direction = new THREE.Vector3(0, 0, 0)

View file

@ -4,7 +4,7 @@ import * as scenes from './scenes'
const qsScene = new URLSearchParams(window.location.search).get('scene')
const Scene: typeof BasePlaygroundScene = qsScene ? scenes[qsScene] : scenes.main
playgroundGlobalUiState.scenes = ['main', 'railsCobweb', 'floorRandom', 'lightingStarfield', 'transparencyIssue', 'entities', 'frequentUpdates']
playgroundGlobalUiState.scenes = ['main', 'railsCobweb', 'floorRandom', 'lightingStarfield', 'transparencyIssue', 'entities', 'frequentUpdates', 'slabsOptimization']
playgroundGlobalUiState.selected = qsScene ?? 'main'
const scene = new Scene()

View file

@ -7,3 +7,4 @@ export { default as transparencyIssue } from './transparencyIssue'
export { default as rotationIssue } from './rotationIssue'
export { default as entities } from './entities'
export { default as frequentUpdates } from './frequentUpdates'
export { default as slabsOptimization } from './slabsOptimization'

View file

@ -0,0 +1,15 @@
import { BasePlaygroundScene } from '../baseScene'
export default class extends BasePlaygroundScene {
expectedNumberOfFaces = 30
setupWorld () {
this.addWorldBlock(0, 1, 0, 'stone_slab')
this.addWorldBlock(0, 0, 0, 'stone')
this.addWorldBlock(0, -1, 0, 'stone_slab', { type: 'top', waterlogged: false })
this.addWorldBlock(0, -1, -1, 'stone_slab', { type: 'top', waterlogged: false })
this.addWorldBlock(0, -1, 1, 'stone_slab', { type: 'top', waterlogged: false })
this.addWorldBlock(-1, -1, 0, 'stone_slab', { type: 'top', waterlogged: false })
this.addWorldBlock(1, -1, 0, 'stone_slab', { type: 'top', waterlogged: false })
}
}

View file

@ -46,7 +46,7 @@ export const appAndRendererSharedConfig = () => defineConfig({
},
server: {
htmlFallback: false,
publicDir: false,
// publicDir: false,
headers: {
// enable shared array buffer
'Cross-Origin-Opener-Policy': 'same-origin',

View file

@ -10,6 +10,7 @@ import stevePng from 'mc-assets/dist/other-textures/latest/entity/player/wide/st
import { NameTagObject } from 'skinview3d/libs/nametag'
import { flat, fromFormattedString } from '@xmcl/text-component'
import mojangson from 'mojangson'
import { snakeCase } from 'change-case'
import * as Entity from './entity/EntityMesh'
import { WalkingGeneralSwing } from './entity/animations'
import externalTexturesJson from './entity/externalTextures.json'
@ -63,11 +64,13 @@ const addNametag = (entity, options, mesh) => {
// todo cleanup
const nametags = {}
const isFirstUpperCase = (str) => str.charAt(0) === str.charAt(0).toUpperCase()
function getEntityMesh (entity, scene, options, overrides) {
if (entity.name) {
try {
// https://github.com/PrismarineJS/prismarine-viewer/pull/410
const entityName = entity.name.toLowerCase()
const entityName = (isFirstUpperCase(entity.name) ? snakeCase(entity.name) : entity.name).toLowerCase()
const e = new Entity.EntityMesh('1.16.4', entityName, scene, overrides)
if (e.mesh) {
@ -340,7 +343,6 @@ export class Entities extends EventEmitter {
}
update (entity: import('prismarine-entity').Entity & { delete?; pos }, overrides) {
console.log('entity', entity)
const isPlayerModel = entity.name === 'player'
if (entity.name === 'zombie' || entity.name === 'zombie_villager' || entity.name === 'husk') {
overrides.texture = `textures/1.16.4/entity/${entity.name === 'zombie_villager' ? 'zombie_villager/zombie_villager.png' : `zombie/${entity.name}.png`}`

View file

@ -65,6 +65,7 @@ function setSectionDirty (pos, value = true) {
const softCleanup = () => {
// clean block cache and loaded chunks
world = new World(world.config.version)
globalThis.world = world
}
const handleMessage = data => {
@ -75,8 +76,10 @@ const handleMessage = data => {
}
if (data.config) {
if (data.type === 'mesherData' && allDataReady) {
world = undefined as any // reset models
if (data.type === 'mesherData' && world) {
// reset models
world.blockCache = {}
world.erroredBlockModel = undefined
}
world ??= new World(data.config.version)

View file

@ -196,13 +196,53 @@ function renderLiquid (world: World, cursor: Vec3, texture: any | undefined, typ
}
}
let needRecompute = false
const identicalCull = (currentElement: BlockElement, neighbor: Block, direction: Vec3) => {
const dirStr = `${direction.x},${direction.y},${direction.z}`
const lookForOppositeSide = {
'0,1,0': 'down',
'0,-1,0': 'up',
'1,0,0': 'east',
'-1,0,0': 'west',
'0,0,1': 'south',
'0,0,-1': 'north',
}[dirStr]!
const elemCompareForm = {
'0,1,0': (e: BlockElement) => `${e.from[0]},${e.from[2]}:${e.to[0]},${e.to[2]}`,
'0,-1,0': (e: BlockElement) => `${e.to[0]},${e.to[2]}:${e.from[0]},${e.from[2]}`,
'1,0,0': (e: BlockElement) => `${e.from[2]},${e.from[1]}:${e.to[2]},${e.to[1]}`,
'-1,0,0': (e: BlockElement) => `${e.to[2]},${e.to[1]}:${e.from[2]},${e.from[1]}`,
'0,0,1': (e: BlockElement) => `${e.from[1]},${e.from[2]}:${e.to[1]},${e.to[2]}`,
'0,0,-1': (e: BlockElement) => `${e.to[1]},${e.to[2]}:${e.from[1]},${e.from[2]}`,
}[dirStr]!
const elementEdgeValidator = {
'0,1,0': (e: BlockElement) => currentElement.from[1] === 0 && e.to[2] === 16,
'0,-1,0': (e: BlockElement) => currentElement.from[1] === 0 && e.to[2] === 16,
'1,0,0': (e: BlockElement) => currentElement.from[0] === 0 && e.to[1] === 16,
'-1,0,0': (e: BlockElement) => currentElement.from[0] === 0 && e.to[1] === 16,
'0,0,1': (e: BlockElement) => currentElement.from[2] === 0 && e.to[0] === 16,
'0,0,-1': (e: BlockElement) => currentElement.from[2] === 0 && e.to[0] === 16,
}[dirStr]!
const useVar = 0
const models = neighbor.models?.map(m => m[useVar] ?? m[0]) ?? []
// TODO we should support it! rewrite with optimizing general pipeline
if (models.some(m => m.x || m.y || m.z)) return
for (const model of models) {
for (const element of model.elements ?? []) {
// todo check alfa on texture
if (element.faces[lookForOppositeSide]?.cullface && elemCompareForm(currentElement) === elemCompareForm(element) && elementEdgeValidator(element)) {
return true
}
}
}
}
let needSectionRecomputeOnChange = false
function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO: boolean, attr: MesherGeometryOutput, globalMatrix: any, globalShift: any, block: Block, biome: string) {
const position = cursor
// const key = `${position.x},${position.y},${position.z}`
// if (!globalThis.allowedBlocks.includes(key)) return
const cullIfIdentical = block.name.includes('glass')
const cullIfIdentical = block.name.includes('glass') || block.name.includes('ice')
// eslint-disable-next-line guard-for-in
for (const face in element.faces) {
@ -211,12 +251,12 @@ function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO:
const dir = matmul3(globalMatrix, elemFaces[face].dir)
if (eFace.cullface) {
const neighbor = world.getBlock(cursor.plus(new Vec3(...dir)))
const neighbor = world.getBlock(cursor.plus(new Vec3(...dir)), blockProvider, {})
if (neighbor) {
if (cullIfIdentical && neighbor.type === block.type) continue
if (!neighbor.transparent && isCube(neighbor)) continue
if (cullIfIdentical && neighbor.stateId === block.stateId) continue
if (!neighbor.transparent && (isCube(neighbor) || identicalCull(element, neighbor, new Vec3(...dir)))) continue
} else {
needRecompute = true
needSectionRecomputeOnChange = true
continue
}
}
@ -310,6 +350,8 @@ function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO:
}
let light = 1
const { smoothLighting } = world.config
// const smoothLighting = true
if (doAO) {
const dx = pos[0] * 2 - 1
const dy = pos[1] * 2 - 1
@ -321,14 +363,25 @@ function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO:
const side2 = world.getBlock(cursor.offset(...side2Dir))
const corner = world.getBlock(cursor.offset(...cornerDir))
let cornerLightResult = 15
// eslint-disable-next-line no-constant-condition, sonarjs/no-gratuitous-expressions
if (/* world.config.smoothLighting */false) { // todo fix
const side1Light = world.getLight(cursor.plus(new Vec3(...side1Dir)), true)
const side2Light = world.getLight(cursor.plus(new Vec3(...side2Dir)), true)
const cornerLight = world.getLight(cursor.plus(new Vec3(...cornerDir)), true)
let cornerLightResult = baseLight * 15
if (smoothLighting) {
const dirVec = new Vec3(...dir)
const getVec = (v: Vec3) => {
for (const coord of ['x', 'y', 'z']) {
if (Math.abs(dirVec[coord]) > 0) v[coord] = 0
}
return v.plus(dirVec)
}
const side1LightDir = getVec(new Vec3(...side1Dir))
const side1Light = world.getLight(cursor.plus(side1LightDir))
const side2DirLight = getVec(new Vec3(...side2Dir))
const side2Light = world.getLight(cursor.plus(side2DirLight))
const cornerLightDir = getVec(new Vec3(...cornerDir))
const cornerLight = world.getLight(cursor.plus(cornerLightDir))
// interpolate
cornerLightResult = (side1Light + side2Light + cornerLight) / 3
const lights = [side1Light, side2Light, cornerLight, baseLight * 15]
cornerLightResult = lights.reduce((acc, cur) => acc + cur, 0) / lights.length
}
const side1Block = world.shouldMakeAo(side1) ? 1 : 0
@ -344,7 +397,7 @@ function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO:
}
if (!needTiles) {
attr.colors.push(baseLight * tint[0] * light, baseLight * tint[1] * light, baseLight * tint[2] * light)
attr.colors.push(tint[0] * light, tint[1] * light, tint[2] * light)
}
}
@ -391,7 +444,6 @@ const invisibleBlocks = new Set(['air', 'cave_air', 'void_air', 'barrier'])
const isBlockWaterlogged = (block: Block) => block.getProperties().waterlogged === true || block.getProperties().waterlogged === 'true'
let unknownBlockModel: BlockModelPartsResolved
let erroredBlockModel: BlockModelPartsResolved
export function getSectionGeometry (sx, sy, sz, world: World) {
let delayedRender = [] as Array<() => void>
@ -421,7 +473,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) {
for (cursor.y = sy; cursor.y < sy + 16; cursor.y++) {
for (cursor.z = sz; cursor.z < sz + 16; cursor.z++) {
for (cursor.x = sx; cursor.x < sx + 16; cursor.x++) {
const block = world.getBlock(cursor)!
let block = world.getBlock(cursor, blockProvider, attr)!
if (!invisibleBlocks.has(block.name)) {
const highest = attr.highestBlocks[`${cursor.x},${cursor.z}`]
if (!highest || highest.y < cursor.y) {
@ -459,6 +511,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) {
if (block.models && JSON.stringify(block._originalProperties) !== JSON.stringify(block._properties)) {
// recompute models
block.models = undefined
block = world.getBlock(cursor, blockProvider, attr)!
}
} else {
block._properties = block._originalProperties ?? block._properties
@ -481,37 +534,6 @@ export function getSectionGeometry (sx, sy, sz, world: World) {
if (block.name !== 'water' && block.name !== 'lava' && !invisibleBlocks.has(block.name)) {
// cache
let { models } = block
if (block.models === undefined) {
const props = block.getProperties()
try {
// fixme
if (world.preflat) {
if (block.name === 'cobblestone_wall') {
props.up = 'true'
for (const key of ['north', 'south', 'east', 'west']) {
const val = props[key]
if (val === 'false' || val === 'true') {
props[key] = val === 'true' ? 'low' : 'none'
}
}
}
}
models = blockProvider.getAllResolvedModels0_1({
name: block.name,
properties: props,
}, world.preflat)! // fixme! this is a hack (also need a setting for all versions)
if (!models.length) {
console.debug('[mesher] block to render not found', block.name, props)
models = null
}
} catch (err) {
models ??= erroredBlockModel
console.error(`Critical assets error. Unable to get block model for ${block.name}[${JSON.stringify(props)}]: ` + err.message, err.stack)
attr.hadErrors = true
}
}
block.models = models ?? null
models ??= unknownBlockModel
@ -607,7 +629,6 @@ export const setBlockStatesData = (blockstatesModels, blocksAtlas: any, _needTil
globalThis.blockProvider = blockProvider
if (useUnknownBlockModel) {
unknownBlockModel = blockProvider.getAllResolvedModels0_1({ name: 'unknown', properties: {} })
erroredBlockModel = blockProvider.getAllResolvedModels0_1({ name: 'errored', properties: {} })
}
needTiles = _needTiles

View file

@ -119,34 +119,12 @@ function renderElement (element: BlockElement, doAO: boolean, attr, globalMatrix
let light = 1
if (doAO) {
const dx = pos[0] * 2 - 1
const dy = pos[1] * 2 - 1
const dz = pos[2] * 2 - 1
const cornerDir = matmul3(globalMatrix, [dx, dy, dz])
const side1Dir = matmul3(globalMatrix, [dx * mask1[0], dy * mask1[1], dz * mask1[2]])
const side2Dir = matmul3(globalMatrix, [dx * mask2[0], dy * mask2[1], dz * mask2[2]])
// const side1 = world.getBlock(cursor.offset(...side1Dir))
// const side2 = world.getBlock(cursor.offset(...side2Dir))
// const corner = world.getBlock(cursor.offset(...cornerDir))
const cornerLightResult = 15
// if (/* world.config.smoothLighting */false) { // todo fix
// const side1Light = world.getLight(cursor.plus(new Vec3(...side1Dir)), true)
// const side2Light = world.getLight(cursor.plus(new Vec3(...side2Dir)), true)
// const cornerLight = world.getLight(cursor.plus(new Vec3(...cornerDir)), true)
// // interpolate
// cornerLightResult = (side1Light + side2Light + cornerLight) / 3
// }
// const side1Block = world.shouldMakeAo(side1) ? 1 : 0
// const side2Block = world.shouldMakeAo(side2) ? 1 : 0
// const cornerBlock = world.shouldMakeAo(corner) ? 1 : 0
const side1Block = 0
const side2Block = 0
const cornerBlock = 0
// TODO: correctly interpolate ao light based on pos (evaluate once for each corner of the block)
const ao = (side1Block && side2Block) ? 0 : (3 - (side1Block + side2Block + cornerBlock))
// todo light should go upper on lower blocks
light = (ao + 1) / 4 * (cornerLightResult / 15)

View file

@ -1,3 +1,4 @@
import { BlockNames } from '../../../../../src/mcDataTypes'
import { setup } from './mesherTester'
const addPositions = [
@ -10,8 +11,10 @@ const addPositions = [
[[0, 0, -1], 'stone'],
] as const
const { mesherWorld, getGeometry, pos, mcData } = setup('1.18.1', addPositions as any)
const { mesherWorld, getGeometry, pos, mcData } = setup('1.21.1', addPositions as any)
// mesherWorld.setBlockStateId(pos, mcData.blocksByName.soul_sand.defaultState)
// mesherWorld.setBlockStateId(pos, 712)
// mesherWorld.setBlockStateId(pos, mcData.blocksByName.stone_slab.defaultState)
mesherWorld.setBlockStateId(pos, 11_225)
// console.log(getGeometry().centerTileNeighbors)
console.log(getGeometry().centerTileNeighbors)

View file

@ -38,6 +38,7 @@ export class World {
blockCache = {}
biomeCache: { [id: number]: mcData.Biome }
preflat: boolean
erroredBlockModel?: BlockModelPartsResolved
constructor (version) {
this.Chunk = Chunks(version) as any
@ -47,6 +48,8 @@ export class World {
}
getLight (pos: Vec3, isNeighbor = false, skipMoreChecks = false, curBlockName = '') {
// for easier testing
if (!(pos instanceof Vec3)) pos = new Vec3(...pos as [number, number, number])
const { enableLighting, skyLight } = this.config
if (!enableLighting) return 15
// const key = `${pos.x},${pos.y},${pos.z}`
@ -70,8 +73,10 @@ export class World {
this.getLight(pos.offset(1, 0, 0), undefined, true),
this.getLight(pos.offset(-1, 0, 0), undefined, true)
].filter(x => x !== 2)
const min = Math.min(...lights)
result = min
if (lights.length) {
const min = Math.min(...lights)
result = min
}
}
if (isNeighbor && result === 2) result = 15 // TODO
return result
@ -108,7 +113,7 @@ export class World {
return this.getColumn(Math.floor(pos.x / 16) * 16, Math.floor(pos.z / 16) * 16)
}
getBlock (pos: Vec3): WorldBlock | null {
getBlock (pos: Vec3, blockProvider?, attr?): WorldBlock | null {
// for easier testing
if (!(pos instanceof Vec3)) pos = new Vec3(...pos as [number, number, number])
const key = columnKey(Math.floor(pos.x / 16) * 16, Math.floor(pos.z / 16) * 16)
@ -122,8 +127,7 @@ export class World {
const stateId = column.getBlockStateId(locInChunk)
if (!this.blockCache[stateId]) {
const b = column.getBlock(locInChunk)
//@ts-expect-error
const b = column.getBlock(locInChunk) as unknown as WorldBlock
b.isCube = isCube(b.shapes)
this.blockCache[stateId] = b
Object.defineProperty(b, 'position', {
@ -132,7 +136,6 @@ export class World {
}
})
if (this.preflat) {
//@ts-expect-error
b._properties = {}
const namePropsStr = legacyJson.blocks[b.type + ':' + b.metadata] || findClosestLegacyBlockFallback(b.type, b.metadata, pos)
@ -145,7 +148,6 @@ export class World {
if (!isNaN(val)) val = parseInt(val, 10)
return [key, val]
}))
//@ts-expect-error
b._properties = newProperties
}
}
@ -153,6 +155,40 @@ export class World {
}
const block = this.blockCache[stateId]
if (block.models === undefined && blockProvider) {
if (!attr) throw new Error('attr is required')
const props = block.getProperties()
try {
// fixme
if (this.preflat) {
if (block.name === 'cobblestone_wall') {
props.up = 'true'
for (const key of ['north', 'south', 'east', 'west']) {
const val = props[key]
if (val === 'false' || val === 'true') {
props[key] = val === 'true' ? 'low' : 'none'
}
}
}
}
block.models = blockProvider.getAllResolvedModels0_1({
name: block.name,
properties: props,
}, this.preflat)! // fixme! this is a hack (also need a setting for all versions)
if (!block.models!.length) {
console.debug('[mesher] block to render not found', block.name, props)
block.models = null
}
} catch (err) {
this.erroredBlockModel ??= blockProvider.getAllResolvedModels0_1({ name: 'errored', properties: {} })
block.models ??= this.erroredBlockModel
console.error(`Critical assets error. Unable to get block model for ${block.name}[${JSON.stringify(props)}]: ` + err.message, err.stack)
attr.hadErrors = true
}
}
if (block.name === 'flowing_water') block.name = 'water'
if (block.name === 'flowing_lava') block.name = 'lava'
// block.position = loc // it overrides position of all currently loaded blocks

View file

@ -98,7 +98,7 @@ export class Viewer {
}
setBlockStateId (pos: Vec3, stateId: number) {
if (!this.world.loadedChunks[`${Math.floor(pos.x / 16)},${Math.floor(pos.z / 16)}`]) {
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)
}
this.world.setBlockStateId(pos, stateId)

View file

@ -21,6 +21,15 @@ const buildingVersion = new Date().toISOString().split(':')[0]
const dev = process.env.NODE_ENV === 'development'
let releaseTag
let releaseChangelog
if (fs.existsSync('./assets/release.json')) {
const releaseJson = JSON.parse(fs.readFileSync('./assets/release.json', 'utf8'))
releaseTag = releaseJson.latestTag
releaseChangelog = releaseJson.changelog?.replace(/<!-- bump-type:[\w]+ -->/, '')
}
// base options are in ./prismarine-viewer/rsbuildSharedConfig.ts
const appConfig = defineConfig({
html: {
@ -47,7 +56,9 @@ const appConfig = defineConfig({
'process.env.MAIN_MENU_LINKS': JSON.stringify(process.env.MAIN_MENU_LINKS),
'process.env.GITHUB_URL':
JSON.stringify(`https://github.com/${process.env.GITHUB_REPOSITORY || `${process.env.VERCEL_GIT_REPO_OWNER}/${process.env.VERCEL_GIT_REPO_SLUG}`}`),
'process.env.DEPS_VERSIONS': JSON.stringify({})
'process.env.DEPS_VERSIONS': JSON.stringify({}),
'process.env.RELEASE_TAG': JSON.stringify(releaseTag),
'process.env.RELEASE_CHANGELOG': JSON.stringify(releaseChangelog),
},
},
server: {
@ -80,6 +91,9 @@ const appConfig = defineConfig({
fs.copyFileSync('./assets/playground.html', './dist/playground.html')
fs.copyFileSync('./assets/manifest.json', './dist/manifest.json')
fs.copyFileSync('./assets/loading-bg.jpg', './dist/loading-bg.jpg')
if (fs.existsSync('./assets/release.json')) {
fs.copyFileSync('./assets/release.json', './dist/release.json')
}
const configJson = JSON.parse(fs.readFileSync('./config.json', 'utf8'))
if (dev) {
configJson.defaultProxy = ':8080'

View file

@ -148,6 +148,13 @@ fs.promises.readdir(path.resolve(__dirname, '../src/react')).then(async (files)
fs.copyFileSync(path.resolve(__dirname, '../README.NPM.MD'), path.resolve(__dirname, '../dist-npm/README.md'))
if (version !== '0.0.0-dev') {
execSync('npm publish', { cwd: path.resolve(__dirname, '../dist-npm') })
execSync('npm publish', {
cwd: path.resolve(__dirname, '../dist-npm'),
env: {
...process.env,
NPM_TOKEN: process.env.NPM_TOKEN,
NODE_AUTH_TOKEN: process.env.NPM_TOKEN
}
})
}
})

View file

@ -42,8 +42,8 @@ const versionToNumber = (ver) => {
return +`${x.padStart(2, '0')}${y.padStart(2, '0')}${z.padStart(2, '0')}`
}
// if not included here (even as {}) will not be bundled & accessible!
const compressedOutput = false
// if not included here (even as {}) will not be bundled & accessible!
// const dataTypeBundling = {
// protocol: {
// // ignoreRemoved: true,
@ -57,6 +57,18 @@ const dataTypeBundling = {
},
blocks: {
arrKey: 'name',
processData (current, prev) {
for (const block of current) {
if (block.transparent) {
const forceOpaque = block.name.includes('shulker_box') || block.name.match(/^double_.+_slab\d?$/) || ['melon_block', 'lit_pumpkin', 'lit_redstone_ore', 'lit_furnace'].includes(block.name)
const prevBlock = prev?.find(x => x.name === block.name);
if (forceOpaque || (prevBlock && !prevBlock.transparent)) {
block.transparent = false
}
}
}
}
// ignoreRemoved: true,
// genChanges (source, diff) {
// const diffs = {}
@ -164,6 +176,7 @@ for (const [i, [version, dataSet]] of versionsArr.reverse().entries()) {
diffSources[dataType] = new JsonOptimizer(config.arrKey, config.ignoreChanges, config.ignoreRemoved)
}
try {
config.processData?.(dataRaw, previousData[dataType])
diffSources[dataType].recordDiff(version, dataRaw)
injectCode = `restoreDiff(sources, ${JSON.stringify(dataType)}, ${JSON.stringify(version)})`
} catch (err) {

View file

@ -12,7 +12,7 @@ export const displayClientChat = (text: string) => {
})
return
}
bot._client.write('chat', {
bot._client.emit('chat', {
message: JSON.stringify(message),
position: 0,
sender: 'minecraft:chat'

View file

@ -16,6 +16,7 @@ browserfs.install(window)
const defaultMountablePoints = {
'/world': { fs: 'LocalStorage' }, // will be removed in future
'/data': { fs: 'IndexedDB' },
'/resourcepack': { fs: 'InMemory' }, // temporary storage for currently loaded resource pack
}
browserfs.configure({
fs: 'MountableFileSystem',

View file

@ -156,6 +156,7 @@ let lastCommandTrigger = null as { command: string, time: number } | null
const secondActionActivationTimeout = 300
const secondActionCommands = {
'general.jump' () {
// if (bot.game.gameMode === 'spectator') return
toggleFly()
},
'general.forward' () {
@ -475,7 +476,7 @@ export const f3Keybinds = [
// TODO!
if (resourcePackState.resourcePackInstalled || loadedGameState.usingServerResourcePack) {
showNotification('Reloading textures...')
await completeTexturePackInstall('default', 'default')
await completeTexturePackInstall('default', 'default', loadedGameState.usingServerResourcePack)
}
},
mobileTitle: 'Reload Textures'
@ -645,6 +646,17 @@ export const onBotCreate = () => {
}
allowFlying = !!(flags & 4)
})
const gamemodeCheck = () => {
if (bot.game.gameMode === 'spectator') {
toggleFly(true, false)
}
}
bot.on('game', () => {
gamemodeCheck()
})
bot.on('login', () => {
gamemodeCheck()
})
}
const standardAirborneAcceleration = 0.02

View file

@ -59,7 +59,7 @@ export const customCommandsConfig = {
}
],
handler ([setting, action, value]) {
if (action === 'toggle') {
if (action === 'toggle' || action === undefined) {
const value = options[setting]
const config = tryFindOptionConfig(setting)
if (config && 'values' in config && config.values) {

View file

@ -10,6 +10,7 @@ import { onGameLoad } from './inventoryWindows'
import { supportedVersions } from 'minecraft-protocol'
import protocolMicrosoftAuth from 'minecraft-protocol/src/client/microsoftAuth'
import microsoftAuthflow from './microsoftAuthflow'
import nbt from 'prismarine-nbt'
import 'core-js/features/array/at'
import 'core-js/features/promise/with-resolvers'
@ -387,9 +388,9 @@ async function connect (connectOptions: ConnectOptions) {
Object.assign(serverOptions, connectOptions.serverOverridesFlat ?? {})
window._LOAD_MC_DATA() // start loading data (if not loaded yet)
const downloadMcData = async (version: string) => {
if (connectOptions.authenticatedAccount && versionToNumber(version) < versionToNumber('1.19.4')) {
if (connectOptions.authenticatedAccount && (versionToNumber(version) < versionToNumber('1.19.4') || versionToNumber(version) >= versionToNumber('1.21'))) {
// todo support it (just need to fix .export crash)
throw new Error('Microsoft authentication is only supported in 1.19.4 and above (at least for now)')
throw new Error('Microsoft authentication is only supported on 1.19.4 - 1.20.6 (at least for now)')
}
// todo expose cache
@ -504,6 +505,7 @@ async function connect (connectOptions: ConnectOptions) {
sessionServer: authData?.sessionEndpoint?.toString(),
auth: connectOptions.authenticatedAccount ? async (client, options) => {
authData!.setOnMsaCodeCallback(options.onMsaCode)
authData?.setConnectingVersion(client.version)
//@ts-expect-error
client.authflow = authData!.authFlow
try {
@ -634,8 +636,16 @@ async function connect (connectOptions: ConnectOptions) {
bot.on('error', handleError)
bot.on('kicked', (kickReason) => {
console.log('User was kicked!', kickReason)
setLoadingScreenStatus(`The Minecraft server kicked you. Kick reason: ${typeof kickReason === 'object' ? JSON.stringify(kickReason) : kickReason}`, true)
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 {}
}
setLoadingScreenStatus(`The Minecraft server kicked you. Kick reason: ${kickReasonString}`, true, undefined, undefined, kickReasonFormatted)
destroyAll()
})

View file

@ -54,9 +54,22 @@ export const onGameLoad = (onLoad) => {
PrismarineItem = PItem(version)
const mapWindowType = (type: string, inventoryStart: number) => {
if (type === 'minecraft:container') {
if (inventoryStart === 45 - 9 * 4) return 'minecraft:generic_9x1'
if (inventoryStart === 45 - 9 * 3) return 'minecraft:generic_9x2'
if (inventoryStart === 45 - 9 * 2) return 'minecraft:generic_9x3'
if (inventoryStart === 45 - 9) return 'minecraft:generic_9x4'
if (inventoryStart === 45) return 'minecraft:generic_9x5'
if (inventoryStart === 45 + 9) return 'minecraft:generic_9x6'
}
return type
}
bot.on('windowOpen', (win) => {
if (implementedContainersGuiMap[win.type]) {
openWindow(implementedContainersGuiMap[win.type])
const implementedWindow = implementedContainersGuiMap[mapWindowType(win.type as string, win.inventoryStart)]
if (implementedWindow) {
openWindow(implementedWindow)
} else if (options.unimplementedContainers) {
openWindow('ChestWin')
} else {
@ -205,7 +218,7 @@ export const getItemNameRaw = (item: Pick<import('prismarine-item').Item, 'nbt'>
const customName = itemNbt.display?.Name
if (!customName) return
try {
const parsed = mojangson.simplify(mojangson.parse(customName))
const parsed = customName.startsWith('{') && customName.endsWith('}') ? mojangson.simplify(mojangson.parse(customName)) : fromFormattedString(customName)
if (parsed.extra) {
return parsed as Record<string, any>
} else {

View file

@ -12,6 +12,7 @@ export const getProxyDetails = async (proxyBaseUrl: string) => {
export default async ({ tokenCaches, proxyBaseUrl, setProgressText = (text) => { }, setCacheResult, connectingServer }) => {
let onMsaCodeCallback
let connectingVersion = ''
// const authEndpoint = 'http://localhost:3000/'
// const sessionEndpoint = 'http://localhost:3000/session'
let authEndpoint: URL | undefined
@ -39,7 +40,8 @@ export default async ({ tokenCaches, proxyBaseUrl, setProgressText = (text) => {
body: JSON.stringify({
...tokenCaches,
// important to set this param and not fake it as auth server might reject the request otherwise
connectingServer
connectingServer,
connectingServerVersion: connectingVersion
}),
}).then(async response => {
if (!response.ok) {
@ -80,6 +82,9 @@ export default async ({ tokenCaches, proxyBaseUrl, setProgressText = (text) => {
})
if (!window.crypto && !isPageSecure()) throw new Error('Crypto API is available only in secure contexts. Be sure to use https!')
const restoredData = await restoreData(result)
if (!restoredData?.certificates?.profileKeys?.privatePEM) {
throw new Error(`Authentication server issue: it didn't return auth data. Most probably because the auth request was rejected by the end authority and retrying won't help until the issue is resolved.`)
}
restoredData.certificates.profileKeys.private = restoredData.certificates.profileKeys.privatePEM
return restoredData
}
@ -89,6 +94,9 @@ export default async ({ tokenCaches, proxyBaseUrl, setProgressText = (text) => {
sessionEndpoint,
setOnMsaCodeCallback (callback) {
onMsaCodeCallback = callback
},
setConnectingVersion (version) {
connectingVersion = version
}
}
}

View file

@ -72,7 +72,7 @@ export const guiOptionsScheme: {
dayCycleAndLighting: {
text: 'Day Cycle',
},
// smoothLighting: {},
smoothLighting: {},
newVersionsLighting: {
text: 'Lighting in newer versions',
},
@ -164,7 +164,7 @@ export const guiOptionsScheme: {
}
if (choice === 'Enable') {
options.enabledResourcepack = name
await completeTexturePackInstall(name, name)
await completeTexturePackInstall(name, name, false)
return
}
if (choice === 'Uninstall') {

View file

@ -10,31 +10,30 @@ export default ({
hideDots = false,
lastStatus = '',
backAction = undefined as undefined | (() => void),
description = '',
description = '' as string | JSX.Element,
actionsSlot = null as React.ReactNode | null,
children
}) => {
const [loadingDots, setLoadingDots] = useState('')
const [loadingDotIndex, setLoadingDotIndex] = useState(0)
useEffect(() => {
const statusRunner = async () => {
const timer = async (ms) => new Promise((resolve) => { setTimeout(resolve, ms) })
const load = async () => {
// eslint-disable-next-line no-constant-condition
while (true) {
setLoadingDotIndex(i => (i + 1) % 4)
await timer(500) // eslint-disable-line no-await-in-loop
}
}
void load()
}
void statusRunner()
}, [])
const statusRunner = async () => {
const array = ['.', '..', '...', '']
const timer = async (ms) => new Promise((resolve) => { setTimeout(resolve, ms) })
const load = async () => {
// eslint-disable-next-line no-constant-condition
for (let i = 0; true; i = (i + 1) % array.length) {
setLoadingDots(array[i])
await timer(500) // eslint-disable-line no-await-in-loop
}
}
void load()
}
return (
<Screen
@ -48,8 +47,17 @@ export default ({
>
{status}
</span>
{isError || hideDots ? '' : loadingDots}
<p className={styles['potential-problem']}>{description}</p>
<div style={{ display: 'inline-flex', gap: '1px', }} hidden={hideDots || isError}>
{
[...'...'].map((dot, i) => {
return <span
key={i} style={{
visibility: loadingDotIndex <= i ? 'hidden' : 'visible',
}}>{dot}</span>
})
}
</div>
<p className={styles.description}>{description}</p>
<p className={styles['last-status']}>{lastStatus ? `Last status: ${lastStatus}` : lastStatus}</p>
</>
}

View file

@ -15,6 +15,8 @@ import Button from './Button'
import { AuthenticatedAccount, updateAuthenticatedAccountData, updateLoadedServerData } from './ServersListProvider'
import { showOptionsModal } from './SelectOption'
import LoadingChunks from './LoadingChunks'
import MessageFormatted from './MessageFormatted'
import MessageFormattedString from './MessageFormattedString'
const initialState = {
status: '',
@ -25,7 +27,8 @@ const initialState = {
hideDots: false,
loadingChunksData: null as null | Record<string, string>,
loadingChunksDataPlayerChunk: null as null | { x: number, z: number },
isDisplaying: false
isDisplaying: false,
minecraftJsonMessage: null as null | Record<string, any>
}
export const appStatusState = proxy(initialState)
export const resetAppStatusState = () => {
@ -37,7 +40,7 @@ export const lastConnectOptions = {
}
export default () => {
const { isError, lastStatus, maybeRecoverable, status, hideDots, descriptionHint, loadingChunksData, loadingChunksDataPlayerChunk } = useSnapshot(appStatusState)
const { isError, lastStatus, maybeRecoverable, status, hideDots, descriptionHint, loadingChunksData, loadingChunksDataPlayerChunk, minecraftJsonMessage } = useSnapshot(appStatusState)
const { active: replayActive } = useSnapshot(packetsReplaceSessionState)
const isOpen = useIsModalActive('app-status')
@ -95,7 +98,11 @@ export default () => {
isError={isError || appStatusState.status === ''} // display back button if status is empty as probably our app is errored
hideDots={hideDots}
lastStatus={lastStatus}
description={displayAuthButton ? '' : (isError ? guessProblem(status) : '') || descriptionHint}
description={<>{
displayAuthButton ? '' : (isError ? guessProblem(status) : '') || descriptionHint
}{
minecraftJsonMessage && <MessageFormattedString message={minecraftJsonMessage} />
}</>}
backAction={maybeRecoverable ? () => {
resetAppStatusState()
miscUiState.gameLoaded = false

View file

@ -19,8 +19,10 @@ interface Props {
mapsProvider?: string
versionStatus?: string
versionTitle?: string
onVersionClick?: () => void
onVersionStatusClick?: () => void
bottomRightLinks?: string
versionText?: string
onVersionTextClick?: () => void
}
const httpsRegex = /^https?:\/\//
@ -33,9 +35,11 @@ export default ({
githubAction,
linksButton,
openFileAction,
versionText,
onVersionTextClick,
versionStatus,
versionTitle,
onVersionClick,
onVersionStatusClick,
bottomRightLinks
}: Props) => {
if (!bottomRightLinks?.trim()) bottomRightLinks = undefined
@ -109,13 +113,16 @@ export default ({
</div>
<div className={styles['bottom-info']}>
<span
title={`${versionTitle} (click to reload)`}
onClick={onVersionClick}
className={styles['product-info']}
>
Prismarine Web Client {versionStatus}
</span>
<div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
<span style={{ fontSize: 10, color: 'gray' }} onClick={onVersionTextClick}>{versionText}</span>
<span
title={`${versionTitle} (click to reload)`}
onClick={onVersionStatusClick}
className={styles['product-info']}
>
Prismarine Web Client {versionStatus}
</span>
</div>
<span className={styles['product-description']}>
<div className={styles['product-link']}>
{linksParsed?.map(([name, link], i, arr) => {

View file

@ -65,8 +65,11 @@ export default () => {
setVersionStatus(`(${isLatest ? 'latest' : 'new version available'}${mainMenuState.serviceWorkerLoaded ? ' - Available Offline' : ''})`)
}
subscribe(mainMenuState, upStatus)
upStatus()
setVersionTitle(`Loaded: ${process.env.BUILD_VERSION}. Remote: ${contents}`)
}, () => { })
}, () => {
setVersionStatus('(offline)')
})
}
}, [])
@ -113,10 +116,14 @@ export default () => {
mapsProvider={mapsProviderUrl}
versionStatus={versionStatus}
versionTitle={versionTitle}
onVersionClick={async () => {
onVersionStatusClick={async () => {
setVersionStatus('(reloading)')
await refreshApp()
}}
onVersionTextClick={async () => {
openGithub('/releases')
}}
versionText={process.env.RELEASE_TAG}
/>
</div>}
</Transition>

View file

@ -1,7 +1,8 @@
.potential-problem {
.description {
color: #808080;
word-break: break-all;
padding: 0 10px;
margin-top: 3px;
}
.last-status {

View file

@ -1,10 +1,9 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
description: string;
'last-status': string;
lastStatus: string;
'potential-problem': string;
potentialProblem: string;
}
declare const cssExports: CssExports;
export default cssExports;

View file

@ -9,6 +9,7 @@ import { options } from './optionsStorage'
import { showOptionsModal } from './react/SelectOption'
import { appStatusState } from './react/AppStatusProvider'
import { appReplacableResources, resourcesContentOriginal } from './generated/resources'
import { loadedGameState } from './globalState'
export const resourcePackState = proxy({
resourcePackInstalled: false,
@ -24,9 +25,14 @@ const getLoadedImage = async (url: string) => {
return img
}
const texturePackBasePath2 = '/data/resourcePacks/'
const texturePackBasePath = '/data/resourcePacks/'
export const uninstallTexturePack = async (name = 'default') => {
const basePath = texturePackBasePath2 + name
if (await existsAsync('/resourcepack/pack.mcmeta')) {
await removeFileRecursiveAsync('/resourcepack')
loadedGameState.usingServerResourcePack = false
}
const basePath = texturePackBasePath + name
if (!(await existsAsync(basePath))) return
await removeFileRecursiveAsync(basePath)
options.enabledResourcepack = null
await updateTexturePackInstalledState()
@ -35,7 +41,7 @@ export const uninstallTexturePack = async (name = 'default') => {
export const getResourcePackNames = async () => {
// TODO
try {
return { [await fs.promises.readFile(join(texturePackBasePath2, 'default', 'name.txt'), 'utf8')]: true }
return { [await fs.promises.readFile(join(texturePackBasePath, 'default', 'name.txt'), 'utf8')]: true }
} catch (err) {
return {}
}
@ -47,7 +53,7 @@ export const fromTexturePackPath = (path) => {
export const updateTexturePackInstalledState = async () => {
try {
resourcePackState.resourcePackInstalled = await existsAsync(texturePackBasePath2 + 'default')
resourcePackState.resourcePackInstalled = await existsAsync(texturePackBasePath + 'default')
} catch {
}
}
@ -58,31 +64,36 @@ export const installTexturePackFromHandle = async () => {
// await completeTexturePackInstall()
}
export const installTexturePack = async (file: File | ArrayBuffer, displayName = file['name'], name = 'default') => {
export const installTexturePack = async (file: File | ArrayBuffer, displayName = file['name'], name = 'default', isServer = false) => {
const installPath = isServer ? '/resourcepack/' : texturePackBasePath + name
try {
await uninstallTexturePack(name)
} catch (err) {
}
const showLoader = !isServer
const status = 'Installing resource pack: copying all files'
setLoadingScreenStatus(status)
if (showLoader) {
setLoadingScreenStatus(status)
}
// extract the zip and write to fs every file in it
const zip = new JSZip()
const zipFile = await zip.loadAsync(file)
if (!zipFile.file('pack.mcmeta')) throw new Error('Not a resource pack: missing /pack.mcmeta')
const basePath = texturePackBasePath2 + name
await mkdirRecursive(basePath)
await mkdirRecursive(installPath)
const allFilesArr = Object.entries(zipFile.files)
.filter(([path]) => !path.startsWith('.') && !path.startsWith('_') && !path.startsWith('/')) // ignore dot files and __MACOSX
let done = 0
const upStatus = () => {
setLoadingScreenStatus(`${status} ${Math.round(done / allFilesArr.length * 100)}%`)
if (showLoader) {
setLoadingScreenStatus(`${status} ${Math.round(done / allFilesArr.length * 100)}%`)
}
}
const createdDirs = new Set<string>()
const copyTasks = [] as Array<Promise<void>>
await Promise.all(allFilesArr.map(async ([path, file]) => {
// ignore dot files and __MACOSX
if (path.startsWith('.') || path.startsWith('_') || path.startsWith('/')) return
const writePath = join(basePath, path)
const writePath = join(installPath, path)
if (path.endsWith('/')) return
const dir = dirname(writePath)
if (!createdDirs.has(dir)) {
@ -93,26 +104,32 @@ export const installTexturePack = async (file: File | ArrayBuffer, displayName =
await Promise.all(copyTasks)
copyTasks.length = 0
}
const promise = fs.promises.writeFile(writePath, Buffer.from(await file.async('arraybuffer')))
const promise = fs.promises.writeFile(writePath, Buffer.from(await file.async('arraybuffer')) as any)
copyTasks.push(promise)
await promise
done++
upStatus()
}))
console.log('done')
await completeTexturePackInstall(displayName, name)
await completeTexturePackInstall(displayName, name, isServer)
}
// or enablement
export const completeTexturePackInstall = async (displayName: string, name: string) => {
const basePath = texturePackBasePath2 + name
await fs.promises.writeFile(join(basePath, 'name.txt'), displayName, 'utf8')
export const completeTexturePackInstall = async (displayName: string | undefined, name: string, isServer: boolean) => {
const basePath = isServer ? '/resourcepack/' : texturePackBasePath + name
if (displayName) {
await fs.promises.writeFile(join(basePath, 'name.txt'), displayName, 'utf8')
}
await updateTextures()
setLoadingScreenStatus(undefined)
showNotification('Texturepack installed & enabled')
await updateTexturePackInstalledState()
options.enabledResourcepack = name
if (isServer) {
loadedGameState.usingServerResourcePack = true
} else {
options.enabledResourcepack = name
}
}
const existsAsync = async (path) => {
@ -138,8 +155,8 @@ const getSizeFromImage = async (filePath: string) => {
}
export const getActiveTexturepackBasePath = async () => {
if (await existsAsync('/data/resourcePacks/server/pack.mcmeta')) {
return '/data/resourcePacks/server'
if (await existsAsync('/resourcepack/pack.mcmeta')) {
return '/resourcepack'
}
const { enabledResourcepack } = options
// const enabledResourcepack = 'default'
@ -262,28 +279,42 @@ const prepareBlockstatesAndModels = async () => {
}
}
const downloadAndUseResourcePack = async (url) => {
console.log('downloadAndUseResourcePack', url)
const downloadAndUseResourcePack = async (url: string): Promise<void> => {
console.log('Downloading server resource pack', url)
const response = await fetch(url)
const resourcePackData = await response.arrayBuffer()
showNotification('Installing resource pack...')
installTexturePack(resourcePackData, undefined, undefined, true).catch((err) => {
console.error(err)
showNotification('Failed to install resource pack: ' + err.message)
})
}
export const onAppLoad = () => {
customEvents.on('gameLoaded', () => {
customEvents.on('mineflayerBotCreated', () => {
// todo also handle resourcePack
bot._client.on('resource_pack_send', async (packet) => {
const handleResourcePackRequest = async (packet) => {
if (options.serverResourcePacks === 'never') return
const promptMessage = 'promptMessage' in packet ? JSON.stringify(packet.promptMessage) : 'Do you want to use server resource pack?'
const promptMessage = ('promptMessage' in packet && packet.promptMessage) ? JSON.stringify(packet.promptMessage) : 'Do you want to use server resource pack?'
// TODO!
const hash = 'hash' in packet ? packet.hash : '-'
const forced = 'forced' in packet ? packet.forced : false
const choice = options.serverResourcePacks === 'always'
? true
: await showOptionsModal(promptMessage, ['Download & Install'], {
: await showOptionsModal(promptMessage, ['Download & Install (recommended)', 'Pretend Installed (not recommended)'], {
cancel: !forced
})
if (!choice) return
await downloadAndUseResourcePack(packet.url)
bot.acceptResourcePack()
})
if (choice === 'Download & Install (recommended)') {
await downloadAndUseResourcePack(packet.url).catch((err) => {
console.error(err)
showNotification('Failed to download resource pack: ' + err.message)
})
}
}
bot._client.on('resource_pack_send', handleResourcePackRequest)
bot._client.on('add_resource_pack' as any, handleResourcePackRequest)
})
subscribe(resourcePackState, () => {

View file

@ -123,7 +123,7 @@ export const isMajorVersionGreater = (ver1: string, ver2: string) => {
}
let ourLastStatus: string | undefined = ''
export const setLoadingScreenStatus = function (status: string | undefined | null, isError = false, hideDots = false, fromFlyingSquid = false) {
export const setLoadingScreenStatus = function (status: string | undefined | null, isError = false, hideDots = false, fromFlyingSquid = false, minecraftJsonMessage?: Record<string, any>) {
// null can come from flying squid, should restore our last status
if (status === null) {
status = ourLastStatus
@ -152,6 +152,7 @@ export const setLoadingScreenStatus = function (status: string | undefined | nul
appStatusState.isError = isError
appStatusState.lastStatus = isError ? appStatusState.status : ''
appStatusState.status = status
appStatusState.minecraftJsonMessage = minecraftJsonMessage ?? null
}
// doesn't support snapshots
@ -185,8 +186,8 @@ export const reloadChunks = async () => {
await worldView.updatePosition(bot.entity.position, true)
}
export const openGithub = () => {
window.open(process.env.GITHUB_URL, '_blank')
export const openGithub = (addUrl = '') => {
window.open(`${process.env.GITHUB_URL}${addUrl}`, '_blank')
}
export const resolveTimeout = async (promise, timeout = 10_000) => {

View file

@ -59,8 +59,7 @@ export const watchOptionsAfterViewerInit = () => {
viewer.world.displayStats = o.renderDebug === 'advanced'
})
// viewer.world.mesherConfig.smoothLighting = options.smoothLighting
viewer.world.mesherConfig.smoothLighting = false // todo not supported for now
viewer.world.mesherConfig.smoothLighting = options.smoothLighting
subscribeKey(options, 'smoothLighting', () => {
viewer.world.mesherConfig.smoothLighting = options.smoothLighting;
(viewer.world as WorldRendererThree).rerenderAllChunks()