-
-
Notifications
You must be signed in to change notification settings - Fork 220
Expand file tree
/
Copy pathmedalSystem.js
More file actions
249 lines (210 loc) · 7.76 KB
/
medalSystem.js
File metadata and controls
249 lines (210 loc) · 7.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
/**
* LittleJS Medal System
* - Achievement/trophy system for games
* - Medal class with name, description, icon, and unlock tracking
* - Automatic saving to local storage
* - Visual display queue with slide-in notifications
* - Newgrounds API integration for online achievements
* - Debug mode to unlock/reset medals during development
* @namespace Medals
*/
'use strict';
let debugMedals = false;
///////////////////////////////////////////////////////////////////////////////
// Medals settings
/** How long to show medals for in seconds
* @type {number}
* @default
* @memberof Settings */
let medalDisplayTime = 5;
/** How quickly to slide on/off medals in seconds
* @type {number}
* @default
* @memberof Settings */
let medalDisplaySlideTime = .5;
/** Size of medal display
* @type {Vector2}
* @default Vector2(640,80)
* @memberof Settings */
let medalDisplaySize = vec2(640, 80);
/** Set to stop medals from being unlockable (like if cheats are enabled)
* @type {boolean}
* @default
* @memberof Settings */
let medalsPreventUnlock = false;
/** List of all medals
* @type {Object}
* @memberof Medals */
const medals = {};
// Engine internal variables not exposed to documentation
let medalsDisplayQueue = [], medalsSaveName, medalsDisplayTimeLast;
///////////////////////////////////////////////////////////////////////////////
/** Initialize medals with a save name used for storage
* - Call this after creating all medals
* - Checks if medals are unlocked
* @param {string} saveName
* @memberof Medals */
function medalsInit(saveName)
{
// check if medals are unlocked
medalsSaveName = saveName;
if (!debugMedals)
medalsForEach(medal=> medal.unlocked = !!localStorage[medal.storageKey()]);
// engine automatically renders medals
engineAddPlugin(undefined, medalsRender);
// plugin functions
function medalsRender()
{
if (!medalsDisplayQueue.length) return;
// update first medal in queue
const medal = medalsDisplayQueue[0];
const time = timeReal - medalsDisplayTimeLast;
if (!medalsDisplayTimeLast)
medalsDisplayTimeLast = timeReal;
else if (time > medalDisplayTime)
{
medalsDisplayTimeLast = 0;
medalsDisplayQueue.shift();
}
else
{
// slide on/off medals
const slideOffTime = medalDisplayTime - medalDisplaySlideTime;
const hidePercent =
time < medalDisplaySlideTime ? 1 - time / medalDisplaySlideTime :
time > slideOffTime ? (time - slideOffTime) / medalDisplaySlideTime : 0;
medal.render(hidePercent);
}
}
}
/**
* @callback MedalCallbackFunction - Function that processes a medal
* @param {Medal} medal
* @memberof Medals
*/
/** Calls a function for each medal
* @param {MedalCallbackFunction} callback
* @memberof Medals */
function medalsForEach(callback)
{ Object.values(medals).forEach(medal=>callback(medal)); }
///////////////////////////////////////////////////////////////////////////////
/**
* Medal - Tracks an unlockable medal
* @memberof Medals
* @example
* // create a medal
* const medal_example = new Medal(0, 'Example Medal', 'More info about the medal goes here.', '🎖️');
*
* // initialize medals
* medalsInit('Example Game');
*
* // unlock the medal
* medal_example.unlock();
*/
class Medal
{
/** Create a medal object and adds it to the list of medals
* @param {number} id - The unique identifier of the medal
* @param {string} name - Name of the medal
* @param {string} [description] - Description of the medal
* @param {string} [icon] - Icon for the medal
* @param {string} [src] - Image location for the medal
*/
constructor(id, name, description='', icon='🏆', src)
{
ASSERT(id >= 0 && !medals[id]);
/** @property {number} - The unique identifier of the medal */
this.id = id;
/** @property {string} - Name of the medal */
this.name = name;
/** @property {string} - Description of the medal */
this.description = description;
/** @property {string} - Icon for the medal */
this.icon = icon;
/** @property {boolean} - Is the medal unlocked? */
this.unlocked = false;
/** @property {HTMLImageElement|undefined} - Source image for the medal icon, if any */
this.image = undefined;
if (src)
(this.image = new Image).src = src;
// add this to list of medals
medals[id] = this;
}
/** Unlocks a medal if not already unlocked */
unlock()
{
if (medalsPreventUnlock || this.unlocked) return;
// save the medal
ASSERT(medalsSaveName, 'save name must be set');
localStorage[this.storageKey()] = this.unlocked = true;
medalsDisplayQueue.push(this);
}
/** Render a medal
* @param {number} [hidePercent] - How much to slide the medal off screen
*/
render(hidePercent=0)
{
const context = mainContext;
const width = min(medalDisplaySize.x, mainCanvas.width);
const height = medalDisplaySize.y;
const x = mainCanvas.width - width;
const y = -height*hidePercent;
const backgroundColor = hsl(0,0,.9);
// draw containing rect and clip to that region
context.save();
context.beginPath();
context.fillStyle = backgroundColor.toString();
context.strokeStyle = BLACK.toString();
context.lineWidth = 3;
context.rect(x, y, width, height);
context.fill();
context.stroke();
context.clip();
// draw the icon
const gap = vec2(.1, .05).scale(height);
const medalDisplayIconSize = height - 2*gap.x;
this.renderIcon(vec2(x + gap.x + medalDisplayIconSize/2, y + height/2), medalDisplayIconSize);
// draw the name
const nameSize = height*.5;
const descriptionSize = height*.3;
const pos = vec2(x + medalDisplayIconSize + 2*gap.x, y + gap.y*2 + nameSize/2);
const textWidth = width - medalDisplayIconSize - 3*gap.x;
drawTextScreen(this.name, pos, nameSize, BLACK, 0, undefined, 'left', undefined, undefined, textWidth);
// draw the description
pos.y = y + height - gap.y*2 - descriptionSize/2;
drawTextScreen(this.description, pos, descriptionSize, BLACK, 0, undefined, 'left', undefined, undefined, textWidth);
context.restore();
}
/** Render the icon for a medal
* @param {Vector2} pos - Screen space position
* @param {number} size - Screen space size
*/
renderIcon(pos, size)
{
// draw the image or icon
if (this.image)
mainContext.drawImage(this.image, pos.x-size/2, pos.y-size/2, size, size);
else
drawTextScreen(this.icon, pos, size*.7, BLACK);
}
// Get local storage key used by the medal
storageKey() { return medalsSaveName + '_' + this.id; }
}
///////////////////////////////////////////////////////////////////////////////
// Medals setting setters
/** Set how long to show medals for in seconds
* @param {number} time
* @memberof Settings */
function setMedalDisplayTime(time) { medalDisplayTime = time; }
/** Set how quickly to slide on/off medals in seconds
* @param {number} time
* @memberof Settings */
function setMedalDisplaySlideTime(time) { medalDisplaySlideTime = time; }
/** Set size of medal display
* @param {Vector2} size
* @memberof Settings */
function setMedalDisplaySize(size) { medalDisplaySize = size.copy(); }
/** Set to stop medals from being unlockable
* @param {boolean} preventUnlock
* @memberof Settings */
function setMedalsPreventUnlock(preventUnlock) { medalsPreventUnlock = preventUnlock; }