diff --git a/OpenRA.Mods.RA/Activities/UnloadCargo.cs b/OpenRA.Mods.RA/Activities/UnloadCargo.cs index 1d5b7f328d..40ca5f011c 100644 --- a/OpenRA.Mods.RA/Activities/UnloadCargo.cs +++ b/OpenRA.Mods.RA/Activities/UnloadCargo.cs @@ -8,79 +8,64 @@ */ #endregion +using System.Collections.Generic; using System.Drawing; using System.Linq; using OpenRA.Mods.RA.Move; -using OpenRA.Mods.RA.Render; using OpenRA.Traits; namespace OpenRA.Mods.RA.Activities { public class UnloadCargo : Activity { - bool unloadAll; + readonly Actor self; + readonly Cargo cargo; + readonly bool unloadAll; - public UnloadCargo(bool unloadAll) { this.unloadAll = unloadAll; } - - CPos? ChooseExitTile(Actor self, Actor cargo) + public UnloadCargo(Actor self, bool unloadAll) { - // is anyone still hogging this tile? - if (self.World.ActorMap.GetUnitsAt(self.Location).Count() > 1) - return null; - - var mobile = cargo.Trait(); - - for (var i = -1; i < 2; i++) - for (var j = -1; j < 2; j++) - if ((i != 0 || j != 0) && - mobile.CanEnterCell(self.Location + new CVec(i, j))) - return self.Location + new CVec(i, j); - - return null; + this.self = self; + cargo = self.Trait(); + this.unloadAll = unloadAll; } - CPos? ChooseRallyPoint(Actor self) + public CPos? ChooseExitCell(Actor passenger) { - var mobile = self.Trait(); + var mobile = passenger.Trait(); - for (var i = -1; i < 2; i++) - for (var j = -1; j < 2; j++) - if ((i != 0 || j != 0) && - mobile.CanEnterCell(self.Location + new CVec(i, j))) - return self.Location + new CVec(i, j); + return cargo.CurrentAdjacentCells + .Shuffle(self.World.SharedRandom) + .Cast() + .FirstOrDefault(c => mobile.CanEnterCell(c.Value)); + } - return self.Location; + IEnumerable BlockedExitCells(Actor passenger) + { + var mobile = passenger.Trait(); + + return cargo.CurrentAdjacentCells + .Where(c => mobile.MovementSpeedForCell(passenger, c) != int.MaxValue && !mobile.CanEnterCell(c)); } public override Activity Tick(Actor self) { - if (IsCanceled) return NextActivity; - - // if we're a thing that can turn, turn to the - // right facing for the unload animation - var facing = self.TraitOrDefault(); - var unloadFacing = self.Info.Traits.Get().UnloadFacing; - if (facing != null && facing.Facing != unloadFacing) - return Util.SequenceActivities( new Turn(unloadFacing), this ); - - // TODO: handle the BS of open/close sequences, which are inconsistent, - // for reasons that probably make good sense to the westwood guys. - - var cargo = self.Trait(); - if (cargo.IsEmpty(self)) + if (IsCanceled || cargo.IsEmpty(self)) return NextActivity; - var ru = self.TraitOrDefault(); - if (ru != null) - ru.PlayCustomAnimation(self, "unload", null); + var actor = cargo.Peek(self); - var exitTile = ChooseExitTile(self, cargo.Peek(self)); - if (exitTile == null) - return this; + var exitCell = ChooseExitCell(actor); + if (exitCell == null) + { + foreach (var blocker in BlockedExitCells(actor).SelectMany(self.World.ActorMap.GetUnitsAt)) + { + foreach (var nbm in blocker.TraitsImplementing()) + nbm.OnNotifyBlockingMove(blocker, self); + } + return Util.SequenceActivities(new Wait(10), this); + } - var actor = cargo.Unload(self); - var exit = exitTile.Value.CenterPosition; - var current = self.Location.CenterPosition; + cargo.Unload(self); self.World.AddFrameEndTask(w => { @@ -88,23 +73,33 @@ namespace OpenRA.Mods.RA.Activities return; var mobile = actor.Trait(); - mobile.Facing = Util.GetFacing(exit - current, mobile.Facing ); - mobile.SetPosition(actor, exitTile.Value); + + var exitSubcell = mobile.GetDesiredSubcell(exitCell.Value, null); + + mobile.fromSubCell = exitSubcell; // these settings make sure that the below Set* calls + mobile.toSubCell = exitSubcell; // and the above GetDesiredSubcell call pick a good free subcell for later units being unloaded + + var exit = exitCell.Value.CenterPosition + MobileInfo.SubCellOffsets[exitSubcell]; + var current = self.Location.CenterPosition + MobileInfo.SubCellOffsets[exitSubcell]; + + mobile.Facing = Util.GetFacing(exit - current, mobile.Facing); + mobile.SetPosition(actor, exitCell.Value); mobile.SetVisualPosition(actor, current); - var speed = mobile.MovementSpeedForCell(actor, exitTile.Value); + var speed = mobile.MovementSpeedForCell(actor, exitCell.Value); var length = speed > 0 ? (exit - current).Length / speed : 0; w.Add(actor); actor.CancelActivity(); actor.QueueActivity(new Drag(current, exit, length)); - actor.QueueActivity(mobile.MoveTo(exitTile.Value, 0)); + actor.QueueActivity(mobile.MoveTo(exitCell.Value, 0)); - var rallyPoint = ChooseRallyPoint(actor).Value; - actor.QueueActivity(mobile.MoveTo(rallyPoint, 0)); - actor.SetTargetLine(Target.FromCell(rallyPoint), Color.Green, false); + actor.SetTargetLine(Target.FromCell(exitCell.Value), Color.Green, false); }); - return unloadAll ? this : NextActivity; + if (!unloadAll || cargo.IsEmpty(self)) + return NextActivity; + + return this; } } } diff --git a/OpenRA.Mods.RA/Cargo.cs b/OpenRA.Mods.RA/Cargo.cs index c0363d3386..e41d412d58 100644 --- a/OpenRA.Mods.RA/Cargo.cs +++ b/OpenRA.Mods.RA/Cargo.cs @@ -22,26 +22,27 @@ namespace OpenRA.Mods.RA public readonly int MaxWeight = 0; public readonly int PipCount = 0; public readonly string[] Types = { }; - public readonly int UnloadFacing = 0; public readonly string[] InitialUnits = { }; - public readonly WRange MaximumUnloadAltitude = WRange.Zero; - public object Create( ActorInitializer init ) { return new Cargo( init, this ); } + public object Create(ActorInitializer init) { return new Cargo(init, this); } } - public class Cargo : IPips, IIssueOrder, IResolveOrder, IOrderVoice, INotifyKilled, INotifyCapture + public class Cargo : IPips, IIssueOrder, IResolveOrder, IOrderVoice, INotifyKilled, INotifyCapture, ITick { readonly Actor self; - readonly CargoInfo info; + public readonly CargoInfo Info; int totalWeight = 0; List cargo = new List(); public IEnumerable Passengers { get { return cargo; } } + CPos currentCell; + public IEnumerable CurrentAdjacentCells { get; private set; } + public Cargo(ActorInitializer init, CargoInfo info) { - this.self = init.self; - this.info = info; + self = init.self; + Info = info; if (init.Contains()) { @@ -59,11 +60,14 @@ namespace OpenRA.Mods.RA Load(self, unit); } } + + currentCell = self.CenterPosition.ToCPos(); + CurrentAdjacentCells = GetAdjacentCells(); } public IEnumerable Orders { - get { yield return new DeployOrderTargeter("Unload", 10, () => CanUnload(self)); } + get { yield return new DeployOrderTargeter("Unload", 10, CanUnload); } } public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued) @@ -78,41 +82,34 @@ namespace OpenRA.Mods.RA { if (order.OrderString == "Unload") { - if (!CanUnload(self)) + if (!CanUnload()) return; self.CancelActivity(); - self.QueueActivity(new UnloadCargo(true)); + self.QueueActivity(new UnloadCargo(self, true)); } } - bool CanUnload(Actor self) + IEnumerable GetAdjacentCells() { - if (IsEmpty(self)) - return false; + return Util.AdjacentCells(Target.FromActor(self)).Where(c => self.Location != c); + } - // Cannot unload mid-air - var ios = self.TraitOrDefault(); - if (ios != null && ios.CenterPosition.Z > info.MaximumUnloadAltitude.Range) - return false; - - // TODO: Check if there is a free tile to unload to - return true; + bool CanUnload() + { + return !IsEmpty(self) && self.CenterPosition.Z == 0 + && CurrentAdjacentCells.Any(c => Passengers.Any(p => p.Trait().CanEnterCell(c))); } public bool CanLoad(Actor self, Actor a) { - if (!HasSpace(GetWeight(a))) - return false; - - // Cannot load mid-air - return self.CenterPosition.Z <= info.MaximumUnloadAltitude.Range; + return HasSpace(GetWeight(a)) && self.CenterPosition.Z == 0; } public string CursorForOrder(Actor self, Order order) { if (order.OrderString != "Unload") return null; - return CanUnload(self) ? "deploy" : "deploy-blocked"; + return CanUnload() ? "deploy" : "deploy-blocked"; } public string VoicePhraseForOrder(Actor self, Order order) @@ -121,10 +118,10 @@ namespace OpenRA.Mods.RA return self.HasVoice("Unload") ? "Unload" : "Move"; } - public bool HasSpace(int weight) { return totalWeight + weight <= info.MaxWeight; } + public bool HasSpace(int weight) { return totalWeight + weight <= Info.MaxWeight; } public bool IsEmpty(Actor self) { return cargo.Count == 0; } - public Actor Peek(Actor self) { return cargo[0]; } + public Actor Peek(Actor self) { return cargo[0]; } static int GetWeight(Actor a) { return a.Info.Traits.Get().Weight; } @@ -142,7 +139,7 @@ namespace OpenRA.Mods.RA public IEnumerable GetPips(Actor self) { - int numPips = info.PipCount; + var numPips = Info.PipCount; for (int i = 0; i < numPips; i++) yield return GetPipAt(i); @@ -150,7 +147,7 @@ namespace OpenRA.Mods.RA PipType GetPipAt(int i) { - var n = i * info.MaxWeight / info.PipCount; + var n = i * Info.MaxWeight / Info.PipCount; foreach (var c in cargo) { @@ -191,6 +188,16 @@ namespace OpenRA.Mods.RA p.Owner = newOwner; }); } + + public void Tick(Actor self) + { + var cell = self.CenterPosition.ToCPos(); + if (currentCell != cell) + { + currentCell = cell; + CurrentAdjacentCells = GetAdjacentCells(); + } + } } public interface INotifyPassengerEntered { void PassengerEntered(Actor self, Actor passenger); } @@ -198,7 +205,8 @@ namespace OpenRA.Mods.RA public class CargoInit : IActorInit { - [FieldFromYamlKey] public readonly Actor[] value = {}; + [FieldFromYamlKey] + public readonly Actor[] value = { }; public CargoInit() { } public CargoInit(Actor[] init) { value = init; } public Actor[] Value(World world) { return value; } diff --git a/OpenRA.Mods.RA/Missions/Allies04Script.cs b/OpenRA.Mods.RA/Missions/Allies04Script.cs index 5b1a3e41f8..b5b2fb1b04 100644 --- a/OpenRA.Mods.RA/Missions/Allies04Script.cs +++ b/OpenRA.Mods.RA/Missions/Allies04Script.cs @@ -285,7 +285,7 @@ namespace OpenRA.Mods.RA.Missions self.QueueActivity(new Move.Move(reinforcementsEntryPoint.Location)); self.QueueActivity(new RemoveSelf()); })); - lst.QueueActivity(new UnloadCargo(true)); + lst.QueueActivity(new UnloadCargo(lst, true)); lst.QueueActivity(new Transform(lst, "lst.unselectable.nocargo") { SkipMakeAnims = true }); } @@ -342,7 +342,7 @@ namespace OpenRA.Mods.RA.Missions })); lst.QueueActivity(new Move.Move(spyReinforcementsUnloadPoint.Location)); lst.QueueActivity(new Wait(10)); - lst.QueueActivity(new UnloadCargo(true)); + lst.QueueActivity(new UnloadCargo(lst, true)); lst.QueueActivity(new Transform(lst, "lst.unselectable.nocargo") { SkipMakeAnims = true }); } diff --git a/OpenRA.Mods.RA/Missions/DesertShellmapScript.cs b/OpenRA.Mods.RA/Missions/DesertShellmapScript.cs index 297f925690..31a1d2f2d5 100644 --- a/OpenRA.Mods.RA/Missions/DesertShellmapScript.cs +++ b/OpenRA.Mods.RA/Missions/DesertShellmapScript.cs @@ -224,7 +224,7 @@ namespace OpenRA.Mods.RA.Missions chinook.QueueActivity(new HeliFly(chinook, Target.FromPos(lz.CenterPosition + offset))); // no reservation of hpad but it's not needed chinook.QueueActivity(new Turn(0)); chinook.QueueActivity(new HeliLand(false)); - chinook.QueueActivity(new UnloadCargo(true)); + chinook.QueueActivity(new UnloadCargo(chinook, true)); chinook.QueueActivity(new Wait(150)); chinook.QueueActivity(new HeliFly(chinook, Target.FromCell(entry))); chinook.QueueActivity(new RemoveSelf()); @@ -313,7 +313,7 @@ namespace OpenRA.Mods.RA.Missions { var cargo = self.Trait(); if (!cargo.IsEmpty(self) && !(self.GetCurrentActivity() is UnloadCargo)) - self.QueueActivity(false, new UnloadCargo(true)); + self.QueueActivity(false, new UnloadCargo(self, true)); } } } diff --git a/OpenRA.Mods.RA/Missions/MissionUtils.cs b/OpenRA.Mods.RA/Missions/MissionUtils.cs index 9083eb54e3..bbbbf977b0 100644 --- a/OpenRA.Mods.RA/Missions/MissionUtils.cs +++ b/OpenRA.Mods.RA/Missions/MissionUtils.cs @@ -63,7 +63,7 @@ namespace OpenRA.Mods.RA.Missions chinook.QueueActivity(new HeliFly(chinook, Target.FromCell(lz))); chinook.QueueActivity(new Turn(0)); chinook.QueueActivity(new HeliLand(true)); - chinook.QueueActivity(new UnloadCargo(true)); + chinook.QueueActivity(new UnloadCargo(chinook, true)); chinook.QueueActivity(new CallFunc(() => afterUnload(unit))); chinook.QueueActivity(new Wait(150)); chinook.QueueActivity(new HeliFly(chinook, Target.FromCell(exit))); diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index b84c567381..e76aa1ef5c 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -333,6 +333,7 @@ + diff --git a/OpenRA.Mods.RA/Render/RenderLandingCraft.cs b/OpenRA.Mods.RA/Render/RenderLandingCraft.cs new file mode 100644 index 0000000000..da8540e8df --- /dev/null +++ b/OpenRA.Mods.RA/Render/RenderLandingCraft.cs @@ -0,0 +1,81 @@ +#region Copyright & License Information +/* + * Copyright 2007-2014 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 System.Linq; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA.Render +{ + public class RenderLandingCraftInfo : RenderUnitInfo + { + public readonly string[] OpenTerrainTypes = { "Clear" }; + public readonly string OpenAnim = "open"; + public readonly string UnloadAnim = "unload"; + + public override object Create(ActorInitializer init) { return new RenderLandingCraft(init.self, this); } + } + + public class RenderLandingCraft : RenderUnit + { + readonly Actor self; + readonly Cargo cargo; + readonly RenderLandingCraftInfo info; + bool open; + + public RenderLandingCraft(Actor self, RenderLandingCraftInfo info) + : base(self) + { + this.self = self; + cargo = self.Trait(); + this.info = info; + } + + public bool ShouldBeOpen() + { + if (self.CenterPosition.Z > 0) + return false; + + return cargo.CurrentAdjacentCells + .Any(c => info.OpenTerrainTypes.Contains(self.World.GetTerrainType(c))); + } + + void Open() + { + if (open || !anim.HasSequence(info.OpenAnim)) + return; + + open = true; + PlayCustomAnimation(self, info.OpenAnim, () => + { + if (anim.HasSequence(info.UnloadAnim)) + PlayCustomAnimRepeating(self, info.UnloadAnim); + }); + } + + void Close() + { + if (!open || !anim.HasSequence(info.OpenAnim)) + return; + + open = false; + PlayCustomAnimBackwards(self, info.OpenAnim, null); + } + + public override void Tick(Actor self) + { + if (ShouldBeOpen()) + Open(); + else + Close(); + + base.Tick(self); + } + } +} diff --git a/mods/common/lua/actor.lua b/mods/common/lua/actor.lua index 7eb9e650c7..1eb1922ab1 100644 --- a/mods/common/lua/actor.lua +++ b/mods/common/lua/actor.lua @@ -80,7 +80,7 @@ Actor.Hunt = function(actor) end Actor.UnloadCargo = function(actor, unloadAll) - actor:QueueActivity(OpenRA.New("UnloadCargo", { unloadAll })) + actor:QueueActivity(OpenRA.New("UnloadCargo", { actor, unloadAll })) end Actor.Harvest = function(actor) diff --git a/mods/ra/sequences/aircraft.yaml b/mods/ra/sequences/aircraft.yaml index 213f8cf344..ce76b19b33 100644 --- a/mods/ra/sequences/aircraft.yaml +++ b/mods/ra/sequences/aircraft.yaml @@ -66,8 +66,7 @@ tran: Start: 32 Length: 4 unload: tran2 - Start: 32 - Length: 4 + Start: 35 icon: tranicon Start: 0 diff --git a/mods/ra/sequences/ships.yaml b/mods/ra/sequences/ships.yaml index 9e992b3d1c..0b9a4c4e8d 100644 --- a/mods/ra/sequences/ships.yaml +++ b/mods/ra/sequences/ships.yaml @@ -47,6 +47,7 @@ lst: open: Start: 1 Length: 4 + Tick: 150 unload: Start: 4 icon: lsticon diff --git a/mods/ra/sequences/vehicles.yaml b/mods/ra/sequences/vehicles.yaml index a257dab998..625354f1b7 100644 --- a/mods/ra/sequences/vehicles.yaml +++ b/mods/ra/sequences/vehicles.yaml @@ -177,7 +177,7 @@ apc: Start: 0 Length: 6 Facings: 8 - close: + open: Start: 32 Length: 3 unload: