From 6d631bd9d568efffd8becbfff8f3099c14912f22 Mon Sep 17 00:00:00 2001 From: Aflaungos Date: Sun, 12 Oct 2025 12:51:54 -0300 Subject: [PATCH 01/12] Treewide: Make the UI Scalable (WIP) --- .../Architect/Content/Groups/ConfigGroup.cs | 24 +- .../Storage/WorldEditorGlobalSettings.cs | 2 + .../Architect/UI/ConfigurationScreen.cs | 156 +++--- WorldEditor/Architect/UI/EditorUIManager.cs | 444 ++++++++++++++---- 4 files changed, 446 insertions(+), 180 deletions(-) diff --git a/WorldEditor/Architect/Content/Groups/ConfigGroup.cs b/WorldEditor/Architect/Content/Groups/ConfigGroup.cs index 5acebdb..c82d418 100644 --- a/WorldEditor/Architect/Content/Groups/ConfigGroup.cs +++ b/WorldEditor/Architect/Content/Groups/ConfigGroup.cs @@ -437,19 +437,19 @@ public static void Initialize() GeoChest = new ConfigGroup(Generic, Attributes.ConfigManager.RegisterConfigType( - new IntConfigType("Large Geo", + new IntConfigType("Large Geo", (o, value) => { o.LocateMyFSM("Chest Control").FsmVariables.FindFsmInt("Geo Large").Value = value.GetValue(); }), "chest_large_geo"), Attributes.ConfigManager.RegisterConfigType( - new IntConfigType("Medium Geo", + new IntConfigType("Medium Geo", (o, value) => { o.LocateMyFSM("Chest Control").FsmVariables.FindFsmInt("Geo Med").Value = value.GetValue(); }), "chest_med_geo"), Attributes.ConfigManager.RegisterConfigType( - new IntConfigType("Small Geo", + new IntConfigType("Small Geo", (o, value) => { o.LocateMyFSM("Chest Control").FsmVariables.FindFsmInt("Geo Small").Value = value.GetValue(); @@ -520,19 +520,19 @@ public static void Initialize() KillableEnemies = new ConfigGroup(Enemies, Attributes.ConfigManager.RegisterConfigType( - new IntConfigType("Health", + new IntConfigType("Health", (o, value) => { o.GetComponent().hp = Mathf.Abs(value.GetValue()); }), "enemy_health"), Attributes.ConfigManager.RegisterConfigType( - new IntConfigType("Large Geo Drops", + new IntConfigType("Large Geo Drops", (o, value) => { o.GetComponent().SetGeoLarge(Mathf.Abs(value.GetValue())); }), "enemy_large_geo"), Attributes.ConfigManager.RegisterConfigType( - new IntConfigType("Medium Geo Drops", + new IntConfigType("Medium Geo Drops", (o, value) => { o.GetComponent().SetGeoMedium(Mathf.Abs(value.GetValue())); }), "enemy_med_geo"), Attributes.ConfigManager.RegisterConfigType( - new IntConfigType("Small Geo Drops", + new IntConfigType("Small Geo Drops", (o, value) => { o.GetComponent().SetGeoSmall(Mathf.Abs(value.GetValue())); }), "enemy_small_geo"), Attributes.ConfigManager.RegisterConfigType(MakePersistenceConfigType("Stay Dead", o => @@ -667,7 +667,7 @@ public static void Initialize() Cocoon = new ConfigGroup(Breakable, Attributes.ConfigManager.RegisterConfigType( - new IntConfigType("Lifeseed Count", + new IntConfigType("Lifeseed Count", (o, value) => { o.GetComponent()?.SetScuttlerAmount(Mathf.Abs(value.GetValue())); }), "lifeseed_count") ); @@ -740,7 +740,7 @@ public static void Initialize() ); TollBench = new ConfigGroup(Levers, - Attributes.ConfigManager.RegisterConfigType(new IntConfigType("Cost", (o, value) => + Attributes.ConfigManager.RegisterConfigType(new IntConfigType("Cost", (o, value) => { foreach (var fsm in o.GetComponents()) { @@ -778,7 +778,7 @@ public static void Initialize() }), "disable_collision"); Thorns = new ConfigGroup(Colours, Attributes.ConfigManager.RegisterConfigType( - new IntConfigType("Damage Amount", + new IntConfigType("Damage Amount", (o, value) => { o.GetOrAddComponent().damageAmount = value.GetValue(); }), "thorns_damage"), disableCollision @@ -1263,7 +1263,7 @@ public static void Initialize() new FloatConfigType("Repeat Delay", (o, value) => { o.GetComponent().repeatDelay = value.GetValue(); }), "timer_delay"), Attributes.ConfigManager.RegisterConfigType( - new IntConfigType("Max Calls", (o, value) => { o.GetComponent().maxCalls = value.GetValue(); }), + new IntConfigType("Max Calls", (o, value) => { o.GetComponent().maxCalls = value.GetValue(); }), "timer_max_calls") ); @@ -1473,7 +1473,7 @@ public static void Initialize() Choice = new ConfigGroup(Invisible, tc, Attributes.ConfigManager.RegisterConfigType( - new IntConfigType("Cost", (o, value) => { o.GetComponent().cost = value.GetValue(); }) + new IntConfigType("Cost", (o, value) => { o.GetComponent().cost = value.GetValue(); }) .WithDefaultValue(0), "choice_cost") ); diff --git a/WorldEditor/Architect/Storage/WorldEditorGlobalSettings.cs b/WorldEditor/Architect/Storage/WorldEditorGlobalSettings.cs index f5b7272..d19969e 100644 --- a/WorldEditor/Architect/Storage/WorldEditorGlobalSettings.cs +++ b/WorldEditor/Architect/Storage/WorldEditorGlobalSettings.cs @@ -18,5 +18,7 @@ public class WorldEditorGlobalSettings [JsonConverter(typeof(PlayerActionSetConverter))] public WorldEditorKeyBinds Keybinds = new(); + public float UIScaleFactor { get; set; } = 1.0f; + public bool TestMode = false; } \ No newline at end of file diff --git a/WorldEditor/Architect/UI/ConfigurationScreen.cs b/WorldEditor/Architect/UI/ConfigurationScreen.cs index 82d476f..f477c6b 100644 --- a/WorldEditor/Architect/UI/ConfigurationScreen.cs +++ b/WorldEditor/Architect/UI/ConfigurationScreen.cs @@ -19,6 +19,19 @@ public static MenuScreen GetScreen(MenuScreen modListMenu, WorldEditorGlobalSett "True", "False" ]; + + // UI Scale options + string[] scaleValues = + [ + "1.0x", + "1.25x", + "1.5x", + "1.75x", + "2.0x", + ]; + + float[] scaleFactors = [1.0f, 1.25f, 1.5f, 1.75f, 2.0f]; + var elements = new List { new TextPanel("Editor"), @@ -36,82 +49,78 @@ public static MenuScreen GetScreen(MenuScreen modListMenu, WorldEditorGlobalSett i => { globalSettings.TestMode = i == 0; }, () => globalSettings.TestMode ? 0 : 1 ), + new HorizontalOption( + "UI Scale Factor", + "Adjusts the size of the editor UI. Changes apply immediately.", + scaleValues, + i => + { + globalSettings.UIScaleFactor = scaleFactors[i]; + EditorUIManager.ApplyUIScale(globalSettings.UIScaleFactor); + }, + () => + { + // Find the closest matching scale factor index + for (int i = 0; i < scaleFactors.Length; i++) + { + if (Mathf.Approximately(globalSettings.UIScaleFactor, scaleFactors[i])) + return i; + } + return 2; + } + ), new TextPanel(""), new TextPanel("Keybinds"), new KeyBind( "Toggle Editor", globalSettings.Keybinds.ToggleEditor ), - new MenuRow([ - new KeyBind( - "Undo", - globalSettings.Keybinds.Undo - ), - new KeyBind( - "Redo", - globalSettings.Keybinds.Redo - ) + new MenuRow( + [ + new KeyBind("Undo", globalSettings.Keybinds.Undo), + new KeyBind("Redo", globalSettings.Keybinds.Redo) ], - "undo_redo"), - new MenuRow([ - new KeyBind( - "Copy Selection", - globalSettings.Keybinds.Copy - ), - new KeyBind( - "Paste Selection", - globalSettings.Keybinds.Paste - ) + "undo_redo" + ), + new MenuRow( + [ + new KeyBind("Copy Selection", globalSettings.Keybinds.Copy), + new KeyBind("Paste Selection", globalSettings.Keybinds.Paste) ], - "copy_paste"), - new MenuRow([ - new KeyBind( - "Rotate", - globalSettings.Keybinds.RotateItem - ), - new KeyBind( - "Use Unsafe Rotations", - globalSettings.Keybinds.UnsafeRotation - ) + "copy_paste" + ), + new MenuRow( + [ + new KeyBind("Rotate", globalSettings.Keybinds.RotateItem), + new KeyBind("Use Unsafe Rotations", globalSettings.Keybinds.UnsafeRotation) ], - "rotations"), - new MenuRow([ - new KeyBind( - "Decrease Scale", - globalSettings.Keybinds.DecreaseScale - ), - new KeyBind( - "Increase Scale", - globalSettings.Keybinds.IncreaseScale - ) + "rotations" + ), + new MenuRow( + [ + new KeyBind("Decrease Scale", globalSettings.Keybinds.DecreaseScale), + new KeyBind("Increase Scale", globalSettings.Keybinds.IncreaseScale) ], - "scale"), - new MenuRow([ - new KeyBind( - "Flip", - globalSettings.Keybinds.FlipItem - ), - new KeyBind( - "Lock Axis", - globalSettings.Keybinds.LockAxis - ) + "scale" + ), + new MenuRow( + [ + new KeyBind("Flip", globalSettings.Keybinds.FlipItem), + new KeyBind("Lock Axis", globalSettings.Keybinds.LockAxis) ], - "flip_lock"), - new MenuRow([ - new KeyBind( - "Moving Object Preview", - globalSettings.Keybinds.TogglePreview - ), - new KeyBind( - "Add Prefab", - globalSettings.Keybinds.AddPrefab - ) + "flip_lock" + ), + new MenuRow( + [ + new KeyBind("Moving Object Preview", globalSettings.Keybinds.TogglePreview), + new KeyBind("Add Prefab", globalSettings.Keybinds.AddPrefab) ], - "preview_prefab"), - + "preview_prefab" + ), new TextPanel(""), new TextPanel("Management"), - new MenuButton("Delete All Edits", + new MenuButton( + "Delete All Edits", "", _ => { @@ -120,20 +129,22 @@ public static MenuScreen GetScreen(MenuScreen modListMenu, WorldEditorGlobalSett ResetObject.ResetRoom(GameManager.instance.sceneName); PlacementManager.InvalidateCache(); } - SceneSaveLoader.WipeAllScenes(); - }), - + } + ), new TextPanel(""), new TextPanel("Content Pack Toggles"), new TextPanel( - "Disable packs you don't need to reduce startup time and memory usage, changes are applied when game is rebooted.\n\nThings will break if you disable a pack that is in use!") + "Disable packs you don't need to reduce startup time and memory usage, changes are applied when game is rebooted.\n\nThings will break if you disable a pack that is in use!" + ) { FontSize = 20, Anchor = TextAnchor.UpperCenter } }; + if (Architect.UsingMultiplayer) + { elements.Insert(3, new HorizontalOption( "Collaboration Mode", "Shares edits across of an HKMP server as soon as they're made, to allow working together online", @@ -141,9 +152,16 @@ public static MenuScreen GetScreen(MenuScreen modListMenu, WorldEditorGlobalSett i => { globalSettings.CollaborationMode = i == 0; }, () => globalSettings.CollaborationMode ? 0 : 1 )); - elements.AddRange(ContentPacks.Packs.Select(pack => new HorizontalOption(pack.GetName(), pack.GetDescription(), - values, i => { globalSettings.ContentPackSettings[pack.GetName()] = i == 0; }, - () => globalSettings.ContentPackSettings[pack.GetName()] ? 0 : 1))); + } + + elements.AddRange(ContentPacks.Packs.Select(pack => new HorizontalOption( + pack.GetName(), + pack.GetDescription(), + values, + i => { globalSettings.ContentPackSettings[pack.GetName()] = i == 0; }, + () => globalSettings.ContentPackSettings[pack.GetName()] ? 0 : 1 + ))); + _menuRef ??= new Menu( Architect.Instance.Name, elements.ToArray() diff --git a/WorldEditor/Architect/UI/EditorUIManager.cs b/WorldEditor/Architect/UI/EditorUIManager.cs index 0f45af1..c7dfe05 100644 --- a/WorldEditor/Architect/UI/EditorUIManager.cs +++ b/WorldEditor/Architect/UI/EditorUIManager.cs @@ -10,6 +10,7 @@ using Architect.Content; using Architect.MultiplayerHook; using Architect.Objects; +using Architect.Storage; using Architect.Util; using JetBrains.Annotations; using MagicUI.Core; @@ -32,6 +33,13 @@ public static class EditorUIManager private const string Nothing = " "; + // UI Scaling + private static float _uiScaleFactor = 1.0f; + public static float UI_SCALE_FACTOR => _uiScaleFactor; + private const int BASE_BUTTON_SIZE = 80; + private const int BASE_IMAGE_SIZE = 40; + private const int BASE_PADDING = 15; + // The current page, item index and filter info private static int _groupIndex; private static int _index; @@ -42,6 +50,7 @@ public static class EditorUIManager private static List _categories; // Info about current selected item and buttons to change item + private static TextObject _editorEnabledText; private static TextObject _selectionInfo; private static TextObject _sceneInfo; private static TextObject _extraInfo; @@ -226,44 +235,60 @@ public static void RefreshSelectedItem(bool useDefaultConfigValues) // Initializes the UI internal static void Initialize(LayoutRoot layout) { - _layout = layout; + try + { + _layout = layout; + + _selectionButtons = []; + PauseOptions = []; - _selectionButtons = []; - PauseOptions = []; + layout.VisibilityCondition = () => EditorManager.IsEditing; - layout.VisibilityCondition = () => EditorManager.IsEditing; + SetupTextDisplay(layout); + SetupLeftSide(layout); + SetupObjectOptions(layout); + SetupFilter(layout); + SetupTools(layout); - SetupTextDisplay(layout); - SetupLeftSide(layout); - SetupObjectOptions(layout); - SetupFilter(layout); - SetupTools(layout); + On.HeroController.SceneInit += (orig, self) => + { + orig(self); + _sceneInfo.Text = "Scene: " + GameManager.instance.sceneName; + }; + } - On.HeroController.SceneInit += (orig, self) => + catch (Exception e) { - orig(self); - _sceneInfo.Text = "Scene: " + GameManager.instance.sceneName; - }; + Debug.LogError($"Failed to initialize Editor UI: {e}"); + throw; // Re-throw to see the actual error + } } private static void SetupLeftSide(LayoutRoot layout) { + var basePadding = 20; + var baseRow1Height = 120; + var baseRow2Height = 280; + var baseRow3Height = 25; + var baseColumnWidth = 150; + var categoriesVerticalOffset = 40; + _leftSideGrid = new GridLayout(layout, "Left Side") { - Padding = new Padding(20, 15), + Padding = new Padding((int)(basePadding * _uiScaleFactor), (int)((basePadding + categoriesVerticalOffset) * _uiScaleFactor)), VerticalAlignment = VerticalAlignment.Bottom, HorizontalAlignment = HorizontalAlignment.Left, RowDefinitions = { - new GridDimension(120, GridUnit.AbsoluteMin), - new GridDimension(320, GridUnit.AbsoluteMin), - new GridDimension(24, GridUnit.AbsoluteMin) + new GridDimension((int)(baseRow1Height * _uiScaleFactor), GridUnit.AbsoluteMin), + new GridDimension((int)(baseRow2Height * _uiScaleFactor), GridUnit.AbsoluteMin), + new GridDimension((int)(baseRow3Height * _uiScaleFactor), GridUnit.AbsoluteMin) }, ColumnDefinitions = { - new GridDimension(160, GridUnit.AbsoluteMin), - new GridDimension(160, GridUnit.AbsoluteMin), - new GridDimension(160, GridUnit.AbsoluteMin) + new GridDimension((int)(baseColumnWidth * _uiScaleFactor), GridUnit.AbsoluteMin), + new GridDimension((int)(baseColumnWidth * _uiScaleFactor), GridUnit.AbsoluteMin), + new GridDimension((int)(baseColumnWidth * _uiScaleFactor), GridUnit.AbsoluteMin) } }; @@ -281,13 +306,21 @@ private static void SetupLeftSide(LayoutRoot layout) private static GridLayout SetupExtraControls(LayoutRoot layout) { + var baseInputWidth = 80; + var basePadding = 20; + var baseFontSize = 12; + + RotationChoice?.Destroy(); + ScaleChoice?.Destroy(); + RotationChoice = new TextInput(layout, "Rotation Input") { ContentType = InputField.ContentType.DecimalNumber, HorizontalAlignment = HorizontalAlignment.Right, - MinWidth = 80, + MinWidth = (int)(baseInputWidth * _uiScaleFactor), Text = "0", - Padding = new Padding(20, 10) + Padding = new Padding((int)(basePadding * _uiScaleFactor), (int)(10 * _uiScaleFactor)), + FontSize = (int)(baseFontSize * _uiScaleFactor) }.WithProp(GridLayout.Column, 1); RotationChoice.TextEditFinished += (_, s) => { @@ -299,9 +332,10 @@ private static GridLayout SetupExtraControls(LayoutRoot layout) { ContentType = InputField.ContentType.DecimalNumber, HorizontalAlignment = HorizontalAlignment.Right, - MinWidth = 80, + MinWidth = (int)(baseInputWidth * _uiScaleFactor), Text = "1", - Padding = new Padding(20, 10) + Padding = new Padding((int)(basePadding * _uiScaleFactor), (int)(10 * _uiScaleFactor)), + FontSize = (int)(baseFontSize * _uiScaleFactor) }.WithProp(GridLayout.Column, 1).WithProp(GridLayout.Row, 1); ScaleChoice.TextEditFinished += (_, s) => { @@ -313,8 +347,8 @@ private static GridLayout SetupExtraControls(LayoutRoot layout) { RowDefinitions = { - new GridDimension(1, GridUnit.Proportional), - new GridDimension(1, GridUnit.Proportional) + new GridDimension((int)(40 * _uiScaleFactor), GridUnit.AbsoluteMin), + new GridDimension((int)(40 * _uiScaleFactor), GridUnit.AbsoluteMin) }, ColumnDefinitions = { @@ -327,13 +361,17 @@ private static GridLayout SetupExtraControls(LayoutRoot layout) { Text = "Rotation", HorizontalAlignment = HorizontalAlignment.Left, - Padding = new Padding(0, 10) + VerticalAlignment = VerticalAlignment.Center, + Padding = new Padding(0, 10), + FontSize = (int)(baseFontSize * _uiScaleFactor) }, new TextObject(layout, "Scale Text") { Text = "Scale", HorizontalAlignment = HorizontalAlignment.Left, - Padding = new Padding(0, 10) + VerticalAlignment = VerticalAlignment.Center, + Padding = new Padding(0, 10), + FontSize = (int)(baseFontSize * _uiScaleFactor) }.WithProp(GridLayout.Row, 1), RotationChoice, ScaleChoice @@ -346,12 +384,23 @@ private static GridLayout SetupExtraControls(LayoutRoot layout) private static void SetupConfigArea(LayoutRoot layout) { + _configButton?.Destroy(); + _broadcasterButton?.Destroy(); + _receiverButton?.Destroy(); + + var baseButtonWidth = 140; + var baseFontSize = 12; + var configButtonsTopPadding = 10; + _configButton = new Button(layout, "Config Choice") { Content = "Config", HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Bottom, - MinWidth = 160, + MinWidth = (int)(baseButtonWidth * _uiScaleFactor), + MinHeight = (int)(25 * _uiScaleFactor), + FontSize = (int)(baseFontSize * _uiScaleFactor), + Padding = new Padding(0, (int)(configButtonsTopPadding * _uiScaleFactor)), Enabled = false }.WithProp(GridLayout.Row, 2); _configButton.Click += _ => @@ -365,8 +414,10 @@ private static void SetupConfigArea(LayoutRoot layout) Content = "Events", HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Bottom, - MinWidth = 160, - Padding = new Padding(20, 0), + MinWidth = (int)(baseButtonWidth * _uiScaleFactor), + MinHeight = (int)(25 * _uiScaleFactor), + FontSize = (int)(baseFontSize * _uiScaleFactor), + Padding = new Padding((int)(20 * _uiScaleFactor), (int)(configButtonsTopPadding * _uiScaleFactor)), Enabled = false }.WithProp(GridLayout.Column, 1).WithProp(GridLayout.Row, 2); _broadcasterButton.Click += _ => @@ -380,7 +431,10 @@ private static void SetupConfigArea(LayoutRoot layout) Content = "Listeners", HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Bottom, - MinWidth = 160, + MinWidth = (int)(baseButtonWidth * _uiScaleFactor), + MinHeight = (int)(25 * _uiScaleFactor), + FontSize = (int)(baseFontSize * _uiScaleFactor), + Padding = new Padding(0, (int)(configButtonsTopPadding * _uiScaleFactor)), Enabled = false }.WithProp(GridLayout.Column, 2).WithProp(GridLayout.Row, 2); _receiverButton.Click += _ => @@ -396,52 +450,67 @@ private static void SetupConfigArea(LayoutRoot layout) private static void SetupTextDisplay(LayoutRoot layout) { - _ = new TextObject(layout) + var baseFontSize = 12; + + _editorEnabledText?.Destroy(); + _selectionInfo?.Destroy(); + _sceneInfo?.Destroy(); + _extraInfo?.Destroy(); + _bigText?.Destroy(); + + var basePadding = 80; + var paddingIncrement = 35; + + _editorEnabledText = new TextObject(layout) { HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Top, - MaxWidth = 200, - MaxHeight = 20, - Padding = new Padding(0, 60), - Text = "Editor Enabled" + MaxWidth = (int)(250 * _uiScaleFactor), + MaxHeight = (int)(25 * _uiScaleFactor), + Padding = new Padding(0, (int)(basePadding * _uiScaleFactor)), + Text = "Editor Enabled", + FontSize = (int)(baseFontSize * _uiScaleFactor) }; _selectionInfo = new TextObject(layout) { HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Top, - Padding = new Padding(0, 90), - MaxWidth = 1000, - MaxHeight = 20, - Text = "Current Item: None" + Padding = new Padding(0, (int)((basePadding + paddingIncrement) * _uiScaleFactor)), + MaxWidth = (int)(1200 * _uiScaleFactor), + MaxHeight = (int)(20 * _uiScaleFactor), + Text = "Current Item: None", + FontSize = (int)(baseFontSize * _uiScaleFactor) }; _sceneInfo = new TextObject(layout) { HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Top, - Padding = new Padding(0, 120), - MaxWidth = 1000, - MaxHeight = 20, - Text = "Scene: None" + Padding = new Padding(0, (int)((basePadding + paddingIncrement * 2) * _uiScaleFactor)), + MaxWidth = (int)(1200 * _uiScaleFactor), + MaxHeight = (int)(25 * _uiScaleFactor), + Text = "Scene: None", + FontSize = (int)(baseFontSize * _uiScaleFactor) }; _extraInfo = new TextObject(layout) { HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Top, - Padding = new Padding(0, 150), - MaxWidth = 1000, - MaxHeight = 20, + Padding = new Padding(0, (int)((basePadding + paddingIncrement * 3) * _uiScaleFactor)), + MaxWidth = (int)(1200 * _uiScaleFactor), + MaxHeight = (int)(25 * _uiScaleFactor), Text = "ID: None", - Visibility = Visibility.Hidden + Visibility = Visibility.Hidden, + FontSize = (int)(baseFontSize * _uiScaleFactor) }; _bigText = new TextObject(layout) { HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, - FontSize = 128, + FontSize = (int)(128 * _uiScaleFactor), Text = "" }; } @@ -462,27 +531,57 @@ public static async Task DisplayExtraInfo(string info) private static GridLayout SetupCategories(LayoutRoot layout) { + var existingCategories = _leftSideGrid?.Children?.FirstOrDefault(c => c.Name == "Categories"); + + existingCategories?.Destroy(); + _categories = []; SetCategory(FavouritesCategory.Instance); Dictionary normalCategories = new(); - foreach (var element in ContentPacks.Packs.Where(pack => pack.IsEnabled()).SelectMany(pack => pack)) + + if (ContentPacks.Packs != null) { - if (!normalCategories.ContainsKey(element.GetCategory())) + foreach (var element in ContentPacks.Packs.Where(pack => pack?.IsEnabled() == true).SelectMany(pack => pack)) { - var normalCategory = new NormalCategory(element.GetCategory()); - normalCategories.Add(element.GetCategory(), normalCategory); - _categories.Add(normalCategory); - } + if (element?.GetCategory() == null) continue; - normalCategories[element.GetCategory()].AddObject(PlaceableObject.Create(element)); + if (!normalCategories.ContainsKey(element.GetCategory())) + { + var normalCategory = new NormalCategory(element.GetCategory()); + normalCategories.Add(element.GetCategory(), normalCategory); + _categories.Add(normalCategory); + } + + var placeable = PlaceableObject.Create(element); + if (placeable != null) + { + normalCategories[element.GetCategory()].AddObject(placeable); + } + } } foreach (var category in normalCategories.Values) category.Sort(); - _categories.Add(new BlankCategory()); - _categories.Add(new PrefabsCategory()); + try + { + _categories.Add(new BlankCategory()); + } + catch (Exception e) + { + Debug.LogError($"Failed to create BlankCategory: {e}"); + } + + try + { + _categories.Add(new PrefabsCategory()); + } + catch (Exception e) + { + Debug.LogError($"Failed to create PrefabsCategory: {e}"); + } + _categories.Add(_category); var grid = new GridLayout(layout, "Categories") @@ -497,13 +596,15 @@ private static GridLayout SetupCategories(LayoutRoot layout) { columnCount -= 1; grid.RowDefinitions.Add(new GridDimension(1, GridUnit.Proportional)); - if (!category.CreateButton()) continue; + if (category == null || !category.CreateButton()) continue; var button = new Button(layout, category.GetName()) { VerticalAlignment = VerticalAlignment.Bottom, Padding = new Padding(0, 2), - Content = category.GetName() + Content = category.GetName(), + FontSize = (int)(12 * _uiScaleFactor), + MinHeight = (int)(30 * _uiScaleFactor) }.WithProp(GridLayout.Row, columnCount); button.Click += _ => { SetCategory(category); }; grid.Children.Add(button); @@ -514,22 +615,44 @@ private static GridLayout SetupCategories(LayoutRoot layout) private static void SetupObjectOptions(LayoutRoot layout) { + var baseButtonSpacingX = 100; + var baseButtonSpacingY = 100; + var baseGridOffsetX = 30; + var baseGridOffsetY = 30; + + foreach (var (button, favButton, image) in _selectionButtons) + { + button?.Destroy(); + favButton?.Destroy(); + image?.Destroy(); + } + + _selectionButtons.Clear(); + for (var i = 0; i < ItemsPerGroup; i++) { - var j = 2 - i / 3; + var row = 2 - i / 3; + var col = i % 3; + + var horizontalPos = baseGridOffsetX + col * baseButtonSpacingX * _uiScaleFactor; + var verticalPos = baseGridOffsetY + row * baseButtonSpacingY * _uiScaleFactor; var (button, image) = - CreateImagedButton(layout, Architect.BlankSprite, i.ToString(), (2 - i % 3) * 100, j * 100, i); + CreateImagedButton(layout, Architect.BlankSprite, i.ToString(), (2 - i % 3) * 100, row * 100, i); var favourite = new Button(layout, i + " Favourite") { Content = EmptyStar, HorizontalAlignment = HorizontalAlignment.Right, VerticalAlignment = VerticalAlignment.Bottom, - MinHeight = 20, - MinWidth = 20, + MinHeight = (int)(20 * _uiScaleFactor), + MinWidth = (int)(20 * _uiScaleFactor), + FontSize = (int)(12 * _uiScaleFactor), Borderless = true, - Padding = new Padding((2 - i % 3) * 100 + 92, j * 100 + 45) + Padding = new Padding( + (int)((horizontalPos + 50) * _uiScaleFactor), + (int)((verticalPos + 50) * _uiScaleFactor) + ), }; var k = i; favourite.Click += _ => { ToggleFavourite(k); }; @@ -538,7 +661,16 @@ private static void SetupObjectOptions(LayoutRoot layout) _selectionButtons.Add((button, favourite, image)); } - CreateImagedButton(layout, ResetObject.Instance.GetSprite(), "Reset", 0, 360, -5); + var resetHorizontalPos = baseGridOffsetX; + var resetVerticalPos = baseGridOffsetY + 3 * baseButtonSpacingY; + + var existingReset = PauseOptions.FirstOrDefault(x => x.Name?.Contains("Reset Button") == true); + if (existingReset == null) + { + CreateImagedButton(layout, ResetObject.Instance.GetSprite(), "Reset", + resetHorizontalPos, + resetVerticalPos, -5); + } RefreshObjects(); RefreshButtons(); @@ -546,13 +678,18 @@ private static void SetupObjectOptions(LayoutRoot layout) private static void SetupFilter(LayoutRoot layout) { + var baseFilterWidth = 200; + var baseFilterX = 100; + var baseFilterY = 350; + var filter = new TextInput(layout, "Search") { ContentType = InputField.ContentType.Standard, HorizontalAlignment = HorizontalAlignment.Right, VerticalAlignment = VerticalAlignment.Bottom, - MinWidth = 200, - Padding = new Padding(55, 315) + MinWidth = (int)(baseFilterWidth * _uiScaleFactor), + FontSize = (int)(12 * _uiScaleFactor), + Padding = new Padding((int)(baseFilterX * _uiScaleFactor), (int)(baseFilterY * _uiScaleFactor)) }; filter.TextChanged += (_, s) => { @@ -565,23 +702,111 @@ private static void SetupFilter(LayoutRoot layout) PauseOptions.Add(filter); } + public static void ApplyUIScale(float scaleFactor) + { + _uiScaleFactor = scaleFactor; + + if (_layout != null && EditorManager.IsEditing) + { + var currentSelectedItem = SelectedItem; + var currentIndex = _index; + var currentGroupIndex = _groupIndex; + var currentCategory = _category; + var currentFilter = _filter; + + // Destroy current UI elements + foreach (var element in PauseOptions.ToArray()) + { + if (element != null) + { + element.Destroy(); + } + } + + PauseOptions.Clear(); + + if (_selectionButtons != null) + { + foreach (var (button, favButton, image) in _selectionButtons) + { + button?.Destroy(); + favButton?.Destroy(); + image?.Destroy(); + } + _selectionButtons.Clear(); + } + else + { + _selectionButtons = new List<(Button, Button, Image)>(); + } + + _configGrid?.Destroy(); + _broadcastersGrid?.Destroy(); + _receiversGrid?.Destroy(); + _editorEnabledText?.Destroy(); + _selectionInfo?.Destroy(); + _sceneInfo?.Destroy(); + _extraInfo?.Destroy(); + _bigText?.Destroy(); + _leftSideGrid?.Destroy(); + _configButton?.Destroy(); + _broadcasterButton?.Destroy(); + _receiverButton?.Destroy(); + RotationChoice?.Destroy(); + ScaleChoice?.Destroy(); + + _configGrid = null; + _broadcastersGrid = null; + _receiversGrid = null; + _editorEnabledText = null; + _selectionInfo = null; + _sceneInfo = null; + _extraInfo = null; + _bigText = null; + _leftSideGrid = null; + _configButton = null; + _broadcasterButton = null; + _receiverButton = null; + RotationChoice = null; + ScaleChoice = null; + + Initialize(_layout); + + _category = currentCategory; + _groupIndex = currentGroupIndex; + _index = currentIndex; + _filter = currentFilter; + SelectedItem = currentSelectedItem; + + RefreshObjects(); + RefreshButtons(); + RefreshSelectedItem(false); + } + } + private static void SetupTools(LayoutRoot layout) { + var baseToolsPaddingX = 300; + var baseToolsPaddingY = 30; + var toolsRightPadding = 80; + var toolSpacing = 70; + var extraSettings = new GridLayout(layout, "Extra Settings") { HorizontalAlignment = HorizontalAlignment.Right, VerticalAlignment = VerticalAlignment.Bottom, - Padding = new Padding(335, 0), + Padding = new Padding((int)((baseToolsPaddingX + toolsRightPadding) * _uiScaleFactor), + (int)(baseToolsPaddingY * _uiScaleFactor)), ColumnDefinitions = { - new GridDimension(1, GridUnit.Proportional), - new GridDimension(1, GridUnit.Proportional), - new GridDimension(1, GridUnit.Proportional), - new GridDimension(1, GridUnit.Proportional) + new GridDimension((int)(toolSpacing * _uiScaleFactor), GridUnit.AbsoluteMin), + new GridDimension((int)(toolSpacing * _uiScaleFactor), GridUnit.AbsoluteMin), + new GridDimension((int)(toolSpacing * _uiScaleFactor), GridUnit.AbsoluteMin), + new GridDimension((int)(toolSpacing * _uiScaleFactor), GridUnit.AbsoluteMin) }, RowDefinitions = { - new GridDimension(1, GridUnit.Proportional) + new GridDimension((int)(toolSpacing * _uiScaleFactor), GridUnit.AbsoluteMin) } }; @@ -589,7 +814,7 @@ private static void SetupTools(LayoutRoot layout) if (Architect.UsingMultiplayer) { - extraSettings.RowDefinitions.Add(new GridDimension(0.2f, GridUnit.Proportional)); + extraSettings.RowDefinitions.Add(new GridDimension((int)(40 * _uiScaleFactor), GridUnit.AbsoluteMin)); var multiplayerRefresh = new Button(layout, "Refresh") { VerticalAlignment = VerticalAlignment.Bottom, @@ -601,29 +826,31 @@ private static void SetupTools(LayoutRoot layout) correctRow++; } - var cursorImagedButton = CreateImagedButton(layout, CursorObject.Instance.GetSprite(), "Cursor", 0, 0, -1); + var toolOffset = 0; + + var cursorImagedButton = CreateImagedButton(layout, CursorObject.Instance.GetSprite(), "Cursor", toolOffset, toolOffset, -1); cursorImagedButton.Item1.WithProp(GridLayout.Column, 0).WithProp(GridLayout.Row, correctRow); - cursorImagedButton.Item2.WithProp(GridLayout.Column, 0).WithProp(GridLayout.Row, correctRow); + cursorImagedButton.Item2.WithProp(GridLayout.Column, 0).WithProp(GridLayout.Row, correctRow); extraSettings.Children.Add(cursorImagedButton.Item1); - extraSettings.Children.Add(cursorImagedButton.Item2); + extraSettings.Children.Add(cursorImagedButton.Item2); - var dragImagedButton = CreateImagedButton(layout, DragObject.Instance.GetSprite(), "Drag", 0, 0, -3); + var dragImagedButton = CreateImagedButton(layout, DragObject.Instance.GetSprite(), "Drag", toolOffset, toolOffset, -3); dragImagedButton.Item1.WithProp(GridLayout.Column, 1).WithProp(GridLayout.Row, correctRow); - dragImagedButton.Item2.WithProp(GridLayout.Column, 1).WithProp(GridLayout.Row, correctRow); + dragImagedButton.Item2.WithProp(GridLayout.Column, 1).WithProp(GridLayout.Row, correctRow); extraSettings.Children.Add(dragImagedButton.Item1); - extraSettings.Children.Add(dragImagedButton.Item2); + extraSettings.Children.Add(dragImagedButton.Item2); - var pickImagedButton = CreateImagedButton(layout, PickObject.Instance.GetSprite(), "Pick", 0, 0, -4); + var pickImagedButton = CreateImagedButton(layout, PickObject.Instance.GetSprite(), "Pick", toolOffset, toolOffset, -4); pickImagedButton.Item1.WithProp(GridLayout.Column, 2).WithProp(GridLayout.Row, correctRow); - pickImagedButton.Item2.WithProp(GridLayout.Column, 2).WithProp(GridLayout.Row, correctRow); + pickImagedButton.Item2.WithProp(GridLayout.Column, 2).WithProp(GridLayout.Row, correctRow); extraSettings.Children.Add(pickImagedButton.Item1); - extraSettings.Children.Add(pickImagedButton.Item2); + extraSettings.Children.Add(pickImagedButton.Item2); - var eraserImagedButton = CreateImagedButton(layout, EraserObject.Instance.GetSprite(), "Eraser", 0, 0, -2); + var eraserImagedButton = CreateImagedButton(layout, EraserObject.Instance.GetSprite(), "Eraser", toolOffset, toolOffset, -2); eraserImagedButton.Item1.WithProp(GridLayout.Column, 3).WithProp(GridLayout.Row, correctRow); - eraserImagedButton.Item2.WithProp(GridLayout.Column, 3).WithProp(GridLayout.Row, correctRow); + eraserImagedButton.Item2.WithProp(GridLayout.Column, 3).WithProp(GridLayout.Row, correctRow); extraSettings.Children.Add(eraserImagedButton.Item1); - extraSettings.Children.Add(eraserImagedButton.Item2); + extraSettings.Children.Add(eraserImagedButton.Item2); PauseOptions.Add(extraSettings); } @@ -631,24 +858,44 @@ private static void SetupTools(LayoutRoot layout) private static (Button, Image) CreateImagedButton(LayoutRoot layout, Sprite sprite, string name, int horizontalPadding, int verticalPadding, int index) { - var img = new Image(layout, sprite, name + " Image") + var buttonSize = (int)(BASE_BUTTON_SIZE * _uiScaleFactor); + var imageSize = (int)(BASE_IMAGE_SIZE * _uiScaleFactor); + + var existingButton = PauseOptions.FirstOrDefault(x => x.Name == name + " Button"); + var existingImage = PauseOptions.FirstOrDefault(x => x.Name == name + " Image"); + + if (existingButton != null) + { + existingButton.Destroy(); + } + if (existingImage != null) + { + existingImage.Destroy(); + } + + var button = new Button(layout, name + " Button") { + Content = "", HorizontalAlignment = HorizontalAlignment.Right, VerticalAlignment = VerticalAlignment.Bottom, - Height = 40, - Width = 40, - PreserveAspectRatio = true, - Padding = new Padding(horizontalPadding + 35, verticalPadding + 35) + MinHeight = buttonSize, + MinWidth = buttonSize, + Padding = new Padding( + (int)(horizontalPadding * _uiScaleFactor), + (int)(verticalPadding * _uiScaleFactor)) }; - var button = new Button(layout, name + " Button") + var imageHorizontalPos = (int)(horizontalPadding * _uiScaleFactor) + (buttonSize - imageSize) / 2; + var imageVerticalPos = (int)(verticalPadding * _uiScaleFactor) + (buttonSize - imageSize) / 2; + + var img = new Image(layout, sprite, name + " Image") { - Content = "", HorizontalAlignment = HorizontalAlignment.Right, VerticalAlignment = VerticalAlignment.Bottom, - MinHeight = 80, - MinWidth = 80, - Padding = new Padding(horizontalPadding + 15, verticalPadding + 15) + Height = imageSize, + Width = imageSize, + PreserveAspectRatio = true, + Padding = new Padding(imageHorizontalPos, imageVerticalPos) }; button.Click += _ => @@ -659,7 +906,6 @@ private static (Button, Image) CreateImagedButton(LayoutRoot layout, Sprite spri SelectedItem?.AfterSelect(); }; - PauseOptions.Add(img); PauseOptions.Add(button); return (button, img); From fb677228d1e882eba7790d055776ac7ccfedf0d4 Mon Sep 17 00:00:00 2001 From: Aflaungos Date: Tue, 4 Nov 2025 09:21:44 -0300 Subject: [PATCH 02/12] Added CanAttack switch * This new switch is intended for all the previous NPCs that couldn't be pacified, now they won't attack whatsoever. --- .../Content/Elements/Custom/RoomObjects.cs | 17 ++++++++- .../Architect/Content/Groups/ConfigGroup.cs | 35 +++++++++++++++++++ WorldEditor/Architect/UI/EditorUIManager.cs | 19 ++++++---- 3 files changed, 64 insertions(+), 7 deletions(-) diff --git a/WorldEditor/Architect/Content/Elements/Custom/RoomObjects.cs b/WorldEditor/Architect/Content/Elements/Custom/RoomObjects.cs index 22dfb03..756e99c 100644 --- a/WorldEditor/Architect/Content/Elements/Custom/RoomObjects.cs +++ b/WorldEditor/Architect/Content/Elements/Custom/RoomObjects.cs @@ -98,7 +98,22 @@ public static void Initialize() if (config) try { - point = o.scene.FindGameObject(config.objectPath); + // First try the current scene + point = o.scene.FindGameObject(config.objectPath); + + // If not found, search all other loaded scenes + if (point == null) + { + for (int i = 0; i < UnityEngine.SceneManagement.SceneManager.sceneCount; i++) + { + var scene = UnityEngine.SceneManagement.SceneManager.GetSceneAt(i); + if (scene != o.scene) // Skip the current scene since we already checked it + { + point = scene.FindGameObject(config.objectPath); + if (point != null) break; + } + } + } } catch (ArgumentException) { diff --git a/WorldEditor/Architect/Content/Groups/ConfigGroup.cs b/WorldEditor/Architect/Content/Groups/ConfigGroup.cs index c82d418..16705cf 100644 --- a/WorldEditor/Architect/Content/Groups/ConfigGroup.cs +++ b/WorldEditor/Architect/Content/Groups/ConfigGroup.cs @@ -490,6 +490,41 @@ public static void Initialize() }) .WithCondition(o => o.GetComponentInChildren() || o.transform.Find("Alert Range")), "alert_range_trigger"), + Attributes.ConfigManager.RegisterConfigType( + new BoolConfigType("Can Attack", + (o, value) => + { + var attackRange = o.transform.Find("Attack Range"); + var attackRegion = o.transform.Find("Attack Region"); + var alertRegion = o.transform.Find("Alert Region"); + var evadeCheck = o.transform.Find("Evade Check"); + var runCheck = o.transform.Find("Run Check"); + var wallCheck = o.transform.Find("Wall Check"); + var HeadBox = o.transform.Find("Head Box"); + + if (attackRange != null) + attackRange.gameObject.SetActive(value.GetValue()); + + if (attackRegion != null) + attackRegion.gameObject.SetActive(value.GetValue()); + + if (alertRegion != null) + alertRegion.gameObject.SetActive(value.GetValue()); + + if (evadeCheck != null) + evadeCheck.gameObject.SetActive(value.GetValue()); + + if (runCheck != null) + runCheck.gameObject.SetActive(value.GetValue()); + + if (wallCheck != null) + wallCheck.gameObject.SetActive(value.GetValue()); + + if (HeadBox != null) + HeadBox.gameObject.SetActive(value.GetValue()); + }) + .WithCondition(o => o.transform.Find("Attack Range") != null || o.transform.Find("Attack Region") != null), + "attack_range_trigger"), Attributes.ConfigManager.RegisterConfigType( new BoolConfigType("Contact Damage", (o, value) => diff --git a/WorldEditor/Architect/UI/EditorUIManager.cs b/WorldEditor/Architect/UI/EditorUIManager.cs index c7dfe05..1173f0c 100644 --- a/WorldEditor/Architect/UI/EditorUIManager.cs +++ b/WorldEditor/Architect/UI/EditorUIManager.cs @@ -659,18 +659,24 @@ private static void SetupObjectOptions(LayoutRoot layout) PauseOptions.Add(favourite); _selectionButtons.Add((button, favourite, image)); + + PauseOptions.Add(button); + PauseOptions.Add(image); } var resetHorizontalPos = baseGridOffsetX; var resetVerticalPos = baseGridOffsetY + 3 * baseButtonSpacingY; var existingReset = PauseOptions.FirstOrDefault(x => x.Name?.Contains("Reset Button") == true); - if (existingReset == null) - { - CreateImagedButton(layout, ResetObject.Instance.GetSprite(), "Reset", - resetHorizontalPos, - resetVerticalPos, -5); - } + if (existingReset == null) + { + var (resetButton, resetImage) = CreateImagedButton(layout, ResetObject.Instance.GetSprite(), "Reset", + resetHorizontalPos, resetVerticalPos, -5); + + // Add reset button and image to PauseOptions + PauseOptions.Add(resetButton); + PauseOptions.Add(resetImage); + } RefreshObjects(); RefreshButtons(); @@ -907,6 +913,7 @@ private static (Button, Image) CreateImagedButton(LayoutRoot layout, Sprite spri }; PauseOptions.Add(button); + PauseOptions.Add(img); return (button, img); } From 5fb114fc5791b9da057891d2e51f3d440e59a5d7 Mon Sep 17 00:00:00 2001 From: arunkapila Date: Fri, 31 Oct 2025 23:01:47 +0000 Subject: [PATCH 03/12] 1.16.15.1 Change to Zote Trophy to use Silksong method --- WorldEditor.sln.DotSettings.user | 2 + WorldEditor/Architect/Architect.csproj | 4 +- .../Elements/Custom/Behaviour/ZoteTrophy.cs | 43 +++++++++++++----- .../ScatteredAndLost/dream_block_exit2.wav | Bin 52238 -> 0 bytes 4 files changed, 35 insertions(+), 14 deletions(-) delete mode 100644 WorldEditor/Architect/Resources/ScatteredAndLost/dream_block_exit2.wav diff --git a/WorldEditor.sln.DotSettings.user b/WorldEditor.sln.DotSettings.user index 667658e..b7eee8e 100644 --- a/WorldEditor.sln.DotSettings.user +++ b/WorldEditor.sln.DotSettings.user @@ -134,12 +134,14 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded diff --git a/WorldEditor/Architect/Architect.csproj b/WorldEditor/Architect/Architect.csproj index f8490d0..de0e7e4 100644 --- a/WorldEditor/Architect/Architect.csproj +++ b/WorldEditor/Architect/Architect.csproj @@ -8,8 +8,8 @@ Architect A mod to add platforms, enemies and more to change areas in the game with an in-game level editor. Copyright © Arun Kapila 2025 - 1.16.15.0 - 1.16.15.0 + 1.16.15.1 + 1.16.15.1 bin\$(Configuration)\ latest diff --git a/WorldEditor/Architect/Content/Elements/Custom/Behaviour/ZoteTrophy.cs b/WorldEditor/Architect/Content/Elements/Custom/Behaviour/ZoteTrophy.cs index 523cfa0..fa410b1 100644 --- a/WorldEditor/Architect/Content/Elements/Custom/Behaviour/ZoteTrophy.cs +++ b/WorldEditor/Architect/Content/Elements/Custom/Behaviour/ZoteTrophy.cs @@ -1,14 +1,12 @@ using Architect.MultiplayerHook; -using HutongGames.PlayMaker; -using Modding; +using SFCore.Utils; using UnityEngine; +using FsmUtil = Satchel.FsmUtil; namespace Architect.Content.Elements.Custom.Behaviour; public class ZoteTrophy : MonoBehaviour { - private static GameObject _titleCard; - private static FsmString _titleCardData; private bool _collected; private float _collectedTime; private bool _particlesStopped; @@ -77,19 +75,40 @@ private void OnTriggerEnter2D(Collider2D other) public static void Init() { - _titleCard = GameCameras.instance.hudCamera.transform.GetChild(10).GetChild(0).gameObject; - _titleCardData = _titleCard.LocateMyFSM("Area Title Control").FsmVariables.FindFsmString("Area Event"); - - ModHooks.LanguageGetHook += (key, _, orig) => + On.PlayMakerFSM.Awake += (orig, fsm) => { - if (key.EndsWith("_RawText_SUPER") || key.EndsWith("_RawText_SUB")) return ""; - return key.EndsWith("_RawText_MAIN") ? key.Replace("_RawText_MAIN", "") : orig; + if (fsm.FsmName != "Area Title Control") return; + + var header = fsm.FsmVariables.FindFsmString("Title Sup"); + var footer = fsm.FsmVariables.FindFsmString("Title Sub"); + var body = fsm.FsmVariables.FindFsmString("Title Main"); + + FsmUtil.AddCustomAction(fsm.GetState("Init all"), () => + { + if (!_overrideAreaText) return; + + header.Value = _areaHeader; + footer.Value = _areaFooter; + body.Value = _areaBody; + + _overrideAreaText = false; + }); + orig(fsm); }; } + + private static bool _overrideAreaText; + private static string _areaHeader; + private static string _areaBody; + private static string _areaFooter; public static void WinScreen(string name) { - _titleCardData.Value = name + "_RawText"; - _titleCard.SetActive(true); + _overrideAreaText = true; + _areaHeader = ""; + _areaBody = name; + _areaFooter = "Wins"; + + AreaTitle.instance.gameObject.SetActive(true); } } \ No newline at end of file diff --git a/WorldEditor/Architect/Resources/ScatteredAndLost/dream_block_exit2.wav b/WorldEditor/Architect/Resources/ScatteredAndLost/dream_block_exit2.wav deleted file mode 100644 index 844817fbad6d8235fb1399115f68bec8b05524e6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52238 zcmagF1z1$wyFNU`Fm!i!cehBVbcd9HA|R-sf>I`;D2QMph=N53(%qdy!w^HvFhkb_ zGkh~Y4#)RB=lrklTGyKOtS9dE?9JYrb+5IzgN3OnMIQj*dfvq(DC~wHBLDy(Ar#}E z0Kj865&$WH5fJEi!*8h<0HDVE;4AR7CmJUrC+a6sCwwPLggT9{$EV}p;Ro>J_-gzv zpb3j#~e7^@$NA*ZtD2r@gCL{dx#;$ZerN6hS&`Z5X+8bCR8{k36qZT z#du>PF?TVkm@k-P%vJ1HY#x@AU?amyVlQKMbqfDy&4p~KM6(K3Xbr?3}} zpB+~mCmsWiDnMeTXVX*`f4Nmr>OyPjm-ibUAb?!AlfXkD@|bqo+`MD0t1x6F&h}p$tVq36v*g)(Wh8rV=zJlsM`f>E~NEj85QbO0F zj|u+Hm?}&)CLi;QaQwrVpP1JKR5HSuAI4B(zYt_q3_sy7QiHxsI1X(zIob*R3eAC8 z!Q8;IAL||~9rGL)9FyT>aO=k@$0f&lIBGoANzjSoNgv)5e}IGI&JnKAM<hdC&qv&Kqo*2FmUqVWcUOGfS@1ATo+lGCR_Cl24=$ z$RK3qn-VurfD47C8K}E4hfdr{g2~kf{u~B7Go~NRr z!O;ZL9@9Ldai>92A5i5{UI0;3bdkrCDU)uHq>&=X?f_*eA!8D92Qn41BeHOceku}Layl|vFKRKWBM=1$3nZas!{J~I1?@Fsh2_mlq zP@J5{Q{h;#8t86R04AJpC-kCu(DzVRjcBgp>Cot(`W3n14%Zhq`Zj(cZIRH8sO|2Zt(^zkR9j^mxyVYS@y4f(-^g%; z8=@Amx9$QDUOQUZU$uaXZ0v41Y>#c~A>3gJuwO{#-HF3l6eDKv_&mUiG>xPaz)Z3S zXg;yT^*X8;h+tmd2SLD`P~CLiY=Hc(!AAIQO>>ejd`JQZNs2jetL73KX{~xXJTK2gtTT zGafXT3dcJ0RfazL2XuHUBQk!>-JL>Y3es<5W}|Z}dk=S% zf)4=o(g7Ls=soE^&}A@`Gl#IwvjCa4XeNMRfS1_aqsGHus15QW6 zxx9-aY*OTsUZOn0d;&qd&-q2g^yE5}8PtHPM@qwr!m^!0RvbPof^526jsl;><)moD zz6pv6H;7A0@5+`cC@R~i_v^J7eKC4$cy!)G@4hmJG)O8+_L0(CZCy(>ub#`&5iMbl zf@s~%to%*pOmxhotbJWY{D*=JE(!bmb}%(xG>kdNtGlGfZn|an(zD6GJuo!jlkaUe zm<_L`fnAj6_khOWfDoaOnGi^@pZ_a27n_CiYuc*1KaF>7cpM^ZIW4Xk^BMN&A8Yt1 zz@>v_@bYEKjT+hKgiX85C(HtkMzu*4ZAHa7!x=WI8$hDK0!lDzxUjL@q?(`Z=sCf2 zYg!nEanU_~QT`YFpZT+RuxzeOBaC{iJDdtU7|sXG=IqiH6n zI|0o*j?39|^z(zump3l&z1@4dk-IE8|7vz{!fuGNe{O_pK@3g?Uth*8yqF(aj9gKI z!Poj=6&qc9l&DG+_^^5BI(%=QWa{a7$aweI{^;|Ok)hDRoDuMp`J(({&8SWT$9JaO zrO$VZ08LroNN5o3+j7lp-f(v-wsNE_rednPt_wuC*m)Z}F<;`~+*2|~A`Q-C_#T9yd z(=71Gt9zgRR_T-ckJ&%T^y0X3m5%<9$Y=QYJqP*CqEfDNY04W`1&7Z*7+mUq z0^T2s?1KCr%FBK?_&WXhw}x_q?WaZR`^fk;*%=_dnNZeS#!1{Q|YQSFC0qw@(G^9oByG5pU>e$Pj&r0>*z+#JC z^QGf}(S(^f1T7keI)pP%H1zX~wXS6xM>13hWJ=+rqD7wbsS0R{C(7@r?rU^vKGW*a z|8CLiGU(eDVBm-LzTj&XQgV$wn);T}o#nV&@h9;(Ld6s6-*Wq|&u#h`@95&#Q{Dge zAnHHf{Se{r+hDh2+2Ppk*BdnDamS2HH&sv0B*0S6F4rp|R4EmR5FmKkeU&E z%9YGk$(+F0%Fsjij6xfC9aV*g&~EW|N+~MT%0HK}7OUdnVVnYK1NW$0*dL2$D~L#& za92_0VCVLpZ7*+6?;fBLR6~5Javh3=k{5ZLn31gKgc{`^Xq4#57`2SgLQnqL$P1zlO~+0BI{Br-&___c&$r0V1r7{VEl z@(A?$9C8#1K!hQkci_AE8#5~z3+40K3sLj_lOw%|CWXew-C5K3*Y&qW;agK$JytEy z_Kwlqg#pNMpGo!4V&>w9CA+_re~f?EELCmr>?4`nUi4hQwz-AyTbY}a8*}K3@0e%; zRHl5>{NV9wC`BoeG(q?w>{U{Je^F+sz%Swa_iw#kUP|G8pp-zC07$5QsGoj28=5JU zLQ3cnH4};X$)^%gLqe#;mRJD^RY`Xq|Lv3fx0PrkdJ^M_?LVW=qR(_hpHsBI(wWcS z=>JPPL;h!bigXrFwEVN2>Cf8J49WkK;aNT;3F~QduWzO2Kkv>^%DMBI?Okl@@1&IE zq9-#?H6IYYlwaNwJA{n?yOpGZqzhu74# zO$OBG-RsL1bH%VPB-9+@90>CG?PZwr=5JINNjjAm^AYcXM1Wk9G^6kWR}mYOg^Tqm zr;5OL(LdtelDjhgiceH1)QvTrG}%@86|$7)&+9tf^q{x((vlI+;ND|ja1&cpnGEa!*eh4 zEP~kPhE01ubj$ULg`*;2CWyeLwd_ElO6wLbw)n2t-QTyu!w&;8F7gN9LqY5h54;ZYwtlQeLENW%rf!XI4(arJ4{^(1?`p3q!!-0(m0KK491 zL=LT{xQ1{UVQ_oRO(-(6@ayxKz(c2T|@mvJH5d+WwDB)(Xt9T94Sdy({Y4mw? z98QefmqvzBn*mH!NYO!^$aIfglJh#xcM&rgWyMR%!K&-((wZ^aSI%czKX<HXU=K)_uWkF{abV9 z_wV-a{d=0r{|oQmQU4qH?|4Lsh)P5vW_jxS&xn7@|Hl2ldqjUC+F9IL`+s{AwL}kM z4yWa>bf!N$>a#qF=zov=H0mt>Gv8A!5#jInXYs`L-}X}|3*H` z=*$xx-xS$?nKpDb()sr2KQj0`>RASVHD`!_^Ce2iTO#32;csu=jIFx+HctNzGTPyW z`}N;Z0#V{OdhZaUiC$-CBjBD*Rx4Ji!LL?Lg2Y6Au2fU-)wVdBM z9&iT;8jGz;j7nw6{ZRg-I-w*c{Y(JJvBu)T{D5}rtj0j1rIM-AsQGnMS&nsJgNi*tqJnBygTK9eHV4(W4pWrpkg>5^R1 z8zPr@kj%enYCsNDDEes@Bx^ZyAd@e1ChH=rE)zFR9y$HVFg6VD4uUg|u^O}Sa{&Y< z`FU6@0a9ClRZqC%kvydaE0i;s%Z|-}O6!0f64-yW`_XU)WPZ&QX}4{G`UIdSIXGBa zc{aDQIJ$8g0|k0BWpOHSwln3^+ypg}zanFySfvIsinBIwFcMbtIQh!>Uh*7rj`7Ax zTv4u6|D)=!z#=s)S|GwN+9L8r?6d5&M(ufP6CXoCZAaxKxnL=juqy8k7m{0>8^K=0 zw#X^Qza~5!|0isj8GHlO_IL_>rKLz>uK0 z%tSFX`)p^RQw>GNcJSAe#SLsG@}_66?rJtAC7I&beUiB-;^L~Y#Q%Wt5g!z zKk3xzH=NTm=C!tPlyus#($Q;`!SU!YFEJXh32?1&+-JT-TLHX;*F;C4vaoxAOb{oX zADt)YTfO)c?xs$iqgqVdJ!#~5c;Q1R0hwo0_lP!==k=`f# zZ;qmhA+1GmZ@aETR=+Q!p<1h#*Xxi!;6In{%-)}VIX}Dp38O<*z@SEhz`xy(+b-X+ z-hYnXC6%FlPB#LiI#xb%K@DSEjyEyi4-uH=R1T^}LeGmAbIMt@Yz z($A88uaHCW@x{C1_d>b51=~fwWsScJN&-s8iqC&v|Md3#?nn8&&^)T#xjfMVl3&_o z)0G>4sDDxC@x5pK7?VHoga0>6#it6H-}B$T=A^v3{`B_)#Qm8E-2{F~oP`M|JxO7I zVVzd=h{(@~Z17b7PZmk^J1zf|h%%AI0SUD|aWzrlzhu>iuhYI}d4G7A>zrTob*a#{ z5ctJD&nSoH&EvGxhi6EDg!jn@53fCjq^hJ}d<9HrO(9x{Ih++D{uyT^%0#AXk&Jk7 zIx2DGf9Cj?tDeQ2YX05(H`_IRJoMZ-b?+f0NiiAtNFWXMb~(SXP~b~xPR<9yIwA)4ie5TA^RlqwC$|K>}MJL&z(}ko++lyzZ}WbdDHRc?i-#D;RS8K-&HSF zVM{H)oqYcD+3<@;-r76I*Wg#3uLIt`$a$Jak!$e&_8af_$$9++8Q-OTWE2}#PqvbF z$+xjL#WXH7Ha4u)TQn*)rPsBUEB)Nh&;KNproUbCjK?z=;dQp4Oovfj#F z)||OFmO4T`+BTu{M*LFI(F&vOx~o|wA+F&jAJ7eDT|0lPH!|I)nJm# z7ob(E^oWFwkj*E%Q>YJ^sY8doj|VkqHB9$m!!G645>f$awqC!iG+#cSxIzYhw1!$< zT^?CETnU8*Y|8KRqU`VqwB>wmV)BBbYz1`oG>@ppD45COP7aTiu%9swxQAqk)D^Us zDKkj;ar5{e6bB4(EC6~SITFi=_D1icxDTebOZO~Kz_e;CPg(A;UgfgnzbD|v7thtg z`jjD_wwy+TiiwQ!_z<;*j>0%#KjMqXbSXT@jYyVHUf`dg(l^k{ZLn!nE#)o3eiC7a zi7Ey-M8(bak;jx>h(?0+7(=tSzLBtHc))@^23S&4uoKpud0*0Bz%A{GADZAC$bu-w zDFR874uz45Yq79|4Y7SEjKQ%7miL$rFhVv8v;px^x6oZ?%w$$$i~?C!zP<#X|i z_h7VehNTa)e-?`1^&8%B8|dU>>hk&3D^T-=^rg=A+xthDi?}j0$0o~cR5yRUPAyAo zec$qsVZUnE_YR>BV5?n2VgqOA@GyG(0r=*CLN{BhbUj%utWmy8rGLAhrk}2#q<5`d zpyf#`qT6dI8LTw^WK0MgIz~D}wv@4)GcO2!)1A}3FlxHs0wte&GZfP7SI%3qRpHYH z0P8P_Bbwo{3sMu$hF=U9P2lGrETN{R`>wXhx4!Lk8*-WLTuaSV z48`;+cFXoDPk65W*i$|Fwk5mre9;V!KEUJ3aHD8>G#f4wfWy;Zd=7MWqc+_UsmQHe zQH%l(aGZwyeJq8uLEYcXUWFcGHT|?a-vsK^b`nyH>~Kx`g%Bstw~cF{sw^SD4NQd~0

cr&mq>!r~(k*yC0R!4i=?$E7a_on2sni99_kWSu_ z+Odz|uOmN4Q9~zvo;};0${p5iT}|25pGv5HVvCh)j#`2{R5}FPbvs78 zD+iKCwuZ1hVQqyiiJjoSfc}mi`Cjla^`zj$rNNi&xeW`|%#}|ndunbphBdj>^VINE z(N|roU?_7cxmhys$DqNmW2M`ri?P+RLBBzZaNSS$YIpiKT2(JsIR9BIb@-+8eW_6M zoBYrH66*5Q-?PO9CDXrY|9q)#Yo_VG+SS(T)_S+gt6#1!sBO7=wAkv$K~aAdq)l!h zY(!;vzpuVKrTb6s;85^X-y#e2{xo(Vs7tPErQ5U5Wk_)R$8`1d)cE!Y!>Iju+xX64 zc6)83Q2P)#c@?ysw&lK_xAJGfbUJd9boO|GarylkW|Q`S`S9KW*^v-t8vTCX8(F>V zzL>FOz1Fs&b6|%hOdXG?w&&pQ*3{QOZ)YBi>>?3;un=VRrYJ0XCU+!eWPMy{rewBZ zrfy<$2;Y6w`JngZa1!_>_~V4wy#JaY^4*r#K_V)2Z*5h4`unIiSZA7g$r|pt)wV~q zuduZZzXj_z3?xdSzN$S$;vSp3YKUAq!C|3HSwyE({6R3M&tnXOpXM6c} z2zg+!-*^8D0Z#tz7bV=0_DeQKwx1ldoXA}A-G6xxU8MJ;4j8z!7eaNlBP=3}HEceD z`i5A{>stnQp2R-9qaAG-VG>O0uNCMS<`CU|3wYBZLNZkU^2JbwD6Tu7;xYHq@BE40 ziT?2)u1w_1_;|Irp=h_8E7#ve3PsXJdSAO7`6${g&hI}qB6}vXW@1gfNBn&d2fn_1 zHTA0ZwJ%Y{k%|$=VRs`MZ=}SX#Gk*LdaLd~-2T*GH~#%y7$M>>0x_Ogn&J#^8eK;F z-0?2Cq;YNaX4EP zx1{3+;*xG}T)z<>8(M!gBO>G)YnXY+jUd+$%P_~A6aQILAg(La+_k-9euwVv#e3LW z=~sci93H-2nSL<=>%PHWIM+u`mmCru@vdmEJHFTaV*~sHgM#oOE?4dZ9r?JsvAfW^ z0X!o--Q9a#ueis0oBA^P?f4Z3a0l~Wy>~6(+QVQu4>CKH%~QvVUKa!4mwpCX2h0SF z`#<*i;;QH%f8nW_zK)C>ub8C7vRt+jNO@Q}NcBKTUIr?bEUGB_LuiTLhVO{Gfir=t zRiIa#N+ws%L5Wx0h_F)zR|ryG)UGpls4t}XK`le0M~g%os`J%=#Wc!7%PQYS#Xi^3 z-JZ|lh5l>xCFM-zHC0kgJ?%klv<|Z>Ca=s zlZH!O@FhepJaKh+tk-Ki&Zpkx5rThiUr<2iUR9yrgv%(1`*mhEb9Q|>Jr z%bJauRT$st|JHfE{dI?Bw|@_&-LpF6Yx?V$^nu4 zUr68{PjN5{1f;8g?i?D z8vXZ7oJvIPSvG$)MD1BUwV$D!_K4XMHK)BZWFpR~Oq7UrVvi^j`5Cc2iy+$mY5#ll zXBfnmSpQmxv1d7*d7bIb@J>hg=P0KrME`$d{X0*h{WPEOMD6?6l7k66m^kv^(a$`H z?J3&du}&qG`>#{Fo}Wt>%x2G7&42R!Zt-ksO=(f-t#X|z+B&hu;s%i_;bPGu>#DS_ zM6l9i)%Z{F;P{W3hWWDj)YFGp>0W=UQKL-Ox4)}DJx7}~aY9sGv?h7068p$84 z9`~H!oJ)g0KIlN-+IzlMJr_HvH36Hv28mb;-g4XP+0Qx@L|-@-AkC$2WxP+b2L2&~RgD(bNVM!JY z7Ecp4=ay!srK6{{qkqQ4$+FJu&X&S+Qv@bKE!80DDYYiEC68D1P;`n{(`Hmxpj=0nlXv#jO8WU zhxURF5{`7vAZHs}3FF_I6Dl7xGz`GzDmKd2aIPW`>p#7G%cr`kUwU&Cc2OI>b4o+m@M4>D*Palh;*#pb??-`5e|H z$u`OTW4UQH%#eO1 zO@$6gkia$G-{M2+tNJ85*(x&%Y6<|k0Vy`A$1-sWZd2D@IXfPS($?BwYO>hw{}*w$i@* z609`zB(!*%uhbQkXv#TSJLXQd*BmAsv8*5HFH?a*EmV}WH)sqggGq`HYt}cR&{eYC zm-teOV-O{o(Q()j^I`Ns(q6;PrCq|(GU_Ha6Y!K`fhrL+4v0d3*b~@NThCej4K;;0 zjGA^_Xc}sB9qgHcEWTLVfm0La)gH*Ht;~%tP_{|Sp{u=v?Gla4HScR2>MdHQ2BhZ; zHzE$ykMxi751;M2t$$p+H7hggHTP*Ye(F5NH_Dh#Nae3aCXB8J|SJ%!trcZ%mB zrx=qh<#Q6i$qC6D$_2(GP9r`KzEE}$-5&}@-~m|>KolQIf(O~q`O!1bN>V~dH}Eg< zZ%K8iY8jZ=Vt6Qo8^!vBZ*e&>A?bHm?+Omc-c`S(&8L&1qoc=XkY|)(9&1Bsw`6N; zm*6zvA>mW*an-TbD#_Hr$i?WYxs5%8d!}cgn~hVNqrOYA`?TA8hjPmwrnfBh9hW?Q z_&8sD?o;Dg?i6Bm(Wp;ga3_5Yum#v4?jt(RxQ;E`q5N1{Tmfx|l@;;z?6 z55_peT)F9Toi1|wnn2W-Yr&x%mjr?u!#ZM~5^!AZ~`E}Ew$3NqPF>V!Q1YEL%froW7>sj z%Q5RkXF9)MmohHKI#`=l>Os}p6sKgP2=6w1Rt2jqDu>DKN(hUsNn|QLK8G`RGEX&7 z(|WExqN%KJVW?|xLyJ_+L1RmsRJ%p_i#USw2g3^ETkb9)Q{m4-)Do=9z1r&fe!6!w zZYerSJ`?^VSR$+>CL&%fdL$GqXeaD1Q7`jO+FZ1oXPC8+se`qYFHq7*eoMw(l#L(9 z?;)-&`%BtO1j_^D++yQoSL41Yg=|gCTr6drW&-@e0s_43WK<(~1-uJLo0*q0h>MPUhC6|)md%Yhf%z3H zl%r8kVuloQ1UU$a+C6X;<9Acq@|^) zVrXao%p=OX$LY(i$nl!jRCH51R?c0zTR4ZOfcvU2NRduA@4T!5T=$Khr_s83rG=20 zu-OkQ4LfB!R_muG2>nXUZz?>BFd3}WvTV7krQYcIqVorO%NiTXv?`4npY+TWpgSZ;Fp&a%t9~u7AgPyx%eq?9rBIb_wM0yK)jk}ULaM}PYYb}Z_y{*Ko zl`lZ;S)39bt8K4a+L_szTUddt?aWDxuNvMpk}$n*&Tk=S{>JQ=CEoV4I#N2?I{;+o$`15)CMh2tC zk1TK47TJs2E7-oXskctDa<_;x%`;{&`C%$*Ze>1d>SX-UfJDzh+efoNeN9zN44D$)^>npWt)X)m z!_WFdYOHeeQtEP|svs>9&Bw~0W&NdEq{$T-)wMKVsN&@TG7lvK#q@;Bg@!~hqJhHA zJhxdQ=zQtSSReB4^0#q?vk0?9a%S^?6f_hl=d0y)4l?xCe%>Ae_ zR33EPjJ%8!v@?|Ez#h`J6L)Oiq0XMg?!s>O&frEG%wctB?Fk}nCl<>9VrCFzxIyDc zB}k)8&(B~@s{}G8xr|jhklW0FJy~I1ore`|fcMtXu7t0hy0ICU`{>=nwH@#E%tgZM zIirVQleu`rH1-YUD2)R7+e2Nr^1|_~8N_HY9ICaly|x6qw?eYe26?&Mvr%}+a-2(e z6TKR*2*^Ki!%%FyF3(Pjja?a~7~L7<>+xyhZxLw?Z2!}Fy*sCuWN>ygdE&>!+!z8} zGR`-}J#}|XV!*lmq`s|Mxq7fVy+*rEx6!^Or`5fcxh=8XpuMXF+0@wbxI?G=Ww%3@ zYsdZ8jYg;1wLjLyYK4ZmMepU_<3A?lIpte^x$*T^L2H5g*Yf<;&nEfM{L(LD1*t!n zi+>iae_P9aoL!s7^*A|+$R^|x*dHZ78&6|-;gGJM`TSMeGxB62^EgOYdG!8?+atq= zGmq1s`KMJp<4T!)6!N&|$?jvweQE+b`SL*XMc7+9!XBGVj!L%U%fS~bnVs)EKKy() zkX4liO8U!m&RD5el4tT6Zy}Cx%3IDj)F}fx{XJzcMEhC)Ebhz;o@n`0A#*27`9)bG z5sN$_{LyjRn^zW@%IT9S*#y2sno#&K^!1b6olgv};{I!_GsG*2hyU4GB#uKwBuUPE zWc5_?S^iVdlYqwpDU(mg?w>KrvkXr2-AiImQAznkutp^^JaI_3coY5}^X6r`^V9Vw zZ_^gDE`CUS-}>sol zuE?z<<6Fi@r7ZYs$lIsyv$Bm|syus;HkCP_J^ae#nb#ABXTq=NauN%_7s;0>{kWVL z{H{8iGMguJJMBga+v5*UBA>fves~A};P*BzJu7whX?fb!S5DdL?`q!Zzd>a_eRY`m zBwP7SNTz3MT;kcWwB2udBAxmx_1m-LG{^L>nY`J_S#BA$X-ChZUsSz%_QouS;>%(I zIN$F*I3qh<`|ZeAm69*N3yRHu7W``YEn8_@8ULrh0$*F*JkZ|MRnY6x-_p0)tu z?4s2~PG3Q1VQ=$T>|EBY(3sD_?f$Pr86&!b`JFS(EUiktAI4o5^$FkTKoCdEV^ahD zF`X~FwT8b;+RVu;_%Dht&_nVe?TZ!56sy^5E=cpeO;iZxdY0-FwL>g%PgW7xF8p&i>3hcKURCz6JXz#93gM31Roru63pDS&tZaXg+COaH|uZ3waOL8U8k=0;yPii4a4kuJf+xEi%pgoTP@R z!>=E(o~V#Jk{{xY5Bw1$tBDBNqYly)kTEbEpK~<0TfRk#)L0u^HiNJ4XOQesR#7Pe zgRwdrPa$t6KFcSJ+Cpd{-=-_TqJyLT+M}6sB&(V*J!sB!_lVd~-Pp&4SMWo);?lcuXs=Hj zMRQrRcxyyUbkn^S$DX~>tjQabCX=pHHz!^WRJ6?0$~M3{_eRHNlOZ-!--l=WFAS^< z{OoOPD{r_{pWj5@UfZtQvQ*Ds-`wonnb-wwX{oiXF==TU%7nPAg}|*=S0L(R9DObA z>dn3N;q}z5T>X|~;PLxI`|U}!1r@KVbDC^BGkOI3$%k#m8Yf~tWQDb*; zM%dtkj;)8tFNh^X8A2REiCo$k-|RtthtaL(t;VfC*z()u-e!Qc%x}!aFMrzD-m%+c zT1lFIK5}trebjGS7(zMg3XbT*ciHs{4L%zBJRsah+qXWTG^#sRIr?yDbf9jqc|>pm zH=DG0cTs;XZ{i3XG=`rLpLJe{fzB0;=U04L6JLFLw(VEwD=i>^}_M|HrJvoWydy#(xA{8y~QULBkTnzrN* z`?(v9`%Q6^E{++*F2XL)I7^zhW4cNPOE^N%9SZHrvdDbkE2HW-&|4c{h=n>L2T-?x zpBYCvkvu{?nVgR~#yM-aQ}`-{V`Pk#gA_|dquE306sVdgmVi-ID@;Y)*Mv~w7IH6D z=ryX<_f$Pp?kHy}_RD>gQIrvsmr>QzwKvQ*9MSut(W2(5?PtVgzfXu9@*a9?pn6CCd}tJ^^DJbG!eNF=yKjW!2ep1%a!Zl$&vlx z=s<5bq@A?mm}h>V_GP)NNl{${CPn0Zr|_gDLuj}qgE z@kGsEK1uXEtA80KQA6}O^Z$$Y*XJ)+{J*XL8Pj?skm-O~mzq zYi~n;2YvTj^@?_rc9C+O^;q{64=D~Sig**obU8F2+8gI)?MmvF<^I4!%Zt=k{L*B| zTyS;(mv^AclHK41ar?_oW-f1CkS<~_u1+5vys%7JDC^PUdevFgzQQWa?2d_niMHv6nYFpHvD z{XWxOhj?$5fL?zpuLpKcCfoXX=Ne2&>_2-%_{4jjcPVjp^DYe5yLvJBy_bQ*h~+2K z`^M>pe)=k!ODY+v{MsPHG0R{_GxtDu6UTVV7e)i;g-yb(R_(Q&Wu5OjW;>pD$#NBT zylWP!WuxFP_e{A)d(}|f5^ZK;>4QNcFl}k(?z0*N0w8H zUq=3uk*+g|SCvblC69iV=8j&R)s9E=rQs{uq4PnyKHnUYOeb{6lr^ObB~;~y^lv(a z1t(m)6mAX7Ve=^gHu?FaJ`aYkClnunQNSt?jLT8@}sGh;H7w76xnY5&@} z-J>I*Hmv8y$LN&ndsl9FYunLVO5030cX*HZ-ShAFi}(BF&k+=HrR<7WfS7Cd1%8{s z3xjqs&K*8NA<)p5m&t?r1F(KbFM1bQ`-=|JZV;~+?|u)Ai?PE~8#1c|>rz`X2WJNZ z`@9QzmL_InW(>Ab9>zfy;d_w=kv~Gk{X^aEINWf+xEy%X1m<6w^=G|k<5T2&FYq{| z@Tz?Hr>kRENUrWjXk1^5ymu8AlpYZ4OXdNvx3a{T=US>-%i4Z%S@#ErWkp^L6AzsA zkaVK8?{v86s_G5)E%P^go>zk?s+*gWlIff#tXI=!mJjJF*+Dk7nK5y4Sh0WVZ%(#?=pQN zU&T9>%Jt+7FY5*=3X98%d=&STVOMyfT%@L~VXaZBQLlAZn^$93?x7$Liw;#ExdHGs z6^wq05k?CFc>s}Aq)anxpE+)G-sLG1LP`Lo+r{R2TbcZUVVLW?#hX@}-P`g9cTnBv zOV}8a_cXn%`W!IEhrnyt*N1No(@+^$VSp3RmBxrEoz0Y0hc=ZY5XHNr3a?q@nxlni zK+!N?#18Dr@|Pv86-k8G&cT5-`onQCNizjM)gkCL5JXmh-#+@j>#`?|9>clgUZEcx zCLQ7rNcP|EgzpF)d_o)H-r~|RLkB@S@XZRO$(lSQcuZ*o3#OWSKkYbeIQ3%Od}y_c ztLPrFzD!G!yw#+oV&45gW|9M0+O?u{Svo1liYEZIYEElkX%%n3oh&PGqx zjm8f1j+Rdd&UVj5KxXHa79Y>|Pb*DykHJRe2LSD*wI&r&6`{3Hn!DTQJ7v3X^#J;I z`=tBr`=1Z=4yg_^59oExx9m0TwOaL(kNQqLn9P_YpHiBv9?u@HA5R@q8iw?Rb~ks_ zx23k$w0U*P_H7M&j=N9Pj_UPaYZqP{v!{@TaW0t1+abyW6XCunAvNQf*h~ z(UjT(X-;j-uFtREt&eDsZ(3+w?F{ew)b339cJzIHa-C`oO9km~P>F77Rz-b7TL-eo zr8~9lO*5*MwD;O@zzF|OgH>#w5#a(P(U&#K_2>UQT2_13V4 zvKooH?q=6c=C174fX4Vn*7oqevEg~}bL0#+Zznb0ZhAXuHSXECn zllJO>cjnrbC04ZNrKg(4KTix!UYq?VVqr%#Z~Gn$9~|vo-vq;P zYff+ygcQ6KDzezLypQ;_?Yaer0p@kVzWpuz#^8@L2#6_UZZ30CWqox|7#n_~Nb=_7 z4mx&c0-3V$bGvMJXqR>G-2U!Cz@gdxtDVW6cZX}(?&EK$SGyU=h1Jo;mU+a&jpb(8 z9&&kWYin<#5@CkeLwrNb!f2P7=4)rJO)*X+jTQ_9_1x?3=|33nTxeR$g@0egFC0vl zOc+diEIA;PcTu}Hck}k^&<;2goHj=A@L)G|H+aAMU}|4$*P8Iwe8+P9y!d?7ax~Iy z@6zFg{mPApEAn$w;F*5T-tPY2BVWhurhd+WbxZ++JZK+$yIYNOJr8?W6Mm4gu z|L)BhS{{8pHaR9U$~xfC6VyG?J<^42S8Nk$SL({``P>`Wr!!DHIM;u(i?UU@IkA>=xY+JS2FX?)b$*8Oy# zad5K_-dWYk&}QCkIp{XxH8jLSVg+(#Z0Ss!rbG< z;}sM<8PNkvS~{Mcok^arTM1t8UY8+ox|BuZMbX8@r65?s*0(*m-D8B%GIXXE96Si^ zYv~^uG6(Zc=uFo_n4o@8T1fIZ!w7ct)pQti8tJ!xa0ESQ-(ExT!joV%u;8_iP#1{g zOz?Ed%razX`7@$*(`a)I$-gnMskI}!4?Pe*FxkI+NQb?Ozlcx7A`b8DG$D=Gjn>Hs zZ#H|bAFa%-ynsa_m=OS2@ACac-&u)q-jUSdC9v}h$1)?5ZRhJAa{uT4 z$K7Y!16$~=h)ucmYA7?rYE}#)y3h()nF*Vgo3WS~n3|YGPHWC1me`hdXLd&xdf)UE z_Wv0AJ~-OD-L2oBG%7H`J*Gd@)qgzr2pm3XF*wHdIfwULa- zgujJ>VHt2`*b^vnR%v2>?9sFsv~xXp<0?V{c466WK5BYlGJB?W{{2$ka_5Ts3TXM` z@>|#;!fBmqS#pkZ8ZqOrAVK&qH9YxsD6jWF=|)^I=d+SOOvl@dth$CblNDYPHh2+O)p7`E+|{ z(_tff3b!Air+&v|~7`4WSxPtiqSi0({rvCrEq-(&&k{VqjMu%b{7=VbP zV4-3sc8dyjeNma<+{faTDgP6bg5;A22Y?BkUDi9o`Uco1jhz z6}(|h&Mle^XE+P{2n&gd2!d^JunO-B3S^hpS-)s}k1)%cgVB5XFM+q}x0fxd;2qf^Znpt|)^ zVV%lR)lS3){WPNjqf{dU6NKq?6x#@8$TilrXt(@GcC|OP&$l-KNj|7UFPTl==1}bP z+L__p;Zovq*y)ztZR?Z7YHSjw0aJ`yPwKL&wt7MOj1R%)V-yx>%U6!uJyCwQgL1=` zE*uI~1T78j3at+_3?&7J1%C)l4R>2Q8DSFzUFpB7f2GdKr`GIC_?Q&FiM8?X z2G1nw7VoXLTh4F#wb69FQCw@Z_NuKbZ$uZzf7!SqX=LM{^>5ai#2LpQ*zhFjZxSq# zzo9wt-PVDf`Md7#FxeWhDL9F;u_69ZOx>#F==1UON$jlwTN{%yH-6vfy>V(CCHC*i zmKF4sjnVA*m0P#&e|`*lihSDQ?A3GKr!9{}?by5i)9S$$l+`EJEL?YDgZ1WhDe}XV zGaE0wI@fa2AXR$E`5b36=5T zG3to?g>M!-UbbxwBPn`Y*ygHrU!y}-MaERGH`sc5XYuySiDNP7NXgQWrB4eOHysL8nZsAkz-5IcOHVK7yO=FNhJ)>M!><^EdRv`11n=0h>MRZJ%JCn>;WnG7UG|YJxDp=>0K> zFslZ6S%G=GAy->lD_%QUuiB{5JcejMPH==fd%6s`{&BnMl4{pN8p2D6lQs(+WR8CJ zhb+++OVHK0SgU_lUBot`oSaNq5U??*#`mtroa1ZT-6XliE%O-j9LzS{Zu}hf4tk&I zdDM2(+h#KJG>hAKBjRQJ9g9ftv>rvzVmz>qaR-Rew%44OQ54?k)F9shpHCDoXKmX? z(i+loD~;7RE2Xuu{fP5HSDEvBhXgwXImY3QiPP?PKuPe*1)~cuhSdZlQjb&9 z{J#6{_A+*hbn3Jpw&huN<7&*TjC%CfnLH(AI@5e|{4e^!sa0O{9-G~g-L`qI@aqXI z@ke-a?FA$n4uxBW>&K5+7TD*z3f&2Ab530j=8k>N%RS<}`+eX((Vi;T0Ebi?zI6;) zWamphX>Dm;Yg^|qs_HdDL#IqHgRVP>d zi&8CREa(q<8PFA=6}a60FZHp{QU9ogSr9Z4pbY9<_$6X1 z*fUQ?EM5FQ{QDyJB~KPVSlAKl>$l1)&fVOZX8V_burR?`V@vS2iLRt_;tS$F(vYQ& zRTHUz_>Q>OGS_C4BcF0BFgkqElAc923l@ds2H6Ha^v$9qI^DFbw9>P3wA3PnSti;# zIH2q$)>R}nsll?8)IvzX58!Gn_L)zk@=aCfdh9=}p+&L9YvO6U4wtuXbI!AN_pPoH z#MoYoZfpo4$`Y`$vhJ{YYqirdiSQe<)D$o@)VrV!Qqnp}I?uFkY2|A{wIMq0`r#-8 z>@&g?4vOAv;%pRSL^G-}GBQljI|t(F&WPp&O8}&8oL({Cmtd>TNV)SVWFlidY*_wkV439q@CeP^D7oO^aG<0+ULMNen!hw zs~J|R*{jY~*Qu@5&s7)H{?K!XWyqb-Qu#*FA|X_|5t^z0!F0R%jL9>-Hu!p#Na7|K z=3NsND&w?zjK5%daK~_Xi)-edD1yEmHYe{CXNs$21(3s9AGF)xFtv-qT9pNfhJ?#K z1Ql#kP8N5FbDw3%3gl)BxssY03nv_+yV zxh3uuKI6HtGw2Uz&W<7>zJCgW!=%uuIJj%SUo zAFrB;Wb9>n%(qQ#n5vvRJ-naSHS^f!L z5$hN&crIivZtmJl?WANPbNbL6X%;^I=HH?L$mrl)EZ0Q9=V&u-&g;{^u!y`j{O$Zn z9)<5K;0i4yC#BD1AEkW~e~}5_j6-AJ;ole6OPHb(K_dS+pCw2ZZ4+!^U7Nc;YfB$x zZQ+PHCj1a_imVXS!h9~j4XWCG2gW6Ak(BqD`F4Kod>W&O%NEi^E`n}O2|Jga%`~H# z&nGa~2=+QME!fr*hVmX;M|O(xrmaoJMf$PzAV4 z8RC3Vfp|T@lX}TRmH(8lWqbMi=UAf;hmFP~r!P-$op?4X9u1wCom@NBH`z3qHRV0? zV0LnDj2^@*k(OwDpfbe@!8`i8*?lvgr!A&e&4_0MXO51F`xJlP^j#mmF!5(Hc|3a9 zaj^0qS3@nc2PpJ;EH+n88-?cAN2qRG?|b$?`UeJ{7qtp9IcWnXIV zSkKUJT!-OLd~0a)_a^;skv~>!|58?0Vb%yF2bT>F1;7*e|=Prz`bpe80fI7B(Gj3iwKDjA)?MCpD&hYwbAw7uDyjIk2|8hE#jKZf9d(v#^=m_@I_px#?3@1-hoF4&QLI@pV%Q*db@OXuj_I`noB# zS=waVoc--{+wPy1KW1A`w|s2^nx1|;&}Q(R(O_5US#+jAKW}xeG>`D{)TecyYw87G zW^1=r|E?OU{#?^lqpJB=YybIj_4!Xm<$o$ts%t8~6`%h=E|z_otMU87Yd|)gXx`qU z)!O*|){h(QoNi(N?|<%p8`|D{X{rwVtZwxEcDjY%D6B^|$Nc)<9p0_~>qPU*x(}6K zODl`hN}%PH6?0X6)r+b*6)#FN3%hbvIlBvv6;aC1SJ7&A*WRk#^X1T2MpJL2^vlo} zorWuoosFLxe7@|h*;d*2NmG8HEVcMlVMsnamy% zNJXyYU7r^>g?;s`*7?AHr~mFwF0vpee_7t~+|ZmWZ|t7yKYV{PI{ijE^rp+*x(Ayc z?R`A+F!uKF)!mmgmyTa~ef8Uw&uM;F`mTCr7~Ckj9+_@=m3(#MwXN6W*S6ePd=K;A z&IhbpFETnaJZ>GnV|V+@_4ss;biItOjL#YK89CQQH)VGo-(U4`-=kBRDbF2W@LrU> z-14&Jd1j{Glhc`do?Uqsl==3*`THgJkNk&vGW~engRYy~u8Gs;udKYLbB&R<_;T0f zd)SUWf>mQ8WzHqbkM&zx`yKf#Ie}aBi z^0eS_?SK7GcD_2EJ71t(c>05Gv2N+A(iO$dh0wg(?5=E=9EizwOKx=hE_< z3U22^a^Ah3&B}f%e2jhkE;BkS_r=|;QU9a#> z=hVFBJ}Z1Iew3DZFw6FJ{=0#^#UDzG+e)3vH+?Gj^y*XKC)B5o@=xV2KH(~>D|r=X zEACdE`+VyQtnPnXXH%83jB(dDBX09RYn@#)azu!jE zrs}31jjtL_80{N-G`?{B(pc5#uMyG+X7u62&e`AeS=KXFFfC>>YczdghE^C!0DgEHq`m0b3vsm-r zW^YXCCLH54sPE`FypRYZZ6&ziZee)l?r03A9=+2{5B1zQ&uF6&*C^Gb5p~YY)gq8| z%5IO#12?oY-{umb4F@IKSZS;aY+T4^9L!wgE_$it9#A&THHLCOGg|195EejdSp!>b|$ zQRo$WBB>G1OT~-!E!183VUcE8W#pQuDg72a(kym%Dr8!yDp|=>{y-b zyX9bFTEg}9+8~RkmFS#E*w7nay=GZV|LWN2me|a-N$Zxa4`2U2{%mZ~O5ezU$jhsy z`T17K`(w~?6%ncwLu$RB_?bMwspJW^W$#B_C?d9fml)e(TyiI36i$1|Fil?MAeeR z%W#o`$RClVQOMQD)?AIxSVv!bHum|-uMvNi99TRZ9=PCkNM-QZ5Z6#t$nyYmU%t-? z-*Ery0sn#uLbrte_FwKPajvj`O3t(2@A}j;$h*PooOd5}lmE_uCH{0Of!gJp5YQYj z=Ks!rd*F0PT6n}#$kO5kbN(Y zVX6IhyHtCY{UQ5q`(CGU_XO`n;AC*4|FG{GZ*ZHz-Gj2sXJf#hAaTHJYM>|E{h^14 z*FK-!K0aQ_9s{n0F85tS++c3+T$VahqD_ezap7;+@YpHjtNFeI%Z}oNx-V|7;yX z+=DB{PGj})$s{wfm1Cv@oZM!0ii9CN#kN}LVDl{EFs0^u%|+&}=;LNnMs7OWppoDn z{$u1m!@npk(=HPP3LJgJ_+uSGMCdrW6TQ{q2(FKS13MFKnMwdAwbhppnQ z^=*u7x~y)HjuA@nxx|xJBQ|?&JFMZBX9?$V2#XFgeU!OLt4WLLJ&b}dY%}8!=Df?P z%)X7h+wP|QANz58ro%L-Omo?J#IfG~rClmn%O;G}j9Y^p!w%z-q$Q*)cr}KFreIqL z^OoDJqpkdjFL8~yZbBkSLJT7i@gMQ6#A?f5mPw>f1XIE|p_Dj5(8ldY$C;_Y&iabE zH`)R1ZMMYthF+ys43eR>Lca^O9H+ACwbOQ5?lSAr>y&KQYvoQlLUbW2@K13_{5O)B z-8+{+%3?3BS0DK6SNc8nU*qRQm3m#JoN*nuzhu3elt6N{lG`}ipK>gBTI_t;`L2_h z!x_7^_TL<;?Tu_~2xl=A^ik|B%V0aC({q<=9y_Qd!HXAdT0≦D5-CKn@|k#4f-h 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 Date: Fri, 31 Oct 2025 23:30:43 +0000 Subject: [PATCH 04/12] Fixed Zote Trophy --- .../Elements/Custom/Behaviour/ZoteTrophy.cs | 29 +++++++------------ .../MultiplayerHook/WeServerAddon.cs | 1 - 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/WorldEditor/Architect/Content/Elements/Custom/Behaviour/ZoteTrophy.cs b/WorldEditor/Architect/Content/Elements/Custom/Behaviour/ZoteTrophy.cs index fa410b1..6d146a9 100644 --- a/WorldEditor/Architect/Content/Elements/Custom/Behaviour/ZoteTrophy.cs +++ b/WorldEditor/Architect/Content/Elements/Custom/Behaviour/ZoteTrophy.cs @@ -77,37 +77,30 @@ public static void Init() { On.PlayMakerFSM.Awake += (orig, fsm) => { + orig(fsm); + if (fsm.FsmName != "Area Title Control") return; - var header = fsm.FsmVariables.FindFsmString("Title Sup"); - var footer = fsm.FsmVariables.FindFsmString("Title Sub"); - var body = fsm.FsmVariables.FindFsmString("Title Main"); - - FsmUtil.AddCustomAction(fsm.GetState("Init all"), () => + FsmUtil.InsertCustomAction(fsm, "Visited Check", f => { - if (!_overrideAreaText) return; - - header.Value = _areaHeader; - footer.Value = _areaFooter; - body.Value = _areaBody; - - _overrideAreaText = false; - }); - orig(fsm); + if (_overrideAreaText) + { + f.SendEvent("UNVISITED"); + f.FsmVariables.FindFsmString("Title Main").Value = _areaBody; + f.FsmVariables.FindFsmString("Title Sup").Value = ""; + f.FsmVariables.FindFsmString("Title Sub").Value = ""; + } + }, 0); }; } private static bool _overrideAreaText; - private static string _areaHeader; private static string _areaBody; - private static string _areaFooter; public static void WinScreen(string name) { _overrideAreaText = true; - _areaHeader = ""; _areaBody = name; - _areaFooter = "Wins"; AreaTitle.instance.gameObject.SetActive(true); } diff --git a/WorldEditor/Architect/MultiplayerHook/WeServerAddon.cs b/WorldEditor/Architect/MultiplayerHook/WeServerAddon.cs index c73e70f..ce14cab 100644 --- a/WorldEditor/Architect/MultiplayerHook/WeServerAddon.cs +++ b/WorldEditor/Architect/MultiplayerHook/WeServerAddon.cs @@ -88,7 +88,6 @@ public override void Initialize(IServerApi serverApi) { Logger.Info("Receiving Win Packet [SERVER]"); sender.SendSingleData(PacketId.Win, packet, serverApi.ServerManager.Players - .Where(player => player.Id != id) .Select(player => player.Id).ToArray()); }); From 1031361064513e13f7844bb115045b44162ce54b Mon Sep 17 00:00:00 2001 From: arunkapila Date: Mon, 3 Nov 2025 22:59:06 +0000 Subject: [PATCH 05/12] 1.17.0.0 - Added the Lock tool, which will prevent an object from being edited until it is unlocked. - This has no effect on actual gameplay, only edit mode, and can be used to lock things like large trigger zones that could get in the way of editing. - Added the Renderer Remover which disables the nearest object that has an attached renderer. --- WorldEditor/Architect/Architect.csproj | 4 +- .../Content/Elements/Custom/RoomObjects.cs | 5 +- WorldEditor/Architect/Objects/LockObject.cs | 46 ++++++++++++++++++ .../Architect/Objects/ObjectPlacement.cs | 27 +++++++--- .../Architect/Objects/PlaceableObject.cs | 3 +- .../Architect/Objects/PlacementManager.cs | 5 +- WorldEditor/Architect/Resources/lock.png | Bin 0 -> 11829 bytes WorldEditor/Architect/UI/EditorUIManager.cs | 22 ++++++--- WorldEditor/Architect/Util/EditorManager.cs | 1 + 9 files changed, 95 insertions(+), 18 deletions(-) create mode 100644 WorldEditor/Architect/Objects/LockObject.cs create mode 100644 WorldEditor/Architect/Resources/lock.png diff --git a/WorldEditor/Architect/Architect.csproj b/WorldEditor/Architect/Architect.csproj index de0e7e4..b8a72a3 100644 --- a/WorldEditor/Architect/Architect.csproj +++ b/WorldEditor/Architect/Architect.csproj @@ -8,8 +8,8 @@ Architect A mod to add platforms, enemies and more to change areas in the game with an in-game level editor. Copyright © Arun Kapila 2025 - 1.16.15.1 - 1.16.15.1 + 1.17.0.0 + 1.17.0.0 bin\$(Configuration)\ latest diff --git a/WorldEditor/Architect/Content/Elements/Custom/RoomObjects.cs b/WorldEditor/Architect/Content/Elements/Custom/RoomObjects.cs index 756e99c..a9b7b5e 100644 --- a/WorldEditor/Architect/Content/Elements/Custom/RoomObjects.cs +++ b/WorldEditor/Architect/Content/Elements/Custom/RoomObjects.cs @@ -84,6 +84,9 @@ public static void Initialize() CreateObjectRemover("door_remover", "Remove Transition", FindObjectsToDisable) .WithConfigGroup(ConfigGroup.Invisible) .WithReceiverGroup(ReceiverGroup.Invisible), + CreateObjectRemover("door_remover", "Remove Renderer", FindObjectsToDisable) + .WithConfigGroup(ConfigGroup.Invisible) + .WithReceiverGroup(ReceiverGroup.Invisible), CreateObjectRemover("enemy_remover", "Remove Enemy", FindObjectsToDisable) .WithConfigGroup(ConfigGroup.Invisible) .WithReceiverGroup(ReceiverGroup.Invisible), @@ -127,7 +130,7 @@ public static void Initialize() ContentPacks.RegisterPack(edits); } - private static Disabler[] FindObjectsToDisable(GameObject disabler) where T : UnityEngine.Behaviour + private static Disabler[] FindObjectsToDisable(GameObject disabler) where T : Component { var objects = disabler.scene.GetRootGameObjects() .Where(obj => !obj.name.StartsWith("[Architect] ")) diff --git a/WorldEditor/Architect/Objects/LockObject.cs b/WorldEditor/Architect/Objects/LockObject.cs new file mode 100644 index 0000000..10163d2 --- /dev/null +++ b/WorldEditor/Architect/Objects/LockObject.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using System.Linq; +using Architect.MultiplayerHook; +using Architect.Util; +using UnityEngine; + +namespace Architect.Objects; + +internal class LockObject : SelectableObject +{ + internal static readonly LockObject Instance = new(); + + private readonly Sprite _sprite; + + private LockObject() : base("Lock") + { + _sprite = PrepareSprite(); + } + + private static Sprite PrepareSprite() + { + return ResourceUtils.LoadInternal("lock"); + } + + public override void OnClickInWorld(Vector3 pos, bool first) + { + if (!first) return; + var placement = PlacementManager.FindClickedObject(pos, true); + placement?.ToggleLock(); + } + + public override bool IsFavourite() + { + return false; + } + + public override Sprite GetSprite() + { + return _sprite; + } + + public override int GetWeight() + { + return 0; + } +} \ No newline at end of file diff --git a/WorldEditor/Architect/Objects/ObjectPlacement.cs b/WorldEditor/Architect/Objects/ObjectPlacement.cs index 4a69df3..f593fd3 100644 --- a/WorldEditor/Architect/Objects/ObjectPlacement.cs +++ b/WorldEditor/Architect/Objects/ObjectPlacement.cs @@ -28,6 +28,7 @@ public class ObjectPlacement public readonly EventReceiver[] Receivers; public readonly float Rotation; public readonly float Scale; + public bool Locked; private Color _defaultColor; private bool _dragging; @@ -42,6 +43,7 @@ public ObjectPlacement( string name, Vector3 pos, bool flipped, + bool locked, float rotation, float scale, string id, @@ -52,6 +54,7 @@ public ObjectPlacement( Name = name; _pos = pos; Flipped = flipped; + Locked = locked; Scale = scale; _id = id; Rotation = rotation; @@ -99,6 +102,7 @@ public bool Touching(Vector2 pos) public bool IsWithinZone(Vector2 pos1, Vector2 pos2) { + if (Locked) return false; var withinX = (_pos.x > pos1.x && _pos.x < pos2.x) || (_pos.x < pos1.x && _pos.x > pos2.x); var withinY = (_pos.y > pos1.y && _pos.y < pos2.y) || (_pos.y < pos1.y && _pos.y > pos2.y); return withinX && withinY; @@ -162,7 +166,7 @@ internal void PlaceGhost() var r = 1f; var g = 1f; var b = 1f; - var a = 0.5f; + var a = Locked ? 0.2f : 0.5f; var renderer = _previewObject.AddComponent(); @@ -191,10 +195,6 @@ internal void PlaceGhost() { b = blue.GetValue(); } - else if (config.GetName() == "a" && config is FloatConfigValue alpha) - { - a *= Mathf.Max(0.15f, alpha.GetValue()); - } else if (config.GetName() == "layer" && config is IntConfigValue layer) { renderer.sortingOrder = layer.GetValue(); @@ -353,6 +353,9 @@ private static void WritePlacementInfo(JsonWriter writer, ObjectPlacement placem writer.WritePropertyName("flipped"); writer.WriteValue(placement.Flipped); + writer.WritePropertyName("locked"); + writer.WriteValue(placement.Locked); + if (placement.Rotation != 0) { writer.WritePropertyName("rotation"); @@ -376,6 +379,7 @@ public override ObjectPlacement ReadJson(JsonReader reader, Type objectType, Obj string id = null; var pos = Vector3.zero; var flipped = true; + var locked = false; var rotation = 0f; var scale = 1f; @@ -413,6 +417,10 @@ public override ObjectPlacement ReadJson(JsonReader reader, Type objectType, Obj reader.ReadAsBoolean(); flipped = (bool)reader.Value; break; + case "locked": + reader.ReadAsBoolean(); + locked = (bool)reader.Value; + break; case "rotation": reader.ReadAsDouble(); rotation = (float)(double)reader.Value; @@ -450,7 +458,7 @@ public override ObjectPlacement ReadJson(JsonReader reader, Type objectType, Obj name = Updater.UpdateObject(name); id ??= Guid.NewGuid().ToString().Substring(0, 8); var placement = - new ObjectPlacement(name, pos, flipped, rotation, scale, id, broadcasters, receivers, config); + new ObjectPlacement(name, pos, flipped, locked, rotation, scale, id, broadcasters, receivers, config); return placement; } @@ -482,4 +490,11 @@ private static ConfigValue[] DeserializeConfig(Dictionary data) return config; } } + + public void ToggleLock() + { + Locked = !Locked; + _defaultColor.a = Locked ? 0.2f : 0.5f; + if (_previewObject) _previewObject.GetComponent().color = _defaultColor; + } } \ No newline at end of file diff --git a/WorldEditor/Architect/Objects/PlaceableObject.cs b/WorldEditor/Architect/Objects/PlaceableObject.cs index d99ccf6..452b59d 100644 --- a/WorldEditor/Architect/Objects/PlaceableObject.cs +++ b/WorldEditor/Architect/Objects/PlaceableObject.cs @@ -37,7 +37,7 @@ public PlaceableObject(AbstractPackElement element) : base(element.GetName()) public override void OnClickInWorld(Vector3 pos, bool first) { if (!first) return; - + var placement = MakePlacement(pos); PlacementManager.GetCurrentPlacements().Add(placement); @@ -61,6 +61,7 @@ public ObjectPlacement MakePlacement(Vector3 pos = default) GetName(), pos, EditorManager.IsFlipped, + false, EditorManager.Rotation, EditorManager.Scale, Guid.NewGuid().ToString().Substring(0, 8), diff --git a/WorldEditor/Architect/Objects/PlacementManager.cs b/WorldEditor/Architect/Objects/PlacementManager.cs index 2f29da6..07eb69b 100644 --- a/WorldEditor/Architect/Objects/PlacementManager.cs +++ b/WorldEditor/Architect/Objects/PlacementManager.cs @@ -59,8 +59,9 @@ internal static void Initialize() } [CanBeNull] - public static ObjectPlacement FindClickedObject(Vector3 mousePos) + public static ObjectPlacement FindClickedObject(Vector3 mousePos, bool ignoreLock = false) { - return GetCurrentPlacements().FirstOrDefault(placement => placement.Touching(mousePos)); + return GetCurrentPlacements().FirstOrDefault(placement => (ignoreLock || !placement.Locked) + && placement.Touching(mousePos)); } } \ No newline at end of file diff --git a/WorldEditor/Architect/Resources/lock.png b/WorldEditor/Architect/Resources/lock.png new file mode 100644 index 0000000000000000000000000000000000000000..391e1b705a8f175a65240ee17d7faab79fad7e6d GIT binary patch literal 11829 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 literal 0 HcmV?d00001 diff --git a/WorldEditor/Architect/UI/EditorUIManager.cs b/WorldEditor/Architect/UI/EditorUIManager.cs index 1173f0c..f0c3913 100644 --- a/WorldEditor/Architect/UI/EditorUIManager.cs +++ b/WorldEditor/Architect/UI/EditorUIManager.cs @@ -202,6 +202,9 @@ private static void UpdateSelectedItem() case -5: SelectedItem = ResetObject.Instance; break; + case -6: + SelectedItem = LockObject.Instance; + break; default: { var index = _groupIndex * ItemsPerGroup + _index; @@ -805,10 +808,11 @@ private static void SetupTools(LayoutRoot layout) (int)(baseToolsPaddingY * _uiScaleFactor)), ColumnDefinitions = { - new GridDimension((int)(toolSpacing * _uiScaleFactor), GridUnit.AbsoluteMin), - new GridDimension((int)(toolSpacing * _uiScaleFactor), GridUnit.AbsoluteMin), - new GridDimension((int)(toolSpacing * _uiScaleFactor), GridUnit.AbsoluteMin), - new GridDimension((int)(toolSpacing * _uiScaleFactor), GridUnit.AbsoluteMin) + new GridDimension(1, GridUnit.Proportional), + new GridDimension(1, GridUnit.Proportional), + new GridDimension(1, GridUnit.Proportional), + new GridDimension(1, GridUnit.Proportional), + new GridDimension(1, GridUnit.Proportional) }, RowDefinitions = { @@ -826,7 +830,7 @@ private static void SetupTools(LayoutRoot layout) VerticalAlignment = VerticalAlignment.Bottom, HorizontalAlignment = HorizontalAlignment.Center, Content = "Reshare Level (HKMP)" - }.WithProp(GridLayout.Column, 0).WithProp(GridLayout.ColumnSpan, 4).WithProp(GridLayout.Row, correctRow); + }.WithProp(GridLayout.Column, 0).WithProp(GridLayout.ColumnSpan, 5).WithProp(GridLayout.Row, correctRow); multiplayerRefresh.Click += _ => { HkmpHook.Refresh(); }; extraSettings.Children.Add(multiplayerRefresh); correctRow++; @@ -858,6 +862,12 @@ private static void SetupTools(LayoutRoot layout) extraSettings.Children.Add(eraserImagedButton.Item1); extraSettings.Children.Add(eraserImagedButton.Item2); + var lockImagedButton = CreateImagedButton(layout, LockObject.Instance.GetSprite(), "Lock", 0, 0, -6); + lockImagedButton.Item1.WithProp(GridLayout.Column, 4).WithProp(GridLayout.Row, correctRow); + lockImagedButton.Item2.WithProp(GridLayout.Column, 4).WithProp(GridLayout.Row, correctRow); + extraSettings.Children.Add(lockImagedButton.Item1); + extraSettings.Children.Add(lockImagedButton.Item2); + PauseOptions.Add(extraSettings); } @@ -1391,4 +1401,4 @@ private enum ConfigMode Broadcasters, Receivers } -} \ No newline at end of file +} diff --git a/WorldEditor/Architect/Util/EditorManager.cs b/WorldEditor/Architect/Util/EditorManager.cs index bd030b7..0b8e94c 100644 --- a/WorldEditor/Architect/Util/EditorManager.cs +++ b/WorldEditor/Architect/Util/EditorManager.cs @@ -496,6 +496,7 @@ public static void DoPaste() pl.Name, wp - obj.Offset, pl.Flipped, + false, pl.Rotation, pl.Scale, converts[pl.GetId()], From c56aa67b604b593afdb01df2350593642b8adb02 Mon Sep 17 00:00:00 2001 From: arunkapila Date: Wed, 12 Nov 2025 11:04:59 +0000 Subject: [PATCH 06/12] 1.18.0.0 - Added the Tilemap Changer. --- WorldEditor.sln.DotSettings.user | 2 + WorldEditor/Architect/Architect.csproj | 4 +- .../Architect/Category/PrefabsCategory.cs | 6 +- .../Architect/MultiplayerHook/HkmpHook.cs | 6 ++ .../MultiplayerHook/Packets/PacketId.cs | 1 + .../MultiplayerHook/Packets/TilePacketData.cs | 38 +++++++++ .../MultiplayerHook/WeClientAddon.cs | 50 +++++++++-- .../MultiplayerHook/WeServerAddon.cs | 16 +++- .../Architect/Objects/PlacementManager.cs | 26 +++++- WorldEditor/Architect/Objects/ResetObject.cs | 18 +++- .../Architect/Objects/TilemapObject.cs | 79 ++++++++++++++++++ WorldEditor/Architect/Resources/tilemap.png | Bin 0 -> 7872 bytes WorldEditor/Architect/Storage/LevelData.cs | 62 ++++++++++++++ .../Architect/Storage/SceneSaveLoader.cs | 59 +++++++++---- WorldEditor/Architect/UI/EditorUIManager.cs | 12 ++- WorldEditor/Architect/Util/EditorManager.cs | 10 ++- WorldEditor/Architect/Util/UndoManager.cs | 19 +++++ 17 files changed, 371 insertions(+), 37 deletions(-) create mode 100644 WorldEditor/Architect/MultiplayerHook/Packets/TilePacketData.cs create mode 100644 WorldEditor/Architect/Objects/TilemapObject.cs create mode 100644 WorldEditor/Architect/Resources/tilemap.png create mode 100644 WorldEditor/Architect/Storage/LevelData.cs diff --git a/WorldEditor.sln.DotSettings.user b/WorldEditor.sln.DotSettings.user index b7eee8e..bd2566a 100644 --- a/WorldEditor.sln.DotSettings.user +++ b/WorldEditor.sln.DotSettings.user @@ -73,6 +73,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -91,6 +92,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded diff --git a/WorldEditor/Architect/Architect.csproj b/WorldEditor/Architect/Architect.csproj index b8a72a3..4fe6fbc 100644 --- a/WorldEditor/Architect/Architect.csproj +++ b/WorldEditor/Architect/Architect.csproj @@ -8,8 +8,8 @@ Architect A mod to add platforms, enemies and more to change areas in the game with an in-game level editor. Copyright © Arun Kapila 2025 - 1.17.0.0 - 1.17.0.0 + 1.18.0.0 + 1.18.0.0 bin\$(Configuration)\ latest diff --git a/WorldEditor/Architect/Category/PrefabsCategory.cs b/WorldEditor/Architect/Category/PrefabsCategory.cs index d1fd7e5..f9d1f67 100644 --- a/WorldEditor/Architect/Category/PrefabsCategory.cs +++ b/WorldEditor/Architect/Category/PrefabsCategory.cs @@ -12,7 +12,7 @@ internal class PrefabsCategory : ObjectCategory public PrefabsCategory() : base("Prefabs") { - foreach (var obj in SceneSaveLoader.Load("Prefabs")) + foreach (var obj in SceneSaveLoader.Load("Prefabs").Placements) { Prefabs.Add(new PrefabObject(obj)); Objects.Add(obj); @@ -26,7 +26,7 @@ public static void TryAddPrefab() var placement = placeable.MakePlacement(); Objects.Add(placement); - SceneSaveLoader.Save("Prefabs", Objects); + SceneSaveLoader.Save("Prefabs", new LevelData(Objects, [])); Prefabs.Add(new PrefabObject(placement)); @@ -38,7 +38,7 @@ public static void RemovePrefab(PrefabObject obj) { Prefabs.Remove(obj); Objects.RemoveAll(placement => placement.GetId() == obj.GetId()); - SceneSaveLoader.Save("Prefabs", Objects); + SceneSaveLoader.Save("Prefabs", new LevelData(Objects, [])); EditorUIManager.RefreshObjects(); EditorUIManager.RefreshButtons(); diff --git a/WorldEditor/Architect/MultiplayerHook/HkmpHook.cs b/WorldEditor/Architect/MultiplayerHook/HkmpHook.cs index 5594658..ef61f16 100644 --- a/WorldEditor/Architect/MultiplayerHook/HkmpHook.cs +++ b/WorldEditor/Architect/MultiplayerHook/HkmpHook.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Threading.Tasks; using Architect.Objects; using Hkmp.Api.Client; @@ -37,6 +38,11 @@ public static void Erase(string guid, string scene) _clientAddon.Erase(guid, scene); } + public static void TilemapChange(string scene, List<(int, int)> changes, bool empty) + { + _clientAddon.TilemapChange(scene, changes, empty); + } + public static void ClearRoom(string scene) { _clientAddon.ClearRoom(scene); diff --git a/WorldEditor/Architect/MultiplayerHook/Packets/PacketId.cs b/WorldEditor/Architect/MultiplayerHook/Packets/PacketId.cs index 4b7f0c8..90eb57a 100644 --- a/WorldEditor/Architect/MultiplayerHook/Packets/PacketId.cs +++ b/WorldEditor/Architect/MultiplayerHook/Packets/PacketId.cs @@ -5,6 +5,7 @@ public enum PacketId Refresh, Edit, Erase, + Tile, Update, Win, Relay, diff --git a/WorldEditor/Architect/MultiplayerHook/Packets/TilePacketData.cs b/WorldEditor/Architect/MultiplayerHook/Packets/TilePacketData.cs new file mode 100644 index 0000000..ded5093 --- /dev/null +++ b/WorldEditor/Architect/MultiplayerHook/Packets/TilePacketData.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using Hkmp.Networking.Packet; + +namespace Architect.MultiplayerHook.Packets; + +public class TilePacketData : IPacketData +{ + public List<(int, int)> Tiles; + public bool Empty; + public string SceneName; + + public void WriteData(IPacket packet) + { + packet.Write(SceneName); + packet.Write(Empty); + packet.Write(Tiles.Count); + foreach (var r in Tiles) + { + packet.Write(r.Item1); + packet.Write(r.Item2); + } + } + + public void ReadData(IPacket packet) + { + SceneName = packet.ReadString(); + Empty = packet.ReadBool(); + Tiles = []; + var count = packet.ReadInt(); + for (var i = 0; i < count; i++) + { + Tiles.Add((packet.ReadInt(), packet.ReadInt())); + } + } + + public bool IsReliable => true; + public bool DropReliableDataIfNewerExists => false; +} \ No newline at end of file diff --git a/WorldEditor/Architect/MultiplayerHook/WeClientAddon.cs b/WorldEditor/Architect/MultiplayerHook/WeClientAddon.cs index c403cee..642352e 100644 --- a/WorldEditor/Architect/MultiplayerHook/WeClientAddon.cs +++ b/WorldEditor/Architect/MultiplayerHook/WeClientAddon.cs @@ -97,8 +97,7 @@ public override void Initialize(IClientApi clientApi) } else { - SceneSaveLoader.ScheduleEdit(packet.SceneName, - edit); // This will store it in a list to be added when the scene is next loaded, or saved when the game is next closed + SceneSaveLoader.ScheduleEdit(packet.SceneName, edit); } }); @@ -123,8 +122,33 @@ public override void Initialize(IClientApi clientApi) } else { - SceneSaveLoader.ScheduleErase(packet.SceneName, - packet.Id); // This will store it in a list to be added when the scene is next loaded, or saved when the game is next closed + SceneSaveLoader.ScheduleErase(packet.SceneName, packet.Id); + } + }); + + netReceiver.RegisterPacketHandler(PacketId.Tile, packet => + { + if (!Architect.GlobalSettings.CollaborationMode) return; + Logger.Info("Receiving Tile Change Packet [CLIENT]"); + + if (packet.SceneName == GameManager.instance.sceneName) + { + var map = PlacementManager.GetTilemap(); + foreach (var (x, y) in packet.Tiles) + { + PlacementManager.GetCurrentLevel().ToggleTile((x, y)); + if (EditorManager.IsEditing) + { + if (!map) continue; + if (packet.Empty) map.ClearTile(x, y, 0); + else map.SetTile(x, y, 0, 0); + } + } + if (map) map.Build(); + } + else + { + SceneSaveLoader.ScheduleTileChange(packet.SceneName, packet.Tiles); } }); @@ -192,6 +216,21 @@ public void Erase(string guid, string scene) }); } + public void TilemapChange(string scene, List<(int, int)> changes, bool empty) + { + if (!_api.NetClient.IsConnected) return; + + Logger.Info("Sending Tile Change Packet"); + + _api.NetClient.GetNetworkSender(this) + .SendSingleData(PacketId.Tile, new TilePacketData + { + SceneName = scene, + Tiles = changes, + Empty = empty + }); + } + public void ClearRoom(string scene) { if (!_api.NetClient.IsConnected) return; @@ -242,7 +281,7 @@ public async void Refresh() Logger.Info("Sending Refresh Packets"); var scene = GameManager.instance.sceneName; - var json = SceneSaveLoader.SerializeSceneData(PlacementManager.GetCurrentPlacements()); + var json = SceneSaveLoader.SerializeSceneData(PlacementManager.GetCurrentLevel()); var bytes = Split(ZipUtils.Zip(json), SplitSize); @@ -300,6 +339,7 @@ public IPacketData InstantiatePacket(PacketId packetId) PacketId.Win => new WinPacketData(), PacketId.Edit => new EditPacketData(), PacketId.Erase => new ErasePacketData(), + PacketId.Tile => new TilePacketData(), PacketId.Update => new UpdatePacketData(), PacketId.Relay => new RelayPacketData(), PacketId.Clear => new ClearPacketData(), diff --git a/WorldEditor/Architect/MultiplayerHook/WeServerAddon.cs b/WorldEditor/Architect/MultiplayerHook/WeServerAddon.cs index ce14cab..9abf992 100644 --- a/WorldEditor/Architect/MultiplayerHook/WeServerAddon.cs +++ b/WorldEditor/Architect/MultiplayerHook/WeServerAddon.cs @@ -20,6 +20,7 @@ public IPacketData InstantiatePacket(PacketId packetId) PacketId.Win => new WinPacketData(), PacketId.Edit => new EditPacketData(), PacketId.Erase => new ErasePacketData(), + PacketId.Tile => new TilePacketData(), PacketId.Update => new UpdatePacketData(), PacketId.Relay => new RelayPacketData(), PacketId.Clear => new ClearPacketData(), @@ -54,6 +55,16 @@ public override void Initialize(IServerApi serverApi) ); }); + netReceiver.RegisterPacketHandler(PacketId.Tile, (id, packet) => + { + Logger.Info("Receiving Tile Change Packet [SERVER]"); + sender.SendSingleData(PacketId.Tile, packet, serverApi.ServerManager.Players + .Where(player => player.Id != id) + .Select(player => player.Id) + .ToArray() + ); + }); + netReceiver.RegisterPacketHandler(PacketId.Clear, (id, packet) => { Logger.Info("Receiving Clear Packet [SERVER]"); @@ -88,7 +99,10 @@ public override void Initialize(IServerApi serverApi) { Logger.Info("Receiving Win Packet [SERVER]"); sender.SendSingleData(PacketId.Win, packet, serverApi.ServerManager.Players - .Select(player => player.Id).ToArray()); + .Where(player => player.Id != id) + .Select(player => player.Id) + .ToArray() + ); }); netReceiver.RegisterPacketHandler(PacketId.Relay, (id, packet) => diff --git a/WorldEditor/Architect/Objects/PlacementManager.cs b/WorldEditor/Architect/Objects/PlacementManager.cs index 07eb69b..eae1baf 100644 --- a/WorldEditor/Architect/Objects/PlacementManager.cs +++ b/WorldEditor/Architect/Objects/PlacementManager.cs @@ -12,11 +12,23 @@ namespace Architect.Objects; public static class PlacementManager { private static string _sceneName; - private static List _currentPlacements; + private static LevelData _currentPlacements; + private static tk2dTileMap _tileMap; public static readonly Dictionary Objects = []; public static List GetCurrentPlacements() + { + return GetCurrentLevel().Placements; + } + + public static tk2dTileMap GetTilemap() + { + if (!_tileMap) _tileMap = Object.FindObjectOfType(); + return _tileMap; + } + + public static LevelData GetCurrentLevel() { var sceneName = GameManager.instance.sceneName; if (_sceneName == sceneName) return _currentPlacements; @@ -37,10 +49,20 @@ private static void LoadPlacements() { Objects.Clear(); CustomObjects.PlayerListeners.Clear(); - foreach (var placement in GetCurrentPlacements().Where(placement => placement.GetPlaceableObject() != null)) + var ld = GetCurrentLevel(); + foreach (var placement in ld.Placements.Where(placement => placement.GetPlaceableObject() != null)) if (EditorManager.IsEditing) placement.PlaceGhost(); else Objects[placement.GetId()] = placement.SpawnObject(); + + var map = GetTilemap(); + if (!map) return; + foreach (var (x, y) in ld.TileChanges) + { + if (map.GetTile(x, y, 0) == -1) map.SetTile(x, y, 0, 0); + else map.ClearTile(x, y, 0); + } + map.Build(); } internal static void Initialize() diff --git a/WorldEditor/Architect/Objects/ResetObject.cs b/WorldEditor/Architect/Objects/ResetObject.cs index ac3ccb1..6a2a123 100644 --- a/WorldEditor/Architect/Objects/ResetObject.cs +++ b/WorldEditor/Architect/Objects/ResetObject.cs @@ -78,13 +78,27 @@ public static void ResetRoom(string sceneName) { if (sceneName == GameManager.instance.sceneName) { - var placements = PlacementManager.GetCurrentPlacements(); + var level = PlacementManager.GetCurrentLevel(); + var placements = level.Placements; while (placements.Count > 0) placements[0].Destroy(); + + var map = PlacementManager.GetTilemap(); + if (map) + { + foreach (var (x, y) in level.TileChanges) + { + if (map.GetTile(x, y, 0) == -1) map.SetTile(x, y, 0, 0); + else map.ClearTile(x, y, 0); + } + map.Build(); + } + level.TileChanges.Clear(); + UndoManager.ClearHistory(); } else { - SceneSaveLoader.SaveScene(sceneName, []); + SceneSaveLoader.SaveScene(sceneName, new LevelData([], [])); } } } \ No newline at end of file diff --git a/WorldEditor/Architect/Objects/TilemapObject.cs b/WorldEditor/Architect/Objects/TilemapObject.cs new file mode 100644 index 0000000..5477506 --- /dev/null +++ b/WorldEditor/Architect/Objects/TilemapObject.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; +using System.Linq; +using Architect.MultiplayerHook; +using Architect.Util; +using UnityEngine; + +namespace Architect.Objects; + +internal class TilemapObject : SelectableObject +{ + internal static readonly TilemapObject Instance = new(); + + private static readonly List<(int, int)> TileFlips = []; + + private static (int, int) _lastPos = (-1, -1); + private static bool _lastEmpty; + + private readonly Sprite _sprite; + + private TilemapObject() : base("Tilemap Editor") + { + _sprite = PrepareSprite(); + } + + private static Sprite PrepareSprite() + { + return ResourceUtils.LoadInternal("tilemap"); + } + + public override void OnClickInWorld(Vector3 pos, bool first) + { + var map = PlacementManager.GetTilemap(); + if (!map || !map.GetTileAtPosition(pos, out var x, out var y)) return; + + var xyPos = (x, y); + if (_lastPos == xyPos && !first) return; + _lastPos = xyPos; + + var empty = map.GetTile(x, y, 0) == -1; + if (first) _lastEmpty = empty; + else if (_lastEmpty != empty) return; + + if (empty) map.SetTile(x, y, 0, 0); + else map.ClearTile(x, y, 0); + map.Build(); + + PlacementManager.GetCurrentLevel().ToggleTile(xyPos); + + TileFlips.Add(xyPos); + } + + public static void Release() + { + if (TileFlips.Count == 0) return; + UndoManager.PerformAction(new ToggleTile(TileFlips.ToList(), _lastEmpty)); + + if (Architect.UsingMultiplayer && Architect.GlobalSettings.CollaborationMode) + { + HkmpHook.TilemapChange(GameManager.instance.sceneName, TileFlips, _lastEmpty); + } + + TileFlips.Clear(); + } + + public override bool IsFavourite() + { + return false; + } + + public override Sprite GetSprite() + { + return _sprite; + } + + public override int GetWeight() + { + return 0; + } +} \ No newline at end of file diff --git a/WorldEditor/Architect/Resources/tilemap.png b/WorldEditor/Architect/Resources/tilemap.png new file mode 100644 index 0000000000000000000000000000000000000000..f4f4565aaf25d7cc3be18d35f003b16112d2d9ab GIT binary patch literal 7872 zcmeHMYgAKL7Cw0hkvHNDj3~rdp*olwqNrdVpa>$*g0?s+gkr!fA&C%6us{;E1B${> zET}MswYr!Nl~>!Dse@pyV@qsdh_glot&m9MQ5*~rF+2j9lY6zg`uIDumMb?u?mlO~ z&ffRjv%lIg5nb;K83BB&$1q^*L#SzS{D7s(XtN|q$Y#)PDcvgv6DMDOzH z(rmF+0I;MXkj&;w(x}qxEMabllug4VLLhCOMrc$_L~@8tOO8#TZWHD3sT=7GI)lb> zpi-%S{f<`aJcbJ9>{8yR4lfN$qBj@@CN6-momV{DF-NEz1X zm0VeryY|*yhx=M>>bPlclfJ%a=Hr!}bJGIzt{M=`YvLOf3u# z4?p>;I*-O!EiP#3bet&~t~Wp9q{h!*^P#zs%PjPZ`AE5D+CrdQ?!m&WdkX~zvv&Jo zIdLO9$-zTBv}G_Wop1ALSVF^4Knb-e*s_G}dfV2py$`li!**1@X7xe0=Q8)`fWg%A z`0?Wb_UvL8vLCr~%aNdgrqYv<%H=M_mqs*7OW@Vc>hmM)ZMqZa*T(sK`)lRdGLsQ+U@(9CFMzqG-1y{oTpjoJ5lx9`aLGnB^smOHE~?kR!qKvD(C z!M)E$?Z|i9_bzy#b}4S|7?42|QILFU`W%KlvO-bN7y1ot2?k;6<%HIy#y|{<^g+ba zs`zmG8(4{shPY(SU^-UETM2MLT3xyWJFcrBkV*VXjiVZp71iwgbp>WS0M_{Wl5*Us z9W4INX9H-M?PLg*qTPKxaNGppkN2Bqt396>8;=qA#diCp@s%jLd)>zdA-?H9HNxJh+oCYW?T6inQiF?3zsaTe`t8XPjm0Zf-=&?FD7We&$@DTq zukg#cip+)C`+Dt1A~m;KHvfA|w9ellX1*}rJejAj=X4jWQmg$eYR05GKVuc~(7>f2 zjqt=f-M@llOOU2{x{BRKPy`vOEaX;&XZQ;o!2ne>jvoeIyxm~KCgJlE9HXq^fq?A= zcpG9P6?S;)4*bu7(H-+^%O^2LqrUfjE5(|Wn~v~t5t&fuehWrl}pW38_} zOEFSWN%Vw>kIm7KJN~Xbg{z7Oet3daN|497GIyMsj2DmNF6wdo<)rv(EQwa)06M!3fNQj@0WzSZCW6Df-Otfi)Pyz zw)eqyYS@m-*Q`EF1l;#!a|>sm-tO=3zY@v4ra(`i55$*Ydj|3MVRN2*SL%r@z}Ah- zPigW}sL3g(uMefGlT@+-rFzTEd*;I6DRV>1cmz+SRbANKcgAy)G1oeReIEjf$)yuY z(DTJ6Z4llYSf_PcEk3{T8=9|n1r99{(ou{-L9m4Ln9BA x0gNKaSp34NtIHLs+feOaRS~XNOD&FO&vXk~R&=ke!TNzWYI|IG^H$!m{{lTkU5@|& literal 0 HcmV?d00001 diff --git a/WorldEditor/Architect/Storage/LevelData.cs b/WorldEditor/Architect/Storage/LevelData.cs new file mode 100644 index 0000000..0ed799f --- /dev/null +++ b/WorldEditor/Architect/Storage/LevelData.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using Architect.Objects; +using Newtonsoft.Json; + +namespace Architect.Storage; + +[JsonConverter(typeof(LevelDataConverter))] +public class LevelData(List placements, List<(int, int)> tileChanges) +{ + public readonly List Placements = placements; + public readonly List<(int, int)> TileChanges = tileChanges; + + public void ToggleTile((int, int) pos) + { + if (!TileChanges.Remove(pos)) TileChanges.Add(pos); + } + + public class LevelDataConverter : JsonConverter + { + public override void WriteJson(JsonWriter writer, LevelData value, JsonSerializer serializer) + { + writer.WriteStartObject(); + writer.WritePropertyName("placements"); + serializer.Serialize(writer, value.Placements); + + writer.WritePropertyName("tilemap"); + serializer.Serialize(writer, value.TileChanges); + writer.WriteEndObject(); + } + + public override LevelData ReadJson(JsonReader reader, Type objectType, LevelData existingValue, bool hasExistingValue, + JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.StartArray) + { + return new LevelData(serializer.Deserialize>(reader), []); + } + + List placements = []; + List<(int, int)> tiles = []; + + while (reader.Read()) + { + if (reader.Value is not string value) break; + switch (value) + { + case "placements": + reader.Read(); + placements = serializer.Deserialize>(reader); + break; + case "tilemap": + reader.Read(); + tiles = serializer.Deserialize>(reader); + break; + } + } + + return new LevelData(placements, tiles); + } + } +} \ No newline at end of file diff --git a/WorldEditor/Architect/Storage/SceneSaveLoader.cs b/WorldEditor/Architect/Storage/SceneSaveLoader.cs index b0a6ee2..105da54 100644 --- a/WorldEditor/Architect/Storage/SceneSaveLoader.cs +++ b/WorldEditor/Architect/Storage/SceneSaveLoader.cs @@ -21,6 +21,7 @@ public static class SceneSaveLoader public static string DataPath; private static readonly Dictionary> ScheduledErases = new(); + private static readonly Dictionary> ScheduledTileChanges = new(); private static readonly Dictionary> ScheduledUpdates = new(); private static readonly Dictionary> ScheduledEdits = new(); @@ -34,53 +35,69 @@ internal static void Initialize() List scenes = []; scenes.AddRange(ScheduledErases.Keys); + scenes.AddRange(ScheduledTileChanges.Keys); scenes.AddRange(ScheduledEdits.Keys); scenes.AddRange(ScheduledUpdates.Keys); foreach (var scene in scenes) { - var placements = LoadScene(scene); + var levelData = LoadScene(scene); if (ScheduledErases.TryGetValue(scene, out var erases)) - placements.RemoveAll(obj => erases.Contains(obj.GetId())); - if (ScheduledEdits.TryGetValue(scene, out var edits)) placements.AddRange(edits); + levelData.Placements.RemoveAll(obj => erases.Contains(obj.GetId())); + if (ScheduledEdits.TryGetValue(scene, out var edits)) levelData.Placements.AddRange(edits); + if (ScheduledTileChanges.TryGetValue(scene, out var tileChanges)) + { + foreach (var tile in tileChanges) levelData.ToggleTile(tile); + } if (ScheduledUpdates.TryGetValue(scene, out var updates)) foreach (var pair in updates) - placements.First(obj => obj.GetId() == pair.Item1).Move(pair.Item2); - SaveScene(scene, placements); + levelData.Placements.First(obj => obj.GetId() == pair.Item1).Move(pair.Item2); + SaveScene(scene, levelData); } + ScheduledTileChanges.Clear(); + ScheduledErases.Clear(); + ScheduledEdits.Clear(); + ScheduledUpdates.Clear(); + orig(self); }; } - public static List LoadScene(string name) + public static LevelData LoadScene(string name) { return Load("Architect/" + name); } - public static List Load(string name) + public static LevelData Load(string name) { var path = DataPath + name + ".architect.json"; - if (!File.Exists(path)) return []; + if (!File.Exists(path)) return new LevelData([], []); var content = File.ReadAllText(path); var data = DeserializeSceneData(content); if (ScheduledErases.TryGetValue(name, out var erase)) { - data.RemoveAll(obj => erase.Contains(obj.GetId())); + data.Placements.RemoveAll(obj => erase.Contains(obj.GetId())); ScheduledErases.Remove(name); } if (ScheduledEdits.TryGetValue(name, out var edit)) { - data.AddRange(edit); - ScheduledErases.Remove(name); + data.Placements.AddRange(edit); + ScheduledEdits.Remove(name); + } + + if (ScheduledTileChanges.TryGetValue(name, out var tileChanges)) + { + foreach (var tile in tileChanges) data.ToggleTile(tile); + ScheduledTileChanges.Remove(name); } if (ScheduledUpdates.TryGetValue(name, out var scheduledUpdate)) { - foreach (var update in scheduledUpdate) data.First(obj => obj.GetId() == update.Item1).Move(update.Item2); + foreach (var update in scheduledUpdate) data.Placements.First(obj => obj.GetId() == update.Item1).Move(update.Item2); ScheduledUpdates.Remove(name); } @@ -88,12 +105,12 @@ public static List Load(string name) return data; } - public static void SaveScene(string name, List placements) + public static void SaveScene(string name, LevelData placements) { Save("Architect/" + name, placements); } - public static void Save(string name, List placements) + public static void Save(string name, LevelData placements) { var data = SerializeSceneData(placements); Save(name, data); @@ -124,12 +141,12 @@ public static void WipeAllScenes() foreach (var file in Directory.GetFiles(DataPath + "Architect/")) File.Delete(file); } - public static List DeserializeSceneData(string data) + public static LevelData DeserializeSceneData(string data) { - return JsonConvert.DeserializeObject>(data); + return JsonConvert.DeserializeObject(data); } - public static string SerializeSceneData(List placements) + public static string SerializeSceneData(LevelData placements) { return JsonConvert.SerializeObject(placements, Opc, @@ -178,7 +195,7 @@ public static IEnumerator LoadAllScenes(Dictionary placements) private static async Task LoadEdits(string data) { var passed = true; - foreach (var config in DeserializeSceneData(data).Select(obj => obj.Config + foreach (var config in DeserializeSceneData(data).Placements.Select(obj => obj.Config .ToDictionary(conf => conf.GetName()))) { if (!await TryDownload(config, "Source URL", CustomAssetLoader.GetSpritePath)) passed = false; @@ -209,6 +226,12 @@ public static void ScheduleErase(string scene, string id) ScheduledErases[scene].Add(id); } + public static void ScheduleTileChange(string scene, List<(int, int)> tiles) + { + if (!ScheduledTileChanges.ContainsKey(scene)) ScheduledTileChanges[scene] = []; + ScheduledTileChanges[scene].AddRange(tiles); + } + public static void ScheduleEdit(string scene, ObjectPlacement placement) { if (!ScheduledEdits.ContainsKey(scene)) ScheduledEdits[scene] = []; diff --git a/WorldEditor/Architect/UI/EditorUIManager.cs b/WorldEditor/Architect/UI/EditorUIManager.cs index f0c3913..a313331 100644 --- a/WorldEditor/Architect/UI/EditorUIManager.cs +++ b/WorldEditor/Architect/UI/EditorUIManager.cs @@ -205,6 +205,9 @@ private static void UpdateSelectedItem() case -6: SelectedItem = LockObject.Instance; break; + case -7: + SelectedItem = TilemapObject.Instance; + break; default: { var index = _groupIndex * ItemsPerGroup + _index; @@ -812,6 +815,7 @@ private static void SetupTools(LayoutRoot layout) new GridDimension(1, GridUnit.Proportional), new GridDimension(1, GridUnit.Proportional), new GridDimension(1, GridUnit.Proportional), + new GridDimension(1, GridUnit.Proportional), new GridDimension(1, GridUnit.Proportional) }, RowDefinitions = @@ -830,7 +834,7 @@ private static void SetupTools(LayoutRoot layout) VerticalAlignment = VerticalAlignment.Bottom, HorizontalAlignment = HorizontalAlignment.Center, Content = "Reshare Level (HKMP)" - }.WithProp(GridLayout.Column, 0).WithProp(GridLayout.ColumnSpan, 5).WithProp(GridLayout.Row, correctRow); + }.WithProp(GridLayout.Column, 0).WithProp(GridLayout.ColumnSpan, 6).WithProp(GridLayout.Row, correctRow); multiplayerRefresh.Click += _ => { HkmpHook.Refresh(); }; extraSettings.Children.Add(multiplayerRefresh); correctRow++; @@ -868,6 +872,12 @@ private static void SetupTools(LayoutRoot layout) extraSettings.Children.Add(lockImagedButton.Item1); extraSettings.Children.Add(lockImagedButton.Item2); + var tileImagedButton = CreateImagedButton(layout, TilemapObject.Instance.GetSprite(), "Tilemap Editor", 0, 0, -7); + tileImagedButton.Item1.WithProp(GridLayout.Column, 5).WithProp(GridLayout.Row, correctRow); + tileImagedButton.Item2.WithProp(GridLayout.Column, 5).WithProp(GridLayout.Row, correctRow); + extraSettings.Children.Add(tileImagedButton.Item1); + extraSettings.Children.Add(tileImagedButton.Item2); + PauseOptions.Add(extraSettings); } diff --git a/WorldEditor/Architect/Util/EditorManager.cs b/WorldEditor/Architect/Util/EditorManager.cs index 0b8e94c..25a4510 100644 --- a/WorldEditor/Architect/Util/EditorManager.cs +++ b/WorldEditor/Architect/Util/EditorManager.cs @@ -66,7 +66,7 @@ public static void Initialize() On.GameManager.SaveGame += (orig, self) => { - if (IsEditing) SceneSaveLoader.SaveScene(self.sceneName, PlacementManager.GetCurrentPlacements()); + if (IsEditing) SceneSaveLoader.SaveScene(self.sceneName, PlacementManager.GetCurrentLevel()); orig(self); }; @@ -331,7 +331,7 @@ private static void CheckToggle(bool paused) if (HeroController.instance.controlReqlinquished) return; if (IsEditing) - SceneSaveLoader.SaveScene(GameManager.instance.sceneName, PlacementManager.GetCurrentPlacements()); + SceneSaveLoader.SaveScene(GameManager.instance.sceneName, PlacementManager.GetCurrentLevel()); else _freeMovePos = HeroController.instance.transform.position; IsEditing = !IsEditing; @@ -366,7 +366,11 @@ private static void TryPlace() var b1 = Input.GetMouseButtonDown(0); var b2 = Input.GetMouseButton(0); - if (!b2) ResetObject.RestartDelay(); + if (!b2) + { + ResetObject.RestartDelay(); + TilemapObject.Release(); + } if (!b1 && !b2) return; diff --git a/WorldEditor/Architect/Util/UndoManager.cs b/WorldEditor/Architect/Util/UndoManager.cs index d45c898..550f6be 100644 --- a/WorldEditor/Architect/Util/UndoManager.cs +++ b/WorldEditor/Architect/Util/UndoManager.cs @@ -105,6 +105,25 @@ public IUndoable Undo() } } +public class ToggleTile(List<(int, int)> tiles, bool empty) : IUndoable +{ + public IUndoable Undo() + { + var map = PlacementManager.GetTilemap(); + if (!map) return null; + foreach (var (x, y) in tiles) + { + if (empty) map.ClearTile(x, y, 0); + else map.SetTile(x, y, 0, 0); + + PlacementManager.GetCurrentLevel().ToggleTile((x, y)); + } + map.Build(); + + return new ToggleTile(tiles, !empty); + } +} + public class MoveObjects(List<(string, Vector3)> data) : IUndoable { public IUndoable Undo() From 6d55b79b365d2b50a7dbea3950185044c72e8ccc Mon Sep 17 00:00:00 2001 From: arunkapila Date: Wed, 12 Nov 2025 11:10:49 +0000 Subject: [PATCH 07/12] 1.18.0.1 - Patch to renderer remover broken --- WorldEditor/Architect/Architect.csproj | 4 ++-- WorldEditor/Architect/Content/Elements/Custom/RoomObjects.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/WorldEditor/Architect/Architect.csproj b/WorldEditor/Architect/Architect.csproj index 4fe6fbc..5be7df6 100644 --- a/WorldEditor/Architect/Architect.csproj +++ b/WorldEditor/Architect/Architect.csproj @@ -8,8 +8,8 @@ Architect A mod to add platforms, enemies and more to change areas in the game with an in-game level editor. Copyright © Arun Kapila 2025 - 1.18.0.0 - 1.18.0.0 + 1.18.0.1 + 1.18.0.1 bin\$(Configuration)\ latest diff --git a/WorldEditor/Architect/Content/Elements/Custom/RoomObjects.cs b/WorldEditor/Architect/Content/Elements/Custom/RoomObjects.cs index a9b7b5e..0e5abc8 100644 --- a/WorldEditor/Architect/Content/Elements/Custom/RoomObjects.cs +++ b/WorldEditor/Architect/Content/Elements/Custom/RoomObjects.cs @@ -84,7 +84,7 @@ public static void Initialize() CreateObjectRemover("door_remover", "Remove Transition", FindObjectsToDisable) .WithConfigGroup(ConfigGroup.Invisible) .WithReceiverGroup(ReceiverGroup.Invisible), - CreateObjectRemover("door_remover", "Remove Renderer", FindObjectsToDisable) + CreateObjectRemover("renderer_remover", "Remove Renderer", FindObjectsToDisable) .WithConfigGroup(ConfigGroup.Invisible) .WithReceiverGroup(ReceiverGroup.Invisible), CreateObjectRemover("enemy_remover", "Remove Enemy", FindObjectsToDisable) From 7e1ab655641e490064618cf7e21d5cd0ad6619ee Mon Sep 17 00:00:00 2001 From: arunkapila Date: Wed, 12 Nov 2025 12:41:12 +0000 Subject: [PATCH 08/12] 1.18.0.1 Fix to prefabs --- WorldEditor/Architect/Category/PrefabsCategory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WorldEditor/Architect/Category/PrefabsCategory.cs b/WorldEditor/Architect/Category/PrefabsCategory.cs index f9d1f67..457314b 100644 --- a/WorldEditor/Architect/Category/PrefabsCategory.cs +++ b/WorldEditor/Architect/Category/PrefabsCategory.cs @@ -14,7 +14,7 @@ public PrefabsCategory() : base("Prefabs") { foreach (var obj in SceneSaveLoader.Load("Prefabs").Placements) { - Prefabs.Add(new PrefabObject(obj)); + if (obj != null) Prefabs.Add(new PrefabObject(obj)); Objects.Add(obj); } } From b66b3d52a80867720d9651aded48cd5e90e46cd2 Mon Sep 17 00:00:00 2001 From: arunkapila Date: Wed, 12 Nov 2025 12:44:19 +0000 Subject: [PATCH 09/12] Fix now includes objects too --- WorldEditor/Architect/Category/PrefabsCategory.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/WorldEditor/Architect/Category/PrefabsCategory.cs b/WorldEditor/Architect/Category/PrefabsCategory.cs index 457314b..bb5627c 100644 --- a/WorldEditor/Architect/Category/PrefabsCategory.cs +++ b/WorldEditor/Architect/Category/PrefabsCategory.cs @@ -14,8 +14,11 @@ public PrefabsCategory() : base("Prefabs") { foreach (var obj in SceneSaveLoader.Load("Prefabs").Placements) { - if (obj != null) Prefabs.Add(new PrefabObject(obj)); - Objects.Add(obj); + if (obj != null) + { + Prefabs.Add(new PrefabObject(obj)); + Objects.Add(obj); + } } } From e37ffa923e0627862ebaf9e0e741a5c6e24b4a0c Mon Sep 17 00:00:00 2001 From: arunkapila Date: Wed, 12 Nov 2025 12:44:37 +0000 Subject: [PATCH 10/12] Fix now includes objects too --- WorldEditor/Architect/Category/PrefabsCategory.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/WorldEditor/Architect/Category/PrefabsCategory.cs b/WorldEditor/Architect/Category/PrefabsCategory.cs index bb5627c..bb4aeb6 100644 --- a/WorldEditor/Architect/Category/PrefabsCategory.cs +++ b/WorldEditor/Architect/Category/PrefabsCategory.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using Architect.Objects; using Architect.Storage; using Architect.UI; @@ -12,13 +13,11 @@ internal class PrefabsCategory : ObjectCategory public PrefabsCategory() : base("Prefabs") { - foreach (var obj in SceneSaveLoader.Load("Prefabs").Placements) + foreach (var obj in SceneSaveLoader.Load("Prefabs").Placements + .Where(obj => obj != null)) { - if (obj != null) - { - Prefabs.Add(new PrefabObject(obj)); - Objects.Add(obj); - } + Prefabs.Add(new PrefabObject(obj)); + Objects.Add(obj); } } From 5d84fd910c28e7b7d5481852b84b1c3ed6c8a991 Mon Sep 17 00:00:00 2001 From: arunkapila Date: Wed, 12 Nov 2025 17:21:23 +0000 Subject: [PATCH 11/12] 1.18.0.2 - Fixed UI overlapping on some resolutions. - Increased scale of objects in the UI. --- WorldEditor/Architect/Architect.cs | 3 +++ WorldEditor/Architect/Architect.csproj | 4 ++-- .../Architect/Category/PrefabsCategory.cs | 2 +- WorldEditor/Architect/UI/EditorUIManager.cs | 22 ++++++++----------- 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/WorldEditor/Architect/Architect.cs b/WorldEditor/Architect/Architect.cs index a85ce6f..41d5f14 100644 --- a/WorldEditor/Architect/Architect.cs +++ b/WorldEditor/Architect/Architect.cs @@ -15,6 +15,7 @@ using Modding; using Satchel; using UnityEngine; +using UnityEngine.UI; using Object = UnityEngine.Object; namespace Architect; @@ -112,6 +113,8 @@ public WorldEditorGlobalSettings OnSaveGlobal() private static void InitializeLayout() { _editorLayout = new LayoutRoot(true, "Architect Editor"); + _editorLayout.Canvas.GetComponent().screenMatchMode = CanvasScaler.ScreenMatchMode.Expand; + EditorUIManager.Initialize(_editorLayout); _menuLayout = new LayoutRoot(true, "Architect Menu") diff --git a/WorldEditor/Architect/Architect.csproj b/WorldEditor/Architect/Architect.csproj index 5be7df6..3d36748 100644 --- a/WorldEditor/Architect/Architect.csproj +++ b/WorldEditor/Architect/Architect.csproj @@ -8,8 +8,8 @@ Architect A mod to add platforms, enemies and more to change areas in the game with an in-game level editor. Copyright © Arun Kapila 2025 - 1.18.0.1 - 1.18.0.1 + 1.18.0.2 + 1.18.0.2 bin\$(Configuration)\ latest diff --git a/WorldEditor/Architect/Category/PrefabsCategory.cs b/WorldEditor/Architect/Category/PrefabsCategory.cs index bb4aeb6..d0462a6 100644 --- a/WorldEditor/Architect/Category/PrefabsCategory.cs +++ b/WorldEditor/Architect/Category/PrefabsCategory.cs @@ -14,7 +14,7 @@ internal class PrefabsCategory : ObjectCategory public PrefabsCategory() : base("Prefabs") { foreach (var obj in SceneSaveLoader.Load("Prefabs").Placements - .Where(obj => obj != null)) + .Where(obj => obj?.GetPlaceableObject() != null)) { Prefabs.Add(new PrefabObject(obj)); Objects.Add(obj); diff --git a/WorldEditor/Architect/UI/EditorUIManager.cs b/WorldEditor/Architect/UI/EditorUIManager.cs index a313331..4efc4a2 100644 --- a/WorldEditor/Architect/UI/EditorUIManager.cs +++ b/WorldEditor/Architect/UI/EditorUIManager.cs @@ -884,20 +884,16 @@ private static void SetupTools(LayoutRoot layout) private static (Button, Image) CreateImagedButton(LayoutRoot layout, Sprite sprite, string name, int horizontalPadding, int verticalPadding, int index) { - var buttonSize = (int)(BASE_BUTTON_SIZE * _uiScaleFactor); - var imageSize = (int)(BASE_IMAGE_SIZE * _uiScaleFactor); - - var existingButton = PauseOptions.FirstOrDefault(x => x.Name == name + " Button"); - var existingImage = PauseOptions.FirstOrDefault(x => x.Name == name + " Image"); - - if (existingButton != null) + var ext = index < 0 ? 0 : 10; + var img = new Image(layout, sprite, name + " Image") { - existingButton.Destroy(); - } - if (existingImage != null) - { - existingImage.Destroy(); - } + HorizontalAlignment = HorizontalAlignment.Right, + VerticalAlignment = VerticalAlignment.Bottom, + Height = 40 + 2 * ext, + Width = 40 + 2 * ext, + PreserveAspectRatio = true, + Padding = new Padding(horizontalPadding + 35 - ext, verticalPadding + 35 - ext) + }; var button = new Button(layout, name + " Button") { From 16d514243064661c09d9fa53960ab23dbd78e80a Mon Sep 17 00:00:00 2001 From: arunkapila Date: Thu, 13 Nov 2025 01:09:38 +0000 Subject: [PATCH 12/12] 1.18.0.3 - Fixed Renderer Remover missing sprite --- WorldEditor/Architect/Architect.csproj | 4 ++-- .../Architect/Resources/renderer_remover.png | Bin 0 -> 5327 bytes 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 WorldEditor/Architect/Resources/renderer_remover.png diff --git a/WorldEditor/Architect/Architect.csproj b/WorldEditor/Architect/Architect.csproj index 3d36748..23dda74 100644 --- a/WorldEditor/Architect/Architect.csproj +++ b/WorldEditor/Architect/Architect.csproj @@ -8,8 +8,8 @@ Architect A mod to add platforms, enemies and more to change areas in the game with an in-game level editor. Copyright © Arun Kapila 2025 - 1.18.0.2 - 1.18.0.2 + 1.18.0.3 + 1.18.0.3 bin\$(Configuration)\ latest diff --git a/WorldEditor/Architect/Resources/renderer_remover.png b/WorldEditor/Architect/Resources/renderer_remover.png new file mode 100644 index 0000000000000000000000000000000000000000..16d0018eb3ddf9bc80d966f6b1d03c627c117f48 GIT binary patch literal 5327 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 Mw(-SUP21Ri0dqiAo&W#< literal 0 HcmV?d00001