@makefully/platypus 4.1.10 → 4.1.13

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.
@@ -6,6 +6,176 @@ import {inflate} from 'pako';
6
6
  import TiledLoader from './TiledLoader.js';
7
7
 
8
8
  const
9
+ assertLevelPiece = function (piece, label) {
10
+ if (piece == null) {
11
+ throw new Error(`Level Builder: Level piece "${label}" is undefined. Check that it is defined in platypus.game.settings.levels or levelPieces.`);
12
+ }
13
+
14
+ if (typeof piece !== 'object') {
15
+ throw new Error(`Level Builder: Level piece "${label}" must be a level definition object, received ${typeof piece}.`);
16
+ }
17
+
18
+ if (piece.width == null) {
19
+ throw new Error(`Level Builder: Level piece "${label}" is missing width.`);
20
+ }
21
+
22
+ if (piece.height == null) {
23
+ throw new Error(`Level Builder: Level piece "${label}" is missing height.`);
24
+ }
25
+
26
+ if (piece.tilewidth == null) {
27
+ throw new Error(`Level Builder: Level piece "${label}" is missing tilewidth.`);
28
+ }
29
+
30
+ if (piece.tileheight == null) {
31
+ throw new Error(`Level Builder: Level piece "${label}" is missing tileheight.`);
32
+ }
33
+
34
+ if (piece.layers == null) {
35
+ throw new Error(`Level Builder: Level piece "${label}" is missing a layers array.`);
36
+ }
37
+
38
+ if (!Array.isArray(piece.layers)) {
39
+ throw new Error(`Level Builder: Level piece "${label}" layers must be an array.`);
40
+ }
41
+
42
+ return piece;
43
+ },
44
+ assertLevelTemplate = function (template) {
45
+ if (template == null) {
46
+ throw new Error(`Level Builder: levelTemplate is required but was not provided.`);
47
+ }
48
+
49
+ if (!Array.isArray(template)) {
50
+ throw new Error(`Level Builder: levelTemplate must be an array, received ${typeof template}.`);
51
+ }
52
+
53
+ if (template.length === 0) {
54
+ throw new Error(`Level Builder: levelTemplate cannot be empty.`);
55
+ }
56
+
57
+ let rowLength = null;
58
+
59
+ for (let i = 0; i < template.length; i++) {
60
+ const
61
+ row = template[i];
62
+
63
+ if (typeof row === 'string') {
64
+ if (!row) {
65
+ throw new Error(`Level Builder: levelTemplate[${i}] is an empty string.`);
66
+ }
67
+
68
+ if (rowLength != null && rowLength !== '1d') {
69
+ throw new Error(`Level Builder: levelTemplate is not rectangular: row 0 has ${rowLength} columns but row ${i} is a string.`);
70
+ }
71
+
72
+ rowLength = '1d';
73
+ } else if (Array.isArray(row)) {
74
+ if (row.length === 0) {
75
+ throw new Error(`Level Builder: levelTemplate row ${i} is empty.`);
76
+ }
77
+
78
+ if (rowLength === '1d') {
79
+ throw new Error(`Level Builder: levelTemplate is not rectangular: row 0 is a string but row ${i} is an array.`);
80
+ }
81
+
82
+ if (rowLength == null) {
83
+ rowLength = row.length;
84
+ } else if (rowLength !== row.length) {
85
+ throw new Error(`Level Builder: levelTemplate is not rectangular: row 0 has ${rowLength} columns but row ${i} has ${row.length}.`);
86
+ }
87
+
88
+ for (let j = 0; j < row.length; j++) {
89
+ if (typeof row[j] !== 'string') {
90
+ throw new Error(`Level Builder: levelTemplate[${i}][${j}] must be a piece id string, received ${typeof row[j]}.`);
91
+ }
92
+
93
+ if (!row[j]) {
94
+ throw new Error(`Level Builder: levelTemplate[${i}][${j}] is an empty string.`);
95
+ }
96
+ }
97
+ } else {
98
+ throw new Error(`Level Builder: levelTemplate[${i}] must be a string or array of strings, received ${typeof row}.`);
99
+ }
100
+ }
101
+
102
+ return template;
103
+ },
104
+ assertLevelPieces = function (pieces) {
105
+ if (pieces == null) {
106
+ return;
107
+ }
108
+
109
+ if (typeof pieces !== 'object' || Array.isArray(pieces)) {
110
+ throw new Error(`Level Builder: levelPieces must be an object, received ${Array.isArray(pieces) ? 'array' : typeof pieces}.`);
111
+ }
112
+
113
+ const
114
+ keys = Object.keys(pieces),
115
+ {length} = keys;
116
+
117
+ for (let i = 0; i < length; i++) {
118
+ const
119
+ key = keys[i],
120
+ value = pieces[key];
121
+
122
+ if (typeof value === 'string') {
123
+ if (!value) {
124
+ throw new Error(`Level Builder: levelPieces["${key}"] is an empty string.`);
125
+ }
126
+ } else if (Array.isArray(value)) {
127
+ if (value.length === 0) {
128
+ throw new Error(`Level Builder: levelPieces["${key}"] is an empty array. Provide at least one level piece id.`);
129
+ }
130
+
131
+ for (let j = 0; j < value.length; j++) {
132
+ if (typeof value[j] !== 'string') {
133
+ throw new Error(`Level Builder: levelPieces["${key}"][${j}] must be a string level id, received ${typeof value[j]}.`);
134
+ }
135
+
136
+ if (!value[j]) {
137
+ throw new Error(`Level Builder: levelPieces["${key}"][${j}] is an empty string.`);
138
+ }
139
+ }
140
+ } else {
141
+ throw new Error(`Level Builder: levelPieces["${key}"] must be a string or array of strings, received ${typeof value}.`);
142
+ }
143
+ }
144
+ },
145
+ resolveLevelPiece = function (piece, label) {
146
+ if (typeof piece === 'string') {
147
+ const
148
+ levelDefinitions = platypus.game.settings.levels,
149
+ transformIndex = piece.indexOf(':');
150
+ let levelDefinition = levelDefinitions?.[piece];
151
+
152
+ if (!levelDefinition && transformIndex >= 0) {
153
+ const
154
+ baseLabel = piece.substring(0, transformIndex),
155
+ transform = piece.substring(transformIndex + 1);
156
+
157
+ levelDefinition = levelDefinitions?.[baseLabel];
158
+
159
+ if (!levelDefinition) {
160
+ throw new Error(`Level Builder: Level piece "${baseLabel}" (from "${piece}") was not found in platypus.game.settings.levels.`);
161
+ }
162
+
163
+ if (transform === 'mirror') {
164
+ return levelDefinitions[piece] = mirrorSegment(assertLevelPiece(levelDefinition, baseLabel));
165
+ }
166
+
167
+ throw new Error(`Level Builder: Unknown transform "${transform}" in level piece "${piece}". Supported transforms: mirror.`);
168
+ }
169
+
170
+ if (!levelDefinition) {
171
+ throw new Error(`Level Builder: Level piece "${piece}" was not found in platypus.game.settings.levels.`);
172
+ }
173
+
174
+ return assertLevelPiece(levelDefinition, piece);
175
+ }
176
+
177
+ return assertLevelPiece(piece, label);
178
+ },
9
179
  maskXFlip = 0x80000000,
