Files
OpenRA/OpenRA.Mods.Common/Traits/World/EditorActorPreview.cs
2024-11-03 16:52:47 +02:00

337 lines
9.2 KiB
C#

#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* 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.Mods.Common.Traits.Radar;
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
public class EditorActorPreview : IEquatable<EditorActorPreview>
{
public readonly string DescriptiveName;
public readonly ActorInfo Info;
public string Tooltip =>
(tooltip == null ? " < " + Info.Name + " >" : FluentProvider.GetMessage(tooltip.Name)) + "\n" + Owner.Name + " (" + Owner.Faction + ")"
+ "\nID: " + ID + "\nType: " + Info.Name;
public string Type => reference.Type;
public string ID { get; }
public PlayerReference Owner { get; set; }
public WPos CenterPosition { get; set; }
public IReadOnlyDictionary<CPos, SubCell> Footprint { get; private set; }
public Rectangle Bounds { get; private set; }
public bool Selected { get; set; }
public Color RadarColor { get; private set; }
public CPos Location { get; private set; }
readonly RadarColorFromTerrainInfo terrainRadarColorInfo;
readonly WorldRenderer worldRenderer;
readonly TooltipInfoBase tooltip;
readonly ActorReference reference;
readonly Dictionary<INotifyEditorPlacementInfo, object> editorData = new();
readonly Action<CPos> onCellEntryChanged;
SelectionBoxAnnotationRenderable selectionBox;
IActorPreview[] previews;
public EditorActorPreview(WorldRenderer worldRenderer, string id, ActorReference reference, PlayerReference owner)
{
ID = id;
this.reference = reference;
Owner = owner;
this.worldRenderer = worldRenderer;
if (!reference.Contains<FactionInit>())
reference.Add(new FactionInit(owner.Faction));
if (!reference.Contains<OwnerInit>())
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()}");
GenerateFootprint();
UpdateFromCellChange(null);
tooltip = Info.TraitInfos<EditorOnlyTooltipInfo>().FirstOrDefault(info => info.EnabledByDefault) as TooltipInfoBase
?? Info.TraitInfos<TooltipInfo>().FirstOrDefault(info => info.EnabledByDefault);
DescriptiveName = tooltip != null ? tooltip.Name : Info.Name;
terrainRadarColorInfo = Info.TraitInfoOrDefault<RadarColorFromTerrainInfo>();
UpdateRadarColor();
onCellEntryChanged = cell => UpdateFromCellChange(cell);
}
public EditorActorPreview WithId(string id)
{
return new EditorActorPreview(worldRenderer, id, reference.Clone(), Owner);
}
void UpdateFromCellChange(CPos? cellChanged)
{
if (cellChanged != null && !Footprint.ContainsKey(cellChanged.Value))
return;
CenterPosition = PreviewPosition(worldRenderer.World, reference);
GeneratePreviews();
GenerateBounds();
}
void GenerateBounds()
{
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);
}
void GenerateFootprint()
{
Location = reference.Get<LocationInit>().Value;
var ios = Info.TraitInfoOrDefault<IOccupySpaceInfo>();
var subCellInit = reference.GetOrDefault<SubCellInit>();
var subCell = subCellInit != null ? subCellInit.Value : SubCell.Any;
var occupiedCells = ios?.OccupiedCells(Info, Location, subCell);
if (occupiedCells == null || occupiedCells.Count == 0)
Footprint = new Dictionary<CPos, SubCell>() { { Location, SubCell.FullCell } };
else
Footprint = occupiedCells;
}
void GeneratePreviews()
{
var init = new ActorPreviewInitializer(reference, worldRenderer);
previews = Info.TraitInfos<IRenderActorPreviewInfo>()
.SelectMany(rpi => rpi.RenderPreview(init))
.ToArray();
}
public void Tick()
{
foreach (var p in previews)
p.Tick();
}
public IEnumerable<IRenderable> 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<IRenderable> RenderAnnotations()
{
if (Selected)
yield return selectionBox;
}
public void UpdateFromMove()
{
CenterPosition = PreviewPosition(worldRenderer.World, reference);
GenerateFootprint();
GenerateBounds();
}
public void AddedToEditor()
{
foreach (var notify in Info.TraitInfos<INotifyEditorPlacementInfo>())
editorData[notify] = notify.AddedToEditor(this, worldRenderer.World);
worldRenderer.World.Map.Height.CellEntryChanged += onCellEntryChanged;
worldRenderer.World.Map.Ramp.CellEntryChanged += onCellEntryChanged;
}
public void RemovedFromEditor()
{
foreach (var kv in editorData)
kv.Key.RemovedFromEditor(this, worldRenderer.World, kv.Value);
worldRenderer.World.Map.Height.CellEntryChanged -= onCellEntryChanged;
worldRenderer.World.Map.Ramp.CellEntryChanged -= onCellEntryChanged;
}
public void AddInit<T>(T init) where T : ActorInit
{
reference.Add(init);
GeneratePreviews();
}
public void ReplaceInit<T>(T init, TraitInfo info) where T : ActorInit
{
var original = GetInitOrDefault<T>(info);
if (original != null)
reference.Remove(original);
reference.Add(init);
GeneratePreviews();
}
public void RemoveInit<T>(TraitInfo info) where T : ActorInit
{
var original = GetInitOrDefault<T>(info);
if (original != null)
reference.Remove(original);
GeneratePreviews();
}
public int RemoveInits<T>() where T : ActorInit
{
var removed = reference.RemoveAll<T>();
GeneratePreviews();
return removed;
}
public T GetInitOrDefault<T>(TraitInfo info) where T : ActorInit
{
return reference.GetOrDefault<T>(info);
}
public IReadOnlyCollection<T> GetInits<T>() where T : ActorInit
{
return reference.GetAll<T>();
}
public T GetInitOrDefault<T>() where T : ActorInit, ISingleInstanceInit
{
return reference.GetOrDefault<T>();
}
public void ReplaceInit<T>(T init) where T : ActorInit, ISingleInstanceInit
{
var original = reference.GetOrDefault<T>();
if (original != null)
reference.Remove(original);
reference.Add(init);
GeneratePreviews();
UpdateRadarColor();
}
public void RemoveInit<T>() where T : ActorInit, ISingleInstanceInit
{
reference.RemoveAll<T>();
GeneratePreviews();
}
public MiniYaml Save()
{
bool SaveInit(ActorInit 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<CenterPositionInit>();
if (centerPositionInit != null)
return centerPositionInit.Value;
var locationInit = actor.GetOrDefault<LocationInit>();
if (locationInit != null)
{
var cell = locationInit.Value;
var offset = WVec.Zero;
var subCellInit = reference.GetOrDefault<SubCellInit>();
var subCell = subCellInit != null ? subCellInit.Value : SubCell.Any;
var buildingInfo = Info.TraitInfoOrDefault<BuildingInfo>();
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 UpdateRadarColor()
{
RadarColor = terrainRadarColorInfo == null ? Owner.Color : terrainRadarColorInfo.GetColorFromTerrain(worldRenderer.World);
}
public ActorReference Export()
{
return reference.Clone();
}
public override string ToString()
{
return $"{Info.Name} {ID}";
}
public bool Equals(EditorActorPreview other)
{
if (other is null)
return false;
if (ReferenceEquals(this, other))
return true;
return string.Equals(ID, other.ID, StringComparison.OrdinalIgnoreCase);
}
public override bool Equals(object obj)
{
if (obj is null)
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;
}
}
}