koishipro-core.js 1.0.10 → 1.1.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/README.md CHANGED
@@ -7,9 +7,10 @@ helpers for loading scripts/cards.
7
7
  ## Features
8
8
  - Load ocgcore WASM (CJS or ESM) and create duels
9
9
  - TypeScript-friendly wrapper around duel APIs
10
- - Script readers (Map/Zip) for loading Lua scripts
10
+ - **All query methods and process messages use [`ygopro-msg-encode`](https://github.com/purerosefallen/ygopro-msg-encode) for typed message parsing**
11
+ - Script readers (Map/Zip/Dir) for loading Lua scripts
11
12
  - SQL.js card reader helper
12
- - Replay (YRP/YRP2) playback to raw message streams
13
+ - Replay (YRP/YRP2) playback with step-by-step control
13
14
  - Constants auto-generated from upstream YGOPro sources
14
15
 
15
16
  ## Install
@@ -50,67 +51,276 @@ wrapper
50
51
  });
51
52
 
52
53
  const duel = wrapper.createDuel(1234);
53
- duel.setPlayerInfo({ playerId: 0, lp: 8000, startCount: 5, drawCount: 1 });
54
- duel.setPlayerInfo({ playerId: 1, lp: 8000, startCount: 5, drawCount: 1 });
54
+ duel.setPlayerInfo({ player: 0, lp: 8000, startHand: 5, drawCount: 1 });
55
+ duel.setPlayerInfo({ player: 1, lp: 8000, startHand: 5, drawCount: 1 });
55
56
  duel.startDuel(0);
56
57
 
57
- const { raw, status } = duel.process();
58
- console.log(raw, status);
58
+ const { raw, status, message } = duel.process();
59
+ console.log('Raw bytes:', raw);
60
+ console.log('Status:', status);
61
+ console.log('Parsed message:', message); // YGOProMsgBase instance from ygopro-msg-encode
59
62
 
60
63
  duel.endDuel();
61
64
  wrapper.finalize();
62
65
  ```
63
66
 
64
67
  ## Replay Playback
65
- `playYrp` replays a `.yrp/.yrp2` file by feeding responses back to ocgcore and
66
- collecting each engine message as `Uint8Array`. You can pass a parsed `YGOProYrp`
67
- instance or raw bytes.
68
+
69
+ ### playYrp (One-shot)
70
+ `playYrp` replays a `.yrp/.yrp2` file and returns all messages at once:
68
71
 
69
72
  ```ts
70
73
  import { createOcgcoreWrapper, playYrp } from 'koishipro-core.js';
71
- import { YGOProYrp } from 'ygopro-yrp-encode';
72
74
 
73
75
  const wrapper = await createOcgcoreWrapper();
74
-
75
- // ...setScriptReader / setCardReader / setMessageHandler...
76
+ // ...setScriptReader / setCardReader...
76
77
 
77
78
  const yrpBytes = await fetch('/replay.yrp').then((r) => r.arrayBuffer());
78
79
  const messages = playYrp(wrapper, new Uint8Array(yrpBytes));
