littlejsengine 1.18.4 → 1.18.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 (176) hide show
  1. package/COPYRIGHT.txt +38 -0
  2. package/FAQ.md +633 -0
  3. package/README.md +19 -1
  4. package/dist/littlejs.d.ts +354 -55
  5. package/dist/littlejs.esm.js +1531 -489
  6. package/dist/littlejs.esm.min.js +1 -1
  7. package/dist/littlejs.js +1499 -476
  8. package/dist/littlejs.min.js +1 -1
  9. package/dist/littlejs.release.js +1493 -470
  10. package/package.json +8 -1
  11. package/plugins/box2d.js +32 -7
  12. package/{src/engineMedals.js → plugins/medalSystem.js} +248 -196
  13. package/plugins/pathFinder.js +758 -0
  14. package/plugins/pluginExport.js +19 -0
  15. package/plugins/{tween.js → tweenSystem.js} +7 -7
  16. package/plugins/uiSystem.js +186 -25
  17. package/src/engine.js +2 -141
  18. package/src/engineAudio.js +3 -3
  19. package/src/engineBuild.mjs +4 -2
  20. package/src/engineDebug.js +7 -7
  21. package/src/engineDraw.js +26 -8
  22. package/src/engineExport.js +13 -13
  23. package/src/engineInput.js +8 -8
  24. package/src/engineLogo.js +146 -0
  25. package/src/engineMath.js +15 -6
  26. package/src/engineObject.js +11 -5
  27. package/src/engineParticles.js +6 -3
  28. package/src/engineRelease.js +1 -1
  29. package/src/engineSettings.js +0 -47
  30. package/src/engineUtilities.js +13 -8
  31. package/src/engineWebGL.js +34 -7
  32. package/src/jsconfig.json +3 -1
  33. package/.github/workflows/test.yml +0 -17
  34. package/AI.md +0 -173
  35. package/CLAUDE.md +0 -1
  36. package/examples/box2d/game.js +0 -192
  37. package/examples/box2d/gameObjects.js +0 -564
  38. package/examples/box2d/index.html +0 -9
  39. package/examples/box2d/scenes.js +0 -194
  40. package/examples/box2d/tiles.png +0 -0
  41. package/examples/breakout/game.js +0 -175
  42. package/examples/breakout/gameObjects.js +0 -147
  43. package/examples/breakout/index.html +0 -8
  44. package/examples/breakout/tiles.png +0 -0
  45. package/examples/breakoutTutorial/README.md +0 -521
  46. package/examples/breakoutTutorial/game.js +0 -191
  47. package/examples/breakoutTutorial/images/1.png +0 -0
  48. package/examples/breakoutTutorial/images/10.png +0 -0
  49. package/examples/breakoutTutorial/images/11.png +0 -0
  50. package/examples/breakoutTutorial/images/2.png +0 -0
  51. package/examples/breakoutTutorial/images/3.png +0 -0
  52. package/examples/breakoutTutorial/images/4.png +0 -0
  53. package/examples/breakoutTutorial/images/5.png +0 -0
  54. package/examples/breakoutTutorial/images/6.png +0 -0
  55. package/examples/breakoutTutorial/images/7.png +0 -0
  56. package/examples/breakoutTutorial/images/8.png +0 -0
  57. package/examples/breakoutTutorial/images/9.png +0 -0
  58. package/examples/breakoutTutorial/index.html +0 -8
  59. package/examples/electron/build.mjs +0 -127
  60. package/examples/electron/electron.js +0 -35
  61. package/examples/electron/game.js +0 -54
  62. package/examples/electron/index.html +0 -13
  63. package/examples/electron/package.json +0 -5
  64. package/examples/electron/tiles.png +0 -0
  65. package/examples/empty/game.js +0 -51
  66. package/examples/empty/index.html +0 -5
  67. package/examples/empty/tiles.png +0 -0
  68. package/examples/favicon.png +0 -0
  69. package/examples/games.jpg +0 -0
  70. package/examples/htmlMenu/game.js +0 -82
  71. package/examples/htmlMenu/index.html +0 -45
  72. package/examples/htmlMenu/tiles.png +0 -0
  73. package/examples/index.html +0 -62
  74. package/examples/logo.png +0 -0
  75. package/examples/logo2.png +0 -0
  76. package/examples/module/build.mjs +0 -124
  77. package/examples/module/game.js +0 -132
  78. package/examples/module/index.html +0 -10
  79. package/examples/module/tiles.png +0 -0
  80. package/examples/particles/index.html +0 -426
  81. package/examples/particles/tiles.png +0 -0
  82. package/examples/platformer/data/gameLevelData.tmx +0 -143
  83. package/examples/platformer/data/gameLevelData.tsx +0 -4
  84. package/examples/platformer/game.js +0 -147
  85. package/examples/platformer/gameCharacter.js +0 -309
  86. package/examples/platformer/gameEffects.js +0 -278
  87. package/examples/platformer/gameLevel.js +0 -205
  88. package/examples/platformer/gameLevelData.json +0 -171
  89. package/examples/platformer/gameObjects.js +0 -378
  90. package/examples/platformer/gamePlayer.js +0 -35
  91. package/examples/platformer/index.html +0 -9
  92. package/examples/platformer/tiles.png +0 -0
  93. package/examples/platformer/tilesLevel.png +0 -0
  94. package/examples/puzzle/game.js +0 -331
  95. package/examples/puzzle/index.html +0 -8
  96. package/examples/puzzle/tiles.png +0 -0
  97. package/examples/screenshot.jpg +0 -0
  98. package/examples/shorts/animation.js +0 -18
  99. package/examples/shorts/base.html +0 -54
  100. package/examples/shorts/blending.js +0 -14
  101. package/examples/shorts/box2d.js +0 -51
  102. package/examples/shorts/box2dCar.js +0 -59
  103. package/examples/shorts/box2dPool.js +0 -107
  104. package/examples/shorts/box2dTileLayer.js +0 -48
  105. package/examples/shorts/cameraDrag.js +0 -22
  106. package/examples/shorts/clock.js +0 -21
  107. package/examples/shorts/colors.js +0 -25
  108. package/examples/shorts/debugDraw.js +0 -37
  109. package/examples/shorts/empty.js +0 -30
  110. package/examples/shorts/flappyGame.js +0 -55
  111. package/examples/shorts/fontImage.js +0 -17
  112. package/examples/shorts/fps.js +0 -90
  113. package/examples/shorts/helloWorld.js +0 -11
  114. package/examples/shorts/hillGlideGame.js +0 -63
  115. package/examples/shorts/input.js +0 -64
  116. package/examples/shorts/landerGame.js +0 -57
  117. package/examples/shorts/maze.js +0 -48
  118. package/examples/shorts/medals.js +0 -51
  119. package/examples/shorts/music.js +0 -78
  120. package/examples/shorts/musicPlayer.js +0 -137
  121. package/examples/shorts/nineSlice.js +0 -40
  122. package/examples/shorts/parallax.js +0 -72
  123. package/examples/shorts/particles.js +0 -28
  124. package/examples/shorts/piano.js +0 -44
  125. package/examples/shorts/platformer.js +0 -45
  126. package/examples/shorts/pongGame.js +0 -41
  127. package/examples/shorts/postProcess.js +0 -62
  128. package/examples/shorts/sequencer.js +0 -124
  129. package/examples/shorts/shader.js +0 -29
  130. package/examples/shorts/shapes.js +0 -21
  131. package/examples/shorts/slidingPuzzle.js +0 -52
  132. package/examples/shorts/song.mp3 +0 -0
  133. package/examples/shorts/sound.js +0 -36
  134. package/examples/shorts/spaceGame.js +0 -58
  135. package/examples/shorts/spriteAtlas.js +0 -31
  136. package/examples/shorts/starfield.js +0 -15
  137. package/examples/shorts/texture.js +0 -16
  138. package/examples/shorts/textureWrapped.js +0 -12
  139. package/examples/shorts/tileLayer.js +0 -48
  140. package/examples/shorts/tileRaycast.js +0 -39
  141. package/examples/shorts/tiles.png +0 -0
  142. package/examples/shorts/tiltedView.js +0 -63
  143. package/examples/shorts/timers.js +0 -53
  144. package/examples/shorts/topDown.js +0 -38
  145. package/examples/shorts/tween.js +0 -24
  146. package/examples/shorts/uiSystem.js +0 -61
  147. package/examples/shorts/video.webm +0 -0
  148. package/examples/shorts/videoPlayer.js +0 -34
  149. package/examples/shorts.js +0 -743
  150. package/examples/starter/build.bat +0 -7
  151. package/examples/starter/build.mjs +0 -126
  152. package/examples/starter/game.js +0 -137
  153. package/examples/starter/index.html +0 -35
  154. package/examples/starter/tiles.png +0 -0
  155. package/examples/stress/index.html +0 -173
  156. package/examples/style.css +0 -150
  157. package/examples/tweenSystem/game.js +0 -171
  158. package/examples/tweenSystem/index.html +0 -10
  159. package/examples/tweenSystem/tiles.png +0 -0
  160. package/examples/typescript/build.mjs +0 -60
  161. package/examples/typescript/game.js +0 -100
  162. package/examples/typescript/game.ts +0 -132
  163. package/examples/typescript/index.html +0 -10
  164. package/examples/typescript/tiles.png +0 -0
  165. package/examples/typescript/tsconfig.json +0 -17
  166. package/examples/uiSystem/game.js +0 -139
  167. package/examples/uiSystem/index.html +0 -10
  168. package/examples/uiSystem/tiles.png +0 -0
  169. package/jsconfig.json +0 -12
  170. package/plugins/desktop.ini +0 -2
  171. package/reference.md +0 -448
  172. package/test/math.test.mjs +0 -774
  173. package/test/setup.mjs +0 -22
  174. package/test/smoke.test.mjs +0 -274
  175. package/test/tween.test.mjs +0 -576
  176. package/test/util.test.mjs +0 -80
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "littlejsengine",
3
- "version": "1.18.4",
3
+ "version": "1.18.8",
4
4
  "description": "LittleJS - Tiny and Fast HTML5 Game Engine",
