quake2ts 0.0.190 → 0.0.191

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quake2ts",
3
- "version": "0.0.190",
3
+ "version": "0.0.191",
4
4
  "description": "Quake II re-release port to TypeScript with WebGL renderer - A complete game engine with physics, networking, and BSP rendering",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@8.15.7",
@@ -1,5 +1,120 @@
1
1
  'use strict';
2
2
 
3
+ var shared = require('@quake2ts/shared');
4
+
5
+ // src/hud/crosshair.ts
6
+ var crosshairPic = null;
7
+ var crosshairColor = [1, 1, 1, 1];
8
+ var CROSSHAIR_NAMES = ["ch1", "ch2", "ch3"];
9
+ var crosshairPics = [null, null, null];
10
+ var Init_Crosshair = (cgi3) => {
11
+ for (let i = 0; i < CROSSHAIR_NAMES.length; i++) {
12
+ const name = CROSSHAIR_NAMES[i];
13
+ try {
14
+ crosshairPics[i] = cgi3.Draw_RegisterPic(`pics/${name}.pcx`);
15
+ } catch (e) {
16
+ if (i === 0) {
17
+ try {
18
+ crosshairPics[i] = cgi3.Draw_RegisterPic("pics/crosshair.pcx");
19
+ } catch (e2) {
20
+ cgi3.Com_Print("Failed to load crosshair image\n");
21
+ }
22
+ }
23
+ }
24
+ }
25
+ crosshairPic = crosshairPics[0];
26
+ };
27
+ var Draw_Crosshair = (cgi3, width, height) => {
28
+ if (crosshairPic) {
29
+ const size = cgi3.Draw_GetPicSize(crosshairPic);
30
+ const x = (width - size.width) / 2;
31
+ const y = (height - size.height) / 2;
32
+ cgi3.SCR_DrawColorPic(x, y, crosshairPic, { x: crosshairColor[0], y: crosshairColor[1], z: crosshairColor[2] }, crosshairColor[3]);
33
+ }
34
+ };
35
+
36
+ // src/hud/icons.ts
37
+ var iconPics = /* @__PURE__ */ new Map();
38
+ var ICON_NAMES = [
39
+ // Weapons
40
+ "w_blaster",
41
+ "w_shotgun",
42
+ "w_sshotgun",
43
+ "w_machinegun",
44
+ "w_chaingun",
45
+ "w_glauncher",
46
+ "w_rlauncher",
47
+ "w_hyperblaster",
48
+ "w_railgun",
49
+ "w_bfg",
50
+ "w_grapple",
51
+ // Ammo
52
+ "a_grenades",
53
+ "a_bullets",
54
+ "a_cells",
55
+ "a_rockets",
56
+ "a_shells",
57
+ "a_slugs",
58
+ // Powerups
59
+ "p_quad",
60
+ "p_invulnerability",
61
+ "p_silencer",
62
+ "p_rebreather",
63
+ "p_envirosuit",
64
+ "p_adrenaline",
65
+ "p_megahealth",
66
+ // Armor
67
+ "i_jacketarmor",
68
+ "i_combatarmor",
69
+ "i_bodyarmor",
70
+ "i_powerscreen",
71
+ "i_powershield",
72
+ // Keys
73
+ "k_datacd",
74
+ "k_powercube",
75
+ "k_pyramid",
76
+ "k_dataspin",
77
+ "k_security",
78
+ "k_bluekey",
79
+ "k_redkey"
80
+ ];
81
+ var Init_Icons = (cgi3) => {
82
+ for (const name of ICON_NAMES) {
83
+ try {
84
+ const pic = cgi3.Draw_RegisterPic(`pics/${name}.pcx`);
85
+ iconPics.set(name, pic);
86
+ } catch (e) {
87
+ cgi3.Com_Print(`Failed to load HUD image: pics/${name}.pcx
88
+ `);
89
+ }
90
+ }
91
+ };
92
+
93
+ // src/hud/damage.ts
94
+ var damagePics = /* @__PURE__ */ new Map();
95
+ var DAMAGE_INDICATOR_NAMES = [
96
+ "d_left",
97
+ "d_right",
98
+ "d_up",
99
+ "d_down"
100
+ ];
101
+ var Init_Damage = (cgi3) => {
102
+ for (const name of DAMAGE_INDICATOR_NAMES) {
103
+ try {
104
+ const pic = cgi3.Draw_RegisterPic(`pics/${name}.pcx`);
105
+ damagePics.set(name, pic);
106
+ } catch (e) {
107
+ cgi3.Com_Print(`Failed to load HUD image: pics/${name}.pcx
108
+ `);
109
+ }
110
+ }
111
+ };
112
+ var Draw_Damage = (cgi3, ps, width, height) => {
113
+ if (!ps.damage_yaw && !ps.damage_pitch && !ps.damage_alpha) {
114
+ return;
115
+ }
116
+ };
117
+
3
118
  // src/hud/messages.ts
4
119
  var CENTER_PRINT_DURATION = 3e3;
5
120
  var NOTIFY_DURATION = 5e3;
@@ -72,6 +187,116 @@ var MessageSystem = class {
72
187
  }
73
188
  };
74
189
 
190
+ // src/hud/blends.ts
191
+ var Draw_Blends = (cgi3, ps, width, height) => {
192
+ if (!ps.blend) return;
193
+ const [r, g, b, a] = ps.blend;
194
+ };
195
+
196
+ // src/hud/pickup.ts
197
+ var Draw_Pickup = (cgi3, ps, width, height) => {
198
+ if (!ps.pickupIcon) return;
199
+ const icon = iconPics.get(ps.pickupIcon);
200
+ if (icon) {
201
+ const size = cgi3.Draw_GetPicSize(icon);
202
+ const x = width - size.width - 10;
203
+ const y = height - size.height - 10;
204
+ cgi3.SCR_DrawPic(x, y, icon);
205
+ }
206
+ };
207
+
208
+ // src/hud/numbers.ts
209
+ var Draw_Number = (cgi3, x, y, value, pics, width, color) => {
210
+ const s = Math.abs(value).toString();
211
+ for (let i = 0; i < s.length; i++) {
212
+ const digit = parseInt(s[i]);
213
+ const pic = pics[digit];
214
+ if (pic) {
215
+ {
216
+ cgi3.SCR_DrawPic(x + i * width, y, pic);
217
+ }
218
+ }
219
+ }
220
+ };
221
+ var WEAPON_ICON_MAP = {
222
+ [shared.WeaponId.Blaster]: "w_blaster",
223
+ [shared.WeaponId.Shotgun]: "w_shotgun",
224
+ [shared.WeaponId.SuperShotgun]: "w_sshotgun",
225
+ [shared.WeaponId.Machinegun]: "w_machinegun",
226
+ [shared.WeaponId.Chaingun]: "w_chaingun",
227
+ [shared.WeaponId.GrenadeLauncher]: "w_glauncher",
228
+ [shared.WeaponId.RocketLauncher]: "w_rlauncher",
229
+ [shared.WeaponId.HyperBlaster]: "w_hyperblaster",
230
+ [shared.WeaponId.Railgun]: "w_railgun",
231
+ [shared.WeaponId.BFG10K]: "w_bfg",
232
+ [shared.WeaponId.Grapple]: "w_grapple",
233
+ [shared.WeaponId.ChainFist]: "w_chainfist",
234
+ [shared.WeaponId.EtfRifle]: "w_etf_rifle",
235
+ [shared.WeaponId.ProxLauncher]: "w_prox_launcher",
236
+ [shared.WeaponId.IonRipper]: "w_ionripper",
237
+ [shared.WeaponId.PlasmaBeam]: "w_plasmabeam",
238
+ [shared.WeaponId.Phalanx]: "w_phalanx",
239
+ [shared.WeaponId.Disruptor]: "w_disruptor"
240
+ };
241
+ var KEY_ICON_MAP = {
242
+ "blue": "k_bluekey",
243
+ "red": "k_redkey",
244
+ "green": "k_security",
245
+ "yellow": "k_pyramid"
246
+ };
247
+ var Draw_StatusBar = (cgi3, client, health, armor, ammo, hudNumberPics2, numberWidth2, timeMs, layout) => {
248
+ if (hudNumberPics2.length > 0) {
249
+ Draw_Number(cgi3, layout.HEALTH_X, layout.HEALTH_Y, health, hudNumberPics2, numberWidth2);
250
+ Draw_Number(cgi3, layout.ARMOR_X, layout.ARMOR_Y, armor, hudNumberPics2, numberWidth2);
251
+ Draw_Number(cgi3, layout.AMMO_X, layout.AMMO_Y, ammo, hudNumberPics2, numberWidth2);
252
+ }
253
+ const armorItem = client.inventory.armor;
254
+ if (armorItem && armorItem.armorCount > 0) {
255
+ const iconName = `i_${armorItem.armorType}armor`;
256
+ const icon = iconPics.get(iconName);
257
+ if (icon) {
258
+ cgi3.SCR_DrawPic(layout.ARMOR_X - 24, layout.ARMOR_Y - 2, icon);
259
+ }
260
+ }
261
+ const currentWeapon = client.inventory.currentWeapon;
262
+ if (currentWeapon) {
263
+ const iconName = WEAPON_ICON_MAP[currentWeapon];
264
+ if (iconName) {
265
+ const icon = iconPics.get(iconName);
266
+ if (icon) {
267
+ cgi3.SCR_DrawPic(layout.WEAPON_ICON_X, layout.WEAPON_ICON_Y, icon);
268
+ }
269
+ }
270
+ }
271
+ const keys = Array.from(client.inventory.keys).sort();
272
+ let keyY = layout.WEAPON_ICON_Y - 150 * layout.scale;
273
+ for (const key of keys) {
274
+ const iconName = KEY_ICON_MAP[key];
275
+ if (iconName) {
276
+ const icon = iconPics.get(iconName);
277
+ if (icon) {
278
+ const size = cgi3.Draw_GetPicSize(icon);
279
+ cgi3.SCR_DrawPic(layout.WEAPON_ICON_X, keyY, icon);
280
+ keyY += size.height + 2;
281
+ }
282
+ }
283
+ }
284
+ let powerupX = layout.POWERUP_X;
285
+ for (const [powerup, expiresAt] of Array.from(client.inventory.powerups.entries())) {
286
+ if (expiresAt && expiresAt > timeMs) {
287
+ const iconName = `p_${powerup}`;
288
+ const icon = iconPics.get(iconName);
289
+ if (icon) {
290
+ const size = cgi3.Draw_GetPicSize(icon);
291
+ cgi3.SCR_DrawPic(powerupX, layout.POWERUP_Y, icon);
292
+ const remainingSeconds = Math.ceil((expiresAt - timeMs) / 1e3);
293
+ Draw_Number(cgi3, powerupX + size.width + 2, layout.POWERUP_Y, remainingSeconds, hudNumberPics2, numberWidth2);
294
+ powerupX -= size.width + numberWidth2 * remainingSeconds.toString().length + 8;
295
+ }
296
+ }
297
+ }
298
+ };
299
+
75
300
  // src/hud/layout.ts
76
301
  var REFERENCE_WIDTH = 640;
77
302
  var REFERENCE_HEIGHT = 480;
@@ -125,24 +350,42 @@ function CG_TouchPics() {
125
350
  `);
126
351
  }
127
352
  }
353
+ Init_Crosshair(cgi);
354
+ Init_Icons(cgi);
355
+ Init_Damage(cgi);
128
356
  }
129
357
  function CG_DrawHUD(isplit, data, hud_vrect, hud_safe, scale, playernum, ps) {
130
358
  if (!cgi) {
131
359
  console.error("CG_DrawHUD: cgame imports not initialized");
132
360
  return;
133
361
  }
134
- cgi.CL_ClientTime();
135
- getHudLayout(hud_vrect.width, hud_vrect.height);
362
+ const timeMs = cgi.CL_ClientTime();
363
+ const layout = getHudLayout(hud_vrect.width, hud_vrect.height);
364
+ Draw_Blends(cgi, ps, hud_vrect.width, hud_vrect.height);
365
+ const tempClientState = {
366
+ inventory: {
367
+ armor: null,
368
+ // TODO: Map from ps.stats
369
+ powerups: /* @__PURE__ */ new Map(),
370
+ // TODO: Map from ps.stats
371
+ keys: /* @__PURE__ */ new Set(),
372
+ // TODO: Map from ps.stats
373
+ currentWeapon: void 0
374
+ // TODO: Map from ps.stats/gunindex
375
+ }
376
+ };
377
+ Draw_StatusBar(cgi, tempClientState, 100, 0, 0, hudNumberPics, numberWidth, timeMs, layout);
378
+ Draw_Pickup(cgi, ps, hud_vrect.width, hud_vrect.height);
379
+ Draw_Damage(cgi, ps, hud_vrect.width, hud_vrect.height);
136
380
  if (ps.centerPrint) {
137
- cgi.SCR_DrawFontString(
138
- hud_vrect.width / 2,
139
- hud_vrect.height / 2 - 20,
140
- ps.centerPrint
141
- );
142
- }
143
- if (ps.notify) {
144
- cgi.SCR_DrawFontString(8, 8, ps.notify);
381
+ const lines = ps.centerPrint.split("\n");
382
+ let y = hud_vrect.height / 2 - lines.length * 10;
383
+ for (const line of lines) {
384
+ cgi.SCR_DrawCenterString(y, line);
385
+ y += 16;
386
+ }
145
387
  }
388
+ Draw_Crosshair(cgi, hud_vrect.width, hud_vrect.height);
146
389
  }
147
390
  function CG_GetMessageSystem() {
148
391
  return messageSystem;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hud/messages.ts","../src/hud/layout.ts","../src/screen.ts","../src/index.ts"],"names":["cgi","messageSystem"],"mappings":"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","file":"index.cjs","sourcesContent":["import { Renderer } from '@quake2ts/engine';\nimport { getHudLayout } from './layout.js';\n\ninterface Message {\n text: string;\n startTime: number;\n duration: number;\n}\n\nconst CENTER_PRINT_DURATION = 3000;\nconst NOTIFY_DURATION = 5000;\nconst MAX_NOTIFY_MESSAGES = 4;\n\nexport class MessageSystem {\n private centerPrintMsg: Message | null = null;\n private notifyMessages: Message[] = [];\n\n addCenterPrint(text: string, now: number) {\n this.centerPrintMsg = {\n text,\n startTime: now,\n duration: CENTER_PRINT_DURATION,\n };\n }\n\n addNotify(text: string, now: number) {\n this.notifyMessages.push({\n text,\n startTime: now,\n duration: NOTIFY_DURATION,\n });\n\n if (this.notifyMessages.length > MAX_NOTIFY_MESSAGES) {\n this.notifyMessages.shift();\n }\n }\n\n // Additional methods for cgame API\n setCenterPrint(text: string, now: number) {\n this.centerPrintMsg = {\n text,\n startTime: now,\n duration: CENTER_PRINT_DURATION,\n };\n }\n\n addNotification(text: string, is_chat: boolean, now: number) {\n this.notifyMessages.push({\n text,\n startTime: now,\n duration: NOTIFY_DURATION,\n });\n\n if (this.notifyMessages.length > MAX_NOTIFY_MESSAGES) {\n this.notifyMessages.shift();\n }\n }\n\n clearNotifications() {\n this.notifyMessages = [];\n }\n\n clearCenterPrint() {\n this.centerPrintMsg = null;\n }\n\n drawCenterPrint(renderer: Renderer, now: number, layout: ReturnType<typeof getHudLayout>) {\n if (!this.centerPrintMsg) return;\n\n if (now > this.centerPrintMsg.startTime + this.centerPrintMsg.duration) {\n this.centerPrintMsg = null;\n return;\n }\n\n // Draw centered text\n const width = this.centerPrintMsg.text.length * 8;\n // We ignore layout.CENTER_PRINT_X because drawCenterString calculates X automatically\n const y = layout.CENTER_PRINT_Y;\n\n renderer.drawCenterString(y, this.centerPrintMsg.text);\n }\n\n drawNotifications(renderer: Renderer, now: number) {\n // Remove expired messages\n while (this.notifyMessages.length > 0 && now > this.notifyMessages[0].startTime + this.notifyMessages[0].duration) {\n this.notifyMessages.shift();\n }\n\n let y = 10; // Start near top-left\n for (const msg of this.notifyMessages) {\n renderer.drawString(10, y, msg.text);\n y += 10; // Line height\n }\n }\n}\n","// Dynamic Layout Scaling\nconst REFERENCE_WIDTH = 640;\nconst REFERENCE_HEIGHT = 480;\n\nexport const getHudLayout = (width: number, height: number) => {\n // Determine scale factor - usually based on height to preserve aspect ratio logic or just scale uniform\n // Quake 2 typically scales 2D elements.\n const scaleX = width / REFERENCE_WIDTH;\n const scaleY = height / REFERENCE_HEIGHT;\n const scale = Math.min(scaleX, scaleY); // Uniform scaling\n\n // Or we can just center the 640x480 rect?\n // Modern approach: Scale UI to fit, or anchor to edges.\n // Let's implement edge anchoring logic relative to 640x480 coordinates.\n\n // Original constants (approximate):\n // HEALTH_X: 100, HEALTH_Y: 450\n // ARMOR_X: 200, ARMOR_Y: 450\n // AMMO_X: 540, AMMO_Y: 450\n // CENTER_PRINT: Center screen\n // WEAPON_ICON: Bottom left\n // POWERUP: Bottom right?\n\n // We'll return scaled coordinates.\n // For bottom elements, we should anchor to bottom.\n\n return {\n // Status bar numbers - Anchored Bottom-Left / Center / Right\n HEALTH_X: 100 * scale,\n HEALTH_Y: height - (REFERENCE_HEIGHT - 450) * scale,\n\n ARMOR_X: 200 * scale,\n ARMOR_Y: height - (REFERENCE_HEIGHT - 450) * scale,\n\n AMMO_X: width - (REFERENCE_WIDTH - 540) * scale, // Anchor right? 540 is near right (640)\n AMMO_Y: height - (REFERENCE_HEIGHT - 450) * scale,\n\n // Center print messages - Center\n CENTER_PRINT_X: width / 2,\n CENTER_PRINT_Y: 100 * scale, // Top anchor\n\n // Weapon and powerup icons\n WEAPON_ICON_X: 10 * scale,\n WEAPON_ICON_Y: height - (REFERENCE_HEIGHT - 450) * scale,\n\n POWERUP_X: width - (REFERENCE_WIDTH - 610) * scale,\n POWERUP_Y: height - (REFERENCE_HEIGHT - 450) * scale,\n\n scale: scale\n };\n};\n\n// Backward compatibility (deprecated, but useful for initial refactor)\nexport const HUD_LAYOUT = {\n HEALTH_X: 100,\n HEALTH_Y: 450,\n ARMOR_X: 200,\n ARMOR_Y: 450,\n AMMO_X: 540,\n AMMO_Y: 450,\n CENTER_PRINT_X: 320,\n CENTER_PRINT_Y: 100,\n WEAPON_ICON_X: 10,\n WEAPON_ICON_Y: 450,\n POWERUP_X: 610,\n POWERUP_Y: 450,\n};\n","/**\n * CGame HUD Screen Drawing\n * Reference: rerelease/cg_screen.cpp\n *\n * This module handles all HUD rendering for the cgame package, including:\n * - Status bar (health, armor, ammo)\n * - Crosshair\n * - Damage indicators\n * - Pickup notifications\n * - Messages and center print\n * - Subtitles\n */\n\nimport type { PlayerState } from '@quake2ts/shared';\nimport type { CGameImport } from './types.js';\n\n// HUD component imports\nimport { Draw_Crosshair, Init_Crosshair } from './hud/crosshair.js';\nimport { Init_Icons } from './hud/icons.js';\nimport { Draw_Damage, Init_Damage } from './hud/damage.js';\nimport { Draw_Diagnostics } from './hud/diagnostics.js';\nimport { MessageSystem } from './hud/messages.js';\nimport { SubtitleSystem } from './hud/subtitles.js';\nimport { Draw_Blends } from './hud/blends.js';\nimport { Draw_Pickup } from './hud/pickup.js';\nimport { Draw_StatusBar } from './hud/statusbar.js';\nimport { getHudLayout } from './hud/layout.js';\n\n// Module-level state\nlet cgi: CGameImport | null = null;\nconst hudNumberPics: unknown[] = []; // Will hold pic handles from cgi.Draw_RegisterPic()\nlet numberWidth = 0;\n\n// Message and subtitle systems\nconst messageSystem = new MessageSystem();\nconst subtitleSystem = new SubtitleSystem();\n\n/**\n * Initialize the CGame screen module with import functions.\n * Reference: rerelease/cg_screen.cpp InitCGame()\n */\nexport function CG_InitScreen(imports: CGameImport): void {\n cgi = imports;\n}\n\n/**\n * Precache all HUD images.\n * Reference: rerelease/cg_screen.cpp:1689 (TouchPics)\n *\n * This is called during level load to register all required HUD assets.\n */\nexport function CG_TouchPics(): void {\n if (!cgi) return;\n\n // Load HUD number pics\n hudNumberPics.length = 0;\n for (let i = 0; i < 10; i++) {\n try {\n const pic = cgi.Draw_RegisterPic(`pics/hud/num_${i}.pcx`);\n hudNumberPics.push(pic);\n if (i === 0) {\n const size = cgi.Draw_GetPicSize(pic);\n numberWidth = size.width;\n }\n } catch (e) {\n cgi.Com_Print(`Warning: Failed to load HUD image: pics/hud/num_${i}.pcx\\n`);\n }\n }\n\n // TODO: Call Init functions for other HUD components\n // These will need to be adapted to use cgi.Draw_RegisterPic()\n // instead of direct asset manager access\n}\n\n/**\n * Main HUD drawing function.\n * Reference: rerelease/cg_screen.cpp CG_DrawHUD()\n *\n * Called each frame by the client to render the HUD overlay.\n *\n * @param isplit - Split-screen index (0 for single player)\n * @param data - Additional HUD data (unused in initial implementation)\n * @param hud_vrect - Virtual HUD rectangle (screen coordinates)\n * @param hud_safe - Safe area rectangle (for overscan)\n * @param scale - HUD scale factor\n * @param playernum - Player number\n * @param ps - Current player state\n */\nexport function CG_DrawHUD(\n isplit: number,\n data: unknown,\n hud_vrect: { x: number; y: number; width: number; height: number },\n hud_safe: { x: number; y: number; width: number; height: number },\n scale: number,\n playernum: number,\n ps: PlayerState\n): void {\n if (!cgi) {\n console.error('CG_DrawHUD: cgame imports not initialized');\n return;\n }\n\n // TODO: Implement full HUD rendering using cgi drawing functions\n // For now, this is a placeholder structure\n\n // The full implementation will need to:\n // 1. Read stats from ps.stats[] array using STAT_* constants\n // 2. Use cgi.SCR_DrawPic(), cgi.SCR_DrawChar(), etc. for rendering\n // 3. Calculate layout based on hud_vrect and hud_safe\n // 4. Draw all HUD components in correct order:\n // - Screen blends (damage, powerups)\n // - Status bar\n // - Pickup messages\n // - Damage indicators\n // - Center print\n // - Notifications\n // - Subtitles\n // - Crosshair\n\n const timeMs = cgi.CL_ClientTime();\n const layout = getHudLayout(hud_vrect.width, hud_vrect.height);\n\n // Basic placeholder rendering\n // TODO: Adapt each Draw_* function to use cgi instead of renderer\n\n if (ps.centerPrint) {\n cgi.SCR_DrawFontString(\n hud_vrect.width / 2,\n hud_vrect.height / 2 - 20,\n ps.centerPrint\n );\n }\n\n if (ps.notify) {\n cgi.SCR_DrawFontString(8, 8, ps.notify);\n }\n}\n\n/**\n * Get message system instance.\n * Used by parsing functions to add messages.\n */\nexport function CG_GetMessageSystem(): MessageSystem {\n return messageSystem;\n}\n\n/**\n * Get subtitle system instance.\n * Used by audio system to display subtitles.\n */\nexport function CG_GetSubtitleSystem(): SubtitleSystem {\n return subtitleSystem;\n}\n","/**\n * CGame Module Entry Point\n * Reference: rerelease/cg_main.cpp\n *\n * This module provides the GetCGameAPI() function that returns the cgame_export_t\n * interface to the client engine.\n */\n\nimport type { CGameImport, CGameExport } from './types.js';\nimport type { PlayerState, Vec3 } from '@quake2ts/shared';\nimport { LayoutFlags } from '@quake2ts/shared';\nimport { CG_InitScreen, CG_TouchPics, CG_DrawHUD, CG_GetMessageSystem, CG_GetSubtitleSystem } from './screen.js';\n\n// Module-level state\nlet cgi: CGameImport | null = null;\n\n/**\n * Initialize the CGame module.\n * Reference: rerelease/cg_main.cpp InitCGame()\n */\nfunction Init(): void {\n if (!cgi) {\n console.error('CGame Init: cgame imports not set');\n return;\n }\n\n cgi.Com_Print('===== CGame Initialization =====\\n');\n\n // Initialize screen/HUD module\n CG_InitScreen(cgi);\n\n cgi.Com_Print('CGame initialized\\n');\n}\n\n/**\n * Shutdown the CGame module.\n * Reference: rerelease/cg_main.cpp ShutdownCGame()\n */\nfunction Shutdown(): void {\n if (cgi) {\n cgi.Com_Print('CGame shutdown\\n');\n }\n cgi = null;\n}\n\n/**\n * Main HUD drawing function (wrapper for CG_DrawHUD).\n * Reference: rerelease/cg_screen.cpp CG_DrawHUD()\n */\nfunction DrawHUD(\n isplit: number,\n data: unknown,\n hud_vrect: { x: number; y: number; width: number; height: number },\n hud_safe: { x: number; y: number; width: number; height: number },\n scale: number,\n playernum: number,\n ps: PlayerState\n): void {\n CG_DrawHUD(isplit, data, hud_vrect, hud_safe, scale, playernum, ps);\n}\n\n/**\n * Precache all HUD images.\n * Reference: rerelease/cg_screen.cpp TouchPics()\n */\nfunction TouchPics(): void {\n CG_TouchPics();\n}\n\n/**\n * Get layout flags for current player state.\n * Reference: rerelease/cg_screen.cpp\n */\nfunction GetLayoutFlags(ps: PlayerState): LayoutFlags {\n // TODO: Implement proper layout flag calculation\n // Based on inventory state, help state, intermission, etc.\n return 0 as LayoutFlags; // No flags set by default\n}\n\n/**\n * Placeholder stubs for remaining CGameExport functions.\n * These will be implemented as needed.\n */\n\nfunction GetActiveWeaponWheelWeapon(ps: PlayerState): number {\n return 0;\n}\n\nfunction GetOwnedWeaponWheelWeapons(ps: PlayerState): number[] {\n return [];\n}\n\nfunction GetWeaponWheelAmmoCount(ps: PlayerState, weapon: number): number {\n return 0;\n}\n\nfunction GetPowerupWheelCount(ps: PlayerState): number {\n return 0;\n}\n\nfunction GetHitMarkerDamage(ps: PlayerState): number {\n return 0;\n}\n\nfunction Pmove(pmove: unknown): void {\n // TODO: Implement client-side movement prediction\n // Should call shared Pmove() function\n}\n\nfunction ParseConfigString(i: number, s: string): void {\n // TODO: Implement config string parsing\n // Handle CONFIG_N64_PHYSICS, CS_AIRACCEL, etc.\n}\n\nfunction ParseCenterPrint(str: string, isplit: number, instant: boolean): void {\n if (!cgi) return;\n const messageSystem = CG_GetMessageSystem();\n // TODO: Parse layout strings and handle key bindings\n messageSystem.setCenterPrint(str, cgi.CL_ClientTime());\n}\n\nfunction NotifyMessage(isplit: number, msg: string, is_chat: boolean): void {\n if (!cgi) return;\n const messageSystem = CG_GetMessageSystem();\n messageSystem.addNotification(msg, is_chat, cgi.CL_ClientTime());\n}\n\nfunction ClearNotify(isplit: number): void {\n const messageSystem = CG_GetMessageSystem();\n messageSystem.clearNotifications();\n}\n\nfunction ClearCenterprint(isplit: number): void {\n const messageSystem = CG_GetMessageSystem();\n messageSystem.clearCenterPrint();\n}\n\nfunction GetMonsterFlashOffset(id: number): Vec3 {\n return { x: 0, y: 0, z: 0 };\n}\n\nfunction GetExtension(name: string): unknown {\n return null;\n}\n\n/**\n * Main entry point for CGame module.\n * Reference: rerelease/cg_main.cpp GetCGameAPI()\n *\n * @param imports - Functions provided by the client engine\n * @returns CGame export interface\n */\nexport function GetCGameAPI(imports: CGameImport): CGameExport {\n cgi = imports;\n\n return {\n // Lifecycle\n Init,\n Shutdown,\n\n // Rendering\n DrawHUD,\n TouchPics,\n\n // Layout\n LayoutFlags: GetLayoutFlags,\n\n // Weapon wheel\n GetActiveWeaponWheelWeapon,\n GetOwnedWeaponWheelWeapons,\n GetWeaponWheelAmmoCount,\n GetPowerupWheelCount,\n\n // Hit markers\n GetHitMarkerDamage,\n\n // Prediction\n Pmove,\n\n // Parsing\n ParseConfigString,\n ParseCenterPrint,\n NotifyMessage,\n\n // State management\n ClearNotify,\n ClearCenterprint,\n\n // Effects\n GetMonsterFlashOffset,\n\n // Extension\n GetExtension,\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/hud/crosshair.ts","../src/hud/icons.ts","../src/hud/damage.ts","../src/hud/messages.ts","../src/hud/blends.ts","../src/hud/pickup.ts","../src/hud/numbers.ts","../src/hud/types.ts","../src/hud/statusbar.ts","../src/hud/layout.ts","../src/screen.ts","../src/index.ts"],"names":["cgi","WeaponId","hudNumberPics","numberWidth","messageSystem"],"mappings":"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","file":"index.cjs","sourcesContent":["import { CGameImport } from '../types.js';\n\nlet crosshairPic: unknown | null = null;\nlet crosshairIndex = 0; // Default to ch1\nlet crosshairColor: [number, number, number, number] = [1, 1, 1, 1]; // Default white\nlet crosshairEnabled = true;\n\nconst CROSSHAIR_NAMES = ['ch1', 'ch2', 'ch3'];\nconst crosshairPics: (unknown | null)[] = [null, null, null];\n\nexport const Init_Crosshair = (cgi: CGameImport) => {\n for (let i = 0; i < CROSSHAIR_NAMES.length; i++) {\n const name = CROSSHAIR_NAMES[i];\n try {\n crosshairPics[i] = cgi.Draw_RegisterPic(`pics/${name}.pcx`);\n } catch (e) {\n if (i === 0) {\n try {\n crosshairPics[i] = cgi.Draw_RegisterPic('pics/crosshair.pcx');\n } catch (e2) {\n cgi.Com_Print('Failed to load crosshair image\\n');\n }\n }\n }\n }\n\n crosshairPic = crosshairPics[0];\n}\n\nexport const Set_Crosshair = (index: number) => {\n // If index is 0, we treat it as disabled if we want to follow Q2 convention strictly,\n // but here index maps to CROSSHAIR_NAMES.\n // If we want a \"None\" option, we can check for that.\n\n // For now, let's keep index pointing to textures.\n // We can add a separate enable/disable function or make index -1 disable it.\n\n if (index === -1) {\n crosshairEnabled = false;\n return;\n }\n\n crosshairEnabled = true;\n if (index >= 0 && index < crosshairPics.length) {\n crosshairIndex = index;\n crosshairPic = crosshairPics[index];\n }\n};\n\nexport const Set_CrosshairColor = (r: number, g: number, b: number, a: number = 1) => {\n crosshairColor = [r, g, b, a];\n}\n\nexport const Cycle_Crosshair = () => {\n // Cycle includes a \"None\" state (represented by index -1 or just disabling)\n // 0 -> 1 -> 2 -> ... -> Disabled -> 0\n\n if (!crosshairEnabled) {\n crosshairEnabled = true;\n crosshairIndex = 0;\n } else {\n crosshairIndex++;\n if (crosshairIndex >= CROSSHAIR_NAMES.length) {\n crosshairEnabled = false;\n crosshairIndex = 0; // Reset for next cycle\n }\n }\n\n if (crosshairEnabled) {\n crosshairPic = crosshairPics[crosshairIndex];\n if (!crosshairPic) {\n let found = false;\n for (let i = 0; i < CROSSHAIR_NAMES.length; i++) {\n const idx = (crosshairIndex + i) % CROSSHAIR_NAMES.length;\n if (crosshairPics[idx]) {\n crosshairIndex = idx;\n crosshairPic = crosshairPics[idx];\n found = true;\n break;\n }\n }\n if (!found) crosshairPic = null;\n }\n } else {\n crosshairPic = null;\n }\n\n return crosshairEnabled ? crosshairIndex : -1;\n};\n\nexport const Draw_Crosshair = (cgi: CGameImport, width: number, height: number) => {\n if (crosshairEnabled && crosshairPic) {\n const size = cgi.Draw_GetPicSize(crosshairPic);\n const x = (width - size.width) / 2;\n const y = (height - size.height) / 2;\n // Draw with color\n cgi.SCR_DrawColorPic(x, y, crosshairPic, { x: crosshairColor[0], y: crosshairColor[1], z: crosshairColor[2] }, crosshairColor[3]);\n }\n};\n","import { CGameImport } from '../types.js';\n\n/**\n * Global map of loaded HUD icons.\n * This is imported and used by statusbar.ts for rendering.\n */\nexport const iconPics = new Map<string, unknown>();\n\nconst ICON_NAMES = [\n // Weapons\n 'w_blaster', 'w_shotgun', 'w_sshotgun', 'w_machinegun', 'w_chaingun',\n 'w_glauncher', 'w_rlauncher', 'w_hyperblaster', 'w_railgun', 'w_bfg',\n 'w_grapple',\n // Ammo\n 'a_grenades', 'a_bullets', 'a_cells', 'a_rockets', 'a_shells', 'a_slugs',\n // Powerups\n 'p_quad', 'p_invulnerability', 'p_silencer', 'p_rebreather', 'p_envirosuit',\n 'p_adrenaline', 'p_megahealth',\n // Armor\n 'i_jacketarmor', 'i_combatarmor', 'i_bodyarmor', 'i_powerscreen', 'i_powershield',\n // Keys\n 'k_datacd', 'k_powercube', 'k_pyramid', 'k_dataspin', 'k_security', 'k_bluekey', 'k_redkey'\n];\n\n/**\n * Initialize and precache all HUD icon images.\n * Called during level load via CG_TouchPics().\n */\nexport const Init_Icons = (cgi: CGameImport) => {\n for (const name of ICON_NAMES) {\n try {\n const pic = cgi.Draw_RegisterPic(`pics/${name}.pcx`);\n iconPics.set(name, pic);\n } catch (e) {\n cgi.Com_Print(`Failed to load HUD image: pics/${name}.pcx\\n`);\n }\n }\n};\n","import { CGameImport } from '../types.js';\nimport { PlayerState, angleVectors, dotVec3, normalizeVec3 } from '@quake2ts/shared';\n\nconst damagePics = new Map<string, unknown>();\n\nconst DAMAGE_INDICATOR_NAMES = [\n 'd_left', 'd_right', 'd_up', 'd_down'\n];\n\nexport const Init_Damage = (cgi: CGameImport) => {\n for (const name of DAMAGE_INDICATOR_NAMES) {\n try {\n const pic = cgi.Draw_RegisterPic(`pics/${name}.pcx`);\n damagePics.set(name, pic);\n } catch (e) {\n cgi.Com_Print(`Failed to load HUD image: pics/${name}.pcx\\n`);\n }\n }\n};\n\nexport const Draw_Damage = (cgi: CGameImport, ps: PlayerState, width: number, height: number) => {\n // Basic placeholder check, ps structure needs damageIndicators support properly added\n // if using new PlayerState from shared. Assuming it matches for now.\n // If not, we need to add damage tracking to client/cgame state from events.\n if (!ps.damage_yaw && !ps.damage_pitch && !ps.damage_alpha) {\n // The original Q2 uses view angles and damage direction to pick quadrant.\n // It's event based usually. For now keeping structure but relying on\n // whatever ps has. Rerelease likely has fields or events.\n return;\n }\n\n // TODO: Implement damage indicator logic based on damage events or state\n // The previous implementation assumed a `damageIndicators` array which might not exist on PlayerState.\n // We will revisit this when implementing the damage event handling.\n};\n","import { Renderer } from '@quake2ts/engine';\nimport { getHudLayout } from './layout.js';\n\ninterface Message {\n text: string;\n startTime: number;\n duration: number;\n}\n\nconst CENTER_PRINT_DURATION = 3000;\nconst NOTIFY_DURATION = 5000;\nconst MAX_NOTIFY_MESSAGES = 4;\n\nexport class MessageSystem {\n private centerPrintMsg: Message | null = null;\n private notifyMessages: Message[] = [];\n\n addCenterPrint(text: string, now: number) {\n this.centerPrintMsg = {\n text,\n startTime: now,\n duration: CENTER_PRINT_DURATION,\n };\n }\n\n addNotify(text: string, now: number) {\n this.notifyMessages.push({\n text,\n startTime: now,\n duration: NOTIFY_DURATION,\n });\n\n if (this.notifyMessages.length > MAX_NOTIFY_MESSAGES) {\n this.notifyMessages.shift();\n }\n }\n\n // Additional methods for cgame API\n setCenterPrint(text: string, now: number) {\n this.centerPrintMsg = {\n text,\n startTime: now,\n duration: CENTER_PRINT_DURATION,\n };\n }\n\n addNotification(text: string, is_chat: boolean, now: number) {\n this.notifyMessages.push({\n text,\n startTime: now,\n duration: NOTIFY_DURATION,\n });\n\n if (this.notifyMessages.length > MAX_NOTIFY_MESSAGES) {\n this.notifyMessages.shift();\n }\n }\n\n clearNotifications() {\n this.notifyMessages = [];\n }\n\n clearCenterPrint() {\n this.centerPrintMsg = null;\n }\n\n drawCenterPrint(renderer: Renderer, now: number, layout: ReturnType<typeof getHudLayout>) {\n if (!this.centerPrintMsg) return;\n\n if (now > this.centerPrintMsg.startTime + this.centerPrintMsg.duration) {\n this.centerPrintMsg = null;\n return;\n }\n\n // Draw centered text\n const width = this.centerPrintMsg.text.length * 8;\n // We ignore layout.CENTER_PRINT_X because drawCenterString calculates X automatically\n const y = layout.CENTER_PRINT_Y;\n\n renderer.drawCenterString(y, this.centerPrintMsg.text);\n }\n\n drawNotifications(renderer: Renderer, now: number) {\n // Remove expired messages\n while (this.notifyMessages.length > 0 && now > this.notifyMessages[0].startTime + this.notifyMessages[0].duration) {\n this.notifyMessages.shift();\n }\n\n let y = 10; // Start near top-left\n for (const msg of this.notifyMessages) {\n renderer.drawString(10, y, msg.text);\n y += 10; // Line height\n }\n }\n}\n","import { CGameImport } from '../types.js';\nimport { PlayerState } from '@quake2ts/shared';\n\nexport const Draw_Blends = (cgi: CGameImport, ps: PlayerState, width: number, height: number) => {\n if (!ps.blend) return;\n\n const [r, g, b, a] = ps.blend;\n\n if (a > 0) {\n // Use SCR_DrawColorPic with a white pixel/texture stretched?\n // Or cgi needs a fill rect function.\n // SCR_DrawPic usually takes a pic.\n // Rerelease might use a specific blend function or just a 1x1 white texture scaled up.\n // Assuming cgi can draw colored quads if we register a \"white\" texture or similar.\n // For now, let's assume we can draw a colored rectangle if we had a white texture.\n\n // Since we don't have a fillRect in CGameImport, we might need to add it or use a registered texture.\n // Let's assume for now we skip or need to register a 'pics/white.pcx' or similar if it existed.\n // A common trick is to use any opaque texture and tint it, but that shows the texture.\n\n // Let's defer this or add a \"DrawFill\" to CGameImport if strictly needed,\n // OR standard Q2 just uses the poly drawing which isn't exposed yet.\n // Actually, Q2 V_CalcBlend handles this by setting palette/gamma or drawing a full screen poly.\n\n // For this port, we likely want a SCR_DrawFill calls.\n // Let's add SCR_DrawFill to CGameImport in types.ts in next step if it's missing.\n // Checking types.ts... it has SCR_DrawChar, SCR_DrawPic... no DrawFill.\n\n // I'll leave it commented/TODO for now or assume a 'white' pic is available.\n // cgi.SCR_DrawColorPic(0, 0, whitePic, {x:r, y:g, z:b}, a);\n }\n};\n","import { CGameImport } from '../types.js';\nimport { PlayerState } from '@quake2ts/shared';\nimport { iconPics } from './icons.js'; // Reuse loaded icons\n\nexport const Draw_Pickup = (cgi: CGameImport, ps: PlayerState, width: number, height: number) => {\n if (!ps.pickupIcon) return;\n\n const icon = iconPics.get(ps.pickupIcon);\n if (icon) {\n const size = cgi.Draw_GetPicSize(icon);\n const x = width - size.width - 10;\n const y = height - size.height - 10;\n cgi.SCR_DrawPic(x, y, icon);\n }\n};\n","import { CGameImport } from '../types.js';\n\nexport const Draw_Number = (cgi: CGameImport, x: number, y: number, value: number, pics: readonly unknown[], width: number, color?: [number, number, number, number]) => {\n const s = Math.abs(value).toString();\n for (let i = 0; i < s.length; i++) {\n const digit = parseInt(s[i]);\n const pic = pics[digit];\n if (pic) {\n if (color) {\n // cgi expects color as Vec3 (x,y,z) and alpha separate.\n // Assuming color is [r, g, b, a]\n cgi.SCR_DrawColorPic(x + i * width, y, pic, { x: color[0], y: color[1], z: color[2] }, color[3]);\n } else {\n cgi.SCR_DrawPic(x + i * width, y, pic);\n }\n }\n }\n};\n","/**\n * Local type definitions for HUD components.\n * These types represent the minimal data structures needed for HUD rendering.\n * TODO: Refactor HUD to read from PlayerState.stats[] instead of inventory structure.\n */\n\nimport { WeaponId, PowerupId } from '@quake2ts/shared';\n\nexport interface ArmorState {\n armorType: string;\n armorCount: number;\n}\n\nexport interface InventoryState {\n armor: ArmorState | null;\n currentWeapon?: WeaponId;\n powerups: Map<PowerupId, number | null>;\n keys: Set<string>;\n}\n\nexport interface ClientState {\n inventory: InventoryState;\n}\n\n/**\n * Mapping from WeaponId to HUD icon names.\n * This avoids needing to import WEAPON_ITEMS from game package.\n */\nexport const WEAPON_ICON_MAP: Record<WeaponId, string> = {\n [WeaponId.Blaster]: 'w_blaster',\n [WeaponId.Shotgun]: 'w_shotgun',\n [WeaponId.SuperShotgun]: 'w_sshotgun',\n [WeaponId.Machinegun]: 'w_machinegun',\n [WeaponId.Chaingun]: 'w_chaingun',\n [WeaponId.GrenadeLauncher]: 'w_glauncher',\n [WeaponId.RocketLauncher]: 'w_rlauncher',\n [WeaponId.HyperBlaster]: 'w_hyperblaster',\n [WeaponId.Railgun]: 'w_railgun',\n [WeaponId.BFG10K]: 'w_bfg',\n [WeaponId.Grapple]: 'w_grapple',\n [WeaponId.ChainFist]: 'w_chainfist',\n [WeaponId.EtfRifle]: 'w_etf_rifle',\n [WeaponId.ProxLauncher]: 'w_prox_launcher',\n [WeaponId.IonRipper]: 'w_ionripper',\n [WeaponId.PlasmaBeam]: 'w_plasmabeam',\n [WeaponId.Phalanx]: 'w_phalanx',\n [WeaponId.Disruptor]: 'w_disruptor',\n};\n\n/**\n * Mapping from key names to HUD icon names.\n * Provides O(1) lookup for key icon rendering.\n */\nexport const KEY_ICON_MAP: Record<string, string> = {\n 'blue': 'k_bluekey',\n 'red': 'k_redkey',\n 'green': 'k_security',\n 'yellow': 'k_pyramid',\n};\n","import { CGameImport } from '../types.js';\nimport { Draw_Number } from './numbers.js';\nimport { iconPics } from './icons.js';\nimport { getHudLayout } from './layout.js';\nimport { ClientState, WEAPON_ICON_MAP, KEY_ICON_MAP } from './types.js';\n\nlet colorblindMode = false;\n\nexport const Set_ColorblindMode = (enabled: boolean) => {\n colorblindMode = enabled;\n};\n\nexport const Draw_StatusBar = (\n cgi: CGameImport,\n client: ClientState,\n health: number,\n armor: number,\n ammo: number,\n hudNumberPics: readonly unknown[],\n numberWidth: number,\n timeMs: number,\n layout: ReturnType<typeof getHudLayout>\n) => {\n // Draw Health\n let healthColor: [number, number, number, number] | undefined = undefined;\n\n if (health <= 25) {\n if (colorblindMode) {\n // Use Blue/Cyan for low health in colorblind mode instead of Red\n healthColor = [0.2, 0.6, 1, 1];\n } else {\n healthColor = [1, 0, 0, 1]; // Red for low health\n }\n }\n\n if (hudNumberPics.length > 0) {\n Draw_Number(cgi, layout.HEALTH_X, layout.HEALTH_Y, health, hudNumberPics, numberWidth, healthColor);\n Draw_Number(cgi, layout.ARMOR_X, layout.ARMOR_Y, armor, hudNumberPics, numberWidth);\n Draw_Number(cgi, layout.AMMO_X, layout.AMMO_Y, ammo, hudNumberPics, numberWidth);\n }\n\n // Draw Armor Icon\n const armorItem = client.inventory.armor;\n if (armorItem && armorItem.armorCount > 0) {\n const iconName = `i_${armorItem.armorType}armor`;\n const icon = iconPics.get(iconName);\n if (icon) {\n cgi.SCR_DrawPic(layout.ARMOR_X - 24, layout.ARMOR_Y - 2, icon);\n }\n }\n\n // Draw Weapon Icon\n const currentWeapon = client.inventory.currentWeapon;\n if (currentWeapon) {\n const iconName = WEAPON_ICON_MAP[currentWeapon];\n if (iconName) {\n const icon = iconPics.get(iconName);\n if (icon) {\n cgi.SCR_DrawPic(layout.WEAPON_ICON_X, layout.WEAPON_ICON_Y, icon);\n }\n }\n }\n\n // Draw Keys\n const keys = Array.from(client.inventory.keys).sort();\n let keyY = layout.WEAPON_ICON_Y - 150 * layout.scale; // Stack above weapon icon?\n\n for (const key of keys) {\n const iconName = KEY_ICON_MAP[key];\n if (iconName) {\n const icon = iconPics.get(iconName);\n if (icon) {\n const size = cgi.Draw_GetPicSize(icon);\n cgi.SCR_DrawPic(layout.WEAPON_ICON_X, keyY, icon);\n keyY += (size.height + 2);\n }\n }\n }\n\n // Draw Powerups\n let powerupX = layout.POWERUP_X;\n for (const [powerup, expiresAt] of Array.from(client.inventory.powerups.entries())) {\n if (expiresAt && expiresAt > timeMs) {\n const iconName = `p_${powerup}`;\n const icon = iconPics.get(iconName);\n if (icon) {\n const size = cgi.Draw_GetPicSize(icon);\n cgi.SCR_DrawPic(powerupX, layout.POWERUP_Y, icon);\n\n // Draw remaining time\n const remainingSeconds = Math.ceil((expiresAt - timeMs) / 1000);\n Draw_Number(cgi, powerupX + size.width + 2, layout.POWERUP_Y, remainingSeconds, hudNumberPics, numberWidth);\n\n powerupX -= (size.width + numberWidth * remainingSeconds.toString().length + 8);\n }\n }\n }\n};\n","// Dynamic Layout Scaling\nconst REFERENCE_WIDTH = 640;\nconst REFERENCE_HEIGHT = 480;\n\nexport const getHudLayout = (width: number, height: number) => {\n // Determine scale factor - usually based on height to preserve aspect ratio logic or just scale uniform\n // Quake 2 typically scales 2D elements.\n const scaleX = width / REFERENCE_WIDTH;\n const scaleY = height / REFERENCE_HEIGHT;\n const scale = Math.min(scaleX, scaleY); // Uniform scaling\n\n // Or we can just center the 640x480 rect?\n // Modern approach: Scale UI to fit, or anchor to edges.\n // Let's implement edge anchoring logic relative to 640x480 coordinates.\n\n // Original constants (approximate):\n // HEALTH_X: 100, HEALTH_Y: 450\n // ARMOR_X: 200, ARMOR_Y: 450\n // AMMO_X: 540, AMMO_Y: 450\n // CENTER_PRINT: Center screen\n // WEAPON_ICON: Bottom left\n // POWERUP: Bottom right?\n\n // We'll return scaled coordinates.\n // For bottom elements, we should anchor to bottom.\n\n return {\n // Status bar numbers - Anchored Bottom-Left / Center / Right\n HEALTH_X: 100 * scale,\n HEALTH_Y: height - (REFERENCE_HEIGHT - 450) * scale,\n\n ARMOR_X: 200 * scale,\n ARMOR_Y: height - (REFERENCE_HEIGHT - 450) * scale,\n\n AMMO_X: width - (REFERENCE_WIDTH - 540) * scale, // Anchor right? 540 is near right (640)\n AMMO_Y: height - (REFERENCE_HEIGHT - 450) * scale,\n\n // Center print messages - Center\n CENTER_PRINT_X: width / 2,\n CENTER_PRINT_Y: 100 * scale, // Top anchor\n\n // Weapon and powerup icons\n WEAPON_ICON_X: 10 * scale,\n WEAPON_ICON_Y: height - (REFERENCE_HEIGHT - 450) * scale,\n\n POWERUP_X: width - (REFERENCE_WIDTH - 610) * scale,\n POWERUP_Y: height - (REFERENCE_HEIGHT - 450) * scale,\n\n scale: scale\n };\n};\n\n// Backward compatibility (deprecated, but useful for initial refactor)\nexport const HUD_LAYOUT = {\n HEALTH_X: 100,\n HEALTH_Y: 450,\n ARMOR_X: 200,\n ARMOR_Y: 450,\n AMMO_X: 540,\n AMMO_Y: 450,\n CENTER_PRINT_X: 320,\n CENTER_PRINT_Y: 100,\n WEAPON_ICON_X: 10,\n WEAPON_ICON_Y: 450,\n POWERUP_X: 610,\n POWERUP_Y: 450,\n};\n","/**\n * CGame HUD Screen Drawing\n * Reference: rerelease/cg_screen.cpp\n *\n * This module handles all HUD rendering for the cgame package, including:\n * - Status bar (health, armor, ammo)\n * - Crosshair\n * - Damage indicators\n * - Pickup notifications\n * - Messages and center print\n * - Subtitles\n */\n\nimport type { PlayerState } from '@quake2ts/shared';\nimport type { CGameImport, ClientState } from './types.js';\n\n// HUD component imports\nimport { Draw_Crosshair, Init_Crosshair } from './hud/crosshair.js';\nimport { Init_Icons } from './hud/icons.js';\nimport { Draw_Damage, Init_Damage } from './hud/damage.js';\nimport { Draw_Diagnostics } from './hud/diagnostics.js';\nimport { MessageSystem } from './hud/messages.js';\nimport { SubtitleSystem } from './hud/subtitles.js';\nimport { Draw_Blends } from './hud/blends.js';\nimport { Draw_Pickup } from './hud/pickup.js';\nimport { Draw_StatusBar } from './hud/statusbar.js';\nimport { getHudLayout } from './hud/layout.js';\n\n// Module-level state\nlet cgi: CGameImport | null = null;\nconst hudNumberPics: unknown[] = []; // Will hold pic handles from cgi.Draw_RegisterPic()\nlet numberWidth = 0;\n\n// Message and subtitle systems\nconst messageSystem = new MessageSystem();\nconst subtitleSystem = new SubtitleSystem();\n\n/**\n * Initialize the CGame screen module with import functions.\n * Reference: rerelease/cg_screen.cpp InitCGame()\n */\nexport function CG_InitScreen(imports: CGameImport): void {\n cgi = imports;\n}\n\n/**\n * Precache all HUD images.\n * Reference: rerelease/cg_screen.cpp:1689 (TouchPics)\n *\n * This is called during level load to register all required HUD assets.\n */\nexport function CG_TouchPics(): void {\n if (!cgi) return;\n\n // Load HUD number pics\n hudNumberPics.length = 0;\n for (let i = 0; i < 10; i++) {\n try {\n const pic = cgi.Draw_RegisterPic(`pics/hud/num_${i}.pcx`);\n hudNumberPics.push(pic);\n if (i === 0) {\n const size = cgi.Draw_GetPicSize(pic);\n numberWidth = size.width;\n }\n } catch (e) {\n cgi.Com_Print(`Warning: Failed to load HUD image: pics/hud/num_${i}.pcx\\n`);\n }\n }\n\n Init_Crosshair(cgi);\n Init_Icons(cgi);\n Init_Damage(cgi);\n}\n\n/**\n * Main HUD drawing function.\n * Reference: rerelease/cg_screen.cpp CG_DrawHUD()\n *\n * Called each frame by the client to render the HUD overlay.\n *\n * @param isplit - Split-screen index (0 for single player)\n * @param data - Additional HUD data (unused in initial implementation)\n * @param hud_vrect - Virtual HUD rectangle (screen coordinates)\n * @param hud_safe - Safe area rectangle (for overscan)\n * @param scale - HUD scale factor\n * @param playernum - Player number\n * @param ps - Current player state\n */\nexport function CG_DrawHUD(\n isplit: number,\n data: unknown,\n hud_vrect: { x: number; y: number; width: number; height: number },\n hud_safe: { x: number; y: number; width: number; height: number },\n scale: number,\n playernum: number,\n ps: PlayerState\n): void {\n if (!cgi) {\n console.error('CG_DrawHUD: cgame imports not initialized');\n return;\n }\n\n const timeMs = cgi.CL_ClientTime();\n const layout = getHudLayout(hud_vrect.width, hud_vrect.height);\n\n // Screen blends (damage, powerups)\n Draw_Blends(cgi, ps, hud_vrect.width, hud_vrect.height);\n\n // Status bar\n // Temporary mapping until ps.stats is implemented\n // This ClientState construction is temporary to support the current Draw_StatusBar signature\n // Eventually Draw_StatusBar should just take `ps` and read stats directly.\n const tempClientState: ClientState = {\n inventory: {\n armor: null, // TODO: Map from ps.stats\n powerups: new Map(), // TODO: Map from ps.stats\n keys: new Set(), // TODO: Map from ps.stats\n currentWeapon: undefined // TODO: Map from ps.stats/gunindex\n }\n };\n\n // TODO: Pass real health/armor/ammo from ps.stats\n Draw_StatusBar(cgi, tempClientState, 100, 0, 0, hudNumberPics, numberWidth, timeMs, layout);\n\n // Pickup messages\n Draw_Pickup(cgi, ps, hud_vrect.width, hud_vrect.height);\n\n // Damage indicators\n Draw_Damage(cgi, ps, hud_vrect.width, hud_vrect.height);\n\n // Center print\n if (ps.centerPrint) {\n const lines = ps.centerPrint.split('\\n');\n let y = hud_vrect.height / 2 - (lines.length * 10); // Approximation\n for (const line of lines) {\n cgi.SCR_DrawCenterString(y, line);\n y += 16;\n }\n }\n\n // Notifications (Chat/Messages)\n // messageSystem.draw(cgi, ...); // TODO: MessageSystem needs refactoring to use cgi\n\n // Subtitles\n // subtitleSystem.draw(cgi, ...); // TODO: SubtitleSystem needs refactoring to use cgi\n\n // Crosshair\n Draw_Crosshair(cgi, hud_vrect.width, hud_vrect.height);\n}\n\n/**\n * Get message system instance.\n * Used by parsing functions to add messages.\n */\nexport function CG_GetMessageSystem(): MessageSystem {\n return messageSystem;\n}\n\n/**\n * Get subtitle system instance.\n * Used by audio system to display subtitles.\n */\nexport function CG_GetSubtitleSystem(): SubtitleSystem {\n return subtitleSystem;\n}\n","/**\n * CGame Module Entry Point\n * Reference: rerelease/cg_main.cpp\n *\n * This module provides the GetCGameAPI() function that returns the cgame_export_t\n * interface to the client engine.\n */\n\nimport type { CGameImport, CGameExport } from './types.js';\nimport type { PlayerState, Vec3 } from '@quake2ts/shared';\nimport { LayoutFlags } from '@quake2ts/shared';\nimport { CG_InitScreen, CG_TouchPics, CG_DrawHUD, CG_GetMessageSystem, CG_GetSubtitleSystem } from './screen.js';\n\n// Module-level state\nlet cgi: CGameImport | null = null;\n\n/**\n * Initialize the CGame module.\n * Reference: rerelease/cg_main.cpp InitCGame()\n */\nfunction Init(): void {\n if (!cgi) {\n console.error('CGame Init: cgame imports not set');\n return;\n }\n\n cgi.Com_Print('===== CGame Initialization =====\\n');\n\n // Initialize screen/HUD module\n CG_InitScreen(cgi);\n\n cgi.Com_Print('CGame initialized\\n');\n}\n\n/**\n * Shutdown the CGame module.\n * Reference: rerelease/cg_main.cpp ShutdownCGame()\n */\nfunction Shutdown(): void {\n if (cgi) {\n cgi.Com_Print('CGame shutdown\\n');\n }\n cgi = null;\n}\n\n/**\n * Main HUD drawing function (wrapper for CG_DrawHUD).\n * Reference: rerelease/cg_screen.cpp CG_DrawHUD()\n */\nfunction DrawHUD(\n isplit: number,\n data: unknown,\n hud_vrect: { x: number; y: number; width: number; height: number },\n hud_safe: { x: number; y: number; width: number; height: number },\n scale: number,\n playernum: number,\n ps: PlayerState\n): void {\n CG_DrawHUD(isplit, data, hud_vrect, hud_safe, scale, playernum, ps);\n}\n\n/**\n * Precache all HUD images.\n * Reference: rerelease/cg_screen.cpp TouchPics()\n */\nfunction TouchPics(): void {\n CG_TouchPics();\n}\n\n/**\n * Get layout flags for current player state.\n * Reference: rerelease/cg_screen.cpp\n */\nfunction GetLayoutFlags(ps: PlayerState): LayoutFlags {\n // TODO: Implement proper layout flag calculation\n // Based on inventory state, help state, intermission, etc.\n return 0 as LayoutFlags; // No flags set by default\n}\n\n/**\n * Placeholder stubs for remaining CGameExport functions.\n * These will be implemented as needed.\n */\n\nfunction GetActiveWeaponWheelWeapon(ps: PlayerState): number {\n return 0;\n}\n\nfunction GetOwnedWeaponWheelWeapons(ps: PlayerState): number[] {\n return [];\n}\n\nfunction GetWeaponWheelAmmoCount(ps: PlayerState, weapon: number): number {\n return 0;\n}\n\nfunction GetPowerupWheelCount(ps: PlayerState): number {\n return 0;\n}\n\nfunction GetHitMarkerDamage(ps: PlayerState): number {\n return 0;\n}\n\nfunction Pmove(pmove: unknown): void {\n // TODO: Implement client-side movement prediction\n // Should call shared Pmove() function\n}\n\nfunction ParseConfigString(i: number, s: string): void {\n // TODO: Implement config string parsing\n // Handle CONFIG_N64_PHYSICS, CS_AIRACCEL, etc.\n}\n\nfunction ParseCenterPrint(str: string, isplit: number, instant: boolean): void {\n if (!cgi) return;\n const messageSystem = CG_GetMessageSystem();\n // TODO: Parse layout strings and handle key bindings\n messageSystem.setCenterPrint(str, cgi.CL_ClientTime());\n}\n\nfunction NotifyMessage(isplit: number, msg: string, is_chat: boolean): void {\n if (!cgi) return;\n const messageSystem = CG_GetMessageSystem();\n messageSystem.addNotification(msg, is_chat, cgi.CL_ClientTime());\n}\n\nfunction ClearNotify(isplit: number): void {\n const messageSystem = CG_GetMessageSystem();\n messageSystem.clearNotifications();\n}\n\nfunction ClearCenterprint(isplit: number): void {\n const messageSystem = CG_GetMessageSystem();\n messageSystem.clearCenterPrint();\n}\n\nfunction GetMonsterFlashOffset(id: number): Vec3 {\n return { x: 0, y: 0, z: 0 };\n}\n\nfunction GetExtension(name: string): unknown {\n return null;\n}\n\n/**\n * Main entry point for CGame module.\n * Reference: rerelease/cg_main.cpp GetCGameAPI()\n *\n * @param imports - Functions provided by the client engine\n * @returns CGame export interface\n */\nexport function GetCGameAPI(imports: CGameImport): CGameExport {\n cgi = imports;\n\n return {\n // Lifecycle\n Init,\n Shutdown,\n\n // Rendering\n DrawHUD,\n TouchPics,\n\n // Layout\n LayoutFlags: GetLayoutFlags,\n\n // Weapon wheel\n GetActiveWeaponWheelWeapon,\n GetOwnedWeaponWheelWeapons,\n GetWeaponWheelAmmoCount,\n GetPowerupWheelCount,\n\n // Hit markers\n GetHitMarkerDamage,\n\n // Prediction\n Pmove,\n\n // Parsing\n ParseConfigString,\n ParseCenterPrint,\n NotifyMessage,\n\n // State management\n ClearNotify,\n ClearCenterprint,\n\n // Effects\n GetMonsterFlashOffset,\n\n // Extension\n GetExtension,\n };\n}\n"]}
@@ -35,6 +35,7 @@ interface CGameImport {
35
35
  SCR_DrawPic(x: number, y: number, pic: unknown): void;
36
36
  SCR_DrawColorPic(x: number, y: number, pic: unknown, color: Vec3, alpha: number): void;
37
37
  SCR_DrawFontString(x: number, y: number, str: string): void;
38
+ SCR_DrawCenterString(y: number, str: string): void;
38
39
  SCR_MeasureFontString(str: string): number;
39
40
  SCR_FontLineHeight(): number;
40
41
  SCR_SetAltTypeface(alt: boolean): void;
@@ -35,6 +35,7 @@ interface CGameImport {
35
35
  SCR_DrawPic(x: number, y: number, pic: unknown): void;
36
36
  SCR_DrawColorPic(x: number, y: number, pic: unknown, color: Vec3, alpha: number): void;
37
37
  SCR_DrawFontString(x: number, y: number, str: string): void;
38
+ SCR_DrawCenterString(y: number, str: string): void;
38
39
  SCR_MeasureFontString(str: string): number;
39
40
  SCR_FontLineHeight(): number;
40
41
  SCR_SetAltTypeface(alt: boolean): void;
@@ -1,3 +1,118 @@
1
+ import { WeaponId } from '@quake2ts/shared';
2
+
3
+ // src/hud/crosshair.ts
4
+ var crosshairPic = null;
5
+ var crosshairColor = [1, 1, 1, 1];
6
+ var CROSSHAIR_NAMES = ["ch1", "ch2", "ch3"];
7
+ var crosshairPics = [null, null, null];
8
+ var Init_Crosshair = (cgi3) => {
9
+ for (let i = 0; i < CROSSHAIR_NAMES.length; i++) {
10
+ const name = CROSSHAIR_NAMES[i];
11
+ try {
12
+ crosshairPics[i] = cgi3.Draw_RegisterPic(`pics/${name}.pcx`);
13
+ } catch (e) {
14
+ if (i === 0) {
15
+ try {
16
+ crosshairPics[i] = cgi3.Draw_RegisterPic("pics/crosshair.pcx");
17
+ } catch (e2) {
18
+ cgi3.Com_Print("Failed to load crosshair image\n");
19
+ }
20
+ }
21
+ }
22
+ }
23
+ crosshairPic = crosshairPics[0];
24
+ };
25
+ var Draw_Crosshair = (cgi3, width, height) => {
26
+ if (crosshairPic) {
27
+ const size = cgi3.Draw_GetPicSize(crosshairPic);
28
+ const x = (width - size.width) / 2;
29
+ const y = (height - size.height) / 2;
30
+ cgi3.SCR_DrawColorPic(x, y, crosshairPic, { x: crosshairColor[0], y: crosshairColor[1], z: crosshairColor[2] }, crosshairColor[3]);
31
+ }
32
+ };
33
+
34
+ // src/hud/icons.ts
35
+ var iconPics = /* @__PURE__ */ new Map();
36
+ var ICON_NAMES = [
37
+ // Weapons
38
+ "w_blaster",
39
+ "w_shotgun",
40
+ "w_sshotgun",
41
+ "w_machinegun",
42
+ "w_chaingun",
43
+ "w_glauncher",
44
+ "w_rlauncher",
45
+ "w_hyperblaster",
46
+ "w_railgun",
47
+ "w_bfg",
48
+ "w_grapple",
49
+ // Ammo
50
+ "a_grenades",
51
+ "a_bullets",
52
+ "a_cells",
53
+ "a_rockets",
54
+ "a_shells",
55
+ "a_slugs",
56
+ // Powerups
57
+ "p_quad",
58
+ "p_invulnerability",
59
+ "p_silencer",
60
+ "p_rebreather",
61
+ "p_envirosuit",
62
+ "p_adrenaline",
63
+ "p_megahealth",
64
+ // Armor
65
+ "i_jacketarmor",
66
+ "i_combatarmor",
67
+ "i_bodyarmor",
68
+ "i_powerscreen",
69
+ "i_powershield",
70
+ // Keys
71
+ "k_datacd",
72
+ "k_powercube",
73
+ "k_pyramid",
74
+ "k_dataspin",
75
+ "k_security",
76
+ "k_bluekey",
77
+ "k_redkey"
78
+ ];
79
+ var Init_Icons = (cgi3) => {
80
+ for (const name of ICON_NAMES) {
81
+ try {
82
+ const pic = cgi3.Draw_RegisterPic(`pics/${name}.pcx`);
83
+ iconPics.set(name, pic);
84
+ } catch (e) {
85
+ cgi3.Com_Print(`Failed to load HUD image: pics/${name}.pcx
86
+ `);
87
+ }
88
+ }
89
+ };
90
+
91
+ // src/hud/damage.ts
92
+ var damagePics = /* @__PURE__ */ new Map();
93
+ var DAMAGE_INDICATOR_NAMES = [
94
+ "d_left",
95
+ "d_right",
96
+ "d_up",
97
+ "d_down"
98
+ ];
99
+ var Init_Damage = (cgi3) => {
100
+ for (const name of DAMAGE_INDICATOR_NAMES) {
101
+ try {
102
+ const pic = cgi3.Draw_RegisterPic(`pics/${name}.pcx`);
103
+ damagePics.set(name, pic);
104
+ } catch (e) {
105
+ cgi3.Com_Print(`Failed to load HUD image: pics/${name}.pcx
106
+ `);
107
+ }
108
+ }
109
+ };
110
+ var Draw_Damage = (cgi3, ps, width, height) => {
111
+ if (!ps.damage_yaw && !ps.damage_pitch && !ps.damage_alpha) {
112
+ return;
113
+ }
114
+ };
115
+
1
116
  // src/hud/messages.ts
2
117
  var CENTER_PRINT_DURATION = 3e3;
3
118
  var NOTIFY_DURATION = 5e3;
@@ -70,6 +185,116 @@ var MessageSystem = class {
70
185
  }
71
186
  };
72
187
 
188
+ // src/hud/blends.ts
189
+ var Draw_Blends = (cgi3, ps, width, height) => {
190
+ if (!ps.blend) return;
191
+ const [r, g, b, a] = ps.blend;
192
+ };
193
+
194
+ // src/hud/pickup.ts
195
+ var Draw_Pickup = (cgi3, ps, width, height) => {
196
+ if (!ps.pickupIcon) return;
197
+ const icon = iconPics.get(ps.pickupIcon);
198
+ if (icon) {
199
+ const size = cgi3.Draw_GetPicSize(icon);
200
+ const x = width - size.width - 10;
201
+ const y = height - size.height - 10;
202
+ cgi3.SCR_DrawPic(x, y, icon);
203
+ }
204
+ };
205
+
206
+ // src/hud/numbers.ts
207
+ var Draw_Number = (cgi3, x, y, value, pics, width, color) => {
208
+ const s = Math.abs(value).toString();
209
+ for (let i = 0; i < s.length; i++) {
210
+ const digit = parseInt(s[i]);
211
+ const pic = pics[digit];
212
+ if (pic) {
213
+ {
214
+ cgi3.SCR_DrawPic(x + i * width, y, pic);
215
+ }
216
+ }
217
+ }
218
+ };
219
+ var WEAPON_ICON_MAP = {
220
+ [WeaponId.Blaster]: "w_blaster",
221
+ [WeaponId.Shotgun]: "w_shotgun",
222
+ [WeaponId.SuperShotgun]: "w_sshotgun",
223
+ [WeaponId.Machinegun]: "w_machinegun",
224
+ [WeaponId.Chaingun]: "w_chaingun",
225
+ [WeaponId.GrenadeLauncher]: "w_glauncher",
226
+ [WeaponId.RocketLauncher]: "w_rlauncher",
227
+ [WeaponId.HyperBlaster]: "w_hyperblaster",
228
+ [WeaponId.Railgun]: "w_railgun",
229
+ [WeaponId.BFG10K]: "w_bfg",
230
+ [WeaponId.Grapple]: "w_grapple",
231
+ [WeaponId.ChainFist]: "w_chainfist",
232
+ [WeaponId.EtfRifle]: "w_etf_rifle",
233
+ [WeaponId.ProxLauncher]: "w_prox_launcher",
234
+ [WeaponId.IonRipper]: "w_ionripper",
235
+ [WeaponId.PlasmaBeam]: "w_plasmabeam",
236
+ [WeaponId.Phalanx]: "w_phalanx",
237
+ [WeaponId.Disruptor]: "w_disruptor"
238
+ };
239
+ var KEY_ICON_MAP = {
240
+ "blue": "k_bluekey",
241
+ "red": "k_redkey",
242
+ "green": "k_security",
243
+ "yellow": "k_pyramid"
244
+ };
245
+ var Draw_StatusBar = (cgi3, client, health, armor, ammo, hudNumberPics2, numberWidth2, timeMs, layout) => {
246
+ if (hudNumberPics2.length > 0) {
247
+ Draw_Number(cgi3, layout.HEALTH_X, layout.HEALTH_Y, health, hudNumberPics2, numberWidth2);
248
+ Draw_Number(cgi3, layout.ARMOR_X, layout.ARMOR_Y, armor, hudNumberPics2, numberWidth2);
249
+ Draw_Number(cgi3, layout.AMMO_X, layout.AMMO_Y, ammo, hudNumberPics2, numberWidth2);
250
+ }
251
+ const armorItem = client.inventory.armor;
252
+ if (armorItem && armorItem.armorCount > 0) {
253
+ const iconName = `i_${armorItem.armorType}armor`;
254
+ const icon = iconPics.get(iconName);
255
+ if (icon) {
256
+ cgi3.SCR_DrawPic(layout.ARMOR_X - 24, layout.ARMOR_Y - 2, icon);
257
+ }
258
+ }
259
+ const currentWeapon = client.inventory.currentWeapon;
260
+ if (currentWeapon) {
261
+ const iconName = WEAPON_ICON_MAP[currentWeapon];
262
+ if (iconName) {
263
+ const icon = iconPics.get(iconName);
264
+ if (icon) {
265
+ cgi3.SCR_DrawPic(layout.WEAPON_ICON_X, layout.WEAPON_ICON_Y, icon);
266
+ }
267
+ }
268
+ }
269
+ const keys = Array.from(client.inventory.keys).sort();
270
+ let keyY = layout.WEAPON_ICON_Y - 150 * layout.scale;
271
+ for (const key of keys) {
272
+ const iconName = KEY_ICON_MAP[key];
273
+ if (iconName) {
274
+ const icon = iconPics.get(iconName);
275
+ if (icon) {
276
+ const size = cgi3.Draw_GetPicSize(icon);
277
+ cgi3.SCR_DrawPic(layout.WEAPON_ICON_X, keyY, icon);
278
+ keyY += size.height + 2;
279
+ }
280
+ }
281
+ }
282
+ let powerupX = layout.POWERUP_X;
283
+ for (const [powerup, expiresAt] of Array.from(client.inventory.powerups.entries())) {
284
+ if (expiresAt && expiresAt > timeMs) {
285
+ const iconName = `p_${powerup}`;
286
+ const icon = iconPics.get(iconName);
287
+ if (icon) {
288
+ const size = cgi3.Draw_GetPicSize(icon);
289
+ cgi3.SCR_DrawPic(powerupX, layout.POWERUP_Y, icon);
290
+ const remainingSeconds = Math.ceil((expiresAt - timeMs) / 1e3);
291
+ Draw_Number(cgi3, powerupX + size.width + 2, layout.POWERUP_Y, remainingSeconds, hudNumberPics2, numberWidth2);
292
+ powerupX -= size.width + numberWidth2 * remainingSeconds.toString().length + 8;
293
+ }
294
+ }
295
+ }
296
+ };
297
+
73
298
  // src/hud/layout.ts
74
299
  var REFERENCE_WIDTH = 640;
75
300
  var REFERENCE_HEIGHT = 480;
@@ -123,24 +348,42 @@ function CG_TouchPics() {
123
348
  `);
