littlejsengine 1.18.29 → 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.
- package/FAQ.md +841 -815
- package/README.md +211 -199
- package/dist/littlejs.d.ts +4383 -812
- package/dist/littlejs.esm.js +31848 -16680
- package/dist/littlejs.esm.min.js +14 -1
- package/dist/littlejs.js +31230 -16166
- package/dist/littlejs.min.js +13 -1
- package/dist/littlejs.release.js +27419 -15457
- package/package.json +10 -5
- package/plugins/audioEffects.js +375 -0
- package/plugins/box2d.js +2516 -2073
- package/plugins/drawUtilities.js +206 -31
- package/plugins/gltf.js +740 -0
- package/plugins/jsconfig.json +19 -0
- package/plugins/lightSystem.js +719 -330
- package/plugins/math3d.js +911 -0
- package/plugins/medalSystem.js +118 -72
- package/plugins/newgrounds.js +400 -199
- package/plugins/pathFinder.js +208 -175
- package/plugins/pluginExport.js +196 -115
- package/plugins/postProcess.js +284 -166
- package/plugins/render3d.js +4072 -0
- package/plugins/render3dDebug.js +109 -0
- package/plugins/render3dExtras.js +1754 -0
- package/plugins/render3dVoxels.js +502 -0
- package/plugins/textureSheet.js +86 -48
- package/plugins/threejs.js +183 -158
- package/plugins/tweakables.js +411 -0
- package/plugins/tweenSystem.js +646 -526
- package/plugins/uiSystem.js +2024 -1687
- package/plugins/zzfxm.js +8 -15
- package/src/engine.js +345 -194
- package/src/engineAudio.js +1022 -777
- package/src/engineBuild.mjs +289 -253
- package/src/engineDebug.js +912 -735
- package/src/engineDraw.js +1774 -1510
- package/src/engineEditor.js +2431 -0
- package/src/engineExport.js +419 -396
- package/src/engineInput.js +1462 -1293
- package/src/engineLogo.js +4 -3
- package/src/engineMath.js +1274 -1183
- package/src/engineObject.js +674 -551
- package/src/engineParticles.js +583 -532
- package/src/engineRelease.js +67 -45
- package/src/engineSettings.js +808 -726
- package/src/engineTileLayer.js +995 -741
- package/src/engineUtilities.js +32 -13
- package/src/engineWebGL.js +1162 -941
- package/src/jsconfig.json +17 -13
- package/plugins/box2d.wasm.js +0 -630
- package/plugins/box2d.wasm.wasm +0 -0
package/plugins/medalSystem.js
CHANGED
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
* LittleJS Medal System
|
|
3
3
|
* - Achievement/trophy system for games
|
|
4
4
|
* - Medal class with name, description, icon, and unlock tracking
|
|
5
|
-
* - Automatic saving to local storage
|
|
5
|
+
* - Automatic saving to local storage, unless a service like Newgrounds holds the medal (see Medal.isLocal)
|
|
6
6
|
* - Visual display queue with slide-in notifications
|
|
7
|
-
* - Newgrounds
|
|
8
|
-
* -
|
|
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
9
|
* @namespace Medals
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
'use strict';
|
|
13
13
|
|
|
14
|
-
let debugMedals = false;
|
|
14
|
+
let debugMedals = false; // see the header
|
|
15
15
|
|
|
16
16
|
///////////////////////////////////////////////////////////////////////////////
|
|
17
17
|
// Medals settings
|
|
@@ -41,62 +41,75 @@ let medalDisplaySize = vec2(640, 80);
|
|
|
41
41
|
let medalsPreventUnlock = false;
|
|
42
42
|
|
|
43
43
|
/** List of all medals
|
|
44
|
-
* @type {Object}
|
|
44
|
+
* @type {Object<number, Medal>}
|
|
45
45
|
* @memberof Medals */
|
|
46
46
|
const medals = {};
|
|
47
47
|
|
|
48
48
|
// Engine internal variables not exposed to documentation
|
|
49
|
-
let medalsDisplayQueue = [], medalsSaveName, medalsDisplayTimeLast;
|
|
49
|
+
let medalsDisplayQueue = [], medalsSaveName, medalsDisplayTimeLast, medalsRenderAdded;
|
|
50
|
+
let medalsLoadWaiting = false; // medalsInit came before any medal, each one made reads its own unlock
|
|
50
51
|
|
|
51
52
|
///////////////////////////////////////////////////////////////////////////////
|
|
52
53
|
|
|
53
54
|
/** Initialize medals with a save name used for storage
|
|
54
55
|
* - Call this after creating all medals
|
|
55
|
-
* -
|
|
56
|
-
*
|
|
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
|
|
57
60
|
* @memberof Medals */
|
|
58
61
|
function medalsInit(saveName)
|
|
59
62
|
{
|
|
60
|
-
// check if medals are unlocked
|
|
61
63
|
medalsSaveName = saveName;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
});
|
|
70
|
-
medalsSave();
|
|
71
|
-
}
|
|
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
|
+
}
|
|
72
71
|
|
|
73
|
-
|
|
74
|
-
|
|
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
|
+
}
|
|
75
88
|
|
|
76
|
-
|
|
77
|
-
|
|
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)
|
|
78
100
|
{
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
}
|
|
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);
|
|
100
113
|
}
|
|
101
114
|
}
|
|
102
115
|
|
|
@@ -110,21 +123,42 @@ function medalsInit(saveName)
|
|
|
110
123
|
* @param {MedalCallbackFunction} callback
|
|
111
124
|
* @memberof Medals */
|
|
112
125
|
function medalsForEach(callback)
|
|
113
|
-
{ Object.values(medals).forEach(medal=>callback(medal)); }
|
|
126
|
+
{ Object.values(medals).forEach(medal=> callback(medal)); }
|
|
114
127
|
|
|
115
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
|
|
116
130
|
* @memberof Medals */
|
|
117
131
|
function medalsReset()
|
|
118
132
|
{
|
|
119
|
-
medalsForEach(medal
|
|
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
|
+
}
|
|
120
143
|
medalsSave();
|
|
121
144
|
}
|
|
122
145
|
|
|
146
|
+
// write the local medals to the save, keeping the entries of medals a service holds
|
|
123
147
|
function medalsSave()
|
|
124
148
|
{
|
|
125
|
-
if (!medalsSaveName) return;
|
|
126
|
-
|
|
127
|
-
|
|
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
|
+
}
|
|
128
162
|
const entry = {
|
|
129
163
|
name: medal.name,
|
|
130
164
|
description: medal.description,
|
|
@@ -134,7 +168,7 @@ function medalsSave()
|
|
|
134
168
|
if (medal.image) entry.src = medal.image.src;
|
|
135
169
|
data[medal.id] = entry;
|
|
136
170
|
});
|
|
137
|
-
|
|
171
|
+
writeSaveData(medalsSaveName, data);
|
|
138
172
|
}
|
|
139
173
|
|
|
140
174
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -154,7 +188,7 @@ function medalsSave()
|
|
|
154
188
|
*/
|
|
155
189
|
class Medal
|
|
156
190
|
{
|
|
157
|
-
/** Create a medal
|
|
191
|
+
/** Create a medal and add it to the list of medals
|
|
158
192
|
* @param {number} id - The unique identifier of the medal
|
|
159
193
|
* @param {string} name - Name of the medal
|
|
160
194
|
* @param {string} [description] - Description of the medal
|
|
@@ -163,7 +197,7 @@ class Medal
|
|
|
163
197
|
*/
|
|
164
198
|
constructor(id, name, description='', icon='🏆', src)
|
|
165
199
|
{
|
|
166
|
-
ASSERT(id >= 0 && !medals[id]);
|
|
200
|
+
ASSERT(isNumber(id) && id >= 0 && !medals[id], 'medal id must be a unique number of 0 or more');
|
|
167
201
|
|
|
168
202
|
/** @property {number} - The unique identifier of the medal */
|
|
169
203
|
this.id = id;
|
|
@@ -180,35 +214,46 @@ class Medal
|
|
|
180
214
|
/** @property {boolean} - Is the medal unlocked? */
|
|
181
215
|
this.unlocked = false;
|
|
182
216
|
|
|
183
|
-
/** @property {HTMLImageElement|undefined} - Source image for the medal icon, if any
|
|
217
|
+
/** @property {HTMLImageElement|undefined} - Source image for the medal icon, if any
|
|
218
|
+
* @type {HTMLImageElement|undefined} */
|
|
184
219
|
this.image = undefined;
|
|
185
220
|
if (src)
|
|
186
221
|
(this.image = new Image).src = src;
|
|
187
222
|
|
|
188
|
-
// add this to list of medals
|
|
223
|
+
// add this to list of medals, unlocked if the save says so when medalsInit came before any medal
|
|
189
224
|
medals[id] = this;
|
|
225
|
+
if (medalsLoadWaiting && !debugMedals && this.isLocal())
|
|
226
|
+
this.unlocked = !!readSaveData(medalsSaveName)[id]?.unlocked;
|
|
190
227
|
}
|
|
191
228
|
|
|
192
|
-
/** Unlocks a medal if not already unlocked
|
|
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 */
|
|
193
232
|
unlock()
|
|
194
233
|
{
|
|
195
|
-
if (medalsPreventUnlock
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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);
|
|
201
242
|
}
|
|
202
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
|
+
|
|
203
248
|
/** Render a medal
|
|
204
249
|
* @param {number} [hidePercent] - How much to slide the medal off screen
|
|
205
250
|
*/
|
|
206
251
|
render(hidePercent=0)
|
|
207
252
|
{
|
|
208
253
|
const context = mainContext;
|
|
209
|
-
const width = min(medalDisplaySize.x,
|
|
254
|
+
const width = min(medalDisplaySize.x, mainCanvasSize.x);
|
|
210
255
|
const height = medalDisplaySize.y;
|
|
211
|
-
const x =
|
|
256
|
+
const x = mainCanvasSize.x - width;
|
|
212
257
|
const y = -height*hidePercent;
|
|
213
258
|
const backgroundColor = hsl(0,0,.9);
|
|
214
259
|
|
|
@@ -217,22 +262,22 @@ class Medal
|
|
|
217
262
|
context.beginPath();
|
|
218
263
|
context.fillStyle = backgroundColor.toString();
|
|
219
264
|
context.strokeStyle = BLACK.toString();
|
|
220
|
-
context.lineWidth = 3;
|
|
221
|
-
context.rect(x, y, width, height);
|
|
265
|
+
const lineWidth = context.lineWidth = 3;
|
|
266
|
+
context.rect(x + lineWidth/2, y + lineWidth/2, width - lineWidth, height - lineWidth); // the whole border shows
|
|
222
267
|
context.fill();
|
|
223
268
|
context.stroke();
|
|
224
269
|
context.clip();
|
|
225
270
|
|
|
226
271
|
// draw the icon
|
|
227
272
|
const gap = vec2(.1, .05).scale(height);
|
|
228
|
-
const
|
|
229
|
-
this.renderIcon(vec2(x + gap.x +
|
|
273
|
+
const iconSize = height - 2*gap.x;
|
|
274
|
+
this.renderIcon(vec2(x + gap.x + iconSize/2, y + height/2), iconSize);
|
|
230
275
|
|
|
231
276
|
// draw the name
|
|
232
277
|
const nameSize = height*.5;
|
|
233
278
|
const descriptionSize = height*.3;
|
|
234
|
-
const pos = vec2(x +
|
|
235
|
-
const textWidth = width -
|
|
279
|
+
const pos = vec2(x + iconSize + 2*gap.x, y + gap.y*2 + nameSize/2);
|
|
280
|
+
const textWidth = width - iconSize - 3*gap.x;
|
|
236
281
|
drawTextScreen(this.name, pos, nameSize, BLACK, 0, undefined, 'left', undefined, undefined, textWidth);
|
|
237
282
|
|
|
238
283
|
// draw the description
|
|
@@ -247,9 +292,10 @@ class Medal
|
|
|
247
292
|
*/
|
|
248
293
|
renderIcon(pos, size)
|
|
249
294
|
{
|
|
250
|
-
// draw the image or icon
|
|
251
|
-
|
|
252
|
-
|
|
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);
|
|
253
299
|
else
|
|
254
300
|
drawTextScreen(this.icon, pos, size*.7, BLACK);
|
|
255
301
|
}
|