From e74033bded9380d9ffac6942c26ae1c0796ca37c Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sun, 12 Jan 2020 12:27:06 +0000 Subject: [PATCH] Render editor actor previews as part of the world. --- OpenRA.Game/Map/ActorReference.cs | 9 + .../EditorBrushes/EditorActorBrush.cs | 123 +++---------- .../Traits/World/EditorCursorLayer.cs | 170 ++++++++++++++++++ .../Logic/Editor/ActorSelectorLogic.cs | 4 +- mods/cnc/chrome/editor.yaml | 2 - mods/cnc/rules/world.yaml | 1 + mods/common/chrome/editor.yaml | 2 - mods/d2k/rules/world.yaml | 1 + mods/ra/rules/world.yaml | 1 + mods/ts/rules/world.yaml | 1 + 10 files changed, 206 insertions(+), 108 deletions(-) create mode 100644 OpenRA.Mods.Common/Traits/World/EditorCursorLayer.cs diff --git a/OpenRA.Game/Map/ActorReference.cs b/OpenRA.Game/Map/ActorReference.cs index d0fe6aac1e..d007c06d28 100644 --- a/OpenRA.Game/Map/ActorReference.cs +++ b/OpenRA.Game/Map/ActorReference.cs @@ -71,5 +71,14 @@ namespace OpenRA // for initialization syntax public void Add(object o) { InitDict.Add(o); } public IEnumerator GetEnumerator() { return InitDict.GetEnumerator(); } + + public ActorReference Clone() + { + var clone = new ActorReference(Type); + foreach (var init in InitDict) + clone.InitDict.Add(init); + + return clone; + } } } diff --git a/OpenRA.Mods.Common/EditorBrushes/EditorActorBrush.cs b/OpenRA.Mods.Common/EditorBrushes/EditorActorBrush.cs index 78d004279e..0c13703072 100644 --- a/OpenRA.Mods.Common/EditorBrushes/EditorActorBrush.cs +++ b/OpenRA.Mods.Common/EditorBrushes/EditorActorBrush.cs @@ -12,70 +12,27 @@ using System.Linq; using OpenRA.Graphics; using OpenRA.Mods.Common.Traits; -using OpenRA.Primitives; -using OpenRA.Traits; namespace OpenRA.Mods.Common.Widgets { public sealed class EditorActorBrush : IEditorBrush { - public readonly ActorInfo Actor; - - readonly WorldRenderer worldRenderer; readonly World world; readonly EditorActorLayer editorLayer; + readonly EditorCursorLayer editorCursor; readonly EditorActionManager editorActionManager; readonly EditorViewportControllerWidget editorWidget; - readonly ActorPreviewWidget preview; - readonly WVec centerOffset; - readonly PlayerReference owner; - readonly CVec[] footprint; - - int facing = 96; + readonly int cursorToken; public EditorActorBrush(EditorViewportControllerWidget editorWidget, ActorInfo actor, PlayerReference owner, WorldRenderer wr) { this.editorWidget = editorWidget; - worldRenderer = wr; world = wr.World; editorLayer = world.WorldActor.Trait(); + editorCursor = world.WorldActor.Trait(); editorActionManager = world.WorldActor.Trait(); - Actor = actor; - this.owner = owner; - var ownerName = owner.Name; - - preview = editorWidget.Get("DRAG_ACTOR_PREVIEW"); - preview.GetScale = () => worldRenderer.Viewport.Zoom; - preview.IsVisible = () => editorWidget.CurrentBrush == this; - - var buildingInfo = actor.TraitInfoOrDefault(); - if (buildingInfo != null) - centerOffset = buildingInfo.CenterOffset(world); - - // Enforce first entry of ValidOwnerNames as owner if the actor has RequiresSpecificOwners - var specificOwnerInfo = actor.TraitInfoOrDefault(); - if (specificOwnerInfo != null && !specificOwnerInfo.ValidOwnerNames.Contains(ownerName)) - ownerName = specificOwnerInfo.ValidOwnerNames.First(); - - var td = new TypeDictionary(); - td.Add(new FacingInit(facing)); - td.Add(new TurretFacingInit(facing)); - td.Add(new OwnerInit(ownerName)); - td.Add(new FactionInit(owner.Faction)); - preview.SetPreview(actor, td); - - var ios = actor.TraitInfoOrDefault(); - if (ios != null) - footprint = ios.OccupiedCells(actor, CPos.Zero) - .Select(c => c.Key - CPos.Zero) - .ToArray(); - else - footprint = new CVec[0]; - - // 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(); + cursorToken = editorCursor.SetActor(wr, actor, owner); } public bool HandleMouseInput(MouseInput mi) @@ -95,38 +52,29 @@ namespace OpenRA.Mods.Common.Widgets return false; } - var cell = worldRenderer.Viewport.ViewToWorld(mi.Location - worldRenderer.ScreenPxOffset(centerOffset)); + if (editorCursor.CurrentToken != cursorToken) + return false; + if (mi.Button == MouseButton.Left && mi.Event == MouseInputEvent.Down) { // Check the actor is inside the map - if (!footprint.All(c => world.Map.Tiles.Contains(cell + c))) + var actor = editorCursor.Actor; + if (!actor.Footprint.All(c => world.Map.Tiles.Contains(c.Key))) return true; - // Enforce first entry of ValidOwnerNames as owner if the actor has RequiresSpecificOwners - var action = new AddActorAction(editorLayer, Actor, cell, owner, facing); + var action = new AddActorAction(editorLayer, actor.Actor); editorActionManager.Add(action); } return true; } - public void Tick() + public void Tick() { } + + public void Dispose() { - var cell = worldRenderer.Viewport.ViewToWorld(Viewport.LastMousePos - worldRenderer.ScreenPxOffset(centerOffset)); - var pos = world.Map.CenterOfCell(cell) + centerOffset; - - 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); + editorCursor.Clear(cursorToken); } - - public void Dispose() { } } class AddActorAction : IEditorAction @@ -134,20 +82,16 @@ namespace OpenRA.Mods.Common.Widgets public string Text { get; private set; } readonly EditorActorLayer editorLayer; - readonly ActorInfo actor; - readonly CPos cell; - readonly PlayerReference owner; - readonly int facing; + readonly ActorReference actor; EditorActorPreview editorActorPreview; - public AddActorAction(EditorActorLayer editorLayer, ActorInfo actor, CPos cell, PlayerReference owner, int facing) + public AddActorAction(EditorActorLayer editorLayer, ActorReference actor) { this.editorLayer = editorLayer; - this.actor = actor; - this.cell = cell; - this.owner = owner; - this.facing = facing; + + // Take an immutable copy of the reference + this.actor = actor.Clone(); } public void Execute() @@ -157,34 +101,7 @@ namespace OpenRA.Mods.Common.Widgets public void Do() { - var ownerName = owner.Name; - var specificOwnerInfo = actor.TraitInfoOrDefault(); - if (specificOwnerInfo != null && !specificOwnerInfo.ValidOwnerNames.Contains(ownerName)) - ownerName = specificOwnerInfo.ValidOwnerNames.First(); - - var newActorReference = new ActorReference(actor.Name); - newActorReference.Add(new OwnerInit(ownerName)); - - newActorReference.Add(new LocationInit(cell)); - - var ios = actor.TraitInfoOrDefault(); - 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.HasTraitInfo()) - initDict.Add(new FacingInit(facing)); - - if (actor.HasTraitInfo()) - initDict.Add(new TurretFacingInit(facing)); - - editorActorPreview = editorLayer.Add(newActorReference); - + editorActorPreview = editorLayer.Add(actor); Text = "Added {0} ({1})".F(editorActorPreview.Info.Name, editorActorPreview.ID); } diff --git a/OpenRA.Mods.Common/Traits/World/EditorCursorLayer.cs b/OpenRA.Mods.Common/Traits/World/EditorCursorLayer.cs new file mode 100644 index 0000000000..0ec0aae541 --- /dev/null +++ b/OpenRA.Mods.Common/Traits/World/EditorCursorLayer.cs @@ -0,0 +1,170 @@ +#region Copyright & License Information +/* + * Copyright 2007-2020 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, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System.Collections.Generic; +using System.Linq; +using OpenRA.Graphics; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits +{ + public enum EditorCursorType { None, Actor } + + [Desc("Required for the map editor to work. Attach this to the world actor.")] + public class EditorCursorLayerInfo : ITraitInfo, Requires + { + public readonly int PreviewFacing = 96; + + public object Create(ActorInitializer init) { return new EditorCursorLayer(init.Self, this); } + } + + public class EditorCursorLayer : ITickRender, IRenderAboveShroud, IRenderAnnotations + { + readonly EditorCursorLayerInfo info; + readonly EditorActorLayer editorLayer; + readonly World world; + + 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 EditorCursorLayer(Actor self, EditorCursorLayerInfo info) + { + this.info = info; + world = self.World; + editorLayer = self.Trait(); + + Type = EditorCursorType.None; + } + + void ITickRender.TickRender(WorldRenderer wr, Actor self) + { + if (wr.World.Type != WorldType.Editor) + return; + + if (Actor != null) + { + // 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.Actor.InitDict.Remove(Actor.Actor.InitDict.Get()); + Actor.Actor.InitDict.Add(new LocationInit(cell)); + updated = true; + } + + if (actorSubCell != subCell) + { + actorSubCell = subCell; + + var subcellInit = Actor.Actor.InitDict.GetOrDefault(); + if (subcellInit != null) + { + Actor.Actor.InitDict.Remove(subcellInit); + updated = true; + } + + var subcell = world.Map.Tiles.Contains(cell) ? editorLayer.FreeSubCellAt(cell) : SubCell.Invalid; + if (subcell != SubCell.Invalid) + { + Actor.Actor.InitDict.Add(new SubCellInit(subcell)); + updated = true; + } + } + + if (updated) + Actor = new EditorActorPreview(wr, null, Actor.Actor, Actor.Owner); + } + } + + static readonly IEnumerable NoRenderables = Enumerable.Empty(); + IEnumerable IRenderAboveShroud.RenderAboveShroud(Actor self, WorldRenderer wr) + { + if (wr.World.Type != WorldType.Editor) + return NoRenderables; + + if (Type == EditorCursorType.Actor) + return Actor.Render().OrderBy(WorldRenderer.RenderableScreenZPositionComparisonKey); + + return NoRenderables; + } + + bool IRenderAboveShroud.SpatiallyPartitionable { get { return false; } } + + public IEnumerable RenderAnnotations(Actor self, WorldRenderer wr) + { + if (wr.World.Type != WorldType.Editor) + return NoRenderables; + + return Actor != null ? Actor.RenderAnnotations() : NoRenderables; + } + + bool IRenderAnnotations.SpatiallyPartitionable { get { return false; } } + + public int SetActor(WorldRenderer wr, ActorInfo actor, PlayerReference owner) + { + var ios = actor.TraitInfoOrDefault(); + var buildingInfo = ios as BuildingInfo; + actorCenterOffset = buildingInfo != null ? 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); + reference.InitDict.Add(new OwnerInit(ownerName)); + reference.InitDict.Add(new FactionInit(owner.Faction)); + + var worldPx = wr.Viewport.ViewToWorldPx(Viewport.LastMousePos) - wr.ScreenPxOffset(actorCenterOffset); + var cell = wr.Viewport.ViewToWorld(wr.Viewport.WorldToViewPx(worldPx)); + + reference.InitDict.Add(new LocationInit(cell)); + if (ios != null && ios.SharesCell) + { + actorSubCell = editorLayer.FreeSubCellAt(cell); + if (actorSubCell != SubCell.Invalid) + reference.InitDict.Add(new SubCellInit(actorSubCell)); + } + + if (actor.HasTraitInfo()) + reference.InitDict.Add(new FacingInit(info.PreviewFacing)); + + if (actor.HasTraitInfo()) + reference.InitDict.Add(new TurretFacingInit(info.PreviewFacing)); + + Type = EditorCursorType.Actor; + Actor = new EditorActorPreview(wr, null, reference, owner); + + return ++CurrentToken; + } + + public void Clear(int token) + { + if (token != CurrentToken) + return; + + Type = EditorCursorType.None; + Actor = null; + } + } +} diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/ActorSelectorLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/ActorSelectorLogic.cs index fedbdf576e..b277e23523 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Editor/ActorSelectorLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/ActorSelectorLogic.cs @@ -40,6 +40,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic readonly DropDownButtonWidget ownersDropDown; readonly Ruleset mapRules; readonly ActorSelectorActor[] allActors; + readonly EditorCursorLayer editorCursor; PlayerReference selectedOwner; @@ -49,6 +50,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic { mapRules = world.Map.Rules; ownersDropDown = widget.Get("OWNERS_DROPDOWN"); + editorCursor = world.WorldActor.Trait(); var editorLayer = world.WorldActor.Trait(); selectedOwner = editorLayer.Players.Players.Values.First(); @@ -181,7 +183,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic try { var item = ScrollItemWidget.Setup(ItemTemplate, - () => { var brush = Editor.CurrentBrush as EditorActorBrush; return brush != null && brush.Actor == actor; }, + () => editorCursor.Type == EditorCursorType.Actor && editorCursor.Actor.Info == actor, () => Editor.SetBrush(new EditorActorBrush(Editor, actor, selectedOwner, WorldRenderer))); var preview = item.Get("ACTOR_PREVIEW"); diff --git a/mods/cnc/chrome/editor.yaml b/mods/cnc/chrome/editor.yaml index f208f26459..08784ffd13 100644 --- a/mods/cnc/chrome/editor.yaml +++ b/mods/cnc/chrome/editor.yaml @@ -224,8 +224,6 @@ Container@EDITOR_WORLD_ROOT: Visible: false Sprite@DRAG_LAYER_PREVIEW: Visible: false - ActorPreview@DRAG_ACTOR_PREVIEW: - Visible: false Background@ACTOR_EDIT_PANEL: Background: panel-black Width: 269 diff --git a/mods/cnc/rules/world.yaml b/mods/cnc/rules/world.yaml index 911f7dfc34..dafe2b023c 100644 --- a/mods/cnc/rules/world.yaml +++ b/mods/cnc/rules/world.yaml @@ -265,6 +265,7 @@ World: EditorWorld: Inherits: ^BaseWorld EditorActorLayer: + EditorCursorLayer: EditorResourceLayer: EditorSelectionLayer: LoadWidgetAtGameStart: diff --git a/mods/common/chrome/editor.yaml b/mods/common/chrome/editor.yaml index e1484e2ca7..7dc2958caf 100644 --- a/mods/common/chrome/editor.yaml +++ b/mods/common/chrome/editor.yaml @@ -211,8 +211,6 @@ Container@EDITOR_WORLD_ROOT: TooltipContainer: TOOLTIP_CONTAINER TooltipTemplate: SIMPLE_TOOLTIP Children: - ActorPreview@DRAG_ACTOR_PREVIEW: - Visible: false TerrainTemplatePreview@DRAG_TILE_PREVIEW: Visible: false Sprite@DRAG_LAYER_PREVIEW: diff --git a/mods/d2k/rules/world.yaml b/mods/d2k/rules/world.yaml index a2d8ade2c2..bf07b0d0b9 100644 --- a/mods/d2k/rules/world.yaml +++ b/mods/d2k/rules/world.yaml @@ -240,6 +240,7 @@ World: EditorWorld: Inherits: ^BaseWorld EditorActorLayer: + EditorCursorLayer: D2kEditorResourceLayer: EditorSelectionLayer: LoadWidgetAtGameStart: diff --git a/mods/ra/rules/world.yaml b/mods/ra/rules/world.yaml index e6fd2bc601..b3e12dba82 100644 --- a/mods/ra/rules/world.yaml +++ b/mods/ra/rules/world.yaml @@ -281,6 +281,7 @@ World: EditorWorld: Inherits: ^BaseWorld EditorActorLayer: + EditorCursorLayer: EditorResourceLayer: EditorSelectionLayer: LoadWidgetAtGameStart: diff --git a/mods/ts/rules/world.yaml b/mods/ts/rules/world.yaml index c5a4f4eac9..bee4b0f5d6 100644 --- a/mods/ts/rules/world.yaml +++ b/mods/ts/rules/world.yaml @@ -375,6 +375,7 @@ World: EditorWorld: Inherits: ^BaseWorld EditorActorLayer: + EditorCursorLayer: EditorResourceLayer: EditorSelectionLayer: Palette: placefootprint