@luna-editor/engine 0.1.0 → 0.3.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 (65) hide show
  1. package/dist/Player.d.ts +1 -1
  2. package/dist/Player.js +527 -85
  3. package/dist/api/conversationBranch.d.ts +4 -0
  4. package/dist/api/conversationBranch.js +83 -0
  5. package/dist/components/BackgroundLayer.d.ts +19 -0
  6. package/dist/components/BackgroundLayer.js +218 -0
  7. package/dist/components/ClickWaitIndicator.d.ts +10 -0
  8. package/dist/components/ClickWaitIndicator.js +31 -0
  9. package/dist/components/ConversationBranchBox.d.ts +2 -0
  10. package/dist/components/ConversationBranchBox.js +29 -0
  11. package/dist/components/DialogueBox.js +16 -1
  12. package/dist/components/FontSettingsPanel.d.ts +10 -0
  13. package/dist/components/FontSettingsPanel.js +30 -0
  14. package/dist/components/FullscreenTextBox.d.ts +6 -0
  15. package/dist/components/FullscreenTextBox.js +70 -0
  16. package/dist/components/GameScreen.d.ts +1 -0
  17. package/dist/components/GameScreen.js +363 -81
  18. package/dist/components/PluginComponentProvider.d.ts +2 -2
  19. package/dist/components/PluginComponentProvider.js +3 -3
  20. package/dist/components/TimeWaitIndicator.d.ts +15 -0
  21. package/dist/components/TimeWaitIndicator.js +17 -0
  22. package/dist/contexts/AudioContext.d.ts +14 -0
  23. package/dist/contexts/AudioContext.js +14 -0
  24. package/dist/contexts/DataContext.d.ts +4 -1
  25. package/dist/contexts/DataContext.js +82 -13
  26. package/dist/hooks/useBacklog.js +3 -0
  27. package/dist/hooks/useConversationBranch.d.ts +16 -0
  28. package/dist/hooks/useConversationBranch.js +125 -0
  29. package/dist/hooks/useFontLoader.d.ts +23 -0
  30. package/dist/hooks/useFontLoader.js +153 -0
  31. package/dist/hooks/useFullscreenText.d.ts +17 -0
  32. package/dist/hooks/useFullscreenText.js +120 -0
  33. package/dist/hooks/usePlayerLogic.d.ts +10 -3
  34. package/dist/hooks/usePlayerLogic.js +115 -18
  35. package/dist/hooks/usePluginEvents.d.ts +4 -1
  36. package/dist/hooks/usePluginEvents.js +16 -11
  37. package/dist/hooks/usePreloadImages.js +27 -7
  38. package/dist/hooks/useSoundPlayer.d.ts +15 -0
  39. package/dist/hooks/useSoundPlayer.js +209 -0
  40. package/dist/hooks/useTypewriter.d.ts +6 -2
  41. package/dist/hooks/useTypewriter.js +42 -6
  42. package/dist/hooks/useVoice.js +7 -1
  43. package/dist/index.d.ts +6 -3
  44. package/dist/index.js +3 -1
  45. package/dist/plugin/PluginManager.d.ts +66 -2
  46. package/dist/plugin/PluginManager.js +352 -79
  47. package/dist/sdk.d.ts +184 -22
  48. package/dist/sdk.js +27 -2
  49. package/dist/types.d.ts +303 -4
  50. package/dist/utils/branchBlockConverter.d.ts +2 -0
  51. package/dist/utils/branchBlockConverter.js +21 -0
  52. package/dist/utils/branchNavigator.d.ts +14 -0
  53. package/dist/utils/branchNavigator.js +55 -0
  54. package/dist/utils/facePositionCalculator.js +0 -1
  55. package/dist/utils/variableManager.d.ts +18 -0
  56. package/dist/utils/variableManager.js +159 -0
  57. package/package.json +1 -1
  58. package/dist/components/ConversationLogUI.d.ts +0 -2
  59. package/dist/components/ConversationLogUI.js +0 -115
  60. package/dist/hooks/useConversationLog.d.ts +0 -14
  61. package/dist/hooks/useConversationLog.js +0 -82
  62. package/dist/hooks/useUIVisibility.d.ts +0 -9
  63. package/dist/hooks/useUIVisibility.js +0 -19
  64. package/dist/plugin/luna-react.d.ts +0 -41
  65. package/dist/plugin/luna-react.js +0 -99
