Rewrite Enter and related activities.

The unit will now path to the nearest adjacent cell, drag inside, do the inner activity, then (if still alive) drag back to its original
location.

This fixes:
 - Mismatch in logic between Enter and MoveAdjacentTo,
   which causes an infinite loop.
 - Building capturing failing from certain directions.
 - Being unable to enter buildings on unpathable tiles.
 - Units being stranded inside a building if the requirements
   for the inner order aren't met.
This commit is contained in:
Paul Chote
2013-04-15 01:50:19 +12:00
parent 8676562d47
commit e76c746b61
10 changed files with 87 additions and 82 deletions

View File

@@ -16,37 +16,35 @@ namespace OpenRA.Mods.RA.Activities
{ {
class Demolish : Activity class Demolish : Activity
{ {
Actor target; Target target;
int delay; int delay;
public Demolish( Actor target, int delay ) public Demolish(Actor target, int delay)
{ {
this.target = target; this.target = Target.FromActor(target);
this.delay = delay; this.delay = delay;
} }
public override Activity Tick(Actor self) public override Activity Tick(Actor self)
{ {
if (IsCanceled) return NextActivity; if (IsCanceled || !target.IsValid)
if (target == null || !target.IsInWorld || target.IsDead()) return NextActivity;
if( !target.OccupiesSpace.OccupiedCells().Any( x => x.First == self.Location ) )
return NextActivity; return NextActivity;
self.World.AddFrameEndTask(w => w.Add(new DelayedAction(delay, () => self.World.AddFrameEndTask(w => w.Add(new DelayedAction(delay, () =>
{ {
// Can't demolish an already dead actor // Can't demolish an already dead actor
if (target.IsDead()) if (!target.IsValid)
return; return;
// Invulnerable actors can't be demolished // Invulnerable actors can't be demolished
var modifier = (float)target.TraitsImplementing<IDamageModifier>() var modifier = (float)target.Actor.TraitsImplementing<IDamageModifier>()
.Concat(self.Owner.PlayerActor.TraitsImplementing<IDamageModifier>()) .Concat(self.Owner.PlayerActor.TraitsImplementing<IDamageModifier>())
.Select(t => t.GetDamageModifier(self, null)).Product(); .Select(t => t.GetDamageModifier(self, null)).Product();
if (target.IsInWorld && modifier > 0) if (modifier > 0)
target.Kill(self); target.Actor.Kill(self);
}))); })));
return NextActivity; return NextActivity;
} }
} }

View File

@@ -16,26 +16,26 @@ namespace OpenRA.Mods.RA.Activities
{ {
class DonateSupplies : Activity class DonateSupplies : Activity
{ {
Actor target; Target target;
int payload; int payload;
public DonateSupplies(Actor target, int payload) public DonateSupplies(Actor target, int payload)
{ {
this.target = target; this.target = Target.FromActor(target);
this.payload = payload; this.payload = payload;
} }
public override Activity Tick(Actor self) public override Activity Tick(Actor self)
{ {
if (IsCanceled) return NextActivity; if (IsCanceled || !target.IsValid)
if (target == null || !target.IsInWorld || target.IsDead()) return NextActivity;
if (!target.OccupiesSpace.OccupiedCells().Any(x => x.First == self.Location))
return NextActivity; return NextActivity;
target.Owner.PlayerActor.Trait<PlayerResources>().GiveCash(payload); var targetPlayer = target.Actor.Owner;
targetPlayer.PlayerActor.Trait<PlayerResources>().GiveCash(payload);
self.Destroy(); self.Destroy();
if (self.World.LocalPlayer == null || self.Owner.Stances[self.World.LocalPlayer] == Stance.Ally)
self.World.AddFrameEndTask(w => w.Add(new CashTick(payload, 30, 2, target.CenterLocation, target.Owner.ColorRamp.GetColor(0)))); if (self.Owner.IsAlliedWith(self.World.RenderPlayer))
self.World.AddFrameEndTask(w => w.Add(new CashTick(payload, 30, 2, target.CenterLocation, targetPlayer.ColorRamp.GetColor(0))));
return this; return this;
} }

View File

@@ -8,6 +8,7 @@
*/ */
#endregion #endregion
using System.Linq;
using OpenRA.Mods.RA.Move; using OpenRA.Mods.RA.Move;
using OpenRA.Traits; using OpenRA.Traits;
@@ -15,20 +16,38 @@ namespace OpenRA.Mods.RA.Activities
{ {
public class Enter : Activity public class Enter : Activity
{ {
readonly Actor target; readonly Target target;
public Enter( Actor target ) { this.target = target; } readonly Activity inner;
public override Activity Tick( Actor self ) public Enter(Actor target, Activity inner)
{ {
if( IsCanceled || target.Destroyed || !target.IsInWorld ) this.target = Target.FromActor(target);
this.inner = inner;
}
public override Activity Tick(Actor self)
{
if (IsCanceled || !target.IsValid)
return NextActivity; return NextActivity;
var mobile = self.Trait<Mobile>(); if (!Util.AdjacentCells(target).Any(c => c == self.Location))
var nearest = target.OccupiesSpace.NearestCellTo( mobile.toCell ); return Util.SequenceActivities(new MoveAdjacentTo(target), this);
if( ( nearest - mobile.toCell ).LengthSquared > 2 )
return Util.SequenceActivities( new MoveAdjacentTo( Target.FromActor(target) ), this );
return Util.SequenceActivities( mobile.MoveTo( nearest, target ), NextActivity ); // Move to the middle of the target, ignoring impassable tiles
var mobile = self.Trait<Mobile>();
var to = target.CenterLocation;
var from = self.CenterLocation;
var speed = mobile.MovementSpeedForCell(self, self.Location);
var length = speed > 0 ? (int)((to - from).Length * 3 / speed) : 0;
return Util.SequenceActivities(
new Turn(Util.GetFacing(to - from, mobile.Facing)),
new Drag(from, to, length),
inner,
new Turn(Util.GetFacing(from - to, mobile.Facing)),
new Drag(to, from, length),
NextActivity
);
} }
} }
} }

View File

@@ -16,31 +16,23 @@ namespace OpenRA.Mods.RA.Activities
{ {
class Infiltrate : Activity class Infiltrate : Activity
{ {
Actor target; Target target;
public Infiltrate(Actor target) { this.target = target; } public Infiltrate(Actor target) { this.target = Target.FromActor(target); }
public override Activity Tick(Actor self) public override Activity Tick(Actor self)
{ {
if (IsCanceled) return NextActivity; if (IsCanceled || !target.IsValid || target.Actor.Owner == self.Owner)
if (target == null || !target.IsInWorld || target.IsDead()) return NextActivity;
if (target.Owner == self.Owner) return NextActivity;
if( !target.OccupiesSpace.OccupiedCells().Any( x => x.First == self.Location ) )
return NextActivity; return NextActivity;
foreach (var t in target.TraitsImplementing<IAcceptInfiltrator>()) foreach (var t in target.Actor.TraitsImplementing<IAcceptInfiltrator>())
t.OnInfiltrate(target, self); t.OnInfiltrate(target.Actor, self);
if (self.HasTrait<DontDestroyWhenInfiltrating>()) if (self.HasTrait<DontDestroyWhenInfiltrating>())
self.World.AddFrameEndTask(w => self.World.AddFrameEndTask(w => { if (!self.Destroyed) w.Remove(self); });
{
if (self.Destroyed) return;
w.Remove(self);
});
else else
self.Destroy(); self.Destroy();
if (target.HasTrait<Building>()) if (target.Actor.HasTrait<Building>())
Sound.PlayToPlayer(self.Owner, "bldginf1.aud"); Sound.PlayToPlayer(self.Owner, "bldginf1.aud");
return this; return this;

View File

@@ -17,33 +17,34 @@ namespace OpenRA.Mods.RA.Activities
{ {
readonly Target target; readonly Target target;
public MoveAdjacentTo( Target target ) { this.target = target; } public MoveAdjacentTo(Target target) { this.target = target; }
public override Activity Tick( Actor self ) public override Activity Tick(Actor self)
{ {
if( IsCanceled || !target.IsValid) return NextActivity; if (IsCanceled || !target.IsValid)
return NextActivity;
var mobile = self.Trait<Mobile>(); var mobile = self.Trait<Mobile>();
var ps1 = new PathSearch(self.World, mobile.Info, self)
var ps1 = new PathSearch( self.World, mobile.Info, self )
{ {
checkForBlocked = true, checkForBlocked = true,
heuristic = location => 0, heuristic = location => 0,
inReverse = true inReverse = true
}; };
foreach( var cell in Util.AdjacentCells(target) ) foreach (var cell in Util.AdjacentCells(target))
{
if (cell == self.Location) if (cell == self.Location)
return NextActivity; return NextActivity;
else else
ps1.AddInitialCell( cell ); ps1.AddInitialCell(cell);
}
ps1.heuristic = PathSearch.DefaultEstimator( mobile.toCell ); ps1.heuristic = PathSearch.DefaultEstimator(mobile.toCell);
var ps2 = PathSearch.FromPoint(self.World, mobile.Info, self, mobile.toCell, target.CenterLocation.ToCPos(), true);
var ret = self.World.WorldActor.Trait<PathFinder>().FindBidiPath(ps1, ps2);
var ps2 = PathSearch.FromPoint( self.World, mobile.Info, self, mobile.toCell, target.CenterLocation.ToCPos(), true ); return Util.SequenceActivities(mobile.MoveTo(() => ret), this);
var ret = self.World.WorldActor.Trait<PathFinder>().FindBidiPath( ps1, ps2 );
return Util.SequenceActivities( mobile.MoveTo( () => ret ), this );
} }
} }
} }

View File

@@ -15,22 +15,20 @@ namespace OpenRA.Mods.RA.Activities
{ {
class RepairBuilding : Activity class RepairBuilding : Activity
{ {
Actor target; Target target;
public RepairBuilding(Actor target) { this.target = target; } public RepairBuilding(Actor target) { this.target = Target.FromActor(target); }
public override Activity Tick(Actor self) public override Activity Tick(Actor self)
{ {
if (IsCanceled) return NextActivity; if (IsCanceled || !target.IsValid)
if (target == null || !target.IsInWorld || target.IsDead()) return NextActivity;
if( !target.OccupiesSpace.OccupiedCells().Any( x => x.First == self.Location ) )
return NextActivity; return NextActivity;
var health = target.Trait<Health>(); var health = target.Actor.Trait<Health>();
if (health.DamageState == DamageState.Undamaged) if (health.DamageState == DamageState.Undamaged)
return NextActivity; return NextActivity;
target.InflictDamage(self, -health.MaxHP, null); target.Actor.InflictDamage(self, -health.MaxHP, null);
self.Destroy(); self.Destroy();
return this; return this;

View File

@@ -52,11 +52,8 @@ namespace OpenRA.Mods.RA
{ {
self.SetTargetLine(Target.FromOrder(order), Color.Red); self.SetTargetLine(Target.FromOrder(order), Color.Red);
var mobile = self.Trait<Mobile>();
self.CancelActivity(); self.CancelActivity();
self.QueueActivity(new Enter(order.TargetActor)); self.QueueActivity(new Enter(order.TargetActor, new Demolish(order.TargetActor, Info.C4Delay)));
self.QueueActivity(new Demolish(order.TargetActor, Info.C4Delay));
self.QueueActivity(mobile.MoveTo(self.Location, 0));
} }
} }

View File

@@ -26,9 +26,9 @@ namespace OpenRA.Mods.RA
get { yield return new EngineerRepairOrderTargeter(); } get { yield return new EngineerRepairOrderTargeter(); }
} }
public Order IssueOrder( Actor self, IOrderTargeter order, Target target, bool queued ) public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)
{ {
if( order.OrderID == "EngineerRepair" ) if (order.OrderID == "EngineerRepair")
return new Order(order.OrderID, self, queued) { TargetActor = target.Actor }; return new Order(order.OrderID, self, queued) { TargetActor = target.Actor };
return null; return null;
@@ -36,41 +36,43 @@ namespace OpenRA.Mods.RA
public string VoicePhraseForOrder(Actor self, Order order) public string VoicePhraseForOrder(Actor self, Order order)
{ {
return (order.OrderString == "EngineerRepair" return (order.OrderString == "EngineerRepair" &&
&& order.TargetActor.GetDamageState() > DamageState.Undamaged) ? "Attack" : null; order.TargetActor.GetDamageState() > DamageState.Undamaged) ? "Attack" : null;
} }
public void ResolveOrder(Actor self, Order order) public void ResolveOrder(Actor self, Order order)
{ {
if (order.OrderString == "EngineerRepair" if (order.OrderString == "EngineerRepair" &&
&& order.TargetActor.GetDamageState() > DamageState.Undamaged) order.TargetActor.GetDamageState() > DamageState.Undamaged)
{ {
self.SetTargetLine(Target.FromOrder(order), Color.Yellow); self.SetTargetLine(Target.FromOrder(order), Color.Yellow);
self.CancelActivity(); self.CancelActivity();
self.QueueActivity(new Enter(order.TargetActor)); self.QueueActivity(new Enter(order.TargetActor, new RepairBuilding(order.TargetActor)));
self.QueueActivity(new RepairBuilding(order.TargetActor));
} }
} }
class EngineerRepairOrderTargeter : UnitTraitOrderTargeter<Building> class EngineerRepairOrderTargeter : UnitTraitOrderTargeter<Building>
{ {
public EngineerRepairOrderTargeter() public EngineerRepairOrderTargeter()
: base( "EngineerRepair", 6, "goldwrench", false, true ) { } : base("EngineerRepair", 6, "goldwrench", false, true) { }
public override bool CanTargetActor(Actor self, Actor target, bool forceAttack, bool forceQueued, ref string cursor) public override bool CanTargetActor(Actor self, Actor target, bool forceAttack, bool forceQueued, ref string cursor)
{ {
if( !base.CanTargetActor( self, target, forceAttack, forceQueued, ref cursor ) ) return false; if (!base.CanTargetActor(self, target, forceAttack, forceQueued, ref cursor))
return false;
if (!target.HasTrait<EngineerRepairable>()) if (!target.HasTrait<EngineerRepairable>())
return false; return false;
if (self.Owner.Stances[ target.Owner ] != Stance.Ally) if (self.Owner.Stances[target.Owner] != Stance.Ally)
return false; return false;
IsQueued = forceQueued; IsQueued = forceQueued;
if( target.GetDamageState() == DamageState.Undamaged ) if (target.GetDamageState() == DamageState.Undamaged)
cursor = "goldwrench-blocked"; cursor = "goldwrench-blocked";
return true; return true;
} }
} }

View File

@@ -67,8 +67,7 @@ namespace OpenRA.Mods.RA
self.SetTargetLine(Target.FromOrder(order), Color.Red); self.SetTargetLine(Target.FromOrder(order), Color.Red);
self.CancelActivity(); self.CancelActivity();
self.QueueActivity(new Enter(order.TargetActor)); self.QueueActivity(new Enter(order.TargetActor, new Infiltrate(order.TargetActor)));
self.QueueActivity(new Infiltrate(order.TargetActor));
} }
} }

View File

@@ -59,8 +59,7 @@ namespace OpenRA.Mods.RA
{ {
self.SetTargetLine(Target.FromOrder(order), Color.Yellow); self.SetTargetLine(Target.FromOrder(order), Color.Yellow);
self.CancelActivity(); self.CancelActivity();
self.QueueActivity(new Enter(order.TargetActor)); self.QueueActivity(new Enter(order.TargetActor, new DonateSupplies(order.TargetActor, Info.Payload)));
self.QueueActivity(new DonateSupplies(order.TargetActor, Info.Payload));
} }
} }