Render editor actor previews as part of the world.

This commit is contained in:
Paul Chote
2020-01-12 12:27:06 +00:00
committed by abcdefg30
parent fe25fdf0ff
commit e74033bded
10 changed files with 206 additions and 108 deletions

View File

@@ -71,5 +71,14 @@ namespace OpenRA
// for initialization syntax // for initialization syntax
public void Add(object o) { InitDict.Add(o); } public void Add(object o) { InitDict.Add(o); }
public IEnumerator GetEnumerator() { return InitDict.GetEnumerator(); } 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;
}
} }
} }

View File

@@ -12,70 +12,27 @@
using System.Linq; using System.Linq;
using OpenRA.Graphics; using OpenRA.Graphics;
using OpenRA.Mods.Common.Traits; using OpenRA.Mods.Common.Traits;
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Widgets namespace OpenRA.Mods.Common.Widgets
{ {
public sealed class EditorActorBrush : IEditorBrush public sealed class EditorActorBrush : IEditorBrush
{ {
public readonly ActorInfo Actor;
readonly WorldRenderer worldRenderer;
readonly World world; readonly World world;
readonly EditorActorLayer editorLayer; readonly EditorActorLayer editorLayer;
readonly EditorCursorLayer editorCursor;
readonly EditorActionManager editorActionManager; readonly EditorActionManager editorActionManager;
readonly EditorViewportControllerWidget editorWidget; readonly EditorViewportControllerWidget editorWidget;
readonly ActorPreviewWidget preview; readonly int cursorToken;
readonly WVec centerOffset;
readonly PlayerReference owner;
readonly CVec[] footprint;
int facing = 96;
public EditorActorBrush(EditorViewportControllerWidget editorWidget, ActorInfo actor, PlayerReference owner, WorldRenderer wr) public EditorActorBrush(EditorViewportControllerWidget editorWidget, ActorInfo actor, PlayerReference owner, WorldRenderer wr)
{ {
this.editorWidget = editorWidget; this.editorWidget = editorWidget;
worldRenderer = wr;
world = wr.World; world = wr.World;
editorLayer = world.WorldActor.Trait<EditorActorLayer>(); editorLayer = world.WorldActor.Trait<EditorActorLayer>();
editorCursor = world.WorldActor.Trait<EditorCursorLayer>();
editorActionManager = world.WorldActor.Trait<EditorActionManager>(); editorActionManager = world.WorldActor.Trait<EditorActionManager>();
Actor = actor; cursorToken = editorCursor.SetActor(wr, actor, owner);
this.owner = owner;
var ownerName = owner.Name;
preview = editorWidget.Get<ActorPreviewWidget>("DRAG_ACTOR_PREVIEW");
preview.GetScale = () => worldRenderer.Viewport.Zoom;
preview.IsVisible = () => editorWidget.CurrentBrush == this;
var buildingInfo = actor.TraitInfoOrDefault<BuildingInfo>();
if (buildingInfo != null)
centerOffset = buildingInfo.CenterOffset(world);
// Enforce first entry of ValidOwnerNames as owner if the actor has RequiresSpecificOwners
var specificOwnerInfo = actor.TraitInfoOrDefault<RequiresSpecificOwnersInfo>();
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<IOccupySpaceInfo>();
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();
} }
public bool HandleMouseInput(MouseInput mi) public bool HandleMouseInput(MouseInput mi)
@@ -95,38 +52,29 @@ namespace OpenRA.Mods.Common.Widgets
return false; 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) if (mi.Button == MouseButton.Left && mi.Event == MouseInputEvent.Down)
{ {
// Check the actor is inside the map // 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; return true;
// Enforce first entry of ValidOwnerNames as owner if the actor has RequiresSpecificOwners var action = new AddActorAction(editorLayer, actor.Actor);
var action = new AddActorAction(editorLayer, Actor, cell, owner, facing);
editorActionManager.Add(action); editorActionManager.Add(action);
} }
return true; return true;
} }
public void Tick() public void Tick() { }
public void Dispose()
{ {
var cell = worldRenderer.Viewport.ViewToWorld(Viewport.LastMousePos - worldRenderer.ScreenPxOffset(centerOffset)); editorCursor.Clear(cursorToken);
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);
} }
public void Dispose() { }
} }
class AddActorAction : IEditorAction class AddActorAction : IEditorAction
@@ -134,20 +82,16 @@ namespace OpenRA.Mods.Common.Widgets
public string Text { get; private set; } public string Text { get; private set; }
readonly EditorActorLayer editorLayer; readonly EditorActorLayer editorLayer;
readonly ActorInfo actor; readonly ActorReference actor;
readonly CPos cell;
readonly PlayerReference owner;
readonly int facing;
EditorActorPreview editorActorPreview; EditorActorPreview editorActorPreview;
public AddActorAction(EditorActorLayer editorLayer, ActorInfo actor, CPos cell, PlayerReference owner, int facing) public AddActorAction(EditorActorLayer editorLayer, ActorReference actor)
{ {
this.editorLayer = editorLayer; this.editorLayer = editorLayer;
this.actor = actor;
this.cell = cell; // Take an immutable copy of the reference
this.owner = owner; this.actor = actor.Clone();
this.facing = facing;
} }
public void Execute() public void Execute()
@@ -157,34 +101,7 @@ namespace OpenRA.Mods.Common.Widgets
public void Do() public void Do()
{ {
var ownerName = owner.Name; editorActorPreview = editorLayer.Add(actor);
var specificOwnerInfo = actor.TraitInfoOrDefault<RequiresSpecificOwnersInfo>();
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<IOccupySpaceInfo>();
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<IFacingInfo>())
initDict.Add(new FacingInit(facing));
if (actor.HasTraitInfo<TurretedInfo>())
initDict.Add(new TurretFacingInit(facing));
editorActorPreview = editorLayer.Add(newActorReference);
Text = "Added {0} ({1})".F(editorActorPreview.Info.Name, editorActorPreview.ID); Text = "Added {0} ({1})".F(editorActorPreview.Info.Name, editorActorPreview.ID);
} }

