steamworks-ffi-node 0.3.0 → 0.4.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 (38) hide show
  1. package/README.md +86 -10
  2. package/dist/index.d.ts +2 -2
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +6 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/internal/SteamAPICore.d.ts +221 -8
  7. package/dist/internal/SteamAPICore.d.ts.map +1 -1
  8. package/dist/internal/SteamAPICore.js +234 -14
  9. package/dist/internal/SteamAPICore.js.map +1 -1
  10. package/dist/internal/SteamAchievementManager.d.ts +602 -31
  11. package/dist/internal/SteamAchievementManager.d.ts.map +1 -1
  12. package/dist/internal/SteamAchievementManager.js +601 -32
  13. package/dist/internal/SteamAchievementManager.js.map +1 -1
  14. package/dist/internal/SteamCallbackPoller.d.ts +68 -0
  15. package/dist/internal/SteamCallbackPoller.d.ts.map +1 -0
  16. package/dist/internal/SteamCallbackPoller.js +134 -0
  17. package/dist/internal/SteamCallbackPoller.js.map +1 -0
  18. package/dist/internal/SteamLeaderboardManager.d.ts +338 -0
  19. package/dist/internal/SteamLeaderboardManager.d.ts.map +1 -0
  20. package/dist/internal/SteamLeaderboardManager.js +734 -0
  21. package/dist/internal/SteamLeaderboardManager.js.map +1 -0
  22. package/dist/internal/SteamLibraryLoader.d.ts +15 -0
  23. package/dist/internal/SteamLibraryLoader.d.ts.map +1 -1
  24. package/dist/internal/SteamLibraryLoader.js +42 -5
  25. package/dist/internal/SteamLibraryLoader.js.map +1 -1
  26. package/dist/internal/SteamStatsManager.d.ts +357 -50
  27. package/dist/internal/SteamStatsManager.d.ts.map +1 -1
  28. package/dist/internal/SteamStatsManager.js +444 -106
  29. package/dist/internal/SteamStatsManager.js.map +1 -1
  30. package/dist/steam.d.ts +169 -9
  31. package/dist/steam.d.ts.map +1 -1
  32. package/dist/steam.js +178 -0
  33. package/dist/steam.js.map +1 -1
  34. package/dist/types.d.ts +91 -0
  35. package/dist/types.d.ts.map +1 -1
  36. package/dist/types.js +34 -0
  37. package/dist/types.js.map +1 -1
  38. package/package.json +4 -3
@@ -36,10 +36,38 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.SteamStatsManager = void 0;
37
37
  const koffi = __importStar(require("koffi"));
38
38
  /**
39
- * Manages Steam user statistics operations
40
- * Handles getting/setting stats, global stats, and friend stats
39
+ * SteamStatsManager
40
+ *
41
+ * Manages all Steam user statistics operations including:
42
+ * - User stats (get/set integer and float values)
43
+ * - Average rate stats (for tracking rates over time)
44
+ * - Friend/user stats (view stats of other players)
45
+ * - Global stats (aggregate statistics across all players)
46
+ * - Global stat history (historical data over time)
47
+ *
48
+ * Stats are different from achievements - they are numeric values that can increase/decrease
49
+ * and are used for leaderboards, progress tracking, and game analytics.
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * const statsManager = new SteamStatsManager(libraryLoader, apiCore);
54
+ *
55
+ * // Set and get player stats
56
+ * await statsManager.setStatInt('total_kills', 100);
57
+ * const kills = await statsManager.getStatInt('total_kills');
58
+ *
59
+ * // Get global stats
60
+ * await statsManager.requestGlobalStats(7);
61
+ * const globalKills = await statsManager.getGlobalStatInt('total_kills');
62
+ * ```
41
63
  */
