diff --git a/.gitignore b/.gitignore index ac9c49142..2461b9473 100644 --- a/.gitignore +++ b/.gitignore @@ -95,6 +95,7 @@ Packages/packages-lock.json GameData/run.cc GameData/player.cc GameData/RemovedMods/ +Vault/Aetheria/.obsidian/plugins/copilot/data.json # Agent scratch, sandboxed toolchain caches, and throwaway checkouts. These had # grown to 47 untracked entries; the worktree must stay legible, so they never diff --git a/.lfsconfig b/.lfsconfig deleted file mode 100644 index d9ce439fc..000000000 --- a/.lfsconfig +++ /dev/null @@ -1,2 +0,0 @@ -[lfs] - url = https://eureka.gamecult.org/forge/GameCult/Aetheria.git/info/lfs diff --git a/Assets/Editor/AetheriaBuild.cs b/Assets/Editor/AetheriaBuild.cs index 3cf434ee4..7d3c891d2 100644 --- a/Assets/Editor/AetheriaBuild.cs +++ b/Assets/Editor/AetheriaBuild.cs @@ -8,12 +8,12 @@ using UnityEditor.AddressableAssets.Settings; using UnityEditor.Build.Reporting; using Debug = UnityEngine.Debug; -// The one way to make a player directory, for Windows or Linux. Owns what the directory contains: the player and its data, +// The one way to make a Win64 player directory. Owns what the directory contains: Aetheria.exe and its data, // GameData/Aetheria.cc, GameData/Narrative/**, GameData/Mods/** and ModTools/AetherDb. Manual builds, smokes and // release cuts all call this and stage nothing themselves. // // Unity -batchmode -quit -projectPath -executeMethod AetheriaBuild.Build -// -aetheriaVersion -aetheriaOut [-aetheriaTarget win64|linux64] -logFile +// -aetheriaVersion -aetheriaOut -logFile // // AetherDb is published from tools/AetherDb, so the headless build's CULTLIB_ROOT / CULTMATH_ROOT (Directory.Build.props) // must be set for dotnet when the defaults do not hold the pinned revisions. @@ -22,14 +22,6 @@ public static class AetheriaBuild { private static readonly Regex SemVer = new Regex(@"^\d+\.\d+\.\d+([-+][0-9A-Za-z.-]+)?$"); - // A target: the player Unity builds, its executable, and the runtime AetherDb is published for. - private static (BuildTarget target, string player, string runtime, string aetherDb) Target(string name) => name switch - { - "win64" => (BuildTarget.StandaloneWindows64, "Aetheria.exe", "win-x64", "AetherDb.exe"), - "linux64" => (BuildTarget.StandaloneLinux64, "Aetheria.x86_64", "linux-x64", "AetherDb"), - _ => throw new InvalidOperationException($"-aetheriaTarget {name}: expected win64 or linux64"), - }; - public static void Build() { var step = "arguments"; @@ -38,7 +30,6 @@ public static class AetheriaBuild var version = Argument("-aetheriaVersion"); if (!SemVer.IsMatch(version)) throw new InvalidOperationException("-aetheriaVersion is not a semver"); var output = Path.GetFullPath(Argument("-aetheriaOut")); - var target = Target(Argument("-aetheriaTarget", "win64")); var repoRoot = Directory.GetParent(UnityEngine.Application.dataPath).FullName; step = "version"; @@ -55,8 +46,8 @@ public static class AetheriaBuild var report = BuildPipeline.BuildPlayer(new BuildPlayerOptions { scenes = scenes, - locationPathName = Path.Combine(output, target.player), - target = target.target, + locationPathName = Path.Combine(output, "Aetheria.exe"), + target = BuildTarget.StandaloneWindows64, options = BuildOptions.None, }); if (report.summary.result != BuildResult.Succeeded || report.summary.totalErrors != 0) @@ -66,7 +57,7 @@ public static class AetheriaBuild PlayerStaging.Stage(repoRoot, output); step = "aetherdb"; - Publish(repoRoot, Path.Combine(output, "ModTools"), target.runtime, target.aetherDb); + Publish(repoRoot, Path.Combine(output, "ModTools")); step = "burst"; var burst = Path.Combine(output, "Aetheria_BurstDebugInformation_DoNotShip"); @@ -83,11 +74,11 @@ public static class AetheriaBuild EditorApplication.Exit(0); } - private static void Publish(string repoRoot, string modTools, string runtime, string aetherDb) + private static void Publish(string repoRoot, string modTools) { var info = new ProcessStartInfo("dotnet") { - Arguments = $"publish tools/AetherDb -c Release -r {runtime} --self-contained -p:PublishSingleFile=true -o \"{modTools}\"", + Arguments = $"publish tools/AetherDb -c Release -r win-x64 --self-contained -p:PublishSingleFile=true -o \"{modTools}\"", WorkingDirectory = repoRoot, UseShellExecute = false, RedirectStandardOutput = true, @@ -101,16 +92,15 @@ public static class AetheriaBuild if (process.ExitCode != 0) throw new InvalidOperationException($"dotnet publish exited {process.ExitCode}:\n{text}\n{errors.Result}"); } - if (!File.Exists(Path.Combine(modTools, aetherDb))) - throw new InvalidOperationException($"dotnet publish produced no ModTools/{aetherDb}"); + if (!File.Exists(Path.Combine(modTools, "AetherDb.exe"))) + throw new InvalidOperationException("dotnet publish produced no ModTools/AetherDb.exe"); } - private static string Argument(string name, string fallback = null) + private static string Argument(string name) { var args = Environment.GetCommandLineArgs(); var at = Array.IndexOf(args, name); - if (at < 0 || at + 1 >= args.Length) - return fallback ?? throw new InvalidOperationException(name + " is required"); + if (at < 0 || at + 1 >= args.Length) throw new InvalidOperationException(name + " is required"); return args[at + 1]; } } diff --git a/Assets/Scripts/Editor/DrawnPoseWiringVerify.cs b/Assets/Scripts/Editor/DrawnPoseWiringVerify.cs index 69109a622..85223b510 100644 --- a/Assets/Scripts/Editor/DrawnPoseWiringVerify.cs +++ b/Assets/Scripts/Editor/DrawnPoseWiringVerify.cs @@ -159,7 +159,7 @@ namespace Aetheria.EditorTools MaxRange = 1e6f, FireRange = 1e6f, CommitTime = 1e5f, ArrivalTime = 2e5f }; var projectile = new GameObject("Wiring Round").AddComponent(); - projectile.Launch(null, zone, shot, new Vector3(0, 0, 0)); + projectile.Launch(zone, shot, new Vector3(0, 0, 0)); yield return null; zone.Update(.5f); diff --git a/Assets/Scripts/Editor/ScenarioPickerCapture.cs b/Assets/Scripts/Editor/ScenarioPickerCapture.cs deleted file mode 100644 index b39dd7a1b..000000000 --- a/Assets/Scripts/Editor/ScenarioPickerCapture.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using UnityEditor; -using UnityEditor.SceneManagement; -using UnityEngine; -using UnityEngine.UI; - -// A render of the New Game screen as the game builds it: the real Main Menu scene's panel prototype, docked and filled -// by ScenarioPicker.Dock and ScenarioPicker.Show with every scenario group, on a plain dark background (the cloud -// backdrop is a play-mode renderer). Writes /Temp/scenario-picker.png at 1920x1080, or the path after -pngOut (Unity empties Temp on exit). -// -// Unity -batchmode -projectPath -executeMethod ScenarioPickerCapture.Run [-pngOut ] -logFile -// -// Needs a GPU device; do not pass -nographics or -quit: Run exits the Editor itself, 0 on success and 1 on any failure. -public static class ScenarioPickerCapture -{ - private const int Width = 1920, Height = 1080; - - public static void Run() - { - try - { - Capture(); - EditorApplication.Exit(0); - } - catch (Exception exception) - { - Debug.LogException(exception); - EditorApplication.Exit(1); - } - } - - // The menu's two panes built the way MainMenu.Start builds them, against the Main Menu scene. - internal static (MainMenu menu, PropertiesPanel list, PropertiesPanel detail) Build() - { - EditorSceneManager.OpenScene("Assets/Scenes/Main Menu.unity", OpenSceneMode.Single); - var menu = UnityEngine.Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None)[0]; - menu.PanelPrototype.gameObject.SetActive(false); - var list = menu.PanelPrototype.Instantiate(); - var detail = menu.PanelPrototype.Instantiate(); - // Edit mode runs no Awake, which is what activates each row a panel adds. - list.Awake(); - detail.Awake(); - ScenarioPicker.Dock(detail, list); - return (menu, list, detail); - } - - internal static List<(string label, Scenario[] scenarios)> AllGroups() => new List<(string label, Scenario[] scenarios)> - { - ("modes", Scenarios.Modes), ("development", Scenarios.Development), ("test arenas", Scenarios.Tests) - }; - - private static void Capture() - { - var (menu, list, detail) = Build(); - menu.Dialog.gameObject.SetActive(false); - list.Title.text = MainMenu.TitleSubtitle("new game", "scenarios"); - ScenarioPicker.Show(list, detail, AllGroups(), _ => { }, () => { }, menu.Dialog.Confirm); - list.RefreshValues(); - detail.RefreshValues(); - - var canvas = menu.GetComponentInParent(true).rootCanvas; - var target = new RenderTexture(Width, Height, 24, RenderTextureFormat.ARGB32); - var cameraObject = new GameObject("ScenarioPickerCapture camera"); - var camera = cameraObject.AddComponent(); - camera.orthographic = true; - camera.orthographicSize = 5; - camera.clearFlags = CameraClearFlags.SolidColor; - camera.backgroundColor = new Color(.04f, .05f, .08f); - camera.cullingMask = 1 << LayerMask.NameToLayer("UI"); - camera.targetTexture = target; - canvas.renderMode = RenderMode.ScreenSpaceCamera; - canvas.worldCamera = camera; - canvas.planeDistance = 10; - canvas.gameObject.SetActive(true); - Canvas.ForceUpdateCanvases(); - LayoutRebuilder.ForceRebuildLayoutImmediate((RectTransform) list.transform); - LayoutRebuilder.ForceRebuildLayoutImmediate((RectTransform) detail.transform); - foreach (var text in list.transform.GetComponentsInChildren()) - text.ForceMeshUpdate(); - Canvas.ForceUpdateCanvases(); - list.Content.GetComponentInParent().onValueChanged.Invoke(Vector2.zero); - camera.Render(); - - var previous = RenderTexture.active; - RenderTexture.active = target; - var read = new Texture2D(Width, Height, TextureFormat.RGBA32, false); - read.ReadPixels(new Rect(0, 0, Width, Height), 0, 0); - RenderTexture.active = previous; - - var background = read.GetPixel(0, 0); - var differing = 0; - foreach (var pixel in read.GetPixels32()) - if (Mathf.Abs(pixel.r / 255f - background.r) + Mathf.Abs(pixel.g / 255f - background.g) + Mathf.Abs(pixel.b / 255f - background.b) > .05f) - differing++; - if (differing < Width * Height / 100) - throw new InvalidOperationException($"The capture is uniform: only {differing} pixels differ from the background."); - - var path = Path.GetFullPath(Path.Combine(Application.dataPath, "..", "Temp", "scenario-picker.png")); - var args = Environment.GetCommandLineArgs(); - var outIndex = Array.IndexOf(args, "-pngOut"); - if (outIndex >= 0) path = Path.GetFullPath(args[outIndex + 1]); - Directory.CreateDirectory(Path.GetDirectoryName(path)); - File.WriteAllBytes(path, read.EncodeToPNG()); - Debug.Log($"[ScenarioPickerCapture] {path} ({differing} of {Width * Height} pixels differ from the background)"); - } -} diff --git a/Assets/Scripts/Editor/ScenarioPickerCapture.cs.meta b/Assets/Scripts/Editor/ScenarioPickerCapture.cs.meta deleted file mode 100644 index ebeb742e8..000000000 --- a/Assets/Scripts/Editor/ScenarioPickerCapture.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 2e79f280179150e4080250c45e07cd69 \ No newline at end of file diff --git a/Assets/Scripts/Editor/ScenarioPickerVerify.cs b/Assets/Scripts/Editor/ScenarioPickerVerify.cs deleted file mode 100644 index 6816cafc9..000000000 --- a/Assets/Scripts/Editor/ScenarioPickerVerify.cs +++ /dev/null @@ -1,265 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using TMPro; -using UnityEditor; -using UnityEngine; -using UnityEngine.EventSystems; -using UnityEngine.UI; - -// The picker's selection and keyboard rules, driven through the events the game delivers: Move and Cancel reach the -// focused Launch button through ExecuteEvents, as the Input System UI module sends them, and a click is Button.onClick. -// -// Unity -batchmode -projectPath -executeMethod ScenarioPickerVerify.Run -logFile -// -// Exits the Editor itself, 0 when every rule holds and 1 on the first that does not. -public static class ScenarioPickerVerify -{ - private sealed class Fake : Scenario - { - private readonly uint _seed; - private readonly bool _ambient; - public Fake(string name, uint seed = 0, bool ambient = true) { Name = name; _seed = seed; _ambient = ambient; } - public override string Name { get; } - public override string Id => Name.ToLowerInvariant(); - public override string Brief => $"brief of {Name}"; - public override uint Seed => _seed; - public override bool Ambient => _ambient; - public override Galaxy Generate(GalaxyStage stage) => throw new NotSupportedException(); - public override void Stage(ScenarioStage stage) => throw new NotSupportedException(); - } - - private sealed class Screen - { - public ScenarioPicker Picker; - public PropertiesPanel List, Detail; - public Button[] Rows; - public Button Back, Launch; - public readonly List Launched = new List(); - public int Backs; - public Scenario[] A, B, C; - } - - public static void Run() - { - try - { - SelectionFollowsClicksAndArrows(); - ArrowsStopAtTheEnds(); - LeftAndRightAreIgnored(); - LaunchIsOnePathAndFocusClears(); - CancelAndBackAreOnePath(); - DetailPaneFollowsSelection(); - OneGroupHasNoSectionLabel(); - LaunchesOnceHoweverItIsReached(); - ArrowingThroughTheRealListKeepsTheSelectionVisible(); - Debug.Log("[ScenarioPickerVerify] ok"); - EditorApplication.Exit(0); - } - catch (Exception exception) - { - Debug.LogException(exception); - EditorApplication.Exit(1); - } - } - - private static void Require(bool condition, string what) - { - if (!condition) throw new InvalidOperationException(what); - } - - private static Screen Build(bool oneGroup = false, bool real = false) - { - var (menu, list, detail) = ScenarioPickerCapture.Build(); - // Build opens the scene afresh, which destroys the previous EventSystem. - var events = new GameObject("verify EventSystem").AddComponent(); - // Edit mode never calls OnEnable, which is what registers an EventSystem as current. - typeof(EventSystem).GetMethod("OnEnable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).Invoke(events, null); - var screen = new Screen - { - List = list, Detail = detail, - A = new Scenario[] { new Fake("Alpha", 7), new Fake("Beta") }, - B = new Scenario[] { new Fake("Gamma", ambient: false) }, - C = new Scenario[] { new Fake("Delta"), new Fake("Echo") } - }; - var groups = real ? ScenarioPickerCapture.AllGroups() : oneGroup - ? new List<(string label, Scenario[] scenarios)> { ("modes", screen.A) } - : new List<(string label, Scenario[] scenarios)> { ("modes", screen.A), ("development", screen.B), ("test arenas", screen.C) }; - // The menu builds a panel while it is inactive and shows it afterwards; the picker must find its scroll all the same. - if (real) list.gameObject.SetActive(false); - screen.Picker = ScenarioPicker.Show(list, detail, groups, screen.Launched.Add, () => screen.Backs++, menu.Dialog.Confirm); - var buttons = list.GetComponentsInChildren(false).Where(button => !button.transform.IsChildOf(detail.transform)).Select(button => button.Button).ToArray(); - screen.Rows = buttons.Take(buttons.Length - 1).ToArray(); - screen.Back = buttons.Last(); - screen.Launch = detail.GetComponentsInChildren