diff --git a/Source/RP0/Harmony/Administration.cs b/Source/RP0/Harmony/Administration.cs index 44ffcea7093..6ac54c4a826 100644 --- a/Source/RP0/Harmony/Administration.cs +++ b/Source/RP0/Harmony/Administration.cs @@ -293,6 +293,7 @@ internal static void Prefix_SetSelectedStrategy(Administration __instance, Admin if (wrapper.strategy is ProgramStrategy ps) { + ps.Program.ApplyProgramModifiers(); // Set best speed before we get description // This is maybe a duplicate of the work we did in CreateStrategiesList but eh. // NOTE: We have to be sure this didn't happen because we pressed a speed button. diff --git a/Source/RP0/Programs/Program.cs b/Source/RP0/Programs/Program.cs index bcf45b4cd4f..374fe8d9ddf 100644 --- a/Source/RP0/Programs/Program.cs +++ b/Source/RP0/Programs/Program.cs @@ -74,7 +74,7 @@ public static double DurationYearsCalc(Speed spd, double years) [Persistent(isPersistant = false)] public double baseFunding; - [Persistent(isPersistant = false)] + [Persistent] public string fundingCurve; [Persistent] @@ -114,7 +114,7 @@ public void SetSpeed(Speed spd) speed = spd; } - private Dictionary confidenceCosts = new Dictionary(); + public Dictionary confidenceCosts = new Dictionary(); public float GetDisplayedConfidenceCostForSpeed(Speed spd) => -(float)CurrencyUtils.Conf(TransactionReasonsRP0.ProgramActivation, -confidenceCosts[spd]); public float ConfidenceCost => confidenceCosts[speed]; public float DisplayConfidenceCost => GetDisplayedConfidenceCostForSpeed(speed); @@ -125,10 +125,10 @@ public void SetSpeed(Speed spd) [Persistent(isPersistant = false)] public string icon; - [Persistent(isPersistant = false)] + [Persistent] public double repDeltaOnCompletePerYearEarly; - [Persistent(isPersistant = false)] + [Persistent] public double repPenaltyPerYearLate; public double RepPenaltyPerYearLate => RepPenaltyPerYearLateCalc(speed, repPenaltyPerYearLate); public static double RepPenaltyPerYearLateCalc(Speed spd, double pen) @@ -140,11 +140,11 @@ public static double RepPenaltyPerYearLateCalc(Speed spd, double pen) return pen * mult; } - [Persistent(isPersistant = false)] - private float repToConfidence = -1f; + [Persistent] + public float repToConfidence = -1f; public float RepToConfidence => repToConfidence >= 0f ? repToConfidence : ProgramHandler.Settings.repToConfidence; - [Persistent(isPersistant = false)] + [Persistent] public int slots = 2; public List programsToDisableOnAccept = new List(); @@ -157,12 +157,9 @@ public static double RepPenaltyPerYearLateCalc(Speed spd, double pen) private Func _requirementsPredicate; private Func _objectivesPredicate; - public static double TotalFundingCalc(Speed spd, double funds) - { - // For now, no change in funding. - return funds * HighLogic.CurrentGame.Parameters.Career.FundsGainMultiplier; - } - public double TotalFunding => totalFunding > 0 ? totalFunding : TotalFundingCalc(speed, baseFunding); + public static double CalcTotalFunding(double funds) => funds * HighLogic.CurrentGame.Parameters.Career.FundsGainMultiplier; + + public double TotalFunding => totalFunding > 0 ? totalFunding : CalcTotalFunding(baseFunding); public bool IsComplete => completedUT != 0; @@ -208,7 +205,7 @@ public Program(Program toCopy) : this() programsToDisableOnAccept = toCopy.programsToDisableOnAccept; optionalContracts = toCopy.optionalContracts; speed = toCopy.speed; - confidenceCosts = toCopy.confidenceCosts; + confidenceCosts = new Dictionary(toCopy.confidenceCosts); repToConfidence = toCopy.repToConfidence; slots = toCopy.slots; } @@ -264,26 +261,7 @@ public void Load(ConfigNode node) optionalContracts.Add(v.name); } - cn = node.GetNode("CONFIDENCECOSTS"); - if (cn != null) - { - for (int i = 0; i < (int)Speed.MAX; ++i) - { - Speed spd = (Speed)i; - float cost = 0; - cn.TryGetValue(spd.ToString(), ref cost); - confidenceCosts[spd] = cost; - } - } - // This is back-compat and can probably go away. - // But maybe a program could be defined with no costs? - else if (confidenceCosts.Count == 0) - { - for (int i = 0; i < (int)Speed.MAX; ++i) - { - confidenceCosts[(Speed)i] = 0; - } - } + LoadConfidenceCosts(node, confidenceCosts); } public void Save(ConfigNode node) @@ -293,18 +271,19 @@ public void Save(ConfigNode node) public Program Accept() { - Confidence.Instance.AddConfidence(-confidenceCosts[speed], TransactionReasonsRP0.ProgramActivation.Stock()); - var p = new Program(this) { acceptedUT = Planetarium.GetUniversalTime(), lastPaymentUT = Planetarium.GetUniversalTime(), - totalFunding = TotalFunding, fundsPaidOut = 0, repPenaltyAssessed = 0, - fracElapsed = 0, - deadlineUT = acceptedUT + DurationYears * secsPerYear + fracElapsed = 0 }; + p.ApplyProgramModifiers(); + p.totalFunding = p.TotalFunding; + p.deadlineUT = p.acceptedUT + p.DurationYears * secsPerYear; + + Confidence.Instance.AddConfidence(-p.confidenceCosts[speed], TransactionReasonsRP0.ProgramActivation.Stock()); CareerLog.Instance?.ProgramAccepted(p); return p; @@ -502,9 +481,19 @@ public string GetDescription(bool extendedInfo) { if (programsToDisableOnAccept.Count > 0) { - text += "\nWill disable the following on accept:"; + text += "\n\nWill disable the following on accept:"; foreach (var s in programsToDisableOnAccept) - text += $"\n{ProgramHandler.PrettyPrintProgramName(s)}"; + text += $"\n* {ProgramHandler.PrettyPrintProgramName(s)}"; + } + + List pModifs = ProgramHandler.ProgramModifiers.FindAll(pm => pm.srcProgram == name); + if (pModifs.Count > 0) + { + text += "\n\nWill affect the following on accept:\n"; + foreach (ProgramModifier pm in pModifs) + { + text += pm.GetDiffString(); + } } text += $"\n\n{Localizer.Format("#rp0_Admin_Program_ConfidenceRequired", DisplayConfidenceCost.ToString("N0"))}"; @@ -569,5 +558,36 @@ public void SetBestAllowableSpeed() } public ProgramStrategy GetStrategy() => StrategySystem.Instance.Strategies.Find(s => s.Config.Name == name) as ProgramStrategy; + + /// + /// Applies all active program modifiers to current program. + /// This should NOT get called on template programs (i.e the ones in ProgramHandler.Programs) since there is currently no logic that resets them to original state. + /// + public void ApplyProgramModifiers() + { + var pms = ProgramHandler.ProgramModifiers.FindAll(pm => pm.tgtProgram == name); + foreach (ProgramModifier pm in pms) + { + if (ProgramHandler.Instance.IsProgramActiveOrCompleted(pm.srcProgram)) + { + pm.Apply(this); + } + } + } + + public static void LoadConfidenceCosts(ConfigNode node, Dictionary confidenceCosts) + { + ConfigNode cn = node.GetNode("CONFIDENCECOSTS"); + if (cn != null) + { + for (int i = 0; i < (int)Speed.MAX; ++i) + { + Speed spd = (Speed)i; + float cost = 0; + cn.TryGetValue(spd.ToString(), ref cost); + confidenceCosts[spd] = cost; + } + } + } } } diff --git a/Source/RP0/Programs/ProgramHandler.cs b/Source/RP0/Programs/ProgramHandler.cs index 729c2dff1df..2010dc6d15f 100644 --- a/Source/RP0/Programs/ProgramHandler.cs +++ b/Source/RP0/Programs/ProgramHandler.cs @@ -40,6 +40,7 @@ public class ProgramHandler : ScenarioModule public static ProgramHandlerSettings Settings { get; private set; } public static List Programs { get; private set; } public static Dictionary ProgramDict { get; private set; } + public static List ProgramModifiers { get; private set; } private static Dictionary programStrategies = new Dictionary(); private static AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly( @@ -78,6 +79,7 @@ public static void EnsurePrograms() { Programs = new List(); ProgramDict = new Dictionary(); + ProgramModifiers = new List(); foreach (ConfigNode n in GameDatabase.Instance.GetConfigNodes("RP0_PROGRAM")) { @@ -95,6 +97,12 @@ public static void EnsurePrograms() if (Strategies.StrategySystem.StrategyTypes.Count > 0) Strategies.StrategySystem.StrategyTypes.Add(t); } + + foreach (ConfigNode n in GameDatabase.Instance.GetConfigNodes("RP0_PROGRAM_MODIFIER")) + { + ProgramModifier pm = new ProgramModifier(n); + ProgramModifiers.Add(pm); + } } } @@ -390,8 +398,7 @@ private void WindowFunction(int windowID) DrawProgramSection(p); } - foreach (Program p in Programs.Where(p => !ActivePrograms.Any(p2 => p.name == p2.name) && - !CompletedPrograms.Any(p2 => p.name == p2.name))) + foreach (Program p in Programs.Where(p => !IsProgramActiveOrCompleted(p.name))) { DrawProgramSection(p); } @@ -502,6 +509,12 @@ private void DrawProgramSection(Program p) GUILayout.EndVertical(); } + public bool IsProgramActiveOrCompleted(string name) + { + return ActivePrograms.Any(p => p.name == name) || + CompletedPrograms.Any(p => p.name == name); + } + public Program ActivateProgram(string programName, Program.Speed speed) { Program p = Programs.Find(p2 => p2.name == programName); diff --git a/Source/RP0/Programs/ProgramModifier.cs b/Source/RP0/Programs/ProgramModifier.cs new file mode 100644 index 00000000000..b395f9df860 --- /dev/null +++ b/Source/RP0/Programs/ProgramModifier.cs @@ -0,0 +1,119 @@ +using KSP.Localization; +using System.Collections.Generic; +using static ConfigNode; +using static RP0.Programs.Program; + +namespace RP0.Programs +{ + public class ProgramModifier : IConfigNode + { + [Persistent] + public string srcProgram; + [Persistent] + public string tgtProgram; + [Persistent] + public double nominalDurationYears = -1; + [Persistent] + public double baseFunding = -1; + [Persistent] + public string fundingCurve = null; + [Persistent] + public double repDeltaOnCompletePerYearEarly = -1; + [Persistent] + public double repPenaltyPerYearLate = -1; + [Persistent] + public float repToConfidence = -1f; + [Persistent] + public int slots = -1; + public Dictionary confidenceCosts = new Dictionary(); + + public ProgramModifier() + { + } + + public ProgramModifier(ConfigNode n) : this() + { + Load(n); + } + + public void Load(ConfigNode node) + { + LoadObjectFromConfig(this, node); + LoadConfidenceCosts(node, confidenceCosts); + } + + public void Save(ConfigNode node) + { + CreateConfigFromObject(this, node); + } + + public void Apply(Program program) + { + if (nominalDurationYears != -1) + program.nominalDurationYears = nominalDurationYears; + + if (baseFunding != -1) + program.baseFunding = baseFunding; + + if (fundingCurve != null) + program.fundingCurve = fundingCurve; + + if (repDeltaOnCompletePerYearEarly != -1) + program.repDeltaOnCompletePerYearEarly = repDeltaOnCompletePerYearEarly; + + if (repPenaltyPerYearLate != -1) + program.repPenaltyPerYearLate = repPenaltyPerYearLate; + + if (repToConfidence != -1) + program.repToConfidence = repToConfidence; + + if (slots != -1) + program.slots = slots; + + if (confidenceCosts.Count > 0) + { + foreach (KeyValuePair kvp in confidenceCosts) + { + program.confidenceCosts[kvp.Key] = kvp.Value; + } + } + } + + public override string ToString() => $"{srcProgram} -> {tgtProgram}"; + + public string GetDiffString() + { + Program baseline = ProgramHandler.ProgramDict[tgtProgram].GetStrategy().Program; + + var sb = StringBuilderCache.Acquire(); + sb.AppendFormat("* {0}\n", baseline.title); + + const string diffFormat = " - {0}: {1} -> {2}\n"; + if (baseline.nominalDurationYears != nominalDurationYears) + sb.AppendFormat(diffFormat, "Nominal duration (years)", baseline.nominalDurationYears, nominalDurationYears); + + if (baseline.baseFunding != baseFunding) + sb.AppendFormat(diffFormat, "Total funds", CalcTotalFunding(baseline.baseFunding).ToString("N0"), CalcTotalFunding(baseFunding).ToString("N0")); + + if (baseline.fundingCurve != fundingCurve) + sb.AppendFormat(diffFormat, "Funding curve", baseline.fundingCurve, fundingCurve); + + if (baseline.slots != slots) + sb.AppendFormat(diffFormat, "Slots", baseline.slots, slots); + + if (confidenceCosts.Count > 0) + { + foreach (KeyValuePair kvp in confidenceCosts) + { + if (baseline.confidenceCosts[kvp.Key] != kvp.Value) + { + string speedTitle = Localizer.GetStringByTag("#rp0_Admin_Program_Speed" + (int)kvp.Key); + sb.AppendFormat(" - Confidence for {0}: {1} -> {2}\n", speedTitle, baseline.confidenceCosts[kvp.Key], kvp.Value); + } + } + } + + return sb.ToStringAndRelease(); + } + } +} diff --git a/Source/RP0/Programs/ProgramStrategy.cs b/Source/RP0/Programs/ProgramStrategy.cs index ee283a665dc..7836d1dc4e0 100644 --- a/Source/RP0/Programs/ProgramStrategy.cs +++ b/Source/RP0/Programs/ProgramStrategy.cs @@ -36,6 +36,10 @@ public override void OnSetupConfig() } // Create a copy so we can mess with the speed freely in the UI _program = new Program(source); + if (!_program.IsComplete && !_program.IsActive) + { + _program.ApplyProgramModifiers(); + } } } diff --git a/Source/RP0/RP0.csproj b/Source/RP0/RP0.csproj index d7d6b807ad3..999de57c968 100644 --- a/Source/RP0/RP0.csproj +++ b/Source/RP0/RP0.csproj @@ -52,6 +52,7 @@ +