defense enemy: add bomber plane

This is a first attempt at an AI flying an airplane and is still very basic.
install-platforms
Maikel de Vries 2018-01-19 14:37:00 +01:00
parent 0bac4579ef
commit e7354ac4b1
10 changed files with 145 additions and 21 deletions

View File

@ -27,6 +27,8 @@ public func FindTarget(effect fx)
// If target can't be attacked just take normal target again.
if (!this->HasWeaponForTarget(fx, target))
target = _inherited(fx, ...);
if (!target)
this->LogAI_Info(fx, Format("No target found by DefenseAI for %v.", fx.Target));
return target;
}

View File

@ -11,8 +11,10 @@
Secondary properties are:
* Energy - Alternative amount of hitpoints of the clonk.
* Skin - Alternative skin for the clonk.
* Inventory - Items for the clonk, either ID or list of IDs.
* Vehicle - Vehicle the enemy is controlling or riding.
* VehicleHP - Hitpoints for the enemy vehicle, can be made stronger.
* VehicleInventory - Items for the vehicle, either ID or list of IDs.
* IsCrew - Is part of a crew of the AI that is controlling the vehicle.
This also defines standard waves which attack the player or its base. The wave is a list of properties, the main ones are:
@ -92,9 +94,9 @@ private func LaunchEnemyAt(proplist prop_enemy, int wave_nr, int enemy_plr, prop
enemy.Bounty = prop_enemy.Bounty;
enemy.Score = prop_enemy.Score;
// Vehicles for enemies that are not a crew of a vehicle.
var vehicle;
if (prop_enemy.Vehicle && !prop_enemy.IsCrew)
{
var vehicle;
if (prop_enemy.Vehicle == Balloon)
{
var balloon = enemy->CreateContents(Balloon);
@ -107,6 +109,16 @@ private func LaunchEnemyAt(proplist prop_enemy, int wave_nr, int enemy_plr, prop
// Add boom attack to enemy list.
GameCallEx("OnEnemyCreation", vehicle, wave_nr);
}
else if (prop_enemy.Vehicle == Airplane)
{
vehicle = CreateObjectAbove(prop_enemy.Vehicle, x, y + 10, enemy_plr);
enemy->Enter(vehicle);
vehicle->PlaneMount(enemy);
// Assume the plane is at one landscape side and wants to fly to the opposite side.
if (vehicle->GetX() > LandscapeWidth() / 2)
vehicle->FaceLeft();
vehicle->StartInstantFlight(vehicle->GetR(), 15);
}
else
{
vehicle = CreateObjectAbove(prop_enemy.Vehicle, x, y + 10, enemy_plr);
@ -121,18 +133,30 @@ private func LaunchEnemyAt(proplist prop_enemy, int wave_nr, int enemy_plr, prop
GameCallEx("OnCreationRuleNoFF", vehicle);
}
// Move crew members onto their vehicles.
if (prop_enemy.IsCrew && prop_enemy.Vehicle)
if (prop_enemy.Vehicle && prop_enemy.IsCrew)
{
vehicle = enemy->FindObject(Find_Distance(50), Find_ID(prop_enemy.Vehicle), Sort_Distance());
if (vehicle)
var crew_vehicle = enemy->FindObject(Find_Distance(50), Find_ID(prop_enemy.Vehicle), Sort_Distance());
if (crew_vehicle)
{
enemy.commander = vehicle.pilot;
// Set commander for crew.
enemy.commander = crew_vehicle.pilot;
}
}
// Enemy inventory
// Vehicle inventory.
if (vehicle && prop_enemy.VehicleInventory)
{
for (var inv in ForceToInventoryArray(prop_enemy.VehicleInventory))
{
var inv_obj = vehicle->CreateContents(inv);
// Infinite ammo.
if (inv_obj)
inv_obj->~SetInfiniteStackCount();
}
}
// Enemy inventory.
if (prop_enemy.Inventory)
{
for (var inv in ForceVal2Array(prop_enemy.Inventory))
for (var inv in ForceToInventoryArray(prop_enemy.Inventory))
{
// Action hacking to instantly pick up carry heavy objects.
enemy->SetAction("Jump");
@ -149,6 +173,9 @@ private func LaunchEnemyAt(proplist prop_enemy, int wave_nr, int enemy_plr, prop
DefenseAI->AddAI(enemy);
DefenseAI->SetMaxAggroDistance(enemy, LandscapeWidth());
DefenseAI->SetGuardRange(enemy, 0, 0, LandscapeWidth(), LandscapeHeight());
// Add vehicle to AI.
if (vehicle)
DefenseAI->SetVehicle(enemy, vehicle);
}
return enemy;
}
@ -192,11 +219,25 @@ private func UpdateEnemyPhysicals(object enemy, proplist prop_enemy)
return;
}
private func ForceVal2Array(/*any*/ v)
private func ForceToInventoryArray(/*any*/ list)
{
if (GetType(v) != C4V_Array)
return [v];
return v;
// Convert single ID to array.
if (GetType(list) != C4V_Array)
return [list];
// Check in array if entries of the form [ID, amount] appear and convert them.
for (var i = 0; i < GetLength(list); )
{
var element = list[i];
if (GetType(element) == C4V_Array)
{
for (var j = 0; j < element[1]; j++)
PushBack(list, element[0]);
RemoveArrayValue(list, element);
continue;
}
i++;
}
return list;
}
// Create an arrow which show the direction the enemy is coming from.
@ -360,6 +401,20 @@ local AirshipCrew = new DefaultEnemy
IsCrew = true
};
// A pilot and a bomber plane.
local BomberPlane = new DefaultEnemy
{
Name = "$EnemyBomberPlane$",
Inventory = [],
Energy = 35,
Bounty = 20,
Color = 0xffaaddff,
Skin = CSKIN_Alchemist,
Vehicle = Airplane,
VehicleHP = 50,
VehicleInventory = [[IronBomb, 8], [Dynamite, 4]]
};
/*-- Wave Launching --*/

View File

@ -9,6 +9,7 @@ EnemyRocketeer=Raketenreiter
EnemyBomber=Bomber
EnemyAirshipPilot=Pilot
EnemyAirshipCrew=Luftmatrose
EnemyBomberPlane=Bombenflugzeug
MsgWave=Welle %d: %s| |
WaveBreak=Pause

View File

@ -9,6 +9,7 @@ EnemyRocketeer=Rocketeer
EnemyBomber=Bomber
EnemyAirshipPilot=Pilot
EnemyAirshipCrew=Airsailor
EnemyBomberPlane=Bomber plane
MsgWave=Wave %d: %s| |
WaveBreak=Break

View File

@ -1,5 +1,5 @@
Name=Verteidigung
Description=Survive as many attack waves as possible, each wave and enemy eliminated earns you points. You need to survive %d waves (<c cd7f32>bronze</c>), %d waves (<c c0c0c0>silver</c>) or %d waves (<c ffd700>gold</c>) for the star achievement.
Description=Survive as many attack waves as possible, each wave and eliminated enemy earns you points. You need to survive %d waves (<c cd7f32>bronze</c>), %d waves (<c c0c0c0>silver</c>) or %d waves (<c ffd700>gold</c>) for the star achievement.
PlayerAttackers=Angreifer

View File

@ -1,5 +1,5 @@
Name=Defense
Description=Survive as many attack waves as possible, each wave and enemy eliminated earns you points. You need to survive %d waves (<c cd7f32>bronze</c>), %d waves (<c c0c0c0>silver</c>) or %d waves (<c ffd700>gold</c>) for the star achievement.
Description=Survive as many attack waves as possible, each wave and eliminated enemy earns you points. You need to survive %d waves (<c cd7f32>bronze</c>), %d waves (<c c0c0c0>silver</c>) or %d waves (<c ffd700>gold</c>) for the star achievement.
PlayerAttackers=Attackers

View File

@ -8,7 +8,7 @@ global func GetRandomAttackTarget(object attacker)
return target;
// Attack structures owned by the enemy of the attacker.
var controller = attacker->GetController();
for (var target in FindObjects(Find_Category(C4D_Structure), Find_Hostile(controller), Sort_Distance()))
for (var target in attacker->FindObjects(Find_Category(C4D_Structure), Find_Hostile(controller), Sort_Distance()))
if (target && PathFree(attacker->GetX(), attacker->GetY(), target->GetX(), target->GetY()))
return target;
// Otherwise return random enemy structure.
@ -23,7 +23,7 @@ global func GetRandomSiegeTarget(object attacker)
return target;
// Attack structures owned by the enemy of the attacker.
var controller = attacker->GetController();
for (var target in FindObjects(Find_Category(C4D_Structure), Find_Hostile(controller), Sort_Distance()))
for (var target in attacker->FindObjects(Find_Category(C4D_Structure), Find_Hostile(controller), Sort_Distance()))
if (target && PathFree(attacker->GetX(), attacker->GetY(), target->GetX(), target->GetY()))
return target;
// Otherwise return random enemy structure.

View File

@ -103,5 +103,8 @@ public func IsVehicleForTarget(effect fx, object vehicle, object target)
// Catapult may fire at everything.
if (vehicle->GetID() == Catapult)
return true;
// Airplanes can attack anything a priori.
if (vehicle->GetID() == Airplane)
return true;
return false;
}

View File

@ -10,7 +10,7 @@
// AI Settings.
local AirshipBoardDistance = 100; // How near must an airship be to the target to dispatch its troops.
local AirshipLostDistance = 50; // How far the pilot must be away from an airship for it to find a new pilot.
local AirshipOccludedTargetMaxDistance = 250; // IF a target is further than this and occluded, search for a new target
local AirshipOccludedTargetMaxDistance = 250; // If a target is further than this and occluded, search for a new target.
/*-- Public interface --*/
@ -23,6 +23,7 @@ public func SetVehicle(object clonk, object new_vehicle)
if (!fx_ai)
return false;
fx_ai.vehicle = new_vehicle;
fx_ai.strategy = this.ExecuteVehicle;
return true;
}
@ -42,7 +43,7 @@ public func OnAddAI(proplist fx_ai)
/*-- General Vehicle --*/
private func ExecuteVehicle(effect fx)
public func ExecuteVehicle(effect fx)
{
// Do we have a vehicle?
if (!fx.vehicle)
@ -56,12 +57,16 @@ private func ExecuteVehicle(effect fx)
if (fx.vehicle->GetID() == Airship)
return this->ExecuteAirship(fx);
// Steer the airplane.
if (fx.vehicle->GetID() == Airplane)
return this->ExecuteAirplane(fx);
// Don't know how to use this vehicle, so reset it.
fx.vehicle = nil;
return false;
}
private func CheckVehicleAmmo(effect fx, object vehicle)
public func CheckVehicleAmmo(effect fx, object vehicle)
{
// Check for specific vehicle.
if (vehicle->GetID() == Catapult)
@ -69,6 +74,11 @@ private func CheckVehicleAmmo(effect fx, object vehicle)
if (this->CheckCatapultAmmo(fx, vehicle))
return true;
}
if (vehicle->GetID() == Airplane)
{
if (this->CheckAirplaneAmmo(fx, vehicle))
return true;
}
// These vehicles don't need ammo.
if (vehicle->GetID() == Airship)
return true;
@ -81,7 +91,7 @@ private func CheckVehicleAmmo(effect fx, object vehicle)
/*-- Catapult --*/
private func ExecuteCatapult(effect fx)
public func ExecuteCatapult(effect fx)
{
// Still pushing it?
if (fx.Target->GetProcedure() != "PUSH" || fx.Target->GetActionTarget() != fx.vehicle)
@ -143,7 +153,7 @@ private func ExecuteCatapult(effect fx)
return true;
}
private func CheckCatapultAmmo(effect fx, object vehicle)
public func CheckCatapultAmmo(effect fx, object vehicle)
{
// Must have ammo in the catapult or in the clonk (or be respawning ammo)
return vehicle->ContentsCount() > 0 || fx.Target->ContentsCount() > 0 || fx.has_ammo_respawn;
@ -317,3 +327,54 @@ public func GetCommanderCrew(effect fx)
PushBack(crew, clonk);
return crew;
}
/*-- Airplane --*/
public func ExecuteAirplane(effect fx)
{
if (fx.Target != fx.vehicle->GetPilot())
{
// Make pilot if possible.
if (!fx.vehicle->GetPilot())
{
fx.vehicle->PlaneMount(fx.Target);
return true;
}
fx.vehicle = nil;
return true;
}
// Assume for now that the AI wants to bomb the enemy.
this->ExecuteAirplaneCarpetBomber(fx);
return true;
}
public func ExecuteAirplaneCarpetBomber(effect fx)
{
if (!fx.vehicle->GetPilot())
return false;
// Drop iron bombs at enemies.
if (fx.vehicle->GetBombAmount() <= 0)
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]));
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]));
fx.vehicle->CancelBomb(fx.Target);
}
return true;
}
public func CheckAirplaneAmmo(effect fx, object vehicle)
{
// Must have ammo in the airplane itself.
return vehicle->GetBombAmount() > 0;
}
public func IsAirplaneTarget(effect fx, object target, object weapon)
{
var target_cat = target->GetCategory();
return target_cat & (C4D_Structure | C4D_Vehicle | C4D_Living);
}

View File

@ -176,6 +176,7 @@ public func ExecuteThrow(effect fx)
public func ExecuteArm(effect fx)
{
// Find shield.
fx.shield = fx.Target->FindContents(Shield);
// Vehicle control overrides all other weapons
@ -192,7 +193,7 @@ public func ExecuteArm(effect fx)
}
else
{
this->LogAI_Info(fx, "Vehicle ammo is not ok. Weapon is %v, vehicle is %v", fx.weapon, fx.vehicle);
this->LogAI_Info(fx, Format("Vehicle ammo is not ok. Weapon is %v, vehicle is %v", fx.weapon, fx.vehicle));
fx.weapon = nil;
}
}