Release Void Bastion alpha.5
This commit is contained in:
1 parent
118061b8d7
commit
56077e88a5
18 files changed
+488
-40
No files matched your search
+41
-2
@@ -1,6 +1,6 @@
|
||||
# Void Bastion – Alpha-Testcheckliste
|
||||
|
||||
Diese Liste wird vor jeder öffentlichen Windows-Alpha vollständig abgearbeitet. Automatische Prüfungen sind mit **A**, manuelle Sicht- und Bedienprüfungen mit **M** gekennzeichnet.
|
||||
Diese Liste wird fortlaufend erweitert und vor jeder öffentlichen Version abgearbeitet. Automatische Prüfungen sind mit **A**, manuelle Sicht- und Bedienprüfungen mit **M** gekennzeichnet. Neue Wünsche werden hier ergänzt; Punkte werden erst nach erfolgreicher Prüfung abgehakt.
|
||||
|
||||
## Build und Installation
|
||||
|
||||
@@ -15,7 +15,7 @@ Diese Liste wird vor jeder öffentlichen Windows-Alpha vollständig abgearbeitet
|
||||
## Darstellung und Bedienung
|
||||
|
||||
- [ ] **M** 1920 × 1080, 2560 × 1440 und eine kleinere 16:9-Auflösung prüfen: Karte und UI bleiben scharf und vollständig sichtbar.
|
||||
- [ ] **M** Alle drei Karten prüfen: Straße, Gegnerlaufweg, Raster und Bausperren stimmen exakt überein.
|
||||
- [ ] **M** Alle sieben Karten prüfen: Straße, Gegnerlaufweg, Raster und Bausperren stimmen exakt überein.
|
||||
- [ ] **M** Reaktoren und freie Weltraumbereiche sind auf allen Karten unbebaubar.
|
||||
- [ ] **M** Reichweitenvorschau erscheint vor dem Bau; Turmwerte und Upgrade-Mehrwert sind lesbar.
|
||||
- [ ] **M** Zurück-Schaltfläche bricht eine laufende Runde verständlich ab.
|
||||
@@ -44,3 +44,42 @@ Diese Liste wird vor jeder öffentlichen Windows-Alpha vollständig abgearbeitet
|
||||
- [ ] **M** Download-Schaltfläche auf `https://void-bastion.gergames.de/` lädt die aktuelle Setup-Datei.
|
||||
- [ ] **M** Datenschutz, Impressum, Supportadresse und Kontolöschseite sind öffentlich erreichbar.
|
||||
- [ ] **M** Änderungen, bekannte Fehler und Testhinweise stehen in den Release-Notizen.
|
||||
|
||||
## Neue offene Punkte (Stand 06.10.2026)
|
||||
|
||||
- [ ] **M** Android-APK auf einem echten Android-Handy installieren und Start, Touch-Steuerung, verschiedene Bildschirmformate, App-Wechsel/Fortsetzen und Performance prüfen. Bisheriger erfolgreicher Start war nur im Emulator.
|
||||
- [ ] **A/M** Android-Audiofehler beheben: Laufzeit-generierte Audioeffekte sind auf Android derzeit deaktiviert; importierte Audioassets einsetzen und auf echtem Gerät prüfen.
|
||||
- [ ] **A/M** Android-AdMob-Einbindung prüfen: Beim Test wurde das native AdMob-Modul nicht gefunden; Testanzeigen erst nach erfolgreicher Integration und Einwilligungsprüfung aktivieren.
|
||||
- [ ] **A** Android-Release-Pipeline erstellen: signierter Release-Build/AAB, sichere Ablage des Signierschlüssels und fortlaufender `versionCode`. Export-Presets stehen jetzt auf Code 2; das vorhandene APK ist weiterhin der alte Debug-Testbuild mit Code 1. Export des neuen APK wartet auf den Java-SDK-Pfad in Godot.
|
||||
- [ ] **M** Schwierigkeit neu balancieren: aktuell schafft der Entwickler alle Karten auf allen Schwierigkeitsstufen problemlos. Schwerpunkt ist **Schwer**, ohne Leicht/Mittel unfair zu machen.
|
||||
- [ ] **A/M** Weitere Gegnerschiffe mit klar unterscheidbaren Rollen, visueller Silhouette und passenden Kontern entwerfen, implementieren und gegen alle sechs Türme testen.
|
||||
- [ ] **M** Online-Koop mit zwei echten Geräten vollständig spielen; Emulator-/Integrationstests ersetzen diesen Test nicht.
|
||||
- [ ] **M** Datenschutzhinweise, Store-Datenerklärungen und Kontolöschung vor einer öffentlichen Store-Veröffentlichung final prüfen.
|
||||
|
||||
## Abarbeitung am 06.10.2026
|
||||
|
||||
- [x] Drei zusätzliche Gegnertypen mit eigener Grafik und Daten angelegt: schneller Abfangjäger, dauerhafter Schildkreuzer und schwerer Belagerer.
|
||||
- [x] Gegnertypen kommen wellenabhängig ins Spiel; auf Schwer erscheinen sie früher (Abfangjäger ab Welle 3, Schildkreuzer ab 5, Belagerer ab 8). Bosswellen verwenden den Belagerer.
|
||||
- [x] Schwer startet jetzt mit 340 Energie und 12 Reaktorleben; die Belohnung beträgt 12 Kernkristalle. Gegnerzahl und Gegnerhülle ziehen ab der Missionsmitte zusätzlich an.
|
||||
- [x] Automatisierter Godot-Smoke-Test besteht (`VOID_BASTION_SMOKE_TEST_OK`); Windows-Testexport wurde erstellt.
|
||||
- [x] Nakama-Laufzeitsimulation besteht (`VOID_BASTION_SERVER_SIM_OK`).
|
||||
- [ ] **A** Windows-Testexport war erfolgreich, meldete aber `app_icon.png` als ungültige Icon-Datei; vor dem nächsten Installer-/Store-Build das Iconformat prüfen und reparieren.
|
||||
- [ ] Manuelle Spieltests für Schwierigkeitsbalance und alle neuen Gegner bleiben offen; die automatisierten Tests beweisen noch nicht, dass Schwer in echten Läufen fair und fordernd ist.
|
||||
- [ ] Android-Testbuild mit den neuen Änderungen noch nicht exportiert: Godot meldet, dass `export/android/java_sdk_path` leer ist. Das JDK ist unter `C:\Program Files\Android\Android Studio\jbr` vorhanden; in Godot unter Editor-Einstellungen > Export > Android als Java-SDK-Pfad setzen, dann Export erneut ausführen. Das vorhandene Emulator-APK ist noch älter; ein echter Android-Gerätetest steht ebenfalls aus.
|
||||
|
||||
## Gegnervielfalt und Balance
|
||||
|
||||
- [ ] **M** Neue gegnerische Flugzeuge/Schiffe mit unterschiedlichen Rollen spezifizieren (z. B. schneller leichter Jäger, gepanzertes Schiff, Schildträger, Unterstützer und Schwarmziel); jede Rolle braucht erkennbare Silhouette und passende Konter.
|
||||
- [ ] **A/M** Pro Gegnertyp Werte und Verhalten dokumentieren: Lebenspunkte, Tempo, Schild/Resistenz, Reaktor-Schaden, Belohnung und passende Türme.
|
||||
- [ ] **M** Alle sieben Karten auf Leicht, Mittel und Schwer vollständig testen und Schwierigkeit, Ressourcen, Gegnerzusammensetzung sowie Sieg-/Niederlagenquote protokollieren.
|
||||
- [ ] **M** Schwer muss spürbar fordernd sein, aber mit sinnvoller Strategie schaffbar; Leicht und Mittel bleiben fair abgestuft.
|
||||
- [ ] **A/M** Wellenmischung je Schwierigkeit testen; Schwierigkeit soll nicht allein durch mehr Lebenspunkte entstehen.
|
||||
- [ ] **M** Testläufe mit Siegquote, Reaktorleben und verwendeten Türmen festhalten; Balance iterativ nach den Ergebnissen anpassen.
|
||||
|
||||
## Android-Test und Store-Vorbereitung
|
||||
|
||||
- [ ] **M** Auf echtem Gerät: Installation, erster Start, Touch-Bauen/Upgraden/Verkaufen, Wellen, Pause, Zurück, App-Wechsel und erneutes Öffnen prüfen.
|
||||
- [ ] **M** Auf kleinem und großem Display prüfen, dass Spielfeld und Bedienelemente erreichbar und nicht abgeschnitten sind.
|
||||
- [ ] **M** Android-Audio auf echtem Gerät prüfen; bis dahin bleibt die Android-spezifische Audiodeaktivierung bestehen.
|
||||
- [ ] **M** Android-Testanzeigen erst freigeben, wenn das native Plugin geladen wird, Anzeigen sichtbar sind und Datenschutz-/Einwilligungsablauf geprüft wurde.
|
||||
- [ ] **M** Google-Play-internen Test mit signiertem AAB durchführen; Release-Schlüssel geschützt und außerhalb des Repositories verwahren.
|
||||
@@ -50,6 +50,28 @@ Color palette: dark titanium, worn steel, deep red markings, red-orange emissive
|
||||
Constraints: genuinely transparent background, no labels, no text, no logos, no watermark, no cropped parts, no perspective tilt
|
||||
```
|
||||
|
||||
## Neue Gegnerschiffe (Balance-Iteration)
|
||||
|
||||
Die Rollenbilder liegen separat, damit die zusätzlichen Schiffe trotz unterschiedlicher Silhouette ohne Sprite-Sheet-Cropping angezeigt werden: `enemy_interceptor.png`, `enemy_shield_carrier.png` und `enemy_siege_destroyer.png`. Alle drei zeigen die Flugrichtung nach rechts; der Laufcode dreht das jeweilige Bild passend zur Route. Quelle: integrierter OpenAI-Bildgenerator, transparenter Hintergrund, Stilreferenz war `enemy_ships.png`.
|
||||
|
||||
**Abfangjäger** — schnell, schmal, geringe Hülle und höherer Reaktorschaden bei Durchbruch.
|
||||
|
||||
```text
|
||||
Einzelner transparenter feindlicher Abfangjäger für Void Bastion, scharf-winklige schmale Flügel, vollständig sichtbar und nach rechts ausgerichtet (Bug rechts, Triebwerke links), verwitterte gunmetal- und rote Panzerplatten, orange Triebwerke, realistischer 3D-gerenderter Top-down-3/4-Sci-Fi-Spielgrafikstil passend zum bestehenden Gegner-Sprite, keine Umgebung, kein Text, kein Rahmen, kein Wasserzeichen.
|
||||
```
|
||||
|
||||
**Schildkreuzer** — mittelgroß, dauerhaft abgeschirmt und mit blau-cyanfarbenen Emittern deutlich erkennbar.
|
||||
|
||||
```text
|
||||
Einzelner transparenter feindlicher Schild-Unterstützer für Void Bastion, breite kompakte Silhouette, cyanfarbene Energie-Emitter-Ringe und leuchtender Kern direkt im Schiff, keine äußere Schutzblase, vollständig sichtbar und nach rechts ausgerichtet, verwitterte gunmetal- und rote Panzerplatten, realistische 3D-gerenderte Sci-Fi-Spielgrafik passend zum bestehenden Gegner-Sprite, keine Umgebung, kein Text, kein Rahmen, kein Wasserzeichen.
|
||||
```
|
||||
|
||||
**Belagerer** — schweres, langsames Schiff mit breiter Silhouette und hohem Reaktorschaden; wird außerdem für Bosswellen genutzt.
|
||||
|
||||
```text
|
||||
Einzelner transparenter schwerer feindlicher Belagerungszerstörer für Void Bastion, breite massive gepanzerte Silhouette mit mehreren sichtbaren Kanonen und drei starken orangefarbenen Triebwerken, vollständig sichtbar und nach rechts ausgerichtet, verwitterte gunmetal- und rote Panzerplatten, realistischer 3D-gerenderter Sci-Fi-Spielgrafikstil passend zum bestehenden Gegner-Sprite, keine Umgebung, kein Text, kein Rahmen, kein Wasserzeichen.
|
||||
```
|
||||
|
||||
## app_icon.png
|
||||
|
||||
```text
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# Vollständiges Projekt-Backup
|
||||
|
||||
- Erstellt: 2026-10-06
|
||||
- Quelle: `C:\Users\Domin\Documents\Codex\2026-10-02\ich\outputs\void-bastion`
|
||||
- Enthält den gesamten Projektordner inklusive `.git`-Historie, Assets, Godot-Importdaten, Plattformprojektordner, lokalen Builds und Installerdateien.
|
||||
- Startpunkt für die Wiederaufnahme: `PROJEKT-HANDBUCH-FORTSETZUNG.md` und danach `README.md`.
|
||||
- Das Backup ist eine Sicherheitskopie. Änderungen darin nicht als Arbeitskopie verwenden; zuerst in einen normalen Projektordner zurückkopieren.
|
||||
- Nicht enthalten sind Geheimnisse/Zugangsdaten, die ausschließlich auf Unraid, im Webhosting oder in einem Passwortmanager liegen.
|
||||
|
||||
## Prüfsumme prüfen
|
||||
|
||||
Nach dem Kopieren lassen sich wichtige Dokumente und Builds mit PowerShell vergleichen:
|
||||
|
||||
```powershell
|
||||
Get-FileHash -Algorithm SHA256 .\PROJEKT-HANDBUCH-FORTSETZUNG.md
|
||||
Get-FileHash -Algorithm SHA256 .\project.godot
|
||||
Get-FileHash -Algorithm SHA256 .\build\android\Void-Bastion-Android-x64-alpha4.apk
|
||||
Get-FileHash -Algorithm SHA256 .\dist\Void-Bastion-Setup-x64.exe
|
||||
```
|
||||
|
||||
Die Ausgabe dieser Befehle muss für Quelldatei und Sicherung identisch sein.
|
||||
@@ -0,0 +1,180 @@
|
||||
# Void Bastion – Projekt- und Fortsetzungs-Handbuch
|
||||
|
||||
Stand: 6. Oktober 2026 · Version: 0.1.0-alpha.4 · Engine: Godot 4.7.2
|
||||
|
||||
Dieses Dokument ist die zentrale technische Übergabe für die Weiterarbeit an Void Bastion – auch nach einem PC-Wechsel. Es beschreibt den aktuellen Stand, die Projektstruktur und die verbindlichen Gestaltungs- und Datenregeln. Bei Änderungen immer zuerst diese Datei, `README.md`, `ART_ASSETS.md` und die Tests lesen.
|
||||
|
||||
## Projektziele und aktueller Stand
|
||||
|
||||
Void Bastion ist ein 2D-Tower-Defense-Spiel im realistischen Science-Fiction-Stil für Windows, Android und später iOS. Der aktuelle Spielstand ist Alpha 4. Einzelspieler mit sieben Karten, sechs Türmen, drei Schwierigkeitsstufen, 15 Wellen plus optionalem Endlosmodus, Shop, lokalem Profil und Bestenlisten ist vorhanden. Ein lokaler Koop-Modus gegen KI sowie eine Nakama-Onlineanbindung sind vorbereitet/implementiert.
|
||||
|
||||
Wichtige Einschränkungen des aktuellen Android-Teststands:
|
||||
|
||||
- Der Android-Emulator (Android 17, x86_64, SwiftShader) startet Alpha 4 und bleibt mindestens 45 Sekunden aktiv. Der Bildschirm zeigt das Hauptmenü; im Log gab es während dieses Laufs keinen Absturz.
|
||||
- Android verwendet den Mobile/Vulkan-Renderer. Die GLES-Compatibility-Variante überschritt auf dem Emulator die verfügbare Shader-Uniform-Grenze.
|
||||
- Wegen eines reproduzierbaren nativen SIGSEGV in Androids AudioTrack/OpenSL-Ausgabepfad sind Musik und UI-Klicktöne derzeit nur auf Android deaktiviert. Windows-Audio bleibt aktiv. Vor dem Wiederaktivieren erst mit regulären, eingebundenen Audioassets und auf echten Geräten erneut testen.
|
||||
- Im Android-Log meldet das AdMob-Plugin, dass sein natives Singleton nicht initialisiert ist. Android-Werbung ist deshalb nicht als funktionsfähig getestet anzusehen.
|
||||
- Die geprüfte Emulator-Datei ist `build/android/Void-Bastion-Android-x64-alpha4.apk`. Sie hat die Paketkennung `de.gergames.voidbastion`, Versionsnamen `0.1.0-alpha.4` und ist mit dem lokalen Debug-Schlüssel signiert. Sie ist nur zum Sideload-/Emulator-Test bestimmt, nicht zur Store-Veröffentlichung.
|
||||
- Es wurde kein echtes Android-Handy in diesem Testlauf geprüft. Touch-Bedienung, Geräteperformance, verschiedene Seitenverhältnisse, Anzeigen und Audio brauchen noch echte Gerätetests.
|
||||
|
||||
## Projektdateien und Zuständigkeiten
|
||||
|
||||
- `project.godot`: Godot-Projekt, Fenster-/Viewport-Einstellungen, Autoloads, Editor-Plugins und Renderer. Desktop bleibt auf `gl_compatibility`; Mobilgeräte nutzen derzeit `mobile`/Vulkan.
|
||||
- `main.gd`: Spielzustand, Karten-/Weg-/Bau-/Turmdaten, Wellen, KI, Kampf, UI, Zeichnen und lokale Navigation. Die zentralen Datenkonstanten stehen am Dateianfang.
|
||||
- `main.tscn`: Hauptszene und Manager-Nodes.
|
||||
- `multiplayer_manager.gd`: lokale Koop-/KI-Regeln und Multiplayer-Helfer.
|
||||
- `online_manager.gd`, `account_manager.gd`: Nakama-Verbindung, Profil, Anmeldung und Ranglisten.
|
||||
- `ads_manager.gd`: Werbeintegration. Testmodus beibehalten, bis Konten, Einwilligung und Store-Konfiguration rechtlich/technisch geprüft sind.
|
||||
- `update_manager.gd`: Prüft öffentliche Releases und bietet einen Update-Link an; installiert nichts ungefragt.
|
||||
- `assets/`: Kartenbasen, Turm-/Gegnersprites, Straßenmaterial und UI-Grafiken.
|
||||
- `tests/smoke_test.gd`: Offline-/Logikregressionstest. Nach Karten-, Weg-, Turm-, Wellen- oder Balancingänderungen immer ausführen.
|
||||
- `tests/online_integration_test.gd`: Nakama-/Onlineintegrationstest; nur gegen ausdrücklich gewählte Testumgebung ausführen.
|
||||
- `packaging/windows/build.ps1` und `packaging/windows/installer.nsi`: Windows-Export und Setup-Installer.
|
||||
- `export_presets.cfg`: Windows-, Android- und iOS-Exportprofile.
|
||||
- `server/nakama/`: Serverkonfiguration/Module. Keine Servergeheimnisse hier ablegen.
|
||||
- `README.md`, `ART_ASSETS.md`, `ONLINE_SETUP.md`, `LEGAL_RELEASE_CHECKLIST.md`, `PLATFORM_ACCOUNT_SETUP.md`, `THIRD_PARTY_NOTICES.md`: ergänzende Projektunterlagen.
|
||||
- `.godot/`, `build/`, `dist/`, `android/build/`: generierte Imports, Builds, Caches und Artefakte. Sie gehören zum vollständigen PC-Backup, aber normalerweise nicht in Git-Commits.
|
||||
|
||||
## Karten: verbindliche Regeln
|
||||
|
||||
### Koordinatensystem und Raster
|
||||
|
||||
- Die logische Spielfläche ist `SCREEN = Vector2(1600, 900)` in `main.gd`.
|
||||
- `GRID_SIZE = 80` Pixel; Karten, Wege, Bauzellen und Turm-Snapping müssen mit demselben Raster arbeiten.
|
||||
- Das Rasterzentrum eines Feldes liegt bei `(40 + 80·x, 40 + 80·y)`. Wegpunkte sollen auf diesen Feldmitten liegen. Der auslaufende Start darf links bei `x = -40` liegen.
|
||||
- Wegsegmente verlaufen nur waagerecht oder senkrecht. Keine diagonalen Segmente. Kurven entstehen durch aufeinanderfolgende rechtwinklige Segmente.
|
||||
- Gegnerrouten und Straßenzeichnung beziehen ihre Daten gemeinsam aus `MAP_PATHS`/`path_cells`. Niemals zusätzlich einen unabhängigen Weg in die Hintergrundgrafik malen oder eine zweite separate Laufroute definieren.
|
||||
|
||||
### Drei parallele Kartendatenlisten
|
||||
|
||||
Jeder Karteneintrag muss denselben Index in ALLEN folgenden Arrays besitzen (in `main.gd`):
|
||||
|
||||
1. `MAP_TEXTURES`: Hintergrundtextur.
|
||||
2. `MAP_NAMES`, `MAP_DESCRIPTIONS`, `MAP_UNLOCK_COSTS`: Name, Kurzbeschreibung und Shoppreis.
|
||||
3. `MAP_PATHS`: geordnete Mittellinienpunkte für Gegner und Straße.
|
||||
4. `MAP_BUILD_ZONES`: erlaubte Deckbereiche als ein oder mehrere `Rect2`-Rechtecke.
|
||||
5. `MAP_REACTOR_ZONES`: Reaktormittelpunkt(e) und Sperrradius/-radien.
|
||||
|
||||
Alle Listen müssen dieselbe Kartenanzahl und identische Reihenfolge haben. Nach Hinzufügen oder Umsortieren einer Karte die Anzahl und Reihenfolge prüfen und Smoke-Test ergänzen.
|
||||
|
||||
### Routenende, Reaktor und Bauverbote
|
||||
|
||||
- Letzter Punkt in `MAP_PATHS[index]` muss exakt im Mittelpunkt des Hauptreaktors landen (Toleranz in `_validate_route_ends_at_reactor()` ist 1 Pixel). `_select_map()` validiert die Route; der Smoke-Test validiert alle Karten.
|
||||
- Die Reaktorgrafik im Hintergrund, `MAP_REACTOR_ZONES[index][0].center` und letzter Wegpunkt müssen optisch/technisch deckungsgleich sein.
|
||||
- Auf jedem Reaktor ist Bauen verboten. Alle Reaktoren/offenen Reaktorkammern derselben Karte in `MAP_REACTOR_ZONES` eintragen. Pyrax hat zusätzlich die offene Reaktorkammer als zweite Sperrzone.
|
||||
- Erlaubte Deckzellen werden über `MAP_BUILD_ZONES` definiert. Das Spiel sperrt zusätzlich Wegzellen, Reaktorzonen, Randzellen und bereits belegte Zellen.
|
||||
- Türme werden auf Feldmitten eingerastet. Weder Türme noch Wegkacheln sollen nur über Bildkoordinaten korrigiert werden: zuerst Raster-/Routendaten korrigieren.
|
||||
|
||||
### Kartengrafik
|
||||
|
||||
- Karte ist eine saubere, hochauflösende Draufsicht im Querformat (bestehende HD-Basen etwa 3344×1882 Pixel).
|
||||
- Hintergrundgrafik ohne eingebauten Weg, Fahrbahn, Raster, Bauplätze, Türme, Gegner, UI, Text oder Logos erstellen. Straße/Raster/Route werden im Spiel gezeichnet; so können sie nicht gegeneinander verrutschen.
|
||||
- Der Reaktor soll gut erkennbar am rechten Rand ungefähr auf Höhe des Spielfeldzentrums liegen, außer die Route/Zone definiert absichtlich eine andere Lage.
|
||||
- Behalte realistische aktive Stationsbeleuchtung, Metallpaneele, Cyan-Energieelemente und kleine warme Positionslichter. Keine dunkle/ausgewaschene Karte und keine starke gelbe Umrandung der kompletten Straße.
|
||||
- Die Straße ist eine Rasterzelle breit (80 px) und nutzt `road_straight.svg`, `road_corner.svg`, `road_surface_material_v1.png`, `road_corner_surface_v1.png`.
|
||||
- Der letzte Abschnitt zum Reaktor wird in `_draw_reactor_approach()` mit leisem Lichtimpuls und langsamem Transparenzverlauf gestaltet. Den Schluss nicht hart abschneiden und keine groben Doppelbögen ans Reaktorzentrum setzen.
|
||||
- Asset-Prompts und Asset-Herkunft in `ART_ASSETS.md` dokumentieren.
|
||||
|
||||
### Kartenprüfung
|
||||
|
||||
1. Für jeden Wegpunkt Rastermitte prüfen, horizontale/vertikale Segmente sicherstellen.
|
||||
2. Ende exakt am Reaktormittelpunkt kontrollieren.
|
||||
3. Reaktor im Hintergrund mit Sperrzone vergleichen; keine Bauzellen auf dem Reaktor.
|
||||
4. Das Projekt starten: Weg muss sichtbar mit Gegnerlaufspur, Raster und Hintergrund zusammenpassen.
|
||||
5. Zwischen Wegen müssen Bauzellen frei bleiben, soweit sie innerhalb einer Build-Zone und außerhalb aller Route-/Reaktor-Sperren liegen.
|
||||
6. `tests/smoke_test.gd` für jede neue Karte um Routenende, Reactor-Sperre, baubare Zellen und Shop-Kosten erweitern und ausführen.
|
||||
|
||||
## Türme und Gegner
|
||||
|
||||
### Turmdaten und Darstellung
|
||||
|
||||
Die Liste `TOWER_DATA` in `main.gd` ist die zentrale Quelle für Turmname, Baukosten, Basisschaden, Reichweite, Schussintervall (`rate`, Sekunden pro Schuss), Projektilgeschwindigkeit, Schadensart, Farbe, Freischaltpreis und Beschreibung. Schüsse/Sekunde wird aus dem Intervall berechnet. Beim Hinzufügen eines Turms müssen UI-Karte, Shop, Platzierung, Reichweitenvorschau, Angriff/Schadenslogik, Upgrade, Speichern/Freischaltung und Tests konsistent ergänzt werden.
|
||||
|
||||
Sprites:
|
||||
|
||||
- Kinetik, Laser, Rakete: drei horizontale Frames aus `assets/defense_towers.png`.
|
||||
- Plasma, EMP-Puls, Railgun: separate Texturen (`tower_plasma.png`, `tower_emp.png`, `tower_railgun.png`).
|
||||
- Ergänzende Sprites im bestehenden maßstäblichen Top-Down-Stil und mit transparenter Fläche erzeugen. In `_draw_tower()` Textur/Fall ergänzen. Bei der Kinetik-Ausrichtung beachten, dass ein falscher Basiswinkel den Lauf um 180° verdreht erscheinen lässt.
|
||||
|
||||
| Turm | Kosten | Schaden | Reichweite | Intervall | Besonderheit |
|
||||
|---|---:|---:|---:|---:|---|
|
||||
| Kinetik | 120 EN | 22 | 190 | 0,55 s | Einzelziel |
|
||||
| Laser | 180 EN | 12 | 165 | 0,18 s | Schnell, effektiv gegen Schilde |
|
||||
| Rakete | 250 EN | 65 | 260 | 1,40 s | Flächenschaden 76 |
|
||||
| Plasma | 230 EN | 48 | 240 | 1,05 s | Flächenschaden 92; 18 Kernkristalle Freischaltung |
|
||||
| EMP-Puls | 210 EN | 34 | 215 | 0,92 s | Flächeneffekt 118; 24 Kernkristalle |
|
||||
| Railgun | 300 EN | 110 | 330 | 1,85 s | Durchschlägt Schild; 30 Kernkristalle |
|
||||
|
||||
Alle Türme unten in der Leiste darstellen. Reichweite schon vor dem Bauen zeigen; Vorschaukasten kompakt/transluzent halten. Turminfo soll Schaden, Schüsse pro Sekunde, Reichweite und Einzel-/Flächentyp zeigen. Upgrade-Vorschau muss den echten Zuwachs darstellen und darf keine verdeckten Felder erzeugen. Bei Balancingänderungen Shoptext und Tests mit anpassen.
|
||||
|
||||
### Gegner
|
||||
|
||||
Gegnerwerte liegen in `ENEMY_DATA`: Jäger (70 HP, Tempo 105, Belohnung 18), Fregatte (185 HP, Tempo 72, Belohnung 34), Kreuzer (520 HP, Tempo 43, Belohnung 78). Skalierung und Wellenlogik werden zusätzlich durch `DIFFICULTY_DATA`/Wellenfunktionen beeinflusst. Schildgegner sollen Laserschaden bevorzugt wirksam bekommen; andere Türme dürfen reduziert, aber nicht grundsätzlich null Schaden verursachen. Railgun ignoriert den Schildschutz.
|
||||
|
||||
## Wellen, Wirtschaft, Highscore
|
||||
|
||||
- `MAX_WAVES = 15`; Bossgegner alle fünf Wellen.
|
||||
- Leicht entspricht dem früheren Balancing, Mittel/Schwer steigern Lebenspunkte, Tempo und Gegneranzahl und reduzieren Startressourcen. Belohnung aktuell 5/7/10 Kernkristalle nach Schwierigkeit.
|
||||
- Nach Welle 15 gibt es die Wahl: Mission beenden oder freiwillig Endlosmodus. Endlos-Verlust gilt nicht als gescheiterte Mission.
|
||||
- Punkte sollen neben erreichter Welle auch Restenergie und effiziente Turmverwendung abbilden. Verkauf aller Türme in der Schlusswelle darf keinen Multiplikator-Trick ermöglichen; deshalb Turmbau-/Verkaufshistorie berücksichtigen, nicht nur die am Ende noch stehenden Türme.
|
||||
- Spielwährung heißt konsistent **Kernkristalle** (nicht abwechselnd „Kern“ oder „Kristalle“). Energie/EN ist die missionsinterne Bauressource.
|
||||
- Für neue Türme/Karten Shopfreischaltungen und Belohnung konsistent speichern; kein echter Geldeinkauf ist für diese Testfassung erforderlich.
|
||||
|
||||
## Multiplayer
|
||||
|
||||
- Lokale KI-Arena verwendet `COOP_LEFT_PATH` und eine Spiegelung exakt um die Achse durch den gemeinsamen Reaktor (`COOP_MIRROR_X = 1480`). Weltbreite ist doppelt; beide Seiten sollen voll groß bleiben, nicht zusammengeschrumpft.
|
||||
- Reaktorleben sind gemeinsam, Credits getrennt; jeder baut auf seiner Seite. KI-Platzierung in `_find_ai_build_position()` anhand echter Gegnerroute und effektiver Turmreichweite bewerten, niemals zufällige/off-route Plätze wählen.
|
||||
- Onlinekonten, Räume, Ranglisten und Ergebnisprüfung laufen über Nakama. Niemals Admin-/Server-/HTTP-Schlüssel in Godot-Projekt, `.env`-Beispiel, Git, Website oder diese Anleitung kopieren.
|
||||
- Server-/Unraid-Konfiguration und öffentliche Adresse sind in `ONLINE_SETUP.md` bzw. `server/nakama/README.md` beschrieben; echte Geheimnisse liegen nur auf dem Server/Webhosting.
|
||||
|
||||
## Builds, Checks und Release
|
||||
|
||||
### Logiktest
|
||||
|
||||
```powershell
|
||||
& 'E:\Program Files\Godot\Godot_v4.7.2-stable_win64_console.exe' --headless --path . --script tests/smoke_test.gd
|
||||
```
|
||||
|
||||
Erfolg endet mit `VOID_BASTION_SMOKE_TEST_OK`. Bekannte Umgebungswarnungen zum CA-Zertifikat oder user://-Log sind nicht automatisch ein Spielfehler; entscheidend ist Rückgabecode und Ergebnis.
|
||||
|
||||
### Windows-Installer
|
||||
|
||||
```powershell
|
||||
& '.\packaging\windows\build.ps1' `
|
||||
-GodotExe 'E:\Program Files\Godot\Godot_v4.7.2-stable_win64_console.exe' `
|
||||
-MakeNsisExe '.\build\nsis-portable\nsis-3.13\makensis.exe'
|
||||
```
|
||||
|
||||
Artefakt: `dist/Void-Bastion-Setup-x64.exe`. Das Skript blendet das Entwicklungs-MCP aus dem Release aus, exportiert und stellt anschließend `project.godot` wieder her.
|
||||
|
||||
### Android
|
||||
|
||||
- Godot Exportprofil heißt `Android Studio Emulator x86_64` (emulator) bzw. `Android Test APK` (ARM64/ARMv7-Geräte).
|
||||
- Erfordert Android SDK, Java 17, Gradle und Godot-Android-Templates. Android-Paketkennung ist `de.gergames.voidbastion`.
|
||||
- Das Editor-MCP-Autoload/Plugin darf nicht ins Release gelangen: temporär vor dem Export `_MCPGameBridge` und `res://addons/agent_tools/plugin.cfg` aus der Exportkopie entfernen und Quelldatei danach wiederherstellen (Windows-Skript zeigt das sichere try/finally-Muster). Nie eine Änderung versehentlich dauerhaft aus der Entwicklerdatei löschen.
|
||||
- Emulator Alpha 4 wurde mit Gradle 8.11.1, AGP 8.6.1, Java 17 und SDK 36 gebaut. Bei einem Gradle `AccessDeniedException` in einer Transform-Cache-Datei neues separates `GRADLE_USER_HOME` nutzen; vorhandene Cache-Datei nicht blind löschen.
|
||||
- Für einen Testbuild prüfen: `aapt dump badging` zeigt `de.gergames.voidbastion` und `0.1.0-alpha.4`; `apksigner verify --verbose` muss `Verifies` melden; danach auf Emulator installieren und mindestens 45 Sekunden starten, Logcat auf SIGSEGV/FATAL prüfen und Screenshot kontrollieren.
|
||||
- Erfolgreiche Testdatei liegt in `build/android/Void-Bastion-Android-x64-alpha4.apk` (Debugsignatur; nur intern). APK ist im Git-Verlauf nicht garantiert; im Vollbackup enthalten.
|
||||
- Für neue Google-Play-/Sideload-Versionen Android `versionCode` erhöhen. Signaturschlüssel für Produktion getrennt und sicher aufbewahren, nicht Debugschlüssel als Store-Schlüssel verwenden.
|
||||
|
||||
### iOS und Stores
|
||||
|
||||
iOS-Export erzeugt ein Xcode-Projekt, erfordert macOS, Xcode, Zertifikate/Provisioning und ein Apple-Developer-Konto. Ein Windows-Rechner kann die iOS-App nicht final signieren oder in den App Store hochladen. Google Play-/Apple Store-/Steam-Accounts, Datenschutz- und Storefreigaben bleiben manuelle Betreiberaufgaben. Vor öffentlichem Release Checklisten/Third-party-Lizenzen und echte Geräteprüfung abschließen.
|
||||
|
||||
### Webseite und Releases
|
||||
|
||||
Homepage: `https://void-bastion.gergames.de/`. Öffentliche Releases/Setup liegen im separaten Gitea-Releases-Projekt; der Ingame-Updater fragt die Release-API ab und soll automatisch das neueste passende Setup/APK verlinken. Nach einer Veröffentlichung Release-Tag, Version in Godot, Windows Installer, Android versionCode und Release-Assetnamen synchron halten. Keine Websiteänderung ist erforderlich, wenn Release-API/Assets korrekt gepflegt sind.
|
||||
|
||||
## Weiterarbeit nach einem PC-Wechsel
|
||||
|
||||
1. Backupordner auf neuen PC kopieren und vorzugsweise in einen stabilen Arbeitsordner legen.
|
||||
2. Git installieren; `git status` und `git log -1` prüfen. Den mitgesicherten `.git`-Ordner nicht entfernen, damit die Historie erhalten bleibt.
|
||||
3. Godot 4.7.2 installieren und `project.godot` importieren; ersten Assetimport abwarten.
|
||||
4. `PROJEKT-HANDBUCH-FORTSETZUNG.md`, `README.md`, `ART_ASSETS.md`, `ALPHA_TEST_CHECKLIST.md`, `ONLINE_SETUP.md` lesen.
|
||||
5. Android-Buildtools/JDK 17 nur einrichten, wenn Android gebraucht wird. Emulator neu anlegen; lokale Gradle-Caches und lokale Godot-Einstellungen sind maschinengebunden und können auf dem neuen PC neu erzeugt werden.
|
||||
6. `tests/smoke_test.gd` ausführen, dann Spiel im Editor starten und zuletzt Plattformbuilds prüfen.
|
||||
7. Serverzugänge, Signierzertifikate, echte AdMob-/Nakama-Schlüssel und Hostingpasswörter separat aus einem Passwortmanager/Server sichern. Diese sind absichtlich nicht Teil des Repositories oder dieser Anleitung.
|
||||
|
||||
## Grundsatz bei jeder Karten-/Turmänderung
|
||||
|
||||
Eine visuelle Änderung ist erst fertig, wenn Datenquelle, Darstellung, Kollisions-/Bauprüfung, Kauf-/Freischaltstatus, UI-Text und automatisierter Test zueinander passen. Bei Karten besonders Route, Straßenkacheln, Raster, Bauflächen, Sperrzonen und Reaktorhintergrund gemeinsam prüfen. Keine unabhängige „optische Reparatur“ über die Route zeichnen.
|
||||
@@ -16,9 +16,9 @@ Ein plattformübergreifender 2D-Tower-Defense-Prototyp für Godot 4. Der aktuell
|
||||
- jede geeignete Plattformzelle ist bebaubar; ausschließlich Wegzellen, Reaktoren und Bereiche außerhalb der Plattform sind gesperrt
|
||||
- neu erzeugte, straßenfreie HD-Kartenbasen mit 3344 × 1882 Pixeln und Mipmaps für eine schärfere Windows-Darstellung
|
||||
- sechs Türme: Kinetik, Laser, Rakete, Plasma, EMP-Puls und Railgun
|
||||
- drei Gegnerklassen mit unterschiedlichen Werten
|
||||
- sechs Gegnerklassen: Jäger, Fregatte, Kreuzer, schneller Abfangjäger, Schildkreuzer und schwerer Belagerer
|
||||
- 15 automatisch skalierende Missionswellen
|
||||
- drei wählbare Schwierigkeitsgrade: Leicht entspricht dem bisherigen Balancing, Mittel ist der neue Standard und Schwer erhöht Gegnerhülle, Tempo und Anzahl deutlich
|
||||
- drei wählbare Schwierigkeitsgrade: Leicht behält die bisherige Kurve; Schwer startet knapper und erhöht ab Missionsmitte Gegnerhülle, Gegnerzahl und fortgeschrittene Gegnertypen stärker
|
||||
- höhere Schwierigkeitsgrade beginnen mit knapperen Ressourcen und belohnen einen Kartensieg mit mehr Kernkristallen
|
||||
- freiwilliger Endlosmodus nach Welle 15 mit unbegrenzt weiter skalierenden Gegnern
|
||||
- das Ende eines Endloslaufs zählt ausdrücklich nicht als gescheiterte Mission
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Void Bastion 0.1.0-alpha.5
|
||||
|
||||
Diese Windows-Testversion erweitert die Gegnerflotte und zieht die Balance der schwierigeren Einsätze an.
|
||||
|
||||
## Neu
|
||||
|
||||
- drei zusätzliche Gegnertypen: Abfangjäger, Schildträger und Belagerungszerstörer
|
||||
- neue Flugzeug- und Schiffsgrafiken für die zusätzlichen Gegnertypen
|
||||
|
||||
## Verbessert
|
||||
|
||||
- höhere Gegnerzahl und stärkere Hüllenwerte in mittleren und späten Wellen auf den Schwierigkeitsgraden Mittel und Schwer
|
||||
- fortgeschrittene Gegner erscheinen auf höheren Schwierigkeitsgraden früher und häufiger
|
||||
- Wellen nach dem Missionssieg skalieren im Endlosmodus weiter
|
||||
|
||||
## Hinweis
|
||||
|
||||
Void Bastion befindet sich weiterhin in einer frühen Alpha-Phase. Unter Windows erscheint keine Werbung. Updates werden im Spiel angekündigt, aber niemals ungefragt installiert. Ruckmeldungen und Fehlerberichte bitte an support@gergames.de senden.
|
||||
@@ -0,0 +1 @@
|
||||
4.7.2.stable
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.7 MiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dq8ng1q3sipdj"
|
||||
path="res://.godot/imported/enemy_interceptor.png-25fe15248366da5309a562c7fda1695b.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/enemy_interceptor.png"
|
||||
dest_files=["res://.godot/imported/enemy_interceptor.png-25fe15248366da5309a562c7fda1695b.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.3 MiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://d0c55da7vg2x8"
|
||||
path="res://.godot/imported/enemy_shield_carrier.png-5797af82d40d1f3dfd002a92a5efe490.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/enemy_shield_carrier.png"
|
||||
dest_files=["res://.godot/imported/enemy_shield_carrier.png-5797af82d40d1f3dfd002a92a5efe490.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.3 MiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://7vnc3kiipsqj"
|
||||
path="res://.godot/imported/enemy_siege_destroyer.png-b87dfe2af9a477c9a1f1ee85d55de962.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/enemy_siege_destroyer.png"
|
||||
dest_files=["res://.godot/imported/enemy_siege_destroyer.png-b87dfe2af9a477c9a1f1ee85d55de962.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
+4
-2
@@ -24,8 +24,8 @@ application/modify_resources=true
|
||||
application/icon="res://assets/app_icon.png"
|
||||
application/console_wrapper_icon=""
|
||||
application/icon_interpolation=4
|
||||
application/file_version="0.1.0.4"
|
||||
application/product_version="0.1.0.4"
|
||||
application/file_version="0.1.0.5"
|
||||
application/product_version="0.1.0.5"
|
||||
application/company_name="GerGames"
|
||||
application/product_name="Void Bastion"
|
||||
application/file_description="Void Bastion – Alpha-Testversion"
|
||||
@@ -62,6 +62,7 @@ architectures/x86=false
|
||||
architectures/x86_64=false
|
||||
package/name="Void Bastion"
|
||||
package/unique_name="de.gergames.voidbastion"
|
||||
version/code=2
|
||||
package/signed=true
|
||||
screen/immersive_mode=true
|
||||
permissions/internet=true
|
||||
@@ -118,6 +119,7 @@ architectures/x86=false
|
||||
architectures/x86_64=true
|
||||
package/name="Void Bastion"
|
||||
package/unique_name="de.gergames.voidbastion"
|
||||
version/code=2
|
||||
package/signed=true
|
||||
screen/immersive_mode=true
|
||||
permissions/internet=true
|
||||
@@ -14,6 +14,9 @@ const PLASMA_TEXTURE := preload("res://assets/tower_plasma.png")
|
||||
const EMP_TEXTURE := preload("res://assets/tower_emp.png")
|
||||
const RAILGUN_TEXTURE := preload("res://assets/tower_railgun.png")
|
||||
const ENEMY_TEXTURE := preload("res://assets/enemy_ships.png")
|
||||
const ENEMY_INTERCEPTOR_TEXTURE := preload("res://assets/enemy_interceptor.png")
|
||||
const ENEMY_SHIELD_CARRIER_TEXTURE := preload("res://assets/enemy_shield_carrier.png")
|
||||
const ENEMY_SIEGE_DESTROYER_TEXTURE := preload("res://assets/enemy_siege_destroyer.png")
|
||||
const ROAD_STRAIGHT_TEXTURE := preload("res://assets/road_straight.svg")
|
||||
const ROAD_CORNER_TEXTURE := preload("res://assets/road_corner.svg")
|
||||
const ROAD_SURFACE_TEXTURE := preload("res://assets/road_surface_material_v1.png")
|
||||
@@ -69,12 +72,12 @@ const COOP_REACTOR_RADIUS := 112.0
|
||||
const DIFFICULTY_DATA := [
|
||||
{"name": "LEICHT", "hp": 1.0, "speed": 1.0, "count": 1.0, "income": 1.0, "spawn_delay": 1.0, "start_credits": 450, "lives": 20, "crystals": 5},
|
||||
{"name": "MITTEL", "hp": 1.32, "speed": 1.07, "count": 1.24, "income": 0.94, "spawn_delay": 0.91, "start_credits": 430, "lives": 17, "crystals": 7},
|
||||
{"name": "SCHWER", "hp": 1.72, "speed": 1.15, "count": 1.48, "income": 0.84, "spawn_delay": 0.82, "start_credits": 400, "lives": 14, "crystals": 10}
|
||||
{"name": "SCHWER", "hp": 1.72, "speed": 1.15, "count": 1.48, "income": 0.78, "spawn_delay": 0.82, "start_credits": 340, "lives": 12, "crystals": 12}
|
||||
]
|
||||
const DIFFICULTY_DESCRIPTIONS := [
|
||||
"Bisheriges Balancing • 20 Reaktor • Kartenbonus 5",
|
||||
"+32 % Gegnerhülle • +24 % Gegner • Kartenbonus 7",
|
||||
"+72 % Gegnerhülle • +48 % Gegner • Kartenbonus 10"
|
||||
"+72 % Grundhülle • stärkere Spätwellen • Start 340 • Kartenbonus 12"
|
||||
]
|
||||
|
||||
const TOWER_DATA := [
|
||||
@@ -87,9 +90,12 @@ const TOWER_DATA := [
|
||||
]
|
||||
|
||||
const ENEMY_DATA := [
|
||||
{"name": "JAEGER", "hp": 70.0, "speed": 105.0, "reward": 18, "size": 58.0},
|
||||
{"name": "FREGATTE", "hp": 185.0, "speed": 72.0, "reward": 34, "size": 78.0},
|
||||
{"name": "KREUZER", "hp": 520.0, "speed": 43.0, "reward": 78, "size": 112.0}
|
||||
{"name": "JAEGER", "hp": 70.0, "speed": 105.0, "reward": 18, "size": 58.0, "breach_damage": 1, "shield_resistance": 0.28},
|
||||
{"name": "FREGATTE", "hp": 185.0, "speed": 72.0, "reward": 34, "size": 78.0, "breach_damage": 2, "shield_resistance": 0.28},
|
||||
{"name": "KREUZER", "hp": 520.0, "speed": 43.0, "reward": 78, "size": 112.0, "breach_damage": 3, "shield_resistance": 0.28},
|
||||
{"name": "ABFANGJAEGER", "hp": 52.0, "speed": 178.0, "reward": 22, "size": 48.0, "breach_damage": 2, "shield_resistance": 0.28},
|
||||
{"name": "SCHILDKREUZER", "hp": 260.0, "speed": 58.0, "reward": 52, "size": 92.0, "breach_damage": 2, "shield_resistance": 0.18, "default_shield": true},
|
||||
{"name": "BELAGERER", "hp": 740.0, "speed": 48.0, "reward": 108, "size": 128.0, "breach_damage": 4, "shield_resistance": 0.28}
|
||||
]
|
||||
|
||||
var path_points := PackedVector2Array()
|
||||
@@ -241,9 +247,13 @@ func _ready() -> void:
|
||||
_select_map(selected_map)
|
||||
_load_progress()
|
||||
_load_audio_settings()
|
||||
music_player.stream = _build_ambient_stream()
|
||||
music_player.play()
|
||||
ui_sound_player.stream = _build_ui_click_stream()
|
||||
# The Android AudioTrack backend currently crashes on this generated PCM stream
|
||||
# on the supported emulator/device test target. Keep the game playable there
|
||||
# without starting native audio until we replace it with a packaged audio asset.
|
||||
if not OS.has_feature("android"):
|
||||
music_player.stream = _build_ambient_stream()
|
||||
music_player.play()
|
||||
ui_sound_player.stream = _build_ui_click_stream()
|
||||
ads_manager.energy_reward_granted.connect(_on_energy_reward_granted)
|
||||
ads_manager.ad_unavailable.connect(_show_status)
|
||||
ads_manager.victory_interstitial_finished.connect(_on_victory_interstitial_finished)
|
||||
@@ -304,15 +314,8 @@ func _update_wave(delta: float) -> void:
|
||||
|
||||
|
||||
func _spawn_enemy() -> void:
|
||||
var type := 0
|
||||
var is_boss := wave > 0 and wave % BOSS_WAVE_INTERVAL == 0 and enemies_to_spawn <= (2 if coop_mode else 1)
|
||||
var roll := randf()
|
||||
if is_boss:
|
||||
type = 2
|
||||
elif wave >= 7 and roll > 0.82:
|
||||
type = 2
|
||||
elif wave >= 3 and roll > 0.53:
|
||||
type = 1
|
||||
var type := _enemy_type_for_wave(wave, randf(), is_boss)
|
||||
var data: Dictionary = ENEMY_DATA[type]
|
||||
var health_scale := _enemy_health_scale(wave, is_boss)
|
||||
var route_id := 0
|
||||
@@ -335,18 +338,37 @@ func _spawn_enemy() -> void:
|
||||
"flash": 0.0,
|
||||
"boss": is_boss,
|
||||
"size_scale": 1.34 if is_boss else 1.0,
|
||||
"shield": wave >= 4 and randf() < minf(0.42, 0.10 + wave * 0.018 + selected_difficulty * 0.06)
|
||||
"shield": bool(data.get("default_shield", false)) or (wave >= 4 and randf() < minf(0.42, 0.10 + wave * 0.018 + selected_difficulty * 0.06)),
|
||||
"shield_resistance": float(data.shield_resistance)
|
||||
})
|
||||
if is_boss:
|
||||
_show_status("WARNUNG: KOMMANDOSCHIFF IM ANFLUG")
|
||||
next_enemy_id += 1
|
||||
|
||||
|
||||
func _enemy_type_for_wave(for_wave: int, roll: float, is_boss: bool) -> int:
|
||||
if is_boss:
|
||||
return 5
|
||||
var advanced_offset := 2 if selected_difficulty == 2 else (1 if selected_difficulty == 1 else 0)
|
||||
if for_wave >= 10 - advanced_offset and roll >= 0.94:
|
||||
return 5
|
||||
if for_wave >= 7 - advanced_offset and roll >= 0.80:
|
||||
return 4
|
||||
if for_wave >= 4 - advanced_offset and roll >= 0.65:
|
||||
return 3
|
||||
if for_wave >= 7 and roll >= 0.45:
|
||||
return 2
|
||||
if for_wave >= 3 and roll >= 0.28:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
func _enemy_health_scale(for_wave: int, is_boss: bool) -> float:
|
||||
var early_ramp := maxf(0.0, for_wave - 1.0) * 0.20
|
||||
var post_four_ramp := maxf(0.0, for_wave - 3.0) * 0.09
|
||||
var hard_late_ramp := maxf(0.0, for_wave - 5.0) * 0.055 if selected_difficulty == 2 else 0.0
|
||||
var endless_ramp := maxf(0.0, for_wave - MAX_WAVES) * 0.035 if endless_mode else 0.0
|
||||
return (1.0 + early_ramp + post_four_ramp + endless_ramp) * (3.25 if is_boss else 1.0) * float(_difficulty().hp)
|
||||
return (1.0 + early_ramp + post_four_ramp + hard_late_ramp + endless_ramp) * (3.25 if is_boss else 1.0) * float(_difficulty().hp)
|
||||
|
||||
|
||||
func _update_enemies(delta: float) -> void:
|
||||
@@ -362,7 +384,8 @@ func _update_enemies(delta: float) -> void:
|
||||
enemy.path_index += 1
|
||||
if enemy.path_index >= route.size():
|
||||
enemies.erase(enemy)
|
||||
reactor_lives -= 1 + enemy.type + (4 if enemy.boss else 0)
|
||||
var enemy_data: Dictionary = ENEMY_DATA[int(enemy.type)]
|
||||
reactor_lives -= int(enemy_data.breach_damage) + (4 if enemy.boss else 0)
|
||||
if coop_mode:
|
||||
multiplayer_manager.shared_reactor_lives = maxi(0, reactor_lives)
|
||||
_spawn_burst(route[-1], Color("54dfff"), 16)
|
||||
@@ -442,7 +465,7 @@ func _apply_damage(enemy: Dictionary, amount: float, owner := 0, weapon_type :=
|
||||
return
|
||||
var applied_damage := amount
|
||||
if bool(enemy.get("shield", false)) and weapon_type not in [1, 5]:
|
||||
applied_damage *= 0.28
|
||||
applied_damage *= float(enemy.get("shield_resistance", 0.28))
|
||||
enemy.hp -= applied_damage
|
||||
enemy.flash = 0.06
|
||||
if enemy.hp <= 0.0:
|
||||
@@ -1142,6 +1165,8 @@ func _start_wave() -> void:
|
||||
return
|
||||
wave += 1
|
||||
var wave_population_scale := 1.0 + maxf(0.0, wave - 3.0) * 0.08
|
||||
if selected_difficulty == 2:
|
||||
wave_population_scale += maxf(0.0, wave - 4.0) * 0.025
|
||||
if endless_mode:
|
||||
wave_population_scale += maxf(0.0, wave - MAX_WAVES) * 0.035
|
||||
enemies_to_spawn = ceili((7 + wave * 2) * float(_difficulty().count) * wave_population_scale) * (2 if coop_mode else 1)
|
||||
@@ -2019,15 +2044,25 @@ func _draw_tower(tower: Dictionary) -> void:
|
||||
|
||||
|
||||
func _draw_enemy(enemy: Dictionary) -> void:
|
||||
var source_width: float = ENEMY_TEXTURE.get_width() / 3.0
|
||||
var source := Rect2(source_width * enemy.type, 0, source_width, ENEMY_TEXTURE.get_height())
|
||||
var enemy_type := int(enemy.type)
|
||||
var enemy_texture: Texture2D = ENEMY_TEXTURE
|
||||
var source: Rect2
|
||||
var destination: Rect2
|
||||
var size: float = ENEMY_DATA[enemy.type].size * enemy.size_scale
|
||||
if enemy_type < 3:
|
||||
var source_width: float = ENEMY_TEXTURE.get_width() / 3.0
|
||||
source = Rect2(source_width * enemy_type, 0, source_width, ENEMY_TEXTURE.get_height())
|
||||
destination = Rect2(-size * 0.5, -size * 0.5, size, size)
|
||||
else:
|
||||
enemy_texture = [ENEMY_INTERCEPTOR_TEXTURE, ENEMY_SHIELD_CARRIER_TEXTURE, ENEMY_SIEGE_DESTROYER_TEXTURE][enemy_type - 3]
|
||||
source = Rect2(Vector2.ZERO, enemy_texture.get_size())
|
||||
destination = Rect2(-size * 0.75, -size * 0.5, size * 1.5, size)
|
||||
var tint := Color.WHITE if enemy.flash <= 0.0 else Color("ffb6a3")
|
||||
if bool(enemy.get("shield", false)):
|
||||
draw_circle(enemy.pos, size * 0.63, Color(0.25, 0.80, 1.0, 0.12))
|
||||
draw_arc(enemy.pos, size * 0.63, 0.0, TAU, 28, Color(0.38, 0.86, 1.0, 0.76), 2.2)
|
||||
draw_set_transform(enemy.pos - camera_offset, enemy.angle, Vector2.ONE)
|
||||
draw_texture_rect_region(ENEMY_TEXTURE, Rect2(-size * 0.5, -size * 0.5, size, size), source, tint)
|
||||
draw_texture_rect_region(enemy_texture, destination, source, tint)
|
||||
draw_set_transform(-camera_offset, 0.0, Vector2.ONE)
|
||||
var bar_width := size * 0.72
|
||||
var ratio: float = clampf(enemy.hp / enemy.max_hp, 0.0, 1.0)
|
||||
|
||||
@@ -6,7 +6,7 @@ RequestExecutionLevel user
|
||||
!include "LogicLib.nsh"
|
||||
|
||||
!define PRODUCT_NAME "Void Bastion"
|
||||
!define PRODUCT_VERSION "0.1.0-alpha.4"
|
||||
!define PRODUCT_VERSION "0.1.0-alpha.5"
|
||||
!define PRODUCT_PUBLISHER "GerGames"
|
||||
!define PRODUCT_WEBSITE "https://void-bastion.gergames.de/"
|
||||
!define PRODUCT_EXE "Void-Bastion.exe"
|
||||
@@ -21,7 +21,7 @@ BrandingText "GerGames"
|
||||
ShowInstDetails show
|
||||
ShowUninstDetails show
|
||||
|
||||
VIProductVersion "0.1.0.4"
|
||||
VIProductVersion "0.1.0.5"
|
||||
VIAddVersionKey /LANG=1031 "ProductName" "${PRODUCT_NAME}"
|
||||
VIAddVersionKey /LANG=1031 "CompanyName" "${PRODUCT_PUBLISHER}"
|
||||
VIAddVersionKey /LANG=1031 "FileDescription" "${PRODUCT_NAME} Setup"
|
||||
|
||||
+2
-2
@@ -15,7 +15,7 @@ compatibility/default_parent_skeleton_in_mesh_instance_3d=true
|
||||
[application]
|
||||
|
||||
config/name="Void Bastion"
|
||||
config/version="0.1.0-alpha.4"
|
||||
config/version="0.1.0-alpha.5"
|
||||
run/main_scene="res://main.tscn"
|
||||
config/features=PackedStringArray("4.7")
|
||||
config/icon="res://assets/app_icon.png"
|
||||
@@ -43,5 +43,5 @@ enabled=PackedStringArray("res://addons/agent_tools/plugin.cfg", "res://addons/A
|
||||
[rendering]
|
||||
|
||||
renderer/rendering_method="gl_compatibility"
|
||||
renderer/rendering_method.mobile="gl_compatibility"
|
||||
renderer/rendering_method.mobile="mobile"
|
||||
textures/vram_compression/import_etc2_astc=true
|
||||
+17
-7
@@ -135,10 +135,10 @@ func _run() -> void:
|
||||
assert(game.get("game_mode") == "menu", "Die Bestenliste benötigt einen funktionierenden Zurück-Button.")
|
||||
assert(not account_manager.player_id.is_empty(), "Beim ersten Start muss automatisch ein stabiles Gastprofil angelegt werden.")
|
||||
assert(not account_manager.display_name.is_empty(), "Das Gastprofil benötigt einen sichtbaren Pilotennamen.")
|
||||
assert(game.call("_app_version") == "0.1.0-alpha.4", "Profil, Installer und Online-Übertragung müssen dieselbe Alpha-Version verwenden.")
|
||||
assert(game.call("_app_version") == "0.1.0-alpha.5", "Profil, Installer und Online-Übertragung müssen dieselbe Alpha-Version verwenden.")
|
||||
var support_url: String = game.call("_support_email_url")
|
||||
assert(support_url.begins_with("mailto:support@gergames.de?"), "Die Feedback-Schaltfläche muss die öffentliche Supportadresse verwenden.")
|
||||
assert(support_url.contains("0.1.0-alpha.4") and support_url.contains(account_manager.short_id()), "Die Support-Mail muss Version und gekürzte Spieler-ID zur Fehlerzuordnung enthalten.")
|
||||
assert(support_url.contains("0.1.0-alpha.5") and support_url.contains(account_manager.short_id()), "Die Support-Mail muss Version und gekürzte Spieler-ID zur Fehlerzuordnung enthalten.")
|
||||
account_manager.storage_enabled = false
|
||||
var original_display_name: String = account_manager.display_name
|
||||
assert(account_manager.validate_display_name("Kommandant Ägir").is_empty(), "Ein normaler internationaler Spielername muss gültig sein.")
|
||||
@@ -152,14 +152,14 @@ func _run() -> void:
|
||||
assert(account_manager.get_map_best_wave(0, false) >= 3, "Die Profilansicht muss den gespeicherten Missionsbestwert lesen können.")
|
||||
assert(not ads_manager.mobile_ads_supported, "Auf Windows müssen Anzeigen vollständig deaktiviert bleiben.")
|
||||
var update_manager = game.get_node("UpdateManager")
|
||||
assert(update_manager.call("_is_newer_version", "0.1.0-alpha.4", "0.1.0-alpha.3"), "Eine neuere Alpha-Version muss als Update erkannt werden.")
|
||||
assert(update_manager.call("_is_newer_version", "0.1.0-alpha.5", "0.1.0-alpha.4"), "Eine neuere Alpha-Version muss als Update erkannt werden.")
|
||||
assert(update_manager.call("_is_newer_version", "0.1.0", "0.1.0-alpha.2"), "Eine stabile Version muss neuer als ihre Alpha-Version sein.")
|
||||
assert(not update_manager.call("_is_newer_version", "0.1.0-alpha.4", "0.1.0-alpha.4"), "Die installierte Version darf nicht als Update angeboten werden.")
|
||||
assert(not update_manager.call("_is_newer_version", "0.1.0-alpha.5", "0.1.0-alpha.5"), "Die installierte Version darf nicht als Update angeboten werden.")
|
||||
var announced_update := {}
|
||||
update_manager.update_available.connect(func(info: Dictionary): announced_update.merge(info, true))
|
||||
var fake_release_body := JSON.stringify([{"tag_name": "v0.1.0-alpha.5", "name": "Alpha.5", "draft": false, "assets": [{"name": "Void-Bastion-Setup-x64.exe", "browser_download_url": "https://git.dominicspringer.de/public-test.exe"}]}]).to_utf8_buffer()
|
||||
var fake_release_body := JSON.stringify([{"tag_name": "v0.1.0-alpha.6", "name": "Alpha.6", "draft": false, "assets": [{"name": "Void-Bastion-Setup-x64.exe", "browser_download_url": "https://git.dominicspringer.de/public-test.exe"}]}]).to_utf8_buffer()
|
||||
update_manager.call("_on_request_completed", HTTPRequest.RESULT_SUCCESS, 200, PackedStringArray(), fake_release_body)
|
||||
assert(announced_update.get("version") == "0.1.0-alpha.5", "Die Update-Prüfung muss eine neuere öffentliche Version ankündigen.")
|
||||
assert(announced_update.get("version") == "0.1.0-alpha.6", "Die Update-Prüfung muss eine neuere öffentliche Version ankündigen.")
|
||||
assert(str(announced_update.get("url", "")).begins_with("https://git.dominicspringer.de/"), "Der Update-Hinweis darf nur einen öffentlichen Download-Link öffnen.")
|
||||
assert(game.get("path_points").size() == 9, "Bastion Prime sollte mit einer eigenen Einfahrt im Reaktor enden.")
|
||||
_assert_grid_aligned_path(game.get("path_points"), "Bastion Prime")
|
||||
@@ -264,6 +264,16 @@ func _run() -> void:
|
||||
assert(game.get("enemies_to_spawn") > 9, "Mittel muss mehr Gegner als Leicht erzeugen.")
|
||||
game.set("selected_difficulty", 2)
|
||||
game.call("_start_game")
|
||||
assert(game.get("credits") == 340 and game.get("reactor_lives") == 12, "Schwer muss mit knapperen Startressourcen und weniger Reaktorleben beginnen.")
|
||||
assert(int(game.call("_difficulty").crystals) == 12, "Ein Sieg auf Schwer muss den höheren Kristallbonus gewähren.")
|
||||
assert(game.get("ENEMY_DATA").size() == 6, "Drei zusätzliche Gegnerrollen müssen im Spiel verfügbar sein.")
|
||||
assert(game.call("_enemy_type_for_wave", 3, 0.9, false) == 3, "Auf Schwer muss der Abfangjäger ab Welle 3 auftreten.")
|
||||
assert(game.call("_enemy_type_for_wave", 5, 0.85, false) == 4, "Auf Schwer muss der Schildkreuzer ab Welle 5 auftreten.")
|
||||
assert(game.call("_enemy_type_for_wave", 8, 0.95, false) == 5, "Auf Schwer muss der Belagerer ab Welle 8 auftreten.")
|
||||
game.set("selected_difficulty", 1)
|
||||
var medium_late_health: float = game.call("_enemy_health_scale", 12, false)
|
||||
game.set("selected_difficulty", 2)
|
||||
assert(game.call("_enemy_health_scale", 12, false) > medium_late_health, "Schwer muss ab der Missionsmitte stärker skalieren als Mittel.")
|
||||
game.set("wave", 1)
|
||||
game.set("enemies_to_spawn", 2)
|
||||
game.call("_spawn_enemy")
|
||||
@@ -298,7 +308,7 @@ func _run() -> void:
|
||||
game.call("_spawn_enemy")
|
||||
var boss: Dictionary = game.get("enemies")[-1]
|
||||
assert(boss.boss, "Jede fünfte Welle sollte ein Kommandoschiff erzeugen.")
|
||||
assert(boss.type == 2, "Das Kommandoschiff sollte die schwere Schiffsklasse verwenden.")
|
||||
assert(boss.type == 5, "Das Kommandoschiff sollte die schwere Belagerer-Klasse verwenden.")
|
||||
game.call("_start_game")
|
||||
var crystals_before: int = game.get("core_crystals")
|
||||
game.set("wave", 15)
|
||||
|
||||
Reference in new issue
Block a user