Files
OpenRA/OpenRA.Utility/UpgradeRules.cs
UberWaffe c972b39687 Custom Warheads refactor
Changes included:

Warhead code split out of weapon code and refactored.
Warhead functionality now split into several classes, each handling one effect/impact.

Additional custom warheads can now be defined and called via yaml.
Custom warheads inherit the abstract class Warhead,
which provides target check functions.

Custom warheads have to define their own impact functions,
and can also define their own replacement for check
functions.
2014-08-03 17:10:44 +02:00

664 lines
22 KiB
C#

#region Copyright & License Information
/*
* Copyright 2007-2014 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.IO;
using System.Linq;
namespace OpenRA.Utility
{
public static class UpgradeRules
{
static void ConvertFloatToRange(ref string input)
{
var value = float.Parse(input);
var cells = (int)value;
var subcells = (int)(1024 * value) - 1024 * cells;
input = "{0}c{1}".F(cells, subcells);
}
static void ConvertPxToRange(ref string input)
{
ConvertPxToRange(ref input, 1, 1);
}
static void ConvertPxToRange(ref string input, int scaleMult, int scaleDiv)
{
var value = Exts.ParseIntegerInvariant(input);
var ts = Game.modData.Manifest.TileSize;
var world = value * 1024 * scaleMult / (scaleDiv * ts.Height);
var cells = world / 1024;
var subcells = world - 1024 * cells;
input = cells != 0 ? "{0}c{1}".F(cells, subcells) : subcells.ToString();
}
static void ConvertAngle(ref string input)
{
var value = float.Parse(input);
input = WAngle.ArcTan((int)(value * 4 * 1024), 1024).ToString();
}
static void ConvertInt2ToWVec(ref string input)
{
var offset = FieldLoader.GetValue<int2>("(value)", input);
var ts = Game.modData.Manifest.TileSize;
var world = new WVec(offset.X * 1024 / ts.Width, offset.Y * 1024 / ts.Height, 0);
input = world.ToString();
}
static void UpgradeActorRules(int engineVersion, ref List<MiniYamlNode> nodes, MiniYamlNode parent, int depth)
{
var parentKey = parent != null ? parent.Key.Split('@').First() : null;
foreach (var node in nodes)
{
// Weapon definitions were converted to world coordinates
if (engineVersion < 20131226)
{
if (depth == 2 && parentKey == "Exit" && node.Key == "SpawnOffset")
ConvertInt2ToWVec(ref node.Value.Value);
if (depth == 2 && (parentKey == "Aircraft" || parentKey == "Helicopter" || parentKey == "Plane"))
{
if (node.Key == "CruiseAltitude")
ConvertPxToRange(ref node.Value.Value);
if (node.Key == "Speed")
ConvertPxToRange(ref node.Value.Value, 7, 32);
}
if (depth == 2 && parentKey == "Mobile" && node.Key == "Speed")
ConvertPxToRange(ref node.Value.Value, 1, 3);
if (depth == 2 && parentKey == "Health" && node.Key == "Radius")
ConvertPxToRange(ref node.Value.Value);
}
// CrateDrop was replaced with CrateSpawner
if (engineVersion < 20131231)
{
if (depth == 1 && parentKey == "World")
{
if (node.Key == "CrateDrop")
node.Key = "CrateSpawner";
if (node.Key == "-CrateDrop")
node.Key = "-CrateSpawner";
}
}
// AttackTesla was replaced with AttackCharge
if (engineVersion < 20140307)
{
if (depth == 1)
{
if (node.Key == "AttackTesla")
node.Key = "AttackCharge";
if (node.Key == "-AttackTesla")
node.Key = "-AttackCharge";
}
}
// AttackMove was generalized to support all moveable actor types
if (engineVersion < 20140116)
{
if (depth == 1 && node.Key == "AttackMove")
node.Value.Nodes.RemoveAll(n => n.Key == "JustMove");
}
// UnloadFacing was removed from Cargo
if (engineVersion < 20140212)
{
if (depth == 1 && node.Key == "Cargo")
node.Value.Nodes.RemoveAll(n => n.Key == "UnloadFacing");
}
// RevealShroud was updated to use world units.
if (engineVersion < 20140220)
{
if (depth == 2 && parentKey == "RevealsShroud" && node.Key == "Range")
ConvertFloatToRange(ref node.Value.Value);
if (depth == 2 && parentKey == "CreatesShroud" && node.Key == "Range")
ConvertFloatToRange(ref node.Value.Value);
}
// Waypoint was renamed to Immobile
if (engineVersion < 20140312)
{
if (depth == 1 && node.Key == "Waypoint")
node.Key = "Immobile";
}
// Spy was renamed to Disguise
if (engineVersion < 20140314)
{
if (depth == 1 && node.Key == "Spy")
node.Key = "Disguise";
if (depth == 1 && node.Key == "SpyToolTip")
node.Key = "DisguiseToolTip";
if (depth == 1 && node.Key == "RenderSpy")
node.Key = "RenderDisguise";
}
// IOccupySpace was removed from Mine
if (engineVersion < 20140320)
{
if (depth == 0 && node.Value.Nodes.Any(n => n.Key == "Mine"))
node.Value.Nodes.Add(new MiniYamlNode("Immobile", new MiniYaml("", new List<MiniYamlNode>() { new MiniYamlNode("OccupiesSpace", "true") })));
else
foreach (var i in nodes.Where(n => n.Key == "Immobile"))
if (!i.Value.Nodes.Any(n => n.Key == "OccupiesSpace"))
i.Value.Nodes.Add(new MiniYamlNode("OccupiesSpace", "false"));
}
// Armaments and muzzleflashes were reworked to support garrisoning
if (engineVersion < 20140321)
{
if (depth == 0)
{
var muzzles = node.Value.Nodes.Where(n => n.Key.StartsWith("WithMuzzleFlash"));
var armaments = node.Value.Nodes.Where(n => n.Key.StartsWith("Armament"));
// Shift muzzle flash definitions to Armament
foreach (var m in muzzles)
{
var muzzleArmNode = m.Value.Nodes.SingleOrDefault(n => n.Key == "Armament");
var muzzleSequenceNode = m.Value.Nodes.SingleOrDefault(n => n.Key == "Sequence");
var muzzleSplitFacingsNode = m.Value.Nodes.SingleOrDefault(n => n.Key == "SplitFacings");
var muzzleFacingsCountNode = m.Value.Nodes.SingleOrDefault(n => n.Key == "FacingCount");
var muzzleArmName = muzzleArmNode != null ? muzzleArmNode.Value.Value.Trim() : "primary";
var muzzleSequence = muzzleSequenceNode != null ? muzzleSequenceNode.Value.Value.Trim() : "muzzle";
var muzzleSplitFacings = muzzleSplitFacingsNode != null ? FieldLoader.GetValue<bool>("SplitFacings", muzzleSplitFacingsNode.Value.Value) : false;
var muzzleFacingsCount = muzzleFacingsCountNode != null ? FieldLoader.GetValue<int>("FacingsCount", muzzleFacingsCountNode.Value.Value) : 8;
foreach (var a in armaments)
{
var armNameNode = m.Value.Nodes.SingleOrDefault(n => n.Key == "Name");
var armName = armNameNode != null ? armNameNode.Value.Value.Trim() : "primary";
if (muzzleArmName == armName)
{
a.Value.Nodes.Add(new MiniYamlNode("MuzzleSequence", muzzleSequence));
if (muzzleSplitFacings)
a.Value.Nodes.Add(new MiniYamlNode("MuzzleSplitFacings", muzzleFacingsCount.ToString()));
}
}
}
foreach (var m in muzzles.ToList().Skip(1))
node.Value.Nodes.Remove(m);
}
// Remove all but the first muzzle flash definition
if (depth == 1 && node.Key.StartsWith("WithMuzzleFlash"))
{
node.Key = "WithMuzzleFlash";
node.Value.Nodes.RemoveAll(n => n.Key == "Armament");
node.Value.Nodes.RemoveAll(n => n.Key == "Sequence");
}
}
// "disabled" palette overlay has been moved into it's own DisabledOverlay trait
if (engineVersion < 20140305)
{
if (node.Value.Nodes.Any(n => n.Key.StartsWith("RequiresPower"))
&& !node.Value.Nodes.Any(n => n.Key.StartsWith("DisabledOverlay")))
{
node.Value.Nodes.Add(new MiniYamlNode("DisabledOverlay", new MiniYaml("")));
}
}
// ChronoshiftDeploy was replaced with PortableChrono
if (engineVersion < 20140321)
{
if (depth == 1 && node.Key == "ChronoshiftDeploy")
node.Key = "PortableChrono";
if (depth == 2 && parentKey == "PortableChrono" && node.Key == "JumpDistance")
node.Key = "MaxDistance";
}
// Added new Lua API
if (engineVersion < 20140421)
{
if (depth == 0 && node.Value.Nodes.Any(n => n.Key == "LuaScriptEvents"))
node.Value.Nodes.Add(new MiniYamlNode("ScriptTriggers", ""));
}
if (engineVersion < 20140517)
{
if (depth == 0)
node.Value.Nodes.RemoveAll(n => n.Key == "TeslaInstantKills");
}
if (engineVersion < 20140615)
{
if (depth == 1 && node.Key == "StoresOre")
node.Key = "StoresResources";
}
// make animation is now it's own trait
if (engineVersion < 20140621)
{
if (depth == 1 && (node.Key.StartsWith("RenderBuilding")))
node.Value.Nodes.RemoveAll(n => n.Key == "HasMakeAnimation");
if (node.Value.Nodes.Any(n => n.Key.StartsWith("RenderBuilding"))
&& !node.Value.Nodes.Any(n => n.Key == "RenderBuildingWall")
&& !node.Value.Nodes.Any(n => n.Key == "WithMakeAnimation"))
{
node.Value.Nodes.Add(new MiniYamlNode("WithMakeAnimation", new MiniYaml("")));
}
}
// ParachuteAttachment was merged into Parachutable
if (engineVersion < 20140701)
{
if (depth == 1 && node.Key == "ParachuteAttachment")
{
node.Key = "Parachutable";
foreach (var subnode in node.Value.Nodes)
if (subnode.Key == "Offset")
subnode.Key = "ParachuteOffset";
}
if (depth == 2 && node.Key == "ParachuteSprite")
node.Key = "ParachuteSequence";
}
// SonarPulsePower was implemented as a generic SpawnActorPower
if (engineVersion < 20140703)
{
if (depth == 1 && node.Key == "SonarPulsePower")
node.Key = "SpawnActorPower";
}
if (engineVersion < 20140707)
{
// SpyPlanePower was removed (use AirstrikePower instead)
if (depth == 1 && node.Key == "SpyPlanePower")
{
node.Key = "AirstrikePower";
var revealTime = 6 * 25;
var revealNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "RevealTime");
if (revealNode != null)
{
revealTime = int.Parse(revealNode.Value.Value) * 25;
node.Value.Nodes.Remove(revealNode);
}
node.Value.Nodes.Add(new MiniYamlNode("CameraActor", new MiniYaml("camera")));
node.Value.Nodes.Add(new MiniYamlNode("CameraRemoveDelay", new MiniYaml(revealTime.ToString())));
node.Value.Nodes.Add(new MiniYamlNode("UnitType", new MiniYaml("u2")));
}
if (depth == 2 && node.Key == "LZRange" && parentKey == "ParaDrop")
{
node.Key = "DropRange";
ConvertFloatToRange(ref node.Value.Value);
}
}
// GiveUnitCrateAction and GiveMcvCrateAction were updated to allow multiple units
if (engineVersion < 20140723)
{
if (depth == 2 && parentKey.Contains("GiveMcvCrateAction"))
if (node.Key == "Unit")
node.Key = "Units";
if (depth == 2 && parentKey.Contains("GiveUnitCrateAction"))
if (node.Key == "Unit")
node.Key = "Units";
}
UpgradeActorRules(engineVersion, ref node.Value.Nodes, node, depth + 1);
}
}
static void UpgradeWeaponRules(int engineVersion, ref List<MiniYamlNode> nodes, MiniYamlNode parent, int depth)
{
var parentKey = parent != null ? parent.Key.Split('@').First() : null;
foreach (var node in nodes)
{
// Weapon definitions were converted to world coordinates
if (engineVersion < 20131226)
{
if (depth == 1)
{
switch (node.Key)
{
case "Range":
case "MinRange":
ConvertFloatToRange(ref node.Value.Value);
break;
default:
break;
}
}
if (depth == 2 && parentKey == "Projectile")
{
switch (node.Key)
{
case "Inaccuracy":
ConvertPxToRange(ref node.Value.Value);
break;
case "Angle":
ConvertAngle(ref node.Value.Value);
break;
case "Speed":
{
if (parent.Value.Value == "Missile")
ConvertPxToRange(ref node.Value.Value, 1, 5);
if (parent.Value.Value == "Bullet")
ConvertPxToRange(ref node.Value.Value, 2, 5);
break;
}
default:
break;
}
}
if (depth == 2 && parentKey == "Warhead")
{
switch (node.Key)
{
case "Spread":
ConvertPxToRange(ref node.Value.Value);
break;
default:
break;
}
}
}
if (engineVersion < 20140615)
{
if (depth == 2 && parentKey == "Warhead" && node.Key == "Ore")
node.Key = "DestroyResources";
}
if (engineVersion < 20140720)
{
// Split out the warheads to individual warhead types.
if (depth == 0)
{
var warheadCounter = 0;
foreach(var curNode in node.Value.Nodes.ToArray())
{
if (curNode.Key.Contains("Warhead") && curNode.Value.Value == null)
{
var newNodes = new List<MiniYamlNode>();
var oldNodeAtName = "";
if (curNode.Key.Contains('@'))
oldNodeAtName = "_" + curNode.Key.Split('@')[1];
// Per Cell Damage Model
if (curNode.Value.Nodes.Where(n => n.Key.Contains("DamageModel") &&
n.Value.Value.Contains("PerCell")).Any())
{
warheadCounter++;
var newYaml = new List<MiniYamlNode>();
var keywords = new List<String>{ "Size","Damage","InfDeath","PreventProne","ProneModifier","Delay","ValidTargets","InvalidTargets" };
foreach(var keyword in keywords)
{
var temp = curNode.Value.Nodes.FirstOrDefault(n => n.Key == keyword);
if (temp != null)
newYaml.Add(new MiniYamlNode(keyword, temp.Value.Value));
}
var tempVersus = curNode.Value.Nodes.FirstOrDefault(n => n.Key == "Versus");
if (tempVersus != null)
newYaml.Add(new MiniYamlNode("Versus", tempVersus.Value));
newNodes.Add(new MiniYamlNode("Warhead@" + warheadCounter.ToString() + "Dam" + oldNodeAtName, "PerCellDamage", newYaml));
}
// HealthPercentage damage model
if (curNode.Value.Nodes.Where(n => n.Key.Contains("DamageModel") &&
n.Value.Value.Contains("HealthPercentage")).Any())
{
warheadCounter++;
var newYaml = new List<MiniYamlNode>();
var temp = curNode.Value.Nodes.FirstOrDefault(n => n.Key == "Size"); // New HealthPercentage warhead allows spreads, as opposed to size
if (temp != null)
{
var newValue = temp.Value.Value.Split(',').First() + "c0";
if (temp.Value.Value.Contains(','))
newValue = newValue + "," + temp.Value.Value.Split(',')[1] + "c0"; ;
newYaml.Add(new MiniYamlNode("Spread", newValue));
}
var keywords = new List<String>{ "Damage","InfDeath","PreventProne","ProneModifier","Delay","ValidTargets","InvalidTargets" };
foreach(var keyword in keywords)
{
var temp2 = curNode.Value.Nodes.FirstOrDefault(n => n.Key == keyword);
if (temp2 != null)
newYaml.Add(new MiniYamlNode(keyword, temp2.Value.Value));
}
var tempVersus = curNode.Value.Nodes.FirstOrDefault(n => n.Key == "Versus");
if (tempVersus != null)
newYaml.Add(new MiniYamlNode("Versus", tempVersus.Value));
newNodes.Add(new MiniYamlNode("Warhead@" + warheadCounter.ToString() + "Dam" + oldNodeAtName, "HealthPercentageDamage", newYaml));
}
// SpreadDamage
{ // Always occurs, since by definition all warheads were SpreadDamage warheads before
warheadCounter++;
var newYaml = new List<MiniYamlNode>();
var keywords = new List<String>{ "Spread","Damage","InfDeath","PreventProne","ProneModifier","Delay","ValidTargets","InvalidTargets" };
foreach(var keyword in keywords)
{
var temp = curNode.Value.Nodes.FirstOrDefault(n => n.Key == keyword);
if (temp != null)
newYaml.Add(new MiniYamlNode(keyword, temp.Value.Value));
}
var tempVersus = curNode.Value.Nodes.FirstOrDefault(n => n.Key == "Versus");
if (tempVersus != null)
newYaml.Add(new MiniYamlNode("Versus", tempVersus.Value));
newNodes.Add(new MiniYamlNode("Warhead@" + warheadCounter.ToString() + "Dam" + oldNodeAtName, "SpreadDamage", newYaml));
}
// DestroyResource
if (curNode.Value.Nodes.Where(n => n.Key.Contains("DestroyResources") ||
n.Key.Contains("Ore")).Any())
{
warheadCounter++;
var newYaml = new List<MiniYamlNode>();
var keywords = new List<String>{ "Size","Delay","ValidTargets","InvalidTargets" };
foreach(var keyword in keywords)
{
var temp = curNode.Value.Nodes.FirstOrDefault(n => n.Key == keyword);
if (temp != null)
newYaml.Add(new MiniYamlNode(keyword, temp.Value.Value));
}
newNodes.Add(new MiniYamlNode("Warhead@" + warheadCounter.ToString() + "Res" + oldNodeAtName, "DestroyResource", newYaml));
}
// CreateResource
if (curNode.Value.Nodes.Where(n => n.Key.Contains("AddsResourceType")).Any())
{
warheadCounter++;
var newYaml = new List<MiniYamlNode>();
var keywords = new List<String>{ "AddsResourceType","Size","Delay","ValidTargets","InvalidTargets" };
foreach(var keyword in keywords)
{
var temp = curNode.Value.Nodes.FirstOrDefault(n => n.Key == keyword);
if (temp != null)
newYaml.Add(new MiniYamlNode(keyword, temp.Value.Value));
}
newNodes.Add(new MiniYamlNode("Warhead@" + warheadCounter.ToString() + "Res" + oldNodeAtName, "CreateResource", newYaml));
}
// LeaveSmudge
if (curNode.Value.Nodes.Where(n => n.Key.Contains("SmudgeType")).Any())
{
warheadCounter++;
var newYaml = new List<MiniYamlNode>();
var keywords = new List<String>{ "SmudgeType","Size","Delay","ValidTargets","InvalidTargets" };
foreach(var keyword in keywords)
{
var temp = curNode.Value.Nodes.FirstOrDefault(n => n.Key == keyword);
if (temp != null)
newYaml.Add(new MiniYamlNode(keyword, temp.Value.Value));
}
newNodes.Add(new MiniYamlNode("Warhead@" + warheadCounter.ToString() + "Smu" + oldNodeAtName, "LeaveSmudge", newYaml));
}
// CreateEffect
if (curNode.Value.Nodes.Where(n => n.Key.Contains("Explosion") ||
n.Key.Contains("WaterExplosion") ||
n.Key.Contains("ImpactSound") ||
n.Key.Contains("WaterImpactSound")).Any())
{
warheadCounter++;
var newYaml = new List<MiniYamlNode>();
var keywords = new List<String>{ "Explosion","WaterExplosion","ImpactSound","WaterImpactSound","Delay","ValidTargets","InvalidTargets" };
foreach(var keyword in keywords)
{
var temp = curNode.Value.Nodes.FirstOrDefault(n => n.Key == keyword);
if (temp != null)
newYaml.Add(new MiniYamlNode(keyword, temp.Value.Value));
}
newNodes.Add(new MiniYamlNode("Warhead@" + warheadCounter.ToString() + "Eff" + oldNodeAtName, "CreateEffect", newYaml));
}
node.Value.Nodes.InsertRange(node.Value.Nodes.IndexOf(curNode),newNodes);
node.Value.Nodes.Remove(curNode);
}
}
}
}
UpgradeWeaponRules(engineVersion, ref node.Value.Nodes, node, depth + 1);
}
}
static void UpgradeTileset(int engineVersion, ref List<MiniYamlNode> nodes, MiniYamlNode parent, int depth)
{
var parentKey = parent != null ? parent.Key.Split('@').First() : null;
var addNodes = new List<MiniYamlNode>();
foreach (var node in nodes)
{
if (engineVersion < 20140104)
{
if (depth == 2 && parentKey == "TerrainType" && node.Key.Split('@').First() == "Type")
addNodes.Add(new MiniYamlNode("TargetTypes", node.Value.Value == "Water" ? "Water" : "Ground"));
}
UpgradeTileset(engineVersion, ref node.Value.Nodes, node, depth + 1);
}
nodes.AddRange(addNodes);
}
[Desc("MAP", "CURRENTENGINE", "Upgrade map rules to the latest engine version.")]
public static void UpgradeMap(string[] args)
{
var map = new Map(args[1]);
var engineDate = Exts.ParseIntegerInvariant(args[2]);
Game.modData = new ModData(map.RequiresMod);
UpgradeWeaponRules(engineDate, ref map.WeaponDefinitions, null, 0);
UpgradeActorRules(engineDate, ref map.RuleDefinitions, null, 0);
map.Save(args[1]);
}
[Desc("MOD", "CURRENTENGINE", "Upgrade mod rules to the latest engine version.")]
public static void UpgradeMod(string[] args)
{
var mod = args[1];
var engineDate = Exts.ParseIntegerInvariant(args[2]);
Game.modData = new ModData(mod);
Game.modData.MapCache.LoadMaps();
Console.WriteLine("Processing Rules:");
foreach (var filename in Game.modData.Manifest.Rules)
{
Console.WriteLine("\t" + filename);
var yaml = MiniYaml.FromFile(filename);
UpgradeActorRules(engineDate, ref yaml, null, 0);
using (var file = new StreamWriter(filename))
file.WriteLine(yaml.WriteToString());
}
Console.WriteLine("Processing Weapons:");
foreach (var filename in Game.modData.Manifest.Weapons)
{
Console.WriteLine("\t" + filename);
var yaml = MiniYaml.FromFile(filename);
UpgradeWeaponRules(engineDate, ref yaml, null, 0);
using (var file = new StreamWriter(filename))
file.WriteLine(yaml.WriteToString());
}
Console.WriteLine("Processing Tilesets:");
foreach (var filename in Game.modData.Manifest.TileSets)
{
Console.WriteLine("\t" + filename);
var yaml = MiniYaml.FromFile(filename);
UpgradeTileset(engineDate, ref yaml, null, 0);
using (var file = new StreamWriter(filename))
file.WriteLine(yaml.WriteToString());
}
Console.WriteLine("Processing Maps:");
var maps = Game.modData.MapCache
.Where(m => m.Status == MapStatus.Available)
.Select(m => m.Map);
foreach (var map in maps)
{
Console.WriteLine("\t" + map.Path);
UpgradeActorRules(engineDate, ref map.RuleDefinitions, null, 0);
UpgradeWeaponRules(engineDate, ref map.WeaponDefinitions, null, 0);
map.Save(map.Path);
}
}
}
}