Merge pull request #8000 from pchote/bogus-yaml-removals

Fix yaml merging
This commit is contained in:
Matthias Mailänder
2015-04-25 13:42:18 +02:00
8 changed files with 156 additions and 254 deletions

View File

@@ -16,15 +16,18 @@ using OpenRA.Traits;
namespace OpenRA namespace OpenRA
{ {
// TODO: This is not exported into the documentation yet. /// <summary>
[Desc("A unit/building inside the game. Every rules starts with one and adds trait to it.", /// A unit/building inside the game. Every rules starts with one and adds trait to it.
"Special actors like world or player are usually defined in system.yaml and affect everything.")] /// Special actors like world or player are usually defined in system.yaml and affect everything.
/// </summary>
public class ActorInfo public class ActorInfo
{ {
[Desc("The actor name can be anything, but the sprites used in the Render*: traits default to this one.", /// <summary>
"If you add an ^ in front of the name, the engine will recognize this as a collection of traits", /// The actor name can be anything, but the sprites used in the Render*: traits default to this one.
"that can be inherited by others (using Inherits:) and not a real unit.", /// If you add an ^ in front of the name, the engine will recognize this as a collection of traits
"You can remove inherited traits by adding a - infront of them as in -TraitName: to inherit everything, but this trait.")] /// that can be inherited by others (using Inherits:) and not a real unit.
/// You can remove inherited traits by adding a - infront of them as in -TraitName: to inherit everything, but this trait.
/// </summary>
public readonly string Name; public readonly string Name;
public readonly TypeDictionary Traits = new TypeDictionary(); public readonly TypeDictionary Traits = new TypeDictionary();
List<ITraitInfo> constructOrderCache = null; List<ITraitInfo> constructOrderCache = null;
@@ -33,45 +36,54 @@ namespace OpenRA
{ {
try try
{ {
var mergedNode = MergeWithParent(node, allUnits).ToDictionary(); var allParents = new HashSet<string>();
// Guard against circular inheritance
allParents.Add(name);
var mergedNode = MergeWithParents(node, allUnits, allParents).ToDictionary();
Name = name; Name = name;
foreach (var t in mergedNode) foreach (var t in mergedNode)
if (t.Key != "Inherits" && !t.Key.StartsWith("-")) {
if (t.Key[0] == '-')
throw new YamlException("Bogus trait removal: " + t.Key);
if (t.Key != "Inherits" && !t.Key.StartsWith("Inherits@"))
Traits.Add(LoadTraitInfo(t.Key.Split('@')[0], t.Value)); Traits.Add(LoadTraitInfo(t.Key.Split('@')[0], t.Value));
} }
}
catch (YamlException e) catch (YamlException e)
{ {
throw new YamlException("Actor type {0}: {1}".F(name, e.Message)); throw new YamlException("Actor type {0}: {1}".F(name, e.Message));
} }
} }
static MiniYaml GetParent(MiniYaml node, Dictionary<string, MiniYaml> allUnits) static Dictionary<string, MiniYaml> GetParents(MiniYaml node, Dictionary<string, MiniYaml> allUnits)
{ {
MiniYaml inherits; return node.Nodes.Where(n => n.Key == "Inherits" || n.Key.StartsWith("Inherits@"))
node.ToDictionary().TryGetValue("Inherits", out inherits); .ToDictionary(n => n.Value.Value, n =>
if (inherits == null || string.IsNullOrEmpty(inherits.Value)) {
return null; MiniYaml i;
if (!allUnits.TryGetValue(n.Value.Value, out i))
throw new YamlException(
"Bogus inheritance -- parent type {0} does not exist".F(n.Value.Value));
MiniYaml parent; return i;
allUnits.TryGetValue(inherits.Value, out parent); });
if (parent == null)
throw new InvalidOperationException(
"Bogus inheritance -- actor type {0} does not exist".F(inherits.Value));
return parent;
} }
static MiniYaml MergeWithParent(MiniYaml node, Dictionary<string, MiniYaml> allUnits) static MiniYaml MergeWithParents(MiniYaml node, Dictionary<string, MiniYaml> allUnits, HashSet<string> allParents)
{ {
var parent = GetParent(node, allUnits); var parents = GetParents(node, allUnits);
if (parent != null)
{
var result = MiniYaml.MergeStrict(node, MergeWithParent(parent, allUnits));
// strip the '-' foreach (var kv in parents)
result.Nodes.RemoveAll(a => a.Key.StartsWith("-")); {
return result; if (!allParents.Add(kv.Key))
throw new YamlException(
"Bogus inheritance -- duplicate inheritance of {0}.".F(kv.Key));
node = MiniYaml.MergeStrict(node, MergeWithParents(kv.Value, allUnits, allParents));
} }
return node; return node;

53
OpenRA.Game/MiniYaml.cs Executable file → Normal file
View File

@@ -254,17 +254,17 @@ namespace OpenRA
return FromLines(text.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries), fileName); return FromLines(text.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries), fileName);
} }
public static List<MiniYamlNode> MergeLiberal(List<MiniYamlNode> a, List<MiniYamlNode> b) public static List<MiniYamlNode> MergeStrict(List<MiniYamlNode> a, List<MiniYamlNode> b)
{ {
return Merge(a, b, false); return Merge(a, b, false);
} }
public static List<MiniYamlNode> MergeStrict(List<MiniYamlNode> a, List<MiniYamlNode> b) public static List<MiniYamlNode> MergeLiberal(List<MiniYamlNode> a, List<MiniYamlNode> b)
{ {
return Merge(a, b, true); return Merge(a, b, true);
} }
static List<MiniYamlNode> Merge(List<MiniYamlNode> a, List<MiniYamlNode> b, bool throwErrors) static List<MiniYamlNode> Merge(List<MiniYamlNode> a, List<MiniYamlNode> b, bool allowUnresolvedRemoves = false)
{ {
if (a.Count == 0) if (a.Count == 0)
return b; return b;
@@ -275,10 +275,11 @@ namespace OpenRA
var dictA = a.ToDictionaryWithConflictLog(x => x.Key, "MiniYaml.Merge", null, x => "{0} (at {1})".F(x.Key, x.Location)); var dictA = a.ToDictionaryWithConflictLog(x => x.Key, "MiniYaml.Merge", null, x => "{0} (at {1})".F(x.Key, x.Location));
var dictB = b.ToDictionaryWithConflictLog(x => x.Key, "MiniYaml.Merge", null, x => "{0} (at {1})".F(x.Key, x.Location)); var dictB = b.ToDictionaryWithConflictLog(x => x.Key, "MiniYaml.Merge", null, x => "{0} (at {1})".F(x.Key, x.Location));
var keys = dictA.Keys.Union(dictB.Keys).ToList(); var allKeys = dictA.Keys.Union(dictB.Keys);
var noInherit = keys.Where(x => x.Length > 0 && x[0] == '-') var keys = allKeys.Where(x => x.Length == 0 || x[0] != '-').ToList();
.ToDictionary(x => x.Substring(1), x => false); var removeKeys = allKeys.Where(x => x.Length > 0 && x[0] == '-')
.Select(k => k.Substring(1)).ToHashSet();
foreach (var key in keys) foreach (var key in keys)
{ {
@@ -286,47 +287,55 @@ namespace OpenRA
dictA.TryGetValue(key, out aa); dictA.TryGetValue(key, out aa);
dictB.TryGetValue(key, out bb); dictB.TryGetValue(key, out bb);
if (noInherit.ContainsKey(key)) if (removeKeys.Contains(key))
{ removeKeys.Remove(key);
if (!throwErrors)
if (aa != null)
ret.Add(aa);
noInherit[key] = true;
}
else else
{ {
var loc = aa == null ? default(MiniYamlNode.SourceLocation) : aa.Location; var loc = aa == null ? default(MiniYamlNode.SourceLocation) : aa.Location;
var merged = (aa == null || bb == null) ? aa ?? bb : new MiniYamlNode(key, Merge(aa.Value, bb.Value, throwErrors), loc); var merged = (aa == null || bb == null) ? aa ?? bb : new MiniYamlNode(key, Merge(aa.Value, bb.Value, allowUnresolvedRemoves), loc);
ret.Add(merged); ret.Add(merged);
} }
} }
if (throwErrors && noInherit.ContainsValue(false)) if (removeKeys.Any())
throw new YamlException("Bogus yaml removals: {0}".F( {
noInherit.Where(x => !x.Value).JoinWith(", "))); if (allowUnresolvedRemoves)
{
// Add the removal nodes back for the next pass to deal with
foreach (var k in removeKeys)
{
var key = "-" + k;
MiniYamlNode rem;
if (!dictA.TryGetValue(key, out rem))
rem = dictB[key];
ret.Add(rem);
}
}
else
throw new YamlException("Bogus yaml removals: {0}".F(removeKeys.JoinWith(", ")));
}
return ret; return ret;
} }
public static MiniYaml MergeLiberal(MiniYaml a, MiniYaml b) public static MiniYaml MergeLiberal(MiniYaml a, MiniYaml b)
{ {
return Merge(a, b, false); return Merge(a, b, true);
} }
public static MiniYaml MergeStrict(MiniYaml a, MiniYaml b) public static MiniYaml MergeStrict(MiniYaml a, MiniYaml b)
{ {
return Merge(a, b, true); return Merge(a, b, false);
} }
static MiniYaml Merge(MiniYaml a, MiniYaml b, bool throwErrors) static MiniYaml Merge(MiniYaml a, MiniYaml b, bool allowUnresolvedRemoves)
{ {
if (a == null) if (a == null)
return b; return b;
if (b == null) if (b == null)
return a; return a;
return new MiniYaml(a.Value ?? b.Value, Merge(a.Nodes, b.Nodes, throwErrors)); return new MiniYaml(a.Value ?? b.Value, Merge(a.Nodes, b.Nodes, allowUnresolvedRemoves));
} }
public IEnumerable<string> ToLines(string name) public IEnumerable<string> ToLines(string name)

View File

@@ -21,17 +21,22 @@ namespace OpenRA.Test
[TestFixture] [TestFixture]
public class MiniYamlTest public class MiniYamlTest
{ {
readonly string yamlForParent = @" readonly string mixedMergeA = @"
^Parent: Merge:
FromParent: FromA:
FromParentRemove: FromARemovedB:
FromARemovedA:
-FromBRemovedA:
-FromARemovedA:
"; ";
readonly string yamlForChild = @" readonly string mixedMergeB = @"
Child: Merge:
Inherits: ^Parent FromB:
FromChild: FromBRemovedA:
-FromParentRemove: FromBRemovedB:
-FromARemovedB:
-FromBRemovedB:
"; ";
readonly string yamlTabStyle = @" readonly string yamlTabStyle = @"
@@ -60,58 +65,28 @@ Root2:
Attribute1: Test Attribute1: Test
"; ";
List<MiniYamlNode> parentList; [TestCase(TestName = "Merging: mixed addition and removal")]
List<MiniYamlNode> childList;
MiniYaml parent;
MiniYaml child;
[SetUp]
public void SetUp()
{
parentList = MiniYaml.FromString(yamlForParent);
childList = MiniYaml.FromString(yamlForChild);
parent = parentList.First().Value;
child = childList.First().Value;
}
void InheritanceTest(List<MiniYamlNode> nodes)
{
Assert.That(nodes.Any(n => n.Key == "FromParent"), Is.True, "Node from parent");
Assert.That(nodes.Any(n => n.Key == "FromChild"), Is.True, "Node from child");
Assert.That(nodes.Any(n => n.Key == "FromParentRemove"), Is.Not.True, "Node from parent - node from child");
}
[TestCase(TestName = "MergeStrict(MiniYaml, MiniYaml)")]
public void MergeYamlA() public void MergeYamlA()
{ {
var res = MiniYaml.MergeStrict(parent, child); var a = MiniYaml.FromString(mixedMergeA, "mixedMergeA");
InheritanceTest(res.Nodes); var b = MiniYaml.FromString(mixedMergeB, "mixedMergeB");
// Merge order should not matter
// Note: All the Merge* variants are different plumbing over the same
// internal logic. Testing only MergeStrict is sufficent.
TestMixedMerge(MiniYaml.MergeStrict(a, b).First().Value);
TestMixedMerge(MiniYaml.MergeStrict(b, a).First().Value);
} }
[Ignore("Disabled until the code is fixed so we don't break continuous integration.")] void TestMixedMerge(MiniYaml result)
[TestCase(TestName = "MergeLiberal(MiniYaml, MiniYaml)")]
public void MergeYamlB()
{ {
var res = MiniYaml.MergeLiberal(parent, child); Console.WriteLine(result.ToLines("result").JoinWith("\n"));
InheritanceTest(res.Nodes); Assert.That(result.Nodes.Any(n => n.Key == "FromA"), Is.True, "Node from A");
} Assert.That(result.Nodes.Any(n => n.Key == "FromB"), Is.True, "Node from B");
Assert.That(result.Nodes.Any(n => n.Key == "FromARemovedA"), Is.Not.True, "Node from A removed by A");
[Ignore("Disabled until the code is fixed so we don't break continuous integration.")] Assert.That(result.Nodes.Any(n => n.Key == "FromARemovedB"), Is.Not.True, "Node from A removed by B");
[TestCase(TestName = "MergeStrict(List<MiniYamlNode>, List<MiniYamlNode>)")] Assert.That(result.Nodes.Any(n => n.Key == "FromBRemovedA"), Is.Not.True, "Node from B removed by A");
public void MergeYamlC() Assert.That(result.Nodes.Any(n => n.Key == "FromBRemovedB"), Is.Not.True, "Node from B removed by B");
{
var res = MiniYaml.MergeStrict(parentList, childList).Last();
Assert.That(res.Key, Is.EqualTo("Child"));
InheritanceTest(res.Value.Nodes);
}
[Ignore("Disabled until the code is fixed so we don't break continuous integration.")]
[TestCase(TestName = "MergeLiberal(List<MiniYamlNode>, List<MiniYamlNode>)")]
public void MergeYamlD()
{
var res = MiniYaml.MergeLiberal(parentList, childList).Last();
Assert.That(res.Key, Is.EqualTo("Child"));
InheritanceTest(res.Value.Nodes);
} }
[TestCase(TestName = "Mixed tabs & spaces indents")] [TestCase(TestName = "Mixed tabs & spaces indents")]

View File

@@ -1330,7 +1330,7 @@ Rules:
Health: Health:
HP: 200 HP: 200
E7: E7:
-Selectable: -AnnounceOnKill:
powerproxy.paratroopers: powerproxy.paratroopers:
ParatroopersPower: ParatroopersPower:
DisplayBeacon: false DisplayBeacon: false

View File

@@ -2102,7 +2102,7 @@ Rules:
MissionObjectives: MissionObjectives:
EarlyGameOver: true EarlyGameOver: true
World: World:
-CrateDrop: -CrateSpawner:
-SpawnMPUnits: -SpawnMPUnits:
-MPStartLocations: -MPStartLocations:
LuaScript: LuaScript:

View File

@@ -666,7 +666,7 @@ Rules:
ParatroopersPower@paratroopers: ParatroopersPower@paratroopers:
ChargeTime: 60 ChargeTime: 60
DropItems: E1,E1,E1,E2,E2 DropItems: E1,E1,E1,E2,E2
#-RallyPoint: # TODO https://github.com/OpenRA/OpenRA/issues/6818 -RallyPoint:
-Sellable: -Sellable:
DOME: DOME:
-Sellable: -Sellable:

View File

@@ -1029,7 +1029,7 @@ Rules:
MissionObjectives: MissionObjectives:
EarlyGameOver: true EarlyGameOver: true
World: World:
-CrateDrop: -CrateSpawner:
-SpawnMPUnits: -SpawnMPUnits:
-MPStartLocations: -MPStartLocations:
LuaScript: LuaScript:

View File

@@ -1,5 +1,40 @@
^Vehicle: ^ExistsInWorld:
AppearsOnRadar: AppearsOnRadar:
UpdatesPlayerStatistics:
CombatDebugOverlay:
DrawLineToTarget:
GivesExperience:
BodyOrientation:
ScriptTriggers:
UpgradeManager:
Huntable:
^GainsExperience:
GainsExperience:
GainsStatUpgrades:
SelfHealing@ELITE:
Step: 2
Ticks: 100
HealIfBelow: 1
DamageCooldown: 125
UpgradeTypes: selfheal
UpgradeMinEnabledLevel: 1
^IronCurtainable:
UpgradeOverlay@IRONCURTAIN:
UpgradeTypes: invulnerability
UpgradeMinEnabledLevel: 1
InvulnerabilityUpgrade@IRONCURTAIN:
UpgradeTypes: invulnerability
UpgradeMinEnabledLevel: 1
UpgradeMaxAcceptedLevel: 2
TimedUpgradeBar:
Upgrade: invulnerability
^Vehicle:
Inherits@1: ^ExistsInWorld
Inherits@2: ^GainsExperience
Inherits@3: ^IronCurtainable
Mobile: Mobile:
Crushes: mine, crate Crushes: mine, crate
TerrainSpeeds: TerrainSpeeds:
@@ -22,9 +57,6 @@
CargoType: Vehicle CargoType: Vehicle
AttackMove: AttackMove:
HiddenUnderFog: HiddenUnderFog:
GainsExperience:
GivesExperience:
DrawLineToTarget:
ActorLostNotification: ActorLostNotification:
ProximityCaptor: ProximityCaptor:
Types: Vehicle Types: Vehicle
@@ -32,11 +64,8 @@
GpsDot: GpsDot:
String: Vehicle String: Vehicle
WithSmoke: WithSmoke:
UpdatesPlayerStatistics:
CombatDebugOverlay:
Guard: Guard:
Guardable: Guardable:
BodyOrientation:
Tooltip: Tooltip:
GenericName: Vehicle GenericName: Vehicle
EjectOnDeath: EjectOnDeath:
@@ -45,36 +74,18 @@
EjectOnGround: true EjectOnGround: true
EjectInAir: false EjectInAir: false
AllowUnsuitableCell: false AllowUnsuitableCell: false
Huntable:
Capturable: Capturable:
Type: vehicle Type: vehicle
CaptureThreshold: 1 CaptureThreshold: 1
CancelActivity: True CancelActivity: True
CaptureNotification: CaptureNotification:
Notification: UnitStolen Notification: UnitStolen
ScriptTriggers:
GainsStatUpgrades:
SelfHealing@ELITE:
Step: 2
Ticks: 100
HealIfBelow: 1
DamageCooldown: 125
UpgradeTypes: selfheal
UpgradeMinEnabledLevel: 1
UpgradeManager:
UpgradeOverlay@IRONCURTAIN:
UpgradeTypes: invulnerability
UpgradeMinEnabledLevel: 1
InvulnerabilityUpgrade@IRONCURTAIN:
UpgradeTypes: invulnerability
UpgradeMinEnabledLevel: 1
UpgradeMaxAcceptedLevel: 2
TimedUpgradeBar:
Upgrade: invulnerability
MustBeDestroyed: MustBeDestroyed:
^Tank: ^Tank:
AppearsOnRadar: Inherits@1: ^ExistsInWorld
Inherits@2: ^GainsExperience
Inherits@3: ^IronCurtainable
Mobile: Mobile:
Crushes: wall, mine, crate Crushes: wall, mine, crate
TerrainSpeeds: TerrainSpeeds:
@@ -97,9 +108,6 @@
CargoType: Vehicle CargoType: Vehicle
AttackMove: AttackMove:
HiddenUnderFog: HiddenUnderFog:
GainsExperience:
GivesExperience:
DrawLineToTarget:
ActorLostNotification: ActorLostNotification:
ProximityCaptor: ProximityCaptor:
Types: Tank Types: Tank
@@ -107,11 +115,8 @@
GpsDot: GpsDot:
String: Vehicle String: Vehicle
WithSmoke: WithSmoke:
UpdatesPlayerStatistics:
CombatDebugOverlay:
Guard: Guard:
Guardable: Guardable:
BodyOrientation:
Tooltip: Tooltip:
GenericName: Tank GenericName: Tank
EjectOnDeath: EjectOnDeath:
@@ -120,32 +125,12 @@
EjectOnGround: true EjectOnGround: true
EjectInAir: false EjectInAir: false
AllowUnsuitableCell: false AllowUnsuitableCell: false
Huntable:
Capturable: Capturable:
Type: vehicle Type: vehicle
CaptureThreshold: 1 CaptureThreshold: 1
CancelActivity: True CancelActivity: True
CaptureNotification: CaptureNotification:
Notification: UnitStolen Notification: UnitStolen
ScriptTriggers:
GainsStatUpgrades:
SelfHealing@ELITE:
Step: 2
Ticks: 100
HealIfBelow: 1
DamageCooldown: 125
UpgradeTypes: selfheal
UpgradeMinEnabledLevel: 1
UpgradeManager:
UpgradeOverlay@IRONCURTAIN:
UpgradeTypes: invulnerability
UpgradeMinEnabledLevel: 1
InvulnerabilityUpgrade@IRONCURTAIN:
UpgradeTypes: invulnerability
UpgradeMinEnabledLevel: 1
UpgradeMaxAcceptedLevel: 2
TimedUpgradeBar:
Upgrade: invulnerability
MustBeDestroyed: MustBeDestroyed:
Parachutable: Parachutable:
ParachuteOffset: 0,0,200 ParachuteOffset: 0,0,200
@@ -158,7 +143,8 @@
WaterCorpsePalette: WaterCorpsePalette:
^Infantry: ^Infantry:
AppearsOnRadar: Inherits@1: ^ExistsInWorld
Inherits@2: ^GainsExperience
Health: Health:
Radius: 128 Radius: 128
Armor: Armor:
@@ -189,9 +175,6 @@
Passenger: Passenger:
CargoType: Infantry CargoType: Infantry
HiddenUnderFog: HiddenUnderFog:
GainsExperience:
GivesExperience:
DrawLineToTarget:
ActorLostNotification: ActorLostNotification:
ProximityCaptor: ProximityCaptor:
Types: Infantry Types: Infantry
@@ -200,11 +183,8 @@
String: Infantry String: Infantry
Crushable: Crushable:
CrushSound: squishy2.aud CrushSound: squishy2.aud
UpdatesPlayerStatistics:
CombatDebugOverlay:
Guard: Guard:
Guardable: Guardable:
BodyOrientation:
Tooltip: Tooltip:
GenericName: Soldier GenericName: Soldier
SelfHealing@HOSPITAL: SelfHealing@HOSPITAL:
@@ -217,8 +197,6 @@
GlobalUpgradable: GlobalUpgradable:
Upgrades: hospitalheal Upgrades: hospitalheal
Prerequisites: hosp Prerequisites: hosp
Huntable:
ScriptTriggers:
DeathSounds@NORMAL: DeathSounds@NORMAL:
DeathTypes: 1, 2, 3, 4 DeathTypes: 1, 2, 3, 4
DeathSounds@BURNED: DeathSounds@BURNED:
@@ -236,19 +214,12 @@
WaterImpactSound: splash9.aud WaterImpactSound: splash9.aud
Cloneable: Cloneable:
Types: Infantry Types: Infantry
GainsStatUpgrades:
SelfHealing@ELITE:
Step: 2
Ticks: 100
HealIfBelow: 1
DamageCooldown: 125
UpgradeTypes: selfheal
UpgradeMinEnabledLevel: 1
UpgradeManager:
MustBeDestroyed: MustBeDestroyed:
^Ship: ^Ship:
AppearsOnRadar: Inherits@1: ^ExistsInWorld
Inherits@2: ^GainsExperience
Inherits@3: ^IronCurtainable
Mobile: Mobile:
Crushes: crate Crushes: crate
TerrainSpeeds: TerrainSpeeds:
@@ -260,9 +231,6 @@
TargetTypes: Ground, Water, Repair TargetTypes: Ground, Water, Repair
HiddenUnderFog: HiddenUnderFog:
AttackMove: AttackMove:
GainsExperience:
GivesExperience:
DrawLineToTarget:
ActorLostNotification: ActorLostNotification:
Notification: NavalUnitLost Notification: NavalUnitLost
ProximityCaptor: ProximityCaptor:
@@ -271,37 +239,16 @@
GpsDot: GpsDot:
String: Ship String: Ship
WithSmoke: WithSmoke:
UpdatesPlayerStatistics:
CombatDebugOverlay:
Guard: Guard:
Guardable: Guardable:
BodyOrientation:
Tooltip: Tooltip:
GenericName: Ship GenericName: Ship
Huntable:
ScriptTriggers:
GainsStatUpgrades:
SelfHealing@ELITE:
Step: 2
Ticks: 100
HealIfBelow: 1
DamageCooldown: 125
UpgradeTypes: selfheal
UpgradeMinEnabledLevel: 1
UpgradeManager:
UpgradeOverlay@IRONCURTAIN:
UpgradeTypes: invulnerability
UpgradeMinEnabledLevel: 1
InvulnerabilityUpgrade@IRONCURTAIN:
UpgradeTypes: invulnerability
UpgradeMinEnabledLevel: 1
UpgradeMaxAcceptedLevel: 2
TimedUpgradeBar:
Upgrade: invulnerability
UpgradeMinEnabledLevel: 1
MustBeDestroyed: MustBeDestroyed:
^Plane: ^Plane:
Inherits@1: ^ExistsInWorld
Inherits@2: ^GainsExperience
Inherits@3: ^IronCurtainable
AppearsOnRadar: AppearsOnRadar:
UseLocation: true UseLocation: true
SelectionDecorations: SelectionDecorations:
@@ -314,9 +261,6 @@
AttackMove: AttackMove:
Guard: Guard:
Guardable: Guardable:
GainsExperience:
GivesExperience:
DrawLineToTarget:
ActorLostNotification: ActorLostNotification:
Notification: AirUnitLost Notification: AirUnitLost
ProximityCaptor: ProximityCaptor:
@@ -330,31 +274,8 @@
GivesBounty: GivesBounty:
GpsDot: GpsDot:
String: Plane String: Plane
UpdatesPlayerStatistics:
CombatDebugOverlay:
BodyOrientation:
Tooltip: Tooltip:
GenericName: Plane GenericName: Plane
Huntable:
ScriptTriggers:
GainsStatUpgrades:
SelfHealing@ELITE:
Step: 2
Ticks: 100
HealIfBelow: 1
DamageCooldown: 125
UpgradeTypes: selfheal
UpgradeMinEnabledLevel: 1
UpgradeManager:
UpgradeOverlay@IRONCURTAIN:
UpgradeTypes: invulnerability
UpgradeMinEnabledLevel: 1
InvulnerabilityUpgrade@IRONCURTAIN:
UpgradeTypes: invulnerability
UpgradeMinEnabledLevel: 1
UpgradeMaxAcceptedLevel: 2
TimedUpgradeBar:
Upgrade: invulnerability
WithShadow: WithShadow:
MustBeDestroyed: MustBeDestroyed:
@@ -367,7 +288,8 @@
Hovers: Hovers:
^Building: ^Building:
AppearsOnRadar: Inherits@1: ^ExistsInWorld
Inherits@2: ^IronCurtainable
SelectionDecorations: SelectionDecorations:
Selectable: Selectable:
Priority: 3 Priority: 3
@@ -393,7 +315,6 @@
ActorTypes: e1,e1,e1,e1,e1,e1,e1,e1,e1,e1,e1,e1,e1,e1,e1,c1,c2,c3,c4,c5,c6,c7,c8,c9,c10,e6,e6,e6,e6,e6 ActorTypes: e1,e1,e1,e1,e1,e1,e1,e1,e1,e1,e1,e1,e1,e1,e1,c1,c2,c3,c4,c5,c6,c7,c8,c9,c10,e6,e6,e6,e6,e6
MustBeDestroyed: MustBeDestroyed:
RequiredForShortGame: true RequiredForShortGame: true
GivesExperience:
CaptureNotification: CaptureNotification:
EditorAppearance: EditorAppearance:
RelativeToTopLeft: true RelativeToTopLeft: true
@@ -404,29 +325,14 @@
SellSounds: cashturn.aud SellSounds: cashturn.aud
AcceptsSupplies: AcceptsSupplies:
GivesBounty: GivesBounty:
UpdatesPlayerStatistics:
CombatDebugOverlay:
Guardable: Guardable:
Range: 3 Range: 3
BodyOrientation:
FrozenUnderFog: FrozenUnderFog:
Tooltip: Tooltip:
GenericName: Structure GenericName: Structure
GpsDot: GpsDot:
String: Structure String: Structure
Huntable:
Demolishable: Demolishable:
ScriptTriggers:
UpgradeManager:
UpgradeOverlay@IRONCURTAIN:
UpgradeTypes: invulnerability
UpgradeMinEnabledLevel: 1
InvulnerabilityUpgrade@IRONCURTAIN:
UpgradeTypes: invulnerability
UpgradeMinEnabledLevel: 1
UpgradeMaxAcceptedLevel: 2
TimedUpgradeBar:
Upgrade: invulnerability
^Defense: ^Defense:
Inherits: ^Building Inherits: ^Building