refactor(backend): modularize simulation engine
This commit is contained in:
570
apps/backend/Simulation/Systems/ShipControlService.cs
Normal file
570
apps/backend/Simulation/Systems/ShipControlService.cs
Normal file
@@ -0,0 +1,570 @@
|
||||
using SpaceGame.Api.Contracts;
|
||||
using SpaceGame.Api.Simulation.AI;
|
||||
using SpaceGame.Api.Simulation.Engine;
|
||||
using SpaceGame.Api.Simulation.Model;
|
||||
using static SpaceGame.Api.Simulation.Systems.InfrastructureSimulationService;
|
||||
using static SpaceGame.Api.Simulation.Support.SimulationRuntimeSupport;
|
||||
|
||||
namespace SpaceGame.Api.Simulation.Systems;
|
||||
|
||||
internal sealed class ShipControlService
|
||||
{
|
||||
private static readonly ShipBehaviorStateMachine _shipBehaviorStateMachine = ShipBehaviorStateMachine.CreateDefault();
|
||||
|
||||
private static CommanderRuntime? GetShipCommander(SimulationWorld world, ShipRuntime ship) =>
|
||||
ship.CommanderId is null
|
||||
? null
|
||||
: world.Commanders.FirstOrDefault(candidate => candidate.Id == ship.CommanderId && candidate.Kind == CommanderKind.Ship);
|
||||
|
||||
private static void SyncCommanderToShip(ShipRuntime ship, CommanderRuntime commander)
|
||||
{
|
||||
if (commander.ActiveBehavior is not null)
|
||||
{
|
||||
ship.DefaultBehavior.Kind = commander.ActiveBehavior.Kind;
|
||||
ship.DefaultBehavior.AreaSystemId = commander.ActiveBehavior.AreaSystemId;
|
||||
ship.DefaultBehavior.ModuleId = commander.ActiveBehavior.ModuleId;
|
||||
ship.DefaultBehavior.NodeId = commander.ActiveBehavior.NodeId;
|
||||
ship.DefaultBehavior.Phase = commander.ActiveBehavior.Phase;
|
||||
ship.DefaultBehavior.PatrolIndex = commander.ActiveBehavior.PatrolIndex;
|
||||
ship.DefaultBehavior.StationId = commander.ActiveBehavior.StationId;
|
||||
}
|
||||
|
||||
if (commander.ActiveOrder is null)
|
||||
{
|
||||
ship.Order = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
ship.Order = new ShipOrderRuntime
|
||||
{
|
||||
Kind = commander.ActiveOrder.Kind,
|
||||
Status = commander.ActiveOrder.Status,
|
||||
DestinationSystemId = commander.ActiveOrder.DestinationSystemId,
|
||||
DestinationPosition = commander.ActiveOrder.DestinationPosition,
|
||||
};
|
||||
}
|
||||
|
||||
if (commander.ActiveTask is not null)
|
||||
{
|
||||
ship.ControllerTask = new ControllerTaskRuntime
|
||||
{
|
||||
Kind = ParseControllerTaskKind(commander.ActiveTask.Kind),
|
||||
Status = commander.ActiveTask.Status,
|
||||
CommanderId = commander.Id,
|
||||
TargetEntityId = commander.ActiveTask.TargetEntityId,
|
||||
TargetNodeId = commander.ActiveTask.TargetNodeId,
|
||||
TargetPosition = commander.ActiveTask.TargetPosition,
|
||||
TargetSystemId = commander.ActiveTask.TargetSystemId,
|
||||
Threshold = commander.ActiveTask.Threshold,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static void SyncShipToCommander(ShipRuntime ship, CommanderRuntime commander)
|
||||
{
|
||||
commander.ActiveBehavior ??= new CommanderBehaviorRuntime { Kind = ship.DefaultBehavior.Kind };
|
||||
commander.ActiveBehavior.Kind = ship.DefaultBehavior.Kind;
|
||||
commander.ActiveBehavior.AreaSystemId = ship.DefaultBehavior.AreaSystemId;
|
||||
commander.ActiveBehavior.ModuleId = ship.DefaultBehavior.ModuleId;
|
||||
commander.ActiveBehavior.NodeId = ship.DefaultBehavior.NodeId;
|
||||
commander.ActiveBehavior.Phase = ship.DefaultBehavior.Phase;
|
||||
commander.ActiveBehavior.PatrolIndex = ship.DefaultBehavior.PatrolIndex;
|
||||
commander.ActiveBehavior.StationId = ship.DefaultBehavior.StationId;
|
||||
|
||||
if (ship.Order is null)
|
||||
{
|
||||
commander.ActiveOrder = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
commander.ActiveOrder ??= new CommanderOrderRuntime
|
||||
{
|
||||
Kind = ship.Order.Kind,
|
||||
DestinationSystemId = ship.Order.DestinationSystemId,
|
||||
DestinationPosition = ship.Order.DestinationPosition,
|
||||
};
|
||||
commander.ActiveOrder.Status = ship.Order.Status;
|
||||
commander.ActiveOrder.TargetEntityId = ship.ControllerTask.TargetEntityId;
|
||||
commander.ActiveOrder.DestinationNodeId = ship.ControllerTask.TargetNodeId ?? ship.SpatialState.DestinationNodeId;
|
||||
}
|
||||
|
||||
commander.ActiveTask ??= new CommanderTaskRuntime { Kind = ship.ControllerTask.Kind.ToContractValue() };
|
||||
commander.ActiveTask.Kind = ship.ControllerTask.Kind.ToContractValue();
|
||||
commander.ActiveTask.Status = ship.ControllerTask.Status;
|
||||
commander.ActiveTask.TargetEntityId = ship.ControllerTask.TargetEntityId;
|
||||
commander.ActiveTask.TargetNodeId = ship.ControllerTask.TargetNodeId;
|
||||
commander.ActiveTask.TargetPosition = ship.ControllerTask.TargetPosition;
|
||||
commander.ActiveTask.TargetSystemId = ship.ControllerTask.TargetSystemId;
|
||||
commander.ActiveTask.Threshold = ship.ControllerTask.Threshold;
|
||||
}
|
||||
|
||||
internal void RefreshControlLayers(ShipRuntime ship, SimulationWorld world)
|
||||
{
|
||||
var commander = GetShipCommander(world, ship);
|
||||
if (commander is not null)
|
||||
{
|
||||
SyncCommanderToShip(ship, commander);
|
||||
}
|
||||
|
||||
if (ship.Order is not null && ship.Order.Status == OrderStatus.Queued)
|
||||
{
|
||||
ship.Order.Status = OrderStatus.Accepted;
|
||||
if (commander?.ActiveOrder is not null)
|
||||
{
|
||||
commander.ActiveOrder.Status = ship.Order.Status;
|
||||
}
|
||||
}
|
||||
|
||||
if (commander is not null)
|
||||
{
|
||||
SyncShipToCommander(ship, commander);
|
||||
}
|
||||
}
|
||||
|
||||
internal void PlanControllerTask(SimulationEngine engine, ShipRuntime ship, SimulationWorld world)
|
||||
{
|
||||
var commander = GetShipCommander(world, ship);
|
||||
if (ship.Order is not null)
|
||||
{
|
||||
ship.ControllerTask = new ControllerTaskRuntime
|
||||
{
|
||||
Kind = ControllerTaskKind.Travel,
|
||||
Status = WorkStatus.Active,
|
||||
CommanderId = commander?.Id,
|
||||
TargetSystemId = ship.Order.DestinationSystemId,
|
||||
TargetNodeId = ship.SpatialState.DestinationNodeId,
|
||||
TargetPosition = ship.Order.DestinationPosition,
|
||||
Threshold = world.Balance.ArrivalThreshold,
|
||||
};
|
||||
SyncCommanderTask(commander, ship.ControllerTask);
|
||||
return;
|
||||
}
|
||||
|
||||
_shipBehaviorStateMachine.Plan(engine, ship, world);
|
||||
SyncCommanderTask(commander, ship.ControllerTask);
|
||||
}
|
||||
|
||||
internal void PlanResourceHarvest(ShipRuntime ship, SimulationWorld world, string resourceItemId, string requiredModule)
|
||||
{
|
||||
var behavior = ship.DefaultBehavior;
|
||||
var refinery = SelectBestBuyStation(world, ship, resourceItemId, behavior.StationId);
|
||||
behavior.StationId = refinery?.Id;
|
||||
var node = behavior.NodeId is null
|
||||
? world.Nodes
|
||||
.Where(candidate =>
|
||||
(behavior.AreaSystemId is null || candidate.SystemId == behavior.AreaSystemId) &&
|
||||
candidate.ItemId == resourceItemId &&
|
||||
candidate.OreRemaining > 0.01f)
|
||||
.OrderByDescending(candidate => candidate.OreRemaining)
|
||||
.FirstOrDefault()
|
||||
: world.Nodes.FirstOrDefault(candidate => candidate.Id == behavior.NodeId && candidate.OreRemaining > 0.01f);
|
||||
|
||||
if (refinery is null || node is null || !HasShipCapabilities(ship.Definition, requiredModule))
|
||||
{
|
||||
behavior.Kind = "idle";
|
||||
ship.ControllerTask = CreateIdleTask(world.Balance.ArrivalThreshold);
|
||||
return;
|
||||
}
|
||||
|
||||
behavior.NodeId ??= node.Id;
|
||||
|
||||
if (GetShipCargoAmount(ship) >= ship.Definition.CargoCapacity - 0.01f
|
||||
&& behavior.Phase is "travel-to-node" or "extract")
|
||||
{
|
||||
behavior.Phase = "travel-to-station";
|
||||
}
|
||||
|
||||
if (ship.DockedStationId == refinery.Id)
|
||||
{
|
||||
if (GetShipCargoAmount(ship) > 0.01f)
|
||||
{
|
||||
behavior.Phase = "unload";
|
||||
}
|
||||
else if (behavior.Phase is "dock" or "unload")
|
||||
{
|
||||
behavior.Phase = "undock";
|
||||
}
|
||||
}
|
||||
else if (behavior.Phase is not "travel-to-station" and not "dock" and not "travel-to-node" and not "extract")
|
||||
{
|
||||
behavior.Phase = "travel-to-station";
|
||||
}
|
||||
|
||||
switch (behavior.Phase)
|
||||
{
|
||||
case "extract":
|
||||
var extractionPosition = GetResourceHoldPosition(node.Position, ship.Id, 20f);
|
||||
ship.ControllerTask = new ControllerTaskRuntime
|
||||
{
|
||||
Kind = ControllerTaskKind.Extract,
|
||||
TargetEntityId = node.Id,
|
||||
TargetSystemId = node.SystemId,
|
||||
TargetPosition = extractionPosition,
|
||||
Threshold = 5f,
|
||||
};
|
||||
break;
|
||||
case "travel-to-station":
|
||||
ship.ControllerTask = new ControllerTaskRuntime
|
||||
{
|
||||
Kind = ControllerTaskKind.Travel,
|
||||
TargetEntityId = refinery.Id,
|
||||
TargetSystemId = refinery.SystemId,
|
||||
TargetPosition = refinery.Position,
|
||||
Threshold = refinery.Radius + 8f,
|
||||
};
|
||||
break;
|
||||
case "dock":
|
||||
ship.ControllerTask = new ControllerTaskRuntime
|
||||
{
|
||||
Kind = ControllerTaskKind.Dock,
|
||||
TargetEntityId = refinery.Id,
|
||||
TargetSystemId = refinery.SystemId,
|
||||
TargetPosition = refinery.Position,
|
||||
Threshold = refinery.Radius + 4f,
|
||||
};
|
||||
break;
|
||||
case "unload":
|
||||
ship.ControllerTask = new ControllerTaskRuntime
|
||||
{
|
||||
Kind = ControllerTaskKind.Unload,
|
||||
TargetEntityId = refinery.Id,
|
||||
TargetSystemId = refinery.SystemId,
|
||||
TargetPosition = refinery.Position,
|
||||
Threshold = 0f,
|
||||
};
|
||||
break;
|
||||
case "undock":
|
||||
ship.ControllerTask = new ControllerTaskRuntime
|
||||
{
|
||||
Kind = ControllerTaskKind.Undock,
|
||||
TargetEntityId = refinery.Id,
|
||||
TargetSystemId = refinery.SystemId,
|
||||
TargetPosition = new Vector3(refinery.Position.X + world.Balance.UndockDistance, refinery.Position.Y, refinery.Position.Z),
|
||||
Threshold = 8f,
|
||||
};
|
||||
break;
|
||||
default:
|
||||
ship.ControllerTask = new ControllerTaskRuntime
|
||||
{
|
||||
Kind = ControllerTaskKind.Travel,
|
||||
TargetEntityId = node.Id,
|
||||
TargetSystemId = node.SystemId,
|
||||
TargetPosition = node.Position,
|
||||
Threshold = 18f,
|
||||
};
|
||||
behavior.Phase = "travel-to-node";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
internal static StationRuntime? SelectBestBuyStation(SimulationWorld world, ShipRuntime ship, string itemId, string? preferredStationId)
|
||||
{
|
||||
var preferred = preferredStationId is null
|
||||
? null
|
||||
: world.Stations.FirstOrDefault(station => station.Id == preferredStationId);
|
||||
|
||||
var bestOrder = world.MarketOrders
|
||||
.Where(order =>
|
||||
order.Kind == MarketOrderKinds.Buy &&
|
||||
order.ConstructionSiteId is null &&
|
||||
order.State != MarketOrderStateKinds.Cancelled &&
|
||||
order.ItemId == itemId &&
|
||||
order.RemainingAmount > 0.01f)
|
||||
.Select(order => (Order: order, Station: world.Stations.FirstOrDefault(station => station.Id == order.StationId)))
|
||||
.Where(entry => entry.Station is not null)
|
||||
.OrderByDescending(entry =>
|
||||
{
|
||||
var distancePenalty = entry.Station!.SystemId == ship.SystemId ? 0f : 0.2f;
|
||||
return entry.Order.Valuation - distancePenalty;
|
||||
})
|
||||
.FirstOrDefault();
|
||||
|
||||
return bestOrder.Station ?? preferred;
|
||||
}
|
||||
|
||||
private static ControllerTaskRuntime CreateStationSupportTask(SimulationWorld world, ShipRuntime ship, StationRuntime station, string? phase) =>
|
||||
phase switch
|
||||
{
|
||||
"dock" => new ControllerTaskRuntime
|
||||
{
|
||||
Kind = ControllerTaskKind.Dock,
|
||||
TargetEntityId = station.Id,
|
||||
TargetSystemId = station.SystemId,
|
||||
TargetPosition = station.Position,
|
||||
Threshold = 8f,
|
||||
},
|
||||
"load" => new ControllerTaskRuntime
|
||||
{
|
||||
Kind = ControllerTaskKind.Load,
|
||||
TargetEntityId = station.Id,
|
||||
TargetSystemId = station.SystemId,
|
||||
TargetPosition = station.Position,
|
||||
Threshold = 8f,
|
||||
},
|
||||
"unload" => new ControllerTaskRuntime
|
||||
{
|
||||
Kind = ControllerTaskKind.Unload,
|
||||
TargetEntityId = station.Id,
|
||||
TargetSystemId = station.SystemId,
|
||||
TargetPosition = station.Position,
|
||||
Threshold = 8f,
|
||||
},
|
||||
"undock" => new ControllerTaskRuntime
|
||||
{
|
||||
Kind = ControllerTaskKind.Undock,
|
||||
TargetEntityId = station.Id,
|
||||
TargetSystemId = station.SystemId,
|
||||
TargetPosition = new Vector3(station.Position.X + world.Balance.UndockDistance, station.Position.Y, station.Position.Z),
|
||||
Threshold = 8f,
|
||||
},
|
||||
_ => CreateIdleTask(world.Balance.ArrivalThreshold),
|
||||
};
|
||||
|
||||
internal void PlanStationConstruction(ShipRuntime ship, SimulationWorld world)
|
||||
{
|
||||
var behavior = ship.DefaultBehavior;
|
||||
var station = world.Stations.FirstOrDefault(candidate => candidate.Id == behavior.StationId);
|
||||
var site = station is null ? null : GetConstructionSiteForStation(world, station.Id);
|
||||
if (station is null)
|
||||
{
|
||||
behavior.Kind = "idle";
|
||||
ship.ControllerTask = CreateIdleTask(world.Balance.ArrivalThreshold);
|
||||
return;
|
||||
}
|
||||
|
||||
var moduleId = site?.BlueprintId ?? GetNextStationModuleToBuild(station, world);
|
||||
behavior.ModuleId = moduleId;
|
||||
if (moduleId is null)
|
||||
{
|
||||
ship.ControllerTask = CreateIdleTask(world.Balance.ArrivalThreshold);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ship.DockedStationId is not null)
|
||||
{
|
||||
var dockedStation = world.Stations.FirstOrDefault(candidate => candidate.Id == ship.DockedStationId);
|
||||
if (dockedStation is not null)
|
||||
{
|
||||
dockedStation.DockedShipIds.Remove(ship.Id);
|
||||
ReleaseDockingPad(dockedStation, ship.Id);
|
||||
}
|
||||
|
||||
ship.DockedStationId = null;
|
||||
ship.AssignedDockingPadIndex = null;
|
||||
ship.Position = GetConstructionHoldPosition(station, ship.Id);
|
||||
ship.TargetPosition = ship.Position;
|
||||
}
|
||||
|
||||
var constructionHoldPosition = GetConstructionHoldPosition(station, ship.Id);
|
||||
var isAtConstructionHold = ship.SystemId == station.SystemId
|
||||
&& ship.Position.DistanceTo(constructionHoldPosition) <= 10f;
|
||||
|
||||
if (isAtConstructionHold)
|
||||
{
|
||||
if (site is not null && site.State == ConstructionSiteStateKinds.Active && !IsConstructionSiteReady(world, site))
|
||||
{
|
||||
behavior.Phase = "deliver-to-site";
|
||||
}
|
||||
else if (site is not null && site.State == ConstructionSiteStateKinds.Active && IsConstructionSiteReady(world, site))
|
||||
{
|
||||
behavior.Phase = "build-site";
|
||||
}
|
||||
else if (site is not null)
|
||||
{
|
||||
behavior.Phase = "wait-for-materials";
|
||||
}
|
||||
else if (CanStartModuleConstruction(station, world.ModuleRecipes[moduleId]))
|
||||
{
|
||||
behavior.Phase = "construct-module";
|
||||
}
|
||||
else
|
||||
{
|
||||
behavior.Phase = "wait-for-materials";
|
||||
}
|
||||
}
|
||||
else if (behavior.Phase != "travel-to-station")
|
||||
{
|
||||
behavior.Phase = "travel-to-station";
|
||||
}
|
||||
|
||||
switch (behavior.Phase)
|
||||
{
|
||||
case "construct-module":
|
||||
ship.ControllerTask = new ControllerTaskRuntime
|
||||
{
|
||||
Kind = ControllerTaskKind.ConstructModule,
|
||||
TargetEntityId = station.Id,
|
||||
TargetSystemId = station.SystemId,
|
||||
TargetPosition = constructionHoldPosition,
|
||||
Threshold = 10f,
|
||||
};
|
||||
break;
|
||||
case "deliver-to-site":
|
||||
ship.ControllerTask = new ControllerTaskRuntime
|
||||
{
|
||||
Kind = ControllerTaskKind.DeliverConstruction,
|
||||
TargetEntityId = site?.Id,
|
||||
TargetSystemId = station.SystemId,
|
||||
TargetPosition = constructionHoldPosition,
|
||||
Threshold = 10f,
|
||||
};
|
||||
break;
|
||||
case "build-site":
|
||||
ship.ControllerTask = new ControllerTaskRuntime
|
||||
{
|
||||
Kind = ControllerTaskKind.BuildConstructionSite,
|
||||
TargetEntityId = site?.Id,
|
||||
TargetSystemId = station.SystemId,
|
||||
TargetPosition = constructionHoldPosition,
|
||||
Threshold = 10f,
|
||||
};
|
||||
break;
|
||||
case "wait-for-materials":
|
||||
ship.ControllerTask = new ControllerTaskRuntime
|
||||
{
|
||||
Kind = ControllerTaskKind.Idle,
|
||||
TargetEntityId = station.Id,
|
||||
TargetSystemId = station.SystemId,
|
||||
TargetPosition = constructionHoldPosition,
|
||||
Threshold = 0f,
|
||||
};
|
||||
break;
|
||||
default:
|
||||
ship.ControllerTask = new ControllerTaskRuntime
|
||||
{
|
||||
Kind = ControllerTaskKind.Travel,
|
||||
TargetEntityId = station.Id,
|
||||
TargetSystemId = station.SystemId,
|
||||
TargetPosition = constructionHoldPosition,
|
||||
Threshold = 10f,
|
||||
};
|
||||
behavior.Phase = "travel-to-station";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
internal void AdvanceControlState(SimulationEngine engine, ShipRuntime ship, SimulationWorld world, string controllerEvent)
|
||||
{
|
||||
var commander = GetShipCommander(world, ship);
|
||||
if (ship.Order is not null && controllerEvent == "arrived")
|
||||
{
|
||||
ship.Order = null;
|
||||
ship.ControllerTask.Kind = ControllerTaskKind.Idle;
|
||||
if (commander is not null)
|
||||
{
|
||||
commander.ActiveOrder = null;
|
||||
commander.ActiveTask = new CommanderTaskRuntime
|
||||
{
|
||||
Kind = ShipTaskKinds.Idle,
|
||||
Status = WorkStatus.Completed,
|
||||
TargetSystemId = ship.SystemId,
|
||||
Threshold = 0f,
|
||||
};
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_shipBehaviorStateMachine.ApplyEvent(engine, ship, world, controllerEvent);
|
||||
if (commander is not null)
|
||||
{
|
||||
SyncShipToCommander(ship, commander);
|
||||
if (commander.ActiveTask is not null)
|
||||
{
|
||||
commander.ActiveTask.Status = controllerEvent == "none" ? WorkStatus.Active : WorkStatus.Completed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void TrackHistory(ShipRuntime ship, string controllerEvent)
|
||||
{
|
||||
var signature = $"{ship.State.ToContractValue()}|{ship.DefaultBehavior.Kind}|{ship.ControllerTask.Kind.ToContractValue()}|{ship.ControllerTask.TargetSystemId}|{ship.ControllerTask.TargetEntityId}|{GetShipCargoAmount(ship):0.0}|{controllerEvent}";
|
||||
if (signature == ship.LastSignature)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ship.LastSignature = signature;
|
||||
var target = ship.ControllerTask.TargetEntityId
|
||||
?? ship.ControllerTask.TargetSystemId
|
||||
?? "none";
|
||||
var eventSummary = controllerEvent == "none" ? string.Empty : $" event={controllerEvent}";
|
||||
ship.History.Add($"{DateTimeOffset.UtcNow:HH:mm:ss} state={ship.State.ToContractValue()} behavior={ship.DefaultBehavior.Kind} task={ship.ControllerTask.Kind.ToContractValue()} target={target} cargo={GetShipCargoAmount(ship):0.#}{eventSummary}");
|
||||
if (ship.History.Count > 18)
|
||||
{
|
||||
ship.History.RemoveAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
internal void EmitShipStateEvents(
|
||||
ShipRuntime ship,
|
||||
ShipState previousState,
|
||||
string previousBehavior,
|
||||
ControllerTaskKind previousTask,
|
||||
string controllerEvent,
|
||||
ICollection<SimulationEventRecord> events)
|
||||
{
|
||||
var occurredAtUtc = DateTimeOffset.UtcNow;
|
||||
|
||||
if (previousState != ship.State)
|
||||
{
|
||||
events.Add(new SimulationEventRecord("ship", ship.Id, "state-changed", $"{ship.Definition.Label} {previousState.ToContractValue()} -> {ship.State.ToContractValue()}", occurredAtUtc));
|
||||
}
|
||||
|
||||
if (previousBehavior != ship.DefaultBehavior.Kind)
|
||||
{
|
||||
events.Add(new SimulationEventRecord("ship", ship.Id, "behavior-changed", $"{ship.Definition.Label} behavior {previousBehavior} -> {ship.DefaultBehavior.Kind}", occurredAtUtc));
|
||||
}
|
||||
|
||||
if (previousTask != ship.ControllerTask.Kind)
|
||||
{
|
||||
events.Add(new SimulationEventRecord("ship", ship.Id, "task-changed", $"{ship.Definition.Label} task {previousTask.ToContractValue()} -> {ship.ControllerTask.Kind.ToContractValue()}", occurredAtUtc));
|
||||
}
|
||||
|
||||
if (controllerEvent != "none")
|
||||
{
|
||||
events.Add(new SimulationEventRecord("ship", ship.Id, controllerEvent, $"{ship.Definition.Label} {controllerEvent}", occurredAtUtc));
|
||||
}
|
||||
}
|
||||
|
||||
internal static ControllerTaskRuntime CreateIdleTask(float threshold) =>
|
||||
new()
|
||||
{
|
||||
Kind = ControllerTaskKind.Idle,
|
||||
Threshold = threshold,
|
||||
};
|
||||
|
||||
private static ControllerTaskKind ParseControllerTaskKind(string kind) => kind switch
|
||||
{
|
||||
"travel" => ControllerTaskKind.Travel,
|
||||
"extract" => ControllerTaskKind.Extract,
|
||||
"dock" => ControllerTaskKind.Dock,
|
||||
"load" => ControllerTaskKind.Load,
|
||||
"unload" => ControllerTaskKind.Unload,
|
||||
"deliver-construction" => ControllerTaskKind.DeliverConstruction,
|
||||
"build-construction-site" => ControllerTaskKind.BuildConstructionSite,
|
||||
|
||||
"construct-module" => ControllerTaskKind.ConstructModule,
|
||||
"undock" => ControllerTaskKind.Undock,
|
||||
_ => ControllerTaskKind.Idle,
|
||||
};
|
||||
|
||||
private static void SyncCommanderTask(CommanderRuntime? commander, ControllerTaskRuntime task)
|
||||
{
|
||||
if (commander is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
commander.ActiveTask = new CommanderTaskRuntime
|
||||
{
|
||||
Kind = task.Kind.ToContractValue(),
|
||||
Status = task.Status,
|
||||
TargetEntityId = task.TargetEntityId,
|
||||
TargetNodeId = task.TargetNodeId,
|
||||
TargetPosition = task.TargetPosition,
|
||||
TargetSystemId = task.TargetSystemId,
|
||||
Threshold = task.Threshold,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user