Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/next-deploy.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Vercel Deploy Next
name: Vercel Deploy Next (Beta)
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
Expand Down Expand Up @@ -63,7 +63,7 @@ jobs:
path: cypress/screenshots/
- name: Set deployment aliases
run: |
for alias in $(echo ${{ secrets.TEST_PREVIEW_DOMAIN }} | tr "," "\n"); do
for alias in $(echo ${{ vars.TEST_PREVIEW_DOMAIN }} | tr "," "\n"); do
vercel alias set ${{ steps.deploy.outputs.stdout }} $alias --token=${{ secrets.VERCEL_TOKEN }} --scope=zaro
done

Expand Down
5 changes: 0 additions & 5 deletions .github/workflows/preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,6 @@ jobs:
env:
CONFIG_JSON_SOURCE: BUNDLED
LOCAL_CONFIG_FILE: config.mcraft-only.json
- name: Copy playground files
run: |
mkdir -p .vercel/output/static/playground
pnpm build-playground
cp -r renderer/dist/* .vercel/output/static/playground/
- name: Write pr redirect index.html
run: |
mkdir -p .vercel/output/static/pr
Expand Down
4 changes: 2 additions & 2 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -225,10 +225,10 @@ Only during development:

### Notable Things that Power this Project

- [Mineflayer](https://github.com/PrismarineJS/mineflayer) - Handles all client-side communications with the server (including the built-in one)
- [Mineflayer](https://github.com/GenerelSchwerz/mineflayer) - Handles all client-side communications with the server (including the built-in one)
- [Forked Flying Squid (Space Squid)](https://github.com/zardoy/space-squid) - The built-in offline server that makes singleplayer & P2P possible!
- [Prismarine Provider Anvil](https://github.com/PrismarineJS/prismarine-provider-anvil) - Handles world loading (region format)
- [Prismarine Physics](https://github.com/PrismarineJS/prismarine-physics) - Does all the physics calculations
- [Prismarine Physics](https://github.com/nxg-org/mineflayer-physics-utils/tree/master/src/physics/engines/botcraft.ts) - Does all the physics calculations
- [Minecraft Protocol](https://github.com/PrismarineJS/node-minecraft-protocol) - Makes connections to servers possible
- [Peer.js](https://peerjs.com/) - P2P networking (when you open to wan)
- [Three.js](https://threejs.org/) - Helping in 3D rendering
Expand Down
61 changes: 61 additions & 0 deletions src/core/iframeChannels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { proxy } from 'valtio'

export const iframeState = proxy({
id: '',
url: '',
title: '',
metadata: null as Record<string, any> | null,
})
globalThis.iframeState = iframeState

export const registerIframeChannels = () => {
registerIframeOpenChannel()
}

const registerIframeOpenChannel = () => {
const CHANNEL_NAME = 'minecraft-web-client:iframe-open'

const packetStructure = [
'container',
[
{
name: 'id',
type: ['pstring', { countType: 'i16' }]
},
{
name: 'url',
type: ['pstring', { countType: 'i16' }]
},
{
name: 'title',
type: ['pstring', { countType: 'i16' }]
},
{
name: 'metadataJson',
type: ['pstring', { countType: 'i16' }]
}
]
]

bot._client.registerChannel(CHANNEL_NAME, packetStructure, true)

bot._client.on(CHANNEL_NAME as any, (data) => {
const { id, url, title, metadataJson } = data

let metadata: Record<string, any> | null = null
if (metadataJson && metadataJson.trim() !== '') {
try {
metadata = JSON.parse(metadataJson)
} catch (error) {
console.warn('Failed to parse iframe metadataJson:', error)
}
}

iframeState.id = id
iframeState.url = url
iframeState.title = title || ''
iframeState.metadata = metadata
})

console.debug(`registered custom channel ${CHANNEL_NAME} channel`)
}
39 changes: 39 additions & 0 deletions src/customChannels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods'
import { options, serverChangedSettings } from './optionsStorage'
import { jeiCustomCategories } from './inventoryWindows'
import { registerIdeChannels } from './core/ideChannels'
import { registerIframeChannels } from './core/iframeChannels'
import { serverSafeSettings } from './defaultOptions'
import { lastConnectOptions } from './appStatus'
import { gameAdditionalState } from './globalState'

const isWebSocketServer = (server: string | undefined) => {
if (!server) return false
Expand All @@ -30,7 +32,9 @@ export default () => {
registerWaypointChannels()
registerFireworksChannels()
registerIdeChannels()
registerIframeChannels()
registerServerSettingsChannel()
registerTypingIndicatorChannel()
})
})
}
Expand Down Expand Up @@ -611,6 +615,41 @@ const registerServerSettingsChannel = () => {
}, false) // Don't wait for world, settings can be applied before world loads
}

const registerTypingIndicatorChannel = () => {
const CHANNEL_NAME = 'minecraft-web-client:typing-indicator'
const packetStructure = [
'container',
[
{
name: 'username',
type: ['pstring', { countType: 'i16' }]
},
{
name: 'isTyping',
type: 'bool'
}
]
]

registerChannel(CHANNEL_NAME, packetStructure, (data) => {
const { username, isTyping } = data

if (isTyping) {
// Add user to typing list if not already there
const existingIndex = gameAdditionalState.typingUsers.findIndex(user => user.username === username)
if (existingIndex === -1) {
gameAdditionalState.typingUsers.push({ username, timestamp: Date.now() })
} else {
// Update timestamp for existing user
gameAdditionalState.typingUsers[existingIndex].timestamp = Date.now()
}
} else {
// Remove user from typing list
gameAdditionalState.typingUsers = gameAdditionalState.typingUsers.filter(user => user.username !== username)
}
})
}

function getCurrentTopDomain (): string {
const { hostname } = location
// Split hostname into parts
Expand Down
49 changes: 34 additions & 15 deletions src/customCommands.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { guiOptionsScheme, tryFindOptionConfig } from './optionsGuiScheme'
import { optionsMeta } from './defaultOptions'
import { options } from './optionsStorage'

export const customCommandsConfig = {
Expand Down Expand Up @@ -29,22 +29,29 @@ export const customCommandsConfig = {
if (!action || value === undefined || action === 'toggle') return null
if (action === 'set') {
const getBase = () => {
const config = tryFindOptionConfig(setting as any)
if (config && 'values' in config) {
const config = optionsMeta[setting as keyof typeof optionsMeta]
if (config?.possibleValues && config.possibleValues.length > 0) {
// Handle both string[] and Array<[string, string]> formats
const { possibleValues } = config
const options = Array.isArray(possibleValues[0]) && typeof possibleValues[0][0] === 'string'
? (possibleValues as Array<[string, string]>).map(([val]) => val)
: possibleValues as string[]
return {
type: 'select',
options: config.values
options
}
}
if (config?.type === 'toggle' || typeof value === 'boolean') {
if (typeof value === 'boolean') {
return {
type: 'select',
options: ['true', 'false']
}
}
if (config?.type === 'slider' || value.type === 'number') {
if (typeof value === 'number') {
return {
type: 'number',
min: config?.min,
max: config?.max,
}
}
return {
Expand All @@ -53,25 +60,37 @@ export const customCommandsConfig = {
}
return {
...getBase(),
placeholder: value
placeholder: String(value)
}
}
}
],
handler ([setting, action, value]) {
if (action === 'toggle' || action === undefined) {
const value = options[setting]
const config = tryFindOptionConfig(setting)
if (config && 'values' in config && config.values) {
const { values } = config
const currentIndex = values.indexOf(value)
const currentValue = options[setting]
const config = optionsMeta[setting as keyof typeof optionsMeta]
if (config?.possibleValues && config.possibleValues.length > 0) {
// Handle both string[] and Array<[string, string]> formats
const { possibleValues } = config
const values = Array.isArray(possibleValues[0]) && typeof possibleValues[0][0] === 'string'
? (possibleValues as Array<[string, string]>).map(([val]) => val)
: possibleValues as string[]
const currentIndex = values.indexOf(String(currentValue))
const nextIndex = (currentIndex + 1) % values.length
options[setting] = values[nextIndex]
options[setting] = values[nextIndex] as any
} else {
options[setting] = typeof value === 'boolean' ? !value : typeof value === 'number' ? value + 1 : value
options[setting] = typeof currentValue === 'boolean' ? !currentValue : typeof currentValue === 'number' ? currentValue + 1 : currentValue
}
} else {
options[setting] = value
// Convert string values to appropriate types
const config = optionsMeta[setting as keyof typeof optionsMeta]
let convertedValue: any = value
if (typeof options[setting] === 'boolean') {
convertedValue = value === 'true' || value === true
} else if (typeof options[setting] === 'number') {
convertedValue = Number(value)
}
options[setting] = convertedValue
}
}
},
Expand Down
86 changes: 86 additions & 0 deletions src/defaultOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,89 @@ export const serverSafeSettings: Partial<Record<keyof typeof defaultOptions, tru
newVersionsLighting: true,
showCursorBlockInSpectator: true,
}
export type OptionValueType = string | number | boolean | string[] | Record<string, any> | null

export type OptionPossibleValues =
| string[]
| Array<[string, string]> // [value, label] tuples

export type OptionMeta = {
possibleValues?: OptionPossibleValues
isCustomInput?: boolean // If true, use showInputsModal for string input
min?: number
max?: number
unit?: string
text?: string
tooltip?: string
}

export const optionsMeta: Partial<Record<keyof typeof defaultOptions, OptionMeta>> = {
gpuPreference: {
possibleValues: [['default', 'Auto'], ['high-performance', 'Dedicated'], ['low-power', 'Low Power']]
},
backgroundRendering: {
possibleValues: [
['full', 'NO'],
['5fps', '5 FPS'],
['20fps', '20 FPS'],
]
},
activeRenderer: {
possibleValues: [
['threejs', 'Three.js (stable)'],
]
},
renderDebug: {
possibleValues: ['advanced', 'basic', 'none']
},
serverResourcePacks: {
possibleValues: ['prompt', 'always', 'never']
},
showMinimap: {
possibleValues: ['always', 'singleplayer', 'never']
},
highlightBlockColor: {
possibleValues: [
['auto', 'Auto'],
['blue', 'Blue'],
['classic', 'Classic']
]
},
wysiwygSignEditor: {
possibleValues: ['auto', 'always', 'never']
},
touchMovementType: {
possibleValues: [['modern', 'Modern'], ['classic', 'Classic']]
},
touchInteractionType: {
possibleValues: [['classic', 'Classic'], ['buttons', 'Buttons']]
},
autoJump: {
possibleValues: ['always', 'auto', 'never']
},
saveLoginPassword: {
possibleValues: ['prompt', 'always', 'never']
},
packetsLoggerPreset: {
possibleValues: [
['all', 'All'],
['no-buffers', 'No Buffers']
]
},
// Custom string inputs (will use showInputsModal)
localUsername: {
isCustomInput: true
},
guestUsername: {
isCustomInput: true
},
language: {
isCustomInput: true
},
enabledResourcepack: {
isCustomInput: true
},
useVersionsTextures: {
isCustomInput: true
}
}
1 change: 1 addition & 0 deletions src/globalState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ export const gameAdditionalState = proxy({
viewerConnection: false,

usingServerResourcePack: false,
typingUsers: [] as Array<{ username: string; timestamp: number }>,
})

window.gameAdditionalState = gameAdditionalState
Loading
Loading