diff --git a/OpenRa.Game/Chrome.cs b/OpenRa.Game/Chrome.cs index a8dac419e0..47a395e019 100644 --- a/OpenRa.Game/Chrome.cs +++ b/OpenRa.Game/Chrome.cs @@ -2,13 +2,11 @@ using System.Collections.Generic; using System.Drawing; using System.Linq; -using System.Windows.Forms; -using IjwFramework.Collections; using IjwFramework.Types; -using OpenRa.Game.Graphics; -using OpenRa.Game.Support; using OpenRa.Game.GameRules; - +using OpenRa.Game.Graphics; +using OpenRa.Game.Orders; +using OpenRa.Game.Support; namespace OpenRa.Game { @@ -363,7 +361,7 @@ namespace OpenRa.Game if (producing.Done) { if (group == "Building" || group == "Defense") - Game.controller.orderGenerator = new PlaceBuilding(player.PlayerActor, item); + Game.controller.orderGenerator = new PlaceBuildingOrderGenerator(player.PlayerActor, item); return; } diff --git a/OpenRa.Game/Controller.cs b/OpenRa.Game/Controller.cs index 1b7ea5dc03..b8ae8cf7f2 100644 --- a/OpenRa.Game/Controller.cs +++ b/OpenRa.Game/Controller.cs @@ -5,6 +5,7 @@ using IjwFramework.Collections; using IjwFramework.Types; using OpenRa.Game.GameRules; using OpenRa.Game.Graphics; +using OpenRa.Game.Orders; using OpenRa.Game.Traits; namespace OpenRa.Game @@ -18,6 +19,12 @@ namespace OpenRa.Game public Controller(Func getModifierKeys) { GetModifierKeys = getModifierKeys; + CancelInputMode(); + } + + public void CancelInputMode() + { + orderGenerator = new UnitOrderGenerator(new Actor[] { }); } List recentOrders = new List(); @@ -62,7 +69,7 @@ namespace OpenRa.Game if (mi.Button == MouseButton.Left && mi.Event == MouseInputEvent.Down) { - if (!(orderGenerator is PlaceBuilding)) + if (!(orderGenerator is PlaceBuildingOrderGenerator)) dragStart = dragEnd = xy; ApplyOrders(xy, mi); } @@ -72,7 +79,7 @@ namespace OpenRa.Game if (mi.Button == MouseButton.Left && mi.Event == MouseInputEvent.Up) { - if (!(orderGenerator is PlaceBuilding)) + if (orderGenerator is UnitOrderGenerator) { var newSelection = Game.SelectActorsInBox(Game.CellSize * dragStart, Game.CellSize * xy); CombineSelection(newSelection, mi.Modifiers.HasModifier(Modifiers.Shift), dragStart == xy); @@ -127,13 +134,22 @@ namespace OpenRa.Game public Cursor ChooseCursor() { var mods = GetModifierKeys(); - var c = (orderGenerator is UnitOrderGenerator) ? orderGenerator.Order(dragEnd.ToInt2(), - new MouseInput { Location = (Game.CellSize * dragEnd - Game.viewport.Location).ToInt2(), Button = MouseButton.Right, Modifiers = mods }) + + var mi = new MouseInput { + Location = (Game.CellSize * dragEnd - Game.viewport.Location).ToInt2(), + Button = MouseButton.Right, + Modifiers = mods, + IsFake = true, + }; + + var c = orderGenerator.Order(dragEnd.ToInt2(), mi) .Where(o => o.Validate()) .Select(o => CursorForOrderString(o.OrderString, o.Subject, o.TargetLocation)) - .FirstOrDefault(a => a != null) : null; + .FirstOrDefault(a => a != null); - return c ?? (Game.SelectActorsInBox(Game.CellSize * dragEnd, Game.CellSize * dragEnd).Any() ? Cursor.Select : Cursor.Default); + return c ?? + (Game.SelectActorsInBox(Game.CellSize * dragEnd, Game.CellSize * dragEnd).Any() + ? Cursor.Select : Cursor.Default); } Cursor CursorForOrderString( string s, Actor a, int2 location ) @@ -162,8 +178,12 @@ namespace OpenRa.Game else return Cursor.MoveBlocked; case "Enter": return Cursor.Enter; + case "Infiltrate": return Cursor.Enter; case "Capture": return Cursor.Capture; case "Harvest": return Cursor.Attack; // TODO: special harvest cursor? + case "PlaceBuilding": return Cursor.Default; + case "Sell": return Cursor.Sell; + case "NoSell": return Cursor.SellBlocked; default: return null; } diff --git a/OpenRa.Game/Cursor.cs b/OpenRa.Game/Cursor.cs index b59bbf6d3d..9c3d5b0f28 100644 --- a/OpenRa.Game/Cursor.cs +++ b/OpenRa.Game/Cursor.cs @@ -26,5 +26,7 @@ namespace OpenRa.Game 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"); } } + public static Cursor Sell { get { return new Cursor("sell"); } } + public static Cursor SellBlocked { get { return new Cursor("sell-blocked"); } } } } diff --git a/OpenRa.Game/Game.cs b/OpenRa.Game/Game.cs index 7be0cad8fd..e3a6cb490c 100644 --- a/OpenRa.Game/Game.cs +++ b/OpenRa.Game/Game.cs @@ -6,6 +6,7 @@ using System.Net.Sockets; using OpenRa.FileFormats; using OpenRa.Game.GameRules; using OpenRa.Game.Graphics; +using OpenRa.Game.Orders; using OpenRa.Game.Support; using OpenRa.Game.Traits; diff --git a/OpenRa.Game/MainWindow.cs b/OpenRa.Game/MainWindow.cs index e49f0a60ba..665f3501a2 100755 --- a/OpenRa.Game/MainWindow.cs +++ b/OpenRa.Game/MainWindow.cs @@ -5,6 +5,7 @@ using System.Runtime.InteropServices; using System.Windows.Forms; using OpenRa.FileFormats; using OpenRa.Game.Graphics; +using OpenRa.Game.Orders; namespace OpenRa.Game { @@ -132,6 +133,8 @@ namespace OpenRa.Game /* temporary hack: DO NOT LEAVE IN */ if (e.KeyCode == Keys.F2) Game.LocalPlayer = Game.players[(Game.LocalPlayer.Index + 1) % 4]; + if (e.KeyCode == Keys.F3) + Game.controller.orderGenerator = new SellOrderGenerator(); if (!Game.chat.isChatting) if (e.KeyCode >= Keys.D0 && e.KeyCode <= Keys.D9) @@ -173,6 +176,7 @@ namespace OpenRa.Game public int2 Location; public MouseButton Button; public Modifiers Modifiers; + public bool IsFake; } enum MouseInputEvent { Down, Move, Up }; diff --git a/OpenRa.Game/OpenRa.Game.csproj b/OpenRa.Game/OpenRa.Game.csproj index 20ad6b77ee..ffad0fc90a 100644 --- a/OpenRa.Game/OpenRa.Game.csproj +++ b/OpenRa.Game/OpenRa.Game.csproj @@ -93,16 +93,17 @@ - - + + - - - + + + + - + @@ -117,6 +118,7 @@ + @@ -145,17 +147,18 @@ + - - + + - + @@ -198,6 +201,7 @@ + @@ -231,14 +235,14 @@ - + - + diff --git a/OpenRa.Game/IOrderGenerator.cs b/OpenRa.Game/Orders/IOrderGenerator.cs similarity index 100% rename from OpenRa.Game/IOrderGenerator.cs rename to OpenRa.Game/Orders/IOrderGenerator.cs diff --git a/OpenRa.Game/IOrderSource.cs b/OpenRa.Game/Orders/IOrderSource.cs similarity index 84% rename from OpenRa.Game/IOrderSource.cs rename to OpenRa.Game/Orders/IOrderSource.cs index 6fe8653969..2abd7fda74 100644 --- a/OpenRa.Game/IOrderSource.cs +++ b/OpenRa.Game/Orders/IOrderSource.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace OpenRa.Game +namespace OpenRa.Game.Orders { interface IOrderSource { diff --git a/OpenRa.Game/LocalOrderSource.cs b/OpenRa.Game/Orders/LocalOrderSource.cs similarity index 91% rename from OpenRa.Game/LocalOrderSource.cs rename to OpenRa.Game/Orders/LocalOrderSource.cs index 2bee88f8cc..6dcb2b88e8 100644 --- a/OpenRa.Game/LocalOrderSource.cs +++ b/OpenRa.Game/Orders/LocalOrderSource.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace OpenRa.Game +namespace OpenRa.Game.Orders { class LocalOrderSource : IOrderSource { diff --git a/OpenRa.Game/NetworkOrderSource.cs b/OpenRa.Game/Orders/NetworkOrderSource.cs similarity index 94% rename from OpenRa.Game/NetworkOrderSource.cs rename to OpenRa.Game/Orders/NetworkOrderSource.cs index 84fa81fc3a..6f279dfa6d 100644 --- a/OpenRa.Game/NetworkOrderSource.cs +++ b/OpenRa.Game/Orders/NetworkOrderSource.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Net.Sockets; using System.Threading; -namespace OpenRa.Game +namespace OpenRa.Game.Orders { class NetworkOrderSource : IOrderSource { diff --git a/OpenRa.Game/Order.cs b/OpenRa.Game/Orders/Order.cs similarity index 100% rename from OpenRa.Game/Order.cs rename to OpenRa.Game/Orders/Order.cs diff --git a/OpenRa.Game/OrderIO.cs b/OpenRa.Game/Orders/OrderIO.cs similarity index 92% rename from OpenRa.Game/OrderIO.cs rename to OpenRa.Game/Orders/OrderIO.cs index dafc95aa31..c2940ddcf7 100644 --- a/OpenRa.Game/OrderIO.cs +++ b/OpenRa.Game/Orders/OrderIO.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.IO; -namespace OpenRa.Game +namespace OpenRa.Game.Orders { static class OrderIO { diff --git a/OpenRa.Game/OrderManager.cs b/OpenRa.Game/Orders/OrderManager.cs old mode 100755 new mode 100644 similarity index 94% rename from OpenRa.Game/OrderManager.cs rename to OpenRa.Game/Orders/OrderManager.cs index 383fb98071..ee0b14a9c9 --- a/OpenRa.Game/OrderManager.cs +++ b/OpenRa.Game/Orders/OrderManager.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; -namespace OpenRa.Game +namespace OpenRa.Game.Orders { class OrderManager { diff --git a/OpenRa.Game/Orders/PlaceBuildingOrderGenerator.cs b/OpenRa.Game/Orders/PlaceBuildingOrderGenerator.cs new file mode 100644 index 0000000000..43bf7be963 --- /dev/null +++ b/OpenRa.Game/Orders/PlaceBuildingOrderGenerator.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using OpenRa.Game.GameRules; + +namespace OpenRa.Game.Orders +{ + class PlaceBuildingOrderGenerator : IOrderGenerator + { + readonly Actor Producer; + readonly BuildingInfo Building; + + public PlaceBuildingOrderGenerator(Actor producer, string name) + { + Producer = producer; + Building = (BuildingInfo)Rules.UnitInfo[ name ]; + } + + public IEnumerable Order(int2 xy, MouseInput mi) + { + if (mi.IsFake) + { + // this order is never actually issued, but it's used for choosing a cursor + yield return new Order("PlaceBuilding", Producer.Owner.PlayerActor, null, xy, Building.Name); + yield break; + } + + if (mi.Button == MouseButton.Left) + { + if (!Game.CanPlaceBuilding(Building, xy, null, true) + || !Game.IsCloseEnoughToBase(Producer.Owner, Building, xy)) + { + Sound.Play("nodeply1.aud"); + yield break; + } + + yield return new Order("PlaceBuilding", Producer.Owner.PlayerActor, null, xy, Building.Name); + } + else + Game.controller.CancelInputMode(); + } + + public void Tick() + { + var producing = Producer.traits.Get().CurrentItem( Rules.UnitCategory[ Building.Name ] ); + if (producing == null || producing.Item != Building.Name || producing.RemainingTime != 0) + Game.controller.CancelInputMode(); + } + + public void Render() + { + Game.worldRenderer.uiOverlay.DrawBuildingGrid( Building ); + } + } +} diff --git a/OpenRa.Game/ReplayOrderSource.cs b/OpenRa.Game/Orders/ReplayOrderSource.cs similarity index 92% rename from OpenRa.Game/ReplayOrderSource.cs rename to OpenRa.Game/Orders/ReplayOrderSource.cs index 02485bbd9f..97d62cf8c6 100644 --- a/OpenRa.Game/ReplayOrderSource.cs +++ b/OpenRa.Game/Orders/ReplayOrderSource.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.IO; -namespace OpenRa.Game +namespace OpenRa.Game.Orders { class ReplayOrderSource : IOrderSource { diff --git a/OpenRa.Game/Orders/SellOrderGenerator.cs b/OpenRa.Game/Orders/SellOrderGenerator.cs new file mode 100644 index 0000000000..aac3baf3b5 --- /dev/null +++ b/OpenRa.Game/Orders/SellOrderGenerator.cs @@ -0,0 +1,40 @@ +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 SellOrderGenerator : IOrderGenerator + { + public IEnumerable Order(int2 xy, MouseInput mi) + { + if (!mi.IsFake && mi.Button == MouseButton.Right) + { + Game.controller.CancelInputMode(); + yield break; + } + + 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 building = underCursor != null ? underCursor.Info as BuildingInfo : null; + + if (building == null || building.Unsellable) + { + yield return new Order("NoSell", Game.LocalPlayer.PlayerActor, null, int2.Zero, null); + yield break; + } + + yield return new Order("Sell", underCursor, null, int2.Zero, null); + } + + public void Tick() {} + public void Render() {} + } +} diff --git a/OpenRa.Game/UnitOrderGenerator.cs b/OpenRa.Game/Orders/UnitOrderGenerator.cs old mode 100755 new mode 100644 similarity index 91% rename from OpenRa.Game/UnitOrderGenerator.cs rename to OpenRa.Game/Orders/UnitOrderGenerator.cs index 8885899db3..91515eec3a --- a/OpenRa.Game/UnitOrderGenerator.cs +++ b/OpenRa.Game/Orders/UnitOrderGenerator.cs @@ -2,7 +2,7 @@ using System.Drawing; using System.Linq; -namespace OpenRa.Game +namespace OpenRa.Game.Orders { class UnitOrderGenerator : IOrderGenerator { diff --git a/OpenRa.Game/UnitOrders.cs b/OpenRa.Game/Orders/UnitOrders.cs old mode 100755 new mode 100644 similarity index 93% rename from OpenRa.Game/UnitOrders.cs rename to OpenRa.Game/Orders/UnitOrders.cs index ec670e62a9..4a03e43ea0 --- a/OpenRa.Game/UnitOrders.cs +++ b/OpenRa.Game/Orders/UnitOrders.cs @@ -1,11 +1,9 @@ -using System; -using System.Drawing; -using System.Linq; +using System.Drawing; using OpenRa.Game.GameRules; -using OpenRa.Game.Traits; using OpenRa.Game.Graphics; +using OpenRa.Game.Traits; -namespace OpenRa.Game +namespace OpenRa.Game.Orders { static class UnitOrders { diff --git a/OpenRa.Game/PathFinder.cs b/OpenRa.Game/PathFinder.cs index 210613a1e6..c93b0f1f6c 100644 --- a/OpenRa.Game/PathFinder.cs +++ b/OpenRa.Game/PathFinder.cs @@ -49,7 +49,7 @@ namespace OpenRa.Game } } - Func AvoidUnitsNear(int2 p, int dist) + public Func AvoidUnitsNear(int2 p, int dist) { return q => p != q && diff --git a/OpenRa.Game/PathSearch.cs b/OpenRa.Game/PathSearch.cs index 983634cbdf..931733726a 100755 --- a/OpenRa.Game/PathSearch.cs +++ b/OpenRa.Game/PathSearch.cs @@ -15,6 +15,7 @@ namespace OpenRa.Game Func customBlock; public bool checkForBlocked; public bool ignoreTerrain; + public Actor ignoreBuilding; public PathSearch() { @@ -28,6 +29,12 @@ namespace OpenRa.Game return this; } + public PathSearch WithIgnoredBuilding(Actor b) + { + ignoreBuilding = b; + return this; + } + public int2 Expand( float[][ , ] passableCost ) { var p = queue.Pop(); @@ -49,7 +56,8 @@ namespace OpenRa.Game { if (passableCost[(int)umt][newHere.X, newHere.Y] == float.PositiveInfinity) continue; - if (!Game.BuildingInfluence.CanMoveHere(newHere)) + if (!Game.BuildingInfluence.CanMoveHere(newHere) && + Game.BuildingInfluence.GetBuildingAt(newHere) != ignoreBuilding) continue; if (Rules.Map.IsOverlaySolid(newHere)) continue; diff --git a/OpenRa.Game/PlaceBuilding.cs b/OpenRa.Game/PlaceBuilding.cs deleted file mode 100644 index 3dbb3940d3..0000000000 --- a/OpenRa.Game/PlaceBuilding.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Collections.Generic; -using OpenRa.Game.GameRules; - -namespace OpenRa.Game -{ - class PlaceBuilding : IOrderGenerator - { - readonly Actor Producer; - readonly BuildingInfo Building; - - public PlaceBuilding(Actor producer, string name) - { - Producer = producer; - Building = (BuildingInfo)Rules.UnitInfo[ name ]; - } - - public IEnumerable Order(int2 xy, MouseInput mi) - { - if( mi.Button == MouseButton.Left ) - { - if (!Game.CanPlaceBuilding(Building, xy, null, true)) - { - Sound.Play("nodeply1.aud"); - yield break; - } - - if (!Game.IsCloseEnoughToBase(Producer.Owner, Building, xy)) - { - Sound.Play("nodeply1.aud"); - yield break; - } - - yield return new Order("PlaceBuilding", Producer.Owner.PlayerActor, null, xy, Building.Name); - } - else // rmb - { - Game.world.AddFrameEndTask( _ => { Game.controller.orderGenerator = null; } ); - } - } - - public void Tick() - { - var producing = Producer.traits.Get().CurrentItem( Rules.UnitCategory[ Building.Name ] ); - if( producing == null || producing.Item != Building.Name || producing.RemainingTime != 0 ) - Game.world.AddFrameEndTask( _ => { Game.controller.orderGenerator = null; } ); - } - - public void Render() - { - Game.worldRenderer.uiOverlay.DrawBuildingGrid( Building ); - } - } -} diff --git a/OpenRa.Game/Traits/Activities/HeliReturn.cs b/OpenRa.Game/Traits/Activities/HeliReturn.cs new file mode 100644 index 0000000000..8da692e3f7 --- /dev/null +++ b/OpenRa.Game/Traits/Activities/HeliReturn.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using OpenRa.Game.GameRules; + +namespace OpenRa.Game.Traits.Activities +{ + class HeliReturn : IActivity + { + public IActivity NextActivity { get; set; } + bool isCanceled; + + static Actor ChooseHelipad(Actor self) + { + return Game.world.Actors.FirstOrDefault( + a => a.Info == Rules.UnitInfo["HPAD"] && + a.Owner == self.Owner && + !Reservable.IsReserved(a)); + } + + public IActivity Tick(Actor self) + { + if (isCanceled) return NextActivity; + var dest = ChooseHelipad(self); + + if (dest == null) + return Util.SequenceActivities( + new Turn(self.Info.InitialFacing), + new HeliLand(true), + NextActivity); + + var res = dest.traits.GetOrDefault(); + if (res != null) + self.traits.Get().reservation = res.Reserve(self); + + var offset = (dest.Info as BuildingInfo).SpawnOffset; + var offsetVec = new float2(offset[0], offset[1]); + + return Util.SequenceActivities( + new HeliFly(dest.CenterLocation + offsetVec), + new Turn(self.Info.InitialFacing), + new HeliLand(false), + new Rearm(), + NextActivity); + } + + public void Cancel(Actor self) { isCanceled = true; NextActivity = null; } + } +} diff --git a/OpenRa.Game/Traits/Activities/Move.cs b/OpenRa.Game/Traits/Activities/Move.cs index e92863fea2..ebab176413 100755 --- a/OpenRa.Game/Traits/Activities/Move.cs +++ b/OpenRa.Game/Traits/Activities/Move.cs @@ -14,6 +14,7 @@ namespace OpenRa.Game.Traits.Activities int nearEnough; public List path; Func> getPath; + public Actor ignoreBuilding; MovePart move; @@ -26,6 +27,18 @@ namespace OpenRa.Game.Traits.Activities this.nearEnough = nearEnough; } + public Move(int2 destination, Actor ignoreBuilding) + { + this.getPath = (self, mobile) => + Game.PathFinder.FindPath( + PathSearch.FromPoint( self.Location, destination, mobile.GetMovementType(), false ) + .WithCustomBlocker( Game.PathFinder.AvoidUnitsNear( self.Location, 4 )).WithIgnoredBuilding( ignoreBuilding )); + + this.destination = destination; + this.nearEnough = 0; + this.ignoreBuilding = ignoreBuilding; + } + public Move( Actor target, int range ) { this.getPath = ( self, mobile ) => Game.PathFinder.FindUnitPathToRange( @@ -42,9 +55,11 @@ namespace OpenRa.Game.Traits.Activities this.nearEnough = 0; } - static bool CanEnterCell( int2 c, Actor self ) + bool CanEnterCell( int2 c, Actor self ) { - if (!Game.BuildingInfluence.CanMoveHere(c)) return false; + if (!Game.BuildingInfluence.CanMoveHere(c) + && Game.BuildingInfluence.GetBuildingAt(c) != ignoreBuilding) + return false; // Cannot enter a cell if any unit inside is uncrushable // This will need to be updated for multiple-infantry-in-a-cell diff --git a/OpenRa.Game/Traits/Activities/Repair.cs b/OpenRa.Game/Traits/Activities/Repair.cs index fa3019f5bc..9d8e1d2002 100644 --- a/OpenRa.Game/Traits/Activities/Repair.cs +++ b/OpenRa.Game/Traits/Activities/Repair.cs @@ -19,12 +19,9 @@ namespace OpenRa.Game.Traits.Activities if (isCanceled) return NextActivity; if (--remainingTicks == 0) { - self.Health += hpPerPoint; - if (self.Health >= self.Info.Strength) - { - self.Health = self.Info.Strength; + self.InflictDamage(self, -hpPerPoint, Rules.WarheadInfo["Super"]); + if (self.Health == self.Info.Strength) return NextActivity; - } var hostBuilding = Game.FindUnits(self.CenterLocation, self.CenterLocation) .FirstOrDefault(a => a.traits.Contains()); diff --git a/OpenRa.Game/Traits/Activities/Sell.cs b/OpenRa.Game/Traits/Activities/Sell.cs new file mode 100644 index 0000000000..109d93785a --- /dev/null +++ b/OpenRa.Game/Traits/Activities/Sell.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OpenRa.Game.Traits.Activities +{ + class Sell : IActivity + { + public IActivity NextActivity { get; set; } + + bool started; + + void DoSell(Actor self) + { + var refund = Rules.General.RefundPercent + * self.Health * self.Info.Cost / self.Info.Strength; + + self.Owner.GiveCash((int)refund); + self.Health = 0; + foreach (var ns in self.traits.WithInterface()) + ns.Sold(self); + Game.world.Remove(self); + + // todo: give dudes + } + + public IActivity Tick(Actor self) + { + if (!started) + { + var rb = self.traits.Get(); + rb.PlayCustomAnimBackwards(self, "make", + () => Game.world.AddFrameEndTask(w => DoSell(self))); + + Sound.Play("cashturn.aud"); + started = true; + } + + return this; + } + + public void Cancel(Actor self) { /* never gonna give you up.. */ } + } +} diff --git a/OpenRa.Game/Traits/AttackHeli.cs b/OpenRa.Game/Traits/AttackHeli.cs index f68bd33532..4749edc089 100644 --- a/OpenRa.Game/Traits/AttackHeli.cs +++ b/OpenRa.Game/Traits/AttackHeli.cs @@ -10,7 +10,7 @@ namespace OpenRa.Game.Traits { target = order.TargetActor; self.QueueActivity(new HeliAttack(order.TargetActor)); - // todo: fly home + self.QueueActivity(new HeliReturn()); } } } diff --git a/OpenRa.Game/Traits/Building.cs b/OpenRa.Game/Traits/Building.cs index 63a1eda894..d102eb3c25 100644 --- a/OpenRa.Game/Traits/Building.cs +++ b/OpenRa.Game/Traits/Building.cs @@ -1,8 +1,9 @@ using OpenRa.Game.GameRules; +using OpenRa.Game.Traits.Activities; namespace OpenRa.Game.Traits { - class Building : INotifyDamage + class Building : INotifyDamage, IOrder { public readonly BuildingInfo unitInfo; @@ -18,5 +19,19 @@ namespace OpenRa.Game.Traits if (e.DamageState == DamageState.Dead) Sound.Play("kaboom22.aud"); } + + public Order IssueOrder(Actor self, int2 xy, MouseInput mi, Actor underCursor) + { + return null; // sell/repair orders are issued through Chrome, not here. + } + + public void ResolveOrder(Actor self, Order order) + { + if (order.OrderString == "Sell") + { + self.CancelActivity(); + self.QueueActivity(new Sell()); + } + } } } diff --git a/OpenRa.Game/Traits/EngineerCapture.cs b/OpenRa.Game/Traits/EngineerCapture.cs index 5703d0c59c..5a31ff027d 100644 --- a/OpenRa.Game/Traits/EngineerCapture.cs +++ b/OpenRa.Game/Traits/EngineerCapture.cs @@ -16,13 +16,13 @@ namespace OpenRa.Game.Traits // todo: other bits - return new Order(underCursor.Health <= EngineerDamage ? "Capture" : "Enter", + return new Order(underCursor.Health <= EngineerDamage ? "Capture" : "Infiltrate", self, underCursor, int2.Zero, null); } public void ResolveOrder(Actor self, Order order) { - if (order.OrderString == "Enter" || order.OrderString == "Capture") + if (order.OrderString == "Infiltrate" || order.OrderString == "Capture") { self.CancelActivity(); self.QueueActivity(new Move(order.TargetActor, 1)); diff --git a/OpenRa.Game/Traits/Helicopter.cs b/OpenRa.Game/Traits/Helicopter.cs index 38bd07f0c5..1361571a91 100644 --- a/OpenRa.Game/Traits/Helicopter.cs +++ b/OpenRa.Game/Traits/Helicopter.cs @@ -9,6 +9,14 @@ namespace OpenRa.Game.Traits public IDisposable reservation; public Helicopter(Actor self) {} + // todo: push into data! + static bool HeliCanEnter(Actor a) + { + if (a.Info == Rules.UnitInfo["HPAD"]) return true; + if (a.Info == Rules.UnitInfo["FIX"]) return true; + return false; + } + public Order IssueOrder(Actor self, int2 xy, MouseInput mi, Actor underCursor) { if (mi.Button == MouseButton.Left) return null; @@ -16,7 +24,7 @@ namespace OpenRa.Game.Traits if (underCursor == null) return new Order("Move", self, null, xy, null); - if (underCursor.Info == Rules.UnitInfo["HPAD"] + if (HeliCanEnter(underCursor) && underCursor.Owner == self.Owner && !Reservable.IsReserved(underCursor)) return new Order("Enter", self, underCursor, int2.Zero, null); @@ -54,7 +62,8 @@ namespace OpenRa.Game.Traits self.QueueActivity(new HeliFly(order.TargetActor.CenterLocation + offsetVec)); self.QueueActivity(new Turn(self.Info.InitialFacing)); self.QueueActivity(new HeliLand(false)); - self.QueueActivity(new Rearm()); + self.QueueActivity(order.TargetActor.Info == Rules.UnitInfo["HPAD"] + ? (IActivity)new Rearm() : new Repair()); } } diff --git a/OpenRa.Game/Traits/Plane.cs b/OpenRa.Game/Traits/Plane.cs index f00bbe54d5..f01c38e76b 100644 --- a/OpenRa.Game/Traits/Plane.cs +++ b/OpenRa.Game/Traits/Plane.cs @@ -12,13 +12,21 @@ namespace OpenRa.Game.Traits public Plane(Actor self) {} + // todo: push into data! + static bool PlaneCanEnter(Actor a) + { + if (a.Info == Rules.UnitInfo["AFLD"]) return true; + if (a.Info == Rules.UnitInfo["FIX"]) return true; + return false; + } + public Order IssueOrder(Actor self, int2 xy, MouseInput mi, Actor underCursor) { if (mi.Button == MouseButton.Left) return null; if (underCursor == null) return new Order("Move", self, null, xy, null); - if (underCursor.Info == Rules.UnitInfo["AFLD"] + if (PlaneCanEnter(underCursor) && underCursor.Owner == self.Owner && !Reservable.IsReserved(underCursor)) return new Order("Enter", self, underCursor, int2.Zero, null); @@ -51,18 +59,12 @@ namespace OpenRa.Game.Traits self.CancelActivity(); self.QueueActivity(new ReturnToBase(self, order.TargetActor)); - self.QueueActivity(new Rearm()); /* todo: something else when it's FIX rather than AFLD */ + self.QueueActivity(order.TargetActor.Info == Rules.UnitInfo["AFLD"] + ? (IActivity)new Rearm() : new Repair()); } } - public UnitMovementType GetMovementType() - { - return UnitMovementType.Fly; - } - - public bool CanEnterCell(int2 location) - { - return true; // Planes can go anywhere (?) - } + public UnitMovementType GetMovementType() { return UnitMovementType.Fly; } + public bool CanEnterCell(int2 location) { return true; } } } diff --git a/OpenRa.Game/Traits/RallyPoint.cs b/OpenRa.Game/Traits/RallyPoint.cs index acb2b8b63a..21edb5e0f1 100644 --- a/OpenRa.Game/Traits/RallyPoint.cs +++ b/OpenRa.Game/Traits/RallyPoint.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using OpenRa.Game.Graphics; +using OpenRa.Game.Orders; namespace OpenRa.Game.Traits { diff --git a/OpenRa.Game/Traits/RenderBuilding.cs b/OpenRa.Game/Traits/RenderBuilding.cs index 2dd81f7266..d5291d0b59 100644 --- a/OpenRa.Game/Traits/RenderBuilding.cs +++ b/OpenRa.Game/Traits/RenderBuilding.cs @@ -5,7 +5,7 @@ using OpenRa.Game.Effects; namespace OpenRa.Game.Traits { - class RenderBuilding : RenderSimple, INotifyDamage + class RenderBuilding : RenderSimple, INotifyDamage, INotifySold { const int SmallBibStart = 1; const int LargeBibStart = 5; @@ -62,6 +62,12 @@ namespace OpenRa.Game.Traits () => anim.PlayRepeating(GetPrefix(self) + "idle")); } + public void PlayCustomAnimBackwards(Actor self, string name, Action a) + { + anim.PlayBackwardsThen(GetPrefix(self) + name, + () => { anim.PlayRepeating(GetPrefix(self) + "idle"); a(); }); + } + public virtual void Damaged(Actor self, AttackInfo e) { if (!e.DamageStateChanged) @@ -82,5 +88,7 @@ namespace OpenRa.Game.Traits break; } } + + public void Sold(Actor self) { DoBib(self, true); } } } diff --git a/OpenRa.Game/Traits/RenderUnitRotor.cs b/OpenRa.Game/Traits/RenderUnitRotor.cs index 322345bc71..b2174a97c4 100755 --- a/OpenRa.Game/Traits/RenderUnitRotor.cs +++ b/OpenRa.Game/Traits/RenderUnitRotor.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using OpenRa.Game.Graphics; +using OpenRa.Game.Graphics; namespace OpenRa.Game.Traits { diff --git a/OpenRa.Game/Traits/Repairable.cs b/OpenRa.Game/Traits/Repairable.cs new file mode 100644 index 0000000000..b5cafac857 --- /dev/null +++ b/OpenRa.Game/Traits/Repairable.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using OpenRa.Game.Traits.Activities; + +namespace OpenRa.Game.Traits +{ + class Repairable : IOrder + { + IDisposable reservation; + public Repairable(Actor self) { } + + public Order IssueOrder(Actor self, int2 xy, MouseInput mi, Actor underCursor) + { + if (mi.Button != MouseButton.Right) return null; + if (underCursor == null) return null; + + if (underCursor.Info == Rules.UnitInfo["FIX"] + && underCursor.Owner == self.Owner + && !Reservable.IsReserved(underCursor)) + return new Order("Enter", self, underCursor, int2.Zero, null); + + return null; + } + + public void ResolveOrder(Actor self, Order order) + { + if (reservation != null) + { + reservation.Dispose(); + reservation = null; + } + + if (order.OrderString == "Enter") + { + if (Reservable.IsReserved(order.TargetActor)) + return; + + var res = order.TargetActor.traits.GetOrDefault(); + if (res != null) reservation = res.Reserve(self); + + self.CancelActivity(); + self.QueueActivity(new Move(((1 / 24f) * order.TargetActor.CenterLocation).ToInt2(), order.TargetActor)); + self.QueueActivity(new Rearm()); + self.QueueActivity(new Repair()); + } + } + } +} diff --git a/OpenRa.Game/Traits/TraitsInterfaces.cs b/OpenRa.Game/Traits/TraitsInterfaces.cs index 9485b850d2..e66744cb4e 100644 --- a/OpenRa.Game/Traits/TraitsInterfaces.cs +++ b/OpenRa.Game/Traits/TraitsInterfaces.cs @@ -13,6 +13,7 @@ namespace OpenRa.Game.Traits interface ITick { void Tick(Actor self); } interface IRender { IEnumerable Render(Actor self); } + interface INotifySold { void Sold(Actor self); } interface INotifyDamage { void Damaged(Actor self, AttackInfo e); } interface INotifyBuildComplete { void BuildingComplete (Actor self); } interface INotifyProduction { void UnitProduced(Actor self, Actor other); } diff --git a/doc/progress.txt b/doc/progress.txt index 0013085c6c..bcc268c1fb 100644 --- a/doc/progress.txt +++ b/doc/progress.txt @@ -22,7 +22,7 @@ All tracked vehicles Light vehicles V2RL Works APC Cargo doesn't work -MNLY Can't reload at FIX +MNLY Works MGG No gap MRJ No radar JEEP Works @@ -31,15 +31,15 @@ HARV Works ARTY Works Helicopters - - Repair as FIX doesn't work + - Return to base after attack doesnt work TRAN Cargo doesn't work -HELI Weapon offsets wrong -HIND Weapon offsets wrong +HELI Works +HIND Works -Planes - - Repair at FIX doesn't work [fix doesn't work?] -YAK Ammo/ROF are funky -MIG Ammo/ROF are funky +Planes + - Ammo/ROF are funky +YAK Works +MIG Works Ships diff --git a/sequences.xml b/sequences.xml index 9f438213cf..bc58377d4d 100644 --- a/sequences.xml +++ b/sequences.xml @@ -372,14 +372,14 @@ - + - + diff --git a/units.ini b/units.ini index b6eb87044c..6d8a795165 100644 --- a/units.ini +++ b/units.ini @@ -16,47 +16,47 @@ MNLY.AT [V2RL] Description=V2 Rocket -Traits=Unit, Mobile, AttackBase, RenderUnitReload, AutoTarget +Traits=Unit, Mobile, AttackBase, RenderUnitReload, AutoTarget, Repairable 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 +Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, AutoTarget, Repairable 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 +Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, AutoTarget, Repairable 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 +Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, AutoTarget, Repairable 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 +Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, AutoTarget, Repairable 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 +Traits=Unit, Mobile, AttackBase, RenderUnit, Explodes, AutoTarget, Repairable 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 +Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted, AutoTarget, Repairable 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 +Traits=Unit, Mobile, AttackBase, RenderUnitMuzzleFlash, AutoTarget, Repairable 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 +Traits=Unit, Mobile, RenderUnitSpinner, Repairable 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 +Traits=Unit, Mobile, RenderUnitSpinner, Repairable 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 +Traits=Harvester, Unit, Mobile, RenderUnit, Repairable SelectionPriority=7 Voice=VehicleVoice LongDesc=Collects Ore and Gems for processing.\n Unarmed [MCV] Description=Mobile Construction Vehicle -Traits=Unit, Mobile, McvDeploy, RenderUnit +Traits=Unit, Mobile, McvDeploy, RenderUnit, Repairable SelectionPriority=3 Voice=VehicleVoice LongDesc=Deploys into another Construction Yard.\n Unarmed [MNLY.AP] Description=Minelayer (Anti-Personnel) -Traits=Unit, Mobile, RenderUnit, Minelayer, MineImmune +Traits=Unit, Mobile, RenderUnit, Minelayer, MineImmune, Repairable 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 +Traits=Unit, Mobile, RenderUnit, Minelayer, MineImmune, Repairable Voice=VehicleVoice LongDesc=Lays mines to destroy unwary enemy units.\n Unarmed Primary=MINV ;; temporary hack