@bscotch/gml-parser 1.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 (208) hide show
  1. package/LICENSE.md +29 -0
  2. package/README.md +151 -0
  3. package/assets/GmlSpec.xml +11419 -0
  4. package/dist/index.d.ts +16 -0
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/index.js +13 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/jsdoc.d.ts +79 -0
  9. package/dist/jsdoc.d.ts.map +1 -0
  10. package/dist/jsdoc.feather.d.ts +23 -0
  11. package/dist/jsdoc.feather.d.ts.map +1 -0
  12. package/dist/jsdoc.feather.js +143 -0
  13. package/dist/jsdoc.feather.js.map +1 -0
  14. package/dist/jsdoc.js +468 -0
  15. package/dist/jsdoc.js.map +1 -0
  16. package/dist/jsdoc.test.d.ts +2 -0
  17. package/dist/jsdoc.test.d.ts.map +1 -0
  18. package/dist/jsdoc.test.js +185 -0
  19. package/dist/jsdoc.test.js.map +1 -0
  20. package/dist/lexer.d.ts +3 -0
  21. package/dist/lexer.d.ts.map +1 -0
  22. package/dist/lexer.js +14 -0
  23. package/dist/lexer.js.map +1 -0
  24. package/dist/lexer.test.d.ts +2 -0
  25. package/dist/lexer.test.d.ts.map +1 -0
  26. package/dist/lexer.test.js +78 -0
  27. package/dist/lexer.test.js.map +1 -0
  28. package/dist/lib.objects.d.ts +190 -0
  29. package/dist/lib.objects.d.ts.map +1 -0
  30. package/dist/lib.objects.js +242 -0
  31. package/dist/lib.objects.js.map +1 -0
  32. package/dist/logger.d.ts +13 -0
  33. package/dist/logger.d.ts.map +1 -0
  34. package/dist/logger.js +14 -0
  35. package/dist/logger.js.map +1 -0
  36. package/dist/modules.d.ts +19 -0
  37. package/dist/modules.d.ts.map +1 -0
  38. package/dist/modules.js +320 -0
  39. package/dist/modules.js.map +1 -0
  40. package/dist/modules.test.d.ts +2 -0
  41. package/dist/modules.test.d.ts.map +1 -0
  42. package/dist/modules.test.js +57 -0
  43. package/dist/modules.test.js.map +1 -0
  44. package/dist/modules.types.d.ts +78 -0
  45. package/dist/modules.types.d.ts.map +1 -0
  46. package/dist/modules.types.js +2 -0
  47. package/dist/modules.types.js.map +1 -0
  48. package/dist/modules.util.d.ts +5 -0
  49. package/dist/modules.util.d.ts.map +1 -0
  50. package/dist/modules.util.js +13 -0
  51. package/dist/modules.util.js.map +1 -0
  52. package/dist/parser.d.ts +121 -0
  53. package/dist/parser.d.ts.map +1 -0
  54. package/dist/parser.js +571 -0
  55. package/dist/parser.js.map +1 -0
  56. package/dist/parser.test.d.ts +2 -0
  57. package/dist/parser.test.d.ts.map +1 -0
  58. package/dist/parser.test.js +143 -0
  59. package/dist/parser.test.js.map +1 -0
  60. package/dist/parser.utility.d.ts +29 -0
  61. package/dist/parser.utility.d.ts.map +1 -0
  62. package/dist/parser.utility.js +125 -0
  63. package/dist/parser.utility.js.map +1 -0
  64. package/dist/project.asset.d.ts +115 -0
  65. package/dist/project.asset.d.ts.map +1 -0
  66. package/dist/project.asset.js +802 -0
  67. package/dist/project.asset.js.map +1 -0
  68. package/dist/project.code.d.ts +130 -0
  69. package/dist/project.code.d.ts.map +1 -0
  70. package/dist/project.code.js +570 -0
  71. package/dist/project.code.js.map +1 -0
  72. package/dist/project.d.ts +533 -0
  73. package/dist/project.d.ts.map +1 -0
  74. package/dist/project.diagnostics.d.ts +32 -0
  75. package/dist/project.diagnostics.d.ts.map +1 -0
  76. package/dist/project.diagnostics.js +23 -0
  77. package/dist/project.diagnostics.js.map +1 -0
  78. package/dist/project.js +1216 -0
  79. package/dist/project.js.map +1 -0
  80. package/dist/project.location.d.ts +133 -0
  81. package/dist/project.location.d.ts.map +1 -0
  82. package/dist/project.location.js +219 -0
  83. package/dist/project.location.js.map +1 -0
  84. package/dist/project.native.d.ts +26 -0
  85. package/dist/project.native.d.ts.map +1 -0
  86. package/dist/project.native.js +321 -0
  87. package/dist/project.native.js.map +1 -0
  88. package/dist/project.spec.d.ts +1298 -0
  89. package/dist/project.spec.d.ts.map +1 -0
  90. package/dist/project.spec.js +263 -0
  91. package/dist/project.spec.js.map +1 -0
  92. package/dist/project.test.d.ts +2 -0
  93. package/dist/project.test.d.ts.map +1 -0
  94. package/dist/project.test.js +633 -0
  95. package/dist/project.test.js.map +1 -0
  96. package/dist/shaderDefaults.d.ts +3 -0
  97. package/dist/shaderDefaults.d.ts.map +1 -0
  98. package/dist/shaderDefaults.js +32 -0
  99. package/dist/shaderDefaults.js.map +1 -0
  100. package/dist/signifiers.d.ts +54 -0
  101. package/dist/signifiers.d.ts.map +1 -0
  102. package/dist/signifiers.flags.d.ts +38 -0
  103. package/dist/signifiers.flags.d.ts.map +1 -0
  104. package/dist/signifiers.flags.js +131 -0
  105. package/dist/signifiers.flags.js.map +1 -0
  106. package/dist/signifiers.js +117 -0
  107. package/dist/signifiers.js.map +1 -0
  108. package/dist/spine.d.ts +28 -0
  109. package/dist/spine.d.ts.map +1 -0
  110. package/dist/spine.js +64 -0
  111. package/dist/spine.js.map +1 -0
  112. package/dist/spine.test.d.ts +2 -0
  113. package/dist/spine.test.d.ts.map +1 -0
  114. package/dist/spine.test.js +420 -0
  115. package/dist/spine.test.js.map +1 -0
  116. package/dist/spine.types.d.ts +89 -0
  117. package/dist/spine.types.d.ts.map +1 -0
  118. package/dist/spine.types.js +2 -0
  119. package/dist/spine.types.js.map +1 -0
  120. package/dist/test.lib.d.ts +3 -0
  121. package/dist/test.lib.d.ts.map +1 -0
  122. package/dist/test.lib.js +16 -0
  123. package/dist/test.lib.js.map +1 -0
  124. package/dist/tokens.categories.d.ts +22 -0
  125. package/dist/tokens.categories.d.ts.map +1 -0
  126. package/dist/tokens.categories.js +78 -0
  127. package/dist/tokens.categories.js.map +1 -0
  128. package/dist/tokens.code.d.ts +2 -0
  129. package/dist/tokens.code.d.ts.map +1 -0
  130. package/dist/tokens.code.js +523 -0
  131. package/dist/tokens.code.js.map +1 -0
  132. package/dist/tokens.d.ts +130 -0
  133. package/dist/tokens.d.ts.map +1 -0
  134. package/dist/tokens.js +13 -0
  135. package/dist/tokens.js.map +1 -0
  136. package/dist/tokens.lib.d.ts +15 -0
  137. package/dist/tokens.lib.d.ts.map +1 -0
  138. package/dist/tokens.lib.js +12 -0
  139. package/dist/tokens.lib.js.map +1 -0
  140. package/dist/tokens.shared.d.ts +4 -0
  141. package/dist/tokens.shared.d.ts.map +1 -0
  142. package/dist/tokens.shared.js +35 -0
  143. package/dist/tokens.shared.js.map +1 -0
  144. package/dist/tokens.strings.d.ts +5 -0
  145. package/dist/tokens.strings.d.ts.map +1 -0
  146. package/dist/tokens.strings.js +111 -0
  147. package/dist/tokens.strings.js.map +1 -0
  148. package/dist/types.checks.d.ts +50 -0
  149. package/dist/types.checks.d.ts.map +1 -0
  150. package/dist/types.checks.js +246 -0
  151. package/dist/types.checks.js.map +1 -0
  152. package/dist/types.d.ts +152 -0
  153. package/dist/types.d.ts.map +1 -0
  154. package/dist/types.feather.d.ts +21 -0
  155. package/dist/types.feather.d.ts.map +1 -0
  156. package/dist/types.feather.js +156 -0
  157. package/dist/types.feather.js.map +1 -0
  158. package/dist/types.hover.d.ts +4 -0
  159. package/dist/types.hover.d.ts.map +1 -0
  160. package/dist/types.hover.js +99 -0
  161. package/dist/types.hover.js.map +1 -0
  162. package/dist/types.js +457 -0
  163. package/dist/types.js.map +1 -0
  164. package/dist/types.primitives.d.ts +10 -0
  165. package/dist/types.primitives.d.ts.map +1 -0
  166. package/dist/types.primitives.js +88 -0
  167. package/dist/types.primitives.js.map +1 -0
  168. package/dist/types.sprites.d.ts +8 -0
  169. package/dist/types.sprites.d.ts.map +1 -0
  170. package/dist/types.sprites.js +417 -0
  171. package/dist/types.sprites.js.map +1 -0
  172. package/dist/types.test.d.ts +2 -0
  173. package/dist/types.test.d.ts.map +1 -0
  174. package/dist/types.test.js +62 -0
  175. package/dist/types.test.js.map +1 -0
  176. package/dist/util.d.ts +50 -0
  177. package/dist/util.d.ts.map +1 -0
  178. package/dist/util.js +168 -0
  179. package/dist/util.js.map +1 -0
  180. package/dist/util.test.d.ts +3 -0
  181. package/dist/util.test.d.ts.map +1 -0
  182. package/dist/util.test.js +63 -0
  183. package/dist/util.test.js.map +1 -0
  184. package/dist/visitor.assign.d.ts +24 -0
  185. package/dist/visitor.assign.d.ts.map +1 -0
  186. package/dist/visitor.assign.js +112 -0
  187. package/dist/visitor.assign.js.map +1 -0
  188. package/dist/visitor.d.ts +89 -0
  189. package/dist/visitor.d.ts.map +1 -0
  190. package/dist/visitor.functionExpression.d.ts +7 -0
  191. package/dist/visitor.functionExpression.d.ts.map +1 -0
  192. package/dist/visitor.functionExpression.js +216 -0
  193. package/dist/visitor.functionExpression.js.map +1 -0
  194. package/dist/visitor.globals.d.ts +59 -0
  195. package/dist/visitor.globals.d.ts.map +1 -0
  196. package/dist/visitor.globals.js +271 -0
  197. package/dist/visitor.globals.js.map +1 -0
  198. package/dist/visitor.identifierAccessor.d.ts +6 -0
  199. package/dist/visitor.identifierAccessor.d.ts.map +1 -0
  200. package/dist/visitor.identifierAccessor.js +381 -0
  201. package/dist/visitor.identifierAccessor.js.map +1 -0
  202. package/dist/visitor.js +605 -0
  203. package/dist/visitor.js.map +1 -0
  204. package/dist/visitor.processor.d.ts +66 -0
  205. package/dist/visitor.processor.d.ts.map +1 -0
  206. package/dist/visitor.processor.js +147 -0
  207. package/dist/visitor.processor.js.map +1 -0
  208. package/package.json +63 -0
