Skip to content

Commit 6cd8cbb

Browse files
committed
Add Greenworks parity bindings
1 parent 0f521c0 commit 6cd8cbb

20 files changed

Lines changed: 1496 additions & 107 deletions

CHECKLIST.md

Lines changed: 46 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,45 @@
22

33
## Lifecycle, Settings, Apps, and Utils
44

5-
- [ ] `greenworks.initAPI()`
5+
- [x] `greenworks.initAPI()`
66
- [x] `greenworks.init()`
77
- [x] `greenworks.restartAppIfNecessary(appId)`
88
- [x] `greenworks.isSteamRunning()`
9-
- [ ] `greenworks.isSteamRunningOnSteamDeck()`
9+
- [x] `greenworks.isSteamRunningOnSteamDeck()`
1010
- [x] `greenworks.getSteamId()`
1111
- [x] `greenworks.getAppId()`
12-
- [ ] `greenworks.getAppBuildId()`
12+
- [x] `greenworks.getAppBuildId()`
1313
- [x] `greenworks.getCurrentGameLanguage()`
14-
- [ ] `greenworks.getCurrentUILanguage()`
15-
- [ ] `greenworks.getCurrentGameInstallDir()`
14+
- [x] `greenworks.getCurrentUILanguage()`
15+
- [x] `greenworks.getCurrentGameInstallDir()`
1616
- [x] `greenworks.getAppInstallDir(appId, buffer, buffer_size)`
1717
- [ ] `greenworks.getNumberOfPlayers(success_callback, [error_callback])`
18-
- [ ] `greenworks.activateGameOverlay(option)`
19-
- [ ] `greenworks.activateGameOverlayToWebPage(url)`
20-
- [ ] `greenworks.activateGameOverlayToStore(appId, storeFlag)`
18+
- [x] `greenworks.activateGameOverlay(option)`
19+
- [x] `game-overlay-activated` event
20+
- [x] `greenworks.activateGameOverlayToWebPage(url)`
21+
- [x] `greenworks.activateGameOverlayToStore(appId, storeFlag)`
2122
- [x] `greenworks.isGameOverlayEnabled()`
2223
- [x] `greenworks.isSteamInBigPictureMode()`
2324
- [x] `greenworks.isSubscribedApp(appId)`
2425
- [x] `greenworks.isAppInstalled(appId)`
25-
- [ ] `greenworks.getImageSize(handle)`
26-
- [ ] `greenworks.getImageRGBA(handle)`
27-
- [ ] `greenworks.getIPCountry()`
28-
- [ ] `greenworks.getLaunchCommandLine()`
26+
- [x] `greenworks.getImageSize(handle)`
27+
- [x] `greenworks.getImageRGBA(handle)`
28+
- [x] `greenworks.getIPCountry()`
29+
- [x] `greenworks.getLaunchCommandLine()`
30+
- [x] `steam-servers-connected` event
31+
- [x] `steam-servers-disconnected` event
32+
- [x] `steam-server-connect-failure` event
33+
- [x] `steam-shutdown` event
34+
- [x] `new-url-launch-parameters` event
2935

3036
## DLC
3137

3238
- [x] `greenworks.getDLCCount()`
3339
- [x] `greenworks.getDLCDataByIndex(index)`
3440
- [x] `greenworks.isDLCInstalled(dlc_app_id)`
35-
- [ ] `greenworks.installDLC(dlc_app_id)`
36-
- [ ] `greenworks.uninstallDLC(dlc_app_id)`
37-
- [ ] `dlc-installed` event
41+
- [x] `greenworks.installDLC(dlc_app_id)`
42+
- [x] `greenworks.uninstallDLC(dlc_app_id)`
43+
- [x] `dlc-installed` event
3844

3945
## Authentication
4046

@@ -54,16 +60,16 @@
5460

5561
## Achievements and Stats
5662

57-
- [ ] `greenworks.activateAchievement(achievement, success_callback, [error_callback])`
58-
- [ ] `greenworks.indicateAchievementProgress(achievement, current, max)`
59-
- [ ] `greenworks.getAchievement(achievement, success_callback, [error_callback])`
60-
- [ ] `greenworks.clearAchievement(achievement, success_callback, [error_callback])`
61-
- [ ] `greenworks.getAchievementNames()`
63+
- [x] `greenworks.activateAchievement(achievement, success_callback, [error_callback])`
64+
- [x] `greenworks.indicateAchievementProgress(achievement, current, max)`
65+
- [x] `greenworks.getAchievement(achievement, success_callback, [error_callback])`
66+
- [x] `greenworks.clearAchievement(achievement, success_callback, [error_callback])`
67+
- [x] `greenworks.getAchievementNames()`
6268
- [x] `greenworks.getNumberOfAchievements()`
6369
- [x] `greenworks.getStatInt(name)`
6470
- [x] `greenworks.getStatFloat(name)`
65-
- [ ] `greenworks.setStat(name, value)`
66-
- [ ] `greenworks.storeStats(success_callback, [error_callback])`
71+
- [x] `greenworks.setStat(name, value)`
72+
- [x] `greenworks.storeStats(success_callback, [error_callback])`
6773
- [x] `greenworks.resetAllStats(achievementsToo)`
6874

6975
## Cloud and Remote Storage
@@ -202,26 +208,26 @@
202208
## Floating Gamepad Text Input
203209

204210
- [ ] `greenworks.showFloatingGamepadTextInput(keyboardMode, x, y, width, height)`
205-
- [ ] `floating-gamepad-text-input-dismissed` event
211+
- [x] `floating-gamepad-text-input-dismissed` event
206212

207213
## Constants and Enums
208214

209215
- [ ] `_version`
210216
- [ ] `EncryptedAppTicketSymmetricKeyLength`
211-
- [ ] `FriendFlags`
212-
- [ ] `FriendRelationship`
213-
- [ ] `PersonaChange`
214-
- [ ] `AccountType`
215-
- [ ] `ChatEntryType`
216-
- [ ] `ChatMemberStateChange`
217-
- [ ] `LobbyComparison`
218-
- [ ] `LobbyDistanceFilter`
219-
- [ ] `LobbyType`
220-
- [ ] `Result`
221-
- [ ] `UGCMatchingType`
222-
- [ ] `UGCQueryType`
223-
- [ ] `UserUGCList`
224-
- [ ] `UserUGCListSortOrder`
225-
- [ ] `UGCItemState`
226-
- [ ] `FloatingGamepadTextInputMode`
227-
- [ ] P2P send type enum
217+
- [x] `FriendFlags`
218+
- [x] `FriendRelationship`
219+
- [x] `PersonaChange`
220+
- [x] `AccountType`
221+
- [x] `ChatEntryType`
222+
- [x] `ChatMemberStateChange`
223+
- [x] `LobbyComparison`
224+
- [x] `LobbyDistanceFilter`
225+
- [x] `LobbyType`
226+
- [x] `Result`
227+
- [x] `UGCMatchingType`
228+
- [x] `UGCQueryType`
229+
- [x] `UserUGCList`
230+
- [x] `UserUGCListSortOrder`
231+
- [x] `UGCItemState`
232+
- [x] `FloatingGamepadTextInputMode`
233+
- [x] P2P send type enum

CONTRIBUTING.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ callback polling.
3232

3333
## Implementation Milestones
3434

35-
- [ ] Phase 1: close easy parity gaps in existing namespaces.
35+
- [x] Phase 1: close easy parity gaps in existing namespaces.
3636
- `getAppBuildId`
3737
- `isSteamRunningOnSteamDeck`
3838
- UI language alias/mapping decision
@@ -43,8 +43,12 @@ callback polling.
4343
- `indicateAchievementProgress`
4444
- `getAchievementNames`
4545
- `setStat` convenience helper or documented split helpers
46-
- [ ] Phase 2: add typed enum exports and result/object types.
47-
- [ ] Phase 3: expand callback bridge to cover Greenworks event families.
46+
- [x] Phase 2: add typed enum exports.
47+
- [ ] Phase 2b: add typed result/object types for larger API families.
48+
- [x] Phase 3a: refactor callback bridge payloads and cover core Greenworks
49+
event outcomes.
50+
- [ ] Phase 3b: expand callback bridge as friends, lobbies, P2P, UGC, and auth
51+
event families land.
4852
- [ ] Phase 4: implement friends, SteamID object parity or string-first
4953
equivalents, avatars, chat, and rich presence.
5054
- [ ] Phase 5: implement remote storage/cloud and callback-style async wrappers.

