Next (#66)
This commit is contained in:
commit
f58eb01c79
38 changed files with 2795 additions and 234 deletions
|
|
@ -8,6 +8,9 @@ After forking the repository, run the following commands to get started:
|
|||
|
||||
A few notes:
|
||||
|
||||
- Use `next` branch for development and as base & target branch for pull requests if possible.
|
||||
- To link dependency locally e.g. flying-squid add this to `pnpm` > `overrides` of root package.json: `"flying-squid": "file:../space-squid",` (with some modules `pnpm link` also works)
|
||||
|
||||
- It's recommended to use debugger for debugging. VSCode has a great debugger built-in. If debugger is slow, you can use `--no-sources` flag that would allow browser to speedup .map file parsing.
|
||||
- Some data are cached between restarts. If you see something doesn't work after upgrading dependencies, try to clear the by simply removing the `dist` folder.
|
||||
- The same folder `dist` is used for both development and production builds, so be careful when deploying the project.
|
||||
|
|
|
|||
BIN
assets/generic_91.png
Normal file
BIN
assets/generic_91.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
BIN
assets/generic_92.png
Normal file
BIN
assets/generic_92.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
BIN
assets/generic_93.png
Normal file
BIN
assets/generic_93.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
BIN
assets/generic_94.png
Normal file
BIN
assets/generic_94.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
BIN
assets/generic_95.png
Normal file
BIN
assets/generic_95.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
|
|
@ -3,5 +3,5 @@
|
|||
"defaultHost": "<from-proxy>",
|
||||
"defaultProxy": "zardoy.site:2344",
|
||||
"defaultVersion": "1.18.2",
|
||||
"mapsProvider": "zardoy.site/maps"
|
||||
"mapsProvider": "https://maps.mcraft.fun/"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,7 +70,8 @@ const buildOptions = {
|
|||
'process.env.NODE_ENV': JSON.stringify(dev ? 'development' : 'production'),
|
||||
'process.env.BUILD_VERSION': JSON.stringify(!dev ? buildingVersion : 'undefined'),
|
||||
'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}`}`)
|
||||
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({})
|
||||
},
|
||||
loader: {
|
||||
// todo use external or resolve issues with duplicating
|
||||
|
|
|
|||
15
index.html
15
index.html
|
|
@ -12,8 +12,8 @@
|
|||
<div style="position: fixed;inset: 0;z-index: -1;background-color: rgba(0, 0, 0, 0.8);"></div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: calc(var(--font-size) * 1.8);color: lightgray;">Loading...</div>
|
||||
<div style="font-size: var(--font-size);color: rgb(176, 176, 176);">A true Minecraft client in your browser!</div>
|
||||
<div style="font-size: calc(var(--font-size) * 1.8);color: lightgray;" class="title">Loading...</div>
|
||||
<div style="font-size: var(--font-size);color: rgb(176, 176, 176);" class="subtitle">A true Minecraft client in your browser!</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
|
@ -22,6 +22,17 @@
|
|||
if (!window.pageLoaded) {
|
||||
document.documentElement.appendChild(loadingDivElem)
|
||||
}
|
||||
// load error handling
|
||||
const onError = (message) => {
|
||||
console.log(message)
|
||||
if (document.querySelector('.initial-loader') && document.querySelector('.initial-loader').querySelector('.title').textContent !== 'Error') {
|
||||
document.querySelector('.initial-loader').querySelector('.title').textContent = 'Error'
|
||||
document.querySelector('.initial-loader').querySelector('.subtitle').textContent = message
|
||||
window.location.hash = '#dev' // show eruda
|
||||
}
|
||||
}
|
||||
window.addEventListener('unhandledrejection', (e) => onError(e.reason))
|
||||
window.addEventListener('error', (e) => onError(e.message))
|
||||
</script>
|
||||
<script type="module" async>
|
||||
const checkLoadEruda = () => {
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@
|
|||
"lit": "^2.8.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"minecraft-assets": "^1.12.2",
|
||||
"minecraft-data": "3.60.0",
|
||||
"minecraft-data": "3.61.0",
|
||||
"net-browserify": "github:zardoy/prismarinejs-net-browserify",
|
||||
"node-gzip": "^1.1.2",
|
||||
"peerjs": "^1.5.0",
|
||||
|
|
@ -129,7 +129,7 @@
|
|||
"diamond-square": "github:zardoy/diamond-square",
|
||||
"prismarine-block": "github:zardoy/prismarine-block#next-era",
|
||||
"prismarine-world": "github:zardoy/prismarine-world#next-era",
|
||||
"minecraft-data": "3.60.0",
|
||||
"minecraft-data": "3.61.0",
|
||||
"prismarine-provider-anvil": "github:zardoy/prismarine-provider-anvil#everything",
|
||||
"minecraft-protocol": "github:zardoy/minecraft-protocol#everything",
|
||||
"react": "^18.2.0"
|
||||
|
|
|
|||
565
pnpm-lock.yaml
generated
565
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -11,7 +11,8 @@ import { loadSkinToCanvas, loadEarsToCanvasFromSkin, inferModelType, loadCapeToC
|
|||
import stevePng from 'minecraft-assets/minecraft-assets/data/1.20.2/entity/player/wide/steve.png'
|
||||
import { WalkingGeneralSwing } from './entity/animations'
|
||||
import { NameTagObject } from 'skinview3d/libs/nametag'
|
||||
import { fromFormattedString } from '@xmcl/text-component'
|
||||
import { flat, fromFormattedString } from '@xmcl/text-component'
|
||||
import mojangson from 'mojangson'
|
||||
|
||||
export const TWEEN_DURATION = 50 // todo should be 100
|
||||
|
||||
|
|
@ -39,6 +40,23 @@ function getUsernameTexture (username, { fontFamily = 'sans-serif' }) {
|
|||
return canvas
|
||||
}
|
||||
|
||||
const addNametag = (entity, options, mesh) => {
|
||||
if (entity.username !== undefined) {
|
||||
if (mesh.children.find(c => c.name === 'nametag')) return // todo update
|
||||
const canvas = getUsernameTexture(entity.username, options)
|
||||
const tex = new THREE.Texture(canvas)
|
||||
tex.needsUpdate = true
|
||||
const spriteMat = new THREE.SpriteMaterial({ map: tex })
|
||||
const sprite = new THREE.Sprite(spriteMat)
|
||||
sprite.renderOrder = 1000
|
||||
sprite.scale.set(canvas.width * 0.005, canvas.height * 0.005, 1)
|
||||
sprite.position.y += entity.height + 0.6
|
||||
sprite.name = 'nametag'
|
||||
|
||||
mesh.add(sprite)
|
||||
}
|
||||
}
|
||||
|
||||
function getEntityMesh (entity, scene, options, overrides) {
|
||||
if (entity.name) {
|
||||
try {
|
||||
|
|
@ -46,18 +64,7 @@ function getEntityMesh (entity, scene, options, overrides) {
|
|||
const entityName = entity.name.toLowerCase()
|
||||
const e = new Entity('1.16.4', entityName, scene, overrides)
|
||||
|
||||
if (entity.username !== undefined) {
|
||||
const canvas = getUsernameTexture(entity.username, options)
|
||||
const tex = new THREE.Texture(canvas)
|
||||
tex.needsUpdate = true
|
||||
const spriteMat = new THREE.SpriteMaterial({ map: tex })
|
||||
const sprite = new THREE.Sprite(spriteMat)
|
||||
sprite.renderOrder = 1000
|
||||
sprite.scale.set(canvas.width * 0.005, canvas.height * 0.005, 1)
|
||||
sprite.position.y += entity.height + 0.6
|
||||
|
||||
e.mesh.add(sprite)
|
||||
}
|
||||
addNametag(entity, options, e.mesh)
|
||||
return e.mesh
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
|
|
@ -228,6 +235,13 @@ export class Entities extends EventEmitter {
|
|||
|
||||
}
|
||||
|
||||
displaySimpleText (jsonLike) {
|
||||
if (!jsonLike) return
|
||||
const parsed = mojangson.simplify(mojangson.parse(jsonLike))
|
||||
const text = flat(parsed).map(x => x.text)
|
||||
return text.join('')
|
||||
}
|
||||
|
||||
update (/** @type {import('prismarine-entity').Entity & {delete?, pos}} */entity, overrides) {
|
||||
if (!this.entities[entity.id] && !entity.delete) {
|
||||
const group = new THREE.Group()
|
||||
|
|
@ -295,6 +309,21 @@ export class Entities extends EventEmitter {
|
|||
this.setVisible(this.visible, group)
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
const isInvisible = entity.metadata?.[0] & 0x20
|
||||
if (isInvisible) {
|
||||
for (const child of this.entities[entity.id].children.find(c => c.name === 'mesh').children) {
|
||||
if (child.name !== 'nametag') {
|
||||
child.visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
// not player
|
||||
const displayText = entity.metadata?.[3] && this.displaySimpleText(entity.metadata[2]);
|
||||
if (entity.name !== 'player') {
|
||||
addNametag({ ...entity, username: displayText }, this.entitiesOptions, this.entities[entity.id].children.find(c => c.name === 'mesh'))
|
||||
}
|
||||
|
||||
// this can be undefined in case where packet entity_destroy was sent twice (so it was already deleted)
|
||||
const e = this.entities[entity.id]
|
||||
|
||||
|
|
|
|||
|
|
@ -259,7 +259,7 @@ export class WorldRenderer {
|
|||
const loadBlockStates = async () => {
|
||||
return new Promise(resolve => {
|
||||
if (this.customBlockStatesData) return resolve(this.customBlockStatesData)
|
||||
return loadJSON(`blocksStates/${this.texturesVersion}.json`, (data) => {
|
||||
return loadJSON(`/blocksStates/${this.texturesVersion}.json`, (data) => {
|
||||
this.downloadedBlockStatesData = data
|
||||
// todo
|
||||
this.renderUpdateEmitter.emit('blockStatesDownloaded')
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { options } from './optionsStorage'
|
||||
import { isCypress } from './standaloneUtils'
|
||||
import { reportWarningOnce } from './utils'
|
||||
|
||||
let audioContext: AudioContext
|
||||
const sounds: Record<string, any> = {}
|
||||
|
|
@ -39,7 +40,12 @@ export async function playSound (url, soundVolume = 1) {
|
|||
|
||||
if (!volume) return
|
||||
|
||||
audioContext ??= new window.AudioContext()
|
||||
try {
|
||||
audioContext ??= new window.AudioContext()
|
||||
} catch (err) {
|
||||
reportWarningOnce('audioContext', 'Failed to create audio context. Some sounds will not play')
|
||||
return
|
||||
}
|
||||
|
||||
for (const [soundName, sound] of Object.entries(sounds)) {
|
||||
if (convertedSounds.includes(soundName)) continue
|
||||
|
|
|
|||
|
|
@ -114,6 +114,6 @@ const blockToItemRemaps = {
|
|||
}
|
||||
|
||||
export const getItemFromBlock = (block: import('prismarine-block').Block) => {
|
||||
const item = loadedData.blocks[blockToItemRemaps[block.name] ?? block.name]
|
||||
const item = loadedData.items[blockToItemRemaps[block.name] ?? block.name]
|
||||
return item
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ export const contro = new ControMax({
|
|||
},
|
||||
gamepadPollingInterval: 10
|
||||
})
|
||||
window.controMax = contro
|
||||
export type Command = CommandEventArgument<typeof contro['_commandsRaw']>['command']
|
||||
|
||||
const setSprinting = (state: boolean) => {
|
||||
|
|
@ -414,12 +415,12 @@ let allowFlying = false
|
|||
|
||||
export const onBotCreate = () => {
|
||||
bot._client.on('abilities', ({ flags }) => {
|
||||
allowFlying = !!(flags & 4)
|
||||
if (flags & 2) { // flying
|
||||
toggleFly(true, false)
|
||||
} else {
|
||||
toggleFly(false, false)
|
||||
}
|
||||
allowFlying = !!(flags & 4)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ export default () => {
|
|||
assertDefined(viewer)
|
||||
// 0 morning
|
||||
const dayTotal = 24_000
|
||||
const evening = 12_542
|
||||
const night = 17_843
|
||||
const morningStart = 22_300
|
||||
const evening = 11_500
|
||||
const night = 13_500
|
||||
const morningStart = 23_000
|
||||
const morningEnd = 23_961
|
||||
const timeProgress = options.dayCycleAndLighting ? bot.time.timeOfDay : 0
|
||||
|
||||
|
|
|
|||
17
src/gameUnload.ts
Normal file
17
src/gameUnload.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { subscribe } from 'valtio'
|
||||
import { miscUiState } from './globalState'
|
||||
|
||||
let toCleanup = [] as Array<() => void>
|
||||
|
||||
export const watchUnloadForCleanup = (func: () => void) => {
|
||||
toCleanup.push(func)
|
||||
}
|
||||
|
||||
subscribe(miscUiState, () => {
|
||||
if (!miscUiState.gameLoaded) {
|
||||
for (const func of toCleanup) {
|
||||
func()
|
||||
}
|
||||
toCleanup = []
|
||||
}
|
||||
})
|
||||
543
src/generatedClientPackets.ts
Normal file
543
src/generatedClientPackets.ts
Normal file
|
|
@ -0,0 +1,543 @@
|
|||
export interface ClientWriteMap {
|
||||
keep_alive: /** 1.7 */ {
|
||||
keepAliveId: number;
|
||||
} | /** 1.12.2 */ {
|
||||
keepAliveId: bigint;
|
||||
};
|
||||
/** Removed in 1.19 */
|
||||
chat: /** 1.7 */ {
|
||||
message: string;
|
||||
};
|
||||
use_entity: /** 1.7 */ {
|
||||
target: number;
|
||||
mouse: number;
|
||||
x: any;
|
||||
y: any;
|
||||
z: any;
|
||||
} | /** 1.9 */ {
|
||||
target: number;
|
||||
mouse: number;
|
||||
x: any;
|
||||
y: any;
|
||||
z: any;
|
||||
hand: any;
|
||||
} | /** 1.16 */ {
|
||||
target: number;
|
||||
mouse: number;
|
||||
x: any;
|
||||
y: any;
|
||||
z: any;
|
||||
hand: any;
|
||||
sneaking: boolean;
|
||||
};
|
||||
flying: /** 1.7 */ {
|
||||
onGround: boolean;
|
||||
};
|
||||
position: /** 1.7 */ {
|
||||
x: number;
|
||||
stance: number;
|
||||
y: number;
|
||||
z: number;
|
||||
onGround: boolean;
|
||||
} | /** 1.8 */ {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
onGround: boolean;
|
||||
};
|
||||
look: /** 1.7 */ {
|
||||
yaw: number;
|
||||
pitch: number;
|
||||
onGround: boolean;
|
||||
};
|
||||
position_look: /** 1.7 */ {
|
||||
x: number;
|
||||
stance: number;
|
||||
y: number;
|
||||
z: number;
|
||||
yaw: number;
|
||||
pitch: number;
|
||||
onGround: boolean;
|
||||
} | /** 1.8 */ {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
yaw: number;
|
||||
pitch: number;
|
||||
onGround: boolean;
|
||||
};
|
||||
block_dig: /** 1.7 */ {
|
||||
status: number;
|
||||
location: any;
|
||||
face: number;
|
||||
} | /** 1.8 */ {
|
||||
status: number;
|
||||
location: { x: number, y: number, z: number };
|
||||
face: number;
|
||||
} | /** 1.19 */ {
|
||||
status: number;
|
||||
location: { x: number, y: number, z: number };
|
||||
face: number;
|
||||
sequence: number;
|
||||
};
|
||||
block_place: /** 1.7 */ {
|
||||
location: any;
|
||||
direction: number;
|
||||
heldItem: any;
|
||||
cursorX: number;
|
||||
cursorY: number;
|
||||
cursorZ: number;
|
||||
} | /** 1.8 */ {
|
||||
location: { x: number, y: number, z: number };
|
||||
direction: number;
|
||||
heldItem: any;
|
||||
cursorX: number;
|
||||
cursorY: number;
|
||||
cursorZ: number;
|
||||
} | /** 1.9 */ {
|
||||
location: { x: number, y: number, z: number };
|
||||
direction: number;
|
||||
hand: number;
|
||||
cursorX: number;
|
||||
cursorY: number;
|
||||
cursorZ: number;
|
||||
} | /** 1.14 */ {
|
||||
hand: number;
|
||||
location: { x: number, y: number, z: number };
|
||||
direction: number;
|
||||
cursorX: number;
|
||||
cursorY: number;
|
||||
cursorZ: number;
|
||||
insideBlock: boolean;
|
||||
} | /** 1.19 */ {
|
||||
hand: number;
|
||||
location: { x: number, y: number, z: number };
|
||||
direction: number;
|
||||
cursorX: number;
|
||||
cursorY: number;
|
||||
cursorZ: number;
|
||||
insideBlock: boolean;
|
||||
sequence: number;
|
||||
};
|
||||
held_item_slot: /** 1.7 */ {
|
||||
slotId: number;
|
||||
};
|
||||
arm_animation: /** 1.7 */ {
|
||||
entityId: number;
|
||||
animation: number;
|
||||
} | /** 1.8 */ {
|
||||
|
||||
} | /** 1.9 */ {
|
||||
hand: number;
|
||||
};
|
||||
entity_action: /** 1.7 */ {
|
||||
entityId: number;
|
||||
actionId: number;
|
||||
jumpBoost: number;
|
||||
};
|
||||
steer_vehicle: /** 1.7 */ {
|
||||
sideways: number;
|
||||
forward: number;
|
||||
jump: boolean;
|
||||
unmount: boolean;
|
||||
} | /** 1.8 */ {
|
||||
sideways: number;
|
||||
forward: number;
|
||||
jump: number;
|
||||
};
|
||||
close_window: /** 1.7 */ {
|
||||
windowId: number;
|
||||
};
|
||||
window_click: /** 1.7 */ {
|
||||
windowId: number;
|
||||
slot: number;
|
||||
mouseButton: number;
|
||||
action: number;
|
||||
mode: number;
|
||||
item: any;
|
||||
} | /** 1.17 */ {
|
||||
windowId: number;
|
||||
slot: number;
|
||||
mouseButton: number;
|
||||
mode: number;
|
||||
changedSlots: any;
|
||||
cursorItem: any;
|
||||
} | /** 1.17.1 */ {
|
||||
windowId: number;
|
||||
stateId: number;
|
||||
slot: number;
|
||||
mouseButton: number;
|
||||
mode: number;
|
||||
changedSlots: any;
|
||||
cursorItem: any;
|
||||
};
|
||||
/** Removed in 1.17 */
|
||||
transaction: /** 1.7 */ {
|
||||
windowId: number;
|
||||
action: number;
|
||||
accepted: boolean;
|
||||
};
|
||||
set_creative_slot: /** 1.7 */ {
|
||||
slot: number;
|
||||
item: any;
|
||||
};
|
||||
enchant_item: /** 1.7 */ {
|
||||
windowId: number;
|
||||
enchantment: number;
|
||||
};
|
||||
update_sign: /** 1.7 */ {
|
||||
location: any;
|
||||
text1: string;
|
||||
text2: string;
|
||||
text3: string;
|
||||
text4: string;
|
||||
} | /** 1.8 */ {
|
||||
location: { x: number, y: number, z: number };
|
||||
text1: string;
|
||||
text2: string;
|
||||
text3: string;
|
||||
text4: string;
|
||||
} | /** 1.20 */ {
|
||||
location: { x: number, y: number, z: number };
|
||||
isFrontText: boolean;
|
||||
text1: string;
|
||||
text2: string;
|
||||
text3: string;
|
||||
text4: string;
|
||||
};
|
||||
abilities: /** 1.7 */ {
|
||||
flags: number;
|
||||
flyingSpeed: number;
|
||||
walkingSpeed: number;
|
||||
} | /** 1.16 */ {
|
||||
flags: number;
|
||||
};
|
||||
tab_complete: /** 1.7 */ {
|
||||
text: string;
|
||||
} | /** 1.8 */ {
|
||||
text: string;
|
||||
block: any;
|
||||
} | /** 1.9 */ {
|
||||
text: string;
|
||||
assumeCommand: boolean;
|
||||
lookedAtBlock: any;
|
||||
} | /** 1.13 */ {
|
||||
transactionId: number;
|
||||
text: string;
|
||||
};
|
||||
settings: /** 1.7 */ {
|
||||
locale: string;
|
||||
viewDistance: number;
|
||||
chatFlags: number;
|
||||
chatColors: boolean;
|
||||
difficulty: number;
|
||||
showCape: boolean;
|
||||
} | /** 1.8 */ {
|
||||
locale: string;
|
||||
viewDistance: number;
|
||||
chatFlags: number;
|
||||
chatColors: boolean;
|
||||
skinParts: number;
|
||||
} | /** 1.9 */ {
|
||||
locale: string;
|
||||
viewDistance: number;
|
||||
chatFlags: number;
|
||||
chatColors: boolean;
|
||||
skinParts: number;
|
||||
mainHand: number;
|
||||
} | /** 1.17 */ {
|
||||
locale: string;
|
||||
viewDistance: number;
|
||||
chatFlags: number;
|
||||
chatColors: boolean;
|
||||
skinParts: number;
|
||||
mainHand: number;
|
||||
disableTextFiltering: boolean;
|
||||
} | /** 1.18 */ {
|
||||
locale: string;
|
||||
viewDistance: number;
|
||||
chatFlags: number;
|
||||
chatColors: boolean;
|
||||
skinParts: number;
|
||||
mainHand: number;
|
||||
enableTextFiltering: boolean;
|
||||
enableServerListing: boolean;
|
||||
};
|
||||
client_command: /** 1.7 */ {
|
||||
payload: number;
|
||||
} | /** 1.9 */ {
|
||||
actionId: number;
|
||||
};
|
||||
custom_payload: /** 1.7 */ {
|
||||
channel: string;
|
||||
data: any;
|
||||
};
|
||||
packet: /** 1.7 */ {
|
||||
name: any;
|
||||
params: any;
|
||||
};
|
||||
spectate: /** 1.8 */ {
|
||||
target: any;
|
||||
};
|
||||
resource_pack_receive: /** 1.8 */ {
|
||||
hash: string;
|
||||
result: number;
|
||||
} | /** 1.10 */ {
|
||||
result: number;
|
||||
} | /** 1.20.3 */ {
|
||||
uuid: any;
|
||||
result: number;
|
||||
};
|
||||
teleport_confirm: /** 1.9 */ {
|
||||
teleportId: number;
|
||||
};
|
||||
vehicle_move: /** 1.9 */ {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
yaw: number;
|
||||
pitch: number;
|
||||
};
|
||||
steer_boat: /** 1.9 */ {
|
||||
leftPaddle: boolean;
|
||||
rightPaddle: boolean;
|
||||
};
|
||||
use_item: /** 1.9 */ {
|
||||
hand: number;
|
||||
} | /** 1.19 */ {
|
||||
hand: number;
|
||||
sequence: number;
|
||||
};
|
||||
/** Removed in 1.12.1 */
|
||||
prepare_crafting_grid: /** 1.12 */ {
|
||||
windowId: number;
|
||||
actionNumber: number;
|
||||
returnEntry: any;
|
||||
prepareEntry: any;
|
||||
};
|
||||
/** Removed in 1.16.2 */
|
||||
crafting_book_data: /** 1.12 */ {
|
||||
type: number;
|
||||
undefined: any;
|
||||
};
|
||||
advancement_tab: /** 1.12 */ {
|
||||
action: number;
|
||||
tabId: any;
|
||||
};
|
||||
craft_recipe_request: /** 1.12.1 */ {
|
||||
windowId: number;
|
||||
recipe: number;
|
||||
makeAll: boolean;
|
||||
} | /** 1.13 */ {
|
||||
windowId: number;
|
||||
recipe: string;
|
||||
makeAll: boolean;
|
||||
};
|
||||
query_block_nbt: /** 1.13 */ {
|
||||
transactionId: number;
|
||||
location: { x: number, y: number, z: number };
|
||||
};
|
||||
edit_book: /** 1.13 */ {
|
||||
new_book: any;
|
||||
signing: boolean;
|
||||
} | /** 1.13.1 */ {
|
||||
new_book: any;
|
||||
signing: boolean;
|
||||
hand: number;
|
||||
} | /** 1.17.1 */ {
|
||||
hand: number;
|
||||
pages: any;
|
||||
title: any;
|
||||
};
|
||||
query_entity_nbt: /** 1.13 */ {
|
||||
transactionId: number;
|
||||
entityId: number;
|
||||
};
|
||||
pick_item: /** 1.13 */ {
|
||||
slot: number;
|
||||
};
|
||||
name_item: /** 1.13 */ {
|
||||
name: string;
|
||||
};
|
||||
select_trade: /** 1.13 */ {
|
||||
slot: number;
|
||||
};
|
||||
set_beacon_effect: /** 1.13 */ {
|
||||
primary_effect: number;
|
||||
secondary_effect: number;
|
||||
} | /** 1.19 */ {
|
||||
primary_effect: any;
|
||||
secondary_effect: any;
|
||||
};
|
||||
update_command_block: /** 1.13 */ {
|
||||
location: { x: number, y: number, z: number };
|
||||
command: string;
|
||||
mode: number;
|
||||
flags: number;
|
||||
};
|
||||
update_command_block_minecart: /** 1.13 */ {
|
||||
entityId: number;
|
||||
command: string;
|
||||
track_output: boolean;
|
||||
};
|
||||
update_structure_block: /** 1.13 */ {
|
||||
location: { x: number, y: number, z: number };
|
||||
action: number;
|
||||
mode: number;
|
||||
name: string;
|
||||
offset_x: number;
|
||||
offset_y: number;
|
||||
offset_z: number;
|
||||
size_x: number;
|
||||
size_y: number;
|
||||
size_z: number;
|
||||
mirror: number;
|
||||
rotation: number;
|
||||
metadata: string;
|
||||
integrity: number;
|
||||
seed: any;
|
||||
flags: number;
|
||||
} | /** 1.19 */ {
|
||||
location: { x: number, y: number, z: number };
|
||||
action: number;
|
||||
mode: number;
|
||||
name: string;
|
||||
offset_x: number;
|
||||
offset_y: number;
|
||||
offset_z: number;
|
||||
size_x: number;
|
||||
size_y: number;
|
||||
size_z: number;
|
||||
mirror: number;
|
||||
rotation: number;
|
||||
metadata: string;
|
||||
integrity: number;
|
||||
seed: number;
|
||||
flags: number;
|
||||
};
|
||||
set_difficulty: /** 1.14 */ {
|
||||
newDifficulty: number;
|
||||
};
|
||||
lock_difficulty: /** 1.14 */ {
|
||||
locked: boolean;
|
||||
};
|
||||
update_jigsaw_block: /** 1.14 */ {
|
||||
location: { x: number, y: number, z: number };
|
||||
attachmentType: string;
|
||||
targetPool: string;
|
||||
finalState: string;
|
||||
} | /** 1.16 */ {
|
||||
location: { x: number, y: number, z: number };
|
||||
name: string;
|
||||
target: string;
|
||||
pool: string;
|
||||
finalState: string;
|
||||
jointType: string;
|
||||
} | /** 1.20.3 */ {
|
||||
location: { x: number, y: number, z: number };
|
||||
name: string;
|
||||
target: string;
|
||||
pool: string;
|
||||
finalState: string;
|
||||
jointType: string;
|
||||
selection_priority: number;
|
||||
placement_priority: number;
|
||||
};
|
||||
generate_structure: /** 1.16 */ {
|
||||
location: { x: number, y: number, z: number };
|
||||
levels: number;
|
||||
keepJigsaws: boolean;
|
||||
};
|
||||
displayed_recipe: /** 1.16.2 */ {
|
||||
recipeId: string;
|
||||
};
|
||||
recipe_book: /** 1.16.2 */ {
|
||||
bookId: number;
|
||||
bookOpen: boolean;
|
||||
filterActive: boolean;
|
||||
};
|
||||
pong: /** 1.17 */ {
|
||||
id: number;
|
||||
};
|
||||
chat_command: /** 1.19 */ {
|
||||
command: string;
|
||||
timestamp: bigint;
|
||||
salt: bigint;
|
||||
argumentSignatures: any;
|
||||
signedPreview: boolean;
|
||||
} | /** 1.19.2 */ {
|
||||
command: string;
|
||||
timestamp: bigint;
|
||||
salt: bigint;
|
||||
argumentSignatures: any;
|
||||
signedPreview: boolean;
|
||||
previousMessages: any;
|
||||
lastRejectedMessage: any;
|
||||
} | /** 1.19.3 */ {
|
||||
command: string;
|
||||
timestamp: bigint;
|
||||
salt: bigint;
|
||||
argumentSignatures: any;
|
||||
messageCount: number;
|
||||
acknowledged: any;
|
||||
};
|
||||
chat_message: /** 1.19 */ {
|
||||
message: string;
|
||||
timestamp: bigint;
|
||||
salt: bigint;
|
||||
signature: any;
|
||||
signedPreview: boolean;
|
||||
} | /** 1.19.2 */ {
|
||||
message: string;
|
||||
timestamp: bigint;
|
||||
salt: bigint;
|
||||
signature: any;
|
||||
signedPreview: boolean;
|
||||
previousMessages: any;
|
||||
lastRejectedMessage: any;
|
||||
} | /** 1.19.3 */ {
|
||||
message: string;
|
||||
timestamp: bigint;
|
||||
salt: bigint;
|
||||
signature: any;
|
||||
offset: number;
|
||||
acknowledged: any;
|
||||
};
|
||||
/** Removed in 1.19.3 */
|
||||
chat_preview: /** 1.19 */ {
|
||||
query: number;
|
||||
message: string;
|
||||
};
|
||||
message_acknowledgement: /** 1.19.2 */ {
|
||||
previousMessages: any;
|
||||
lastRejectedMessage: any;
|
||||
} | /** 1.19.3 */ {
|
||||
count: number;
|
||||
};
|
||||
chat_session_update: /** 1.19.3 */ {
|
||||
sessionUUID: any;
|
||||
expireTime: bigint;
|
||||
publicKey: any;
|
||||
signature: any;
|
||||
};
|
||||
chunk_batch_received: /** 1.20.2 */ {
|
||||
chunksPerTick: number;
|
||||
};
|
||||
/** Removed in 1.20.3 */
|
||||
configuation_acknowledged: /** 1.20.2 */ {
|
||||
|
||||
};
|
||||
ping_request: /** 1.20.2 */ {
|
||||
id: bigint;
|
||||
};
|
||||
configuration_acknowledged: /** 1.20.3 */ {
|
||||
|
||||
};
|
||||
set_slot_state: /** 1.20.3 */ {
|
||||
slot_id: number;
|
||||
window_id: number;
|
||||
state: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export declare const clientWrite: <T extends keyof ClientWriteMap>(name: T, data: ClientWriteMap[T]) => Buffer
|
||||
1495
src/generatedServerPackets.ts
Normal file
1495
src/generatedServerPackets.ts
Normal file
File diff suppressed because it is too large
Load diff
15
src/globals.d.ts
vendored
15
src/globals.d.ts
vendored
|
|
@ -2,7 +2,12 @@
|
|||
|
||||
declare const THREE: typeof import('three')
|
||||
// todo make optional
|
||||
declare const bot: Omit<import('mineflayer').Bot, 'world'> & { world: import('prismarine-world').world.WorldSync }
|
||||
declare const bot: Omit<import('mineflayer').Bot, 'world' | '_client'> & {
|
||||
world: import('prismarine-world').world.WorldSync
|
||||
_client: import('minecraft-protocol').Client & {
|
||||
write: typeof import('./generatedClientPackets').clientWrite
|
||||
}
|
||||
}
|
||||
declare const __type_bot: typeof bot
|
||||
declare const viewer: import('prismarine-viewer/viewer/lib/viewer').Viewer
|
||||
declare const worldView: import('prismarine-viewer/viewer/lib/worldDataEmitter').WorldDataEmitter | undefined
|
||||
|
|
@ -66,3 +71,11 @@ declare module '*.png' {
|
|||
const png: string
|
||||
export default png
|
||||
}
|
||||
|
||||
interface PromiseConstructor {
|
||||
withResolvers<T> (): {
|
||||
resolve: (value: T) => void;
|
||||
reject: (reason: any) => void;
|
||||
promise: Promise<T>;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
28
src/index.ts
28
src/index.ts
|
|
@ -23,6 +23,8 @@ import './menus/hud'
|
|||
import './menus/play_screen'
|
||||
import './menus/pause_screen'
|
||||
import './menus/keybinds_screen'
|
||||
import 'core-js/features/array/at'
|
||||
import 'core-js/features/promise/with-resolvers'
|
||||
import { initWithRenderer, statsEnd, statsStart } from './topRightStats'
|
||||
import PrismarineBlock from 'prismarine-block'
|
||||
|
||||
|
|
@ -88,9 +90,11 @@ import { loadInMemorySave } from './react/SingleplayerProvider'
|
|||
|
||||
// side effects
|
||||
import { downloadSoundsIfNeeded } from './soundSystem'
|
||||
import { ua } from './react/utils'
|
||||
|
||||
window.debug = debug
|
||||
window.THREE = THREE
|
||||
window.worldInteractions = worldInteractions
|
||||
window.beforeRenderFrame = []
|
||||
|
||||
// ACTUAL CODE
|
||||
|
|
@ -100,16 +104,32 @@ watchFov()
|
|||
initCollisionShapes()
|
||||
|
||||
// Create three.js context, add to page
|
||||
const renderer = new THREE.WebGLRenderer({
|
||||
powerPreference: options.gpuPreference,
|
||||
})
|
||||
let renderer: THREE.WebGLRenderer
|
||||
try {
|
||||
renderer = new THREE.WebGLRenderer({
|
||||
powerPreference: options.gpuPreference,
|
||||
})
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
throw new Error(`Failed to create WebGL context, not possible to render (restart browser): ${err.message}`)
|
||||
}
|
||||
|
||||
// renderer.localClippingEnabled = true
|
||||
initWithRenderer(renderer.domElement)
|
||||
window.renderer = renderer
|
||||
renderer.setPixelRatio(window.devicePixelRatio || 1) // todo this value is too high on ios, need to check, probably we should use avg, also need to make it configurable
|
||||
let pixelRatio = window.devicePixelRatio || 1 // todo this value is too high on ios, need to check, probably we should use avg, also need to make it configurable
|
||||
if (!renderer.capabilities.isWebGL2) pixelRatio = 1 // webgl1 has issues with high pixel ratio (sometimes screen is clipped)
|
||||
renderer.setPixelRatio(pixelRatio)
|
||||
renderer.setSize(window.innerWidth, window.innerHeight)
|
||||
renderer.domElement.id = 'viewer-canvas'
|
||||
document.body.appendChild(renderer.domElement)
|
||||
|
||||
const isFirefox = ua.getBrowser().name === 'Firefox'
|
||||
if (isFirefox) {
|
||||
// set custom property
|
||||
document.body.style.setProperty('--thin-if-firefox', 'thin')
|
||||
}
|
||||
|
||||
// Create viewer
|
||||
const viewer: import('prismarine-viewer/viewer/lib/viewer').Viewer = new Viewer(renderer, options.numWorkers)
|
||||
window.viewer = viewer
|
||||
|
|
|
|||
|
|
@ -93,11 +93,15 @@ class HealthBar extends LitElement {
|
|||
}
|
||||
|
||||
effectAdded (effect) {
|
||||
this.shadowRoot.querySelector('#health').classList.add(getEffectClass(effect))
|
||||
const effectClass = getEffectClass(effect)
|
||||
if (!effectClass) return
|
||||
this.shadowRoot.querySelector('#health').classList.add(effectClass)
|
||||
}
|
||||
|
||||
effectEnded (effect) {
|
||||
this.shadowRoot.querySelector('#health').classList.remove(getEffectClass(effect))
|
||||
const effectClass = getEffectClass(effect)
|
||||
if (!effectClass) return
|
||||
this.shadowRoot.querySelector('#health').classList.remove(effectClass)
|
||||
}
|
||||
|
||||
onDamage () {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ const { subscribe } = require('valtio')
|
|||
const { subscribeKey } = require('valtio/utils')
|
||||
const { hideCurrentModal, showModal, miscUiState, notification, openOptionsMenu } = require('../globalState')
|
||||
const { fsState } = require('../loadSave')
|
||||
const { disconnect, openGithub } = require('../utils')
|
||||
const { openGithub } = require('../utils')
|
||||
const { disconnect } = require('../flyingSquidUtils')
|
||||
const { closeWan, openToWanAndCopyJoinLink, getJoinLink } = require('../localServerMultiplayer')
|
||||
const { uniqueFileNameFromWorldName, copyFilesAsyncWithProgress } = require('../browserfs')
|
||||
const { showOptionsModal } = require('../react/SelectOption')
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ const defaultOptions = {
|
|||
touchButtonsSize: 40,
|
||||
touchButtonsOpacity: 80,
|
||||
touchButtonsPosition: 12,
|
||||
touchControlsPositions: {} as Record<string, [number, number]>,
|
||||
gpuPreference: 'default' as 'default' | 'high-performance' | 'low-power',
|
||||
/** @unstable */
|
||||
disableAssets: false,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,12 @@ import LargeChestLikeGui from 'minecraft-assets/minecraft-assets/data/1.17.1/gui
|
|||
import FurnaceGui from 'minecraft-assets/minecraft-assets/data/1.17.1/gui/container/furnace.png'
|
||||
import CraftingTableGui from 'minecraft-assets/minecraft-assets/data/1.17.1/gui/container/crafting_table.png'
|
||||
import DispenserGui from 'minecraft-assets/minecraft-assets/data/1.17.1/gui/container/dispenser.png'
|
||||
import HopperGui from 'minecraft-assets/minecraft-assets/data/1.17.1/gui/container/hopper.png'
|
||||
import HorseGui from 'minecraft-assets/minecraft-assets/data/1.17.1/gui/container/horse.png'
|
||||
import VillagerGui from 'minecraft-assets/minecraft-assets/data/1.17.1/gui/container/villager2.png'
|
||||
import EnchantingGui from 'minecraft-assets/minecraft-assets/data/1.17.1/gui/container/enchanting_table.png'
|
||||
import AnvilGui from 'minecraft-assets/minecraft-assets/data/1.17.1/gui/container/anvil.png'
|
||||
import BeaconGui from 'minecraft-assets/minecraft-assets/data/1.17.1/gui/container/beacon.png'
|
||||
|
||||
import Dirt from 'minecraft-assets/minecraft-assets/data/1.17.1/blocks/dirt.png'
|
||||
import { subscribeKey } from 'valtio/utils'
|
||||
|
|
@ -22,6 +28,7 @@ import mojangson from 'mojangson'
|
|||
import nbt from 'prismarine-nbt'
|
||||
import { splitEvery, equals } from 'rambda'
|
||||
import PItem, { Item } from 'prismarine-item'
|
||||
import Generic95 from '../assets/generic_95.png'
|
||||
import { activeModalStack, hideCurrentModal, miscUiState, showModal } from './globalState'
|
||||
import invspriteJson from './invsprite.json'
|
||||
import { options } from './optionsStorage'
|
||||
|
|
@ -184,6 +191,13 @@ const getImageSrc = (path): string | HTMLImageElement => {
|
|||
case 'gui/container/crafting_table': return CraftingTableGui
|
||||
case 'gui/container/shulker_box': return ChestLikeGui
|
||||
case 'gui/container/generic_54': return LargeChestLikeGui
|
||||
case 'gui/container/generic_95': return Generic95
|
||||
case 'gui/container/hopper': return HopperGui
|
||||
case 'gui/container/horse': return HorseGui
|
||||
case 'gui/container/villager2': return VillagerGui
|
||||
case 'gui/container/enchanting_table': return EnchantingGui
|
||||
case 'gui/container/anvil': return AnvilGui
|
||||
case 'gui/container/beacon': return BeaconGui
|
||||
}
|
||||
return Dirt
|
||||
}
|
||||
|
|
@ -228,7 +242,7 @@ const isFullBlock = (block: string) => {
|
|||
return shape[0] === 0 && shape[1] === 0 && shape[2] === 0 && shape[3] === 1 && shape[4] === 1 && shape[5] === 1
|
||||
}
|
||||
|
||||
type RenderSlot = Pick<import('prismarine-item').Item, 'name' | 'displayName'>
|
||||
type RenderSlot = Pick<import('prismarine-item').Item, 'name' | 'displayName' | 'durabilityUsed' | 'maxDurability' | 'enchants'>
|
||||
const renderSlot = (slot: RenderSlot, skipBlock = false): { texture: string, blockData?, scale?: number, slice?: number[] } | undefined => {
|
||||
const itemName = slot.name
|
||||
const isItem = loadedData.itemsByName[itemName]
|
||||
|
|
@ -332,11 +346,21 @@ export const onModalClose = (callback: () => any) => {
|
|||
const implementedContainersGuiMap = {
|
||||
// todo allow arbitrary size instead!
|
||||
'minecraft:generic_9x3': 'ChestWin',
|
||||
'minecraft:generic_9x5': 'Generic95Win',
|
||||
// hopper
|
||||
'minecraft:generic_5x1': 'HopperWin',
|
||||
'minecraft:generic_9x6': 'LargeChestWin',
|
||||
'minecraft:generic_3x3': 'DropDispenseWin',
|
||||
'minecraft:furnace': 'FurnaceWin',
|
||||
'minecraft:smoker': 'FurnaceWin',
|
||||
'minecraft:crafting': 'CraftingWin'
|
||||
'minecraft:crafting': 'CraftingWin',
|
||||
'minecraft:anvil': 'AnvilWin',
|
||||
// enchant
|
||||
'minecraft:enchanting_table': 'EnchantingWin',
|
||||
// horse
|
||||
'minecraft:horse': 'HorseWin',
|
||||
// villager
|
||||
'minecraft:villager': 'VillagerWin',
|
||||
}
|
||||
|
||||
const upJei = (search: string) => {
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ type Story = StoryObj<typeof Chat>
|
|||
|
||||
export const Primary: Story = {
|
||||
args: {
|
||||
usingTouch: false,
|
||||
messages: [{
|
||||
parts: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ div.chat-wrapper {
|
|||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-width: var(--thin-if-firefox);
|
||||
}
|
||||
|
||||
.chat.opened {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ const MessageLine = ({ message }: { message: Message }) => {
|
|||
|
||||
type Props = {
|
||||
messages: Message[]
|
||||
usingTouch: boolean
|
||||
opacity?: number
|
||||
opened?: boolean
|
||||
onClose?: () => void
|
||||
|
|
@ -52,9 +53,7 @@ export const fadeMessage = (message: Message, initialTimeout: boolean, requestUp
|
|||
}, initialTimeout ? 5000 : 0)
|
||||
}
|
||||
|
||||
export default ({ messages, opacity = 1, fetchCompletionItems, opened, sendMessage, onClose }: Props) => {
|
||||
const usingTouch = useSnapshot(miscUiState).currentTouch
|
||||
|
||||
export default ({ messages, opacity = 1, fetchCompletionItems, opened, sendMessage, onClose, usingTouch }: Props) => {
|
||||
const sendHistoryRef = useRef(JSON.parse(window.sessionStorage.chatHistory || '[]'))
|
||||
|
||||
const [completePadText, setCompletePadText] = useState('')
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { formatMessage } from '../botUtils'
|
||||
import { getBuiltinCommandsList, tryHandleBuiltinCommand } from '../builtinCommands'
|
||||
import { hideCurrentModal } from '../globalState'
|
||||
import { hideCurrentModal, miscUiState } from '../globalState'
|
||||
import { options } from '../optionsStorage'
|
||||
import ChatContainer, { Message, fadeMessage } from './ChatContainer'
|
||||
import { useIsModalActive } from './utils'
|
||||
|
|
@ -11,6 +12,7 @@ export default () => {
|
|||
const isChatActive = useIsModalActive('chat')
|
||||
const { messagesLimit, chatOpacity, chatOpacityOpened } = options
|
||||
const lastMessageId = useRef(0)
|
||||
const usingTouch = useSnapshot(miscUiState).currentTouch
|
||||
|
||||
useEffect(() => {
|
||||
bot.addListener('message', (jsonMsg, position) => {
|
||||
|
|
@ -33,6 +35,7 @@ export default () => {
|
|||
}, [])
|
||||
|
||||
return <ChatContainer
|
||||
usingTouch={!!usingTouch}
|
||||
opacity={(isChatActive ? chatOpacityOpened : chatOpacity) / 100}
|
||||
messages={messages}
|
||||
opened={isChatActive}
|
||||
|
|
|
|||
52
src/react/ConceptCommandsGui.stories.tsx
Normal file
52
src/react/ConceptCommandsGui.stories.tsx
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
import Button from './Button'
|
||||
|
||||
const defaultIcon = <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M3 21H5H19H21V3H19H5H3V21ZM19 5V19H5V5H19ZM11 17H13V11H15V9H13V7H11V9H9V11H11V17ZM9 13V11H7V13H9ZM17 13H15V11H17V13Z" fill="currentColor"></path></svg>
|
||||
|
||||
const Button2 = ({ title, icon }) => {
|
||||
//@ts-expect-error
|
||||
return <Button style={{ '--scale': 4 }}>
|
||||
<div style={{ fontSize: '22px', fontWeight: 'bold', display: 'flex', gap: 3, flexDirection: 'column', alignItems: 'center' }}>
|
||||
<div>
|
||||
{title}
|
||||
</div>
|
||||
{/* <iconify-icon icon="pixelarticons: */}
|
||||
<div style={{ width: 30, height: 30 }} className='full-svg'>
|
||||
{icon}
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
}
|
||||
|
||||
const Comp = () => {
|
||||
return <div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(4, 1fr)',
|
||||
gap: 10
|
||||
}}>
|
||||
<Button2 title="/give" icon={defaultIcon} />
|
||||
<Button2 title="/tell" icon={<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M4 2h18v16H6v2H4v-2h2v-2h14V4H4v18H2V2h2zm5 7H7v2h2V9zm2 0h2v2h-2V9zm6 0h-2v2h2V9z" fill="currentColor" /> </svg>} />
|
||||
<Button2 title="/setblock" icon={<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M2 2h20v20H2V2zm2 2v4h4V4H4zm6 0v4h4V4h-4zm6 0v4h4V4h-4zm4 6h-4v4h4v-4zm0 6h-4v4h4v-4zm-6 4v-4h-4v4h4zm-6 0v-4H4v4h4zm-4-6h4v-4H4v4zm6-4v4h4v-4h-4z" fill="currentColor" /> </svg>} />
|
||||
<Button2 title="/tp" icon={<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M16 5H2v14h14v-2h2v-2h2v-2h2v-2h-2V9h-2V7h-2V5zm0 2v2h2v2h2v2h-2v2h-2v2H4V7h12z" fill="currentColor" /> </svg>} />
|
||||
<Button2 title="/clone" icon={<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M5 3H3v2h2V3zm2 4h2v2H7V7zm4 0h2v2h-2V7zm2 12h-2v2h2v-2zm2 0h2v2h-2v-2zm6 0h-2v2h2v-2zM7 11h2v2H7v-2zm14 0h-2v2h2v-2zm-2 4h2v2h-2v-2zM7 19h2v2H7v-2zM19 7h2v2h-2V7zM7 3h2v2H7V3zm2 12H7v2h2v-2zM3 7h2v2H3V7zm14 0h-2v2h2V7zM3 11h2v2H3v-2zm2 4H3v2h2v-2zm6-12h2v2h-2V3zm6 0h-2v2h2V3z" fill="currentColor" /> </svg>} />
|
||||
<Button2 title="/fill" icon={<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M21 3h-8v2h4v2h2v4h2V3zm-4 4h-2v2h-2v2h2V9h2V7zm-8 8h2v-2H9v2H7v2h2v-2zm-4-2v4h2v2H5h6v2H3v-8h2z" fill="currentColor" /> </svg>} />
|
||||
<Button2 title="/home" icon={<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M14 2h-4v2H8v2H6v2H4v2H2v2h2v10h7v-6h2v6h7V12h2v-2h-2V8h-2V6h-2V4h-2V2zm0 2v2h2v2h2v2h2v2h-2v8h-3v-6H9v6H6v-8H4v-2h2V8h2V6h2V4h4z" fill="currentColor" /> </svg>} />
|
||||
<Button2 title="/time" icon={<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24"> <path d="M20 0h2v2h2v2h-2v2h-2V4h-2V2h2V0ZM8 4h8v2h-2v2h-2V6H8V4ZM6 8V6h2v2H6Zm0 8H4V8h2v8Zm2 2H6v-2h2v2Zm8 0v2H8v-2h8Zm2-2v2h-2v-2h2Zm-2-4v-2h2V8h2v8h-2v-4h-2Zm-4 0h4v2h-4v-2Zm0 0V8h-2v4h2Zm-8 6H2v2H0v2h2v2h2v-2h2v-2H4v-2Z" /> </svg>} />
|
||||
<Button2 title="/gamerule" icon={<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M4 5h16v2H4V5zm0 12H2V7h2v10zm16 0v2H4v-2h16zm0 0h2V7h-2v10zm-2-8h-4v6h4V9z" fill="currentColor" /> </svg>} />
|
||||
<Button2 title="/vanish" icon={<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M8 6h8v2H8V6zm-4 4V8h4v2H4zm-2 2v-2h2v2H2zm0 2v-2H0v2h2zm2 2H2v-2h2v2zm4 2H4v-2h4v2zm8 0v2H8v-2h8zm4-2v2h-4v-2h4zm2-2v2h-2v-2h2zm0-2h2v2h-2v-2zm-2-2h2v2h-2v-2zm0 0V8h-4v2h4zm-10 1h4v4h-4v-4z" fill="currentColor" /> </svg>} />
|
||||
<Button2 title="/clear" icon={<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M16 2v4h6v2h-2v14H4V8H2V6h6V2h8zm-2 2h-4v2h4V4zm0 4H6v12h12V8h-4z" fill="currentColor" /> </svg>} />
|
||||
<Button2 title="/setspawnpoint" icon={<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M13 2v4h5v5h4v2h-4v5h-5v4h-2v-4H6v-5H2v-2h4V6h5V2h2zM8 8v8h8V8H8zm2 2h4v4h-4v-4z" fill="currentColor" /> </svg>} />
|
||||
</div>
|
||||
}
|
||||
const meta: Meta<any> = {
|
||||
component: Comp,
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<any>;
|
||||
|
||||
export const Primary: Story = {
|
||||
args: {
|
||||
},
|
||||
}
|
||||
4
src/react/PixelartIcon.tsx
Normal file
4
src/react/PixelartIcon.tsx
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// names: https://pixelarticons.com/free/
|
||||
export default ({ iconName, width, styles = {}, className = undefined }) => {
|
||||
return <iconify-icon icon={`pixelarticons:${iconName}`} style={{ width, height: width, ...styles }} className={className} />
|
||||
}
|
||||
19
src/react/TouchAreas.stories.tsx
Normal file
19
src/react/TouchAreas.stories.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import TouchAreasControls from './TouchAreasControls'
|
||||
|
||||
const meta: Meta<typeof TouchAreasControls> = {
|
||||
component: TouchAreasControls,
|
||||
args: {
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof TouchAreasControls>;
|
||||
|
||||
export const Primary: Story = {
|
||||
args: {
|
||||
touchActive: true,
|
||||
setupActive: true,
|
||||
},
|
||||
}
|
||||
109
src/react/TouchAreasControls.tsx
Normal file
109
src/react/TouchAreasControls.tsx
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
import { CSSProperties, useEffect, useRef, useState } from 'react'
|
||||
import PixelartIcon from './PixelartIcon'
|
||||
|
||||
export type Button = 'action' | 'sneak' | 'break'
|
||||
|
||||
interface Props {
|
||||
touchActive: boolean
|
||||
setupActive: boolean
|
||||
buttonsPositions: Record<Button, [number, number]>
|
||||
}
|
||||
|
||||
export default ({ touchActive, setupActive, buttonsPositions }: Props) => {
|
||||
if (setupActive) touchActive = true
|
||||
|
||||
const [joystickPosition, setJoystickPosition] = useState(null as { x, y, pointerId } | null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!touchActive) return
|
||||
const controller = new AbortController()
|
||||
const { signal } = controller
|
||||
addEventListener('pointerdown', (e) => {
|
||||
if (e.pointerId === joystickPosition?.pointerId) {
|
||||
const x = e.clientX - joystickPosition.x
|
||||
const y = e.clientY - joystickPosition.y
|
||||
const supportsPressure = (e as any).pressure !== undefined && (e as any).pressure !== 0 && (e as any).pressure !== 0.5 && (e as any).pressure !== 1 && (e.pointerType === 'touch' || e.pointerType === 'pen')
|
||||
if ((e as any).pressure > 0.5) {
|
||||
// todo
|
||||
}
|
||||
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (e.clientX < window.innerWidth / 2) {
|
||||
setJoystickPosition({
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
pointerId: e.pointerId,
|
||||
})
|
||||
}
|
||||
}, {
|
||||
signal,
|
||||
})
|
||||
return () => {
|
||||
controller.abort()
|
||||
}
|
||||
}, [touchActive])
|
||||
|
||||
buttonsPositions = {
|
||||
// 0-100
|
||||
action: [
|
||||
90,
|
||||
70
|
||||
],
|
||||
sneak: [
|
||||
90,
|
||||
90
|
||||
],
|
||||
break: [
|
||||
70,
|
||||
70
|
||||
]
|
||||
}
|
||||
|
||||
const buttonStyles = (name: Button) => ({
|
||||
padding: 10,
|
||||
position: 'fixed',
|
||||
left: `${buttonsPositions[name][0]}%`,
|
||||
top: `${buttonsPositions[name][1]}%`,
|
||||
borderRadius: '50%',
|
||||
} satisfies CSSProperties)
|
||||
|
||||
return <div>
|
||||
<div
|
||||
className='movement_joystick_outer'
|
||||
style={{
|
||||
display: joystickPosition ? 'block' : 'none',
|
||||
borderRadius: '50%',
|
||||
width: 50,
|
||||
height: 50,
|
||||
border: '2px solid rgba(0, 0, 0, 0.5)',
|
||||
backgroundColor: 'rgba(255, 255, div, 0.5)',
|
||||
position: 'fixed',
|
||||
left: joystickPosition?.x,
|
||||
top: joystickPosition?.y,
|
||||
}}>
|
||||
<div
|
||||
className='movement_joystick_inner'
|
||||
style={{
|
||||
borderRadius: '50%',
|
||||
width: 20,
|
||||
height: 20,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
position: 'absolute',
|
||||
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div style={buttonStyles('action')}>
|
||||
<PixelartIcon width={10} iconName='circle' />
|
||||
</div>
|
||||
<div style={buttonStyles('sneak')}>
|
||||
<PixelartIcon width={10} iconName='arrow-down' />
|
||||
</div>
|
||||
<div style={buttonStyles('break')}>
|
||||
<PixelartIcon width={10} iconName='arrow-down' />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
13
src/react/TouchAreasControlsProvider.tsx
Normal file
13
src/react/TouchAreasControlsProvider.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { useSnapshot } from 'valtio'
|
||||
import { activeModalStack } from '../globalState'
|
||||
import { options } from '../optionsStorage'
|
||||
import TouchAreasControls from './TouchAreasControls'
|
||||
import { useIsModalActive, useUsingTouch } from './utils'
|
||||
|
||||
export default () => {
|
||||
const usingTouch = useUsingTouch()
|
||||
const hasModals = useSnapshot(activeModalStack).length !== 0
|
||||
const setupActive = useIsModalActive('touch-areas-setup')
|
||||
|
||||
return <TouchAreasControls touchActive={!!(usingTouch && hasModals)} setupActive={setupActive} buttonsPositions={options.touchControlsPositions} />
|
||||
}
|
||||
|
|
@ -14,7 +14,8 @@
|
|||
flex: 1;
|
||||
margin: 5px;
|
||||
overflow: auto;
|
||||
scrollbar-width: thin;
|
||||
/* todo think of better workaround */
|
||||
scrollbar-width: var(--thin-if-firefox);
|
||||
}
|
||||
|
||||
.world_root {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import TouchControls from './react/TouchControls'
|
|||
import widgets from './react/widgets'
|
||||
import { useIsWidgetActive } from './react/utils'
|
||||
import GlobalSearchInput from './GlobalSearchInput'
|
||||
import TouchAreasControlsProvider from './react/TouchAreasControlsProvider'
|
||||
|
||||
const Portal = ({ children, to }) => {
|
||||
return createPortal(children, to)
|
||||
|
|
@ -59,6 +60,7 @@ const InGameUi = () => {
|
|||
<DeathScreenProvider />
|
||||
<ChatProvider />
|
||||
<SoundMuffler />
|
||||
<TouchAreasControlsProvider />
|
||||
</Portal>
|
||||
<DisplayQr />
|
||||
<Portal to={document.body}>
|
||||
|
|
|
|||
|
|
@ -187,3 +187,11 @@ export function assertDefined<T> (x: T | undefined): asserts x is T {
|
|||
export const haveDirectoryPicker = () => {
|
||||
return !!window.showDirectoryPicker
|
||||
}
|
||||
|
||||
const reportedWarnings = new Set<string>()
|
||||
|
||||
export const reportWarningOnce = (id: string, message: string) => {
|
||||
if (reportedWarnings.has(id)) return
|
||||
reportedWarnings.add(id)
|
||||
console.warn(message)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue