forked from Mirrors/openclonk
ai: add bomber and improve defense ai
parent
2807f36319
commit
caca643c83
|
@ -68,7 +68,7 @@ public func GetAttackWave(int nr)
|
|||
if (nr == 1) return new DefenseWave.Break { Duration = 120 };
|
||||
|
||||
// Attack positions.
|
||||
var pos_land = {X = LandscapeWidth(), Y = 740, Exact = true};
|
||||
var pos_land = {X = LandscapeWidth(), Y = 760, Exact = true};
|
||||
var pos_sky = {X = LandscapeWidth() - 100, Y = 0};
|
||||
var pos_above = {X = 200, Y = 0};
|
||||
|
||||
|
@ -83,27 +83,32 @@ public func GetAttackWave(int nr)
|
|||
Enemies = []
|
||||
};
|
||||
|
||||
// Add enemy ground troups: swordsman, archer or spearman.
|
||||
// Add enemy ground troups: swordsman, archer, spearman, grenadier, bomber.
|
||||
PushBack(wave.Enemies, new DefenseEnemy.Swordsman {
|
||||
Amount = BoundBy((nr + 2) / 4, 0, 20),
|
||||
Amount = BoundBy((nr + 2) / 5, 0, 20),
|
||||
Energy = BoundBy(20 + nr, 30, 100),
|
||||
Position = pos_land
|
||||
});
|
||||
PushBack(wave.Enemies, new DefenseEnemy.Archer {
|
||||
Amount = BoundBy((nr + 1) / 4, 0, 20),
|
||||
Amount = BoundBy((nr + 1) / 5, 0, 20),
|
||||
Energy = BoundBy(10 + nr, 20, 50),
|
||||
Position = pos_land
|
||||
});
|
||||
PushBack(wave.Enemies, new DefenseEnemy.Spearman {
|
||||
Amount = BoundBy(nr / 4, 0, 20),
|
||||
Amount = BoundBy(nr / 5, 0, 20),
|
||||
Energy = BoundBy(10 + nr, 20, 50),
|
||||
Position = pos_land
|
||||
});
|
||||
PushBack(wave.Enemies, new DefenseEnemy.Grenadier {
|
||||
Amount = BoundBy((nr - 1) / 4, 0, 20),
|
||||
Amount = BoundBy((nr - 1) / 5, 0, 20),
|
||||
Energy = BoundBy(25 + nr, 30, 80),
|
||||
Position = pos_land
|
||||
});
|
||||
PushBack(wave.Enemies, new DefenseEnemy.Bomber {
|
||||
Amount = BoundBy((nr - 2) / 5, 0, 20),
|
||||
Energy = BoundBy(10 + nr, 20, 50),
|
||||
Position = pos_land
|
||||
});
|
||||
// Add enemy: boom attack.
|
||||
PushBack(wave.Enemies, new DefenseEnemy.BoomAttack {
|
||||
Amount = BoundBy(nr / 2 + 1, 1, 20),
|
||||
|
|
|
@ -12,19 +12,42 @@
|
|||
local AltTargetDistance = 400; // Use the scenario given target if normal AI target is further away than this distance.
|
||||
|
||||
|
||||
private func FindTarget(effect fx)
|
||||
// Alternative target finding for defense scenarios: partially controlled by the scenario script.
|
||||
public func FindTarget(effect fx)
|
||||
{
|
||||
var target = _inherited(fx, ...);
|
||||
// Focus on defense target if normal target is too far away.
|
||||
if (!target || ObjectDistance(target, fx.Target) > fx.control.AltTargetDistance)
|
||||
target = GetRandomAttackTarget(fx.Target);
|
||||
{
|
||||
if (fx.is_siege)
|
||||
target = GetRandomSiegeTarget(fx.Target);
|
||||
else
|
||||
target = GetRandomAttackTarget(fx.Target);
|
||||
}
|
||||
// If target can't be attacked just take normal target again.
|
||||
if (!this->HasWeaponForTarget(fx, target))
|
||||
target = _inherited(fx, ...);
|
||||
return target;
|
||||
}
|
||||
|
||||
private func FindInventoryWeapon(effect fx)
|
||||
// Move to a target if idle.
|
||||
public func ExecuteIdle(effect fx)
|
||||
{
|
||||
if (!fx.target)
|
||||
fx.target = this->FindEmergencyTarget(fx);
|
||||
if (!Random(5) && fx.target)
|
||||
{
|
||||
var tx = fx.target->GetX();
|
||||
if (Abs(tx - fx.Target->GetX())>30)
|
||||
{
|
||||
fx.Target->SetCommand("MoveTo", nil, BoundBy(fx.Target->GetX(), tx - 30, tx + 30), fx.target->GetY());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public func FindInventoryWeapon(effect fx)
|
||||
{
|
||||
// Alternative behavior when riding the boom attack.
|
||||
if (fx.vehicle && fx.vehicle->GetID() == DefenseBoomAttack)
|
||||
|
@ -37,5 +60,40 @@ private func FindInventoryWeapon(effect fx)
|
|||
fx.ranged = true;
|
||||
return true;
|
||||
}
|
||||
// Make a bomber out of those who carry the powderkeg.
|
||||
if (fx.weapon = fx.Target->FindContents(PowderKeg))
|
||||
{
|
||||
fx.is_siege = true;
|
||||
fx.strategy = this.ExecuteBomber;
|
||||
return true;
|
||||
}
|
||||
return _inherited(fx, ...);
|
||||
}
|
||||
|
||||
|
||||
/*-- Bomber --*/
|
||||
|
||||
public func ExecuteBomber(effect fx)
|
||||
{
|
||||
// Still carrying the bomb?
|
||||
if (fx.weapon->Contained() != fx.Target)
|
||||
{
|
||||
fx.weapon = nil;
|
||||
return false;
|
||||
}
|
||||
// Are we in range?
|
||||
if (fx.Target->ObjectDistance(fx.target) < 16 || Distance(fx.Target->GetX(), fx.Target->GetY(), fx.target->GetX(), fx.target->GetY() + fx.target->GetBottom()) < 16)
|
||||
{
|
||||
// Suicide!
|
||||
fx.weapon->Explode(fx.weapon->GetExplosionStrength());
|
||||
fx.Target->Kill();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not in range. Walk there.
|
||||
if (!fx.Target->GetCommand() || !Random(10))
|
||||
fx.Target->SetCommand("MoveTo", fx.target);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -296,6 +296,17 @@ local Rocketeer = new DefaultEnemy
|
|||
Vehicle = DefenseBoomAttack
|
||||
};
|
||||
|
||||
// An archer riding a boom attack.
|
||||
local Bomber = new DefaultEnemy
|
||||
{
|
||||
Name = "$EnemyBomber$",
|
||||
Inventory = PowderKeg,
|
||||
Energy = 50,
|
||||
Bounty = 10,
|
||||
Color = 0xff55aaff,
|
||||
Skin = CSKIN_Default
|
||||
};
|
||||
|
||||
// Commander of the airship.
|
||||
local AirshipPilot = new DefaultEnemy
|
||||
{
|
||||
|
|
|
@ -6,5 +6,6 @@ EnemyBoomAttack=Rakete
|
|||
EnemyRapidBoomAttack=Rasche Rakete
|
||||
EnemyBallooner=Fallschirmspringer
|
||||
EnemyRocketeer=Raketenreiter
|
||||
EnemyBomber=Bomber
|
||||
EnemyAirshipPilot=Pilot
|
||||
EnemyAirshipCrew=Luftmatrose
|
|
@ -6,5 +6,6 @@ EnemyBoomAttack=Rocket
|
|||
EnemyRapidBoomAttack=Rapid Rocket
|
||||
EnemyBallooner=Parachutist
|
||||
EnemyRocketeer=Rocketeer
|
||||
EnemyBomber=Bomber
|
||||
EnemyAirshipPilot=Pilot
|
||||
EnemyAirshipCrew=Airsailor
|
|
@ -4,11 +4,28 @@ global func GetRandomAttackTarget(object attacker)
|
|||
{
|
||||
// First let the scenario tell what to do by a gamecall.
|
||||
var target = GameCall("GiveRandomAttackTarget", attacker);
|
||||
if (!target)
|
||||
{
|
||||
// Attack structures owned by the enemy of the attacker.
|
||||
var controller = attacker->GetController();
|
||||
var target = FindObject(Find_Category(C4D_Structure), Find_Hostile(controller), Sort_Random());
|
||||
}
|
||||
return target;
|
||||
if (target)
|
||||
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()))
|
||||
if (target && PathFree(attacker->GetX(), attacker->GetY(), target->GetX(), target->GetY()))
|
||||
return target;
|
||||
// Otherwise return random enemy structure.
|
||||
return FindObject(Find_Category(C4D_Structure), Find_Hostile(controller), Sort_Random());
|
||||
}
|
||||
|
||||
global func GetRandomSiegeTarget(object attacker)
|
||||
{
|
||||
// First let the scenario tell what to do by a gamecall.
|
||||
var target = GameCall("GiveRandomSiegeTarget", attacker);
|
||||
if (target)
|
||||
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()))
|
||||
if (target && PathFree(attacker->GetX(), attacker->GetY(), target->GetX(), target->GetY()))
|
||||
return target;
|
||||
// Otherwise return random enemy structure.
|
||||
return FindObject(Find_Category(C4D_Structure), Find_Hostile(controller), Sort_Random());
|
||||
}
|
|
@ -390,7 +390,7 @@ public func Execute(effect fx, int time)
|
|||
this->ExecuteAppearance(fx);
|
||||
// Attack it!
|
||||
if (!this->IsWeaponForTarget(fx))
|
||||
this->LogAI(fx, Format("weapon of type %i is not fit to attack %v.", fx.weapon->GetID(), fx.target));
|
||||
this->LogAI(fx, Format("weapon of type %i is not fit to attack %v (type: %i).", fx.weapon->GetID(), fx.target, fx.target->GetID()));
|
||||
return this->Call(fx.strategy, fx);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue