@where-stars-drift/core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +143 -0
  3. package/dist/src/base/bounded-body.d.ts +8 -0
  4. package/dist/src/base/bounded-body.js +10 -0
  5. package/dist/src/base/celestial-body.d.ts +12 -0
  6. package/dist/src/base/celestial-body.js +11 -0
  7. package/dist/src/base/hoverable.d.ts +10 -0
  8. package/dist/src/base/hoverable.js +4 -0
  9. package/dist/src/base/massive-body.d.ts +8 -0
  10. package/dist/src/base/massive-body.js +10 -0
  11. package/dist/src/config/effects.d.ts +14 -0
  12. package/dist/src/config/effects.js +14 -0
  13. package/dist/src/config/grid.d.ts +10 -0
  14. package/dist/src/config/grid.js +10 -0
  15. package/dist/src/config/panel.d.ts +26 -0
  16. package/dist/src/config/panel.js +26 -0
  17. package/dist/src/config/simulation.d.ts +21 -0
  18. package/dist/src/config/simulation.js +23 -0
  19. package/dist/src/controllers/clan-controller.d.ts +7 -0
  20. package/dist/src/controllers/clan-controller.js +12 -0
  21. package/dist/src/controllers/debug-controller.d.ts +45 -0
  22. package/dist/src/controllers/debug-controller.js +82 -0
  23. package/dist/src/controllers/effects-controller.d.ts +17 -0
  24. package/dist/src/controllers/effects-controller.js +71 -0
  25. package/dist/src/controllers/hover-controller.d.ts +11 -0
  26. package/dist/src/controllers/hover-controller.js +107 -0
  27. package/dist/src/controllers/layer-controller.d.ts +16 -0
  28. package/dist/src/controllers/layer-controller.js +64 -0
  29. package/dist/src/controllers/physics-controller.d.ts +14 -0
  30. package/dist/src/controllers/physics-controller.js +152 -0
  31. package/dist/src/controllers/star-controller.d.ts +12 -0
  32. package/dist/src/controllers/star-controller.js +38 -0
  33. package/dist/src/controllers/starship-controller.d.ts +17 -0
  34. package/dist/src/controllers/starship-controller.js +58 -0
  35. package/dist/src/draw-debug-line.d.ts +9 -0
  36. package/dist/src/draw-debug-line.js +29 -0
  37. package/dist/src/entities/black-hole-factory.d.ts +15 -0
  38. package/dist/src/entities/black-hole-factory.js +23 -0
  39. package/dist/src/entities/black-hole-shapes.d.ts +9 -0
  40. package/dist/src/entities/black-hole-shapes.js +224 -0
  41. package/dist/src/entities/black-hole.d.ts +69 -0
  42. package/dist/src/entities/black-hole.js +210 -0
  43. package/dist/src/entities/clan-manager.d.ts +12 -0
  44. package/dist/src/entities/clan-manager.js +22 -0
  45. package/dist/src/entities/clans.d.ts +15 -0
  46. package/dist/src/entities/clans.js +76 -0
  47. package/dist/src/entities/comet.d.ts +27 -0
  48. package/dist/src/entities/comet.js +81 -0
  49. package/dist/src/entities/docking-point.d.ts +20 -0
  50. package/dist/src/entities/docking-point.js +22 -0
  51. package/dist/src/entities/fleet.d.ts +45 -0
  52. package/dist/src/entities/fleet.js +374 -0
  53. package/dist/src/entities/formations.d.ts +51 -0
  54. package/dist/src/entities/formations.js +340 -0
  55. package/dist/src/entities/meteor.d.ts +26 -0
  56. package/dist/src/entities/meteor.js +48 -0
  57. package/dist/src/entities/nebula.d.ts +18 -0
  58. package/dist/src/entities/nebula.js +43 -0
  59. package/dist/src/entities/orbit.d.ts +23 -0
  60. package/dist/src/entities/orbit.js +43 -0
  61. package/dist/src/entities/pulsar.d.ts +18 -0
  62. package/dist/src/entities/pulsar.js +41 -0
  63. package/dist/src/entities/ring.d.ts +13 -0
  64. package/dist/src/entities/ring.js +26 -0
  65. package/dist/src/entities/ringed-planet.d.ts +21 -0
  66. package/dist/src/entities/ringed-planet.js +68 -0
  67. package/dist/src/entities/sector-grid.d.ts +16 -0
  68. package/dist/src/entities/sector-grid.js +70 -0
  69. package/dist/src/entities/star-factory.d.ts +29 -0
  70. package/dist/src/entities/star-factory.js +47 -0
  71. package/dist/src/entities/star.d.ts +48 -0
  72. package/dist/src/entities/star.js +167 -0
  73. package/dist/src/entities/starship-classes.d.ts +0 -0
  74. package/dist/src/entities/starship-classes.js +2 -0
  75. package/dist/src/entities/starship.d.ts +91 -0
  76. package/dist/src/entities/starship.js +760 -0
  77. package/dist/src/entities/supernova.d.ts +26 -0
  78. package/dist/src/entities/supernova.js +54 -0
  79. package/dist/src/index.d.ts +20 -0
  80. package/dist/src/index.js +19 -0
  81. package/dist/src/lib/energy-stream.d.ts +5 -0
  82. package/dist/src/lib/energy-stream.js +98 -0
  83. package/dist/src/lib/quadtree.d.ts +31 -0
  84. package/dist/src/lib/quadtree.js +124 -0
  85. package/dist/src/lib/simplified-stream.d.ts +6 -0
  86. package/dist/src/lib/simplified-stream.js +19 -0
  87. package/dist/src/types.d.ts +14 -0
  88. package/dist/src/types.js +1 -0
  89. package/dist/src/ui/black-holes-panel.d.ts +2 -0
  90. package/dist/src/ui/black-holes-panel.js +76 -0
  91. package/dist/src/ui/clans-panel.d.ts +2 -0
  92. package/dist/src/ui/clans-panel.js +20 -0
  93. package/dist/src/ui/debug-panel-controller.d.ts +41 -0
  94. package/dist/src/ui/debug-panel-controller.js +285 -0
  95. package/dist/src/ui/fleets-panel.d.ts +2 -0
  96. package/dist/src/ui/fleets-panel.js +127 -0
  97. package/dist/src/ui/formations-panel.d.ts +3 -0
  98. package/dist/src/ui/formations-panel.js +129 -0
  99. package/dist/src/ui/panel-config.d.ts +26 -0
  100. package/dist/src/ui/panel-config.js +26 -0
  101. package/dist/src/ui/ships-panel.d.ts +12 -0
  102. package/dist/src/ui/ships-panel.js +61 -0
  103. package/dist/src/ui/stars-panel.d.ts +2 -0
  104. package/dist/src/ui/stars-panel.js +120 -0
  105. package/dist/src/where-stars-drift.d.ts +71 -0
  106. package/dist/src/where-stars-drift.js +440 -0
  107. package/dist/tsconfig.tsbuildinfo +1 -0
  108. package/package.json +35 -0
  109. package/src/base/bounded-body.ts +14 -0
  110. package/src/base/celestial-body.ts +20 -0
  111. package/src/base/hoverable.ts +11 -0
  112. package/src/base/massive-body.ts +14 -0
  113. package/src/config/effects.ts +15 -0
  114. package/src/config/grid.ts +11 -0
  115. package/src/config/panel.ts +26 -0
  116. package/src/config/simulation.ts +25 -0
  117. package/src/controllers/clan-controller.ts +19 -0
  118. package/src/controllers/debug-controller.ts +112 -0
  119. package/src/controllers/effects-controller.ts +86 -0
  120. package/src/controllers/hover-controller.ts +128 -0
  121. package/src/controllers/layer-controller.ts +78 -0
  122. package/src/controllers/physics-controller.ts +173 -0
  123. package/src/controllers/star-controller.ts +51 -0
  124. package/src/controllers/starship-controller.ts +76 -0
  125. package/src/draw-debug-line.ts +37 -0
  126. package/src/entities/black-hole-factory.ts +28 -0
  127. package/src/entities/black-hole-shapes.ts +276 -0
  128. package/src/entities/black-hole.ts +246 -0
  129. package/src/entities/clan-manager.ts +33 -0
  130. package/src/entities/clans.ts +98 -0
  131. package/src/entities/comet.ts +102 -0
  132. package/src/entities/docking-point.ts +34 -0
  133. package/src/entities/fleet.ts +446 -0
  134. package/src/entities/formations.ts +423 -0
  135. package/src/entities/meteor.ts +59 -0
  136. package/src/entities/nebula.ts +50 -0
  137. package/src/entities/orbit.ts +53 -0
  138. package/src/entities/pulsar.ts +64 -0
  139. package/src/entities/ring.ts +42 -0
  140. package/src/entities/ringed-planet.ts +85 -0
  141. package/src/entities/sector-grid.ts +81 -0
  142. package/src/entities/star-factory.ts +59 -0
  143. package/src/entities/star.ts +222 -0
  144. package/src/entities/starship-classes.ts +1 -0
  145. package/src/entities/starship.ts +906 -0
  146. package/src/entities/supernova.ts +75 -0
  147. package/src/index.ts +24 -0
  148. package/src/lib/energy-stream.ts +127 -0
  149. package/src/lib/quadtree.ts +159 -0
  150. package/src/lib/simplified-stream.ts +28 -0
  151. package/src/types.ts +16 -0
  152. package/src/ui/black-holes-panel.ts +91 -0
  153. package/src/ui/clans-panel.ts +27 -0
  154. package/src/ui/debug-panel-controller.ts +339 -0
  155. package/src/ui/fleets-panel.ts +153 -0
  156. package/src/ui/formations-panel.ts +155 -0
  157. package/src/ui/panel-config.ts +26 -0
  158. package/src/ui/ships-panel.ts +85 -0
  159. package/src/ui/stars-panel.ts +146 -0
  160. package/src/where-stars-drift.ts +542 -0