124
349
  }
125
350
  }
351
+ Init_Crosshair(cgi);
352
+ Init_Icons(cgi);
353
+ Init_Damage(cgi);
126
354
  }
127
355
  function CG_DrawHUD(isplit, data, hud_vrect, hud_safe, scale, playernum, ps) {
128
356
  if (!cgi) {
129
357
  console.error("CG_DrawHUD: cgame imports not initialized");
130
358
  return;
131
359
  }
132
- cgi.CL_ClientTime();
133
- getHudLayout(hud_vrect.width, hud_vrect.height);
360
+ const timeMs = cgi.CL_ClientTime();
361
+ const layout = getHudLayout(hud_vrect.width, hud_vrect.height);
362
+ Draw_Blends(cgi, ps, hud_vrect.width, hud_vrect.height);
363
+ const tempClientState = {
364
+ inventory: {
365
+ armor: null,
366
+ // TODO: Map from ps.stats
367
+ powerups: /* @__PURE__ */ new Map(),
368
+ // TODO: Map from ps.stats
369
+ keys: /* @__PURE__ */ new Set(),
370
+ // TODO: Map from ps.stats
371
+ currentWeapon: void 0
372
+ // TODO: Map from ps.stats/gunindex
373
+ }
374
+ };
375
+ Draw_StatusBar(cgi, tempClientState, 100, 0, 0, hudNumberPics, numberWidth, timeMs, layout);
376
+ Draw_Pickup(cgi, ps, hud_vrect.width, hud_vrect.height);
377
+ Draw_Damage(cgi, ps, hud_vrect.width, hud_vrect.height);
134
378
  if (ps.centerPrint) {
135
- cgi.SCR_DrawFontString(
136
- hud_vrect.width / 2,
137
- hud_vrect.height / 2 - 20,
138
- ps.centerPrint
139
- );
140
- }
141
- if (ps.notify) {
142
- cgi.SCR_DrawFontString(8, 8, ps.notify);
379
+ const lines = ps.centerPrint.split("\n");
380
+ let y = hud_vrect.height / 2 - lines.length * 10;
381
+ for (const line of lines) {
382
+ cgi.SCR_DrawCenterString(y, line);
383
+ y += 16;
384
+ }
143
385
  }
386
+ Draw_Crosshair(cgi, hud_vrect.width, hud_vrect.height);
144
387
  }
