Add attack path to AI and EnemySpawn

alut-include-path
Sven Eberhardt 2017-02-20 22:25:00 -05:00
parent 9abb0bc200
commit 65db84d07e
8 changed files with 110 additions and 7 deletions

View File

@ -59,6 +59,11 @@ local SingleWeaponAttackMode = {
{
if (!(fx.weapon = fx.Target->FindContents(fx.attack_mode.Weapon))) return false;
fx.strategy = fx.attack_mode.Strategy;
if (fx.weapon->~HasExplosionOnImpact())
{
fx.can_attack_structures = true;
fx.can_attack_structures_after_weapon_respawn = fx.attack_mode.Respawn; // allow structure attack even during respawn time
}
return true;
},
GetName = func()

View File

@ -89,15 +89,48 @@ public func ExecuteJump(effect fx)
return false;
}
// Return to the AI's home if not yet there.
// Follow attack path or return to the AI's home if not yet there.
public func ExecuteIdle(effect fx)
{
if (fx.attack_path)
{
var next_pt = fx.attack_path[0];
// Check for structure to kill on path. Only if the structure is alive or of the clonk can attack structures with the current weapon.
var alive_check;
if (!fx.can_attack_structures && !fx.can_attack_structures_after_weapon_respawn)
{
alive_check = Find_OCF(OCF_Alive);
}
if ((fx.target = FindObject(Find_AtPoint(next_pt.X, next_pt.Y), Find_Func("IsStructure"), alive_check)))
{
// Do not advance on path unless target(s) destroyed.
return true;
}
// Follow path
fx.home_x = next_pt.X;
fx.home_y = next_pt.Y;
fx.home_dir = Random(2);
}
if (!Inside(fx.Target->GetX() - fx.home_x, -5, 5) || !Inside(fx.Target->GetY() - fx.home_y, -15, 15))
{
return fx.Target->SetCommand("MoveTo", nil, fx.home_x, fx.home_y);
}
else
{
// Next section on path or done?
if (fx.attack_path)
{
if (GetLength(fx.attack_path) > 1)
{
fx.attack_path = fx.attack_path[1:];
// Wait one cycle. After that, ExecuteIdle will continue the path.
}
else
{
fx.attack_path = nil;
}
}
// Movement done (for now)
fx.Target->SetCommand("None");
fx.Target->SetComDir(COMD_Stop);
fx.Target->SetDir(fx.home_dir);

View File

@ -206,6 +206,18 @@ public func SetEncounterCB(object clonk, string cb_fn)
return true;
}
// Set attack path
public func SetAttackPath(object clonk, array new_attack_path)
{
if (GetType(this) != C4V_Def)
Log("WARNING: SetAttackPath(%v, %v) not called from definition context but from %v", clonk, new_attack_path, this);
var fx_ai = GetAI(clonk);
if (!fx_ai)
return false;
fx_ai.attack_path = new_attack_path;
return true;
}
/*-- AI Effect --*/
@ -287,16 +299,27 @@ local FxAI = new Effect
},
SetAttackMode = func(proplist attack_mode)
{
// Called by editor delegate when attack mdoe is changed.
// Called by editor delegate when attack mode is changed.
// For now, attack mode parameter delegates are not supported. Just set by name.
return this.control->SetAttackMode(this.Target, attack_mode.Identifier);
},
SetAttackPath = func(array attack_path)
{
// Called by editor delegate when attack path is changed.
return this.control->SetAttackPath(this.Target, attack_path);
},
EditorProps = {
guard_range = { Name = "$GuardRange$", Type = "rect", Storage = "proplist", Color = 0xff00, Relative = false },
ignore_allies = { Name = "$IgnoreAllies$", Type = "bool" },
max_aggro_distance = { Name = "$MaxAggroDistance$", Type = "circle", Color = 0x808080 },
active = { Name = "$Active$", EditorHelp = "$ActiveHelp$", Type = "bool", Priority = 50, AsyncGet = "GetActive", Set = "SetActive" },
auto_search_target = { Name = "$AutoSearchTarget$", EditorHelp = "$AutoSearchTargetHelp$", Type = "bool" }
auto_search_target = { Name = "$AutoSearchTarget$", EditorHelp = "$AutoSearchTargetHelp$", Type = "bool" },
attack_path = { Name = "$AttackPath$", EditorHelp = "$AttackPathHelp$", Type = "enum", Set = "SetAttackPath", Options = [
{ Name="$None$" },
{ Name="$AttackPath$", Type=C4V_Array, Value = [{X = 0, Y = 0}], Delegate =
{ Name="$AttackPath$", EditorHelp="$AttackPathHelp$", Type="polyline", StartFromObject=true, DrawArrows=true, Color=0xdf0000, Relative=false }
}
] }
},
// Save this effect and the AI for scenarios.
SaveScen = func(proplist props)
@ -308,6 +331,8 @@ local FxAI = new Effect
props->AddCall("AI", this.control, "SetActive", this.Target, false);
if (this.attack_mode.Identifier != "Default")
props->AddCall("AI", this.control, "SetAttackMode", this.Target, Format("%v", this.attack_mode.Identifier));
if (this.attack_path)
props->AddCall("AI", this.control, "SetAttackPath", this.Target, this.attack_path);
if (this.home_x != this.Target->GetX() || this.home_y != this.Target->GetY() || this.home_dir != this.Target->GetDir())
props->AddCall("AI", this.control, "SetHome", this.Target, this.home_x, this.home_y, GetConstantNameByValueSafe(this.home_dir, "DIR_"));
props->AddCall("AI", this.control, "SetGuardRange", this.Target, this.guard_range.x, this.guard_range.y, this.guard_range.wdt, this.guard_range.hgt);
@ -344,6 +369,7 @@ public func Execute(effect fx, int time)
// Find something to fight with.
if (!fx.weapon)
{
fx.can_attack_structures = false;
this->CancelAiming(fx);
if (!this->ExecuteArm(fx))
return this->ExecuteIdle(fx);
@ -518,6 +544,7 @@ public func FindInventoryWeapon(effect fx)
// Throwing weapons.
if ((fx.weapon = fx.Target->FindContents(Firestone)) || (fx.weapon = fx.Target->FindContents(Rock)) || (fx.weapon = fx.Target->FindContents(Lantern)))
{
fx.can_attack_structures = fx.weapon->~HasExplosionOnImpact();
fx.strategy = this.ExecuteThrow;
return true;
}
@ -542,6 +569,7 @@ private func FindInventoryWeaponGrenadeLauncher(effect fx)
fx.aim_wait = 85;
fx.ammo_check = this.HasBombs;
fx.ranged = true;
fx.can_attack_structures = true;
return true;
}
else
@ -579,6 +607,8 @@ private func FindInventoryWeaponBow(effect fx)
fx.aim_wait = 0;
fx.ammo_check = this.HasArrows;
fx.ranged = true;
var arrow = fx.weapon->Contents(0) ?? FindObject(Find_Container(fx.Target), Find_Func("IsArrow"));
fx.can_attack_structures = arrow && arrow->~IsExplosive();
return true;
}
else

