diff --git a/package-lock.json b/package-lock.json index 418b4d6..458b642 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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" diff --git a/package.json b/package.json index 6287245..3249158 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/app/app.component.html b/src/app/app.component.html index 1c9adfa..e94a5be 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,7 +1,7 @@
@if (gameService.showShipInfo) { - + } @if (gameService.showPlanetInfo) { - + } \ No newline at end of file diff --git a/src/app/app.component.scss b/src/app/app.component.scss index 1fa9853..9e05a98 100644 --- a/src/app/app.component.scss +++ b/src/app/app.component.scss @@ -1,5 +1,4 @@ .game-container { - position: absolute; top: 0; left: 0; width: 100vw; diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 3559aa4..792e63a 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -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' }) diff --git a/src/app/components/dialog/planet-dialog/planet-dialog.component.scss b/src/app/components/dialog/planet-dialog/planet-dialog.component.scss index 3c3bec3..5e1336a 100644 --- a/src/app/components/dialog/planet-dialog/planet-dialog.component.scss +++ b/src/app/components/dialog/planet-dialog/planet-dialog.component.scss @@ -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; } \ No newline at end of file diff --git a/src/app/components/dialog/planet-dialog/planet-dialog.component.ts b/src/app/components/dialog/planet-dialog/planet-dialog.component.ts index 1415165..44af38f 100644 --- a/src/app/components/dialog/planet-dialog/planet-dialog.component.ts +++ b/src/app/components/dialog/planet-dialog/planet-dialog.component.ts @@ -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' }) diff --git a/src/app/components/dialog/ship-dialog/ship-dialog.component.html b/src/app/components/dialog/ship-dialog/ship-dialog.component.html index 39d2e2e..fcf0112 100644 --- a/src/app/components/dialog/ship-dialog/ship-dialog.component.html +++ b/src/app/components/dialog/ship-dialog/ship-dialog.component.html @@ -1,4 +1,4 @@ -
+
@@ -26,6 +26,10 @@
  • Geschwindigkeit:
    +
    {{ ship.currentSpeed | number:'0.0-0' }} km/s
    +
  • +
  • +
    Max Geschwindigkeit:
    {{ ship.maxSpeed | number:'0.0-0' }} km/s
  • @@ -58,7 +62,7 @@
    - +
diff --git a/src/app/components/dialog/ship-dialog/ship-dialog.component.scss b/src/app/components/dialog/ship-dialog/ship-dialog.component.scss index 8d7606f..6c32757 100644 --- a/src/app/components/dialog/ship-dialog/ship-dialog.component.scss +++ b/src/app/components/dialog/ship-dialog/ship-dialog.component.scss @@ -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 { diff --git a/src/app/components/dialog/ship-dialog/ship-dialog.component.ts b/src/app/components/dialog/ship-dialog/ship-dialog.component.ts index e78d5d0..c528943 100644 --- a/src/app/components/dialog/ship-dialog/ship-dialog.component.ts +++ b/src/app/components/dialog/ship-dialog/ship-dialog.component.ts @@ -33,4 +33,9 @@ export class ShipDialogComponent { } return '' } + + onClick(event: MouseEvent) { + event.stopPropagation(); + console.log(event) + } } diff --git a/src/app/model/planet.model.ts b/src/app/model/planet.model.ts index 89950d4..d6f0649 100644 --- a/src/app/model/planet.model.ts +++ b/src/app/model/planet.model.ts @@ -19,6 +19,9 @@ export class Planet { demandSecondsBuffer = 30; // Anfrage immer mindestens 30 Sekunden überleben private productionLevel: Map = 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 { diff --git a/src/app/model/ship.ts b/src/app/model/ship.ts index 2c68fc0..319a263 100644 --- a/src/app/model/ship.ts +++ b/src/app/model/ship.ts @@ -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(); + startOrbit(planet: PlanetUi) { + this.model.flightMode = FlightMode.Orbiting; + const from = this.getWorldPoint(); + const to = planet.getWorldPoint(); - // 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); + // 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) } -} \ No newline at end of file + +} + + diff --git a/src/app/model/ships/ship.model.ts b/src/app/model/ships/ship.model.ts index 458033d..2e9e3b4 100644 --- a/src/app/model/ships/ship.model.ts +++ b/src/app/model/ships/ship.model.ts @@ -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); } @@ -120,4 +130,12 @@ export class Ship { }, amount * 1000 / this.loadingSpeed) }) } +} + +export enum FlightMode { + Normal, // Normales Fliegen zum Planeten + Orbiting, // Kreisen um den Planeten + Docking, // Gerader Flug und Landung + Undocking, + Docked } \ No newline at end of file diff --git a/src/app/scene/map.scene.ts b/src/app/scene/map.scene.ts index 1494127..e82ad73 100644 --- a/src/app/scene/map.scene.ts +++ b/src/app/scene/map.scene.ts @@ -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) { diff --git a/src/app/service/game.service.ts b/src/app/service/game.service.ts index 0fb6865..d19069f 100644 --- a/src/app/service/game.service.ts +++ b/src/app/service/game.service.ts @@ -22,6 +22,6 @@ export class GameService { } get canDrag(): boolean { - return true; + return this.showShipInfo == undefined && this.showPlanetInfo == undefined; } } \ No newline at end of file diff --git a/src/styles.scss b/src/styles.scss index 232473a..31ce052 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -10,3 +10,9 @@ html { )); } +html, body { + width: 100vw; + height: 100vh; + margin: 0; + padding: 0; +} \ No newline at end of file