Exemple #1
0
        internal static bool IsValidStaticItemPosition(Position position)         //cannot accept position by ref here
        {
            if (!IsValidBlockLocation(position.X, position.Y, position.Z))
            {
                Game.UiHost.AddChatMessage(new ChatMessage(ChatMessageType.Error, string.Format("Invalid item position.")));
                return(false);
            }
            var chunk = Chunks[position];

            if (chunk.LightSources.Any(lightSource => position.IsOnBlock(ref lightSource.Value.Coords)) || chunk.Clutters.Any(clutter => position.IsOnBlock(ref clutter.Coords)))
            {
                Game.UiHost.AddChatMessage(new ChatMessage(ChatMessageType.Error, string.Format("Item already exists on selected block.")));
                return(false);
            }
            return(true);
        }
Exemple #2
0
        /// <summary>Place a single block in the world. This will mark the block as dirty.</summary>
        /// <param name="position">position to place the block at</param>
        /// <param name="type">type of block to place</param>
        /// <param name="isMultipleBlockPlacement">Use this when placing multiple blocks at once so lighting and chunk queueing only happens once.</param>
        internal static void PlaceBlock(Position position, Block.BlockType type, bool isMultipleBlockPlacement = false)
        {
            if (!position.IsValidBlockLocation || position.Y <= 0)
            {
                return;
            }

            //this was a multiple block placement, prevent placing blocks on yourself and getting stuck; used to be able to place cuboids on yourself and get stuck
            //only check in single player for now because in multiplayer this could allow the blocks on different clients to get out of sync and placements of multiple blocks in multiplayer will be rare
            if (Config.IsSinglePlayer && isMultipleBlockPlacement && (position.IsOnBlock(ref Game.Player.Coords) || position == Game.Player.CoordsHead.ToPosition()))
            {
                return;
            }

            if (type == Block.BlockType.Air)
            {
                //if destroying a block under water, fill with water instead of air
                if (position.Y + 1 < Chunk.CHUNK_HEIGHT && GetBlock(position.X, position.Y + 1, position.Z).Type == Block.BlockType.Water)
                {
                    type = Block.BlockType.Water;
                }
            }

            var chunk   = Chunks[position];
            var block   = position.GetBlock();
            var oldType = block.Type;

            block.Type = type;             //assign the new type
            var isTransparentBlock    = Block.IsBlockTypeTransparent(type);
            var isTransparentOldBlock = Block.IsBlockTypeTransparent(oldType);

            block.BlockData        = (ushort)(block.BlockData | 0x8000); //mark the block as dirty for the save file "diff" logic
            chunk.Blocks[position] = block;                              //insert the new block
            chunk.UpdateHeightMap(ref block, position.X % Chunk.CHUNK_SIZE, position.Y, position.Z % Chunk.CHUNK_SIZE);

            if (!isTransparentBlock || type == Block.BlockType.Water)
            {
                var below = position;
                below.Y--;
                if (below.Y > 0)
                {
                    if (below.GetBlock().Type == Block.BlockType.Grass || below.GetBlock().Type == Block.BlockType.Snow)
                    {
                        PlaceBlock(below, Block.BlockType.Dirt, true);                         //dont queue with this dirt block change, the source block changing takes care of it, prevents double queueing the chunk and playing sound twice
                    }
                }
            }

            if (!chunk.WaterExpanding)             //water is not expanding, check if this change should make it start
            {
                switch (type)
                {
                case Block.BlockType.Water:
                    chunk.WaterExpanding = true;
                    break;

                case Block.BlockType.Air:
                    for (var q = 0; q < 5; q++)
                    {
                        Position adjacent;
                        switch (q)
                        {
                        case 0:
                            adjacent = new Position(position.X + 1, position.Y, position.Z);
                            break;

                        case 1:
                            adjacent = new Position(position.X - 1, position.Y, position.Z);
                            break;

                        case 2:
                            adjacent = new Position(position.X, position.Y + 1, position.Z);
                            break;

                        case 3:
                            adjacent = new Position(position.X, position.Y, position.Z + 1);
                            break;

                        default:
                            adjacent = new Position(position.X, position.Y, position.Z - 1);
                            break;
                        }
                        if (adjacent.IsValidBlockLocation && adjacent.GetBlock().Type == Block.BlockType.Water)
                        {
                            Chunks[adjacent].WaterExpanding = true;
                        }
                    }
                    break;
                }
            }

            //its easier to just set .GrassGrowing on all affected chunks to true, and then let that logic figure it out and turn it off, this way the logic is contained in one spot
            //and also the logic here doesnt need to run every time a block gets placed. ie: someone is building a house, its running through this logic for every block placement;
            //now it can only check once on the grass grow interval and turn it back off
            //gm: an additional optimization, grass could never start growing unless this is an air block and its replacing a non transparent block
            //OR this is a non transparent block filling in a previously transparent block to cause grass to die
            if (!isTransparentBlock || (type == Block.BlockType.Air && !isTransparentOldBlock))
            {
                chunk.GrassGrowing = true;
                if (position.IsOnChunkBorder)
                {
                    foreach (var adjacentChunk in position.BorderChunks)
                    {
                        adjacentChunk.GrassGrowing = true;
                    }
                }
            }

            //determine if any static game items need to be removed as a result of this block placement
            if (type != Block.BlockType.Air)
            {
                lock (chunk.Clutters)                 //lock because clutter is stored in a HashSet
                {
                    //if theres clutter on this block then destroy it to place the block (FirstOrDefault returns null if no match is found)
                    var clutterToRemove = chunk.Clutters.FirstOrDefault(clutter => position.IsOnBlock(ref clutter.Coords));
                    if (clutterToRemove != null)
                    {
                        chunk.Clutters.Remove(clutterToRemove);
                    }
                }

                var lightSourceToRemove = chunk.LightSources.FirstOrDefault(lightSource => position.IsOnBlock(ref lightSource.Value.Coords));
                if (lightSourceToRemove.Value != null)
                {
                    LightSource temp;
                    chunk.LightSources.TryRemove(lightSourceToRemove.Key, out temp);
                }
            }
            else                      //destroying block
            {
                lock (chunk.Clutters) //lock because clutter is stored in a HashSet
                {
                    //if theres clutter on top of this block then remove it as well (FirstOrDefault returns null if no match is found)
                    var clutterToRemove = chunk.Clutters.FirstOrDefault(clutter => clutter.Coords.Xblock == position.X && clutter.Coords.Yblock == position.Y + 1 && clutter.Coords.Zblock == position.Z);                     //add one to Y to look on the block above
                    if (clutterToRemove != null)
                    {
                        chunk.Clutters.Remove(clutterToRemove);
                    }
                }

                //look on ALL 6 adjacent blocks for static items, and those only get destroyed if its on the matching opposite attached to face
                var adjacentPositions = position.AdjacentPositionFaces;
                foreach (var tuple in adjacentPositions)
                {
                    var adjBlock = tuple.Item1.GetBlock();
                    if (adjBlock.Type != Block.BlockType.Air)
                    {
                        continue;                                                                                                  //position cannot contain an item if the block is not air
                    }
                    var adjChunk            = tuple.Item2 == Face.Top || tuple.Item2 == Face.Bottom ? chunk : Chunks[tuple.Item1]; //get the chunk in case the adjacent position crosses a chunk boundary
                    var lightSourceToRemove = adjChunk.LightSources.FirstOrDefault(lightSource => tuple.Item1.IsOnBlock(ref lightSource.Value.Coords));
                    if (lightSourceToRemove.Value != null && lightSourceToRemove.Value.AttachedToFace == tuple.Item2.ToOpposite()) //remove the light source
                    {
                        LightSource temp;
                        chunk.LightSources.TryRemove(lightSourceToRemove.Key, out temp);
                    }
                }

                //if theres a dynamic item on top of this block then let it fall
                foreach (var item in chunk.GameItems.Values)
                {
                    if (!item.IsMoving && item.Coords.Xblock == position.X && item.Coords.Yblock == position.Y + 1 && item.Coords.Zblock == position.Z)
                    {
                        item.IsMoving = true;
                    }
                }
            }

            if (!Config.IsServer && !isMultipleBlockPlacement)
            {
                Debug.WriteLineIf(type == Block.BlockType.Ice && oldType == Block.BlockType.Water, "Growth change Water->Ice; Multiple lightbox updates and chunk queues are possible");
                ModifyLightAndQueueChunksForBlockChange(position, isTransparentOldBlock != isTransparentBlock, type);

                //sounds dont play for multi/cuboid placements; they are responsible for their own sounds; prevents sound spam
                switch (type)
                {
                case Block.BlockType.Air:
                    switch (oldType)
                    {
                    case Block.BlockType.Water:                                     //remove water
                        Sounds.Audio.PlaySound(Sounds.SoundType.JumpOutOfWater, ref position);
                        break;

                    default:                                     //remove another type
                        Sounds.Audio.PlaySound(Sounds.SoundType.RemoveBlock, ref position);
                        break;
                    }
                    break;

                case Block.BlockType.Water:                         //place water
                    Sounds.Audio.PlaySound(Sounds.SoundType.JumpOutOfWater, ref position);
                    break;

                default:
                    //only play the add block sound if the old type was air; the only way blocks can change otherwise are the auto changes in chunks for grass/snow/dirt/etc.
                    if (oldType == Block.BlockType.Air)
                    {
                        Sounds.Audio.PlaySound(Sounds.SoundType.AddBlock, ref position);
                    }
                    break;
                }
            }
        }
