@makefully/platypus 4.1.4 → 4.1.6

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 (134) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +4 -2
  3. package/lib/platypus.js +1 -1
  4. package/package.json +7 -2
  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 +833 -0
  98. package/src/components/RenderSprite.js +527 -0
  99. package/src/components/RenderText.js +147 -0
  100. package/src/components/RenderTiles.js +1391 -0
  101. package/src/components/SceneChanger.js +128 -0
  102. package/src/components/TiledLoader.js +1451 -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
@@ -0,0 +1,1391 @@
1
+ /* global platypus */
2
+ import {Container, Graphics, Rectangle, RenderTexture, Sprite} from 'pixi.js';
3
+ import {arrayCache, greenSlice, greenSplice, union} from '../utils/array.js';
4
+ import AABB from '../AABB.js';
5
+ import PIXIAnimation from '../PIXIAnimation.js';
6
+ import RenderContainer from './RenderContainer.js';
7
+ import createComponentClass from '../factory.js';
8
+ import recycle from 'recycle';
9
+
10
+ const
11
+ EDGE_BLEED = 1,
12
+ EDGES_BLEED = EDGE_BLEED * 2,
13
+ doNothing = function () {
14
+ return null;
15
+ },
16
+ tempCache = AABB.setUp(),
17
+ sort = function (a, b) {
18
+ return a.z - b.z;
19
+ },
20
+ getPowerOfTwo = function (amount) {
21
+ let x = 1;
22
+
23
+ while (x < amount) {
24
+ x *= 2;
25
+ }
26
+
27
+ return x;
28
+ },
29
+ transformCheck = function (v, tile) {
30
+ if (0x80000000 & v) {
31
+ tile.scale.x = -1;
32
+ }
33
+ if (0x40000000 & v) {
34
+ tile.scale.y = -1;
35
+ }
36
+ if (0x20000000 & v) {
37
+ const x = tile.scale.x;
38
+ tile.scale.x = tile.scale.y;
39
+ tile.scale.y = -x;
40
+ tile.rotation = Math.PI / 2;
41
+ }
42
+ },
43
+ createTeardown = (component, map) => {
44
+ const
45
+ tds = Object.keys(map).map((key) => {
46
+ const
47
+ handler = component.addEventListener(key, map[key]);
48
+
49
+ return () => component.removeEventListener(key, handler);
50
+ });
51
+
52
+ return () => {
53
+ tds.forEach((td) => td());
54
+ };
55
+ },
56
+ Template = function (tileSpriteSheet, id, uninitializedTiles) {
57
+ this.id = id;
58
+ this.instances = arrayCache.setUp();
59
+ this.index = 0;
60
+
61
+ // jit sprite
62
+ this.tileSpriteSheet = tileSpriteSheet;
63
+ this.getNext = this.initializeAndGetNext;
64
+ this.uninitializedTiles = uninitializedTiles;
65
+ uninitializedTiles.push(this);
66
+ },
67
+ nullTemplate = {
68
+ getNext: doNothing,
69
+ destroy: doNothing
70
+ },
71
+ prototype = Template.prototype;
72
+
73
+ prototype.initializeAndGetNext = function () {
74
+ this.initialize();
75
+
76
+ this.index += 1;
77
+ return this.instances[0];
78
+ };
79
+
80
+ prototype.initialize = function () {
81
+ const
82
+ index = +(this.id.substring(4)),
83
+ anim = 'tile' + (0x0fffffff & index),
84
+ tile = new Sprite((this.tileSpriteSheet._animations[anim] || this.tileSpriteSheet._animations.default).texture);
85
+
86
+ transformCheck(index, tile);
87
+ tile.template = this; // backwards reference for clearing index later.
88
+ this.instances.push(tile);
89
+ greenSplice(this.uninitializedTiles, this.uninitializedTiles.indexOf(this));
90
+
91
+ delete this.getNext;
92
+ };
93
+
94
+ prototype.getNext = function () {
95
+ let instance = this.instances[this.index];
96
+
97
+ if (!instance) {
98
+ const
99
+ template = this.instances[0];
100
+
101
+ instance = this.instances[this.index] = new Sprite(template.texture);
102
+
103
+ // Copy properties
104
+ instance.scale = template.scale;
105
+ instance.rotation = template.rotation;
106
+ instance.anchor = template.anchor || template._animation.anchor;
107
+ }
108
+
109
+ this.index += 1;
110
+
111
+ return instance;
112
+ };
113
+
114
+ prototype.clear = function () {
115
+ this.index = 0;
116
+ };
117
+
118
+ prototype.destroy = function () {
119
+ for (let i = 0; i < this.instances.length; i++) {
120
+ this.instances[i].destroy();
121
+ }
122
+
123
+ arrayCache.recycle(this.instances);
124
+ this.recycle();
125
+ };
126
+
127
+ recycle.add(Template, 'Template', Template, null, true);
128
+
129
+ export default createComponentClass(/** @lends platypus.components.RenderTiles.prototype */{
130
+
131
+ id: 'RenderTiles',
132
+
133
+ properties: {
134
+ /**
135
+ * The amount of space in pixels around the edge of the camera that we include in the buffered image. If not set, largest buffer allowed by maximumBuffer is used.
136
+ *
137
+ * @property buffer
138
+ * @type number
139
+ * @default 0
140
+ */
141
+ buffer: 0,
142
+
143
+ /**
144
+ * Determines whether to cache the entire map across one or more texture caches. By default this is `false`; however, if the entire map fits on one or two texture caches, this is set to `true` since it is more efficient than dynamic buffering.
145
+ *
146
+ * @property cacheAll
147
+ * @type Boolean
148
+ * @default false
149
+ */
150
+ cacheAll: false,
151
+
152
+ /**
153
+ * Whether to cache entities on this layer if the entity's render component requests caching.
154
+ *
155
+ * @property entityCache
156
+ * @type boolean
157
+ * @default false
158
+ */
159
+ entityCache: false,
160
+
161
+ /**
162
+ * This is a two dimensional array of the spritesheet indexes that describe the map that you're rendering.
163
+ *
164
+ * @property imageMap
165
+ * @type Array
166
+ * @default []
167
+ */
168
+ imageMap: [],
169
+
170
+ /**
171
+ * The amount of space that is buffered. Defaults to 2048 x 2048 or a smaller area that encloses the tile layer.
172
+ *
173
+ * @property maximumBuffer
174
+ * @type number
175
+ * @default 2048
176
+ */
177
+ maximumBuffer: 2048,
178
+
179
+ /**
180
+ * The acceptable buffer between camera viewport and cached tiles. This is needed when the camera contents are skewed or rotated to prevent cropping before refresh need is detected.
181
+ *
182
+ * @property refreshBuffer
183
+ * @type number
184
+ * @default 0
185
+ */
186
+ refreshBuffer: 0,
187
+
188
+ /**
189
+ * The x-scale the tilemap is being displayed at.
190
+ *
191
+ * @property scaleX
192
+ * @type number
193
+ * @default 1
194
+ */
195
+ scaleX: 1,
196
+
197
+ /**
198
+ * The y-scale the tilemap is being displayed at.
199
+ *
200
+ * @property scaleY
201
+ * @type number
202
+ * @default 1
203
+ */
204
+ scaleY: 1,
205
+
206
+ /**
207
+ * A sprite sheet describing all the tile images.
208
+ *
209
+ * Accepts an array of sprite sheet data since 0.8.4
210
+ *
211
+ * @property spriteSheet
212
+ * @type Object|Array|String
213
+ * @default null
214
+ */
215
+ spriteSheet: null,
216
+
217
+ /**
218
+ * Whether to cache the tile map to a large texture.
219
+ *
220
+ * @property tileCache
221
+ * @type boolean
222
+ * @default true
223
+ */
224
+ tileCache: true,
225
+
226
+ /**
227
+ * This is the height in pixels of individual tiles.
228
+ *
229
+ * @property tileHeight
230
+ * @type number
231
+ * @default 10
232
+ */
233
+ tileHeight: 10,
234
+
235
+ /**
236
+ * This is the width in pixels of individual tiles.
237
+ *
238
+ * @property tileWidth
239
+ * @type number
240
+ * @default 10
241
+ */
242
+ tileWidth: 10,
243
+
244
+ /**
245
+ * The map's top offset.
246
+ *
247
+ * @property top
248
+ * @type Number
249
+ * @default 0
250
+ */
251
+ top: 0,
252
+
253
+ /**
254
+ * The map's left offset.
255
+ *
256
+ * @property left
257
+ * @type Number
258
+ * @default 0
259
+ */
260
+ left: 0
261
+ },
262
+
263
+ /**
264
+ * This component handles rendering tile map backgrounds.
265
+ *
266
+ * When rendering the background, this component figures out what tiles are being displayed and caches them so they are rendered as one image rather than individually.
267
+ *
268
+ * As the camera moves, the cache is updated by blitting the relevant part of the old cached image into a new cache and then rendering tiles that have shifted into the camera's view into the cache.
269
+ *
270
+ * @memberof platypus.components
271
+ * @uses platypus.Component
272
+ * @constructs
273
+ * @listens platypus.Entity#add-tiles
274
+ * @listens platypus.Entity#cache-sprite
275
+ * @listens platypus.Entity#camera-loaded
276
+ * @listens platypus.Entity#camera-update
277
+ * @listens platypus.Entity#change-tile
278
+ * @listens platypus.Entity#handle-render
279
+ * @listens platypus.Entity#peer-entity-added
280
+ */
281
+ initialize: function (definition) {
282
+ const
283
+ imgMap = this.imageMap,
284
+ handleRender = this.addEventListener('handle-render', function () {
285
+ // Pre-render any tiles left to be prerendered to reduce lag on camera movement
286
+ if (this.uninitializedTiles.length) {
287
+ this.uninitializedTiles[0].initialize();
288
+ } else {
289
+ this.removeEventListener('handle-render', handleRender);
290
+ this.owner.parent.triggerEvent('child-entity-updated', this.owner);
291
+ }
292
+ });
293
+
294
+ this.doMap = null; //list of display objects that should overlay tile map.
295
+ this.cachedDisplayObjects = null;
296
+ this.populate = this.populateTiles;
297
+
298
+ this.tiles = {};
299
+
300
+ this.renderer = platypus.game.renderer;
301
+ this.tilesSprite = null;
302
+ this.cacheTexture = null;
303
+ this.mapContainer = null;
304
+ this.laxCam = AABB.setUp();
305
+
306
+ // temp values
307
+ this.worldWidth = this.tileWidth;
308
+ this.worldHeight = this.tileHeight;
309
+
310
+ this.cache = AABB.setUp();
311
+ this.cachePixels = AABB.setUp();
312
+
313
+ this.uninitializedTiles = arrayCache.setUp();
314
+
315
+ // Set up containers
316
+ this.spriteSheet = PIXIAnimation.formatSpriteSheet(this.spriteSheet);
317
+ this.tileSpriteSheet = new PIXIAnimation(this.spriteSheet);
318
+ this.tileContainer = new Container();
319
+ this.mapContainer = new Container();
320
+ this.mapContainer.addChild(this.tileContainer);
321
+
322
+ this.updateCache = false;
323
+
324
+ // Prepare map tiles
325
+ this.imageMap = arrayCache.setUp(this.createMap(imgMap));
326
+
327
+ this.tilesWidth = this.imageMap[0].length;
328
+ this.tilesHeight = this.imageMap[0][0].length;
329
+ this.layerWidth = this.tilesWidth * this.tileWidth;
330
+ this.layerHeight = this.tilesHeight * this.tileHeight;
331
+
332
+ this.edgeBleed = EDGE_BLEED;
333
+ this.edgesBleed = EDGES_BLEED;
334
+
335
+ // Set up buffer cache size
336
+ this.cacheWidth = Math.min(getPowerOfTwo(this.layerWidth + this.edgesBleed), this.maximumBuffer);
337
+ this.cacheHeight = Math.min(getPowerOfTwo(this.layerHeight + this.edgesBleed), this.maximumBuffer);
338
+
339
+ if (!this.owner.container) {
340
+ this.owner.addComponent(new RenderContainer(this.owner, definition));
341
+ }
342
+
343
+ this.container = this.owner.container;
344
+
345
+ if (!this.tileCache) {
346
+ this.teardown = this.setupNoCache();
347
+ } else if ((this.layerWidth <= this.cacheWidth) && (this.layerHeight <= this.cacheHeight)) { // We never need to recache.
348
+ this.teardown = this.setupSingleCache();
349
+ } else if (this.cacheAll || ((this.layerWidth <= this.cacheWidth * 2) && (this.layerHeight <= this.cacheHeight)) || ((this.layerWidth <= this.cacheWidth) && (this.layerHeight <= this.cacheHeight * 2))) { // We cache everything across several textures creating a cache grid.
350
+ this.teardown = this.setupFullCache();
351
+ } else {
352
+ this.teardown = this.setupPanningCache();
353
+ }
354
+ },
355
+
356
+ events: {
357
+ "cache-sprite": function (entity) {
358
+ this.cacheSprite(entity);
359
+ },
360
+
361
+ "refresh-cache": function () {
362
+ this.updateCache = true;
363
+ },
364
+
365
+ "peer-entity-added": function (entity) {
366
+ this.cacheSprite(entity);
367
+ },
368
+
369
+ "peer-entity-removed": function (entity) {
370
+ this.uncacheSprite(entity);
371
+ },
372
+
373
+ /**
374
+ * This event adds a layer of tiles to render on top of the existing layer of rendered tiles.
375
+ *
376
+ * @event platypus.Entity#add-tiles
377
+ * @param message.imageMap {Array} This is a 2D mapping of tile indexes to be rendered.
378
+ */
379
+ "add-tiles": function (definition) {
380
+ const
381
+ map = definition.imageMap;
382
+
383
+ if (map) {
384
+ this.imageMap.push(this.createMap(map));
385
+ this.updateCache = true;
386
+ }
387
+ },
388
+
389
+ /**
390
+ * This event edits the tile index of a rendered tile.
391
+ *
392
+ * @event platypus.Entity#change-tile
393
+ * @param tile {String} A string representing the name of the tile to switch to.
394
+ * @param x {Number} The column of the tile to edit.
395
+ * @param y {Number} The row of the tile to edit.
396
+ * @param [z] {Number} If RenderTiles has multiple layers, this value specifies the layer, with `0` being the bottom-most layer.
397
+ */
398
+ "change-tile": function (tile, x, y, z) {
399
+ const
400
+ map = this.imageMap;
401
+
402
+ if (map) {
403
+ this.updateTile(tile, map[z || 0], x, y);
404
+ // Only flag a re-render if the changed tile is within the currently
405
+ // cached region. Changes outside the cache will be drawn correctly
406
+ // the next time the camera pans to include them.
407
+ if (!this.cache.empty &&
408
+ x >= this.cache.left && x <= this.cache.right &&
409
+ y >= this.cache.top && y <= this.cache.bottom) {
410
+ this.updateCache = true;
411
+ }
412
+ }
413
+ },
414
+
415
+ "camera-loaded": function (camera) {
416
+ this.worldWidth = camera.world.width;
417
+ this.worldHeight = camera.world.height;
418
+
419
+ if (this.buffer && !this.cacheAll) { // do this here to set the correct mask before the first caching.
420
+ this.updateBufferRegion(camera.viewport);
421
+ }
422
+ },
423
+
424
+ /**
425
+ * Forces an immediate full re-render centered on the current camera
426
+ * position. Call this after bulk tile changes to ensure the display
427
+ * is up to date without requiring a camera pan.
428
+ *
429
+ * For the panning cache, we invalidate cachePixels and re-run the full
430
+ * camera-update recache logic using the last known camera, which correctly
431
+ * handles texture swapping, tile population, and sprite positioning.
432
+ */
433
+ "force-render": function () {
434
+ if (this.cacheAll) {
435
+ // Single/full cache — re-render entire layer.
436
+ this.populateTiles(this.cache, null);
437
+ this.renderCache(this.cache, this.cacheTexture, this.mapContainer, this.mapContainerWrapper);
438
+ } else if (this.cacheTexture) {
439
+ // Panning cache — invalidate cachePixels so the recache branch
440
+ // runs, then execute the full camera-update recache inline.
441
+ const
442
+ {viewport: vp} = this.owner.parent.worldCamera,
443
+ {cache, cachePixels} = this,
444
+ tempC = tempCache,
445
+ ctw = this.cacheTilesWidth - 1,
446
+ cth = this.cacheTilesHeight - 1,
447
+ ctw2 = ctw / 2,
448
+ cth2 = cth / 2;
449
+
450
+ this.convertCamera(vp);
451
+
452
+ // Force the recache branch to run regardless of camera position.
453
+ cachePixels.empty = true;
454
+
455
+ tempC.setAll(
456
+ Math.round((this.laxCam.x - this.left) / this.tileWidth - ctw2) + ctw2,
457
+ Math.round((this.laxCam.y - this.top) / this.tileHeight - cth2) + cth2,
458
+ ctw, cth
459
+ );
460
+ if (tempC.left < 0) {
461
+ tempC.moveX(tempC.halfWidth);
462
+ } else if (tempC.right > this.tilesWidth - 1) {
463
+ tempC.moveX(this.tilesWidth - 1 - tempC.halfWidth);
464
+ }
465
+ if (tempC.top < 0) {
466
+ tempC.moveY(tempC.halfHeight);
467
+ } else if (tempC.bottom > this.tilesHeight - 1) {
468
+ tempC.moveY(this.tilesHeight - 1 - tempC.halfHeight);
469
+ }
470
+
471
+ this.tilesSpriteCache.texture = this.cacheTexture;
472
+ this.cacheTexture = this.cacheTexture.alternate;
473
+ this.tilesSprite.texture = this.cacheTexture;
474
+
475
+ this.populateTiles(tempC, cache);
476
+ this.renderCache(tempC, this.cacheTexture, this.mapContainer, this.mapContainerWrapper, this.tilesSpriteCache);
477
+ cache.set(tempC);
478
+
479
+ cachePixels.setAll(
480
+ (cache.x + 0.5) * this.tileWidth + this.left,
481
+ (cache.y + 0.5) * this.tileHeight + this.top,
482
+ (cache.width + 1) * this.tileWidth,
483
+ (cache.height + 1) * this.tileHeight
484
+ );
485
+
486
+ this.tilesSprite.x = vp.left - this.laxCam.left + cache.left * this.tileWidth + this.left;
487
+ this.tilesSprite.y = vp.top - this.laxCam.top + cache.top * this.tileHeight + this.top;
488
+ }
489
+ }
490
+ },
491
+
492
+ methods: {
493
+ setupSingleCache () {
494
+ const
495
+ {container, mapContainer, owner} = this,
496
+ {z} = owner,
497
+ cacheTexture = RenderTexture.create({
498
+ width: this.cacheWidth,
499
+ height: this.cacheHeight
500
+ }),
501
+ sprite = new Sprite(cacheTexture);
502
+
503
+ this.mapContainerWrapper = new Container();
504
+ this.mapContainerWrapper.addChild(mapContainer);
505
+
506
+ this.cacheAll = true;
507
+ this.edgeBleed = 0;
508
+ this.edgesBleed = 0;
509
+ this.updateRegion(0);
510
+
511
+ this.render = this.renderCache;
512
+ this.cacheTexture = cacheTexture;
513
+
514
+ this.tilesSprite = sprite;
515
+ sprite.scale.x = this.scaleX;
516
+ sprite.scale.y = this.scaleY;
517
+ sprite.z = z;
518
+
519
+ this.cache.setBounds(0, 0, this.tilesWidth - 1, this.tilesHeight - 1);
520
+ this.populateTiles(this.cache, null);
521
+ this.renderCache(this.cache, cacheTexture, this.mapContainer, this.mapContainerWrapper);
522
+ container.addChild(sprite);
523
+
524
+ return createTeardown(this, {
525
+ 'camera-update': function (camera) {
526
+ const
527
+ {laxCam, left, top} = this,
528
+ {viewport: vp} = camera;
529
+
530
+ this.convertCamera(vp);
531
+
532
+ this.tilesSprite.x = vp.left - laxCam.left + this.cache.left * this.tileWidth + left;
533
+ this.tilesSprite.y = vp.top - laxCam.top + this.cache.top * this.tileHeight + top;
534
+ },
535
+ 'handle-render': function () {
536
+ if (this.updateCache) {
537
+ this.updateCache = false;
538
+ this.populateTiles(this.cache, null);
539
+ this.renderCache(this.cache, this.cacheTexture, this.mapContainer, this.mapContainerWrapper);
540
+ }
541
+ }
542
+ });
543
+ },
544
+
545
+ setupFullCache () {
546
+ const
547
+ extrusionMargin = 2,
548
+ {container, mapContainer, owner} = this,
549
+ {z} = owner;
550
+
551
+ this.mapContainerWrapper = new Container();
552
+ this.mapContainerWrapper.addChild(mapContainer);
553
+
554
+ this.cacheAll = true;
555
+
556
+ // Make sure there's room for the one-pixel extrusion around edges of caches
557
+ this.cacheWidth = Math.min(getPowerOfTwo(this.layerWidth + extrusionMargin), this.maximumBuffer);
558
+ this.cacheHeight = Math.min(getPowerOfTwo(this.layerHeight + extrusionMargin), this.maximumBuffer);
559
+ this.updateRegion(extrusionMargin);
560
+
561
+ this.tileContainer.x = -1; // due to extrusion.
562
+ this.tileContainer.y = -1;
563
+ this.render = this.renderCacheWithExtrusion;
564
+ this.cacheGrid = this.createGrid(container);
565
+
566
+ this.updateCache = true;
567
+
568
+ return createTeardown(this, {
569
+ 'camera-update': function (camera) {
570
+ const
571
+ {cacheClipHeight, cacheClipWidth, cacheGrid, cachePixels, laxCam, left, top} = this,
572
+ {viewport: vp} = camera;
573
+
574
+ this.convertCamera(vp);
575
+
576
+ for (let x = 0; x < cacheGrid.length; x++) {
577
+ for (let y = 0; y < cacheGrid[x].length; y++) {
578
+ cachePixels.setAll((x + 0.5) * cacheClipWidth + left, (y + 0.5) * cacheClipHeight + top, cacheClipWidth, cacheClipHeight);
579
+
580
+ const
581
+ sprite = cacheGrid[x][y],
582
+ inFrame = cachePixels.intersects(laxCam);
583
+
584
+ if (sprite.visible && !inFrame) {
585
+ sprite.visible = false;
586
+ } else if (!sprite.visible && inFrame) {
587
+ sprite.visible = true;
588
+ }
589
+
590
+ if (sprite.visible && inFrame) {
591
+ sprite.x = vp.left - laxCam.left + x * cacheClipWidth + left;
592
+ sprite.y = vp.top - laxCam.top + y * cacheClipHeight + top;
593
+ }
594
+ }
595
+ }
596
+ },
597
+ 'handle-render': function () {
598
+ if (this.updateCache) {
599
+ const
600
+ {cache, cacheGrid, cacheTilesHeight, cacheTilesWidth, tilesHeight, tilesWidth} = this,
601
+ tsh = tilesHeight - 1,
602
+ tsw = tilesWidth - 1;
603
+
604
+ this.updateCache = false;
605
+
606
+ for (let x = 0; x < cacheGrid.length; x++) {
607
+ for (let y = 0; y < cacheGrid[x].length; y++) {
608
+ cache.setBounds(x * cacheTilesWidth, y * cacheTilesHeight, Math.min((x + 1) * cacheTilesWidth, tsw), Math.min((y + 1) * cacheTilesHeight, tsh));
609
+
610
+ this.populateTiles(cache, null);
611
+ this.renderCacheWithExtrusion(cache, cacheGrid[x][y].texture, this.mapContainer, this.mapContainerWrapper);
612
+ }
613
+ }
614
+ }
615
+ }
616
+ });
617
+ },
618
+
619
+ setupPanningCache () {
620
+ const
621
+ {container, mapContainer, owner} = this,
622
+ {z} = owner;
623
+
624
+ this.mapContainerWrapper = new Container();
625
+ this.mapContainerWrapper.addChild(mapContainer);
626
+
627
+ this.updateRegion(0);
628
+ this.render = this.renderCache;
629
+ this.cacheAll = false;
630
+
631
+ this.cacheTexture = RenderTexture.create({
632
+ width: this.cacheWidth,
633
+ height: this.cacheHeight
634
+ });
635
+
636
+ this.tilesSprite = new Sprite(this.cacheTexture);
637
+ this.tilesSprite.scale.x = this.scaleX;
638
+ this.tilesSprite.scale.y = this.scaleY;
639
+ this.tilesSprite.z = z;
640
+
641
+ // Set up copy buffer and circular pointers
642
+ this.cacheTexture.alternate = RenderTexture.create({
643
+ width: this.cacheWidth,
644
+ height: this.cacheHeight
645
+ });
646
+ this.tilesSpriteCache = new Sprite(this.cacheTexture.alternate);
647
+
648
+ this.cacheTexture.alternate.alternate = this.cacheTexture;
649
+ container.addChild(this.tilesSprite);
650
+
651
+ return createTeardown(this, {
652
+ 'camera-update': function (camera) {
653
+ const
654
+ {buffer, cache, cachePixels, laxCam, refreshBuffer} = this,
655
+ {viewport: vp} = camera,
656
+ resized = (buffer && ((vp.width !== laxCam.width) || (vp.height !== laxCam.height))),
657
+ tempC = tempCache;
658
+
659
+ this.convertCamera(vp);
660
+
661
+ if ((cachePixels.empty || !cachePixels.contains(laxCam)) && (this.imageMap.length > 0)) {
662
+ if (resized) {
663
+ this.updateBufferRegion(laxCam);
664
+ }
665
+
666
+ const
667
+ ctw = this.cacheTilesWidth - 1,
668
+ cth = this.cacheTilesHeight - 1,
669
+ ctw2 = ctw / 2,
670
+ cth2 = cth / 2;
671
+
672
+ //only attempt to draw children that are relevant
673
+ tempC.setAll(Math.round((laxCam.x - this.left) / this.tileWidth - ctw2) + ctw2, Math.round((laxCam.y - this.top) / this.tileHeight - cth2) + cth2, ctw, cth);
674
+ if (tempC.left < 0) {
675
+ tempC.moveX(tempC.halfWidth);
676
+ } else if (tempC.right > this.tilesWidth - 1) {
677
+ tempC.moveX(this.tilesWidth - 1 - tempC.halfWidth);
678
+ }
679
+ if (tempC.top < 0) {
680
+ tempC.moveY(tempC.halfHeight);
681
+ } else if (tempC.bottom > this.tilesHeight - 1) {
682
+ tempC.moveY(this.tilesHeight - 1 - tempC.halfHeight);
683
+ }
684
+
685
+ this.tilesSpriteCache.texture = this.cacheTexture;
686
+ this.cacheTexture = this.cacheTexture.alternate;
687
+ this.tilesSprite.texture = this.cacheTexture;
688
+
689
+ this.populateTiles(tempC, cache);
690
+ this.renderCache(tempC, this.cacheTexture, this.mapContainer, this.mapContainerWrapper, this.tilesSpriteCache, cache);
691
+ cache.set(tempC);
692
+
693
+ // Store pixel bounding box for checking later.
694
+ cachePixels.setAll((cache.x + 0.5) * this.tileWidth + this.left, (cache.y + 0.5) * this.tileHeight + this.top, (cache.width + 1) * this.tileWidth, (cache.height + 1) * this.tileHeight);
695
+ }
696
+
697
+ this.tilesSprite.x = vp.left - laxCam.left + cache.left * this.tileWidth + this.left;
698
+ this.tilesSprite.y = vp.top - laxCam.top + cache.top * this.tileHeight + this.top;
699
+ },
700
+ 'handle-render': function () {
701
+ if (this.updateCache && !this.cache.empty) {
702
+ this.updateCache = false;
703
+ this.populateTiles(this.cache, null);
704
+ this.renderCacheClear(this.cache, this.cacheTexture, this.mapContainer, this.mapContainerWrapper);
705
+ this.tilesSpriteCache.texture = this.cacheTexture;
706
+ this.cachePixels.setAll(
707
+ (this.cache.x + 0.5) * this.tileWidth + this.left,
708
+ (this.cache.y + 0.5) * this.tileHeight + this.top,
709
+ (this.cache.width + 1) * this.tileWidth,
710
+ (this.cache.height + 1) * this.tileHeight
711
+ );
712
+ }
713
+ }
714
+ });
715
+ },
716
+
717
+ setupNoCache () {
718
+ const
719
+ {container, left, mapContainer, owner, scaleX, scaleY, top} = this,
720
+ {z} = owner;
721
+
722
+ this.updateRegion(0);
723
+
724
+ mapContainer.scale.x = scaleX;
725
+ mapContainer.scale.y = scaleY;
726
+ mapContainer.x = left;
727
+ mapContainer.y = top;
728
+ mapContainer.z = z;
729
+ container.addChild(mapContainer);
730
+
731
+ this.buffer = 0; // prevents buffer logic from running if tiles aren't being cached.
732
+ this.cacheAll = false; // so tiles are updated as camera moves.
733
+
734
+ return createTeardown(this, {
735
+ 'camera-update': function (camera) {
736
+ const
737
+ {buffer, cache, cachePixels, laxCam} = this,
738
+ {viewport: vp} = camera,
739
+ resized = (buffer && ((vp.width !== laxCam.width) || (vp.height !== laxCam.height))),
740
+ tempC = tempCache;
741
+
742
+ this.convertCamera(vp);
743
+
744
+ if ((cachePixels.empty || !cachePixels.contains(laxCam)) && (this.imageMap.length > 0)) {
745
+ if (resized) {
746
+ this.updateBufferRegion(laxCam);
747
+ }
748
+
749
+ const
750
+ ctw = this.cacheTilesWidth - 1,
751
+ cth = this.cacheTilesHeight - 1,
752
+ ctw2 = ctw / 2,
753
+ cth2 = cth / 2;
754
+
755
+ //only attempt to draw children that are relevant
756
+ tempC.setAll(Math.round((laxCam.x - this.left) / this.tileWidth - ctw2) + ctw2, Math.round((laxCam.y - this.top) / this.tileHeight - cth2) + cth2, ctw, cth);
757
+ if (tempC.left < 0) {
758
+ tempC.moveX(tempC.halfWidth);
759
+ } else if (tempC.right > this.tilesWidth - 1) {
760
+ tempC.moveX(this.tilesWidth - 1 - tempC.halfWidth);
761
+ }
762
+ if (tempC.top < 0) {
763
+ tempC.moveY(tempC.halfHeight);
764
+ } else if (tempC.bottom > this.tilesHeight - 1) {
765
+ tempC.moveY(this.tilesHeight - 1 - tempC.halfHeight);
766
+ }
767
+
768
+ this.populateTiles(tempC, null);
769
+ cache.set(tempC);
770
+
771
+ // Store pixel bounding box for checking later.
772
+ cachePixels.setAll((cache.x + 0.5) * this.tileWidth + this.left, (cache.y + 0.5) * this.tileHeight + this.top, (cache.width + 1) * this.tileWidth, (cache.height + 1) * this.tileHeight);
773
+ }
774
+ },
775
+ 'handle-render': function () {
776
+ if (this.updateCache) {
777
+ this.updateCache = false;
778
+ // Invalidate cachePixels so camera-update immediately re-runs
779
+ // populateTiles on the next tick rather than skipping because
780
+ // the old region still appears valid.
781
+ this.cachePixels.empty = true;
782
+ }
783
+ }
784
+ });
785
+ },
786
+
787
+ cacheSprite: function (entity) {
788
+ const
789
+ object = entity.cacheRender;
790
+
791
+ // Determine whether to merge this image with the background.
792
+ if (this.entityCache && object) {
793
+ const
794
+ {x: boundsX, y: boundsY, width, height} = object.getBounds(object.transformMatrix),
795
+ offsetX = boundsX - this.left,
796
+ offsetY = boundsY - this.top,
797
+ top = Math.max(0, Math.floor(offsetY / this.tileHeight)),
798
+ bottom = Math.min(this.tilesHeight, Math.ceil((offsetY + height) / this.tileHeight)),
799
+ left = Math.max(0, Math.floor(offsetX / this.tileWidth)),
800
+ right = Math.min(this.tilesWidth, Math.ceil((offsetX + width) / this.tileWidth));
801
+
802
+ if (!this.doMap) {
803
+ this.doMap = arrayCache.setUp();
804
+ this.cachedDisplayObjects = arrayCache.setUp();
805
+ this.populate = this.populateTilesAndEntities;
806
+ }
807
+ this.cachedDisplayObjects.push(object);
808
+
809
+ // Find tiles that should include this display object
810
+ for (let x = left; x < right; x++) {
811
+ if (!this.doMap[x]) {
812
+ this.doMap[x] = arrayCache.setUp();
813
+ }
814
+ for (let y = top; y < bottom; y++) {
815
+ if (!this.doMap[x][y]) {
816
+ this.doMap[x][y] = arrayCache.setUp();
817
+ }
818
+ this.doMap[x][y].push(object);
819
+ }
820
+ }
821
+
822
+ // Prevent subsequent draws
823
+ entity.removeFromParentContainer();
824
+
825
+ this.updateCache = true; //TODO: This currently causes a blanket cache update - may be worthwhile to only recache if this entity's location is currently in a cache (either cacheGrid or the current viewable area).
826
+ }
827
+ },
828
+
829
+ uncacheSprite: function (entity) {
830
+ const
831
+ object = entity.cacheRender;
832
+
833
+ // Determine whether to merge this image with the background.
834
+ if (this.entityCache && object) {
835
+ const
836
+ {x: boundsX, y: boundsY, width, height} = object.getBounds(object.transformMatrix),
837
+ index = this.cachedDisplayObjects.indexOf(object),
838
+ offsetX = boundsX - this.left,
839
+ offsetY = boundsY - this.top,
840
+ top = Math.max(0, Math.floor(offsetY / this.tileHeight)),
841
+ bottom = Math.min(this.tilesHeight, Math.ceil((offsetY + height) / this.tileHeight)),
842
+ left = Math.max(0, Math.floor(offsetX / this.tileWidth)),
843
+ right = Math.min(this.tilesWidth, Math.ceil((offsetX + width) / this.tileWidth));
844
+
845
+ if (index >= 0) {
846
+ this.cachedDisplayObjects.splice(index, 1);
847
+ }
848
+
849
+ // Find tiles that should include this display object
850
+ for (let x = left; x < right; x++) {
851
+ for (let y = top; y < bottom; y++) {
852
+ if (this.doMap?.[x]?.[y]) {
853
+ const
854
+ index = this.doMap[x][y].indexOf(object);
855
+
856
+ if (index >= 0) {
857
+ this.doMap[x][y].splice(index, 1);
858
+ }
859
+ }
860
+ }
861
+ }
862
+
863
+ this.updateCache = true;
864
+ }
865
+ },
866
+
867
+ /**
868
+ * Computes a parallax-adjusted camera AABB (`laxCam`) for this layer.
869
+ *
870
+ * For layers smaller than the world, the layer must scroll slower so that
871
+ * it still covers the full world space as the camera travels edge-to-edge.
872
+ * The position is mapped proportionally: when the camera is at the start of
873
+ * its world travel, laxCam is at the start of the layer's travel, and
874
+ * likewise at the end.
875
+ *
876
+ * IMPORTANT: laxCam is resized to the *actual viewport dimensions* (not
877
+ * viewport + refreshBuffer). Its purpose is solely to compute the parallax
878
+ * offset for sprite positioning (vp.left - laxCam.left). Inflating it with
879
+ * refreshBuffer caused sprite offsets to be wrong by `refreshBuffer` pixels
880
+ * on every camera update, breaking parallax for differently-sized layers.
881
+ */
882
+ convertCamera: function (camera) {
883
+ const
884
+ worldWidth = this.worldWidth / this.scaleX,
885
+ worldPosX = worldWidth - camera.width,
886
+ worldHeight = this.worldHeight / this.scaleY,
887
+ worldPosY = worldHeight - camera.height,
888
+ laxCam = this.laxCam;
889
+
890
+ if ((worldWidth === this.layerWidth) || !worldPosX) {
891
+ laxCam.moveX(camera.x);
892
+ } else {
893
+ laxCam.moveX((camera.left - this.left) * (this.layerWidth - camera.width) / worldPosX + camera.halfWidth + this.left);
894
+ }
895
+
896
+ if ((worldHeight === this.layerHeight) || !worldPosY) {
897
+ laxCam.moveY(camera.y);
898
+ } else {
899
+ laxCam.moveY((camera.top - this.top) * (this.layerHeight - camera.height) / worldPosY + camera.halfHeight + this.top);
900
+ }
901
+
902
+ // FIX: resize laxCam to the actual viewport size only — never camera + refreshBuffer.
903
+ if (camera.width !== laxCam.width || camera.height !== laxCam.height) {
904
+ laxCam.resize(camera.width, camera.height);
905
+ }
906
+ },
907
+
908
+ createTile: function (imageName) {
909
+ // "tile-1" is empty, so it remains a null reference.
910
+ if (imageName === 'tile-1') {
911
+ return nullTemplate;
912
+ }
913
+
914
+ return Template.setUp(this.tileSpriteSheet, imageName, this.uninitializedTiles);
915
+ },
916
+
917
+ createMap: function (mapDefinition) {
918
+ if (typeof mapDefinition[0][0] !== 'string') { // This is not a map definition: it's an actual RenderTiles map.
919
+ return mapDefinition;
920
+ } else {
921
+ const
922
+ map = arrayCache.setUp();
923
+
924
+ for (let x = 0; x < mapDefinition.length; x++) {
925
+ map[x] = arrayCache.setUp();
926
+ for (let y = 0; y < mapDefinition[x].length; y++) {
927
+ this.updateTile(mapDefinition[x][y], map, x, y);
928
+ }
929
+ }
930
+
931
+ return map;
932
+ }
933
+ },
934
+
935
+ updateTile: function (index, map, x, y) {
936
+ const
937
+ id = index.id ?? index,
938
+ {tiles} = this,
939
+ tile = tiles[id];
940
+
941
+ if (!tile && (tile !== null)) { // Empty grid spaces are null, so we needn't create a new tile.
942
+ map[x][y] = tiles[id] = this.createTile(id);
943
+ } else {
944
+ map[x][y] = tile;
945
+ }
946
+ },
947
+
948
+ createGrid: function (container) {
949
+ const
950
+ {edgeBleed: extrusion, edgesBleed: outerMargin} = this,
951
+ ch = this.cacheHeight,
952
+ cw = this.cacheWidth,
953
+ cth = this.cacheTilesHeight,
954
+ ctw = this.cacheTilesWidth,
955
+ sx = this.scaleX,
956
+ sy = this.scaleY,
957
+ th = this.tileHeight,
958
+ tw = this.tileWidth,
959
+ tsh = this.tilesHeight,
960
+ tsw = this.tilesWidth,
961
+ cg = arrayCache.setUp();
962
+ let ct = null,
963
+ z = this.owner.z;
964
+
965
+ for (let x = 0; x < tsw; x += ctw) {
966
+ const
967
+ col = arrayCache.setUp();
968
+ cg.push(col);
969
+ for (let y = 0; y < tsh; y += cth) {
970
+ // This prevents us from using too large of a cache for the right and bottom edges of the map.
971
+ const
972
+ width = Math.min(getPowerOfTwo((tsw - x) * tw + outerMargin), cw),
973
+ height = Math.min(getPowerOfTwo((tsh - y) * th + outerMargin), ch),
974
+ rt = RenderTexture.create({
975
+ width,
976
+ height
977
+ });
978
+
979
+ rt.frame = new Rectangle(extrusion, extrusion, (((width - outerMargin) / tw) >> 0) * tw + extrusion, (((height - outerMargin) / th) >> 0) * th + extrusion);
980
+ ct = new Sprite(rt);
981
+ ct.z = z;
982
+ ct.scale.x = sx;
983
+ ct.scale.y = sy;
984
+ col.push(ct);
985
+ container.addChild(ct);
986
+
987
+ z -= 0.000001; // so that tiles of large caches overlap consistently.
988
+ }
989
+ }
990
+
991
+ return cg;
992
+ },
993
+
994
+ updateRegion: function (margin) {
995
+ const
996
+ {edgesBleed} = this,
997
+ tw = this.tileWidth * this.scaleX,
998
+ th = this.tileHeight * this.scaleY,
999
+ ctw = Math.min(this.tilesWidth, ((this.cacheWidth - edgesBleed) / tw) >> 0),
1000
+ cth = Math.min(this.tilesHeight, ((this.cacheHeight - edgesBleed) / th) >> 0);
1001
+
1002
+ if (!ctw) {
1003
+ platypus.debug.warn('"' + this.owner.type + '" RenderTiles: The tiles are ' + tw + 'px wide which is larger than ' + (this.cacheWidth - edgesBleed) + 'px (maximum cache size of ' + this.cacheWidth + 'px minus a 2px edge bleed). Increase the maximum cache size or reduce tile size.');
1004
+ }
1005
+ if (!cth) {
1006
+ platypus.debug.warn('"' + this.owner.type + '" RenderTiles: The tiles are ' + th + 'px high which is larger than ' + (this.cacheHeight - edgesBleed) + 'px (maximum cache size of ' + this.cacheHeight + 'px minus a 2px edge bleed). Increase the maximum cache size or reduce tile size.');
1007
+ }
1008
+
1009
+ this.cacheTilesWidth = ctw;
1010
+ this.cacheTilesHeight = cth;
1011
+ this.cacheClipWidth = ctw * tw;
1012
+ this.cacheClipHeight = cth * th;
1013
+
1014
+ if (this.tileCache) {
1015
+ this.mapContainer.mask = new Graphics().rect(0, 0, this.cacheClipWidth + margin, this.cacheClipHeight + margin).fill(0x000000);
1016
+ }
1017
+ },
1018
+
1019
+ updateBufferRegion: function (viewport) {
1020
+ const
1021
+ tw = this.tileWidth * this.scaleX,
1022
+ th = this.tileHeight * this.scaleY;
1023
+
1024
+ this.cacheTilesWidth = Math.min(this.tilesWidth, Math.ceil((viewport.width + this.buffer * 2) / tw), (this.cacheWidth / tw) >> 0);
1025
+ this.cacheTilesHeight = Math.min(this.tilesHeight, Math.ceil((viewport.height + this.buffer * 2) / th), (this.cacheHeight / th) >> 0);
1026
+
1027
+ this.cacheClipWidth = this.cacheTilesWidth * tw;
1028
+ this.cacheClipHeight = this.cacheTilesHeight * th;
1029
+
1030
+ this.mapContainer.mask = new Graphics().rect(0, 0, this.cacheClipWidth, this.cacheClipHeight).fill(0x000000);
1031
+ },
1032
+
1033
+ populateTiles: function (bounds, oldBounds) {
1034
+ const
1035
+ tiles = arrayCache.setUp();
1036
+
1037
+ this.tileContainer.removeChildren();
1038
+ for (let x = bounds.left; x <= bounds.right; x++) {
1039
+ for (let y = bounds.top; y <= bounds.bottom; y++) {
1040
+ if (!oldBounds || oldBounds.empty || (y > oldBounds.bottom) || (y < oldBounds.top) || (x > oldBounds.right) || (x < oldBounds.left)) {
1041
+ for (let layer = 0; layer < this.imageMap.length; layer++) {
1042
+ const
1043
+ tile = this.imageMap[layer][x][y].getNext();
1044
+
1045
+ if (tile) {
1046
+ if (tile.template) {
1047
+ tiles.push(tile.template);
1048
+ }
1049
+ tile.x = (x + 0.5) * this.tileWidth;
1050
+ tile.y = (y + 0.5) * this.tileHeight;
1051
+ this.tileContainer.addChild(tile);
1052
+ }
1053
+ }
1054
+ }
1055
+ }
1056
+ }
1057
+
1058
+ // Clear out tile instances
1059
+ for (let z = 0; z < tiles.length; z++) {
1060
+ tiles[z].clear();
1061
+ }
1062
+ arrayCache.recycle(tiles);
1063
+ },
1064
+
1065
+ populateTilesAndEntities: function (bounds, oldBounds) {
1066
+ const
1067
+ ents = arrayCache.setUp(),
1068
+ tiles = arrayCache.setUp();
1069
+
1070
+ this.tileContainer.removeChildren();
1071
+ for (let x = bounds.left; x <= bounds.right; x++) {
1072
+ for (let y = bounds.top; y <= bounds.bottom; y++) {
1073
+ if (!oldBounds || oldBounds.empty || (y > oldBounds.bottom) || (y < oldBounds.top) || (x > oldBounds.right) || (x < oldBounds.left)) {
1074
+ // draw tiles
1075
+ for (let layer = 0; layer < this.imageMap.length; layer++) {
1076
+ const
1077
+ tile = this.imageMap[layer][x][y].getNext();
1078
+
1079
+ if (tile) {
1080
+ if (tile.template) {
1081
+ tiles.push(tile.template);
1082
+ }
1083
+ tile.x = (x + 0.5) * this.tileWidth;
1084
+ tile.y = (y + 0.5) * this.tileHeight;
1085
+ this.tileContainer.addChild(tile);
1086
+ }
1087
+ }
1088
+
1089
+ // check for cached entities
1090
+ if (this.doMap[x] && this.doMap[x][y]) {
1091
+ const
1092
+ oList = this.doMap[x][y];
1093
+
1094
+ for (let z = 0; z < oList.length; z++) {
1095
+ if (!oList[z].drawn) {
1096
+ oList[z].drawn = true;
1097
+ ents.push(oList[z]);
1098
+ }
1099
+ }
1100
+ }
1101
+ }
1102
+ }
1103
+ }
1104
+
1105
+ this.mapContainer.removeChildren();
1106
+ this.mapContainer.addChild(this.tileContainer);
1107
+
1108
+ // Draw cached entities
1109
+ if (ents.length) {
1110
+ ents.sort(sort);
1111
+ for (let z = 0; z < ents.length; z++) {
1112
+ const
1113
+ ent = ents[z];
1114
+
1115
+ delete ent.drawn;
1116
+ this.mapContainer.addChild(ent);
1117
+ if (ent.mask) {
1118
+ this.mapContainer.addChild(ent.mask);
1119
+ }
1120
+ }
1121
+ }
1122
+
1123
+ // Clear out tile instances
1124
+ for (let z = 0; z < tiles.length; z++) {
1125
+ tiles[z].clear();
1126
+ }
1127
+
1128
+ arrayCache.recycle(tiles);
1129
+ arrayCache.recycle(ents);
1130
+ },
1131
+
1132
+ renderCache: function (bounds, renderTexture, src, wrapper, oldCache, oldBounds) {
1133
+ const
1134
+ {renderer} = this;
1135
+
1136
+ if (oldCache && oldBounds && !oldBounds.empty) {
1137
+ oldCache.x = oldBounds.left * this.tileWidth;
1138
+ oldCache.y = oldBounds.top * this.tileHeight;
1139
+ src.addChild(oldCache); // To copy last rendering over.
1140
+ }
1141
+
1142
+ src.x = -bounds.left * this.tileWidth;
1143
+ src.y = -bounds.top * this.tileHeight;
1144
+ renderer.render({
1145
+ container: wrapper,
1146
+ target: renderTexture
1147
+ });
1148
+ renderTexture.requiresUpdate = true;
1149
+ },
1150
+
1151
+ renderCacheClear: function (bounds, renderTexture, src, wrapper) {
1152
+ const
1153
+ {renderer} = this;
1154
+
1155
+ src.x = -bounds.left * this.tileWidth;
1156
+ src.y = -bounds.top * this.tileHeight;
1157
+ renderer.render({
1158
+ container: wrapper,
1159
+ target: renderTexture,
1160
+ clear: true
1161
+ });
1162
+ renderTexture.requiresUpdate = true;
1163
+ },
1164
+
1165
+ renderCacheWithExtrusion: function (bounds, renderTexture, src, wrapper) {
1166
+ const
1167
+ extrusion = 1,
1168
+ border = new Graphics(),
1169
+ {renderer} = this,
1170
+ rendering = {
1171
+ container: wrapper,
1172
+ target: renderTexture
1173
+ };
1174
+
1175
+ // This mask makes only the extruded border drawn for the next 4 draws so that inner holes aren't extruded in addition to the outer rim.
1176
+ border.rect(0.5, 0.5, this.cacheClipWidth + 1, this.cacheClipHeight + 1);
1177
+ border.stroke({
1178
+ width: 1,
1179
+ color: 0x000000
1180
+ });
1181
+
1182
+ // There is probably a better way to do this. Currently for the extrusion, everything is rendered once offset in the n, s, e, w directions and then once in the middle to create the effect.
1183
+ wrapper.mask = border;
1184
+ src.x = -bounds.left * this.tileWidth;
1185
+ src.y = -bounds.top * this.tileHeight + extrusion;
1186
+ renderer.render(rendering);
1187
+ src.x = -bounds.left * this.tileWidth + extrusion;
1188
+ src.y = -bounds.top * this.tileHeight;
1189
+ renderer.render(rendering);
1190
+ src.x = -bounds.left * this.tileWidth + extrusion * 2;
1191
+ src.y = -bounds.top * this.tileHeight + extrusion;
1192
+ renderer.render(rendering);
1193
+ src.x = -bounds.left * this.tileWidth + extrusion;
1194
+ src.y = -bounds.top * this.tileHeight + extrusion * 2;
1195
+ renderer.render(rendering);
1196
+ wrapper.mask = null;
1197
+ src.x = -bounds.left * this.tileWidth + extrusion;
1198
+ src.y = -bounds.top * this.tileHeight + extrusion;
1199
+ renderer.render(rendering);
1200
+ renderTexture.requiresUpdate = true;
1201
+ },
1202
+
1203
+ toJSON: function () {
1204
+ const
1205
+ imageMap = this.imageMap[0],
1206
+ imgMap = [];
1207
+ let x = imageMap.length;
1208
+
1209
+ while (x--) {
1210
+ let y = imageMap[x].length;
1211
+
1212
+ imgMap[x] = [];
1213
+ while (y--) {
1214
+ imgMap[x][y] = imageMap[x][y].id;
1215
+ }
1216
+ }
1217
+
1218
+ return {
1219
+ type: 'RenderTiles',
1220
+ buffer: this.buffer,
1221
+ cacheAll: this.cacheAll,
1222
+ entityCache: this.entityCache,
1223
+ imageMap: imgMap,
1224
+ maximumBuffer: this.maximumBuffer,
1225
+ scaleX: this.scaleX,
1226
+ scaleY: this.scaleY,
1227
+ spriteSheet: this.spriteSheet,
1228
+ tileCache: this.tileCache,
1229
+ tileHeight: this.tileHeight,
1230
+ tileWidth: this.tileWidth,
1231
+ top: this.top,
1232
+ left: this.left
1233
+ };
1234
+ },
1235
+
1236
+ destroy: function () {
1237
+ const
1238
+ grid = this.cacheGrid,
1239
+ map = this.doMap,
1240
+ img = this.imageMap,
1241
+ keys = Object.keys(this.tiles),
1242
+ {length} = keys;
1243
+
1244
+ if (grid) {
1245
+ for (let x = 0; x < grid.length; x++) {
1246
+ for (let y = 0; y < grid[x].length; y++) {
1247
+ grid[x][y].texture.destroy(true);
1248
+ this.container.removeChild(grid[x][y]);
1249
+ }
1250
+ }
1251
+ arrayCache.recycle(grid, 2);
1252
+ delete this.cacheGrid;
1253
+ } else if (this.tilesSprite) {
1254
+ if (this.tilesSprite.texture.alternate) {
1255
+ this.tilesSprite.texture.alternate.destroy(true);
1256
+ }
1257
+ this.tilesSprite.texture.destroy(true);
1258
+ this.container.removeChild(this.tilesSprite);
1259
+ } else {
1260
+ this.container.removeChild(this.mapContainer);
1261
+ }
1262
+
1263
+ arrayCache.recycle(img, 2);
1264
+
1265
+ for (let i = 0; i < length; i++) {
1266
+ this.tiles[keys[i]].destroy();
1267
+ }
1268
+ this.tiles = null;
1269
+ this.container = null;
1270
+ this.tilesSprite = null;
1271
+ this.spriteSheet.recycleSpriteSheet();
1272
+
1273
+ if (map) {
1274
+ for (let x = 0; x < this.cachedDisplayObjects.length; x++) {
1275
+ this.cachedDisplayObjects[x].destroy();
1276
+ }
1277
+ arrayCache.recycle(this.cachedDisplayObjects);
1278
+
1279
+ for (let x = 0; x < map.length; x++) {
1280
+ if (map[x]) {
1281
+ for (let y = 0; y < map[x].length; y++) {
1282
+ if (map[x][y]) {
1283
+ arrayCache.recycle(map[x][y]);
1284
+ }
1285
+ }
1286
+ arrayCache.recycle(map[x]);
1287
+ }
1288
+ }
1289
+ arrayCache.recycle(map);
1290
+ }
1291
+
1292
+ this.laxCam.recycle();
1293
+ this.cache.recycle();
1294
+ this.cachePixels.recycle();
1295
+ arrayCache.recycle(this.uninitializedTiles);
1296
+
1297
+ this.teardown?.();
1298
+ }
1299
+ },
1300
+
1301
+ publicMethods: {
1302
+ getMapDimensions: function () {
1303
+ return {
1304
+ tilesWidth: this.tilesWidth,
1305
+ tilesHeight: this.tilesHeight,
1306
+ layerWidth: this.layerWidth,
1307
+ layerHeight: this.layerHeight
1308
+ }
1309
+ },
1310
+ getTileAt: function (x, y, z) {
1311
+ return this.imageMap[z][x][y];
1312
+ },
1313
+ getRootTileIndex: function (tileId) {
1314
+ const nonTransformData = ~(0x80000000 + 0x40000000 + 0x20000000);
1315
+ let index = tileId;
1316
+
1317
+ if (typeof tileId === 'string') {
1318
+ index = +tileId.slice(('tile').length);
1319
+ }
1320
+
1321
+ return index & nonTransformData;
1322
+ },
1323
+ getTileVariants: function (tileId) {
1324
+ // FIX: each transform flag must use its own distinct bitmask constant.
1325
+ // Previously all three were 0x80000000 (X-flip), making variants 2-7
1326
+ // all duplicates of each other. Correct values:
1327
+ // transformA = 0x80000000 (X-flip / horizontal mirror)
1328
+ // transformB = 0x40000000 (Y-flip / vertical mirror)
1329
+ // transformC = 0x20000000 (diagonal / rotate)
1330
+ const transformA = 0x80000000,
1331
+ transformB = 0x40000000,
1332
+ transformC = 0x20000000;
1333
+ let index = this.getRootTileIndex(tileId);
1334
+
1335
+ return [
1336
+ index,
1337
+ index + transformA,
1338
+ index + transformB,
1339
+ index + transformC,
1340
+ index + transformA + transformB,
1341
+ index + transformA + transformC,
1342
+ index + transformB + transformC,
1343
+ index + transformA + transformB + transformC
1344
+ ];
1345
+ }
1346
+ },
1347
+
1348
+ getAssetList: (function () {
1349
+ const
1350
+ getImages = function (ss, spriteSheets) {
1351
+ if (ss) {
1352
+ if (typeof ss === 'string') {
1353
+ return getImages(spriteSheets[ss], spriteSheets);
1354
+ } else if (ss.images) {
1355
+ return greenSlice(ss.images);
1356
+ }
1357
+ }
1358
+
1359
+ return arrayCache.setUp();
1360
+ };
1361
+
1362
+ return function (component, props, defaultProps) {
1363
+ const
1364
+ spriteSheets = platypus.game.settings.spriteSheets,
1365
+ ss = component?.spriteSheet ?? props?.spriteSheet ?? defaultProps?.spriteSheet;
1366
+
1367
+ if (ss) {
1368
+ if (typeof ss === 'string' && (ss !== 'import')) {
1369
+ return getImages(ss, spriteSheets);
1370
+ } else if (Array.isArray(ss)) {
1371
+ const
1372
+ images = arrayCache.setUp();
1373
+ let i = ss.length;
1374
+
1375
+ while (i--) {
1376
+ const
1377
+ arr = getImages(ss[i], spriteSheets);
1378
+
1379
+ union(images, arr);
1380
+ arrayCache.recycle(arr);
1381
+ }
1382
+ return images;
1383
+ } else if (ss.images) {
1384
+ return greenSlice(ss.images);
1385
+ }
1386
+ }
1387
+
1388
+ return arrayCache.setUp();
1389
+ };
1390
+ }())
1391
+ });