10
180
  decodeString = (str, index) => (((str.charCodeAt(index)) + (str.charCodeAt(index + 1) << 8) + (str.charCodeAt(index + 2) << 16) + (str.charCodeAt(index + 3) << 24 )) >>> 0),
11
181
  decodeArray = (arr, index) => ((arr[index] + (arr[index + 1] << 8) + (arr[index + 2] << 16) + (arr[index + 3] << 24 )) >>> 0),
@@ -25,7 +195,11 @@ const
25
195
 
26
196
  return arr;
27
197
  },
28
- decodeLayer = function (layer) {
198
+ decodeLayer = function (layer, label) {
199
+ if (layer == null) {
200
+ throw new Error(`Level Builder: Cannot decode layer${label ? ` in "${label}"` : ''}: layer is undefined.`);
201
+ }
202
+
29
203
  if (layer.encoding === 'base64') {
30
204
  layer.data = decodeBase64(layer.data, layer.compression);
31
205
  layer.encoding = 'csv'; // So we won't have to decode again.
@@ -81,8 +255,8 @@ const
81
255
  }
82
256
  return list;
83
257
  },
84
- mergeSegment = function (level, segment, mergeAxis) {
85
- segment = cloneLevelDefinition(segment);
258
+ mergeSegment = function (level, segment, mergeAxis, segmentLabel) {
259
+ segment = cloneLevelDefinition(assertLevelPiece(segment, segmentLabel ?? 'segment'));
86
260
 
87
261
  if (!level.tilewidth && !level.tileheight) {
88
262
  //set level tile size data if it's not already set.
@@ -107,9 +281,13 @@ const
107
281
  }
108
282
 
109
283
  for (let i = 0; i < segment.layers.length; i++) {
284
+ if (segment.layers[i] == null) {
285
+ throw new Error(`Level Builder: Level piece "${segmentLabel ?? 'segment'}" layer ${i} is undefined.`);
286
+ }
287
+
110
288
  if (!level.layers[i]) {
111
289
  //if the level doesn't have a layer yet, we're creating it and then copying it from the segment.
112
- decodeLayer(segment.layers[i]);
290
+ decodeLayer(segment.layers[i], segmentLabel);
113
291
 
114
292
  const
115
293
  layer = level.layers[i] = cloneLevelDefinition(segment.layers[i]);
@@ -126,7 +304,7 @@ const
126
304
  //if the level does have a layer, we're appending the new data to it.
127
305
  if (level.layers[i].data && segment.layers[i].data) {
128
306
  // Make sure we're not trying to merge compressed levels.
129
- decodeLayer(segment.layers[i]);
307
+ decodeLayer(segment.layers[i], segmentLabel);
130
308
 
131
309
  if (mergeAxis === 'horizontal') {
132
310
  level.layers[i].data = mergeData(level.layers[i].data, level.width, segment.layers[i].data, segment.width, level.height, mergeAxis);
@@ -169,7 +347,6 @@ const
169
347
  },
170
348
  mergeLevels = function (levelSegments) {
171
349
  const
172
- levelDefinitions = platypus.game.settings.levels,
173
350
  level = {
174
351
  height: 0,
175
352
  width: 0,
@@ -184,35 +361,25 @@ const
184
361
  layers: []
185
362
  };
186
363
 
364
+ if (!Array.isArray(levelSegments[i])) {
365
+ throw new Error(`Level Builder: mergeLevels row ${i} must be an array of level pieces, received ${typeof levelSegments[i]}.`);
366
+ }
367
+
187
368
  for (let j = 0; j < levelSegments[i].length; j++) {
188
- //Merge horizontally
189
- if (typeof levelSegments[i][j] === 'string') {
190
- const
191
- levelDefinitionLabel = levelSegments[i][j],
192
- transformIndex = levelDefinitionLabel.indexOf(':');
193
- let levelDefinition = levelDefinitions[levelDefinitionLabel];
194
-
195
- // check for transform
196
- if (!levelDefinition && (transformIndex >= 0)) {
197
- const transform = levelDefinitionLabel.substring(transformIndex + 1);
369
+ const
370
+ piece = levelSegments[i][j],
371
+ pieceLabel = typeof piece === 'string' ? piece : `row ${i}, column ${j}`;
198
372
 
199
- levelDefinition = levelDefinitions[levelDefinitionLabel.substring(0, transformIndex)];
200
- if (transform === 'mirror') {
201
- levelDefinition = levelDefinitions[levelDefinitionLabel] = mirrorSegment(levelDefinition);
202
- }
203
- }
204
- mergeSegment(row, levelDefinition, 'horizontal');
205
- } else {
206
- mergeSegment(row, levelSegments[i][j], 'horizontal');
207
- }
373
+ //Merge horizontally
374
+ mergeSegment(row, resolveLevelPiece(piece, pieceLabel), 'horizontal', pieceLabel);
208
375
  }
209
376
  //Then merge vertically
210
- mergeSegment(level, row, 'vertical');
377
+ mergeSegment(level, row, 'vertical', `row ${i}`);
211
378
  }
212
379
  return level;
213
380
  },
214
- mirrorSegment = function (segment) {
215
- segment = cloneLevelDefinition(segment);
381
+ mirrorSegment = function (segment, segmentLabel) {
382
+ segment = cloneLevelDefinition(assertLevelPiece(segment, segmentLabel ?? 'segment'));
216
383
 
217
384
  const
218
385
  newSegment = {
@@ -221,7 +388,11 @@ const
221
388
  width = segment.width * segment.tilewidth;
222
389
 
223
390
  for (let i = 0; i < segment.layers.length; i++) {
224
- decodeLayer(segment.layers[i]);
391
+ if (segment.layers[i] == null) {
392
+ throw new Error(`Level Builder: Level piece "${segmentLabel ?? 'segment'}" layer ${i} is undefined.`);
393
+ }
394
+
395
+ decodeLayer(segment.layers[i], segmentLabel);
225
396
 
226
397
  const
227
398
  fromLayer = segment.layers[i],
@@ -369,6 +540,8 @@ export default createComponentClass(/** @lends platypus.components.LevelBuilder.
369
540
  this.levelMessage.persistentData = data;
370
541
  this.levelTemplate = data?.levelTemplate ?? this.levelTemplate;
371
542
  this.useUniques = data?.useUniques ?? this.useUniques;
543
+ assertLevelPieces(piecesToCopy);
544
+
372
545
  this.levelPieces = {};
373
546
  if (piecesToCopy) {
374
547
  const
@@ -385,32 +558,26 @@ export default createComponentClass(/** @lends platypus.components.LevelBuilder.
385
558
  this.levelPieces[key] = [
386
559
  ...piecesToCopy[key]
387
560
  ];
388
- } else {
389
- throw ('Level Builder: Level pieces of incorrect type: ' + piecesToCopy[key]);
390
561
  }
391
562
  }
392
563
  }
393
564
 
394
565
  if (this.levelTemplate) {
395
- if (this.levelTemplate) {
396
- this.levelMessage.level = [];
397
- for (let i = 0; i < this.levelTemplate.length; i++) {
398
- const
399
- templateRow = this.levelTemplate[i];
400
-
401
- if (typeof templateRow === "string") {
402
- this.levelMessage.level[i] = this.getLevelPiece(templateRow);
403
- } else if (templateRow.length) {
404
- this.levelMessage.level[i] = [];
405
- for (let j = 0; j < templateRow.length; j++) {
406
- this.levelMessage.level[i][j] = this.getLevelPiece(templateRow[j]);
407
- }
408
- } else {
409
- throw ('Level Builder: Template row is neither a string or array. What is it?');
566
+ assertLevelTemplate(this.levelTemplate);
567
+
568
+ this.levelMessage.level = [];
569
+ for (let i = 0; i < this.levelTemplate.length; i++) {
570
+ const
571
+ templateRow = this.levelTemplate[i];
572
+
573
+ if (typeof templateRow === "string") {
574
+ this.levelMessage.level[i] = this.getLevelPiece(templateRow);
575
+ } else {
576
+ this.levelMessage.level[i] = [];
577
+ for (let j = 0; j < templateRow.length; j++) {
578
+ this.levelMessage.level[i][j] = this.getLevelPiece(templateRow[j]);
410
579
  }
411
580
  }
412
- } else {
413
- platypus.debug.warn('Level Builder: Template is not defined');
414
581
  }
415
582
  } else {
416
583
  platypus.debug.warn('Level Builder: There is no level template.');
@@ -452,6 +619,10 @@ export default createComponentClass(/** @lends platypus.components.LevelBuilder.
452
619
 
453
620
  methods: {// These are methods that are called by this component.
454
621
  getLevelPiece: function (type) {
622
+ if (type == null || type === '') {
623
+ throw new Error(`Level Builder: Template references an invalid piece id: ${type}.`);
624
+ }
625
+
455
626
  const
456
627
  pieces = this.levelPieces[type] ?? type;
457
628
 
@@ -178,11 +178,17 @@ export default createComponentClass(/** @lends platypus.components.RenderSprite.
178
178
 
179
179
  getAssetList (a, b, c) {
180
180
  const
181
- image = a?.image ?? b?.image ?? c?.image;
181
+ image = a?.image ?? b?.image ?? c?.image,
182
+ formatImageAsset = (src) => {
183
+ const
184
+ alias = platypus.assetCache?.getFileId ? platypus.assetCache.getFileId(src) : src;
185
+
186
+ return {
187
+ alias: [alias],
188
+ src
189
+ };
190
+ };
182
191
 
183
- return (image ? [image] : a?.images ?? b?.images ?? c?.images ?? []).map((src) => ({
184
- alias: [src],
185
- src
186
- }));
192
+ return (image ? [image] : a?.images ?? b?.images ?? c?.images ?? []).map(formatImageAsset);
187
193
  }
188
194
  });
@@ -379,14 +379,6 @@ export default createComponentClass(/** @lends platypus.components.RenderSpine.p
379
379
  spine = this.spine = new Spine({skeletonData, autoUpdate: false}),
380
380
  map = createAnimationMap(this.animationMap, skeleton.animations);
381
381
 
382
- // Spine 4.3 inherits Pixi container world-transform deltas into skeleton physics by default.
383
- // Platypus pans the camera by moving the world container, which shifts every entity's world
384
- // transform even when owner.x/y are unchanged — causing unwanted physics on stationary characters.
385
- // Use the `apply-spine-physics` event for intentional movement-driven physics instead.
386
- spine.setPhysicsPositionInheritanceFactor(0, 0);
387
- spine.physicsRotationInheritanceFactor = 0;
388
- spine.resetPhysicsTransform();
389
-
390
382
  let animation = '';
391
383
 
392
384
  spine.state.addListener({
@@ -489,12 +489,21 @@ export default createComponentClass(/** @lends platypus.components.RenderSprite.
489
489
 
490
490
  getAssetList: (function () {
491
491
  const
492
+ formatImageAsset = function (src) {
493
+ const
494
+ alias = platypus.assetCache?.getFileId ? platypus.assetCache.getFileId(src) : src;
495
+
496
+ return {
497
+ alias: [alias],
498
+ src
499
+ };
500
+ },
492
501
  getImages = function (ss, spriteSheets) {
493
502
  if (ss) {
494
503
  if (typeof ss === 'string') {
495
504
  return getImages(spriteSheets[ss], spriteSheets);
496
505
  } else if (ss.images) {
497
- return greenSlice(ss.images);
506
+ return greenSlice(ss.images).map(formatImageAsset);
498
507
  }
499
508
  }
500
509
 
package/src/index.js CHANGED
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * @namespace platypus
7
7
  */
8
- /* global global, navigator, window */
8
+ /* global global, navigator */
9
9
 
10
10
  import AssetManager from './AssetManager.js';
11
11
  import {Debugger} from 'springroll';
@@ -46,7 +46,6 @@ const
46
46
  components
47
47
  },
48
48
  uagent = navigator.userAgent.toLowerCase(),
49
- isEdge = (uagent.search('edge') > -1),
50
49
  isIPod = (uagent.search('ipod') > -1),
51
50
  isIPhone = (uagent.search('iphone') > -1),
52
51
  isIPad = (uagent.search('ipad') > -1) || (uagent.search('mac') > -1 && navigator.maxTouchPoints > 2 && !isIPhone),
@@ -109,20 +108,21 @@ const
109
108
 
110
109
  /**
111
110
  * This is an object of boolean key/value pairs describing the current browser's properties.
111
+ * Obsolete capability flags (`touch`, `edge`, `ie`) are fixed to match modern browser support.
112
112
  * @property supports
113
113
  * @type Object
114
114
  **/
115
115
  platypus.supports = {
116
- touch: (window.ontouchstart !== 'undefined'),
117
- edge: isEdge,
116
+ touch: true, // Touch Events are universally available in modern browsers
117
+ edge: false, // Legacy EdgeHTML is no longer supported
118
118
  iPod: isIPod,
119
119
  iPhone: isIPhone,
120
120
  iPad: isIPad,
121
- safari: (uagent.search('safari') > -1) && !isEdge,
122
- ie: (uagent.search('msie') > -1) || (uagent.search('trident') > -1),
121
+ safari: (uagent.search('safari') > -1),
122
+ ie: false, // Internet Explorer is no longer supported
123
123
  firefox: (uagent.search('firefox') > -1),
124
124
  android: isAndroid,
125
- chrome: (uagent.search('chrome') > -1) && !isEdge,
125
+ chrome: (uagent.search('chrome') > -1),
126
126
  silk: isSilk,
127
127
  iOS: isIOS,
128
128
  mobile: isMobile,
package/src/sayHello.js CHANGED
@@ -30,7 +30,7 @@ export default function (app) {
30
30
  author = docAuth ? `by ${docAuth}` : '',
31
31
  title = `${options.name ?? app.name ?? document.title ?? ''}${version ? ` ${version}` : ''}`,
32
32
  using = arrayCache.setUp(),
33
- {springroll, supports} = platypus;
33
+ {springroll} = platypus;
34
34
 
35
35
  if (springroll) {
36
36
  using.push('SpringRoll ' + springroll.version);
@@ -38,13 +38,10 @@ export default function (app) {
38
38
 
39
39
  using.push('Pixi.js ' + pixiVersion);
40
40
 
41
- if (supports.firefox || supports.chrome) {
42
- console.log(`%c ${title} %c ${author}`, getStyle(title, title.substr(title.lastIndexOf(' ') - title.length + 1)), 'line-height: 1.5em;');
43
- using.push(`Platypus ${platypus.version}`);
44
- console.log(`Using %c ${using.join(' %c %c ')} %c `, ...getVersions(using));
45
- } else {
46
- console.log(`--- "${title}" ${author} - Using ${using.join(', ')}, and Platypus ${platypus.version} ---`);
47
- }
41
+ // Styled console (%c) is supported in all modern browsers.
42
+ console.log(`%c ${title} %c ${author}`, getStyle(title, title.substr(title.lastIndexOf(' ') - title.length + 1)), 'line-height: 1.5em;');
43
+ using.push(`Platypus ${platypus.version}`);
44
+ console.log(`Using %c ${using.join(' %c %c ')} %c `, ...getVersions(using));
48
45
 
49
46
  arrayCache.recycle(using);
50
47
  };