diff --git a/.gitignore b/.gitignore index 2461b9473..ac9c49142 100644 --- a/.gitignore +++ b/.gitignore @@ -95,7 +95,6 @@ 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 new file mode 100644 index 000000000..d9ce439fc --- /dev/null +++ b/.lfsconfig @@ -0,0 +1,2 @@ +[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 7d3c891d2..3cf434ee4 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 Win64 player directory. Owns what the directory contains: Aetheria.exe and its data, +// The one way to make a player directory, for Windows or Linux. Owns what the directory contains: the player 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 -logFile +// -aetheriaVersion -aetheriaOut [-aetheriaTarget win64|linux64] -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,6 +22,14 @@ 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"; @@ -30,6 +38,7 @@ 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"; @@ -46,8 +55,8 @@ public static class AetheriaBuild var report = BuildPipeline.BuildPlayer(new BuildPlayerOptions { scenes = scenes, - locationPathName = Path.Combine(output, "Aetheria.exe"), - target = BuildTarget.StandaloneWindows64, + locationPathName = Path.Combine(output, target.player), + target = target.target, options = BuildOptions.None, }); if (report.summary.result != BuildResult.Succeeded || report.summary.totalErrors != 0) @@ -57,7 +66,7 @@ public static class AetheriaBuild PlayerStaging.Stage(repoRoot, output); step = "aetherdb"; - Publish(repoRoot, Path.Combine(output, "ModTools")); + Publish(repoRoot, Path.Combine(output, "ModTools"), target.runtime, target.aetherDb); step = "burst"; var burst = Path.Combine(output, "Aetheria_BurstDebugInformation_DoNotShip"); @@ -74,11 +83,11 @@ public static class AetheriaBuild EditorApplication.Exit(0); } - private static void Publish(string repoRoot, string modTools) + private static void Publish(string repoRoot, string modTools, string runtime, string aetherDb) { var info = new ProcessStartInfo("dotnet") { - Arguments = $"publish tools/AetherDb -c Release -r win-x64 --self-contained -p:PublishSingleFile=true -o \"{modTools}\"", + Arguments = $"publish tools/AetherDb -c Release -r {runtime} --self-contained -p:PublishSingleFile=true -o \"{modTools}\"", WorkingDirectory = repoRoot, UseShellExecute = false, RedirectStandardOutput = true, @@ -92,15 +101,16 @@ 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.exe"))) - throw new InvalidOperationException("dotnet publish produced no ModTools/AetherDb.exe"); + if (!File.Exists(Path.Combine(modTools, aetherDb))) + throw new InvalidOperationException($"dotnet publish produced no ModTools/{aetherDb}"); } - private static string Argument(string name) + private static string Argument(string name, string fallback = null) { var args = Environment.GetCommandLineArgs(); var at = Array.IndexOf(args, name); - if (at < 0 || at + 1 >= args.Length) throw new InvalidOperationException(name + " is required"); + if (at < 0 || at + 1 >= args.Length) + return fallback ?? throw new InvalidOperationException(name + " is required"); return args[at + 1]; } } diff --git a/Assets/Plugins/GPU Noise/ClassicNoise2D.cginc b/Assets/Plugins/GPU Noise/ClassicNoise2D.cginc deleted file mode 100644 index ef6b2e96c..000000000 --- a/Assets/Plugins/GPU Noise/ClassicNoise2D.cginc +++ /dev/null @@ -1,129 +0,0 @@ -// -// Noise Shader Library for Unity - https://github.com/keijiro/NoiseShader -// -// Original work (webgl-noise) Copyright (C) 2011 Stefan Gustavson -// Translation and modification was made by Keijiro Takahashi. -// -// This shader is based on the webgl-noise GLSL shader. For further details -// of the original shader, please see the following description from the -// original source code. -// - -// -// GLSL textureless classic 2D noise "cnoise", -// with an RSL-style periodic variant "pnoise". -// Author: Stefan Gustavson (stefan.gustavson@liu.se) -// Version: 2011-08-22 -// -// Many thanks to Ian McEwan of Ashima Arts for the -// ideas for permutation and gradient selection. -// -// Copyright (c) 2011 Stefan Gustavson. All rights reserved. -// Distributed under the MIT license. See LICENSE file. -// https://github.com/ashima/webgl-noise -// - -float4 mod(float4 x, float4 y) -{ - return x - y * floor(x / y); -} - -float4 mod289(float4 x) -{ - return x - floor(x / 289.0) * 289.0; -} - -float4 permute(float4 x) -{ - return mod289(((x*34.0)+1.0)*x); -} - -float4 taylorInvSqrt(float4 r) -{ - return (float4)1.79284291400159 - r * 0.85373472095314; -} - -float2 fade(float2 t) { - return t*t*t*(t*(t*6.0-15.0)+10.0); -} - -// Classic Perlin noise -float cnoise(float2 P) -{ - float4 Pi = floor(P.xyxy) + float4(0.0, 0.0, 1.0, 1.0); - float4 Pf = frac (P.xyxy) - float4(0.0, 0.0, 1.0, 1.0); - Pi = mod289(Pi); // To avoid truncation effects in permutation - float4 ix = Pi.xzxz; - float4 iy = Pi.yyww; - float4 fx = Pf.xzxz; - float4 fy = Pf.yyww; - - float4 i = permute(permute(ix) + iy); - - float4 gx = frac(i / 41.0) * 2.0 - 1.0 ; - float4 gy = abs(gx) - 0.5 ; - float4 tx = floor(gx + 0.5); - gx = gx - tx; - - float2 g00 = float2(gx.x,gy.x); - float2 g10 = float2(gx.y,gy.y); - float2 g01 = float2(gx.z,gy.z); - float2 g11 = float2(gx.w,gy.w); - - float4 norm = taylorInvSqrt(float4(dot(g00, g00), dot(g01, g01), dot(g10, g10), dot(g11, g11))); - g00 *= norm.x; - g01 *= norm.y; - g10 *= norm.z; - g11 *= norm.w; - - float n00 = dot(g00, float2(fx.x, fy.x)); - float n10 = dot(g10, float2(fx.y, fy.y)); - float n01 = dot(g01, float2(fx.z, fy.z)); - float n11 = dot(g11, float2(fx.w, fy.w)); - - float2 fade_xy = fade(Pf.xy); - float2 n_x = lerp(float2(n00, n01), float2(n10, n11), fade_xy.x); - float n_xy = lerp(n_x.x, n_x.y, fade_xy.y); - return 2.3 * n_xy; -} - -// Classic Perlin noise, periodic variant -float pnoise(float2 P, float2 rep) -{ - float4 Pi = floor(P.xyxy) + float4(0.0, 0.0, 1.0, 1.0); - float4 Pf = frac (P.xyxy) - float4(0.0, 0.0, 1.0, 1.0); - Pi = mod(Pi, rep.xyxy); // To create noise with explicit period - Pi = mod289(Pi); // To avoid truncation effects in permutation - float4 ix = Pi.xzxz; - float4 iy = Pi.yyww; - float4 fx = Pf.xzxz; - float4 fy = Pf.yyww; - - float4 i = permute(permute(ix) + iy); - - float4 gx = frac(i / 41.0) * 2.0 - 1.0 ; - float4 gy = abs(gx) - 0.5 ; - float4 tx = floor(gx + 0.5); - gx = gx - tx; - - float2 g00 = float2(gx.x,gy.x); - float2 g10 = float2(gx.y,gy.y); - float2 g01 = float2(gx.z,gy.z); - float2 g11 = float2(gx.w,gy.w); - - float4 norm = taylorInvSqrt(float4(dot(g00, g00), dot(g01, g01), dot(g10, g10), dot(g11, g11))); - g00 *= norm.x; - g01 *= norm.y; - g10 *= norm.z; - g11 *= norm.w; - - float n00 = dot(g00, float2(fx.x, fy.x)); - float n10 = dot(g10, float2(fx.y, fy.y)); - float n01 = dot(g01, float2(fx.z, fy.z)); - float n11 = dot(g11, float2(fx.w, fy.w)); - - float2 fade_xy = fade(Pf.xy); - float2 n_x = lerp(float2(n00, n01), float2(n10, n11), fade_xy.x); - float n_xy = lerp(n_x.x, n_x.y, fade_xy.y); - return 2.3 * n_xy; -} diff --git a/Assets/Plugins/GPU Noise/ClassicNoise2D.cginc.meta b/Assets/Plugins/GPU Noise/ClassicNoise2D.cginc.meta deleted file mode 100644 index ff437e70a..000000000 --- a/Assets/Plugins/GPU Noise/ClassicNoise2D.cginc.meta +++ /dev/null @@ -1,5 +0,0 @@ -fileFormatVersion: 2 -guid: e1b0b5fd6a4e44923b40d856017562e1 -ShaderImporter: - defaultTextures: [] - userData: diff --git a/Assets/Plugins/GPU Noise/ClassicNoise3D.cginc b/Assets/Plugins/GPU Noise/ClassicNoise3D.cginc deleted file mode 100644 index 738f6d12a..000000000 --- a/Assets/Plugins/GPU Noise/ClassicNoise3D.cginc +++ /dev/null @@ -1,194 +0,0 @@ -// -// Noise Shader Library for Unity - https://github.com/keijiro/NoiseShader -// -// Original work (webgl-noise) Copyright (C) 2011 Stefan Gustavson -// Translation and modification was made by Keijiro Takahashi. -// -// This shader is based on the webgl-noise GLSL shader. For further details -// of the original shader, please see the following description from the -// original source code. -// - -// -// GLSL textureless classic 3D noise "cnoise", -// with an RSL-style periodic variant "pnoise". -// Author: Stefan Gustavson (stefan.gustavson@liu.se) -// Version: 2011-10-11 -// -// Many thanks to Ian McEwan of Ashima Arts for the -// ideas for permutation and gradient selection. -// -// Copyright (c) 2011 Stefan Gustavson. All rights reserved. -// Distributed under the MIT license. See LICENSE file. -// https://github.com/ashima/webgl-noise -// - -float3 mod(float3 x, float3 y) -{ - return x - y * floor(x / y); -} - -float3 mod289(float3 x) -{ - return x - floor(x / 289.0) * 289.0; -} - -float4 mod289(float4 x) -{ - return x - floor(x / 289.0) * 289.0; -} - -float4 permute(float4 x) -{ - return mod289(((x*34.0)+1.0)*x); -} - -float4 taylorInvSqrt(float4 r) -{ - return (float4)1.79284291400159 - r * 0.85373472095314; -} - -float3 fade(float3 t) { - return t*t*t*(t*(t*6.0-15.0)+10.0); -} - -// Classic Perlin noise -float cnoise(float3 P) -{ - float3 Pi0 = floor(P); // Integer part for indexing - float3 Pi1 = Pi0 + (float3)1.0; // Integer part + 1 - Pi0 = mod289(Pi0); - Pi1 = mod289(Pi1); - float3 Pf0 = frac(P); // Fractional part for interpolation - float3 Pf1 = Pf0 - (float3)1.0; // Fractional part - 1.0 - float4 ix = float4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); - float4 iy = float4(Pi0.y, Pi0.y, Pi1.y, Pi1.y); - float4 iz0 = (float4)Pi0.z; - float4 iz1 = (float4)Pi1.z; - - float4 ixy = permute(permute(ix) + iy); - float4 ixy0 = permute(ixy + iz0); - float4 ixy1 = permute(ixy + iz1); - - float4 gx0 = ixy0 / 7.0; - float4 gy0 = frac(floor(gx0) / 7.0) - 0.5; - gx0 = frac(gx0); - float4 gz0 = (float4)0.5 - abs(gx0) - abs(gy0); - float4 sz0 = step(gz0, (float4)0.0); - gx0 -= sz0 * (step((float4)0.0, gx0) - 0.5); - gy0 -= sz0 * (step((float4)0.0, gy0) - 0.5); - - float4 gx1 = ixy1 / 7.0; - float4 gy1 = frac(floor(gx1) / 7.0) - 0.5; - gx1 = frac(gx1); - float4 gz1 = (float4)0.5 - abs(gx1) - abs(gy1); - float4 sz1 = step(gz1, (float4)0.0); - gx1 -= sz1 * (step((float4)0.0, gx1) - 0.5); - gy1 -= sz1 * (step((float4)0.0, gy1) - 0.5); - - float3 g000 = float3(gx0.x,gy0.x,gz0.x); - float3 g100 = float3(gx0.y,gy0.y,gz0.y); - float3 g010 = float3(gx0.z,gy0.z,gz0.z); - float3 g110 = float3(gx0.w,gy0.w,gz0.w); - float3 g001 = float3(gx1.x,gy1.x,gz1.x); - float3 g101 = float3(gx1.y,gy1.y,gz1.y); - float3 g011 = float3(gx1.z,gy1.z,gz1.z); - float3 g111 = float3(gx1.w,gy1.w,gz1.w); - - float4 norm0 = taylorInvSqrt(float4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110))); - g000 *= norm0.x; - g010 *= norm0.y; - g100 *= norm0.z; - g110 *= norm0.w; - - float4 norm1 = taylorInvSqrt(float4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111))); - g001 *= norm1.x; - g011 *= norm1.y; - g101 *= norm1.z; - g111 *= norm1.w; - - float n000 = dot(g000, Pf0); - float n100 = dot(g100, float3(Pf1.x, Pf0.y, Pf0.z)); - float n010 = dot(g010, float3(Pf0.x, Pf1.y, Pf0.z)); - float n110 = dot(g110, float3(Pf1.x, Pf1.y, Pf0.z)); - float n001 = dot(g001, float3(Pf0.x, Pf0.y, Pf1.z)); - float n101 = dot(g101, float3(Pf1.x, Pf0.y, Pf1.z)); - float n011 = dot(g011, float3(Pf0.x, Pf1.y, Pf1.z)); - float n111 = dot(g111, Pf1); - - float3 fade_xyz = fade(Pf0); - float4 n_z = lerp(float4(n000, n100, n010, n110), float4(n001, n101, n011, n111), fade_xyz.z); - float2 n_yz = lerp(n_z.xy, n_z.zw, fade_xyz.y); - float n_xyz = lerp(n_yz.x, n_yz.y, fade_xyz.x); - return 2.2 * n_xyz; -} - -// Classic Perlin noise, periodic variant -float pnoise(float3 P, float3 rep) -{ - float3 Pi0 = mod(floor(P), rep); // Integer part, modulo period - float3 Pi1 = mod(Pi0 + (float3)1.0, rep); // Integer part + 1, mod period - Pi0 = mod289(Pi0); - Pi1 = mod289(Pi1); - float3 Pf0 = frac(P); // Fractional part for interpolation - float3 Pf1 = Pf0 - (float3)1.0; // Fractional part - 1.0 - float4 ix = float4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); - float4 iy = float4(Pi0.y, Pi0.y, Pi1.y, Pi1.y); - float4 iz0 = (float4)Pi0.z; - float4 iz1 = (float4)Pi1.z; - - float4 ixy = permute(permute(ix) + iy); - float4 ixy0 = permute(ixy + iz0); - float4 ixy1 = permute(ixy + iz1); - - float4 gx0 = ixy0 / 7.0; - float4 gy0 = frac(floor(gx0) / 7.0) - 0.5; - gx0 = frac(gx0); - float4 gz0 = (float4)0.5 - abs(gx0) - abs(gy0); - float4 sz0 = step(gz0, (float4)0.0); - gx0 -= sz0 * (step((float4)0.0, gx0) - 0.5); - gy0 -= sz0 * (step((float4)0.0, gy0) - 0.5); - - float4 gx1 = ixy1 / 7.0; - float4 gy1 = frac(floor(gx1) / 7.0) - 0.5; - gx1 = frac(gx1); - float4 gz1 = (float4)0.5 - abs(gx1) - abs(gy1); - float4 sz1 = step(gz1, (float4)0.0); - gx1 -= sz1 * (step((float4)0.0, gx1) - 0.5); - gy1 -= sz1 * (step((float4)0.0, gy1) - 0.5); - - float3 g000 = float3(gx0.x,gy0.x,gz0.x); - float3 g100 = float3(gx0.y,gy0.y,gz0.y); - float3 g010 = float3(gx0.z,gy0.z,gz0.z); - float3 g110 = float3(gx0.w,gy0.w,gz0.w); - float3 g001 = float3(gx1.x,gy1.x,gz1.x); - float3 g101 = float3(gx1.y,gy1.y,gz1.y); - float3 g011 = float3(gx1.z,gy1.z,gz1.z); - float3 g111 = float3(gx1.w,gy1.w,gz1.w); - - float4 norm0 = taylorInvSqrt(float4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110))); - g000 *= norm0.x; - g010 *= norm0.y; - g100 *= norm0.z; - g110 *= norm0.w; - float4 norm1 = taylorInvSqrt(float4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111))); - g001 *= norm1.x; - g011 *= norm1.y; - g101 *= norm1.z; - g111 *= norm1.w; - - float n000 = dot(g000, Pf0); - float n100 = dot(g100, float3(Pf1.x, Pf0.y, Pf0.z)); - float n010 = dot(g010, float3(Pf0.x, Pf1.y, Pf0.z)); - float n110 = dot(g110, float3(Pf1.x, Pf1.y, Pf0.z)); - float n001 = dot(g001, float3(Pf0.x, Pf0.y, Pf1.z)); - float n101 = dot(g101, float3(Pf1.x, Pf0.y, Pf1.z)); - float n011 = dot(g011, float3(Pf0.x, Pf1.y, Pf1.z)); - float n111 = dot(g111, Pf1); - - float3 fade_xyz = fade(Pf0); - float4 n_z = lerp(float4(n000, n100, n010, n110), float4(n001, n101, n011, n111), fade_xyz.z); - float2 n_yz = lerp(n_z.xy, n_z.zw, fade_xyz.y); - float n_xyz = lerp(n_yz.x, n_yz.y, fade_xyz.x); - return 2.2 * n_xyz; -} diff --git a/Assets/Plugins/GPU Noise/ClassicNoise3D.cginc.meta b/Assets/Plugins/GPU Noise/ClassicNoise3D.cginc.meta deleted file mode 100644 index b4dd551ab..000000000 --- a/Assets/Plugins/GPU Noise/ClassicNoise3D.cginc.meta +++ /dev/null @@ -1,5 +0,0 @@ -fileFormatVersion: 2 -guid: 3ba1abd1cb44c47b68fbb577c6c4ba3e -ShaderImporter: - defaultTextures: [] - userData: diff --git a/Assets/Plugins/GPU Noise/SimplexNoise3D.cginc b/Assets/Plugins/GPU Noise/SimplexNoise3D.cginc deleted file mode 100644 index 0b7add240..000000000 --- a/Assets/Plugins/GPU Noise/SimplexNoise3D.cginc +++ /dev/null @@ -1,114 +0,0 @@ -// -// Noise Shader Library for Unity - https://github.com/keijiro/NoiseShader -// -// Original work (webgl-noise) Copyright (C) 2011 Ashima Arts. -// Translation and modification was made by Keijiro Takahashi. -// -// This shader is based on the webgl-noise GLSL shader. For further details -// of the original shader, please see the following description from the -// original source code. -// - -// -// Description : Array and textureless GLSL 2D/3D/4D simplex -// noise functions. -// Author : Ian McEwan, Ashima Arts. -// Maintainer : ijm -// Lastmod : 20110822 (ijm) -// License : Copyright (C) 2011 Ashima Arts. All rights reserved. -// Distributed under the MIT License. See LICENSE file. -// https://github.com/ashima/webgl-noise -// - -float3 mod289(float3 x) -{ - return x - floor(x / 289.0) * 289.0; -} - -float4 mod289(float4 x) -{ - return x - floor(x / 289.0) * 289.0; -} - -float4 permute(float4 x) -{ - return mod289((x * 34.0 + 1.0) * x); -} - -float4 taylorInvSqrt(float4 r) -{ - return 1.79284291400159 - r * 0.85373472095314; -} - -float snoise(float3 v) -{ - const float2 C = float2(1.0 / 6.0, 1.0 / 3.0); - - // First corner - float3 i = floor(v + dot(v, C.yyy)); - float3 x0 = v - i + dot(i, C.xxx); - - // Other corners - float3 g = step(x0.yzx, x0.xyz); - float3 l = 1.0 - g; - float3 i1 = min(g.xyz, l.zxy); - float3 i2 = max(g.xyz, l.zxy); - - // x1 = x0 - i1 + 1.0 * C.xxx; - // x2 = x0 - i2 + 2.0 * C.xxx; - // x3 = x0 - 1.0 + 3.0 * C.xxx; - float3 x1 = x0 - i1 + C.xxx; - float3 x2 = x0 - i2 + C.yyy; - float3 x3 = x0 - 0.5; - - // Permutations - i = mod289(i); // Avoid truncation effects in permutation - float4 p = - permute(permute(permute(i.z + float4(0.0, i1.z, i2.z, 1.0)) - + i.y + float4(0.0, i1.y, i2.y, 1.0)) - + i.x + float4(0.0, i1.x, i2.x, 1.0)); - - // Gradients: 7x7 points over a square, mapped onto an octahedron. - // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) - float4 j = p - 49.0 * floor(p / 49.0); // mod(p,7*7) - - float4 x_ = floor(j / 7.0); - float4 y_ = floor(j - 7.0 * x_); // mod(j,N) - - float4 x = (x_ * 2.0 + 0.5) / 7.0 - 1.0; - float4 y = (y_ * 2.0 + 0.5) / 7.0 - 1.0; - - float4 h = 1.0 - abs(x) - abs(y); - - float4 b0 = float4(x.xy, y.xy); - float4 b1 = float4(x.zw, y.zw); - - //float4 s0 = float4(lessThan(b0, 0.0)) * 2.0 - 1.0; - //float4 s1 = float4(lessThan(b1, 0.0)) * 2.0 - 1.0; - float4 s0 = floor(b0) * 2.0 + 1.0; - float4 s1 = floor(b1) * 2.0 + 1.0; - float4 sh = -step(h, 0.0); - - float4 a0 = b0.xzyw + s0.xzyw * sh.xxyy; - float4 a1 = b1.xzyw + s1.xzyw * sh.zzww; - - float3 g0 = float3(a0.xy, h.x); - float3 g1 = float3(a0.zw, h.y); - float3 g2 = float3(a1.xy, h.z); - float3 g3 = float3(a1.zw, h.w); - - // Normalise gradients - float4 norm = taylorInvSqrt(float4(dot(g0, g0), dot(g1, g1), dot(g2, g2), dot(g3, g3))); - g0 *= norm.x; - g1 *= norm.y; - g2 *= norm.z; - g3 *= norm.w; - - // Mix final noise value - float4 m = max(0.6 - float4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0); - m = m * m; - m = m * m; - - float4 px = float4(dot(x0, g0), dot(x1, g1), dot(x2, g2), dot(x3, g3)); - return 42.0 * dot(m, px); -} diff --git a/Assets/Plugins/GPU Noise/SimplexNoise3D.cginc.meta b/Assets/Plugins/GPU Noise/SimplexNoise3D.cginc.meta deleted file mode 100644 index 612bd9ef0..000000000 --- a/Assets/Plugins/GPU Noise/SimplexNoise3D.cginc.meta +++ /dev/null @@ -1,9 +0,0 @@ -fileFormatVersion: 2 -guid: b577a94f58a1398409bfcfe9cd5dbf99 -timeCreated: 1448788105 -licenseType: Pro -ShaderImporter: - defaultTextures: [] - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Plugins/GPU Noise/SimplexNoiseGrad3D.cginc b/Assets/Plugins/GPU Noise/SimplexNoiseGrad3D.cginc deleted file mode 100644 index 4cfef835a..000000000 --- a/Assets/Plugins/GPU Noise/SimplexNoiseGrad3D.cginc +++ /dev/null @@ -1,118 +0,0 @@ -// -// Noise Shader Library for Unity - https://github.com/keijiro/NoiseShader -// -// Original work (webgl-noise) Copyright (C) 2011 Ashima Arts. -// Translation and modification was made by Keijiro Takahashi. -// -// This shader is based on the webgl-noise GLSL shader. For further details -// of the original shader, please see the following description from the -// original source code. -// - -// -// Description : Array and textureless GLSL 2D/3D/4D simplex -// noise functions. -// Author : Ian McEwan, Ashima Arts. -// Maintainer : ijm -// Lastmod : 20110822 (ijm) -// License : Copyright (C) 2011 Ashima Arts. All rights reserved. -// Distributed under the MIT License. See LICENSE file. -// https://github.com/ashima/webgl-noise -// - -float3 mod289(float3 x) -{ - return x - floor(x / 289.0) * 289.0; -} - -float4 mod289(float4 x) -{ - return x - floor(x / 289.0) * 289.0; -} - -float4 permute(float4 x) -{ - return mod289((x * 34.0 + 1.0) * x); -} - -float4 taylorInvSqrt(float4 r) -{ - return 1.79284291400159 - r * 0.85373472095314; -} - -float3 snoise_grad(float3 v) -{ - const float2 C = float2(1.0 / 6.0, 1.0 / 3.0); - - // First corner - float3 i = floor(v + dot(v, C.yyy)); - float3 x0 = v - i + dot(i, C.xxx); - - // Other corners - float3 g = step(x0.yzx, x0.xyz); - float3 l = 1.0 - g; - float3 i1 = min(g.xyz, l.zxy); - float3 i2 = max(g.xyz, l.zxy); - - // x1 = x0 - i1 + 1.0 * C.xxx; - // x2 = x0 - i2 + 2.0 * C.xxx; - // x3 = x0 - 1.0 + 3.0 * C.xxx; - float3 x1 = x0 - i1 + C.xxx; - float3 x2 = x0 - i2 + C.yyy; - float3 x3 = x0 - 0.5; - - // Permutations - i = mod289(i); // Avoid truncation effects in permutation - float4 p = - permute(permute(permute(i.z + float4(0.0, i1.z, i2.z, 1.0)) - + i.y + float4(0.0, i1.y, i2.y, 1.0)) - + i.x + float4(0.0, i1.x, i2.x, 1.0)); - - // Gradients: 7x7 points over a square, mapped onto an octahedron. - // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) - float4 j = p - 49.0 * floor(p / 49.0); // mod(p,7*7) - - float4 x_ = floor(j / 7.0); - float4 y_ = floor(j - 7.0 * x_); // mod(j,N) - - float4 x = (x_ * 2.0 + 0.5) / 7.0 - 1.0; - float4 y = (y_ * 2.0 + 0.5) / 7.0 - 1.0; - - float4 h = 1.0 - abs(x) - abs(y); - - float4 b0 = float4(x.xy, y.xy); - float4 b1 = float4(x.zw, y.zw); - - //float4 s0 = float4(lessThan(b0, 0.0)) * 2.0 - 1.0; - //float4 s1 = float4(lessThan(b1, 0.0)) * 2.0 - 1.0; - float4 s0 = floor(b0) * 2.0 + 1.0; - float4 s1 = floor(b1) * 2.0 + 1.0; - float4 sh = -step(h, 0.0); - - float4 a0 = b0.xzyw + s0.xzyw * sh.xxyy; - float4 a1 = b1.xzyw + s1.xzyw * sh.zzww; - - float3 g0 = float3(a0.xy, h.x); - float3 g1 = float3(a0.zw, h.y); - float3 g2 = float3(a1.xy, h.z); - float3 g3 = float3(a1.zw, h.w); - - // Normalise gradients - float4 norm = taylorInvSqrt(float4(dot(g0, g0), dot(g1, g1), dot(g2, g2), dot(g3, g3))); - g0 *= norm.x; - g1 *= norm.y; - g2 *= norm.z; - g3 *= norm.w; - - // Compute gradient of noise function at P - float4 m = max(0.6 - float4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0); - float4 m2 = m * m; - float4 m3 = m2 * m; - float4 m4 = m2 * m2; - float3 grad = - -6.0 * m3.x * x0 * dot(x0, g0) + m4.x * g0 + - -6.0 * m3.y * x1 * dot(x1, g1) + m4.y * g1 + - -6.0 * m3.z * x2 * dot(x2, g2) + m4.z * g2 + - -6.0 * m3.w * x3 * dot(x3, g3) + m4.w * g3; - return 42.0 * grad; -} diff --git a/Assets/Plugins/GPU Noise/SimplexNoiseGrad3D.cginc.meta b/Assets/Plugins/GPU Noise/SimplexNoiseGrad3D.cginc.meta deleted file mode 100644 index 250b646fa..000000000 --- a/Assets/Plugins/GPU Noise/SimplexNoiseGrad3D.cginc.meta +++ /dev/null @@ -1,9 +0,0 @@ -fileFormatVersion: 2 -guid: a7111eafc2463224faacf7be3bddb9a4 -timeCreated: 1449292284 -licenseType: Pro -ShaderImporter: - defaultTextures: [] - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Resources/Settings.asset b/Assets/Resources/Settings.asset index f4c21ce13..46bee7272 100644 --- a/Assets/Resources/Settings.asset +++ b/Assets/Resources/Settings.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:95d1ba64331895cc66862010f94a8cd4598ec9dd211a83b14b6091ac948afd56 -size 12327 +oid sha256:7f09c54dfdb87dffd76fa92365d62c53ce333457b89ccf8ead09093ffffedc42 +size 12525 diff --git a/Assets/Scripts/Editor/BeltFloaterLayerVerify.cs b/Assets/Scripts/Editor/BeltFloaterLayerVerify.cs index 43153b267..500ae1cea 100644 --- a/Assets/Scripts/Editor/BeltFloaterLayerVerify.cs +++ b/Assets/Scripts/Editor/BeltFloaterLayerVerify.cs @@ -122,7 +122,7 @@ namespace Aetheria.EditorTools var cache = AetheriaStores.Open(Path.Combine(_work, "belts.cc"), Path.Combine(_work, "belts-run.cc"), catalogWritable: true); cache.Upsert(new VerseGrammar { Revision = 1 }); cache.FlushAsync().Wait(); - var items = new ItemManager(cache, new ProvenanceLedger(), settings, _ => { }); + var items = new ItemManager(1u, cache, new ProvenanceLedger(), settings, _ => { }); CultRecordKey Orbit(CultRecordKey parent, float distance, float phase, float2 fixedPosition = default) { diff --git a/Assets/Scripts/Editor/CapabilityPresentationSmoke.cs b/Assets/Scripts/Editor/CapabilityPresentationSmoke.cs index fd8f1cffd..135163e24 100644 --- a/Assets/Scripts/Editor/CapabilityPresentationSmoke.cs +++ b/Assets/Scripts/Editor/CapabilityPresentationSmoke.cs @@ -62,7 +62,7 @@ public static class CapabilityPresentationSmoke cache.FlushAsync().Wait(); var ledger = new ProvenanceLedger(); for (var i = 1; i <= 8; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 }; - var items = new ItemManager(cache, ledger, settings, _ => { }); + var items = new ItemManager(1u, cache, ledger, settings, _ => { }); var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Smoke", Owner = null }, null); var hullRef = items.ItemData.RefOf(items.ItemData.GetByName("Smoke Hull")); var ship = new Ship(items, zone, new EquippableItem { Data = hullRef, Durability = 1000, Lot = 1 }, new EntitySettings()); diff --git a/Assets/Scripts/Editor/DrawnPoseWiringVerify.cs b/Assets/Scripts/Editor/DrawnPoseWiringVerify.cs index c1994ebeb..69109a622 100644 --- a/Assets/Scripts/Editor/DrawnPoseWiringVerify.cs +++ b/Assets/Scripts/Editor/DrawnPoseWiringVerify.cs @@ -115,7 +115,7 @@ namespace Aetheria.EditorTools cache.Upsert(new SimpleCommodityData { Name = "Wiring Ore", Shape = oneCell, MaxStack = 100 }); cache.FlushAsync().Wait(); var ledger = new ProvenanceLedger(); - var items = new ItemManager(cache, ledger, settings, _ => { }); + var items = new ItemManager(1u, cache, ledger, settings, _ => { }); // A sun and a planet on a moving rail round it, both near the bodies below: the field under a floater changes with the // drawn time, so a presenter that samples it at the step's time, drops it, or reads lead 0 for the height draws a different y. @@ -159,7 +159,7 @@ namespace Aetheria.EditorTools MaxRange = 1e6f, FireRange = 1e6f, CommitTime = 1e5f, ArrivalTime = 2e5f }; var projectile = new GameObject("Wiring Round").AddComponent(); - projectile.Launch(zone, shot, new Vector3(0, 0, 0)); + projectile.Launch(null, zone, shot, new Vector3(0, 0, 0)); yield return null; zone.Update(.5f); diff --git a/Assets/Scripts/Editor/FloatingItemsDrawnSmoke.cs b/Assets/Scripts/Editor/FloatingItemsDrawnSmoke.cs index 1caf77b6c..742e8aa9d 100644 --- a/Assets/Scripts/Editor/FloatingItemsDrawnSmoke.cs +++ b/Assets/Scripts/Editor/FloatingItemsDrawnSmoke.cs @@ -124,7 +124,7 @@ public static class FloatingItemsDrawnSmoke cache.FlushAsync().Wait(); var ledger = new ProvenanceLedger(); for (var i = 1; i <= 8; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 }; - var items = new ItemManager(cache, ledger, settings, _ => { }); + var items = new ItemManager(1u, cache, ledger, settings, _ => { }); var zone = new Zone(items, new PlanetSettings(), new ZonePack(), null, null); EquippableItem Make(string name, int lot) => new EquippableItem { diff --git a/Assets/Scripts/Editor/FlowFieldParityVerify.cs b/Assets/Scripts/Editor/FlowFieldParityVerify.cs index 90979f7e8..2fa3e5fda 100644 --- a/Assets/Scripts/Editor/FlowFieldParityVerify.cs +++ b/Assets/Scripts/Editor/FlowFieldParityVerify.cs @@ -106,7 +106,7 @@ namespace Aetheria.EditorTools var cache = AetheriaStores.Open(Path.Combine(work, Guid.NewGuid().ToString("N") + ".cc"), catalogWritable: true); cache.Upsert(new VerseGrammar { Revision = 1 }); cache.FlushAsync().Wait(); - var items = new ItemManager(cache, new ProvenanceLedger(), settings.GameplaySettings, _ => { }); + var items = new ItemManager(1u, cache, new ProvenanceLedger(), settings.GameplaySettings, _ => { }); return new Zone(items, new PlanetSettings { Flow = flow }, new ZonePack { Time = time }, new GalaxyZone { Name = "Parity", Owner = null }, null); } diff --git a/Assets/Scripts/Editor/GravityFieldParityVerify.cs b/Assets/Scripts/Editor/GravityFieldParityVerify.cs index 766a58a6f..d996d3227 100644 --- a/Assets/Scripts/Editor/GravityFieldParityVerify.cs +++ b/Assets/Scripts/Editor/GravityFieldParityVerify.cs @@ -154,7 +154,7 @@ namespace Aetheria.EditorTools var cache = AetheriaStores.Open(Path.Combine(work, "catalog.cc"), Path.Combine(work, "run.cc"), catalogWritable: true); cache.Upsert(new VerseGrammar { Revision = 1 }); cache.FlushAsync().Wait(); - var items = new ItemManager(cache, new ProvenanceLedger(), settings.GameplaySettings, _ => { }); + var items = new ItemManager(1u, cache, new ProvenanceLedger(), settings.GameplaySettings, _ => { }); var positions = new List { new Vector2(-130f, 85f), new Vector2(610f, -240f), new Vector2(670f, -270f), new Vector2(1700f, 300f) }; var masses = new List { 14152f, 273.9f, 29.2f, 0f }; var parents = new List { -1, 0, 1, -1 }; diff --git a/Assets/Scripts/Editor/MapBeltPerf.cs b/Assets/Scripts/Editor/MapBeltPerf.cs index edc771a50..ea3eac5c0 100644 --- a/Assets/Scripts/Editor/MapBeltPerf.cs +++ b/Assets/Scripts/Editor/MapBeltPerf.cs @@ -52,7 +52,7 @@ public static class MapBeltPerf Planets = { new CultRecordRef(sunKey), new CultRecordRef(beltKey) }, Radius = 5000f, Mass = 10000f, Time = 0 }; - var items = new ItemManager(cache, new ProvenanceLedger(), settings.GameplaySettings, _ => { }); + var items = new ItemManager(1u, cache, new ProvenanceLedger(), settings.GameplaySettings, _ => { }); var zone = new Zone(items, new PlanetSettings { BodyRadius = new ExponentialCurve(), WellStrength = new ExponentialCurve { Multiplier = 1f, Exponent = 1f }, diff --git a/Assets/Scripts/Editor/MapPresenterSmoke.cs b/Assets/Scripts/Editor/MapPresenterSmoke.cs index 88309c40e..cccc4883c 100644 --- a/Assets/Scripts/Editor/MapPresenterSmoke.cs +++ b/Assets/Scripts/Editor/MapPresenterSmoke.cs @@ -115,7 +115,7 @@ public static class MapPresenterSmoke }; var ledger = new ProvenanceLedger(); for (var i = 1; i <= 8; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 }; - var items = new ItemManager(cache, ledger, settings.GameplaySettings, _ => { }); + var items = new ItemManager(1u, cache, ledger, settings.GameplaySettings, _ => { }); var zone = new Zone(items, planetSettings, pack, new GalaxyZone { Name = "Smoke", Owner = null }, null); var hullRef = items.ItemData.RefOf(items.ItemData.GetByName("Smoke Hull")); var ship = new Ship(items, zone, new EquippableItem { Data = hullRef, Durability = 1000, Lot = 1 }, new EntitySettings()); diff --git a/Assets/Scripts/Editor/ScenarioPickerCapture.cs b/Assets/Scripts/Editor/ScenarioPickerCapture.cs new file mode 100644 index 000000000..b39dd7a1b --- /dev/null +++ b/Assets/Scripts/Editor/ScenarioPickerCapture.cs @@ -0,0 +1,108 @@ +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 new file mode 100644 index 000000000..ebeb742e8 --- /dev/null +++ b/Assets/Scripts/Editor/ScenarioPickerCapture.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2e79f280179150e4080250c45e07cd69 \ No newline at end of file diff --git a/Assets/Scripts/Editor/ScenarioPickerVerify.cs b/Assets/Scripts/Editor/ScenarioPickerVerify.cs new file mode 100644 index 000000000..6816cafc9 --- /dev/null +++ b/Assets/Scripts/Editor/ScenarioPickerVerify.cs @@ -0,0 +1,265 @@ +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