mirror of
https://github.com/k1tbyte/Wand-Enhancer.git
synced 2026-09-03 02:00:41 +00:00
310 lines
9.3 KiB
JavaScript
Vendored
310 lines
9.3 KiB
JavaScript
Vendored
import {
|
|
GAME_ENDED_EVENT,
|
|
GAME_LAUNCHED_EVENT,
|
|
GAME_STATUS_CHANNEL,
|
|
SYNTHETIC_SESSION_IDLE_EVENT,
|
|
SYNTHETIC_SESSION_RUNNING_EVENT,
|
|
TRAINER_ENDED_EVENT,
|
|
TRAINER_SNAPSHOT_CHANNEL,
|
|
} from './constants.js';
|
|
import { invokeIpc, isRecord, safeString, toStringId } from './runtime.js';
|
|
|
|
export function createIdleGameSession() {
|
|
return {
|
|
state: 'idle',
|
|
event: 'snapshot',
|
|
processId: null,
|
|
gameId: null,
|
|
titleId: null,
|
|
titleName: null,
|
|
sessionDurationSeconds: null,
|
|
startedAt: null,
|
|
endedAt: null,
|
|
};
|
|
}
|
|
|
|
export function createIdleTrainerStatus() {
|
|
return {
|
|
state: 'idle',
|
|
event: 'snapshot',
|
|
trainerId: null,
|
|
displayName: null,
|
|
gameId: null,
|
|
titleId: null,
|
|
};
|
|
}
|
|
|
|
export function installGameStatusSubscriptions(state) {
|
|
let installed = false;
|
|
|
|
if (state.gameLifecycleService && !state.gameLifecycleSubscriptionsInstalled) {
|
|
installed = installLifecycleSubscriptions(state) || installed;
|
|
}
|
|
|
|
if (state.trainerVisibilityService && !state.trainerVisibilitySubscriptionInstalled) {
|
|
state.currentRunningTrainer = normalizeRunningTrainerStatus(
|
|
state.trainerVisibilityService.runningTrainer,
|
|
'snapshot',
|
|
);
|
|
syncGameSessionFromTrainerStatus(state, state.currentRunningTrainer);
|
|
installed = installTrainerVisibilitySubscription(state) || installed;
|
|
state.trainerVisibilitySubscriptionInstalled = true;
|
|
}
|
|
|
|
if (
|
|
state.trainerService &&
|
|
!state.trainerEndedSubscriptionInstalled &&
|
|
typeof state.trainerService.onTrainerEnded === 'function'
|
|
) {
|
|
state.trainerService.onTrainerEnded(() => {
|
|
clearTrainerSnapshot(state, TRAINER_ENDED_EVENT, true);
|
|
});
|
|
state.trainerEndedSubscriptionInstalled = true;
|
|
installed = true;
|
|
}
|
|
|
|
if (!installed) {
|
|
return;
|
|
}
|
|
|
|
state.log(
|
|
'info',
|
|
'Game status hooks installed.',
|
|
`lifecycle=${state.gameLifecycleSubscriptionsInstalled ? 'yes' : 'no'}, trainer=${state.trainerVisibilitySubscriptionInstalled ? 'yes' : 'no'}, trainerEnded=${state.trainerEndedSubscriptionInstalled ? 'yes' : 'no'}`,
|
|
);
|
|
void syncGameStatus(state, true);
|
|
}
|
|
|
|
export function clearTrainerSnapshot(state, reason, clearSession = false) {
|
|
state.currentRunningTrainer = {
|
|
...createIdleTrainerStatus(),
|
|
event: reason,
|
|
};
|
|
|
|
if (clearSession) {
|
|
clearGameSession(state, reason);
|
|
} else if (
|
|
state.currentGameSession.state === 'running' &&
|
|
isSyntheticGameSessionEvent(state.currentGameSession.event)
|
|
) {
|
|
syncGameSessionFromTrainerStatus(state, state.currentRunningTrainer);
|
|
}
|
|
|
|
void syncGameStatus(state, true);
|
|
void invokeIpc(state, TRAINER_SNAPSHOT_CHANNEL, null, 'Trainer snapshot clear');
|
|
}
|
|
|
|
export async function syncGameStatus(state, force = false) {
|
|
if (!state.ipcRenderer) {
|
|
return false;
|
|
}
|
|
|
|
const snapshot = buildGameStatusSnapshot(state);
|
|
const signature = makeGameStatusSignature(snapshot);
|
|
if (!force && signature === state.lastGameStatusSignature) {
|
|
return false;
|
|
}
|
|
|
|
state.lastGameStatusSignature = signature;
|
|
|
|
const sent = await invokeIpc(
|
|
state,
|
|
GAME_STATUS_CHANNEL,
|
|
snapshot,
|
|
'Game status snapshot',
|
|
'error',
|
|
);
|
|
if (sent) {
|
|
state.log(
|
|
'info',
|
|
'Game status snapshot sent.',
|
|
`session=${snapshot.session.state}/${snapshot.session.event}, trainer=${snapshot.trainer.state}/${snapshot.trainer.event}`,
|
|
);
|
|
}
|
|
return sent;
|
|
}
|
|
|
|
function installLifecycleSubscriptions(state) {
|
|
let installed = false;
|
|
|
|
if (typeof state.gameLifecycleService.onGameLaunched === 'function') {
|
|
state.gameLifecycleService.onGameLaunched((event) => {
|
|
state.currentGameSession = {
|
|
state: 'running',
|
|
event: GAME_LAUNCHED_EVENT,
|
|
processId: typeof event?.processId === 'number' ? event.processId : null,
|
|
gameId: toStringId(event?.gameId),
|
|
titleId: toStringId(event?.titleId),
|
|
titleName: safeString(event?.titleName),
|
|
sessionDurationSeconds: null,
|
|
startedAt: new Date().toISOString(),
|
|
endedAt: null,
|
|
};
|
|
void syncGameStatus(state, true);
|
|
});
|
|
installed = true;
|
|
}
|
|
|
|
if (typeof state.gameLifecycleService.onGameEnded === 'function') {
|
|
state.gameLifecycleService.onGameEnded((event) => {
|
|
clearGameSession(
|
|
state,
|
|
GAME_ENDED_EVENT,
|
|
typeof event?.sessionDurationSeconds === 'number'
|
|
? event.sessionDurationSeconds
|
|
: state.currentGameSession.sessionDurationSeconds,
|
|
);
|
|
void syncGameStatus(state, true);
|
|
});
|
|
installed = true;
|
|
}
|
|
|
|
if (installed) {
|
|
state.gameLifecycleSubscriptionsInstalled = true;
|
|
}
|
|
|
|
return installed;
|
|
}
|
|
|
|
function installTrainerVisibilitySubscription(state) {
|
|
if (typeof state.trainerVisibilityService.onRunningTrainerChanged !== 'function') {
|
|
return false;
|
|
}
|
|
|
|
state.trainerVisibilityService.onRunningTrainerChanged((runningTrainer) => {
|
|
state.currentRunningTrainer = normalizeRunningTrainerStatus(
|
|
runningTrainer,
|
|
runningTrainer ? 'trainer-running' : 'trainer-idle',
|
|
);
|
|
syncGameSessionFromTrainerStatus(state, state.currentRunningTrainer);
|
|
void syncGameStatus(state, true);
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
function normalizeRunningTrainerStatus(runningTrainer, event = 'snapshot') {
|
|
const info = isRecord(runningTrainer?.info)
|
|
? runningTrainer.info
|
|
: isRecord(runningTrainer)
|
|
? runningTrainer
|
|
: null;
|
|
if (!info) {
|
|
return {
|
|
...createIdleTrainerStatus(),
|
|
event,
|
|
};
|
|
}
|
|
|
|
return {
|
|
state: 'running',
|
|
event,
|
|
trainerId: toStringId(info.trainerId) || toStringId(info.id),
|
|
displayName: safeString(
|
|
info.displayName,
|
|
info.gameName,
|
|
info.titleName,
|
|
info.title,
|
|
info.name,
|
|
),
|
|
gameId: toStringId(info.gameId),
|
|
titleId: toStringId(info.titleId),
|
|
};
|
|
}
|
|
|
|
function syncGameSessionFromTrainerStatus(state, trainerStatus) {
|
|
if (trainerStatus?.state === 'running') {
|
|
if (
|
|
state.currentGameSession.state === 'running' &&
|
|
!isSyntheticGameSessionEvent(state.currentGameSession.event)
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
state.currentGameSession = {
|
|
state: 'running',
|
|
event: SYNTHETIC_SESSION_RUNNING_EVENT,
|
|
processId: state.currentGameSession.processId,
|
|
gameId: trainerStatus.gameId ?? state.currentGameSession.gameId,
|
|
titleId: trainerStatus.titleId ?? state.currentGameSession.titleId,
|
|
titleName: trainerStatus.displayName ?? state.currentGameSession.titleName,
|
|
sessionDurationSeconds: null,
|
|
startedAt:
|
|
state.currentGameSession.state === 'running' &&
|
|
isSyntheticGameSessionEvent(state.currentGameSession.event)
|
|
? state.currentGameSession.startedAt
|
|
: new Date().toISOString(),
|
|
endedAt: null,
|
|
};
|
|
|
|
return true;
|
|
}
|
|
|
|
if (
|
|
state.currentGameSession.state !== 'running' ||
|
|
!isSyntheticGameSessionEvent(state.currentGameSession.event)
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
const startedAt = state.currentGameSession.startedAt;
|
|
const sessionDurationSeconds = startedAt
|
|
? Math.max(0, Math.round((Date.now() - new Date(startedAt).getTime()) / 1000))
|
|
: null;
|
|
|
|
clearGameSession(state, SYNTHETIC_SESSION_IDLE_EVENT, sessionDurationSeconds);
|
|
|
|
return true;
|
|
}
|
|
|
|
function clearGameSession(
|
|
state,
|
|
event,
|
|
sessionDurationSeconds = state.currentGameSession.sessionDurationSeconds,
|
|
) {
|
|
state.currentGameSession = {
|
|
state: 'idle',
|
|
event,
|
|
processId: null,
|
|
gameId: null,
|
|
titleId: null,
|
|
titleName: null,
|
|
sessionDurationSeconds,
|
|
startedAt: state.currentGameSession.startedAt,
|
|
endedAt: new Date().toISOString(),
|
|
};
|
|
}
|
|
|
|
function isSyntheticGameSessionEvent(event) {
|
|
return event === SYNTHETIC_SESSION_RUNNING_EVENT || event === SYNTHETIC_SESSION_IDLE_EVENT;
|
|
}
|
|
|
|
function buildGameStatusSnapshot(state) {
|
|
return {
|
|
instanceId: 'wand-game-status',
|
|
updatedAt: new Date().toISOString(),
|
|
session: { ...state.currentGameSession },
|
|
trainer: { ...state.currentRunningTrainer },
|
|
};
|
|
}
|
|
|
|
function makeGameStatusSignature(snapshot) {
|
|
return [
|
|
snapshot.session.state,
|
|
snapshot.session.event,
|
|
snapshot.session.processId ?? '',
|
|
snapshot.session.gameId ?? '',
|
|
snapshot.session.titleId ?? '',
|
|
snapshot.session.titleName ?? '',
|
|
snapshot.session.sessionDurationSeconds ?? '',
|
|
snapshot.session.startedAt ?? '',
|
|
snapshot.session.endedAt ?? '',
|
|
snapshot.trainer.state,
|
|
snapshot.trainer.event,
|
|
snapshot.trainer.trainerId ?? '',
|
|
snapshot.trainer.displayName ?? '',
|
|
snapshot.trainer.gameId ?? '',
|
|
snapshot.trainer.titleId ?? '',
|
|
].join('|');
|
|
}
|