private void MapToMapMovement(IOperationContext context, ITile sourceTile, ITile destinationTile, bool isTeleport) { var requestor = this.GetRequestor(context.CreatureManager); IThing thingMoving = this.ThingMovingId == CreatureConstants.CreatureTypeId ? sourceTile.TopCreature : sourceTile.TopItem; // Declare some pre-conditions. var sourceTileIsNull = sourceTile == null; var destinationHasGround = destinationTile?.Ground != null; var thingExists = thingMoving != null; var locationsMatch = thingMoving?.Location == this.FromLocation; var isIntendedThing = this.ThingMovingId != CreatureConstants.CreatureTypeId ? thingMoving?.TypeId == this.ThingMovingId : (thingMoving as ICreature)?.Id == this.FromCreatureId; var requestorInRange = requestor == null || (requestor.Location - this.FromLocation).MaxValueIn2D <= 1; var canThrowBetweenLocations = isTeleport || requestor == null || context.Map.CanThrowBetweenLocations(this.FromLocation, this.ToLocation, checkLineOfSight: true); if (sourceTileIsNull || !locationsMatch || !isIntendedThing) { // Silent fail. return; } else if (!thingExists) { this.DispatchTextNotification(context, OperationMessage.MayNotMoveThis); } else if (!destinationHasGround || !canThrowBetweenLocations) { this.DispatchTextNotification(context, OperationMessage.MayNotThrowThere); } else if (thingMoving is ICreature creature) { var distanceBetweenLocations = this.FromLocation - this.ToLocation; // More pre-conditions. var creatureCanBeMoved = (thingMoving == requestor && requestor.CanWalk) || requestor.CanPush(creature); var canThrowThatFar = isTeleport || requestor == null || (distanceBetweenLocations.MaxValueIn2D <= 1 && distanceBetweenLocations.Z == 0); var creatureAvoidsDestination = !isTeleport && requestor != null && requestor != creature && destinationTile.IsPathBlocking(/*this.Requestor.DamageTypesToAvoid*/); var destinationIsObstructed = !isTeleport && distanceBetweenLocations.Z == 0 && (destinationTile.BlocksLay || destinationTile.BlocksPass); if (creatureAvoidsDestination) { this.DispatchTextNotification(context, OperationMessage.NotEnoughRoom); } else if (destinationIsObstructed) { if (requestor != null && creature.Id == this.RequestorId && creature is IPlayer player) { this.SendNotification(context, new PlayerCancelWalkNotification(player)); } this.DispatchTextNotification(context, OperationMessage.NotEnoughRoom); } else if (!requestorInRange) { this.DispatchTextNotification(context, OperationMessage.TooFarAway); } else if (!canThrowThatFar) { this.DispatchTextNotification(context, OperationMessage.DestinationTooFarAway); } else if (!this.PerformCreatureMovement(context, creature, this.ToLocation, isTeleport, requestorCreature: requestor)) { // Something else went wrong. this.DispatchTextNotification(context); } else if (requestor is IPlayer player && this.ToLocation != player.Location && player != thingMoving) { var directionToDestination = player.Location.DirectionTo(this.ToLocation); context.Scheduler.ScheduleEvent(new TurnToDirectionOperation(player, directionToDestination)); } } else if (thingMoving is IItem item) { var distanceBetweenLocations = (requestor?.Location ?? this.FromLocation) - this.ToLocation; var distanceFromSource = (requestor?.Location ?? this.FromLocation) - this.FromLocation; // More pre-conditions. var itemCanBeMoved = item.CanBeMoved; var sourceTileHasEnoughItemAmount = this.ThingMovingId == item.TypeId && item.Amount >= this.Amount; var destinationIsObstructed = destinationTile.BlocksLay || (item.BlocksPass && destinationTile.BlocksPass); var movementInRange = requestor == null || (distanceFromSource.MaxValueIn2D <= 1 && distanceFromSource.Z == 0 && (!item.Type.HasItemFlag(ItemFlag.BlocksWalk) || (distanceBetweenLocations.MaxValueIn2D <= 2 && distanceBetweenLocations.Z == 0))); if (!itemCanBeMoved) { this.DispatchTextNotification(context, OperationMessage.MayNotMoveThis); } else if (destinationIsObstructed) { this.DispatchTextNotification(context, OperationMessage.MayNotThrowThere); } else if (!movementInRange) { this.DispatchTextNotification(context, OperationMessage.DestinationTooFarAway); } else if (!sourceTileHasEnoughItemAmount) { this.DispatchTextNotification(context, OperationMessage.NotEnoughQuantity); } else if (!this.PerformItemMovement(context, item, sourceTile, destinationTile, this.FromIndex, amountToMove: this.Amount, requestorCreature: requestor)) { // Something else went wrong. this.DispatchTextNotification(context); } else if (requestor is IPlayer player && this.ToLocation != player.Location && player != thingMoving) { var directionToDestination = player.Location.DirectionTo(this.ToLocation); context.Scheduler.ScheduleEvent(new TurnToDirectionOperation(player, directionToDestination)); } // TODO: Check if the item is an IContainerItem and, if it got moved, check if there are players that have it open that now are too far away from it. } }