forked from Mirrors/openclonk
ranged ai: use upper ballistic angle if lower is blocked
parent
706f4455d4
commit
da6ce3d5d6
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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...
|
||||
|
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue