diff --git a/public/sprites/ships/swift-hauler.png b/public/sprites/ships/swift-hauler.png new file mode 100644 index 0000000..bed6db3 Binary files /dev/null and b/public/sprites/ships/swift-hauler.png differ diff --git a/src/app/app.component.html b/src/app/app.component.html index e94a5be..2e36826 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,7 +1,17 @@
@if (gameService.showShipInfo) { - + } @if (gameService.showPlanetInfo) { - + +} + +@if (gameService.showBuyShip) { + +} + + + +@if (gameService.showTutorial) { + } \ No newline at end of file diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 792e63a..1761833 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,4 +1,4 @@ -import { Component, ElementRef, inject, ViewChild } from '@angular/core'; +import { Component, ElementRef, inject, LOCALE_ID, ViewChild } from '@angular/core'; import Phaser from 'phaser'; import { MapScene } from './scene/map.scene'; import { MatDialog, MatDialogModule } from '@angular/material/dialog'; @@ -6,10 +6,20 @@ 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'; +import { StatusBarComponent } from './components/status-bar/status-bar.component'; +import { BuyComponent } from './components/ships/buy/buy.component'; + +import { registerLocaleData } from '@angular/common'; +import localeDe from '@angular/common/locales/de'; +import localeDeExtra from '@angular/common/locales/extra/de'; +import { WelcomeComponent } from './components/tutorial/welcome/welcome.component'; + +registerLocaleData(localeDe, 'de-DE', localeDeExtra); @Component({ selector: 'app-root', - imports: [MatDialogModule, ShipDialogComponent, PlanetDialogComponent, DragDropModule], + imports: [MatDialogModule, ShipDialogComponent, PlanetDialogComponent, DragDropModule, StatusBarComponent, BuyComponent, WelcomeComponent], + providers: [{ provide: LOCALE_ID, useValue: 'de-DE' }], templateUrl: './app.component.html', styleUrl: './app.component.scss' }) @@ -53,4 +63,8 @@ export class AppComponent { const game = new Phaser.Game(config); } + + stopPropagation(event: any) { + event.stopPropagation(); + } } diff --git a/src/app/components/dialog/planet-dialog/planet-dialog.component.html b/src/app/components/dialog/planet-dialog/planet-dialog.component.html index c1da5d4..302e5f0 100644 --- a/src/app/components/dialog/planet-dialog/planet-dialog.component.html +++ b/src/app/components/dialog/planet-dialog/planet-dialog.component.html @@ -1,5 +1,5 @@
-
+
{{ planet.name }}
@@ -7,7 +7,8 @@
-
👥 Bevölkerung: {{ population }} 🚀
+ @if (planet.hasHarbour) { +
👥 Bevölkerung: {{ population }} 🚀
Landebuchten: {{ planet.dockCapacity }}
@@ -21,7 +22,9 @@
-
📦 Vorräte:
+
📦 Vorräte:
@for (item of storedItems; track $index) {
{{ item.type }}: {{ item.amount | number:'0.0-1' }}
@@ -38,19 +41,42 @@
-
-
📦 In Lieferung:
-
    - @for (item of goodsInTransit; track $index) { -
  • {{ item.type }}: {{ item.amount | number:'0.0-1' }}
  • - } -
-
+ @if (goodsInTransit.length > 0) { +
+
📦 In Lieferung:
+
    + @for (item of goodsInTransit; track $index) { +
  • {{ item.type }}: {{ item.amount | number:'0.0-1' }}
  • + } +
+
+ } + + + } @else { +
Baue einen Raumhafen um diesen Planeten zu erschließen, und auf die Ressourcen zuzugreifen. Es bildet sich eine kleine Siedlung um den Raumhafen. Je besser diese versorgt ist, desto mehr Menschen ziehen hin. Gibt es zu wenig Rohstoffe, verlassen die Menschen die Siedlung und ziehen ins Umland um sich selbst zu versorgen.
+
+
🏭 Produktion:
+
    + @for (item of producedItems; track $index) { +
  • {{ item.type }}: +{{ item.productionRate | number:'0.0-2' }}/s
  • + } +
+
+ }
-
- - +
+ @if (planet.hasHarbour) { + + } @else { + + + }
\ No newline at end of file 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 5e1336a..40d0c0c 100644 --- a/src/app/components/dialog/planet-dialog/planet-dialog.component.scss +++ b/src/app/components/dialog/planet-dialog/planet-dialog.component.scss @@ -4,4 +4,10 @@ position: absolute; top: 24px; left: 24px; + width: 500px; +} + +img { + width: 128px; + height: 128px; } \ 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 3a39828..c433d80 100644 --- a/src/app/components/dialog/planet-dialog/planet-dialog.component.ts +++ b/src/app/components/dialog/planet-dialog/planet-dialog.component.ts @@ -5,11 +5,12 @@ 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'; +import {CdkDragHandle, DragDropModule} from '@angular/cdk/drag-drop'; +import {MatTooltipModule} from '@angular/material/tooltip'; @Component({ selector: 'app-planet-dialog', - imports: [CommonModule, MatDialogModule, DragDropModule], + imports: [CommonModule, MatDialogModule, DragDropModule, MatTooltipModule, CdkDragHandle], templateUrl: './planet-dialog.component.html', styleUrl: './planet-dialog.component.scss' }) @@ -17,7 +18,6 @@ export class PlanetDialogComponent { public gameService: GameService = inject(GameService) ngOnInit() { - console.log(this.planet) } @@ -67,5 +67,10 @@ export class PlanetDialogComponent { this.planet.upgradeHarbour() } + + buildHarbour() { + this.planet.buildHarbour() + } + } 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 9ac2d15..4e96e82 100644 --- a/src/app/components/dialog/ship-dialog/ship-dialog.component.html +++ b/src/app/components/dialog/ship-dialog/ship-dialog.component.html @@ -1,6 +1,6 @@
-
+
{{ ship.name }}
@@ -70,7 +70,7 @@
-
+
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 f3747a4..4abcda4 100644 --- a/src/app/components/dialog/ship-dialog/ship-dialog.component.ts +++ b/src/app/components/dialog/ship-dialog/ship-dialog.component.ts @@ -2,12 +2,12 @@ import { CommonModule } from '@angular/common'; import { Component, inject } from '@angular/core'; import { GameService } from '../../../service/game.service'; import { Ship } from '../../../model/ships/ship.model'; -import { Good } from '../../../model/goods/good.interface'; import { TradeInstance } from '../../../model/planet.model'; +import { CdkDragHandle } from '@angular/cdk/drag-drop'; @Component({ selector: 'app-ship-dialog', - imports: [CommonModule], + imports: [CommonModule, CdkDragHandle], templateUrl: './ship-dialog.component.html', styleUrl: './ship-dialog.component.scss' }) diff --git a/src/app/components/ships/buy/buy.component.html b/src/app/components/ships/buy/buy.component.html new file mode 100644 index 0000000..5fc1b6f --- /dev/null +++ b/src/app/components/ships/buy/buy.component.html @@ -0,0 +1,86 @@ +
+
+ Raumschiff einsetzen +
+ +
+
🚀 Neues Schiff: Pioneer-1
+
    +
  • +
    Kosten:
    +
    {{ config.buyCost | number:'0.0-2'}} Credits
    +
  • +
  • +
    Frachtraum:
    +
    {{ config.cargoSize | number: '0.0-2'}} t
    +
  • +
  • +
    Max Geschwindigkeit:
    +
    {{ config.maxSpeed | number: '0.0-2'}} km/s
    +
  • + +
  • +
    Unterhalt:
    +
    {{ config.cost | number: '0.0-2'}} Credits
    +
  • +
