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

View File

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

View File

@ -1,7 +1,7 @@
<div #gameContainer class="game-container"></div>
@if (gameService.showShipInfo) {
<app-ship-dialog />
<app-ship-dialog cdkDrag cdkDragBoundary="html"/>
}
@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 {
position: absolute;
top: 0;
left: 0;
width: 100vw;

View File

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

View File

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

View File

@ -5,10 +5,11 @@ import { CommonModule } from '@angular/common';
import { Good } from '../../../model/goods/good.interface';
import { GoodType } from '../../../model/goods/good-type.enum';
import { GameService } from '../../../service/game.service';
import {DragDropModule} from '@angular/cdk/drag-drop';
@Component({
selector: 'app-planet-dialog',
imports: [CommonModule, MatDialogModule],
imports: [CommonModule, MatDialogModule, DragDropModule],
templateUrl: './planet-dialog.component.html',
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="image"><img [src]="'sprites/ships/01-starter.png'" alt=""></div>
@ -26,6 +26,10 @@
<ul>
<li class="flex">
<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>
</li>
<li class="flex">
@ -58,7 +62,7 @@
<div class="ui-section">
<button class="button">Produktion upgraden</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>

View File

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

View File

@ -33,4 +33,9 @@ export class ShipDialogComponent {
}
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
private productionLevel: Map<GoodType, number> = new Map();
private dockedShips: Ship[] = [];
private dockCapacity: number = 0;
private shipsWaitingForDocking: Ship[] = [];
constructor(config: PlanetInit) {
@ -49,6 +52,9 @@ export class Planet {
});
this.updatePopulation(0)
setTimeout(() => {
this.dockCapacity = 1;
}, 10000)
}
@ -229,6 +235,53 @@ export class Planet {
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 {

View File

@ -2,7 +2,7 @@ import { MapScene } from "../scene/map.scene";
import { GameService } from "../service/game.service";
import { PlanetUi } from "../ui/planet.ui";
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 {
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 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) {
super(scene, x, y, 'ship');
this.gameService = gameService;
@ -37,6 +41,8 @@ export class ShipUi extends Phaser.Physics.Arcade.Sprite {
});
}
moveTo(target: PlanetUi | undefined) {
if (!target) { return; }
this.target = target;
@ -46,49 +52,109 @@ export class ShipUi extends Phaser.Physics.Arcade.Sprite {
}
override update(time: number, delta: number): void {
if (!this.target) { return; }
if (!this.targetVector) return;
if (!(this.body instanceof Phaser.Physics.Arcade.Body)) { return; }
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);
const distance = toTarget.length();
this.setRotation(this.velocity.angle() + Phaser.Math.DegToRad(180)); // Zeigt Schiff zur Zielrichtung
if (distance < 4) {
this.body.setVelocity(0, 0);
this.velocity.set(0, 0);
if (this.targetVector && this.target) {
this.targetReached(this.target);
}
this.targetVector = null;
return;
switch (this.model.flightMode) {
case FlightMode.Normal:
if (distance < 150) {
this.startOrbit(this.target);
return;
}
this.flyDirectlyToTarget(to, dt);
break;
case FlightMode.Orbiting:
this.updateOrbit(to, dt);
break;
case FlightMode.Docking:
this.flyDirectlyToTarget(to, dt);
if (distance < 5) {
this.land();
}
break;
case FlightMode.Undocking:
if (distance > 160) {
this.model.flightMode = FlightMode.Normal;
}
}
// Richtung zum Ziel
const desiredDirection = toTarget.normalize();
// Zielgeschwindigkeit abhängig von Entfernung (langsamer bremsen)
const targetSpeed = (distance < this.model.slowDownRadius)
? 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);
}
startOrbit(planet: PlanetUi) {
this.model.flightMode = FlightMode.Orbiting;
const from = this.getWorldPoint();
const to = planet.getWorldPoint();
// Starte Orbit am aktuellen Winkel zum Planeten
this.orbitAngle = Phaser.Math.Angle.Between(to.x, to.y, from.x, from.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) {
await this.model.exchangeGoods(target.model)
if (this.model.route) {
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(() => {
this.moveTo(target);
this.model.flightMode = FlightMode.Undocking;
this.model.undock(target.model)
this.moveTo(newTarget);
}, 1000)
}
}
@ -122,4 +188,7 @@ export class ShipUi extends Phaser.Physics.Arcade.Sprite {
this.moveTo(planet)
}
}

View File

@ -1,18 +1,19 @@
import { GoodType } from "../goods/good-type.enum";
import { Planet, TradeInstance } from "../planet.model";
import { TradeRoute } from "../routes/trade-route.model";
export class Ship {
public acceleration = 200; // Pixel pro Sekunde²
public maxSpeed = 4000;
public slowDownRadius = 2000; // Startet Bremsen, wenn Ziel nahe ist
public acceleration = 10; // Pixel pro Sekunde²
public maxSpeed = 100;
public currentSpeed = 0;
public cargoSize = 100;
cargoSpace: TradeInstance[] = [];
public route: TradeRoute | undefined = new TradeRoute([])
public name = "Pioneer-01";
public name = "Pioneer-01-" + Math.round(Math.random() * 100);
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) {
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) {
if (!planet || this.cargoSpace.length == 0) { return Promise.resolve(null); }
@ -121,3 +131,11 @@ export class Ship {
})
}
}
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 { GoodType } from "../model/goods/good-type.enum";
import { TradeRoute } from "../model/routes/trade-route.model";
import { ShipUi } from "../model/ship";
import { GameService } from "../service/game.service";
@ -67,7 +66,7 @@ export class MapScene extends Phaser.Scene {
});
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 dragY = pointer.y - this.dragStart.y;
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.createShip();
setTimeout(() => {
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();
}, 1000)
this.createShip();
}, 100)
}
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 {
if (this.ship) {

View File

@ -22,6 +22,6 @@ export class GameService {
}
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;
}