Overhaul Carryall behaviour, adding support for manual control.

This commit is contained in:
Paul Chote
2016-07-31 02:01:29 +01:00
parent d08cc10abb
commit 1ae86f34f8
20 changed files with 842 additions and 402 deletions

View File

@@ -9,191 +9,90 @@
*/
#endregion
using System.Linq;
using OpenRA.Activities;
using OpenRA.Mods.Common.Activities;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Desc("Can be carried by units with the trait `Carryall`.")]
[Desc("Can be carried by actors with the `Carryall` trait.")]
public class CarryableInfo : ITraitInfo, Requires<UpgradeManagerInfo>
{
[Desc("Required distance away from destination before requesting a pickup. Default is 6 cells.")]
public readonly WDist MinDistance = WDist.FromCells(6);
[UpgradeGrantedReference]
[Desc("The upgrades to grant to self while waiting or being carried.")]
public readonly string[] CarryableUpgrades = { };
public object Create(ActorInitializer init) { return new Carryable(init.Self, this); }
[Desc("Carryall attachment point relative to body.")]
public readonly WVec LocalOffset = WVec.Zero;
public virtual object Create(ActorInitializer init) { return new Carryable(init.Self, this); }
}
public class Carryable : INotifyHarvesterAction, ICallForTransport
public class Carryable
{
readonly CarryableInfo info;
readonly Actor self;
readonly UpgradeManager upgradeManager;
public bool Reserved { get; private set; }
public Actor Carrier { get; private set; }
public bool Reserved { get { return state != State.Free; } }
public CPos? Destination { get; protected set; }
public bool WantsTransport { get { return Destination != null; } }
// 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;
void Unlock()
{
if (!locked)
return;
locked = false;
foreach (var u in info.CarryableUpgrades)
upgradeManager.RevokeUpgrade(self, u, this);
}
void Lock()
{
if (locked)
return;
locked = true;
foreach (var u in info.CarryableUpgrades)
upgradeManager.GrantUpgrade(self, u, this);
}
public bool WantsTransport { get; set; }
public CPos Destination;
Activity afterLandActivity;
protected enum State { Free, Reserved, Locked }
protected State state = State.Free;
protected bool attached;
public Carryable(Actor self, CarryableInfo info)
{
this.info = info;
this.self = self;
upgradeManager = self.Trait<UpgradeManager>();
locked = false;
Reserved = false;
WantsTransport = false;
}
public void MovingToResources(Actor self, CPos targetCell, Activity next) { RequestTransport(targetCell, next); }
public void MovingToRefinery(Actor self, CPos targetCell, Activity next) { RequestTransport(targetCell, next); }
public WDist MinimumDistance { get { return info.MinDistance; } }
public void RequestTransport(CPos destination, Activity afterLandActivity)
public virtual void Attached(Actor self)
{
var destPos = self.World.Map.CenterOfCell(destination);
if (destination == CPos.Zero || (self.CenterPosition - destPos).LengthSquared < info.MinDistance.LengthSquared)
{
WantsTransport = false; // Be sure to cancel any pending transports
return;
}
Destination = destination;
this.afterLandActivity = afterLandActivity;
WantsTransport = true;
if (locked || Reserved)
if (attached)
return;
// Inform all idle carriers
var carriers = self.World.ActorsWithTrait<Carryall>()
.Where(c => !c.Trait.IsBusy && !c.Actor.IsDead && c.Actor.Owner == self.Owner && c.Actor.IsInWorld)
.OrderBy(p => (self.Location - p.Actor.Location).LengthSquared);
// 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
public void MovementCancelled(Actor self)
{
if (locked)
return;
WantsTransport = false;
afterLandActivity = null;
// TODO: We could implement something like a carrier.Trait<Carryall>().CancelTransportNotify(self) and call it here
}
// We do not handle Harvested notification
public void Harvested(Actor self, ResourceType resource) { }
public void Docked() { }
public void Undocked() { }
public Actor GetClosestIdleCarrier()
{
// Find carriers
var carriers = self.World.ActorsHavingTrait<Carryall>(c => !c.IsBusy)
.Where(a => a.Owner == self.Owner && a.IsInWorld);
return carriers.ClosestTo(self);
attached = true;
foreach (var u in info.CarryableUpgrades)
upgradeManager.GrantUpgrade(self, u, this);
}
// This gets called by carrier after we touched down
public void Dropped()
public virtual void Detached(Actor self)
{
WantsTransport = false;
Unlock();
if (!attached)
return;
if (afterLandActivity != null)
{
// HACK: Harvesters need special treatment to avoid getting stuck on resource fields,
// so if a Harvester's afterLandActivity is not DeliverResources, queue a new FindResources activity
var findResources = self.Info.HasTraitInfo<HarvesterInfo>() && !(afterLandActivity is DeliverResources);
if (findResources)
self.QueueActivity(new FindResources(self));
else
self.QueueActivity(false, afterLandActivity);
}
attached = false;
foreach (var u in info.CarryableUpgrades)
upgradeManager.RevokeUpgrade(self, u, this);
}
public bool Reserve(Actor carrier)
public virtual bool Reserve(Actor self, Actor carrier)
{
if (Reserved)
return false;
var destPos = self.World.Map.CenterOfCell(Destination);
if ((self.CenterPosition - destPos).LengthSquared < info.MinDistance.LengthSquared)
{
MovementCancelled(self);
return false;
}
Reserved = true;
state = State.Reserved;
Carrier = carrier;
return true;
}
public void UnReserve(Actor carrier)
public virtual void UnReserve(Actor self)
{
Reserved = false;
Unlock();
state = State.Free;
Carrier = null;
}
// Prepare for transport pickup
public bool StandbyForPickup(Actor carrier)
public virtual bool LockForPickup(Actor self, Actor carrier)
{
if (Destination == CPos.Zero)
if (state == State.Locked)
return false;
if (locked || !WantsTransport)
return false;
// Last chance to change our mind...
var destPos = self.World.Map.CenterOfCell(Destination);
if ((self.CenterPosition - destPos).LengthSquared < info.MinDistance.LengthSquared)
{
MovementCancelled(self);
return false;
}
// Cancel our activities
self.CancelActivity();
Lock();
state = State.Locked;
Carrier = carrier;
self.QueueActivity(false, new WaitFor(() => state != State.Locked, false));
return true;
}
}