Skip to content

Commit

Permalink
Add support for Program Modifiers (#2423)
Browse files Browse the repository at this point in the history
These will change the funding, duration etc values on a program if another gets accepted.
  • Loading branch information
siimav authored Aug 18, 2024
1 parent 25688df commit a6ea8bf
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 43 deletions.
2 changes: 2 additions & 0 deletions Source/RP0/Harmony/Administration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,8 @@ internal static void Prefix_SetSelectedStrategy(Administration __instance, Admin

if (wrapper.strategy is ProgramStrategy ps)
{
if (!ps.Program.IsActive && !ps.Program.IsComplete)
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.
Expand Down
103 changes: 62 additions & 41 deletions Source/RP0/Programs/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -114,7 +114,7 @@ public void SetSpeed(Speed spd)
speed = spd;
}

private Dictionary<Speed, float> confidenceCosts = new Dictionary<Speed, float>();
public Dictionary<Speed, float> confidenceCosts = new Dictionary<Speed, float>();
public float GetDisplayedConfidenceCostForSpeed(Speed spd) => -(float)CurrencyUtils.Conf(TransactionReasonsRP0.ProgramActivation, -confidenceCosts[spd]);
public float ConfidenceCost => confidenceCosts[speed];
public float DisplayConfidenceCost => GetDisplayedConfidenceCostForSpeed(speed);
Expand All @@ -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)
Expand All @@ -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<string> programsToDisableOnAccept = new List<string>();
Expand All @@ -157,12 +157,9 @@ public static double RepPenaltyPerYearLateCalc(Speed spd, double pen)
private Func<bool> _requirementsPredicate;
private Func<bool> _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;

Expand Down Expand Up @@ -208,7 +205,7 @@ public Program(Program toCopy) : this()
programsToDisableOnAccept = toCopy.programsToDisableOnAccept;
optionalContracts = toCopy.optionalContracts;
speed = toCopy.speed;
confidenceCosts = toCopy.confidenceCosts;
confidenceCosts = new Dictionary<Speed, float>(toCopy.confidenceCosts);
repToConfidence = toCopy.repToConfidence;
slots = toCopy.slots;
}
Expand Down Expand Up @@ -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)
Expand All @@ -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;
Expand Down Expand Up @@ -502,9 +481,20 @@ 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<ProgramModifier> pModifs = ProgramHandler.ProgramModifiers
.FindAll(pm => pm.srcProgram == name && !ProgramHandler.Instance.IsProgramActiveOrCompleted(pm.tgtProgram));
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"))}";
Expand Down Expand Up @@ -569,5 +559,36 @@ public void SetBestAllowableSpeed()
}

public ProgramStrategy GetStrategy() => StrategySystem.Instance.Strategies.Find(s => s.Config.Name == name) as ProgramStrategy;

/// <summary>
/// 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.
/// </summary>
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<Speed, float> 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;
}
}
}
}
}
17 changes: 15 additions & 2 deletions Source/RP0/Programs/ProgramHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public class ProgramHandler : ScenarioModule
public static ProgramHandlerSettings Settings { get; private set; }
public static List<Program> Programs { get; private set; }
public static Dictionary<string, Program> ProgramDict { get; private set; }
public static List<ProgramModifier> ProgramModifiers { get; private set; }

private static Dictionary<string, Type> programStrategies = new Dictionary<string, Type>();
private static AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
Expand Down Expand Up @@ -78,6 +79,7 @@ public static void EnsurePrograms()
{
Programs = new List<Program>();
ProgramDict = new Dictionary<string, Program>();
ProgramModifiers = new List<ProgramModifier>();

foreach (ConfigNode n in GameDatabase.Instance.GetConfigNodes("RP0_PROGRAM"))
{
Expand All @@ -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);
}
}
}

Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand Down
119 changes: 119 additions & 0 deletions Source/RP0/Programs/ProgramModifier.cs
Original file line number Diff line number Diff line change
@@ -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<Speed, float> confidenceCosts = new Dictionary<Speed, float>();

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<Speed, float> 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 (nominalDurationYears != -1 && baseline.nominalDurationYears != nominalDurationYears)
sb.AppendFormat(diffFormat, "Nominal duration (years)", baseline.nominalDurationYears, nominalDurationYears);

if (baseFunding != -1 && baseline.baseFunding != baseFunding)
sb.AppendFormat(diffFormat, "Total funds", CalcTotalFunding(baseline.baseFunding).ToString("N0"), CalcTotalFunding(baseFunding).ToString("N0"));

if (fundingCurve != null && baseline.fundingCurve != fundingCurve)
sb.AppendFormat(diffFormat, "Funding curve", baseline.fundingCurve, fundingCurve);

if (slots != -1 && baseline.slots != slots)
sb.AppendFormat(diffFormat, "Slots", baseline.slots, slots);

if (confidenceCosts.Count > 0)
{
foreach (KeyValuePair<Speed, float> 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();
}
}
}
4 changes: 4 additions & 0 deletions Source/RP0/Programs/ProgramStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
}

Expand Down
1 change: 1 addition & 0 deletions Source/RP0/RP0.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
<Compile Include="Harmony\RealAntennas.cs" />
<Compile Include="ModIntegrations\KSCSwitcherInterop.cs" />
<Compile Include="ModIntegrations\TFInterop.cs" />
<Compile Include="Programs\ProgramModifier.cs" />
<Compile Include="Singletons\LeaderNotifications.cs" />
<Compile Include="SpaceCenter\Projects\HireStaffProject.cs" />
<Compile Include="SpaceCenter\Projects\VesselRepairProject.cs" />
Expand Down

0 comments on commit a6ea8bf

Please sign in to comment.