From 2b4e40e58976e6bbc85e9eb39c55a841eb95e072 Mon Sep 17 00:00:00 2001 From: Maikel de Vries Date: Sat, 20 Jan 2018 22:42:35 +0100 Subject: [PATCH] improve AI for defense rounds (boom attack path finding) --- planet/Defense.ocf/KingOfTheHill.ocs/Script.c | 68 ++++++++++++++----- .../DefenseBoomAttack.ocd/Script.c | 48 +++++++++++-- .../Goals.ocd/Defense.ocd/Script.c | 10 +++ .../Components.ocd/Vehicles.ocd/Script.c | 2 +- .../AI.ocd/Controller.ocd/Script.c | 2 +- 5 files changed, 105 insertions(+), 25 deletions(-) diff --git a/planet/Defense.ocf/KingOfTheHill.ocs/Script.c b/planet/Defense.ocf/KingOfTheHill.ocs/Script.c index bb8688a65..fa6440240 100644 --- a/planet/Defense.ocf/KingOfTheHill.ocs/Script.c +++ b/planet/Defense.ocf/KingOfTheHill.ocs/Script.c @@ -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)} + ]; } diff --git a/planet/Objects.ocd/Goals.ocd/Defense.ocd/DefenseBoomAttack.ocd/Script.c b/planet/Objects.ocd/Goals.ocd/Defense.ocd/DefenseBoomAttack.ocd/Script.c index 12447a3e2..3469a97cb 100644 --- a/planet/Objects.ocd/Goals.ocd/Defense.ocd/DefenseBoomAttack.ocd/Script.c +++ b/planet/Objects.ocd/Goals.ocd/Defense.ocd/DefenseBoomAttack.ocd/Script.c @@ -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()); } }; diff --git a/planet/Objects.ocd/Goals.ocd/Defense.ocd/Script.c b/planet/Objects.ocd/Goals.ocd/Defense.ocd/Script.c index ab860b838..ec33c77bd 100644 --- a/planet/Objects.ocd/Goals.ocd/Defense.ocd/Script.c +++ b/planet/Objects.ocd/Goals.ocd/Defense.ocd/Script.c @@ -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 --*/ diff --git a/planet/Objects.ocd/Helpers.ocd/AI.ocd/Components.ocd/Vehicles.ocd/Script.c b/planet/Objects.ocd/Helpers.ocd/AI.ocd/Components.ocd/Vehicles.ocd/Script.c index 0c040fc5d..85bf7c8dd 100644 --- a/planet/Objects.ocd/Helpers.ocd/AI.ocd/Components.ocd/Vehicles.ocd/Script.c +++ b/planet/Objects.ocd/Helpers.ocd/AI.ocd/Components.ocd/Vehicles.ocd/Script.c @@ -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])); diff --git a/planet/Objects.ocd/Helpers.ocd/AI.ocd/Controller.ocd/Script.c b/planet/Objects.ocd/Helpers.ocd/AI.ocd/Controller.ocd/Script.c index ba0c8d4a8..2d35b5389 100644 --- a/planet/Objects.ocd/Helpers.ocd/AI.ocd/Controller.ocd/Script.c +++ b/planet/Objects.ocd/Helpers.ocd/AI.ocd/Controller.ocd/Script.c @@ -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); }