diff --git a/source/AI.cpp b/source/AI.cpp index c84f0850f281..d8ef5e64add9 100644 --- a/source/AI.cpp +++ b/source/AI.cpp @@ -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; @@ -338,8 +298,9 @@ namespace { -AI::AI(const List &ships, const List &minables, const List &flotsam) - : ships(ships), minables(minables), flotsam(flotsam) +AI::AI(const PlayerInfo &player, const List &ships, + const List &minables, const List &flotsam) + : player(player), ships(ships), minables(minables), flotsam(flotsam) { // Allocate a starting amount of hardpoints for ships. firingCommands.SetHardpoints(12); @@ -348,7 +309,7 @@ AI::AI(const List &ships, const List &minables, const List &target) +void AI::IssueShipTarget(const shared_ptr &target) { Orders newOrders; bool isEnemy = target->GetGovernment()->IsEnemy(); @@ -356,23 +317,23 @@ void AI::IssueShipTarget(const PlayerInfo &player, const shared_ptr &targe : 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 &targetAsteroid) +void AI::IssueAsteroidTarget(const shared_ptr &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; @@ -380,7 +341,7 @@ void AI::IssueMoveTarget(const PlayerInfo &player, const Point &target, const Sy 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); } @@ -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. @@ -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(); @@ -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; } @@ -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 @@ -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()); @@ -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)) { @@ -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; diff --git a/source/AI.h b/source/AI.h index 00eaae08f38b..ecdb500eb2d2 100644 --- a/source/AI.h +++ b/source/AI.h @@ -51,13 +51,14 @@ class AI { // Any object that can be a ship's target is in a list of this type: template using List = std::list>; - // Constructor, giving the AI access to various object lists. - AI(const List &ships, const List &minables, const List &flotsam); + // Constructor, giving the AI access to the player and various object lists. + AI(const PlayerInfo &player, const List &ships, + const List &minables, const List &flotsam); // Fleet commands from the player. - void IssueShipTarget(const PlayerInfo &player, const std::shared_ptr &target); - void IssueAsteroidTarget(const PlayerInfo &player, const std::shared_ptr &targetAsteroid); - void IssueMoveTarget(const PlayerInfo &player, const Point &target, const System *moveToSystem); + void IssueShipTarget(const std::shared_ptr &target); + void IssueAsteroidTarget(const std::shared_ptr &targetAsteroid); + void IssueMoveTarget(const Point &target, const System *moveToSystem); // Commands issued via the keyboard (mostly, to the flagship). void UpdateKeys(PlayerInfo &player, Command &clickCommands); @@ -69,7 +70,7 @@ template // 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); @@ -100,6 +101,10 @@ template 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. @@ -153,7 +158,7 @@ template // 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; @@ -200,12 +205,14 @@ template 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 &ships; const List &minables; diff --git a/source/DistanceMap.cpp b/source/DistanceMap.cpp index a41651ce066b..c0e45997179e 100644 --- a/source/DistanceMap.cpp +++ b/source/DistanceMap.cpp @@ -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; diff --git a/source/DistanceMap.h b/source/DistanceMap.h index 2babedec2b09..3cd08c07587d 100644 --- a/source/DistanceMap.h +++ b/source/DistanceMap.h @@ -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; diff --git a/source/Engine.cpp b/source/Engine.cpp index c3060b35f490..6233ea8b1780 100644 --- a/source/Engine.cpp +++ b/source/Engine.cpp @@ -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(); @@ -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(); @@ -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(); } @@ -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. @@ -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)