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.
- package/LICENSE +21 -0
- package/README.md +274 -0
- package/SECURITY.md +160 -0
- package/index.js +90 -0
- package/package.json +68 -0
- package/src/cache/CacheManager.js +393 -0
- package/src/cache/MemoryAdapter.js +259 -0
- package/src/cache/MultiLevelCache.js +362 -0
- package/src/cache/RedisAdapter.js +329 -0
- package/src/cache/SQLiteAdapter.js +280 -0
- package/src/cache/index.js +15 -0
- package/src/client/Client.js +554 -0
- package/src/client/ShardingManager.js +274 -0
- package/src/config/ConfigValidator.js +172 -0
- package/src/config/index.js +7 -0
- package/src/dashboard/DashboardManager.js +362 -0
- package/src/datagen/DataGenerator.js +47 -0
- package/src/datagen/apis/GraphqlMocker.js +16 -0
- package/src/datagen/apis/OpenApiMocker.js +18 -0
- package/src/datagen/cli.js +130 -0
- package/src/datagen/formatters/csv.js +45 -0
- package/src/datagen/formatters/index.js +15 -0
- package/src/datagen/formatters/json.js +33 -0
- package/src/datagen/formatters/mongo.js +22 -0
- package/src/datagen/formatters/sql.js +32 -0
- package/src/datagen/index.js +17 -0
- package/src/datagen/plugin/PluginManager.js +43 -0
- package/src/datagen/plugins/example-plugin.js +21 -0
- package/src/datagen/schema/SchemaGenerator.js +172 -0
- package/src/datagen/schema/loadSchema.js +14 -0
- package/src/datagen/utils/seed.js +26 -0
- package/src/di/ServiceContainer.js +229 -0
- package/src/errors/ErrorCodes.js +110 -0
- package/src/errors/LumisError.js +85 -0
- package/src/game/EconomyManager.js +161 -0
- package/src/game/GameSessionManager.js +76 -0
- package/src/game/GuildManager.js +197 -0
- package/src/game/InventoryManager.js +140 -0
- package/src/game/LevelingSystem.js +194 -0
- package/src/game/MusicManager.js +587 -0
- package/src/health/HealthChecker.js +170 -0
- package/src/health/index.js +7 -0
- package/src/performance/PerformanceMonitor.js +244 -0
- package/src/performance/index.js +7 -0
- package/src/security/InputSanitizer.js +185 -0
- package/src/security/SecurityMiddleware.js +231 -0
- package/src/security/index.js +9 -0
- package/src/shutdown/GracefulShutdown.js +159 -0
- package/src/shutdown/index.js +7 -0
- 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;
|