@makefully/platypus 4.1.5 → 4.1.8

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 (136) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +3 -3
  3. package/lib/platypus.js +1 -1
  4. package/package.json +7 -6
  5. package/src/AABB.js +685 -0
  6. package/src/ActionState.js +121 -0
  7. package/src/AssetManager.js +206 -0
  8. package/src/CollisionData.js +96 -0
  9. package/src/CollisionDataContainer.js +127 -0
  10. package/src/CollisionShape.js +315 -0
  11. package/src/Component.js +231 -0
  12. package/src/Data.js +64 -0
  13. package/src/DataMap.js +204 -0
  14. package/src/Entity.js +404 -0
  15. package/src/EntityLinker.js +113 -0
  16. package/src/EventHandler.js +115 -0
  17. package/src/EventHandlerList.js +251 -0
  18. package/src/Game.js +840 -0
  19. package/src/Gamepad.js +175 -0
  20. package/src/ID3CaptionPlayer.js +61 -0
  21. package/src/Messenger.js +365 -0
  22. package/src/PIXIAnimation.js +622 -0
  23. package/src/RandomSet.js +140 -0
  24. package/src/SFXPlayer.js +120 -0
  25. package/src/StateMap.js +252 -0
  26. package/src/Storage.js +152 -0
  27. package/src/TickerClient.js +48 -0
  28. package/src/TickerServer.js +23 -0
  29. package/src/TimeEvent.js +50 -0
  30. package/src/TimeEventList.js +100 -0
  31. package/src/VOPlayer.js +684 -0
  32. package/src/Vector.js +702 -0
  33. package/src/components/AIChaser.js +161 -0
  34. package/src/components/AIPacer.js +107 -0
  35. package/src/components/AudioMusic.js +151 -0
  36. package/src/components/AudioSFX.js +673 -0
  37. package/src/components/AudioVO.js +301 -0
  38. package/src/components/CollisionBasic.js +826 -0
  39. package/src/components/CollisionFilter.js +74 -0
  40. package/src/components/CollisionGroup.js +448 -0
  41. package/src/components/CollisionTiles.js +608 -0
  42. package/src/components/ComponentSwitcher.js +173 -0
  43. package/src/components/ControllerInput.js +65 -0
  44. package/src/components/Counter.js +165 -0
  45. package/src/components/EntityCamera.js +215 -0
  46. package/src/components/EntityContainer.js +549 -0
  47. package/src/components/EntityController.js +541 -0
  48. package/src/components/HandlerCamera.js +1052 -0
  49. package/src/components/HandlerCollision.js +1318 -0
  50. package/src/components/HandlerController.js +291 -0
  51. package/src/components/HandlerLogic.js +394 -0
  52. package/src/components/HandlerRender.js +217 -0
  53. package/src/components/Interactive.js +502 -0
  54. package/src/components/LevelBuilder.js +524 -0
  55. package/src/components/LogicAngularMovement.js +154 -0
  56. package/src/components/LogicAttachment.js +265 -0
  57. package/src/components/LogicButton.js +466 -0
  58. package/src/components/LogicCarrier.js +39 -0
  59. package/src/components/LogicDestroyMe.js +58 -0
  60. package/src/components/LogicDirectionalMovement.js +325 -0
  61. package/src/components/LogicDragDrop.js +371 -0
  62. package/src/components/LogicImpactLaunch.js +144 -0
  63. package/src/components/LogicPacingPlatform.js +64 -0
  64. package/src/components/LogicPortable.js +112 -0
  65. package/src/components/LogicPortal.js +115 -0
  66. package/src/components/LogicPushable.js +98 -0
  67. package/src/components/LogicRebounder.js +136 -0
  68. package/src/components/LogicRegionSpawner.js +139 -0
  69. package/src/components/LogicRotationalMovement.js +205 -0
  70. package/src/components/LogicSpawner.js +159 -0
  71. package/src/components/LogicStateMachine.js +382 -0
  72. package/src/components/LogicSwitch.js +77 -0
  73. package/src/components/LogicTeleportee.js +68 -0
  74. package/src/components/LogicTeleporter.js +93 -0
  75. package/src/components/LogicTimer.js +178 -0
  76. package/src/components/LogicWindUpRacer.js +179 -0
  77. package/src/components/Motion.js +371 -0
  78. package/src/components/Mover.js +566 -0
  79. package/src/components/Node.js +289 -0
  80. package/src/components/NodeMap.js +295 -0
  81. package/src/components/NodeResident.js +636 -0
  82. package/src/components/Orientation.js +697 -0
  83. package/src/components/RandomEvents.js +73 -0
  84. package/src/components/RelativePosition.js +204 -0
  85. package/src/components/RelayFamily.js +106 -0
  86. package/src/components/RelayGame.js +53 -0
  87. package/src/components/RelayLinker.js +94 -0
  88. package/src/components/RelayParent.js +57 -0
  89. package/src/components/RelaySelf.js +55 -0
  90. package/src/components/Render.js +178 -0
  91. package/src/components/RenderAnimator.js +275 -0
  92. package/src/components/RenderContainer.js +646 -0
  93. package/src/components/RenderDebug.js +319 -0
  94. package/src/components/RenderDestroyMe.js +59 -0
  95. package/src/components/RenderLottie.js +149 -0
  96. package/src/components/RenderProgress.js +120 -0
  97. package/src/components/RenderSpine.js +840 -0
  98. package/src/components/RenderSprite.js +527 -0
  99. package/src/components/RenderText.js +147 -0
  100. package/src/components/RenderTiles.js +1440 -0
  101. package/src/components/SceneChanger.js +128 -0
  102. package/src/components/TiledLoader.js +1459 -0
  103. package/src/components/Timeline.js +314 -0
  104. package/src/components/Tutorial.js +267 -0
  105. package/src/components/Tween.js +296 -0
  106. package/src/components/VoiceOver.js +373 -0
  107. package/src/components/XHR.js +129 -0
  108. package/src/components/index.js +79 -0
  109. package/src/factory.js +305 -0
  110. package/src/index.js +159 -0
  111. package/src/mixins/PIXI.Graphics.js +41 -0
  112. package/src/platypus.css +68 -0
  113. package/src/sayHello.js +50 -0
  114. package/src/shapes/Capsule.js +74 -0
  115. package/src/shapes/Circle.js +43 -0
  116. package/src/shapes/Ellipse.js +117 -0
  117. package/src/shapes/Point.js +191 -0
  118. package/src/shapes/Polygon.js +112 -0
  119. package/src/shapes/Polyline.js +97 -0
  120. package/src/shapes/Rectangle.js +78 -0
  121. package/src/shapes/RoundedRectangle.js +33 -0
  122. package/src/shapes/Segment.js +346 -0
  123. package/src/shapes/adjustedXY.js +19 -0
  124. package/src/shapes/castFromBox2D.js +92 -0
  125. package/src/shapes/castFromObject.js +53 -0
  126. package/src/shapes/castFromPixiGraphics.js +6 -0
  127. package/src/shapes/castFromPixiShape.js +89 -0
  128. package/src/shapes/castToBox2D.js +190 -0
  129. package/src/shapes/castToPixiGraphics.js +100 -0
  130. package/src/shapes/castToPixiShape.js +61 -0
  131. package/src/shapes/checkPointsMap.js +313 -0
  132. package/src/shapes/index.js +35 -0
  133. package/src/utils/array.js +73 -0
  134. package/src/utils/string.js +39 -0
  135. package/lib/243.platypus.mjs +0 -1
  136. package/lib/platypus.mjs +0 -1
