Sector & Loadout Generation Fixes
Adjusted sector generation to improve sample elimination performance and better preserve density map Added unit tests for item shape rotation and fit functions + fixed those (required for correct loadout generation)! Fixed faction hostility by marking player ship Added faction to entity serialization Generating starting loadout for player ship
This commit is contained in:
parent
ebde23a64d
commit
351cb9f2af
23 changed files with 879 additions and 128 deletions
|
|
@ -39,6 +39,7 @@ public static class EntitySerializer
|
|||
|
||||
pack.Hull = entity.Hull;
|
||||
pack.Name = entity.Name;
|
||||
pack.Faction = entity.Faction?.ID ?? Guid.Empty;
|
||||
pack.Equipment = entity.Equipment.Select(e => (e.Position, e.EquippableItem)).ToArray();
|
||||
pack.CargoBays = entity.CargoBays.Select(e => (e.Position, e.EquippableItem)).ToArray();
|
||||
pack.DockingBays = entity.DockingBays.Select(e => (e.Position, e.EquippableItem)).ToArray();
|
||||
|
|
@ -86,6 +87,7 @@ public static class EntitySerializer
|
|||
private static void Restore(ItemManager itemManager, Zone zone, EntityPack pack, Entity entity, bool instantiate = false)
|
||||
{
|
||||
entity.Name = pack.Name;
|
||||
entity.Faction = itemManager.ItemData.Get<MegaCorporation>(pack.Faction);
|
||||
entity.Children = pack.Children.Select(c =>
|
||||
{
|
||||
var child = Unpack(itemManager, zone, c, instantiate);
|
||||
|
|
@ -138,15 +140,15 @@ public static class EntitySerializer
|
|||
[MessagePackObject]
|
||||
public class ShipPack : EntityPack
|
||||
{
|
||||
[Key(15)] public float3 Position;
|
||||
[Key(16)] public float2 Direction;
|
||||
[Key(17)] public bool IsPlayerShip;
|
||||
[Key(16)] public float3 Position;
|
||||
[Key(17)] public float2 Direction;
|
||||
[Key(18)] public bool IsPlayerShip;
|
||||
}
|
||||
|
||||
[MessagePackObject]
|
||||
public class OrbitalEntityPack : EntityPack
|
||||
{
|
||||
[Key(15)]
|
||||
[Key(16)]
|
||||
public Guid Orbit;
|
||||
}
|
||||
|
||||
|
|
@ -197,6 +199,9 @@ public abstract class EntityPack
|
|||
[Key(14)]
|
||||
public EntitySettings Settings;
|
||||
|
||||
[Key(15)]
|
||||
public Guid Faction;
|
||||
|
||||
private int _price;
|
||||
|
||||
public int Price(ItemManager itemManager)
|
||||
|
|
|
|||
|
|
@ -68,17 +68,13 @@ public class Shape
|
|||
|
||||
public int2 Rotate(int2 position, ItemRotation rotation)
|
||||
{
|
||||
switch (rotation)
|
||||
return rotation switch
|
||||
{
|
||||
case ItemRotation.Clockwise:
|
||||
return int2(position.y, Width - 1 - position.x);
|
||||
case ItemRotation.Reversed:
|
||||
return int2(Width - 1 - position.x, Height - 1 - position.y);
|
||||
case ItemRotation.CounterClockwise:
|
||||
return int2(Height - 1 - position.y, position.x);
|
||||
default:
|
||||
return int2(position.x, position.y);
|
||||
}
|
||||
ItemRotation.Clockwise => int2(position.y, Width - 1 - position.x),
|
||||
ItemRotation.Reversed => int2(Width - 1 - position.x, Height - 1 - position.y),
|
||||
ItemRotation.CounterClockwise => int2(Height - 1 - position.y, position.x),
|
||||
_ => int2(position.x, position.y)
|
||||
};
|
||||
}
|
||||
|
||||
private int2[] _cachedShapeCoordinates;
|
||||
|
|
@ -199,10 +195,12 @@ public class Shape
|
|||
// Try to find a position with which a shape can be placed to fit within another shape
|
||||
public bool FitsWithin(Shape other, ItemRotation rotation, out int2 position)
|
||||
{
|
||||
var width = rotation == ItemRotation.Clockwise || rotation == ItemRotation.CounterClockwise ? Height : Width;
|
||||
var height = rotation == ItemRotation.Clockwise || rotation == ItemRotation.CounterClockwise ? Width : Height;
|
||||
// Try every item position that could possibly fit
|
||||
for(int x = 0; x < other.Width - Width + 1; x++)
|
||||
for(int x = 0; x < other.Width - width + 1; x++)
|
||||
{
|
||||
for (int y = 0; y < other.Height - Height + 1; y++)
|
||||
for (int y = 0; y < other.Height - height + 1; y++)
|
||||
{
|
||||
position = int2(x, y);
|
||||
var fits = true;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
|
|
@ -50,10 +51,10 @@ public class LoadoutGenerator
|
|||
public HullData RandomHull(HullType type)
|
||||
{
|
||||
return ItemManager.ItemData.GetAll<HullData>()
|
||||
.Where(item => item.HullType == type && (item.Manufacturer == Faction.ID || Faction.Allegiance.ContainsKey(item.Manufacturer)))
|
||||
.Where(item => item.HullType == type && (Faction == null || item.Manufacturer == Faction.ID || Faction.Allegiance.ContainsKey(item.Manufacturer)))
|
||||
.WeightedRandomElements(ref Random,
|
||||
item =>
|
||||
(item.Manufacturer == Faction.ID ? 1 : Faction.Allegiance[item.Manufacturer]) / // Prioritize items from allied manufacturers
|
||||
(Faction == null || item.Manufacturer == Faction.ID ? 1 : Faction.Allegiance[item.Manufacturer]) / // Prioritize items from allied manufacturers
|
||||
Zone.Distance[Sector.HomeZones[ItemManager.ItemData.Get<MegaCorporation>(item.Manufacturer)]] / // Penalize distance to manufacturer headquarters
|
||||
pow(item.Price, PriceExponent), // Penalize item price to a controllable degree
|
||||
1
|
||||
|
|
@ -64,10 +65,10 @@ public class LoadoutGenerator
|
|||
{
|
||||
return ItemManager.ItemData.GetAll<T>()
|
||||
.Where(item => Sector.ContainsFaction(item.Manufacturer) &&
|
||||
(item.Manufacturer == Faction.ID || Faction.Allegiance.ContainsKey(item.Manufacturer)) &&
|
||||
(Faction == null || item.Manufacturer == Faction.ID || Faction.Allegiance.ContainsKey(item.Manufacturer)) &&
|
||||
(filter?.Invoke(item) ?? true))
|
||||
.WeightedRandomElements(ref Random, item =>
|
||||
(item.Manufacturer == Faction.ID ? 1 : Faction.Allegiance[item.Manufacturer]) * // Prioritize items from allied manufacturers
|
||||
(Faction == null || item.Manufacturer == Faction.ID ? 1 : Faction.Allegiance[item.Manufacturer]) * // Prioritize items from allied manufacturers
|
||||
item.Shape.Coordinates.Length / // Prioritize items that fill more of the hardpoint's slots
|
||||
Zone.Distance[Sector.HomeZones[ItemManager.ItemData.Get<MegaCorporation>(item.Manufacturer)]] / // Penalize distance to manufacturer headquarters
|
||||
pow(item.Price, PriceExponent), // Penalize item price to a controllable degree
|
||||
|
|
@ -85,6 +86,8 @@ public class LoadoutGenerator
|
|||
private void OutfitEntity(Entity entity)
|
||||
{
|
||||
var hullData = ItemManager.GetData(entity.Hull) as HullData;
|
||||
foreach (var v in hullData.Shape.Coordinates) entity.HullConductivity[v.x, v.y] = true;
|
||||
var previousItems = new List<EquippableItemData>();
|
||||
foreach (var hardpoint in hullData.Hardpoints)
|
||||
{
|
||||
if (hardpoint.Type == HardpointType.ControlModule)
|
||||
|
|
@ -100,7 +103,10 @@ public class LoadoutGenerator
|
|||
}
|
||||
else
|
||||
{
|
||||
var itemData = RandomItem<GearData>(hardpoint);
|
||||
// If a previously selected item fits, use that one
|
||||
var itemData = previousItems
|
||||
.FirstOrDefault(i => i.HardpointType == hardpoint.Type && i.Shape.FitsWithin(hardpoint.Shape, hardpoint.Rotation, out _));
|
||||
itemData ??= RandomItem<GearData>(hardpoint);
|
||||
if (itemData == null) ItemManager.Log($"No compatible item found for entity {Enum.GetName(typeof(HardpointType), hardpoint.Type)} hardpoint!");
|
||||
else
|
||||
{
|
||||
|
|
@ -110,6 +116,7 @@ public class LoadoutGenerator
|
|||
{
|
||||
throw new InvalidLoadoutException($"Failed to equip selected {Enum.GetName(typeof(HardpointType), hardpoint.Type)}!");
|
||||
}
|
||||
previousItems.Add(itemData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ public static class WeightedSampleElimination
|
|||
// of a sample point based on the placement of its neighbors within d_max radius. The weight function
|
||||
// must have the following form:
|
||||
//
|
||||
// float weightFunction( float2 p0, float2 p1, float dist2, float dmax )
|
||||
// float weightFunction( float2 p0, float2 p1, float dist2, float dmax, float density )
|
||||
//
|
||||
// The arguments p0 and p1 are the two neighboring points, dist2 is the square of the Euclidean distance
|
||||
// between these two points, and d_max is the current radius for the weight function.
|
||||
|
|
@ -45,44 +45,46 @@ public static class WeightedSampleElimination
|
|||
// different than the d_max value passed to this method.
|
||||
public static void Eliminate (
|
||||
float2[] inputPoints,
|
||||
float2[] outputPoints,
|
||||
Func<float2, float2, float, float, float> weightFunction,
|
||||
float2[] outputPoints,
|
||||
Func<float2, float2, float, float, float, float> weightFunction,
|
||||
Func<float2, float> densityFunction,
|
||||
float d_max = 0)
|
||||
{
|
||||
if ( d_max < .001f ) d_max = 2 * GetMaxPoissonDiskRadius( outputPoints.Length );
|
||||
DoEliminate( inputPoints, outputPoints, d_max, weightFunction );
|
||||
DoEliminate( inputPoints, outputPoints, d_max, weightFunction, densityFunction );
|
||||
}
|
||||
|
||||
public static void Eliminate(
|
||||
float2[] inputPoints,
|
||||
float2[] outputPoints,
|
||||
float d_max = 0,
|
||||
float alpha = 8,
|
||||
float beta = 0.65f,
|
||||
float gamma = 1.5f)
|
||||
{
|
||||
if ( d_max < .001f ) d_max = 2 * GetMaxPoissonDiskRadius( outputPoints.Length );
|
||||
float d_min = d_max * GetWeightLimitFraction( inputPoints.Length, outputPoints.Length, beta, gamma );
|
||||
Eliminate( inputPoints, outputPoints, (p0, p1, d2, dmax) =>
|
||||
{
|
||||
float d = sqrt(d2);
|
||||
if ( d < d_min ) d = d_min;
|
||||
return pow( 1f / (d / dmax + .01f), alpha );
|
||||
}, d_max);
|
||||
}
|
||||
// public static void Eliminate(
|
||||
// float2[] inputPoints,
|
||||
// float2[] outputPoints,
|
||||
// float d_max = 0,
|
||||
// float alpha = 8,
|
||||
// float beta = 0.65f,
|
||||
// float gamma = 1.5f)
|
||||
// {
|
||||
// if ( d_max < .001f ) d_max = 2 * GetMaxPoissonDiskRadius( outputPoints.Length );
|
||||
// float d_min = d_max * GetWeightLimitFraction( inputPoints.Length, outputPoints.Length, beta, gamma );
|
||||
// Eliminate( inputPoints, outputPoints, (p0, p1, d2, dmax, density) =>
|
||||
// {
|
||||
// float d = sqrt(d2);
|
||||
// if ( d < d_min ) d = d_min;
|
||||
// return pow( 1f / (d / dmax + .01f), alpha );
|
||||
// }, v => 1, d_max);
|
||||
// }
|
||||
|
||||
public static void Eliminate(
|
||||
float2[] inputPoints,
|
||||
float2[] outputPoints,
|
||||
Func<float2, float> density = null,
|
||||
Func<float2, float> densityFunc = null,
|
||||
float d_max = 0)
|
||||
{
|
||||
if (densityFunc == null) densityFunc = v => 1;
|
||||
if ( d_max < .001f ) d_max = 2 * GetMaxPoissonDiskRadius( outputPoints.Length );
|
||||
Eliminate( inputPoints, outputPoints, (p0, p1, d2, dmax) =>
|
||||
Eliminate( inputPoints, outputPoints, (p0, p1, d2, dmax, density) =>
|
||||
{
|
||||
float d = sqrt(d2);
|
||||
return pow( 1 - (d / dmax), 5 + 6 * saturate((density ?? (v => .5f))((p0 + p1) / 2)) );
|
||||
}, d_max);
|
||||
return pow( 1 - d / dmax, 5 + 6 * density );
|
||||
}, densityFunc, d_max);
|
||||
}
|
||||
|
||||
public static float2[] GeneratePoints(int count, Func<float2, float> density = null, Func<float2, float> envelope = null, Action<string> progressCallback = null, uint seed = 1337)
|
||||
|
|
@ -127,7 +129,8 @@ public static class WeightedSampleElimination
|
|||
float2[] inputPoints,
|
||||
float2[] outputPoints,
|
||||
float d_max,
|
||||
Func<float2, float2, float, float, float> weightFunction
|
||||
Func<float2, float2, float, float, float, float> weightFunction,
|
||||
Func<float2, float> density
|
||||
)
|
||||
{
|
||||
// Build a k-d tree for samples
|
||||
|
|
@ -137,13 +140,15 @@ public static class WeightedSampleElimination
|
|||
var dmax2 = d_max * d_max;
|
||||
// Assign weights to each sample
|
||||
float[] weights = new float[inputPoints.Length];
|
||||
float[] densities = new float[inputPoints.Length];
|
||||
for (int i = 0; i < inputPoints.Length; i++)
|
||||
{
|
||||
var p = inputPoints[i];
|
||||
var point = inputPoints[i];
|
||||
densities[i] += density(point);
|
||||
foreach (var (qi, d2) in query.Radius(kdtree, inputPoints[i], d_max))
|
||||
{
|
||||
var p2 = inputPoints[qi];
|
||||
weights[i] += weightFunction(p, p2, d2, d_max);
|
||||
var otherPoint = inputPoints[qi];
|
||||
weights[i] += weightFunction(point, otherPoint, d2, d_max, densities[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -157,13 +162,13 @@ public static class WeightedSampleElimination
|
|||
while (sampleCount > outputPoints.Length)
|
||||
{
|
||||
// Pull the top sample from heap
|
||||
var i = heap.PopObj();
|
||||
var p = inputPoints[i];
|
||||
var elim = heap.PopObj();
|
||||
var pElim = inputPoints[elim];
|
||||
// For each sample around it, remove its weight contribution and update the heap
|
||||
foreach (var (qi, d2) in query.Radius(kdtree, p, d_max))
|
||||
foreach (var (qi, d2) in query.Radius(kdtree, pElim, d_max))
|
||||
{
|
||||
var p2 = inputPoints[qi];
|
||||
weights[qi] -= weightFunction(p, p2, d2, d_max);
|
||||
var point = inputPoints[qi];
|
||||
weights[qi] -= weightFunction(point, pElim, d2, d_max, densities[qi]);
|
||||
heap.SetValue(qi, weights[qi]);
|
||||
}
|
||||
sampleCount--;
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ public class Sector
|
|||
|
||||
// Create a delaunay triangulation to connect adjacent sectors
|
||||
var triangulation = DelaunayTriangulation<Vertex2<SectorZone>, Cell2<SectorZone>>
|
||||
.Create(Zones.Select(z => new Vertex2<SectorZone>(z.Position, z)).ToList(), 1e-5f);
|
||||
.Create(Zones.Select(z => new Vertex2<SectorZone>(z.Position, z)).ToList(), 1e-7f);
|
||||
var links = new HashSet<(SectorZone, SectorZone)>();
|
||||
foreach(var cell in triangulation.Cells)
|
||||
{
|
||||
|
|
@ -104,22 +104,22 @@ public class Sector
|
|||
|
||||
progressCallback?.Invoke("Eliminating Zone Links");
|
||||
if(progressCallback!=null) Thread.Sleep(500); // Inserting Delay to make it seem like it's doing more work lmao
|
||||
foreach (var link in links.ToArray())
|
||||
{
|
||||
foreach (var zone in Zones)
|
||||
{
|
||||
if(zone != link.Item1 && zone != link.Item2)
|
||||
if (AetheriaMath.FindDistanceToSegment(zone.Position, link.Item1.Position, link.Item2.Position, out _) < minLineSeparation)
|
||||
links.Remove(link);
|
||||
}
|
||||
}
|
||||
// foreach (var link in links.ToArray())
|
||||
// {
|
||||
// foreach (var zone in Zones)
|
||||
// {
|
||||
// if(zone != link.Item1 && zone != link.Item2)
|
||||
// if (AetheriaMath.FindDistanceToSegment(zone.Position, link.Item1.Position, link.Item2.Position, out _) < minLineSeparation)
|
||||
// links.Remove(link);
|
||||
// }
|
||||
// }
|
||||
|
||||
foreach (var link in links)
|
||||
{
|
||||
link.Item1.AdjacentZones.Add(link.Item2);
|
||||
link.Item2.AdjacentZones.Add(link.Item1);
|
||||
}
|
||||
|
||||
|
||||
float LinkWeight((SectorZone, SectorZone) link)
|
||||
{
|
||||
return 1 / saturate(settings.CloudDensity((link.Item1.Position + link.Item2.Position) / 2)) *
|
||||
|
|
@ -158,12 +158,12 @@ public class Sector
|
|||
|
||||
progressCallback?.Invoke("Finding Chokepoints");
|
||||
if(progressCallback!=null) Thread.Sleep(500); // Inserting Delay to make it seem like it's doing more work lmao
|
||||
|
||||
// Entrance is the most isolated zone (highest total distance to all other zones
|
||||
Entrance = Zones.MaxBy(z => z.Isolation);
|
||||
|
||||
// Exit is the zone furthest from the entrance
|
||||
Exit = Zones.MaxBy(z => Entrance.Distance[z]);
|
||||
// Exit is the most isolated zone (highest total distance to all other zones)
|
||||
Exit = Zones.MaxBy(z => z.Isolation);
|
||||
|
||||
// Entrance is the zone furthest from the exit
|
||||
Entrance = Zones.MaxBy(z => Exit.Distance[z]);
|
||||
|
||||
// Find all zones on the exit path where removing that zone would disconnect the entrance from the exit
|
||||
// Disregard "corridor" zones with only two adjacent zones
|
||||
|
|
@ -226,7 +226,7 @@ public class Sector
|
|||
|
||||
for (var i = 0; i < Factions.Length; i++)
|
||||
{
|
||||
progressCallback?.Invoke($"Feeding Markov Chains: {i} / {Factions.Length}");
|
||||
progressCallback?.Invoke($"Feeding Markov Chains: {i+1} / {Factions.Length}");
|
||||
//if(progressCallback!=null) Thread.Sleep(250); // Inserting Delay to make it seem like it's doing more work lmao
|
||||
var faction = Factions[i];
|
||||
_nameGenerators[faction] = new MarkovNameGenerator(ref random, database.Get<NameFile>(faction.GeonameFile).Names, settings);
|
||||
|
|
@ -241,7 +241,7 @@ public class Sector
|
|||
}
|
||||
else
|
||||
{
|
||||
zone.Name = $"EAC-{random.NextInt(99999).ToString()}";
|
||||
zone.Name = $"EAC-{random.NextInt(9999).ToString()}";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue