Add campaign editor and mission scripting docs

This commit is contained in:
Richard Zink committed 2026-11-05 12:23:05 +01:00
1 parent b06c48172b
commit 2b7e8af674
2 files changed
+2284

No files matched your search

+769
View File
@@ -0,0 +1,769 @@
# Kampagneneditor - Ziel, Dateiformat und Verzweigungen
Dieses Dokument beschreibt den geplanten umfassenden Kampagneneditor für DungeonCraft. Der Editor soll vom Hauptmenü startbar sein und es ermöglichen, ganze Kampagnen zu erstellen, zu speichern, zu öffnen und an andere Spieler weiterzugeben.
Status: Entwurf plus erste Umsetzung. Die Kernstruktur (`CampaignDoc`, `MissionDoc`, `BranchDoc`, `.dccamp`, Menüpunkt, Editorseite, Start einzelner Missionen und Branch-Auswahl) ist implementiert; Karten-/Zonen-Editing folgt. Der Script-Basisruntime (`on_start`, `m.log`, `m.objective`, `m.spawn_at`, `m.spawn_count_in_zone`, `m.spawn_wave`) ist bereits in Rust integriert.
## 1. Ziel
Der Kampagneneditor soll später Folgendes können:
- Kampagnen aus dem Hauptmenü starten.
- Neue Kampagnen erstellen.
- Bestehende Kampagnen öffnen.
- Kampagnen als Datei speichern und teilen.
- Missionen einer Kampagne bearbeiten.
- Missionsziele definieren.
- Kampagnenverzweigungen erstellen.
- Briefing, Titel, Story, Hinweise und objectives bearbeiten.
- Kartenparameter bearbeiten.
- Festungen, Tore, Flüsse, Höhlen, Manaquellen und Rivalen setzen.
- Triggerzonen grafisch festlegen.
- Missionsskripte mit Rhai bearbeiten.
- Skripteditor mit Syntax-Highlighting und Syntaxprüfung.
- Missionen testweise starten.
- Kampagnenfortschritt speichern.
## 2. Start im Hauptmenü
Der Editor soll im Hauptmenü erreichbar sein, zum Beispiel:
```text
DungeonCraft
- Kampagne
- Multiplayer
- Neues Spiel
- Spiel laden
- Einstellungen
- Konto
- Standalone herunterladen
- Bestiarium
- Kampagneneditor
- Hilfe
```
Geplante Menüaktion:
```rhai
Btn::CampaignEditor
MenuAction::OpenCampaignEditor
Page::CampaignEditor
```
Der Editor soll auch aus dem Pausemenü erreichbar sein, wenn eine Kampagne gespielt wird, aber im Spielbetrieb nicht die aktuelle Mission zerstören, ohne eine Sicherung anzulegen.
## 3. Kampagnendatei speichern und öffnen
Kampagnendateien sollen weitergegeben werden können. Geplante Endung:
```text
.dccamp
```
Eine `.dccamp`-Datei ist JSON. Sie enthält die vollständige Kampagne:
- Kampagnenname
- Missionen
- Missionsziele
- Verzweigungen
- Kartenparameter
- Scripte
- Triggerzonen
- optionale Sprachtexte
- Version und Formatangaben
Beispielhafter Export:
```json
{
"format": "dungeoncraft.campaign",
"version": 1,
"name": "Meine eigene Kampagne",
"author": "Spieler",
"missions": [
{
"key": "my01",
"title": "Der erste Schacht",
"story": "Ein kleiner Posten, ein Splitter, zwei Wege.",
"size": [96, 80],
"seed": 101010,
"gold": 2500,
"goals": [
"Shards"
],
"next": "my02a",
"branches": [
{
"id": "killall_path",
"goal": "KillAll",
"title": "Die Wachstube zerstören",
"next": "my02a"
},
{
"id": "loot_path",
"goal": "Loot(3000)",
"title": "Die Tresore plündern",
"next": "my02b"
}
],
"zones": [
{
"name": "north_gate",
"shape": "circle",
"at": [0.70, 0.50],
"radius": 3.0
}
],
"script": {
"language": "rhai",
"source": "fn on_start(m) { m.log('Krummfinger: Es gibt zwei Wege.'); }"
}
}
]
}
```
Geplante Menüfunktionen:
```text
Kampagne speichern ...
Kampagne öffnen ...
Kampagne herunterladen
Kampagne testweise spielen
```
Im Browser kann das mit den vorhandenen Datei-APIs realisiert werden:
- Speichern: Datei als `.dccamp` herunterladen.
- Öffnen: Datei mit Dateiauswahl öffnen und JSON lesen.
- Herunterladen: aktuelle Kampagne als `.dccamp` exportieren.
## 4. Kampagnenstruktur
Eine Kampagne ist nicht mehr nur eine lineare Liste von Missionen. Sie ist ein Missionsgraph.
Vereinfacht:
```text
my01
|
|--> Ziel A erreicht: my02a
|--> Ziel B erreicht: my02b
```
Komplexer:
```text
my01
|
|--> KillAll: my02a
|--> Loot(3000): my02b
my02a
|
|--> Shards: my03a
|--> Rivals: my03b
my02b
|
|--> Freed(1): my03c
|--> Towers: my03b
```
Die Kampagne kann dadurch mehrere Abschnitte, alternative Wege und Wiedervereinigungen haben.
## 5. Mission mit Verzweigung
Eine Missionsdefinition muss später zwischen normalen Zielen und Verzweigungszielen unterscheiden.
Geplante Bedeutung:
- `goals`: optionale oder normale Ziele.
- `branches`: alternative Missionsenden.
- Eine Mission gilt als erfolgreich, wenn ein Verzweigungsziel erreicht ist.
- Das erreichte Verzweigungsziel bestimmt die nächste Mission.
Beispiel:
```json
{
"key": "my01",
"title": "Die two Wege",
"story": "Die Ritter haben entweder eine Wachstube oder ein Schatzhaus. Du kannst dich entscheiden, was du zuerst schlägst.",
"goals": [
"Shards"
],
"branches": [
{
"id": "kill_path",
"goal": "KillAll",
"title": "Die Wachstube zerstören",
"next": "my02a",
"priority": 1
},
{
"id": "loot_path",
"goal": "Loot(3000)",
"title": "Die Tresore plündern",
"next": "my02b",
"priority": 2
}
]
}
```
Regel:
1. `KillAll` wird zuerst erreicht: Mission endet, nächste Mission ist `my02a`.
2. `Loot(3000)` wird zuerst erreicht: Mission endet, nächste Mission ist `my02b`.
3. Wenn beide gleichzeitig erreicht werden, gewinnt die Branch mit niedrigerer `priority`. Ist keine Priorität gesetzt, gewinnt die zuerst definierte Branch.
4. Wenn eine Mission mehrere `branches` hat, darf sie nicht als vollständig gelten, solange keine Branch erreicht ist.
5. Eine Mission ohne `branches` nutzt weiterhin `next` oder die lineare Reihenfolge.
## 6. Mission mit alternativem Ziel a und b
Beispiel mit zwei alternativen Hauptzielen:
```json
{
"key": "my03",
"title": "Der Nachbarsstreit",
"story": "Mossgrim oder der Orden - du kannst nur einen von beiden zuerst ausschalten.",
"goal_mode": "any",
"branches": [
{
"id": "mossgrim_first",
"goal": "Rivals",
"title": "Mossgrim zuerst ausschalten",
"next": "my04a"
},
{
"id": "order_first",
"goal": "Heart",
"title": "Das Tor des Ordens zuerst brechen",
"next": "my04b"
}
]
}
```
Spielerlebnis:
- Der Spieler sieht im Briefing beide Wege.
- Das Interface zeigt zwei objectives:
- Mossgrims Kern zerstören.
- Das Ordenstor brechen.
- Sobald eines davon erreicht ist, wird die Mission beendet.
- Der Fortschritt speichert, welcher Weg gewählt wurde.
- Die nächste Mission hängt von diesem Weg ab.
## 7. Missionsfortschritt bei Verzweigungen
Der Kampagnenfortschritt muss später Verzweigungen speichern.
Geplanter Fortschritt:
```json
{
"format": "dungeoncraft.progress",
"campaign": "Meine eigene Kampagne",
"done": {
"my01": true,
"my02b": false
},
"completed_choice": {
"my01": "loot_path"
},
"unlocked": [
"my01",
"my02b"
],
"carry": {
"my01": {
"creatures": [["Ogre", 3, 120]],
"gold": 500,
"rune_free": 0,
"rune_final": 0
}
}
}
```
Regeln:
- Eine Mission ist nur dann abgeschlossen, wenn eine ihrer Branches erreicht wurde.
- Die erreichte Branch speichert `completed_choice`.
- Die nächste Mission wird durch `next` der erreichten Branch bestimmt.
- Andere Branches einer bereits entschiedenen Mission gelten als übersprungen.
- Carry wird von der entschiedenen Mission zur nächsten Mission mitgenommen.
- Eine wiedervereinigte Mission kann von mehreren Wegpunkten betreten werden. In diesem Fall muss der Editor entscheiden, welcher Carry-Status verwendet wird, oder jeder Pfad speichert seinen eigenen Carry.
Geplante Variante mit getrenntem Carry pro Branch:
```json
"completed_choice": {
"my01": "loot_path"
},
"branch_done": {
"my01.kill_path": false,
"my01.loot_path": true
},
"branch_carry": {
"my01.loot_path": {
"gold": 900,
"creatures": [["Ogre", 3, 0]]
}
}
```
## 8. Editor-Ansicht für Kampagnenverzweigungen
Im Editor soll es eine Kampagnenübersicht geben.
Geplante Darstellung:
```text
My01 Die two Wege
|- KillAll -> My02a Die Wachstube
| |- Shards -> My03a
| `- Rivals -> My03b
`- Loot(3000) -> My02b Das Schatzhaus
|- Freed(1) -> My03c
`- Towers -> My03b
```
Editor-Funktionen:
- Mission hinzufügen.
- Mission entfernen.
- Mission umbenennen.
- Mission verschieben.
- Verzweigungsziel hinzufügen.
- `goal` auswählen.
- `next` Mission auswählen.
- Branch-Titel und Beschreibung bearbeiten.
- Priorität setzen.
- Testpfad starten.
- Zyklusprüfung: Kampagnen dürfen keine Endlosschleifen ohne Spielfortschritt enthalten.
- Erreichbarkeitsprüfung: alle Missionen sollen vom Start aus erreichbar sein, außer sie sind explizit als optionale Enden oder Extra-Missionen markiert.
## 9. Kampagnendatei mit Verzweigungen speichern
Eine gespeicherte Kampagne muss die Verzweigungen enthalten.
Beispielmission mit zwei Folgemissionen:
```json
{
"key": "my01",
"title": "Die zwei Wege",
"size": [96, 80],
"seed": 101010,
"gold": 2500,
"branches": [
{
"id": "kill_path",
"goal": "KillAll",
"title": "Die Wacht zuerst ausschalten",
"next": "my02a",
"priority": 1
},
{
"id": "loot_path",
"goal": "Loot(3000)",
"title": "Die Tresore zuerst plündern",
"next": "my02b",
"priority": 2
}
],
"script": {
"language": "rhai",
"source": "fn on_start(m) { m.objective('Wähle einen Weg: die Wachstube oder die Tresore.'); }"
}
}
```
Wenn die Kampagne gespeichert wird, muss der Editor prüfen:
- `my02a` existiert.
- `my02b` existiert.
- `goal KillAll` ist für diese Mission sinnvoll.
- `goal Loot(3000)` ist für diese Mission sinnvoll.
- Es gibt mindestens einen Startpfad.
- Die Mission endet wirklich, wenn eines der Branch-Ziele erreicht wird.
## 10. Geplante Rust-Datenstruktur
Für die Implementierung könnte der Editor mit datengetriebenen Missionen arbeiten.
Geplante Rust-Strukturen:
```rust
pub struct CampaignDoc {
pub format: String,
pub version: u32,
pub name: String,
pub author: String,
pub missions: Vec<MissionDoc>,
}
pub struct MissionDoc {
pub key: String,
pub title: String,
pub story: String,
pub size: (usize, usize),
pub seed: u32,
pub gold: f32,
pub portals: usize,
pub treasures: usize,
pub goals: Vec<String>,
pub waves: bool,
pub wave_limit: u32,
pub first_wave: f32,
pub time_limit: f32,
pub next: Option<String>,
pub branches: Vec<BranchDoc>,
pub script: Option<ScriptDoc>,
pub zones: Vec<ZoneDoc>,
}
pub struct BranchDoc {
pub id: String,
pub goal: String,
pub title: String,
pub next: String,
pub priority: Option<u32>,
}
pub struct ScriptDoc {
pub language: String,
pub source: String,
}
pub struct ZoneDoc {
pub name: String,
pub shape: String,
pub at: Option<(f32, f32)>,
pub radius: Option<f32>,
pub tiles: Option<Vec<(i32, i32)>>,
pub from: Option<(f32, f32)>,
pub to: Option<(f32, f32)>,
pub width: Option<f32>,
}
```
Die `String`-Ziele müssen beim Spielen in echte `Goal`-Werte übersetzt werden.
Beispiel:
```text
"Shards" -> Goal::Shards
"Heart" -> Goal::Heart
"KillAll" -> Goal::KillAll
"Loot(3000)" -> Goal::Loot(3000)
"Survive(4)" -> Goal::Survive(4)
"Springs(2)" -> Goal::Springs(2)
"Freed(1)" -> Goal::Freed(1)
"Rivals" -> Goal::Rivals
"Towers" -> Goal::Towers
```
## 11. Verzweigungslogik im Spiel
Die Spiellogik braucht einen Missionsabschluss mit Branch.
Geplanter Ablauf:
```text
Mission läuft.
Wenn ein normales Ziel erreicht wird: Fortschritt anzeigen.
Wenn ein Branch-Ziel erreicht wird:
Mission sofort beenden.
completed_choice speichern.
Carry aus aktuellem Spielstand erzeugen.
unlocked nächste Mission setzen.
Briefing oder Erfolgsmeldung anzeigen.
```
Pseudo-Logik:
```rust
if mission_has_branches {
for branch in mission.branches {
if goal_is_done(goal) && mission.state != Victory {
mission.state = Victory;
mission.completed_choice = branch.id;
progress.set_done(mission.key, branch.id, carry_from_game());
progress.unlock_mission(branch.next);
break;
}
}
} else if all_mission_goals_done() {
mission.state = Victory;
}
```
Bei zeitgleicher Erfüllung mehrerer Branches:
- Sortiere Branches nach `priority`.
- Wenn keine `priority` gesetzt ist, nutze die Reihenfolge in der JSON.
- Wenn der Spieler eine Auswahl haben soll, kann der Editor `ask_on_conflict: true` aktivieren.
## 12. Beispielmission im Kampagneneditor
Beispiel für eine Mission mit grafischen Zonen, Script und Verzweigung:
```json
{
"key": "my05",
"title": "Die Nordwand oder der Tresor",
"story": "Im Norden ist eine dünne Wand. Dahinter liegt eine Ritterstaffel. Im Osten liegt ein Tresor. Du musst dich entscheiden.",
"size": [104, 96],
"seed": 123456,
"gold": 3000,
"branches": [
{
"id": "north_wand",
"goal": "KillAll",
"title": "Die Nordwand sprengen",
"next": "my06a",
"priority": 1
},
{
"id": "east_vault",
"goal": "Loot(4000)",
"title": "Den Tresor räumen",
"next": "my06b",
"priority": 2
}
],
"zones": [
{
"name": "north_gate",
"shape": "circle",
"at": [0.70, 0.50],
"radius": 3.0
},
{
"name": "east_vault",
"shape": "tiles",
"tiles": [[74, 44], [74, 45], [75, 44], [75, 45]]
}
],
"script": {
"language": "rhai",
"source": "fn on_start(m) { m.load_editor_zones(); m.objective('Wähle: die Nordwand oder den Tresor.'); }\n\nfn on_unit_reaches(m, unit, zone) {\n if zone == 'north_gate' && unit.side == 0 && !m.has_flag('north_opened') {\n m.set_flag('north_opened');\n m.warn('Die Nordwand fällt!');\n m.destroy_walls_in_zone('north_gate');\n m.spawn_count_in_zone('north_gate', 'Knight', 3, 1, 2);\n }\n\n if zone == 'east_vault' && unit.side == 0 && !m.has_flag('vault_alarm') {\n m.set_flag('vault_alarm');\n m.warn('Tresoralarm!');\n m.destroy_doors_in_zone('east_vault');\n m.spawn_count_in_zone('east_vault', 'Knight', 2, 1, 3);\n }\n}"
}
}
```
## 13. Editor-Fehlerprüfungen
Der Editor soll Fehler anzeigen, bevor eine Kampagne gespeichert oder gespielt wird:
- Mission `key` ist leer oder doppelt.
- `title` oder `story` fehlen.
- `size` ist zu klein oder zu groß.
- `seed` fehlt.
- `gold` ist unzulässig.
- Zielname ist unbekannt.
- `next` zeigt auf eine Mission, die nicht existiert.
- Branch zeigt auf eine Mission, die nicht existiert.
- Eine Mission hat keine erreichbare Startmission.
- Eine Mission ist nach keiner anderen Mission erreichbar.
- Branches erzeugen einen Zyklus ohne Abschluss.
- `KillAll` wird gewählt, aber es gibt keine Ritter.
- `Heart` wird gewählt, aber die Mission hat kein Tor.
- `Loot(3000)` wird gewählt, aber es gibt nicht genug Tresorgold.
- `Springs(2)` wird gewählt, aber es gibt weniger als zwei Manaquellen.
- `Freed(1)` wird gewählt, aber es gibt keine angekettete Kreatur.
- Skript enthält unbekannte Funktionen.
- Zone hat eine ungültige Form oder ungültige Koordinaten.
- Scriptquelle kann nicht als Rhai gelesen werden.
## 14. Scripteditor mit Syntax-Highlighting
Der Editor soll ein Textfeld für Rhai enthalten.
Geplante Editorfeatures:
- Textarea im Spiel-Editor.
- Zeilennummerierung.
- Syntax-Highlighting.
- Tastatureingabe am Desktop.
- Touch: großes Textfeld oder Editor-Hilfsdialog.
- Speichern als `script.source` in der `.dccamp`-Datei.
- Testmission direkt starten.
- Fehleranzeige für ungültige Rhai-Syntax oder unbekannte Missionsfunktionen.
Geplante Farben:
```text
Keyword: hellgelb
Comment: grau
String: grün
Number: orange
Mission method: hellblau
Creature kind: cyan
Zone name: violett
Error: rot
```
Beispiel:
```rhai
const HEROES = 1;
const PLAYER = 0;
fn on_start(m) {
m.load_editor_zones();
m.objective("Zwei Wege: Nordwand oder Tresor.");
}
fn on_unit_reaches(m, unit, zone) {
if zone != "north_gate" {
return;
}
if unit.side != PLAYER {
return;
}
if m.has_flag("north_opened") {
return;
}
m.set_flag("north_opened");
m.warn("Die Nordwand fällt!");
m.destroy_walls_in_zone("north_gate");
m.spawn_count_in_zone("north_gate", "Knight", 3, HEROES, 2);
}
```
## 15. Kampagnenweitergabe
Ein Spieler kann eine Kampagne als Datei weitergeben:
```text
meine_kampagne.dccamp
```
Ein anderer Spieler öffnet:
```text
Hauptmenü -> Kampagneneditor -> Kampagne öffnen -> meine_kampagne.dccamp
```
Danach kann die Kampagne gespielt werden.
Die Datei enthält keine Spielstände. Sie enthält nur Missionsdefinitionen, Skripte und optionale Startfortschrittsdaten. Spielstände bleiben getrennt.
Geplante Dateitypen:
```text
.dccamp - Kampagnendefinition
.dcsave - Spielstand
```
Ein Spielstand kann später auch eine aktive Kampagne referenzieren, aber die Kampagnendatei bleibt veränderbar und teilbar.
## 16. Empfohlene Implementierungsreihenfolge
Die Umsetzung sollte in Etappen erfolgen.
### Etappe A: Datenmodell
- `CampaignDoc`
- `MissionDoc`
- `BranchDoc`
- `ZoneDoc`
- `ScriptDoc`
- JSON-Laden und Speichern
- Tests für Beispielkampagnen
### Etappe B: Kampagnenstart im Menü
- Hauptmenüpunkt `Kampagneneditor`.
- Editorseite mit Kampagnenliste.
- Kampagne neu erstellen.
- Kampagne öffnen.
- Kampagne speichern.
### Etappe C: Missionseditor
- Missionsliste innerhalb einer Kampagne.
- Mission hinzufügen, entfernen, auswählen.
- Missionsdaten bearbeiten.
- Branches bearbeiten.
- Ziele bearbeiten.
- Kartenparameter bearbeiten.
### Etappe D: Verzweigungen spielen
- Mission endet bei Branch-Erfolg.
- Folgemission wird entsperrt.
- Progress speichert `completed_choice`.
- Carry übernimmt den gewählten Pfad.
- Kampagnenübersicht zeigt verzweigten Fortschritt.
### Etappe E: Zoneneditor
- Kreis zeichnen.
- Rechteck zeichnen.
- Polygon zeichnen.
- Tiles markieren.
- Linien setzen.
- Zonen automatisch ins Skript übernehmen.
### Etappe F: Scripteditor
- Texteditor mit Syntax-Highlighting.
- Rhai-Syntaxprüfung.
- Mission-API-Hilfe.
- Testmission starten.
### Etappe G: Kampagnentests
- Beispielkampagne bauen.
- Branch-Kampagne bauen.
- `.dccamp` exportieren.
- `.dccamp` importieren.
- Fortschritt speichern und laden.
## 16a. Umgesetzter Stand
- `src/campaign_doc.rs`: `CampaignDoc`, `MissionDoc`, `BranchDoc`, `ZoneDoc`, `ScriptDoc`, `CampaignProgress`.
- JSON-Dateien `.dccamp` können geladen, validiert, gespeichert und mit `CampaignDoc::file_name()` benannt werden.
- Hauptmenü: `Btn::CampaignEditor` führt auf `Page::CampaignEditor`.
- Editorseite: Kampagnenname, Missionsliste, Auswahl, Neu, Add, Delete, Open, Save, Start.
- Feldbearbeitung im ersten Schritt: Titel, Ziele, nächste Mission, Branches und Rhai-Skriptquelle über Browser-/Desktop-Eingabeaufforderungen.
- Missionsstart: `campaign::mission_def_from_doc()` und `campaign::start_mission_doc()` können eine einzelne Editormission spielen.
- Branch-Auswahl: `CampaignDoc::next_mission_choice()` wählt nach Missionsende die nächste Mission; `CampaignProgress` speichert `done` und `completed_choice`.
- Script-Basisruntime: `src/script.rs` führt `on_start(m)` in Rhai aus. Unterstützt sind vorerst `m.log(text)`, `m.objective(text)`, `m.spawn_at(side, kind, x, y, count, level)`, `m.spawn_count_in_zone(zone, kind, count, side, level)` und `m.spawn_wave(count)`; Seiten können als Zahlen (`0` bis `4`) oder Namen (`PLAYER`, `HEROES`, `MOSSGRIM`, `NIGHTWELL`, `RIVAL3`) geschrieben werden. Meldungen, objective, Spawns und eine sofortige Missionswelle werden in `Game` angewendet.
- Script-Editor (einfach): `Btn::CampaignEditScript` speichert eine Rhai-Quelle in `MissionDoc.script`; die Quelle wird vor dem Speichern mit `script::run_start()` und `validate_script_source()` geprüft. Syntaxfehler, unbekannte Kreaturnamen und unbekannte Zonennamen werden abgelehnt.
- Debug-Anzeige: Einstellungen haben eigene Debug-Optionen für FPS, Framezeit, Ping, Triggerzonen, Debug-Befehlsmarker und Pfadlinien. `guard`, `attack` und `flag`/`gather` zeigen feste Befehlsmarker; alle übrigen Befehlsmarker sind debugpflichtig.
- Noch offen: grafischer Zoneneditor, ausgebauter Rhai-Skripteditor mit Textfeld und Syntax-Highlighting, weitere Mission-Hooks (`on_unit_reaches`, `on_unit_crosses`, Flags, Zonen, zeitgesteuerte Hooks), Zonen-Spawn-API, vollständige Kartenparameter, Tragen von Einheiten/Gold durch Branches.
## 17. Kurzfassung
Ein Kampagneneditor für ganze Kampagnen braucht nicht nur Missionstexte, sondern einen Missionsgraphen.
Kernidee:
```text
Mission my01
-> Ziel A erreicht: Mission my02a
-> Ziel B erreicht: Mission my02b
```
Die Kampagne wird als `.dccamp` gespeichert:
```json
"branches": [
{ "goal": "KillAll", "next": "my02a" },
{ "goal": "Loot(3000)", "next": "my02b" }
]
```
Der Editor muss diese Verzweigungen anzeigen, prüfen und speichern können. Der Spieler sieht dann alternative objectives und die Kampagne führt je nach erreichten Zielen zu unterschiedlichen Folgemissionen.
File diff suppressed because it is too large. Load diff