Exemple #3
0
 //cannot accept position by ref here
 internal static bool IsValidStaticItemPosition(Position position)
 {
     if (!IsValidBlockLocation(position.X, position.Y, position.Z))
     {
         Game.UiHost.AddChatMessage(new ChatMessage(ChatMessageType.Error, string.Format("Invalid item position.")));
         return false;
     }
     var chunk = Chunks[position];
     if (chunk.LightSources.Any(lightSource => position.IsOnBlock(ref lightSource.Value.Coords)) || chunk.Clutters.Any(clutter => position.IsOnBlock(ref clutter.Coords)))
     {
         Game.UiHost.AddChatMessage(new ChatMessage(ChatMessageType.Error, string.Format("Item already exists on selected block.")));
         return false;
     }
     return true;
 }
Exemple #4
0
        /// <summary>Place a single block in the world. This will mark the block as dirty.</summary>
        /// <param name="position">position to place the block at</param>
        /// <param name="type">type of block to place</param>
        /// <param name="isMultipleBlockPlacement">Use this when placing multiple blocks at once so lighting and chunk queueing only happens once.</param>
        internal static void PlaceBlock(Position position, Block.BlockType type, bool isMultipleBlockPlacement = false)
        {
            if (!position.IsValidBlockLocation || position.Y <= 0) return;

            //this was a multiple block placement, prevent placing blocks on yourself and getting stuck; used to be able to place cuboids on yourself and get stuck
            //only check in single player for now because in multiplayer this could allow the blocks on different clients to get out of sync and placements of multiple blocks in multiplayer will be rare
            if (Config.IsSinglePlayer && isMultipleBlockPlacement && (position.IsOnBlock(ref Game.Player.Coords) || position == Game.Player.CoordsHead.ToPosition())) return;

            if (type == Block.BlockType.Air)
            {
                //if destroying a block under water, fill with water instead of air
                if (position.Y + 1 < Chunk.CHUNK_HEIGHT && GetBlock(position.X, position.Y + 1, position.Z).Type == Block.BlockType.Water) type = Block.BlockType.Water;
            }

            var chunk = Chunks[position];
            var block = position.GetBlock();
            var oldType = block.Type;
            block.Type = type; //assign the new type
            var isTransparentBlock = Block.IsBlockTypeTransparent(type);
            var isTransparentOldBlock = Block.IsBlockTypeTransparent(oldType);
            block.BlockData = (ushort)(block.BlockData | 0x8000); //mark the block as dirty for the save file "diff" logic
            chunk.Blocks[position] = block; //insert the new block
            chunk.UpdateHeightMap(ref block, position.X % Chunk.CHUNK_SIZE, position.Y, position.Z % Chunk.CHUNK_SIZE);

            if (!isTransparentBlock || type == Block.BlockType.Water)
            {
                var below = position;
                below.Y--;
                if (below.Y > 0)
                {
                    if (below.GetBlock().Type == Block.BlockType.Grass || below.GetBlock().Type == Block.BlockType.Snow)
                    {
                        PlaceBlock(below, Block.BlockType.Dirt, true); //dont queue with this dirt block change, the source block changing takes care of it, prevents double queueing the chunk and playing sound twice
                    }
                }
            }

            if (!chunk.WaterExpanding) //water is not expanding, check if this change should make it start
            {
                switch (type)
                {
                    case Block.BlockType.Water:
                        chunk.WaterExpanding = true;
                        break;
                    case Block.BlockType.Air:
                        for (var q = 0; q < 5; q++)
                        {
                            Position adjacent;
                            switch (q)
                            {
                                case 0:
                                    adjacent = new Position(position.X + 1, position.Y, position.Z);
                                    break;
                                case 1:
                                    adjacent = new Position(position.X - 1, position.Y, position.Z);
                                    break;
                                case 2:
                                    adjacent = new Position(position.X, position.Y + 1, position.Z);
                                    break;
                                case 3:
                                    adjacent = new Position(position.X, position.Y, position.Z + 1);
                                    break;
                                default:
                                    adjacent = new Position(position.X, position.Y, position.Z - 1);
                                    break;
                            }
                            if (adjacent.IsValidBlockLocation && adjacent.GetBlock().Type == Block.BlockType.Water)
                            {
                                Chunks[adjacent].WaterExpanding = true;
                            }
                        }
                        break;
                }
            }

            //its easier to just set .GrassGrowing on all affected chunks to true, and then let that logic figure it out and turn it off, this way the logic is contained in one spot
            //and also the logic here doesnt need to run every time a block gets placed. ie: someone is building a house, its running through this logic for every block placement;
            //now it can only check once on the grass grow interval and turn it back off
            //gm: an additional optimization, grass could never start growing unless this is an air block and its replacing a non transparent block
            //OR this is a non transparent block filling in a previously transparent block to cause grass to die
            if (!isTransparentBlock || (type == Block.BlockType.Air && !isTransparentOldBlock))
            {
                chunk.GrassGrowing = true;
                if (position.IsOnChunkBorder)
                {
                    foreach (var adjacentChunk in position.BorderChunks) adjacentChunk.GrassGrowing = true;
                }
            }

            //determine if any static game items need to be removed as a result of this block placement
            if (type != Block.BlockType.Air)
            {
                lock (chunk.Clutters) //lock because clutter is stored in a HashSet
                {
                    //if theres clutter on this block then destroy it to place the block (FirstOrDefault returns null if no match is found)
                    var clutterToRemove = chunk.Clutters.FirstOrDefault(clutter => position.IsOnBlock(ref clutter.Coords));
                    if (clutterToRemove != null) chunk.Clutters.Remove(clutterToRemove);
                }

                var lightSourceToRemove = chunk.LightSources.FirstOrDefault(lightSource => position.IsOnBlock(ref lightSource.Value.Coords));
                if (lightSourceToRemove.Value != null)
                {
                    LightSource temp;
                    chunk.LightSources.TryRemove(lightSourceToRemove.Key, out temp);
                }
            }
            else //destroying block
            {
                lock (chunk.Clutters) //lock because clutter is stored in a HashSet
                {
                    //if theres clutter on top of this block then remove it as well (FirstOrDefault returns null if no match is found)
                    var clutterToRemove = chunk.Clutters.FirstOrDefault(clutter => clutter.Coords.Xblock == position.X && clutter.Coords.Yblock == position.Y + 1 && clutter.Coords.Zblock == position.Z); //add one to Y to look on the block above
                    if (clutterToRemove != null) chunk.Clutters.Remove(clutterToRemove);
                }

                //look on ALL 6 adjacent blocks for static items, and those only get destroyed if its on the matching opposite attached to face
                var adjacentPositions = position.AdjacentPositionFaces;
                foreach (var tuple in adjacentPositions)
                {
                    var adjBlock = tuple.Item1.GetBlock();
                    if (adjBlock.Type != Block.BlockType.Air) continue; //position cannot contain an item if the block is not air
                    var adjChunk = tuple.Item2 == Face.Top || tuple.Item2 == Face.Bottom ? chunk : Chunks[tuple.Item1]; //get the chunk in case the adjacent position crosses a chunk boundary
                    var lightSourceToRemove = adjChunk.LightSources.FirstOrDefault(lightSource => tuple.Item1.IsOnBlock(ref lightSource.Value.Coords));
                    if (lightSourceToRemove.Value != null && lightSourceToRemove.Value.AttachedToFace == tuple.Item2.ToOpposite()) //remove the light source
                    {
                        LightSource temp;
                        chunk.LightSources.TryRemove(lightSourceToRemove.Key, out temp);
                    }
                }

                //if theres a dynamic item on top of this block then let it fall
                foreach (var item in chunk.GameItems.Values)
                {
                    if (!item.IsMoving && item.Coords.Xblock == position.X && item.Coords.Yblock == position.Y + 1 && item.Coords.Zblock == position.Z)
                    {
                        item.IsMoving = true;
                    }
                }
            }

            if (!Config.IsServer && !isMultipleBlockPlacement)
            {
                Debug.WriteLineIf(type == Block.BlockType.Ice && oldType == Block.BlockType.Water, "Growth change Water->Ice; Multiple lightbox updates and chunk queues are possible");
                ModifyLightAndQueueChunksForBlockChange(position, isTransparentOldBlock != isTransparentBlock, type);

                //sounds dont play for multi/cuboid placements; they are responsible for their own sounds; prevents sound spam
                switch (type)
                {
                    case Block.BlockType.Air:
                        switch (oldType)
                        {
                            case Block.BlockType.Water: //remove water
                                Sounds.Audio.PlaySound(Sounds.SoundType.JumpOutOfWater, ref position);
                                break;
                            default: //remove another type
                                Sounds.Audio.PlaySound(Sounds.SoundType.RemoveBlock, ref position);
                                break;
                        }
                        break;
                    case Block.BlockType.Water: //place water
                        Sounds.Audio.PlaySound(Sounds.SoundType.JumpOutOfWater, ref position);
                        break;
                    default:
                        //only play the add block sound if the old type was air; the only way blocks can change otherwise are the auto changes in chunks for grass/snow/dirt/etc.
                        if (oldType == Block.BlockType.Air) Sounds.Audio.PlaySound(Sounds.SoundType.AddBlock, ref position);
                        break;
                }
            }
        }
        /// <summary>Send message to the server to add or remove a block. Block will be removed if the block type is Air.</summary>
        /// <param name="position">position to add the block</param>
        /// <param name="blockType">block type to add</param>
        public static void SendAddOrRemoveBlock(Position position, Block.BlockType blockType)
        {
            if (!position.IsValidBlockLocation) return;
            if (blockType == Block.BlockType.Air) //remove block
            {
                if (position.Y == 0) { Game.UiHost.AddChatMessage(new ChatMessage(ChatMessageType.Error, "Cannot remove a block at the base of the world. Block cancelled.")); return; }
                new RemoveBlock(ref position).Send();
            }
            else //add block
            {
                var head = Game.Player.CoordsHead;
                if (!Config.CreativeMode && Game.Player.Inventory[(int)blockType] <= 0) { Game.UiHost.AddChatMessage(new ChatMessage(ChatMessageType.Error, string.Format("No {0} in inventory.", blockType))); return; }
                if (Block.IsBlockTypeSolid(blockType) && (position.IsOnBlock(ref Game.Player.Coords) || position.IsOnBlock(ref head))) { Game.UiHost.AddChatMessage(new ChatMessage(ChatMessageType.Error, "Attempted to build solid block on self. Not smart. Block cancelled.")); return; }

                foreach (var player in Players.Values)
                {
                    head = player.CoordsHead;
                    if (!Block.IsBlockTypeSolid(blockType) || (!position.IsOnBlock(ref player.Coords) && !position.IsOnBlock(ref head))) continue;
                    Game.UiHost.AddChatMessage(new ChatMessage(ChatMessageType.Error, "Attempted to build solid block on other player. Not nice. Block cancelled.")); return;
                }
                new AddBlock(ref position, blockType).Send();
                if (!Config.CreativeMode) Game.Player.Inventory[(int)blockType]--;
            }
        }