2021-03-18 00:36:34 +00:00
|
|
|
using System;
|
2021-03-18 22:48:33 +00:00
|
|
|
using System.Collections.Generic;
|
2021-03-18 00:36:34 +00:00
|
|
|
using System.Linq;
|
2026-06-21 09:54:15 +01:00
|
|
|
using GameCult.Aetheria.State.Verse;
|
2026-06-21 09:37:54 +01:00
|
|
|
using Random = CultMath.Random;
|
2021-03-18 00:36:34 +00:00
|
|
|
|
|
|
|
|
public class LoadoutGenerator
|
|
|
|
|
{
|
2026-06-14 09:31:17 +01:00
|
|
|
private enum RuntimeItemCandidateKind
|
|
|
|
|
{
|
|
|
|
|
Equipment,
|
|
|
|
|
Gear,
|
|
|
|
|
CargoBay,
|
|
|
|
|
DockingBay,
|
|
|
|
|
Hull
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-18 00:36:34 +00:00
|
|
|
public Random Random;
|
|
|
|
|
public ItemManager ItemManager { get; }
|
2026-06-13 13:35:00 +01:00
|
|
|
public AetheriaRuntimeCatalogSnapshot RuntimeCatalog { get; }
|
2021-05-14 03:03:28 +01:00
|
|
|
public Galaxy Galaxy { get; }
|
|
|
|
|
public GalaxyZone Zone { get; }
|
2021-03-20 04:20:17 +00:00
|
|
|
public Faction Faction { get; }
|
2021-03-18 00:36:34 +00:00
|
|
|
public float PriceExponent { get; }
|
|
|
|
|
|
|
|
|
|
public LoadoutGenerator(
|
|
|
|
|
ref Random random,
|
|
|
|
|
ItemManager itemManager,
|
2026-06-13 13:35:00 +01:00
|
|
|
AetheriaRuntimeCatalogSnapshot runtimeCatalog,
|
2021-05-14 03:03:28 +01:00
|
|
|
Galaxy galaxy,
|
|
|
|
|
GalaxyZone zone,
|
2021-03-20 04:20:17 +00:00
|
|
|
Faction faction,
|
2021-03-18 00:36:34 +00:00
|
|
|
float priceExponent)
|
|
|
|
|
{
|
|
|
|
|
Random = random;
|
|
|
|
|
ItemManager = itemManager;
|
2026-06-13 13:35:00 +01:00
|
|
|
RuntimeCatalog = runtimeCatalog ?? throw new InvalidOperationException("Loadout generation requires the typed Aetheria runtime catalog.");
|
2021-05-14 03:03:28 +01:00
|
|
|
Galaxy = galaxy;
|
2021-03-18 00:36:34 +00:00
|
|
|
Zone = zone;
|
|
|
|
|
Faction = faction;
|
|
|
|
|
PriceExponent = priceExponent;
|
|
|
|
|
}
|
2026-06-14 20:40:13 +01:00
|
|
|
public EntityConstructionBlueprint GenerateShipLoadout(Predicate<AetheriaRuntimeCatalogItem> hullFilter = null)
|
2021-03-18 00:36:34 +00:00
|
|
|
{
|
2026-06-14 07:27:23 +01:00
|
|
|
var hullRow = RandomHull(HullType.Ship, hullFilter);
|
|
|
|
|
if(hullRow==null)
|
2022-07-12 14:22:23 +01:00
|
|
|
{
|
|
|
|
|
ItemManager.Log("Unable to generate ship loadout: no compatible hull found!");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2026-06-14 07:27:23 +01:00
|
|
|
var hull = ItemManager.CreateEquippableInstance(hullRow);
|
2021-04-30 11:19:25 +01:00
|
|
|
if(hull==null)
|
|
|
|
|
ItemManager.Log("WHAT???");
|
2021-03-18 00:36:34 +00:00
|
|
|
var entity = new Ship(ItemManager, null, hull, ItemManager.GameplaySettings.DefaultEntitySettings);
|
|
|
|
|
entity.Faction = Faction;
|
|
|
|
|
OutfitEntity(entity);
|
2026-06-28 15:23:42 +01:00
|
|
|
return EntityConstructionBlueprintCapture.Capture(entity);
|
2021-03-18 00:36:34 +00:00
|
|
|
}
|
|
|
|
|
|
2026-06-14 20:40:13 +01:00
|
|
|
public OrbitalEntityConstructionBlueprint GenerateTurretLoadout()
|
2021-03-18 00:36:34 +00:00
|
|
|
{
|
2026-06-14 07:27:23 +01:00
|
|
|
var hullRow = RandomHull(HullType.Turret);
|
|
|
|
|
if(hullRow==null)
|
2022-07-12 14:22:23 +01:00
|
|
|
{
|
|
|
|
|
ItemManager.Log("Unable to generate turret loadout: no compatible hull found!");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2026-06-14 07:27:23 +01:00
|
|
|
var hull = ItemManager.CreateEquippableInstance(hullRow);
|
2026-06-16 16:07:20 +01:00
|
|
|
var entity = new OrbitalEntity(ItemManager, null, hull, "", ItemManager.GameplaySettings.DefaultEntitySettings);
|
2021-03-18 00:36:34 +00:00
|
|
|
entity.Faction = Faction;
|
|
|
|
|
OutfitEntity(entity);
|
2026-06-28 15:23:42 +01:00
|
|
|
return EntityConstructionBlueprintCapture.Capture(entity) as OrbitalEntityConstructionBlueprint;
|
2021-03-18 00:36:34 +00:00
|
|
|
}
|
|
|
|
|
|
2026-06-14 20:40:13 +01:00
|
|
|
public OrbitalEntityConstructionBlueprint GenerateStationLoadout()
|
2021-03-20 04:20:17 +00:00
|
|
|
{
|
2026-06-14 07:27:23 +01:00
|
|
|
var hullRow = RandomHull(HullType.Station);
|
|
|
|
|
if(hullRow==null)
|
2022-07-12 14:22:23 +01:00
|
|
|
{
|
|
|
|
|
ItemManager.Log("Unable to generate station loadout: no compatible hull found!");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2026-06-14 07:27:23 +01:00
|
|
|
var hull = ItemManager.CreateEquippableInstance(hullRow);
|
2026-06-16 16:07:20 +01:00
|
|
|
var entity = new OrbitalEntity(ItemManager, null, hull, "", ItemManager.GameplaySettings.DefaultEntitySettings);
|
2021-03-20 04:20:17 +00:00
|
|
|
entity.Faction = Faction;
|
|
|
|
|
|
|
|
|
|
var emptyShape = entity.UnoccupiedSpace;
|
|
|
|
|
|
2026-06-14 09:31:17 +01:00
|
|
|
var dockingBayRow = RandomCatalogItem(RuntimeItemCandidateKind.DockingBay, 2, item => FitsWithin(item, emptyShape));
|
2026-06-14 07:27:23 +01:00
|
|
|
if (dockingBayRow == null) throw new InvalidLoadoutException("No compatible docking bay found for station!");
|
2021-03-20 04:20:17 +00:00
|
|
|
|
2026-06-14 05:25:56 +01:00
|
|
|
ToShape(dockingBayRow).FitsWithin(emptyShape, out var cargoRotation, out var cargoPosition);
|
2026-06-14 07:27:23 +01:00
|
|
|
var dockingBay = ItemManager.CreateEquippableInstance(dockingBayRow);
|
2021-03-20 04:20:17 +00:00
|
|
|
dockingBay.Rotation = cargoRotation;
|
|
|
|
|
if (!entity.TryEquip(dockingBay, cargoPosition))
|
|
|
|
|
{
|
|
|
|
|
throw new InvalidLoadoutException("Failed to equip selected docking bay!");
|
|
|
|
|
}
|
2022-08-08 22:43:55 +01:00
|
|
|
|
|
|
|
|
OutfitEntity(entity);
|
2021-03-20 04:20:17 +00:00
|
|
|
|
|
|
|
|
var cargo = entity.CargoBays.First();
|
2026-06-14 09:31:17 +01:00
|
|
|
IEnumerable<AetheriaRuntimeCatalogItem> inventory = RandomCatalogItems(RuntimeItemCandidateKind.Equipment, 16, 1,
|
2026-06-14 09:38:55 +01:00
|
|
|
item => item.Category != AetheriaRuntimeItemCategories.CargoBay && item.Category != AetheriaRuntimeItemCategories.DockingBay &&
|
|
|
|
|
(item.Category != AetheriaRuntimeItemCategories.Hull || item.HullType == nameof(HullType.Ship)));
|
2026-06-14 05:25:56 +01:00
|
|
|
inventory = inventory
|
|
|
|
|
.Where(item => item != null);
|
2021-04-14 03:29:14 +01:00
|
|
|
inventory = inventory
|
2026-06-14 07:27:23 +01:00
|
|
|
.OrderByDescending(item=>item.OccupiedCells);
|
2021-03-20 04:20:17 +00:00
|
|
|
foreach (var item in inventory)
|
|
|
|
|
{
|
2026-06-14 07:27:23 +01:00
|
|
|
var instance = ItemManager.CreateEquippableInstance(item);
|
2021-03-20 04:20:17 +00:00
|
|
|
cargo.TryStore(instance);
|
|
|
|
|
}
|
2021-06-18 12:32:03 +01:00
|
|
|
|
2026-06-14 07:27:23 +01:00
|
|
|
entity.CanTow = hullRow.HullCanTow;
|
2021-03-20 04:20:17 +00:00
|
|
|
|
2026-06-28 15:23:42 +01:00
|
|
|
return EntityConstructionBlueprintCapture.Capture(entity) as OrbitalEntityConstructionBlueprint;
|
2021-03-20 04:20:17 +00:00
|
|
|
}
|
|
|
|
|
|
2026-06-14 07:27:23 +01:00
|
|
|
public AetheriaRuntimeCatalogItem RandomHull(HullType type, Predicate<AetheriaRuntimeCatalogItem> hullFilter = null)
|
2021-03-18 00:36:34 +00:00
|
|
|
{
|
2026-06-14 09:31:17 +01:00
|
|
|
return RandomCatalogItem(RuntimeItemCandidateKind.Hull, 0, item =>
|
2026-06-14 05:25:56 +01:00
|
|
|
string.Equals(item.HullType, type.ToString(), StringComparison.Ordinal) &&
|
|
|
|
|
(hullFilter?.Invoke(item) ?? true));
|
2021-03-18 00:36:34 +00:00
|
|
|
}
|
|
|
|
|
|
2026-06-14 09:31:17 +01:00
|
|
|
private AetheriaRuntimeCatalogItem[] RandomCatalogItems(
|
|
|
|
|
RuntimeItemCandidateKind candidateKind,
|
2026-06-13 23:20:16 +01:00
|
|
|
int count,
|
|
|
|
|
float sizeExponent,
|
2026-06-14 09:31:17 +01:00
|
|
|
Predicate<AetheriaRuntimeCatalogItem> typedFilter = null)
|
2021-03-18 00:36:34 +00:00
|
|
|
{
|
2026-06-13 13:35:00 +01:00
|
|
|
return RuntimeCatalog.EquipmentItems
|
2026-06-14 09:31:17 +01:00
|
|
|
.Where(item => IsTypedCandidate(item, candidateKind))
|
2026-06-13 13:35:00 +01:00
|
|
|
.Where(item =>
|
|
|
|
|
item.Price > 0 &&
|
2026-06-15 17:25:40 +01:00
|
|
|
!string.IsNullOrWhiteSpace(item.ManufacturerKey) &&
|
|
|
|
|
(Galaxy.IsPrelude || Galaxy.ContainsFaction(item.ManufacturerKey) &&
|
|
|
|
|
(Faction == null || Faction.AllegianceByKey.ContainsKey(item.ManufacturerKey))) &&
|
2026-06-13 23:20:16 +01:00
|
|
|
(typedFilter?.Invoke(item) ?? true))
|
2021-03-18 00:36:34 +00:00
|
|
|
.WeightedRandomElements(ref Random, item =>
|
2026-06-14 05:25:56 +01:00
|
|
|
{
|
2026-06-15 17:25:40 +01:00
|
|
|
var manufacturerKey = item.ManufacturerKey;
|
|
|
|
|
if (string.IsNullOrWhiteSpace(manufacturerKey))
|
2026-06-14 05:25:56 +01:00
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var allegianceWeight = Faction == null
|
|
|
|
|
? 1
|
2026-06-15 17:25:40 +01:00
|
|
|
: string.Equals(manufacturerKey, Faction.FactionKey, StringComparison.OrdinalIgnoreCase)
|
2026-06-14 05:25:56 +01:00
|
|
|
? 1
|
2026-06-15 17:25:40 +01:00
|
|
|
: Faction.AllegianceByKey.TryGetValue(manufacturerKey, out var allegiance)
|
|
|
|
|
? allegiance / ManufacturerDistancePenalty(manufacturerKey)
|
2026-06-14 05:25:56 +01:00
|
|
|
: 0;
|
|
|
|
|
return allegianceWeight *
|
2026-06-21 09:37:54 +01:00
|
|
|
MathF.Pow(item.OccupiedCells, sizeExponent) / // Prioritize larger items
|
|
|
|
|
MathF.Pow(item.Price, PriceExponent); // Penalize item price to a controllable degree
|
2026-06-14 05:25:56 +01:00
|
|
|
},
|
2021-03-20 04:20:17 +00:00
|
|
|
count
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-14 09:31:17 +01:00
|
|
|
private static bool IsTypedCandidate(AetheriaRuntimeCatalogItem item, RuntimeItemCandidateKind candidateKind)
|
2026-06-13 13:35:00 +01:00
|
|
|
{
|
2026-06-14 09:31:17 +01:00
|
|
|
switch (candidateKind)
|
2026-06-13 13:35:00 +01:00
|
|
|
{
|
2026-06-14 09:31:17 +01:00
|
|
|
case RuntimeItemCandidateKind.Equipment:
|
|
|
|
|
return !string.IsNullOrWhiteSpace(item.HardpointType);
|
|
|
|
|
case RuntimeItemCandidateKind.Gear:
|
2026-06-14 09:38:55 +01:00
|
|
|
return item.Category == AetheriaRuntimeItemCategories.Gear || item.Category == AetheriaRuntimeItemCategories.Weapon;
|
2026-06-14 09:31:17 +01:00
|
|
|
case RuntimeItemCandidateKind.CargoBay:
|
2026-06-14 09:38:55 +01:00
|
|
|
return item.Category == AetheriaRuntimeItemCategories.CargoBay || item.Category == AetheriaRuntimeItemCategories.DockingBay;
|
2026-06-14 09:31:17 +01:00
|
|
|
case RuntimeItemCandidateKind.DockingBay:
|
2026-06-14 09:38:55 +01:00
|
|
|
return item.Category == AetheriaRuntimeItemCategories.DockingBay;
|
2026-06-14 09:31:17 +01:00
|
|
|
case RuntimeItemCandidateKind.Hull:
|
2026-06-14 09:38:55 +01:00
|
|
|
return item.Category == AetheriaRuntimeItemCategories.Hull;
|
2026-06-14 09:31:17 +01:00
|
|
|
default:
|
|
|
|
|
throw new ArgumentOutOfRangeException(nameof(candidateKind), candidateKind, null);
|
2026-06-13 13:35:00 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-15 17:25:40 +01:00
|
|
|
private float ManufacturerDistancePenalty(string manufacturerKey)
|
2026-06-13 13:24:36 +01:00
|
|
|
{
|
2026-06-15 17:25:40 +01:00
|
|
|
if (Galaxy == null || Zone == null || !Galaxy.ContainsFaction(manufacturerKey))
|
2026-06-13 13:24:36 +01:00
|
|
|
{
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-15 17:25:40 +01:00
|
|
|
var faction = Galaxy.ResolveFactionByKey(manufacturerKey);
|
2026-06-13 13:24:36 +01:00
|
|
|
return faction != null && Galaxy.HomeZones.TryGetValue(faction, out var homeZone)
|
|
|
|
|
? Zone.Distance[homeZone]
|
|
|
|
|
: 1;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-14 09:31:17 +01:00
|
|
|
private AetheriaRuntimeCatalogItem RandomCatalogItem(
|
|
|
|
|
RuntimeItemCandidateKind candidateKind,
|
2026-06-13 23:20:16 +01:00
|
|
|
float sizeExponent,
|
2026-06-14 09:31:17 +01:00
|
|
|
Predicate<AetheriaRuntimeCatalogItem> typedFilter = null)
|
2021-03-20 04:20:17 +00:00
|
|
|
{
|
2026-06-14 09:31:17 +01:00
|
|
|
return RandomCatalogItems(candidateKind, 1, sizeExponent, typedFilter).FirstOrDefault();
|
2021-03-18 00:36:34 +00:00
|
|
|
}
|
|
|
|
|
|
2026-06-14 09:31:17 +01:00
|
|
|
private AetheriaRuntimeCatalogItem RandomCatalogItem(
|
|
|
|
|
RuntimeItemCandidateKind candidateKind,
|
|
|
|
|
AetheriaRuntimeHardpoint hardpoint,
|
|
|
|
|
float sizeExponent,
|
|
|
|
|
Predicate<AetheriaRuntimeCatalogItem> filter = null)
|
2021-03-18 00:36:34 +00:00
|
|
|
{
|
2026-06-14 09:31:17 +01:00
|
|
|
return RandomCatalogItem(
|
|
|
|
|
candidateKind,
|
2026-06-13 23:20:16 +01:00
|
|
|
sizeExponent,
|
2026-06-14 05:25:56 +01:00
|
|
|
item => FitsHardpoint(item, hardpoint) && (filter?.Invoke(item) ?? true));
|
2026-06-13 23:20:16 +01:00
|
|
|
}
|
|
|
|
|
|
2026-06-14 05:25:56 +01:00
|
|
|
private static bool FitsHardpoint(AetheriaRuntimeCatalogItem item, AetheriaRuntimeHardpoint hardpoint)
|
2026-06-13 23:20:16 +01:00
|
|
|
{
|
2026-06-14 05:25:56 +01:00
|
|
|
return item != null &&
|
|
|
|
|
hardpoint != null &&
|
|
|
|
|
string.Equals(item.HardpointType, hardpoint.Type, StringComparison.Ordinal) &&
|
|
|
|
|
item.OccupiedCells == hardpoint.OccupiedCells &&
|
|
|
|
|
FitsWithin(item, ToShape(hardpoint), GetRotation(hardpoint));
|
2026-06-13 23:20:16 +01:00
|
|
|
}
|
|
|
|
|
|
2026-06-13 23:26:50 +01:00
|
|
|
private static bool HasBehaviorKind(AetheriaRuntimeCatalogItem item, string behaviorKind)
|
|
|
|
|
{
|
|
|
|
|
return !string.IsNullOrWhiteSpace(behaviorKind) &&
|
|
|
|
|
item.BehaviorKinds.Contains(behaviorKind, StringComparer.Ordinal);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-13 23:20:16 +01:00
|
|
|
private static bool FitsWithin(AetheriaRuntimeCatalogItem item, Shape target)
|
|
|
|
|
{
|
2026-06-14 05:25:56 +01:00
|
|
|
if (item == null)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ToShape(item).FitsWithin(target, out _, out _);
|
2026-06-13 23:20:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static bool FitsWithin(AetheriaRuntimeCatalogItem item, Shape target, ItemRotation rotation)
|
|
|
|
|
{
|
2026-06-14 05:25:56 +01:00
|
|
|
if (item == null)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ToShape(item).FitsWithin(target, rotation, out _);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static Shape ToShape(AetheriaRuntimeCatalogItem item)
|
|
|
|
|
{
|
|
|
|
|
if (item == null)
|
|
|
|
|
{
|
|
|
|
|
throw new InvalidLoadoutException("Typed catalog shape was requested before a compatible item was selected.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ToShape(item.ShapeWidth, item.ShapeHeight, item.ShapeCells);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static Shape ToShape(AetheriaRuntimeHardpoint hardpoint)
|
|
|
|
|
{
|
|
|
|
|
if (hardpoint == null)
|
|
|
|
|
{
|
|
|
|
|
throw new InvalidLoadoutException("Typed hardpoint shape was requested before a hardpoint was selected.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ToShape(hardpoint.ShapeWidth, hardpoint.ShapeHeight, hardpoint.ShapeCells);
|
2026-06-13 23:20:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static Shape ToShape(int width, int height, IReadOnlyList<AetheriaRuntimeShapeCell> cells)
|
|
|
|
|
{
|
|
|
|
|
var shape = new Shape(Math.Max(width, 1), Math.Max(height, 1));
|
|
|
|
|
foreach (var cell in cells)
|
|
|
|
|
{
|
2026-06-21 09:37:54 +01:00
|
|
|
shape.SetCell(cell.X, cell.Y, true);
|
2026-06-13 23:20:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return shape;
|
2021-03-18 00:36:34 +00:00
|
|
|
}
|
|
|
|
|
|
2026-06-14 05:25:56 +01:00
|
|
|
private static ItemRotation GetRotation(AetheriaRuntimeHardpoint hardpoint)
|
|
|
|
|
{
|
|
|
|
|
return Enum.TryParse(hardpoint.Rotation, out ItemRotation rotation)
|
|
|
|
|
? rotation
|
|
|
|
|
: ItemRotation.None;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-18 00:36:34 +00:00
|
|
|
private void OutfitEntity(Entity entity)
|
|
|
|
|
{
|
2026-06-14 05:25:56 +01:00
|
|
|
var typedHull = ItemManager.GetRuntimeItem(entity.Hull);
|
|
|
|
|
if (typedHull == null)
|
2021-03-18 00:36:34 +00:00
|
|
|
{
|
2026-06-14 05:25:56 +01:00
|
|
|
throw new InvalidLoadoutException("Selected hull is missing from the typed runtime catalog.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var v in ToShape(typedHull).Coordinates) entity.HullConductivity[v.x, v.y] = true;
|
|
|
|
|
var previousItems = new List<AetheriaRuntimeCatalogItem>();
|
|
|
|
|
foreach (var hardpoint in typedHull.Hardpoints.OrderByDescending(h=>h.OccupiedCells))
|
|
|
|
|
{
|
|
|
|
|
if (hardpoint.Type == nameof(HardpointType.ControlModule))
|
2021-03-18 00:36:34 +00:00
|
|
|
{
|
2026-06-13 23:26:50 +01:00
|
|
|
var controllerBehaviorKind = entity is Ship
|
2026-06-14 21:48:43 +01:00
|
|
|
? "Cockpit"
|
2026-06-13 23:26:50 +01:00
|
|
|
: entity is OrbitalEntity
|
2026-06-14 21:48:43 +01:00
|
|
|
? "TurretController"
|
2026-06-13 23:26:50 +01:00
|
|
|
: null;
|
2026-06-14 09:31:17 +01:00
|
|
|
var controllerRow = RandomCatalogItem(RuntimeItemCandidateKind.Gear, hardpoint, 2, item => HasBehaviorKind(item, controllerBehaviorKind));
|
2026-06-14 07:27:23 +01:00
|
|
|
if (controllerRow == null)
|
2021-03-27 10:03:24 +00:00
|
|
|
throw new InvalidLoadoutException("No compatible controller found for entity!");
|
2026-06-14 07:27:23 +01:00
|
|
|
var controller = ItemManager.CreateEquippableInstance(controllerRow);
|
2021-03-18 00:36:34 +00:00
|
|
|
if (!entity.TryEquip(controller))
|
|
|
|
|
{
|
2026-06-14 05:25:56 +01:00
|
|
|
throw new InvalidLoadoutException($"Failed to equip selected {hardpoint.Type}!");
|
2021-03-18 00:36:34 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2021-03-20 04:20:17 +00:00
|
|
|
// If a previously selected item fits, use that one (this is why we must process larger hardpoints first)
|
2026-06-14 05:25:56 +01:00
|
|
|
var itemRow = previousItems.FirstOrDefault(i => FitsHardpoint(i, hardpoint));
|
|
|
|
|
var previousItem = itemRow == null
|
|
|
|
|
? null
|
2026-06-15 01:12:22 +01:00
|
|
|
: entity.Equipment.FirstOrDefault(item => item.EquippableItem.ItemKey == itemRow.ItemKey);
|
2026-06-14 09:31:17 +01:00
|
|
|
itemRow ??= RandomCatalogItem(RuntimeItemCandidateKind.Gear, hardpoint, 2);
|
2026-06-14 07:27:23 +01:00
|
|
|
if (itemRow == null) ItemManager.Log($"No compatible item found for entity {hardpoint.Type} hardpoint!");
|
2021-03-18 00:36:34 +00:00
|
|
|
else
|
|
|
|
|
{
|
2026-06-14 05:25:56 +01:00
|
|
|
//throw new InvalidLoadoutException($"No compatible item found for entity {hardpoint.Type} hardpoint!");
|
2021-03-27 10:03:24 +00:00
|
|
|
EquippableItem item;
|
|
|
|
|
if(previousItem!=null)
|
2026-06-14 07:27:23 +01:00
|
|
|
item = ItemManager.CreateEquippableInstance(itemRow, previousItem.EquippableItem.Quality);
|
|
|
|
|
else item = ItemManager.CreateEquippableInstance(itemRow);
|
2021-03-18 00:36:34 +00:00
|
|
|
if (!entity.TryEquip(item))
|
|
|
|
|
{
|
2026-06-14 05:25:56 +01:00
|
|
|
throw new InvalidLoadoutException($"Failed to equip selected {hardpoint.Type}!");
|
2021-03-18 00:36:34 +00:00
|
|
|
}
|
2026-06-14 05:25:56 +01:00
|
|
|
previousItems.Add(itemRow);
|
2021-03-18 00:36:34 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-20 04:20:17 +00:00
|
|
|
var emptyShape = entity.UnoccupiedSpace;
|
|
|
|
|
|
2026-06-14 09:38:55 +01:00
|
|
|
var cargoRow = RandomCatalogItem(RuntimeItemCandidateKind.CargoBay, 3, item => item.Category != AetheriaRuntimeItemCategories.DockingBay && FitsWithin(item, emptyShape));
|
2026-06-14 07:27:23 +01:00
|
|
|
if (cargoRow == null) throw new InvalidLoadoutException("No compatible cargo bay found for entity!");
|
2021-03-20 04:20:17 +00:00
|
|
|
|
2026-06-14 05:25:56 +01:00
|
|
|
ToShape(cargoRow).FitsWithin(emptyShape, out var cargoRotation, out var cargoPosition);
|
2026-06-14 07:27:23 +01:00
|
|
|
var cargo = ItemManager.CreateEquippableInstance(cargoRow);
|
2021-03-20 04:20:17 +00:00
|
|
|
cargo.Rotation = cargoRotation;
|
|
|
|
|
if (!entity.TryEquip(cargo, cargoPosition))
|
|
|
|
|
throw new InvalidLoadoutException("Failed to equip selected cargo bay!");
|
|
|
|
|
|
|
|
|
|
emptyShape = entity.UnoccupiedSpace;
|
2021-03-18 00:36:34 +00:00
|
|
|
|
2026-06-14 09:31:17 +01:00
|
|
|
var capacitorRow = RandomCatalogItem(RuntimeItemCandidateKind.Gear, 2,
|
2026-06-14 21:48:43 +01:00
|
|
|
item => item.BehaviorKinds.Contains("Capacitor", StringComparer.Ordinal) &&
|
2026-06-13 23:20:16 +01:00
|
|
|
FitsWithin(item, emptyShape));
|
2026-06-14 07:27:23 +01:00
|
|
|
if (capacitorRow == null) throw new InvalidLoadoutException("No compatible capacitor found for entity!");
|
2021-03-18 00:36:34 +00:00
|
|
|
|
2026-06-14 05:25:56 +01:00
|
|
|
ToShape(capacitorRow).FitsWithin(emptyShape, out var capacitorRotation, out var capacitorPosition);
|
2026-06-14 07:27:23 +01:00
|
|
|
var capacitor = ItemManager.CreateEquippableInstance(capacitorRow);
|
2021-03-18 00:36:34 +00:00
|
|
|
capacitor.Rotation = capacitorRotation;
|
|
|
|
|
if (!entity.TryEquip(capacitor, capacitorPosition))
|
|
|
|
|
throw new InvalidLoadoutException("Failed to equip selected capacitor!");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class InvalidLoadoutException : Exception
|
|
|
|
|
{
|
|
|
|
|
public InvalidLoadoutException()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public InvalidLoadoutException(string message)
|
|
|
|
|
: base(message)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public InvalidLoadoutException(string message, Exception inner)
|
|
|
|
|
: base(message, inner)
|
|
|
|
|
{
|
|
|
|
|
}
|
2026-06-13 10:49:29 +01:00
|
|
|
}
|