Merge pull request #4616 from ScottNZ/transport

Fix cargo loading/unloading
This commit is contained in:
Paul Chote
2014-02-13 23:31:16 +13:00
20 changed files with 196 additions and 111 deletions

View File

@@ -1,5 +1,7 @@
NEW:
All Mods:
Fixed unloading passengers moving unnecessarily far from their transport.
Fixed the unload cursor sometimes being displayed for transports even when they were unable to unload.
An error dialog is now displayed when server connections are lost.
Added AttackMove and Guard abilities to Aircraft and Helicopters.
Aircraft and Helicopters can now be guarded by other units.
@@ -16,6 +18,7 @@ NEW:
Removed the build radius restrictions.
Added the BLOXXMAS terrain tiles from the 1.06 patch.
Red Alert:
Transports will now open their doors when at shore.
Tanya can now plant C4 on bridges.
Submarine torpedoes can now hit bridges when force fired.
Increased torpedo splash damage and raised multiplier vs. concrete.

View File

@@ -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<Mobile>();
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<Cargo>();
this.unloadAll = unloadAll;
}
CPos? ChooseRallyPoint(Actor self)
public CPos? ChooseExitCell(Actor passenger)
{
var mobile = self.Trait<Mobile>();
var mobile = passenger.Trait<Mobile>();
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<CPos?>()
.FirstOrDefault(c => mobile.CanEnterCell(c.Value));
}
return self.Location;
IEnumerable<CPos> BlockedExitCells(Actor passenger)
{
var mobile = passenger.Trait<Mobile>();
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<IFacing>();
var unloadFacing = self.Info.Traits.Get<CargoInfo>().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<Cargo>();
if (cargo.IsEmpty(self))
if (IsCanceled || cargo.IsEmpty(self))
return NextActivity;
var ru = self.TraitOrDefault<RenderUnit>();
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<INotifyBlockingMove>())
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>();
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;
}
}
}

View File

@@ -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<Actor> cargo = new List<Actor>();
public IEnumerable<Actor> Passengers { get { return cargo; } }
CPos currentCell;
public IEnumerable<CPos> 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<CargoInit>())
{
@@ -59,11 +60,14 @@ namespace OpenRA.Mods.RA
Load(self, unit);
}
}
currentCell = self.CenterPosition.ToCPos();
CurrentAdjacentCells = GetAdjacentCells();
}
public IEnumerable<IOrderTargeter> 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<CPos> 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<IOccupySpace>();
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<IPositionable>().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<PassengerInfo>().Weight; }
@@ -142,7 +139,7 @@ namespace OpenRA.Mods.RA
public IEnumerable<PipType> 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<Actor[]>
{
[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; }

View File

@@ -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 });
}

View File

@@ -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<Cargo>();
if (!cargo.IsEmpty(self) && !(self.GetCurrentActivity() is UnloadCargo))
self.QueueActivity(false, new UnloadCargo(true));
self.QueueActivity(false, new UnloadCargo(self, true));
}
}
}

View File

@@ -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)));

View File

@@ -333,6 +333,7 @@
<Compile Include="Render\RenderInfantry.cs" />
<Compile Include="Render\RenderInfantryPanic.cs" />
<Compile Include="Render\RenderSpy.cs" />
<Compile Include="Render\RenderLandingCraft.cs" />
<Compile Include="Render\RenderUnit.cs" />
<Compile Include="Render\RenderUnitReload.cs" />
<Compile Include="Render\WithBuildingExplosion.cs" />

View File

@@ -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<Cargo>();
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);
}
}
}

View File

@@ -34,10 +34,10 @@ namespace OpenRA.Mods.RA.Render
() => { PlayCustomAnimRepeating(self, name); });
}
public void PlayCustomAnimBackwards(Actor self, string name, Action a)
public void PlayCustomAnimBackwards(Actor self, string name, Action after)
{
anim.PlayBackwardsThen(name,
() => { anim.PlayRepeating("idle"); a(); });
() => { anim.PlayRepeating("idle"); if (after != null) after(); });
}
}
}

View File

@@ -100,6 +100,13 @@ namespace OpenRA.Utility
node.Value.Nodes.RemoveAll(n => n.Key == "JustMove");
}
// UnloadFacing was removed from Cargo
if (engineVersion < 20140212)
{
if (depth == 1 && node.Key == "Cargo")
node.Value.Nodes.RemoveAll(n => n.Key == "UnloadFacing");
}
UpgradeActorRules(engineVersion, ref node.Value.Nodes, node, depth + 1);
}
}

View File

@@ -115,7 +115,6 @@ APC:
Types: Infantry
MaxWeight: 5
PipCount: 5
UnloadFacing: 220
LeavesHusk:
HuskActor: APC.Husk

View File

@@ -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)

View File

@@ -27,7 +27,6 @@
Types: Vehicle
MaxWeight: 1
PipCount: 1
MaximumUnloadAltitude: 800
LeavesHusk:
HuskActor: CARRYALL.Husk

View File

@@ -902,7 +902,6 @@ Rules:
Types: Infantry
MaxWeight: 5
PipCount: 5
UnloadFacing: 220
ARTY:
Inherits: ^Tank
Buildable:

View File

@@ -216,7 +216,8 @@ LST:
Speed: 113
RevealsShroud:
Range: 6
RenderUnit:
RenderLandingCraft:
OpenTerrainTypes: Clear, Rough, Road, Ore, Gems, Beach
Cargo:
Types: Infantry, Vehicle
MaxWeight: 5

View File

@@ -353,7 +353,6 @@ JEEP:
Types: Infantry
MaxWeight: 1
PipCount: 1
UnloadFacing: 220
APC:
Inherits: ^Tank
@@ -386,7 +385,6 @@ APC:
Types: Infantry
MaxWeight: 5
PipCount: 5
UnloadFacing: 220
MNLY.AP:
Inherits: ^Tank

View File

@@ -66,8 +66,7 @@ tran:
Start: 32
Length: 4
unload: tran2
Start: 32
Length: 4
Start: 35
icon: tranicon
Start: 0

View File

@@ -47,6 +47,7 @@ lst:
open:
Start: 1
Length: 4
Tick: 150
unload:
Start: 4
icon: lsticon

View File

@@ -177,7 +177,7 @@ apc:
Start: 0
Length: 6
Facings: 8
close:
open:
Start: 32
Length: 3
unload:

View File

@@ -60,7 +60,6 @@ APC:
Types: Infantry
MaxWeight: 5
PipCount: 5
UnloadFacing: 220
RenderSprites:
RenderVoxels:
WithVoxelBody:
@@ -350,7 +349,6 @@ BUS:
Types: Infantry
MaxWeight: 20
PipCount: 5
UnloadFacing: 220
RenderSprites:
RenderVoxels:
WithVoxelBody:
@@ -378,7 +376,6 @@ PICK:
Types: Infantry
MaxWeight: 2
PipCount: 5
UnloadFacing: 220
RenderSprites:
RenderVoxels:
WithVoxelBody:
@@ -406,7 +403,6 @@ CAR:
Types: Infantry
MaxWeight: 4
PipCount: 5
UnloadFacing: 220
RenderSprites:
RenderVoxels:
WithVoxelBody:
@@ -459,7 +455,6 @@ WINI:
Types: Infantry
MaxWeight: 5
PipCount: 5
UnloadFacing: 220
RenderSprites:
RenderVoxels:
WithVoxelBody:
@@ -638,7 +633,6 @@ SAPC:
Types: Infantry
MaxWeight: 5
PipCount: 5
UnloadFacing: 220
RenderSprites:
RenderVoxels:
WithVoxelBody: