diff --git a/WorldEditor.sln.DotSettings.user b/WorldEditor.sln.DotSettings.user
index 667658e..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
@@ -134,12 +136,14 @@
ForceIncluded
ForceIncluded
ForceIncluded
+ ForceIncluded
ForceIncluded
ForceIncluded
ForceIncluded
ForceIncluded
ForceIncluded
ForceIncluded
+ ForceIncluded
ForceIncluded
ForceIncluded
ForceIncluded
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 f8490d0..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.16.15.0
- 1.16.15.0
+ 1.18.0.3
+ 1.18.0.3
bin\$(Configuration)\
latest
diff --git a/WorldEditor/Architect/Category/PrefabsCategory.cs b/WorldEditor/Architect/Category/PrefabsCategory.cs
index d1fd7e5..d0462a6 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,7 +13,8 @@ internal class PrefabsCategory : ObjectCategory
public PrefabsCategory() : base("Prefabs")
{
- foreach (var obj in SceneSaveLoader.Load("Prefabs"))
+ foreach (var obj in SceneSaveLoader.Load("Prefabs").Placements
+ .Where(obj => obj?.GetPlaceableObject() != null))
{
Prefabs.Add(new PrefabObject(obj));
Objects.Add(obj);
@@ -26,7 +28,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 +40,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/Content/Elements/Custom/Behaviour/ZoteTrophy.cs b/WorldEditor/Architect/Content/Elements/Custom/Behaviour/ZoteTrophy.cs
index 523cfa0..6d146a9 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,33 @@ 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;
+ orig(fsm);
+
+ if (fsm.FsmName != "Area Title Control") return;
+
+ FsmUtil.InsertCustomAction(fsm, "Visited Check", f =>
+ {
+ 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 _areaBody;
public static void WinScreen(string name)
{
- _titleCardData.Value = name + "_RawText";
- _titleCard.SetActive(true);
+ _overrideAreaText = true;
+ _areaBody = name;
+
+ AreaTitle.instance.gameObject.SetActive(true);
}
}
\ No newline at end of file
diff --git a/WorldEditor/Architect/Content/Elements/Custom/RoomObjects.cs b/WorldEditor/Architect/Content/Elements/Custom/RoomObjects.cs
index 22dfb03..0e5abc8 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("renderer_remover", "Remove Renderer", FindObjectsToDisable)
+ .WithConfigGroup(ConfigGroup.Invisible)
+ .WithReceiverGroup(ReceiverGroup.Invisible),
CreateObjectRemover("enemy_remover", "Remove Enemy", FindObjectsToDisable)
.WithConfigGroup(ConfigGroup.Invisible)
.WithReceiverGroup(ReceiverGroup.Invisible),
@@ -98,7 +101,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)
{
@@ -112,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/Content/Groups/ConfigGroup.cs b/WorldEditor/Architect/Content/Groups/ConfigGroup.cs
index 5acebdb..16705cf 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();
@@ -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) =>
@@ -520,19 +555,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 +702,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 +775,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 +813,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 +1298,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 +1508,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/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 c73e70f..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,8 +99,10 @@ 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());
+ .Where(player => player.Id != id)
+ .Select(player => player.Id)
+ .ToArray()
+ );
});
netReceiver.RegisterPacketHandler(PacketId.Relay, (id, packet) =>
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..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()
@@ -59,8 +81,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/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/ScatteredAndLost/dream_block_exit2.wav b/WorldEditor/Architect/Resources/ScatteredAndLost/dream_block_exit2.wav
deleted file mode 100644
index 844817f..0000000
Binary files a/WorldEditor/Architect/Resources/ScatteredAndLost/dream_block_exit2.wav and /dev/null differ
diff --git a/WorldEditor/Architect/Resources/lock.png b/WorldEditor/Architect/Resources/lock.png
new file mode 100644
index 0000000..391e1b7
Binary files /dev/null and b/WorldEditor/Architect/Resources/lock.png differ
diff --git a/WorldEditor/Architect/Resources/renderer_remover.png b/WorldEditor/Architect/Resources/renderer_remover.png
new file mode 100644
index 0000000..16d0018
Binary files /dev/null and b/WorldEditor/Architect/Resources/renderer_remover.png differ
diff --git a/WorldEditor/Architect/Resources/tilemap.png b/WorldEditor/Architect/Resources/tilemap.png
new file mode 100644
index 0000000..f4f4565
Binary files /dev/null and b/WorldEditor/Architect/Resources/tilemap.png differ
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/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..4efc4a2 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;
@@ -193,6 +202,12 @@ private static void UpdateSelectedItem()
case -5:
SelectedItem = ResetObject.Instance;
break;
+ case -6:
+ SelectedItem = LockObject.Instance;
+ break;
+ case -7:
+ SelectedItem = TilemapObject.Instance;
+ break;
default:
{
var index = _groupIndex * ItemsPerGroup + _index;
@@ -226,44 +241,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 +312,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 +338,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 +353,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 +367,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 +390,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 +420,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 +437,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 +456,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 +537,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;
+
+ if (!normalCategories.ContainsKey(element.GetCategory()))
+ {
+ var normalCategory = new NormalCategory(element.GetCategory());
+ normalCategories.Add(element.GetCategory(), normalCategory);
+ _categories.Add(normalCategory);
+ }
- normalCategories[element.GetCategory()].AddObject(PlaceableObject.Create(element));
+ 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 +602,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,31 +621,68 @@ 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); };
PauseOptions.Add(favourite);
_selectionButtons.Add((button, favourite, image));
+
+ PauseOptions.Add(button);
+ PauseOptions.Add(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)
+ {
+ 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();
@@ -546,13 +690,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,15 +714,105 @@ 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(1, GridUnit.Proportional),
@@ -581,7 +820,7 @@ private static void SetupTools(LayoutRoot layout)
},
RowDefinitions =
{
- new GridDimension(1, GridUnit.Proportional)
+ new GridDimension((int)(toolSpacing * _uiScaleFactor), GridUnit.AbsoluteMin)
}
};
@@ -589,41 +828,55 @@ 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,
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, 6).WithProp(GridLayout.Row, correctRow);
multiplayerRefresh.Click += _ => { HkmpHook.Refresh(); };
extraSettings.Children.Add(multiplayerRefresh);
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);
+
+ 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);
+
+ 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);
}
@@ -631,14 +884,15 @@ private static void SetupTools(LayoutRoot layout)
private static (Button, Image) CreateImagedButton(LayoutRoot layout, Sprite sprite, string name,
int horizontalPadding, int verticalPadding, int index)
{
+ var ext = index < 0 ? 0 : 10;
var img = new Image(layout, sprite, name + " Image")
{
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Bottom,
- Height = 40,
- Width = 40,
+ Height = 40 + 2 * ext,
+ Width = 40 + 2 * ext,
PreserveAspectRatio = true,
- Padding = new Padding(horizontalPadding + 35, verticalPadding + 35)
+ Padding = new Padding(horizontalPadding + 35 - ext, verticalPadding + 35 - ext)
};
var button = new Button(layout, name + " Button")
@@ -646,9 +900,24 @@ private static (Button, Image) CreateImagedButton(LayoutRoot layout, Sprite spri
Content = "",
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Bottom,
- MinHeight = 80,
- MinWidth = 80,
- Padding = new Padding(horizontalPadding + 15, verticalPadding + 15)
+ MinHeight = buttonSize,
+ MinWidth = buttonSize,
+ Padding = new Padding(
+ (int)(horizontalPadding * _uiScaleFactor),
+ (int)(verticalPadding * _uiScaleFactor))
+ };
+
+ var imageHorizontalPos = (int)(horizontalPadding * _uiScaleFactor) + (buttonSize - imageSize) / 2;
+ var imageVerticalPos = (int)(verticalPadding * _uiScaleFactor) + (buttonSize - imageSize) / 2;
+
+ var img = new Image(layout, sprite, name + " Image")
+ {
+ HorizontalAlignment = HorizontalAlignment.Right,
+ VerticalAlignment = VerticalAlignment.Bottom,
+ Height = imageSize,
+ Width = imageSize,
+ PreserveAspectRatio = true,
+ Padding = new Padding(imageHorizontalPos, imageVerticalPos)
};
button.Click += _ =>
@@ -659,8 +928,8 @@ private static (Button, Image) CreateImagedButton(LayoutRoot layout, Sprite spri
SelectedItem?.AfterSelect();
};
- PauseOptions.Add(img);
PauseOptions.Add(button);
+ PauseOptions.Add(img);
return (button, img);
}
@@ -1138,4 +1407,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..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;
@@ -496,6 +500,7 @@ public static void DoPaste()
pl.Name,
wp - obj.Offset,
pl.Flipped,
+ false,
pl.Rotation,
pl.Scale,
converts[pl.GetId()],
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()