lumisjs 1.0.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/LICENSE +21 -0
  2. package/README.md +274 -0
  3. package/SECURITY.md +160 -0
  4. package/index.js +90 -0
  5. package/package.json +68 -0
  6. package/src/cache/CacheManager.js +393 -0
  7. package/src/cache/MemoryAdapter.js +259 -0
  8. package/src/cache/MultiLevelCache.js +362 -0
  9. package/src/cache/RedisAdapter.js +329 -0
  10. package/src/cache/SQLiteAdapter.js +280 -0
  11. package/src/cache/index.js +15 -0
  12. package/src/client/Client.js +554 -0
  13. package/src/client/ShardingManager.js +274 -0
  14. package/src/config/ConfigValidator.js +172 -0
  15. package/src/config/index.js +7 -0
  16. package/src/dashboard/DashboardManager.js +362 -0
  17. package/src/datagen/DataGenerator.js +47 -0
  18. package/src/datagen/apis/GraphqlMocker.js +16 -0
  19. package/src/datagen/apis/OpenApiMocker.js +18 -0
  20. package/src/datagen/cli.js +130 -0
  21. package/src/datagen/formatters/csv.js +45 -0
  22. package/src/datagen/formatters/index.js +15 -0
  23. package/src/datagen/formatters/json.js +33 -0
  24. package/src/datagen/formatters/mongo.js +22 -0
  25. package/src/datagen/formatters/sql.js +32 -0
  26. package/src/datagen/index.js +17 -0
  27. package/src/datagen/plugin/PluginManager.js +43 -0
  28. package/src/datagen/plugins/example-plugin.js +21 -0
  29. package/src/datagen/schema/SchemaGenerator.js +172 -0
  30. package/src/datagen/schema/loadSchema.js +14 -0
  31. package/src/datagen/utils/seed.js +26 -0
  32. package/src/di/ServiceContainer.js +229 -0
  33. package/src/errors/ErrorCodes.js +110 -0
  34. package/src/errors/LumisError.js +85 -0
  35. package/src/game/EconomyManager.js +161 -0
  36. package/src/game/GameSessionManager.js +76 -0
  37. package/src/game/GuildManager.js +197 -0
  38. package/src/game/InventoryManager.js +140 -0
  39. package/src/game/LevelingSystem.js +194 -0
  40. package/src/game/MusicManager.js +587 -0
  41. package/src/health/HealthChecker.js +170 -0
  42. package/src/health/index.js +7 -0
  43. package/src/performance/PerformanceMonitor.js +244 -0
  44. package/src/performance/index.js +7 -0
  45. package/src/security/InputSanitizer.js +185 -0
  46. package/src/security/SecurityMiddleware.js +231 -0
  47. package/src/security/index.js +9 -0
  48. package/src/shutdown/GracefulShutdown.js +159 -0
  49. package/src/shutdown/index.js +7 -0
  50. package/src/utils/StructuredLogger.js +118 -0
