/// <summary> /// Checks if the movement is possible. /// </summary> /// <param name="thing">The thing to eval.</param> /// <param name="distance">The distance to move.</param> /// <param name="isWater">True if this distance is over water, false if over land.</param> /// <param name="waterSpeedRatio">The ratio of land to water speed.</param> /// <returns>True if there is enough remaining move to make this distance.</returns> private static bool CanMove(BaseMovingThing thing, float distance, bool isWater, float waterSpeedRatio) { if (isWater) { return(distance <= thing.ResidualMovement); } else { return(distance <= thing.ResidualMovement * waterSpeedRatio); } }
/// <summary> /// Completes the movement onto the specified thing. /// </summary> /// <param name="newThing">The new version of the inputted thing.</param> public void ApplyMovement(BaseMovingThing newThing) { bool canFly = newThing.MovingThingDefinition.EvalFlies(this.random, newThing); float landSpeed = newThing.MovingThingDefinition.EvalLandSpeed(this.random, newThing); float waterSpeed = newThing.MovingThingDefinition.EvalWaterSpeed(this.random, newThing); float waterSpeedRatio = 1; if (!canFly) { waterSpeedRatio = landSpeed / waterSpeed; } int startX = newThing.X; int startY = newThing.Y; int destinationX, destinationY; Guid?thingMovingTowards = null; switch (newThing.MoveType) { case MoveType.ToCoords: destinationX = newThing.MoveToCoordX ?? throw new InvalidOperationException("Object is moving towards a coord but MoveToCoordX is null."); destinationY = newThing.MoveToCoordY ?? throw new InvalidOperationException("Object is moving towards a coord but MoveToCoordY is null."); break; case MoveType.ToThing: BaseThing thingToMoveTo = this.world.FindThing(newThing.MoveToThing ?? throw new InvalidOperationException("Object is moving towards a thing but MoveToThing is null.")); destinationX = thingToMoveTo.X; destinationY = thingToMoveTo.Y; thingMovingTowards = newThing.MoveToThing; break; default: throw new InvalidOperationException($"Unsupported movetype {newThing.MoveType}"); } bool NeedsRecalcPath() { // If we're moving towards a thing, we need to ensure it hasn't moved. var lastEntry = newThing.Path?.LastOrDefault(); if (lastEntry == null || thingMovingTowards == null) { return(false); } return(lastEntry.X != destinationX || lastEntry.Y != destinationY); } // The residual movement is filled with full movement, and will be reduced as we process. newThing.ResidualMovement = landSpeed + newThing.ResidualMovement; if (newThing.Path == null || NeedsRecalcPath()) { // A new path needs calculating. if (canFly) { newThing.Path = FlightPath((startX, startY), (destinationX, destinationY)).ToImmutableList(); } else { var path = this.finder.FindPath(new Point(startX, startY), new Point(destinationX, destinationY), waterSpeedRatio); if (path != null) { newThing.Path = path.Select(p => new Location() { X = p.X, Y = p.Y }).ToImmutableList(); } } } // Walk the path until we're out of movement. while (true) { var pathEntry = newThing.Path?.FirstOrDefault(); if (pathEntry == null) { // We're done walking. break; } var square = this.world.Grid.GetSquare(pathEntry.X, pathEntry.Y); float movementCost = square.GetMovementCost(this.random); bool isWater = square.SquareDefinition?.Definition.IsWater ?? false; if (!CanMove(newThing, movementCost, isWater, waterSpeedRatio)) { // Costs too much to enter this square. break; } // Remove this path entry. newThing.Path = newThing.Path?.RemoveAt(0); // Apply the movement newThing.X = pathEntry.X; newThing.Y = pathEntry.Y; if (isWater) { newThing.ResidualMovement -= movementCost * waterSpeedRatio; } else { newThing.ResidualMovement -= movementCost; } } if (newThing.X == destinationX && newThing.Y == destinationY) { Log.Info($"{newThing.BaseDefinition.Name} {newThing.Name} has completed its movement."); newThing.CompleteMovement(); // If moving towards a site, enter the site. if (thingMovingTowards.HasValue && newThing is ICanEnterSites enterSites) { enterSites.InSiteId = thingMovingTowards; } } }
/// <summary> /// Initializes a new instance of the <see cref="BaseMovingThingPres"/> class. /// </summary> /// <param name="inner">The inner object.</param> /// <param name="world">The world.</param> public BaseMovingThingPres(BaseMovingThing inner, World world) : base(inner, world) { }