145
388
  function CG_GetMessageSystem() {
146
389
  return messageSystem;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hud/messages.ts","../src/hud/layout.ts","../src/screen.ts","../src/index.ts"],"names":["cgi","messageSystem"],"mappings":"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","file":"index.js","sourcesContent":["import { Renderer } from '@quake2ts/engine';\nimport { getHudLayout } from './layout.js';\n\ninterface Message {\n text: string;\n startTime: number;\n duration: number;\n}\n\nconst CENTER_PRINT_DURATION = 3000;\nconst NOTIFY_DURATION = 5000;\nconst MAX_NOTIFY_MESSAGES = 4;\n\nexport class MessageSystem {\n private centerPrintMsg: Message | null = null;\n private notifyMessages: Message[] = [];\n\n addCenterPrint(text: string, now: number) {\n this.centerPrintMsg = {\n text,\n startTime: now,\n duration: CENTER_PRINT_DURATION,\n };\n }\n\n addNotify(text: string, now: number) {\n this.notifyMessages.push({\n text,\n startTime: now,\n duration: NOTIFY_DURATION,\n });\n\n if (this.notifyMessages.length > MAX_NOTIFY_MESSAGES) {\n this.notifyMessages.shift();\n }\n }\n\n // Additional methods for cgame API\n setCenterPrint(text: string, now: number) {\n this.centerPrintMsg = {\n text,\n startTime: now,\n duration: CENTER_PRINT_DURATION,\n };\n }\n\n addNotification(text: string, is_chat: boolean, now: number) {\n this.notifyMessages.push({\n text,\n startTime: now,\n duration: NOTIFY_DURATION,\n });\n\n if (this.notifyMessages.length > MAX_NOTIFY_MESSAGES) {\n this.notifyMessages.shift();\n }\n }\n\n clearNotifications() {\n this.notifyMessages = [];\n }\n\n clearCenterPrint() {\n this.centerPrintMsg = null;\n }\n\n drawCenterPrint(renderer: Renderer, now: number, layout: ReturnType<typeof getHudLayout>) {\n if (!this.centerPrintMsg) return;\n\n if (now > this.centerPrintMsg.startTime + this.centerPrintMsg.duration) {\n this.centerPrintMsg = null;\n return;\n }\n\n // Draw centered text\n const width = this.centerPrintMsg.text.length * 8;\n // We ignore layout.CENTER_PRINT_X because drawCenterString calculates X automatically\n const y = layout.CENTER_PRINT_Y;\n\n renderer.drawCenterString(y, this.centerPrintMsg.text);\n }\n\n drawNotifications(renderer: Renderer, now: number) {\n // Remove expired messages\n while (this.notifyMessages.length > 0 && now > this.notifyMessages[0].startTime + this.notifyMessages[0].duration) {\n this.notifyMessages.shift();\n }\n\n let y = 10; // Start near top-left\n for (const msg of this.notifyMessages) {\n renderer.drawString(10, y, msg.text);\n y += 10; // Line height\n }\n }\n}\n","// Dynamic Layout Scaling\nconst REFERENCE_WIDTH = 640;\nconst REFERENCE_HEIGHT = 480;\n\nexport const getHudLayout = (width: number, height: number) => {\n // Determine scale factor - usually based on height to preserve aspect ratio logic or just scale uniform\n // Quake 2 typically scales 2D elements.\n const scaleX = width / REFERENCE_WIDTH;\n const scaleY = height / REFERENCE_HEIGHT;\n const scale = Math.min(scaleX, scaleY); // Uniform scaling\n\n // Or we can just center the 640x480 rect?\n // Modern approach: Scale UI to fit, or anchor to edges.\n // Let's implement edge anchoring logic relative to 640x480 coordinates.\n\n // Original constants (approximate):\n // HEALTH_X: 100, HEALTH_Y: 450\n // ARMOR_X: 200, ARMOR_Y: 450\n // AMMO_X: 540, AMMO_Y: 450\n // CENTER_PRINT: Center screen\n // WEAPON_ICON: Bottom left\n // POWERUP: Bottom right?\n\n // We'll return scaled coordinates.\n // For bottom elements, we should anchor to bottom.\n\n return {\n // Status bar numbers - Anchored Bottom-Left / Center / Right\n HEALTH_X: 100 * scale,\n HEALTH_Y: height - (REFERENCE_HEIGHT - 450) * scale,\n\n ARMOR_X: 200 * scale,\n ARMOR_Y: height - (REFERENCE_HEIGHT - 450) * scale,\n\n AMMO_X: width - (REFERENCE_WIDTH - 540) * scale, // Anchor right? 540 is near right (640)\n AMMO_Y: height - (REFERENCE_HEIGHT - 450) * scale,\n\n // Center print messages - Center\n CENTER_PRINT_X: width / 2,\n CENTER_PRINT_Y: 100 * scale, // Top anchor\n\n // Weapon and powerup icons\n WEAPON_ICON_X: 10 * scale,\n WEAPON_ICON_Y: height - (REFERENCE_HEIGHT - 450) * scale,\n\n POWERUP_X: width - (REFERENCE_WIDTH - 610) * scale,\n POWERUP_Y: height - (REFERENCE_HEIGHT - 450) * scale,\n\n scale: scale\n };\n};\n\n// Backward compatibility (deprecated, but useful for initial refactor)\nexport const HUD_LAYOUT = {\n HEALTH_X: 100,\n HEALTH_Y: 450,\n ARMOR_X: 200,\n ARMOR_Y: 450,\n AMMO_X: 540,\n AMMO_Y: 450,\n CENTER_PRINT_X: 320,\n CENTER_PRINT_Y: 100,\n WEAPON_ICON_X: 10,\n WEAPON_ICON_Y: 450,\n POWERUP_X: 610,\n POWERUP_Y: 450,\n};\n","/**\n * CGame HUD Screen Drawing\n * Reference: rerelease/cg_screen.cpp\n *\n * This module handles all HUD rendering for the cgame package, including:\n * - Status bar (health, armor, ammo)\n * - Crosshair\n * - Damage indicators\n * - Pickup notifications\n * - Messages and center print\n * - Subtitles\n */\n\nimport type { PlayerState } from '@quake2ts/shared';\nimport type { CGameImport } from './types.js';\n\n// HUD component imports\nimport { Draw_Crosshair, Init_Crosshair } from './hud/crosshair.js';\nimport { Init_Icons } from './hud/icons.js';\nimport { Draw_Damage, Init_Damage } from './hud/damage.js';\nimport { Draw_Diagnostics } from './hud/diagnostics.js';\nimport { MessageSystem } from './hud/messages.js';\nimport { SubtitleSystem } from './hud/subtitles.js';\nimport { Draw_Blends } from './hud/blends.js';\nimport { Draw_Pickup } from './hud/pickup.js';\nimport { Draw_StatusBar } from './hud/statusbar.js';\nimport { getHudLayout } from './hud/layout.js';\n\n// Module-level state\nlet cgi: CGameImport | null = null;\nconst hudNumberPics: unknown[] = []; // Will hold pic handles from cgi.Draw_RegisterPic()\nlet numberWidth = 0;\n\n// Message and subtitle systems\nconst messageSystem = new MessageSystem();\nconst subtitleSystem = new SubtitleSystem();\n\n/**\n * Initialize the CGame screen module with import functions.\n * Reference: rerelease/cg_screen.cpp InitCGame()\n */\nexport function CG_InitScreen(imports: CGameImport): void {\n cgi = imports;\n}\n\n/**\n * Precache all HUD images.\n * Reference: rerelease/cg_screen.cpp:1689 (TouchPics)\n *\n * This is called during level load to register all required HUD assets.\n */\nexport function CG_TouchPics(): void {\n if (!cgi) return;\n\n // Load HUD number pics\n hudNumberPics.length = 0;\n for (let i = 0; i < 10; i++) {\n try {\n const pic = cgi.Draw_RegisterPic(`pics/hud/num_${i}.pcx`);\n hudNumberPics.push(pic);\n if (i === 0) {\n const size = cgi.Draw_GetPicSize(pic);\n numberWidth = size.width;\n }\n } catch (e) {\n cgi.Com_Print(`Warning: Failed to load HUD image: pics/hud/num_${i}.pcx\\n`);\n }\n }\n\n // TODO: Call Init functions for other HUD components\n // These will need to be adapted to use cgi.Draw_RegisterPic()\n // instead of direct asset manager access\n}\n\n/**\n * Main HUD drawing function.\n * Reference: rerelease/cg_screen.cpp CG_DrawHUD()\n *\n * Called each frame by the client to render the HUD overlay.\n *\n * @param isplit - Split-screen index (0 for single player)\n * @param data - Additional HUD data (unused in initial implementation)\n * @param hud_vrect - Virtual HUD rectangle (screen coordinates)\n * @param hud_safe - Safe area rectangle (for overscan)\n * @param scale - HUD scale factor\n * @param playernum - Player number\n * @param ps - Current player state\n */\nexport function CG_DrawHUD(\n isplit: number,\n data: unknown,\n hud_vrect: { x: number; y: number; width: number; height: number },\n hud_safe: { x: number; y: number; width: number; height: number },\n scale: number,\n playernum: number,\n ps: PlayerState\n): void {\n if (!cgi) {\n console.error('CG_DrawHUD: cgame imports not initialized');\n return;\n }\n\n // TODO: Implement full HUD rendering using cgi drawing functions\n // For now, this is a placeholder structure\n\n // The full implementation will need to:\n // 1. Read stats from ps.stats[] array using STAT_* constants\n // 2. Use cgi.SCR_DrawPic(), cgi.SCR_DrawChar(), etc. for rendering\n // 3. Calculate layout based on hud_vrect and hud_safe\n // 4. Draw all HUD components in correct order:\n // - Screen blends (damage, powerups)\n // - Status bar\n // - Pickup messages\n // - Damage indicators\n // - Center print\n // - Notifications\n // - Subtitles\n // - Crosshair\n\n const timeMs = cgi.CL_ClientTime();\n const layout = getHudLayout(hud_vrect.width, hud_vrect.height);\n\n // Basic placeholder rendering\n // TODO: Adapt each Draw_* function to use cgi instead of renderer\n\n if (ps.centerPrint) {\n cgi.SCR_DrawFontString(\n hud_vrect.width / 2,\n hud_vrect.height / 2 - 20,\n ps.centerPrint\n );\n }\n\n if (ps.notify) {\n cgi.SCR_DrawFontString(8, 8, ps.notify);\n }\n}\n\n/**\n * Get message system instance.\n * Used by parsing functions to add messages.\n */\nexport function CG_GetMessageSystem(): MessageSystem {\n return messageSystem;\n}\n\n/**\n * Get subtitle system instance.\n * Used by audio system to display subtitles.\n */\nexport function CG_GetSubtitleSystem(): SubtitleSystem {\n return subtitleSystem;\n}\n","/**\n * CGame Module Entry Point\n * Reference: rerelease/cg_main.cpp\n *\n * This module provides the GetCGameAPI() function that returns the cgame_export_t\n * interface to the client engine.\n */\n\nimport type { CGameImport, CGameExport } from './types.js';\nimport type { PlayerState, Vec3 } from '@quake2ts/shared';\nimport { LayoutFlags } from '@quake2ts/shared';\nimport { CG_InitScreen, CG_TouchPics, CG_DrawHUD, CG_GetMessageSystem, CG_GetSubtitleSystem } from './screen.js';\n\n// Module-level state\nlet cgi: CGameImport | null = null;\n\n/**\n * Initialize the CGame module.\n * Reference: rerelease/cg_main.cpp InitCGame()\n */\nfunction Init(): void {\n if (!cgi) {\n console.error('CGame Init: cgame imports not set');\n return;\n }\n\n cgi.Com_Print('===== CGame Initialization =====\\n');\n\n // Initialize screen/HUD module\n CG_InitScreen(cgi);\n\n cgi.Com_Print('CGame initialized\\n');\n}\n\n/**\n * Shutdown the CGame module.\n * Reference: rerelease/cg_main.cpp ShutdownCGame()\n */\nfunction Shutdown(): void {\n if (cgi) {\n cgi.Com_Print('CGame shutdown\\n');\n }\n cgi = null;\n}\n\n/**\n * Main HUD drawing function (wrapper for CG_DrawHUD).\n * Reference: rerelease/cg_screen.cpp CG_DrawHUD()\n */\nfunction DrawHUD(\n isplit: number,\n data: unknown,\n hud_vrect: { x: number; y: number; width: number; height: number },\n hud_safe: { x: number; y: number; width: number; height: number },\n scale: number,\n playernum: number,\n ps: PlayerState\n): void {\n CG_DrawHUD(isplit, data, hud_vrect, hud_safe, scale, playernum, ps);\n}\n\n/**\n * Precache all HUD images.\n * Reference: rerelease/cg_screen.cpp TouchPics()\n */\nfunction TouchPics(): void {\n CG_TouchPics();\n}\n\n/**\n * Get layout flags for current player state.\n * Reference: rerelease/cg_screen.cpp\n */\nfunction GetLayoutFlags(ps: PlayerState): LayoutFlags {\n // TODO: Implement proper layout flag calculation\n // Based on inventory state, help state, intermission, etc.\n return 0 as LayoutFlags; // No flags set by default\n}\n\n/**\n * Placeholder stubs for remaining CGameExport functions.\n * These will be implemented as needed.\n */\n\nfunction GetActiveWeaponWheelWeapon(ps: PlayerState): number {\n return 0;\n}\n\nfunction GetOwnedWeaponWheelWeapons(ps: PlayerState): number[] {\n return [];\n}\n\nfunction GetWeaponWheelAmmoCount(ps: PlayerState, weapon: number): number {\n return 0;\n}\n\nfunction GetPowerupWheelCount(ps: PlayerState): number {\n return 0;\n}\n\nfunction GetHitMarkerDamage(ps: PlayerState): number {\n return 0;\n}\n\nfunction Pmove(pmove: unknown): void {\n // TODO: Implement client-side movement prediction\n // Should call shared Pmove() function\n}\n\nfunction ParseConfigString(i: number, s: string): void {\n // TODO: Implement config string parsing\n // Handle CONFIG_N64_PHYSICS, CS_AIRACCEL, etc.\n}\n\nfunction ParseCenterPrint(str: string, isplit: number, instant: boolean): void {\n if (!cgi) return;\n const messageSystem = CG_GetMessageSystem();\n // TODO: Parse layout strings and handle key bindings\n messageSystem.setCenterPrint(str, cgi.CL_ClientTime());\n}\n\nfunction NotifyMessage(isplit: number, msg: string, is_chat: boolean): void {\n if (!cgi) return;\n const messageSystem = CG_GetMessageSystem();\n messageSystem.addNotification(msg, is_chat, cgi.CL_ClientTime());\n}\n\nfunction ClearNotify(isplit: number): void {\n const messageSystem = CG_GetMessageSystem();\n messageSystem.clearNotifications();\n}\n\nfunction ClearCenterprint(isplit: number): void {\n const messageSystem = CG_GetMessageSystem();\n messageSystem.clearCenterPrint();\n}\n\nfunction GetMonsterFlashOffset(id: number): Vec3 {\n return { x: 0, y: 0, z: 0 };\n}\n\nfunction GetExtension(name: string): unknown {\n return null;\n}\n\n/**\n * Main entry point for CGame module.\n * Reference: rerelease/cg_main.cpp GetCGameAPI()\n *\n * @param imports - Functions provided by the client engine\n * @returns CGame export interface\n */\nexport function GetCGameAPI(imports: CGameImport): CGameExport {\n cgi = imports;\n\n return {\n // Lifecycle\n Init,\n Shutdown,\n\n // Rendering\n DrawHUD,\n TouchPics,\n\n // Layout\n LayoutFlags: GetLayoutFlags,\n\n // Weapon wheel\n GetActiveWeaponWheelWeapon,\n GetOwnedWeaponWheelWeapons,\n GetWeaponWheelAmmoCount,\n GetPowerupWheelCount,\n\n // Hit markers\n GetHitMarkerDamage,\n\n // Prediction\n Pmove,\n\n // Parsing\n ParseConfigString,\n ParseCenterPrint,\n NotifyMessage,\n\n // State management\n ClearNotify,\n ClearCenterprint,\n\n // Effects\n GetMonsterFlashOffset,\n\n // Extension\n GetExtension,\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/hud/crosshair.ts","../src/hud/icons.ts","../src/hud/damage.ts","../src/hud/messages.ts","../src/hud/blends.ts","../src/hud/pickup.ts","../src/hud/numbers.ts","../src/hud/types.ts","../src/hud/statusbar.ts","../src/hud/layout.ts","../src/screen.ts","../src/index.ts"],"names":["cgi","hudNumberPics","numberWidth","messageSystem"],"mappings":";;;AAEA,IAAI,YAAA,GAA+B,IAAA;AAEnC,IAAI,cAAA,GAAmD,CAAC,CAAA,EAAG,CAAA,EAAG,GAAG,CAAC,CAAA;AAGlE,IAAM,eAAA,GAAkB,CAAC,KAAA,EAAO,KAAA,EAAO,KAAK,CAAA;AAC5C,IAAM,aAAA,GAAoC,CAAC,IAAA,EAAM,IAAA,EAAM,IAAI,CAAA;AAEpD,IAAM,cAAA,GAAiB,CAACA,IAAAA,KAAqB;AAChD,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,eAAA,CAAgB,QAAQ,CAAA,EAAA,EAAK;AAC7C,IAAA,MAAM,IAAA,GAAO,gBAAgB,CAAC,CAAA;AAC9B,IAAA,IAAI;AACA,MAAA,aAAA,CAAc,CAAC,CAAA,GAAIA,IAAAA,CAAI,gBAAA,CAAiB,CAAA,KAAA,EAAQ,IAAI,CAAA,IAAA,CAAM,CAAA;AAAA,IAC9D,SAAS,CAAA,EAAG;AACR,MAAA,IAAI,MAAM,CAAA,EAAG;AACR,QAAA,IAAI;AACD,UAAA,aAAA,CAAc,CAAC,CAAA,GAAIA,IAAAA,CAAI,gBAAA,CAAiB,oBAAoB,CAAA;AAAA,QAChE,SAAS,EAAA,EAAI;AACT,UAAAA,IAAAA,CAAI,UAAU,kCAAkC,CAAA;AAAA,QACpD;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AAEA,EAAA,YAAA,GAAe,cAAc,CAAC,CAAA;AAClC,CAAA;AA+DO,IAAM,cAAA,GAAiB,CAACA,IAAAA,EAAkB,KAAA,EAAe,MAAA,KAAmB;AAC/E,EAAA,IAAwB,YAAA,EAAc;AAClC,IAAA,MAAM,IAAA,GAAOA,IAAAA,CAAI,eAAA,CAAgB,YAAY,CAAA;AAC7C,IAAA,MAAM,CAAA,GAAA,CAAK,KAAA,GAAQ,IAAA,CAAK,KAAA,IAAS,CAAA;AACjC,IAAA,MAAM,CAAA,GAAA,CAAK,MAAA,GAAS,IAAA,CAAK,MAAA,IAAU,CAAA;AAEnC,IAAAA,IAAAA,CAAI,iBAAiB,CAAA,EAAG,CAAA,EAAG,cAAc,EAAE,CAAA,EAAG,eAAe,CAAC,CAAA,EAAG,GAAG,cAAA,CAAe,CAAC,GAAG,CAAA,EAAG,cAAA,CAAe,CAAC,CAAA,EAAE,EAAG,cAAA,CAAe,CAAC,CAAC,CAAA;AAAA,EACpI;AACJ,CAAA;;;AC5FO,IAAM,QAAA,uBAAe,GAAA,EAAqB;AAEjD,IAAM,UAAA,GAAa;AAAA;AAAA,EAEf,WAAA;AAAA,EAAa,WAAA;AAAA,EAAa,YAAA;AAAA,EAAc,cAAA;AAAA,EAAgB,YAAA;AAAA,EACxD,aAAA;AAAA,EAAe,aAAA;AAAA,EAAe,gBAAA;AAAA,EAAkB,WAAA;AAAA,EAAa,OAAA;AAAA,EAC7D,WAAA;AAAA;AAAA,EAEA,YAAA;AAAA,EAAc,WAAA;AAAA,EAAa,SAAA;AAAA,EAAW,WAAA;AAAA,EAAa,UAAA;AAAA,EAAY,SAAA;AAAA;AAAA,EAE/D,QAAA;AAAA,EAAU,mBAAA;AAAA,EAAqB,YAAA;AAAA,EAAc,cAAA;AAAA,EAAgB,cAAA;AAAA,EAC7D,cAAA;AAAA,EAAgB,cAAA;AAAA;AAAA,EAEhB,eAAA;AAAA,EAAiB,eAAA;AAAA,EAAiB,aAAA;AAAA,EAAe,eAAA;AAAA,EAAiB,eAAA;AAAA;AAAA,EAElE,UAAA;AAAA,EAAY,aAAA;AAAA,EAAe,WAAA;AAAA,EAAa,YAAA;AAAA,EAAc,YAAA;AAAA,EAAc,WAAA;AAAA,EAAa;AACrF,CAAA;AAMO,IAAM,UAAA,GAAa,CAACA,IAAAA,KAAqB;AAC5C,EAAA,KAAA,MAAW,QAAQ,UAAA,EAAY;AAC3B,IAAA,IAAI;AACA,MAAA,MAAM,GAAA,GAAMA,IAAAA,CAAI,gBAAA,CAAiB,CAAA,KAAA,EAAQ,IAAI,CAAA,IAAA,CAAM,CAAA;AACnD,MAAA,QAAA,CAAS,GAAA,CAAI,MAAM,GAAG,CAAA;AAAA,IAC1B,SAAS,CAAA,EAAG;AACR,MAAAA,IAAAA,CAAI,SAAA,CAAU,CAAA,+BAAA,EAAkC,IAAI,CAAA;AAAA,CAAQ,CAAA;AAAA,IAChE;AAAA,EACJ;AACJ,CAAA;;;AClCA,IAAM,UAAA,uBAAiB,GAAA,EAAqB;AAE5C,IAAM,sBAAA,GAAyB;AAAA,EAC3B,QAAA;AAAA,EAAU,SAAA;AAAA,EAAW,MAAA;AAAA,EAAQ;AACjC,CAAA;AAEO,IAAM,WAAA,GAAc,CAACA,IAAAA,KAAqB;AAC7C,EAAA,KAAA,MAAW,QAAQ,sBAAA,EAAwB;AACvC,IAAA,IAAI;AACA,MAAA,MAAM,GAAA,GAAMA,IAAAA,CAAI,gBAAA,CAAiB,CAAA,KAAA,EAAQ,IAAI,CAAA,IAAA,CAAM,CAAA;AACnD,MAAA,UAAA,CAAW,GAAA,CAAI,MAAM,GAAG,CAAA;AAAA,IAC5B,SAAS,CAAA,EAAG;AACR,MAAAA,IAAAA,CAAI,SAAA,CAAU,CAAA,+BAAA,EAAkC,IAAI,CAAA;AAAA,CAAQ,CAAA;AAAA,IAChE;AAAA,EACJ;AACJ,CAAA;AAEO,IAAM,WAAA,GAAc,CAACA,IAAAA,EAAkB,EAAA,EAAiB,OAAe,MAAA,KAAmB;AAI7F,EAAA,IAAI,CAAC,GAAG,UAAA,IAAc,CAAC,GAAG,YAAA,IAAgB,CAAC,GAAG,YAAA,EAAc;AAIxD,IAAA;AAAA,EACJ;AAKJ,CAAA;;;ACzBA,IAAM,qBAAA,GAAwB,GAAA;AAC9B,IAAM,eAAA,GAAkB,GAAA;AACxB,IAAM,mBAAA,GAAsB,CAAA;AAErB,IAAM,gBAAN,MAAoB;AAAA,EAApB,WAAA,GAAA;AACL,IAAA,IAAA,CAAQ,cAAA,GAAiC,IAAA;AACzC,IAAA,IAAA,CAAQ,iBAA4B,EAAC;AAAA,EAAA;AAAA,EAErC,cAAA,CAAe,MAAc,GAAA,EAAa;AACxC,IAAA,IAAA,CAAK,cAAA,GAAiB;AAAA,MACpB,IAAA;AAAA,MACA,SAAA,EAAW,GAAA;AAAA,MACX,QAAA,EAAU;AAAA,KACZ;AAAA,EACF;AAAA,EAEA,SAAA,CAAU,MAAc,GAAA,EAAa;AACnC,IAAA,IAAA,CAAK,eAAe,IAAA,CAAK;AAAA,MACvB,IAAA;AAAA,MACA,SAAA,EAAW,GAAA;AAAA,MACX,QAAA,EAAU;AAAA,KACX,CAAA;AAED,IAAA,IAAI,IAAA,CAAK,cAAA,CAAe,MAAA,GAAS,mBAAA,EAAqB;AACpD,MAAA,IAAA,CAAK,eAAe,KAAA,EAAM;AAAA,IAC5B;AAAA,EACF;AAAA;AAAA,EAGA,cAAA,CAAe,MAAc,GAAA,EAAa;AACxC,IAAA,IAAA,CAAK,cAAA,GAAiB;AAAA,MACpB,IAAA;AAAA,MACA,SAAA,EAAW,GAAA;AAAA,MACX,QAAA,EAAU;AAAA,KACZ;AAAA,EACF;AAAA,EAEA,eAAA,CAAgB,IAAA,EAAc,OAAA,EAAkB,GAAA,EAAa;AAC3D,IAAA,IAAA,CAAK,eAAe,IAAA,CAAK;AAAA,MACvB,IAAA;AAAA,MACA,SAAA,EAAW,GAAA;AAAA,MACX,QAAA,EAAU;AAAA,KACX,CAAA;AAED,IAAA,IAAI,IAAA,CAAK,cAAA,CAAe,MAAA,GAAS,mBAAA,EAAqB;AACpD,MAAA,IAAA,CAAK,eAAe,KAAA,EAAM;AAAA,IAC5B;AAAA,EACF;AAAA,EAEA,kBAAA,GAAqB;AACnB,IAAA,IAAA,CAAK,iBAAiB,EAAC;AAAA,EACzB;AAAA,EAEA,gBAAA,GAAmB;AACjB,IAAA,IAAA,CAAK,cAAA,GAAiB,IAAA;AAAA,EACxB;AAAA,EAEA,eAAA,CAAgB,QAAA,EAAoB,GAAA,EAAa,MAAA,EAAyC;AACxF,IAAA,IAAI,CAAC,KAAK,cAAA,EAAgB;AAE1B,IAAA,IAAI,MAAM,IAAA,CAAK,cAAA,CAAe,SAAA,GAAY,IAAA,CAAK,eAAe,QAAA,EAAU;AACtE,MAAA,IAAA,CAAK,cAAA,GAAiB,IAAA;AACtB,MAAA;AAAA,IACF;AAGA,IAAc,IAAA,CAAK,cAAA,CAAe,IAAA,CAAK,MAAA,GAAS;AAEhD,IAAA,MAAM,IAAI,MAAA,CAAO,cAAA;AAEjB,IAAA,QAAA,CAAS,gBAAA,CAAiB,CAAA,EAAG,IAAA,CAAK,cAAA,CAAe,IAAI,CAAA;AAAA,EACvD;AAAA,EAEA,iBAAA,CAAkB,UAAoB,GAAA,EAAa;AAEjD,IAAA,OAAO,IAAA,CAAK,cAAA,CAAe,MAAA,GAAS,CAAA,IAAK,MAAM,IAAA,CAAK,cAAA,CAAe,CAAC,CAAA,CAAE,SAAA,GAAY,IAAA,CAAK,cAAA,CAAe,CAAC,EAAE,QAAA,EAAU;AACjH,MAAA,IAAA,CAAK,eAAe,KAAA,EAAM;AAAA,IAC5B;AAEA,IAAA,IAAI,CAAA,GAAI,EAAA;AACR,IAAA,KAAA,MAAW,GAAA,IAAO,KAAK,cAAA,EAAgB;AACrC,MAAA,QAAA,CAAS,UAAA,CAAW,EAAA,EAAI,CAAA,EAAG,GAAA,CAAI,IAAI,CAAA;AACnC,MAAA,CAAA,IAAK,EAAA;AAAA,IACP;AAAA,EACF;AACF,CAAA;;;AC3FO,IAAM,WAAA,GAAc,CAACA,IAAAA,EAAkB,EAAA,EAAiB,OAAe,MAAA,KAAmB;AAC7F,EAAA,IAAI,CAAC,GAAG,KAAA,EAAO;AAEf,EAAA,MAAM,CAAC,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAC,IAAI,EAAA,CAAG,KAAA;AAyB5B,CAAA;;;AC3BO,IAAM,WAAA,GAAc,CAACA,IAAAA,EAAkB,EAAA,EAAiB,OAAe,MAAA,KAAmB;AAC7F,EAAA,IAAI,CAAC,GAAG,UAAA,EAAY;AAEpB,EAAA,MAAM,IAAA,GAAO,QAAA,CAAS,GAAA,CAAI,EAAA,CAAG,UAAU,CAAA;AACvC,EAAA,IAAI,IAAA,EAAM;AACN,IAAA,MAAM,IAAA,GAAOA,IAAAA,CAAI,eAAA,CAAgB,IAAI,CAAA;AACrC,IAAA,MAAM,CAAA,GAAI,KAAA,GAAQ,IAAA,CAAK,KAAA,GAAQ,EAAA;AAC/B,IAAA,MAAM,CAAA,GAAI,MAAA,GAAS,IAAA,CAAK,MAAA,GAAS,EAAA;AACjC,IAAAA,IAAAA,CAAI,WAAA,CAAY,CAAA,EAAG,CAAA,EAAG,IAAI,CAAA;AAAA,EAC9B;AACJ,CAAA;;;ACZO,IAAM,WAAA,GAAc,CAACA,IAAAA,EAAkB,CAAA,EAAW,GAAW,KAAA,EAAe,IAAA,EAA0B,OAAe,KAAA,KAA6C;AACrK,EAAA,MAAM,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,KAAK,EAAE,QAAA,EAAS;AACnC,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,CAAE,QAAQ,CAAA,EAAA,EAAK;AAC/B,IAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,CAAA,CAAE,CAAC,CAAC,CAAA;AAC3B,IAAA,MAAM,GAAA,GAAM,KAAK,KAAK,CAAA;AACtB,IAAA,IAAI,GAAA,EAAK;AACJ,MAIM;AACH,QAAAA,KAAI,WAAA,CAAY,CAAA,GAAI,CAAA,GAAI,KAAA,EAAO,GAAG,GAAG,CAAA;AAAA,MACzC;AAAA,IACJ;AAAA,EACJ;AACJ,CAAA;ACWO,IAAM,eAAA,GAA4C;AAAA,EACvD,CAAC,QAAA,CAAS,OAAO,GAAG,WAAA;AAAA,EACpB,CAAC,QAAA,CAAS,OAAO,GAAG,WAAA;AAAA,EACpB,CAAC,QAAA,CAAS,YAAY,GAAG,YAAA;AAAA,EACzB,CAAC,QAAA,CAAS,UAAU,GAAG,cAAA;AAAA,EACvB,CAAC,QAAA,CAAS,QAAQ,GAAG,YAAA;AAAA,EACrB,CAAC,QAAA,CAAS,eAAe,GAAG,aAAA;AAAA,EAC5B,CAAC,QAAA,CAAS,cAAc,GAAG,aAAA;AAAA,EAC3B,CAAC,QAAA,CAAS,YAAY,GAAG,gBAAA;AAAA,EACzB,CAAC,QAAA,CAAS,OAAO,GAAG,WAAA;AAAA,EACpB,CAAC,QAAA,CAAS,MAAM,GAAG,OAAA;AAAA,EACnB,CAAC,QAAA,CAAS,OAAO,GAAG,WAAA;AAAA,EACpB,CAAC,QAAA,CAAS,SAAS,GAAG,aAAA;AAAA,EACtB,CAAC,QAAA,CAAS,QAAQ,GAAG,aAAA;AAAA,EACrB,CAAC,QAAA,CAAS,YAAY,GAAG,iBAAA;AAAA,EACzB,CAAC,QAAA,CAAS,SAAS,GAAG,aAAA;AAAA,EACtB,CAAC,QAAA,CAAS,UAAU,GAAG,cAAA;AAAA,EACvB,CAAC,QAAA,CAAS,OAAO,GAAG,WAAA;AAAA,EACpB,CAAC,QAAA,CAAS,SAAS,GAAG;AACxB,CAAA;AAMO,IAAM,YAAA,GAAuC;AAAA,EAClD,MAAA,EAAQ,WAAA;AAAA,EACR,KAAA,EAAO,UAAA;AAAA,EACP,OAAA,EAAS,YAAA;AAAA,EACT,QAAA,EAAU;AACZ,CAAA;AC9CO,IAAM,cAAA,GAAiB,CAC1BA,IAAAA,EACA,MAAA,EACA,MAAA,EACA,OACA,IAAA,EACAC,cAAAA,EACAC,YAAAA,EACA,MAAA,EACA,MAAA,KACC;AAaD,EAAA,IAAID,cAAAA,CAAc,SAAS,CAAA,EAAG;AAC1B,IAAA,WAAA,CAAYD,IAAAA,EAAK,OAAO,QAAA,EAAU,MAAA,CAAO,UAAU,MAAA,EAAQC,cAAAA,EAAeC,YAAwB,CAAA;AAClG,IAAA,WAAA,CAAYF,MAAK,MAAA,CAAO,OAAA,EAAS,OAAO,OAAA,EAAS,KAAA,EAAOC,gBAAeC,YAAW,CAAA;AAClF,IAAA,WAAA,CAAYF,MAAK,MAAA,CAAO,MAAA,EAAQ,OAAO,MAAA,EAAQ,IAAA,EAAMC,gBAAeC,YAAW,CAAA;AAAA,EACnF;AAGA,EAAA,MAAM,SAAA,GAAY,OAAO,SAAA,CAAU,KAAA;AACnC,EAAA,IAAI,SAAA,IAAa,SAAA,CAAU,UAAA,GAAa,CAAA,EAAG;AACvC,IAAA,MAAM,QAAA,GAAW,CAAA,EAAA,EAAK,SAAA,CAAU,SAAS,CAAA,KAAA,CAAA;AACzC,IAAA,MAAM,IAAA,GAAO,QAAA,CAAS,GAAA,CAAI,QAAQ,CAAA;AAClC,IAAA,IAAI,IAAA,EAAM;AACN,MAAAF,IAAAA,CAAI,YAAY,MAAA,CAAO,OAAA,GAAU,IAAI,MAAA,CAAO,OAAA,GAAU,GAAG,IAAI,CAAA;AAAA,IACjE;AAAA,EACJ;AAGA,EAAA,MAAM,aAAA,GAAgB,OAAO,SAAA,CAAU,aAAA;AACvC,EAAA,IAAI,aAAA,EAAe;AACf,IAAA,MAAM,QAAA,GAAW,gBAAgB,aAAa,CAAA;AAC9C,IAAA,IAAI,QAAA,EAAU;AACV,MAAA,MAAM,IAAA,GAAO,QAAA,CAAS,GAAA,CAAI,QAAQ,CAAA;AAClC,MAAA,IAAI,IAAA,EAAM;AACN,QAAAA,KAAI,WAAA,CAAY,MAAA,CAAO,aAAA,EAAe,MAAA,CAAO,eAAe,IAAI,CAAA;AAAA,MACpE;AAAA,IACJ;AAAA,EACJ;AAGA,EAAA,MAAM,OAAO,KAAA,CAAM,IAAA,CAAK,OAAO,SAAA,CAAU,IAAI,EAAE,IAAA,EAAK;AACpD,EAAA,IAAI,IAAA,GAAO,MAAA,CAAO,aAAA,GAAgB,GAAA,GAAM,MAAA,CAAO,KAAA;AAE/C,EAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACpB,IAAA,MAAM,QAAA,GAAW,aAAa,GAAG,CAAA;AACjC,IAAA,IAAI,QAAA,EAAU;AACV,MAAA,MAAM,IAAA,GAAO,QAAA,CAAS,GAAA,CAAI,QAAQ,CAAA;AAClC,MAAA,IAAI,IAAA,EAAM;AACN,QAAA,MAAM,IAAA,GAAOA,IAAAA,CAAI,eAAA,CAAgB,IAAI,CAAA;AACrC,QAAAA,IAAAA,CAAI,WAAA,CAAY,MAAA,CAAO,aAAA,EAAe,MAAM,IAAI,CAAA;AAChD,QAAA,IAAA,IAAS,KAAK,MAAA,GAAS,CAAA;AAAA,MAC3B;AAAA,IACJ;AAAA,EACJ;AAGA,EAAA,IAAI,WAAW,MAAA,CAAO,SAAA;AACtB,EAAA,KAAA,MAAW,CAAC,OAAA,EAAS,SAAS,CAAA,IAAK,KAAA,CAAM,IAAA,CAAK,MAAA,CAAO,SAAA,CAAU,QAAA,CAAS,OAAA,EAAS,CAAA,EAAG;AAChF,IAAA,IAAI,SAAA,IAAa,YAAY,MAAA,EAAQ;AACjC,MAAA,MAAM,QAAA,GAAW,KAAK,OAAO,CAAA,CAAA;AAC7B,MAAA,MAAM,IAAA,GAAO,QAAA,CAAS,GAAA,CAAI,QAAQ,CAAA;AAClC,MAAA,IAAI,IAAA,EAAM;AACN,QAAA,MAAM,IAAA,GAAOA,IAAAA,CAAI,eAAA,CAAgB,IAAI,CAAA;AACrC,QAAAA,IAAAA,CAAI,WAAA,CAAY,QAAA,EAAU,MAAA,CAAO,WAAW,IAAI,CAAA;AAGhD,QAAA,MAAM,gBAAA,GAAmB,IAAA,CAAK,IAAA,CAAA,CAAM,SAAA,GAAY,UAAU,GAAI,CAAA;AAC9D,QAAA,WAAA,CAAYA,IAAAA,EAAK,WAAW,IAAA,CAAK,KAAA,GAAQ,GAAG,MAAA,CAAO,SAAA,EAAW,gBAAA,EAAkBC,cAAAA,EAAeC,YAAW,CAAA;AAE1G,QAAA,QAAA,IAAa,KAAK,KAAA,GAAQA,YAAAA,GAAc,gBAAA,CAAiB,QAAA,GAAW,MAAA,GAAS,CAAA;AAAA,MACjF;AAAA,IACJ;AAAA,EACJ;AACJ,CAAA;;;AChGA,IAAM,eAAA,GAAkB,GAAA;AACxB,IAAM,gBAAA,GAAmB,GAAA;AAElB,IAAM,YAAA,GAAe,CAAC,KAAA,EAAe,MAAA,KAAmB;AAG3D,EAAA,MAAM,SAAS,KAAA,GAAQ,eAAA;AACvB,EAAA,MAAM,SAAS,MAAA,GAAS,gBAAA;AACxB,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,MAAA,EAAQ,MAAM,CAAA;AAiBrC,EAAA,OAAO;AAAA;AAAA,IAEH,UAAU,GAAA,GAAM,KAAA;AAAA,IAChB,QAAA,EAAU,MAAA,GAAA,CAAU,gBAAA,GAAmB,GAAA,IAAO,KAAA;AAAA,IAE9C,SAAS,GAAA,GAAM,KAAA;AAAA,IACf,OAAA,EAAS,MAAA,GAAA,CAAU,gBAAA,GAAmB,GAAA,IAAO,KAAA;AAAA,IAE7C,MAAA,EAAQ,KAAA,GAAA,CAAS,eAAA,GAAkB,GAAA,IAAO,KAAA;AAAA;AAAA,IAC1C,MAAA,EAAQ,MAAA,GAAA,CAAU,gBAAA,GAAmB,GAAA,IAAO,KAAA;AAAA;AAAA,IAG5C,gBAAgB,KAAA,GAAQ,CAAA;AAAA,IACxB,gBAAgB,GAAA,GAAM,KAAA;AAAA;AAAA;AAAA,IAGtB,eAAe,EAAA,GAAK,KAAA;AAAA,IACpB,aAAA,EAAe,MAAA,GAAA,CAAU,gBAAA,GAAmB,GAAA,IAAO,KAAA;AAAA,IAEnD,SAAA,EAAW,KAAA,GAAA,CAAS,eAAA,GAAkB,GAAA,IAAO,KAAA;AAAA,IAC7C,SAAA,EAAW,MAAA,GAAA,CAAU,gBAAA,GAAmB,GAAA,IAAO,KAAA;AAAA,IAE/C;AAAA,GACJ;AACJ,CAAA;;;ACrBA,IAAI,GAAA,GAA0B,IAAA;AAC9B,IAAM,gBAA2B,EAAC;AAClC,IAAI,WAAA,GAAc,CAAA;AAGlB,IAAM,aAAA,GAAgB,IAAI,aAAA,EAAc;AAOjC,SAAS,cAAc,OAAA,EAA4B;AACtD,EAAA,GAAA,GAAM,OAAA;AACV;AAQO,SAAS,YAAA,GAAqB;AACjC,EAAA,IAAI,CAAC,GAAA,EAAK;AAGV,EAAA,aAAA,CAAc,MAAA,GAAS,CAAA;AACvB,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,EAAA,EAAI,CAAA,EAAA,EAAK;AACzB,IAAA,IAAI;AACA,MAAA,MAAM,GAAA,GAAM,GAAA,CAAI,gBAAA,CAAiB,CAAA,aAAA,EAAgB,CAAC,CAAA,IAAA,CAAM,CAAA;AACxD,MAAA,aAAA,CAAc,KAAK,GAAG,CAAA;AACtB,MAAA,IAAI,MAAM,CAAA,EAAG;AACT,QAAA,MAAM,IAAA,GAAO,GAAA,CAAI,eAAA,CAAgB,GAAG,CAAA;AACpC,QAAA,WAAA,GAAc,IAAA,CAAK,KAAA;AAAA,MACvB;AAAA,IACJ,SAAS,CAAA,EAAG;AACR,MAAA,GAAA,CAAI,SAAA,CAAU,mDAAmD,CAAC,CAAA;AAAA,CAAQ,CAAA;AAAA,IAC9E;AAAA,EACJ;AAEA,EAAA,cAAA,CAAe,GAAG,CAAA;AAClB,EAAA,UAAA,CAAW,GAAG,CAAA;AACd,EAAA,WAAA,CAAY,GAAG,CAAA;AACnB;AAgBO,SAAS,WACZ,MAAA,EACA,IAAA,EACA,WACA,QAAA,EACA,KAAA,EACA,WACA,EAAA,EACI;AACJ,EAAA,IAAI,CAAC,GAAA,EAAK;AACN,IAAA,OAAA,CAAQ,MAAM,2CAA2C,CAAA;AACzD,IAAA;AAAA,EACJ;AAEA,EAAA,MAAM,MAAA,GAAS,IAAI,aAAA,EAAc;AACjC,EAAA,MAAM,MAAA,GAAS,YAAA,CAAa,SAAA,CAAU,KAAA,EAAO,UAAU,MAAM,CAAA;AAG7D,EAAA,WAAA,CAAY,GAAA,EAAK,EAAA,EAAI,SAAA,CAAU,KAAA,EAAO,UAAU,MAAM,CAAA;AAMtD,EAAA,MAAM,eAAA,GAA+B;AAAA,IACjC,SAAA,EAAW;AAAA,MACP,KAAA,EAAO,IAAA;AAAA;AAAA,MACP,QAAA,sBAAc,GAAA,EAAI;AAAA;AAAA,MAClB,IAAA,sBAAU,GAAA,EAAI;AAAA;AAAA,MACd,aAAA,EAAe;AAAA;AAAA;AACnB,GACJ;AAGA,EAAA,cAAA,CAAe,GAAA,EAAK,iBAAiB,GAAA,EAAK,CAAA,EAAG,GAAG,aAAA,EAAe,WAAA,EAAa,QAAQ,MAAM,CAAA;AAG1F,EAAA,WAAA,CAAY,GAAA,EAAK,EAAA,EAAI,SAAA,CAAU,KAAA,EAAO,UAAU,MAAM,CAAA;AAGtD,EAAA,WAAA,CAAY,GAAA,EAAK,EAAA,EAAI,SAAA,CAAU,KAAA,EAAO,UAAU,MAAM,CAAA;AAGtD,EAAA,IAAI,GAAG,WAAA,EAAa;AAChB,IAAA,MAAM,KAAA,GAAQ,EAAA,CAAG,WAAA,CAAY,KAAA,CAAM,IAAI,CAAA;AACvC,IAAA,IAAI,CAAA,GAAI,SAAA,CAAU,MAAA,GAAS,CAAA,GAAK,MAAM,MAAA,GAAS,EAAA;AAC/C,IAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACrB,MAAA,GAAA,CAAI,oBAAA,CAAqB,GAAG,IAAI,CAAA;AAChC,MAAA,CAAA,IAAK,EAAA;AAAA,IACV;AAAA,EACJ;AASA,EAAA,cAAA,CAAe,GAAA,EAAK,SAAA,CAAU,KAAA,EAAO,SAAA,CAAU,MAAM,CAAA;AACzD;AAMO,SAAS,mBAAA,GAAqC;AACjD,EAAA,OAAO,aAAA;AACX;;;AC9IA,IAAIF,IAAAA,GAA0B,IAAA;AAM9B,SAAS,IAAA,GAAa;AAClB,EAAA,IAAI,CAACA,IAAAA,EAAK;AACN,IAAA,OAAA,CAAQ,MAAM,mCAAmC,CAAA;AACjD,IAAA;AAAA,EACJ;AAEA,EAAAA,IAAAA,CAAI,UAAU,oCAAoC,CAAA;AAGlD,EAAA,aAAA,CAAcA,IAAG,CAAA;AAEjB,EAAAA,IAAAA,CAAI,UAAU,qBAAqB,CAAA;AACvC;AAMA,SAAS,QAAA,GAAiB;AACtB,EAAA,IAAIA,IAAAA,EAAK;AACL,IAAAA,IAAAA,CAAI,UAAU,kBAAkB,CAAA;AAAA,EACpC;AACA,EAAAA,IAAAA,GAAM,IAAA;AACV;AAMA,SAAS,QACL,MAAA,EACA,IAAA,EACA,WACA,QAAA,EACA,KAAA,EACA,WACA,EAAA,EACI;AACJ,EAAA,UAAA,CAAW,QAAQ,IAAA,EAAM,SAAA,EAAW,QAAA,EAAU,KAAA,EAAO,WAAW,EAAE,CAAA;AACtE;AAMA,SAAS,SAAA,GAAkB;AACvB,EAAA,YAAA,EAAa;AACjB;AAMA,SAAS,eAAe,EAAA,EAA8B;AAGlD,EAAA,OAAO,CAAA;AACX;AAOA,SAAS,2BAA2B,EAAA,EAAyB;AACzD,EAAA,OAAO,CAAA;AACX;AAEA,SAAS,2BAA2B,EAAA,EAA2B;AAC3D,EAAA,OAAO,EAAC;AACZ;AAEA,SAAS,uBAAA,CAAwB,IAAiB,MAAA,EAAwB;AACtE,EAAA,OAAO,CAAA;AACX;AAEA,SAAS,qBAAqB,EAAA,EAAyB;AACnD,EAAA,OAAO,CAAA;AACX;AAEA,SAAS,mBAAmB,EAAA,EAAyB;AACjD,EAAA,OAAO,CAAA;AACX;AAEA,SAAS,MAAM,KAAA,EAAsB;AAGrC;AAEA,SAAS,iBAAA,CAAkB,GAAW,CAAA,EAAiB;AAGvD;AAEA,SAAS,gBAAA,CAAiB,GAAA,EAAa,MAAA,EAAgB,OAAA,EAAwB;AAC3E,EAAA,IAAI,CAACA,IAAAA,EAAK;AACV,EAAA,MAAMG,iBAAgB,mBAAA,EAAoB;AAE1C,EAAAA,cAAAA,CAAc,cAAA,CAAe,GAAA,EAAKH,IAAAA,CAAI,eAAe,CAAA;AACzD;AAEA,SAAS,aAAA,CAAc,MAAA,EAAgB,GAAA,EAAa,OAAA,EAAwB;AACxE,EAAA,IAAI,CAACA,IAAAA,EAAK;AACV,EAAA,MAAMG,iBAAgB,mBAAA,EAAoB;AAC1C,EAAAA,eAAc,eAAA,CAAgB,GAAA,EAAK,OAAA,EAASH,IAAAA,CAAI,eAAe,CAAA;AACnE;AAEA,SAAS,YAAY,MAAA,EAAsB;AACvC,EAAA,MAAMG,iBAAgB,mBAAA,EAAoB;AAC1C,EAAAA,eAAc,kBAAA,EAAmB;AACrC;AAEA,SAAS,iBAAiB,MAAA,EAAsB;AAC5C,EAAA,MAAMA,iBAAgB,mBAAA,EAAoB;AAC1C,EAAAA,eAAc,gBAAA,EAAiB;AACnC;AAEA,SAAS,sBAAsB,EAAA,EAAkB;AAC7C,EAAA,OAAO,EAAE,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,GAAG,CAAA,EAAE;AAC9B;AAEA,SAAS,aAAa,IAAA,EAAuB;AACzC,EAAA,OAAO,IAAA;AACX;AASO,SAAS,YAAY,OAAA,EAAmC;AAC3D,EAAAH,IAAAA,GAAM,OAAA;AAEN,EAAA,OAAO;AAAA;AAAA,IAEH,IAAA;AAAA,IACA,QAAA;AAAA;AAAA,IAGA,OAAA;AAAA,IACA,SAAA;AAAA;AAAA,IAGA,WAAA,EAAa,cAAA;AAAA;AAAA,IAGb,0BAAA;AAAA,IACA,0BAAA;AAAA,IACA,uBAAA;AAAA,IACA,oBAAA;AAAA;AAAA,IAGA,kBAAA;AAAA;AAAA,IAGA,KAAA;AAAA;AAAA,IAGA,iBAAA;AAAA,IACA,gBAAA;AAAA,IACA,aAAA;AAAA;AAAA,IAGA,WAAA;AAAA,IACA,gBAAA;AAAA;AAAA,IAGA,qBAAA;AAAA;AAAA,IAGA;AAAA,GACJ;AACJ","file":"index.js","sourcesContent":["import { CGameImport } from '../types.js';\n\nlet crosshairPic: unknown | null = null;\nlet crosshairIndex = 0; // Default to ch1\nlet crosshairColor: [number, number, number, number] = [1, 1, 1, 1]; // Default white\nlet crosshairEnabled = true;\n\nconst CROSSHAIR_NAMES = ['ch1', 'ch2', 'ch3'];\nconst crosshairPics: (unknown | null)[] = [null, null, null];\n\nexport const Init_Crosshair = (cgi: CGameImport) => {\n for (let i = 0; i < CROSSHAIR_NAMES.length; i++) {\n const name = CROSSHAIR_NAMES[i];\n try {\n crosshairPics[i] = cgi.Draw_RegisterPic(`pics/${name}.pcx`);\n } catch (e) {\n if (i === 0) {\n try {\n crosshairPics[i] = cgi.Draw_RegisterPic('pics/crosshair.pcx');\n } catch (e2) {\n cgi.Com_Print('Failed to load crosshair image\\n');\n }\n }\n }\n }\n\n crosshairPic = crosshairPics[0];\n}\n\nexport const Set_Crosshair = (index: number) => {\n // If index is 0, we treat it as disabled if we want to follow Q2 convention strictly,\n // but here index maps to CROSSHAIR_NAMES.\n // If we want a \"None\" option, we can check for that.\n\n // For now, let's keep index pointing to textures.\n // We can add a separate enable/disable function or make index -1 disable it.\n\n if (index === -1) {\n crosshairEnabled = false;\n return;\n }\n\n crosshairEnabled = true;\n if (index >= 0 && index < crosshairPics.length) {\n crosshairIndex = index;\n crosshairPic = crosshairPics[index];\n }\n};\n\nexport const Set_CrosshairColor = (r: number, g: number, b: number, a: number = 1) => {\n crosshairColor = [r, g, b, a];\n}\n\nexport const Cycle_Crosshair = () => {\n // Cycle includes a \"None\" state (represented by index -1 or just disabling)\n // 0 -> 1 -> 2 -> ... -> Disabled -> 0\n\n if (!crosshairEnabled) {\n crosshairEnabled = true;\n crosshairIndex = 0;\n } else {\n crosshairIndex++;\n if (crosshairIndex >= CROSSHAIR_NAMES.length) {\n crosshairEnabled = false;\n crosshairIndex = 0; // Reset for next cycle\n }\n }\n\n if (crosshairEnabled) {\n crosshairPic = crosshairPics[crosshairIndex];\n if (!crosshairPic) {\n let found = false;\n for (let i = 0; i < CROSSHAIR_NAMES.length; i++) {\n const idx = (crosshairIndex + i) % CROSSHAIR_NAMES.length;\n if (crosshairPics[idx]) {\n crosshairIndex = idx;\n crosshairPic = crosshairPics[idx];\n found = true;\n break;\n }\n }\n if (!found) crosshairPic = null;\n }\n } else {\n crosshairPic = null;\n }\n\n return crosshairEnabled ? crosshairIndex : -1;\n};\n\nexport const Draw_Crosshair = (cgi: CGameImport, width: number, height: number) => {\n if (crosshairEnabled && crosshairPic) {\n const size = cgi.Draw_GetPicSize(crosshairPic);\n const x = (width - size.width) / 2;\n const y = (height - size.height) / 2;\n // Draw with color\n cgi.SCR_DrawColorPic(x, y, crosshairPic, { x: crosshairColor[0], y: crosshairColor[1], z: crosshairColor[2] }, crosshairColor[3]);\n }\n};\n","import { CGameImport } from '../types.js';\n\n/**\n * Global map of loaded HUD icons.\n * This is imported and used by statusbar.ts for rendering.\n */\nexport const iconPics = new Map<string, unknown>();\n\nconst ICON_NAMES = [\n // Weapons\n 'w_blaster', 'w_shotgun', 'w_sshotgun', 'w_machinegun', 'w_chaingun',\n 'w_glauncher', 'w_rlauncher', 'w_hyperblaster', 'w_railgun', 'w_bfg',\n 'w_grapple',\n // Ammo\n 'a_grenades', 'a_bullets', 'a_cells', 'a_rockets', 'a_shells', 'a_slugs',\n // Powerups\n 'p_quad', 'p_invulnerability', 'p_silencer', 'p_rebreather', 'p_envirosuit',\n 'p_adrenaline', 'p_megahealth',\n // Armor\n 'i_jacketarmor', 'i_combatarmor', 'i_bodyarmor', 'i_powerscreen', 'i_powershield',\n // Keys\n 'k_datacd', 'k_powercube', 'k_pyramid', 'k_dataspin', 'k_security', 'k_bluekey', 'k_redkey'\n];\n\n/**\n * Initialize and precache all HUD icon images.\n * Called during level load via CG_TouchPics().\n */\nexport const Init_Icons = (cgi: CGameImport) => {\n for (const name of ICON_NAMES) {\n try {\n const pic = cgi.Draw_RegisterPic(`pics/${name}.pcx`);\n iconPics.set(name, pic);\n } catch (e) {\n cgi.Com_Print(`Failed to load HUD image: pics/${name}.pcx\\n`);\n }\n }\n};\n","import { CGameImport } from '../types.js';\nimport { PlayerState, angleVectors, dotVec3, normalizeVec3 } from '@quake2ts/shared';\n\nconst damagePics = new Map<string, unknown>();\n\nconst DAMAGE_INDICATOR_NAMES = [\n 'd_left', 'd_right', 'd_up', 'd_down'\n];\n\nexport const Init_Damage = (cgi: CGameImport) => {\n for (const name of DAMAGE_INDICATOR_NAMES) {\n try {\n const pic = cgi.Draw_RegisterPic(`pics/${name}.pcx`);\n damagePics.set(name, pic);\n } catch (e) {\n cgi.Com_Print(`Failed to load HUD image: pics/${name}.pcx\\n`);\n }\n }\n};\n\nexport const Draw_Damage = (cgi: CGameImport, ps: PlayerState, width: number, height: number) => {\n // Basic placeholder check, ps structure needs damageIndicators support properly added\n // if using new PlayerState from shared. Assuming it matches for now.\n // If not, we need to add damage tracking to client/cgame state from events.\n if (!ps.damage_yaw && !ps.damage_pitch && !ps.damage_alpha) {\n // The original Q2 uses view angles and damage direction to pick quadrant.\n // It's event based usually. For now keeping structure but relying on\n // whatever ps has. Rerelease likely has fields or events.\n return;\n }\n\n // TODO: Implement damage indicator logic based on damage events or state\n // The previous implementation assumed a `damageIndicators` array which might not exist on PlayerState.\n // We will revisit this when implementing the damage event handling.\n};\n","import { Renderer } from '@quake2ts/engine';\nimport { getHudLayout } from './layout.js';\n\ninterface Message {\n text: string;\n startTime: number;\n duration: number;\n}\n\nconst CENTER_PRINT_DURATION = 3000;\nconst NOTIFY_DURATION = 5000;\nconst MAX_NOTIFY_MESSAGES = 4;\n\nexport class MessageSystem {\n private centerPrintMsg: Message | null = null;\n private notifyMessages: Message[] = [];\n\n addCenterPrint(text: string, now: number) {\n this.centerPrintMsg = {\n text,\n startTime: now,\n duration: CENTER_PRINT_DURATION,\n };\n }\n\n addNotify(text: string, now: number) {\n this.notifyMessages.push({\n text,\n startTime: now,\n duration: NOTIFY_DURATION,\n });\n\n if (this.notifyMessages.length > MAX_NOTIFY_MESSAGES) {\n this.notifyMessages.shift();\n }\n }\n\n // Additional methods for cgame API\n setCenterPrint(text: string, now: number) {\n this.centerPrintMsg = {\n text,\n startTime: now,\n duration: CENTER_PRINT_DURATION,\n };\n }\n\n addNotification(text: string, is_chat: boolean, now: number) {\n this.notifyMessages.push({\n text,\n startTime: now,\n duration: NOTIFY_DURATION,\n });\n\n if (this.notifyMessages.length > MAX_NOTIFY_MESSAGES) {\n this.notifyMessages.shift();\n }\n }\n\n clearNotifications() {\n this.notifyMessages = [];\n }\n\n clearCenterPrint() {\n this.centerPrintMsg = null;\n }\n\n drawCenterPrint(renderer: Renderer, now: number, layout: ReturnType<typeof getHudLayout>) {\n if (!this.centerPrintMsg) return;\n\n if (now > this.centerPrintMsg.startTime + this.centerPrintMsg.duration) {\n this.centerPrintMsg = null;\n return;\n }\n\n // Draw centered text\n const width = this.centerPrintMsg.text.length * 8;\n // We ignore layout.CENTER_PRINT_X because drawCenterString calculates X automatically\n const y = layout.CENTER_PRINT_Y;\n\n renderer.drawCenterString(y, this.centerPrintMsg.text);\n }\n\n drawNotifications(renderer: Renderer, now: number) {\n // Remove expired messages\n while (this.notifyMessages.length > 0 && now > this.notifyMessages[0].startTime + this.notifyMessages[0].duration) {\n this.notifyMessages.shift();\n }\n\n let y = 10; // Start near top-left\n for (const msg of this.notifyMessages) {\n renderer.drawString(10, y, msg.text);\n y += 10; // Line height\n }\n }\n}\n","import { CGameImport } from '../types.js';\nimport { PlayerState } from '@quake2ts/shared';\n\nexport const Draw_Blends = (cgi: CGameImport, ps: PlayerState, width: number, height: number) => {\n if (!ps.blend) return;\n\n const [r, g, b, a] = ps.blend;\n\n if (a > 0) {\n // Use SCR_DrawColorPic with a white pixel/texture stretched?\n // Or cgi needs a fill rect function.\n // SCR_DrawPic usually takes a pic.\n // Rerelease might use a specific blend function or just a 1x1 white texture scaled up.\n // Assuming cgi can draw colored quads if we register a \"white\" texture or similar.\n // For now, let's assume we can draw a colored rectangle if we had a white texture.\n\n // Since we don't have a fillRect in CGameImport, we might need to add it or use a registered texture.\n // Let's assume for now we skip or need to register a 'pics/white.pcx' or similar if it existed.\n // A common trick is to use any opaque texture and tint it, but that shows the texture.\n\n // Let's defer this or add a \"DrawFill\" to CGameImport if strictly needed,\n // OR standard Q2 just uses the poly drawing which isn't exposed yet.\n // Actually, Q2 V_CalcBlend handles this by setting palette/gamma or drawing a full screen poly.\n\n // For this port, we likely want a SCR_DrawFill calls.\n // Let's add SCR_DrawFill to CGameImport in types.ts in next step if it's missing.\n // Checking types.ts... it has SCR_DrawChar, SCR_DrawPic... no DrawFill.\n\n // I'll leave it commented/TODO for now or assume a 'white' pic is available.\n // cgi.SCR_DrawColorPic(0, 0, whitePic, {x:r, y:g, z:b}, a);\n }\n};\n","import { CGameImport } from '../types.js';\nimport { PlayerState } from '@quake2ts/shared';\nimport { iconPics } from './icons.js'; // Reuse loaded icons\n\nexport const Draw_Pickup = (cgi: CGameImport, ps: PlayerState, width: number, height: number) => {\n if (!ps.pickupIcon) return;\n\n const icon = iconPics.get(ps.pickupIcon);\n if (icon) {\n const size = cgi.Draw_GetPicSize(icon);\n const x = width - size.width - 10;\n const y = height - size.height - 10;\n cgi.SCR_DrawPic(x, y, icon);\n }\n};\n","import { CGameImport } from '../types.js';\n\nexport const Draw_Number = (cgi: CGameImport, x: number, y: number, value: number, pics: readonly unknown[], width: number, color?: [number, number, number, number]) => {\n const s = Math.abs(value).toString();\n for (let i = 0; i < s.length; i++) {\n const digit = parseInt(s[i]);\n const pic = pics[digit];\n if (pic) {\n if (color) {\n // cgi expects color as Vec3 (x,y,z) and alpha separate.\n // Assuming color is [r, g, b, a]\n cgi.SCR_DrawColorPic(x + i * width, y, pic, { x: color[0], y: color[1], z: color[2] }, color[3]);\n } else {\n cgi.SCR_DrawPic(x + i * width, y, pic);\n }\n }\n }\n};\n","/**\n * Local type definitions for HUD components.\n * These types represent the minimal data structures needed for HUD rendering.\n * TODO: Refactor HUD to read from PlayerState.stats[] instead of inventory structure.\n */\n\nimport { WeaponId, PowerupId } from '@quake2ts/shared';\n\nexport interface ArmorState {\n armorType: string;\n armorCount: number;\n}\n\nexport interface InventoryState {\n armor: ArmorState | null;\n currentWeapon?: WeaponId;\n powerups: Map<PowerupId, number | null>;\n keys: Set<string>;\n}\n\nexport interface ClientState {\n inventory: InventoryState;\n}\n\n/**\n * Mapping from WeaponId to HUD icon names.\n * This avoids needing to import WEAPON_ITEMS from game package.\n */\nexport const WEAPON_ICON_MAP: Record<WeaponId, string> = {\n [WeaponId.Blaster]: 'w_blaster',\n [WeaponId.Shotgun]: 'w_shotgun',\n [WeaponId.SuperShotgun]: 'w_sshotgun',\n [WeaponId.Machinegun]: 'w_machinegun',\n [WeaponId.Chaingun]: 'w_chaingun',\n [WeaponId.GrenadeLauncher]: 'w_glauncher',\n [WeaponId.RocketLauncher]: 'w_rlauncher',\n [WeaponId.HyperBlaster]: 'w_hyperblaster',\n [WeaponId.Railgun]: 'w_railgun',\n [WeaponId.BFG10K]: 'w_bfg',\n [WeaponId.Grapple]: 'w_grapple',\n [WeaponId.ChainFist]: 'w_chainfist',\n [WeaponId.EtfRifle]: 'w_etf_rifle',\n [WeaponId.ProxLauncher]: 'w_prox_launcher',\n [WeaponId.IonRipper]: 'w_ionripper',\n [WeaponId.PlasmaBeam]: 'w_plasmabeam',\n [WeaponId.Phalanx]: 'w_phalanx',\n [WeaponId.Disruptor]: 'w_disruptor',\n};\n\n/**\n * Mapping from key names to HUD icon names.\n * Provides O(1) lookup for key icon rendering.\n */\nexport const KEY_ICON_MAP: Record<string, string> = {\n 'blue': 'k_bluekey',\n 'red': 'k_redkey',\n 'green': 'k_security',\n 'yellow': 'k_pyramid',\n};\n","import { CGameImport } from '../types.js';\nimport { Draw_Number } from './numbers.js';\nimport { iconPics } from './icons.js';\nimport { getHudLayout } from './layout.js';\nimport { ClientState, WEAPON_ICON_MAP, KEY_ICON_MAP } from './types.js';\n\nlet colorblindMode = false;\n\nexport const Set_ColorblindMode = (enabled: boolean) => {\n colorblindMode = enabled;\n};\n\nexport const Draw_StatusBar = (\n cgi: CGameImport,\n client: ClientState,\n health: number,\n armor: number,\n ammo: number,\n hudNumberPics: readonly unknown[],\n numberWidth: number,\n timeMs: number,\n layout: ReturnType<typeof getHudLayout>\n) => {\n // Draw Health\n let healthColor: [number, number, number, number] | undefined = undefined;\n\n if (health <= 25) {\n if (colorblindMode) {\n // Use Blue/Cyan for low health in colorblind mode instead of Red\n healthColor = [0.2, 0.6, 1, 1];\n } else {\n healthColor = [1, 0, 0, 1]; // Red for low health\n }\n }\n\n if (hudNumberPics.length > 0) {\n Draw_Number(cgi, layout.HEALTH_X, layout.HEALTH_Y, health, hudNumberPics, numberWidth, healthColor);\n Draw_Number(cgi, layout.ARMOR_X, layout.ARMOR_Y, armor, hudNumberPics, numberWidth);\n Draw_Number(cgi, layout.AMMO_X, layout.AMMO_Y, ammo, hudNumberPics, numberWidth);\n }\n\n // Draw Armor Icon\n const armorItem = client.inventory.armor;\n if (armorItem && armorItem.armorCount > 0) {\n const iconName = `i_${armorItem.armorType}armor`;\n const icon = iconPics.get(iconName);\n if (icon) {\n cgi.SCR_DrawPic(layout.ARMOR_X - 24, layout.ARMOR_Y - 2, icon);\n }\n }\n\n // Draw Weapon Icon\n const currentWeapon = client.inventory.currentWeapon;\n if (currentWeapon) {\n const iconName = WEAPON_ICON_MAP[currentWeapon];\n if (iconName) {\n const icon = iconPics.get(iconName);\n if (icon) {\n cgi.SCR_DrawPic(layout.WEAPON_ICON_X, layout.WEAPON_ICON_Y, icon);\n }\n }\n }\n\n // Draw Keys\n const keys = Array.from(client.inventory.keys).sort();\n let keyY = layout.WEAPON_ICON_Y - 150 * layout.scale; // Stack above weapon icon?\n\n for (const key of keys) {\n const iconName = KEY_ICON_MAP[key];\n if (iconName) {\n const icon = iconPics.get(iconName);\n if (icon) {\n const size = cgi.Draw_GetPicSize(icon);\n cgi.SCR_DrawPic(layout.WEAPON_ICON_X, keyY, icon);\n keyY += (size.height + 2);\n }\n }\n }\n\n // Draw Powerups\n let powerupX = layout.POWERUP_X;\n for (const [powerup, expiresAt] of Array.from(client.inventory.powerups.entries())) {\n if (expiresAt && expiresAt > timeMs) {\n const iconName = `p_${powerup}`;\n const icon = iconPics.get(iconName);\n if (icon) {\n const size = cgi.Draw_GetPicSize(icon);\n cgi.SCR_DrawPic(powerupX, layout.POWERUP_Y, icon);\n\n // Draw remaining time\n const remainingSeconds = Math.ceil((expiresAt - timeMs) / 1000);\n Draw_Number(cgi, powerupX + size.width + 2, layout.POWERUP_Y, remainingSeconds, hudNumberPics, numberWidth);\n\n powerupX -= (size.width + numberWidth * remainingSeconds.toString().length + 8);\n }\n }\n }\n};\n","// Dynamic Layout Scaling\nconst REFERENCE_WIDTH = 640;\nconst REFERENCE_HEIGHT = 480;\n\nexport const getHudLayout = (width: number, height: number) => {\n // Determine scale factor - usually based on height to preserve aspect ratio logic or just scale uniform\n // Quake 2 typically scales 2D elements.\n const scaleX = width / REFERENCE_WIDTH;\n const scaleY = height / REFERENCE_HEIGHT;\n const scale = Math.min(scaleX, scaleY); // Uniform scaling\n\n // Or we can just center the 640x480 rect?\n // Modern approach: Scale UI to fit, or anchor to edges.\n // Let's implement edge anchoring logic relative to 640x480 coordinates.\n\n // Original constants (approximate):\n // HEALTH_X: 100, HEALTH_Y: 450\n // ARMOR_X: 200, ARMOR_Y: 450\n // AMMO_X: 540, AMMO_Y: 450\n // CENTER_PRINT: Center screen\n // WEAPON_ICON: Bottom left\n // POWERUP: Bottom right?\n\n // We'll return scaled coordinates.\n // For bottom elements, we should anchor to bottom.\n\n return {\n // Status bar numbers - Anchored Bottom-Left / Center / Right\n HEALTH_X: 100 * scale,\n HEALTH_Y: height - (REFERENCE_HEIGHT - 450) * scale,\n\n ARMOR_X: 200 * scale,\n ARMOR_Y: height - (REFERENCE_HEIGHT - 450) * scale,\n\n AMMO_X: width - (REFERENCE_WIDTH - 540) * scale, // Anchor right? 540 is near right (640)\n AMMO_Y: height - (REFERENCE_HEIGHT - 450) * scale,\n\n // Center print messages - Center\n CENTER_PRINT_X: width / 2,\n CENTER_PRINT_Y: 100 * scale, // Top anchor\n\n // Weapon and powerup icons\n WEAPON_ICON_X: 10 * scale,\n WEAPON_ICON_Y: height - (REFERENCE_HEIGHT - 450) * scale,\n\n POWERUP_X: width - (REFERENCE_WIDTH - 610) * scale,\n POWERUP_Y: height - (REFERENCE_HEIGHT - 450) * scale,\n\n scale: scale\n };\n};\n\n// Backward compatibility (deprecated, but useful for initial refactor)\nexport const HUD_LAYOUT = {\n HEALTH_X: 100,\n HEALTH_Y: 450,\n ARMOR_X: 200,\n ARMOR_Y: 450,\n AMMO_X: 540,\n AMMO_Y: 450,\n CENTER_PRINT_X: 320,\n CENTER_PRINT_Y: 100,\n WEAPON_ICON_X: 10,\n WEAPON_ICON_Y: 450,\n POWERUP_X: 610,\n POWERUP_Y: 450,\n};\n","/**\n * CGame HUD Screen Drawing\n * Reference: rerelease/cg_screen.cpp\n *\n * This module handles all HUD rendering for the cgame package, including:\n * - Status bar (health, armor, ammo)\n * - Crosshair\n * - Damage indicators\n * - Pickup notifications\n * - Messages and center print\n * - Subtitles\n */\n\nimport type { PlayerState } from '@quake2ts/shared';\nimport type { CGameImport, ClientState } from './types.js';\n\n// HUD component imports\nimport { Draw_Crosshair, Init_Crosshair } from './hud/crosshair.js';\nimport { Init_Icons } from './hud/icons.js';\nimport { Draw_Damage, Init_Damage } from './hud/damage.js';\nimport { Draw_Diagnostics } from './hud/diagnostics.js';\nimport { MessageSystem } from './hud/messages.js';\nimport { SubtitleSystem } from './hud/subtitles.js';\nimport { Draw_Blends } from './hud/blends.js';\nimport { Draw_Pickup } from './hud/pickup.js';\nimport { Draw_StatusBar } from './hud/statusbar.js';\nimport { getHudLayout } from './hud/layout.js';\n\n// Module-level state\nlet cgi: CGameImport | null = null;\nconst hudNumberPics: unknown[] = []; // Will hold pic handles from cgi.Draw_RegisterPic()\nlet numberWidth = 0;\n\n// Message and subtitle systems\nconst messageSystem = new MessageSystem();\nconst subtitleSystem = new SubtitleSystem();\n\n/**\n * Initialize the CGame screen module with import functions.\n * Reference: rerelease/cg_screen.cpp InitCGame()\n */\nexport function CG_InitScreen(imports: CGameImport): void {\n cgi = imports;\n}\n\n/**\n * Precache all HUD images.\n * Reference: rerelease/cg_screen.cpp:1689 (TouchPics)\n *\n * This is called during level load to register all required HUD assets.\n */\nexport function CG_TouchPics(): void {\n if (!cgi) return;\n\n // Load HUD number pics\n hudNumberPics.length = 0;\n for (let i = 0; i < 10; i++) {\n try {\n const pic = cgi.Draw_RegisterPic(`pics/hud/num_${i}.pcx`);\n hudNumberPics.push(pic);\n if (i === 0) {\n const size = cgi.Draw_GetPicSize(pic);\n numberWidth = size.width;\n }\n } catch (e) {\n cgi.Com_Print(`Warning: Failed to load HUD image: pics/hud/num_${i}.pcx\\n`);\n }\n }\n\n Init_Crosshair(cgi);\n Init_Icons(cgi);\n Init_Damage(cgi);\n}\n\n/**\n * Main HUD drawing function.\n * Reference: rerelease/cg_screen.cpp CG_DrawHUD()\n *\n * Called each frame by the client to render the HUD overlay.\n *\n * @param isplit - Split-screen index (0 for single player)\n * @param data - Additional HUD data (unused in initial implementation)\n * @param hud_vrect - Virtual HUD rectangle (screen coordinates)\n * @param hud_safe - Safe area rectangle (for overscan)\n * @param scale - HUD scale factor\n * @param playernum - Player number\n * @param ps - Current player state\n */\nexport function CG_DrawHUD(\n isplit: number,\n data: unknown,\n hud_vrect: { x: number; y: number; width: number; height: number },\n hud_safe: { x: number; y: number; width: number; height: number },\n scale: number,\n playernum: number,\n ps: PlayerState\n): void {\n if (!cgi) {\n console.error('CG_DrawHUD: cgame imports not initialized');\n return;\n }\n\n const timeMs = cgi.CL_ClientTime();\n const layout = getHudLayout(hud_vrect.width, hud_vrect.height);\n\n // Screen blends (damage, powerups)\n Draw_Blends(cgi, ps, hud_vrect.width, hud_vrect.height);\n\n // Status bar\n // Temporary mapping until ps.stats is implemented\n // This ClientState construction is temporary to support the current Draw_StatusBar signature\n // Eventually Draw_StatusBar should just take `ps` and read stats directly.\n const tempClientState: ClientState = {\n inventory: {\n armor: null, // TODO: Map from ps.stats\n powerups: new Map(), // TODO: Map from ps.stats\n keys: new Set(), // TODO: Map from ps.stats\n currentWeapon: undefined // TODO: Map from ps.stats/gunindex\n }\n };\n\n // TODO: Pass real health/armor/ammo from ps.stats\n Draw_StatusBar(cgi, tempClientState, 100, 0, 0, hudNumberPics, numberWidth, timeMs, layout);\n\n // Pickup messages\n Draw_Pickup(cgi, ps, hud_vrect.width, hud_vrect.height);\n\n // Damage indicators\n Draw_Damage(cgi, ps, hud_vrect.width, hud_vrect.height);\n\n // Center print\n if (ps.centerPrint) {\n const lines = ps.centerPrint.split('\\n');\n let y = hud_vrect.height / 2 - (lines.length * 10); // Approximation\n for (const line of lines) {\n cgi.SCR_DrawCenterString(y, line);\n y += 16;\n }\n }\n\n // Notifications (Chat/Messages)\n // messageSystem.draw(cgi, ...); // TODO: MessageSystem needs refactoring to use cgi\n\n // Subtitles\n // subtitleSystem.draw(cgi, ...); // TODO: SubtitleSystem needs refactoring to use cgi\n\n // Crosshair\n Draw_Crosshair(cgi, hud_vrect.width, hud_vrect.height);\n}\n\n/**\n * Get message system instance.\n * Used by parsing functions to add messages.\n */\nexport function CG_GetMessageSystem(): MessageSystem {\n return messageSystem;\n}\n\n/**\n * Get subtitle system instance.\n * Used by audio system to display subtitles.\n */\nexport function CG_GetSubtitleSystem(): SubtitleSystem {\n return subtitleSystem;\n}\n","/**\n * CGame Module Entry Point\n * Reference: rerelease/cg_main.cpp\n *\n * This module provides the GetCGameAPI() function that returns the cgame_export_t\n * interface to the client engine.\n */\n\nimport type { CGameImport, CGameExport } from './types.js';\nimport type { PlayerState, Vec3 } from '@quake2ts/shared';\nimport { LayoutFlags } from '@quake2ts/shared';\nimport { CG_InitScreen, CG_TouchPics, CG_DrawHUD, CG_GetMessageSystem, CG_GetSubtitleSystem } from './screen.js';\n\n// Module-level state\nlet cgi: CGameImport | null = null;\n\n/**\n * Initialize the CGame module.\n * Reference: rerelease/cg_main.cpp InitCGame()\n */\nfunction Init(): void {\n if (!cgi) {\n console.error('CGame Init: cgame imports not set');\n return;\n }\n\n cgi.Com_Print('===== CGame Initialization =====\\n');\n\n // Initialize screen/HUD module\n CG_InitScreen(cgi);\n\n cgi.Com_Print('CGame initialized\\n');\n}\n\n/**\n * Shutdown the CGame module.\n * Reference: rerelease/cg_main.cpp ShutdownCGame()\n */\nfunction Shutdown(): void {\n if (cgi) {\n cgi.Com_Print('CGame shutdown\\n');\n }\n cgi = null;\n}\n\n/**\n * Main HUD drawing function (wrapper for CG_DrawHUD).\n * Reference: rerelease/cg_screen.cpp CG_DrawHUD()\n */\nfunction DrawHUD(\n isplit: number,\n data: unknown,\n hud_vrect: { x: number; y: number; width: number; height: number },\n hud_safe: { x: number; y: number; width: number; height: number },\n scale: number,\n playernum: number,\n ps: PlayerState\n): void {\n CG_DrawHUD(isplit, data, hud_vrect, hud_safe, scale, playernum, ps);\n}\n\n/**\n * Precache all HUD images.\n * Reference: rerelease/cg_screen.cpp TouchPics()\n */\nfunction TouchPics(): void {\n CG_TouchPics();\n}\n\n/**\n * Get layout flags for current player state.\n * Reference: rerelease/cg_screen.cpp\n */\nfunction GetLayoutFlags(ps: PlayerState): LayoutFlags {\n // TODO: Implement proper layout flag calculation\n // Based on inventory state, help state, intermission, etc.\n return 0 as LayoutFlags; // No flags set by default\n}\n\n/**\n * Placeholder stubs for remaining CGameExport functions.\n * These will be implemented as needed.\n */\n\nfunction GetActiveWeaponWheelWeapon(ps: PlayerState): number {\n return 0;\n}\n\nfunction GetOwnedWeaponWheelWeapons(ps: PlayerState): number[] {\n return [];\n}\n\nfunction GetWeaponWheelAmmoCount(ps: PlayerState, weapon: number): number {\n return 0;\n}\n\nfunction GetPowerupWheelCount(ps: PlayerState): number {\n return 0;\n}\n\nfunction GetHitMarkerDamage(ps: PlayerState): number {\n return 0;\n}\n\nfunction Pmove(pmove: unknown): void {\n // TODO: Implement client-side movement prediction\n // Should call shared Pmove() function\n}\n\nfunction ParseConfigString(i: number, s: string): void {\n // TODO: Implement config string parsing\n // Handle CONFIG_N64_PHYSICS, CS_AIRACCEL, etc.\n}\n\nfunction ParseCenterPrint(str: string, isplit: number, instant: boolean): void {\n if (!cgi) return;\n const messageSystem = CG_GetMessageSystem();\n // TODO: Parse layout strings and handle key bindings\n messageSystem.setCenterPrint(str, cgi.CL_ClientTime());\n}\n\nfunction NotifyMessage(isplit: number, msg: string, is_chat: boolean): void {\n if (!cgi) return;\n const messageSystem = CG_GetMessageSystem();\n messageSystem.addNotification(msg, is_chat, cgi.CL_ClientTime());\n}\n\nfunction ClearNotify(isplit: number): void {\n const messageSystem = CG_GetMessageSystem();\n messageSystem.clearNotifications();\n}\n\nfunction ClearCenterprint(isplit: number): void {\n const messageSystem = CG_GetMessageSystem();\n messageSystem.clearCenterPrint();\n}\n\nfunction GetMonsterFlashOffset(id: number): Vec3 {\n return { x: 0, y: 0, z: 0 };\n}\n\nfunction GetExtension(name: string): unknown {\n return null;\n}\n\n/**\n * Main entry point for CGame module.\n * Reference: rerelease/cg_main.cpp GetCGameAPI()\n *\n * @param imports - Functions provided by the client engine\n * @returns CGame export interface\n */\nexport function GetCGameAPI(imports: CGameImport): CGameExport {\n cgi = imports;\n\n return {\n // Lifecycle\n Init,\n Shutdown,\n\n // Rendering\n DrawHUD,\n TouchPics,\n\n // Layout\n LayoutFlags: GetLayoutFlags,\n\n // Weapon wheel\n GetActiveWeaponWheelWeapon,\n GetOwnedWeaponWheelWeapons,\n GetWeaponWheelAmmoCount,\n GetPowerupWheelCount,\n\n // Hit markers\n GetHitMarkerDamage,\n\n // Prediction\n Pmove,\n\n // Parsing\n ParseConfigString,\n ParseCenterPrint,\n NotifyMessage,\n\n // State management\n ClearNotify,\n ClearCenterprint,\n\n // Effects\n GetMonsterFlashOffset,\n\n // Extension\n GetExtension,\n };\n}\n"]}