From 5f0146da3e03146c6f0fa24feab2a6391ef3723f Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Sat, 12 Mar 2016 11:21:42 +1100 Subject: [PATCH] Use BulkBlockUpdate to reduce bandwidth consumption, also create a BulkBlockSender class for future usage elsewhere. --- Commands/Command.cs | 132 +++++++++++------------ Commands/Moderation/CmdUnbanip.cs | 34 +++--- Commands/building/CustomBlockCommand.cs | 2 +- Database/Database.cs | 86 +++++++-------- Levels/BlockDefinitions.cs | 4 +- Levels/BufferedBlockSender.cs | 138 ++++++++++++++++++++++++ Levels/Level.Physics.cs | 46 ++------ Levels/Level.cs | 1 + MCGalaxy_.csproj | 1 + Network/Player.Networking.cs | 9 +- Player/Player.CPE.cs | 9 +- Player/Player.Handlers.cs | 6 +- 12 files changed, 289 insertions(+), 179 deletions(-) create mode 100644 Levels/BufferedBlockSender.cs diff --git a/Commands/Command.cs b/Commands/Command.cs index a3b4e2d57..bb9800242 100644 --- a/Commands/Command.cs +++ b/Commands/Command.cs @@ -1,19 +1,19 @@ /* - Copyright 2010 MCSharp team (Modified for use with MCZall/MCLawl/MCGalaxy) + Copyright 2010 MCSharp team (Modified for use with MCZall/MCLawl/MCGalaxy) - Dual-licensed under the Educational Community License, Version 2.0 and - the GNU General Public License, Version 3 (the "Licenses"); you may - not use this file except in compliance with the Licenses. You may - obtain a copy of the Licenses at - - http://www.opensource.org/licenses/ecl2.php - http://www.gnu.org/licenses/gpl-3.0.html - - Unless required by applicable law or agreed to in writing, - software distributed under the Licenses are distributed on an "AS IS" - BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - or implied. See the Licenses for the specific language governing - permissions and limitations under the Licenses. + Dual-licensed under the Educational Community License, Version 2.0 and + the GNU General Public License, Version 3 (the "Licenses"); you may + not use this file except in compliance with the Licenses. You may + obtain a copy of the Licenses at + + http://www.opensource.org/licenses/ecl2.php + http://www.gnu.org/licenses/gpl-3.0.html + + Unless required by applicable law or agreed to in writing, + software distributed under the Licenses are distributed on an "AS IS" + BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + or implied. See the Licenses for the specific language governing + permissions and limitations under the Licenses. */ using System; using System.Collections.Generic; @@ -22,64 +22,64 @@ using MCGalaxy.Commands; namespace MCGalaxy { - public abstract partial class Command - { - public abstract string name { get; } - public abstract string shortcut { get; } - public abstract string type { get; } - public abstract bool museumUsable { get; } - public abstract LevelPermission defaultRank { get; } - public abstract void Use(Player p, string message); - public abstract void Help(Player p); - public virtual CommandPerm[] AdditionalPerms { get { return null; } } + public abstract partial class Command + { + public abstract string name { get; } + public abstract string shortcut { get; } + public abstract string type { get; } + public abstract bool museumUsable { get; } + public abstract LevelPermission defaultRank { get; } + public abstract void Use(Player p, string message); + public abstract void Help(Player p); + public virtual CommandPerm[] AdditionalPerms { get { return null; } } - public static CommandList all = new CommandList(); - public static CommandList core = new CommandList(); - - public static void InitAll() { + public static CommandList all = new CommandList(); + public static CommandList core = new CommandList(); + + public static void InitAll() { all.AddOtherPerms = true; Type[] types = Assembly.GetExecutingAssembly().GetTypes(); for (int i = 0; i < types.Length; i++) { - Type type = types[i]; - if (!type.IsSubclassOf(typeof(Command)) || type.IsAbstract) continue; - all.Add((Command)Activator.CreateInstance(type)); + Type type = types[i]; + if (!type.IsSubclassOf(typeof(Command)) || type.IsAbstract) continue; + all.Add((Command)Activator.CreateInstance(type)); } core.commands = new List(all.commands); Scripting.Autoload(); } - - protected static void RevertAndClearState(Player p, ushort x, ushort y, ushort z) { - p.ClearBlockchange(); - p.RevertBlock(x, y, z); - } - - protected void MessageInGameOnly(Player p) { - Player.SendMessage(p, "/" + name + " can only be used in-game."); - } - } - - public struct CommandPerm { - public LevelPermission Perm; - public string Description; - public int Number; - - public CommandPerm(LevelPermission perm, string desc) { - Perm = perm; Description = desc; Number = 1; - } - - public CommandPerm(LevelPermission perm, string desc, int num) { - Perm = perm; Description = desc; Number = num; - } - } - - public sealed class CommandTypes { - public const string Building = "build"; - public const string Chat = "chat"; - public const string Economy = "economy"; - public const string Games = "game"; - public const string Information = "information"; - public const string Moderation = "mod"; - public const string Other = "other"; - public const string World = "world"; - } + + protected static void RevertAndClearState(Player p, ushort x, ushort y, ushort z) { + p.ClearBlockchange(); + p.RevertBlock(x, y, z); + } + + protected void MessageInGameOnly(Player p) { + Player.SendMessage(p, "/" + name + " can only be used in-game."); + } + } + + public struct CommandPerm { + public LevelPermission Perm; + public string Description; + public int Number; + + public CommandPerm(LevelPermission perm, string desc) { + Perm = perm; Description = desc; Number = 1; + } + + public CommandPerm(LevelPermission perm, string desc, int num) { + Perm = perm; Description = desc; Number = num; + } + } + + public sealed class CommandTypes { + public const string Building = "build"; + public const string Chat = "chat"; + public const string Economy = "economy"; + public const string Games = "game"; + public const string Information = "information"; + public const string Moderation = "mod"; + public const string Other = "other"; + public const string World = "world"; + } } diff --git a/Commands/Moderation/CmdUnbanip.cs b/Commands/Moderation/CmdUnbanip.cs index d9e8e3a89..9dfe95ce3 100644 --- a/Commands/Moderation/CmdUnbanip.cs +++ b/Commands/Moderation/CmdUnbanip.cs @@ -1,19 +1,19 @@ /* - Copyright 2010 MCSharp team (Modified for use with MCZall/MCLawl/MCGalaxy) - - Dual-licensed under the Educational Community License, Version 2.0 and - the GNU General Public License, Version 3 (the "Licenses"); you may - not use this file except in compliance with the Licenses. You may - obtain a copy of the Licenses at - - http://www.opensource.org/licenses/ecl2.php - http://www.gnu.org/licenses/gpl-3.0.html - - Unless required by applicable law or agreed to in writing, - software distributed under the Licenses are distributed on an "AS IS" - BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - or implied. See the Licenses for the specific language governing - permissions and limitations under the Licenses. + Copyright 2010 MCSharp team (Modified for use with MCZall/MCLawl/MCGalaxy) + + Dual-licensed under the Educational Community License, Version 2.0 and + the GNU General Public License, Version 3 (the "Licenses"); you may + not use this file except in compliance with the Licenses. You may + obtain a copy of the Licenses at + + http://www.opensource.org/licenses/ecl2.php + http://www.gnu.org/licenses/gpl-3.0.html + + Unless required by applicable law or agreed to in writing, + software distributed under the Licenses are distributed on an "AS IS" + BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + or implied. See the Licenses for the specific language governing + permissions and limitations under the Licenses. */ using System; using System.Data; @@ -24,7 +24,7 @@ namespace MCGalaxy.Commands { public override string name { get { return "unbanip"; } } public override string shortcut { get { return ""; } } - public override string type { get { return CommandTypes.Moderation; } } + public override string type { get { return CommandTypes.Moderation; } } public override bool museumUsable { get { return true; } } public override LevelPermission defaultRank { get { return LevelPermission.Operator; } } public CmdUnbanip() { } @@ -37,7 +37,7 @@ namespace MCGalaxy.Commands message = message.Remove(0, 1).Trim(); Player who = PlayerInfo.Find(message); if (who == null) { - DatabaseParameterisedQuery query = DatabaseParameterisedQuery.Create(); + DatabaseParameterisedQuery query = DatabaseParameterisedQuery.Create(); query.AddParam("@Name", message); DataTable ip = Database.fillData(query, "SELECT IP FROM Players WHERE Name = @Name"); if (ip.Rows.Count > 0) { diff --git a/Commands/building/CustomBlockCommand.cs b/Commands/building/CustomBlockCommand.cs index 7cb7950c7..d348c1089 100644 --- a/Commands/building/CustomBlockCommand.cs +++ b/Commands/building/CustomBlockCommand.cs @@ -407,7 +407,7 @@ namespace MCGalaxy.Commands { static void ReloadMap(Player p, bool global) { Player[] players = PlayerInfo.Online; foreach (Player pl in players) { - if (!pl.HasCpeExt(CpeExt.BlockDefinitions)) continue; + if (!pl.hasBlockDefs) continue; if (!global && p.level != pl.level) continue; if (pl.level == null || !pl.level.HasCustomBlocks) continue; diff --git a/Database/Database.cs b/Database/Database.cs index be6e89e4f..20886804f 100644 --- a/Database/Database.cs +++ b/Database/Database.cs @@ -1,19 +1,19 @@ /* - Copyright 2011 MCForge - - Dual-licensed under the Educational Community License, Version 2.0 and - the GNU General Public License, Version 3 (the "Licenses"); you may - not use this file except in compliance with the Licenses. You may - obtain a copy of the Licenses at - - http://www.opensource.org/licenses/ecl2.php - http://www.gnu.org/licenses/gpl-3.0.html - - Unless required by applicable law or agreed to in writing, - software distributed under the Licenses are distributed on an "AS IS" - BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - or implied. See the Licenses for the specific language governing - permissions and limitations under the Licenses. + Copyright 2011 MCForge + + Dual-licensed under the Educational Community License, Version 2.0 and + the GNU General Public License, Version 3 (the "Licenses"); you may + not use this file except in compliance with the Licenses. You may + obtain a copy of the Licenses at + + http://www.opensource.org/licenses/ecl2.php + http://www.gnu.org/licenses/gpl-3.0.html + + Unless required by applicable law or agreed to in writing, + software distributed under the Licenses are distributed on an "AS IS" + BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + or implied. See the Licenses for the specific language governing + permissions and limitations under the Licenses. */ using System; using System.Collections.Generic; @@ -237,19 +237,19 @@ namespace MCGalaxy public static void executeQuery(string queryString, bool createDB = false) { if (Server.useMySQL) executeQuery(MySQL.query, queryString, createDB); - else executeQuery(SQLite.query, queryString, createDB); + else executeQuery(SQLite.query, queryString, createDB); } public static void executeQuery(DatabaseParameterisedQuery query, string queryString, bool createDB = false) { Exception e = null; for (int i = 0; i < 10; i++) { - try { - query.Execute(queryString, createDB); - query.ClearParams(); - return; - } catch (Exception ex) { - e = ex; // try yet again - } + try { + query.Execute(queryString, createDB); + query.ClearParams(); + return; + } catch (Exception ex) { + e = ex; // try yet again + } } File.AppendAllText("MySQL_error.log", DateTime.Now + " " + queryString + "\r\n"); @@ -258,29 +258,29 @@ namespace MCGalaxy } public static DataTable fillData(string queryString, bool skipError = false) { - if (Server.useMySQL) return fillData(MySQL.query, queryString, skipError); - else return fillData(SQLite.query, queryString, skipError); + if (Server.useMySQL) return fillData(MySQL.query, queryString, skipError); + else return fillData(SQLite.query, queryString, skipError); } public static DataTable fillData(DatabaseParameterisedQuery query, string queryString, bool skipError = false) { - using (DataTable results = new DataTable("toReturn")) { - Exception e = null; - for (int i = 0; i < 10; i++) { - try { - query.Fill(queryString, results); - query.ClearParams(); - return results; - } catch (Exception ex) { - e = ex; // try yet again - } - } - - if (skipError) return results; - File.AppendAllText("MySQL_error.log", DateTime.Now + " " + queryString + "\r\n"); - Server.ErrorLog(e); - query.ClearParams(); - return results; - } + using (DataTable results = new DataTable("toReturn")) { + Exception e = null; + for (int i = 0; i < 10; i++) { + try { + query.Fill(queryString, results); + query.ClearParams(); + return results; + } catch (Exception ex) { + e = ex; // try yet again + } + } + + if (skipError) return results; + File.AppendAllText("MySQL_error.log", DateTime.Now + " " + queryString + "\r\n"); + Server.ErrorLog(e); + query.ClearParams(); + return results; + } } internal static void fillDatabase(Stream stream) diff --git a/Levels/BlockDefinitions.cs b/Levels/BlockDefinitions.cs index ccf462232..11fde53e5 100644 --- a/Levels/BlockDefinitions.cs +++ b/Levels/BlockDefinitions.cs @@ -144,7 +144,7 @@ namespace MCGalaxy { Player[] players = PlayerInfo.Online; foreach (Player pl in players) { if (!global && pl.level != level) continue; - if (!pl.HasCpeExt(CpeExt.BlockDefinitions)) continue; + if (!pl.hasBlockDefs) continue; if (global && pl.level.CustomBlockDefs[id] != GlobalDefs[id]) continue; if (pl.HasCpeExt(CpeExt.BlockDefinitionsExt, 2) && def.Shape != 0) @@ -176,7 +176,7 @@ namespace MCGalaxy { if (!global && pl.level != level) continue; if (global && pl.level.CustomBlockDefs[id] != null) continue; - if (pl.HasCpeExt(CpeExt.BlockDefinitions)) + if (pl.hasBlockDefs) pl.SendRaw(Opcode.CpeRemoveBlockDefinition, id); } Save(global, level); diff --git a/Levels/BufferedBlockSender.cs b/Levels/BufferedBlockSender.cs new file mode 100644 index 000000000..99e604ce7 --- /dev/null +++ b/Levels/BufferedBlockSender.cs @@ -0,0 +1,138 @@ +/* + Copyright 2015 MCGalaxy + + Dual-licensed under the Educational Community License, Version 2.0 and + the GNU General Public License, Version 3 (the "Licenses"); you may + not use this file except in compliance with the Licenses. You may + obtain a copy of the Licenses at + + http://www.opensource.org/licenses/ecl2.php + http://www.gnu.org/licenses/gpl-3.0.html + + Unless required by applicable law or agreed to in writing, + software distributed under the Licenses are distributed on an "AS IS" + BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + or implied. See the Licenses for the specific language governing + permissions and limitations under the Licenses. + */ +using System; + +namespace MCGalaxy { + + public sealed class BufferedBlockSender { + + int[] indices = new int[256]; + byte[] types = new byte[256]; + int count = 0; + Level level; + + public BufferedBlockSender(Level level) { + this.level = level; + } + + public void Add(int index, byte type, byte extType) { + indices[count] = index; + if (type == Block.custom_block) types[count] = extType; + else types[count] = Block.Convert(type); + count++; + } + + public void CheckIfSend(bool force) { + if (count > 0 && (force || count == 256)) { + byte[] bulk = null, normal = null, noBlockDefs = null, original = null; + Player[] players = PlayerInfo.Online; + + foreach (Player p in players) { + if (p.level != level) continue; + + // Different clients support varying types of blocks + byte[] packet = null; + if (p.HasCpeExt(CpeExt.BulkBlockUpdate) && p.hasCustomBlocks && p.hasBlockDefs && count >= 1600) { + if (bulk == null) bulk = MakeBulkPacket(); + packet = bulk; + } else if (p.hasCustomBlocks && p.hasBlockDefs) { + if (normal == null) normal = MakeNormalPacket(); + packet = normal; + } else if (p.hasCustomBlocks) { + if (noBlockDefs == null) noBlockDefs = MakeNoBlockDefsPacket(); + packet = noBlockDefs; + } else { + if (original == null) original = MakeOriginalOnlyPacket(); + packet = original; + } + p.SendRaw(packet); + } + count = 0; + } + } + + #region Packet construction + + byte[] MakeBulkPacket() { + byte[] data = new byte[2 + 256 * 5]; + data[0] = Opcode.CpeBulkBlockUpdate; + data[1] = (byte)(count - 1); + for (int i = 0, j = 2; i < count; i++) { + int index = indices[i]; + data[j++] = (byte)(index >> 24); data[j++] = (byte)(index >> 16); + data[j++] = (byte)(index >> 8); data[j++] = (byte)index; + } + for (int i = 0, j = 2 + 256 * sizeof(int); i < count; i++) + data[j++] = types[i]; + return data; + } + + byte[] MakeNormalPacket() { + byte[] data = new byte[count * 8]; + for (int i = 0, j = 0; i < count; i++) { + int index = indices[i]; + int x = (index % level.Width); + int y = (index / level.Width) / level.Length; + int z = (index / level.Width) % level.Length; + + data[j++] = Opcode.SetBlock; + data[j++] = (byte)(x >> 8); data[j++] = (byte)x; + data[j++] = (byte)(y >> 8); data[j++] = (byte)y; + data[j++] = (byte)(z >> 8); data[j++] = (byte)z; + data[j++] = types[i]; + } + return data; + } + + byte[] MakeNoBlockDefsPacket() { + byte[] data = new byte[count * 8]; + for (int i = 0, j = 0; i < count; i++) { + int index = indices[i]; + int x = (index % level.Width); + int y = (index / level.Width) / level.Length; + int z = (index / level.Width) % level.Length; + + data[j++] = Opcode.SetBlock; + data[j++] = (byte)(x >> 8); data[j++] = (byte)x; + data[j++] = (byte)(y >> 8); data[j++] = (byte)y; + data[j++] = (byte)(z >> 8); data[j++] = (byte)z; + data[j++] = types[i] < Block.CpeCount ? types[i] : level.GetFallback(types[i]); + } + return data; + } + + byte[] MakeOriginalOnlyPacket() { + byte[] data = new byte[count * 8]; + for (int i = 0, j = 0; i < count; i++) { + int index = indices[i]; + int x = (index % level.Width); + int y = (index / level.Width) / level.Length; + int z = (index / level.Width) % level.Length; + + data[j++] = Opcode.SetBlock; + data[j++] = (byte)(x >> 8); data[j++] = (byte)x; + data[j++] = (byte)(y >> 8); data[j++] = (byte)y; + data[j++] = (byte)(z >> 8); data[j++] = (byte)z; + data[j++] = types[i] < Block.CpeCount ? Block.ConvertCPE(types[i]) + : Block.ConvertCPE(level.GetFallback(types[i])); + } + return data; + } + #endregion + } +} diff --git a/Levels/Level.Physics.cs b/Levels/Level.Physics.cs index 549517074..2de9080ba 100644 --- a/Levels/Level.Physics.cs +++ b/Levels/Level.Physics.cs @@ -168,26 +168,23 @@ namespace MCGalaxy { RemoveExpiredChecks(); lastUpdate = ListUpdate.Count; - int* pendingIndices = stackalloc int[256]; - byte* pendingTypes = stackalloc byte[256]; - int pendingCount = 0; + if (ListUpdate.Count > 0 && bulkSender == null) + bulkSender = new BufferedBlockSender(this); for (int i = 0; i < ListUpdate.Count; i++) { Update C = ListUpdate.Items[i]; try { string info = C.data as string; if (info == null) info = ""; - if (DoPhysicsBlockchange(C.b, C.type, false,info, 0, true)) { - pendingIndices[pendingCount] = C.b; - pendingTypes[pendingCount] = C.type; - pendingCount++; - } + if (DoPhysicsBlockchange(C.b, C.type, false, info, 0, true)) + bulkSender.Add(C.b, C.type, 0); } catch { Server.s.Log("Phys update issue"); } - SendUpdates(pendingIndices, pendingTypes, ref pendingCount, false); + bulkSender.CheckIfSend(false); } - SendUpdates(pendingIndices, pendingTypes, ref pendingCount, true); + if (bulkSender != null) + bulkSender.CheckIfSend(true); ListUpdate.Clear(); listUpdateExists.Clear(); } catch (Exception e) { Server.s.Log("Level physics error"); @@ -195,35 +192,6 @@ namespace MCGalaxy { } } - unsafe void SendUpdates(int* pendingIndices, byte* pendingTypes, ref int pendingCount, bool force) { - try { - if (pendingCount > 0 && (force || pendingCount == 256)) { - byte[] data = new byte[pendingCount * 8]; - for (int i = 0; i < pendingCount; i++) { - int index = pendingIndices[i]; - int x = (index % Width); - int y = (index / Width) / Length; - int z = (index / Width) % Length; - - data[(i << 3)] = Opcode.SetBlock; - data[(i << 3) + 1] = (byte)(x >> 8); data[(i << 3) + 2] = (byte)x; - data[(i << 3) + 3] = (byte)(y >> 8); data[(i << 3) + 4] = (byte)y; - data[(i << 3) + 5] = (byte)(z >> 8); data[(i << 3) + 6] = (byte)z; - // TODO: do we need conversion for non-CPE block clients? - data[(i << 3) + 7] = Block.Convert(pendingTypes[i]); - } - Player[] players = PlayerInfo.Online; - foreach (Player p in players) { - if (p.level == this) p.SendRaw(data); - } - pendingCount = 0; - } - } catch { - Server.s.Log("Phys update issue"); - pendingCount = 0; - } - } - void DoNormalPhysics(ushort x, ushort y, ushort z, Random rand, Check C) { switch (blocks[C.b]) { diff --git a/Levels/Level.cs b/Levels/Level.cs index fcc70fc04..dc7342469 100644 --- a/Levels/Level.cs +++ b/Levels/Level.cs @@ -199,6 +199,7 @@ namespace MCGalaxy public bool bufferblocks = Server.bufferblocks; public List blockqueue = new List(); private readonly object physThreadLock = new object(); + BufferedBlockSender bulkSender; public List C4list = new List(); diff --git a/MCGalaxy_.csproj b/MCGalaxy_.csproj index 4bd9a0260..13906d1ab 100644 --- a/MCGalaxy_.csproj +++ b/MCGalaxy_.csproj @@ -420,6 +420,7 @@ + diff --git a/Network/Player.Networking.cs b/Network/Player.Networking.cs index 4813906b6..e5d2980d5 100644 --- a/Network/Player.Networking.cs +++ b/Network/Player.Networking.cs @@ -72,7 +72,7 @@ namespace MCGalaxy { } } - public bool hasCpe = false, hasCustomBlocks = false, hasTextColors, finishedCpeLogin = false; + public bool hasCpe, hasCustomBlocks, hasBlockDefs, hasTextColors, finishedCpeLogin = false; public string appName; public int extensionCount; public List extensions = new List(); @@ -304,7 +304,7 @@ namespace MCGalaxy { int usedLength = 0; byte[] buffer = CompressRawMap(out usedLength); - if (HasCpeExt(CpeExt.BlockDefinitions)) { + if (hasBlockDefs) { if (oldLevel != null && oldLevel != level) RemoveOldLevelCustomBlocks(oldLevel); BlockDefinition.SendLevelCustomBlocks(this); @@ -365,7 +365,6 @@ namespace MCGalaxy { byte[] buffer = new byte[bufferSize]; MemoryStream temp = new MemoryStream(); int bIndex = 0; - bool hasBlockDefs = HasCpeExt(CpeExt.BlockDefinitions); using (GZipStream compressor = new GZipStream(temp, CompressionMode.Compress, true)) { NetUtils.WriteI32(level.blocks.Length, buffer, 0); @@ -478,7 +477,7 @@ namespace MCGalaxy { NetUtils.WriteU16(z, buffer, 5); if (type == Block.custom_block) { - if (HasCpeExt(CpeExt.BlockDefinitions)) + if (hasBlockDefs) buffer[7] = level.GetExtTile(x, y, z); else buffer[7] = level.GetFallbackExtTile(x, y, z); @@ -502,7 +501,7 @@ namespace MCGalaxy { NetUtils.WriteU16(z, buffer, 5); if (type == Block.custom_block) { - if (HasCpeExt(CpeExt.BlockDefinitions)) + if (hasBlockDefs) buffer[7] = extType; else buffer[7] = level.GetFallback(extType); diff --git a/Player/Player.CPE.cs b/Player/Player.CPE.cs index bce5ae7b1..4377eae12 100644 --- a/Player/Player.CPE.cs +++ b/Player/Player.CPE.cs @@ -131,7 +131,8 @@ namespace MCGalaxy case CpeExt.FullCP437: FullCP437 = version; break; case CpeExt.BlockDefinitions: - BlockDefinitions = version; break; + BlockDefinitions = version; + hasBlockDefs = true; break; case CpeExt.BlockDefinitionsExt: BlockDefinitionsExt = version; break; case CpeExt.TextColors: @@ -191,9 +192,9 @@ namespace MCGalaxy public void SendCurrentMapAppearance() { byte edgeBlock = level.EdgeBlock, horBlock = level.HorizonBlock; - if (edgeBlock >= Block.CpeCount && !HasCpeExt(CpeExt.BlockDefinitions)) + if (edgeBlock >= Block.CpeCount && !hasBlockDefs) edgeBlock = level.GetFallback(edgeBlock); - if (horBlock >= Block.CpeCount && !HasCpeExt(CpeExt.BlockDefinitions)) + if (horBlock >= Block.CpeCount && !hasBlockDefs) horBlock = level.GetFallback(horBlock); if (EnvMapAppearance == 2) { @@ -239,7 +240,7 @@ namespace MCGalaxy SendSetBlockPermission(i, canPlace, canDelete); } - if (!HasCpeExt(CpeExt.BlockDefinitions)) return; + if (!hasBlockDefs) return; for (int i = count; i < 256; i++) { if (level.CustomBlockDefs[i] == null) continue; SendSetBlockPermission((byte)i, level.Buildable, level.Deletable); diff --git a/Player/Player.Handlers.cs b/Player/Player.Handlers.cs index 7920a6a54..18321c0e0 100644 --- a/Player/Player.Handlers.cs +++ b/Player/Player.Handlers.cs @@ -351,7 +351,7 @@ namespace MCGalaxy { if (type == 0x42) { hasCpe = true; - SendExtInfo(16); + SendExtInfo(18); SendExtEntry(CpeExt.ClickDistance, 1); SendExtEntry(CpeExt.CustomBlocks, 1); SendExtEntry(CpeExt.HeldBlock, 1); @@ -373,6 +373,8 @@ namespace MCGalaxy { SendExtEntry(CpeExt.BlockDefinitions, 1); SendExtEntry(CpeExt.BlockDefinitionsExt, 2); + SendExtEntry(CpeExt.TextColors, 1); + SendExtEntry(CpeExt.BulkBlockUpdate, 1); } try { left.Remove(name.ToLower()); } @@ -656,7 +658,7 @@ namespace MCGalaxy { } if (type >= Block.CpeCount) { - if (!HasCpeExt(CpeExt.BlockDefinitions) || level.CustomBlockDefs[type] == null) { + if (!hasBlockDefs || level.CustomBlockDefs[type] == null) { SendMessage("Invalid block type: " + type); RevertBlock(x, y, z); return; }