diff --git a/OpenRA.Mods.Common/EditorBrushes/EditorActorBrush.cs b/OpenRA.Mods.Common/EditorBrushes/EditorActorBrush.cs index 1a2bac87c4..df8818b8fe 100644 --- a/OpenRA.Mods.Common/EditorBrushes/EditorActorBrush.cs +++ b/OpenRA.Mods.Common/EditorBrushes/EditorActorBrush.cs @@ -9,30 +9,65 @@ */ #endregion +using System.Collections.Generic; using System.Linq; using OpenRA.Graphics; using OpenRA.Mods.Common.Traits; +using OpenRA.Traits; namespace OpenRA.Mods.Common.Widgets { public sealed class EditorActorBrush : IEditorBrush { + public EditorActorPreview Preview; + readonly World world; readonly EditorActorLayer editorLayer; - readonly EditorCursorLayer editorCursor; readonly EditorActionManager editorActionManager; readonly EditorViewportControllerWidget editorWidget; - readonly int cursorToken; + readonly WVec centerOffset; + readonly bool sharesCell; + + CPos cell; + SubCell subcell = SubCell.Invalid; public EditorActorBrush(EditorViewportControllerWidget editorWidget, ActorInfo actor, PlayerReference owner, WorldRenderer wr) { this.editorWidget = editorWidget; world = wr.World; editorLayer = world.WorldActor.Trait(); - editorCursor = world.WorldActor.Trait(); editorActionManager = world.WorldActor.Trait(); - cursorToken = editorCursor.SetActor(wr, actor, owner); + var ios = actor.TraitInfoOrDefault(); + centerOffset = (ios as BuildingInfo)?.CenterOffset(world) ?? WVec.Zero; + sharesCell = ios != null && ios.SharesCell; + + // Enforce first entry of ValidOwnerNames as owner if the actor has RequiresSpecificOwners. + var ownerName = owner.Name; + var specificOwnerInfo = actor.TraitInfoOrDefault(); + if (specificOwnerInfo != null && !specificOwnerInfo.ValidOwnerNames.Contains(ownerName)) + ownerName = specificOwnerInfo.ValidOwnerNames.First(); + + var reference = new ActorReference(actor.Name) + { + new OwnerInit(ownerName), + new FactionInit(owner.Faction) + }; + + var worldPx = wr.Viewport.ViewToWorldPx(Viewport.LastMousePos) - wr.ScreenPxOffset(centerOffset); + cell = wr.Viewport.ViewToWorld(wr.Viewport.WorldToViewPx(worldPx)); + reference.Add(new LocationInit(cell)); + if (sharesCell) + { + subcell = editorLayer.FreeSubCellAt(cell); + if (subcell != SubCell.Invalid) + reference.Add(new SubCellInit(subcell)); + } + + if (actor.HasTraitInfo()) + reference.Add(new FacingInit(editorLayer.Info.DefaultActorFacing)); + + Preview = new EditorActorPreview(wr, null, reference, owner); } public bool HandleMouseInput(MouseInput mi) @@ -52,29 +87,56 @@ namespace OpenRA.Mods.Common.Widgets return false; } - if (editorCursor.CurrentToken != cursorToken) - return false; - if (mi.Button == MouseButton.Left && mi.Event == MouseInputEvent.Down) { // Check the actor is inside the map - var actor = editorCursor.Actor; - if (!actor.Footprint.All(c => world.Map.Tiles.Contains(c.Key))) + if (!Preview.Footprint.All(c => world.Map.Tiles.Contains(c.Key))) return true; - var action = new AddActorAction(editorLayer, actor.Export()); + var action = new AddActorAction(editorLayer, Preview.Export()); editorActionManager.Add(action); } return true; } + void IEditorBrush.TickRender(WorldRenderer wr, Actor self) + { + // Offset mouse position by the center offset (in world pixels) + var worldPx = wr.Viewport.ViewToWorldPx(Viewport.LastMousePos) - wr.ScreenPxOffset(centerOffset); + var currentCell = wr.Viewport.ViewToWorld(wr.Viewport.WorldToViewPx(worldPx)); + var currentSubcell = sharesCell ? editorLayer.FreeSubCellAt(currentCell) : SubCell.Invalid; + if (cell != currentCell || subcell != currentSubcell) + { + cell = currentCell; + Preview.ReplaceInit(new LocationInit(cell)); + + if (sharesCell) + { + subcell = editorLayer.FreeSubCellAt(cell); + if (subcell == SubCell.Invalid) + Preview.RemoveInit(); + else + Preview.ReplaceInit(new SubCellInit(subcell)); + } + + Preview.UpdateFromMove(); + } + } + + IEnumerable IEditorBrush.RenderAboveShroud(Actor self, WorldRenderer wr) + { + return Preview.Render().OrderBy(WorldRenderer.RenderableZPositionComparisonKey); + } + + IEnumerable IEditorBrush.RenderAnnotations(Actor self, WorldRenderer wr) + { + return Preview.RenderAnnotations(); + } + public void Tick() { } - public void Dispose() - { - editorCursor.Clear(cursorToken); - } + public void Dispose() { } } sealed class AddActorAction : IEditorAction diff --git a/OpenRA.Mods.Common/EditorBrushes/EditorCopyPasteBrush.cs b/OpenRA.Mods.Common/EditorBrushes/EditorCopyPasteBrush.cs index 23de19fe38..0e75edd20f 100644 --- a/OpenRA.Mods.Common/EditorBrushes/EditorCopyPasteBrush.cs +++ b/OpenRA.Mods.Common/EditorBrushes/EditorCopyPasteBrush.cs @@ -14,6 +14,7 @@ using System.Collections.Generic; using System.Linq; using OpenRA.Graphics; using OpenRA.Mods.Common.EditorBrushes; +using OpenRA.Mods.Common.Graphics; using OpenRA.Mods.Common.Traits; namespace OpenRA.Mods.Common.Widgets @@ -94,14 +95,23 @@ namespace OpenRA.Mods.Common.Widgets return false; } + void IEditorBrush.TickRender(WorldRenderer wr, Actor self) { } + IEnumerable IEditorBrush.RenderAboveShroud(Actor self, WorldRenderer wr) { yield break; } + IEnumerable IEditorBrush.RenderAnnotations(Actor self, WorldRenderer wr) + { + if (PastePreviewPosition != null) + { + yield return new EditorSelectionAnnotationRenderable(Region, editorWidget.SelectionAltColor, editorWidget.SelectionAltOffset, PastePreviewPosition); + yield return new EditorSelectionAnnotationRenderable(Region, editorWidget.PasteColor, int2.Zero, PastePreviewPosition); + } + } + public void Tick() { PastePreviewPosition = worldRenderer.Viewport.ViewToWorld(Viewport.LastMousePos); } - public void Dispose() - { - } + public void Dispose() { } } sealed class CopyPasteEditorAction : IEditorAction diff --git a/OpenRA.Mods.Common/EditorBrushes/EditorDefaultBrush.cs b/OpenRA.Mods.Common/EditorBrushes/EditorDefaultBrush.cs index 78e88164da..ca2979a191 100644 --- a/OpenRA.Mods.Common/EditorBrushes/EditorDefaultBrush.cs +++ b/OpenRA.Mods.Common/EditorBrushes/EditorDefaultBrush.cs @@ -10,7 +10,9 @@ #endregion using System; +using System.Collections.Generic; using OpenRA.Graphics; +using OpenRA.Mods.Common.Graphics; using OpenRA.Mods.Common.Traits; using OpenRA.Widgets; @@ -20,6 +22,10 @@ namespace OpenRA.Mods.Common.Widgets { bool HandleMouseInput(MouseInput mi); void Tick(); + + void TickRender(WorldRenderer wr, Actor self); + IEnumerable RenderAboveShroud(Actor self, WorldRenderer wr); + IEnumerable RenderAnnotations(Actor self, WorldRenderer wr); } public class EditorSelection @@ -255,6 +261,17 @@ namespace OpenRA.Mods.Common.Widgets return true; } + void IEditorBrush.TickRender(WorldRenderer wr, Actor self) { } + IEnumerable IEditorBrush.RenderAboveShroud(Actor self, WorldRenderer wr) { yield break; } + IEnumerable IEditorBrush.RenderAnnotations(Actor self, WorldRenderer wr) + { + if (CurrentDragBounds != null) + { + yield return new EditorSelectionAnnotationRenderable(CurrentDragBounds, editorWidget.SelectionAltColor, editorWidget.SelectionAltOffset, null); + yield return new EditorSelectionAnnotationRenderable(CurrentDragBounds, editorWidget.SelectionMainColor, int2.Zero, null); + } + } + public void Tick() { } public void Dispose() { } diff --git a/OpenRA.Mods.Common/EditorBrushes/EditorMarkerLayerBrush.cs b/OpenRA.Mods.Common/EditorBrushes/EditorMarkerLayerBrush.cs index 10e6752d2e..7a19f6a8ae 100644 --- a/OpenRA.Mods.Common/EditorBrushes/EditorMarkerLayerBrush.cs +++ b/OpenRA.Mods.Common/EditorBrushes/EditorMarkerLayerBrush.cs @@ -75,6 +75,10 @@ namespace OpenRA.Mods.Common.Widgets return true; } + void IEditorBrush.TickRender(WorldRenderer wr, Actor self) { } + IEnumerable IEditorBrush.RenderAboveShroud(Actor self, WorldRenderer wr) { yield break; } + IEnumerable IEditorBrush.RenderAnnotations(Actor self, WorldRenderer wr) { yield break; } + public void Tick() { } public void Dispose() { } diff --git a/OpenRA.Mods.Common/EditorBrushes/EditorResourceBrush.cs b/OpenRA.Mods.Common/EditorBrushes/EditorResourceBrush.cs index 8a856fd610..5bec32a633 100644 --- a/OpenRA.Mods.Common/EditorBrushes/EditorResourceBrush.cs +++ b/OpenRA.Mods.Common/EditorBrushes/EditorResourceBrush.cs @@ -10,6 +10,7 @@ #endregion using System.Collections.Generic; +using System.Linq; using OpenRA.Graphics; using OpenRA.Mods.Common.Traits; @@ -23,13 +24,15 @@ namespace OpenRA.Mods.Common.Widgets readonly World world; readonly EditorViewportControllerWidget editorWidget; readonly EditorActionManager editorActionManager; - readonly EditorCursorLayer editorCursor; readonly IResourceLayer resourceLayer; - readonly int cursorToken; AddResourcesEditorAction action; bool resourceAdded; + CPos cell; + readonly List preview = new(); + readonly IResourceRenderer[] resourceRenderers; + public EditorResourceBrush(EditorViewportControllerWidget editorWidget, string resourceType, WorldRenderer wr) { this.editorWidget = editorWidget; @@ -37,11 +40,13 @@ namespace OpenRA.Mods.Common.Widgets worldRenderer = wr; world = wr.World; editorActionManager = world.WorldActor.Trait(); - editorCursor = world.WorldActor.Trait(); resourceLayer = world.WorldActor.Trait(); - action = new AddResourcesEditorAction(resourceType, resourceLayer); - cursorToken = editorCursor.SetResource(wr, resourceType); + resourceRenderers = world.WorldActor.TraitsImplementing().ToArray(); + cell = wr.Viewport.ViewToWorld(wr.Viewport.WorldToViewPx(Viewport.LastMousePos)); + UpdatePreview(); + + action = new AddResourcesEditorAction(resourceType, resourceLayer); } public bool HandleMouseInput(MouseInput mi) @@ -61,9 +66,6 @@ namespace OpenRA.Mods.Common.Widgets return false; } - if (editorCursor.CurrentToken != cursorToken) - return false; - var cell = worldRenderer.Viewport.ViewToWorld(mi.Location); if (mi.Button == MouseButton.Left && mi.Event != MouseInputEvent.Up && resourceLayer.CanAddResource(ResourceType, cell)) @@ -81,12 +83,30 @@ namespace OpenRA.Mods.Common.Widgets return true; } + void UpdatePreview() + { + var pos = world.Map.CenterOfCell(cell); + + preview.Clear(); + preview.AddRange(resourceRenderers.SelectMany(r => r.RenderPreview(worldRenderer, ResourceType, pos))); + } + + void IEditorBrush.TickRender(WorldRenderer wr, Actor self) + { + var currentCell = wr.Viewport.ViewToWorld(Viewport.LastMousePos); + if (cell != currentCell) + { + cell = currentCell; + UpdatePreview(); + } + } + + IEnumerable IEditorBrush.RenderAboveShroud(Actor self, WorldRenderer wr) { return preview; } + IEnumerable IEditorBrush.RenderAnnotations(Actor self, WorldRenderer wr) { yield break; } + public void Tick() { } - public void Dispose() - { - editorCursor.Clear(cursorToken); - } + public void Dispose() { } } readonly struct CellResource diff --git a/OpenRA.Mods.Common/EditorBrushes/EditorTileBrush.cs b/OpenRA.Mods.Common/EditorBrushes/EditorTileBrush.cs index 2a54dc9d70..e9867a6cc4 100644 --- a/OpenRA.Mods.Common/EditorBrushes/EditorTileBrush.cs +++ b/OpenRA.Mods.Common/EditorBrushes/EditorTileBrush.cs @@ -20,6 +20,7 @@ namespace OpenRA.Mods.Common.Widgets { public sealed class EditorTileBrush : IEditorBrush { + public readonly TerrainTemplateInfo TerrainTemplate; public readonly ushort Template; readonly WorldRenderer worldRenderer; @@ -27,11 +28,14 @@ namespace OpenRA.Mods.Common.Widgets readonly ITemplatedTerrainInfo terrainInfo; readonly EditorViewportControllerWidget editorWidget; readonly EditorActionManager editorActionManager; - readonly EditorCursorLayer editorCursor; - readonly int cursorToken; bool painting; + readonly ITiledTerrainRenderer terrainRenderer; + + CPos cell; + readonly List preview = new(); + public EditorTileBrush(EditorViewportControllerWidget editorWidget, ushort id, WorldRenderer wr) { this.editorWidget = editorWidget; @@ -42,12 +46,12 @@ namespace OpenRA.Mods.Common.Widgets throw new InvalidDataException("EditorTileBrush can only be used with template-based tilesets"); editorActionManager = world.WorldActor.Trait(); - editorCursor = world.WorldActor.Trait(); + terrainRenderer = world.WorldActor.Trait(); Template = id; - - var template = terrainInfo.Templates.First(t => t.Value.Id == id).Value; - cursorToken = editorCursor.SetTerrainTemplate(wr, template); + TerrainTemplate = terrainInfo.Templates.First(t => t.Value.Id == id).Value; + cell = wr.Viewport.ViewToWorld(wr.Viewport.WorldToViewPx(Viewport.LastMousePos)); + UpdatePreview(); } public bool HandleMouseInput(MouseInput mi) @@ -81,9 +85,6 @@ namespace OpenRA.Mods.Common.Widgets if (mi.Event != MouseInputEvent.Down && mi.Event != MouseInputEvent.Move) return true; - if (editorCursor.CurrentToken != cursorToken) - return false; - var cell = worldRenderer.Viewport.ViewToWorld(mi.Location); var isMoving = mi.Event == MouseInputEvent.Move; @@ -143,12 +144,30 @@ namespace OpenRA.Mods.Common.Widgets return false; } + void UpdatePreview() + { + var pos = world.Map.CenterOfCell(cell); + + preview.Clear(); + preview.AddRange(terrainRenderer.RenderPreview(worldRenderer, TerrainTemplate, pos)); + } + + void IEditorBrush.TickRender(WorldRenderer wr, Actor self) + { + var currentCell = wr.Viewport.ViewToWorld(Viewport.LastMousePos); + if (cell != currentCell) + { + cell = currentCell; + UpdatePreview(); + } + } + + IEnumerable IEditorBrush.RenderAboveShroud(Actor self, WorldRenderer wr) { return preview; } + IEnumerable IEditorBrush.RenderAnnotations(Actor self, WorldRenderer wr) { yield break; } + public void Tick() { } - public void Dispose() - { - editorCursor.Clear(cursorToken); - } + public void Dispose() { } } sealed class PaintTileEditorAction : IEditorAction diff --git a/OpenRA.Mods.Common/Traits/World/EditorActorLayer.cs b/OpenRA.Mods.Common/Traits/World/EditorActorLayer.cs index 015cd1c6c6..eb2dde17ae 100644 --- a/OpenRA.Mods.Common/Traits/World/EditorActorLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/EditorActorLayer.cs @@ -28,6 +28,9 @@ namespace OpenRA.Mods.Common.Traits [Desc("Size of partition bins (world pixels).")] public readonly int BinSize = 250; + [Desc("Facing of new actors.")] + public readonly WAngle DefaultActorFacing = new(384); + void ICreatePlayersInfo.CreateServerPlayers(MapPreview map, Session lobbyInfo, List players, MersenneTwister playerRandom) { throw new NotImplementedException("EditorActorLayer must not be defined on the world actor."); diff --git a/OpenRA.Mods.Common/Traits/World/EditorCursorLayer.cs b/OpenRA.Mods.Common/Traits/World/EditorCursorLayer.cs index 4ab901d23c..3bc8c7c6b2 100644 --- a/OpenRA.Mods.Common/Traits/World/EditorCursorLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/EditorCursorLayer.cs @@ -12,57 +12,24 @@ using System.Collections.Generic; using System.Linq; using OpenRA.Graphics; -using OpenRA.Mods.Common.Terrain; +using OpenRA.Mods.Common.Widgets; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { - public enum EditorCursorType { None, Actor, TerrainTemplate, Resource } - [TraitLocation(SystemActors.EditorWorld)] [Desc("Required for the map editor to work. Attach this to the world actor.")] - public class EditorCursorLayerInfo : TraitInfo, Requires, Requires + public class EditorCursorLayerInfo : TraitInfo, Requires, Requires { } + + public class EditorCursorLayer : ITickRender, IRenderAboveShroud, IRenderAnnotations { - public readonly WAngle PreviewFacing = new(384); + IEditorBrush brush; - public override object Create(ActorInitializer init) { return new EditorCursorLayer(init.Self, this); } - } + static readonly IEnumerable NoRenderables = Enumerable.Empty(); - public class EditorCursorLayer : IWorldLoaded, ITickRender, IRenderAboveShroud, IRenderAnnotations - { - readonly EditorCursorLayerInfo info; - readonly EditorActorLayer editorLayer; - readonly ITiledTerrainRenderer terrainRenderer; - readonly World world; - IResourceRenderer[] resourceRenderers; - - public int CurrentToken { get; private set; } - public EditorCursorType Type { get; private set; } - public EditorActorPreview Actor { get; private set; } - CPos actorLocation; - SubCell actorSubCell; - WVec actorCenterOffset; - bool actorSharesCell; - - public TerrainTemplateInfo TerrainTemplate { get; private set; } - public string ResourceType { get; private set; } - CPos terrainOrResourceCell; - bool terrainOrResourceDirty; - readonly List terrainOrResourcePreview = new(); - - public EditorCursorLayer(Actor self, EditorCursorLayerInfo info) + public void SetBrush(IEditorBrush brush) { - this.info = info; - world = self.World; - editorLayer = self.Trait(); - terrainRenderer = self.Trait(); - - Type = EditorCursorType.None; - } - - void IWorldLoaded.WorldLoaded(World w, WorldRenderer wr) - { - resourceRenderers = w.WorldActor.TraitsImplementing().ToArray(); + this.brush = brush; } void ITickRender.TickRender(WorldRenderer wr, Actor self) @@ -70,69 +37,15 @@ namespace OpenRA.Mods.Common.Traits if (wr.World.Type != WorldType.Editor) return; - if (Type == EditorCursorType.TerrainTemplate || Type == EditorCursorType.Resource) - { - var cell = wr.Viewport.ViewToWorld(Viewport.LastMousePos); - if (terrainOrResourceCell != cell || terrainOrResourceDirty) - { - terrainOrResourceCell = cell; - terrainOrResourceDirty = false; - terrainOrResourcePreview.Clear(); - - var pos = world.Map.CenterOfCell(cell); - if (Type == EditorCursorType.TerrainTemplate) - terrainOrResourcePreview.AddRange(terrainRenderer.RenderPreview(wr, TerrainTemplate, pos)); - else - terrainOrResourcePreview.AddRange(resourceRenderers.SelectMany(r => r.RenderPreview(wr, ResourceType, pos))); - } - } - else if (Type == EditorCursorType.Actor) - { - // Offset mouse position by the center offset (in world pixels) - var worldPx = wr.Viewport.ViewToWorldPx(Viewport.LastMousePos) - wr.ScreenPxOffset(actorCenterOffset); - var cell = wr.Viewport.ViewToWorld(wr.Viewport.WorldToViewPx(worldPx)); - var subCell = actorSharesCell ? editorLayer.FreeSubCellAt(cell) : SubCell.Invalid; - var updated = false; - if (actorLocation != cell) - { - actorLocation = cell; - Actor.ReplaceInit(new LocationInit(cell)); - updated = true; - } - - if (actorSubCell != subCell) - { - actorSubCell = subCell; - - if (Actor.RemoveInits() > 0) - updated = true; - - var subcell = world.Map.Tiles.Contains(cell) ? editorLayer.FreeSubCellAt(cell) : SubCell.Invalid; - if (subcell != SubCell.Invalid) - { - Actor.AddInit(new SubCellInit(subcell)); - updated = true; - } - } - - if (updated) - Actor = new EditorActorPreview(wr, null, Actor.Export(), Actor.Owner); - } + brush?.TickRender(wr, self); } - static readonly IEnumerable NoRenderables = Enumerable.Empty(); IEnumerable IRenderAboveShroud.RenderAboveShroud(Actor self, WorldRenderer wr) { if (wr.World.Type != WorldType.Editor) return NoRenderables; - if (Type == EditorCursorType.TerrainTemplate || Type == EditorCursorType.Resource) - return terrainOrResourcePreview; - - if (Type == EditorCursorType.Actor) - return Actor.Render().OrderBy(WorldRenderer.RenderableZPositionComparisonKey); - - return NoRenderables; + return brush?.RenderAboveShroud(self, wr) ?? NoRenderables; } bool IRenderAboveShroud.SpatiallyPartitionable => false; @@ -142,88 +55,9 @@ namespace OpenRA.Mods.Common.Traits if (wr.World.Type != WorldType.Editor) return NoRenderables; - return Type == EditorCursorType.Actor ? Actor.RenderAnnotations() : NoRenderables; + return brush?.RenderAnnotations(self, wr) ?? NoRenderables; } bool IRenderAnnotations.SpatiallyPartitionable => false; - - public int SetActor(WorldRenderer wr, ActorInfo actor, PlayerReference owner) - { - var ios = actor.TraitInfoOrDefault(); - var buildingInfo = ios as BuildingInfo; - actorCenterOffset = buildingInfo?.CenterOffset(world) ?? WVec.Zero; - actorSharesCell = ios != null && ios.SharesCell; - actorSubCell = SubCell.Invalid; - - // Enforce first entry of ValidOwnerNames as owner if the actor has RequiresSpecificOwners - var ownerName = owner.Name; - var specificOwnerInfo = actor.TraitInfoOrDefault(); - if (specificOwnerInfo != null && !specificOwnerInfo.ValidOwnerNames.Contains(ownerName)) - ownerName = specificOwnerInfo.ValidOwnerNames.First(); - - var reference = new ActorReference(actor.Name) - { - new OwnerInit(ownerName), - new FactionInit(owner.Faction) - }; - - var worldPx = wr.Viewport.ViewToWorldPx(Viewport.LastMousePos) - wr.ScreenPxOffset(actorCenterOffset); - var cell = wr.Viewport.ViewToWorld(wr.Viewport.WorldToViewPx(worldPx)); - - reference.Add(new LocationInit(cell)); - if (ios != null && ios.SharesCell) - { - actorSubCell = editorLayer.FreeSubCellAt(cell); - if (actorSubCell != SubCell.Invalid) - reference.Add(new SubCellInit(actorSubCell)); - } - - if (actor.HasTraitInfo()) - reference.Add(new FacingInit(info.PreviewFacing)); - - Type = EditorCursorType.Actor; - Actor = new EditorActorPreview(wr, null, reference, owner); - TerrainTemplate = null; - ResourceType = null; - - return ++CurrentToken; - } - - public int SetTerrainTemplate(WorldRenderer wr, TerrainTemplateInfo template) - { - terrainOrResourceCell = wr.Viewport.ViewToWorld(wr.Viewport.WorldToViewPx(Viewport.LastMousePos)); - - Type = EditorCursorType.TerrainTemplate; - TerrainTemplate = template; - Actor = null; - ResourceType = null; - terrainOrResourceDirty = true; - - return ++CurrentToken; - } - - public int SetResource(WorldRenderer wr, string resourceType) - { - terrainOrResourceCell = wr.Viewport.ViewToWorld(wr.Viewport.WorldToViewPx(Viewport.LastMousePos)); - - Type = EditorCursorType.Resource; - ResourceType = resourceType; - Actor = null; - TerrainTemplate = null; - terrainOrResourceDirty = true; - - return ++CurrentToken; - } - - public void Clear(int token) - { - if (token != CurrentToken) - return; - - Type = EditorCursorType.None; - Actor = null; - TerrainTemplate = null; - ResourceType = null; - } } } diff --git a/OpenRA.Mods.Common/Traits/World/EditorSelectionLayer.cs b/OpenRA.Mods.Common/Traits/World/EditorSelectionLayer.cs deleted file mode 100644 index 1c6b62aa1b..0000000000 --- a/OpenRA.Mods.Common/Traits/World/EditorSelectionLayer.cs +++ /dev/null @@ -1,77 +0,0 @@ -#region Copyright & License Information -/* - * Copyright (c) The OpenRA Developers and Contributors - * 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, either version 3 of - * the License, or (at your option) any later version. For more - * information, see COPYING. - */ -#endregion - -using System.Collections.Generic; -using OpenRA.Graphics; -using OpenRA.Mods.Common.Graphics; -using OpenRA.Mods.Common.Widgets; -using OpenRA.Traits; -using OpenRA.Widgets; -using Color = OpenRA.Primitives.Color; - -namespace OpenRA.Mods.Common.Traits -{ - [TraitLocation(SystemActors.EditorWorld)] - [Desc("Renders the selection grid in the editor.")] - public class EditorSelectionLayerInfo : TraitInfo, Requires, IEditorSelectionLayer - { - [Desc("Main color of the selection grid.")] - public readonly Color MainColor = Color.White; - - [Desc("Alternate color of the selection grid.")] - public readonly Color AltColor = Color.Black; - - [Desc("Main color of the paste grid.")] - public readonly Color PasteColor = Color.FromArgb(0xFF4CFF00); - - [Desc("Thickness of the selection grid lines.")] - public readonly int LineThickness = 1; - - [Desc("Render offset of the secondary grid lines.")] - public readonly int2 AltPixelOffset = new(1, 1); - - public override object Create(ActorInitializer init) { return new EditorSelectionLayer(this); } - } - - public class EditorSelectionLayer : IRenderAnnotations, IWorldLoaded - { - readonly EditorSelectionLayerInfo info; - EditorViewportControllerWidget editor; - - public EditorSelectionLayer(EditorSelectionLayerInfo info) - { - this.info = info; - } - - void IWorldLoaded.WorldLoaded(World w, WorldRenderer wr) - { - var worldRoot = Ui.Root.Get("EDITOR_WORLD_ROOT"); - editor = worldRoot.Get("MAP_EDITOR"); - } - - IEnumerable IRenderAnnotations.RenderAnnotations(Actor self, WorldRenderer wr) - { - if (editor.DefaultBrush.CurrentDragBounds != null) - { - yield return new EditorSelectionAnnotationRenderable(editor.DefaultBrush.CurrentDragBounds, info.AltColor, info.AltPixelOffset, null); - yield return new EditorSelectionAnnotationRenderable(editor.DefaultBrush.CurrentDragBounds, info.MainColor, int2.Zero, null); - } - - if (editor.CurrentBrush is EditorCopyPasteBrush pasteBrush && pasteBrush.PastePreviewPosition != null) - { - yield return new EditorSelectionAnnotationRenderable(pasteBrush.Region, info.AltColor, info.AltPixelOffset, pasteBrush.PastePreviewPosition); - yield return new EditorSelectionAnnotationRenderable(pasteBrush.Region, info.PasteColor, int2.Zero, pasteBrush.PastePreviewPosition); - } - } - - bool IRenderAnnotations.SpatiallyPartitionable => false; - } -} diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20231010/MovePreviewFacing.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20231010/MovePreviewFacing.cs new file mode 100644 index 0000000000..f0beeb9977 --- /dev/null +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20231010/MovePreviewFacing.cs @@ -0,0 +1,42 @@ +#region Copyright & License Information +/* + * Copyright (c) The OpenRA Developers and Contributors + * 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, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System.Collections.Generic; + +namespace OpenRA.Mods.Common.UpdateRules.Rules +{ + public class MovePreviewFacing : UpdateRule + { + public override string Name => "Move map editor preview facing to EditorActorLayer"; + + public override string Description => + "PreviewFacing property was moved from the EditorCursorLayer to the EditorActorLayer."; + + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) + { + var cursorLayer = actorNode.LastChildMatching("EditorCursorLayer"); + if (cursorLayer == null || cursorLayer.IsRemoval()) + yield break; + + var node = cursorLayer.LastChildMatching("PreviewFacing"); + cursorLayer.RemoveNodes("PreviewFacing"); + if (node == null || node.IsRemoval()) + yield break; + + var actorLayer = actorNode.LastChildMatching("EditorActorLayer"); + if (actorLayer != null && !actorLayer.IsRemoval()) + { + node.RenameKey("DefaultActorFacing"); + actorLayer.AddNode(node); + } + } + } +} diff --git a/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs b/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs index 58718c7d42..2c20151655 100644 --- a/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs +++ b/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs @@ -88,6 +88,7 @@ namespace OpenRA.Mods.Common.UpdateRules new RemoveEditorSelectionLayerProperties(), new AddMarkerLayerOverlay(), new AddSupportPowerBlockedCursor(), + new MovePreviewFacing(), // Execute these rules last to avoid premature yaml merge crashes. new ReplaceCloakPalette(), diff --git a/OpenRA.Mods.Common/Widgets/EditorViewportControllerWidget.cs b/OpenRA.Mods.Common/Widgets/EditorViewportControllerWidget.cs index 0eeb87becf..d649cf78bb 100644 --- a/OpenRA.Mods.Common/Widgets/EditorViewportControllerWidget.cs +++ b/OpenRA.Mods.Common/Widgets/EditorViewportControllerWidget.cs @@ -11,12 +11,23 @@ using System; using OpenRA.Graphics; +using OpenRA.Mods.Common.Traits; using OpenRA.Widgets; +using Color = OpenRA.Primitives.Color; namespace OpenRA.Mods.Common.Widgets { public class EditorViewportControllerWidget : Widget { + [Desc("Main color of the selection grid.")] + public readonly Color SelectionMainColor = Color.White; + + [Desc("Alternate color of the selection grid.")] + public readonly Color SelectionAltColor = Color.Black; + + [Desc("Main color of the copy / paste grid.")] + public readonly Color PasteColor = Color.FromArgb(0xFF4CFF00); + public IEditorBrush CurrentBrush { get; private set; } public readonly string TooltipContainer; @@ -27,6 +38,8 @@ namespace OpenRA.Mods.Common.Widgets readonly Lazy tooltipContainer; readonly WorldRenderer worldRenderer; + readonly EditorCursorLayer editorCursor; + public int2 SelectionAltOffset { get; } bool enableTooltips; @@ -37,8 +50,15 @@ namespace OpenRA.Mods.Common.Widgets tooltipContainer = Exts.Lazy(() => Ui.Root.Get(TooltipContainer)); CurrentBrush = DefaultBrush = new EditorDefaultBrush(this, worldRenderer); + editorCursor = worldRenderer.World.WorldActor.Trait(); + editorCursor.SetBrush(CurrentBrush); + // Allow zooming out to full map size worldRenderer.Viewport.UnlockMinimumZoom(0.25f); + + SelectionAltOffset = worldRenderer.World.Map.Grid.Type == MapGridType.Rectangular + ? new int2(1, 1) + : new int2(0, 1); } public void ClearBrush() { SetBrush(null); } @@ -50,6 +70,7 @@ namespace OpenRA.Mods.Common.Widgets CurrentBrush = brush ?? DefaultBrush; BrushChanged?.Invoke(); + editorCursor.SetBrush(CurrentBrush); } public override void MouseEntered() diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/ActorSelectorLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/ActorSelectorLogic.cs index 41c5c8763c..97de867d69 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Editor/ActorSelectorLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/ActorSelectorLogic.cs @@ -43,7 +43,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic readonly DropDownButtonWidget ownersDropDown; readonly Ruleset mapRules; readonly ActorSelectorActor[] allActors; - readonly EditorCursorLayer editorCursor; + readonly EditorViewportControllerWidget editor; PlayerReference selectedOwner; @@ -53,7 +53,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic { mapRules = world.Map.Rules; ownersDropDown = widget.Get("OWNERS_DROPDOWN"); - editorCursor = world.WorldActor.Trait(); + editor = widget.Parent.Parent.Get("MAP_EDITOR"); var editorLayer = world.WorldActor.Trait(); selectedOwner = editorLayer.Players.Players.Values.First(); @@ -167,9 +167,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic ownersDropDown.TextColor = option.Color; InitializePreviews(); - var actor = editorCursor.Actor; - if (actor != null) + if (editor.CurrentBrush is EditorActorBrush brush) { + var actor = brush.Preview; actor.Owner = option; actor.ReplaceInit(new OwnerInit(option.Name)); actor.ReplaceInit(new FactionInit(option.Faction)); @@ -204,7 +204,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic try { var item = ScrollItemWidget.Setup(ItemTemplate, - () => editorCursor.Type == EditorCursorType.Actor && editorCursor.Actor.Info == actor, + () => Editor.CurrentBrush is EditorActorBrush eab && eab.Preview.Info == actor, () => Editor.SetBrush(new EditorActorBrush(Editor, actor, selectedOwner, WorldRenderer))); var preview = item.Get("ACTOR_PREVIEW"); diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/LayerSelectorLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/LayerSelectorLogic.cs index 6100c04680..4e83fce53e 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Editor/LayerSelectorLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/LayerSelectorLogic.cs @@ -19,7 +19,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic { readonly EditorViewportControllerWidget editor; readonly WorldRenderer worldRenderer; - readonly EditorCursorLayer editorCursor; readonly ScrollPanelWidget layerTemplateList; readonly ScrollItemWidget layerPreviewTemplate; @@ -29,8 +28,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic { this.worldRenderer = worldRenderer; editor = widget.Parent.Parent.Get("MAP_EDITOR"); - editorCursor = worldRenderer.World.WorldActor.Trait(); - layerTemplateList = widget.Get("LAYERTEMPLATE_LIST"); layerTemplateList.Layout = new GridLayout(layerTemplateList); layerPreviewTemplate = layerTemplateList.Get("LAYERPREVIEW_TEMPLATE"); @@ -46,7 +43,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic foreach (var resourceType in resourceRenderer.ResourceTypes) { var newResourcePreviewTemplate = ScrollItemWidget.Setup(layerPreviewTemplate, - () => editorCursor.Type == EditorCursorType.Resource && editorCursor.ResourceType == resourceType, + () => editor.CurrentBrush is EditorResourceBrush brush && brush.ResourceType == resourceType, () => editor.SetBrush(new EditorResourceBrush(editor, resourceType, worldRenderer))); newResourcePreviewTemplate.Bounds.X = 0; diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/TileSelectorLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/TileSelectorLogic.cs index 440510b54f..9887e738fe 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Editor/TileSelectorLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/TileSelectorLogic.cs @@ -15,7 +15,6 @@ using System.IO; using System.Linq; using OpenRA.Graphics; using OpenRA.Mods.Common.Terrain; -using OpenRA.Mods.Common.Traits; using OpenRA.Widgets; namespace OpenRA.Mods.Common.Widgets.Logic @@ -40,7 +39,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic readonly ITemplatedTerrainInfo terrainInfo; readonly TileSelectorTemplate[] allTemplates; - readonly EditorCursorLayer editorCursor; [ObjectCreator.UseCtor] public TileSelectorLogic(Widget widget, ModData modData, World world, WorldRenderer worldRenderer) @@ -51,7 +49,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic throw new InvalidDataException("TileSelectorLogic requires a template-based tileset."); allTemplates = terrainInfo.Templates.Values.Select(t => new TileSelectorTemplate(t)).ToArray(); - editorCursor = world.WorldActor.Trait(); allCategories = allTemplates.SelectMany(t => t.Categories) .Distinct() @@ -108,7 +105,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic var tileId = t.Template.Id; var item = ScrollItemWidget.Setup(ItemTemplate, - () => editorCursor.Type == EditorCursorType.TerrainTemplate && editorCursor.TerrainTemplate.Id == tileId, + () => Editor.CurrentBrush is EditorTileBrush editorCursor && editorCursor.TerrainTemplate.Id == tileId, () => Editor.SetBrush(new EditorTileBrush(Editor, tileId, WorldRenderer))); var preview = item.Get("TILE_PREVIEW"); diff --git a/mods/cnc/rules/world.yaml b/mods/cnc/rules/world.yaml index 76e18622db..20c4c488f3 100644 --- a/mods/cnc/rules/world.yaml +++ b/mods/cnc/rules/world.yaml @@ -294,7 +294,6 @@ EditorWorld: TerrainType: BlueTiberium AllowedTerrainTypes: Clear, Road MaxDensity: 12 - EditorSelectionLayer: LoadWidgetAtGameStart: EditorActionManager: BuildableTerrainOverlay: diff --git a/mods/d2k/rules/world.yaml b/mods/d2k/rules/world.yaml index e1dc6439bf..43b14fcb34 100644 --- a/mods/d2k/rules/world.yaml +++ b/mods/d2k/rules/world.yaml @@ -260,7 +260,6 @@ EditorWorld: TerrainType: Spice AllowedTerrainTypes: SpiceSand MaxDensity: 20 - EditorSelectionLayer: LoadWidgetAtGameStart: EditorActionManager: BuildableTerrainOverlay: diff --git a/mods/ra/rules/world.yaml b/mods/ra/rules/world.yaml index c8bcce582c..83801d417d 100644 --- a/mods/ra/rules/world.yaml +++ b/mods/ra/rules/world.yaml @@ -316,7 +316,6 @@ EditorWorld: TerrainType: Gems AllowedTerrainTypes: Clear, Road MaxDensity: 3 - EditorSelectionLayer: LoadWidgetAtGameStart: EditorActionManager: BuildableTerrainOverlay: diff --git a/mods/ts/rules/world.yaml b/mods/ts/rules/world.yaml index 1d025555cf..33ba443af0 100644 --- a/mods/ts/rules/world.yaml +++ b/mods/ts/rules/world.yaml @@ -422,8 +422,6 @@ EditorWorld: AllowedTerrainTypes: Clear, Rough, DirtRoad MaxDensity: 2 VeinholeActors: veinhole - EditorSelectionLayer: - AltPixelOffset: 0,1 LoadWidgetAtGameStart: EditorActionManager: BuildableTerrainOverlay: