Merge branch 'master' into Enchanting
This commit is contained in:
commit
cb90029f72
@ -2931,6 +2931,7 @@ end
|
||||
{ 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 = "UsingChunkStays.html", Title = "Using ChunkStays" },
|
||||
{ FileName = "WebWorldThreads.html", Title = "Webserver vs World threads" },
|
||||
}
|
||||
} ;
|
||||
|
167
MCServer/Plugins/APIDump/UsingChunkStays.html
Normal file
167
MCServer/Plugins/APIDump/UsingChunkStays.html
Normal file
@ -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>
|
116
src/BlockEntities/BeaconEntity.cpp
Normal file
116
src/BlockEntities/BeaconEntity.cpp
Normal file
@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
44
src/BlockEntities/BeaconEntity.h
Normal file
44
src/BlockEntities/BeaconEntity.h
Normal file
@ -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;
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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:
|
||||
|
@ -772,6 +772,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)
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user