@@ -0,0 +1,802 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ import { pathy } from '@bscotch/pathy';
3
+ import { sequential } from '@bscotch/utility';
4
+ import { Yy, yyObjectEventSchema, yyRoomInstanceLayerSchema, yyRoomInstanceSchema, yySchemas, yySpriteSchema, } from '@bscotch/yy';
5
+ import { logger } from './logger.js';
6
+ import { Code } from './project.code.js';
7
+ import { Diagnostic } from './project.diagnostics.js';
8
+ import { Signifier } from './signifiers.js';
9
+ import { Type } from './types.js';
10
+ import { assert, getPngSize, groupPathToPosix, ok } from './util.js';
11
+ export function isAssetOfKind(asset, kind) {
12
+ return (asset !== null && typeof asset === 'object' && asset.assetKind === kind);
13
+ }
14
+ export function assertIsAssetOfKind(asset, kind) {
15
+ assert(isAssetOfKind(asset, kind), `Expected asset to be of kind ${kind}`);
16
+ }
17
+ export class Asset {
18
+ project;
19
+ resource;
20
+ $tag = 'Asset';
21
+ assetKind;
22
+ gmlFiles = new Map();
23
+ yy;
24
+ yyPath;
25
+ signifier;
26
+ /** For objects, their instance type. */
27
+ instanceType;
28
+ assetType;
29
+ variables;
30
+ nativeVariables;
31
+ /** For objects, their parent */
32
+ _parent = undefined;
33
+ initalized = {
34
+ globals: false,
35
+ locals: false,
36
+ };
37
+ constructor(project, resource, yyPath) {
38
+ this.project = project;
39
+ this.resource = resource;
40
+ assert(yyPath, 'Must provide a YY path');
41
+ this.assetKind = resource.id.path.split(/[/\\]/)[0];
42
+ this.yyPath = yyPath.withValidator(yySchemas[this.assetKind]);
43
+ // Create the symbol
44
+ this.signifier = new Signifier(this.project.self, this.name);
45
+ this.signifier.def = {};
46
+ this.signifier.global = true;
47
+ this.signifier.asset = true;
48
+ // Create the Asset.<> type
49
+ this.assetType = new Type(this.assetTypeKind).named(this.name);
50
+ this.signifier.setType(this.assetType);
51
+ this.assetType.signifier = this.signifier;
52
+ // Add this asset to the project lookup, unless it is a script.
53
+ if (!['scripts', 'extensions'].includes(this.assetKind)) {
54
+ this.project.self.addMember(this.signifier);
55
+ if (this.assetType.kind !== 'Any') {
56
+ this.project.types.set(this.typeName, this.assetType);
57
+ }
58
+ }
59
+ // If this is an object, also create the instance type
60
+ if (this.assetKind === 'objects') {
61
+ this.nativeVariables = Type.Struct;
62
+ // Create the base struct-type to store all of the variables.
63
+ for (const member of this.project.native.objectInstanceBase.listMembers()) {
64
+ if (member.name === 'id')
65
+ continue; // This is added later
66
+ const copy = member.copy();
67
+ copy.override = true; // Guarantee we keep this as a copy
68
+ this.nativeVariables.addMember(copy);
69
+ }
70
+ this.variables = new Type('Struct');
71
+ this.variables.extends = this.nativeVariables;
72
+ this.variables.signifier = this.signifier;
73
+ // It will be used as the parent for the Instance/Asset types
74
+ this.assetType.extends = this.variables;
75
+ this.instanceType = new Type('Id.Instance').named(this.name);
76
+ this.instanceType.extends = this.variables;
77
+ this.instanceType.signifier = this.signifier;
78
+ const id = new Signifier(this.variables, 'id', this.instanceType);
79
+ id.instance = true;
80
+ id.native = 'Base';
81
+ id.writable = false;
82
+ id.override = true; // We are guaranteeing that we're using
83
+ this.variables.addMember(id);
84
+ this.project.types.set(this.instanceTypeName, this.instanceType);
85
+ }
86
+ }
87
+ get typeName() {
88
+ return `${this.assetType.kind}.${this.name}`;
89
+ }
90
+ get instanceTypeName() {
91
+ return `Id.Instance.${this.name}`;
92
+ }
93
+ async saveYy() {
94
+ assert(this.yyPath, 'Cannot save YY without a path');
95
+ await Yy.write(this.yyPath.absolute, this.yy, this.assetKind, this.project.yyp);
96
+ }
97
+ get isScript() {
98
+ return this.assetKind === 'scripts';
99
+ }
100
+ get isObject() {
101
+ return this.assetKind === 'objects';
102
+ }
103
+ get isSound() {
104
+ return this.assetKind === 'sounds';
105
+ }
106
+ get isRoom() {
107
+ return this.assetKind === 'rooms';
108
+ }
109
+ get isSprite() {
110
+ return this.assetKind === 'sprites';
111
+ }
112
+ get isSpineSprite() {
113
+ if (this.assetKind !== 'sprites') {
114
+ return false;
115
+ }
116
+ const yy = this.yy;
117
+ return yy.type === 2;
118
+ }
119
+ get soundFile() {
120
+ assert(isAssetOfKind(this, 'sounds'), 'Can only get sound files from sound assets');
121
+ const yy = this.yy;
122
+ return this.dir.join(yy.soundFile);
123
+ }
124
+ get sprite() {
125
+ assert(isAssetOfKind(this, 'objects'), 'Can only get sprites from objects');
126
+ const yy = this.yy;
127
+ const spriteName = yy.spriteId?.name;
128
+ const sprite = spriteName
129
+ ? this.project.getAssetByName(spriteName)
130
+ : undefined;
131
+ if (spriteName && !sprite) {
132
+ logger.warn(`Sprite ${spriteName} has no asset`);
133
+ }
134
+ return sprite;
135
+ }
136
+ set sprite(sprite) {
137
+ assert(isAssetOfKind(this, 'objects'), 'Can only set sprites on objects');
138
+ const yy = this.yy;
139
+ yy.spriteId = sprite ? sprite.resource.id : null;
140
+ // Fire off async to avoid blocking
141
+ void this.saveYy();
142
+ }
143
+ get children() {
144
+ assert(isAssetOfKind(this, 'objects'), 'Can only get children of objects');
145
+ const children = [];
146
+ for (const asset of this.project.assets.values()) {
147
+ if (isAssetOfKind(asset, 'objects') && asset.parent === this) {
148
+ children.push(asset);
149
+ }
150
+ }
151
+ return children;
152
+ }
153
+ get parent() {
154
+ return this._parent;
155
+ }
156
+ set parent(parent) {
157
+ const oldParent = this._parent;
158
+ if (oldParent === parent) {
159
+ // No change!
160
+ return;
161
+ }
162
+ this._parent = parent;
163
+ if (parent) {
164
+ // The instanceType parent is a struct that holds all of this
165
+ // object's instance variables. We need to set ITS parent to
166
+ // the parent's instanceType.
167
+ this.variables.extends = parent.variables;
168
+ }
169
+ else {
170
+ this.variables.extends = this.nativeVariables;
171
+ }
172
+ // Do we need to change the yy file?
173
+ const yy = this.yy;
174
+ const parentFromYy = this.project.getAssetByName(yy.parentObjectId?.name);
175
+ if (parentFromYy !== parent) {
176
+ // Then we need to update the yy. Just fire it off async for now so
177
+ // that this function can remain synchronous.
178
+ if (!parent) {
179
+ yy.parentObjectId = null;
180
+ }
181
+ else {
182
+ yy.parentObjectId = parent.resource.id;
183
+ }
184
+ // TODO: Maybe reprocess?
185
+ this.updateDiagnostics();
186
+ void this.saveYy();
187
+ }
188
+ }
189
+ /**
190
+ * Get the entire parent heirarchy, with immediate first
191
+ * and most-distant last
192
+ */
193
+ get parents() {
194
+ if (!this.parent) {
195
+ return [];
196
+ }
197
+ return [this.parent, ...this.parent.parents];
198
+ }
199
+ /**
200
+ * Get the first GML file belonging to this resource.
201
+ * For scripts, this is the *only* GML file.*/
202
+ get gmlFile() {
203
+ return this.gmlFilesArray[0];
204
+ }
205
+ get gmlFilesArray() {
206
+ return [...this.gmlFiles.values()].sort((a, b) => {
207
+ if (a.name === 'Create_0') {
208
+ return -1;
209
+ }
210
+ else if (b.name === 'Create_0') {
211
+ return 1;
212
+ }
213
+ return 0;
214
+ });
215
+ }
216
+ getEventByName(name) {
217
+ assert(this.isObject, 'Can only get events for objects');
218
+ return this.gmlFilesArray.find((gml) => gml.name === name);
219
+ }
220
+ get shaderPaths() {
221
+ if (this.assetKind !== 'shaders') {
222
+ return undefined;
223
+ }
224
+ return {
225
+ vertex: this.yyPath.changeExtension('vsh'),
226
+ fragment: this.yyPath.changeExtension('fsh'),
227
+ };
228
+ }
229
+ get roomInstances() {
230
+ assertIsAssetOfKind(this, 'rooms');
231
+ const instances = new Map();
232
+ // Loop through each instance layer's instances to get IDs and objects
233
+ const yy = this.yy;
234
+ for (const layer of yy.layers) {
235
+ if (layer.resourceType !== 'GMRInstanceLayer') {
236
+ continue;
237
+ }
238
+ for (const instance of layer.instances || []) {
239
+ const obj = this.project.getAssetByName(instance.objectId.name);
240
+ if (isAssetOfKind(obj, 'objects')) {
241
+ instances.set(instance.name, obj);
242
+ }
243
+ }
244
+ }
245
+ // Loop through the instance order to get everything in the expected order
246
+ return yy.instanceCreationOrder
247
+ .map((x) => ({
248
+ instanceId: x.name,
249
+ object: instances.get(x.name),
250
+ }))
251
+ .filter((x) => !!x.object);
252
+ }
253
+ get frameIds() {
254
+ if (this.assetKind !== 'sprites') {
255
+ return [];
256
+ }
257
+ const yy = this.yy;
258
+ return yy.frames.map((f) => f.name);
259
+ }
260
+ get framePaths() {
261
+ const paths = [];
262
+ if (this.assetKind !== 'sprites') {
263
+ return paths;
264
+ }
265
+ const yy = this.yy;
266
+ for (const frame of yy.frames || []) {
267
+ paths.push(this.dir.join(`${frame.name}.png`));
268
+ }
269
+ return paths;
270
+ }
271
+ get spinePaths() {
272
+ if (!this.isSpineSprite) {
273
+ return undefined;
274
+ }
275
+ const yy = this.yy;
276
+ const frameId = yy.frames?.[0].name;
277
+ if (!frameId) {
278
+ return undefined;
279
+ }
280
+ return {
281
+ json: this.dir.join(`${frameId}.json`),
282
+ atlas: this.dir.join(`${frameId}.atlas`),
283
+ };
284
+ }
285
+ /**
286
+ * During an Object asset rename, we need to ensure that all references to the
287
+ * old name are updated to the new name. This includes the object's name in rooms.
288
+ */
289
+ async renameRoomInstanceObjects(oldObjectName, newObjectName) {
290
+ assert(this.isRoom, 'Can only rename object instances in rooms'); // Iterate through each instance layer and remove any instances with the given ID
291
+ const yy = this.yy;
292
+ let didUpdate = false;
293
+ for (const layer of yy.layers) {
294
+ if (layer.resourceType !== 'GMRInstanceLayer') {
295
+ continue;
296
+ }
297
+ layer.instances.forEach((instance) => {
298
+ if (instance.objectId.name.toLowerCase() === oldObjectName.toLowerCase()) {
299
+ instance.objectId.name = newObjectName;
300
+ instance.objectId.path = `objects/${newObjectName}/${newObjectName}.yy`;
301
+ didUpdate = true;
302
+ }
303
+ });
304
+ }
305
+ if (didUpdate) {
306
+ await this.saveYy();
307
+ }
308
+ }
309
+ async removeRoomInstance(instanceId) {
310
+ assert(this.isRoom, 'Can only add object instances to rooms');
311
+ const yy = this.yy;
312
+ // Iterate through each instance layer and remove any instances with the given ID
313
+ for (const layer of yy.layers) {
314
+ if (layer.resourceType !== 'GMRInstanceLayer') {
315
+ continue;
316
+ }
317
+ layer.instances = (layer.instances || []).filter((x) => x.name !== instanceId);
318
+ }
319
+ // Remove the instance from the creation order
320
+ yy.instanceCreationOrder = (yy.instanceCreationOrder || []).filter((x) => x.name !== instanceId);
321
+ await this.saveYy();
322
+ }
323
+ async reorganizeRoomInstances(instanceIds) {
324
+ assert(this.isRoom, 'Can only add object instances to rooms');
325
+ const instanceIdsSet = new Set(instanceIds);
326
+ assert(instanceIds.length === instanceIdsSet.size, 'Cannot have duplicate instance IDs');
327
+ const yy = this.yy;
328
+ const currentIds = new Set(yy.instanceCreationOrder.map((x) => x.name));
329
+ // Ensure that the new order includes all existing instances
330
+ assert(instanceIdsSet.size === currentIds.size &&
331
+ [...instanceIdsSet].every((x) => currentIds.has(x)), 'New order must include all existing instances');
332
+ yy.instanceCreationOrder = instanceIds.map((name) => ({
333
+ name,
334
+ path: `rooms/${this.name}/${this.name}.yy`,
335
+ }));
336
+ await this.saveYy();
337
+ }
338
+ async addRoomInstance(obj, x = 0, y = 0) {
339
+ assert(this.isRoom, 'Can only add object instances to rooms');
340
+ const yy = this.yy;
341
+ // Ensure we have an instance layer
342
+ let instanceLayer = yy.layers.find((x) => x.resourceType === 'GMRInstanceLayer');
343
+ if (!instanceLayer) {
344
+ instanceLayer = yyRoomInstanceLayerSchema.parse({});
345
+ yy.layers.unshift(instanceLayer);
346
+ }
347
+ // Add a new instance
348
+ const instance = yyRoomInstanceSchema.parse({
349
+ objectId: obj.resource.id,
350
+ x,
351
+ y,
352
+ });
353
+ instanceLayer.instances.push(instance);
354
+ yy.instanceCreationOrder ||= [];
355
+ yy.instanceCreationOrder.push({
356
+ name: instance.name,
357
+ path: `rooms/${this.name}/${this.name}.yy`,
358
+ });
359
+ await this.saveYy();
360
+ }
361
+ get folder() {
362
+ return groupPathToPosix(this.yy.parent.path);
363
+ }
364
+ /**
365
+ * Check if this asset is in the given asset group ("folder").
366
+ * @param path E.g. `my/folder/of/stuff`
367
+ */
368
+ isInFolder(path) {
369
+ // Normalize the incoming path
370
+ path = groupPathToPosix(path);
371
+ const currentFolder = this.folder;
372
+ return path === currentFolder || currentFolder.startsWith(`${path}/`);
373
+ }
374
+ /** Move to a different, *existing* folder. */
375
+ async moveToFolder(path) {
376
+ assert(path, 'Must provide a path with non-zero length!');
377
+ if (!path.endsWith('.yy')) {
378
+ path = `folders/${path}.yy`;
379
+ }
380
+ const folderName = path
381
+ .split(/[/\\]+/)
382
+ .pop()
383
+ .replace(/\.yy$/, '');
384
+ // Make sure that folder exists.
385
+ assert(folderName, 'Folder name is empty');
386
+ assert(this.project.yyp.Folders.find((x) => x.folderPath === path), `Folder ${path} does not exist`);
387
+ assert(this.yy.parent, 'Asset has no folder field');
388
+ // @ts-expect-error
389
+ logger.info('moving', this.name, 'from', this.yy.parent.path, 'to', path);
390
+ this.yy.parent = { name: folderName, path };
391
+ await this.saveYy();
392
+ }
393
+ /**
394
+ * Re-order the existing frames of a sprite.
395
+ * Any frames not included in the new order will be deleted.
396
+ */
397
+ async reorganizeFrames(newFrameIdOrder) {
398
+ assert(this.isSprite, 'Can only delete frames from a sprite');
399
+ assert(!this.isSpineSprite, 'Cannot delete frames from a Spine sprite');
400
+ if (!newFrameIdOrder.length)
401
+ return;
402
+ let yy = this.yy;
403
+ const oldFrameIds = yy.frames.map((x) => x.name);
404
+ assert(newFrameIdOrder.every((x) => oldFrameIds.includes(x)), "Can't reorder frames that don't exist");
405
+ yy.frames = newFrameIdOrder.map((frameId) => {
406
+ const oldFrame = yy.frames.find((x) => x.name === frameId);
407
+ assert(oldFrame, 'Frame not found');
408
+ return oldFrame;
409
+ });
410
+ await this.saveYy();
411
+ // Delete any old frame images
412
+ await Promise.all(oldFrameIds
413
+ .filter((x) => !newFrameIdOrder.includes(x))
414
+ .map((frameId) => {
415
+ const path = this.dir.join(`${frameId}.png`);
416
+ return path.delete();
417
+ }));
418
+ }
419
+ async deleteFrames(frameIds) {
420
+ assert(this.isSprite, 'Can only delete frames from a sprite');
421
+ assert(!this.isSpineSprite, 'Cannot delete frames from a Spine sprite');
422
+ if (!frameIds.length)
423
+ return;
424
+ let yy = this.yy;
425
+ yy.frames = yy.frames.filter((x) => !frameIds.includes(x.name));
426
+ await this.saveYy();
427
+ // TODO: Delete the image files
428
+ await Promise.all(frameIds.map((frameId) => {
429
+ const path = this.dir.join(`${frameId}.png`);
430
+ return path.delete();
431
+ }));
432
+ }
433
+ async addFrames(sourceImages) {
434
+ assert(this.isSprite, 'Can only add frames to a sprite');
435
+ assert(!this.isSpineSprite, 'Cannot add frames to a Spine sprite');
436
+ if (!sourceImages.length)
437
+ return;
438
+ assert(sourceImages.every((x) => x.hasExtension('png')), 'All frames must be PNGs');
439
+ let yy = this.yy;
440
+ let expectedDims;
441
+ if (!yy.frames.length) {
442
+ // Then get the expected dimensions from the first image
443
+ expectedDims = await getPngSize(sourceImages[0]);
444
+ }
445
+ else {
446
+ expectedDims = {
447
+ width: yy.width,
448
+ height: yy.height,
449
+ };
450
+ }
451
+ const frameSizes = await Promise.all(sourceImages.map((x) => getPngSize(x)));
452
+ assert(frameSizes.every((x) => x.width === expectedDims.width && x.height === expectedDims.height), `Expected all frames to have width ${expectedDims.width} and height ${expectedDims.height}`);
453
+ const startingFrameCount = yy.frames.length;
454
+ // Update the YY file to get new frameIds
455
+ yy.frames.length = startingFrameCount + sourceImages.length;
456
+ yy = yySpriteSchema.parse(yy); // Will fill out the new frames
457
+ // Copy the new frames over.
458
+ await Promise.all(sourceImages.map((source, i) => {
459
+ const dest = this.dir.join(`${yy.frames[startingFrameCount + i].name}.png`);
460
+ return source.copy(dest);
461
+ }));
462
+ await this.saveYy();
463
+ }
464
+ async createEvent(eventInfo) {
465
+ assert(this.isObject, 'Can only create events for objects');
466
+ // Create the file if it doesn't already exist
467
+ const path = this.dir.join(`${eventInfo.name}.gml`);
468
+ if (!(await path.exists())) {
469
+ await path.write('/// ');
470
+ }
471
+ // Update the YY file
472
+ const yy = this.yy;
473
+ yy.eventList ||= [];
474
+ if (yy.eventList.find((x) => x.eventNum === eventInfo.eventNum &&
475
+ x.eventType === eventInfo.eventType)) {
476
+ logger.warn(`Event ${eventInfo.name} already exists on ${this.name}`);
477
+ return;
478
+ }
479
+ yy.eventList.push(yyObjectEventSchema.parse({
480
+ eventNum: eventInfo.eventNum,
481
+ eventType: eventInfo.eventType,
482
+ }));
483
+ await this.saveYy();
484
+ return this.addGmlFile(path);
485
+ }
486
+ async readYy() {
487
+ let asPath = pathy(this.yyPath);
488
+ if (!(await asPath.exists())) {
489
+ const filePattern = new RegExp(`${this.name}\\.yy$`, 'i');
490
+ const paths = await pathy(this.dir).listChildren();
491
+ asPath = paths.find((x) => filePattern.test(x.basename));
492
+ }
493
+ ok(asPath, `Could not find a .yy file for ${this.name}`);
494
+ this.yy = await Yy.read(asPath.absolute, this.assetKind);
495
+ return this.yy;
496
+ }
497
+ get dir() {
498
+ return this.yyPath.up();
499
+ }
500
+ get name() {
501
+ return this.resource.id.name;
502
+ }
503
+ /**
504
+ * Reprocess an existing file after it has been modified.
505
+ */
506
+ async reloadFile(path, virtualContent) {
507
+ const gml = this.getGmlFile(path);
508
+ if (!gml) {
509
+ return;
510
+ }
511
+ await gml.reload(virtualContent, { reloadDirty: true });
512
+ }
513
+ getGmlFile(path) {
514
+ assert(path, 'GML Path does not exist');
515
+ return this.gmlFiles.get(path.absolute.toLocaleLowerCase());
516
+ }
517
+ updateParent() {
518
+ if (this.assetKind !== 'objects') {
519
+ return;
520
+ }
521
+ const yy = this.yy;
522
+ if (!yy.parentObjectId) {
523
+ return;
524
+ }
525
+ const parent = this.project.getAssetByName(yy.parentObjectId.name);
526
+ if (!parent || parent.assetKind !== 'objects') {
527
+ // TODO: Add diagnostic if parent missing
528
+ return;
529
+ }
530
+ // Set the parent
531
+ this.parent = parent;
532
+ }
533
+ updateGlobals(initial = false) {
534
+ this.updateParent();
535
+ // Ensure parent is updated first
536
+ if (initial && !this.initalized.globals && this.parent) {
537
+ this.parent.updateGlobals(initial);
538
+ }
539
+ else if (initial && this.initalized.globals) {
540
+ // Already initialized by a child
541
+ return;
542
+ }
543
+ for (const gml of this.gmlFilesArray) {
544
+ gml.updateGlobals();
545
+ }
546
+ this.initalized.globals = true;
547
+ }
548
+ updateAllSymbols(initial = false) {
549
+ // Ensure parent is updated first
550
+ if (initial && !this.initalized.locals && this.parent) {
551
+ this.parent.updateAllSymbols(initial);
552
+ }
553
+ else if (initial && this.initalized.locals) {
554
+ // Already initialized by a child
555
+ return;
556
+ }
557
+ for (const gml of this.gmlFilesArray) {
558
+ gml.updateAllSymbols();
559
+ }
560
+ this.initalized.locals = true;
561
+ }
562
+ updateDiagnostics() {
563
+ for (const gml of this.gmlFilesArray) {
564
+ gml.updateDiagnostics();
565
+ }
566
+ }
567
+ addGmlFile(path) {
568
+ const gml = this.getGmlFile(path) ||
569
+ new Code(this, path);
570
+ assert(path, 'Cannot add GML file, path does not exist');
571
+ this.gmlFiles.set(path.absolute.toLocaleLowerCase(), gml);
572
+ return gml;
573
+ }
574
+ async reload() {
575
+ // Find all immediate children, which might include legacy GML files
576
+ const [, children] = await Promise.all([
577
+ await this.readYy(),
578
+ this.dir.listChildren(),
579
+ ]);
580
+ if (this.assetKind === 'scripts') {
581
+ this.addScriptFile(children);
582
+ }
583
+ else if (this.assetKind === 'objects') {
584
+ this.gmlFiles.clear();
585
+ this.addObjectFile(children);
586
+ }
587
+ else if (this.assetKind === 'extensions') {
588
+ const diagnostics = [];
589
+ // Load constants and functions from the extension
590
+ const typeIndexToName = (idx) => {
591
+ return idx === 1 ? 'String' : 'Real';
592
+ };
593
+ const yy = this.yy;
594
+ for (const file of yy.files) {
595
+ for (const constant of file.constants) {
596
+ if (constant.hidden) {
597
+ continue;
598
+ }
599
+ if (this.project.self.getMember(constant.name)) {
600
+ continue;
601
+ }
602
+ try {
603
+ // Get the type by parsing the value. For now we'll just check for number or string, else "Any".
604
+ const type = constant.value.startsWith('"')
605
+ ? 'String'
606
+ : constant.value.match(/^-?[\d_.]+$/)
607
+ ? 'Real'
608
+ : 'Any';
609
+ const signifier = new Signifier(this.project.self, constant.name, new Type(type));
610
+ signifier.macro = true;
611
+ signifier.global = true;
612
+ signifier.writable = false;
613
+ signifier.def = {};
614
+ this.project.self.addMember(signifier);
615
+ }
616
+ catch (err) {
617
+ diagnostics.push(new Diagnostic(`Error loading extension constant: ${constant.name}`, {
618
+ start: { line: 0, column: 0, offset: 0 },
619
+ end: { line: 0, column: 0, offset: 0 },
620
+ }, 'error', err));
621
+ }
622
+ }
623
+ for (const func of file.functions) {
624
+ if (func.hidden) {
625
+ continue;
626
+ }
627
+ if (this.project.self.getMember(func.externalName)) {
628
+ continue;
629
+ }
630
+ try {
631
+ const type = new Type('Function')
632
+ .named(func.externalName)
633
+ .describe(func.help);
634
+ type.setReturnType(new Type(typeIndexToName(func.returnType)));
635
+ for (let i = 0; i < func.args.length; i++) {
636
+ const typeIdx = func.args[i];
637
+ type.addParameter(i, `argument${i}`, {
638
+ type: new Type(typeIndexToName(typeIdx)),
639
+ });
640
+ }
641
+ const signifier = new Signifier(this.project.self, func.name, type);
642
+ signifier.global = true;
643
+ signifier.writable = false;
644
+ signifier.def = {};
645
+ this.project.self.addMember(signifier);
646
+ this.project.types.set(`Function.${func.externalName}`, type);
647
+ }
648
+ catch (err) {
649
+ diagnostics.push(new Diagnostic(`Error loading extension function: ${func.name}`, {
650
+ start: { line: 1, column: 1, offset: 0 },
651
+ end: { line: 1, column: 1, offset: 0 },
652
+ }, 'error', err));
653
+ }
654
+ }
655
+ }
656
+ this.project.emitDiagnostics(this.yyPath.absolute, diagnostics);
657
+ }
658
+ await this.initiallyReadAndParseGml();
659
+ }
660
+ async onRemove() {
661
+ await Promise.all(this.gmlFilesArray.map((gml) => gml.remove()));
662
+ // Remove this signifier and any global types from the project
663
+ this.project.self.removeMember(this.signifier.name);
664
+ for (const typeName of [this.typeName, this.instanceTypeName]) {
665
+ const type = this.project.types.get(typeName);
666
+ if (type) {
667
+ this.project.types.delete(this.typeName);
668
+ // Try to get any refereneces using this type updated
669
+ for (const ref of type.signifier?.refs || []) {
670
+ ref.file.dirty = true;
671
+ }
672
+ }
673
+ }
674
+ // Remove the associated files
675
+ await this.dir.delete({ force: true, recursive: true });
676
+ }
677
+ addObjectFile(children) {
678
+ // Objects have one file per event, named after the event.
679
+ // The YY file includes the list of events, but references them by
680
+ // numeric identifiers instead of their name. For now we'll just
681
+ // assume that the GML files are correct.
682
+ children
683
+ .filter((p) => p.hasExtension('gml'))
684
+ .forEach((p) => this.addGmlFile(p));
685
+ }
686
+ addScriptFile(children) {
687
+ // Scripts should have exactly one GML file, which is the script itself,
688
+ // named the same as the script (though there could be casing variations)
689
+ const matches = children.filter((p) => p.basename.toLocaleLowerCase() ===
690
+ `${this.name?.toLocaleLowerCase?.()}.gml`);
691
+ if (matches.length !== 1) {
692
+ logger.error(`Script ${this.name} has ${matches.length} GML files. Expected 1.`);
693
+ }
694
+ else {
695
+ this.addGmlFile(matches[0]);
696
+ }
697
+ }
698
+ async initiallyReadAndParseGml() {
699
+ const parseWaits = [];
700
+ for (const file of this.gmlFilesArray) {
701
+ parseWaits.push(file.parse());
702
+ }
703
+ return await Promise.all(parseWaits);
704
+ }
705
+ get assetTypeKind() {
706
+ switch (this.assetKind) {
707
+ case 'objects':
708
+ return 'Asset.GMObject';
709
+ case 'rooms':
710
+ return 'Asset.GMRoom';
711
+ case 'scripts':
712
+ return 'Asset.GMScript';
713
+ case 'sprites':
714
+ return 'Asset.GMSprite';
715
+ case 'sounds':
716
+ return 'Asset.GMSound';
717
+ case 'paths':
718
+ return 'Asset.GMPath';
719
+ case 'shaders':
720
+ return 'Asset.GMShader';
721
+ case 'timelines':
722
+ return 'Asset.GMTimeline';
723
+ case 'fonts':
724
+ return 'Asset.GMFont';
725
+ default:
726
+ return 'Any';
727
+ }
728
+ }
729
+ static async from(project, resource) {
730
+ let yyPath = project.dir.join(resource.id.path);
731
+ if (!(await yyPath.exists())) {
732
+ const assetsDir = yyPath.up(2);
733
+ const namePattern = new RegExp(`^${resource.id.name}$`, 'i');
734
+ const dir = (await assetsDir.listChildren()).find((p) => p.basename.match(namePattern));
735
+ if (dir) {
736
+ const yyPattern = new RegExp(`^${resource.id.name}\\.yy$`, 'i');
737
+ yyPath = (await dir.listChildren()).find((p) => p.basename.match(yyPattern));
738
+ if (!yyPath) {
739
+ logger.warn(`Could not find file for "${resource.id.path}"`);
740
+ }
741
+ }
742
+ else {
743
+ logger.warn(`Could not find folder for "${resource.id.path}"`);
744
+ }
745
+ }
746
+ if (!yyPath) {
747
+ return;
748
+ }
749
+ const item = new Asset(project, resource, yyPath);
750
+ await item.reload();
751
+ return item;
752
+ }
753
+ }
754
+ __decorate([
755
+ sequential,
756
+ __metadata("design:type", Function),
757
+ __metadata("design:paramtypes", []),
758
+ __metadata("design:returntype", Promise)
759
+ ], Asset.prototype, "saveYy", null);
760
+ __decorate([
761
+ sequential,
762
+ __metadata("design:type", Function),
763
+ __metadata("design:paramtypes", [String, String]),
764
+ __metadata("design:returntype", Promise)
765
+ ], Asset.prototype, "renameRoomInstanceObjects", null);
766
+ __decorate([
767
+ sequential,
768
+ __metadata("design:type", Function),
769
+ __metadata("design:paramtypes", [String]),
770
+ __metadata("design:returntype", Promise)
771
+ ], Asset.prototype, "removeRoomInstance", null);
772
+ __decorate([
773
+ sequential,
774
+ __metadata("design:type", Function),
775
+ __metadata("design:paramtypes", [Array]),
776
+ __metadata("design:returntype", Promise)
777
+ ], Asset.prototype, "reorganizeRoomInstances", null);
778
+ __decorate([
779
+ sequential,
780
+ __metadata("design:type", Function),
781
+ __metadata("design:paramtypes", [Asset, Object, Object]),
782
+ __metadata("design:returntype", Promise)
783
+ ], Asset.prototype, "addRoomInstance", null);
784
+ __decorate([
785
+ sequential,
786
+ __metadata("design:type", Function),
787
+ __metadata("design:paramtypes", [Array]),
788
+ __metadata("design:returntype", Promise)
789
+ ], Asset.prototype, "reorganizeFrames", null);
790
+ __decorate([
791
+ sequential,
792
+ __metadata("design:type", Function),
793
+ __metadata("design:paramtypes", [Array]),
794
+ __metadata("design:returntype", Promise)
795
+ ], Asset.prototype, "deleteFrames", null);
796
+ __decorate([
797
+ sequential,
798
+ __metadata("design:type", Function),
799
+ __metadata("design:paramtypes", [Array]),
800
+ __metadata("design:returntype", Promise)
801
+ ], Asset.prototype, "addFrames", null);
802
+ //# sourceMappingURL=project.asset.js.map