@@ -0,0 +1,194 @@
1
+ 'use strict';
2
+
3
+ class LevelingSystem {
4
+ /**
5
+ * @param {import('../client/Client')} client
6
+ * @param {object} [options]
7
+ * @param {number} [options.baseXp=500]
8
+ * @param {number} [options.multiplier=1.2]
9
+ * @param {boolean} [options.autoTrack=true]
10
+ * @param {number} [options.minXpPerMessage=5]
11
+ * @param {number} [options.maxXpPerMessage=15]
12
+ * @param {number} [options.cooldown=60] Cooldown in seconds
13
+ */
14
+ constructor(client, options = {}) {
15
+ this.client = client;
16
+ this.baseXp = options.baseXp ?? 500;
17
+ this.multiplier = options.multiplier ?? 1.2;
18
+ this.autoTrack = options.autoTrack !== false;
19
+
20
+ this.minXp = options.minXpPerMessage ?? 5;
21
+ this.maxXp = options.maxXpPerMessage ?? 15;
22
+ this.cooldownSec = options.cooldown ?? 60;
23
+
24
+ if (this.minXp > this.maxXp) {
25
+ throw new RangeError('minXpPerMessage cannot be greater than maxXpPerMessage');
26
+ }
27
+
28
+ // Cooldown tracked per-user with timestamps instead of a Set — prevents
29
+ // memory accumulation if setTimeout fires late or is cleared.
30
+ this._cooldowns = new Map();
31
+
32
+ if (this.autoTrack) {
33
+ this._boundHandler = this._handleMessage.bind(this);
34
+ this.client.on('messageCreate', this._boundHandler);
35
+ }
36
+ }
37
+
38
+ _key(userId) {
39
+ return `level_xp_${userId}`;
40
+ }
41
+
42
+ /**
43
+ * Calculate level from total XP.
44
+ * @param {number} totalXp
45
+ * @returns {number}
46
+ */
47
+ calculateLevel(totalXp) {
48
+ if (totalXp < 0) return 0;
49
+ let remaining = totalXp;
50
+ let level = 0;
51
+ let threshold = this.baseXp;
52
+
53
+ while (remaining >= threshold) {
54
+ remaining -= threshold;
55
+ level++;
56
+ threshold = Math.floor(threshold * this.multiplier);
57
+ }
58
+
59
+ return level;
60
+ }
61
+
62
+ /**
63
+ * Get the XP required to reach the next level from the given level.
64
+ * @param {number} currentLevel
65
+ * @returns {number}
66
+ */
67
+ calculateNextLevelXp(currentLevel) {
68
+ if (currentLevel < 0) return this.baseXp;
69
+ let required = this.baseXp;
70
+ for (let i = 0; i < currentLevel; i++) {
71
+ required = Math.floor(required * this.multiplier);
72
+ }
73
+ return required;
74
+ }
75
+
76
+ /**
77
+ * Get total XP needed to reach a given level from 0.
78
+ * @param {number} targetLevel
79
+ * @returns {number}
80
+ */
81
+ totalXpForLevel(targetLevel) {
82
+ let total = 0;
83
+ let threshold = this.baseXp;
84
+ for (let i = 0; i < targetLevel; i++) {
85
+ total += threshold;
86
+ threshold = Math.floor(threshold * this.multiplier);
87
+ }
88
+ return total;
89
+ }
90
+
91
+ /**
92
+ * Get a user's XP and level stats.
93
+ * @param {string} userId
94
+ * @returns {Promise<{ xp: number, level: number, nextLevelXp: number, progress: number }>}
95
+ */
96
+ async getUserStats(userId) {
97
+ if (!userId) throw new TypeError('userId is required');
98
+
99
+ const val = await this.client.cache.get(this._key(userId));
100
+ const xp = val ? Number(val) : 0;
101
+ const level = this.calculateLevel(xp);
102
+ const nextLevelXp = this.calculateNextLevelXp(level);
103
+
104
+ // XP within the current level
105
+ const levelStartXp = this.totalXpForLevel(level);
106
+ const progressXp = xp - levelStartXp;
107
+ const progress = nextLevelXp > 0 ? progressXp / nextLevelXp : 0;
108
+
109
+ return { xp, level, nextLevelXp, progressXp, progress };
110
+ }
111
+
112
+ /**
113
+ * Add XP to a user.
114
+ * @param {string} userId
115
+ * @param {number} amount
116
+ * @returns {Promise<{ xp: number, level: number, leveledUp: boolean }>}
117
+ */
118
+ async addXP(userId, amount) {
119
+ if (!userId) throw new TypeError('userId is required');
120
+ if (typeof amount !== 'number' || isNaN(amount) || amount < 0) {
121
+ throw new RangeError('amount must be a non-negative number');
122
+ }
123
+
124
+ const stats = await this.getUserStats(userId);
125
+ const newXp = stats.xp + amount;
126
+ const newLevel = this.calculateLevel(newXp);
127
+
128
+ await this.client.cache.set(this._key(userId), newXp);
129
+
130
+ const leveledUp = newLevel > stats.level;
131
+
132
+ if (leveledUp) {
133
+ const user = this.client.users.get(userId) || { id: userId };
134
+ this.client.emit('levelUp', user, newLevel, stats.level);
135
+ }
136
+
137
+ return { xp: newXp, level: newLevel, leveledUp };
138
+ }
139
+
140
+ /**
141
+ * Set a user's XP directly.
142
+ * @param {string} userId
143
+ * @param {number} xp
144
+ * @returns {Promise<{ xp: number, level: number }>}
145
+ */
146
+ async setXP(userId, xp) {
147
+ if (!userId) throw new TypeError('userId is required');
148
+ if (typeof xp !== 'number' || isNaN(xp) || xp < 0) {
149
+ throw new RangeError('xp must be a non-negative number');
150
+ }
151
+
152
+ await this.client.cache.set(this._key(userId), xp);
153
+ return { xp, level: this.calculateLevel(xp) };
154
+ }
155
+
156
+ /**
157
+ * Reset a user's XP to zero.
158
+ * @param {string} userId
159
+ */
160
+ async resetXP(userId) {
161
+ if (!userId) throw new TypeError('userId is required');
162
+ await this.client.cache.set(this._key(userId), 0);
163
+ }
164
+
165
+ /** @private */
166
+ async _handleMessage(message) {
167
+ if (!message?.author || message.author.bot) return;
168
+
169
+ const userId = message.author.id;
170
+ const now = Date.now();
171
+ const lastMessage = this._cooldowns.get(userId);
172
+
173
+ if (lastMessage && now - lastMessage < this.cooldownSec * 1000) return;
174
+
175
+ this._cooldowns.set(userId, now);
176
+
177
+ const xpEarned =
178
+ Math.floor(Math.random() * (this.maxXp - this.minXp + 1)) + this.minXp;
179
+
180
+ await this.addXP(userId, xpEarned);
181
+ }
182
+
183
+ /**
184
+ * Remove the message listener and clean up cooldown map.
185
+ */
186
+ destroy() {
187
+ if (this._boundHandler) {
188
+ this.client.off('messageCreate', this._boundHandler);
189
+ }
190
+ this._cooldowns.clear();
191
+ }
192
+ }
193
+
194
+ module.exports = LevelingSystem;