79
- console.log('message count:', messages.length);
80
+ console.log('Total messages:', messages.length);
80
81
  ```
81
82
 
83
+ ### playYrpStep (Generator)
84
+ `playYrpStep` gives you step-by-step control over replay execution. Use this when you need to:
85
+ - Query game state during replay
86
+ - Inspect individual messages and their parsed objects
87
+ - Control replay execution flow
88
+
89
+ ```ts
90
+ import { createOcgcoreWrapper, playYrpStep } from 'koishipro-core.js';
91
+ import { YGOProMsgNewTurn } from 'ygopro-msg-encode';
92
+
93
+ const wrapper = await createOcgcoreWrapper();
94
+ // ...setScriptReader / setCardReader...
95
+
96
+ const yrpBytes = await fetch('/replay.yrp').then((r) => r.arrayBuffer());
97
+
98
+ for (const { duel, result } of playYrpStep(wrapper, new Uint8Array(yrpBytes))) {
99
+ // Access parsed message (from ygopro-msg-encode)
100
+ if (result.message instanceof YGOProMsgNewTurn) {
101
+ console.log('New turn started!');
102
+
103
+ // Query game state at this point
104
+ const fieldInfo = duel.queryFieldInfo();
105
+ console.log('Player 0 LP:', fieldInfo.field.players[0].lp);
106
+
107
+ const mzoneCards = duel.queryFieldCard({
108
+ player: 0,
109
+ location: LOCATION_MZONE,
110
+ queryFlag: QUERY_CODE | QUERY_ATTACK | QUERY_DEFENSE,
111
+ });
112
+ console.log('Monster zone cards:', mzoneCards.cards);
113
+ }
114
+
115
+ // Access raw bytes if needed
116
+ console.log('Raw message bytes:', result.raw);
117
+ }
118
+ ```
119
+
120
+
121
+ ## API Reference
122
+
123
+ ### Core Classes
124
+
125
+ #### `createOcgcoreWrapper(options?): Promise<OcgcoreWrapper>`
126
+ Load the ocgcore WASM module and return an `OcgcoreWrapper`.
127
+
128
+ **Options:**
129
+ - `scriptBufferSize?: number` - Buffer size for script loading (default: 0x100000)
130
+ - `logBufferSize?: number` - Buffer size for log messages (default: 1024)
131
+
132
+ #### `OcgcoreWrapper`
133
+ Manages the WASM module, script/card/message handlers, and duel creation.
134
+
135
+ **Methods:**
136
+ - `setScriptReader(reader: ScriptReader, reset?: boolean): this`
137
+ Register a script reader. Multiple readers are tried in order (fallback).
138
+ - `setCardReader(reader: CardReader, reset?: boolean): this`
139
+ Register a card reader. Multiple readers are tried in order (fallback).
140
+ - `setMessageHandler(handler: MessageHandler, reset?: boolean): this`
141
+ Register a message handler for debug/error messages (fan-out pattern).
142
+ - `createDuel(seed: number): OcgcoreDuel`
143
+ Create a new duel with a single seed.
144
+ - `createDuelV2(seedSequence: number[]): OcgcoreDuel`
145
+ Create a new duel with a seed sequence (YRP2 format).
146
+ - `finalize(): void`
147
+ Clean up all allocated resources. Call this before discarding the wrapper.
82
148
 
83
- ## API Overview
84
- ### Core
85
- - `createOcgcoreWrapper(options?)`
86
- Load the ocgcore WASM module and return an `OcgcoreWrapper`.
87
- - `OcgcoreWrapper`
88
- Manages the WASM module, script/card/message handlers, and duel creation.
89
- Script readers and handlers can be registered multiple times (fallback/fan-out).
90
- - `OcgcoreDuel`
91
- Duel lifecycle and core engine calls (`startDuel`, `process`, `setResponse`, etc.).
149
+ #### `OcgcoreDuel`
150
+ Represents a single duel instance with full lifecycle management.
151
+
152
+ **Core Methods:**
153
+ - `startDuel(options: number | OcgcoreStartDuelOptions): void`
154
+ Start the duel with specified options (duel rules, shuffle mode, etc.).
155
+ - `process(): OcgcoreProcessResult`
156
+ Process the next game event. Returns `{ raw: Uint8Array, status: number, message?: YGOProMsgBase }`.
157
+ **The `message` field contains the parsed message from `ygopro-msg-encode`.**
158
+ - `setResponse(response: Uint8Array): void`
159
+ Provide a response to the engine (player action).
160
+ - `setResponseInt(value: number): void`
161
+ Provide an integer response.
162
+ - `endDuel(): void`
163
+ End the duel and clean up resources.
164
+
165
+ **Card Management:**
166
+ - `newCard(card: OcgcoreNewCardParams): void`
167
+ Add a card to the duel.
168
+ - `newTagCard(card: OcgcoreNewTagCardParams): void`
169
+ Add a tag duel card.
170
+
171
+ **Query Methods (All return `ygopro-msg-encode` objects):**
172
+ - `queryCard(query: OcgcoreQueryCardParams): OcgcoreCardQueryResult`
173
+ Query information about a single card.
174
+ **Returns `{ card: CardQuery | null }` from `ygopro-msg-encode`.**
175
+
176
+ - `queryFieldCard(query: OcgcoreQueryFieldCardParams): OcgcoreFieldCardQueryResult`
177
+ Query all cards in a location.
178
+ **Returns `{ cards: CardQuery[] }` from `ygopro-msg-encode`.**
179
+
180
+ - `queryFieldInfo(): OcgcoreFieldInfoResult`
181
+ Query the entire field state.
182
+ **Returns `{ field: YGOProMsgReloadField }` from `ygopro-msg-encode`.**
183
+
184
+ - `queryFieldCount(query: OcgcoreQueryFieldCountParams): number`
185
+ Get the number of cards in a location.
186
+
187
+ **Player Info:**
188
+ - `setPlayerInfo(info: OcgcoreSetPlayerInfoParams): void`
189
+ Set initial player state (LP, hand size, draw count).
190
+
191
+ **Script Preloading:**
192
+ - `preloadScript(scriptPath: string): void`
193
+ Preload a Lua script before duel starts.
194
+
195
+ **Registry (Key-Value Storage):**
196
+ - `setRegistryValue(key: string, value: string): void`
197
+ - `getRegistryValue(key: string): OcgcoreRegistryValueResult`
198
+ - `getRegistryKeys(): OcgcoreRegistryKeysResult`
199
+ - `dumpRegistry(): OcgcoreRegistryDumpResult`
200
+ - `loadRegistry(input: Uint8Array): void`
201
+ - `clearRegistry(): void`
92
202
 
93
203
  ### Script Readers
94
- - `MapReader(...maps)`
95
- Resolve Lua scripts from one or more `Map<string, string | Uint8Array>` with fallback.
96
- - `ZipReader(...zipBytes)`
97
- Load all `.lua` files from one or more zips and expose them via `MapReader` (fallback order).
98
- - `DirReader(...dirs)`
99
- Node-only directory reader with the same resolution rules (fallback order).
100
-
101
- ### Replay
102
- - `playYrp(wrapper, yrpOrBytes)`
103
- Run a replay and return a list of raw message `Uint8Array`s.
104
- Accepts `YGOProYrp` or raw `.yrp/.yrp2` bytes.
105
- Throws `Got MSG_RETRY` if a retry is encountered.
204
+
205
+ #### `MapReader(...maps: Map<string, string | Uint8Array>[]): ScriptReader`
206
+ Resolve Lua scripts from one or more Maps with fallback order.
207
+
208
+ ```ts
209
+ const scripts = new Map([
210
+ ['c12345.lua', 'function c12345.initial_effect(c) end'],
211
+ ]);
212
+ wrapper.setScriptReader(MapReader(scripts));
213
+ ```
214
+
215
+ #### `ZipReader(...zipBytes: Uint8Array[]): Promise<ScriptReader>`
216
+ Load all `.lua` files from one or more zips.
217
+
218
+ ```ts
219
+ const zipBytes = await fetch('/scripts.zip').then(r => r.arrayBuffer());
220
+ wrapper.setScriptReader(await ZipReader(new Uint8Array(zipBytes)));
221
+ ```
222
+
223
+ #### `DirReader(...dirs: string[]): ScriptReader`
224
+ Node-only directory reader with fallback order.
225
+
226
+ ```ts
227
+ wrapper.setScriptReader(DirReader('./ygopro-scripts', './custom-scripts'));
228
+ ```
229
+
230
+ ### Replay Functions
231
+
232
+ #### `playYrp(wrapper: OcgcoreWrapper, yrpOrBytes: YGOProYrp | Uint8Array): Uint8Array[]`
233
+ Run a complete replay and return all messages as raw bytes.
234
+
235
+ **Parameters:**
236
+ - `wrapper`: Initialized `OcgcoreWrapper` with script/card readers configured
237
+ - `yrpOrBytes`: `YGOProYrp` instance or raw `.yrp/.yrp2` bytes
238
+
239
+ **Returns:** Array of raw message bytes
240
+
241
+ **Throws:** `'Got MSG_RETRY'` if a retry message is encountered
242
+
243
+ #### `playYrpStep(wrapper: OcgcoreWrapper, yrpOrBytes: YGOProYrp | Uint8Array): Generator<{ duel: OcgcoreDuel, result: OcgcoreProcessResult }>`
244
+ Step through a replay with full control over execution.
245
+
246
+ **Yields:**
247
+ - `duel`: Current `OcgcoreDuel` instance (use for queries)
248
+ - `result`: Process result with `{ raw, status, message? }` where `message` is from `ygopro-msg-encode`
249
+
250
+ **Example:**
251
+ ```ts
252
+ for (const { duel, result } of playYrpStep(wrapper, yrpBytes)) {
253
+ if (result.message) {
254
+ console.log('Message type:', result.message.constructor.name);
255
+ }
256
+
257
+ // Query game state at any point
258
+ const fieldInfo = duel.queryFieldInfo();
259
+ const handCards = duel.queryFieldCard({
260
+ player: 0,
261
+ location: LOCATION_HAND,
262
+ queryFlag: QUERY_CODE
263
+ });
264
+ }
265
+ ```
106
266
 
107
267
  ### Card Reader
108
- - `createSqljsCardReader(...dbs)`
109
- Build a `CardReader` from one or more SQL.js databases.
268
+
269
+ #### `createSqljsCardReader(...dbs: Database[]): CardReader`
270
+ Build a `CardReader` from one or more SQL.js databases with fallback order.
271
+
272
+ ```ts
273
+ import initSqlJs from 'sql.js';
274
+
275
+ const SQL = await initSqlJs();
276
+ const db1 = new SQL.Database(officialCards);
277
+ const db2 = new SQL.Database(customCards);
278
+
279
+ // Try db1 first, fallback to db2
280
+ wrapper.setCardReader(createSqljsCardReader(db1, db2));
281
+ ```
110
282
 
111
283
  ### Constants
112
- - `OcgcoreCommonConstants`, `OcgcoreScriptConstants`
113
- Pre-generated constants from upstream sources.
284
+
285
+ #### `OcgcoreCommonConstants`
286
+ Message types and query flags (e.g., `MSG_NEW_TURN`, `QUERY_CODE`, `QUERY_ATTACK`).
287
+
288
+ #### `OcgcoreScriptConstants`
289
+ Game constants (e.g., `LOCATION_MZONE`, `POS_FACEUP_ATTACK`, `TYPE_MONSTER`).
290
+
291
+ ### Integration with ygopro-msg-encode
292
+
293
+ All query methods and `process()` return typed objects from [`ygopro-msg-encode`](https://github.com/purerosefallen/ygopro-msg-encode):
294
+
295
+ ```ts
296
+ import { YGOProMsgNewTurn, CardQuery } from 'ygopro-msg-encode';
297
+
298
+ // Process returns parsed messages
299
+ const { message } = duel.process();
300
+ if (message instanceof YGOProMsgNewTurn) {
301
+ console.log('Turn player:', message.player);
302
+ }
303
+
304
+ // Query methods return CardQuery objects
305
+ const { card } = duel.queryCard({
306
+ player: 0,
307
+ location: LOCATION_MZONE,
308
+ sequence: 0,
309
+ queryFlag: QUERY_CODE | QUERY_ATTACK
310
+ });
311
+
312
+ if (card) {
313
+ console.log('Card code:', card.code);
314
+ console.log('Attack:', card.attack);
315
+ console.log('Position:', card.position);
316
+ }
317
+
318
+ // queryFieldInfo returns YGOProMsgReloadField
319
+ const { field } = duel.queryFieldInfo();
320
+ console.log('Duel rule:', field.duelRule);
321
+ console.log('Player 0 LP:', field.players[0].lp);
322
+ console.log('Player 0 hand count:', field.players[0].handCount);
323
+ ```
114
324
 
115
325
  ## Build / Scripts
116
326
  - `npm run build` – build CJS + ESM + types