@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,1440 @@
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
+ * Tiled parallax factor for horizontal scrolling. 1.0 scrolls with the camera; values less than 1.0 scroll slower (background); 0 is fixed to the screen.
264
+ *
265
+ * @property parallaxX
266
+ * @type number
267
+ * @default 1
268
+ */
269
+ parallaxX: 1,
270
+
271
+ /**
272
+ * Tiled parallax factor for vertical scrolling.
273
+ *
274
+ * @property parallaxY
275
+ * @type number
276
+ * @default 1
277
+ */
278
+ parallaxY: 1,
279
+
280
+ /**
281
+ * Map parallax origin X in world coordinates (Tiled `parallaxoriginx`, plus any map offset).
282
+ *
283
+ * @property parallaxOriginX
284
+ * @type number
285
+ * @default 0
286
+ */
287
+ parallaxOriginX: 0,
288
+
289
+ /**
290
+ * Map parallax origin Y in world coordinates (Tiled `parallaxoriginy`, plus any map offset).
291
+ *
292
+ * @property parallaxOriginY
293
+ * @type number
294
+ * @default 0
295
+ */
296
+ parallaxOriginY: 0
297
+ },
298
+
299
+ /**
300
+ * This component handles rendering tile map backgrounds.
301
+ *
302
+ * 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.
303
+ *
304
+ * 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.
305
+ *
306
+ * @memberof platypus.components
307
+ * @uses platypus.Component
308
+ * @constructs
309
+ * @listens platypus.Entity#add-tiles
310
+ * @listens platypus.Entity#cache-sprite
311
+ * @listens platypus.Entity#camera-loaded
312
+ * @listens platypus.Entity#camera-update
313
+ * @listens platypus.Entity#change-tile
314
+ * @listens platypus.Entity#handle-render
315
+ * @listens platypus.Entity#peer-entity-added
316
+ */
317
+ initialize: function (definition) {
318
+ const
319
+ imgMap = this.imageMap,
320
+ handleRender = this.addEventListener('handle-render', function () {
321
+ // Pre-render any tiles left to be prerendered to reduce lag on camera movement
322
+ if (this.uninitializedTiles.length) {
323
+ this.uninitializedTiles[0].initialize();
324
+ } else {
325
+ this.removeEventListener('handle-render', handleRender);
326
+ this.owner.parent.triggerEvent('child-entity-updated', this.owner);
327
+ }
328
+ });
329
+
330
+ this.doMap = null; //list of display objects that should overlay tile map.
331
+ this.cachedDisplayObjects = null;
332
+ this.populate = this.populateTiles;
333
+
334
+ this.tiles = {};
335
+
336
+ this.renderer = platypus.game.renderer;
337
+ this.tilesSprite = null;
338
+ this.cacheTexture = null;
339
+ this.mapContainer = null;
340
+ this.laxCam = AABB.setUp();
341
+
342
+ // temp values
343
+ this.worldWidth = this.tileWidth;
344
+ this.worldHeight = this.tileHeight;
345
+
346
+ this.cache = AABB.setUp();
347
+ this.cachePixels = AABB.setUp();
348
+
349
+ this.uninitializedTiles = arrayCache.setUp();
350
+
351
+ // Set up containers
352
+ this.spriteSheet = PIXIAnimation.formatSpriteSheet(this.spriteSheet);
353
+ this.tileSpriteSheet = new PIXIAnimation(this.spriteSheet);
354
+ this.tileContainer = new Container();
355
+ this.mapContainer = new Container();
356
+ this.mapContainer.addChild(this.tileContainer);
357
+
358
+ this.updateCache = false;
359
+
360
+ // Prepare map tiles
361
+ this.imageMap = arrayCache.setUp(this.createMap(imgMap));
362
+
363
+ this.tilesWidth = this.imageMap[0].length;
364
+ this.tilesHeight = this.imageMap[0][0].length;
365
+ this.layerWidth = this.tilesWidth * this.tileWidth;
366
+ this.layerHeight = this.tilesHeight * this.tileHeight;
367
+
368
+ this.edgeBleed = EDGE_BLEED;
369
+ this.edgesBleed = EDGES_BLEED;
370
+
371
+ // Set up buffer cache size
372
+ this.cacheWidth = Math.min(getPowerOfTwo(this.layerWidth + this.edgesBleed), this.maximumBuffer);
373
+ this.cacheHeight = Math.min(getPowerOfTwo(this.layerHeight + this.edgesBleed), this.maximumBuffer);
374
+
375
+ if (!this.owner.container) {
376
+ this.owner.addComponent(new RenderContainer(this.owner, definition));
377
+ }
378
+
379
+ this.container = this.owner.container;
380
+
381
+ if (!this.tileCache) {
382
+ this.teardown = this.setupNoCache();
383
+ } else if ((this.layerWidth <= this.cacheWidth) && (this.layerHeight <= this.cacheHeight)) { // We never need to recache.
384
+ this.teardown = this.setupSingleCache();
385
+ } 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.
386
+ this.teardown = this.setupFullCache();
387
+ } else {
388
+ this.teardown = this.setupPanningCache();
389
+ }
390
+ },
391
+
392
+ events: {
393
+ "cache-sprite": function (entity) {
394
+ this.cacheSprite(entity);
395
+ },
396
+
397
+ "refresh-cache": function () {
398
+ this.updateCache = true;
399
+ },
400
+
401
+ "peer-entity-added": function (entity) {
402
+ this.cacheSprite(entity);
403
+ },
404
+
405
+ "peer-entity-removed": function (entity) {
406
+ this.uncacheSprite(entity);
407
+ },
408
+
409
+ /**
410
+ * This event adds a layer of tiles to render on top of the existing layer of rendered tiles.
411
+ *
412
+ * @event platypus.Entity#add-tiles
413
+ * @param message.imageMap {Array} This is a 2D mapping of tile indexes to be rendered.
414
+ */
415
+ "add-tiles": function (definition) {
416
+ const
417
+ map = definition.imageMap;
418
+
419
+ if (map) {
420
+ this.imageMap.push(this.createMap(map));
421
+ this.updateCache = true;
422
+ }
423
+ },
424
+
425
+ /**
426
+ * This event edits the tile index of a rendered tile.
427
+ *
428
+ * @event platypus.Entity#change-tile
429
+ * @param tile {String} A string representing the name of the tile to switch to.
430
+ * @param x {Number} The column of the tile to edit.
431
+ * @param y {Number} The row of the tile to edit.
432
+ * @param [z] {Number} If RenderTiles has multiple layers, this value specifies the layer, with `0` being the bottom-most layer.
433
+ */
434
+ "change-tile": function (tile, x, y, z) {
435
+ const
436
+ map = this.imageMap;
437
+
438
+ if (map) {
439
+ this.updateTile(tile, map[z || 0], x, y);
440
+ // Only flag a re-render if the changed tile is within the currently
441
+ // cached region. Changes outside the cache will be drawn correctly
442
+ // the next time the camera pans to include them.
443
+ if (!this.cache.empty &&
444
+ x >= this.cache.left && x <= this.cache.right &&
445
+ y >= this.cache.top && y <= this.cache.bottom) {
446
+ this.updateCache = true;
447
+ }
448
+ }
449
+ },
450
+
451
+ "camera-loaded": function (camera) {
452
+ this.worldWidth = camera.world.width;
453
+ this.worldHeight = camera.world.height;
454
+
455
+ if (this.buffer && !this.cacheAll) { // do this here to set the correct mask before the first caching.
456
+ this.updateBufferRegion(camera.viewport);
457
+ }
458
+ },
459
+
460
+ /**
461
+ * Forces an immediate full re-render centered on the current camera
462
+ * position. Call this after bulk tile changes to ensure the display
463
+ * is up to date without requiring a camera pan.
464
+ *
465
+ * For the panning cache, we invalidate cachePixels and re-run the full
466
+ * camera-update recache logic using the last known camera, which correctly
467
+ * handles texture swapping, tile population, and sprite positioning.
468
+ */
469
+ "force-render": function () {
470
+ if (this.cacheAll) {
471
+ // Single/full cache — re-render entire layer.
472
+ this.populateTiles(this.cache, null);
473
+ this.renderCache(this.cache, this.cacheTexture, this.mapContainer, this.mapContainerWrapper);
474
+ } else if (this.cacheTexture) {
475
+ // Panning cache — invalidate cachePixels so the recache branch
476
+ // runs, then execute the full camera-update recache inline.
477
+ const
478
+ {viewport: vp} = this.owner.parent.worldCamera,
479
+ {cache, cachePixels} = this,
480
+ tempC = tempCache,
481
+ ctw = this.cacheTilesWidth - 1,
482
+ cth = this.cacheTilesHeight - 1,
483
+ ctw2 = ctw / 2,
484
+ cth2 = cth / 2;
485
+
486
+ this.convertCamera(vp);
487
+
488
+ // Force the recache branch to run regardless of camera position.
489
+ cachePixels.empty = true;
490
+
491
+ tempC.setAll(
492
+ Math.round((this.laxCam.x - this.left) / this.tileWidth - ctw2) + ctw2,
493
+ Math.round((this.laxCam.y - this.top) / this.tileHeight - cth2) + cth2,
494
+ ctw, cth
495
+ );
496
+ if (tempC.left < 0) {
497
+ tempC.moveX(tempC.halfWidth);
498
+ } else if (tempC.right > this.tilesWidth - 1) {
499
+ tempC.moveX(this.tilesWidth - 1 - tempC.halfWidth);
500
+ }
501
+ if (tempC.top < 0) {
502
+ tempC.moveY(tempC.halfHeight);
503
+ } else if (tempC.bottom > this.tilesHeight - 1) {
504
+ tempC.moveY(this.tilesHeight - 1 - tempC.halfHeight);
505
+ }
506
+
507
+ this.tilesSpriteCache.texture = this.cacheTexture;
508
+ this.cacheTexture = this.cacheTexture.alternate;
509
+ this.tilesSprite.texture = this.cacheTexture;
510
+
511
+ this.populateTiles(tempC, cache);
512
+ this.renderCache(tempC, this.cacheTexture, this.mapContainer, this.mapContainerWrapper, this.tilesSpriteCache);
513
+ cache.set(tempC);
514
+
515
+ cachePixels.setAll(
516
+ (cache.x + 0.5) * this.tileWidth + this.left,
517
+ (cache.y + 0.5) * this.tileHeight + this.top,
518
+ (cache.width + 1) * this.tileWidth,
519
+ (cache.height + 1) * this.tileHeight
520
+ );
521
+
522
+ this.tilesSprite.x = vp.left - this.laxCam.left + cache.left * this.tileWidth + this.left;
523
+ this.tilesSprite.y = vp.top - this.laxCam.top + cache.top * this.tileHeight + this.top;
524
+ }
525
+ }
526
+ },
527
+
528
+ methods: {
529
+ setupSingleCache () {
530
+ const
531
+ {container, mapContainer, owner} = this,
532
+ {z} = owner,
533
+ cacheTexture = RenderTexture.create({
534
+ width: this.cacheWidth,
535
+ height: this.cacheHeight
536
+ }),
537
+ sprite = new Sprite(cacheTexture);
538
+
539
+ this.mapContainerWrapper = new Container();
540
+ this.mapContainerWrapper.addChild(mapContainer);
541
+
542
+ this.cacheAll = true;
543
+ this.edgeBleed = 0;
544
+ this.edgesBleed = 0;
545
+ this.updateRegion(0);
546
+
547
+ this.render = this.renderCache;
548
+ this.cacheTexture = cacheTexture;
549
+
550
+ this.tilesSprite = sprite;
551
+ sprite.scale.x = this.scaleX;
552
+ sprite.scale.y = this.scaleY;
553
+ sprite.z = z;
554
+
555
+ this.cache.setBounds(0, 0, this.tilesWidth - 1, this.tilesHeight - 1);
556
+ this.populateTiles(this.cache, null);
557
+ this.renderCache(this.cache, cacheTexture, this.mapContainer, this.mapContainerWrapper);
558
+ container.addChild(sprite);
559
+
560
+ return createTeardown(this, {
561
+ 'camera-update': function (camera) {
562
+ const
563
+ {laxCam, left, top} = this,
564
+ {viewport: vp} = camera;
565
+
566
+ this.convertCamera(vp);
567
+
568
+ this.tilesSprite.x = vp.left - laxCam.left + this.cache.left * this.tileWidth + left;
569
+ this.tilesSprite.y = vp.top - laxCam.top + this.cache.top * this.tileHeight + top;
570
+ },
571
+ 'handle-render': function () {
572
+ if (this.updateCache) {
573
+ this.updateCache = false;
574
+ this.populateTiles(this.cache, null);
575
+ this.renderCache(this.cache, this.cacheTexture, this.mapContainer, this.mapContainerWrapper);
576
+ }
577
+ }
578
+ });
579
+ },
580
+
581
+ setupFullCache () {
582
+ const
583
+ extrusionMargin = 2,
584
+ {container, mapContainer, owner} = this,
585
+ {z} = owner;
586
+
587
+ this.mapContainerWrapper = new Container();
588
+ this.mapContainerWrapper.addChild(mapContainer);
589
+
590
+ this.cacheAll = true;
591
+
592
+ // Make sure there's room for the one-pixel extrusion around edges of caches
593
+ this.cacheWidth = Math.min(getPowerOfTwo(this.layerWidth + extrusionMargin), this.maximumBuffer);
594
+ this.cacheHeight = Math.min(getPowerOfTwo(this.layerHeight + extrusionMargin), this.maximumBuffer);
595
+ this.updateRegion(extrusionMargin);
596
+
597
+ this.tileContainer.x = -1; // due to extrusion.
598
+ this.tileContainer.y = -1;
599
+ this.render = this.renderCacheWithExtrusion;
600
+ this.cacheGrid = this.createGrid(container);
601
+
602
+ this.updateCache = true;
603
+
604
+ return createTeardown(this, {
605
+ 'camera-update': function (camera) {
606
+ const
607
+ {cacheClipHeight, cacheClipWidth, cacheGrid, cachePixels, laxCam, left, top} = this,
608
+ {viewport: vp} = camera;
609
+
610
+ this.convertCamera(vp);
611
+
612
+ for (let x = 0; x < cacheGrid.length; x++) {
613
+ for (let y = 0; y < cacheGrid[x].length; y++) {
614
+ cachePixels.setAll((x + 0.5) * cacheClipWidth + left, (y + 0.5) * cacheClipHeight + top, cacheClipWidth, cacheClipHeight);
615
+
616
+ const
617
+ sprite = cacheGrid[x][y],
618
+ inFrame = cachePixels.intersects(laxCam);
619
+
620
+ if (sprite.visible && !inFrame) {
621
+ sprite.visible = false;
622
+ } else if (!sprite.visible && inFrame) {
623
+ sprite.visible = true;
624
+ }
625
+
626
+ if (sprite.visible && inFrame) {
627
+ sprite.x = vp.left - laxCam.left + x * cacheClipWidth + left;
628
+ sprite.y = vp.top - laxCam.top + y * cacheClipHeight + top;
629
+ }
630
+ }
631
+ }
632
+ },
633
+ 'handle-render': function () {
634
+ if (this.updateCache) {
635
+ const
636
+ {cache, cacheGrid, cacheTilesHeight, cacheTilesWidth, tilesHeight, tilesWidth} = this,
637
+ tsh = tilesHeight - 1,
638
+ tsw = tilesWidth - 1;
639
+
640
+ this.updateCache = false;
641
+
642
+ for (let x = 0; x < cacheGrid.length; x++) {
643
+ for (let y = 0; y < cacheGrid[x].length; y++) {
644
+ cache.setBounds(x * cacheTilesWidth, y * cacheTilesHeight, Math.min((x + 1) * cacheTilesWidth, tsw), Math.min((y + 1) * cacheTilesHeight, tsh));
645
+
646
+ this.populateTiles(cache, null);
647
+ this.renderCacheWithExtrusion(cache, cacheGrid[x][y].texture, this.mapContainer, this.mapContainerWrapper);
648
+ }
649
+ }
650
+ }
651
+ }
652
+ });
653
+ },
654
+
655
+ setupPanningCache () {
656
+ const
657
+ {container, mapContainer, owner} = this,
658
+ {z} = owner;
659
+
660
+ this.mapContainerWrapper = new Container();
661
+ this.mapContainerWrapper.addChild(mapContainer);
662
+
663
+ this.updateRegion(0);
664
+ this.render = this.renderCache;
665
+ this.cacheAll = false;
666
+
667
+ this.cacheTexture = RenderTexture.create({
668
+ width: this.cacheWidth,
669
+ height: this.cacheHeight
670
+ });
671
+
672
+ this.tilesSprite = new Sprite(this.cacheTexture);
673
+ this.tilesSprite.scale.x = this.scaleX;
674
+ this.tilesSprite.scale.y = this.scaleY;
675
+ this.tilesSprite.z = z;
676
+
677
+ // Set up copy buffer and circular pointers
678
+ this.cacheTexture.alternate = RenderTexture.create({
679
+ width: this.cacheWidth,
680
+ height: this.cacheHeight
681
+ });
682
+ this.tilesSpriteCache = new Sprite(this.cacheTexture.alternate);
683
+
684
+ this.cacheTexture.alternate.alternate = this.cacheTexture;
685
+ container.addChild(this.tilesSprite);
686
+
687
+ return createTeardown(this, {
688
+ 'camera-update': function (camera) {
689
+ const
690
+ {buffer, cache, cachePixels, laxCam, refreshBuffer} = this,
691
+ {viewport: vp} = camera,
692
+ resized = (buffer && ((vp.width !== laxCam.width) || (vp.height !== laxCam.height))),
693
+ tempC = tempCache;
694
+
695
+ this.convertCamera(vp);
696
+
697
+ if ((cachePixels.empty || !cachePixels.contains(laxCam)) && (this.imageMap.length > 0)) {
698
+ if (resized) {
699
+ this.updateBufferRegion(laxCam);
700
+ }
701
+
702
+ const
703
+ ctw = this.cacheTilesWidth - 1,
704
+ cth = this.cacheTilesHeight - 1,
705
+ ctw2 = ctw / 2,
706
+ cth2 = cth / 2;
707
+
708
+ //only attempt to draw children that are relevant
709
+ tempC.setAll(Math.round((laxCam.x - this.left) / this.tileWidth - ctw2) + ctw2, Math.round((laxCam.y - this.top) / this.tileHeight - cth2) + cth2, ctw, cth);
710
+ if (tempC.left < 0) {
711
+ tempC.moveX(tempC.halfWidth);
712
+ } else if (tempC.right > this.tilesWidth - 1) {
713
+ tempC.moveX(this.tilesWidth - 1 - tempC.halfWidth);
714
+ }
715
+ if (tempC.top < 0) {
716
+ tempC.moveY(tempC.halfHeight);
717
+ } else if (tempC.bottom > this.tilesHeight - 1) {
718
+ tempC.moveY(this.tilesHeight - 1 - tempC.halfHeight);
719
+ }
720
+
721
+ this.tilesSpriteCache.texture = this.cacheTexture;
722
+ this.cacheTexture = this.cacheTexture.alternate;
723
+ this.tilesSprite.texture = this.cacheTexture;
724
+
725
+ this.populateTiles(tempC, cache);
726
+ this.renderCache(tempC, this.cacheTexture, this.mapContainer, this.mapContainerWrapper, this.tilesSpriteCache, cache);
727
+ cache.set(tempC);
728
+
729
+ // Store pixel bounding box for checking later.
730
+ 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);
731
+ }
732
+
733
+ this.tilesSprite.x = vp.left - laxCam.left + cache.left * this.tileWidth + this.left;
734
+ this.tilesSprite.y = vp.top - laxCam.top + cache.top * this.tileHeight + this.top;
735
+ },
736
+ 'handle-render': function () {
737
+ if (this.updateCache && !this.cache.empty) {
738
+ this.updateCache = false;
739
+ this.populateTiles(this.cache, null);
740
+ this.renderCacheClear(this.cache, this.cacheTexture, this.mapContainer, this.mapContainerWrapper);
741
+ this.tilesSpriteCache.texture = this.cacheTexture;
742
+ this.cachePixels.setAll(
743
+ (this.cache.x + 0.5) * this.tileWidth + this.left,
744
+ (this.cache.y + 0.5) * this.tileHeight + this.top,
745
+ (this.cache.width + 1) * this.tileWidth,
746
+ (this.cache.height + 1) * this.tileHeight
747
+ );
748
+ }
749
+ }
750
+ });
751
+ },
752
+
753
+ setupNoCache () {
754
+ const
755
+ {container, left, mapContainer, owner, scaleX, scaleY, top} = this,
756
+ {z} = owner;
757
+
758
+ this.updateRegion(0);
759
+
760
+ mapContainer.scale.x = scaleX;
761
+ mapContainer.scale.y = scaleY;
762
+ mapContainer.x = left;
763
+ mapContainer.y = top;
764
+ mapContainer.z = z;
765
+ container.addChild(mapContainer);
766
+
767
+ this.buffer = 0; // prevents buffer logic from running if tiles aren't being cached.
768
+ this.cacheAll = false; // so tiles are updated as camera moves.
769
+
770
+ return createTeardown(this, {
771
+ 'camera-update': function (camera) {
772
+ const
773
+ {buffer, cache, cachePixels, laxCam} = this,
774
+ {viewport: vp} = camera,
775
+ resized = (buffer && ((vp.width !== laxCam.width) || (vp.height !== laxCam.height))),
776
+ tempC = tempCache;
777
+
778
+ this.convertCamera(vp);
779
+
780
+ if ((cachePixels.empty || !cachePixels.contains(laxCam)) && (this.imageMap.length > 0)) {
781
+ if (resized) {
782
+ this.updateBufferRegion(laxCam);
783
+ }
784
+
785
+ const
786
+ ctw = this.cacheTilesWidth - 1,
787
+ cth = this.cacheTilesHeight - 1,
788
+ ctw2 = ctw / 2,
789
+ cth2 = cth / 2;
790
+
791
+ //only attempt to draw children that are relevant
792
+ tempC.setAll(Math.round((laxCam.x - this.left) / this.tileWidth - ctw2) + ctw2, Math.round((laxCam.y - this.top) / this.tileHeight - cth2) + cth2, ctw, cth);
793
+ if (tempC.left < 0) {
794
+ tempC.moveX(tempC.halfWidth);
795
+ } else if (tempC.right > this.tilesWidth - 1) {
796
+ tempC.moveX(this.tilesWidth - 1 - tempC.halfWidth);
797
+ }
798
+ if (tempC.top < 0) {
799
+ tempC.moveY(tempC.halfHeight);
800
+ } else if (tempC.bottom > this.tilesHeight - 1) {
801
+ tempC.moveY(this.tilesHeight - 1 - tempC.halfHeight);
802
+ }
803
+
804
+ this.populateTiles(tempC, null);
805
+ cache.set(tempC);
806
+
807
+ // Store pixel bounding box for checking later.
808
+ 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);
809
+ }
810
+ },
811
+ 'handle-render': function () {
812
+ if (this.updateCache) {
813
+ this.updateCache = false;
814
+ // Invalidate cachePixels so camera-update immediately re-runs
815
+ // populateTiles on the next tick rather than skipping because
816
+ // the old region still appears valid.
817
+ this.cachePixels.empty = true;
818
+ }
819
+ }
820
+ });
821
+ },
822
+
823
+ cacheSprite: function (entity) {
824
+ const
825
+ object = entity.cacheRender;
826
+
827
+ // Determine whether to merge this image with the background.
828
+ if (this.entityCache && object) {
829
+ const
830
+ {x: boundsX, y: boundsY, width, height} = object.getBounds(object.transformMatrix),
831
+ offsetX = boundsX - this.left,
832
+ offsetY = boundsY - this.top,
833
+ top = Math.max(0, Math.floor(offsetY / this.tileHeight)),
834
+ bottom = Math.min(this.tilesHeight, Math.ceil((offsetY + height) / this.tileHeight)),
835
+ left = Math.max(0, Math.floor(offsetX / this.tileWidth)),
836
+ right = Math.min(this.tilesWidth, Math.ceil((offsetX + width) / this.tileWidth));
837
+
838
+ if (!this.doMap) {
839
+ this.doMap = arrayCache.setUp();
840
+ this.cachedDisplayObjects = arrayCache.setUp();
841
+ this.populate = this.populateTilesAndEntities;
842
+ }
843
+ this.cachedDisplayObjects.push(object);
844
+
845
+ // Find tiles that should include this display object
846
+ for (let x = left; x < right; x++) {
847
+ if (!this.doMap[x]) {
848
+ this.doMap[x] = arrayCache.setUp();
849
+ }
850
+ for (let y = top; y < bottom; y++) {
851
+ if (!this.doMap[x][y]) {
852
+ this.doMap[x][y] = arrayCache.setUp();
853
+ }
854
+ this.doMap[x][y].push(object);
855
+ }
856
+ }
857
+
858
+ // Prevent subsequent draws
859
+ entity.removeFromParentContainer();
860
+
861
+ 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).
862
+ }
863
+ },
864
+
865
+ uncacheSprite: function (entity) {
866
+ const
867
+ object = entity.cacheRender;
868
+
869
+ // Determine whether to merge this image with the background.
870
+ if (this.entityCache && object) {
871
+ const
872
+ {x: boundsX, y: boundsY, width, height} = object.getBounds(object.transformMatrix),
873
+ index = this.cachedDisplayObjects.indexOf(object),
874
+ offsetX = boundsX - this.left,
875
+ offsetY = boundsY - this.top,
876
+ top = Math.max(0, Math.floor(offsetY / this.tileHeight)),
877
+ bottom = Math.min(this.tilesHeight, Math.ceil((offsetY + height) / this.tileHeight)),
878
+ left = Math.max(0, Math.floor(offsetX / this.tileWidth)),
879
+ right = Math.min(this.tilesWidth, Math.ceil((offsetX + width) / this.tileWidth));
880
+
881
+ if (index >= 0) {
882
+ this.cachedDisplayObjects.splice(index, 1);
883
+ }
884
+
885
+ // Find tiles that should include this display object
886
+ for (let x = left; x < right; x++) {
887
+ for (let y = top; y < bottom; y++) {
888
+ if (this.doMap?.[x]?.[y]) {
889
+ const
890
+ index = this.doMap[x][y].indexOf(object);
891
+
892
+ if (index >= 0) {
893
+ this.doMap[x][y].splice(index, 1);
894
+ }
895
+ }
896
+ }
897
+ }
898
+
899
+ this.updateCache = true;
900
+ }
901
+ },
902
+
903
+ /**
904
+ * Computes a parallax-adjusted camera AABB (`laxCam`) for this layer.
905
+ *
906
+ * For layers smaller than the world, the layer must scroll slower so that
907
+ * it still covers the full world space as the camera travels edge-to-edge.
908
+ * The position is mapped proportionally: when the camera is at the start of
909
+ * its world travel, laxCam is at the start of the layer's travel, and
910
+ * likewise at the end.
911
+ *
912
+ * Tiled `parallaxx` / `parallaxy` factors are then applied relative to the
913
+ * map's parallax origin so the layer's effective render position is:
914
+ * `layerPos - (camera - parallaxOrigin) * (1 - parallaxFactor)`.
915
+ *
916
+ * IMPORTANT: laxCam is resized to the *actual viewport dimensions* (not
917
+ * viewport + refreshBuffer). Its purpose is solely to compute the parallax
918
+ * offset for sprite positioning (vp.left - laxCam.left). Inflating it with
919
+ * refreshBuffer caused sprite offsets to be wrong by `refreshBuffer` pixels
920
+ * on every camera update, breaking parallax for differently-sized layers.
921
+ */
922
+ convertCamera: function (camera) {
923
+ const
924
+ worldWidth = this.worldWidth / this.scaleX,
925
+ worldPosX = worldWidth - camera.width,
926
+ worldHeight = this.worldHeight / this.scaleY,
927
+ worldPosY = worldHeight - camera.height,
928
+ laxCam = this.laxCam,
929
+ {parallaxOriginX, parallaxOriginY, parallaxX, parallaxY} = this;
930
+
931
+ if ((worldWidth === this.layerWidth) || !worldPosX) {
932
+ laxCam.moveX(camera.x);
933
+ } else {
934
+ laxCam.moveX((camera.left - this.left) * (this.layerWidth - camera.width) / worldPosX + camera.halfWidth + this.left);
935
+ }
936
+
937
+ if ((worldHeight === this.layerHeight) || !worldPosY) {
938
+ laxCam.moveY(camera.y);
939
+ } else {
940
+ laxCam.moveY((camera.top - this.top) * (this.layerHeight - camera.height) / worldPosY + camera.halfHeight + this.top);
941
+ }
942
+
943
+ if (parallaxX !== 1) {
944
+ laxCam.moveX(laxCam.x + (camera.x - parallaxOriginX) * (1 - parallaxX));
945
+ }
946
+
947
+ if (parallaxY !== 1) {
948
+ laxCam.moveY(laxCam.y + (camera.y - parallaxOriginY) * (1 - parallaxY));
949
+ }
950
+
951
+ // FIX: resize laxCam to the actual viewport size only — never camera + refreshBuffer.
952
+ if (camera.width !== laxCam.width || camera.height !== laxCam.height) {
953
+ laxCam.resize(camera.width, camera.height);
954
+ }
955
+ },
956
+
957
+ createTile: function (imageName) {
958
+ // "tile-1" is empty, so it remains a null reference.
959
+ if (imageName === 'tile-1') {
960
+ return nullTemplate;
961
+ }
962
+
963
+ return Template.setUp(this.tileSpriteSheet, imageName, this.uninitializedTiles);
964
+ },
965
+
966
+ createMap: function (mapDefinition) {
967
+ if (typeof mapDefinition[0][0] !== 'string') { // This is not a map definition: it's an actual RenderTiles map.
968
+ return mapDefinition;
969
+ } else {
970
+ const
971
+ map = arrayCache.setUp();
972
+
973
+ for (let x = 0; x < mapDefinition.length; x++) {
974
+ map[x] = arrayCache.setUp();
975
+ for (let y = 0; y < mapDefinition[x].length; y++) {
976
+ this.updateTile(mapDefinition[x][y], map, x, y);
977
+ }
978
+ }
979
+
980
+ return map;
981
+ }
982
+ },
983
+
984
+ updateTile: function (index, map, x, y) {
985
+ const
986
+ id = index.id ?? index,
987
+ {tiles} = this,
988
+ tile = tiles[id];
989
+
990
+ if (!tile && (tile !== null)) { // Empty grid spaces are null, so we needn't create a new tile.
991
+ map[x][y] = tiles[id] = this.createTile(id);
992
+ } else {
993
+ map[x][y] = tile;
994
+ }
995
+ },
996
+
997
+ createGrid: function (container) {
998
+ const
999
+ {edgeBleed: extrusion, edgesBleed: outerMargin} = this,
1000
+ ch = this.cacheHeight,
1001
+ cw = this.cacheWidth,
1002
+ cth = this.cacheTilesHeight,
1003
+ ctw = this.cacheTilesWidth,
1004
+ sx = this.scaleX,
1005
+ sy = this.scaleY,
1006
+ th = this.tileHeight,
1007
+ tw = this.tileWidth,
1008
+ tsh = this.tilesHeight,
1009
+ tsw = this.tilesWidth,
1010
+ cg = arrayCache.setUp();
1011
+ let ct = null,
1012
+ z = this.owner.z;
1013
+
1014
+ for (let x = 0; x < tsw; x += ctw) {
1015
+ const
1016
+ col = arrayCache.setUp();
1017
+ cg.push(col);
1018
+ for (let y = 0; y < tsh; y += cth) {
1019
+ // This prevents us from using too large of a cache for the right and bottom edges of the map.
1020
+ const
1021
+ width = Math.min(getPowerOfTwo((tsw - x) * tw + outerMargin), cw),
1022
+ height = Math.min(getPowerOfTwo((tsh - y) * th + outerMargin), ch),
1023
+ rt = RenderTexture.create({
1024
+ width,
1025
+ height
1026
+ });
1027
+
1028
+ rt.frame = new Rectangle(extrusion, extrusion, (((width - outerMargin) / tw) >> 0) * tw + extrusion, (((height - outerMargin) / th) >> 0) * th + extrusion);
1029
+ ct = new Sprite(rt);
1030
+ ct.z = z;
1031
+ ct.scale.x = sx;
1032
+ ct.scale.y = sy;
1033
+ col.push(ct);
1034
+ container.addChild(ct);
1035
+
1036
+ z -= 0.000001; // so that tiles of large caches overlap consistently.
1037
+ }
1038
+ }
1039
+
1040
+ return cg;
1041
+ },
1042
+
1043
+ updateRegion: function (margin) {
1044
+ const
1045
+ {edgesBleed} = this,
1046
+ tw = this.tileWidth * this.scaleX,
1047
+ th = this.tileHeight * this.scaleY,
1048
+ ctw = Math.min(this.tilesWidth, ((this.cacheWidth - edgesBleed) / tw) >> 0),
1049
+ cth = Math.min(this.tilesHeight, ((this.cacheHeight - edgesBleed) / th) >> 0);
1050
+
1051
+ if (!ctw) {
1052
+ 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.');
1053
+ }
1054
+ if (!cth) {
1055
+ 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.');
1056
+ }
1057
+
1058
+ this.cacheTilesWidth = ctw;
1059
+ this.cacheTilesHeight = cth;
1060
+ this.cacheClipWidth = ctw * tw;
1061
+ this.cacheClipHeight = cth * th;
1062
+
1063
+ if (this.tileCache) {
1064
+ this.mapContainer.mask = new Graphics().rect(0, 0, this.cacheClipWidth + margin, this.cacheClipHeight + margin).fill(0x000000);
1065
+ }
1066
+ },
1067
+
1068
+ updateBufferRegion: function (viewport) {
1069
+ const
1070
+ tw = this.tileWidth * this.scaleX,
1071
+ th = this.tileHeight * this.scaleY;
1072
+
1073
+ this.cacheTilesWidth = Math.min(this.tilesWidth, Math.ceil((viewport.width + this.buffer * 2) / tw), (this.cacheWidth / tw) >> 0);
1074
+ this.cacheTilesHeight = Math.min(this.tilesHeight, Math.ceil((viewport.height + this.buffer * 2) / th), (this.cacheHeight / th) >> 0);
1075
+
1076
+ this.cacheClipWidth = this.cacheTilesWidth * tw;
1077
+ this.cacheClipHeight = this.cacheTilesHeight * th;
1078
+
1079
+ this.mapContainer.mask = new Graphics().rect(0, 0, this.cacheClipWidth, this.cacheClipHeight).fill(0x000000);
1080
+ },
1081
+
1082
+ populateTiles: function (bounds, oldBounds) {
1083
+ const
1084
+ tiles = arrayCache.setUp();
1085
+
1086
+ this.tileContainer.removeChildren();
1087
+ for (let x = bounds.left; x <= bounds.right; x++) {
1088
+ for (let y = bounds.top; y <= bounds.bottom; y++) {
1089
+ if (!oldBounds || oldBounds.empty || (y > oldBounds.bottom) || (y < oldBounds.top) || (x > oldBounds.right) || (x < oldBounds.left)) {
1090
+ for (let layer = 0; layer < this.imageMap.length; layer++) {
1091
+ const
1092
+ tile = this.imageMap[layer][x][y].getNext();
1093
+
1094
+ if (tile) {
1095
+ if (tile.template) {
1096
+ tiles.push(tile.template);
1097
+ }
1098
+ tile.x = (x + 0.5) * this.tileWidth;
1099
+ tile.y = (y + 0.5) * this.tileHeight;
1100
+ this.tileContainer.addChild(tile);
1101
+ }
1102
+ }
1103
+ }
1104
+ }
1105
+ }
1106
+
1107
+ // Clear out tile instances
1108
+ for (let z = 0; z < tiles.length; z++) {
1109
+ tiles[z].clear();
1110
+ }
1111
+ arrayCache.recycle(tiles);
1112
+ },
1113
+
1114
+ populateTilesAndEntities: function (bounds, oldBounds) {
1115
+ const
1116
+ ents = arrayCache.setUp(),
1117
+ tiles = arrayCache.setUp();
1118
+
1119
+ this.tileContainer.removeChildren();
1120
+ for (let x = bounds.left; x <= bounds.right; x++) {
1121
+ for (let y = bounds.top; y <= bounds.bottom; y++) {
1122
+ if (!oldBounds || oldBounds.empty || (y > oldBounds.bottom) || (y < oldBounds.top) || (x > oldBounds.right) || (x < oldBounds.left)) {
1123
+ // draw tiles
1124
+ for (let layer = 0; layer < this.imageMap.length; layer++) {
1125
+ const
1126
+ tile = this.imageMap[layer][x][y].getNext();
1127
+
1128
+ if (tile) {
1129
+ if (tile.template) {
1130
+ tiles.push(tile.template);
1131
+ }
1132
+ tile.x = (x + 0.5) * this.tileWidth;
1133
+ tile.y = (y + 0.5) * this.tileHeight;
1134
+ this.tileContainer.addChild(tile);
1135
+ }
1136
+ }
1137
+
1138
+ // check for cached entities
1139
+ if (this.doMap[x] && this.doMap[x][y]) {
1140
+ const
1141
+ oList = this.doMap[x][y];
1142
+
1143
+ for (let z = 0; z < oList.length; z++) {
1144
+ if (!oList[z].drawn) {
1145
+ oList[z].drawn = true;
1146
+ ents.push(oList[z]);
1147
+ }
1148
+ }
1149
+ }
1150
+ }
1151
+ }
1152
+ }
1153
+
1154
+ this.mapContainer.removeChildren();
1155
+ this.mapContainer.addChild(this.tileContainer);
1156
+
1157
+ // Draw cached entities
1158
+ if (ents.length) {
1159
+ ents.sort(sort);
1160
+ for (let z = 0; z < ents.length; z++) {
1161
+ const
1162
+ ent = ents[z];
1163
+
1164
+ delete ent.drawn;
1165
+ this.mapContainer.addChild(ent);
1166
+ if (ent.mask) {
1167
+ this.mapContainer.addChild(ent.mask);
1168
+ }
1169
+ }
1170
+ }
1171
+
1172
+ // Clear out tile instances
1173
+ for (let z = 0; z < tiles.length; z++) {
1174
+ tiles[z].clear();
1175
+ }
1176
+
1177
+ arrayCache.recycle(tiles);
1178
+ arrayCache.recycle(ents);
1179
+ },
1180
+
1181
+ renderCache: function (bounds, renderTexture, src, wrapper, oldCache, oldBounds) {
1182
+ const
1183
+ {renderer} = this;
1184
+
1185
+ if (oldCache && oldBounds && !oldBounds.empty) {
1186
+ oldCache.x = oldBounds.left * this.tileWidth;
1187
+ oldCache.y = oldBounds.top * this.tileHeight;
1188
+ src.addChild(oldCache); // To copy last rendering over.
1189
+ }
1190
+
1191
+ src.x = -bounds.left * this.tileWidth;
1192
+ src.y = -bounds.top * this.tileHeight;
1193
+ renderer.render({
1194
+ container: wrapper,
1195
+ target: renderTexture
1196
+ });
1197
+ renderTexture.requiresUpdate = true;
1198
+ },
1199
+
1200
+ renderCacheClear: function (bounds, renderTexture, src, wrapper) {
1201
+ const
1202
+ {renderer} = this;
1203
+
1204
+ src.x = -bounds.left * this.tileWidth;
1205
+ src.y = -bounds.top * this.tileHeight;
1206
+ renderer.render({
1207
+ container: wrapper,
1208
+ target: renderTexture,
1209
+ clear: true
1210
+ });
1211
+ renderTexture.requiresUpdate = true;
1212
+ },
1213
+
1214
+ renderCacheWithExtrusion: function (bounds, renderTexture, src, wrapper) {
1215
+ const
1216
+ extrusion = 1,
1217
+ border = new Graphics(),
1218
+ {renderer} = this,
1219
+ rendering = {
1220
+ container: wrapper,
1221
+ target: renderTexture
1222
+ };
1223
+
1224
+ // 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.
1225
+ border.rect(0.5, 0.5, this.cacheClipWidth + 1, this.cacheClipHeight + 1);
1226
+ border.stroke({
1227
+ width: 1,
1228
+ color: 0x000000
1229
+ });
1230
+
1231
+ // 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.
1232
+ wrapper.mask = border;
1233
+ src.x = -bounds.left * this.tileWidth;
1234
+ src.y = -bounds.top * this.tileHeight + extrusion;
1235
+ renderer.render(rendering);
1236
+ src.x = -bounds.left * this.tileWidth + extrusion;
1237
+ src.y = -bounds.top * this.tileHeight;
1238
+ renderer.render(rendering);
1239
+ src.x = -bounds.left * this.tileWidth + extrusion * 2;
1240
+ src.y = -bounds.top * this.tileHeight + extrusion;
1241
+ renderer.render(rendering);
1242
+ src.x = -bounds.left * this.tileWidth + extrusion;
1243
+ src.y = -bounds.top * this.tileHeight + extrusion * 2;
1244
+ renderer.render(rendering);
1245
+ wrapper.mask = null;
1246
+ src.x = -bounds.left * this.tileWidth + extrusion;
1247
+ src.y = -bounds.top * this.tileHeight + extrusion;
1248
+ renderer.render(rendering);
1249
+ renderTexture.requiresUpdate = true;
1250
+ },
1251
+
1252
+ toJSON: function () {
1253
+ const
1254
+ imageMap = this.imageMap[0],
1255
+ imgMap = [];
1256
+ let x = imageMap.length;
1257
+
1258
+ while (x--) {
1259
+ let y = imageMap[x].length;
1260
+
1261
+ imgMap[x] = [];
1262
+ while (y--) {
1263
+ imgMap[x][y] = imageMap[x][y].id;
1264
+ }
1265
+ }
1266
+
1267
+ return {
1268
+ type: 'RenderTiles',
1269
+ buffer: this.buffer,
1270
+ cacheAll: this.cacheAll,
1271
+ entityCache: this.entityCache,
1272
+ imageMap: imgMap,
1273
+ maximumBuffer: this.maximumBuffer,
1274
+ scaleX: this.scaleX,
1275
+ scaleY: this.scaleY,
1276
+ spriteSheet: this.spriteSheet,
1277
+ tileCache: this.tileCache,
1278
+ tileHeight: this.tileHeight,
1279
+ tileWidth: this.tileWidth,
1280
+ top: this.top,
1281
+ left: this.left
1282
+ };
1283
+ },
1284
+
1285
+ destroy: function () {
1286
+ const
1287
+ grid = this.cacheGrid,
1288
+ map = this.doMap,
1289
+ img = this.imageMap,
1290
+ keys = Object.keys(this.tiles),
1291
+ {length} = keys;
1292
+
1293
+ if (grid) {
1294
+ for (let x = 0; x < grid.length; x++) {
1295
+ for (let y = 0; y < grid[x].length; y++) {
1296
+ grid[x][y].texture.destroy(true);
1297
+ this.container.removeChild(grid[x][y]);
1298
+ }
1299
+ }
1300
+ arrayCache.recycle(grid, 2);
1301
+ delete this.cacheGrid;
1302
+ } else if (this.tilesSprite) {
1303
+ if (this.tilesSprite.texture.alternate) {
1304
+ this.tilesSprite.texture.alternate.destroy(true);
1305
+ }
1306
+ this.tilesSprite.texture.destroy(true);
1307
+ this.container.removeChild(this.tilesSprite);
1308
+ } else {
1309
+ this.container.removeChild(this.mapContainer);
1310
+ }
1311
+
1312
+ arrayCache.recycle(img, 2);
1313
+
1314
+ for (let i = 0; i < length; i++) {
1315
+ this.tiles[keys[i]].destroy();
1316
+ }
1317
+ this.tiles = null;
1318
+ this.container = null;
1319
+ this.tilesSprite = null;
1320
+ this.spriteSheet.recycleSpriteSheet();
1321
+
1322
+ if (map) {
1323
+ for (let x = 0; x < this.cachedDisplayObjects.length; x++) {
1324
+ this.cachedDisplayObjects[x].destroy();
1325
+ }
1326
+ arrayCache.recycle(this.cachedDisplayObjects);
1327
+
1328
+ for (let x = 0; x < map.length; x++) {
1329
+ if (map[x]) {
1330
+ for (let y = 0; y < map[x].length; y++) {
1331
+ if (map[x][y]) {
1332
+ arrayCache.recycle(map[x][y]);
1333
+ }
1334
+ }
1335
+ arrayCache.recycle(map[x]);
1336
+ }
1337
+ }
1338
+ arrayCache.recycle(map);
1339
+ }
1340
+
1341
+ this.laxCam.recycle();
1342
+ this.cache.recycle();
1343
+ this.cachePixels.recycle();
1344
+ arrayCache.recycle(this.uninitializedTiles);
1345
+
1346
+ this.teardown?.();
1347
+ }
1348
+ },
1349
+
1350
+ publicMethods: {
1351
+ getMapDimensions: function () {
1352
+ return {
1353
+ tilesWidth: this.tilesWidth,
1354
+ tilesHeight: this.tilesHeight,
1355
+ layerWidth: this.layerWidth,
1356
+ layerHeight: this.layerHeight
1357
+ }
1358
+ },
1359
+ getTileAt: function (x, y, z) {
1360
+ return this.imageMap[z][x][y];
1361
+ },
1362
+ getRootTileIndex: function (tileId) {
1363
+ const nonTransformData = ~(0x80000000 + 0x40000000 + 0x20000000);
1364
+ let index = tileId;
1365
+
1366
+ if (typeof tileId === 'string') {
1367
+ index = +tileId.slice(('tile').length);
1368
+ }
1369
+
1370
+ return index & nonTransformData;
1371
+ },
1372
+ getTileVariants: function (tileId) {
1373
+ // FIX: each transform flag must use its own distinct bitmask constant.
1374
+ // Previously all three were 0x80000000 (X-flip), making variants 2-7
1375
+ // all duplicates of each other. Correct values:
1376
+ // transformA = 0x80000000 (X-flip / horizontal mirror)
1377
+ // transformB = 0x40000000 (Y-flip / vertical mirror)
1378
+ // transformC = 0x20000000 (diagonal / rotate)
1379
+ const transformA = 0x80000000,
1380
+ transformB = 0x40000000,
1381
+ transformC = 0x20000000;
1382
+ let index = this.getRootTileIndex(tileId);
1383
+
1384
+ return [
1385
+ index,
1386
+ index + transformA,
1387
+ index + transformB,
1388
+ index + transformC,
1389
+ index + transformA + transformB,
1390
+ index + transformA + transformC,
1391
+ index + transformB + transformC,
1392
+ index + transformA + transformB + transformC
1393
+ ];
1394
+ }
1395
+ },
1396
+
1397
+ getAssetList: (function () {
1398
+ const
1399
+ getImages = function (ss, spriteSheets) {
1400
+ if (ss) {
1401
+ if (typeof ss === 'string') {
1402
+ return getImages(spriteSheets[ss], spriteSheets);
1403
+ } else if (ss.images) {
1404
+ return greenSlice(ss.images);
1405
+ }
1406
+ }
1407
+
1408
+ return arrayCache.setUp();
1409
+ };
1410
+
1411
+ return function (component, props, defaultProps) {
1412
+ const
1413
+ spriteSheets = platypus.game.settings.spriteSheets,
1414
+ ss = component?.spriteSheet ?? props?.spriteSheet ?? defaultProps?.spriteSheet;
1415
+
1416
+ if (ss) {
1417
+ if (typeof ss === 'string' && (ss !== 'import')) {
1418
+ return getImages(ss, spriteSheets);
1419
+ } else if (Array.isArray(ss)) {
1420
+ const
1421
+ images = arrayCache.setUp();
1422
+ let i = ss.length;
1423
+
1424
+ while (i--) {
1425
+ const
1426
+ arr = getImages(ss[i], spriteSheets);
1427
+
1428
+ union(images, arr);
1429
+ arrayCache.recycle(arr);
1430
+ }
1431
+ return images;
1432
+ } else if (ss.images) {
1433
+ return greenSlice(ss.images);
1434
+ }
1435
+ }
1436
+
1437
+ return arrayCache.setUp();
1438
+ };
1439
+ }())
1440
+ });