diff --git a/OpenRA.Mods.Common/Activities/WaitForTransport.cs b/OpenRA.Mods.Common/Activities/WaitForTransport.cs new file mode 100644 index 0000000000..2e039f3426 --- /dev/null +++ b/OpenRA.Mods.Common/Activities/WaitForTransport.cs @@ -0,0 +1,51 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using OpenRA.Activities; +using OpenRA.Mods.Common.Traits; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Activities +{ + public class WaitForTransport : Activity + { + readonly ICallForTransport transportable; + + Activity inner; + + public WaitForTransport(Actor self, Activity innerActivity) + { + transportable = self.TraitOrDefault(); + inner = innerActivity; + } + + public override Activity Tick(Actor self) + { + if (inner == null) + { + if (transportable != null) + transportable.MovementCancelled(self); + + return NextActivity; + } + + inner = Util.RunActivity(self, inner); + return this; + } + + public override void Cancel(Actor self) + { + if (transportable != null) + transportable.WantsTransport = false; + + inner.Cancel(self); + } + } +} diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index 4049908978..2c6ecd2864 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -115,6 +115,7 @@ + diff --git a/OpenRA.Mods.Common/Orders/RepairOrderGenerator.cs b/OpenRA.Mods.Common/Orders/RepairOrderGenerator.cs index 61f58b8d2d..32fbee25e7 100644 --- a/OpenRA.Mods.Common/Orders/RepairOrderGenerator.cs +++ b/OpenRA.Mods.Common/Orders/RepairOrderGenerator.cs @@ -26,21 +26,35 @@ namespace OpenRA.Mods.Common.Orders return OrderInner(world, mi); } - IEnumerable OrderInner(World world, MouseInput mi) + static IEnumerable OrderInner(World world, MouseInput mi) { - if (mi.Button == MouseButton.Left) - { - var underCursor = world.ScreenMap.ActorsAt(mi) - .FirstOrDefault(a => !world.FogObscures(a) && a.AppearsFriendlyTo(world.LocalPlayer.PlayerActor) - && a.TraitsImplementing().Any(t => !t.IsTraitDisabled)); + if (mi.Button != MouseButton.Left) + yield break; - if (underCursor == null) - yield break; + var underCursor = world.ScreenMap.ActorsAt(mi) + .FirstOrDefault(a => a.AppearsFriendlyTo(world.LocalPlayer.PlayerActor) && !world.FogObscures(a)); - if (underCursor.Info.Traits.Contains() - && underCursor.GetDamageState() > DamageState.Undamaged) - yield return new Order("RepairBuilding", world.LocalPlayer.PlayerActor, false) { TargetActor = underCursor }; - } + if (underCursor == null) + yield break; + + if (underCursor.GetDamageState() == DamageState.Undamaged) + yield break; + + // Repair a building. + if (underCursor.Info.Traits.Contains()) + yield return new Order("RepairBuilding", world.LocalPlayer.PlayerActor, false) { TargetActor = underCursor }; + + // Test for generic Repairable (used on units). + var repairable = underCursor.TraitOrDefault(); + if (repairable == null) + yield break; + + // Find a building to repair at. + var repairBuilding = repairable.FindRepairBuilding(underCursor); + if (repairBuilding == null) + yield break; + + yield return new Order("Repair", underCursor, false) { TargetActor = repairBuilding }; } public void Tick(World world) diff --git a/OpenRA.Mods.Common/Traits/Repairable.cs b/OpenRA.Mods.Common/Traits/Repairable.cs index b275311bfa..bd3d2c4648 100644 --- a/OpenRA.Mods.Common/Traits/Repairable.cs +++ b/OpenRA.Mods.Common/Traits/Repairable.cs @@ -42,8 +42,7 @@ namespace OpenRA.Mods.Common.Traits { get { - yield return new EnterAlliedActorTargeter("Repair", 5, - target => CanRepairAt(target), _ => CanRepair() || CanRearm()); + yield return new EnterAlliedActorTargeter("Repair", 5, CanRepairAt, _ => CanRepair() || CanRearm()); } } @@ -92,21 +91,60 @@ namespace OpenRA.Mods.Common.Traits self.SetTargetLine(target, Color.Green); self.CancelActivity(); - self.QueueActivity(new MoveAdjacentTo(self, target)); - self.QueueActivity(movement.MoveTo(self.World.Map.CellContaining(order.TargetActor.CenterPosition), order.TargetActor)); - if (CanRearmAt(order.TargetActor) && CanRearm()) - self.QueueActivity(new Rearm(self)); + self.QueueActivity(new WaitForTransport(self, Util.SequenceActivities(new MoveAdjacentTo(self, target), + new CallFunc(() => AfterReachActivities(self, order, movement))))); - self.QueueActivity(new Repair(order.TargetActor)); - - var rp = order.TargetActor.TraitOrDefault(); - if (rp != null) - self.QueueActivity(new CallFunc(() => - { - self.SetTargetLine(Target.FromCell(self.World, rp.Location), Color.Green); - self.QueueActivity(movement.MoveTo(rp.Location, order.TargetActor)); - })); + TryCallTransport(self, target, new CallFunc(() => AfterReachActivities(self, order, movement))); } } + + void AfterReachActivities(Actor self, Order order, IMove movement) + { + if (!order.TargetActor.IsInWorld || order.TargetActor.IsDead || order.TargetActor.IsDisabled()) + return; + + // TODO: This is hacky, but almost every single component affected + // will need to be rewritten anyway, so this is OK for now. + self.QueueActivity(movement.MoveTo(self.World.Map.CellContaining(order.TargetActor.CenterPosition), order.TargetActor)); + if (CanRearmAt(order.TargetActor) && CanRearm()) + self.QueueActivity(new Rearm(self)); + + self.QueueActivity(new Repair(order.TargetActor)); + + var rp = order.TargetActor.TraitOrDefault(); + if (rp != null) + { + self.QueueActivity(new CallFunc(() => + { + self.SetTargetLine(Target.FromCell(self.World, rp.Location), Color.Green); + self.QueueActivity(movement.MoveTo(rp.Location, order.TargetActor)); + })); + } + } + + public Actor FindRepairBuilding(Actor self) + { + var repairBuilding = self.World.ActorsWithTrait() + .Where(a => !a.Actor.IsDead && a.Actor.IsInWorld + && a.Actor.Owner == self.Owner && + info.RepairBuildings.Contains(a.Actor.Info.Name)) + .OrderBy(p => (self.Location - p.Actor.Location).LengthSquared); + + // Worst case FirstOrDefault() will return a TraitPair, which is OK. + return repairBuilding.FirstOrDefault().Actor; + } + + static void TryCallTransport(Actor self, Target target, Activity nextActivity) + { + var transport = self.TraitOrDefault(); + if (transport == null) + return; + + var targetCell = self.World.Map.CellContaining(target.CenterPosition); + if ((self.CenterPosition - target.CenterPosition).LengthSquared < transport.MinimumDistance.Range * transport.MinimumDistance.Range) + return; + + transport.RequestTransport(targetCell, nextActivity); + } } } diff --git a/OpenRA.Mods.Common/TraitsInterfaces.cs b/OpenRA.Mods.Common/TraitsInterfaces.cs index 3918b7f151..5cffc5a85e 100644 --- a/OpenRA.Mods.Common/TraitsInterfaces.cs +++ b/OpenRA.Mods.Common/TraitsInterfaces.cs @@ -85,4 +85,12 @@ namespace OpenRA.Mods.Common.Traits { IEnumerable PaletteNames { get; } } + + public interface ICallForTransport + { + WRange MinimumDistance { get; } + bool WantsTransport { get; set; } + void MovementCancelled(Actor self); + void RequestTransport(CPos destination, Activity afterLandActivity); + } } diff --git a/OpenRA.Mods.D2k/Traits/Carryable.cs b/OpenRA.Mods.D2k/Traits/Carryable.cs index 2e577f1627..651cdc3afd 100644 --- a/OpenRA.Mods.D2k/Traits/Carryable.cs +++ b/OpenRA.Mods.D2k/Traits/Carryable.cs @@ -18,13 +18,13 @@ namespace OpenRA.Mods.D2k.Traits [Desc("Can be carried by units with the trait `Carryall`.")] public class CarryableInfo : ITraitInfo { - [Desc("Required distance away from destination before requesting a pickup.")] - public int MinDistance = 6; + [Desc("Required distance away from destination before requesting a pickup. Default is 6 cells.")] + public WRange MinDistance = WRange.FromCells(6); public object Create(ActorInitializer init) { return new Carryable(init.Self, this); } } - public class Carryable : IDisableMove, INotifyHarvesterAction + public class Carryable : IDisableMove, INotifyHarvesterAction, ICallForTransport { readonly CarryableInfo info; readonly Actor self; @@ -34,7 +34,7 @@ namespace OpenRA.Mods.D2k.Traits // If we're locked there isn't much we can do. We'll have to wait for the carrier to finish with us. We should not move or get new orders! bool locked; - public bool WantsTransport { get; private set; } + public bool WantsTransport { get; set; } public CPos Destination; Activity afterLandActivity; @@ -51,9 +51,12 @@ namespace OpenRA.Mods.D2k.Traits public void MovingToResources(Actor self, CPos targetCell, Activity next) { RequestTransport(targetCell, next); } public void MovingToRefinery(Actor self, CPos targetCell, Activity next) { RequestTransport(targetCell, next); } - void RequestTransport(CPos destination, Activity afterLandActivity) + public WRange MinimumDistance { get { return info.MinDistance; } } + + public void RequestTransport(CPos destination, Activity afterLandActivity) { - if (destination == CPos.Zero || (self.Location - destination).Length < info.MinDistance) + var destPos = self.World.Map.CenterOfCell(destination); + if (destination == CPos.Zero || (self.CenterPosition - destPos).LengthSquared < info.MinDistance.Range * info.MinDistance.Range) { WantsTransport = false; // Be sure to cancel any pending transports return; @@ -71,12 +74,9 @@ namespace OpenRA.Mods.D2k.Traits .Where(c => !c.Trait.IsBusy && !c.Actor.IsDead && c.Actor.Owner == self.Owner && c.Actor.IsInWorld) .OrderBy(p => (self.Location - p.Actor.Location).LengthSquared); - foreach (var carrier in carriers) - { - // Notify the carrier and see if he's willing to transport us.. - if (carrier.Trait.RequestTransportNotify(self)) - break; // If true then we're done - } + // Is any carrier able to transport the actor? + // Any will return once it finds a carrier that returns true. + carriers.Any(carrier => carrier.Trait.RequestTransportNotify(self)); } // No longer want to be carried @@ -119,7 +119,8 @@ namespace OpenRA.Mods.D2k.Traits if (Reserved) return false; - if ((self.Location - Destination).Length < info.MinDistance) + var destPos = self.World.Map.CenterOfCell(Destination); + if ((self.CenterPosition - destPos).LengthSquared < info.MinDistance.Range * info.MinDistance.Range) { MovementCancelled(self); return false; @@ -146,7 +147,8 @@ namespace OpenRA.Mods.D2k.Traits return false; // Last change to change our mind... - if ((self.Location - Destination).Length < info.MinDistance) + var destPos = self.World.Map.CenterOfCell(Destination); + if ((self.CenterPosition - destPos).LengthSquared < info.MinDistance.Range * info.MinDistance.Range) { MovementCancelled(self); return false; diff --git a/OpenRA.Mods.D2k/Traits/Carryall.cs b/OpenRA.Mods.D2k/Traits/Carryall.cs index b5abbf1a03..1e0f928059 100644 --- a/OpenRA.Mods.D2k/Traits/Carryall.cs +++ b/OpenRA.Mods.D2k/Traits/Carryall.cs @@ -103,6 +103,9 @@ namespace OpenRA.Mods.D2k.Traits if (!trait.WantsTransport) return false; + if (actor.IsIdle) + return false; + return true; }) .OrderBy(p => (self.Location - p.Actor.Location).LengthSquared); diff --git a/mods/d2k/rules/defaults.yaml b/mods/d2k/rules/defaults.yaml index 12c8b23997..0e1f9362e5 100644 --- a/mods/d2k/rules/defaults.yaml +++ b/mods/d2k/rules/defaults.yaml @@ -53,6 +53,11 @@ Notification: EnemyUnitsDetected Voiced: VoiceSet: VehicleVoice + Carryable: + WithDecorationCarryable: + Image: pips + Sequence: pickup-indicator + Offset: -12, -12 ^Tank: AppearsOnRadar: @@ -109,6 +114,11 @@ Notification: EnemyUnitsDetected Voiced: VoiceSet: VehicleVoice + Carryable: + WithDecorationCarryable: + Image: pips + Sequence: pickup-indicator + Offset: -12, -12 ^Husk: Health: diff --git a/mods/d2k/rules/vehicles.yaml b/mods/d2k/rules/vehicles.yaml index 32860101ba..8332d4464d 100644 --- a/mods/d2k/rules/vehicles.yaml +++ b/mods/d2k/rules/vehicles.yaml @@ -61,11 +61,6 @@ harvester: UnloadTicksPerBale: 5 SearchFromProcRadius: 24 SearchFromOrderRadius: 12 - Carryable: - WithDecorationCarryable: - Image: pips - Sequence: pickup-indicator - Offset: -12, -12 Health: HP: 1000 Armor: