forked from Mirrors/openclonk
defense enemy: add bomber plane
This is a first attempt at an AI flying an airplane and is still very basic.install-platforms
parent
0bac4579ef
commit
e7354ac4b1
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 --*/
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ EnemyRocketeer=Raketenreiter
|
|||
EnemyBomber=Bomber
|
||||
EnemyAirshipPilot=Pilot
|
||||
EnemyAirshipCrew=Luftmatrose
|
||||
EnemyBomberPlane=Bombenflugzeug
|
||||
|
||||
MsgWave=Welle %d: %s| |
|
||||
WaveBreak=Pause
|
|
@ -9,6 +9,7 @@ EnemyRocketeer=Rocketeer
|
|||
EnemyBomber=Bomber
|
||||
EnemyAirshipPilot=Pilot
|
||||
EnemyAirshipCrew=Airsailor
|
||||
EnemyBomberPlane=Bomber plane
|
||||
|
||||
MsgWave=Wave %d: %s| |
|
||||
WaveBreak=Break
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue