Skip to content

Commit

Permalink
feat: Ability to limit turret firing arc (#7094)
Browse files Browse the repository at this point in the history
* Introduce a capability to limit the rotation of a turret.

Co-authored-by: Hurleveur <94366726+Hurleveur@users.noreply.github.com>
Co-authored-by: oo13 <ooyooxei+gh@gmail.com>
Co-authored-by: OOTA, Masato <ooyooxei+gh@gmail.com>
Co-authored-by: Rising Leaf <85687254+RisingLeaf@users.noreply.github.com>
Co-authored-by: tehhowch <tehhowch@users.noreply.github.com>
Co-authored-by: tibetiroka <68112292+tibetiroka@users.noreply.github.com>
Co-authored-by: TomGoodIdea <108272452+TomGoodIdea@users.noreply.github.com>
  • Loading branch information
7 people committed May 11, 2024
1 parent 7945ccc commit 94ec4ac
Show file tree
Hide file tree
Showing 13 changed files with 326 additions and 52 deletions.
3 changes: 3 additions & 0 deletions data/_ui/tooltips.txt
Original file line number Diff line number Diff line change
Expand Up @@ -990,6 +990,9 @@ tip "shots / second:"
tip "turret turn rate:"
`Degrees this turret can turn per second.`

tip "arc:"
`The angular range of protection provided by this weapon, in degrees.`

tip "homing:"
`This projectile's ability to track its target. Weapons with better homing can adjust to the target's speed to plot a more effective intercept course.`

Expand Down
37 changes: 33 additions & 4 deletions source/AI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3357,7 +3357,7 @@ void AI::AimTurrets(const Ship &ship, FireCommand &command, bool opportunistic)
{
// Get the index of this weapon.
int index = &hardpoint - &ship.Weapons().front();
double offset = (hardpoint.HarmonizedAngle() - hardpoint.GetAngle()).Degrees();
double offset = (hardpoint.GetIdleAngle() - hardpoint.GetAngle()).Degrees();
command.SetAim(index, offset / hardpoint.GetOutfit()->TurretTurn());
}
return;
Expand All @@ -3375,8 +3375,12 @@ void AI::AimTurrets(const Ship &ship, FireCommand &command, bool opportunistic)
if(!previous && (Random::Int(60)))
continue;

Angle centerAngle = Angle(hardpoint.GetPoint());
double bias = (centerAngle - hardpoint.GetAngle()).Degrees() / 180.;
// Sweep between the min and max arc.
Angle centerAngle = Angle(hardpoint.GetIdleAngle());
const Angle minArc = hardpoint.GetMinArc();
const Angle maxArc = hardpoint.GetMaxArc();
const double arcMiddleDegrees = (minArc.AbsDegrees() + maxArc.AbsDegrees()) / 2.;
double bias = (centerAngle - hardpoint.GetAngle()).Degrees() / min(arcMiddleDegrees, 180.);
double acceleration = Random::Real() - Random::Real() + bias;
command.SetAim(index, previous + .1 * acceleration);
}
Expand Down Expand Up @@ -3438,7 +3442,32 @@ void AI::AimTurrets(const Ship &ship, FireCommand &command, bool opportunistic)
}

// Determine how much the turret must turn to face that vector.
double degrees = (Angle(p) - aim).Degrees();
double degrees = 0.;
Angle angleToPoint = Angle(p);
if(hardpoint.IsOmnidirectional())
degrees = (angleToPoint - aim).Degrees();
else
{
// For turret with limited arc, determine the turn up to the nearest arc limit.
// Also reduce priority of target if it's not within the firing arc.
const Angle facing = ship.Facing();
const Angle minArc = hardpoint.GetMinArc() + facing;
const Angle maxArc = hardpoint.GetMaxArc() + facing;
if(!angleToPoint.IsInRange(minArc, maxArc))
{
// Decrease the priority of the target.
rendezvousTime += 2. * weapon->TotalLifetime();

// Point to the nearer edge of the arc.
const double minDegree = (minArc - angleToPoint).Degrees();
const double maxDegree = (maxArc - angleToPoint).Degrees();
if(fabs(minDegree) < fabs(maxDegree))
angleToPoint = minArc;
else
angleToPoint = maxArc;
}
degrees = (angleToPoint - minArc).AbsDegrees() - (aim - minArc).AbsDegrees();
}
double turnTime = fabs(degrees) / weapon->TurretTurn();
// Always prefer targets that you are able to hit.
double score = turnTime + (180. / weapon->TurretTurn()) * rendezvousTime;
Expand Down
21 changes: 21 additions & 0 deletions source/Angle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,14 @@ double Angle::Degrees() const



