summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--MCServer/Plugins/APIDump/APIDesc.lua377
-rw-r--r--MCServer/Plugins/APIDump/WebWorldThreads.html60
-rw-r--r--MCServer/Plugins/APIDump/main.lua14
-rw-r--r--VC2008/MCServer.vcproj4
-rw-r--r--source/Bindings.cpp140
-rw-r--r--source/Bindings.h2
-rw-r--r--source/BlockID.cpp42
-rw-r--r--source/ChunkDef.h49
-rw-r--r--source/Entities/Entity.h24
-rw-r--r--source/Entities/Minecart.cpp58
-rw-r--r--source/Entities/Minecart.h13
-rw-r--r--source/HTTPServer/HTTPFormParser.cpp12
-rw-r--r--source/HTTPServer/HTTPFormParser.h17
-rw-r--r--source/ManualBindings.cpp1
-rw-r--r--source/Mobs/Bat.h5
-rw-r--r--source/Mobs/Cavespider.cpp3
-rw-r--r--source/Mobs/Creeper.cpp21
-rw-r--r--source/Mobs/Creeper.h9
-rw-r--r--source/Mobs/Enderman.cpp6
-rw-r--r--source/Mobs/Enderman.h11
-rw-r--r--source/Mobs/Ghast.h2
-rw-r--r--source/Mobs/Horse.cpp101
-rw-r--r--source/Mobs/Horse.h21
-rw-r--r--source/Mobs/Magmacube.h1
-rw-r--r--source/Mobs/Monster.cpp2
-rw-r--r--source/Mobs/Monster.h6
-rw-r--r--source/Mobs/Ocelot.h3
-rw-r--r--source/Mobs/Pig.cpp49
-rw-r--r--source/Mobs/Pig.h7
-rw-r--r--source/Mobs/Sheep.cpp28
-rw-r--r--source/Mobs/Sheep.h16
-rw-r--r--source/Mobs/Silverfish.h3
-rw-r--r--source/Mobs/Skeleton.cpp5
-rw-r--r--source/Mobs/Skeleton.h8
-rw-r--r--source/Mobs/Slime.cpp2
-rw-r--r--source/Mobs/Slime.h1
-rw-r--r--source/Mobs/Villager.cpp22
-rw-r--r--source/Mobs/Villager.h22
-rw-r--r--source/Mobs/Witch.h2
-rw-r--r--source/Mobs/Wolf.cpp79
-rw-r--r--source/Mobs/Wolf.h22
-rw-r--r--source/Mobs/Zombie.cpp6
-rw-r--r--source/Mobs/Zombie.h10
-rw-r--r--source/Protocol/Protocol125.cpp284
-rw-r--r--source/Protocol/Protocol125.h13
-rw-r--r--source/Protocol/Protocol132.cpp7
-rw-r--r--source/WebAdmin.cpp23
-rw-r--r--source/WebAdmin.h6
-rw-r--r--source/World.cpp83
49 files changed, 1564 insertions, 138 deletions
diff --git a/MCServer/Plugins/APIDump/APIDesc.lua b/MCServer/Plugins/APIDump/APIDesc.lua
index dc04d3e81..12665888b 100644
--- a/MCServer/Plugins/APIDump/APIDesc.lua
+++ b/MCServer/Plugins/APIDump/APIDesc.lua
@@ -546,7 +546,7 @@ World:ForEachChestInChunk(Player:GetChunkX(), Player:GetChunkZ(),
Desc = [[
This class is used to represent a crafting recipe, either a built-in one, or one created dynamically in a plugin. It is used only as a parameter for {{OnCraftingNoRecipe|OnCraftingNoRecipe}}, {{OnPostCrafting|OnPostCrafting}} and {{OnPreCrafting|OnPreCrafting}} hooks. Plugins may use it to inspect or modify a crafting recipe that a player views in their crafting window, either at a crafting table or the survival inventory screen.
</p>
- <p>Internally, the class contains a {{cItem|cItem}} for the result.
+ <p>Internally, the class contains a {{cCraftingGrid}} for the ingredients and a {{cItem}} for the result.
]],
Functions =
{
@@ -2136,6 +2136,381 @@ end;
the second value is not provided, the original message is used.
]],
}, -- HOOK_CHAT
+
+ HOOK_CHUNK_AVAILABLE =
+ {
+ CalledWhen = "A chunk has just been added to world, either generated or loaded. ",
+ DefaultFnName = "OnChunkAvailable", -- also used as pagename
+ Desc = [[
+ This hook is called after a chunk is either generated or loaded from the disk. The chunk is
+ already available for manipulation using the {{cWorld}} API. This is a notification-only callback,
+ there is no behavior that plugins could override.
+ ]],
+ Params =
+ {
+ { Name = "World", Type = "{{cWorld}}", Notes = "The world to which the chunk belongs" },
+ { Name = "ChunkX", Type = "number", Notes = "X-coord of the chunk" },
+ { Name = "ChunkZ", Type = "number", Notes = "Z-coord of the chunk" },
+ },
+ Returns = [[
+ If the function returns false or no value, the next plugin's callback is called. If the function
+ returns true, no other callback is called for this event.
+ ]],
+ }, -- HOOK_CHUNK_AVAILABLE
+
+ HOOK_CHUNK_GENERATED =
+ {
+ CalledWhen = "After a chunk was generated. Notification only.",
+ DefaultFnName = "OnChunkGenerated", -- also used as pagename
+ Desc = [[
+ This hook is called when world generator finished its work on a chunk. The chunk data has already
+ been generated and is about to be stored in the {{cWorld|world}}. A plugin may provide some
+ last-minute finishing touches to the generated data. Note that the chunk is not yet stored in the
+ world, so regular {{cWorld}} block API will not work! Instead, use the {{cChunkDesc}} object
+ received as the parameter.</p>
+ <p>
+ See also the {{OnChunkGenerating|HOOK_CHUNK_GENERATING}} hook.
+ ]],
+ Params =
+ {
+ { Name = "World", Type = "{{cWorld}}", Notes = "The world to which the chunk will be added" },
+ { Name = "ChunkX", Type = "number", Notes = "X-coord of the chunk" },
+ { Name = "ChunkZ", Type = "number", Notes = "Z-coord of the chunk" },
+ { Name = "ChunkDesc", Type = "{{cChunkDesc}}", Notes = "Generated chunk data. Plugins may still modify the chunk data contained." },
+ },
+ Returns = [[
+ If the plugin returns false or no value, MCServer will call other plugins' callbacks for this event.
+ If a plugin returns true, no other callback is called for this event.</p>
+ <p>
+ In either case, MCServer will then store the data from ChunkDesc as the chunk's contents in the world.
+ ]],
+ CodeExamples =
+ {
+ {
+ Title = "Generate emerald ore",
+ Desc = "This example callback function generates one block of emerald ore in each chunk, under the condition that the randomly chosen location is in an ExtremeHills biome.",
+ Code = [[
+function OnChunkGenerated(a_World, a_ChunkX, a_ChunkZ, a_ChunkDesc)
+ -- Generate a psaudorandom value that is always the same for the same X/Z pair, but is otherwise random enough:
+ -- This is actually similar to how MCServer does its noise functions
+ local PseudoRandom = (a_ChunkX * 57 + a_ChunkZ) * 57 + 19785486
+ PseudoRandom = PseudoRandom * 8192 + PseudoRandom;
+ PseudoRandom = ((PseudoRandom * (PseudoRandom * PseudoRandom * 15731 + 789221) + 1376312589) % 0x7fffffff;
+ PseudoRandom = PseudoRandom / 7;
+
+ -- Based on the PseudoRandom value, choose a location for the ore:
+ local OreX = PseudoRandom % 16;
+ local OreY = 2 + ((PseudoRandom / 16) % 20);
+ local OreZ = (PseudoRandom / 320) % 16;
+
+ -- Check if the location is in ExtremeHills:
+ if (a_ChunkDesc:GetBiome(OreX, OreZ) ~= biExtremeHills) then
+ return false;
+ end
+
+ -- Only replace allowed blocks with the ore:
+ local CurrBlock = a_ChunDesc:GetBlockType(OreX, OreY, OreZ);
+ if (
+ (CurrBlock == E_BLOCK_STONE) or
+ (CurrBlock == E_BLOCK_DIRT) or
+ (CurrBlock == E_BLOCK_GRAVEL)
+ ) then
+ a_ChunkDesc:SetBlockTypeMeta(OreX, OreY, OreZ, E_BLOCK_EMERALD_ORE, 0);
+ end
+end;
+ ]],
+ },
+ } , -- CodeExamples
+ }, -- HOOK_CHUNK_GENERATED
+
+ HOOK_CHUNK_GENERATING =
+ {
+ CalledWhen = "A chunk is about to be generated. Plugin can override the built-in generator.",
+ DefaultFnName = "OnChunkGenerating", -- also used as pagename
+ Desc = [[
+ This hook is called before the world generator starts generating a chunk. The plugin may provide
+ some or all parts of the generation, by-passing the built-in generator. The function is given access
+ to the {{cChunkDesc|ChunkDesc}} object representing the contents of the chunk. It may override parts
+ of the built-in generator by using the object's <i>SetUseDefaultXXX(false)</i> functions. After all
+ the callbacks for a chunk have been processed, the server will generate the chunk based on the
+ {{cChunkDesc|ChunkDesc}} description - those parts that are set for generating (by default
+ everything) are generated, the rest are read from the ChunkDesc object.</p>
+ <p>
+ See also the {{OnChunkGenerated|HOOK_CHUNK_GENERATED}} hook.
+ ]],
+ Params =
+ {
+ { Name = "World", Type = "{{cWorld}}", Notes = "The world to which the chunk will be added" },
+ { Name = "ChunkX", Type = "number", Notes = "X-coord of the chunk" },
+ { Name = "ChunkZ", Type = "number", Notes = "Z-coord of the chunk" },
+ { Name = "ChunkDesc", Type = "{{cChunkDesc}}", Notes = "Generated chunk data." },
+ },
+ Returns = [[
+ If this function returns true, the server will not call any other plugin with the same chunk. If
+ this function returns false, the server will call the rest of the plugins with the same chunk,
+ possibly overwriting the ChunkDesc's contents.
+ ]],
+ }, -- HOOK_CHUNK_GENERATING
+
+ HOOK_CHUNK_UNLOADED =
+ {
+ CalledWhen = "A chunk has been unloaded from the memory.",
+ DefaultFnName = "OnChunkUnloaded", -- also used as pagename
+ Desc = [[
+ This hook is called when a chunk is unloaded from the memory. Though technically still in memory,
+ the plugin should behave as if the chunk was already not present. In particular, {{cWorld}} block
+ API should not be used in the area of the specified chunk.
+ ]],
+ Params =
+ {
+ { Name = "World", Type = "{{cWorld}}", Notes = "The world from which the chunk is unloading" },
+ { Name = "ChunkX", Type = "number", Notes = "X-coord of the chunk" },
+ { Name = "ChunkZ", Type = "number", Notes = "Z-coord of the chunk" },
+ },
+ Returns = [[
+ If the function returns false or no value, the next plugin's callback is called. If the function
+ returns true, no other callback is called for this event. There is no behavior that plugins could
+ override.
+ ]],
+ }, -- HOOK_CHUNK_UNLOADED
+
+ HOOK_CHUNK_UNLOADING =
+ {
+ CalledWhen = " A chunk is about to be unloaded from the memory. Plugins may refuse the unload.",
+ DefaultFnName = "OnChunkUnloading", -- also used as pagename
+ Desc = [[
+ MCServer calls this function when a chunk is about to be unloaded from the memory. A plugin may
+ force MCServer to keep the chunk in memory by returning true.</p>
+ <p>
+ FIXME: The return value should be used only for event propagation stopping, not for the actual
+ decision whether to unload.
+ ]],
+ Params =
+ {
+ { Name = "World", Type = "{{cWorld}}", Notes = "The world from which the chunk is unloading" },
+ { Name = "ChunkX", Type = "number", Notes = "X-coord of the chunk" },
+ { Name = "ChunkZ", Type = "number", Notes = "Z-coord of the chunk" },
+ },
+ Returns = [[
+ If the function returns false or no value, the next plugin's callback is called and finally MCServer
+ unloads the chunk. If the function returns true, no other callback is called for this event and the
+ chunk is left in the memory.
+ ]],
+ }, -- HOOK_CHUNK_UNLOADING
+
+ HOOK_COLLECTING_PICKUP =
+ {
+ CalledWhen = "Player is about to collect a pickup. Plugin can refuse / override behavior. ",
+ DefaultFnName = "OnCollectingPickup", -- also used as pagename
+ Desc = [[
+ This hook is called when a player is about to collect a pickup. Plugins may refuse the action.</p>
+ <p>
+ Pickup collection happens within the world tick, so if the collecting is refused, it will be tried
+ again in the next world tick, as long as the player is within reach of the pickup.</p>
+ <p>
+ FIXME: There is no OnCollectedPickup() callback.</p>
+ <p>
+ FIXME: This callback is called even if the pickup doesn't fit into the player's inventory.</p>
+ ]],
+ Params =
+ {
+ { Name = "Player", Type = "{{cPlayer}}", Notes = "The player who's collecting the pickup" },
+ { Name = "Pickup", Type = "{{cPickup}}", Notes = "The pickup being collected" },
+ },
+ Returns = [[
+ If the function returns false or no value, MCServer calls other plugins' callbacks and finally the
+ pickup is collected. If the function returns true, no other plugins are called for this event and
+ the pickup is not collected.
+ ]],
+ }, -- HOOK_COLLECTING_PICKUP
+
+ HOOK_CRAFTING_NO_RECIPE =
+ {
+ CalledWhen = " No built-in crafting recipe is found. Plugin may provide a recipe.",
+ DefaultFnName = "OnCraftingNoRecipe", -- also used as pagename
+ Desc = [[
+ This callback is called when a player places items in their {{cCraftingGrid|crafting grid}} and
+ MCServer cannot find a built-in {{cCraftingRecipe|recipe}} for the combination. Plugins may provide
+ a recipe for the ingredients given.
+ ]],
+ Params =
+ {
+ { Name = "Player", Type = "{{cPlayer}}", Notes = "The player whose crafting is reported in this hook" },
+ { Name = "Grid", Type = "{{cCraftingGrid}}", Notes = "Contents of the player's crafting grid" },
+ { Name = "Recipe", Type = "{{cCraftingRecipe}}", Notes = "The recipe that will be used (can be filled by plugins)" },
+ },
+ Returns = [[
+ If the function returns false or no value, no recipe will be used. If the function returns true, no
+ other plugin will have their callback called for this event and MCServer will use the crafting
+ recipe in Recipe.</p>
+ <p>
+ FIXME: To allow plugins give suggestions and overwrite other plugins' suggestions, we should change
+ the behavior with returning false, so that the recipe will still be used, but fill the recipe with
+ empty values by default.
+ ]],
+ }, -- HOOK_CRAFTING_NO_RECIPE
+
+ HOOK_DISCONNECT =
+ {
+ CalledWhen = "A player has explicitly disconnected.",
+ DefaultFnName = "OnDisconnect", -- also used as pagename
+ Desc = [[
+ This hook is called when a client sends the disconnect packet and is about to be disconnected from
+ the server.</p>
+ <p>
+ Note that this callback is not called if the client drops the connection or is kicked by the
+ server.</p>
+ <p>
+ FIXME: There is no callback for "client destroying" that would be called in all circumstances.</p>
+ ]],
+ Params =
+ {
+ { Name = "Player", Type = "{{cPlayer}}", Notes = "The player who has disconnected" },
+ { Name = "Reason", Type = "string", Notes = "The reason that the client has sent in the disconnect packet" },
+ },
+ Returns = [[
+ If the function returns false or no value, MCServer calls other plugins' callbacks for this event
+ and finally broadcasts a disconnect message to the player's world. If the function returns true, no
+ other plugins are called for this event and the disconnect message is not broadcast. In either case,
+ the player is disconnected.
+ ]],
+ }, -- HOOK_DISCONNECT
+
+ HOOK_EXECUTE_COMMAND =
+ {
+ CalledWhen = "A player executes an in-game command, or the admin issues a console command. Note that built-in console commands are exempt to this hook - they are always performed and the hook is not called.",
+ DefaultFnName = "OnExecuteCommand", -- also used as pagename
+ Desc = [[
+ A plugin may implement a callback for this hook to intercept both in-game commands executed by the
+ players and console commands executed by the server admin. The function is called for every in-game
+ command sent from any player and for those server console commands that are not built in in the
+ server.</p>
+ <p>
+ If the command is in-game, the first parameter to the hook function is the {{cPlayer|player}} who's
+ executing the command. If the command comes from the server console, the first parameter is nil.
+ ]],
+ Params =
+ {
+ { Name = "Player", Type = "{{cPlayer}}", Notes = "For in-game commands, the player who has sent the message. For console commands, nil" },
+ { Name = "Command", Type = "table of strings", Notes = "The command and its parameters, broken into a table by spaces" },
+ },
+ Returns = [[
+ If the plugin returns true, the command will be blocked and none of the remaining hook handlers will
+ be called. If the plugin returns false, MCServer calls all the remaining hook handlers and finally
+ the command will be executed.
+ ]],
+ }, -- HOOK_EXECUTE_COMMAND
+
+ HOOK_EXPLODED =
+ {
+ CalledWhen = "An explosion has happened",
+ DefaultFnName = "OnExploded", -- also used as pagename
+ Desc = [[
+ This hook is called after an explosion has been processed in a world.</p>
+ <p>
+ See also {{OnHookExploding|HOOK_EXPLODING}} for a similar hook called before the explosion.</p>
+ <p>
+ The explosion carries with it the type of its source - whether it's a creeper exploding, or TNT,
+ etc. It also carries the identification of the actual source. The exact type of the identification
+ depends on the source kind:
+ <table>
+ <tr><th>Source</th><th>SourceData Type</th><th>Notes</th></tr>
+ <tr><td>esPrimedTNT</td><td>{{cTNTEntity}}</td><td>An exploding primed TNT entity</td></tr>
+ <tr><td>esCreeper</td><td>{{cCreeper}}</td><td>An exploding creeper or charged creeper</td></tr>
+ <tr><td>esBed</td><td>{{Vector3i}}</td><td>A bed exploding in the Nether or in the End. The bed coords are given.</td></tr>
+ <tr><td>esEnderCrystal</td><td>{{Vector3i}}</td><td>An ender crystal exploding upon hit. The block coords are given.</td></tr>
+ <tr><td>esGhastFireball</td><td>{{cGhastFireballEntity}}</td><td>A ghast fireball hitting ground or an {{cEntity|entity}}.</td></tr>
+ <tr><td>esWitherSkullBlack</td><td><i>TBD</i></td><td>A black wither skull hitting ground or an {{cEntity|entity}}.</td></tr>
+ <tr><td>esWitherSkullBlue</td><td><i>TBD</i></td><td>A blue wither skull hitting ground or an {{cEntity|entity}}.</td></tr>
+ <tr><td>esWitherBirth</td><td><i>TBD</i></td><td>A wither boss being created</td></tr>
+ <tr><td>esOther</td><td><i>TBD</i></td><td>Any other previously unspecified type.</td></tr>
+ <tr><td>esPlugin</td><td>object</td><td>An explosion created by a plugin. The plugin may specify any kind of data.</td></tr>
+ </table></p>
+ ]],
+ Params =
+ {
+ { Name = "World", Type = "{{cWorld}}", Notes = "The world where the explosion happened" },
+ { Name = "ExplosionSize", Type = "number", Notes = "The relative explosion size" },
+ { Name = "CanCauseFire", Type = "bool", Notes = "True if the explosion has turned random air blocks to fire (such as a ghast fireball)" },
+ { Name = "X", Type = "number", Notes = "X-coord of the explosion center" },
+ { Name = "Y", Type = "number", Notes = "Y-coord of the explosion center" },
+ { Name = "Z", Type = "number", Notes = "Z-coord of the explosion center" },
+ { Name = "Source", Type = "eExplosionSource", Notes = "Source of the explosion. See the table above." },
+ { Name = "SourceData", Type = "varies", Notes = "Additional data for the source. The exact type varies by the source. See the table above." },
+ },
+ Returns = [[
+ If the function returns false or no value, the next plugin's callback is called. If the function
+ returns true, no other callback is called for this event. There is no overridable behaviour.
+ ]],
+ }, -- HOOK_EXPLODED
+
+ HOOK_EXPLODING =
+ {
+ CalledWhen = "An explosion is about to be processed",
+ DefaultFnName = "OnExploding", -- also used as pagename
+ Desc = [[
+ This hook is called before an explosion has been processed in a world.</p>
+ <p>
+ See also {{OnHookExploded|HOOK_EXPLODED}} for a similar hook called after the explosion.</p>
+ <p>
+ The explosion carries with it the type of its source - whether it's a creeper exploding, or TNT,
+ etc. It also carries the identification of the actual source. The exact type of the identification
+ depends on the source kind:
+ <table>
+ <tr><th>Source</th><th>SourceData Type</th><th>Notes</th></tr>
+ <tr><td>esPrimedTNT</td><td>{{cTNTEntity}}</td><td>An exploding primed TNT entity</td></tr>
+ <tr><td>esCreeper</td><td>{{cCreeper}}</td><td>An exploding creeper or charged creeper</td></tr>
+ <tr><td>esBed</td><td>{{Vector3i}}</td><td>A bed exploding in the Nether or in the End. The bed coords are given.</td></tr>
+ <tr><td>esEnderCrystal</td><td>{{Vector3i}}</td><td>An ender crystal exploding upon hit. The block coords are given.</td></tr>
+ <tr><td>esGhastFireball</td><td>{{cGhastFireballEntity}}</td><td>A ghast fireball hitting ground or an {{cEntity|entity}}.</td></tr>
+ <tr><td>esWitherSkullBlack</td><td><i>TBD</i></td><td>A black wither skull hitting ground or an {{cEntity|entity}}.</td></tr>
+ <tr><td>esWitherSkullBlue</td><td><i>TBD</i></td><td>A blue wither skull hitting ground or an {{cEntity|entity}}.</td></tr>
+ <tr><td>esWitherBirth</td><td><i>TBD</i></td><td>A wither boss being created</td></tr>
+ <tr><td>esOther</td><td><i>TBD</i></td><td>Any other previously unspecified type.</td></tr>
+ <tr><td>esPlugin</td><td>object</td><td>An explosion created by a plugin. The plugin may specify any kind of data.</td></tr>
+ </table></p>
+ ]],
+ Params =
+ {
+ { Name = "World", Type = "{{cWorld}}", Notes = "The world where the explosion happens" },
+ { Name = "ExplosionSize", Type = "number", Notes = "The relative explosion size" },
+ { Name = "CanCauseFire", Type = "bool", Notes = "True if the explosion will turn random air blocks to fire (such as a ghast fireball)" },
+ { Name = "X", Type = "number", Notes = "X-coord of the explosion center" },
+ { Name = "Y", Type = "number", Notes = "Y-coord of the explosion center" },
+ { Name = "Z", Type = "number", Notes = "Z-coord of the explosion center" },
+ { Name = "Source", Type = "eExplosionSource", Notes = "Source of the explosion. See the table above." },
+ { Name = "SourceData", Type = "varies", Notes = "Additional data for the source. The exact type varies by the source. See the table above." },
+ },
+ Returns = [[
+ If the function returns false or no value, the next plugin's callback is called, and finally
+ MCServer will process the explosion - destroy blocks and push + hurt entities. If the function
+ returns true, no other callback is called for this event and the explosion will not occur.
+ ]],
+ }, -- HOOK_EXPLODING
+
+ HOOK_HANDSHAKE =
+ {
+ CalledWhen = "A client is connecting.",
+ DefaultFnName = "OnHandshake", -- also used as pagename
+ Desc = [[
+ This hook is called when a client sends the Handshake packet. At this stage, only the client IP and
+ (unverified) username are known. Plugins may refuse access to the server based on this
+ information.</p>
+ <p>
+ Note that the username is not authenticated - the authentication takes place only after this hook is
+ processed.
+ ]],
+ Params =
+ {
+ { Name = "Client", Type = "{{cClientHandle}}", Notes = "The client handle representing the connection. Note that there's no {{cPlayer}} object for this client yet." },
+ { Name = "UserName", Type = "string", Notes = "The username presented in the packet. Note that this username is unverified." },
+ },
+ Returns = [[
+ If the function returns false, the user is let in to the server. If the function returns true, no
+ other plugin's callback is called, the user is kicked and the connection is closed.
+ ]],
+ }, -- HOOK_HANDSHAKE
+
}, -- Hooks[]
diff --git a/MCServer/Plugins/APIDump/WebWorldThreads.html b/MCServer/Plugins/APIDump/WebWorldThreads.html
index 1a593ad8d..a77209b0b 100644
--- a/MCServer/Plugins/APIDump/WebWorldThreads.html
+++ b/MCServer/Plugins/APIDump/WebWorldThreads.html
@@ -1,8 +1,64 @@
<html>
<head>
-<title>Webserver vs World threads</title>
+<title>MCServer - Webserver vs World threads</title>
+<script src="https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js"></script>
+<script src="http://google-code-prettify.googlecode.com/svn/trunk/src/lang-lua.js"></script>
</head>
<body>
-This is a temporary test
+
+<h1>Webserver vs World threads</h1>
+<p>
+This article will explain the threading issues that arise between the webserver and world threads are of concern to plugin authors.</p>
+<p>
+Generally, plugins that provide webadmin pages should be quite careful about their interactions. Most operations on MCServer objects requires synchronization, that MCServer provides automatically and transparently to plugins - when a block is written, the chunkmap is locked, or when an entity is being manipulated, the entity list is locked. Each plugin also has a mutex lock, so that only one thread at a time may be executing plugin code.</p>
+<p>
+This locking can be a source of deadlocks for plugins that are not written carefully.</p>
+
+<h2>Example scenario</h2>
+<p>Consider the following example. A plugin provides a webadmin page that allows the admin to kick players off the server. When the admin presses the "Kick" button, the plugin calls cWorld:DoWithPlayer() with a callback to kick the player. Everything seems to be working fine now.</p>
+<p>
+A new feature is developed in the plugin, now the plugin adds a new in-game command so that the admins can kick players while they're playing the game. The plugin registers a command callback with cPluginManager.AddCommand(). Now there are problems bound to happen.</p>
+<p>
+Suppose that two admins are in, one is using the webadmin and the other is in-game. Both try to kick a player at the same time. The webadmin locks the plugin, so that it can execute the plugin code, but right at this moment the OS switches threads. The world thread locks the world so that it can access the list of in-game commands, receives the in-game command, it tries to lock the plugin. The plugin is already locked, so the world thread is put on hold. After a while, the webadmin thread is woken up again and continues processing. It tries to lock the world so that it can traverse the playerlist, but the lock is already held by the world thread. Now both threads are holding one lock each and trying to grab the other lock, and are therefore deadlocked.</p>
+
+<h2>How to avoid the deadlock</h2>
+<p>
+There are two main ways to avoid such a deadlock. The first approach is using tasks: Everytime you need to execute a task inside a world, instead of executing it, queue it, using <a href="cWorld.html">cWorld</a>:QueueTask(). This handy utility can will call the given function inside the world's TickThread, thus eliminating the deadlock, because now there's only one thread. However, this approach will not let you get data back. You cannot query the player list, or the entities, or anything - because when the task runs, the webadmin page has already been served to the browser.</p>
+<p>
+To accommodate this, you'll need to use the second approach - preparing and caching data in the tick thread, possibly using callbacks. This means that the plugin will have global variables that will store the data, and update those variables when the data changes; then the webserver thread will only read those variables, instead of calling the world functions. For example, if a webpage was to display the list of currently connected players, the plugin should maintain a global variable, g_WorldPlayers, which would be a table of worlds, each item being a list of currently connected players. The webadmin handler would read this variable and create the page from it; the plugin would use HOOK_PLAYER_JOINED and HOOK_DISCONNECT to update the variable.</p>
+
+<h2>What to avoid</h2>
+<p>
+Now that we know what the danger is and how to avoid it, how do we know if our code is susceptible?</p>
+<p>
+The general rule of thumb is to avoid calling any functions that read or write lists of things in the webserver thread. This means most ForEach() and DoWith() functions. Only <a href="cRoot.html">cRoot</a>:ForEachWorld() is safe - because the list of worlds is not expected to change, so it is not guarded by a mutex. Getting and setting world's blocks is, naturally, unsafe, as is calling other plugins, or creating entities.</p>
+
+<h2>Example</h2>
+The Core has the facility to kick players using the web interface. It used the following code for the kicking (inside the webadmin handler):
+<pre class="prettyprint lang-lua">
+local KickPlayerName = Request.Params["players-kick"]
+local FoundPlayerCallback = function(Player)
+ if (Player:GetName() == KickPlayerName) then
+ Player:GetClientHandle():Kick("You were kicked from the game!")
+ end
+end
+cRoot:Get():FindAndDoWithPlayer(KickPlayerName, FoundPlayerCallback)
+</pre>
+The cRoot:FindAndDoWithPlayer() is unsafe and could have caused a deadlock. The new solution is queue a task; but since we don't know in which world the player is, we need to queue the task to all worlds:
+<pre class="prettyprint lang-lua">
+cRoot:Get():ForEachWorld( -- For each world...
+ function(World)
+ World:QueueTask( -- ... queue a task...
+ function(a_World)
+ a_World:DoWithPlayer(KickPlayerName, -- ... to walk the playerlist...
+ function (a_Player)
+ a_Player:GetClientHandle():Kick("You were kicked from the game!") -- ... and kick the player
+ end
+ )
+ end
+ )
+ end
+)
+</pre>
</body>
</html> \ No newline at end of file
diff --git a/MCServer/Plugins/APIDump/main.lua b/MCServer/Plugins/APIDump/main.lua
index 3827668e3..8c07144f2 100644
--- a/MCServer/Plugins/APIDump/main.lua
+++ b/MCServer/Plugins/APIDump/main.lua
@@ -327,10 +327,18 @@ function DumpAPIHtml()
f:write("\t\t" .. hook .. " =\n\t\t{\n");
f:write("\t\t\tCalledWhen = \"\",\n");
f:write("\t\t\tDefaultFnName = \"On\", -- also used as pagename\n");
- f:write("\t\t\tDesc = [[]],\n");
+ f:write("\t\t\tDesc = [[\n\t\t\t\t\n\t\t\t]],\n");
f:write("\t\t\tParams =\n\t\t\t{\n");
- f:write("\t\t\t\t{ Name = \"\", Type = \"\", Notes = \"\" },\n\t\t\t},\n");
- f:write("\t\t\tReturns = [[]],\n");
+ f:write("\t\t\t\t{ Name = \"\", Type = \"\", Notes = \"\" },\n");
+ f:write("\t\t\t\t{ Name = \"\", Type = \"\", Notes = \"\" },\n");
+ f:write("\t\t\t\t{ Name = \"\", Type = \"\", Notes = \"\" },\n");
+ f:write("\t\t\t\t{ Name = \"\", Type = \"\", Notes = \"\" },\n");
+ f:write("\t\t\t\t{ Name = \"\", Type = \"\", Notes = \"\" },\n");
+ f:write("\t\t\t\t{ Name = \"\", Type = \"\", Notes = \"\" },\n");
+ f:write("\t\t\t\t{ Name = \"\", Type = \"\", Notes = \"\" },\n");
+ f:write("\t\t\t\t{ Name = \"\", Type = \"\", Notes = \"\" },\n");
+ f:write("\t\t\t},\n");
+ f:write("\t\t\tReturns = [[\n\t\t\t\t\n\t\t\t]],\n");
f:write("\t\t}, -- " .. hook .. "\n");
end
end
diff --git a/VC2008/MCServer.vcproj b/VC2008/MCServer.vcproj
index 57d8cca9e..2e5b71ffe 100644
--- a/VC2008/MCServer.vcproj
+++ b/VC2008/MCServer.vcproj
@@ -1140,6 +1140,10 @@
>
</File>
<File
+ RelativePath="..\source\Mobs\Wolf.cpp"
+ >
+ </File>
+ <File
RelativePath="..\source\Mobs\Wolf.h"
>
</File>
diff --git a/source/Bindings.cpp b/source/Bindings.cpp
index 8d856cfa5..c241bad75 100644
--- a/source/Bindings.cpp
+++ b/source/Bindings.cpp
@@ -1,6 +1,6 @@
/*
** Lua binding: AllToLua
-** Generated automatically by tolua++-1.0.92 on 10/11/13 10:08:32.
+** Generated automatically by tolua++-1.0.92 on 10/13/13 18:01:21.
*/
#ifndef __cplusplus
@@ -4998,6 +4998,38 @@ static int tolua_AllToLua_cEntity_IsMob00(lua_State* tolua_S)
}
#endif //#ifndef TOLUA_DISABLE
+/* method: IsFallingBlock of class cEntity */
+#ifndef TOLUA_DISABLE_tolua_AllToLua_cEntity_IsFallingBlock00
+static int tolua_AllToLua_cEntity_IsFallingBlock00(lua_State* tolua_S)
+{
+#ifndef TOLUA_RELEASE
+ tolua_Error tolua_err;
+ if (
+ !tolua_isusertype(tolua_S,1,"const cEntity",0,&tolua_err) ||
+ !tolua_isnoobj(tolua_S,2,&tolua_err)
+ )
+ goto tolua_lerror;
+ else
+#endif
+ {
+ const cEntity* self = (const cEntity*) tolua_tousertype(tolua_S,1,0);
+#ifndef TOLUA_RELEASE
+ if (!self) tolua_error(tolua_S,"invalid 'self' in function 'IsFallingBlock'", NULL);
+#endif
+ {
+ bool tolua_ret = (bool) self->IsFallingBlock();
+ tolua_pushboolean(tolua_S,(bool)tolua_ret);
+ }
+ }
+ return 1;
+#ifndef TOLUA_RELEASE
+ tolua_lerror:
+ tolua_error(tolua_S,"#ferror in function 'IsFallingBlock'.",&tolua_err);
+ return 0;
+#endif
+}
+#endif //#ifndef TOLUA_DISABLE
+
/* method: IsMinecart of class cEntity */
#ifndef TOLUA_DISABLE_tolua_AllToLua_cEntity_IsMinecart00
static int tolua_AllToLua_cEntity_IsMinecart00(lua_State* tolua_S)
@@ -5094,6 +5126,38 @@ static int tolua_AllToLua_cEntity_IsTNT00(lua_State* tolua_S)
}
#endif //#ifndef TOLUA_DISABLE
+/* method: IsProjectile of class cEntity */
+#ifndef TOLUA_DISABLE_tolua_AllToLua_cEntity_IsProjectile00
+static int tolua_AllToLua_cEntity_IsProjectile00(lua_State* tolua_S)
+{
+#ifndef TOLUA_RELEASE
+ tolua_Error tolua_err;
+ if (
+ !tolua_isusertype(tolua_S,1,"const cEntity",0,&tolua_err) ||
+ !tolua_isnoobj(tolua_S,2,&tolua_err)
+ )
+ goto tolua_lerror;
+ else
+#endif
+ {
+ const cEntity* self = (const cEntity*) tolua_tousertype(tolua_S,1,0);
+#ifndef TOLUA_RELEASE
+ if (!self) tolua_error(tolua_S,"invalid 'self' in function 'IsProjectile'", NULL);
+#endif
+ {
+ bool tolua_ret = (bool) self->IsProjectile();
+ tolua_pushboolean(tolua_S,(bool)tolua_ret);
+ }
+ }
+ return 1;
+#ifndef TOLUA_RELEASE
+ tolua_lerror:
+ tolua_error(tolua_S,"#ferror in function 'IsProjectile'.",&tolua_err);
+ return 0;
+#endif
+}
+#endif //#ifndef TOLUA_DISABLE
+
/* method: IsA of class cEntity */
#ifndef TOLUA_DISABLE_tolua_AllToLua_cEntity_IsA00
static int tolua_AllToLua_cEntity_IsA00(lua_State* tolua_S)
@@ -7958,6 +8022,38 @@ static int tolua_AllToLua_cEntity_IsRclking00(lua_State* tolua_S)
}
#endif //#ifndef TOLUA_DISABLE
+/* method: IsInvisible of class cEntity */
+#ifndef TOLUA_DISABLE_tolua_AllToLua_cEntity_IsInvisible00
+static int tolua_AllToLua_cEntity_IsInvisible00(lua_State* tolua_S)
+{
+#ifndef TOLUA_RELEASE
+ tolua_Error tolua_err;
+ if (
+ !tolua_isusertype(tolua_S,1,"const cEntity",0,&tolua_err) ||
+ !tolua_isnoobj(tolua_S,2,&tolua_err)
+ )
+ goto tolua_lerror;
+ else
+#endif
+ {
+ const cEntity* self = (const cEntity*) tolua_tousertype(tolua_S,1,0);
+#ifndef TOLUA_RELEASE
+ if (!self) tolua_error(tolua_S,"invalid 'self' in function 'IsInvisible'", NULL);
+#endif
+ {
+ bool tolua_ret = (bool) self->IsInvisible();
+ tolua_pushboolean(tolua_S,(bool)tolua_ret);
+ }
+ }
+ return 1;
+#ifndef TOLUA_RELEASE
+ tolua_lerror:
+ tolua_error(tolua_S,"#ferror in function 'IsInvisible'.",&tolua_err);
+ return 0;
+#endif
+}
+#endif //#ifndef TOLUA_DISABLE
+
/* method: GetEyeHeight of class cPlayer */
#ifndef TOLUA_DISABLE_tolua_AllToLua_cPlayer_GetEyeHeight00
static int tolua_AllToLua_cPlayer_GetEyeHeight00(lua_State* tolua_S)
@@ -29131,8 +29227,47 @@ TOLUA_API int tolua_AllToLua_open (lua_State* tolua_S)
tolua_constant(tolua_S,"biExtremeHillsEdge",biExtremeHillsEdge);
tolua_constant(tolua_S,"biJungle",biJungle);
tolua_constant(tolua_S,"biJungleHills",biJungleHills);
+ tolua_constant(tolua_S,"biJungleEdge",biJungleEdge);
+ tolua_constant(tolua_S,"biDeepOcean",biDeepOcean);
+ tolua_constant(tolua_S,"biStoneBeach",biStoneBeach);
+ tolua_constant(tolua_S,"biColdBeach",biColdBeach);
+ tolua_constant(tolua_S,"biBirchForest",biBirchForest);
+ tolua_constant(tolua_S,"biBirchForestHills",biBirchForestHills);
+ tolua_constant(tolua_S,"biRoofedForest",biRoofedForest);
+ tolua_constant(tolua_S,"biColdTaiga",biColdTaiga);
+ tolua_constant(tolua_S,"biColdTaigaHills",biColdTaigaHills);
+ tolua_constant(tolua_S,"biMegaTaiga",biMegaTaiga);
+ tolua_constant(tolua_S,"biMegaTaigaHills",biMegaTaigaHills);
+ tolua_constant(tolua_S,"biExtremeHillsPlus",biExtremeHillsPlus);
+ tolua_constant(tolua_S,"biSavanna",biSavanna);
+ tolua_constant(tolua_S,"biSavannaPlateau",biSavannaPlateau);
+ tolua_constant(tolua_S,"biMesa",biMesa);
+ tolua_constant(tolua_S,"biMesaPlateauF",biMesaPlateauF);
+ tolua_constant(tolua_S,"biMesaPlateau",biMesaPlateau);
tolua_constant(tolua_S,"biNumBiomes",biNumBiomes);
tolua_constant(tolua_S,"biMaxBiome",biMaxBiome);
+ tolua_constant(tolua_S,"biVariant",biVariant);
+ tolua_constant(tolua_S,"biSunflowerPlains",biSunflowerPlains);
+ tolua_constant(tolua_S,"biDesertM",biDesertM);
+ tolua_constant(tolua_S,"biExtremeHillsM",biExtremeHillsM);
+ tolua_constant(tolua_S,"biFlowerForest",biFlowerForest);
+ tolua_constant(tolua_S,"biTaigaM",biTaigaM);
+ tolua_constant(tolua_S,"biSwamplandM",biSwamplandM);
+ tolua_constant(tolua_S,"biIcePlainsSpikes",biIcePlainsSpikes);
+ tolua_constant(tolua_S,"biJungleM",biJungleM);
+ tolua_constant(tolua_S,"biJungleEdgeM",biJungleEdgeM);
+ tolua_constant(tolua_S,"biBirchForestM",biBirchForestM);
+ tolua_constant(tolua_S,"biBirchForestHillsM",biBirchForestHillsM);
+ tolua_constant(tolua_S,"biRoofedForestM",biRoofedForestM);
+ tolua_constant(tolua_S,"biColdTaigaM",biColdTaigaM);
+ tolua_constant(tolua_S,"biMegaSpruceTaiga",biMegaSpruceTaiga);
+ tolua_constant(tolua_S,"biMegaSpruceTaigaHills",biMegaSpruceTaigaHills);
+ tolua_constant(tolua_S,"biExtremeHillsPlusM",biExtremeHillsPlusM);
+ tolua_constant(tolua_S,"biSavannaM",biSavannaM);
+ tolua_constant(tolua_S,"biSavannaPlateauM",biSavannaPlateauM);
+ tolua_constant(tolua_S,"biMesaBryce",biMesaBryce);
+ tolua_constant(tolua_S,"biMesaPlateauFM",biMesaPlateauFM);
+ tolua_constant(tolua_S,"biMesaPlateauM",biMesaPlateauM);
#ifdef __cplusplus
tolua_cclass(tolua_S,"cIniFile","cIniFile","",tolua_collect_cIniFile);
#else
@@ -30008,9 +30143,11 @@ TOLUA_API int tolua_AllToLua_open (lua_State* tolua_S)
tolua_function(tolua_S,"IsPlayer",tolua_AllToLua_cEntity_IsPlayer00);
tolua_function(tolua_S,"IsPickup",tolua_AllToLua_cEntity_IsPickup00);
tolua_function(tolua_S,"IsMob",tolua_AllToLua_cEntity_IsMob00);
+ tolua_function(tolua_S,"IsFallingBlock",tolua_AllToLua_cEntity_IsFallingBlock00);
tolua_function(tolua_S,"IsMinecart",tolua_AllToLua_cEntity_IsMinecart00);
tolua_function(tolua_S,"IsBoat",tolua_AllToLua_cEntity_IsBoat00);
tolua_function(tolua_S,"IsTNT",tolua_AllToLua_cEntity_IsTNT00);
+ tolua_function(tolua_S,"IsProjectile",tolua_AllToLua_cEntity_IsProjectile00);
tolua_function(tolua_S,"IsA",tolua_AllToLua_cEntity_IsA00);
tolua_function(tolua_S,"GetClass",tolua_AllToLua_cEntity_GetClass00);
tolua_function(tolua_S,"GetClassStatic",tolua_AllToLua_cEntity_GetClassStatic00);
@@ -30097,6 +30234,7 @@ TOLUA_API int tolua_AllToLua_open (lua_State* tolua_S)
tolua_function(tolua_S,"IsRiding",tolua_AllToLua_cEntity_IsRiding00);
tolua_function(tolua_S,"IsSprinting",tolua_AllToLua_cEntity_IsSprinting00);
tolua_function(tolua_S,"IsRclking",tolua_AllToLua_cEntity_IsRclking00);
+ tolua_function(tolua_S,"IsInvisible",tolua_AllToLua_cEntity_IsInvisible00);
tolua_endmodule(tolua_S);
tolua_cclass(tolua_S,"cPawn","cPawn","cEntity",NULL);
tolua_beginmodule(tolua_S,"cPawn");
diff --git a/source/Bindings.h b/source/Bindings.h
index 73f3140e5..1d567520c 100644
--- a/source/Bindings.h
+++ b/source/Bindings.h
@@ -1,6 +1,6 @@
/*
** Lua binding: AllToLua
-** Generated automatically by tolua++-1.0.92 on 10/11/13 10:08:33.
+** Generated automatically by tolua++-1.0.92 on 10/13/13 18:01:22.
*/
/* Exported function */
diff --git a/source/BlockID.cpp b/source/BlockID.cpp
index 0c1cb3265..7c3fa0b8e 100644
--- a/source/BlockID.cpp
+++ b/source/BlockID.cpp
@@ -307,6 +307,48 @@ EMCSBiome StringToBiome(const AString & a_BiomeString)
{biExtremeHillsEdge, "ExtremeHillsEdge"},
{biJungle, "Jungle"},
{biJungleHills, "JungleHills"},
+
+ // Release 1.7 biomes:
+ {biJungleEdge, "JungleEdge"},
+ {biDeepOcean, "DeepOcean"},
+ {biStoneBeach, "StoneBeach"},
+ {biColdBeach, "ColdBeach"},
+ {biBirchForest, "BirchForest"},
+ {biBirchForestHills, "BirchForestHills"},
+ {biRoofedForest, "RoofedForest"},
+ {biColdTaiga, "ColdTaiga"},
+ {biColdTaigaHills, "ColdTaigaHills"},
+ {biMegaTaiga, "MegaTaiga"},
+ {biMegaTaigaHills, "MegaTaigaHills"},
+ {biExtremeHillsPlus, "ExtremeHillsPlus"},
+ {biSavanna, "Savanna"},
+ {biSavannaPlateau, "SavannaPlateau"},
+ {biMesa, "Mesa"},
+ {biMesaPlateauF, "MesaPlateauF"},
+ {biMesaPlateau, "MesaPlateau"},
+
+ // Release 1.7 variants:
+ {biSunflowerPlains, "SunflowerPlains"},
+ {biDesertM, "DesertM"},
+ {biExtremeHillsM, "ExtremeHillsM"},
+ {biFlowerForest, "FlowerForest"},
+ {biTaigaM, "TaigaM"},
+ {biSwamplandM, "SwamplandM"},
+ {biIcePlainsSpikes, "IcePlainsSpikes"},
+ {biJungleM, "JungleM"},
+ {biJungleEdgeM, "JungleEdgeM"},
+ {biBirchForestM, "BirchForestM"},
+ {biBirchForestHillsM, "BirchForestHillsM"},
+ {biRoofedForestM, "RoofedForestM"},
+ {biColdTaigaM, "ColdTaigaM"},
+ {biMegaSpruceTaiga, "MegaSpruceTaiga"},
+ {biMegaSpruceTaigaHills, "MegaSpruceTaigaHills"},
+ {biExtremeHillsPlusM, "ExtremeHillsPlusM"},
+ {biSavannaM, "SavannaM"},
+ {biSavannaPlateauM, "SavannaPlateauM"},
+ {biMesaBryce, "MesaBryce"},
+ {biMesaPlateauFM, "MesaPlateauFM"},
+ {biMesaPlateauM, "MesaPlateauM"},
} ;
for (int i = 0; i < ARRAYCOUNT(BiomeMap); i++)
diff --git a/source/ChunkDef.h b/source/ChunkDef.h
index 4cc2d15b0..9db88f293 100644
--- a/source/ChunkDef.h
+++ b/source/ChunkDef.h
@@ -93,9 +93,54 @@ enum EMCSBiome
biJungle = 21,
biJungleHills = 22,
- // Automatically capture the maximum biome value into biMaxBiome:
+ // Release 1.7 biomes:
+ biJungleEdge = 23,
+ biDeepOcean = 24,
+ biStoneBeach = 25,
+ biColdBeach = 26,
+ biBirchForest = 27,
+ biBirchForestHills = 28,
+ biRoofedForest = 29,
+ biColdTaiga = 30,
+ biColdTaigaHills = 31,
+ biMegaTaiga = 32,
+ biMegaTaigaHills = 33,
+ biExtremeHillsPlus = 34,
+ biSavanna = 35,
+ biSavannaPlateau = 36,
+ biMesa = 37,
+ biMesaPlateauF = 38,
+ biMesaPlateau = 39,
+
+ // Automatically capture the maximum consecutive biome value into biMaxBiome:
biNumBiomes, // True number of biomes, since they are zero-based
- biMaxBiome = biNumBiomes - 1 // The maximum biome value
+ biMaxBiome = biNumBiomes - 1, // The maximum biome value
+
+ // Add this number to the biomes to get the variant
+ biVariant = 128,
+
+ // Release 1.7 biome variants:
+ biSunflowerPlains = 129,
+ biDesertM = 130,
+ biExtremeHillsM = 131,
+ biFlowerForest = 132,
+ biTaigaM = 133,
+ biSwamplandM = 134,
+ biIcePlainsSpikes = 140,
+ biJungleM = 149,
+ biJungleEdgeM = 151,
+ biBirchForestM = 155,
+ biBirchForestHillsM = 156,
+ biRoofedForestM = 157,
+ biColdTaigaM = 158,
+ biMegaSpruceTaiga = 160,
+ biMegaSpruceTaigaHills = 161,
+ biExtremeHillsPlusM = 162,
+ biSavannaM = 163,
+ biSavannaPlateauM = 164,
+ biMesaBryce = 165,
+ biMesaPlateauFM = 166,
+ biMesaPlateauM = 167,
} ;
// tolua_end
diff --git a/source/Entities/Entity.h b/source/Entities/Entity.h
index d6c449b92..c6b70a7fc 100644
--- a/source/Entities/Entity.h
+++ b/source/Entities/Entity.h
@@ -90,6 +90,13 @@ public:
ENTITY_STATUS_WOLF_SHAKING = 8,
ENTITY_STATUS_EATING_ACCEPTED = 9,
ENTITY_STATUS_SHEEP_EATING = 10,
+ ENTITY_STATUS_GOLEM_ROSING = 11,
+ ENTITY_STATUS_VILLAGER_HEARTS = 12,
+ ENTITY_STATUS_VILLAGER_ANGRY = 13,
+ ENTITY_STATUS_VILLAGER_HAPPY = 14,
+ ENTITY_STATUS_WITCH_MAGICKING = 15,
+ // It seems 16 (zombie conversion) is now done with metadata
+ ENTITY_STATUS_FIREWORK_EXPLODE= 17,
} ;
enum
@@ -113,12 +120,14 @@ public:
eEntityType GetEntityType(void) const { return m_EntityType; }
- bool IsPlayer (void) const { return (m_EntityType == etPlayer); }
- bool IsPickup (void) const { return (m_EntityType == etPickup); }
- bool IsMob (void) const { return (m_EntityType == etMob); }
- bool IsMinecart(void) const { return (m_EntityType == etMinecart); }
- bool IsBoat (void) const { return (m_EntityType == etBoat); }
- bool IsTNT (void) const { return (m_EntityType == etTNT); }
+ bool IsPlayer (void) const { return (m_EntityType == etPlayer); }
+ bool IsPickup (void) const { return (m_EntityType == etPickup); }
+ bool IsMob (void) const { return (m_EntityType == etMonster); }
+ bool IsFallingBlock(void) const { return (m_EntityType == etFallingBlock); }
+ bool IsMinecart (void) const { return (m_EntityType == etMinecart); }
+ bool IsBoat (void) const { return (m_EntityType == etBoat); }
+ bool IsTNT (void) const { return (m_EntityType == etTNT); }
+ bool IsProjectile (void) const { return (m_EntityType == etProjectile); }
/// 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;
@@ -324,12 +333,13 @@ public:
// tolua_begin
- // Metadata flags; descendants may override the defaults:
+ // COMMON metadata flags; descendants may override the defaults:
virtual bool IsOnFire (void) const {return (m_TicksLeftBurning > 0); }
virtual bool IsCrouched (void) const {return false; }
virtual bool IsRiding (void) const {return false; }
virtual bool IsSprinting(void) const {return false; }
virtual bool IsRclking (void) const {return false; }
+ virtual bool IsInvisible(void) const {return false; }
// tolua_end
diff --git a/source/Entities/Minecart.cpp b/source/Entities/Minecart.cpp
index 95bad6570..f75e23d8b 100644
--- a/source/Entities/Minecart.cpp
+++ b/source/Entities/Minecart.cpp
@@ -17,7 +17,8 @@
cMinecart::cMinecart(ePayload a_Payload, double a_X, double a_Y, double a_Z) :
super(etMinecart, a_X, a_Y, a_Z, 0.98, 0.7),
- m_Payload(a_Payload)
+ m_Payload(a_Payload),
+ m_LastDamage(0)
{
SetMass(20.f);
SetMaxHealth(6);
@@ -357,11 +358,51 @@ void cMinecart::HandleRailPhysics(float a_Dt, cChunk & a_Chunk)
void cMinecart::DoTakeDamage(TakeDamageInfo & TDI)
{
+ m_LastDamage = TDI.FinalDamage;
super::DoTakeDamage(TDI);
+ m_World->BroadcastEntityMetadata(*this);
+
if (GetHealth() <= 0)
{
Destroy(true);
+
+ cItems Drops;
+ switch (m_Payload)
+ {
+ case mpNone:
+ {
+ Drops.push_back(cItem(E_ITEM_MINECART, 1, 0));
+ break;
+ }
+ case mpChest:
+ {
+ Drops.push_back(cItem(E_ITEM_CHEST_MINECART, 1, 0));
+ break;
+ }
+ case mpFurnace:
+ {
+ Drops.push_back(cItem(E_ITEM_FURNACE_MINECART, 1, 0));
+ break;
+ }
+ case mpTNT:
+ {
+ Drops.push_back(cItem(E_ITEM_MINECART_WITH_TNT, 1, 0));
+ break;
+ }
+ case mpHopper:
+ {
+ Drops.push_back(cItem(E_ITEM_MINECART_WITH_HOPPER, 1, 0));
+ break;
+ }
+ default:
+ {
+ ASSERT(!"Unhandled minecart type when spawning pickup!");
+ return;
+ }
+ }
+
+ m_World->SpawnItemPickups(Drops, GetPosX(), GetPosY(), GetPosZ());
}
}
@@ -447,7 +488,8 @@ void cMinecartWithChest::OnRightClicked(cPlayer & a_Player)
// cMinecartWithFurnace:
cMinecartWithFurnace::cMinecartWithFurnace(double a_X, double a_Y, double a_Z) :
- super(mpFurnace, a_X, a_Y, a_Z)
+ super(mpFurnace, a_X, a_Y, a_Z),
+ m_IsFueled(false)
{
}
@@ -457,8 +499,16 @@ cMinecartWithFurnace::cMinecartWithFurnace(double a_X, double a_Y, double a_Z) :
void cMinecartWithFurnace::OnRightClicked(cPlayer & a_Player)
{
- // Try to power the furnace with whatever the player is holding
- // TODO
+ if (a_Player.GetEquippedItem().m_ItemType == E_ITEM_COAL)
+ {
+ if (!a_Player.IsGameModeCreative())
+ {
+ a_Player.GetInventory().RemoveOneEquippedItem();
+ }
+
+ m_IsFueled = true;
+ m_World->BroadcastEntityMetadata(*this);
+ }
}
diff --git a/source/Entities/Minecart.h b/source/Entities/Minecart.h
index 0152f5dfc..b1b48be4e 100644
--- a/source/Entities/Minecart.h
+++ b/source/Entities/Minecart.h
@@ -50,16 +50,19 @@ public:
// cEntity overrides:
virtual void SpawnOn(cClientHandle & a_ClientHandle) override;
virtual void HandlePhysics(float a_Dt, cChunk & a_Chunk) override;
- void HandleRailPhysics(float a_Dt, cChunk & a_Chunk);
virtual void DoTakeDamage(TakeDamageInfo & TDI) override;
-
+ int LastDamage(void) const { return m_LastDamage; }
+ void HandleRailPhysics(float a_Dt, cChunk & a_Chunk);
ePayload GetPayload(void) const { return m_Payload; }
protected:
ePayload m_Payload;
cMinecart(ePayload a_Payload, double a_X, double a_Y, double a_Z);
+
+ int m_LastDamage;
+
} ;
@@ -127,6 +130,12 @@ public:
// cEntity overrides:
virtual void OnRightClicked(cPlayer & a_Player) override;
+ bool IsFueled (void) const { return m_IsFueled; }
+
+private:
+
+ bool m_IsFueled;
+
} ;
diff --git a/source/HTTPServer/HTTPFormParser.cpp b/source/HTTPServer/HTTPFormParser.cpp
index 7db7b4e6d..596db424e 100644
--- a/source/HTTPServer/HTTPFormParser.cpp
+++ b/source/HTTPServer/HTTPFormParser.cpp
@@ -52,6 +52,18 @@ cHTTPFormParser::cHTTPFormParser(cHTTPRequest & a_Request, cCallbacks & a_Callba
+cHTTPFormParser::cHTTPFormParser(eKind a_Kind, const char * a_Data, int a_Size, cCallbacks & a_Callbacks) :
+ m_Callbacks(a_Callbacks),
+ m_Kind(a_Kind),
+ m_IsValid(true)
+{
+ Parse(a_Data, a_Size);
+}
+
+
+
+
+
void cHTTPFormParser::Parse(const char * a_Data, int a_Size)
{
if (!m_IsValid)
diff --git a/source/HTTPServer/HTTPFormParser.h b/source/HTTPServer/HTTPFormParser.h
index b92ef9d3c..a554ca5a4 100644
--- a/source/HTTPServer/HTTPFormParser.h
+++ b/source/HTTPServer/HTTPFormParser.h
@@ -26,6 +26,13 @@ class cHTTPFormParser :
public cMultipartParser::cCallbacks
{
public:
+ enum eKind
+ {
+ fpkURL, ///< The form has been transmitted as parameters to a GET request
+ fpkFormUrlEncoded, ///< The form has been POSTed or PUT, with Content-Type of "application/x-www-form-urlencoded"
+ fpkMultipart, ///< The form has been POSTed or PUT, with Content-Type of "multipart/form-data"
+ } ;
+
class cCallbacks
{
public:
@@ -40,8 +47,12 @@ public:
} ;
+ /// Creates a parser that is tied to a request and notifies of various events using a callback mechanism
cHTTPFormParser(cHTTPRequest & a_Request, cCallbacks & a_Callbacks);
+ /// Creates a parser with the specified content type that reads data from a string
+ cHTTPFormParser(eKind a_Kind, const char * a_Data, int a_Size, cCallbacks & a_Callbacks);
+
/// Adds more data into the parser, as the request body is received
void Parse(const char * a_Data, int a_Size);
@@ -54,12 +65,6 @@ public:
static bool HasFormData(const cHTTPRequest & a_Request);
protected:
- enum eKind
- {
- fpkURL, ///< The form has been transmitted as parameters to a GET request
- fpkFormUrlEncoded, ///< The form has been POSTed or PUT, with Content-Type of "application/x-www-form-urlencoded"
- fpkMultipart, ///< The form has been POSTed or PUT, with Content-Type of "multipart/form-data"
- };
/// The callbacks to call for incoming file data
cCallbacks & m_Callbacks;
diff --git a/source/ManualBindings.cpp b/source/ManualBindings.cpp
index 4242db46a..e6605ddb0 100644
--- a/source/ManualBindings.cpp
+++ b/source/ManualBindings.cpp
@@ -2096,6 +2096,7 @@ void ManualBindings::Bind(lua_State * tolua_S)
tolua_function(tolua_S, "ForEachPlayer", tolua_ForEach< cWorld, cPlayer, &cWorld::ForEachPlayer>);
tolua_function(tolua_S, "GetBlockInfo", tolua_cWorld_GetBlockInfo);
tolua_function(tolua_S, "GetBlockTypeMeta", tolua_cWorld_GetBlockTypeMeta);
+ tolua_function(tolua_S, "GetSignLines", tolua_cWorld_GetSignLines);
tolua_function(tolua_S, "QueueTask", tolua_cWorld_QueueTask);
tolua_function(tolua_S, "SetSignLines", tolua_cWorld_SetSignLines);
tolua_function(tolua_S, "TryGetHeight", tolua_cWorld_TryGetHeight);
diff --git a/source/Mobs/Bat.h b/source/Mobs/Bat.h
index 8e4cde29f..0b50e06cd 100644
--- a/source/Mobs/Bat.h
+++ b/source/Mobs/Bat.h
@@ -14,12 +14,13 @@ class cBat :
public:
cBat(void) :
- // TODO: The size is only a guesstimate, measure in vanilla and fix the size values here
- super("Bat", 65, "mob.bat.hurt", "mob.bat.death", 0.7, 0.7)
+ super("Bat", 65, "mob.bat.hurt", "mob.bat.death", 0.5, 0.9)
{
}
CLASS_PROTODEF(cBat);
+
+ bool IsHanging(void) const {return false; }
} ;
diff --git a/source/Mobs/Cavespider.cpp b/source/Mobs/Cavespider.cpp
index 569aadcc4..2d50b391a 100644
--- a/source/Mobs/Cavespider.cpp
+++ b/source/Mobs/Cavespider.cpp
@@ -9,8 +9,7 @@
cCavespider::cCavespider(void) :
- // TODO: The size is only a guesstimate, measure in vanilla and fix the size values here
- super("Cavespider", 59, "mob.spider.say", "mob.spider.death", 0.9, 0.6)
+ super("Cavespider", 59, "mob.spider.say", "mob.spider.death", 0.7, 0.5)
{
}
diff --git a/source/Mobs/Creeper.cpp b/source/Mobs/Creeper.cpp
index 9b1b68b79..b41b05f42 100644
--- a/source/Mobs/Creeper.cpp
+++ b/source/Mobs/Creeper.cpp
@@ -2,13 +2,16 @@
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#include "Creeper.h"
+#include "../World.h"
cCreeper::cCreeper(void) :
- super("Creeper", 50, "mob.creeper.say", "mob.creeper.say", 0.6, 1.8)
+ super("Creeper", 50, "mob.creeper.say", "mob.creeper.say", 0.6, 1.8),
+ m_bIsBlowing(false),
+ m_bIsCharged(false)
{
}
@@ -26,3 +29,19 @@ void cCreeper::GetDrops(cItems & a_Drops, cEntity * a_Killer)
+
+void cCreeper::DoTakeDamage(TakeDamageInfo & a_TDI)
+{
+ super::DoTakeDamage(a_TDI);
+
+ if (a_TDI.DamageType == dtLightning)
+ {
+ m_bIsCharged = true;
+ }
+
+ m_World->BroadcastEntityMetadata(*this);
+}
+
+
+
+
diff --git a/source/Mobs/Creeper.h b/source/Mobs/Creeper.h
index c1d46f462..c3d4edeae 100644
--- a/source/Mobs/Creeper.h
+++ b/source/Mobs/Creeper.h
@@ -18,6 +18,15 @@ public:
CLASS_PROTODEF(cCreeper);
virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override;
+ virtual void DoTakeDamage(TakeDamageInfo & a_TDI) override;
+
+ bool IsBlowing(void) const {return m_bIsBlowing; }
+ bool IsCharged(void) const {return m_bIsCharged; }
+
+private:
+
+ bool m_bIsBlowing, m_bIsCharged;
+
} ;
diff --git a/source/Mobs/Enderman.cpp b/source/Mobs/Enderman.cpp
index 1dc47876f..c0ea3d6aa 100644
--- a/source/Mobs/Enderman.cpp
+++ b/source/Mobs/Enderman.cpp
@@ -8,8 +8,10 @@
cEnderman::cEnderman(void) :
- // TODO: The size is only a guesstimate, measure in vanilla and fix the size values here
- super("Enderman", 58, "mob.endermen.hit", "mob.endermen.death", 0.5, 2.5)
+ super("Enderman", 58, "mob.endermen.hit", "mob.endermen.death", 0.5, 2.9),
+ m_bIsScreaming(false),
+ CarriedBlock(E_BLOCK_AIR),
+ CarriedMeta(0)
{
}
diff --git a/source/Mobs/Enderman.h b/source/Mobs/Enderman.h
index c4f4ee364..32e40e70b 100644
--- a/source/Mobs/Enderman.h
+++ b/source/Mobs/Enderman.h
@@ -18,6 +18,17 @@ public:
CLASS_PROTODEF(cEnderman);
virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override;
+
+ bool IsScreaming(void) const {return m_bIsScreaming; }
+ BLOCKTYPE GetCarriedBlock(void) const {return CarriedBlock; }
+ NIBBLETYPE GetCarriedMeta(void) const {return CarriedMeta; }
+
+private:
+
+ bool m_bIsScreaming;
+ BLOCKTYPE CarriedBlock;
+ NIBBLETYPE CarriedMeta;
+
} ;
diff --git a/source/Mobs/Ghast.h b/source/Mobs/Ghast.h
index f9b60dfcf..a2adc21b9 100644
--- a/source/Mobs/Ghast.h
+++ b/source/Mobs/Ghast.h
@@ -18,6 +18,8 @@ public:
CLASS_PROTODEF(cGhast);
virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override;
+
+ bool IsCharging(void) const {return false; }
} ;
diff --git a/source/Mobs/Horse.cpp b/source/Mobs/Horse.cpp
index 05ac73c15..46e7969cc 100644
--- a/source/Mobs/Horse.cpp
+++ b/source/Mobs/Horse.cpp
@@ -2,13 +2,28 @@
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#include "Horse.h"
+#include "../World.h"
+#include "../Entities/Player.h"
-cHorse::cHorse(void) :
- super("Horse", 100, "mob.horse.hit", "mob.horse.death", 1.4, 1.6)
+cHorse::cHorse(int Type, int Color, int Style, int TameTimes) :
+ super("Horse", 100, "mob.horse.hit", "mob.horse.death", 1.4, 1.6),
+ m_bHasChest(false),
+ m_bIsEating(false),
+ m_bIsRearing(false),
+ m_bIsMouthOpen(false),
+ m_bIsTame(false),
+ m_bIsSaddled(false),
+ m_Type(Type),
+ m_Color(Color),
+ m_Style(Style),
+ m_Armour(0),
+ m_TimesToTame(TameTimes),
+ m_TameAttemptTimes(0),
+ m_RearTickCount(0)
{
}
@@ -16,6 +31,88 @@ cHorse::cHorse(void) :
+void cHorse::Tick(float a_Dt, cChunk & a_Chunk)
+{
+ super::Tick(a_Dt, a_Chunk);
+
+ if (!m_bIsMouthOpen)
+ {
+ if (m_World->GetTickRandomNumber(50) == 25)
+ {
+ m_bIsMouthOpen = true;
+ }
+ }
+ else
+ {
+ if (m_World->GetTickRandomNumber(10) == 5)
+ {
+ m_bIsMouthOpen = false;
+ }
+ }
+
+ if ((m_Attachee != NULL) && (!m_bIsTame))
+ {
+ if (m_TameAttemptTimes < m_TimesToTame)
+ {
+ if (m_World->GetTickRandomNumber(50) == 25)
+ {
+ m_World->BroadcastSoundParticleEffect(2000, (int)(floor(GetPosX()) * 8), (int)(floor(GetPosY()) * 8), (int)(floor(GetPosZ()) * 8), 0);
+ m_World->BroadcastSoundParticleEffect(2000, (int)(floor(GetPosX()) * 8), (int)(floor(GetPosY()) * 8), (int)(floor(GetPosZ()) * 8), 2);
+ m_World->BroadcastSoundParticleEffect(2000, (int)(floor(GetPosX()) * 8), (int)(floor(GetPosY()) * 8), (int)(floor(GetPosZ()) * 8), 6);
+ m_World->BroadcastSoundParticleEffect(2000, (int)(floor(GetPosX()) * 8), (int)(floor(GetPosY()) * 8), (int)(floor(GetPosZ()) * 8), 8);
+
+ m_Attachee->Detach();
+ m_bIsRearing = true;
+ }
+ }
+ else
+ {
+ m_bIsTame = true;
+ }
+ }
+
+ if (m_bIsRearing)
+ {
+ if (m_RearTickCount == 20)
+ {
+ m_bIsRearing = false;
+ }
+ else { m_RearTickCount++;}
+ }
+
+ m_World->BroadcastEntityMetadata(*this);
+}
+
+
+
+
+
+void cHorse::OnRightClicked(cPlayer & a_Player)
+{
+ if (m_Attachee != NULL)
+ {
+ if (m_Attachee->GetUniqueID() == a_Player.GetUniqueID())
+ {
+ a_Player.Detach();
+ return;
+ }
+
+ if (m_Attachee->IsPlayer())
+ {
+ return;
+ }
+
+ m_Attachee->Detach();
+ }
+
+ m_TameAttemptTimes++;
+ a_Player.AttachTo(this);
+}
+
+
+
+
+
void cHorse::GetDrops(cItems & a_Drops, cEntity * a_Killer)
{
AddRandomDropItem(a_Drops, 0, 2, E_ITEM_LEATHER);
diff --git a/source/Mobs/Horse.h b/source/Mobs/Horse.h
index 83e64308e..be0c23f9b 100644
--- a/source/Mobs/Horse.h
+++ b/source/Mobs/Horse.h
@@ -13,11 +13,30 @@ class cHorse :
typedef cPassiveMonster super;
public:
- cHorse(void);
+ cHorse(int Type, int Color, int Style, int TameTimes);
CLASS_PROTODEF(cHorse);
virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override;
+ virtual void Tick(float a_Dt, cChunk & a_Chunk) override;
+ virtual void OnRightClicked(cPlayer & a_Player) override;
+
+ bool IsSaddled (void) const {return m_bIsSaddled; }
+ bool IsChested (void) const {return m_bHasChest; }
+ bool IsEating (void) const {return m_bIsEating; }
+ bool IsRearing (void) const {return m_bIsRearing; }
+ bool IsMthOpen (void) const {return m_bIsMouthOpen; }
+ bool IsTame (void) const {return m_bIsTame; }
+ int GetHorseType (void) const {return m_Type; }
+ int GetHorseColor (void) const {return m_Color; }
+ int GetHorseStyle (void) const {return m_Style; }
+ int GetHorseArmour (void) const {return m_Armour;}
+
+private:
+
+ bool m_bHasChest, m_bIsEating, m_bIsRearing, m_bIsMouthOpen, m_bIsTame, m_bIsSaddled;
+ int m_Type, m_Color, m_Style, m_Armour, m_TimesToTame, m_TameAttemptTimes, m_RearTickCount;
+
} ;
diff --git a/source/Mobs/Magmacube.h b/source/Mobs/Magmacube.h
index 80a1d0701..130952970 100644
--- a/source/Mobs/Magmacube.h
+++ b/source/Mobs/Magmacube.h
@@ -19,6 +19,7 @@ public:
CLASS_PROTODEF(cMagmaCube);
virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override;
+ int GetSize(void) const { return m_Size; }
protected:
diff --git a/source/Mobs/Monster.cpp b/source/Mobs/Monster.cpp
index 76b9414f7..334229a42 100644
--- a/source/Mobs/Monster.cpp
+++ b/source/Mobs/Monster.cpp
@@ -24,7 +24,7 @@
cMonster::cMonster(const AString & a_ConfigName, char a_ProtocolMobType, const AString & a_SoundHurt, const AString & a_SoundDeath, double a_Width, double a_Height)
- : super(etMob, a_Width, a_Height)
+ : super(etMonster, a_Width, a_Height)
, m_Target(NULL)
, m_AttackRate(3)
, idle_interval(0)
diff --git a/source/Mobs/Monster.h b/source/Mobs/Monster.h
index b2676f5b1..d784f2eec 100644
--- a/source/Mobs/Monster.h
+++ b/source/Mobs/Monster.h
@@ -113,6 +113,11 @@ public:
/// Sets whether the mob burns in daylight. Only evaluated at next burn-decision tick
void SetBurnsInDaylight(bool a_BurnsInDaylight) { m_BurnsInDaylight = a_BurnsInDaylight; }
+
+ // Overridables to handle ageable mobs
+ virtual bool IsBaby (void) const { return false; }
+ virtual bool IsTame (void) const { return false; }
+ virtual bool IsSitting (void) const { return false; }
enum MState{ATTACKING, IDLE, CHASING, ESCAPING} m_EMState;
enum MPersonality{PASSIVE,AGGRESSIVE,COWARDLY} m_EMPersonality;
@@ -147,6 +152,7 @@ protected:
void AddRandomDropItem(cItems & a_Drops, unsigned int a_Min, unsigned int a_Max, short a_Item, short a_ItemHealth = 0);
void HandleDaylightBurning(cChunk & a_Chunk);
+
} ; // tolua_export
diff --git a/source/Mobs/Ocelot.h b/source/Mobs/Ocelot.h
index 98ea224e2..6d24c5672 100644
--- a/source/Mobs/Ocelot.h
+++ b/source/Mobs/Ocelot.h
@@ -14,8 +14,7 @@ class cOcelot :
public:
cOcelot(void) :
- // TODO: The size is only a guesstimate, measure in vanilla and fix the size values here
- super("Ocelot", 98, "mob.cat.hitt", "mob.cat.hitt", 0.9, 0.5)
+ super("Ocelot", 98, "mob.cat.hitt", "mob.cat.hitt", 0.6, 0.8)
{
}
diff --git a/source/Mobs/Pig.cpp b/source/Mobs/Pig.cpp
index 9df2c2571..cd18c087f 100644
--- a/source/Mobs/Pig.cpp
+++ b/source/Mobs/Pig.cpp
@@ -2,13 +2,16 @@
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#include "Pig.h"
+#include "../Entities/Player.h"
+#include "../World.h"
cPig::cPig(void) :
- super("Pig", 90, "mob.pig.say", "mob.pig.death", 0.9, 0.9)
+ super("Pig", 90, "mob.pig.say", "mob.pig.death", 0.9, 0.9),
+ m_bIsSaddled(false)
{
}
@@ -24,3 +27,47 @@ void cPig::GetDrops(cItems & a_Drops, cEntity * a_Killer)
+
+void cPig::OnRightClicked(cPlayer & a_Player)
+{
+ if (m_bIsSaddled)
+ {
+ if (m_Attachee != NULL)
+ {
+ if (m_Attachee->GetUniqueID() == a_Player.GetUniqueID())
+ {
+ // This player is already sitting in, they want out.
+ a_Player.Detach();
+ return;
+ }
+
+ if (m_Attachee->IsPlayer())
+ {
+ // Another player is already sitting in here, cannot attach
+ return;
+ }
+
+ // Detach whatever is sitting in this pig now:
+ m_Attachee->Detach();
+ }
+
+ // Attach the player to this pig
+ a_Player.AttachTo(this);
+ }
+ else if (a_Player.GetEquippedItem().m_ItemType == E_ITEM_SADDLE)
+ {
+ if (!a_Player.IsGameModeCreative())
+ {
+ a_Player.GetInventory().RemoveOneEquippedItem();
+ }
+
+ // Set saddle state & broadcast metadata
+ m_bIsSaddled = true;
+ m_World->BroadcastEntityMetadata(*this);
+ }
+}
+
+
+
+
+
diff --git a/source/Mobs/Pig.h b/source/Mobs/Pig.h
index ae790ac2f..4fd0d8db8 100644
--- a/source/Mobs/Pig.h
+++ b/source/Mobs/Pig.h
@@ -18,6 +18,13 @@ public:
CLASS_PROTODEF(cPig);
virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override;
+ virtual void OnRightClicked(cPlayer & a_Player) override;
+ bool IsSaddled(void) const { return m_bIsSaddled; }
+
+private:
+
+ bool m_bIsSaddled;
+
} ;
diff --git a/source/Mobs/Sheep.cpp b/source/Mobs/Sheep.cpp
index 2f371f384..440c5c2b9 100644
--- a/source/Mobs/Sheep.cpp
+++ b/source/Mobs/Sheep.cpp
@@ -3,15 +3,17 @@
#include "Sheep.h"
#include "../BlockID.h"
+#include "../Entities/Player.h"
+#include "../World.h"
-cSheep::cSheep(void) :
+cSheep::cSheep(int a_Color) :
super("Sheep", 91, "mob.sheep.say", "mob.sheep.say", 0.6, 1.3),
m_IsSheared(false),
- m_WoolColor(E_META_WOOL_WHITE)
+ m_WoolColor(a_Color)
{
}
@@ -30,3 +32,25 @@ void cSheep::GetDrops(cItems & a_Drops, cEntity * a_Killer)
+
+void cSheep::OnRightClicked(cPlayer & a_Player)
+{
+ if ((a_Player.GetEquippedItem().m_ItemType == E_ITEM_SHEARS) && (!m_IsSheared))
+ {
+ m_IsSheared = true;
+ m_World->BroadcastEntityMetadata(*this);
+
+ if (!a_Player.IsGameModeCreative())
+ {
+ a_Player.UseEquippedItem();
+ }
+
+ cItems Drops;
+ Drops.push_back(cItem(E_BLOCK_WOOL, 4, m_WoolColor));
+ m_World->SpawnItemPickups(Drops, GetPosX(), GetPosY(), GetPosZ(), 10);
+ }
+}
+
+
+
+
diff --git a/source/Mobs/Sheep.h b/source/Mobs/Sheep.h
index 369fc78c5..8293a2c05 100644
--- a/source/Mobs/Sheep.h
+++ b/source/Mobs/Sheep.h
@@ -13,14 +13,20 @@ class cSheep :
typedef cPassiveMonster super;
public:
- cSheep(void);
+ cSheep(int a_Color);
- bool m_IsSheared;
- NIBBLETYPE m_WoolColor; // Uses E_META_WOOL_ constants for colors
-
CLASS_PROTODEF(cSheep);
-
+
virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override;
+ virtual void OnRightClicked(cPlayer & a_Player) override;
+ bool IsSheared(void) const { return m_IsSheared; }
+ int GetFurColor(void) const { return m_WoolColor; }
+
+private:
+
+ bool m_IsSheared;
+ int m_WoolColor;
+
} ;
diff --git a/source/Mobs/Silverfish.h b/source/Mobs/Silverfish.h
index 7d675a9c0..d632ac169 100644
--- a/source/Mobs/Silverfish.h
+++ b/source/Mobs/Silverfish.h
@@ -14,8 +14,7 @@ class cSilverfish :
public:
cSilverfish(void) :
- // TODO: The size is only a guesstimate, measure in vanilla and fix the size values here
- super("Silverfish", 60, "mob.silverfish.hit", "mob.silverfish.kill", 0.9, 0.3)
+ super("Silverfish", 60, "mob.silverfish.hit", "mob.silverfish.kill", 0.3, 0.7)
{
}
diff --git a/source/Mobs/Skeleton.cpp b/source/Mobs/Skeleton.cpp
index 10dad4065..6297b867c 100644
--- a/source/Mobs/Skeleton.cpp
+++ b/source/Mobs/Skeleton.cpp
@@ -8,8 +8,9 @@
-cSkeleton::cSkeleton(void) :
- super("Skeleton", 51, "mob.skeleton.hurt", "mob.skeleton.death", 0.6, 1.8)
+cSkeleton::cSkeleton(bool IsWither) :
+ super("Skeleton", 51, "mob.skeleton.hurt", "mob.skeleton.death", 0.6, 1.8),
+ m_bIsWither(IsWither)
{
SetBurnsInDaylight(true);
}
diff --git a/source/Mobs/Skeleton.h b/source/Mobs/Skeleton.h
index d0a2da490..7a4af7e22 100644
--- a/source/Mobs/Skeleton.h
+++ b/source/Mobs/Skeleton.h
@@ -13,11 +13,17 @@ class cSkeleton :
typedef cAggressiveMonster super;
public:
- cSkeleton();
+ cSkeleton(bool IsWither);
CLASS_PROTODEF(cSkeleton);
virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override;
+ bool IsWither(void) const { return m_bIsWither; };
+
+private:
+
+ bool m_bIsWither;
+
} ;
diff --git a/source/Mobs/Slime.cpp b/source/Mobs/Slime.cpp
index b209ac869..7a9487a06 100644
--- a/source/Mobs/Slime.cpp
+++ b/source/Mobs/Slime.cpp
@@ -3,8 +3,6 @@
#include "Slime.h"
-// TODO: Implement sized slimes
-
diff --git a/source/Mobs/Slime.h b/source/Mobs/Slime.h
index 88136ff32..782c3113f 100644
--- a/source/Mobs/Slime.h
+++ b/source/Mobs/Slime.h
@@ -19,6 +19,7 @@ public:
CLASS_PROTODEF(cSlime);
virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override;
+ int GetSize(void) const { return m_Size; }
protected:
diff --git a/source/Mobs/Villager.cpp b/source/Mobs/Villager.cpp
index 98e5276e1..97d6dc3ca 100644
--- a/source/Mobs/Villager.cpp
+++ b/source/Mobs/Villager.cpp
@@ -2,16 +2,34 @@
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#include "Villager.h"
+#include "../World.h"
-cVillager::cVillager(void) :
- super("Villager", 120, "", "", 0.6, 1.8)
+cVillager::cVillager(eVillagerType VillagerType) :
+ super("Villager", 120, "", "", 0.6, 1.8),
+ m_Type(VillagerType)
{
}
+
+void cVillager::DoTakeDamage(TakeDamageInfo & a_TDI)
+{
+ super::DoTakeDamage(a_TDI);
+ if (a_TDI.Attacker->IsPlayer())
+ {
+ if (m_World->GetTickRandomNumber(5) == 3)
+ {
+ m_World->BroadcastEntityStatus(*this, ENTITY_STATUS_VILLAGER_ANGRY);
+ }
+ }
+}
+
+
+
+
diff --git a/source/Mobs/Villager.h b/source/Mobs/Villager.h
index 92267a979..4cd9aaa8e 100644
--- a/source/Mobs/Villager.h
+++ b/source/Mobs/Villager.h
@@ -13,9 +13,29 @@ class cVillager :
typedef cPassiveMonster super;
public:
- cVillager();
+
+ enum eVillagerType
+ {
+ vtFarmer = 0,
+ vtLibrarian = 1,
+ vtPriest = 2,
+ vtBlacksmith = 3,
+ vtButcher = 4,
+ vtGeneric = 5,
+ vtMax
+ } ;
+
+ cVillager(eVillagerType VillagerType);
CLASS_PROTODEF(cVillager);
+
+ virtual void DoTakeDamage(TakeDamageInfo & a_TDI) override;
+ int GetVilType(void) const { return m_Type; }
+
+private:
+
+ int m_Type;
+
} ;
diff --git a/source/Mobs/Witch.h b/source/Mobs/Witch.h
index ce0b49deb..4e637beea 100644
--- a/source/Mobs/Witch.h
+++ b/source/Mobs/Witch.h
@@ -18,6 +18,8 @@ public:
CLASS_PROTODEF(cWitch);
virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override;
+
+ bool IsAngry(void) const {return ((m_EMState == ATTACKING) || (m_EMState == CHASING)); }
} ;
diff --git a/source/Mobs/Wolf.cpp b/source/Mobs/Wolf.cpp
new file mode 100644
index 000000000..e76f991dc
--- /dev/null
+++ b/source/Mobs/Wolf.cpp
@@ -0,0 +1,79 @@
+
+#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
+
+#include "Wolf.h"
+#include "../World.h"
+#include "../Entities/Player.h"
+
+
+
+
+
+cWolf::cWolf(void) :
+ super("Wolf", 95, "mob.wolf.hurt", "mob.wolf.death", 0.6, 0.8),
+ m_bIsAngry(false),
+ m_bIsTame(false),
+ m_bIsSitting(false),
+ m_bIsBegging(false)
+{
+}
+
+
+
+
+
+void cWolf::DoTakeDamage(TakeDamageInfo & a_TDI)
+{
+ super::DoTakeDamage(a_TDI);
+ if (!m_bIsTame)
+ {
+ m_bIsAngry = true;
+ }
+ m_World->BroadcastEntityMetadata(*this); // Broadcast health and possibly angry face
+}
+
+
+
+
+
+void cWolf::OnRightClicked(cPlayer & a_Player)
+{
+ if ((!m_bIsTame) && (!m_bIsAngry))
+ {
+ if (a_Player.GetEquippedItem().m_ItemType == E_ITEM_BONE)
+ {
+ if (!a_Player.IsGameModeCreative())
+ {
+ a_Player.GetInventory().RemoveOneEquippedItem();
+ }
+
+ if (m_World->GetTickRandomNumber(10) == 5)
+ {
+ SetMaxHealth(20);
+ m_bIsTame = true;
+ m_World->BroadcastEntityStatus(*this, ENTITY_STATUS_WOLF_TAMED);
+ }
+ else
+ {
+ m_World->BroadcastEntityStatus(*this, ENTITY_STATUS_WOLF_TAMING);
+ }
+ }
+ }
+ else if (m_bIsTame)
+ {
+ if (m_bIsSitting)
+ {
+ m_bIsSitting = false;
+ }
+ else
+ {
+ m_bIsSitting = true;
+ }
+ }
+
+ m_World->BroadcastEntityMetadata(*this);
+}
+
+
+
+
diff --git a/source/Mobs/Wolf.h b/source/Mobs/Wolf.h
index 405df80a6..98074ba11 100644
--- a/source/Mobs/Wolf.h
+++ b/source/Mobs/Wolf.h
@@ -13,13 +13,25 @@ class cWolf :
typedef cPassiveAggressiveMonster super;
public:
- cWolf(void) :
- // TODO: The size is only a guesstimate, measure in vanilla and fix the size values here (wiki.vg values are suspicious)
- super("Wolf", 95, "mob.wolf.hurt", "mob.wolf.death", 0.9, 0.9)
- {
- }
+ cWolf(void);
CLASS_PROTODEF(cWolf);
+
+ virtual void DoTakeDamage(TakeDamageInfo & a_TDI) override;
+ virtual void OnRightClicked(cPlayer & a_Player) override;
+
+ bool IsSitting(void) const { return m_bIsSitting; }
+ bool IsTame(void) const { return m_bIsTame; }
+ bool IsBegging(void) const { return m_bIsBegging; }
+ bool IsAngry(void) const { return m_bIsAngry; }
+
+private:
+
+ bool m_bIsSitting;
+ bool m_bIsTame;
+ bool m_bIsBegging;
+ bool m_bIsAngry;
+
} ;
diff --git a/source/Mobs/Zombie.cpp b/source/Mobs/Zombie.cpp
index 9b238baef..f495fe5ee 100644
--- a/source/Mobs/Zombie.cpp
+++ b/source/Mobs/Zombie.cpp
@@ -8,8 +8,10 @@
-cZombie::cZombie(void) :
- super("Zombie", 54, "mob.zombie.hurt", "mob.zombie.death", 0.6, 1.8)
+cZombie::cZombie(bool IsVillagerZombie) :
+ super("Zombie", 54, "mob.zombie.hurt", "mob.zombie.death", 0.6, 1.8),
+ m_bIsConverting(false),
+ m_bIsVillagerZombie(IsVillagerZombie)
{
SetBurnsInDaylight(true);
}
diff --git a/source/Mobs/Zombie.h b/source/Mobs/Zombie.h
index 4835a53c4..148b1121e 100644
--- a/source/Mobs/Zombie.h
+++ b/source/Mobs/Zombie.h
@@ -12,11 +12,19 @@ class cZombie :
typedef cAggressiveMonster super;
public:
- cZombie(void);
+ cZombie(bool IsVillagerZombie);
CLASS_PROTODEF(cZombie);
virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override;
+
+ bool IsVillagerZombie(void) const {return m_bIsVillagerZombie; }
+ bool IsConverting(void) const {return m_bIsConverting; }
+
+private:
+
+ bool m_bIsVillagerZombie, m_bIsConverting;
+
} ;
diff --git a/source/Protocol/Protocol125.cpp b/source/Protocol/Protocol125.cpp
index 050132917..fb7315468 100644
--- a/source/Protocol/Protocol125.cpp
+++ b/source/Protocol/Protocol125.cpp
@@ -24,8 +24,27 @@ Documentation:
#include "../UI/Window.h"
#include "../Root.h"
#include "../Server.h"
+
+#include "../Entities/ProjectileEntity.h"
+#include "../Entities/Minecart.h"
#include "../Entities/FallingBlock.h"
+#include "../Mobs/Monster.h"
+#include "../Mobs/Creeper.h"
+#include "../Mobs/Bat.h"
+#include "../Mobs/Pig.h"
+#include "../Mobs/Villager.h"
+#include "../Mobs/Zombie.h"
+#include "../Mobs/Ghast.h"
+#include "../Mobs/Wolf.h"
+#include "../Mobs/Sheep.h"
+#include "../Mobs/Enderman.h"
+#include "../Mobs/Skeleton.h"
+#include "../Mobs/Witch.h"
+#include "../Mobs/Slime.h"
+#include "../Mobs/Magmacube.h"
+#include "../Mobs/Horse.h"
+
@@ -343,8 +362,18 @@ void cProtocol125::SendEntityMetadata(const cEntity & a_Entity)
cCSLock Lock(m_CSPacket);
WriteByte(PACKET_METADATA);
WriteInt (a_Entity.GetUniqueID());
- AString MetaData = GetEntityMetaData(a_Entity);
- SendData(MetaData.data(), MetaData.size());
+
+ WriteCommonMetadata(a_Entity);
+ if (a_Entity.IsMob())
+ {
+ WriteMobMetadata(((const cMonster &)a_Entity));
+ }
+ else
+ {
+ WriteEntityMetadata(a_Entity);
+ }
+ WriteByte(0x7f);
+
Flush();
}
@@ -713,8 +742,11 @@ void cProtocol125::SendSpawnMob(const cMonster & a_Mob)
WriteByte (0);
WriteByte (0);
WriteByte (0);
- AString MetaData = GetEntityMetaData(a_Mob);
- SendData (MetaData.data(), MetaData.size());
+
+ WriteCommonMetadata(a_Mob);
+ WriteMobMetadata(a_Mob);
+ WriteByte(0x7f);
+
Flush();
}
@@ -1617,48 +1649,242 @@ int cProtocol125::ParseItem(cItem & a_Item)
-AString cProtocol125::GetEntityMetaData(const cEntity & a_Entity)
+void cProtocol125::WriteCommonMetadata(const cEntity & a_Entity)
{
- // We should send all the metadata here
- AString MetaData;
- // Common metadata (index 0, byte):
- MetaData.push_back(0);
- MetaData.push_back(GetEntityMetadataFlags(a_Entity));
-
- // TODO: Add more entity-specific metadata
-
- MetaData.push_back(0x7f); // End metadata
- return MetaData;
-}
-
+ Byte CommonMetadata = 0;
-
-
-
-char cProtocol125::GetEntityMetadataFlags(const cEntity & a_Entity)
-{
- char Flags = 0;
if (a_Entity.IsOnFire())
{
- Flags |= 1;
+ CommonMetadata |= 0x1;
}
if (a_Entity.IsCrouched())
{
- Flags |= 2;
+ CommonMetadata |= 0x2;
}
if (a_Entity.IsRiding())
{
- Flags |= 4;
+ CommonMetadata |= 0x4;
}
if (a_Entity.IsSprinting())
{
- Flags |= 8;
+ CommonMetadata |= 0x8;
}
if (a_Entity.IsRclking())
{
- Flags |= 16;
+ CommonMetadata |= 0x10;
+ }
+ if (a_Entity.IsInvisible())
+ {
+ CommonMetadata |= 0x20;
+ }
+
+ WriteByte(0x0);
+ WriteByte(CommonMetadata);
+}
+
+
+
+
+
+void cProtocol125::WriteEntityMetadata(const cEntity & a_Entity)
+{
+ if (a_Entity.IsMinecart())
+ {
+ WriteByte(0x51);
+ // No idea how Mojang makes their carts shakey shakey, so here is a complicated one-liner expression that does something similar
+ WriteInt( (((a_Entity.GetMaxHealth() / 2) - (a_Entity.GetHealth() - (a_Entity.GetMaxHealth() / 2))) * ((const cMinecart &)a_Entity).LastDamage()) * 4 );
+ WriteByte(0x52);
+ WriteInt(1); // Shaking direction, doesn't seem to affect anything
+ WriteByte(0x73);
+ WriteFloat((float)(((const cMinecart &)a_Entity).LastDamage() + 10)); // Damage taken / shake effect multiplyer
+
+ if (((cMinecart &)a_Entity).GetPayload() == cMinecart::mpFurnace)
+ {
+ WriteByte(0x10);
+ WriteByte(((const cMinecartWithFurnace &)a_Entity).IsFueled() ? 1 : 0); // Fueled?
+ }
+ }
+ else if ((a_Entity.IsProjectile() && ((cProjectileEntity &)a_Entity).GetProjectileKind() == cProjectileEntity::pkArrow))
+ {
+ WriteByte(0x10);
+ WriteByte(((const cArrowEntity &)a_Entity).IsCritical() ? 1 : 0); // Critical hitting arrow?
+ }
+}
+
+
+
+
+
+void cProtocol125::WriteMobMetadata(const cMonster & a_Mob)
+{
+ switch (a_Mob.GetMobType())
+ {
+ case cMonster::mtCreeper:
+ {
+ WriteByte(0x10);
+ WriteByte(((const cCreeper &)a_Mob).IsBlowing() ? 1 : -1); // Blowing up?
+ WriteByte(0x11);
+ WriteByte(((const cCreeper &)a_Mob).IsCharged() ? 1 : 0); // Lightning-charged?
+ break;
+ }
+ case cMonster::mtBat:
+ {
+ WriteByte(0x10);
+ WriteByte(((const cBat &)a_Mob).IsHanging() ? 1 : 0); // Upside down?
+ break;
+ }
+ case cMonster::mtPig:
+ {
+ WriteByte(0x10);
+ WriteByte(((const cPig &)a_Mob).IsSaddled() ? 1 : 0); // Saddled?
+ break;
+ }
+ case cMonster::mtVillager:
+ {
+ WriteByte(0x50);
+ WriteInt(((const cVillager &)a_Mob).GetVilType()); // What sort of TESTIFICATE?
+ break;
+ }
+ case cMonster::mtZombie:
+ {
+ WriteByte(0xC);
+ WriteByte(((const cZombie &)a_Mob).IsBaby() ? 1 : 0); // Babby zombie?
+ WriteByte(0xD);
+ WriteByte(((const cZombie &)a_Mob).IsVillagerZombie() ? 1 : 0); // Converted zombie?
+ WriteByte(0xE);
+ WriteByte(((const cZombie &)a_Mob).IsConverting() ? 1 : 0); // Converted-but-converting-back zombllager?
+ break;
+ }
+ case cMonster::mtGhast:
+ {
+ WriteByte(0x10);
+ WriteByte(((const cGhast &)a_Mob).IsCharging()); // About to eject un flamé-bol? :P
+ break;
+ }
+ case cMonster::mtWolf:
+ {
+ Byte WolfStatus = 0;
+ if (((const cWolf &)a_Mob).IsSitting())
+ {
+ WolfStatus |= 0x1;
+ }
+ if (((const cWolf &)a_Mob).IsAngry())
+ {
+ WolfStatus |= 0x2;
+ }
+ if (((const cWolf &)a_Mob).IsTame())
+ {
+ WolfStatus |= 0x4;
+ }
+ WriteByte(0x10);
+ WriteByte(WolfStatus);
+
+ WriteByte(0x72);
+ WriteFloat((float)(a_Mob.GetHealth())); // Tail health-o-meter (only shown when tamed, by the way)
+ WriteByte(0x13);
+ WriteByte(((const cWolf &)a_Mob).IsBegging() ? 1 : 0); // Ultra cute mode?
+ break;
+ }
+ case cMonster::mtSheep:
+ {
+ // [1](1111)
+ // [] = Is sheared? () = Color, from 0 to 15
+
+ WriteByte(0x10);
+ Byte SheepMetadata = 0;
+ SheepMetadata = ((const cSheep &)a_Mob).GetFurColor(); // Fur colour
+
+ if (((const cSheep &)a_Mob).IsSheared()) // Is sheared?
+ {
+ SheepMetadata |= 0x16;
+ }
+ WriteByte(SheepMetadata);
+ break;
+ }
+ case cMonster::mtEnderman:
+ {
+ WriteByte(0x10);
+ WriteByte((Byte)(((const cEnderman &)a_Mob).GetCarriedBlock())); // Block that he stole from your house
+ WriteByte(0x11);
+ WriteByte((Byte)(((const cEnderman &)a_Mob).GetCarriedMeta())); // Meta of block that he stole from your house
+ WriteByte(0x12);
+ WriteByte(((const cEnderman &)a_Mob).IsScreaming() ? 1 : 0); // Screaming at your face?
+ break;
+ }
+ case cMonster::mtSkeleton:
+ {
+ WriteByte(0xD);
+ WriteByte(((const cSkeleton &)a_Mob).IsWither() ? 1 : 0); // It's a skeleton, but it's not
+ break;
+ }
+ case cMonster::mtWitch:
+ {
+ WriteByte(0x15);
+ WriteByte(((const cWitch &)a_Mob).IsAngry() ? 1 : 0); // Aggravated? Doesn't seem to do anything
+ break;
+ }
+ case cMonster::mtSlime:
+ case cMonster::mtMagmaCube:
+ {
+ WriteByte(0x10);
+ if (a_Mob.GetMobType() == cMonster::mtSlime)
+ {
+ WriteByte(((const cSlime &)a_Mob).GetSize()); // Size of slime - HEWGE, meh, cute BABBY SLIME
+ }
+ else
+ {
+ WriteByte(((const cMagmaCube &)a_Mob).GetSize()); // Size of slime - HEWGE, meh, cute BABBY SLIME
+ }
+ break;
+ }
+ case cMonster::mtHorse:
+ {
+ int Flags = 0;
+ if (((const cHorse &)a_Mob).IsTame())
+ {
+ Flags |= 0x2;
+ }
+ if (((const cHorse &)a_Mob).IsSaddled())
+ {
+ Flags |= 0x4;
+ }
+ if (((const cHorse &)a_Mob).IsChested())
+ {
+ Flags |= 0x8;
+ }
+ if (((const cHorse &)a_Mob).IsBaby())
+ {
+ Flags |= 0x10; // IsBred flag, according to wiki.vg - don't think it does anything in multiplayer
+ }
+ if (((const cHorse &)a_Mob).IsEating())
+ {
+ Flags |= 0x20;
+ }
+ if (((const cHorse &)a_Mob).IsRearing())
+ {
+ Flags |= 0x40;
+ }
+ if (((const cHorse &)a_Mob).IsMthOpen())
+ {
+ Flags |= 0x80;
+ }
+ WriteByte(0x50);
+ WriteInt(Flags);
+
+ WriteByte(0x13);
+ WriteByte(((const cHorse &)a_Mob).GetHorseType()); // Type of horse (donkey, chestnut, etc.)
+
+ WriteByte(0x54);
+ int Appearance = 0;
+ Appearance = ((const cHorse &)a_Mob).GetHorseColor(); // Mask FF
+ Appearance |= ((const cHorse &)a_Mob).GetHorseStyle() * 256; // Mask FF00, so multiply by 256
+ WriteInt(Appearance);
+
+ WriteByte(0x56);
+ WriteInt(((const cHorse &)a_Mob).GetHorseArmour()); // Horshey armour
+ break;
+ }
}
- return Flags;
}
diff --git a/source/Protocol/Protocol125.h b/source/Protocol/Protocol125.h
index c5c8cd1a0..ae198780c 100644
--- a/source/Protocol/Protocol125.h
+++ b/source/Protocol/Protocol125.h
@@ -142,11 +142,14 @@ protected:
/// Parses one item, "slot" as the protocol wiki calls it, from m_ReceivedData; returns the usual ParsePacket() codes
virtual int ParseItem(cItem & a_Item);
- /// Returns the entity metadata representation
- AString GetEntityMetaData(const cEntity & a_Entity);
-
- /// Returns the entity common metadata, index 0 (generic flags)
- char GetEntityMetadataFlags(const cEntity & a_Entity);
+ /// Writes the COMMON entity metadata
+ void WriteCommonMetadata(const cEntity & a_Entity);
+
+ /// Writes normal entity metadata
+ void WriteEntityMetadata(const cEntity & a_Entity);
+
+ /// Writes mobile entity metadata
+ void WriteMobMetadata(const cMonster & a_Mob);
} ;
diff --git a/source/Protocol/Protocol132.cpp b/source/Protocol/Protocol132.cpp
index a06eb0b8b..53159a3b3 100644
--- a/source/Protocol/Protocol132.cpp
+++ b/source/Protocol/Protocol132.cpp
@@ -416,8 +416,11 @@ void cProtocol132::SendSpawnMob(const cMonster & a_Mob)
WriteShort ((short)(a_Mob.GetSpeedX() * 400));
WriteShort ((short)(a_Mob.GetSpeedY() * 400));
WriteShort ((short)(a_Mob.GetSpeedZ() * 400));
- AString MetaData = GetEntityMetaData(a_Mob);
- SendData (MetaData.data(), MetaData.size());
+
+ WriteCommonMetadata(a_Mob);
+ WriteMobMetadata(a_Mob);
+ WriteByte(0x7f);
+
Flush();
}
diff --git a/source/WebAdmin.cpp b/source/WebAdmin.cpp
index 08817139a..316513f11 100644
--- a/source/WebAdmin.cpp
+++ b/source/WebAdmin.cpp
@@ -79,8 +79,14 @@ bool cWebAdmin::Init(void)
return false;
}
- AString PortsIPv4 = m_IniFile.GetValue("WebAdmin", "Port", "8080");
- AString PortsIPv6 = m_IniFile.GetValue("WebAdmin", "PortsIPv6", "");
+ if (!m_IniFile.GetValueSetB("WebAdmin", "Enabled", true))
+ {
+ // WebAdmin is disabled, bail out faking a success
+ return true;
+ }
+
+ AString PortsIPv4 = m_IniFile.GetValueSet("WebAdmin", "Port", "8080");
+ AString PortsIPv6 = m_IniFile.GetValueSet("WebAdmin", "PortsIPv6", "");
if (!m_HTTPServer.Initialize(PortsIPv4, PortsIPv6))
{
@@ -185,8 +191,19 @@ void cWebAdmin::HandleWebadminRequest(cHTTPConnection & a_Connection, cHTTPReque
HTTPfd.Name = itr->first;
TemplateRequest.Request.FormData[itr->first] = HTTPfd;
TemplateRequest.Request.PostParams[itr->first] = itr->second;
- TemplateRequest.Request.Params[itr->first] = itr->second;
} // for itr - Data->m_Form[]
+
+ // Parse the URL into individual params:
+ size_t idxQM = a_Request.GetURL().find('?');
+ if (idxQM != AString::npos)
+ {
+ cHTTPFormParser URLParams(cHTTPFormParser::fpkURL, a_Request.GetURL().c_str() + idxQM + 1, a_Request.GetURL().length() - idxQM - 1, *Data);
+ URLParams.Finish();
+ for (cHTTPFormParser::const_iterator itr = URLParams.begin(), end = URLParams.end(); itr != end; ++itr)
+ {
+ TemplateRequest.Request.Params[itr->first] = itr->second;
+ } // for itr - URLParams[]
+ }
}
// Try to get the template from the Lua template script
diff --git a/source/WebAdmin.h b/source/WebAdmin.h
index 16b5dd4dc..72c77ddfb 100644
--- a/source/WebAdmin.h
+++ b/source/WebAdmin.h
@@ -56,8 +56,14 @@ struct HTTPRequest
AString Path;
AString Username;
// tolua_end
+
+ /// Parameters given in the URL, after the questionmark
StringStringMap Params; // >> EXPORTED IN MANUALBINDINGS <<
+
+ /// Parameters posted as a part of a form - either in the URL (GET method) or in the body (POST method)
StringStringMap PostParams; // >> EXPORTED IN MANUALBINDINGS <<
+
+ /// Same as PostParams
FormDataMap FormData; // >> EXPORTED IN MANUALBINDINGS <<
} ; // tolua_export
diff --git a/source/World.cpp b/source/World.cpp
index 2b8fe54c6..8a482f0ad 100644
--- a/source/World.cpp
+++ b/source/World.cpp
@@ -2591,40 +2591,57 @@ bool cWorld::IsBlockDirectlyWatered(int a_BlockX, int a_BlockY, int a_BlockZ)
int cWorld::SpawnMob(double a_PosX, double a_PosY, double a_PosZ, cMonster::eType a_MonsterType)
{
cMonster * Monster = NULL;
-
- int Size = GetTickRandomNumber(2) + 1; // 1 .. 3
+
+ int SlSize = GetTickRandomNumber(2) + 1; // 1 .. 3 - Slime
+ int ShColor = GetTickRandomNumber(15); // 0 .. 15 - Sheep
+ bool SkType = GetDimension() == biNether; // Skeleton
+
+ int VilType = GetTickRandomNumber(cVillager::vtMax); // 0 .. 6 - Villager
+ if (VilType == 6) { VilType = 0; } // Give farmers a better chance of spawning
+
+ int HseType = GetTickRandomNumber(7); // 0 .. 7 - Horse Type (donkey, zombie, etc.)
+ int HseColor = GetTickRandomNumber(6); // 0 .. 6 - Horse
+ int HseStyle = GetTickRandomNumber(4); // 0 .. 4 - Horse
+ int HseTameTimes = GetTickRandomNumber(6) + 1; // 1 .. 7 - Horse tame amount
+ if ((HseType == 5) || (HseType == 6) || (HseType == 7)) { HseType = 0; } // 5,6,7 = 0 because little chance of getting 0 with TickRand
switch (a_MonsterType)
{
- case cMonster::mtBat: Monster = new cBat(); break;
- case cMonster::mtBlaze: Monster = new cBlaze(); break;
- case cMonster::mtCaveSpider: Monster = new cCavespider(); break;
- case cMonster::mtChicken: Monster = new cChicken(); break;
- case cMonster::mtCow: Monster = new cCow(); break;
- case cMonster::mtCreeper: Monster = new cCreeper(); break;
- case cMonster::mtEnderman: Monster = new cEnderman(); break;
- case cMonster::mtEnderDragon: Monster = new cEnderDragon(); break;
- case cMonster::mtGhast: Monster = new cGhast(); break;
- case cMonster::mtGiant: Monster = new cGiant(); break;
- case cMonster::mtHorse: Monster = new cHorse(); break;
- case cMonster::mtIronGolem: Monster = new cIronGolem(); break;
- case cMonster::mtMagmaCube: Monster = new cMagmaCube(Size); break;
- case cMonster::mtMooshroom: Monster = new cMooshroom(); break;
- case cMonster::mtOcelot: Monster = new cOcelot(); break;
- case cMonster::mtPig: Monster = new cPig(); break;
- case cMonster::mtSheep: Monster = new cSheep(); break;
- case cMonster::mtSilverfish: Monster = new cSilverfish(); break;
- case cMonster::mtSkeleton: Monster = new cSkeleton(); break;
- case cMonster::mtSlime: Monster = new cSlime(Size); break;
- case cMonster::mtSnowGolem: Monster = new cSnowGolem(); break;
- case cMonster::mtSpider: Monster = new cSpider(); break;
- case cMonster::mtSquid: Monster = new cSquid(); break;
- case cMonster::mtVillager: Monster = new cVillager(); break;
- case cMonster::mtWitch: Monster = new cWitch(); break;
- case cMonster::mtWither: Monster = new cWither(); break;
- case cMonster::mtWolf: Monster = new cWolf(); break;
- case cMonster::mtZombie: Monster = new cZombie(); break;
- case cMonster::mtZombiePigman: Monster = new cZombiePigman(); break;
+ case cMonster::mtBat: Monster = new cBat(); break;
+ case cMonster::mtBlaze: Monster = new cBlaze(); break;
+ case cMonster::mtCaveSpider: Monster = new cCavespider(); break;
+ case cMonster::mtChicken: Monster = new cChicken(); break;
+ case cMonster::mtCow: Monster = new cCow(); break;
+ case cMonster::mtCreeper: Monster = new cCreeper(); break;
+ case cMonster::mtEnderman: Monster = new cEnderman(); break;
+ case cMonster::mtEnderDragon: Monster = new cEnderDragon(); break;
+ case cMonster::mtGhast: Monster = new cGhast(); break;
+ case cMonster::mtGiant: Monster = new cGiant(); break;
+ case cMonster::mtHorse:
+ {
+ Monster = new cHorse(HseType, HseColor, HseStyle, HseTameTimes); break;
+ }
+ case cMonster::mtIronGolem: Monster = new cIronGolem(); break;
+ case cMonster::mtMagmaCube: Monster = new cMagmaCube(SlSize); break;
+ case cMonster::mtMooshroom: Monster = new cMooshroom(); break;
+ case cMonster::mtOcelot: Monster = new cOcelot(); break;
+ case cMonster::mtPig: Monster = new cPig(); break;
+ case cMonster::mtSheep: Monster = new cSheep(ShColor); break;
+ case cMonster::mtSilverfish: Monster = new cSilverfish(); break;
+ case cMonster::mtSkeleton: Monster = new cSkeleton(SkType); break;
+ case cMonster::mtSlime: Monster = new cSlime(SlSize); break;
+ case cMonster::mtSnowGolem: Monster = new cSnowGolem(); break;
+ case cMonster::mtSpider: Monster = new cSpider(); break;
+ case cMonster::mtSquid: Monster = new cSquid(); break;
+ case cMonster::mtVillager:
+ {
+ Monster = new cVillager((cVillager::eVillagerType)VilType); break;
+ }
+ case cMonster::mtWitch: Monster = new cWitch(); break;
+ case cMonster::mtWither: Monster = new cWither(); break;
+ case cMonster::mtWolf: Monster = new cWolf(); break;
+ case cMonster::mtZombie: Monster = new cZombie(false); break; // TODO: Villager infection
+ case cMonster::mtZombiePigman: Monster = new cZombiePigman(); break;
default:
{
@@ -2646,7 +2663,11 @@ int cWorld::SpawnMob(double a_PosX, double a_PosY, double a_PosZ, cMonster::eTyp
delete Monster;
return -1;
}
+
BroadcastSpawnEntity(*Monster);
+ // Because it's logical that ALL mob spawns need spawn effects, not just spawners
+ BroadcastSoundParticleEffect(2004, (int)(floor(a_PosX) * 8), (int)(floor(a_PosY) * 8), (int)(floor(a_PosZ) * 8), 0);
+
cPluginManager::Get()->CallHookSpawnedMonster(*this, *Monster);
return Monster->GetUniqueID();
}