@bscotch/gcdata 0.14.2

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 (107) hide show
  1. package/LICENSE.md +29 -0
  2. package/README.md +36 -0
  3. package/dist/GameChanger.d.ts +209 -0
  4. package/dist/GameChanger.d.ts.map +1 -0
  5. package/dist/GameChanger.events.d.ts +13 -0
  6. package/dist/GameChanger.events.d.ts.map +1 -0
  7. package/dist/GameChanger.events.js +3 -0
  8. package/dist/GameChanger.events.js.map +1 -0
  9. package/dist/GameChanger.js +478 -0
  10. package/dist/GameChanger.js.map +1 -0
  11. package/dist/SpellChecker.d.ts +31 -0
  12. package/dist/SpellChecker.d.ts.map +1 -0
  13. package/dist/SpellChecker.js +92 -0
  14. package/dist/SpellChecker.js.map +1 -0
  15. package/dist/assert.d.ts +6 -0
  16. package/dist/assert.d.ts.map +1 -0
  17. package/dist/assert.js +22 -0
  18. package/dist/assert.js.map +1 -0
  19. package/dist/cl2.loc.d.ts +1 -0
  20. package/dist/cl2.loc.d.ts.map +1 -0
  21. package/dist/cl2.loc.js +2 -0
  22. package/dist/cl2.loc.js.map +1 -0
  23. package/dist/cl2.quest.d.ts +4 -0
  24. package/dist/cl2.quest.d.ts.map +1 -0
  25. package/dist/cl2.quest.js +4 -0
  26. package/dist/cl2.quest.js.map +1 -0
  27. package/dist/cl2.quest.parse.d.ts +7 -0
  28. package/dist/cl2.quest.parse.d.ts.map +1 -0
  29. package/dist/cl2.quest.parse.js +825 -0
  30. package/dist/cl2.quest.parse.js.map +1 -0
  31. package/dist/cl2.quest.pointers.d.ts +3 -0
  32. package/dist/cl2.quest.pointers.d.ts.map +1 -0
  33. package/dist/cl2.quest.pointers.js +2 -0
  34. package/dist/cl2.quest.pointers.js.map +1 -0
  35. package/dist/cl2.quest.stringify.d.ts +5 -0
  36. package/dist/cl2.quest.stringify.d.ts.map +1 -0
  37. package/dist/cl2.quest.stringify.js +148 -0
  38. package/dist/cl2.quest.stringify.js.map +1 -0
  39. package/dist/cl2.quest.types.d.ts +168 -0
  40. package/dist/cl2.quest.types.d.ts.map +1 -0
  41. package/dist/cl2.quest.types.js +116 -0
  42. package/dist/cl2.quest.types.js.map +1 -0
  43. package/dist/cl2.quest.utils.d.ts +16 -0
  44. package/dist/cl2.quest.utils.d.ts.map +1 -0
  45. package/dist/cl2.quest.utils.js +105 -0
  46. package/dist/cl2.quest.utils.js.map +1 -0
  47. package/dist/cl2.storyline.d.ts +4 -0
  48. package/dist/cl2.storyline.d.ts.map +1 -0
  49. package/dist/cl2.storyline.js +4 -0
  50. package/dist/cl2.storyline.js.map +1 -0
  51. package/dist/cl2.storyline.parse.d.ts +7 -0
  52. package/dist/cl2.storyline.parse.d.ts.map +1 -0
  53. package/dist/cl2.storyline.parse.js +208 -0
  54. package/dist/cl2.storyline.parse.js.map +1 -0
  55. package/dist/cl2.storyline.pointers.d.ts +3 -0
  56. package/dist/cl2.storyline.pointers.d.ts.map +1 -0
  57. package/dist/cl2.storyline.pointers.js +2 -0
  58. package/dist/cl2.storyline.pointers.js.map +1 -0
  59. package/dist/cl2.storyline.stringify.d.ts +4 -0
  60. package/dist/cl2.storyline.stringify.d.ts.map +1 -0
  61. package/dist/cl2.storyline.stringify.js +18 -0
  62. package/dist/cl2.storyline.stringify.js.map +1 -0
  63. package/dist/cl2.storyline.types.d.ts +24 -0
  64. package/dist/cl2.storyline.types.d.ts.map +1 -0
  65. package/dist/cl2.storyline.types.js +87 -0
  66. package/dist/cl2.storyline.types.js.map +1 -0
  67. package/dist/cl2.storyline.utils.d.ts +1 -0
  68. package/dist/cl2.storyline.utils.d.ts.map +1 -0
  69. package/dist/cl2.storyline.utils.js +2 -0
  70. package/dist/cl2.storyline.utils.js.map +1 -0
  71. package/dist/cl2.types.auto.d.ts +22682 -0
  72. package/dist/cl2.types.auto.d.ts.map +1 -0
  73. package/dist/cl2.types.auto.js +2 -0
  74. package/dist/cl2.types.auto.js.map +1 -0
  75. package/dist/cl2.types.editor.d.ts +31 -0
  76. package/dist/cl2.types.editor.d.ts.map +1 -0
  77. package/dist/cl2.types.editor.js +2 -0
  78. package/dist/cl2.types.editor.js.map +1 -0
  79. package/dist/dict.d.ts +3 -0
  80. package/dist/dict.d.ts.map +1 -0
  81. package/dist/dict.js +49777 -0
  82. package/dist/dict.js.map +1 -0
  83. package/dist/helpers.d.ts +44 -0
  84. package/dist/helpers.d.ts.map +1 -0
  85. package/dist/helpers.js +149 -0
  86. package/dist/helpers.js.map +1 -0
  87. package/dist/index.d.ts +9 -0
  88. package/dist/index.d.ts.map +1 -0
  89. package/dist/index.js +9 -0
  90. package/dist/index.js.map +1 -0
  91. package/dist/types.cl2.rumpus.d.ts +190 -0
  92. package/dist/types.cl2.rumpus.d.ts.map +1 -0
  93. package/dist/types.cl2.rumpus.js +30 -0
  94. package/dist/types.cl2.rumpus.js.map +1 -0
  95. package/dist/types.d.ts +586 -0
  96. package/dist/types.d.ts.map +1 -0
  97. package/dist/types.editor.d.ts +25 -0
  98. package/dist/types.editor.d.ts.map +1 -0
  99. package/dist/types.editor.js +2 -0
  100. package/dist/types.editor.js.map +1 -0
  101. package/dist/types.js +98 -0
  102. package/dist/types.js.map +1 -0
  103. package/dist/util.d.ts +42 -0
  104. package/dist/util.d.ts.map +1 -0
  105. package/dist/util.js +272 -0
  106. package/dist/util.js.map +1 -0
  107. package/package.json +32 -0
