improve AI for defense rounds (boom attack path finding)

install-platforms
Maikel de Vries 2018-01-20 22:42:35 +01:00
parent 3acd4a22f2
commit 2b4e40e589
5 changed files with 105 additions and 25 deletions

View File

@ -100,7 +100,7 @@ public func GetAttackWave(int nr)
Score = 50,
Enemies = []
};
// Add enemy ground troups: swordsman, archer, spearman, grenadier, bomber.
PushBack(wave.Enemies, new DefenseEnemy.Swordsman {
Amount = BoundBy((nr + 2) / 5, 0, 20),
@ -154,35 +154,69 @@ public func GetAttackWave(int nr)
// The attackers should go for flagpoles, then crewmembers, and then hostile structures.
public func GiveRandomAttackTarget(object attacker)
{
var target = FindObject(Find_Category(C4D_Structure), Find_Func("IsFlagpole"), Find_Hostile(attacker->GetController()), Sort_Random());
var controller = attacker->GetController();
var target = FindObject(Find_ID(Flagpole), Find_Hostile(controller), Sort_Random());
if (target)
return target;
target = FindObject(Find_OCF(OCF_CrewMember), Find_Hostile(attacker->GetController()), Sort_Distance());
target = FindObject(Find_OCF(OCF_CrewMember), Find_Hostile(controller), Sort_Distance());
if (target)
return target;
var target = FindObject(Find_Category(C4D_Structure), Find_Hostile(attacker->GetController()), Sort_Random());
var target = FindObject(Find_Category(C4D_Structure), Find_Hostile(controller), Sort_Random());
if (target)
return target;
return;
}
// Returns what boom attacks should go for while on their respective paths.
public func GiveAttackTargetOnWaypointPath(object boomattack)
{
var controller = boomattack->GetController();
var target = boomattack->FindObject(Find_ID(Flagpole), Find_Hostile(controller), Find_Distance(100), boomattack->Find_PathFree(), Sort_Distance());
if (target)
return target;
target = boomattack->FindObject(Find_OCF(OCF_CrewMember), Find_Hostile(controller), Find_Distance(100), boomattack->Find_PathFree(), Sort_Distance());
if (target)
return target;
var target = boomattack->FindObject(Find_Category(C4D_Structure), Find_Hostile(controller), Find_Distance(100), boomattack->Find_PathFree(), Sort_Distance());
if (target)
return target;
return;
}
// Give some of the boom attacks a certain path to ensure the inside of the hill is attacked.
public func GetBoomAttackWaypoints(object boompack)
{
// Construct three different paths which all pass through the inside of the hill and cover a large area.
// Upper path through the upper entrance and then straight down and finally into the ruby mine.
if (!Random(3))
{
// Choose a path through the rock or on the side of the main flagpole.
if (!Random(2))
return [
{X = 450 + Random(10), Y = 680 + Random(10)},
{X = 440 + Random(10), Y = 700 + Random(10)},
{X = 100 + Random(200), Y = 500 + Random(200)}
];
return [
{X = 70 + Random(30), Y = 460 + Random(10)},
{X = 70 + Random(30), Y = 490 + Random(10)},
{X = 100 + Random(200), Y = 500 + Random(200)}
{X = 360 + Random(40), Y = 80 + Random(280)},
{X = 75 + Random(20), Y = 440 + Random(20)},
{X = 60 + Random(100), Y = 720 + Random(120)},
{X = 395 + Random(5), Y = 845 + Random(5)},
{X = 465 + Random(5), Y = 885 + Random(5)},
{X = 500 + Random(180), Y = 850 + Random(100)}
];
}
return nil;
// Middle path through the upper entrance and then diagonally down into the ruby mine.
if (!Random(2))
return [
{X = 500 + Random(100), Y = 350 + Random(100)},
{X = 200 + Random(50), Y = 400 + Random(20)},
{X = 75 + Random(20), Y = 440 + Random(20)},
{X = 280 + Random(40), Y = 680 + Random(60)},
{X = 395 + Random(5), Y = 845 + Random(5)},
{X = 465 + Random(5), Y = 885 + Random(5)},
{X = 500 + Random(180), Y = 850 + Random(100)}
];
// Lower path throught the lower entrance and then into the ruby mine.
return [
{X = 660 + Random(240), Y = 580 + Random(80)},
{X = 480 + Random(10), Y = 650 + Random(10)},
{X = 455 + Random(5), Y = 680 + Random(10)},
{X = 440 + Random(10), Y = 700 + Random(10)},
{X = 150 + Random(100), Y = 640 + Random(120)},
{X = 395 + Random(5), Y = 845 + Random(5)},
{X = 465 + Random(5), Y = 885 + Random(5)},
{X = 500 + Random(180), Y = 850 + Random(100)}
];
}

View File

@ -49,10 +49,15 @@ local FxFlight = new Effect
{
Construction = func()
{
// Add AI for logging purposes.
this.fx_ai = DefenseAI->AddAI(Target);
this.fx_ai->SetActive(false);
// Get a target.
this.target = GetRandomAttackTarget(Target);
// Get the boom attack waypoints from the scenario or make an array.
this.waypoints = GameCall("GetBoomAttackWaypoints", Target) ?? [];
this.current_waypoint = nil;
this.attack_on_way_point_flight = false;
if (this.target)
{
var dx = this.target->GetX() - Target->GetX();
@ -73,6 +78,7 @@ local FxFlight = new Effect
if (!this.target)
{
this.target = GetRandomAttackTarget(Target);
DefenseAI->LogAI_Info(this.fx_ai, Format("BoomAttack lost target and updated it to %v.", this.target));
if (!this.target && !this.current_waypoint && GetLength(this.waypoints) == 0)
{
Target->DoFireworks(NO_OWNER);
@ -81,9 +87,11 @@ local FxFlight = new Effect
}
// Check if reached current waypoint.
if (this.current_waypoint)
if (Distance(this.current_waypoint.X, this.current_waypoint.Y, Target->GetX(), Target->GetY()) < 8)
this.current_waypoint = nil;
if (this.current_waypoint && Distance(this.current_waypoint.X, this.current_waypoint.Y, Target->GetX(), Target->GetY()) < 8)
{
DefenseAI->LogAI_Info(this.fx_ai, Format("BoomAttack reached waypoint (%d, %d).", this.current_waypoint.X, this.current_waypoint.Y));
this.current_waypoint = nil;
}
// Get relative coordinates to target.
var dx, dy;
@ -103,10 +111,22 @@ local FxFlight = new Effect
// Explode if close enough to target.
if (ObjectDistance(Target, this.target) < 12)
{
Target->DoFireworks(NO_OWNER);
DefenseAI->LogAI_Info(this.fx_ai, Format("BoomAttack is in reach of enemy %v and explodes now.", this.target));
Target->DoFireworks(NO_OWNER);
return FX_Execute_Kill;
}
// Move to a nearby target if path is free and attacking is allowed.
var target_on_path = GetAttackTargetOnWaypointPath();
if (target_on_path && this.current_waypoint)
{
// Give up current and future waypoints and set to new target.
this.current_waypoint = nil;
this.waypoints = [];
this.target = target_on_path;
DefenseAI->LogAI_Info(this.fx_ai, Format("BoomAttack found new target %v on waypoint path.", target_on_path));
}
// Get relative coordinates to target.
if (!this.current_waypoint)
{
@ -127,7 +147,8 @@ local FxFlight = new Effect
if (!PathFree(Target->GetX(), Target->GetY(), way_x, way_y) || !PathFree(this.target->GetX(), this.target->GetY(), way_x, way_y))
continue;
if (!Inside(way_x, 0, LandscapeWidth()) || !Inside(way_y, 0, LandscapeHeight()))
continue;
continue;
DefenseAI->LogAI_Info(this.fx_ai, Format("BoomAttack at (%d, %d) is aiming for %v at (%d, %d) takes a new route through (%d, %d).", Target->GetX(), Target->GetY(), this.target, this.target->GetX(), this.target->GetY(), way_x, way_y));
this.current_waypoint = {X = way_x, Y = way_y};
break;
}
@ -147,7 +168,7 @@ local FxFlight = new Effect
angle_rocket += 360;
// Gradually update the angle.
var angle_delta = angle_rocket - angle_to_target;
var angle_step = BoundBy(Target.FlySpeed / 25, 4, 8);
var angle_step = BoundBy(Target.FlySpeed / 25, 4, 10);
if (Inside(angle_delta, 0, 180) || Inside(angle_delta, -360, -180))
Target->SetR(Target->GetR() - Min(angle_step, Abs(angle_delta)));
else if (Inside(angle_delta, -180, 0) || Inside(angle_delta, 180, 360))
@ -179,6 +200,21 @@ local FxFlight = new Effect
SetTarget = func(object target)
{
this.target = target;
},
SetAttackOnWaypointPath = func(bool on)
{
this.attack_on_way_point_path = on;
},
GetAttackTargetOnWaypointPath = func()
{
var attack_target = GameCall("GiveAttackTargetOnWaypointPath", Target);
if (attack_target && PathFree(Target->GetX(), Target->GetY(), attack_target->GetX(), attack_target->GetY()))
return attack_target;
if (!this.attack_on_way_point_path)
return nil;
return Target->FindObject(Find_Category(C4D_Structure | C4D_Living | C4D_Vehicle), Find_Hostile(Target->GetController()), Find_Distance(100), Target->Find_PathFree(), Sort_Distance());
}
};

View File

@ -177,6 +177,16 @@ local FxTrackObservation = new Effect
public func IsFulfilled() { return is_fulfilled; }
// Can be called by a scenario if some other survival condition has failed.
// For example if some object that needed to be defended has been destroyed.
public func NotifyFailedSurvival()
{
// Relaunch all players so that the goal is correctly fulfilled.
for (var plr in GetPlayers(C4PT_User))
RelaunchPlayer(plr);
return;
}
/*-- Score --*/

View File

@ -358,7 +358,7 @@ public func ExecuteAirplaneCarpetBomber(effect fx)
return false;
// Calculate where the bomb would land and check if it would hit any enemies.
var bomb_flight = fx.vehicle->SimFlight(0, 12);
var bomb_target = FindObject(Find_Hostile(fx.Target->GetController()), Find_Distance(20, bomb_flight[0], bomb_flight[1]));
var bomb_target = FindObject(Find_Hostile(fx.Target->GetController()), Find_Or(Find_AtPoint(bomb_flight[0], bomb_flight[1]), Find_Distance(20, bomb_flight[0], bomb_flight[1])));
if (bomb_target && this->IsAirplaneTarget(fx, bomb_target, nil))
{
this->LogAI_Info(fx, Format("ExecuteAirplaneCarpetBomber for %v at (%d, %d) found bomb target %v at (%d, %d).", fx.vehicle, fx.vehicle->GetX(), fx.vehicle->GetY(), bomb_target, bomb_flight[0], bomb_flight[1]));

View File

@ -314,7 +314,7 @@ public func OnActivateAI(proplist fx_ai)
}
// Callback when the AI is deactivated by a trigger
public func OnDectivateAI(proplist fx_ai)
public func OnDeactivateAI(proplist fx_ai)
{
_inherited(fx_ai);
}