Dies ist eine alte Version des Dokuments!
Inhaltsverzeichnis
Armeesteuerung
In vorigen Kapiteln wie im Artikel zur Erstellung einfacher Computergegner oder im Artikel zu Banditenlagern und Spawnern haben wir große Teile der Armeesteuerung an Comfortfunktionen übergeben und lediglich Parameter definiert. In diesem Kapitel werden dagegen elementare Kontrollfunktionen für Armeen vorgestellt, wie sie funktionieren und wo ihre Grenzen sind. Für das Verständnis dieses Artikels ist es von Vorteil, mit Banditenlagern und Spawnern vertraut zu sein und damit bereits experimentiert zu haben.
Armeen sind Truppenverbände, die immer im Gesamten gesteuert werden (es werden also nie die einzelnen Truppen angesprochen). Jede Armee hat eine Position (auch Anker genannt) und einen Radius, in dem sie um den Anker herum operiert. Das Gebiet innerhalb des Radius um den Anker werden wir im Folgenden als Aktionsgebiet bezeichnen. Eine Armee wird ausschließlich über das Verschieben des Aktionsgebietes gesteuert. Im Abschnitt zu den Armeesteuerungsfunktionen findest du einige Grafiken, die das Prinzip verdeutlichen.
Der Artikel ist folgendermaßen strukturiert: Zuerst beschreiben wir, wie Armeen aufgesetzt werden. Anschließend listen wir Funktionen, mit denen sich der Status einer Armee abfragen lässt. Darauf folgen die elementaren Kontrollfunktionen, die für die Steuerung einer Armee zur Verfügung stehen und wie diese einzusetzen sind. Zum Schluss wollen wir all diese Funktionen zu einer komplexeren Armeesteuerung vereinen, die es zum Beispiel TickOffensiveAIController
auch macht.
Aufsetzen einer Armee
Falls du den Abschitt zu Armee-Basisparametern schon kennst, kannst du diesen Abschnitt überspringen. Die Inhalte sind hier der Vollständigkeit halber enthalten.
Armeen werden mit der Funktion SetupArmy(_ArmyTable)
aufgesetzt. Der Anker wird auf die angegebene Position gelegt und der angegebene Radius grenzt das Aktionsgebiet ein. Der Parameter _ArmyTable
ist ein Table mit den folgenden Key-Value-Paaren:
Key | Value-Typ | Bedeutung |
---|---|---|
player | Player Id | Spieler-Id des Spielers, dem die Armee gehören soll |
id | Ganze Zahl (0 - 9) | Id der Armee. Es darf pro Spieler-Id und Armee-Id maximal eine Armee geben. Somit ist die Anzahl der Armeen pro Spieler auf 10 beschränkt |
position | Position (Table der Form {X = x, Y = y} ) | Anfangsposition der Armee |
rodeLength | Number | Radius um position , innerhalb dessen sich die Armee bewegt |
beAgressive | Boolean | Legt fest, ob die Armee auf dem Weg zu einem Angriffsziel Gegner angreifen soll (für ausschließlich defensive Armee irrelevant). true ist hier eigentlich immer sinnvoll |
Für einige der Funktionen, die den Armeestatus abfragen, brauchen außerdem eine Angabe der maximalen Armeestärke im Parameter strength
:
strength | Integer ≤ 8 | Anzahl der Truppen, die eine Armee maximal besitzen können soll. Da eine Armee nur höchstens 8 Truppen steuern kann, sollte die Armeestärke 8 nicht überschreiten |
Eine Armee für Spieler 2 kann also beispielsweise so aufgesetzt werden:
function CreateArmy() -- Wir definieren das Armee-Table -- Um die Armee später steuern zu können, muss dieses Table global sein! Army = { player = 2, -- wir wählen die Id 0 -- bei mehreren Armeen für Spieler 2 müssen alle unterschiedliche Ids haben id = 0, position = GetPosition("PositionArmy"), rodeLength = 4000, beAgressive = true, strength = 6 } SetupArmy(Army) end
Spieler-Id, Armee-Id, und Aggressivität bleiben für jede Armee fest. Um die Armee zu steuern, werden später nur die Position und die rodeLength
, also der Radius, modifiziert.
Achtung: Die Verwendung von Armeen setzt voraus, dass für den KI-Spieler, dem die Armee gehört, auch eine KI aktiv ist! SetupPlayerAi
ist also zwingend notwendig! Weil das Spiel abstürzt, wenn eine KI aktiviert wird, ohne, dass der KI-Spieler ein Gebäude besitzt, muss für jeden Spieler mit einer Armee auch mindestens 1 Gebäude auf der Map stehen.
Gleichzeitig gibt es Konflikte mit der Funktion MapEditor_SetupAI. Die Angabe einer Armee-Id ist zwingend erforderlich. MapEditor_SetupAI
belegt einige dieser Ids, abhängig vom angegebenen _Strength
-Wert und „bemächtigt“ sich aller Armeen, deren Ids MapEditor_SetupAI
für sich reserviert. Dadurch werden sie der Steuerung durch eigene Skripte entzogen. Die Tabelle unten zeigt die Armee-Ids, die durch MapEditor_SetupAI
nicht mehr zur Verfügung stehen.
_Strength | Reservierte Armee-Ids |
---|---|
0 | keine |
1 | 1 bis 2 |
2 | 1 bis 4 |
3 | 1 bis 6 |
Wichtig: Mit dem Aufruf von SetupArmy
hat die Armee keine Truppen und wird sie auch nicht selbstständig rekrutieren! Es wird dem Spiel lediglich klargemacht, dass der angegebene Spieler eine Armee mit der designierten Id bekommt und ein leerer Container dafür erstellt. Wie die Armee Truppen erhalten kann (sowohl gespawnt als auch rekrutiert) wird im Abschnitt zu Truppenerstellung beschrieben.
Ein Wort zur automatischen Truppenverstärkung
Unabhängig davon, wie wir weiter unten die Armee befüllen, wird die KI automatisch versuchen, angeschlagene Truppen zu verstärken, indem sie sie zu einer Kaserne (falls vorhanden) zurückzieht und dort neue Soldaten anwirbt. Dieses Verhalten ist nicht abstellbar und kann insbesondere bei rekrutierten Truppen für unerwünschtes Verhalten sorgen (mehr dazu siehe im entsprechenden Abschnitt). Solange die KI einen Trupp zur Verstärkung zurückzieht, ist dieser kein Teil der Armee und kann darum auch nicht mehr über Armeefunktionen gesteuert werden. Erst nachdem der Trupp wieder komplett ist, gibt die KI ihn frei und fügt ihn automatisch zurück zur Armee hinzu.
Funktionen zum Armeestatus
Mit diesen Funktionen kannst du den Status deiner Armee abfragen: Wie viele Truppen sind darin noch enthalten und wo ist der nächste Gegner zum aktuellen Anker? Alle diese Funktionen haben gemeinsam, dass sie als (ersten) Parameter das Armeetable erwarten, das wir oben angelegt haben. Denke wie weiter oben beschrieben daran, dass Truppen, die von der KI gerade verstärkt werden, nicht Teil der Armee sind, bis sie wieder volle Truppstärke haben!
IsDead
Mit IsDead(_ArmyTable)
fragst du ab, ob die angegebene Armee besiegt wurde. Eine Armee gilt als besiegt, wenn sie keine Truppen mehr besitzt. Die Funktion gibt einen Boolean zurück. Die komplette Beschreibung findest du hier.
IsAlive
IsAlive(_ArmyTable)
entspricht genau not IsDead(_ArmyTable)
.
HasFullStrength
HasFullStrength(_ArmyTable)
gibt Auskunft darüber, ob die Armee volle Stärke besitzt, das heißt, ob sie über mindestens so viele Hauptmänner verfügt, wie bei strength
im ArmyTable angegeben ist. Der Rückgabewert ist ein Boolean. Näheres dazu findest du hier.
IsWeak
IsWeak(_ArmyTable)
entspricht genau not HasFullStrength(_ArmyTable)
.
IsVeryWeak
Mit IsVeryWeak(_ArmyTable)
wird ermittelt, ob die Armee weniger als ein Drittel ihrer maximalen Truppenstärke besitzt. Beispielsweise haben wir oben eine strength
von 6
angegeben. IsVeryWeak
wäre in dem Fall true, wenn die Armee weniger als 2 Truppen hat. Zur Funktionsreferenz gehts hier entlang.
GetNumberOfLeaders
Wenn du abseits von IsDead
, HasFullStrength
und IsVeryWeak
genaue Auskunft über die Sträke einer Armee abfragen möchtest, kannst du das mit GetNumberOfLeaders(_ArmyTable)
tun. Die Funktion gibt die genaue Anzahl an Hauptmännern zurück, die sich aktuell in der Armee befinden. Der Artikel in der Funktionsreferenz ist hier.
GetClosestEntity
GetClosestEntity(_ArmyTable, _Radius)
gibt die Entity-Id des nächsten Gegners an, der innerhalb des angegebenen _Radius
dem aktuellen Anker am nächsten liegt. Ist kein _Radius
angegeben, wird der aktuelle Aktionsradius/rodeLength verwendet. Die genaue Beschreibung der Funktion findest du hier.
GetPosition
GetPosition(_ArmyTable)
bezogen auf die Armee im _ArmyTable
gibt die aktuelle Position des Ankers der Armee zurück. Beachte, dass eine Armee sich auch auf dem Weg zum Anker befinden kann, wodurch die Position der Truppen nicht mehr der Position des Ankers entspricht. Den Referenzartikel findest du hier.
Elementare Funktionen zur Armeesteuerung
Alle Funktionen, die zum Steuern der Armee verwendet werden, müssen ständig wiederholt aufgerufen werden (üblicherweise alle 5 - 10 Sekunden). Das liegt daran, dass jede dieser Funktionen lediglich das Aktionsgebiet verschiebt (also den Anker und den Radius modifiziert). Anschließend wird geprüft, ob alle Truppen der Armee im Aktionsgebiet sind und ob sich Gegner darin befinden. Würden die Funktionen nicht wiederholt aufgerufen werden, würden die Truppen der Armee nicht zuverlässig im Aktionsgebiet stationiert werden und auch nicht gemeinsam einen eindringenden Gegner angreifen.
In der Regel wird also so oder so ähnlich beim Erstellen einer Armee ein SimpleJob
aufgesetzt, der die Armee steuert:
function CreateExampleArmy() -- hier steht der Code für das ArmyTable -- [ ... ] SetupArmy(ExampleArmy) StartSimpleJob("ControlExampleArmy") end function ControlExampleArmy() -- Die Kontrollbefehle, die weiter unten folgen, sollen nur alle 5 Sekunden aufgerufen werden -- Denke daran, jeder Kontrollfunktion einen eigenen Counternamen zu geben if not Counter.Tick2("ControlExampleArmyCounter", 5) then return end -- Wenn die Armee tot ist (= keine Truppen mehr übrig), wird der Kontrolljob beendet -- Natürlich kann auch eine andere Bedingung den Job abbrechen. Beispielsweise kann -- eine Armee aus einem Gebäude spawnen. Dann ist es viel sinnvoller, den Tod des Gebäudes -- statt den der Armee zur Bedingung für das Ende des Kontrolljobs zu definieren if IsDead(ExampleArmy) then return true end -- hier werden nun die Kontrollfunktionen, die wir weiter unten vorstellen, eingefügt. Diese -- können untereinander kombiniert und beispielsweise nur unter verschiedenen -- Voraussetzungen aufgerufen werden. Ein komplexeres Beispiel findest du im letzten Abschnitt -- dieses Artikels -- [ ... ] end