@react-text-game/core 0.1.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.
Files changed (178) hide show
  1. package/README.md +744 -0
  2. package/dist/baseGameObject.d.ts +90 -0
  3. package/dist/baseGameObject.d.ts.map +1 -0
  4. package/dist/baseGameObject.js +109 -0
  5. package/dist/baseGameObject.js.map +1 -0
  6. package/dist/constants.d.ts +12 -0
  7. package/dist/constants.d.ts.map +1 -0
  8. package/dist/constants.js +12 -0
  9. package/dist/constants.js.map +1 -0
  10. package/dist/game.d.ts +294 -0
  11. package/dist/game.d.ts.map +1 -0
  12. package/dist/game.js +489 -0
  13. package/dist/game.js.map +1 -0
  14. package/dist/helpers.d.ts +2 -0
  15. package/dist/helpers.d.ts.map +1 -0
  16. package/dist/helpers.js +6 -0
  17. package/dist/helpers.js.map +1 -0
  18. package/dist/hooks/index.d.ts +4 -0
  19. package/dist/hooks/index.d.ts.map +1 -0
  20. package/dist/hooks/index.js +4 -0
  21. package/dist/hooks/index.js.map +1 -0
  22. package/dist/hooks/useCurrentPassage.d.ts +10 -0
  23. package/dist/hooks/useCurrentPassage.d.ts.map +1 -0
  24. package/dist/hooks/useCurrentPassage.js +17 -0
  25. package/dist/hooks/useCurrentPassage.js.map +1 -0
  26. package/dist/hooks/useGameEntity.d.ts +21 -0
  27. package/dist/hooks/useGameEntity.d.ts.map +1 -0
  28. package/dist/hooks/useGameEntity.js +70 -0
  29. package/dist/hooks/useGameEntity.js.map +1 -0
  30. package/dist/hooks/useGameIsStarted.d.ts +12 -0
  31. package/dist/hooks/useGameIsStarted.d.ts.map +1 -0
  32. package/dist/hooks/useGameIsStarted.js +18 -0
  33. package/dist/hooks/useGameIsStarted.js.map +1 -0
  34. package/dist/index.d.ts +12 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +10 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/logger.d.ts +8 -0
  39. package/dist/logger.d.ts.map +1 -0
  40. package/dist/logger.js +36 -0
  41. package/dist/logger.js.map +1 -0
  42. package/dist/options.d.ts +13 -0
  43. package/dist/options.d.ts.map +1 -0
  44. package/dist/options.js +15 -0
  45. package/dist/options.js.map +1 -0
  46. package/dist/passages/interactiveMap/fabric.d.ts +4 -0
  47. package/dist/passages/interactiveMap/fabric.d.ts.map +1 -0
  48. package/dist/passages/interactiveMap/fabric.js +3 -0
  49. package/dist/passages/interactiveMap/fabric.js.map +1 -0
  50. package/dist/passages/interactiveMap/index.d.ts +4 -0
  51. package/dist/passages/interactiveMap/index.d.ts.map +1 -0
  52. package/dist/passages/interactiveMap/index.js +4 -0
  53. package/dist/passages/interactiveMap/index.js.map +1 -0
  54. package/dist/passages/interactiveMap/interactiveMap.d.ts +89 -0
  55. package/dist/passages/interactiveMap/interactiveMap.d.ts.map +1 -0
  56. package/dist/passages/interactiveMap/interactiveMap.js +103 -0
  57. package/dist/passages/interactiveMap/interactiveMap.js.map +1 -0
  58. package/dist/passages/interactiveMap/types.d.ts +822 -0
  59. package/dist/passages/interactiveMap/types.d.ts.map +1 -0
  60. package/dist/passages/interactiveMap/types.js +2 -0
  61. package/dist/passages/interactiveMap/types.js.map +1 -0
  62. package/dist/passages/passage.d.ts +57 -0
  63. package/dist/passages/passage.d.ts.map +1 -0
  64. package/dist/passages/passage.js +64 -0
  65. package/dist/passages/passage.js.map +1 -0
  66. package/dist/passages/story/fabric.d.ts +4 -0
  67. package/dist/passages/story/fabric.d.ts.map +1 -0
  68. package/dist/passages/story/fabric.js +3 -0
  69. package/dist/passages/story/fabric.js.map +1 -0
  70. package/dist/passages/story/index.d.ts +5 -0
  71. package/dist/passages/story/index.d.ts.map +1 -0
  72. package/dist/passages/story/index.js +5 -0
  73. package/dist/passages/story/index.js.map +1 -0
  74. package/dist/passages/story/start.d.ts +14 -0
  75. package/dist/passages/story/start.d.ts.map +1 -0
  76. package/dist/passages/story/start.js +22 -0
  77. package/dist/passages/story/start.js.map +1 -0
  78. package/dist/passages/story/story.d.ts +84 -0
  79. package/dist/passages/story/story.d.ts.map +1 -0
  80. package/dist/passages/story/story.js +88 -0
  81. package/dist/passages/story/story.js.map +1 -0
  82. package/dist/passages/story/types.d.ts +911 -0
  83. package/dist/passages/story/types.d.ts.map +1 -0
  84. package/dist/passages/story/types.js +2 -0
  85. package/dist/passages/story/types.js.map +1 -0
  86. package/dist/passages/types/index.d.ts +3 -0
  87. package/dist/passages/types/index.d.ts.map +1 -0
  88. package/dist/passages/types/index.js +2 -0
  89. package/dist/passages/types/index.js.map +1 -0
  90. package/dist/passages/widget.d.ts +62 -0
  91. package/dist/passages/widget.d.ts.map +1 -0
  92. package/dist/passages/widget.js +66 -0
  93. package/dist/passages/widget.js.map +1 -0
  94. package/dist/saves/constants.d.ts +17 -0
  95. package/dist/saves/constants.d.ts.map +1 -0
  96. package/dist/saves/constants.js +17 -0
  97. package/dist/saves/constants.js.map +1 -0
  98. package/dist/saves/db.d.ts +119 -0
  99. package/dist/saves/db.d.ts.map +1 -0
  100. package/dist/saves/db.js +231 -0
  101. package/dist/saves/db.js.map +1 -0
  102. package/dist/saves/helpers.d.ts +28 -0
  103. package/dist/saves/helpers.d.ts.map +1 -0
  104. package/dist/saves/helpers.js +84 -0
  105. package/dist/saves/helpers.js.map +1 -0
  106. package/dist/saves/hooks/index.d.ts +10 -0
  107. package/dist/saves/hooks/index.d.ts.map +1 -0
  108. package/dist/saves/hooks/index.js +10 -0
  109. package/dist/saves/hooks/index.js.map +1 -0
  110. package/dist/saves/hooks/useDeleteAllSlots.d.ts +18 -0
  111. package/dist/saves/hooks/useDeleteAllSlots.d.ts.map +1 -0
  112. package/dist/saves/hooks/useDeleteAllSlots.js +18 -0
  113. package/dist/saves/hooks/useDeleteAllSlots.js.map +1 -0
  114. package/dist/saves/hooks/useDeleteGame.d.ts +22 -0
  115. package/dist/saves/hooks/useDeleteGame.d.ts.map +1 -0
  116. package/dist/saves/hooks/useDeleteGame.js +33 -0
  117. package/dist/saves/hooks/useDeleteGame.js.map +1 -0
  118. package/dist/saves/hooks/useExportSaves.d.ts +27 -0
  119. package/dist/saves/hooks/useExportSaves.d.ts.map +1 -0
  120. package/dist/saves/hooks/useExportSaves.js +54 -0
  121. package/dist/saves/hooks/useExportSaves.js.map +1 -0
  122. package/dist/saves/hooks/useImportSaves.d.ts +29 -0
  123. package/dist/saves/hooks/useImportSaves.d.ts.map +1 -0
  124. package/dist/saves/hooks/useImportSaves.js +108 -0
  125. package/dist/saves/hooks/useImportSaves.js.map +1 -0
  126. package/dist/saves/hooks/useLastLoadGame.d.ts +39 -0
  127. package/dist/saves/hooks/useLastLoadGame.d.ts.map +1 -0
  128. package/dist/saves/hooks/useLastLoadGame.js +72 -0
  129. package/dist/saves/hooks/useLastLoadGame.js.map +1 -0
  130. package/dist/saves/hooks/useLoadGame.d.ts +22 -0
  131. package/dist/saves/hooks/useLoadGame.d.ts.map +1 -0
  132. package/dist/saves/hooks/useLoadGame.js +40 -0
  133. package/dist/saves/hooks/useLoadGame.js.map +1 -0
  134. package/dist/saves/hooks/useRestartGame.d.ts +20 -0
  135. package/dist/saves/hooks/useRestartGame.d.ts.map +1 -0
  136. package/dist/saves/hooks/useRestartGame.js +29 -0
  137. package/dist/saves/hooks/useRestartGame.js.map +1 -0
  138. package/dist/saves/hooks/useSaveGame.d.ts +22 -0
  139. package/dist/saves/hooks/useSaveGame.d.ts.map +1 -0
  140. package/dist/saves/hooks/useSaveGame.js +34 -0
  141. package/dist/saves/hooks/useSaveGame.js.map +1 -0
  142. package/dist/saves/hooks/useSaveSlots.d.ts +45 -0
  143. package/dist/saves/hooks/useSaveSlots.d.ts.map +1 -0
  144. package/dist/saves/hooks/useSaveSlots.js +42 -0
  145. package/dist/saves/hooks/useSaveSlots.js.map +1 -0
  146. package/dist/saves/index.d.ts +4 -0
  147. package/dist/saves/index.d.ts.map +1 -0
  148. package/dist/saves/index.js +3 -0
  149. package/dist/saves/index.js.map +1 -0
  150. package/dist/saves/types.d.ts +52 -0
  151. package/dist/saves/types.d.ts.map +1 -0
  152. package/dist/saves/types.js +2 -0
  153. package/dist/saves/types.js.map +1 -0
  154. package/dist/storage.d.ts +124 -0
  155. package/dist/storage.d.ts.map +1 -0
  156. package/dist/storage.js +229 -0
  157. package/dist/storage.js.map +1 -0
  158. package/dist/tests/game.test.d.ts +2 -0
  159. package/dist/tests/game.test.d.ts.map +1 -0
  160. package/dist/tests/game.test.js +602 -0
  161. package/dist/tests/game.test.js.map +1 -0
  162. package/dist/tests/interactiveMap.test.d.ts +2 -0
  163. package/dist/tests/interactiveMap.test.d.ts.map +1 -0
  164. package/dist/tests/interactiveMap.test.js +1003 -0
  165. package/dist/tests/interactiveMap.test.js.map +1 -0
  166. package/dist/tests/storage.test.d.ts +2 -0
  167. package/dist/tests/storage.test.d.ts.map +1 -0
  168. package/dist/tests/storage.test.js +328 -0
  169. package/dist/tests/storage.test.js.map +1 -0
  170. package/dist/tests/story.test.d.ts +2 -0
  171. package/dist/tests/story.test.d.ts.map +1 -0
  172. package/dist/tests/story.test.js +698 -0
  173. package/dist/tests/story.test.js.map +1 -0
  174. package/dist/types.d.ts +19 -0
  175. package/dist/types.d.ts.map +1 -0
  176. package/dist/types.js +2 -0
  177. package/dist/types.js.map +1 -0
  178. package/package.json +60 -0
