forked from Mirrors/openclonk
Add airship spawn and attack path movement to EnemySpawn
parent
3ebedd0c5f
commit
d2e57dd318
|
@ -95,8 +95,23 @@ public func ExecuteIdle(effect fx)
|
|||
// Persist commands because constant command resets may hinder execution
|
||||
if (fx.Target->GetCommand() && Random(4)) return true;
|
||||
// Follow attack path
|
||||
if (this->ExecuteAttackPath(fx)) return true;
|
||||
// Movement done (for now)
|
||||
fx.Target->SetCommand("None");
|
||||
fx.Target->SetComDir(COMD_Stop);
|
||||
fx.Target->SetDir(fx.home_dir);
|
||||
if (fx.vehicle) fx.vehicle->SetCommand();
|
||||
// Nothing to do.
|
||||
return false;
|
||||
}
|
||||
|
||||
public func ExecuteAttackPath(effect fx)
|
||||
{
|
||||
// Attack path is to follow the commander.
|
||||
if (fx.commander) return true;
|
||||
if (fx.attack_path)
|
||||
{
|
||||
// Follow attack path
|
||||
var next_pt = fx.attack_path[0];
|
||||
// Check for structure to kill on path. Only if the structure is alive or of the clonk can attack structures with the current weapon.
|
||||
var alive_check;
|
||||
|
@ -119,7 +134,19 @@ public func ExecuteIdle(effect fx)
|
|||
{
|
||||
if (!Inside(fx.vehicle->GetX() - fx.home_x, -15, 15) || !Inside(fx.vehicle->GetY() - fx.home_y, -20, 20))
|
||||
{
|
||||
return fx.Target->SetCommand("PushTo", fx.vehicle, fx.home_x, fx.home_y);
|
||||
if (fx.vehicle->IsAirship())
|
||||
{
|
||||
if (!fx.vehicle->GetCommand())
|
||||
{
|
||||
fx.vehicle->SetCommand("MoveTo", nil, fx.home_x, fx.home_y);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default vehicle movement
|
||||
return fx.Target->SetCommand("PushTo", fx.vehicle, fx.home_x, fx.home_y);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -130,23 +157,25 @@ public func ExecuteIdle(effect fx)
|
|||
}
|
||||
}
|
||||
// Next section on path or done?
|
||||
this->AdvanceAttackPath(fx);
|
||||
return false;
|
||||
}
|
||||
|
||||
public func AdvanceAttackPath(effect fx)
|
||||
{
|
||||
// Pick next element in attack path if an attack path is set. Return whether a path remains.
|
||||
if (fx.attack_path)
|
||||
{
|
||||
if (GetLength(fx.attack_path) > 1)
|
||||
{
|
||||
fx.attack_path = fx.attack_path[1:];
|
||||
// Wait one cycle. After that, ExecuteIdle will continue the path.
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
fx.attack_path = nil;
|
||||
}
|
||||
}
|
||||
// Movement done (for now)
|
||||
fx.Target->SetCommand("None");
|
||||
fx.Target->SetComDir(COMD_Stop);
|
||||
fx.Target->SetDir(fx.home_dir);
|
||||
// Nothing to do.
|
||||
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
|
||||
|
||||
/*-- General Vehicle --*/
|
||||
|
||||
|
@ -62,6 +62,8 @@ private func ExecuteCatapult(effect fx)
|
|||
fx.Target->SetCommand("Grab", fx.vehicle);
|
||||
return true;
|
||||
}
|
||||
// No target? Revert to default idle proc
|
||||
if (!fx.target) return false;
|
||||
// Target still in guard range?
|
||||
if (!this->CheckTargetInGuardRange(fx))
|
||||
return false;
|
||||
|
@ -137,39 +139,66 @@ public func ExecuteAirship(effect fx)
|
|||
return true;
|
||||
}
|
||||
if (!fx.Target->GetCommand())
|
||||
{
|
||||
fx.Target->SetCommand("Grab", fx.vehicle);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Move the airship to the target. Check if no command or is making contact, this means a new control needs to be issued.
|
||||
// Move the airship to the target or along path. Check if no command or is making contact, this means a new control needs to be issued.
|
||||
if (!fx.vehicle->GetCommand() || fx.vehicle->GetContact(-1))
|
||||
{
|
||||
// If close enough (also in y-coordinates, must be above target) release the crew.
|
||||
if (fx.vehicle->ObjectDistance(fx.target) < fx.control.AirshipBoardDistance && Inside(fx.vehicle->GetY() - fx.target->GetY(), -fx.control.AirshipBoardDistance / 2, 0))
|
||||
if (!fx.attack_path)
|
||||
{
|
||||
// Unboard the crew and let go of airship.
|
||||
for (var clonk in this->GetCommanderCrew(fx))
|
||||
var tx, ty;
|
||||
if (fx.target)
|
||||
{
|
||||
var clonk_ai = clonk->GetAI();
|
||||
clonk_ai.commander = nil;
|
||||
if (clonk->GetProcedure() == "PUSH")
|
||||
clonk->SetAction("Walk");
|
||||
clonk_ai.target = fx.target;
|
||||
tx = fx.target->GetX();
|
||||
ty = fx.target->GetY();
|
||||
}
|
||||
fx.vehicle = nil;
|
||||
fx.weapon = nil;
|
||||
fx.Target->SetCommand("UnGrab");
|
||||
return true;
|
||||
}
|
||||
// Find a boarding point for the target.
|
||||
var boarding_point = this->GetAirshipBoardingPoint(fx);
|
||||
if (boarding_point)
|
||||
else
|
||||
{
|
||||
tx = fx.home_x;
|
||||
ty = fx.home_y;
|
||||
}
|
||||
if (Distance(fx.vehicle->GetX(), fx.vehicle->GetY(), tx, ty) < fx.control.AirshipBoardDistance
|
||||
&& Inside(fx.vehicle->GetY() - ty, -fx.control.AirshipBoardDistance / 2, 0))
|
||||
{
|
||||
// Unboard the crew and let go of airship.
|
||||
for (var clonk in this->GetCommanderCrew(fx))
|
||||
{
|
||||
var clonk_ai = clonk->GetAI();
|
||||
clonk_ai.commander = nil;
|
||||
if (clonk->GetProcedure() == "PUSH")
|
||||
clonk->SetAction("Walk");
|
||||
clonk_ai.target = fx.target;
|
||||
}
|
||||
fx.vehicle = nil;
|
||||
fx.weapon = nil;
|
||||
fx.Target->SetCommand("UnGrab");
|
||||
return true;
|
||||
}
|
||||
// Find a boarding point for the target.
|
||||
var boarding_point = this->GetAirshipBoardingPoint(fx);
|
||||
if (boarding_point)
|
||||
{
|
||||
fx.vehicle->SetCommand("MoveTo", nil, boarding_point[0], boarding_point[1]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fx.vehicle->SetCommand("MoveTo", nil, boarding_point[0], boarding_point[1]);
|
||||
return true;
|
||||
// Always follow attack path
|
||||
fx.vehicle->SetCommand("MoveTo", nil, fx.home_x, fx.home_y);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
else if (fx.attack_path)
|
||||
{
|
||||
this->ExecuteAttackPath(fx);
|
||||
}
|
||||
// Nothing to do. But return handled because we need to keep pushing.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Finds a location where to board the airship close to the target.
|
||||
|
@ -179,16 +208,25 @@ public func GetAirshipBoardingPoint(effect fx)
|
|||
return nil;
|
||||
var vx = fx.vehicle->GetX();
|
||||
var vy = fx.vehicle->GetY();
|
||||
var tx = fx.target->GetX();
|
||||
var ty = fx.target->GetY();
|
||||
// Look for a new target if the current path is not free and too far away.
|
||||
if (fx.vehicle->ObjectDistance(fx.target) > 250 && !PathFree(vx - 30, vy, tx, ty) && !PathFree(vx + 30, vy, tx, ty))
|
||||
var tx, ty;
|
||||
if (fx.target)
|
||||
{
|
||||
fx.target = this->FindTarget(fx);
|
||||
if (!fx.target)
|
||||
return nil;
|
||||
tx = fx.target->GetX();
|
||||
ty = fx.target->GetY();
|
||||
// Look for a new target if the current path is not free and too far away.
|
||||
if (fx.auto_search_target && fx.vehicle->ObjectDistance(fx.target) > AirshipOccludedTargetMaxDistance && !PathFree(vx - 30, vy, tx, ty) && !PathFree(vx + 30, vy, tx, ty))
|
||||
{
|
||||
fx.target = this->FindTarget(fx);
|
||||
if (!fx.target)
|
||||
return nil;
|
||||
tx = fx.target->GetX();
|
||||
ty = fx.target->GetY();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tx = fx.home_x;
|
||||
ty = fx.home_y;
|
||||
}
|
||||
// Approach from above or the side if possible, so move airship up if below target.
|
||||
if (vy > ty)
|
||||
|
@ -233,6 +271,7 @@ public func PromoteNewAirshipCaptain(effect fx)
|
|||
fx_ai.weapon = fx.vehicle;
|
||||
fx_ai.vehicle = fx.vehicle;
|
||||
fx_ai.strategy = this.ExecuteVehicle;
|
||||
fx_ai.attack_path = fx.attack_path;
|
||||
// Set new commander for remaining crew members.
|
||||
for (var crew in crew_members)
|
||||
if (crew != new_pilot)
|
||||
|
|
|
@ -142,9 +142,9 @@ public func FxIntAirshipMovementTimer(object target, proplist effect, int time)
|
|||
}
|
||||
|
||||
//Rise in water
|
||||
if (GBackLiquid(0,25))
|
||||
//if (GBackLiquid(0,25))
|
||||
//effect.SpeedY = -10;
|
||||
if (GBackLiquid(0,25) && !GBackLiquid(0,24) && effect.SpeedY > 1)
|
||||
//if (GBackLiquid(0,25) && !GBackLiquid(0,24) && effect.SpeedY > 1)
|
||||
//effect.SpeedY = 0;
|
||||
|
||||
// Turn the airship around if needed
|
||||
|
@ -160,14 +160,14 @@ public func FxIntAirshipMovementTimer(object target, proplist effect, int time)
|
|||
return 1;
|
||||
}
|
||||
|
||||
func TurnAirship(int to_dir)
|
||||
func TurnAirship(int to_dir, bool instant)
|
||||
{
|
||||
// Default direction is left
|
||||
var animName = "TurnLeft";
|
||||
if (to_dir == DIR_Right)
|
||||
animName = "TurnRight";
|
||||
|
||||
turnanim = PlayAnimation(animName, 10, Anim_Linear(0, 0, GetAnimationLength(animName), 36, ANIM_Hold));
|
||||
turnanim = PlayAnimation(animName, 10, Anim_Linear(0, GetAnimationLength(animName) * !!instant, GetAnimationLength(animName), 36, ANIM_Hold));
|
||||
|
||||
SetAnimDir(to_dir);
|
||||
|
||||
|
@ -362,11 +362,107 @@ local ActMap = {
|
|||
},
|
||||
};
|
||||
|
||||
func Definition(def)
|
||||
/* Register enemy spawn with catapult */
|
||||
|
||||
func Definition(proplist def)
|
||||
{
|
||||
SetProperty("PictureTransformation",Trans_Mul(Trans_Rotate(-25,1,0,0),Trans_Rotate(40,0,1,0)),def);
|
||||
def.PictureTransformation = Trans_Mul(Trans_Rotate(-25,1,0,0),Trans_Rotate(40,0,1,0));
|
||||
if (def == Airship)
|
||||
{
|
||||
var clonk_editor_props = { Type="enum", ValueKey="Properties", OptionKey="Type", Options=[
|
||||
{ Name="$None$", EditorHelp="$NoPilotHelp$" },
|
||||
{ Name=Clonk->GetName(), EditorHelp="$ClonkPilotHelp$", Value={ Type="Clonk", Properties=EnemySpawn->GetAIClonkDefaultPropValues() }, Delegate=EnemySpawn->GetAIClonkEditorProps() }
|
||||
] };
|
||||
var spawn_editor_props = { Type="proplist", Name=def->GetName(), EditorProps= {
|
||||
Pilot = new clonk_editor_props { Name="$Pilot$", EditorHelp="$PilotHelp$" },
|
||||
FlySpeed = { Name="$FlySpeed$", EditorHelp="$FlySpeedHelp$", Type="int", Min=5, Max=10000 },
|
||||
Crew = { Name="$Crew$", EditorHelp="$CrewHelp$", Type="array", Elements=clonk_editor_props }
|
||||
} };
|
||||
var spawn_default_values = {
|
||||
Pilot = { Type="Clonk", Properties=EnemySpawn->GetAIClonkDefaultPropValues() },
|
||||
FlySpeed = def.FlySpeed,
|
||||
Crew = [ { Type="Clonk", Properties=EnemySpawn->GetAIClonkDefaultPropValues("BowArrow", true) } ],
|
||||
};
|
||||
EnemySpawn->AddEnemyDef("Airship",
|
||||
{ SpawnType=Airship,
|
||||
SpawnFunction=def.SpawnAirship,
|
||||
OffsetAttackPathByPos=true,
|
||||
GetInfoString=def.GetSpawnInfoString },
|
||||
spawn_default_values, spawn_editor_props);
|
||||
}
|
||||
}
|
||||
|
||||
private func SpawnAirship(array pos, proplist enemy_data, proplist enemy_def, array attack_path, object spawner)
|
||||
{
|
||||
// Spawn the boomattack
|
||||
var airship = CreateObjectAbove(Airship, pos[0], pos[1]+15, g_enemyspawn_player);
|
||||
var rval = [airship], n=1;
|
||||
if (!airship) return;
|
||||
airship->TurnAirship(attack_path[0].X > pos[0], true);
|
||||
// Boomattack settings
|
||||
airship.FlySpeed = enemy_data.FlySpeed;
|
||||
// Pilot
|
||||
var clonk, pilot;
|
||||
if (enemy_data.Pilot && enemy_data.Pilot.Type == "Clonk")
|
||||
{
|
||||
// Target the rider AI to the final position of the attack path (in case it gets shot down)
|
||||
airship.pilot = pilot = EnemySpawn->SpawnClonk(pos, enemy_data.Pilot.Properties, enemy_def, attack_path, spawner);
|
||||
if (pilot)
|
||||
{
|
||||
pilot->SetAction("Push", airship);
|
||||
rval[n++] = pilot;
|
||||
// Set attack mode
|
||||
AI->SetVehicle(pilot, airship);
|
||||
}
|
||||
}
|
||||
// Crew
|
||||
if (enemy_data.Crew)
|
||||
{
|
||||
var idx = 0;
|
||||
for (var crew_data in enemy_data.Crew)
|
||||
{
|
||||
if (crew_data && crew_data.Type == "Clonk")
|
||||
{
|
||||
var xpos = pos[0] - 15 + 30 * idx / Max(1, GetLength(enemy_data.Crew)-1);
|
||||
clonk = EnemySpawn->SpawnClonk([xpos, pos[1]], crew_data.Properties, enemy_def, attack_path, spawner);
|
||||
if (clonk)
|
||||
{
|
||||
rval[n++] = clonk;
|
||||
clonk.commander = pilot;
|
||||
var ai = clonk->~GetAI();
|
||||
if (ai) ai.commander = pilot;
|
||||
}
|
||||
}
|
||||
++idx;
|
||||
}
|
||||
}
|
||||
// Return airship and all created enemies
|
||||
return rval;
|
||||
}
|
||||
|
||||
private func GetSpawnInfoString(proplist enemy_data)
|
||||
{
|
||||
var s = "{{Airship}}";
|
||||
if (enemy_data.Pilot && enemy_data.Pilot.Type == "Clonk")
|
||||
{
|
||||
s = Format("%s%s", s, EnemySpawn->GetAIClonkInfoString(enemy_data.Pilot.Properties));
|
||||
}
|
||||
if (enemy_data.Crew)
|
||||
{
|
||||
for (var crew_data in enemy_data.Crew)
|
||||
{
|
||||
if (crew_data && crew_data.Type == "Clonk")
|
||||
{
|
||||
s = Format("%s%s", s, EnemySpawn->GetAIClonkInfoString(crew_data.Properties));
|
||||
}
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
/* Properties */
|
||||
|
||||
local Name = "$Name$";
|
||||
local Description = "$Description$";
|
||||
local Touchable = 2;
|
||||
|
@ -375,3 +471,5 @@ local SolidMaskPlane = 275;
|
|||
local BorderBound = C4D_Border_Sides | C4D_Border_Top | C4D_Border_Bottom;
|
||||
local HitPoints = 30;
|
||||
local Components = {Metal = 4, Wood = 4, Cloth = 2};
|
||||
|
||||
public func IsAirship() { return true; }
|
||||
|
|
|
@ -1,2 +1,11 @@
|
|||
Name=Luftschiff
|
||||
Description=Aerostatisches Reise- und Transportfahrzeug. Warnung: Enthält brennbare Gase. Von Zündquellen fernhalten - nicht rauchen. Vor Beschädigungen schützen. Darf nicht in die Hände von Kindern gelangen.
|
||||
None=Keiner
|
||||
NoPilotHelp=Das Luftschiff steht ohne Pilot in der Luft.
|
||||
ClonkPilotHelp=Ein Clonk steuert das Luftschiff.
|
||||
Pilot=Pilot
|
||||
PilotHelp=Clonktyp, der das Luftschiff steuert.
|
||||
FlySpeed=Fluggeschwindigkeit
|
||||
FlySpeedHelp=Wie schnell das Luftschiff fliegen kann.
|
||||
Crew=Crew
|
||||
CrewHelp=Weitere Clonks auf dem Luftschiff.
|
|
@ -1,2 +1,11 @@
|
|||
Name=Airship
|
||||
Description=Lighter-than-air travel and transport vehicle. Caution: the balloon contains volatile gas. Do not puncture.
|
||||
Description=Lighter-than-air travel and transport vehicle. Caution: the balloon contains volatile gas. Do not puncture.
|
||||
None=None
|
||||
NoPilotHelp=The airship will stand in the air without a pilot.
|
||||
ClonkPilotHelp=A clonk controls the airship.
|
||||
Pilot=Pilot
|
||||
PilotHelp=Typ eof clonk to steer the airship.
|
||||
FlySpeed=Flight speed
|
||||
FlySpeedHelp=How fast the airship can fly.
|
||||
Crew=Crew
|
||||
CrewHelp=Additional clonks to be placed on each airship.
|
Loading…
Reference in New Issue