Merge faction-curated-place-names (7da0d414), --no-ff
Code from eureka/aetheria-release-faction-curated-place-names. Conflicts kept both sides: Faction holds Assembly (key 19) and Places (key 21); AetherDb dispatches assembly-from-maker, place-names and doctrine-catalog, and its usage names both; FlightTests counts the Faction wire at 22 slots (19 is Assembly, 21 is Places).
The two LFS files are the merge's resolution, replayed on the post-assembly bytes, never merged as text or by side.
GameData/Aetheria.cc, per h1's two-step replay: (1) AetherDb place-names apply built at the branch's commit 1 (740e1ee7, merged onto the assembly merge so key 19 survives), run on the post-assembly catalog: purges the Markov NameFile records while their type exists and authors 15 place lists; (2) place-names apply built at this merged tree, run on step 1's output: authors Miss Terri's list (1 list). Each step's second apply said nothing to do and left the bytes identical; the whole chain run twice from the assembly catalog gave bytes equal except the record write timestamps (masked, both hash alike). Decoded independently with cultcache-py: 16 factions, key 19 Assembly filled on the 15 makers (Ewan Hart has none), key 21 Places 20 names on all 16, key 9 nil on all, no namefile type among the 321 records.
Assets/Resources/Settings.asset: the post-assembly file (Rarity values removed) with the four NameGeneratorSettings lines removed (NameGeneratorSettings, MinLength 5, MaxLength 10, Order 3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
55bda75ac3
57 changed files with 584 additions and 1891 deletions
|
|
@ -1,8 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0f3e518a4c9bcc2469abe6cebdcb3691
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Resources/Geonames/Australia.csv
(Stored with Git LFS)
BIN
Assets/Resources/Geonames/Australia.csv
(Stored with Git LFS)
Binary file not shown.
|
|
|
@ -1,7 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8ba25d5d0ecee31408c7ce7460b4e015
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Resources/Geonames/Congo.csv
(Stored with Git LFS)
BIN
Assets/Resources/Geonames/Congo.csv
(Stored with Git LFS)
Binary file not shown.
|
|
|
@ -1,7 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1e43b383bf860524986a6947496776d0
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Resources/Geonames/Germany.csv
(Stored with Git LFS)
BIN
Assets/Resources/Geonames/Germany.csv
(Stored with Git LFS)
Binary file not shown.
|
|
|
@ -1,7 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ed470b8eb6cc68c4485ddc05d5a17ca1
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Resources/Geonames/India.csv
(Stored with Git LFS)
BIN
Assets/Resources/Geonames/India.csv
(Stored with Git LFS)
Binary file not shown.
|
|
|
@ -1,7 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ca62f6d649360184aaa975c2716dfb27
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Resources/Geonames/Iran.csv
(Stored with Git LFS)
BIN
Assets/Resources/Geonames/Iran.csv
(Stored with Git LFS)
Binary file not shown.
|
|
|
@ -1,7 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e2d60c7158f6a01418d78e46c5845729
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Resources/Geonames/Japan.csv
(Stored with Git LFS)
BIN
Assets/Resources/Geonames/Japan.csv
(Stored with Git LFS)
Binary file not shown.
|
|
|
@ -1,7 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5dd450a6f5ec92d49871674b33509135
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Resources/Geonames/Mexico.csv
(Stored with Git LFS)
BIN
Assets/Resources/Geonames/Mexico.csv
(Stored with Git LFS)
Binary file not shown.
|
|
|
@ -1,7 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b75e17ea31f6a62488628bf9605bd48c
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Resources/Geonames/Russia.csv
(Stored with Git LFS)
BIN
Assets/Resources/Geonames/Russia.csv
(Stored with Git LFS)
Binary file not shown.
|
|
|
@ -1,7 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5c1aee30ccf3a1e44bddeb4d550edbf0
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Resources/Geonames/Swahili.csv
(Stored with Git LFS)
BIN
Assets/Resources/Geonames/Swahili.csv
(Stored with Git LFS)
Binary file not shown.
|
|
|
@ -1,7 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 63e21276398c8b0449e01d97a088add1
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Resources/Geonames/Thailand.csv
(Stored with Git LFS)
BIN
Assets/Resources/Geonames/Thailand.csv
(Stored with Git LFS)
Binary file not shown.
|
|
|
@ -1,7 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2923f8bd3a2f0fb4084f64138e6d793d
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Resources/Geonames/USA.csv
(Stored with Git LFS)
BIN
Assets/Resources/Geonames/USA.csv
(Stored with Git LFS)
Binary file not shown.
|
|
|
@ -1,7 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a06c0e26f73d51f489303deadad77030
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,7 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 790ef350f8658b04480e0645919a21fa
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Resources/Geonames/pleiades.csv
(Stored with Git LFS)
BIN
Assets/Resources/Geonames/pleiades.csv
(Stored with Git LFS)
Binary file not shown.
|
|
|
@ -1,7 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b532694a5ec3cc6458d42aa2835537b5
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Resources/Settings.asset
(Stored with Git LFS)
BIN
Assets/Resources/Settings.asset
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1,93 +0,0 @@
|
|||
using System.Buffers.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using MessagePack;
|
||||
using CultMath;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using Random = CultMath.Random;
|
||||
|
||||
public class NameTools : EditorWindow
|
||||
{
|
||||
public int NameGeneratorMinLength = 5;
|
||||
public int NameGeneratorMaxLength = 10;
|
||||
public int NameGeneratorOrder = 4;
|
||||
private int minWordLength = 4;
|
||||
private TextAsset nameFile;
|
||||
private MarkovNameGenerator _nameGenerator;
|
||||
private bool _stripNumberTokens;
|
||||
|
||||
|
||||
// Add menu named "My Window" to the Window menu
|
||||
[MenuItem("Window/Aetheria/Name Tools")]
|
||||
static void Init()
|
||||
{
|
||||
// Get existing open window or if none, make a new one:
|
||||
NameTools window = (NameTools)EditorWindow.GetWindow(typeof(NameTools));
|
||||
window.Show();
|
||||
}
|
||||
|
||||
bool HasNonASCIIChars(string str)
|
||||
{
|
||||
return (System.Text.Encoding.UTF8.GetByteCount(str) != str.Length);
|
||||
}
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
|
||||
nameFile = (TextAsset) EditorGUILayout.ObjectField("Name File", nameFile, typeof(TextAsset), false);
|
||||
minWordLength = EditorGUILayout.IntField("Minimum File Word Length", minWordLength);
|
||||
NameGeneratorMinLength = EditorGUILayout.IntField("Generated Minimum Word Length", NameGeneratorMinLength);
|
||||
NameGeneratorMaxLength = EditorGUILayout.IntField("Generated Maximum Word Length", NameGeneratorMaxLength);
|
||||
NameGeneratorOrder = EditorGUILayout.IntField("Generator Order", NameGeneratorOrder);
|
||||
_stripNumberTokens = GUILayout.Toggle(_stripNumberTokens, "Strip Number Tokens");
|
||||
|
||||
if (GUILayout.Button("Clean Name File"))
|
||||
{
|
||||
var lines = nameFile.text.Split('\n');
|
||||
using StreamWriter outputFile = new StreamWriter(Path.Combine(Application.dataPath, nameFile.name + ".csv"));
|
||||
var names = new HashSet<string>();
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var tokens = line.Split(',', ' ');
|
||||
foreach (var t in tokens)
|
||||
{
|
||||
if (!HasNonASCIIChars(t))
|
||||
{
|
||||
var s = new string(t.Where(c => char.IsLetter(c) || c == '-' || c == '`' || c == '\'').ToArray()).Trim().Trim('`','-');
|
||||
if(s.Length >= minWordLength && !names.Contains(s))
|
||||
{
|
||||
names.Add(s);
|
||||
outputFile.WriteLine(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Process Name File") && nameFile != null)
|
||||
{
|
||||
var names = new HashSet<string>();
|
||||
var lines = nameFile.text.Split('\n');
|
||||
foreach (var line in lines)
|
||||
{
|
||||
foreach(var word in line.ToUpperInvariant().Split(' ', ',', '.', '"'))
|
||||
if (word.Length >= minWordLength && !names.Contains(word))
|
||||
names.Add(word);
|
||||
}
|
||||
Debug.Log($"Found {lines.Length} lines, with {names.Count} unique names!");
|
||||
var random = new Random(1337);
|
||||
_nameGenerator = new MarkovNameGenerator(ref random, names, NameGeneratorOrder, NameGeneratorMinLength, NameGeneratorMaxLength);
|
||||
}
|
||||
|
||||
if (_nameGenerator != null)
|
||||
{
|
||||
if (GUILayout.Button("Generate Name"))
|
||||
{
|
||||
Debug.Log(_nameGenerator.NextName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 88b5f578fe63a904b997331630e61416
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -22,7 +22,6 @@ public class GameSettings : ScriptableObject
|
|||
public float MinimapAsteroidSize = 3;
|
||||
public float MinimapIconSize = 1f / 32;
|
||||
public float PlanetRotationSpeed = 1;
|
||||
public NameGeneratorSettings NameGeneratorSettings;
|
||||
public TutorialGenerationSettings TutorialGenerationSettings;
|
||||
public SectorBackgroundSettings TutorialBackgroundSettings;
|
||||
public SectorBackgroundSettings SectorBackgroundSettings;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ using GameCult.Caching.MessagePack;
|
|||
// Aetheria's stores: one home store per type, composed here and nowhere else.
|
||||
public static class AetheriaStores
|
||||
{
|
||||
public static readonly Type[] CatalogTypes = { typeof(ItemData), typeof(ShipAuthoring), typeof(Faction), typeof(FactionProductData), typeof(PersonalityAttribute), typeof(NameFile), typeof(InputLayout), typeof(Loadout), typeof(FieldKindData), typeof(VerseGrammar), typeof(VerseVerb) };
|
||||
public static readonly Type[] CatalogTypes = { typeof(ItemData), typeof(ShipAuthoring), typeof(Faction), typeof(FactionProductData), typeof(PersonalityAttribute), typeof(InputLayout), typeof(Loadout), typeof(FieldKindData), typeof(VerseGrammar), typeof(VerseVerb) };
|
||||
public static readonly Type[] RunTypes = { typeof(OrbitData), typeof(BodyData), typeof(SavedZone), typeof(SavedGame), typeof(ProvenanceLedger) };
|
||||
public static readonly Type[] PlayerTypes = { typeof(PlayerSettings) };
|
||||
|
||||
|
|
|
|||
|
|
@ -37,8 +37,7 @@ public class Faction
|
|||
[InspectableColor, JsonProperty("secondaryColor"), Key(8)]
|
||||
public float3 SecondaryColor;
|
||||
|
||||
[Inspectable, JsonProperty("nameFile"), Key(9)]
|
||||
public CultRecordRef<NameFile> GeonameFile;
|
||||
// Key 9 was the faction's name-file link, retired by ruling faction-curated-place-names; never reuse it.
|
||||
|
||||
[Inspectable, JsonProperty("bossHull"), Key(10)]
|
||||
public CultRecordRef<HullData> BossHull;
|
||||
|
|
@ -70,11 +69,23 @@ public class Faction
|
|||
// tune it in Studio after that. Key 16 is reserved for Relations and 18 for Livery.
|
||||
[Inspectable, JsonProperty("assembly"), Key(19)]
|
||||
public AssemblyDistribution Assembly;
|
||||
|
||||
// Curated place names in authored order; the home zone takes the first (ruling faction-curated-place-names).
|
||||
// Authored by AetherDb place-names, which restores the table on every apply.
|
||||
[Inspectable, JsonProperty("places"), Key(21)]
|
||||
public FactionPlace[] Places = Array.Empty<FactionPlace>();
|
||||
}
|
||||
|
||||
[CultDocument("aetheria.namefile", "1"), Inspectable, MessagePackObject]
|
||||
public class NameFile
|
||||
// Historical, narrative and strategic intent join as keys 2+ when economics-driven placement lands (ruling faction-curated-place-names).
|
||||
[MessagePackObject, JsonObject(MemberSerialization.OptIn)]
|
||||
public class FactionPlace
|
||||
{
|
||||
[CultName, Key(1)] public string Name;
|
||||
[Key(2)] public string[] Names;
|
||||
[Inspectable, JsonProperty("name"), Key(1)]
|
||||
public string Name;
|
||||
|
||||
// The one rule for a valid place name: trimmed, non-empty and not a catalogue designation (EAC-, any case). Galaxy.NamePlaces
|
||||
// treats an invalid entry as absent; AetherDb place-names refuses a table that holds one.
|
||||
public static bool IsValidName(string name) =>
|
||||
!string.IsNullOrEmpty(name) && name == name.Trim() && !name.StartsWith("EAC-", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ public class Galaxy
|
|||
public HashSet<GalaxyZone> DiscoveredZones = new HashSet<GalaxyZone>();
|
||||
|
||||
public SectorBackgroundSettings Background { get; }
|
||||
public NameGeneratorSettings NameGeneratorSettings { get; }
|
||||
public Faction[] Factions { get; }
|
||||
public GalaxyZone[] Zones { get; }
|
||||
public GalaxyZone Entrance { get; }
|
||||
|
|
@ -34,7 +33,6 @@ public class Galaxy
|
|||
public uint Seed { get; }
|
||||
|
||||
private GalaxyZone[] _exitPath;
|
||||
private Dictionary<Faction, MarkovNameGenerator> _nameGenerators = new Dictionary<Faction, MarkovNameGenerator>();
|
||||
private readonly CultCache _cache;
|
||||
|
||||
public GalaxyZone[] ExitPath
|
||||
|
|
@ -96,7 +94,6 @@ public class Galaxy
|
|||
public Galaxy(
|
||||
SectorGenerationSettings settings,
|
||||
SectorBackgroundSettings background,
|
||||
NameGeneratorSettings nameGeneratorSettings,
|
||||
CultCache cache,
|
||||
Action<string> log,
|
||||
Action<string> progressCallback = null,
|
||||
|
|
@ -131,7 +128,7 @@ public class Galaxy
|
|||
|
||||
CalculateFactionInfluence(progressCallback);
|
||||
|
||||
GenerateNames(cache, nameGeneratorSettings, ref random, progressCallback);
|
||||
NamePlaces(ref random, progressCallback);
|
||||
|
||||
progressCallback?.Invoke("Done!");
|
||||
if(progressCallback!=null) Thread.Sleep(500); // Inserting Delay to make it seem like it's doing more work lmao
|
||||
|
|
@ -162,7 +159,6 @@ public class Galaxy
|
|||
public Galaxy(
|
||||
TutorialGenerationSettings settings,
|
||||
SectorBackgroundSettings background,
|
||||
NameGeneratorSettings nameGeneratorSettings,
|
||||
CultCache cache,
|
||||
PlayerSettings playerSettings,
|
||||
DirectoryInfo narrativeDirectory,
|
||||
|
|
@ -259,7 +255,7 @@ public class Galaxy
|
|||
|
||||
CalculateFactionInfluence(progressCallback);
|
||||
|
||||
GenerateNames(cache, nameGeneratorSettings, ref random, progressCallback);
|
||||
NamePlaces(ref random, progressCallback);
|
||||
|
||||
// progressCallback?.Invoke("Weaving Narrative");
|
||||
// var processor = new StoryProcessor(playerSettings, narrativeDirectory, this, ref random, Log);
|
||||
|
|
@ -350,31 +346,37 @@ public class Galaxy
|
|||
}
|
||||
}
|
||||
|
||||
private void GenerateNames(CultCache cache,
|
||||
NameGeneratorSettings nameGeneratorSettings,
|
||||
ref Random random,
|
||||
Action<string> progressCallback = null)
|
||||
// Each faction names the zones it owns from its own curated list (ruling faction-curated-place-names): nearest its
|
||||
// home first, so the home zone takes the capital, which is Places[0]. A zone left over when a list is spent, and every
|
||||
// zone nobody owns, takes a catalogue designation. A name is never used twice, ignoring case.
|
||||
internal void NamePlaces(ref Random random, Action<string> progressCallback = null)
|
||||
{
|
||||
var used = new HashSet<string>();
|
||||
for (var i = 0; i < Factions.Length; i++)
|
||||
progressCallback?.Invoke("Naming places");
|
||||
var used = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var zone in Zones) zone.Name = null;
|
||||
|
||||
foreach (var faction in Factions)
|
||||
{
|
||||
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, cache.Get(faction.GeonameFile).Names, nameGeneratorSettings, used);
|
||||
if (!HomeZones.TryGetValue(faction, out var home)) continue;
|
||||
var places = (faction.Places ?? Array.Empty<FactionPlace>()).Select(p => p?.Name).Where(FactionPlace.IsValidName).ToArray();
|
||||
var next = 0;
|
||||
var owned = Zones.Select((zone, index) => (zone, index)).Where(z => z.zone.Owner == faction)
|
||||
.OrderBy(z => z.zone.Distance[home]).ThenBy(z => z.index);
|
||||
foreach (var (zone, _) in owned)
|
||||
{
|
||||
while (next < places.Length && used.Contains(places[next])) next++;
|
||||
if (next == places.Length) break;
|
||||
zone.Name = places[next];
|
||||
used.Add(places[next++]);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate zone name using the owner's name generator, otherwise assign catalogue ID
|
||||
foreach (var zone in Zones)
|
||||
foreach (var zone in Zones.Where(z => z.Name == null))
|
||||
{
|
||||
if (zone.Owner != null)
|
||||
{
|
||||
zone.Name = _nameGenerators[zone.Owner].NextName.Trim();
|
||||
}
|
||||
else
|
||||
{
|
||||
zone.Name = $"EAC-{random.NextInt(9999).ToString()}";
|
||||
}
|
||||
string designation;
|
||||
do designation = $"EAC-{random.NextInt(9999)}";
|
||||
while (!used.Add(designation));
|
||||
zone.Name = designation;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ public sealed class GalaxyStage
|
|||
private readonly SectorBackgroundSettings _sectorBackground;
|
||||
private readonly TutorialGenerationSettings _prelude;
|
||||
private readonly SectorBackgroundSettings _preludeBackground;
|
||||
private readonly NameGeneratorSettings _names;
|
||||
private readonly CultCache _cache;
|
||||
private readonly PlayerSettings _playerSettings;
|
||||
private readonly DirectoryInfo _narrativeDirectory;
|
||||
|
|
@ -28,7 +27,6 @@ public sealed class GalaxyStage
|
|||
SectorBackgroundSettings sectorBackground,
|
||||
TutorialGenerationSettings prelude,
|
||||
SectorBackgroundSettings preludeBackground,
|
||||
NameGeneratorSettings names,
|
||||
CultCache cache,
|
||||
PlayerSettings playerSettings,
|
||||
DirectoryInfo narrativeDirectory,
|
||||
|
|
@ -40,7 +38,6 @@ public sealed class GalaxyStage
|
|||
_sectorBackground = sectorBackground;
|
||||
_prelude = prelude;
|
||||
_preludeBackground = preludeBackground;
|
||||
_names = names;
|
||||
_cache = cache;
|
||||
_playerSettings = playerSettings;
|
||||
_narrativeDirectory = narrativeDirectory;
|
||||
|
|
@ -64,7 +61,7 @@ public sealed class GalaxyStage
|
|||
var background = Copy(_sectorBackground);
|
||||
var random = new Random(Seed);
|
||||
background.NoisePosition = random.NextFloat() * 1000;
|
||||
return new Galaxy(_sector, background, _names, _cache, _log, _progress, Seed);
|
||||
return new Galaxy(_sector, background, _cache, _log, _progress, Seed);
|
||||
}
|
||||
|
||||
// A prelude galaxy: the tutorial settings and their fixed factions, centred in dense cloud.
|
||||
|
|
@ -81,7 +78,7 @@ public sealed class GalaxyStage
|
|||
background.NoisePosition = random.NextFloat() * 1000;
|
||||
_progress?.Invoke($"Finding Galaxy Position: iteration {iteration++}");
|
||||
} while (background.CloudDensity(float2(.5f)) < .5f);
|
||||
return new Galaxy(cast, background, _names, _cache, _playerSettings, _narrativeDirectory, _log, _progress, Seed);
|
||||
return new Galaxy(cast, background, _cache, _playerSettings, _narrativeDirectory, _log, _progress, Seed);
|
||||
}
|
||||
|
||||
private static SectorBackgroundSettings Copy(SectorBackgroundSettings settings) =>
|
||||
|
|
|
|||
|
|
@ -1,129 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using static CultMath.math;
|
||||
using Random = CultMath.Random;
|
||||
|
||||
//Generates random names based on the statistical weight of letter sequences
|
||||
//in a collection of sample names
|
||||
public class MarkovNameGenerator
|
||||
{
|
||||
private Dictionary<string, List<char>> _chains = new Dictionary<string, List<char>>();
|
||||
private List<string> _samples = new List<string>();
|
||||
// The names already given, shared by every generator that must not repeat another's: a galaxy passes one set to
|
||||
// all its factions' generators. Never process-wide, so one galaxy's names cannot change the next galaxy's.
|
||||
private readonly HashSet<string> _used;
|
||||
private int _order;
|
||||
private int _minLength;
|
||||
private int _maxLength;
|
||||
private Random _random;
|
||||
|
||||
public MarkovNameGenerator(ref Random random, IEnumerable<string> sampleNames, NameGeneratorSettings settings, HashSet<string> used)
|
||||
{
|
||||
_used = used;
|
||||
var names = new HashSet<string>();
|
||||
foreach (var line in sampleNames)
|
||||
{
|
||||
foreach(var word in line.ToUpperInvariant().Split(' ', ',', '.', '"'))
|
||||
if (word.Length >= settings.NameGeneratorMinLength && !names.Contains(word))
|
||||
names.Add(word);
|
||||
}
|
||||
|
||||
_random = random;
|
||||
|
||||
_order = max(1, settings.NameGeneratorOrder);
|
||||
_minLength = max(1, settings.NameGeneratorMinLength);
|
||||
_maxLength = settings.NameGeneratorMaxLength;
|
||||
|
||||
Initialize(names);
|
||||
}
|
||||
|
||||
public MarkovNameGenerator(ref Random random, IEnumerable<string> sampleNames, int order, int minLength, int maxLength)
|
||||
{
|
||||
_used = new HashSet<string>();
|
||||
_random = random;
|
||||
|
||||
_order = max(1, order);
|
||||
_minLength = max(1, minLength);
|
||||
_maxLength = maxLength;
|
||||
|
||||
Initialize(sampleNames);
|
||||
}
|
||||
|
||||
private void Initialize(IEnumerable<string> sampleNames)
|
||||
{
|
||||
//split comma delimited lines
|
||||
foreach (string line in sampleNames)
|
||||
{
|
||||
string[] tokens = line.Split(' ','\'','(',')');
|
||||
foreach (string word in tokens)
|
||||
{
|
||||
string upper = word.Trim().ToLowerInvariant();
|
||||
if (upper.Length < _order + 1)
|
||||
continue;
|
||||
_samples.Add(upper + '|'); // Add end character
|
||||
}
|
||||
}
|
||||
|
||||
//Build chains
|
||||
foreach (string word in _samples)
|
||||
{
|
||||
for (int letter = 0; letter < word.Length - _order; letter++)
|
||||
{
|
||||
string token = word.Substring(letter, _order);
|
||||
List<char> entry = null;
|
||||
if (_chains.ContainsKey(token))
|
||||
entry = _chains[token];
|
||||
else
|
||||
{
|
||||
entry = new List<char>();
|
||||
_chains[token] = entry;
|
||||
}
|
||||
entry.Add(word[letter + _order]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Get the next random name
|
||||
public string NextName
|
||||
{
|
||||
get
|
||||
{
|
||||
|
||||
string s = "";
|
||||
do
|
||||
{
|
||||
int n = _random.NextInt(0,_samples.Count);
|
||||
//int nameLength = _samples[n].Length;
|
||||
s = _samples[n].Substring(0, _order);//get a random token at the start of a sample word
|
||||
while (true)//(s.Length < nameLength)
|
||||
{
|
||||
string token = s.Substring(s.Length - _order, _order);
|
||||
char c = GetLetter(token);
|
||||
if (c != '?' && c != '|')
|
||||
s += c;//GetLetter(token);
|
||||
else
|
||||
break;
|
||||
}
|
||||
s = s.Substring(0, 1).ToUpper() + s.Substring(1);
|
||||
}
|
||||
while (_used.Contains(s) || s.Length < _minLength || s.Length > _maxLength);
|
||||
_used.Add(s);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
//Get a random letter from the chain
|
||||
private char GetLetter(string token)
|
||||
{
|
||||
if (!_chains.ContainsKey(token))
|
||||
return '?';
|
||||
List<char> letters = _chains[token];
|
||||
int n = _random.NextInt(0,letters.Count);
|
||||
return letters[n];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 33c3b119ccc9de447b8c8318b6f0d1af
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -44,14 +44,6 @@ public class GalaxyShapeSettings
|
|||
public float TwistExponent = 2;
|
||||
}
|
||||
|
||||
[Serializable, MessagePackObject(keyAsPropertyName:true), JsonObject]
|
||||
public class NameGeneratorSettings
|
||||
{
|
||||
public int NameGeneratorMinLength = 5;
|
||||
public int NameGeneratorMaxLength = 10;
|
||||
public int NameGeneratorOrder = 4;
|
||||
}
|
||||
|
||||
[Serializable, MessagePackObject(keyAsPropertyName:true), JsonObject]
|
||||
public class SectorBackgroundSettings
|
||||
{
|
||||
|
|
|
|||
|
|
@ -177,7 +177,6 @@ public class MainMenu : MonoBehaviour
|
|||
Settings.SectorBackgroundSettings,
|
||||
Settings.TutorialGenerationSettings,
|
||||
Settings.TutorialBackgroundSettings,
|
||||
Settings.NameGeneratorSettings,
|
||||
ActionGameManager.CultCache,
|
||||
ActionGameManager.PlayerSettings,
|
||||
ActionGameManager.GameDataDirectory.CreateSubdirectory("Narrative"),
|
||||
|
|
|
|||
BIN
GameData/Aetheria.cc
(Stored with Git LFS)
BIN
GameData/Aetheria.cc
(Stored with Git LFS)
Binary file not shown.
|
|
@ -40,7 +40,7 @@ public sealed class ChildrenWithinReachTests : IClassFixture<ChildrenWithinReach
|
|||
var authored = AuthoredSettings.Load(RunStartTests.FindRepoRoot());
|
||||
var galaxy = new Galaxy(authored.Read<TutorialGenerationSettings>("TutorialGenerationSettings"),
|
||||
authored.Read<SectorBackgroundSettings>("TutorialBackgroundSettings"),
|
||||
authored.Read<NameGeneratorSettings>("NameGeneratorSettings"), cache, new PlayerSettings(),
|
||||
cache, new PlayerSettings(),
|
||||
Directory.CreateDirectory(Path.Combine(dir, "Narrative")), _ => { }, null, seed);
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), authored.Read<GameplaySettings>("GameplaySettings"), _ => { });
|
||||
var planetSettings = authored.Read<PlanetSettings>("PlanetSettings");
|
||||
|
|
|
|||
|
|
@ -280,8 +280,7 @@ public sealed class ConsumableSupplyTests : IDisposable
|
|||
|
||||
var authored = AuthoredSettings.Load(repo);
|
||||
var galaxy = new Galaxy(authored.Read<TutorialGenerationSettings>("TutorialGenerationSettings"),
|
||||
authored.Read<SectorBackgroundSettings>("TutorialBackgroundSettings"), authored.Read<NameGeneratorSettings>("NameGeneratorSettings"),
|
||||
cache, new PlayerSettings(), Directory.CreateDirectory(Path.Combine(_root, "Narrative")), _ => { }, null, 1);
|
||||
authored.Read<SectorBackgroundSettings>("TutorialBackgroundSettings"), cache, new PlayerSettings(), Directory.CreateDirectory(Path.Combine(_root, "Narrative")), _ => { }, null, 1);
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), authored.Read<GameplaySettings>("GameplaySettings"), _ => { });
|
||||
|
||||
int stations = 0, ships = 0;
|
||||
|
|
|
|||
|
|
@ -910,7 +910,8 @@ public sealed class FlightTests : IDisposable
|
|||
Assert.Equal(turns, turn != 0f);
|
||||
}
|
||||
|
||||
// Faction.Doctrine rides at wire slot 17; slot 16 is reserved for Relations. Kills: the key moved.
|
||||
// Faction.Doctrine rides at wire slot 17; slot 16 is reserved for Relations; Places rides at 21, which sets the array's length.
|
||||
// Kills: the key moved.
|
||||
[Fact]
|
||||
public void TheFactionDoctrineRidesAtWireSlot17()
|
||||
{
|
||||
|
|
@ -919,7 +920,7 @@ public sealed class FlightTests : IDisposable
|
|||
var slots = MessagePack.MessagePackSerializer.Deserialize<object[]>(MessagePack.MessagePackSerializer.Serialize(faction,
|
||||
CultDocumentMessagePackSerialization.OptionsFor(typeof(Faction).Assembly)));
|
||||
|
||||
Assert.Equal(20, slots.Length); // 19 is Assembly
|
||||
Assert.Equal(22, slots.Length); // 19 is Assembly, 21 is Places
|
||||
Assert.Null(slots[16]);
|
||||
var doctrine = Assert.IsType<object[]>(slots[17]);
|
||||
Assert.Equal(6f, Convert.ToSingle(doctrine[1]));
|
||||
|
|
|
|||
|
|
@ -814,7 +814,7 @@ public sealed partial class MiningCut3Tests : IDisposable
|
|||
try
|
||||
{
|
||||
galaxy = new Galaxy(RestoredHullsTests.TutorialGalaxySettings(), RestoredHullsTests.TutorialBackgroundSettings(),
|
||||
RestoredHullsTests.TutorialNameSettings(), cache, new PlayerSettings(), new DirectoryInfo(dir), _ => { }, null, seed);
|
||||
cache, new PlayerSettings(), new DirectoryInfo(dir), _ => { }, null, seed);
|
||||
}
|
||||
catch (Exception) { continue; } // Galaxy's own seed-dependent flakiness (RestoredHullsTests)
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), GameSettings(), _ => { });
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ public sealed class MinorPowersTests : IDisposable
|
|||
foreach (var shortName in Minor)
|
||||
{
|
||||
var faction = Assert.Single(factions, f => f.ShortName == shortName);
|
||||
Assert.NotNull(cache.Get(faction.GeonameFile));
|
||||
Assert.NotEmpty(faction.Places);
|
||||
Assert.True(faction.InfluenceDistance > 0, shortName);
|
||||
var named = faction.Allegiance.Keys.Select(k => k.Key).ToArray();
|
||||
var expected = factions.Where(f => f != faction && f.ShortName != "Pirates").Select(f => cache.RefOf(f).Key).ToArray();
|
||||
|
|
|
|||
|
|
@ -253,7 +253,7 @@ public sealed class OrbitsFromFieldTests : IDisposable
|
|||
var authored = AuthoredSettings.Load(RunStartTests.FindRepoRoot());
|
||||
var tutorial = authored.Read<TutorialGenerationSettings>("TutorialGenerationSettings");
|
||||
var galaxy = new Galaxy(tutorial, authored.Read<SectorBackgroundSettings>("TutorialBackgroundSettings"),
|
||||
authored.Read<NameGeneratorSettings>("NameGeneratorSettings"), cache, new PlayerSettings(),
|
||||
cache, new PlayerSettings(),
|
||||
Directory.CreateDirectory(Path.Combine(dir, "Narrative")), _ => { }, null, seed);
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), authored.Read<GameplaySettings>("GameplaySettings"), _ => { });
|
||||
var pack = ZoneGenerator.GenerateZone(items, authored.Read<ZoneGenerationSettings>("ZoneSettings"), authored.Read<PlanetSettings>("PlanetSettings"), galaxy, galaxy.Entrance,
|
||||
|
|
|
|||
|
|
@ -82,7 +82,6 @@ public sealed class PiratesFactionCommandTests : IDisposable
|
|||
var db = AetherDb.Open(root: _root);
|
||||
try
|
||||
{
|
||||
Assert.Equal("pleiades", db.Cache.Get(faction.GeonameFile).Name);
|
||||
var expected = new (string Design, string Name, string Description)[]
|
||||
{
|
||||
("Autocannon", "🫳🔫", "Serial number filed off. Still shoots."),
|
||||
|
|
@ -172,17 +171,4 @@ public sealed class PiratesFactionCommandTests : IDisposable
|
|||
Assert.Throws<InvalidOperationException>(() => Program.PiratesFaction(apply: true, root: _root));
|
||||
Assert.Empty(Read().Pirates);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_missing_name_file_refuses_and_lands_nothing()
|
||||
{
|
||||
var db = AetherDb.Open(catalogWritable: true, root: _root);
|
||||
var pleiades = db.Cache.GetAll<NameFile>().Single(file => file.Name == "pleiades");
|
||||
Assert.True(db.Cache.Remove(db.Cache.RefOf(pleiades).Key));
|
||||
db.Cache.FlushAsync().Wait();
|
||||
db.Cache.Dispose();
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => Program.PiratesFaction(apply: true, root: _root));
|
||||
Assert.Empty(Read().Pirates);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ public sealed partial class RunStartTests
|
|||
var others = _cache.GetAll<Faction>().Where(faction => faction != pirates).Select(faction => _cache.RefOf(faction)).ToArray();
|
||||
Assert.NotEmpty(others);
|
||||
Assert.All(others, other => Assert.True(pirates.Allegiance.ContainsKey(other)));
|
||||
Assert.NotNull(_cache.Get(pirates.GeonameFile));
|
||||
Assert.NotEmpty(pirates.Places);
|
||||
|
||||
var products = PiratesProducts();
|
||||
Assert.Equal(8, products.Length);
|
||||
|
|
|
|||
366
tests/Aetheria.Shared.Tests/PlaceNamesTests.cs
Normal file
366
tests/Aetheria.Shared.Tests/PlaceNamesTests.cs
Normal file
|
|
@ -0,0 +1,366 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using GameCult.Caching;
|
||||
using Xunit;
|
||||
using Random = CultMath.Random;
|
||||
|
||||
// Each faction names the zones it owns from its own curated list (aetheria-release, cut faction-curated-place-names): the
|
||||
// home zone takes the capital, the rest follow in distance order, a spent or absent list falls back to catalogue designations,
|
||||
// and no name is used twice. Galaxies are built the way the game builds them, from the authored settings and the shipped catalog.
|
||||
public sealed class PlaceNamesTests : IDisposable
|
||||
{
|
||||
private static readonly Regex Designation = new(@"^EAC-\d{1,4}$");
|
||||
private static readonly AuthoredSettings Authored = AuthoredSettings.Load(RunStartTests.FindRepoRoot());
|
||||
private static readonly CultCache Shared = RestoredHullsTests.OpenCatalog();
|
||||
private static readonly Dictionary<(bool, uint), Galaxy> Built = new();
|
||||
|
||||
private readonly List<CultCache> _own = new();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var cache in _own) cache.Dispose();
|
||||
foreach (var root in _scratch) Directory.Delete(root, true);
|
||||
}
|
||||
|
||||
private static Galaxy Build(CultCache cache, bool prelude, uint seed) => prelude
|
||||
? new Galaxy(Authored.Read<TutorialGenerationSettings>("TutorialGenerationSettings"), Authored.Read<SectorBackgroundSettings>("TutorialBackgroundSettings"),
|
||||
cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed)
|
||||
: new Galaxy(Authored.Read<SectorGenerationSettings>("SectorGenerationSettings"), Authored.Read<SectorBackgroundSettings>("SectorBackgroundSettings"),
|
||||
cache, _ => { }, null, seed);
|
||||
|
||||
// A prelude galaxy sometimes refuses to generate for a seed (RestoredHullsTests calls it Galaxy's own seed-dependent
|
||||
// flakiness); those seeds are skipped, and a test that depends on how many built says so.
|
||||
private static Galaxy TryBuild(bool prelude, uint seed)
|
||||
{
|
||||
lock (Built)
|
||||
{
|
||||
if (Built.TryGetValue((prelude, seed), out var known)) return known;
|
||||
Galaxy galaxy;
|
||||
try { galaxy = Build(Shared, prelude, seed); }
|
||||
catch (Exception) { galaxy = null; }
|
||||
return Built[(prelude, seed)] = galaxy;
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<Galaxy> Galaxies(bool prelude, uint last) =>
|
||||
Enumerable.Range(1, (int)last).Select(seed => TryBuild(prelude, (uint)seed)).Where(g => g != null);
|
||||
|
||||
private static Faction[] Placed(Galaxy galaxy) =>
|
||||
galaxy.Factions.Where(f => galaxy.HomeZones.ContainsKey(f) && f.Places.Length != 0).ToArray();
|
||||
|
||||
private static GalaxyZone[] Owned(Galaxy galaxy, Faction faction) =>
|
||||
galaxy.Zones.Select((zone, index) => (zone, index)).Where(z => z.zone.Owner == faction)
|
||||
.OrderBy(z => z.zone.Distance[galaxy.HomeZones[faction]]).ThenBy(z => z.index).Select(z => z.zone).ToArray();
|
||||
|
||||
// Two factions can share a home zone (the prelude puts the quest faction on the buffer's); the zone's owner is the
|
||||
// nearest home in faction order, and that owner's capital is what the zone is called.
|
||||
[Fact]
|
||||
public void TheHomeZoneTakesTheOwnersFirstName()
|
||||
{
|
||||
var own = 0;
|
||||
var shared = 0;
|
||||
foreach (var galaxy in Galaxies(true, 10).Concat(Galaxies(false, 10)))
|
||||
foreach (var faction in galaxy.Factions.Where(galaxy.HomeZones.ContainsKey))
|
||||
{
|
||||
var home = galaxy.HomeZones[faction];
|
||||
var owner = home.Owner;
|
||||
Assert.NotNull(owner);
|
||||
if (owner.Places.Length == 0) Assert.Matches(Designation, home.Name);
|
||||
else Assert.Equal(owner.Places[0].Name, home.Name);
|
||||
if (owner == faction) own++; else shared++;
|
||||
}
|
||||
Assert.True(own >= 20, $"only {own} factions owned their home");
|
||||
Assert.True(shared > 0, "no galaxy shared a home zone, so the shared case was not exercised");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OwnedZonesTakeTheListInDistanceOrder()
|
||||
{
|
||||
var checkedFactions = 0;
|
||||
foreach (var galaxy in Galaxies(true, 10).Concat(Galaxies(false, 10)))
|
||||
foreach (var faction in Placed(galaxy))
|
||||
{
|
||||
var owned = Owned(galaxy, faction);
|
||||
var listed = Math.Min(owned.Length, faction.Places.Length);
|
||||
Assert.Equal(faction.Places.Take(listed).Select(p => p.Name), owned.Take(listed).Select(z => z.Name));
|
||||
Assert.All(owned.Skip(listed).Select(z => z.Name), name => Assert.Matches(Designation, name));
|
||||
checkedFactions++;
|
||||
}
|
||||
Assert.True(checkedFactions >= 20, $"only {checkedFactions} factions were checked");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoZoneCarriesAnotherFactionsName()
|
||||
{
|
||||
var curated = Shared.GetAll<Faction>().SelectMany(f => f.Places.Select(p => (p.Name, Faction: f))).ToDictionary(t => t.Name, t => t.Faction, StringComparer.OrdinalIgnoreCase);
|
||||
var zones = 0;
|
||||
foreach (var galaxy in Galaxies(true, 10).Concat(Galaxies(false, 10)))
|
||||
foreach (var zone in galaxy.Zones.Where(z => curated.ContainsKey(z.Name)))
|
||||
{
|
||||
Assert.True(ReferenceEquals(curated[zone.Name], zone.Owner),
|
||||
$"zone '{zone.Name}' is owned by '{zone.Owner?.ShortName}' but curated by '{curated[zone.Name].ShortName}'");
|
||||
zones++;
|
||||
}
|
||||
Assert.True(zones >= 50, $"only {zones} curated zones were checked");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ASpentListFallsBackToCatalogueDesignations()
|
||||
{
|
||||
// Its own cache: the faction's list is cut, which must not reach the galaxies the other tests share.
|
||||
var cache = RestoredHullsTests.OpenCatalog();
|
||||
_own.Add(cache);
|
||||
var galaxy = Enumerable.Range(1, 20).Select(seed => { try { return Build(cache, true, (uint)seed); } catch (Exception) { return null; } })
|
||||
.First(g => g != null && Placed(g).Any(f => Owned(g, f).Length >= 4));
|
||||
var faction = Placed(galaxy).First(f => Owned(galaxy, f).Length >= 4);
|
||||
faction.Places = faction.Places.Take(2).ToArray();
|
||||
|
||||
var random = new Random(7);
|
||||
galaxy.NamePlaces(ref random);
|
||||
|
||||
var owned = Owned(galaxy, faction);
|
||||
Assert.Equal(faction.Places.Select(p => p.Name), owned.Take(2).Select(z => z.Name));
|
||||
Assert.True(ReferenceEquals(owned[0], galaxy.HomeZones[faction]), $"the nearest zone '{owned[0].Name}' is not {faction.ShortName}'s home '{galaxy.HomeZones[faction].Name}'");
|
||||
Assert.All(owned.Skip(2).Select(z => z.Name), name => Assert.Matches(Designation, name));
|
||||
}
|
||||
|
||||
// The table refuses a repeated name, but a list that did not come through it (a package's factions) can still hold
|
||||
// one: the later faction skips a name the earlier one already used, whatever its case.
|
||||
[Fact]
|
||||
public void AnotherFactionsNameIsNotReusedWhateverItsCase()
|
||||
{
|
||||
var cache = RestoredHullsTests.OpenCatalog();
|
||||
_own.Add(cache);
|
||||
var galaxy = Enumerable.Range(1, 20).Select(seed => { try { return Build(cache, true, (uint)seed); } catch (Exception) { return null; } })
|
||||
.First(g => g != null && Placed(g).Count(f => Owned(g, f).Length >= 2) >= 2);
|
||||
var pair = Placed(galaxy).Where(f => Owned(galaxy, f).Length >= 2).Take(2).ToArray();
|
||||
var (first, later) = (pair[0], pair[1]);
|
||||
Assert.True(Array.IndexOf(galaxy.Factions, first) < Array.IndexOf(galaxy.Factions, later));
|
||||
var taken = first.Places[0].Name;
|
||||
var shouting = taken.ToUpperInvariant();
|
||||
Assert.NotEqual(taken, shouting);
|
||||
// Two consecutive names are taken, so the later faction must skip a run, not a single name.
|
||||
var takenToo = first.Places[1].Name;
|
||||
var own = later.Places[0].Name;
|
||||
later.Places = new[] { new FactionPlace { Name = shouting }, new FactionPlace { Name = takenToo.ToUpperInvariant() }, new FactionPlace { Name = own } };
|
||||
|
||||
var random = new Random(7);
|
||||
galaxy.NamePlaces(ref random);
|
||||
|
||||
Assert.Equal(taken, Owned(galaxy, first)[0].Name);
|
||||
Assert.Equal(own, Owned(galaxy, later)[0].Name);
|
||||
var names = galaxy.Zones.Select(z => z.Name).ToArray();
|
||||
Assert.Equal(names.Length, names.Distinct(StringComparer.OrdinalIgnoreCase).Count());
|
||||
}
|
||||
|
||||
// The rule is one function (FactionPlace.IsValidName); an entry that fails it counts as absent, so the next valid name
|
||||
// is the capital and nothing invalid ever titles a zone.
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
[InlineData(" Padded")]
|
||||
[InlineData("eac-6")]
|
||||
[InlineData("EAC-6")]
|
||||
[InlineData(null)]
|
||||
public void AnInvalidEntryCountsAsAbsent(string bad)
|
||||
{
|
||||
var cache = RestoredHullsTests.OpenCatalog();
|
||||
_own.Add(cache);
|
||||
var galaxy = Enumerable.Range(1, 20).Select(seed => { try { return Build(cache, true, (uint)seed); } catch (Exception) { return null; } })
|
||||
.First(g => g != null && Placed(g).Any(f => Owned(g, f).Length >= 3));
|
||||
var faction = Placed(galaxy).First(f => Owned(galaxy, f).Length >= 3);
|
||||
faction.Places = new[] { new FactionPlace { Name = bad }, new FactionPlace { Name = "Real" }, null, new FactionPlace { Name = "Second" } };
|
||||
|
||||
var random = new Random(7);
|
||||
galaxy.NamePlaces(ref random);
|
||||
|
||||
Assert.Equal(new[] { "Real", "Second" }, Owned(galaxy, faction).Take(2).Select(z => z.Name));
|
||||
Assert.Matches(Designation, Owned(galaxy, faction)[2].Name);
|
||||
}
|
||||
|
||||
// A faction with no home does not stop the ones after it from naming their zones.
|
||||
[Fact]
|
||||
public void AFactionWithoutAHomeDoesNotEndTheNaming()
|
||||
{
|
||||
var cache = RestoredHullsTests.OpenCatalog();
|
||||
_own.Add(cache);
|
||||
var galaxy = Enumerable.Range(1, 20).Select(seed => { try { return Build(cache, true, (uint)seed); } catch (Exception) { return null; } })
|
||||
.First(g => g != null && Placed(g).Count(f => g.HomeZones[f].Owner == f) >= 2);
|
||||
var homeless = galaxy.Factions.First(galaxy.HomeZones.ContainsKey);
|
||||
var later = galaxy.Factions.Where(f => f != homeless && galaxy.HomeZones.ContainsKey(f) && f.Places.Length != 0 && galaxy.HomeZones[f].Owner == f)
|
||||
.First(f => Array.IndexOf(galaxy.Factions, f) > Array.IndexOf(galaxy.Factions, homeless));
|
||||
galaxy.HomeZones.Remove(homeless);
|
||||
|
||||
var random = new Random(7);
|
||||
galaxy.NamePlaces(ref random);
|
||||
|
||||
Assert.Equal(later.Places[0].Name, galaxy.HomeZones[later].Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoTwoZonesShareAName()
|
||||
{
|
||||
var checkedGalaxies = 0;
|
||||
foreach (var galaxy in Galaxies(true, 40).Concat(Galaxies(false, 40)))
|
||||
{
|
||||
var names = galaxy.Zones.Select(z => z.Name).ToArray();
|
||||
Assert.All(names, name => Assert.False(string.IsNullOrEmpty(name)));
|
||||
Assert.Equal(names.Length, names.Distinct(StringComparer.OrdinalIgnoreCase).Count());
|
||||
checkedGalaxies++;
|
||||
}
|
||||
Assert.True(checkedGalaxies >= 40, $"only {checkedGalaxies} galaxies were checked");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AFactionWithoutAListGetsCatalogueDesignations()
|
||||
{
|
||||
// Its own cache: the list is cleared, which must not reach the galaxies the other tests share.
|
||||
var cache = RestoredHullsTests.OpenCatalog();
|
||||
_own.Add(cache);
|
||||
var terris = cache.GetAll<Faction>().Single(f => f.ShortName == "Miss Terri's");
|
||||
Assert.NotEmpty(terris.Places);
|
||||
var galaxy = Enumerable.Range(1, 20).Select(seed => { try { return Build(cache, true, (uint)seed); } catch (Exception) { return null; } })
|
||||
.First(g => g != null && g.Zones.Any(z => z.Owner == terris));
|
||||
terris.Places = Array.Empty<FactionPlace>();
|
||||
|
||||
var random = new Random(7);
|
||||
galaxy.NamePlaces(ref random);
|
||||
|
||||
var held = galaxy.Zones.Where(z => z.Owner == terris).ToArray();
|
||||
Assert.NotEmpty(held);
|
||||
Assert.All(held.Select(z => z.Name), name => Assert.Matches(Designation, name));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TheShippedListsAreTheTable()
|
||||
{
|
||||
var factions = Shared.GetAll<Faction>().ToArray();
|
||||
foreach (var row in PlaceNamesCommand.Table)
|
||||
{
|
||||
var matching = factions.Where(f => Shared.RefOf(f).Key.Value == row.RecordKey).ToArray();
|
||||
Assert.True(matching.Length == 1, $"{matching.Length} catalog factions carry the key of the {row.ShortName} row");
|
||||
var faction = matching[0];
|
||||
Assert.Equal(row.ShortName, faction.ShortName);
|
||||
Assert.Equal(row.Names, faction.Places.Select(p => p.Name));
|
||||
}
|
||||
Assert.Equal(PlaceNamesCommand.Table.Length, factions.Count(f => f.Places.Length != 0));
|
||||
|
||||
var names = factions.SelectMany(f => f.Places.Select(p => p.Name)).ToArray();
|
||||
Assert.All(names, name =>
|
||||
{
|
||||
Assert.False(string.IsNullOrEmpty(name));
|
||||
Assert.Equal(name.Trim(), name);
|
||||
Assert.False(name.StartsWith("EAC-", StringComparison.OrdinalIgnoreCase));
|
||||
});
|
||||
Assert.Equal(names.Length, names.Distinct(StringComparer.OrdinalIgnoreCase).Count());
|
||||
}
|
||||
|
||||
// The command's scratch catalog: the shipped one with every list cleared, so an apply has all of it to author.
|
||||
private string ScratchWithoutLists()
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), "place-names-" + Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(Path.Combine(root, "GameData"));
|
||||
var path = Path.Combine(root, "GameData", "Aetheria.cc");
|
||||
File.Copy(Path.Combine(RunStartTests.FindRepoRoot(), "GameData", "Aetheria.cc"), path);
|
||||
using (var cache = new CultCache(TestCatalog.Registry()))
|
||||
{
|
||||
cache.AddBackingStore(new GameCult.Caching.MessagePack.SingleFileMessagePackBackingStore(path), AetheriaStores.CatalogTypes);
|
||||
foreach (var faction in cache.GetAll<Faction>().ToArray())
|
||||
{
|
||||
faction.Places = Array.Empty<FactionPlace>();
|
||||
cache.Commit(batch => batch.Upsert(typeof(Faction), faction, cache.RefOf(faction).Key));
|
||||
}
|
||||
cache.FlushAsync().Wait();
|
||||
}
|
||||
_scratch.Add(root);
|
||||
return root;
|
||||
}
|
||||
|
||||
private readonly List<string> _scratch = new();
|
||||
private static string CatalogOf(string root) => Path.Combine(root, "GameData", "Aetheria.cc");
|
||||
|
||||
[Fact]
|
||||
public void TheCommandIsIdempotentAndRefusesAMismatch()
|
||||
{
|
||||
var root = ScratchWithoutLists();
|
||||
var before = File.ReadAllBytes(CatalogOf(root));
|
||||
Assert.Equal(0, PlaceNamesCommand.Run(new string[0], root));
|
||||
Assert.Equal(before, File.ReadAllBytes(CatalogOf(root)));
|
||||
|
||||
Assert.Equal(0, PlaceNamesCommand.Run(new[] { "apply" }, root));
|
||||
var landed = File.ReadAllBytes(CatalogOf(root));
|
||||
Assert.NotEqual(before, landed);
|
||||
var db = AetherDb.Open(root: root);
|
||||
try
|
||||
{
|
||||
foreach (var row in PlaceNamesCommand.Table)
|
||||
Assert.Equal(row.Names, db.Cache.GetAll<Faction>().Single(f => db.Cache.RefOf(f).Key.Value == row.RecordKey).Places.Select(p => p.Name));
|
||||
}
|
||||
finally { db.Cache.Dispose(); }
|
||||
Assert.Equal(0, PlaceNamesCommand.Run(new[] { "apply" }, root));
|
||||
Assert.Equal(landed, File.ReadAllBytes(CatalogOf(root)));
|
||||
|
||||
// Aya's ShortName changed under its key: identity is the key, so the row no longer describes that faction.
|
||||
var squatter = AetherDb.Open(catalogWritable: true, root: root);
|
||||
var aya = squatter.Cache.GetAll<Faction>().Single(f => f.ShortName == "Aya");
|
||||
aya.ShortName = "Ayx";
|
||||
squatter.Cache.Commit(batch => batch.Upsert(typeof(Faction), aya, squatter.Cache.RefOf(aya).Key));
|
||||
squatter.Cache.Dispose();
|
||||
var changed = File.ReadAllBytes(CatalogOf(root));
|
||||
var refusal = Assert.Throws<InvalidOperationException>(() => PlaceNamesCommand.Run(new[] { "apply" }, root));
|
||||
Assert.DoesNotContain("Ayx", refusal.Message);
|
||||
Assert.Equal(changed, File.ReadAllBytes(CatalogOf(root)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApplyRestoresAnEditedListOfTheSameLength()
|
||||
{
|
||||
var root = ScratchWithoutLists();
|
||||
Assert.Equal(0, PlaceNamesCommand.Run(new[] { "apply" }, root));
|
||||
var row = PlaceNamesCommand.Table.First();
|
||||
var edited = AetherDb.Open(catalogWritable: true, root: root);
|
||||
var faction = edited.Cache.GetAll<Faction>().Single(f => edited.Cache.RefOf(f).Key.Value == row.RecordKey);
|
||||
faction.Places = row.Names.Reverse().Select(n => new FactionPlace { Name = n }).ToArray();
|
||||
edited.Cache.Commit(batch => batch.Upsert(typeof(Faction), faction, edited.Cache.RefOf(faction).Key));
|
||||
edited.Cache.Dispose();
|
||||
|
||||
Assert.Equal(0, PlaceNamesCommand.Run(new[] { "apply" }, root));
|
||||
|
||||
var db = AetherDb.Open(root: root);
|
||||
try { Assert.Equal(row.Names, db.Cache.GetAll<Faction>().Single(f => db.Cache.RefOf(f).Key.Value == row.RecordKey).Places.Select(p => p.Name)); }
|
||||
finally { db.Cache.Dispose(); }
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("EAC-12")]
|
||||
[InlineData("eac-12")]
|
||||
[InlineData(" Padded")]
|
||||
[InlineData("")]
|
||||
[InlineData("shunya")]
|
||||
public void ATableWithABadNameIsRefusedWithNothingWritten(string bad)
|
||||
{
|
||||
var root = ScratchWithoutLists();
|
||||
var before = File.ReadAllBytes(CatalogOf(root));
|
||||
var table = PlaceNamesCommand.Table.Select((row, i) => i == 1 ? row with { List = row.List + " | " + bad } : row).ToArray();
|
||||
var refusal = Assert.Throws<InvalidOperationException>(() => PlaceNamesCommand.Run(new[] { "apply" }, root, table));
|
||||
if (bad.Length != 0) Assert.DoesNotContain(bad, refusal.Message);
|
||||
Assert.Equal(before, File.ReadAllBytes(CatalogOf(root)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ARowForAKeyTheCatalogDoesNotHoldIsRefusedWithNothingWritten()
|
||||
{
|
||||
var root = ScratchWithoutLists();
|
||||
var before = File.ReadAllBytes(CatalogOf(root));
|
||||
var table = PlaceNamesCommand.Table.Append(new PlaceNamesCommand.Row("00000000-0000-0000-0000-000000000000", "Nobody", "Elsewhere")).ToArray();
|
||||
Assert.Throws<InvalidOperationException>(() => PlaceNamesCommand.Run(new[] { "apply" }, root, table));
|
||||
Assert.Equal(before, File.ReadAllBytes(CatalogOf(root)));
|
||||
}
|
||||
}
|
||||
|
|
@ -91,9 +91,6 @@ public sealed class RestoredHullsTests
|
|||
NoisePosition = 536.5106f, CloudExponent = 10, CloudAmplitude = .01f,
|
||||
};
|
||||
|
||||
internal static NameGeneratorSettings TutorialNameSettings() =>
|
||||
new NameGeneratorSettings { NameGeneratorMinLength = 5, NameGeneratorMaxLength = 10, NameGeneratorOrder = 3 };
|
||||
|
||||
// A station's orbit is a copy of its source planet's own (Parent, Distance) (ZoneGenerator.CreateLagrangeOrbit),
|
||||
// so it always matches that one planet. It matches MORE than one planet only when the source planet shares its
|
||||
// distance with a sibling under the same parent -- exactly what ZoneGenerator's potentialLagrangePoints filter
|
||||
|
|
@ -153,8 +150,7 @@ public sealed class RestoredHullsTests
|
|||
var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
for (var seed = 1u; seed <= 64u; seed++)
|
||||
{
|
||||
var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), TutorialNameSettings(),
|
||||
cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed);
|
||||
var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed);
|
||||
foreach (var zone in galaxy.Zones.Where(z => !ReferenceEquals(z, galaxy.Entrance)))
|
||||
{
|
||||
var orbits = ParentedPlanetOrbits(cache, ZoneGenerator.GenerateZone(items, TutorialZoneSettings(), AuthoredPlanetSettings(), galaxy, zone, isTutorial: false, ambient: false));
|
||||
|
|
@ -366,7 +362,6 @@ public sealed class RestoredHullsTests
|
|||
var zoneSettings = TutorialZoneSettings();
|
||||
var tutorialSettings = TutorialGalaxySettings();
|
||||
var background = TutorialBackgroundSettings();
|
||||
var names = TutorialNameSettings();
|
||||
var gameData = TestCatalog.Repo;
|
||||
var sawGenuineLagrangeOrbit = false;
|
||||
for (uint seed = 1; seed <= 25; seed++)
|
||||
|
|
@ -375,7 +370,7 @@ public sealed class RestoredHullsTests
|
|||
try
|
||||
{
|
||||
using var cache = OpenReadOnlyRealCatalogWithScratchRun(gameData, scratchRun);
|
||||
var galaxy = new Galaxy(tutorialSettings, background, names, cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed);
|
||||
var galaxy = new Galaxy(tutorialSettings, background, cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed);
|
||||
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var pack = ZoneGenerator.GenerateZone(items, zoneSettings, AuthoredPlanetSettings(), galaxy, galaxy.Entrance, isTutorial: true);
|
||||
|
|
@ -407,7 +402,6 @@ public sealed class RestoredHullsTests
|
|||
var zoneSettings = TutorialZoneSettings();
|
||||
var tutorialSettings = TutorialGalaxySettings();
|
||||
var background = TutorialBackgroundSettings();
|
||||
var names = TutorialNameSettings();
|
||||
var gameData = TestCatalog.Repo;
|
||||
var sawGenuineCandidate = false;
|
||||
for (uint seed = 1; seed <= 25; seed++)
|
||||
|
|
@ -416,7 +410,7 @@ public sealed class RestoredHullsTests
|
|||
try
|
||||
{
|
||||
using var cache = OpenReadOnlyRealCatalogWithScratchRun(gameData, scratchRun);
|
||||
var galaxy = new Galaxy(tutorialSettings, background, names, cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed);
|
||||
var galaxy = new Galaxy(tutorialSettings, background, cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed);
|
||||
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var pack = ZoneGenerator.GenerateZone(items, zoneSettings, AuthoredPlanetSettings(), galaxy, galaxy.Entrance, isTutorial: true);
|
||||
|
|
@ -454,8 +448,7 @@ public sealed class RestoredHullsTests
|
|||
try
|
||||
{
|
||||
using var cache = OpenReadOnlyRealCatalogWithScratchRun(gameData, scratchRun);
|
||||
var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), TutorialNameSettings(),
|
||||
cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed: 1u);
|
||||
var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed: 1u);
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
|
||||
var zoneSettings = TutorialZoneSettings();
|
||||
|
|
@ -490,8 +483,7 @@ public sealed class RestoredHullsTests
|
|||
try
|
||||
{
|
||||
using var cache = OpenReadOnlyRealCatalogWithScratchRun(gameData, scratchRun);
|
||||
var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), TutorialNameSettings(),
|
||||
cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed: 1u);
|
||||
var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed: 1u);
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
|
||||
var zoneSettings = TutorialZoneSettings();
|
||||
|
|
@ -532,7 +524,6 @@ public sealed class RestoredHullsTests
|
|||
var zoneSettings = TutorialZoneSettings();
|
||||
var tutorialSettings = TutorialGalaxySettings();
|
||||
var background = TutorialBackgroundSettings();
|
||||
var names = TutorialNameSettings();
|
||||
var gameData = TestCatalog.Repo;
|
||||
|
||||
var checkedAny = false;
|
||||
|
|
@ -542,7 +533,7 @@ public sealed class RestoredHullsTests
|
|||
try
|
||||
{
|
||||
using var cache = OpenReadOnlyRealCatalogWithScratchRun(gameData, scratchRun);
|
||||
var galaxy = new Galaxy(tutorialSettings, background, names, cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed);
|
||||
var galaxy = new Galaxy(tutorialSettings, background, cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed);
|
||||
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var pack = ZoneGenerator.GenerateZone(items, zoneSettings, AuthoredPlanetSettings(), galaxy, galaxy.Entrance, isTutorial: true);
|
||||
|
|
@ -677,8 +668,7 @@ public sealed class RestoredHullsTests
|
|||
// real zone from a real galaxy (e.g. one already known to have no Lagrange candidate) and make it the Entrance.
|
||||
private static Galaxy BuildGalaxyWithForcedEntrance(CultCache cache, uint seed, string entranceZoneName)
|
||||
{
|
||||
var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), TutorialNameSettings(),
|
||||
cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed);
|
||||
var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed);
|
||||
var target = galaxy.Zones.Single(z => z.Name == entranceZoneName);
|
||||
|
||||
var factions = galaxy.Factions;
|
||||
|
|
@ -789,8 +779,7 @@ public sealed class RestoredHullsTests
|
|||
try
|
||||
{
|
||||
using var cache = OpenReadOnlyRealCatalogWithScratchRun(gameData, scratchRun);
|
||||
var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), TutorialNameSettings(),
|
||||
cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed: 1u);
|
||||
var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed: 1u);
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
|
||||
int StationCount(GalaxyZone zone, bool isTutorial) =>
|
||||
|
|
@ -830,8 +819,7 @@ public sealed class RestoredHullsTests
|
|||
try
|
||||
{
|
||||
using var cache = OpenReadOnlyRealCatalogWithScratchRun(gameData, scratchRun);
|
||||
var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), TutorialNameSettings(),
|
||||
cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed: 1u);
|
||||
var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed: 1u);
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
|
||||
var pack = ZoneGenerator.GenerateZone(items, TutorialZoneSettings(), AuthoredPlanetSettings(), galaxy, galaxy.Entrance, isTutorial: true);
|
||||
|
|
@ -866,8 +854,7 @@ public sealed class RestoredHullsTests
|
|||
// Must fail if that guard is dropped for real.
|
||||
private static Galaxy BuildGalaxyWithBoostedPresence(CultCache cache, uint seed, string zoneName)
|
||||
{
|
||||
var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), TutorialNameSettings(),
|
||||
cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed);
|
||||
var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed);
|
||||
var target = galaxy.Zones.Single(z => z.Name == zoneName);
|
||||
|
||||
var factions = galaxy.Factions;
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ public sealed partial class RunStartTests : IDisposable
|
|||
_planetSettings = authored.Read<PlanetSettings>("PlanetSettings");
|
||||
var tutorial = _tutorialSettings = authored.Read<TutorialGenerationSettings>("TutorialGenerationSettings");
|
||||
_galaxy = new Galaxy(tutorial, authored.Read<SectorBackgroundSettings>("TutorialBackgroundSettings"),
|
||||
authored.Read<NameGeneratorSettings>("NameGeneratorSettings"), _cache, new PlayerSettings(),
|
||||
_cache, new PlayerSettings(),
|
||||
Directory.CreateDirectory(Path.Combine(_root, "Narrative")), _ => { }, null, GalaxySeed);
|
||||
_items = new ItemManager(_cache, new ProvenanceLedger(), authored.Read<GameplaySettings>("GameplaySettings"), _ => { });
|
||||
_protagonist = _galaxy.ResolveFaction(tutorial.ProtagonistFaction);
|
||||
|
|
@ -122,7 +122,7 @@ public sealed partial class RunStartTests : IDisposable
|
|||
var authored = AuthoredSettings.Load(FindRepoRoot());
|
||||
return new Galaxy(authored.Read<SectorGenerationSettings>("SectorGenerationSettings"),
|
||||
authored.Read<SectorBackgroundSettings>("SectorBackgroundSettings"),
|
||||
authored.Read<NameGeneratorSettings>("NameGeneratorSettings"), _cache, _ => { }, null, GalaxySeed);
|
||||
_cache, _ => { }, null, GalaxySeed);
|
||||
}
|
||||
|
||||
// Every item a station carries, equipped or stocked, with the product it was made as.
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@ public sealed partial class RunStartTests
|
|||
authored.Read<SectorBackgroundSettings>("SectorBackgroundSettings"),
|
||||
authored.Read<TutorialGenerationSettings>("TutorialGenerationSettings"),
|
||||
authored.Read<SectorBackgroundSettings>("TutorialBackgroundSettings"),
|
||||
authored.Read<NameGeneratorSettings>("NameGeneratorSettings"),
|
||||
_cache, new PlayerSettings(), Directory.CreateDirectory(Path.Combine(_root, "Narrative")), _ => { }, null, clock);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ public sealed class StationPlacementTests : IDisposable
|
|||
var settings = authored.Read<GameplaySettings>("GameplaySettings");
|
||||
var galaxy = new Galaxy(authored.Read<TutorialGenerationSettings>("TutorialGenerationSettings"),
|
||||
authored.Read<SectorBackgroundSettings>("TutorialBackgroundSettings"),
|
||||
authored.Read<NameGeneratorSettings>("NameGeneratorSettings"), cache, new PlayerSettings(),
|
||||
cache, new PlayerSettings(),
|
||||
Directory.CreateDirectory(Path.Combine(dir, "Narrative")), _ => { }, null, seed);
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), settings, _ => { });
|
||||
stock?.Invoke(items);
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ public sealed class SunsNeverGreenTests : IDisposable
|
|||
var authored = AuthoredSettings.Load(RunStartTests.FindRepoRoot());
|
||||
var tutorial = authored.Read<TutorialGenerationSettings>("TutorialGenerationSettings");
|
||||
var galaxy = new Galaxy(tutorial, authored.Read<SectorBackgroundSettings>("TutorialBackgroundSettings"),
|
||||
authored.Read<NameGeneratorSettings>("NameGeneratorSettings"), cache, new PlayerSettings(),
|
||||
cache, new PlayerSettings(),
|
||||
Directory.CreateDirectory(Path.Combine(dir, "Narrative")), _ => { }, null, seed);
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), authored.Read<GameplaySettings>("GameplaySettings"), _ => { });
|
||||
var zoneSettings = authored.Read<ZoneGenerationSettings>("ZoneSettings");
|
||||
|
|
|
|||
|
|
@ -19,23 +19,22 @@ public static class MinorPowerFactionsCommand
|
|||
{
|
||||
// Own: the weights this faction names for the other minor powers; every other faction but the Pirates gets Default.
|
||||
// Gain: the weight every existing faction (bar the Pirates and Miss Terri's) gains toward this faction.
|
||||
// Geonames is the name file the faction draws place names from (question minor-power-geonames: the nearest existing file).
|
||||
internal sealed record Def(string Name, string ShortName, string Description, float3 Primary, float3 Secondary,
|
||||
string Geonames, int Influence, float Gain, float Default, (string ShortName, float Weight)[] Own);
|
||||
int Influence, float Gain, float Default, (string ShortName, float Weight)[] Own);
|
||||
|
||||
internal static readonly Def[] Factions =
|
||||
{
|
||||
new("Aya Collective", "Aya",
|
||||
"A federation of communes, clinics and repair crews. Its equipment comes with the drawings, so the people it protects can keep it running.",
|
||||
new float3(.72f, .36f, .22f), new float3(.12f, .36f, .25f), "Congo", 3, .3f, .3f,
|
||||
new float3(.72f, .36f, .22f), new float3(.12f, .36f, .25f), 3, .3f, .3f,
|
||||
new[] { ("Forge", 1f), ("Megiddo", .5f) }),
|
||||
new("Megiddo", "Megiddo",
|
||||
"A defended fleet that holds its boundary until the question is answered. Others buy its protection without sharing its faith.",
|
||||
new float3(.07f, .09f, .15f), new float3(.90f, .85f, .74f), "pleiades", 4, .3f, .2f,
|
||||
new float3(.07f, .09f, .15f), new float3(.90f, .85f, .74f), 4, .3f, .2f,
|
||||
new[] { ("Aya", .5f), ("Forge", .5f) }),
|
||||
new("Orbital Forge", "Forge",
|
||||
"Belt engineers who hollow asteroids into homes and yards. Their standards leave room to adapt them, and their drawings travel further than their ships.",
|
||||
new float3(.42f, .30f, .22f), new float3(.30f, .38f, .52f), "pleiades", 3, .5f, .5f,
|
||||
new float3(.42f, .30f, .22f), new float3(.30f, .38f, .52f), 3, .5f, .5f,
|
||||
Array.Empty<(string, float)>()),
|
||||
};
|
||||
|
||||
|
|
@ -51,17 +50,14 @@ public static class MinorPowerFactionsCommand
|
|||
var apply = args.Contains("apply");
|
||||
var db = AetherDb.Open(catalogWritable: apply, root: root);
|
||||
var existing = db.Cache.GetAll<Faction>().ToArray();
|
||||
var nameFiles = db.Cache.GetAll<NameFile>().ToArray();
|
||||
|
||||
// Refuse before any write: a name or short name held by a faction under another key, and a missing name file.
|
||||
// Refuse before any write: a name or short name held by a faction under another key.
|
||||
foreach (var def in Factions)
|
||||
{
|
||||
var key = KeyOf(def);
|
||||
foreach (var other in existing.Where(f => f.Name == def.Name || f.ShortName == def.ShortName))
|
||||
if (!db.Cache.RefOf(other).Key.Equals(key))
|
||||
throw new InvalidOperationException("Faction Name or ShortName: held by a faction under another key (conflict).");
|
||||
if (nameFiles.Count(n => n.Name == def.Geonames) != 1)
|
||||
throw new InvalidOperationException("Faction GeonameFile: expected exactly one name file (missing).");
|
||||
}
|
||||
|
||||
var minorKeys = Factions.ToDictionary(d => d.ShortName, KeyOf);
|
||||
|
|
@ -80,7 +76,7 @@ public static class MinorPowerFactionsCommand
|
|||
}
|
||||
|
||||
foreach (var def in created)
|
||||
Console.WriteLine($"New faction \"{def.Name}\" ({def.ShortName}): influence {def.Influence}, names \"{def.Geonames}\", colours ({def.Primary.x:0.00}, {def.Primary.y:0.00}, {def.Primary.z:0.00}) / ({def.Secondary.x:0.00}, {def.Secondary.y:0.00}, {def.Secondary.z:0.00})\n {def.Description}\n allegiance: {string.Join(", ", def.Own.Select(o => $"{o.ShortName} {o.Weight:0.##}"))} others {def.Default:0.##}");
|
||||
Console.WriteLine($"New faction \"{def.Name}\" ({def.ShortName}): influence {def.Influence}, colours ({def.Primary.x:0.00}, {def.Primary.y:0.00}, {def.Primary.z:0.00}) / ({def.Secondary.x:0.00}, {def.Secondary.y:0.00}, {def.Secondary.z:0.00})\n {def.Description}\n allegiance: {string.Join(", ", def.Own.Select(o => $"{o.ShortName} {o.Weight:0.##}"))} others {def.Default:0.##}");
|
||||
foreach (var (holder, missing) in gains)
|
||||
Console.WriteLine($" {holder.ShortName} gains {string.Join(", ", missing.Select(d => $"{d.ShortName} {d.Gain:0.##}"))}");
|
||||
|
||||
|
|
@ -101,7 +97,7 @@ public static class MinorPowerFactionsCommand
|
|||
{
|
||||
Name = def.Name, ShortName = def.ShortName, Description = def.Description,
|
||||
PrimaryColor = def.Primary, SecondaryColor = def.Secondary,
|
||||
GeonameFile = db.Cache.RefOf(nameFiles.Single(n => n.Name == def.Geonames)), InfluenceDistance = def.Influence,
|
||||
InfluenceDistance = def.Influence,
|
||||
};
|
||||
foreach (var reference in everyone) faction.Allegiance[reference] = def.Default;
|
||||
foreach (var other in Factions.Where(o => o != def))
|
||||
|
|
|
|||
126
tools/AetherDb/PlaceNamesCommand.cs
Normal file
126
tools/AetherDb/PlaceNamesCommand.cs
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using GameCult.Caching;
|
||||
|
||||
// place-names [apply] (aetheria-release, cut faction-curated-place-names; ruling faction-curated-place-names):
|
||||
// authors every curated faction's Places from the table below, which is the one source of those names. A faction is
|
||||
// identified by its record key, and its ShortName must agree (ghostlight-verse ruling faction-identity-is-record-key).
|
||||
// A faction without a row keeps the catalogue designations Galaxy.NamePlaces falls back to.
|
||||
// Order is meaning: the first name is the faction's capital, and the home zone takes it. Dry run unless passed "apply".
|
||||
public static class PlaceNamesCommand
|
||||
{
|
||||
// List holds the names separated by " | ", in authored order.
|
||||
internal sealed record Row(string RecordKey, string ShortName, string List)
|
||||
{
|
||||
public string[] Names => List.Split(" | ");
|
||||
}
|
||||
|
||||
internal static readonly Row[] Table =
|
||||
{
|
||||
new("bbd619ed-76c1-464c-829d-3f419cdc2525", "Adrasteia",
|
||||
"Shunya | Unlisted | Tamas | Silent Address | Maya | The Warm Hour | Nishabda | The Vouch | Akasha | No Such Node | Chhaya | Heat Death Reach | Santulan | Cold Margin | Antara | Without Light | Nirvata | Redacted Deep | Quiet Reach | Last Known"),
|
||||
new("8443f966-0d88-4ec7-a20e-8edf73fa62f6", "AU",
|
||||
"Ceres Ramp | Pallas Provisional | Ramp Two | Claimshare | Vesta Works | Hygiea | Bloom Seven | Half Measure | Further Out | Semi-Permanent | Under Construction | Drawing Board | Survey Pending | Ceres Control | Margin of Error | Proof of Concept | Ramp Nine | Good Enough | Ambition | Not Yet Named"),
|
||||
new("a1f76631-d6d1-443a-9be1-c9b1267d6ca2", "Alakrita",
|
||||
"Aurea | Celeritas | Elegantia | Via Alakrita | Fatum | Quiet Bay | Laurea | Gloria | Prima Linea | Destiny Yard | Aequor | Corona | Vela | Lumen | Nobilis | Portus Aureus | Inscription | Tessera | Proportio | Decus"),
|
||||
new("48bbb7537ff9ed0a73c3c353598e8f69", "Aya",
|
||||
"Hellas | Sanctuary | Common Kitchen | Open Courtyard | Enceladus Commons | Mutual | Assembly Hall | Repair Circle | Refuge Route | Not Inevitable | Free Clinic | Shared Table | Drawings Included | All Welcome | Congress | Keep It Running | Terracotta | Open Door | Freehold Garden | Green Shade"),
|
||||
// DME's voice is lowercase on purpose.
|
||||
new("8419f9bc-0167-46ec-86c3-3265d2905fbc", "DME",
|
||||
"the choir loft | encore | burn it down | turn it up | void communion | last call | margin poem | basement tape | loud room | second encore | feedback | no permit | the pit | spare amp | open mic | after hours | smoke machine | b-side | green room | yara's version"),
|
||||
new("a9672650-c335-484a-9555-71a35c841aa1", "Ewan Hart",
|
||||
"Homestead | Second Harvest | Convoy's End | Invented Spring | Grange Hall | Long Furrow | Silo Nine | Good Dog | Feed and Seed | False October | County Line | Still Growing | Tractor Pull | Prairie Bloom | Rain Check | Elevator | Hayloft | Late Frost | Sealed Summer | Old Blue"),
|
||||
new("1dfaa629-6c63-42a7-91e1-f1ae90f92f09", "Finch",
|
||||
"Tycho | Serenity Ward | Tranquility | Far Side | Nectaris Clinic | Grace | Crisium | Mireille | Copernicus | Imbrium | Full Coverage | Aitken | Fecunditas | Vaporum | Recovery Ring | Clavius | Earthrise | Second Opinion | Frigoris | Plato"),
|
||||
new("2f74e6b4-d268-48a9-b2a8-24591c680e84", "Lightsail",
|
||||
"Mile Zero | Ganymede Stop | Last Anchorage | Dispatch | The Drop Book | Truck Stop | Route Choir | On Time | Callisto Crossing | Haul Road | Weigh Station | Long Haul | Bill of Lading | Overnight | Deadhead | Jake Brake | Signed For | The Line | Breaker Nineteen | Final Mile"),
|
||||
new("a012e0f4-4b22-47b7-8b24-ff3e1439c949", "Lucent",
|
||||
"Ishtar Studios | Prime Time | Maxwell Heights | Cloud Nine Live | Channel Aphrodite | Clout | Buzz Market | Ratings Tower | Lagrange Feed | Spotlight | Exclusive | Trending | Broadcast Row | Red Carpet | Season Finale | Sponsored | Headliner | Viral | Golden Hour | Breaking News"),
|
||||
new("087df3f89e59beab14f9d41c849b202f", "Megiddo",
|
||||
"First Vigil | Earth Stars | Continuity | The Long Silence | Deferred Ground | Unverified | Boundary Watch | Custody | Not Our World | Pleiades Watch | Second Vault | Presumption | Under Orion | Night Watch | Witness | Flatland Edge | Old Sky | Record Keeper | Patience | Revelation Pending"),
|
||||
// Real Australian place names, Indigenous-derived and English, a draft Emily may replace (ruling miss-terris-improvised-places).
|
||||
new("d6466821-6776-4f40-9609-2342debf5c86", "Miss Terri's",
|
||||
"Woolloomooloo | Wagga Wagga | Humpty Doo | Come By Chance | Yackandandah | Nowhere Else | Kurri Kurri | Lightning Ridge | Mooloolaba | Woy Woy | Banana | Coober Pedy | Gundagai | Burpengary | Toowoomba | Kalgoorlie | Bald Hills | Wollongong | Innaloo | Ballarat"),
|
||||
new("73d368dd-58a8-4431-b9ba-2d44754d36a1", "NiteLife",
|
||||
"Caloris Array | Arete Grid | Malam | Cahaya | Graveyard Shift | Lights On | Bintang | Night Count | Pelita | Uptime Pro | Bulan | Grid Plus | Kilat | Afterglow | Load Balance | Peak Hours | Terang | Version Two | Midnight Blue | Keep On"),
|
||||
new("e34d0354f46873df7ec27273fa2abe3d", "Forge",
|
||||
"Hollow Pallas | Cinderlace | Stubborn | Open Drawing | Pattern Yard | Bored Through | Slagside | Tolerance | Free Standard | Lathe | Anvil Rock | Copied Twice | Mandrel | Inner Shell | Quench | Forge Light | Billet | Rivet | Blueprint | Worlds Enough"),
|
||||
// The two emoji are U+1F3F4 and U+1F99C (ruling pirates-record-content).
|
||||
new("1d8a168def994a76a1ce7cb18f59c038", "Pirates",
|
||||
"Trojan Nest | Genuine Rescue | Ring Shadow | Patched Cavity | \U0001F3F4 Nobody's | Repainted | Finders Keepers | Hidden Dock | Free Parking | Legit Waystation | New Management | No Questions | Second Hand | False Beacon | Salvage Rights | The Fence | Elegance. Ours. | \U0001F99C Roost | Maintenance Due | Not A Base"),
|
||||
new("adfb9b08-aabc-45c6-9586-915698a9c4b0", "R&D",
|
||||
"Prüfstand | Findings | Recertification | Configuration B | Asterisk | Freigabe | Friendly | Sicherheit | Compliance | Revision C | Normal Operation | Abnahme | Test Bench Four | Fine Print | Hold Music | Reassurance | Warranty | Grundlage | Standard Procedure | Kinderleicht"),
|
||||
new("16bad4f9-7fb5-41c5-8da0-dc76709eb7d3", "Zhestokost",
|
||||
"Cimmeria | Tharsis Bastion | Olimp | Molot | Valles Forward | Arsenal Commission | Eternal Council | Moneta | Kuznya | Overkill | Assignment Board | Stal | Krepost | Gromada | Campaign | Frontline | Beton | Plamya | Zhelezo | Pobeda"),
|
||||
};
|
||||
|
||||
public static int Run(string[] args, string root = null) => Run(args, root, Table);
|
||||
|
||||
// The table is a parameter so a test can hand it a malformed one; production always passes Table.
|
||||
internal static int Run(string[] args, string root, Row[] table)
|
||||
{
|
||||
var apply = args.Contains("apply");
|
||||
var db = AetherDb.Open(catalogWritable: apply, root: root);
|
||||
var factions = db.Cache.GetAll<Faction>().ToArray();
|
||||
var keyOf = factions.ToDictionary(f => db.Cache.RefOf(f).Key.Value, StringComparer.Ordinal);
|
||||
|
||||
// Refuse before any write. A refusal never echoes the offending value.
|
||||
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var row in table)
|
||||
{
|
||||
if (!keyOf.TryGetValue(row.RecordKey, out var faction))
|
||||
throw new InvalidOperationException("Place names row: its key is not a faction in the catalog (missing).");
|
||||
if (faction.ShortName != row.ShortName)
|
||||
throw new InvalidOperationException("Place names row: its ShortName differs from the faction under its key (conflict).");
|
||||
foreach (var name in row.Names)
|
||||
{
|
||||
if (!FactionPlace.IsValidName(name))
|
||||
throw new InvalidOperationException("Place names row: a name is empty, untrimmed or a catalogue designation (invalid).");
|
||||
if (!seen.Add(name))
|
||||
throw new InvalidOperationException("Place names table: a name is repeated (conflict).");
|
||||
}
|
||||
}
|
||||
|
||||
var rows = table.ToDictionary(r => r.RecordKey, StringComparer.Ordinal);
|
||||
var changed = table.Where(r => !keyOf[r.RecordKey].Places.Select(p => p.Name).SequenceEqual(r.Names)).ToArray();
|
||||
|
||||
foreach (var faction in factions.OrderBy(f => f.Name, StringComparer.Ordinal))
|
||||
{
|
||||
if (!rows.TryGetValue(db.Cache.RefOf(faction).Key.Value, out var row))
|
||||
{
|
||||
Console.WriteLine($"{faction.Name}: no list: catalogue designations");
|
||||
continue;
|
||||
}
|
||||
Console.WriteLine($"{faction.Name}: {row.Names.Length} places, capital {row.Names[0]}, {(changed.Contains(row) ? "changed" : "unchanged")}");
|
||||
}
|
||||
if (changed.Length == 0)
|
||||
{
|
||||
Console.WriteLine("nothing to do");
|
||||
return 0;
|
||||
}
|
||||
if (!apply)
|
||||
{
|
||||
Console.WriteLine($"\nDry run. Pass \"apply\" to author {changed.Length} place lists.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!db.Cache.Commit(batch =>
|
||||
{
|
||||
foreach (var row in changed)
|
||||
{
|
||||
var faction = keyOf[row.RecordKey];
|
||||
faction.Places = row.Names.Select(n => new FactionPlace { Name = n }).ToArray();
|
||||
batch.Upsert(typeof(Faction), faction, db.Cache.RefOf(faction).Key);
|
||||
}
|
||||
}))
|
||||
throw new InvalidOperationException("The catalog changed while place names were being authored; nothing was written. Run it again.");
|
||||
Console.WriteLine($"\nAuthored {changed.Length} place lists in Aetheria.cc");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -58,9 +58,12 @@ public static class Program
|
|||
case "product-lines": return ProductLinesCommand.Run(args.Skip(1).ToArray());
|
||||
case "minor-power-factions": return MinorPowerFactionsCommand.Run(args.Skip(1).ToArray());
|
||||
case "assembly-from-maker": return AssemblyFromMakerCommand.Run(AetherDb.Open(catalogWritable: args.Contains("apply")).Cache, args.Contains("apply"), Console.Out);
|
||||
case "place-names":
|
||||
Console.OutputEncoding = new System.Text.UTF8Encoding(false);
|
||||
return PlaceNamesCommand.Run(args.Skip(1).ToArray());
|
||||
case "doctrine-catalog": return DoctrineCatalog.Run(AetherDb.Open(catalogWritable: args.Contains("apply")).Cache, args.Contains("apply"), Console.Out);
|
||||
default:
|
||||
Console.WriteLine("commands: census, factions, station-fit, hardpoint-fit, loadout [seed], save, settings, settings-dump, dangling [clear <Type.Member>]... [apply], shield-migrate [apply], brownout-migrate [apply], roles-migrate [apply], firing-arc-migrate [apply], range-falloff [apply] [--effective <fraction>] [--end <multiplier>], targeting-catalog [apply], targeting-catalog-6c [apply], targeting-catalog-6d [apply], ship-authoring create|inspect|validate|compose, field-kinds [apply], mine-launcher [apply], smoke-hull [apply], smoke-rounds [apply], roles-backfill [apply], pd-gear [apply], consumables-first-set [apply], launch-suppressor [apply], pirates-faction [apply], verse-grammar [apply], generic-designs [apply], sensor-stat-set [apply], inspection-scanner [apply], minor-power-factions [apply], product-lines weapons|minor-powers|gear [apply], doctrine-catalog [apply], assembly-from-maker [apply]");
|
||||
Console.WriteLine("commands: census, factions, station-fit, hardpoint-fit, loadout [seed], save, settings, settings-dump, dangling [clear <Type.Member>]... [apply], shield-migrate [apply], brownout-migrate [apply], roles-migrate [apply], firing-arc-migrate [apply], range-falloff [apply] [--effective <fraction>] [--end <multiplier>], targeting-catalog [apply], targeting-catalog-6c [apply], targeting-catalog-6d [apply], ship-authoring create|inspect|validate|compose, field-kinds [apply], mine-launcher [apply], smoke-hull [apply], smoke-rounds [apply], roles-backfill [apply], pd-gear [apply], consumables-first-set [apply], launch-suppressor [apply], pirates-faction [apply], verse-grammar [apply], generic-designs [apply], sensor-stat-set [apply], inspection-scanner [apply], minor-power-factions [apply], place-names [apply], product-lines weapons|minor-powers|gear [apply], doctrine-catalog [apply], assembly-from-maker [apply]");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -542,7 +545,6 @@ public static class Program
|
|||
|
||||
var tutorial = authored.Read<TutorialGenerationSettings>("TutorialGenerationSettings");
|
||||
var background = authored.Read<SectorBackgroundSettings>("TutorialBackgroundSettings");
|
||||
var names = authored.Read<NameGeneratorSettings>("NameGeneratorSettings");
|
||||
var zones = authored.Read<ZoneGenerationSettings>("ZoneSettings");
|
||||
var gameplay = authored.Read<GameplaySettings>("GameplaySettings");
|
||||
|
||||
|
|
@ -552,7 +554,6 @@ public static class Program
|
|||
$"{tutorial.ZoneCount} zones, link density {tutorial.LinkDensity}");
|
||||
Console.WriteLine($"background: frequency {background.NoiseFrequency}, amplitude {background.NoiseAmplitude}, " +
|
||||
$"cloud exponent {background.CloudExponent}, density at centre {background.CloudDensity(new float2(.5f)):0.000}");
|
||||
Console.WriteLine($"names: order {names.NameGeneratorOrder}, length {names.NameGeneratorMinLength}-{names.NameGeneratorMaxLength}");
|
||||
Console.WriteLine($"zones: sun mass {zones.SunMass}, planet mass {zones.PlanetMass}, satellite passes {zones.SatellitePasses}, " +
|
||||
$"belt probability {zones.BeltProbability}");
|
||||
Console.WriteLine($" zone radius curve {zones.ZoneRadius?.Minimum}-{zones.ZoneRadius?.Maximum} exponent {zones.ZoneRadius?.Exponent}");
|
||||
|
|
@ -694,23 +695,22 @@ public static class Program
|
|||
return false;
|
||||
}
|
||||
|
||||
// Each faction's generation-critical links. Galaxy.GenerateNames dereferences the geoname file without a
|
||||
// guard, so an unset link there stops galaxy generation outright.
|
||||
// Each faction's generation-critical links and the size of its curated place list. An empty list is not broken:
|
||||
// Galaxy.NamePlaces falls back to catalogue designations for a faction without one.
|
||||
private static int Factions()
|
||||
{
|
||||
var db = AetherDb.Open();
|
||||
var products = db.Cache.GetAll<FactionProductData>().ToArray();
|
||||
Console.WriteLine($"{"faction",-26} {"short",-13} {"geonames",-22} {"boss hull",-14} {"influence",-9} {"doctrine",-10} products");
|
||||
Console.WriteLine($"{"faction",-26} {"short",-13} {"places",-22} {"boss hull",-14} {"influence",-9} {"doctrine",-10} products");
|
||||
var broken = 0;
|
||||
foreach (var faction in db.Cache.GetAll<Faction>().OrderBy(f => f.Name))
|
||||
{
|
||||
var key = db.Cache.RefOf(faction).Key;
|
||||
var geonames = faction.GeonameFile.IsSet() ? "set" : "UNSET";
|
||||
var boss = !faction.BossHull.IsSet()
|
||||
? "none"
|
||||
: db.Cache.Get(faction.BossHull)?.Name ?? "DANGLING";
|
||||
if (geonames == "UNSET" || boss == "DANGLING") broken++;
|
||||
Console.WriteLine($"{faction.Name,-26} {faction.ShortName,-13} {geonames,-22} {boss,-14} {faction.InfluenceDistance,-9} {(faction.Doctrine == null ? "none" : faction.Doctrine.EngageOn.ToString()),-10} " +
|
||||
if (boss == "DANGLING") broken++;
|
||||
Console.WriteLine($"{faction.Name,-26} {faction.ShortName,-13} {faction.Places.Length,-22} {boss,-14} {faction.InfluenceDistance,-9} {(faction.Doctrine == null ? "none" : faction.Doctrine.EngageOn.ToString()),-10} " +
|
||||
products.Count(p => p.Manufacturer.Key.Equals(key)));
|
||||
}
|
||||
Console.WriteLine($"\n{broken} factions carry a link that would break generation");
|
||||
|
|
@ -2303,9 +2303,6 @@ public static class Program
|
|||
|
||||
string CodePoints(string text) => string.Join(" ", text.EnumerateRunes().Select(r => $"U+{r.Value:X4}"));
|
||||
|
||||
var nameFile = db.Cache.GetAll<NameFile>().FirstOrDefault(n => n.Name == "pleiades");
|
||||
if (nameFile == null) throw new InvalidOperationException("No name file named \"pleiades\".");
|
||||
|
||||
var others = db.Cache.GetAll<Faction>().ToArray();
|
||||
var pirates = new Faction
|
||||
{
|
||||
|
|
@ -2314,7 +2311,6 @@ public static class Program
|
|||
Description = "A loose coalition of crews and hidden docks moving people and goods outside official control. No command, no common law: possession has a maintenance schedule.",
|
||||
PrimaryColor = new float3(.42f, .40f, .37f),
|
||||
SecondaryColor = new float3(1f, .45f, .05f),
|
||||
GeonameFile = db.Cache.RefOf(nameFile),
|
||||
InfluenceDistance = 3,
|
||||
};
|
||||
foreach (var other in AllegianceGaps(db, pirates, others)) pirates.Allegiance[db.Cache.RefOf(other)] = 1f;
|
||||
|
|
@ -2338,7 +2334,7 @@ public static class Program
|
|||
Roles: (design.Roles ?? new List<ItemRole>()).Select(role => new ProductRole { Role = role.Name, Mean = PirateProfile.Mean, StandardDeviation = PirateProfile.Dev }).ToList());
|
||||
}).ToArray();
|
||||
|
||||
Console.WriteLine($"New faction \"{pirates.Name}\": influence {pirates.InfluenceDistance}, names \"{nameFile.Name}\", allegiance 1.0 to {pirates.Allegiance.Count} factions.");
|
||||
Console.WriteLine($"New faction \"{pirates.Name}\": influence {pirates.InfluenceDistance}, allegiance 1.0 to {pirates.Allegiance.Count} factions.");
|
||||
Console.WriteLine($" colours: primary ({pirates.PrimaryColor.x:0.00}, {pirates.PrimaryColor.y:0.00}, {pirates.PrimaryColor.z:0.00}) primer grey, secondary ({pirates.SecondaryColor.x:0.00}, {pirates.SecondaryColor.y:0.00}, {pirates.SecondaryColor.z:0.00}) hazard orange");
|
||||
Console.WriteLine($" {pirates.Description}");
|
||||
Console.WriteLine($"\nNew products ({products.Length}):");
|
||||
|
|
|
|||
Loading…
Reference in a new issue