littlejsengine 1.18.28 → 1.19.3

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.
@@ -1,280 +1,280 @@
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, mainCanvas.width);
210
- const height = medalDisplaySize.y;
211
- const x = mainCanvas.width - 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
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; }