@genex-ai/cli-demo 0.15.0 → 0.16.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/dist/index.js
CHANGED
|
@@ -1110,6 +1110,24 @@ async function detectMultiplayer(cwd = process.cwd()) {
|
|
|
1110
1110
|
return false;
|
|
1111
1111
|
}
|
|
1112
1112
|
}
|
|
1113
|
+
async function detectMatchmaking(log, cwd = process.cwd()) {
|
|
1114
|
+
let pkg;
|
|
1115
|
+
try {
|
|
1116
|
+
pkg = JSON.parse(await fs8.readFile(path9.join(cwd, "package.json"), "utf8"));
|
|
1117
|
+
} catch (err) {
|
|
1118
|
+
log.dim(` (skipping matchmaking \u2014 couldn't read package.json: ${String(err)})`);
|
|
1119
|
+
return null;
|
|
1120
|
+
}
|
|
1121
|
+
const mm = pkg.genex?.matchmaking;
|
|
1122
|
+
if (mm && typeof mm.preset === "string" && mm.preset) {
|
|
1123
|
+
return {
|
|
1124
|
+
preset: mm.preset,
|
|
1125
|
+
...mm.winCondition ? { winCondition: mm.winCondition } : {},
|
|
1126
|
+
...mm.config ? { config: mm.config } : {}
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
return null;
|
|
1130
|
+
}
|
|
1113
1131
|
async function runPublish(opts) {
|
|
1114
1132
|
const log = createLogger({ quiet: opts.quiet });
|
|
1115
1133
|
log.plain(c.bold("genex publish"));
|
|
@@ -1151,6 +1169,8 @@ async function runPublish(opts) {
|
|
|
1151
1169
|
const embedSdkVersion = await detectEmbedSdkVersion();
|
|
1152
1170
|
if (embedSdkVersion) body.embedSdkVersion = embedSdkVersion;
|
|
1153
1171
|
body.multiplayer = await detectMultiplayer();
|
|
1172
|
+
const matchmaking = await detectMatchmaking(log);
|
|
1173
|
+
if (matchmaking) body.matchmaking = matchmaking;
|
|
1154
1174
|
res = await fetch(`${apiUrl}/api/projects/${meta.id}/publish`, {
|
|
1155
1175
|
method: "POST",
|
|
1156
1176
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
package/package.json
CHANGED
|
@@ -37,7 +37,62 @@ example, the shared-object/ball code, rotation, and host usage. Read
|
|
|
37
37
|
npm i @genex-ai/multiplayer
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
This skill targets `@genex-ai/multiplayer` **≥ 0.7.0** (`objects`/`host` since 0.4; `matchmake()` since 0.5; presets + `score()`/`finish()` since 0.6; `createPrivate()`/`joinPrivate()` since 0.7).
|
|
41
|
+
|
|
42
|
+
## Matchmaking (competitive presets — server-owned)
|
|
43
|
+
|
|
44
|
+
When players should be **matched into separate capped rooms** rather than share one big room (a 1v1
|
|
45
|
+
duel, an FFA arena, N-v-N teams, an invite lobby), use `matchmake()` instead of `connect()`. It
|
|
46
|
+
returns a handle whose **`session` is `null` while searching** — render your OWN "finding a match…"
|
|
47
|
+
HUD from `mm.matchmaking` (its `status`, `queue.position`, `players`/`opponents`, `teams`, `scores`,
|
|
48
|
+
`winCondition`), and switch to the game once `session` goes live:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
// Pass auth as a FUNCTION — one matchmake() handle re-joins the queue/match many times (re-search,
|
|
52
|
+
// requeue) over a session, and embed tokens rotate (~10 min). A function is read fresh each (re)join;
|
|
53
|
+
// a static object goes stale and gets rejected mid-session.
|
|
54
|
+
const mm = await matchmake<MyState>({ url, room: slug, auth: () => getColyseusAuth() });
|
|
55
|
+
mm.on('matched', () => {/* session is live — start the game */});
|
|
56
|
+
// each frame: if (mm.session) renderGame(mm.session); else renderSearchingHud(mm.matchmaking);
|
|
57
|
+
mm.on('matchEnded', ({ winnerId, scores, draw }) => {/* result screen */});
|
|
58
|
+
mm.on('error', (e) => {/* a (re)join failed, e.g. auth — usually transient; the handle keeps searching */});
|
|
59
|
+
|
|
60
|
+
// Report ONLY your own outcome — the server adjudicates. Which call fits depends on the win condition:
|
|
61
|
+
mm.eliminated(); // I'm out (lastStanding)
|
|
62
|
+
mm.score(1); // I scored (firstToScore / highScoreInTime)
|
|
63
|
+
mm.finish(); // I finished the race (firstToFinish)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Everything is **server-owned** — set once in `package.json` under `genex.matchmaking`, reported at
|
|
67
|
+
publish; the client declares nothing. You never run matchmaking logic: the server owns the queue,
|
|
68
|
+
roles, winner-stays, forfeit, timeout, and the win condition.
|
|
69
|
+
|
|
70
|
+
```jsonc
|
|
71
|
+
"genex": {
|
|
72
|
+
"matchmaking": {
|
|
73
|
+
"preset": "arena", // duel | arena | teams | private
|
|
74
|
+
"winCondition": "firstToScore", // lastStanding | firstToScore | highScoreInTime | firstToFinish
|
|
75
|
+
"config": { "scoreTarget": 20, "maxPlayers": 8 } // numeric knobs, optional
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Presets: **duel** (1v1 winner-stays), **arena** (N-player FFA, join-anytime), **teams** (balanced
|
|
81
|
+
N-v-N), **private** (invite-code lobby). Win conditions: **lastStanding** (last one alive),
|
|
82
|
+
**firstToScore** (first to the score target), **highScoreInTime** (top score at the time cap),
|
|
83
|
+
**firstToFinish** (first to finish). A round that hits the time cap undecided is a draw.
|
|
84
|
+
|
|
85
|
+
**Private lobbies** (for `preset: 'private'`) don't use `matchmake()` — a host makes an invite code
|
|
86
|
+
and friends join it; the lobby is persistent (rounds replay, nobody is evicted):
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import { createPrivate, joinPrivate } from "@genex-ai/multiplayer";
|
|
90
|
+
const lobby = await createPrivate<MyState>({ url, room: slug, auth: () => getColyseusAuth() }); // live NOW
|
|
91
|
+
showCode(lobby.code); // share this
|
|
92
|
+
// a friend, elsewhere:
|
|
93
|
+
const lobby = await joinPrivate<MyState>(code, { url, room: slug, auth: () => getColyseusAuth() });
|
|
94
|
+
// same handle API as matchmake(): lobby.session, lobby.matchmaking, eliminated()/score()/finish(), cancel()
|
|
95
|
+
```
|
|
41
96
|
|
|
42
97
|
## Connect
|
|
43
98
|
|