Deepen faction economy and station planning
This commit is contained in:
@@ -15,23 +15,54 @@ internal sealed class StationSimulationService
|
||||
}
|
||||
|
||||
var desiredOrders = new List<DesiredMarketOrder>();
|
||||
var economy = FactionEconomyAnalyzer.Build(world, station.FactionId);
|
||||
var role = DetermineStationRole(station);
|
||||
var site = GetConstructionSiteForStation(world, station.Id);
|
||||
var waterReserve = MathF.Max(30f, station.Population * 3f);
|
||||
var refinedReserve = HasStationModules(station, "module_gen_prod_hullparts_01") ? 140f : 40f;
|
||||
var oreReserve = HasRefineryCapability(station) ? 180f : 0f;
|
||||
var shipPartsReserve = HasStationModules(station, "module_gen_prod_hullparts_01")
|
||||
&& !HasStationModules(station, "module_gen_prod_advancedelectronics_01", "module_gen_build_l_01")
|
||||
var constructionEnergyReserve = GetConstructionDemandForItem(world, site, "energycells");
|
||||
var constructionHullpartsReserve = GetConstructionDemandForItem(world, site, "hullparts");
|
||||
var constructionClayReserve = GetConstructionDemandForItem(world, site, "claytronics");
|
||||
var constructionRefinedReserve = GetConstructionDemandForItem(world, site, "refinedmetals");
|
||||
var iceReserve = role == "water" ? 260f : 0f;
|
||||
var energyReserve = role switch
|
||||
{
|
||||
"power" => 120f,
|
||||
"refinery" => 160f,
|
||||
"hullparts" => 180f,
|
||||
"claytronics" => 220f,
|
||||
"water" => 140f,
|
||||
_ => 60f,
|
||||
} + constructionEnergyReserve;
|
||||
var refinedReserve = role switch
|
||||
{
|
||||
"hullparts" => 220f,
|
||||
"shipyard" => 260f,
|
||||
"refinery" => 80f,
|
||||
_ => 0f,
|
||||
};
|
||||
var oreReserve = role == "refinery" ? 260f : 0f;
|
||||
var hullpartsReserve = MathF.Max(constructionHullpartsReserve, HasStationModules(station, "module_gen_build_l_01") ? 120f : 0f);
|
||||
var claytronicsReserve = MathF.Max(constructionClayReserve, HasStationModules(station, "module_gen_build_l_01") ? 120f : 0f);
|
||||
var shipPartsReserve = HasStationModules(station, "module_gen_build_l_01")
|
||||
&& FactionCommanderHasDirective(world, station.FactionId, "produce-military-ships")
|
||||
? 90f
|
||||
: 0f;
|
||||
|
||||
AddDemandOrder(desiredOrders, station, "water", waterReserve, valuationBase: 1.1f);
|
||||
AddDemandOrder(desiredOrders, station, "ore", oreReserve, valuationBase: 1.0f);
|
||||
AddDemandOrder(desiredOrders, station, "refinedmetals", refinedReserve, valuationBase: 1.15f);
|
||||
AddDemandOrder(desiredOrders, station, "hullparts", shipPartsReserve, valuationBase: 1.3f);
|
||||
AddDemandOrder(desiredOrders, station, "water", ScaleReserveByEconomy(economy, "water", waterReserve), valuationBase: ScaleDemandValuation(economy, "water", 1.1f));
|
||||
AddDemandOrder(desiredOrders, station, "energycells", ScaleReserveByEconomy(economy, "energycells", energyReserve), valuationBase: ScaleDemandValuation(economy, "energycells", 1.0f));
|
||||
AddDemandOrder(desiredOrders, station, "ice", ScaleReserveByEconomy(economy, "ice", iceReserve), valuationBase: ScaleDemandValuation(economy, "ice", 1.0f));
|
||||
AddDemandOrder(desiredOrders, station, "ore", ScaleReserveByEconomy(economy, "ore", oreReserve), valuationBase: ScaleDemandValuation(economy, "ore", 1.0f));
|
||||
AddDemandOrder(desiredOrders, station, "refinedmetals", ScaleReserveByEconomy(economy, "refinedmetals", MathF.Max(refinedReserve, constructionRefinedReserve)), valuationBase: ScaleDemandValuation(economy, "refinedmetals", 1.15f));
|
||||
AddDemandOrder(desiredOrders, station, "hullparts", ScaleReserveByEconomy(economy, "hullparts", hullpartsReserve + shipPartsReserve), valuationBase: ScaleDemandValuation(economy, "hullparts", 1.3f));
|
||||
AddDemandOrder(desiredOrders, station, "claytronics", ScaleReserveByEconomy(economy, "claytronics", claytronicsReserve), valuationBase: ScaleDemandValuation(economy, "claytronics", 1.35f));
|
||||
|
||||
AddSupplyOrder(desiredOrders, station, "water", waterReserve * 1.5f, reserveFloor: waterReserve, valuationBase: 0.65f);
|
||||
AddSupplyOrder(desiredOrders, station, "ore", oreReserve * 1.4f, reserveFloor: oreReserve, valuationBase: 0.7f);
|
||||
AddSupplyOrder(desiredOrders, station, "refinedmetals", refinedReserve * 1.4f, reserveFloor: refinedReserve, valuationBase: 0.95f);
|
||||
AddSupplyOrder(desiredOrders, station, "water", ScaleSupplyTriggerByEconomy(economy, "water", waterReserve * 1.5f), reserveFloor: waterReserve, valuationBase: ScaleSupplyValuation(economy, "water", 0.65f));
|
||||
AddSupplyOrder(desiredOrders, station, "energycells", ScaleSupplyTriggerByEconomy(economy, "energycells", energyReserve * 1.4f), reserveFloor: energyReserve, valuationBase: ScaleSupplyValuation(economy, "energycells", 0.7f));
|
||||
AddSupplyOrder(desiredOrders, station, "ice", ScaleSupplyTriggerByEconomy(economy, "ice", iceReserve * 1.4f), reserveFloor: iceReserve, valuationBase: ScaleSupplyValuation(economy, "ice", 0.5f));
|
||||
AddSupplyOrder(desiredOrders, station, "ore", ScaleSupplyTriggerByEconomy(economy, "ore", oreReserve * 1.4f), reserveFloor: oreReserve, valuationBase: ScaleSupplyValuation(economy, "ore", 0.7f));
|
||||
AddSupplyOrder(desiredOrders, station, "refinedmetals", ScaleSupplyTriggerByEconomy(economy, "refinedmetals", MathF.Max(refinedReserve, constructionRefinedReserve) * 1.4f), reserveFloor: MathF.Max(refinedReserve, constructionRefinedReserve), valuationBase: ScaleSupplyValuation(economy, "refinedmetals", 0.95f));
|
||||
AddSupplyOrder(desiredOrders, station, "hullparts", ScaleSupplyTriggerByEconomy(economy, "hullparts", MathF.Max(hullpartsReserve * 1.35f, hullpartsReserve + 40f)), reserveFloor: hullpartsReserve, valuationBase: ScaleSupplyValuation(economy, "hullparts", 1.05f));
|
||||
AddSupplyOrder(desiredOrders, station, "claytronics", ScaleSupplyTriggerByEconomy(economy, "claytronics", MathF.Max(claytronicsReserve * 1.35f, claytronicsReserve + 30f)), reserveFloor: claytronicsReserve, valuationBase: ScaleSupplyValuation(economy, "claytronics", 1.1f));
|
||||
|
||||
ReconcileStationMarketOrders(world, station, desiredOrders);
|
||||
}
|
||||
@@ -112,11 +143,11 @@ internal sealed class StationSimulationService
|
||||
.OrderByDescending(recipe => GetStationRecipePriority(world, station, recipe))
|
||||
.FirstOrDefault(recipe => CanRunRecipe(world, station, recipe));
|
||||
|
||||
private static string? GetStationProductionLaneKey(SimulationWorld world, RecipeDefinition recipe) =>
|
||||
internal static string? GetStationProductionLaneKey(SimulationWorld world, RecipeDefinition recipe) =>
|
||||
recipe.RequiredModules.FirstOrDefault(moduleId =>
|
||||
world.ModuleDefinitions.TryGetValue(moduleId, out var def) && !string.IsNullOrEmpty(def.ProductionMode));
|
||||
|
||||
private static float GetStationProductionThroughput(SimulationWorld world, StationRuntime station, RecipeDefinition recipe)
|
||||
internal static float GetStationProductionThroughput(SimulationWorld world, StationRuntime station, RecipeDefinition recipe)
|
||||
{
|
||||
var laneModuleId = GetStationProductionLaneKey(world, recipe);
|
||||
if (laneModuleId is null)
|
||||
@@ -180,7 +211,7 @@ internal sealed class StationSimulationService
|
||||
};
|
||||
}
|
||||
|
||||
private static bool RecipeAppliesToStation(StationRuntime station, RecipeDefinition recipe)
|
||||
internal static bool RecipeAppliesToStation(StationRuntime station, RecipeDefinition recipe)
|
||||
{
|
||||
var categoryMatch = string.Equals(recipe.FacilityCategory, "station", StringComparison.Ordinal)
|
||||
|| string.Equals(recipe.FacilityCategory, "farm", StringComparison.Ordinal)
|
||||
@@ -240,6 +271,71 @@ internal sealed class StationSimulationService
|
||||
private static bool HasRefineryCapability(StationRuntime station) =>
|
||||
HasStationModules(station, "module_gen_prod_refinedmetals_01", "module_gen_prod_energycells_01", "module_arg_stor_solid_m_01");
|
||||
|
||||
internal static string NormalizeStationObjective(string? objective)
|
||||
{
|
||||
return objective?.Trim().ToLowerInvariant() switch
|
||||
{
|
||||
"power" or "energy" or "energycells" => "power",
|
||||
"water" or "ice-refinery" => "water",
|
||||
"refinery" or "refinedmetals" => "refinery",
|
||||
"hullparts" or "hull" => "hullparts",
|
||||
"claytronics" or "clay" => "claytronics",
|
||||
"shipyard" or "ship-production" => "shipyard",
|
||||
_ => "general",
|
||||
};
|
||||
}
|
||||
|
||||
internal static string DetermineStationRole(StationRuntime station)
|
||||
{
|
||||
var objective = NormalizeStationObjective(station.Objective);
|
||||
if (!string.Equals(objective, "general", StringComparison.Ordinal))
|
||||
{
|
||||
return objective;
|
||||
}
|
||||
|
||||
if (HasStationModules(station, "module_gen_build_l_01"))
|
||||
{
|
||||
return "shipyard";
|
||||
}
|
||||
|
||||
if (HasStationModules(station, "module_gen_prod_water_01"))
|
||||
{
|
||||
return "water";
|
||||
}
|
||||
|
||||
if (HasStationModules(station, "module_gen_prod_claytronics_01"))
|
||||
{
|
||||
return "claytronics";
|
||||
}
|
||||
|
||||
if (HasStationModules(station, "module_gen_prod_hullparts_01"))
|
||||
{
|
||||
return "hullparts";
|
||||
}
|
||||
|
||||
if (HasStationModules(station, "module_gen_prod_refinedmetals_01"))
|
||||
{
|
||||
return "refinery";
|
||||
}
|
||||
|
||||
if (HasStationModules(station, "module_gen_prod_energycells_01"))
|
||||
{
|
||||
return "power";
|
||||
}
|
||||
|
||||
return "general";
|
||||
}
|
||||
|
||||
private static float GetConstructionDemandForItem(SimulationWorld world, ConstructionSiteRuntime? site, string itemId)
|
||||
{
|
||||
if (site is null || !site.RequiredItems.TryGetValue(itemId, out var required))
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
return MathF.Max(0f, required - GetConstructionDeliveredAmount(world, site, itemId));
|
||||
}
|
||||
|
||||
private static void AddDemandOrder(ICollection<DesiredMarketOrder> desiredOrders, StationRuntime station, string itemId, float targetAmount, float valuationBase)
|
||||
{
|
||||
var current = GetInventoryAmount(station.Inventory, itemId);
|
||||
@@ -267,7 +363,9 @@ internal sealed class StationSimulationService
|
||||
return;
|
||||
}
|
||||
|
||||
desiredOrders.Add(new DesiredMarketOrder(MarketOrderKinds.Sell, itemId, surplus, valuationBase, reserveFloor));
|
||||
var surplusRatio = triggerAmount <= 0.01f ? 1f : MathF.Min(1f, surplus / triggerAmount);
|
||||
var liquidationValuation = MathF.Max(0.05f, valuationBase * (1f - (0.85f * surplusRatio)));
|
||||
desiredOrders.Add(new DesiredMarketOrder(MarketOrderKinds.Sell, itemId, surplus, liquidationValuation, reserveFloor));
|
||||
}
|
||||
|
||||
private static void ReconcileStationMarketOrders(SimulationWorld world, StationRuntime station, IReadOnlyCollection<DesiredMarketOrder> desiredOrders)
|
||||
@@ -330,6 +428,50 @@ internal sealed class StationSimulationService
|
||||
return world.Systems.Count(system => FactionControlsSystem(world, factionId, system.Definition.Id));
|
||||
}
|
||||
|
||||
private static float ScaleReserveByEconomy(FactionEconomySnapshot economy, string itemId, float baseReserve)
|
||||
{
|
||||
var commodity = economy.GetCommodity(itemId);
|
||||
if (float.IsPositiveInfinity(commodity.ShortageHorizonSeconds))
|
||||
{
|
||||
return MathF.Max(0f, baseReserve);
|
||||
}
|
||||
|
||||
return commodity.ShortageHorizonSeconds < 180f
|
||||
? baseReserve * 1.5f
|
||||
: commodity.ShortageHorizonSeconds < 360f
|
||||
? baseReserve * 1.2f
|
||||
: baseReserve;
|
||||
}
|
||||
|
||||
private static float ScaleSupplyTriggerByEconomy(FactionEconomySnapshot economy, string itemId, float baseTrigger)
|
||||
{
|
||||
var commodity = economy.GetCommodity(itemId);
|
||||
return commodity.NetRatePerSecond < -0.01f ? baseTrigger * 1.2f : baseTrigger;
|
||||
}
|
||||
|
||||
private static float ScaleDemandValuation(FactionEconomySnapshot economy, string itemId, float baseValuation)
|
||||
{
|
||||
var commodity = economy.GetCommodity(itemId);
|
||||
if (float.IsPositiveInfinity(commodity.ShortageHorizonSeconds))
|
||||
{
|
||||
return commodity.ProductionRatePerSecond > 0.01f ? baseValuation : baseValuation * 1.3f;
|
||||
}
|
||||
|
||||
return commodity.ShortageHorizonSeconds < 180f
|
||||
? baseValuation * 1.5f
|
||||
: commodity.ShortageHorizonSeconds < 360f
|
||||
? baseValuation * 1.25f
|
||||
: baseValuation;
|
||||
}
|
||||
|
||||
private static float ScaleSupplyValuation(FactionEconomySnapshot economy, string itemId, float baseValuation)
|
||||
{
|
||||
var commodity = economy.GetCommodity(itemId);
|
||||
return commodity.NetRatePerSecond > 0.01f && commodity.ShortageHorizonSeconds > 600f
|
||||
? baseValuation * 0.75f
|
||||
: baseValuation;
|
||||
}
|
||||
|
||||
private static bool FactionControlsSystem(SimulationWorld world, string factionId, string systemId)
|
||||
{
|
||||
var totalLagrangePoints = world.Celestials.Count(node =>
|
||||
|
||||
Reference in New Issue
Block a user