diff options
-rw-r--r-- | MCServer/Plugins/APIDump/APIDesc.lua | 7 | ||||
-rw-r--r-- | MCServer/Plugins/APIDump/UsingChunkStays.html | 167 | ||||
-rw-r--r-- | MCServer/Plugins/APIDump/WebWorldThreads.html | 48 | ||||
-rw-r--r-- | src/BlockEntities/BeaconEntity.cpp | 116 | ||||
-rw-r--r-- | src/BlockEntities/BeaconEntity.h | 44 | ||||
-rw-r--r-- | src/BlockEntities/BlockEntity.cpp | 2 | ||||
-rw-r--r-- | src/Chunk.cpp | 2 | ||||
-rw-r--r-- | src/Entities/Entity.cpp | 10 | ||||
-rw-r--r-- | src/Mobs/Monster.cpp | 5 | ||||
-rw-r--r-- | src/World.cpp | 5 |
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(); |