5
5
  "main": "dist/littlejs.esm.js",
6
6
  "types": "dist/littlejs.d.ts",
@@ -29,6 +29,13 @@
29
29
  "url": "https://github.com/KilledByAPixel/LittleJS/issues"
30
30
  },
31
31
  "homepage": "https://github.com/KilledByAPixel/LittleJS",
32
+ "files": [
33
+ "dist/",
34
+ "src/",
35
+ "plugins/",
36
+ "COPYRIGHT.txt",
37
+ "FAQ.md"
38
+ ],
32
39
  "scripts": {
33
40
  "build": "node src/engineBuild.mjs",
34
41
  "test": "node --test --import ./test/setup.mjs \"test/**/*.test.mjs\""
package/plugins/box2d.js CHANGED
@@ -525,14 +525,29 @@ class Box2dObject extends EngineObject
525
525
  this.body.ApplyForce(box2d.vec2dTo(force), box2d.vec2dTo(pos));
526
526
  }
527
527
 
528
- /** Apply acceleration to this object
528
+ /** Apply acceleration to this object (changes velocity by acceleration,
529
+ * mass-independent — matches EngineObject.applyAcceleration semantics).
530
+ * Use applyImpulse if you want the mass-dependent velocity change
531
+ * Δv = impulse / mass, or applyForce for a Newton-style sustained force.
529
532
  * @param {Vector2} acceleration
530
533
  * @param {Vector2} [pos] */
531
534
  applyAcceleration(acceleration, pos)
532
- {
535
+ {
536
+ pos ||= this.getCenterOfMass();
537
+ this.setAwake();
538
+ const impulse = acceleration.scale(this.getMass());
539
+ this.body.ApplyLinearImpulse(box2d.vec2dTo(impulse), box2d.vec2dTo(pos));
540
+ }
541
+
542
+ /** Apply an instantaneous linear impulse. Changes velocity immediately by
543
+ * impulse / mass (so heavier bodies move less for the same impulse).
544
+ * @param {Vector2} impulse
545
+ * @param {Vector2} [pos] */
546
+ applyImpulse(impulse, pos)
547
+ {
533
548
  pos ||= this.getCenterOfMass();
534
549
  this.setAwake();
535
- this.body.ApplyLinearImpulse(box2d.vec2dTo(acceleration), box2d.vec2dTo(pos));
550
+ this.body.ApplyLinearImpulse(box2d.vec2dTo(impulse), box2d.vec2dTo(pos));
536
551
  }
537
552
 
538
553
  /** Apply torque to this object
@@ -542,13 +557,23 @@ class Box2dObject extends EngineObject
542
557
  this.setAwake();
543
558
  this.body.ApplyTorque(torque);
544
559
  }
545
-
546
- /** Apply angular acceleration to this object
560
+
561
+ /** Apply angular acceleration to this object (changes angular velocity by
562
+ * acceleration, mass-independent — matches EngineObject semantics).
547
563
  * @param {number} acceleration */
548
564
  applyAngularAcceleration(acceleration)
549
565
  {
550
566
  this.setAwake();
551
- this.body.ApplyAngularImpulse(acceleration);
567
+ this.body.ApplyAngularImpulse(acceleration * this.getInertia());
568
+ }
569
+
570
+ /** Apply an instantaneous angular impulse. Changes angular velocity by
571
+ * impulse / inertia immediately.
572
+ * @param {number} impulse */
573
+ applyAngularImpulse(impulse)
574
+ {
575
+ this.setAwake();
576
+ this.body.ApplyAngularImpulse(impulse);
552
577
  }
553
578
 
554
579
  ///////////////////////////////////////////////////////////////////////////////
@@ -794,7 +819,7 @@ class Box2dJoint
794
819
 
795
820
  /** Check if the connected bodies should collide
796
821
  * @return {boolean} */
797
- getCollideConnected() { return this.box2dJoint.getCollideConnected();}
822
+ getCollideConnected() { return this.box2dJoint.GetCollideConnected();}
798
823
 
799
824
  /** Check if either connected body is active
800
825
  * @return {boolean} */
@@ -1,196 +1,248 @@
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
- /** List of all medals
15
- * @type {Object}
16
- * @memberof Medals */
17
- const medals = {};
18
-
19
- // Engine internal variables not exposed to documentation
20
- let medalsDisplayQueue = [], medalsSaveName, medalsDisplayTimeLast;
21
-
22
- ///////////////////////////////////////////////////////////////////////////////
23
-
24
- /** Initialize medals with a save name used for storage
25
- * - Call this after creating all medals
26
- * - Checks if medals are unlocked
27
- * @param {string} saveName
28
- * @memberof Medals */
29
- function medalsInit(saveName)
30
- {
31
- // check if medals are unlocked
32
- medalsSaveName = saveName;
33
- if (!debugMedals)
34
- medalsForEach(medal=> medal.unlocked = !!localStorage[medal.storageKey()]);
35
-
36
- // engine automatically renders medals
37
- engineAddPlugin(undefined, medalsRender);
38
-
39
- // plugin functions
40
- function medalsRender()
41
- {
42
- if (!medalsDisplayQueue.length) return;
43
-
44
- // update first medal in queue
45
- const medal = medalsDisplayQueue[0];
46
- const time = timeReal - medalsDisplayTimeLast;
47
- if (!medalsDisplayTimeLast)
48
- medalsDisplayTimeLast = timeReal;
49
- else if (time > medalDisplayTime)
50
- {
51
- medalsDisplayTimeLast = 0;
52
- medalsDisplayQueue.shift();
53
- }
54
- else
55
- {
56
- // slide on/off medals
57
- const slideOffTime = medalDisplayTime - medalDisplaySlideTime;
58
- const hidePercent =
59
- time < medalDisplaySlideTime ? 1 - time / medalDisplaySlideTime :
60
- time > slideOffTime ? (time - slideOffTime) / medalDisplaySlideTime : 0;
61
- medal.render(hidePercent);
62
- }
63
- }
64
- }
65
-
66
- /**
67
- * @callback MedalCallbackFunction - Function that processes a medal
68
- * @param {Medal} medal
69
- * @memberof Medals
70
- */
71
-
72
- /** Calls a function for each medal
73
- * @param {MedalCallbackFunction} callback
74
- * @memberof Medals */
75
- function medalsForEach(callback)
76
- { Object.values(medals).forEach(medal=>callback(medal)); }
77
-
78
- ///////////////////////////////////////////////////////////////////////////////
79
-
80
- /**
81
- * Medal - Tracks an unlockable medal
82
- * @memberof Medals
83
- * @example
84
- * // create a medal
85
- * const medal_example = new Medal(0, 'Example Medal', 'More info about the medal goes here.', '🎖️');
86
- *
87
- * // initialize medals
88
- * medalsInit('Example Game');
89
- *
90
- * // unlock the medal
91
- * medal_example.unlock();
92
- */
93
- class Medal
94
- {
95
- /** Create a medal object and adds it to the list of medals
96
- * @param {number} id - The unique identifier of the medal
97
- * @param {string} name - Name of the medal
98
- * @param {string} [description] - Description of the medal
99
- * @param {string} [icon] - Icon for the medal
100
- * @param {string} [src] - Image location for the medal
101
- */
102
- constructor(id, name, description='', icon='🏆', src)
103
- {
104
- ASSERT(id >= 0 && !medals[id]);
105
-
106
- /** @property {number} - The unique identifier of the medal */
107
- this.id = id;
108
-
109
- /** @property {string} - Name of the medal */
110
- this.name = name;
111
-
112
- /** @property {string} - Description of the medal */
113
- this.description = description;
114
-
115
- /** @property {string} - Icon for the medal */
116
- this.icon = icon;
117
-
118
- /** @property {boolean} - Is the medal unlocked? */
119
- this.unlocked = false;
120
-
121
- // load the source image if provided
122
- if (src)
123
- (this.image = new Image).src = src;
124
-
125
- // add this to list of medals
126
- medals[id] = this;
127
- }
128
-
129
- /** Unlocks a medal if not already unlocked */
130
- unlock()
131
- {
132
- if (medalsPreventUnlock || this.unlocked) return;
133
-
134
- // save the medal
135
- ASSERT(medalsSaveName, 'save name must be set');
136
- localStorage[this.storageKey()] = this.unlocked = true;
137
- medalsDisplayQueue.push(this);
138
- }
139
-
140
- /** Render a medal
141
- * @param {number} [hidePercent] - How much to slide the medal off screen
142
- */
143
- render(hidePercent=0)
144
- {
145
- const context = mainContext;
146
- const width = min(medalDisplaySize.x, mainCanvas.width);
147
- const height = medalDisplaySize.y;
148
- const x = mainCanvas.width - width;
149
- const y = -height*hidePercent;
150
- const backgroundColor = hsl(0,0,.9);
151
-
152
- // draw containing rect and clip to that region
153
- context.save();
154
- context.beginPath();
155
- context.fillStyle = backgroundColor.toString();
156
- context.strokeStyle = BLACK.toString();
157
- context.lineWidth = 3;
158
- context.rect(x, y, width, height);
159
- context.fill();
160
- context.stroke();
161
- context.clip();
162
-
163
- // draw the icon
164
- const gap = vec2(.1, .05).scale(height);
165
- const medalDisplayIconSize = height - 2*gap.x;
166
- this.renderIcon(vec2(x + gap.x + medalDisplayIconSize/2, y + height/2), medalDisplayIconSize);
167
-
168
- // draw the name
169
- const nameSize = height*.5;
170
- const descriptionSize = height*.3;
171
- const pos = vec2(x + medalDisplayIconSize + 2*gap.x, y + gap.y*2 + nameSize/2);
172
- const textWidth = width - medalDisplayIconSize - 3*gap.x;
173
- drawTextScreen(this.name, pos, nameSize, BLACK, 0, undefined, 'left', undefined, undefined, textWidth);
174
-
175
- // draw the description
176
- pos.y = y + height - gap.y*2 - descriptionSize/2;
177
- drawTextScreen(this.description, pos, descriptionSize, BLACK, 0, undefined, 'left', undefined, undefined, textWidth);
178
- context.restore();
179
- }
180
-
181
- /** Render the icon for a medal
182
- * @param {Vector2} pos - Screen space position
183
- * @param {number} size - Screen space size
184
- */
185
- renderIcon(pos, size)
186
- {
187
- // draw the image or icon
188
- if (this.image)
189
- mainContext.drawImage(this.image, pos.x-size/2, pos.y-size/2, size, size);
190
- else
191
- drawTextScreen(this.icon, pos, size*.7, BLACK);
192
- }
193
-
194
- // Get local storage key used by the medal
195
- storageKey() { return medalsSaveName + '_' + this.id; }
196
- }
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
+ medalsForEach(medal=> medal.unlocked = !!localStorage[medal.storageKey()]);
64
+
65
+ // engine automatically renders medals
66
+ engineAddPlugin(undefined, medalsRender);
67
+
68
+ // plugin functions
69
+ function medalsRender()
70
+ {
71
+ if (!medalsDisplayQueue.length) return;
72
+
73
+ // update first medal in queue
74
+ const medal = medalsDisplayQueue[0];
75
+ const time = timeReal - medalsDisplayTimeLast;
76
+ if (!medalsDisplayTimeLast)
77
+ medalsDisplayTimeLast = timeReal;
78
+ else if (time > medalDisplayTime)
79
+ {
80
+ medalsDisplayTimeLast = 0;
81
+ medalsDisplayQueue.shift();
82
+ }
83
+ else
84
+ {
85
+ // slide on/off medals
86
+ const slideOffTime = medalDisplayTime - medalDisplaySlideTime;
87
+ const hidePercent =
88
+ time < medalDisplaySlideTime ? 1 - time / medalDisplaySlideTime :
89
+ time > slideOffTime ? (time - slideOffTime) / medalDisplaySlideTime : 0;
90
+ medal.render(hidePercent);
91
+ }
92
+ }
93
+ }
94
+
95
+ /**
96
+ * @callback MedalCallbackFunction - Function that processes a medal
97
+ * @param {Medal} medal
98
+ * @memberof Medals
99
+ */
100
+
101
+ /** Calls a function for each medal
102
+ * @param {MedalCallbackFunction} callback
103
+ * @memberof Medals */
104
+ function medalsForEach(callback)
105
+ { Object.values(medals).forEach(medal=>callback(medal)); }
106
+
107
+ ///////////////////////////////////////////////////////////////////////////////
108
+
109
+ /**
110
+ * Medal - Tracks an unlockable medal
111
+ * @memberof Medals
112
+ * @example
113
+ * // create a medal
114
+ * const medal_example = new Medal(0, 'Example Medal', 'More info about the medal goes here.', '🎖️');
115
+ *
116
+ * // initialize medals
117
+ * medalsInit('Example Game');
118
+ *
119
+ * // unlock the medal
120
+ * medal_example.unlock();
121
+ */
122
+ class Medal
123
+ {
124
+ /** Create a medal object and adds it to the list of medals
125
+ * @param {number} id - The unique identifier of the medal
126
+ * @param {string} name - Name of the medal
127
+ * @param {string} [description] - Description of the medal
128
+ * @param {string} [icon] - Icon for the medal
129
+ * @param {string} [src] - Image location for the medal
130
+ */
131
+ constructor(id, name, description='', icon='🏆', src)
132
+ {
133
+ ASSERT(id >= 0 && !medals[id]);
134
+
135
+ /** @property {number} - The unique identifier of the medal */
136
+ this.id = id;
137
+
138
+ /** @property {string} - Name of the medal */
139
+ this.name = name;
140
+
141
+ /** @property {string} - Description of the medal */
142
+ this.description = description;
143
+
144
+ /** @property {string} - Icon for the medal */
145
+ this.icon = icon;
146
+
147
+ /** @property {boolean} - Is the medal unlocked? */
148
+ this.unlocked = false;
149
+
150
+ // load the source image if provided
151
+ if (src)
152
+ (this.image = new Image).src = src;
153
+
154
+ // add this to list of medals
155
+ medals[id] = this;
156
+ }
157
+
158
+ /** Unlocks a medal if not already unlocked */
159
+ unlock()
160
+ {
161
+ if (medalsPreventUnlock || this.unlocked) return;
162
+
163
+ // save the medal
164
+ ASSERT(medalsSaveName, 'save name must be set');
165
+ localStorage[this.storageKey()] = this.unlocked = true;
166
+ medalsDisplayQueue.push(this);
167
+ }
168
+
169
+ /** Render a medal
170
+ * @param {number} [hidePercent] - How much to slide the medal off screen
171
+ */
172
+ render(hidePercent=0)
173
+ {
174
+ const context = mainContext;
175
+ const width = min(medalDisplaySize.x, mainCanvas.width);
176
+ const height = medalDisplaySize.y;
177
+ const x = mainCanvas.width - width;
178
+ const y = -height*hidePercent;
179
+ const backgroundColor = hsl(0,0,.9);
180
+
181
+ // draw containing rect and clip to that region
182
+ context.save();
183
+ context.beginPath();
184
+ context.fillStyle = backgroundColor.toString();
185
+ context.strokeStyle = BLACK.toString();
186
+ context.lineWidth = 3;
187
+ context.rect(x, y, width, height);
188
+ context.fill();
189
+ context.stroke();
190
+ context.clip();
191
+
192
+ // draw the icon
193
+ const gap = vec2(.1, .05).scale(height);
194
+ const medalDisplayIconSize = height - 2*gap.x;
195
+ this.renderIcon(vec2(x + gap.x + medalDisplayIconSize/2, y + height/2), medalDisplayIconSize);
196
+
197
+ // draw the name
198
+ const nameSize = height*.5;
199
+ const descriptionSize = height*.3;
200
+ const pos = vec2(x + medalDisplayIconSize + 2*gap.x, y + gap.y*2 + nameSize/2);
201
+ const textWidth = width - medalDisplayIconSize - 3*gap.x;
202
+ drawTextScreen(this.name, pos, nameSize, BLACK, 0, undefined, 'left', undefined, undefined, textWidth);
203
+
204
+ // draw the description
205
+ pos.y = y + height - gap.y*2 - descriptionSize/2;
206
+ drawTextScreen(this.description, pos, descriptionSize, BLACK, 0, undefined, 'left', undefined, undefined, textWidth);
207
+ context.restore();
208
+ }
209
+
210
+ /** Render the icon for a medal
211
+ * @param {Vector2} pos - Screen space position
212
+ * @param {number} size - Screen space size
213
+ */
214
+ renderIcon(pos, size)
215
+ {
216
+ // draw the image or icon
217
+ if (this.image)
218
+ mainContext.drawImage(this.image, pos.x-size/2, pos.y-size/2, size, size);
219
+ else
220
+ drawTextScreen(this.icon, pos, size*.7, BLACK);
221
+ }
222
+
223
+ // Get local storage key used by the medal
224
+ storageKey() { return medalsSaveName + '_' + this.id; }
225
+ }
226
+
227
+ ///////////////////////////////////////////////////////////////////////////////
228
+ // Medals setting setters
229
+
230
+ /** Set how long to show medals for in seconds
231
+ * @param {number} time
232
+ * @memberof Settings */
233
+ function setMedalDisplayTime(time) { medalDisplayTime = time; }
234
+
235
+ /** Set how quickly to slide on/off medals in seconds
236
+ * @param {number} time
237
+ * @memberof Settings */
238
+ function setMedalDisplaySlideTime(time) { medalDisplaySlideTime = time; }
239
+
240
+ /** Set size of medal display
241
+ * @param {Vector2} size
242
+ * @memberof Settings */
243
+ function setMedalDisplaySize(size) { medalDisplaySize = size.copy(); }
244
+
245
+ /** Set to stop medals from being unlockable
246
+ * @param {boolean} preventUnlock
247
+ * @memberof Settings */
248
+ function setMedalsPreventUnlock(preventUnlock) { medalsPreventUnlock = preventUnlock; }