@@ -0,0 +1,1459 @@
1
+ /* global atob, platypus */
2
+ import {arrayCache, greenSlice, union} from '../utils/array.js';
3
+ import AABB from '../AABB.js';
4
+ import Data from '../Data.js';
5
+ import DataMap from '../DataMap.js';
6
+ import Entity from '../Entity.js';
7
+ import EntityLinker from '../EntityLinker';
8
+ import Vector from '../Vector.js';
9
+ import createComponentClass from '../factory.js';
10
+ import {inflate} from 'pako';
11
+
12
+ const
13
+ FILENAME_TO_ID = /^(?:(\w+:)\/{2}(\w+(?:\.\w+)*\/?))?([\/.]*?(?:[^?]+)?\/)?(?:(([^\/?]+)\.(\w+))|([^\/?]+))(?:\?((?:(?:[^&]*?[\/=])?(?:((?:(?:[^\/?&=]+)\.(\w+)))\S*?)|\S+))?)?$/,
14
+ POSITIONS = {
15
+ top: -1,
16
+ bottom: 1,
17
+ left: -1,
18
+ right: 1,
19
+ center: 0,
20
+ middle: 0
21
+ },
22
+ defaultEntityModifier = (object) => object,
23
+ maskId = 0x0fffffff,
24
+ maskXFlip = 0x80000000,
25
+ maskYFlip = 0x40000000,
26
+ maskRotate = 0x20000000,
27
+ decodeString = (str, index) => (((str.charCodeAt(index)) + (str.charCodeAt(index + 1) << 8) + (str.charCodeAt(index + 2) << 16) + (str.charCodeAt(index + 3) << 24 )) >>> 0),
28
+ decodeArray = (arr, index) => ((arr[index] + (arr[index + 1] << 8) + (arr[index + 2] << 16) + (arr[index + 3] << 24 )) >>> 0),
29
+ decodeBase64 = (data, compression) => {
30
+ const
31
+ arr = [],
32
+ compressed = compression === 'zlib',
33
+ step1 = window.atob(data.replace(/\\/g, '')),
34
+ step2 = compressed ? inflate(step1) : step1,
35
+ decode = compressed ? decodeArray : decodeString;
36
+ let index = 4;
37
+
38
+ while (index <= step2.length) {
39
+ arr.push(decode(step2, index - 4));
40
+ index += 4;
41
+ }
42
+
43
+ return arr;
44
+ },
45
+ decodeLayer = function (layer) {
46
+ if (layer.encoding === 'base64') {
47
+ layer.data = decodeBase64(layer.data, layer.compression);
48
+ layer.encoding = 'csv'; // So we won't have to decode again.
49
+ }
50
+ return layer;
51
+ },
52
+ getKey = function (path) {
53
+ const
54
+ result = path.match(FILENAME_TO_ID);
55
+
56
+ return result[5] ?? result[7];
57
+ },
58
+ getPowerOfTen = function (amount) {
59
+ let x = 1;
60
+
61
+ while (x < amount) {
62
+ x *= 10;
63
+ }
64
+
65
+ return x;
66
+ },
67
+ transform = {
68
+ x: 1,
69
+ y: 1,
70
+ id: -1
71
+ },
72
+ getProperty = (...args) => {
73
+ const obj = getPropertyObject(...args);
74
+
75
+ return obj && obj.value;
76
+ },
77
+ getPropertyObject = (obj, key) => { // Handle Tiled map versions
78
+ if (obj) {
79
+ if (Array.isArray(obj)) {
80
+ let i = obj.length;
81
+ while (i--) {
82
+ if (obj[i].name === key) {
83
+ return obj[i];
84
+ }
85
+ }
86
+ return null;
87
+ } else {
88
+ platypus.debug.warn('This Tiled map version is deprecated.');
89
+ return {
90
+ name: key,
91
+ type: typeof obj[key],
92
+ value: obj[key]
93
+ };
94
+ }
95
+ } else {
96
+ return null;
97
+ }
98
+ },
99
+ setProperty = (obj, key, value) => { // Handle Tiled map versions
100
+ if (obj) {
101
+ if (Array.isArray(obj)) {
102
+ let i = obj.length;
103
+
104
+ while (i--) {
105
+ if (obj[i].name === key) {
106
+ obj[i].type = typeof value;
107
+ obj[i].value = value;
108
+ return;
109
+ }
110
+ }
111
+ obj.push({
112
+ name: key,
113
+ type: typeof value,
114
+ value: value
115
+ });
116
+ } else {
117
+ obj[key] = value;
118
+ }
119
+ }
120
+ },
121
+ entityTransformCheck = function (v) {
122
+ const
123
+ a = !!(maskRotate & v),
124
+ b = !!(maskYFlip & v),
125
+ c = !!(maskXFlip & v);
126
+
127
+ transform.id = maskId & v;
128
+ transform.x = c ? -1 : 1;
129
+ transform.y = b ? -1 : 1;
130
+ transform.rotation = a ? 90 : 0;
131
+
132
+ return transform;
133
+ },
134
+ createTilesetObjectGroupReference = function (reference, tilesets) {
135
+ for (let i = 0; i < tilesets.length; i++) {
136
+ const
137
+ tileset = tilesets[i],
138
+ tiles = tileset.tiles;
139
+
140
+ if (tiles) {
141
+ for (let j = 0; j < tiles.length; j++) {
142
+ const
143
+ tile = tiles[j];
144
+
145
+ reference.set(tile.id + tileset.firstgid, tile);
146
+ }
147
+ }
148
+ }
149
+ },
150
+ getObjectCentered = (object) => {
151
+ const
152
+ {height = 0, rotation = 0, width = 0, x = 0, y = 0} = object,
153
+ hw = width / 2,
154
+ hh = height / 2,
155
+ angle = rotation / 180 * Math.PI,
156
+ topLeftOffset = Vector.setUp(hw, hh).rotate(angle),
157
+ centerOfObject = Vector.setUp(x, y).addVector(topLeftOffset),
158
+ centeredObject = {
159
+ ...object,
160
+ x: centerOfObject.x,
161
+ y: centerOfObject.y
162
+ };
163
+
164
+ topLeftOffset.recycle();
165
+ centerOfObject.recycle();
166
+
167
+ return centeredObject;
168
+ },
169
+ getObjectTransformed = (object, {rotation = 0, x = 1, y = 1}, width = 0, height = 0) => {
170
+ const
171
+ coords = Vector.setUp(object.x, object.y).add(-width / 2, -height / 2);
172
+
173
+ if (rotation) {
174
+ coords.rotate(rotation / 180 * Math.PI);
175
+ coords.x *= -x;
176
+ coords.y *= y;
177
+ } else {
178
+ coords.x *= x;
179
+ coords.y *= y;
180
+ }
181
+ coords.add(width / 2, height / 2);
182
+
183
+ return {
184
+ ...object,
185
+ rotation: object.rotation + rotation,
186
+ flipX: x,
187
+ flipY: y,
188
+ x: coords.x,
189
+ y: coords.y
190
+ };
191
+ },
192
+ getEntityData = function (obj, tilesets, entityLinker) {
193
+ const
194
+ properties = {},
195
+ data = {
196
+ gid: -1,
197
+ transform: null,
198
+ properties,
199
+ type: ''
200
+ };
201
+ let gid = obj.gid || -1,
202
+ props = null;
203
+
204
+ if (gid !== -1) {
205
+ data.transform = entityTransformCheck(gid);
206
+ gid = data.gid = transform.id;
207
+ }
208
+
209
+ if (tilesets) {
210
+ let tileset = null;
211
+
212
+ for (let x = 0; x < tilesets.length; x++) {
213
+ if (tilesets[x].firstgid > gid) {
214
+ break;
215
+ } else {
216
+ tileset = tilesets[x];
217
+ }
218
+ }
219
+
220
+ if (tileset?.tiles) {
221
+ const
222
+ tiles = tileset.tiles,
223
+ entityTilesetIndex = gid - tileset.firstgid;
224
+
225
+ for (let i = 0; i < tiles.length; i++) {
226
+ const tile = tiles[i];
227
+ if (tile.id === entityTilesetIndex) {
228
+ props = tile.properties || null;
229
+ data.type = tile.type || '';
230
+ break;
231
+ }
232
+ }
233
+ }
234
+ }
235
+
236
+ // Check Tiled data to find this object's type
237
+ data.type = obj.type || data.type;
238
+
239
+ if (!data.type) { // undefined entity
240
+ return null;
241
+ }
242
+
243
+ //Copy properties from Tiled
244
+ if (data.transform) {
245
+ properties.scaleX = data.transform.x;
246
+ properties.scaleY = data.transform.y;
247
+ } else {
248
+ properties.scaleX = 1;
249
+ properties.scaleY = 1;
250
+ }
251
+
252
+ if (obj.name) {
253
+ properties.name = obj.name; // Unlike "type", this specifies particular entities. Unlike "id", entities can share the same name.
254
+ }
255
+ if (entityLinker) {
256
+ entityLinker.linkObject(data.properties.tiledId = obj.id);
257
+ }
258
+ mergeAndFormatProperties(props, data.properties, entityLinker);
259
+ mergeAndFormatProperties(obj.properties, data.properties, entityLinker);
260
+
261
+ return data;
262
+ },
263
+ mergeAndFormatProperties = function (src, dest, entityLinker) {
264
+ if (src && dest) {
265
+ if (Array.isArray(src)) {
266
+ for (let i = 0; i < src.length; i++) {
267
+ setProperty(dest, src[i].name, formatPropertyObject(src[i], entityLinker));
268
+ }
269
+ } else {
270
+ const
271
+ keys = Object.keys(src),
272
+ {length} = keys;
273
+
274
+ for (let i = 0; i < length; i++) {
275
+ const
276
+ key = keys[i];
277
+
278
+ setProperty(dest, key, formatProperty(src[key]));
279
+ }
280
+ }
281
+ }
282
+
283
+ return dest;
284
+ },
285
+ formatProperty = function (value) {
286
+ if (typeof value === 'string') {
287
+ //This is going to assume that if you pass in something that starts with a number, it is a number and converts it to one.
288
+ // eslint-disable-next-line radix
289
+ const
290
+ numberProperty = parseFloat(value) ?? parseInt(value); // to handle floats and 0x respectively.
291
+
292
+ if (numberProperty === 0 || (!!numberProperty)) {
293
+ return numberProperty;
294
+ } else if (value === 'true') {
295
+ return true;
296
+ } else if (value === 'false') {
297
+ return false;
298
+ } else if ((value.length > 1) && (((value[0] === '{') && (value[value.length - 1] === '}')) || ((value[0] === '[') && (value[value.length - 1] === ']')))) {
299
+ try {
300
+ return JSON.parse(value);
301
+ } catch (e) {
302
+ }
303
+ }
304
+ }
305
+
306
+ return value;
307
+ },
308
+ formatPropertyObject = function ({name, value, type}, entityLinker) {
309
+ switch (type) {
310
+ case 'color':
311
+ // Convert ARGB to RGBA
312
+ return `#${value.substring(3)}${value.substring(1, 3)}`;
313
+ case 'object':
314
+ if (entityLinker && value !== 0) {
315
+ return entityLinker.getEntity(value, name); // if unfound, entityLinker saves this request and will try to fulfill it once the entity is added.
316
+ } else {
317
+ return null;
318
+ }
319
+ case 'string':
320
+ if ((value.length > 1) && (((value[0] === '{') && (value[value.length - 1] === '}')) || ((value[0] === '[') && (value[value.length - 1] === ']')))) {
321
+ try {
322
+ return JSON.parse(value);
323
+ } catch (e) {
324
+ }
325
+ }
326
+ break;
327
+ case 'bool':
328
+ case 'float':
329
+ case 'file':
330
+ case 'int':
331
+ default:
332
+ break;
333
+ }
334
+
335
+ return value;
336
+ },
337
+ checkLevel = function (levelOrLevelId, ss) {
338
+ const
339
+ addObjectGroupAssets = (assets, objectGroup, tilesets) => {
340
+ const objects = objectGroup.objects;
341
+
342
+ for (let i = 0; i < objects.length; i++) {
343
+ const entity = getEntityData(objects[i], tilesets);
344
+ if (entity) {
345
+ const entityAssets = Entity.getAssetList(entity);
346
+ union(assets, entityAssets);
347
+ arrayCache.recycle(entityAssets);
348
+ }
349
+ }
350
+ },
351
+ level = (typeof levelOrLevelId === 'string') ? platypus.game.settings.levels[levelOrLevelId] : levelOrLevelId,
352
+ assets = arrayCache.setUp();
353
+
354
+ if (level) {
355
+ if (level.tilesets) {
356
+ level.tilesets = importTilesetData(level.tilesets);
357
+ }
358
+
359
+ if (level.assets) { // Property added by a previous parse (so that this algorithm isn't run on the same level multiple times)
360
+ union(assets, level.assets);
361
+ } else if (level.layers) {
362
+ for (let i = 0; i < level.layers.length; i++) {
363
+ const
364
+ layer = level.layers[i];
365
+
366
+ if (layer.type === 'objectgroup') {
367
+ addObjectGroupAssets(assets, layer, level.tilesets);
368
+ } else if (layer.type === 'imagelayer') {
369
+ // Check for custom layer entity
370
+ const
371
+ entityType = getProperty(layer.properties, 'entity');
372
+
373
+ if (entityType) {
374
+ const
375
+ data = Data.setUp('type', entityType, 'properties', mergeAndFormatProperties(layer.properties, {})),
376
+ arr = Entity.getAssetList(data);
377
+
378
+ union(assets, arr);
379
+ arrayCache.recycle(arr);
380
+ data.recycle();
381
+ } else {
382
+ union(assets, [layer.image]);
383
+ }
384
+ } else {
385
+ const
386
+ entityType = getProperty(level.layers[i].properties, 'entity'),
387
+ tiles = arrayCache.setUp();
388
+
389
+ // must decode first so we can check for tiles' objects
390
+ decodeLayer(layer);
391
+
392
+ // Check for relevant objectgroups in tileset
393
+ union(tiles, layer.data); // merge used tiles into one-off list
394
+ for (let j = 0; j < tiles.length; j++) {
395
+ const
396
+ id = maskId & tiles[j];
397
+
398
+ for (let k = 0; k < level.tilesets.length; k++) {
399
+ const
400
+ {tiles, firstgid} = level.tilesets[k];
401
+
402
+ if (tiles) {
403
+ for (let l = 0; l < tiles.length; l++) {
404
+ const
405
+ tile = tiles[l];
406
+
407
+ if (((tile.id + firstgid) === id) && tile.objectgroup) {
408
+ addObjectGroupAssets(assets, tile.objectgroup);
409
+ }
410
+ }
411
+ }
412
+ }
413
+ }
414
+
415
+ // Check for custom layer entity
416
+ if (entityType) {
417
+ const
418
+ data = Data.setUp('type', entityType),
419
+ arr = Entity.getAssetList(data);
420
+
421
+ union(assets, arr);
422
+ arrayCache.recycle(arr);
423
+ data.recycle();
424
+ }
425
+ }
426
+ }
427
+ if (!ss) { //We need to load the tileset images since there is not a separate spriteSheet describing them
428
+ const
429
+ levelTilesets = level.tilesets,
430
+ tilesets = arrayCache.setUp();
431
+
432
+ for (let i = 0; i < levelTilesets.length; i++) {
433
+ const
434
+ tileset = levelTilesets[i];
435
+
436
+ if (tileset.image) {
437
+ tilesets.push(tileset.image);
438
+ } else {
439
+ const
440
+ tiles = tileset.tiles;
441
+
442
+ for (let j = 0; j < tiles.length; j++) {
443
+ const
444
+ tile = tiles[j];
445
+
446
+ if (tile.image) {
447
+ tilesets.push(tile.image);
448
+ }
449
+ }
450
+ }
451
+ }
452
+ union(assets, tilesets);
453
+ arrayCache.recycle(tilesets);
454
+ }
455
+ level.assets = greenSlice(assets); // Save for later in case this level is checked again.
456
+ }
457
+ }
458
+
459
+ return assets;
460
+ },
461
+ // These are provided but can be overwritten by entities of the same name in the configuration.
462
+ standardEntityLayers = {
463
+ "render-layer": {
464
+ "id": "render-layer",
465
+ "components": [{
466
+ "type": "RenderTiles",
467
+ "spriteSheet": "import",
468
+ "imageMap": "import",
469
+ "entityCache": true
470
+ }]
471
+ },
472
+ "collision-layer": {
473
+ "id": "collision-layer",
474
+ "components": [{
475
+ "type": "CollisionTiles",
476
+ "collisionMap": "import"
477
+ }]
478
+ },
479
+ "image-layer": {
480
+ "id": "image-layer",
481
+ "components": [{
482
+ "type": "RenderTiles",
483
+ "spriteSheet": "import",
484
+ "imageMap": "import"
485
+ }]
486
+ }
487
+ },
488
+ importTileset = function (tileset) {
489
+ const
490
+ source = platypus.game.settings.tilesets[getKey(tileset.source)],
491
+ keys = Object.keys(source),
492
+ {length} = keys;
493
+
494
+ for (let i = 0; i < length; i++) {
495
+ const
496
+ key = keys[i];
497
+
498
+ tileset[key] = source[key];
499
+ }
500
+
501
+ delete tileset.source; // We remove this so we never have to rerun this import. Note that we can't simply replace the tileset properties since the tileset's firstgid property may change from level to level.
502
+
503
+ return tileset;
504
+ },
505
+ importTilesetData = function (tilesets) {
506
+ for (let i = 0; i < tilesets.length; i++) {
507
+ if (tilesets[i].source) {
508
+ tilesets[i] = importTileset(tilesets[i]);
509
+ }
510
+ }
511
+
512
+ return tilesets;
513
+ };
514
+
515
+ export default createComponentClass(/** @lends platypus.components.TiledLoader.prototype */{
516
+ id: 'TiledLoader',
517
+
518
+ properties: {
519
+ /**
520
+ * This causes the entire map to be offset automatically by an order of magnitude higher than the height and width of the world so that the number of digits below zero is constant throughout the world space. This fixes potential floating point issues when, for example, 97 is added to 928.0000000000001 giving 1025 since a significant digit was lost when going into the thousands.
521
+ *
522
+ * @property offsetMap
523
+ * @type Boolean
524
+ * @default false
525
+ */
526
+ offsetMap: false,
527
+
528
+ /**
529
+ * If set to `true` and if the game is running in debug mode, this causes the collision layer to appear.
530
+ *
531
+ * @property showCollisionTiles
532
+ * @type Boolean
533
+ * @default false
534
+ */
535
+ showCollisionTiles: false,
536
+
537
+ /**
538
+ * If specified, the referenced images are used as the game sprite sheets instead of the images referenced in the Tiled map. This is useful for using different or better quality art from the art used in creating the Tiled map.
539
+ *
540
+ * @property images
541
+ * @type Array
542
+ * @default null
543
+ */
544
+ images: null,
545
+
546
+ /**
547
+ * Adds a number to each additional Tiled layer's z coordinate to maintain z-order. Defaults to 1000.
548
+ *
549
+ * @property layerIncrement
550
+ * @type number
551
+ * @default 1000
552
+ */
553
+ layerIncrement: 1000,
554
+
555
+ /**
556
+ * Keeps the tile maps in separate render layers. Default is 'false' to for better optimization.
557
+ *
558
+ * @property separateTiles
559
+ * @type boolean
560
+ * @default false
561
+ */
562
+ separateTiles: false,
563
+
564
+ /**
565
+ * If a particular sprite sheet should be used that's not defined by the level images themselves. This is useful for making uniquely-themed variations of the same level. This is overridden by `"spriteSheet": "import"` in the "render-layer" Entity definition, so be sure to remove that when setting this property.
566
+ *
567
+ * @property spriteSheet
568
+ * @type String | Object
569
+ * @default null
570
+ */
571
+ spriteSheet: null,
572
+
573
+ /**
574
+ * Whether to continue loading `lazyLoad` entities in the background after level starts regardless of camera position. If `false`, entities with a `lazyLoad` property will only load once within camera range.
575
+ *
576
+ * @property backgroundLoad
577
+ * @type Boolean
578
+ * @default true
579
+ */
580
+ backgroundLoad: true
581
+ },
582
+
583
+ publicProperties: {
584
+ /**
585
+ * Specifies the JSON level to load. Available on the entity as `entity.level`.
586
+ *
587
+ * @property level
588
+ * @type String
589
+ * @default null
590
+ */
591
+ level: null,
592
+
593
+ /**
594
+ * Specifies a function that entity definitions should be passed through before becoming entities to allow for specific changes apart from the level definition (like stored data or meta game mechanics)
595
+ *
596
+ * @property entityModifier
597
+ * @type Function
598
+ * @default defaultEntityModifier (identity function)
599
+ */
600
+ entityModifier: defaultEntityModifier,
601
+
602
+ /**
603
+ * Can be "left", "right", or "center". Defines where entities registered X position should be when spawned. Available on the entity as `entity.entityPositionX`.
604
+ *
605
+ * @property entityPositionX
606
+ * @type String
607
+ * @default "center"
608
+ */
609
+ entityPositionX: "center",
610
+
611
+ /**
612
+ * Can be "top", "bottom", or "center". Defines where entities registered Y position should be when spawned. Available on the entity as `entity.entityPositionY`.
613
+ *
614
+ * @property entityPositionY
615
+ * @type String
616
+ * @default "bottom"
617
+ */
618
+ entityPositionY: "bottom",
619
+
620
+ /**
621
+ * Whether to wait for a "load-level" event before before loading. Available on the entity as `entity.manuallyLoad`.
622
+ *
623
+ * @property manuallyLoad
624
+ * @type boolean
625
+ * @default false
626
+ */
627
+ manuallyLoad: false,
628
+
629
+ tileWidth: null,
630
+
631
+ tileHeight: null
632
+ },
633
+
634
+ /**
635
+ * This component is attached to a top-level entity and, once its peer components are loaded, ingests a JSON file exported from the [Tiled map editor](http://www.mapeditor.org/) and creates the tile maps and entities. Once it has finished loading the map, it removes itself from the list of components on the entity.
636
+ *
637
+ * This component requires an [EntityContainer](platypus.components.EntityContainer.html) since it calls `entity.addEntity()` on the entity, provided by `EntityContainer`.
638
+ *
639
+ * This component looks for the following entities, and if not found will load default versions:
640
+
641
+ {
642
+ "render-layer": {
643
+ "id": "render-layer",
644
+ "components":[{
645
+ "type": "RenderTiles",
646
+ "spriteSheet": "import",
647
+ "imageMap": "import",
648
+ "entityCache": true
649
+ }]
650
+ },
651
+ "collision-layer": {
652
+ "id": "collision-layer",
653
+ "components":[{
654
+ "type": "CollisionTiles",
655
+ "collisionMap": "import"
656
+ }]
657
+ },
658
+ "image-layer": {
659
+ "id": "image-layer",
660
+ "components":[{
661
+ "type": "RenderTiles",
662
+ "spriteSheet": "import",
663
+ "imageMap": "import"
664
+ }]
665
+ }
666
+ }
667
+
668
+ * @memberof platypus.components
669
+ * @uses platypus.Component
670
+ * @constructs
671
+ * @listens platypus.Entity#camera-update
672
+ * @listens platypus.Entity#layer-loaded
673
+ * @listens platypus.Entity#load-level
674
+ * @listens platypus.Entity#tick
675
+ * @fires platypus.Entity#world-loaded
676
+ * @fires platypus.Entity#level-loading-progress
677
+ */
678
+ initialize: function () {
679
+ this.assetCache = platypus.assetCache;
680
+ this.layerZ = 0;
681
+ this.followEntity = false;
682
+ this.lazyLoads = arrayCache.setUp();
683
+ },
684
+
685
+ events: {
686
+ "layer-loaded": function (persistentData, holds) {
687
+ if (!this.manuallyLoad) {
688
+ holds.count += 1;
689
+ this.loadLevel({
690
+ level: this.level || persistentData.level,
691
+ persistentData: persistentData
692
+ }, holds.release);
693
+ }
694
+ },
695
+
696
+ "load-level": function (levelData, callback) {
697
+ this.loadLevel(levelData, callback);
698
+ }
699
+ },
700
+
701
+ methods: {
702
+ createLayer: function (entityKind, rawLayer, offsetX, offsetY, tileWidth, tileHeight, tilesets, tileSetTileData, images, combineRenderLayer, progress, entityLinker, parallaxOriginX, parallaxOriginY) {
703
+ const
704
+ //This builds in parallaxing support by allowing the addition of width and height properties into Tiled layers so they pan at a separate rate than other layers.
705
+ checkParallax = ({data, height, properties, width}) => {
706
+ const
707
+ layerHeight = getProperty(properties, 'height'),
708
+ layerWidth = getProperty(properties, 'width'),
709
+ newWidth = layerWidth ? parseInt(layerWidth, 10) : width,
710
+ newHeight = layerHeight ? parseInt(layerHeight, 10) : height,
711
+ newData = (newWidth !== width || newHeight !== height) ? [] : data;
712
+
713
+ if (newData.length === 0) {
714
+ for (let x = 0; x < newWidth; x++) {
715
+ for (let y = 0; y < newHeight; y++) {
716
+ if ((x < width) && (y < height)) {
717
+ newData[x + y * newWidth] = data[x + y * width];
718
+ } else {
719
+ newData[x + y * newWidth] = 0;
720
+ }
721
+ }
722
+ }
723
+ }
724
+
725
+ return {
726
+ width: newWidth,
727
+ height: newHeight,
728
+ data: newData
729
+ };
730
+ },
731
+
732
+ layer = decodeLayer(rawLayer),
733
+ parallaxX = layer.parallaxx ?? 1,
734
+ parallaxY = layer.parallaxy ?? 1,
735
+ {
736
+ offsetx: layerOffsetX = 0,
737
+ offsety: layerOffsetY = 0,
738
+ properties: layerProperties,
739
+ tileheight: tHeight = tileHeight,
740
+ tilewidth: tWidth = tileWidth
741
+ } = layer,
742
+ {
743
+ data,
744
+ height,
745
+ width
746
+ } = layerProperties ? checkParallax(layer) : layer,
747
+ mapOffsetX = offsetX + layerOffsetX,
748
+ mapOffsetY = offsetY + layerOffsetY,
749
+ layerDefinition = JSON.parse(JSON.stringify(platypus.game.settings.entities[entityKind] ?? standardEntityLayers[entityKind])), //TODO: a bit of a hack to copy an object instead of overwrite values
750
+ layerDefinitionProperties = layerDefinition.properties = layerDefinition.properties ?? {},
751
+ {components} = layerDefinition,
752
+ importAnimation = {},
753
+ importCollision = [],
754
+ importFrames = [],
755
+ importRender = [],
756
+ importSpriteSheet = {
757
+ images: (layer.image ? [layer.image] : images),
758
+ frames: importFrames,
759
+ animations: importAnimation
760
+ },
761
+ createFrames = function (frames, index, tileset) {
762
+ const
763
+ {image, tiles} = tileset;
764
+
765
+ if (image) {
766
+ const
767
+ {
768
+ columns = (((tileset.imagewidth / (tileset.tilewidth + tileset.spacing)) + (tileset.margin * 2 - tileset.spacing)) >> 0),
769
+ imageheight,
770
+ margin = 0,
771
+ spacing = 0,
772
+ tileheight,
773
+ tilewidth
774
+ } = tileset,
775
+ tileWidthHalf = tilewidth / 2,
776
+ tileHeightHalf = tileheight / 2,
777
+ tileWidthSpace = tilewidth + spacing,
778
+ tileHeightSpace = tileheight + spacing,
779
+ margin2 = margin * 2,
780
+ marginSpace = margin2 - spacing,
781
+ rows = /* Tiled tileset def doesn't seem to have rows */ (((imageheight / tileHeightSpace) + marginSpace) >> 0);
782
+
783
+ for (let y = 0; y < rows; y++) {
784
+ for (let x = 0; x < columns; x++) {
785
+ frames.push([
786
+ margin + x * tileWidthSpace,
787
+ margin + y * tileHeightSpace,
788
+ tilewidth,
789
+ tileheight,
790
+ index,
791
+ tileWidthHalf,
792
+ tileHeightHalf
793
+ ]);
794
+ }
795
+ }
796
+ index += 1;
797
+ } else if (tiles) {
798
+ for (let i = 0; i < tiles.length; i++) {
799
+ const
800
+ {imageheight, imagewidth} = tiles[i];
801
+
802
+ frames.push([
803
+ 0,
804
+ 0,
805
+ imageheight,
806
+ imagewidth,
807
+ index,
808
+ imageheight / 2,
809
+ imagewidth / 2
810
+ ]);
811
+ index += 1;
812
+ }
813
+ }
814
+
815
+ return index;
816
+ };
817
+ let index = 0,
818
+ lastSet = null,
819
+ renderTiles = false,
820
+ collisionTiles = false;
821
+
822
+ entityLinker.linkObject(layerDefinitionProperties.tiledId = layer.id);
823
+
824
+ if (layerProperties) {
825
+ mergeAndFormatProperties(layerProperties, layerDefinitionProperties, entityLinker);
826
+ }
827
+
828
+ layerDefinitionProperties.width = tWidth * width;
829
+ layerDefinitionProperties.height = tHeight * height;
830
+ layerDefinitionProperties.columns = width;
831
+ layerDefinitionProperties.rows = height;
832
+ layerDefinitionProperties.tileWidth = tWidth;
833
+ layerDefinitionProperties.tileHeight = tHeight;
834
+ layerDefinitionProperties.scaleX = 1;
835
+ layerDefinitionProperties.scaleY = 1;
836
+ layerDefinitionProperties.layerZ = this.layerZ;
837
+ layerDefinitionProperties.left = layerDefinitionProperties.x || mapOffsetX;
838
+ layerDefinitionProperties.top = layerDefinitionProperties.y || mapOffsetY;
839
+ layerDefinitionProperties.z = layerDefinitionProperties.z || this.layerZ;
840
+ layerDefinitionProperties.parallaxX = parallaxX;
841
+ layerDefinitionProperties.parallaxY = parallaxY;
842
+ layerDefinitionProperties.parallaxOriginX = parallaxOriginX;
843
+ layerDefinitionProperties.parallaxOriginY = parallaxOriginY;
844
+
845
+ if (tilesets.length) {
846
+ let imageIndex = 0;
847
+
848
+ for (let x = 0; x < tilesets.length; x++) {
849
+ imageIndex = createFrames(importFrames, imageIndex, tilesets[x]);
850
+ }
851
+
852
+ lastSet = tilesets[tilesets.length - 1];
853
+ {
854
+ const
855
+ tileTypes = lastSet.firstgid + lastSet.tilecount;
856
+
857
+ for (let x = -1; x < tileTypes; x++) {
858
+ importAnimation['tile' + x] = x;
859
+ }
860
+ }
861
+ }
862
+ for (let x = 0; x < width; x++) {
863
+ importCollision[x] = [];
864
+ importRender[x] = [];
865
+ for (let y = 0; y < height; y++) {
866
+ index = +data[x + y * width] - 1; // -1 from original src to make it zero-based.
867
+ importRender[x][y] = 'tile' + index;
868
+ index += 1; // So collision map matches original src indexes. Render (above) should probably be changed at some point as well. DDD 3/30/2016
869
+ importCollision[x][y] = index;
870
+
871
+ if (tileSetTileData) {
872
+ const
873
+ transform = entityTransformCheck(index);
874
+
875
+ if (tileSetTileData.has(transform.id)) {
876
+ const
877
+ {objectgroup, properties} = tileSetTileData.get(transform.id);
878
+
879
+ if (objectgroup && !layerDefinitionProperties.ignoreTileCollision) {
880
+ const
881
+ offsetX = mapOffsetX + tileWidth * x,
882
+ offsetY = mapOffsetY + tileHeight * y;
883
+
884
+ this.setUpEntities(objectgroup.objects.map((object) => getObjectCentered(object)).map((object) => getObjectTransformed(object, transform, tileWidth, tileHeight)), objectgroup, offsetX, offsetY, tilesets, progress, entityLinker);
885
+ }
886
+
887
+ if (properties && getProperty(properties, 'hide')) {
888
+ importRender[x][y] = 'tile-1'; // hide this tile's art.
889
+ }
890
+ }
891
+ }
892
+ }
893
+ }
894
+ for (let x = 0; x < components.length; x++) {
895
+ if (components[x].type === 'RenderTiles') {
896
+ renderTiles = components[x];
897
+ }
898
+ if (components[x].type === 'CollisionTiles') {
899
+ collisionTiles = components[x];
900
+ }
901
+ if (components[x].spriteSheet === 'import') {
902
+ components[x].spriteSheet = importSpriteSheet;
903
+ } else if (components[x].spriteSheet) {
904
+ if (typeof components[x].spriteSheet === 'string' && platypus.game.settings.spriteSheets[components[x].spriteSheet]) {
905
+ components[x].spriteSheet = platypus.game.settings.spriteSheets[components[x].spriteSheet];
906
+ }
907
+ if (!components[x].spriteSheet.animations) {
908
+ components[x].spriteSheet.animations = importAnimation;
909
+ }
910
+ }
911
+ if (components[x].collisionMap === 'import') {
912
+ components[x].collisionMap = [importCollision];
913
+ }
914
+ if (components[x].imageMap === 'import') {
915
+ components[x].imageMap = importRender;
916
+ }
917
+ }
918
+
919
+ if ((!this.separateTiles) && (combineRenderLayer?.parallaxX ?? 1) === parallaxX && (combineRenderLayer?.parallaxY ?? 1) === parallaxY && (combineRenderLayer?.tileHeight === tHeight) && (combineRenderLayer?.tileWidth === tWidth) && (combineRenderLayer?.columns === width) && (combineRenderLayer?.rows === height)) {
920
+ combineRenderLayer.triggerEvent('add-tiles', renderTiles);
921
+ combineRenderLayer.triggerEvent('add-collision-tiles', collisionTiles);
922
+
923
+ this.updateLoadingProgress(progress);
924
+ return combineRenderLayer;
925
+ } else {
926
+ const
927
+ properties = {};
928
+
929
+ if (this.spriteSheet) {
930
+ if (typeof this.spriteSheet === 'string') {
931
+ properties.spriteSheet = platypus.game.settings.spriteSheets[this.spriteSheet];
932
+ } else {
933
+ properties.spriteSheet = this.spriteSheet;
934
+ }
935
+ if (!properties.spriteSheet.animations) {
936
+ properties.spriteSheet.animations = importAnimation;
937
+ }
938
+ }
939
+ return entityLinker.linkEntity(this.owner.addEntity(new Entity(layerDefinition, {
940
+ properties
941
+ }, this.updateLoadingProgress.bind(this, progress), this.owner)));
942
+ }
943
+ },
944
+
945
+ convertImageLayer: function (imageLayer) {
946
+ const
947
+ {image, name, properties} = imageLayer,
948
+ namedAsset = this.assetCache.get(name),
949
+ imageId = namedAsset ? name : getKey(image),
950
+ asset = namedAsset ?? this.assetCache.get(imageId) ?? null,
951
+ repeat = getProperty(properties, 'repeat'),
952
+ repeatX = getProperty(properties, 'repeat-x'),
953
+ repeatY = getProperty(properties, 'repeat-y'),
954
+ height = +(repeatY ?? repeat ?? 1),
955
+ width = +(repeatX ?? repeat ?? 1),
956
+ dataCells = width * height,
957
+ tileheight = asset?.height ?? 1,
958
+ tilewidth = asset?.width ?? 1,
959
+ tileLayer = {
960
+ ...imageLayer,
961
+ data: Array(dataCells).fill(1),
962
+ image: asset ? imageId : image,
963
+ height,
964
+ type: 'tilelayer',
965
+ width,
966
+ tileheight,
967
+ tilewidth,
968
+ properties
969
+ };
970
+
971
+ if (!asset) { // Prefer to have name in tiled match image id in game
972
+ platypus.debug.warn(`Component TiledLoader: Cannot find the "${name}" sprite sheet. Add it to the list of assets in config.json and give it the id "${name}".`);
973
+ }
974
+
975
+ tileLayer.tileset = {
976
+ columns: 1,
977
+ image: tileLayer.image,
978
+ imageheight: tileheight,
979
+ imagewidth: tilewidth,
980
+ margin: 0,
981
+ name,
982
+ spacing: 0,
983
+ tilecount: 1,
984
+ tileheight,
985
+ tilewidth,
986
+ type: "tileset"
987
+ };
988
+
989
+ return tileLayer;
990
+ },
991
+
992
+ loadLevel: function (levelData, callback) {
993
+ const
994
+ {assetCache, offsetMap, owner} = this,
995
+ entityLinker = EntityLinker.setUp(),
996
+ images = this.images ? greenSlice(this.images) : arrayCache.setUp(),
997
+ level = (typeof levelData.level === 'string') ? platypus.game.settings.levels[levelData.level] : levelData.level, //format level appropriately
998
+ layers = level.layers,
999
+ progress = Data.setUp('count', 0, 'progress', 0, 'total', 0),
1000
+ tileSetTileData = DataMap.setUp(),
1001
+ tilesets = importTilesetData(level.tilesets),
1002
+ tileWidth = this.tileWidth = level.tilewidth,
1003
+ tileHeight = this.tileHeight = level.tileheight,
1004
+ height = level.height * tileHeight,
1005
+ width = level.width * tileWidth,
1006
+ x = offsetMap ? getPowerOfTen(width) : 0,
1007
+ y = offsetMap ? getPowerOfTen(height) : 0,
1008
+ parallaxOriginX = (level.parallaxoriginx ?? 0) + x,
1009
+ parallaxOriginY = (level.parallaxoriginy ?? 0) + y;
1010
+ let layer = null;
1011
+
1012
+ createTilesetObjectGroupReference(tileSetTileData, tilesets);
1013
+
1014
+ if (level.properties) {
1015
+ entityLinker.linkObject(owner.tiledId = 0); // Level
1016
+ mergeAndFormatProperties(level.properties, owner, entityLinker);
1017
+ entityLinker.linkEntity(owner);
1018
+ }
1019
+
1020
+ if (images.length === 0) {
1021
+ const
1022
+ addImage = ({image}) => {
1023
+ const
1024
+ imageKey = getKey(image),
1025
+ asset = assetCache.get(imageKey);
1026
+
1027
+ if (asset) { // Prefer to have name in tiled match image id in game
1028
+ images.push(imageKey);
1029
+ } else {
1030
+ platypus.debug.warn('Component TiledLoader: Cannot find the "' + imageKey + '" sprite sheet. Add it to the list of assets in config.json and give it the id "' + imageKey + '".');
1031
+ images.push(image);
1032
+ }
1033
+ };
1034
+
1035
+ for (let i = 0; i < tilesets.length; i++) {
1036
+ const
1037
+ tileset = tilesets[i];
1038
+
1039
+ if (tileset.image) {
1040
+ addImage(tileset);
1041
+ } else {
1042
+ tileset.tiles.forEach(addImage);
1043
+ }
1044
+ }
1045
+ }
1046
+
1047
+ progress.total = layers.length;
1048
+
1049
+ this.finishedLoading = () => {
1050
+ const
1051
+ {lazyLoads} = this,
1052
+ message = Data.setUp(
1053
+ "level", null,
1054
+ "world", AABB.setUp(),
1055
+ "tile", AABB.setUp(),
1056
+ "camera", null,
1057
+ lazyLoads
1058
+ ),
1059
+ lazyLoad = (entity) => {
1060
+ const
1061
+ aabb = entity.aabb,
1062
+ entityLinker = entity.entityLinker;
1063
+
1064
+ aabb.recycle();
1065
+ entity.aabb = null;
1066
+ entity.entityLinker = null;
1067
+ entityLinker.linkEntity(owner.addEntity(entity));
1068
+ };
1069
+
1070
+ /**
1071
+ * Once finished loading the map, this message is triggered on the entity to notify other components of completion.
1072
+ *
1073
+ * @event platypus.Entity#world-loaded
1074
+ * @param message {platypus.Data} World data.
1075
+ * @param message.level {Object} The Tiled level data used to load the level.
1076
+ * @param message.width {number} The width of the world in world units.
1077
+ * @param message.height {number} The height of the world in world units.
1078
+ * @param message.tile {platypus.AABB} Dimensions of the world tiles.
1079
+ * @param message.world {platypus.AABB} Dimensions of the world.
1080
+ * @param message.camera {platypus.Entity} If a camera property is found on one of the loaded entities, this property will point to the entity on load that a world camera should focus on.
1081
+ * @param message.lazyLoads {Array} List of objects representing entity definitions that will await camera focus before generating actual entities.
1082
+ */
1083
+ message.level = level;
1084
+ message.camera = this.followEntity; // TODO: in 0.9.0 this should probably be removed, using something like "child-entity-added" instead. Currently this is particular to TiledLoader and Camera and should be generalized. - DDD 3/15/2016
1085
+ message.width = width;
1086
+ message.height = height;
1087
+ message.world.setBounds(x, y, x + width, y + height);
1088
+ message.tile.setBounds(0, 0, tileWidth, tileHeight);
1089
+ owner.triggerEvent('world-loaded', message);
1090
+ message.world.recycle();
1091
+ message.tile.recycle();
1092
+ message.recycle();
1093
+
1094
+ if (lazyLoads.length) {
1095
+ this.addEventListener("camera-update", (camera) => {
1096
+ const
1097
+ viewport = AABB.setUp(camera.viewport);
1098
+ let i = lazyLoads.length;
1099
+
1100
+ viewport.resize(viewport.width * 1.5, viewport.height * 1.5);
1101
+
1102
+ while (i--) {
1103
+ const entity = lazyLoads[i],
1104
+ aabb = entity.aabb;
1105
+
1106
+ if (viewport.intersects(aabb)) {
1107
+ lazyLoad(entity);
1108
+ for (let j = i + 1; j < lazyLoads.length; j++) {
1109
+ lazyLoads[j - 1] = lazyLoads[j];
1110
+ }
1111
+ lazyLoads.length -= 1;
1112
+ }
1113
+ }
1114
+ });
1115
+ if (this.backgroundLoad) {
1116
+ this.addEventListener('tick', function () {
1117
+ const
1118
+ i = lazyLoads.length;
1119
+
1120
+ if (i) {
1121
+ lazyLoad(lazyLoads.pop());
1122
+ }
1123
+ });
1124
+ }
1125
+ } else {
1126
+ owner.removeComponent(this);
1127
+ }
1128
+
1129
+ if (callback) {
1130
+ callback();
1131
+ }
1132
+ };
1133
+
1134
+ for (let i = 0; i < layers.length; i++) {
1135
+ const
1136
+ layerDefinition = layers[i];
1137
+
1138
+ switch (layerDefinition.type) {
1139
+ case 'imagelayer':
1140
+ layer = this.convertImageLayer(layerDefinition);
1141
+ layer = this.createLayer(getProperty(layer.properties, 'entity') || 'image-layer', layer, x, y, layer.tilewidth, layer.tileheight, [layer.tileset], null, images, layer, progress, entityLinker, parallaxOriginX, parallaxOriginY);
1142
+ break;
1143
+ case 'objectgroup':
1144
+ this.setUpEntities(layerDefinition.objects.map((object) => getObjectCentered(object)), layerDefinition, x, y, tilesets, progress, entityLinker);
1145
+ layer = null;
1146
+ this.updateLoadingProgress(progress);
1147
+ break;
1148
+ case 'tilelayer':
1149
+ layer = this.setupLayer(layerDefinition, layer, x, y, tileWidth, tileHeight, tilesets, tileSetTileData, images, progress, entityLinker, parallaxOriginX, parallaxOriginY);
1150
+ break;
1151
+ default:
1152
+ platypus.debug.warn('Component TiledLoader: Platypus does not support Tiled layers of type "' + layerDefinition.type + '". This layer will not be loaded.');
1153
+ this.updateLoadingProgress(progress);
1154
+ }
1155
+ this.layerZ += this.layerIncrement;
1156
+ }
1157
+
1158
+ tileSetTileData.recycle();
1159
+ },
1160
+
1161
+ setUpEntities: (function () {
1162
+ const
1163
+ tBoth = function (point) {
1164
+ return Data.setUp('x', -point.x, 'y', -point.y);
1165
+ },
1166
+ tNone = function (point) {
1167
+ return Data.setUp('x', point.x, 'y', point.y);
1168
+ },
1169
+ tX = function (point) {
1170
+ return Data.setUp('x', -point.x, 'y', point.y);
1171
+ },
1172
+ tY = function (point) {
1173
+ return Data.setUp('x', point.x, 'y', -point.y);
1174
+ },
1175
+ transformPoints = function (points, transformX, transformY) {
1176
+ const
1177
+ arr = arrayCache.setUp(),
1178
+ reverseCycle = transformX ^ transformY,
1179
+ transform = transformX ? transformY ? tBoth : tX : transformY ? tY : tNone;
1180
+
1181
+ if (reverseCycle) {
1182
+ let i = points.length;
1183
+ while (i--) {
1184
+ arr.push(transform(points[i]));
1185
+ }
1186
+ arr.unshift(arr.pop()); // so the same point is at the beginning.
1187
+ } else {
1188
+ for (let i = 0; i < points.length; i++) {
1189
+ arr.push(transform(points[i]));
1190
+ }
1191
+ }
1192
+
1193
+ return arr;
1194
+ },
1195
+ getPolyShape = function (type, points, transformX, transformY, decomposed) {
1196
+ const
1197
+ shape = {
1198
+ type: type,
1199
+ points: transformPoints(points, transformX, transformY)
1200
+ };
1201
+
1202
+ if (decomposed) {
1203
+ const decomposedPoints = [];
1204
+ let p = 0;
1205
+
1206
+ for (p = 0; p < decomposed.length; p++) {
1207
+ decomposedPoints.push(transformPoints(decomposed[p], transformX, transformY));
1208
+ }
1209
+
1210
+ shape.decomposedPolygon = decomposedPoints;
1211
+ }
1212
+
1213
+ return shape;
1214
+ };
1215
+
1216
+ return function (objects, layer, offsetX, offsetY, tilesets, progress, entityLinker) {
1217
+ const
1218
+ {offsetx: layerOffsetX = 0, offsety: layerOffsetY = 0, properties: layerProperties} = layer,
1219
+ mapOffsetX = offsetX + layerOffsetX,
1220
+ mapOffsetY = offsetY + layerOffsetY,
1221
+ len = objects.length;
1222
+
1223
+ progress.total += len;
1224
+
1225
+ objects.forEach((object) => {
1226
+ const
1227
+ entityData = getEntityData(object, tilesets, entityLinker);
1228
+
1229
+ if (entityData) {
1230
+ const
1231
+ entityPositionX = getProperty(layerProperties, 'entityPositionX') ?? this.entityPositionX,
1232
+ entityPositionY = getProperty(layerProperties, 'entityPositionY') ?? this.entityPositionY,
1233
+ {flipX = 1, flipY = 1, height, point, polygon, polyline, rotation, width, x, y} = object,
1234
+ angle = (rotation) / 180 * Math.PI,
1235
+ tiledLoaderOffset = Vector.setUp(width / 2 * POSITIONS[entityPositionX] * flipX, height / 2 * POSITIONS[entityPositionY] * flipY).rotate(angle),
1236
+ entityLocation = Vector.setUp(mapOffsetX + x, mapOffsetY + y).addVector(tiledLoaderOffset),
1237
+ {type: entityType, properties} = entityData,
1238
+ entityPackage = {
1239
+ properties
1240
+ },
1241
+ entityDefinition = platypus.game.settings.entities[entityType],
1242
+ entityDefProps = entityDefinition?.properties ?? null,
1243
+ hh = (point ? (entityDefProps?.height ?? 0) * (1 + POSITIONS[entityPositionY]) : height) / 2,
1244
+ hw = (point ? (entityDefProps?.width ?? 0) * (1 + POSITIONS[entityPositionX]) : width) / 2,
1245
+ registrationPoint = Vector.setUp(tiledLoaderOffset).add(hw, hh),
1246
+ addShape = (shape, properties) => {
1247
+ if (!properties.shapes) {
1248
+ properties.shapes = [];
1249
+ }
1250
+ // Platypus collision uses a shapes array.
1251
+ shape.regX = registrationPoint.x;
1252
+ shape.regY = registrationPoint.y;
1253
+ properties.shapes.push(shape);
1254
+ },
1255
+ addCollisionShapes = ({ellipse, height, width}, properties) => {
1256
+ if (ellipse) {
1257
+ if (width === height) {
1258
+ addShape({
1259
+ type: 'circle',
1260
+ radius: width / 2
1261
+ }, properties);
1262
+ } else {
1263
+ addShape({
1264
+ type: 'ellipse',
1265
+ width,
1266
+ height
1267
+ }, properties);
1268
+ }
1269
+ } else if (width && height) {
1270
+ addShape({
1271
+ type: 'rectangle',
1272
+ width,
1273
+ height,
1274
+ }, properties);
1275
+ }
1276
+ };
1277
+
1278
+ entityPackage[entityDefinition ? 'type' : 'id'] = entityType;
1279
+
1280
+ if (polygon || polyline) {
1281
+ //Figuring out the width of the polygon and shifting the origin so it's in the top-left.
1282
+ const
1283
+ polyPoints = polygon ?? polyline;
1284
+ let smallestX = Infinity,
1285
+ largestX = -Infinity,
1286
+ smallestY = Infinity,
1287
+ largestY = -Infinity;
1288
+
1289
+ for (let x = 0; x < polyPoints.length; x++) {
1290
+ if (polyPoints[x].x > largestX) {
1291
+ largestX = polyPoints[x].x;
1292
+ }
1293
+ if (polyPoints[x].x < smallestX) {
1294
+ smallestX = polyPoints[x].x;
1295
+ }
1296
+ if (polyPoints[x].y > largestY) {
1297
+ largestY = polyPoints[x].y;
1298
+ }
1299
+ if (polyPoints[x].y < smallestY) {
1300
+ smallestY = polyPoints[x].y;
1301
+ }
1302
+ }
1303
+ properties.width = largestX - smallestX;
1304
+ properties.height = largestY - smallestY;
1305
+ properties.x = entityLocation.x;
1306
+ properties.y = entityLocation.y;
1307
+
1308
+ if (polygon) {
1309
+ addShape(getPolyShape('polygon', polyPoints, flipX === -1, flipY === -1, properties.decomposedPolygon), properties);
1310
+ } else if (polyline) {
1311
+ addShape(getPolyShape('polyline', polyPoints, flipX === -1, flipY === -1, null), properties);
1312
+ }
1313
+
1314
+ if (rotation) {
1315
+ properties.rotation = rotation;
1316
+ }
1317
+ } else {
1318
+ if (!point) {
1319
+ properties.width = width;
1320
+ properties.height = height;
1321
+ }
1322
+ properties.x = entityLocation.x;
1323
+ properties.y = entityLocation.y;
1324
+ properties.rotation = object.rotation;
1325
+ properties.regX = registrationPoint.x;
1326
+ properties.regY = registrationPoint.y;
1327
+
1328
+ addCollisionShapes(object, properties);
1329
+ }
1330
+
1331
+ if (entityDefProps) {
1332
+ properties.scaleX *= (entityDefProps.scaleX || 1);
1333
+ properties.scaleY *= (entityDefProps.scaleY || 1);
1334
+ }
1335
+ properties.scaleX *= flipX;
1336
+ properties.scaleY *= flipY;
1337
+ properties.layerZ = this.layerZ;
1338
+
1339
+ //Setting the z value. All values are getting added to the layerZ value.
1340
+ if (properties.z) {
1341
+ properties.z += this.layerZ;
1342
+ } else if (entityDefProps && (typeof entityDefProps.z === 'number')) {
1343
+ properties.z = this.layerZ + entityDefProps.z;
1344
+ } else {
1345
+ properties.z = this.layerZ;
1346
+ }
1347
+
1348
+ {
1349
+ const
1350
+ proofedPackage = this.entityModifier(entityPackage);
1351
+
1352
+ if (proofedPackage) {
1353
+ if (properties.lazyLoad || (entityDefProps && entityDefProps.lazyLoad)) {
1354
+ const
1355
+ {height: h = 1, regX = 0, regY = 0, width: w = 1, x = 0, y = 0} = properties;
1356
+
1357
+ proofedPackage.aabb = AABB.setUp(x + w / 2 - regX, y + h / 2 - regY, w, h);
1358
+ proofedPackage.entityLinker = entityLinker;
1359
+ this.lazyLoads.push(proofedPackage);
1360
+ this.updateLoadingProgress(progress);
1361
+ } else {
1362
+ const
1363
+ createdEntity = this.owner.addEntity(proofedPackage, this.updateLoadingProgress.bind(this, progress));
1364
+
1365
+ entityLinker.linkEntity(createdEntity);
1366
+
1367
+ if (createdEntity && createdEntity.camera) {
1368
+ this.followEntity = {
1369
+ entity: createdEntity,
1370
+ mode: createdEntity.camera
1371
+ }; //used by camera
1372
+ }
1373
+ }
1374
+ } else {
1375
+ this.updateLoadingProgress(progress);
1376
+ }
1377
+ }
1378
+ } else {
1379
+ this.updateLoadingProgress(progress);
1380
+ }
1381
+ });
1382
+ };
1383
+ }()),
1384
+
1385
+ setupLayer: function (layer, combineRenderLayer, mapOffsetX, mapOffsetY, tileWidth, tileHeight, tilesets, tileSetTileData, images, progress, entityLinker, parallaxOriginX, parallaxOriginY) {
1386
+ const
1387
+ entity = getProperty(layer.properties, 'entity') ?? 'render-layer', // default
1388
+ entityDefinition = platypus.game.settings.entities[entity] ?? standardEntityLayers[entity];
1389
+ let canCombine = false;
1390
+
1391
+ // Need to check whether the entity can be combined for optimization. This combining of tile layers might be a nice addition to the compilation tools so it's not happening here.
1392
+ if (entityDefinition) {
1393
+ let i = entityDefinition.components.length;
1394
+
1395
+ while (i--) {
1396
+ if (entityDefinition.components[i].type === "RenderTiles") {
1397
+ canCombine = true;
1398
+ break;
1399
+ }
1400
+ }
1401
+ }
1402
+
1403
+ if (canCombine) {
1404
+ return this.createLayer(entity, layer, mapOffsetX, mapOffsetY, tileWidth, tileHeight, tilesets, tileSetTileData, images, combineRenderLayer, progress, entityLinker, parallaxOriginX, parallaxOriginY);
1405
+ } else {
1406
+ this.createLayer(entity, layer, mapOffsetX, mapOffsetY, tileWidth, tileHeight, tilesets, tileSetTileData, images, combineRenderLayer, progress, entityLinker, parallaxOriginX, parallaxOriginY);
1407
+ return null;
1408
+ }
1409
+ },
1410
+
1411
+ updateLoadingProgress: function (progress) {
1412
+ progress.count += 1;
1413
+ progress.progress = progress.count / progress.total;
1414
+
1415
+ /**
1416
+ * As a level is loaded, this event is triggered to show progress.
1417
+ *
1418
+ * @event platypus.Entity#level-loading-progress
1419
+ * @param message {platypus.Data} Contains progress data.
1420
+ * @param message.count {Number} The number of loaded entities.
1421
+ * @param message.progress {Number} A fraction of count / total.
1422
+ * @param message.total {Number} The total number of entities being loaded by this component.
1423
+ */
1424
+ this.owner.triggerEvent('level-loading-progress', progress);
1425
+
1426
+ if (progress.count === progress.total) {
1427
+ progress.recycle();
1428
+ this.finishedLoading();
1429
+ }
1430
+ },
1431
+
1432
+ destroy: function () {
1433
+ arrayCache.recycle(this.lazyLoads);
1434
+ this.lazyLoads = null;
1435
+ }
1436
+ },
1437
+
1438
+ getAssetList: function (definition, props, defaultProps, data) {
1439
+ const
1440
+ ss = definition?.spriteSheet ?? props?.spriteSheet ?? defaultProps?.spriteSheet,
1441
+ images = definition?.images ?? props?.images ?? defaultProps?.images,
1442
+ assets = checkLevel(data?.level ?? definition?.level ?? props?.level ?? defaultProps?.level, ss);
1443
+
1444
+ if (ss) {
1445
+ if (typeof ss === 'string') {
1446
+ union(assets, platypus.game.settings.spriteSheets[ss].images);
1447
+ } else {
1448
+ union(assets, ss.images);
1449
+ }
1450
+ }
1451
+
1452
+ if (images) {
1453
+ union(assets, images);
1454
+ }
1455
+
1456
+ return assets;
1457
+ }
1458
+ });
1459
+