Add the world components of the new editor.

This commit is contained in:
Paul Chote
2015-04-06 14:57:13 +01:00
parent 1f024a8695
commit d211fe9fe1
15 changed files with 833 additions and 117 deletions

View File

@@ -0,0 +1,268 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Mods.Common.Graphics;
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Desc("Required for the map editor to work. Attach this to the world actor.")]
public class EditorActorLayerInfo : ITraitInfo
{
[Desc("Size of partition bins (world pixels)")]
public readonly int BinSize = 250;
public object Create(ActorInitializer init) { return new EditorActorLayer(init.Self, this); }
}
public class EditorActorLayer : IWorldLoaded, ITickRender, IRender, IRadarSignature, ICreatePlayers
{
readonly EditorActorLayerInfo info;
readonly Dictionary<string, EditorActorPreview> previews = new Dictionary<string, EditorActorPreview>();
readonly Dictionary<CPos, List<EditorActorPreview>> cellMap = new Dictionary<CPos, List<EditorActorPreview>>();
SpatiallyPartitioned<EditorActorPreview> screenMap;
WorldRenderer worldRenderer;
public MapPlayers Players { get; private set; }
public EditorActorLayer(Actor self, EditorActorLayerInfo info)
{
this.info = info;
}
public void CreatePlayers(World w)
{
if (w.Type != WorldType.Editor)
return;
Players = new MapPlayers(w.Map.PlayerDefinitions);
}
public void WorldLoaded(World world, WorldRenderer wr)
{
if (world.Type != WorldType.Editor)
return;
worldRenderer = wr;
foreach (var pr in Players.Players.Values)
wr.UpdatePalettesForPlayer(pr.Name, pr.Color, false);
var ts = Game.ModData.Manifest.TileSize;
var width = world.Map.MapSize.X * ts.Width;
var height = world.Map.MapSize.Y * ts.Height;
screenMap = new SpatiallyPartitioned<EditorActorPreview>(width, height, info.BinSize);
foreach (var kv in world.Map.ActorDefinitions)
Add(kv.Key, new ActorReference(kv.Value.Value, kv.Value.ToDictionary()), true);
// Update neighbours in one pass
foreach (var p in previews.Values)
UpdateNeighbours(p.Footprint);
}
public void TickRender(WorldRenderer wr, Actor self)
{
if (wr.World.Type != WorldType.Editor)
return;
foreach (var kv in previews.Values)
kv.Tick();
}
static readonly IEnumerable<IRenderable> NoRenderables = Enumerable.Empty<IRenderable>();
public virtual IEnumerable<IRenderable> Render(Actor self, WorldRenderer wr)
{
if (wr.World.Type != WorldType.Editor)
return NoRenderables;
return PreviewsInBox(wr.Viewport.TopLeft, wr.Viewport.BottomRight)
.SelectMany(p => p.Render());
}
public EditorActorPreview Add(ActorReference reference) { return Add(NextActorName(), reference); }
EditorActorPreview Add(string id, ActorReference reference, bool initialSetup = false)
{
var owner = Players.Players[reference.InitDict.Get<OwnerInit>().PlayerName];
var preview = new EditorActorPreview(worldRenderer, id, reference, owner);
previews.Add(id, preview);
screenMap.Add(preview, preview.Bounds);
foreach (var kv in preview.Footprint)
{
List<EditorActorPreview> list;
if (!cellMap.TryGetValue(kv.Key, out list))
{
list = new List<EditorActorPreview>();
cellMap.Add(kv.Key, list);
}
list.Add(preview);
}
if (!initialSetup)
{
UpdateNeighbours(preview.Footprint);
if (reference.Type == "mpspawn")
SyncMultiplayerCount();
}
return preview;
}
public void Remove(EditorActorPreview preview)
{
previews.Remove(preview.ID);
screenMap.Remove(preview);
foreach (var kv in preview.Footprint)
{
List<EditorActorPreview> list;
if (!cellMap.TryGetValue(kv.Key, out list))
continue;
list.Remove(preview);
if (!list.Any())
cellMap.Remove(kv.Key);
}
UpdateNeighbours(preview.Footprint);
if (preview.Info.Name == "mpspawn")
SyncMultiplayerCount();
}
void SyncMultiplayerCount()
{
var newCount = previews.Count(p => p.Value.Info.Name == "mpspawn");
var mp = Players.Players.Where(p => p.Key.StartsWith("Multi")).ToList();
foreach (var kv in mp)
{
var name = kv.Key;
var index = int.Parse(name.Substring(5));
if (index >= newCount)
{
Players.Players.Remove(name);
worldRenderer.World.Players.RemoveAll(pp => pp.InternalName == name);
}
}
for (var index = 0; index < newCount; index++)
{
if (Players.Players.ContainsKey("Multi{0}".F(index)))
continue;
var pr = new PlayerReference
{
Name = "Multi{0}".F(index),
Race = "Random",
Playable = true,
Enemies = new[] { "Creeps" }
};
Players.Players.Add(pr.Name, pr);
worldRenderer.UpdatePalettesForPlayer(pr.Name, pr.Color, true);
}
}
void UpdateNeighbours(IReadOnlyDictionary<CPos, SubCell> footprint)
{
// Include actors inside the footprint too
var cells = OpenRA.Traits.Util.ExpandFootprint(footprint.Keys, true);
foreach (var p in cells.SelectMany(c => PreviewsAt(c)))
p.ReplaceInit(new RuntimeNeighbourInit(NeighbouringPreviews(p.Footprint)));
}
Dictionary<CPos, string[]> NeighbouringPreviews(IReadOnlyDictionary<CPos, SubCell> footprint)
{
var cells = OpenRA.Traits.Util.ExpandFootprint(footprint.Keys, true).Except(footprint.Keys);
return cells.ToDictionary(c => c, c => PreviewsAt(c).Select(p => p.Info.Name).ToArray());
}
public IEnumerable<EditorActorPreview> PreviewsInBox(int2 a, int2 b)
{
return screenMap.InBox(Rectangle.FromLTRB(Math.Min(a.X, b.X), Math.Min(a.Y, b.Y), Math.Max(a.X, b.X), Math.Max(a.Y, b.Y)));
}
public IEnumerable<EditorActorPreview> PreviewsInBox(Rectangle r)
{
return screenMap.InBox(r);
}
public IEnumerable<EditorActorPreview> PreviewsAt(CPos cell)
{
List<EditorActorPreview> list;
if (cellMap.TryGetValue(cell, out list))
return list;
return Enumerable.Empty<EditorActorPreview>();
}
public SubCell FreeSubCellAt(CPos cell)
{
var map = worldRenderer.World.Map;
var previews = PreviewsAt(cell).ToList();
if (!previews.Any())
return map.DefaultSubCell;
for (var i = (int)SubCell.First; i < map.SubCellOffsets.Length; i++)
if (!previews.Any(p => p.Footprint[cell] == (SubCell)i))
return (SubCell)i;
return SubCell.Invalid;
}
public IEnumerable<EditorActorPreview> PreviewsAt(int2 worldPx)
{
return screenMap.At(worldPx);
}
string NextActorName()
{
var id = previews.Count();
var possibleName = "Actor" + id.ToString();
while (previews.ContainsKey(possibleName))
{
id++;
possibleName = "Actor" + id.ToString();
}
return possibleName;
}
public List<MiniYamlNode> Save()
{
var nodes = new List<MiniYamlNode>();
foreach (var a in previews)
nodes.Add(new MiniYamlNode(a.Key, a.Value.Save()));
return nodes;
}
public IEnumerable<Pair<CPos, Color>> RadarSignatureCells(Actor self)
{
return cellMap.SelectMany(c => c.Value.Select(p => Pair.New(c.Key, p.Owner.Color.RGB)));
}
}
}