package/README.md ADDED
@@ -0,0 +1,744 @@
1
+ # @react-text-game/core
2
+
3
+ A powerful, reactive text-based game engine built for React applications. This package provides a comprehensive framework for creating interactive narrative experiences with support for story passages, interactive maps, and state management.
4
+
5
+ ## Features
6
+
7
+ - **Reactive State Management** - Built on Valtio for automatic UI updates
8
+ - **Multiple Passage Types** - Story, Interactive Map, and Widget passages
9
+ - **Flexible Save System** - JSONPath-based storage with auto-save support
10
+ - **Entity Registry** - Automatic registration and proxying of game objects
11
+ - **Type-Safe** - Full TypeScript support with comprehensive types
12
+ - **React Hooks** - Built-in hooks for seamless React integration
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ bun add @react-text-game/core
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ```tsx
23
+ import { Game, BaseGameObject, newStory } from '@react-text-game/core';
24
+
25
+ // IMPORTANT: Initialize the game first
26
+ await Game.init({
27
+ // your game options
28
+ });
29
+
30
+ // Create a game entity
31
+ class Player extends BaseGameObject<{ health: number; name: string }> {
32
+ constructor() {
33
+ super({
34
+ id: 'player',
35
+ variables: { health: 100, name: 'Hero' }
36
+ });
37
+ }
38
+ }
39
+
40
+ const player = new Player();
41
+
42
+ // Create a story passage
43
+ const introStory = newStory('intro', () => [
44
+ {
45
+ type: 'header',
46
+ content: 'Welcome to the Game',
47
+ props: { level: 1 }
48
+ },
49
+ {
50
+ type: 'text',
51
+ content: `Hello, ${player.variables.name}!`
52
+ },
53
+ {
54
+ type: 'actions',
55
+ content: [
56
+ {
57
+ label: 'Start Adventure',
58
+ action: () => Game.jumpTo('adventure')
59
+ }
60
+ ]
61
+ }
62
+ ]);
63
+
64
+ // Navigate to passage
65
+ Game.jumpTo(introStory);
66
+ ```
67
+
68
+ ## Core Concepts
69
+
70
+ ### Game
71
+
72
+ The `Game` class is the central orchestrator that manages:
73
+
74
+ - **Initialization** - **MUST call `Game.init(options)` before using any other methods**
75
+ - **Entity Registry** - All game objects (entities) are registered and proxied
76
+ - **Passage Registry** - All passages (screens/scenes) are registered
77
+ - **Navigation** - `jumpTo()` and `setCurrent()` for passage navigation
78
+ - **State Management** - `getState()` / `setState()` for full serialization
79
+ - **Auto-Save** - Optional auto-save to session storage with debouncing
80
+
81
+ ```typescript
82
+ // Initialize the game (REQUIRED)
83
+ await Game.init({
84
+ // your options
85
+ });
86
+
87
+ // Register entities
88
+ Game.registerEntity(player, inventory, quest);
89
+
90
+ // Register passages
91
+ Game.registerPassage(intro, chapter1, finalBattle);
92
+
93
+ // Navigate
94
+ Game.jumpTo('chapter1');
95
+
96
+ // Save/Load
97
+ const savedState = Game.getState();
98
+ Game.setState(savedState);
99
+
100
+ // Auto-save
101
+ Game.enableAutoSave();
102
+ Game.loadFromSessionStorage();
103
+ ```
104
+
105
+ ### BaseGameObject
106
+
107
+ Base class for all game entities. Provides:
108
+
109
+ - **Auto-registration** - Entities register with `Game` on construction
110
+ - **Reactive Variables** - `_variables` object for state storage
111
+ - **Persistence** - `save()` / `load()` methods sync with `Storage`
112
+ - **JSONPath Integration** - Each entity has a unique storage path
113
+
114
+ ```typescript
115
+ class Inventory extends BaseGameObject<{ items: string[] }> {
116
+ constructor() {
117
+ super({
118
+ id: 'inventory',
119
+ variables: { items: [] }
120
+ });
121
+ }
122
+
123
+ addItem(item: string) {
124
+ this._variables.items.push(item);
125
+ this.save(); // Persist to storage
126
+ }
127
+ }
128
+ ```
129
+
130
+ ### Passages
131
+
132
+ Passages represent different screens or scenes in your game. Three types are available:
133
+
134
+ #### Story Passages
135
+
136
+ Text-based narrative passages with rich components:
137
+
138
+ ```typescript
139
+ import { newStory } from '@react-text-game/core';
140
+
141
+ const myStory = newStory('my-story', (props) => [
142
+ {
143
+ type: 'header',
144
+ content: 'Chapter 1',
145
+ props: { level: 1 }
146
+ },
147
+ {
148
+ type: 'text',
149
+ content: 'Once upon a time...'
150
+ },
151
+ {
152
+ type: 'image',
153
+ content: '/assets/scene.jpg',
154
+ props: { alt: 'A beautiful scene' }
155
+ },
156
+ {
157
+ type: 'video',
158
+ content: '/assets/intro.mp4',
159
+ props: { controls: true, autoPlay: false }
160
+ },
161
+ {
162
+ type: 'conversation',
163
+ content: [
164
+ {
165
+ content: 'Hello there!',
166
+ who: { name: 'NPC', avatar: '/avatars/npc.png' },
167
+ side: 'left'
168
+ },
169
+ {
170
+ content: 'Hi!',
171
+ who: { name: 'Player' },
172
+ side: 'right'
173
+ }
174
+ ],
175
+ props: { variant: 'messenger' },
176
+ appearance: 'atOnce'
177
+ },
178
+ {
179
+ type: 'actions',
180
+ content: [
181
+ {
182
+ label: 'Continue',
183
+ action: () => Game.jumpTo('chapter-2'),
184
+ color: 'primary'
185
+ },
186
+ {
187
+ label: 'Go Back',
188
+ action: () => Game.jumpTo('intro'),
189
+ color: 'secondary',
190
+ variant: 'bordered'
191
+ }
192
+ ],
193
+ props: { direction: 'horizontal' }
194
+ }
195
+ ], {
196
+ background: { image: '/bg.jpg' },
197
+ classNames: { container: 'story-container' }
198
+ });
199
+ ```
200
+
201
+ **Available Components:**
202
+ - `text` - Text content with ReactNode support and custom styling
203
+ - `header` - Semantic headers (h1-h6) with configurable levels
204
+ - `image` - Images with built-in modal viewer and custom click handlers
205
+ - `video` - HTML5 video with autoplay, loop, mute, and controls options
206
+ - `actions` - Interactive button groups with tooltips and disabled states
207
+ - `conversation` - Dialogue with chat/messenger variants and progressive reveal (byClick/atOnce)
208
+ - `anotherStory` - Embed other story passages for composition and reuse
209
+
210
+ #### Interactive Map Passages
211
+
212
+ Map-based interactive passages with hotspots:
213
+
214
+ ```typescript
215
+ import { newInteractiveMap } from '@react-text-game/core';
216
+
217
+ const worldMap = newInteractiveMap('world-map', {
218
+ caption: 'World Map',
219
+ image: '/maps/world.jpg',
220
+ bgImage: '/maps/world-bg.jpg',
221
+ props: { bgOpacity: 0.3 },
222
+ hotspots: [
223
+ // Map label hotspot - positioned on the map
224
+ {
225
+ type: 'label',
226
+ content: 'Village',
227
+ position: { x: 30, y: 40 }, // Percentage-based (0-100)
228
+ action: () => Game.jumpTo('village'),
229
+ props: { color: 'primary', variant: 'solid' }
230
+ },
231
+ // Map image hotspot - with state-dependent images
232
+ {
233
+ type: 'image',
234
+ content: {
235
+ idle: '/icons/chest.png',
236
+ hover: '/icons/chest-glow.png',
237
+ active: '/icons/chest-open.png',
238
+ disabled: '/icons/chest-locked.png'
239
+ },
240
+ position: { x: 60, y: 70 },
241
+ action: () => openChest(),
242
+ isDisabled: () => !player.hasKey,
243
+ tooltip: {
244
+ content: () => player.hasKey ? 'Open chest' : 'Locked',
245
+ position: 'top'
246
+ },
247
+ props: { zoom: '150%' }
248
+ },
249
+ // Conditional hotspot - only visible if discovered
250
+ () => player.hasDiscovered('forest') ? {
251
+ type: 'label',
252
+ content: 'Forest',
253
+ position: { x: 80, y: 50 },
254
+ action: () => Game.jumpTo('forest')
255
+ } : undefined,
256
+ // Side hotspot - positioned on edge
257
+ {
258
+ type: 'label',
259
+ content: 'Menu',
260
+ position: 'top', // top/bottom/left/right
261
+ action: () => openMenu()
262
+ },
263
+ // Context menu - multiple choices at a location
264
+ {
265
+ type: 'menu',
266
+ position: { x: 50, y: 50 },
267
+ direction: 'vertical',
268
+ items: [
269
+ { type: 'label', content: 'Examine', action: () => examine() },
270
+ { type: 'label', content: 'Take', action: () => take() },
271
+ () => player.hasMagic ? {
272
+ type: 'label',
273
+ content: 'Cast Spell',
274
+ action: () => castSpell()
275
+ } : undefined
276
+ ]
277
+ }
278
+ ],
279
+ classNames: {
280
+ container: 'bg-gradient-to-b from-sky-900 to-indigo-900',
281
+ topHotspots: 'bg-muted/50 backdrop-blur-sm'
282
+ }
283
+ });
284
+ ```
285
+
286
+ **Hotspot Types:**
287
+ - `MapLabelHotspot` - Text buttons positioned on map using percentage coordinates (x/y: 0-100)
288
+ - `MapImageHotspot` - Image buttons with state variants (idle/hover/active/disabled) and zoom support
289
+ - `SideLabelHotspot` - Text buttons on map edges (top/bottom/left/right)
290
+ - `SideImageHotspot` - Image buttons on map edges
291
+ - `MapMenu` - Contextual menu with multiple items at a specific position
292
+
293
+ **Dynamic Features:**
294
+ - Hotspots can be functions returning `undefined` for conditional visibility
295
+ - Images and positions support dynamic functions: `image: () => '/maps/' + season + '.jpg'`
296
+ - Disabled states with custom tooltips explaining why actions are unavailable
297
+
298
+ #### Widget Passages
299
+
300
+ Custom React components as passages:
301
+
302
+ ```tsx
303
+ import { newWidget } from '@react-text-game/core';
304
+
305
+ const customUI = newWidget('custom-ui', (
306
+ <div>
307
+ <h1>Custom Interface</h1>
308
+ <MyCustomComponent />
309
+ </div>
310
+ ));
311
+ ```
312
+
313
+ ### Storage
314
+
315
+ JSONPath-based storage system using the `jsonpath` library:
316
+
317
+ ```typescript
318
+ import { Storage } from '@react-text-game/core';
319
+
320
+ // Get values
321
+ const health = Storage.getValue<number>('$.player.health');
322
+
323
+ // Set values
324
+ Storage.setValue('$.player.health', 75);
325
+
326
+ // Full state
327
+ const state = Storage.getState();
328
+ Storage.setState(state);
329
+ ```
330
+
331
+ **Key Features:**
332
+ - JSONPath queries for flexible data access
333
+ - Protected system paths (prefixed with `$._system`)
334
+ - Automatic path creation
335
+ - Type-safe with generics
336
+
337
+ ### Save System
338
+
339
+ The engine includes a comprehensive save/load system built on IndexedDB (via Dexie) with encryption support for export/import:
340
+
341
+ ```typescript
342
+ import {
343
+ useSaveSlots,
344
+ useSaveGame,
345
+ useLoadGame,
346
+ useDeleteGame,
347
+ useLastLoadGame,
348
+ useExportSaves,
349
+ useImportSaves,
350
+ useRestartGame
351
+ } from '@react-text-game/core/saves';
352
+ ```
353
+
354
+ **Features:**
355
+ - **Persistent Storage** - IndexedDB for browser-based saves
356
+ - **Multiple Save Slots** - Unlimited save slots with metadata
357
+ - **Export/Import** - Encrypted file export/import (`.sx` format)
358
+ - **System Saves** - Hidden initial state for game restart
359
+ - **Real-time Updates** - Live queries for reactive save lists
360
+ - **Type-Safe** - Full TypeScript support
361
+
362
+ #### Save Management Hooks
363
+
364
+ **useSaveSlots** - Get save slots with live updates and action methods:
365
+
366
+ ```tsx
367
+ function SavesList() {
368
+ const slots = useSaveSlots({ count: 5 });
369
+
370
+ return (
371
+ <div>
372
+ {slots.map((slot, index) => (
373
+ <div key={index}>
374
+ <p>Slot {index}: {slot.data ? 'Saved' : 'Empty'}</p>
375
+ <button onClick={() => slot.save()}>Save</button>
376
+ <button onClick={() => slot.load()} disabled={!slot.data}>Load</button>
377
+ <button onClick={() => slot.delete()} disabled={!slot.data}>Delete</button>
378
+ </div>
379
+ ))}
380
+ </div>
381
+ );
382
+ }
383
+ ```
384
+
385
+ **useSaveGame** - Save current game state to a slot:
386
+
387
+ ```tsx
388
+ function SaveButton({ slotNumber }) {
389
+ const saveGame = useSaveGame();
390
+
391
+ const handleSave = async () => {
392
+ const result = await saveGame(slotNumber);
393
+ if (result?.success === false) {
394
+ alert(result.message);
395
+ }
396
+ };
397
+
398
+ return (
399
+ <button onClick={handleSave}>
400
+ Save to Slot {slotNumber}
401
+ </button>
402
+ );
403
+ }
404
+ ```
405
+
406
+ **useLoadGame** - Load a saved game by ID:
407
+
408
+ ```tsx
409
+ function LoadButton({ saveId }) {
410
+ const loadGame = useLoadGame();
411
+
412
+ const handleLoad = async () => {
413
+ const result = await loadGame(saveId);
414
+ if (result?.success === false) {
415
+ alert(result.message);
416
+ }
417
+ };
418
+
419
+ return <button onClick={handleLoad}>Load Game</button>;
420
+ }
421
+ ```
422
+
423
+ **useDeleteGame** - Delete a saved game by ID:
424
+
425
+ ```tsx
426
+ function DeleteButton({ saveId }) {
427
+ const deleteGame = useDeleteGame();
428
+
429
+ const handleDelete = async () => {
430
+ const result = await deleteGame(saveId);
431
+ if (result?.success === false) {
432
+ alert(result.message);
433
+ }
434
+ };
435
+
436
+ return <button onClick={handleDelete}>Delete Save</button>;
437
+ }
438
+ ```
439
+
440
+ **useLastLoadGame** - Load the most recent saved game:
441
+
442
+ ```tsx
443
+ function ContinueButton() {
444
+ const { hasLastSave, loadLastGame, isLoading } = useLastLoadGame();
445
+
446
+ if (isLoading) {
447
+ return <div>Loading...</div>;
448
+ }
449
+
450
+ return (
451
+ <button onClick={loadLastGame} disabled={!hasLastSave}>
452
+ Continue Last Game
453
+ </button>
454
+ );
455
+ }
456
+ ```
457
+
458
+ **useExportSaves** - Export all saves to encrypted file:
459
+
460
+ ```tsx
461
+ function ExportButton() {
462
+ const exportSaves = useExportSaves();
463
+
464
+ const handleExport = async () => {
465
+ const result = await exportSaves();
466
+ if (result.success) {
467
+ console.log('Saves exported successfully');
468
+ } else {
469
+ alert(`Export failed: ${result.error}`);
470
+ }
471
+ };
472
+
473
+ return <button onClick={handleExport}>Export Saves</button>;
474
+ }
475
+ ```
476
+
477
+ **useImportSaves** - Import saves from encrypted file:
478
+
479
+ ```tsx
480
+ function ImportButton() {
481
+ const importSaves = useImportSaves();
482
+
483
+ const handleImport = async () => {
484
+ const result = await importSaves();
485
+ if (result.success) {
486
+ console.log(`Imported ${result.count} saves`);
487
+ } else {
488
+ alert(`Import failed: ${result.error}`);
489
+ }
490
+ };
491
+
492
+ return <button onClick={handleImport}>Import Saves</button>;
493
+ }
494
+ ```
495
+
496
+ **useRestartGame** - Restart game from initial state:
497
+
498
+ ```tsx
499
+ function RestartButton() {
500
+ const restartGame = useRestartGame();
501
+
502
+ return (
503
+ <button onClick={restartGame}>
504
+ Restart Game
505
+ </button>
506
+ );
507
+ }
508
+ ```
509
+
510
+ #### Direct Database Access
511
+
512
+ For advanced use cases, you can access the database directly:
513
+
514
+ ```typescript
515
+ import {
516
+ saveGame,
517
+ loadGame,
518
+ getAllSaves,
519
+ deleteSave,
520
+ db
521
+ } from '@react-text-game/core/saves';
522
+
523
+ // Save game manually
524
+ await saveGame('my-save', gameData, 'Description', screenshotBase64);
525
+
526
+ // Load by ID
527
+ const save = await loadGame(1);
528
+
529
+ // Get all saves
530
+ const allSaves = await getAllSaves();
531
+
532
+ // Delete a save
533
+ await deleteSave(1);
534
+
535
+ // Direct Dexie access
536
+ await db.saves.where('name').equals('my-save').first();
537
+ ```
538
+
539
+ #### Save File Encryption
540
+
541
+ Exported save files are encrypted using AES encryption with PBKDF2 key derivation:
542
+ - **Algorithm**: AES-256-CBC
543
+ - **Key Derivation**: PBKDF2 with 1000 iterations
544
+ - **Salt & IV**: Randomly generated for each export
545
+ - **Password**: Derived from `gameId` and `SAVE_POSTFIX`
546
+
547
+ ### React Hooks
548
+
549
+ #### useCurrentPassage
550
+
551
+ Get the current passage with reactive updates:
552
+
553
+ ```tsx
554
+ import { useCurrentPassage } from '@react-text-game/core';
555
+
556
+ function GameScreen() {
557
+ const passage = useCurrentPassage();
558
+
559
+ if (!passage) return <div>Loading...</div>;
560
+
561
+ // Render based on passage type
562
+ if (passage.type === 'story') {
563
+ const { components } = passage.display();
564
+ // Render story components
565
+ }
566
+ }
567
+ ```
568
+
569
+ #### useGameEntity
570
+
571
+ Monitor entity changes with automatic re-renders:
572
+
573
+ ```tsx
574
+ import { useGameEntity } from '@react-text-game/core';
575
+
576
+ function PlayerStats({ player }) {
577
+ const reactivePlayer = useGameEntity(player);
578
+
579
+ return (
580
+ <div>
581
+ Health: {reactivePlayer.variables.health}
582
+ {/* Updates automatically when health changes */}
583
+ </div>
584
+ );
585
+ }
586
+ ```
587
+
588
+ #### useGameIsStarted
589
+
590
+ Check if game has started:
591
+
592
+ ```tsx
593
+ import { useGameIsStarted } from '@react-text-game/core';
594
+
595
+ function GameUI() {
596
+ const isStarted = useGameIsStarted();
597
+
598
+ return isStarted ? <GameScreen /> : <MainMenu />;
599
+ }
600
+ ```
601
+
602
+ ## Architecture
603
+
604
+ ### State Flow
605
+
606
+ 1. **Initialization** - Call `Game.init(options)` to initialize the game engine
607
+ 2. **Entities** extend `BaseGameObject` and auto-register on construction
608
+ 3. **Passages** extend `Passage` and auto-register on construction
609
+ 4. **All state changes** go through Valtio proxies for reactivity
610
+ 5. **Storage** uses JSONPath queries for flexible state access
611
+ 6. **Auto-save** (if enabled) debounces writes to session storage
612
+
613
+ ### Registry Pattern
614
+
615
+ The engine uses two registries:
616
+ - `objectRegistry` - Stores all game entities as Valtio proxies
617
+ - `passagesRegistry` - Stores all passages
618
+
619
+ All objects are automatically wrapped in Valtio proxies for reactive state management.
620
+
621
+ ### Save System
622
+
623
+ The save system consists of:
624
+ - **Entity State** - Each entity's `_variables` stored at `$.{entityId}`
625
+ - **Game State** - Current passage stored at `$._system.game`
626
+ - **JSONPath Access** - Flexible queries for any state data
627
+ - **Auto-Save** - Debounced saves to session storage (500ms)
628
+
629
+ ## API Reference
630
+
631
+ ### Game
632
+
633
+ Static methods:
634
+ - `init(options)` - **Initialize the game (REQUIRED - must be called first)**
635
+ - `registerEntity(...objects)` - Register game objects
636
+ - `registerPassage(...passages)` - Register passages
637
+ - `jumpTo(passage)` - Navigate to passage
638
+ - `setCurrent(passage)` - Set current passage
639
+ - `getPassageById(id)` - Get passage by ID
640
+ - `getAllPassages()` - Get all passages
641
+ - `getState()` - Get full game state
642
+ - `setState(state)` - Restore game state
643
+ - `enableAutoSave()` - Enable auto-save
644
+ - `disableAutoSave()` - Disable auto-save
645
+ - `loadFromSessionStorage()` - Load from session storage
646
+ - `clearAutoSave()` - Clear auto-saved state
647
+
648
+ Properties:
649
+ - `currentPassage` - Get current passage
650
+ - `selfState` - Get game internal state
651
+ - `options` - Get game options
652
+
653
+ ### BaseGameObject
654
+
655
+ Constructor:
656
+ - `new BaseGameObject({ id, variables? })`
657
+
658
+ Properties:
659
+ - `id` - Unique identifier
660
+ - `variables` - Entity variables (readonly)
661
+ - `_variables` - Internal variables (protected)
662
+
663
+ Methods:
664
+ - `save()` - Save to storage
665
+ - `load()` - Load from storage
666
+
667
+ ### Passage Types
668
+
669
+ **Story:**
670
+ ```typescript
671
+ newStory(id: string, content: StoryContent, options?: StoryOptions): Story
672
+ ```
673
+
674
+ **Interactive Map:**
675
+ ```typescript
676
+ newInteractiveMap(id: string, options: InteractiveMapOptions): InteractiveMap
677
+ ```
678
+
679
+ **Widget:**
680
+ ```typescript
681
+ newWidget(id: string, content: ReactNode): Widget
682
+ ```
683
+
684
+ ## TypeScript
685
+
686
+ Full TypeScript support with comprehensive types and detailed JSDoc documentation:
687
+
688
+ ```typescript
689
+ // Import types from main package
690
+ import type {
691
+ GameSaveState,
692
+ JsonPath,
693
+ InitVarsType,
694
+ PassageType,
695
+ ButtonColor,
696
+ ButtonVariant
697
+ } from '@react-text-game/core';
698
+
699
+ // Import story passage types
700
+ import type {
701
+ Component,
702
+ StoryContent,
703
+ StoryOptions,
704
+ TextComponent,
705
+ HeaderComponent,
706
+ ImageComponent,
707
+ VideoComponent,
708
+ ActionsComponent,
709
+ ConversationComponent,
710
+ AnotherStoryComponent,
711
+ ActionType,
712
+ ConversationBubble,
713
+ ConversationVariant,
714
+ ConversationAppearance
715
+ } from '@react-text-game/core/passages';
716
+
717
+ // Import interactive map types
718
+ import type {
719
+ InteractiveMapOptions,
720
+ InteractiveMapType,
721
+ AnyHotspot,
722
+ MapLabelHotspot,
723
+ MapImageHotspot,
724
+ SideLabelHotspot,
725
+ SideImageHotspot,
726
+ MapMenu,
727
+ LabelHotspot,
728
+ ImageHotspot
729
+ } from '@react-text-game/core/passages';
730
+ ```
731
+
732
+ All types include comprehensive JSDoc comments with:
733
+ - Detailed descriptions of each property
734
+ - Usage examples and code snippets
735
+ - Default value annotations
736
+ - Remarks about behavior and implementation details
737
+
738
+ ## Examples
739
+
740
+ See the `apps/example-game` directory for a complete implementation example.
741
+
742
+ ## License
743
+
744
+ MIT (c) laruss