#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 previews = new Dictionary(); readonly Dictionary> cellMap = new Dictionary>(); SpatiallyPartitioned 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); var worldOwner = Players.Players.Select(kvp => kvp.Value).First(p => !p.Playable && p.OwnsWorld); w.WorldActor.Owner = new Player(w, null, worldOwner); } 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 = world.Map.Grid.TileSize; var width = world.Map.MapSize.X * ts.Width; var height = world.Map.MapSize.Y * ts.Height; screenMap = new SpatiallyPartitioned(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 NoRenderables = Enumerable.Empty(); public virtual IEnumerable 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().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 list; if (!cellMap.TryGetValue(kv.Key, out list)) { list = new List(); 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 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); } 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), Faction = "Random", Playable = true, Enemies = new[] { "Creeps" } }; Players.Players.Add(pr.Name, pr); worldRenderer.UpdatePalettesForPlayer(pr.Name, pr.Color, true); } var creeps = Players.Players.Keys.FirstOrDefault(p => p == "Creeps"); if (!string.IsNullOrEmpty(creeps)) Players.Players[creeps].Enemies = Players.Players.Keys.Where(p => !Players.Players[p].NonCombatant).ToArray(); } void UpdateNeighbours(IReadOnlyDictionary 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 NeighbouringPreviews(IReadOnlyDictionary 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 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 PreviewsInBox(Rectangle r) { return screenMap.InBox(r); } public IEnumerable PreviewsAt(CPos cell) { List list; if (cellMap.TryGetValue(cell, out list)) return list; return Enumerable.Empty(); } 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 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 Save() { var nodes = new List(); foreach (var a in previews) nodes.Add(new MiniYamlNode(a.Key, a.Value.Save())); return nodes; } public IEnumerable> RadarSignatureCells(Actor self) { return cellMap.SelectMany(c => c.Value.Select(p => Pair.New(c.Key, p.Owner.Color.RGB))); } } }