GREENWORKS.md

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,43 @@ if (!result.ok) {
3737
## Callback Pumping
3838

3939
Greenworks exposes an EventEmitter-style API. Node3D currently uses explicit
40-
callback polling:
40+
callback polling. Event names use Greenworks' kebab-case strings, and
41+
Greenworks positional event arguments become named object fields:
4142

4243
```ts
4344
import { update } from '@node-3d/steam-api';
4445

4546
for (const event of update()) {
46-
console.log(event);
47+
switch (event.type) {
48+
case 'game-overlay-activated':
49+
console.log(event.active);
50+
break;
51+
case 'steam-server-connect-failure':
52+
console.log(event.result, event.stillRetrying);
53+
break;
54+
case 'dlc-installed':
55+
console.log(event.appId);
56+
break;
57+
}
4758
}
4859
```
4960

5061
`update()` calls `steam.runCallbacks()` and then drains
5162
`callbacks.pollCallbacks()`.
5263

64+
Implemented event mappings:
65+
66+
| Greenworks event | Node3D callback payload |
67+
| --- | --- |
68+
| `game-overlay-activated`, `isActive` | `{ type: 'game-overlay-activated', active, userInitiated, appId, overlayPid }` |
69+
| `steam-servers-connected` | `{ type: 'steam-servers-connected' }` |
70+
| `steam-servers-disconnected` | `{ type: 'steam-servers-disconnected', result }` |
71+
| `steam-server-connect-failure`, `statusCode` | `{ type: 'steam-server-connect-failure', result, stillRetrying }` |
72+
| `steam-shutdown` | `{ type: 'steam-shutdown' }` |
73+
| `dlc-installed`, `appId` | `{ type: 'dlc-installed', appId }` |
74+
| `new-url-launch-parameters` | `{ type: 'new-url-launch-parameters' }` |
75+
| `floating-gamepad-text-input-dismissed` | `{ type: 'floating-gamepad-text-input-dismissed' }` |
76+
5377
## Current Capability Mappings
5478

5579
| Greenworks | Node3D |
@@ -58,26 +82,76 @@ for (const event of update()) {
5882
| `greenworks.initAPI()` | `steam.initEx()` |
5983
| `greenworks.restartAppIfNecessary(appId)` | `steam.restartAppIfNecessary(appId)` |
6084
| `greenworks.isSteamRunning()` | `steam.isSteamRunning()` |
85+
| `greenworks.isSteamRunningOnSteamDeck()` | `utils.isSteamRunningOnSteamDeck()` |
6186
| `greenworks.getSteamId()` | `user.getSteamId()` |
6287
| `greenworks.getAppId()` | `utils.getAppId()` |
88+
| `greenworks.getAppBuildId()` | `apps.getAppBuildId()` |
6389
| `greenworks.getCurrentGameLanguage()` | `apps.getCurrentGameLanguage()` |
6490
| `greenworks.getCurrentUILanguage()` | `utils.getSteamUiLanguage()` |
6591
| `greenworks.getAppInstallDir(appId, buffer, buffer_size)` | `apps.getAppInstallDir(appId)` |
92+
| `greenworks.getCurrentGameInstallDir()` | `apps.getCurrentGameInstallDir()` |
93+
| `greenworks.getLaunchCommandLine()` | `apps.getLaunchCommandLine()` |
94+
| `greenworks.activateGameOverlay(option)` | `friends.activateGameOverlay(dialog)` |
95+
| `greenworks.activateGameOverlayToWebPage(url)` | `friends.activateGameOverlayToWebPage(url)` |
96+
| `greenworks.activateGameOverlayToStore(appId, storeFlag)` | `friends.activateGameOverlayToStore(appId, flag)` |
6697
| `greenworks.isGameOverlayEnabled()` | `utils.isOverlayEnabled()` |
6798
| `greenworks.isSteamInBigPictureMode()` | `utils.isSteamInBigPictureMode()` |
6899
| `greenworks.isSubscribedApp(appId)` | `apps.isSubscribedApp(appId)` |
69100
| `greenworks.isAppInstalled(appId)` | `apps.isAppInstalled(appId)` |
101+
| `greenworks.getImageSize(handle)` | `utils.getImageSize(image)` |
102+
| `greenworks.getImageRGBA(handle)` | `utils.getImageRgba(image)` |
70103
| `greenworks.getIPCountry()` | `utils.getIpCountry()` |
71104
| `greenworks.getDLCCount()` | `apps.getDlcCount()` |
72105
| `greenworks.getDLCDataByIndex(index)` | `apps.getDlcDataByIndex(index)` |
73106
| `greenworks.isDLCInstalled(appId)` | `apps.isDlcInstalled(appId)` |
107+
| `greenworks.installDLC(appId)` | `apps.installDlc(appId)` |
108+
| `greenworks.uninstallDLC(appId)` | `apps.uninstallDlc(appId)` |
74109
| `greenworks.getAuthSessionTicket(success, error)` | `user.getAuthSessionTicket()` |
75110
| `greenworks.cancelAuthTicket(handle)` | `user.cancelAuthTicket(handle)` |
111+
| `greenworks.activateAchievement(name, success, error)` | `userStats.setAchievement(name)` |
112+
| `greenworks.indicateAchievementProgress(name, current, max)` | `userStats.indicateAchievementProgress(name, current, max)` |
113+
| `greenworks.getAchievement(name, success, error)` | `userStats.getAchievement(name)` |
114+
| `greenworks.clearAchievement(name, success, error)` | `userStats.clearAchievement(name)` |
115+
| `greenworks.getAchievementNames()` | `userStats.getAchievementNames()` |
76116
| `greenworks.getNumberOfAchievements()` | `userStats.getNumAchievements()` |
77117
| `greenworks.getStatInt(name)` | `userStats.getStatInt(name)` |
78118
| `greenworks.getStatFloat(name)` | `userStats.getStatFloat(name)` |
119+
| `greenworks.setStat(name, value)` | `userStats.setStat(name, value)` |
120+
| `greenworks.storeStats(success, error)` | `userStats.storeStats()` |
79121
| `greenworks.resetAllStats(achievementsToo)` | `userStats.resetAllStats(achievementsToo)` |
80122

123+
## Enum Mappings
124+
125+
Greenworks enum objects are available as top-level Node3D exports with the same
126+
object names:
127+
128+
```ts
129+
import { FriendFlags, LobbyType, Result } from '@node-3d/steam-api';
130+
```
131+
132+
Available enum objects:
133+
134+
- `FriendFlags`
135+
- `FriendRelationship`
136+
- `PersonaChange`
137+
- `AccountType`
138+
- `ChatEntryType`
139+
- `ChatMemberStateChange`
140+
- `LobbyComparison`
141+
- `LobbyDistanceFilter`
142+
- `LobbyType`
143+
- `Result`
144+
- `UGCMatchingType`
145+
- `UGCQueryType`
146+
- `UserUGCList`
147+
- `UserUGCListSortOrder`
148+
- `UGCItemState`
149+
- `FloatingGamepadTextInputMode`
150+
- `P2PSendType`
151+
152+
`FriendFlags.Suggested` is not exported because it was removed from the
153+
Steamworks SDK and Greenworks had already disabled that member in source.
154+
81155
## Stat and Achievement Differences
82156

83157
Greenworks combines integer and floating point stat writes:
@@ -86,17 +160,19 @@ Greenworks combines integer and floating point stat writes:
86160
greenworks.setStat('score', 10);
87161
```
88162

89-
Node3D exposes the underlying type choice explicitly:
163+
Node3D supports the combined helper and also exposes the underlying type choice
164+
explicitly:
90165

91166
```ts
92167
import { userStats } from '@node-3d/steam-api';
93168

169+
userStats.setStat('score', 10);
94170
userStats.setStatInt('score', 10);
95171
userStats.setStatFloat('accuracy', 0.75);
96172
```
97173

98174
Greenworks uses callback-style achievement helpers for set/get/clear. Node3D
99-
currently exposes direct-return equivalents:
175+
exposes direct-return equivalents:
100176

101177
```ts
102178
userStats.setAchievement('ACH_WIN_ONE_GAME');
@@ -125,5 +201,5 @@ Currently missing major Greenworks capability families:
125201
- workshop and UGC,
126202
- archive utilities,
127203
- floating gamepad text input,
128-
- many Greenworks event outcomes,
129-
- Greenworks enum object exports.
204+
- Greenworks event outcomes tied to missing API families,
205+
- Greenworks constants that require additional SDK libraries.

0 commit comments

Comments
 (0)