@@ -0,0 +1,285 @@
1
+ /**
2
+ * @fileoverview Defines the DebugPanelController for rendering the debug catalog panel.
3
+ */
4
+ import { DEBUG_CONFIG } from '../config/simulation';
5
+ import { SHAPES, SHIP_CLASS_TEMPLATES } from '../entities/starship';
6
+ import { PANEL_CONFIG } from './panel-config';
7
+ import { drawStarshipCatalog } from './ships-panel';
8
+ import { drawFormationCatalog } from './formations-panel';
9
+ import { drawClanCatalog } from './clans-panel';
10
+ import { drawFleetCatalog } from './fleets-panel';
11
+ import { drawStarCatalog } from './stars-panel';
12
+ import { drawBlackHoleCatalog } from './black-holes-panel';
13
+ import { StarFactory } from '../entities/star-factory';
14
+ import { BlackHoleFactory } from '../entities/black-hole-factory';
15
+ export class DebugPanelController {
16
+ constructor(ctx, allFormations) {
17
+ this.activeTab = null;
18
+ this.tabs = [];
19
+ this.shipCatalog = { 'Military': {}, 'Non-Military': {} };
20
+ this.formationCatalog = {};
21
+ this.starCatalog = [];
22
+ this.blackHoleCatalog = [];
23
+ this.panelY = 0;
24
+ this.panelHeight = 0;
25
+ this.scrollTop = 0;
26
+ this.contentHeight = 0;
27
+ // State for scrollbar dragging
28
+ this.isDraggingScrollbar = false;
29
+ this.dragStartY = 0;
30
+ this.dragStartScrollTop = 0;
31
+ this.scrollbarThumb = null;
32
+ this.scrollbarTrack = null;
33
+ // New state for formation ship visualization
34
+ this.showFormationShips = false;
35
+ this.ctx = ctx;
36
+ this.starFactory = new StarFactory();
37
+ this.blackHoleFactory = new BlackHoleFactory();
38
+ this.initializeTabs();
39
+ this.cacheShipClasses();
40
+ this.cacheFormations(allFormations);
41
+ this.cacheStarVariations();
42
+ this.cacheBlackHoleTypes();
43
+ this.calculatePanelDimensions();
44
+ }
45
+ initializeTabs() {
46
+ if (DEBUG_CONFIG.SHOW_STARSHIP_CATALOG)
47
+ this.tabs.push('Ships');
48
+ if (DEBUG_CONFIG.SHOW_FORMATION_CATALOG)
49
+ this.tabs.push('Formations');
50
+ if (DEBUG_CONFIG.SHOW_CLAN_CATALOG)
51
+ this.tabs.push('Clans');
52
+ if (DEBUG_CONFIG.SHOW_FLEET_CATALOG)
53
+ this.tabs.push('Fleets');
54
+ if (DEBUG_CONFIG.SHOW_STAR_CATALOG)
55
+ this.tabs.push('Stars');
56
+ if (DEBUG_CONFIG.SHOW_BLACK_HOLE_CATALOG)
57
+ this.tabs.push('Holes');
58
+ if (this.tabs.length > 0) {
59
+ this.activeTab = this.tabs[0];
60
+ }
61
+ }
62
+ cacheShipClasses() {
63
+ const militaryCategories = ['Capital', 'Cruiser', 'Escort', 'Support', 'Small Craft'];
64
+ Object.values(SHIP_CLASS_TEMPLATES).forEach(template => {
65
+ const groupKey = militaryCategories.includes(template.category) ? 'Military' : 'Non-Military';
66
+ if (!this.shipCatalog[groupKey][template.category]) {
67
+ this.shipCatalog[groupKey][template.category] = [];
68
+ }
69
+ this.shipCatalog[groupKey][template.category].push({
70
+ template,
71
+ variants: SHAPES[template.shapeKey] || [],
72
+ });
73
+ });
74
+ }
75
+ cacheFormations(allFormations) {
76
+ allFormations.forEach(formation => {
77
+ const categoryKey = formation.category;
78
+ const typeKey = formation.type;
79
+ if (!this.formationCatalog[categoryKey]) {
80
+ this.formationCatalog[categoryKey] = {};
81
+ }
82
+ if (!this.formationCatalog[categoryKey][typeKey]) {
83
+ this.formationCatalog[categoryKey][typeKey] = [];
84
+ }
85
+ this.formationCatalog[categoryKey][typeKey].push(formation);
86
+ });
87
+ }
88
+ cacheStarVariations() {
89
+ // Standard Stars
90
+ const starWithAura = this.starFactory.createStar(0, 0);
91
+ starWithAura.hasAura = true;
92
+ this.starCatalog.push(starWithAura);
93
+ this.starCatalog.push(this.starFactory.createStar(0, 0, { radius: 1.5 }));
94
+ this.starCatalog.push(this.starFactory.createStar(0, 0, { radius: 2 }));
95
+ // Pulsars
96
+ const pulsarWithAura = this.starFactory.createPulsar(0, 0);
97
+ pulsarWithAura.hasAura = true;
98
+ this.starCatalog.push(pulsarWithAura);
99
+ this.starCatalog.push(this.starFactory.createPulsar(0, 0));
100
+ this.starCatalog.push(this.starFactory.createPulsar(0, 0));
101
+ // Ringed Planets
102
+ const planetWithAura = this.starFactory.createRingedPlanet(0, 0, { ringCount: 1 });
103
+ planetWithAura.hasAura = true;
104
+ this.starCatalog.push(planetWithAura);
105
+ this.starCatalog.push(this.starFactory.createRingedPlanet(0, 0, { ringCount: 2 }));
106
+ this.starCatalog.push(this.starFactory.createRingedPlanet(0, 0, { ringCount: 3 }));
107
+ }
108
+ cacheBlackHoleTypes() {
109
+ const blackHoleTypes = this.blackHoleFactory.getAllBlackHoleTypes();
110
+ this.blackHoleCatalog = blackHoleTypes.map(type => this.blackHoleFactory.createBlackHole(0, 0, type));
111
+ }
112
+ calculatePanelDimensions() {
113
+ this.panelHeight = this.ctx.canvas.height * PANEL_CONFIG.HEIGHT_PERCENT;
114
+ this.panelY = (this.ctx.canvas.height - this.panelHeight) / 2;
115
+ }
116
+ isMouseOver(x, y) {
117
+ return x >= PANEL_CONFIG.X && x <= PANEL_CONFIG.X + PANEL_CONFIG.WIDTH &&
118
+ y >= this.panelY && y <= this.panelY + this.panelHeight;
119
+ }
120
+ handleScroll(deltaY) {
121
+ this.scrollTop += deltaY > 0 ? PANEL_CONFIG.SCROLL_SPEED : -PANEL_CONFIG.SCROLL_SPEED;
122
+ const contentAreaHeight = this.panelHeight - (PANEL_CONFIG.HEADER_HEIGHT + PANEL_CONFIG.TAB_HEIGHT);
123
+ const maxScroll = Math.max(0, this.contentHeight - contentAreaHeight);
124
+ this.scrollTop = Math.max(0, Math.min(this.scrollTop, maxScroll));
125
+ }
126
+ isDragging() {
127
+ return this.isDraggingScrollbar;
128
+ }
129
+ handleMouseDown(x, y) {
130
+ if (!this.activeTab || !this.isMouseOver(x, y))
131
+ return;
132
+ // Check for scrollbar thumb click
133
+ const contentAreaHeight = this.panelHeight - (PANEL_CONFIG.HEADER_HEIGHT + PANEL_CONFIG.TAB_HEIGHT);
134
+ if (this.scrollbarTrack && this.scrollbarThumb && this.contentHeight > contentAreaHeight) {
135
+ const track = this.scrollbarTrack;
136
+ const thumb = this.scrollbarThumb;
137
+ if (x >= track.x && x <= track.x + track.width && y >= thumb.y && y <= thumb.y + thumb.height) {
138
+ this.isDraggingScrollbar = true;
139
+ this.dragStartY = y;
140
+ this.dragStartScrollTop = this.scrollTop;
141
+ return;
142
+ }
143
+ }
144
+ // Handle tab clicks
145
+ const tabY = this.panelY + PANEL_CONFIG.HEADER_HEIGHT;
146
+ if (y >= tabY && y <= tabY + PANEL_CONFIG.TAB_HEIGHT) {
147
+ const tabWidth = PANEL_CONFIG.WIDTH / this.tabs.length;
148
+ for (let i = 0; i < this.tabs.length; i++) {
149
+ const tabX = PANEL_CONFIG.X + i * tabWidth;
150
+ if (x >= tabX && x < tabX + tabWidth) {
151
+ if (this.activeTab !== this.tabs[i]) {
152
+ this.activeTab = this.tabs[i];
153
+ this.scrollTop = 0; // Reset scroll on tab change
154
+ }
155
+ return;
156
+ }
157
+ }
158
+ }
159
+ // Handle toggle clicks within panels
160
+ const contentY = this.panelY + PANEL_CONFIG.HEADER_HEIGHT + PANEL_CONFIG.TAB_HEIGHT;
161
+ const contentClickY = y - (contentY - this.scrollTop);
162
+ if (this.activeTab === 'Formations') {
163
+ const button = PANEL_CONFIG.TOGGLE_BUTTON;
164
+ if (contentClickY >= button.y && contentClickY <= button.y + button.h &&
165
+ x >= PANEL_CONFIG.X + button.x && x <= PANEL_CONFIG.X + button.x + button.w) {
166
+ this.showFormationShips = !this.showFormationShips;
167
+ }
168
+ }
169
+ }
170
+ handleMouseMove(x, y) {
171
+ if (!this.isDraggingScrollbar)
172
+ return;
173
+ const contentAreaHeight = this.panelHeight - (PANEL_CONFIG.HEADER_HEIGHT + PANEL_CONFIG.TAB_HEIGHT);
174
+ const maxScrollTop = Math.max(0, this.contentHeight - contentAreaHeight);
175
+ if (maxScrollTop <= 0)
176
+ return;
177
+ const thumbHeight = this.scrollbarThumb ? this.scrollbarThumb.height : 20;
178
+ const draggableAreaHeight = contentAreaHeight - thumbHeight;
179
+ if (draggableAreaHeight <= 0)
180
+ return;
181
+ const scrollDelta = y - this.dragStartY;
182
+ const scrollTopDelta = (scrollDelta / draggableAreaHeight) * maxScrollTop;
183
+ const newScrollTop = this.dragStartScrollTop + scrollTopDelta;
184
+ this.scrollTop = Math.max(0, Math.min(newScrollTop, maxScrollTop));
185
+ }
186
+ handleMouseUp() {
187
+ this.isDraggingScrollbar = false;
188
+ }
189
+ handleResize() {
190
+ this.calculatePanelDimensions();
191
+ }
192
+ draw(data, pulseTime, frameCount) {
193
+ if (!this.activeTab)
194
+ return;
195
+ this.ctx.save();
196
+ this.drawPanelBase();
197
+ this.drawTabs();
198
+ const contentY = this.panelY + PANEL_CONFIG.HEADER_HEIGHT + PANEL_CONFIG.TAB_HEIGHT;
199
+ const contentAreaHeight = this.panelHeight - (PANEL_CONFIG.HEADER_HEIGHT + PANEL_CONFIG.TAB_HEIGHT);
200
+ this.ctx.save();
201
+ this.ctx.beginPath();
202
+ this.ctx.rect(PANEL_CONFIG.X, contentY, PANEL_CONFIG.WIDTH, contentAreaHeight);
203
+ this.ctx.clip();
204
+ this.ctx.translate(0, -this.scrollTop);
205
+ switch (this.activeTab) {
206
+ case 'Ships':
207
+ this.contentHeight = drawStarshipCatalog(this.ctx, contentY, this.shipCatalog);
208
+ break;
209
+ case 'Formations':
210
+ this.contentHeight = drawFormationCatalog(this.ctx, contentY, this.formationCatalog, this.showFormationShips);
211
+ break;
212
+ case 'Clans':
213
+ this.contentHeight = drawClanCatalog(this.ctx, contentY, data.clans);
214
+ break;
215
+ case 'Fleets':
216
+ this.contentHeight = drawFleetCatalog(this.ctx, contentY, data.fleets);
217
+ break;
218
+ case 'Stars':
219
+ this.contentHeight = drawStarCatalog(this.ctx, contentY, this.starCatalog, pulseTime);
220
+ break;
221
+ case 'Holes':
222
+ this.contentHeight = drawBlackHoleCatalog(this.ctx, contentY, this.blackHoleCatalog, pulseTime, frameCount);
223
+ break;
224
+ }
225
+ this.ctx.restore(); // Restore from translate and clip
226
+ this.drawScrollbar();
227
+ this.ctx.restore(); // Restore from initial save
228
+ }
229
+ drawPanelBase() {
230
+ this.ctx.fillStyle = PANEL_CONFIG.BG_COLOR;
231
+ this.ctx.strokeStyle = PANEL_CONFIG.BORDER_COLOR;
232
+ this.ctx.lineWidth = 1;
233
+ this.ctx.fillRect(PANEL_CONFIG.X, this.panelY, PANEL_CONFIG.WIDTH, this.panelHeight);
234
+ this.ctx.strokeRect(PANEL_CONFIG.X, this.panelY, PANEL_CONFIG.WIDTH, this.panelHeight);
235
+ this.ctx.fillStyle = PANEL_CONFIG.TEXT_COLOR;
236
+ this.ctx.font = PANEL_CONFIG.TITLE_FONT;
237
+ this.ctx.textAlign = 'left';
238
+ this.ctx.textBaseline = 'middle';
239
+ this.ctx.fillText('Debug Catalog', PANEL_CONFIG.X + PANEL_CONFIG.PADDING, this.panelY + PANEL_CONFIG.HEADER_HEIGHT / 2);
240
+ }
241
+ drawTabs() {
242
+ const tabY = this.panelY + PANEL_CONFIG.HEADER_HEIGHT;
243
+ const tabWidth = PANEL_CONFIG.WIDTH / this.tabs.length;
244
+ this.ctx.font = PANEL_CONFIG.TAB_FONT;
245
+ this.ctx.textAlign = 'center';
246
+ this.tabs.forEach((tab, i) => {
247
+ const tabX = PANEL_CONFIG.X + i * tabWidth;
248
+ if (tab === this.activeTab) {
249
+ this.ctx.fillStyle = 'rgba(255, 255, 255, 0.1)';
250
+ this.ctx.fillRect(tabX, tabY, tabWidth, PANEL_CONFIG.TAB_HEIGHT);
251
+ this.ctx.fillStyle = '#fff';
252
+ }
253
+ else {
254
+ this.ctx.fillStyle = PANEL_CONFIG.ACCENT_COLOR;
255
+ }
256
+ this.ctx.fillText(tab, tabX + tabWidth / 2, tabY + PANEL_CONFIG.TAB_HEIGHT / 2);
257
+ });
258
+ this.ctx.beginPath();
259
+ this.ctx.moveTo(PANEL_CONFIG.X, tabY + PANEL_CONFIG.TAB_HEIGHT);
260
+ this.ctx.lineTo(PANEL_CONFIG.X + PANEL_CONFIG.WIDTH, tabY + PANEL_CONFIG.TAB_HEIGHT);
261
+ this.ctx.stroke();
262
+ }
263
+ drawScrollbar() {
264
+ const contentY = this.panelY + PANEL_CONFIG.HEADER_HEIGHT + PANEL_CONFIG.TAB_HEIGHT;
265
+ const contentAreaHeight = this.panelHeight - (PANEL_CONFIG.HEADER_HEIGHT + PANEL_CONFIG.TAB_HEIGHT);
266
+ if (this.contentHeight <= contentAreaHeight) {
267
+ this.scrollbarThumb = null;
268
+ this.scrollbarTrack = null;
269
+ return; // No scrollbar needed
270
+ }
271
+ const scrollbarWidth = 8;
272
+ const scrollbarX = PANEL_CONFIG.X + PANEL_CONFIG.WIDTH - scrollbarWidth - 2;
273
+ this.scrollbarTrack = { x: scrollbarX, y: contentY, width: scrollbarWidth, height: contentAreaHeight };
274
+ // Draw scrollbar track
275
+ this.ctx.fillStyle = 'rgba(255, 255, 255, 0.1)';
276
+ this.ctx.fillRect(scrollbarX, contentY, scrollbarWidth, contentAreaHeight);
277
+ // Draw scrollbar thumb
278
+ const thumbHeight = Math.max(20, contentAreaHeight * (contentAreaHeight / this.contentHeight));
279
+ const maxScrollTop = this.contentHeight - contentAreaHeight;
280
+ const thumbY = contentY + (this.scrollTop / maxScrollTop) * (contentAreaHeight - thumbHeight);
281
+ this.scrollbarThumb = { y: thumbY, height: thumbHeight };
282
+ this.ctx.fillStyle = this.isDraggingScrollbar ? 'rgba(255, 255, 255, 0.6)' : 'rgba(255, 255, 255, 0.4)';
283
+ this.ctx.fillRect(scrollbarX, thumbY, scrollbarWidth, thumbHeight);
284
+ }
285
+ }
@@ -0,0 +1,2 @@
1
+ import type { Fleet } from '../entities/fleet';
2
+ export declare function drawFleetCatalog(ctx: CanvasRenderingContext2D, startY: number, fleets: Fleet[]): number;
@@ -0,0 +1,127 @@
1
+ import { PANEL_CONFIG } from './panel-config';
2
+ import { FLEET_CONFIG } from '../entities/fleet';
3
+ function drawFleetDetails(ctx, fleet, x, y) {
4
+ var _a, _b;
5
+ let currentY = y;
6
+ const lineHeight = 14;
7
+ ctx.font = 'bold 12px "Roboto Mono", monospace';
8
+ ctx.fillStyle = '#fff';
9
+ ctx.textAlign = 'left';
10
+ ctx.textBaseline = 'top';
11
+ ctx.fillText(fleet.name, x, currentY);
12
+ currentY += lineHeight * 1.5;
13
+ const details = [
14
+ { label: 'Clan', value: ((_a = fleet.clan) === null || _a === void 0 ? void 0 : _a.name) || 'Unknown' },
15
+ { label: 'State', value: fleet.state },
16
+ { label: 'Formation', value: ((_b = fleet.formation) === null || _b === void 0 ? void 0 : _b.name) || 'None' },
17
+ { label: 'Ships', value: fleet.members.length.toString() },
18
+ ];
19
+ ctx.font = '10px "Roboto Mono", monospace';
20
+ details.forEach(detail => {
21
+ ctx.fillStyle = PANEL_CONFIG.ACCENT_COLOR;
22
+ ctx.fillText(`${detail.label}:`, x, currentY);
23
+ ctx.fillStyle = PANEL_CONFIG.TEXT_COLOR;
24
+ ctx.fillText(detail.value, x + 70, currentY);
25
+ currentY += lineHeight;
26
+ });
27
+ currentY += lineHeight * 0.5;
28
+ ctx.fillStyle = PANEL_CONFIG.ACCENT_COLOR;
29
+ ctx.fillText('Roster:', x, currentY);
30
+ currentY += lineHeight;
31
+ fleet.members.forEach(ship => {
32
+ if (ship.shipClass) {
33
+ ctx.fillStyle = ship.shipClass.color;
34
+ ctx.fillText(`\u2022 ${ship.shipClass.name} (${ship.name})`, x + 5, currentY);
35
+ }
36
+ else {
37
+ ctx.fillStyle = '#ff00ff'; // Fallback color
38
+ ctx.fillText(`\u2022 Unknown Ship (${ship.name})`, x + 5, currentY);
39
+ }
40
+ currentY += lineHeight;
41
+ });
42
+ return currentY - y; // Return the height of this section
43
+ }
44
+ function drawFleetVisualization(ctx, fleet, centerX, centerY) {
45
+ if (!fleet.formation)
46
+ return;
47
+ const positions = fleet.formation.getPosition(fleet.members);
48
+ const maxOffset = positions.reduce((max, offset) => {
49
+ const dist = Math.sqrt(offset.x * offset.x + offset.y * offset.y);
50
+ return Math.max(max, dist);
51
+ }, 0);
52
+ // Scale the formation to fit nicely in the panel
53
+ const vizRadius = Math.min(40, PANEL_CONFIG.WIDTH / 4);
54
+ const scaleFactor = vizRadius / (maxOffset + 5);
55
+ ctx.save();
56
+ ctx.translate(centerX, centerY);
57
+ // Draw bounding ring
58
+ ctx.beginPath();
59
+ ctx.strokeStyle = FLEET_CONFIG.RING_COLOR;
60
+ ctx.lineWidth = FLEET_CONFIG.RING_WIDTH;
61
+ ctx.setLineDash([2, 3]);
62
+ ctx.arc(0, 0, vizRadius, 0, Math.PI * 2);
63
+ ctx.stroke();
64
+ ctx.setLineDash([]);
65
+ // Draw ships
66
+ fleet.members.forEach((ship, i) => {
67
+ if (i >= positions.length || !ship.shipClass)
68
+ return;
69
+ ctx.save();
70
+ const pos = positions[i];
71
+ // Here we use the fleet's current rotation to orient the ships
72
+ const rotatedX = pos.x * Math.cos(fleet.rotation) - pos.y * Math.sin(fleet.rotation);
73
+ const rotatedY = pos.x * Math.sin(fleet.rotation) + pos.y * Math.cos(fleet.rotation);
74
+ ctx.translate(rotatedX * scaleFactor, rotatedY * scaleFactor);
75
+ ctx.rotate(fleet.rotation + Math.PI / 2); // Ships face "up" relative to fleet direction
76
+ ctx.beginPath();
77
+ ship.shipClass.shape(ctx, ship.shipClass.radius * scaleFactor);
78
+ ctx.fillStyle = ship.shipClass.color;
79
+ if (fleet.clan) {
80
+ ctx.strokeStyle = fleet.clan.color;
81
+ }
82
+ else {
83
+ ctx.strokeStyle = '#FFFFFF';
84
+ }
85
+ ctx.lineWidth = 0.5;
86
+ ctx.fill();
87
+ ctx.stroke();
88
+ ctx.restore();
89
+ });
90
+ ctx.restore();
91
+ }
92
+ export function drawFleetCatalog(ctx, startY, fleets) {
93
+ let currentY = startY + PANEL_CONFIG.PADDING;
94
+ ctx.font = PANEL_CONFIG.GROUP_FONT;
95
+ ctx.fillStyle = PANEL_CONFIG.TEXT_COLOR;
96
+ ctx.textAlign = 'left';
97
+ ctx.textBaseline = 'top';
98
+ ctx.fillText('Active Fleets', PANEL_CONFIG.X + PANEL_CONFIG.PADDING, currentY);
99
+ currentY += PANEL_CONFIG.GROUP_HEADER_HEIGHT;
100
+ if (fleets.length === 0) {
101
+ ctx.font = '11px "Roboto Mono", monospace';
102
+ ctx.fillStyle = PANEL_CONFIG.ACCENT_COLOR;
103
+ ctx.fillText('No active fleets in simulation.', PANEL_CONFIG.X + PANEL_CONFIG.PADDING + 10, currentY);
104
+ currentY += 20;
105
+ return currentY - startY;
106
+ }
107
+ fleets.forEach((fleet, i) => {
108
+ const rowHeight = 150 + fleet.members.length * 12;
109
+ const vizCenterX = PANEL_CONFIG.X + PANEL_CONFIG.PADDING + 45;
110
+ const vizCenterY = currentY + 70;
111
+ drawFleetVisualization(ctx, fleet, vizCenterX, vizCenterY);
112
+ const detailsX = vizCenterX + 60;
113
+ drawFleetDetails(ctx, fleet, detailsX, currentY);
114
+ currentY += rowHeight;
115
+ // Draw separator
116
+ if (i < fleets.length - 1) {
117
+ ctx.beginPath();
118
+ ctx.moveTo(PANEL_CONFIG.X + PANEL_CONFIG.PADDING, currentY);
119
+ ctx.lineTo(PANEL_CONFIG.X + PANEL_CONFIG.WIDTH - PANEL_CONFIG.PADDING, currentY);
120
+ ctx.strokeStyle = PANEL_CONFIG.BORDER_COLOR;
121
+ ctx.lineWidth = 0.5;
122
+ ctx.stroke();
123
+ currentY += PANEL_CONFIG.PADDING;
124
+ }
125
+ });
126
+ return currentY - startY;
127
+ }
@@ -0,0 +1,3 @@
1
+ import type { Formation } from '../entities/formations';
2
+ export type FormationCatalog = Record<string, Record<string, Formation[]>>;
3
+ export declare function drawFormationCatalog(ctx: CanvasRenderingContext2D, startY: number, formationCatalog: FormationCatalog, showShips: boolean): number;
@@ -0,0 +1,129 @@
1
+ import { SHAPES, SHIP_CLASS_TEMPLATES } from '../entities/starship';
2
+ import { FLEET_CONFIG } from '../entities/fleet';
3
+ import { PANEL_CONFIG } from './panel-config';
4
+ const createMockShip = (shipClassName) => {
5
+ const template = Object.values(SHIP_CLASS_TEMPLATES).find(t => t.name === shipClassName);
6
+ if (!template) {
7
+ return { shipClass: { name: shipClassName, radius: 2, color: '#ff00ff', shape: SHAPES.UTILITY[0] } };
8
+ }
9
+ const shapeVariants = SHAPES[template.shapeKey];
10
+ const shape = shapeVariants[0]; // Use the first variant for consistency
11
+ return {
12
+ shipClass: Object.assign(Object.assign({}, template), { shape, accentColor: '#fff' }),
13
+ };
14
+ };
15
+ export function drawFormationCatalog(ctx, startY, formationCatalog, showShips) {
16
+ let currentY = startY + PANEL_CONFIG.PADDING;
17
+ // Draw the toggle button
18
+ const button = PANEL_CONFIG.TOGGLE_BUTTON;
19
+ ctx.strokeStyle = showShips ? '#fff' : PANEL_CONFIG.BORDER_COLOR;
20
+ ctx.fillStyle = showShips ? 'rgba(255, 255, 255, 0.1)' : 'transparent';
21
+ ctx.lineWidth = 1;
22
+ ctx.fillRect(button.x, currentY + button.y, button.w, button.h);
23
+ ctx.strokeRect(button.x, currentY + button.y, button.w, button.h);
24
+ ctx.fillStyle = PANEL_CONFIG.TEXT_COLOR;
25
+ ctx.font = '11px "Roboto Mono", monospace';
26
+ ctx.textAlign = 'left';
27
+ ctx.textBaseline = 'middle';
28
+ ctx.fillText(`Show Ships: ${showShips ? 'ON' : 'OFF'}`, button.x + 5, currentY + button.y + button.h / 2);
29
+ currentY += button.h + PANEL_CONFIG.PADDING * 2;
30
+ Object.keys(formationCatalog).forEach(category => {
31
+ ctx.font = PANEL_CONFIG.GROUP_FONT;
32
+ ctx.fillStyle = PANEL_CONFIG.TEXT_COLOR;
33
+ ctx.textAlign = 'left';
34
+ ctx.textBaseline = 'top';
35
+ ctx.fillText(category, PANEL_CONFIG.X + PANEL_CONFIG.PADDING, currentY);
36
+ currentY += PANEL_CONFIG.GROUP_HEADER_HEIGHT;
37
+ const types = formationCatalog[category];
38
+ Object.keys(types).forEach(type => {
39
+ ctx.font = PANEL_CONFIG.ROW_FONT;
40
+ ctx.fillStyle = PANEL_CONFIG.ACCENT_COLOR;
41
+ ctx.fillText(type, PANEL_CONFIG.X + PANEL_CONFIG.PADDING + 10, currentY);
42
+ currentY += PANEL_CONFIG.ROW_HEIGHT * 0.8;
43
+ const formationList = types[type];
44
+ formationList.forEach(formation => {
45
+ ctx.fillStyle = PANEL_CONFIG.TEXT_COLOR;
46
+ ctx.fillText(formation.name, PANEL_CONFIG.X + PANEL_CONFIG.PADDING + 20, currentY);
47
+ currentY += 15;
48
+ // Draw description
49
+ ctx.font = '11px "Roboto Mono", monospace';
50
+ ctx.fillStyle = PANEL_CONFIG.ACCENT_COLOR;
51
+ const words = formation.description.split(' ');
52
+ let line = '';
53
+ for (let n = 0; n < words.length; n++) {
54
+ const testLine = line + words[n] + ' ';
55
+ const metrics = ctx.measureText(testLine);
56
+ if (metrics.width > PANEL_CONFIG.WIDTH - 60 && n > 0) {
57
+ ctx.fillText(line, PANEL_CONFIG.X + PANEL_CONFIG.PADDING + 20, currentY);
58
+ line = words[n] + ' ';
59
+ currentY += 12;
60
+ }
61
+ else {
62
+ line = testLine;
63
+ }
64
+ }
65
+ ctx.fillText(line, PANEL_CONFIG.X + PANEL_CONFIG.PADDING + 20, currentY);
66
+ currentY += 15;
67
+ // Draw formation visualization
68
+ let mockShips;
69
+ if (formation.type === 'Strict') {
70
+ mockShips = formation.composition
71
+ .map(c => Array.from({ length: c.count }, () => createMockShip(c.shipClassName)))
72
+ .flat();
73
+ }
74
+ else {
75
+ // For free formations, just use a set number of generic ships
76
+ mockShips = Array.from({ length: 7 }, () => createMockShip(''));
77
+ }
78
+ const positions = formation.getPosition(mockShips);
79
+ const maxOffset = positions.reduce((max, offset) => {
80
+ const dist = Math.sqrt(offset.x * offset.x + offset.y * offset.y);
81
+ return Math.max(max, dist);
82
+ }, 0);
83
+ const formationRadius = maxOffset + 5; // Add some padding
84
+ ctx.save();
85
+ ctx.translate(PANEL_CONFIG.X + PANEL_CONFIG.WIDTH / 2, currentY + formationRadius + 10);
86
+ // Draw dashed circle
87
+ ctx.beginPath();
88
+ ctx.strokeStyle = FLEET_CONFIG.RING_COLOR;
89
+ ctx.lineWidth = FLEET_CONFIG.RING_WIDTH;
90
+ ctx.setLineDash([2, 3]);
91
+ ctx.arc(0, 0, formationRadius, 0, Math.PI * 2);
92
+ ctx.stroke();
93
+ ctx.setLineDash([]);
94
+ // Draw ships first (if enabled)
95
+ if (showShips && formation.type === 'Strict') {
96
+ mockShips.forEach((ship, i) => {
97
+ if (!ship.shipClass || i >= positions.length)
98
+ return;
99
+ ctx.save();
100
+ ctx.translate(positions[i].x, positions[i].y);
101
+ // Ships face "up" by default, so no extra rotation needed unless the formation dictates it
102
+ ctx.rotate(Math.PI / 2);
103
+ ctx.beginPath();
104
+ ship.shipClass.shape(ctx, ship.shipClass.radius * PANEL_CONFIG.SHIP_RENDER_SCALE);
105
+ ctx.fillStyle = ship.shipClass.color;
106
+ ctx.strokeStyle = '#fff';
107
+ ctx.lineWidth = 0.5;
108
+ ctx.fill();
109
+ ctx.stroke();
110
+ ctx.restore();
111
+ });
112
+ }
113
+ // Draw dots on top
114
+ ctx.fillStyle = 'red';
115
+ positions.forEach(pos => {
116
+ ctx.beginPath();
117
+ ctx.arc(pos.x, pos.y, 1.5, 0, Math.PI * 2);
118
+ ctx.fill();
119
+ });
120
+ ctx.restore();
121
+ const totalFormationHeight = formationRadius * 2 + 20;
122
+ currentY += Math.max(30, totalFormationHeight); // Add spacing for next item
123
+ ctx.font = PANEL_CONFIG.ROW_FONT; // Reset font
124
+ });
125
+ currentY += PANEL_CONFIG.ROW_HEIGHT / 2;
126
+ });
127
+ });
128
+ return currentY - startY;
129
+ }
@@ -0,0 +1,26 @@
1
+ export declare const PANEL_CONFIG: {
2
+ X: number;
3
+ WIDTH: number;
4
+ HEIGHT_PERCENT: number;
5
+ HEADER_HEIGHT: number;
6
+ TAB_HEIGHT: number;
7
+ ROW_HEIGHT: number;
8
+ GROUP_HEADER_HEIGHT: number;
9
+ PADDING: number;
10
+ BG_COLOR: string;
11
+ BORDER_COLOR: string;
12
+ TEXT_COLOR: string;
13
+ ACCENT_COLOR: string;
14
+ TITLE_FONT: string;
15
+ TAB_FONT: string;
16
+ ROW_FONT: string;
17
+ GROUP_FONT: string;
18
+ SHIP_RENDER_SCALE: number;
19
+ SCROLL_SPEED: number;
20
+ TOGGLE_BUTTON: {
21
+ x: number;
22
+ y: number;
23
+ w: number;
24
+ h: number;
25
+ };
26
+ };
@@ -0,0 +1,26 @@
1
+ export const PANEL_CONFIG = {
2
+ X: 10,
3
+ WIDTH: 350,
4
+ HEIGHT_PERCENT: 0.5,
5
+ HEADER_HEIGHT: 40,
6
+ TAB_HEIGHT: 30,
7
+ ROW_HEIGHT: 25,
8
+ GROUP_HEADER_HEIGHT: 20,
9
+ PADDING: 10,
10
+ BG_COLOR: 'rgba(0, 0, 0, 0.75)',
11
+ BORDER_COLOR: '#555',
12
+ TEXT_COLOR: '#ddd',
13
+ ACCENT_COLOR: '#aaa',
14
+ TITLE_FONT: '14px "Roboto Mono", monospace',
15
+ TAB_FONT: '11px "Roboto Mono", monospace',
16
+ ROW_FONT: '12px "Roboto Mono", monospace',
17
+ GROUP_FONT: 'bold 12px "Roboto Mono", monospace',
18
+ SHIP_RENDER_SCALE: 1,
19
+ SCROLL_SPEED: 20,
20
+ TOGGLE_BUTTON: {
21
+ x: 15,
22
+ y: 5,
23
+ w: 120,
24
+ h: 22,
25
+ },
26
+ };
@@ -0,0 +1,12 @@
1
+ import type { ShipClass } from '../entities/starship';
2
+ export type ShipCatalogEntry = {
3
+ template: Omit<ShipClass, 'shape' | 'accentColor'> & {
4
+ shapeKey: string;
5
+ };
6
+ variants: ((ctx: CanvasRenderingContext2D, r: number) => void)[];
7
+ };
8
+ export type ShipCatalog = {
9
+ 'Military': Record<string, ShipCatalogEntry[]>;
10
+ 'Non-Military': Record<string, ShipCatalogEntry[]>;
11
+ };
12
+ export declare function drawStarshipCatalog(ctx: CanvasRenderingContext2D, startY: number, shipCatalog: ShipCatalog): number;