This commit is contained in:
Bastian Wagner 2025-04-26 13:59:43 +02:00
parent 9d1f4649d7
commit 758c358eea
16 changed files with 220 additions and 61 deletions

2
package-lock.json generated
View File

@ -8,6 +8,7 @@
"name": "stellar-lines", "name": "stellar-lines",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@angular/cdk": "^19.2.11",
"@angular/common": "^19.2.0", "@angular/common": "^19.2.0",
"@angular/compiler": "^19.2.0", "@angular/compiler": "^19.2.0",
"@angular/core": "^19.2.0", "@angular/core": "^19.2.0",
@ -500,7 +501,6 @@
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-19.2.11.tgz", "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-19.2.11.tgz",
"integrity": "sha512-G568yWIJlnsuS563WxvCofmxc1405+wRQvDGQ32+qWOblJScFkHgr4jeDkZGcyt/r8OudaW0H0/rNeg1dzdnIQ==", "integrity": "sha512-G568yWIJlnsuS563WxvCofmxc1405+wRQvDGQ32+qWOblJScFkHgr4jeDkZGcyt/r8OudaW0H0/rNeg1dzdnIQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"parse5": "^7.1.2", "parse5": "^7.1.2",
"tslib": "^2.3.0" "tslib": "^2.3.0"

View File

@ -10,6 +10,7 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/cdk": "^19.2.11",
"@angular/common": "^19.2.0", "@angular/common": "^19.2.0",
"@angular/compiler": "^19.2.0", "@angular/compiler": "^19.2.0",
"@angular/core": "^19.2.0", "@angular/core": "^19.2.0",

View File

@ -1,7 +1,7 @@
<div #gameContainer class="game-container"></div> <div #gameContainer class="game-container"></div>
@if (gameService.showShipInfo) { @if (gameService.showShipInfo) {
<app-ship-dialog /> <app-ship-dialog cdkDrag cdkDragBoundary="html"/>
} }
@if (gameService.showPlanetInfo) { @if (gameService.showPlanetInfo) {
<app-planet-dialog></app-planet-dialog> <app-planet-dialog cdkDrag cdkDragBoundary="html"></app-planet-dialog>
} }

View File

@ -1,5 +1,4 @@
.game-container { .game-container {
position: absolute;
top: 0; top: 0;
left: 0; left: 0;
width: 100vw; width: 100vw;

View File

@ -5,10 +5,11 @@ import { MatDialog, MatDialogModule } from '@angular/material/dialog';
import { GameService } from './service/game.service'; import { GameService } from './service/game.service';
import { ShipDialogComponent } from './components/dialog/ship-dialog/ship-dialog.component'; import { ShipDialogComponent } from './components/dialog/ship-dialog/ship-dialog.component';
import { PlanetDialogComponent } from './components/dialog/planet-dialog/planet-dialog.component'; import { PlanetDialogComponent } from './components/dialog/planet-dialog/planet-dialog.component';
import { DragDropModule } from '@angular/cdk/drag-drop';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
imports: [MatDialogModule, ShipDialogComponent, PlanetDialogComponent], imports: [MatDialogModule, ShipDialogComponent, PlanetDialogComponent, DragDropModule],
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrl: './app.component.scss' styleUrl: './app.component.scss'
}) })

View File

@ -2,10 +2,6 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
position: absolute; position: absolute;
// top: 24px; top: 24px;
// left: 24px; left: 24px;
// width: 240px;
// height: 240px;
// background-color: var(--background-color);
// color: var(--primary-color);
} }

View File

@ -5,10 +5,11 @@ import { CommonModule } from '@angular/common';
import { Good } from '../../../model/goods/good.interface'; import { Good } from '../../../model/goods/good.interface';
import { GoodType } from '../../../model/goods/good-type.enum'; import { GoodType } from '../../../model/goods/good-type.enum';
import { GameService } from '../../../service/game.service'; import { GameService } from '../../../service/game.service';
import {DragDropModule} from '@angular/cdk/drag-drop';
@Component({ @Component({
selector: 'app-planet-dialog', selector: 'app-planet-dialog',
imports: [CommonModule, MatDialogModule], imports: [CommonModule, MatDialogModule, DragDropModule],
templateUrl: './planet-dialog.component.html', templateUrl: './planet-dialog.component.html',
styleUrl: './planet-dialog.component.scss' styleUrl: './planet-dialog.component.scss'
}) })

