#region Copyright & License Information /* * Copyright 2007-2021 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.Collections.ObjectModel; using System.IO; using System.Linq; using OpenRA.Graphics; using OpenRA.Mods.Common.Graphics; using OpenRA.Mods.Common.Traits.Radar; 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 string Tooltip => (tooltip == null ? " < " + Info.Name + " >" : tooltip.Name) + "\n" + Owner.Name + " (" + Owner.Faction + ")" + "\nID: " + ID + "\nType: " + Info.Name; public string Type => reference.Type; public string ID { get; set; } public PlayerReference Owner { get; set; } public SubCell SubCell { get; private set; } public bool Selected { get; set; } public readonly Color RadarColor; readonly WorldRenderer worldRenderer; readonly TooltipInfoBase tooltip; IActorPreview[] previews; readonly ActorReference reference; readonly Dictionary editorData = new Dictionary(); public EditorActorPreview(WorldRenderer worldRenderer, string id, ActorReference reference, PlayerReference owner) { ID = id; this.reference = reference; Owner = owner; this.worldRenderer = worldRenderer; if (!reference.Contains()) reference.Add(new FactionInit(owner.Faction)); if (!reference.Contains()) reference.Add(new OwnerInit(owner.Name)); var world = worldRenderer.World; 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 radarColorInfo = Info.TraitInfoOrDefault(); RadarColor = radarColorInfo == null ? owner.Color : radarColorInfo.GetColorFromTerrain(world); Footprint = ios?.OccupiedCells(Info, location, subCell) ?? new Dictionary() { { location, SubCell.FullCell } }; 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 overlay = items.Where(r => !r.IsDecoration && r is IModifyableRenderable) .Select(r => { var mr = (IModifyableRenderable)r; return mr.WithTint(float3.Ones, mr.TintModifiers | TintModifiers.ReplaceColor).WithAlpha(0.5f); }); return items.Concat(overlay); } return items; } public IEnumerable RenderAnnotations() { if (Selected) yield return SelectionBox; } public void AddedToEditor() { foreach (var notify in Info.TraitInfos()) editorData[notify] = notify.AddedToEditor(this, worldRenderer.World); } public void RemovedFromEditor() { foreach (var kv in editorData) kv.Key.RemovedFromEditor(this, worldRenderer.World, kv.Value); } public void AddInit(T init) where T : ActorInit { reference.Add(init); GeneratePreviews(); } public void ReplaceInit(T init, TraitInfo info) where T : ActorInit { var original = GetInitOrDefault(info); if (original != null) reference.Remove(original); reference.Add(init); GeneratePreviews(); } public void RemoveInit(TraitInfo info) where T : ActorInit { var original = GetInitOrDefault(info); if (original != null) reference.Remove(original); GeneratePreviews(); } public int RemoveInits() where T : ActorInit { var removed = reference.RemoveAll(); GeneratePreviews(); return removed; } public T GetInitOrDefault(TraitInfo info) where T : ActorInit { return reference.GetOrDefault(info); } public IEnumerable GetInits() where T : ActorInit { return reference.GetAll(); } public T GetInitOrDefault() where T : ActorInit, ISingleInstanceInit { return reference.GetOrDefault(); } public void ReplaceInit(T init) where T : ActorInit, ISingleInstanceInit { var original = reference.GetOrDefault(); if (original != null) reference.Remove(original); reference.Add(init); GeneratePreviews(); } public void RemoveInit() where T : ActorInit, ISingleInstanceInit { reference.RemoveAll(); GeneratePreviews(); } public MiniYaml Save() { Func saveInit = init => { if (init is FactionInit factionInit && factionInit.Value == Owner.Faction) return false; if (init is HealthInit healthInit && healthInit.Value == 100) return false; // TODO: Other default values will need to be filtered // here after we have built a properties panel return true; }; return reference.Save(saveInit); } WPos PreviewPosition(World world, ActorReference actor) { var centerPositionInit = actor.GetOrDefault(); if (centerPositionInit != null) return centerPositionInit.Value; var locationInit = actor.GetOrDefault(); if (locationInit != null) { var cell = locationInit.Value; var offset = WVec.Zero; var subCellInit = reference.GetOrDefault(); var subCell = subCellInit != null ? subCellInit.Value : 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 {ID} must define Location or CenterPosition"); } void GeneratePreviews() { var init = new ActorPreviewInitializer(reference, worldRenderer); previews = Info.TraitInfos() .SelectMany(rpi => rpi.RenderPreview(init)) .ToArray(); } public ActorReference Export() { return reference.Clone(); } public override string ToString() { return $"{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; } } }