Save/Load and Main Menu Submenus
Redid all of the property panel UI prefabs (oof) Implemented saving and loading of the game state Moved sector generation to sector constructor (along with loading from save file) Entity settings now separate from player settings Player settings (incl. saved run) now statically accessible from game manager Added animations and submenus to the main menu Unified main menu settings and properties panel styles for prefab reusability
This commit is contained in:
parent
549811f403
commit
b6828a975a
63 changed files with 2007 additions and 1158 deletions
|
|
@ -2,17 +2,20 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using DataStructures.ViliWonka.Heap;
|
||||
using Unity.Mathematics;
|
||||
using MIConvexHull;
|
||||
using JM.LinqFaster;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using Random = Unity.Mathematics.Random;
|
||||
|
||||
public class Sector
|
||||
{
|
||||
public List<SectorZone> Zones = new List<SectorZone>();
|
||||
public Dictionary<MegaCorporation, SectorZone> HomeZones = new Dictionary<MegaCorporation, SectorZone>();
|
||||
public Dictionary<MegaCorporation, SectorZone> BossZones = new Dictionary<MegaCorporation, SectorZone>();
|
||||
public SectorZone Entrance;
|
||||
public SectorZone Exit;
|
||||
|
||||
public SectorZone[] Zones { get; }
|
||||
public SectorZone Entrance { get; }
|
||||
public SectorZone Exit { get; }
|
||||
|
||||
private SectorZone[] _exitPath;
|
||||
|
||||
|
|
@ -24,6 +27,188 @@ public class Sector
|
|||
}
|
||||
}
|
||||
|
||||
public Sector(DatabaseCache database, SavedGame savedGame)
|
||||
{
|
||||
var factions = savedGame.Factions.Select(database.Get<MegaCorporation>).ToArray();
|
||||
Zones = savedGame.Zones.Select(zone =>
|
||||
{
|
||||
return new SectorZone
|
||||
{
|
||||
Name = zone.Name,
|
||||
Position = zone.Position
|
||||
};
|
||||
}).ToArray();
|
||||
for (var i = 0; i < Zones.Length; i++)
|
||||
{
|
||||
Zones[i].AdjacentZones = savedGame.Zones[i].AdjacentZones.Select(azi => Zones[azi]).ToList();
|
||||
Zones[i].Factions = savedGame.Zones[i].Factions.Select(mi => factions[mi]).ToArray();
|
||||
Zones[i].Owner = factions[savedGame.Zones[i].Owner];
|
||||
}
|
||||
|
||||
HomeZones = savedGame.HomeZones.ToDictionary(
|
||||
x => factions[x.Key],
|
||||
x => Zones[x.Value]);
|
||||
|
||||
BossZones = savedGame.BossZones.ToDictionary(
|
||||
x => factions[x.Key],
|
||||
x => Zones[x.Value]);
|
||||
|
||||
Entrance = Zones[savedGame.Entrance];
|
||||
Exit = Zones[savedGame.Exit];
|
||||
|
||||
CalculateDistanceMatrix();
|
||||
}
|
||||
|
||||
public Sector(SectorGenerationSettings settings, MegaCorporation[] factions, ref Random random, float minLineSeparation = .01f)
|
||||
{
|
||||
var outputSamples = WeightedSampleElimination.GeneratePoints(settings.ZoneCount,
|
||||
settings.CloudDensity,
|
||||
v => (.2f - lengthsq(v - float2(.5f))) * 4);
|
||||
Zones = outputSamples.Select(v => new SectorZone {Position = v}).ToArray();
|
||||
|
||||
#region Link Placement
|
||||
|
||||
// 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);
|
||||
var links = new HashSet<(SectorZone, SectorZone)>();
|
||||
foreach(var cell in triangulation.Cells)
|
||||
{
|
||||
if(!links.Contains((cell.Vertices[0].StoredObject, cell.Vertices[1].StoredObject)) &&
|
||||
!links.Contains((cell.Vertices[1].StoredObject, cell.Vertices[0].StoredObject)))
|
||||
links.Add((cell.Vertices[0].StoredObject, cell.Vertices[1].StoredObject));
|
||||
if(!links.Contains((cell.Vertices[1].StoredObject, cell.Vertices[2].StoredObject)) &&
|
||||
!links.Contains((cell.Vertices[2].StoredObject, cell.Vertices[1].StoredObject)))
|
||||
links.Add((cell.Vertices[1].StoredObject, cell.Vertices[2].StoredObject));
|
||||
if(!links.Contains((cell.Vertices[0].StoredObject, cell.Vertices[2].StoredObject)) &&
|
||||
!links.Contains((cell.Vertices[2].StoredObject, cell.Vertices[0].StoredObject)))
|
||||
links.Add((cell.Vertices[0].StoredObject, cell.Vertices[2].StoredObject));
|
||||
}
|
||||
|
||||
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)) *
|
||||
lengthsq(link.Item1.Position - link.Item2.Position) *
|
||||
(link.Item1.AdjacentZones.Count - 1) * (link.Item2.AdjacentZones.Count - 1);
|
||||
}
|
||||
|
||||
var heap = new MaxHeap<(SectorZone, SectorZone)>(links.Count);
|
||||
foreach(var link in links) heap.PushObj(link, LinkWeight(link));
|
||||
while (heap.Count > settings.LinkDensity * links.Count)
|
||||
{
|
||||
var link = heap.PopObj();
|
||||
if(ConnectedRegion(link.Item1, link.Item1, link.Item2).Contains(link.Item2))
|
||||
{
|
||||
link.Item1.AdjacentZones.Remove(link.Item2);
|
||||
link.Item2.AdjacentZones.Remove(link.Item1);
|
||||
foreach (var secondary in link.Item1.AdjacentZones)
|
||||
{
|
||||
heap.SetValue((link.Item1, secondary), LinkWeight((link.Item1, secondary)));
|
||||
heap.SetValue((secondary, link.Item1), LinkWeight((secondary, link.Item1)));
|
||||
}
|
||||
|
||||
foreach (var secondary in link.Item2.AdjacentZones)
|
||||
{
|
||||
heap.SetValue((link.Item2, secondary), LinkWeight((link.Item2, secondary)));
|
||||
heap.SetValue((secondary, link.Item2), LinkWeight((secondary, link.Item2)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
CalculateDistanceMatrix();
|
||||
|
||||
// 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 exit
|
||||
Exit = 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
|
||||
var chokePoints = ExitPath
|
||||
.Where(z => z.AdjacentZones.Count > 2 && !ConnectedRegion(Entrance, z).Contains(Exit));
|
||||
|
||||
// Choose some megas to have bosses placed based on whether a boss hull is assigned
|
||||
var bossMegas = factions
|
||||
.Where(m => m.BossHull != Guid.Empty)
|
||||
.Take(settings.BossCount)
|
||||
.ToArray();
|
||||
|
||||
// Place boss zones along the critical path as far apart from each other as possible
|
||||
foreach (var mega in bossMegas)
|
||||
{
|
||||
BossZones[mega] = chokePoints.MaxBy(z =>
|
||||
Exit.Distance[z] * Entrance.Distance[z] *
|
||||
BossZones.Values.Aggregate(1, (i, os) => i * os.Distance[z]));
|
||||
}
|
||||
|
||||
// Place boss mega headquarters such that their sphere of influence encompasses their boss zone
|
||||
// While occupying as much territory as possible
|
||||
foreach (var mega in bossMegas)
|
||||
{
|
||||
HomeZones[mega] = ConnectedRegion(BossZones[mega], mega.InfluenceDistance)
|
||||
.MaxBy(z =>
|
||||
ConnectedRegion(z, mega.InfluenceDistance).Count *
|
||||
HomeZones.Values.Aggregate(1f, (i, os) => i * sqrt(os.Distance[z])));
|
||||
}
|
||||
|
||||
// Place remaining headquarters away from existing megas while also maximizing territory
|
||||
foreach (var mega in factions.Where(m=>!bossMegas.Contains(m)))
|
||||
{
|
||||
HomeZones[mega] = Zones.MaxBy(z =>
|
||||
pow(ConnectedRegion(z, mega.InfluenceDistance).Count, HomeZones.Count) *
|
||||
Exit.Distance[z] * Entrance.Distance[z] *
|
||||
HomeZones.Values.Aggregate(1f, (i, os) => i * sqrt(os.Distance[z])) *
|
||||
BossZones.Values.Aggregate(1f, (i, os) => i * sqrt(os.Distance[z])));
|
||||
}
|
||||
|
||||
// Assign faction presence
|
||||
foreach (var zone in Zones)
|
||||
{
|
||||
// Factions are present in all zones within their sphere of influence
|
||||
zone.Factions = factions
|
||||
.Where(m => zone.Distance[HomeZones[m]] <= m.InfluenceDistance)
|
||||
.ToArray();
|
||||
|
||||
// Owner of a zone is the faction with the nearest headquarters
|
||||
var nearestFaction = factions.MinBy(m => zone.Distance[HomeZones[m]]);
|
||||
if (zone.Distance[HomeZones[nearestFaction]] <= nearestFaction.InfluenceDistance)
|
||||
zone.Owner = nearestFaction;
|
||||
}
|
||||
|
||||
// Generate zone name using the owner's name generator, otherwise assign catalogue ID
|
||||
foreach (var zone in Zones)
|
||||
zone.Name = zone.Owner?.NameGenerator.NextName ?? $"EAC-{random.NextInt(99999).ToString()}";
|
||||
}
|
||||
|
||||
// Cache distance matrix and calculate isolation for every zone (used extensively for placing stuff)
|
||||
private void CalculateDistanceMatrix()
|
||||
{
|
||||
foreach (var zone in Zones)
|
||||
{
|
||||
zone.Distance = ConnectedRegionDistance(zone);
|
||||
zone.Isolation = zone.Distance.Sum(x => x.Value);
|
||||
}
|
||||
}
|
||||
|
||||
class DijkstraVertex
|
||||
{
|
||||
public float Cost;
|
||||
|
|
@ -31,26 +216,6 @@ public class Sector
|
|||
public SectorZone Zone;
|
||||
}
|
||||
|
||||
// public HashSet<SectorZone> ConnectedRegion(SectorZone v, SectorZone ignoreZone = null)
|
||||
// {
|
||||
// var members = new HashSet<SectorZone> {v};
|
||||
// while (true)
|
||||
// {
|
||||
// var lastCount = members.Count;
|
||||
// // For each member, add all vertices that are connected to it but are not already a member
|
||||
// // Also, if there is a zone being ignored, do not traverse across it
|
||||
// foreach (var zone in members.ToArray())
|
||||
// foreach(var adjacentZone in zone.AdjacentZones)
|
||||
// if (!members.Contains(adjacentZone) && !(zone == v && ignoreZone == adjacentZone))
|
||||
// members.Add(adjacentZone);
|
||||
// // If we have stopped finding neighbors, stop traversing
|
||||
// if (members.Count == lastCount)
|
||||
// return members;
|
||||
// }
|
||||
// }
|
||||
|
||||
//public int TotalDistance(SectorZone v) => ConnectedRegionDistance(v).Sum(x => x.distance);
|
||||
|
||||
public Dictionary<SectorZone, int> ConnectedRegionDistance(SectorZone v)
|
||||
{
|
||||
var members = new Dictionary<SectorZone, int> {{v, 0}};
|
||||
|
|
@ -75,11 +240,11 @@ public class Sector
|
|||
|
||||
public HashSet<SectorZone> ConnectedRegion(
|
||||
SectorZone v,
|
||||
SectorZone ignoreLink1,
|
||||
SectorZone ignoreLink2,
|
||||
SectorZone ignoreLinkSource,
|
||||
SectorZone ignoreLinkTarget,
|
||||
int maxDistance = int.MaxValue)
|
||||
{
|
||||
return ConnectedRegion(v, x => x.Item1 != ignoreLink1 || x.Item2 != ignoreLink2, maxDistance);
|
||||
return ConnectedRegion(v, x => x.Item1 != ignoreLinkSource || x.Item2 != ignoreLinkTarget, maxDistance);
|
||||
}
|
||||
|
||||
public HashSet<SectorZone> ConnectedRegion(
|
||||
|
|
@ -99,7 +264,7 @@ public class Sector
|
|||
|
||||
public HashSet<SectorZone> ConnectedRegion(
|
||||
SectorZone v,
|
||||
Predicate<(SectorZone,SectorZone)> linkFilter,
|
||||
Predicate<(SectorZone source, SectorZone target)> linkFilter,
|
||||
int maxDistance = int.MaxValue)
|
||||
{
|
||||
var visited = new HashSet<SectorZone> {v};
|
||||
|
|
@ -160,7 +325,7 @@ public class SectorZone
|
|||
public List<SectorZone> AdjacentZones = new List<SectorZone>();
|
||||
public int Isolation;
|
||||
public Dictionary<SectorZone, int> Distance;
|
||||
public MegaCorporation[] Megas;
|
||||
//public List<MegaCorporation> Megas;
|
||||
public MegaCorporation[] Factions;
|
||||
public MegaCorporation Owner;
|
||||
public Zone Contents;
|
||||
}
|
||||
Loading…
Reference in a new issue