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/Resources/Settings.asset b/Assets/Resources/Settings.asset index 46bee7272..78735dab3 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:7f09c54dfdb87dffd76fa92365d62c53ce333457b89ccf8ead09093ffffedc42 -size 12525 +oid sha256:7a0a0d2e7b8367e7c402975e5683434068f99b8c13c848764bbac39b43886411 +size 12477 diff --git a/Assets/Scripts/Editor/BeltFloaterLayerVerify.cs b/Assets/Scripts/Editor/BeltFloaterLayerVerify.cs index 500ae1cea..43153b267 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(1u, cache, new ProvenanceLedger(), settings, _ => { }); + var items = new ItemManager(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 135163e24..fd8f1cffd 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(1u, cache, ledger, settings, _ => { }); + var items = new ItemManager(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 69109a622..c1994ebeb 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(1u, cache, ledger, settings, _ => { }); + var items = new ItemManager(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(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/FloatingItemsDrawnSmoke.cs b/Assets/Scripts/Editor/FloatingItemsDrawnSmoke.cs index 742e8aa9d..1caf77b6c 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(1u, cache, ledger, settings, _ => { }); + var items = new ItemManager(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 2fa3e5fda..90979f7e8 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(1u, cache, new ProvenanceLedger(), settings.GameplaySettings, _ => { }); + var items = new ItemManager(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 d996d3227..766a58a6f 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(1u, cache, new ProvenanceLedger(), settings.GameplaySettings, _ => { }); + var items = new ItemManager(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 ea3eac5c0..edc771a50 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(1u, cache, new ProvenanceLedger(), settings.GameplaySettings, _ => { }); + var items = new ItemManager(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 cccc4883c..88309c40e 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(1u, cache, ledger, settings.GameplaySettings, _ => { }); + var items = new ItemManager(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 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