static bool PickBlock(Game game, PickedPos pos) { if (!game.CanPick(t.Block)) { return(false); } // This cell falls on the path of the ray. Now perform an additional bounding box test, // since some blocks do not occupy a whole cell. float t0, t1; if (!Intersection.RayIntersectsBox(t.Origin, t.Dir, t.Min, t.Max, out t0, out t1)) { return(false); } Vector3 I = t.Origin + t.Dir * t0; // Only pick the block if the block is precisely within reach distance. float lenSq = (I - t.Origin).LengthSquared; float reach = game.LocalPlayer.ReachDistance; if (lenSq <= reach * reach) { pos.SetAsValid(t.X, t.Y, t.Z, t.Min, t.Max, t.Block, I); } else { pos.SetAsInvalid(); } return(true); }
// http://www.xnawiki.com/index.php/Voxel_traversal // https://web.archive.org/web/20120113051728/http://www.xnawiki.com/index.php?title=Voxel_traversal /// <summary> Determines the picked block based on the given origin and direction vector.<br/> /// Marks pickedPos as invalid if a block could not be found due to going outside map boundaries /// or not being able to find a suitable candiate within the given reach distance. </summary> public static void CalculatePickedBlock(Game game, Vector3 origin, Vector3 dir, float reach, PickedPos pickedPos) { // Implementation based on: "A Fast Voxel Traversal Algorithm for Ray Tracing" // John Amanatides, Andrew Woo // http://www.cse.yorku.ca/~amana/research/grid.pdf // http://www.devmaster.net/articles/raytracing_series/A%20faster%20voxel%20traversal%20algorithm%20for%20ray%20tracing.pdf // The cell in which the ray starts. Vector3I start = Vector3I.Floor(origin); // Rounds the position's X, Y and Z down to the nearest integer values. int x = start.X, y = start.Y, z = start.Z; Vector3I step, cellBoundary; Vector3 tMax, tDelta; CalcVectors(origin, dir, out step, out cellBoundary, out tMax, out tDelta); Map map = game.Map; BlockInfo info = game.BlockInfo; float reachSquared = reach * reach; int iterations = 0; // For each step, determine which distance to the next voxel boundary is lowest (i.e. // which voxel boundary is nearest) and walk that way. while (iterations < 10000) { byte block = GetBlock(map, x, y, z, origin); Vector3 min = new Vector3(x, y, z) + info.MinBB[block]; Vector3 max = new Vector3(x, y, z) + info.MaxBB[block]; float dx = Math.Min(Math.Abs(origin.X - min.X), Math.Abs(origin.X - max.X)); float dy = Math.Min(Math.Abs(origin.Y - min.Y), Math.Abs(origin.Y - max.Y)); float dz = Math.Min(Math.Abs(origin.Z - min.Z), Math.Abs(origin.Z - max.Z)); if (dx * dx + dy * dy + dz * dz > reachSquared) { pickedPos.SetAsInvalid(); return; } if (game.CanPick(block)) { // This cell falls on the path of the ray. Now perform an additional bounding box test, // since some blocks do not occupy a whole cell. float t0, t1; if (Intersection.RayIntersectsBox(origin, dir, min, max, out t0, out t1)) { Vector3 intersect = origin + dir * t0; pickedPos.SetAsValid(x, y, z, min, max, block, intersect); return; } } Step(ref tMax, ref tDelta, ref step, ref x, ref y, ref z); iterations++; } throw new InvalidOperationException("did over 10000 iterations in CalculatePickedBlock(). " + "Something has gone wrong. (dir: " + dir + ")"); }
/// <summary> Determines the picked block based on the given origin and direction vector.<br/> /// Marks pickedPos as invalid if a block could not be found due to going outside map boundaries /// or not being able to find a suitable candiate within the given reach distance. </summary> public static void CalculatePickedBlock(Game game, Vector3 origin, Vector3 dir, float reach, PickedPos pickedPos) { tracer.SetRayData(origin, dir); World map = game.World; BlockInfo info = game.BlockInfo; float reachSquared = reach * reach; int iterations = 0; Vector3I pOrigin = Vector3I.Floor(origin); while (iterations < 10000) { int x = tracer.X, y = tracer.Y, z = tracer.Z; byte block = GetBlock(map, x, y, z, pOrigin); Vector3 min = new Vector3(x, y, z) + info.MinBB[block]; Vector3 max = new Vector3(x, y, z) + info.MaxBB[block]; if (info.IsLiquid[block]) { min.Y -= 1.5f / 16; max.Y -= 1.5f / 16; } float dx = Math.Min(Math.Abs(origin.X - min.X), Math.Abs(origin.X - max.X)); float dy = Math.Min(Math.Abs(origin.Y - min.Y), Math.Abs(origin.Y - max.Y)); float dz = Math.Min(Math.Abs(origin.Z - min.Z), Math.Abs(origin.Z - max.Z)); if (dx * dx + dy * dy + dz * dz > reachSquared) { pickedPos.SetAsInvalid(); return; } if (game.CanPick(block)) { // This cell falls on the path of the ray. Now perform an additional bounding box test, // since some blocks do not occupy a whole cell. float t0, t1; if (Intersection.RayIntersectsBox(origin, dir, min, max, out t0, out t1)) { Vector3 intersect = origin + dir * t0; pickedPos.SetAsValid(x, y, z, min, max, block, intersect); return; } } tracer.Step(); iterations++; } throw new InvalidOperationException("did over 10000 iterations in CalculatePickedBlock(). " + "Something has gone wrong. (dir: " + dir + ")"); }
static bool PickBlock(Game game, PickedPos pos) { if (!game.CanPick(t.Block)) { return(false); } // This cell falls on the path of the ray. Now perform an additional bounding box test, // since some blocks do not occupy a whole cell. float t0, t1; if (!Intersection.RayIntersectsBox(t.Origin, t.Dir, t.Min, t.Max, out t0, out t1)) { return(false); } Vector3 I = t.Origin + t.Dir * t0; pos.SetAsValid(t.X, t.Y, t.Z, t.Min, t.Max, t.Block, I); return(true); }
/// <summary> Determines the picked block based on the given origin and direction vector.<br/> /// Marks pickedPos as invalid if a block could not be found due to going outside map boundaries /// or not being able to find a suitable candiate within the given reach distance. </summary> public static void CalculatePickedBlock( Game game, Vector3 origin, Vector3 dir, float reach, PickedPos pickedPos ) { tracer.SetRayData( origin, dir ); World map = game.World; BlockInfo info = game.BlockInfo; float reachSquared = reach * reach; int iterations = 0; Vector3I pOrigin = Vector3I.Floor( origin ); while( iterations < 10000 ) { int x = tracer.X, y = tracer.Y, z = tracer.Z; byte block = GetBlock( map, x, y, z, pOrigin ); Vector3 min = new Vector3( x, y, z ) + info.MinBB[block]; Vector3 max = new Vector3( x, y, z ) + info.MaxBB[block]; float dx = Math.Min( Math.Abs( origin.X - min.X ), Math.Abs( origin.X - max.X ) ); float dy = Math.Min( Math.Abs( origin.Y - min.Y ), Math.Abs( origin.Y - max.Y ) ); float dz = Math.Min( Math.Abs( origin.Z - min.Z ), Math.Abs( origin.Z - max.Z ) ); if( dx * dx + dy * dy + dz * dz > reachSquared ) { pickedPos.SetAsInvalid(); return; } if( game.CanPick( block ) ) { // This cell falls on the path of the ray. Now perform an additional bounding box test, // since some blocks do not occupy a whole cell. float t0, t1; if( Intersection.RayIntersectsBox( origin, dir, min, max, out t0, out t1 ) ) { Vector3 intersect = origin + dir * t0; pickedPos.SetAsValid( x, y, z, min, max, block, intersect ); return; } } tracer.Step(); iterations++; } throw new InvalidOperationException( "did over 10000 iterations in CalculatePickedBlock(). " + "Something has gone wrong. (dir: " + dir + ")" ); }
public void PickBlocks(bool cooldown, bool left, bool middle, bool right) { DateTime now = DateTime.UtcNow; double delta = (now - lastClick).TotalMilliseconds; if (cooldown && delta < 250) { return; // 4 times per second } lastClick = now; Inventory inv = game.Inventory; if (game.Server.UsingPlayerClick && !game.Gui.ActiveScreen.HandlesAllInput) { input.pickingId = -1; input.ButtonStateChanged(MouseButton.Left, left); input.ButtonStateChanged(MouseButton.Right, right); input.ButtonStateChanged(MouseButton.Middle, middle); } if (game.Gui.ActiveScreen.HandlesAllInput || !inv.CanPick) { return; } if (left) { // always play delete animations, even if we aren't picking a block. game.HeldBlockRenderer.ClickAnim(true); Vector3I pos = game.SelectedPos.BlockPos; if (!game.SelectedPos.Valid || !game.World.IsValidPos(pos)) { return; } BlockID old = game.World.GetBlock(pos); if (BlockInfo.Draw[old] == DrawType.Gas || !BlockInfo.CanDelete[old]) { return; } game.UpdateBlock(pos.X, pos.Y, pos.Z, Block.Air); game.UserEvents.RaiseBlockChanged(pos, old, Block.Air); } else if (right) { Vector3I pos = game.SelectedPos.TranslatedPos; if (!game.SelectedPos.Valid || !game.World.IsValidPos(pos)) { return; } BlockID old = game.World.GetBlock(pos); BlockID block = inv.Selected; if (game.AutoRotate) { block = AutoRotate.RotateBlock(game, block); } if (game.CanPick(old) || !BlockInfo.CanPlace[block]) { return; } // air-ish blocks can only replace over other air-ish blocks if (BlockInfo.Draw[block] == DrawType.Gas && BlockInfo.Draw[old] != DrawType.Gas) { return; } if (!PickingHandler.CheckIsFree(game, block)) { return; } game.UpdateBlock(pos.X, pos.Y, pos.Z, block); game.UserEvents.RaiseBlockChanged(pos, old, block); } else if (middle) { Vector3I pos = game.SelectedPos.BlockPos; if (!game.SelectedPos.Valid || !game.World.IsValidPos(pos)) { return; } BlockID cur = game.World.GetBlock(pos); if (BlockInfo.Draw[cur] == DrawType.Gas) { return; } if (!(BlockInfo.CanPlace[cur] || BlockInfo.CanDelete[cur])) { return; } if (!inv.CanChangeSelected() || inv.Selected == cur) { return; } // Is the currently selected block an empty slot if (inv[inv.SelectedIndex] == Block.Air) { inv.Selected = cur; return; } // Try to replace same block for (int i = 0; i < Inventory.BlocksPerRow; i++) { if (inv[i] != cur) { continue; } inv.SelectedIndex = i; return; } // Try to replace empty slots for (int i = 0; i < Inventory.BlocksPerRow; i++) { if (inv[i] != Block.Air) { continue; } inv[i] = cur; inv.SelectedIndex = i; return; } // Finally, replace the currently selected block. inv.Selected = cur; } }
public void PickBlocks(bool cooldown, bool left, bool middle, bool right) { DateTime now = DateTime.UtcNow; double delta = (now - lastClick).TotalMilliseconds; if (cooldown && delta < 250) { return; // 4 times per second } lastClick = now; Inventory inv = game.Inventory; if (game.Network.UsingPlayerClick && !game.ScreenLockedInput) { byte targetId = game.Players.GetClosetPlayer(game.LocalPlayer); ButtonStateChanged(MouseButton.Left, left, targetId); ButtonStateChanged(MouseButton.Right, right, targetId); ButtonStateChanged(MouseButton.Middle, middle, targetId); } int buttonsDown = (left ? 1 : 0) + (right ? 1 : 0) + (middle ? 1 : 0); if (buttonsDown > 1 || game.ScreenLockedInput || inv.HeldBlock == Block.Air) { return; } // always play delete animations, even if we aren't picking a block. if (left) { game.BlockHandRenderer.SetAnimationClick(true); } if (!game.SelectedPos.Valid) { return; } if (middle) { Vector3I pos = game.SelectedPos.BlockPos; byte block = 0; if (game.Map.IsValidPos(pos) && (block = game.Map.GetBlock(pos)) != 0 && (inv.CanPlace[block] || inv.CanDelete[block])) { for (int i = 0; i < inv.Hotbar.Length; i++) { if (inv.Hotbar[i] == (Block)block) { inv.HeldBlockIndex = i; return; } } inv.HeldBlock = (Block)block; } } else if (left) { Vector3I pos = game.SelectedPos.BlockPos; byte block = 0; if (game.Map.IsValidPos(pos) && (block = game.Map.GetBlock(pos)) != 0 && inv.CanDelete[block]) { game.ParticleManager.BreakBlockEffect(pos, block); game.AudioPlayer.PlayDigSound(game.BlockInfo.DigSounds[block]); game.UpdateBlock(pos.X, pos.Y, pos.Z, 0); game.Network.SendSetBlock(pos.X, pos.Y, pos.Z, false, (byte)inv.HeldBlock); } } else if (right) { Vector3I pos = game.SelectedPos.TranslatedPos; if (!game.Map.IsValidPos(pos)) { return; } byte block = (byte)inv.HeldBlock; if (!game.CanPick(game.Map.GetBlock(pos)) && inv.CanPlace[block] && CheckIsFree(game.SelectedPos, block)) { game.UpdateBlock(pos.X, pos.Y, pos.Z, block); game.Network.SendSetBlock(pos.X, pos.Y, pos.Z, true, block); game.BlockHandRenderer.SetAnimationClick(false); } } }
public void PickBlocks(bool cooldown, bool left, bool middle, bool right) { DateTime now = DateTime.UtcNow; double delta = (now - lastClick).TotalMilliseconds; if (cooldown && delta < 250) { return; // 4 times per second } lastClick = now; Inventory inv = game.Inventory; if (game.Server.UsingPlayerClick && !game.Gui.ActiveScreen.HandlesAllInput) { input.pickingId = -1; input.ButtonStateChanged(MouseButton.Left, left); input.ButtonStateChanged(MouseButton.Right, right); input.ButtonStateChanged(MouseButton.Middle, middle); } if (game.Gui.ActiveScreen.HandlesAllInput || !inv.CanPick) { return; } if (left) { if (game.Mode.PickingLeft()) { return; } Vector3I pos = game.SelectedPos.BlockPos; if (!game.SelectedPos.Valid || !game.World.IsValidPos(pos)) { return; } BlockID old = game.World.GetBlock(pos); if (BlockInfo.Draw[old] == DrawType.Gas || !BlockInfo.CanDelete[old]) { return; } game.Mode.PickLeft(old); } else if (right) { if (game.Mode.PickingRight()) { return; } Vector3I pos = game.SelectedPos.TranslatedPos; if (!game.SelectedPos.Valid || !game.World.IsValidPos(pos)) { return; } BlockID old = game.World.GetBlock(pos); BlockID block = inv.Selected; if (game.AutoRotate) { block = AutoRotate.RotateBlock(game, block); } if (game.CanPick(old) || !BlockInfo.CanPlace[block]) { return; } // air-ish blocks can only replace over other air-ish blocks if (BlockInfo.Draw[block] == DrawType.Gas && BlockInfo.Draw[old] != DrawType.Gas) { return; } if (!PickingHandler.CheckIsFree(game, block)) { return; } game.Mode.PickRight(old, block); } else if (middle) { Vector3I pos = game.SelectedPos.BlockPos; if (!game.SelectedPos.Valid || !game.World.IsValidPos(pos)) { return; } BlockID old = game.World.GetBlock(pos); game.Mode.PickMiddle(old); } }
public void PickBlocks(bool cooldown, bool left, bool middle, bool right) { DateTime now = DateTime.UtcNow; double delta = (now - lastClick).TotalMilliseconds; if (cooldown && delta < 250) { return; // 4 times per second } lastClick = now; Inventory inv = game.Inventory; if (game.Server.UsingPlayerClick && !game.Gui.ActiveScreen.HandlesAllInput) { byte targetId = game.Entities.GetClosetPlayer(game.LocalPlayer); input.ButtonStateChanged(MouseButton.Left, left, targetId); input.ButtonStateChanged(MouseButton.Right, right, targetId); input.ButtonStateChanged(MouseButton.Middle, middle, targetId); } int buttonsDown = (left ? 1 : 0) + (right ? 1 : 0) + (middle ? 1 : 0); if (buttonsDown > 1 || game.Gui.ActiveScreen.HandlesAllInput || inv.HeldBlock == Block.Air) { return; } // always play delete animations, even if we aren't picking a block. if (left) { game.HeldBlockRenderer.anim.SetClickAnim(true); } if (!game.SelectedPos.Valid) { return; } BlockInfo info = game.BlockInfo; if (middle) { Vector3I pos = game.SelectedPos.BlockPos; if (!game.World.IsValidPos(pos)) { return; } byte old = game.World.GetBlock(pos); if (!info.IsAir[old] && (inv.CanPlace[old] || inv.CanDelete[old])) { for (int i = 0; i < inv.Hotbar.Length; i++) { if (inv.Hotbar[i] == old) { inv.HeldBlockIndex = i; return; } } inv.HeldBlock = old; } } else if (left) { Vector3I pos = game.SelectedPos.BlockPos; if (!game.World.IsValidPos(pos)) { return; } byte old = game.World.GetBlock(pos); if (!info.IsAir[old] && inv.CanDelete[old]) { game.UpdateBlock(pos.X, pos.Y, pos.Z, 0); game.UserEvents.RaiseBlockChanged(pos, old, 0); } } else if (right) { Vector3I pos = game.SelectedPos.TranslatedPos; if (!game.World.IsValidPos(pos)) { return; } byte old = game.World.GetBlock(pos); byte block = (byte)inv.HeldBlock; if (!game.CanPick(old) && inv.CanPlace[block] && CheckIsFree(game.SelectedPos, block)) { game.UpdateBlock(pos.X, pos.Y, pos.Z, block); game.UserEvents.RaiseBlockChanged(pos, old, block); } } }
// http://www.xnawiki.com/index.php/Voxel_traversal public static void GetPickedBlockPos( Game window, Vector3 origin, Vector3 dir, float reach, PickedPos pickedPos ) { // Implementation is based on: // "A Fast Voxel Traversal Algorithm for Ray Tracing" // John Amanatides, Andrew Woo // http://www.cse.yorku.ca/~amana/research/grid.pdf // http://www.devmaster.net/articles/raytracing_series/A%20faster%20voxel%20traversal%20algorithm%20for%20ray%20tracing.pdf // NOTES: // * This code assumes that the ray's position and direction are in 'cell coordinates', which means // that one unit equals one cell in all directions. // * When the ray doesn't start within the voxel grid, calculate the first position at which the // ray could enter the grid. If it never enters the grid, there is nothing more to do here. // * Also, it is important to test when the ray exits the voxel grid when the grid isn't infinite. // * The Point3D structure is a simple structure having three integer fields (X, Y and Z). // The cell in which the ray starts. Vector3I start = Vector3I.Floor( origin ); // Rounds the position's X, Y and Z down to the nearest integer values. int x = start.X; int y = start.Y; int z = start.Z; // Determine which way we go. int stepX = Math.Sign( dir.X ); int stepY = Math.Sign( dir.Y ); int stepZ = Math.Sign( dir.Z ); // Calculate cell boundaries. When the step (i.e. direction sign) is positive, // the next boundary is AFTER our current position, meaning that we have to add 1. // Otherwise, it is BEFORE our current position, in which case we add nothing. Vector3I cellBoundary = new Vector3I( x + ( stepX > 0 ? 1 : 0 ), y + ( stepY > 0 ? 1 : 0 ), z + ( stepZ > 0 ? 1 : 0 ) ); // NOTE: For the following calculations, the result will be Single.PositiveInfinity // when ray.Direction.X, Y or Z equals zero, which is OK. However, when the left-hand // value of the division also equals zero, the result is Single.NaN, which is not OK. // Determine how far we can travel along the ray before we hit a voxel boundary. Vector3 tMax = new Vector3( ( cellBoundary.X - origin.X ) / dir.X, // Boundary is a plane on the YZ axis. ( cellBoundary.Y - origin.Y ) / dir.Y, // Boundary is a plane on the XZ axis. ( cellBoundary.Z - origin.Z ) / dir.Z ); // Boundary is a plane on the XY axis. if( Single.IsNaN( tMax.X ) || Single.IsInfinity( tMax.X ) ) tMax.X = Single.PositiveInfinity; if( Single.IsNaN( tMax.Y ) || Single.IsInfinity( tMax.Y ) ) tMax.Y = Single.PositiveInfinity; if( Single.IsNaN( tMax.Z ) || Single.IsInfinity( tMax.Z ) ) tMax.Z = Single.PositiveInfinity; // Determine how far we must travel along the ray before we have crossed a gridcell. Vector3 tDelta = new Vector3( stepX / dir.X, // Crossing the width of a cell. stepY / dir.Y, // Crossing the height of a cell. stepZ / dir.Z ); // Crossing the depth of a cell. if( Single.IsNaN( tDelta.X ) ) tDelta.X = Single.PositiveInfinity; if( Single.IsNaN( tDelta.Y ) ) tDelta.Y = Single.PositiveInfinity; if( Single.IsNaN( tDelta.Z ) ) tDelta.Z = Single.PositiveInfinity; Map map = window.Map; BlockInfo info = window.BlockInfo; float reachSquared = reach * reach; int iterations = 0; // For each step, determine which distance to the next voxel boundary is lowest (i.e. // which voxel boundary is nearest) and walk that way. while( iterations < 10000 ) { byte block = GetBlock( map, x, y, z, origin ); Vector3 min = new Vector3( x, y, z ) + info.MinBB[block]; Vector3 max = new Vector3( x, y, z ) + info.MaxBB[block]; float dx = Math.Min( Math.Abs( origin.X - min.X ), Math.Abs( origin.X - max.X ) ); float dy = Math.Min( Math.Abs( origin.Y - min.Y ), Math.Abs( origin.Y - max.Y ) ); float dz = Math.Min( Math.Abs( origin.Z - min.Z ), Math.Abs( origin.Z - max.Z ) ); if( dx * dx + dy * dy + dz * dz > reachSquared ) { pickedPos.SetAsInvalid(); return; } if( window.CanPick( block ) ) { // This cell falls on the path of the ray. Now perform an additional bounding box test, // since some blocks do not occupy a whole cell. float t0, t1; if( Intersection.RayIntersectsBox( origin, dir, min, max, out t0, out t1 ) ) { Vector3 intersect = origin + dir * t0; pickedPos.SetAsValid( min, max, block, intersect ); return; } } if( tMax.X < tMax.Y && tMax.X < tMax.Z ) { // tMax.X is the lowest, an YZ cell boundary plane is nearest. x += stepX; tMax.X += tDelta.X; } else if( tMax.Y < tMax.Z ) { // tMax.Y is the lowest, an XZ cell boundary plane is nearest. y += stepY; tMax.Y += tDelta.Y; } else { // tMax.Z is the lowest, an XY cell boundary plane is nearest. z += stepZ; tMax.Z += tDelta.Z; } iterations++; } throw new InvalidOperationException( "did over 10000 iterations in GetPickedBlockPos(). " + "Something has gone wrong. (dir: " + dir + ")" ); }
public void PickBlocks(bool cooldown, bool left, bool middle, bool right) { DateTime now = DateTime.UtcNow; double delta = (now - lastClick).TotalMilliseconds; if (cooldown && delta < 250) { return; // 4 times per second } lastClick = now; Inventory inv = game.Inventory; if (game.Server.UsingPlayerClick && !game.Gui.ActiveScreen.HandlesAllInput) { input.pickingId = -1; input.ButtonStateChanged(MouseButton.Left, left); input.ButtonStateChanged(MouseButton.Right, right); input.ButtonStateChanged(MouseButton.Middle, middle); } int btns = (left ? 1 : 0) + (right ? 1 : 0) + (middle ? 1 : 0); if (btns > 1 || game.Gui.ActiveScreen.HandlesAllInput || inv.Selected == Block.Invalid) { return; } // always play delete animations, even if we aren't picking a block. if (left) { game.HeldBlockRenderer.anim.SetClickAnim(true); byte id = game.Entities.GetClosetPlayer(game.LocalPlayer); if (id != EntityList.SelfID && game.Mode.PickEntity(id)) { return; } } if (!game.SelectedPos.Valid) { return; } if (middle) { Vector3I pos = game.SelectedPos.BlockPos; if (!game.World.IsValidPos(pos)) { return; } BlockID old = game.World.GetBlock(pos); game.Mode.PickMiddle(old); } else if (left) { Vector3I pos = game.SelectedPos.BlockPos; if (!game.World.IsValidPos(pos)) { return; } BlockID old = game.World.GetBlock(pos); if (game.BlockInfo.Draw[old] == DrawType.Gas || !inv.CanDelete[old]) { return; } game.Mode.PickLeft(old); } else if (right) { Vector3I pos = game.SelectedPos.TranslatedPos; if (!game.World.IsValidPos(pos)) { return; } BlockID old = game.World.GetBlock(pos); BlockID block = inv.Selected; block = AutoRotate.RotateBlock(game, block); if (game.CanPick(old)) { return; } if (!inv.CanPlace[block] && game.SelectedPos.Block != Block.Chest1 && game.SelectedPos.Block != Block.Chest2 && game.SelectedPos.Block != Block.Chest3 && game.SelectedPos.Block != Block.Chest4) { return; } if (!PickingHandler.CheckIsFree(game, block)) { return; } game.Mode.PickRight(old, block); } }