@energy8platform/game-engine 0.15.2 → 0.16.0
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 +22 -12
- package/dist/assets.d.ts +2 -14
- package/dist/core.cjs.js +16 -201
- package/dist/core.cjs.js.map +1 -1
- package/dist/core.d.ts +4 -35
- package/dist/core.esm.js +12 -197
- package/dist/core.esm.js.map +1 -1
- package/dist/debug.cjs.js +5 -233
- package/dist/debug.cjs.js.map +1 -1
- package/dist/debug.d.ts +2 -140
- package/dist/debug.esm.js +2 -233
- package/dist/debug.esm.js.map +1 -1
- package/dist/index.cjs.js +21 -432
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +10 -195
- package/dist/index.esm.js +14 -429
- package/dist/index.esm.js.map +1 -1
- package/dist/lua.cjs.js +7 -1493
- package/dist/lua.cjs.js.map +1 -1
- package/dist/lua.d.ts +1 -403
- package/dist/lua.esm.js +1 -1483
- package/dist/lua.esm.js.map +1 -1
- package/dist/react.cjs.js.map +1 -1
- package/dist/react.d.ts +4 -35
- package/dist/react.esm.js.map +1 -1
- package/dist/vite.cjs.js +19 -175
- package/dist/vite.cjs.js.map +1 -1
- package/dist/vite.d.ts +8 -3
- package/dist/vite.esm.js +10 -173
- package/dist/vite.esm.js.map +1 -1
- package/package.json +6 -16
- package/src/core/GameApplication.ts +14 -18
- package/src/debug/index.ts +4 -2
- package/src/index.ts +3 -3
- package/src/loading/LoadingScene.ts +1 -1
- package/src/loading/index.ts +9 -2
- package/src/lua/index.ts +3 -31
- package/src/types.ts +16 -41
- package/src/vite/index.ts +13 -196
- package/bin/simulate.ts +0 -139
- package/scripts/install-simulate.mjs +0 -101
- package/src/debug/DevBridge.ts +0 -305
- package/src/loading/CSSPreloader.ts +0 -129
- package/src/loading/logo.ts +0 -95
- package/src/lua/ActionRouter.ts +0 -132
- package/src/lua/LuaEngine.ts +0 -412
- package/src/lua/LuaEngineAPI.ts +0 -314
- package/src/lua/NativeSimulationRunner.ts +0 -367
- package/src/lua/ParallelSimulationRunner.ts +0 -156
- package/src/lua/PersistentState.ts +0 -80
- package/src/lua/SessionManager.ts +0 -227
- package/src/lua/SimulationRunner.ts +0 -192
- package/src/lua/SimulationWorker.ts +0 -44
- package/src/lua/fengari.d.ts +0 -10
- package/src/lua/types.ts +0 -149
package/src/debug/DevBridge.ts
DELETED
|
@@ -1,305 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Bridge,
|
|
3
|
-
type BridgeMessageType,
|
|
4
|
-
type InitData,
|
|
5
|
-
type GameConfigData,
|
|
6
|
-
type PlayResultData,
|
|
7
|
-
type SessionData,
|
|
8
|
-
type PlayResultAckPayload,
|
|
9
|
-
type PlayParams,
|
|
10
|
-
} from '@energy8platform/game-sdk';
|
|
11
|
-
import type { GameDefinition } from '../lua/types';
|
|
12
|
-
|
|
13
|
-
export interface DevBridgeConfig {
|
|
14
|
-
/** Mock initial balance */
|
|
15
|
-
balance?: number;
|
|
16
|
-
/** Mock currency */
|
|
17
|
-
currency?: string;
|
|
18
|
-
/** Game config */
|
|
19
|
-
gameConfig?: Partial<GameConfigData>;
|
|
20
|
-
/** Base URL for assets (default: '/assets/') */
|
|
21
|
-
assetsUrl?: string;
|
|
22
|
-
/** Active session to resume (null = no active session) */
|
|
23
|
-
session?: SessionData | null;
|
|
24
|
-
/** Custom play result handler — return mock result data */
|
|
25
|
-
onPlay?: (params: PlayParams) => Partial<PlayResultData>;
|
|
26
|
-
/** Simulated network delay in ms */
|
|
27
|
-
networkDelay?: number;
|
|
28
|
-
/** Enable debug logging */
|
|
29
|
-
debug?: boolean;
|
|
30
|
-
/** Lua script source code. When set, play requests are routed to the Vite dev server's LuaEngine. */
|
|
31
|
-
luaScript?: string;
|
|
32
|
-
/** Game definition for Lua engine (actions, transitions, etc.) */
|
|
33
|
-
gameDefinition?: GameDefinition;
|
|
34
|
-
/** RNG seed for deterministic Lua execution */
|
|
35
|
-
luaSeed?: number;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const DEFAULT_CONFIG: Omit<Required<DevBridgeConfig>, 'luaScript' | 'gameDefinition' | 'luaSeed'> = {
|
|
39
|
-
balance: 10000,
|
|
40
|
-
currency: 'USD',
|
|
41
|
-
gameConfig: {
|
|
42
|
-
id: 'dev-game',
|
|
43
|
-
type: 'slot',
|
|
44
|
-
version: '1.0.0',
|
|
45
|
-
viewport: { width: 1920, height: 1080 },
|
|
46
|
-
betLevels: [0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50],
|
|
47
|
-
},
|
|
48
|
-
assetsUrl: '/assets/',
|
|
49
|
-
session: null,
|
|
50
|
-
onPlay: () => ({}),
|
|
51
|
-
networkDelay: 200,
|
|
52
|
-
debug: true,
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Mock host bridge for local development.
|
|
57
|
-
*
|
|
58
|
-
* Uses the SDK's `Bridge` class in `devMode` to communicate with
|
|
59
|
-
* `CasinoGameSDK` via a shared in-memory `MemoryChannel`, removing
|
|
60
|
-
* the need for postMessage and iframes.
|
|
61
|
-
*
|
|
62
|
-
* When `luaScript` is set, play requests are sent to the Vite dev server
|
|
63
|
-
* which runs LuaEngine in Node.js — no fengari in the browser.
|
|
64
|
-
*
|
|
65
|
-
* @example
|
|
66
|
-
* ```ts
|
|
67
|
-
* import { DevBridge } from '@energy8platform/game-engine/debug';
|
|
68
|
-
*
|
|
69
|
-
* const devBridge = new DevBridge({
|
|
70
|
-
* balance: 5000,
|
|
71
|
-
* currency: 'EUR',
|
|
72
|
-
* gameConfig: { id: 'my-slot', type: 'slot', betLevels: [0.2, 0.5, 1, 2] },
|
|
73
|
-
* onPlay: ({ action, bet }) => ({
|
|
74
|
-
* totalWin: Math.random() > 0.5 ? bet * (Math.random() * 20) : 0,
|
|
75
|
-
* data: { matrix: [[1,2,3],[4,5,6],[7,8,9]] },
|
|
76
|
-
* }),
|
|
77
|
-
* });
|
|
78
|
-
* devBridge.start();
|
|
79
|
-
* ```
|
|
80
|
-
*/
|
|
81
|
-
export class DevBridge {
|
|
82
|
-
private _config: Required<Pick<DevBridgeConfig, 'balance' | 'currency' | 'gameConfig' | 'assetsUrl' | 'session' | 'onPlay' | 'networkDelay' | 'debug'>> & Pick<DevBridgeConfig, 'luaScript' | 'gameDefinition' | 'luaSeed'>;
|
|
83
|
-
private _balance: number;
|
|
84
|
-
private _roundCounter = 0;
|
|
85
|
-
private _bridge: Bridge | null = null;
|
|
86
|
-
private _useLuaServer: boolean;
|
|
87
|
-
|
|
88
|
-
constructor(config: DevBridgeConfig = {}) {
|
|
89
|
-
this._config = { ...DEFAULT_CONFIG, ...config };
|
|
90
|
-
this._balance = this._config.balance;
|
|
91
|
-
this._useLuaServer = !!(this._config.luaScript && this._config.gameDefinition);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** Current mock balance */
|
|
95
|
-
get balance(): number {
|
|
96
|
-
return this._balance;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/** Start listening for SDK messages */
|
|
100
|
-
start(): void {
|
|
101
|
-
if (this._bridge) return;
|
|
102
|
-
|
|
103
|
-
console.debug('[DevBridge] Starting with config:', this._config);
|
|
104
|
-
|
|
105
|
-
this._bridge = new Bridge({ devMode: true, debug: this._config.debug });
|
|
106
|
-
|
|
107
|
-
this._bridge.on('GAME_READY', (_payload: unknown, id?: string) => {
|
|
108
|
-
this.handleGameReady(id);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
this._bridge.on('PLAY_REQUEST', (payload: PlayParams, id?: string) => {
|
|
112
|
-
this.handlePlayRequest(payload, id);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
this._bridge.on('PLAY_RESULT_ACK', (payload: PlayResultAckPayload) => {
|
|
116
|
-
this.handlePlayAck(payload);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
this._bridge.on('GET_BALANCE', (_payload: unknown, id?: string) => {
|
|
120
|
-
this.handleGetBalance(id);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
this._bridge.on('GET_STATE', (_payload: unknown, id?: string) => {
|
|
124
|
-
this.handleGetState(id);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
this._bridge.on('OPEN_DEPOSIT', () => {
|
|
128
|
-
this.handleOpenDeposit();
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
if (this._config.debug) {
|
|
132
|
-
const mode = this._useLuaServer ? 'Lua (server-side)' : 'onPlay callback';
|
|
133
|
-
console.log(`[DevBridge] Started — mode: ${mode}`);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/** Stop listening */
|
|
138
|
-
stop(): void {
|
|
139
|
-
if (this._bridge) {
|
|
140
|
-
this._bridge.destroy();
|
|
141
|
-
this._bridge = null;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (this._config.debug) {
|
|
145
|
-
console.log('[DevBridge] Stopped');
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/** Set mock balance */
|
|
150
|
-
setBalance(balance: number): void {
|
|
151
|
-
this._balance = balance;
|
|
152
|
-
this._bridge?.send('BALANCE_UPDATE', { balance: this._balance });
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/** Destroy the dev bridge */
|
|
156
|
-
destroy(): void {
|
|
157
|
-
this.stop();
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// ─── Message Handling ──────────────────────────────────
|
|
161
|
-
|
|
162
|
-
private handleGameReady(id?: string): void {
|
|
163
|
-
const initData: InitData = {
|
|
164
|
-
balance: this._balance,
|
|
165
|
-
currency: this._config.currency,
|
|
166
|
-
config: this._config.gameConfig as GameConfigData,
|
|
167
|
-
session: this._config.session,
|
|
168
|
-
assetsUrl: this._config.assetsUrl,
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
this.delayedSend('INIT', initData, id);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
private handlePlayRequest(
|
|
175
|
-
payload: PlayParams,
|
|
176
|
-
id?: string,
|
|
177
|
-
): void {
|
|
178
|
-
const { action, bet, roundId, params } = payload;
|
|
179
|
-
this._roundCounter++;
|
|
180
|
-
|
|
181
|
-
if (this._useLuaServer) {
|
|
182
|
-
// Debit bet (server deducts before Lua execution)
|
|
183
|
-
// For session actions (free spins), debit is 0 — LuaEngine handles bet from session
|
|
184
|
-
this._balance -= bet;
|
|
185
|
-
|
|
186
|
-
this.executeLuaOnServer({ action, bet, roundId, params })
|
|
187
|
-
.then((result) => {
|
|
188
|
-
this._bridge?.send('PLAY_RESULT', result, id);
|
|
189
|
-
})
|
|
190
|
-
.catch((err) => {
|
|
191
|
-
console.error('[DevBridge] Lua server error:', err);
|
|
192
|
-
this._balance += bet;
|
|
193
|
-
this._bridge?.send('PLAY_RESULT', this.buildFallbackResult(action, bet, roundId), id);
|
|
194
|
-
});
|
|
195
|
-
} else {
|
|
196
|
-
// Fallback to onPlay callback
|
|
197
|
-
const customResult = this._config.onPlay({ action, bet, roundId, params });
|
|
198
|
-
const totalWin = customResult.totalWin ?? (Math.random() > 0.6 ? bet * (1 + Math.random() * 10) : 0);
|
|
199
|
-
|
|
200
|
-
this._balance += totalWin;
|
|
201
|
-
|
|
202
|
-
const result: PlayResultData = {
|
|
203
|
-
roundId: roundId ?? `dev-round-${this._roundCounter}`,
|
|
204
|
-
action,
|
|
205
|
-
balanceAfter: this._balance,
|
|
206
|
-
totalWin: Math.round(totalWin * 100) / 100,
|
|
207
|
-
data: customResult.data ?? {},
|
|
208
|
-
nextActions: customResult.nextActions ?? ['spin'],
|
|
209
|
-
session: customResult.session ?? null,
|
|
210
|
-
creditPending: false,
|
|
211
|
-
bonusFreeSpin: customResult.bonusFreeSpin ?? null,
|
|
212
|
-
currency: this._config.currency,
|
|
213
|
-
gameId: this._config.gameConfig?.id ?? 'dev-game',
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
this.delayedSend('PLAY_RESULT', result, id);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
private async executeLuaOnServer(params: PlayParams): Promise<PlayResultData> {
|
|
221
|
-
const response = await fetch('/__lua-play', {
|
|
222
|
-
method: 'POST',
|
|
223
|
-
headers: { 'Content-Type': 'application/json' },
|
|
224
|
-
body: JSON.stringify(params),
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
if (!response.ok) {
|
|
228
|
-
const err = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
229
|
-
throw new Error(err.error ?? `HTTP ${response.status}`);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const luaResult = await response.json();
|
|
233
|
-
|
|
234
|
-
// Server credit logic:
|
|
235
|
-
// shouldCredit = (no session) OR (session.completed)
|
|
236
|
-
// creditAmount = result.totalWin
|
|
237
|
-
const shouldCredit = !luaResult.session || luaResult.session.completed;
|
|
238
|
-
if (shouldCredit && luaResult.totalWin > 0) {
|
|
239
|
-
this._balance += luaResult.totalWin;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
return {
|
|
243
|
-
roundId: params.roundId ?? `dev-round-${this._roundCounter}`,
|
|
244
|
-
action: params.action,
|
|
245
|
-
balanceAfter: this._balance,
|
|
246
|
-
totalWin: Math.round(luaResult.totalWin * 100) / 100,
|
|
247
|
-
data: luaResult.data,
|
|
248
|
-
nextActions: luaResult.nextActions,
|
|
249
|
-
session: luaResult.session,
|
|
250
|
-
creditPending: !shouldCredit,
|
|
251
|
-
bonusFreeSpin: null,
|
|
252
|
-
currency: this._config.currency,
|
|
253
|
-
gameId: this._config.gameConfig?.id ?? 'dev-game',
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
private buildFallbackResult(action: string, bet: number, roundId?: string): PlayResultData {
|
|
258
|
-
return {
|
|
259
|
-
roundId: roundId ?? `dev-round-${this._roundCounter}`,
|
|
260
|
-
action,
|
|
261
|
-
balanceAfter: this._balance,
|
|
262
|
-
totalWin: 0,
|
|
263
|
-
data: { error: 'Lua execution failed' },
|
|
264
|
-
nextActions: ['spin'],
|
|
265
|
-
session: null,
|
|
266
|
-
creditPending: false,
|
|
267
|
-
bonusFreeSpin: null,
|
|
268
|
-
currency: this._config.currency,
|
|
269
|
-
gameId: this._config.gameConfig?.id ?? 'dev-game',
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
private handlePlayAck(_payload: PlayResultAckPayload): void {
|
|
274
|
-
if (this._config.debug) {
|
|
275
|
-
console.log('[DevBridge] Play acknowledged');
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
private handleGetBalance(id?: string): void {
|
|
280
|
-
this.delayedSend('BALANCE_UPDATE', { balance: this._balance }, id);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
private handleGetState(id?: string): void {
|
|
284
|
-
this.delayedSend('STATE_RESPONSE', { session: this._config.session ?? null }, id);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
private handleOpenDeposit(): void {
|
|
288
|
-
if (this._config.debug) {
|
|
289
|
-
console.log('[DevBridge] Open deposit requested (mock: adding 1000)');
|
|
290
|
-
}
|
|
291
|
-
this._balance += 1000;
|
|
292
|
-
this._bridge?.send('BALANCE_UPDATE', { balance: this._balance });
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// ─── Communication ─────────────────────────────────────
|
|
296
|
-
|
|
297
|
-
private delayedSend(type: BridgeMessageType, payload: unknown, id?: string): void {
|
|
298
|
-
const delay = this._config.networkDelay;
|
|
299
|
-
if (delay > 0) {
|
|
300
|
-
setTimeout(() => this._bridge?.send(type, payload, id), delay);
|
|
301
|
-
} else {
|
|
302
|
-
this._bridge?.send(type, payload, id);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
}
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import type { LoadingScreenConfig } from '../types';
|
|
2
|
-
import { buildLogoSVG } from './logo';
|
|
3
|
-
|
|
4
|
-
const PRELOADER_ID = '__ge-css-preloader__';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Inline SVG logo with animated loader bar.
|
|
8
|
-
* The `#loader` path acts as the progress fill — animated via clipPath.
|
|
9
|
-
*/
|
|
10
|
-
const LOGO_SVG = buildLogoSVG({
|
|
11
|
-
idPrefix: 'pl',
|
|
12
|
-
svgClass: 'ge-logo-svg',
|
|
13
|
-
clipRectClass: 'ge-clip-rect',
|
|
14
|
-
textClass: 'ge-preloader-svg-text',
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Creates a lightweight CSS-only preloader that appears instantly,
|
|
19
|
-
* BEFORE PixiJS/WebGL is initialized.
|
|
20
|
-
*
|
|
21
|
-
* Displays the Energy8 logo SVG with an animated loader bar.
|
|
22
|
-
*/
|
|
23
|
-
export function createCSSPreloader(
|
|
24
|
-
container: HTMLElement,
|
|
25
|
-
config?: LoadingScreenConfig,
|
|
26
|
-
): void {
|
|
27
|
-
if (document.getElementById(PRELOADER_ID)) return;
|
|
28
|
-
|
|
29
|
-
const bgColor =
|
|
30
|
-
typeof config?.backgroundColor === 'string'
|
|
31
|
-
? config.backgroundColor
|
|
32
|
-
: typeof config?.backgroundColor === 'number'
|
|
33
|
-
? `#${config.backgroundColor.toString(16).padStart(6, '0')}`
|
|
34
|
-
: '#0a0a1a';
|
|
35
|
-
|
|
36
|
-
const bgGradient = config?.backgroundGradient ?? `linear-gradient(135deg, ${bgColor} 0%, #1a1a3e 100%)`;
|
|
37
|
-
|
|
38
|
-
const customHTML = config?.cssPreloaderHTML ?? '';
|
|
39
|
-
|
|
40
|
-
const el = document.createElement('div');
|
|
41
|
-
el.id = PRELOADER_ID;
|
|
42
|
-
el.innerHTML = customHTML || `
|
|
43
|
-
<div class="ge-preloader-content">
|
|
44
|
-
${LOGO_SVG}
|
|
45
|
-
</div>
|
|
46
|
-
`;
|
|
47
|
-
|
|
48
|
-
const style = document.createElement('style');
|
|
49
|
-
style.textContent = `
|
|
50
|
-
#${PRELOADER_ID} {
|
|
51
|
-
position: absolute;
|
|
52
|
-
top: 0; left: 0;
|
|
53
|
-
width: 100%; height: 100%;
|
|
54
|
-
background: ${bgGradient};
|
|
55
|
-
display: flex;
|
|
56
|
-
align-items: center;
|
|
57
|
-
justify-content: center;
|
|
58
|
-
z-index: 10000;
|
|
59
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
60
|
-
transition: opacity 0.4s ease-out;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
#${PRELOADER_ID}.ge-preloader-hidden {
|
|
64
|
-
opacity: 0;
|
|
65
|
-
pointer-events: none;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
.ge-preloader-content {
|
|
69
|
-
display: flex;
|
|
70
|
-
flex-direction: column;
|
|
71
|
-
align-items: center;
|
|
72
|
-
width: 80%;
|
|
73
|
-
max-width: 700px;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.ge-logo-svg {
|
|
77
|
-
width: 100%;
|
|
78
|
-
height: auto;
|
|
79
|
-
filter: drop-shadow(0 0 30px rgba(121, 57, 194, 0.4));
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/* Animate the loader clip-rect to shimmer while waiting */
|
|
83
|
-
.ge-clip-rect {
|
|
84
|
-
animation: ge-loader-fill 2s ease-in-out infinite;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
@keyframes ge-loader-fill {
|
|
88
|
-
0% { width: 0; }
|
|
89
|
-
50% { width: 174; }
|
|
90
|
-
100% { width: 0; }
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/* Animate the SVG text opacity */
|
|
94
|
-
.ge-preloader-svg-text {
|
|
95
|
-
animation: ge-pulse 1.5s ease-in-out infinite;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
@keyframes ge-pulse {
|
|
99
|
-
0%, 100% { opacity: 0.4; }
|
|
100
|
-
50% { opacity: 1; }
|
|
101
|
-
}
|
|
102
|
-
`;
|
|
103
|
-
|
|
104
|
-
container.style.position = container.style.position || 'relative';
|
|
105
|
-
container.appendChild(style);
|
|
106
|
-
container.appendChild(el);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Remove the CSS preloader with a smooth fade-out transition.
|
|
111
|
-
*/
|
|
112
|
-
export function removeCSSPreloader(container: HTMLElement): void {
|
|
113
|
-
const el = document.getElementById(PRELOADER_ID);
|
|
114
|
-
if (!el) return;
|
|
115
|
-
|
|
116
|
-
el.classList.add('ge-preloader-hidden');
|
|
117
|
-
|
|
118
|
-
// Remove after transition
|
|
119
|
-
el.addEventListener('transitionend', () => {
|
|
120
|
-
el.remove();
|
|
121
|
-
// Also remove the style element
|
|
122
|
-
const styles = container.querySelectorAll('style');
|
|
123
|
-
for (const style of styles) {
|
|
124
|
-
if (style.textContent?.includes(PRELOADER_ID)) {
|
|
125
|
-
style.remove();
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
}
|
package/src/loading/logo.ts
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared Energy8 SVG logo with an embedded loader bar.
|
|
3
|
-
*
|
|
4
|
-
* The loader bar fill is controlled via a `<clipPath>` whose `<rect>` width
|
|
5
|
-
* is animatable. Different consumers customise gradient IDs and the clip
|
|
6
|
-
* element's ID/class to avoid collisions when both CSSPreloader and
|
|
7
|
-
* LoadingScene appear in the same DOM.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/** SVG path data for the Energy8 wordmark — reused across loaders */
|
|
11
|
-
const WORDMARK_PATHS = `
|
|
12
|
-
<path d="m241 81.75h-19.28c-1.77 0-6.73 4.98-7.43 6.99l-4.36 12.22c-0.49 1.37 0.05 2.92 1.06 4.32-2.07 1.19-3.69 3.08-4.36 5.43l-3.25 10.41c-0.86 2.89 2.39 6.63 4.31 6.63h19.28c1.96 0 7.4-5.56 7.96-7.51l2.96-10.22c0.63-2.25 0.1-3.98-1.22-4.99 2.55-1.56 3.86-4.14 4.55-6.31l2.77-9.31c0.74-2.57-1.37-7.66-2.99-7.66zm-13.36 28.31-2.27 7.03h-8.28l2.58-8.28h8.28l-0.31 1.25zm4.06-16.97-2.11 6.7h-7.04l2.25-7.34h7.26l-0.36 0.64z" fill="url(#GID0)"/>
|
|
13
|
-
<path d="m202.5 81.75-9.31 14.97-2.32-14.97h-11.82l4.32 25.15-0.57 4.91-8.64 26.44 15.31-12.76 5.63-16.48 19.96-27.26h-12.56z" fill="url(#GID1)"/>
|
|
14
|
-
<path d="m174.2 81.75h-19.78l-5.75 5.16-10.79 33.2c-0.77 2.53 2.48 6.93 4.87 6.93h17.38c2.63 0 7.85-5.34 8.32-6.83l5.37-18.14h-15.17l-2.2 7.64h3.78l-2.25 7.2h-8.01l7.1-25.52h7.58l-1.48 8.4 12.78-5.98c1.28-0.63 1.97-3.99 1.61-6.61-0.36-2.34-1.64-5.45-3.36-5.45z" fill="url(#GID2)"/>
|
|
15
|
-
<path d="m140.6 81.75h-70.6l-5.36 19.37-4.26-19.37h-46.76l2.95 5.88-10.58 39.28h26.84l2.95-9.52-15.63-0.13 2.55-8.34h8.74l8.47-9.81h-14.61l2.11-7.3h15.47l2.54-8.71 2.58 4.74-11.4 39.07h11.05l6.46-21.49 8.84 36.33 19.18-55.67-1.83-3.36 3.68 4.09-12.07 40.1h28.18l3.39-10.31h-17.01l2.67-8.03h9.98l7.58-9.52h-14.28l1.93-6.6h14.61l3.25-9.73 2.81 5.12-11.3 38.89h11.05l5.23-17.81h1.62l1.48 17.6h10.69l-1.48-16.81c4.75-1.28 7.52-5.9 8.64-9.81l2.95-11.3c0.86-2.73-1.43-6.85-3.3-6.85zm-9.8 17.3h-8.69l2.54-7.84h8.35l-2.2 7.84z" fill="url(#GID3)"/>
|
|
16
|
-
<path d="m205.9 148.9h-122.6l-2.61-3.12h-32.4l-2.51 3.12h-1.59c-5.34 0-7.94 4.88-7.94 7.65v0.03c0 4.2 3.55 7.6 7.74 7.6h103.6l2.11 3.12h36.09l1.82-3.12h18.3c5.25 0 6.64-5.3 6.64-7.35v-0.25c0-4.23-2.9-7.68-6.64-7.68zm-0.7 12.83h-160.6c-3.69 0-6.11-2.58-6.11-5.47v-0.03c0-2.89 2.1-5.47 5.61-5.47h161.1c3.45 0 4.89 3.12 4.89 5.65v0.17c0 2.57-2.11 5.15-4.89 5.15z" fill="url(#GID4)"/>`;
|
|
17
|
-
|
|
18
|
-
/** Gradient definitions template (gradient IDs are replaced per-consumer) */
|
|
19
|
-
const GRADIENT_DEFS = `
|
|
20
|
-
<linearGradient id="GID0" x1="223.7" x2="223.7" y1="81.75" y2="127.8" gradientUnits="userSpaceOnUse">
|
|
21
|
-
<stop stop-color="#663BA6"/><stop stop-color="#7939C2" offset=".349"/><stop stop-color="#8A2FC0" offset=".6615"/><stop stop-color="#791BA3" offset="1"/>
|
|
22
|
-
</linearGradient>
|
|
23
|
-
<linearGradient id="GID1" x1="194.6" x2="194.6" y1="81.75" y2="138.3" gradientUnits="userSpaceOnUse">
|
|
24
|
-
<stop stop-color="#663BA6"/><stop stop-color="#7939C2" offset=".349"/><stop stop-color="#8A2FC0" offset=".6615"/><stop stop-color="#791BA3" offset="1"/>
|
|
25
|
-
</linearGradient>
|
|
26
|
-
<linearGradient id="GID2" x1="157.8" x2="157.8" y1="81.75" y2="127" gradientUnits="userSpaceOnUse">
|
|
27
|
-
<stop stop-color="#663BA6"/><stop stop-color="#7939C2" offset=".349"/><stop stop-color="#8A2FC0" offset=".6615"/><stop stop-color="#791BA3" offset="1"/>
|
|
28
|
-
</linearGradient>
|
|
29
|
-
<linearGradient id="GID3" x1="79.96" x2="79.96" y1="81.75" y2="141.8" gradientUnits="userSpaceOnUse">
|
|
30
|
-
<stop stop-color="#663BA6"/><stop stop-color="#7939C2" offset=".349"/><stop stop-color="#8A2FC0" offset=".6615"/><stop stop-color="#791BA3" offset="1"/>
|
|
31
|
-
</linearGradient>
|
|
32
|
-
<linearGradient id="GID4" x1="36.18" x2="212.5" y1="156.6" y2="156.6" gradientUnits="userSpaceOnUse">
|
|
33
|
-
<stop stop-color="#316FB0"/><stop stop-color="#1FCDE6" offset=".5"/><stop stop-color="#29FEE7" offset="1"/>
|
|
34
|
-
</linearGradient>
|
|
35
|
-
<linearGradient id="GID5" x1="40.27" x2="208.2" y1="156.4" y2="156.4" gradientUnits="userSpaceOnUse">
|
|
36
|
-
<stop stop-color="#316FB0"/><stop stop-color="#1FCDE6" offset=".5"/><stop stop-color="#29FEE7" offset="1"/>
|
|
37
|
-
</linearGradient>`;
|
|
38
|
-
|
|
39
|
-
/** Max width of the loader bar in SVG units */
|
|
40
|
-
export const LOADER_BAR_MAX_WIDTH = 174;
|
|
41
|
-
|
|
42
|
-
interface LogoSVGOptions {
|
|
43
|
-
/** Prefix for gradient/clip IDs to avoid collisions (e.g. 'pl' or 'ls') */
|
|
44
|
-
idPrefix: string;
|
|
45
|
-
/** Optional CSS class on the root <svg> */
|
|
46
|
-
svgClass?: string;
|
|
47
|
-
/** Optional inline style on the root <svg> */
|
|
48
|
-
svgStyle?: string;
|
|
49
|
-
/** Optional CSS class on the clip <rect> */
|
|
50
|
-
clipRectClass?: string;
|
|
51
|
-
/** Optional id on the clip <rect> (for JS access) */
|
|
52
|
-
clipRectId?: string;
|
|
53
|
-
/** Optional id on the percentage <text> */
|
|
54
|
-
textId?: string;
|
|
55
|
-
/** Default text content */
|
|
56
|
-
textContent?: string;
|
|
57
|
-
/** Optional CSS class on the <text> */
|
|
58
|
-
textClass?: string;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Build the Energy8 SVG logo with a loader bar, using unique IDs.
|
|
63
|
-
*
|
|
64
|
-
* @param opts - Configuration to avoid element ID collisions
|
|
65
|
-
* @returns SVG markup string
|
|
66
|
-
*/
|
|
67
|
-
export function buildLogoSVG(opts: LogoSVGOptions): string {
|
|
68
|
-
const { idPrefix, svgClass, svgStyle, clipRectClass, clipRectId, textId, textContent, textClass } = opts;
|
|
69
|
-
|
|
70
|
-
// Replace gradient ID placeholders with prefixed versions
|
|
71
|
-
const paths = WORDMARK_PATHS.replace(/GID(\d)/g, `${idPrefix}$1`);
|
|
72
|
-
const defs = GRADIENT_DEFS.replace(/GID(\d)/g, `${idPrefix}$1`);
|
|
73
|
-
|
|
74
|
-
const clipId = `${idPrefix}-loader-clip`;
|
|
75
|
-
const fillGradientId = `${idPrefix}5`;
|
|
76
|
-
|
|
77
|
-
const classAttr = svgClass ? ` class="${svgClass}"` : '';
|
|
78
|
-
const styleAttr = svgStyle ? ` style="${svgStyle}"` : '';
|
|
79
|
-
const rectClassAttr = clipRectClass ? ` class="${clipRectClass}"` : '';
|
|
80
|
-
const rectIdAttr = clipRectId ? ` id="${clipRectId}"` : '';
|
|
81
|
-
const txtIdAttr = textId ? ` id="${textId}"` : '';
|
|
82
|
-
const txtClassAttr = textClass ? ` class="${textClass}"` : '';
|
|
83
|
-
|
|
84
|
-
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 200" fill="none"${classAttr}${styleAttr}>
|
|
85
|
-
${paths}
|
|
86
|
-
<clipPath id="${clipId}">
|
|
87
|
-
<rect${rectIdAttr} x="37" y="148" width="0" height="20"${rectClassAttr}/>
|
|
88
|
-
</clipPath>
|
|
89
|
-
<path d="m204.5 152.6h-159.8c-2.78 0-4.45 1.69-4.45 3.99v0.11c0 2.04 1.42 3.43 3.64 3.43h160.6c2.88 0 3.67-2.07 3.67-3.43v-0.25c0-2.04-1.48-3.85-3.67-3.85z" fill="url(#${fillGradientId})" clip-path="url(#${clipId})"/>
|
|
90
|
-
<text${txtIdAttr} x="125" y="196" text-anchor="middle" fill="rgba(255,255,255,0.6)" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif" font-size="8" font-weight="600" letter-spacing="1.5"${txtClassAttr}>${textContent ?? 'Loading...'}</text>
|
|
91
|
-
<defs>
|
|
92
|
-
${defs}
|
|
93
|
-
</defs>
|
|
94
|
-
</svg>`;
|
|
95
|
-
}
|
package/src/lua/ActionRouter.ts
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import type { ActionDefinition, TransitionRule, GameDefinition } from './types';
|
|
2
|
-
|
|
3
|
-
export interface TransitionMatch {
|
|
4
|
-
rule: TransitionRule;
|
|
5
|
-
nextActions: string[];
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Replicates the platform's action dispatch and transition evaluation.
|
|
10
|
-
* Routes play requests to the correct action, evaluates transition conditions
|
|
11
|
-
* against current variables to determine next actions and session operations.
|
|
12
|
-
*/
|
|
13
|
-
export class ActionRouter {
|
|
14
|
-
private actions: Record<string, ActionDefinition>;
|
|
15
|
-
|
|
16
|
-
constructor(gameDefinition: GameDefinition) {
|
|
17
|
-
this.actions = gameDefinition.actions;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** Look up action by name and validate prerequisites */
|
|
21
|
-
resolveAction(actionName: string, hasSession: boolean): ActionDefinition {
|
|
22
|
-
const action = this.actions[actionName];
|
|
23
|
-
if (!action) {
|
|
24
|
-
throw new Error(`Unknown action: "${actionName}". Available: ${Object.keys(this.actions).join(', ')}`);
|
|
25
|
-
}
|
|
26
|
-
if (action.requires_session && !hasSession) {
|
|
27
|
-
throw new Error(`Action "${actionName}" requires an active session`);
|
|
28
|
-
}
|
|
29
|
-
return action;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** Evaluate transitions in order, return the first matching rule */
|
|
33
|
-
evaluateTransitions(
|
|
34
|
-
action: ActionDefinition,
|
|
35
|
-
variables: Record<string, number>,
|
|
36
|
-
): TransitionMatch {
|
|
37
|
-
for (const rule of action.transitions) {
|
|
38
|
-
if (evaluateCondition(rule.condition, variables)) {
|
|
39
|
-
return { rule, nextActions: rule.next_actions };
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
throw new Error(
|
|
43
|
-
`No matching transition for action with stage "${action.stage}". ` +
|
|
44
|
-
`Variables: ${JSON.stringify(variables)}`
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// ─── Condition Evaluator ────────────────────────────────
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Evaluates a transition condition expression against variables.
|
|
53
|
-
*
|
|
54
|
-
* Supports:
|
|
55
|
-
* - "always" → true
|
|
56
|
-
* - Simple comparisons: "var > 0", "var == 1", "var >= 10", "var != 0", "var < 5", "var <= 3"
|
|
57
|
-
* - Logical connectives: "expr && expr", "expr || expr"
|
|
58
|
-
*
|
|
59
|
-
* This covers all patterns used by the platform's govaluate conditions.
|
|
60
|
-
*/
|
|
61
|
-
export function evaluateCondition(
|
|
62
|
-
condition: string,
|
|
63
|
-
variables: Record<string, number>,
|
|
64
|
-
): boolean {
|
|
65
|
-
const trimmed = condition.trim();
|
|
66
|
-
|
|
67
|
-
if (trimmed === 'always') return true;
|
|
68
|
-
|
|
69
|
-
// Handle || (OR) — lowest precedence
|
|
70
|
-
if (trimmed.includes('||')) {
|
|
71
|
-
const parts = splitOnOperator(trimmed, '||');
|
|
72
|
-
return parts.some(part => evaluateCondition(part, variables));
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Handle && (AND)
|
|
76
|
-
if (trimmed.includes('&&')) {
|
|
77
|
-
const parts = splitOnOperator(trimmed, '&&');
|
|
78
|
-
return parts.every(part => evaluateCondition(part, variables));
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Single comparison: "variable op value"
|
|
82
|
-
return evaluateComparison(trimmed, variables);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function splitOnOperator(expr: string, operator: string): string[] {
|
|
86
|
-
const parts: string[] = [];
|
|
87
|
-
let depth = 0;
|
|
88
|
-
let current = '';
|
|
89
|
-
|
|
90
|
-
for (let i = 0; i < expr.length; i++) {
|
|
91
|
-
if (expr[i] === '(') depth++;
|
|
92
|
-
else if (expr[i] === ')') depth--;
|
|
93
|
-
|
|
94
|
-
if (depth === 0 && expr.substring(i, i + operator.length) === operator) {
|
|
95
|
-
parts.push(current);
|
|
96
|
-
current = '';
|
|
97
|
-
i += operator.length - 1;
|
|
98
|
-
} else {
|
|
99
|
-
current += expr[i];
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
parts.push(current);
|
|
103
|
-
return parts;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function evaluateComparison(
|
|
107
|
-
expr: string,
|
|
108
|
-
variables: Record<string, number>,
|
|
109
|
-
): boolean {
|
|
110
|
-
// Match: variable_name operator value
|
|
111
|
-
const match = expr.trim().match(
|
|
112
|
-
/^\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*(>=|<=|!=|==|>|<)\s*(-?\d+(?:\.\d+)?)\s*$/
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
if (!match) {
|
|
116
|
-
throw new Error(`Cannot parse condition: "${expr}"`);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const [, varName, op, valueStr] = match;
|
|
120
|
-
const left = variables[varName] ?? 0;
|
|
121
|
-
const right = parseFloat(valueStr);
|
|
122
|
-
|
|
123
|
-
switch (op) {
|
|
124
|
-
case '>': return left > right;
|
|
125
|
-
case '>=': return left >= right;
|
|
126
|
-
case '<': return left < right;
|
|
127
|
-
case '<=': return left <= right;
|
|
128
|
-
case '==': return left === right;
|
|
129
|
-
case '!=': return left !== right;
|
|
130
|
-
default: return false;
|
|
131
|
-
}
|
|
132
|
-
}
|