Reorganized Mobile.cs to be more readable and structured
Implicit interface members, explicit interface members, local method etc. were happily scattered all over the place. We can't explicitly implement most IMove/IPositionable interface members without some large rewrite, so we should at least organize the file in a way that makes it less of a pain to tell which parts belong to which interface.
This commit is contained in:
@@ -94,8 +94,11 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
readonly Actor self;
|
||||
readonly Lazy<IEnumerable<int>> speedModifiers;
|
||||
|
||||
#region IMove IsMoving checks
|
||||
public bool IsMoving { get; set; }
|
||||
public bool IsMovingVertically { get { return false; } set { } }
|
||||
#endregion
|
||||
|
||||
int facing;
|
||||
CPos fromCell, toCell;
|
||||
@@ -104,6 +107,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
INotifyVisualPositionChanged[] notifyVisualPositionChanged;
|
||||
INotifyFinishedMoving[] notifyFinishedMoving;
|
||||
|
||||
#region IFacing
|
||||
[Sync] public int Facing
|
||||
{
|
||||
get { return facing; }
|
||||
@@ -111,31 +115,27 @@ namespace OpenRA.Mods.Common.Traits
|
||||
}
|
||||
|
||||
public int TurnSpeed { get { return Info.TurnSpeed; } }
|
||||
#endregion
|
||||
|
||||
[Sync] public WPos CenterPosition { get; private set; }
|
||||
[Sync] public CPos FromCell { get { return fromCell; } }
|
||||
[Sync] public CPos ToCell { get { return toCell; } }
|
||||
|
||||
[Sync] public int PathHash; // written by Move.EvalPath, to temporarily debug this crap.
|
||||
|
||||
// Sets only the location (fromCell, toCell, FromSubCell, ToSubCell)
|
||||
public void SetLocation(CPos from, SubCell fromSub, CPos to, SubCell toSub)
|
||||
#region IOccupySpace
|
||||
[Sync] public WPos CenterPosition { get; private set; }
|
||||
public CPos TopLeft { get { return ToCell; } }
|
||||
|
||||
public Pair<CPos, SubCell>[] OccupiedCells()
|
||||
{
|
||||
if (FromCell == from && ToCell == to && FromSubCell == fromSub && ToSubCell == toSub)
|
||||
return;
|
||||
if (FromCell == ToCell)
|
||||
return new[] { Pair.New(FromCell, FromSubCell) };
|
||||
if (CanEnterCell(ToCell))
|
||||
return new[] { Pair.New(ToCell, ToSubCell) };
|
||||
|
||||
RemoveInfluence();
|
||||
fromCell = from;
|
||||
toCell = to;
|
||||
FromSubCell = fromSub;
|
||||
ToSubCell = toSub;
|
||||
AddInfluence();
|
||||
|
||||
// Most custom layer conditions are added/removed when starting the transition between layers.
|
||||
if (toCell.Layer != fromCell.Layer)
|
||||
foreach (var n in notifyCustomLayerChanged)
|
||||
n.CustomLayerChanged(self, fromCell.Layer, toCell.Layer);
|
||||
return new[] { Pair.New(FromCell, FromSubCell), Pair.New(ToCell, ToSubCell) };
|
||||
}
|
||||
#endregion
|
||||
|
||||
public Mobile(ActorInitializer init, MobileInfo info)
|
||||
: base(info)
|
||||
@@ -171,65 +171,6 @@ namespace OpenRA.Mods.Common.Traits
|
||||
base.Created(self);
|
||||
}
|
||||
|
||||
// Returns a valid sub-cell
|
||||
public SubCell GetValidSubCell(SubCell preferred = SubCell.Any)
|
||||
{
|
||||
// Try same sub-cell
|
||||
if (preferred == SubCell.Any)
|
||||
preferred = FromSubCell;
|
||||
|
||||
// Fix sub-cell assignment
|
||||
if (Info.LocomotorInfo.SharesCell)
|
||||
{
|
||||
if (preferred <= SubCell.FullCell)
|
||||
return self.World.Map.Grid.DefaultSubCell;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (preferred != SubCell.FullCell)
|
||||
return SubCell.FullCell;
|
||||
}
|
||||
|
||||
return preferred;
|
||||
}
|
||||
|
||||
// Sets the location (fromCell, toCell, FromSubCell, ToSubCell) and visual position (CenterPosition)
|
||||
public void SetPosition(Actor self, CPos cell, SubCell subCell = SubCell.Any)
|
||||
{
|
||||
subCell = GetValidSubCell(subCell);
|
||||
SetLocation(cell, subCell, cell, subCell);
|
||||
|
||||
var position = cell.Layer == 0 ? self.World.Map.CenterOfCell(cell) :
|
||||
self.World.GetCustomMovementLayers()[cell.Layer].CenterOfCell(cell);
|
||||
|
||||
var subcellOffset = self.World.Map.Grid.OffsetOfSubCell(subCell);
|
||||
SetVisualPosition(self, position + subcellOffset);
|
||||
FinishedMoving(self);
|
||||
}
|
||||
|
||||
// Sets the location (fromCell, toCell, FromSubCell, ToSubCell) and visual position (CenterPosition)
|
||||
public void SetPosition(Actor self, WPos pos)
|
||||
{
|
||||
var cell = self.World.Map.CellContaining(pos);
|
||||
SetLocation(cell, FromSubCell, cell, FromSubCell);
|
||||
SetVisualPosition(self, self.World.Map.CenterOfSubCell(cell, FromSubCell) + new WVec(0, 0, self.World.Map.DistanceAboveTerrain(pos).Length));
|
||||
FinishedMoving(self);
|
||||
}
|
||||
|
||||
// Sets only the visual position (CenterPosition)
|
||||
public void SetVisualPosition(Actor self, WPos pos)
|
||||
{
|
||||
CenterPosition = pos;
|
||||
self.World.UpdateMaps(self, this);
|
||||
|
||||
// The first time SetVisualPosition is called is in the constructor before creation, so we need a null check here as well
|
||||
if (notifyVisualPositionChanged == null)
|
||||
return;
|
||||
|
||||
foreach (var n in notifyVisualPositionChanged)
|
||||
n.VisualPositionChanged(self, fromCell.Layer, toCell.Layer);
|
||||
}
|
||||
|
||||
void INotifyAddedToWorld.AddedToWorld(Actor self)
|
||||
{
|
||||
self.World.AddToMaps(self, this);
|
||||
@@ -240,213 +181,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
self.World.RemoveFromMaps(self, this);
|
||||
}
|
||||
|
||||
public IEnumerable<IOrderTargeter> Orders { get { yield return new MoveOrderTargeter(self, this); } }
|
||||
|
||||
// Note: Returns a valid order even if the unit can't move to the target
|
||||
public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)
|
||||
{
|
||||
if (order is MoveOrderTargeter)
|
||||
return new Order("Move", self, target, queued);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public CPos NearestMoveableCell(CPos target)
|
||||
{
|
||||
// Limit search to a radius of 10 tiles
|
||||
return NearestMoveableCell(target, 1, 10);
|
||||
}
|
||||
|
||||
public CPos NearestMoveableCell(CPos target, int minRange, int maxRange)
|
||||
{
|
||||
// HACK: This entire method is a hack, and needs to be replaced with
|
||||
// a proper path search that can account for movement layer transitions.
|
||||
// HACK: Work around code that blindly tries to move to cells in invalid movement layers.
|
||||
// This will need to change (by removing this method completely as above) before we can
|
||||
// properly support user-issued orders on to elevated bridges or other interactable custom layers
|
||||
if (target.Layer != 0)
|
||||
target = new CPos(target.X, target.Y);
|
||||
|
||||
if (CanEnterCell(target))
|
||||
return target;
|
||||
|
||||
foreach (var tile in self.World.Map.FindTilesInAnnulus(target, minRange, maxRange))
|
||||
if (CanEnterCell(tile))
|
||||
return tile;
|
||||
|
||||
// Couldn't find a cell
|
||||
return target;
|
||||
}
|
||||
|
||||
public CPos NearestCell(CPos target, Func<CPos, bool> check, int minRange, int maxRange)
|
||||
{
|
||||
if (check(target))
|
||||
return target;
|
||||
|
||||
foreach (var tile in self.World.Map.FindTilesInAnnulus(target, minRange, maxRange))
|
||||
if (check(tile))
|
||||
return tile;
|
||||
|
||||
// Couldn't find a cell
|
||||
return target;
|
||||
}
|
||||
|
||||
public void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
if (order.OrderString == "Move")
|
||||
{
|
||||
var loc = self.World.Map.Clamp(order.TargetLocation);
|
||||
|
||||
if (!Info.LocomotorInfo.MoveIntoShroud && !self.Owner.Shroud.IsExplored(loc))
|
||||
return;
|
||||
|
||||
if (!order.Queued)
|
||||
self.CancelActivity();
|
||||
|
||||
TicksBeforePathing = AverageTicksBeforePathing + self.World.SharedRandom.Next(-SpreadTicksBeforePathing, SpreadTicksBeforePathing);
|
||||
|
||||
self.SetTargetLine(Target.FromCell(self.World, loc), Color.Green);
|
||||
self.QueueActivity(order.Queued, new Move(self, loc, WDist.FromCells(8), null, true));
|
||||
}
|
||||
|
||||
if (order.OrderString == "Stop")
|
||||
self.CancelActivity();
|
||||
|
||||
if (order.OrderString == "Scatter")
|
||||
Nudge(self, self, true);
|
||||
}
|
||||
|
||||
public string VoicePhraseForOrder(Actor self, Order order)
|
||||
{
|
||||
if (!Info.LocomotorInfo.MoveIntoShroud && !self.Owner.Shroud.IsExplored(order.TargetLocation))
|
||||
return null;
|
||||
|
||||
switch (order.OrderString)
|
||||
{
|
||||
case "Move":
|
||||
case "Scatter":
|
||||
case "Stop":
|
||||
return Info.Voice;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public CPos TopLeft { get { return ToCell; } }
|
||||
|
||||
public Pair<CPos, SubCell>[] OccupiedCells()
|
||||
{
|
||||
if (FromCell == ToCell)
|
||||
return new[] { Pair.New(FromCell, FromSubCell) };
|
||||
if (CanEnterCell(ToCell))
|
||||
return new[] { Pair.New(ToCell, ToSubCell) };
|
||||
|
||||
return new[] { Pair.New(FromCell, FromSubCell), Pair.New(ToCell, ToSubCell) };
|
||||
}
|
||||
|
||||
public bool IsLeavingCell(CPos location, SubCell subCell = SubCell.Any)
|
||||
{
|
||||
return ToCell != location && fromCell == location
|
||||
&& (subCell == SubCell.Any || FromSubCell == subCell || subCell == SubCell.FullCell || FromSubCell == SubCell.FullCell);
|
||||
}
|
||||
|
||||
public SubCell GetAvailableSubCell(CPos a, SubCell preferredSubCell = SubCell.Any, Actor ignoreActor = null, bool checkTransientActors = true)
|
||||
{
|
||||
var cellConditions = checkTransientActors ? CellConditions.All : CellConditions.None;
|
||||
return Info.LocomotorInfo.GetAvailableSubCell(self.World, self, a, preferredSubCell, ignoreActor, cellConditions);
|
||||
}
|
||||
|
||||
public bool CanExistInCell(CPos cell)
|
||||
{
|
||||
return Info.LocomotorInfo.MovementCostForCell(self.World, cell) != int.MaxValue;
|
||||
}
|
||||
|
||||
public bool CanEnterCell(CPos cell, Actor ignoreActor = null, bool checkTransientActors = true)
|
||||
{
|
||||
return Info.CanEnterCell(self.World, self, cell, ignoreActor, checkTransientActors);
|
||||
}
|
||||
|
||||
public bool CanMoveFreelyInto(CPos cell, Actor ignoreActor = null, bool checkTransientActors = true)
|
||||
{
|
||||
return Info.LocomotorInfo.CanMoveFreelyInto(self.World, self, cell, ignoreActor, checkTransientActors ? CellConditions.All : CellConditions.BlockedByMovers);
|
||||
}
|
||||
|
||||
public void EnteringCell(Actor self)
|
||||
{
|
||||
// Only make actor crush if it is on the ground
|
||||
if (!self.IsAtGroundLevel())
|
||||
return;
|
||||
|
||||
var actors = self.World.ActorMap.GetActorsAt(ToCell).Where(a => a != self).ToList();
|
||||
if (!AnyCrushables(actors))
|
||||
return;
|
||||
|
||||
var notifiers = actors.SelectMany(a => a.TraitsImplementing<INotifyCrushed>().Select(t => new TraitPair<INotifyCrushed>(a, t)));
|
||||
foreach (var notifyCrushed in notifiers)
|
||||
notifyCrushed.Trait.WarnCrush(notifyCrushed.Actor, self, Info.LocomotorInfo.Crushes);
|
||||
}
|
||||
|
||||
public void FinishedMoving(Actor self)
|
||||
{
|
||||
// Need to check both fromCell and toCell because FinishedMoving is called multiple times during the move
|
||||
if (fromCell.Layer == toCell.Layer)
|
||||
foreach (var n in notifyFinishedMoving)
|
||||
n.FinishedMoving(self, fromCell.Layer, toCell.Layer);
|
||||
|
||||
// Only make actor crush if it is on the ground
|
||||
if (!self.IsAtGroundLevel())
|
||||
return;
|
||||
|
||||
var actors = self.World.ActorMap.GetActorsAt(ToCell).Where(a => a != self).ToList();
|
||||
if (!AnyCrushables(actors))
|
||||
return;
|
||||
|
||||
var notifiers = actors.SelectMany(a => a.TraitsImplementing<INotifyCrushed>().Select(t => new TraitPair<INotifyCrushed>(a, t)));
|
||||
foreach (var notifyCrushed in notifiers)
|
||||
notifyCrushed.Trait.OnCrush(notifyCrushed.Actor, self, Info.LocomotorInfo.Crushes);
|
||||
}
|
||||
|
||||
bool AnyCrushables(List<Actor> actors)
|
||||
{
|
||||
var crushables = actors.SelectMany(a => a.TraitsImplementing<ICrushable>().Select(t => new TraitPair<ICrushable>(a, t))).ToList();
|
||||
if (crushables.Count == 0)
|
||||
return false;
|
||||
|
||||
foreach (var crushes in crushables)
|
||||
if (crushes.Trait.CrushableBy(crushes.Actor, self, Info.LocomotorInfo.Crushes))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public int MovementSpeedForCell(Actor self, CPos cell)
|
||||
{
|
||||
var index = cell.Layer == 0 ? self.World.Map.GetTerrainIndex(cell) :
|
||||
self.World.GetCustomMovementLayers()[cell.Layer].GetTerrainIndex(cell);
|
||||
|
||||
if (index == byte.MaxValue)
|
||||
return 0;
|
||||
|
||||
var terrainSpeed = Info.LocomotorInfo.TilesetTerrainInfo[self.World.Map.Rules.TileSet][index].Speed;
|
||||
if (terrainSpeed == 0)
|
||||
return 0;
|
||||
|
||||
var modifiers = speedModifiers.Value.Append(terrainSpeed);
|
||||
|
||||
return Util.ApplyPercentageModifiers(Info.Speed, modifiers);
|
||||
}
|
||||
|
||||
public void AddInfluence()
|
||||
{
|
||||
if (self.IsInWorld)
|
||||
self.World.ActorMap.AddInfluence(self, this);
|
||||
}
|
||||
|
||||
public void RemoveInfluence()
|
||||
{
|
||||
if (self.IsInWorld)
|
||||
self.World.ActorMap.RemoveInfluence(self, this);
|
||||
}
|
||||
#region Local misc stuff
|
||||
|
||||
public void Nudge(Actor self, Actor nudger, bool force)
|
||||
{
|
||||
@@ -529,11 +264,410 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IPositionable
|
||||
|
||||
// Returns a valid sub-cell
|
||||
public SubCell GetValidSubCell(SubCell preferred = SubCell.Any)
|
||||
{
|
||||
// Try same sub-cell
|
||||
if (preferred == SubCell.Any)
|
||||
preferred = FromSubCell;
|
||||
|
||||
// Fix sub-cell assignment
|
||||
if (Info.LocomotorInfo.SharesCell)
|
||||
{
|
||||
if (preferred <= SubCell.FullCell)
|
||||
return self.World.Map.Grid.DefaultSubCell;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (preferred != SubCell.FullCell)
|
||||
return SubCell.FullCell;
|
||||
}
|
||||
|
||||
return preferred;
|
||||
}
|
||||
|
||||
// Sets the location (fromCell, toCell, FromSubCell, ToSubCell) and visual position (CenterPosition)
|
||||
public void SetPosition(Actor self, CPos cell, SubCell subCell = SubCell.Any)
|
||||
{
|
||||
subCell = GetValidSubCell(subCell);
|
||||
SetLocation(cell, subCell, cell, subCell);
|
||||
|
||||
var position = cell.Layer == 0 ? self.World.Map.CenterOfCell(cell) :
|
||||
self.World.GetCustomMovementLayers()[cell.Layer].CenterOfCell(cell);
|
||||
|
||||
var subcellOffset = self.World.Map.Grid.OffsetOfSubCell(subCell);
|
||||
SetVisualPosition(self, position + subcellOffset);
|
||||
FinishedMoving(self);
|
||||
}
|
||||
|
||||
// Sets the location (fromCell, toCell, FromSubCell, ToSubCell) and visual position (CenterPosition)
|
||||
public void SetPosition(Actor self, WPos pos)
|
||||
{
|
||||
var cell = self.World.Map.CellContaining(pos);
|
||||
SetLocation(cell, FromSubCell, cell, FromSubCell);
|
||||
SetVisualPosition(self, self.World.Map.CenterOfSubCell(cell, FromSubCell) + new WVec(0, 0, self.World.Map.DistanceAboveTerrain(pos).Length));
|
||||
FinishedMoving(self);
|
||||
}
|
||||
|
||||
// Sets only the visual position (CenterPosition)
|
||||
public void SetVisualPosition(Actor self, WPos pos)
|
||||
{
|
||||
CenterPosition = pos;
|
||||
self.World.UpdateMaps(self, this);
|
||||
|
||||
// The first time SetVisualPosition is called is in the constructor before creation, so we need a null check here as well
|
||||
if (notifyVisualPositionChanged == null)
|
||||
return;
|
||||
|
||||
foreach (var n in notifyVisualPositionChanged)
|
||||
n.VisualPositionChanged(self, fromCell.Layer, toCell.Layer);
|
||||
}
|
||||
|
||||
public bool IsLeavingCell(CPos location, SubCell subCell = SubCell.Any)
|
||||
{
|
||||
return ToCell != location && fromCell == location
|
||||
&& (subCell == SubCell.Any || FromSubCell == subCell || subCell == SubCell.FullCell || FromSubCell == SubCell.FullCell);
|
||||
}
|
||||
|
||||
public SubCell GetAvailableSubCell(CPos a, SubCell preferredSubCell = SubCell.Any, Actor ignoreActor = null, bool checkTransientActors = true)
|
||||
{
|
||||
var cellConditions = checkTransientActors ? CellConditions.All : CellConditions.None;
|
||||
return Info.LocomotorInfo.GetAvailableSubCell(self.World, self, a, preferredSubCell, ignoreActor, cellConditions);
|
||||
}
|
||||
|
||||
public bool CanExistInCell(CPos cell)
|
||||
{
|
||||
return Info.LocomotorInfo.MovementCostForCell(self.World, cell) != int.MaxValue;
|
||||
}
|
||||
|
||||
public bool CanEnterCell(CPos cell, Actor ignoreActor = null, bool checkTransientActors = true)
|
||||
{
|
||||
return Info.CanEnterCell(self.World, self, cell, ignoreActor, checkTransientActors);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Local IPositionable-related
|
||||
|
||||
// Sets only the location (fromCell, toCell, FromSubCell, ToSubCell)
|
||||
public void SetLocation(CPos from, SubCell fromSub, CPos to, SubCell toSub)
|
||||
{
|
||||
if (FromCell == from && ToCell == to && FromSubCell == fromSub && ToSubCell == toSub)
|
||||
return;
|
||||
|
||||
RemoveInfluence();
|
||||
fromCell = from;
|
||||
toCell = to;
|
||||
FromSubCell = fromSub;
|
||||
ToSubCell = toSub;
|
||||
AddInfluence();
|
||||
|
||||
// Most custom layer conditions are added/removed when starting the transition between layers.
|
||||
if (toCell.Layer != fromCell.Layer)
|
||||
foreach (var n in notifyCustomLayerChanged)
|
||||
n.CustomLayerChanged(self, fromCell.Layer, toCell.Layer);
|
||||
}
|
||||
|
||||
public void FinishedMoving(Actor self)
|
||||
{
|
||||
// Need to check both fromCell and toCell because FinishedMoving is called multiple times during the move
|
||||
if (fromCell.Layer == toCell.Layer)
|
||||
foreach (var n in notifyFinishedMoving)
|
||||
n.FinishedMoving(self, fromCell.Layer, toCell.Layer);
|
||||
|
||||
// Only make actor crush if it is on the ground
|
||||
if (!self.IsAtGroundLevel())
|
||||
return;
|
||||
|
||||
var actors = self.World.ActorMap.GetActorsAt(ToCell).Where(a => a != self).ToList();
|
||||
if (!AnyCrushables(actors))
|
||||
return;
|
||||
|
||||
var notifiers = actors.SelectMany(a => a.TraitsImplementing<INotifyCrushed>().Select(t => new TraitPair<INotifyCrushed>(a, t)));
|
||||
foreach (var notifyCrushed in notifiers)
|
||||
notifyCrushed.Trait.OnCrush(notifyCrushed.Actor, self, Info.LocomotorInfo.Crushes);
|
||||
}
|
||||
|
||||
bool AnyCrushables(List<Actor> actors)
|
||||
{
|
||||
var crushables = actors.SelectMany(a => a.TraitsImplementing<ICrushable>().Select(t => new TraitPair<ICrushable>(a, t))).ToList();
|
||||
if (crushables.Count == 0)
|
||||
return false;
|
||||
|
||||
foreach (var crushes in crushables)
|
||||
if (crushes.Trait.CrushableBy(crushes.Actor, self, Info.LocomotorInfo.Crushes))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void AddInfluence()
|
||||
{
|
||||
if (self.IsInWorld)
|
||||
self.World.ActorMap.AddInfluence(self, this);
|
||||
}
|
||||
|
||||
public void RemoveInfluence()
|
||||
{
|
||||
if (self.IsInWorld)
|
||||
self.World.ActorMap.RemoveInfluence(self, this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IMove
|
||||
|
||||
public Activity MoveTo(CPos cell, int nearEnough) { return new Move(self, cell, WDist.FromCells(nearEnough)); }
|
||||
public Activity MoveTo(CPos cell, Actor ignoreActor) { return new Move(self, cell, WDist.Zero, ignoreActor); }
|
||||
public Activity MoveWithinRange(Target target, WDist range) { return new MoveWithinRange(self, target, WDist.Zero, range); }
|
||||
public Activity MoveWithinRange(Target target, WDist minRange, WDist maxRange) { return new MoveWithinRange(self, target, minRange, maxRange); }
|
||||
public Activity MoveFollow(Actor self, Target target, WDist minRange, WDist maxRange) { return new Follow(self, target, minRange, maxRange); }
|
||||
|
||||
public Activity MoveIntoWorld(Actor self, CPos cell, SubCell subCell = SubCell.Any)
|
||||
{
|
||||
var pos = self.CenterPosition;
|
||||
|
||||
if (subCell == SubCell.Any)
|
||||
subCell = Info.LocomotorInfo.SharesCell ? self.World.ActorMap.FreeSubCell(cell, subCell) : SubCell.FullCell;
|
||||
|
||||
// TODO: solve/reduce cell is full problem
|
||||
if (subCell == SubCell.Invalid)
|
||||
subCell = self.World.Map.Grid.DefaultSubCell;
|
||||
|
||||
// Reserve the exit cell
|
||||
SetPosition(self, cell, subCell);
|
||||
SetVisualPosition(self, pos);
|
||||
|
||||
return VisualMove(self, pos, self.World.Map.CenterOfSubCell(cell, subCell), cell);
|
||||
}
|
||||
|
||||
public Activity MoveToTarget(Actor self, Target target)
|
||||
{
|
||||
if (target.Type == TargetType.Invalid)
|
||||
return null;
|
||||
|
||||
return new MoveAdjacentTo(self, target);
|
||||
}
|
||||
|
||||
public Activity MoveIntoTarget(Actor self, Target target)
|
||||
{
|
||||
if (target.Type == TargetType.Invalid)
|
||||
return null;
|
||||
|
||||
return VisualMove(self, self.CenterPosition, target.Positions.PositionClosestTo(self.CenterPosition));
|
||||
}
|
||||
|
||||
public Activity VisualMove(Actor self, WPos fromPos, WPos toPos)
|
||||
{
|
||||
return VisualMove(self, fromPos, toPos, self.Location);
|
||||
}
|
||||
|
||||
public CPos NearestMoveableCell(CPos target)
|
||||
{
|
||||
// Limit search to a radius of 10 tiles
|
||||
return NearestMoveableCell(target, 1, 10);
|
||||
}
|
||||
|
||||
public bool CanEnterTargetNow(Actor self, Target target)
|
||||
{
|
||||
return self.Location == self.World.Map.CellContaining(target.CenterPosition) || Util.AdjacentCells(self.World, target).Any(c => c == self.Location);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Local IMove-related
|
||||
|
||||
public int MovementSpeedForCell(Actor self, CPos cell)
|
||||
{
|
||||
var index = cell.Layer == 0 ? self.World.Map.GetTerrainIndex(cell) :
|
||||
self.World.GetCustomMovementLayers()[cell.Layer].GetTerrainIndex(cell);
|
||||
|
||||
if (index == byte.MaxValue)
|
||||
return 0;
|
||||
|
||||
var terrainSpeed = Info.LocomotorInfo.TilesetTerrainInfo[self.World.Map.Rules.TileSet][index].Speed;
|
||||
if (terrainSpeed == 0)
|
||||
return 0;
|
||||
|
||||
var modifiers = speedModifiers.Value.Append(terrainSpeed);
|
||||
|
||||
return Util.ApplyPercentageModifiers(Info.Speed, modifiers);
|
||||
}
|
||||
|
||||
public CPos NearestMoveableCell(CPos target, int minRange, int maxRange)
|
||||
{
|
||||
// HACK: This entire method is a hack, and needs to be replaced with
|
||||
// a proper path search that can account for movement layer transitions.
|
||||
// HACK: Work around code that blindly tries to move to cells in invalid movement layers.
|
||||
// This will need to change (by removing this method completely as above) before we can
|
||||
// properly support user-issued orders on to elevated bridges or other interactable custom layers
|
||||
if (target.Layer != 0)
|
||||
target = new CPos(target.X, target.Y);
|
||||
|
||||
if (CanEnterCell(target))
|
||||
return target;
|
||||
|
||||
foreach (var tile in self.World.Map.FindTilesInAnnulus(target, minRange, maxRange))
|
||||
if (CanEnterCell(tile))
|
||||
return tile;
|
||||
|
||||
// Couldn't find a cell
|
||||
return target;
|
||||
}
|
||||
|
||||
public CPos NearestCell(CPos target, Func<CPos, bool> check, int minRange, int maxRange)
|
||||
{
|
||||
if (check(target))
|
||||
return target;
|
||||
|
||||
foreach (var tile in self.World.Map.FindTilesInAnnulus(target, minRange, maxRange))
|
||||
if (check(tile))
|
||||
return tile;
|
||||
|
||||
// Couldn't find a cell
|
||||
return target;
|
||||
}
|
||||
|
||||
public bool CanMoveFreelyInto(CPos cell, Actor ignoreActor = null, bool checkTransientActors = true)
|
||||
{
|
||||
return Info.LocomotorInfo.CanMoveFreelyInto(self.World, self, cell, ignoreActor, checkTransientActors ? CellConditions.All : CellConditions.BlockedByMovers);
|
||||
}
|
||||
|
||||
public void EnteringCell(Actor self)
|
||||
{
|
||||
// Only make actor crush if it is on the ground
|
||||
if (!self.IsAtGroundLevel())
|
||||
return;
|
||||
|
||||
var actors = self.World.ActorMap.GetActorsAt(ToCell).Where(a => a != self).ToList();
|
||||
if (!AnyCrushables(actors))
|
||||
return;
|
||||
|
||||
var notifiers = actors.SelectMany(a => a.TraitsImplementing<INotifyCrushed>().Select(t => new TraitPair<INotifyCrushed>(a, t)));
|
||||
foreach (var notifyCrushed in notifiers)
|
||||
notifyCrushed.Trait.WarnCrush(notifyCrushed.Actor, self, Info.LocomotorInfo.Crushes);
|
||||
}
|
||||
|
||||
public Activity ScriptedMove(CPos cell) { return new Move(self, cell); }
|
||||
public Activity MoveTo(Func<List<CPos>> pathFunc) { return new Move(self, pathFunc); }
|
||||
|
||||
public Activity VisualMove(Actor self, WPos fromPos, WPos toPos, CPos cell)
|
||||
{
|
||||
var speed = MovementSpeedForCell(self, cell);
|
||||
var length = speed > 0 ? (toPos - fromPos).Length / speed : 0;
|
||||
|
||||
var delta = toPos - fromPos;
|
||||
var facing = delta.HorizontalLengthSquared != 0 ? delta.Yaw.Facing : Facing;
|
||||
return ActivityUtils.SequenceActivities(new Turn(self, facing), new Drag(self, fromPos, toPos, length));
|
||||
}
|
||||
|
||||
CPos? ClosestGroundCell()
|
||||
{
|
||||
var above = new CPos(TopLeft.X, TopLeft.Y);
|
||||
if (CanEnterCell(above))
|
||||
return above;
|
||||
|
||||
var pathFinder = self.World.WorldActor.Trait<IPathFinder>();
|
||||
List<CPos> path;
|
||||
using (var search = PathSearch.Search(self.World, Info.LocomotorInfo, self, true,
|
||||
loc => loc.Layer == 0 && CanEnterCell(loc))
|
||||
.FromPoint(self.Location))
|
||||
path = pathFinder.FindPath(search);
|
||||
|
||||
if (path.Count > 0)
|
||||
return path[0];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
void IActorPreviewInitModifier.ModifyActorPreviewInit(Actor self, TypeDictionary inits)
|
||||
{
|
||||
if (!inits.Contains<DynamicFacingInit>() && !inits.Contains<FacingInit>())
|
||||
inits.Add(new DynamicFacingInit(() => facing));
|
||||
}
|
||||
|
||||
public void ModifyDeathActorInit(Actor self, TypeDictionary init)
|
||||
{
|
||||
init.Add(new FacingInit(facing));
|
||||
|
||||
// Allows the husk to drag to its final position
|
||||
if (CanEnterCell(self.Location, self, false))
|
||||
init.Add(new HuskSpeedInit(MovementSpeedForCell(self, self.Location)));
|
||||
}
|
||||
|
||||
void INotifyBecomingIdle.OnBecomingIdle(Actor self)
|
||||
{
|
||||
if (TopLeft.Layer == 0)
|
||||
return;
|
||||
|
||||
var moveTo = ClosestGroundCell();
|
||||
if (moveTo != null)
|
||||
self.QueueActivity(MoveTo(moveTo.Value, 0));
|
||||
}
|
||||
|
||||
void INotifyBlockingMove.OnNotifyBlockingMove(Actor self, Actor blocking)
|
||||
{
|
||||
if (self.IsIdle && self.AppearsFriendlyTo(blocking))
|
||||
Nudge(self, blocking, true);
|
||||
}
|
||||
|
||||
public IEnumerable<IOrderTargeter> Orders { get { yield return new MoveOrderTargeter(self, this); } }
|
||||
|
||||
// Note: Returns a valid order even if the unit can't move to the target
|
||||
public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)
|
||||
{
|
||||
if (order is MoveOrderTargeter)
|
||||
return new Order("Move", self, target, queued);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
if (order.OrderString == "Move")
|
||||
{
|
||||
var loc = self.World.Map.Clamp(order.TargetLocation);
|
||||
|
||||
if (!Info.LocomotorInfo.MoveIntoShroud && !self.Owner.Shroud.IsExplored(loc))
|
||||
return;
|
||||
|
||||
if (!order.Queued)
|
||||
self.CancelActivity();
|
||||
|
||||
TicksBeforePathing = AverageTicksBeforePathing + self.World.SharedRandom.Next(-SpreadTicksBeforePathing, SpreadTicksBeforePathing);
|
||||
|
||||
self.SetTargetLine(Target.FromCell(self.World, loc), Color.Green);
|
||||
self.QueueActivity(order.Queued, new Move(self, loc, WDist.FromCells(8), null, true));
|
||||
}
|
||||
|
||||
if (order.OrderString == "Stop")
|
||||
self.CancelActivity();
|
||||
|
||||
if (order.OrderString == "Scatter")
|
||||
Nudge(self, self, true);
|
||||
}
|
||||
|
||||
public string VoicePhraseForOrder(Actor self, Order order)
|
||||
{
|
||||
if (!Info.LocomotorInfo.MoveIntoShroud && !self.Owner.Shroud.IsExplored(order.TargetLocation))
|
||||
return null;
|
||||
|
||||
switch (order.OrderString)
|
||||
{
|
||||
case "Move":
|
||||
case "Scatter":
|
||||
case "Stop":
|
||||
return Info.Voice;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class MoveOrderTargeter : IOrderTargeter
|
||||
{
|
||||
@@ -576,111 +710,5 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public Activity ScriptedMove(CPos cell) { return new Move(self, cell); }
|
||||
public Activity MoveTo(CPos cell, int nearEnough) { return new Move(self, cell, WDist.FromCells(nearEnough)); }
|
||||
public Activity MoveTo(CPos cell, Actor ignoreActor) { return new Move(self, cell, WDist.Zero, ignoreActor); }
|
||||
public Activity MoveWithinRange(Target target, WDist range) { return new MoveWithinRange(self, target, WDist.Zero, range); }
|
||||
public Activity MoveWithinRange(Target target, WDist minRange, WDist maxRange) { return new MoveWithinRange(self, target, minRange, maxRange); }
|
||||
public Activity MoveFollow(Actor self, Target target, WDist minRange, WDist maxRange) { return new Follow(self, target, minRange, maxRange); }
|
||||
public Activity MoveTo(Func<List<CPos>> pathFunc) { return new Move(self, pathFunc); }
|
||||
|
||||
void INotifyBlockingMove.OnNotifyBlockingMove(Actor self, Actor blocking)
|
||||
{
|
||||
if (self.IsIdle && self.AppearsFriendlyTo(blocking))
|
||||
Nudge(self, blocking, true);
|
||||
}
|
||||
|
||||
public Activity MoveIntoWorld(Actor self, CPos cell, SubCell subCell = SubCell.Any)
|
||||
{
|
||||
var pos = self.CenterPosition;
|
||||
|
||||
if (subCell == SubCell.Any)
|
||||
subCell = Info.LocomotorInfo.SharesCell ? self.World.ActorMap.FreeSubCell(cell, subCell) : SubCell.FullCell;
|
||||
|
||||
// TODO: solve/reduce cell is full problem
|
||||
if (subCell == SubCell.Invalid)
|
||||
subCell = self.World.Map.Grid.DefaultSubCell;
|
||||
|
||||
// Reserve the exit cell
|
||||
SetPosition(self, cell, subCell);
|
||||
SetVisualPosition(self, pos);
|
||||
|
||||
return VisualMove(self, pos, self.World.Map.CenterOfSubCell(cell, subCell), cell);
|
||||
}
|
||||
|
||||
public Activity MoveToTarget(Actor self, Target target)
|
||||
{
|
||||
if (target.Type == TargetType.Invalid)
|
||||
return null;
|
||||
|
||||
return new MoveAdjacentTo(self, target);
|
||||
}
|
||||
|
||||
public Activity MoveIntoTarget(Actor self, Target target)
|
||||
{
|
||||
if (target.Type == TargetType.Invalid)
|
||||
return null;
|
||||
|
||||
return VisualMove(self, self.CenterPosition, target.Positions.PositionClosestTo(self.CenterPosition));
|
||||
}
|
||||
|
||||
public bool CanEnterTargetNow(Actor self, Target target)
|
||||
{
|
||||
return self.Location == self.World.Map.CellContaining(target.CenterPosition) || Util.AdjacentCells(self.World, target).Any(c => c == self.Location);
|
||||
}
|
||||
|
||||
public Activity VisualMove(Actor self, WPos fromPos, WPos toPos)
|
||||
{
|
||||
return VisualMove(self, fromPos, toPos, self.Location);
|
||||
}
|
||||
|
||||
public Activity VisualMove(Actor self, WPos fromPos, WPos toPos, CPos cell)
|
||||
{
|
||||
var speed = MovementSpeedForCell(self, cell);
|
||||
var length = speed > 0 ? (toPos - fromPos).Length / speed : 0;
|
||||
|
||||
var delta = toPos - fromPos;
|
||||
var facing = delta.HorizontalLengthSquared != 0 ? delta.Yaw.Facing : Facing;
|
||||
return ActivityUtils.SequenceActivities(new Turn(self, facing), new Drag(self, fromPos, toPos, length));
|
||||
}
|
||||
|
||||
public void ModifyDeathActorInit(Actor self, TypeDictionary init)
|
||||
{
|
||||
init.Add(new FacingInit(facing));
|
||||
|
||||
// Allows the husk to drag to its final position
|
||||
if (CanEnterCell(self.Location, self, false))
|
||||
init.Add(new HuskSpeedInit(MovementSpeedForCell(self, self.Location)));
|
||||
}
|
||||
|
||||
CPos? ClosestGroundCell()
|
||||
{
|
||||
var above = new CPos(TopLeft.X, TopLeft.Y);
|
||||
if (CanEnterCell(above))
|
||||
return above;
|
||||
|
||||
var pathFinder = self.World.WorldActor.Trait<IPathFinder>();
|
||||
List<CPos> path;
|
||||
using (var search = PathSearch.Search(self.World, Info.LocomotorInfo, self, true,
|
||||
loc => loc.Layer == 0 && CanEnterCell(loc))
|
||||
.FromPoint(self.Location))
|
||||
path = pathFinder.FindPath(search);
|
||||
|
||||
if (path.Count > 0)
|
||||
return path[0];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
void INotifyBecomingIdle.OnBecomingIdle(Actor self)
|
||||
{
|
||||
if (TopLeft.Layer == 0)
|
||||
return;
|
||||
|
||||
var moveTo = ClosestGroundCell();
|
||||
if (moveTo != null)
|
||||
self.QueueActivity(MoveTo(moveTo.Value, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user