diff --git a/OpenRa.Game/Chrome.cs b/OpenRa.Game/Chrome.cs index 12844e1aed..3d29042c2b 100644 --- a/OpenRa.Game/Chrome.cs +++ b/OpenRa.Game/Chrome.cs @@ -26,7 +26,7 @@ namespace OpenRa.Game readonly Animation repairButton; readonly Animation sellButton; - + readonly SpriteRenderer buildPaletteRenderer; readonly Animation cantBuild; readonly Animation ready; @@ -245,6 +245,22 @@ namespace OpenRa.Game void DrawButtons() { + // Chronoshift + Rectangle chronoshiftRect = new Rectangle(6, 14, repairButton.Image.bounds.Width, repairButton.Image.bounds.Height); + var chronoshiftDrawPos = Game.viewport.Location + new float2(chronoshiftRect.Location); + + var hasChronosphere = Game.world.Actors.Any(a => a.Owner == Game.LocalPlayer && a.traits.Contains()); + + if (!hasChronosphere) + repairButton.ReplaceAnim("disabled"); + else + { + //repairButton.ReplaceAnim(Game.controller.orderGenerator is RepairOrderGenerator ? "pressed" : "normal"); + AddButton(chronoshiftRect, isLmb => Game.controller.ToggleInputMode()); + } + buildPaletteRenderer.DrawSprite(repairButton.Image, chronoshiftDrawPos, PaletteType.Chrome); + + // Repair Rectangle repairRect = new Rectangle(Game.viewport.Width - 100, 5, repairButton.Image.bounds.Width, repairButton.Image.bounds.Height); var repairDrawPos = Game.viewport.Location + new float2(repairRect.Location); diff --git a/OpenRa.Game/Cursor.cs b/OpenRa.Game/Cursor.cs index d5ff149cb9..23f380a32e 100644 --- a/OpenRa.Game/Cursor.cs +++ b/OpenRa.Game/Cursor.cs @@ -22,7 +22,8 @@ namespace OpenRa.Game public static Cursor Deploy { get { return new Cursor("deploy"); } } public static Cursor Enter { get { return new Cursor("enter"); } } public static Cursor DeployBlocked { get { return new Cursor("deploy-blocked"); } } - public static Cursor Chronoshift { get { return new Cursor("chrono"); } } + public static Cursor Chronoshift { get { return new Cursor("chrono-target"); } } + public static Cursor ChronoshiftSelect { get { return new Cursor("chrono-select"); } } 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/OpenRa.Game.csproj b/OpenRa.Game/OpenRa.Game.csproj index d5903beeee..d13fdbe492 100644 --- a/OpenRa.Game/OpenRa.Game.csproj +++ b/OpenRa.Game/OpenRa.Game.csproj @@ -95,6 +95,7 @@ + @@ -103,7 +104,7 @@ - + @@ -196,6 +197,8 @@ + + diff --git a/OpenRa.Game/Orders/TeleportOrderGenerator.cs b/OpenRa.Game/Orders/ChronoshiftDestinationOrderGenerator.cs similarity index 83% rename from OpenRa.Game/Orders/TeleportOrderGenerator.cs rename to OpenRa.Game/Orders/ChronoshiftDestinationOrderGenerator.cs index 4c9375105f..e85e248752 100644 --- a/OpenRa.Game/Orders/TeleportOrderGenerator.cs +++ b/OpenRa.Game/Orders/ChronoshiftDestinationOrderGenerator.cs @@ -7,11 +7,11 @@ using OpenRa.Game.Traits; namespace OpenRa.Game.Orders { - class TeleportOrderGenerator : IOrderGenerator + class ChronoshiftDestinationOrderGenerator : IOrderGenerator { public readonly Actor self; - public TeleportOrderGenerator(Actor self) + public ChronoshiftDestinationOrderGenerator(Actor self) { this.self = self; } diff --git a/OpenRa.Game/Orders/ChronosphereSelectOrderGenerator.cs b/OpenRa.Game/Orders/ChronosphereSelectOrderGenerator.cs new file mode 100644 index 0000000000..ffc5ea7b09 --- /dev/null +++ b/OpenRa.Game/Orders/ChronosphereSelectOrderGenerator.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 ChronosphereSelectOrderGenerator : 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("ChronosphereSelect", underCursor, null, int2.Zero, null); + } + } + } + + public void Tick() + { + var hasChronosphere = Game.world.Actors + .Any(a => a.Owner == Game.LocalPlayer && a.traits.Contains()); + + if (!hasChronosphere) + Game.controller.CancelInputMode(); + } + + public void Render() { } + + public Cursor GetCursor(int2 xy, MouseInput mi) + { + mi.Button = MouseButton.Left; + return OrderInner(xy, mi).Any() + ? Cursor.ChronoshiftSelect : Cursor.MoveBlocked; + } + } +} diff --git a/OpenRa.Game/Traits/ChronoshiftDeploy.cs b/OpenRa.Game/Traits/ChronoshiftDeploy.cs index b2c280cf8e..3f577df4b4 100644 --- a/OpenRa.Game/Traits/ChronoshiftDeploy.cs +++ b/OpenRa.Game/Traits/ChronoshiftDeploy.cs @@ -49,7 +49,7 @@ namespace OpenRa.Game.Traits { if (order.OrderString == "Deploy") { - Game.controller.orderGenerator = new TeleportOrderGenerator(self); + Game.controller.orderGenerator = new ChronoshiftDestinationOrderGenerator(self); return; } @@ -68,7 +68,7 @@ namespace OpenRa.Game.Traits public float GetSpeedModifier() { // ARGH! You must not do this, it will desync! - return (Game.controller.orderGenerator is TeleportOrderGenerator) ? 0f : 1f; + return (Game.controller.orderGenerator is ChronoshiftDestinationOrderGenerator) ? 0f : 1f; } // Display 5 pips indicating the current charge status diff --git a/OpenRa.Game/Traits/Chronoshiftable.cs b/OpenRa.Game/Traits/Chronoshiftable.cs new file mode 100644 index 0000000000..84f004a310 --- /dev/null +++ b/OpenRa.Game/Traits/Chronoshiftable.cs @@ -0,0 +1,127 @@ +using OpenRa.Game.Traits; +using OpenRa.Game.Orders; +using System.Collections.Generic; +using System.Linq; +using System.Drawing; + +namespace OpenRa.Game.Traits +{ + class Chronoshiftable : IOrder, ISpeedModifier, ITick, IPaletteModifier + { + // Return-to-sender logic + int2 chronoshiftOrigin; + int chronoshiftReturnTicks = 0; + + // Screen fade logic + int animationTick = 0; + int animationLength = 10; + bool animationStarted = false; + + public Chronoshiftable(Actor self) { } + + public void Tick(Actor self) + { + if (animationStarted) + { + if (animationTick < animationLength) + animationTick++; + else + animationStarted = false; + } + else if (animationTick > 0) + animationTick--; + + if (chronoshiftReturnTicks <= 0) + return; + + if (chronoshiftReturnTicks > 0) + chronoshiftReturnTicks--; + + // Return to original location + if (chronoshiftReturnTicks == 0) + { + self.CancelActivity(); + // Todo: need a new Teleport method that will move to the closest available cell + self.QueueActivity(new Activities.Teleport(chronoshiftOrigin)); + } + } + + 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 == "ChronosphereSelect") + { + Game.controller.orderGenerator = new ChronoshiftDestinationOrderGenerator(self); + } + + var movement = self.traits.WithInterface().FirstOrDefault(); + if (order.OrderString == "Chronoshift" && movement.CanEnterCell(order.TargetLocation)) + { + chronoshiftOrigin = self.Location; + chronoshiftReturnTicks = (int)(Rules.General.ChronoDuration * 60 * 25); + // TODO: Kill cargo if Rules.General.ChronoKillCargo says so + Game.controller.CancelInputMode(); + self.CancelActivity(); + self.QueueActivity(new Activities.Teleport(order.TargetLocation)); + Sound.Play("chrono2.aud"); + animationStarted = true; + } + } + + public float GetSpeedModifier() + { + // ARGH! You must not do this, it will desync! + return (Game.controller.orderGenerator is ChronoshiftDestinationOrderGenerator) ? 0f : 1f; + } + + public void AdjustPalette(Bitmap bmp) + { + if (!animationStarted && animationTick == 0) + return; + + // saturation modifier + var f = 1 - (animationTick * 1.0f / animationLength); + + using (var bitmapCopy = new Bitmap(bmp)) + for (int j = 0; j < 8; j++) + for (int i = 0; i < bmp.Width; i++) + { + var h = bitmapCopy.GetPixel(i, j).GetHue(); // 0-360 + var s = f * bitmapCopy.GetPixel(i, j).GetSaturation(); // 0-1.0 + var l = bitmapCopy.GetPixel(i, j).GetBrightness(); // 0-1.0 + var alpha = bitmapCopy.GetPixel(i, j).A; + + // Convert from HSL to RGB + // Refactor me! + var q = (l < 0.5f) ? l * (1 + s) : l + s - (l * s); + var p = 2 * l - q; + var hk = h / 360.0f; + + double[] trgb = { hk + 1 / 3.0f, + hk, + hk - 1/3.0f }; + double[] rgb = { 0, 0, 0 }; + + for (int k = 0; k < 3; k++) + { + // mod doesn't seem to work right... do it manually + while (trgb[k] < 0) trgb[k] += 1.0f; + while (trgb[k] > 1) trgb[k] -= 1.0f; + } + + for (int k = 0; k < 3; k++) + { + if (trgb[k] < 1 / 6.0f) { rgb[k] = (p + ((q - p) * 6 * trgb[k])); } + else if (trgb[k] >= 1 / 6.0f && trgb[k] < 0.5) { rgb[k] = q; } + else if (trgb[k] >= 0.5f && trgb[k] < 2.0f / 3) { rgb[k] = (p + ((q - p) * 6 * (2.0f / 3 - trgb[k]))); } + else { rgb[k] = p; } + } + bmp.SetPixel(i, j, Color.FromArgb(alpha, (int)(rgb[0] * 255), (int)(rgb[1] * 255), (int)(rgb[2] * 255))); + } + } + } +} diff --git a/OpenRa.Game/Traits/Chronosphere.cs b/OpenRa.Game/Traits/Chronosphere.cs new file mode 100644 index 0000000000..cad8a1cf13 --- /dev/null +++ b/OpenRa.Game/Traits/Chronosphere.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OpenRa.Game.Traits +{ + class Chronosphere + { + public Chronosphere(Actor self) { } + } +} diff --git a/aftermathUnits.ini b/aftermathUnits.ini index b0599a1ccd..f48554b9d7 100755 --- a/aftermathUnits.ini +++ b/aftermathUnits.ini @@ -7,28 +7,28 @@ QTNK [STNK] Description=Stealth Tank -Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, Cloak +Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, Cloak, Chronoshiftable Recoil=2 Voice=VehicleVoice [TTNK] Description=Tesla Tank -Traits=Unit, Mobile, AttackBase, RenderUnitSpinner +Traits=Unit, Mobile, AttackBase, RenderUnitSpinner, Chronoshiftable Voice=VehicleVoice [CTNK] Description=Chrono Tank -Traits=Unit, Mobile, AttackBase, RenderUnit, ChronoshiftDeploy +Traits=Unit, Mobile, AttackBase, RenderUnit, ChronoshiftDeploy, Chronoshiftable Voice=VehicleVoice [DTRK] Description=Demo Truck -Traits=Unit, Mobile, AttackBase, RenderUnit +Traits=Unit, Mobile, AttackBase, RenderUnit, Chronoshiftable Voice=VehicleVoice [QTNK] Description=M.A.D. Tank -Traits=Unit, Mobile, RenderUnit +Traits=Unit, Mobile, RenderUnit, Chronoshiftable Voice=VehicleVoice @@ -42,7 +42,7 @@ MSUB Description=Missile Submarine WaterBound=yes BuiltAt=spen -Traits=Unit, Mobile, AttackBase, RenderUnit, Submarine +Traits=Unit, Mobile, AttackBase, RenderUnit, Submarine, Chronoshiftable FireDelay=2 diff --git a/sequences.xml b/sequences.xml index 18ad5e5e17..7e8d0a9378 100644 --- a/sequences.xml +++ b/sequences.xml @@ -376,7 +376,8 @@ - + + diff --git a/session.ini b/session.ini index e78029b52a..acafbf8481 100644 --- a/session.ini +++ b/session.ini @@ -8,4 +8,6 @@ s0=Multi0,mcv,600,2841,0,Guard,None s1=Multi2,mcv,600,12445,0,Guard,None s2=Multi1,mcv,600,12505,0,Guard,None ;s2=Multi1,e3,600,12505,0,Guard,None -s3=Multi3,mcv,600,2910,0,Guard,None \ No newline at end of file +s3=Multi3,mcv,600,2910,0,Guard,None +;s4=Multi1,ctnk,600,12506,Gaurd,None +s5=Multi1,pdox,600,12510,Gaurd,None \ No newline at end of file diff --git a/units.ini b/units.ini index 335bc7017f..5f54681c07 100644 --- a/units.ini +++ b/units.ini @@ -16,47 +16,47 @@ MNLY.AT [V2RL] Description=V2 Rocket -Traits=Unit, Mobile, AttackBase, RenderUnitReload, AutoTarget, Repairable +Traits=Unit, Mobile, AttackBase, RenderUnitReload, AutoTarget, Repairable, Chronoshiftable 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 +Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, AutoTarget, Repairable, Chronoshiftable 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 +Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, AutoTarget, Repairable, Chronoshiftable 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 +Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, AutoTarget, Repairable, Chronoshiftable 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 +Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, AutoTarget, Repairable, Chronoshiftable Voice=VehicleVoice LongDesc=Big and slow tank, with anti-air capability.\n Strong vs Tanks, Aircraft\n Weak vs Infantry [ARTY] Description=Artillery -Traits=Unit, Mobile, AttackBase, RenderUnit, Explodes, AutoTarget, Repairable +Traits=Unit, Mobile, AttackBase, RenderUnit, Explodes, AutoTarget, Repairable, Chronoshiftable 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 +Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, AutoTarget, Repairable, Chronoshiftable 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 +Traits=Unit, Mobile, AttackBase, RenderUnitMuzzleFlash, AutoTarget, Repairable, Chronoshiftable PrimaryOffset=0,0,0,-4 MuzzleFlash=yes Voice=VehicleVoice @@ -65,40 +65,40 @@ LongDesc=Tough infantry transport.\n Strong vs Infantry, Light Vehicles\n Weak ;; non-combat vehicles [MRJ] Description=Radar Jammer -Traits=Unit, Mobile, RenderUnitSpinner, Repairable +Traits=Unit, Mobile, RenderUnitSpinner, Repairable, Chronoshiftable 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 +Traits=Unit, Mobile, RenderUnitSpinner, Repairable, Chronoshiftable 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 +Traits=Harvester, Unit, Mobile, RenderUnit, Repairable, Chronoshiftable SelectionPriority=7 Voice=VehicleVoice LongDesc=Collects Ore and Gems for processing.\n Unarmed [MCV] Description=Mobile Construction Vehicle -Traits=Unit, Mobile, McvDeploy, RenderUnit, Repairable +Traits=Unit, Mobile, McvDeploy, RenderUnit, Repairable, Chronoshiftable 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 +Traits=Unit, Mobile, RenderUnit, Minelayer, MineImmune, Repairable, LimitedAmmo, Chronoshiftable 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 +Traits=Unit, Mobile, RenderUnit, Minelayer, MineImmune, Repairable, LimitedAmmo, Chronoshiftable Voice=VehicleVoice LongDesc=Lays mines to destroy unwary enemy units.\n Unarmed Primary=MINV ;; temporary hack @@ -116,21 +116,21 @@ PT Description=Submarine WaterBound=yes BuiltAt=spen -Traits=Unit, Mobile, RenderUnit, Submarine, AttackBase +Traits=Unit, Mobile, RenderUnit, Submarine, AttackBase, Chronoshiftable 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 +Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, AutoTarget, Chronoshiftable 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 +Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, AutoTarget, Chronoshiftable 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 @@ -138,13 +138,13 @@ Recoil=3 [LST] Description=Transport WaterBound=yes -Traits=Unit, Mobile, RenderUnit +Traits=Unit, Mobile, RenderUnit, Chronoshiftable LongDesc=General-purpose naval transport.\nCan carry infantry and tanks.\n Unarmed [PT] Description=Gunboat WaterBound=yes BuiltAt=syrd -Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, AutoTarget +Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, AutoTarget, Chronoshiftable PrimaryOffset=0,-6,0,-1 LongDesc=Light scout & support ship. \n Strong vs Ships, Submarines\n Weak vs Aircraft @@ -289,7 +289,7 @@ SelectionPriority=3 LongDesc=Makes a group of units invulnerable for a \nshort time.\n Special Ability: Invulnerability [PDOX] Description=Chronosphere -Traits=Building, RenderBuilding +Traits=Building, RenderBuilding, Chronosphere Dimensions=2,2 Footprint=xx xx SelectionPriority=3