Skip to content

Commit

Permalink
feat(enhancement): Add engine support for time travel (endless-sky#8937)
Browse files Browse the repository at this point in the history
Co-authored-by: Hurleveur <[email protected]>
Co-authored-by: Rising Leaf <[email protected]>
Co-authored-by: Peter van der Meer <[email protected]>
  • Loading branch information
4 people authored Dec 3, 2024
1 parent abea2a0 commit 9adae12
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 79 deletions.
2 changes: 1 addition & 1 deletion source/Engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1277,7 +1277,7 @@ void Engine::EnterSystem()

doEnter = true;
doEnterLabels = true;
player.IncrementDate();
player.AdvanceDate();
const Date &today = player.GetDate();

const System *system = flagship->GetSystem();
Expand Down
8 changes: 8 additions & 0 deletions source/GameEvent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -254,3 +254,11 @@ const list<DataNode> &GameEvent::Changes() const
{
return changes;
}



// Date comparison.
bool GameEvent::operator<(const GameEvent &other) const
{
return date < other.date;
}
2 changes: 2 additions & 0 deletions source/GameEvent.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ class GameEvent {

const std::list<DataNode> &Changes() const;

// Comparison operator, based on the date of the event.
bool operator<(const GameEvent &other) const;

private:
Date date;
Expand Down
9 changes: 5 additions & 4 deletions source/Messages.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,21 @@ namespace {


// Add a message to the list along with its level of importance
void Messages::Add(const string &message, Importance importance)
// When forced, the message is forcibly added to the log, but not to the list.
void Messages::Add(const string &message, Importance importance, bool force)
{
lock_guard<mutex> lock(incomingMutex);
incoming.emplace_back(message, importance);
AddLog(message, importance);
AddLog(message, importance, force);
}



// Add a message to the log. For messages meant to be shown
// also on the main panel, use Add instead.
void Messages::AddLog(const string &message, Importance importance)
void Messages::AddLog(const string &message, Importance importance, bool force)
{
if(logged.empty() || message != logged.front().first)
if(force || logged.empty() || message != logged.front().first)
{
logged.emplace_front(message, importance);
if(logged.size() > MAX_LOG)
Expand Down
5 changes: 3 additions & 2 deletions source/Messages.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,11 @@ class Messages {

public:
// Add a message to the list along with its level of importance
static void Add(const std::string &message, Importance importance = Importance::Low);
// When forced, the message is forcibly added to the log, but not to the list.
static void Add(const std::string &message, Importance importance = Importance::Low, bool force = false);
// Add a message to the log. For messages meant to be shown
// also on the main panel, use Add instead.
static void AddLog(const std::string &message, Importance importance = Importance::Low);
static void AddLog(const std::string &message, Importance importance = Importance::Low, bool force = false);

// Get the messages for the given game step. Any messages that are too old
// will be culled out, and new ones that have just been added will have
Expand Down
148 changes: 80 additions & 68 deletions source/PlayerInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ void PlayerInfo::Load(const string &path)
giftedShips[grand.Token(0)] = EsUuid::FromString(grand.Token(1));
}
else if(child.Token(0) == "event")
gameEvents.emplace_back(child);
gameEvents.emplace(GameEvent(child));
else if(child.Token(0) == "changes")
{
for(const DataNode &grand : child)
Expand Down Expand Up @@ -594,7 +594,7 @@ void PlayerInfo::AddChanges(list<DataNode> &changes)


// Add an event that will happen at the given date.
void PlayerInfo::AddEvent(const GameEvent &event, const Date &date)
void PlayerInfo::AddEvent(GameEvent event, const Date &date)
{
// Check if the event should be applied directly.
if(date <= this->date)
Expand All @@ -606,8 +606,8 @@ void PlayerInfo::AddEvent(const GameEvent &event, const Date &date)
}
else
{
gameEvents.push_back(event);
gameEvents.back().SetDate(date);
event.SetDate(date);
gameEvents.insert(std::move(event));
}
}

Expand Down Expand Up @@ -721,79 +721,40 @@ const Date &PlayerInfo::GetDate() const



// Set the date to the next day, and perform all daily actions.
void PlayerInfo::IncrementDate()
// Set the date, and perform all daily actions the given number of times.
void PlayerInfo::AdvanceDate(int amount)
{
++date;

// Check if any special events should happen today.
auto it = gameEvents.begin();
list<DataNode> eventChanges;
while(it != gameEvents.end())
if(amount <= 0)
return;
while(amount--)
{
if(date < it->GetDate())
++it;
else
++date;

// Check if any special events should happen today.
auto it = gameEvents.begin();
list<DataNode> eventChanges;
while(it != gameEvents.end() && date >= it->GetDate())
{
eventChanges.splice(eventChanges.end(), it->Apply(*this));
GameEvent event = *it;
eventChanges.splice(eventChanges.end(), event.Apply(*this));
it = gameEvents.erase(it);
}
}
if(!eventChanges.empty())
AddChanges(eventChanges);

// Check if any missions have failed because of deadlines and
// do any daily mission actions for those that have not failed.
for(Mission &mission : missions)
{
if(mission.CheckDeadline(date) && mission.IsVisible())
Messages::Add("You failed to meet the deadline for the mission \"" + mission.Name() + "\".",
Messages::Importance::Highest);
if(!mission.IsFailed(*this))
mission.Do(Mission::DAILY, *this);
}
if(!eventChanges.empty())
AddChanges(eventChanges);

// Check what salaries and tribute the player receives.
int64_t salariesIncome = accounts.SalariesIncomeTotal();
int64_t tributeIncome = GetTributeTotal();
FleetBalance b = MaintenanceAndReturns();
if(salariesIncome || tributeIncome || b.assetsReturns)
{
string message = "You receive ";
if(salariesIncome)
message += Format::CreditString(salariesIncome) + " salary";
if(salariesIncome && tributeIncome)
// Check if any missions have failed because of deadlines and
// do any daily mission actions for those that have not failed.
for(Mission &mission : missions)
{
if(b.assetsReturns)
message += ", ";
else
message += " and ";
if(mission.CheckDeadline(date) && mission.IsVisible())
Messages::Add("You failed to meet the deadline for the mission \"" + mission.Name() + "\".",
Messages::Importance::Highest);
if(!mission.IsFailed(*this))
mission.Do(Mission::DAILY, *this);
}
if(tributeIncome)
message += Format::CreditString(tributeIncome) + " in tribute";
if(salariesIncome && tributeIncome && b.assetsReturns)
message += ",";
if((salariesIncome || tributeIncome) && b.assetsReturns)
message += " and ";
if(b.assetsReturns)
message += Format::CreditString(b.assetsReturns) + " based on outfits and ships";
message += ".";
Messages::Add(message, Messages::Importance::High);
accounts.AddCredits(salariesIncome + tributeIncome + b.assetsReturns);
}

// For accounting, keep track of the player's net worth. This is for
// calculation of yearly income to determine maximum mortgage amounts.
int64_t assets = depreciation.Value(ships, date.DaysSinceEpoch());
for(const shared_ptr<Ship> &ship : ships)
assets += ship->Cargo().Value(system);

// Have the player pay salaries, mortgages, etc. and print a message that
// summarizes the payments that were made.
string message = accounts.Step(assets, Salaries(), b.maintenanceCosts);
if(!message.empty())
Messages::Add(message, Messages::Importance::High);

DoAccounting();
}
// Reset the reload counters for all your ships.
for(const shared_ptr<Ship> &ship : ships)
ship->GetArmament().ReloadAll();
Expand Down Expand Up @@ -914,6 +875,57 @@ Account &PlayerInfo::Accounts()



// Handle the daily salaries and payments.
void PlayerInfo::DoAccounting()
{
// Check what salaries and tribute the player receives.
int64_t salariesIncome = accounts.SalariesIncomeTotal();
int64_t tributeIncome = GetTributeTotal();
FleetBalance balance = MaintenanceAndReturns();
if(salariesIncome || tributeIncome || balance.assetsReturns)
{
string message = "You receive ";
if(salariesIncome)
{
message += Format::CreditString(salariesIncome) + " salary";
if(tributeIncome)
{
if(balance.assetsReturns)
message += ", ";
else
message += " and ";
}
}
if(tributeIncome)
message += Format::CreditString(tributeIncome) + " in tribute";
if(balance.assetsReturns)
{
if(salariesIncome && tributeIncome)
message += ",";
if(salariesIncome || tributeIncome)
message += " and ";
message += Format::CreditString(balance.assetsReturns) + " based on outfits and ships";
}
message += ".";
Messages::Add(message, Messages::Importance::High, true);
accounts.AddCredits(salariesIncome + tributeIncome + balance.assetsReturns);
}

// For accounting, keep track of the player's net worth. This is for
// calculation of yearly income to determine maximum mortgage amounts.
int64_t assets = depreciation.Value(ships, date.DaysSinceEpoch());
for(const shared_ptr<Ship> &ship : ships)
assets += ship->Cargo().Value(system);

// Have the player pay salaries, mortgages, etc. and print a message that
// summarizes the payments that were made.
string message = accounts.Step(assets, Salaries(), balance.maintenanceCosts);
if(!message.empty())
Messages::Add(message, Messages::Importance::High, true);
}



// Calculate how much the player pays in daily salaries.
int64_t PlayerInfo::Salaries() const
{
Expand Down
10 changes: 6 additions & 4 deletions source/PlayerInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ class PlayerInfo {
// Apply the given changes and store them in the player's saved game file.
void AddChanges(std::list<DataNode> &changes);
// Add an event that will happen at the given date.
void AddEvent(const GameEvent &event, const Date &date);
void AddEvent(GameEvent event, const Date &date);

// Mark the player as dead, or check if they have died.
void Die(int response = 0, const std::shared_ptr<Ship> &capturer = nullptr);
Expand All @@ -113,7 +113,7 @@ class PlayerInfo {

// Get or change the current date.
const Date &GetDate() const;
void IncrementDate();
void AdvanceDate(int amount = 1);

// Get basic data about the player's starting scenario.
const CoreStartData &StartData() const noexcept;
Expand Down Expand Up @@ -375,6 +375,8 @@ class PlayerInfo {

// Check that this player's current state can be saved.
bool CanBeSaved() const;
// Handle the daily salaries and payments.
void DoAccounting();


private:
Expand Down Expand Up @@ -454,8 +456,8 @@ class PlayerInfo {
DataNode economy;
// Persons that have been killed in this player's universe:
std::vector<std::string> destroyedPersons;
// Events that are going to happen some time in the future:
std::list<GameEvent> gameEvents;
// Events that are going to happen some time in the future (sorted by date for easy chronological access):
std::multiset<GameEvent> gameEvents;

// The system and position therein to which the "orbits" system UI issued a move order.
std::pair<const System *, Point> interstellarEscortDestination;
Expand Down

0 comments on commit 9adae12

Please sign in to comment.