Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ShipAI weapons targeting #2013

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 116 additions & 52 deletions src/ai/ai.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -500,25 +500,133 @@ void ShipAI::runOrders()
}
}

static glm::vec2 calculateInterceptPosition(glm::vec2 target_position, glm::vec2 target_velocity, float interceptor_speed)
{
// Calculate the position vector of the point of interception.
// If interception is not possible, return the target position.
const float target_distance = glm::length(target_position);
const float target_speed = glm::length(target_velocity);
const float a = target_speed*target_speed - interceptor_speed*interceptor_speed;
const float b = 2.0f * glm::dot(target_position, target_velocity);
const float c = target_distance * target_distance;
const float det = b*b - 4.0f*a*c;
float intercept_time;
if (det > 0.0f)
intercept_time = (-b - glm::sqrt(det)) / 2.0f / a;
else
intercept_time = 0.0f;
return target_position + target_velocity*intercept_time;
}

void ShipAI::runAttack(P<SpaceObject> target)
{
float attack_distance = 4000.0;
const glm::vec2 target_position = target->getPosition() - owner->getPosition();
const glm::vec2 target_velocity = target->getVelocity();
const float target_distance = glm::length(target_position);
const float target_heading = vec2ToAngle(target_position);

float attack_distance = 4000.0f;
if (has_missiles && best_missile_type == MW_HVLI)
attack_distance = 2500.0;
attack_distance = 2500.0f;
if (has_beams)
attack_distance = beam_weapon_range * 0.7f;

auto position_diff = target->getPosition() - owner->getPosition();
float distance = glm::length(position_diff);
float attack_distance_tolerance;
if (owner->turn_speed > 0.0f && owner->impulse_max_speed > 0.0f)
attack_distance_tolerance = owner->impulse_max_speed / glm::radians(owner->turn_speed); // Match ship turn radius
else
attack_distance_tolerance = 1000.0f; // Null and divide-by-zero error protection

float weapon_angle;
if ((weapon_direction == EWeaponDirection::Side && angleDifference(target_heading, owner->getRotation()) >= 0.0f) || weapon_direction == EWeaponDirection::Left)
weapon_angle = -90.0f;
else if ((weapon_direction == EWeaponDirection::Side && angleDifference(target_heading, owner->getRotation()) < 0.0f) || weapon_direction == EWeaponDirection::Right)
weapon_angle = 90.0f;
else
weapon_angle = 0.0f;

// WARNING:
// Special logic to detect when we should aim specifically for HVLI.
// Not robust to changes in HVLI missile!

// HVLI angle tolerance of +-30deg means that no matter the placement of
// the weapon tube, one of the cardinal directions will always be inside
// the tolerance arc.
const float hvli_tolerance = 30.0f;
const float hvli_min_distance = 1000.0f; // Min distance for full damage
bool aim_for_hvli = false;
int hvli_tube_index;
float hvli_speed;
for (int n=0; n < owner->weapon_tube_count && aim_for_hvli == false; n++)
{
WeaponTube &tube = owner->weapon_tube[n];
const float tube_angle = owner->getRotation() + tube.getDirection();
if (
tube.getLoadType() == MW_HVLI &&
(
(
tube.isLoaded() &&
std::abs(angleDifference(target_heading, tube_angle)) < hvli_tolerance &&
target_distance < attack_distance + attack_distance_tolerance &&
target_distance > hvli_min_distance
) ||
tube.isFiring()
)
)
{
aim_for_hvli = true;
hvli_tube_index = n;
hvli_speed = MissileWeaponData::getDataFor(MW_HVLI).speed / MissileWeaponData::convertSizeToCategoryModifier(owner->weapon_tube[hvli_tube_index].getSize());
weapon_angle = tube.getDirection();
}
}

// Maneuver orders
if (owner->getOrder() == AI_StandGround)
{
// Aim weapons at target
if (aim_for_hvli == true)
owner->target_rotation = vec2ToAngle(calculateInterceptPosition(target_position, target_velocity, hvli_speed)) - weapon_angle;
else
owner->target_rotation = target_heading - weapon_angle;
}
else if (std::abs(target_distance - attack_distance) < attack_distance_tolerance || aim_for_hvli == true)
{
// Aim weapons at target
if (aim_for_hvli == true)
owner->target_rotation = vec2ToAngle(calculateInterceptPosition(target_position, target_velocity, hvli_speed)) - weapon_angle;
else
owner->target_rotation = target_heading - weapon_angle;

// Maintain distance from target
const float normalized_target_distance = (target_distance - attack_distance) / attack_distance_tolerance;
if (weapon_direction == EWeaponDirection::Front)
owner->impulse_request = normalized_target_distance;
else if (weapon_direction == EWeaponDirection::Rear)
owner->impulse_request = -normalized_target_distance;
else
owner->impulse_request = 1.0f;
}
else
{
// Move to attack position
// For side firing, fly into an orbit around the target at attack
// distance, while pointing weapons at the target. Otherwise, plot an
// intercept course.
if (weapon_direction == EWeaponDirection::Side || weapon_direction == EWeaponDirection::Left || weapon_direction == EWeaponDirection::Right)
flyTowards(target->getPosition() - attack_distance*target_position/target_distance + vec2FromAngle(target_heading - weapon_angle)*attack_distance_tolerance, 0.0f);
else
flyTowards(calculateInterceptPosition(target->getPosition(), target_velocity, owner->impulse_max_speed), attack_distance);
}

// missile attack
if (distance < 4500 && has_missiles)
// Fire missiles if there is a valid firing solution
if (target_distance < 4500.0f && has_missiles)
{
for(int n=0; n<owner->weapon_tube_count; n++)
{
if (owner->weapon_tube[n].isLoaded() && missile_fire_delay <= 0.0f)
{
float target_angle = calculateFiringSolution(target, n);
const float target_angle = calculateFiringSolution(target, n);
if (target_angle != std::numeric_limits<float>::infinity())
{
owner->weapon_tube[n].fire(target_angle);
Expand All @@ -527,27 +635,6 @@ void ShipAI::runAttack(P<SpaceObject> target)
}
}
}

if (owner->getOrder() == AI_StandGround)
{
owner->target_rotation = vec2ToAngle(position_diff);
}else{
if (weapon_direction == EWeaponDirection::Side || weapon_direction == EWeaponDirection::Left || weapon_direction == EWeaponDirection::Right)
{
//We have side beams, find out where we want to attack from.
auto target_position = target->getPosition();
auto diff = target_position - owner->getPosition();
float angle = vec2ToAngle(diff);
if ((weapon_direction == EWeaponDirection::Side && angleDifference(angle, owner->getRotation()) > 0) || weapon_direction == EWeaponDirection::Left)
angle += 160;
else
angle -= 160;
target_position += vec2FromAngle(angle) * (attack_distance + target->getRadius());
flyTowards(target_position, 0);
}else{
flyTowards(target->getPosition(), attack_distance);
}
}
}

void ShipAI::flyTowards(glm::vec2 target, float keep_distance)
Expand Down Expand Up @@ -768,28 +855,6 @@ float ShipAI::calculateFiringSolution(P<SpaceObject> target, int tube_index)
}
}

