summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordaniel0916 <theschokolps@gmail.com>2014-04-19 20:56:29 +0200
committerdaniel0916 <theschokolps@gmail.com>2014-04-19 20:56:29 +0200
commitcb90029f720d867ba6398569c1b1dac2ee76e205 (patch)
treed16ea187d087b1e91c5136d349b66aafad02d7d6
parentFixed Code (2) (diff)
parentAPIDump: Added a ChunkStay article. (diff)
downloadcuberite-cb90029f720d867ba6398569c1b1dac2ee76e205.tar
cuberite-cb90029f720d867ba6398569c1b1dac2ee76e205.tar.gz
cuberite-cb90029f720d867ba6398569c1b1dac2ee76e205.tar.bz2
cuberite-cb90029f720d867ba6398569c1b1dac2ee76e205.tar.lz
cuberite-cb90029f720d867ba6398569c1b1dac2ee76e205.tar.xz
cuberite-cb90029f720d867ba6398569c1b1dac2ee76e205.tar.zst
cuberite-cb90029f720d867ba6398569c1b1dac2ee76e205.zip
-rw-r--r--MCServer/Plugins/APIDump/APIDesc.lua7
-rw-r--r--MCServer/Plugins/APIDump/UsingChunkStays.html167
-rw-r--r--MCServer/Plugins/APIDump/WebWorldThreads.html48
-rw-r--r--src/BlockEntities/BeaconEntity.cpp116
-rw-r--r--src/BlockEntities/BeaconEntity.h44
-rw-r--r--src/BlockEntities/BlockEntity.cpp2
-rw-r--r--src/Chunk.cpp2
-rw-r--r--src/Entities/Entity.cpp10
-rw-r--r--src/Mobs/Monster.cpp5
-rw-r--r--src/World.cpp5
10 files changed, 377 insertions, 29 deletions
diff --git a/MCServer/Plugins/APIDump/APIDesc.lua b/MCServer/Plugins/APIDump/APIDesc.lua
index 233bdbc46..45e8d9677 100644
--- a/MCServer/Plugins/APIDump/APIDesc.lua
+++ b/MCServer/Plugins/APIDump/APIDesc.lua
@@ -2929,9 +2929,10 @@ end
{
-- No sorting is provided for these, they will be output in the same order as defined here
{ FileName = "Writing-a-MCServer-plugin.html", Title = "Writing a MCServer plugin" },
- { FileName = "SettingUpDecoda.html", Title = "Setting up the Decoda Lua IDE" },
- { FileName = "SettingUpZeroBrane.html", Title = "Setting up the ZeroBrane Studio Lua IDE" },
- { FileName = "WebWorldThreads.html", Title = "Webserver vs World threads" },
+ { FileName = "SettingUpDecoda.html", Title = "Setting up the Decoda Lua IDE" },
+ { FileName = "SettingUpZeroBrane.html", Title = "Setting up the ZeroBrane Studio Lua IDE" },
+ { FileName = "UsingChunkStays.html", Title = "Using ChunkStays" },
+ { FileName = "WebWorldThreads.html", Title = "Webserver vs World threads" },
}
} ;
diff --git a/MCServer/Plugins/APIDump/UsingChunkStays.html b/MCServer/Plugins/APIDump/UsingChunkStays.html
new file mode 100644
index 000000000..d3ecc6674
--- /dev/null
+++ b/MCServer/Plugins/APIDump/UsingChunkStays.html
@@ -0,0 +1,167 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>MCServer - Using ChunkStays</title>
+ <link rel="stylesheet" type="text/css" href="main.css" />
+ <link rel="stylesheet" type="text/css" href="prettify.css" />
+ <script src="prettify.js"></script>
+ <script src="lang-lua.js"></script>
+ <meta charset="UTF-8">
+ </head>
+ <body>
+ <div id="content">
+ <h1>Using ChunkStays</h1>
+ <p>
+ A plugin may need to manipulate data in arbitrary chunks, and it needs a way to make the server
+ guarantee that the chunks are available in memory.</p>
+
+ <h2>The problem</h2>
+ <p>
+ Usually when plugins want to manipulate larger areas of world data, they need to make sure that the
+ server has the appropriate chunks loaded in the memory. When the data being manipulated can be further
+ away from the connected players, or the data is being manipulated from a console handler, there is a
+ real chance that the chunks are not loaded.</p>
+ <p>
+ This gets even more important when using the <a href="cBlockArea.html">cBlockArea</a> class for reading
+ and writing. Those functions will fail when any of the required chunks aren't valid. This means that
+ either the block area has incomplete data (Read() failed) or incomplete data has been written to the
+ world (Write() failed). Recovery from this is near impossible - you can't simply read or write again
+ later, because the world may have changed in the meantime.</p>
+
+ <h2>The solution</h2>
+ <p>
+ The naive solution would be to monitor chunk loads and unloads, and postpone the operations until all
+ the chunks are available. This would be quite ineffective and also very soon it would become very
+ difficult to maintain, if there were multiple code paths requiring this handling.</p>
+ <p>
+ An alternate approach has been implemented, accessible through a single (somewhat hidden) function
+ call: <a href="cWorld.html">cWorld:ChunkStay()</a>. All that this call basically does is, it tells the
+ server "Load these chunks for me, and call this callback function once you have them all." And the
+ server does exactly that - it remembers the callback and asks the world loader / generator to provide
+ the chunks. Once the chunks become available, it calls the callback function for the plugin.</p>
+ <p>
+ There are a few gotcha-s, though. If the code that was requesting the read or write had access to some
+ of the volatile objects, such as <a href="cPlayer.html">cPlayer</a> or
+ <a href="cEntity.html">cEntity</a> objects, those cannot be accessed by the callback anymore, because
+ they may have become invalid in the meantime - the player may have disconnected, the entity may have
+ despawned. So the callback must use the longer way to access such objects, such as calling
+ <a href="cWorld.html">cWorld:DoWithEntityByID()</a> or
+ <a href="cWorld.html">cWorld:DoWithPlayer()</a>.</p>
+
+ <h2>The example</h2>
+ <p>
+ As a simple example, consider a theoretical plugin that allows a player to save the immediate
+ surroundings of the spawn into a schematic file. The player issues a command to initiate the save, and
+ the plugin reads a 50 x 50 x 50 block area around the spawn into a cBlockArea and saves it on the disk
+ as "<PlayerName>_spawn.schematic". When it's done with the saving, it wants to send a message to the
+ player to let them know the command has succeeded.</p>
+ <p>
+ The first attempt shows the naive approach. It simply reads the block area and saves it, then sends the
+ message. I'll repeat once more, this code is <b>the wrong way</b> to do it!</p>
+<pre class="prettyprint lang-lua">
+function HandleCommandSaveSpawn(a_Split, a_Player)
+ -- Get the coords for the spawn:
+ local SpawnX = a_Player:GetWorld():GetSpawnX()
+ local SpawnY = a_Player:GetWorld():GetSpawnY()
+ local SpawnZ = a_Player:GetWorld():GetSpawnZ()
+ local Bounds = cCuboid(SpawnX - 25, SpawnY - 25, SpawnZ - 25, SpawnX + 25, SpawnY + 25, SpawnZ + 25)
+ Bounds:ClampY(0, 255)
+
+ -- Read the area around spawn into a cBlockArea, save to file:
+ local Area = cBlockArea()
+ local FileName = a_Player:GetName() .. "_spawn.schematic"
+ Area:Read(a_Player:GetWorld(), Bounds, cBlockArea.baTypes + cBlockArea.baMetas)
+ Area:SaveToSchematicFile(FileName)
+
+ -- Notify the player:
+ a_Player:SendMessage(cCompositeChat("The spawn has been saved", mtInfo))
+ return true
+end
+</pre>
+ <p>
+ Now if the player goes exploring far and uses the command to save their spawn, the chunks aren't
+ loaded, so the BlockArea reading fails, the BlockArea contains bad data. Note that the plugin fails to
+ do any error checking and if the area isn't read from the world, it happily saves the incomplete data
+ and says "hey, everything's right", althought it has just trashed any previous backup of the spawn
+ schematic with nonsense data.</p>
+ <hr/>
+ <p>
+ The following script uses the ChunkStay method to alleviate chunk-related problems. This is <b>the
+ right way</b> of doing it:</p>
+<pre class="prettyprint lang-lua">
+function HandleCommandSaveSpawn(a_Split, a_Player)
+ -- Get the coords for the spawn:
+ local SpawnX = a_Player:GetWorld():GetSpawnX()
+ local SpawnY = a_Player:GetWorld():GetSpawnY()
+ local SpawnZ = a_Player:GetWorld():GetSpawnZ()
+ local Bounds = cCuboid(SpawnX - 25, SpawnY - 25, SpawnZ - 25, SpawnX + 25, SpawnY + 25, SpawnZ + 25)
+ Bounds:ClampY(0, 255)
+
+ -- Get a list of chunks that we need loaded:
+ local MinChunkX = math.floor((SpawnX - 25) / 16)
+ local MaxChunkX = math.ceil ((SpawnX + 25) / 16)
+ local MinChunkZ = math.floor((SpawnZ - 25) / 16)
+ local MaxChunkZ = math.ceil ((SpawnZ + 25) / 16)
+ local Chunks = {}
+ for x = MinChunkX, MaxChunkX do
+ for z = MinChunkZ, MaxChunkZ do
+ table.insert(Chunks, {x, z})
+ end
+ end -- for x
+
+ -- Store the player's name and world to use in the callback, because the a_Player object may no longer be valid:
+ local PlayerName = a_Player:GetName()
+ local World = a_Player:GetWorld()
+
+ -- This is the callback that is executed once all the chunks are loaded:
+ local OnAllChunksAvailable = function()
+ -- Read the area around spawn into a cBlockArea, save to file:
+ local Area = cBlockArea()
+ local FileName = PlayerName .. "_spawn.schematic"
+ if (Area:Read(World, Bounds, cBlockArea.baTypes + cBlockArea.baMetas)) then
+ Area:SaveToSchematicFile(FileName)
+ Msg = cCompositeChat("The spawn has been saved", mtInfo)
+ else
+ Msg = cCompositeChat("Cannot save the spawn", mtFailure)
+ end
+
+ -- Notify the player:
+ -- Note that we cannot use a_Player here, because it may no longer be valid (if the player disconnected before the command completes)
+ World:DoWithPlayer(PlayerName,
+ function (a_CBPlayer)
+ a_CBPlayer:SendMessage(Msg)
+ end
+ )
+ end
+
+ -- Ask the server to load our chunks and notify us once it's done:
+ World:ChunkStay(Chunks, nil, OnAllChunksAvailable)
+
+ -- Note that code here may get executed before the callback is called!
+ -- The ChunkStay says "once you have the chunks", not "wait until you have the chunks"
+ -- So you can't notify the player here, because the saving needn't have occurred yet.
+
+ return true
+end
+</pre>
+ <p>
+ Note that this code does its error checking of the Area:Read() function, and it will not overwrite the
+ previous file unless it actually has the correct data. If you're wondering how the reading could fail
+ when we've got the chunks loaded, there's still the issue of free RAM - if the memory for the area
+ cannot be allocated, it cannot be read even with all the chunks present. So we still do need that
+ check.</p>
+
+ <h2>The conclusion</h2>
+ <p>
+ Although it makes the code a little bit longer and is a bit more difficult to grasp at first, the
+ ChunkStay is a useful technique to add to your repertoire. It is to be used whenever you need access to
+ chunks that may potentially be inaccessible, and you really need the data.</p>
+ <p>Possibly the biggest hurdle in using the ChunkStay is the fact that it does its work in the
+ background, thus invalidating all cPlayer and cEntity objects your function may hold, so you need to
+ re-acquire them from their IDs and names. This is the penalty for using multi-threaded code.</p>
+ <script>
+ prettyPrint();
+ </script>
+ </div>
+ </body>
+</html>
diff --git a/MCServer/Plugins/APIDump/WebWorldThreads.html b/MCServer/Plugins/APIDump/WebWorldThreads.html
index fc80a6178..ee0b172e6 100644
--- a/MCServer/Plugins/APIDump/WebWorldThreads.html
+++ b/MCServer/Plugins/APIDump/WebWorldThreads.html
@@ -39,31 +39,31 @@
<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
+<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
- 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>
+ )
+ end
+ )
+ end
+)
+</pre>
<script>
prettyPrint();
</script>
diff --git a/src/BlockEntities/BeaconEntity.cpp b/src/BlockEntities/BeaconEntity.cpp
new file mode 100644
index 000000000..0914353eb
--- /dev/null
+++ b/src/BlockEntities/BeaconEntity.cpp
@@ -0,0 +1,116 @@
+
+#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
+
+#include "BeaconEntity.h"
+#include "../BlockArea.h"
+
+
+
+
+
+cBeaconEntity::cBeaconEntity(int a_BlockX, int a_BlockY, int a_BlockZ, cWorld * a_World) :
+ super(E_BLOCK_BEACON, a_BlockX, a_BlockY, a_BlockZ, a_World)
+{
+}
+
+
+
+
+
+int cBeaconEntity::GetPyramidLevel(void)
+{
+ cBlockArea Area;
+ int MinY = GetPosY() - 4;
+ if (MinY < 0)
+ {
+ MinY = 0;
+ }
+ int MaxY = GetPosY() - 1;
+ if (MaxY < 0)
+ {
+ MaxY = 0;
+ }
+
+ Area.Read(
+ m_World,
+ GetPosX() - 4, GetPosX() + 4,
+ MinY, MaxY,
+ GetPosZ() - 4, GetPosZ() + 4,
+ cBlockArea::baTypes
+ );
+
+ int Layer = 1;
+ int MiddleXZ = 4;
+
+ for (int Y = Area.GetSizeY() - 1; Y > 0; Y--)
+ {
+ for (int X = MiddleXZ - Layer; X <= (MiddleXZ + Layer); X++)
+ {
+ for (int Z = MiddleXZ - Layer; Z <= (MiddleXZ + Layer); Z++)
+ {
+ if (!IsMineralBlock(Area.GetRelBlockType(X, Y, Z)))
+ {
+ return Layer - 1;
+ }
+ }
+ }
+ Layer++;
+ }
+
+ return Layer - 1;
+}
+
+
+
+
+
+bool cBeaconEntity::IsMineralBlock(BLOCKTYPE a_BlockType)
+{
+ switch(a_BlockType)
+ {
+ case E_BLOCK_DIAMOND_BLOCK:
+ case E_BLOCK_GOLD_BLOCK:
+ case E_BLOCK_IRON_BLOCK:
+ case E_BLOCK_EMERALD_BLOCK:
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+
+
+
+bool cBeaconEntity::Tick(float a_Dt, cChunk & a_Chunk)
+{
+ return false;
+}
+
+
+
+
+
+void cBeaconEntity::SaveToJson(Json::Value& a_Value)
+{
+}
+
+
+
+
+void cBeaconEntity::SendTo(cClientHandle & a_Client)
+{
+}
+
+
+
+
+
+void cBeaconEntity::UsedBy(cPlayer * a_Player)
+{
+}
+
+
+
+
diff --git a/src/BlockEntities/BeaconEntity.h b/src/BlockEntities/BeaconEntity.h
new file mode 100644
index 000000000..b1df68bc4
--- /dev/null
+++ b/src/BlockEntities/BeaconEntity.h
@@ -0,0 +1,44 @@
+
+#pragma once
+
+#include "BlockEntity.h"
+
+
+
+
+
+namespace Json
+{
+ class Value;
+}
+
+
+
+
+
+class cBeaconEntity :
+ public cBlockEntity
+{
+ typedef cBlockEntity super;
+
+public:
+
+ /** The initial constructor */
+ cBeaconEntity(int a_BlockX, int a_BlockY, int a_BlockZ, cWorld * a_World);
+
+ /** Returns the amount of layers the pyramid below the beacon has. */
+ int GetPyramidLevel(void);
+
+ /** Returns true if the block is a diamond block, a golden block, an iron block or an emerald block. */
+ static bool IsMineralBlock(BLOCKTYPE a_BlockType);
+
+ // cBlockEntity overrides:
+ virtual void SaveToJson(Json::Value& a_Value ) override;
+ virtual void SendTo(cClientHandle & a_Client) override;
+ virtual void UsedBy(cPlayer * a_Player) override;
+ virtual bool Tick(float a_Dt, cChunk & /* a_Chunk */) override;
+} ;
+
+
+
+
diff --git a/src/BlockEntities/BlockEntity.cpp b/src/BlockEntities/BlockEntity.cpp
index b42318c2f..430f04551 100644
--- a/src/BlockEntities/BlockEntity.cpp
+++ b/src/BlockEntities/BlockEntity.cpp
@@ -4,6 +4,7 @@
// Implements the cBlockEntity class that is the common ancestor for all block entities
#include "Globals.h"
+#include "BeaconEntity.h"
#include "BlockEntity.h"
#include "ChestEntity.h"
#include "CommandBlockEntity.h"
@@ -26,6 +27,7 @@ cBlockEntity * cBlockEntity::CreateByBlockType(BLOCKTYPE a_BlockType, NIBBLETYPE
{
switch (a_BlockType)
{
+ case E_BLOCK_BEACON: return new cBeaconEntity (a_BlockX, a_BlockY, a_BlockZ, a_World);
case E_BLOCK_CHEST: return new cChestEntity (a_BlockX, a_BlockY, a_BlockZ, a_World);
case E_BLOCK_COMMAND_BLOCK: return new cCommandBlockEntity(a_BlockX, a_BlockY, a_BlockZ, a_World);
case E_BLOCK_DISPENSER: return new cDispenserEntity (a_BlockX, a_BlockY, a_BlockZ, a_World);
diff --git a/src/Chunk.cpp b/src/Chunk.cpp
index fe9cd9b31..4366111ef 100644
--- a/src/Chunk.cpp
+++ b/src/Chunk.cpp
@@ -1299,6 +1299,7 @@ void cChunk::CreateBlockEntities(void)
BLOCKTYPE BlockType = cChunkDef::GetBlock(m_BlockTypes, x, y, z);
switch (BlockType)
{
+ case E_BLOCK_BEACON:
case E_BLOCK_CHEST:
case E_BLOCK_COMMAND_BLOCK:
case E_BLOCK_DISPENSER:
@@ -1429,6 +1430,7 @@ void cChunk::SetBlock(int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE a_BlockType,
// If the new block is a block entity, create the entity object:
switch (a_BlockType)
{
+ case E_BLOCK_BEACON:
case E_BLOCK_CHEST:
case E_BLOCK_COMMAND_BLOCK:
case E_BLOCK_DISPENSER:
diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp
index 86aee4c6f..56ef36ef8 100644
--- a/src/Entities/Entity.cpp
+++ b/src/Entities/Entity.cpp
@@ -771,6 +771,16 @@ void cEntity::TickBurning(cChunk & a_Chunk)
{
// Remember the current burning state:
bool HasBeenBurning = (m_TicksLeftBurning > 0);
+
+ if (GetWorld()->GetWeather() == eWeather_Rain)
+ {
+ if (HasBeenBurning)
+ {
+ m_TicksLeftBurning = 0;
+ OnFinishedBurning();
+ }
+ return;
+ }
// Do the burning damage:
if (m_TicksLeftBurning > 0)
diff --git a/src/Mobs/Monster.cpp b/src/Mobs/Monster.cpp
index d63758b3d..248e88f5d 100644
--- a/src/Mobs/Monster.cpp
+++ b/src/Mobs/Monster.cpp
@@ -540,7 +540,7 @@ void cMonster::KilledBy(cEntity * a_Killer)
break;
}
}
- if (a_Killer != NULL)
+ if ((a_Killer != NULL) && (!IsBaby()))
{
m_World->SpawnExperienceOrb(GetPosX(), GetPosY(), GetPosZ(), Reward);
}
@@ -1003,7 +1003,8 @@ void cMonster::HandleDaylightBurning(cChunk & a_Chunk)
(a_Chunk.GetSkyLight(RelX, RelY, RelZ) == 15) && // In the daylight
(a_Chunk.GetBlock(RelX, RelY, RelZ) != E_BLOCK_SOULSAND) && // Not on soulsand
(GetWorld()->GetTimeOfDay() < (12000 + 1000)) && // It is nighttime
- !IsOnFire() // Not already burning
+ !IsOnFire() && // Not already burning
+ (GetWorld()->GetWeather() != eWeather_Rain) // Not raining
)
{
// Burn for 100 ticks, then decide again
diff --git a/src/World.cpp b/src/World.cpp
index c23e255f8..800bdde0e 100644
--- a/src/World.cpp
+++ b/src/World.cpp
@@ -1691,6 +1691,11 @@ int cWorld::SpawnFallingBlock(int a_X, int a_Y, int a_Z, BLOCKTYPE BlockType, NI
int cWorld::SpawnExperienceOrb(double a_X, double a_Y, double a_Z, int a_Reward)
{
+ if (a_Reward < 1)
+ {
+ return -1;
+ }
+
cExpOrb * ExpOrb = new cExpOrb(a_X, a_Y, a_Z, a_Reward);
ExpOrb->Initialize(this);
return ExpOrb->GetUniqueID();