View File

@ -1,4 +1,4 @@
<div class="ui-panel"> <div class="ui-panel" (click)="onClick($event)">
<div class="ui-title"> <div class="ui-title">
<div class="image"><img [src]="'sprites/ships/01-starter.png'" alt=""></div> <div class="image"><img [src]="'sprites/ships/01-starter.png'" alt=""></div>
@ -26,6 +26,10 @@
<ul> <ul>
<li class="flex"> <li class="flex">
<div>Geschwindigkeit: </div> <div>Geschwindigkeit: </div>
<div>{{ ship.currentSpeed | number:'0.0-0' }} km/s</div>
</li>
<li class="flex">
<div>Max Geschwindigkeit: </div>
<div>{{ ship.maxSpeed | number:'0.0-0' }} km/s</div> <div>{{ ship.maxSpeed | number:'0.0-0' }} km/s</div>
</li> </li>
<li class="flex"> <li class="flex">
@ -58,7 +62,7 @@
<div class="ui-section"> <div class="ui-section">
<button class="button">Produktion upgraden</button> <button class="button">Produktion upgraden</button>
<button class="button">Siedeln</button> <button class="button">Siedeln</button>
<button class="button" (click)="close()" >Schließen</button> <button class="button" (click)="onClick($event); close();" >Schließen</button>
</div> </div>
</div> </div>

View File

@ -1,13 +1,15 @@
:host { :host {
position: absolute;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
position: absolute; position: absolute;
// top: 24px; top: 24px;
// left: 24px; left: 24px;
// width: 240px; // width: 240px;
// height: 240px; // height: 240px;
// background-color: var(--background-color); // background-color: var(--background-color);
// color: var(--primary-color); // color: var(--primary-color);
z-index: 1;
} }
img { img {

View File

@ -33,4 +33,9 @@ export class ShipDialogComponent {
} }
return '' return ''
} }
onClick(event: MouseEvent) {
event.stopPropagation();
console.log(event)
}
} }

View File

