littlejsengine 1.19.3 → 1.20.0

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 (50) hide show
  1. package/FAQ.md +841 -820
  2. package/README.md +29 -25
  3. package/dist/littlejs.d.ts +2455 -935
  4. package/dist/littlejs.esm.js +30344 -21110
  5. package/dist/littlejs.esm.min.js +4 -3
  6. package/dist/littlejs.js +30259 -21052
  7. package/dist/littlejs.min.js +2 -2
  8. package/dist/littlejs.release.js +26567 -20455
  9. package/package.json +10 -5
  10. package/plugins/audioEffects.js +28 -24
  11. package/plugins/box2d.js +2516 -2073
  12. package/plugins/drawUtilities.js +206 -31
  13. package/plugins/gltf.js +740 -0
  14. package/plugins/jsconfig.json +19 -0
  15. package/plugins/lightSystem.js +719 -332
  16. package/plugins/math3d.js +58 -17
  17. package/plugins/medalSystem.js +326 -280
  18. package/plugins/newgrounds.js +400 -199
  19. package/plugins/pathFinder.js +208 -175
  20. package/plugins/pluginExport.js +15 -0
  21. package/plugins/postProcess.js +56 -13
  22. package/plugins/render3d.js +1643 -1577
  23. package/plugins/render3dDebug.js +109 -0
  24. package/plugins/render3dExtras.js +1754 -0
  25. package/plugins/render3dVoxels.js +502 -0
  26. package/plugins/textureSheet.js +496 -458
  27. package/plugins/threejs.js +183 -163
  28. package/plugins/tweakables.js +411 -0
  29. package/plugins/tweenSystem.js +263 -145
  30. package/plugins/uiSystem.js +2024 -1687
  31. package/plugins/zzfxm.js +159 -159
  32. package/src/engine.js +191 -75
  33. package/src/engineAudio.js +229 -125
  34. package/src/engineBuild.mjs +289 -256
  35. package/src/engineDebug.js +912 -742
  36. package/src/engineDraw.js +295 -193
  37. package/src/engineEditor.js +2431 -0
  38. package/src/engineExport.js +12 -0
  39. package/src/engineInput.js +1462 -1293
  40. package/src/engineMath.js +1274 -1252
  41. package/src/engineObject.js +194 -80
  42. package/src/engineParticles.js +583 -532
  43. package/src/engineRelease.js +67 -45
  44. package/src/engineSettings.js +77 -29
  45. package/src/engineTileLayer.js +995 -741
  46. package/src/engineUtilities.js +19 -13
  47. package/src/engineWebGL.js +239 -82
  48. package/src/jsconfig.json +17 -13
  49. package/plugins/box2d.wasm.js +0 -630
  50. package/plugins/box2d.wasm.wasm +0 -0
