steamworks-ffi-node 0.1.1 → 0.2.1
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/README.md +63 -129
- package/THIRD_PARTY_LICENSES.md +24 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -18
- package/dist/index.js.map +1 -1
- package/dist/internal/SteamAPICore.d.ts +46 -0
- package/dist/internal/SteamAPICore.d.ts.map +1 -0
- package/dist/internal/SteamAPICore.js +185 -0
- package/dist/internal/SteamAPICore.js.map +1 -0
- package/dist/internal/SteamAchievementManager.d.ts +117 -0
- package/dist/internal/SteamAchievementManager.d.ts.map +1 -0
- package/dist/internal/SteamAchievementManager.js +673 -0
- package/dist/internal/SteamAchievementManager.js.map +1 -0
- package/dist/internal/SteamLibraryLoader.d.ts +52 -0
- package/dist/internal/SteamLibraryLoader.d.ts.map +1 -0
- package/dist/internal/SteamLibraryLoader.js +134 -0
- package/dist/internal/SteamLibraryLoader.js.map +1 -0
- package/dist/steam.d.ts +88 -30
- package/dist/steam.d.ts.map +1 -1
- package/dist/steam.js +126 -316
- package/dist/steam.js.map +1 -1
- package/dist/types.d.ts +23 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -4
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.SteamAchievementManager = void 0;
|
|
37
|
+
const koffi = __importStar(require("koffi"));
|
|
38
|
+
/**
|
|
39
|
+
* Manages all Steam achievement operations
|
|
40
|
+
*/
|
|
41
|
+
class SteamAchievementManager {
|
|
42
|
+
constructor(libraryLoader, apiCore) {
|
|
43
|
+
this.libraryLoader = libraryLoader;
|
|
44
|
+
this.apiCore = apiCore;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get all achievements from Steam
|
|
48
|
+
*/
|
|
49
|
+
async getAllAchievements() {
|
|
50
|
+
if (!this.apiCore.isInitialized()) {
|
|
51
|
+
console.warn('[Steamworks] WARNING: Steam API not initialized');
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
const userStatsInterface = this.apiCore.getUserStatsInterface();
|
|
55
|
+
if (!userStatsInterface) {
|
|
56
|
+
console.warn('[Steamworks] WARNING: UserStats interface not available');
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
// Run callbacks to ensure we have latest data
|
|
61
|
+
this.apiCore.runCallbacks();
|
|
62
|
+
const achievements = [];
|
|
63
|
+
const numAchievements = this.libraryLoader.SteamAPI_ISteamUserStats_GetNumAchievements(userStatsInterface);
|
|
64
|
+
console.log(`[Steamworks] Found ${numAchievements} achievements in Steam`);
|
|
65
|
+
for (let i = 0; i < numAchievements; i++) {
|
|
66
|
+
try {
|
|
67
|
+
// Get achievement API name
|
|
68
|
+
const apiName = this.libraryLoader.SteamAPI_ISteamUserStats_GetAchievementName(userStatsInterface, i);
|
|
69
|
+
if (!apiName)
|
|
70
|
+
continue;
|
|
71
|
+
// Get display name
|
|
72
|
+
const displayName = this.libraryLoader.SteamAPI_ISteamUserStats_GetAchievementDisplayAttribute(userStatsInterface, apiName, 'name') || apiName;
|
|
73
|
+
// Get description
|
|
74
|
+
const description = this.libraryLoader.SteamAPI_ISteamUserStats_GetAchievementDisplayAttribute(userStatsInterface, apiName, 'desc') || '';
|
|
75
|
+
// Check if unlocked and get unlock time
|
|
76
|
+
const unlockedPtr = koffi.alloc('bool', 1);
|
|
77
|
+
const unlockTimePtr = koffi.alloc('uint32', 1);
|
|
78
|
+
const hasAchievement = this.libraryLoader.SteamAPI_ISteamUserStats_GetAchievementAndUnlockTime(userStatsInterface, apiName, unlockedPtr, unlockTimePtr);
|
|
79
|
+
const unlocked = hasAchievement ? koffi.decode(unlockedPtr, 'bool') : false;
|
|
80
|
+
const unlockTime = hasAchievement && unlocked ? koffi.decode(unlockTimePtr, 'uint32') : 0;
|
|
81
|
+
achievements.push({
|
|
82
|
+
apiName,
|
|
83
|
+
displayName,
|
|
84
|
+
description,
|
|
85
|
+
unlocked,
|
|
86
|
+
unlockTime // Now returns actual Steam unlock time (Unix timestamp)
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
console.warn(`Failed to get achievement ${i}:`, error.message);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return achievements;
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
console.error('[Steamworks] ERROR: Failed to get achievements:', error.message);
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Unlock achievement in Steam
|
|
102
|
+
*/
|
|
103
|
+
async unlockAchievement(achievementName) {
|
|
104
|
+
if (!this.apiCore.isInitialized()) {
|
|
105
|
+
console.warn('[Steamworks] WARNING: Steam API not initialized');
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
const userStatsInterface = this.apiCore.getUserStatsInterface();
|
|
109
|
+
if (!userStatsInterface) {
|
|
110
|
+
console.warn('[Steamworks] WARNING: UserStats interface not available');
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
console.log(`[Steamworks] Attempting to unlock achievement: ${achievementName}`);
|
|
115
|
+
// Set the achievement
|
|
116
|
+
const result = this.libraryLoader.SteamAPI_ISteamUserStats_SetAchievement(userStatsInterface, achievementName);
|
|
117
|
+
if (result) {
|
|
118
|
+
// Store stats to commit the achievement to Steam servers
|
|
119
|
+
const storeResult = this.libraryLoader.SteamAPI_ISteamUserStats_StoreStats(userStatsInterface);
|
|
120
|
+
if (storeResult) {
|
|
121
|
+
// Run callbacks to process the achievement unlock
|
|
122
|
+
this.apiCore.runCallbacks();
|
|
123
|
+
console.log(`[Steamworks] Achievement unlocked successfully: ${achievementName}`);
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
console.error(`[Steamworks] ERROR: Failed to store stats for achievement: ${achievementName}`);
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
console.error(`[Steamworks] ERROR: Failed to set achievement: ${achievementName}`);
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
console.error(`[Steamworks] ERROR: Error unlocking achievement ${achievementName}:`, error.message);
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Clear achievement in Steam (for testing)
|
|
143
|
+
*/
|
|
144
|
+
async clearAchievement(achievementName) {
|
|
145
|
+
if (!this.apiCore.isInitialized()) {
|
|
146
|
+
console.warn('[Steamworks] WARNING: Steam API not initialized');
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
const userStatsInterface = this.apiCore.getUserStatsInterface();
|
|
150
|
+
if (!userStatsInterface) {
|
|
151
|
+
console.warn('[Steamworks] WARNING: UserStats interface not available');
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
console.log(`[Steamworks] Attempting to clear achievement: ${achievementName}`);
|
|
156
|
+
// Clear the achievement
|
|
157
|
+
const result = this.libraryLoader.SteamAPI_ISteamUserStats_ClearAchievement(userStatsInterface, achievementName);
|
|
158
|
+
if (result) {
|
|
159
|
+
// Store stats to commit the change to Steam servers
|
|
160
|
+
const storeResult = this.libraryLoader.SteamAPI_ISteamUserStats_StoreStats(userStatsInterface);
|
|
161
|
+
if (storeResult) {
|
|
162
|
+
// Run callbacks to process the change
|
|
163
|
+
this.apiCore.runCallbacks();
|
|
164
|
+
console.log(`[Steamworks] Achievement cleared successfully: ${achievementName}`);
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
console.error(`[Steamworks] ERROR: Failed to store stats for clearing achievement: ${achievementName}`);
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
console.error(`[Steamworks] ERROR: Failed to clear achievement: ${achievementName}`);
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
console.error(`[Steamworks] ERROR: Error clearing achievement ${achievementName}:`, error.message);
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Check if achievement is unlocked
|
|
184
|
+
*/
|
|
185
|
+
async isAchievementUnlocked(achievementName) {
|
|
186
|
+
if (!this.apiCore.isInitialized()) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
const userStatsInterface = this.apiCore.getUserStatsInterface();
|
|
190
|
+
if (!userStatsInterface) {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
try {
|
|
194
|
+
const unlockedPtr = koffi.alloc('bool', 1);
|
|
195
|
+
const unlockTimePtr = koffi.alloc('uint32', 1);
|
|
196
|
+
const hasAchievement = this.libraryLoader.SteamAPI_ISteamUserStats_GetAchievementAndUnlockTime(userStatsInterface, achievementName, unlockedPtr, unlockTimePtr);
|
|
197
|
+
return hasAchievement ? koffi.decode(unlockedPtr, 'bool') : false;
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
console.error(`[Steamworks] ERROR: Error checking achievement ${achievementName}:`, error.message);
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Get achievement by API name
|
|
206
|
+
*/
|
|
207
|
+
async getAchievementByName(achievementName) {
|
|
208
|
+
const achievements = await this.getAllAchievements();
|
|
209
|
+
return achievements.find(a => a.apiName === achievementName) || null;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Get total number of achievements
|
|
213
|
+
*/
|
|
214
|
+
async getTotalAchievementCount() {
|
|
215
|
+
if (!this.apiCore.isInitialized()) {
|
|
216
|
+
return 0;
|
|
217
|
+
}
|
|
218
|
+
const userStatsInterface = this.apiCore.getUserStatsInterface();
|
|
219
|
+
if (!userStatsInterface) {
|
|
220
|
+
return 0;
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
return this.libraryLoader.SteamAPI_ISteamUserStats_GetNumAchievements(userStatsInterface);
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
console.error('[Steamworks] ERROR: Error getting achievement count:', error.message);
|
|
227
|
+
return 0;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Get number of unlocked achievements
|
|
232
|
+
*/
|
|
233
|
+
async getUnlockedAchievementCount() {
|
|
234
|
+
const achievements = await this.getAllAchievements();
|
|
235
|
+
return achievements.filter(a => a.unlocked).length;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Get achievement icon handle
|
|
239
|
+
* Returns icon handle for use with ISteamUtils::GetImageRGBA()
|
|
240
|
+
* Returns 0 if no icon set or still loading
|
|
241
|
+
*/
|
|
242
|
+
async getAchievementIcon(achievementName) {
|
|
243
|
+
if (!this.apiCore.isInitialized()) {
|
|
244
|
+
console.warn('[Steamworks] WARNING: Steam API not initialized');
|
|
245
|
+
return 0;
|
|
246
|
+
}
|
|
247
|
+
const userStatsInterface = this.apiCore.getUserStatsInterface();
|
|
248
|
+
if (!userStatsInterface) {
|
|
249
|
+
console.warn('[Steamworks] WARNING: UserStats interface not available');
|
|
250
|
+
return 0;
|
|
251
|
+
}
|
|
252
|
+
try {
|
|
253
|
+
const iconHandle = this.libraryLoader.SteamAPI_ISteamUserStats_GetAchievementIcon(userStatsInterface, achievementName);
|
|
254
|
+
console.log(`[Steamworks] Achievement icon handle for ${achievementName}: ${iconHandle}`);
|
|
255
|
+
return iconHandle;
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
console.error(`[Steamworks] ERROR: Error getting achievement icon:`, error.message);
|
|
259
|
+
return 0;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Indicate achievement progress to user
|
|
264
|
+
* Shows a progress notification in Steam overlay
|
|
265
|
+
*/
|
|
266
|
+
async indicateAchievementProgress(achievementName, currentProgress, maxProgress) {
|
|
267
|
+
if (!this.apiCore.isInitialized()) {
|
|
268
|
+
console.warn('[Steamworks] WARNING: Steam API not initialized');
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
const userStatsInterface = this.apiCore.getUserStatsInterface();
|
|
272
|
+
if (!userStatsInterface) {
|
|
273
|
+
console.warn('[Steamworks] WARNING: UserStats interface not available');
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
console.log(`[Steamworks] Indicating achievement progress: ${achievementName} (${currentProgress}/${maxProgress})`);
|
|
278
|
+
const result = this.libraryLoader.SteamAPI_ISteamUserStats_IndicateAchievementProgress(userStatsInterface, achievementName, currentProgress, maxProgress);
|
|
279
|
+
if (result) {
|
|
280
|
+
this.apiCore.runCallbacks();
|
|
281
|
+
console.log(`[Steamworks] Achievement progress indicated successfully`);
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
console.error(`[Steamworks] ERROR: Failed to indicate achievement progress`);
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
catch (error) {
|
|
290
|
+
console.error(`[Steamworks] ERROR: Error indicating achievement progress:`, error.message);
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Get achievement progress limits (for integer-based progress)
|
|
296
|
+
*/
|
|
297
|
+
async getAchievementProgressLimitsInt(achievementName) {
|
|
298
|
+
if (!this.apiCore.isInitialized()) {
|
|
299
|
+
console.warn('[Steamworks] WARNING: Steam API not initialized');
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
const userStatsInterface = this.apiCore.getUserStatsInterface();
|
|
303
|
+
if (!userStatsInterface) {
|
|
304
|
+
console.warn('[Steamworks] WARNING: UserStats interface not available');
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
try {
|
|
308
|
+
const minPtr = koffi.alloc('int32', 1);
|
|
309
|
+
const maxPtr = koffi.alloc('int32', 1);
|
|
310
|
+
const result = this.libraryLoader.SteamAPI_ISteamUserStats_GetAchievementProgressLimitsInt32(userStatsInterface, achievementName, minPtr, maxPtr);
|
|
311
|
+
if (result) {
|
|
312
|
+
return {
|
|
313
|
+
minProgress: koffi.decode(minPtr, 'int32'),
|
|
314
|
+
maxProgress: koffi.decode(maxPtr, 'int32')
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
console.error(`[Steamworks] ERROR: Error getting achievement progress limits:`, error.message);
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Get achievement progress limits (for float-based progress)
|
|
326
|
+
*/
|
|
327
|
+
async getAchievementProgressLimitsFloat(achievementName) {
|
|
328
|
+
if (!this.apiCore.isInitialized()) {
|
|
329
|
+
console.warn('[Steamworks] WARNING: Steam API not initialized');
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
const userStatsInterface = this.apiCore.getUserStatsInterface();
|
|
333
|
+
if (!userStatsInterface) {
|
|
334
|
+
console.warn('[Steamworks] WARNING: UserStats interface not available');
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
try {
|
|
338
|
+
const minPtr = koffi.alloc('float', 1);
|
|
339
|
+
const maxPtr = koffi.alloc('float', 1);
|
|
340
|
+
const result = this.libraryLoader.SteamAPI_ISteamUserStats_GetAchievementProgressLimitsFloat(userStatsInterface, achievementName, minPtr, maxPtr);
|
|
341
|
+
if (result) {
|
|
342
|
+
return {
|
|
343
|
+
minProgress: koffi.decode(minPtr, 'float'),
|
|
344
|
+
maxProgress: koffi.decode(maxPtr, 'float')
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
console.error(`[Steamworks] ERROR: Error getting achievement progress limits:`, error.message);
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Request stats for another user (friend)
|
|
356
|
+
* This is async - you need to wait for the callback before calling getUserAchievement
|
|
357
|
+
*/
|
|
358
|
+
async requestUserStats(steamId) {
|
|
359
|
+
if (!this.apiCore.isInitialized()) {
|
|
360
|
+
console.warn('[Steamworks] WARNING: Steam API not initialized');
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
const userStatsInterface = this.apiCore.getUserStatsInterface();
|
|
364
|
+
if (!userStatsInterface) {
|
|
365
|
+
console.warn('[Steamworks] WARNING: UserStats interface not available');
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
try {
|
|
369
|
+
console.log(`[Steamworks] Requesting user stats for Steam ID: ${steamId}`);
|
|
370
|
+
const steamIdNum = BigInt(steamId);
|
|
371
|
+
const callHandle = this.libraryLoader.SteamAPI_ISteamUserStats_RequestUserStats(userStatsInterface, steamIdNum);
|
|
372
|
+
if (callHandle !== BigInt(0)) {
|
|
373
|
+
console.log(`[Steamworks] User stats request sent (call handle: ${callHandle})`);
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
console.error(`[Steamworks] ERROR: Failed to request user stats`);
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
catch (error) {
|
|
382
|
+
console.error(`[Steamworks] ERROR: Error requesting user stats:`, error.message);
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Get achievement status for another user (friend)
|
|
388
|
+
* Must call requestUserStats() first and wait for callback
|
|
389
|
+
*/
|
|
390
|
+
async getUserAchievement(steamId, achievementName) {
|
|
391
|
+
if (!this.apiCore.isInitialized()) {
|
|
392
|
+
console.warn('[Steamworks] WARNING: Steam API not initialized');
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
const userStatsInterface = this.apiCore.getUserStatsInterface();
|
|
396
|
+
if (!userStatsInterface) {
|
|
397
|
+
console.warn('[Steamworks] WARNING: UserStats interface not available');
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
try {
|
|
401
|
+
const steamIdNum = BigInt(steamId);
|
|
402
|
+
const unlockedPtr = koffi.alloc('bool', 1);
|
|
403
|
+
const unlockTimePtr = koffi.alloc('uint32', 1);
|
|
404
|
+
const result = this.libraryLoader.SteamAPI_ISteamUserStats_GetUserAchievementAndUnlockTime(userStatsInterface, steamIdNum, achievementName, unlockedPtr, unlockTimePtr);
|
|
405
|
+
if (result) {
|
|
406
|
+
// Get display info for achievement
|
|
407
|
+
const displayName = this.libraryLoader.SteamAPI_ISteamUserStats_GetAchievementDisplayAttribute(userStatsInterface, achievementName, 'name') || achievementName;
|
|
408
|
+
const description = this.libraryLoader.SteamAPI_ISteamUserStats_GetAchievementDisplayAttribute(userStatsInterface, achievementName, 'desc') || '';
|
|
409
|
+
const unlocked = koffi.decode(unlockedPtr, 'bool');
|
|
410
|
+
const unlockTime = unlocked ? koffi.decode(unlockTimePtr, 'uint32') : 0;
|
|
411
|
+
return {
|
|
412
|
+
steamId,
|
|
413
|
+
apiName: achievementName,
|
|
414
|
+
displayName,
|
|
415
|
+
description,
|
|
416
|
+
unlocked,
|
|
417
|
+
unlockTime
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
catch (error) {
|
|
423
|
+
console.error(`[Steamworks] ERROR: Error getting user achievement:`, error.message);
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Request global achievement percentages
|
|
429
|
+
* This is async - wait for callback before calling getAchievementAchievedPercent
|
|
430
|
+
*/
|
|
431
|
+
async requestGlobalAchievementPercentages() {
|
|
432
|
+
if (!this.apiCore.isInitialized()) {
|
|
433
|
+
console.warn('[Steamworks] WARNING: Steam API not initialized');
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
const userStatsInterface = this.apiCore.getUserStatsInterface();
|
|
437
|
+
if (!userStatsInterface) {
|
|
438
|
+
console.warn('[Steamworks] WARNING: UserStats interface not available');
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
try {
|
|
442
|
+
console.log(`[Steamworks] Requesting global achievement percentages...`);
|
|
443
|
+
const callHandle = this.libraryLoader.SteamAPI_ISteamUserStats_RequestGlobalAchievementPercentages(userStatsInterface);
|
|
444
|
+
if (callHandle !== BigInt(0)) {
|
|
445
|
+
console.log(`[Steamworks] Global achievement percentages request sent (call handle: ${callHandle})`);
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
console.error(`[Steamworks] ERROR: Failed to request global achievement percentages`);
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
catch (error) {
|
|
454
|
+
console.error(`[Steamworks] ERROR: Error requesting global achievement percentages:`, error.message);
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Get percentage of users who unlocked a specific achievement
|
|
460
|
+
* Must call requestGlobalAchievementPercentages() first
|
|
461
|
+
*/
|
|
462
|
+
async getAchievementAchievedPercent(achievementName) {
|
|
463
|
+
if (!this.apiCore.isInitialized()) {
|
|
464
|
+
console.warn('[Steamworks] WARNING: Steam API not initialized');
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
const userStatsInterface = this.apiCore.getUserStatsInterface();
|
|
468
|
+
if (!userStatsInterface) {
|
|
469
|
+
console.warn('[Steamworks] WARNING: UserStats interface not available');
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
try {
|
|
473
|
+
const percentPtr = koffi.alloc('float', 1);
|
|
474
|
+
const result = this.libraryLoader.SteamAPI_ISteamUserStats_GetAchievementAchievedPercent(userStatsInterface, achievementName, percentPtr);
|
|
475
|
+
if (result) {
|
|
476
|
+
const percent = koffi.decode(percentPtr, 'float');
|
|
477
|
+
console.log(`[Steamworks] Achievement ${achievementName} global unlock: ${percent.toFixed(2)}%`);
|
|
478
|
+
return percent;
|
|
479
|
+
}
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
catch (error) {
|
|
483
|
+
console.error(`[Steamworks] ERROR: Error getting achievement percentage:`, error.message);
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Get all achievements with global unlock percentages
|
|
489
|
+
* Must call requestGlobalAchievementPercentages() first and wait for callback
|
|
490
|
+
*/
|
|
491
|
+
async getAllAchievementsWithGlobalStats() {
|
|
492
|
+
const achievements = await this.getAllAchievements();
|
|
493
|
+
const result = [];
|
|
494
|
+
for (const ach of achievements) {
|
|
495
|
+
const percent = await this.getAchievementAchievedPercent(ach.apiName);
|
|
496
|
+
result.push({
|
|
497
|
+
apiName: ach.apiName,
|
|
498
|
+
displayName: ach.displayName,
|
|
499
|
+
description: ach.description,
|
|
500
|
+
unlocked: ach.unlocked,
|
|
501
|
+
globalUnlockPercentage: percent ?? 0
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
return result;
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Get most achieved achievement info
|
|
508
|
+
* Returns iterator for use with getNextMostAchievedAchievementInfo
|
|
509
|
+
*/
|
|
510
|
+
async getMostAchievedAchievementInfo() {
|
|
511
|
+
if (!this.apiCore.isInitialized()) {
|
|
512
|
+
console.warn('[Steamworks] WARNING: Steam API not initialized');
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
const userStatsInterface = this.apiCore.getUserStatsInterface();
|
|
516
|
+
if (!userStatsInterface) {
|
|
517
|
+
console.warn('[Steamworks] WARNING: UserStats interface not available');
|
|
518
|
+
return null;
|
|
519
|
+
}
|
|
520
|
+
try {
|
|
521
|
+
const nameBuffer = Buffer.alloc(256);
|
|
522
|
+
const percentPtr = koffi.alloc('float', 1);
|
|
523
|
+
const unlockedPtr = koffi.alloc('bool', 1);
|
|
524
|
+
const iterator = this.libraryLoader.SteamAPI_ISteamUserStats_GetMostAchievedAchievementInfo(userStatsInterface, nameBuffer, 256, percentPtr, unlockedPtr);
|
|
525
|
+
if (iterator !== -1) {
|
|
526
|
+
const apiName = nameBuffer.toString('utf8').split('\0')[0];
|
|
527
|
+
const percent = koffi.decode(percentPtr, 'float');
|
|
528
|
+
const unlocked = koffi.decode(unlockedPtr, 'bool');
|
|
529
|
+
return { apiName, percent, unlocked, iterator };
|
|
530
|
+
}
|
|
531
|
+
return null;
|
|
532
|
+
}
|
|
533
|
+
catch (error) {
|
|
534
|
+
console.error(`[Steamworks] ERROR: Error getting most achieved achievement:`, error.message);
|
|
535
|
+
return null;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Get next most achieved achievement info (iterate by popularity)
|
|
540
|
+
*/
|
|
541
|
+
async getNextMostAchievedAchievementInfo(previousIterator) {
|
|
542
|
+
if (!this.apiCore.isInitialized()) {
|
|
543
|
+
console.warn('[Steamworks] WARNING: Steam API not initialized');
|
|
544
|
+
return null;
|
|
545
|
+
}
|
|
546
|
+
const userStatsInterface = this.apiCore.getUserStatsInterface();
|
|
547
|
+
if (!userStatsInterface) {
|
|
548
|
+
console.warn('[Steamworks] WARNING: UserStats interface not available');
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
try {
|
|
552
|
+
const nameBuffer = Buffer.alloc(256);
|
|
553
|
+
const percentPtr = koffi.alloc('float', 1);
|
|
554
|
+
const unlockedPtr = koffi.alloc('bool', 1);
|
|
555
|
+
const iterator = this.libraryLoader.SteamAPI_ISteamUserStats_GetNextMostAchievedAchievementInfo(userStatsInterface, previousIterator, nameBuffer, 256, percentPtr, unlockedPtr);
|
|
556
|
+
if (iterator !== -1) {
|
|
557
|
+
const apiName = nameBuffer.toString('utf8').split('\0')[0];
|
|
558
|
+
const percent = koffi.decode(percentPtr, 'float');
|
|
559
|
+
const unlocked = koffi.decode(unlockedPtr, 'bool');
|
|
560
|
+
return { apiName, percent, unlocked, iterator };
|
|
561
|
+
}
|
|
562
|
+
return null;
|
|
563
|
+
}
|
|
564
|
+
catch (error) {
|
|
565
|
+
console.error(`[Steamworks] ERROR: Error getting next most achieved achievement:`, error.message);
|
|
566
|
+
return null;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Get all achievements sorted by global unlock percentage (most achieved first)
|
|
571
|
+
* Must call requestGlobalAchievementPercentages() first
|
|
572
|
+
*/
|
|
573
|
+
async getAllAchievementsSortedByPopularity() {
|
|
574
|
+
const results = [];
|
|
575
|
+
// Get first achievement
|
|
576
|
+
const first = await this.getMostAchievedAchievementInfo();
|
|
577
|
+
if (!first) {
|
|
578
|
+
console.warn('[Steamworks] WARNING: No global achievement data available. Call requestGlobalAchievementPercentages() first.');
|
|
579
|
+
return results;
|
|
580
|
+
}
|
|
581
|
+
// Get display info
|
|
582
|
+
const userStatsInterface = this.apiCore.getUserStatsInterface();
|
|
583
|
+
if (userStatsInterface) {
|
|
584
|
+
const displayName = this.libraryLoader.SteamAPI_ISteamUserStats_GetAchievementDisplayAttribute(userStatsInterface, first.apiName, 'name') || first.apiName;
|
|
585
|
+
const description = this.libraryLoader.SteamAPI_ISteamUserStats_GetAchievementDisplayAttribute(userStatsInterface, first.apiName, 'desc') || '';
|
|
586
|
+
results.push({
|
|
587
|
+
apiName: first.apiName,
|
|
588
|
+
displayName,
|
|
589
|
+
description,
|
|
590
|
+
unlocked: first.unlocked,
|
|
591
|
+
globalUnlockPercentage: first.percent
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
// Iterate through remaining achievements
|
|
595
|
+
let iterator = first.iterator;
|
|
596
|
+
while (iterator !== -1) {
|
|
597
|
+
const next = await this.getNextMostAchievedAchievementInfo(iterator);
|
|
598
|
+
if (!next)
|
|
599
|
+
break;
|
|
600
|
+
if (userStatsInterface) {
|
|
601
|
+
const displayName = this.libraryLoader.SteamAPI_ISteamUserStats_GetAchievementDisplayAttribute(userStatsInterface, next.apiName, 'name') || next.apiName;
|
|
602
|
+
const description = this.libraryLoader.SteamAPI_ISteamUserStats_GetAchievementDisplayAttribute(userStatsInterface, next.apiName, 'desc') || '';
|
|
603
|
+
results.push({
|
|
604
|
+
apiName: next.apiName,
|
|
605
|
+
displayName,
|
|
606
|
+
description,
|
|
607
|
+
unlocked: next.unlocked,
|
|
608
|
+
globalUnlockPercentage: next.percent
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
iterator = next.iterator;
|
|
612
|
+
}
|
|
613
|
+
return results;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Reset all stats and optionally achievements
|
|
617
|
+
* WARNING: This clears ALL user stats and achievements!
|
|
618
|
+
*/
|
|
619
|
+
async resetAllStats(includeAchievements = false) {
|
|
620
|
+
if (!this.apiCore.isInitialized()) {
|
|
621
|
+
console.warn('[Steamworks] WARNING: Steam API not initialized');
|
|
622
|
+
return false;
|
|
623
|
+
}
|
|
624
|
+
const userStatsInterface = this.apiCore.getUserStatsInterface();
|
|
625
|
+
if (!userStatsInterface) {
|
|
626
|
+
console.warn('[Steamworks] WARNING: UserStats interface not available');
|
|
627
|
+
return false;
|
|
628
|
+
}
|
|
629
|
+
try {
|
|
630
|
+
console.log(`[Steamworks] Resetting all stats (achievements: ${includeAchievements ? 'YES' : 'NO'})`);
|
|
631
|
+
console.warn('[Steamworks] WARNING: This will reset ALL user statistics!');
|
|
632
|
+
const result = this.libraryLoader.SteamAPI_ISteamUserStats_ResetAllStats(userStatsInterface, includeAchievements);
|
|
633
|
+
if (result) {
|
|
634
|
+
// Store the reset
|
|
635
|
+
const storeResult = this.libraryLoader.SteamAPI_ISteamUserStats_StoreStats(userStatsInterface);
|
|
636
|
+
if (storeResult) {
|
|
637
|
+
this.apiCore.runCallbacks();
|
|
638
|
+
console.log(`[Steamworks] All stats reset successfully`);
|
|
639
|
+
return true;
|
|
640
|
+
}
|
|
641
|
+
else {
|
|
642
|
+
console.error(`[Steamworks] ERROR: Failed to store stats after reset`);
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
else {
|
|
647
|
+
console.error(`[Steamworks] ERROR: Failed to reset stats`);
|
|
648
|
+
return false;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
catch (error) {
|
|
652
|
+
console.error(`[Steamworks] ERROR: Error resetting stats:`, error.message);
|
|
653
|
+
return false;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Get all achievements with icon handles
|
|
658
|
+
*/
|
|
659
|
+
async getAllAchievementsWithIcons() {
|
|
660
|
+
const achievements = await this.getAllAchievements();
|
|
661
|
+
const result = [];
|
|
662
|
+
for (const ach of achievements) {
|
|
663
|
+
const iconHandle = await this.getAchievementIcon(ach.apiName);
|
|
664
|
+
result.push({
|
|
665
|
+
...ach,
|
|
666
|
+
iconHandle
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
return result;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
exports.SteamAchievementManager = SteamAchievementManager;
|
|
673
|
+
//# sourceMappingURL=SteamAchievementManager.js.map
|