Add copy/paste to the map editor.
This commit is contained in:
174
OpenRA.Mods.Common/EditorBrushes/EditorCopyPasteBrush.cs
Normal file
174
OpenRA.Mods.Common/EditorBrushes/EditorCopyPasteBrush.cs
Normal file
@@ -0,0 +1,174 @@
|
||||
#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 sealed class EditorCopyPasteBrush : IEditorBrush
|
||||
{
|
||||
enum State { SelectFirst, SelectSecond, Paste }
|
||||
|
||||
readonly WorldRenderer worldRenderer;
|
||||
readonly EditorViewportControllerWidget editorWidget;
|
||||
readonly EditorSelectionLayer selectionLayer;
|
||||
readonly EditorActorLayer editorLayer;
|
||||
|
||||
State state;
|
||||
CPos start;
|
||||
CPos end;
|
||||
|
||||
public EditorCopyPasteBrush(EditorViewportControllerWidget editorWidget, WorldRenderer wr)
|
||||
{
|
||||
this.editorWidget = editorWidget;
|
||||
worldRenderer = wr;
|
||||
|
||||
selectionLayer = wr.World.WorldActor.Trait<EditorSelectionLayer>();
|
||||
editorLayer = wr.World.WorldActor.Trait<EditorActorLayer>();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (mi.Event == MouseInputEvent.Up)
|
||||
{
|
||||
editorWidget.ClearBrush();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mi.Button == MouseButton.Left && (mi.Event == MouseInputEvent.Up || mi.Event == MouseInputEvent.Down))
|
||||
{
|
||||
var cell = worldRenderer.Viewport.ViewToWorld(mi.Location);
|
||||
switch (state)
|
||||
{
|
||||
case State.SelectFirst:
|
||||
if (mi.Event != MouseInputEvent.Down)
|
||||
break;
|
||||
start = cell;
|
||||
selectionLayer.SetCopyRegion(start, end);
|
||||
state = State.SelectSecond;
|
||||
break;
|
||||
case State.SelectSecond:
|
||||
if (mi.Event != MouseInputEvent.Up)
|
||||
break;
|
||||
end = cell;
|
||||
selectionLayer.SetCopyRegion(start, end);
|
||||
state = State.Paste;
|
||||
break;
|
||||
case State.Paste:
|
||||
{
|
||||
var gridType = worldRenderer.World.Map.Grid.Type;
|
||||
var source = CellRegion.BoundingRegion(gridType, new[] { start, end });
|
||||
Copy(source, cell - end);
|
||||
editorWidget.ClearBrush();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Copy(CellRegion source, CVec offset)
|
||||
{
|
||||
var gridType = worldRenderer.World.Map.Grid.Type;
|
||||
var mapTiles = worldRenderer.World.Map.MapTiles.Value;
|
||||
var mapHeight = worldRenderer.World.Map.MapHeight.Value;
|
||||
var mapResources = worldRenderer.World.Map.MapResources.Value;
|
||||
|
||||
var dest = new CellRegion(gridType, source.TopLeft + offset, source.BottomRight + offset);
|
||||
|
||||
var previews = new Dictionary<string, ActorReference>();
|
||||
var tiles = new Dictionary<CPos, Tuple<TerrainTile, ResourceTile, byte>>();
|
||||
|
||||
foreach (var cell in source)
|
||||
{
|
||||
if (!mapTiles.Contains(cell) || !mapTiles.Contains(cell + offset))
|
||||
continue;
|
||||
|
||||
tiles.Add(cell + offset, Tuple.Create(mapTiles[cell], mapResources[cell], mapHeight[cell]));
|
||||
|
||||
foreach (var preview in editorLayer.PreviewsAt(cell))
|
||||
{
|
||||
if (previews.ContainsKey(preview.ID))
|
||||
continue;
|
||||
|
||||
var copy = preview.Export();
|
||||
if (copy.InitDict.Contains<LocationInit>())
|
||||
{
|
||||
var location = copy.InitDict.Get<LocationInit>();
|
||||
copy.InitDict.Remove(location);
|
||||
copy.InitDict.Add(new LocationInit(location.Value(worldRenderer.World) + offset));
|
||||
}
|
||||
|
||||
previews.Add(preview.ID, copy);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var kv in tiles)
|
||||
{
|
||||
mapTiles[kv.Key] = kv.Value.Item1;
|
||||
mapResources[kv.Key] = kv.Value.Item2;
|
||||
mapHeight[kv.Key] = kv.Value.Item3;
|
||||
}
|
||||
|
||||
var removeActors = dest.SelectMany(editorLayer.PreviewsAt).Distinct().ToList();
|
||||
foreach (var preview in removeActors)
|
||||
editorLayer.Remove(preview);
|
||||
|
||||
foreach (var kv in previews)
|
||||
editorLayer.Add(kv.Value);
|
||||
}
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
var cell = worldRenderer.Viewport.ViewToWorld(Viewport.LastMousePos);
|
||||
if (state == State.Paste)
|
||||
{
|
||||
selectionLayer.SetPasteRegion(cell + (start - end), cell);
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == State.SelectFirst)
|
||||
start = end = cell;
|
||||
else if (state == State.SelectSecond)
|
||||
end = cell;
|
||||
|
||||
selectionLayer.SetCopyRegion(start, end);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
selectionLayer.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -708,6 +708,8 @@
|
||||
<Compile Include="Lint\CheckChromeLogic.cs" />
|
||||
<Compile Include="Lint\CheckMapMetadata.cs" />
|
||||
<Compile Include="Widgets\Logic\MultiplayerLogic.cs" />
|
||||
<Compile Include="EditorBrushes\EditorCopyPasteBrush.cs" />
|
||||
<Compile Include="Traits\World\EditorSelectionLayer.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -161,5 +161,10 @@ namespace OpenRA.Mods.Common.Traits
|
||||
.SelectMany(rpi => rpi.RenderPreview(init))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public ActorReference Export()
|
||||
{
|
||||
return new ActorReference(actor.Type, actor.Save().ToDictionary());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
105
OpenRA.Mods.Common/Traits/World/EditorSelectionLayer.cs
Normal file
105
OpenRA.Mods.Common/Traits/World/EditorSelectionLayer.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
#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 EditorSelectionLayerInfo : ITraitInfo
|
||||
{
|
||||
[PaletteReference]
|
||||
[Desc("Palette to use for rendering the placement sprite.")]
|
||||
public readonly string Palette = "terrain";
|
||||
|
||||
[Desc("Sequence image where the selection overlay types are defined.")]
|
||||
public readonly string Image = "editor-overlay";
|
||||
|
||||
[SequenceReference("Image")]
|
||||
[Desc("Sequence to use for the copy overlay.")]
|
||||
public readonly string CopySequence = "copy";
|
||||
|
||||
[SequenceReference("Image")]
|
||||
[Desc("Sequence to use for the paste overlay.")]
|
||||
public readonly string PasteSequence = "paste";
|
||||
|
||||
public virtual object Create(ActorInitializer init) { return new EditorSelectionLayer(init.Self, this); }
|
||||
}
|
||||
|
||||
public class EditorSelectionLayer : IWorldLoaded, IPostRender
|
||||
{
|
||||
readonly EditorSelectionLayerInfo info;
|
||||
readonly Map map;
|
||||
readonly Sprite copySprite;
|
||||
readonly Sprite pasteSprite;
|
||||
PaletteReference palette;
|
||||
|
||||
public CellRegion CopyRegion { get; private set; }
|
||||
public CellRegion PasteRegion { get; private set; }
|
||||
|
||||
public EditorSelectionLayer(Actor self, EditorSelectionLayerInfo info)
|
||||
{
|
||||
if (self.World.Type != WorldType.Editor)
|
||||
return;
|
||||
|
||||
this.info = info;
|
||||
map = self.World.Map;
|
||||
copySprite = map.SequenceProvider.GetSequence(info.Image, info.CopySequence).GetSprite(0);
|
||||
pasteSprite = map.SequenceProvider.GetSequence(info.Image, info.PasteSequence).GetSprite(0);
|
||||
}
|
||||
|
||||
public void WorldLoaded(World w, WorldRenderer wr)
|
||||
{
|
||||
if (w.Type != WorldType.Editor)
|
||||
return;
|
||||
|
||||
palette = wr.Palette(info.Palette);
|
||||
}
|
||||
|
||||
public void SetCopyRegion(CPos start, CPos end)
|
||||
{
|
||||
CopyRegion = CellRegion.BoundingRegion(map.Grid.Type, new[] { start, end });
|
||||
}
|
||||
|
||||
public void SetPasteRegion(CPos start, CPos end)
|
||||
{
|
||||
PasteRegion = CellRegion.BoundingRegion(map.Grid.Type, new[] { start, end });
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
CopyRegion = PasteRegion = null;
|
||||
}
|
||||
|
||||
public void RenderAfterWorld(WorldRenderer wr, Actor self)
|
||||
{
|
||||
if (wr.World.Type != WorldType.Editor)
|
||||
return;
|
||||
|
||||
if (CopyRegion != null)
|
||||
foreach (var c in CopyRegion)
|
||||
new SpriteRenderable(copySprite, wr.World.Map.CenterOfCell(c),
|
||||
WVec.Zero, -511, palette, 1f, true).PrepareRender(wr).Render(wr);
|
||||
|
||||
if (PasteRegion != null)
|
||||
foreach (var c in PasteRegion)
|
||||
new SpriteRenderable(pasteSprite, wr.World.Map.CenterOfCell(c),
|
||||
WVec.Zero, -511, palette, 1f, true).PrepareRender(wr).Render(wr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
[ObjectCreator.UseCtor]
|
||||
public MapEditorLogic(Widget widget, World world, WorldRenderer worldRenderer)
|
||||
{
|
||||
var editorViewport = widget.Get<EditorViewportControllerWidget>("MAP_EDITOR");
|
||||
|
||||
var gridButton = widget.GetOrNull<ButtonWidget>("GRID_BUTTON");
|
||||
var terrainGeometryTrait = world.WorldActor.Trait<TerrainGeometryOverlay>();
|
||||
|
||||
@@ -63,6 +65,13 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
};
|
||||
}
|
||||
|
||||
var copypasteButton = widget.GetOrNull<ButtonWidget>("COPYPASTE_BUTTON");
|
||||
if (copypasteButton != null)
|
||||
{
|
||||
copypasteButton.OnClick = () => editorViewport.SetBrush(new EditorCopyPasteBrush(editorViewport, worldRenderer));
|
||||
copypasteButton.IsHighlighted = () => editorViewport.CurrentBrush is EditorCopyPasteBrush;
|
||||
}
|
||||
|
||||
var coordinateLabel = widget.GetOrNull<LabelWidget>("COORDINATE_LABEL");
|
||||
if (coordinateLabel != null)
|
||||
coordinateLabel.GetText = () => worldRenderer.Viewport.ViewToWorld(Viewport.LastMousePos).ToString();
|
||||
|
||||
@@ -362,7 +362,7 @@ Container@EDITOR_WORLD_ROOT:
|
||||
Text: Actors
|
||||
Font: Bold
|
||||
Button@GRID_BUTTON:
|
||||
X: WINDOW_RIGHT - 420
|
||||
X: WINDOW_RIGHT - 500
|
||||
Y: 5
|
||||
Width: 100
|
||||
Height: 25
|
||||
@@ -373,7 +373,7 @@ Container@EDITOR_WORLD_ROOT:
|
||||
TooltipText: Toggle the terrain grid
|
||||
TooltipContainer: TOOLTIP_CONTAINER
|
||||
Label@ZOOM_LABEL:
|
||||
X: WINDOW_RIGHT - 500 - 55
|
||||
X: WINDOW_RIGHT - 580 - 55
|
||||
Y: 5
|
||||
Width: 50
|
||||
Height: 25
|
||||
@@ -382,11 +382,17 @@ Container@EDITOR_WORLD_ROOT:
|
||||
Font: Bold
|
||||
Contrast: true
|
||||
DropDownButton@ZOOM_BUTTON:
|
||||
X: WINDOW_RIGHT - 500
|
||||
X: WINDOW_RIGHT - 580
|
||||
Y: 5
|
||||
Width: 70
|
||||
Height: 25
|
||||
Font: Bold
|
||||
Button@COPYPASTE_BUTTON:
|
||||
X: WINDOW_RIGHT-390
|
||||
Y: 5
|
||||
Width: 96
|
||||
Height: 25
|
||||
Text: Copy/Paste
|
||||
Label@COORDINATE_LABEL:
|
||||
X: 10
|
||||
Width: 50
|
||||
|
||||
@@ -154,4 +154,5 @@ EditorWorld:
|
||||
Inherits: ^BaseWorld
|
||||
EditorActorLayer:
|
||||
EditorResourceLayer:
|
||||
EditorSelectionLayer:
|
||||
|
||||
|
||||
@@ -240,6 +240,12 @@ overlay:
|
||||
target-invalid:
|
||||
Start: 1
|
||||
|
||||
editor-overlay:
|
||||
copy: overlay
|
||||
Start: 0
|
||||
paste: overlay
|
||||
Start: 1
|
||||
|
||||
poweroff:
|
||||
offline:
|
||||
Length: *
|
||||
|
||||
@@ -148,4 +148,5 @@ EditorWorld:
|
||||
Inherits: ^BaseWorld
|
||||
EditorActorLayer:
|
||||
D2kEditorResourceLayer:
|
||||
EditorSelectionLayer:
|
||||
|
||||
|
||||
@@ -126,19 +126,23 @@ rank:
|
||||
Length: *
|
||||
|
||||
overlay:
|
||||
build-valid-arrakis: DATA.R8
|
||||
Defaults: DATA.R8
|
||||
Offset: -16,-16
|
||||
build-invalid: DATA.R8
|
||||
build-valid-arrakis:
|
||||
build-invalid:
|
||||
Start: 1
|
||||
Offset: -16,-16
|
||||
target-select: DATA.R8
|
||||
target-select:
|
||||
Start: 2
|
||||
Offset: -16,-16
|
||||
target-valid-arrakis: DATA.R8
|
||||
Offset: -16,-16
|
||||
target-invalid: DATA.R8
|
||||
target-valid-arrakis:
|
||||
target-invalid:
|
||||
Start: 1
|
||||
|
||||
editor-overlay:
|
||||
Defaults: DATA.R8
|
||||
Offset: -16,-16
|
||||
copy:
|
||||
paste:
|
||||
Start: 1
|
||||
|
||||
rallypoint:
|
||||
flag: flagfly.shp
|
||||
|
||||
@@ -351,9 +351,15 @@ Container@EDITOR_WORLD_ROOT:
|
||||
TooltipContainer: TOOLTIP_CONTAINER
|
||||
Font: Bold
|
||||
Key: escape
|
||||
Button@COPYPASTE_BUTTON:
|
||||
X: 170
|
||||
Width: 90
|
||||
Height: 25
|
||||
Text: Copy/Paste
|
||||
Font: Bold
|
||||
Button@GRID_BUTTON:
|
||||
X: 180
|
||||
Width: 160
|
||||
X: 270
|
||||
Width: 70
|
||||
Height: 25
|
||||
Text: Grid
|
||||
TooltipTemplate: BUTTON_TOOLTIP
|
||||
|
||||
@@ -173,4 +173,5 @@ EditorWorld:
|
||||
Inherits: ^BaseWorld
|
||||
EditorActorLayer:
|
||||
EditorResourceLayer:
|
||||
EditorSelectionLayer:
|
||||
|
||||
|
||||
@@ -474,6 +474,12 @@ overlay:
|
||||
target-invalid:
|
||||
Start: 1
|
||||
|
||||
editor-overlay:
|
||||
copy: overlay
|
||||
Start: 0
|
||||
paste: overlay
|
||||
Start: 1
|
||||
|
||||
resources:
|
||||
Defaults:
|
||||
Length: *
|
||||
|
||||
@@ -175,4 +175,6 @@ EditorWorld:
|
||||
Inherits: ^BaseWorld
|
||||
EditorActorLayer:
|
||||
EditorResourceLayer:
|
||||
EditorSelectionLayer:
|
||||
Palette: placebuilding
|
||||
|
||||
|
||||
@@ -7,6 +7,13 @@ overlay:
|
||||
Start: 1
|
||||
target-select:
|
||||
|
||||
editor-overlay:
|
||||
Defaults: place
|
||||
Offset: 0, -12
|
||||
copy:
|
||||
paste:
|
||||
Start: 1
|
||||
|
||||
poweroff:
|
||||
offline:
|
||||
Length: *
|
||||
|
||||
Reference in New Issue
Block a user