ranged ai: use upper ballistic angle if lower is blocked

alut-include-path
Maikel de Vries 2017-01-24 21:20:22 +01:00
parent 706f4455d4
commit da6ce3d5d6
4 changed files with 84 additions and 72 deletions

View File

@ -313,17 +313,19 @@ private func ExecuteRanged(effect fx)
ty += this->GetTargetYDir(fx.target, dt);
if (!fx.target->GetContact(-1)) if (!fx.target->GetCategory() & C4D_StaticBack) ty += GetGravity()*dt*dt/200;
// Get shooting angle
// Get shooting angle.
var shooting_angle;
if (fx.ranged_direct)
shooting_angle = Angle(x, y, tx, ty, 10);
else {
if (PathFree(x,y,tx,ty))
shooting_angle = GetBallisticAngle(tx-x, ty-y, fx.projectile_speed, 160);
else
shooting_angle = GetUpperBallisticAngle(tx-x, ty-y, fx.projectile_speed, 160);
{
// For straight shots it is just the angle to the target if the path is free.
if (PathFree(x, y, tx, ty))
shooting_angle = Angle(x, y, tx, ty, 10);
}
if (GetType(shooting_angle) != C4V_Nil)
// For ballistic shots get the angle (path free check is done inside).
// The lower of the two angles is preferentially returned.
else
shooting_angle = this->GetBallisticAngle(x, y, tx, ty, fx.projectile_speed, 160);
if (GetType(shooting_angle) != nil)
{
// No ally on path? Also search for allied animals, just in case.
var ally;
@ -361,28 +363,6 @@ private func ExecuteRanged(effect fx)
return true;
}
// Used when no free path to target (presumably because of the airship gondola floor)
// Returns the 'upper' shooting angle, see GetBallisticAngle etc.
private func GetUpperBallisticAngle(int dx, int dy, int v, int max_angle)
{
// v is in 1/10 pix/frame
// gravity is in 1/100 pix/frame^2
var g = GetGravity();
// correct vertical distance to account for integration error
// engine adds gravity after movement, so targets fly higher than they should
// thus, we aim lower. we don't know the travel time yet, so we assume some 90% of v is horizontal
// (it's ~2px correction for 200px shooting distance)
dy += Abs(dx)*g*10/(v*180);
//Log("Correction: Aiming %d lower!", Abs(dx)*q*10/(v*180));
// q is in 1/10000 (pix/frame)^4
var q = v**4 - g*(g*dx*dx-2*dy*v*v); // dy is negative up
if (q<0) return nil; // out of range
var a = (Angle(0, 0, g * dx, -Sqrt(q) - v * v, 10) + 1800) % 3600 - 1800;
// Check bounds
if(!Inside(a, -10 * max_angle, 10 * max_angle)) return nil;
return a;
}
// Don't move exacty to target's position (throwing check will fail then)
private func ExecuteThrow(effect fx)
{

View File

@ -35,8 +35,10 @@ private func GetTargetYDir(object target, int prec)
// Helper function: Convert target coordinates and projectile out speed to desired shooting angle. Because
// http://en.wikipedia.org/wiki/Trajectory_of_a_projectile says so. No SimFlight checks to check upper
// angle (as that is really easy to evade anyway) just always shoot the lower angle if sight is free.
private func GetBallisticAngle(int dx, int dy, int v, int max_angle)
private func GetBallisticAngle(int x, int y, int tx, int ty, int v, int max_angle)
{
var dx = tx - x;
var dy = ty - y;
// The variable v is in 1/10 pix/frame.
// The variable gravity is in 1/100 pix/frame^2.
var g = GetGravity();
@ -44,13 +46,33 @@ private func GetBallisticAngle(int dx, int dy, int v, int max_angle)
// targets fly higher than they should. Thus, we aim lower. we don't know the travel time yet, so we
// assume some 90% of v is horizontal (it's ~2px correction for 200px shooting distance).
dy += Abs(dx) * g * 10 / (v * 180);
// The variable q is in 1/10000 (pix/frame)^4.
var q = v**4 - g * (g * dx * dx - 2 * dy * v * v); // dy is negative up
// The variable q is in 1/10000 (pix/frame)^4 and dy is negative up.
var q = v**4 - g * (g * dx * dx - 2 * dy * v * v);
// Check if target is out of range.
if (q < 0)
return nil; // Out of range.
var a = (Angle(0, 0, g * dx, Sqrt(q) - v * v, 10) + 1800) % 3600 - 1800;
// Check bounds.
if (!Inside(a, -10 * max_angle, 10 * max_angle))
return nil;
return a;
// Return lower angle if possible.
var lower_angle = (Angle(0, 0, g * dx, Sqrt(q) - v * v, 10) + 1800) % 3600 - 1800;
if (Inside(lower_angle, -10 * max_angle, 10 * max_angle) && this->CheckBallisticPath(x, y, tx, ty, v, lower_angle))
return lower_angle;
// Otherwise return upper angle if that one is possible.
var upper_angle = (Angle(0, 0, g * dx, -Sqrt(q) - v * v, 10) + 1800) % 3600 - 1800;
if (Inside(upper_angle, -10 * max_angle, 10 * max_angle) && this->CheckBallisticPath(x, y, tx, ty, v, upper_angle))
return upper_angle;
// No possible shooting angle.
return nil;
}
// Returns whether the ballistic path is free.
public func CheckBallisticPath(int x, int y, int tx, int ty, int v, int angle)
{
// The projected flight time is now known.
var vx = Sin(angle, v * 10, 10);
var vy = -Cos(angle, v * 10, 10);
var flight_time = 100 * Abs(tx - x) / Max(1, Abs(vx));
// Simulate the flight and see if the flight time corresponds to the expected time.
var flight = this->SimFlight(x, y, vx, vy, nil, nil, flight_time, 100);
//Log("(%d, %d)->(%d, %d) with v = (%d, %d) and angle = %d in t = %d %v", x, y, tx, ty, vx, vy, angle / 10, flight_time, flight);
// Both the projected and the simulated flight times should be the same for a free path.
return -flight[4] == flight_time;
}

View File

@ -62,44 +62,47 @@ private func ExecuteRanged(effect fx)
if (!fx.target->GetContact(-1))
if (!fx.target->GetCategory() & C4D_StaticBack)
ty += GetGravity() * dt * dt / 200;
// Path to target free?
if (PathFree(x, y, tx, ty))
// Get shooting angle.
var shooting_angle;
if (fx.ranged_direct)
{
// Get shooting angle.
var shooting_angle;
if (fx.ranged_direct)
// For straight shots it is just the angle to the target if the path is free.
if (PathFree(x, y, tx, ty))
shooting_angle = Angle(x, y, tx, ty, 10);
else
shooting_angle = this->GetBallisticAngle(tx - x, ty - y, fx.projectile_speed, 160);
if (shooting_angle != nil)
}
// For ballistic shots get the angle (path free check is done inside).
// The lower of the two angles is preferentially returned.
else
shooting_angle = this->GetBallisticAngle(x, y, tx, ty, fx.projectile_speed, 160);
// If we have a valid shooting angle we can proceed.
if (shooting_angle != nil)
{
// No ally on path? Also search for allied animals, just in case.
// Ignore this if requested or if no friendly fire rules is active.
var ally;
if (!fx.ignore_allies || FindObject(Find_ID(Rule_NoFriendlyFire)))
ally = FindObject(Find_OnLine(0, 0, tx - x, ty - y), Find_Exclude(fx.Target), Find_OCF(OCF_Alive), Find_Owner(fx.Target->GetOwner()));
if (ally)
{
// No ally on path? Also search for allied animals, just in case.
// Ignore this if requested or if no friendly fire rules is active.
var ally;
if (!fx.ignore_allies || FindObject(Find_ID(Rule_NoFriendlyFire)))
ally = FindObject(Find_OnLine(0, 0, tx - x, ty - y), Find_Exclude(fx.Target), Find_OCF(OCF_Alive), Find_Owner(fx.Target->GetOwner()));
if (ally)
{
// Try to jump, if not possible just wait.
if (this->ExecuteJump())
return true;
}
else
{
// Aim / shoot there.
x = Sin(shooting_angle, 1000, 10);
y = -Cos(shooting_angle, 1000, 10);
fx.aim_weapon->ControlUseHolding(fx.Target, x, y);
if (fx.Target->IsAiming() && fx.time >= fx.aim_time + fx.aim_wait)
{
fx.aim_weapon->ControlUseStop(fx.Target, x, y);
// Assign post-aim status to allow slower shoot animations to pass.
fx.post_aim_weapon = fx.aim_weapon;
fx.post_aim_weapon_time = FrameCounter();
fx.aim_weapon = nil;
}
// Try to jump, if not possible just wait.
if (this->ExecuteJump())
return true;
}
else
{
// Aim / shoot there.
x = Sin(shooting_angle, 1000, 10);
y = -Cos(shooting_angle, 1000, 10);
fx.aim_weapon->ControlUseHolding(fx.Target, x, y);
if (fx.Target->IsAiming() && fx.time >= fx.aim_time + fx.aim_wait)
{
fx.aim_weapon->ControlUseStop(fx.Target, x, y);
// Assign post-aim status to allow slower shoot animations to pass.
fx.post_aim_weapon = fx.aim_weapon;
fx.post_aim_weapon_time = FrameCounter();
fx.aim_weapon = nil;
}
return true;
}
}
// Path not free or out of range. Just wait for enemy to come...

View File

@ -13,7 +13,7 @@ public func FindTarget(effect fx)
if (fx.Target->GetOwner() == NO_OWNER)
hostile_criteria = Find_Not(Find_Owner(fx.Target->GetOwner()));
for (var target in fx.Target->FindObjects(Find_InRect(fx.guard_range.x - fx.Target->GetX(), fx.guard_range.y - fx.Target->GetY(), fx.guard_range.wdt, fx.guard_range.hgt), Find_OCF(OCF_CrewMember), hostile_criteria, Find_NoContainer(), Sort_Random()))
if (PathFree(fx.Target->GetX(), fx.Target->GetY(), target->GetX(), target->GetY()))
if (fx.ranged || PathFree(fx.Target->GetX(), fx.Target->GetY(), target->GetX(), target->GetY()))
return target;
// Nothing found.
return;
@ -45,4 +45,11 @@ public func CheckTargetInGuardRange(effect fx)
}
}
return true;
}
// Returns whether a weapon can be used for a certain target.
public func IsWeaponForTarget(effect fx, object weapon, object target)
{
// TODO: Implement shooting at different targets, e.g. alive vs. structure.
return false;
}