@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,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Defines the abstract base class for all celestial bodies that have mass.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { BoundedBody } from './bounded-body';
|
|
6
|
+
|
|
7
|
+
export abstract class MassiveBody extends BoundedBody {
|
|
8
|
+
mass: number;
|
|
9
|
+
|
|
10
|
+
constructor(x: number, y: number, boundingRadius: number, mass: number) {
|
|
11
|
+
super(x, y, boundingRadius);
|
|
12
|
+
this.mass = mass;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Centralized configuration for visual effects in the simulation.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const EFFECTS_CONFIG = {
|
|
6
|
+
/**
|
|
7
|
+
* If true, renders the energy streams flowing between two interacting black holes.
|
|
8
|
+
*/
|
|
9
|
+
RENDER_INTER_HOLE_STREAMS: true,
|
|
10
|
+
/**
|
|
11
|
+
* If true, renders the energy streams from stars that are being consumed by a black hole.
|
|
12
|
+
* This is computationally expensive and can be disabled for performance.
|
|
13
|
+
*/
|
|
14
|
+
RENDER_STAR_TO_HOLE_STREAMS: true,
|
|
15
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Centralized configuration for the sector grid.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const GRID_CONFIG = {
|
|
6
|
+
SECTOR_SIZE: 360,
|
|
7
|
+
LINE_COLOR: 'rgba(100, 120, 150, 0.2)',
|
|
8
|
+
LINE_WIDTH: 0.5,
|
|
9
|
+
FONT: '8px "Roboto Mono", monospace',
|
|
10
|
+
FONT_COLOR: 'rgba(100, 120, 150, 0.3)',
|
|
11
|
+
};
|
|
@@ -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,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Centralized configuration for the night sky simulation.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// --- General Simulation ---
|
|
6
|
+
export const SIMULATION_CONFIG = {
|
|
7
|
+
STARS_COUNT: 468,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// --- Debugging ---
|
|
11
|
+
export const DEBUG_CONFIG = {
|
|
12
|
+
SHOW_DEBUG_INFO: true,
|
|
13
|
+
SHOW_ENTITY_HOVER_INFO: true, // Shows a detailed info panel on hover for individual entities
|
|
14
|
+
SHOW_STARSHIP_TARGET_LINE: true,
|
|
15
|
+
SHOW_INTER_HOLE_DISTANCE: true,
|
|
16
|
+
SHOW_FORMATION_TARGET_DOTS: false,
|
|
17
|
+
SHOW_CATALOG_PANEL: true,
|
|
18
|
+
SHOW_STARSHIP_CATALOG: true,
|
|
19
|
+
SHOW_FORMATION_CATALOG: true,
|
|
20
|
+
SHOW_CLAN_CATALOG: true,
|
|
21
|
+
SHOW_FLEET_CATALOG: true,
|
|
22
|
+
SHOW_STAR_CATALOG: true,
|
|
23
|
+
SHOW_BLACK_HOLE_CATALOG: true,
|
|
24
|
+
SHOW_QUADTREE: false,
|
|
25
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
import { ClanManager } from '../entities/clan-manager';
|
|
3
|
+
import type { Clan } from '../entities/clans';
|
|
4
|
+
|
|
5
|
+
export class ClanController {
|
|
6
|
+
private clanManager: ClanManager;
|
|
7
|
+
|
|
8
|
+
constructor() {
|
|
9
|
+
this.clanManager = new ClanManager();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
public getNextClan(): Clan {
|
|
13
|
+
return this.clanManager.getClanForNewFleet();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public getClans(): Clan[] {
|
|
17
|
+
return this.clanManager.getClans();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Defines the DebugController for managing and drawing debug visualizations.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
interface Point {
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface BodyWithId extends Point {
|
|
11
|
+
id: number | string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface LineToDraw {
|
|
15
|
+
source: Point;
|
|
16
|
+
target: Point;
|
|
17
|
+
options: {
|
|
18
|
+
drawLine: boolean;
|
|
19
|
+
showDistance: boolean;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class DebugController {
|
|
24
|
+
private linesToDraw: Map<string, LineToDraw>;
|
|
25
|
+
|
|
26
|
+
constructor() {
|
|
27
|
+
this.linesToDraw = new Map();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Resets the list of lines for the current frame.
|
|
32
|
+
*/
|
|
33
|
+
public clear() {
|
|
34
|
+
this.linesToDraw.clear();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Registers a line to be drawn between two bodies, preventing duplicates.
|
|
39
|
+
* @param body1 The first body (source).
|
|
40
|
+
* @param body2 The second body (target).
|
|
41
|
+
* @param options Flags to control rendering.
|
|
42
|
+
*/
|
|
43
|
+
public registerLine(body1: BodyWithId, body2: BodyWithId, options = { drawLine: true, showDistance: true }) {
|
|
44
|
+
// Create a unique, sorted key to prevent duplicate lines (A-B vs B-A)
|
|
45
|
+
const ids = [body1.id.toString(), body2.id.toString()].sort();
|
|
46
|
+
const lineKey = ids.join('-');
|
|
47
|
+
|
|
48
|
+
if (!this.linesToDraw.has(lineKey)) {
|
|
49
|
+
this.linesToDraw.set(lineKey, {
|
|
50
|
+
source: { x: body1.x, y: body1.y },
|
|
51
|
+
target: { x: body2.x, y: body2.y },
|
|
52
|
+
options,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Registers a directional line from a source to a simple point target.
|
|
59
|
+
* Duplicates are not checked here as the source guarantees uniqueness for its own target line.
|
|
60
|
+
* @param source The source body.
|
|
61
|
+
* @param target The target point.
|
|
62
|
+
*/
|
|
63
|
+
public registerDirectionalLine(source: BodyWithId, target: Point, options = { drawLine: true, showDistance: true }) {
|
|
64
|
+
const lineKey = `dir-${source.id}`;
|
|
65
|
+
this.linesToDraw.set(lineKey, { source, target, options });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private drawDebugLineWithDistance(ctx: CanvasRenderingContext2D, source: Point, target: Point, drawLine: boolean, showDistance: boolean) {
|
|
69
|
+
const dx = target.x - source.x;
|
|
70
|
+
const dy = target.y - source.y;
|
|
71
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
72
|
+
|
|
73
|
+
// Draw the dashed line if requested
|
|
74
|
+
if (drawLine) {
|
|
75
|
+
ctx.beginPath();
|
|
76
|
+
ctx.setLineDash([2, 4]);
|
|
77
|
+
ctx.moveTo(source.x, source.y);
|
|
78
|
+
ctx.lineTo(target.x, target.y);
|
|
79
|
+
ctx.strokeStyle = 'rgba(255, 255, 255, 0.2)';
|
|
80
|
+
ctx.lineWidth = 0.5;
|
|
81
|
+
ctx.stroke();
|
|
82
|
+
ctx.setLineDash([]);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Draw the distance text if requested
|
|
86
|
+
if (showDistance) {
|
|
87
|
+
const midX = source.x + dx / 2;
|
|
88
|
+
const midY = source.y + dy / 2;
|
|
89
|
+
ctx.fillStyle = 'rgba(255, 255, 255, 0.4)';
|
|
90
|
+
ctx.font = '10px "Roboto Mono", monospace';
|
|
91
|
+
ctx.textAlign = 'center';
|
|
92
|
+
ctx.textBaseline = 'bottom';
|
|
93
|
+
ctx.fillText(dist.toFixed(0), midX, midY - 2);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Draws all registered lines for the current frame.
|
|
99
|
+
* @param ctx The canvas rendering context.
|
|
100
|
+
*/
|
|
101
|
+
public draw(ctx: CanvasRenderingContext2D) {
|
|
102
|
+
if (this.linesToDraw.size === 0) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
ctx.save();
|
|
107
|
+
this.linesToDraw.forEach(({ source, target, options }) => {
|
|
108
|
+
this.drawDebugLineWithDistance(ctx, source, target, options.drawLine, options.showDistance);
|
|
109
|
+
});
|
|
110
|
+
ctx.restore();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
|
|
2
|
+
import { Supernova } from '../entities/supernova';
|
|
3
|
+
import { Meteor, METEOR_CONFIG } from '../entities/meteor';
|
|
4
|
+
import { STAR_CONFIG, Star } from '../entities/star';
|
|
5
|
+
import { BlackHle } from '../entities/black-hole';
|
|
6
|
+
import { EFFECTS_CONFIG } from '../config/effects';
|
|
7
|
+
import { drawSimpleStarStream } from '../lib/simplified-stream';
|
|
8
|
+
|
|
9
|
+
export class EffectsController {
|
|
10
|
+
private supernovas: Supernova[] = [];
|
|
11
|
+
private meteors: Meteor[] = [];
|
|
12
|
+
private meteorShowerCooldown: number;
|
|
13
|
+
private width: number;
|
|
14
|
+
private height: number;
|
|
15
|
+
|
|
16
|
+
constructor(width: number, height: number) {
|
|
17
|
+
this.width = width;
|
|
18
|
+
this.height = height;
|
|
19
|
+
this.meteorShowerCooldown = this.getNewMeteorCooldown();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private getNewMeteorCooldown(): number {
|
|
23
|
+
return Math.random() * METEOR_CONFIG.SHOWER_COOLDOWN_VARIANCE + METEOR_CONFIG.SHOWER_COOLDOWN_MIN;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public addSupernovas(newSupernovas: Supernova[]) {
|
|
27
|
+
this.supernovas.push(...newSupernovas);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public updateAndDrawSupernovas(ctx: CanvasRenderingContext2D) {
|
|
31
|
+
this.supernovas = this.supernovas.filter(nova => {
|
|
32
|
+
nova.update();
|
|
33
|
+
if (nova.isAlive()) {
|
|
34
|
+
nova.draw(ctx);
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private spawnMeteorShower() {
|
|
42
|
+
const radiantX = Math.random() * this.width;
|
|
43
|
+
const radiantY = Math.random() * this.height * 0.5;
|
|
44
|
+
const baseAngle = Math.random() * Math.PI * 2;
|
|
45
|
+
const meteorCount = Math.random() * METEOR_CONFIG.COUNT_VARIANCE + METEOR_CONFIG.COUNT_MIN;
|
|
46
|
+
const showerColor = STAR_CONFIG.RARE_STAR_COLORS[Math.floor(Math.random() * STAR_CONFIG.RARE_STAR_COLORS.length)];
|
|
47
|
+
|
|
48
|
+
for (let i = 0; i < meteorCount; i++) {
|
|
49
|
+
const angle = baseAngle + (Math.random() - 0.5) * 0.3;
|
|
50
|
+
const speed = Math.random() * METEOR_CONFIG.SPEED_VARIANCE + METEOR_CONFIG.SPEED_MIN;
|
|
51
|
+
this.meteors.push(new Meteor(radiantX, radiantY, Math.cos(angle) * speed, Math.sin(angle) * speed, showerColor));
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
public updateAndDrawMeteors(ctx: CanvasRenderingContext2D) {
|
|
56
|
+
this.meteorShowerCooldown--;
|
|
57
|
+
if (this.meteorShowerCooldown <= 0) {
|
|
58
|
+
this.spawnMeteorShower();
|
|
59
|
+
this.meteorShowerCooldown = this.getNewMeteorCooldown();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
this.meteors = this.meteors.filter(meteor => {
|
|
63
|
+
meteor.update();
|
|
64
|
+
if (meteor.isAlive(this.width, this.height)) {
|
|
65
|
+
meteor.draw(ctx);
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public drawStarToHoleStreams(ctx: CanvasRenderingContext2D, allStars: Star[], blackHoles: BlackHle[]) {
|
|
73
|
+
if (!EFFECTS_CONFIG.RENDER_STAR_TO_HOLE_STREAMS) return;
|
|
74
|
+
|
|
75
|
+
for (const star of allStars) {
|
|
76
|
+
for (const hole of blackHoles) {
|
|
77
|
+
const dx = hole.x - star.x;
|
|
78
|
+
const dy = hole.y - star.y;
|
|
79
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
80
|
+
if (dist < hole.radius + 50) {
|
|
81
|
+
drawSimpleStarStream(ctx, star, hole);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Defines the HoverController class for managing hover states.
|
|
4
|
+
*/
|
|
5
|
+
import type { Hoverable } from '../base/hoverable';
|
|
6
|
+
import { Starship, STARSHIP_CONFIG } from '../entities/starship';
|
|
7
|
+
import { Fleet, FLEET_CONFIG } from '../entities/fleet';
|
|
8
|
+
import { Star, STAR_CONFIG } from '../entities/star';
|
|
9
|
+
import { BlackHole, BLACK_HOLE_CONFIG } from '../entities/black-hole';
|
|
10
|
+
import { CelestialBody } from '../base/celestial-body';
|
|
11
|
+
|
|
12
|
+
function isHoverable(body: any): body is Hoverable & CelestialBody {
|
|
13
|
+
return 'isHovered' in body && 'hoverTimer' in body && 'hoverRadius' in body && 'x' in body && 'y' in body;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class HoverController {
|
|
17
|
+
private bodies: CelestialBody[];
|
|
18
|
+
|
|
19
|
+
constructor(bodies: CelestialBody[]) {
|
|
20
|
+
this.bodies = bodies;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public update(mouse: { x: number; y: number }, isMouseInside: boolean, debug: boolean) {
|
|
24
|
+
if (!debug) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
this.updateHoverStates();
|
|
29
|
+
if (isMouseInside) {
|
|
30
|
+
this.checkHoverState(mouse);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private checkHoverState(mouse: { x: number; y: number }) {
|
|
35
|
+
const allHoverables = this.bodies.filter(isHoverable);
|
|
36
|
+
|
|
37
|
+
let hoveredShip: Starship | null = null;
|
|
38
|
+
let hoveredFleet: Fleet | null = null;
|
|
39
|
+
let hoveredStar: Hoverable | null = null;
|
|
40
|
+
let hoveredBlackHole: BlackHole | null = null;
|
|
41
|
+
|
|
42
|
+
// Prioritize smaller, more specific objects first by checking them in order.
|
|
43
|
+
// Once a hovered object is found, we stop to prevent hovering multiple objects at once.
|
|
44
|
+
|
|
45
|
+
// 1. Ships
|
|
46
|
+
for (const body of allHoverables) {
|
|
47
|
+
if (body instanceof Starship) {
|
|
48
|
+
const dx = body.x - mouse.x;
|
|
49
|
+
const dy = body.y - mouse.y;
|
|
50
|
+
const distSq = dx * dx + dy * dy;
|
|
51
|
+
if (distSq < body.hoverRadius * body.hoverRadius) {
|
|
52
|
+
hoveredShip = body;
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 2. Fleets
|
|
59
|
+
if (!hoveredShip) {
|
|
60
|
+
for (const body of allHoverables) {
|
|
61
|
+
if (body instanceof Fleet) {
|
|
62
|
+
const dx = body.x - mouse.x;
|
|
63
|
+
const dy = body.y - mouse.y;
|
|
64
|
+
const distSq = dx * dx + dy * dy;
|
|
65
|
+
if (distSq < body.hoverRadius * body.hoverRadius) {
|
|
66
|
+
hoveredFleet = body;
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 3. Black Holes
|
|
74
|
+
if (!hoveredShip && !hoveredFleet) {
|
|
75
|
+
for (const body of allHoverables) {
|
|
76
|
+
if (body instanceof BlackHole) {
|
|
77
|
+
const dx = body.x - mouse.x;
|
|
78
|
+
const dy = body.y - mouse.y;
|
|
79
|
+
const distSq = dx * dx + dy * dy;
|
|
80
|
+
if (distSq < body.hoverRadius * body.hoverRadius) {
|
|
81
|
+
hoveredBlackHole = body;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 4. Stars (as a fallback)
|
|
89
|
+
if (!hoveredShip && !hoveredFleet && !hoveredBlackHole) {
|
|
90
|
+
for (const body of allHoverables) {
|
|
91
|
+
if (!(body instanceof Starship) && !(body instanceof Fleet) && !(body instanceof BlackHole)) {
|
|
92
|
+
const dx = body.x - mouse.x;
|
|
93
|
+
const dy = body.y - mouse.y;
|
|
94
|
+
const distSq = dx * dx + dy * dy;
|
|
95
|
+
if (distSq < body.hoverRadius * body.hoverRadius) {
|
|
96
|
+
hoveredStar = body;
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Set timers for all hovered items
|
|
104
|
+
if (hoveredShip) {
|
|
105
|
+
hoveredShip.hoverTimer = STARSHIP_CONFIG.HOVER_TIMEOUT;
|
|
106
|
+
}
|
|
107
|
+
if (hoveredFleet) {
|
|
108
|
+
hoveredFleet.hoverTimer = FLEET_CONFIG.HOVER_TIMEOUT;
|
|
109
|
+
}
|
|
110
|
+
if (hoveredBlackHole) {
|
|
111
|
+
hoveredBlackHole.hoverTimer = BLACK_HOLE_CONFIG.HOVER_TIMEOUT;
|
|
112
|
+
}
|
|
113
|
+
if (hoveredStar) {
|
|
114
|
+
hoveredStar.hoverTimer = STAR_CONFIG.HOVER_TIMEOUT;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private updateHoverStates() {
|
|
119
|
+
this.bodies.forEach(obj => {
|
|
120
|
+
if (isHoverable(obj)) {
|
|
121
|
+
if (obj.hoverTimer > 0) {
|
|
122
|
+
obj.hoverTimer--;
|
|
123
|
+
}
|
|
124
|
+
obj.isHovered = obj.hoverTimer > 0;
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
|
|
2
|
+
import type { CelestialBody } from '../base/celestial-body';
|
|
3
|
+
|
|
4
|
+
export type Layer = {
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
bodies: CelestialBody[];
|
|
8
|
+
isInteractive: boolean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export class LayerController {
|
|
12
|
+
private layers: Record<string, Layer> = {};
|
|
13
|
+
|
|
14
|
+
constructor() {
|
|
15
|
+
this.initializeLayers();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
private initializeLayers() {
|
|
19
|
+
this.layers = {
|
|
20
|
+
backgroundNebulas: {
|
|
21
|
+
name: 'Background Nebulas',
|
|
22
|
+
description: 'Slow-moving, non-interactive nebula clouds for visual depth.',
|
|
23
|
+
bodies: [],
|
|
24
|
+
isInteractive: false,
|
|
25
|
+
},
|
|
26
|
+
sectorGrid: {
|
|
27
|
+
name: 'Sector Grid',
|
|
28
|
+
description: 'A grid of sectors that divides the map.',
|
|
29
|
+
bodies: [],
|
|
30
|
+
isInteractive: false,
|
|
31
|
+
},
|
|
32
|
+
staticStars: {
|
|
33
|
+
name: 'Static Stars',
|
|
34
|
+
description: 'A layer of stars that provides parallax but does not react to mouse interaction.',
|
|
35
|
+
bodies: [],
|
|
36
|
+
isInteractive: false,
|
|
37
|
+
},
|
|
38
|
+
interactiveStars: {
|
|
39
|
+
name: 'Interactive Stars',
|
|
40
|
+
description: 'A layer of stars that reacts to mouse movement and can be hovered.',
|
|
41
|
+
bodies: [],
|
|
42
|
+
isInteractive: true,
|
|
43
|
+
},
|
|
44
|
+
blackHoles: {
|
|
45
|
+
name: 'Black Holes',
|
|
46
|
+
description: 'Gravitational wells that affect other objects.',
|
|
47
|
+
bodies: [],
|
|
48
|
+
isInteractive: true,
|
|
49
|
+
},
|
|
50
|
+
dynamicObjects: {
|
|
51
|
+
name: 'Dynamic Objects',
|
|
52
|
+
description: 'Comets, starships, and fleets that move and interact within the simulation.',
|
|
53
|
+
bodies: [],
|
|
54
|
+
isInteractive: true,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public getLayers(): Record<string, Layer> {
|
|
60
|
+
return this.layers;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public getLayer(layerName: string): Layer | undefined {
|
|
64
|
+
return this.layers[layerName];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public getBodies(layerName: string): CelestialBody[] {
|
|
68
|
+
return this.layers[layerName]?.bodies || [];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public addBody(layerName: string, body: CelestialBody) {
|
|
72
|
+
if (this.layers[layerName]) {
|
|
73
|
+
this.layers[layerName].bodies.push(body);
|
|
74
|
+
} else {
|
|
75
|
+
console.warn(`Layer "${layerName}" does not exist.`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|