@@ -1,280 +1,326 @@
1
- /**
2
- * LittleJS Medal System
3
- * - Achievement/trophy system for games
4
- * - Medal class with name, description, icon, and unlock tracking
5
- * - Automatic saving to local storage
6
- * - Visual display queue with slide-in notifications
7
- * - Newgrounds API integration for online achievements
8
- * - Debug mode to unlock/reset medals during development
9
- * @namespace Medals
10
- */
11
-
12
- 'use strict';
13
-
14
- let debugMedals = false;
15
-
16
- ///////////////////////////////////////////////////////////////////////////////
17
- // Medals settings
18
-
19
- /** How long to show medals for in seconds
20
- * @type {number}
21
- * @default
22
- * @memberof Settings */
23
- let medalDisplayTime = 5;
24
-
25
- /** How quickly to slide on/off medals in seconds
26
- * @type {number}
27
- * @default
28
- * @memberof Settings */
29
- let medalDisplaySlideTime = .5;
30
-
31
- /** Size of medal display
32
- * @type {Vector2}
33
- * @default Vector2(640,80)
34
- * @memberof Settings */
35
- let medalDisplaySize = vec2(640, 80);
36
-
37
- /** Set to stop medals from being unlockable (like if cheats are enabled)
38
- * @type {boolean}
39
- * @default
40
- * @memberof Settings */
41
- let medalsPreventUnlock = false;
42
-
43
- /** List of all medals
44
- * @type {Object}
45
- * @memberof Medals */
46
- const medals = {};
47
-
48
- // Engine internal variables not exposed to documentation
49
- let medalsDisplayQueue = [], medalsSaveName, medalsDisplayTimeLast;
50
-
51
- ///////////////////////////////////////////////////////////////////////////////
52
-
53
- /** Initialize medals with a save name used for storage
54
- * - Call this after creating all medals
55
- * - Checks if medals are unlocked
56
- * @param {string} saveName
57
- * @memberof Medals */
58
- function medalsInit(saveName)
59
- {
60
- // check if medals are unlocked
61
- medalsSaveName = saveName;
62
- if (!debugMedals)
63
- {
64
- let saved = {};
65
- try { saved = JSON.parse(localStorage[saveName] || '{}'); }
66
- catch (e) { saved = {}; }
67
- medalsForEach(medal => {
68
- medal.unlocked = !!(saved[medal.id] && saved[medal.id].unlocked);
69
- });
70
- medalsSave();
71
- }
72
-
73
- // engine automatically renders medals
74
- engineAddPlugin(undefined, medalsRender);
75
-
76
- // plugin functions
77
- function medalsRender()
78
- {
79
- if (!medalsDisplayQueue.length) return;
80
-
81
- // update first medal in queue
82
- const medal = medalsDisplayQueue[0];
83
- const time = timeReal - medalsDisplayTimeLast;
84
- if (!medalsDisplayTimeLast)
85
- medalsDisplayTimeLast = timeReal;
86
- else if (time > medalDisplayTime)
87
- {
88
- medalsDisplayTimeLast = 0;
89
- medalsDisplayQueue.shift();
90
- }
91
- else
92
- {
93
- // slide on/off medals
94
- const slideOffTime = medalDisplayTime - medalDisplaySlideTime;
95
- const hidePercent =
96
- time < medalDisplaySlideTime ? 1 - time / medalDisplaySlideTime :
97
- time > slideOffTime ? (time - slideOffTime) / medalDisplaySlideTime : 0;
98
- medal.render(hidePercent);
99
- }
100
- }
101
- }
102
-
103
- /**
104
- * @callback MedalCallbackFunction - Function that processes a medal
105
- * @param {Medal} medal
106
- * @memberof Medals
107
- */
108
-
109
- /** Calls a function for each medal
110
- * @param {MedalCallbackFunction} callback
111
- * @memberof Medals */
112
- function medalsForEach(callback)
113
- { Object.values(medals).forEach(medal=>callback(medal)); }
114
-
115
- /** Reset all medals to locked and persist the cleared catalog
116
- * @memberof Medals */
117
- function medalsReset()
118
- {
119
- medalsForEach(medal => medal.unlocked = false);
120
- medalsSave();
121
- }
122
-
123
- function medalsSave()
124
- {
125
- if (!medalsSaveName) return;
126
- const data = {};
127
- medalsForEach(medal => {
128
- const entry = {
129
- name: medal.name,
130
- description: medal.description,
131
- icon: medal.icon,
132
- unlocked: medal.unlocked,
133
- };
134
- if (medal.image) entry.src = medal.image.src;
135
- data[medal.id] = entry;
136
- });
137
- localStorage[medalsSaveName] = JSON.stringify(data);
138
- }
139
-
140
- ///////////////////////////////////////////////////////////////////////////////
141
-
142
- /**
143
- * Medal - Tracks an unlockable medal
144
- * @memberof Medals
145
- * @example
146
- * // create a medal
147
- * const medal_example = new Medal(0, 'Example Medal', 'More info about the medal goes here.', '🎖️');
148
- *
149
- * // initialize medals
150
- * medalsInit('Example Game');
151
- *
152
- * // unlock the medal
153
- * medal_example.unlock();
154
- */
155
- class Medal
156
- {
157
- /** Create a medal object and adds it to the list of medals
158
- * @param {number} id - The unique identifier of the medal
159
- * @param {string} name - Name of the medal
160
- * @param {string} [description] - Description of the medal
161
- * @param {string} [icon] - Icon for the medal
162
- * @param {string} [src] - Image location for the medal
163
- */
164
- constructor(id, name, description='', icon='🏆', src)
165
- {
166
- ASSERT(id >= 0 && !medals[id]);
167
-
168
- /** @property {number} - The unique identifier of the medal */
169
- this.id = id;
170
-
171
- /** @property {string} - Name of the medal */
172
- this.name = name;
173
-
174
- /** @property {string} - Description of the medal */
175
- this.description = description;
176
-
177
- /** @property {string} - Icon for the medal */
178
- this.icon = icon;
179
-
180
- /** @property {boolean} - Is the medal unlocked? */
181
- this.unlocked = false;
182
-
183
- /** @property {HTMLImageElement|undefined} - Source image for the medal icon, if any */
184
- this.image = undefined;
185
- if (src)
186
- (this.image = new Image).src = src;
187
-
188
- // add this to list of medals
189
- medals[id] = this;
190
- }
191
-
192
- /** Unlocks a medal if not already unlocked */
193
- unlock()
194
- {
195
- if (medalsPreventUnlock || this.unlocked) return;
196
-
197
- ASSERT(medalsSaveName, 'save name must be set');
198
- this.unlocked = true;
199
- medalsSave();
200
- medalsDisplayQueue.push(this);
201
- }
202
-
203
- /** Render a medal
204
- * @param {number} [hidePercent] - How much to slide the medal off screen
205
- */
206
- render(hidePercent=0)
207
- {
208
- const context = mainContext;
209
- const width = min(medalDisplaySize.x, mainCanvasSize.x);
210
- const height = medalDisplaySize.y;
211
- const x = mainCanvasSize.x - width;
212
- const y = -height*hidePercent;
213
- const backgroundColor = hsl(0,0,.9);
214
-
215
- // draw containing rect and clip to that region
216
- context.save();
217
- context.beginPath();
218
- context.fillStyle = backgroundColor.toString();
219
- context.strokeStyle = BLACK.toString();
220
- context.lineWidth = 3;
221
- context.rect(x, y, width, height);
222
- context.fill();
223
- context.stroke();
224
- context.clip();
225
-
226
- // draw the icon
227
- const gap = vec2(.1, .05).scale(height);
228
- const medalDisplayIconSize = height - 2*gap.x;
229
- this.renderIcon(vec2(x + gap.x + medalDisplayIconSize/2, y + height/2), medalDisplayIconSize);
230
-
231
- // draw the name
232
- const nameSize = height*.5;
233
- const descriptionSize = height*.3;
234
- const pos = vec2(x + medalDisplayIconSize + 2*gap.x, y + gap.y*2 + nameSize/2);
235
- const textWidth = width - medalDisplayIconSize - 3*gap.x;
236
- drawTextScreen(this.name, pos, nameSize, BLACK, 0, undefined, 'left', undefined, undefined, textWidth);
237
-
238
- // draw the description
239
- pos.y = y + height - gap.y*2 - descriptionSize/2;
240
- drawTextScreen(this.description, pos, descriptionSize, BLACK, 0, undefined, 'left', undefined, undefined, textWidth);
241
- context.restore();
242
- }
243
-
244
- /** Render the icon for a medal
245
- * @param {Vector2} pos - Screen space position
246
- * @param {number} size - Screen space size
247
- */
248
- renderIcon(pos, size)
249
- {
250
- // draw the image or icon
251
- if (this.image)
252
- mainContext.drawImage(this.image, pos.x-size/2, pos.y-size/2, size, size);
253
- else
254
- drawTextScreen(this.icon, pos, size*.7, BLACK);
255
- }
256
-
257
- }
258
-
259
- ///////////////////////////////////////////////////////////////////////////////
260
- // Medals setting setters
261
-
262
- /** Set how long to show medals for in seconds
263
- * @param {number} time
264
- * @memberof Settings */
265
- function setMedalDisplayTime(time) { medalDisplayTime = time; }
266
-
267
- /** Set how quickly to slide on/off medals in seconds
268
- * @param {number} time
269
- * @memberof Settings */
270
- function setMedalDisplaySlideTime(time) { medalDisplaySlideTime = time; }
271
-
272
- /** Set size of medal display
273
- * @param {Vector2} size
274
- * @memberof Settings */
275
- function setMedalDisplaySize(size) { medalDisplaySize = size.copy(); }
276
-
277
- /** Set to stop medals from being unlockable
278
- * @param {boolean} preventUnlock
279
- * @memberof Settings */
280
- function setMedalsPreventUnlock(preventUnlock) { medalsPreventUnlock = preventUnlock; }
1
+ /**
2
+ * LittleJS Medal System
3
+ * - Achievement/trophy system for games
4
+ * - Medal class with name, description, icon, and unlock tracking
5
+ * - Automatic saving to local storage, unless a service like Newgrounds holds the medal (see Medal.isLocal)
6
+ * - Visual display queue with slide-in notifications
7
+ * - The Newgrounds plugin extends it with NewgroundsMedal, held on the server while logged in
8
+ * - Setting debugMedals = true in the game code before medalsInit skips the load and the save, and in the debug build logs the Newgrounds traffic; it is not exported, so only a script tag build can set it
9
+ * @namespace Medals
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ let debugMedals = false; // see the header
15
+
16
+ ///////////////////////////////////////////////////////////////////////////////
17
+ // Medals settings
18
+
19
+ /** How long to show medals for in seconds
20
+ * @type {number}
21
+ * @default
22
+ * @memberof Settings */
23
+ let medalDisplayTime = 5;
24
+
25
+ /** How quickly to slide on/off medals in seconds
26
+ * @type {number}
27
+ * @default
28
+ * @memberof Settings */
29
+ let medalDisplaySlideTime = .5;
30
+
31
+ /** Size of medal display
32
+ * @type {Vector2}
33
+ * @default Vector2(640,80)
34
+ * @memberof Settings */
35
+ let medalDisplaySize = vec2(640, 80);
36
+
37
+ /** Set to stop medals from being unlockable (like if cheats are enabled)
38
+ * @type {boolean}
39
+ * @default
40
+ * @memberof Settings */
41
+ let medalsPreventUnlock = false;
42
+
43
+ /** List of all medals
44
+ * @type {Object<number, Medal>}
45
+ * @memberof Medals */
46
+ const medals = {};
47
+
48
+ // Engine internal variables not exposed to documentation
49
+ let medalsDisplayQueue = [], medalsSaveName, medalsDisplayTimeLast, medalsRenderAdded;
50
+ let medalsLoadWaiting = false; // medalsInit came before any medal, each one made reads its own unlock
51
+
52
+ ///////////////////////////////////////////////////////////////////////////////
53
+
54
+ /** Initialize medals with a save name used for storage
55
+ * - Call this after creating all medals
56
+ * - Loads which medals are unlocked from the save, and writes the catalog back
57
+ * - A medal a service like Newgrounds holds is left as it is, see Medal.isLocal
58
+ * @param {string} saveName - The localStorage key the medals are kept under, a different one from the game's own
59
+ * readSaveData and writeSaveData, or each would overwrite the other
60
+ * @memberof Medals */
61
+ function medalsInit(saveName)
62
+ {
63
+ medalsSaveName = saveName;
64
+ medalsLoad();
65
+
66
+ // add the medal display once, however often this is called
67
+ if (!medalsRenderAdded)
68
+ engineAddPlugin(undefined, medalsRender);
69
+ medalsRenderAdded = true;
70
+ }
71
+
72
+ // check which local medals are unlocked in the save, and write the catalog back
73
+ function medalsLoad()
74
+ {
75
+ // with no medals made yet, the save is left as it is for them, a game that calls medalsInit first keeps its unlocks;
76
+ // it keeps waiting from then on, so loading again (a dropped Newgrounds session) does not drop medals still to come
77
+ medalsLoadWaiting ||= !Object.keys(medals).length;
78
+ if (debugMedals || !medalsSaveName) return;
79
+ const saved = readSaveData(medalsSaveName);
80
+ ASSERT(Object.keys(saved).every(key=> isNumber(+key)),
81
+ 'the medals save name holds other data, give medalsInit a name of its own');
82
+ medalsForEach(medal=> {
83
+ if (medal.isLocal())
84
+ medal.unlocked = !!saved[medal.id]?.unlocked;
85
+ });
86
+ medalsSave();
87
+ }
88
+
89
+ // show the first medal in the queue, sliding it on and off
90
+ function medalsRender()
91
+ {
92
+ if (!medalsDisplayQueue.length) return;
93
+
94
+ // update first medal in queue
95
+ const medal = medalsDisplayQueue[0];
96
+ const elapsed = timeReal - medalsDisplayTimeLast;
97
+ if (!medalsDisplayTimeLast)
98
+ medalsDisplayTimeLast = timeReal;
99
+ else if (elapsed > medalDisplayTime)
100
+ {
101
+ medalsDisplayTimeLast = 0;
102
+ medalsDisplayQueue.shift();
103
+ }
104
+ else
105
+ {
106
+ // slide on/off medals, the slides share the display time when it is short
107
+ const slideTime = min(medalDisplaySlideTime, medalDisplayTime/2);
108
+ const slideOffTime = medalDisplayTime - slideTime;
109
+ const hidePercent =
110
+ elapsed < slideTime ? 1 - elapsed / slideTime :
111
+ elapsed > slideOffTime ? (elapsed - slideOffTime) / slideTime : 0;
112
+ medal.render(hidePercent);
113
+ }
114
+ }
115
+
116
+ /**
117
+ * @callback MedalCallbackFunction - Function that processes a medal
118
+ * @param {Medal} medal
119
+ * @memberof Medals
120
+ */
121
+
122
+ /** Calls a function for each medal
123
+ * @param {MedalCallbackFunction} callback
124
+ * @memberof Medals */
125
+ function medalsForEach(callback)
126
+ { Object.values(medals).forEach(medal=> callback(medal)); }
127
+
128
+ /** Reset all medals to locked and persist the cleared catalog
129
+ * - A medal a service like Newgrounds holds is left alone, the service has it
130
+ * @memberof Medals */
131
+ function medalsReset()
132
+ {
133
+ medalsForEach(medal=> medal.isLocal() && (medal.unlocked = false));
134
+ if (medalsLoadWaiting && medalsSaveName && !debugMedals)
135
+ {
136
+ // the saved unlocks of medals not made yet are cleared too, they are read when those medals are made
137
+ const saved = readSaveData(medalsSaveName);
138
+ for (const id in saved)
139
+ if (!medals[id] && saved[id] && typeof saved[id] === 'object')
140
+ saved[id].unlocked = false;
141
+ writeSaveData(medalsSaveName, saved);
142
+ }
143
+ medalsSave();
144
+ }
145
+
146
+ // write the local medals to the save, keeping the entries of medals a service holds
147
+ function medalsSave()
148
+ {
149
+ if (debugMedals || !medalsSaveName) return;
150
+ // while medalsInit waits for medals made later, their saved entries are kept for them
151
+ const saved = readSaveData(medalsSaveName);
152
+ const data = medalsLoadWaiting ? {...saved} : {};
153
+ for (const key in saved) // what is not a medal is the game's own, saved under the same name, and stays
154
+ isNumber(+key) || (data[key] = saved[key]);
155
+ medalsForEach(medal=> {
156
+ if (!medal.isLocal())
157
+ {
158
+ // a service holds this medal, its entry stays as it was for when it is local again
159
+ if (saved[medal.id]) data[medal.id] = saved[medal.id];
160
+ return;
161
+ }
162
+ const entry = {
163
+ name: medal.name,
164
+ description: medal.description,
165
+ icon: medal.icon,
166
+ unlocked: medal.unlocked,
167
+ };
168
+ if (medal.image) entry.src = medal.image.src;
169
+ data[medal.id] = entry;
170
+ });
171
+ writeSaveData(medalsSaveName, data);
172
+ }
173
+
174
+ ///////////////////////////////////////////////////////////////////////////////
175
+
176
+ /**
177
+ * Medal - Tracks an unlockable medal
178
+ * @memberof Medals
179
+ * @example
180
+ * // create a medal
181
+ * const medal_example = new Medal(0, 'Example Medal', 'More info about the medal goes here.', '🎖️');
182
+ *
183
+ * // initialize medals
184
+ * medalsInit('Example Game');
185
+ *
186
+ * // unlock the medal
187
+ * medal_example.unlock();
188
+ */
189
+ class Medal
190
+ {
191
+ /** Create a medal and add it to the list of medals
192
+ * @param {number} id - The unique identifier of the medal
193
+ * @param {string} name - Name of the medal
194
+ * @param {string} [description] - Description of the medal
195
+ * @param {string} [icon] - Icon for the medal
196
+ * @param {string} [src] - Image location for the medal
197
+ */
198
+ constructor(id, name, description='', icon='🏆', src)
199
+ {
200
+ ASSERT(isNumber(id) && id >= 0 && !medals[id], 'medal id must be a unique number of 0 or more');
201
+
202
+ /** @property {number} - The unique identifier of the medal */
203
+ this.id = id;
204
+
205
+ /** @property {string} - Name of the medal */
206
+ this.name = name;
207
+
208
+ /** @property {string} - Description of the medal */
209
+ this.description = description;
210
+
211
+ /** @property {string} - Icon for the medal */
212
+ this.icon = icon;
213
+
214
+ /** @property {boolean} - Is the medal unlocked? */
215
+ this.unlocked = false;
216
+
217
+ /** @property {HTMLImageElement|undefined} - Source image for the medal icon, if any
218
+ * @type {HTMLImageElement|undefined} */
219
+ this.image = undefined;
220
+ if (src)
221
+ (this.image = new Image).src = src;
222
+
223
+ // add this to list of medals, unlocked if the save says so when medalsInit came before any medal
224
+ medals[id] = this;
225
+ if (medalsLoadWaiting && !debugMedals && this.isLocal())
226
+ this.unlocked = !!readSaveData(medalsSaveName)[id]?.unlocked;
227
+ }
228
+
229
+ /** Unlocks a medal if not already unlocked
230
+ * - The promise is optional, for when a game wants to know the outcome
231
+ * @return {Promise<boolean>} - Whether the medal is unlocked, right away unless a service like Newgrounds has to confirm */
232
+ unlock()
233
+ {
234
+ if (!medalsPreventUnlock && !this.unlocked)
235
+ {
236
+ ASSERT(medalsSaveName, 'save name must be set');
237
+ this.unlocked = true;
238
+ medalsSave();
239
+ medalsDisplayQueue.push(this);
240
+ }
241
+ return Promise.resolve(this.unlocked);
242
+ }
243
+
244
+ /** Whether the local save holds this medal, it is neither loaded nor written while a service like Newgrounds holds it
245
+ * @return {boolean} */
246
+ isLocal() { return true; }
247
+
248
+ /** Render a medal
249
+ * @param {number} [hidePercent] - How much to slide the medal off screen
250
+ */
251
+ render(hidePercent=0)
252
+ {
253
+ const context = mainContext;
254
+ const width = min(medalDisplaySize.x, mainCanvasSize.x);
255
+ const height = medalDisplaySize.y;
256
+ const x = mainCanvasSize.x - width;
257
+ const y = -height*hidePercent;
258
+ const backgroundColor = hsl(0,0,.9);
259
+
260
+ // draw containing rect and clip to that region
261
+ context.save();
262
+ context.beginPath();
263
+ context.fillStyle = backgroundColor.toString();
264
+ context.strokeStyle = BLACK.toString();
265
+ const lineWidth = context.lineWidth = 3;
266
+ context.rect(x + lineWidth/2, y + lineWidth/2, width - lineWidth, height - lineWidth); // the whole border shows
267
+ context.fill();
268
+ context.stroke();
269
+ context.clip();
270
+
271
+ // draw the icon
272
+ const gap = vec2(.1, .05).scale(height);
273
+ const iconSize = height - 2*gap.x;
274
+ this.renderIcon(vec2(x + gap.x + iconSize/2, y + height/2), iconSize);
275
+
276
+ // draw the name
277
+ const nameSize = height*.5;
278
+ const descriptionSize = height*.3;
279
+ const pos = vec2(x + iconSize + 2*gap.x, y + gap.y*2 + nameSize/2);
280
+ const textWidth = width - iconSize - 3*gap.x;
281
+ drawTextScreen(this.name, pos, nameSize, BLACK, 0, undefined, 'left', undefined, undefined, textWidth);
282
+
283
+ // draw the description
284
+ pos.y = y + height - gap.y*2 - descriptionSize/2;
285
+ drawTextScreen(this.description, pos, descriptionSize, BLACK, 0, undefined, 'left', undefined, undefined, textWidth);
286
+ context.restore();
287
+ }
288
+
289
+ /** Render the icon for a medal
290
+ * @param {Vector2} pos - Screen space position
291
+ * @param {number} size - Screen space size
292
+ */
293
+ renderIcon(pos, size)
294
+ {
295
+ // draw the image once it has loaded, or the icon; a broken image would throw
296
+ const image = this.image;
297
+ if (image && image.complete && image.naturalWidth)
298
+ mainContext.drawImage(image, pos.x-size/2, pos.y-size/2, size, size);
299
+ else
300
+ drawTextScreen(this.icon, pos, size*.7, BLACK);
301
+ }
302
+
303
+ }
304
+
305
+ ///////////////////////////////////////////////////////////////////////////////
306
+ // Medals setting setters
307
+
308
+ /** Set how long to show medals for in seconds
309
+ * @param {number} time
310
+ * @memberof Settings */
311
+ function setMedalDisplayTime(time) { medalDisplayTime = time; }
312
+
313
+ /** Set how quickly to slide on/off medals in seconds
314
+ * @param {number} time
315
+ * @memberof Settings */
316
+ function setMedalDisplaySlideTime(time) { medalDisplaySlideTime = time; }
317
+
318
+ /** Set size of medal display
319
+ * @param {Vector2} size
320
+ * @memberof Settings */
321
+ function setMedalDisplaySize(size) { medalDisplaySize = size.copy(); }
322
+
323
+ /** Set to stop medals from being unlockable
324
+ * @param {boolean} preventUnlock
325
+ * @memberof Settings */
326
+ function setMedalsPreventUnlock(preventUnlock) { medalsPreventUnlock = preventUnlock; }