Files
space-game/apps/backend/Simulation/SimulationEngine.ResourceAndInfrastructureSystems.cs

263 lines
9.7 KiB
C#

using SpaceGame.Simulation.Api.Contracts;
using SpaceGame.Simulation.Api.Data;
namespace SpaceGame.Simulation.Api.Simulation;
public sealed partial class SimulationEngine
{
private static void UpdateClaims(SimulationWorld world, ICollection<SimulationEventRecord> events)
{
foreach (var claim in world.Claims)
{
if (claim.State == ClaimStateKinds.Destroyed || claim.Health <= 0f)
{
if (claim.State != ClaimStateKinds.Destroyed)
{
claim.State = ClaimStateKinds.Destroyed;
events.Add(new SimulationEventRecord("claim", claim.Id, "claim-destroyed", $"Claim {claim.Id} was destroyed.", world.GeneratedAtUtc));
}
foreach (var site in world.ConstructionSites.Where(candidate => candidate.ClaimId == claim.Id))
{
site.State = ConstructionSiteStateKinds.Destroyed;
}
continue;
}
if (claim.State == ClaimStateKinds.Activating && world.GeneratedAtUtc >= claim.ActivatesAtUtc)
{
claim.State = ClaimStateKinds.Active;
events.Add(new SimulationEventRecord("claim", claim.Id, "claim-activated", $"Claim {claim.Id} is now active.", world.GeneratedAtUtc));
}
}
}
private static void UpdateConstructionSites(SimulationWorld world, ICollection<SimulationEventRecord> events)
{
foreach (var site in world.ConstructionSites)
{
if (site.State == ConstructionSiteStateKinds.Destroyed)
{
continue;
}
var claim = site.ClaimId is null
? null
: world.Claims.FirstOrDefault(candidate => candidate.Id == site.ClaimId);
if (claim?.State == ClaimStateKinds.Destroyed)
{
site.State = ConstructionSiteStateKinds.Destroyed;
continue;
}
if (claim?.State == ClaimStateKinds.Active && site.State == ConstructionSiteStateKinds.Planned)
{
site.State = ConstructionSiteStateKinds.Active;
events.Add(new SimulationEventRecord("construction-site", site.Id, "site-active", $"Construction site {site.Id} is active.", world.GeneratedAtUtc));
}
foreach (var orderId in site.MarketOrderIds)
{
var order = world.MarketOrders.FirstOrDefault(candidate => candidate.Id == orderId);
if (order is null || !site.RequiredItems.TryGetValue(order.ItemId, out var required))
{
continue;
}
var remaining = MathF.Max(0f, required - GetInventoryAmount(site.DeliveredItems, order.ItemId));
order.RemainingAmount = remaining;
order.State = remaining <= 0.01f
? MarketOrderStateKinds.Filled
: remaining < order.Amount
? MarketOrderStateKinds.PartiallyFilled
: MarketOrderStateKinds.Open;
}
}
}
private static bool TryEnsureModuleConstructionStarted(StationRuntime station, ModuleRecipeDefinition recipe, string shipId)
{
if (station.InstalledModules.Contains(recipe.ModuleId, StringComparer.Ordinal))
{
return true;
}
if (station.ActiveConstruction is not null)
{
return string.Equals(station.ActiveConstruction.ModuleId, recipe.ModuleId, StringComparison.Ordinal)
&& string.Equals(station.ActiveConstruction.AssignedConstructorShipId, shipId, StringComparison.Ordinal);
}
if (!CanStartModuleConstruction(station, recipe))
{
return false;
}
foreach (var input in recipe.Inputs)
{
RemoveInventory(station.Inventory, input.ItemId, input.Amount);
}
station.ActiveConstruction = new ModuleConstructionRuntime
{
ModuleId = recipe.ModuleId,
RequiredSeconds = recipe.Duration,
AssignedConstructorShipId = shipId,
};
return true;
}
private static string? GetNextStationModuleToBuild(StationRuntime station, SimulationWorld world)
{
foreach (var moduleId in new[] { "gas-tank", "fuel-processor", "refinery-stack", "dock-bay-small" })
{
if (!station.InstalledModules.Contains(moduleId, StringComparer.Ordinal)
&& world.ModuleRecipes.ContainsKey(moduleId))
{
return moduleId;
}
}
return null;
}
private static void PrepareNextConstructionSiteStep(SimulationWorld world, StationRuntime station, ConstructionSiteRuntime site)
{
var nextModuleId = GetNextStationModuleToBuild(station, world);
foreach (var orderId in site.MarketOrderIds)
{
var order = world.MarketOrders.FirstOrDefault(candidate => candidate.Id == orderId);
if (order is not null)
{
order.State = MarketOrderStateKinds.Cancelled;
order.RemainingAmount = 0f;
}
}
site.MarketOrderIds.Clear();
site.Inventory.Clear();
site.DeliveredItems.Clear();
site.RequiredItems.Clear();
site.AssignedConstructorShipIds.Clear();
site.Progress = 0f;
if (nextModuleId is null || !world.ModuleRecipes.TryGetValue(nextModuleId, out var recipe))
{
site.State = ConstructionSiteStateKinds.Completed;
site.BlueprintId = null;
return;
}
site.BlueprintId = nextModuleId;
site.State = ConstructionSiteStateKinds.Active;
foreach (var input in recipe.Inputs)
{
site.RequiredItems[input.ItemId] = input.Amount;
site.DeliveredItems[input.ItemId] = 0f;
var orderId = $"market-order-{station.Id}-{nextModuleId}-{input.ItemId}";
site.MarketOrderIds.Add(orderId);
station.MarketOrderIds.Add(orderId);
world.MarketOrders.Add(new MarketOrderRuntime
{
Id = orderId,
FactionId = station.FactionId,
StationId = station.Id,
ConstructionSiteId = site.Id,
Kind = MarketOrderKinds.Buy,
ItemId = input.ItemId,
Amount = input.Amount,
RemainingAmount = input.Amount,
Valuation = 1f,
State = MarketOrderStateKinds.Open,
});
}
}
private static int GetDockingPadCount(StationRuntime station) =>
CountModules(station.InstalledModules, "dock-bay-small") * 2;
private static int? ReserveDockingPad(StationRuntime station, string shipId)
{
if (station.DockingPadAssignments.FirstOrDefault(entry => string.Equals(entry.Value, shipId, StringComparison.Ordinal)) is var existing
&& !string.IsNullOrEmpty(existing.Value))
{
return existing.Key;
}
var padCount = GetDockingPadCount(station);
for (var padIndex = 0; padIndex < padCount; padIndex += 1)
{
if (station.DockingPadAssignments.ContainsKey(padIndex))
{
continue;
}
station.DockingPadAssignments[padIndex] = shipId;
return padIndex;
}
return null;
}
private static void ReleaseDockingPad(StationRuntime station, string shipId)
{
var assignment = station.DockingPadAssignments.FirstOrDefault(entry => string.Equals(entry.Value, shipId, StringComparison.Ordinal));
if (!string.IsNullOrEmpty(assignment.Value))
{
station.DockingPadAssignments.Remove(assignment.Key);
}
}
private static Vector3 GetDockingPadPosition(StationRuntime station, int padIndex)
{
var padCount = Math.Max(1, GetDockingPadCount(station));
var angle = ((MathF.PI * 2f) / padCount) * padIndex;
var radius = station.Definition.Radius + 14f;
return new Vector3(
station.Position.X + (MathF.Cos(angle) * radius),
station.Position.Y,
station.Position.Z + (MathF.Sin(angle) * radius));
}
private static Vector3 GetDockingHoldPosition(StationRuntime station, string shipId)
{
var hash = Math.Abs(shipId.GetHashCode(StringComparison.Ordinal));
var angle = (hash % 360) * (MathF.PI / 180f);
var radius = station.Definition.Radius + 34f;
return new Vector3(
station.Position.X + (MathF.Cos(angle) * radius),
station.Position.Y,
station.Position.Z + (MathF.Sin(angle) * radius));
}
private static Vector3 GetUndockTargetPosition(StationRuntime station, int? padIndex, float distance)
{
if (padIndex is null)
{
return new Vector3(station.Position.X + distance, station.Position.Y, station.Position.Z);
}
var pad = GetDockingPadPosition(station, padIndex.Value);
var dx = pad.X - station.Position.X;
var dz = pad.Z - station.Position.Z;
var length = MathF.Sqrt((dx * dx) + (dz * dz));
if (length <= 0.001f)
{
return new Vector3(station.Position.X + distance, station.Position.Y, station.Position.Z);
}
var scale = distance / length;
return new Vector3(
pad.X + (dx * scale),
station.Position.Y,
pad.Z + (dz * scale));
}
private static Vector3 GetShipDockedPosition(ShipRuntime ship, StationRuntime station) =>
ship.AssignedDockingPadIndex is int padIndex
? GetDockingPadPosition(station, padIndex)
: station.Position;
}