Files
OpenRA/OpenRA.Editor/Surface.cs
2015-03-19 17:20:34 +00:00

573 lines
16 KiB
C#

#region Copyright & License Information
/*
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Windows.Forms;
using OpenRA.Graphics;
using OpenRA.Primitives;
using OpenRA.Traits;
using SGraphics = System.Drawing.Graphics;
namespace OpenRA.Editor
{
static class ActorReferenceExts
{
public static CPos Location(this ActorReference ar)
{
return (CPos)ar.InitDict.Get<LocationInit>().Value(null);
}
public static void DrawStringContrast(this SGraphics g, Font f, string s, int x, int y, Brush fg, Brush bg)
{
g.DrawString(s, f, bg, x - 1, y - 1);
g.DrawString(s, f, bg, x + 1, y - 1);
g.DrawString(s, f, bg, x - 1, y + 1);
g.DrawString(s, f, bg, x + 1, y + 1);
g.DrawString(s, f, fg, x, y);
}
}
class Surface : Control
{
public Map Map { get; private set; }
public TileSet TileSet { get; private set; }
public TileSetRenderer TileSetRenderer { get; private set; }
public IPalette Palette { get; private set; }
public IPalette PlayerPalette { get; private set; }
public int2 Offset;
public int2 GetOffset() { return Offset; }
public float Zoom = 1.0f;
ITool currentTool;
public bool IsPanning;
public bool IsErasing;
public bool ShowActorNames;
public bool ShowGrid;
public bool ShowRuler;
public bool IsPaste { get { return TileSelection != null && ResourceSelection != null; } }
public TerrainTile[,] TileSelection;
public ResourceTile[,] ResourceSelection;
public CPos SelectionStart;
public CPos SelectionEnd;
public string NewActorOwner;
public event Action AfterChange = () => { };
public event Action<string> MousePositionChanged = _ => { };
public event Action<KeyValuePair<string, ActorReference>> ActorDoubleClicked = _ => { };
Dictionary<string, ActorTemplate> actorTemplates = new Dictionary<string, ActorTemplate>();
public Dictionary<int, ResourceTemplate> ResourceTemplates = new Dictionary<int, ResourceTemplate>();
static readonly Font MarkerFont = new Font(FontFamily.GenericSansSerif, 12.0f, FontStyle.Regular);
static readonly SolidBrush TextBrush = new SolidBrush(Color.Red);
public Keys GetModifiers() { return ModifierKeys; }
public void Bind(Map m, TileSet ts, TileSetRenderer tsr, IPalette p, IPalette pp)
{
Map = m;
TileSet = ts;
TileSetRenderer = tsr;
Palette = p;
PlayerPalette = pp;
playerPalettes = null;
Chunks.Clear();
currentTool = null;
}
public void SetTool(ITool tool) { currentTool = tool; ClearSelection(); }
public void BindActorTemplates(IEnumerable<ActorTemplate> templates)
{
actorTemplates = templates.ToDictionary(a => a.Info.Name.ToLowerInvariant());
}
public void BindResourceTemplates(IEnumerable<ResourceTemplate> templates)
{
ResourceTemplates = templates.ToDictionary(a => a.Info.ResourceType);
}
public Dictionary<int2, Bitmap> Chunks = new Dictionary<int2, Bitmap>();
public Surface()
{
BackColor = Color.Black;
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
SetStyle(ControlStyles.ResizeRedraw, true);
UpdateStyles();
}
static readonly Pen SelectionPen = new Pen(Color.Blue);
static readonly Pen PastePen = new Pen(Color.Green);
static readonly Pen CordonPen = new Pen(Color.Red);
int2 mousePos;
public void Scroll(int2 dx)
{
Offset -= dx;
Invalidate();
}
protected override void OnDoubleClick(EventArgs e)
{
base.OnDoubleClick(e);
var x = Map.Actors.Value.FirstOrDefault(a => a.Value.Location() == GetBrushLocation());
if (x.Key != null)
ActorDoubleClicked(x);
}
protected override void OnMouseWheel(MouseEventArgs e)
{
base.OnMouseWheel(e);
if (Map == null) return;
Zoom *= e.Delta > 0 ? 4.0f / 3.0f : .75f;
Invalidate();
}
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
this.Parent.Focus();
Invalidate();
}
protected override void OnMouseEnter(EventArgs e)
{
base.OnMouseLeave(e);
this.Focus();
Invalidate();
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (Map == null) return;
var oldMousePos = mousePos;
mousePos = new int2(e.Location);
MousePositionChanged(GetBrushLocation().ToString());
if (e.Button == MouseButtons.Middle || (e.Button != MouseButtons.None && IsPanning))
Scroll(oldMousePos - mousePos);
else
{
if (e.Button == MouseButtons.Right || (IsErasing && e.Button == MouseButtons.Left))
Erase();
if (e.Button == MouseButtons.Left && !IsErasing)
Draw();
Invalidate();
}
}
void Erase()
{
// Crash preventing
var brushLocation = GetBrushLocation();
if (Map == null || brushLocation.X >= Map.MapSize.X ||
brushLocation.Y >= Map.MapSize.Y ||
brushLocation.X < 0 ||
brushLocation.Y < 0)
return;
currentTool = null;
var key = Map.Actors.Value.FirstOrDefault(a => a.Value.Location() == brushLocation);
if (key.Key != null) Map.Actors.Value.Remove(key.Key);
if (Map.MapResources.Value[brushLocation].Type != 0)
{
Map.MapResources.Value[brushLocation] = new ResourceTile(0, 0);
var ch = new int2(brushLocation.X / ChunkSize, brushLocation.Y / ChunkSize);
if (Chunks.ContainsKey(ch))
{
Chunks[ch].Dispose();
Chunks.Remove(ch);
}
}
AfterChange();
ClearSelection();
}
void Draw()
{
if (currentTool != null)
{
currentTool.Apply(this);
AfterChange();
}
else if (IsPaste)
PasteSelection();
else
SelectionEnd = GetBrushLocationBR();
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
if (Map == null) return;
if (!IsPanning)
{
if (e.Button == MouseButtons.Right) Erase();
if (e.Button == MouseButtons.Left && !IsErasing)
{
Draw();
if (!IsPaste)
SelectionStart = SelectionEnd = GetBrushLocation();
}
}
Invalidate();
}
public const int ChunkSize = 8; // 8x8 chunks ==> 192x192 bitmaps.
Bitmap RenderChunk(int u, int v)
{
var bitmap = new Bitmap(ChunkSize * TileSetRenderer.TileSize, ChunkSize * TileSetRenderer.TileSize);
var data = bitmap.LockBits(bitmap.Bounds(),
ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
unsafe
{
var p = (int*)data.Scan0.ToPointer();
var stride = data.Stride >> 2;
for (var i = 0; i < ChunkSize; i++)
for (var j = 0; j < ChunkSize; j++)
{
var ui = u * ChunkSize + i;
var vj = v * ChunkSize + j;
var uv = new MPos(ui, vj);
var tr = Map.MapTiles.Value[uv];
var tile = TileSetRenderer.Data(tr.Type);
if (tile == null)
continue;
var index = (tr.Index < tile.Length) ? tr.Index : (byte)0;
var rawImage = tile[index];
for (var x = 0; x < TileSetRenderer.TileSize; x++)
for (var y = 0; y < TileSetRenderer.TileSize; y++)
p[(j * TileSetRenderer.TileSize + y) * stride + i * TileSetRenderer.TileSize + x] =
Palette.GetColor(rawImage[x + TileSetRenderer.TileSize * y]).ToArgb();
if (Map.MapResources.Value[uv].Type != 0)
{
var resourceImage = ResourceTemplates[Map.MapResources.Value[uv].Type].Bitmap;
var srcdata = resourceImage.LockBits(resourceImage.Bounds(),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
var q = (int*)srcdata.Scan0.ToPointer();
var srcstride = srcdata.Stride >> 2;
for (var x = 0; x < TileSetRenderer.TileSize; x++)
for (var y = 0; y < TileSetRenderer.TileSize; y++)
{
var c = q[y * srcstride + x];
if ((c & 0xff000000) != 0) /* quick & dirty, i cbf doing real alpha */
p[(j * TileSetRenderer.TileSize + y) * stride + i * TileSetRenderer.TileSize + x] = c;
}
resourceImage.UnlockBits(srcdata);
}
}
}
bitmap.UnlockBits(data);
if (ShowGrid)
{
using (var g = SGraphics.FromImage(bitmap))
{
var ts = Game.ModData.Manifest.TileSize;
var rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
ControlPaint.DrawGrid(g, rect, new Size(2, ts.Height), Color.DarkRed);
ControlPaint.DrawGrid(g, rect, new Size(ts.Width, 2), Color.DarkRed);
ControlPaint.DrawGrid(g, rect, new Size(ts.Width, ts.Height), Color.Red);
}
}
return bitmap;
}
public CPos GetBrushLocation()
{
var vX = (int)Math.Floor((mousePos.X - Offset.X) / Zoom);
var vY = (int)Math.Floor((mousePos.Y - Offset.Y) / Zoom);
return new CPos(vX / TileSetRenderer.TileSize, vY / TileSetRenderer.TileSize);
}
public CPos GetBrushLocationBR()
{
var vX = (int)Math.Floor((mousePos.X - Offset.X) / Zoom);
var vY = (int)Math.Floor((mousePos.Y - Offset.Y) / Zoom);
return new CPos(
(vX + TileSetRenderer.TileSize - 1) / TileSetRenderer.TileSize,
(vY + TileSetRenderer.TileSize - 1) / TileSetRenderer.TileSize);
}
public void DrawActor(SGraphics g, CPos p, ActorTemplate t, ColorPalette cp)
{
var centered = t.Appearance == null || !t.Appearance.RelativeToTopLeft;
var actorPalette = cp;
if (t.Appearance != null && t.Appearance.UseTerrainPalette)
actorPalette = Palette.AsSystemPalette();
DrawImage(g, t.Bitmap, p, centered, actorPalette);
}
float2 GetDrawPosition(CPos location, Bitmap bmp, bool centered)
{
float offsetX = centered ? bmp.Width / 2 - TileSetRenderer.TileSize / 2 : 0;
var drawX = TileSetRenderer.TileSize * location.X * Zoom + Offset.X - offsetX;
float offsetY = centered ? bmp.Height / 2 - TileSetRenderer.TileSize / 2 : 0;
var drawY = TileSetRenderer.TileSize * location.Y * Zoom + Offset.Y - offsetY;
return new float2(drawX, drawY);
}
public void DrawImage(SGraphics g, Bitmap bmp, CPos location, bool centered, ColorPalette cp)
{
var drawPos = GetDrawPosition(location, bmp, centered);
var sourceRect = new RectangleF(0, 0, bmp.Width, bmp.Height);
var destRect = new RectangleF(drawPos.X, drawPos.Y, bmp.Width * Zoom, bmp.Height * Zoom);
var restorePalette = bmp.Palette;
if (cp != null) bmp.Palette = cp;
g.DrawImage(bmp, destRect, sourceRect, GraphicsUnit.Pixel);
if (cp != null) bmp.Palette = restorePalette;
}
void DrawActorBorder(SGraphics g, CPos p, ActorTemplate t)
{
var centered = t.Appearance == null || !t.Appearance.RelativeToTopLeft;
var drawPos = GetDrawPosition(p, t.Bitmap, centered);
g.DrawRectangle(CordonPen,
drawPos.X, drawPos.Y,
t.Bitmap.Width * Zoom, t.Bitmap.Height * Zoom);
}
ColorPalette GetPaletteForPlayerInner(string name)
{
var pr = Map.Players[name];
var pcpi = Program.Rules.Actors["player"].Traits.Get<PlayerColorPaletteInfo>();
var remap = new PlayerColorRemap(pcpi.RemapIndex, pr.Color, pcpi.Ramp);
return new ImmutablePalette(PlayerPalette, remap).AsSystemPalette();
}
Cache<string, ColorPalette> playerPalettes;
public ColorPalette GetPaletteForPlayer(string player)
{
if (playerPalettes == null)
playerPalettes = new Cache<string, ColorPalette>(GetPaletteForPlayerInner);
return playerPalettes[player];
}
ColorPalette GetPaletteForActor(ActorReference ar)
{
var ownerInit = ar.InitDict.GetOrDefault<OwnerInit>();
if (ownerInit == null)
return null;
return GetPaletteForPlayer(ownerInit.PlayerName);
}
protected override void OnPaint(PaintEventArgs e)
{
if (Map == null) return;
if (TileSet == null) return;
for (var u = 0; u < Map.MapSize.X; u += ChunkSize)
for (var v = 0; v < Map.MapSize.Y; v += ChunkSize)
{
var x = new int2(u / ChunkSize, v / ChunkSize);
if (!Chunks.ContainsKey(x)) Chunks[x] = RenderChunk(u / ChunkSize, v / ChunkSize);
var bmp = Chunks[x];
var drawX = TileSetRenderer.TileSize * (float)ChunkSize * (float)x.X * Zoom + Offset.X;
var drawY = TileSetRenderer.TileSize * (float)ChunkSize * (float)x.Y * Zoom + Offset.Y;
var sourceRect = new RectangleF(0, 0, bmp.Width, bmp.Height);
var destRect = new RectangleF(drawX, drawY, bmp.Width * Zoom, bmp.Height * Zoom);
e.Graphics.DrawImage(bmp, destRect, sourceRect, GraphicsUnit.Pixel);
}
e.Graphics.DrawRectangle(CordonPen,
Map.Bounds.Left * TileSetRenderer.TileSize * Zoom + Offset.X,
Map.Bounds.Top * TileSetRenderer.TileSize * Zoom + Offset.Y,
Map.Bounds.Width * TileSetRenderer.TileSize * Zoom,
Map.Bounds.Height * TileSetRenderer.TileSize * Zoom);
e.Graphics.DrawRectangle(SelectionPen,
(SelectionStart.X * TileSetRenderer.TileSize * Zoom) + Offset.X,
(SelectionStart.Y * TileSetRenderer.TileSize * Zoom) + Offset.Y,
(SelectionEnd - SelectionStart).X * TileSetRenderer.TileSize * Zoom,
(SelectionEnd - SelectionStart).Y * TileSetRenderer.TileSize * Zoom);
if (IsPaste)
{
var loc = GetBrushLocation();
var width = Math.Abs((SelectionStart - SelectionEnd).X);
var height = Math.Abs((SelectionStart - SelectionEnd).Y);
e.Graphics.DrawRectangle(PastePen,
(loc.X * TileSetRenderer.TileSize * Zoom) + Offset.X,
(loc.Y * TileSetRenderer.TileSize * Zoom) + Offset.Y,
width * (TileSetRenderer.TileSize * Zoom),
height * (TileSetRenderer.TileSize * Zoom));
}
foreach (var ar in Map.Actors.Value)
{
if (actorTemplates.ContainsKey(ar.Value.Type))
DrawActor(e.Graphics, ar.Value.Location(), actorTemplates[ar.Value.Type],
GetPaletteForActor(ar.Value));
else
Console.WriteLine("Warning: Unknown or excluded actor: {0}", ar.Value.Type);
}
if (ShowActorNames)
foreach (var ar in Map.Actors.Value)
if (!ar.Key.StartsWith("Actor")) // if it has a custom name
e.Graphics.DrawStringContrast(Font, ar.Key,
(int)(ar.Value.Location().X * TileSetRenderer.TileSize * Zoom + Offset.X),
(int)(ar.Value.Location().Y * TileSetRenderer.TileSize * Zoom + Offset.Y),
Brushes.White,
Brushes.Black);
if (ShowRuler && Zoom > 0.2)
{
for (var i = Map.Bounds.Left; i <= Map.Bounds.Right; i += 8)
{
if (i % 8 == 0)
{
var point = new PointF(i * TileSetRenderer.TileSize * Zoom + Offset.X, (Map.Bounds.Top - 8) * TileSetRenderer.TileSize * Zoom + Offset.Y);
e.Graphics.DrawString((i - Map.Bounds.Left).ToString(), MarkerFont, TextBrush, point);
}
}
for (var i = Map.Bounds.Top; i <= Map.Bounds.Bottom; i += 8)
{
if (i % 8 == 0)
{
var point = new PointF((Map.Bounds.Left - 8) * TileSetRenderer.TileSize * Zoom + Offset.X, i * TileSetRenderer.TileSize * Zoom + Offset.Y);
e.Graphics.DrawString((i - Map.Bounds.Left).ToString(), MarkerFont, TextBrush, point);
}
}
}
if (currentTool != null)
currentTool.Preview(this, e.Graphics);
if (currentTool == null)
{
var x = Map.Actors.Value.FirstOrDefault(a => a.Value.Location() == GetBrushLocation());
if (x.Key != null && actorTemplates.ContainsKey(x.Value.Type))
DrawActorBorder(e.Graphics, x.Value.Location(), actorTemplates[x.Value.Type]);
}
}
public void CopySelection()
{
// Grab tiles and resources within selection (doesn't do actors)
var start = SelectionStart;
var end = SelectionEnd;
if (start == end) return;
var width = Math.Abs((start - end).X);
var height = Math.Abs((start - end).Y);
TileSelection = new TerrainTile[width, height];
ResourceSelection = new ResourceTile[width, height];
for (var x = 0; x < width; x++)
{
for (var y = 0; y < height; y++)
{
// TODO: crash prevention
var cell = new CPos(start.X + x, start.Y + y);
TileSelection[x, y] = Map.MapTiles.Value[cell];
ResourceSelection[x, y] = Map.MapResources.Value[cell];
}
}
}
void PasteSelection()
{
var loc = GetBrushLocation();
var width = Math.Abs((SelectionStart - SelectionEnd).X);
var height = Math.Abs((SelectionStart - SelectionEnd).Y);
for (var x = 0; x < width; x++)
{
for (var y = 0; y < height; y++)
{
var mapX = loc.X + x;
var mapY = loc.Y + y;
var cell = new CPos(mapX, mapY);
// TODO: crash prevention for outside of bounds
Map.MapTiles.Value[cell] = TileSelection[x, y];
Map.MapResources.Value[cell] = ResourceSelection[x, y];
var ch = new int2(mapX / ChunkSize, mapY / ChunkSize);
if (Chunks.ContainsKey(ch))
{
Chunks[ch].Dispose();
Chunks.Remove(ch);
}
}
}
AfterChange();
}
void ClearSelection()
{
SelectionStart = CPos.Zero;
SelectionEnd = CPos.Zero;
TileSelection = null;
ResourceSelection = null;
}
}
}