diff --git a/OpenRA.Mods.Common/Traits/Mobile.cs b/OpenRA.Mods.Common/Traits/Mobile.cs index cea44e5ca7..d75570dca2 100644 --- a/OpenRA.Mods.Common/Traits/Mobile.cs +++ b/OpenRA.Mods.Common/Traits/Mobile.cs @@ -94,8 +94,11 @@ namespace OpenRA.Mods.Common.Traits readonly Actor self; readonly Lazy> 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[] 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 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 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[] 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().Select(t => new TraitPair(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().Select(t => new TraitPair(a, t))); - foreach (var notifyCrushed in notifiers) - notifyCrushed.Trait.OnCrush(notifyCrushed.Actor, self, Info.LocomotorInfo.Crushes); - } - - bool AnyCrushables(List actors) - { - var crushables = actors.SelectMany(a => a.TraitsImplementing().Select(t => new TraitPair(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().Select(t => new TraitPair(a, t))); + foreach (var notifyCrushed in notifiers) + notifyCrushed.Trait.OnCrush(notifyCrushed.Actor, self, Info.LocomotorInfo.Crushes); + } + + bool AnyCrushables(List actors) + { + var crushables = actors.SelectMany(a => a.TraitsImplementing().Select(t => new TraitPair(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 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().Select(t => new TraitPair(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> 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(); + List 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() && !inits.Contains()) 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 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> 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(); - List 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)); - } } }