209 lines
6.7 KiB
C#
209 lines
6.7 KiB
C#
using System.Text.Json;
|
|
using SpaceGame.Simulation.Api.Data;
|
|
|
|
namespace SpaceGame.Simulation.Api.Simulation;
|
|
|
|
public sealed class ScenarioLoader
|
|
{
|
|
private readonly string _dataRoot;
|
|
private readonly JsonSerializerOptions _jsonOptions = new()
|
|
{
|
|
PropertyNameCaseInsensitive = true,
|
|
};
|
|
|
|
public ScenarioLoader(string contentRootPath)
|
|
{
|
|
_dataRoot = Path.GetFullPath(Path.Combine(contentRootPath, "..", "..", "shared", "data"));
|
|
}
|
|
|
|
public SimulationWorld Load()
|
|
{
|
|
var systems = Read<List<SolarSystemDefinition>>("systems.json");
|
|
var scenario = Read<ScenarioDefinition>("scenario.json");
|
|
var ships = Read<List<ShipDefinition>>("ships.json");
|
|
var constructibles = Read<List<ConstructibleDefinition>>("constructibles.json");
|
|
var balance = Read<BalanceDefinition>("balance.json");
|
|
|
|
var shipDefinitions = ships.ToDictionary((definition) => definition.Id, StringComparer.Ordinal);
|
|
var constructibleDefinitions = constructibles.ToDictionary((definition) => definition.Id, StringComparer.Ordinal);
|
|
var systemRuntimes = systems
|
|
.Select((definition) => new SystemRuntime
|
|
{
|
|
Definition = definition,
|
|
Position = ToVector(definition.Position),
|
|
})
|
|
.ToList();
|
|
var systemsById = systemRuntimes.ToDictionary((system) => system.Definition.Id, StringComparer.Ordinal);
|
|
|
|
var nodes = new List<ResourceNodeRuntime>();
|
|
var nodeIdCounter = 0;
|
|
foreach (var system in systemRuntimes)
|
|
{
|
|
foreach (var node in system.Definition.ResourceNodes)
|
|
{
|
|
nodes.Add(new ResourceNodeRuntime
|
|
{
|
|
Id = $"node-{++nodeIdCounter}",
|
|
SystemId = system.Definition.Id,
|
|
Position = new Vector3(
|
|
system.Position.X + (MathF.Cos(node.Angle) * node.RadiusOffset),
|
|
balance.YPlane,
|
|
system.Position.Z + (MathF.Sin(node.Angle) * node.RadiusOffset)),
|
|
ItemId = node.ItemId,
|
|
OreRemaining = node.OreAmount,
|
|
MaxOre = node.OreAmount,
|
|
});
|
|
}
|
|
}
|
|
|
|
var stations = new List<StationRuntime>();
|
|
var stationIdCounter = 0;
|
|
foreach (var plan in scenario.InitialStations)
|
|
{
|
|
if (!constructibleDefinitions.TryGetValue(plan.ConstructibleId, out var definition) || !systemsById.TryGetValue(plan.SystemId, out var system))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
stations.Add(new StationRuntime
|
|
{
|
|
Id = $"station-{++stationIdCounter}",
|
|
SystemId = system.Definition.Id,
|
|
Definition = definition,
|
|
Position = ResolveStationPosition(system, plan, balance),
|
|
FactionId = plan.FactionId ?? "sol-dominion",
|
|
OreStored = definition.Category == "refining" ? 120f : 0f,
|
|
RefinedStock = definition.Category == "shipyard" ? 180f : 40f,
|
|
});
|
|
}
|
|
|
|
var refinery = stations.FirstOrDefault((station) =>
|
|
station.Definition.Category == "refining" && station.SystemId == scenario.MiningDefaults.RefinerySystemId)
|
|
?? stations.FirstOrDefault((station) => station.Definition.Category == "refining");
|
|
|
|
var patrolRoutes = scenario.PatrolRoutes.ToDictionary(
|
|
(route) => route.SystemId,
|
|
(route) => route.Points.Select(ToVector).ToList(),
|
|
StringComparer.Ordinal);
|
|
|
|
var shipsRuntime = new List<ShipRuntime>();
|
|
var shipIdCounter = 0;
|
|
foreach (var formation in scenario.ShipFormations)
|
|
{
|
|
if (!shipDefinitions.TryGetValue(formation.ShipId, out var definition))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (var index = 0; index < formation.Count; index += 1)
|
|
{
|
|
var offset = new Vector3((index % 3) * 18f, balance.YPlane, (index / 3) * 18f);
|
|
var position = Add(ToVector(formation.Center), offset);
|
|
shipsRuntime.Add(new ShipRuntime
|
|
{
|
|
Id = $"ship-{++shipIdCounter}",
|
|
SystemId = formation.SystemId,
|
|
Definition = definition,
|
|
FactionId = formation.FactionId ?? "sol-dominion",
|
|
Position = position,
|
|
TargetPosition = position,
|
|
DefaultBehavior = CreateBehavior(definition, formation.SystemId, scenario, patrolRoutes, refinery),
|
|
ControllerTask = new ControllerTaskRuntime { Kind = "idle", Threshold = balance.ArrivalThreshold },
|
|
Health = definition.MaxHealth,
|
|
});
|
|
}
|
|
}
|
|
|
|
var factions = new List<FactionRuntime>
|
|
{
|
|
new()
|
|
{
|
|
Id = "sol-dominion",
|
|
Label = "Sol Dominion",
|
|
Color = "#7ed4ff",
|
|
Credits = 240f,
|
|
},
|
|
};
|
|
|
|
return new SimulationWorld
|
|
{
|
|
Label = "Split Viewer / Simulation World",
|
|
Seed = 1,
|
|
Balance = balance,
|
|
Systems = systemRuntimes,
|
|
Nodes = nodes,
|
|
Stations = stations,
|
|
Ships = shipsRuntime,
|
|
Factions = factions,
|
|
ShipDefinitions = shipDefinitions,
|
|
GeneratedAtUtc = DateTimeOffset.UtcNow,
|
|
};
|
|
}
|
|
|
|
private T Read<T>(string fileName)
|
|
{
|
|
var path = Path.Combine(_dataRoot, fileName);
|
|
var json = File.ReadAllText(path);
|
|
return JsonSerializer.Deserialize<T>(json, _jsonOptions)
|
|
?? throw new InvalidOperationException($"Unable to read {fileName}.");
|
|
}
|
|
|
|
private static DefaultBehaviorRuntime CreateBehavior(
|
|
ShipDefinition definition,
|
|
string systemId,
|
|
ScenarioDefinition scenario,
|
|
IReadOnlyDictionary<string, List<Vector3>> patrolRoutes,
|
|
StationRuntime? refinery)
|
|
{
|
|
if (definition.Role == "mining" && refinery is not null)
|
|
{
|
|
return new DefaultBehaviorRuntime
|
|
{
|
|
Kind = "auto-mine",
|
|
AreaSystemId = scenario.MiningDefaults.NodeSystemId,
|
|
RefineryId = refinery.Id,
|
|
Phase = "travel-to-node",
|
|
};
|
|
}
|
|
|
|
if (definition.Role == "military" && patrolRoutes.TryGetValue(systemId, out var route))
|
|
{
|
|
return new DefaultBehaviorRuntime
|
|
{
|
|
Kind = "patrol",
|
|
PatrolPoints = route,
|
|
PatrolIndex = 0,
|
|
};
|
|
}
|
|
|
|
return new DefaultBehaviorRuntime
|
|
{
|
|
Kind = "idle",
|
|
};
|
|
}
|
|
|
|
private static Vector3 ResolveStationPosition(SystemRuntime system, InitialStationDefinition plan, BalanceDefinition balance)
|
|
{
|
|
if (plan.Position is { Length: 3 })
|
|
{
|
|
return ToVector(plan.Position);
|
|
}
|
|
|
|
if (plan.PlanetIndex is int planetIndex && planetIndex >= 0 && planetIndex < system.Definition.Planets.Count)
|
|
{
|
|
var planet = system.Definition.Planets[planetIndex];
|
|
var side = plan.LagrangeSide ?? 1;
|
|
return new Vector3(
|
|
system.Position.X + planet.OrbitRadius + (side * 72f),
|
|
balance.YPlane,
|
|
system.Position.Z + ((planetIndex + 1) * 42f * side));
|
|
}
|
|
|
|
return new Vector3(system.Position.X + 180f, balance.YPlane, system.Position.Z);
|
|
}
|
|
|
|
private static Vector3 ToVector(float[] values) => new(values[0], values[1], values[2]);
|
|
|
|
private static Vector3 Add(Vector3 left, Vector3 right) => new(left.X + right.X, left.Y + right.Y, left.Z + right.Z);
|
|
}
|