From 954b814ac81f2c7c2f4f6966d898cb6b423fa4c6 Mon Sep 17 00:00:00 2001 From: Amazinite Date: Sat, 18 May 2024 17:00:27 -0400 Subject: [PATCH] feat(enhancement): Allow GameActions to give the player debt (#9826) --- source/Account.cpp | 41 +++++++++++++++++++++++++++++++---------- source/Account.h | 5 ++++- source/GameAction.cpp | 30 ++++++++++++++++++++++++++++++ source/GameAction.h | 11 +++++++++++ source/Mortgage.cpp | 21 ++++++++++++++++++--- source/Mortgage.h | 9 +++++++-- source/PlayerInfo.cpp | 4 ++++ source/Ship.cpp | 2 +- 8 files changed, 106 insertions(+), 17 deletions(-) diff --git a/source/Account.cpp b/source/Account.cpp index 898e7fe69fa1..406dc7df5b37 100644 --- a/source/Account.cpp +++ b/source/Account.cpp @@ -207,6 +207,7 @@ string Account::Step(int64_t assets, int64_t salaries, int64_t maintenance) // or skipped completely (accruing interest and reducing your credit score). int64_t mortgagesPaid = 0; int64_t finesPaid = 0; + int64_t debtPaid = 0; for(Mortgage &mortgage : mortgages) { int64_t payment = mortgage.Payment(); @@ -221,11 +222,13 @@ string Account::Step(int64_t assets, int64_t salaries, int64_t maintenance) { payment = mortgage.MakePayment(); credits -= payment; - // For the status text, keep track of whether this is a mortgage or a fine. + // For the status text, keep track of whether this is a mortgage, fine, or debt. if(mortgage.Type() == "Mortgage") mortgagesPaid += payment; - else + else if(mortgage.Type() == "Fine") finesPaid += payment; + else + debtPaid += payment; } assets -= mortgage.Principal(); } @@ -249,7 +252,7 @@ string Account::Step(int64_t assets, int64_t salaries, int64_t maintenance) creditScore = max(200, min(800, creditScore + (missedPayment ? -5 : 1))); // If you didn't make any payments, no need to continue further. - if(!(salariesPaid + maintenancePaid + mortgagesPaid + finesPaid)) + if(!(salariesPaid + maintenancePaid + mortgagesPaid + finesPaid + debtPaid)) return out.str(); else if(missedPayment) out << " "; @@ -265,6 +268,8 @@ string Account::Step(int64_t assets, int64_t salaries, int64_t maintenance) typesPaid["mortgages"] = mortgagesPaid; if(finesPaid) typesPaid["fines"] = finesPaid; + if(debtPaid) + typesPaid["debt"] = debtPaid; // If you made payments of three or more types, the punctuation needs to // include commas, so just handle that separately here. @@ -282,15 +287,18 @@ string Account::Step(int64_t assets, int64_t salaries, int64_t maintenance) { if(salariesPaid) out << Format::CreditString(salariesPaid) << " in crew salaries" - << ((mortgagesPaid || finesPaid || maintenancePaid) ? " and " : "."); + << ((mortgagesPaid || finesPaid || debtPaid || maintenancePaid) ? " and " : "."); if(maintenancePaid) out << Format::CreditString(maintenancePaid) << " in maintenance" - << ((mortgagesPaid || finesPaid) ? " and " : "."); + << ((mortgagesPaid || finesPaid || debtPaid) ? " and " : "."); if(mortgagesPaid) out << Format::CreditString(mortgagesPaid) << " in mortgages" - << (finesPaid ? " and " : "."); + << ((finesPaid || debtPaid) ? " and " : "."); if(finesPaid) - out << Format::CreditString(finesPaid) << " in fines."; + out << Format::CreditString(finesPaid) << " in fines" + << (debtPaid ? " and " : "."); + if(debtPaid) + out << Format::CreditString(debtPaid) << " in debt."; } return out.str(); } @@ -373,7 +381,7 @@ const vector &Account::Mortgages() const // your credit score. void Account::AddMortgage(int64_t principal) { - mortgages.emplace_back(principal, creditScore); + mortgages.emplace_back("Mortgage", principal, creditScore); credits += principal; } @@ -382,7 +390,19 @@ void Account::AddMortgage(int64_t principal) // Add a "fine" with a high, fixed interest rate and a short term. void Account::AddFine(int64_t amount) { - mortgages.emplace_back(amount, 0, 60); + mortgages.emplace_back("Fine", amount, 0, 60); +} + + + +// Add debt with the given interest rate and term. If no interest rate is +// given then the player's credit score is used to determine the interest rate. +void Account::AddDebt(int64_t amount, optional interest, int term) +{ + if(interest) + mortgages.emplace_back("Debt", amount, *interest, term); + else + mortgages.emplace_back("Debt", amount, creditScore, term); } @@ -424,7 +444,8 @@ int Account::CreditScore() const -// Get the total amount owed for "Mortgage", "Fine", or both. +// Get the total amount owed for a specific type of mortgage, or all +// mortgages if a blank string is provided. int64_t Account::TotalDebt(const string &type) const { int64_t total = 0; diff --git a/source/Account.h b/source/Account.h index 4e99246eacd8..5c30a4025b6a 100644 --- a/source/Account.h +++ b/source/Account.h @@ -20,6 +20,7 @@ this program. If not, see . #include #include +#include #include #include @@ -60,13 +61,15 @@ class Account { const std::vector &Mortgages() const; void AddMortgage(int64_t principal); void AddFine(int64_t amount); + void AddDebt(int64_t amount, std::optional interest, int term); int64_t Prequalify() const; // Assets: int64_t NetWorth() const; // Find out the player's credit rating. int CreditScore() const; - // Get the total amount owed for "Mortgage", "Fine", or both. + // Get the total amount owed for a specific type of mortgage, or all + // mortgages if a blank string is provided. int64_t TotalDebt(const std::string &type = "") const; diff --git a/source/GameAction.cpp b/source/GameAction.cpp index 240173d734b9..4ec522bd9b4c 100644 --- a/source/GameAction.cpp +++ b/source/GameAction.cpp @@ -185,6 +185,21 @@ void GameAction::LoadSingle(const DataNode &child) else child.PrintTrace("Error: Skipping invalid \"fine\" with non-positive value:"); } + else if(key == "debt" && hasValue) + { + GameAction::Debt &debtEntry = debt.emplace_back(max(0, child.Value(1))); + for(const DataNode &grand : child) + { + const string &grandKey = grand.Token(0); + bool grandHasValue = (grand.Size() > 1); + if(grandKey == "term" && grandHasValue) + debtEntry.term = max(1, grand.Value(1)); + else if(grandKey == "interest" && grandHasValue) + debtEntry.interest = clamp(grand.Value(1), 0., 0.999); + else + grand.PrintTrace("Error: Skipping unrecognized \"debt\" attribute:"); + } + } else if(key == "event" && hasValue) { int minDays = (child.Size() >= 3 ? child.Value(2) : 1); @@ -240,6 +255,17 @@ void GameAction::Save(DataWriter &out) const out.Write("payment", payment); if(fine) out.Write("fine", fine); + for(auto &&debtEntry : debt) + { + out.Write("debt", debtEntry.amount); + out.BeginChild(); + { + if(debtEntry.interest) + out.Write("interest", *debtEntry.interest); + out.Write("term", debtEntry.term); + } + out.EndChild(); + } for(auto &&it : events) out.Write("event", it.first->Name(), it.second.first, it.second.second); for(const string &name : fail) @@ -363,6 +389,8 @@ void GameAction::Do(PlayerInfo &player, UI *ui, const Mission *caller) const } if(fine) player.Accounts().AddFine(fine); + for(const auto &debtEntry : debt) + player.Accounts().AddDebt(debtEntry.amount, debtEntry.interest, debtEntry.term); for(const auto &it : events) player.AddEvent(*it.first, player.GetDate() + it.second.first); @@ -424,6 +452,8 @@ GameAction GameAction::Instantiate(map &subs, int jumps, int pay if(result.fine) subs[""] = Format::CreditString(result.fine); + result.debt = debt; + if(!logText.empty()) result.logText = Format::Replace(logText, subs); for(auto &&it : specialLogText) diff --git a/source/GameAction.h b/source/GameAction.h index 1bca4258919e..65e8084dee88 100644 --- a/source/GameAction.h +++ b/source/GameAction.h @@ -75,6 +75,16 @@ class GameAction { GameAction Instantiate(std::map &subs, int jumps, int payload) const; +private: + struct Debt { + Debt(int64_t amount) : amount(amount) {} + + int64_t amount = 0; + std::optional interest; + int term = 365; + }; + + private: bool isEmpty = true; std::string logText; @@ -87,6 +97,7 @@ class GameAction { int64_t payment = 0; int64_t paymentMultiplier = 0; int64_t fine = 0; + std::vector debt; std::optional music; diff --git a/source/Mortgage.cpp b/source/Mortgage.cpp index 1754e0f60d0b..8a78c4034cf6 100644 --- a/source/Mortgage.cpp +++ b/source/Mortgage.cpp @@ -46,8 +46,8 @@ int64_t Mortgage::Maximum(int64_t annualRevenue, int creditScore, double current // Create a new mortgage of the given amount. -Mortgage::Mortgage(int64_t principal, int creditScore, int term) - : type(creditScore <= 0 ? "Fine" : "Mortgage"), +Mortgage::Mortgage(string type, int64_t principal, int creditScore, int term) + : type(std::move(type)), principal(principal), interest((600 - creditScore / 2) * .00001), interestString("0." + to_string(600 - creditScore / 2) + "%"), @@ -57,6 +57,20 @@ Mortgage::Mortgage(int64_t principal, int creditScore, int term) +// Create a mortgage with a specific interest rate instead of using the player's +// credit score. Due to how the class is set up, the interest rate must currently +// be within the range [0, 1). +Mortgage::Mortgage(string type, int64_t principal, double interest, int term) + : type(std::move(type)), + principal(principal), + interest(interest * .01), + interestString("0." + to_string(static_cast(1000. * interest)) + "%"), + term(term) +{ +} + + + // Construct and Load() at the same time. Mortgage::Mortgage(const DataNode &node) { @@ -138,7 +152,8 @@ int64_t Mortgage::PayExtra(int64_t amount) // The type is "Mortgage" if this is a mortgage you applied for from a bank, -// and "Fine" if this is a fine imposed on you for illegal activities. +// "Fine" if this is a fine imposed on you for illegal activities, and +// "Debt" if this is debt given to you by a mission. const string &Mortgage::Type() const { return type; diff --git a/source/Mortgage.h b/source/Mortgage.h index 766fa211b4ac..a809ea05ea8f 100644 --- a/source/Mortgage.h +++ b/source/Mortgage.h @@ -40,7 +40,11 @@ class Mortgage { Mortgage() = default; // Create a new mortgage of the given amount. If this is a fine, set the // credit score to zero for a higher interest rate. - Mortgage(int64_t principal, int creditScore, int term = 365); + Mortgage(std::string type, int64_t principal, int creditScore, int term = 365); + // Create a mortgage with a specific interest rate instead of using the player's + // credit score. Due to how the class is set up, the interest rate must currently + // be within the range [0, 1). + Mortgage(std::string type, int64_t principal, double interest, int term = 365); // Construct and Load() at the same time. Mortgage(const DataNode &node); @@ -58,7 +62,8 @@ class Mortgage { int64_t PayExtra(int64_t amount); // The type is "Mortgage" if this is a mortgage you applied for from a bank, - // and "Fine" if this is a fine imposed on you for illegal activities. + // "Fine" if this is a fine imposed on you for illegal activities, and + // "Debt" if this is debt given to you by a mission. const std::string &Type() const; // Get the remaining mortgage principal. int64_t Principal() const; diff --git a/source/PlayerInfo.cpp b/source/PlayerInfo.cpp index 6ef5ed8dfd56..8abaf095b39d 100644 --- a/source/PlayerInfo.cpp +++ b/source/PlayerInfo.cpp @@ -3177,6 +3177,10 @@ void PlayerInfo::RegisterDerivedConditions() unpaidFinesProvider.SetGetFunction([this](const string &name) { return min(limit, accounts.TotalDebt("Fine")); }); + auto &&unpaidDebtsProvider = conditions.GetProviderNamed("unpaid debts"); + unpaidDebtsProvider.SetGetFunction([this](const string &name) { + return min(limit, accounts.TotalDebt("Debt")); }); + auto &&unpaidSalariesProvider = conditions.GetProviderNamed("unpaid salaries"); unpaidSalariesProvider.SetGetFunction([this](const string &name) { return min(limit, accounts.CrewSalariesOwed()); }); diff --git a/source/Ship.cpp b/source/Ship.cpp index fcab78dda27e..169b84490c5e 100644 --- a/source/Ship.cpp +++ b/source/Ship.cpp @@ -2979,7 +2979,7 @@ double Ship::MaxVelocity(bool withAfterburner) const // v = thrust / drag double thrust = attributes.Get("thrust"); double afterburnerThrust = attributes.Get("afterburner thrust"); - return (thrust ? thrust + afterburnerThrust * withAfterburner: afterburnerThrust) / Drag(); + return (thrust ? thrust + afterburnerThrust * withAfterburner : afterburnerThrust) / Drag(); }