steamworks-ffi-node 0.6.0 → 0.6.2
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 +74 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/SteamAPICore.d.ts +23 -0
- package/dist/internal/SteamAPICore.d.ts.map +1 -1
- package/dist/internal/SteamAPICore.js +30 -0
- package/dist/internal/SteamAPICore.js.map +1 -1
- package/dist/internal/SteamCallbackPoller.d.ts +72 -0
- package/dist/internal/SteamCallbackPoller.d.ts.map +1 -1
- package/dist/internal/SteamCallbackPoller.js +144 -2
- package/dist/internal/SteamCallbackPoller.js.map +1 -1
- package/dist/internal/SteamCloudManager.d.ts.map +1 -1
- package/dist/internal/SteamCloudManager.js +18 -18
- package/dist/internal/SteamCloudManager.js.map +1 -1
- package/dist/internal/SteamLibraryLoader.d.ts +27 -0
- package/dist/internal/SteamLibraryLoader.d.ts.map +1 -1
- package/dist/internal/SteamLibraryLoader.js +43 -2
- package/dist/internal/SteamLibraryLoader.js.map +1 -1
- package/dist/internal/SteamOverlayManager.js +21 -21
- package/dist/internal/SteamOverlayManager.js.map +1 -1
- package/dist/internal/SteamRichPresenceManager.js +18 -18
- package/dist/internal/SteamRichPresenceManager.js.map +1 -1
- package/dist/internal/SteamWorkshopManager.d.ts +602 -0
- package/dist/internal/SteamWorkshopManager.d.ts.map +1 -0
- package/dist/internal/SteamWorkshopManager.js +1426 -0
- package/dist/internal/SteamWorkshopManager.js.map +1 -0
- package/dist/internal/callbackTypes/SteamCallbackIds.d.ts +26 -0
- package/dist/internal/callbackTypes/SteamCallbackIds.d.ts.map +1 -0
- package/dist/internal/callbackTypes/SteamCallbackIds.js +35 -0
- package/dist/internal/callbackTypes/SteamCallbackIds.js.map +1 -0
- package/dist/internal/callbackTypes/SteamCallbackTypes.d.ts +89 -0
- package/dist/internal/callbackTypes/SteamCallbackTypes.d.ts.map +1 -0
- package/dist/internal/callbackTypes/SteamCallbackTypes.js +9 -0
- package/dist/internal/callbackTypes/SteamCallbackTypes.js.map +1 -0
- package/dist/internal/callbackTypes/index.d.ts +6 -0
- package/dist/internal/callbackTypes/index.d.ts.map +1 -0
- package/dist/internal/callbackTypes/index.js +22 -0
- package/dist/internal/callbackTypes/index.js.map +1 -0
- package/dist/steam.d.ts +42 -0
- package/dist/steam.d.ts.map +1 -1
- package/dist/steam.js +4 -0
- package/dist/steam.js.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/workshop.d.ts +302 -0
- package/dist/types/workshop.d.ts.map +1 -0
- package/dist/types/workshop.js +203 -0
- package/dist/types/workshop.js.map +1 -0
- package/docs/README.md +12 -0
- package/package.json +3 -1
|
@@ -0,0 +1,1426 @@
|
|
|
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.SteamWorkshopManager = void 0;
|
|
37
|
+
const SteamCallbackPoller_1 = require("./SteamCallbackPoller");
|
|
38
|
+
const types_1 = require("../types");
|
|
39
|
+
const callbackTypes_1 = require("./callbackTypes");
|
|
40
|
+
const koffi = __importStar(require("koffi"));
|
|
41
|
+
// Manual buffer parsing for CreateItemResult_t callback
|
|
42
|
+
// Steam uses #pragma pack causing tight packing - Koffi can't handle this properly
|
|
43
|
+
// Layout: [int32:0-3][uint64:4-11][uint8:12] = 13 bytes, padded to 16
|
|
44
|
+
const CreateItemResult_t = koffi.struct('CreateItemResult_t', {
|
|
45
|
+
rawBytes: koffi.array('uint8', 16)
|
|
46
|
+
});
|
|
47
|
+
// Manual buffer parsing for SubmitItemUpdateResult_t callback
|
|
48
|
+
// Steam uses #pragma pack causing tight packing - Koffi can't handle this properly
|
|
49
|
+
// Layout: [int32:0-3][bool:4][padding:5-7][uint64:8-15] = 16 bytes
|
|
50
|
+
const SubmitItemUpdateResult_t = koffi.struct('SubmitItemUpdateResult_t', {
|
|
51
|
+
rawBytes: koffi.array('uint8', 16)
|
|
52
|
+
});
|
|
53
|
+
// Manual buffer parsing for RemoteStorageSubscribePublishedFileResult_t callback
|
|
54
|
+
// Steam uses #pragma pack causing tight packing - Koffi can't handle this properly
|
|
55
|
+
// Layout: [int32:0-3][uint64:4-11] = 12 bytes (NO padding!)
|
|
56
|
+
const RemoteStorageSubscribePublishedFileResult_t = koffi.struct('RemoteStorageSubscribePublishedFileResult_t', {
|
|
57
|
+
rawBytes: koffi.array('uint8', 12)
|
|
58
|
+
});
|
|
59
|
+
// Manual buffer parsing for RemoteStorageUnsubscribePublishedFileResult_t callback
|
|
60
|
+
// Steam uses #pragma pack causing tight packing - Koffi can't handle this properly
|
|
61
|
+
// Layout: [int32:0-3][uint64:4-11] = 12 bytes (NO padding!)
|
|
62
|
+
const RemoteStorageUnsubscribePublishedFileResult_t = koffi.struct('RemoteStorageUnsubscribePublishedFileResult_t', {
|
|
63
|
+
rawBytes: koffi.array('uint8', 12)
|
|
64
|
+
});
|
|
65
|
+
// Koffi struct for SteamUGCQueryCompleted_t callback
|
|
66
|
+
const SteamUGCQueryCompleted_t = koffi.struct('SteamUGCQueryCompleted_t', {
|
|
67
|
+
m_handle: 'uint64', // UGCQueryHandle_t
|
|
68
|
+
m_eResult: 'int', // EResult
|
|
69
|
+
m_unNumResultsReturned: 'uint32',
|
|
70
|
+
m_unTotalMatchingResults: 'uint32',
|
|
71
|
+
m_bCachedData: 'bool'
|
|
72
|
+
});
|
|
73
|
+
// Manual buffer parsing for SetUserItemVoteResult_t callback
|
|
74
|
+
// Steam uses #pragma pack causing tight packing - Koffi can't handle this properly
|
|
75
|
+
// Layout: [uint64:0-7][int32:8-11][bool:12] = 13 bytes
|
|
76
|
+
const SetUserItemVoteResult_t = koffi.struct('SetUserItemVoteResult_t', {
|
|
77
|
+
rawBytes: koffi.array('uint8', 13)
|
|
78
|
+
});
|
|
79
|
+
// Manual buffer parsing for GetUserItemVoteResult_t callback
|
|
80
|
+
// Steam uses #pragma pack causing tight packing - Koffi can't handle this properly
|
|
81
|
+
// Layout: [uint64:0-7][int32:8-11][bool×3:12-14] = 15 bytes
|
|
82
|
+
const GetUserItemVoteResult_t = koffi.struct('GetUserItemVoteResult_t', {
|
|
83
|
+
rawBytes: koffi.array('uint8', 15)
|
|
84
|
+
});
|
|
85
|
+
// Koffi struct for UserFavoriteItemsListChanged_t callback
|
|
86
|
+
const UserFavoriteItemsListChanged_t = koffi.struct('UserFavoriteItemsListChanged_t', {
|
|
87
|
+
m_nPublishedFileId: 'uint64', // PublishedFileId_t (8 bytes)
|
|
88
|
+
m_eResult: 'int', // EResult (4 bytes)
|
|
89
|
+
m_bWasAddRequest: 'bool' // bool (1 byte)
|
|
90
|
+
});
|
|
91
|
+
/**
|
|
92
|
+
* Manager for Steam Workshop / User Generated Content (UGC) operations
|
|
93
|
+
*
|
|
94
|
+
* The SteamWorkshopManager provides comprehensive access to Steam's Workshop system,
|
|
95
|
+
* allowing you to browse, subscribe to, create, and manage user-generated content.
|
|
96
|
+
*
|
|
97
|
+
* Key Features:
|
|
98
|
+
* - Query and browse Workshop items
|
|
99
|
+
* - Subscribe/unsubscribe to Workshop content
|
|
100
|
+
* - Download and install Workshop items
|
|
101
|
+
* - Create and update your own Workshop items
|
|
102
|
+
* - Vote on and favorite Workshop content
|
|
103
|
+
* - Track download and installation progress
|
|
104
|
+
*
|
|
105
|
+
* @remarks
|
|
106
|
+
* All methods require the Steam API to be initialized.
|
|
107
|
+
* Workshop items are automatically downloaded and installed when subscribed.
|
|
108
|
+
*
|
|
109
|
+
* @example Basic subscription
|
|
110
|
+
* ```typescript
|
|
111
|
+
* const steam = SteamworksSDK.getInstance();
|
|
112
|
+
* steam.init({ appId: 480 });
|
|
113
|
+
*
|
|
114
|
+
* // Subscribe to a Workshop item
|
|
115
|
+
* const itemId = BigInt('123456789');
|
|
116
|
+
* await steam.workshop.subscribeItem(itemId);
|
|
117
|
+
*
|
|
118
|
+
* // Check if subscribed
|
|
119
|
+
* const subscribed = steam.workshop.getSubscribedItems();
|
|
120
|
+
* console.log(`Subscribed to ${subscribed.length} items`);
|
|
121
|
+
*
|
|
122
|
+
* // Get installation info
|
|
123
|
+
* const info = steam.workshop.getItemInstallInfo(itemId);
|
|
124
|
+
* if (info) {
|
|
125
|
+
* console.log(`Installed at: ${info.folder}`);
|
|
126
|
+
* }
|
|
127
|
+
* ```
|
|
128
|
+
*
|
|
129
|
+
* @example Query Workshop items
|
|
130
|
+
* ```typescript
|
|
131
|
+
* // Query popular items
|
|
132
|
+
* const query = steam.workshop.createQueryAllUGCRequest(
|
|
133
|
+
* EUGCQuery.RankedByVote,
|
|
134
|
+
* EUGCMatchingUGCType.Items,
|
|
135
|
+
* 1 // page 1
|
|
136
|
+
* );
|
|
137
|
+
*
|
|
138
|
+
* // Results come through callbacks
|
|
139
|
+
* // Listen for query completion in your callback handler
|
|
140
|
+
* ```
|
|
141
|
+
*
|
|
142
|
+
* @see {@link https://partner.steamgames.com/doc/api/ISteamUGC ISteamUGC Documentation}
|
|
143
|
+
*/
|
|
144
|
+
class SteamWorkshopManager {
|
|
145
|
+
constructor(libraryLoader, apiCore) {
|
|
146
|
+
this.libraryLoader = libraryLoader;
|
|
147
|
+
this.apiCore = apiCore;
|
|
148
|
+
this.callbackPoller = new SteamCallbackPoller_1.SteamCallbackPoller(libraryLoader, apiCore);
|
|
149
|
+
}
|
|
150
|
+
// ========================================
|
|
151
|
+
// Subscription Management
|
|
152
|
+
// ========================================
|
|
153
|
+
/**
|
|
154
|
+
* Subscribes to a Workshop item
|
|
155
|
+
*
|
|
156
|
+
* @param publishedFileId - The Workshop item ID to subscribe to
|
|
157
|
+
* @returns True if subscription was successful, false otherwise
|
|
158
|
+
*
|
|
159
|
+
* @remarks
|
|
160
|
+
* - Item will be automatically downloaded and installed
|
|
161
|
+
* - Use getItemInstallInfo() to check installation status
|
|
162
|
+
* - Use getItemDownloadInfo() to track download progress
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```typescript
|
|
166
|
+
* const itemId = BigInt('123456789');
|
|
167
|
+
* const success = await steam.workshop.subscribeItem(itemId);
|
|
168
|
+
*
|
|
169
|
+
* if (success) {
|
|
170
|
+
* console.log('Successfully subscribed!');
|
|
171
|
+
* // Check download progress
|
|
172
|
+
* const progress = steam.workshop.getItemDownloadInfo(itemId);
|
|
173
|
+
* if (progress) {
|
|
174
|
+
* console.log(`Downloading: ${progress.percentComplete}%`);
|
|
175
|
+
* }
|
|
176
|
+
* }
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
async subscribeItem(publishedFileId) {
|
|
180
|
+
if (!this.apiCore.isInitialized()) {
|
|
181
|
+
console.error('[Steamworks] Steam API not initialized');
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
const ugc = this.apiCore.getUGCInterface();
|
|
185
|
+
if (!ugc) {
|
|
186
|
+
console.error('[Steamworks] ISteamUGC interface not available');
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
const callHandle = this.libraryLoader.SteamAPI_ISteamUGC_SubscribeItem(ugc, publishedFileId);
|
|
191
|
+
if (callHandle === BigInt(0)) {
|
|
192
|
+
console.error('[Steamworks] Failed to initiate item subscription');
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
// Wait for the callback result
|
|
196
|
+
const result = await this.callbackPoller.poll(BigInt(callHandle), RemoteStorageSubscribePublishedFileResult_t, callbackTypes_1.K_I_REMOTE_STORAGE_SUBSCRIBE_PUBLISHED_FILE_RESULT, 50, // max retries (5 seconds total)
|
|
197
|
+
100 // delay ms
|
|
198
|
+
);
|
|
199
|
+
if (result && result.m_eResult === 1) { // k_EResultOK = 1
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
console.error(`[Steamworks] Subscribe item failed with result: ${result?.m_eResult || 'unknown'}`);
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
console.error('[Steamworks] Error subscribing to item:', error);
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
} /**
|
|
210
|
+
* Unsubscribes from a Workshop item
|
|
211
|
+
*
|
|
212
|
+
* @param publishedFileId - The Workshop item ID to unsubscribe from
|
|
213
|
+
* @returns True if unsubscription was successful, false otherwise
|
|
214
|
+
*
|
|
215
|
+
* @remarks
|
|
216
|
+
* - Item will be uninstalled after the game quits
|
|
217
|
+
*
|
|
218
|
+
* @example
|
|
219
|
+
* ```typescript
|
|
220
|
+
* const itemId = BigInt('123456789');
|
|
221
|
+
* const success = await steam.workshop.unsubscribeItem(itemId);
|
|
222
|
+
*
|
|
223
|
+
* if (success) {
|
|
224
|
+
* console.log('Successfully unsubscribed!');
|
|
225
|
+
* }
|
|
226
|
+
* ```
|
|
227
|
+
*/
|
|
228
|
+
async unsubscribeItem(publishedFileId) {
|
|
229
|
+
if (!this.apiCore.isInitialized()) {
|
|
230
|
+
console.error('[Steamworks] Steam API not initialized');
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
const ugc = this.apiCore.getUGCInterface();
|
|
234
|
+
if (!ugc) {
|
|
235
|
+
console.error('[Steamworks] ISteamUGC interface not available');
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
const callHandle = this.libraryLoader.SteamAPI_ISteamUGC_UnsubscribeItem(ugc, publishedFileId);
|
|
240
|
+
if (callHandle === BigInt(0)) {
|
|
241
|
+
console.error('[Steamworks] Failed to initiate item unsubscription');
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
// Wait for the callback result
|
|
245
|
+
const result = await this.callbackPoller.poll(BigInt(callHandle), RemoteStorageUnsubscribePublishedFileResult_t, callbackTypes_1.K_I_REMOTE_STORAGE_UNSUBSCRIBE_PUBLISHED_FILE_RESULT, 50, // max retries (5 seconds total)
|
|
246
|
+
100 // delay ms
|
|
247
|
+
);
|
|
248
|
+
if (result && result.m_eResult === 1) { // k_EResultOK = 1
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
console.error(`[Steamworks] Unsubscribe item failed with result: ${result?.m_eResult || 'unknown'}`);
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
console.error('[Steamworks] Error unsubscribing from item:', error);
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Gets the number of subscribed Workshop items
|
|
261
|
+
*
|
|
262
|
+
* @returns Number of subscribed items
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* ```typescript
|
|
266
|
+
* const count = steam.workshop.getNumSubscribedItems();
|
|
267
|
+
* console.log(`Subscribed to ${count} Workshop items`);
|
|
268
|
+
* ```
|
|
269
|
+
*/
|
|
270
|
+
getNumSubscribedItems() {
|
|
271
|
+
if (!this.apiCore.isInitialized()) {
|
|
272
|
+
return 0;
|
|
273
|
+
}
|
|
274
|
+
const ugc = this.apiCore.getUGCInterface();
|
|
275
|
+
if (!ugc) {
|
|
276
|
+
return 0;
|
|
277
|
+
}
|
|
278
|
+
try {
|
|
279
|
+
return this.libraryLoader.SteamAPI_ISteamUGC_GetNumSubscribedItems(ugc);
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
console.error('[Steamworks] Error getting subscribed items count:', error);
|
|
283
|
+
return 0;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Gets all subscribed Workshop item IDs
|
|
288
|
+
*
|
|
289
|
+
* @returns Array of subscribed Workshop item IDs
|
|
290
|
+
*
|
|
291
|
+
* @example
|
|
292
|
+
* ```typescript
|
|
293
|
+
* const items = steam.workshop.getSubscribedItems();
|
|
294
|
+
* items.forEach(itemId => {
|
|
295
|
+
* console.log(`Subscribed to item: ${itemId}`);
|
|
296
|
+
* const info = steam.workshop.getItemInstallInfo(itemId);
|
|
297
|
+
* if (info) {
|
|
298
|
+
* console.log(` Installed at: ${info.folder}`);
|
|
299
|
+
* }
|
|
300
|
+
* });
|
|
301
|
+
* ```
|
|
302
|
+
*/
|
|
303
|
+
getSubscribedItems() {
|
|
304
|
+
if (!this.apiCore.isInitialized()) {
|
|
305
|
+
return [];
|
|
306
|
+
}
|
|
307
|
+
const ugc = this.apiCore.getUGCInterface();
|
|
308
|
+
if (!ugc) {
|
|
309
|
+
return [];
|
|
310
|
+
}
|
|
311
|
+
try {
|
|
312
|
+
const count = this.getNumSubscribedItems();
|
|
313
|
+
if (count === 0) {
|
|
314
|
+
return [];
|
|
315
|
+
}
|
|
316
|
+
// Allocate array for item IDs
|
|
317
|
+
const itemsBuffer = Buffer.alloc(count * 8); // uint64 array
|
|
318
|
+
const numReturned = this.libraryLoader.SteamAPI_ISteamUGC_GetSubscribedItems(ugc, itemsBuffer, count);
|
|
319
|
+
// Read the item IDs from buffer
|
|
320
|
+
const items = [];
|
|
321
|
+
for (let i = 0; i < numReturned; i++) {
|
|
322
|
+
items.push(itemsBuffer.readBigUInt64LE(i * 8));
|
|
323
|
+
}
|
|
324
|
+
return items;
|
|
325
|
+
}
|
|
326
|
+
catch (error) {
|
|
327
|
+
console.error('[Steamworks] Error getting subscribed items:', error);
|
|
328
|
+
return [];
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// ========================================
|
|
332
|
+
// Item State and Information
|
|
333
|
+
// ========================================
|
|
334
|
+
/**
|
|
335
|
+
* Gets the state flags for a Workshop item
|
|
336
|
+
*
|
|
337
|
+
* @param publishedFileId - The Workshop item ID
|
|
338
|
+
* @returns State flags (combination of EItemState values)
|
|
339
|
+
*
|
|
340
|
+
* @remarks
|
|
341
|
+
* Use bitwise operations to check for specific states:
|
|
342
|
+
* - EItemState.Subscribed - User is subscribed
|
|
343
|
+
* - EItemState.Installed - Item is installed
|
|
344
|
+
* - EItemState.Downloading - Currently downloading
|
|
345
|
+
* - EItemState.NeedsUpdate - Update available
|
|
346
|
+
*
|
|
347
|
+
* @example
|
|
348
|
+
* ```typescript
|
|
349
|
+
* const itemId = BigInt('123456789');
|
|
350
|
+
* const state = steam.workshop.getItemState(itemId);
|
|
351
|
+
*
|
|
352
|
+
* if (state & EItemState.Subscribed) {
|
|
353
|
+
* console.log('Subscribed');
|
|
354
|
+
* }
|
|
355
|
+
* if (state & EItemState.Installed) {
|
|
356
|
+
* console.log('Installed');
|
|
357
|
+
* }
|
|
358
|
+
* if (state & EItemState.Downloading) {
|
|
359
|
+
* console.log('Downloading...');
|
|
360
|
+
* }
|
|
361
|
+
* if (state & EItemState.NeedsUpdate) {
|
|
362
|
+
* console.log('Update available');
|
|
363
|
+
* }
|
|
364
|
+
* ```
|
|
365
|
+
*/
|
|
366
|
+
getItemState(publishedFileId) {
|
|
367
|
+
if (!this.apiCore.isInitialized()) {
|
|
368
|
+
return types_1.EItemState.None;
|
|
369
|
+
}
|
|
370
|
+
const ugc = this.apiCore.getUGCInterface();
|
|
371
|
+
if (!ugc) {
|
|
372
|
+
return types_1.EItemState.None;
|
|
373
|
+
}
|
|
374
|
+
try {
|
|
375
|
+
return this.libraryLoader.SteamAPI_ISteamUGC_GetItemState(ugc, publishedFileId);
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
console.error('[Steamworks] Error getting item state:', error);
|
|
379
|
+
return types_1.EItemState.None;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Gets installation information for a Workshop item
|
|
384
|
+
*
|
|
385
|
+
* @param publishedFileId - The Workshop item ID
|
|
386
|
+
* @returns Installation info or null if not installed
|
|
387
|
+
*
|
|
388
|
+
* @remarks
|
|
389
|
+
* Only returns data if item has EItemState.Installed flag set
|
|
390
|
+
*
|
|
391
|
+
* @example
|
|
392
|
+
* ```typescript
|
|
393
|
+
* const itemId = BigInt('123456789');
|
|
394
|
+
* const info = steam.workshop.getItemInstallInfo(itemId);
|
|
395
|
+
*
|
|
396
|
+
* if (info) {
|
|
397
|
+
* console.log(`Installed at: ${info.folder}`);
|
|
398
|
+
* console.log(`Size on disk: ${info.sizeOnDisk} bytes`);
|
|
399
|
+
* console.log(`Install time: ${new Date(info.timestamp * 1000)}`);
|
|
400
|
+
* }
|
|
401
|
+
* ```
|
|
402
|
+
*/
|
|
403
|
+
getItemInstallInfo(publishedFileId) {
|
|
404
|
+
if (!this.apiCore.isInitialized()) {
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
const ugc = this.apiCore.getUGCInterface();
|
|
408
|
+
if (!ugc) {
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
try {
|
|
412
|
+
const sizeBuffer = Buffer.alloc(8); // uint64
|
|
413
|
+
const folderBuffer = Buffer.alloc(4096); // path buffer
|
|
414
|
+
const timestampBuffer = Buffer.alloc(4); // uint32
|
|
415
|
+
const success = this.libraryLoader.SteamAPI_ISteamUGC_GetItemInstallInfo(ugc, publishedFileId, sizeBuffer, folderBuffer, 4096, timestampBuffer);
|
|
416
|
+
if (!success) {
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
// Read folder path (null-terminated string)
|
|
420
|
+
const folderEnd = folderBuffer.indexOf(0);
|
|
421
|
+
const folder = folderBuffer.toString('utf8', 0, folderEnd > 0 ? folderEnd : 4096);
|
|
422
|
+
return {
|
|
423
|
+
sizeOnDisk: sizeBuffer.readBigUInt64LE(0),
|
|
424
|
+
folder: folder,
|
|
425
|
+
timestamp: timestampBuffer.readUInt32LE(0)
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
catch (error) {
|
|
429
|
+
console.error('[Steamworks] Error getting item install info:', error);
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Gets download progress for a Workshop item
|
|
435
|
+
*
|
|
436
|
+
* @param publishedFileId - The Workshop item ID
|
|
437
|
+
* @returns Download info or null if not downloading
|
|
438
|
+
*
|
|
439
|
+
* @remarks
|
|
440
|
+
* Only returns data if item has EItemState.Downloading flag set
|
|
441
|
+
*
|
|
442
|
+
* @example
|
|
443
|
+
* ```typescript
|
|
444
|
+
* const itemId = BigInt('123456789');
|
|
445
|
+
* const progress = steam.workshop.getItemDownloadInfo(itemId);
|
|
446
|
+
*
|
|
447
|
+
* if (progress) {
|
|
448
|
+
* console.log(`Download: ${progress.percentComplete.toFixed(1)}%`);
|
|
449
|
+
* console.log(`${progress.bytesDownloaded} / ${progress.bytesTotal} bytes`);
|
|
450
|
+
* }
|
|
451
|
+
* ```
|
|
452
|
+
*/
|
|
453
|
+
getItemDownloadInfo(publishedFileId) {
|
|
454
|
+
if (!this.apiCore.isInitialized()) {
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
const ugc = this.apiCore.getUGCInterface();
|
|
458
|
+
if (!ugc) {
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
try {
|
|
462
|
+
const downloadedBuffer = Buffer.alloc(8); // uint64
|
|
463
|
+
const totalBuffer = Buffer.alloc(8); // uint64
|
|
464
|
+
const success = this.libraryLoader.SteamAPI_ISteamUGC_GetItemDownloadInfo(ugc, publishedFileId, downloadedBuffer, totalBuffer);
|
|
465
|
+
if (!success) {
|
|
466
|
+
return null;
|
|
467
|
+
}
|
|
468
|
+
const bytesDownloaded = downloadedBuffer.readBigUInt64LE(0);
|
|
469
|
+
const bytesTotal = totalBuffer.readBigUInt64LE(0);
|
|
470
|
+
const percentComplete = bytesTotal > BigInt(0)
|
|
471
|
+
? Number((bytesDownloaded * BigInt(100)) / bytesTotal)
|
|
472
|
+
: 0;
|
|
473
|
+
return {
|
|
474
|
+
bytesDownloaded,
|
|
475
|
+
bytesTotal,
|
|
476
|
+
percentComplete
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
catch (error) {
|
|
480
|
+
console.error('[Steamworks] Error getting item download info:', error);
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Downloads a Workshop item (forces download if not subscribed)
|
|
486
|
+
*
|
|
487
|
+
* @param publishedFileId - The Workshop item ID
|
|
488
|
+
* @param highPriority - If true, suspends other downloads and prioritizes this one
|
|
489
|
+
* @returns True if download started, false otherwise
|
|
490
|
+
*
|
|
491
|
+
* @remarks
|
|
492
|
+
* - If item is already installed, files should not be used until callback received
|
|
493
|
+
* - Listen for DownloadItemResult_t callback for completion
|
|
494
|
+
* - If not subscribed, item will be cached temporarily
|
|
495
|
+
*
|
|
496
|
+
* @example
|
|
497
|
+
* ```typescript
|
|
498
|
+
* const itemId = BigInt('123456789');
|
|
499
|
+
* const started = steam.workshop.downloadItem(itemId, true);
|
|
500
|
+
*
|
|
501
|
+
* if (started) {
|
|
502
|
+
* console.log('Download started');
|
|
503
|
+
* // Poll getItemDownloadInfo() for progress
|
|
504
|
+
* }
|
|
505
|
+
* ```
|
|
506
|
+
*/
|
|
507
|
+
downloadItem(publishedFileId, highPriority = false) {
|
|
508
|
+
if (!this.apiCore.isInitialized()) {
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
511
|
+
const ugc = this.apiCore.getUGCInterface();
|
|
512
|
+
if (!ugc) {
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
515
|
+
try {
|
|
516
|
+
return this.libraryLoader.SteamAPI_ISteamUGC_DownloadItem(ugc, publishedFileId, highPriority);
|
|
517
|
+
}
|
|
518
|
+
catch (error) {
|
|
519
|
+
console.error('[Steamworks] Error downloading item:', error);
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
// ========================================
|
|
524
|
+
// Query Operations
|
|
525
|
+
// ========================================
|
|
526
|
+
/**
|
|
527
|
+
* Creates a query for a user's Workshop items
|
|
528
|
+
*
|
|
529
|
+
* @param accountId - Steam account ID (32-bit, lower part of SteamID)
|
|
530
|
+
* @param listType - Type of list to query
|
|
531
|
+
* @param matchingType - Type of UGC to match
|
|
532
|
+
* @param sortOrder - How to sort results
|
|
533
|
+
* @param creatorAppId - App that created the items
|
|
534
|
+
* @param consumerAppId - App that will consume the items
|
|
535
|
+
* @param page - Page number (1-based)
|
|
536
|
+
* @returns Query handle for use with sendQueryUGCRequest
|
|
537
|
+
*
|
|
538
|
+
* @example
|
|
539
|
+
* ```typescript
|
|
540
|
+
* // Query current user's published items
|
|
541
|
+
* const accountId = 12345678; // Get from user's SteamID
|
|
542
|
+
* const query = steam.workshop.createQueryUserUGCRequest(
|
|
543
|
+
* accountId,
|
|
544
|
+
* EUserUGCList.Published,
|
|
545
|
+
* EUGCMatchingUGCType.Items,
|
|
546
|
+
* EUserUGCListSortOrder.CreationOrderDesc,
|
|
547
|
+
* 480, // Spacewar
|
|
548
|
+
* 480,
|
|
549
|
+
* 1 // First page
|
|
550
|
+
* );
|
|
551
|
+
*
|
|
552
|
+
* steam.workshop.sendQueryUGCRequest(query);
|
|
553
|
+
* // Wait for SteamUGCQueryCompleted_t callback
|
|
554
|
+
* ```
|
|
555
|
+
*/
|
|
556
|
+
createQueryUserUGCRequest(accountId, listType, matchingType, sortOrder, creatorAppId, consumerAppId, page) {
|
|
557
|
+
if (!this.apiCore.isInitialized()) {
|
|
558
|
+
return types_1.K_UGC_QUERY_HANDLE_INVALID;
|
|
559
|
+
}
|
|
560
|
+
const ugc = this.apiCore.getUGCInterface();
|
|
561
|
+
if (!ugc) {
|
|
562
|
+
return types_1.K_UGC_QUERY_HANDLE_INVALID;
|
|
563
|
+
}
|
|
564
|
+
try {
|
|
565
|
+
const handle = this.libraryLoader.SteamAPI_ISteamUGC_CreateQueryUserUGCRequest(ugc, accountId, listType, matchingType, sortOrder, creatorAppId, consumerAppId, page);
|
|
566
|
+
return BigInt(handle);
|
|
567
|
+
}
|
|
568
|
+
catch (error) {
|
|
569
|
+
console.error('[Steamworks] Error creating user UGC query:', error);
|
|
570
|
+
return types_1.K_UGC_QUERY_HANDLE_INVALID;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Creates a query for all Workshop items
|
|
575
|
+
*
|
|
576
|
+
* @param queryType - Type of query/sorting
|
|
577
|
+
* @param matchingType - Type of UGC to match
|
|
578
|
+
* @param creatorAppId - App that created the items
|
|
579
|
+
* @param consumerAppId - App that will consume the items
|
|
580
|
+
* @param page - Page number (1-based)
|
|
581
|
+
* @returns Query handle for use with sendQueryUGCRequest
|
|
582
|
+
*
|
|
583
|
+
* @example
|
|
584
|
+
* ```typescript
|
|
585
|
+
* // Query most popular items
|
|
586
|
+
* const query = steam.workshop.createQueryAllUGCRequest(
|
|
587
|
+
* EUGCQuery.RankedByVote,
|
|
588
|
+
* EUGCMatchingUGCType.Items,
|
|
589
|
+
* 480, // Spacewar
|
|
590
|
+
* 480,
|
|
591
|
+
* 1 // First page
|
|
592
|
+
* );
|
|
593
|
+
*
|
|
594
|
+
* steam.workshop.sendQueryUGCRequest(query);
|
|
595
|
+
* // Wait for SteamUGCQueryCompleted_t callback
|
|
596
|
+
* ```
|
|
597
|
+
*/
|
|
598
|
+
createQueryAllUGCRequest(queryType, matchingType, creatorAppId, consumerAppId, page) {
|
|
599
|
+
if (!this.apiCore.isInitialized()) {
|
|
600
|
+
return types_1.K_UGC_QUERY_HANDLE_INVALID;
|
|
601
|
+
}
|
|
602
|
+
const ugc = this.apiCore.getUGCInterface();
|
|
603
|
+
if (!ugc) {
|
|
604
|
+
return types_1.K_UGC_QUERY_HANDLE_INVALID;
|
|
605
|
+
}
|
|
606
|
+
try {
|
|
607
|
+
const handle = this.libraryLoader.SteamAPI_ISteamUGC_CreateQueryAllUGCRequestPage(ugc, queryType, matchingType, creatorAppId, consumerAppId, page);
|
|
608
|
+
return BigInt(handle);
|
|
609
|
+
}
|
|
610
|
+
catch (error) {
|
|
611
|
+
console.error('[Steamworks] Error creating all UGC query:', error);
|
|
612
|
+
return types_1.K_UGC_QUERY_HANDLE_INVALID;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Sends a UGC query to Steam and waits for results
|
|
617
|
+
*
|
|
618
|
+
* @param queryHandle - Handle from createQueryUserUGCRequest or createQueryAllUGCRequest
|
|
619
|
+
* @returns Query result with number of results and metadata, or null if failed
|
|
620
|
+
*
|
|
621
|
+
* @remarks
|
|
622
|
+
* After processing results, call releaseQueryUGCRequest to free memory
|
|
623
|
+
* Use getQueryUGCResult to retrieve individual item details
|
|
624
|
+
*
|
|
625
|
+
* @example
|
|
626
|
+
* ```typescript
|
|
627
|
+
* const query = steam.workshop.createQueryAllUGCRequest(...);
|
|
628
|
+
* const result = await steam.workshop.sendQueryUGCRequest(query);
|
|
629
|
+
*
|
|
630
|
+
* if (result) {
|
|
631
|
+
* console.log(`Found ${result.numResults} items (${result.totalResults} total)`);
|
|
632
|
+
*
|
|
633
|
+
* // Get individual results
|
|
634
|
+
* for (let i = 0; i < result.numResults; i++) {
|
|
635
|
+
* const item = steam.workshop.getQueryUGCResult(query, i);
|
|
636
|
+
* if (item) {
|
|
637
|
+
* console.log(`Item: ${item.title}`);
|
|
638
|
+
* }
|
|
639
|
+
* }
|
|
640
|
+
*
|
|
641
|
+
* // Clean up
|
|
642
|
+
* steam.workshop.releaseQueryUGCRequest(query);
|
|
643
|
+
* }
|
|
644
|
+
* ```
|
|
645
|
+
*/
|
|
646
|
+
async sendQueryUGCRequest(queryHandle) {
|
|
647
|
+
if (!this.apiCore.isInitialized()) {
|
|
648
|
+
console.error('[Steamworks] Steam API not initialized');
|
|
649
|
+
return null;
|
|
650
|
+
}
|
|
651
|
+
const ugc = this.apiCore.getUGCInterface();
|
|
652
|
+
if (!ugc) {
|
|
653
|
+
console.error('[Steamworks] ISteamUGC interface not available');
|
|
654
|
+
return null;
|
|
655
|
+
}
|
|
656
|
+
try {
|
|
657
|
+
const callHandle = this.libraryLoader.SteamAPI_ISteamUGC_SendQueryUGCRequest(ugc, queryHandle);
|
|
658
|
+
if (callHandle === BigInt(0)) {
|
|
659
|
+
console.error('[Steamworks] Failed to initiate UGC query');
|
|
660
|
+
return null;
|
|
661
|
+
}
|
|
662
|
+
// Wait for the callback result
|
|
663
|
+
const result = await this.callbackPoller.poll(BigInt(callHandle), SteamUGCQueryCompleted_t, callbackTypes_1.K_I_STEAM_UGC_QUERY_COMPLETED, 600, // max retries (60 seconds total)
|
|
664
|
+
100 // delay ms
|
|
665
|
+
);
|
|
666
|
+
if (result && result.m_eResult === 1) { // k_EResultOK = 1
|
|
667
|
+
return {
|
|
668
|
+
numResults: result.m_unNumResultsReturned,
|
|
669
|
+
totalResults: result.m_unTotalMatchingResults,
|
|
670
|
+
cachedData: result.m_bCachedData
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
console.error(`[Steamworks] UGC query failed with result: ${result?.m_eResult || 'unknown'}`);
|
|
674
|
+
return null;
|
|
675
|
+
}
|
|
676
|
+
catch (error) {
|
|
677
|
+
console.error('[Steamworks] Error sending UGC query:', error);
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Gets a single result from a completed UGC query
|
|
683
|
+
*
|
|
684
|
+
* @param queryHandle - Handle from a completed query
|
|
685
|
+
* @param index - Index of the result to retrieve (0-based)
|
|
686
|
+
* @returns Workshop item details or null if failed
|
|
687
|
+
*
|
|
688
|
+
* @remarks
|
|
689
|
+
* Call this after receiving SteamUGCQueryCompleted_t callback
|
|
690
|
+
* Index must be less than the number of results returned in callback
|
|
691
|
+
*
|
|
692
|
+
* @example
|
|
693
|
+
* ```typescript
|
|
694
|
+
* // After query completes via callback:
|
|
695
|
+
* const query = steam.workshop.createQueryAllUGCRequest(...);
|
|
696
|
+
* steam.workshop.sendQueryUGCRequest(query);
|
|
697
|
+
*
|
|
698
|
+
* // In callback handler when query completes:
|
|
699
|
+
* // Get first 10 results
|
|
700
|
+
* for (let i = 0; i < 10; i++) {
|
|
701
|
+
* const item = steam.workshop.getQueryUGCResult(query, i);
|
|
702
|
+
* if (item) {
|
|
703
|
+
* console.log(`${item.title} by ${item.steamIdOwner}`);
|
|
704
|
+
* console.log(`Votes: ${item.votesUp} up, ${item.votesDown} down`);
|
|
705
|
+
* }
|
|
706
|
+
* }
|
|
707
|
+
*
|
|
708
|
+
* steam.workshop.releaseQueryUGCRequest(query);
|
|
709
|
+
* ```
|
|
710
|
+
*/
|
|
711
|
+
getQueryUGCResult(queryHandle, index) {
|
|
712
|
+
if (!this.apiCore.isInitialized()) {
|
|
713
|
+
return null;
|
|
714
|
+
}
|
|
715
|
+
const ugc = this.apiCore.getUGCInterface();
|
|
716
|
+
if (!ugc) {
|
|
717
|
+
return null;
|
|
718
|
+
}
|
|
719
|
+
try {
|
|
720
|
+
// Allocate buffer for SteamUGCDetails_t struct
|
|
721
|
+
// Struct size: ~10KB (8KB description + 1KB tags + other fields)
|
|
722
|
+
const detailsBuffer = Buffer.alloc(10240);
|
|
723
|
+
const success = this.libraryLoader.SteamAPI_ISteamUGC_GetQueryUGCResult(ugc, queryHandle, index, detailsBuffer);
|
|
724
|
+
if (!success) {
|
|
725
|
+
return null;
|
|
726
|
+
}
|
|
727
|
+
// Parse the SteamUGCDetails_t struct
|
|
728
|
+
let offset = 0;
|
|
729
|
+
// PublishedFileId_t m_nPublishedFileId (uint64)
|
|
730
|
+
const publishedFileId = detailsBuffer.readBigUInt64LE(offset);
|
|
731
|
+
offset += 8;
|
|
732
|
+
// EResult m_eResult (int32)
|
|
733
|
+
const result = detailsBuffer.readInt32LE(offset);
|
|
734
|
+
offset += 4;
|
|
735
|
+
// EWorkshopFileType m_eFileType (int32)
|
|
736
|
+
const fileType = detailsBuffer.readInt32LE(offset);
|
|
737
|
+
offset += 4;
|
|
738
|
+
// AppId_t m_nCreatorAppID (uint32)
|
|
739
|
+
const creatorAppId = detailsBuffer.readUInt32LE(offset);
|
|
740
|
+
offset += 4;
|
|
741
|
+
// AppId_t m_nConsumerAppID (uint32)
|
|
742
|
+
const consumerAppId = detailsBuffer.readUInt32LE(offset);
|
|
743
|
+
offset += 4;
|
|
744
|
+
// char m_rgchTitle[k_cchPublishedDocumentTitleMax] (129 bytes)
|
|
745
|
+
const titleEnd = detailsBuffer.indexOf(0, offset);
|
|
746
|
+
const title = detailsBuffer.toString('utf8', offset, titleEnd > offset ? titleEnd : offset + 129);
|
|
747
|
+
offset += 129;
|
|
748
|
+
// char m_rgchDescription[k_cchPublishedDocumentDescriptionMax] (8000 bytes)
|
|
749
|
+
const descEnd = detailsBuffer.indexOf(0, offset);
|
|
750
|
+
const description = detailsBuffer.toString('utf8', offset, descEnd > offset ? descEnd : offset + 8000);
|
|
751
|
+
offset += 8000;
|
|
752
|
+
// uint64 m_ulSteamIDOwner
|
|
753
|
+
const steamIdOwner = detailsBuffer.readBigUInt64LE(offset);
|
|
754
|
+
offset += 8;
|
|
755
|
+
// uint32 m_rtimeCreated
|
|
756
|
+
const timeCreated = detailsBuffer.readUInt32LE(offset);
|
|
757
|
+
offset += 4;
|
|
758
|
+
// uint32 m_rtimeUpdated
|
|
759
|
+
const timeUpdated = detailsBuffer.readUInt32LE(offset);
|
|
760
|
+
offset += 4;
|
|
761
|
+
// uint32 m_rtimeAddedToUserList
|
|
762
|
+
const timeAddedToUserList = detailsBuffer.readUInt32LE(offset);
|
|
763
|
+
offset += 4;
|
|
764
|
+
// ERemoteStoragePublishedFileVisibility m_eVisibility (int32)
|
|
765
|
+
const visibility = detailsBuffer.readInt32LE(offset);
|
|
766
|
+
offset += 4;
|
|
767
|
+
// bool m_bBanned
|
|
768
|
+
const banned = detailsBuffer.readUInt8(offset) !== 0;
|
|
769
|
+
offset += 1;
|
|
770
|
+
// bool m_bAcceptedForUse
|
|
771
|
+
const acceptedForUse = detailsBuffer.readUInt8(offset) !== 0;
|
|
772
|
+
offset += 1;
|
|
773
|
+
// bool m_bTagsTruncated
|
|
774
|
+
const tagsTruncated = detailsBuffer.readUInt8(offset) !== 0;
|
|
775
|
+
offset += 1;
|
|
776
|
+
// Padding alignment (structs are aligned)
|
|
777
|
+
offset += 1; // Align to 4 bytes
|
|
778
|
+
// char m_rgchTags[k_cchTagListMax] (1025 bytes)
|
|
779
|
+
const tagsEnd = detailsBuffer.indexOf(0, offset);
|
|
780
|
+
const tagsString = detailsBuffer.toString('utf8', offset, tagsEnd > offset ? tagsEnd : offset + 1025);
|
|
781
|
+
const tags = tagsString.split(',').map(t => t.trim()).filter(t => t.length > 0);
|
|
782
|
+
offset += 1025;
|
|
783
|
+
// Padding alignment
|
|
784
|
+
offset += 3; // Align to 8 bytes
|
|
785
|
+
// UGCHandle_t m_hFile (uint64)
|
|
786
|
+
const fileHandle = detailsBuffer.readBigUInt64LE(offset);
|
|
787
|
+
offset += 8;
|
|
788
|
+
// UGCHandle_t m_hPreviewFile (uint64)
|
|
789
|
+
const previewFileHandle = detailsBuffer.readBigUInt64LE(offset);
|
|
790
|
+
offset += 8;
|
|
791
|
+
// char m_pchFileName[k_cchFilenameMax] (260 bytes)
|
|
792
|
+
const fileNameEnd = detailsBuffer.indexOf(0, offset);
|
|
793
|
+
const fileName = detailsBuffer.toString('utf8', offset, fileNameEnd > offset ? fileNameEnd : offset + 260);
|
|
794
|
+
offset += 260;
|
|
795
|
+
// int32 m_nFileSize
|
|
796
|
+
const fileSize = detailsBuffer.readInt32LE(offset);
|
|
797
|
+
offset += 4;
|
|
798
|
+
// int32 m_nPreviewFileSize
|
|
799
|
+
const previewFileSize = detailsBuffer.readInt32LE(offset);
|
|
800
|
+
offset += 4;
|
|
801
|
+
// char m_rgchURL[k_cchPublishedFileURLMax] (256 bytes)
|
|
802
|
+
const urlEnd = detailsBuffer.indexOf(0, offset);
|
|
803
|
+
const url = detailsBuffer.toString('utf8', offset, urlEnd > offset ? urlEnd : offset + 256);
|
|
804
|
+
offset += 256;
|
|
805
|
+
// uint32 m_unVotesUp
|
|
806
|
+
const votesUp = detailsBuffer.readUInt32LE(offset);
|
|
807
|
+
offset += 4;
|
|
808
|
+
// uint32 m_unVotesDown
|
|
809
|
+
const votesDown = detailsBuffer.readUInt32LE(offset);
|
|
810
|
+
offset += 4;
|
|
811
|
+
// float m_flScore
|
|
812
|
+
const score = detailsBuffer.readFloatLE(offset);
|
|
813
|
+
offset += 4;
|
|
814
|
+
// uint32 m_unNumChildren
|
|
815
|
+
const numChildren = detailsBuffer.readUInt32LE(offset);
|
|
816
|
+
offset += 4;
|
|
817
|
+
// uint64 m_ulTotalFilesSize
|
|
818
|
+
const totalFilesSize = detailsBuffer.readBigUInt64LE(offset);
|
|
819
|
+
return {
|
|
820
|
+
publishedFileId,
|
|
821
|
+
result,
|
|
822
|
+
fileType,
|
|
823
|
+
creatorAppID: creatorAppId,
|
|
824
|
+
consumerAppID: consumerAppId,
|
|
825
|
+
title,
|
|
826
|
+
description,
|
|
827
|
+
steamIDOwner: steamIdOwner,
|
|
828
|
+
timeCreated,
|
|
829
|
+
timeUpdated,
|
|
830
|
+
timeAddedToUserList,
|
|
831
|
+
visibility,
|
|
832
|
+
banned,
|
|
833
|
+
acceptedForUse,
|
|
834
|
+
tagsTruncated,
|
|
835
|
+
tags,
|
|
836
|
+
file: fileHandle,
|
|
837
|
+
previewFile: previewFileHandle,
|
|
838
|
+
fileName,
|
|
839
|
+
fileSize,
|
|
840
|
+
previewFileSize,
|
|
841
|
+
url,
|
|
842
|
+
votesUp,
|
|
843
|
+
votesDown,
|
|
844
|
+
score,
|
|
845
|
+
numChildren,
|
|
846
|
+
totalFilesSize
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
catch (error) {
|
|
850
|
+
console.error('[Steamworks] Error getting query UGC result:', error);
|
|
851
|
+
return null;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* Releases a query handle and frees associated memory
|
|
856
|
+
*
|
|
857
|
+
* @param queryHandle - Handle to release
|
|
858
|
+
* @returns True if released successfully
|
|
859
|
+
*
|
|
860
|
+
* @remarks
|
|
861
|
+
* Always call this after processing query results to prevent memory leaks
|
|
862
|
+
*
|
|
863
|
+
* @example
|
|
864
|
+
* ```typescript
|
|
865
|
+
* const query = steam.workshop.createQueryAllUGCRequest(...);
|
|
866
|
+
* steam.workshop.sendQueryUGCRequest(query);
|
|
867
|
+
* // After callback and processing results:
|
|
868
|
+
* steam.workshop.releaseQueryUGCRequest(query);
|
|
869
|
+
* ```
|
|
870
|
+
*/
|
|
871
|
+
releaseQueryUGCRequest(queryHandle) {
|
|
872
|
+
if (!this.apiCore.isInitialized()) {
|
|
873
|
+
return false;
|
|
874
|
+
}
|
|
875
|
+
const ugc = this.apiCore.getUGCInterface();
|
|
876
|
+
if (!ugc) {
|
|
877
|
+
return false;
|
|
878
|
+
}
|
|
879
|
+
try {
|
|
880
|
+
return this.libraryLoader.SteamAPI_ISteamUGC_ReleaseQueryUGCRequest(ugc, queryHandle);
|
|
881
|
+
}
|
|
882
|
+
catch (error) {
|
|
883
|
+
console.error('[Steamworks] Error releasing UGC query:', error);
|
|
884
|
+
return false;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
// ========================================
|
|
888
|
+
// Item Creation and Update
|
|
889
|
+
// ========================================
|
|
890
|
+
/**
|
|
891
|
+
* Creates a new Workshop item and waits for the result
|
|
892
|
+
*
|
|
893
|
+
* @param consumerAppId - App ID that will consume this item
|
|
894
|
+
* @param fileType - Type of Workshop file
|
|
895
|
+
* @returns The published file ID of the newly created item, or null if failed
|
|
896
|
+
*
|
|
897
|
+
* @remarks
|
|
898
|
+
* This method automatically waits for the CreateItemResult_t callback.
|
|
899
|
+
* Once created, use startItemUpdate to set content and properties.
|
|
900
|
+
* Blocks until result is ready or times out (default 5 seconds).
|
|
901
|
+
*
|
|
902
|
+
* @example
|
|
903
|
+
* ```typescript
|
|
904
|
+
* const publishedFileId = await steam.workshop.createItem(
|
|
905
|
+
* 480,
|
|
906
|
+
* EWorkshopFileType.Community
|
|
907
|
+
* );
|
|
908
|
+
*
|
|
909
|
+
* if (publishedFileId) {
|
|
910
|
+
* console.log(`Created item with ID: ${publishedFileId}`);
|
|
911
|
+
*
|
|
912
|
+
* // Now you can update the item
|
|
913
|
+
* const updateHandle = steam.workshop.startItemUpdate(480, publishedFileId);
|
|
914
|
+
* steam.workshop.setItemTitle(updateHandle, 'My Awesome Mod');
|
|
915
|
+
* steam.workshop.setItemContent(updateHandle, '/path/to/content');
|
|
916
|
+
* steam.workshop.submitItemUpdate(updateHandle, 'Initial release');
|
|
917
|
+
* }
|
|
918
|
+
* ```
|
|
919
|
+
*/
|
|
920
|
+
async createItem(consumerAppId, fileType) {
|
|
921
|
+
if (!this.apiCore.isInitialized()) {
|
|
922
|
+
console.error('[Steamworks] Steam API not initialized');
|
|
923
|
+
return null;
|
|
924
|
+
}
|
|
925
|
+
const ugc = this.apiCore.getUGCInterface();
|
|
926
|
+
if (!ugc) {
|
|
927
|
+
console.error('[Steamworks] ISteamUGC interface not available');
|
|
928
|
+
return null;
|
|
929
|
+
}
|
|
930
|
+
try {
|
|
931
|
+
// Call Steam API to create the item
|
|
932
|
+
const callHandle = this.libraryLoader.SteamAPI_ISteamUGC_CreateItem(ugc, consumerAppId, fileType);
|
|
933
|
+
if (callHandle === BigInt(0)) {
|
|
934
|
+
console.error('[Steamworks] Failed to initiate item creation');
|
|
935
|
+
return null;
|
|
936
|
+
}
|
|
937
|
+
// Wait for the callback result (longer timeout for item creation)
|
|
938
|
+
const result = await this.callbackPoller.poll(BigInt(callHandle), CreateItemResult_t, callbackTypes_1.K_I_CREATE_ITEM_RESULT, 600, // max retries (60 seconds total - item creation can be slow)
|
|
939
|
+
100 // delay ms
|
|
940
|
+
);
|
|
941
|
+
if (result && result.m_eResult === 1) { // k_EResultOK = 1
|
|
942
|
+
if (result.m_bUserNeedsToAcceptWorkshopLegalAgreement) {
|
|
943
|
+
console.warn('[Steamworks] User needs to accept Workshop Legal Agreement');
|
|
944
|
+
console.warn('[Steamworks] Visit: https://steamcommunity.com/sharedfiles/workshoplegalagreement');
|
|
945
|
+
}
|
|
946
|
+
return BigInt(result.m_nPublishedFileId);
|
|
947
|
+
}
|
|
948
|
+
console.error(`[Steamworks] Create item failed with result: ${result?.m_eResult || 'unknown'}`);
|
|
949
|
+
return null;
|
|
950
|
+
}
|
|
951
|
+
catch (error) {
|
|
952
|
+
console.error('[Steamworks] Error creating item:', error);
|
|
953
|
+
return null;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* Starts an update for a Workshop item
|
|
958
|
+
*
|
|
959
|
+
* @param consumerAppId - App ID
|
|
960
|
+
* @param publishedFileId - Workshop item ID to update
|
|
961
|
+
* @returns Update handle for use with set* functions
|
|
962
|
+
*
|
|
963
|
+
* @remarks
|
|
964
|
+
* Call set* functions to modify properties, then submitItemUpdate to commit
|
|
965
|
+
*
|
|
966
|
+
* @example
|
|
967
|
+
* ```typescript
|
|
968
|
+
* const itemId = BigInt('123456789');
|
|
969
|
+
* const updateHandle = steam.workshop.startItemUpdate(480, itemId);
|
|
970
|
+
*
|
|
971
|
+
* steam.workshop.setItemTitle(updateHandle, 'My Awesome Mod v2.0');
|
|
972
|
+
* steam.workshop.setItemDescription(updateHandle, 'Updated with new features!');
|
|
973
|
+
* steam.workshop.setItemContent(updateHandle, '/path/to/mod/folder');
|
|
974
|
+
*
|
|
975
|
+
* steam.workshop.submitItemUpdate(updateHandle, 'Version 2.0 changelog');
|
|
976
|
+
* ```
|
|
977
|
+
*/
|
|
978
|
+
startItemUpdate(consumerAppId, publishedFileId) {
|
|
979
|
+
if (!this.apiCore.isInitialized()) {
|
|
980
|
+
return types_1.K_UGC_UPDATE_HANDLE_INVALID;
|
|
981
|
+
}
|
|
982
|
+
const ugc = this.apiCore.getUGCInterface();
|
|
983
|
+
if (!ugc) {
|
|
984
|
+
return types_1.K_UGC_UPDATE_HANDLE_INVALID;
|
|
985
|
+
}
|
|
986
|
+
try {
|
|
987
|
+
const handle = this.libraryLoader.SteamAPI_ISteamUGC_StartItemUpdate(ugc, consumerAppId, publishedFileId);
|
|
988
|
+
return BigInt(handle);
|
|
989
|
+
}
|
|
990
|
+
catch (error) {
|
|
991
|
+
console.error('[Steamworks] Error starting item update:', error);
|
|
992
|
+
return types_1.K_UGC_UPDATE_HANDLE_INVALID;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* Sets the title of a Workshop item being updated
|
|
997
|
+
*
|
|
998
|
+
* @param updateHandle - Handle from startItemUpdate
|
|
999
|
+
* @param title - New title for the item
|
|
1000
|
+
* @returns True if set successfully
|
|
1001
|
+
*/
|
|
1002
|
+
setItemTitle(updateHandle, title) {
|
|
1003
|
+
if (!this.apiCore.isInitialized()) {
|
|
1004
|
+
return false;
|
|
1005
|
+
}
|
|
1006
|
+
const ugc = this.apiCore.getUGCInterface();
|
|
1007
|
+
if (!ugc) {
|
|
1008
|
+
return false;
|
|
1009
|
+
}
|
|
1010
|
+
try {
|
|
1011
|
+
return this.libraryLoader.SteamAPI_ISteamUGC_SetItemTitle(ugc, updateHandle, title);
|
|
1012
|
+
}
|
|
1013
|
+
catch (error) {
|
|
1014
|
+
console.error('[Steamworks] Error setting item title:', error);
|
|
1015
|
+
return false;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
1019
|
+
* Sets the description of a Workshop item being updated
|
|
1020
|
+
*
|
|
1021
|
+
* @param updateHandle - Handle from startItemUpdate
|
|
1022
|
+
* @param description - New description for the item
|
|
1023
|
+
* @returns True if set successfully
|
|
1024
|
+
*/
|
|
1025
|
+
setItemDescription(updateHandle, description) {
|
|
1026
|
+
if (!this.apiCore.isInitialized()) {
|
|
1027
|
+
return false;
|
|
1028
|
+
}
|
|
1029
|
+
const ugc = this.apiCore.getUGCInterface();
|
|
1030
|
+
if (!ugc) {
|
|
1031
|
+
return false;
|
|
1032
|
+
}
|
|
1033
|
+
try {
|
|
1034
|
+
return this.libraryLoader.SteamAPI_ISteamUGC_SetItemDescription(ugc, updateHandle, description);
|
|
1035
|
+
}
|
|
1036
|
+
catch (error) {
|
|
1037
|
+
console.error('[Steamworks] Error setting item description:', error);
|
|
1038
|
+
return false;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* Sets the visibility of a Workshop item being updated
|
|
1043
|
+
*
|
|
1044
|
+
* @param updateHandle - Handle from startItemUpdate
|
|
1045
|
+
* @param visibility - Visibility setting
|
|
1046
|
+
* @returns True if set successfully
|
|
1047
|
+
*/
|
|
1048
|
+
setItemVisibility(updateHandle, visibility) {
|
|
1049
|
+
if (!this.apiCore.isInitialized()) {
|
|
1050
|
+
return false;
|
|
1051
|
+
}
|
|
1052
|
+
const ugc = this.apiCore.getUGCInterface();
|
|
1053
|
+
if (!ugc) {
|
|
1054
|
+
return false;
|
|
1055
|
+
}
|
|
1056
|
+
try {
|
|
1057
|
+
return this.libraryLoader.SteamAPI_ISteamUGC_SetItemVisibility(ugc, updateHandle, visibility);
|
|
1058
|
+
}
|
|
1059
|
+
catch (error) {
|
|
1060
|
+
console.error('[Steamworks] Error setting item visibility:', error);
|
|
1061
|
+
return false;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Sets the content folder for a Workshop item being updated
|
|
1066
|
+
*
|
|
1067
|
+
* @param updateHandle - Handle from startItemUpdate
|
|
1068
|
+
* @param contentFolder - Path to folder containing item content
|
|
1069
|
+
* @returns True if set successfully
|
|
1070
|
+
*
|
|
1071
|
+
* @remarks
|
|
1072
|
+
* All files in the folder will be uploaded to Workshop
|
|
1073
|
+
*/
|
|
1074
|
+
setItemContent(updateHandle, contentFolder) {
|
|
1075
|
+
if (!this.apiCore.isInitialized()) {
|
|
1076
|
+
return false;
|
|
1077
|
+
}
|
|
1078
|
+
const ugc = this.apiCore.getUGCInterface();
|
|
1079
|
+
if (!ugc) {
|
|
1080
|
+
return false;
|
|
1081
|
+
}
|
|
1082
|
+
try {
|
|
1083
|
+
return this.libraryLoader.SteamAPI_ISteamUGC_SetItemContent(ugc, updateHandle, contentFolder);
|
|
1084
|
+
}
|
|
1085
|
+
catch (error) {
|
|
1086
|
+
console.error('[Steamworks] Error setting item content:', error);
|
|
1087
|
+
return false;
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Sets the preview image for a Workshop item being updated
|
|
1092
|
+
*
|
|
1093
|
+
* @param updateHandle - Handle from startItemUpdate
|
|
1094
|
+
* @param previewFile - Path to preview image file (must be under 1MB)
|
|
1095
|
+
* @returns True if set successfully
|
|
1096
|
+
*/
|
|
1097
|
+
setItemPreview(updateHandle, previewFile) {
|
|
1098
|
+
if (!this.apiCore.isInitialized()) {
|
|
1099
|
+
return false;
|
|
1100
|
+
}
|
|
1101
|
+
const ugc = this.apiCore.getUGCInterface();
|
|
1102
|
+
if (!ugc) {
|
|
1103
|
+
return false;
|
|
1104
|
+
}
|
|
1105
|
+
try {
|
|
1106
|
+
return this.libraryLoader.SteamAPI_ISteamUGC_SetItemPreview(ugc, updateHandle, previewFile);
|
|
1107
|
+
}
|
|
1108
|
+
catch (error) {
|
|
1109
|
+
console.error('[Steamworks] Error setting item preview:', error);
|
|
1110
|
+
return false;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
/**
|
|
1114
|
+
* Submits an item update to Steam Workshop and waits for completion
|
|
1115
|
+
*
|
|
1116
|
+
* @param updateHandle - Handle from startItemUpdate
|
|
1117
|
+
* @param changeNote - Description of changes (shown in update history)
|
|
1118
|
+
* @returns True if update was submitted successfully, false otherwise
|
|
1119
|
+
*
|
|
1120
|
+
* @remarks
|
|
1121
|
+
* Use getItemUpdateProgress to track upload progress
|
|
1122
|
+
*
|
|
1123
|
+
* @example
|
|
1124
|
+
* ```typescript
|
|
1125
|
+
* const updateHandle = steam.workshop.startItemUpdate(480, itemId);
|
|
1126
|
+
* steam.workshop.setItemTitle(updateHandle, 'Updated Title');
|
|
1127
|
+
* const success = await steam.workshop.submitItemUpdate(updateHandle, 'Fixed bugs');
|
|
1128
|
+
*
|
|
1129
|
+
* if (success) {
|
|
1130
|
+
* console.log('Update submitted successfully!');
|
|
1131
|
+
* }
|
|
1132
|
+
* ```
|
|
1133
|
+
*/
|
|
1134
|
+
async submitItemUpdate(updateHandle, changeNote) {
|
|
1135
|
+
if (!this.apiCore.isInitialized()) {
|
|
1136
|
+
console.error('[Steamworks] Steam API not initialized');
|
|
1137
|
+
return false;
|
|
1138
|
+
}
|
|
1139
|
+
const ugc = this.apiCore.getUGCInterface();
|
|
1140
|
+
if (!ugc) {
|
|
1141
|
+
console.error('[Steamworks] ISteamUGC interface not available');
|
|
1142
|
+
return false;
|
|
1143
|
+
}
|
|
1144
|
+
try {
|
|
1145
|
+
const callHandle = this.libraryLoader.SteamAPI_ISteamUGC_SubmitItemUpdate(ugc, updateHandle, changeNote);
|
|
1146
|
+
if (callHandle === BigInt(0)) {
|
|
1147
|
+
console.error('[Steamworks] Failed to initiate item update submission');
|
|
1148
|
+
return false;
|
|
1149
|
+
}
|
|
1150
|
+
// Wait for the callback result (longer timeout for uploads)
|
|
1151
|
+
const result = await this.callbackPoller.poll(BigInt(callHandle), SubmitItemUpdateResult_t, callbackTypes_1.K_I_SUBMIT_ITEM_UPDATE_RESULT, 300, // max retries (30 seconds total - uploads can be slow)
|
|
1152
|
+
100 // delay ms
|
|
1153
|
+
);
|
|
1154
|
+
if (result && result.m_eResult === 1) { // k_EResultOK = 1
|
|
1155
|
+
if (result.m_bUserNeedsToAcceptWorkshopLegalAgreement) {
|
|
1156
|
+
console.warn('[Steamworks] User needs to accept Workshop Legal Agreement');
|
|
1157
|
+
console.warn('[Steamworks] Visit: https://steamcommunity.com/sharedfiles/workshoplegalagreement');
|
|
1158
|
+
}
|
|
1159
|
+
return true;
|
|
1160
|
+
}
|
|
1161
|
+
console.error(`[Steamworks] Submit item update failed with result: ${result?.m_eResult || 'timeout'}`);
|
|
1162
|
+
return false;
|
|
1163
|
+
}
|
|
1164
|
+
catch (error) {
|
|
1165
|
+
console.error('[Steamworks] Error submitting item update:', error);
|
|
1166
|
+
return false;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
/**
|
|
1170
|
+
* Gets the progress of an item update submission
|
|
1171
|
+
*
|
|
1172
|
+
* @param updateHandle - Handle from startItemUpdate
|
|
1173
|
+
* @returns Update progress info
|
|
1174
|
+
*
|
|
1175
|
+
* @example
|
|
1176
|
+
* ```typescript
|
|
1177
|
+
* const updateHandle = steam.workshop.startItemUpdate(480, itemId);
|
|
1178
|
+
* // ... set properties and submit ...
|
|
1179
|
+
*
|
|
1180
|
+
* // Poll for progress
|
|
1181
|
+
* const progress = steam.workshop.getItemUpdateProgress(updateHandle);
|
|
1182
|
+
* console.log(`Status: ${EItemUpdateStatus[progress.status]}`);
|
|
1183
|
+
* console.log(`Progress: ${progress.percentComplete.toFixed(1)}%`);
|
|
1184
|
+
* ```
|
|
1185
|
+
*/
|
|
1186
|
+
getItemUpdateProgress(updateHandle) {
|
|
1187
|
+
const result = {
|
|
1188
|
+
status: types_1.EItemUpdateStatus.Invalid,
|
|
1189
|
+
bytesProcessed: BigInt(0),
|
|
1190
|
+
bytesTotal: BigInt(0),
|
|
1191
|
+
percentComplete: 0
|
|
1192
|
+
};
|
|
1193
|
+
if (!this.apiCore.isInitialized()) {
|
|
1194
|
+
return result;
|
|
1195
|
+
}
|
|
1196
|
+
const ugc = this.apiCore.getUGCInterface();
|
|
1197
|
+
if (!ugc) {
|
|
1198
|
+
return result;
|
|
1199
|
+
}
|
|
1200
|
+
try {
|
|
1201
|
+
const processedBuffer = Buffer.alloc(8); // uint64
|
|
1202
|
+
const totalBuffer = Buffer.alloc(8); // uint64
|
|
1203
|
+
const status = this.libraryLoader.SteamAPI_ISteamUGC_GetItemUpdateProgress(ugc, updateHandle, processedBuffer, totalBuffer);
|
|
1204
|
+
const bytesProcessed = processedBuffer.readBigUInt64LE(0);
|
|
1205
|
+
const bytesTotal = totalBuffer.readBigUInt64LE(0);
|
|
1206
|
+
const percentComplete = bytesTotal > BigInt(0)
|
|
1207
|
+
? Number((bytesProcessed * BigInt(100)) / bytesTotal)
|
|
1208
|
+
: 0;
|
|
1209
|
+
result.status = status;
|
|
1210
|
+
result.bytesProcessed = bytesProcessed;
|
|
1211
|
+
result.bytesTotal = bytesTotal;
|
|
1212
|
+
result.percentComplete = percentComplete;
|
|
1213
|
+
return result;
|
|
1214
|
+
}
|
|
1215
|
+
catch (error) {
|
|
1216
|
+
console.error('[Steamworks] Error getting item update progress:', error);
|
|
1217
|
+
return result;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
// ========================================
|
|
1221
|
+
// Voting and Favorites
|
|
1222
|
+
// ========================================
|
|
1223
|
+
/**
|
|
1224
|
+
* Votes on a Workshop item
|
|
1225
|
+
*
|
|
1226
|
+
* @param publishedFileId - Workshop item ID
|
|
1227
|
+
* @param voteUp - True to vote up, false to vote down
|
|
1228
|
+
* @returns True if vote was successful, false otherwise
|
|
1229
|
+
*
|
|
1230
|
+
* @example
|
|
1231
|
+
* ```typescript
|
|
1232
|
+
* const itemId = BigInt('123456789');
|
|
1233
|
+
* const success = await steam.workshop.setUserItemVote(itemId, true); // Vote up
|
|
1234
|
+
*
|
|
1235
|
+
* if (success) {
|
|
1236
|
+
* console.log('Vote registered!');
|
|
1237
|
+
* }
|
|
1238
|
+
* ```
|
|
1239
|
+
*/
|
|
1240
|
+
async setUserItemVote(publishedFileId, voteUp) {
|
|
1241
|
+
if (!this.apiCore.isInitialized()) {
|
|
1242
|
+
console.error('[Steamworks] Steam API not initialized');
|
|
1243
|
+
return false;
|
|
1244
|
+
}
|
|
1245
|
+
const ugc = this.apiCore.getUGCInterface();
|
|
1246
|
+
if (!ugc) {
|
|
1247
|
+
console.error('[Steamworks] ISteamUGC interface not available');
|
|
1248
|
+
return false;
|
|
1249
|
+
}
|
|
1250
|
+
try {
|
|
1251
|
+
const callHandle = this.libraryLoader.SteamAPI_ISteamUGC_SetUserItemVote(ugc, publishedFileId, voteUp);
|
|
1252
|
+
if (callHandle === BigInt(0)) {
|
|
1253
|
+
console.error('[Steamworks] Failed to initiate vote');
|
|
1254
|
+
return false;
|
|
1255
|
+
}
|
|
1256
|
+
// Wait for the callback result
|
|
1257
|
+
const result = await this.callbackPoller.poll(BigInt(callHandle), SetUserItemVoteResult_t, callbackTypes_1.K_I_SET_USER_ITEM_VOTE_RESULT, 50, // max retries (5 seconds total)
|
|
1258
|
+
100 // delay ms
|
|
1259
|
+
);
|
|
1260
|
+
if (result && result.m_eResult === 1) { // k_EResultOK = 1
|
|
1261
|
+
return true;
|
|
1262
|
+
}
|
|
1263
|
+
console.error(`[Steamworks] Set user item vote failed with result: ${result?.m_eResult || 'unknown'}`);
|
|
1264
|
+
return false;
|
|
1265
|
+
}
|
|
1266
|
+
catch (error) {
|
|
1267
|
+
console.error('[Steamworks] Error setting item vote:', error);
|
|
1268
|
+
return false;
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
/**
|
|
1272
|
+
* Gets the user's vote on a Workshop item
|
|
1273
|
+
*
|
|
1274
|
+
* @param publishedFileId - Workshop item ID
|
|
1275
|
+
* @returns Vote status object, or null if failed
|
|
1276
|
+
*
|
|
1277
|
+
* @example
|
|
1278
|
+
* ```typescript
|
|
1279
|
+
* const itemId = BigInt('123456789');
|
|
1280
|
+
* const vote = await steam.workshop.getUserItemVote(itemId);
|
|
1281
|
+
*
|
|
1282
|
+
* if (vote) {
|
|
1283
|
+
* if (vote.votedUp) {
|
|
1284
|
+
* console.log('You voted up');
|
|
1285
|
+
* } else if (vote.votedDown) {
|
|
1286
|
+
* console.log('You voted down');
|
|
1287
|
+
* } else {
|
|
1288
|
+
* console.log('You have not voted');
|
|
1289
|
+
* }
|
|
1290
|
+
* }
|
|
1291
|
+
* ```
|
|
1292
|
+
*/
|
|
1293
|
+
async getUserItemVote(publishedFileId) {
|
|
1294
|
+
if (!this.apiCore.isInitialized()) {
|
|
1295
|
+
console.error('[Steamworks] Steam API not initialized');
|
|
1296
|
+
return null;
|
|
1297
|
+
}
|
|
1298
|
+
const ugc = this.apiCore.getUGCInterface();
|
|
1299
|
+
if (!ugc) {
|
|
1300
|
+
console.error('[Steamworks] ISteamUGC interface not available');
|
|
1301
|
+
return null;
|
|
1302
|
+
}
|
|
1303
|
+
try {
|
|
1304
|
+
const callHandle = this.libraryLoader.SteamAPI_ISteamUGC_GetUserItemVote(ugc, publishedFileId);
|
|
1305
|
+
if (callHandle === BigInt(0)) {
|
|
1306
|
+
console.error('[Steamworks] Failed to initiate get vote');
|
|
1307
|
+
return null;
|
|
1308
|
+
}
|
|
1309
|
+
// Wait for the callback result
|
|
1310
|
+
const result = await this.callbackPoller.poll(BigInt(callHandle), GetUserItemVoteResult_t, callbackTypes_1.K_I_GET_USER_ITEM_VOTE_RESULT, 50, // max retries (5 seconds total)
|
|
1311
|
+
100 // delay ms
|
|
1312
|
+
);
|
|
1313
|
+
if (result && result.m_eResult === 1) { // k_EResultOK = 1
|
|
1314
|
+
return {
|
|
1315
|
+
votedUp: result.m_bVotedUp,
|
|
1316
|
+
votedDown: result.m_bVotedDown,
|
|
1317
|
+
voteSkipped: result.m_bVoteSkipped
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
console.error(`[Steamworks] Get user item vote failed with result: ${result?.m_eResult || 'unknown'}`);
|
|
1321
|
+
return null;
|
|
1322
|
+
}
|
|
1323
|
+
catch (error) {
|
|
1324
|
+
console.error('[Steamworks] Error getting item vote:', error);
|
|
1325
|
+
return null;
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
/**
|
|
1329
|
+
* Adds a Workshop item to the user's favorites
|
|
1330
|
+
*
|
|
1331
|
+
* @param appId - App ID
|
|
1332
|
+
* @param publishedFileId - Workshop item ID
|
|
1333
|
+
* @returns True if added successfully, false otherwise
|
|
1334
|
+
*
|
|
1335
|
+
* @example
|
|
1336
|
+
* ```typescript
|
|
1337
|
+
* const itemId = BigInt('123456789');
|
|
1338
|
+
* const success = await steam.workshop.addItemToFavorites(480, itemId);
|
|
1339
|
+
*
|
|
1340
|
+
* if (success) {
|
|
1341
|
+
* console.log('Added to favorites!');
|
|
1342
|
+
* }
|
|
1343
|
+
* ```
|
|
1344
|
+
*/
|
|
1345
|
+
async addItemToFavorites(appId, publishedFileId) {
|
|
1346
|
+
if (!this.apiCore.isInitialized()) {
|
|
1347
|
+
console.error('[Steamworks] Steam API not initialized');
|
|
1348
|
+
return false;
|
|
1349
|
+
}
|
|
1350
|
+
const ugc = this.apiCore.getUGCInterface();
|
|
1351
|
+
if (!ugc) {
|
|
1352
|
+
console.error('[Steamworks] ISteamUGC interface not available');
|
|
1353
|
+
return false;
|
|
1354
|
+
}
|
|
1355
|
+
try {
|
|
1356
|
+
const callHandle = this.libraryLoader.SteamAPI_ISteamUGC_AddItemToFavorites(ugc, appId, publishedFileId);
|
|
1357
|
+
if (callHandle === BigInt(0)) {
|
|
1358
|
+
console.error('[Steamworks] Failed to initiate add to favorites');
|
|
1359
|
+
return false;
|
|
1360
|
+
}
|
|
1361
|
+
// Wait for the callback result
|
|
1362
|
+
const result = await this.callbackPoller.poll(BigInt(callHandle), UserFavoriteItemsListChanged_t, callbackTypes_1.K_I_USER_FAVORITE_ITEMS_LIST_CHANGED, 50, // max retries (5 seconds total)
|
|
1363
|
+
100 // delay ms
|
|
1364
|
+
);
|
|
1365
|
+
if (result && result.m_eResult === 1) { // k_EResultOK = 1
|
|
1366
|
+
return true;
|
|
1367
|
+
}
|
|
1368
|
+
console.error(`[Steamworks] Add item to favorites failed with result: ${result?.m_eResult || 'unknown'}`);
|
|
1369
|
+
return false;
|
|
1370
|
+
}
|
|
1371
|
+
catch (error) {
|
|
1372
|
+
console.error('[Steamworks] Error adding item to favorites:', error);
|
|
1373
|
+
return false;
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
/**
|
|
1377
|
+
* Removes a Workshop item from the user's favorites
|
|
1378
|
+
*
|
|
1379
|
+
* @param appId - App ID
|
|
1380
|
+
* @param publishedFileId - Workshop item ID
|
|
1381
|
+
* @returns True if removed successfully, false otherwise
|
|
1382
|
+
*
|
|
1383
|
+
* @example
|
|
1384
|
+
* ```typescript
|
|
1385
|
+
* const itemId = BigInt('123456789');
|
|
1386
|
+
* const success = await steam.workshop.removeItemFromFavorites(480, itemId);
|
|
1387
|
+
*
|
|
1388
|
+
* if (success) {
|
|
1389
|
+
* console.log('Removed from favorites!');
|
|
1390
|
+
* }
|
|
1391
|
+
* ```
|
|
1392
|
+
*/
|
|
1393
|
+
async removeItemFromFavorites(appId, publishedFileId) {
|
|
1394
|
+
if (!this.apiCore.isInitialized()) {
|
|
1395
|
+
console.error('[Steamworks] Steam API not initialized');
|
|
1396
|
+
return false;
|
|
1397
|
+
}
|
|
1398
|
+
const ugc = this.apiCore.getUGCInterface();
|
|
1399
|
+
if (!ugc) {
|
|
1400
|
+
console.error('[Steamworks] ISteamUGC interface not available');
|
|
1401
|
+
return false;
|
|
1402
|
+
}
|
|
1403
|
+
try {
|
|
1404
|
+
const callHandle = this.libraryLoader.SteamAPI_ISteamUGC_RemoveItemFromFavorites(ugc, appId, publishedFileId);
|
|
1405
|
+
if (callHandle === BigInt(0)) {
|
|
1406
|
+
console.error('[Steamworks] Failed to initiate remove from favorites');
|
|
1407
|
+
return false;
|
|
1408
|
+
}
|
|
1409
|
+
// Wait for the callback result
|
|
1410
|
+
const result = await this.callbackPoller.poll(BigInt(callHandle), UserFavoriteItemsListChanged_t, callbackTypes_1.K_I_USER_FAVORITE_ITEMS_LIST_CHANGED, 50, // max retries (5 seconds total)
|
|
1411
|
+
100 // delay ms
|
|
1412
|
+
);
|
|
1413
|
+
if (result && result.m_eResult === 1) { // k_EResultOK = 1
|
|
1414
|
+
return true;
|
|
1415
|
+
}
|
|
1416
|
+
console.error(`[Steamworks] Remove item from favorites failed with result: ${result?.m_eResult || 'unknown'}`);
|
|
1417
|
+
return false;
|
|
1418
|
+
}
|
|
1419
|
+
catch (error) {
|
|
1420
|
+
console.error('[Steamworks] Error removing item from favorites:', error);
|
|
1421
|
+
return false;
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
exports.SteamWorkshopManager = SteamWorkshopManager;
|
|
1426
|
+
//# sourceMappingURL=SteamWorkshopManager.js.map
|