From 5754de141d42c8a72c2679ebaeb8da4788a02061 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Fri, 3 Apr 2015 16:28:04 +0100 Subject: [PATCH 01/12] Add Remove support to TypeDictionary. --- OpenRA.Game/Primitives/TypeDictionary.cs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/OpenRA.Game/Primitives/TypeDictionary.cs b/OpenRA.Game/Primitives/TypeDictionary.cs index bc41195ac4..e4bbf0c656 100644 --- a/OpenRA.Game/Primitives/TypeDictionary.cs +++ b/OpenRA.Game/Primitives/TypeDictionary.cs @@ -91,6 +91,27 @@ namespace OpenRA.Primitives return new T[0]; } + public void Remove(T val) + { + var t = val.GetType(); + + foreach (var i in t.GetInterfaces()) + InnerRemove(i, val); + foreach (var tt in t.BaseTypes()) + InnerRemove(tt, val); + } + + void InnerRemove(Type t, object val) + { + List objs; + object obj; + + if (dataMultiple.TryGetValue(t, out objs)) + objs.Remove(val); + else if (dataSingular.TryGetValue(t, out obj)) + dataSingular.Remove(t); + } + public IEnumerator GetEnumerator() { return WithInterface().GetEnumerator(); From 9e5e1f1a898d71cc0fe96e0f21f0243907fff143 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Mon, 6 Apr 2015 14:15:57 +0100 Subject: [PATCH 02/12] Add methods to IOccupySpaceInfo. --- OpenRA.Game/Traits/TraitsInterfaces.cs | 7 ++++++- OpenRA.Mods.Common/Traits/Air/Aircraft.cs | 3 +++ OpenRA.Mods.Common/Traits/Buildings/Building.cs | 10 ++++++++++ OpenRA.Mods.Common/Traits/Crates/Crate.cs | 8 ++++++++ OpenRA.Mods.Common/Traits/Husk.cs | 8 ++++++++ OpenRA.Mods.Common/Traits/Immobile.cs | 10 ++++++++++ OpenRA.Mods.Common/Traits/Mobile.cs | 7 +++++++ OpenRA.Mods.RA/Traits/Mine.cs | 2 +- 8 files changed, 53 insertions(+), 2 deletions(-) diff --git a/OpenRA.Game/Traits/TraitsInterfaces.cs b/OpenRA.Game/Traits/TraitsInterfaces.cs index 570ea1ea4d..301d162542 100644 --- a/OpenRA.Game/Traits/TraitsInterfaces.cs +++ b/OpenRA.Game/Traits/TraitsInterfaces.cs @@ -158,7 +158,12 @@ namespace OpenRA.Traits public interface IRadarColorModifier { Color RadarColorOverride(Actor self); } - public interface IOccupySpaceInfo : ITraitInfo { } + public interface IOccupySpaceInfo : ITraitInfo + { + IReadOnlyDictionary OccupiedCells(ActorInfo info, CPos location, SubCell subCell = SubCell.Any); + bool SharesCell { get; } + } + public interface IOccupySpace { WPos CenterPosition { get; } diff --git a/OpenRA.Mods.Common/Traits/Air/Aircraft.cs b/OpenRA.Mods.Common/Traits/Air/Aircraft.cs index b5c920d19d..fbe23c3a0b 100644 --- a/OpenRA.Mods.Common/Traits/Air/Aircraft.cs +++ b/OpenRA.Mods.Common/Traits/Air/Aircraft.cs @@ -43,6 +43,9 @@ namespace OpenRA.Mods.Common.Traits public virtual object Create(ActorInitializer init) { return new Aircraft(init, this); } public int GetInitialFacing() { return InitialFacing; } public WRange GetCruiseAltitude() { return CruiseAltitude; } + + public IReadOnlyDictionary OccupiedCells(ActorInfo info, CPos location, SubCell subCell = SubCell.Any) { return new ReadOnlyDictionary(); } + bool IOccupySpaceInfo.SharesCell { get { return false; } } } public class Aircraft : IFacing, IPositionable, ISync, INotifyKilled, IIssueOrder, IOrderVoice, INotifyAddedToWorld, INotifyRemovedFromWorld diff --git a/OpenRA.Mods.Common/Traits/Buildings/Building.cs b/OpenRA.Mods.Common/Traits/Buildings/Building.cs index 4985072538..59984719ce 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/Building.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/Building.cs @@ -104,6 +104,16 @@ namespace OpenRA.Mods.Common.Traits .Any(b => Math.Abs(a.X - b.X) <= Adjacent && Math.Abs(a.Y - b.Y) <= Adjacent)); } + + public IReadOnlyDictionary OccupiedCells(ActorInfo info, CPos topLeft, SubCell subCell = SubCell.Any) + { + var occupied = FootprintUtils.UnpathableTiles(info.Name, this, topLeft) + .ToDictionary(c => c, c => SubCell.FullCell); + + return new ReadOnlyDictionary(occupied); + } + + bool IOccupySpaceInfo.SharesCell { get { return false; } } } public class Building : IOccupySpace, INotifySold, INotifyTransform, ISync, INotifyCreated, INotifyAddedToWorld, INotifyRemovedFromWorld diff --git a/OpenRA.Mods.Common/Traits/Crates/Crate.cs b/OpenRA.Mods.Common/Traits/Crates/Crate.cs index 3ab80b2234..aef7c0ff83 100644 --- a/OpenRA.Mods.Common/Traits/Crates/Crate.cs +++ b/OpenRA.Mods.Common/Traits/Crates/Crate.cs @@ -28,6 +28,14 @@ namespace OpenRA.Mods.Common.Traits public readonly string CrushClass = "crate"; public object Create(ActorInitializer init) { return new Crate(init, this); } + + public IReadOnlyDictionary OccupiedCells(ActorInfo info, CPos location, SubCell subCell = SubCell.Any) + { + var occupied = new Dictionary() { { location, SubCell.FullCell } }; + return new ReadOnlyDictionary(occupied); + } + + bool IOccupySpaceInfo.SharesCell { get { return false; } } } class Crate : ITick, IPositionable, ICrushable, ISync, INotifyParachuteLanded, INotifyAddedToWorld, INotifyRemovedFromWorld diff --git a/OpenRA.Mods.Common/Traits/Husk.cs b/OpenRA.Mods.Common/Traits/Husk.cs index 14c5e6eeb3..00cfdc4d87 100644 --- a/OpenRA.Mods.Common/Traits/Husk.cs +++ b/OpenRA.Mods.Common/Traits/Husk.cs @@ -24,6 +24,14 @@ namespace OpenRA.Mods.Common.Traits public object Create(ActorInitializer init) { return new Husk(init, this); } public int GetInitialFacing() { return 128; } + + public IReadOnlyDictionary OccupiedCells(ActorInfo info, CPos location, SubCell subCell = SubCell.Any) + { + var occupied = new Dictionary() { { location, SubCell.FullCell } }; + return new ReadOnlyDictionary(occupied); + } + + bool IOccupySpaceInfo.SharesCell { get { return false; } } } public class Husk : IPositionable, IFacing, ISync, INotifyCreated, INotifyAddedToWorld, INotifyRemovedFromWorld, IDisable diff --git a/OpenRA.Mods.Common/Traits/Immobile.cs b/OpenRA.Mods.Common/Traits/Immobile.cs index 043d306cef..be0ea75bbd 100644 --- a/OpenRA.Mods.Common/Traits/Immobile.cs +++ b/OpenRA.Mods.Common/Traits/Immobile.cs @@ -18,6 +18,16 @@ namespace OpenRA.Mods.Common.Traits { public readonly bool OccupiesSpace = true; public object Create(ActorInitializer init) { return new Immobile(init, this); } + + public IReadOnlyDictionary OccupiedCells(ActorInfo info, CPos location, SubCell subCell = SubCell.Any) + { + var occupied = OccupiesSpace ? new Dictionary() { { location, SubCell.FullCell } } : + new Dictionary(); + + return new ReadOnlyDictionary(occupied); + } + + bool IOccupySpaceInfo.SharesCell { get { return false; } } } class Immobile : IOccupySpace, ISync, INotifyAddedToWorld, INotifyRemovedFromWorld diff --git a/OpenRA.Mods.Common/Traits/Mobile.cs b/OpenRA.Mods.Common/Traits/Mobile.cs index 28c69392ac..87509573b4 100644 --- a/OpenRA.Mods.Common/Traits/Mobile.cs +++ b/OpenRA.Mods.Common/Traits/Mobile.cs @@ -295,6 +295,13 @@ namespace OpenRA.Mods.Common.Traits } public int GetInitialFacing() { return InitialFacing; } + + public IReadOnlyDictionary OccupiedCells(ActorInfo info, CPos location, SubCell subCell = SubCell.Any) + { + return new ReadOnlyDictionary(new Dictionary() { { location, subCell } }); + } + + bool IOccupySpaceInfo.SharesCell { get { return SharesCell; } } } public class Mobile : IIssueOrder, IResolveOrder, IOrderVoice, IPositionable, IMove, IFacing, ISync, INotifyAddedToWorld, INotifyRemovedFromWorld, INotifyBlockingMove diff --git a/OpenRA.Mods.RA/Traits/Mine.cs b/OpenRA.Mods.RA/Traits/Mine.cs index 5abd3e0ede..6e27882fc0 100644 --- a/OpenRA.Mods.RA/Traits/Mine.cs +++ b/OpenRA.Mods.RA/Traits/Mine.cs @@ -14,7 +14,7 @@ using OpenRA.Traits; namespace OpenRA.Mods.RA.Traits { - class MineInfo : ITraitInfo, IOccupySpaceInfo + class MineInfo : ITraitInfo { public readonly string[] CrushClasses = { }; public readonly bool AvoidFriendly = true; From b889101ba1cdc854133304af4d3a780f60fac6a3 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Wed, 6 May 2015 22:40:35 +0100 Subject: [PATCH 03/12] Make sure that ScreenMap is initialized before other traits. --- OpenRA.Game/World.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/OpenRA.Game/World.cs b/OpenRA.Game/World.cs index aab063b763..b93f73b73d 100644 --- a/OpenRA.Game/World.cs +++ b/OpenRA.Game/World.cs @@ -198,8 +198,16 @@ namespace OpenRA public void LoadComplete(WorldRenderer wr) { + // ScreenMap must be initialized before anything else + using (new Support.PerfTimer("ScreenMap.WorldLoaded")) + ScreenMap.WorldLoaded(this, wr); + foreach (var wlh in WorldActor.TraitsImplementing()) { + // These have already been initialized + if (wlh == ScreenMap) + continue; + using (new Support.PerfTimer(wlh.GetType().Name + ".WorldLoaded")) wlh.WorldLoaded(this, wr); } From 443bc63fa7b42a0682171f7d8dfc2c1a5205990f Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sun, 10 May 2015 14:35:21 +0100 Subject: [PATCH 04/12] Render terrain grid above actors, too. --- OpenRA.Mods.Common/Traits/World/TerrainGeometryOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenRA.Mods.Common/Traits/World/TerrainGeometryOverlay.cs b/OpenRA.Mods.Common/Traits/World/TerrainGeometryOverlay.cs index fafd8571d9..d6fe424846 100644 --- a/OpenRA.Mods.Common/Traits/World/TerrainGeometryOverlay.cs +++ b/OpenRA.Mods.Common/Traits/World/TerrainGeometryOverlay.cs @@ -21,7 +21,7 @@ namespace OpenRA.Mods.Common.Traits [Desc("Renders a debug overlay showing the terrain cells. Attach this to the world actor.")] public class TerrainGeometryOverlayInfo : TraitInfo { } - public class TerrainGeometryOverlay : IRenderOverlay, IWorldLoaded, IChatCommand + public class TerrainGeometryOverlay : IPostRender, IWorldLoaded, IChatCommand { const string CommandName = "terrainoverlay"; const string CommandDesc = "Toggles the terrain geometry overlay"; @@ -46,7 +46,7 @@ namespace OpenRA.Mods.Common.Traits Enabled ^= true; } - public void Render(WorldRenderer wr) + public void RenderAfterWorld(WorldRenderer wr, Actor self) { if (!Enabled) return; From 038847cc4c6a2c3155ab7da6613a1d6ecbba4b72 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sun, 10 May 2015 15:17:26 +0100 Subject: [PATCH 05/12] Force the first template variant in TTPW. --- OpenRA.Game/Graphics/Theater.cs | 4 ++-- OpenRA.Mods.Common/Widgets/TerrainTemplatePreviewWidget.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/OpenRA.Game/Graphics/Theater.cs b/OpenRA.Game/Graphics/Theater.cs index 78bd1010f6..15227b2993 100644 --- a/OpenRA.Game/Graphics/Theater.cs +++ b/OpenRA.Game/Graphics/Theater.cs @@ -82,7 +82,7 @@ namespace OpenRA.Graphics Sheet.ReleaseBuffer(); } - public Sprite TileSprite(TerrainTile r) + public Sprite TileSprite(TerrainTile r, int? variant = null) { TheaterTemplate template; if (!templates.TryGetValue(r.Type, out template)) @@ -91,7 +91,7 @@ namespace OpenRA.Graphics if (r.Index >= template.Stride) return missingTile; - var start = template.Variants > 1 ? random.Next(template.Variants) : 0; + var start = template.Variants > 1 ? variant.HasValue ? variant.Value : random.Next(template.Variants) : 0; return template.Sprites[start * template.Stride + r.Index]; } diff --git a/OpenRA.Mods.Common/Widgets/TerrainTemplatePreviewWidget.cs b/OpenRA.Mods.Common/Widgets/TerrainTemplatePreviewWidget.cs index abb08c3a78..d46470999f 100644 --- a/OpenRA.Mods.Common/Widgets/TerrainTemplatePreviewWidget.cs +++ b/OpenRA.Mods.Common/Widgets/TerrainTemplatePreviewWidget.cs @@ -89,7 +89,7 @@ namespace OpenRA.Mods.Common.Widgets if (tileInfo == null) continue; - var sprite = worldRenderer.Theater.TileSprite(tile); + var sprite = worldRenderer.Theater.TileSprite(tile, 0); var size = new float2(sprite.Size.X * scale, sprite.Size.Y * scale); var u = shape == TileShape.Rectangle ? x : (x - y) / 2f; From b1dc5012c3494af6f3cc9d17a5119cfe784d4869 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sun, 10 May 2015 11:56:01 +0100 Subject: [PATCH 06/12] =?UTF-8?q?Don=E2=80=99t=20crash=20if=20IQBO=20isn?= =?UTF-8?q?=E2=80=99t=20present=20on=20an=20actor.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenRA.Mods.Common/Traits/Render/RenderSprites.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/OpenRA.Mods.Common/Traits/Render/RenderSprites.cs b/OpenRA.Mods.Common/Traits/Render/RenderSprites.cs index c81f43a3fc..36f5ee47cd 100644 --- a/OpenRA.Mods.Common/Traits/Render/RenderSprites.cs +++ b/OpenRA.Mods.Common/Traits/Render/RenderSprites.cs @@ -64,9 +64,15 @@ namespace OpenRA.Mods.Common.Traits var facings = 0; var body = init.Actor.Traits.GetOrDefault(); if (body != null) - facings = body.QuantizedFacings == -1 ? - init.Actor.Traits.Get().QuantizedBodyFacings(init.Actor, sequenceProvider, race) : - body.QuantizedFacings; + { + facings = body.QuantizedFacings; + + if (facings == -1) + { + var qbo = init.Actor.Traits.GetOrDefault(); + facings = qbo != null ? qbo.QuantizedBodyFacings(init.Actor, sequenceProvider, race) : 1; + } + } foreach (var spi in init.Actor.Traits.WithInterface()) foreach (var preview in spi.RenderPreviewSprites(init, this, image, facings, palette)) From 444c02a498294ba6fb86409c5da5b3c9f88c036e Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sun, 10 May 2015 12:33:17 +0100 Subject: [PATCH 07/12] Add ClickThrough property to ContainerWidget. --- OpenRA.Game/Widgets/Widget.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/OpenRA.Game/Widgets/Widget.cs b/OpenRA.Game/Widgets/Widget.cs index 39bd0e3415..9154244b39 100644 --- a/OpenRA.Game/Widgets/Widget.cs +++ b/OpenRA.Game/Widgets/Widget.cs @@ -493,6 +493,8 @@ namespace OpenRA.Widgets public class ContainerWidget : Widget { + public readonly bool ClickThrough = true; + public ContainerWidget() { IgnoreMouseOver = true; } public ContainerWidget(ContainerWidget other) : base(other) { IgnoreMouseOver = true; } @@ -501,6 +503,11 @@ namespace OpenRA.Widgets public override Widget Clone() { return new ContainerWidget(this); } public Func OnKeyPress = _ => false; public override bool HandleKeyPress(KeyInput e) { return OnKeyPress(e); } + + public override bool HandleMouseInput(MouseInput mi) + { + return !ClickThrough && EventBounds.Contains(mi.Location); + } } public class WidgetArgs : Dictionary From 1f024a869563df20d183a525df4168c753cd862a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Maila=CC=88nder?= Date: Sat, 21 Feb 2015 16:01:20 +0100 Subject: [PATCH 08/12] Add menu plumbing for the new map editor. --- OpenRA.Game/Game.cs | 5 ++ OpenRA.Game/World.cs | 2 +- .../Widgets/Logic/Ingame/IngameMenuLogic.cs | 30 +++++++++-- .../Widgets/Logic/MainMenuLogic.cs | 50 ++++++++++++++++++- .../Widgets/Logic/ServerCreationLogic.cs | 1 + mods/cnc/chrome/mainmenu.yaml | 46 ++++++++++++++++- mods/d2k/chrome/mainmenu.yaml | 45 ++++++++++++++++- mods/ra/chrome/mainmenu.yaml | 45 ++++++++++++++++- 8 files changed, 213 insertions(+), 11 deletions(-) diff --git a/OpenRA.Game/Game.cs b/OpenRA.Game/Game.cs index 69e795569a..77fd66493a 100644 --- a/OpenRA.Game/Game.cs +++ b/OpenRA.Game/Game.cs @@ -375,6 +375,11 @@ namespace OpenRA ModData.LoadScreen.StartGame(args); } + public static void LoadEditor(string mapUid) + { + StartGame(mapUid, WorldType.Editor); + } + public static void LoadShellMap() { var shellmap = ChooseShellmap(); diff --git a/OpenRA.Game/World.cs b/OpenRA.Game/World.cs index b93f73b73d..c1933e3cc2 100644 --- a/OpenRA.Game/World.cs +++ b/OpenRA.Game/World.cs @@ -22,7 +22,7 @@ using OpenRA.Traits; namespace OpenRA { - public enum WorldType { Regular, Shellmap } + public enum WorldType { Regular, Shellmap, Editor } public class World { diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameMenuLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameMenuLogic.cs index c1a819a0a3..88ea5197a0 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameMenuLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameMenuLogic.cs @@ -36,7 +36,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic // TODO: Create a mechanism to do things like this cleaner. Also needed for scripted missions Action onQuit = () => { - Sound.PlayNotification(world.Map.Rules, null, "Speech", "Leave", world.LocalPlayer == null ? null : world.LocalPlayer.Country.Race); + if (world.Type == WorldType.Regular) + Sound.PlayNotification(world.Map.Rules, null, "Speech", "Leave", world.LocalPlayer == null ? null : world.LocalPlayer.Country.Race); + resumeDisabled = true; var exitDelay = 1200; @@ -64,18 +66,29 @@ namespace OpenRA.Mods.Common.Widgets.Logic Action showMenu = () => hideMenu = false; - menu.Get("ABORT_MISSION").OnClick = () => + var abortMissionButton = menu.Get("ABORT_MISSION"); + abortMissionButton.IsVisible = () => world.Type == WorldType.Regular; + abortMissionButton.OnClick = () => { hideMenu = true; ConfirmationDialogs.PromptConfirmAction("Abort Mission", "Leave this game and return to the menu?", onQuit, showMenu); }; + var exitEditorButton = menu.Get("EXIT_EDITOR"); + exitEditorButton.IsVisible = () => world.Type == WorldType.Editor; + exitEditorButton.OnClick = () => + { + hideMenu = true; + ConfirmationDialogs.PromptConfirmAction("Exit Map Editor", "Exit and lose all unsaved changes?", onQuit, showMenu); + }; + Action onSurrender = () => { world.IssueOrder(new Order("Surrender", world.LocalPlayer.PlayerActor, false)); closeMenu(); }; var surrenderButton = menu.Get("SURRENDER"); + surrenderButton.IsVisible = () => world.Type == WorldType.Regular; surrenderButton.IsDisabled = () => (world.LocalPlayer == null || world.LocalPlayer.WinState != WinState.Undefined); surrenderButton.OnClick = () => { @@ -84,6 +97,17 @@ namespace OpenRA.Mods.Common.Widgets.Logic }; surrenderButton.IsDisabled = () => world.LocalPlayer == null || world.LocalPlayer.WinState != WinState.Undefined; + var saveMapButton = menu.Get("SAVE_MAP"); + saveMapButton.IsVisible = () => world.Type == WorldType.Editor; + saveMapButton.OnClick = () => + { + Ui.OpenWindow("SAVE_MAP_PANEL", new WidgetArgs() + { + { "onExit", () => widget.Visible = true }, + { "world", world }, + }); + }; + menu.Get("MUSIC").OnClick = () => { hideMenu = true; @@ -111,7 +135,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic resumeButton.OnClick = closeMenu; var panelRoot = widget.GetOrNull("PANEL_ROOT"); - if (panelRoot != null) + if (panelRoot != null && world.Type != WorldType.Editor) { var gameInfoPanel = Game.LoadWidget(world, "GAME_INFO_PANEL", panelRoot, new WidgetArgs() { diff --git a/OpenRA.Mods.Common/Widgets/Logic/MainMenuLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/MainMenuLogic.cs index 46aff00b19..b58a92f4ef 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/MainMenuLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/MainMenuLogic.cs @@ -21,7 +21,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic { public class MainMenuLogic { - protected enum MenuType { Main, Singleplayer, Extras, None } + protected enum MenuType { Main, Singleplayer, Extras, MapEditor, None } protected MenuType menuType = MenuType.Main; readonly Widget rootMenu; @@ -122,6 +122,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic }); }; + extrasMenu.Get("MAP_EDITOR_BUTTON").OnClick = () => menuType = MenuType.MapEditor; + var assetBrowserButton = extrasMenu.GetOrNull("ASSETBROWSER_BUTTON"); if (assetBrowserButton != null) assetBrowserButton.OnClick = () => @@ -144,6 +146,43 @@ namespace OpenRA.Mods.Common.Widgets.Logic extrasMenu.Get("BACK_BUTTON").OnClick = () => menuType = MenuType.Main; + // Map editor menu + var mapEditorMenu = widget.Get("MAP_EDITOR_MENU"); + mapEditorMenu.IsVisible = () => menuType == MenuType.MapEditor; + + var onSelect = new Action(uid => + { + RemoveShellmapUI(); + LoadMapIntoEditor(Game.ModData.MapCache[uid].Map); + }); + + var newMapButton = widget.Get("NEW_MAP_BUTTON"); + newMapButton.OnClick = () => + { + menuType = MenuType.None; + Game.OpenWindow("NEW_MAP_BG", new WidgetArgs() + { + { "onSelect", onSelect }, + { "onExit", () => menuType = MenuType.MapEditor } + }); + }; + + var loadMapButton = widget.Get("LOAD_MAP_BUTTON"); + loadMapButton.OnClick = () => + { + var initialMap = Game.ModData.MapCache.FirstOrDefault(); + menuType = MenuType.None; + Game.OpenWindow("MAPCHOOSER_PANEL", new WidgetArgs() + { + { "initialMap", initialMap != null ? initialMap.Uid : null }, + { "onExit", () => menuType = MenuType.MapEditor }, + { "onSelect", onSelect }, + { "filter", MapVisibility.Lobby | MapVisibility.Shellmap | MapVisibility.MissionSelector }, + }); + }; + + mapEditorMenu.Get("BACK_BUTTON").OnClick = () => menuType = MenuType.Extras; + var newsBG = widget.GetOrNull("NEWS_BG"); if (newsBG != null) { @@ -189,6 +228,15 @@ namespace OpenRA.Mods.Common.Widgets.Logic }; } + void LoadMapIntoEditor(Map map) + { + ConnectionLogic.Connect(System.Net.IPAddress.Loopback.ToString(), + Game.CreateLocalServer(map.Uid), + "", + () => { Game.LoadEditor(map.Uid); }, + () => { Game.CloseServer(); menuType = MenuType.MapEditor; }); + } + void SetNewsStatus(string message) { message = WidgetUtils.WrapText(message, newsStatus.Bounds.Width, Game.Renderer.Fonts[newsStatus.Font]); diff --git a/OpenRA.Mods.Common/Widgets/Logic/ServerCreationLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/ServerCreationLogic.cs index 0a13a5aba8..71f1f67e6d 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/ServerCreationLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/ServerCreationLogic.cs @@ -47,6 +47,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic { "onExit", () => { } }, { "onSelect", (Action)(uid => preview = Game.ModData.MapCache[uid]) }, { "filter", MapVisibility.Lobby }, + { "onStart", () => { } } }); }; diff --git a/mods/cnc/chrome/mainmenu.yaml b/mods/cnc/chrome/mainmenu.yaml index 02662fce9e..8fe1b2a355 100644 --- a/mods/cnc/chrome/mainmenu.yaml +++ b/mods/cnc/chrome/mainmenu.yaml @@ -165,14 +165,21 @@ Container@MENU_BACKGROUND: Width: 140 Height: 35 Text: Music - Button@ASSETBROWSER_BUTTON: + Button@MAP_EDITOR_BUTTON: X: 300 Y: 0 Width: 140 Height: 35 + Text: Map Editor + Font: Bold + Button@ASSETBROWSER_BUTTON: + X: 450 + Y: 0 + Width: 140 + Height: 35 Text: Asset Browser Button@CREDITS_BUTTON: - X: 450 + X: 600 Y: 0 Width: 140 Height: 35 @@ -184,6 +191,41 @@ Container@MENU_BACKGROUND: Width: 140 Height: 35 Text: Back + Background@MAP_EDITOR_MENU: + Width: PARENT_RIGHT + Visible: False + Children: + Label@MAP_EDITOR_MENU_TITLE: + X: 0 + Y: 0-30 + Width: PARENT_RIGHT + Height: 20 + Text: Map Editor + Align: Center + Font: Bold + Contrast: True + Button@NEW_MAP_BUTTON: + X: 0 + Y: 0 + Width: 140 + Height: 35 + Text: New Map + Font: Bold + Button@LOAD_MAP_BUTTON: + X: 150 + Y: 0 + Width: 140 + Height: 35 + Text: Load Map + Font: Bold + Button@BACK_BUTTON: + X: 300 + Y: 0 + Width: 140 + Height: 35 + Text: Back + Font: Bold + Key: escape Container@NEWS_BG: Children: DropDownButton@NEWS_BUTTON: diff --git a/mods/d2k/chrome/mainmenu.yaml b/mods/d2k/chrome/mainmenu.yaml index a64df6482c..f7aae64d42 100644 --- a/mods/d2k/chrome/mainmenu.yaml +++ b/mods/d2k/chrome/mainmenu.yaml @@ -127,16 +127,23 @@ Container@MAINMENU: Height: 30 Text: Music Font: Bold - Button@ASSETBROWSER_BUTTON: + Button@MAP_EDITOR_BUTTON: X: PARENT_RIGHT/2-WIDTH/2 Y: 140 Width: 140 Height: 30 + Text: Map Editor + Font: Bold + Button@ASSETBROWSER_BUTTON: + X: PARENT_RIGHT/2-WIDTH/2 + Y: 180 + Width: 140 + Height: 30 Text: Asset Browser Font: Bold Button@CREDITS_BUTTON: X: PARENT_RIGHT/2-WIDTH/2 - Y: 180 + Y: 220 Width: 140 Height: 30 Text: Credits @@ -149,6 +156,40 @@ Container@MAINMENU: Height: 30 Text: Back Font: Bold + Background@MAP_EDITOR_MENU: + Width: PARENT_RIGHT + Height: PARENT_BOTTOM + Children: + Label@MAP_EDITOR_MENU_TITLE: + X: 0 + Y: 20 + Width: 200 + Height: 30 + Text: Map Editor + Align: Center + Font: Bold + Button@NEW_MAP_BUTTON: + X: PARENT_RIGHT/2-WIDTH/2 + Y: 60 + Width: 140 + Height: 30 + Text: New Map + Font: Bold + Button@LOAD_MAP_BUTTON: + X: PARENT_RIGHT/2-WIDTH/2 + Y: 100 + Width: 140 + Height: 30 + Text: Load Map + Font: Bold + Button@BACK_BUTTON: + X: PARENT_RIGHT/2-WIDTH/2 + Key: escape + Y: 260 + Width: 140 + Height: 30 + Text: Back + Font: Bold Background@NEWS_BG: X: (WINDOW_RIGHT - WIDTH)/2 Y: 35 diff --git a/mods/ra/chrome/mainmenu.yaml b/mods/ra/chrome/mainmenu.yaml index 082a8275d0..c0308d9df1 100644 --- a/mods/ra/chrome/mainmenu.yaml +++ b/mods/ra/chrome/mainmenu.yaml @@ -140,16 +140,23 @@ Container@MAINMENU: Height: 30 Text: Music Font: Bold - Button@ASSETBROWSER_BUTTON: + Button@MAP_EDITOR_BUTTON: X: PARENT_RIGHT/2-WIDTH/2 Y: 140 Width: 140 Height: 30 + Text: Map Editor + Font: Bold + Button@ASSETBROWSER_BUTTON: + X: PARENT_RIGHT/2-WIDTH/2 + Y: 180 + Width: 140 + Height: 30 Text: Asset Browser Font: Bold Button@CREDITS_BUTTON: X: PARENT_RIGHT/2-WIDTH/2 - Y: 180 + Y: 220 Width: 140 Height: 30 Text: Credits @@ -162,6 +169,40 @@ Container@MAINMENU: Height: 30 Text: Back Font: Bold + Background@MAP_EDITOR_MENU: + Width: PARENT_RIGHT + Height: PARENT_BOTTOM + Children: + Label@MAP_EDITOR_MENU_TITLE: + X: 0 + Y: 20 + Width: 200 + Height: 30 + Text: Map Editor + Align: Center + Font: Bold + Button@NEW_MAP_BUTTON: + X: PARENT_RIGHT/2-WIDTH/2 + Y: 60 + Width: 140 + Height: 30 + Text: New Map + Font: Bold + Button@LOAD_MAP_BUTTON: + X: PARENT_RIGHT/2-WIDTH/2 + Y: 100 + Width: 140 + Height: 30 + Text: Load Map + Font: Bold + Button@BACK_BUTTON: + X: PARENT_RIGHT/2-WIDTH/2 + Key: escape + Y: 260 + Width: 140 + Height: 30 + Text: Back + Font: Bold Container@PERFORMANCE_INFO: Logic: PerfDebugLogic Children: From d211fe9fe1de739dbad0e8c67df7aceefed39abd Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Mon, 6 Apr 2015 14:57:13 +0100 Subject: [PATCH 09/12] Add the world components of the new editor. --- OpenRA.Game/Map/ActorReference.cs | 5 +- OpenRA.Game/World.cs | 3 +- OpenRA.Mods.Common/OpenRA.Mods.Common.csproj | 3 + .../Traits/World/EditorActorLayer.cs | 268 ++++++++++++++++++ .../Traits/World/EditorActorPreview.cs | 164 +++++++++++ .../Traits/World/EditorResourceLayer.cs | 148 ++++++++++ .../Traits/World/ResourceLayer.cs | 1 + .../Widgets/Logic/Ingame/LeaveMapLogic.cs | 5 +- OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj | 1 + .../Traits/World/D2kEditorResourceLayer.cs | 96 +++++++ .../Traits/World/D2kResourceLayer.cs | 6 +- mods/cnc/rules/world.yaml | 56 ++-- mods/d2k/rules/world.yaml | 54 ++-- mods/ra/rules/world.yaml | 67 +++-- mods/ts/rules/world.yaml | 73 ++--- 15 files changed, 833 insertions(+), 117 deletions(-) create mode 100644 OpenRA.Mods.Common/Traits/World/EditorActorLayer.cs create mode 100644 OpenRA.Mods.Common/Traits/World/EditorActorPreview.cs create mode 100644 OpenRA.Mods.Common/Traits/World/EditorResourceLayer.cs create mode 100644 OpenRA.Mods.D2k/Traits/World/D2kEditorResourceLayer.cs diff --git a/OpenRA.Game/Map/ActorReference.cs b/OpenRA.Game/Map/ActorReference.cs index 192d1c5f16..058efe3f2e 100755 --- a/OpenRA.Game/Map/ActorReference.cs +++ b/OpenRA.Game/Map/ActorReference.cs @@ -48,7 +48,7 @@ namespace OpenRA return info; } - public MiniYaml Save() + public MiniYaml Save(Func initFilter = null) { var ret = new MiniYaml(Type); foreach (var init in InitDict) @@ -56,6 +56,9 @@ namespace OpenRA if (init is ISuppressInitExport) continue; + if (initFilter != null && !initFilter(init)) + continue; + var initName = init.GetType().Name; ret.Nodes.Add(new MiniYamlNode(initName.Substring(0, initName.Length - 4), FieldSaver.Save(init))); } diff --git a/OpenRA.Game/World.cs b/OpenRA.Game/World.cs index c1933e3cc2..a53a2163d1 100644 --- a/OpenRA.Game/World.cs +++ b/OpenRA.Game/World.cs @@ -170,7 +170,8 @@ namespace OpenRA TileSet = map.Rules.TileSets[Map.Tileset]; SharedRandom = new MersenneTwister(orderManager.LobbyInfo.GlobalSettings.RandomSeed); - WorldActor = CreateActor("World", new TypeDictionary()); + var worldActorType = type == WorldType.Editor ? "EditorWorld" : "World"; + WorldActor = CreateActor(worldActorType, new TypeDictionary()); ActorMap = WorldActor.Trait(); ScreenMap = WorldActor.Trait(); diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index 893e2eb317..d109070c18 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -627,6 +627,9 @@ + + + diff --git a/OpenRA.Mods.Common/Traits/World/EditorActorLayer.cs b/OpenRA.Mods.Common/Traits/World/EditorActorLayer.cs new file mode 100644 index 0000000000..d3d63db563 --- /dev/null +++ b/OpenRA.Mods.Common/Traits/World/EditorActorLayer.cs @@ -0,0 +1,268 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using OpenRA.Graphics; +using OpenRA.Mods.Common.Graphics; +using OpenRA.Primitives; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits +{ + [Desc("Required for the map editor to work. Attach this to the world actor.")] + public class EditorActorLayerInfo : ITraitInfo + { + [Desc("Size of partition bins (world pixels)")] + public readonly int BinSize = 250; + + public object Create(ActorInitializer init) { return new EditorActorLayer(init.Self, this); } + } + + public class EditorActorLayer : IWorldLoaded, ITickRender, IRender, IRadarSignature, ICreatePlayers + { + readonly EditorActorLayerInfo info; + readonly Dictionary previews = new Dictionary(); + readonly Dictionary> cellMap = new Dictionary>(); + + SpatiallyPartitioned screenMap; + WorldRenderer worldRenderer; + + public MapPlayers Players { get; private set; } + + public EditorActorLayer(Actor self, EditorActorLayerInfo info) + { + this.info = info; + } + + public void CreatePlayers(World w) + { + if (w.Type != WorldType.Editor) + return; + + Players = new MapPlayers(w.Map.PlayerDefinitions); + } + + public void WorldLoaded(World world, WorldRenderer wr) + { + if (world.Type != WorldType.Editor) + return; + + worldRenderer = wr; + + foreach (var pr in Players.Players.Values) + wr.UpdatePalettesForPlayer(pr.Name, pr.Color, false); + + var ts = Game.ModData.Manifest.TileSize; + var width = world.Map.MapSize.X * ts.Width; + var height = world.Map.MapSize.Y * ts.Height; + screenMap = new SpatiallyPartitioned(width, height, info.BinSize); + + foreach (var kv in world.Map.ActorDefinitions) + Add(kv.Key, new ActorReference(kv.Value.Value, kv.Value.ToDictionary()), true); + + // Update neighbours in one pass + foreach (var p in previews.Values) + UpdateNeighbours(p.Footprint); + } + + public void TickRender(WorldRenderer wr, Actor self) + { + if (wr.World.Type != WorldType.Editor) + return; + + foreach (var kv in previews.Values) + kv.Tick(); + } + + static readonly IEnumerable NoRenderables = Enumerable.Empty(); + public virtual IEnumerable Render(Actor self, WorldRenderer wr) + { + if (wr.World.Type != WorldType.Editor) + return NoRenderables; + + return PreviewsInBox(wr.Viewport.TopLeft, wr.Viewport.BottomRight) + .SelectMany(p => p.Render()); + } + + public EditorActorPreview Add(ActorReference reference) { return Add(NextActorName(), reference); } + + EditorActorPreview Add(string id, ActorReference reference, bool initialSetup = false) + { + var owner = Players.Players[reference.InitDict.Get().PlayerName]; + + var preview = new EditorActorPreview(worldRenderer, id, reference, owner); + previews.Add(id, preview); + screenMap.Add(preview, preview.Bounds); + + foreach (var kv in preview.Footprint) + { + List list; + if (!cellMap.TryGetValue(kv.Key, out list)) + { + list = new List(); + cellMap.Add(kv.Key, list); + } + + list.Add(preview); + } + + if (!initialSetup) + { + UpdateNeighbours(preview.Footprint); + + if (reference.Type == "mpspawn") + SyncMultiplayerCount(); + } + + return preview; + } + + public void Remove(EditorActorPreview preview) + { + previews.Remove(preview.ID); + screenMap.Remove(preview); + + foreach (var kv in preview.Footprint) + { + List list; + if (!cellMap.TryGetValue(kv.Key, out list)) + continue; + + list.Remove(preview); + + if (!list.Any()) + cellMap.Remove(kv.Key); + } + + UpdateNeighbours(preview.Footprint); + + if (preview.Info.Name == "mpspawn") + SyncMultiplayerCount(); + } + + void SyncMultiplayerCount() + { + var newCount = previews.Count(p => p.Value.Info.Name == "mpspawn"); + var mp = Players.Players.Where(p => p.Key.StartsWith("Multi")).ToList(); + foreach (var kv in mp) + { + var name = kv.Key; + var index = int.Parse(name.Substring(5)); + + if (index >= newCount) + { + Players.Players.Remove(name); + worldRenderer.World.Players.RemoveAll(pp => pp.InternalName == name); + } + } + + for (var index = 0; index < newCount; index++) + { + if (Players.Players.ContainsKey("Multi{0}".F(index))) + continue; + + var pr = new PlayerReference + { + Name = "Multi{0}".F(index), + Race = "Random", + Playable = true, + Enemies = new[] { "Creeps" } + }; + + Players.Players.Add(pr.Name, pr); + worldRenderer.UpdatePalettesForPlayer(pr.Name, pr.Color, true); + } + } + + void UpdateNeighbours(IReadOnlyDictionary footprint) + { + // Include actors inside the footprint too + var cells = OpenRA.Traits.Util.ExpandFootprint(footprint.Keys, true); + foreach (var p in cells.SelectMany(c => PreviewsAt(c))) + p.ReplaceInit(new RuntimeNeighbourInit(NeighbouringPreviews(p.Footprint))); + } + + Dictionary NeighbouringPreviews(IReadOnlyDictionary footprint) + { + var cells = OpenRA.Traits.Util.ExpandFootprint(footprint.Keys, true).Except(footprint.Keys); + return cells.ToDictionary(c => c, c => PreviewsAt(c).Select(p => p.Info.Name).ToArray()); + } + + public IEnumerable PreviewsInBox(int2 a, int2 b) + { + return screenMap.InBox(Rectangle.FromLTRB(Math.Min(a.X, b.X), Math.Min(a.Y, b.Y), Math.Max(a.X, b.X), Math.Max(a.Y, b.Y))); + } + + public IEnumerable PreviewsInBox(Rectangle r) + { + return screenMap.InBox(r); + } + + public IEnumerable PreviewsAt(CPos cell) + { + List list; + if (cellMap.TryGetValue(cell, out list)) + return list; + + return Enumerable.Empty(); + } + + public SubCell FreeSubCellAt(CPos cell) + { + var map = worldRenderer.World.Map; + var previews = PreviewsAt(cell).ToList(); + if (!previews.Any()) + return map.DefaultSubCell; + + for (var i = (int)SubCell.First; i < map.SubCellOffsets.Length; i++) + if (!previews.Any(p => p.Footprint[cell] == (SubCell)i)) + return (SubCell)i; + + return SubCell.Invalid; + } + + public IEnumerable PreviewsAt(int2 worldPx) + { + return screenMap.At(worldPx); + } + + string NextActorName() + { + var id = previews.Count(); + var possibleName = "Actor" + id.ToString(); + + while (previews.ContainsKey(possibleName)) + { + id++; + possibleName = "Actor" + id.ToString(); + } + + return possibleName; + } + + public List Save() + { + var nodes = new List(); + foreach (var a in previews) + nodes.Add(new MiniYamlNode(a.Key, a.Value.Save())); + + return nodes; + } + + public IEnumerable> RadarSignatureCells(Actor self) + { + return cellMap.SelectMany(c => c.Value.Select(p => Pair.New(c.Key, p.Owner.Color.RGB))); + } + } +} diff --git a/OpenRA.Mods.Common/Traits/World/EditorActorPreview.cs b/OpenRA.Mods.Common/Traits/World/EditorActorPreview.cs new file mode 100644 index 0000000000..db5011915a --- /dev/null +++ b/OpenRA.Mods.Common/Traits/World/EditorActorPreview.cs @@ -0,0 +1,164 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using OpenRA.Graphics; +using OpenRA.Mods.Common.Graphics; +using OpenRA.Primitives; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits +{ + public class EditorActorPreview + { + public readonly string Tooltip; + public readonly string ID; + public readonly ActorInfo Info; + public readonly PlayerReference Owner; + public readonly WPos CenterPosition; + public readonly IReadOnlyDictionary Footprint; + public readonly Rectangle Bounds; + + public SubCell SubCell { get; private set; } + + readonly ActorReference actor; + readonly WorldRenderer worldRenderer; + IActorPreview[] previews; + + public EditorActorPreview(WorldRenderer worldRenderer, string id, ActorReference actor, PlayerReference owner) + { + ID = id; + this.actor = actor; + this.Owner = owner; + this.worldRenderer = worldRenderer; + + if (!actor.InitDict.Contains()) + actor.InitDict.Add(new RaceInit(owner.Race)); + + if (!actor.InitDict.Contains()) + actor.InitDict.Add(new OwnerInit(owner.Name)); + + var world = worldRenderer.World; + if (!world.Map.Rules.Actors.TryGetValue(actor.Type.ToLowerInvariant(), out Info)) + throw new InvalidDataException("Actor {0} of unknown type {1}".F(id, actor.Type.ToLowerInvariant())); + + CenterPosition = PreviewPosition(world, actor.InitDict); + + var location = actor.InitDict.Get().Value(worldRenderer.World); + var ios = Info.Traits.GetOrDefault(); + + var subCellInit = actor.InitDict.GetOrDefault(); + var subCell = subCellInit != null ? subCellInit.Value(worldRenderer.World) : SubCell.Any; + + if (ios != null) + Footprint = ios.OccupiedCells(Info, location, subCell); + else + { + var footprint = new Dictionary() { { location, SubCell.FullCell } }; + Footprint = new ReadOnlyDictionary(footprint); + } + + var tooltipInfo = Info.Traits.GetOrDefault(); + Tooltip = "{0} ({1})".F(tooltipInfo != null ? tooltipInfo.TooltipForPlayerStance(Stance.None) : Info.Name, ID); + + GeneratePreviews(); + + // Bounds are fixed from the initial render. + // If this is a problem, then we may need to fetch the area from somewhere else + var r = previews + .SelectMany(p => p.Render(worldRenderer, CenterPosition)) + .Select(rr => rr.PrepareRender(worldRenderer)); + + if (r.Any()) + { + Bounds = r.First().ScreenBounds(worldRenderer); + foreach (var rr in r.Skip(1)) + Bounds = Rectangle.Union(Bounds, rr.ScreenBounds(worldRenderer)); + } + } + + public void Tick() + { + foreach (var p in previews) + p.Tick(); + } + + public IEnumerable Render() + { + return previews.SelectMany(p => p.Render(worldRenderer, CenterPosition)); + } + + public void ReplaceInit(T init) + { + var original = actor.InitDict.GetOrDefault(); + if (original != null) + actor.InitDict.Remove(original); + + actor.InitDict.Add(init); + GeneratePreviews(); + } + + public T Init() + { + return actor.InitDict.GetOrDefault(); + } + + public MiniYaml Save() + { + Func saveInit = init => + { + var race = init as RaceInit; + if (race != null && race.Race == Owner.Race) + return false; + + // TODO: Other default values will need to be filtered + // here after we have built a properties panel + return true; + }; + + return actor.Save(saveInit); + } + + WPos PreviewPosition(World world, TypeDictionary init) + { + if (init.Contains()) + return init.Get().Value(world); + + if (init.Contains()) + { + var cell = init.Get().Value(world); + var offset = WVec.Zero; + + var subCellInit = actor.InitDict.GetOrDefault(); + var subCell = subCellInit != null ? subCellInit.Value(worldRenderer.World) : SubCell.Any; + + var buildingInfo = Info.Traits.GetOrDefault(); + if (buildingInfo != null) + offset = FootprintUtils.CenterOffset(world, buildingInfo); + + return world.Map.CenterOfSubCell(cell, subCell) + offset; + } + else + throw new InvalidDataException("Actor {0} must define Location or CenterPosition".F(ID)); + } + + void GeneratePreviews() + { + var init = new ActorPreviewInitializer(Info, worldRenderer, actor.InitDict); + previews = Info.Traits.WithInterface() + .SelectMany(rpi => rpi.RenderPreview(init)) + .ToArray(); + } + } +} diff --git a/OpenRA.Mods.Common/Traits/World/EditorResourceLayer.cs b/OpenRA.Mods.Common/Traits/World/EditorResourceLayer.cs new file mode 100644 index 0000000000..e38dd1403e --- /dev/null +++ b/OpenRA.Mods.Common/Traits/World/EditorResourceLayer.cs @@ -0,0 +1,148 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using OpenRA.Graphics; +using OpenRA.Mods.Common.Graphics; +using OpenRA.Primitives; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits +{ + using CellContents = ResourceLayer.CellContents; + + [Desc("Required for the map editor to work. Attach this to the world actor.")] + public class EditorResourceLayerInfo : ITraitInfo, Requires + { + public virtual object Create(ActorInitializer init) { return new EditorResourceLayer(init.Self); } + } + + public class EditorResourceLayer : IWorldLoaded, IRenderOverlay + { + protected readonly Map Map; + protected readonly TileSet Tileset; + protected readonly Dictionary Resources; + protected readonly CellLayer Tiles; + protected readonly HashSet Dirty = new HashSet(); + + public EditorResourceLayer(Actor self) + { + if (self.World.Type != WorldType.Editor) + return; + + Map = self.World.Map; + Tileset = self.World.TileSet; + + Tiles = new CellLayer(Map); + Resources = self.TraitsImplementing() + .ToDictionary(r => r.Info.ResourceType, r => r); + + Map.MapResources.Value.CellEntryChanged += UpdateCell; + } + + public void WorldLoaded(World w, WorldRenderer wr) + { + if (w.Type != WorldType.Editor) + return; + + foreach (var cell in Map.Cells) + UpdateCell(cell); + } + + public void UpdateCell(CPos cell) + { + var uv = cell.ToMPos(Map); + var tile = Map.MapResources.Value[uv]; + + ResourceType type; + if (Resources.TryGetValue(tile.Type, out type)) + { + Tiles[uv] = new CellContents + { + Type = type, + Variant = ChooseRandomVariant(type), + }; + + Map.CustomTerrain[uv] = Tileset.GetTerrainIndex(type.Info.TerrainType); + } + else + { + Tiles[uv] = CellContents.Empty; + Map.CustomTerrain[uv] = byte.MaxValue; + } + + // Ingame resource rendering is a giant hack (#6395), + // so we must also touch all the neighbouring tiles + Dirty.Add(cell); + foreach (var d in CVec.Directions) + { + var c = cell + d; + if (Map.Contains(c)) + Dirty.Add(c); + } + } + + protected virtual string ChooseRandomVariant(ResourceType t) + { + return t.Variants.Keys.Random(Game.CosmeticRandom); + } + + public virtual CellContents UpdateDirtyTile(CPos c) + { + var t = Tiles[c]; + + // Empty tile + if (t.Type == null) + { + t.Sprite = null; + return t; + } + + // Set density based on the number of neighboring resources + var adjacent = 0; + var type = t.Type; + for (var u = -1; u < 2; u++) + for (var v = -1; v < 2; v++) + if (Map.MapResources.Value[c + new CVec(u, v)].Type == type.Info.ResourceType) + adjacent++; + + t.Density = Math.Max(int2.Lerp(0, type.Info.MaxDensity, adjacent, 9), 1); + + var sprites = type.Variants[t.Variant]; + var frame = int2.Lerp(0, sprites.Length - 1, t.Density - 1, type.Info.MaxDensity); + t.Sprite = sprites[frame]; + + return t; + } + + public void Render(WorldRenderer wr) + { + if (wr.World.Type != WorldType.Editor) + return; + + foreach (var c in Dirty) + Tiles[c] = UpdateDirtyTile(c); + + Dirty.Clear(); + + foreach (var uv in wr.Viewport.VisibleCells.MapCoords) + { + var t = Tiles[uv]; + if (t.Sprite != null) + new SpriteRenderable(t.Sprite, wr.World.Map.CenterOfCell(uv.ToCPos(Map)), + WVec.Zero, -511, t.Type.Palette, 1f, true).Render(wr); + } + } + } +} diff --git a/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs b/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs index da226d769b..cff197e5b2 100644 --- a/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs @@ -248,6 +248,7 @@ namespace OpenRA.Mods.Common.Traits public struct CellContents { + public static readonly CellContents Empty = new CellContents(); public ResourceType Type; public int Density; public string Variant; diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/LeaveMapLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/LeaveMapLogic.cs index 3b060bf450..eb6b21c99f 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/LeaveMapLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/LeaveMapLogic.cs @@ -102,8 +102,9 @@ namespace OpenRA.Mods.Common.Widgets { leaveButton.Disabled = true; - Sound.PlayNotification(world.Map.Rules, null, "Speech", "Leave", - world.LocalPlayer == null ? null : world.LocalPlayer.Country.Race); + if (world.Type == WorldType.Regular) + Sound.PlayNotification(world.Map.Rules, null, "Speech", "Leave", + world.LocalPlayer == null ? null : world.LocalPlayer.Country.Race); var exitDelay = 1200; if (mpe != null) diff --git a/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj b/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj index 08e23c5c17..a951482a96 100644 --- a/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj +++ b/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj @@ -90,6 +90,7 @@ + diff --git a/OpenRA.Mods.D2k/Traits/World/D2kEditorResourceLayer.cs b/OpenRA.Mods.D2k/Traits/World/D2kEditorResourceLayer.cs new file mode 100644 index 0000000000..c44441ed6a --- /dev/null +++ b/OpenRA.Mods.D2k/Traits/World/D2kEditorResourceLayer.cs @@ -0,0 +1,96 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using OpenRA.Graphics; +using OpenRA.Mods.Common.Traits; +using OpenRA.Traits; + +namespace OpenRA.Mods.D2k.Traits +{ + using CellContents = D2kResourceLayer.CellContents; + using ClearSides = D2kResourceLayer.ClearSides; + + [Desc("Used to render spice with round borders.")] + public class D2kEditorResourceLayerInfo : EditorResourceLayerInfo + { + public override object Create(ActorInitializer init) { return new D2kEditorResourceLayer(init.Self); } + } + + public class D2kEditorResourceLayer : EditorResourceLayer + { + public D2kEditorResourceLayer(Actor self) + : base(self) { } + + public override CellContents UpdateDirtyTile(CPos c) + { + var t = Tiles[c]; + + // Empty tile + if (t.Type == null) + { + t.Sprite = null; + return t; + } + + int index; + var clear = FindClearSides(t.Type, c); + if (clear == ClearSides.None) + { + var sprites = D2kResourceLayer.Variants[t.Variant]; + var frame = t.Density > t.Type.Info.MaxDensity / 2 ? 1 : 0; + t.Sprite = t.Type.Variants.First().Value[sprites[frame]]; + } + else if (D2kResourceLayer.SpriteMap.TryGetValue(clear, out index)) + t.Sprite = t.Type.Variants.First().Value[index]; + else + t.Sprite = null; + + return t; + } + + protected override string ChooseRandomVariant(ResourceType t) + { + return D2kResourceLayer.Variants.Keys.Random(Game.CosmeticRandom); + } + + ClearSides FindClearSides(ResourceType t, CPos p) + { + var ret = ClearSides.None; + if (Tiles[p + new CVec(0, -1)].Type != t) + ret |= ClearSides.Top | ClearSides.TopLeft | ClearSides.TopRight; + + if (Tiles[p + new CVec(-1, 0)].Type != t) + ret |= ClearSides.Left | ClearSides.TopLeft | ClearSides.BottomLeft; + + if (Tiles[p + new CVec(1, 0)].Type != t) + ret |= ClearSides.Right | ClearSides.TopRight | ClearSides.BottomRight; + + if (Tiles[p + new CVec(0, 1)].Type != t) + ret |= ClearSides.Bottom | ClearSides.BottomLeft | ClearSides.BottomRight; + + if (Tiles[p + new CVec(-1, -1)].Type != t) + ret |= ClearSides.TopLeft; + + if (Tiles[p + new CVec(1, -1)].Type != t) + ret |= ClearSides.TopRight; + + if (Tiles[p + new CVec(-1, 1)].Type != t) + ret |= ClearSides.BottomLeft; + + if (Tiles[p + new CVec(1, 1)].Type != t) + ret |= ClearSides.BottomRight; + + return ret; + } + } +} diff --git a/OpenRA.Mods.D2k/Traits/World/D2kResourceLayer.cs b/OpenRA.Mods.D2k/Traits/World/D2kResourceLayer.cs index 070cab02f7..dce447e39c 100644 --- a/OpenRA.Mods.D2k/Traits/World/D2kResourceLayer.cs +++ b/OpenRA.Mods.D2k/Traits/World/D2kResourceLayer.cs @@ -21,7 +21,7 @@ namespace OpenRA.Mods.D2k.Traits public class D2kResourceLayer : ResourceLayer { - [Flags] enum ClearSides : byte + [Flags] public enum ClearSides : byte { None = 0x0, Left = 0x1, @@ -37,7 +37,7 @@ namespace OpenRA.Mods.D2k.Traits All = 0xFF } - static readonly Dictionary Variants = new Dictionary() + public static readonly Dictionary Variants = new Dictionary() { { "cleara", new[] { 0, 50 } }, { "clearb", new[] { 1, 51 } }, @@ -45,7 +45,7 @@ namespace OpenRA.Mods.D2k.Traits { "cleard", new[] { 0, 53 } }, }; - static readonly Dictionary SpriteMap = new Dictionary() + public static readonly Dictionary SpriteMap = new Dictionary() { { ClearSides.None, 0 }, { ClearSides.Left | ClearSides.Top | ClearSides.TopLeft | ClearSides.TopRight | ClearSides.BottomLeft | ClearSides.BottomRight, 2 }, diff --git a/mods/cnc/rules/world.yaml b/mods/cnc/rules/world.yaml index 67426dead2..772c8218b8 100644 --- a/mods/cnc/rules/world.yaml +++ b/mods/cnc/rules/world.yaml @@ -1,17 +1,8 @@ -World: +^BaseWorld: Inherits: ^Palettes - ChatCommands: - DevCommands: - PlayerCommands: - HelpCommand: ScreenMap: ActorMap: - LoadWidgetAtGameStart: - ShellmapRoot: MENU_BACKGROUND - ScreenShaker: - BuildingInfluence: - BridgeLayer: - Bridges: bridge1, bridge2, bridge3, bridge4 + TerrainGeometryOverlay: ShroudRenderer: ShroudVariants: typea, typeb, typec, typed FogVariants: typea, typeb, typec, typed @@ -30,18 +21,6 @@ World: Name: Nod Race: nod Description: Brotherhood of Nod\nThe Brotherhood is a religious cult centered around their leader Kane\nand the alien substance Tiberium. They utilize stealth technology\nand guerilla tactics to defeat those who oppose them. - ProductionQueueFromSelection: - ProductionTabsWidget: PRODUCTION_TABS - DomainIndex: - SmudgeLayer@SCORCH: - Type: Scorch - Sequence: scorches - SmokePercentage: 50 - SmudgeLayer@CRATER: - Type: Crater - Sequence: craters - ResourceLayer: - ResourceClaimLayer: ResourceType@green-tib: ResourceType: 1 Palette: staticterrain @@ -66,8 +45,33 @@ World: PipColor: Blue AllowedTerrainTypes: Clear,Road AllowUnderActors: true + LoadWidgetAtGameStart: + ShellmapRoot: MENU_BACKGROUND + +World: + Inherits: ^BaseWorld + ChatCommands: + DevCommands: + PlayerCommands: + HelpCommand: + ScreenShaker: + NukePaletteEffect: + BuildingInfluence: + BridgeLayer: + Bridges: bridge1, bridge2, bridge3, bridge4 + ProductionQueueFromSelection: + ProductionTabsWidget: PRODUCTION_TABS + DomainIndex: + SmudgeLayer@SCORCH: + Type: Scorch + Sequence: scorches + SmokePercentage: 50 + SmudgeLayer@CRATER: + Type: Crater + Sequence: craters + ResourceLayer: + ResourceClaimLayer: PathfinderDebugOverlay: - TerrainGeometryOverlay: SpawnMapActors: MPStartLocations: CreateMPPlayers: @@ -143,3 +147,7 @@ World: PanelName: SKIRMISH_STATS RadarPings: +EditorWorld: + Inherits: ^BaseWorld + EditorActorLayer: + EditorResourceLayer: diff --git a/mods/d2k/rules/world.yaml b/mods/d2k/rules/world.yaml index bdaab89409..26f85db217 100644 --- a/mods/d2k/rules/world.yaml +++ b/mods/d2k/rules/world.yaml @@ -1,23 +1,8 @@ -World: +^BaseWorld: Inherits: ^Palettes - ChatCommands: - DevCommands: - PlayerCommands: - HelpCommand: ScreenMap: ActorMap: - LoadWidgetAtGameStart: - ScreenShaker: - BuildingInfluence: - ProductionQueueFromSelection: - ProductionPaletteWidget: PRODUCTION_PALETTE - WormManager: - CrateSpawner: - Minimum: 0 - Maximum: 2 - SpawnInterval: 60 - WaterChance: 0 - ValidGround: Sand, Dune, Rock + TerrainGeometryOverlay: ShroudRenderer: ShroudVariants: typea, typeb, typec, typed FogVariants: typea, typeb, typec, typed @@ -46,12 +31,6 @@ World: Name: Corrino Race: corrino Selectable: false - DomainIndex: - PathfinderDebugOverlay: - BuildableTerrainLayer: - D2kResourceLayer: - TerrainGeometryOverlay: - ResourceClaimLayer: ResourceType@Spice: ResourceType: 1 Palette: d2k @@ -64,6 +43,30 @@ World: PipColor: green AllowedTerrainTypes: Sand AllowUnderActors: true + LoadWidgetAtGameStart: + +World: + Inherits: ^BaseWorld + ChatCommands: + DevCommands: + PlayerCommands: + HelpCommand: + ScreenShaker: + BuildingInfluence: + ProductionQueueFromSelection: + ProductionPaletteWidget: PRODUCTION_PALETTE + WormManager: + CrateSpawner: + Minimum: 0 + Maximum: 2 + SpawnInterval: 60 + WaterChance: 0 + ValidGround: Sand, Dune, Rock + DomainIndex: + PathfinderDebugOverlay: + BuildableTerrainLayer: + D2kResourceLayer: + ResourceClaimLayer: SmudgeLayer@Rock: Type: RockCrater Sequence: rockcraters @@ -135,3 +138,8 @@ World: RadarPings: ObjectivesPanel: PanelName: SKIRMISH_STATS + +EditorWorld: + Inherits: ^BaseWorld + EditorActorLayer: + D2kEditorResourceLayer: diff --git a/mods/ra/rules/world.yaml b/mods/ra/rules/world.yaml index 5bf84686a4..4b92667286 100644 --- a/mods/ra/rules/world.yaml +++ b/mods/ra/rules/world.yaml @@ -1,25 +1,9 @@ -World: +^BaseWorld: Inherits: ^Palettes - ChatCommands: - DevCommands: - PlayerCommands: - HelpCommand: - ScreenMap: ActorMap: + ScreenMap: + TerrainGeometryOverlay: LoadWidgetAtGameStart: - ScreenShaker: - BuildingInfluence: - ProductionQueueFromSelection: - ProductionPaletteWidget: PRODUCTION_PALETTE - BridgeLayer: - Bridges: bridge1, bridge2, br1, br2, br3, sbridge1, sbridge2, sbridge3, sbridge4 - CrateSpawner: - DeliveryAircraft: badr - QuantizedFacings: 16 - Minimum: 1 - Maximum: 3 - SpawnInterval: 120 - WaterChance: .2 ShroudRenderer: FogVariants: shroud Index: 255, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 20, 40, 56, 65, 97, 130, 148, 194, 24, 33, 66, 132, 28, 41, 67, 134, 1, 2, 4, 8, 3, 6, 12, 9, 7, 14, 13, 11, 5, 10, 15, 255 @@ -77,16 +61,6 @@ World: RandomRaceMembers: russia, ukraine Side: Random Description: A random Soviet country. - DomainIndex: - SmudgeLayer@SCORCH: - Type: Scorch - Sequence: scorches - SmokePercentage: 50 - SmudgeLayer@CRATER: - Type: Crater - Sequence: craters - ResourceLayer: - ResourceClaimLayer: ResourceType@ore: ResourceType: 1 Palette: player @@ -111,8 +85,37 @@ World: AllowedTerrainTypes: Clear,Road AllowUnderActors: true TerrainType: Gems + +World: + Inherits: ^BaseWorld + ChatCommands: + DevCommands: + PlayerCommands: + HelpCommand: + ScreenShaker: + BuildingInfluence: + ProductionQueueFromSelection: + ProductionPaletteWidget: PRODUCTION_PALETTE + BridgeLayer: + Bridges: bridge1, bridge2, br1, br2, br3, sbridge1, sbridge2, sbridge3, sbridge4 + CrateSpawner: + DeliveryAircraft: badr + QuantizedFacings: 16 + Minimum: 1 + Maximum: 3 + SpawnInterval: 120 + WaterChance: .2 + DomainIndex: + SmudgeLayer@SCORCH: + Type: Scorch + Sequence: scorches + SmokePercentage: 50 + SmudgeLayer@CRATER: + Type: Crater + Sequence: craters + ResourceLayer: + ResourceClaimLayer: PathfinderDebugOverlay: - TerrainGeometryOverlay: SpawnMapActors: CreateMPPlayers: MPStartUnits@mcvonly: @@ -162,3 +165,7 @@ World: ObjectivesPanel: PanelName: SKIRMISH_STATS +EditorWorld: + Inherits: ^BaseWorld + EditorActorLayer: + EditorResourceLayer: diff --git a/mods/ts/rules/world.yaml b/mods/ts/rules/world.yaml index afbbc93f43..d6beedbf5f 100644 --- a/mods/ts/rules/world.yaml +++ b/mods/ts/rules/world.yaml @@ -1,15 +1,8 @@ -World: +^BaseWorld: Inherits: ^Palettes - ChatCommands: - DevCommands: - PlayerCommands: - HelpCommand: ScreenMap: ActorMap: LoadWidgetAtGameStart: - BuildingInfluence: - ProductionQueueFromSelection: - ProductionPaletteWidget: PRODUCTION_PALETTE ShroudRenderer: Index: 255, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 20, 40, 56, 65, 97, 130, 148, 194, 24, 33, 66, 132, 28, 41, 67, 134, 1, 2, 4, 8, 3, 6, 12, 9, 7, 14, 13, 11, 5, 10, 15, 255 UseExtendedIndex: true @@ -25,6 +18,41 @@ World: Country@1: Name: Nod Race: nod + ResourceType@Tiberium: + ResourceType: 1 + Palette: greentiberium + EditorSprite: waypoint # TODO: editor can't handle the real ones + Variants: tib01, tib02, tib03, tib04, tib05, tib06, tib07, tib08, tib09, tib10, tib11, tib12, tib13, tib14, tib15, tib16, tib17, tib18, tib19, tib20 + MaxDensity: 12 + ValuePerUnit: 50 + Name: Tiberium + PipColor: Green + AllowedTerrainTypes: Clear, Rough, DirtRoad + AllowUnderActors: true + TerrainType: Tiberium + ResourceType@BlueTiberium: + ResourceType: 2 + Palette: bluetiberium + EditorSprite: waypoint # TODO: editor can't handle the real ones + Variants: tib01, tib02, tib03, tib04, tib05, tib06, tib07, tib08, tib09, tib10, tib11, tib12, tib13, tib14, tib15, tib16, tib17, tib18, tib19, tib20 + MaxDensity: 12 + ValuePerUnit: 100 + Name: BlueTiberium + PipColor: Blue + AllowedTerrainTypes: Clear, Rough, DirtRoad + AllowUnderActors: true + TerrainType: BlueTiberium + TerrainGeometryOverlay: + +World: + Inherits: ^BaseWorld + ChatCommands: + DevCommands: + PlayerCommands: + HelpCommand: + BuildingInfluence: + ProductionQueueFromSelection: + ProductionPaletteWidget: PRODUCTION_PALETTE DomainIndex: SmudgeLayer@SMALLSCORCH: Type: SmallScorch @@ -55,32 +83,7 @@ World: Sequence: largecraters ResourceLayer: ResourceClaimLayer: - ResourceType@Tiberium: - ResourceType: 1 - Palette: greentiberium - EditorSprite: waypoint # TODO: editor can't handle the real ones - Variants: tib01, tib02, tib03, tib04, tib05, tib06, tib07, tib08, tib09, tib10, tib11, tib12, tib13, tib14, tib15, tib16, tib17, tib18, tib19, tib20 - MaxDensity: 12 - ValuePerUnit: 50 - Name: Tiberium - PipColor: Green - AllowedTerrainTypes: Clear, Rough, DirtRoad - AllowUnderActors: true - TerrainType: Tiberium - ResourceType@BlueTiberium: - ResourceType: 2 - Palette: bluetiberium - EditorSprite: waypoint # TODO: editor can't handle the real ones - Variants: tib01, tib02, tib03, tib04, tib05, tib06, tib07, tib08, tib09, tib10, tib11, tib12, tib13, tib14, tib15, tib16, tib17, tib18, tib19, tib20 - MaxDensity: 12 - ValuePerUnit: 100 - Name: BlueTiberium - PipColor: Blue - AllowedTerrainTypes: Clear, Rough, DirtRoad - AllowUnderActors: true - TerrainType: BlueTiberium PathfinderDebugOverlay: - TerrainGeometryOverlay: SpawnMapActors: CreateMPPlayers: MPStartUnits@MCV: @@ -151,3 +154,7 @@ World: RadarPings: StartGameNotification: +EditorWorld: + Inherits: ^BaseWorld + EditorActorLayer: + EditorResourceLayer: From 469f47aeea7fad378756b398e138820e6864c5dd Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Mon, 6 Apr 2015 14:57:13 +0100 Subject: [PATCH 10/12] Add new map editor UI. --- .../EditorBrushes/EditorActorBrush.cs | 134 +++++++ .../EditorBrushes/EditorDefaultBrush.cs | 114 ++++++ .../EditorBrushes/EditorResourceBrush.cs | 115 ++++++ .../EditorBrushes/EditorTileBrush.cs | 151 +++++++ OpenRA.Mods.Common/OpenRA.Mods.Common.csproj | 16 + .../Traits/World/EditorResourceLayer.cs | 25 +- .../Traits/World/LoadWidgetAtGameStart.cs | 7 +- .../Widgets/EditorViewportControllerWidget.cs | 98 +++++ .../Logic/Editor/ActorSelectorLogic.cs | 150 +++++++ .../Logic/Editor/LayerSelectorLogic.cs | 84 ++++ .../Widgets/Logic/Editor/MapEditorLogic.cs | 70 ++++ .../Logic/Editor/MapEditorTabsLogic.cs | 53 +++ .../Widgets/Logic/Editor/NewMapLogic.cs | 94 +++++ .../Widgets/Logic/Editor/SaveMapLogic.cs | 122 ++++++ .../Widgets/Logic/Editor/TileSelectorLogic.cs | 92 +++++ .../Logic/Ingame/LoadMapEditorLogic.cs | 24 ++ OpenRA.Mods.Common/Widgets/SpriteWidget.cs | 12 +- .../Traits/World/D2kEditorResourceLayer.cs | 2 + mods/cnc/chrome/editor.yaml | 378 ++++++++++++++++++ mods/cnc/chrome/ingame-menu.yaml | 12 + mods/cnc/mod.yaml | 1 + mods/d2k/chrome/ingame-menu.yaml | 14 + mods/d2k/mod.yaml | 1 + mods/d2k/rules/arrakis.yaml | 1 + mods/ra/chrome/editor.yaml | 367 +++++++++++++++++ mods/ra/chrome/ingame-menu.yaml | 14 + mods/ra/mod.yaml | 1 + mods/ts/mod.yaml | 1 + 28 files changed, 2142 insertions(+), 11 deletions(-) create mode 100644 OpenRA.Mods.Common/EditorBrushes/EditorActorBrush.cs create mode 100644 OpenRA.Mods.Common/EditorBrushes/EditorDefaultBrush.cs create mode 100644 OpenRA.Mods.Common/EditorBrushes/EditorResourceBrush.cs create mode 100644 OpenRA.Mods.Common/EditorBrushes/EditorTileBrush.cs create mode 100644 OpenRA.Mods.Common/Widgets/EditorViewportControllerWidget.cs create mode 100644 OpenRA.Mods.Common/Widgets/Logic/Editor/ActorSelectorLogic.cs create mode 100644 OpenRA.Mods.Common/Widgets/Logic/Editor/LayerSelectorLogic.cs create mode 100644 OpenRA.Mods.Common/Widgets/Logic/Editor/MapEditorLogic.cs create mode 100644 OpenRA.Mods.Common/Widgets/Logic/Editor/MapEditorTabsLogic.cs create mode 100644 OpenRA.Mods.Common/Widgets/Logic/Editor/NewMapLogic.cs create mode 100644 OpenRA.Mods.Common/Widgets/Logic/Editor/SaveMapLogic.cs create mode 100644 OpenRA.Mods.Common/Widgets/Logic/Editor/TileSelectorLogic.cs create mode 100644 OpenRA.Mods.Common/Widgets/Logic/Ingame/LoadMapEditorLogic.cs create mode 100644 mods/cnc/chrome/editor.yaml create mode 100644 mods/ra/chrome/editor.yaml diff --git a/OpenRA.Mods.Common/EditorBrushes/EditorActorBrush.cs b/OpenRA.Mods.Common/EditorBrushes/EditorActorBrush.cs new file mode 100644 index 0000000000..0bf3e7cec9 --- /dev/null +++ b/OpenRA.Mods.Common/EditorBrushes/EditorActorBrush.cs @@ -0,0 +1,134 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using OpenRA.FileFormats; +using OpenRA.Graphics; +using OpenRA.Mods.Common; +using OpenRA.Mods.Common.Graphics; +using OpenRA.Mods.Common.Traits; +using OpenRA.Orders; +using OpenRA.Primitives; +using OpenRA.Traits; +using OpenRA.Widgets; + +namespace OpenRA.Mods.Common.Widgets +{ + public class EditorActorBrush : IEditorBrush + { + public readonly ActorInfo Actor; + + readonly WorldRenderer worldRenderer; + readonly World world; + readonly EditorActorLayer editorLayer; + readonly EditorViewportControllerWidget editorWidget; + readonly ActorPreviewWidget preview; + readonly CVec locationOffset; + readonly WVec previewOffset; + readonly PlayerReference owner; + + int facing = 92; + + public EditorActorBrush(EditorViewportControllerWidget editorWidget, ActorInfo actor, PlayerReference owner, WorldRenderer wr) + { + this.editorWidget = editorWidget; + worldRenderer = wr; + world = wr.World; + editorLayer = world.WorldActor.Trait(); + + Actor = actor; + this.owner = owner; + + preview = editorWidget.Get("DRAG_ACTOR_PREVIEW"); + preview.GetScale = () => worldRenderer.Viewport.Zoom; + preview.IsVisible = () => editorWidget.CurrentBrush == this; + + var buildingInfo = actor.Traits.GetOrDefault(); + if (buildingInfo != null) + { + locationOffset = -FootprintUtils.AdjustForBuildingSize(buildingInfo); + previewOffset = FootprintUtils.CenterOffset(world, buildingInfo); + } + + var td = new TypeDictionary(); + td.Add(new FacingInit(facing)); + td.Add(new TurretFacingInit(facing)); + td.Add(new OwnerInit(owner.Name)); + td.Add(new RaceInit(owner.Race)); + preview.SetPreview(actor, td); + + // The preview widget may be rendered by the higher-level code before it is ticked. + // Force a manual tick to ensure the bounds are set correctly for this first draw. + Tick(); + } + + public bool HandleMouseInput(MouseInput mi) + { + // Exclusively uses left and right mouse buttons, but nothing else + if (mi.Button != MouseButton.Left && mi.Button != MouseButton.Right) + return false; + + if (mi.Button == MouseButton.Right) + { + editorWidget.ClearBrush(); + return true; + } + + var cell = worldRenderer.Viewport.ViewToWorld(mi.Location); + if (mi.Button == MouseButton.Left && mi.Event == MouseInputEvent.Down && world.Map.Contains(cell)) + { + var newActorReference = new ActorReference(Actor.Name); + newActorReference.Add(new OwnerInit(owner.Name)); + + cell += locationOffset; + newActorReference.Add(new LocationInit(cell)); + + var ios = Actor.Traits.GetOrDefault(); + if (ios != null && ios.SharesCell) + { + var subcell = editorLayer.FreeSubCellAt(cell); + if (subcell != SubCell.Invalid) + newActorReference.Add(new SubCellInit(subcell)); + } + + var initDict = newActorReference.InitDict; + + if (Actor.Traits.Contains()) + initDict.Add(new FacingInit(facing)); + + if (Actor.Traits.Contains()) + initDict.Add(new TurretFacingInit(facing)); + + editorLayer.Add(newActorReference); + } + + return true; + } + + public void Tick() + { + var cell = worldRenderer.Viewport.ViewToWorld(Viewport.LastMousePos); + var pos = world.Map.CenterOfCell(cell + locationOffset) + previewOffset; + + var origin = worldRenderer.Viewport.WorldToViewPx(worldRenderer.ScreenPxPosition(pos)); + + var zoom = worldRenderer.Viewport.Zoom; + var s = preview.IdealPreviewSize; + var o = preview.PreviewOffset; + preview.Bounds.X = origin.X - (int)(zoom * (o.X + s.X / 2)); + preview.Bounds.Y = origin.Y - (int)(zoom * (o.Y + s.Y / 2)); + preview.Bounds.Width = (int)(zoom * s.X); + preview.Bounds.Height = (int)(zoom * s.Y); + } + } +} diff --git a/OpenRA.Mods.Common/EditorBrushes/EditorDefaultBrush.cs b/OpenRA.Mods.Common/EditorBrushes/EditorDefaultBrush.cs new file mode 100644 index 0000000000..5a8178192a --- /dev/null +++ b/OpenRA.Mods.Common/EditorBrushes/EditorDefaultBrush.cs @@ -0,0 +1,114 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using OpenRA.FileFormats; +using OpenRA.Graphics; +using OpenRA.Mods.Common; +using OpenRA.Mods.Common.Graphics; +using OpenRA.Mods.Common.Traits; +using OpenRA.Orders; +using OpenRA.Primitives; +using OpenRA.Traits; +using OpenRA.Widgets; + +namespace OpenRA.Mods.Common.Widgets +{ + public interface IEditorBrush + { + bool HandleMouseInput(MouseInput mi); + void Tick(); + } + + public class EditorDefaultBrush : IEditorBrush + { + public readonly ActorInfo Actor; + + readonly WorldRenderer worldRenderer; + readonly World world; + readonly EditorViewportControllerWidget editorWidget; + readonly EditorActorLayer editorLayer; + readonly Dictionary resources; + + public EditorDefaultBrush(EditorViewportControllerWidget editorWidget, WorldRenderer wr) + { + this.editorWidget = editorWidget; + worldRenderer = wr; + world = wr.World; + + editorLayer = world.WorldActor.Trait(); + resources = world.WorldActor.TraitsImplementing() + .ToDictionary(r => r.Info.ResourceType, r => r); + } + + public bool HandleMouseInput(MouseInput mi) + { + // Exclusively uses left and right mouse buttons, but nothing else + // Mouse move events are important for tooltips, so we always allow these through + if (mi.Button != MouseButton.Left && mi.Button != MouseButton.Right && mi.Event != MouseInputEvent.Move) + return false; + + var cell = worldRenderer.Viewport.ViewToWorld(mi.Location); + if (mi.Event == MouseInputEvent.Up) + return true; + + var underCursor = editorLayer.PreviewsAt(worldRenderer.Viewport.ViewToWorldPx(mi.Location)) + .FirstOrDefault(); + + ResourceType type; + if (underCursor != null) + editorWidget.SetTooltip(underCursor.Tooltip); + else if (world.Map.Contains(cell) && resources.TryGetValue(world.Map.MapResources.Value[cell].Type, out type)) + editorWidget.SetTooltip(type.Info.Name); + else + editorWidget.SetTooltip(null); + + // Finished with mouse move events, so let them bubble up the widget tree + if (mi.Event == MouseInputEvent.Move) + return false; + + if (mi.Button == MouseButton.Right) + { + editorWidget.SetTooltip(null); + + if (underCursor != null) + editorLayer.Remove(underCursor); + + if (world.Map.MapResources.Value[cell].Type != 0) + world.Map.MapResources.Value[cell] = new ResourceTile(); + } + else if (mi.Button == MouseButton.Left && mi.Event == MouseInputEvent.Down) + { + if (underCursor != null) + { + // Test case / demonstration of how to edit an existing actor + var facing = underCursor.Init(); + if (facing != null) + underCursor.ReplaceInit(new FacingInit((facing.Value(world) + 32) % 256)); + else if (underCursor.Info.Traits.WithInterface>().Any()) + underCursor.ReplaceInit(new FacingInit(32)); + + var turret = underCursor.Init(); + if (turret != null) + underCursor.ReplaceInit(new TurretFacingInit((turret.Value(world) + 32) % 256)); + else if (underCursor.Info.Traits.WithInterface>().Any()) + underCursor.ReplaceInit(new TurretFacingInit(32)); + } + } + + return true; + } + + public void Tick() { } + } +} diff --git a/OpenRA.Mods.Common/EditorBrushes/EditorResourceBrush.cs b/OpenRA.Mods.Common/EditorBrushes/EditorResourceBrush.cs new file mode 100644 index 0000000000..bf94b01a86 --- /dev/null +++ b/OpenRA.Mods.Common/EditorBrushes/EditorResourceBrush.cs @@ -0,0 +1,115 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using OpenRA.FileFormats; +using OpenRA.Graphics; +using OpenRA.Mods.Common; +using OpenRA.Mods.Common.Graphics; +using OpenRA.Mods.Common.Traits; +using OpenRA.Orders; +using OpenRA.Primitives; +using OpenRA.Traits; +using OpenRA.Widgets; + +namespace OpenRA.Mods.Common.Widgets +{ + public class EditorResourceBrush : IEditorBrush + { + public readonly ResourceTypeInfo ResourceType; + + readonly WorldRenderer worldRenderer; + readonly World world; + readonly EditorViewportControllerWidget editorWidget; + readonly SpriteWidget preview; + + public EditorResourceBrush(EditorViewportControllerWidget editorWidget, ResourceTypeInfo resource, WorldRenderer wr) + { + this.editorWidget = editorWidget; + ResourceType = resource; + worldRenderer = wr; + world = wr.World; + + preview = editorWidget.Get("DRAG_LAYER_PREVIEW"); + preview.Palette = resource.Palette; + preview.GetScale = () => worldRenderer.Viewport.Zoom; + preview.IsVisible = () => editorWidget.CurrentBrush == this; + + var variant = resource.Variants.FirstOrDefault(); + var sequenceProvider = wr.World.Map.Rules.Sequences[world.TileSet.Id]; + var sequence = sequenceProvider.GetSequence("resources", variant); + var sprite = sequence.GetSprite(resource.MaxDensity - 1); + preview.GetSprite = () => sprite; + + // The preview widget may be rendered by the higher-level code before it is ticked. + // Force a manual tick to ensure the bounds are set correctly for this first draw. + Tick(); + } + + public bool HandleMouseInput(MouseInput mi) + { + // Exclusively uses left and right mouse buttons, but nothing else + if (mi.Button != MouseButton.Left && mi.Button != MouseButton.Right) + return false; + + if (mi.Button == MouseButton.Right) + { + editorWidget.ClearBrush(); + return true; + } + + var cell = worldRenderer.Viewport.ViewToWorld(mi.Location); + + if (mi.Button == MouseButton.Left && AllowResourceAt(cell)) + { + var type = (byte)ResourceType.ResourceType; + var index = (byte)ResourceType.MaxDensity; + world.Map.MapResources.Value[cell] = new ResourceTile(type, index); + } + + return true; + } + + public bool AllowResourceAt(CPos cell) + { + if (!world.Map.Contains(cell)) + return false; + + var tile = world.Map.MapTiles.Value[cell]; + var tileInfo = world.TileSet.GetTileInfo(tile); + var terrainType = world.TileSet.TerrainInfo[tileInfo.TerrainType]; + + if (world.Map.MapResources.Value[cell].Type == ResourceType.ResourceType) + return false; + + if (!ResourceType.AllowedTerrainTypes.Contains(terrainType.Type)) + return false; + + return ResourceType.AllowOnRamps || tileInfo == null || tileInfo.RampType == 0; + } + + public void Tick() + { + var cell = worldRenderer.Viewport.ViewToWorld(Viewport.LastMousePos); + var offset = WVec.Zero; + var location = world.Map.CenterOfCell(cell) + offset; + + var cellScreenPosition = worldRenderer.ScreenPxPosition(location); + var cellScreenPixel = worldRenderer.Viewport.WorldToViewPx(cellScreenPosition); + var zoom = worldRenderer.Viewport.Zoom; + + preview.Bounds.X = cellScreenPixel.X; + preview.Bounds.Y = cellScreenPixel.Y; + } + } +} diff --git a/OpenRA.Mods.Common/EditorBrushes/EditorTileBrush.cs b/OpenRA.Mods.Common/EditorBrushes/EditorTileBrush.cs new file mode 100644 index 0000000000..47fcd26704 --- /dev/null +++ b/OpenRA.Mods.Common/EditorBrushes/EditorTileBrush.cs @@ -0,0 +1,151 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using OpenRA.FileFormats; +using OpenRA.Graphics; +using OpenRA.Mods.Common; +using OpenRA.Mods.Common.Graphics; +using OpenRA.Mods.Common.Traits; +using OpenRA.Orders; +using OpenRA.Primitives; +using OpenRA.Traits; +using OpenRA.Widgets; + +namespace OpenRA.Mods.Common.Widgets +{ + public class EditorTileBrush : IEditorBrush + { + public readonly ushort Template; + + readonly WorldRenderer worldRenderer; + readonly World world; + readonly EditorViewportControllerWidget editorWidget; + readonly TerrainTemplatePreviewWidget preview; + readonly Rectangle bounds; + + bool painting; + + public EditorTileBrush(EditorViewportControllerWidget editorWidget, ushort template, WorldRenderer wr) + { + this.editorWidget = editorWidget; + Template = template; + worldRenderer = wr; + world = wr.World; + + preview = editorWidget.Get("DRAG_TILE_PREVIEW"); + preview.GetScale = () => worldRenderer.Viewport.Zoom; + preview.IsVisible = () => editorWidget.CurrentBrush == this; + + preview.Template = world.TileSet.Templates.First(t => t.Value.Id == template).Value; + bounds = worldRenderer.Theater.TemplateBounds(preview.Template, Game.ModData.Manifest.TileSize, world.Map.TileShape); + + // The preview widget may be rendered by the higher-level code before it is ticked. + // Force a manual tick to ensure the bounds are set correctly for this first draw. + Tick(); + } + + public bool HandleMouseInput(MouseInput mi) + { + // Exclusively uses left and right mouse buttons, but nothing else + if (mi.Button != MouseButton.Left && mi.Button != MouseButton.Right) + return false; + + if (mi.Button == MouseButton.Right) + { + editorWidget.ClearBrush(); + return true; + } + + if (mi.Button == MouseButton.Left) + { + if (mi.Event == MouseInputEvent.Down) + painting = true; + else if (mi.Event == MouseInputEvent.Up) + painting = false; + } + + if (!painting) + return true; + + var map = world.Map; + var cell = worldRenderer.Viewport.ViewToWorld(mi.Location); + + if (mi.Event != MouseInputEvent.Down && mi.Event != MouseInputEvent.Move) + return true; + + var rules = map.Rules; + var tileset = rules.TileSets[map.Tileset]; + var template = tileset.Templates[Template]; + var baseHeight = map.Contains(cell) ? map.MapHeight.Value[cell] : 0; + if (mi.Event == MouseInputEvent.Move && PlacementOverlapsSameTemplate(template, cell)) + return true; + + var i = 0; + for (var y = 0; y < template.Size.Y; y++) + { + for (var x = 0; x < template.Size.X; x++, i++) + { + if (template.Contains(i) && template[i] != null) + { + var index = template.PickAny ? (byte)Game.CosmeticRandom.Next(0, template.TilesCount) : (byte)i; + var c = cell + new CVec(x, y); + if (!map.Contains(c)) + continue; + + map.MapTiles.Value[c] = new TerrainTile(Template, index); + map.MapHeight.Value[c] = (byte)(baseHeight + template[index].Height).Clamp(0, world.TileSet.MaxGroundHeight); + } + } + } + + return true; + } + + bool PlacementOverlapsSameTemplate(TerrainTemplateInfo template, CPos cell) + { + var map = world.Map; + var i = 0; + for (var y = 0; y < template.Size.Y; y++) + { + for (var x = 0; x < template.Size.X; x++, i++) + { + if (template.Contains(i) && template[i] != null) + { + var c = cell + new CVec(x, y); + if (map.Contains(c) && map.MapTiles.Value[c].Type == template.Id) + return true; + } + } + } + + return false; + } + + public void Tick() + { + var cell = worldRenderer.Viewport.ViewToWorld(Viewport.LastMousePos); + var offset = WVec.Zero; + var location = world.Map.CenterOfCell(cell) + offset; + + var cellScreenPosition = worldRenderer.ScreenPxPosition(location); + var cellScreenPixel = worldRenderer.Viewport.WorldToViewPx(cellScreenPosition); + var zoom = worldRenderer.Viewport.Zoom; + + preview.Bounds.X = cellScreenPixel.X + (int)(zoom * bounds.X); + preview.Bounds.Y = cellScreenPixel.Y + (int)(zoom * bounds.Y); + preview.Bounds.Width = (int)(zoom * bounds.Width); + preview.Bounds.Height = (int)(zoom * bounds.Height); + } + } +} diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index d109070c18..cc1b29a043 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -539,6 +539,7 @@ + @@ -559,6 +560,13 @@ + + + + + + + @@ -628,8 +636,13 @@ + + + + + @@ -644,4 +657,7 @@ cd "$(SolutionDir)" --> + + + \ No newline at end of file diff --git a/OpenRA.Mods.Common/Traits/World/EditorResourceLayer.cs b/OpenRA.Mods.Common/Traits/World/EditorResourceLayer.cs index e38dd1403e..c7608ed245 100644 --- a/OpenRA.Mods.Common/Traits/World/EditorResourceLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/EditorResourceLayer.cs @@ -98,26 +98,33 @@ namespace OpenRA.Mods.Common.Traits return t.Variants.Keys.Random(Game.CosmeticRandom); } + public int ResourceDensityAt(CPos c) + { + // Set density based on the number of neighboring resources + var adjacent = 0; + var type = Tiles[c].Type; + for (var u = -1; u < 2; u++) + for (var v = -1; v < 2; v++) + if (Map.MapResources.Value[c + new CVec(u, v)].Type == type.Info.ResourceType) + adjacent++; + + return Math.Max(int2.Lerp(0, type.Info.MaxDensity, adjacent, 9), 1); + } + public virtual CellContents UpdateDirtyTile(CPos c) { var t = Tiles[c]; + var type = t.Type; // Empty tile - if (t.Type == null) + if (type == null) { t.Sprite = null; return t; } // Set density based on the number of neighboring resources - var adjacent = 0; - var type = t.Type; - for (var u = -1; u < 2; u++) - for (var v = -1; v < 2; v++) - if (Map.MapResources.Value[c + new CVec(u, v)].Type == type.Info.ResourceType) - adjacent++; - - t.Density = Math.Max(int2.Lerp(0, type.Info.MaxDensity, adjacent, 9), 1); + t.Density = ResourceDensityAt(c); var sprites = type.Variants[t.Variant]; var frame = int2.Lerp(0, sprites.Length - 1, t.Density - 1, type.Info.MaxDensity); diff --git a/OpenRA.Mods.Common/Traits/World/LoadWidgetAtGameStart.cs b/OpenRA.Mods.Common/Traits/World/LoadWidgetAtGameStart.cs index 8ab785161f..75462811b8 100644 --- a/OpenRA.Mods.Common/Traits/World/LoadWidgetAtGameStart.cs +++ b/OpenRA.Mods.Common/Traits/World/LoadWidgetAtGameStart.cs @@ -23,6 +23,9 @@ namespace OpenRA.Mods.Common.Traits [Desc("The widget tree to open when a regular map is loaded (i.e. the ingame UI).")] public readonly string IngameRoot = "INGAME_ROOT"; + [Desc("The widget tree to open when the map editor is loaded.")] + public readonly string EditorRoot = "EDITOR_ROOT"; + [Desc("Remove any existing UI when a map is loaded.")] public readonly bool ClearRoot = true; @@ -44,7 +47,9 @@ namespace OpenRA.Mods.Common.Traits if (info.ClearRoot) Ui.ResetAll(); - var widget = world.Type == WorldType.Shellmap ? info.ShellmapRoot : info.IngameRoot; + var widget = world.Type == WorldType.Shellmap ? info.ShellmapRoot : + world.Type == WorldType.Editor ? info.EditorRoot : info.IngameRoot; + Game.LoadWidget(world, widget, Ui.Root, new WidgetArgs()); } } diff --git a/OpenRA.Mods.Common/Widgets/EditorViewportControllerWidget.cs b/OpenRA.Mods.Common/Widgets/EditorViewportControllerWidget.cs new file mode 100644 index 0000000000..78ceb84c71 --- /dev/null +++ b/OpenRA.Mods.Common/Widgets/EditorViewportControllerWidget.cs @@ -0,0 +1,98 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using OpenRA.FileFormats; +using OpenRA.Graphics; +using OpenRA.Mods.Common; +using OpenRA.Mods.Common.Graphics; +using OpenRA.Mods.Common.Traits; +using OpenRA.Orders; +using OpenRA.Primitives; +using OpenRA.Traits; +using OpenRA.Widgets; + +namespace OpenRA.Mods.Common.Widgets +{ + public class EditorViewportControllerWidget : Widget + { + public IEditorBrush CurrentBrush { get; private set; } + + public readonly string TooltipContainer; + public readonly string TooltipTemplate; + + readonly Lazy tooltipContainer; + readonly EditorDefaultBrush defaultBrush; + readonly WorldRenderer worldRenderer; + + bool enableTooltips; + + [ObjectCreator.UseCtor] + public EditorViewportControllerWidget(World world, WorldRenderer worldRenderer) + { + this.worldRenderer = worldRenderer; + tooltipContainer = Exts.Lazy(() => Ui.Root.Get(TooltipContainer)); + CurrentBrush = defaultBrush = new EditorDefaultBrush(this, worldRenderer); + } + + public void ClearBrush() { SetBrush(null); } + public void SetBrush(IEditorBrush brush) + { + CurrentBrush = brush ?? defaultBrush; + } + + public override void MouseEntered() + { + enableTooltips = true; + } + + public override void MouseExited() + { + tooltipContainer.Value.RemoveTooltip(); + enableTooltips = false; + } + + public void SetTooltip(string tooltip) + { + if (!enableTooltips) + return; + + if (tooltip != null) + { + Func getTooltip = () => tooltip; + tooltipContainer.Value.SetTooltip(TooltipTemplate, new WidgetArgs() { { "getText", getTooltip } }); + } + else + tooltipContainer.Value.RemoveTooltip(); + } + + public override bool HandleMouseInput(MouseInput mi) + { + if (CurrentBrush.HandleMouseInput(mi)) + return true; + + return base.HandleMouseInput(mi); + } + + WPos cachedViewportPosition; + public override void Tick() + { + // Clear any tooltips when the viewport is scrolled using the keyboard + if (worldRenderer.Viewport.CenterPosition != cachedViewportPosition) + SetTooltip(null); + + cachedViewportPosition = worldRenderer.Viewport.CenterPosition; + CurrentBrush.Tick(); + } + } +} diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/ActorSelectorLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/ActorSelectorLogic.cs new file mode 100644 index 0000000000..909c9513ba --- /dev/null +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/ActorSelectorLogic.cs @@ -0,0 +1,150 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Drawing; +using System.Linq; +using OpenRA.FileFormats; +using OpenRA.Graphics; +using OpenRA.Mods.Common; +using OpenRA.Mods.Common.Graphics; +using OpenRA.Mods.Common.Traits; +using OpenRA.Mods.Common.Widgets; +using OpenRA.Primitives; +using OpenRA.Traits; +using OpenRA.Widgets; + +namespace OpenRA.Mods.Common.Widgets.Logic +{ + public class ActorSelectorLogic + { + readonly EditorViewportControllerWidget editor; + readonly DropDownButtonWidget ownersDropDown; + readonly ScrollPanelWidget panel; + readonly ScrollItemWidget itemTemplate; + readonly Ruleset modRules; + readonly World world; + readonly WorldRenderer worldRenderer; + + PlayerReference selectedOwner; + + [ObjectCreator.UseCtor] + public ActorSelectorLogic(Widget widget, World world, WorldRenderer worldRenderer, Ruleset modRules) + { + this.modRules = modRules; + this.world = world; + this.worldRenderer = worldRenderer; + + editor = widget.Parent.Get("MAP_EDITOR"); + ownersDropDown = widget.Get("OWNERS_DROPDOWN"); + + panel = widget.Get("ACTORTEMPLATE_LIST"); + itemTemplate = panel.Get("ACTORPREVIEW_TEMPLATE"); + panel.Layout = new GridLayout(panel); + + var editorLayer = world.WorldActor.Trait(); + + selectedOwner = editorLayer.Players.Players.Values.First(); + Func setupItem = (option, template) => + { + var item = ScrollItemWidget.Setup(template, () => selectedOwner == option, () => + { + selectedOwner = option; + + ownersDropDown.Text = selectedOwner.Name; + ownersDropDown.TextColor = selectedOwner.Color.RGB; + + IntializeActorPreviews(); + }); + + item.Get("LABEL").GetText = () => option.Name; + item.GetColor = () => option.Color.RGB; + + return item; + }; + + ownersDropDown.OnClick = () => + { + var owners = editorLayer.Players.Players.Values.OrderBy(p => p.Name); + ownersDropDown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 270, owners, setupItem); + }; + + ownersDropDown.Text = selectedOwner.Name; + ownersDropDown.TextColor = selectedOwner.Color.RGB; + + IntializeActorPreviews(); + } + + void IntializeActorPreviews() + { + panel.RemoveChildren(); + + var actors = modRules.Actors.Where(a => !a.Value.Name.Contains('^')) + .Select(a => a.Value); + + foreach (var a in actors) + { + var actor = a; + if (actor.Traits.Contains()) // bridge layer takes care about that automatically + continue; + + if (!actor.Traits.Contains()) + continue; + + var filter = actor.Traits.GetOrDefault(); + if (filter != null) + { + if (filter.ExcludeTilesets != null && filter.ExcludeTilesets.Contains(world.TileSet.Id)) + continue; + if (filter.RequireTilesets != null && !filter.RequireTilesets.Contains(world.TileSet.Id)) + continue; + } + + var td = new TypeDictionary(); + td.Add(new FacingInit(92)); + td.Add(new TurretFacingInit(92)); + td.Add(new HideBibPreviewInit()); + td.Add(new OwnerInit(selectedOwner.Name)); + td.Add(new RaceInit(selectedOwner.Race)); + + try + { + var item = ScrollItemWidget.Setup(itemTemplate, + () => { var brush = editor.CurrentBrush as EditorActorBrush; return brush != null && brush.Actor == actor; }, + () => editor.SetBrush(new EditorActorBrush(editor, actor, selectedOwner, worldRenderer))); + + var preview = item.Get("ACTOR_PREVIEW"); + preview.SetPreview(actor, td); + + // Scale templates to fit within the panel + var scale = 1f; + if (scale * preview.IdealPreviewSize.X > itemTemplate.Bounds.Width) + scale = (float)(itemTemplate.Bounds.Width - panel.ItemSpacing) / (float)preview.IdealPreviewSize.X; + + preview.GetScale = () => scale; + preview.Bounds.Width = (int)(scale * preview.IdealPreviewSize.X); + preview.Bounds.Height = (int)(scale * preview.IdealPreviewSize.Y); + + item.Bounds.Width = preview.Bounds.Width + 2 * preview.Bounds.X; + item.Bounds.Height = preview.Bounds.Height + 2 * preview.Bounds.Y; + item.IsVisible = () => true; + item.GetTooltipText = () => actor.Name; + panel.AddChild(item); + } + catch + { + Log.Write("debug", "Map editor ignoring actor {0}, because of missing sprites for tileset {1}.", + actor.Name, world.TileSet.Id); + continue; + } + } + } + } +} diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/LayerSelectorLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/LayerSelectorLogic.cs new file mode 100644 index 0000000000..2412f4c8d9 --- /dev/null +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/LayerSelectorLogic.cs @@ -0,0 +1,84 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Linq; +using OpenRA.FileFormats; +using OpenRA.Graphics; +using OpenRA.Traits; +using OpenRA.Widgets; + +namespace OpenRA.Mods.Common.Widgets.Logic +{ + public class LayerSelectorLogic + { + readonly EditorViewportControllerWidget editor; + readonly Ruleset modRules; + readonly World world; + readonly WorldRenderer worldRenderer; + + readonly ScrollPanelWidget layerTemplateList; + readonly ScrollItemWidget layerPreviewTemplate; + + [ObjectCreator.UseCtor] + public LayerSelectorLogic(Widget widget, WorldRenderer worldRenderer, Ruleset modRules) + { + this.modRules = modRules; + this.worldRenderer = worldRenderer; + this.world = worldRenderer.World; + + editor = widget.Parent.Get("MAP_EDITOR"); + + layerTemplateList = widget.Get("LAYERTEMPLATE_LIST"); + layerTemplateList.Layout = new GridLayout(layerTemplateList); + layerPreviewTemplate = layerTemplateList.Get("LAYERPREVIEW_TEMPLATE"); + + IntializeLayerPreview(widget); + } + + void IntializeLayerPreview(Widget widget) + { + layerTemplateList.RemoveChildren(); + + var resources = modRules.Actors["world"].Traits.WithInterface(); + foreach (var resource in resources) + { + var newResourcePreviewTemplate = ScrollItemWidget.Setup(layerPreviewTemplate, + () => { var brush = editor.CurrentBrush as EditorResourceBrush; return brush != null && brush.ResourceType == resource; }, + () => editor.SetBrush(new EditorResourceBrush(editor, resource, worldRenderer))); + + newResourcePreviewTemplate.Bounds.X = 0; + newResourcePreviewTemplate.Bounds.Y = 0; + + var layerPreview = newResourcePreviewTemplate.Get("LAYER_PREVIEW"); + layerPreview.IsVisible = () => true; + layerPreview.GetPalette = () => resource.Palette; + + var variant = resource.Variants.FirstOrDefault(); + var sequenceProvier = modRules.Sequences[world.TileSet.Id]; + var sequence = sequenceProvier.GetSequence("resources", variant); + var frame = sequence.Frames != null ? sequence.Frames.Last() : resource.MaxDensity - 1; + layerPreview.GetSprite = () => sequence.GetSprite(frame); + + var tileWidth = Game.ModData.Manifest.TileSize.Width; + var tileHeight = Game.ModData.Manifest.TileSize.Height; + layerPreview.Bounds.Width = tileWidth; + layerPreview.Bounds.Height = tileHeight; + newResourcePreviewTemplate.Bounds.Width = tileWidth + (layerPreview.Bounds.X * 2); + newResourcePreviewTemplate.Bounds.Height = tileHeight + (layerPreview.Bounds.Y * 2); + + newResourcePreviewTemplate.IsVisible = () => true; + newResourcePreviewTemplate.GetTooltipText = () => resource.Name; + + layerTemplateList.AddChild(newResourcePreviewTemplate); + } + } + } +} diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/MapEditorLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/MapEditorLogic.cs new file mode 100644 index 0000000000..2a1ee4a80b --- /dev/null +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/MapEditorLogic.cs @@ -0,0 +1,70 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Drawing; +using System.Linq; +using OpenRA.Graphics; +using OpenRA.Mods.Common.Traits; +using OpenRA.Primitives; +using OpenRA.Traits; +using OpenRA.Widgets; + +namespace OpenRA.Mods.Common.Widgets.Logic +{ + public class MapEditorLogic + { + [ObjectCreator.UseCtor] + public MapEditorLogic(Widget widget, World world, WorldRenderer worldRenderer) + { + var gridButton = widget.GetOrNull("GRID_BUTTON"); + var terrainGeometryTrait = world.WorldActor.Trait(); + + if (gridButton != null && terrainGeometryTrait != null) + { + gridButton.OnClick = () => terrainGeometryTrait.Enabled ^= true; + gridButton.IsHighlighted = () => terrainGeometryTrait.Enabled; + } + + var zoomDropdown = widget.Get("ZOOM_BUTTON"); + if (zoomDropdown != null) + { + var selectedZoom = Game.Settings.Graphics.PixelDouble ? 2f : 1f; + var selectedLabel = selectedZoom.ToString(); + Func setupItem = (zoom, itemTemplate) => + { + var item = ScrollItemWidget.Setup(itemTemplate, + () => selectedZoom == zoom, + () => { worldRenderer.Viewport.Zoom = selectedZoom = zoom; selectedLabel = zoom.ToString(); }); + + var label = zoom.ToString(); + item.Get("LABEL").GetText = () => label; + + return item; + }; + + var options = new[] { 2f, 1f, 0.5f, 0.25f }; + zoomDropdown.OnMouseDown = _ => zoomDropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 150, options, setupItem); + zoomDropdown.GetText = () => selectedLabel; + zoomDropdown.GetKey = _ => Game.Settings.Keys.TogglePixelDoubleKey; + zoomDropdown.OnKeyPress = e => + { + var key = Hotkey.FromKeyInput(e); + if (key != Game.Settings.Keys.TogglePixelDoubleKey) + return; + + var selected = (options.IndexOf(selectedZoom) + 1) % options.Length; + worldRenderer.Viewport.Zoom = selectedZoom = options[selected]; + selectedLabel = selectedZoom.ToString(); + }; + } + } + } +} diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/MapEditorTabsLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/MapEditorTabsLogic.cs new file mode 100644 index 0000000000..5edae36850 --- /dev/null +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/MapEditorTabsLogic.cs @@ -0,0 +1,53 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ + +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using OpenRA.GameRules; +using OpenRA.Graphics; +using OpenRA.Widgets; + +namespace OpenRA.Mods.Common.Widgets.Logic +{ + public class MapEditorTabsLogic + { + protected enum MenuType { Tiles, Layers, Actors } + protected MenuType menuType = MenuType.Tiles; + + [ObjectCreator.UseCtor] + public MapEditorTabsLogic(Widget widget, WorldRenderer worldRenderer) + { + var tabContainer = widget.Get("MAP_EDITOR_TAB_CONTAINER"); + + var tilesTab = tabContainer.Get("TILES_TAB"); + tilesTab.IsHighlighted = () => menuType == MenuType.Tiles; + tilesTab.OnClick = () => { menuType = MenuType.Tiles; }; + + var overlaysTab = tabContainer.Get("OVERLAYS_TAB"); + overlaysTab.IsHighlighted = () => menuType == MenuType.Layers; + overlaysTab.OnClick = () => { menuType = MenuType.Layers; }; + + var actorsTab = tabContainer.Get("ACTORS_TAB"); + actorsTab.IsHighlighted = () => menuType == MenuType.Actors; + actorsTab.OnClick = () => { menuType = MenuType.Actors; }; + + var tileContainer = widget.Parent.Get("TILE_WIDGETS"); + tileContainer.IsVisible = () => menuType == MenuType.Tiles; + + var layerContainer = widget.Parent.Get("LAYER_WIDGETS"); + layerContainer.IsVisible = () => menuType == MenuType.Layers; + + var actorContainer = widget.Parent.Get("ACTOR_WIDGETS"); + actorContainer.IsVisible = () => menuType == MenuType.Actors; + } + } +} diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/NewMapLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/NewMapLogic.cs new file mode 100644 index 0000000000..c9d07117b4 --- /dev/null +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/NewMapLogic.cs @@ -0,0 +1,94 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using OpenRA; +using OpenRA.FileFormats; +using OpenRA.Mods.Common.Widgets; +using OpenRA.Traits; +using OpenRA.Widgets; + +namespace OpenRA.Mods.Common.Widgets.Logic +{ + public class NewMapLogic + { + Widget panel; + + [ObjectCreator.UseCtor] + public NewMapLogic(Action onExit, Action onSelect, Ruleset modRules, Widget widget, World world) + { + panel = widget; + + panel.Get("CANCEL_BUTTON").OnClick = () => { Ui.CloseWindow(); onExit(); }; + + var tilesetDropDown = panel.Get("TILESET"); + var tilesets = modRules.TileSets.Select(t => t.Key).ToList(); + Func setupItem = (option, template) => + { + var item = ScrollItemWidget.Setup(template, + () => tilesetDropDown.Text == option, + () => { tilesetDropDown.Text = option; }); + item.Get("LABEL").GetText = () => option; + return item; + }; + tilesetDropDown.Text = tilesets.First(); + tilesetDropDown.OnClick = () => + tilesetDropDown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 210, tilesets, setupItem); + + var widthTextField = panel.Get("WIDTH"); + var heightTextField = panel.Get("HEIGHT"); + + panel.Get("CREATE_BUTTON").OnClick = () => + { + var tileset = modRules.TileSets[tilesetDropDown.Text]; + var map = Map.FromTileset(tileset); + + int width, height; + int.TryParse(widthTextField.Text, out width); + int.TryParse(heightTextField.Text, out height); + + // Require at least a 2x2 playable area so that the + // ground is visible through the edge shroud + width = Math.Max(2, width); + height = Math.Max(2, height); + + map.Resize(width + 2, height + tileset.MaxGroundHeight + 2); + map.ResizeCordon(1, 1, width + 1, height + tileset.MaxGroundHeight + 1); + map.PlayerDefinitions = new MapPlayers(map.Rules, map.SpawnPoints.Value.Length).ToMiniYaml(); + map.FixOpenAreas(modRules); + + var userMapFolder = Game.ModData.Manifest.MapFolders.First(f => f.Value == "User").Key; + + // Ignore optional flag + if (userMapFolder.StartsWith("~")) + userMapFolder = userMapFolder.Substring(1); + + var mapDir = Platform.ResolvePath(userMapFolder); + Directory.CreateDirectory(mapDir); + var tempLocation = Path.Combine(mapDir, "temp") + ".oramap"; + map.Save(tempLocation); // TODO: load it right away and save later properly + + var newMap = new Map(tempLocation); + Game.ModData.MapCache[newMap.Uid].UpdateFromMap(newMap, MapClassification.User); + + ConnectionLogic.Connect(System.Net.IPAddress.Loopback.ToString(), + Game.CreateLocalServer(newMap.Uid), + "", + () => { Game.LoadEditor(newMap.Uid); }, + () => { Game.CloseServer(); onExit(); }); + onSelect(newMap.Uid); + }; + } + } +} diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/SaveMapLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/SaveMapLogic.cs new file mode 100644 index 0000000000..73144f0c94 --- /dev/null +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/SaveMapLogic.cs @@ -0,0 +1,122 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using OpenRA.Mods.Common.Traits; +using OpenRA.Widgets; + +namespace OpenRA.Mods.Common.Widgets.Logic +{ + public class SaveMapLogic + { + [ObjectCreator.UseCtor] + public SaveMapLogic(Widget widget, Action onExit, World world) + { + var newMap = world.Map; + + var title = widget.GetOrNull("TITLE"); + if (title != null) + title.Text = newMap.Title; + + var description = widget.GetOrNull("DESCRIPTION"); + if (description != null) + description.Text = newMap.Description; + + var author = widget.GetOrNull("AUTHOR"); + if (author != null) + author.Text = newMap.Author; + + var visibilityDropdown = widget.GetOrNull("CLASS_DROPDOWN"); + if (visibilityDropdown != null) + { + var mapVisibility = new List(Enum.GetNames(typeof(MapVisibility))); + Func setupItem = (option, template) => + { + var item = ScrollItemWidget.Setup(template, + () => visibilityDropdown.Text == option, + () => { visibilityDropdown.Text = option; }); + item.Get("LABEL").GetText = () => option; + return item; + }; + visibilityDropdown.Text = Enum.GetName(typeof(MapVisibility), newMap.Visibility); + visibilityDropdown.OnClick = () => + visibilityDropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 210, mapVisibility, setupItem); + } + + var pathDropdown = widget.GetOrNull("PATH_DROPDOWN"); + if (pathDropdown != null) + { + var mapFolders = new List(); + foreach (var mapFolder in Game.ModData.Manifest.MapFolders.Keys) + { + var folder = mapFolder; + if (mapFolder.StartsWith("~")) + folder = mapFolder.Substring(1); + + mapFolders.Add(Platform.ResolvePath(folder)); + } + + Func setupItem = (option, template) => + { + var item = ScrollItemWidget.Setup(template, + () => pathDropdown.Text == Platform.UnresolvePath(option), + () => { pathDropdown.Text = Platform.UnresolvePath(option); }); + item.Get("LABEL").GetText = () => option; + return item; + }; + + var userMapFolder = Game.ModData.Manifest.MapFolders.First(f => f.Value == "User").Key; + if (userMapFolder.StartsWith("~")) + userMapFolder = userMapFolder.Substring(1); + pathDropdown.Text = Platform.UnresolvePath(userMapFolder); + pathDropdown.OnClick = () => + pathDropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 210, mapFolders, setupItem); + } + + var filename = widget.GetOrNull("FILENAME"); + if (filename != null) + filename.Text = Path.GetFileName(world.Map.Path); + + var close = widget.GetOrNull("CLOSE"); + if (close != null) + close.OnClick = () => { Ui.CloseWindow(); onExit(); }; + + var save = widget.GetOrNull("SAVE"); + if (save != null && !string.IsNullOrEmpty(filename.Text)) + { + var editorLayer = world.WorldActor.Trait(); + save.OnClick = () => + { + newMap.Title = title.Text; + newMap.Description = description.Text; + newMap.Author = author.Text; + newMap.Visibility = (MapVisibility)Enum.Parse(typeof(MapVisibility), visibilityDropdown.Text); + newMap.ActorDefinitions = editorLayer.Save(); + newMap.PlayerDefinitions = editorLayer.Players.ToMiniYaml(); + newMap.RequiresMod = Game.ModData.Manifest.Mod.Id; + + var combinedPath = Path.Combine(pathDropdown.Text, filename.Text); + var resolvedPath = Platform.ResolvePath(combinedPath); + newMap.Save(resolvedPath); + + // Update the map cache so it can be loaded without restarting the game + Game.ModData.MapCache[newMap.Uid].UpdateFromMap(newMap, MapClassification.User); + + Console.WriteLine("Saved current map at {0}", resolvedPath); + Ui.CloseWindow(); + onExit(); + }; + } + } + } +} diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/TileSelectorLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/TileSelectorLogic.cs new file mode 100644 index 0000000000..6693e6e3a3 --- /dev/null +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/TileSelectorLogic.cs @@ -0,0 +1,92 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Linq; +using OpenRA.FileFormats; +using OpenRA.Graphics; +using OpenRA.Widgets; + +namespace OpenRA.Mods.Common.Widgets.Logic +{ + public class TileSelectorLogic + { + readonly EditorViewportControllerWidget editor; + readonly ScrollPanelWidget panel; + readonly ScrollItemWidget itemTemplate; + + [ObjectCreator.UseCtor] + public TileSelectorLogic(Widget widget, WorldRenderer worldRenderer, Ruleset modRules) + { + var tileset = modRules.TileSets[worldRenderer.World.Map.Tileset]; + + editor = widget.Parent.Get("MAP_EDITOR"); + panel = widget.Get("TILETEMPLATE_LIST"); + itemTemplate = panel.Get("TILEPREVIEW_TEMPLATE"); + panel.Layout = new GridLayout(panel); + + var tileCategorySelector = widget.Get("TILE_CATEGORY"); + var categories = tileset.EditorTemplateOrder; + Func setupItem = (option, template) => + { + var item = ScrollItemWidget.Setup(template, + () => tileCategorySelector.Text == option, + () => { tileCategorySelector.Text = option; IntializeTilePreview(widget, worldRenderer, tileset, option); }); + + item.Get("LABEL").GetText = () => option; + return item; + }; + + tileCategorySelector.OnClick = () => + tileCategorySelector.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 270, categories, setupItem); + + tileCategorySelector.Text = categories.First(); + IntializeTilePreview(widget, worldRenderer, tileset, categories.First()); + } + + void IntializeTilePreview(Widget widget, WorldRenderer worldRenderer, TileSet tileset, string category) + { + panel.RemoveChildren(); + + var tileIds = tileset.Templates + .Where(t => t.Value.Category == category) + .Select(t => t.Value.Id); + + foreach (var t in tileIds) + { + var tileId = t; + var item = ScrollItemWidget.Setup(itemTemplate, + () => { var brush = editor.CurrentBrush as EditorTileBrush; return brush != null && brush.Template == tileId; }, + () => editor.SetBrush(new EditorTileBrush(editor, tileId, worldRenderer))); + + var preview = item.Get("TILE_PREVIEW"); + var template = tileset.Templates[tileId]; + var bounds = worldRenderer.Theater.TemplateBounds(template, Game.ModData.Manifest.TileSize, worldRenderer.World.Map.TileShape); + + // Scale templates to fit within the panel + var scale = 1f; + while (scale * bounds.Width > itemTemplate.Bounds.Width) + scale /= 2; + + preview.Template = template; + preview.GetScale = () => scale; + preview.Bounds.Width = (int)(scale * bounds.Width); + preview.Bounds.Height = (int)(scale * bounds.Height); + + item.Bounds.Width = preview.Bounds.Width + 2 * preview.Bounds.X; + item.Bounds.Height = preview.Bounds.Height + 2 * preview.Bounds.Y; + item.IsVisible = () => true; + item.GetTooltipText = () => tileId.ToString(); + + panel.AddChild(item); + } + } + } +} diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/LoadMapEditorLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/LoadMapEditorLogic.cs new file mode 100644 index 0000000000..74b6320ca3 --- /dev/null +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/LoadMapEditorLogic.cs @@ -0,0 +1,24 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using OpenRA.Widgets; + +namespace OpenRA.Mods.Common.Widgets.Logic +{ + public class LoadMapEditorLogic + { + [ObjectCreator.UseCtor] + public LoadMapEditorLogic(Widget widget, World world) + { + var editorRoot = widget.Get("WORLD_ROOT"); + Game.LoadWidget(world, "EDITOR_WORLD_ROOT", editorRoot, new WidgetArgs()); + } + } +} diff --git a/OpenRA.Mods.Common/Widgets/SpriteWidget.cs b/OpenRA.Mods.Common/Widgets/SpriteWidget.cs index cde5e4a9e8..a9858140cc 100644 --- a/OpenRA.Mods.Common/Widgets/SpriteWidget.cs +++ b/OpenRA.Mods.Common/Widgets/SpriteWidget.cs @@ -16,6 +16,7 @@ namespace OpenRA.Mods.Common.Widgets { public class SpriteWidget : Widget { + public Func GetScale = () => 1f; public string Palette = "chrome"; public Func GetPalette; public Func GetSprite; @@ -44,6 +45,7 @@ namespace OpenRA.Mods.Common.Widgets Sprite cachedSprite = null; string cachedPalette = null; + float cachedScale; PaletteReference pr; float2 offset = float2.Zero; @@ -51,6 +53,7 @@ namespace OpenRA.Mods.Common.Widgets { var sprite = GetSprite(); var palette = GetPalette(); + var scale = GetScale(); if (sprite == null || palette == null) return; @@ -67,7 +70,14 @@ namespace OpenRA.Mods.Common.Widgets cachedPalette = palette; } - Game.Renderer.SpriteRenderer.DrawSprite(sprite, RenderOrigin + offset, pr); + if (scale != cachedScale) + { + offset *= scale; + cachedScale = scale; + } + + var size = new float2(sprite.Size.X * scale, sprite.Size.Y * scale); + Game.Renderer.SpriteRenderer.DrawSprite(sprite, RenderOrigin + offset, pr, size); } } } diff --git a/OpenRA.Mods.D2k/Traits/World/D2kEditorResourceLayer.cs b/OpenRA.Mods.D2k/Traits/World/D2kEditorResourceLayer.cs index c44441ed6a..f46354380f 100644 --- a/OpenRA.Mods.D2k/Traits/World/D2kEditorResourceLayer.cs +++ b/OpenRA.Mods.D2k/Traits/World/D2kEditorResourceLayer.cs @@ -42,6 +42,8 @@ namespace OpenRA.Mods.D2k.Traits return t; } + t.Density = ResourceDensityAt(c); + int index; var clear = FindClearSides(t.Type, c); if (clear == ClearSides.None) diff --git a/mods/cnc/chrome/editor.yaml b/mods/cnc/chrome/editor.yaml new file mode 100644 index 0000000000..8d3df16217 --- /dev/null +++ b/mods/cnc/chrome/editor.yaml @@ -0,0 +1,378 @@ +Container@NEW_MAP_BG: + Logic: NewMapLogic + X: (WINDOW_RIGHT - WIDTH)/2 + Y: (WINDOW_BOTTOM - HEIGHT)/2 + Width: 300 + Height: 150 + Children: + Label@TITLE: + Text:New Map + Width: PARENT_RIGHT + Y: 0-25 + Font: BigBold + Contrast: true + Align: Center + Background@bg: + Width: PARENT_RIGHT + Height: 115 + Background: panel-black + Children: + Label@TILESET_LABEL: + X: 5 + Y: 19 + Width: 95 + Height: 25 + Align: Right + Text: Tileset: + DropDownButton@TILESET: + X: 105 + Y: 22 + Width: 150 + Height: 25 + Label@WIDTH_LABEL: + X: 5 + Y: 59 + Width: 95 + Height: 25 + Align: Right + Text: Width: + TextField@WIDTH: + X: 105 + Y: 60 + Width: 50 + MaxLength: 3 + Height: 25 + Text: 128 + Label@HEIGHT_LABEL: + X: 115 + Y: 59 + Width: 95 + Height: 25 + Align: Right + Text: Height: + TextField@HEIGHT: + X: 215 + Y: 60 + Width: 50 + MaxLength: 3 + Height: 25 + Text: 128 + Button@CREATE_BUTTON: + X: 0 + Y: 114 + Width: 140 + Height: 35 + Text: Create + Font: Bold + Key: return + Button@CANCEL_BUTTON: + X: PARENT_RIGHT - WIDTH + Y: 114 + Width: 140 + Height: 35 + Text: Cancel + Font: Bold + Key: escape + +Container@SAVE_MAP_PANEL: + X: (WINDOW_RIGHT - WIDTH)/2 + Y: (WINDOW_BOTTOM - HEIGHT)/2 + Width: 350 + Height: 330 + Children: + Label@TITLE: + Text: Save Map + Width: PARENT_RIGHT + Y: 0-25 + Font: BigBold + Contrast: true + Align: Center + Background@SAVE_MAP_BACKGROUND: + Logic: SaveMapLogic + Width: PARENT_RIGHT + Height: 295 + Background: panel-black + Children: + Label@TITLE_LABEL: + X: 10 + Y: 39 + Width: 95 + Height: 25 + Align: Right + Text: Title: + TextField@TITLE: + X: 110 + Y: 40 + Width: 210 + MaxLength: 50 + Height: 25 + Label@DESCRIPTION_LABEL: + X: 10 + Y: 79 + Width: 95 + Height: 25 + Align: Right + Text: Description: + TextField@DESCRIPTION: + X: 110 + Y: 80 + Width: 210 + MaxLength: 50 + Height: 25 + Label@AUTHOR_LABEL: + X: 10 + Y: 119 + Width: 95 + Height: 25 + Align: Right + Text: Author: + TextField@AUTHOR: + X: 110 + Y: 120 + Width: 210 + MaxLength: 50 + Height: 25 + Label@CLASS_LABEL: + X: 10 + Y: 160 + Width: 95 + Height: 25 + Align: Right + Text: Class: + DropDownButton@CLASS_DROPDOWN: + X: 110 + Y: 160 + Width: 210 + Height: 25 + Label@PATH_LABEL: + X: 10 + Y: 199 + Width: 40 + Height: 25 + Align: Right + Text: Path: + DropDownButton@PATH_DROPDOWN: + X: 60 + Y: 200 + Width: 270 + Height: 25 + Label@FILENAME_LABEL: + X: 10 + Y: 239 + Width: 40 + Height: 25 + Align: Right + Text: File: + TextField@FILENAME: + X: 60 + Y: 240 + Width: 270 + Height: 25 + Button@CLOSE: + X: 0 + Y: 294 + Width: 140 + Height: 35 + Text: Close + Font: Bold + Key: escape + Button@SAVE: + X: PARENT_RIGHT - WIDTH + Y: 294 + Width: 140 + Height: 35 + Text: Save + Font: Bold + +Container@EDITOR_ROOT: + Logic: LoadMapEditorLogic + Children: + Container@WORLD_ROOT: + Container@MENU_ROOT: + TooltipContainer@TOOLTIP_CONTAINER: + +Container@EDITOR_WORLD_ROOT: + Logic: LoadIngamePerfLogic, MapEditorLogic + Children: + Container@PERF_ROOT: + ViewportController: + Width: WINDOW_RIGHT + Height: WINDOW_BOTTOM + EditorViewportController@MAP_EDITOR: + Width: WINDOW_RIGHT + Height: WINDOW_BOTTOM + TooltipContainer: TOOLTIP_CONTAINER + TooltipTemplate: SIMPLE_TOOLTIP + Children: + TerrainTemplatePreview@DRAG_TILE_PREVIEW: + Visible: false + Sprite@DRAG_LAYER_PREVIEW: + Visible: false + ActorPreview@DRAG_ACTOR_PREVIEW: + Visible: false + Background@RADAR_BG: + X: WINDOW_RIGHT-255 + Y: 5 + Width: 250 + Height: 250 + Background: panel-gray + Children: + Radar@INGAME_RADAR: + X: 1 + Y: 1 + Width: PARENT_RIGHT-2 + Height: PARENT_BOTTOM-2 + MenuButton@OPTIONS_BUTTON: + Logic: MenuButtonsChromeLogic + Key: escape + X: WINDOW_RIGHT-254-WIDTH + Y: 5 + Width: 30 + Height: 25 + TooltipText: Menu + TooltipContainer: TOOLTIP_CONTAINER + Children: + Image: + X: 7 + Y: 5 + ImageCollection: order-icons + ImageName: options + Container@TILE_WIDGETS: + Logic: TileSelectorLogic + X: WINDOW_RIGHT-255 + Y: 278 + Width: 250 + Height: 324 + ClickThrough: false + Children: + Container@TILES_BG: + Width: PARENT_RIGHT + Height: PARENT_BOTTOM + Children: + DropDownButton@TILE_CATEGORY: + Width: PARENT_RIGHT + Height: 25 + Font: Bold + ScrollPanel@TILETEMPLATE_LIST: + Y: 24 + Width: PARENT_RIGHT + Height: PARENT_BOTTOM - 24 + ItemSpacing: 4 + Children: + ScrollItem@TILEPREVIEW_TEMPLATE: + Visible: false + Width: PARENT_RIGHT - 35 + TooltipContainer: TOOLTIP_CONTAINER + Children: + TerrainTemplatePreview@TILE_PREVIEW: + X: 4 + Y: 4 + Container@LAYER_WIDGETS: + Logic: LayerSelectorLogic + X: WINDOW_RIGHT-255 + Y: 278 + Width: 250 + Height: 324 + ClickThrough: false + Children: + Container@LAYERS_BG: + Width: PARENT_RIGHT + Height: PARENT_BOTTOM + Children: + ScrollPanel@LAYERTEMPLATE_LIST: + Width: PARENT_RIGHT + Height: PARENT_BOTTOM + ItemSpacing: 4 + Children: + ScrollItem@LAYERPREVIEW_TEMPLATE: + Visible: false + IgnoreChildMouseOver: true + TooltipContainer: TOOLTIP_CONTAINER + Children: + Sprite@LAYER_PREVIEW: + X: 4 + Y: 4 + Visible: false + Container@ACTOR_WIDGETS: + Logic: ActorSelectorLogic + X: WINDOW_RIGHT-255 + Y: 278 + Width: 250 + Height: 324 + ClickThrough: false + Children: + Container@ACTORS_BG: + Width: PARENT_RIGHT + Height: PARENT_BOTTOM + Children: + DropDownButton@OWNERS_DROPDOWN: + Width: PARENT_RIGHT + Height: 25 + Font: Bold + ScrollPanel@ACTORTEMPLATE_LIST: + Y: 24 + Width: PARENT_RIGHT + Height: PARENT_BOTTOM - 24 + ItemSpacing: 4 + Children: + ScrollItem@ACTORPREVIEW_TEMPLATE: + Visible: false + Width: PARENT_RIGHT - 35 + TooltipContainer: TOOLTIP_CONTAINER + IgnoreChildMouseOver: true + Children: + ActorPreview@ACTOR_PREVIEW: + X: 4 + Y: 4 + Visible: true + Container@MAP_EDITOR_TAB_CONTAINER: + Logic: MapEditorTabsLogic + X: WINDOW_RIGHT-255 + Y: 254 + Width: 250 + Height: 25 + ClickThrough: false + Children: + Button@TILES_TAB: + Width: 81 + Height: 25 + Text: Tiles + Font: Bold + Button@OVERLAYS_TAB: + X: 80 + Width: 90 + Height: 25 + Text: Overlays + Font: Bold + Button@ACTORS_TAB: + X: 169 + Width: 81 + Height: 25 + Text: Actors + Font: Bold + Button@GRID_BUTTON: + X: WINDOW_RIGHT - 420 + Y: 5 + Width: 100 + Height: 25 + Text: Grid + Font: Bold + Key: f1 + TooltipTemplate: BUTTON_TOOLTIP + TooltipText: Toggle the terrain grid + TooltipContainer: TOOLTIP_CONTAINER + Label@ZOOM_LABEL: + X: WINDOW_RIGHT - 500 - 55 + Y: 5 + Width: 50 + Height: 25 + Text: Zoom: + Align: Right + Font: Bold + Contrast: true + DropDownButton@ZOOM_BUTTON: + X: WINDOW_RIGHT - 500 + Y: 5 + Width: 70 + Height: 25 + Font: Bold \ No newline at end of file diff --git a/mods/cnc/chrome/ingame-menu.yaml b/mods/cnc/chrome/ingame-menu.yaml index 0cf8a5a81d..6fe83efae8 100644 --- a/mods/cnc/chrome/ingame-menu.yaml +++ b/mods/cnc/chrome/ingame-menu.yaml @@ -33,12 +33,24 @@ Container@INGAME_MENU: Width: 140 Height: 35 Text: Abort Mission + Button@EXIT_EDITOR: + X: 0 + Y: 0 + Width: 140 + Height: 35 + Text: Exit Map Editor Button@SURRENDER: X: 150 Y: 0 Width: 140 Height: 35 Text: Surrender + Button@SAVE_MAP: + X: 150 + Y: 0 + Width: 140 + Height: 35 + Text: Save Map Button@MUSIC: X: 300 Y: 0 diff --git a/mods/cnc/mod.yaml b/mods/cnc/mod.yaml index cbfe2428e5..832b1ad871 100644 --- a/mods/cnc/mod.yaml +++ b/mods/cnc/mod.yaml @@ -120,6 +120,7 @@ ChromeLayout: ./mods/cnc/chrome/tooltips.yaml ./mods/cnc/chrome/assetbrowser.yaml ./mods/cnc/chrome/missionbrowser.yaml + ./mods/cnc/chrome/editor.yaml Voices: ./mods/cnc/audio/voices.yaml diff --git a/mods/d2k/chrome/ingame-menu.yaml b/mods/d2k/chrome/ingame-menu.yaml index 61d6286484..24837fbe63 100644 --- a/mods/d2k/chrome/ingame-menu.yaml +++ b/mods/d2k/chrome/ingame-menu.yaml @@ -53,6 +53,13 @@ Container@INGAME_MENU: Height: 30 Text: Surrender Font: Bold + Button@SAVE_MAP: + X: (PARENT_RIGHT - WIDTH)/2 + Y: 180 + Width: 140 + Height: 30 + Text: Save Map + Font: Bold Button@ABORT_MISSION: X: (PARENT_RIGHT - WIDTH)/2 Y: 220 @@ -60,3 +67,10 @@ Container@INGAME_MENU: Height: 30 Text: Abort Mission Font: Bold + Button@EXIT_EDITOR: + X: (PARENT_RIGHT - WIDTH)/2 + Y: 220 + Width: 140 + Height: 30 + Text: Exit Map Editor + Font: Bold diff --git a/mods/d2k/mod.yaml b/mods/d2k/mod.yaml index a419c758b5..76f981f112 100644 --- a/mods/d2k/mod.yaml +++ b/mods/d2k/mod.yaml @@ -100,6 +100,7 @@ ChromeLayout: ./mods/ra/chrome/assetbrowser.yaml ./mods/ra/chrome/missionbrowser.yaml ./mods/ra/chrome/confirmation-dialogs.yaml + ./mods/ra/chrome/editor.yaml Weapons: ./mods/d2k/weapons.yaml diff --git a/mods/d2k/rules/arrakis.yaml b/mods/d2k/rules/arrakis.yaml index fa648ea655..0b63cb91f1 100644 --- a/mods/d2k/rules/arrakis.yaml +++ b/mods/d2k/rules/arrakis.yaml @@ -76,4 +76,5 @@ sietch: Power: Amount: 0 ProvidesPrerequisite@buildingname: + -WithCrumbleOverlay: diff --git a/mods/ra/chrome/editor.yaml b/mods/ra/chrome/editor.yaml new file mode 100644 index 0000000000..c4896664d5 --- /dev/null +++ b/mods/ra/chrome/editor.yaml @@ -0,0 +1,367 @@ +Background@NEW_MAP_BG: + Logic: NewMapLogic + X: (WINDOW_RIGHT - WIDTH)/2 + Y: (WINDOW_BOTTOM - HEIGHT)/2 + Width: 300 + Height: 200 + Children: + Label@LABEL_TITLE: + X: 0 + Y: 20 + Width: 300 + Height: 25 + Text: New Map + Align: Center + Font: Bold + Label@TILESET_LABEL: + X: 5 + Y: 59 + Width: 95 + Height: 25 + Align: Right + Text: Tileset: + DropDownButton@TILESET: + X: 105 + Y: 60 + Width: 150 + Height: 25 + Label@WIDTH_LABEL: + X: 5 + Y: 99 + Width: 95 + Height: 25 + Align: Right + Text: Width: + TextField@WIDTH: + X: 105 + Y: 100 + Width: 50 + MaxLength: 3 + Height: 25 + Text: 128 + Label@HEIGHT_LABEL: + X: 115 + Y: 99 + Width: 95 + Height: 25 + Align: Right + Text: Height: + TextField@HEIGHT: + X: 215 + Y: 100 + Width: 50 + MaxLength: 3 + Height: 25 + Text: 128 + Button@CREATE_BUTTON: + X: 15 + Y: PARENT_BOTTOM - 45 + Width: 120 + Height: 25 + Text: Create + Font: Bold + Key: return + Button@CANCEL_BUTTON: + X: PARENT_RIGHT - 15 - WIDTH + Y: PARENT_BOTTOM - 45 + Width: 120 + Height: 25 + Text: Cancel + Font: Bold + Key: escape + +Background@SAVE_MAP_PANEL: + Logic: SaveMapLogic + X: (WINDOW_RIGHT - WIDTH)/2 + Y: (WINDOW_BOTTOM - HEIGHT)/2 + Width: 350 + Height: 350 + Children: + Label@LABEL_TITLE: + X: (PARENT_RIGHT - WIDTH)/2 + Y: 20 + Width: 250 + Height: 25 + Text: Save Map + Align: Center + Font: Bold + Label@TITLE_LABEL: + X: 10 + Y: 59 + Width: 95 + Height: 25 + Align: Right + Text: Title: + TextField@TITLE: + X: 110 + Y: 60 + Width: 210 + MaxLength: 50 + Height: 25 + Label@DESCRIPTION_LABEL: + X: 10 + Y: 99 + Width: 95 + Height: 25 + Align: Right + Text: Description: + TextField@DESCRIPTION: + X: 110 + Y: 100 + Width: 210 + MaxLength: 50 + Height: 25 + Label@AUTHOR_LABEL: + X: 10 + Y: 139 + Width: 95 + Height: 25 + Align: Right + Text: Author: + TextField@AUTHOR: + X: 110 + Y: 140 + Width: 210 + MaxLength: 50 + Height: 25 + Label@CLASS_LABEL: + X: 10 + Y: 180 + Width: 95 + Height: 25 + Align: Right + Text: Class: + DropDownButton@CLASS_DROPDOWN: + X: 110 + Y: 180 + Width: 210 + Height: 25 + Label@PATH_LABEL: + X: 10 + Y: 219 + Width: 40 + Height: 25 + Align: Right + Text: Path: + DropDownButton@PATH_DROPDOWN: + X: 60 + Y: 220 + Width: 270 + Height: 25 + Label@FILENAME_LABEL: + X: 10 + Y: 259 + Width: 40 + Height: 25 + Align: Right + Text: File: + TextField@FILENAME: + X: 60 + Y: 260 + Width: 270 + Height: 25 + Button@CLOSE: + X: 30 + Y: 300 + Width: 100 + Height: 25 + Text: Close + Font: Bold + Key: escape + Button@SAVE: + X: 210 + Y: 300 + Width: 100 + Height: 25 + Text: Save + Font: Bold + +Container@EDITOR_ROOT: + Logic: LoadMapEditorLogic + Children: + Container@WORLD_ROOT: + Container@MENU_ROOT: + TooltipContainer@TOOLTIP_CONTAINER: + +Container@EDITOR_WORLD_ROOT: + Logic: LoadIngamePerfLogic, MapEditorLogic + Children: + Container@PERF_ROOT: + ViewportController: + X: 0 + Y: 0 + Width: WINDOW_RIGHT + Height: WINDOW_BOTTOM + TooltipContainer: TOOLTIP_CONTAINER + EditorViewportController@MAP_EDITOR: + Width: WINDOW_RIGHT + Height: WINDOW_BOTTOM + TooltipContainer: TOOLTIP_CONTAINER + TooltipTemplate: SIMPLE_TOOLTIP + Children: + ActorPreview@DRAG_ACTOR_PREVIEW: + Visible: false + TerrainTemplatePreview@DRAG_TILE_PREVIEW: + Visible: false + Sprite@DRAG_LAYER_PREVIEW: + Visible: false + Background@RADAR_BG: + X: WINDOW_RIGHT-255 + Y: 5 + Width: 250 + Height: 250 + Children: + Radar@INGAME_RADAR: + X: 10 + Y: 10 + Width: PARENT_RIGHT-19 + Height: PARENT_BOTTOM-19 + Container@TILE_WIDGETS: + Logic: TileSelectorLogic + Children: + Background@TILES_BG: + X: WINDOW_RIGHT-250 + Y: 290 + Width: 240 + Height: 360 + Children: + DropDownButton@TILE_CATEGORY: + X: 10 + Y: 10 + Width: 220 + Height: 25 + Font: Bold + ScrollPanel@TILETEMPLATE_LIST: + X: 10 + Y: 35 + Width: PARENT_RIGHT-20 + Height: PARENT_BOTTOM-45 + ItemSpacing: 4 + Children: + ScrollItem@TILEPREVIEW_TEMPLATE: + Visible: false + Width: PARENT_RIGHT - 35 + TooltipContainer: TOOLTIP_CONTAINER + Children: + TerrainTemplatePreview@TILE_PREVIEW: + X: 4 + Y: 4 + Container@LAYER_WIDGETS: + Visible: false + Logic: LayerSelectorLogic + Children: + Background@LAYERS_BG: + X: WINDOW_RIGHT-250 + Y: 290 + Width: 240 + Height: 360 + Children: + ScrollPanel@LAYERTEMPLATE_LIST: + X: 10 + Y: 10 + Width: PARENT_RIGHT-20 + Height: PARENT_BOTTOM-20 + ItemSpacing: 4 + Children: + ScrollItem@LAYERPREVIEW_TEMPLATE: + Visible: false + IgnoreChildMouseOver: true + TooltipContainer: TOOLTIP_CONTAINER + Children: + Sprite@LAYER_PREVIEW: + X: 4 + Y: 4 + Visible: false + Container@ACTOR_WIDGETS: + Visible: false + Logic: ActorSelectorLogic + Children: + Background@ACTORS_BG: + X: WINDOW_RIGHT-250 + Y: 290 + Width: 240 + Height: 360 + Children: + DropDownButton@OWNERS_DROPDOWN: + X: 10 + Y: 10 + Width: 220 + Height: 25 + Font: Bold + ScrollPanel@ACTORTEMPLATE_LIST: + X: 10 + Y: 35 + Width: PARENT_RIGHT-20 + Height: PARENT_BOTTOM-45 + ItemSpacing: 4 + Children: + ScrollItem@ACTORPREVIEW_TEMPLATE: + Visible: false + Width: PARENT_RIGHT - 35 + TooltipContainer: TOOLTIP_CONTAINER + IgnoreChildMouseOver: true + Children: + ActorPreview@ACTOR_PREVIEW: + X: 4 + Y: 4 + Visible: true + Container@MAP_EDITOR_TAB_CONTAINER: + Logic: MapEditorTabsLogic + X: WINDOW_RIGHT-245 + Y: 260 + Width: 240 + Height: 25 + Children: + Button@TILES_TAB: + X: 0 + Width: 70 + Height: 25 + Text: Tiles + Font: Bold + Button@OVERLAYS_TAB: + X: 70 + Width: 90 + Height: 25 + Text: Overlays + Font: Bold + Button@ACTORS_TAB: + X: 160 + Width: 70 + Height: 25 + Text: Actors + Font: Bold + MenuButton@OPTIONS_BUTTON: + Logic: MenuButtonsChromeLogic + MenuContainer: INGAME_MENU + HideIngameUI: true + Pause: true + Width: 160 + Height: 25 + Text: Menu + TooltipText: Menu + TooltipContainer: TOOLTIP_CONTAINER + Font: Bold + Key: escape + Button@GRID_BUTTON: + X: 180 + Width: 160 + Height: 25 + Text: Grid + TooltipTemplate: BUTTON_TOOLTIP + TooltipText: Toggle the terrain grid + TooltipContainer: TOOLTIP_CONTAINER + Font: Bold + Key: f1 + Label@ZOOM_LABEL: + X: 345 + Width: 50 + Height: 25 + Text: Zoom: + Align: Right + Font: Bold + Contrast: true + DropDownButton@ZOOM_BUTTON: + X: 400 + Width: 70 + Height: 25 + Font: Bold \ No newline at end of file diff --git a/mods/ra/chrome/ingame-menu.yaml b/mods/ra/chrome/ingame-menu.yaml index 19783bc110..f42993f72f 100644 --- a/mods/ra/chrome/ingame-menu.yaml +++ b/mods/ra/chrome/ingame-menu.yaml @@ -66,6 +66,13 @@ Container@INGAME_MENU: Height: 30 Text: Surrender Font: Bold + Button@SAVE_MAP: + X: (PARENT_RIGHT - WIDTH)/2 + Y: 180 + Width: 140 + Height: 30 + Text: Save Map + Font: Bold Button@ABORT_MISSION: X: (PARENT_RIGHT - WIDTH)/2 Y: 220 @@ -73,3 +80,10 @@ Container@INGAME_MENU: Height: 30 Text: Abort Mission Font: Bold + Button@EXIT_EDITOR: + X: (PARENT_RIGHT - WIDTH)/2 + Y: 220 + Width: 140 + Height: 30 + Text: Exit Map Editor + Font: Bold diff --git a/mods/ra/mod.yaml b/mods/ra/mod.yaml index c0b1c1e584..1322ac5bcb 100644 --- a/mods/ra/mod.yaml +++ b/mods/ra/mod.yaml @@ -114,6 +114,7 @@ ChromeLayout: ./mods/ra/chrome/assetbrowser.yaml ./mods/ra/chrome/missionbrowser.yaml ./mods/ra/chrome/confirmation-dialogs.yaml + ./mods/ra/chrome/editor.yaml Weapons: ./mods/ra/weapons/explosions.yaml diff --git a/mods/ts/mod.yaml b/mods/ts/mod.yaml index 331a76b933..eca1a34a79 100644 --- a/mods/ts/mod.yaml +++ b/mods/ts/mod.yaml @@ -168,6 +168,7 @@ ChromeLayout: ./mods/ra/chrome/assetbrowser.yaml ./mods/ra/chrome/missionbrowser.yaml ./mods/ra/chrome/confirmation-dialogs.yaml + ./mods/ra/chrome/editor.yaml Voices: ./mods/ts/audio/voices.yaml From 445c0d76a2f22fe6a2d5168e28f127d8b16c2887 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sun, 17 May 2015 11:57:29 +0100 Subject: [PATCH 11/12] Fix create map panel geometry. --- mods/cnc/chrome/editor.yaml | 50 ++++++++++++++++++------------------- mods/ra/chrome/editor.yaml | 28 ++++++++++----------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/mods/cnc/chrome/editor.yaml b/mods/cnc/chrome/editor.yaml index 8d3df16217..2307ad7218 100644 --- a/mods/cnc/chrome/editor.yaml +++ b/mods/cnc/chrome/editor.yaml @@ -3,7 +3,7 @@ Container@NEW_MAP_BG: X: (WINDOW_RIGHT - WIDTH)/2 Y: (WINDOW_BOTTOM - HEIGHT)/2 Width: 300 - Height: 150 + Height: 125 Children: Label@TITLE: Text:New Map @@ -14,65 +14,65 @@ Container@NEW_MAP_BG: Align: Center Background@bg: Width: PARENT_RIGHT - Height: 115 + Height: 90 Background: panel-black Children: Label@TILESET_LABEL: - X: 5 - Y: 19 + X: 25 + Y: 14 Width: 95 Height: 25 Align: Right Text: Tileset: DropDownButton@TILESET: - X: 105 - Y: 22 - Width: 150 + X: 125 + Y: 15 + Width: 160 Height: 25 Label@WIDTH_LABEL: - X: 5 - Y: 59 + X: 25 + Y: 49 Width: 95 Height: 25 Align: Right Text: Width: TextField@WIDTH: - X: 105 - Y: 60 + X: 125 + Y: 50 Width: 50 MaxLength: 3 Height: 25 Text: 128 Label@HEIGHT_LABEL: - X: 115 - Y: 59 + X: 135 + Y: 49 Width: 95 Height: 25 Align: Right Text: Height: TextField@HEIGHT: - X: 215 - Y: 60 + X: 235 + Y: 50 Width: 50 MaxLength: 3 Height: 25 Text: 128 - Button@CREATE_BUTTON: - X: 0 - Y: 114 - Width: 140 - Height: 35 - Text: Create - Font: Bold - Key: return Button@CANCEL_BUTTON: - X: PARENT_RIGHT - WIDTH - Y: 114 + Y: 89 Width: 140 Height: 35 Text: Cancel Font: Bold Key: escape + Button@CREATE_BUTTON: + X: PARENT_RIGHT - WIDTH + Y: 89 + Width: 140 + Height: 35 + Text: Create + Font: Bold + Key: return + Container@SAVE_MAP_PANEL: X: (WINDOW_RIGHT - WIDTH)/2 diff --git a/mods/ra/chrome/editor.yaml b/mods/ra/chrome/editor.yaml index c4896664d5..64d0d21bb2 100644 --- a/mods/ra/chrome/editor.yaml +++ b/mods/ra/chrome/editor.yaml @@ -3,7 +3,7 @@ Background@NEW_MAP_BG: X: (WINDOW_RIGHT - WIDTH)/2 Y: (WINDOW_BOTTOM - HEIGHT)/2 Width: 300 - Height: 200 + Height: 185 Children: Label@LABEL_TITLE: X: 0 @@ -14,47 +14,47 @@ Background@NEW_MAP_BG: Align: Center Font: Bold Label@TILESET_LABEL: - X: 5 + X: 20 Y: 59 Width: 95 Height: 25 Align: Right Text: Tileset: DropDownButton@TILESET: - X: 105 + X: 120 Y: 60 - Width: 150 + Width: 160 Height: 25 Label@WIDTH_LABEL: - X: 5 - Y: 99 + X: 20 + Y: 94 Width: 95 Height: 25 Align: Right Text: Width: TextField@WIDTH: - X: 105 - Y: 100 + X: 120 + Y: 95 Width: 50 MaxLength: 3 Height: 25 Text: 128 Label@HEIGHT_LABEL: - X: 115 - Y: 99 + X: 130 + Y: 94 Width: 95 Height: 25 Align: Right Text: Height: TextField@HEIGHT: - X: 215 - Y: 100 + X: 230 + Y: 95 Width: 50 MaxLength: 3 Height: 25 Text: 128 Button@CREATE_BUTTON: - X: 15 + X: 30 Y: PARENT_BOTTOM - 45 Width: 120 Height: 25 @@ -62,7 +62,7 @@ Background@NEW_MAP_BG: Font: Bold Key: return Button@CANCEL_BUTTON: - X: PARENT_RIGHT - 15 - WIDTH + X: 160 Y: PARENT_BOTTOM - 45 Width: 120 Height: 25 From 60f96fcb7b1646e00a4dd03f98032144d79cb834 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sun, 17 May 2015 10:54:55 +0100 Subject: [PATCH 12/12] Overhaul save panel. --- .../Widgets/Logic/Editor/SaveMapLogic.cs | 141 ++++++++++-------- .../Widgets/Logic/Ingame/IngameMenuLogic.cs | 10 +- mods/cnc/chrome/editor.yaml | 127 ++++++++-------- mods/ra/chrome/editor.yaml | 103 +++++++------ 4 files changed, 207 insertions(+), 174 deletions(-) diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/SaveMapLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/SaveMapLogic.cs index 73144f0c94..ee662f5cb9 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Editor/SaveMapLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/SaveMapLogic.cs @@ -20,24 +20,20 @@ namespace OpenRA.Mods.Common.Widgets.Logic public class SaveMapLogic { [ObjectCreator.UseCtor] - public SaveMapLogic(Widget widget, Action onExit, World world) + public SaveMapLogic(Widget widget, Action onExit, Map map, EditorActorLayer editorActorLayer) { - var newMap = world.Map; + var title = widget.Get("TITLE"); + title.Text = map.Title; - var title = widget.GetOrNull("TITLE"); - if (title != null) - title.Text = newMap.Title; + var author = widget.Get("AUTHOR"); + author.Text = map.Author; - var description = widget.GetOrNull("DESCRIPTION"); - if (description != null) - description.Text = newMap.Description; + // TODO: This should use a multi-line textfield once they exist + var description = widget.Get("DESCRIPTION"); + description.Text = map.Description; - var author = widget.GetOrNull("AUTHOR"); - if (author != null) - author.Text = newMap.Author; - - var visibilityDropdown = widget.GetOrNull("CLASS_DROPDOWN"); - if (visibilityDropdown != null) + // TODO: This should use a multi-selection dropdown once they exist + var visibilityDropdown = widget.Get("VISIBILITY_DROPDOWN"); { var mapVisibility = new List(Enum.GetNames(typeof(MapVisibility))); Func setupItem = (option, template) => @@ -48,75 +44,98 @@ namespace OpenRA.Mods.Common.Widgets.Logic item.Get("LABEL").GetText = () => option; return item; }; - visibilityDropdown.Text = Enum.GetName(typeof(MapVisibility), newMap.Visibility); + + visibilityDropdown.Text = Enum.GetName(typeof(MapVisibility), map.Visibility); visibilityDropdown.OnClick = () => visibilityDropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 210, mapVisibility, setupItem); } - var pathDropdown = widget.GetOrNull("PATH_DROPDOWN"); - if (pathDropdown != null) + var directoryDropdown = widget.Get("DIRECTORY_DROPDOWN"); { - var mapFolders = new List(); - foreach (var mapFolder in Game.ModData.Manifest.MapFolders.Keys) + var mapDirectories = Game.ModData.Manifest.MapFolders.Keys.Select(ff => { - var folder = mapFolder; - if (mapFolder.StartsWith("~")) - folder = mapFolder.Substring(1); + var f = Platform.UnresolvePath(ff); + if (f.StartsWith("~")) + f = f.Substring(1); - mapFolders.Add(Platform.ResolvePath(folder)); - } + return f; + }).ToList(); Func setupItem = (option, template) => { var item = ScrollItemWidget.Setup(template, - () => pathDropdown.Text == Platform.UnresolvePath(option), - () => { pathDropdown.Text = Platform.UnresolvePath(option); }); + () => directoryDropdown.Text == option, + () => directoryDropdown.Text = option); item.Get("LABEL").GetText = () => option; return item; }; - var userMapFolder = Game.ModData.Manifest.MapFolders.First(f => f.Value == "User").Key; - if (userMapFolder.StartsWith("~")) - userMapFolder = userMapFolder.Substring(1); - pathDropdown.Text = Platform.UnresolvePath(userMapFolder); - pathDropdown.OnClick = () => - pathDropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 210, mapFolders, setupItem); + var mapDirectory = Platform.UnresolvePath(Path.GetDirectoryName(map.Path)); + var initialDirectory = mapDirectories.FirstOrDefault(f => f == mapDirectory); + + if (initialDirectory == null) + initialDirectory = mapDirectories.First(); + + directoryDropdown.Text = initialDirectory; + directoryDropdown.OnClick = () => + directoryDropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 210, mapDirectories, setupItem); } - var filename = widget.GetOrNull("FILENAME"); - if (filename != null) - filename.Text = Path.GetFileName(world.Map.Path); + var filename = widget.Get("FILENAME"); + filename.Text = Path.GetFileNameWithoutExtension(map.Path); - var close = widget.GetOrNull("CLOSE"); - if (close != null) - close.OnClick = () => { Ui.CloseWindow(); onExit(); }; - - var save = widget.GetOrNull("SAVE"); - if (save != null && !string.IsNullOrEmpty(filename.Text)) + var fileTypes = new Dictionary() { - var editorLayer = world.WorldActor.Trait(); - save.OnClick = () => + { ".oramap", ".oramap" }, + { "(unpacked)", "" } + }; + + var typeDropdown = widget.Get("TYPE_DROPDOWN"); + { + Func setupItem = (option, template) => { - newMap.Title = title.Text; - newMap.Description = description.Text; - newMap.Author = author.Text; - newMap.Visibility = (MapVisibility)Enum.Parse(typeof(MapVisibility), visibilityDropdown.Text); - newMap.ActorDefinitions = editorLayer.Save(); - newMap.PlayerDefinitions = editorLayer.Players.ToMiniYaml(); - newMap.RequiresMod = Game.ModData.Manifest.Mod.Id; - - var combinedPath = Path.Combine(pathDropdown.Text, filename.Text); - var resolvedPath = Platform.ResolvePath(combinedPath); - newMap.Save(resolvedPath); - - // Update the map cache so it can be loaded without restarting the game - Game.ModData.MapCache[newMap.Uid].UpdateFromMap(newMap, MapClassification.User); - - Console.WriteLine("Saved current map at {0}", resolvedPath); - Ui.CloseWindow(); - onExit(); + var item = ScrollItemWidget.Setup(template, + () => typeDropdown.Text == option, + () => typeDropdown.Text = option); + item.Get("LABEL").GetText = () => option; + return item; }; + + typeDropdown.Text = Path.GetExtension(map.Path); + if (string.IsNullOrEmpty(typeDropdown.Text)) + typeDropdown.Text = fileTypes.First(t => t.Value == "").Key; + + typeDropdown.OnClick = () => + typeDropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 210, fileTypes.Keys, setupItem); } + + var close = widget.Get("BACK_BUTTON"); + close.OnClick = () => { Ui.CloseWindow(); onExit(); }; + + var save = widget.Get("SAVE_BUTTON"); + save.OnClick = () => + { + if (string.IsNullOrEmpty(filename.Text)) + return; + + map.Title = title.Text; + map.Description = description.Text; + map.Author = author.Text; + map.Visibility = (MapVisibility)Enum.Parse(typeof(MapVisibility), visibilityDropdown.Text); + map.ActorDefinitions = editorActorLayer.Save(); + map.PlayerDefinitions = editorActorLayer.Players.ToMiniYaml(); + map.RequiresMod = Game.ModData.Manifest.Mod.Id; + + var combinedPath = Platform.ResolvePath(Path.Combine(directoryDropdown.Text, filename.Text + fileTypes[typeDropdown.Text])); + map.Save(combinedPath); + + // Update the map cache so it can be loaded without restarting the game + Game.ModData.MapCache[map.Uid].UpdateFromMap(map, MapClassification.User); + + Console.WriteLine("Saved current map at {0}", combinedPath); + Ui.CloseWindow(); + onExit(); + }; } } } diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameMenuLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameMenuLogic.cs index 88ea5197a0..f06b6be483 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameMenuLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameMenuLogic.cs @@ -101,11 +101,13 @@ namespace OpenRA.Mods.Common.Widgets.Logic saveMapButton.IsVisible = () => world.Type == WorldType.Editor; saveMapButton.OnClick = () => { + hideMenu = true; Ui.OpenWindow("SAVE_MAP_PANEL", new WidgetArgs() - { - { "onExit", () => widget.Visible = true }, - { "world", world }, - }); + { + { "onExit", () => hideMenu = false }, + { "map", world.Map }, + { "editorActorLayer", world.WorldActor.Trait() } + }); }; menu.Get("MUSIC").OnClick = () => diff --git a/mods/cnc/chrome/editor.yaml b/mods/cnc/chrome/editor.yaml index 2307ad7218..cdd1733c4a 100644 --- a/mods/cnc/chrome/editor.yaml +++ b/mods/cnc/chrome/editor.yaml @@ -74,13 +74,14 @@ Container@NEW_MAP_BG: Key: return -Container@SAVE_MAP_PANEL: +Background@SAVE_MAP_PANEL: + Logic: SaveMapLogic X: (WINDOW_RIGHT - WIDTH)/2 Y: (WINDOW_BOTTOM - HEIGHT)/2 - Width: 350 - Height: 330 + Width: 345 + Height: 264 Children: - Label@TITLE: + Label@LABEL_TITLE: Text: Save Map Width: PARENT_RIGHT Y: 0-25 @@ -88,101 +89,107 @@ Container@SAVE_MAP_PANEL: Contrast: true Align: Center Background@SAVE_MAP_BACKGROUND: - Logic: SaveMapLogic Width: PARENT_RIGHT - Height: 295 + Height: 230 Background: panel-black Children: Label@TITLE_LABEL: X: 10 - Y: 39 + Y: 14 Width: 95 Height: 25 Align: Right Text: Title: TextField@TITLE: X: 110 - Y: 40 - Width: 210 - MaxLength: 50 - Height: 25 - Label@DESCRIPTION_LABEL: - X: 10 - Y: 79 - Width: 95 - Height: 25 - Align: Right - Text: Description: - TextField@DESCRIPTION: - X: 110 - Y: 80 - Width: 210 + Y: 15 + Width: 220 MaxLength: 50 Height: 25 Label@AUTHOR_LABEL: X: 10 - Y: 119 + Y: 49 Width: 95 Height: 25 Align: Right Text: Author: TextField@AUTHOR: X: 110 - Y: 120 - Width: 210 + Y: 50 + Width: 220 MaxLength: 50 Height: 25 - Label@CLASS_LABEL: + Label@DESCRIPTION_LABEL: X: 10 - Y: 160 + Y: 84 Width: 95 Height: 25 Align: Right - Text: Class: - DropDownButton@CLASS_DROPDOWN: + Text: Description: + TextField@DESCRIPTION: X: 110 - Y: 160 - Width: 210 + Y: 85 + Width: 220 + MaxLength: 50 Height: 25 - Label@PATH_LABEL: + Label@VISIBILITY_LABEL: X: 10 - Y: 199 - Width: 40 + Y: 119 + Width: 95 Height: 25 Align: Right - Text: Path: - DropDownButton@PATH_DROPDOWN: - X: 60 - Y: 200 - Width: 270 + Text: Visibility: + DropDownButton@VISIBILITY_DROPDOWN: + X: 110 + Y: 120 + Width: 220 Height: 25 + Font: Regular + Label@DIRECTORY_LABEL: + X: 10 + Y: 154 + Width: 95 + Height: 25 + Align: Right + Text: Directory: + DropDownButton@DIRECTORY_DROPDOWN: + X: 110 + Y: 155 + Width: 220 + Height: 25 + Font: Regular Label@FILENAME_LABEL: X: 10 - Y: 239 - Width: 40 + Y: 189 + Width: 95 Height: 25 Align: Right - Text: File: + Text: Filename: TextField@FILENAME: - X: 60 - Y: 240 - Width: 270 + X: 110 + Y: 190 + Width: 105 Height: 25 - Button@CLOSE: - X: 0 - Y: 294 - Width: 140 - Height: 35 - Text: Close - Font: Bold - Key: escape - Button@SAVE: - X: PARENT_RIGHT - WIDTH - Y: 294 - Width: 140 - Height: 35 - Text: Save - Font: Bold + DropDownButton@TYPE_DROPDOWN: + X: 220 + Y: 190 + Width: 110 + Height: 25 + Font: Regular + Button@BACK_BUTTON: + Y: PARENT_BOTTOM - 35 + Width: 140 + Height: 35 + Text: Cancel + Font: Bold + Key: escape + Button@SAVE_BUTTON: + X: PARENT_RIGHT - 140 + Y: PARENT_BOTTOM - 35 + Width: 140 + Height: 35 + Text: Save + Font: Bold Container@EDITOR_ROOT: Logic: LoadMapEditorLogic diff --git a/mods/ra/chrome/editor.yaml b/mods/ra/chrome/editor.yaml index 64d0d21bb2..e1ce379a32 100644 --- a/mods/ra/chrome/editor.yaml +++ b/mods/ra/chrome/editor.yaml @@ -75,7 +75,7 @@ Background@SAVE_MAP_PANEL: X: (WINDOW_RIGHT - WIDTH)/2 Y: (WINDOW_BOTTOM - HEIGHT)/2 Width: 350 - Height: 350 + Height: 335 Children: Label@LABEL_TITLE: X: (PARENT_RIGHT - WIDTH)/2 @@ -95,86 +95,91 @@ Background@SAVE_MAP_PANEL: TextField@TITLE: X: 110 Y: 60 - Width: 210 - MaxLength: 50 - Height: 25 - Label@DESCRIPTION_LABEL: - X: 10 - Y: 99 - Width: 95 - Height: 25 - Align: Right - Text: Description: - TextField@DESCRIPTION: - X: 110 - Y: 100 - Width: 210 + Width: 220 MaxLength: 50 Height: 25 Label@AUTHOR_LABEL: X: 10 - Y: 139 + Y: 94 Width: 95 Height: 25 Align: Right Text: Author: TextField@AUTHOR: X: 110 - Y: 140 - Width: 210 + Y: 95 + Width: 220 MaxLength: 50 Height: 25 - Label@CLASS_LABEL: + Label@DESCRIPTION_LABEL: X: 10 - Y: 180 + Y: 129 Width: 95 Height: 25 Align: Right - Text: Class: - DropDownButton@CLASS_DROPDOWN: + Text: Description: + TextField@DESCRIPTION: X: 110 - Y: 180 - Width: 210 + Y: 130 + Width: 220 + MaxLength: 50 Height: 25 - Label@PATH_LABEL: + Label@VISIBILITY_LABEL: X: 10 - Y: 219 - Width: 40 + Y: 164 + Width: 95 Height: 25 Align: Right - Text: Path: - DropDownButton@PATH_DROPDOWN: - X: 60 - Y: 220 - Width: 270 + Text: Visibility: + DropDownButton@VISIBILITY_DROPDOWN: + X: 110 + Y: 165 + Width: 220 + Height: 25 + Label@DIRECTORY_LABEL: + X: 10 + Y: 199 + Width: 95 + Height: 25 + Align: Right + Text: Directory: + DropDownButton@DIRECTORY_DROPDOWN: + X: 110 + Y: 200 + Width: 220 Height: 25 Label@FILENAME_LABEL: X: 10 - Y: 259 - Width: 40 + Y: 234 + Width: 95 Height: 25 Align: Right - Text: File: + Text: Filename: TextField@FILENAME: - X: 60 - Y: 260 - Width: 270 + X: 110 + Y: 235 + Width: 105 Height: 25 - Button@CLOSE: - X: 30 - Y: 300 - Width: 100 + DropDownButton@TYPE_DROPDOWN: + X: 220 + Y: 235 + Width: 110 Height: 25 - Text: Close - Font: Bold - Key: escape - Button@SAVE: - X: 210 - Y: 300 - Width: 100 + Button@SAVE_BUTTON: + X: 80 + Y: PARENT_BOTTOM - 45 + Width: 120 Height: 25 Text: Save Font: Bold + Button@BACK_BUTTON: + X: 210 + Y: PARENT_BOTTOM - 45 + Width: 120 + Height: 25 + Text: Cancel + Font: Bold + Key: escape Container@EDITOR_ROOT: Logic: LoadMapEditorLogic