package/dist/types.d.ts CHANGED
@@ -1,4 +1,7 @@
1
- export type ScenarioBlockType = "dialogue" | "narration" | "action_node" | "character_entrance" | "character_exit";
1
+ export type SoundCategory = "bgm" | "se" | "voice";
2
+ /** Block options stored as key-value pairs */
3
+ export type BlockOptions = Record<string, string | number | boolean>;
4
+ export type ScenarioBlockType = "dialogue" | "narration" | "action_node" | "character_entrance" | "character_state_change" | "bgm_play" | "se_play" | "bgm_stop" | "conversation_branch" | "background_change" | "background_group" | "variable_operation" | "fullscreen_text" | "click_wait" | "time_wait";
2
5
  export interface ScenarioBlock {
3
6
  id: string;
4
7
  scenarioId: string;
@@ -9,9 +12,10 @@ export interface ScenarioBlock {
9
12
  speakerStateId: string | null;
10
13
  partVoiceId: string | null;
11
14
  actionNodeId: string | null;
12
- linkedExitBlockId: string | null;
13
15
  createdAt: Date;
14
16
  updatedAt: Date;
17
+ /** Plugin-defined block options (key-value pairs) */
18
+ options?: BlockOptions | null;
15
19
  speaker?: {
16
20
  id: string;
17
21
  name: string;
@@ -28,15 +32,64 @@ export interface ScenarioBlock {
28
32
  scale?: number;
29
33
  translateY?: number;
30
34
  translateX?: number;
35
+ isDefault?: boolean;
36
+ layers?: EntityStateLayer[];
31
37
  } | null;
32
38
  actionNode?: ActionNode | null;
33
39
  attributeValues: ScenarioBlockAttributeValue[];
34
40
  entityAttributeValues: ScenarioBlockEntityAttributeValue[];
35
41
  characters?: ScenarioBlockCharacter[];
36
- linkedExitBlock?: {
42
+ soundId?: string | null;
43
+ sound?: {
37
44
  id: string;
38
- order: number;
45
+ name: string;
46
+ url: string;
47
+ duration: number | null;
48
+ loop: boolean;
49
+ soundType: {
50
+ id: string;
51
+ name: string;
52
+ category: SoundCategory;
53
+ };
54
+ } | null;
55
+ backgroundObjectId?: string | null;
56
+ backgroundStateId?: string | null;
57
+ backgroundObjectFit?: "contain" | "cover" | null;
58
+ backgroundLoop?: boolean | null;
59
+ backgroundObject?: {
60
+ id: string;
61
+ name: string;
39
62
  } | null;
63
+ backgroundState?: {
64
+ id: string;
65
+ name: string;
66
+ imageUrl: string | null;
67
+ webmUrl?: string | null;
68
+ } | null;
69
+ backgroundGroupItems?: BackgroundGroupItem[];
70
+ }
71
+ /** 背景の表示レイヤー(BackgroundGroupItem用) */
72
+ type BackgroundGroupLayerType = "background" | "above_character" | "above_dialogue" | "above_all_ui";
73
+ export interface BackgroundGroupItem {
74
+ id: string;
75
+ scenarioBlockId: string;
76
+ objectId: string;
77
+ stateId: string;
78
+ objectFit: "contain" | "cover";
79
+ loop: boolean;
80
+ opacity: number;
81
+ order: number;
82
+ layer: BackgroundGroupLayerType;
83
+ object: {
84
+ id: string;
85
+ name: string;
86
+ };
87
+ state: {
88
+ id: string;
89
+ name: string;
90
+ imageUrl: string | null;
91
+ webmUrl?: string | null;
92
+ };
40
93
  }
41
94
  export interface ScenarioBlockCharacter {
42
95
  id: string;
@@ -50,6 +103,7 @@ export interface ScenarioBlockCharacter {
50
103
  object: {
51
104
  id: string;
52
105
  name: string;
106
+ isLayerEnabled?: boolean;
53
107
  };
54
108
  entityState: {
55
109
  id: string;
@@ -59,7 +113,18 @@ export interface ScenarioBlockCharacter {
59
113
  scale?: number;
60
114
  translateY?: number;
61
115
  translateX?: number;
116
+ isDefault?: boolean;
117
+ layers?: EntityStateLayer[];
62
118
  };
119
+ baseBodyState?: {
120
+ id: string;
121
+ objectId: string;
122
+ name: string;
123
+ imageUrl: string | null;
124
+ scale?: number;
125
+ translateY?: number;
126
+ translateX?: number;
127
+ } | null;
63
128
  }
64
129
  export interface ActionNode {
65
130
  id: string;
@@ -115,10 +180,18 @@ export interface Character {
115
180
  state?: EntityState;
116
181
  element?: HTMLElement;
117
182
  }
183
+ export interface EntityStateLayer {
184
+ id: string;
185
+ entityStateId: string;
186
+ imageUrl: string;
187
+ sortOrder: number;
188
+ }
118
189
  export interface EntityState {
119
190
  id: string;
120
191
  name: string;
121
192
  imageUrl?: string | null;
193
+ isDefault?: boolean;
194
+ layers?: EntityStateLayer[];
122
195
  }
123
196
  export interface DisplayedCharacter {
124
197
  objectId: string;
@@ -130,6 +203,7 @@ export interface DisplayedCharacter {
130
203
  object: {
131
204
  id: string;
132
205
  name: string;
206
+ isLayerEnabled?: boolean;
133
207
  };
134
208
  entityState: {
135
209
  id: string;
@@ -139,7 +213,69 @@ export interface DisplayedCharacter {
139
213
  scale?: number;
140
214
  translateY?: number;
141
215
  translateX?: number;
216
+ isDefault?: boolean;
217
+ layers?: EntityStateLayer[];
142
218
  };
219
+ baseBodyState?: {
220
+ id: string;
221
+ name: string;
222
+ imageUrl: string | null;
223
+ scale?: number;
224
+ translateY?: number;
225
+ translateX?: number;
226
+ } | null;
227
+ }
228
+ export type VariableType = "string" | "number" | "choice" | "boolean";
229
+ export type VariableOperationType = "set" | "add" | "subtract" | "toggle";
230
+ export type VariableConditionType = "equals" | "less_than" | "greater_than";
231
+ export type BranchType = "choice" | "variable";
232
+ export interface Variable {
233
+ id: string;
234
+ workId: string;
235
+ name: string;
236
+ type: VariableType;
237
+ defaultValueString: string | null;
238
+ defaultValueNumber: number | null;
239
+ defaultValueChoice: string | null;
240
+ defaultValueBoolean: boolean | null;
241
+ choices?: VariableChoice[];
242
+ }
243
+ export interface VariableChoice {
244
+ id: string;
245
+ variableId: string;
246
+ label: string;
247
+ order: number;
248
+ }
249
+ export interface VariableOperation {
250
+ id: string;
251
+ blockId: string;
252
+ variableId: string;
253
+ operationType: VariableOperationType;
254
+ valueString: string | null;
255
+ valueNumber: number | null;
256
+ valueChoice: string | null;
257
+ valueBoolean: boolean | null;
258
+ variable?: Variable;
259
+ }
260
+ export interface VariableBranchCondition {
261
+ id: string;
262
+ branchId: string;
263
+ choiceId: string;
264
+ variableId: string;
265
+ conditionType: VariableConditionType;
266
+ valueString: string | null;
267
+ valueNumber: number | null;
268
+ valueChoice: string | null;
269
+ valueBoolean: boolean | null;
270
+ order: number;
271
+ }
272
+ export type FontType = "google" | "custom";
273
+ export interface WorkFont {
274
+ id: string;
275
+ name: string;
276
+ fontFamily: string;
277
+ type: FontType;
278
+ url: string | null;
143
279
  }
144
280
  export interface PublishedScenario {
145
281
  id: string;
@@ -149,31 +285,75 @@ export interface PublishedScenario {
149
285
  description: string | null;
150
286
  blocks: ScenarioBlock[];
151
287
  publishedAt: Date;
288
+ variables?: Variable[];
289
+ variableOperations?: VariableOperation[];
290
+ variableBranchConditions?: VariableBranchCondition[];
291
+ fonts?: WorkFont[];
152
292
  }
153
293
  export interface PlayerSettings {
154
294
  aspectRatio: string;
155
295
  bgObjectFit: "contain" | "cover";
296
+ /** 文字送り速度 (ミリ秒/文字, デフォルト: 80) */
297
+ textSpeed?: number;
298
+ /** 自動再生速度 (秒, デフォルト: 3) */
299
+ autoPlaySpeed?: number;
300
+ /** BGM音量 (0-1, デフォルト: 1) */
301
+ bgmVolume?: number;
302
+ /** SE音量 (0-1, デフォルト: 1) */
303
+ seVolume?: number;
304
+ /** パートボイス音量 (0-1, デフォルト: 1) */
305
+ voiceVolume?: number;
306
+ /** エフェクト音音量 (0-1, デフォルト: 1) */
307
+ effectVolume?: number;
308
+ /** テキスト音音量 (0-1, デフォルト: 1) */
309
+ textSoundVolume?: number;
310
+ /** デフォルト背景フェード時間 (ミリ秒, デフォルト: 0) */
311
+ defaultBackgroundFadeDuration?: number;
312
+ /** 全ての音声をミュートする (デフォルト: false) */
313
+ muteAudio?: boolean;
314
+ /** 選択されたフォントファミリー */
315
+ selectedFontFamily?: string;
156
316
  }
157
317
  export interface PluginConfig {
158
318
  packageName: string;
159
319
  bundleUrl: string;
160
320
  config?: unknown;
161
321
  }
322
+ /** 作品のサウンドデータ(プラグインからアクセス可能) */
323
+ export interface WorkSound {
324
+ id: string;
325
+ name: string;
326
+ url: string;
327
+ }
162
328
  export interface PlayerProps {
163
329
  scenario: PublishedScenario;
164
330
  settings?: PlayerSettings;
165
331
  plugins?: PluginConfig[];
332
+ /** 作品のサウンドデータ(プラグインからplayWorkSoundで使用可能) */
333
+ sounds?: WorkSound[];
166
334
  onEnd?: () => void;
167
335
  onScenarioEnd?: () => void;
168
336
  onScenarioStart?: () => void;
169
337
  onScenarioCancelled?: () => void;
338
+ onSettingsChange?: (settings: PlayerSettings) => void;
170
339
  className?: string;
171
340
  autoplay?: boolean;
341
+ /** ホイールスクロールを無効化するかどうか(デフォルト: true) */
342
+ preventDefaultScroll?: boolean;
343
+ /** 画面サイズを明示的に指定(プレビュー用)。指定しない場合はwindowサイズを使用 */
344
+ screenSize?: {
345
+ width: number;
346
+ height: number;
347
+ };
348
+ /** キーボードナビゲーションを無効化するかどうか(デフォルト: false) */
349
+ disableKeyboardNavigation?: boolean;
172
350
  }
351
+ export type VariableValue = string | number | boolean;
173
352
  export interface PlayerState {
174
353
  currentBlockIndex: number;
175
354
  isPlaying: boolean;
176
355
  isEnded: boolean;
356
+ variables?: Map<string, VariableValue>;
177
357
  }
178
358
  export interface BacklogEntry {
179
359
  id: string;
@@ -183,4 +363,123 @@ export interface BacklogEntry {
183
363
  content: string | null;
184
364
  speakerName?: string;
185
365
  speakerState?: string;
366
+ /** Block options (for plugin-defined options like bubble style) */
367
+ options?: BlockOptions | null;
368
+ }
369
+ export interface ConversationBranch {
370
+ id: string;
371
+ blockId: string;
372
+ workId: string;
373
+ branchType?: BranchType;
374
+ createdAt: Date;
375
+ updatedAt: Date;
376
+ branchChoices: ConversationChoice[];
377
+ }
378
+ export interface ConversationChoice {
379
+ id: string;
380
+ branchId: string;
381
+ choiceText: string;
382
+ order: number;
383
+ createdAt: Date;
384
+ updatedAt: Date;
385
+ branchBlocks: ConversationBranchBlock[];
386
+ }
387
+ export interface ConversationBranchBlock {
388
+ id: string;
389
+ choiceId: string;
390
+ blockType: ScenarioBlockType;
391
+ content: string | null;
392
+ speakerId: string | null;
393
+ speakerStateId: string | null;
394
+ actionNodeId: string | null;
395
+ partVoiceId: string | null;
396
+ order: number;
397
+ createdAt: Date;
398
+ updatedAt: Date;
399
+ speaker?: {
400
+ id: string;
401
+ name: string;
402
+ typeId: string;
403
+ workId: string;
404
+ description: string | null;
405
+ order: number;
406
+ } | null;
407
+ speakerState?: {
408
+ id: string;
409
+ name: string;
410
+ imageUrl: string | null;
411
+ cropArea: string | null;
412
+ scale?: number;
413
+ translateY?: number;
414
+ translateX?: number;
415
+ } | null;
416
+ actionNode?: ActionNode | null;
417
+ backgroundObjectId?: string | null;
418
+ backgroundStateId?: string | null;
419
+ backgroundObjectFit?: "contain" | "cover" | null;
420
+ backgroundLoop?: boolean | null;
421
+ backgroundObject?: {
422
+ id: string;
423
+ name: string;
424
+ } | null;
425
+ backgroundState?: {
426
+ id: string;
427
+ name: string;
428
+ imageUrl: string | null;
429
+ webmUrl?: string | null;
430
+ } | null;
431
+ }
432
+ export interface ChoiceData {
433
+ id: string;
434
+ text: string;
435
+ order: number;
436
+ isDisabled?: boolean;
437
+ }
438
+ export type ConversationBranchError = "DATA_MISSING" | "NETWORK_ERROR" | "INVALID_STRUCTURE" | "NO_MATCHING_CONDITION";
439
+ export interface ConversationBranchState {
440
+ isActive: boolean;
441
+ branchType?: BranchType;
442
+ currentChoices: ChoiceData[];
443
+ selectedChoiceId: string | null;
444
+ errorState: ConversationBranchError | null;
445
+ }
446
+ export interface BranchChoiceHistory {
447
+ branchBlockId: string;
448
+ selectedChoiceId: string;
449
+ branchBlockIndex?: number;
450
+ }
451
+ export type ConversationBranchEvent = "START" | "CHOICE_AVAILABLE" | "CHOICE_SELECTED" | "END" | "ERROR";
452
+ export interface BranchEventCallback {
453
+ onChoiceSelected?: (choiceId: string) => void;
454
+ onBranchStarted?: (choices: ChoiceData[]) => void;
455
+ onBranchEnded?: () => void;
456
+ onError?: (error: ConversationBranchError) => void;
457
+ }
458
+ export interface EnhancedPlayerState extends PlayerState {
459
+ branchHistory: BranchChoiceHistory[];
460
+ isInBranch: boolean;
461
+ currentBranchState?: ConversationBranchState;
462
+ }
463
+ export type BackgroundMediaType = "image" | "gif" | "video";
464
+ /** 背景の表示レイヤー */
465
+ export type BackgroundLayerType = "background" | "above_character" | "above_dialogue" | "above_all_ui";
466
+ export interface BackgroundData {
467
+ objectId: string;
468
+ stateId: string;
469
+ objectName: string;
470
+ stateName: string;
471
+ imageUrl: string;
472
+ /** WebM形式のURL(iOS/macOS以外で使用) */
473
+ webmUrl?: string | null;
474
+ objectFit: "contain" | "cover";
475
+ loop: boolean;
476
+ mediaType: BackgroundMediaType;
477
+ /** フェード時間(ミリ秒) */
478
+ fadeDuration?: number;
479
+ /** 不透明度 (0.0-1.0, default 1.0) */
480
+ opacity?: number;
481
+ /** 表示レイヤー (default: "background") */
482
+ layer?: BackgroundLayerType;
186
483
  }
484
+ export type BackgroundGroupData = BackgroundData[];
485
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { ConversationBranchBlock, ScenarioBlock } from "../types";
2
+ export declare function convertBranchBlockToScenarioBlock(branchBlock: ConversationBranchBlock): ScenarioBlock;
@@ -0,0 +1,21 @@
1
+ export function convertBranchBlockToScenarioBlock(branchBlock) {
2
+ return {
3
+ id: branchBlock.id,
4
+ scenarioId: "",
5
+ order: branchBlock.order,
6
+ blockType: branchBlock.blockType,
7
+ content: branchBlock.content,
8
+ speakerId: branchBlock.speakerId,
9
+ speakerStateId: branchBlock.speakerStateId,
10
+ partVoiceId: branchBlock.partVoiceId,
11
+ actionNodeId: branchBlock.actionNodeId,
12
+ createdAt: branchBlock.createdAt,
13
+ updatedAt: branchBlock.updatedAt,
14
+ speaker: branchBlock.speaker,
15
+ speakerState: branchBlock.speakerState,
16
+ actionNode: branchBlock.actionNode,
17
+ attributeValues: [],
18
+ entityAttributeValues: [],
19
+ characters: [],
20
+ };
21
+ }
@@ -0,0 +1,14 @@
1
+ import type { ConversationBranchBlock, ConversationChoice } from "../types";
2
+ export declare class BranchNavigator {
3
+ private branchBlocks;
4
+ private currentIndex;
5
+ private isActive;
6
+ startBranchNavigation(choice: ConversationChoice, startIndex?: number): void;
7
+ nextBranchBlock(): ConversationBranchBlock | null;
8
+ getCurrentBranchBlock(): ConversationBranchBlock | null;
9
+ getCurrentBranchBlockIndex(): number;
10
+ previousBranchBlock(): ConversationBranchBlock | null;
11
+ hasMoreBranchBlocks(): boolean;
12
+ reset(): void;
13
+ getIsActive(): boolean;
14
+ }
@@ -0,0 +1,55 @@
1
+ export class BranchNavigator {
2
+ constructor() {
3
+ this.branchBlocks = [];
4
+ this.currentIndex = 0;
5
+ this.isActive = false;
6
+ }
7
+ startBranchNavigation(choice, startIndex = 0) {
8
+ this.branchBlocks = [...choice.branchBlocks].sort((a, b) => a.order - b.order);
9
+ this.currentIndex = startIndex;
10
+ this.isActive = this.branchBlocks.length > 0;
11
+ if (this.currentIndex >= this.branchBlocks.length) {
12
+ this.currentIndex = 0;
13
+ this.isActive = false;
14
+ }
15
+ }
16
+ nextBranchBlock() {
17
+ if (!this.isActive || this.currentIndex >= this.branchBlocks.length) {
18
+ return null;
19
+ }
20
+ const block = this.branchBlocks[this.currentIndex];
21
+ this.currentIndex++;
22
+ if (this.currentIndex >= this.branchBlocks.length) {
23
+ this.isActive = false;
24
+ }
25
+ return block;
26
+ }
27
+ getCurrentBranchBlock() {
28
+ if (!this.isActive || this.currentIndex >= this.branchBlocks.length) {
29
+ return null;
30
+ }
31
+ return this.branchBlocks[this.currentIndex];
32
+ }
33
+ getCurrentBranchBlockIndex() {
34
+ return this.currentIndex;
35
+ }
36
+ previousBranchBlock() {
37
+ if (this.currentIndex > 0) {
38
+ this.currentIndex--;
39
+ this.isActive = true;
40
+ return this.branchBlocks[this.currentIndex];
41
+ }
42
+ return null;
43
+ }
44
+ hasMoreBranchBlocks() {
45
+ return this.isActive && this.currentIndex < this.branchBlocks.length;
46
+ }
47
+ reset() {
48
+ this.branchBlocks = [];
49
+ this.currentIndex = 0;
50
+ this.isActive = false;
51
+ }
52
+ getIsActive() {
53
+ return this.isActive;
54
+ }
55
+ }
@@ -115,7 +115,6 @@ export function calculateEffectPosition(facePosition, relativePosition, offsetX
115
115
  case "chin":
116
116
  baseY = facePosition.y + facePosition.height * 0.3;
117
117
  break;
118
- case "center":
119
118
  default:
120
119
  // Use face center as is
121
120
  break;
@@ -0,0 +1,18 @@
1
+ import type { Variable, VariableBranchCondition, VariableOperation, VariableValue } from "../types";
2
+ export declare class VariableManager {
3
+ private variables;
4
+ private variableDefinitions;
5
+ private variableNameToId;
6
+ constructor(variables: Variable[]);
7
+ private getDefaultValue;
8
+ getValue(variableId: string): VariableValue | undefined;
9
+ getValueByName(variableName: string): VariableValue | undefined;
10
+ getVariableDefinition(variableId: string): Variable | undefined;
11
+ executeOperation(operation: VariableOperation): void;
12
+ private setValue;
13
+ evaluateCondition(condition: VariableBranchCondition): boolean;
14
+ private evaluateEquals;
15
+ interpolateText(text: string): string;
16
+ getVariablesMap(): Map<string, VariableValue>;
17
+ reset(): void;
18
+ }
@@ -0,0 +1,159 @@
1
+ export class VariableManager {
2
+ constructor(variables) {
3
+ this.variables = new Map();
4
+ this.variableDefinitions = new Map();
5
+ this.variableNameToId = new Map();
6
+ for (const variable of variables) {
7
+ this.variableDefinitions.set(variable.id, variable);
8
+ this.variableNameToId.set(variable.name, variable.id);
9
+ this.variables.set(variable.id, this.getDefaultValue(variable));
10
+ }
11
+ }
12
+ getDefaultValue(variable) {
13
+ switch (variable.type) {
14
+ case "string":
15
+ return variable.defaultValueString || "";
16
+ case "number":
17
+ return variable.defaultValueNumber || 0;
18
+ case "boolean":
19
+ return variable.defaultValueBoolean || false;
20
+ case "choice":
21
+ return variable.defaultValueChoice || "";
22
+ default:
23
+ return "";
24
+ }
25
+ }
26
+ getValue(variableId) {
27
+ return this.variables.get(variableId);
28
+ }
29
+ getValueByName(variableName) {
30
+ const variableId = this.variableNameToId.get(variableName);
31
+ if (!variableId)
32
+ return undefined;
33
+ return this.variables.get(variableId);
34
+ }
35
+ getVariableDefinition(variableId) {
36
+ return this.variableDefinitions.get(variableId);
37
+ }
38
+ executeOperation(operation) {
39
+ const variable = this.variableDefinitions.get(operation.variableId);
40
+ if (!variable) {
41
+ console.warn(`Variable not found: ${operation.variableId}`);
42
+ return;
43
+ }
44
+ const currentValue = this.variables.get(operation.variableId);
45
+ switch (operation.operationType) {
46
+ case "set":
47
+ this.setValue(operation.variableId, variable, operation);
48
+ break;
49
+ case "add":
50
+ if (variable.type === "number" && typeof currentValue === "number") {
51
+ const addValue = operation.valueNumber || 0;
52
+ const newValue = Math.max(-1000000, Math.min(1000000, currentValue + addValue));
53
+ this.variables.set(operation.variableId, newValue);
54
+ }
55
+ break;
56
+ case "subtract":
57
+ if (variable.type === "number" && typeof currentValue === "number") {
58
+ const subtractValue = operation.valueNumber || 0;
59
+ const newValue = Math.max(-1000000, Math.min(1000000, currentValue - subtractValue));
60
+ this.variables.set(operation.variableId, newValue);
61
+ }
62
+ break;
63
+ case "toggle":
64
+ if (variable.type === "boolean" && typeof currentValue === "boolean") {
65
+ this.variables.set(operation.variableId, !currentValue);
66
+ }
67
+ break;
68
+ }
69
+ }
70
+ setValue(variableId, variable, operation) {
71
+ switch (variable.type) {
72
+ case "string":
73
+ this.variables.set(variableId, operation.valueString || "");
74
+ break;
75
+ case "number":
76
+ this.variables.set(variableId, operation.valueNumber || 0);
77
+ break;
78
+ case "boolean":
79
+ this.variables.set(variableId, operation.valueBoolean || false);
80
+ break;
81
+ case "choice":
82
+ this.variables.set(variableId, operation.valueChoice || "");
83
+ break;
84
+ }
85
+ }
86
+ evaluateCondition(condition) {
87
+ const variable = this.variableDefinitions.get(condition.variableId);
88
+ if (!variable) {
89
+ console.warn(`Variable not found: ${condition.variableId}`);
90
+ return false;
91
+ }
92
+ const currentValue = this.variables.get(condition.variableId);
93
+ switch (condition.conditionType) {
94
+ case "equals":
95
+ return this.evaluateEquals(variable, currentValue, condition);
96
+ case "less_than":
97
+ if (variable.type === "number" &&
98
+ typeof currentValue === "number" &&
99
+ condition.valueNumber !== null) {
100
+ return currentValue < condition.valueNumber;
101
+ }
102
+ return false;
103
+ case "greater_than":
104
+ if (variable.type === "number" &&
105
+ typeof currentValue === "number" &&
106
+ condition.valueNumber !== null) {
107
+ return currentValue > condition.valueNumber;
108
+ }
109
+ return false;
110
+ default:
111
+ return false;
112
+ }
113
+ }
114
+ evaluateEquals(variable, currentValue, condition) {
115
+ switch (variable.type) {
116
+ case "string":
117
+ return currentValue === (condition.valueString || "");
118
+ case "number":
119
+ return currentValue === (condition.valueNumber || 0);
120
+ case "boolean":
121
+ return currentValue === (condition.valueBoolean || false);
122
+ case "choice":
123
+ return currentValue === (condition.valueChoice || "");
124
+ default:
125
+ return false;
126
+ }
127
+ }
128
+ interpolateText(text) {
129
+ return text.replace(/\{([^}]+)\}/g, (match, variableName) => {
130
+ var _a;
131
+ const trimmedName = variableName.trim();
132
+ const variableId = this.variableNameToId.get(trimmedName);
133
+ if (!variableId) {
134
+ return match;
135
+ }
136
+ const value = this.variables.get(variableId);
137
+ if (value === undefined) {
138
+ return match;
139
+ }
140
+ const variable = this.variableDefinitions.get(variableId);
141
+ if (!variable) {
142
+ return match;
143
+ }
144
+ if (variable.type === "choice" && typeof value === "string") {
145
+ const choice = (_a = variable.choices) === null || _a === void 0 ? void 0 : _a.find((c) => c.id === value);
146
+ return choice ? choice.label : match;
147
+ }
148
+ return String(value);
149
+ });
150
+ }
151
+ getVariablesMap() {
152
+ return new Map(this.variables);
153
+ }
154
+ reset() {
155
+ for (const [id, variable] of this.variableDefinitions) {
156
+ this.variables.set(id, this.getDefaultValue(variable));
157
+ }
158
+ }
159
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luna-editor/engine",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Luna Editor scenario playback engine",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",