View File

@@ -0,0 +1,164 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Mods.Common.Graphics;
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
public class EditorActorPreview
{
public readonly string Tooltip;
public readonly string ID;
public readonly ActorInfo Info;
public readonly PlayerReference Owner;
public readonly WPos CenterPosition;
public readonly IReadOnlyDictionary<CPos, SubCell> Footprint;
public readonly Rectangle Bounds;
public SubCell SubCell { get; private set; }
readonly ActorReference actor;
readonly WorldRenderer worldRenderer;
IActorPreview[] previews;
public EditorActorPreview(WorldRenderer worldRenderer, string id, ActorReference actor, PlayerReference owner)
{
ID = id;
this.actor = actor;
this.Owner = owner;
this.worldRenderer = worldRenderer;
if (!actor.InitDict.Contains<RaceInit>())
actor.InitDict.Add(new RaceInit(owner.Race));
if (!actor.InitDict.Contains<OwnerInit>())
actor.InitDict.Add(new OwnerInit(owner.Name));
var world = worldRenderer.World;
if (!world.Map.Rules.Actors.TryGetValue(actor.Type.ToLowerInvariant(), out Info))
throw new InvalidDataException("Actor {0} of unknown type {1}".F(id, actor.Type.ToLowerInvariant()));
CenterPosition = PreviewPosition(world, actor.InitDict);
var location = actor.InitDict.Get<LocationInit>().Value(worldRenderer.World);
var ios = Info.Traits.GetOrDefault<IOccupySpaceInfo>();
var subCellInit = actor.InitDict.GetOrDefault<SubCellInit>();
var subCell = subCellInit != null ? subCellInit.Value(worldRenderer.World) : SubCell.Any;
if (ios != null)
Footprint = ios.OccupiedCells(Info, location, subCell);
else
{
var footprint = new Dictionary<CPos, SubCell>() { { location, SubCell.FullCell } };
Footprint = new ReadOnlyDictionary<CPos, SubCell>(footprint);
}
var tooltipInfo = Info.Traits.GetOrDefault<ITooltipInfo>();
Tooltip = "{0} ({1})".F(tooltipInfo != null ? tooltipInfo.TooltipForPlayerStance(Stance.None) : Info.Name, ID);
GeneratePreviews();
// Bounds are fixed from the initial render.
// If this is a problem, then we may need to fetch the area from somewhere else
var r = previews
.SelectMany(p => p.Render(worldRenderer, CenterPosition))
.Select(rr => rr.PrepareRender(worldRenderer));
if (r.Any())
{
Bounds = r.First().ScreenBounds(worldRenderer);
foreach (var rr in r.Skip(1))
Bounds = Rectangle.Union(Bounds, rr.ScreenBounds(worldRenderer));
}
}
public void Tick()
{
foreach (var p in previews)
p.Tick();
}
public IEnumerable<IRenderable> Render()
{
return previews.SelectMany(p => p.Render(worldRenderer, CenterPosition));
}
public void ReplaceInit<T>(T init)
{
var original = actor.InitDict.GetOrDefault<T>();
if (original != null)
actor.InitDict.Remove(original);
actor.InitDict.Add(init);
GeneratePreviews();
}
public T Init<T>()
{
return actor.InitDict.GetOrDefault<T>();
}
public MiniYaml Save()
{
Func<object, bool> saveInit = init =>
{
var race = init as RaceInit;
if (race != null && race.Race == Owner.Race)
return false;
// TODO: Other default values will need to be filtered
// here after we have built a properties panel
return true;
};
return actor.Save(saveInit);
}
WPos PreviewPosition(World world, TypeDictionary init)
{
if (init.Contains<CenterPositionInit>())
return init.Get<CenterPositionInit>().Value(world);
if (init.Contains<LocationInit>())
{
var cell = init.Get<LocationInit>().Value(world);
var offset = WVec.Zero;
var subCellInit = actor.InitDict.GetOrDefault<SubCellInit>();
var subCell = subCellInit != null ? subCellInit.Value(worldRenderer.World) : SubCell.Any;
var buildingInfo = Info.Traits.GetOrDefault<BuildingInfo>();
if (buildingInfo != null)
offset = FootprintUtils.CenterOffset(world, buildingInfo);
return world.Map.CenterOfSubCell(cell, subCell) + offset;
}
else
throw new InvalidDataException("Actor {0} must define Location or CenterPosition".F(ID));
}
void GeneratePreviews()
{
var init = new ActorPreviewInitializer(Info, worldRenderer, actor.InitDict);
previews = Info.Traits.WithInterface<IRenderActorPreviewInfo>()
.SelectMany(rpi => rpi.RenderPreview(init))
.ToArray();
}
}
}