@ -19,6 +19,9 @@ export class Planet {
demandSecondsBuffer = 30; // Anfrage immer mindestens 30 Sekunden überleben demandSecondsBuffer = 30; // Anfrage immer mindestens 30 Sekunden überleben
private productionLevel: Map<GoodType, number> = new Map(); private productionLevel: Map<GoodType, number> = new Map();
private dockedShips: Ship[] = [];
private dockCapacity: number = 0;
private shipsWaitingForDocking: Ship[] = [];
constructor(config: PlanetInit) { constructor(config: PlanetInit) {
@ -49,6 +52,9 @@ export class Planet {
}); });
this.updatePopulation(0) this.updatePopulation(0)
setTimeout(() => {
this.dockCapacity = 1;
}, 10000)
} }
@ -229,6 +235,53 @@ export class Planet {
return critical; return critical;
} }
shipCanDock(ship: Ship): boolean {
if (this.dockedShips.includes(ship)) { return true; } // bereits gedockt
/** Dock ist voll */
if (this.dockedShips.length >= this.dockCapacity) {
if (!this.shipsWaitingForDocking.includes(ship)) {
/** Schiff auf Warteliste */
this.shipsWaitingForDocking.push(ship);
}
return false;
}
if (this.shipsWaitingForDocking.length == 0) {
this.dock(ship)
return true;
} // keine wartenden schiffe
/* Dock ist nicht voll, aber es gibt Wartende Schiffe */
/* in warteschlange pushen */
if (!this.shipsWaitingForDocking.includes(ship)) {
this.shipsWaitingForDocking.push(ship)
} else {
/* Schiff ist schon registriert */
const first = this.shipsWaitingForDocking.indexOf(ship) == 0; // ist an erster stelle?
if (first) {
this.dock(ship);
return true;
}
}
return false;
}
dock(ship: Ship) {
console.log("DOCK:", ship.name)
if (!this.dockedShips.includes(ship)) {
console.log("DOCK:", ship.name)
this.dockedShips.push(ship);
this.shipsWaitingForDocking = this.shipsWaitingForDocking.filter(s => s != ship); // aus warteschlange entfernen
}
}
undock(ship: Ship) {
console.log("undock", ship.name)
this.dockedShips = this.dockedShips.filter(d => d != ship);
console.log('Angedockt: ',this.dockedShips.map(d => d.name).join(','))
console.log('wartend: ',this.shipsWaitingForDocking.map(d => d.name).join(','))
}
} }
export interface PlanetInit { export interface PlanetInit {

View File

@ -2,7 +2,7 @@ import { MapScene } from "../scene/map.scene";
import { GameService } from "../service/game.service"; import { GameService } from "../service/game.service";
import { PlanetUi } from "../ui/planet.ui"; import { PlanetUi } from "../ui/planet.ui";
import { TradeInstance } from "./planet.model"; import { TradeInstance } from "./planet.model";
import { Ship } from "./ships/ship.model"; import { FlightMode, Ship } from "./ships/ship.model";
export class ShipUi extends Phaser.Physics.Arcade.Sprite { export class ShipUi extends Phaser.Physics.Arcade.Sprite {
private velocity = new Phaser.Math.Vector2(0, 0); private velocity = new Phaser.Math.Vector2(0, 0);
@ -11,6 +11,10 @@ export class ShipUi extends Phaser.Physics.Arcade.Sprite {
public model: Ship = new Ship(); public model: Ship = new Ship();
public gameService: GameService; public gameService: GameService;
private orbitAngle: number = 0;
private orbitRadius: number = 120;
private orbitSpeed: number = Math.PI / 25; // Eine Runde in 2 Sekunden
constructor(scene: MapScene, x: number, y: number, gameService: GameService) { constructor(scene: MapScene, x: number, y: number, gameService: GameService) {
super(scene, x, y, 'ship'); super(scene, x, y, 'ship');
this.gameService = gameService; this.gameService = gameService;
@ -37,6 +41,8 @@ export class ShipUi extends Phaser.Physics.Arcade.Sprite {
}); });
} }
moveTo(target: PlanetUi | undefined) { moveTo(target: PlanetUi | undefined) {
if (!target) { return; } if (!target) { return; }
this.target = target; this.target = target;
@ -46,49 +52,109 @@ export class ShipUi extends Phaser.Physics.Arcade.Sprite {
} }
override update(time: number, delta: number): void { override update(time: number, delta: number): void {
if (!this.target) { return; }
if (!this.targetVector) return; if (!this.targetVector) return;
if (!(this.body instanceof Phaser.Physics.Arcade.Body)) { return; } if (!(this.body instanceof Phaser.Physics.Arcade.Body)) { return; }
const dt = delta / 1000; // ms → Sekunden const dt = delta / 1000; // ms → Sekunden
const from = this.getWorldPoint();
const to = this.target.getWorldPoint();
const distance = Phaser.Math.Distance.Between(from.x, from.y, to.x, to.y);
const toTarget = new Phaser.Math.Vector2(this.targetVector.x - this.getWorldPoint().x, this.targetVector.y - this.getWorldPoint().y); switch (this.model.flightMode) {
const distance = toTarget.length(); case FlightMode.Normal:
if (distance < 150) {
this.setRotation(this.velocity.angle() + Phaser.Math.DegToRad(180)); // Zeigt Schiff zur Zielrichtung this.startOrbit(this.target);
return;
if (distance < 4) { }
this.body.setVelocity(0, 0); this.flyDirectlyToTarget(to, dt);
this.velocity.set(0, 0); break;
if (this.targetVector && this.target) { case FlightMode.Orbiting:
this.targetReached(this.target); this.updateOrbit(to, dt);
} break;
this.targetVector = null; case FlightMode.Docking:
return; this.flyDirectlyToTarget(to, dt);
if (distance < 5) {
this.land();
}
break;
case FlightMode.Undocking:
if (distance > 160) {
this.model.flightMode = FlightMode.Normal;
}
} }
}
// Richtung zum Ziel startOrbit(planet: PlanetUi) {
const desiredDirection = toTarget.normalize(); this.model.flightMode = FlightMode.Orbiting;
const from = this.getWorldPoint();
const to = planet.getWorldPoint();
// Zielgeschwindigkeit abhängig von Entfernung (langsamer bremsen) // Starte Orbit am aktuellen Winkel zum Planeten
const targetSpeed = (distance < this.model.slowDownRadius) this.orbitAngle = Phaser.Math.Angle.Between(to.x, to.y, from.x, from.y);
? this.model.maxSpeed * (distance / this.model.slowDownRadius)
: this.model.maxSpeed;
const desiredVelocity = desiredDirection.scale(targetSpeed);
// Steuerung per "Lerp" zur Zielgeschwindigkeit (weiches Beschleunigen)
this.velocity.lerp(desiredVelocity, this.model.acceleration * dt / this.model.maxSpeed);
this.body.setVelocity(this.velocity.x, this.velocity.y);
} }
flyDirectlyToTarget(target: any, dt: any) {
if (!(this.body instanceof Phaser.Physics.Arcade.Body)) { return; }
const angle = Phaser.Math.Angle.Between(this.getWorldPoint().x, this.getWorldPoint().y, target.x, target.y);
// Sanfte Beschleunigung
this.model.currentSpeed = Phaser.Math.Clamp(this.model.currentSpeed + this.model.acceleration * dt, 0, this.model.maxSpeed);
this.velocity.set(Math.cos(angle) * this.model.currentSpeed, Math.sin(angle) * this.model.currentSpeed);
this.body.setVelocity(this.velocity.x, this.velocity.y);
this.setRotation(this.velocity.angle() + Phaser.Math.DegToRad(180));
}
private updateOrbit(center: Phaser.Math.Vector2, dt: number): void {
if (!(this.body instanceof Phaser.Physics.Arcade.Body)) { return; }
this.orbitAngle += this.orbitSpeed * dt;
const targetX = center.x + Math.cos(this.orbitAngle) * this.orbitRadius;
const targetY = center.y + Math.sin(this.orbitAngle) * this.orbitRadius;
const desiredVelocityX = (targetX - this.getWorldPoint().x) * 5;
const desiredVelocityY = (targetY - this.getWorldPoint().y) * 5;
this.body.setVelocity(desiredVelocityX, desiredVelocityY);
this.setRotation(this.body.velocity.angle() + Phaser.Math.DegToRad(180));
// Umdrehungen zählen
if (this.orbitAngle >= Math.PI * 2) {
this.orbitAngle -= Math.PI * 2;
}
if (this.target?.model.shipCanDock(this.model)) {
this.startDocking();
}
}
private startDocking() {
this.model.flightMode = FlightMode.Docking;
// this.currentSpeed = 0;
}
private land() {
if (!(this.body instanceof Phaser.Physics.Arcade.Body)) { return; }
if (!this.target) { return; }
this.body.setVelocity(0, 0);
this.velocity.set(0, 0);
this.model.flightMode = FlightMode.Docked;
this.targetReached(this.target); // Deine Dock-Funktion
this.model.currentSpeed = 0;
}
async targetReached(target: PlanetUi) { async targetReached(target: PlanetUi) {
await this.model.exchangeGoods(target.model) await this.model.exchangeGoods(target.model)
if (this.model.route) { if (this.model.route) {
this.model.route.routePointReached(); this.model.route.routePointReached();
const target = (this.scene as MapScene).getPlanetUiByName(this.model.route.nextPlanetName); const newTarget = (this.scene as MapScene).getPlanetUiByName(this.model.route.nextPlanetName);
setTimeout(() => { setTimeout(() => {
this.moveTo(target); this.model.flightMode = FlightMode.Undocking;
this.model.undock(target.model)
this.moveTo(newTarget);
}, 1000) }, 1000)
} }
} }
@ -122,4 +188,7 @@ export class ShipUi extends Phaser.Physics.Arcade.Sprite {
this.moveTo(planet) this.moveTo(planet)
} }
}
}

