@myvillage/cli 2.6.1 → 2.8.0
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/package.json +1 -1
- package/src/assets/game-toolkit/myvillage-game.js +434 -0
- package/src/assets/templates/arena/AGENTS.md +93 -0
- package/src/assets/templates/arena/README.md +36 -0
- package/src/assets/templates/arena/index.html +74 -0
- package/src/assets/templates/arena/src/arena-game.js +443 -0
- package/src/assets/templates/arena/src/arena.json +114 -0
- package/src/assets/templates/arena/src/lobby.js +94 -0
- package/src/assets/templates/arena/src/main.js +46 -0
- package/src/commands/create-game.js +6 -1
- package/src/commands/deploy.js +48 -0
- package/src/index.js +1 -1
- package/src/utils/templates.js +252 -115
- package/vendor/realtime/myvillage-realtime-client.js +634 -0
|
@@ -24,7 +24,7 @@ function normEngine(v) {
|
|
|
24
24
|
function normType(v) {
|
|
25
25
|
if (!v) return null;
|
|
26
26
|
v = String(v).toLowerCase();
|
|
27
|
-
return ['quiz', 'exploration', 'narrative', 'custom'].includes(v) ? v : null;
|
|
27
|
+
return ['quiz', 'exploration', 'narrative', 'custom', 'arena'].includes(v) ? v : null;
|
|
28
28
|
}
|
|
29
29
|
function normAge(v) {
|
|
30
30
|
if (!v) return null;
|
|
@@ -127,6 +127,7 @@ export async function createGameCommand(options = {}) {
|
|
|
127
127
|
{ name: 'Exploration Game - Navigate and discover', value: 'exploration' },
|
|
128
128
|
{ name: 'Narrative Game - Story-driven experience', value: 'narrative' },
|
|
129
129
|
{ name: 'Custom - Start with base Three.js template', value: 'custom' },
|
|
130
|
+
{ name: 'Arena (multiplayer) - Real-time first-person tag / blaster matches', value: 'arena' },
|
|
130
131
|
],
|
|
131
132
|
});
|
|
132
133
|
if (needName)
|
|
@@ -213,6 +214,10 @@ export async function createGameCommand(options = {}) {
|
|
|
213
214
|
console.log(` ${brand.gold('myvillage deploy')} ${brand.teal('- Deploy to MyVillageOS')}`);
|
|
214
215
|
console.log();
|
|
215
216
|
console.log(brand.teal(` Game type: ${game.type} | Orientation: ${orientation} | Age group: ${game.ageGroup}`));
|
|
217
|
+
if (game.type === 'arena') {
|
|
218
|
+
console.log(brand.teal(' Multiplayer: open the dev server in two browser tabs and press Quick match in both.'));
|
|
219
|
+
console.log(brand.teal(' Match rules + map: src/arena.json (sent with every deploy).'));
|
|
220
|
+
}
|
|
216
221
|
console.log(brand.teal(` Cover art: drop thumbnail.png / banner.png in ${slug}/assets/ before deploy.`));
|
|
217
222
|
console.log();
|
|
218
223
|
} catch (err) {
|
package/src/commands/deploy.js
CHANGED
|
@@ -22,6 +22,42 @@ function readPackageJson(dir) {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
const MAX_MULTIPLAYER_BYTES = 64 * 1024;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The game's real-time multiplayer config for the deploy metadata, from
|
|
29
|
+
* `myvillage.multiplayer` in package.json: `{ arena: 'src/arena.json' }` (a
|
|
30
|
+
* path) or `{ arena: {...} }` (inline). Returns undefined when the game doesn't
|
|
31
|
+
* declare one (the platform keeps its current setting), null to turn it off.
|
|
32
|
+
* Throws a readable error for a missing or malformed config.
|
|
33
|
+
*/
|
|
34
|
+
export function readMultiplayerConfig(dir, mv) {
|
|
35
|
+
const setting = mv?.multiplayer;
|
|
36
|
+
if (setting === undefined) return undefined;
|
|
37
|
+
if (setting === null || setting === false) return null;
|
|
38
|
+
if (typeof setting !== 'object' || Array.isArray(setting)) {
|
|
39
|
+
throw new Error('myvillage.multiplayer must be an object like { "arena": "src/arena.json" }');
|
|
40
|
+
}
|
|
41
|
+
let arena = setting.arena ?? {};
|
|
42
|
+
if (typeof arena === 'string') {
|
|
43
|
+
const file = join(dir, arena);
|
|
44
|
+
if (!existsSync(file)) throw new Error(`myvillage.multiplayer.arena points to ${arena}, which doesn't exist`);
|
|
45
|
+
try {
|
|
46
|
+
arena = JSON.parse(readFileSync(file, 'utf-8'));
|
|
47
|
+
} catch (err) {
|
|
48
|
+
throw new Error(`${arena} is not valid JSON: ${err.message}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (typeof arena !== 'object' || arena === null || Array.isArray(arena)) {
|
|
52
|
+
throw new Error('myvillage.multiplayer.arena must be a JSON object (or a path to one)');
|
|
53
|
+
}
|
|
54
|
+
const config = { arena };
|
|
55
|
+
if (JSON.stringify(config).length > MAX_MULTIPLAYER_BYTES) {
|
|
56
|
+
throw new Error('Multiplayer config is larger than 64 KB — simplify the map');
|
|
57
|
+
}
|
|
58
|
+
return config;
|
|
59
|
+
}
|
|
60
|
+
|
|
25
61
|
function readMyVillageJson(dir) {
|
|
26
62
|
const configPath = join(dir, 'myvillage.json');
|
|
27
63
|
if (!existsSync(configPath)) return null;
|
|
@@ -217,6 +253,17 @@ async function deployThreeJsGame(projectDir, pkg, options = {}) {
|
|
|
217
253
|
// If this game is already live, confirm + unpublish before we rebuild/upload.
|
|
218
254
|
if (!(await confirmRedeployIfPublished(pkg.myvillage.gameId, options))) return;
|
|
219
255
|
|
|
256
|
+
// Multiplayer rules + map ship with the deploy; a broken config should fail
|
|
257
|
+
// before anything is built or uploaded.
|
|
258
|
+
let multiplayer;
|
|
259
|
+
try {
|
|
260
|
+
multiplayer = readMultiplayerConfig(projectDir, pkg.myvillage);
|
|
261
|
+
} catch (err) {
|
|
262
|
+
if (json) console.log(JSON.stringify({ ok: false, error: err.message }));
|
|
263
|
+
else console.log(chalk.red(`\n ✗ ${err.message}`));
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
220
267
|
// Village visibility is part of the submission — gather it up front (before
|
|
221
268
|
// the build) so it travels in the deploy metadata.
|
|
222
269
|
const villageIds = await gatherVillages(pkg.myvillage.villages || [], options);
|
|
@@ -263,6 +310,7 @@ async function deployThreeJsGame(projectDir, pkg, options = {}) {
|
|
|
263
310
|
orientation: pkg.myvillage.orientation || 'landscape',
|
|
264
311
|
entryFile: 'index.html',
|
|
265
312
|
villageIds,
|
|
313
|
+
...(multiplayer !== undefined && { multiplayer }),
|
|
266
314
|
};
|
|
267
315
|
formData.append('metadata', JSON.stringify(metadata));
|
|
268
316
|
|
package/src/index.js
CHANGED
|
@@ -215,7 +215,7 @@ export function run() {
|
|
|
215
215
|
.option('--description <text>', 'Game description')
|
|
216
216
|
.option('--engine <engine>', 'Engine: threejs | unity')
|
|
217
217
|
.option('--orientation <orientation>', 'portrait | landscape')
|
|
218
|
-
.option('--type <type>', 'Three.js type: quiz | exploration | narrative | custom')
|
|
218
|
+
.option('--type <type>', 'Three.js type: quiz | exploration | narrative | custom | arena (multiplayer)')
|
|
219
219
|
.option('--category <category>', 'Unity category: education | action | adventure | puzzle | other')
|
|
220
220
|
.option('--target-age <age>', 'Target age: under-5 | 6-12 | 13-and-over')
|
|
221
221
|
.option('--dir <path>', 'Parent directory to scaffold into (defaults to cwd)')
|