Skip to content

Commit

Permalink
feat(mechanics): Make escorts only pathfind through systems that the …
Browse files Browse the repository at this point in the history
…player has visited (endless-sky#6127)
  • Loading branch information
quyykk authored Dec 17, 2023
1 parent 4c70eca commit 5c9b49c
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 74 deletions.
117 changes: 60 additions & 57 deletions source/AI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -225,46 +225,6 @@ namespace {
return fuel < route.RequiredFuel(from, (to ? to : route.End()));
}

// Set the ship's TargetStellar or TargetSystem in order to reach the
// next desired system. Will target a landable planet to refuel.
void SelectRoute(Ship &ship, const System *targetSystem)
{
const System *from = ship.GetSystem();
if(from == targetSystem || !targetSystem)
return;
const DistanceMap route(ship, targetSystem);
const bool needsRefuel = ShouldRefuel(ship, route);
const System *to = route.Route(from);
// The destination may be accessible by both jump and wormhole.
// Prefer wormhole travel in these cases, to conserve fuel. Must
// check accessibility as DistanceMap may only see the jump path.
if(to && !needsRefuel)
for(const StellarObject &object : from->Objects())
{
if(!object.HasSprite() || !object.HasValidPlanet())
continue;

const Planet &planet = *object.GetPlanet();
if(planet.IsWormhole() && planet.IsAccessible(&ship)
&& &planet.GetWormhole()->WormholeDestination(*from) == to)
{
ship.SetTargetStellar(&object);
ship.SetTargetSystem(nullptr);
return;
}
}
else if(needsRefuel)
{
// There is at least one planet that can refuel the ship.
ship.SetTargetStellar(AI::FindLandingLocation(ship));
return;
}
// Either there is no viable wormhole route to this system, or
// the target system cannot be reached.
ship.SetTargetSystem(to);
ship.SetTargetStellar(nullptr);
}

// The health remaining before becoming disabled, at which fighters and
// other ships consider retreating from battle.
const double RETREAT_HEALTH = .25;
Expand Down Expand Up @@ -338,8 +298,9 @@ namespace {



AI::AI(const List<Ship> &ships, const List<Minable> &minables, const List<Flotsam> &flotsam)
: ships(ships), minables(minables), flotsam(flotsam)
AI::AI(const PlayerInfo &player, const List<Ship> &ships,
const List<Minable> &minables, const List<Flotsam> &flotsam)
: player(player), ships(ships), minables(minables), flotsam(flotsam)
{
// Allocate a starting amount of hardpoints for ships.
firingCommands.SetHardpoints(12);
Expand All @@ -348,39 +309,39 @@ AI::AI(const List<Ship> &ships, const List<Minable> &minables, const List<Flotsa


// Fleet commands from the player.
void AI::IssueShipTarget(const PlayerInfo &player, const shared_ptr<Ship> &target)
void AI::IssueShipTarget(const shared_ptr<Ship> &target)
{
Orders newOrders;
bool isEnemy = target->GetGovernment()->IsEnemy();
newOrders.type = (!isEnemy ? Orders::KEEP_STATION
: target->IsDisabled() ? Orders::FINISH_OFF : Orders::ATTACK);
newOrders.target = target;
string description = (isEnemy ? "focusing fire on" : "following") + (" \"" + target->Name() + "\".");
IssueOrders(player, newOrders, description);
IssueOrders(newOrders, description);
}



void AI::IssueAsteroidTarget(const PlayerInfo &player, const shared_ptr<Minable> &targetAsteroid)
void AI::IssueAsteroidTarget(const shared_ptr<Minable> &targetAsteroid)
{
Orders newOrders;
newOrders.type = Orders::MINE;
newOrders.targetAsteroid = targetAsteroid;
IssueOrders(player, newOrders,
IssueOrders(newOrders,
"focusing fire on " + targetAsteroid->DisplayName() + " " + targetAsteroid->Noun() + ".");
}



void AI::IssueMoveTarget(const PlayerInfo &player, const Point &target, const System *moveToSystem)
void AI::IssueMoveTarget(const Point &target, const System *moveToSystem)
{
Orders newOrders;
newOrders.type = Orders::MOVE_TO;
newOrders.point = target;
newOrders.targetSystem = moveToSystem;
string description = "moving to the given location";
description += player.GetSystem() == moveToSystem ? "." : (" in the " + moveToSystem->Name() + " system.");
IssueOrders(player, newOrders, description);
IssueOrders(newOrders, description);
}


Expand Down Expand Up @@ -440,20 +401,20 @@ void AI::UpdateKeys(PlayerInfo &player, Command &activeCommands)
{
newOrders.type = target->IsDisabled() ? Orders::FINISH_OFF : Orders::ATTACK;
newOrders.target = target;
IssueOrders(player, newOrders, "focusing fire on \"" + target->Name() + "\".");
IssueOrders(newOrders, "focusing fire on \"" + target->Name() + "\".");
}
else if(activeCommands.Has(Command::FIGHT) && targetAsteroid)
IssueAsteroidTarget(player, targetAsteroid);
IssueAsteroidTarget(targetAsteroid);
if(activeCommands.Has(Command::HOLD))
{
newOrders.type = Orders::HOLD_POSITION;
IssueOrders(player, newOrders, "holding position.");
IssueOrders(newOrders, "holding position.");
}
if(activeCommands.Has(Command::GATHER))
{
newOrders.type = Orders::GATHER;
newOrders.target = player.FlagshipPtr();
IssueOrders(player, newOrders, "gathering around your flagship.");
IssueOrders(newOrders, "gathering around your flagship.");
}

// Get rid of any invalid orders. Carried ships will retain orders in case they are deployed.
Expand Down Expand Up @@ -555,7 +516,7 @@ void AI::ClearOrders()



void AI::Step(const PlayerInfo &player, Command &activeCommands)
void AI::Step(Command &activeCommands)
{
// First, figure out the comparative strengths of the present governments.
const System *playerSystem = player.GetSystem();
Expand Down Expand Up @@ -605,7 +566,7 @@ void AI::Step(const PlayerInfo &player, Command &activeCommands)
{
// Player cannot do anything if the flagship is landing.
if(!flagship->IsLanding())
MovePlayer(*it, player, activeCommands);
MovePlayer(*it, activeCommands);
continue;
}

Expand Down Expand Up @@ -1985,6 +1946,48 @@ bool AI::CanRefuel(const Ship &ship, const StellarObject *target)
}


// Set the ship's target system or planet in order to reach the
// next desired system. Will target a landable planet to refuel.
// If the ship is an escort it will only use routes known to the player.
void AI::SelectRoute(Ship &ship, const System *targetSystem) const
{
const System *from = ship.GetSystem();
if(from == targetSystem || !targetSystem)
return;
const DistanceMap route(ship, targetSystem, ship.IsYours() ? &player : nullptr);
const bool needsRefuel = ShouldRefuel(ship, route);
const System *to = route.Route(from);
// The destination may be accessible by both jump and wormhole.
// Prefer wormhole travel in these cases, to conserve fuel. Must
// check accessibility as DistanceMap may only see the jump path.
if(to && !needsRefuel)
for(const StellarObject &object : from->Objects())
{
if(!object.HasSprite() || !object.HasValidPlanet())
continue;

const Planet &planet = *object.GetPlanet();
if(planet.IsWormhole() && planet.IsAccessible(&ship)
&& &planet.GetWormhole()->WormholeDestination(*from) == to)
{
ship.SetTargetStellar(&object);
ship.SetTargetSystem(nullptr);
return;
}
}
else if(needsRefuel)
{
// There is at least one planet that can refuel the ship.
ship.SetTargetStellar(AI::FindLandingLocation(ship));
return;
}
// Either there is no viable wormhole route to this system, or
// the target system cannot be reached.
ship.SetTargetSystem(to);
ship.SetTargetStellar(nullptr);
}



// Determine if a carried ship meets any of the criteria for returning to its parent.
bool AI::ShouldDock(const Ship &ship, const Ship &parent, const System *playerSystem) const
Expand Down Expand Up @@ -3725,7 +3728,7 @@ bool AI::TargetMinable(Ship &ship) const



void AI::MovePlayer(Ship &ship, const PlayerInfo &player, Command &activeCommands)
void AI::MovePlayer(Ship &ship, Command &activeCommands)
{
Command command;
firingCommands.SetHardpoints(ship.Weapons().size());
Expand Down Expand Up @@ -4173,7 +4176,7 @@ void AI::MovePlayer(Ship &ship, const PlayerInfo &player, Command &activeCommand
{
Orders newOrders;
newOrders.type = Orders::HARVEST;
IssueOrders(player, newOrders, "preparing to harvest.");
IssueOrders(newOrders, "preparing to harvest.");
}
else if(activeCommands.Has(Command::NEAREST_ASTEROID))
{
Expand Down Expand Up @@ -4494,7 +4497,7 @@ void AI::CacheShipLists()



void AI::IssueOrders(const PlayerInfo &player, const Orders &newOrders, const string &description)
void AI::IssueOrders(const Orders &newOrders, const string &description)
{
string who;

Expand Down
23 changes: 15 additions & 8 deletions source/AI.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,14 @@ class AI {
// Any object that can be a ship's target is in a list of this type:
template <class Type>
using List = std::list<std::shared_ptr<Type>>;
// Constructor, giving the AI access to various object lists.
AI(const List<Ship> &ships, const List<Minable> &minables, const List<Flotsam> &flotsam);
// Constructor, giving the AI access to the player and various object lists.
AI(const PlayerInfo &player, const List<Ship> &ships,
const List<Minable> &minables, const List<Flotsam> &flotsam);

// Fleet commands from the player.
void IssueShipTarget(const PlayerInfo &player, const std::shared_ptr<Ship> &target);
void IssueAsteroidTarget(const PlayerInfo &player, const std::shared_ptr<Minable> &targetAsteroid);
void IssueMoveTarget(const PlayerInfo &player, const Point &target, const System *moveToSystem);
void IssueShipTarget(const std::shared_ptr<Ship> &target);
void IssueAsteroidTarget(const std::shared_ptr<Minable> &targetAsteroid);
void IssueMoveTarget(const Point &target, const System *moveToSystem);
// Commands issued via the keyboard (mostly, to the flagship).
void UpdateKeys(PlayerInfo &player, Command &clickCommands);

Expand All @@ -69,7 +70,7 @@ template <class Type>
// but not when they jump from one system to another.
void ClearOrders();
// Issue AI commands to all ships for one game step.
void Step(const PlayerInfo &player, Command &activeCommands);
void Step(Command &activeCommands);

// Set the mouse position for turning the player's flagship.
void SetMousePosition(Point position);
Expand Down Expand Up @@ -100,6 +101,10 @@ template <class Type>
void MoveEscort(Ship &ship, Command &command) const;
static void Refuel(Ship &ship, Command &command);
static bool CanRefuel(const Ship &ship, const StellarObject *target);
// Set the ship's target system or planet in order to reach the
// next desired system. Will target a landable planet to refuel.
// If the ship is an escort it will only use routes known to the player.
void SelectRoute(Ship &ship, const System *targetSystem) const;
bool ShouldDock(const Ship &ship, const Ship &parent, const System *playerSystem) const;

// Methods of moving from the current position to a desired position / orientation.
Expand Down Expand Up @@ -153,7 +158,7 @@ template <class Type>
// projectile. If it cannot hit the target, this returns NaN.
static double RendezvousTime(const Point &p, const Point &v, double vp);

void MovePlayer(Ship &ship, const PlayerInfo &player, Command &activeCommands);
void MovePlayer(Ship &ship, Command &activeCommands);

// True if found asteroid.
bool TargetMinable(Ship &ship) const;
Expand Down Expand Up @@ -200,12 +205,14 @@ template <class Type>


private:
void IssueOrders(const PlayerInfo &player, const Orders &newOrders, const std::string &description);
void IssueOrders(const Orders &newOrders, const std::string &description);
// Convert order types based on fulfillment status.
void UpdateOrders(const Ship &ship);


private:
// TODO: Figure out a way to remove the player dependency.
const PlayerInfo &player;
// Data from the game engine.
const List<Ship> &ships;
const List<Minable> &minables;
Expand Down
6 changes: 4 additions & 2 deletions source/DistanceMap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,10 @@ DistanceMap::DistanceMap(const PlayerInfo &player, const System *center)
// Calculate the path for the given ship to get to the given system. The
// ship will use a jump drive or hyperdrive depending on what it has. The
// pathfinding will stop once a path to the destination is found.
DistanceMap::DistanceMap(const Ship &ship, const System *destination)
: source(ship.GetSystem()), center(destination)
// If a player is given, the path will only include systems that the
// player has visited.
DistanceMap::DistanceMap(const Ship &ship, const System *destination, const PlayerInfo *player)
: player(player), source(ship.GetSystem()), center(destination)
{
if(!source || !destination)
return;
Expand Down
4 changes: 3 additions & 1 deletion source/DistanceMap.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ class DistanceMap {
// Calculate the path for the given ship to get to the given system. The
// ship will use a jump drive or hyperdrive depending on what it has. The
// pathfinding will stop once a path to the destination is found.
DistanceMap(const Ship &ship, const System *destination);
// If a player is given, the path will only include systems that the
// player has visited.
DistanceMap(const Ship &ship, const System *destination, const PlayerInfo *player = nullptr);

// Find out if the given system is reachable.
bool HasRoute(const System *system) const;
Expand Down
12 changes: 6 additions & 6 deletions source/Engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ namespace {


Engine::Engine(PlayerInfo &player)
: player(player), ai(ships, asteroids.Minables(), flotsam),
: player(player), ai(player, ships, asteroids.Minables(), flotsam),
ammoDisplay(player), shipCollisions(256u, 32u)
{
zoom.base = Preferences::ViewZoom();
Expand Down Expand Up @@ -1438,7 +1438,7 @@ void Engine::CalculateStep()
// Handle the mouse input of the mouse navigation
HandleMouseInput(activeCommands);
// Now, all the ships must decide what they are doing next.
ai.Step(player, activeCommands);
ai.Step(activeCommands);

// Clear the active players commands, they are all processed at this point.
activeCommands.Clear();
Expand Down Expand Up @@ -1984,7 +1984,7 @@ void Engine::HandleMouseClicks()
if(player.HasEscortDestination())
{
auto moveTarget = player.GetEscortDestination();
ai.IssueMoveTarget(player, moveTarget.second, moveTarget.first);
ai.IssueMoveTarget(moveTarget.second, moveTarget.first);
player.SetEscortDestination();
}

Expand Down Expand Up @@ -2049,7 +2049,7 @@ void Engine::HandleMouseClicks()
if(clickTarget)
{
if(isRightClick)
ai.IssueShipTarget(player, clickTarget);
ai.IssueShipTarget(clickTarget);
else
{
// Left click: has your flagship select or board the target.
Expand Down Expand Up @@ -2080,12 +2080,12 @@ void Engine::HandleMouseClicks()
clickRange = range;
flagship->SetTargetAsteroid(minable);
if(isRightClick)
ai.IssueAsteroidTarget(player, minable);
ai.IssueAsteroidTarget(minable);
}
}
}
if(isRightClick && !clickTarget && !clickedAsteroid && !isMouseTurningEnabled)
ai.IssueMoveTarget(player, clickPoint + center, playerSystem);
ai.IssueMoveTarget(clickPoint + center, playerSystem);

// Treat an "empty" click as a request to clear targets.
if(!clickTarget && !isRightClick && !clickedAsteroid && !clickedPlanet)
Expand Down

0 comments on commit 5c9b49c

Please sign in to comment.