From 7b82d85b27427f8f3eefa614dc2795c06798fd82 Mon Sep 17 00:00:00 2001 From: Wojciech Walaszek Date: Wed, 13 Dec 2023 19:10:18 +0100 Subject: [PATCH] Editor actor move --- .../EditorBrushes/EditorDefaultBrush.cs | 93 ++++++++++++++++++- .../Traits/World/EditorActorPreview.cs | 91 ++++++++++-------- .../Traits/World/EditorCursorLayer.cs | 16 ++++ mods/common/languages/en.ftl | 1 + 4 files changed, 160 insertions(+), 41 deletions(-) diff --git a/OpenRA.Mods.Common/EditorBrushes/EditorDefaultBrush.cs b/OpenRA.Mods.Common/EditorBrushes/EditorDefaultBrush.cs index 0518095a9d..612babc24b 100644 --- a/OpenRA.Mods.Common/EditorBrushes/EditorDefaultBrush.cs +++ b/OpenRA.Mods.Common/EditorBrushes/EditorDefaultBrush.cs @@ -43,6 +43,7 @@ namespace OpenRA.Mods.Common.Widgets readonly EditorActorLayer editorLayer; readonly EditorActionManager editorActionManager; readonly IResourceLayer resourceLayer; + readonly EditorCursorLayer cursorLayer; public CellRegion CurrentDragBounds => selectionBounds ?? Selection.Area; @@ -53,6 +54,8 @@ namespace OpenRA.Mods.Common.Widgets int2? selectionStartLocation; CPos? selectionStartCell; int2 worldPixel; + bool draggingActor; + MoveActorAction moveAction; public EditorDefaultBrush(EditorViewportControllerWidget editorWidget, WorldRenderer wr) { @@ -63,6 +66,7 @@ namespace OpenRA.Mods.Common.Widgets editorLayer = world.WorldActor.Trait(); editorActionManager = world.WorldActor.Trait(); resourceLayer = world.WorldActor.TraitOrDefault(); + cursorLayer = world.WorldActor.Trait(); } long CalculateActorSelectionPriority(EditorActorPreview actor) @@ -126,16 +130,44 @@ namespace OpenRA.Mods.Common.Widgets 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)) + { + editorWidget.SetTooltip(null); + var cellViewPx = worldRenderer.Viewport.WorldToViewPx(worldRenderer.ScreenPosition(world.Map.CenterOfCell(cell))); + var pixelOffset = cellViewPx - mi.Location; + var cellOffset = underCursor.Location - cell; + moveAction = new MoveActorAction(underCursor, cursorLayer, worldRenderer, pixelOffset, cellOffset); + draggingActor = true; + return false; + } + else if (mi.Event == MouseInputEvent.Up && draggingActor) + { + editorWidget.SetTooltip(null); + draggingActor = false; + editorActionManager.Add(moveAction); + moveAction = null; + return false; + } + else if (mi.Event == MouseInputEvent.Move && draggingActor) + { + editorWidget.SetTooltip(null); + moveAction.Move(mi.Location); + return false; + } + } + // Selection box drag. - if (selectionStartLocation != null && + 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 width = bottomRight.X - topLeft.X; - var height = bottomRight.Y - topLeft.Y; var gridType = worldRenderer.World.Map.Grid.Type; // We've dragged enough to capture more than one cell, make a selection box. @@ -211,7 +243,7 @@ namespace OpenRA.Mods.Common.Widgets editorWidget.SetTooltip(null); // Delete actor. - if (underCursor != null && underCursor != Selection.Actor) + if (underCursor != null && underCursor != Selection.Actor && !draggingActor) editorActionManager.Add(new RemoveActorAction(editorLayer, underCursor)); // Or delete resource if found under cursor. @@ -368,6 +400,59 @@ namespace OpenRA.Mods.Common.Widgets } } + sealed class MoveActorAction : IEditorAction + { + [TranslationReference("id", "x1", "y1", "x2", "y2")] + const string MovedActor = "notification-moved-actor"; + + public string Text { get; private set; } + + readonly EditorActorPreview actor; + readonly EditorCursorLayer layer; + readonly WorldRenderer worldRenderer; + readonly int2 pixelOffset; + readonly CVec cellOffset; + readonly CPos from; + + CPos to; + + public MoveActorAction( + EditorActorPreview actor, + EditorCursorLayer layer, + WorldRenderer worldRenderer, + int2 pixelOffset, + CVec cellOffset) + { + this.actor = actor; + this.layer = layer; + this.worldRenderer = worldRenderer; + this.pixelOffset = pixelOffset; + this.cellOffset = cellOffset; + + from = actor.Location; + } + + public void Execute() { } + + public void Do() + { + layer.MoveActor(actor, to); + } + + public void Undo() + { + layer.MoveActor(actor, from); + } + + public void Move(int2 pixelTo) + { + to = worldRenderer.Viewport.ViewToWorld(pixelTo + pixelOffset) + cellOffset; + layer.MoveActor(actor, to); + + Text = TranslationProvider.GetString(MovedActor, Translation.Arguments("id", actor.ID, "x1", from.X, "y1", from.Y, "x2", to.X, "y2", to.Y)); + } + } + sealed class RemoveResourceAction : IEditorAction { [TranslationReference("type")] diff --git a/OpenRA.Mods.Common/Traits/World/EditorActorPreview.cs b/OpenRA.Mods.Common/Traits/World/EditorActorPreview.cs index 6f198e6bfb..f5ac254280 100644 --- a/OpenRA.Mods.Common/Traits/World/EditorActorPreview.cs +++ b/OpenRA.Mods.Common/Traits/World/EditorActorPreview.cs @@ -25,10 +25,6 @@ namespace OpenRA.Mods.Common.Traits { public readonly string DescriptiveName; public readonly ActorInfo Info; - public readonly WPos CenterPosition; - public readonly IReadOnlyDictionary Footprint; - public readonly Rectangle Bounds; - public readonly SelectionBoxAnnotationRenderable SelectionBox; public string Tooltip => (tooltip == null ? " < " + Info.Name + " >" : TranslationProvider.GetString(tooltip.Name)) + "\n" + Owner.Name + " (" + Owner.Faction + ")" @@ -38,17 +34,22 @@ namespace OpenRA.Mods.Common.Traits public string ID { get; set; } public PlayerReference Owner { get; set; } - public SubCell SubCell { get; } + public WPos CenterPosition { get; set; } + public IReadOnlyDictionary Footprint { get; private set; } + public Rectangle Bounds { get; private set; } public bool Selected { get; set; } public Color RadarColor { get; private set; } - readonly RadarColorFromTerrainInfo terrainRadarColorInfo; + public CPos Location { get; private set; } + readonly RadarColorFromTerrainInfo terrainRadarColorInfo; readonly WorldRenderer worldRenderer; readonly TooltipInfoBase tooltip; - IActorPreview[] previews; readonly ActorReference reference; - readonly Action onCellEntryChanged; readonly Dictionary editorData = new(); + readonly Action onCellEntryChanged; + + SelectionBoxAnnotationRenderable selectionBox; + IActorPreview[] previews; public EditorActorPreview(WorldRenderer worldRenderer, string id, ActorReference reference, PlayerReference owner) { @@ -67,41 +68,58 @@ namespace OpenRA.Mods.Common.Traits if (!world.Map.Rules.Actors.TryGetValue(reference.Type.ToLowerInvariant(), out Info)) throw new InvalidDataException($"Actor {id} of unknown type {reference.Type.ToLowerInvariant()}"); - CenterPosition = PreviewPosition(world, reference); - - var location = reference.Get().Value; - var ios = Info.TraitInfoOrDefault(); - - var subCellInit = reference.GetOrDefault(); - var subCell = subCellInit != null ? subCellInit.Value : SubCell.Any; - - var occupiedCells = ios?.OccupiedCells(Info, location, subCell); - if (occupiedCells == null || occupiedCells.Count == 0) - Footprint = new Dictionary() { { location, SubCell.FullCell } }; - else - Footprint = occupiedCells; + UpdateFromCellChange(); + GenerateFootprint(); tooltip = Info.TraitInfos().FirstOrDefault(info => info.EnabledByDefault) as TooltipInfoBase ?? Info.TraitInfos().FirstOrDefault(info => info.EnabledByDefault); DescriptiveName = tooltip != null ? tooltip.Name : Info.Name; - GeneratePreviews(); - terrainRadarColorInfo = Info.TraitInfoOrDefault(); UpdateRadarColor(); - // Bounds are fixed from the initial render. - // If this is a problem, then we may need to fetch the area from somewhere else + // TODO: updating all actors on the map is not very efficient. + onCellEntryChanged = _ => UpdateFromCellChange(); + } + + void UpdateFromCellChange() + { + CenterPosition = PreviewPosition(worldRenderer.World, reference); + GeneratePreviews(); + GenerateBounds(); + } + + void GenerateBounds() + { var r = previews.SelectMany(p => p.ScreenBounds(worldRenderer, CenterPosition)); Bounds = r.Union(); - SelectionBox = new SelectionBoxAnnotationRenderable(new WPos(CenterPosition.X, CenterPosition.Y, 8192), + selectionBox = new SelectionBoxAnnotationRenderable(new WPos(CenterPosition.X, CenterPosition.Y, 8192), new Rectangle(Bounds.X, Bounds.Y, Bounds.Width, Bounds.Height), Color.White); + } - // TODO: updating all actors on the map is not very efficient. - onCellEntryChanged = _ => GeneratePreviews(); + void GenerateFootprint() + { + Location = reference.Get().Value; + var ios = Info.TraitInfoOrDefault(); + var subCellInit = reference.GetOrDefault(); + var subCell = subCellInit != null ? subCellInit.Value : SubCell.Any; + + var occupiedCells = ios?.OccupiedCells(Info, Location, subCell); + if (occupiedCells == null || occupiedCells.Count == 0) + Footprint = new Dictionary() { { Location, SubCell.FullCell } }; + else + Footprint = occupiedCells; + } + + void GeneratePreviews() + { + var init = new ActorPreviewInitializer(reference, worldRenderer); + previews = Info.TraitInfos() + .SelectMany(rpi => rpi.RenderPreview(init)) + .ToArray(); } public void Tick() @@ -131,7 +149,14 @@ namespace OpenRA.Mods.Common.Traits public IEnumerable RenderAnnotations() { if (Selected) - yield return SelectionBox; + yield return selectionBox; + } + + public void UpdateFromMove() + { + CenterPosition = PreviewPosition(worldRenderer.World, reference); + GenerateFootprint(); + GenerateBounds(); } public void AddedToEditor() @@ -258,14 +283,6 @@ namespace OpenRA.Mods.Common.Traits throw new InvalidDataException($"Actor {ID} must define Location or CenterPosition"); } - void GeneratePreviews() - { - var init = new ActorPreviewInitializer(reference, worldRenderer); - previews = Info.TraitInfos() - .SelectMany(rpi => rpi.RenderPreview(init)) - .ToArray(); - } - void UpdateRadarColor() { RadarColor = terrainRadarColorInfo == null ? Owner.Color : terrainRadarColorInfo.GetColorFromTerrain(worldRenderer.World); diff --git a/OpenRA.Mods.Common/Traits/World/EditorCursorLayer.cs b/OpenRA.Mods.Common/Traits/World/EditorCursorLayer.cs index 4ab901d23c..0020ac89b4 100644 --- a/OpenRA.Mods.Common/Traits/World/EditorCursorLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/EditorCursorLayer.cs @@ -189,6 +189,22 @@ namespace OpenRA.Mods.Common.Traits return ++CurrentToken; } + public void MoveActor(EditorActorPreview preview, CPos location) + { + editorLayer.Remove(preview); + preview.ReplaceInit(new LocationInit(location)); + var ios = preview.Info.TraitInfoOrDefault(); + if (ios != null && ios.SharesCell) + { + var actorSubCell = editorLayer.FreeSubCellAt(location); + if (actorSubCell != SubCell.Invalid) + preview.ReplaceInit(new SubCellInit(actorSubCell)); + } + + preview.UpdateFromMove(); + editorLayer.Add(preview); + } + public int SetTerrainTemplate(WorldRenderer wr, TerrainTemplateInfo template) { terrainOrResourceCell = wr.Viewport.ViewToWorld(wr.Viewport.WorldToViewPx(Viewport.LastMousePos)); diff --git a/mods/common/languages/en.ftl b/mods/common/languages/en.ftl index 6608076e3f..8040df9d3c 100644 --- a/mods/common/languages/en.ftl +++ b/mods/common/languages/en.ftl @@ -810,6 +810,7 @@ notification-selected-actor = Selected actor { $id } notification-cleared-selection = Cleared selection notification-removed-actor = Removed { $name } ({ $id }) notification-removed-resource = Removed { $type } +notification-moved-actor = Moved { $id } from { $x1 },{ $y1 } to { $x2 },{ $y2 } ## EditorResourceBrush notification-added-resource =