Files
dungeoncraft/docs/CAMPAIGN_EDITOR.md
T

21 KiB

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:

DungeonCraft

- Kampagne
- Multiplayer
- Neues Spiel
- Spiel laden
- Einstellungen
- Konto
- Standalone herunterladen
- Bestiarium
- Kampagneneditor
- Hilfe

Geplante Menüaktion:

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:

.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:

{
  "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:

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:

my01
  |
  |--> Ziel A erreicht: my02a
  |--> Ziel B erreicht: my02b

Komplexer:

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:

{
  "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:

{
  "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:

{
  "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:

"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:

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:

{
  "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:

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:

"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:

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:

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:

{
  "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:

Keyword:        hellgelb
Comment:        grau
String:         grün
Number:         orange
Mission method: hellblau
Creature kind:  cyan
Zone name:      violett
Error:          rot

Beispiel:

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:

meine_kampagne.dccamp

Ein anderer Spieler öffnet:

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:

.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:

Mission my01
  -> Ziel A erreicht: Mission my02a
  -> Ziel B erreicht: Mission my02b

Die Kampagne wird als .dccamp gespeichert:

"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.