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

Add support for Program Modifiers #2423

Merged
merged 1 commit into from
Aug 18, 2024
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
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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if you define two PM config with different source programs that modifies the same target program, you'd apply both and I am not sure the result would be deterministic here or be the intended effect?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PMs get applied in the order they are configured and thus the effects should be deterministic.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So since "modifiers" just a new value configuration, the last one configured is the only one used at the end right? or could we do partial PM configs that could be combined? (One defines nee confidence, one defines new fundingcurve)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They would combine.

}
}
}

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
Loading