263 lines
9.7 KiB
C#
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;
|
|
}
|