summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Bindings/LuaState.cpp34
-rw-r--r--src/Bindings/LuaState.h3
-rw-r--r--src/Bindings/ManualBindings.cpp24
-rw-r--r--src/BlockEntities/CommandBlockEntity.cpp14
-rw-r--r--src/Blocks/BlockCommandBlock.h32
-rw-r--r--src/Blocks/BlockHandler.cpp2
-rw-r--r--src/ClientHandle.cpp30
-rw-r--r--src/ClientHandle.h1
-rw-r--r--src/Enchantments.cpp79
-rw-r--r--src/Enchantments.h8
-rw-r--r--src/HTTPServer/HTTPConnection.cpp2
-rw-r--r--src/OSSupport/Socket.cpp19
-rw-r--r--src/OSSupport/Socket.h6
-rw-r--r--src/OSSupport/SocketThreads.cpp283
-rw-r--r--src/OSSupport/SocketThreads.h82
-rw-r--r--src/Protocol/Protocol.h1
-rw-r--r--src/Protocol/Protocol125.h1
-rw-r--r--src/Protocol/Protocol132.cpp5
-rw-r--r--src/Protocol/Protocol17x.cpp76
-rw-r--r--src/Protocol/Protocol17x.h2
-rw-r--r--src/Protocol/ProtocolRecognizer.cpp10
-rw-r--r--src/Protocol/ProtocolRecognizer.h1
-rw-r--r--src/Server.cpp11
-rw-r--r--src/Server.h8
-rw-r--r--src/World.cpp36
-rw-r--r--src/World.h276
-rw-r--r--src/WorldStorage/EnchantmentSerializer.cpp88
-rw-r--r--src/WorldStorage/EnchantmentSerializer.h17
-rw-r--r--src/WorldStorage/NBTChunkSerializer.cpp3
-rw-r--r--src/WorldStorage/WSSAnvil.cpp3
30 files changed, 679 insertions, 478 deletions
diff --git a/src/Bindings/LuaState.cpp b/src/Bindings/LuaState.cpp
index 00e62fcf6..bfee1d037 100644
--- a/src/Bindings/LuaState.cpp
+++ b/src/Bindings/LuaState.cpp
@@ -914,6 +914,40 @@ bool cLuaState::CheckParamString(int a_StartParam, int a_EndParam)
+bool cLuaState::CheckParamFunction(int a_StartParam, int a_EndParam)
+{
+ ASSERT(IsValid());
+
+ if (a_EndParam < 0)
+ {
+ a_EndParam = a_StartParam;
+ }
+
+ for (int i = a_StartParam; i <= a_EndParam; i++)
+ {
+ if (lua_isfunction(m_LuaState, i))
+ {
+ continue;
+ }
+ // Not the correct parameter
+ lua_Debug entry;
+ VERIFY(lua_getstack(m_LuaState, 0, &entry));
+ VERIFY(lua_getinfo (m_LuaState, "n", &entry));
+ AString ErrMsg = Printf("Error in function '%s' parameter #%d. Function expected, got %s",
+ (entry.name != NULL) ? entry.name : "?", i, GetTypeText(i).c_str()
+ );
+ LogStackTrace();
+ return false;
+ } // for i - Param
+
+ // All params checked ok
+ return true;
+}
+
+
+
+
+
bool cLuaState::CheckParamEnd(int a_Param)
{
tolua_Error tolua_err;
diff --git a/src/Bindings/LuaState.h b/src/Bindings/LuaState.h
index 414e5e4b2..f8b67f5cd 100644
--- a/src/Bindings/LuaState.h
+++ b/src/Bindings/LuaState.h
@@ -815,6 +815,9 @@ public:
/// Returns true if the specified parameters on the stack are strings; also logs warning if not
bool CheckParamString(int a_StartParam, int a_EndParam = -1);
+ /// Returns true if the specified parameters on the stack are functions; also logs warning if not
+ bool CheckParamFunction(int a_StartParam, int a_EndParam = -1);
+
/// Returns true if the specified parameter on the stack is nil (indicating an end-of-parameters)
bool CheckParamEnd(int a_Param);
diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp
index 3ebe7b294..2206dd371 100644
--- a/src/Bindings/ManualBindings.cpp
+++ b/src/Bindings/ManualBindings.cpp
@@ -986,11 +986,10 @@ static int tolua_cWorld_QueueTask(lua_State * tolua_S)
class cLuaScheduledWorldTask :
- public cWorld::cScheduledTask
+ public cWorld::cTask
{
public:
- cLuaScheduledWorldTask(cPluginLua & a_Plugin, int a_FnRef, int a_Ticks) :
- cScheduledTask(a_Ticks),
+ cLuaScheduledWorldTask(cPluginLua & a_Plugin, int a_FnRef) :
m_Plugin(a_Plugin),
m_FnRef(a_FnRef)
{
@@ -1025,14 +1024,19 @@ static int tolua_cWorld_ScheduleTask(lua_State * tolua_S)
}
// Retrieve the args:
- cWorld * self = (cWorld *)tolua_tousertype(tolua_S, 1, 0);
- if (self == NULL)
+ cLuaState L(tolua_S);
+ if (
+ !L.CheckParamUserType(1, "cWorld") ||
+ !L.CheckParamNumber (2) ||
+ !L.CheckParamFunction(3)
+ )
{
- return lua_do_error(tolua_S, "Error in function call '#funcname#': Not called on an object instance");
+ return 0;
}
- if (!lua_isfunction(tolua_S, 2))
+ cWorld * World = (cWorld *)tolua_tousertype(tolua_S, 1, NULL);
+ if (World == NULL)
{
- return lua_do_error(tolua_S, "Error in function call '#funcname#': Expected a function for parameter #1");
+ return lua_do_error(tolua_S, "Error in function call '#funcname#': Not called on an object instance");
}
// Create a reference to the function:
@@ -1042,9 +1046,9 @@ static int tolua_cWorld_ScheduleTask(lua_State * tolua_S)
return lua_do_error(tolua_S, "Error in function call '#funcname#': Could not get function reference of parameter #1");
}
- int Ticks = (int) tolua_tonumber (tolua_S, 3, 0);
+ int DelayTicks = (int)tolua_tonumber(tolua_S, 2, 0);
- self->ScheduleTask(new cLuaScheduledWorldTask(*Plugin, FnRef, Ticks));
+ World->ScheduleTask(DelayTicks, new cLuaScheduledWorldTask(*Plugin, FnRef));
return 0;
}
diff --git a/src/BlockEntities/CommandBlockEntity.cpp b/src/BlockEntities/CommandBlockEntity.cpp
index ca617b04d..7e9015d33 100644
--- a/src/BlockEntities/CommandBlockEntity.cpp
+++ b/src/BlockEntities/CommandBlockEntity.cpp
@@ -7,6 +7,7 @@
#include "json/json.h"
#include "CommandBlockEntity.h"
#include "../Entities/Player.h"
+#include "../WorldStorage/FastNBT.h"
#include "../CommandOutput.h"
#include "../Root.h"
@@ -40,6 +41,15 @@ void cCommandBlockEntity::UsedBy(cPlayer * a_Player)
void cCommandBlockEntity::SetCommand(const AString & a_Cmd)
{
m_Command = a_Cmd;
+
+ /*
+ Vanilla requires that the server send a Block Entity Update after a command has been set
+ Therefore, command blocks don't support on-the-fly (when window is open) updating of a command and therefore...
+ ...the following code can't be put in UsedBy just before the window opens
+
+ Just documenting my experience in getting this to work :P
+ */
+ m_World->BroadcastBlockEntity(GetPosX(), GetPosY(), GetPosZ());
}
@@ -48,6 +58,7 @@ void cCommandBlockEntity::SetCommand(const AString & a_Cmd)
void cCommandBlockEntity::SetLastOutput(const AString & a_LastOut)
{
+ m_World->BroadcastBlockEntity(GetPosX(), GetPosY(), GetPosZ());
m_LastOutput = a_LastOut;
}
@@ -131,8 +142,7 @@ bool cCommandBlockEntity::Tick(float a_Dt, cChunk & a_Chunk)
void cCommandBlockEntity::SendTo(cClientHandle & a_Client)
{
- // Nothing needs to be sent
- UNUSED(a_Client);
+ a_Client.SendUpdateBlockEntity(*this);
}
diff --git a/src/Blocks/BlockCommandBlock.h b/src/Blocks/BlockCommandBlock.h
new file mode 100644
index 000000000..cf0103765
--- /dev/null
+++ b/src/Blocks/BlockCommandBlock.h
@@ -0,0 +1,32 @@
+
+#pragma once
+
+#include "BlockEntity.h"
+
+
+
+
+
+class cBlockCommandBlockHandler :
+ public cBlockEntityHandler
+{
+public:
+ cBlockCommandBlockHandler(BLOCKTYPE a_BlockType)
+ : cBlockEntityHandler(a_BlockType)
+ {
+ }
+
+ virtual void ConvertToPickups(cItems & a_Pickups, NIBBLETYPE a_BlockMeta) override
+ {
+ a_Pickups.push_back(cItem(E_BLOCK_AIR, 8, 0));
+ }
+
+ virtual const char * GetStepSound(void) override
+ {
+ return "step.stone";
+ }
+} ;
+
+
+
+
diff --git a/src/Blocks/BlockHandler.cpp b/src/Blocks/BlockHandler.cpp
index ff1022e12..b9c0887ce 100644
--- a/src/Blocks/BlockHandler.cpp
+++ b/src/Blocks/BlockHandler.cpp
@@ -14,6 +14,7 @@
#include "BlockChest.h"
#include "BlockCloth.h"
#include "BlockCobWeb.h"
+#include "BlockCommandBlock.h"
#include "BlockComparator.h"
#include "BlockCrops.h"
#include "BlockDeadBush.h"
@@ -116,6 +117,7 @@ cBlockHandler * cBlockHandler::CreateBlockHandler(BLOCKTYPE a_BlockType)
case E_BLOCK_CAULDRON: return new cBlockCauldronHandler (a_BlockType);
case E_BLOCK_CHEST: return new cBlockChestHandler (a_BlockType);
case E_BLOCK_COAL_ORE: return new cBlockOreHandler (a_BlockType);
+ case E_BLOCK_COMMAND_BLOCK: return new cBlockCommandBlockHandler (a_BlockType);
case E_BLOCK_ACTIVE_COMPARATOR: return new cBlockComparatorHandler (a_BlockType);
case E_BLOCK_COBBLESTONE: return new cBlockStoneHandler (a_BlockType);
case E_BLOCK_COBBLESTONE_STAIRS: return new cBlockStairsHandler (a_BlockType);
diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp
index b06dbc84a..0a2d3c1be 100644
--- a/src/ClientHandle.cpp
+++ b/src/ClientHandle.cpp
@@ -120,9 +120,6 @@ cClientHandle::~cClientHandle()
LOGD("Deleting client \"%s\" at %p", GetUsername().c_str(), this);
- // Remove from cSocketThreads, we're not to be called anymore:
- cRoot::Get()->GetServer()->ClientDestroying(this);
-
{
cCSLock Lock(m_CSChunkLists);
m_LoadedChunks.clear();
@@ -160,8 +157,7 @@ cClientHandle::~cClientHandle()
cRoot::Get()->GetServer()->WriteToClient(this, Data);
}
- // Queue the socket to close as soon as it sends all outgoing data:
- cRoot::Get()->GetServer()->QueueClientClose(this);
+ // Close the socket as soon as it sends all outgoing data:
cRoot::Get()->GetServer()->RemoveClient(this);
delete m_Protocol;
@@ -552,12 +548,18 @@ void cClientHandle::HandlePlayerPos(double a_PosX, double a_PosY, double a_PosZ,
void cClientHandle::HandlePluginMessage(const AString & a_Channel, const AString & a_Message)
{
- if (a_Channel == "MC|AdvCdm") // Command block
+ if (a_Channel == "MC|AdvCdm") // Command block, set text, Client -> Server
{
const char* Data = a_Message.c_str();
-
HandleCommandBlockMessage(Data, a_Message.size());
-
+ return;
+ }
+ else if (a_Channel == "MC|Brand") // Client <-> Server branding exchange
+ {
+ // We are custom,
+ // We are awesome,
+ // We are MCServer.
+ SendPluginMessage("MC|Brand", "MCServer");
return;
}
@@ -572,6 +574,7 @@ void cClientHandle::HandleCommandBlockMessage(const char* a_Data, unsigned int a
{
if (a_Length < 14)
{
+ SendChat(Printf("%s[INFO]%s Failure setting command block command; bad request", cChatColor::Red.c_str(), cChatColor::White.c_str()));
LOGD("Malformed MC|AdvCdm packet.");
return;
}
@@ -601,6 +604,7 @@ void cClientHandle::HandleCommandBlockMessage(const char* a_Data, unsigned int a
default:
{
+ SendChat(Printf("%s[INFO]%s Failure setting command block command; unhandled mode", cChatColor::Red.c_str(), cChatColor::White.c_str()));
LOGD("Unhandled MC|AdvCdm packet mode.");
return;
}
@@ -623,6 +627,8 @@ void cClientHandle::HandleCommandBlockMessage(const char* a_Data, unsigned int a
cWorld * World = m_Player->GetWorld();
World->DoWithCommandBlockAt(BlockX, BlockY, BlockZ, CmdBlockCB);
+
+ SendChat(Printf("%s[INFO]%s Successfully set command block command", cChatColor::Green.c_str(), cChatColor::White.c_str()));
}
@@ -2236,6 +2242,14 @@ void cClientHandle::SendUnloadChunk(int a_ChunkX, int a_ChunkZ)
+void cClientHandle::SendUpdateBlockEntity(cBlockEntity & a_BlockEntity)
+{
+ m_Protocol->SendUpdateBlockEntity(a_BlockEntity);
+}
+
+
+
+
void cClientHandle::SendUpdateSign(
int a_BlockX, int a_BlockY, int a_BlockZ,
diff --git a/src/ClientHandle.h b/src/ClientHandle.h
index 636934f6f..e1f326543 100644
--- a/src/ClientHandle.h
+++ b/src/ClientHandle.h
@@ -140,6 +140,7 @@ public:
void SendThunderbolt (int a_BlockX, int a_BlockY, int a_BlockZ);
void SendTimeUpdate (Int64 a_WorldAge, Int64 a_TimeOfDay);
void SendUnloadChunk (int a_ChunkX, int a_ChunkZ);
+ void SendUpdateBlockEntity (cBlockEntity & a_BlockEntity);
void SendUpdateSign (int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4);
void SendUseBed (const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ );
void SendWeather (eWeather a_Weather);
diff --git a/src/Enchantments.cpp b/src/Enchantments.cpp
index 95ca201f0..1d8188e96 100644
--- a/src/Enchantments.cpp
+++ b/src/Enchantments.cpp
@@ -213,87 +213,8 @@ bool cEnchantments::operator !=(const cEnchantments & a_Other) const
-void cEnchantments::WriteToNBTCompound(cFastNBTWriter & a_Writer, const AString & a_ListTagName) const
-{
- // Write the enchantments into the specified NBT writer
- // begin with the LIST tag of the specified name ("ench" or "StoredEnchantments")
-
- a_Writer.BeginList(a_ListTagName, TAG_Compound);
- for (cMap::const_iterator itr = m_Enchantments.begin(), end = m_Enchantments.end(); itr != end; ++itr)
- {
- a_Writer.BeginCompound("");
- a_Writer.AddShort("id", itr->first);
- a_Writer.AddShort("lvl", itr->second);
- a_Writer.EndCompound();
- } // for itr - m_Enchantments[]
- a_Writer.EndList();
-}
-
-void cEnchantments::ParseFromNBT(const cParsedNBT & a_NBT, int a_EnchListTagIdx)
-{
- // Read the enchantments from the specified NBT list tag (ench or StoredEnchantments)
-
- // Verify that the tag is a list:
- if (a_NBT.GetType(a_EnchListTagIdx) != TAG_List)
- {
- LOGWARNING("%s: Invalid EnchListTag type: exp %d, got %d. Enchantments not parsed",
- __FUNCTION__, TAG_List, a_NBT.GetType(a_EnchListTagIdx)
- );
- ASSERT(!"Bad EnchListTag type");
- return;
- }
-
- // Verify that the list is of Compounds:
- if (a_NBT.GetChildrenType(a_EnchListTagIdx) != TAG_Compound)
- {
- LOGWARNING("%s: Invalid NBT list children type: exp %d, got %d. Enchantments not parsed",
- __FUNCTION__, TAG_Compound, a_NBT.GetChildrenType(a_EnchListTagIdx)
- );
- ASSERT(!"Bad EnchListTag children type");
- return;
- }
-
- Clear();
-
- // Iterate over all the compound children, parse an enchantment from each:
- for (int tag = a_NBT.GetFirstChild(a_EnchListTagIdx); tag >= 0; tag = a_NBT.GetNextSibling(tag))
- {
- // tag is the compound inside the "ench" list tag
- ASSERT(a_NBT.GetType(tag) == TAG_Compound);
-
- // Search for the id and lvl tags' values:
- int id = -1, lvl = -1;
- for (int ch = a_NBT.GetFirstChild(tag); ch >= 0; ch = a_NBT.GetNextSibling(ch))
- {
- if (a_NBT.GetType(ch) != TAG_Short)
- {
- continue;
- }
- if (a_NBT.GetName(ch) == "id")
- {
- id = a_NBT.GetShort(ch);
- }
- else if (a_NBT.GetName(ch) == "lvl")
- {
- lvl = a_NBT.GetShort(ch);
- }
- } // for ch - children of the compound tag
-
- if ((id == -1) || (lvl <= 0))
- {
- // Failed to parse either the id or the lvl, skip this compound
- continue;
- }
-
- // Store the enchantment:
- m_Enchantments[id] = lvl;
- } // for tag - children of the ench list tag
-}
-
-
-
diff --git a/src/Enchantments.h b/src/Enchantments.h
index 7581b87b5..e984df92e 100644
--- a/src/Enchantments.h
+++ b/src/Enchantments.h
@@ -8,6 +8,7 @@
#pragma once
+#include "WorldStorage/EnchantmentSerializer.h"
@@ -20,7 +21,6 @@ class cParsedNBT;
-// tolua_begin
/** Class that stores item enchantments or stored-enchantments
The enchantments may be serialized to a stringspec and read back from such stringspec.
@@ -29,10 +29,12 @@ mapping each enchantment's id onto its level. ID may be either a number or the e
Level value of 0 means no such enchantment, and it will not be stored in the m_Enchantments.
Serialization will never put zero-level enchantments into the stringspec and will always use numeric IDs.
*/
+// tolua_begin
class cEnchantments
{
public:
/// Individual enchantment IDs, corresponding to their NBT IDs ( http://www.minecraftwiki.net/wiki/Data_Values#Enchantment_IDs )
+
enum
{
enchProtection = 0,
@@ -97,10 +99,10 @@ public:
bool operator !=(const cEnchantments & a_Other) const;
/// Writes the enchantments into the specified NBT writer; begins with the LIST tag of the specified name ("ench" or "StoredEnchantments")
- void WriteToNBTCompound(cFastNBTWriter & a_Writer, const AString & a_ListTagName) const;
+ friend void EnchantmentSerializer::WriteToNBTCompound(cEnchantments const& a_Enchantments, cFastNBTWriter & a_Writer, const AString & a_ListTagName);
/// Reads the enchantments from the specified NBT list tag (ench or StoredEnchantments)
- void ParseFromNBT(const cParsedNBT & a_NBT, int a_EnchListTagIdx);
+ friend void EnchantmentSerializer::ParseFromNBT(cEnchantments& a_Enchantments, const cParsedNBT & a_NBT, int a_EnchListTagIdx);
protected:
/// Maps enchantment ID -> enchantment level
diff --git a/src/HTTPServer/HTTPConnection.cpp b/src/HTTPServer/HTTPConnection.cpp
index a4af706f2..78b7ce4d9 100644
--- a/src/HTTPServer/HTTPConnection.cpp
+++ b/src/HTTPServer/HTTPConnection.cpp
@@ -26,7 +26,7 @@ cHTTPConnection::cHTTPConnection(cHTTPServer & a_HTTPServer) :
cHTTPConnection::~cHTTPConnection()
{
- // LOGD("HTTP: Del connection at %p", this);
+ delete m_CurrentRequest;
}
diff --git a/src/OSSupport/Socket.cpp b/src/OSSupport/Socket.cpp
index 8ea5d8320..d80c9bb3d 100644
--- a/src/OSSupport/Socket.cpp
+++ b/src/OSSupport/Socket.cpp
@@ -87,6 +87,25 @@ void cSocket::CloseSocket()
+void cSocket::ShutdownReadWrite(void)
+{
+ #ifdef _WIN32
+ int res = shutdown(m_Socket, SD_BOTH);
+ #else
+ int res = shutdown(m_Socket, SHUT_RDWR);
+ #endif
+ if (res != 0)
+ {
+ LOGWARN("%s: Error shutting down socket %d (%s): %d (%s)",
+ __FUNCTION__, m_Socket, m_IPString.c_str(), this->GetLastError(), GetLastErrorString().c_str()
+ );
+ }
+}
+
+
+
+
+
AString cSocket::GetErrorString( int a_ErrNo )
{
char buffer[ 1024 ];
diff --git a/src/OSSupport/Socket.h b/src/OSSupport/Socket.h
index b86560de8..91c9ca5fd 100644
--- a/src/OSSupport/Socket.h
+++ b/src/OSSupport/Socket.h
@@ -39,7 +39,11 @@ public:
bool IsValid(void) const { return IsValidSocket(m_Socket); }
void CloseSocket(void);
-
+
+ /** Notifies the socket that we don't expect any more reads nor writes on it.
+ Most TCPIP implementations use this to send the FIN flag in a packet */
+ void ShutdownReadWrite(void);
+
operator xSocket(void) const;
xSocket GetSocket(void) const;
diff --git a/src/OSSupport/SocketThreads.cpp b/src/OSSupport/SocketThreads.cpp
index 3e505616c..b8069cf00 100644
--- a/src/OSSupport/SocketThreads.cpp
+++ b/src/OSSupport/SocketThreads.cpp
@@ -71,29 +71,6 @@ bool cSocketThreads::AddClient(const cSocket & a_Socket, cCallback * a_Client)
-/*
-void cSocketThreads::RemoveClient(const cSocket * a_Socket)
-{
- // Remove the socket (and associated client) from processing
-
- cCSLock Lock(m_CS);
- for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr)
- {
- if ((*itr)->RemoveSocket(a_Socket))
- {
- return;
- }
- } // for itr - m_Threads[]
-
- // Cannot assert here, this may actually happen legally, since cClientHandle has to clean up the socket and it may have already closed in the meantime
- // ASSERT(!"Removing an unknown socket");
-}
-*/
-
-
-
-
-
void cSocketThreads::RemoveClient(const cCallback * a_Client)
{
// Remove the associated socket and the client from processing
@@ -155,47 +132,6 @@ void cSocketThreads::Write(const cCallback * a_Client, const AString & a_Data)
-/// Stops reading from the socket - when this call returns, no more calls to the callbacks are made
-void cSocketThreads::StopReading(const cCallback * a_Client)
-{
- cCSLock Lock(m_CS);
- for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr)
- {
- if ((*itr)->StopReading(a_Client))
- {
- return;
- }
- } // for itr - m_Threads[]
-
- // Cannot assert, this normally happens if the socket is closed before the client deinitializes
- // ASSERT(!"Stopping reading on an unknown client");
-}
-
-
-
-
-
-/// Queues the socket for closing, as soon as its outgoing data is sent
-void cSocketThreads::QueueClose(const cCallback * a_Client)
-{
- LOGD("QueueClose(client %p)", a_Client);
-
- cCSLock Lock(m_CS);
- for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr)
- {
- if ((*itr)->QueueClose(a_Client))
- {
- return;
- }
- } // for itr - m_Threads[]
-
- ASSERT(!"Queueing close of an unknown client");
-}
-
-
-
-
-
////////////////////////////////////////////////////////////////////////////////
// cSocketThreads::cSocketThread:
@@ -233,13 +169,13 @@ cSocketThreads::cSocketThread::~cSocketThread()
void cSocketThreads::cSocketThread::AddClient(const cSocket & a_Socket, cCallback * a_Client)
{
+ ASSERT(m_Parent->m_CS.IsLockedByCurrentThread());
ASSERT(m_NumSlots < MAX_SLOTS); // Use HasEmptySlot() to check before adding
m_Slots[m_NumSlots].m_Client = a_Client;
m_Slots[m_NumSlots].m_Socket = a_Socket;
m_Slots[m_NumSlots].m_Outgoing.clear();
- m_Slots[m_NumSlots].m_ShouldClose = false;
- m_Slots[m_NumSlots].m_ShouldCallClient = true;
+ m_Slots[m_NumSlots].m_State = sSlot::ssNormal;
m_NumSlots++;
// Notify the thread of the change:
@@ -253,7 +189,7 @@ void cSocketThreads::cSocketThread::AddClient(const cSocket & a_Socket, cCallbac
bool cSocketThreads::cSocketThread::RemoveClient(const cCallback * a_Client)
{
- // Returns true if removed, false if not found
+ ASSERT(m_Parent->m_CS.IsLockedByCurrentThread());
if (m_NumSlots == 0)
{
@@ -267,36 +203,29 @@ bool cSocketThreads::cSocketThread::RemoveClient(const cCallback * a_Client)
continue;
}
- // Found, remove it:
- m_Slots[i] = m_Slots[--m_NumSlots];
-
- // Notify the thread of the change:
- ASSERT(m_ControlSocket2.IsValid());
- m_ControlSocket2.Send("r", 1);
- return true;
- } // for i - m_Slots[]
-
- // Not found
- return false;
-}
-
-
-
-
-
-bool cSocketThreads::cSocketThread::RemoveSocket(const cSocket * a_Socket)
-{
- // Returns true if removed, false if not found
-
- for (int i = m_NumSlots - 1; i >= 0 ; --i)
- {
- if (m_Slots[i].m_Socket != *a_Socket)
+ // Found the slot:
+ if (m_Slots[i].m_State == sSlot::ssRemoteClosed)
{
- continue;
+ // The remote has already closed the socket, remove the slot altogether:
+ m_Slots[i] = m_Slots[--m_NumSlots];
+ }
+ else
+ {
+ // Query and queue the last batch of outgoing data:
+ m_Slots[i].m_Client->GetOutgoingData(m_Slots[i].m_Outgoing);
+ if (m_Slots[i].m_Outgoing.empty())
+ {
+ // No more outgoing data, shut the socket down immediately:
+ m_Slots[i].m_Socket.ShutdownReadWrite();
+ m_Slots[i].m_State = sSlot::ssShuttingDown;
+ }
+ else
+ {
+ // More data to send, shut down reading and wait for the rest to get sent:
+ m_Slots[i].m_State = sSlot::ssWritingRestOut;
+ }
+ m_Slots[i].m_Client = NULL;
}
-
- // Found, remove it:
- m_Slots[i] = m_Slots[--m_NumSlots];
// Notify the thread of the change:
ASSERT(m_ControlSocket2.IsValid());
@@ -314,6 +243,8 @@ bool cSocketThreads::cSocketThread::RemoveSocket(const cSocket * a_Socket)
bool cSocketThreads::cSocketThread::HasClient(const cCallback * a_Client) const
{
+ ASSERT(m_Parent->m_CS.IsLockedByCurrentThread());
+
for (int i = m_NumSlots - 1; i >= 0; --i)
{
if (m_Slots[i].m_Client == a_Client)
@@ -346,6 +277,8 @@ bool cSocketThreads::cSocketThread::HasSocket(const cSocket * a_Socket) const
bool cSocketThreads::cSocketThread::NotifyWrite(const cCallback * a_Client)
{
+ ASSERT(m_Parent->m_CS.IsLockedByCurrentThread());
+
if (HasClient(a_Client))
{
// Notify the thread that there's another packet in the queue:
@@ -362,7 +295,7 @@ bool cSocketThreads::cSocketThread::NotifyWrite(const cCallback * a_Client)
bool cSocketThreads::cSocketThread::Write(const cCallback * a_Client, const AString & a_Data)
{
- // Returns true if socket handled by this thread
+ ASSERT(m_Parent->m_CS.IsLockedByCurrentThread());
for (int i = m_NumSlots - 1; i >= 0; --i)
{
if (m_Slots[i].m_Client == a_Client)
@@ -383,47 +316,6 @@ bool cSocketThreads::cSocketThread::Write(const cCallback * a_Client, const AStr
-bool cSocketThreads::cSocketThread::StopReading (const cCallback * a_Client)
-{
- // Returns true if client handled by this thread
- for (int i = m_NumSlots - 1; i >= 0; --i)
- {
- if (m_Slots[i].m_Client == a_Client)
- {
- m_Slots[i].m_ShouldCallClient = false;
- return true;
- }
- } // for i - m_Slots[]
- return false;
-}
-
-
-
-
-
-bool cSocketThreads::cSocketThread::QueueClose(const cCallback * a_Client)
-{
- // Returns true if socket handled by this thread
- for (int i = m_NumSlots - 1; i >= 0; --i)
- {
- if (m_Slots[i].m_Client == a_Client)
- {
- m_Slots[i].m_ShouldClose = true;
-
- // Notify the thread that there's a close queued (in case its conditions are already met):
- ASSERT(m_ControlSocket2.IsValid());
- m_ControlSocket2.Send("c", 1);
-
- return true;
- }
- } // for i - m_Slots[]
- return false;
-}
-
-
-
-
-
bool cSocketThreads::cSocketThread::Start(void)
{
// Create the control socket listener
@@ -497,10 +389,13 @@ void cSocketThreads::cSocketThread::Execute(void)
fd_set fdRead;
cSocket::xSocket Highest = m_ControlSocket1.GetSocket();
- PrepareSet(&fdRead, Highest);
+ PrepareSet(&fdRead, Highest, false);
// Wait for the sockets:
- if (select(Highest + 1, &fdRead, NULL, NULL, NULL) == -1)
+ timeval Timeout;
+ Timeout.tv_sec = 5;
+ Timeout.tv_usec = 0;
+ if (select(Highest + 1, &fdRead, NULL, NULL, &Timeout) == -1)
{
LOG("select(R) call failed in cSocketThread: \"%s\"", cSocket::GetLastErrorString().c_str());
continue;
@@ -511,8 +406,7 @@ void cSocketThreads::cSocketThread::Execute(void)
// Test sockets for writing:
fd_set fdWrite;
Highest = m_ControlSocket1.GetSocket();
- PrepareSet(&fdWrite, Highest);
- timeval Timeout;
+ PrepareSet(&fdWrite, Highest, true);
Timeout.tv_sec = 0;
Timeout.tv_usec = 0;
if (select(Highest + 1, NULL, &fdWrite, NULL, &Timeout) == -1)
@@ -522,6 +416,8 @@ void cSocketThreads::cSocketThread::Execute(void)
}
WriteToSockets(&fdWrite);
+
+ CleanUpShutSockets();
} // while (!mShouldTerminate)
}
@@ -529,7 +425,7 @@ void cSocketThreads::cSocketThread::Execute(void)
-void cSocketThreads::cSocketThread::PrepareSet(fd_set * a_Set, cSocket::xSocket & a_Highest)
+void cSocketThreads::cSocketThread::PrepareSet(fd_set * a_Set, cSocket::xSocket & a_Highest, bool a_IsForWriting)
{
FD_ZERO(a_Set);
FD_SET(m_ControlSocket1.GetSocket(), a_Set);
@@ -541,6 +437,11 @@ void cSocketThreads::cSocketThread::PrepareSet(fd_set * a_Set, cSocket::xSocket
{
continue;
}
+ if (m_Slots[i].m_State == sSlot::ssRemoteClosed)
+ {
+ // This socket won't provide nor consume any data anymore, don't put it in the Set
+ continue;
+ }
cSocket::xSocket s = m_Slots[i].m_Socket.GetSocket();
FD_SET(s, a_Set);
if (s > a_Highest)
@@ -576,29 +477,42 @@ void cSocketThreads::cSocketThread::ReadFromSockets(fd_set * a_Read)
}
char Buffer[1024];
int Received = m_Slots[i].m_Socket.Receive(Buffer, ARRAYCOUNT(Buffer), 0);
- if (Received == 0)
- {
- // The socket has been closed by the remote party, close our socket and let it be removed after we process all reading
- m_Slots[i].m_Socket.CloseSocket();
- if (m_Slots[i].m_ShouldCallClient)
- {
- m_Slots[i].m_Client->SocketClosed();
- }
- }
- else if (Received > 0)
+ if (Received <= 0)
{
- if (m_Slots[i].m_ShouldCallClient)
+ // The socket has been closed by the remote party
+ switch (m_Slots[i].m_State)
{
- m_Slots[i].m_Client->DataReceived(Buffer, Received);
- }
+ case sSlot::ssNormal:
+ {
+ // Notify the callback that the remote has closed the socket; keep the slot
+ m_Slots[i].m_Client->SocketClosed();
+ m_Slots[i].m_State = sSlot::ssRemoteClosed;
+ break;
+ }
+ case sSlot::ssWritingRestOut:
+ case sSlot::ssShuttingDown:
+ case sSlot::ssShuttingDown2:
+ {
+ // Force-close the socket and remove the slot:
+ m_Slots[i].m_Socket.CloseSocket();
+ m_Slots[i] = m_Slots[--m_NumSlots];
+ break;
+ }
+ default:
+ {
+ LOG("%s: Unexpected socket state: %d (%s)",
+ __FUNCTION__, m_Slots[i].m_Socket.GetSocket(), m_Slots[i].m_Socket.GetIPString().c_str()
+ );
+ ASSERT(!"Unexpected socket state");
+ break;
+ }
+ } // switch (m_Slots[i].m_State)
}
else
{
- // The socket has encountered an error, close it and let it be removed after we process all reading
- m_Slots[i].m_Socket.CloseSocket();
- if (m_Slots[i].m_ShouldCallClient)
+ if (m_Slots[i].m_Client != NULL)
{
- m_Slots[i].m_Client->SocketClosed();
+ m_Slots[i].m_Client->DataReceived(Buffer, Received);
}
}
} // for i - m_Slots[]
@@ -622,22 +536,17 @@ void cSocketThreads::cSocketThread::WriteToSockets(fd_set * a_Write)
if (m_Slots[i].m_Outgoing.empty())
{
// Request another chunk of outgoing data:
- if (m_Slots[i].m_ShouldCallClient)
+ if (m_Slots[i].m_Client != NULL)
{
m_Slots[i].m_Client->GetOutgoingData(m_Slots[i].m_Outgoing);
}
if (m_Slots[i].m_Outgoing.empty())
{
- // Nothing ready
- if (m_Slots[i].m_ShouldClose)
+ // No outgoing data is ready
+ if (m_Slots[i].m_State == sSlot::ssWritingRestOut)
{
- // Socket was queued for closing and there's no more data to send, close it now:
-
- // DEBUG
- LOGD("Socket was queued for closing, closing now. Slot %d, client %p, socket %d", i, m_Slots[i].m_Client, m_Slots[i].m_Socket.GetSocket());
-
- m_Slots[i].m_Socket.CloseSocket();
- // The slot must be freed actively by the client, using RemoveClient()
+ m_Slots[i].m_State = sSlot::ssShuttingDown;
+ m_Slots[i].m_Socket.ShutdownReadWrite();
}
continue;
}
@@ -649,7 +558,7 @@ void cSocketThreads::cSocketThread::WriteToSockets(fd_set * a_Write)
int Err = cSocket::GetLastError();
LOGWARNING("Error %d while writing to client \"%s\", disconnecting. \"%s\"", Err, m_Slots[i].m_Socket.GetIPString().c_str(), cSocket::GetErrorString(Err).c_str());
m_Slots[i].m_Socket.CloseSocket();
- if (m_Slots[i].m_ShouldCallClient)
+ if (m_Slots[i].m_Client != NULL)
{
m_Slots[i].m_Client->SocketClosed();
}
@@ -657,6 +566,12 @@ void cSocketThreads::cSocketThread::WriteToSockets(fd_set * a_Write)
}
m_Slots[i].m_Outgoing.erase(0, Sent);
+ if (m_Slots[i].m_Outgoing.empty() && (m_Slots[i].m_State == sSlot::ssWritingRestOut))
+ {
+ m_Slots[i].m_State = sSlot::ssShuttingDown;
+ m_Slots[i].m_Socket.ShutdownReadWrite();
+ }
+
// _X: If there's data left, it means the client is not reading fast enough, the server would unnecessarily spin in the main loop with zero actions taken; so signalling is disabled
// This means that if there's data left, it will be sent only when there's incoming data or someone queues another packet (for any socket handled by this thread)
/*
@@ -673,3 +588,31 @@ void cSocketThreads::cSocketThread::WriteToSockets(fd_set * a_Write)
+
+void cSocketThreads::cSocketThread::CleanUpShutSockets(void)
+{
+ for (int i = m_NumSlots - 1; i >= 0; i--)
+ {
+ switch (m_Slots[i].m_State)
+ {
+ case sSlot::ssShuttingDown2:
+ {
+ // The socket has reached the shutdown timeout, close it and clear its slot:
+ m_Slots[i].m_Socket.CloseSocket();
+ m_Slots[i] = m_Slots[--m_NumSlots];
+ break;
+ }
+ case sSlot::ssShuttingDown:
+ {
+ // The socket has been shut down for a single thread loop, let it loop once more before closing:
+ m_Slots[i].m_State = sSlot::ssShuttingDown2;
+ break;
+ }
+ default: break;
+ }
+ } // for i - m_Slots[]
+}
+
+
+
+
diff --git a/src/OSSupport/SocketThreads.h b/src/OSSupport/SocketThreads.h
index 858729c49..9e1947ab6 100644
--- a/src/OSSupport/SocketThreads.h
+++ b/src/OSSupport/SocketThreads.h
@@ -7,19 +7,20 @@
/*
Additional details:
-When a client is terminating a connection:
-- they call the StopReading() method to disable callbacks for the incoming data
-- they call the Write() method to queue any outstanding outgoing data
-- they call the QueueClose() method to queue the socket to close after outgoing data has been sent.
-When a socket slot is marked as having no callback, it is kept alive until its outgoing data queue is empty and its m_ShouldClose flag is set.
-This means that the socket can be written to several times before finally closing it via QueueClose()
+When a client wants to terminate the connection, they call the RemoveClient() function. This calls the
+callback one last time to read all the available outgoing data, putting it in the slot's m_OutgoingData
+buffer. Then it marks the slot as having no callback. The socket is kept alive until its outgoing data
+queue is empty, then shutdown is called on it and finally the socket is closed after a timeout.
+If at any time within this the remote end closes the socket, then the socket is closed directly.
+As soon as the socket is closed, the slot is finally removed from the SocketThread.
+The graph in $/docs/SocketThreads States.gv shows the state-machine transitions of the slot.
*/
-/// How many clients should one thread handle? (must be less than FD_SETSIZE for your platform)
+/** How many clients should one thread handle? (must be less than FD_SETSIZE for your platform) */
#define MAX_SLOTS 63
@@ -27,8 +28,6 @@ This means that the socket can be written to several times before finally closin
#pragma once
-#ifndef CSOCKETTHREADS_H_INCLUDED
-#define CSOCKETTHREADS_H_INCLUDED
#include "Socket.h"
#include "IsThread.h"
@@ -64,13 +63,13 @@ public:
// Force a virtual destructor in all subclasses:
virtual ~cCallback() {}
- /// Called when data is received from the remote party
+ /** Called when data is received from the remote party */
virtual void DataReceived(const char * a_Data, int a_Size) = 0;
- /// Called when data can be sent to remote party; the function is supposed to append outgoing data to a_Data
+ /** Called when data can be sent to remote party; the function is supposed to *append* outgoing data to a_Data */
virtual void GetOutgoingData(AString & a_Data) = 0;
- /// Called when the socket has been closed for any reason
+ /** Called when the socket has been closed for any reason */
virtual void SocketClosed(void) = 0;
} ;
@@ -78,24 +77,21 @@ public:
cSocketThreads(void);
~cSocketThreads();
- /// Add a (socket, client) pair for processing, data from a_Socket is to be sent to a_Client; returns true if successful
+ /** Add a (socket, client) pair for processing, data from a_Socket is to be sent to a_Client; returns true if successful */
bool AddClient(const cSocket & a_Socket, cCallback * a_Client);
- /// Remove the associated socket and the client from processing. The socket is left to send its data and is removed only after all its m_OutgoingData is sent
+ /** Remove the associated socket and the client from processing.
+ The socket is left to send its last outgoing data and is removed only after all its m_Outgoing is sent
+ and after the socket is properly shutdown (unless the remote disconnects before that)
+ */
void RemoveClient(const cCallback * a_Client);
- /// Notify the thread responsible for a_Client that the client has something to write
+ /** Notify the thread responsible for a_Client that the client has something to write */
void NotifyWrite(const cCallback * a_Client);
- /// Puts a_Data into outgoing data queue for a_Client
+ /** Puts a_Data into outgoing data queue for a_Client */
void Write(const cCallback * a_Client, const AString & a_Data);
- /// Stops reading from the client - when this call returns, no more calls to the callbacks are made
- void StopReading(const cCallback * a_Client);
-
- /// Queues the client for closing, as soon as its outgoing data is sent
- void QueueClose(const cCallback * a_Client);
-
private:
class cSocketThread :
@@ -114,13 +110,10 @@ private:
void AddClient (const cSocket & a_Socket, cCallback * a_Client); // Takes ownership of the socket
bool RemoveClient(const cCallback * a_Client); // Returns true if removed, false if not found
- bool RemoveSocket(const cSocket * a_Socket); // Returns true if removed, false if not found
bool HasClient (const cCallback * a_Client) const;
bool HasSocket (const cSocket * a_Socket) const;
bool NotifyWrite (const cCallback * a_Client); // Returns true if client handled by this thread
bool Write (const cCallback * a_Client, const AString & a_Data); // Returns true if client handled by this thread
- bool StopReading (const cCallback * a_Client); // Returns true if client handled by this thread
- bool QueueClose (const cCallback * a_Client); // Returns true if client handled by this thread
bool Start(void); // Hide the cIsThread's Start method, we need to provide our own startup to create the control socket
@@ -134,24 +127,45 @@ private:
cSocket m_ControlSocket1;
cSocket m_ControlSocket2;
- // Socket-client-packetqueues triplets.
+ // Socket-client-dataqueues-state quadruplets.
// Manipulation with these assumes that the parent's m_CS is locked
struct sSlot
{
- cSocket m_Socket; // The socket is primarily owned by this
+ /** The socket is primarily owned by this object */
+ cSocket m_Socket;
+
+ /** The callback to call for events. May be NULL */
cCallback * m_Client;
- AString m_Outgoing; // If sending writes only partial data, the rest is stored here for another send
- bool m_ShouldClose; // If true, the socket is to be closed after sending all outgoing data
- bool m_ShouldCallClient; // If true, the client callbacks are called. Set to false in StopReading()
+
+ /** If sending writes only partial data, the rest is stored here for another send.
+ Also used when the slot is being removed to store the last batch of outgoing data. */
+ AString m_Outgoing;
+
+ enum eState
+ {
+ ssNormal, ///< Normal read / write operations
+ ssWritingRestOut, ///< The client callback was removed, continue to send outgoing data
+ ssShuttingDown, ///< The last outgoing data has been sent, the socket has called shutdown()
+ ssShuttingDown2, ///< The shutdown has been done at least 1 thread loop ago (timeout detection)
+ ssRemoteClosed, ///< The remote end has closed the connection (and we still have a client callback)
+ } m_State;
} ;
+
sSlot m_Slots[MAX_SLOTS];
int m_NumSlots; // Number of slots actually used
virtual void Execute(void) override;
- void PrepareSet (fd_set * a_Set, cSocket::xSocket & a_Highest); // Puts all sockets into the set, along with m_ControlSocket1
+ /** Puts all sockets into the set, along with m_ControlSocket1.
+ Only sockets that are able to send and receive data are put in the Set.
+ Is a_IsForWriting is true, the ssWritingRestOut sockets are added as well. */
+ void PrepareSet(fd_set * a_Set, cSocket::xSocket & a_Highest, bool a_IsForWriting);
+
void ReadFromSockets(fd_set * a_Read); // Reads from sockets indicated in a_Read
void WriteToSockets (fd_set * a_Write); // Writes to sockets indicated in a_Write
+
+ /** Removes those slots in ssShuttingDown2 state, sets those with ssShuttingDown state to ssShuttingDown2 */
+ void CleanUpShutSockets(void);
} ;
typedef std::list<cSocketThread *> cSocketThreadList;
@@ -164,9 +178,3 @@ private:
-
-#endif // CSOCKETTHREADS_H_INCLUDED
-
-
-
-
diff --git a/src/Protocol/Protocol.h b/src/Protocol/Protocol.h
index 0a213f476..791082537 100644
--- a/src/Protocol/Protocol.h
+++ b/src/Protocol/Protocol.h
@@ -107,6 +107,7 @@ public:
virtual void SendThunderbolt (int a_BlockX, int a_BlockY, int a_BlockZ) = 0;
virtual void SendTimeUpdate (Int64 a_WorldAge, Int64 a_TimeOfDay) = 0;
virtual void SendUnloadChunk (int a_ChunkX, int a_ChunkZ) = 0;
+ virtual void SendUpdateBlockEntity (cBlockEntity & a_BlockEntity) = 0;
virtual void SendUpdateSign (int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4) = 0;
virtual void SendUseBed (const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ ) = 0;
virtual void SendWeather (eWeather a_Weather) = 0;
diff --git a/src/Protocol/Protocol125.h b/src/Protocol/Protocol125.h
index fb08fb120..cd15ab518 100644
--- a/src/Protocol/Protocol125.h
+++ b/src/Protocol/Protocol125.h
@@ -82,6 +82,7 @@ public:
virtual void SendThunderbolt (int a_BlockX, int a_BlockY, int a_BlockZ) override;
virtual void SendTimeUpdate (Int64 a_WorldAge, Int64 a_TimeOfDay) override;
virtual void SendUnloadChunk (int a_ChunkX, int a_ChunkZ) override;
+ virtual void SendUpdateBlockEntity (cBlockEntity & a_BlockEntity) override {};
virtual void SendUpdateSign (int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4) override;
virtual void SendUseBed (const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ ) override;
virtual void SendWeather (eWeather a_Weather) override;
diff --git a/src/Protocol/Protocol132.cpp b/src/Protocol/Protocol132.cpp
index 29fbb4bba..b4ca37d37 100644
--- a/src/Protocol/Protocol132.cpp
+++ b/src/Protocol/Protocol132.cpp
@@ -16,6 +16,7 @@
#include "../UI/Window.h"
#include "../Entities/Pickup.h"
#include "../WorldStorage/FastNBT.h"
+#include "../WorldStorage/EnchantmentSerializer.h"
#include "../StringCompression.h"
#ifdef _MSC_VER
@@ -763,7 +764,7 @@ void cProtocol132::WriteItem(const cItem & a_Item)
// Send the enchantments:
cFastNBTWriter Writer;
const char * TagName = (a_Item.m_ItemType == E_ITEM_BOOK) ? "StoredEnchantments" : "ench";
- a_Item.m_Enchantments.WriteToNBTCompound(Writer, TagName);
+ EnchantmentSerializer::WriteToNBTCompound(a_Item.m_Enchantments, Writer, TagName);
Writer.Finish();
AString Compressed;
CompressStringGZIP(Writer.GetResult().data(), Writer.GetResult().size(), Compressed);
@@ -849,7 +850,7 @@ int cProtocol132::ParseItemMetadata(cItem & a_Item, const AString & a_Metadata)
)
)
{
- a_Item.m_Enchantments.ParseFromNBT(NBT, tag);
+ EnchantmentSerializer::ParseFromNBT(a_Item.m_Enchantments, NBT, tag);
}
}
diff --git a/src/Protocol/Protocol17x.cpp b/src/Protocol/Protocol17x.cpp
index 926be6027..9506332dc 100644
--- a/src/Protocol/Protocol17x.cpp
+++ b/src/Protocol/Protocol17x.cpp
@@ -1,4 +1,3 @@
-
// Protocol17x.cpp
/*
@@ -16,6 +15,7 @@ Implements the 1.7.x protocol classes:
#include "../Server.h"
#include "../World.h"
#include "../WorldStorage/FastNBT.h"
+#include "../WorldStorage/EnchantmentSerializer.h"
#include "../StringCompression.h"
#include "../Entities/ExpOrb.h"
#include "../Entities/Minecart.h"
@@ -24,6 +24,7 @@ Implements the 1.7.x protocol classes:
#include "../Entities/Player.h"
#include "../Mobs/IncludeAllMonsters.h"
#include "../UI/Window.h"
+#include "../BlockEntities/CommandBlockEntity.h"
@@ -122,7 +123,7 @@ void cProtocol172::SendBlockAction(int a_BlockX, int a_BlockY, int a_BlockZ, cha
void cProtocol172::SendBlockBreakAnim(int a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage)
{
cPacketizer Pkt(*this, 0x25); // Block Break Animation packet
- Pkt.WriteInt(a_EntityID);
+ Pkt.WriteVarInt(a_EntityID);
Pkt.WriteInt(a_BlockX);
Pkt.WriteInt(a_BlockY);
Pkt.WriteInt(a_BlockZ);
@@ -932,6 +933,28 @@ void cProtocol172::SendUnloadChunk(int a_ChunkX, int a_ChunkZ)
+void cProtocol172::SendUpdateBlockEntity(cBlockEntity & a_BlockEntity)
+{
+ cPacketizer Pkt(*this, 0x35); // Update tile entity packet
+ Pkt.WriteInt(a_BlockEntity.GetPosX());
+ Pkt.WriteShort(a_BlockEntity.GetPosY());
+ Pkt.WriteInt(a_BlockEntity.GetPosZ());
+
+ Byte Action = 0;
+ switch (a_BlockEntity.GetBlockType())
+ {
+ case E_BLOCK_MOB_SPAWNER: Action = 1; break; // Update mob spawner spinny mob thing
+ case E_BLOCK_COMMAND_BLOCK: Action = 2; break; // Update command block text
+ default: ASSERT(!"Unhandled or unimplemented BlockEntity update request!"); break;
+ }
+ Pkt.WriteByte(Action);
+
+ Pkt.WriteBlockEntity(a_BlockEntity);
+}
+
+
+
+
void cProtocol172::SendUpdateSign(int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4)
{
@@ -1736,7 +1759,7 @@ void cProtocol172::ParseItemMetadata(cItem & a_Item, const AString & a_Metadata)
)
)
{
- a_Item.m_Enchantments.ParseFromNBT(NBT, tag);
+ EnchantmentSerializer::ParseFromNBT(a_Item.m_Enchantments, NBT, tag);
}
else if ((NBT.GetType(tag) == TAG_Compound) && (NBT.GetName(tag) == "display")) // Custom name and lore tag
{
@@ -1821,7 +1844,7 @@ void cProtocol172::cPacketizer::WriteItem(const cItem & a_Item)
if (!a_Item.m_Enchantments.IsEmpty())
{
const char * TagName = (a_Item.m_ItemType == E_ITEM_BOOK) ? "StoredEnchantments" : "ench";
- a_Item.m_Enchantments.WriteToNBTCompound(Writer, TagName);
+ EnchantmentSerializer::WriteToNBTCompound(a_Item.m_Enchantments,Writer, TagName);
}
if (!a_Item.IsBothNameAndLoreEmpty())
{
@@ -1859,6 +1882,51 @@ void cProtocol172::cPacketizer::WriteItem(const cItem & a_Item)
+void cProtocol172::cPacketizer::WriteBlockEntity(const cBlockEntity & a_BlockEntity)
+{
+ cFastNBTWriter Writer;
+
+ switch (a_BlockEntity.GetBlockType())
+ {
+ case E_BLOCK_COMMAND_BLOCK:
+ {
+ cCommandBlockEntity & CommandBlockEntity = (cCommandBlockEntity &)a_BlockEntity;
+
+ Writer.AddByte("TrackOutput", 1); // Neither I nor the MC wiki has any idea about this
+ Writer.AddInt("SuccessCount", CommandBlockEntity.GetResult());
+ Writer.AddInt("x", CommandBlockEntity.GetPosX());
+ Writer.AddInt("y", CommandBlockEntity.GetPosY());
+ Writer.AddInt("z", CommandBlockEntity.GetPosZ());
+ Writer.AddString("Command", CommandBlockEntity.GetCommand().c_str());
+ // You can set custom names for windows in Vanilla
+ // For a command block, this would be the 'name' prepended to anything it outputs into global chat
+ // MCS doesn't have this, so just leave it @ '@'. (geddit?)
+ Writer.AddString("CustomName", "@");
+ Writer.AddString("id", "Control"); // "Tile Entity ID" - MC wiki; vanilla server always seems to send this though
+
+ if (!CommandBlockEntity.GetLastOutput().empty())
+ {
+ AString Output;
+ Printf(Output, "{\"text\":\"%s\"}", CommandBlockEntity.GetLastOutput().c_str());
+
+ Writer.AddString("LastOutput", Output.c_str());
+ }
+ break;
+ }
+ default: break;
+ }
+
+ Writer.Finish();
+
+ AString Compressed;
+ CompressStringGZIP(Writer.GetResult().data(), Writer.GetResult().size(), Compressed);
+ WriteShort(Compressed.size());
+ WriteBuf(Compressed.data(), Compressed.size());
+}
+
+
+
+
void cProtocol172::cPacketizer::WriteByteAngle(double a_Angle)
{
diff --git a/src/Protocol/Protocol17x.h b/src/Protocol/Protocol17x.h
index bbbf820a6..3f440f313 100644
--- a/src/Protocol/Protocol17x.h
+++ b/src/Protocol/Protocol17x.h
@@ -105,6 +105,7 @@ public:
virtual void SendThunderbolt (int a_BlockX, int a_BlockY, int a_BlockZ) override;
virtual void SendTimeUpdate (Int64 a_WorldAge, Int64 a_TimeOfDay) override;
virtual void SendUnloadChunk (int a_ChunkX, int a_ChunkZ) override;
+ virtual void SendUpdateBlockEntity (cBlockEntity & a_BlockEntity) override;
virtual void SendUpdateSign (int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4) override;
virtual void SendUseBed (const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ ) override;
virtual void SendWeather (eWeather a_Weather) override;
@@ -192,6 +193,7 @@ protected:
void WriteEntityMetadata(const cEntity & a_Entity); // Writes the metadata for the specified entity, not including the terminating 0x7f
void WriteMobMetadata(const cMonster & a_Mob); // Writes the mob-specific metadata for the specified mob
void WriteEntityProperties(const cEntity & a_Entity); // Writes the entity properties for the specified entity, including the Count field
+ void WriteBlockEntity(const cBlockEntity & a_BlockEntity);
protected:
cProtocol172 & m_Protocol;
diff --git a/src/Protocol/ProtocolRecognizer.cpp b/src/Protocol/ProtocolRecognizer.cpp
index 04db2a995..de5dd3fb9 100644
--- a/src/Protocol/ProtocolRecognizer.cpp
+++ b/src/Protocol/ProtocolRecognizer.cpp
@@ -666,6 +666,16 @@ void cProtocolRecognizer::SendUnloadChunk(int a_ChunkX, int a_ChunkZ)
+void cProtocolRecognizer::SendUpdateBlockEntity(cBlockEntity & a_BlockEntity)
+{
+ ASSERT(m_Protocol != NULL);
+ m_Protocol->SendUpdateBlockEntity(a_BlockEntity);
+}
+
+
+
+
+
void cProtocolRecognizer::SendUpdateSign(int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4)
{
ASSERT(m_Protocol != NULL);
diff --git a/src/Protocol/ProtocolRecognizer.h b/src/Protocol/ProtocolRecognizer.h
index 0b811f4c6..daeed1845 100644
--- a/src/Protocol/ProtocolRecognizer.h
+++ b/src/Protocol/ProtocolRecognizer.h
@@ -117,6 +117,7 @@ public:
virtual void SendThunderbolt (int a_BlockX, int a_BlockY, int a_BlockZ) override;
virtual void SendTimeUpdate (Int64 a_WorldAge, Int64 a_TimeOfDay) override;
virtual void SendUnloadChunk (int a_ChunkX, int a_ChunkZ) override;
+ virtual void SendUpdateBlockEntity (cBlockEntity & a_BlockEntity) override;
virtual void SendUpdateSign (int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4) override;
virtual void SendUseBed (const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ ) override;
virtual void SendWeather (eWeather a_Weather) override;
diff --git a/src/Server.cpp b/src/Server.cpp
index eb76fcaeb..34ee066cb 100644
--- a/src/Server.cpp
+++ b/src/Server.cpp
@@ -118,7 +118,7 @@ cServer::cServer(void) :
void cServer::ClientDestroying(const cClientHandle * a_Client)
{
- m_SocketThreads.StopReading(a_Client);
+ m_SocketThreads.RemoveClient(a_Client);
}
@@ -143,15 +143,6 @@ void cServer::WriteToClient(const cClientHandle * a_Client, const AString & a_Da
-void cServer::QueueClientClose(const cClientHandle * a_Client)
-{
- m_SocketThreads.QueueClose(a_Client);
-}
-
-
-
-
-
void cServer::RemoveClient(const cClientHandle * a_Client)
{
m_SocketThreads.RemoveClient(a_Client);
diff --git a/src/Server.h b/src/Server.h
index 703a7077e..bb55e81b6 100644
--- a/src/Server.h
+++ b/src/Server.h
@@ -88,14 +88,14 @@ public: // tolua_export
const AString & GetServerID(void) const { return m_ServerID; } // tolua_export
- void ClientDestroying(const cClientHandle * a_Client); // Called by cClientHandle::Destroy(); stop m_SocketThreads from calling back into a_Client
+ /** Called by cClientHandle's destructor; stop m_SocketThreads from calling back into a_Client */
+ void ClientDestroying(const cClientHandle * a_Client);
- void NotifyClientWrite(const cClientHandle * a_Client); // Notifies m_SocketThreads that client has something to be written
+ /** Notifies m_SocketThreads that client has something to be written */
+ void NotifyClientWrite(const cClientHandle * a_Client);
void WriteToClient(const cClientHandle * a_Client, const AString & a_Data); // Queues outgoing data for the client through m_SocketThreads
- void QueueClientClose(const cClientHandle * a_Client); // Queues the clienthandle to close when all its outgoing data is sent
-
void RemoveClient(const cClientHandle * a_Client); // Removes the clienthandle from m_SocketThreads
/// Don't tick a_Client anymore, it will be ticked from its cPlayer instead
diff --git a/src/World.cpp b/src/World.cpp
index fb20e2242..f83de0342 100644
--- a/src/World.cpp
+++ b/src/World.cpp
@@ -866,28 +866,27 @@ void cWorld::TickQueuedTasks(void)
} // for itr - m_Tasks[]
}
-void cWorld::TickScheduledTasks()
+
+
+
+
+void cWorld::TickScheduledTasks(void)
{
- ScheduledTaskList Tasks;
// Make a copy of the tasks to avoid deadlocks on accessing m_Tasks
+ cScheduledTasks Tasks;
{
cCSLock Lock(m_CSScheduledTasks);
- ScheduledTaskList::iterator itr = m_ScheduledTasks.begin();
- while (itr != m_ScheduledTasks.end() && (*itr)->Ticks > 0)
+ while (!m_ScheduledTasks.empty() && (m_ScheduledTasks.front()->m_TargetTick < m_WorldAge))
{
Tasks.push_back(m_ScheduledTasks.front());
m_ScheduledTasks.pop_front();
}
- for(;itr != m_ScheduledTasks.end(); itr++)
- {
- (*itr)->Ticks--;
- }
}
// Execute and delete each task:
- for (ScheduledTaskList::iterator itr = Tasks.begin(), end = Tasks.end(); itr != end; ++itr)
+ for (cScheduledTasks::iterator itr = Tasks.begin(), end = Tasks.end(); itr != end; ++itr)
{
- (*itr)->Run(*this);
+ (*itr)->m_Task->Run(*this);
delete *itr;
} // for itr - m_Tasks[]
}
@@ -2687,18 +2686,25 @@ void cWorld::QueueTask(cTask * a_Task)
m_Tasks.push_back(a_Task);
}
-void cWorld::ScheduleTask(cScheduledTask * a_Task)
+
+
+
+
+void cWorld::ScheduleTask(int a_DelayTicks, cTask * a_Task)
{
+ Int64 TargetTick = a_DelayTicks + m_WorldAge;
+
+ // Insert the task into the list of scheduled tasks, ordered by its target tick
cCSLock Lock(m_CSScheduledTasks);
- for(ScheduledTaskList::iterator itr = m_ScheduledTasks.begin(); itr != m_ScheduledTasks.end(); itr++)
+ for (cScheduledTasks::iterator itr = m_ScheduledTasks.begin(), end = m_ScheduledTasks.end(); itr != end; ++itr)
{
- if((*itr)->Ticks >= a_Task->Ticks)
+ if ((*itr)->m_TargetTick >= TargetTick)
{
- m_ScheduledTasks.insert(itr, a_Task);
+ m_ScheduledTasks.insert(itr, new cScheduledTask(TargetTick, a_Task));
return;
}
}
- m_ScheduledTasks.push_back(a_Task);
+ m_ScheduledTasks.push_back(new cScheduledTask(TargetTick, a_Task));
}
diff --git a/src/World.h b/src/World.h
index 51c3af8cb..2d6baa99f 100644
--- a/src/World.h
+++ b/src/World.h
@@ -67,7 +67,7 @@ public:
// tolua_end
- /// A simple RAII locker for the chunkmap - locks the chunkmap in its constructor, unlocks it in the destructor
+ /** A simple RAII locker for the chunkmap - locks the chunkmap in its constructor, unlocks it in the destructor */
class cLock :
public cCSLock
{
@@ -75,8 +75,9 @@ public:
public:
cLock(cWorld & a_World);
} ;
+
- /// A common ancestor for all tasks queued onto the tick thread
+ /** A common ancestor for all tasks queued onto the tick thread */
class cTask
{
public:
@@ -84,18 +85,8 @@ public:
virtual void Run(cWorld & a_World) = 0;
} ;
- /// A common ancestor for all scheduled tasks queued onto the tick thread
- class cScheduledTask
- {
- public:
- cScheduledTask(const int a_Ticks) : Ticks(a_Ticks) {};
- virtual ~cScheduledTask() {};
- virtual void Run(cWorld & a_World) = 0;
- int Ticks;
- };
-
typedef std::vector<cTask *> cTasks;
- typedef std::list<cScheduledTask *> ScheduledTaskList;
+
class cTaskSaveAllChunks :
public cTask
@@ -129,16 +120,16 @@ public:
BroadcastTimeUpdate();
}
- /// Returns the current game mode. Partly OBSOLETE, you should use IsGameModeXXX() functions wherever applicable
+ /** Returns the current game mode. Partly OBSOLETE, you should use IsGameModeXXX() functions wherever applicable */
eGameMode GetGameMode(void) const { return m_GameMode; }
- /// Returns true if the world is in Creative mode
+ /** Returns true if the world is in Creative mode */
bool IsGameModeCreative(void) const { return (m_GameMode == gmCreative); }
- /// Returns true if the world is in Survival mode
+ /** Returns true if the world is in Survival mode */
bool IsGameModeSurvival(void) const { return (m_GameMode == gmSurvival); }
- /// Returns true if the world is in Adventure mode
+ /** Returns true if the world is in Adventure mode */
bool IsGameModeAdventure(void) const { return (m_GameMode == gmAdventure); }
bool IsPVPEnabled(void) const { return m_bEnabledPVP; }
@@ -148,12 +139,12 @@ public:
eDimension GetDimension(void) const { return m_Dimension; }
- /// Returns the world height at the specified coords; waits for the chunk to get loaded / generated
+ /** Returns the world height at the specified coords; waits for the chunk to get loaded / generated */
int GetHeight(int a_BlockX, int a_BlockZ);
// tolua_end
- /// Retrieves the world height at the specified coords; returns false if chunk not loaded / generated
+ /** Retrieves the world height at the specified coords; returns false if chunk not loaded / generated */
bool TryGetHeight(int a_BlockX, int a_BlockZ, int & a_Height); // Exported in ManualBindings.cpp
// Broadcast respective packets to all clients of the chunk where the event is taking place
@@ -191,7 +182,7 @@ public:
void BroadcastUseBed (const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ );
void BroadcastWeather (eWeather a_Weather, const cClientHandle * a_Exclude = NULL);
- /// If there is a block entity at the specified coords, sends it to the client specified
+ /** If there is a block entity at the specified coords, sends it to the client specified */
void SendBlockEntity(int a_BlockX, int a_BlockY, int a_BlockZ, cClientHandle & a_Client);
void MarkChunkDirty (int a_ChunkX, int a_ChunkZ);
@@ -225,7 +216,7 @@ public:
bool GetChunkData (int a_ChunkX, int a_ChunkZ, cChunkDataCallback & a_Callback);
- /// Gets the chunk's blocks, only the block types
+ /** Gets the chunk's blocks, only the block types */
bool GetChunkBlockTypes(int a_ChunkX, int a_ChunkZ, BLOCKTYPE * a_BlockTypes);
bool IsChunkValid (int a_ChunkX, int a_ChunkZ) const;
@@ -238,13 +229,13 @@ public:
void AddPlayer( cPlayer* a_Player );
void RemovePlayer( cPlayer* a_Player );
- /// Calls the callback for each player in the list; returns true if all players processed, false if the callback aborted by returning true
- bool ForEachPlayer(cPlayerListCallback & a_Callback); // >> EXPORTED IN MANUALBINDINGS <<
-
- /// Calls the callback for the player of the given name; returns true if the player was found and the callback called, false if player not found. Callback return ignored
- bool DoWithPlayer(const AString & a_PlayerName, cPlayerListCallback & a_Callback); // >> EXPORTED IN MANUALBINDINGS <<
+ /** Calls the callback for each player in the list; returns true if all players processed, false if the callback aborted by returning true */
+ bool ForEachPlayer(cPlayerListCallback & a_Callback); // >> EXPORTED IN MANUALBINDINGS <<
+
+ /** Calls the callback for the player of the given name; returns true if the player was found and the callback called, false if player not found. Callback return ignored */
+ bool DoWithPlayer(const AString & a_PlayerName, cPlayerListCallback & a_Callback); // >> EXPORTED IN MANUALBINDINGS <<
- /// Finds a player from a partial or complete player name and calls the callback - case-insensitive
+ /** Finds a player from a partial or complete player name and calls the callback - case-insensitive */
bool FindAndDoWithPlayer(const AString & a_PlayerNameHint, cPlayerListCallback & a_Callback); // >> EXPORTED IN MANUALBINDINGS <<
// TODO: This interface is dangerous - rewrite to DoWithClosestPlayer(pos, sight, action)
@@ -252,74 +243,74 @@ public:
void SendPlayerList(cPlayer * a_DestPlayer); // Sends playerlist to the player
- /// Adds the entity into its appropriate chunk; takes ownership of the entity ptr
+ /** Adds the entity into its appropriate chunk; takes ownership of the entity ptr */
void AddEntity(cEntity * a_Entity);
bool HasEntity(int a_UniqueID);
- /// Removes the entity, the entity ptr ownership is assumed taken by the caller
+ /** Removes the entity, the entity ptr ownership is assumed taken by the caller */
void RemoveEntity(cEntity * a_Entity);
- /// Calls the callback for each entity in the entire world; returns true if all entities processed, false if the callback aborted by returning true
+ /** Calls the callback for each entity in the entire world; returns true if all entities processed, false if the callback aborted by returning true */
bool ForEachEntity(cEntityCallback & a_Callback); // Exported in ManualBindings.cpp
- /// Calls the callback for each entity in the specified chunk; returns true if all entities processed, false if the callback aborted by returning true
+ /** Calls the callback for each entity in the specified chunk; returns true if all entities processed, false if the callback aborted by returning true */
bool ForEachEntityInChunk(int a_ChunkX, int a_ChunkZ, cEntityCallback & a_Callback); // Exported in ManualBindings.cpp
- /// Calls the callback if the entity with the specified ID is found, with the entity object as the callback param. Returns true if entity found and callback returned false.
+ /** Calls the callback if the entity with the specified ID is found, with the entity object as the callback param. Returns true if entity found and callback returned false. */
bool DoWithEntityByID(int a_UniqueID, cEntityCallback & a_Callback); // Exported in ManualBindings.cpp
- /// Compares clients of two chunks, calls the callback accordingly
+ /** Compares clients of two chunks, calls the callback accordingly */
void CompareChunkClients(int a_ChunkX1, int a_ChunkZ1, int a_ChunkX2, int a_ChunkZ2, cClientDiffCallback & a_Callback);
- /// Adds client to a chunk, if not already present; returns true if added, false if present
+ /** Adds client to a chunk, if not already present; returns true if added, false if present */
bool AddChunkClient(int a_ChunkX, int a_ChunkZ, cClientHandle * a_Client);
- /// Removes client from the chunk specified
+ /** Removes client from the chunk specified */
void RemoveChunkClient(int a_ChunkX, int a_ChunkZ, cClientHandle * a_Client);
- /// Removes the client from all chunks it is present in
+ /** Removes the client from all chunks it is present in */
void RemoveClientFromChunks(cClientHandle * a_Client);
- /// Sends the chunk to the client specified, if the chunk is valid. If not valid, the request is postponed (ChunkSender will send that chunk when it becomes valid+lighted)
+ /** Sends the chunk to the client specified, if the chunk is valid. If not valid, the request is postponed (ChunkSender will send that chunk when it becomes valid+lighted) */
void SendChunkTo(int a_ChunkX, int a_ChunkZ, cClientHandle * a_Client);
- /// Removes client from ChunkSender's queue of chunks to be sent
+ /** Removes client from ChunkSender's queue of chunks to be sent */
void RemoveClientFromChunkSender(cClientHandle * a_Client);
- /// Touches the chunk, causing it to be loaded or generated
+ /** Touches the chunk, causing it to be loaded or generated */
void TouchChunk(int a_ChunkX, int a_ChunkY, int a_ChunkZ);
- /// Loads the chunk, if not already loaded. Doesn't generate. Returns true if chunk valid (even if already loaded before)
+ /** Loads the chunk, if not already loaded. Doesn't generate. Returns true if chunk valid (even if already loaded before) */
bool LoadChunk(int a_ChunkX, int a_ChunkY, int a_ChunkZ);
- /// Loads the chunks specified. Doesn't report failure, other than chunks being !IsValid()
+ /** Loads the chunks specified. Doesn't report failure, other than chunks being !IsValid() */
void LoadChunks(const cChunkCoordsList & a_Chunks);
- /// Marks the chunk as failed-to-load:
+ /** Marks the chunk as failed-to-load: */
void ChunkLoadFailed(int a_ChunkX, int a_ChunkY, int a_ChunkZ);
- /// Sets the sign text, asking plugins for permission first. a_Player is the player who this change belongs to, may be NULL. Returns true if sign text changed. Same as UpdateSign()
+ /** Sets the sign text, asking plugins for permission first. a_Player is the player who this change belongs to, may be NULL. Returns true if sign text changed. Same as UpdateSign() */
bool SetSignLines(int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4, cPlayer * a_Player = NULL); // Exported in ManualBindings.cpp
- /// Sets the sign text, asking plugins for permission first. a_Player is the player who this change belongs to, may be NULL. Returns true if sign text changed. Same as SetSignLines()
+ /** Sets the sign text, asking plugins for permission first. a_Player is the player who this change belongs to, may be NULL. Returns true if sign text changed. Same as SetSignLines() */
bool UpdateSign(int a_X, int a_Y, int a_Z, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4, cPlayer * a_Player = NULL); // Exported in ManualBindings.cpp
- /// Marks (a_Stay == true) or unmarks (a_Stay == false) chunks as non-unloadable. To be used only by cChunkStay!
+ /** Marks (a_Stay == true) or unmarks (a_Stay == false) chunks as non-unloadable. To be used only by cChunkStay! */
void ChunksStay(const cChunkCoordsList & a_Chunks, bool a_Stay = true);
- /// Regenerate the given chunk:
+ /** Regenerate the given chunk: */
void RegenerateChunk(int a_ChunkX, int a_ChunkZ); // tolua_export
- /// Generates the given chunk, if not already generated
+ /** Generates the given chunk, if not already generated */
void GenerateChunk(int a_ChunkX, int a_ChunkZ); // tolua_export
- /// Queues a chunk for lighting; a_Callback is called after the chunk is lighted
+ /** Queues a chunk for lighting; a_Callback is called after the chunk is lighted */
void QueueLightChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * a_Callback = NULL);
bool IsChunkLighted(int a_ChunkX, int a_ChunkZ);
- /// Calls the callback for each chunk in the coords specified (all cords are inclusive). Returns true if all chunks have been processed successfully
+ /** Calls the callback for each chunk in the coords specified (all cords are inclusive). Returns true if all chunks have been processed successfully */
bool ForEachChunkInRect(int a_MinChunkX, int a_MaxChunkX, int a_MinChunkZ, int a_MaxChunkZ, cChunkDataCallback & a_Callback);
// tolua_begin
@@ -370,30 +361,30 @@ public:
// tolua_begin
- /// Spawns item pickups for each item in the list. May compress pickups if too many entities:
+ /** Spawns item pickups for each item in the list. May compress pickups if too many entities: */
void SpawnItemPickups(const cItems & a_Pickups, double a_BlockX, double a_BlockY, double a_BlockZ, double a_FlyAwaySpeed = 1.0, bool IsPlayerCreated = false);
- /// Spawns item pickups for each item in the list. May compress pickups if too many entities. All pickups get the speed specified:
+ /** Spawns item pickups for each item in the list. May compress pickups if too many entities. All pickups get the speed specified: */
void SpawnItemPickups(const cItems & a_Pickups, double a_BlockX, double a_BlockY, double a_BlockZ, double a_SpeedX, double a_SpeedY, double a_SpeedZ, bool IsPlayerCreated = false);
- /// Spawns an falling block entity at the given position. It returns the UniqueID of the spawned falling block.
+ /** Spawns an falling block entity at the given position. It returns the UniqueID of the spawned falling block. */
int SpawnFallingBlock(int a_X, int a_Y, int a_Z, BLOCKTYPE BlockType, NIBBLETYPE BlockMeta);
- /// Spawns an minecart at the given coordinates.
+ /** Spawns an minecart at the given coordinates. */
int SpawnMinecart(double a_X, double a_Y, double a_Z, int a_MinecartType, const cItem & a_Content = cItem(), int a_BlockHeight = 1);
- /// Spawns an experience orb at the given location with the given reward. It returns the UniqueID of the spawned experience orb.
+ /** Spawns an experience orb at the given location with the given reward. It returns the UniqueID of the spawned experience orb. */
int SpawnExperienceOrb(double a_X, double a_Y, double a_Z, int a_Reward);
- /// Spawns a new primed TNT entity at the specified block coords and specified fuse duration. Initial velocity is given based on the relative coefficient provided
+ /** Spawns a new primed TNT entity at the specified block coords and specified fuse duration. Initial velocity is given based on the relative coefficient provided */
void SpawnPrimedTNT(double a_X, double a_Y, double a_Z, double a_FuseTimeInSec, double a_InitialVelocityCoeff = 1);
// tolua_end
- /// Replaces world blocks with a_Blocks, if they are of type a_FilterBlockType
+ /** Replaces world blocks with a_Blocks, if they are of type a_FilterBlockType */
void ReplaceBlocks(const sSetBlockVector & a_Blocks, BLOCKTYPE a_FilterBlockType);
- /// Retrieves block types of the specified blocks. If a chunk is not loaded, doesn't modify the block. Returns true if all blocks were read.
+ /** Retrieves block types of the specified blocks. If a chunk is not loaded, doesn't modify the block. Returns true if all blocks were read. */
bool GetBlocks(sSetBlockVector & a_Blocks, bool a_ContinueOnFailure);
// tolua_begin
@@ -404,10 +395,10 @@ public:
double GetSpawnY(void) const { return m_SpawnY; }
double GetSpawnZ(void) const { return m_SpawnZ; }
- /// Wakes up the simulators for the specified block
+ /** Wakes up the simulators for the specified block */
void WakeUpSimulators(int a_BlockX, int a_BlockY, int a_BlockZ);
- /// Wakes up the simulators for the specified area of blocks
+ /** Wakes up the simulators for the specified area of blocks */
void WakeUpSimulatorsInArea(int a_MinBlockX, int a_MaxBlockX, int a_MinBlockY, int a_MaxBlockY, int a_MinBlockZ, int a_MaxBlockZ);
// tolua_end
@@ -418,22 +409,22 @@ public:
inline cFluidSimulator * GetLavaSimulator (void) { return m_LavaSimulator; }
inline cRedstoneSimulator * GetRedstoneSimulator(void) { return m_RedstoneSimulator; }
- /// Calls the callback for each block entity in the specified chunk; returns true if all block entities processed, false if the callback aborted by returning true
+ /** Calls the callback for each block entity in the specified chunk; returns true if all block entities processed, false if the callback aborted by returning true */
bool ForEachBlockEntityInChunk(int a_ChunkX, int a_ChunkZ, cBlockEntityCallback & a_Callback); // Exported in ManualBindings.cpp
- /// Calls the callback for each chest in the specified chunk; returns true if all chests processed, false if the callback aborted by returning true
+ /** Calls the callback for each chest in the specified chunk; returns true if all chests processed, false if the callback aborted by returning true */
bool ForEachChestInChunk(int a_ChunkX, int a_ChunkZ, cChestCallback & a_Callback); // Exported in ManualBindings.cpp
- /// Calls the callback for each dispenser in the specified chunk; returns true if all dispensers processed, false if the callback aborted by returning true
+ /** Calls the callback for each dispenser in the specified chunk; returns true if all dispensers processed, false if the callback aborted by returning true */
bool ForEachDispenserInChunk(int a_ChunkX, int a_ChunkZ, cDispenserCallback & a_Callback);
- /// Calls the callback for each dropper in the specified chunk; returns true if all droppers processed, false if the callback aborted by returning true
+ /** Calls the callback for each dropper in the specified chunk; returns true if all droppers processed, false if the callback aborted by returning true */
bool ForEachDropperInChunk(int a_ChunkX, int a_ChunkZ, cDropperCallback & a_Callback);
- /// Calls the callback for each dropspenser in the specified chunk; returns true if all dropspensers processed, false if the callback aborted by returning true
+ /** Calls the callback for each dropspenser in the specified chunk; returns true if all dropspensers processed, false if the callback aborted by returning true */
bool ForEachDropSpenserInChunk(int a_ChunkX, int a_ChunkZ, cDropSpenserCallback & a_Callback);
- /// Calls the callback for each furnace in the specified chunk; returns true if all furnaces processed, false if the callback aborted by returning true
+ /** Calls the callback for each furnace in the specified chunk; returns true if all furnaces processed, false if the callback aborted by returning true */
bool ForEachFurnaceInChunk(int a_ChunkX, int a_ChunkZ, cFurnaceCallback & a_Callback); // Exported in ManualBindings.cpp
/** Does an explosion with the specified strength at the specified coordinate
@@ -451,71 +442,71 @@ public:
*/
void DoExplosionAt(double a_ExplosionSize, double a_BlockX, double a_BlockY, double a_BlockZ, bool a_CanCauseFire, eExplosionSource a_Source, void * a_SourceData); // tolua_export
- /// Calls the callback for the block entity at the specified coords; returns false if there's no block entity at those coords, true if found
+ /** Calls the callback for the block entity at the specified coords; returns false if there's no block entity at those coords, true if found */
bool DoWithBlockEntityAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBlockEntityCallback & a_Callback); // Exported in ManualBindings.cpp
- /// Calls the callback for the chest at the specified coords; returns false if there's no chest at those coords, true if found
+ /** Calls the callback for the chest at the specified coords; returns false if there's no chest at those coords, true if found */
bool DoWithChestAt(int a_BlockX, int a_BlockY, int a_BlockZ, cChestCallback & a_Callback); // Exported in ManualBindings.cpp
- /// Calls the callback for the dispenser at the specified coords; returns false if there's no dispenser at those coords or callback returns true, returns true if found
+ /** Calls the callback for the dispenser at the specified coords; returns false if there's no dispenser at those coords or callback returns true, returns true if found */
bool DoWithDispenserAt(int a_BlockX, int a_BlockY, int a_BlockZ, cDispenserCallback & a_Callback); // Exported in ManualBindings.cpp
- /// Calls the callback for the dropper at the specified coords; returns false if there's no dropper at those coords or callback returns true, returns true if found
+ /** Calls the callback for the dropper at the specified coords; returns false if there's no dropper at those coords or callback returns true, returns true if found */
bool DoWithDropperAt(int a_BlockX, int a_BlockY, int a_BlockZ, cDropperCallback & a_Callback); // Exported in ManualBindings.cpp
- /// Calls the callback for the dropspenser at the specified coords; returns false if there's no dropspenser at those coords or callback returns true, returns true if found
+ /** Calls the callback for the dropspenser at the specified coords; returns false if there's no dropspenser at those coords or callback returns true, returns true if found */
bool DoWithDropSpenserAt(int a_BlockX, int a_BlockY, int a_BlockZ, cDropSpenserCallback & a_Callback); // Exported in ManualBindings.cpp
- /// Calls the callback for the furnace at the specified coords; returns false if there's no furnace at those coords or callback returns true, returns true if found
+ /** Calls the callback for the furnace at the specified coords; returns false if there's no furnace at those coords or callback returns true, returns true if found */
bool DoWithFurnaceAt(int a_BlockX, int a_BlockY, int a_BlockZ, cFurnaceCallback & a_Callback); // Exported in ManualBindings.cpp
- /// Calls the callback for the noteblock at the specified coords; returns false if there's no noteblock at those coords or callback returns true, returns true if found
+ /** Calls the callback for the noteblock at the specified coords; returns false if there's no noteblock at those coords or callback returns true, returns true if found */
bool DoWithNoteBlockAt(int a_BlockX, int a_BlockY, int a_BlockZ, cNoteBlockCallback & a_Callback); // Exported in ManualBindings.cpp
- /// Calls the callback for the command block at the specified coords; returns false if there's no command block at those coords or callback returns true, returns true if found
+ /** Calls the callback for the command block at the specified coords; returns false if there's no command block at those coords or callback returns true, returns true if found */
bool DoWithCommandBlockAt(int a_BlockX, int a_BlockY, int a_BlockZ, cCommandBlockCallback & a_Callback); // Exported in ManualBindings.cpp
- /// Retrieves the test on the sign at the specified coords; returns false if there's no sign at those coords, true if found
+ /** Retrieves the test on the sign at the specified coords; returns false if there's no sign at those coords, true if found */
bool GetSignLines (int a_BlockX, int a_BlockY, int a_BlockZ, AString & a_Line1, AString & a_Line2, AString & a_Line3, AString & a_Line4); // Exported in ManualBindings.cpp
- /// a_Player is using block entity at [x, y, z], handle that:
+ /** a_Player is using block entity at [x, y, z], handle that: */
void UseBlockEntity(cPlayer * a_Player, int a_BlockX, int a_BlockY, int a_BlockZ) {m_ChunkMap->UseBlockEntity(a_Player, a_BlockX, a_BlockY, a_BlockZ); } // tolua_export
- /// Calls the callback for the chunk specified, with ChunkMapCS locked; returns false if the chunk doesn't exist, otherwise returns the same value as the callback
+ /** Calls the callback for the chunk specified, with ChunkMapCS locked; returns false if the chunk doesn't exist, otherwise returns the same value as the callback */
bool DoWithChunk(int a_ChunkX, int a_ChunkZ, cChunkCallback & a_Callback);
void GrowTreeImage(const sSetBlockVector & a_Blocks);
// tolua_begin
- /// Grows a tree at the specified coords, either from a sapling there, or based on the biome
+ /** Grows a tree at the specified coords, either from a sapling there, or based on the biome */
void GrowTree (int a_BlockX, int a_BlockY, int a_BlockZ);
- /// Grows a tree at the specified coords, based on the sapling meta provided
+ /** Grows a tree at the specified coords, based on the sapling meta provided */
void GrowTreeFromSapling(int a_BlockX, int a_BlockY, int a_BlockZ, NIBBLETYPE a_SaplingMeta);
- /// Grows a tree at the specified coords, based on the biome in the place
+ /** Grows a tree at the specified coords, based on the biome in the place */
void GrowTreeByBiome (int a_BlockX, int a_BlockY, int a_BlockZ);
- /// Grows the plant at the specified block to its ripe stage (bonemeal used); returns false if the block is not growable. If a_IsBonemeal is true, block is not grown if not allowed in world.ini
+ /** Grows the plant at the specified block to its ripe stage (bonemeal used); returns false if the block is not growable. If a_IsBonemeal is true, block is not grown if not allowed in world.ini */
bool GrowRipePlant(int a_BlockX, int a_BlockY, int a_BlockZ, bool a_IsByBonemeal = false);
- /// Grows a cactus present at the block specified by the amount of blocks specified, up to the max height specified in the config
+ /** Grows a cactus present at the block specified by the amount of blocks specified, up to the max height specified in the config */
void GrowCactus(int a_BlockX, int a_BlockY, int a_BlockZ, int a_NumBlocksToGrow);
- /// Grows a melon or a pumpkin next to the block specified (assumed to be the stem)
+ /** Grows a melon or a pumpkin next to the block specified (assumed to be the stem) */
void GrowMelonPumpkin(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType);
- /// Grows a sugarcane present at the block specified by the amount of blocks specified, up to the max height specified in the config
+ /** Grows a sugarcane present at the block specified by the amount of blocks specified, up to the max height specified in the config */
void GrowSugarcane(int a_BlockX, int a_BlockY, int a_BlockZ, int a_NumBlocksToGrow);
- /// Returns the biome at the specified coords. Reads the biome from the chunk, if loaded, otherwise uses the world generator to provide the biome value
+ /** Returns the biome at the specified coords. Reads the biome from the chunk, if loaded, otherwise uses the world generator to provide the biome value */
int GetBiomeAt(int a_BlockX, int a_BlockZ);
- /// Returns the name of the world
+ /** Returns the name of the world */
const AString & GetName(void) const { return m_WorldName; }
- /// Returns the name of the world.ini file used by this world
+ /** Returns the name of the world.ini file used by this world */
const AString & GetIniFileName(void) const {return m_IniFileName; }
/// Returns the associated scoreboard instance
@@ -550,22 +541,23 @@ public:
if(a_Z < 0 && a_Z % cChunkDef::Width != 0) a_ChunkZ--;
}
- /// Saves all chunks immediately. Dangerous interface, may deadlock, use QueueSaveAllChunks() instead
+ /** Saves all chunks immediately. Dangerous interface, may deadlock, use QueueSaveAllChunks() instead */
void SaveAllChunks(void);
- /// Queues a task to save all chunks onto the tick thread. The prefferred way of saving chunks from external sources
+ /** Queues a task to save all chunks onto the tick thread. The prefferred way of saving chunks from external sources */
void QueueSaveAllChunks(void); // tolua_export
- /// Queues a task onto the tick thread. The task object will be deleted once the task is finished
+ /** Queues a task onto the tick thread. The task object will be deleted once the task is finished */
void QueueTask(cTask * a_Task); // Exported in ManualBindings.cpp
- // Queues a task onto the tick thread. The task object will be deleted once the task is finished
- void ScheduleTask(cScheduledTask * a_Task);
+ /** Queues a task onto the tick thread, with the specified delay.
+ The task object will be deleted once the task is finished */
+ void ScheduleTask(int a_DelayTicks, cTask * a_Task);
- /// Returns the number of chunks loaded
+ /** Returns the number of chunks loaded */
int GetNumChunks() const; // tolua_export
- /// Returns the number of chunks loaded and dirty, and in the lighting queue
+ /** Returns the number of chunks loaded and dirty, and in the lighting queue */
void GetChunkStats(int & a_NumValid, int & a_NumDirty, int & a_NumInLightingQueue);
// Various queues length queries (cannot be const, they lock their CS):
@@ -576,13 +568,13 @@ public:
void InitializeSpawn(void);
- /// Starts threads that belong to this world
+ /** Starts threads that belong to this world */
void Start(void);
- /// Stops threads that belong to this world (part of deinit)
+ /** Stops threads that belong to this world (part of deinit) */
void Stop(void);
- /// Processes the blocks queued for ticking with a delay (m_BlockTickQueue[])
+ /** Processes the blocks queued for ticking with a delay (m_BlockTickQueue[]) */
void TickQueuedBlocks(void);
struct BlockTickQueueItem
@@ -593,27 +585,27 @@ public:
int TicksToWait;
};
- /// Queues the block to be ticked after the specified number of game ticks
+ /** Queues the block to be ticked after the specified number of game ticks */
void QueueBlockForTick(int a_BlockX, int a_BlockY, int a_BlockZ, int a_TicksToWait); // tolua_export
// tolua_begin
- /// Casts a thunderbolt at the specified coords
+ /** Casts a thunderbolt at the specified coords */
void CastThunderbolt(int a_BlockX, int a_BlockY, int a_BlockZ);
- /// Sets the specified weather; resets weather interval; asks and notifies plugins of the change
+ /** Sets the specified weather; resets weather interval; asks and notifies plugins of the change */
void SetWeather (eWeather a_NewWeather);
- /// Forces a weather change in the next game tick
+ /** Forces a weather change in the next game tick */
void ChangeWeather (void);
- /// Returns the current weather. Instead of comparing values directly to the weather constants, use IsWeatherXXX() functions, if possible
+ /** Returns the current weather. Instead of comparing values directly to the weather constants, use IsWeatherXXX() functions, if possible */
eWeather GetWeather (void) const { return m_Weather; };
bool IsWeatherSunny(void) const { return (m_Weather == wSunny); }
bool IsWeatherRain (void) const { return (m_Weather == wRain); }
bool IsWeatherStorm(void) const { return (m_Weather == wStorm); }
- /// Returns true if the current weather has any precipitation - rain or storm
+ /** Returns true if the current weather has any precipitation - rain or storm */
bool IsWeatherWet (void) const { return (m_Weather != wSunny); }
// tolua_end
@@ -622,7 +614,7 @@ public:
cWorldStorage & GetStorage (void) { return m_Storage; }
cChunkMap * GetChunkMap (void) { return m_ChunkMap; }
- /// Sets the blockticking to start at the specified block. Only one blocktick per chunk may be set, second call overwrites the first call
+ /** Sets the blockticking to start at the specified block. Only one blocktick per chunk may be set, second call overwrites the first call */
void SetNextBlockTick(int a_BlockX, int a_BlockY, int a_BlockZ); // tolua_export
int GetMaxSugarcaneHeight(void) const { return m_MaxSugarcaneHeight; } // tolua_export
@@ -630,20 +622,20 @@ public:
bool IsBlockDirectlyWatered(int a_BlockX, int a_BlockY, int a_BlockZ); // tolua_export
- /// Spawns a mob of the specified type. Returns the mob's EntityID if recognized and spawned, <0 otherwise
+ /** Spawns a mob of the specified type. Returns the mob's EntityID if recognized and spawned, <0 otherwise */
int SpawnMob(double a_PosX, double a_PosY, double a_PosZ, cMonster::eType a_MonsterType); // tolua_export
int SpawnMobFinalize(cMonster* a_Monster);
- /// Creates a projectile of the specified type. Returns the projectile's EntityID if successful, <0 otherwise
+ /** Creates a projectile of the specified type. Returns the projectile's EntityID if successful, <0 otherwise */
int CreateProjectile(double a_PosX, double a_PosY, double a_PosZ, cProjectileEntity::eKind a_Kind, cEntity * a_Creator, const Vector3d * a_Speed = NULL); // tolua_export
- /// Returns a random number from the m_TickRand in range [0 .. a_Range]. To be used only in the tick thread!
+ /** Returns a random number from the m_TickRand in range [0 .. a_Range]. To be used only in the tick thread! */
int GetTickRandomNumber(unsigned a_Range) { return (int)(m_TickRand.randInt(a_Range)); }
- /// Appends all usernames starting with a_Text (case-insensitive) into Results
+ /** Appends all usernames starting with a_Text (case-insensitive) into Results */
void TabCompleteUserName(const AString & a_Text, AStringVector & a_Results);
- /// Get the current darkness level based on the time
+ /** Get the current darkness level based on the time */
NIBBLETYPE GetSkyDarkness() { return m_SkyDarkness; }
private:
@@ -686,18 +678,41 @@ private:
} ;
+ /** A container for tasks that have been scheduled for a specific game tick */
+ class cScheduledTask
+ {
+ public:
+ Int64 m_TargetTick;
+ cTask * m_Task;
+
+ /** Creates a new scheduled task; takes ownership of the task object passed to it. */
+ cScheduledTask(Int64 a_TargetTick, cTask * a_Task) :
+ m_TargetTick(a_TargetTick),
+ m_Task(a_Task)
+ {
+ }
+
+ virtual ~cScheduledTask()
+ {
+ delete m_Task;
+ }
+ };
+
+ typedef std::list<cScheduledTask *> cScheduledTasks;
+
+
AString m_WorldName;
AString m_IniFileName;
- /// Name of the storage schema used to load and save chunks
+ /** Name of the storage schema used to load and save chunks */
AString m_StorageSchema;
int m_StorageCompressionFactor;
- /// The dimension of the world, used by the client to provide correct lighting scheme
+ /** The dimension of the world, used by the client to provide correct lighting scheme */
eDimension m_Dimension;
- /// This random generator is to be used only in the Tick() method, and thus only in the World-Tick-thread (MTRand is not exactly thread-safe)
+ /** This random generator is to be used only in the Tick() method, and thus only in the World-Tick-thread (MTRand is not exactly thread-safe) */
MTRand m_TickRand;
bool m_IsSpawnExplicitlySet;
@@ -774,29 +789,30 @@ private:
cLightingThread m_Lighting;
cTickThread m_TickThread;
- /// Guards the m_Tasks
+ /** Guards the m_Tasks */
cCriticalSection m_CSTasks;
- /// Guards the m_ScheduledTasks
- cCriticalSection m_CSScheduledTasks;
-
- /// Tasks that have been queued onto the tick thread; guarded by m_CSTasks
+ /** Tasks that have been queued onto the tick thread; guarded by m_CSTasks */
cTasks m_Tasks;
- /// Tasks that have been queued to be executed on the tick thread at some number of ticks in
- /// the future; guarded by m_CSScheduledTasks
- ScheduledTaskList m_ScheduledTasks;
+ /** Guards the m_ScheduledTasks */
+ cCriticalSection m_CSScheduledTasks;
+
+ /** Tasks that have been queued to be executed on the tick thread at target tick in the future.
+ Ordered by increasing m_TargetTick.
+ Guarded by m_CSScheduledTasks */
+ cScheduledTasks m_ScheduledTasks;
- /// Guards m_Clients
+ /** Guards m_Clients */
cCriticalSection m_CSClients;
- /// List of clients in this world, these will be ticked by this world
+ /** List of clients in this world, these will be ticked by this world */
cClientHandleList m_Clients;
- /// Clients that are scheduled for removal (ticked in another world), waiting for TickClients() to remove them
+ /** Clients that are scheduled for removal (ticked in another world), waiting for TickClients() to remove them */
cClientHandleList m_ClientsToRemove;
- /// Clients that are scheduled for adding, waiting for TickClients to add them
+ /** Clients that are scheduled for adding, waiting for TickClients to add them */
cClientHandleList m_ClientsToAdd;
@@ -805,27 +821,27 @@ private:
void Tick(float a_Dt, int a_LastTickDurationMSec);
- /// Handles the weather in each tick
+ /** Handles the weather in each tick */
void TickWeather(float a_Dt);
- /// Handles the mob spawning/moving/destroying each tick
+ /** Handles the mob spawning/moving/destroying each tick */
void TickMobs(float a_Dt);
- /// Executes all tasks queued onto the tick thread
+ /** Executes all tasks queued onto the tick thread */
void TickQueuedTasks(void);
- /// Executes all tasks queued onto the tick thread
+ /** Executes all tasks queued onto the tick thread */
void TickScheduledTasks(void);
- /// Ticks all clients that are in this world
+ /** Ticks all clients that are in this world */
void TickClients(float a_Dt);
void UpdateSkyDarkness(void);
- /// <summary>Generates a random spawnpoint on solid land by walking chunks and finding their biomes</summary>
+ /** <summary>Generates a random spawnpoint on solid land by walking chunks and finding their biomes</summary> */
void GenerateRandomSpawn(void);
- /// Creates a new fluid simulator, loads its settings from the inifile (a_FluidName section)
+ /** Creates a new fluid simulator, loads its settings from the inifile (a_FluidName section) */
cFluidSimulator * InitializeFluidSimulator(cIniFile & a_IniFile, const char * a_FluidName, BLOCKTYPE a_SimulateBlock, BLOCKTYPE a_StationaryBlock);
}; // tolua_export
diff --git a/src/WorldStorage/EnchantmentSerializer.cpp b/src/WorldStorage/EnchantmentSerializer.cpp
new file mode 100644
index 000000000..56072207f
--- /dev/null
+++ b/src/WorldStorage/EnchantmentSerializer.cpp
@@ -0,0 +1,88 @@
+
+#include "Globals.h"
+
+#include "EnchantmentSerializer.h"
+#include "Enchantments.h"
+#include "FastNBT.h"
+
+void EnchantmentSerializer::WriteToNBTCompound(cEnchantments const& a_Enchantments, cFastNBTWriter & a_Writer, const AString & a_ListTagName)
+{
+ // Write the enchantments into the specified NBT writer
+ // begin with the LIST tag of the specified name ("ench" or "StoredEnchantments")
+
+ a_Writer.BeginList(a_ListTagName, TAG_Compound);
+ for (cEnchantments::cMap::const_iterator itr = a_Enchantments.m_Enchantments.begin(), end = a_Enchantments.m_Enchantments.end(); itr != end; ++itr)
+ {
+ a_Writer.BeginCompound("");
+ a_Writer.AddShort("id", itr->first);
+ a_Writer.AddShort("lvl", itr->second);
+ a_Writer.EndCompound();
+ } // for itr - m_Enchantments[]
+ a_Writer.EndList();
+}
+
+
+
+
+
+void EnchantmentSerializer::ParseFromNBT(cEnchantments& a_Enchantments, const cParsedNBT & a_NBT, int a_EnchListTagIdx)
+{
+ // Read the enchantments from the specified NBT list tag (ench or StoredEnchantments)
+
+ // Verify that the tag is a list:
+ if (a_NBT.GetType(a_EnchListTagIdx) != TAG_List)
+ {
+ LOGWARNING("%s: Invalid EnchListTag type: exp %d, got %d. Enchantments not parsed",
+ __FUNCTION__, TAG_List, a_NBT.GetType(a_EnchListTagIdx)
+ );
+ ASSERT(!"Bad EnchListTag type");
+ return;
+ }
+
+ // Verify that the list is of Compounds:
+ if (a_NBT.GetChildrenType(a_EnchListTagIdx) != TAG_Compound)
+ {
+ LOGWARNING("%s: Invalid NBT list children type: exp %d, got %d. Enchantments not parsed",
+ __FUNCTION__, TAG_Compound, a_NBT.GetChildrenType(a_EnchListTagIdx)
+ );
+ ASSERT(!"Bad EnchListTag children type");
+ return;
+ }
+
+ a_Enchantments.Clear();
+
+ // Iterate over all the compound children, parse an enchantment from each:
+ for (int tag = a_NBT.GetFirstChild(a_EnchListTagIdx); tag >= 0; tag = a_NBT.GetNextSibling(tag))
+ {
+ // tag is the compound inside the "ench" list tag
+ ASSERT(a_NBT.GetType(tag) == TAG_Compound);
+
+ // Search for the id and lvl tags' values:
+ int id = -1, lvl = -1;
+ for (int ch = a_NBT.GetFirstChild(tag); ch >= 0; ch = a_NBT.GetNextSibling(ch))
+ {
+ if (a_NBT.GetType(ch) != TAG_Short)
+ {
+ continue;
+ }
+ if (a_NBT.GetName(ch) == "id")
+ {
+ id = a_NBT.GetShort(ch);
+ }
+ else if (a_NBT.GetName(ch) == "lvl")
+ {
+ lvl = a_NBT.GetShort(ch);
+ }
+ } // for ch - children of the compound tag
+
+ if ((id == -1) || (lvl <= 0))
+ {
+ // Failed to parse either the id or the lvl, skip this compound
+ continue;
+ }
+
+ // Store the enchantment:
+ a_Enchantments.m_Enchantments[id] = lvl;
+ } // for tag - children of the ench list tag
+}
+
diff --git a/src/WorldStorage/EnchantmentSerializer.h b/src/WorldStorage/EnchantmentSerializer.h
new file mode 100644
index 000000000..9ed362900
--- /dev/null
+++ b/src/WorldStorage/EnchantmentSerializer.h
@@ -0,0 +1,17 @@
+
+#pragma once
+
+class cEnchantments;
+class cFastNBTWriter;
+class cParsedNBT;
+
+namespace EnchantmentSerializer
+{
+
+ /// Writes the enchantments into the specified NBT writer; begins with the LIST tag of the specified name ("ench" or "StoredEnchantments")
+ void WriteToNBTCompound(cEnchantments const& a_Enchantments, cFastNBTWriter & a_Writer, const AString & a_ListTagName);
+
+ /// Reads the enchantments from the specified NBT list tag (ench or StoredEnchantments)
+ void ParseFromNBT(cEnchantments& a_Enchantments, const cParsedNBT & a_NBT, int a_EnchListTagIdx);
+
+};
diff --git a/src/WorldStorage/NBTChunkSerializer.cpp b/src/WorldStorage/NBTChunkSerializer.cpp
index 447296c7f..c84128022 100644
--- a/src/WorldStorage/NBTChunkSerializer.cpp
+++ b/src/WorldStorage/NBTChunkSerializer.cpp
@@ -4,6 +4,7 @@
#include "Globals.h"
#include "NBTChunkSerializer.h"
+#include "EnchantmentSerializer.h"
#include "../BlockID.h"
#include "../ItemGrid.h"
#include "../StringCompression.h"
@@ -92,7 +93,7 @@ void cNBTChunkSerializer::AddItem(const cItem & a_Item, int a_Slot, const AStrin
{
const char * TagName = (a_Item.m_ItemType == E_ITEM_BOOK) ? "StoredEnchantments" : "ench";
m_Writer.BeginCompound("tag");
- a_Item.m_Enchantments.WriteToNBTCompound(m_Writer, TagName);
+ EnchantmentSerializer::WriteToNBTCompound(a_Item.m_Enchantments, m_Writer, TagName);
m_Writer.EndCompound();
}
diff --git a/src/WorldStorage/WSSAnvil.cpp b/src/WorldStorage/WSSAnvil.cpp
index 600eb0a51..8be6372e2 100644
--- a/src/WorldStorage/WSSAnvil.cpp
+++ b/src/WorldStorage/WSSAnvil.cpp
@@ -7,6 +7,7 @@
#include "WSSAnvil.h"
#include "NBTChunkSerializer.h"
#include "FastNBT.h"
+#include "EnchantmentSerializer.h"
#include "zlib/zlib.h"
#include "../World.h"
#include "../BlockID.h"
@@ -644,7 +645,7 @@ bool cWSSAnvil::LoadItemFromNBT(cItem & a_Item, const cParsedNBT & a_NBT, int a_
int EnchTag = a_NBT.FindChildByName(TagTag, EnchName);
if (EnchTag > 0)
{
- a_Item.m_Enchantments.ParseFromNBT(a_NBT, EnchTag);
+ EnchantmentSerializer::ParseFromNBT(a_Item.m_Enchantments, a_NBT, EnchTag);
}
return true;