#region Copyright & License Information /* * Copyright (c) The OpenRA Developers and Contributors * 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, either version 3 of * the License, or (at your option) any later version. For more * information, see COPYING. */ #endregion using System.Linq; namespace OpenRA.Mods.Common.Traits { [Desc("Can be carried by units with the trait `" + nameof(Carryall) + "`.")] public class AutoCarryableInfo : CarryableInfo { [Desc("Required distance away from destination before requesting a pickup. Default is 6 cells.")] public readonly WDist MinDistance = WDist.FromCells(6); public override object Create(ActorInitializer init) { return new AutoCarryable(this); } } public class AutoCarryable : Carryable, ICallForTransport { readonly AutoCarryableInfo info; bool autoReserved = false; public CPos? Destination { get; private set; } public bool WantsTransport => Destination != null && !IsTraitDisabled; public AutoCarryable(AutoCarryableInfo info) : base(info) { this.info = info; } public WDist MinimumDistance => info.MinDistance; // No longer want to be carried void ICallForTransport.MovementCancelled(Actor self) { MovementCancelled(); } void ICallForTransport.RequestTransport(Actor self, CPos destination) { RequestTransport(self, destination); } void MovementCancelled() { if (state == State.Locked) return; Destination = null; autoReserved = false; // TODO: We could implement something like a carrier.Trait().CancelTransportNotify(self) and call it here } void RequestTransport(Actor self, CPos destination) { if (!IsValidAutoCarryDistance(self, destination)) { Destination = null; return; } Destination = destination; if (state != State.Free) return; // Inform all idle carriers var carriers = self.World.ActorsWithTrait() .Where(c => c.Trait.State == Carryall.CarryallState.Idle && !c.Trait.IsTraitDisabled && c.Trait.EnableAutoCarry && !c.Actor.IsDead && c.Actor.Owner == self.Owner && c.Actor.IsInWorld) .OrderBy(p => (self.Location - p.Actor.Location).LengthSquared); // Enumerate idle carriers to find the first that is able to transport us foreach (var carrier in carriers) if (carrier.Trait.RequestTransportNotify(carrier.Actor, self)) return; } // This gets called by carrier after we touched down public override void Detached(Actor self) { if (!attached) return; Destination = null; base.Detached(self); } public bool AutoReserve(Actor self, Actor carrier) { if (Reserved || !WantsTransport) return false; if (!IsValidAutoCarryDistance(self, Destination.Value)) { // Cancel pickup MovementCancelled(); return false; } if (Reserve(self, carrier)) { autoReserved = true; return true; } return false; } // Prepare for transport pickup public override LockResponse LockForPickup(Actor self, Actor carrier) { if (state == State.Locked && Carrier != carrier) return LockResponse.Failed; // When "autoReserved" is true, the carrying operation is given by auto command // we still need to check the validity of "Destination" to ensure an effective trip. if (autoReserved) { if (!WantsTransport) { // Cancel pickup MovementCancelled(); return LockResponse.Failed; } if (!IsValidAutoCarryDistance(self, Destination.Value)) { // Cancel pickup MovementCancelled(); return LockResponse.Failed; } // Reset "autoReserved" as we finished the check autoReserved = false; } return base.LockForPickup(self, carrier); } bool IsValidAutoCarryDistance(Actor self, CPos destination) { if (Mobile == null) return false; // TODO: change the check here to pathfinding distance in the future return (self.World.Map.CenterOfCell(destination) - self.CenterPosition).HorizontalLengthSquared >= info.MinDistance.LengthSquared || !Mobile.PathFinder.PathExistsForLocomotor(Mobile.Locomotor, self.Location, destination); } } }