View File

@ -1,18 +1,19 @@
import { GoodType } from "../goods/good-type.enum";
import { Planet, TradeInstance } from "../planet.model"; import { Planet, TradeInstance } from "../planet.model";
import { TradeRoute } from "../routes/trade-route.model"; import { TradeRoute } from "../routes/trade-route.model";
export class Ship { export class Ship {
public acceleration = 200; // Pixel pro Sekunde² public acceleration = 10; // Pixel pro Sekunde²
public maxSpeed = 4000; public maxSpeed = 100;
public slowDownRadius = 2000; // Startet Bremsen, wenn Ziel nahe ist public currentSpeed = 0;
public cargoSize = 100; public cargoSize = 100;
cargoSpace: TradeInstance[] = []; cargoSpace: TradeInstance[] = [];
public route: TradeRoute | undefined = new TradeRoute([]) public route: TradeRoute | undefined = new TradeRoute([])
public name = "Pioneer-01"; public name = "Pioneer-01-" + Math.round(Math.random() * 100);
public loadingSpeed = 1.5; public loadingSpeed = 1.5;
public status: 'loading' | 'unloading' | 'waiting' | 'idle' | 'traveling' = 'idle' public status: 'loading' | 'unloading' | 'waiting' | 'idle' | 'traveling' = 'idle';
public flightMode: FlightMode = FlightMode.Normal;
async exchangeGoods(planet: Planet) { async exchangeGoods(planet: Planet) {
if (!this.route) { if (!this.route) {
@ -25,6 +26,15 @@ export class Ship {
} }
dock(planet: Planet) {
planet.dock(this)
}
undock(planet: Planet) {
planet.undock(this);
}
private async unloadGoods(planet: Planet) { private async unloadGoods(planet: Planet) {
if (!planet || this.cargoSpace.length == 0) { return Promise.resolve(null); } if (!planet || this.cargoSpace.length == 0) { return Promise.resolve(null); }
@ -120,4 +130,12 @@ export class Ship {
}, amount * 1000 / this.loadingSpeed) }, amount * 1000 / this.loadingSpeed)
}) })
} }
}
export enum FlightMode {
Normal, // Normales Fliegen zum Planeten
Orbiting, // Kreisen um den Planeten
Docking, // Gerader Flug und Landung
Undocking,
Docked
} }

View File

@ -1,5 +1,4 @@
import { PLANETCONFIGS } from "../data/planets.data"; import { PLANETCONFIGS } from "../data/planets.data";
import { GoodType } from "../model/goods/good-type.enum";
import { TradeRoute } from "../model/routes/trade-route.model"; import { TradeRoute } from "../model/routes/trade-route.model";
import { ShipUi } from "../model/ship"; import { ShipUi } from "../model/ship";
import { GameService } from "../service/game.service"; import { GameService } from "../service/game.service";
@ -67,7 +66,7 @@ export class MapScene extends Phaser.Scene {
}); });
this.input.on('pointermove', (pointer: Phaser.Input.Pointer) => { this.input.on('pointermove', (pointer: Phaser.Input.Pointer) => {
if (!this.isDragging) return; if (!this.isDragging || !this.gameService.canDrag) return;
const dragX = pointer.x - this.dragStart.x; const dragX = pointer.x - this.dragStart.x;
const dragY = pointer.y - this.dragStart.y; const dragY = pointer.y - this.dragStart.y;
this.camera.scrollX -= dragX / this.camera.zoom; this.camera.scrollX -= dragX / this.camera.zoom;
@ -92,18 +91,23 @@ export class MapScene extends Phaser.Scene {
this.camera.setZoom(Phaser.Math.Clamp(this.camera.zoom, minScale, 1)); this.camera.setZoom(Phaser.Math.Clamp(this.camera.zoom, minScale, 1));
}); });
this.createShip();
setTimeout(() => { setTimeout(() => {
const s = new ShipUi(this, 100, 100, this.gameService); this.createShip();
s.model.route = new TradeRoute(this.planets.filter(planet => ['Terra Nova', 'Aqualis'].includes(planet.model.name)).map(planet => planet.model)) }, 100)
this.ships.push(s)
this.physics.world.enable(s);
s.activateRoute();
}, 1000)
} }
createShip() {
const s = new ShipUi(this, 100, 100, this.gameService);
s.model.route = new TradeRoute(this.planets.filter(planet => ['Terra Nova', 'Aqualis'].includes(planet.model.name)).map(planet => planet.model))
this.ships.push(s)
this.physics.world.enable(s);
s.activateRoute();
}
override update(time: number, delta: number): void { override update(time: number, delta: number): void {
if (this.ship) { if (this.ship) {

View File

@ -22,6 +22,6 @@ export class GameService {
} }
get canDrag(): boolean { get canDrag(): boolean {
return true; return this.showShipInfo == undefined && this.showPlanetInfo == undefined;
} }
} }

View File

@ -10,3 +10,9 @@ html {
)); ));
} }
html, body {
width: 100vw;
height: 100vh;
margin: 0;
padding: 0;
}