Skip to content

Commit 857972e

Browse files
Merge pull request #797 from OpenWebGAL/steamworks
Integrate Steamworks
2 parents 806840e + 5110cbf commit 857972e

File tree

9 files changed

+139
-0
lines changed

9 files changed

+139
-0
lines changed

packages/parser/src/config/scriptConfig.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export const SCRIPT_CONFIG = [
3838
{ scriptString: 'getUserInput', scriptType: commandType.getUserInput },
3939
{ scriptString: 'applyStyle', scriptType: commandType.applyStyle },
4040
{ scriptString: 'wait', scriptType: commandType.wait },
41+
{ scriptString: 'callSteam', scriptType: commandType.callSteam },
4142
];
4243
export const ADD_NEXT_ARG_LIST = [
4344
commandType.bgm,
@@ -53,6 +54,7 @@ export const ADD_NEXT_ARG_LIST = [
5354
commandType.playEffect,
5455
commandType.setTransition,
5556
commandType.applyStyle,
57+
commandType.callSteam,
5658
];
5759

5860
export type ConfigMap = Map<string, ConfigItem>;

packages/parser/src/interface/sceneInterface.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export enum commandType {
3939
getUserInput,
4040
applyStyle,
4141
wait,
42+
callSteam, // 调用Steam功能
4243
}
4344

4445
/**

packages/webgal/src/Core/controller/scene/sceneInterface.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export enum commandType {
3939
getUserInput,
4040
applyStyle,
4141
wait,
42+
callSteam, // 调用Steam功能
4243
}
4344

4445
/**
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { ISentence } from '@/Core/controller/scene/sceneInterface';
2+
import { IPerform } from '@/Core/Modules/perform/performInterface';
3+
import { WebGAL } from '@/Core/WebGAL';
4+
import { logger } from '@/Core/util/logger';
5+
import { getStringArgByKey } from '@/Core/util/getSentenceArg';
6+
import { WEBGAL_NONE } from '../constants';
7+
8+
/**
9+
* Unlocks a Steam achievement via the renderer → Electron bridge.
10+
* The script expects the first positional parameter to be the achievement id.
11+
*/
12+
export const callSteam = (sentence: ISentence): IPerform => {
13+
for (const arg of sentence.args) {
14+
if (arg.key === 'achievementId') {
15+
const achievementId = getStringArgByKey(sentence, 'achievementId');
16+
if (achievementId) {
17+
WebGAL.steam
18+
.unlockAchievement(achievementId)
19+
.then((result) => {
20+
logger.info(`callSteam: achievement ${achievementId} unlock ${result ? 'succeeded' : 'failed'}`);
21+
})
22+
.catch((error) => {
23+
logger.error(`callSteam: achievement ${achievementId} unlock threw`, error);
24+
});
25+
}
26+
}
27+
}
28+
const noperform: IPerform = {
29+
performName: WEBGAL_NONE,
30+
duration: 0,
31+
isHoldOn: false,
32+
stopFunction: () => {},
33+
blockingNext: () => false,
34+
blockingAuto: () => true,
35+
stopTimeout: undefined,
36+
};
37+
38+
return noperform;
39+
};
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { logger } from '@/Core/util/logger';
2+
3+
interface SteamBridge {
4+
initialize: (appId: string) => boolean | Promise<boolean>;
5+
unlockAchievement: (achievementId: string) => boolean | Promise<boolean>;
6+
}
7+
8+
const isWindowAvailable = (): boolean => typeof window !== 'undefined';
9+
10+
/**
11+
* Provides a thin bridge between the renderer process and the Electron Steam integration.
12+
*/
13+
export class SteamIntegration {
14+
public appId: string | null = null;
15+
private initialized = false;
16+
17+
public get isInitialized(): boolean {
18+
return this.initialized;
19+
}
20+
21+
public async initialize(appId: string): Promise<boolean> {
22+
this.appId = appId;
23+
const bridge = this.getSteamBridge();
24+
if (!bridge?.initialize) {
25+
logger.warn('Steam integration initialize call skipped: Electron bridge not present');
26+
this.initialized = false;
27+
return false;
28+
}
29+
30+
try {
31+
const result = await Promise.resolve(bridge.initialize(appId));
32+
if (result) {
33+
logger.info(`Steam integration initialized with AppID ${appId}`);
34+
}
35+
this.initialized = result;
36+
return result;
37+
} catch (error) {
38+
logger.error('Steam integration failed to initialize', error);
39+
this.initialized = false;
40+
return false;
41+
}
42+
}
43+
44+
public async unlockAchievement(achievementId: string): Promise<boolean> {
45+
const bridge = this.getSteamBridge();
46+
if (!bridge?.unlockAchievement) {
47+
logger.warn(`Steam integration unlock call skipped for ${achievementId}: Electron bridge not present`);
48+
return false;
49+
}
50+
51+
if (!this.initialized) {
52+
if (this.appId) {
53+
await this.initialize(this.appId);
54+
} else {
55+
logger.warn('Steam integration unlock call skipped: AppID not set');
56+
return false;
57+
}
58+
}
59+
60+
try {
61+
const result = await Promise.resolve(bridge.unlockAchievement(achievementId));
62+
return result;
63+
} catch (error) {
64+
logger.error(`Steam integration failed to unlock achievement ${achievementId}`, error);
65+
return false;
66+
}
67+
}
68+
69+
private getSteamBridge(): SteamBridge | undefined {
70+
if (!isWindowAvailable()) {
71+
logger.debug('Steam integration unavailable: window is undefined');
72+
return undefined;
73+
}
74+
return window.electronFuncs?.steam;
75+
}
76+
}

packages/webgal/src/Core/parser/sceneParser.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { setTransform } from '@/Core/gameScripts/setTransform';
2727
import { setTransition } from '@/Core/gameScripts/setTransition';
2828
import { unlockBgm } from '@/Core/gameScripts/unlockBgm';
2929
import { unlockCg } from '@/Core/gameScripts/unlockCg';
30+
import { callSteam } from '@/Core/gameScripts/callSteam';
3031
import { end } from '../gameScripts/end';
3132
import { jumpLabel } from '../gameScripts/jumpLabel';
3233
import { pixiInit } from '../gameScripts/pixi/pixiInit';
@@ -72,6 +73,7 @@ export const SCRIPT_TAG_MAP = defineScripts({
7273
getUserInput: ScriptConfig(commandType.getUserInput, getUserInput),
7374
applyStyle: ScriptConfig(commandType.applyStyle, applyStyle, { next: true }),
7475
wait: ScriptConfig(commandType.wait, wait),
76+
callSteam: ScriptConfig(commandType.callSteam, callSteam, { next: true }),
7577
});
7678

7779
export const SCRIPT_CONFIG: IConfigInterface[] = Object.values(SCRIPT_TAG_MAP);

packages/webgal/src/Core/util/coreInitialFunction/infoFetcher.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ export const infoFetcher = (url: string) => {
5858
if (command === 'Legacy_Expression_Blend_Mode') {
5959
Live2D.legacyExpressionBlendMode = res === true;
6060
}
61+
if (command === 'Steam_AppID') {
62+
const appId = String(res);
63+
WebGAL.steam.initialize(appId);
64+
}
6165
}
6266
}
6367
});

packages/webgal/src/Core/webgalCore.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { SceneManager } from '@/Core/Modules/scene';
44
import { AnimationManager } from '@/Core/Modules/animations';
55
import { Gameplay } from './Modules/gamePlay';
66
import { Events } from '@/Core/Modules/events';
7+
import { SteamIntegration } from '@/Core/integration/steamIntegration';
78

89
export class WebgalCore {
910
public sceneManager = new SceneManager();
@@ -13,4 +14,5 @@ export class WebgalCore {
1314
public gameName = '';
1415
public gameKey = '';
1516
public events = new Events();
17+
public steam = new SteamIntegration();
1618
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export {};
2+
3+
declare global {
4+
interface Window {
5+
electronFuncs?: {
6+
steam?: {
7+
initialize: (appId: string) => boolean | Promise<boolean>;
8+
unlockAchievement: (achievementId: string) => boolean | Promise<boolean>;
9+
};
10+
};
11+
}
12+
}

0 commit comments

Comments
 (0)