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