Initial commit: OpenRA game engine
Fork from OpenRA/OpenRA with one-click launch script (start-ra.cmd) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
180
OpenRA.Mods.Common/EditorBrushes/EditorActorBrush.cs
Normal file
180
OpenRA.Mods.Common/EditorBrushes/EditorActorBrush.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
#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 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 EditorActionManager editorActionManager;
|
||||
readonly EditorViewportControllerWidget editorWidget;
|
||||
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<EditorActorLayer>();
|
||||
editorActionManager = world.WorldActor.Trait<EditorActionManager>();
|
||||
|
||||
var ios = actor.TraitInfoOrDefault<IOccupySpaceInfo>();
|
||||
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<RequiresSpecificOwnersInfo>();
|
||||
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<IFacingInfo>())
|
||||
reference.Add(new FacingInit(editorLayer.Info.DefaultActorFacing));
|
||||
|
||||
Preview = new EditorActorPreview(wr, null, reference, owner);
|
||||
}
|
||||
|
||||
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.Down)
|
||||
{
|
||||
// Check the actor is inside the map
|
||||
if (!Preview.Footprint.All(c => world.Map.Tiles.Contains(c.Key)))
|
||||
return true;
|
||||
|
||||
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<SubCellInit>();
|
||||
else
|
||||
Preview.ReplaceInit(new SubCellInit(subcell));
|
||||
}
|
||||
|
||||
Preview.UpdateFromMove();
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<IRenderable> IEditorBrush.RenderAboveShroud(Actor self, WorldRenderer wr)
|
||||
{
|
||||
return Preview.Render().OrderBy(WorldRenderer.RenderableZPositionComparisonKey);
|
||||
}
|
||||
|
||||
IEnumerable<IRenderable> IEditorBrush.RenderAnnotations(Actor self, WorldRenderer wr)
|
||||
{
|
||||
return Preview.RenderAnnotations();
|
||||
}
|
||||
|
||||
public void Tick() { }
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
|
||||
sealed class AddActorAction : IEditorAction
|
||||
{
|
||||
public string Text { get; private set; }
|
||||
|
||||
[FluentReference("name", "id")]
|
||||
const string AddedActor = "notification-added-actor";
|
||||
|
||||
readonly EditorActorLayer editorLayer;
|
||||
readonly ActorReference actor;
|
||||
|
||||
EditorActorPreview editorActorPreview;
|
||||
|
||||
public AddActorAction(EditorActorLayer editorLayer, ActorReference actor)
|
||||
{
|
||||
this.editorLayer = editorLayer;
|
||||
|
||||
// Take an immutable copy of the reference.
|
||||
this.actor = actor.Clone();
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
Do();
|
||||
}
|
||||
|
||||
public void Do()
|
||||
{
|
||||
editorActorPreview = editorLayer.Add(actor);
|
||||
Text = FluentProvider.GetMessage(AddedActor,
|
||||
"name", editorActorPreview.Info.Name,
|
||||
"id", editorActorPreview.ID);
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
editorLayer.Remove(editorActorPreview);
|
||||
}
|
||||
}
|
||||
}
|
||||
363
OpenRA.Mods.Common/EditorBrushes/EditorBlit.cs
Normal file
363
OpenRA.Mods.Common/EditorBrushes/EditorBlit.cs
Normal file
@@ -0,0 +1,363 @@
|
||||
#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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Common.MapGenerator;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Support;
|
||||
|
||||
namespace OpenRA.Mods.Common.EditorBrushes
|
||||
{
|
||||
public readonly record struct BlitTile(TerrainTile TerrainTile, ResourceTile ResourceTile, ResourceLayerContents? ResourceLayerContents, byte Height);
|
||||
|
||||
public readonly record struct EditorBlitSource(CellCoordsRegion CellCoords, Dictionary<string, EditorActorPreview> Actors, Dictionary<CPos, BlitTile> Tiles);
|
||||
|
||||
[Flags]
|
||||
public enum MapBlitFilters
|
||||
{
|
||||
None = 0,
|
||||
Terrain = 1,
|
||||
Resources = 2,
|
||||
Actors = 4,
|
||||
All = Terrain | Resources | Actors
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Core implementation for EditorActions which overwrite a region of the map (such as
|
||||
/// copy-paste).
|
||||
/// </summary>
|
||||
public sealed class EditorBlit
|
||||
{
|
||||
readonly MapBlitFilters blitFilters;
|
||||
readonly IResourceLayer resourceLayer;
|
||||
readonly EditorActorLayer editorActorLayer;
|
||||
readonly EditorBlitSource commitBlitSource;
|
||||
readonly EditorBlitSource revertBlitSource;
|
||||
readonly CPos blitPosition;
|
||||
readonly Map map;
|
||||
readonly bool respectBounds;
|
||||
|
||||
public EditorBlit(
|
||||
MapBlitFilters blitFilters,
|
||||
IResourceLayer resourceLayer,
|
||||
CPos blitPosition,
|
||||
Map map,
|
||||
EditorBlitSource blitSource,
|
||||
EditorActorLayer editorActorLayer,
|
||||
bool respectBounds)
|
||||
{
|
||||
this.blitFilters = blitFilters;
|
||||
this.resourceLayer = resourceLayer;
|
||||
this.blitPosition = blitPosition;
|
||||
this.editorActorLayer = editorActorLayer;
|
||||
this.map = map;
|
||||
this.respectBounds = respectBounds;
|
||||
|
||||
var blitSize = blitSource.CellCoords.BottomRight - blitSource.CellCoords.TopLeft;
|
||||
|
||||
// Only include into the revert blit stuff which would be modified by the main blit.
|
||||
var mask = GetBlitSourceMask(
|
||||
blitSource, blitPosition - blitSource.CellCoords.TopLeft);
|
||||
|
||||
commitBlitSource = blitSource;
|
||||
revertBlitSource = CopyRegionContents(
|
||||
map,
|
||||
editorActorLayer,
|
||||
resourceLayer,
|
||||
new CellCoordsRegion(blitPosition, blitPosition + blitSize),
|
||||
blitFilters,
|
||||
mask);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an EditorBlitSource containing the map contents for a given region.
|
||||
/// If a mask is supplied, only tiles and actors (fully or partially) overlapping the mask
|
||||
/// are included in the EditorBlitSource.
|
||||
/// </summary>
|
||||
public static EditorBlitSource CopyRegionContents(
|
||||
Map map,
|
||||
EditorActorLayer editorActorLayer,
|
||||
IResourceLayer resourceLayer,
|
||||
CellCoordsRegion region,
|
||||
MapBlitFilters blitFilters,
|
||||
IReadOnlySet<CPos> mask = null)
|
||||
{
|
||||
var mapTiles = map.Tiles;
|
||||
var mapHeight = map.Height;
|
||||
var mapResources = map.Resources;
|
||||
|
||||
var previews = new Dictionary<string, EditorActorPreview>();
|
||||
var tiles = new Dictionary<CPos, BlitTile>();
|
||||
|
||||
if (blitFilters.HasFlag(MapBlitFilters.Terrain) || blitFilters.HasFlag(MapBlitFilters.Resources))
|
||||
{
|
||||
foreach (var cell in region)
|
||||
{
|
||||
if (!mapTiles.Contains(cell) || (mask != null && !mask.Contains(cell)))
|
||||
continue;
|
||||
|
||||
tiles.Add(
|
||||
cell,
|
||||
new BlitTile(mapTiles[cell],
|
||||
mapResources[cell],
|
||||
resourceLayer?.GetResource(cell),
|
||||
mapHeight[cell]));
|
||||
}
|
||||
}
|
||||
|
||||
if (blitFilters.HasFlag(MapBlitFilters.Actors))
|
||||
foreach (var preview in editorActorLayer.PreviewsInCellRegion(region))
|
||||
if (mask == null || preview.Footprint.Keys.Any(mask.Contains))
|
||||
previews.TryAdd(preview.ID, preview);
|
||||
|
||||
return new EditorBlitSource(region, previews, tiles);
|
||||
}
|
||||
|
||||
void Blit(bool isRevert)
|
||||
{
|
||||
var source = isRevert ? revertBlitSource : commitBlitSource;
|
||||
var blitPos = isRevert ? source.CellCoords.TopLeft : blitPosition;
|
||||
var blitVec = blitPos - source.CellCoords.TopLeft;
|
||||
var blitSize = source.CellCoords.BottomRight - source.CellCoords.TopLeft;
|
||||
var blitRegion = new CellCoordsRegion(blitPos, blitPos + blitSize);
|
||||
|
||||
if (blitFilters.HasFlag(MapBlitFilters.Actors))
|
||||
{
|
||||
// Clear any existing actors in the paste cells.
|
||||
//
|
||||
// revertBlitSource's mask may be a superset of the commitBlitSource's mask if
|
||||
// - Its a sparse blit; and
|
||||
// - The revert actors removed by the commit are partially outside of the commit mask.
|
||||
// Otherwise, it's a (practically) equal set. (Subject to map bounds.)
|
||||
//
|
||||
// This implies that:
|
||||
// - commitBlitSource's mask will overlap all commit actors.
|
||||
// - revertBlitSource's mask will overlap all revert actors.
|
||||
// - commitBlitSource's mask will overlap all and no more than the revert actors.
|
||||
// - revertBlitSource's mask will overlap all revert actors BUT MAY OVERLAP MORE!
|
||||
//
|
||||
// This means we use the commit mask, not the revert one.
|
||||
var commitBlitVec = blitPosition - commitBlitSource.CellCoords.TopLeft;
|
||||
var mask = GetBlitSourceMask(commitBlitSource, commitBlitVec);
|
||||
using (new PerfTimer("RemoveActors", 1))
|
||||
editorActorLayer.RemoveRegion(blitRegion, mask);
|
||||
}
|
||||
|
||||
foreach (var tileKeyValuePair in source.Tiles)
|
||||
{
|
||||
var position = tileKeyValuePair.Key + blitVec;
|
||||
if (!map.Tiles.Contains(position) || (respectBounds && !map.Contains(position)))
|
||||
continue;
|
||||
|
||||
// Clear any existing resources.
|
||||
if (resourceLayer != null && blitFilters.HasFlag(MapBlitFilters.Resources))
|
||||
resourceLayer.ClearResources(position);
|
||||
|
||||
var tile = tileKeyValuePair.Value;
|
||||
var resourceLayerContents = tile.ResourceLayerContents;
|
||||
|
||||
if (blitFilters.HasFlag(MapBlitFilters.Terrain))
|
||||
{
|
||||
map.Tiles[position] = tile.TerrainTile;
|
||||
map.Height[position] = tile.Height;
|
||||
}
|
||||
|
||||
if (blitFilters.HasFlag(MapBlitFilters.Resources) &&
|
||||
resourceLayerContents.HasValue &&
|
||||
!string.IsNullOrWhiteSpace(resourceLayerContents.Value.Type) &&
|
||||
resourceLayer.CanAddResource(resourceLayerContents.Value.Type, position))
|
||||
{
|
||||
resourceLayer.AddResource(resourceLayerContents.Value.Type, position, resourceLayerContents.Value.Density);
|
||||
}
|
||||
}
|
||||
|
||||
if (blitFilters.HasFlag(MapBlitFilters.Actors))
|
||||
{
|
||||
if (isRevert)
|
||||
{
|
||||
// For reverts, just place the original actors back exactly how they were.
|
||||
using (new PerfTimer("AddActors", 1))
|
||||
editorActorLayer.AddRange(source.Actors.Values.ToArray().AsSpan());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create copies of the original actors, update their locations, and place.
|
||||
var copies = new List<ActorReference>(source.Actors.Count);
|
||||
foreach (var actorKeyValuePair in source.Actors)
|
||||
{
|
||||
var copy = actorKeyValuePair.Value.Export();
|
||||
var locationInit = copy.GetOrDefault<LocationInit>();
|
||||
if (locationInit != null)
|
||||
{
|
||||
var actorPosition = locationInit.Value + blitVec;
|
||||
if (respectBounds && !map.Contains(actorPosition))
|
||||
continue;
|
||||
|
||||
copy.RemoveAll<LocationInit>();
|
||||
copy.Add(new LocationInit(actorPosition));
|
||||
}
|
||||
|
||||
copies.Add(copy);
|
||||
}
|
||||
|
||||
using (new PerfTimer("AddActors", 1))
|
||||
editorActorLayer.AddRange(CollectionsMarshal.AsSpan(copies));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<IRenderable> PreviewBlitSource(
|
||||
EditorBlitSource blitSource,
|
||||
MapBlitFilters filters,
|
||||
CVec offset,
|
||||
WorldRenderer wr,
|
||||
bool stickToGround)
|
||||
{
|
||||
var world = wr.World;
|
||||
var map = world.Map;
|
||||
var mapHeight = map.Height;
|
||||
var mapGrid = map.Grid;
|
||||
|
||||
if (filters.HasFlag(MapBlitFilters.Terrain))
|
||||
{
|
||||
var terrainRenderer = world.WorldActor.Trait<ITiledTerrainRenderer>();
|
||||
foreach (var (pos, tile) in blitSource.Tiles)
|
||||
{
|
||||
var cPos = pos + offset;
|
||||
var height = stickToGround ? (mapHeight.TryGetValue(cPos, out var isoHeight) ? isoHeight : byte.MinValue) : tile.Height;
|
||||
var wPos = CellLayerUtils.CPosToWPos(cPos, height, mapGrid.Type);
|
||||
var preview = terrainRenderer.RenderPreview(wr, tile.TerrainTile, wPos);
|
||||
|
||||
foreach (var renderable in preview)
|
||||
yield return renderable;
|
||||
}
|
||||
}
|
||||
|
||||
if (filters.HasFlag(MapBlitFilters.Resources))
|
||||
{
|
||||
var resourceRenderers = world.WorldActor.TraitsImplementing<IResourceRenderer>().ToArray();
|
||||
var resourceLayer = world.WorldActor.Trait<IResourceLayer>();
|
||||
foreach (var (pos, tile) in blitSource.Tiles)
|
||||
{
|
||||
if (tile.ResourceLayerContents == null || tile.ResourceLayerContents.Value.Type == null)
|
||||
continue;
|
||||
|
||||
var cPos = pos + offset;
|
||||
if (!filters.HasFlag(MapBlitFilters.Terrain) && !resourceLayer.CanAddResource(tile.ResourceLayerContents.Value.Type, cPos))
|
||||
continue;
|
||||
|
||||
byte height;
|
||||
if (filters.HasFlag(MapBlitFilters.Terrain) && !stickToGround)
|
||||
{
|
||||
// We won't change relative tile height, use the saved value.
|
||||
height = tile.Height;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!mapHeight.TryGetValue(cPos, out height))
|
||||
height = byte.MinValue;
|
||||
|
||||
// If a tile has inherent height, we know it will raise terrain.
|
||||
if (filters.HasFlag(MapBlitFilters.Terrain) && stickToGround)
|
||||
height += map.Rules.TerrainInfo.GetTerrainInfo(tile.TerrainTile).Height;
|
||||
}
|
||||
|
||||
var wPos = CellLayerUtils.CPosToWPos(cPos, height, mapGrid.Type);
|
||||
var preview = resourceRenderers
|
||||
.SelectMany(r => r.RenderPreview(wr, tile.ResourceLayerContents.Value.Type, wPos));
|
||||
|
||||
foreach (var renderable in preview)
|
||||
yield return renderable;
|
||||
}
|
||||
}
|
||||
|
||||
if (filters.HasFlag(MapBlitFilters.Actors))
|
||||
{
|
||||
foreach (var (_, editorActorPreview) in blitSource.Actors)
|
||||
{
|
||||
var useGround = stickToGround;
|
||||
if (!filters.HasFlag(MapBlitFilters.Terrain) || !blitSource.Tiles.ContainsKey(editorActorPreview.Location))
|
||||
useGround = true;
|
||||
|
||||
var wOffset = CellLayerUtils.CVecToWVec(offset, 0, mapGrid.Type);
|
||||
if (useGround)
|
||||
{
|
||||
var actorPos = editorActorPreview.CenterPosition + wOffset;
|
||||
wOffset -= new WVec(0, 0, map.DistanceAboveTerrain(actorPos).Length);
|
||||
}
|
||||
|
||||
var preview = editorActorPreview.RenderWithOffset(wOffset)
|
||||
.OrderBy(WorldRenderer.RenderableZPositionComparisonKey);
|
||||
|
||||
foreach (var renderable in preview)
|
||||
yield return renderable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the set of cells within an EditorBlitSource that are actually occupied by a
|
||||
/// BlitTile or actor. Note that all tiles must be inside the CellRegion, and actors must
|
||||
/// be at least partially inside the CellRegion. If an actor partially lies outside of the
|
||||
/// CellRegion, only cells within the CellRegion are included in the output set.
|
||||
/// </summary>
|
||||
static HashSet<CPos> GetBlitSourceMask(
|
||||
EditorBlitSource blitSource,
|
||||
CVec offset)
|
||||
{
|
||||
var mask = new HashSet<CPos>();
|
||||
|
||||
var sourceCellCoords = blitSource.CellCoords;
|
||||
|
||||
foreach (var (cpos, _) in blitSource.Tiles)
|
||||
{
|
||||
if (!sourceCellCoords.Contains(cpos))
|
||||
throw new ArgumentException("EditorBlitSource contains a BlitTile outside of its CellRegion");
|
||||
mask.Add(cpos + offset);
|
||||
}
|
||||
|
||||
foreach (var (_, editorActorPreview) in blitSource.Actors)
|
||||
{
|
||||
var anyContained = false;
|
||||
foreach (var cpos in editorActorPreview.Footprint.Keys)
|
||||
{
|
||||
if (sourceCellCoords.Contains(cpos))
|
||||
{
|
||||
mask.Add(cpos + offset);
|
||||
anyContained = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!anyContained)
|
||||
throw new ArgumentException("EditorBlitSource contains an actor entirely outside of its CellRegion");
|
||||
}
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
public void Commit() => Blit(false);
|
||||
public void Revert() => Blit(true);
|
||||
|
||||
public int TileCount()
|
||||
{
|
||||
return commitBlitSource.Tiles.Count;
|
||||
}
|
||||
|
||||
public int ActorCount()
|
||||
{
|
||||
return commitBlitSource.Actors.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
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 (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;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Common.EditorBrushes;
|
||||
using OpenRA.Mods.Common.Graphics;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Widgets
|
||||
{
|
||||
public sealed class EditorCopyPasteBrush : IEditorBrush
|
||||
{
|
||||
readonly WorldRenderer worldRenderer;
|
||||
readonly EditorViewportControllerWidget editorWidget;
|
||||
readonly EditorActorLayer editorActorLayer;
|
||||
readonly EditorActionManager editorActionManager;
|
||||
readonly EditorBlitSource clipboard;
|
||||
readonly IResourceLayer resourceLayer;
|
||||
readonly Func<MapBlitFilters> getCopyFilters;
|
||||
|
||||
public CPos PastePreviewPosition { get; private set; }
|
||||
|
||||
public CellCoordsRegion Region => clipboard.CellCoords;
|
||||
|
||||
public EditorCopyPasteBrush(
|
||||
EditorViewportControllerWidget editorWidget,
|
||||
WorldRenderer wr,
|
||||
EditorBlitSource clipboard,
|
||||
IResourceLayer resourceLayer,
|
||||
Func<MapBlitFilters> getCopyFilters)
|
||||
{
|
||||
this.getCopyFilters = getCopyFilters;
|
||||
this.editorWidget = editorWidget;
|
||||
this.clipboard = clipboard;
|
||||
this.resourceLayer = resourceLayer;
|
||||
worldRenderer = wr;
|
||||
|
||||
editorActionManager = wr.World.WorldActor.Trait<EditorActionManager>();
|
||||
editorActorLayer = wr.World.WorldActor.Trait<EditorActorLayer>();
|
||||
PastePreviewPosition = worldRenderer.Viewport.ViewToWorld(Viewport.LastMousePos);
|
||||
}
|
||||
|
||||
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.Down)
|
||||
{
|
||||
var pastePosition = worldRenderer.Viewport.ViewToWorld(Viewport.LastMousePos);
|
||||
var editorBlit = new EditorBlit(
|
||||
getCopyFilters(),
|
||||
resourceLayer,
|
||||
pastePosition,
|
||||
worldRenderer.World.Map,
|
||||
clipboard,
|
||||
editorActorLayer,
|
||||
true);
|
||||
var action = new CopyPasteEditorAction(editorBlit);
|
||||
|
||||
editorActionManager.Add(action);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void IEditorBrush.TickRender(WorldRenderer wr, Actor self) { }
|
||||
IEnumerable<IRenderable> IEditorBrush.RenderAboveShroud(Actor self, WorldRenderer wr)
|
||||
{
|
||||
var filters = getCopyFilters();
|
||||
var stickToGround = !filters.HasFlag(MapBlitFilters.Terrain)
|
||||
;
|
||||
var preview = EditorBlit.PreviewBlitSource(
|
||||
clipboard,
|
||||
filters,
|
||||
PastePreviewPosition - Region.TopLeft,
|
||||
wr,
|
||||
stickToGround);
|
||||
foreach (var renderable in preview)
|
||||
yield return renderable;
|
||||
}
|
||||
|
||||
IEnumerable<IRenderable> IEditorBrush.RenderAnnotations(Actor self, WorldRenderer wr)
|
||||
{
|
||||
yield return new EditorSelectionAnnotationRenderable(
|
||||
Region,
|
||||
editorWidget.SelectionAltColor,
|
||||
editorWidget.SelectionAltOffset,
|
||||
PastePreviewPosition - Region.TopLeft);
|
||||
|
||||
yield return new EditorSelectionAnnotationRenderable(
|
||||
Region,
|
||||
editorWidget.PasteColor,
|
||||
int2.Zero,
|
||||
PastePreviewPosition - Region.TopLeft);
|
||||
}
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
PastePreviewPosition = worldRenderer.Viewport.ViewToWorld(Viewport.LastMousePos);
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
|
||||
sealed class CopyPasteEditorAction : IEditorAction
|
||||
{
|
||||
[FluentReference("tiles")]
|
||||
const string CopiedTiles = "notification-copied-tiles";
|
||||
|
||||
[FluentReference("actors")]
|
||||
const string CopiedActors = "notification-copied-actors";
|
||||
|
||||
[FluentReference("tiles", "actors")]
|
||||
const string CopiedTilesAndActors = "notification-copied-tiles-actors";
|
||||
|
||||
public string Text { get; }
|
||||
|
||||
readonly EditorBlit editorBlit;
|
||||
|
||||
public CopyPasteEditorAction(EditorBlit editorBlit)
|
||||
{
|
||||
this.editorBlit = editorBlit;
|
||||
|
||||
var actors = editorBlit.ActorCount();
|
||||
var tiles = editorBlit.TileCount();
|
||||
|
||||
if (tiles > 0 && actors == 0)
|
||||
Text = FluentProvider.GetMessage(CopiedTiles, "tiles", tiles);
|
||||
else if (tiles == 0 && actors > 0)
|
||||
Text = FluentProvider.GetMessage(CopiedActors, "actors", actors);
|
||||
else
|
||||
Text = FluentProvider.GetMessage(CopiedTilesAndActors, "tiles", tiles, "actors", actors);
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
Do();
|
||||
}
|
||||
|
||||
public void Do()
|
||||
{
|
||||
editorBlit.Commit();
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
editorBlit.Revert();
|
||||
}
|
||||
}
|
||||
}
|
||||
627
OpenRA.Mods.Common/EditorBrushes/EditorDefaultBrush.cs
Normal file
627
OpenRA.Mods.Common/EditorBrushes/EditorDefaultBrush.cs
Normal file
@@ -0,0 +1,627 @@
|
||||
#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;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Common.EditorBrushes;
|
||||
using OpenRA.Mods.Common.Graphics;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Support;
|
||||
using OpenRA.Widgets;
|
||||
|
||||
namespace OpenRA.Mods.Common.Widgets
|
||||
{
|
||||
public interface IEditorBrush : IDisposable
|
||||
{
|
||||
bool HandleMouseInput(MouseInput mi);
|
||||
void Tick();
|
||||
|
||||
void TickRender(WorldRenderer wr, Actor self);
|
||||
IEnumerable<IRenderable> RenderAboveShroud(Actor self, WorldRenderer wr);
|
||||
IEnumerable<IRenderable> RenderAnnotations(Actor self, WorldRenderer wr);
|
||||
}
|
||||
|
||||
public class EditorSelection
|
||||
{
|
||||
public CellCoordsRegion? Area;
|
||||
public EditorActorPreview Actor;
|
||||
|
||||
public bool HasSelection => Area.HasValue || Actor != null;
|
||||
}
|
||||
|
||||
public sealed class EditorDefaultBrush : IEditorBrush
|
||||
{
|
||||
const int MinMouseMoveBeforeDrag = 32;
|
||||
|
||||
public event Action SelectionChanged;
|
||||
public event Action UpdateSelectedTab;
|
||||
|
||||
readonly WorldRenderer worldRenderer;
|
||||
readonly World world;
|
||||
readonly EditorViewportControllerWidget editorWidget;
|
||||
readonly EditorActorLayer editorLayer;
|
||||
readonly EditorActionManager editorActionManager;
|
||||
readonly IResourceLayer resourceLayer;
|
||||
readonly EditorActorLayer actorLayer;
|
||||
|
||||
public CellCoordsRegion? CurrentDragBounds => selectionBounds ?? Selection.Area;
|
||||
|
||||
public EditorSelection Selection { get; private set; } = new();
|
||||
|
||||
EditorSelection previousSelection;
|
||||
CellCoordsRegion? selectionBounds;
|
||||
int2? selectionStartLocation;
|
||||
CPos? selectionStartCell;
|
||||
int2 worldPixel;
|
||||
|
||||
bool draggingActor;
|
||||
MoveActorAction moveAction;
|
||||
int2 dragPixelOffset;
|
||||
CVec dragCellOffset;
|
||||
|
||||
public EditorDefaultBrush(EditorViewportControllerWidget editorWidget, WorldRenderer wr)
|
||||
{
|
||||
this.editorWidget = editorWidget;
|
||||
worldRenderer = wr;
|
||||
world = wr.World;
|
||||
|
||||
editorLayer = world.WorldActor.Trait<EditorActorLayer>();
|
||||
editorActionManager = world.WorldActor.Trait<EditorActionManager>();
|
||||
resourceLayer = world.WorldActor.TraitOrDefault<IResourceLayer>();
|
||||
actorLayer = world.WorldActor.Trait<EditorActorLayer>();
|
||||
}
|
||||
|
||||
long CalculateActorSelectionPriority(EditorActorPreview actor)
|
||||
{
|
||||
var centerPixel = new int2(actor.Bounds.X, actor.Bounds.Y);
|
||||
var pixelDistance = (centerPixel - worldPixel).Length;
|
||||
|
||||
// If 2+ actors have the same pixel position, then the highest appears on top.
|
||||
var worldZPosition = actor.CenterPosition.Z;
|
||||
|
||||
// Sort by pixel distance then in world z position.
|
||||
return ((long)pixelDistance << 32) + worldZPosition;
|
||||
}
|
||||
|
||||
public void DeleteSelection(MapBlitFilters filters)
|
||||
{
|
||||
if (Selection.Area.HasValue)
|
||||
editorActionManager.Add(new DeleteAreaAction(world.Map, filters, Selection.Area.Value, resourceLayer, actorLayer));
|
||||
}
|
||||
|
||||
public void ClearSelection(bool updateSelectedTab = false)
|
||||
{
|
||||
if (Selection.HasSelection)
|
||||
{
|
||||
previousSelection = Selection;
|
||||
SetSelection(new EditorSelection());
|
||||
editorActionManager.Add(new ChangeSelectionAction(this, Selection, previousSelection));
|
||||
|
||||
if (updateSelectedTab)
|
||||
UpdateSelectedTab?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetSelection(EditorSelection selection)
|
||||
{
|
||||
if (Selection == selection)
|
||||
return;
|
||||
|
||||
if (Selection.Actor != null)
|
||||
Selection.Actor.Selected = false;
|
||||
|
||||
Selection = selection;
|
||||
if (Selection.Actor != null)
|
||||
Selection.Actor.Selected = true;
|
||||
|
||||
SelectionChanged?.Invoke();
|
||||
}
|
||||
|
||||
public bool HandleMouseInput(MouseInput mi)
|
||||
{
|
||||
// Exclusively uses mouse wheel and both 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 && mi.Event != MouseInputEvent.Scroll)
|
||||
return false;
|
||||
|
||||
worldPixel = worldRenderer.Viewport.ViewToWorldPx(mi.Location);
|
||||
var cell = worldRenderer.Viewport.ViewToWorld(mi.Location);
|
||||
|
||||
var underCursor = editorLayer.PreviewsAtWorldPixel(worldPixel).MinByOrDefault(CalculateActorSelectionPriority);
|
||||
var resourceUnderCursor = resourceLayer?.GetResource(cell).Type;
|
||||
|
||||
if (underCursor != null)
|
||||
editorWidget.SetTooltip(underCursor.Tooltip);
|
||||
else if (resourceUnderCursor != null)
|
||||
editorWidget.SetTooltip(resourceUnderCursor);
|
||||
else
|
||||
editorWidget.SetTooltip(null);
|
||||
|
||||
// Actor drag.
|
||||
if (mi.Button == MouseButton.Left)
|
||||
{
|
||||
if (mi.Event == MouseInputEvent.Down && underCursor != null && (mi.Modifiers.HasModifier(Modifiers.Shift) || underCursor == Selection.Actor))
|
||||
{
|
||||
var cellViewPx = worldRenderer.Viewport.WorldToViewPx(worldRenderer.ScreenPosition(world.Map.CenterOfCell(cell)));
|
||||
dragPixelOffset = cellViewPx - mi.Location;
|
||||
dragCellOffset = underCursor.Location - cell;
|
||||
moveAction = new MoveActorAction(underCursor, actorLayer);
|
||||
draggingActor = true;
|
||||
return false;
|
||||
}
|
||||
else if (mi.Event == MouseInputEvent.Up && draggingActor)
|
||||
{
|
||||
editorWidget.SetTooltip(null);
|
||||
draggingActor = false;
|
||||
if (moveAction.HasMoved)
|
||||
editorActionManager.Add(moveAction);
|
||||
|
||||
moveAction = null;
|
||||
return false;
|
||||
}
|
||||
else if (mi.Event == MouseInputEvent.Move && draggingActor)
|
||||
{
|
||||
editorWidget.SetTooltip(null);
|
||||
var to = worldRenderer.Viewport.ViewToWorld(mi.Location + dragPixelOffset) + dragCellOffset;
|
||||
moveAction.Move(to);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Selection box drag.
|
||||
if (mi.Event == MouseInputEvent.Move &&
|
||||
selectionStartLocation != null &&
|
||||
(selectionBounds != null || (mi.Location - selectionStartLocation.Value).LengthSquared > MinMouseMoveBeforeDrag))
|
||||
{
|
||||
selectionStartCell ??= worldRenderer.Viewport.ViewToWorld(selectionStartLocation.Value);
|
||||
|
||||
var topLeft = new CPos(Math.Min(selectionStartCell.Value.X, cell.X), Math.Min(selectionStartCell.Value.Y, cell.Y));
|
||||
var bottomRight = new CPos(Math.Max(selectionStartCell.Value.X, cell.X), Math.Max(selectionStartCell.Value.Y, cell.Y));
|
||||
var gridType = worldRenderer.World.Map.Grid.Type;
|
||||
|
||||
// We've dragged enough to capture more than one cell, make a selection box.
|
||||
if (selectionBounds == null)
|
||||
{
|
||||
selectionBounds = new CellCoordsRegion(topLeft, bottomRight);
|
||||
|
||||
// Lose focus on any search boxes so we can always copy/paste.
|
||||
Ui.KeyboardFocusWidget = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We already have a drag box; resize it
|
||||
selectionBounds = new CellCoordsRegion(topLeft, bottomRight);
|
||||
}
|
||||
}
|
||||
|
||||
// Finished with mouse move events, so let them bubble up the widget tree.
|
||||
if (mi.Event == MouseInputEvent.Move)
|
||||
return false;
|
||||
|
||||
if (mi.Event == MouseInputEvent.Down && mi.Button == MouseButton.Left && selectionStartLocation == null)
|
||||
{
|
||||
// Start area drag.
|
||||
selectionStartLocation = mi.Location;
|
||||
}
|
||||
|
||||
if (mi.Event == MouseInputEvent.Up)
|
||||
{
|
||||
if (mi.Button == MouseButton.Left)
|
||||
{
|
||||
editorWidget.SetTooltip(null);
|
||||
selectionStartLocation = null;
|
||||
selectionStartCell = null;
|
||||
|
||||
// If we've released a bounds drag.
|
||||
if (selectionBounds != null)
|
||||
{
|
||||
// Set this as the editor selection.
|
||||
previousSelection = Selection;
|
||||
SetSelection(new EditorSelection
|
||||
{
|
||||
Area = selectionBounds
|
||||
});
|
||||
|
||||
selectionBounds = null;
|
||||
editorActionManager.Add(new ChangeSelectionAction(this, Selection, previousSelection));
|
||||
UpdateSelectedTab?.Invoke();
|
||||
}
|
||||
else if (underCursor != null)
|
||||
{
|
||||
// We've clicked on an actor.
|
||||
if (Selection.Actor != underCursor)
|
||||
{
|
||||
previousSelection = Selection;
|
||||
SetSelection(new EditorSelection
|
||||
{
|
||||
Actor = underCursor,
|
||||
});
|
||||
|
||||
editorActionManager.Add(new ChangeSelectionAction(this, Selection, previousSelection));
|
||||
UpdateSelectedTab?.Invoke();
|
||||
}
|
||||
}
|
||||
else if (Selection.HasSelection)
|
||||
{
|
||||
// Released left mouse without dragging or selecting an actor - deselect current.
|
||||
ClearSelection(updateSelectedTab: true);
|
||||
}
|
||||
}
|
||||
else if (mi.Button == MouseButton.Right)
|
||||
{
|
||||
editorWidget.SetTooltip(null);
|
||||
|
||||
// Delete actor.
|
||||
if (underCursor != null && underCursor != Selection.Actor && !draggingActor)
|
||||
editorActionManager.Add(new RemoveActorAction(editorLayer, underCursor));
|
||||
|
||||
// Or delete resource if found under cursor.
|
||||
if (resourceUnderCursor != null)
|
||||
editorActionManager.Add(new RemoveResourceAction(resourceLayer, cell, resourceUnderCursor));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void IEditorBrush.TickRender(WorldRenderer wr, Actor self) { }
|
||||
IEnumerable<IRenderable> IEditorBrush.RenderAboveShroud(Actor self, WorldRenderer wr) { yield break; }
|
||||
IEnumerable<IRenderable> IEditorBrush.RenderAnnotations(Actor self, WorldRenderer wr)
|
||||
{
|
||||
if (CurrentDragBounds.HasValue)
|
||||
{
|
||||
yield return new EditorSelectionAnnotationRenderable(CurrentDragBounds.Value, editorWidget.SelectionAltColor, editorWidget.SelectionAltOffset, CVec.Zero);
|
||||
yield return new EditorSelectionAnnotationRenderable(CurrentDragBounds.Value, editorWidget.SelectionMainColor, int2.Zero, CVec.Zero);
|
||||
}
|
||||
}
|
||||
|
||||
public void Tick() { }
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
|
||||
sealed class ChangeSelectionAction : IEditorAction
|
||||
{
|
||||
[FluentReference("x", "y", "width", "height")]
|
||||
const string SelectedArea = "notification-selected-area";
|
||||
|
||||
[FluentReference("id")]
|
||||
const string SelectedActor = "notification-selected-actor";
|
||||
|
||||
[FluentReference]
|
||||
const string ClearedSelection = "notification-cleared-selection";
|
||||
|
||||
public string Text { get; }
|
||||
|
||||
readonly EditorSelection selection;
|
||||
readonly EditorSelection previousSelection;
|
||||
readonly EditorDefaultBrush defaultBrush;
|
||||
|
||||
public ChangeSelectionAction(
|
||||
EditorDefaultBrush defaultBrush,
|
||||
EditorSelection selection,
|
||||
EditorSelection previousSelection)
|
||||
{
|
||||
this.defaultBrush = defaultBrush;
|
||||
this.selection = selection;
|
||||
this.previousSelection = new EditorSelection
|
||||
{
|
||||
Actor = previousSelection.Actor,
|
||||
Area = previousSelection.Area
|
||||
};
|
||||
|
||||
if (selection.Area.HasValue)
|
||||
Text = FluentProvider.GetMessage(SelectedArea,
|
||||
"x", selection.Area.Value.TopLeft.X,
|
||||
"y", selection.Area.Value.TopLeft.Y,
|
||||
"width", selection.Area.Value.BottomRight.X - selection.Area.Value.TopLeft.X,
|
||||
"height", selection.Area.Value.BottomRight.Y - selection.Area.Value.TopLeft.Y);
|
||||
else if (selection.Actor != null)
|
||||
Text = FluentProvider.GetMessage(SelectedActor, "id", selection.Actor.ID);
|
||||
else
|
||||
Text = FluentProvider.GetMessage(ClearedSelection);
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
Do();
|
||||
}
|
||||
|
||||
public void Do()
|
||||
{
|
||||
defaultBrush.SetSelection(selection);
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
defaultBrush.SetSelection(previousSelection);
|
||||
}
|
||||
}
|
||||
|
||||
sealed class DeleteAreaAction : IEditorAction
|
||||
{
|
||||
[FluentReference("x", "y", "width", "height")]
|
||||
const string RemovedArea = "notification-removed-area";
|
||||
|
||||
public string Text { get; }
|
||||
|
||||
readonly EditorBlitSource editorBlitSource;
|
||||
readonly MapBlitFilters blitFilters;
|
||||
readonly IResourceLayer resourceLayer;
|
||||
readonly EditorActorLayer editorActorLayer;
|
||||
readonly CellCoordsRegion area;
|
||||
readonly Map map;
|
||||
|
||||
public DeleteAreaAction(Map map, MapBlitFilters blitFilters, CellCoordsRegion area, IResourceLayer resourceLayer, EditorActorLayer editorActorLayer)
|
||||
{
|
||||
this.map = map;
|
||||
this.blitFilters = blitFilters;
|
||||
this.resourceLayer = resourceLayer;
|
||||
this.editorActorLayer = editorActorLayer;
|
||||
this.area = area;
|
||||
|
||||
editorBlitSource = EditorBlit.CopyRegionContents(map, editorActorLayer, resourceLayer, area, blitFilters);
|
||||
|
||||
Text = FluentProvider.GetMessage(RemovedArea,
|
||||
"x", area.TopLeft.X,
|
||||
"y", area.TopLeft.Y,
|
||||
"width", area.BottomRight.X - area.TopLeft.X,
|
||||
"height", area.BottomRight.Y - area.TopLeft.Y);
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
Do();
|
||||
}
|
||||
|
||||
public void Do()
|
||||
{
|
||||
if (blitFilters.HasFlag(MapBlitFilters.Actors))
|
||||
{
|
||||
// Clear any existing actors in the paste cells.
|
||||
using (new PerfTimer("RemoveActors", 1))
|
||||
editorActorLayer.RemoveRegion(area);
|
||||
}
|
||||
|
||||
foreach (var tileKeyValuePair in editorBlitSource.Tiles)
|
||||
{
|
||||
var position = tileKeyValuePair.Key;
|
||||
if (!map.Tiles.Contains(position))
|
||||
continue;
|
||||
|
||||
// Clear any existing resources.
|
||||
if (resourceLayer != null && blitFilters.HasFlag(MapBlitFilters.Resources))
|
||||
resourceLayer.ClearResources(position);
|
||||
|
||||
if (blitFilters.HasFlag(MapBlitFilters.Terrain))
|
||||
{
|
||||
map.Tiles[position] = map.Rules.TerrainInfo.DefaultTerrainTile;
|
||||
map.Height[position] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
foreach (var tileKeyValuePair in editorBlitSource.Tiles)
|
||||
{
|
||||
var position = tileKeyValuePair.Key;
|
||||
if (!map.Tiles.Contains(position))
|
||||
continue;
|
||||
|
||||
var tile = tileKeyValuePair.Value;
|
||||
var resourceLayerContents = tile.ResourceLayerContents;
|
||||
|
||||
if (blitFilters.HasFlag(MapBlitFilters.Terrain))
|
||||
{
|
||||
map.Tiles[position] = tile.TerrainTile;
|
||||
map.Height[position] = tile.Height;
|
||||
}
|
||||
|
||||
if (blitFilters.HasFlag(MapBlitFilters.Resources) &&
|
||||
resourceLayerContents.HasValue &&
|
||||
!string.IsNullOrWhiteSpace(resourceLayerContents.Value.Type))
|
||||
resourceLayer.AddResource(resourceLayerContents.Value.Type, position, resourceLayerContents.Value.Density);
|
||||
}
|
||||
|
||||
if (blitFilters.HasFlag(MapBlitFilters.Actors))
|
||||
{
|
||||
// Create copies of the original actors, update their locations, and place.
|
||||
var copies = new List<ActorReference>(editorBlitSource.Actors.Count);
|
||||
foreach (var actorKeyValuePair in editorBlitSource.Actors)
|
||||
{
|
||||
var copy = actorKeyValuePair.Value.Export();
|
||||
var locationInit = copy.GetOrDefault<LocationInit>();
|
||||
if (locationInit != null)
|
||||
{
|
||||
if (!map.Tiles.Contains(locationInit.Value))
|
||||
continue;
|
||||
|
||||
copy.RemoveAll<LocationInit>();
|
||||
copy.Add(new LocationInit(locationInit.Value));
|
||||
}
|
||||
|
||||
copies.Add(copy);
|
||||
}
|
||||
|
||||
editorActorLayer.AddRange(CollectionsMarshal.AsSpan(copies));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class RemoveSelectedActorAction : IEditorAction
|
||||
{
|
||||
[FluentReference("name", "id")]
|
||||
const string RemovedActor = "notification-removed-actor";
|
||||
|
||||
public string Text { get; }
|
||||
|
||||
readonly EditorSelection selection;
|
||||
readonly EditorDefaultBrush defaultBrush;
|
||||
readonly EditorActorLayer editorActorLayer;
|
||||
readonly EditorActorPreview actor;
|
||||
|
||||
public RemoveSelectedActorAction(
|
||||
EditorDefaultBrush defaultBrush,
|
||||
EditorActorLayer editorActorLayer,
|
||||
EditorActorPreview actor)
|
||||
{
|
||||
this.defaultBrush = defaultBrush;
|
||||
this.editorActorLayer = editorActorLayer;
|
||||
this.actor = actor;
|
||||
selection = new EditorSelection
|
||||
{
|
||||
Actor = defaultBrush.Selection.Actor
|
||||
};
|
||||
|
||||
Text = FluentProvider.GetMessage(RemovedActor, "name", actor.Info.Name, "id", actor.ID);
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
Do();
|
||||
}
|
||||
|
||||
public void Do()
|
||||
{
|
||||
defaultBrush.SetSelection(new EditorSelection());
|
||||
editorActorLayer.Remove(actor);
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
editorActorLayer.Add(actor);
|
||||
defaultBrush.SetSelection(selection);
|
||||
}
|
||||
}
|
||||
|
||||
sealed class RemoveActorAction : IEditorAction
|
||||
{
|
||||
[FluentReference("name", "id")]
|
||||
const string RemovedActor = "notification-removed-actor";
|
||||
|
||||
public string Text { get; }
|
||||
|
||||
readonly EditorActorLayer editorActorLayer;
|
||||
readonly EditorActorPreview actor;
|
||||
|
||||
public RemoveActorAction(EditorActorLayer editorActorLayer, EditorActorPreview actor)
|
||||
{
|
||||
this.editorActorLayer = editorActorLayer;
|
||||
this.actor = actor;
|
||||
|
||||
Text = FluentProvider.GetMessage(RemovedActor, "name", actor.Info.Name, "id", actor.ID);
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
Do();
|
||||
}
|
||||
|
||||
public void Do()
|
||||
{
|
||||
editorActorLayer.Remove(actor);
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
editorActorLayer.Add(actor);
|
||||
}
|
||||
}
|
||||
|
||||
sealed class MoveActorAction : IEditorAction
|
||||
{
|
||||
[FluentReference("id", "x1", "y1", "x2", "y2")]
|
||||
const string MovedActor = "notification-moved-actor";
|
||||
|
||||
public string Text { get; private set; }
|
||||
|
||||
readonly EditorActorPreview actor;
|
||||
readonly EditorActorLayer layer;
|
||||
readonly CPos from;
|
||||
|
||||
CPos to;
|
||||
|
||||
public MoveActorAction(
|
||||
EditorActorPreview actor,
|
||||
EditorActorLayer layer)
|
||||
{
|
||||
this.actor = actor;
|
||||
this.layer = layer;
|
||||
|
||||
from = actor.Location;
|
||||
to = from;
|
||||
}
|
||||
|
||||
public void Execute() { }
|
||||
|
||||
public void Do()
|
||||
{
|
||||
layer.MoveActor(actor, to);
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
layer.MoveActor(actor, from);
|
||||
}
|
||||
|
||||
public bool HasMoved => from != to;
|
||||
|
||||
public void Move(CPos to)
|
||||
{
|
||||
this.to = to;
|
||||
layer.MoveActor(actor, this.to);
|
||||
|
||||
Text = FluentProvider.GetMessage(MovedActor, "id", actor.ID, "x1", from.X, "y1", from.Y, "x2", this.to.X, "y2", this.to.Y);
|
||||
}
|
||||
}
|
||||
|
||||
sealed class RemoveResourceAction : IEditorAction
|
||||
{
|
||||
[FluentReference("type")]
|
||||
const string RemovedResource = "notification-removed-resource";
|
||||
|
||||
public string Text { get; }
|
||||
|
||||
readonly IResourceLayer resourceLayer;
|
||||
readonly CPos cell;
|
||||
|
||||
ResourceLayerContents resourceContents;
|
||||
|
||||
public RemoveResourceAction(IResourceLayer resourceLayer, CPos cell, string resourceType)
|
||||
{
|
||||
this.resourceLayer = resourceLayer;
|
||||
this.cell = cell;
|
||||
|
||||
Text = FluentProvider.GetMessage(RemovedResource, "type", resourceType);
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
Do();
|
||||
}
|
||||
|
||||
public void Do()
|
||||
{
|
||||
resourceContents = resourceLayer.GetResource(cell);
|
||||
resourceLayer.ClearResources(cell);
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
resourceLayer.ClearResources(cell);
|
||||
resourceLayer.AddResource(resourceContents.Type, cell, resourceContents.Density);
|
||||
}
|
||||
}
|
||||
}
|
||||
265
OpenRA.Mods.Common/EditorBrushes/EditorMarkerLayerBrush.cs
Normal file
265
OpenRA.Mods.Common/EditorBrushes/EditorMarkerLayerBrush.cs
Normal file
@@ -0,0 +1,265 @@
|
||||
#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.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Widgets
|
||||
{
|
||||
public sealed class EditorMarkerLayerBrush : IEditorBrush
|
||||
{
|
||||
public int? Template;
|
||||
|
||||
readonly WorldRenderer worldRenderer;
|
||||
readonly World world;
|
||||
readonly EditorActionManager editorActionManager;
|
||||
readonly MarkerLayerOverlay markerLayerOverlay;
|
||||
readonly EditorViewportControllerWidget editorWidget;
|
||||
|
||||
readonly List<PaintMarkerTile> paintTiles = [];
|
||||
bool painting;
|
||||
CPos cell;
|
||||
|
||||
public EditorMarkerLayerBrush(EditorViewportControllerWidget editorWidget, int? id, WorldRenderer wr)
|
||||
{
|
||||
this.editorWidget = editorWidget;
|
||||
worldRenderer = wr;
|
||||
world = wr.World;
|
||||
|
||||
editorActionManager = world.WorldActor.Trait<EditorActionManager>();
|
||||
markerLayerOverlay = world.WorldActor.Trait<MarkerLayerOverlay>();
|
||||
|
||||
Template = id;
|
||||
}
|
||||
|
||||
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)
|
||||
return true;
|
||||
|
||||
if (mi.Event == MouseInputEvent.Up)
|
||||
{
|
||||
UpdatePreview();
|
||||
if (paintTiles.Count != 0)
|
||||
{
|
||||
editorActionManager.Add(new PaintMarkerTileEditorAction(Template, paintTiles.ToImmutableArray(), markerLayerOverlay));
|
||||
paintTiles.Clear();
|
||||
UpdatePreview(true);
|
||||
}
|
||||
|
||||
painting = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
painting = true;
|
||||
UpdatePreview();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UpdatePreview(bool forceRefresh = false)
|
||||
{
|
||||
var currentCell = worldRenderer.Viewport.ViewToWorld(Viewport.LastMousePos);
|
||||
if (!forceRefresh && cell == currentCell)
|
||||
return;
|
||||
|
||||
cell = currentCell;
|
||||
|
||||
if (!painting)
|
||||
{
|
||||
foreach (var paintTile in paintTiles)
|
||||
markerLayerOverlay.SetTile(paintTile.Cell, paintTile.Previous);
|
||||
|
||||
paintTiles.Clear();
|
||||
}
|
||||
|
||||
foreach (var cell in markerLayerOverlay.CalculateMirrorPositions(cell))
|
||||
{
|
||||
if (paintTiles.Any(t => t.Cell == cell))
|
||||
continue;
|
||||
|
||||
var existing = markerLayerOverlay.CellLayer[cell];
|
||||
if (existing == Template)
|
||||
continue;
|
||||
|
||||
paintTiles.Add(new PaintMarkerTile(cell, existing));
|
||||
markerLayerOverlay.SetTile(cell, Template);
|
||||
}
|
||||
}
|
||||
|
||||
void IEditorBrush.TickRender(WorldRenderer wr, Actor self) { UpdatePreview(); }
|
||||
IEnumerable<IRenderable> IEditorBrush.RenderAboveShroud(Actor self, WorldRenderer wr) { yield break; }
|
||||
IEnumerable<IRenderable> IEditorBrush.RenderAnnotations(Actor self, WorldRenderer wr) { yield break; }
|
||||
|
||||
public void Tick() { }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var paintTile in paintTiles)
|
||||
markerLayerOverlay.SetTile(paintTile.Cell, paintTile.Previous);
|
||||
}
|
||||
}
|
||||
|
||||
readonly struct PaintMarkerTile
|
||||
{
|
||||
public readonly CPos Cell;
|
||||
public readonly int? Previous;
|
||||
|
||||
public PaintMarkerTile(CPos cell, int? previous)
|
||||
{
|
||||
Cell = cell;
|
||||
Previous = previous;
|
||||
}
|
||||
}
|
||||
|
||||
sealed class PaintMarkerTileEditorAction : IEditorAction
|
||||
{
|
||||
[FluentReference("count", "type")]
|
||||
const string AddedMarkerTiles = "notification-added-marker-tiles";
|
||||
|
||||
[FluentReference("count")]
|
||||
const string RemovedMarkerTiles = "notification-removed-marker-tiles";
|
||||
|
||||
public string Text { get; }
|
||||
|
||||
readonly int? type;
|
||||
readonly MarkerLayerOverlay markerLayerOverlay;
|
||||
|
||||
readonly ImmutableArray<PaintMarkerTile> paintTiles = [];
|
||||
|
||||
public PaintMarkerTileEditorAction(
|
||||
int? type,
|
||||
ImmutableArray<PaintMarkerTile> paintTiles,
|
||||
MarkerLayerOverlay markerLayerOverlay)
|
||||
{
|
||||
this.type = type;
|
||||
this.paintTiles = paintTiles;
|
||||
this.markerLayerOverlay = markerLayerOverlay;
|
||||
|
||||
if (type != null)
|
||||
{
|
||||
var typeLabel = FluentProvider.GetMessage(markerLayerOverlay.Info.Colors.ElementAt(type.Value).Key);
|
||||
Text = FluentProvider.GetMessage(AddedMarkerTiles, "count", paintTiles.Length, "type", typeLabel);
|
||||
}
|
||||
else
|
||||
Text = FluentProvider.GetMessage(RemovedMarkerTiles, "count", paintTiles.Length);
|
||||
}
|
||||
|
||||
public void Execute() { }
|
||||
|
||||
public void Do()
|
||||
{
|
||||
foreach (var paintTile in paintTiles)
|
||||
markerLayerOverlay.SetTile(paintTile.Cell, type);
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
foreach (var paintTile in paintTiles)
|
||||
markerLayerOverlay.SetTile(paintTile.Cell, paintTile.Previous);
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ClearSelectedMarkerTilesEditorAction : IEditorAction
|
||||
{
|
||||
[FluentReference("count", "type")]
|
||||
const string ClearedSelectedMarkerTiles = "notification-cleared-selected-marker-tiles";
|
||||
|
||||
public string Text { get; }
|
||||
|
||||
readonly MarkerLayerOverlay markerLayerOverlay;
|
||||
readonly ImmutableArray<CPos> tiles;
|
||||
readonly int tile;
|
||||
|
||||
public ClearSelectedMarkerTilesEditorAction(
|
||||
int tile,
|
||||
MarkerLayerOverlay markerLayerOverlay)
|
||||
{
|
||||
this.tile = tile;
|
||||
this.markerLayerOverlay = markerLayerOverlay;
|
||||
|
||||
tiles = markerLayerOverlay.Tiles[tile].ToImmutableArray();
|
||||
var typeLabel = FluentProvider.GetMessage(markerLayerOverlay.Info.Colors.ElementAt(tile).Key);
|
||||
Text = FluentProvider.GetMessage(ClearedSelectedMarkerTiles, "count", tiles.Length, "type", typeLabel);
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
Do();
|
||||
}
|
||||
|
||||
public void Do()
|
||||
{
|
||||
markerLayerOverlay.ClearSelected(tile);
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
markerLayerOverlay.SetSelected(tile, tiles.AsSpan());
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ClearAllMarkerTilesEditorAction : IEditorAction
|
||||
{
|
||||
[FluentReference("count")]
|
||||
const string ClearedAllMarkerTiles = "notification-cleared-all-marker-tiles";
|
||||
|
||||
public string Text { get; }
|
||||
|
||||
readonly MarkerLayerOverlay markerLayerOverlay;
|
||||
readonly FrozenDictionary<int, ImmutableArray<CPos>> tiles;
|
||||
|
||||
public ClearAllMarkerTilesEditorAction(
|
||||
MarkerLayerOverlay markerLayerOverlay)
|
||||
{
|
||||
this.markerLayerOverlay = markerLayerOverlay;
|
||||
tiles = markerLayerOverlay.Tiles.ToFrozenDictionary(t => t.Key, t => t.Value.ToImmutableArray());
|
||||
var allTilesCount = tiles.Values.Sum(x => x.Length);
|
||||
|
||||
Text = FluentProvider.GetMessage(ClearedAllMarkerTiles, "count", allTilesCount);
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
Do();
|
||||
}
|
||||
|
||||
public void Do()
|
||||
{
|
||||
markerLayerOverlay.ClearAll();
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
markerLayerOverlay.SetAll(tiles);
|
||||
}
|
||||
}
|
||||
}
|
||||
161
OpenRA.Mods.Common/EditorBrushes/EditorResourceBrush.cs
Normal file
161
OpenRA.Mods.Common/EditorBrushes/EditorResourceBrush.cs
Normal file
@@ -0,0 +1,161 @@
|
||||
#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 System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Widgets
|
||||
{
|
||||
public sealed class EditorResourceBrush : IEditorBrush
|
||||
{
|
||||
public readonly string ResourceType;
|
||||
|
||||
readonly WorldRenderer worldRenderer;
|
||||
readonly World world;
|
||||
readonly EditorViewportControllerWidget editorWidget;
|
||||
readonly EditorActionManager editorActionManager;
|
||||
readonly IResourceLayer resourceLayer;
|
||||
|
||||
AddResourcesEditorAction action;
|
||||
bool resourceAdded;
|
||||
|
||||
CPos cell;
|
||||
readonly List<IRenderable> preview = [];
|
||||
readonly IResourceRenderer[] resourceRenderers;
|
||||
|
||||
public EditorResourceBrush(EditorViewportControllerWidget editorWidget, string resourceType, WorldRenderer wr)
|
||||
{
|
||||
this.editorWidget = editorWidget;
|
||||
ResourceType = resourceType;
|
||||
worldRenderer = wr;
|
||||
world = wr.World;
|
||||
editorActionManager = world.WorldActor.Trait<EditorActionManager>();
|
||||
resourceLayer = world.WorldActor.Trait<IResourceLayer>();
|
||||
|
||||
resourceRenderers = world.WorldActor.TraitsImplementing<IResourceRenderer>().ToArray();
|
||||
cell = wr.Viewport.ViewToWorld(wr.Viewport.WorldToViewPx(Viewport.LastMousePos));
|
||||
UpdatePreview();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
var cell = worldRenderer.Viewport.ViewToWorld(mi.Location);
|
||||
|
||||
if (mi.Button == MouseButton.Left && mi.Event != MouseInputEvent.Up && resourceLayer.CanAddResource(ResourceType, cell))
|
||||
{
|
||||
action ??= new AddResourcesEditorAction(ResourceType, resourceLayer);
|
||||
action.Add(new CellResource(cell, resourceLayer.GetResource(cell)));
|
||||
resourceAdded = true;
|
||||
}
|
||||
else if (resourceAdded && mi.Button == MouseButton.Left && mi.Event == MouseInputEvent.Up)
|
||||
{
|
||||
editorActionManager.Add(action);
|
||||
action = null;
|
||||
resourceAdded = false;
|
||||
}
|
||||
|
||||
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<IRenderable> IEditorBrush.RenderAboveShroud(Actor self, WorldRenderer wr) { return action == null ? preview : null; }
|
||||
IEnumerable<IRenderable> IEditorBrush.RenderAnnotations(Actor self, WorldRenderer wr) { yield break; }
|
||||
|
||||
public void Tick() { }
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
|
||||
readonly record struct CellResource(CPos Cell, ResourceLayerContents OldResourceTile);
|
||||
|
||||
sealed class AddResourcesEditorAction : IEditorAction
|
||||
{
|
||||
[FluentReference("count", "type")]
|
||||
const string AddedResource = "notification-added-resource";
|
||||
|
||||
public string Text { get; private set; }
|
||||
|
||||
readonly IResourceLayer resourceLayer;
|
||||
readonly string resourceType;
|
||||
readonly List<CellResource> cellResources = [];
|
||||
|
||||
public AddResourcesEditorAction(string resourceType, IResourceLayer resourceLayer)
|
||||
{
|
||||
this.resourceType = resourceType;
|
||||
this.resourceLayer = resourceLayer;
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
cellResources.TrimExcess();
|
||||
}
|
||||
|
||||
public void Do()
|
||||
{
|
||||
foreach (var resourceCell in cellResources)
|
||||
resourceLayer.AddResource(resourceType, resourceCell.Cell, resourceLayer.GetMaxDensity(resourceType));
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
foreach (var resourceCell in cellResources)
|
||||
{
|
||||
// If resources match, simulate a replace command.
|
||||
if (resourceCell.OldResourceTile.Type == resourceType || resourceCell.OldResourceTile.Type == null)
|
||||
resourceLayer.ClearResources(resourceCell.Cell);
|
||||
|
||||
if (resourceCell.OldResourceTile.Type == resourceType || resourceCell.OldResourceTile.Type != null)
|
||||
resourceLayer.AddResource(resourceCell.OldResourceTile.Type, resourceCell.Cell, resourceCell.OldResourceTile.Density);
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(CellResource resourceCell)
|
||||
{
|
||||
resourceLayer.AddResource(resourceType, resourceCell.Cell, resourceLayer.GetMaxDensity(resourceType));
|
||||
cellResources.Add(resourceCell);
|
||||
Text = FluentProvider.GetMessage(AddedResource, "count", cellResources.Count, "type", resourceType);
|
||||
}
|
||||
}
|
||||
}
|
||||
383
OpenRA.Mods.Common/EditorBrushes/EditorTileBrush.cs
Normal file
383
OpenRA.Mods.Common/EditorBrushes/EditorTileBrush.cs
Normal file
@@ -0,0 +1,383 @@
|
||||
#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 System.IO;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Common.Terrain;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Widgets
|
||||
{
|
||||
public sealed class EditorTileBrush : IEditorBrush
|
||||
{
|
||||
public readonly TerrainTemplateInfo TerrainTemplate;
|
||||
public readonly ushort Template;
|
||||
|
||||
readonly WorldRenderer worldRenderer;
|
||||
readonly World world;
|
||||
readonly ITemplatedTerrainInfo terrainInfo;
|
||||
readonly EditorViewportControllerWidget editorWidget;
|
||||
readonly EditorActionManager editorActionManager;
|
||||
|
||||
bool painting;
|
||||
|
||||
readonly ITiledTerrainRenderer terrainRenderer;
|
||||
|
||||
CPos cell;
|
||||
readonly List<IRenderable> preview = [];
|
||||
|
||||
public EditorTileBrush(EditorViewportControllerWidget editorWidget, ushort id, WorldRenderer wr)
|
||||
{
|
||||
this.editorWidget = editorWidget;
|
||||
worldRenderer = wr;
|
||||
world = wr.World;
|
||||
terrainInfo = world.Map.Rules.TerrainInfo as ITemplatedTerrainInfo;
|
||||
if (terrainInfo == null)
|
||||
throw new InvalidDataException($"{nameof(EditorTileBrush)} can only be used with template-based tilesets");
|
||||
|
||||
editorActionManager = world.WorldActor.Trait<EditorActionManager>();
|
||||
terrainRenderer = world.WorldActor.Trait<ITiledTerrainRenderer>();
|
||||
|
||||
Template = id;
|
||||
TerrainTemplate = terrainInfo.Templates[Template];
|
||||
cell = wr.Viewport.ViewToWorld(wr.Viewport.WorldToViewPx(Viewport.LastMousePos));
|
||||
UpdatePreview();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (mi.Event == MouseInputEvent.Down)
|
||||
painting = true;
|
||||
else if (mi.Event == MouseInputEvent.Up)
|
||||
painting = false;
|
||||
}
|
||||
|
||||
if (!painting)
|
||||
return true;
|
||||
|
||||
if (mi.Event != MouseInputEvent.Down && mi.Event != MouseInputEvent.Move)
|
||||
return true;
|
||||
|
||||
var cell = worldRenderer.Viewport.ViewToWorld(mi.Location);
|
||||
var isMoving = mi.Event == MouseInputEvent.Move;
|
||||
|
||||
if (mi.Modifiers.HasModifier(Modifiers.Shift))
|
||||
{
|
||||
FloodFillWithBrush(cell);
|
||||
painting = false;
|
||||
}
|
||||
else
|
||||
PaintCell(cell, isMoving);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PaintCell(CPos cell, bool isMoving)
|
||||
{
|
||||
var template = terrainInfo.Templates[Template];
|
||||
if (isMoving && PlacementOverlapsSameTemplate(template, cell))
|
||||
return;
|
||||
|
||||
editorActionManager.Add(new PaintTileEditorAction(Template, world.Map, cell));
|
||||
}
|
||||
|
||||
void FloodFillWithBrush(CPos cell)
|
||||
{
|
||||
var map = world.Map;
|
||||
if (!map.Contains(cell))
|
||||
return;
|
||||
|
||||
var mapTiles = map.Tiles;
|
||||
var replace = mapTiles[cell];
|
||||
|
||||
if (replace.Type == Template)
|
||||
return;
|
||||
|
||||
editorActionManager.Add(new FloodFillEditorAction(Template, map, cell));
|
||||
}
|
||||
|
||||
bool PlacementOverlapsSameTemplate(TerrainTemplateInfo template, CPos cell)
|
||||
{
|
||||
var map = world.Map;
|
||||
var mapTiles = map.Tiles;
|
||||
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 (mapTiles.Contains(c) && mapTiles[c].Type == template.Id)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<IRenderable> IEditorBrush.RenderAboveShroud(Actor self, WorldRenderer wr) { return preview; }
|
||||
IEnumerable<IRenderable> IEditorBrush.RenderAnnotations(Actor self, WorldRenderer wr) { yield break; }
|
||||
|
||||
public void Tick() { }
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
|
||||
sealed class PaintTileEditorAction : IEditorAction
|
||||
{
|
||||
[FluentReference("id")]
|
||||
const string AddedTile = "notification-added-tile";
|
||||
|
||||
public string Text { get; }
|
||||
|
||||
readonly ushort template;
|
||||
readonly Map map;
|
||||
readonly CPos cell;
|
||||
|
||||
readonly Queue<UndoTile> undoTiles = [];
|
||||
readonly TerrainTemplateInfo terrainTemplate;
|
||||
|
||||
public PaintTileEditorAction(ushort template, Map map, CPos cell)
|
||||
{
|
||||
this.template = template;
|
||||
this.map = map;
|
||||
this.cell = cell;
|
||||
|
||||
var terrainInfo = (ITemplatedTerrainInfo)map.Rules.TerrainInfo;
|
||||
terrainTemplate = terrainInfo.Templates[template];
|
||||
Text = FluentProvider.GetMessage(AddedTile, "id", terrainTemplate.Id);
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
Do();
|
||||
}
|
||||
|
||||
public void Do()
|
||||
{
|
||||
var mapTiles = map.Tiles;
|
||||
var mapHeight = map.Height;
|
||||
var baseHeight = mapHeight.Contains(cell) ? mapHeight[cell] : (byte)0;
|
||||
|
||||
var i = 0;
|
||||
for (var y = 0; y < terrainTemplate.Size.Y; y++)
|
||||
{
|
||||
for (var x = 0; x < terrainTemplate.Size.X; x++, i++)
|
||||
{
|
||||
if (terrainTemplate.Contains(i) && terrainTemplate[i] != null)
|
||||
{
|
||||
var index = terrainTemplate.PickAny ? (byte)Game.CosmeticRandom.Next(0, terrainTemplate.TilesCount) : (byte)i;
|
||||
var c = cell + new CVec(x, y);
|
||||
if (!mapTiles.Contains(c))
|
||||
continue;
|
||||
|
||||
undoTiles.Enqueue(new UndoTile(c, mapTiles[c], mapHeight[c]));
|
||||
|
||||
mapTiles[c] = new TerrainTile(template, index);
|
||||
mapHeight[c] = (byte)(baseHeight + terrainTemplate[index].Height).Clamp(0, map.Grid.MaximumTerrainHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
var mapTiles = map.Tiles;
|
||||
var mapHeight = map.Height;
|
||||
|
||||
while (undoTiles.Count > 0)
|
||||
{
|
||||
var undoTile = undoTiles.Dequeue();
|
||||
|
||||
mapTiles[undoTile.Cell] = undoTile.MapTile;
|
||||
mapHeight[undoTile.Cell] = undoTile.Height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class FloodFillEditorAction : IEditorAction
|
||||
{
|
||||
[FluentReference("id")]
|
||||
const string FilledTile = "notification-filled-tile";
|
||||
|
||||
public string Text { get; }
|
||||
|
||||
readonly ushort template;
|
||||
readonly Map map;
|
||||
readonly CPos cell;
|
||||
|
||||
readonly Queue<UndoTile> undoTiles = [];
|
||||
readonly TerrainTemplateInfo terrainTemplate;
|
||||
|
||||
public FloodFillEditorAction(ushort template, Map map, CPos cell)
|
||||
{
|
||||
this.template = template;
|
||||
this.map = map;
|
||||
this.cell = cell;
|
||||
|
||||
var terrainInfo = (ITemplatedTerrainInfo)map.Rules.TerrainInfo;
|
||||
terrainTemplate = terrainInfo.Templates[template];
|
||||
Text = FluentProvider.GetMessage(FilledTile, "id", terrainTemplate.Id);
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
Do();
|
||||
}
|
||||
|
||||
public void Do()
|
||||
{
|
||||
var queue = new Queue<CPos>();
|
||||
var touched = new CellLayer<bool>(map);
|
||||
var mapTiles = map.Tiles;
|
||||
var replace = mapTiles[cell];
|
||||
|
||||
void MaybeEnqueue(CPos newCell)
|
||||
{
|
||||
if (map.Contains(cell) && !touched[newCell])
|
||||
{
|
||||
queue.Enqueue(newCell);
|
||||
touched[newCell] = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool ShouldPaint(CPos cellToCheck)
|
||||
{
|
||||
for (var y = 0; y < terrainTemplate.Size.Y; y++)
|
||||
{
|
||||
for (var x = 0; x < terrainTemplate.Size.X; x++)
|
||||
{
|
||||
var c = cellToCheck + new CVec(x, y);
|
||||
if (!map.Contains(c) || mapTiles[c].Type != replace.Type)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CPos FindEdge(CPos refCell, CVec direction)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var newCell = refCell + direction;
|
||||
if (!ShouldPaint(newCell))
|
||||
return refCell;
|
||||
refCell = newCell;
|
||||
}
|
||||
}
|
||||
|
||||
queue.Enqueue(cell);
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
var queuedCell = queue.Dequeue();
|
||||
if (!ShouldPaint(queuedCell))
|
||||
continue;
|
||||
|
||||
var previousCell = FindEdge(queuedCell, new CVec(-1 * terrainTemplate.Size.X, 0));
|
||||
var nextCell = FindEdge(queuedCell, new CVec(1 * terrainTemplate.Size.X, 0));
|
||||
|
||||
for (var x = previousCell.X; x <= nextCell.X; x += terrainTemplate.Size.X)
|
||||
{
|
||||
PaintSingleCell(new CPos(x, queuedCell.Y));
|
||||
var upperCell = new CPos(x, queuedCell.Y - 1 * terrainTemplate.Size.Y);
|
||||
var lowerCell = new CPos(x, queuedCell.Y + 1 * terrainTemplate.Size.Y);
|
||||
|
||||
if (ShouldPaint(upperCell))
|
||||
MaybeEnqueue(upperCell);
|
||||
if (ShouldPaint(lowerCell))
|
||||
MaybeEnqueue(lowerCell);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
var mapTiles = map.Tiles;
|
||||
var mapHeight = map.Height;
|
||||
|
||||
while (undoTiles.Count > 0)
|
||||
{
|
||||
var undoTile = undoTiles.Dequeue();
|
||||
|
||||
mapTiles[undoTile.Cell] = undoTile.MapTile;
|
||||
mapHeight[undoTile.Cell] = undoTile.Height;
|
||||
}
|
||||
}
|
||||
|
||||
void PaintSingleCell(CPos cellToPaint)
|
||||
{
|
||||
var mapTiles = map.Tiles;
|
||||
var mapHeight = map.Height;
|
||||
var baseHeight = mapHeight.Contains(cellToPaint) ? mapHeight[cellToPaint] : (byte)0;
|
||||
|
||||
var i = 0;
|
||||
for (var y = 0; y < terrainTemplate.Size.Y; y++)
|
||||
{
|
||||
for (var x = 0; x < terrainTemplate.Size.X; x++, i++)
|
||||
{
|
||||
if (terrainTemplate.Contains(i) && terrainTemplate[i] != null)
|
||||
{
|
||||
var index = terrainTemplate.PickAny ? (byte)Game.CosmeticRandom.Next(0, terrainTemplate.TilesCount) : (byte)i;
|
||||
var c = cellToPaint + new CVec(x, y);
|
||||
if (!mapTiles.Contains(c))
|
||||
continue;
|
||||
|
||||
undoTiles.Enqueue(new UndoTile(c, mapTiles[c], mapHeight[c]));
|
||||
|
||||
mapTiles[c] = new TerrainTile(template, index);
|
||||
mapHeight[c] = (byte)(baseHeight + terrainTemplate[index].Height).Clamp(0, map.Grid.MaximumTerrainHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed record UndoTile(CPos Cell, TerrainTile MapTile, byte Height);
|
||||
}
|
||||
376
OpenRA.Mods.Common/EditorBrushes/EditorTilingPathBrush.cs
Normal file
376
OpenRA.Mods.Common/EditorBrushes/EditorTilingPathBrush.cs
Normal file
@@ -0,0 +1,376 @@
|
||||
#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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Common.EditorBrushes;
|
||||
using OpenRA.Mods.Common.Graphics;
|
||||
using OpenRA.Mods.Common.MapGenerator;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Mods.Common.Widgets
|
||||
{
|
||||
public sealed class EditorTilingPathBrush : IEditorBrush
|
||||
{
|
||||
readonly TilingPathTool tool;
|
||||
readonly WorldRenderer worldRenderer;
|
||||
readonly EditorActionManager editorActionManager;
|
||||
|
||||
MouseInput? startingMouseInput = null;
|
||||
bool isDragging = false;
|
||||
TilingPathTool.PathPlan previewPlan = null;
|
||||
|
||||
public EditorTilingPathBrush(TilingPathTool tool)
|
||||
{
|
||||
this.tool = tool;
|
||||
worldRenderer = tool.WorldRenderer;
|
||||
editorActionManager = worldRenderer.World.WorldActor.Trait<EditorActionManager>();
|
||||
}
|
||||
|
||||
public bool HandleMouseInput(MouseInput mouseInput)
|
||||
{
|
||||
if (mouseInput.Button != MouseButton.Left)
|
||||
return false;
|
||||
|
||||
var isFinal = false;
|
||||
if (mouseInput.Event == MouseInputEvent.Down)
|
||||
{
|
||||
startingMouseInput = mouseInput;
|
||||
isDragging = false;
|
||||
}
|
||||
else if (startingMouseInput != null)
|
||||
{
|
||||
if (mouseInput.Event == MouseInputEvent.Up)
|
||||
{
|
||||
isFinal = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CPos ViewToWorldCorner(int2 xy) =>
|
||||
CellLayerUtils.WPosToCorner(
|
||||
worldRenderer.ProjectedPosition(
|
||||
worldRenderer.Viewport.ViewToWorldPx(xy)),
|
||||
worldRenderer.World.Map.Grid.Type);
|
||||
|
||||
var from = ViewToWorldCorner(startingMouseInput.Value.Location);
|
||||
var to = ViewToWorldCorner(mouseInput.Location);
|
||||
|
||||
void UpdatePlan(TilingPathTool.PathPlan newPlan, bool preview)
|
||||
{
|
||||
if (isFinal)
|
||||
{
|
||||
editorActionManager.Add(
|
||||
new UpdateTilingPathPlanEditorAction(tool, newPlan));
|
||||
}
|
||||
else if (preview)
|
||||
{
|
||||
previewPlan = newPlan;
|
||||
}
|
||||
}
|
||||
|
||||
if (isFinal)
|
||||
{
|
||||
previewPlan = null;
|
||||
startingMouseInput = null;
|
||||
}
|
||||
|
||||
isDragging |= to != from;
|
||||
var plan = tool.Plan;
|
||||
|
||||
if (plan == null)
|
||||
{
|
||||
UpdatePlan(new TilingPathTool.PathPlan(to), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
var points = plan.PointsWithRallyIndex();
|
||||
|
||||
(bool IsInside, bool IsRally, int RallyIndex, bool IsStartDirector, bool IsEndDirector)
|
||||
AssessCPos(CPos cpos)
|
||||
{
|
||||
var isInside = points.Select(p => p.CPos).Contains(cpos);
|
||||
var isRally = plan.Rallies.Contains(cpos);
|
||||
var rallyIndex =
|
||||
isRally
|
||||
? plan.Rallies.TakeWhile(r => r != cpos).Count()
|
||||
: points
|
||||
.Where(p => p.CPos == cpos)
|
||||
.Select(p => p.RallyIndex)
|
||||
.FirstOrDefault(0);
|
||||
var isStartDirector =
|
||||
plan.AutoStart != Direction.None
|
||||
&& cpos == plan.FirstPoint - plan.AutoStart.ToCVec();
|
||||
var isEndDirector =
|
||||
plan.AutoEnd != Direction.None
|
||||
&& cpos == plan.LastPoint + plan.AutoEnd.ToCVec();
|
||||
return (isInside, isRally, rallyIndex, isStartDirector, isEndDirector);
|
||||
}
|
||||
|
||||
var (fromIsInside, fromIsRally, fromRallyIndex, fromIsStartDirector, fromIsEndDirector) =
|
||||
AssessCPos(from);
|
||||
var (toIsInside, toIsRally, toRallyIndex, toIsStartDirector, toIsEndDirector) =
|
||||
AssessCPos(to);
|
||||
|
||||
if (isDragging)
|
||||
{
|
||||
if (fromIsStartDirector)
|
||||
{
|
||||
var offset = plan.FirstPoint - to;
|
||||
var direction =
|
||||
offset != CVec.Zero
|
||||
? DirectionExts.ClosestFromCVec(offset)
|
||||
: Direction.None;
|
||||
UpdatePlan(plan.WithStart(direction), true);
|
||||
}
|
||||
else if (fromIsEndDirector)
|
||||
{
|
||||
var offset = to - plan.LastPoint;
|
||||
var direction =
|
||||
offset != CVec.Zero
|
||||
? DirectionExts.ClosestFromCVec(offset)
|
||||
: Direction.None;
|
||||
UpdatePlan(plan.WithEnd(direction), true);
|
||||
}
|
||||
else if (fromIsInside)
|
||||
{
|
||||
if (fromIsRally)
|
||||
{
|
||||
if (!toIsRally || to == from)
|
||||
{
|
||||
UpdatePlan(plan.WithRallyReplaced(fromRallyIndex, to), true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdatePlan(plan.Moved(to - from), true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!toIsRally)
|
||||
{
|
||||
UpdatePlan(plan.WithRallyAppended(to), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (toIsInside)
|
||||
{
|
||||
if (toIsRally)
|
||||
{
|
||||
if (toRallyIndex == 0)
|
||||
{
|
||||
UpdatePlan(plan.WithLoop(!plan.Loop), false);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdatePlan(plan.WithRallyRemoved(toRallyIndex), false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdatePlan(plan.WithRallyInserted(toRallyIndex, to), false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdatePlan(plan.WithRallyAppended(to), true);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void IEditorBrush.TickRender(WorldRenderer wr, Actor self) { }
|
||||
IEnumerable<IRenderable> IEditorBrush.RenderAboveShroud(Actor self, WorldRenderer wr)
|
||||
{
|
||||
if (tool.EditorBlitSource == null)
|
||||
yield break;
|
||||
|
||||
var stickToGround = tool.EditorBlitSource.Value.Tiles.Count == 0;
|
||||
var preview = EditorBlit.PreviewBlitSource(
|
||||
tool.EditorBlitSource.Value,
|
||||
MapBlitFilters.Terrain | MapBlitFilters.Actors,
|
||||
CVec.Zero,
|
||||
wr,
|
||||
stickToGround);
|
||||
foreach (var renderable in preview)
|
||||
yield return renderable;
|
||||
}
|
||||
|
||||
IEnumerable<IRenderable> IEditorBrush.RenderAnnotations(Actor self, WorldRenderer wr)
|
||||
{
|
||||
var plan = previewPlan ?? tool.Plan;
|
||||
if (plan == null)
|
||||
yield break;
|
||||
|
||||
var mainColor = tool.EditorBlitSource != null ? Color.Cyan : Color.Red;
|
||||
|
||||
var map = worldRenderer.World.Map;
|
||||
var gridType = map.Grid.Type;
|
||||
WPos CornerOfCell(CPos cpos) => CellLayerUtils.CornerToWPos(cpos, gridType);
|
||||
|
||||
var points = plan.Points();
|
||||
for (var i = 1; i < points.Length; i++)
|
||||
{
|
||||
yield return new CircleAnnotationRenderable(
|
||||
CornerOfCell(points[i]), new WDist(128), 1, Color.Yellow, false);
|
||||
yield return new LineAnnotationRenderable(
|
||||
CornerOfCell(points[i - 1]),
|
||||
CornerOfCell(points[i]),
|
||||
1,
|
||||
Color.Yellow,
|
||||
Color.Yellow);
|
||||
}
|
||||
|
||||
for (var i = 1; i < plan.Rallies.Length; i++)
|
||||
{
|
||||
yield return new CircleAnnotationRenderable(
|
||||
CornerOfCell(plan.Rallies[i]), new WDist(512), 1, mainColor, false);
|
||||
yield return new LineAnnotationRenderable(
|
||||
CornerOfCell(plan.Rallies[i - 1]),
|
||||
CornerOfCell(plan.Rallies[i]),
|
||||
1,
|
||||
mainColor,
|
||||
mainColor);
|
||||
}
|
||||
|
||||
if (plan.AutoEnd != Direction.None)
|
||||
yield return new CircleAnnotationRenderable(
|
||||
CornerOfCell(plan.LastPoint) + map.Offset(plan.AutoEnd.ToCVec(), 0) * 768 / 1024,
|
||||
new WDist(256),
|
||||
2,
|
||||
plan.End != Direction.None ? Color.Magenta : Color.Gray,
|
||||
false);
|
||||
|
||||
if (plan.AutoStart != Direction.None)
|
||||
yield return new CircleAnnotationRenderable(
|
||||
CornerOfCell(plan.FirstPoint) - map.Offset(plan.AutoStart.ToCVec(), 0) * 768 / 1024,
|
||||
new WDist(256),
|
||||
2,
|
||||
plan.Start != Direction.None ? Color.Magenta : Color.Gray,
|
||||
true);
|
||||
|
||||
yield return new CircleAnnotationRenderable(
|
||||
CornerOfCell(plan.Rallies[0]), new WDist(512), 1, mainColor, true);
|
||||
}
|
||||
|
||||
public void Tick() { }
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
|
||||
sealed class UpdateTilingPathPlanEditorAction : IEditorAction
|
||||
{
|
||||
[FluentReference]
|
||||
const string StartedPlan = "notification-tiling-path-started";
|
||||
[FluentReference]
|
||||
const string UpdatedPlan = "notification-tiling-path-updated";
|
||||
[FluentReference]
|
||||
const string ResetPlan = "notification-tiling-path-reset";
|
||||
|
||||
public string Text { get; }
|
||||
|
||||
readonly TilingPathTool tool;
|
||||
readonly TilingPathTool.PathPlan oldPlan;
|
||||
readonly TilingPathTool.PathPlan newPlan;
|
||||
|
||||
public UpdateTilingPathPlanEditorAction(
|
||||
TilingPathTool tool,
|
||||
TilingPathTool.PathPlan newPlan)
|
||||
{
|
||||
this.tool = tool;
|
||||
oldPlan = tool.Plan;
|
||||
this.newPlan = newPlan;
|
||||
if (oldPlan == null && newPlan == null)
|
||||
throw new ArgumentException("oldPlan and newPlan cannot both be null");
|
||||
else if (oldPlan == null)
|
||||
Text = FluentProvider.GetMessage(StartedPlan);
|
||||
else if (newPlan == null)
|
||||
Text = FluentProvider.GetMessage(ResetPlan);
|
||||
else
|
||||
Text = FluentProvider.GetMessage(UpdatedPlan);
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
Do();
|
||||
}
|
||||
|
||||
public void Do()
|
||||
{
|
||||
tool.SetPlan(newPlan);
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
tool.SetPlan(oldPlan);
|
||||
}
|
||||
}
|
||||
|
||||
sealed class PaintTilingPathEditorAction : IEditorAction
|
||||
{
|
||||
[FluentReference]
|
||||
const string Painted = "notification-tiling-path-painted";
|
||||
|
||||
public string Text { get; }
|
||||
|
||||
readonly TilingPathTool tool;
|
||||
readonly TilingPathTool.PathPlan plan;
|
||||
readonly EditorBlit editorBlit;
|
||||
|
||||
public PaintTilingPathEditorAction(TilingPathTool tool)
|
||||
{
|
||||
this.tool = tool;
|
||||
plan = tool.Plan;
|
||||
Text = FluentProvider.GetMessage(Painted);
|
||||
|
||||
var world = tool.World;
|
||||
var editorActorLayer = world.WorldActor.Trait<EditorActorLayer>();
|
||||
|
||||
var blitSource = tool.EditorBlitSource.Value;
|
||||
|
||||
editorBlit = new EditorBlit(
|
||||
MapBlitFilters.Terrain | MapBlitFilters.Actors,
|
||||
null,
|
||||
blitSource.CellCoords.TopLeft,
|
||||
world.Map,
|
||||
blitSource,
|
||||
editorActorLayer,
|
||||
false);
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
Do();
|
||||
}
|
||||
|
||||
public void Do()
|
||||
{
|
||||
tool.SetPlan(null);
|
||||
editorBlit.Commit();
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
editorBlit.Revert();
|
||||
tool.SetPlan(plan);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user