gamedev 0.0.2-alpha.0 → 0.0.4-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app-server/WorldManifest.js +1 -1
- package/app-server/cli.js +1 -1
- package/app-server/commands.js +7 -7
- package/app-server/direct.js +3 -3
- package/app-server/scaffold.js +9 -9
- package/app-server/targets.js +1 -1
- package/app-server/templates/claude/skills/hyperfy-app-scripting/SKILL.md +2 -2
- package/bin/{hyperfy.mjs → gamedev.mjs} +16 -125
- package/build/public/admin.html +2 -2
- package/build/public/index.html +2 -2
- package/package.json +3 -2
- package/src/core/packets.js +78 -0
|
@@ -22,7 +22,7 @@ export class WorldManifest {
|
|
|
22
22
|
if (parsed.formatVersion !== 2) {
|
|
23
23
|
throw new Error(
|
|
24
24
|
'Invalid world.json format version. Expected formatVersion 2.\n' +
|
|
25
|
-
'Run "
|
|
25
|
+
'Run "gamedev world export" to regenerate world.json (formatVersion 2).'
|
|
26
26
|
)
|
|
27
27
|
}
|
|
28
28
|
|
package/app-server/cli.js
CHANGED
|
@@ -17,7 +17,7 @@ async function main() {
|
|
|
17
17
|
console.error(`❌ ${err?.message || err}`)
|
|
18
18
|
process.exit(1)
|
|
19
19
|
}
|
|
20
|
-
const exitCode = await runAppCommand({ command, args, helpPrefix: '
|
|
20
|
+
const exitCode = await runAppCommand({ command, args, helpPrefix: 'gamedev apps' })
|
|
21
21
|
process.exit(exitCode)
|
|
22
22
|
}
|
|
23
23
|
|
package/app-server/commands.js
CHANGED
|
@@ -246,7 +246,7 @@ export class HyperfyCLI {
|
|
|
246
246
|
const blueprints = listLocalBlueprints(this.appsDir)
|
|
247
247
|
if (blueprints.length === 0) {
|
|
248
248
|
console.log(`📝 No local blueprints found in ${this.appsDir}`)
|
|
249
|
-
console.log(`💡 Run "
|
|
249
|
+
console.log(`💡 Run "gamedev world export" to pull blueprints from the world.`)
|
|
250
250
|
console.log(` Use --include-built-scripts if you need script code locally.`)
|
|
251
251
|
return
|
|
252
252
|
}
|
|
@@ -399,7 +399,7 @@ app.on("update", (delta) => {
|
|
|
399
399
|
console.log(`✅ Successfully created app in world: ${appName}`)
|
|
400
400
|
console.log(` • Blueprint: ${blueprintId}`)
|
|
401
401
|
console.log(` • Entity: ${entityId}`)
|
|
402
|
-
console.log(`💡 Run "
|
|
402
|
+
console.log(`💡 Run "gamedev world export" to sync into ${this.appsDir}.`)
|
|
403
403
|
console.log(` Use --include-built-scripts if you need script code locally.`)
|
|
404
404
|
} catch (error) {
|
|
405
405
|
console.error(`❌ Error creating app:`, error?.message || error)
|
|
@@ -563,7 +563,7 @@ app.on("update", (delta) => {
|
|
|
563
563
|
console.log(`✅ Script validation passed for ${appName}`)
|
|
564
564
|
console.log(`🔗 Hash: ${localHash}`)
|
|
565
565
|
} else {
|
|
566
|
-
console.log(`💡 Run '
|
|
566
|
+
console.log(`💡 Run 'gamedev apps deploy ${appName}' (or save the file with app-server running)`)
|
|
567
567
|
}
|
|
568
568
|
} catch (error) {
|
|
569
569
|
console.error(`❌ Error validating app:`, error?.message || error)
|
|
@@ -677,9 +677,9 @@ app.on("update", (delta) => {
|
|
|
677
677
|
}
|
|
678
678
|
}
|
|
679
679
|
|
|
680
|
-
showHelp({ commandPrefix = '
|
|
680
|
+
showHelp({ commandPrefix = 'gamedev apps' } = {}) {
|
|
681
681
|
console.log(`
|
|
682
|
-
🚀
|
|
682
|
+
🚀 Gamedev CLI (direct /admin mode)
|
|
683
683
|
|
|
684
684
|
Usage:
|
|
685
685
|
${commandPrefix} <command> [options]
|
|
@@ -697,7 +697,7 @@ Commands:
|
|
|
697
697
|
reset [--force] Delete local apps/assets/world.json
|
|
698
698
|
status Show /admin snapshot summary
|
|
699
699
|
help Show this help
|
|
700
|
-
--target <name> Use .
|
|
700
|
+
--target <name> Use .lobby/targets.json entry for WORLD_URL/WORLD_ID/ADMIN_CODE/DEPLOY_CODE
|
|
701
701
|
|
|
702
702
|
Options:
|
|
703
703
|
--dry-run, -n Show deploy plan without applying changes
|
|
@@ -739,7 +739,7 @@ export async function runAppCommand({ command, args = [], rootDir = process.cwd(
|
|
|
739
739
|
}
|
|
740
740
|
}
|
|
741
741
|
const cli = new HyperfyCLI({ rootDir })
|
|
742
|
-
const commandPrefix = helpPrefix || '
|
|
742
|
+
const commandPrefix = helpPrefix || 'gamedev apps'
|
|
743
743
|
let exitCode = 0
|
|
744
744
|
|
|
745
745
|
switch (command) {
|
package/app-server/direct.js
CHANGED
|
@@ -558,7 +558,7 @@ export class DirectAppServer {
|
|
|
558
558
|
} else if (!hasWorldFile && hasApps) {
|
|
559
559
|
throw new Error(
|
|
560
560
|
'world.json missing; cannot safely apply exact world layout. ' +
|
|
561
|
-
'Run "
|
|
561
|
+
'Run "gamedev world export" to generate it from the world, or create world.json to seed a new world.'
|
|
562
562
|
)
|
|
563
563
|
} else {
|
|
564
564
|
const manifest = this.manifest.read()
|
|
@@ -586,7 +586,7 @@ export class DirectAppServer {
|
|
|
586
586
|
const err = new Error(
|
|
587
587
|
'Local project is empty and this world already has content. ' +
|
|
588
588
|
'Script code is not downloaded by default. ' +
|
|
589
|
-
'Run "
|
|
589
|
+
'Run "gamedev world export --include-built-scripts" to scaffold from the world.'
|
|
590
590
|
)
|
|
591
591
|
err.code = 'empty_project_requires_export'
|
|
592
592
|
throw err
|
|
@@ -657,7 +657,7 @@ export class DirectAppServer {
|
|
|
657
657
|
async importWorldFromDisk() {
|
|
658
658
|
const manifest = this.manifest.read()
|
|
659
659
|
if (!manifest) {
|
|
660
|
-
throw new Error('world.json missing. Run "
|
|
660
|
+
throw new Error('world.json missing. Run "gamedev world export" to generate it first.')
|
|
661
661
|
}
|
|
662
662
|
const errors = this.manifest.validate(manifest)
|
|
663
663
|
if (errors.length) {
|
package/app-server/scaffold.js
CHANGED
|
@@ -29,9 +29,9 @@ dist/
|
|
|
29
29
|
!.env.example
|
|
30
30
|
|
|
31
31
|
# Local world state
|
|
32
|
-
.
|
|
33
|
-
!.
|
|
34
|
-
!.
|
|
32
|
+
.lobby/*
|
|
33
|
+
!.lobby/
|
|
34
|
+
!.lobby/targets.example.json
|
|
35
35
|
|
|
36
36
|
# Claude local settings
|
|
37
37
|
.claude/settings.local.json
|
|
@@ -56,7 +56,7 @@ const DEFAULT_TSCONFIG = {
|
|
|
56
56
|
|
|
57
57
|
function normalizePackageName(name, fallback) {
|
|
58
58
|
const raw = (name || '').trim() || (fallback || '').trim()
|
|
59
|
-
const base = raw || '
|
|
59
|
+
const base = raw || 'lobby-world'
|
|
60
60
|
const scopeMatch = base.startsWith('@') ? base.split('/') : null
|
|
61
61
|
if (scopeMatch && scopeMatch.length === 2) {
|
|
62
62
|
const scope = scopeMatch[0].toLowerCase().replace(/[^a-z0-9._-]+/g, '')
|
|
@@ -64,7 +64,7 @@ function normalizePackageName(name, fallback) {
|
|
|
64
64
|
if (scope && pkg) return `${scope}/${pkg}`
|
|
65
65
|
}
|
|
66
66
|
const normalized = base.toLowerCase().replace(/[^a-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '')
|
|
67
|
-
return normalized || '
|
|
67
|
+
return normalized || 'lobby-world'
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
function buildPackageJson({ packageName, sdkName, sdkVersion }) {
|
|
@@ -79,8 +79,8 @@ function buildPackageJson({ packageName, sdkName, sdkVersion }) {
|
|
|
79
79
|
private: true,
|
|
80
80
|
type: 'module',
|
|
81
81
|
scripts: {
|
|
82
|
-
dev: '
|
|
83
|
-
build: '
|
|
82
|
+
dev: 'gamedev dev',
|
|
83
|
+
build: 'gamedev apps build --all',
|
|
84
84
|
typecheck: 'tsc --noEmit',
|
|
85
85
|
},
|
|
86
86
|
devDependencies,
|
|
@@ -101,7 +101,7 @@ function resolveSdkVersion() {
|
|
|
101
101
|
|
|
102
102
|
function buildEnvExample() {
|
|
103
103
|
return `# Hyperfy project environment (example)
|
|
104
|
-
# Run "
|
|
104
|
+
# Run "gamedev dev" to generate a local .env automatically.
|
|
105
105
|
WORLD_URL=http://localhost:5000
|
|
106
106
|
WORLD_ID=local-your-world-id
|
|
107
107
|
ADMIN_CODE=your-admin-code
|
|
@@ -250,7 +250,7 @@ export function scaffoldBaseProject({
|
|
|
250
250
|
report,
|
|
251
251
|
})
|
|
252
252
|
|
|
253
|
-
writeFileWithPolicy(path.join(rootDir, '.
|
|
253
|
+
writeFileWithPolicy(path.join(rootDir, '.lobby', 'targets.example.json'), buildTargetsExample(), {
|
|
254
254
|
force,
|
|
255
255
|
writeFile,
|
|
256
256
|
report,
|
package/app-server/targets.js
CHANGED
|
@@ -16,7 +16,7 @@ description: Hyperfy world-project app scripting workflow and constraints. Use w
|
|
|
16
16
|
1. Identify the target app(s) and update the entry script at `apps/<AppName>/index.ts`.
|
|
17
17
|
2. Update blueprint defaults in `apps/<AppName>/*.json` (model, props, flags).
|
|
18
18
|
3. Keep per-instance overrides in `world.json`.
|
|
19
|
-
4. Use `
|
|
19
|
+
4. Use `gamedev dev` for continuous sync; use `gamedev apps build --all` and `gamedev apps deploy <app>` for explicit deploys.
|
|
20
20
|
|
|
21
21
|
## Runtime Constraints
|
|
22
22
|
|
|
@@ -38,4 +38,4 @@ description: Hyperfy world-project app scripting workflow and constraints. Use w
|
|
|
38
38
|
## Guardrails
|
|
39
39
|
|
|
40
40
|
- Do not edit `dist/` (build output).
|
|
41
|
-
- Do not edit `.
|
|
41
|
+
- Do not edit `.lobby/*` except `targets.json` (local only).
|
|
@@ -7,7 +7,7 @@ import { fileURLToPath } from 'url'
|
|
|
7
7
|
import { spawn } from 'child_process'
|
|
8
8
|
import { customAlphabet } from 'nanoid'
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { runAppCommand } from '../app-server/commands.js'
|
|
11
11
|
import { DirectAppServer } from '../app-server/direct.js'
|
|
12
12
|
import { scaffoldBaseProject, scaffoldBuiltins, writeManifest } from '../app-server/scaffold.js'
|
|
13
13
|
import { applyTargetEnv, parseTargetArgs, resolveTarget, readTargets } from '../app-server/targets.js'
|
|
@@ -59,7 +59,7 @@ function writeDotEnv(filePath, content) {
|
|
|
59
59
|
fs.writeFileSync(filePath, content.endsWith('\n') ? content : `${content}\n`, 'utf8')
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
const TARGETS_FILE = path.join('.
|
|
62
|
+
const TARGETS_FILE = path.join('.lobby', 'targets.json')
|
|
63
63
|
|
|
64
64
|
function writeTargetsFile(targets, rootDir = projectDir) {
|
|
65
65
|
const filePath = path.join(rootDir, TARGETS_FILE)
|
|
@@ -68,13 +68,6 @@ function writeTargetsFile(targets, rootDir = projectDir) {
|
|
|
68
68
|
return filePath
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
function isProjectEmpty(dirPath) {
|
|
72
|
-
const ignore = new Set(['.git', '.DS_Store'])
|
|
73
|
-
if (!fs.existsSync(dirPath)) return true
|
|
74
|
-
const entries = fs.readdirSync(dirPath, { withFileTypes: true })
|
|
75
|
-
return entries.every(entry => ignore.has(entry.name))
|
|
76
|
-
}
|
|
77
|
-
|
|
78
71
|
function generateAdminCode() {
|
|
79
72
|
return crypto.randomBytes(16).toString('base64url')
|
|
80
73
|
}
|
|
@@ -161,7 +154,7 @@ function isLocalWorld({ worldUrl }) {
|
|
|
161
154
|
}
|
|
162
155
|
|
|
163
156
|
function getWorldDir(worldId) {
|
|
164
|
-
return path.join(projectDir, '.
|
|
157
|
+
return path.join(projectDir, '.lobby', worldId)
|
|
165
158
|
}
|
|
166
159
|
|
|
167
160
|
function hasKey(env, key) {
|
|
@@ -282,15 +275,6 @@ function validateLocalEnv(env, derived) {
|
|
|
282
275
|
return issues
|
|
283
276
|
}
|
|
284
277
|
|
|
285
|
-
async function confirmAction(prompt) {
|
|
286
|
-
if (!process.stdin.isTTY) return false
|
|
287
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
288
|
-
const answer = await new Promise(resolve => rl.question(prompt, resolve))
|
|
289
|
-
rl.close()
|
|
290
|
-
const normalized = (answer || '').trim().toLowerCase()
|
|
291
|
-
return normalized === 'y' || normalized === 'yes'
|
|
292
|
-
}
|
|
293
|
-
|
|
294
278
|
async function promptValue(prompt) {
|
|
295
279
|
if (!process.stdin.isTTY) return null
|
|
296
280
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
@@ -302,12 +286,6 @@ async function promptValue(prompt) {
|
|
|
302
286
|
|
|
303
287
|
function ensureEnvForStart() {
|
|
304
288
|
if (!fs.existsSync(envPath)) {
|
|
305
|
-
if (!isProjectEmpty(projectDir)) {
|
|
306
|
-
console.error('Error: Missing .env in a non-empty project.')
|
|
307
|
-
console.error('Hint: Create a .env with WORLD_URL, WORLD_ID, ADMIN_CODE, and world server settings.')
|
|
308
|
-
return { ok: false }
|
|
309
|
-
}
|
|
310
|
-
|
|
311
289
|
const worldId = `local-${uuid()}`
|
|
312
290
|
const adminCode = generateAdminCode()
|
|
313
291
|
const jwtSecret = generateJwtSecret()
|
|
@@ -469,10 +447,10 @@ async function startCommand(args = []) {
|
|
|
469
447
|
|
|
470
448
|
function printInitHelp() {
|
|
471
449
|
console.log(`
|
|
472
|
-
|
|
450
|
+
Gamedev Init
|
|
473
451
|
|
|
474
452
|
Usage:
|
|
475
|
-
|
|
453
|
+
gamedev init [options]
|
|
476
454
|
|
|
477
455
|
Options:
|
|
478
456
|
--name <package> Package name (defaults to folder name)
|
|
@@ -568,7 +546,7 @@ async function initCommand(args = []) {
|
|
|
568
546
|
|
|
569
547
|
async function appsCommand(args) {
|
|
570
548
|
if (!args.length || ['help', '--help', '-h'].includes(args[0])) {
|
|
571
|
-
await runAppCommand({ command: 'help', args: [], rootDir: projectDir, helpPrefix: '
|
|
549
|
+
await runAppCommand({ command: 'help', args: [], rootDir: projectDir, helpPrefix: 'gamedev apps' })
|
|
572
550
|
return 0
|
|
573
551
|
}
|
|
574
552
|
|
|
@@ -589,7 +567,7 @@ async function appsCommand(args) {
|
|
|
589
567
|
const env = readDotEnv(envPath)
|
|
590
568
|
if (env) applyEnvToProcess(env)
|
|
591
569
|
|
|
592
|
-
return runAppCommand({ command, args: commandArgs, rootDir: projectDir, helpPrefix: '
|
|
570
|
+
return runAppCommand({ command, args: commandArgs, rootDir: projectDir, helpPrefix: 'gamedev apps' })
|
|
593
571
|
}
|
|
594
572
|
|
|
595
573
|
async function connectAdminServer({ worldUrl, adminCode, rootDir }) {
|
|
@@ -610,53 +588,6 @@ async function connectAdminServer({ worldUrl, adminCode, rootDir }) {
|
|
|
610
588
|
}
|
|
611
589
|
}
|
|
612
590
|
|
|
613
|
-
async function projectCommand(args) {
|
|
614
|
-
if (!args.length || ['help', '--help', '-h'].includes(args[0])) {
|
|
615
|
-
printHelp()
|
|
616
|
-
return 0
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
if (args[0] !== 'reset') {
|
|
620
|
-
console.error(`Error: Unknown project command: ${args[0]}`)
|
|
621
|
-
printHelp()
|
|
622
|
-
return 1
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
const force = args.includes('--force') || args.includes('-f')
|
|
626
|
-
const cli = new HyperfyCLI({ rootDir: projectDir })
|
|
627
|
-
await cli.reset({ force })
|
|
628
|
-
return 0
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
async function worldsCommand(args) {
|
|
632
|
-
if (!args.length || ['help', '--help', '-h'].includes(args[0]) || args[0] === 'list') {
|
|
633
|
-
const root = path.join(projectDir, '.hyperfy')
|
|
634
|
-
if (!fs.existsSync(root)) {
|
|
635
|
-
console.log('Worlds: No local worlds found.')
|
|
636
|
-
return 0
|
|
637
|
-
}
|
|
638
|
-
const entries = fs
|
|
639
|
-
.readdirSync(root, { withFileTypes: true })
|
|
640
|
-
.filter(entry => entry.isDirectory())
|
|
641
|
-
.map(entry => entry.name)
|
|
642
|
-
|
|
643
|
-
if (!entries.length) {
|
|
644
|
-
console.log('Worlds: No local worlds found.')
|
|
645
|
-
return 0
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
console.log('Worlds: Local worlds:')
|
|
649
|
-
for (const entry of entries) {
|
|
650
|
-
console.log(` - ${entry}`)
|
|
651
|
-
}
|
|
652
|
-
return 0
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
console.error(`Error: Unknown worlds command: ${args[0]}`)
|
|
656
|
-
printHelp()
|
|
657
|
-
return 1
|
|
658
|
-
}
|
|
659
|
-
|
|
660
591
|
async function worldCommand(args) {
|
|
661
592
|
if (!args.length || ['help', '--help', '-h'].includes(args[0])) {
|
|
662
593
|
printHelp()
|
|
@@ -665,7 +596,7 @@ async function worldCommand(args) {
|
|
|
665
596
|
|
|
666
597
|
const action = args[0]
|
|
667
598
|
|
|
668
|
-
if (action === 'export' || action === 'import'
|
|
599
|
+
if (action === 'export' || action === 'import') {
|
|
669
600
|
const env = readDotEnv(envPath)
|
|
670
601
|
if (!env) {
|
|
671
602
|
console.error('Error: Missing .env in this project.')
|
|
@@ -680,37 +611,6 @@ async function worldCommand(args) {
|
|
|
680
611
|
return 1
|
|
681
612
|
}
|
|
682
613
|
|
|
683
|
-
if (action === 'wipe') {
|
|
684
|
-
if (!isLocalWorld({ worldUrl })) {
|
|
685
|
-
console.error('Error: WORLD_URL does not indicate a local world. Refusing to wipe.')
|
|
686
|
-
return 1
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
const worldDir = getWorldDir(worldId)
|
|
690
|
-
if (!fs.existsSync(worldDir)) {
|
|
691
|
-
console.log(`Worlds: No local world found at ${worldDir}`)
|
|
692
|
-
return 0
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
const force = args.includes('--force') || args.includes('-f')
|
|
696
|
-
if (!force) {
|
|
697
|
-
const ok = await confirmAction(`Delete local world data at ${worldDir}? (y/N): `)
|
|
698
|
-
if (!ok) {
|
|
699
|
-
console.log('World wipe cancelled')
|
|
700
|
-
return 1
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
try {
|
|
705
|
-
fs.rmSync(worldDir, { recursive: true, force: true })
|
|
706
|
-
console.log(`Deleted ${worldDir}`)
|
|
707
|
-
return 0
|
|
708
|
-
} catch (error) {
|
|
709
|
-
console.error(`Error: Failed to delete ${worldDir}`, error?.message || error)
|
|
710
|
-
return 1
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
|
|
714
614
|
let server
|
|
715
615
|
try {
|
|
716
616
|
server = await connectAdminServer({ worldUrl, adminCode: env.ADMIN_CODE, rootDir: projectDir })
|
|
@@ -740,10 +640,10 @@ async function worldCommand(args) {
|
|
|
740
640
|
|
|
741
641
|
function printFlyHelp() {
|
|
742
642
|
console.log(`
|
|
743
|
-
|
|
643
|
+
Gamedev Fly
|
|
744
644
|
|
|
745
645
|
Usage:
|
|
746
|
-
|
|
646
|
+
gamedev fly <command> [options]
|
|
747
647
|
|
|
748
648
|
Commands:
|
|
749
649
|
init Generate fly.toml and optional GitHub workflow
|
|
@@ -755,11 +655,11 @@ Init options:
|
|
|
755
655
|
--region <code> Fly region (default: ams)
|
|
756
656
|
--persist Enable volume mount + SAVE_INTERVAL
|
|
757
657
|
--world-id <id> WORLD_ID override (default: fly-<app>)
|
|
758
|
-
--target <name> Update .
|
|
658
|
+
--target <name> Update .lobby/targets.json with worldUrl/worldId
|
|
759
659
|
--force, -f Overwrite existing fly.toml / workflow
|
|
760
660
|
|
|
761
661
|
Secrets options:
|
|
762
|
-
--target <name> Update .
|
|
662
|
+
--target <name> Update .lobby/targets.json with generated codes
|
|
763
663
|
--no-deploy-code Skip DEPLOY_CODE generation
|
|
764
664
|
--force, -f Overwrite existing target codes
|
|
765
665
|
`)
|
|
@@ -1067,26 +967,22 @@ async function flyCommand(args) {
|
|
|
1067
967
|
|
|
1068
968
|
function printHelp() {
|
|
1069
969
|
console.log(`
|
|
1070
|
-
|
|
970
|
+
Gamedev CLI
|
|
1071
971
|
|
|
1072
972
|
Usage:
|
|
1073
|
-
|
|
973
|
+
gamedev <command> [options]
|
|
1074
974
|
|
|
1075
975
|
Commands:
|
|
1076
976
|
init Scaffold a new world project in the current folder
|
|
1077
|
-
|
|
1078
|
-
dev Alias for start
|
|
977
|
+
dev Start the world (local or remote) + app-server sync
|
|
1079
978
|
apps <command> Manage apps (create, list, build, clean, deploy, update, validate, status)
|
|
1080
979
|
fly <command> Fly.io deployment helpers
|
|
1081
|
-
project reset [--force] Delete local apps/assets/world.json in this project
|
|
1082
980
|
world export Export world.json + apps/assets from the world (use --include-built-scripts for scripts)
|
|
1083
981
|
world import Import local apps + world.json into the world
|
|
1084
|
-
world wipe [--force] Delete the local world runtime directory for this project
|
|
1085
|
-
worlds list List local world directories in ./.hyperfy
|
|
1086
982
|
help Show this help
|
|
1087
983
|
|
|
1088
984
|
Options:
|
|
1089
|
-
--target <name> Use .
|
|
985
|
+
--target <name> Use .lobby/targets.json entry (applies to dev/apps)
|
|
1090
986
|
`)
|
|
1091
987
|
}
|
|
1092
988
|
|
|
@@ -1096,17 +992,12 @@ async function main() {
|
|
|
1096
992
|
switch (command) {
|
|
1097
993
|
case 'init':
|
|
1098
994
|
return initCommand(args)
|
|
1099
|
-
case 'start':
|
|
1100
995
|
case 'dev':
|
|
1101
996
|
return startCommand(args)
|
|
1102
997
|
case 'apps':
|
|
1103
998
|
return appsCommand(args)
|
|
1104
|
-
case 'project':
|
|
1105
|
-
return projectCommand(args)
|
|
1106
999
|
case 'world':
|
|
1107
1000
|
return worldCommand(args)
|
|
1108
|
-
case 'worlds':
|
|
1109
|
-
return worldsCommand(args)
|
|
1110
1001
|
case 'fly':
|
|
1111
1002
|
return flyCommand(args)
|
|
1112
1003
|
case 'help':
|
package/build/public/admin.html
CHANGED
|
@@ -25,14 +25,14 @@
|
|
|
25
25
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
26
26
|
<meta name="mobile-web-app-capable" content="yes" />
|
|
27
27
|
<link rel="preload" href="/rubik.woff2" as="font" type="font/woff2" crossorigin />
|
|
28
|
-
<link rel="stylesheet" type="text/css" href="/index.css?v=
|
|
28
|
+
<link rel="stylesheet" type="text/css" href="/index.css?v=1768873891919" />
|
|
29
29
|
<script>
|
|
30
30
|
window.PARTICLES_PATH = '/particles-4YQR4CFO.js'
|
|
31
31
|
</script>
|
|
32
32
|
</head>
|
|
33
33
|
<body>
|
|
34
34
|
<div id="root"></div>
|
|
35
|
-
<script src="/env.js?v=
|
|
35
|
+
<script src="/env.js?v=1768873891919"></script>
|
|
36
36
|
<script src="/admin-EJMJPJ7M.js" type="module"></script>
|
|
37
37
|
</body>
|
|
38
38
|
</html>
|
package/build/public/index.html
CHANGED
|
@@ -26,14 +26,14 @@
|
|
|
26
26
|
<meta name="mobile-web-app-capable" content="yes" />
|
|
27
27
|
<!-- <link rel='icon' href='/favicon.ico' /> -->
|
|
28
28
|
<link rel="preload" href="/rubik.woff2" as="font" type="font/woff2" crossorigin />
|
|
29
|
-
<link rel="stylesheet" type="text/css" href="/index.css?v=
|
|
29
|
+
<link rel="stylesheet" type="text/css" href="/index.css?v=1768873891919" />
|
|
30
30
|
<script>
|
|
31
31
|
window.PARTICLES_PATH = '/particles-4YQR4CFO.js'
|
|
32
32
|
</script>
|
|
33
33
|
</head>
|
|
34
34
|
<body>
|
|
35
35
|
<div id="root"></div>
|
|
36
|
-
<script src="/env.js?v=
|
|
36
|
+
<script src="/env.js?v=1768873891919"></script>
|
|
37
37
|
<script src="/index-RJRKEY6V.js" type="module"></script>
|
|
38
38
|
</body>
|
|
39
39
|
</html>
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gamedev",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4-alpha.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.node.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
7
|
"files": [
|
|
8
8
|
"build/**",
|
|
9
9
|
"app-server/**",
|
|
10
|
+
"src/core/packets.js",
|
|
10
11
|
"index.node.js",
|
|
11
12
|
"index.node-client.js",
|
|
12
13
|
"index.d.ts",
|
|
@@ -65,7 +66,7 @@
|
|
|
65
66
|
"test:integration": "node --test \"test/integration/**/*.test.js\""
|
|
66
67
|
},
|
|
67
68
|
"bin": {
|
|
68
|
-
"
|
|
69
|
+
"gamedev": "bin/gamedev.mjs"
|
|
69
70
|
},
|
|
70
71
|
"dependencies": {
|
|
71
72
|
"@aws-sdk/client-s3": "^3.859.0",
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Packr } from 'msgpackr'
|
|
2
|
+
|
|
3
|
+
const packr = new Packr({ structuredClone: true })
|
|
4
|
+
|
|
5
|
+
// prettier-ignore
|
|
6
|
+
const names = [
|
|
7
|
+
'snapshot',
|
|
8
|
+
'command',
|
|
9
|
+
'chatAdded',
|
|
10
|
+
'chatCleared',
|
|
11
|
+
'blueprintAdded',
|
|
12
|
+
'blueprintModified',
|
|
13
|
+
'entityAdded',
|
|
14
|
+
'entityModified',
|
|
15
|
+
'entityEvent',
|
|
16
|
+
'entityRemoved',
|
|
17
|
+
'playerTeleport',
|
|
18
|
+
'playerPush',
|
|
19
|
+
'playerSessionAvatar',
|
|
20
|
+
'liveKitLevel',
|
|
21
|
+
'mute',
|
|
22
|
+
'settingsModified',
|
|
23
|
+
'spawnModified',
|
|
24
|
+
'modifyRank',
|
|
25
|
+
'kick',
|
|
26
|
+
'ping',
|
|
27
|
+
'pong',
|
|
28
|
+
'blueprintRemoved',
|
|
29
|
+
// admin protocol
|
|
30
|
+
'adminAuth',
|
|
31
|
+
'adminAuthOk',
|
|
32
|
+
'adminAuthError',
|
|
33
|
+
'adminCommand',
|
|
34
|
+
'adminResult',
|
|
35
|
+
// admin player streaming
|
|
36
|
+
'playerJoined',
|
|
37
|
+
'playerUpdated',
|
|
38
|
+
'playerLeft',
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
const byName = {}
|
|
42
|
+
const byId = {}
|
|
43
|
+
|
|
44
|
+
let ids = -1
|
|
45
|
+
|
|
46
|
+
for (const name of names) {
|
|
47
|
+
const id = ++ids
|
|
48
|
+
const info = {
|
|
49
|
+
id,
|
|
50
|
+
name,
|
|
51
|
+
method: `on${capitalize(name)}`, // eg 'connect' -> 'onConnect'
|
|
52
|
+
}
|
|
53
|
+
byName[name] = info
|
|
54
|
+
byId[id] = info
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function capitalize(str) {
|
|
58
|
+
return str.charAt(0).toUpperCase() + str.slice(1)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function writePacket(name, data) {
|
|
62
|
+
const info = byName[name]
|
|
63
|
+
if (!info) throw new Error(`writePacket failed: ${name} (name not found)`)
|
|
64
|
+
const packet = packr.pack([info.id, data])
|
|
65
|
+
return packet
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function readPacket(packet) {
|
|
69
|
+
try {
|
|
70
|
+
const [id, data] = packr.unpack(packet)
|
|
71
|
+
const info = byId[id]
|
|
72
|
+
if (!info) throw new Error(`readPacket failed: ${id} (id not found)`)
|
|
73
|
+
return [info.method, data]
|
|
74
|
+
} catch (err) {
|
|
75
|
+
console.error(err)
|
|
76
|
+
return []
|
|
77
|
+
}
|
|
78
|
+
}
|