@@ -0,0 +1,825 @@
1
+ import { assert } from './assert.js';
2
+ import { arrayTagPattern, lineIsArrayItem, linePatterns, parseIfMatch, } from './cl2.quest.types.js';
3
+ import { getMomentStyleNames, getMoteLists, getRequirementStyleNames, getReuirementQuestStatuses, isEmoteMoment, } from './cl2.quest.utils.js';
4
+ import { bsArrayToArray, changedPosition, createBsArrayKey, updateBsArrayOrder, } from './helpers.js';
5
+ import { parsedItemToWords } from './util.js';
6
+ export function parseStringifiedQuest(text, packed, options = {}) {
7
+ const motes = getMoteLists(packed.working);
8
+ const momentStyles = getMomentStyleNames(packed.working);
9
+ // Remove 'Dialogue' and 'Emote' from the list of moment styles
10
+ for (const style of ['Dialogue', 'Emote']) {
11
+ momentStyles.splice(momentStyles.indexOf(style), 1);
12
+ }
13
+ const requirementStyles = getRequirementStyleNames(packed.working);
14
+ const requirementQuestStatuses = getReuirementQuestStatuses(packed.working);
15
+ const requirementCompletions = [...requirementStyles];
16
+ requirementCompletions.splice(requirementCompletions.indexOf('Quest'), 1);
17
+ requirementCompletions.push(...requirementQuestStatuses.map((s) => `Quest ${s}`));
18
+ /**
19
+ * Shared list of keywords that can be used at the start of any line,
20
+ * with required-unique entries removed when found.
21
+ */
22
+ const nonUniqueGlobalLabels = new Set(['Clue']);
23
+ const availableGlobalLabels = new Set([
24
+ 'Draft',
25
+ 'Name',
26
+ 'Storyline',
27
+ 'Giver',
28
+ 'Receiver',
29
+ 'Clue',
30
+ 'Start Requirements',
31
+ 'Start Moments',
32
+ 'End Requirements',
33
+ 'End Moments',
34
+ 'Log',
35
+ ]);
36
+ const result = {
37
+ diagnostics: [],
38
+ hovers: [],
39
+ edits: [],
40
+ completions: [],
41
+ words: [],
42
+ parsed: {
43
+ clues: [],
44
+ quest_end_moments: [],
45
+ quest_start_moments: [],
46
+ quest_start_requirements: [],
47
+ quest_end_requirements: [],
48
+ comments: [],
49
+ },
50
+ };
51
+ const lines = text.split(/(\r?\n)/g);
52
+ let index = 0;
53
+ let lineNumber = 0;
54
+ const emojiIdFromName = (name) => {
55
+ if (!name) {
56
+ return undefined;
57
+ }
58
+ const emoji = motes.emojis.find((e) => packed.working.getMoteName(e)?.toLowerCase() ===
59
+ name?.trim().toLowerCase() || e.id === name?.trim());
60
+ return emoji?.id;
61
+ };
62
+ const checkSpelling = (item) => {
63
+ if (!item || !options.checkSpelling)
64
+ return;
65
+ // Parse out the word positions so they can be used as ranges to check cursor position
66
+ const words = parsedItemToWords(item);
67
+ for (const word of words) {
68
+ result.words.push(packed.spellChecker.checkWord(word));
69
+ }
70
+ };
71
+ /** The MoteId for the last speaker we saw. Used to figure out who to assign stuff to */
72
+ let lastSpeaker;
73
+ let lastClue;
74
+ let lastSectionGroup;
75
+ let lastEmojiGroup;
76
+ for (const line of lines) {
77
+ const trace = [];
78
+ try {
79
+ // Is this just a newline?
80
+ if (line.match(/\r?\n/)) {
81
+ // Then we just need to increment the index
82
+ index += line.length;
83
+ lineNumber++;
84
+ continue;
85
+ }
86
+ const lineRange = {
87
+ start: {
88
+ index,
89
+ line: lineNumber,
90
+ character: 0,
91
+ },
92
+ end: {
93
+ index: index + line.length,
94
+ line: lineNumber,
95
+ character: line.length,
96
+ },
97
+ };
98
+ // Is this just a blank line?
99
+ if (!line) {
100
+ // Add global autocompletes
101
+ result.completions.push({
102
+ type: 'labels',
103
+ start: lineRange.start,
104
+ end: lineRange.end,
105
+ options: availableGlobalLabels,
106
+ });
107
+ if (isQuestMomentLabel(lastSectionGroup)) {
108
+ result.completions.push({
109
+ type: 'momentStyles',
110
+ options: momentStyles,
111
+ start: lineRange.start,
112
+ end: lineRange.end,
113
+ });
114
+ }
115
+ else if (isQuestRequirementLabel(lastSectionGroup)) {
116
+ result.completions.push({
117
+ type: 'requirementStyles',
118
+ options: requirementCompletions,
119
+ start: lineRange.start,
120
+ end: lineRange.end,
121
+ });
122
+ }
123
+ continue;
124
+ }
125
+ // Find the first matching pattern and pull the values from it.
126
+ let parsedLine = null;
127
+ for (const pattern of linePatterns) {
128
+ parsedLine = parseIfMatch(pattern, line, lineRange.start);
129
+ if (parsedLine)
130
+ break;
131
+ }
132
+ if (!parsedLine) {
133
+ // Then this is likely the result of uncommenting something
134
+ // that was commented out, resulting in a line that starts with
135
+ // the comment's array tag. Provide a deletion edit!
136
+ parsedLine = parseIfMatch(`^${arrayTagPattern} +(?<text>.*)$`, line, lineRange.start);
137
+ if (parsedLine) {
138
+ result.edits.push({
139
+ start: lineRange.start,
140
+ end: lineRange.end,
141
+ newText: parsedLine.text.value,
142
+ });
143
+ }
144
+ else {
145
+ result.diagnostics.push({
146
+ message: `Unfamiliar syntax: ${line}`,
147
+ ...lineRange,
148
+ });
149
+ }
150
+ index += line.length;
151
+ continue;
152
+ }
153
+ // Ensure the array tag. It goes right after the label or indicator.
154
+ if (!parsedLine.arrayTag?.value && lineIsArrayItem(line)) {
155
+ const arrayTag = createBsArrayKey();
156
+ const start = parsedLine.indicator?.end || parsedLine.label?.end;
157
+ result.edits.push({
158
+ start,
159
+ end: start,
160
+ newText: `#${arrayTag}`,
161
+ });
162
+ parsedLine.arrayTag = {
163
+ start,
164
+ end: start,
165
+ value: arrayTag,
166
+ };
167
+ }
168
+ // If this has a label, remove it from the list of available labels
169
+ if (parsedLine.label?.value &&
170
+ availableGlobalLabels.has(parsedLine.label.value) &&
171
+ !nonUniqueGlobalLabels.has(parsedLine.label.value)) {
172
+ availableGlobalLabels.delete(parsedLine.label.value);
173
+ }
174
+ // Track common problems so that we don't need to repeat logic
175
+ /** The character where a mote should exist. */
176
+ let requiresMote;
177
+ let requiresEmoji;
178
+ // Work through each line type to add diagnostics and completions
179
+ const labelLower = parsedLine.label?.value?.toLowerCase();
180
+ const indicator = parsedLine.indicator?.value;
181
+ // Resets
182
+ if (indicator !== '>') {
183
+ // Then we need to reset the speaker
184
+ lastSpeaker = undefined;
185
+ lastClue = undefined;
186
+ }
187
+ if (indicator !== '!') {
188
+ lastEmojiGroup = undefined;
189
+ }
190
+ // Parsing
191
+ if (labelLower === 'start moments') {
192
+ lastSectionGroup = 'quest_start_moments';
193
+ }
194
+ else if (labelLower === 'end moments') {
195
+ lastSectionGroup = 'quest_end_moments';
196
+ }
197
+ else if (labelLower === 'start requirements') {
198
+ lastSectionGroup = 'quest_start_requirements';
199
+ }
200
+ else if (labelLower === 'end requirements') {
201
+ lastSectionGroup = 'quest_end_requirements';
202
+ }
203
+ if (indicator === '\t') {
204
+ // No data gets stored here, this is just a convenience marker
205
+ // to set the speaker for the next set of dialog lines.
206
+ requiresMote = {
207
+ at: parsedLine.indicator.end,
208
+ options: motes.allowedSpeakers,
209
+ };
210
+ lastSpeaker = parsedLine.moteTag?.value;
211
+ }
212
+ else if (labelLower === 'clue') {
213
+ requiresMote = {
214
+ at: parsedLine.labelGroup.end,
215
+ options: motes.allowedSpeakers,
216
+ };
217
+ lastClue = {
218
+ id: parsedLine.arrayTag?.value?.trim(),
219
+ speaker: parsedLine.moteTag?.value?.trim(),
220
+ phrases: [],
221
+ };
222
+ result.parsed.clues ||= [];
223
+ result.parsed.clues.push(lastClue);
224
+ }
225
+ else if (indicator === '>') {
226
+ // Then this is a dialog line, either within a Clue or a Dialog Moment
227
+ const emoji = emojiIdFromName(parsedLine.emojiName?.value);
228
+ if (parsedLine.emojiGroup) {
229
+ // Emojis are optional. If we see a "group" (parentheses) then
230
+ // that changes to a requirement.
231
+ requiresEmoji = {
232
+ at: changedPosition(parsedLine.emojiGroup.start, { characters: 1 }),
233
+ options: motes.emojis,
234
+ };
235
+ }
236
+ checkSpelling(parsedLine.text);
237
+ const moment = {
238
+ kind: 'dialogue',
239
+ id: parsedLine.arrayTag?.value?.trim(),
240
+ speaker: lastSpeaker,
241
+ emoji,
242
+ text: parsedLine.text?.value?.trim() || '',
243
+ };
244
+ if (lastClue) {
245
+ lastClue.phrases.push(moment);
246
+ }
247
+ else if (isQuestMomentLabel(lastSectionGroup)) {
248
+ result.parsed[lastSectionGroup].push(moment);
249
+ }
250
+ else {
251
+ // Then this is an error!
252
+ result.diagnostics.push({
253
+ message: `Dialog line without a Clue or Moment!`,
254
+ ...lineRange,
255
+ });
256
+ }
257
+ }
258
+ else if (labelLower === 'name') {
259
+ result.parsed.name = parsedLine.text?.value?.trim();
260
+ if (!result.parsed.name) {
261
+ result.diagnostics.push({
262
+ message: `Quest name required!`,
263
+ ...lineRange,
264
+ });
265
+ }
266
+ }
267
+ else if (labelLower === 'draft') {
268
+ result.parsed.draft = parsedLine.text?.value?.trim() === 'true';
269
+ }
270
+ else if (labelLower === 'log') {
271
+ result.parsed.quest_start_log = parsedLine.text?.value?.trim();
272
+ checkSpelling(parsedLine.text);
273
+ }
274
+ else if (labelLower === 'storyline') {
275
+ requiresMote = {
276
+ at: parsedLine.labelGroup.end,
277
+ options: motes.storylines,
278
+ };
279
+ result.parsed.storyline = parsedLine.moteTag?.value?.trim();
280
+ }
281
+ else if (labelLower === 'giver') {
282
+ requiresMote = {
283
+ at: parsedLine.labelGroup.end,
284
+ options: motes.allowedGivers,
285
+ };
286
+ result.parsed.quest_giver = parsedLine.moteTag?.value?.trim();
287
+ }
288
+ else if (labelLower === 'receiver') {
289
+ requiresMote = {
290
+ at: parsedLine.labelGroup.end,
291
+ options: motes.allowedGivers,
292
+ };
293
+ result.parsed.quest_receiver = parsedLine.moteTag?.value?.trim();
294
+ }
295
+ else if (indicator === ':)') {
296
+ if (isQuestMomentLabel(lastSectionGroup)) {
297
+ // Then this is a declaration line for an Emote moment
298
+ // Create the new emoji group
299
+ lastEmojiGroup = {
300
+ kind: 'emote',
301
+ id: parsedLine.arrayTag?.value?.trim(),
302
+ emotes: [],
303
+ };
304
+ result.parsed[lastSectionGroup].push(lastEmojiGroup);
305
+ }
306
+ else {
307
+ result.diagnostics.push({
308
+ message: `Must be defined in a Start/End Moments section!`,
309
+ ...lineRange,
310
+ });
311
+ }
312
+ }
313
+ else if (indicator === '!') {
314
+ // Then this is an emote within a Emote moment
315
+ if (lastEmojiGroup) {
316
+ // Add speaker autocompletes
317
+ if (parsedLine.sep) {
318
+ requiresMote = {
319
+ at: parsedLine.sep.end,
320
+ options: motes.allowedSpeakers,
321
+ };
322
+ }
323
+ else {
324
+ result.diagnostics.push({
325
+ message: `Invalid syntax. Space required after the prefix.`,
326
+ ...lineRange,
327
+ });
328
+ }
329
+ // Add emoji autocompletes
330
+ if (parsedLine.emojiGroup) {
331
+ requiresEmoji = {
332
+ at: changedPosition(parsedLine.emojiGroup.start, {
333
+ characters: 1,
334
+ }),
335
+ options: motes.emojis,
336
+ };
337
+ }
338
+ else {
339
+ result.diagnostics.push({
340
+ message: `Invalid syntax. Emoji required after the speaker.`,
341
+ ...lineRange,
342
+ });
343
+ }
344
+ lastEmojiGroup.emotes.push({
345
+ id: parsedLine.arrayTag?.value?.trim(),
346
+ speaker: parsedLine.moteTag?.value?.trim(),
347
+ emoji: emojiIdFromName(parsedLine.emojiName?.value),
348
+ });
349
+ }
350
+ else {
351
+ result.diagnostics.push({
352
+ message: `Missing an Emote declaration line!`,
353
+ ...lineRange,
354
+ });
355
+ }
356
+ }
357
+ else if (indicator === '?') {
358
+ // Then this is a non-dialog quest moment. These are not implemented, but should be available as placeholders.
359
+ if (isQuestMomentLabel(lastSectionGroup)) {
360
+ // Put an autocomplete after the "?#meh " prefix
361
+ if (parsedLine.sep) {
362
+ result.completions.push({
363
+ type: 'momentStyles',
364
+ options: momentStyles,
365
+ start: parsedLine.sep.end,
366
+ end: parsedLine.sep.end,
367
+ });
368
+ }
369
+ else {
370
+ result.diagnostics.push({
371
+ message: `Invalid syntax. Space required after the prefix.`,
372
+ ...lineRange,
373
+ });
374
+ }
375
+ // Add to data if this momentStyle actually exists
376
+ if (momentStyles.includes(parsedLine.style?.value)) {
377
+ result.parsed[lastSectionGroup].push({
378
+ kind: 'other',
379
+ id: parsedLine.arrayTag?.value?.trim(),
380
+ style: parsedLine.style?.value,
381
+ });
382
+ }
383
+ else {
384
+ result.diagnostics.push({
385
+ message: `Unknown moment style "${parsedLine.style?.value}"`,
386
+ ...lineRange,
387
+ });
388
+ }
389
+ }
390
+ else if (isQuestRequirementLabel(lastSectionGroup)) {
391
+ // Put an autocomplete after the "?#meh " prefix
392
+ if (parsedLine.sep) {
393
+ result.completions.push({
394
+ type: 'requirementStyles',
395
+ options: requirementCompletions,
396
+ start: parsedLine.sep.end,
397
+ end: parsedLine.sep.end,
398
+ });
399
+ }
400
+ else {
401
+ result.diagnostics.push({
402
+ message: `Invalid syntax. Space required after the prefix.`,
403
+ ...lineRange,
404
+ });
405
+ }
406
+ const style = parsedLine.style?.value;
407
+ const isValidStyle = requirementStyles.includes(style);
408
+ const isQuest = style === 'Quest';
409
+ const hasValidQuestStatus = requirementQuestStatuses.includes(parsedLine.status?.value);
410
+ // Autocomplete Quests
411
+ if (isQuest) {
412
+ const canProvideAutocomplete = !!parsedLine.sep;
413
+ if (canProvideAutocomplete) {
414
+ requiresMote = {
415
+ at: parsedLine.sep.end,
416
+ options: motes.quests,
417
+ };
418
+ }
419
+ else {
420
+ result.diagnostics.push({
421
+ message: `Invalid syntax. Space required after the prefix.`,
422
+ ...lineRange,
423
+ });
424
+ }
425
+ }
426
+ // Add to parsed data
427
+ if (isQuest) {
428
+ if (hasValidQuestStatus) {
429
+ result.parsed[lastSectionGroup].push({
430
+ kind: 'quest',
431
+ style: 'Quest',
432
+ id: parsedLine.arrayTag?.value?.trim(),
433
+ quest: parsedLine.moteTag?.value?.trim(),
434
+ status: parsedLine.status?.value,
435
+ });
436
+ }
437
+ else {
438
+ result.diagnostics.push({
439
+ message: `Unknown quest status "${parsedLine.status?.value}"`,
440
+ ...lineRange,
441
+ });
442
+ }
443
+ }
444
+ else if (isValidStyle) {
445
+ result.parsed[lastSectionGroup].push({
446
+ kind: 'other',
447
+ id: parsedLine.arrayTag?.value?.trim(),
448
+ style: style,
449
+ });
450
+ }
451
+ else {
452
+ result.diagnostics.push({
453
+ message: `Unknown requirement style "${style}"`,
454
+ ...lineRange,
455
+ });
456
+ }
457
+ }
458
+ else {
459
+ result.diagnostics.push({
460
+ message: `Must be defined in a Start/End Moments/Requirements section!`,
461
+ ...lineRange,
462
+ });
463
+ }
464
+ }
465
+ else if (indicator === '//') {
466
+ // Then this is a comment/note
467
+ result.parsed.comments.push({
468
+ id: parsedLine.arrayTag?.value?.trim(),
469
+ text: parsedLine.text?.value?.trim(),
470
+ });
471
+ checkSpelling(parsedLine.text);
472
+ }
473
+ if (requiresEmoji) {
474
+ const where = {
475
+ start: requiresEmoji.at,
476
+ end: parsedLine.emojiGroup.end,
477
+ };
478
+ if (!parsedLine.emojiName?.value) {
479
+ result.completions.push({
480
+ type: 'motes',
481
+ options: requiresEmoji.options,
482
+ ...where,
483
+ });
484
+ }
485
+ else if (!emojiIdFromName(parsedLine.emojiName?.value)) {
486
+ result.diagnostics.push({
487
+ message: `Emoji "${parsedLine.emojiName?.value}" not found!`,
488
+ ...where,
489
+ });
490
+ }
491
+ }
492
+ if (requiresMote) {
493
+ if (!parsedLine.moteName || !parsedLine.moteTag) {
494
+ const where = {
495
+ start: requiresMote.at,
496
+ end: parsedLine.emojiGroup?.start || lineRange.end,
497
+ };
498
+ result.completions.push({
499
+ type: 'motes',
500
+ options: requiresMote.options,
501
+ ...where,
502
+ });
503
+ if (!parsedLine.moteTag) {
504
+ result.diagnostics.push({
505
+ message: `Mote required!`,
506
+ ...where,
507
+ });
508
+ }
509
+ else if (!packed.working.getMote(parsedLine.moteTag.value)) {
510
+ result.diagnostics.push({
511
+ message: `Mote not found!`,
512
+ ...where,
513
+ });
514
+ }
515
+ }
516
+ }
517
+ }
518
+ catch (err) {
519
+ if (err instanceof Error) {
520
+ err.cause = trace;
521
+ }
522
+ throw err;
523
+ }
524
+ index += line.length;
525
+ }
526
+ return result;
527
+ }
528
+ export async function updateChangesFromParsedQuest(parsed, moteId, packed) {
529
+ const _traceLogs = [];
530
+ const trace = (log) => _traceLogs.push(log);
531
+ trace(`Updating mote ${moteId}`);
532
+ try {
533
+ // We're always going to be computing ALL changes, so clear whatever
534
+ // we previously had.
535
+ packed.clearMoteChanges(moteId);
536
+ const questMoteBase = packed.base.getMote(moteId);
537
+ const questMoteWorking = packed.working.getMote(moteId);
538
+ const schema = packed.working.getSchema('cl2_quest');
539
+ assert(schema.name, 'Quest mote must have a name pointer');
540
+ assert(schema, 'cl2_quest schema not found in working copy');
541
+ const updateMote = (path, value) => {
542
+ packed.updateMoteData(moteId, path, value);
543
+ };
544
+ updateMote('data/name', parsed.name);
545
+ updateMote('data/quest_giver/item', parsed.quest_giver);
546
+ updateMote('data/quest_receiver/item', parsed.quest_receiver);
547
+ updateMote('data/quest_start_log/text', parsed.quest_start_log);
548
+ updateMote('data/wip/draft', parsed.draft);
549
+ updateMote('data/storyline', parsed.storyline);
550
+ const parsedComments = parsed.comments.filter((c) => !!c.text);
551
+ const parsedClues = parsed.clues.filter((c) => !!c.id && !!c.speaker);
552
+ //#region COMMENTS
553
+ // Add/Update COMMENTS
554
+ trace(`Updating comments`);
555
+ for (const comment of parsedComments) {
556
+ trace(`Updating comment ${comment.id} with text "${comment.text}"`);
557
+ updateMote(`data/wip/comments/${comment.id}/element`, comment.text);
558
+ }
559
+ // Remove deleted comments
560
+ for (const existingComment of bsArrayToArray(questMoteBase?.data.wip?.comments || {})) {
561
+ if (!parsedComments.find((c) => c.id === existingComment.id)) {
562
+ trace(`Deleting comment ${existingComment.id}`);
563
+ updateMote(`data/wip/comments/${existingComment.id}`, null);
564
+ }
565
+ }
566
+ // Get the BASE order of the comments (if any) and use those
567
+ // as the starting point for an up to date order.
568
+ const comments = parsedComments.map((c) => {
569
+ // Look up the base comment
570
+ let comment = questMoteBase?.data.wip?.comments?.[c.id];
571
+ if (!comment) {
572
+ comment = questMoteWorking?.data.wip?.comments?.[c.id];
573
+ // @ts-expect-error - order is a required field, but it'll be re-added
574
+ delete comment?.order;
575
+ }
576
+ assert(comment, `Comment ${c.id} not found in base or working mote`);
577
+ return { ...comment, id: c.id };
578
+ });
579
+ trace('Updating comment order');
580
+ updateBsArrayOrder(comments);
581
+ comments.forEach((comment) => {
582
+ trace(`Updating comment ${comment.id} order to ${comment.order}`);
583
+ updateMote(`data/wip/comments/${comment.id}/order`, comment.order);
584
+ });
585
+ //#endregion
586
+ //#region CLUES
587
+ // Add/update clues
588
+ trace(`Updating clues`);
589
+ for (const clue of parsedClues) {
590
+ trace(`Setting clue ${clue.id} speaker to "${clue.speaker}"`);
591
+ updateMote(`data/clues/${clue.id}/element/speaker`, clue.speaker);
592
+ for (const phrase of clue.phrases) {
593
+ trace(`Setting phrase ${phrase.id} text to "${phrase.text}"`);
594
+ updateMote(`data/clues/${clue.id}/element/phrases/${phrase.id}/element/phrase/text/text`, phrase.text || '');
595
+ if (phrase.emoji) {
596
+ trace(`Setting phrase ${phrase.id} emoji to "${phrase.emoji}"`);
597
+ updateMote(`data/clues/${clue.id}/element/phrases/${phrase.id}/element/phrase/emoji`, phrase.emoji);
598
+ }
599
+ }
600
+ }
601
+ // Delete clues that were removed
602
+ trace(`Deleting removed clues`);
603
+ for (const existingClue of bsArrayToArray(questMoteBase?.data.clues || {})) {
604
+ const parsedClue = parsedClues.find((c) => c.id === existingClue.id);
605
+ if (!parsedClue) {
606
+ trace(`Deleting clue ${existingClue.id}`);
607
+ updateMote(`data/clues/${existingClue.id}`, null);
608
+ }
609
+ else {
610
+ // Delete phrases that were removed
611
+ for (const existingPhrase of bsArrayToArray(existingClue.element.phrases)) {
612
+ if (!parsedClue.phrases.find((p) => p.id === existingPhrase.id)) {
613
+ trace(`Deleting phrase ${existingPhrase.id} from clue ${existingClue.id}`);
614
+ updateMote(`data/clues/${existingClue.id}/element/phrases/${existingPhrase.id}`, null);
615
+ }
616
+ }
617
+ }
618
+ }
619
+ // Update the order of the clues and phrases
620
+ trace(`Updating clue order`);
621
+ const clues = parsedClues.map((c) => {
622
+ // Look up the base clue
623
+ let clue = questMoteBase?.data.clues?.[c.id];
624
+ if (!clue) {
625
+ clue = questMoteWorking?.data.clues?.[c.id];
626
+ // @ts-expect-error - order is a required field, but it'll be re-added
627
+ delete clue?.order;
628
+ }
629
+ assert(clue, `Clue ${c.id} not found in base or working mote`);
630
+ const phrases = c.phrases.map((p) => {
631
+ let phrase = questMoteBase?.data.clues?.[c.id]?.element?.phrases?.[p.id];
632
+ if (!phrase) {
633
+ phrase =
634
+ questMoteWorking?.data.clues?.[c.id]?.element?.phrases?.[p.id];
635
+ // @ts-expect-error - order is a required field, but it'll be re-added
636
+ delete phrase?.order;
637
+ }
638
+ assert(phrase, `Phrase ${p.id} not found in base or working mote`);
639
+ return { ...phrase, id: p.id };
640
+ });
641
+ trace(`Updating phrase order for clue ${c.id}`);
642
+ updateBsArrayOrder(phrases);
643
+ return { ...clue, phrases, id: c.id };
644
+ });
645
+ updateBsArrayOrder(clues);
646
+ clues.forEach((clue) => {
647
+ trace(`Updating clue ${clue.id} order to ${clue.order}`);
648
+ updateMote(`data/clues/${clue.id}/order`, clue.order);
649
+ clue.phrases.forEach((phrase) => {
650
+ trace(`Updating phrase ${phrase.id} order to ${phrase.order}`);
651
+ updateMote(`data/clues/${clue.id}/element/phrases/${phrase.id}/order`, phrase.order);
652
+ });
653
+ });
654
+ //#endregion
655
+ for (const requirementGroup of [
656
+ 'quest_start_requirements',
657
+ 'quest_end_requirements',
658
+ ]) {
659
+ trace(`Updating Requirement Group ${requirementGroup}`);
660
+ const parsedRequirements = parsed[requirementGroup];
661
+ // Add/Update requirements
662
+ trace('Adding/updating requirements');
663
+ for (const requirement of parsedRequirements) {
664
+ trace(`Updating requirement ${requirement.id}`);
665
+ updateMote(`data/${requirementGroup}/${requirement.id}/element/style`, requirement.style);
666
+ if (requirement.kind === 'quest') {
667
+ updateMote(`data/${requirementGroup}/${requirement.id}/element/quest`, requirement.quest);
668
+ updateMote(`data/${requirementGroup}/${requirement.id}/element/quest_status`, requirement.status);
669
+ }
670
+ }
671
+ // Delete requirements that were removed
672
+ trace('Deleting removed requirements');
673
+ for (const existingRequirement of bsArrayToArray(questMoteBase?.data[requirementGroup] || {})) {
674
+ const parsedRequirement = parsedRequirements.find((r) => r.id === existingRequirement.id);
675
+ if (!parsedRequirement) {
676
+ trace(`Deleting removed requirement ${existingRequirement.id}`);
677
+ updateMote(`data/${requirementGroup}/${existingRequirement.id}`, null);
678
+ }
679
+ }
680
+ // Update the requirement order
681
+ trace('Updating requirement order');
682
+ const requirements = parsedRequirements.map((r) => {
683
+ let requirement = questMoteBase?.data[requirementGroup]?.[r.id];
684
+ if (!requirement) {
685
+ requirement = questMoteWorking?.data[requirementGroup]?.[r.id];
686
+ // @ts-expect-error - order is a required field, but it'll be re-added
687
+ delete requirement?.order;
688
+ }
689
+ assert(requirement, `Requirement ${r.id} not found in base or working mote`);
690
+ return { ...requirement, id: r.id };
691
+ });
692
+ updateBsArrayOrder(requirements);
693
+ requirements.forEach((r) => {
694
+ trace(`Updating requirement ${r.id} order to ${r.order}`);
695
+ updateMote(`data/${requirementGroup}/${r.id}/order`, r.order);
696
+ });
697
+ }
698
+ //#region QUEST MOMENTS
699
+ for (const momentGroup of [
700
+ 'quest_start_moments',
701
+ 'quest_end_moments',
702
+ ]) {
703
+ trace(`Updating Moment Group ${momentGroup}`);
704
+ const parsedMoments = parsed[momentGroup];
705
+ // Add/Update moments
706
+ trace('Adding/updating moments');
707
+ for (const moment of parsedMoments) {
708
+ trace(`Updating moment ${moment.id} of kind ${moment.kind}`);
709
+ const setStyle = (style) => updateMote(`data/${momentGroup}/${moment.id}/element/style`, style);
710
+ if (moment.kind === 'other') {
711
+ // Note: we're only tracking style for moment types that
712
+ // are not fully implemented.
713
+ setStyle(moment.style);
714
+ }
715
+ else if (moment.kind === 'dialogue') {
716
+ trace('Updating speaker');
717
+ setStyle('Dialogue');
718
+ updateMote(`data/${momentGroup}/${moment.id}/element/speech/speaker`, moment.speaker);
719
+ trace('Updating emoji');
720
+ updateMote(`data/${momentGroup}/${moment.id}/element/speech/emotion`, moment.emoji);
721
+ trace('Updating text');
722
+ updateMote(`data/${momentGroup}/${moment.id}/element/speech/text/text`, moment.text);
723
+ }
724
+ else if (moment.kind === 'emote') {
725
+ setStyle('Emote');
726
+ for (const emote of moment.emotes) {
727
+ updateMote(`data/${momentGroup}/${moment.id}/element/emotes/${emote.id}/element/key`, emote.speaker);
728
+ updateMote(`data/${momentGroup}/${moment.id}/element/emotes/${emote.id}/element/value`, emote.emoji);
729
+ }
730
+ }
731
+ trace(`Updated moment ${moment.id}`);
732
+ }
733
+ // Delete moments that were removed
734
+ trace('Deleting removed moments');
735
+ for (const existingMoment of bsArrayToArray(questMoteBase?.data[momentGroup] || {})) {
736
+ const parsedMoment = parsedMoments.find((m) => m.id === existingMoment.id);
737
+ const existingElement = existingMoment.element;
738
+ if (!parsedMoment) {
739
+ trace(`Deleting removed moment ${existingMoment.id}`);
740
+ updateMote(`data/${momentGroup}/${existingMoment.id}`, null);
741
+ }
742
+ else if (existingElement.style === 'Emote') {
743
+ // Delete emotes that were removed
744
+ trace(`Deleting removed emotes from moment ${existingMoment.id}`);
745
+ assert(parsedMoment.kind === 'emote', `Expected moment ${existingMoment.id} to be an emote`);
746
+ for (const existingEmote of bsArrayToArray(existingElement.emotes)) {
747
+ if (!parsedMoment.emotes.find((e) => e.id === existingEmote.id)) {
748
+ trace(`Deleting removed emote ${existingEmote.id} from moment ${existingMoment.id}`);
749
+ updateMote(`data/${momentGroup}/${existingMoment.id}/element/emotes/${existingEmote.id}`, null);
750
+ }
751
+ }
752
+ }
753
+ }
754
+ // Update the order of the moments
755
+ trace('Updating moment order');
756
+ const moments = parsedMoments.map((m) => {
757
+ // Look up the base moment
758
+ let moment = questMoteBase?.data[momentGroup]?.[m.id];
759
+ if (!moment) {
760
+ moment = questMoteWorking?.data[momentGroup]?.[m.id];
761
+ // @ts-expect-error - order is a required field, but it'll be re-added
762
+ delete moment?.order;
763
+ }
764
+ assert(moment, `Moment ${m.id} not found in base or working mote`);
765
+ moment.element.style;
766
+ const element = moment.element;
767
+ if (element.style === 'Emote') {
768
+ assert(m.kind === 'emote', `Expected moment ${m.id} to be an emote`);
769
+ // Then make sure the emotes are in the right order
770
+ const emotes = m.emotes.map((e) => {
771
+ let emoteElement = questMoteBase?.data[momentGroup]?.[m.id]?.element;
772
+ let emote;
773
+ if (emoteElement && isEmoteMoment(emoteElement)) {
774
+ emote = emoteElement.emotes[e.id];
775
+ }
776
+ if (!emote) {
777
+ emoteElement =
778
+ questMoteWorking?.data[momentGroup]?.[m.id]?.element;
779
+ if (emoteElement && isEmoteMoment(emoteElement)) {
780
+ emote = emoteElement.emotes[e.id];
781
+ }
782
+ // Then we don't need to try to keep a prior order value
783
+ delete emote?.order;
784
+ }
785
+ assert(emote, `Emote ${e.id} not found in base or working mote`);
786
+ return { ...emote, id: e.id };
787
+ });
788
+ trace(`Updating emote order for moment ${m.id}`);
789
+ updateBsArrayOrder(emotes);
790
+ return { ...moment, emotes, id: m.id };
791
+ }
792
+ return { ...moment, id: m.id };
793
+ });
794
+ updateBsArrayOrder(moments);
795
+ moments.forEach((m) => {
796
+ trace(`Updating moment ${m.id} order to ${m.order}`);
797
+ updateMote(`data/${momentGroup}/${m.id}/order`, m.order);
798
+ if ('emotes' in m) {
799
+ m.emotes.forEach((e) => {
800
+ trace(`Updating emote ${e.id} order to ${e.order}`);
801
+ updateMote(`data/${momentGroup}/${m.id}/element/emotes/${e.id}/order`, e.order);
802
+ });
803
+ }
804
+ });
805
+ }
806
+ //#endregion
807
+ trace(`Writing changes`);
808
+ await packed.writeChanges();
809
+ }
810
+ catch (err) {
811
+ console.error(err);
812
+ console.error(_traceLogs);
813
+ if (err instanceof Error) {
814
+ err.cause = _traceLogs;
815
+ }
816
+ throw err;
817
+ }
818
+ }
819
+ function isQuestRequirementLabel(label) {
820
+ return !!(label?.startsWith('quest_') && label.endsWith('_requirements'));
821
+ }
822
+ function isQuestMomentLabel(label) {
823
+ return !!(label?.startsWith('quest_') && label.endsWith('_moments'));
824
+ }
825
+ //# sourceMappingURL=cl2.quest.parse.js.map