diff --git a/OpenRa.FileFormats/OpenRa.FileFormats.csproj b/OpenRa.FileFormats/OpenRa.FileFormats.csproj index 26f9983855..0016cbc237 100644 --- a/OpenRa.FileFormats/OpenRa.FileFormats.csproj +++ b/OpenRa.FileFormats/OpenRa.FileFormats.csproj @@ -60,10 +60,11 @@ - + + diff --git a/OpenRa.FileFormats/PaletteRemap.cs b/OpenRa.FileFormats/PlayerColorRemap.cs similarity index 53% rename from OpenRa.FileFormats/PaletteRemap.cs rename to OpenRa.FileFormats/PlayerColorRemap.cs index 635c0d5653..164b06f4de 100644 --- a/OpenRa.FileFormats/PaletteRemap.cs +++ b/OpenRa.FileFormats/PlayerColorRemap.cs @@ -4,13 +4,12 @@ using System.IO; namespace OpenRa.FileFormats { - public class PaletteRemap : IPaletteRemap + public class PlayerColorRemap : IPaletteRemap { int offset; List remapColors = new List(); - Color shadowColor; - public PaletteRemap(Stream s) + public PlayerColorRemap(Stream s) { using (BinaryReader reader = new BinaryReader(s)) { @@ -27,22 +26,12 @@ namespace OpenRa.FileFormats offset = 80; } - public PaletteRemap( Color shadowColor ) - { - this.shadowColor = shadowColor; - } - public Color GetRemappedColor(Color original, int index) { - if (remapColors.Count > 0) - { - if (index < offset || index >= offset + remapColors.Count) - return original; + if (index < offset || index >= offset + remapColors.Count) + return original; - return remapColors[index - offset]; - } - - return original.A > 0 ? shadowColor : original; + return remapColors[index - offset]; } } } diff --git a/OpenRa.FileFormats/SingleColorRemap.cs b/OpenRa.FileFormats/SingleColorRemap.cs new file mode 100644 index 0000000000..149779d96b --- /dev/null +++ b/OpenRa.FileFormats/SingleColorRemap.cs @@ -0,0 +1,18 @@ +using System.Drawing; + +namespace OpenRa.FileFormats +{ + public class SingleColorRemap : IPaletteRemap + { + Color c; + public SingleColorRemap(Color c) + { + this.c = c; + } + + public Color GetRemappedColor(Color original, int index) + { + return original.A > 0 ? c : original; + } + } +} diff --git a/OpenRa.Game/Chrome.cs b/OpenRa.Game/Chrome.cs index dfa218f8c8..6e69caf944 100644 --- a/OpenRa.Game/Chrome.cs +++ b/OpenRa.Game/Chrome.cs @@ -27,6 +27,17 @@ namespace OpenRa.Game readonly Animation repairButton; readonly Animation sellButton; readonly Animation pwrdownButton; + readonly Animation optionsButton; + + readonly Sprite optionsTop; + readonly Sprite optionsBottom; + readonly Sprite optionsLeft; + readonly Sprite optionsRight; + readonly Sprite optionsTopLeft; + readonly Sprite optionsTopRight; + readonly Sprite optionsBottomLeft; + readonly Sprite optionsBottomRight; + readonly Sprite optionsBackground; readonly SpriteRenderer buildPaletteRenderer; readonly Animation cantBuild; @@ -41,7 +52,8 @@ namespace OpenRa.Game readonly int paletteColumns; readonly int2 paletteOrigin; - + bool hadRadar = false; + bool optionsPressed = false; const int MinRows = 4; public Chrome(Renderer r) @@ -82,6 +94,19 @@ namespace OpenRa.Game pwrdownButton = new Animation("repair"); pwrdownButton.PlayRepeating("normal"); + optionsButton = new Animation("tabs"); + optionsButton.PlayRepeating("left-normal"); + + optionsLeft = SpriteSheetBuilder.LoadAllSprites("dd-left")[0]; + optionsRight = SpriteSheetBuilder.LoadAllSprites("dd-right")[0]; + optionsTop = SpriteSheetBuilder.LoadAllSprites("dd-top")[0]; + optionsBottom = SpriteSheetBuilder.LoadAllSprites("dd-botm")[0]; + optionsTopLeft = SpriteSheetBuilder.LoadAllSprites("dd-crnr")[0]; + optionsTopRight = SpriteSheetBuilder.LoadAllSprites("dd-crnr")[1]; + optionsBottomLeft = SpriteSheetBuilder.LoadAllSprites("dd-crnr")[2]; + optionsBottomRight = SpriteSheetBuilder.LoadAllSprites("dd-crnr")[3]; + optionsBackground = SpriteSheetBuilder.LoadAllSprites("dd-bkgnd")[Game.CosmeticRandom.Next(4)]; + blank = SheetBuilder.Add(new Size(64, 48), 16); sprites = groups @@ -132,7 +157,7 @@ namespace OpenRa.Game Game.LocalPlayer.PowerDrained, Game.LocalPlayer.PowerProvided, Game.LocalPlayer.IsReady ? "Yes" : "No" - ), new int2(140, 5), Color.White); + ), new int2(140, 15), Color.White); PerfHistory.Render(renderer, Game.worldRenderer.lineRenderer); @@ -149,6 +174,7 @@ namespace OpenRa.Game int paletteHeight = DrawBuildPalette(currentTab); DrawBuildTabs(paletteHeight); DrawChat(); + DrawOptionsMenu(); } void DrawMinimap() @@ -156,7 +182,11 @@ namespace OpenRa.Game var hasRadar = Game.world.Actors.Any(a => a.Owner == Game.LocalPlayer && a.traits.Contains() && a.traits.Get().IsActive()); - + + if (hasRadar != hadRadar) + Sound.Play((hasRadar) ? "radaron2.aud" : "radardn1.aud"); + hadRadar = hasRadar; + if (hasRadar) Game.minimap.Draw(new float2(Game.viewport.Width - 256, 8)); } @@ -296,6 +326,21 @@ namespace OpenRa.Game AddButton(chronoshiftRect, isLmb => HandleChronosphereButton()); } buildPaletteRenderer.DrawSprite(repairButton.Image, chronoshiftDrawPos, PaletteType.Chrome); + + // Iron Curtain + Rectangle curtainRect = new Rectangle(6, 14+50, repairButton.Image.bounds.Width, repairButton.Image.bounds.Height); + var curtainDrawPos = Game.viewport.Location + new float2(curtainRect.Location); + + var hasCurtain = Game.world.Actors.Any(a => a.Owner == Game.LocalPlayer && a.traits.Contains()); + + if (!hasCurtain) + repairButton.ReplaceAnim("disabled"); + else + { + //repairButton.ReplaceAnim(Game.controller.orderGenerator is RepairOrderGenerator ? "pressed" : "normal"); + AddButton(curtainRect, isLmb => HandleIronCurtainButton()); + } + buildPaletteRenderer.DrawSprite(repairButton.Image, curtainDrawPos, PaletteType.Chrome); // Repair @@ -339,6 +384,52 @@ namespace OpenRa.Game buildPaletteRenderer.DrawSprite(pwrdownButton.Image, pwrdownDrawPos, PaletteType.Chrome); } buildPaletteRenderer.Flush(); + + //Options + Rectangle optionsRect = new Rectangle(0 + 40,0, optionsButton.Image.bounds.Width, + optionsButton.Image.bounds.Height); + + var optionsDrawPos = Game.viewport.Location + new float2(optionsRect.Location); + + optionsButton.ReplaceAnim(optionsPressed ? "left-pressed" : "left-normal"); + + AddButton(optionsRect, isLmb => optionsPressed = !optionsPressed); + buildPaletteRenderer.DrawSprite(optionsButton.Image, optionsDrawPos, PaletteType.Chrome); + buildPaletteRenderer.Flush(); + + renderer.DrawText("Options", new int2(80, -2) , Color.White); + } + + void DrawOptionsMenu() + { + if (optionsPressed){ + var menuDrawPos = Game.viewport.Location + new float2(Game.viewport.Width/2, Game.viewport.Height/2); + var width = optionsTop.bounds.Width + optionsTopLeft.bounds.Width + optionsTopRight.bounds.Width; + var height = optionsLeft.bounds.Height + optionsTopLeft.bounds.Height + optionsBottomLeft.bounds.Height; + var adjust = 8; + + menuDrawPos = menuDrawPos + new float2(-width/2, -height/2); + + var backgroundDrawPos = menuDrawPos + new float2( (width - optionsBackground.bounds.Width)/2, (height - optionsBackground.bounds.Height)/2); + + //draw background + buildPaletteRenderer.DrawSprite(optionsBackground, backgroundDrawPos, PaletteType.Chrome); + + //draw borders + buildPaletteRenderer.DrawSprite(optionsTopLeft, menuDrawPos, PaletteType.Chrome); + buildPaletteRenderer.DrawSprite(optionsLeft, menuDrawPos + new float2(0, optionsTopLeft.bounds.Height), PaletteType.Chrome); + buildPaletteRenderer.DrawSprite(optionsBottomLeft, menuDrawPos + new float2(0, optionsTopLeft.bounds.Height + optionsLeft.bounds.Height), PaletteType.Chrome); + + buildPaletteRenderer.DrawSprite(optionsTop, menuDrawPos + new float2(optionsTopLeft.bounds.Width, 0), PaletteType.Chrome); + buildPaletteRenderer.DrawSprite(optionsTopRight, menuDrawPos + new float2(optionsTopLeft.bounds.Width + optionsTop.bounds.Width, 0), PaletteType.Chrome); + + buildPaletteRenderer.DrawSprite(optionsBottom, menuDrawPos + new float2(optionsTopLeft.bounds.Width, optionsTopLeft.bounds.Height + optionsLeft.bounds.Height +adjust), PaletteType.Chrome); + buildPaletteRenderer.DrawSprite(optionsBottomRight, menuDrawPos + new float2(optionsBottomLeft.bounds.Width + optionsBottom.bounds.Width, optionsTopLeft.bounds.Height + optionsLeft.bounds.Height), PaletteType.Chrome); + + buildPaletteRenderer.DrawSprite(optionsRight, menuDrawPos + new float2(optionsTopLeft.bounds.Width + optionsTop.bounds.Width + adjust + 1, optionsTopRight.bounds.Height), PaletteType.Chrome); + + buildPaletteRenderer.Flush(); + } } void HandleChronosphereButton() @@ -347,6 +438,12 @@ namespace OpenRa.Game Sound.Play("slcttgt1.aud"); } + void HandleIronCurtainButton() + { + if (Game.controller.ToggleInputMode()) + Sound.Play("slcttgt1.aud"); + } + void DrawChat() { var chatpos = new int2(400, Game.viewport.Height - 20); @@ -606,7 +703,10 @@ namespace OpenRa.Game p += new int2(0, 15); if (!Rules.TechTree.CanBuild(info, Game.LocalPlayer, buildings)) { - var prereqs = info.Prerequisite.Select(a => Rules.UnitInfo[a.ToLowerInvariant()].Description); + var prereqs = info.Prerequisite + .Select(a => Rules.UnitInfo[a.ToLowerInvariant()]) + .Where( u => u.Owner.Any( o => o == Game.LocalPlayer.Race ) ) + .Select( a => a.Description ); renderer.DrawText("Requires {0}".F( string.Join( ", ", prereqs.ToArray() ) ), p.ToInt2(), Color.White); } diff --git a/OpenRa.Game/Cursor.cs b/OpenRa.Game/Cursor.cs index 54ebacc76d..89b5f12d94 100644 --- a/OpenRa.Game/Cursor.cs +++ b/OpenRa.Game/Cursor.cs @@ -25,6 +25,7 @@ namespace OpenRa.Game public static Cursor DeployBlocked { get { return new Cursor("deploy-blocked"); } } public static Cursor Chronoshift { get { return new Cursor("chrono-target"); } } public static Cursor ChronoshiftSelect { get { return new Cursor("chrono-select"); } } + public static Cursor Ability { get { return new Cursor("ability"); } } public static Cursor C4 { get { return new Cursor("c4"); } } public static Cursor Capture { get { return new Cursor("capture"); } } public static Cursor Heal { get { return new Cursor("heal"); } } diff --git a/OpenRa.Game/Effects/Bullet.cs b/OpenRa.Game/Effects/Bullet.cs index f342a2d76d..5104585dd3 100644 --- a/OpenRa.Game/Effects/Bullet.cs +++ b/OpenRa.Game/Effects/Bullet.cs @@ -31,6 +31,8 @@ namespace OpenRa.Game.Effects FiredBy = firedBy; Src = src; Dest = dest; + SrcAltitude = srcAltitude; + DestAltitude = destAltitude; VisualDest = Dest + new int2( Game.CosmeticRandom.Next(-10, 10), Game.CosmeticRandom.Next(-10, 10)); diff --git a/OpenRa.Game/Effects/FlashTarget.cs b/OpenRa.Game/Effects/FlashTarget.cs new file mode 100644 index 0000000000..21e0fe8c85 --- /dev/null +++ b/OpenRa.Game/Effects/FlashTarget.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using OpenRa.Game.Traits; +using OpenRa.Game.Graphics; + +namespace OpenRa.Game.Effects +{ + class FlashTarget : IEffect + { + Actor target; + int remainingTicks = 4; + + public FlashTarget(Actor target) + { + this.target = target; + foreach (var e in Game.world.Effects.OfType().Where(a => a.target == target).ToArray()) + Game.world.Remove(e); + } + + public void Tick() + { + if (--remainingTicks == 0) + Game.world.AddFrameEndTask(w => w.Remove(this)); + } + + public IEnumerable Render() + { + if (remainingTicks % 2 == 0) + foreach (var r in target.Render()) + yield return r.WithPalette(PaletteType.Highlight); + } + } +} diff --git a/OpenRa.Game/Effects/InvulnEffect.cs b/OpenRa.Game/Effects/InvulnEffect.cs new file mode 100644 index 0000000000..b7f28a0a49 --- /dev/null +++ b/OpenRa.Game/Effects/InvulnEffect.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using OpenRa.Game.Graphics; +using OpenRa.Game.Traits; + +namespace OpenRa.Game.Effects +{ + class InvulnEffect : IEffect + { + Actor a; + IronCurtainable b; + + public InvulnEffect(Actor a) + { + this.a = a; + this.b = a.traits.Get(); + } + + public void Tick() + { + if (a.IsDead || b.GetDamageModifier() > 0) + Game.world.AddFrameEndTask(w => w.Remove(this)); + } + + public IEnumerable Render() + { + foreach (var r in a.Render()) + yield return r.WithPalette(PaletteType.Invuln); + } + } +} diff --git a/OpenRa.Game/Effects/Missile.cs b/OpenRa.Game/Effects/Missile.cs index ee5d2ce88a..c0247567d1 100644 --- a/OpenRa.Game/Effects/Missile.cs +++ b/OpenRa.Game/Effects/Missile.cs @@ -21,7 +21,7 @@ namespace OpenRa.Game.Effects int Altitude; public Missile(string weapon, Player owner, Actor firedBy, - int2 src, Actor target, int altitude) + int2 src, Actor target, int altitude, int facing) { Weapon = Rules.WeaponInfo[weapon]; Projectile = Rules.ProjectileInfo[Weapon.Projectile]; @@ -31,9 +31,7 @@ namespace OpenRa.Game.Effects Target = target; Pos = src.ToFloat2(); Altitude = altitude; - - /* todo: initial facing should be turret facing, or unit facing if we're not turreted */ - Facing = Traits.Util.GetFacing( Target.CenterLocation - src.ToFloat2(), 0 ); + Facing = facing; if (Projectile.Image != null && Projectile.Image != "none") { @@ -47,7 +45,7 @@ namespace OpenRa.Game.Effects } const int MissileCloseEnough = 7; - const float Scale = .3f; + const float Scale = .2f; public void Tick() { @@ -73,14 +71,16 @@ namespace OpenRa.Game.Effects return; } - var move = (Scale * Weapon.Speed / dist.Length) * dist; + var speed = Scale * Weapon.Speed * ((targetAltitude > 0 && Weapon.TurboBoost) ? 1.5f : 1f); + + var angle = Facing / 128f * Math.PI; + var move = speed * -float2.FromAngle((float)angle); Pos += move; if (Projectile.Animates) Game.world.AddFrameEndTask(w => w.Add(new Smoke((Pos - 1.5f * move - new int2( 0, Altitude )).ToInt2()))); // todo: running out of fuel - // todo: turbo boost vs aircraft } public IEnumerable Render() diff --git a/OpenRa.Game/Effects/PowerDownIndicator.cs b/OpenRa.Game/Effects/PowerDownIndicator.cs new file mode 100644 index 0000000000..185366a078 --- /dev/null +++ b/OpenRa.Game/Effects/PowerDownIndicator.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using OpenRa.Game.Graphics; +using OpenRa.Game.Traits; + +namespace OpenRa.Game.Effects +{ + class PowerDownIndicator : IEffect + { + Actor a; + Building b; + Animation anim = new Animation("powerdown"); + + public PowerDownIndicator(Actor a) + { + this.a = a; + this.b = a.traits.Get(); + anim.PlayRepeating("disabled"); + } + + public void Tick() + { + if (!b.Disabled || a.IsDead) + Game.world.AddFrameEndTask(w => w.Remove(this)); + } + + public IEnumerable Render() + { + foreach (var r in a.Render()) + yield return r.WithPalette(PaletteType.Disabled); + + if (b.ManuallyDisabled) + yield return new Renderable(anim.Image, + a.CenterLocation - .5f * anim.Image.size, PaletteType.Chrome); + } + } +} diff --git a/OpenRa.Game/GameRules/AftermathInfo.cs b/OpenRa.Game/GameRules/AftermathInfo.cs index 96c403d00c..60511c4d5c 100644 --- a/OpenRa.Game/GameRules/AftermathInfo.cs +++ b/OpenRa.Game/GameRules/AftermathInfo.cs @@ -3,12 +3,12 @@ namespace OpenRa.Game.GameRules { class AftermathInfo { - public readonly int MTankDistance; - public readonly float QuakeUnitDamage; - public readonly float QuakeBuildingDamage; - public readonly float QuakeInfantryDamage; - public readonly int QuakeDelay; - public readonly int CarrierLaunchDelay; - public readonly int ChronoTankDuration; + public readonly int MTankDistance = 0; + public readonly float QuakeUnitDamage = 0f; + public readonly float QuakeBuildingDamage = 0f; + public readonly float QuakeInfantryDamage = 0f; + public readonly int QuakeDelay = 0; + public readonly int CarrierLaunchDelay = 0; + public readonly int ChronoTankDuration = 0; } } diff --git a/OpenRa.Game/GameRules/GeneralInfo.cs b/OpenRa.Game/GameRules/GeneralInfo.cs index e351a7be66..cbd26f3d6b 100644 --- a/OpenRa.Game/GameRules/GeneralInfo.cs +++ b/OpenRa.Game/GameRules/GeneralInfo.cs @@ -1,126 +1,127 @@  +using System; namespace OpenRa.Game.GameRules { class GeneralInfo { /* Crates */ - public readonly int CrateMinimum; - public readonly int CrateMaximum; - public readonly float CrateRadius; - public readonly float CrateRegen; - public readonly string UnitCrateType; /* =none, if any */ - public readonly float WaterCrateChance; + public readonly int CrateMinimum = 0; + public readonly int CrateMaximum = 0; + public readonly float CrateRadius = 0; + public readonly float CrateRegen = 0; + public readonly string UnitCrateType = null; /* =none, if any */ + public readonly float WaterCrateChance = 0; - public readonly int SoloCrateMoney; - public readonly string SilverCrate; /* solo play crate contents */ - public readonly string WaterCrate; - public readonly string WoodCrate; + public readonly int SoloCrateMoney = 2000; + public readonly string SilverCrate = null; /* solo play crate contents */ + public readonly string WaterCrate = null; + public readonly string WoodCrate = null; /* Special Weapons */ - public readonly int ChronoDuration; - public readonly bool ChronoKillCargo; - public readonly int ChronoTechLevel; - public readonly int GPSTechLevel; - public readonly int GapRadius; - public readonly float GapRegenInterval; - public readonly float IronCurtain; /* minutes */ - public readonly int ParaTech; - public readonly int ParabombTech; - public readonly int RadarJamRadius; - public readonly int SpyPlaneTech; - public readonly int BadgerBombCount; + public readonly int ChronoDuration = 0; + public readonly bool ChronoKillCargo = true; + [Obsolete] public readonly int ChronoTechLevel = -1; + [Obsolete] public readonly int GPSTechLevel = -1; + public readonly int GapRadius = 0; + public readonly float GapRegenInterval =0; + public readonly float IronCurtain = 0; /* minutes */ + [Obsolete] public readonly int ParaTech = -1; + [Obsolete] public readonly int ParabombTech = -1; + public readonly int RadarJamRadius = 1; + [Obsolete] public readonly int SpyPlaneTech = -1; + public readonly int BadgerBombCount = 1; /* Chrono Side Effects */ - public readonly float QuakeChance; - public readonly float QuakeDamage; /* percent */ - public readonly float VortexChance; - public readonly int VortexDamage; - public readonly int VortexRange; - public readonly int VortexSpeed; + public readonly float QuakeChance = 0; + public readonly float QuakeDamage = 0; /* percent */ + public readonly float VortexChance = 0; + public readonly int VortexDamage = 0; + public readonly int VortexRange = 0; + public readonly int VortexSpeed = 0; /* Repair & Refit */ - public readonly float RefundPercent; - public readonly float ReloadRate; - public readonly float RepairPercent; - public readonly float RepairRate; - public readonly int RepairStep; - public readonly float URepairPercent; - public readonly int URepairStep; + public readonly float RefundPercent = 0; + public readonly float ReloadRate = 0; + public readonly float RepairPercent = 0; + public readonly float RepairRate = 0; + public readonly int RepairStep = 0; + public readonly float URepairPercent = 0; + public readonly int URepairStep = 0; /* Combat & Damage */ - public readonly float TurboBoost; - public readonly int APMineDamage; - public readonly int AVMineDamage; - public readonly int AtomDamage; - public readonly float BallisticScatter; - public readonly int BridgeStrength; - public readonly float C4Delay; - public readonly float Crush; - public readonly float ExpSpread; - public readonly int FireSupress; - public readonly float HomingScatter; - public readonly int MaxDamage; - public readonly int MinDamage; - public readonly bool OreExplosive; - public readonly bool PlayerAutoCrush; - public readonly bool PlayerReturnFire; - public readonly bool PlayerScatter; - public readonly float ProneDamage; - public readonly bool TreeTargeting; - public readonly int Incoming; + public readonly float TurboBoost = 1.5f; + public readonly int APMineDamage = 0; + public readonly int AVMineDamage = 0; + public readonly int AtomDamage = 0; + public readonly float BallisticScatter = 0; + public readonly int BridgeStrength = 0; + public readonly float C4Delay = 0; + public readonly float Crush = 0; + public readonly float ExpSpread = 0; + public readonly int FireSupress = 0; + public readonly float HomingScatter = 0; + public readonly int MaxDamage = 0; + public readonly int MinDamage = 0; + public readonly bool OreExplosive = false; + public readonly bool PlayerAutoCrush = false; + public readonly bool PlayerReturnFire = false; + public readonly bool PlayerScatter = false; + public readonly float ProneDamage = 0; + public readonly bool TreeTargeting = false; + public readonly int Incoming = 0; /* Income & Production */ - public readonly int BailCount; - public readonly float BuildSpeed; - public readonly float BuildupTime; - public readonly int GemValue; - public readonly int GoldValue; - public readonly float GrowthRate; - public readonly bool OreGrows; - public readonly bool OreSpreads; - public readonly float OreTruckRate; - public readonly bool SeparateAircraft; - public readonly float SurvivorRate; + public readonly int BailCount = 0; + public readonly float BuildSpeed = 0; + public readonly float BuildupTime = 0; + public readonly int GemValue = 0; + public readonly int GoldValue = 0; + public readonly float GrowthRate = 0; + public readonly bool OreGrows = true; + public readonly bool OreSpreads = true; + public readonly float OreTruckRate = 0; + public readonly bool SeparateAircraft = true; + public readonly float SurvivorRate = 0; /* Audo/Visual Map Controls */ - public readonly bool AllyReveal; - public readonly float ConditionRed; - public readonly float ConditionYellow; - public readonly int DropZoneRadius; - public readonly bool EnemyHealth; - public readonly int Gravity; - public readonly float IdleActionFrequency; - public readonly float MessageDelay; - public readonly float MovieTime; - public readonly bool NamedCivilians; - public readonly float SavourDelay; - public readonly int ShroudRate; - public readonly int SpeakDelay; - public readonly int TimerWarning; - public readonly bool FlashLowPower; + public readonly bool AllyReveal = true; + public readonly float ConditionRed = 0; + public readonly float ConditionYellow = 0; + public readonly int DropZoneRadius = 0; + public readonly bool EnemyHealth = true; + public readonly int Gravity = 0; + public readonly float IdleActionFrequency = 0; + public readonly float MessageDelay = 0; + public readonly float MovieTime = 0; + public readonly bool NamedCivilians = false; + public readonly float SavourDelay = 0; + public readonly int ShroudRate = 0; + public readonly int SpeakDelay = 0; + public readonly int TimerWarning = 0; + public readonly bool FlashLowPower = false; /* Computer & Movement Controls */ - public readonly bool CurleyShuffle; - public readonly float BaseBias; - public readonly float BaseDefenseDelay; - public readonly float CloseEnough; - public readonly int DamageDelay; - public readonly int GameSpeeBias; - public readonly int LZScanRadius; - public readonly bool MineAware; - public readonly float Stray; - public readonly float SubmergeDelay; - public readonly float SuspendDelay; - public readonly int SuspendPriority; - public readonly float TeamDelay; + public readonly bool CurleyShuffle = false; + public readonly float BaseBias = 0; + public readonly float BaseDefenseDelay = 0; + public readonly float CloseEnough = 0; + public readonly int DamageDelay = 0; + public readonly int GameSpeeBias = 0; + public readonly int LZScanRadius = 0; + public readonly bool MineAware = false; + public readonly float Stray = 0; + public readonly float SubmergeDelay = 0; + public readonly float SuspendDelay = 0; + public readonly int SuspendPriority = 0; + public readonly float TeamDelay = 0; /* Misc */ - public readonly bool FineDiffControl; - public readonly bool MCVUndeploy; + [Obsolete] + public readonly bool FineDiffControl = false; + public readonly bool MCVUndeploy = false; /* OpenRA-specific */ - public readonly float OreChance; /* chance of spreading to a - * particular eligible cell */ - public readonly int LowPowerSlowdown; /* build time multiplier */ + public readonly float OreChance = 0; /* chance of spreading to a particular eligible cell */ + public readonly int LowPowerSlowdown = 3; /* build time multiplier */ } } diff --git a/OpenRa.Game/GameRules/Rules.cs b/OpenRa.Game/GameRules/Rules.cs index 70a3acdb5e..3a9c135180 100755 --- a/OpenRa.Game/GameRules/Rules.cs +++ b/OpenRa.Game/GameRules/Rules.cs @@ -30,18 +30,18 @@ namespace OpenRa.Game AllRules = new IniFile( FileSystem.Open( "session.ini" ), FileSystem.Open( mapFileName ), + FileSystem.Open("aftermathUnits.ini"), + FileSystem.Open("units.ini"), FileSystem.Open( "aftrmath.ini" ), FileSystem.Open( "rules.ini" ), - FileSystem.Open( "aftermathUnits.ini" ), - FileSystem.Open( "units.ini" ), FileSystem.Open("campaignUnits.ini"), FileSystem.Open("trees.ini")); else AllRules = new IniFile( FileSystem.Open("session.ini"), FileSystem.Open(mapFileName), - FileSystem.Open("rules.ini"), FileSystem.Open("units.ini"), + FileSystem.Open("rules.ini"), FileSystem.Open("campaignUnits.ini"), FileSystem.Open("trees.ini")); diff --git a/OpenRa.Game/GameRules/TechTree.cs b/OpenRa.Game/GameRules/TechTree.cs index 463934796e..af0fd79ce4 100755 --- a/OpenRa.Game/GameRules/TechTree.cs +++ b/OpenRa.Game/GameRules/TechTree.cs @@ -35,8 +35,9 @@ namespace OpenRa.Game.GameRules return false; foreach( var p in unit.Prerequisite ) - if( playerBuildings[ p ].Count == 0 ) - return false; + if (Rules.UnitInfo[p.ToLowerInvariant()].Owner.Any(x => x == player.Race)) + if( playerBuildings[ p ].Count == 0 ) + return false; if( producesIndex[ Rules.UnitCategory[ unit.Name ] ].All( x => playerBuildings[ x.Name ].Count == 0 ) ) return false; diff --git a/OpenRa.Game/GameRules/UnitInfo.cs b/OpenRa.Game/GameRules/UnitInfo.cs index 63acbe1870..6054874fe0 100755 --- a/OpenRa.Game/GameRules/UnitInfo.cs +++ b/OpenRa.Game/GameRules/UnitInfo.cs @@ -1,4 +1,5 @@  +using System; namespace OpenRa.Game.GameRules { public enum ArmorType @@ -19,8 +20,8 @@ namespace OpenRa.Game.GameRules public readonly int Ammo = -1; public readonly ArmorType Armor = ArmorType.none; - public readonly bool DoubleOwned = false; - public readonly bool Cloakable = false; + [Obsolete] public readonly bool DoubleOwned = false; + [Obsolete] public readonly bool Cloakable = false; public readonly int Cost = 0; public readonly bool Crewed = false; public readonly bool Explodes = false; @@ -35,7 +36,7 @@ namespace OpenRa.Game.GameRules public readonly int ROT = 255; public readonly int Reload = 0; public readonly bool SelfHealing = false; - public readonly bool Sensors = false; // no idea what this does + [Obsolete] public readonly bool Sensors = false; // no idea what this does public readonly int Sight = 1; public readonly int Strength = 1; public readonly int TechLevel = -1; @@ -59,6 +60,10 @@ namespace OpenRa.Game.GameRules public readonly int UnloadFacing = 0; public readonly UnitMovementType[] PassengerTypes = null; + // weapon origins and firing angles within the turrets. 3 values per position. + public readonly int[] PrimaryLocalOffset = { }; + public readonly int[] SecondaryLocalOffset = { }; + public UnitInfo(string name) { Name = name; } } diff --git a/OpenRa.Game/Graphics/HardwarePalette.cs b/OpenRa.Game/Graphics/HardwarePalette.cs index 2092291dd0..147a639a34 100644 --- a/OpenRa.Game/Graphics/HardwarePalette.cs +++ b/OpenRa.Game/Graphics/HardwarePalette.cs @@ -5,8 +5,8 @@ namespace OpenRa.Game.Graphics { public enum PaletteType { - Gold, Blue, Red, Orange, Teal, Salmon, Green, Gray, - Shadow, Invuln, Chrome, Shroud, + Gold, Blue, Red, Orange, Teal, Salmon, Green, Gray, + Shadow, Invuln, Disabled, Highlight, Shroud, Chrome, }; class HardwarePalette : Sheet @@ -21,12 +21,14 @@ namespace OpenRa.Game.Graphics AddPalette(pal); foreach (string remap in new string[] { "blue", "red", "orange", "teal", "salmon", "green", "gray" }) - AddPalette(new Palette(pal, new PaletteRemap(FileSystem.Open(remap + ".rem")))); + AddPalette(new Palette(pal, new PlayerColorRemap(FileSystem.Open(remap + ".rem")))); - AddPalette(new Palette(pal, new PaletteRemap(Color.FromArgb(140, 0, 0, 0)))); - AddPalette(pal); // iron curtain. todo: remap! - AddPalette(pal); // chrome (it's like gold, but we're not going to hax it in palettemods) - AddPalette(new Palette(pal, new ShroudPaletteRemap())); + AddPalette(new Palette(pal, new SingleColorRemap(Color.FromArgb(140, 0, 0, 0)))); // Shadow + AddPalette(new Palette(pal, new SingleColorRemap(Color.FromArgb(128, 128, 0, 0)))); // Invulnerable (Iron Curtain) + AddPalette(new Palette(pal, new SingleColorRemap(Color.FromArgb(180, 0, 0, 0)))); // Disabled / Low power + AddPalette(new Palette(pal, new SingleColorRemap(Color.FromArgb(128, 255, 255, 255)))); // Highlight + AddPalette(new Palette(pal, new ShroudPaletteRemap())); // Shroud + AddPalette(pal); // Chrome (it's like gold, but we're not going to hax it in palettemods) } int AddPalette(Palette p) diff --git a/OpenRa.Game/Graphics/WorldRenderer.cs b/OpenRa.Game/Graphics/WorldRenderer.cs index 157b9ee2ab..1060f5467a 100644 --- a/OpenRa.Game/Graphics/WorldRenderer.cs +++ b/OpenRa.Game/Graphics/WorldRenderer.cs @@ -242,6 +242,9 @@ namespace OpenRa.Game.Graphics { foreach (var tag in tags.GetTags()) { + if (tag == TagType.None) + continue; + var tagImages = new Animation("pips"); tagImages.PlayRepeating(tagStrings[(int)tag]); spriteRenderer.DrawSprite(tagImages.Image, tagxyBase + tagxyOffset, PaletteType.Chrome); diff --git a/OpenRa.Game/OpenRa.Game.csproj b/OpenRa.Game/OpenRa.Game.csproj index 4aa7ac18d0..80e5bc199f 100644 --- a/OpenRa.Game/OpenRa.Game.csproj +++ b/OpenRa.Game/OpenRa.Game.csproj @@ -82,7 +82,10 @@ + + + @@ -96,8 +99,10 @@ + + @@ -215,6 +220,8 @@ + + @@ -304,4 +311,4 @@ --> - + \ No newline at end of file diff --git a/OpenRa.Game/Orders/ChronoshiftDestinationOrderGenerator.cs b/OpenRa.Game/Orders/ChronoshiftDestinationOrderGenerator.cs index e85e248752..a2cd5ef75f 100644 --- a/OpenRa.Game/Orders/ChronoshiftDestinationOrderGenerator.cs +++ b/OpenRa.Game/Orders/ChronoshiftDestinationOrderGenerator.cs @@ -35,6 +35,9 @@ namespace OpenRa.Game.Orders public Cursor GetCursor(int2 xy, MouseInput mi) { + if (!Game.LocalPlayer.Shroud.IsExplored(xy)) + return Cursor.MoveBlocked; + var movement = self.traits.WithInterface().FirstOrDefault(); return (movement.CanEnterCell(xy)) ? Cursor.Chronoshift : Cursor.MoveBlocked; } diff --git a/OpenRa.Game/Orders/ChronoshiftSelfDestinationOrderGenerator.cs b/OpenRa.Game/Orders/ChronoshiftSelfDestinationOrderGenerator.cs new file mode 100644 index 0000000000..5c949c1c51 --- /dev/null +++ b/OpenRa.Game/Orders/ChronoshiftSelfDestinationOrderGenerator.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Drawing; +using OpenRa.Game.Traits; + +namespace OpenRa.Game.Orders +{ + class ChronoshiftSelfDestinationOrderGenerator : IOrderGenerator + { + public readonly Actor self; + + public ChronoshiftSelfDestinationOrderGenerator(Actor self) + { + this.self = self; + } + + public IEnumerable Order(int2 xy, MouseInput mi) + { + if (mi.Button == MouseButton.Left) + { + Game.controller.CancelInputMode(); + yield break; + } + + yield return new Order("ChronoshiftSelf", self, null, xy, null); + } + + public void Tick() { } + public void Render() + { + Game.worldRenderer.DrawSelectionBox(self, Color.White, true); + } + + public Cursor GetCursor(int2 xy, MouseInput mi) + { + if (!Game.LocalPlayer.Shroud.IsExplored(xy)) + return Cursor.MoveBlocked; + + var movement = self.traits.WithInterface().FirstOrDefault(); + return (movement.CanEnterCell(xy)) ? Cursor.Chronoshift : Cursor.MoveBlocked; + } + } +} diff --git a/OpenRa.Game/Orders/ChronosphereSelectOrderGenerator.cs b/OpenRa.Game/Orders/ChronosphereSelectOrderGenerator.cs index ae7de0551f..4ff3de312d 100644 --- a/OpenRa.Game/Orders/ChronosphereSelectOrderGenerator.cs +++ b/OpenRa.Game/Orders/ChronosphereSelectOrderGenerator.cs @@ -24,7 +24,7 @@ namespace OpenRa.Game.Orders var loc = mi.Location + Game.viewport.Location; var underCursor = Game.FindUnits(loc, loc) .Where(a => a.Owner == Game.LocalPlayer - && a.traits.WithInterface().Any() + && a.traits.WithInterface().Any() && a.Info.Selectable).FirstOrDefault(); var unit = underCursor != null ? underCursor.Info as UnitInfo : null; diff --git a/OpenRa.Game/Orders/IronCurtainOrderGenerator.cs b/OpenRa.Game/Orders/IronCurtainOrderGenerator.cs new file mode 100644 index 0000000000..d73c787f19 --- /dev/null +++ b/OpenRa.Game/Orders/IronCurtainOrderGenerator.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using OpenRa.Game.GameRules; +using OpenRa.Game.Traits; + +namespace OpenRa.Game.Orders +{ + class IronCurtainOrderGenerator : IOrderGenerator + { + public IEnumerable Order(int2 xy, MouseInput mi) + { + if (mi.Button == MouseButton.Right) + Game.controller.CancelInputMode(); + + return OrderInner(xy, mi); + } + + IEnumerable OrderInner(int2 xy, MouseInput mi) + { + if (mi.Button == MouseButton.Left) + { + var loc = mi.Location + Game.viewport.Location; + var underCursor = Game.FindUnits(loc, loc) + .Where(a => a.Owner == Game.LocalPlayer + && a.traits.Contains() + && a.Info.Selectable).FirstOrDefault(); + + var unit = underCursor != null ? underCursor.Info as UnitInfo : null; + + if (unit != null) + { + yield return new Order("IronCurtain", underCursor, null, int2.Zero, null); + } + } + } + + public void Tick() + { + var hasStructure = Game.world.Actors + .Any(a => a.Owner == Game.LocalPlayer && a.traits.Contains()); + + if (!hasStructure) + Game.controller.CancelInputMode(); + } + + public void Render() { } + + public Cursor GetCursor(int2 xy, MouseInput mi) + { + mi.Button = MouseButton.Left; + return OrderInner(xy, mi).Any() + ? Cursor.Ability : Cursor.MoveBlocked; + } + } +} diff --git a/OpenRa.Game/Shroud.cs b/OpenRa.Game/Shroud.cs index 8f64f7365c..56cc9efb59 100644 --- a/OpenRa.Game/Shroud.cs +++ b/OpenRa.Game/Shroud.cs @@ -14,6 +14,11 @@ namespace OpenRa.Game Sprite[,] sprites = new Sprite[128, 128]; bool dirty; + public bool IsExplored(int2 xy) + { + return explored[ xy.X, xy.Y ]; + } + public void Explore(Actor a) { foreach (var t in Game.FindTilesInCircle((1f / Game.CellSize * a.CenterLocation).ToInt2(), a.Info.Sight)) diff --git a/OpenRa.Game/Traits/AttackBase.cs b/OpenRa.Game/Traits/AttackBase.cs index ebb76cbb6d..411770437b 100644 --- a/OpenRa.Game/Traits/AttackBase.cs +++ b/OpenRa.Game/Traits/AttackBase.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using IjwFramework.Types; using OpenRa.Game.Effects; @@ -72,7 +73,7 @@ namespace OpenRa.Game.Traits var unit = self.traits.GetOrDefault(); if (self.Info.Primary != null && CheckFire(self, unit, self.Info.Primary, ref primaryFireDelay, - self.Info.PrimaryOffset, ref primaryBurst)) + self.Info.PrimaryOffset, ref primaryBurst, self.Info.PrimaryLocalOffset)) { secondaryFireDelay = Math.Max(4, secondaryFireDelay); primaryRecoil = 1; @@ -80,7 +81,7 @@ namespace OpenRa.Game.Traits } if (self.Info.Secondary != null && CheckFire(self, unit, self.Info.Secondary, ref secondaryFireDelay, - self.Info.SecondaryOffset ?? self.Info.PrimaryOffset, ref secondaryBurst)) + self.Info.SecondaryOffset ?? self.Info.PrimaryOffset, ref secondaryBurst, self.Info.SecondaryLocalOffset)) { if (self.Info.SecondaryOffset != null) secondaryRecoil = 1; else primaryRecoil = 1; @@ -88,7 +89,7 @@ namespace OpenRa.Game.Traits } } - bool CheckFire(Actor self, Unit unit, string weaponName, ref int fireDelay, int[] offset, ref int burst) + bool CheckFire(Actor self, Unit unit, string weaponName, ref int fireDelay, int[] offset, ref int burst, int[] localOffset) { if (fireDelay > 0) return false; @@ -101,6 +102,17 @@ namespace OpenRa.Game.Traits if (!Combat.WeaponValidForTarget(weapon, target)) return false; + var numOffsets = (localOffset.Length + 2) / 3; + if (numOffsets == 0) numOffsets = 1; + var localOffsetForShot = burst % numOffsets; + var thisLocalOffset = localOffset.Skip(3 * localOffsetForShot).Take(3).ToArray(); + + var fireOffset = new[] { + offset.ElementAtOrDefault(0) + thisLocalOffset.ElementAtOrDefault(0), + offset.ElementAtOrDefault(1) + thisLocalOffset.ElementAtOrDefault(1), + offset.ElementAtOrDefault(2), + offset.ElementAtOrDefault(3) }; + if (--burst > 0) fireDelay = 5; else @@ -109,7 +121,7 @@ namespace OpenRa.Game.Traits burst = weapon.Burst; } - var firePos = self.CenterLocation.ToInt2() + Util.GetTurretPosition(self, unit, offset, 0f).ToInt2(); + var firePos = self.CenterLocation.ToInt2() + Util.GetTurretPosition(self, unit, fireOffset, 0f).ToInt2(); var thisTarget = target; // closure. var destUnit = thisTarget.traits.GetOrDefault(); @@ -121,9 +133,14 @@ namespace OpenRa.Game.Traits if( weapon.RenderAsTesla ) Game.world.Add( new TeslaZap( firePos, thisTarget.CenterLocation.ToInt2() ) ); - if( Rules.ProjectileInfo[ weapon.Projectile ].ROT != 0 ) + if (Rules.ProjectileInfo[weapon.Projectile].ROT != 0) + { + var fireFacing = thisLocalOffset.ElementAtOrDefault(2) + + (self.traits.Contains() ? self.traits.Get().turretFacing : unit.Facing); + Game.world.Add(new Missile(weaponName, self.Owner, self, - firePos, thisTarget, srcAltitude)); + firePos, thisTarget, srcAltitude, fireFacing)); + } else Game.world.Add(new Bullet(weaponName, self.Owner, self, firePos, thisTarget.CenterLocation.ToInt2(), srcAltitude, destAltitude)); @@ -155,6 +172,9 @@ namespace OpenRa.Game.Traits { self.CancelActivity(); QueueAttack(self, order); + + if (self.Owner == Game.LocalPlayer) + Game.world.AddFrameEndTask(w => w.Add(new FlashTarget(order.TargetActor))); } else target = null; diff --git a/OpenRa.Game/Traits/AttackTurreted.cs b/OpenRa.Game/Traits/AttackTurreted.cs index 1d0007e223..9c0f513251 100755 --- a/OpenRa.Game/Traits/AttackTurreted.cs +++ b/OpenRa.Game/Traits/AttackTurreted.cs @@ -26,10 +26,9 @@ namespace OpenRa.Game.Traits protected override void QueueAttack( Actor self, Order order ) { - var b = self.traits.Get(); - if (b != null && b.InsuffientPower()) + if (self.traits.Contains() && self.traits.Get().Disabled) return; - + const int RangeTolerance = 1; /* how far inside our maximum range we should try to sit */ /* todo: choose the appropriate weapon, when only one works against this target */ var weapon = order.Subject.Info.Primary ?? order.Subject.Info.Secondary; @@ -39,6 +38,7 @@ namespace OpenRa.Game.Traits Math.Max( 0, (int)Rules.WeaponInfo[ weapon ].Range - RangeTolerance ) ) ); target = order.TargetActor; + } bool buildComplete = false; diff --git a/OpenRa.Game/Traits/Building.cs b/OpenRa.Game/Traits/Building.cs index dfb870285c..bd48fc1c0d 100644 --- a/OpenRa.Game/Traits/Building.cs +++ b/OpenRa.Game/Traits/Building.cs @@ -9,13 +9,16 @@ using OpenRa.Game.Graphics; namespace OpenRa.Game.Traits { - class Building : INotifyDamage, IOrder, ITick, IRenderModifier + class Building : INotifyDamage, IOrder, ITick { readonly Actor self; public readonly BuildingInfo unitInfo; bool isRepairing = false; - bool isPoweredDown = false; - + bool manuallyDisabled = false; + public bool ManuallyDisabled { get { return manuallyDisabled; } } + public bool Disabled { get { return (manuallyDisabled || (unitInfo.Powered && self.Owner.GetPowerState() != PowerState.Normal)); } } + bool wasDisabled = false; + public Building(Actor self) { this.self = self; @@ -24,14 +27,9 @@ namespace OpenRa.Game.Traits * ((float2)self.Location + .5f * (float2)unitInfo.Dimensions); } - public bool InsuffientPower() - { - return (isPoweredDown || (unitInfo.Powered && self.Owner.GetPowerState() != PowerState.Normal)); - } - public int GetPowerUsage() { - if (isPoweredDown) + if (manuallyDisabled) return 0; if (unitInfo.Power > 0) /* todo: is this how real-ra scales it? */ @@ -40,32 +38,6 @@ namespace OpenRa.Game.Traits return unitInfo.Power; } - public Animation iconAnim; - public IEnumerable - ModifyRender(Actor self, IEnumerable rs) - { - if (!InsuffientPower()) - return rs; - - List nrs = new List(rs); - foreach(var r in rs) - { - // Need 2 shadows to make it dark enough - nrs.Add(r.WithPalette(PaletteType.Shadow)); - nrs.Add(r.WithPalette(PaletteType.Shadow)); - } - - if (isPoweredDown) - { - iconAnim = new Animation("powerdown"); - iconAnim.PlayRepeating("disabled"); - nrs.Add(new Renderable(iconAnim.Image, self.CenterLocation - 0.5f*iconAnim.Image.size, PaletteType.Chrome)); - } - - - return nrs; - } - public void Damaged(Actor self, AttackInfo e) { if (e.DamageState == DamageState.Dead) @@ -92,8 +64,8 @@ namespace OpenRa.Game.Traits if (order.OrderString == "PowerDown") { - isPoweredDown = !isPoweredDown; - Sound.Play((isPoweredDown) ? "bleep12.aud" : "bleep11.aud"); + manuallyDisabled = !manuallyDisabled; + Sound.Play((manuallyDisabled) ? "bleep12.aud" : "bleep11.aud"); } } @@ -101,6 +73,11 @@ namespace OpenRa.Game.Traits public void Tick(Actor self) { + // If the disabled state has changed since the last frame + if (Disabled ^ wasDisabled + && (wasDisabled = Disabled)) // Yes, I mean assignment + Game.world.AddFrameEndTask(w => w.Add(new PowerDownIndicator(self))); + if (!isRepairing) return; if (remainingTicks == 0) diff --git a/OpenRa.Game/Traits/ChronoshiftDeploy.cs b/OpenRa.Game/Traits/ChronoshiftDeploy.cs index 7c4252fc97..089ec5ab54 100644 --- a/OpenRa.Game/Traits/ChronoshiftDeploy.cs +++ b/OpenRa.Game/Traits/ChronoshiftDeploy.cs @@ -30,13 +30,17 @@ namespace OpenRa.Game.Traits { if (order.OrderString == "Deploy") { - Game.controller.orderGenerator = new ChronoshiftDestinationOrderGenerator(self); + Game.controller.orderGenerator = new ChronoshiftSelfDestinationOrderGenerator(self); return; } var movement = self.traits.WithInterface().FirstOrDefault(); - if (order.OrderString == "Chronoshift" && movement.CanEnterCell(order.TargetLocation)) + if (order.OrderString == "ChronoshiftSelf" && movement.CanEnterCell(order.TargetLocation)) { + // Cannot chronoshift into unexplored location + if (!Game.LocalPlayer.Shroud.IsExplored(order.TargetLocation)) + return; + Game.controller.CancelInputMode(); self.CancelActivity(); self.QueueActivity(new Activities.Teleport(order.TargetLocation)); diff --git a/OpenRa.Game/Traits/Chronoshiftable.cs b/OpenRa.Game/Traits/Chronoshiftable.cs index bf97b15542..7f86dca0be 100644 --- a/OpenRa.Game/Traits/Chronoshiftable.cs +++ b/OpenRa.Game/Traits/Chronoshiftable.cs @@ -6,7 +6,7 @@ using System.Drawing; namespace OpenRa.Game.Traits { - class Chronoshiftable : IOrder, ISpeedModifier, ITick, IChronoshiftable + class Chronoshiftable : IOrder, ISpeedModifier, ITick { // Return-to-sender logic int2 chronoshiftOrigin; @@ -46,12 +46,27 @@ namespace OpenRa.Game.Traits var movement = self.traits.WithInterface().FirstOrDefault(); if (order.OrderString == "Chronoshift" && movement.CanEnterCell(order.TargetLocation)) { - + // Cannot chronoshift into unexplored location + if (!Game.LocalPlayer.Shroud.IsExplored(order.TargetLocation)) + return; + // Set up return-to-sender info chronoshiftOrigin = self.Location; chronoshiftReturnTicks = (int)(Rules.General.ChronoDuration * 60 * 25); - - // TODO: Kill cargo if Rules.General.ChronoKillCargo says so + + var chronosphere = Game.world.Actors.Where(a => a.Owner == order.Subject.Owner && a.traits.Contains()).FirstOrDefault(); + + // Kill cargo + if (Rules.General.ChronoKillCargo && self.traits.Contains()) + { + var cargo = self.traits.Get(); + while (!cargo.IsEmpty(self)) + { + if (chronosphere != null) + chronosphere.Owner.Kills++; + cargo.Unload(self); + } + } // Set up the teleport Game.controller.CancelInputMode(); @@ -63,7 +78,6 @@ namespace OpenRa.Game.Traits a.traits.Get().DoChronoshift(); // Play chronosphere active anim - var chronosphere = Game.world.Actors.Where(a => a.Owner == order.Subject.Owner && a.traits.Contains()).FirstOrDefault(); if (chronosphere != null) chronosphere.traits.Get().PlayCustomAnim(chronosphere, "active"); } diff --git a/OpenRa.Game/Traits/DemoTruck.cs b/OpenRa.Game/Traits/DemoTruck.cs index add4849cff..b84bcdec27 100644 --- a/OpenRa.Game/Traits/DemoTruck.cs +++ b/OpenRa.Game/Traits/DemoTruck.cs @@ -6,29 +6,27 @@ using OpenRa.Game.Orders; namespace OpenRa.Game.Traits { - class DemoTruck : IOrder, ISpeedModifier, INotifyDamage, IChronoshiftable + class DemoTruck : Chronoshiftable, IOrder, INotifyDamage { readonly Actor self; public DemoTruck(Actor self) + : base(self) { this.self = self; } - - // Fire primary on Chronoshift - public Order IssueOrder(Actor self, int2 xy, MouseInput mi, Actor underCursor) + + public new void ResolveOrder(Actor self, Order order) { - return null; // Chronoshift order is issued through Chrome. - } - - public void ResolveOrder(Actor self, Order order) - { - if (order.OrderString == "ChronosphereSelect") - Game.controller.orderGenerator = new ChronoshiftDestinationOrderGenerator(self); - + // Override chronoshifting action to detonate vehicle var movement = self.traits.WithInterface().FirstOrDefault(); var chronosphere = Game.world.Actors.Where(a => a.Owner == order.Subject.Owner && a.traits.Contains()).FirstOrDefault(); if (order.OrderString == "Chronoshift" && movement.CanEnterCell(order.TargetLocation)) + { self.InflictDamage(chronosphere, self.Health, Rules.WarheadInfo["Super"]); + return; + } + + base.ResolveOrder(self, order); } // Fire primary on death @@ -49,11 +47,5 @@ namespace OpenRa.Game.Traits w => w.Add(new Bullet(self.Info.Primary, detonatedBy.Owner, detonatedBy, detonateLocation, detonateLocation, altitude, altitude))); } - - public float GetSpeedModifier() - { - // ARGH! You must not do this, it will desync! - return (Game.controller.orderGenerator is ChronoshiftDestinationOrderGenerator) ? 0f : 1f; - } } } diff --git a/OpenRa.Game/Traits/IronCurtain.cs b/OpenRa.Game/Traits/IronCurtain.cs new file mode 100644 index 0000000000..668b469bd1 --- /dev/null +++ b/OpenRa.Game/Traits/IronCurtain.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OpenRa.Game.Traits +{ + class IronCurtain + { + public IronCurtain(Actor self) {} + } +} diff --git a/OpenRa.Game/Traits/IronCurtainable.cs b/OpenRa.Game/Traits/IronCurtainable.cs new file mode 100644 index 0000000000..685075273f --- /dev/null +++ b/OpenRa.Game/Traits/IronCurtainable.cs @@ -0,0 +1,47 @@ +using OpenRa.Game.Traits; +using OpenRa.Game.Orders; +using System.Collections.Generic; +using System.Linq; +using System.Drawing; +using OpenRa.Game.Effects; +using OpenRa.Game.Graphics; + +namespace OpenRa.Game.Traits +{ + class IronCurtainable: IOrder, IDamageModifier, ITick + { + int RemainingTicks = 0; + + public IronCurtainable(Actor self) { } + + public void Tick(Actor self) + { + if (RemainingTicks > 0) + RemainingTicks--; + } + public float GetDamageModifier() + { + return (RemainingTicks > 0) ? 0.0f : 1.0f; + } + + public Order IssueOrder(Actor self, int2 xy, MouseInput mi, Actor underCursor) + { + return null; // Chronoshift order is issued through Chrome. + } + + public void ResolveOrder(Actor self, Order order) + { + if (order.OrderString == "IronCurtain") + { + Game.controller.CancelInputMode(); + Game.world.AddFrameEndTask(w => w.Add(new InvulnEffect(self))); + RemainingTicks = (int)(Rules.General.IronCurtain * 60 * 25); + Sound.Play("ironcur9.aud"); + // Play active anim + var ironCurtain = Game.world.Actors.Where(a => a.Owner == order.Subject.Owner && a.traits.Contains()).FirstOrDefault(); + if (ironCurtain != null) + ironCurtain.traits.Get().PlayCustomAnim(ironCurtain, "active"); + } + } + } +} diff --git a/OpenRa.Game/Traits/Production.cs b/OpenRa.Game/Traits/Production.cs index c4d1413f39..e24eec3a68 100755 --- a/OpenRa.Game/Traits/Production.cs +++ b/OpenRa.Game/Traits/Production.cs @@ -4,8 +4,11 @@ using System.Collections.Generic; namespace OpenRa.Game.Traits { - class Production : IProducer, ITags + class Production : IOrder, IProducer, ITags { + bool isPrimary = false; + public bool IsPrimary { get { return isPrimary; } } + public Production( Actor self ) { } public virtual int2? CreationLocation( Actor self, UnitInfo producee ) @@ -50,7 +53,45 @@ namespace OpenRa.Game.Traits public IEnumerable GetTags() { - yield return (true) ? TagType.Primary : TagType.None; + yield return (isPrimary) ? TagType.Primary : TagType.None; + } + + public Order IssueOrder(Actor self, int2 xy, MouseInput mi, Actor underCursor) + { + if (mi.Button == MouseButton.Right && underCursor == self) + return new Order("Deploy", self, null, int2.Zero, null); + return null; + } + + public void ResolveOrder(Actor self, Order order) + { + if (order.OrderString == "Deploy") + { + SetPrimaryProducer(self, !isPrimary); + } + } + + public void SetPrimaryProducer(Actor self, bool state) + { + if (state == false) + { + isPrimary = false; + return; + } + + // Cancel existing primaries + foreach (var p in (self.Info as BuildingInfo).Produces) + { + foreach (var b in Game.world.Actors.Where(x => x.traits.Contains() + && x.Owner == self.Owner + && x.traits.Get().IsPrimary == true + && (x.Info as BuildingInfo).Produces.Contains(p))) + { + b.traits.Get().SetPrimaryProducer(b, false); + } + } + isPrimary = true; + Sound.Play("pribldg1.aud"); } } } diff --git a/OpenRa.Game/Traits/ProductionQueue.cs b/OpenRa.Game/Traits/ProductionQueue.cs index 869277d5c8..824229b64d 100755 --- a/OpenRa.Game/Traits/ProductionQueue.cs +++ b/OpenRa.Game/Traits/ProductionQueue.cs @@ -128,12 +128,36 @@ namespace OpenRa.Game.Traits { var newUnitType = Rules.UnitInfo[ name ]; var producerTypes = Rules.TechTree.UnitBuiltAt( newUnitType ); - - // TODO: choose producer based on "primary building" - var producer = Game.world.Actors - .Where( x => producerTypes.Contains( x.Info ) && x.Owner == self.Owner ) - .FirstOrDefault(); - + Actor producer = null; + + // Prioritise primary structure in build order + var primaryProducers = Game.world.Actors + .Where(x => x.traits.Contains() + && producerTypes.Contains(x.Info) + && x.Owner == self.Owner + && x.traits.Get().IsPrimary == true); + + foreach (var p in primaryProducers) + { + // Ignore buildings that are disabled + if (p.traits.Contains() && p.traits.Get().Disabled) + continue; + producer = p; + break; + } + + // TODO: Be smart about disabled buildings. Units in progress should be paused(?) + // Ignore this for now + + // Pick the first available producer + if (producer == null) + { + producer = Game.world.Actors + .Where( x => producerTypes.Contains( x.Info ) && x.Owner == self.Owner ) + .FirstOrDefault(); + } + + // Something went wrong somewhere... if( producer == null ) { CancelProduction( Rules.UnitCategory[ name ] ); diff --git a/OpenRa.Game/Traits/ProvidesRadar.cs b/OpenRa.Game/Traits/ProvidesRadar.cs index 41c3ced78c..7a4ba5e0c6 100644 --- a/OpenRa.Game/Traits/ProvidesRadar.cs +++ b/OpenRa.Game/Traits/ProvidesRadar.cs @@ -19,7 +19,7 @@ namespace OpenRa.Game.Traits // Check if powered var b = self.traits.Get(); - if (b != null && b.InsuffientPower()) + if (b != null && b.Disabled) return false; return true; diff --git a/OpenRa.Game/Traits/RenderSimple.cs b/OpenRa.Game/Traits/RenderSimple.cs index 48f9ec7837..d5db307c09 100644 --- a/OpenRa.Game/Traits/RenderSimple.cs +++ b/OpenRa.Game/Traits/RenderSimple.cs @@ -33,6 +33,7 @@ namespace OpenRa.Game.Traits public Animation Animation; public Func OffsetFunc; public Func DisableFunc; + public int ZOffset; public AnimationWithOffset( Animation a ) : this( a, null, null ) @@ -48,10 +49,9 @@ namespace OpenRa.Game.Traits public Renderable Image( Actor self ) { - if( OffsetFunc != null ) - return Util.Centered( self, Animation.Image, self.CenterLocation + OffsetFunc() ); - else - return Util.Centered( self, Animation.Image, self.CenterLocation ); + var r = Util.Centered( self, Animation.Image, self.CenterLocation + + (OffsetFunc != null ? OffsetFunc() : float2.Zero) ); + return ZOffset != 0 ? r.WithZOffset(ZOffset) : r; } public static implicit operator AnimationWithOffset( Animation a ) diff --git a/OpenRa.Game/Traits/RenderUnitTurreted.cs b/OpenRa.Game/Traits/RenderUnitTurreted.cs index c06614f4bd..f05caa57f4 100644 --- a/OpenRa.Game/Traits/RenderUnitTurreted.cs +++ b/OpenRa.Game/Traits/RenderUnitTurreted.cs @@ -19,16 +19,16 @@ namespace OpenRa.Game.Traits turretAnim.PlayFacing( "turret", () => turreted.turretFacing ); if( self.Info.PrimaryOffset != null ) - anims.Add( "turret_1", new AnimationWithOffset( + anims.Add("turret_1", new AnimationWithOffset( turretAnim, - () => Util.GetTurretPosition( self, unit, self.Info.PrimaryOffset, attack.primaryRecoil ), - null ) ); + () => Util.GetTurretPosition(self, unit, self.Info.PrimaryOffset, attack.primaryRecoil), + null) { ZOffset = 1 }); if( self.Info.SecondaryOffset != null ) - anims.Add( "turret_2", new AnimationWithOffset( + anims.Add("turret_2", new AnimationWithOffset( turretAnim, - () => Util.GetTurretPosition( self, unit, self.Info.SecondaryOffset, attack.secondaryRecoil ), - null ) ); + () => Util.GetTurretPosition(self, unit, self.Info.SecondaryOffset, attack.secondaryRecoil), + null) { ZOffset = 1 }); if( self.Info.MuzzleFlash ) { diff --git a/OpenRa.Game/Traits/TraitsInterfaces.cs b/OpenRa.Game/Traits/TraitsInterfaces.cs index de5bc49c19..5ee0a485ef 100644 --- a/OpenRa.Game/Traits/TraitsInterfaces.cs +++ b/OpenRa.Game/Traits/TraitsInterfaces.cs @@ -22,7 +22,11 @@ namespace OpenRa.Game.Traits Order IssueOrder( Actor self, int2 xy, MouseInput mi, Actor underCursor ); void ResolveOrder( Actor self, Order order ); } - interface IProducer { bool Produce( Actor self, UnitInfo producee ); } + interface IProducer + { + bool Produce( Actor self, UnitInfo producee ); + void SetPrimaryProducer(Actor self, bool isPrimary); + } interface IOccupySpace { IEnumerable OccupiedCells(); } interface INotifyAttack { void Attacking(Actor self); } interface IRenderModifier { IEnumerable ModifyRender(Actor self, IEnumerable r); } @@ -43,7 +47,6 @@ namespace OpenRa.Game.Traits bool IsCrushableBy(UnitMovementType umt, Player player); bool IsPathableCrush(UnitMovementType umt, Player player); } - interface IChronoshiftable{} struct Renderable { public readonly Sprite Sprite; diff --git a/aftermathUnits.ini b/aftermathUnits.ini index d352dc379c..9e07413c2c 100755 --- a/aftermathUnits.ini +++ b/aftermathUnits.ini @@ -54,15 +54,16 @@ MECH [SHOK] Description=Tesla Trooper -Traits=Unit, Mobile, AttackBase, RenderInfantry, TakeCover +Traits=Unit, Mobile, AttackBase, RenderInfantry, TakeCover, Passenger SquadSize=1 Voice=ShokVoice [MECH] Description=Mechanic -Traits=Unit, Mobile, AttackBase, RenderInfantry, TakeCover, SquishByTank -SquadSize=1 Voice=MechVoice +Traits=Unit, Mobile, RenderInfantry, AutoHeal, AttackBase, TakeCover, SquishByTank, Passenger +LongDesc=Heals nearby vehicles.\n Strong vs Nothing\n Weak vs Everything +SelectionSize=12,17,0,-9 @@ -81,16 +82,13 @@ RenderAsTesla=true [TTankZap] RenderAsTesla=true -[GoodWrench] - - - - - [WarheadTypes] Mechanical [Mechanical] +; Stop the mechanic from `healing' people with his wrench +Verses=0%,0%,100%,100%,100% +Spread=1 [VoiceTypes] ShokVoice diff --git a/doc/progress.txt b/doc/progress.txt index cd612fc2cd..1d3b8f14f0 100644 --- a/doc/progress.txt +++ b/doc/progress.txt @@ -8,7 +8,7 @@ E3 Works E4 Works E6 Works E7 Works -MEDI Works +MEDI Works SPY Infiltrate action missing THF Steal action missing C1,C2,Einstein,Kosygin Not implemented @@ -17,16 +17,16 @@ All tracked vehicles 1TNK Works 2TNK Works 3TNK Works -4TNK Gun, missile origins are wrong +4TNK Works Light vehicles V2RL Works -APC Works +APC Works MNLY Works -MGG No gap -MRJ No radar +MGG No gap +MRJ No radar JEEP Works -MCV Works +MCV Works HARV Works ARTY Works @@ -48,3 +48,12 @@ DD depth charges don't work PT depth charges don't work LST Works +Aftermath Units: +STNK Stealth effect looks wrong (turret alpha stacking) +CTNK Missile origins are wrong +TTNK Works +DTRK Attack doesn't work; Scorches wrong +QTNK No weapon +MSUB Works +SHOK Works +MECH Cannot enter transports \ No newline at end of file diff --git a/rules.ini b/rules.ini index 8c029a573e..34338ddc33 100644 --- a/rules.ini +++ b/rules.ini @@ -543,10 +543,9 @@ Armor=none TechLevel=2 Sight=4 Speed=3 -Owner=allies +Owner=allies,soviet Cost=300 Points=10 -DoubleOwned=yes ; Flamethrower [E4] @@ -602,7 +601,7 @@ Infiltrate=yes ; Tanya [E7] -Prerequisite=atek +Prerequisite=atek,stek Primary=Colt45 Secondary=Colt45 Strength=100 @@ -615,7 +614,6 @@ Cost=1200 Points=25 Infiltrate=yes C4=yes -DoubleOwned=yes ; field medic [MEDI] diff --git a/sequences-aftermath.xml b/sequences-aftermath.xml index 911a79510a..bd25d095f0 100644 --- a/sequences-aftermath.xml +++ b/sequences-aftermath.xml @@ -74,7 +74,7 @@ - + @@ -99,6 +99,15 @@ - + + + + + + + + + + \ No newline at end of file diff --git a/sequences.xml b/sequences.xml index 9e1f0e0ef5..6a463e969c 100644 --- a/sequences.xml +++ b/sequences.xml @@ -201,8 +201,10 @@ - - + + + + @@ -404,10 +406,10 @@ - + - + @@ -1024,4 +1026,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/units.ini b/units.ini index 38418d8b72..5c39efaabf 100644 --- a/units.ini +++ b/units.ini @@ -16,47 +16,50 @@ MNLY.AT [V2RL] Description=V2 Rocket -Traits=Unit, Mobile, AttackBase, RenderUnitReload, AutoTarget, Repairable, Chronoshiftable, Passenger +Traits=Unit, Mobile, AttackBase, RenderUnitReload, AutoTarget, Repairable, Chronoshiftable, Passenger, IronCurtainable Voice=VehicleVoice LongDesc=Long-range rocket artillery.\n Strong vs Infantry, Buildings\n Weak vs Tanks, Aircraft [1TNK] Description=Light Tank -Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, AutoTarget, Repairable, Chronoshiftable, Passenger +Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, AutoTarget, Repairable, Chronoshiftable, Passenger, IronCurtainable Recoil=2 Voice=VehicleVoice LongDesc=Light Tank, good for scouting.\n Strong vs Light Vehicles\n Weak vs Tanks, Aircraft [2TNK] Description=Medium Tank -Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, AutoTarget, Repairable, Chronoshiftable, Passenger +Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, AutoTarget, Repairable, Chronoshiftable, Passenger, IronCurtainable Recoil=3 Voice=VehicleVoice LongDesc=Allied Main Battle Tank.\n Strong vs Tanks, Light Vehicles\n Weak vs Infantry, Aircraft [3TNK] Description=Heavy Tank -Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, AutoTarget, Repairable, Chronoshiftable, Passenger +Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, AutoTarget, Repairable, Chronoshiftable, Passenger, IronCurtainable Recoil=3 Voice=VehicleVoice LongDesc=Soviet Main Battle Tank, with dual cannons\n Strong vs Tanks, Light Vehicles\n Weak vs Infantry, Aircraft [4TNK] Description=Mammoth Tank -Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, AutoTarget, Repairable, Chronoshiftable, Passenger +Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, AutoTarget, Repairable, Chronoshiftable, Passenger, IronCurtainable Voice=VehicleVoice LongDesc=Big and slow tank, with anti-air capability.\n Strong vs Tanks, Aircraft\n Weak vs Infantry +PrimaryLocalOffset=-4,-5,0,4,-5,0 +SecondaryLocalOffset=-7,2,25,7,2,-25 +Recoil=4 [ARTY] Description=Artillery -Traits=Unit, Mobile, AttackBase, RenderUnit, Explodes, AutoTarget, Repairable, Chronoshiftable, Passenger +Traits=Unit, Mobile, AttackBase, RenderUnit, Explodes, AutoTarget, Repairable, Chronoshiftable, Passenger, IronCurtainable Voice=VehicleVoice LongDesc=Long-range artillery.\n Strong vs Infantry, Buildings\n Weak vs Tanks, Aircraft [JEEP] Description=Ranger -Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, AutoTarget, Repairable, Chronoshiftable, Passenger +Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, AutoTarget, Repairable, Chronoshiftable, Passenger, IronCurtainable PrimaryOffset=0,0,0,-2 MuzzleFlash=yes Voice=VehicleVoice LongDesc=Fast scout & anti-infantry vehicle.\n Strong vs Infantry\n Weak vs Tanks, Aircraft [APC] Description=Armored Personnel Carrier -Traits=Unit, Mobile, AttackBase, RenderUnitMuzzleFlash, AutoTarget, Repairable, Chronoshiftable, Cargo, Passenger +Traits=Unit, Mobile, AttackBase, RenderUnitMuzzleFlash, AutoTarget, Repairable, Chronoshiftable, Cargo, Passenger, IronCurtainable PrimaryOffset=0,0,0,-4 MuzzleFlash=yes Voice=VehicleVoice @@ -67,40 +70,40 @@ PassengerTypes=Foot ;; non-combat vehicles [MRJ] Description=Radar Jammer -Traits=Unit, Mobile, RenderUnitSpinner, Repairable, Chronoshiftable, Passenger +Traits=Unit, Mobile, RenderUnitSpinner, Repairable, Chronoshiftable, Passenger, IronCurtainable PrimaryOffset=0,4,0,-6 SelectionPriority=3 Voice=VehicleVoice LongDesc=Hides nearby units on the enemy's minimap.\n Unarmed [MGG] Description=Mobile Gap Generator -Traits=Unit, Mobile, RenderUnitSpinner, Repairable, Chronoshiftable, Passenger +Traits=Unit, Mobile, RenderUnitSpinner, Repairable, Chronoshiftable, Passenger, IronCurtainable PrimaryOffset=0,6,0,-3 SelectionPriority=3 Voice=VehicleVoice LongDesc=Regenerates Fog of War in a small area \naround the unit.\n Unarmed [HARV] Description=Ore Truck -Traits=Harvester, Unit, Mobile, RenderUnit, Repairable, Chronoshiftable, Passenger +Traits=Harvester, Unit, Mobile, RenderUnit, Repairable, Chronoshiftable, Passenger, IronCurtainable SelectionPriority=7 Voice=VehicleVoice LongDesc=Collects Ore and Gems for processing.\n Unarmed [MCV] Description=Mobile Construction Vehicle -Traits=Unit, Mobile, McvDeploy, RenderUnit, Repairable, Chronoshiftable, Passenger +Traits=Unit, Mobile, McvDeploy, RenderUnit, Repairable, Chronoshiftable, Passenger, IronCurtainable SelectionPriority=3 Voice=VehicleVoice LongDesc=Deploys into another Construction Yard.\n Unarmed [MNLY.AP] Description=Minelayer (Anti-Personnel) -Traits=Unit, Mobile, RenderUnit, Minelayer, MineImmune, Repairable, LimitedAmmo, Chronoshiftable, Passenger +Traits=Unit, Mobile, RenderUnit, Minelayer, MineImmune, Repairable, LimitedAmmo, Chronoshiftable, Passenger, IronCurtainable Voice=VehicleVoice LongDesc=Lays mines to destroy unwary enemy units.\n Unarmed Primary=MINP ;; temporary hack [MNLY.AT] Description=Minelayer (Anti-Tank) -Traits=Unit, Mobile, RenderUnit, Minelayer, MineImmune, Repairable, LimitedAmmo, Chronoshiftable, Passenger +Traits=Unit, Mobile, RenderUnit, Minelayer, MineImmune, Repairable, LimitedAmmo, Chronoshiftable, Passenger, IronCurtainable Voice=VehicleVoice LongDesc=Lays mines to destroy unwary enemy units.\n Unarmed Primary=MINV ;; temporary hack @@ -118,21 +121,21 @@ PT Description=Submarine WaterBound=yes BuiltAt=spen -Traits=Unit, Mobile, RenderUnit, Submarine, AttackBase, Chronoshiftable +Traits=Unit, Mobile, RenderUnit, Submarine, AttackBase, Chronoshiftable, IronCurtainable FireDelay=2 LongDesc=Submerged anti-ship unit armed with \ntorpedoes.\n Strong vs Ships\n Weak vs Everything\n Special Ability: Submerge [DD] Description=Destroyer WaterBound=yes BuiltAt=syrd -Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, AutoTarget, Chronoshiftable +Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, AutoTarget, Chronoshiftable, IronCurtainable PrimaryOffset=0,-8,0,-3 LongDesc=Fast multi-role ship. \n Strong vs Submarines, Aircraft\n Weak vs Infantry, Tanks [CA] Description=Cruiser WaterBound=yes BuiltAt=syrd -Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, AutoTarget, Chronoshiftable +Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, AutoTarget, Chronoshiftable, IronCurtainable PrimaryOffset=0,17,0,-2 SecondaryOffset=0,-17,0,-2 LongDesc=Very slow long-range ship. \n Strong vs Buildings\n Weak vs Ships, Submarines @@ -140,14 +143,14 @@ Recoil=3 [LST] Description=Transport WaterBound=yes -Traits=Unit, Mobile, RenderUnit, Cargo +Traits=Unit, Mobile, RenderUnit, Cargo, IronCurtainable LongDesc=General-purpose naval transport.\nCan carry infantry and tanks.\n Unarmed PassengerTypes=Foot,Wheel,Track [PT] Description=Gunboat WaterBound=yes BuiltAt=syrd -Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, AutoTarget, Chronoshiftable +Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, AutoTarget, Chronoshiftable, IronCurtainable PrimaryOffset=0,-6,0,-1 LongDesc=Light scout & support ship. \n Strong vs Ships, Submarines\n Weak vs Aircraft @@ -168,13 +171,13 @@ HIND [MIG] Description=Mig Attack Plane BuiltAt=afld -Traits=Unit, AttackPlane, Plane, RenderUnit, WithShadow, LimitedAmmo +Traits=Unit, AttackPlane, Plane, RenderUnit, WithShadow, LimitedAmmo, IronCurtainable InitialFacing=192 LongDesc=Fast Ground-Attack Plane.\n Strong vs Buildings\n Weak vs Infantry, Light Vehicles [YAK] Description=Yak Attack Plane BuiltAt=afld -Traits=Unit, AttackPlane, Plane, RenderUnit, WithShadow, LimitedAmmo +Traits=Unit, AttackPlane, Plane, RenderUnit, WithShadow, LimitedAmmo, IronCurtainable InitialFacing=192 LongDesc=Anti-Tanks & Anti-Infantry Plane.\n Strong vs Infantry, Tanks\n Weak vs Buildings [TRAN] @@ -182,14 +185,14 @@ Description=Transport Helicopter RotorOffset=0,14,0,-4 RotorOffset2=0,-14,0,-2 BuiltAt=hpad -Traits=Unit, Helicopter, RenderUnitRotor, WithShadow, Cargo +Traits=Unit, Helicopter, RenderUnitRotor, WithShadow, Cargo, IronCurtainable InitialFacing=20 LongDesc=Fast Infantry Transport Helicopter.\n Unarmed PassengerTypes=Foot [HELI] Description=Longbow BuiltAt=hpad -Traits=Unit, AttackHeli, Helicopter, RenderUnitRotor, WithShadow, LimitedAmmo +Traits=Unit, AttackHeli, Helicopter, RenderUnitRotor, WithShadow, LimitedAmmo, IronCurtainable RotorOffset=0,0,0,-2 PrimaryOffset=-5,0,0,2 SecondaryOffset=5,0,0,2 @@ -198,7 +201,7 @@ LongDesc=Helicopter Gunship with AG Missiles.\n Strong vs Buildings, Tanks\n W [HIND] Description=Hind BuiltAt=hpad -Traits=Unit, AttackHeli, Helicopter, RenderUnitRotor, WithShadow, LimitedAmmo +Traits=Unit, AttackHeli, Helicopter, RenderUnitRotor, WithShadow, LimitedAmmo, IronCurtainable PrimaryOffset=-5,0,0,2 SecondaryOffset=5,0,0,2 InitialFacing=20 @@ -226,21 +229,21 @@ MSLO [PBOX] Description=Pillbox -Traits=Building, Turreted, RenderBuilding, AttackTurreted, AutoTarget +Traits=Building, Turreted, RenderBuilding, AttackTurreted, AutoTarget, IronCurtainable Dimensions=1,1 Footprint=x SelectionPriority=3 LongDesc=Basic defensive structure.\n Strong vs Infantry, Light Vehicles\n Weak vs Tanks, Aircraft [HBOX] Description=Camo Pillbox -Traits=Building, Turreted, RenderBuilding, AttackTurreted, AutoTarget +Traits=Building, Turreted, RenderBuilding, AttackTurreted, AutoTarget, IronCurtainable Dimensions=1,1 Footprint=x SelectionPriority=3 LongDesc=Hidden defensive structure.\n Strong vs Infantry, Light Vehicles\n Weak vs Tanks, Aircraft [TSLA] Description=Tesla Coil -Traits=Building, Turreted, RenderBuildingCharge, AttackTurreted, AutoTarget +Traits=Building, Turreted, RenderBuildingCharge, AttackTurreted, AutoTarget, IronCurtainable Dimensions=1,2 Footprint=_ x SelectionPriority=3 @@ -248,7 +251,7 @@ FireDelay=8 LongDesc=Advanced base defense. Requires power\nto operate.\n Strong vs Tanks, Infantry\n Weak vs Aircraft [GUN] Description=Turret -Traits=Building, Turreted, RenderBuildingTurreted, AttackTurreted, AutoTarget +Traits=Building, Turreted, RenderBuildingTurreted, AttackTurreted, AutoTarget, IronCurtainable Dimensions=1,1 Footprint=x SelectionPriority=3 @@ -256,7 +259,7 @@ InitialFacing=50 LongDesc=Anti-Armor base defense.\n Strong vs Tanks\n Weak vs Infantry, Aircraft [AGUN] Description=AA Gun -Traits=Building, Turreted, RenderBuildingTurreted, AttackTurreted, AutoTarget +Traits=Building, Turreted, RenderBuildingTurreted, AttackTurreted, AutoTarget, IronCurtainable Dimensions=1,2 Footprint=_ x SelectionPriority=3 @@ -264,14 +267,14 @@ InitialFacing=224 LongDesc=Anti-Air base defense.\n Strong vs Aircraft\n Weak vs Infantry, Tanks [FTUR] Description=Flame Turret -Traits=Turreted, Building, RenderBuilding, AttackTurreted, AutoTarget +Traits=Turreted, Building, RenderBuilding, AttackTurreted, AutoTarget, IronCurtainable Dimensions=1,1 Footprint=x SelectionPriority=3 LongDesc=Anti-Infantry base defense.\n Strong vs Infantry\n Weak vs Aircraft [SAM] Description=SAM Site -Traits=Building, Turreted, RenderBuildingTurreted, AttackTurreted, AutoTarget +Traits=Building, Turreted, RenderBuildingTurreted, AttackTurreted, AutoTarget, IronCurtainable Dimensions=2,1 Footprint=xx SelectionPriority=3 @@ -279,28 +282,28 @@ LongDesc=Anti-Air base defense.\n Strong vs Aircraft\n Weak vs Infantry, Tanks [MSLO] Description=Missile Silo -Traits=Building, RenderBuilding +Traits=Building, RenderBuilding, IronCurtainable Dimensions=2,1 Footprint=xx SelectionPriority=3 LongDesc=Launches a devastating nuclear strike.\n Strong vs Infantry, Buildings\n Weak vs Tanks\n Special Ability: Nuclear Missile [IRON] Description=Iron Curtain -Traits=Building, RenderBuilding +Traits=Building, RenderBuilding, IronCurtainable, IronCurtain Dimensions=2,2 Footprint=xx xx SelectionPriority=3 LongDesc=Makes a group of units invulnerable for a \nshort time.\n Special Ability: Invulnerability [PDOX] Description=Chronosphere -Traits=Building, RenderBuilding, Chronosphere +Traits=Building, RenderBuilding, Chronosphere, IronCurtainable Dimensions=2,2 Footprint=xx xx SelectionPriority=3 -LongDesc=Teleports a group of units from one place \nto another, for a limited time.\n Special Ability: Chronoshift +LongDesc=Teleports a unit from one place \nto another, for a limited time.\n Special Ability: Chronoshift [GAP] Description=Gap Generator -Traits=Building, RenderBuilding +Traits=Building, RenderBuilding, IronCurtainable Dimensions=1,2 Footprint=_ x SelectionPriority=3 @@ -345,14 +348,14 @@ MINV ; `Produces` is a category of objects that this building can produce. [ATEK] Description=Allied Tech Center -Traits=Building, RenderBuilding +Traits=Building, RenderBuilding, IronCurtainable Dimensions=2,2 Footprint=xx xx SelectionPriority=3 LongDesc=Provides Allied advanced technologies.\n Special Ability: GPS Satellite [WEAP] Description=War Factory -Traits=Building, RenderWarFactory, RenderBuilding, RallyPoint, Production +Traits=Building, RenderWarFactory, RenderBuilding, RallyPoint, Production, IronCurtainable Dimensions=3,2 Footprint=xxx xxx Produces=Vehicle @@ -361,7 +364,7 @@ SelectionPriority=3 LongDesc=Produces tanks & light vehicles. [SYRD] Description=Shipyard -Traits=Building, RenderBuilding, ProductionSurround +Traits=Building, RenderBuilding, ProductionSurround, IronCurtainable Dimensions=3,3 Footprint=xxx xxx xxx Produces=Ship @@ -369,7 +372,7 @@ SelectionPriority=3 LongDesc=Produces and repairs ships [SPEN] Description=Sub Pen -Traits=Building, RenderBuilding, ProductionSurround +Traits=Building, RenderBuilding, ProductionSurround, IronCurtainable Dimensions=3,3 Footprint=xxx xxx xxx Produces=Ship @@ -377,7 +380,7 @@ SelectionPriority=3 LongDesc=Produces and repairs submarines and \ntransports [FACT] Description=Construction Yard -Traits=Building, RenderBuilding, ConstructionYard +Traits=Building, RenderBuilding, ConstructionYard, IronCurtainable Dimensions=3,3 Footprint=xxx xxx xxx Produces=Building,Defense @@ -385,7 +388,7 @@ SelectionPriority=3 LongDesc=Produces other structures [PROC] Description=Ore Refinery -Traits=Building, RenderBuilding, AcceptsOre, StoresOre +Traits=Building, RenderBuilding, AcceptsOre, StoresOre, IronCurtainable Dimensions=3,3 Footprint=_x_ xxx x== SelectionPriority=3 @@ -393,7 +396,7 @@ OrePips=17 LongDesc=Converts Ore and Gems into money [SILO] Description=Silo -Traits=Building, RenderBuildingOre, StoresOre +Traits=Building, RenderBuildingOre, StoresOre, IronCurtainable Dimensions=1,1 Footprint=x SelectionPriority=3 @@ -402,7 +405,7 @@ LongDesc=Stores excess harvested Ore [HPAD] Description=Helipad -Traits=Building, RenderBuilding, Production, BelowUnits, Reservable +Traits=Building, RenderBuilding, Production, BelowUnits, Reservable, IronCurtainable Dimensions=2,2 Footprint=xx xx Produces=Plane @@ -411,14 +414,14 @@ SpawnOffset=0,-4 LongDesc=Produces and reloads helicopters [DOME] Description=Radar Dome -Traits=Building, RenderBuilding, ProvidesRadar +Traits=Building, RenderBuilding, ProvidesRadar, IronCurtainable Dimensions=2,2 Footprint=xx xx SelectionPriority=3 LongDesc=Provides an overview of the battlefield.\n Requires power to operate. [AFLD] Description=Airstrip -Traits=Building, RenderBuilding, Production, BelowUnits, Reservable +Traits=Building, RenderBuilding, Production, BelowUnits, Reservable, IronCurtainable Dimensions=3,2 Footprint=xxx xxx Produces=Plane @@ -426,28 +429,28 @@ SelectionPriority=3 LongDesc=Produces and reloads planes\n Special Ability: Paratroopers\n Special Ability: Spy Plane [POWR] Description=Power Plant -Traits=Building, RenderBuilding +Traits=Building, RenderBuilding, IronCurtainable Dimensions=2,2 Footprint=xx xx SelectionPriority=3 LongDesc=Provides power for other structures [APWR] Description=Advanced Power Plant -Traits=Building, RenderBuilding +Traits=Building, RenderBuilding, IronCurtainable Dimensions=3,3 Footprint=___ xxx xxx SelectionPriority=3 LongDesc=Provides more power, cheaper than the \nstandard Power Plant [STEK] Description=Soviet Tech Center -Traits=Building, RenderBuilding +Traits=Building, RenderBuilding, IronCurtainable Dimensions=3,2 Footprint=xxx xxx SelectionPriority=3 LongDesc=Provides Soviet advanced technologies [BARR] Description=Soviet Barracks -Traits=Building, RenderBuilding, RallyPoint, Production +Traits=Building, RenderBuilding, RallyPoint, Production, IronCurtainable Dimensions=2,2 Footprint=xx xx Produces=Infantry @@ -456,7 +459,7 @@ SelectionPriority=3 LongDesc=Produces infantry [TENT] Description=Allied Barracks -Traits=Building, RenderBuilding, RallyPoint, Production +Traits=Building, RenderBuilding, RallyPoint, Production, IronCurtainable Dimensions=2,2 Footprint=xx xx Produces=Infantry @@ -465,7 +468,7 @@ SelectionPriority=3 LongDesc=Produces infantry [KENN] Description=Kennel -Traits=Building, RenderBuilding, RallyPoint, Production +Traits=Building, RenderBuilding, RallyPoint, Production, IronCurtainable Dimensions=1,1 Footprint=x RallyPoint=1,2 @@ -473,21 +476,21 @@ SelectionPriority=3 LongDesc=Produces attack dogs [FIX] Description=Service Depot -Traits=Building, RenderBuilding, BelowUnits, Reservable +Traits=Building, RenderBuilding, BelowUnits, Reservable, IronCurtainable Dimensions=3,3 Footprint=_x_ xxx _x_ SelectionPriority=3 LongDesc=Repairs vehicles, reloads minelayers, and \nallows the construction of additional bases. [FACF] Description=Fake Construction Yard -Traits=Building, RenderBuilding, Fake +Traits=Building, RenderBuilding, Fake, IronCurtainable Dimensions=3,3 Footprint=xxx xxx xxx SelectionPriority=3 LongDesc=Looks like a Construction Yard. [WEAF] Description=Fake War Factory -Traits=Building, RenderWarFactory, RenderBuilding, Fake +Traits=Building, RenderWarFactory, RenderBuilding, Fake, IronCurtainable Dimensions=3,2 Footprint=xxx xxx SelectionPriority=3