View File

@@ -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<EditorActorLayerInfo>
{
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<EditorActorLayer>();
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<LocationInit>());
Actor.Actor.InitDict.Add(new LocationInit(cell));
updated = true;
}
if (actorSubCell != subCell)
{
actorSubCell = subCell;
var subcellInit = Actor.Actor.InitDict.GetOrDefault<SubCellInit>();
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<IRenderable> NoRenderables = Enumerable.Empty<IRenderable>();
IEnumerable<IRenderable> 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<IRenderable> 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<IOccupySpaceInfo>();
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<RequiresSpecificOwnersInfo>();
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<IFacingInfo>())
reference.InitDict.Add(new FacingInit(info.PreviewFacing));
if (actor.HasTraitInfo<TurretedInfo>())
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;
}
}
}

View File

@@ -40,6 +40,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
readonly DropDownButtonWidget ownersDropDown; readonly DropDownButtonWidget ownersDropDown;
readonly Ruleset mapRules; readonly Ruleset mapRules;
readonly ActorSelectorActor[] allActors; readonly ActorSelectorActor[] allActors;
readonly EditorCursorLayer editorCursor;
PlayerReference selectedOwner; PlayerReference selectedOwner;
@@ -49,6 +50,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
{ {
mapRules = world.Map.Rules; mapRules = world.Map.Rules;
ownersDropDown = widget.Get<DropDownButtonWidget>("OWNERS_DROPDOWN"); ownersDropDown = widget.Get<DropDownButtonWidget>("OWNERS_DROPDOWN");
editorCursor = world.WorldActor.Trait<EditorCursorLayer>();
var editorLayer = world.WorldActor.Trait<EditorActorLayer>(); var editorLayer = world.WorldActor.Trait<EditorActorLayer>();
selectedOwner = editorLayer.Players.Players.Values.First(); selectedOwner = editorLayer.Players.Players.Values.First();
@@ -181,7 +183,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
try try
{ {
var item = ScrollItemWidget.Setup(ItemTemplate, 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))); () => Editor.SetBrush(new EditorActorBrush(Editor, actor, selectedOwner, WorldRenderer)));
var preview = item.Get<ActorPreviewWidget>("ACTOR_PREVIEW"); var preview = item.Get<ActorPreviewWidget>("ACTOR_PREVIEW");

View File

@@ -224,8 +224,6 @@ Container@EDITOR_WORLD_ROOT:
Visible: false Visible: false
Sprite@DRAG_LAYER_PREVIEW: Sprite@DRAG_LAYER_PREVIEW:
Visible: false Visible: false
ActorPreview@DRAG_ACTOR_PREVIEW:
Visible: false
Background@ACTOR_EDIT_PANEL: Background@ACTOR_EDIT_PANEL:
Background: panel-black Background: panel-black
Width: 269 Width: 269

View File

@@ -265,6 +265,7 @@ World:
EditorWorld: EditorWorld:
Inherits: ^BaseWorld Inherits: ^BaseWorld
EditorActorLayer: EditorActorLayer:
EditorCursorLayer:
EditorResourceLayer: EditorResourceLayer:
EditorSelectionLayer: EditorSelectionLayer:
LoadWidgetAtGameStart: LoadWidgetAtGameStart:

View File

@@ -211,8 +211,6 @@ Container@EDITOR_WORLD_ROOT:
TooltipContainer: TOOLTIP_CONTAINER TooltipContainer: TOOLTIP_CONTAINER
TooltipTemplate: SIMPLE_TOOLTIP TooltipTemplate: SIMPLE_TOOLTIP
Children: Children:
ActorPreview@DRAG_ACTOR_PREVIEW:
Visible: false
TerrainTemplatePreview@DRAG_TILE_PREVIEW: TerrainTemplatePreview@DRAG_TILE_PREVIEW:
Visible: false Visible: false
Sprite@DRAG_LAYER_PREVIEW: Sprite@DRAG_LAYER_PREVIEW:

View File

@@ -240,6 +240,7 @@ World:
EditorWorld: EditorWorld:
Inherits: ^BaseWorld Inherits: ^BaseWorld
EditorActorLayer: EditorActorLayer:
EditorCursorLayer:
D2kEditorResourceLayer: D2kEditorResourceLayer:
EditorSelectionLayer: EditorSelectionLayer:
LoadWidgetAtGameStart: LoadWidgetAtGameStart:

View File

@@ -281,6 +281,7 @@ World:
EditorWorld: EditorWorld:
Inherits: ^BaseWorld Inherits: ^BaseWorld
EditorActorLayer: EditorActorLayer:
EditorCursorLayer:
EditorResourceLayer: EditorResourceLayer:
EditorSelectionLayer: EditorSelectionLayer:
LoadWidgetAtGameStart: LoadWidgetAtGameStart:

View File

@@ -375,6 +375,7 @@ World:
EditorWorld: EditorWorld:
Inherits: ^BaseWorld Inherits: ^BaseWorld
EditorActorLayer: EditorActorLayer:
EditorCursorLayer:
EditorResourceLayer: EditorResourceLayer:
EditorSelectionLayer: EditorSelectionLayer:
Palette: placefootprint Palette: placefootprint