42
64
  class SteamStatsManager {
65
+ /**
66
+ * Creates a new SteamStatsManager instance
67
+ *
68
+ * @param libraryLoader - The Steam library loader for FFI calls
69
+ * @param apiCore - The Steam API core for lifecycle management
70
+ */
43
71
  constructor(libraryLoader, apiCore) {
44
72
  this.libraryLoader = libraryLoader;
45
73
  this.apiCore = apiCore;
@@ -48,17 +76,35 @@ class SteamStatsManager {
48
76
  // User Stats Operations (Get/Set)
49
77
  // ========================================
50
78
  /**
51
- * Get an integer stat value
79
+ * Get an integer stat value for the current user
52
80
  *
53
- * @param statName - Name of the stat to retrieve
54
- * @returns The stat value, or null if not found/error
81
+ * Retrieves a 32-bit integer stat value from Steam. Stats are numeric values
82
+ * that track player progress and can be used for leaderboards and analytics.
83
+ *
84
+ * @param statName - Name of the stat to retrieve (as defined in Steamworks Partner site)
85
+ * @returns SteamStat object with name, value, and type, or null if not found or on error
86
+ *
87
+ * @example
88
+ * ```typescript
89
+ * const killsStat = await statsManager.getStatInt('total_kills');
90
+ * if (killsStat) {
91
+ * console.log(`[Steamworks] ${killsStat.name}: ${killsStat.value}`);
92
+ * // Access value directly: killsStat.value
93
+ * }
94
+ * ```
95
+ *
96
+ * @remarks
97
+ * - Returns null if Steam API is not initialized
98
+ * - Stat names are case-sensitive and must match Steamworks configuration
99
+ * - Use getStatFloat() for decimal values
100
+ * - Returns structured SteamStat object with full metadata
55
101
  *
56
102
  * Steamworks SDK Functions:
57
103
  * - `SteamAPI_ISteamUserStats_GetStatInt32()` - Get int32 stat value
58
104
  */
59
105
  async getStatInt(statName) {
60
106
  if (!this.apiCore.isInitialized()) {
61
- console.warn('⚠️ Steam API not initialized');
107
+ console.warn('[Steamworks] Steam API not initialized');
62
108
  return null;
63
109
  }
64
110
  try {
@@ -67,31 +113,51 @@ class SteamStatsManager {
67
113
  const success = this.libraryLoader.SteamAPI_ISteamUserStats_GetStatInt32(userStatsInterface, statName, valueOut);
68
114
  if (success) {
69
115
  const value = koffi.decode(valueOut, 'int32');
70
- console.log(`📊 Got stat "${statName}": ${value}`);
71
- return value;
116
+ console.log(`[Steamworks] Got stat "${statName}": ${value}`);
117
+ return {
118
+ name: statName,
119
+ value,
120
+ type: 'int'
121
+ };
72
122
  }
73
123
  else {
74
- console.warn(`⚠️ Failed to get stat: ${statName}`);
124
+ console.warn(`[Steamworks] Failed to get stat: ${statName}`);
75
125
  return null;
76
126
  }
77
127
  }
78
128
  catch (error) {
79
- console.error(`❌ Error getting stat "${statName}":`, error.message);
129
+ console.error(`[Steamworks] Error getting stat "${statName}":`, error.message);
80
130
  return null;
81
131
  }
82
132
  }
83
133
  /**
84
- * Get a float stat value
134
+ * Get a float stat value for the current user
85
135
  *
86
- * @param statName - Name of the stat to retrieve
87
- * @returns The stat value, or null if not found/error
136
+ * Retrieves a floating-point stat value from Steam. Use this for stats that
137
+ * require decimal precision (e.g., average accuracy, distance traveled).
138
+ *
139
+ * @param statName - Name of the stat to retrieve (as defined in Steamworks Partner site)
140
+ * @returns SteamStat object with name, value, and type, or null if not found or on error
141
+ *
142
+ * @example
143
+ * ```typescript
144
+ * const accuracyStat = await statsManager.getStatFloat('shooting_accuracy');
145
+ * if (accuracyStat) {
146
+ * console.log(`[Steamworks] ${accuracyStat.name}: ${(accuracyStat.value * 100).toFixed(2)}%`);
147
+ * }
148
+ * ```
149
+ *
150
+ * @remarks
151
+ * - Returns null if Steam API is not initialized
152
+ * - Use getStatInt() for whole number values
153
+ * - Returns structured SteamStat object with full metadata
88
154
  *
89
155
  * Steamworks SDK Functions:
90
156
  * - `SteamAPI_ISteamUserStats_GetStatFloat()` - Get float stat value
91
157
  */
92
158
  async getStatFloat(statName) {
93
159
  if (!this.apiCore.isInitialized()) {
94
- console.warn('⚠️ Steam API not initialized');
160
+ console.warn('[Steamworks] Steam API not initialized');
95
161
  return null;
96
162
  }
97
163
  try {
@@ -100,26 +166,49 @@ class SteamStatsManager {
100
166
  const success = this.libraryLoader.SteamAPI_ISteamUserStats_GetStatFloat(userStatsInterface, statName, valueOut);
101
167
  if (success) {
102
168
  const value = koffi.decode(valueOut, 'float');
103
- console.log(`📊 Got stat "${statName}": ${value}`);
104
- return value;
169
+ console.log(`[Steamworks] Got stat "${statName}": ${value}`);
170
+ return {
171
+ name: statName,
172
+ value,
173
+ type: 'float'
174
+ };
105
175
  }
106
176
  else {
107
- console.warn(`⚠️ Failed to get stat: ${statName}`);
177
+ console.warn(`[Steamworks] Failed to get stat: ${statName}`);
108
178
  return null;
109
179
  }
110
180
  }
111
181
  catch (error) {
112
- console.error(`❌ Error getting stat "${statName}":`, error.message);
182
+ console.error(`[Steamworks] Error getting stat "${statName}":`, error.message);
113
183
  return null;
114
184
  }
115
185
  }
116
186
  /**
117
- * Set an integer stat value
187
+ * Set an integer stat value for the current user
118
188
  *
119
- * @param statName - Name of the stat to set
189
+ * Updates a 32-bit integer stat value and immediately stores it to Steam servers.
190
+ * The new value will be visible in your Steam profile and can trigger achievements.
191
+ *
192
+ * @param statName - Name of the stat to set (as defined in Steamworks Partner site)
120
193
  * @param value - Integer value to set
121
194
  * @returns true if successful, false otherwise
122
195
  *
196
+ * @example
197
+ * ```typescript
198
+ * // Increment kill count
199
+ * const currentKills = await statsManager.getStatInt('total_kills') || 0;
200
+ * const success = await statsManager.setStatInt('total_kills', currentKills + 1);
201
+ * if (success) {
202
+ * console.log('[Steamworks] Kill count updated!');
203
+ * }
204
+ * ```
205
+ *
206
+ * @remarks
207
+ * - Automatically calls StoreStats() to save to Steam servers
208
+ * - Runs callbacks to process the store operation
209
+ * - Can trigger stat-based achievements
210
+ * - Use setStatFloat() for decimal values
211
+ *
123
212
  * Steamworks SDK Functions:
124
213
  * - `SteamAPI_ISteamUserStats_SetStatInt32()` - Set int32 stat value
125
214
  * - `SteamAPI_ISteamUserStats_StoreStats()` - Store stats to Steam servers
@@ -127,7 +216,7 @@ class SteamStatsManager {
127
216
  */
128
217
  async setStatInt(statName, value) {
129
218
  if (!this.apiCore.isInitialized()) {
130
- console.warn('⚠️ Steam API not initialized');
219
+ console.warn('[Steamworks] Steam API not initialized');
131
220
  return false;
132
221
  }
133
222
  try {
@@ -137,33 +226,50 @@ class SteamStatsManager {
137
226
  // Store the stats to Steam servers
138
227
  const stored = this.libraryLoader.SteamAPI_ISteamUserStats_StoreStats(userStatsInterface);
139
228
  if (stored) {
140
- console.log(`✅ Set stat "${statName}" to ${value}`);
229
+ console.log(`[Steamworks] Set stat "${statName}" to ${value}`);
141
230
  // Process callbacks
142
231
  this.libraryLoader.SteamAPI_RunCallbacks();
143
232
  return true;
144
233
  }
145
234
  else {
146
- console.warn(`⚠️ Failed to store stat: ${statName}`);
235
+ console.warn(`[Steamworks] Failed to store stat: ${statName}`);
147
236
  return false;
148
237
  }
149
238
  }
150
239
  else {
151
- console.warn(`⚠️ Failed to set stat: ${statName}`);
240
+ console.warn(`[Steamworks] Failed to set stat: ${statName}`);
152
241
  return false;
153
242
  }
154
243
  }
155
244
  catch (error) {
156
- console.error(`❌ Error setting stat "${statName}":`, error.message);
245
+ console.error(`[Steamworks] Error setting stat "${statName}":`, error.message);
157
246
  return false;
158
247
  }
159
248
  }
160
249
  /**
161
- * Set a float stat value
250
+ * Set a float stat value for the current user
162
251
  *
163
- * @param statName - Name of the stat to set
252
+ * Updates a floating-point stat value and immediately stores it to Steam servers.
253
+ * Use this for stats requiring decimal precision.
254
+ *
255
+ * @param statName - Name of the stat to set (as defined in Steamworks Partner site)
164
256
  * @param value - Float value to set
165
257
  * @returns true if successful, false otherwise
166
258
  *
259
+ * @example
260
+ * ```typescript
261
+ * // Update accuracy based on hits/shots
262
+ * const hits = 85;
263
+ * const shots = 100;
264
+ * const accuracy = hits / shots; // 0.85
265
+ * await statsManager.setStatFloat('shooting_accuracy', accuracy);
266
+ * ```
267
+ *
268
+ * @remarks
269
+ * - Automatically calls StoreStats() to save to Steam servers
270
+ * - Runs callbacks to process the store operation
271
+ * - Use setStatInt() for whole number values
272
+ *
167
273
  * Steamworks SDK Functions:
168
274
  * - `SteamAPI_ISteamUserStats_SetStatFloat()` - Set float stat value
169
275
  * - `SteamAPI_ISteamUserStats_StoreStats()` - Store stats to Steam servers
@@ -171,7 +277,7 @@ class SteamStatsManager {
171
277
  */
172
278
  async setStatFloat(statName, value) {
173
279
  if (!this.apiCore.isInitialized()) {
174
- console.warn('⚠️ Steam API not initialized');
280
+ console.warn('[Steamworks] Steam API not initialized');
175
281
  return false;
176
282
  }
177
283
  try {
@@ -181,35 +287,52 @@ class SteamStatsManager {
181
287
  // Store the stats to Steam servers
182
288
  const stored = this.libraryLoader.SteamAPI_ISteamUserStats_StoreStats(userStatsInterface);
183
289
  if (stored) {
184
- console.log(`✅ Set stat "${statName}" to ${value}`);
290
+ console.log(`[Steamworks] Set stat "${statName}" to ${value}`);
185
291
  // Process callbacks
186
292
  this.libraryLoader.SteamAPI_RunCallbacks();
187
293
  return true;
188
294
  }
189
295
  else {
190
- console.warn(`⚠️ Failed to store stat: ${statName}`);
296
+ console.warn(`[Steamworks] Failed to store stat: ${statName}`);
191
297
  return false;
192
298
  }
193
299
  }
194
300
  else {
195
- console.warn(`⚠️ Failed to set stat: ${statName}`);
301
+ console.warn(`[Steamworks] Failed to set stat: ${statName}`);
196
302
  return false;
197
303
  }
198
304
  }
199
305
  catch (error) {
200
- console.error(`❌ Error setting stat "${statName}":`, error.message);
306
+ console.error(`[Steamworks] Error setting stat "${statName}":`, error.message);
201
307
  return false;
202
308
  }
203
309
  }
204
310
  /**
205
311
  * Update an average rate stat
206
- * This is used for stats like "average speed" or "kills per hour"
207
312
  *
208
- * @param statName - Name of the stat to update
209
- * @param countThisSession - Count for this session
313
+ * Updates stats that represent rates or averages over time (e.g., "kills per hour",
314
+ * "average speed"). Steam automatically maintains the average calculation.
315
+ *
316
+ * @param statName - Name of the stat to update (as defined in Steamworks Partner site)
317
+ * @param countThisSession - Count/value for this session (e.g., kills this session)
210
318
  * @param sessionLength - Length of session in seconds
211
319
  * @returns true if successful, false otherwise
212
320
  *
321
+ * @example
322
+ * ```typescript
323
+ * // Update kills per hour stat
324
+ * const sessionKills = 25;
325
+ * const sessionSeconds = 1800; // 30 minutes
326
+ * await statsManager.updateAvgRateStat('kills_per_hour', sessionKills, sessionSeconds);
327
+ * // Steam calculates: (25 / 1800) * 3600 = 50 kills/hour
328
+ * ```
329
+ *
330
+ * @remarks
331
+ * - Automatically calls StoreStats() to save to Steam servers
332
+ * - Steam maintains the running average across all sessions
333
+ * - sessionLength should be in seconds
334
+ * - Used for "per hour" or "per game" statistics
335
+ *
213
336
  * Steamworks SDK Functions:
214
337
  * - `SteamAPI_ISteamUserStats_UpdateAvgRateStat()` - Update average rate stat
215
338
  * - `SteamAPI_ISteamUserStats_StoreStats()` - Store stats to Steam servers
@@ -217,7 +340,7 @@ class SteamStatsManager {
217
340
  */
218
341
  async updateAvgRateStat(statName, countThisSession, sessionLength) {
219
342
  if (!this.apiCore.isInitialized()) {
220
- console.warn('⚠️ Steam API not initialized');
343
+ console.warn('[Steamworks] Steam API not initialized');
221
344
  return false;
222
345
  }
223
346
  try {
@@ -227,23 +350,23 @@ class SteamStatsManager {
227
350
  // Store the stats to Steam servers
228
351
  const stored = this.libraryLoader.SteamAPI_ISteamUserStats_StoreStats(userStatsInterface);
229
352
  if (stored) {
230
- console.log(`✅ Updated avg rate stat "${statName}": ${countThisSession} over ${sessionLength}s`);
353
+ console.log(`[Steamworks] Updated avg rate stat "${statName}": ${countThisSession} over ${sessionLength}s`);
231
354
  // Process callbacks
232
355
  this.libraryLoader.SteamAPI_RunCallbacks();
233
356
  return true;
234
357
  }
235
358
  else {
236
- console.warn(`⚠️ Failed to store stat: ${statName}`);
359
+ console.warn(`[Steamworks] Failed to store stat: ${statName}`);
237
360
  return false;
238
361
  }
239
362
  }
240
363
  else {
241
- console.warn(`⚠️ Failed to update avg rate stat: ${statName}`);
364
+ console.warn(`[Steamworks] Failed to update avg rate stat: ${statName}`);
242
365
  return false;
243
366
  }
244
367
  }
245
368
  catch (error) {
246
- console.error(`❌ Error updating avg rate stat "${statName}":`, error.message);
369
+ console.error(`[Steamworks] Error updating avg rate stat "${statName}":`, error.message);
247
370
  return false;
248
371
  }
249
372
  }
@@ -252,17 +375,42 @@ class SteamStatsManager {
252
375
  // ========================================
253
376
  /**
254
377
  * Request stats for another user (friend)
255
- * Must be called before getting user stats
378
+ *
379
+ * Requests stat data from Steam servers for a specific user. Must be called
380
+ * before getting user stats with getUserStatInt() or getUserStatFloat().
381
+ * This is an asynchronous operation - wait a moment before reading the stats.
256
382
  *
257
383
  * @param steamId - Steam ID of the user (as string or BigInt)
258
- * @returns true if request was sent, false otherwise
384
+ * @returns true if request was sent successfully, false otherwise
385
+ *
386
+ * @example
387
+ * ```typescript
388
+ * const friendSteamId = '76561198012345678';
389
+ *
390
+ * // Request the friend's stats
391
+ * const success = await statsManager.requestUserStats(friendSteamId);
392
+ * if (success) {
393
+ * // Wait a moment for Steam to fetch the data
394
+ * await new Promise(resolve => setTimeout(resolve, 100));
395
+ *
396
+ * // Now get the friend's stats
397
+ * const friendKills = await statsManager.getUserStatInt(friendSteamId, 'total_kills');
398
+ * console.log(`[Steamworks] Friend has ${friendKills} kills`);
399
+ * }
400
+ * ```
401
+ *
402
+ * @remarks
403
+ * - Must be called before getting user stats
404
+ * - Returns true immediately if request was sent (not when data arrives)
405
+ * - Wait 50-100ms after requesting before reading stats
406
+ * - User's stats must be public for this to work
259
407
  *
260
408
  * Steamworks SDK Functions:
261
- * - `SteamAPI_ISteamUserStats_RequestUserStats()` - Request user stats from Steam
409
+ * - `SteamAPI_ISteamUserStats_RequestUserStats()` - Request user stats from Steam servers
262
410
  */
263
411
  async requestUserStats(steamId) {
264
412
  if (!this.apiCore.isInitialized()) {
265
- console.warn('⚠️ Steam API not initialized');
413
+ console.warn('[Steamworks] Steam API not initialized');
266
414
  return false;
267
415
  }
268
416
  try {
@@ -270,33 +418,56 @@ class SteamStatsManager {
270
418
  const steamIdBigInt = typeof steamId === 'string' ? BigInt(steamId) : steamId;
271
419
  const callHandle = this.libraryLoader.SteamAPI_ISteamUserStats_RequestUserStats(userStatsInterface, steamIdBigInt);
272
420
  if (callHandle !== BigInt(0)) {
273
- console.log(`📡 Requested stats for user: ${steamId}`);
421
+ console.log(`[Steamworks] Requested stats for user: ${steamId}`);
274
422
  return true;
275
423
  }
276
424
  else {
277
- console.warn(`⚠️ Failed to request user stats: ${steamId}`);
425
+ console.warn(`[Steamworks] Failed to request user stats: ${steamId}`);
278
426
  return false;
279
427
  }
280
428
  }
281
429
  catch (error) {
282
- console.error(`❌ Error requesting user stats:`, error.message);
430
+ console.error(`[Steamworks] Error requesting user stats:`, error.message);
283
431
  return false;
284
432
  }
285
433
  }
286
434
  /**
287
435
  * Get an integer stat value for another user (friend)
288
- * Must call requestUserStats() first and wait for callback
436
+ *
437
+ * Retrieves a 32-bit integer stat value for a specific user. Must call
438
+ * requestUserStats() first and wait for the data to arrive.
289
439
  *
290
440
  * @param steamId - Steam ID of the user
291
441
  * @param statName - Name of the stat to retrieve
292
- * @returns The stat value, or null if not found/error
442
+ * @returns UserStat object with steamId, name, value, and type, or null if not found or on error
443
+ *
444
+ * @example
445
+ * ```typescript
446
+ * // Compare your kills to a friend's
447
+ * const friendId = '76561198012345678';
448
+ * await statsManager.requestUserStats(friendId);
449
+ * await new Promise(resolve => setTimeout(resolve, 100));
450
+ *
451
+ * const myStat = await statsManager.getStatInt('total_kills');
452
+ * const friendStat = await statsManager.getUserStatInt(friendId, 'total_kills');
453
+ *
454
+ * if (myStat && friendStat) {
455
+ * console.log(`[Steamworks] You: ${myStat.value}, Friend: ${friendStat.value}`);
456
+ * }
457
+ * ```
458
+ *
459
+ * @remarks
460
+ * - Must call requestUserStats() first
461
+ * - Wait 50-100ms after requesting before calling this
462
+ * - Returns null if stats haven't arrived yet or user's stats are private
463
+ * - Returns structured UserStat object with full metadata including steamId
293
464
  *
294
465
  * Steamworks SDK Functions:
295
466
  * - `SteamAPI_ISteamUserStats_GetUserStatInt32()` - Get user's int32 stat value
296
467
  */
297
468
  async getUserStatInt(steamId, statName) {
298
469
  if (!this.apiCore.isInitialized()) {
299
- console.warn('⚠️ Steam API not initialized');
470
+ console.warn('[Steamworks] Steam API not initialized');
300
471
  return null;
301
472
  }
302
473
  try {
@@ -306,33 +477,59 @@ class SteamStatsManager {
306
477
  const success = this.libraryLoader.SteamAPI_ISteamUserStats_GetUserStatInt32(userStatsInterface, steamIdBigInt, statName, valueOut);
307
478
  if (success) {
308
479
  const value = koffi.decode(valueOut, 'int32');
309
- console.log(`📊 Got user stat "${statName}" for ${steamId}: ${value}`);
310
- return value;
480
+ console.log(`[Steamworks] Got user stat "${statName}" for ${steamId}: ${value}`);
481
+ return {
482
+ steamId: typeof steamId === 'string' ? steamId : steamId.toString(),
483
+ name: statName,
484
+ value,
485
+ type: 'int'
486
+ };
311
487
  }
312
488
  else {
313
- console.warn(`⚠️ Failed to get user stat: ${statName}`);
489
+ console.warn(`[Steamworks] Failed to get user stat: ${statName}`);
314
490
  return null;
315
491
  }
316
492
  }
317
493
  catch (error) {
318
- console.error(`❌ Error getting user stat "${statName}":`, error.message);
494
+ console.error(`[Steamworks] Error getting user stat "${statName}":`, error.message);
319
495
  return null;
320
496
  }
321
497
  }
322
498
  /**
323
499
  * Get a float stat value for another user (friend)
324
- * Must call requestUserStats() first and wait for callback
500
+ *
501
+ * Retrieves a floating-point stat value for a specific user. Must call
502
+ * requestUserStats() first and wait for the data to arrive.
325
503
  *
326
504
  * @param steamId - Steam ID of the user
327
505
  * @param statName - Name of the stat to retrieve
328
- * @returns The stat value, or null if not found/error
506
+ * @returns UserStat object with steamId, name, value, and type, or null if not found or on error
507
+ *
508
+ * @example
509
+ * ```typescript
510
+ * // Compare accuracy with a friend
511
+ * const friendId = '76561198012345678';
512
+ * await statsManager.requestUserStats(friendId);
513
+ * await new Promise(resolve => setTimeout(resolve, 100));
514
+ *
515
+ * const friendStat = await statsManager.getUserStatFloat(friendId, 'shooting_accuracy');
516
+ * if (friendStat) {
517
+ * console.log(`[Steamworks] Friend accuracy: ${(friendStat.value * 100).toFixed(2)}%`);
518
+ * }
519
+ * ```
520
+ *
521
+ * @remarks
522
+ * - Must call requestUserStats() first
523
+ * - Wait 50-100ms after requesting before calling this
524
+ * - Returns null if stats haven't arrived yet or user's stats are private
525
+ * - Returns structured UserStat object with full metadata including steamId
329
526
  *
330
527
  * Steamworks SDK Functions:
331
528
  * - `SteamAPI_ISteamUserStats_GetUserStatFloat()` - Get user's float stat value
332
529
  */
333
530
  async getUserStatFloat(steamId, statName) {
334
531
  if (!this.apiCore.isInitialized()) {
335
- console.warn('⚠️ Steam API not initialized');
532
+ console.warn('[Steamworks] Steam API not initialized');
336
533
  return null;
337
534
  }
338
535
  try {
@@ -342,16 +539,21 @@ class SteamStatsManager {
342
539
  const success = this.libraryLoader.SteamAPI_ISteamUserStats_GetUserStatFloat(userStatsInterface, steamIdBigInt, statName, valueOut);
343
540
  if (success) {
344
541
  const value = koffi.decode(valueOut, 'float');
345
- console.log(`📊 Got user stat "${statName}" for ${steamId}: ${value}`);
346
- return value;
542
+ console.log(`[Steamworks] Got user stat "${statName}" for ${steamId}: ${value}`);
543
+ return {
544
+ steamId: typeof steamId === 'string' ? steamId : steamId.toString(),
545
+ name: statName,
546
+ value,
547
+ type: 'float'
548
+ };
347
549
  }
348
550
  else {
349
- console.warn(`⚠️ Failed to get user stat: ${statName}`);
551
+ console.warn(`[Steamworks] Failed to get user stat: ${statName}`);
350
552
  return null;
351
553
  }
352
554
  }
353
555
  catch (error) {
354
- console.error(`❌ Error getting user stat "${statName}":`, error.message);
556
+ console.error(`[Steamworks] Error getting user stat "${statName}":`, error.message);
355
557
  return null;
356
558
  }
357
559
  }
@@ -360,17 +562,40 @@ class SteamStatsManager {
360
562
  // ========================================
361
563
  /**
362
564
  * Request global stats data from Steam
363
- * Must be called before getting global stats
364
565
  *
365
- * @param historyDays - Number of days of history to retrieve (0-60)
366
- * @returns true if request was sent, false otherwise
566
+ * Requests aggregated statistics across all players from Steam servers.
567
+ * Optionally includes historical data for trend analysis.
568
+ * Must be called before getting global stats.
569
+ *
570
+ * @param historyDays - Number of days of history to retrieve (0-60, default: 0)
571
+ * @returns true if request was sent successfully, false otherwise
572
+ *
573
+ * @example
574
+ * ```typescript
575
+ * // Request current global stats (no history)
576
+ * await statsManager.requestGlobalStats(0);
577
+ *
578
+ * // Request with 7 days of history for trends
579
+ * await statsManager.requestGlobalStats(7);
580
+ * await new Promise(resolve => setTimeout(resolve, 100));
581
+ *
582
+ * const totalKills = await statsManager.getGlobalStatInt('total_kills');
583
+ * console.log(`[Steamworks] All players combined: ${totalKills} kills`);
584
+ * ```
585
+ *
586
+ * @remarks
587
+ * - Must be called before getting global stats
588
+ * - Returns true immediately if request was sent (not when data arrives)
589
+ * - Wait 50-100ms after requesting before reading stats
590
+ * - historyDays is automatically clamped to 0-60 range
591
+ * - Historical data allows tracking trends over time
367
592
  *
368
593
  * Steamworks SDK Functions:
369
- * - `SteamAPI_ISteamUserStats_RequestGlobalStats()` - Request global stats from Steam
594
+ * - `SteamAPI_ISteamUserStats_RequestGlobalStats()` - Request global stats from Steam servers
370
595
  */
371
596
  async requestGlobalStats(historyDays = 0) {
372
597
  if (!this.apiCore.isInitialized()) {
373
- console.warn('⚠️ Steam API not initialized');
598
+ console.warn('[Steamworks] Steam API not initialized');
374
599
  return false;
375
600
  }
376
601
  try {
@@ -379,32 +604,53 @@ class SteamStatsManager {
379
604
  const userStatsInterface = this.libraryLoader.SteamAPI_SteamUserStats_v013();
380
605
  const callHandle = this.libraryLoader.SteamAPI_ISteamUserStats_RequestGlobalStats(userStatsInterface, days);
381
606
  if (callHandle !== BigInt(0)) {
382
- console.log(`📡 Requested global stats with ${days} days of history`);
607
+ console.log(`[Steamworks] Requested global stats with ${days} days of history`);
383
608
  return true;
384
609
  }
385
610
  else {
386
- console.warn(`⚠️ Failed to request global stats`);
611
+ console.warn(`[Steamworks] Failed to request global stats`);
387
612
  return false;
388
613
  }
389
614
  }
390
615
  catch (error) {
391
- console.error(`❌ Error requesting global stats:`, error.message);
616
+ console.error(`[Steamworks] Error requesting global stats:`, error.message);
392
617
  return false;
393
618
  }
394
619
  }
395
620
  /**
396
- * Get a global stat value (int64)
397
- * Must call requestGlobalStats() first and wait for callback
621
+ * Get a global stat value (64-bit integer)
622
+ *
623
+ * Retrieves an aggregated integer stat value across all players.
624
+ * Must call requestGlobalStats() first and wait for the data to arrive.
398
625
  *
399
626
  * @param statName - Name of the global stat to retrieve
400
- * @returns The stat value, or null if not found/error
627
+ * @returns GlobalStat object with name, value, and type, or null if not found or on error
628
+ *
629
+ * @example
630
+ * ```typescript
631
+ * // Get total kills across all players
632
+ * await statsManager.requestGlobalStats();
633
+ * await new Promise(resolve => setTimeout(resolve, 100));
634
+ *
635
+ * const globalStat = await statsManager.getGlobalStatInt('total_kills');
636
+ * if (globalStat) {
637
+ * console.log(`[Steamworks] ${globalStat.name}: ${globalStat.value}`);
638
+ * }
639
+ * ```
640
+ *
641
+ * @remarks
642
+ * - Must call requestGlobalStats() first
643
+ * - Wait 50-100ms after requesting before calling this
644
+ * - Useful for tracking game-wide statistics
645
+ * - Returns structured GlobalStat object with full metadata
646
+ * - Value is number (BigInt converted to number for consistency)
401
647
  *
402
648
  * Steamworks SDK Functions:
403
649
  * - `SteamAPI_ISteamUserStats_GetGlobalStatInt64()` - Get global int64 stat value
404
650
  */
405
651
  async getGlobalStatInt(statName) {
406
652
  if (!this.apiCore.isInitialized()) {
407
- console.warn('⚠️ Steam API not initialized');
653
+ console.warn('[Steamworks] Steam API not initialized');
408
654
  return null;
409
655
  }
410
656
  try {
@@ -413,32 +659,58 @@ class SteamStatsManager {
413
659
  const success = this.libraryLoader.SteamAPI_ISteamUserStats_GetGlobalStatInt64(userStatsInterface, statName, valueOut);
414
660
  if (success) {
415
661
  const value = koffi.decode(valueOut, 'int64');
416
- console.log(`🌍 Got global stat "${statName}": ${value}`);
417
- return value;
662
+ console.log(`[Steamworks] Got global stat "${statName}": ${value}`);
663
+ return {
664
+ name: statName,
665
+ value: Number(value),
666
+ type: 'int64'
667
+ };
418
668
  }
419
669
  else {
420
- console.warn(`⚠️ Failed to get global stat: ${statName}`);
670
+ console.warn(`[Steamworks] Failed to get global stat: ${statName}`);
421
671
  return null;
422
672
  }
423
673
  }
424
674
  catch (error) {
425
- console.error(`❌ Error getting global stat "${statName}":`, error.message);
675
+ console.error(`[Steamworks] Error getting global stat "${statName}":`, error.message);
426
676
  return null;
427
677
  }
428
678
  }
429
679
  /**
430
- * Get a global stat value (double)
431
- * Must call requestGlobalStats() first and wait for callback
680
+ * Get a global stat value (double-precision float)
681
+ *
682
+ * Retrieves an aggregated floating-point stat value across all players.
683
+ * Must call requestGlobalStats() first and wait for the data to arrive.
684
+ * Use this for averages or stats requiring decimal precision.
432
685
  *
433
686
  * @param statName - Name of the global stat to retrieve
434
- * @returns The stat value, or null if not found/error
687
+ * @returns GlobalStat object with name, value, and type, or null if not found or on error
688
+ *
689
+ * @example
690
+ * ```typescript
691
+ * // Get average accuracy across all players
692
+ * await statsManager.requestGlobalStats();
693
+ * await new Promise(resolve => setTimeout(resolve, 100));
694
+ *
695
+ * const avgStat = await statsManager.getGlobalStatDouble('average_accuracy');
696
+ * if (avgStat) {
697
+ * console.log(`[Steamworks] ${avgStat.name}: ${(avgStat.value * 100).toFixed(2)}%`);
698
+ * }
699
+ * ```
700
+ *
701
+ * @remarks
702
+ * - Use for stats requiring decimal precision
703
+ * - Must call requestGlobalStats() first
704
+ * - Wait 50-100ms after requesting before calling this
705
+ * - Perfect for calculating game-wide averages
706
+ * - Returns structured GlobalStat object with full metadata
435
707
  *
436
708
  * Steamworks SDK Functions:
437
709
  * - `SteamAPI_ISteamUserStats_GetGlobalStatDouble()` - Get global double stat value
438
710
  */
439
711
  async getGlobalStatDouble(statName) {
440
712
  if (!this.apiCore.isInitialized()) {
441
- console.warn('⚠️ Steam API not initialized');
713
+ console.warn('[Steamworks] Steam API not initialized');
442
714
  return null;
443
715
  }
444
716
  try {
@@ -447,33 +719,63 @@ class SteamStatsManager {
447
719
  const success = this.libraryLoader.SteamAPI_ISteamUserStats_GetGlobalStatDouble(userStatsInterface, statName, valueOut);
448
720
  if (success) {
449
721
  const value = koffi.decode(valueOut, 'double');
450
- console.log(`🌍 Got global stat "${statName}": ${value}`);
451
- return value;
722
+ console.log(`[Steamworks] Got global stat "${statName}": ${value}`);
723
+ return {
724
+ name: statName,
725
+ value,
726
+ type: 'double'
727
+ };
452
728
  }
453
729
  else {
454
- console.warn(`⚠️ Failed to get global stat: ${statName}`);
730
+ console.warn(`[Steamworks] Failed to get global stat: ${statName}`);
455
731
  return null;
456
732
  }
457
733
  }
458
734
  catch (error) {
459
- console.error(`❌ Error getting global stat "${statName}":`, error.message);
735
+ console.error(`[Steamworks] Error getting global stat "${statName}":`, error.message);
460
736
  return null;
461
737
  }
462
738
  }
463
739
  /**
464
- * Get global stat history (int64)
465
- * Returns daily values for the stat, with [0] being today, [1] yesterday, etc.
740
+ * Get global stat history for an integer stat
741
+ *
742
+ * Retrieves historical daily values for a global stat. Array index [0] is today,
743
+ * [1] is yesterday, etc. Useful for tracking trends and creating graphs.
466
744
  *
467
745
  * @param statName - Name of the global stat
468
- * @param days - Number of days of history to retrieve (max 60)
469
- * @returns Array of daily values, or null if error
746
+ * @param days - Number of days of history to retrieve (1-60, default: 7)
747
+ * @returns GlobalStatHistory object with name, history array, and type, or null if error
748
+ *
749
+ * @example
750
+ * ```typescript
751
+ * // Get 7 days of global kill history
752
+ * await statsManager.requestGlobalStats(7);
753
+ * await new Promise(resolve => setTimeout(resolve, 100));
754
+ *
755
+ * const history = await statsManager.getGlobalStatHistoryInt('total_kills', 7);
756
+ * if (history) {
757
+ * console.log(`[Steamworks] ${history.name} history (${history.type}):`);
758
+ * history.history.forEach((kills, index) => {
759
+ * const daysAgo = index === 0 ? 'today' : `${index} days ago`;
760
+ * console.log(`[Steamworks] ${daysAgo}: ${kills} kills`);
761
+ * });
762
+ * }
763
+ * ```
764
+ *
765
+ * @remarks
766
+ * - Array index [0] = today, [1] = yesterday, etc.
767
+ * - Must call requestGlobalStats(days) first with same or greater number of days
768
+ * - Returns structured GlobalStatHistory object with full metadata
769
+ * - Values are numbers (BigInt converted to number for consistency)
770
+ * - Maximum 60 days of history (automatically clamped)
771
+ * - Perfect for trend analysis and visualizations
470
772
  *
471
773
  * Steamworks SDK Functions:
472
- * - `SteamAPI_ISteamUserStats_GetGlobalStatHistoryInt64()` - Get global stat history
774
+ * - `SteamAPI_ISteamUserStats_GetGlobalStatHistoryInt64()` - Get historical int64 values
473
775
  */
474
776
  async getGlobalStatHistoryInt(statName, days = 7) {
475
777
  if (!this.apiCore.isInitialized()) {
476
- console.warn('⚠️ Steam API not initialized');
778
+ console.warn('[Steamworks] Steam API not initialized');
477
779
  return null;
478
780
  }
479
781
  try {
@@ -485,35 +787,67 @@ class SteamStatsManager {
485
787
  if (elementsReturned > 0) {
486
788
  const history = [];
487
789
  for (let i = 0; i < elementsReturned; i++) {
488
- history.push(koffi.decode(historyOut, 'int64', i));
790
+ history.push(Number(koffi.decode(historyOut, 'int64', i))); // Convert BigInt to number
489
791
  }
490
- console.log(`🌍 Got ${elementsReturned} days of history for "${statName}"`);
491
- return history;
792
+ console.log(`[Steamworks] Got ${elementsReturned} days of history for "${statName}"`);
793
+ return {
794
+ name: statName,
795
+ history,
796
+ type: 'int64'
797
+ };
492
798
  }
493
799
  else {
494
- console.warn(`⚠️ Failed to get global stat history: ${statName}`);
800
+ console.warn(`[Steamworks] Failed to get global stat history: ${statName}`);
495
801
  return null;
496
802
  }
497
803
  }
498
804
  catch (error) {
499
- console.error(`❌ Error getting global stat history "${statName}":`, error.message);
805
+ console.error(`[Steamworks] Error getting global stat history "${statName}":`, error.message);
500
806
  return null;
501
807
  }
502
808
  }
503
809
  /**
504
- * Get global stat history (double)
505
- * Returns daily values for the stat, with [0] being today, [1] yesterday, etc.
810
+ * Get global stat history for a floating-point stat
811
+ *
812
+ * Retrieves historical daily values for a global stat with decimal precision.
813
+ * Array index [0] is today, [1] is yesterday, etc. Ideal for tracking averages
814
+ * and rates over time.
506
815
  *
507
816
  * @param statName - Name of the global stat
508
- * @param days - Number of days of history to retrieve (max 60)
509
- * @returns Array of daily values, or null if error
817
+ * @param days - Number of days of history to retrieve (1-60, default: 7)
818
+ * @returns GlobalStatHistory object with name, history array, and type, or null if error
819
+ *
820
+ * @example
821
+ * ```typescript
822
+ * // Track average accuracy trend over 30 days
823
+ * await statsManager.requestGlobalStats(30);
824
+ * await new Promise(resolve => setTimeout(resolve, 100));
825
+ *
826
+ * const history = await statsManager.getGlobalStatHistoryDouble('average_accuracy', 30);
827
+ * if (history) {
828
+ * console.log(`[Steamworks] ${history.name} trend (${history.type}):`);
829
+ * history.history.forEach((accuracy, index) => {
830
+ * if (index % 7 === 0) { // Weekly intervals
831
+ * console.log(`[Steamworks] Week ${index/7}: ${(accuracy * 100).toFixed(2)}%`);
832
+ * }
833
+ * });
834
+ * }
835
+ * ```
836
+ *
837
+ * @remarks
838
+ * - Array index [0] = today, [1] = yesterday, etc.
839
+ * - Must call requestGlobalStats(days) first with same or greater number of days
840
+ * - Returns structured GlobalStatHistory object with full metadata
841
+ * - Returns floating-point values for decimal precision
842
+ * - Maximum 60 days of history (automatically clamped)
843
+ * - Perfect for tracking averages, rates, and percentages over time
510
844
  *
511
845
  * Steamworks SDK Functions:
512
- * - `SteamAPI_ISteamUserStats_GetGlobalStatHistoryDouble()` - Get global stat history
846
+ * - `SteamAPI_ISteamUserStats_GetGlobalStatHistoryDouble()` - Get historical double values
513
847
  */
514
848
  async getGlobalStatHistoryDouble(statName, days = 7) {
515
849
  if (!this.apiCore.isInitialized()) {
516
- console.warn('⚠️ Steam API not initialized');
850
+ console.warn('[Steamworks] Steam API not initialized');
517
851
  return null;
518
852
  }
519
853
  try {
@@ -527,16 +861,20 @@ class SteamStatsManager {
527
861
  for (let i = 0; i < elementsReturned; i++) {
528
862
  history.push(koffi.decode(historyOut, 'double', i));
529
863
  }
530
- console.log(`🌍 Got ${elementsReturned} days of history for "${statName}"`);
531
- return history;
864
+ console.log(`[Steamworks] Got ${elementsReturned} days of history for "${statName}"`);
865
+ return {
866
+ name: statName,
867
+ history,
868
+ type: 'double'
869
+ };
532
870
  }
533
871
  else {
534
- console.warn(`⚠️ Failed to get global stat history: ${statName}`);
872
+ console.warn(`[Steamworks] Failed to get global stat history: ${statName}`);
535
873
  return null;
536
874
  }
537
875
  }
538
876
  catch (error) {
539
- console.error(`❌ Error getting global stat history "${statName}":`, error.message);
877
+ console.error(`[Steamworks] Error getting global stat history "${statName}":`, error.message);
540
878
  return null;
541
879
  }
542
880
  }