#region Copyright & License Information /* * Copyright 2007-2020 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, 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.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 : IEquatable { 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 readonly ActorReference Actor; public string Tooltip { get { return (tooltip == null ? " < " + Info.Name + " >" : tooltip.Name) + "\n" + Owner.Name + " (" + Owner.Faction + ")" + "\nID: " + ID + "\nType: " + Info.Name; } } public string ID { get; set; } public PlayerReference Owner { get; set; } public SubCell SubCell { get; private set; } public bool Selected { get; set; } readonly WorldRenderer worldRenderer; readonly TooltipInfoBase tooltip; IActorPreview[] previews; public EditorActorPreview(WorldRenderer worldRenderer, string id, ActorReference actor, PlayerReference owner) { ID = id; Actor = actor; Owner = owner; this.worldRenderer = worldRenderer; if (!actor.InitDict.Contains()) actor.InitDict.Add(new FactionInit(owner.Faction)); if (!actor.InitDict.Contains()) 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().Value(worldRenderer.World); var ios = Info.TraitInfoOrDefault(); var subCellInit = actor.InitDict.GetOrDefault(); var subCell = subCellInit != null ? subCellInit.Value(worldRenderer.World) : SubCell.Any; if (ios != null) Footprint = ios.OccupiedCells(Info, location, subCell); else { var footprint = new Dictionary() { { location, SubCell.FullCell } }; Footprint = new ReadOnlyDictionary(footprint); } tooltip = Info.TraitInfos().FirstOrDefault(info => info.EnabledByDefault) as TooltipInfoBase ?? Info.TraitInfos().FirstOrDefault(info => info.EnabledByDefault); DescriptiveName = tooltip != null ? tooltip.Name : Info.Name; 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.ScreenBounds(worldRenderer, CenterPosition)); Bounds = r.Union(); SelectionBox = new SelectionBoxAnnotationRenderable(new WPos(CenterPosition.X, CenterPosition.Y, 8192), new Rectangle(Bounds.X, Bounds.Y, Bounds.Width, Bounds.Height), Color.White); } public void Tick() { foreach (var p in previews) p.Tick(); } public IEnumerable Render() { var items = previews.SelectMany(p => p.Render(worldRenderer, CenterPosition)); if (Selected) { var highlight = worldRenderer.Palette("highlight"); var overlay = items.Where(r => !r.IsDecoration) .Select(r => r.WithPalette(highlight)); return items.Concat(overlay); } return items; } public IEnumerable RenderAnnotations() { if (Selected) yield return SelectionBox; } public void ReplaceInit(T init) { var original = Actor.InitDict.GetOrDefault(); if (original != null) Actor.InitDict.Remove(original); Actor.InitDict.Add(init); GeneratePreviews(); } public void RemoveInit() { var original = Actor.InitDict.GetOrDefault(); if (original != null) Actor.InitDict.Remove(original); GeneratePreviews(); } public T Init() { return Actor.InitDict.GetOrDefault(); } public MiniYaml Save() { Func saveInit = init => { var factionInit = init as FactionInit; if (factionInit != null && factionInit.Faction == Owner.Faction) 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()) return init.Get().Value(world); if (init.Contains()) { var cell = init.Get().Value(world); var offset = WVec.Zero; var subCellInit = Actor.InitDict.GetOrDefault(); var subCell = subCellInit != null ? subCellInit.Value(worldRenderer.World) : SubCell.Any; var buildingInfo = Info.TraitInfoOrDefault(); if (buildingInfo != null) offset = buildingInfo.CenterOffset(world); 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.TraitInfos() .SelectMany(rpi => rpi.RenderPreview(init)) .ToArray(); } public ActorReference Export() { return new ActorReference(Actor.Type, Actor.Save().ToDictionary()); } public override string ToString() { return "{0} {1}".F(Info.Name, ID); } public bool Equals(EditorActorPreview other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; return string.Equals(ID, other.ID, StringComparison.OrdinalIgnoreCase); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != GetType()) return false; return Equals((EditorActorPreview)obj); } public override int GetHashCode() { return ID != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(ID) : 0; } } }