View File

@@ -0,0 +1,148 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Mods.Common.Graphics;
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
using CellContents = ResourceLayer.CellContents;
[Desc("Required for the map editor to work. Attach this to the world actor.")]
public class EditorResourceLayerInfo : ITraitInfo, Requires<ResourceTypeInfo>
{
public virtual object Create(ActorInitializer init) { return new EditorResourceLayer(init.Self); }
}
public class EditorResourceLayer : IWorldLoaded, IRenderOverlay
{
protected readonly Map Map;
protected readonly TileSet Tileset;
protected readonly Dictionary<int, ResourceType> Resources;
protected readonly CellLayer<CellContents> Tiles;
protected readonly HashSet<CPos> Dirty = new HashSet<CPos>();
public EditorResourceLayer(Actor self)
{
if (self.World.Type != WorldType.Editor)
return;
Map = self.World.Map;
Tileset = self.World.TileSet;
Tiles = new CellLayer<CellContents>(Map);
Resources = self.TraitsImplementing<ResourceType>()
.ToDictionary(r => r.Info.ResourceType, r => r);
Map.MapResources.Value.CellEntryChanged += UpdateCell;
}
public void WorldLoaded(World w, WorldRenderer wr)
{
if (w.Type != WorldType.Editor)
return;
foreach (var cell in Map.Cells)
UpdateCell(cell);
}
public void UpdateCell(CPos cell)
{
var uv = cell.ToMPos(Map);
var tile = Map.MapResources.Value[uv];
ResourceType type;
if (Resources.TryGetValue(tile.Type, out type))
{
Tiles[uv] = new CellContents
{
Type = type,
Variant = ChooseRandomVariant(type),
};
Map.CustomTerrain[uv] = Tileset.GetTerrainIndex(type.Info.TerrainType);
}
else
{
Tiles[uv] = CellContents.Empty;
Map.CustomTerrain[uv] = byte.MaxValue;
}
// Ingame resource rendering is a giant hack (#6395),
// so we must also touch all the neighbouring tiles
Dirty.Add(cell);
foreach (var d in CVec.Directions)
{
var c = cell + d;
if (Map.Contains(c))
Dirty.Add(c);
}
}
protected virtual string ChooseRandomVariant(ResourceType t)
{
return t.Variants.Keys.Random(Game.CosmeticRandom);
}
public virtual CellContents UpdateDirtyTile(CPos c)
{
var t = Tiles[c];
// Empty tile
if (t.Type == null)
{
t.Sprite = null;
return t;
}
// Set density based on the number of neighboring resources
var adjacent = 0;
var type = t.Type;
for (var u = -1; u < 2; u++)
for (var v = -1; v < 2; v++)
if (Map.MapResources.Value[c + new CVec(u, v)].Type == type.Info.ResourceType)
adjacent++;
t.Density = Math.Max(int2.Lerp(0, type.Info.MaxDensity, adjacent, 9), 1);
var sprites = type.Variants[t.Variant];
var frame = int2.Lerp(0, sprites.Length - 1, t.Density - 1, type.Info.MaxDensity);
t.Sprite = sprites[frame];
return t;
}
public void Render(WorldRenderer wr)
{
if (wr.World.Type != WorldType.Editor)
return;
foreach (var c in Dirty)
Tiles[c] = UpdateDirtyTile(c);
Dirty.Clear();
foreach (var uv in wr.Viewport.VisibleCells.MapCoords)
{
var t = Tiles[uv];
if (t.Sprite != null)
new SpriteRenderable(t.Sprite, wr.World.Map.CenterOfCell(uv.ToCPos(Map)),
WVec.Zero, -511, t.Type.Palette, 1f, true).Render(wr);
}
}
}
}

View File

@@ -248,6 +248,7 @@ namespace OpenRA.Mods.Common.Traits
public struct CellContents
{
public static readonly CellContents Empty = new CellContents();
public ResourceType Type;
public int Density;
public string Variant;