summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--MCServer/Plugins/APIDump/APIDesc.lua27
-rw-r--r--MCServer/Plugins/ChunkWorx/chunkworx_main.lua2
-rw-r--r--MCServer/Plugins/ChunkWorx/chunkworx_web.lua2
m---------MCServer/Plugins/Core0
-rw-r--r--MCServer/Plugins/Debuggers/Debuggers.lua40
-rw-r--r--MCServer/Plugins/InfoReg.lua157
-rw-r--r--Tools/ProtoProxy/.gitignore1
-rw-r--r--lib/inifile/iniFile.cpp38
-rw-r--r--lib/inifile/iniFile.h2
-rw-r--r--src/Bindings/AllToLua.pkg1
-rw-r--r--src/Bindings/LuaState.cpp26
-rw-r--r--src/Bindings/LuaState.h3
-rw-r--r--src/Bindings/PluginLua.cpp8
-rw-r--r--src/Bindings/PluginManager.cpp2
-rw-r--r--src/Bindings/PluginManager.h4
-rw-r--r--src/BlockID.h2
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/Chunk.cpp32
-rw-r--r--src/Chunk.h8
-rw-r--r--src/ChunkMap.cpp61
-rw-r--r--src/ChunkMap.h9
-rw-r--r--src/ClientHandle.cpp8
-rw-r--r--src/ClientHandle.h2
-rw-r--r--src/Entities/Entity.h4
-rw-r--r--src/Entities/ItemFrame.cpp124
-rw-r--r--src/Entities/ItemFrame.h42
-rw-r--r--src/Entities/Painting.cpp43
-rw-r--r--src/Entities/Painting.h42
-rw-r--r--src/ForEachChunkProvider.h27
-rw-r--r--src/Items/ItemHandler.cpp9
-rw-r--r--src/Items/ItemItemFrame.h53
-rw-r--r--src/Items/ItemPainting.h98
-rw-r--r--src/Mobs/Monster.cpp4
-rw-r--r--src/Protocol/Protocol.h2
-rw-r--r--src/Protocol/Protocol125.h1
-rw-r--r--src/Protocol/Protocol17x.cpp29
-rw-r--r--src/Protocol/Protocol17x.h1
-rw-r--r--src/Protocol/ProtocolRecognizer.cpp8
-rw-r--r--src/Protocol/ProtocolRecognizer.h1
-rw-r--r--src/World.cpp41
-rw-r--r--src/World.h32
-rw-r--r--src/WorldStorage/NBTChunkSerializer.cpp2
42 files changed, 980 insertions, 19 deletions
diff --git a/MCServer/Plugins/APIDump/APIDesc.lua b/MCServer/Plugins/APIDump/APIDesc.lua
index 34aff5ddb..fd4b1d947 100644
--- a/MCServer/Plugins/APIDump/APIDesc.lua
+++ b/MCServer/Plugins/APIDump/APIDesc.lua
@@ -713,6 +713,7 @@ end
IsMinecart = { Params = "", Return = "bool", Notes = "Returns true if the entity represents a {{cMinecart|minecart}}" },
IsMob = { Params = "", Return = "bool", Notes = "Returns true if the entity represents any {{cMonster|mob}}." },
IsOnFire = { Params = "", Return = "bool", Notes = "Returns true if the entity is on fire" },
+ IsPainting = { Params = "", Return = "bool", Notes = "Returns if this entity is a painting." },
IsPickup = { Params = "", Return = "bool", Notes = "Returns true if the entity represents a {{cPickup|pickup}}." },
IsPlayer = { Params = "", Return = "bool", Notes = "Returns true if the entity represents a {{cPlayer|player}}" },
IsProjectile = { Params = "", Return = "bool", Notes = "Returns true if the entity is a {{cProjectileEntity}} descendant." },
@@ -779,6 +780,7 @@ end
etPickup = { Notes = "The entity is a {{cPickup}}" },
etProjectile = { Notes = "The entity is a {{cProjectileEntity}} descendant" },
etTNT = { Notes = "The entity is a {{cTNTEntity}}" },
+ etPainting = { Notes = "The entity is a {{cPainting}}" },
},
ConstantGroups =
{
@@ -1109,6 +1111,9 @@ These ItemGrids are available in the API and can be manipulated by the plugins,
IsFullStack = { Params = "", Return = "bool", Notes = "Returns true if the item is stacked up to its maximum stacking" },
IsSameType = { Params = "cItem", Return = "bool", Notes = "Returns true if the item in the parameter is of the same ItemType as the one stored in the object. This is true even if the two items have different enchantments" },
IsStackableWith = { Params = "cItem", Return = "bool", Notes = "Returns true if the item in the parameter is stackable with the one stored in the object. Two items with different enchantments cannot be stacked" },
+ IsBothNameAndLoreEmpty = { Params = "", Return = "bool", Notes = "Returns if both the custom name and lore are not set." },
+ IsCustomNameEmpty = { Params = "", Return = "bool", Notes = "Returns if the custom name of the cItem is empty." },
+ IsLoreEmpty = { Params = "", Return = "", Notes = "Returns if the lore of the cItem is empty." },
},
Variables =
{
@@ -1116,6 +1121,8 @@ These ItemGrids are available in the API and can be manipulated by the plugins,
m_ItemCount = { Type = "number", Notes = "Number of items in this stack" },
m_ItemDamage = { Type = "number", Notes = "The damage of the item. Zero means no damage. Maximum damage can be queried with GetMaxDamage()" },
m_ItemType = { Type = "number", Notes = "The item type. One of E_ITEM_ or E_BLOCK_ constants" },
+ m_CustomName = { Type = "string", Notes = "The custom name for an item." },
+ m_Lore = { Type = "string", Notes = "The lore for an item. Line breaks are represented by the ` character." },
},
AdditionalInfo =
{
@@ -1160,6 +1167,17 @@ local Item5 = cItem(E_ITEM_DIAMOND_CHESTPLATE, 1, 0, "thorns=1;unbreaking=3");
},
}, -- cItem
+ cPainting =
+ {
+ Desc = "This class represents a painting in the world. These paintings are special and different from Vanilla in that they can be critical-hit.",
+ Functions =
+ {
+ GetDirection = { Params = "", Return = "number", Notes = "Returns the direction the painting faces. Directions: ZP - 0, ZM - 2, XM - 1, XP - 3. Note that these are not the BLOCK_FACE constants." },
+ GetName = { Params = "", Return = "string", Notes = "Returns the name of the painting" },
+ },
+
+ }, -- cPainting
+
cItemGrid =
{
Desc = [[This class represents a 2D array of items. It is used as the underlying storage and API for all cases that use a grid of items:
@@ -1749,6 +1767,7 @@ cPluginManager.AddHook(cPluginManager.HOOK_CHAT, OnChatMessage);
GetCurrentPlugin = { Params = "", Return = "{{cPlugin}}", Notes = "Returns the {{cPlugin}} object for the calling plugin. This is the same object that the Initialize function receives as the argument." },
GetNumPlugins = { Params = "", Return = "number", Notes = "Returns the number of plugins, including the disabled ones" },
GetPlugin = { Params = "PluginName", Return = "{{cPlugin}}", Notes = "(<b>DEPRECATED, UNSAFE</b>) Returns a plugin handle of the specified plugin, or nil if such plugin is not loaded. Note thatdue to multithreading the handle is not guaranteed to be safe for use when stored - a single-plugin reload may have been triggered in the mean time for the requested plugin." },
+ GetPluginsPath = { Params = "", Return = "string", Notes = "Returns the path where the individual plugin folders are located. Doesn't include the path separator at the end of the returned string." },
IsCommandBound = { Params = "Command", Return = "bool", Notes = "Returns true if in-game Command is already bound (by any plugin)" },
IsConsoleCommandBound = { Params = "Command", Return = "bool", Notes = "Returns true if console Command is already bound (by any plugin)" },
LoadPlugin = { Params = "PluginFolder", Return = "", Notes = "(<b>DEPRECATED</b>) Loads a plugin from the specified folder. NOTE: Loading plugins may be an unsafe operation and may result in a deadlock or a crash. This API is deprecated and might be removed." },
@@ -2026,6 +2045,7 @@ end
AreCommandBlocksEnabled = { Params = "", Return = "bool", Notes = "Returns whether command blocks are enabled on the (entire) server" },
BroadcastBlockAction = { Params = "BlockX, BlockY, BlockZ, ActionByte1, ActionByte2, BlockType, [{{cClientHandle|ExcludeClient}}]", Return = "", Notes = "Broadcasts the BlockAction packet to all clients who have the appropriate chunk loaded (except ExcludeClient). The contents of the packet are specified by the parameters for the call, the blocktype needn't match the actual block that is present in the world data at the specified location." },
BroadcastChat = { Params = "Message, [{{cClientHandle|ExcludeClient}}]", Return = "", Notes = "Sends the Message to all players in this world, except the optional ExcludeClient. No formatting is done by the server." },
+ BroadcastChatDeath = { Params = "Message, [{{cClientHandle|ExcludeClient}}]", Return = "", Notes = "Prepends Gray [DEATH] / colours entire text (depending on ShouldUseChatPrefixes()) and broadcasts message. For when a player dies." },
BroadcastChatFailure = { Params = "Message, [{{cClientHandle|ExcludeClient}}]", Return = "", Notes = "Prepends Rose [INFO] / colours entire text (depending on ShouldUseChatPrefixes()) and broadcasts message. For a command that failed to run because of insufficient permissions, etc." },
BroadcastChatFatal = { Params = "Message, [{{cClientHandle|ExcludeClient}}]", Return = "", Notes = "Prepends Red [FATAL] / colours entire text (depending on ShouldUseChatPrefixes()) and broadcasts message. For a plugin that crashed, or similar." },
BroadcastChatInfo = { Params = "Message, [{{cClientHandle|ExcludeClient}}]", Return = "", Notes = "Prepends Yellow [INFO] / colours entire text (depending on ShouldUseChatPrefixes()) and broadcasts message. For informational messages, such as command usage." },
@@ -2042,6 +2062,7 @@ end
DoExplosionAt = { Params = "Force, X, Y, Z, CanCauseFire, Source, SourceData", Return = "", Notes = "Creates an explosion of the specified relative force in the specified position. If CanCauseFire is set, the explosion will set blocks on fire, too. The Source parameter specifies the source of the explosion, one of the esXXX constants. The SourceData parameter is specific to each source type, usually it provides more info about the source." },
DoWithBlockEntityAt = { Params = "X, Y, Z, CallbackFunction, [CallbackData]", Return = "bool", Notes = "If there is a block entity at the specified coords, calls the CallbackFunction with the {{cBlockEntity}} parameter representing the block entity. The CallbackFunction has the following signature: <pre class=\"prettyprint lang-lua\">function Callback({{cBlockEntity|BlockEntity}}, [CallbackData])</pre> The function returns false if there is no block entity, or if there is, it returns the bool value that the callback has returned. Use {{tolua}}.cast() to cast the Callback's BlockEntity parameter to the correct {{cBlockEntity}} descendant." },
DoWithChestAt = { Params = "X, Y, Z, CallbackFunction, [CallbackData]", Return = "bool", Notes = "If there is a chest at the specified coords, calls the CallbackFunction with the {{cChestEntity}} parameter representing the chest. The CallbackFunction has the following signature: <pre class=\"prettyprint lang-lua\">function Callback({{cChestEntity|ChestEntity}}, [CallbackData])</pre> The function returns false if there is no chest, or if there is, it returns the bool value that the callback has returned." },
+ DoWithCommandBlockAt = { Params = "X, Y, Z, CallbackFunction, [CallbackData]", Return = "bool", Notes = "If there is a command block at the specified coords, calls the CallbackFunction with the {{cCommandBlockEntity}} parameter representing the command block. The CallbackFunction has the following signature: <pre class=\"prettyprint lang-lua\">function Callback({{cCommandBlockEntity|CommandBlockEntity}}, [CallbackData])</pre> The function returns false if there is no command block, or if there is, it returns the bool value that the callback has returned." },
DoWithDispenserAt = { Params = "X, Y, Z, CallbackFunction, [CallbackData]", Return = "bool", Notes = "If there is a dispenser at the specified coords, calls the CallbackFunction with the {{cDispenserEntity}} parameter representing the dispenser. The CallbackFunction has the following signature: <pre class=\"prettyprint lang-lua\">function Callback({{cDispenserEntity|DispenserEntity}}, [CallbackData])</pre> The function returns false if there is no dispenser, or if there is, it returns the bool value that the callback has returned." },
DoWithDropSpenserAt = { Params = "X, Y, Z, CallbackFunction, [CallbackData]", Return = "bool", Notes = "If there is a dropper or a dispenser at the specified coords, calls the CallbackFunction with the {{cDropSpenserEntity}} parameter representing the dropper or dispenser. The CallbackFunction has the following signature: <pre class=\"prettyprint lang-lua\">function Callback({{cDropSpenserEntity|DropSpenserEntity}}, [CallbackData])</pre> Note that this can be used to access both dispensers and droppers in a similar way. The function returns false if there is neither dispenser nor dropper, or if there is, it returns the bool value that the callback has returned." },
DoWithDropperAt = { Params = "X, Y, Z, CallbackFunction, [CallbackData]", Return = "bool", Notes = "If there is a dropper at the specified coords, calls the CallbackFunction with the {{cDropperEntity}} parameter representing the dropper. The CallbackFunction has the following signature: <pre class=\"prettyprint lang-lua\">function Callback({{cDropperEntity|DropperEntity}}, [CallbackData])</pre> The function returns false if there is no dropper, or if there is, it returns the bool value that the callback has returned." },
@@ -2087,6 +2108,7 @@ end
GetMaxSugarcaneHeight = { Params = "", Return = "number", Notes = "Returns the configured maximum height to which sugarcane will grow naturally." },
GetName = { Params = "", Return = "string", Notes = "Returns the name of the world, as specified in the settings.ini file." },
GetNumChunks = { Params = "", Return = "number", Notes = "Returns the number of chunks currently loaded." },
+ GetScoreBoard = { Params = "", Return = "{{cScoreBoard}}", Notes = "Returns the {{cScoreBoard|ScoreBoard}} object used by this world. " },
GetSignLines = { Params = "BlockX, BlockY, BlockZ", Return = "IsValid, [Line1, Line2, Line3, Line4]", Notes = "Returns true and the lines of a sign at the specified coords, or false if there is no sign at the coords." },
GetSpawnX = { Params = "", Return = "number", Notes = "Returns the X coord of the default spawn" },
GetSpawnY = { Params = "", Return = "number", Notes = "Returns the Y coord of the default spawn" },
@@ -2122,6 +2144,11 @@ end
RegenerateChunk = { Params = "ChunkX, ChunkZ", Return = "", Notes = "Queues the specified chunk to be re-generated, overwriting the current data. To queue a chunk for generating only if it doesn't exist, use the GenerateChunk() instead." },
ScheduleTask = { Params = "DelayTicks, TaskFunction", Return = "", Notes = "Queues the specified function to be executed in the world's tick thread after a the specified number of ticks. This enables operations to be queued for execution in the future. The function signature is <pre class=\"pretty-print lang-lua\">function({{cWorld|World}})</pre>All return values from the function are ignored. Note that it is unsafe to store references to MCServer objects, such as entities, across from the caller to the task handler function; store the EntityID instead." },
SendBlockTo = { Params = "BlockX, BlockY, BlockZ, {{cPlayer|Player}}", Return = "", Notes = "Sends the block at the specified coords to the specified player's client, as an UpdateBlock packet." },
+ SetAreaBiome = {
+ { Params = "MinX, MaxX, MinZ, MaxZ, EMCSBiome", Return = "bool", Notes = "Sets the biome in the rectangular area specified. Returns true if successful, false if any of the chunks were unloaded." },
+ { Params = "{{cCuboid|Cuboid}}, EMCSBiome", Return = "bool", Notes = "Sets the biome in the cuboid specified. Returns true if successful, false if any of the chunks were unloaded. The cuboid needn't be sorted." },
+ },
+ SetBiomeAt = { Params = "BlockX, BlockZ, EMCSBiome", Return = "bool", Notes = "Sets the biome at the specified block coords. Returns true if successful, false otherwise." },
SetBlock = { Params = "BlockX, BlockY, BlockZ, BlockType, BlockMeta", Return = "", Notes = "Sets the block at the specified coords, replaces the block entities for the previous block type, creates a new block entity for the new block, if appropriate, and wakes up the simulators. This is the preferred way to set blocks, as opposed to FastSetBlock(), which is only to be used under special circumstances." },
SetBlockMeta =
{
diff --git a/MCServer/Plugins/ChunkWorx/chunkworx_main.lua b/MCServer/Plugins/ChunkWorx/chunkworx_main.lua
index f74c4ea2d..88ecb3979 100644
--- a/MCServer/Plugins/ChunkWorx/chunkworx_main.lua
+++ b/MCServer/Plugins/ChunkWorx/chunkworx_main.lua
@@ -122,7 +122,7 @@ function OnTick( DeltaTime )
end
end
WW_instance:QueueSaveAllChunks()
- WW_instance:UnloadUnusedChunks()
+ WW_instance:QueueUnloadUnusedChunks()
end
end
end \ No newline at end of file
diff --git a/MCServer/Plugins/ChunkWorx/chunkworx_web.lua b/MCServer/Plugins/ChunkWorx/chunkworx_web.lua
index 44993f81c..6c5eab676 100644
--- a/MCServer/Plugins/ChunkWorx/chunkworx_web.lua
+++ b/MCServer/Plugins/ChunkWorx/chunkworx_web.lua
@@ -44,7 +44,7 @@ function HandleRequest_Generation( Request )
if (Request.PostParams["AGHRRRR"] ~= nil) then
GENERATION_STATE = 0
WW_instance:SaveAllChunks()
- WW_instance:UnloadUnusedChunks()
+ WW_instance:QueueUnloadUnusedChunks()
LOGERROR("" .. PLUGIN:GetName() .. " v" .. PLUGIN:GetVersion() .. ": works ABORTED by admin")
end
--Content = Content .. "<head><meta http-equiv=\"refresh\" content=\"2;\"></head>"
diff --git a/MCServer/Plugins/Core b/MCServer/Plugins/Core
-Subproject 5c8557d4fdfa580c100510cde07a1a778ea2e24
+Subproject 3b416b07a339b3abcbc127070d56eea05b05373
diff --git a/MCServer/Plugins/Debuggers/Debuggers.lua b/MCServer/Plugins/Debuggers/Debuggers.lua
index 60e84c947..66894d835 100644
--- a/MCServer/Plugins/Debuggers/Debuggers.lua
+++ b/MCServer/Plugins/Debuggers/Debuggers.lua
@@ -55,6 +55,7 @@ function Initialize(Plugin)
PM:BindCommand("/sched", "debuggers", HandleSched, "- Schedules a simple countdown using cWorld:ScheduleTask()");
PM:BindCommand("/cs", "debuggers", HandleChunkStay, "- Tests the ChunkStay Lua integration for the specified chunk coords");
PM:BindCommand("/compo", "debuggers", HandleCompo, "- Tests the cCompositeChat bindings")
+ PM:BindCommand("/sb", "debuggers", HandleSetBiome, "- Sets the biome around you to the specified one");
Plugin:AddWebTab("Debuggers", HandleRequest_Debuggers)
Plugin:AddWebTab("StressTest", HandleRequest_StressTest)
@@ -1218,3 +1219,42 @@ end
+
+function HandleSetBiome(a_Split, a_Player)
+ local Biome = biJungle
+ local Size = 20
+ local SplitSize = #a_Split
+ if (SplitSize > 3) then
+ a_Player:SendMessage("Too many parameters. Usage: " .. a_Split[1] .. " <BiomeType>")
+ return true
+ end
+
+ if (SplitSize >= 2) then
+ Biome = StringToBiome(a_Split[2])
+ if (Biome == biInvalidBiome) then
+ a_Player:SendMessage("Unknown biome: '" .. a_Split[2] .. "'. Command ignored.")
+ return true
+ end
+ end
+ if (SplitSize >= 3) then
+ Size = tostring(a_Split[3])
+ if (Size == nil) then
+ a_Player:SendMessage("Unknown size: '" .. a_Split[3] .. "'. Command ignored.")
+ return true
+ end
+ end
+
+ local BlockX = math.floor(a_Player:GetPosX())
+ local BlockZ = math.floor(a_Player:GetPosZ())
+ a_Player:GetWorld():SetAreaBiome(BlockX - Size, BlockX + Size, BlockZ - Size, BlockZ + Size, Biome)
+ a_Player:SendMessage(
+ "Blocks {" .. (BlockX - Size) .. ", " .. (BlockZ - Size) ..
+ "} - {" .. (BlockX + Size) .. ", " .. (BlockZ + Size) ..
+ "} set to biome #" .. tostring(Biome) .. "."
+ )
+ return true
+end
+
+
+
+
diff --git a/MCServer/Plugins/InfoReg.lua b/MCServer/Plugins/InfoReg.lua
new file mode 100644
index 000000000..3afb57488
--- /dev/null
+++ b/MCServer/Plugins/InfoReg.lua
@@ -0,0 +1,157 @@
+
+-- InfoReg.lua
+
+-- Implements registration functions that process g_PluginInfo
+
+
+
+
+
+--- Lists all the subcommands that the player has permissions for
+local function ListSubcommands(a_Player, a_Subcommands, a_CmdString)
+ a_Player:SendMessage("The " .. a_CmdString .. " command requires another verb:");
+ local Verbs = {};
+ for cmd, info in pairs(a_Subcommands) do
+ if (a_Player:HasPermission(info.Permission or "")) then
+ table.insert(Verbs, a_CmdString .. " " .. cmd);
+ end
+ end
+ table.sort(Verbs);
+ for idx, verb in ipairs(Verbs) do
+ a_Player:SendMessage(verb);
+ end
+end
+
+
+
+
+
+--- This is a generic command callback used for handling multicommands' parent commands
+-- For example, if there are "/gal save" and "/gal load" commands, this callback handles the "/gal" command
+local function MultiCommandHandler(a_Split, a_Player, a_CmdString, a_CmdInfo, a_Level)
+ local Verb = a_Split[a_Level + 1];
+ if (Verb == nil) then
+ -- No verb was specified. If there is a handler for the upper level command, call it:
+ if (a_CmdInfo.Handler ~= nil) then
+ return a_CmdInfo.Handler(a_Split, a_Player);
+ end
+ -- Let the player know they need to give a subcommand:
+ ListSubcommands(a_Player, a_CmdInfo.Subcommands, a_CmdString);
+ return true;
+ end
+
+ -- A verb was specified, look it up in the subcommands table:
+ local Subcommand = a_CmdInfo.Subcommands[Verb];
+ if (Subcommand == nil) then
+ if (a_Level > 1) then
+ -- This is a true subcommand, display the message and make MCS think the command was handled
+ -- Otherwise we get weird behavior: for "/cmd verb" we get "unknown command /cmd" although "/cmd" is valid
+ a_Player:SendMessage("The " .. a_CmdString .. " command doesn't support verb " .. Verb);
+ return true;
+ end
+ -- This is a top-level command, let MCS handle the unknown message
+ return false;
+ end
+
+ -- Check the permission:
+ if not(a_Player:HasPermission(Subcommand.Permission or "")) then
+ a_Player:SendMessage("You don't have permission to execute this command");
+ return true;
+ end
+
+ -- Check if the handler is valid:
+ if (Subcommand.Handler == nil) then
+ if (Subcommand.Subcommands == nil) then
+ LOG("Cannot find handler for command " .. a_CmdString .. " " .. Verb);
+ return false;
+ end
+ ListSubcommands(a_Player, Subcommand.Subcommands, a_CmdString .. " " .. Verb);
+ return true;
+ end
+
+ -- Execute:
+ return Subcommand.Handler(a_Split, a_Player);
+end
+
+
+
+
+
+--- Registers all commands specified in the g_PluginInfo.Commands
+function RegisterPluginInfoCommands()
+ -- A sub-function that registers all subcommands of a single command, using the command's Subcommands table
+ -- The a_Prefix param already contains the space after the previous command
+ -- a_Level is the depth of the subcommands being registered, with 1 being the top level command
+ local function RegisterSubcommands(a_Prefix, a_Subcommands, a_Level)
+ assert(a_Subcommands ~= nil);
+
+ for cmd, info in pairs(a_Subcommands) do
+ local CmdName = a_Prefix .. cmd;
+ local Handler = info.Handler;
+ -- Provide a special handler for multicommands:
+ if (info.Subcommands ~= nil) then
+ Handler = function(a_Split, a_Player)
+ return MultiCommandHandler(a_Split, a_Player, CmdName, info, a_Level);
+ end
+ end
+
+ if (Handler == nil) then
+ LOGWARNING(g_PluginInfo.Name .. ": Invalid handler for command " .. CmdName .. ", command will not be registered.");
+ else
+ local HelpString;
+ if (info.HelpString ~= nil) then
+ HelpString = " - " .. info.HelpString;
+ else
+ HelpString = "";
+ end
+ cPluginManager.BindCommand(CmdName, info.Permission or "", Handler, HelpString);
+ -- Register all aliases for the command:
+ if (info.Alias ~= nil) then
+ if (type(info.Alias) == "string") then
+ info.Alias = {info.Alias};
+ end
+ for idx, alias in ipairs(info.Alias) do
+ cPluginManager.BindCommand(a_Prefix .. alias, info.Permission or "", Handler, HelpString);
+ end
+ end
+ end
+
+ -- Recursively register any subcommands:
+ if (info.Subcommands ~= nil) then
+ RegisterSubcommands(a_Prefix .. cmd .. " ", info.Subcommands, a_Level + 1);
+ end
+ end
+ end
+
+ -- Loop through all commands in the plugin info, register each:
+ RegisterSubcommands("", g_PluginInfo.Commands, 1);
+end
+
+
+
+
+
+--- Registers all console commands specified in the g_PluginInfo.ConsoleCommands
+function RegisterPluginInfoConsoleCommands()
+ -- A sub-function that registers all subcommands of a single command, using the command's Subcommands table
+ -- The a_Prefix param already contains the space after the previous command
+ local function RegisterSubcommands(a_Prefix, a_Subcommands)
+ assert(a_Subcommands ~= nil);
+
+ for cmd, info in pairs(a_Subcommands) do
+ local CmdName = a_Prefix .. cmd;
+ cPluginManager.BindConsoleCommand(cmd, info.Handler, info.HelpString or "");
+ -- Recursively register any subcommands:
+ if (info.Subcommands ~= nil) then
+ RegisterSubcommands(a_Prefix .. cmd .. " ", info.Subcommands);
+ end
+ end
+ end
+
+ -- Loop through all commands in the plugin info, register each:
+ RegisterSubcommands("", g_PluginInfo.ConsoleCommands);
+end
+
+
+
+
diff --git a/Tools/ProtoProxy/.gitignore b/Tools/ProtoProxy/.gitignore
index 8def77d0b..158a30080 100644
--- a/Tools/ProtoProxy/.gitignore
+++ b/Tools/ProtoProxy/.gitignore
@@ -1,6 +1,7 @@
Debug
Release
Logs/
+lib/
*.log
*.nbt
*.sln
diff --git a/lib/inifile/iniFile.cpp b/lib/inifile/iniFile.cpp
index afa1c110d..cf8b63987 100644
--- a/lib/inifile/iniFile.cpp
+++ b/lib/inifile/iniFile.cpp
@@ -83,6 +83,8 @@ bool cIniFile::ReadFile(const AString & a_FileName, bool a_AllowExampleRedirect)
}
}
+ bool IsFirstLine = true;
+
while (getline(f, line))
{
// To be compatible with Win32, check for existence of '\r'.
@@ -90,6 +92,14 @@ bool cIniFile::ReadFile(const AString & a_FileName, bool a_AllowExampleRedirect)
// Note that the '\r' will be written to INI files from
// Unix so that the created INI file can be read under Win32
// without change.
+
+ // Removes UTF-8 Byte Order Markers (BOM) if, present.
+ if (IsFirstLine)
+ {
+ RemoveBom(line);
+ IsFirstLine = false;
+ }
+
size_t lineLength = line.length();
if (lineLength == 0)
{
@@ -162,11 +172,12 @@ bool cIniFile::ReadFile(const AString & a_FileName, bool a_AllowExampleRedirect)
{
return false;
}
-
+
if (IsFromExampleRedirect)
{
WriteFile(FILE_IO_PREFIX + a_FileName);
}
+
return true;
}
@@ -824,3 +835,28 @@ AString cIniFile::CheckCase(const AString & s) const
+
+void cIniFile::RemoveBom(AString & a_line) const
+{
+ // The BOM sequence for UTF-8 is 0xEF,0xBB,0xBF
+ static unsigned const char BOM[] = { 0xEF, 0xBB, 0xBF };
+
+ // The BOM sequence, if present, is always th e first three characters of the input.
+ const AString ref = a_line.substr(0, 3);
+
+ // If any of the first three chars do not match, return and do nothing.
+ for (int i = 0; i < 3; ++i)
+ {
+ if (static_cast<unsigned char>(ref[i]) != BOM[i])
+ {
+ return;
+ }
+ }
+
+ // First three characters match; erase them.
+ a_line.erase(0, 3);
+}
+
+
+
+
diff --git a/lib/inifile/iniFile.h b/lib/inifile/iniFile.h
index 40af618dc..0bf1d917e 100644
--- a/lib/inifile/iniFile.h
+++ b/lib/inifile/iniFile.h
@@ -51,6 +51,8 @@ private:
/// If the object is case-insensitive, returns s as lowercase; otherwise returns s as-is
AString CheckCase(const AString & s) const;
+ /// Removes the UTF-8 BOMs (Byte order makers), if present.
+ void RemoveBom(AString & a_line) const;
public:
enum errors
{
diff --git a/src/Bindings/AllToLua.pkg b/src/Bindings/AllToLua.pkg
index 6c295102f..1f08c66dc 100644
--- a/src/Bindings/AllToLua.pkg
+++ b/src/Bindings/AllToLua.pkg
@@ -34,6 +34,7 @@ $cfile "../Entities/Entity.h"
$cfile "../Entities/Floater.h"
$cfile "../Entities/Pawn.h"
$cfile "../Entities/Player.h"
+$cfile "../Entities/Painting.h"
$cfile "../Entities/Pickup.h"
$cfile "../Entities/ProjectileEntity.h"
$cfile "../Entities/TNTEntity.h"
diff --git a/src/Bindings/LuaState.cpp b/src/Bindings/LuaState.cpp
index c6be7be3c..ca7f6b255 100644
--- a/src/Bindings/LuaState.cpp
+++ b/src/Bindings/LuaState.cpp
@@ -173,6 +173,31 @@ void cLuaState::Detach(void)
+void cLuaState::AddPackagePath(const AString & a_PathVariable, const AString & a_Path)
+{
+ // Get the current path:
+ lua_getfield(m_LuaState, LUA_GLOBALSINDEX, "package"); // Stk: <package>
+ lua_getfield(m_LuaState, -1, a_PathVariable.c_str()); // Stk: <package> <package.path>
+ size_t len = 0;
+ const char * PackagePath = lua_tolstring(m_LuaState, -1, &len);
+
+ // Append the new path:
+ AString NewPackagePath(PackagePath, len);
+ NewPackagePath.append(LUA_PATHSEP);
+ NewPackagePath.append(a_Path);
+
+ // Set the new path to the environment:
+ lua_pop(m_LuaState, 1); // Stk: <package>
+ lua_pushlstring(m_LuaState, NewPackagePath.c_str(), NewPackagePath.length()); // Stk: <package> <NewPackagePath>
+ lua_setfield(m_LuaState, -2, a_PathVariable.c_str()); // Stk: <package>
+ lua_pop(m_LuaState, 1);
+ lua_pop(m_LuaState, 1); // Stk: -
+}
+
+
+
+
+
bool cLuaState::LoadFile(const AString & a_FileName)
{
ASSERT(IsValid());
@@ -1251,6 +1276,7 @@ void cLuaState::LogStack(lua_State * a_LuaState, const char * a_Header)
case LUA_TLIGHTUSERDATA: Printf(Value, "%p", lua_touserdata(a_LuaState, i)); break;
case LUA_TNUMBER: Printf(Value, "%f", (double)lua_tonumber(a_LuaState, i)); break;
case LUA_TSTRING: Printf(Value, "%s", lua_tostring(a_LuaState, i)); break;
+ case LUA_TTABLE: Printf(Value, "%p", lua_topointer(a_LuaState, i)); break;
default: break;
}
LOGD(" Idx %d: type %d (%s) %s", i, Type, lua_typename(a_LuaState, Type), Value.c_str());
diff --git a/src/Bindings/LuaState.h b/src/Bindings/LuaState.h
index b9bf10142..dcb660c3f 100644
--- a/src/Bindings/LuaState.h
+++ b/src/Bindings/LuaState.h
@@ -154,6 +154,9 @@ public:
/** Returns true if the m_LuaState is valid */
bool IsValid(void) const { return (m_LuaState != NULL); }
+ /** Adds the specified path to package.<a_PathVariable> */
+ void AddPackagePath(const AString & a_PathVariable, const AString & a_Path);
+
/** Loads the specified file
Returns false and logs a warning to the console if not successful (but the LuaState is kept open).
m_SubsystemName is displayed in the warning log message.
diff --git a/src/Bindings/PluginLua.cpp b/src/Bindings/PluginLua.cpp
index 773d522c4..45c8216be 100644
--- a/src/Bindings/PluginLua.cpp
+++ b/src/Bindings/PluginLua.cpp
@@ -82,6 +82,14 @@ bool cPluginLua::Initialize(void)
lua_pushstring(m_LuaState, GetName().c_str());
lua_setglobal(m_LuaState, LUA_PLUGIN_NAME_VAR_NAME);
+ // Add the plugin's folder to the package.path and package.cpath variables (#693):
+ m_LuaState.AddPackagePath("path", FILE_IO_PREFIX + GetLocalFolder() + "/?.lua");
+ #ifdef _WIN32
+ m_LuaState.AddPackagePath("cpath", GetLocalFolder() + "\\?.dll");
+ #else
+ m_LuaState.AddPackagePath("cpath", FILE_IO_PREFIX + GetLocalFolder() + "/?.so");
+ #endif
+
tolua_pushusertype(m_LuaState, this, "cPluginLua");
lua_setglobal(m_LuaState, "g_Plugin");
}
diff --git a/src/Bindings/PluginManager.cpp b/src/Bindings/PluginManager.cpp
index c6c8c081e..c7df6357e 100644
--- a/src/Bindings/PluginManager.cpp
+++ b/src/Bindings/PluginManager.cpp
@@ -56,7 +56,7 @@ void cPluginManager::ReloadPlugins(void)
void cPluginManager::FindPlugins(void)
{
- AString PluginsPath = FILE_IO_PREFIX + AString( "Plugins/" );
+ AString PluginsPath = GetPluginsPath() + "/";
// First get a clean list of only the currently running plugins, we don't want to mess those up
for (PluginMap::iterator itr = m_Plugins.begin(); itr != m_Plugins.end();)
diff --git a/src/Bindings/PluginManager.h b/src/Bindings/PluginManager.h
index c78bceda1..44bc5a8d7 100644
--- a/src/Bindings/PluginManager.h
+++ b/src/Bindings/PluginManager.h
@@ -266,6 +266,10 @@ public: // tolua_export
Returns false if plugin not found, and the value that the callback has returned otherwise. */
bool DoWithPlugin(const AString & a_PluginName, cPluginCallback & a_Callback);
+ /** Returns the path where individual plugins' folders are expected.
+ The path doesn't end in a slash. */
+ static AString GetPluginsPath(void) { return FILE_IO_PREFIX + AString("Plugins"); } // tolua_export
+
private:
friend class cRoot;
diff --git a/src/BlockID.h b/src/BlockID.h
index d5b3da672..740c5fc90 100644
--- a/src/BlockID.h
+++ b/src/BlockID.h
@@ -266,7 +266,7 @@ enum ENUM_ITEM_ID
E_ITEM_FLINT = 318,
E_ITEM_RAW_PORKCHOP = 319,
E_ITEM_COOKED_PORKCHOP = 320,
- E_ITEM_PAINTINGS = 321,
+ E_ITEM_PAINTING = 321,
E_ITEM_GOLDEN_APPLE = 322,
E_ITEM_SIGN = 323,
E_ITEM_WOODEN_DOOR = 324,
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index bfece3312..387556775 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -53,6 +53,7 @@ if (NOT MSVC)
Entities/Entity.h
Entities/Floater.h
Entities/Pawn.h
+ Entities/Painting.h
Entities/Pickup.h
Entities/Player.h
Entities/ProjectileEntity.h
diff --git a/src/Chunk.cpp b/src/Chunk.cpp
index 0e757be6e..4f301c209 100644
--- a/src/Chunk.cpp
+++ b/src/Chunk.cpp
@@ -1655,6 +1655,38 @@ void cChunk::UseBlockEntity(cPlayer * a_Player, int a_X, int a_Y, int a_Z)
+void cChunk::SetBiomeAt(int a_RelX, int a_RelZ, EMCSBiome a_Biome)
+{
+ cChunkDef::SetBiome(m_BiomeMap, a_RelX, a_RelZ, a_Biome);
+ MarkDirty();
+}
+
+
+
+
+
+void cChunk::SetAreaBiome(int a_MinRelX, int a_MaxRelX, int a_MinRelZ, int a_MaxRelZ, EMCSBiome a_Biome)
+{
+ for (int z = a_MinRelZ; z <= a_MaxRelZ; z++)
+ {
+ for (int x = a_MinRelX; x <= a_MaxRelX; x++)
+ {
+ cChunkDef::SetBiome(m_BiomeMap, x, z, a_Biome);
+ }
+ }
+ MarkDirty();
+
+ // Re-send the chunk to all clients:
+ for (cClientHandleList::iterator itr = m_LoadedByClient.begin(); itr != m_LoadedByClient.end(); ++itr)
+ {
+ m_World->ForceSendChunkTo(m_PosX, m_PosZ, (*itr));
+ } // for itr - m_LoadedByClient[]
+}
+
+
+
+
+
void cChunk::CollectPickupsByPlayer(cPlayer * a_Player)
{
double PosX = a_Player->GetPosX();
diff --git a/src/Chunk.h b/src/Chunk.h
index 6bec60813..1b7a6fa07 100644
--- a/src/Chunk.h
+++ b/src/Chunk.h
@@ -177,6 +177,14 @@ public:
EMCSBiome GetBiomeAt(int a_RelX, int a_RelZ) const {return cChunkDef::GetBiome(m_BiomeMap, a_RelX, a_RelZ); }
+ /** Sets the biome at the specified relative coords.
+ Doesn't resend the chunk to clients. */
+ void SetBiomeAt(int a_RelX, int a_RelZ, EMCSBiome a_Biome);
+
+ /** Sets the biome in the specified relative coords area. All the coords are inclusive.
+ Sends the chunk to all relevant clients. */
+ void SetAreaBiome(int a_MinRelX, int a_MaxRelX, int a_MinRelZ, int a_MaxRelZ, EMCSBiome a_Biome);
+
void CollectPickupsByPlayer(cPlayer * a_Player);
/** Sets the sign text. Returns true if successful. Also sends update packets to all clients in the chunk */
diff --git a/src/ChunkMap.cpp b/src/ChunkMap.cpp
index 0d19ecd28..fbb8706e0 100644
--- a/src/ChunkMap.cpp
+++ b/src/ChunkMap.cpp
@@ -1390,10 +1390,10 @@ void cChunkMap::ReplaceTreeBlocks(const sSetBlockVector & a_Blocks)
EMCSBiome cChunkMap::GetBiomeAt (int a_BlockX, int a_BlockZ)
{
int ChunkX, ChunkZ, X = a_BlockX, Y = 0, Z = a_BlockZ;
- cChunkDef::AbsoluteToRelative( X, Y, Z, ChunkX, ChunkZ );
+ cChunkDef::AbsoluteToRelative(X, Y, Z, ChunkX, ChunkZ);
cCSLock Lock(m_CSLayers);
- cChunkPtr Chunk = GetChunk( ChunkX, ZERO_CHUNK_Y, ChunkZ );
+ cChunkPtr Chunk = GetChunk(ChunkX, ZERO_CHUNK_Y, ChunkZ);
if ((Chunk != NULL) && Chunk->IsValid())
{
return Chunk->GetBiomeAt(X, Z);
@@ -1408,6 +1408,63 @@ EMCSBiome cChunkMap::GetBiomeAt (int a_BlockX, int a_BlockZ)
+bool cChunkMap::SetBiomeAt(int a_BlockX, int a_BlockZ, EMCSBiome a_Biome)
+{
+ int ChunkX, ChunkZ, X = a_BlockX, Y = 0, Z = a_BlockZ;
+ cChunkDef::AbsoluteToRelative(X, Y, Z, ChunkX, ChunkZ);
+
+ cCSLock Lock(m_CSLayers);
+ cChunkPtr Chunk = GetChunk(ChunkX, ZERO_CHUNK_Y, ChunkZ);
+ if ((Chunk != NULL) && Chunk->IsValid())
+ {
+ Chunk->SetBiomeAt(X, Z, a_Biome);
+ return true;
+ }
+ return false;
+}
+
+
+
+
+
+bool cChunkMap::SetAreaBiome(int a_MinX, int a_MaxX, int a_MinZ, int a_MaxZ, EMCSBiome a_Biome)
+{
+ // Translate coords to relative:
+ int Y = 0;
+ int MinChunkX, MinChunkZ, MinX = a_MinX, MinZ = a_MinZ;
+ int MaxChunkX, MaxChunkZ, MaxX = a_MaxX, MaxZ = a_MaxZ;
+ cChunkDef::AbsoluteToRelative(MinX, Y, MinZ, MinChunkX, MinChunkZ);
+ cChunkDef::AbsoluteToRelative(MaxX, Y, MaxZ, MaxChunkX, MaxChunkZ);
+
+ // Go through all chunks, set:
+ bool res = true;
+ cCSLock Lock(m_CSLayers);
+ for (int x = MinChunkX; x <= MaxChunkX; x++)
+ {
+ int MinRelX = (x == MinChunkX) ? MinX : 0;
+ int MaxRelX = (x == MaxChunkX) ? MaxX : cChunkDef::Width - 1;
+ for (int z = MinChunkZ; z <= MaxChunkZ; z++)
+ {
+ int MinRelZ = (z == MinChunkZ) ? MinZ : 0;
+ int MaxRelZ = (z == MaxChunkZ) ? MaxZ : cChunkDef::Width - 1;
+ cChunkPtr Chunk = GetChunkNoLoad(x, ZERO_CHUNK_Y, z);
+ if ((Chunk != NULL) && Chunk->IsValid())
+ {
+ Chunk->SetAreaBiome(MinRelX, MaxRelX, MinRelZ, MaxRelZ, a_Biome);
+ }
+ else
+ {
+ res = false;
+ }
+ } // for z
+ } // for x
+ return res;
+}
+
+
+
+
+
bool cChunkMap::GetBlocks(sSetBlockVector & a_Blocks, bool a_ContinueOnFailure)
{
bool res = true;
diff --git a/src/ChunkMap.h b/src/ChunkMap.h
index fb587a52e..9df68c403 100644
--- a/src/ChunkMap.h
+++ b/src/ChunkMap.h
@@ -161,8 +161,17 @@ public:
/** Special function used for growing trees, replaces only blocks that tree may overwrite */
void ReplaceTreeBlocks(const sSetBlockVector & a_Blocks);
+ /** Returns the biome at the specified coords. Reads the biome from the chunk, if loaded, otherwise uses the world generator to provide the biome value */
EMCSBiome GetBiomeAt (int a_BlockX, int a_BlockZ);
+ /** Sets the biome at the specified coords. Returns true if successful, false if not (chunk not loaded).
+ Doesn't resend the chunk to clients. */
+ bool SetBiomeAt(int a_BlockX, int a_BlockZ, EMCSBiome a_Biome);
+
+ /** Sets the biome at the area. Returns true if successful, false if any subarea failed (chunk not loaded).
+ (Re)sends the chunks to their relevant clients if successful. */
+ bool SetAreaBiome(int a_MinX, int a_MaxX, int a_MinZ, int a_MaxZ, EMCSBiome a_Biome);
+
/** Retrieves block types of the specified blocks. If a chunk is not loaded, doesn't modify the block. Returns true if all blocks were read. */
bool GetBlocks(sSetBlockVector & a_Blocks, bool a_ContinueOnFailure);
diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp
index c91a0c01b..84286fc41 100644
--- a/src/ClientHandle.cpp
+++ b/src/ClientHandle.cpp
@@ -2091,6 +2091,14 @@ void cClientHandle::SendPickupSpawn(const cPickup & a_Pickup)
+void cClientHandle::SendPaintingSpawn(const cPainting & a_Painting)
+{
+ m_Protocol->SendPaintingSpawn(a_Painting);
+}
+
+
+
+
void cClientHandle::SendEntityAnimation(const cEntity & a_Entity, char a_Animation)
{
diff --git a/src/ClientHandle.h b/src/ClientHandle.h
index 5faa94004..aefca7233 100644
--- a/src/ClientHandle.h
+++ b/src/ClientHandle.h
@@ -27,6 +27,7 @@ class cInventory;
class cMonster;
class cPawn;
class cExpOrb;
+class cPainting;
class cPickup;
class cPlayer;
class cProtocol;
@@ -111,6 +112,7 @@ public:
void SendGameMode (eGameMode a_GameMode);
void SendHealth (void);
void SendInventorySlot (char a_WindowID, short a_SlotNum, const cItem & a_Item);
+ void SendPaintingSpawn (const cPainting & a_Painting);
void SendPickupSpawn (const cPickup & a_Pickup);
void SendEntityAnimation (const cEntity & a_Entity, char a_Animation);
void SendParticleEffect (const AString & a_ParticleName, float a_SrcX, float a_SrcY, float a_SrcZ, float a_OffsetX, float a_OffsetY, float a_OffsetZ, float a_ParticleData, int a_ParticleAmmount);
diff --git a/src/Entities/Entity.h b/src/Entities/Entity.h
index b2edfc2b4..b3b1cef83 100644
--- a/src/Entities/Entity.h
+++ b/src/Entities/Entity.h
@@ -81,6 +81,8 @@ public:
etProjectile,
etExpOrb,
etFloater,
+ etItemFrame,
+ etPainting,
// Common variations
etMob = etMonster, // DEPRECATED, use etMonster instead!
@@ -139,6 +141,8 @@ public:
bool IsProjectile (void) const { return (m_EntityType == etProjectile); }
bool IsExpOrb (void) const { return (m_EntityType == etExpOrb); }
bool IsFloater (void) const { return (m_EntityType == etFloater); }
+ bool IsItemFrame (void) const { return (m_EntityType == etItemFrame); }
+ bool IsPainting (void) const { return (m_EntityType == etPainting); }
/// Returns true if the entity is of the specified class or a subclass (cPawn's IsA("cEntity") returns true)
virtual bool IsA(const char * a_ClassName) const;
diff --git a/src/Entities/ItemFrame.cpp b/src/Entities/ItemFrame.cpp
new file mode 100644
index 000000000..8cfa5e18d
--- /dev/null
+++ b/src/Entities/ItemFrame.cpp
@@ -0,0 +1,124 @@
+
+#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
+
+#include "ItemFrame.h"
+#include "ClientHandle.h"
+#include "Player.h"
+
+
+
+
+
+cItemFrame::cItemFrame(eBlockFace a_BlockFace, double a_X, double a_Y, double a_Z)
+ : cEntity(etItemFrame, a_X, a_Y, a_Z, 0.8, 0.8),
+ m_BlockFace(a_BlockFace),
+ m_Item(E_BLOCK_AIR),
+ m_Rotation(0)
+{
+ SetMaxHealth(1);
+ SetHealth(1);
+}
+
+
+
+
+
+void cItemFrame::SpawnOn(cClientHandle & a_ClientHandle)
+{
+ int Dir = 0;
+
+ // The client uses different values for item frame directions and block faces. Our constants are for the block faces, so we convert them here to item frame faces
+ switch (m_BlockFace)
+ {
+ case BLOCK_FACE_ZP: break; // Initialised to zero
+ case BLOCK_FACE_ZM: Dir = 2; break;
+ case BLOCK_FACE_XM: Dir = 1; break;
+ case BLOCK_FACE_XP: Dir = 3; break;
+ default: ASSERT(!"Unhandled block face when trying to spawn item frame!"); return;
+ }
+
+ if ((Dir == 0) || (Dir == 2)) // Probably a client bug, but two directions are flipped and contrary to the norm, so we do -180
+ {
+ SetYaw((Dir * 90) - 180);
+ }
+ else
+ {
+ SetYaw(Dir * 90);
+ }
+
+ a_ClientHandle.SendSpawnObject(*this, 71, Dir, (Byte)GetYaw(), (Byte)GetPitch());
+}
+
+
+
+
+
+void cItemFrame::OnRightClicked(cPlayer & a_Player)
+{
+ if (!m_Item.IsEmpty())
+ {
+ // Item not empty, rotate, clipping values to zero to three inclusive
+ m_Rotation++;
+ if (m_Rotation >= 4)
+ {
+ m_Rotation = 0;
+ }
+ }
+ else if (!a_Player.GetEquippedItem().IsEmpty())
+ {
+ // Item empty, and player held item not empty - add this item to self
+ m_Item = a_Player.GetEquippedItem();
+ m_Item.m_ItemCount = 1;
+
+ if (!a_Player.IsGameModeCreative())
+ {
+ a_Player.GetInventory().RemoveOneEquippedItem();
+ }
+ }
+
+ GetWorld()->BroadcastEntityMetadata(*this); // Update clients
+}
+
+
+
+
+
+
+void cItemFrame::KilledBy(cEntity * a_Killer)
+{
+ if (m_Item.IsEmpty())
+ {
+ super::KilledBy(a_Killer);
+ Destroy();
+ return;
+ }
+
+ if ((a_Killer != NULL) && a_Killer->IsPlayer() && !((cPlayer *)a_Killer)->IsGameModeCreative())
+ {
+ cItems Item;
+ Item.push_back(m_Item);
+
+ GetWorld()->SpawnItemPickups(Item, GetPosX(), GetPosY(), GetPosZ());
+ }
+
+ SetHealth(GetMaxHealth());
+ m_Item.Clear();
+ m_Rotation = 0;
+ GetWorld()->BroadcastEntityMetadata(*this);
+}
+
+
+
+
+
+void cItemFrame::GetDrops(cItems & a_Items, cEntity * a_Killer)
+{
+ if ((a_Killer != NULL) && a_Killer->IsPlayer() && !((cPlayer *)a_Killer)->IsGameModeCreative())
+ {
+ a_Items.push_back(cItem(E_ITEM_ITEM_FRAME));
+ }
+}
+
+
+
+
diff --git a/src/Entities/ItemFrame.h b/src/Entities/ItemFrame.h
new file mode 100644
index 000000000..43915e3f9
--- /dev/null
+++ b/src/Entities/ItemFrame.h
@@ -0,0 +1,42 @@
+
+#pragma once
+
+#include "Entity.h"
+
+
+
+
+
+// tolua_begin
+class cItemFrame :
+ public cEntity
+{
+ // tolua_end
+ typedef cEntity super;
+
+public:
+
+ CLASS_PROTODEF(cItemFrame);
+
+ cItemFrame(eBlockFace a_BlockFace, double a_X, double a_Y, double a_Z);
+
+ const cItem & GetItem(void) { return m_Item; }
+ Byte GetRotation(void) const { return m_Rotation; }
+
+private:
+
+ virtual void SpawnOn(cClientHandle & a_ClientHandle) override;
+ virtual void OnRightClicked(cPlayer & a_Player) override;
+ virtual void Tick(float a_Dt, cChunk & a_Chunk) override {};
+ virtual void KilledBy(cEntity * a_Killer) override;
+ virtual void GetDrops(cItems & a_Items, cEntity * a_Killer) override;
+
+ eBlockFace m_BlockFace;
+ cItem m_Item;
+ Byte m_Rotation;
+
+}; // tolua_export
+
+
+
+
diff --git a/src/Entities/Painting.cpp b/src/Entities/Painting.cpp
new file mode 100644
index 000000000..b98c1e67a
--- /dev/null
+++ b/src/Entities/Painting.cpp
@@ -0,0 +1,43 @@
+
+#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
+
+#include "Painting.h"
+#include "ClientHandle.h"
+#include "Player.h"
+
+
+
+
+
+cPainting::cPainting(const AString & a_Name, int a_Direction, double a_X, double a_Y, double a_Z)
+ : cEntity(etPainting, a_X, a_Y, a_Z, 1, 1),
+ m_Name(a_Name),
+ m_Direction(a_Direction)
+{
+}
+
+
+
+
+
+
+void cPainting::SpawnOn(cClientHandle & a_Client)
+{
+ a_Client.SendPaintingSpawn(*this);
+}
+
+
+
+
+
+void cPainting::GetDrops(cItems & a_Items, cEntity * a_Killer)
+{
+ if ((a_Killer != NULL) && a_Killer->IsPlayer() && !((cPlayer *)a_Killer)->IsGameModeCreative())
+ {
+ a_Items.push_back(cItem(E_ITEM_PAINTING));
+ }
+}
+
+
+
+
diff --git a/src/Entities/Painting.h b/src/Entities/Painting.h
new file mode 100644
index 000000000..95afbed1e
--- /dev/null
+++ b/src/Entities/Painting.h
@@ -0,0 +1,42 @@
+
+#pragma once
+
+#include "Entity.h"
+
+
+
+
+
+// tolua_begin
+class cPainting :
+ public cEntity
+{
+ // tolua_end
+ typedef cEntity super;
+
+public:
+ CLASS_PROTODEF(cPainting);
+
+ cPainting(const AString & a_Name, int a_Direction, double a_X, double a_Y, double a_Z);
+ const AString & GetName(void) const { return m_Name; } // tolua_export
+ int GetDirection(void) const { return m_Direction; } // tolua_export
+
+private:
+
+ virtual void SpawnOn(cClientHandle & a_Client) override;
+ virtual void Tick(float a_Dt, cChunk & a_Chunk) override {};
+ virtual void GetDrops(cItems & a_Items, cEntity * a_Killer) override;
+ virtual void KilledBy(cEntity * a_Killer) override
+ {
+ super::KilledBy(a_Killer);
+ Destroy();
+ }
+
+ AString m_Name;
+ int m_Direction;
+
+}; // tolua_export
+
+
+
+
diff --git a/src/ForEachChunkProvider.h b/src/ForEachChunkProvider.h
index 70cd2196a..6017173ee 100644
--- a/src/ForEachChunkProvider.h
+++ b/src/ForEachChunkProvider.h
@@ -1,14 +1,39 @@
+// ForEachChunkProvider.h
+
+// Declares the cForEachChunkProvider class which acts as an interface for classes that provide a ForEachChunkInRect() function
+// Primarily serves as a decoupling between cBlockArea and cWorld
+
+
+
+
+
#pragma once
-class cChunkDataCallback;
+
+
+// fwd:
+class cChunkDataCallback;
class cBlockArea;
+
+
+
+
class cForEachChunkProvider
{
public:
+ /** Calls the callback for each chunk in the specified range. */
virtual bool ForEachChunkInRect(int a_MinChunkX, int a_MaxChunkX, int a_MinChunkZ, int a_MaxChunkZ, cChunkDataCallback & a_Callback) = 0;
+ /** Writes the block area into the specified coords.
+ Returns true if all chunks have been processed.
+ a_DataTypes is a bitmask of cBlockArea::baXXX constants ORed together.
+ */
virtual bool WriteBlockArea(cBlockArea & a_Area, int a_MinBlockX, int a_MinBlockY, int a_MinBlockZ, int a_DataTypes) = 0;
};
+
+
+
+
diff --git a/src/Items/ItemHandler.cpp b/src/Items/ItemHandler.cpp
index 3c3d98858..e9bb616a6 100644
--- a/src/Items/ItemHandler.cpp
+++ b/src/Items/ItemHandler.cpp
@@ -21,11 +21,13 @@
#include "ItemFishingRod.h"
#include "ItemFlowerPot.h"
#include "ItemFood.h"
+#include "ItemItemFrame.h"
#include "ItemHoe.h"
#include "ItemLeaves.h"
#include "ItemLighter.h"
#include "ItemMinecart.h"
#include "ItemNetherWart.h"
+#include "ItemPainting.h"
#include "ItemPickaxe.h"
#include "ItemThrowable.h"
#include "ItemRedstoneDust.h"
@@ -106,7 +108,9 @@ cItemHandler *cItemHandler::CreateItemHandler(int a_ItemType)
case E_ITEM_FISHING_ROD: return new cItemFishingRodHandler(a_ItemType);
case E_ITEM_FLINT_AND_STEEL: return new cItemLighterHandler(a_ItemType);
case E_ITEM_FLOWER_POT: return new cItemFlowerPotHandler(a_ItemType);
+ case E_ITEM_ITEM_FRAME: return new cItemItemFrameHandler(a_ItemType);
case E_ITEM_NETHER_WART: return new cItemNetherWartHandler(a_ItemType);
+ case E_ITEM_PAINTING: return new cItemPaintingHandler(a_ItemType);
case E_ITEM_REDSTONE_DUST: return new cItemRedstoneDustHandler(a_ItemType);
case E_ITEM_REDSTONE_REPEATER: return new cItemRedstoneRepeaterHandler(a_ItemType);
case E_ITEM_SHEARS: return new cItemShearsHandler(a_ItemType);
@@ -307,7 +311,7 @@ char cItemHandler::GetMaxStackSize(void)
case E_ITEM_BOWL: return 64;
case E_ITEM_BREAD: return 64;
case E_ITEM_BREWING_STAND: return 64;
- case E_ITEM_BUCKET: return 1; // TODO: change this to 16 when turning compatibility to 1.3
+ case E_ITEM_BUCKET: return 16;
case E_ITEM_CARROT: return 64;
case E_ITEM_CAULDRON: return 64;
case E_ITEM_CLAY: return 64;
@@ -344,6 +348,7 @@ char cItemHandler::GetMaxStackSize(void)
case E_ITEM_GUNPOWDER: return 64;
case E_ITEM_HEAD: return 64;
case E_ITEM_IRON: return 64;
+ case E_ITEM_ITEM_FRAME: return 64;
case E_ITEM_LEATHER: return 64;
case E_ITEM_MAGMA_CREAM: return 64;
case E_ITEM_MAP: return 64;
@@ -351,7 +356,7 @@ char cItemHandler::GetMaxStackSize(void)
case E_ITEM_MELON_SLICE: return 64;
case E_ITEM_NETHER_BRICK: return 64;
case E_ITEM_NETHER_WART: return 64;
- case E_ITEM_PAINTINGS: return 64;
+ case E_ITEM_PAINTING: return 64;
case E_ITEM_PAPER: return 64;
case E_ITEM_POISONOUS_POTATO: return 64;
case E_ITEM_POTATO: return 64;
diff --git a/src/Items/ItemItemFrame.h b/src/Items/ItemItemFrame.h
new file mode 100644
index 000000000..74e987445
--- /dev/null
+++ b/src/Items/ItemItemFrame.h
@@ -0,0 +1,53 @@
+
+#pragma once
+
+#include "ItemHandler.h"
+#include "Entities/ItemFrame.h"
+#include "../Entities/Player.h"
+
+
+
+
+
+class cItemItemFrameHandler :
+ public cItemHandler
+{
+public:
+ cItemItemFrameHandler(int a_ItemType)
+ : cItemHandler(a_ItemType)
+ {
+
+ }
+
+ virtual bool OnItemUse(cWorld *a_World, cPlayer *a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_Dir) override
+ {
+ if ((a_Dir == BLOCK_FACE_NONE) || (a_Dir == BLOCK_FACE_YP) || (a_Dir == BLOCK_FACE_YM))
+ {
+ // Client sends this if clicked on top or bottom face
+ return false;
+ }
+
+ AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_Dir); // Make sure block that will be occupied is free
+ BLOCKTYPE Block = a_World->GetBlock(a_BlockX, a_BlockY, a_BlockZ);
+ AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_Dir, true); // We want the clicked block, so go back again
+
+ if (Block == E_BLOCK_AIR)
+ {
+ cItemFrame * ItemFrame = new cItemFrame(a_Dir, a_BlockX, a_BlockY, a_BlockZ);
+ ItemFrame->Initialize(a_World);
+
+ if (!a_Player->IsGameModeCreative())
+ {
+ a_Player->GetInventory().RemoveOneEquippedItem();
+ }
+
+ return true;
+
+ }
+ return false;
+ }
+};
+
+
+
+
diff --git a/src/Items/ItemPainting.h b/src/Items/ItemPainting.h
new file mode 100644
index 000000000..b85098221
--- /dev/null
+++ b/src/Items/ItemPainting.h
@@ -0,0 +1,98 @@
+
+#pragma once
+
+#include "ItemHandler.h"
+#include "../World.h"
+#include "../Entities/Player.h"
+#include "../Entities/Painting.h"
+
+
+
+
+
+class cItemPaintingHandler :
+ public cItemHandler
+{
+public:
+ cItemPaintingHandler(int a_ItemType)
+ : cItemHandler(a_ItemType)
+ {
+ }
+
+ virtual bool OnItemUse(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_Dir) override
+ {
+ if (a_Dir == BLOCK_FACE_NONE)
+ {
+ // Client sends this if clicked on top or bottom face
+ return false;
+ }
+
+ AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_Dir); // Make sure block that will be occupied is free
+ BLOCKTYPE Block = a_World->GetBlock(a_BlockX, a_BlockY, a_BlockZ);
+ AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_Dir, true); // We want the clicked block, so go back again
+
+ if (Block == E_BLOCK_AIR)
+ {
+ int Dir = 0;
+
+ // The client uses different values for painting directions and block faces. Our constants are for the block faces, so we convert them here to painting faces
+ switch (a_Dir)
+ {
+ case BLOCK_FACE_ZP: break; // Initialised to zero
+ case BLOCK_FACE_ZM: Dir = 2; break;
+ case BLOCK_FACE_XM: Dir = 1; break;
+ case BLOCK_FACE_XP: Dir = 3; break;
+ default: ASSERT(!"Unhandled block face when trying spawn painting!"); return false;
+ }
+
+ static const struct // Define all the possible painting titles
+ {
+ AString Title;
+ } gPaintingTitlesList[] =
+ {
+ { "Kebab" },
+ { "Aztec" },
+ { "Alban" },
+ { "Aztec2" },
+ { "Bomb" },
+ { "Plant" },
+ { "Wasteland" },
+ { "Wanderer" },
+ { "Graham" },
+ { "Pool" },
+ { "Courbet" },
+ { "Sunset" },
+ { "Sea" },
+ { "Creebet" },
+ { "Match" },
+ { "Bust" },
+ { "Stage" },
+ { "Void" },
+ { "SkullAndRoses" },
+ { "Wither" },
+ { "Fighters" },
+ { "Skeleton" },
+ { "DonkeyKong" },
+ { "Pointer" },
+ { "Pigscene" },
+ { "BurningSkull" }
+ };
+
+ cPainting * Painting = new cPainting(gPaintingTitlesList[a_World->GetTickRandomNumber(ARRAYCOUNT(gPaintingTitlesList) - 1)].Title, Dir, a_BlockX, a_BlockY, a_BlockZ);
+ Painting->Initialize(a_World);
+
+ if (!a_Player->IsGameModeCreative())
+ {
+ a_Player->GetInventory().RemoveOneEquippedItem();
+ }
+
+ return true;
+
+ }
+ return false;
+ }
+};
+
+
+
+
diff --git a/src/Mobs/Monster.cpp b/src/Mobs/Monster.cpp
index 9817901c9..b5cf693cb 100644
--- a/src/Mobs/Monster.cpp
+++ b/src/Mobs/Monster.cpp
@@ -142,11 +142,11 @@ void cMonster::TickPathFinding()
BLOCKTYPE BlockAtYPP = m_World->GetBlock(gCrossCoords[i].x + PosX, PosY + 2, gCrossCoords[i].z + PosZ);
BLOCKTYPE BlockAtYM = m_World->GetBlock(gCrossCoords[i].x + PosX, PosY - 1, gCrossCoords[i].z + PosZ);
- if (!g_BlockIsSolid[BlockAtY] && !g_BlockIsSolid[BlockAtYP] && !IsBlockLava(BlockAtYM))
+ if ((!g_BlockIsSolid[BlockAtY]) && (!g_BlockIsSolid[BlockAtYP]) && (!IsBlockLava(BlockAtYM)) && (BlockAtY != E_BLOCK_FENCE) && (BlockAtY != E_BLOCK_FENCE_GATE))
{
m_PotentialCoordinates.push_back(Vector3d((gCrossCoords[i].x + PosX), PosY, gCrossCoords[i].z + PosZ));
}
- else if (g_BlockIsSolid[BlockAtY] && !g_BlockIsSolid[BlockAtYP] && !g_BlockIsSolid[BlockAtYPP] && !IsBlockLava(BlockAtYM))
+ else if ((g_BlockIsSolid[BlockAtY]) && (!g_BlockIsSolid[BlockAtYP]) && (!g_BlockIsSolid[BlockAtYPP]) && (!IsBlockLava(BlockAtYM)) && (BlockAtY != E_BLOCK_FENCE) && (BlockAtY != E_BLOCK_FENCE_GATE))
{
m_PotentialCoordinates.push_back(Vector3d((gCrossCoords[i].x + PosX), PosY + 1, gCrossCoords[i].z + PosZ));
}
diff --git a/src/Protocol/Protocol.h b/src/Protocol/Protocol.h
index f5b9fd403..46b627254 100644
--- a/src/Protocol/Protocol.h
+++ b/src/Protocol/Protocol.h
@@ -24,6 +24,7 @@ class cWindow;
class cInventory;
class cPawn;
class cPickup;
+class cPainting;
class cWorld;
class cMonster;
class cChunkDataSerializer;
@@ -81,6 +82,7 @@ public:
virtual void SendInventorySlot (char a_WindowID, short a_SlotNum, const cItem & a_Item) = 0;
virtual void SendKeepAlive (int a_PingID) = 0;
virtual void SendLogin (const cPlayer & a_Player, const cWorld & a_World) = 0;
+ virtual void SendPaintingSpawn (const cPainting & a_Painting) = 0;
virtual void SendPickupSpawn (const cPickup & a_Pickup) = 0;
virtual void SendPlayerAbilities (void) = 0;
virtual void SendEntityAnimation (const cEntity & a_Entity, char a_Animation) = 0;
diff --git a/src/Protocol/Protocol125.h b/src/Protocol/Protocol125.h
index 1a3209333..54551ea5f 100644
--- a/src/Protocol/Protocol125.h
+++ b/src/Protocol/Protocol125.h
@@ -56,6 +56,7 @@ public:
virtual void SendKeepAlive (int a_PingID) override;
virtual void SendLogin (const cPlayer & a_Player, const cWorld & a_World) override;
virtual void SendParticleEffect (const AString & a_ParticleName, float a_SrcX, float a_SrcY, float a_SrcZ, float a_OffsetX, float a_OffsetY, float a_OffsetZ, float a_ParticleData, int a_ParticleAmmount) override;
+ virtual void SendPaintingSpawn (const cPainting & a_Painting) override {};
virtual void SendPickupSpawn (const cPickup & a_Pickup) override;
virtual void SendPlayerAbilities (void) override {} // This protocol doesn't support such message
virtual void SendEntityAnimation (const cEntity & a_Entity, char a_Animation) override;
diff --git a/src/Protocol/Protocol17x.cpp b/src/Protocol/Protocol17x.cpp
index 344dcc676..aaf8830cd 100644
--- a/src/Protocol/Protocol17x.cpp
+++ b/src/Protocol/Protocol17x.cpp
@@ -21,8 +21,10 @@ Implements the 1.7.x protocol classes:
#include "../Entities/ExpOrb.h"
#include "../Entities/Minecart.h"
#include "../Entities/FallingBlock.h"
+#include "../Entities/Painting.h"
#include "../Entities/Pickup.h"
#include "../Entities/Player.h"
+#include "../Entities/ItemFrame.h"
#include "../Mobs/IncludeAllMonsters.h"
#include "../UI/Window.h"
#include "../BlockEntities/CommandBlockEntity.h"
@@ -570,6 +572,20 @@ void cProtocol172::SendLogin(const cPlayer & a_Player, const cWorld & a_World)
+void cProtocol172::SendPaintingSpawn(const cPainting & a_Painting)
+{
+ cPacketizer Pkt(*this, 0x10); // Spawn Painting packet
+ Pkt.WriteVarInt(a_Painting.GetUniqueID());
+ Pkt.WriteString(a_Painting.GetName().c_str());
+ Pkt.WriteInt((int)a_Painting.GetPosX());
+ Pkt.WriteInt((int)a_Painting.GetPosY());
+ Pkt.WriteInt((int)a_Painting.GetPosZ());
+ Pkt.WriteInt(a_Painting.GetDirection());
+}
+
+
+
+
void cProtocol172::SendPickupSpawn(const cPickup & a_Pickup)
{
@@ -924,8 +940,8 @@ void cProtocol172::SendSpawnObject(const cEntity & a_Entity, char a_ObjectType,
Pkt.WriteFPInt(a_Entity.GetPosX());
Pkt.WriteFPInt(a_Entity.GetPosY());
Pkt.WriteFPInt(a_Entity.GetPosZ());
- Pkt.WriteByteAngle(a_Entity.GetYaw());
Pkt.WriteByteAngle(a_Entity.GetPitch());
+ Pkt.WriteByteAngle(a_Entity.GetYaw());
Pkt.WriteInt(a_ObjectData);
if (a_ObjectData != 0)
{
@@ -947,8 +963,8 @@ void cProtocol172::SendSpawnVehicle(const cEntity & a_Vehicle, char a_VehicleTyp
Pkt.WriteFPInt(a_Vehicle.GetPosX());
Pkt.WriteFPInt(a_Vehicle.GetPosY());
Pkt.WriteFPInt(a_Vehicle.GetPosZ());
- Pkt.WriteByteAngle(a_Vehicle.GetYaw());
Pkt.WriteByteAngle(a_Vehicle.GetPitch());
+ Pkt.WriteByteAngle(a_Vehicle.GetYaw());
Pkt.WriteInt(a_VehicleSubType);
if (a_VehicleSubType != 0)
{
@@ -2406,6 +2422,15 @@ void cProtocol172::cPacketizer::WriteEntityMetadata(const cEntity & a_Entity)
WriteMobMetadata((const cMonster &)a_Entity);
break;
}
+ case cEntity::etItemFrame:
+ {
+ cItemFrame & Frame = (cItemFrame &)a_Entity;
+ WriteByte(0xA2);
+ WriteItem(Frame.GetItem());
+ WriteByte(0x3);
+ WriteByte(Frame.GetRotation());
+ break;
+ }
}
}
diff --git a/src/Protocol/Protocol17x.h b/src/Protocol/Protocol17x.h
index d19be0f05..ae3577867 100644
--- a/src/Protocol/Protocol17x.h
+++ b/src/Protocol/Protocol17x.h
@@ -87,6 +87,7 @@ public:
virtual void SendInventorySlot (char a_WindowID, short a_SlotNum, const cItem & a_Item) override;
virtual void SendKeepAlive (int a_PingID) override;
virtual void SendLogin (const cPlayer & a_Player, const cWorld & a_World) override;
+ virtual void SendPaintingSpawn (const cPainting & a_Painting) override;
virtual void SendPickupSpawn (const cPickup & a_Pickup) override;
virtual void SendPlayerAbilities (void) override;
virtual void SendEntityAnimation (const cEntity & a_Entity, char a_Animation) override;
diff --git a/src/Protocol/ProtocolRecognizer.cpp b/src/Protocol/ProtocolRecognizer.cpp
index 6e51ee9cd..b658dc9db 100644
--- a/src/Protocol/ProtocolRecognizer.cpp
+++ b/src/Protocol/ProtocolRecognizer.cpp
@@ -405,6 +405,14 @@ void cProtocolRecognizer::SendParticleEffect(const AString & a_ParticleName, flo
+void cProtocolRecognizer::SendPaintingSpawn(const cPainting & a_Painting)
+{
+ m_Protocol->SendPaintingSpawn(a_Painting);
+}
+
+
+
+
void cProtocolRecognizer::SendPickupSpawn(const cPickup & a_Pickup)
{
diff --git a/src/Protocol/ProtocolRecognizer.h b/src/Protocol/ProtocolRecognizer.h
index 800163be6..abbb22827 100644
--- a/src/Protocol/ProtocolRecognizer.h
+++ b/src/Protocol/ProtocolRecognizer.h
@@ -91,6 +91,7 @@ public:
virtual void SendKeepAlive (int a_PingID) override;
virtual void SendLogin (const cPlayer & a_Player, const cWorld & a_World) override;
virtual void SendParticleEffect (const AString & a_ParticleName, float a_SrcX, float a_SrcY, float a_SrcZ, float a_OffsetX, float a_OffsetY, float a_OffsetZ, float a_ParticleData, int a_ParticleAmmount) override;
+ virtual void SendPaintingSpawn (const cPainting & a_Painting) override;
virtual void SendPickupSpawn (const cPickup & a_Pickup) override;
virtual void SendPlayerAbilities (void) override;
virtual void SendEntityAnimation (const cEntity & a_Entity, char a_Animation) override;
diff --git a/src/World.cpp b/src/World.cpp
index c0a621a2c..7d8bdd95f 100644
--- a/src/World.cpp
+++ b/src/World.cpp
@@ -1496,6 +1496,37 @@ EMCSBiome cWorld::GetBiomeAt (int a_BlockX, int a_BlockZ)
+bool cWorld::SetBiomeAt(int a_BlockX, int a_BlockZ, EMCSBiome a_Biome)
+{
+ return m_ChunkMap->SetBiomeAt(a_BlockX, a_BlockZ, a_Biome);
+}
+
+
+
+
+
+bool cWorld::SetAreaBiome(int a_MinX, int a_MaxX, int a_MinZ, int a_MaxZ, EMCSBiome a_Biome)
+{
+ return m_ChunkMap->SetAreaBiome(a_MinX, a_MaxX, a_MinZ, a_MaxZ, a_Biome);
+}
+
+
+
+
+
+bool cWorld::SetAreaBiome(const cCuboid & a_Area, EMCSBiome a_Biome)
+{
+ return SetAreaBiome(
+ std::min(a_Area.p1.x, a_Area.p2.x), std::max(a_Area.p1.x, a_Area.p2.x),
+ std::min(a_Area.p1.z, a_Area.p2.z), std::max(a_Area.p1.z, a_Area.p2.z),
+ a_Biome
+ );
+}
+
+
+
+
+
void cWorld::SetBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta)
{
m_ChunkMap->SetBlock(*this, a_BlockX, a_BlockY, a_BlockZ, a_BlockType, a_BlockMeta);
@@ -2499,6 +2530,16 @@ void cWorld::SendChunkTo(int a_ChunkX, int a_ChunkZ, cClientHandle * a_Client)
+void cWorld::ForceSendChunkTo(int a_ChunkX, int a_ChunkZ, cClientHandle * a_Client)
+{
+ a_Client->AddWantedChunk(a_ChunkX, a_ChunkZ);
+ m_ChunkSender.QueueSendChunkTo(a_ChunkX, a_ChunkZ, a_Client);
+}
+
+
+
+
+
void cWorld::RemoveClientFromChunkSender(cClientHandle * a_Client)
{
m_ChunkSender.RemoveClient(a_Client);
diff --git a/src/World.h b/src/World.h
index 9c7079a92..5c18c5d23 100644
--- a/src/World.h
+++ b/src/World.h
@@ -48,6 +48,7 @@ class cNoteEntity;
class cMobHeadEntity;
class cMobCensus;
class cCompositeChat;
+class cCuboid;
typedef std::list< cPlayer * > cPlayerList;
@@ -66,7 +67,10 @@ typedef cItemCallback<cMobHeadEntity> cMobHeadBlockCallback;
// tolua_begin
-class cWorld : public cForEachChunkProvider, public cWorldInterface, public cBroadcastInterface
+class cWorld :
+ public cForEachChunkProvider,
+ public cWorldInterface,
+ public cBroadcastInterface
{
public:
@@ -305,9 +309,14 @@ public:
/** Removes the client from all chunks it is present in */
void RemoveClientFromChunks(cClientHandle * a_Client);
- /** Sends the chunk to the client specified, if the chunk is valid. If not valid, the request is postponed (ChunkSender will send that chunk when it becomes valid+lighted) */
+ /** Sends the chunk to the client specified, if the client doesn't have the chunk yet.
+ If chunk not valid, the request is postponed (ChunkSender will send that chunk when it becomes valid + lighted). */
void SendChunkTo(int a_ChunkX, int a_ChunkZ, cClientHandle * a_Client);
+ /** Sends the chunk to the client specified, even if the client already has the chunk.
+ If the chunk's not valid, the request is postponed (ChunkSender will send that chunk when it becomes valid + lighted). */
+ void ForceSendChunkTo(int a_ChunkX, int a_ChunkZ, cClientHandle * a_Client);
+
/** Removes client from ChunkSender's queue of chunks to be sent */
void RemoveClientFromChunkSender(cClientHandle * a_Client);
@@ -403,15 +412,15 @@ public:
Prefer cBlockArea::Write() instead, this is the internal implementation; cBlockArea does error checking, too.
a_DataTypes is a bitmask of cBlockArea::baXXX constants ORed together.
*/
- virtual bool WriteBlockArea(cBlockArea & a_Area, int a_MinBlockX, int a_MinBlockY, int a_MinBlockZ, int a_DataTypes);
+ virtual bool WriteBlockArea(cBlockArea & a_Area, int a_MinBlockX, int a_MinBlockY, int a_MinBlockZ, int a_DataTypes) override;
// tolua_begin
/** Spawns item pickups for each item in the list. May compress pickups if too many entities: */
- virtual void SpawnItemPickups(const cItems & a_Pickups, double a_BlockX, double a_BlockY, double a_BlockZ, double a_FlyAwaySpeed = 1.0, bool IsPlayerCreated = false);
+ virtual void SpawnItemPickups(const cItems & a_Pickups, double a_BlockX, double a_BlockY, double a_BlockZ, double a_FlyAwaySpeed = 1.0, bool IsPlayerCreated = false); // override; cannot specify it here due to tolua
/** Spawns item pickups for each item in the list. May compress pickups if too many entities. All pickups get the speed specified: */
- virtual void SpawnItemPickups(const cItems & a_Pickups, double a_BlockX, double a_BlockY, double a_BlockZ, double a_SpeedX, double a_SpeedY, double a_SpeedZ, bool IsPlayerCreated = false);
+ virtual void SpawnItemPickups(const cItems & a_Pickups, double a_BlockX, double a_BlockY, double a_BlockZ, double a_SpeedX, double a_SpeedY, double a_SpeedZ, bool IsPlayerCreated = false); // override; cannot specify it here due to tolua
/** Spawns an falling block entity at the given position. It returns the UniqueID of the spawned falling block. */
int SpawnFallingBlock(int a_X, int a_Y, int a_Z, BLOCKTYPE BlockType, NIBBLETYPE BlockMeta);
@@ -551,6 +560,19 @@ public:
/** Returns the biome at the specified coords. Reads the biome from the chunk, if loaded, otherwise uses the world generator to provide the biome value */
EMCSBiome GetBiomeAt(int a_BlockX, int a_BlockZ);
+
+ /** Sets the biome at the specified coords. Returns true if successful, false if not (chunk not loaded).
+ Doesn't resend the chunk to clients, use ForceSendChunkTo() for that. */
+ bool SetBiomeAt(int a_BlockX, int a_BlockZ, EMCSBiome a_Biome);
+
+ /** Sets the biome at the area. Returns true if successful, false if any subarea failed (chunk not loaded).
+ (Re)sends the chunks to their relevant clients if successful. */
+ bool SetAreaBiome(int a_MinX, int a_MaxX, int a_MinZ, int a_MaxZ, EMCSBiome a_Biome);
+
+ /** Sets the biome at the area. Returns true if successful, false if any subarea failed (chunk not loaded).
+ (Re)sends the chunks to their relevant clients if successful.
+ The cuboid needn't be sorted. */
+ bool SetAreaBiome(const cCuboid & a_Area, EMCSBiome a_Biome);
/** Returns the name of the world */
const AString & GetName(void) const { return m_WorldName; }
diff --git a/src/WorldStorage/NBTChunkSerializer.cpp b/src/WorldStorage/NBTChunkSerializer.cpp
index cf6df114e..2a1eda523 100644
--- a/src/WorldStorage/NBTChunkSerializer.cpp
+++ b/src/WorldStorage/NBTChunkSerializer.cpp
@@ -642,6 +642,8 @@ void cNBTChunkSerializer::Entity(cEntity * a_Entity)
case cEntity::etProjectile: AddProjectileEntity ((cProjectileEntity *)a_Entity); break;
case cEntity::etTNT: /* TODO */ break;
case cEntity::etExpOrb: /* TODO */ break;
+ case cEntity::etItemFrame: /* TODO */ break;
+ case cEntity::etPainting: /* TODO */ break;
case cEntity::etPlayer: return; // Players aren't saved into the world
default:
{