// Convert an Angle object to degrees, in the range 0 to 360.
double Angle::AbsDegrees() const
{
return angle / DEG_TO_STEP;
}



// Return a point rotated by this angle around (0, 0).
Point Angle::Rotate(const Point &point) const
{
Expand All @@ -183,6 +191,19 @@ Point Angle::Rotate(const Point &point) const



// Judge whether this is inside from "base" to "limit."
// The range from "base" to "limit" is expressed by "clock" orientation.
bool Angle::IsInRange(const Angle& base, const Angle& limit) const
{
// Choose an edge of the arc as the reference angle (base) and
// compare relative angles to decide whether this is in the range.
Angle normalizedLimit = limit - base;
Angle normalizedTarget = *this - base;
return normalizedTarget.angle <= normalizedLimit.angle;
}



// Constructor using Angle's internal representation.
Angle::Angle(const int32_t angle)
: angle(angle)
Expand Down
6 changes: 6 additions & 0 deletions source/Angle.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,16 @@ class Angle {
Point Unit() const;
// Convert an Angle object to degrees, in the range -180 to 180.
double Degrees() const;
// Convert an Angle object to degrees, in the range 0 to 360.
double AbsDegrees() const;

// Return a point rotated by this angle around (0, 0).
Point Rotate(const Point &point) const;

// Judge whether this is inside from "base" to "limit."
// The range from "base" to "limit" is expressed by "clock" orientation.
bool IsInRange(const Angle &base, const Angle &limit) const;


private:
explicit Angle(int32_t angle);
Expand Down
12 changes: 7 additions & 5 deletions source/Armament.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,19 @@ using namespace std;


// Add a gun hardpoint (fixed-direction weapon).
void Armament::AddGunPort(const Point &point, const Angle &angle, bool isParallel, bool isUnder, const Outfit *outfit)
void Armament::AddGunPort(const Point &point, const Hardpoint::BaseAttributes &attributes,
bool isUnder, const Outfit *outfit)
{
hardpoints.emplace_back(point, angle, false, isParallel, isUnder, outfit);
hardpoints.emplace_back(point, attributes, false, isUnder, outfit);
}



// Add a turret hardpoint (omnidirectional weapon).
void Armament::AddTurret(const Point &point, bool isUnder, const Outfit *outfit)
// Add a turret hardpoint.
void Armament::AddTurret(const Point &point, const Hardpoint::BaseAttributes &attributes,
bool isUnder, const Outfit *outfit)
{
hardpoints.emplace_back(point, Angle(0.), true, false, isUnder, outfit);
hardpoints.emplace_back(point, attributes, true, isUnder, outfit);
}


Expand Down
6 changes: 4 additions & 2 deletions source/Armament.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@ class Visual;
class Armament {
public:
// Add a gun or turret hard-point.
void AddGunPort(const Point &point, const Angle &angle, bool isParallel, bool isUnder, const Outfit *outfit = nullptr);
void AddTurret(const Point &point, bool isUnder, const Outfit *outfit = nullptr);
void AddGunPort(const Point &point, const Hardpoint::BaseAttributes &attributes,
bool isUnder, const Outfit *outfit = nullptr);
void AddTurret(const Point &point, const Hardpoint::BaseAttributes &attributes,
bool isUnder, const Outfit *outfit = nullptr);
// This must be called after all the outfit data is loaded. If you add more
// of a given weapon than there are slots for it, the extras will not fire.
// But, the "gun ports" attribute should keep that from happening. To
Expand Down
157 changes: 134 additions & 23 deletions source/Hardpoint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,12 @@ namespace {


// Constructor.
Hardpoint::Hardpoint(const Point &point, const Angle &baseAngle, bool isTurret,
bool isParallel, bool isUnder, const Outfit *outfit)
: outfit(outfit), point(point * .5), baseAngle(baseAngle), isTurret(isTurret), isParallel(isParallel), isUnder(isUnder)
Hardpoint::Hardpoint(const Point &point, const BaseAttributes &attributes,
bool isTurret, bool isUnder, const Outfit *outfit)
: outfit(outfit), point(point * .5), baseAngle(attributes.baseAngle), baseAttributes(attributes),
isTurret(isTurret), isParallel(baseAttributes.isParallel), isUnder(isUnder)
{
UpdateArc();
}


Expand Down Expand Up @@ -77,14 +79,31 @@ const Angle &Hardpoint::GetAngle() const



// Get the default facing direction for a gun
const Angle &Hardpoint::GetBaseAngle() const
// Get the angle of a turret when idling, relative to the ship.
// For guns, this function is equal to GetAngle().
const Angle &Hardpoint::GetIdleAngle() const
{
return baseAngle;
}



// Get the arc of fire if this is a directional turret,
// otherwise a pair of 180 degree + baseAngle.
const Angle &Hardpoint::GetMinArc() const
{
return minArc;
}



const Angle &Hardpoint::GetMaxArc() const
{
return maxArc;
}



// Get the angle this weapon ought to point at for ideal gun harmonization.
Angle Hardpoint::HarmonizedAngle() const
{
Expand Down Expand Up @@ -122,6 +141,13 @@ bool Hardpoint::IsParallel() const



bool Hardpoint::IsOmnidirectional() const
{
return isOmnidirectional;
}



bool Hardpoint::IsUnder() const
{
return isUnder;
Expand Down Expand Up @@ -201,13 +227,25 @@ void Hardpoint::Step()


// Adjust this weapon's aim by the given amount, relative to its maximum
// "turret turn" rate.
// "turret turn" rate. Up to its angle limit.
void Hardpoint::Aim(double amount)
{
if(!outfit)
return;

angle += outfit->TurretTurn() * amount;
const double add = outfit->TurretTurn() * amount;
if(isOmnidirectional)
angle += add;
else
{
const Angle newAngle = angle + add;
if(add < 0. && minArc.IsInRange(newAngle, angle))
angle = minArc;
else if(add > 0. && maxArc.IsInRange(angle, newAngle))
angle = maxArc;
else
angle += add;
}
}


Expand Down Expand Up @@ -306,20 +344,23 @@ void Hardpoint::Install(const Outfit *outfit)
this->outfit = outfit;
Reload();

// For fixed weapons, apply "gun harmonization," pointing them slightly
// inward so the projectiles will converge. For turrets, start them out
// pointing outward from the center of the ship.
if(!isTurret)
// Update the arc of fire because of changing an outfit.
UpdateArc();

// For fixed weapons and idling turrets, apply "gun harmonization,"
// pointing them slightly inward so the projectiles will converge.
// Weapons that fire parallel beams don't get a harmonized angle.
// And some hardpoints/gunslots are configured not to get harmonized.
// So only harmonize when both the port and the outfit supports it.
if(!isParallel && !outfit->IsParallel())
{
angle = baseAngle;
// Weapons that fire in parallel beams don't get a harmonized angle.
// And some hardpoints/gunslots are configured not to get harmonized.
// So only harmonize when both the port and the outfit supports it.
if(!isParallel && !outfit->IsParallel())
angle += HarmonizedAngle();
const Angle harmonized = baseAngle + HarmonizedAngle();
// The harmonized angle might be out of the arc of a turret.
// If so, this turret is forced "parallel."
if(!isTurret || isOmnidirectional || harmonized.IsInRange(GetMinArc(), GetMaxArc()))
baseAngle = harmonized;
}
else
angle = Angle(point);
angle = baseAngle;
}
}

Expand All @@ -339,6 +380,17 @@ void Hardpoint::Reload()
void Hardpoint::Uninstall()
{
outfit = nullptr;

// Update the arc of fire because of changing an outfit.
UpdateArc();
}



// Get the attributes that can be used as a parameter of the constructor when cloning this.
const Hardpoint::BaseAttributes &Hardpoint::GetBaseAttributes() const
{
return baseAttributes;
}


Expand All @@ -350,20 +402,29 @@ bool Hardpoint::FireSpecialSystem(Ship &ship, const Body &body, std::vector<Visu
// Get the weapon range. Anti-missile and tractor beam shots always last a
// single frame, so their range is equal to their velocity.
double range = outfit->Velocity();
Angle facing = ship.Facing();

// Check if the body is within range of this hardpoint.
Point start = ship.Position() + ship.Facing().Rotate(point);
Point start = ship.Position() + facing.Rotate(point);
Point offset = body.Position() - start;
if(offset.Length() > range)
return false;

// Check if the target is within the arc of fire.
Angle aim(offset);
if(!isOmnidirectional)
{
const Angle minArc = GetMinArc() + facing;
const Angle maxArc = GetMaxArc() + facing;
if(!aim.IsInRange(minArc, maxArc))
return false;
}

// Precompute the number of visuals that will be added.
visuals.reserve(visuals.size() + outfit->FireEffects().size()
+ outfit->HitEffects().size() + outfit->DieEffects().size());

// Firing effects are displayed at the weapon hardpoint that just fired.
Angle aim(offset);
angle = aim - ship.Facing();
angle = aim - facing;
start += aim.Rotate(outfit->HardpointOffset());
CreateEffects(outfit->FireEffects(), start, ship.Velocity(), aim, visuals);

Expand Down Expand Up @@ -407,3 +468,53 @@ void Hardpoint::Fire(Ship &ship, const Point &start, const Angle &aim)
// case the outfit is its own ammunition.
ship.ExpendAmmo(*outfit);
}



// The arc depends on both the base hardpoint and the installed outfit.
void Hardpoint::UpdateArc()
{
if(!outfit)
return;

// Restore the initial value (from baseAttributes).
isOmnidirectional = baseAttributes.isOmnidirectional;
baseAngle = baseAttributes.baseAngle;
if(isOmnidirectional)
{
const Angle opposite = baseAngle + Angle(180.);
minArc = opposite;
maxArc = opposite;
}
else
{
minArc = baseAttributes.minArc;
maxArc = baseAttributes.maxArc;
}

// The installed weapon restricts the arc of fire.
const double hardpointsArc = (maxArc - minArc).AbsDegrees();
const double weaponsArc = outfit->Arc();
if(weaponsArc < 360. && (isOmnidirectional || weaponsArc < hardpointsArc))
{
isOmnidirectional = false;
const double weaponsHalf = weaponsArc / 2.;

// The base angle is placed as close to center as possible.
const Angle &firstAngle = minArc;
const Angle &secondAngle = maxArc;
double hardpointsMinArc = (baseAngle - firstAngle).AbsDegrees();
double hardpointsMaxArc = (secondAngle - baseAngle).AbsDegrees();
if(hardpointsMinArc < weaponsHalf)
hardpointsMaxArc = weaponsArc - hardpointsMinArc;
else if(hardpointsMaxArc < weaponsHalf)
hardpointsMinArc = weaponsArc - hardpointsMaxArc;
else
{
hardpointsMinArc = weaponsHalf;
hardpointsMaxArc = weaponsHalf;
}
minArc = baseAngle - hardpointsMinArc;
maxArc = baseAngle + hardpointsMaxArc;
}
}
Loading

0 comments on commit 94ec4ac

Please sign in to comment.