+
+ +
+
🛣️ Route festlegen:
+ @if (availablePlanets.length == 0) { +
+ Um Planeten anfliegen zu können, musst du einen Weltraumhafen bauen. +
+ } +
+
+
Ausgewählte Planeten
+
+ @for (planet of selectedPlanets; track planet.name) { +
+ {{ planet.name }} +
+ } +
+
+ +
+
Verfügbare Planeten
+ +
+ @for (planet of availablePlanets; track planet.name) { +
+ {{ planet.name }} +
+ } +
+
+
+
+ +
+
Ausgewählte Route:
+
+ @for (planet of selectedPlanets; track planet.name) { +
{{ planet.name }}
+ } +
+
+ +
+ + + +
+
\ No newline at end of file diff --git a/src/app/components/ships/buy/buy.component.scss b/src/app/components/ships/buy/buy.component.scss new file mode 100644 index 0000000..b00528e --- /dev/null +++ b/src/app/components/ships/buy/buy.component.scss @@ -0,0 +1,56 @@ +:host { + position: absolute; + display: flex; + flex-direction: column; + position: absolute; + top: 24px; + left: 24px; + // width: 240px; + // height: 240px; + // background-color: var(--background-color); + // color: var(--primary-color); + width: 600px; + z-index: 1; +} + +.route-containers { + gap: 16px; + margin: 16px 0; +} + +.route-column { + flex: 1; +} + +.route-header { + font-size: 14px; + color: var(--secundary-color); + margin-bottom: 8px; +} + +.route-selection { + min-height: 100px; + border: 1px solid #202d3e; + border-radius: 8px; + padding: 8px; +} + +.planet-option { + padding: 8px; + margin: 4px 0; + background-color: #1c243b; + border-radius: 4px; + cursor: move; + + &:hover { + background-color: #3c8dbc; + } +} + +.route { + display: flex; + gap: 8px; + padding: 8px; + border: 1px solid #202d3e; + border-radius: 4px; +} \ No newline at end of file diff --git a/src/app/components/ships/buy/buy.component.spec.ts b/src/app/components/ships/buy/buy.component.spec.ts new file mode 100644 index 0000000..effc14b --- /dev/null +++ b/src/app/components/ships/buy/buy.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { BuyComponent } from './buy.component'; + +describe('BuyComponent', () => { + let component: BuyComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [BuyComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(BuyComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/ships/buy/buy.component.ts b/src/app/components/ships/buy/buy.component.ts new file mode 100644 index 0000000..b8f6b7f --- /dev/null +++ b/src/app/components/ships/buy/buy.component.ts @@ -0,0 +1,59 @@ +import { CommonModule } from '@angular/common'; +import { Component, inject } from '@angular/core'; +import { GameService } from '../../../service/game.service'; +import { CdkDrag, CdkDragDrop, CdkDragHandle, CdkDropList, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop'; +import { Planet } from '../../../model/planet.model'; +import { ShipConfig } from '../../../model/ships/ship.model'; + +@Component({ + selector: 'app-buy', + imports: [CommonModule, CdkDropList, CdkDrag, CdkDragHandle], + templateUrl: './buy.component.html', + styleUrl: './buy.component.scss' +}) +export class BuyComponent { + private gameService: GameService = inject(GameService); + + availablePlanets: Planet[] = this.gameService.planets.filter(p => p.hasHarbour); + selectedPlanets: Planet[] = []; + config: ShipConfig = { + name: 'Pioneer-01', + acceleration: 500, + cargoSize: 10, + cost: 0.6, + loadingSpeed: 25, + maxSpeed: 500, + planetRoute: [], + buyCost: 2000 + } + + canAffordShip() { + return this.gameService.money >= 3000; + } + + buyShip() { + if (!this.canAffordShip()) return; + this.gameService.createShip({ + ...this.config, + planetRoute: this.selectedPlanets + }) + this.close(); + } + + close() { + this.gameService.showBuyShip = false; + } + + drop(event: CdkDragDrop) { + if (event.previousContainer === event.container) { + moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); + } else { + transferArrayItem( + event.previousContainer.data, + event.container.data, + event.previousIndex, + event.currentIndex, + ); + } + } +} diff --git a/src/app/components/status-bar/status-bar.component.html b/src/app/components/status-bar/status-bar.component.html new file mode 100644 index 0000000..39fc4f1 --- /dev/null +++ b/src/app/components/status-bar/status-bar.component.html @@ -0,0 +1,8 @@ +
+ +
+ Credits: {{gameService.money | number:'0.0-0'}} +
+ + +
\ No newline at end of file diff --git a/src/app/components/status-bar/status-bar.component.scss b/src/app/components/status-bar/status-bar.component.scss new file mode 100644 index 0000000..a39c4da --- /dev/null +++ b/src/app/components/status-bar/status-bar.component.scss @@ -0,0 +1,11 @@ +:host { + display: flex; + position: absolute; + top: 4px; + left: 40%; + pointer-events: auto; +} + +.gamebar { + align-items: center; +} \ No newline at end of file diff --git a/src/app/components/status-bar/status-bar.component.spec.ts b/src/app/components/status-bar/status-bar.component.spec.ts new file mode 100644 index 0000000..c808399 --- /dev/null +++ b/src/app/components/status-bar/status-bar.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { StatusBarComponent } from './status-bar.component'; + +describe('StatusBarComponent', () => { + let component: StatusBarComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [StatusBarComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(StatusBarComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/status-bar/status-bar.component.ts b/src/app/components/status-bar/status-bar.component.ts new file mode 100644 index 0000000..aeb6720 --- /dev/null +++ b/src/app/components/status-bar/status-bar.component.ts @@ -0,0 +1,26 @@ +import { Component, inject } from '@angular/core'; +import { GameService } from '../../service/game.service'; +import { CommonModule } from '@angular/common'; + +@Component({ + selector: 'app-status-bar', + imports: [CommonModule], + templateUrl: './status-bar.component.html', + styleUrl: './status-bar.component.scss' +}) +export class StatusBarComponent { + public gameService: GameService = inject(GameService); + + + onClick(event: any) { + event.stopPropagation(); + } + + print() { + console.log("PRI") + } + + toggleBuy() { + this.gameService.showBuyShip = !this.gameService.showBuyShip; + } +} diff --git a/src/app/components/tutorial/welcome/welcome.component.html b/src/app/components/tutorial/welcome/welcome.component.html new file mode 100644 index 0000000..cf5c838 --- /dev/null +++ b/src/app/components/tutorial/welcome/welcome.component.html @@ -0,0 +1,57 @@ +
+
+

🚀 Willkommen bei StellarLines

+
+ +
+
+

Deine interplanetare Handelsgesellschaft

+

+ Willkommen im Jahr 2184! +
+
+ Als aufstrebender Unternehmer ist es deine Aufgabe, + ein florierendes Handelsnetzwerk zwischen den Planeten aufzubauen. Gründe ein + interplanetares Frachtunternehmen, transportiere wichtige Güter zwischen den + Kolonien und erwirtschafte Profite mit jedem erfolgreichen Transport. +

+
+ +
+

🎯 Deine Ziele:

+
    +
  • Errichte Raumhäfen auf verschiedenen Planeten
  • +
  • Baue Handelsrouten zwischen den Planeten auf
  • +
  • Versorge die Siedlungen mit wichtigen Gütern
  • +
  • Erweitere deine Handelsflotte
  • +
  • Schaffe prosperierende Kolonien
  • +
+
+ +
+

🎮 So spielst du:

+
    +
  1. Wähle einen Planeten und baue einen Raumhafen
  2. +
  3. Kaufe dein erstes Handelsschiff (Hierfür brauchst du 2 Planeten)
  4. +
  5. Lege Handelsrouten zwischen Planeten fest
  6. +
  7. Versorge die Siedlungen mit Rohstoffen
  8. +
  9. Erwirtschafte Gewinne durch geschickten Handel
  10. +
+
+ +
+

💡 Tipp:

+

+ Achte auf die Bedürfnisse der Siedlungen. Gut versorgte Kolonien wachsen und + erhöhen ihren Bedarf an Waren. Bei Mangel wandern die Siedler ab! Für den Anfang benötigen die Kolonien nur Nahrung und Wasser. Mit wachsender Bevölkerung steigt auch der Bedarf an weiteren Rohstoffen. +

+ Du beginnst mit 10.000 Credits. Gib sie weise aus, Schiffe und Raumhäfen kosten Unterhalt. Je mehr Waren du transportierst, desto mehr Geld verdienst du auch. Planeten kaufen allerdings nur Waren, die sie auch brauchen. + Schiffe auf Handelsrouten fliegen die Planeten ab, und versorgen sie automatisch mit den Gütern die sie benötigen. +

+
+
+ +
+ +
+
\ No newline at end of file diff --git a/src/app/components/tutorial/welcome/welcome.component.scss b/src/app/components/tutorial/welcome/welcome.component.scss new file mode 100644 index 0000000..bc4be99 --- /dev/null +++ b/src/app/components/tutorial/welcome/welcome.component.scss @@ -0,0 +1,20 @@ +:host { + display: flex; + flex-direction: column; + position: absolute; + top: 24px; + left: 24px; + right: 24px; + bottom: 24px; +} + +.ui-body{ + overflow-y: auto; +} + +.ui-panel { + height: 100%; + overflow: hidden; + display: flex; + flex-direction: column; +} \ No newline at end of file diff --git a/src/app/components/tutorial/welcome/welcome.component.spec.ts b/src/app/components/tutorial/welcome/welcome.component.spec.ts new file mode 100644 index 0000000..92182b5 --- /dev/null +++ b/src/app/components/tutorial/welcome/welcome.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WelcomeComponent } from './welcome.component'; + +describe('WelcomeComponent', () => { + let component: WelcomeComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [WelcomeComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(WelcomeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/tutorial/welcome/welcome.component.ts b/src/app/components/tutorial/welcome/welcome.component.ts new file mode 100644 index 0000000..98d7b02 --- /dev/null +++ b/src/app/components/tutorial/welcome/welcome.component.ts @@ -0,0 +1,18 @@ +import { Component, inject } from '@angular/core'; +import { GameService } from '../../../service/game.service'; + +@Component({ + selector: 'app-welcome', + imports: [], + templateUrl: './welcome.component.html', + styleUrl: './welcome.component.scss' +}) +export class WelcomeComponent { + + gameService = inject(GameService); + + + close() { + this.gameService.showTutorial = false; + } +} diff --git a/src/app/data/planets.data.ts b/src/app/data/planets.data.ts index 928b0ed..369fad2 100644 --- a/src/app/data/planets.data.ts +++ b/src/app/data/planets.data.ts @@ -3,21 +3,20 @@ import { PlanetInit } from "../model/planet.model"; export const PLANETCONFIGS: { x: number, y: number, texture: string, config: PlanetInit}[] = [ { - x: 200, - y: 300, + x: 600, + y: 800, texture: 'terra-nova', config: { name: 'Terra Nova', initialGoods: [ - { type: GoodType.Wasser, amount: 120 }, - { type: GoodType.Nahrung, amount: 80, productionBonus: 1.3 }, - { type: GoodType.Erz, amount: 100 } + { type: GoodType.Wasser, amount: 15 }, + { type: GoodType.Nahrung, amount: 8, productionBonus: 1.3 }, ] } }, { - x: 600, - y: 200, + x: 3000, + y: 900, texture: 'mechanica-prime', config: { name: 'Mechanica Prime', @@ -31,21 +30,21 @@ export const PLANETCONFIGS: { x: number, y: number, texture: string, config: Pla } }, { - x: 400, - y: 700, + x: 1800, + y: 2800, texture: 'aqualis', config: { name: 'Aqualis', initialGoods: [ - { type: GoodType.Wasser, amount: 300, productionBonus: 2.0 }, - { type: GoodType.Nahrung, amount: 60 }, + { type: GoodType.Wasser, amount: 30, productionBonus: 2.0 }, + { type: GoodType.Nahrung, amount: 15 }, { type: GoodType.Treibstoff, amount: 10 } ] } }, { - x: 900, - y: 500, + x: 3900, + y: 2500, texture: 'planet', config: { name: 'Ferron', @@ -59,8 +58,8 @@ export const PLANETCONFIGS: { x: number, y: number, texture: string, config: Pla } }, { - x: 800, - y: 800, + x: 5400, + y: 1400, texture: 'novus-reach', config: { name: 'Novus Reach', @@ -71,4 +70,31 @@ export const PLANETCONFIGS: { x: number, y: number, texture: string, config: Pla ] } } + , + { + x: 3700, + y: 4600, + texture: 'novus-reach', + config: { + name: 'Novus Reach 2', + initialGoods: [ + { type: GoodType.Nahrung, amount: 90, productionBonus: 1.5 }, + { type: GoodType.Wasser, amount: 50 }, + { type: GoodType.Bauteile, amount: 15 } + ] + } + }, + { + x: 6000, + y: 3600, + texture: 'terra-nova', + config: { + name: 'Terra Nova 2', + initialGoods: [ + { type: GoodType.Wasser, amount: 120 }, + { type: GoodType.Nahrung, amount: 80, productionBonus: 1.3 }, + { type: GoodType.Erz, amount: 100 } + ] + } + } ] \ No newline at end of file diff --git a/src/app/model/goods/good-config.ts b/src/app/model/goods/good-config.ts index 58ec627..c277342 100644 --- a/src/app/model/goods/good-config.ts +++ b/src/app/model/goods/good-config.ts @@ -9,8 +9,8 @@ export interface GoodConfig { export const GOODS_DATA: Record = { [GoodType.Erz]: { baseProduction: 1.0, baseDemand: 0, storageLimit: 500, isRawResource: true }, - [GoodType.Wasser]: { baseProduction: 1, baseDemand: 0.015, storageLimit: 300, isRawResource: true }, - [GoodType.Nahrung]: { baseProduction: 1, baseDemand: 0.01, storageLimit: 200, isRawResource: true }, + [GoodType.Wasser]: { baseProduction: 1, baseDemand: 0.0015, storageLimit: 300, isRawResource: true }, + [GoodType.Nahrung]: { baseProduction: 1, baseDemand: 0.001, storageLimit: 200, isRawResource: true }, [GoodType.Metall]: { baseProduction: 1, baseDemand: 0, storageLimit: 300, isRawResource: false }, [GoodType.Treibstoff]: { baseProduction: 1, baseDemand: 0, storageLimit: 150, isRawResource: false }, [GoodType.Elektronik]: { baseProduction: 1, baseDemand: 0, storageLimit: 100, isRawResource: false }, diff --git a/src/app/model/planet.model.ts b/src/app/model/planet.model.ts index 4d1e3d1..ad0a405 100644 --- a/src/app/model/planet.model.ts +++ b/src/app/model/planet.model.ts @@ -1,8 +1,7 @@ -import { interval, Subscription } from "rxjs"; +import { interval } from "rxjs"; import { GoodType } from "./goods/good-type.enum"; import { GOODS_DATA } from "./goods/good-config"; import { Good } from "./goods/good.interface"; -import { ShipUi } from "./ship"; import { Ship } from "./ships/ship.model"; import { GameService } from "../service/game.service"; @@ -17,7 +16,7 @@ export class Planet { public isGrowing: boolean = false; private populationGrowthRate = 0.002; // Basiswachstum pro Tick (%) private populationDeclineRate = 0.005; // Basisrückgang bei Mangel (%) - demandSecondsBuffer = 30; // Anfrage immer mindestens 30 Sekunden überleben + demandSecondsBuffer = 150; // Anfrage immer mindestens 30 Sekunden überleben private productionLevel: Map = new Map(); private dockedShips: Ship[] = []; @@ -26,6 +25,8 @@ export class Planet { private gameService: GameService; + hasHarbour = false; + constructor(config: PlanetInit, gameService: GameService) { this.name = config.name; @@ -133,12 +134,14 @@ export class Planet { const inTransit = this.goodsInTransit.filter(g => g.type == good.type).reduce((acc, succ) => acc + succ.amount, 0); + if (demand < good.amount + inTransit) { + console.log(`[${this.name}] Skipping request for ${good.type}: ${inTransit} in transit + ${good.amount} > ${demand}`); continue; // Es werden genug geliefert. } result.push({ - amount: demand * 3, // Request 3x buffer amount + amount: demand * 5, // Request 3x buffer amount type: good.type, target: this.name, }) @@ -286,10 +289,7 @@ export class Planet { } 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(',')) } get goodsInTransit(): TradeInstance[] { @@ -303,6 +303,12 @@ export class Planet { this.dockCapacity++; } + + buildHarbour() { + this.hasHarbour = true; + this.gameService.money -= 3000; + } + } export interface PlanetInit { diff --git a/src/app/model/ship.ts b/src/app/model/ship.ts index 2d970e7..1e8b54f 100644 --- a/src/app/model/ship.ts +++ b/src/app/model/ship.ts @@ -8,23 +8,23 @@ export class ShipUi extends Phaser.Physics.Arcade.Sprite { private velocity = new Phaser.Math.Vector2(0, 0); private target: PlanetUi | null = null; private targetVector: Phaser.Math.Vector2 | null = null; // Später Planet! - public model: Ship = new Ship(); + public model: 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) { + constructor(scene: MapScene, x: number, y: number, gameService: GameService, ship: Ship) { super(scene, x, y, 'ship'); this.gameService = gameService; - + this.model = ship; scene.add.existing(this); scene.physics.add.existing(this); this.setOrigin(0.5); this.setScale(1); // oder z. B. 0.5 je nach Bildgröße - this.setDisplaySize(32, 32) + this.setDisplaySize(96, 128) this.setCollideWorldBounds(true); const conf: Phaser.Types.Input.InputConfiguration = { useHandCursor: true @@ -62,7 +62,7 @@ export class ShipUi extends Phaser.Physics.Arcade.Sprite { switch (this.model.flightMode) { case FlightMode.Normal: - if (distance < 150) { + if (distance < (this.target.height / 2) + 100) { this.startOrbit(this.target); return; } @@ -73,7 +73,7 @@ export class ShipUi extends Phaser.Physics.Arcade.Sprite { break; case FlightMode.Docking: this.flyDirectlyToTarget(to, dt); - if (distance < 5) { + if (distance < 150) { this.land(); } break; @@ -104,21 +104,21 @@ export class ShipUi extends Phaser.Physics.Arcade.Sprite { 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)); + this.setRotation(this.velocity.angle() + Phaser.Math.DegToRad(90)); } 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 targetX = center.x + Math.cos(this.orbitAngle) * this.target!.height / 2; + const targetY = center.y + Math.sin(this.orbitAngle) * this.target!.height / 2; 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)); + this.setRotation(this.body.velocity.angle() + Phaser.Math.DegToRad(90)); // Umdrehungen zählen if (this.orbitAngle >= Math.PI * 2) { diff --git a/src/app/model/ships/ship.model.ts b/src/app/model/ships/ship.model.ts index b4da8d4..37fa974 100644 --- a/src/app/model/ships/ship.model.ts +++ b/src/app/model/ships/ship.model.ts @@ -1,17 +1,49 @@ +import { interval } from "rxjs"; import { Good } from "../goods/good.interface"; import { Planet, TradeInstance } from "../planet.model"; import { TradeRoute } from "../routes/trade-route.model"; +import { GameService } from "../../service/game.service"; + +export interface ShipConfig { + name: string; + cargoSize: number; + acceleration: number; + maxSpeed: number; + loadingSpeed: number; + cost: number; + planetRoute: Planet[]; + buyCost: number; +} export class Ship { - public acceleration = 10; // Pixel pro Sekunde² - public maxSpeed = 100; + public acceleration = 20; // Pixel pro Sekunde² + public maxSpeed = 500; public currentSpeed = 0; - public cargoSize = 50; + public cargoSize = 10; cargoSpace: TradeInstance[] = []; public route: TradeRoute | undefined = new TradeRoute([]) public name = "Pioneer-01-" + Math.round(Math.random() * 100); public loadingSpeed = 3.5; + public cost = 0.5 + + private updateInterval = interval(1000); + private gameService: GameService; + + constructor(gameService: GameService, config: ShipConfig) { + this.gameService = gameService; + this.acceleration = config.acceleration; + this.cargoSize = config.cargoSize; + this.maxSpeed = config.maxSpeed; + this.loadingSpeed = config.loadingSpeed; + this.cost = config.cost; + this.route = new TradeRoute(config.planetRoute) + + this.updateInterval.subscribe(() => { + this.update(); + }) + } + public status: 'loading' | 'unloading' | 'waiting' | 'idle' | 'traveling' = 'idle'; public flightMode: FlightMode = FlightMode.Normal; @@ -89,6 +121,7 @@ export class Ship { if (existing) { existing.amount -= (cargo.amount / steps); to.amount += (cargo.amount / steps); + this.gameService.money += 5; } await this.waitForLoading(1) } @@ -126,6 +159,10 @@ export class Ship { }, amount * 1000 / this.loadingSpeed) }) } + + update() { + this.gameService.money -= this.cost; + } } export enum FlightMode { diff --git a/src/app/scene/map.scene.ts b/src/app/scene/map.scene.ts index 1a0d9dc..780f739 100644 --- a/src/app/scene/map.scene.ts +++ b/src/app/scene/map.scene.ts @@ -21,14 +21,14 @@ export class MapScene extends Phaser.Scene { } preload() { - this.load.image('ship', 'sprites/ships/01-starter.png'); + this.load.image('ship', 'sprites/ships/swift-hauler.png'); this.load.image('harbour', 'sprites/buildings/harbour.png'); - this.load.image('planet', 'sprites/planets/sm/planet-1.png'); - this.load.image('terra-nova', 'sprites/planets/sm/terra-nova.png'); - this.load.image('mechanica-prime', 'sprites/planets/sm/mechanica-prime.png'); - this.load.image('novus-reach', 'sprites/planets/sm/novus-reach.png'); - this.load.image('aqualis', 'sprites/planets/sm/aqualis.png'); - this.load.image('ferron', 'sprites/planets/sm/ferron.png'); + this.load.image('planet', 'sprites/planets/planet-1.png'); + this.load.image('terra-nova', 'sprites/planets/terra-nova.png'); + this.load.image('mechanica-prime', 'sprites/planets/mechanica-prime.png'); + this.load.image('novus-reach', 'sprites/planets/novus-reach.png'); + this.load.image('aqualis', 'sprites/planets/aqualis.png'); + this.load.image('ferron', 'sprites/planets/ferron.png'); } create() { @@ -79,7 +79,7 @@ export class MapScene extends Phaser.Scene { const now = Date.now(); if (now - this.lastZoomTime < 50) return; // nur alle 100ms this.lastZoomTime = now; - const factor = 1.05; + const factor = 1.1; if (deltaY > 0) { this.camera.setZoom(this.camera.zoom / factor); } else { @@ -91,23 +91,20 @@ export class MapScene extends Phaser.Scene { this.camera.setZoom(Phaser.Math.Clamp(this.camera.zoom, minScale, 1)); }); - this.createShip(); - setTimeout(() => { - this.createShip(); - }, 100) + this.camera.setZoom(0.4) + + this.gameService.onShipCreate.subscribe(ship => { + const ui = new ShipUi(this, 100, 100, this.gameService, ship); + ui.activateRoute(); + this.physics.world.enable(ui); + this.ships.push(ui); + }) + // this.enableMouseLogging(); } - 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(); - this.gameService.ships.push(s.model) - } override update(time: number, delta: number): void { if (this.ship) { @@ -132,6 +129,7 @@ export class MapScene extends Phaser.Scene { }); this.planets.push(planet); this.gameService.planets.push(planet.model); + } } @@ -146,4 +144,20 @@ export class MapScene extends Phaser.Scene { getPlanetUiByName(name: string) { return this.planets.find(p => p.model.name == name); } + + enableMouseLogging() { + let mouseText: Phaser.GameObjects.Text; + + this.input.on('pointermove', (pointer: Phaser.Input.Pointer) => { + if (!mouseText) { + mouseText = this.add.text(30, 30, '', { + fontSize: '160px', + color: '#00ff00', + backgroundColor: '#000000', + padding: { x: 5, y: 2 } + }).setScrollFactor(0).setDepth(9999); + } + mouseText.setText(`X: ${pointer.worldX.toFixed(0)}\nY: ${pointer.worldY.toFixed(0)}`); + }); + } } \ No newline at end of file diff --git a/src/app/service/game.service.ts b/src/app/service/game.service.ts index 2768cd2..76c5a3a 100644 --- a/src/app/service/game.service.ts +++ b/src/app/service/game.service.ts @@ -1,8 +1,6 @@ -import { inject, Injectable } from "@angular/core"; -import { MatDialog, MatDialogRef } from "@angular/material/dialog"; -import { PlanetDialogComponent } from "../components/dialog/planet-dialog/planet-dialog.component"; +import { EventEmitter, Injectable } from "@angular/core"; import { Planet } from "../model/planet.model"; -import { Ship } from "../model/ships/ship.model"; +import { Ship, ShipConfig } from "../model/ships/ship.model"; @Injectable({ providedIn: 'root', @@ -10,10 +8,17 @@ import { Ship } from "../model/ships/ship.model"; export class GameService { public showPlanetInfo: Planet | undefined; public showShipInfo: Ship | undefined; + public showBuyShip = false; public ships: Ship[] = []; public planets: Planet[] = []; + showTutorial = true; + + public money = 10000; + + onShipCreate: EventEmitter = new EventEmitter(); + constructor() {} showDialog(planet: Planet) { @@ -27,4 +32,15 @@ export class GameService { get canDrag(): boolean { return this.showShipInfo == undefined && this.showPlanetInfo == undefined; } + + + createShip(config: ShipConfig) { + if (this.money < config.buyCost) { + return; + } + this.money -= config.buyCost; + const ship = new Ship(this, config); + this.ships.push(ship); + this.onShipCreate.emit(ship); + } } \ No newline at end of file diff --git a/src/app/ui/planet-status.ui.ts b/src/app/ui/planet-status.ui.ts index 7e54b1a..6810bdd 100644 --- a/src/app/ui/planet-status.ui.ts +++ b/src/app/ui/planet-status.ui.ts @@ -25,6 +25,7 @@ export class PlanetStatus { } update() { + this.container.setScale(1 / this.scene.cameras.main.zoom) const population = Math.floor(this.getPopulation()); const growth = this.getGrowthState(); const criticalGoods = this.getCriticalGoods(); @@ -41,7 +42,13 @@ export class PlanetStatus { criticalText = ' 🛑 ' + criticalGoods.map(g => g.toString().substring(0, 2)).join(','); } - this.text.setText(`${this.planetSprite.model.name}, 👥 ${population} ${growthSymbol}${criticalText}`); + let text = `${this.planetSprite.model.name}`; + + if (this.planetSprite.model.hasHarbour) { + text += `, 👥 ${population} ${growthSymbol}${criticalText}` + } + + this.text.setText(text); // Position immer über Planet aktualisieren this.container.setPosition(this.planetSprite.x, this.planetSprite.y - (this.planetSprite.height / 2) - 5); diff --git a/src/app/ui/planet.ui.ts b/src/app/ui/planet.ui.ts index 057f142..c20af56 100644 --- a/src/app/ui/planet.ui.ts +++ b/src/app/ui/planet.ui.ts @@ -10,6 +10,7 @@ export class PlanetUi extends Phaser.Physics.Arcade.Sprite { public rightClick: EventEmitter = new EventEmitter(); public model: Planet; private status: PlanetStatus | undefined; + private harbourImage!: any; private updateInterval = interval(1000) @@ -41,8 +42,11 @@ export class PlanetUi extends Phaser.Physics.Arcade.Sprite { this.updateInterval.subscribe(() => this.update()) - const image = this.scene.add.image(this.getWorldPoint().x, this.getWorldPoint().y, 'harbour') - image.setDisplaySize(64, 96); + this.harbourImage = this.scene.add.image(this.getWorldPoint().x, this.getWorldPoint().y, 'harbour') + this.harbourImage.setDisplaySize(164, 256); + this.harbourImage.setVisible(false); + + // this.setScale(0.5) // this.setInteractive(new Phaser.Geom.Circle(x, y, 200), Phaser.Geom.Circle.Contains); } @@ -65,6 +69,8 @@ export class PlanetUi extends Phaser.Physics.Arcade.Sprite { } this.status?.update(); + this.harbourImage.setVisible(this.model.hasHarbour) + } } \ No newline at end of file diff --git a/src/styles/_ui.scss b/src/styles/_ui.scss index d5b7de2..a298489 100644 --- a/src/styles/_ui.scss +++ b/src/styles/_ui.scss @@ -5,9 +5,13 @@ $color-background: #0b1120; $color-panel: #1c243b; $color-text-primary: #e0f0ff; $color-text-secondary: #a0b8d8; -$color-water: #4fc1e9; -$color-food: #f7c06b; +$color-water: #137092; +$color-food: #a87f41; $color-ore: #c1543a; +$color-metal: #4d4d4d; +$color-fuel: #2e5c2d; +$color-electronics: #4d2d6b; +$color-workparts: #441631; $color-success: #4CAF50; $color-error: #f44336; $color-button: #1c243b; @@ -86,6 +90,10 @@ $font-size-base: 14px; &.wasser .progress-fill { background-color: $color-water; } &.nahrung .progress-fill { background-color: $color-food; } &.erz .progress-fill { background-color: $color-ore; } + &.metall .progress-fill { background-color: $color-metal; } + &.treibstoff .progress-fill { background-color: $color-fuel; } + &.elektronik .progress-fill { background-color: $color-electronics; } + &.bauteile .progress-fill { background-color: $color-workparts; } } .button { @@ -113,6 +121,7 @@ $font-size-base: 14px; .flex { display: flex; justify-content: space-between; + gap: 8px; } .ui-body > .ui-section {