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

Limit number of fleets & spawn specified number when entering system #24

Merged
Merged
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
3 changes: 3 additions & 0 deletions source/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ target_sources(EndlessSkyLib PRIVATE
ItemInfoDisplay.cpp
ItemInfoDisplay.h
JumpTypes.h
LimitedEvents.h
Logger.cpp
Logger.h
LineShader.cpp
Expand Down Expand Up @@ -294,6 +295,8 @@ target_sources(EndlessSkyLib PRIVATE
ShopPanel.h
SpaceportPanel.cpp
SpaceportPanel.h
SpawnedFleet.cpp
SpawnedFleet.h
SpriteShader.cpp
SpriteShader.h
StarField.cpp
Expand Down
157 changes: 147 additions & 10 deletions source/Engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1423,6 +1423,8 @@ void Engine::EnterSystem()
if(!flagship)
return;

updateFleetCounters = player.Conditions().Get(PlayerInfo::UPDATE_FLEET_COUNTERS_CONDITION_NAME);

doEnter = true;
doEnterLabels = true;
player.IncrementDate();
Expand Down Expand Up @@ -1492,14 +1494,35 @@ void Engine::EnterSystem()

// Clear any active weather events
activeWeather.clear();

// If any fleets have an initial spawn count, spawn them.
for(const auto &fleet : system->Fleets())
if(fleet.InitialCount() > 0 && !fleet.Category().empty())
{
size_t toPlace = FleetPlacementLimit(fleet, 0, true);
for(size_t i = 0; i < toPlace ; ++i)
{
fleetShips.clear();
fleet.Get()->Place(*system, fleetShips, true);
AddSpawnedFleet(fleet);
}
}

// Place five seconds worth of fleets and weather events. Check for
// undefined fleets by not trying to create anything with no
// government set.
for(int i = 0; i < 5; ++i)
{
for(const auto &fleet : system->Fleets())
if(fleet.Get()->GetGovernment() && Random::Int(fleet.Period()) < 60)
fleet.Get()->Place(*system, newShips);
// Skip fleets that don't want to spawn on system entry,
// or fleets whose limits have already been reached.
if(!fleet.GetFlags(Fleet::SKIP_SYSTEM_ENTRY) &&
FleetPlacementLimit(fleet, 60, true))
{
fleetShips.clear();
fleet.Get()->Place(*system, fleetShips, true);
AddSpawnedFleet(fleet);
}

auto CreateWeather = [this](const RandomEvent<Hazard> &hazard, Point origin)
{
Expand Down Expand Up @@ -1676,6 +1699,9 @@ void Engine::CalculateStep()
visual.Move();
Prune(visuals);

// Remove destroyed ships and fleets from the spawnedFleets list.
PruneSpawnedFleets();

// Perform various minor actions.
SpawnFleets();
SpawnPersons();
Expand Down Expand Up @@ -1949,20 +1975,25 @@ void Engine::SpawnFleets()
// Non-mission NPCs spawn at random intervals in neighboring systems,
// or coming from planets in the current one.
for(const auto &fleet : player.GetSystem()->Fleets())
if(!Random::Int(fleet.Period()))
if(FleetPlacementLimit(fleet, 1, true) > 0)
{
const Government *gov = fleet.Get()->GetGovernment();
if(!gov)
continue;

// Don't spawn a fleet if its allies in-system already far outnumber
// its enemies. This is to avoid having a system get mobbed with
// massive numbers of "reinforcements" during a battle.
int64_t enemyStrength = ai.EnemyStrength(gov);
if(enemyStrength && ai.AllyStrength(gov) > 2 * enemyStrength)
continue;
if(!fleet.GetFlags(Fleet::IGNORE_ENEMY_STRENGTH))
{
// Don't spawn a fleet if its allies in-system already far outnumber
// its enemies. This is to avoid having a system get mobbed with
// massive numbers of "reinforcements" during a battle.
int64_t enemyStrength = ai.EnemyStrength(gov);
if(enemyStrength && ai.AllyStrength(gov) > 2 * enemyStrength)
continue;
}

fleet.Get()->Enter(*player.GetSystem(), newShips);
fleetShips.clear();
fleet.Get()->Enter(*player.GetSystem(), fleetShips, nullptr);
AddSpawnedFleet(fleet);
}
}

Expand Down Expand Up @@ -2880,6 +2911,112 @@ void Engine::DoGrudge(const shared_ptr<Ship> &target, const Government *attacker



size_t Engine::FleetPlacementLimit(const LimitedEvents<Fleet> &fleet, unsigned frames, bool requireGovernment)
{
// frames = how many frames worth of ships to place:
// 0 = used to indicate the fleet.InitialCount() number of fleets should
// be spawned if they aren't already present
// 60 = used immediately after that, five times when entering the system to
// spawn five seconds of ships
// 1 = the normal value, used when spawning random event ships

if(requireGovernment && !fleet.Get()->GetGovernment())
// Fleet has no government, but caller required one.
return 0;
else if(frames && Random::Int(fleet.Period()) >= frames)
// It is not yet time to place this fleet.
return 0;
else if(frames && !fleet.HasLimit() && !fleet.HasNonDisabledLimit())
// This is not an initalCount spawn, and the fleet is unlimited.
return numeric_limits<int>::max();
else if(!frames && fleet.InitialCount() <= 0)
// During an initialCount spawn, if the initialCount is 0, there's nothing to spawn.
return 0;
else if(!frames)
return static_cast<size_t>(max<int>(0, fleet.InitialCount() -
CountFleetsWithCategory(fleet.Category())));

int available = numeric_limits<int>::max();

// Count the disabled & non-disabled ships together first since that is a cheap calculation.
if(fleet.HasLimit())
available = max<int>(0, fleet.Limit() - CountFleetsWithCategory(fleet.Category()));

// More expensive non-disabled count is last, if requested:
if(available && fleet.HasNonDisabledLimit())
available = min<int>(available, max<int>(0, fleet.NonDisabledLimit() -
CountNonDisabledFleetsWithCategory(fleet.Category())));

return static_cast<size_t>(available);
}



size_t Engine::CountFleetsWithCategory(const string &category)
{
return category.empty() ? 0 : spawnedFleets.count(category);
}



size_t Engine::CountNonDisabledFleetsWithCategory(const string &category)
{
if(category.empty())
return 0;
auto range = spawnedFleets.equal_range(category);
size_t count = 0;
for(auto it = range.first; it != range.second; it++)
try {
shared_ptr<SpawnedFleet> fleet(it->second);
if(fleet->CountNonDisabledShips())
// Some ships are not yet disabled or destroyed
count++;
}
catch(const bad_weak_ptr &bwp)
{
}
return count;
}



void Engine::PruneSpawnedFleets()
{
for(auto it = spawnedFleets.begin(); it != spawnedFleets.end();)
try {
shared_ptr<SpawnedFleet> fleet(it->second);
fleet->PruneShips();
if(fleet->CountShips())
// some ships remain
++it;
else
it = spawnedFleets.erase(it);
}
catch(const bad_weak_ptr &bwp)
{
it = spawnedFleets.erase(it);
}
}



void Engine::AddSpawnedFleet(const LimitedEvents<Fleet> &fleetEvent)
{
const std::string &category = fleetEvent.Category();
shared_ptr<SpawnedFleet> fleet = make_shared<SpawnedFleet>(category, fleetShips);
fleet->ConnectToShips();
spawnedFleets.emplace(category, fleet);
newShips.splice(newShips.end(), fleetShips);
if(updateFleetCounters)
{
const std::string &name = fleetEvent.Get()->Name();
if(!name.empty())
player.FleetCounters()[name]++;
}
}



void Engine::CreateStatusOverlays()
{
const auto overlayAllSetting = Preferences::StatusOverlaysState(Preferences::OverlayType::ALL);
Expand Down
14 changes: 14 additions & 0 deletions source/Engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,16 @@ this program. If not, see <https://www.gnu.org/licenses/>.
#include "Command.h"
#include "DrawList.h"
#include "EscortDisplay.h"
#include "Fleet.h"
#include "Information.h"
#include "LimitedEvents.h"
#include "PlanetLabel.h"
#include "Point.h"
#include "Preferences.h"
#include "Projectile.h"
#include "Radar.h"
#include "Rectangle.h"
#include "SpawnedFleet.h"
#include "TaskQueue.h"

#include <condition_variable>
Expand Down Expand Up @@ -181,6 +184,12 @@ class Engine {

void DoGrudge(const std::shared_ptr<Ship> &target, const Government *attacker);

size_t FleetPlacementLimit(const LimitedEvents<Fleet> &fleet, unsigned frames, bool requireGovernment);
size_t CountFleetsWithCategory(const std::string &category);
size_t CountNonDisabledFleetsWithCategory(const std::string &category);
void PruneSpawnedFleets();
void AddSpawnedFleet(const LimitedEvents<Fleet> &category);

void CreateStatusOverlays();
void EmplaceStatusOverlay(const std::shared_ptr<Ship> &ship, Preferences::OverlayState overlaySetting,
int value, double cloak);
Expand All @@ -195,6 +204,11 @@ class Engine {
std::list<std::shared_ptr<Flotsam>> flotsam;
std::vector<Visual> visuals;
AsteroidField asteroids;
std::unordered_multimap<std::string, std::weak_ptr<SpawnedFleet>> spawnedFleets;
bool updateFleetCounters = false;

// Temporary usage while adding a fleet:
std::list<std::shared_ptr<Ship>> fleetShips;

// New objects created within the latest step:
std::list<std::shared_ptr<Ship>> newShips;
Expand Down
7 changes: 7 additions & 0 deletions source/Fleet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,13 @@ int64_t Fleet::Strength() const



const std::string &Fleet::Name() const
{
return fleetName;
}



// Obtain a positional reference and the radius of the object at that position (e.g. a planet).
// Spaceport status can be modified during normal gameplay, so this information is not cached.
pair<Point, double> Fleet::ChooseCenter(const System &system)
Expand Down
8 changes: 8 additions & 0 deletions source/Fleet.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ class System;
// names are chosen based on a given random "phrase" generator.
class Fleet {
public:
// Flags for LimitedEvents<Fleet>
static const unsigned DEFAULT_FLEET_CATEGORY = 1;
static const unsigned IGNORE_ENEMY_STRENGTH = 2;
static const unsigned SKIP_SYSTEM_ENTRY = 4;


Fleet() = default;
// Construct and Load() at the same time.
Fleet(const DataNode &node);
Expand All @@ -65,6 +71,7 @@ class Fleet {
void Enter(const System &system, std::list<std::shared_ptr<Ship>> &ships, const Planet *planet = nullptr) const;
// Place a fleet in the given system, already "in action." If the carried flag is set, only
// uncarried ships will be added to the list (as any carriables will be stored in bays).
// Give it an id if it is part of a limited count random event fleet.
void Place(const System &system, std::list<std::shared_ptr<Ship>> &ships,
bool carried = true, bool addCargo = true) const;

Expand All @@ -74,6 +81,7 @@ class Fleet {
static void Place(const System &system, Ship &ship);

int64_t Strength() const;
const std::string &Name() const;


private:
Expand Down
Loading
Loading