Rework decoration renderable traits:
- Removed implicit pip definitions and IPips interface. New decoration traits have been added to render them. Pip types are no longer hardcoded in OpenRA.Game. - Decoration rendering is now managed by SelectionDecorations(Base), which allows us to remove assumptions about the selection box geometry from the decoration traits. - RenderNameTag has been replaced by WithNameTagDecoration, which is an otherwise normal decoration trait. - Unify the configuration and reduce duplication between traits. - Removed hardcoded references to specific selection box renderables. - Remove legacy cruft.
This commit is contained in:
273
OpenRA.Mods.Common/UpdateRules/Rules/AddPipDecorationTraits.cs
Normal file
273
OpenRA.Mods.Common/UpdateRules/Rules/AddPipDecorationTraits.cs
Normal file
@@ -0,0 +1,273 @@
|
||||
#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.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.Mods.Common.UpdateRules.Rules
|
||||
{
|
||||
public class AddPipDecorationTraits : UpdateRule
|
||||
{
|
||||
public override string Name { get { return "Add decoration traits for selection pips."; } }
|
||||
public override string Description
|
||||
{
|
||||
get
|
||||
{
|
||||
return "The AmmoPool, Cargo, Harvester, and StoresResources traits no longer\n" +
|
||||
"automatically add pips to the selection box. New traits WithAmmoPipsDecoration,\n" +
|
||||
"WithCargoPipsDecoration, WithHarvesterPipsDecoration,\n" +
|
||||
"WithResourceStoragePipsDecoration are added to provide the same functionality.\n\n" +
|
||||
"Passenger.PipType has been replaced with CustomPipType, which now references a\n" +
|
||||
"sequence defined in WithCargoDecoration.CustomPipTypeSequences.\n\n" +
|
||||
"ResourceType.PipColor has been removed and resource pip colours are now defined\n" +
|
||||
"in WithHarvesterPipsDecoration.ResourceSequences.";
|
||||
}
|
||||
}
|
||||
|
||||
static readonly Dictionary<string, string> PipReplacements = new Dictionary<string, string>
|
||||
{
|
||||
{ "transparent", "pip-empty" },
|
||||
{ "green", "pip-green" },
|
||||
{ "yellow", "pip-yellow" },
|
||||
{ "red", "pip-red" },
|
||||
{ "gray", "pip-gray" },
|
||||
{ "blue", "pip-blue" },
|
||||
{ "ammo", "pip-ammo" },
|
||||
{ "ammoempty", "pip-ammoempty" },
|
||||
};
|
||||
|
||||
bool customPips;
|
||||
readonly List<string> locations = new List<string>();
|
||||
readonly List<string> cargoPipLocations = new List<string>();
|
||||
readonly HashSet<string> cargoCustomPips = new HashSet<string>();
|
||||
readonly List<string> harvesterPipLocations = new List<string>();
|
||||
readonly Dictionary<string, string> harvesterCustomPips = new Dictionary<string, string>();
|
||||
|
||||
public override IEnumerable<string> AfterUpdate(ModData modData)
|
||||
{
|
||||
if (customPips && locations.Any())
|
||||
yield return "Custom pip Images and Palettes are now defined on the individual With*PipsDecoration traits.\n" +
|
||||
"You should review the following definitions and manually define the Image and Palette properties as required:\n" +
|
||||
UpdateUtils.FormatMessageList(locations);
|
||||
|
||||
if (cargoCustomPips.Any() && cargoPipLocations.Any())
|
||||
yield return "Some passenger types define custom cargo pips. Review the following definitions:\n" +
|
||||
UpdateUtils.FormatMessageList(cargoPipLocations) +
|
||||
"\nand, if required, add the following to the WithCargoPipsDecoration traits:\n" +
|
||||
"CustomPipSequences:\n" + cargoCustomPips.Select(p => "\t{0}: {1}".F(p, PipReplacements[p])).JoinWith("\n");
|
||||
|
||||
if (harvesterCustomPips.Any() && harvesterPipLocations.Any())
|
||||
yield return "Review the following definitions:\n" +
|
||||
UpdateUtils.FormatMessageList(harvesterPipLocations) +
|
||||
"\nand, if required, add the following to the WithHarvesterPipsDecoration traits:\n" +
|
||||
"ResourceSequences:\n" + harvesterCustomPips.Select(kv => "\t{0}: {1}".F(kv.Key, PipReplacements[kv.Value])).JoinWith("\n");
|
||||
|
||||
customPips = false;
|
||||
locations.Clear();
|
||||
cargoPipLocations.Clear();
|
||||
harvesterPipLocations.Clear();
|
||||
}
|
||||
|
||||
public override IEnumerable<string> UpdateActorNode(ModData modData, MiniYamlNode actorNode)
|
||||
{
|
||||
var addNodes = new List<MiniYamlNode>();
|
||||
|
||||
foreach (var selectionDecorations in actorNode.ChildrenMatching("SelectionDecorations"))
|
||||
{
|
||||
customPips |= selectionDecorations.RemoveNodes("Palette") > 0;
|
||||
customPips |= selectionDecorations.RemoveNodes("Image") > 0;
|
||||
}
|
||||
|
||||
foreach (var ammoPool in actorNode.ChildrenMatching("AmmoPool"))
|
||||
{
|
||||
var ammoPips = new MiniYamlNode("WithAmmoPipsDecoration", "");
|
||||
ammoPips.AddNode("Position", "BottomLeft");
|
||||
ammoPips.AddNode("RequiresSelection", "true");
|
||||
|
||||
var pipCountNode = ammoPool.LastChildMatching("PipCount");
|
||||
if (pipCountNode != null)
|
||||
{
|
||||
ammoPool.RemoveNode(pipCountNode);
|
||||
var pipCount = pipCountNode.NodeValue<int>();
|
||||
if (pipCount == 0)
|
||||
{
|
||||
addNodes.Add(new MiniYamlNode("-" + ammoPips.Key, ""));
|
||||
continue;
|
||||
}
|
||||
|
||||
var ammoNode = ammoPool.LastChildMatching("Ammo");
|
||||
var maxAmmo = ammoNode != null ? ammoNode.NodeValue<int>() : 0;
|
||||
if (pipCount != maxAmmo)
|
||||
ammoPips.AddNode("PipCount", pipCount);
|
||||
}
|
||||
|
||||
var pipTypeNode = ammoPool.LastChildMatching("PipType");
|
||||
if (pipTypeNode != null)
|
||||
{
|
||||
ammoPool.RemoveNode(pipTypeNode);
|
||||
|
||||
string sequence;
|
||||
if (PipReplacements.TryGetValue(pipTypeNode.Value.Value.ToLowerInvariant(), out sequence))
|
||||
ammoPips.AddNode("FullSequence", sequence);
|
||||
}
|
||||
|
||||
var pipTypeEmptyNode = ammoPool.LastChildMatching("PipTypeEmpty");
|
||||
if (pipTypeEmptyNode != null)
|
||||
{
|
||||
ammoPool.RemoveNode(pipTypeEmptyNode);
|
||||
|
||||
string sequence;
|
||||
if (PipReplacements.TryGetValue(pipTypeEmptyNode.Value.Value.ToLowerInvariant(), out sequence))
|
||||
ammoPips.AddNode("EmptySequence", sequence);
|
||||
}
|
||||
|
||||
addNodes.Add(ammoPips);
|
||||
locations.Add("{0}: {1} ({2})".F(actorNode.Key, ammoPips.Key, actorNode.Location.Filename));
|
||||
}
|
||||
|
||||
foreach (var cargo in actorNode.ChildrenMatching("Cargo"))
|
||||
{
|
||||
var cargoPips = new MiniYamlNode("WithCargoPipsDecoration", "");
|
||||
cargoPips.AddNode("Position", "BottomLeft");
|
||||
cargoPips.AddNode("RequiresSelection", "true");
|
||||
|
||||
var pipCountNode = cargo.LastChildMatching("PipCount");
|
||||
if (pipCountNode != null)
|
||||
{
|
||||
cargo.RemoveNode(pipCountNode);
|
||||
|
||||
var pipCount = pipCountNode.NodeValue<int>();
|
||||
if (pipCount == 0)
|
||||
{
|
||||
addNodes.Add(new MiniYamlNode("-" + cargoPips.Key, ""));
|
||||
continue;
|
||||
}
|
||||
|
||||
var maxWeightNode = cargo.LastChildMatching("MaxWeight");
|
||||
var maxWeight = maxWeightNode != null ? maxWeightNode.NodeValue<int>() : 0;
|
||||
if (pipCount != maxWeight)
|
||||
cargoPips.AddNode("PipCount", pipCount);
|
||||
}
|
||||
else
|
||||
continue;
|
||||
|
||||
addNodes.Add(cargoPips);
|
||||
locations.Add("{0}: {1} ({2})".F(actorNode.Key, cargoPips.Key, actorNode.Location.Filename));
|
||||
cargoPipLocations.Add("{0} ({1})".F(actorNode.Key, actorNode.Location.Filename));
|
||||
}
|
||||
|
||||
foreach (var passenger in actorNode.ChildrenMatching("Passenger"))
|
||||
{
|
||||
var pipTypeNode = passenger.LastChildMatching("PipType");
|
||||
if (pipTypeNode != null)
|
||||
{
|
||||
pipTypeNode.RenameKey("CustomPipType");
|
||||
pipTypeNode.Value.Value = pipTypeNode.Value.Value.ToLowerInvariant();
|
||||
cargoCustomPips.Add(pipTypeNode.Value.Value);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var harvester in actorNode.ChildrenMatching("Harvester"))
|
||||
{
|
||||
var harvesterPips = new MiniYamlNode("WithHarvesterPipsDecoration", "");
|
||||
harvesterPips.AddNode("Position", "BottomLeft");
|
||||
harvesterPips.AddNode("RequiresSelection", "true");
|
||||
|
||||
// Harvester hardcoded a default PipCount > 0 so we can't use that to determine whether
|
||||
// this is a definition or an override. Resources isn't ideal either, but is better than nothing
|
||||
var resourcesNode = harvester.LastChildMatching("Resources");
|
||||
if (resourcesNode == null)
|
||||
continue;
|
||||
|
||||
var pipCountNode = harvester.LastChildMatching("PipCount");
|
||||
if (pipCountNode != null)
|
||||
{
|
||||
harvester.RemoveNode(pipCountNode);
|
||||
|
||||
var pipCount = pipCountNode.NodeValue<int>();
|
||||
if (pipCount == 0)
|
||||
{
|
||||
addNodes.Add(new MiniYamlNode("-" + harvesterPips.Key, ""));
|
||||
continue;
|
||||
}
|
||||
|
||||
harvesterPips.AddNode("PipCount", pipCount);
|
||||
}
|
||||
else
|
||||
harvesterPips.AddNode("PipCount", 7);
|
||||
|
||||
addNodes.Add(harvesterPips);
|
||||
locations.Add("{0}: {1} ({2})".F(actorNode.Key, harvesterPips.Key, actorNode.Location.Filename));
|
||||
harvesterPipLocations.Add("{0} ({1})".F(actorNode.Key, actorNode.Location.Filename));
|
||||
}
|
||||
|
||||
foreach (var resourceType in actorNode.ChildrenMatching("ResourceType"))
|
||||
{
|
||||
var pipColor = "yellow";
|
||||
var pipCountNode = resourceType.LastChildMatching("PipColor");
|
||||
if (pipCountNode != null)
|
||||
{
|
||||
pipColor = pipCountNode.Value.Value.ToLowerInvariant();
|
||||
resourceType.RemoveNode(pipCountNode);
|
||||
}
|
||||
|
||||
var typeNode = resourceType.LastChildMatching("Type");
|
||||
if (typeNode != null)
|
||||
harvesterCustomPips.Add(typeNode.Value.Value, pipColor);
|
||||
}
|
||||
|
||||
foreach (var storesResources in actorNode.ChildrenMatching("StoresResources"))
|
||||
{
|
||||
var storagePips = new MiniYamlNode("WithResourceStoragePipsDecoration", "");
|
||||
storagePips.AddNode("Position", "BottomLeft");
|
||||
storagePips.AddNode("RequiresSelection", "true");
|
||||
|
||||
var pipCountNode = storesResources.LastChildMatching("PipCount");
|
||||
if (pipCountNode != null)
|
||||
{
|
||||
storesResources.RemoveNode(pipCountNode);
|
||||
var pipCount = pipCountNode.NodeValue<int>();
|
||||
if (pipCount == 0)
|
||||
{
|
||||
addNodes.Add(new MiniYamlNode("-" + storagePips.Key, ""));
|
||||
continue;
|
||||
}
|
||||
|
||||
storagePips.AddNode("PipCount", pipCount);
|
||||
}
|
||||
else
|
||||
continue;
|
||||
|
||||
// Default pip color changed from yellow to green for consistency with other pip traits
|
||||
var pipColorNode = storesResources.LastChildMatching("PipColor");
|
||||
if (pipColorNode != null)
|
||||
{
|
||||
storesResources.RemoveNode(pipColorNode);
|
||||
|
||||
string sequence;
|
||||
var type = pipColorNode.Value.Value.ToLowerInvariant();
|
||||
if (type != "green" && PipReplacements.TryGetValue(type, out sequence))
|
||||
storagePips.AddNode("FullSequence", sequence);
|
||||
}
|
||||
else
|
||||
storagePips.AddNode("FullSequence", PipReplacements["yellow"]);
|
||||
|
||||
addNodes.Add(storagePips);
|
||||
locations.Add("{0}: {1} ({2})".F(actorNode.Key, storagePips.Key, actorNode.Location.Filename));
|
||||
}
|
||||
|
||||
foreach (var addNode in addNodes)
|
||||
actorNode.AddNode(addNode);
|
||||
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
#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.Linq;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.UpdateRules.Rules
|
||||
{
|
||||
public class ModernizeDecorationTraits : UpdateRule
|
||||
{
|
||||
public override string Name { get { return "Modernize SelectionDecorations and With*Decoration traits."; } }
|
||||
public override string Description
|
||||
{
|
||||
get
|
||||
{
|
||||
return "The configuration properties exposed on the SelectionDecorations and With*Decoration\n" +
|
||||
"traits have been reworked. RenderSelectionBars and RenderSelectionBox have been removed from\n" +
|
||||
"SelectionDecorations. The obsolete ZOffset and ScreenOffset has been removed from With*Decoration, and ReferencePoint has\n" +
|
||||
"been replaced by Position which takes a single value (TopLeft, TopRight, BottomLeft, BottomRight, Center, or Top).\n" +
|
||||
"A new Margin property is available to control the decoration offset relative to the edges of the selection box.\n" +
|
||||
"RenderNameTag has been renamed to WithNameTagDecoration and now behaves like a normal decoration trait.\n";
|
||||
}
|
||||
}
|
||||
|
||||
static readonly string[] LegacyDecorationTraits = { "WithDecoration", "WithSpriteControlGroupDecoration", "WithTextControlGroupDecoration", "WithTextDecoration", "WithBuildingRepairDecoration", "InfiltrateForDecoration" };
|
||||
static readonly string[] ModernDecorationTraits = { "WithAmmoPipsDecoration", "WithCargoPipsDecoration", "WithHarvesterPipsDecoration", "WithResourceStoragePipsDecoration", "WithNameTagDecoration" };
|
||||
|
||||
[Flags]
|
||||
public enum LegacyReferencePoints
|
||||
{
|
||||
Center = 0,
|
||||
Top = 1,
|
||||
Bottom = 2,
|
||||
Left = 4,
|
||||
Right = 8,
|
||||
}
|
||||
|
||||
static readonly Dictionary<LegacyReferencePoints, DecorationPosition> PositionMap = new Dictionary<LegacyReferencePoints, DecorationPosition>()
|
||||
{
|
||||
{ LegacyReferencePoints.Center, DecorationPosition.Center },
|
||||
{ LegacyReferencePoints.Top, DecorationPosition.Top },
|
||||
{ LegacyReferencePoints.Top | LegacyReferencePoints.Left, DecorationPosition.TopLeft },
|
||||
{ LegacyReferencePoints.Top | LegacyReferencePoints.Right, DecorationPosition.TopRight },
|
||||
{ LegacyReferencePoints.Bottom | LegacyReferencePoints.Left, DecorationPosition.BottomLeft },
|
||||
{ LegacyReferencePoints.Bottom | LegacyReferencePoints.Right, DecorationPosition.BottomRight }
|
||||
};
|
||||
|
||||
readonly Dictionary<string, List<string>> locations = new Dictionary<string, List<string>>();
|
||||
|
||||
public override IEnumerable<string> AfterUpdate(ModData modData)
|
||||
{
|
||||
if (locations.Any())
|
||||
yield return "The way that decorations are positioned relative to the selection box has changed.\n" +
|
||||
"Review the following definitions and define Margin properties as required:\n" +
|
||||
UpdateUtils.FormatMessageList(locations.Select(
|
||||
kv => kv.Key + ":\n" + UpdateUtils.FormatMessageList(kv.Value)));
|
||||
|
||||
locations.Clear();
|
||||
}
|
||||
|
||||
public override IEnumerable<string> UpdateActorNode(ModData modData, MiniYamlNode actorNode)
|
||||
{
|
||||
var locationKey = "{0} ({1})".F(actorNode.Key, actorNode.Location.Filename);
|
||||
|
||||
foreach (var trait in LegacyDecorationTraits)
|
||||
{
|
||||
foreach (var node in actorNode.ChildrenMatching(trait))
|
||||
{
|
||||
node.RemoveNodes("ZOffset");
|
||||
node.RemoveNodes("ScreenOffset");
|
||||
|
||||
var positionNode = node.LastChildMatching("ReferencePoint");
|
||||
if (positionNode != null)
|
||||
{
|
||||
DecorationPosition value;
|
||||
if (!PositionMap.TryGetValue(positionNode.NodeValue<LegacyReferencePoints>(), out value))
|
||||
value = DecorationPosition.TopLeft;
|
||||
|
||||
if (value != DecorationPosition.TopLeft)
|
||||
{
|
||||
positionNode.RenameKey("Position");
|
||||
positionNode.ReplaceValue(FieldSaver.FormatValue(value));
|
||||
}
|
||||
else
|
||||
node.RemoveNode(positionNode);
|
||||
}
|
||||
|
||||
locations.GetOrAdd(locationKey).Add(node.Key);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var trait in ModernDecorationTraits)
|
||||
foreach (var node in actorNode.ChildrenMatching(trait))
|
||||
locations.GetOrAdd(locationKey).Add(node.Key);
|
||||
|
||||
foreach (var selection in actorNode.ChildrenMatching("SelectionDecorations"))
|
||||
{
|
||||
selection.RemoveNodes("RenderSelectionBars");
|
||||
selection.RemoveNodes("RenderSelectionBox");
|
||||
}
|
||||
|
||||
foreach (var nameTag in actorNode.ChildrenMatching("RenderNameTag"))
|
||||
{
|
||||
nameTag.RenameKey("WithNameTagDecoration");
|
||||
nameTag.AddNode("Position", "Top");
|
||||
nameTag.AddNode("UsePlayerColor", "true");
|
||||
locations.GetOrAdd(locationKey).Add(nameTag.Key);
|
||||
}
|
||||
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user