if (type == MW_HVLI) //Custom HVLI targeting for AI, as the calculate firing solution
{
const MissileWeaponData& data = MissileWeaponData::getDataFor(type);

auto target_position = target->getPosition();
float target_angle = vec2ToAngle(target_position - owner->getPosition());
float fire_angle = owner->getRotation() + owner->weapon_tube[tube_index].getDirection();

//HVLI missiles do not home or turn. So use a different targeting mechanism.
float angle_diff = angleDifference(target_angle, fire_angle);

//Target is moving. Estimate where he will be when the missile hits.
float fly_time = target_distance / data.speed;
target_position += target->getVelocity() * fly_time;

//If our "error" of hitting is less then double the radius of the target, fire.
if (std::abs(angle_diff) < 80.0f && target_distance * glm::degrees(tanf(fabs(angle_diff))) < target->getRadius() * 2.0f)
return fire_angle;

return std::numeric_limits<float>::infinity();
}

if (type == MW_Nuke || type == MW_EMP)
{
auto target_position = target->getPosition();
Expand Down Expand Up @@ -849,5 +914,4 @@ P<SpaceObject> ShipAI::findBestMissileRestockTarget(glm::vec2 position, float ra
}
}
return target;
}

}
2 changes: 1 addition & 1 deletion src/ai/fighterAI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ void FighterAI::runAttack(P<SpaceObject> target)
{
if (owner->weapon_tube[n].isLoaded() && missile_fire_delay <= 0.0f)
{
float target_angle = calculateFiringSolution(target, owner->weapon_tube[n].getLoadType());
float target_angle = calculateFiringSolution(target, n);
if (target_angle != std::numeric_limits<float>::infinity())
{
owner->weapon_tube[n].fire(target_angle);
Expand Down
134 changes: 82 additions & 52 deletions src/spaceObjects/spaceshipParts/weaponTube.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -327,81 +327,111 @@ static float calculateTurnAngle(glm::vec2 aim_position, float turn_direction, fl

float WeaponTube::calculateFiringSolution(P<SpaceObject> target)
{
const MissileWeaponData& missile = MissileWeaponData::getDataFor(type_loaded);
float missile_angle;
if (!target || missile.turnrate == 0.0f)
if (!target)
{
missile_angle = std::numeric_limits<float>::infinity();
}
else
{
const MissileWeaponData& missile = MissileWeaponData::getDataFor(type_loaded);
const float missile_speed = missile.speed / MissileWeaponData::convertSizeToCategoryModifier(size); // Adjust for missile size
const float tube_angle = parent->getRotation() + direction; // Degrees
const float turn_rate = glm::radians(missile.turnrate);
const float turn_radius = missile.speed / turn_rate;

// Get target parameters in the tube centered reference frame:
// X axis pointing in direction of fire
// Y axis pointing to the right of the tube
const glm::vec2 target_position = rotateVec2(target->getPosition() - parent->getPosition(), -tube_angle);
const glm::vec2 target_velocity = rotateVec2(target->getVelocity(), -tube_angle);

const int MAX_ITER = 10;
const float tolerance = 0.1f * target->getRadius();
bool converged = false;
glm::vec2 aim_position = target_position; // Set initial aim point
float turn_direction; // Left: -1, Right: +1, No turn: 0
float turn_angle; // In radians. Value of 0 means no turn.
for (int iterations=0; iterations<MAX_ITER && converged == false; iterations++)
if (missile.turnrate == 0.0f)
{
// Select turn direction and calculate turn angle
// Turn in the direction of the target on condition that the target
// is not inside the turning circle of that side. If it is inside
// the turning circle, turn in the opposite direction.
const float d_left = glm::length(aim_position + glm::vec2(0.0f, turn_radius)); // Distance from left turn center
const float d_right = glm::length(aim_position - glm::vec2(0.0f, turn_radius)); // Distance from right turn center
if (d_left >= turn_radius && (aim_position.y < 0.0f || d_right < turn_radius))
{
turn_direction = -1.0f;
turn_angle = calculateTurnAngle(aim_position, turn_direction, turn_radius);
}
else if (d_right >= turn_radius && (aim_position.y >= 0.0f || d_left < turn_radius))
{
turn_direction = 1.0f;
turn_angle = calculateTurnAngle(aim_position, turn_direction, turn_radius);
}
else
{
turn_direction = 0.0f;
turn_angle = 0.0f;
}

// Calculate missile and target parameters at turn exit
const float exit_time = turn_angle / turn_rate;
const glm::vec2 missile_position_exit = turn_radius * glm::vec2(glm::sin(turn_angle), turn_direction * (1.0f - glm::cos(turn_angle)));
const glm::vec2 missile_velocity = missile.speed * glm::vec2(glm::cos(turn_angle), turn_direction * glm::sin(turn_angle));
const glm::vec2 target_position_exit = glm::vec2(target_position + target_velocity*exit_time);

// Calculate nearest approach
const glm::vec2 relative_position_exit = target_position_exit - missile_position_exit;
// For missiles that cannot turn (e.g. HVLI), check if the missile
// will intercept the target within the specified distance
// tolerance.
const glm::vec2 missile_velocity = missile_speed * glm::vec2(1.0f, 0.0f);
const float tolerance = target->getRadius();
const glm::vec2 relative_velocity = target_velocity - missile_velocity;
const float relative_speed = glm::length(relative_velocity);
float nearest_time; // Time after turn exit when nearest approach occurs
float intercept_time;
if (relative_speed == 0.0f)
nearest_time = 0.0f;
intercept_time = 0.0f;
else
nearest_time = -glm::dot(relative_position_exit, relative_velocity) / relative_speed / relative_speed;
const float nearest_distance = glm::length(relative_position_exit + relative_velocity*nearest_time);
intercept_time = -glm::dot(target_position, relative_velocity) / relative_speed / relative_speed;
const float intercept_error = glm::length(target_position + relative_velocity*intercept_time);

// Check if solution has converged or if we must adjust aim
if (nearest_distance < tolerance && nearest_time >= 0.0f)
converged = true;
if (intercept_error < tolerance && intercept_time >= 0.0f)
missile_angle = tube_angle;
else
aim_position = target_position + target_velocity*(exit_time + nearest_time);
missile_angle = std::numeric_limits<float>::infinity();
}
if (converged == true && turn_angle < float(M_PI))
missile_angle = tube_angle + glm::degrees(turn_direction*turn_angle);
else
missile_angle = std::numeric_limits<float>::infinity();
{
// For missiles that can turn (e.g. homing missiles), calculate the
// turn angle required to intercept the target within the specified
// distance tolerance.
const float turn_rate = glm::radians(missile.turnrate);
const float turn_radius = missile_speed / turn_rate;
const int MAX_ITER = 10;
const float tolerance = 0.1f * target->getRadius();
bool converged = false;
glm::vec2 aim_position = target_position; // Set initial aim point
float turn_direction; // Left: -1, Right: +1, No turn: 0
float turn_angle; // In radians. Value of 0 means no turn.
float intercept_time;
for (int iterations=0; iterations<MAX_ITER && converged == false; iterations++)
{
// Select turn direction and calculate turn angle
// Turn in the direction of the target on condition that the target
// is not inside the turning circle of that side. If it is inside
// the turning circle, turn in the opposite direction.
const float d_left = glm::length(aim_position + glm::vec2(0.0f, turn_radius)); // Distance from left turn center
const float d_right = glm::length(aim_position - glm::vec2(0.0f, turn_radius)); // Distance from right turn center
if (d_left >= turn_radius && (aim_position.y < 0.0f || d_right < turn_radius))
{
turn_direction = -1.0f;
turn_angle = calculateTurnAngle(aim_position, turn_direction, turn_radius);
}
else if (d_right >= turn_radius && (aim_position.y >= 0.0f || d_left < turn_radius))
{
turn_direction = 1.0f;
turn_angle = calculateTurnAngle(aim_position, turn_direction, turn_radius);
}
else
{
turn_direction = 0.0f;
turn_angle = 0.0f;
}

// Calculate missile and target parameters at turn exit
const float exit_time = turn_angle / turn_rate;
const glm::vec2 missile_position_exit = turn_radius * glm::vec2(glm::sin(turn_angle), turn_direction * (1.0f - glm::cos(turn_angle)));
const glm::vec2 missile_velocity = missile_speed * glm::vec2(glm::cos(turn_angle), turn_direction * glm::sin(turn_angle));
const glm::vec2 target_position_exit = glm::vec2(target_position + target_velocity*exit_time);

// Calculate nearest approach
const glm::vec2 relative_position_exit = target_position_exit - missile_position_exit;
const glm::vec2 relative_velocity = target_velocity - missile_velocity;
const float relative_speed = glm::length(relative_velocity);
float nearest_time; // Time after turn exit when nearest approach occurs
if (relative_speed == 0.0f)
nearest_time = 0.0f;
else
nearest_time = -glm::dot(relative_position_exit, relative_velocity) / relative_speed / relative_speed;
const float nearest_distance = glm::length(relative_position_exit + relative_velocity*nearest_time);

// Check if solution has converged or if we must adjust aim
intercept_time = exit_time + nearest_time;
if (nearest_distance < tolerance && intercept_time > exit_time)
converged = true;
else
aim_position = target_position + target_velocity*intercept_time;
}
if (converged == true && turn_angle < float(M_PI) && missile.lifetime > intercept_time)
missile_angle = tube_angle + glm::degrees(turn_direction*turn_angle);
else
missile_angle = std::numeric_limits<float>::infinity();
}
}
return missile_angle;
}
Expand Down
Loading