#region Copyright & License Information /* * Copyright 2007-2019 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, either version 3 of * the License, or (at your option) any later version. For more * information, see COPYING. */ #endregion using System.Collections.Generic; using System.Linq; using OpenRA.Activities; using OpenRA.Mods.Common.Traits; using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Common.Activities { public class UnloadCargo : Activity { readonly Actor self; readonly Cargo cargo; readonly INotifyUnload[] notifiers; readonly bool unloadAll; readonly Aircraft aircraft; readonly bool assignTargetOnFirstRun; readonly WDist unloadRange; Target destination; bool takeOffAfterUnload; public UnloadCargo(Actor self, WDist unloadRange, bool unloadAll = true) : this(self, Target.Invalid, unloadRange, unloadAll) { assignTargetOnFirstRun = true; } public UnloadCargo(Actor self, Target destination, WDist unloadRange, bool unloadAll = true) { this.self = self; cargo = self.Trait(); notifiers = self.TraitsImplementing().ToArray(); this.unloadAll = unloadAll; aircraft = self.TraitOrDefault(); this.destination = destination; this.unloadRange = unloadRange; } public Pair? ChooseExitSubCell(Actor passenger) { var pos = passenger.Trait(); return cargo.CurrentAdjacentCells .Shuffle(self.World.SharedRandom) .Select(c => Pair.New(c, pos.GetAvailableSubCell(c))) .Cast?>() .FirstOrDefault(s => s.Value.Second != SubCell.Invalid); } IEnumerable BlockedExitCells(Actor passenger) { var pos = passenger.Trait(); // Find the cells that are blocked by transient actors return cargo.CurrentAdjacentCells .Where(c => pos.CanEnterCell(c, null, true) != pos.CanEnterCell(c, null, false)); } protected override void OnFirstRun(Actor self) { if (assignTargetOnFirstRun) destination = Target.FromCell(self.World, self.Location); // Move to the target destination if (aircraft != null) { // Queue the activity even if already landed in case self.Location != destination QueueChild(new Land(self, destination, unloadRange)); takeOffAfterUnload = !aircraft.AtLandAltitude; } else { var cell = self.World.Map.Clamp(this.self.World.Map.CellContaining(destination.CenterPosition)); QueueChild(new Move(self, cell, unloadRange)); } QueueChild(new Wait(cargo.Info.BeforeUnloadDelay)); } public override bool Tick(Actor self) { if (IsCanceling || cargo.IsEmpty(self)) return true; if (cargo.CanUnload()) { foreach (var inu in notifiers) inu.Unloading(self); var actor = cargo.Peek(self); var spawn = self.CenterPosition; var exitSubCell = ChooseExitSubCell(actor); if (exitSubCell == null) { self.NotifyBlocker(BlockedExitCells(actor)); QueueChild(new Wait(10)); return false; } cargo.Unload(self); self.World.AddFrameEndTask(w => { if (actor.Disposed) return; var move = actor.Trait(); var pos = actor.Trait(); actor.CancelActivity(); pos.SetVisualPosition(actor, spawn); actor.QueueActivity(move.MoveIntoWorld(actor, exitSubCell.Value.First, exitSubCell.Value.Second)); actor.SetTargetLine(Target.FromCell(w, exitSubCell.Value.First, exitSubCell.Value.Second), Color.Green, false); w.Add(actor); }); } if (!unloadAll || !cargo.CanUnload()) { Cancel(self, true); if (cargo.Info.AfterUnloadDelay > 0) QueueChild(new Wait(cargo.Info.AfterUnloadDelay, false)); if (takeOffAfterUnload) QueueChild(new TakeOff(self)); } return false; } } }