View File

@ -22,3 +22,6 @@ NewHomeDirHelp=In welche Richtung der KI-Clonk schauen soll, wenn kein Gegner in
Unchanged=Ungeändert
Left=Links
Right=Rechts
AttackPath=Angriffspfad
AttackPathHelp=Pfad, entlang dessen sich der KI-Gegner berwegt. Befindet sich ein Gebaeude auf einem Eckpunkt des Pfades, greift der Clonk das Gebaeude an.
None=Keiner

View File

@ -22,3 +22,6 @@ NewHomeDirHelp=In which direction the AI clonk will look if no enemy is nearby.
Unchanged=Unchanged
Left=Left
Right=Right
AttackPath=Attack path
AttackPathHelp=Path along which the AI clonk moves. If a vertex of the attack path is placed on a structure (e.g. a gate), the clonk will attack the structure.
None=None

View File

@ -48,6 +48,17 @@ public func SetAutoActivate(bool new_auto_activate) { auto_activate = new_auto_a
public func Initialize()
{
// Default attack path
var tx=0, ty=0;
if (GetY() < 100 && LandscapeHeight() > 400)
{
ty = 100;
}
else
{
if (GetX() < LandscapeWidth()/2) tx = Min(100, LandscapeWidth()-GetX()-8); else tx = Max(-100, -GetX()+8);
}
attack_path = [{X=tx, Y=ty}];
spawned_enemies = [];
// Make sure there's an enemy script player
if (!GetType(g_enemyspawn_player))
@ -325,14 +336,28 @@ public func SpawnClonk(array pos, proplist clonk_data, proplist enemy_def, array
// AI
AI->AddAI(clonk);
AI->SetMaxAggroDistance(clonk, Max(LandscapeWidth(), LandscapeHeight()));
var last_pos = clonk_attack_path[-1];
AI->SetHome(clonk, last_pos.X, last_pos.Y, Random(2));
var guard_range = clonk_data.GuardRange ?? { x=last_pos.X-300, y=last_pos.Y-150, wdt=600, hgt=300 };
var guard_range = clonk_data.GuardRange;
if (!guard_range)
{
// Automatic guard range around attack path
var guard_min_x = spawner->GetX();
var guard_min_y = spawner->GetY();
var guard_max_x = guard_min_x, guard_max_y = guard_min_y;
for (var attack_pos in clonk_attack_path)
{
guard_min_x = Min(guard_min_x, attack_pos.X);
guard_min_y = Min(guard_min_y, attack_pos.Y);
guard_max_x = Max(guard_max_x, attack_pos.X);
guard_max_y = Max(guard_max_y, attack_pos.Y);
}
guard_range = { x=guard_min_x-300, y=guard_min_y-150, wdt=guard_max_x-guard_min_x+600, hgt=guard_max_y-guard_min_y+300 };
}
AI->SetGuardRange(clonk, guard_range.x,guard_range.y,guard_range.wdt,guard_range.hgt);
if (clonk_data.AttackMode)
{
AI->SetAttackMode(clonk, clonk_data.AttackMode.Identifier);
}
AI->SetAttackPath(clonk, clonk_attack_path);
// Return clonk to be added to spawned enemy list
return clonk;
}
@ -401,7 +426,7 @@ public func Definition(def)
] };
def.EditorProps.spawn_delay = { Name="$SpawnDelay$", EditorHelp="$SpawnDelayHelp$", Type="int", Min=0, Set="SetSpawnDelay", Save="SpawnDelay" };
def.EditorProps.spawn_interval = { Name="$SpawnInterval$", EditorHelp="$SpawnIntervalHelp$", Type="int", Min=0, Set="SetSpawnInterval", Save="SpawnInterval" };
//def.EditorProps.attack_path
def.EditorProps.attack_path = { Name="$AttackPath$", EditorHelp="$AttackPathHelp$", Type="polyline", StartFromObject=true, DrawArrows=true, Color=0xdf0000, Relative=true, Save="AttackPath" }; // always saved
def.EditorProps.auto_activate = { Name="$AutoActivate$", EditorHelp="$AutoActivateHelp$", Type="bool", Set="SetAutoActivate", Save="AutoActivate" };
AddEnemyDef("Clonk", { SpawnType=Clonk, SpawnFunction=def.SpawnClonk }, def->GetAIClonkDefaultPropValues(), def->GetAIClonkEditorProps() );
}

View File

@ -46,3 +46,5 @@ Speed=Geschwindigkeit
SpeedHelp=Geschwindigkeit der Clonks in Prozent der Standardgeschwindigkeit
Energy=Energie
EnergyHelp=Lebenspunkte
AttackPath=Angriffspfad
AttackPathHelp=Pfad, entlang dessen sich die KI-Gegner bewegen. Flugrichtung fuer Raketen und Laufrichtung fuer Clonks. Flugrichtungen werden automatisch um den Spawn-Offset verschoben. Befindet sich ein Gebaeude auf einem Eckpunkt des Pfades, greifen Clonks das Gebaeude an.

View File

@ -46,3 +46,5 @@ Speed=Speed
SpeedHelp=Movement speed of the clonk in percent of the default.
Energy=Energy
EnergyHelp=Life points of the spwned clonk.
AttackPath=Attack path
AttackPathHelp=Path along which AI clonks move. Flight path for rockets and walk path for clonks. Complete flight is offset by spawn position if a spawn range is given. If a vertex of the attack path is placed on a structure (e.g. a gate), clonks will attack the structure.