internal override void Receive() { if (!Config.IsSinglePlayer) { lock (TcpClient) { base.Receive(); var blockCount = BitConverter.ToInt32(ReadStream(sizeof(int)), 0); for (var i = 0; i < blockCount; i++) { var bytes = ReadStream(Position.SIZE + sizeof(ushort)); var position = new Position(bytes, 0); var blockType = (Block.BlockType)BitConverter.ToUInt16(bytes, Position.SIZE); Blocks.Add(new AddBlock(ref position, blockType)); } } } Settings.ChunkUpdatesDisabled = true; foreach (var addBlock in Blocks) { WorldData.PlaceBlock(addBlock.Position, addBlock.BlockType); } Settings.ChunkUpdatesDisabled = false; if (Config.IsServer) { //bm: this has to wait until the server can manage who's in creative mode //if (ConnectedPlayer.Inventory[(int)BlockType] <= 0) return; //ConnectedPlayer.Inventory[(int)BlockType] -= 1; foreach (var player in Server.Controller.Players.Values) { var addBlockMulti = new AddBlockMulti { ConnectedPlayer = player }; addBlockMulti.Blocks.AddRange(Blocks); addBlockMulti.Send(); } } }
internal override void Receive() { if (!Config.IsSinglePlayer) { lock (TcpClient) { base.Receive(); var blockCount = BitConverter.ToInt32(ReadStream(sizeof(int)), 0); for (var i = 0; i < blockCount; i++) { var bytes = ReadStream(Position.SIZE + sizeof(ushort)); var position = new Position(bytes, 0); var blockType = (Block.BlockType)BitConverter.ToUInt16(bytes, Position.SIZE); Blocks.Add(new AddBlock(ref position, blockType)); } } } Settings.ChunkUpdatesDisabled = true; foreach (var addBlock in Blocks) WorldData.PlaceBlock(addBlock.Position, addBlock.BlockType); Settings.ChunkUpdatesDisabled = false; if (Config.IsServer) { //bm: this has to wait until the server can manage who's in creative mode //if (ConnectedPlayer.Inventory[(int)BlockType] <= 0) return; //ConnectedPlayer.Inventory[(int)BlockType] -= 1; foreach (var player in Server.Controller.Players.Values) { var addBlockMulti = new AddBlockMulti { ConnectedPlayer = player }; addBlockMulti.Blocks.AddRange(Blocks); addBlockMulti.Send(); } } }
/// <summary>Only called for SinglePlayer and Servers.</summary> private void GrassGrow() { var possibleChanges = new List<Tuple<Block.BlockType, Position>>(); for (var x = 0; x < CHUNK_SIZE; x++) { int worldX = Coords.WorldCoordsX + x; for (var z = 0; z < CHUNK_SIZE; z++) { int worldZ = Coords.WorldCoordsZ + z; for (var y = 0; y <= Math.Min(CHUNK_HEIGHT - 1, HeightMap[x, z] + 1); y++) //look +1 above heightmap as water directly above heightmap could change to ice { var blockType = Blocks[x, y, z].Type; switch (blockType) { case Block.BlockType.Grass: case Block.BlockType.Dirt: case Block.BlockType.Snow: case Block.BlockType.Water: case Block.BlockType.Sand: case Block.BlockType.SandDark: break; default: continue; //continue if this block type can never cause changes } bool hasAirAbove = y >= CHUNK_HEIGHT - 1 || Blocks[x, y + 1, z].Type == Block.BlockType.Air; bool isReceivingSunlight = y > HeightMap[x, z] || (hasAirAbove && WorldData.HasAdjacentBlockReceivingDirectSunlight(worldX, y, worldZ)); switch (WorldData.WorldType) { case WorldType.Grass: if (isReceivingSunlight) { switch (blockType) { case Block.BlockType.Dirt: if (hasAirAbove) possibleChanges.Add(new Tuple<Block.BlockType, Position>(Block.BlockType.Grass, new Position(worldX, y, worldZ))); continue; case Block.BlockType.Snow: possibleChanges.Add(new Tuple<Block.BlockType, Position>(Block.BlockType.Dirt, new Position(worldX, y, worldZ))); continue; } } else { switch (blockType) { case Block.BlockType.Grass: case Block.BlockType.Snow: possibleChanges.Add(new Tuple<Block.BlockType, Position>(Block.BlockType.Dirt, new Position(worldX, y, worldZ))); continue; } } break; case WorldType.Desert: //lighting doesnt matter for deserts switch (blockType) { case Block.BlockType.Grass: case Block.BlockType.Snow: possibleChanges.Add(new Tuple<Block.BlockType, Position>(Block.BlockType.Sand, new Position(worldX, y, worldZ))); continue; case Block.BlockType.SandDark: if (hasAirAbove) possibleChanges.Add(new Tuple<Block.BlockType, Position>(Block.BlockType.Sand, new Position(worldX, y, worldZ))); continue; } break; case WorldType.Winter: switch (blockType) { case Block.BlockType.Water: //water with air above and without more water below can freeze //note: this will cause multiple lightbox updates and chunk queues if multiple water freezes at once because water -> ice is a change in transparency; therefore this is acceptable if (hasAirAbove) { var hasWaterBelow = y > 0 && Blocks[x, y - 1, z].Type == Block.BlockType.Water; if (!hasWaterBelow) possibleChanges.Add(new Tuple<Block.BlockType, Position>(Block.BlockType.Ice, new Position(worldX, y, worldZ))); } continue; } if (isReceivingSunlight) { switch (blockType) { case Block.BlockType.Dirt: if (hasAirAbove) possibleChanges.Add(new Tuple<Block.BlockType, Position>(Block.BlockType.Snow, new Position(worldX, y, worldZ))); continue; case Block.BlockType.Grass: possibleChanges.Add(new Tuple<Block.BlockType, Position>(Block.BlockType.Snow, new Position(worldX, y, worldZ))); continue; } } else { switch (blockType) { case Block.BlockType.Grass: case Block.BlockType.Snow: possibleChanges.Add(new Tuple<Block.BlockType, Position>(Block.BlockType.Dirt, new Position(worldX, y, worldZ))); continue; } } break; } } } } if (possibleChanges.Count == 0) { //this happens after a change is made in the chunk that did not cause any possible grass grow style changes GrassGrowing = false; Debug.WriteLine("Grass finished growing in chunk {0} No possible changes found", Coords); return; } Debug.WriteLine("Grass growing in chunk {0} {1} possible change(s)", Coords, possibleChanges.Count); var changesMade = 0; var addBlocks = new List<AddBlock>(); //only gets used for servers Settings.ChunkUpdatesDisabled = true; //change blocks while updates are disabled so chunk is only rebuilt once { foreach (var change in possibleChanges) { //add some randomness so the changes dont happen all at once if (possibleChanges.Count > 1) { switch (change.Item1) //can assign different percentages based on block type { case Block.BlockType.Ice: if (Settings.Random.NextDouble() > 0.05) continue; //give ice forming a very low chance because its a change in transparency and causes lightbox updates and must queue multiple chunks break; default: if (Settings.Random.NextDouble() > 0.18) continue; break; } } else //when only one possible change is left, greatly increase its chance; prevents tons of chunks lingering performing the logic until the final change gets made { if (Settings.Random.NextDouble() > 0.5) continue; } changesMade++; var changePosition = change.Item2; WorldData.PlaceBlock(changePosition, change.Item1); if (Config.IsServer) { addBlocks.Add(new AddBlock(ref changePosition, change.Item1)); } } } Settings.ChunkUpdatesDisabled = false; //send updates to multiplayer clients if (Config.IsServer && addBlocks.Count > 0) { foreach (var player in Server.Controller.Players.Values) { var addBlockMulti = new AddBlockMulti {ConnectedPlayer = player}; addBlockMulti.Blocks.AddRange(addBlocks); addBlockMulti.Send(); } } if (changesMade == possibleChanges.Count) { //when all possible changes have been made we can stop GrassGrowing here without waiting for the next iteration to confirm it GrassGrowing = false; Debug.WriteLine("Grass finished growing in chunk {0} All possible changes made", Coords); } }
// ReSharper restore FunctionNeverReturns private static void PlayerThread(NetworkPlayer player) { NetworkStream clientStream; //make all the introductions. we do this before sending the world so the client doesn't see them as new connections foreach (var otherPlayer in Players.Values) { try { new Connect(otherPlayer.Id, otherPlayer.UserName, otherPlayer.Coords) { ConnectedPlayer = player, Immediate = true }.Send(); } catch (Exception ex) { WriteToServerConsoleLog(string.Format("{0} {1} caused an exception and was removed: {2}", player.UserName, player.IpAddress, ex.Message)); #if DEBUG WriteToServerConsoleLog(ex.StackTrace); #endif } new Connect(player.Id, player.UserName, player.Coords) { ConnectedPlayer = otherPlayer }.Send(); } try { Players.TryAdd(player.Id, player); //note: it is not possible for the add to fail on ConcurrentDictionary, see: http://www.albahari.com/threading/part5.aspx#_Concurrent_Collections UpdateServerConsolePlayerList(); var getWorld = new GetWorld { ConnectedPlayer = player }; getWorld.Send(); WriteToServerConsoleLog(String.Format("World send complete to {0} ({1} compressed, {2} uncompressed)", player.IpAddress, getWorld.DataLength, getWorld.UncompressedLength)); //create a thread to handle communication with connected client player.TcpClient.NoDelay = true; clientStream = player.TcpClient.GetStream(); } catch (Exception ex) { HandleNetworkError(player, ex); return; } var actionTypebytes = new byte[sizeof(ushort)]; try { if (!string.IsNullOrWhiteSpace(Config.MOTD)) new ServerMsg(Config.MOTD, player).Send(); while (true) { Thread.Sleep(10); //bm: polling is expensive. don't remove this or the server will pin your machine when only a couple users are online GameAction gameAction; while (player.SendQueue.Count > 0 && player.SendQueue.TryDequeue(out gameAction)) { gameAction.Immediate = true; gameAction.Send(); } if (!clientStream.DataAvailable) continue; var bytesRead = 0; while (bytesRead < actionTypebytes.Length) bytesRead += clientStream.Read(actionTypebytes, bytesRead, actionTypebytes.Length - bytesRead); var actionType = (ActionType)BitConverter.ToUInt16(actionTypebytes, 0); switch (actionType) { case ActionType.AddBlock: gameAction = new AddBlock(); break; case ActionType.AddBlockItem: gameAction = new AddBlockItem(); break; case ActionType.AddBlockMulti: gameAction = new AddBlockMulti(); break; case ActionType.AddCuboid: gameAction = new AddCuboid(); break; case ActionType.AddProjectile: gameAction = new AddProjectile(); break; case ActionType.AddStaticItem: gameAction = new AddStaticItem(); break; case ActionType.AddStructure: gameAction = new AddStructure(); break; case ActionType.ChatMsg: gameAction = new ChatMsg(); break; case ActionType.Disconnect: gameAction = new Disconnect(); break; case ActionType.PickupBlockItem: gameAction = new PickupBlockItem(); break; case ActionType.PlayerInfo: gameAction = new PlayerInfo(); break; case ActionType.PlayerMove: gameAction = new PlayerMove(); break; case ActionType.PlayerOption: gameAction = new PlayerOption(); break; case ActionType.RemoveBlock: gameAction = new RemoveBlock(); break; case ActionType.RemoveBlockItem: gameAction = new RemoveBlockItem(); break; case ActionType.RemoveBlockMulti: gameAction = new RemoveBlockMulti(); break; case ActionType.ServerCommand: gameAction = new ServerCommand(); break; case ActionType.Connect: case ActionType.ServerMsg: case ActionType.ServerSync: case ActionType.GetWorld: throw new Exception(string.Format("Server should not receive action type: {0}", actionType)); case ActionType.Error: var bytes = 0; while (clientStream.ReadByte() != -1) { bytes++; } throw new Exception("GameAction 'Error' received. " + bytes + " byte(s) remained in the stream."); default: throw new Exception(string.Format("Unknown action type: {0}", actionType)); } gameAction.ConnectedPlayer = player; gameAction.Receive(); if (HasServerConsole && CaptureIncoming) //only stream messages if there is a console window and it has requested to display them { _serverConsole.UpdateStreamLogInvokable(gameAction, player, false); } if (actionType == ActionType.Disconnect) return; } } catch (Exception ex) { HandleNetworkError(player, ex); } }
/// <summary>Only called for SinglePlayer and Servers.</summary> private void WaterExpand() { Debug.WriteLine("Water expanding in chunk {0}...", Coords); var newWater = new List<Position>(); for (var i = 0; i < CHUNK_SIZE; i++) { for (var j = 0; j < CHUNK_HEIGHT; j++) { for (var k = 0; k < CHUNK_SIZE; k++) { if (Blocks[i, j, k].Type != Block.BlockType.Water) continue; var belowCurrent = new Position(); for (var q = 0; q < 5; q++) { Position adjacent; switch (q) { case 0: adjacent = new Position(Coords.WorldCoordsX + i, j - 1, Coords.WorldCoordsZ + k); belowCurrent = adjacent; break; case 1: adjacent = new Position(Coords.WorldCoordsX + i + 1, j, Coords.WorldCoordsZ + k); break; case 2: adjacent = new Position(Coords.WorldCoordsX + i - 1, j, Coords.WorldCoordsZ + k); break; case 3: adjacent = new Position(Coords.WorldCoordsX + i, j, Coords.WorldCoordsZ + k + 1); break; default: adjacent = new Position(Coords.WorldCoordsX + i, j, Coords.WorldCoordsZ + k - 1); break; } if (newWater.Contains(adjacent)) continue; //if there's air or water below the current block, don't spread sideways if (q != 0 && belowCurrent.IsValidBlockLocation && (Blocks[belowCurrent].Type == Block.BlockType.Air || Blocks[belowCurrent].Type == Block.BlockType.Water)) continue; if (adjacent.IsValidBlockLocation && adjacent.GetBlock().Type == Block.BlockType.Air) newWater.Add(adjacent); } } } } if (newWater.Count == 0) { WaterExpanding = false; Debug.WriteLine("Water finished expanding in chunk {0}", Coords); return; } var addBlocks = new List<AddBlock>(); Settings.ChunkUpdatesDisabled = true; //change blocks while updates are disabled so chunk is only rebuilt once foreach (var newWaterPosition in newWater.Where(newWaterCoords => newWaterCoords.GetBlock().Type != Block.BlockType.Water)) { WorldData.PlaceBlock(newWaterPosition, Block.BlockType.Water); if (Config.IsServer) { var temp = newWaterPosition; addBlocks.Add(new AddBlock(ref temp, Block.BlockType.Water)); } } Settings.ChunkUpdatesDisabled = false; if (Config.IsServer && addBlocks.Count > 0) { foreach (var player in Server.Controller.Players.Values) { var addBlockMulti = new AddBlockMulti {ConnectedPlayer = player}; addBlockMulti.Blocks.AddRange(addBlocks); addBlockMulti.Send(); } } }