diff --git a/MCServer/Plugins/APIDump/APIDesc.lua b/MCServer/Plugins/APIDump/APIDesc.lua
index ffd228d1a..718cb4e98 100644
--- a/MCServer/Plugins/APIDump/APIDesc.lua
+++ b/MCServer/Plugins/APIDump/APIDesc.lua
@@ -2344,6 +2344,7 @@ end
ForEachBlockEntityInChunk = { Params = "ChunkX, ChunkZ, CallbackFunction, [CallbackData]", Return = "bool", Notes = "Calls the specified callback for each block entity in the chunk. Returns true if all block entities in the chunk have been processed (including when there are zero block entities), or false if the callback has aborted the enumeration by returning true. The CallbackFunction has the following signature:
function Callback({{cBlockEntity|BlockEntity}}, [CallbackData])
The callback should return false or no value to continue with the next block entity, or true to abort the enumeration. Use {{tolua}}.cast() to cast the Callback's BlockEntity parameter to the correct {{cBlockEntity}} descendant." },
ForEachChestInChunk = { Params = "ChunkX, ChunkZ, CallbackFunction, [CallbackData]", Return = "bool", Notes = "Calls the specified callback for each chest in the chunk. Returns true if all chests in the chunk have been processed (including when there are zero chests), or false if the callback has aborted the enumeration by returning true. The CallbackFunction has the following signature: function Callback({{cChestEntity|ChestEntity}}, [CallbackData])
The callback should return false or no value to continue with the next chest, or true to abort the enumeration." },
ForEachEntity = { Params = "CallbackFunction, [CallbackData]", Return = "bool", Notes = "Calls the specified callback for each entity in the loaded world. Returns true if all the entities have been processed (including when there are zero entities), or false if the callback function has aborted the enumeration by returning true. The callback function has the following signature: function Callback({{cEntity|Entity}}, [CallbackData])
The callback should return false or no value to continue with the next entity, or true to abort the enumeration." },
+ ForEachEntityInBox = { Params = "{{cBoundingBox|Box}}, CallbackFunction", Return = "bool", Notes = "Calls the specified callback for each entity in the specified bounding box. Returns true if all the entities have been processed (including when there are zero entities), or false if the callback function has aborted the enumeration by returning true. If any chunk within the bounding box is not valid, it is silently skipped without any notification. The callback function has the following signature: function Callback({{cEntity|Entity}})
The callback should return false or no value to continue with the next entity, or true to abort the enumeration." },
ForEachEntityInChunk = { Params = "ChunkX, ChunkZ, CallbackFunction, [CallbackData]", Return = "bool", Notes = "Calls the specified callback for each entity in the specified chunk. Returns true if all the entities have been processed (including when there are zero entities), or false if the chunk is not loaded or the callback function has aborted the enumeration by returning true. The callback function has the following signature: function Callback({{cEntity|Entity}}, [CallbackData])
The callback should return false or no value to continue with the next entity, or true to abort the enumeration." },
ForEachFurnaceInChunk = { Params = "ChunkX, ChunkZ, CallbackFunction, [CallbackData]", Return = "bool", Notes = "Calls the specified callback for each furnace in the chunk. Returns true if all furnaces in the chunk have been processed (including when there are zero furnaces), or false if the callback has aborted the enumeration by returning true. The CallbackFunction has the following signature: function Callback({{cFurnaceEntity|FurnaceEntity}}, [CallbackData])
The callback should return false or no value to continue with the next furnace, or true to abort the enumeration." },
ForEachPlayer = { Params = "CallbackFunction, [CallbackData]", Return = "bool", Notes = "Calls the specified callback for each player in the loaded world. Returns true if all the players have been processed (including when there are zero players), or false if the callback function has aborted the enumeration by returning true. The callback function has the following signature: function Callback({{cPlayer|Player}}, [CallbackData])
The callback should return false or no value to continue with the next player, or true to abort the enumeration." },
diff --git a/MCServer/Plugins/Debuggers/Debuggers.lua b/MCServer/Plugins/Debuggers/Debuggers.lua
index 179935c08..0e7e647d5 100644
--- a/MCServer/Plugins/Debuggers/Debuggers.lua
+++ b/MCServer/Plugins/Debuggers/Debuggers.lua
@@ -65,6 +65,8 @@ function Initialize(Plugin)
PM:BindCommand("/sb", "debuggers", HandleSetBiome, "- Sets the biome around you to the specified one");
PM:BindCommand("/wesel", "debuggers", HandleWESel, "- Expands the current WE selection by 1 block in X/Z");
PM:BindCommand("/rmitem", "debuggers", HandleRMItem, "- Remove the specified item from the inventory.");
+ PM:BindCommand("/pickups", "debuggers", HandlePickups, "- Spawns random pickups around you");
+ PM:BindCommand("/poof", "debuggers", HandlePoof, "- Nudges pickups close to you away from you");
Plugin:AddWebTab("Debuggers", HandleRequest_Debuggers)
Plugin:AddWebTab("StressTest", HandleRequest_StressTest)
@@ -1558,3 +1560,57 @@ end
+
+local PossibleItems =
+{
+ cItem(E_ITEM_DIAMOND),
+ cItem(E_ITEM_GOLD),
+ cItem(E_ITEM_IRON),
+ cItem(E_ITEM_DYE, 1, E_META_DYE_BLUE), -- Lapis lazuli
+ cItem(E_ITEM_COAL),
+}
+
+
+
+
+
+function HandlePickups(a_Split, a_Player)
+ local PlayerX = a_Player:GetPosX()
+ local PlayerY = a_Player:GetPosY()
+ local PlayerZ = a_Player:GetPosZ()
+ local World = a_Player:GetWorld()
+ local Range = 12
+ for x = 0, Range do for z = 0, Range do
+ local px = PlayerX + x - Range / 2
+ local pz = PlayerZ + z - Range / 2
+ local Items = cItems()
+ Items:Add(PossibleItems[math.random(#PossibleItems)])
+ World:SpawnItemPickups(Items, px, PlayerY, pz, 0)
+ end end -- for z, for x
+ return true
+end
+
+
+
+
+function HandlePoof(a_Split, a_Player)
+ local PlayerPos = Vector3d(a_Player:GetPosition()) -- Create a copy of the position
+ PlayerPos.y = PlayerPos.y - 1
+ local Box = cBoundingBox(PlayerPos, 4, 2)
+ local NumEntities = 0
+ a_Player:GetWorld():ForEachEntityInBox(Box,
+ function (a_Entity)
+ if not(a_Entity:IsPlayer()) then
+ local AddSpeed = a_Entity:GetPosition() - PlayerPos -- Speed away from the player
+ a_Entity:AddSpeed(AddSpeed * 32 / (AddSpeed:SqrLength() + 1)) -- The further away, the less speed to add
+ NumEntities = NumEntities + 1
+ end
+ end
+ )
+ a_Player:SendMessage("Poof! (" .. NumEntities .. " entities)")
+ return true
+end
+
+
+
+
diff --git a/src/Bindings/LuaState.cpp b/src/Bindings/LuaState.cpp
index 9fe93ccc2..ba2f3c5e0 100644
--- a/src/Bindings/LuaState.cpp
+++ b/src/Bindings/LuaState.cpp
@@ -859,6 +859,32 @@ void cLuaState::GetStackValue(int a_StackPos, eWeather & a_ReturnedVal)
+void cLuaState::GetStackValue(int a_StackPos, pBoundingBox & a_ReturnedVal)
+{
+ tolua_Error err;
+ if (tolua_isusertype(m_LuaState, a_StackPos, "cBoundingBox", false, &err))
+ {
+ a_ReturnedVal = *((cBoundingBox **)lua_touserdata(m_LuaState, a_StackPos));
+ }
+}
+
+
+
+
+
+void cLuaState::GetStackValue(int a_StackPos, pWorld & a_ReturnedVal)
+{
+ tolua_Error err;
+ if (tolua_isusertype(m_LuaState, a_StackPos, "cWorld", false, &err))
+ {
+ a_ReturnedVal = *((cWorld **)lua_touserdata(m_LuaState, a_StackPos));
+ }
+}
+
+
+
+
+
bool cLuaState::CallFunction(int a_NumResults)
{
ASSERT (m_NumCurrentFunctionArgs >= 0); // A function must be pushed to stack first
diff --git a/src/Bindings/LuaState.h b/src/Bindings/LuaState.h
index eeb93fd4d..44f187701 100644
--- a/src/Bindings/LuaState.h
+++ b/src/Bindings/LuaState.h
@@ -59,6 +59,10 @@ class cTNTEntity;
class cCreeper;
class cHopperEntity;
class cBlockEntity;
+class cBoundingBox;
+
+typedef cBoundingBox * pBoundingBox;
+typedef cWorld * pWorld;
@@ -230,6 +234,12 @@ public:
/** Retrieve value at a_StackPos, if it is a valid number, converting and clamping it to eWeather.
If not, a_Value is unchanged. */
void GetStackValue(int a_StackPos, eWeather & a_Value);
+
+ /** Retrieve value at a_StackPos, if it is a valid cBoundingBox class. If not, a_Value is unchanged */
+ void GetStackValue(int a_StackPos, pBoundingBox & a_Value);
+
+ /** Retrieve value at a_StackPos, if it is a valid cWorld class. If not, a_Value is unchanged */
+ void GetStackValue(int a_StackPos, pWorld & a_Value);
// Include the cLuaState::Call() overload implementation that is generated by the gen_LuaState_Call.lua script:
@@ -328,6 +338,14 @@ protected:
*/
bool PushFunction(int a_FnRef);
+ /** Pushes a function that has been saved as a reference.
+ Returns true if successful. Logs a warning on failure
+ */
+ bool PushFunction(const cRef & a_FnRef)
+ {
+ return PushFunction((int)a_FnRef);
+ }
+
/** Pushes a function that is stored in a referenced table by name
Returns true if successful. Logs a warning on failure
*/
diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp
index adf10a72f..b7ea65759 100644
--- a/src/Bindings/ManualBindings.cpp
+++ b/src/Bindings/ManualBindings.cpp
@@ -676,6 +676,75 @@ static int tolua_ForEachInChunk(lua_State * tolua_S)
+template <
+ class Ty1,
+ class Ty2,
+ bool (Ty1::*Func1)(const cBoundingBox &, cItemCallback &)
+>
+static int tolua_ForEachInBox(lua_State * tolua_S)
+{
+ // Check params:
+ cLuaState L(tolua_S);
+ if (
+ !L.CheckParamUserType(1, "cWorld") ||
+ !L.CheckParamUserType(2, "cBoundingBox") ||
+ !L.CheckParamFunction(3) ||
+ !L.CheckParamEnd(4)
+ )
+ {
+ return 0;
+ }
+
+ // Get the params:
+ Ty1 * Self = NULL;
+ cBoundingBox * Box = NULL;
+ L.GetStackValues(1, Self, Box);
+ ASSERT(Self != NULL); // We have verified the type at the top, so we should get valid objects here
+ ASSERT(Box != NULL);
+
+ // Create a reference for the function:
+ cLuaState::cRef FnRef(L, 3);
+
+ // Callback wrapper for the Lua function:
+ class cLuaCallback : public cItemCallback
+ {
+ public:
+ cLuaCallback(cLuaState & a_LuaState, cLuaState::cRef & a_FuncRef) :
+ m_LuaState(a_LuaState),
+ m_FnRef(a_FuncRef)
+ {}
+
+ private:
+ // cItemCallback overrides:
+ virtual bool Item(Ty2 * a_Item) override
+ {
+ bool res = false;
+ if (!m_LuaState.Call(m_FnRef, a_Item, cLuaState::Return, res))
+ {
+ LOGWARNING("Failed to call Lua callback");
+ m_LuaState.LogStackTrace();
+ return true; // Abort enumeration
+ }
+
+ return res;
+ }
+ cLuaState & m_LuaState;
+ cLuaState::cRef & m_FnRef;
+ } Callback(L, FnRef);
+
+ bool bRetVal = (Self->*Func1)(*Box, Callback);
+
+ FnRef.UnRef();
+
+ /* Push return value on stack */
+ tolua_pushboolean(tolua_S, bRetVal);
+ return 1;
+}
+
+
+
+
+
template <
class Ty1,
class Ty2,
@@ -3327,6 +3396,7 @@ void ManualBindings::Bind(lua_State * tolua_S)
tolua_function(tolua_S, "ForEachBlockEntityInChunk", tolua_ForEachInChunk);
tolua_function(tolua_S, "ForEachChestInChunk", tolua_ForEachInChunk);
tolua_function(tolua_S, "ForEachEntity", tolua_ForEach< cWorld, cEntity, &cWorld::ForEachEntity>);
+ tolua_function(tolua_S, "ForEachEntityInBox", tolua_ForEachInBox< cWorld, cEntity, &cWorld::ForEachEntityInBox>);
tolua_function(tolua_S, "ForEachEntityInChunk", tolua_ForEachInChunk);
tolua_function(tolua_S, "ForEachFurnaceInChunk", tolua_ForEachInChunk);
tolua_function(tolua_S, "ForEachPlayer", tolua_ForEach< cWorld, cPlayer, &cWorld::ForEachPlayer>);
diff --git a/src/Bindings/gen_LuaState_Call.lua b/src/Bindings/gen_LuaState_Call.lua
index 2d8630d12..7f62573c7 100644
--- a/src/Bindings/gen_LuaState_Call.lua
+++ b/src/Bindings/gen_LuaState_Call.lua
@@ -109,7 +109,7 @@ local function WriteOverload(f, a_NumParams, a_NumReturns)
-- Write the function signature:
f:write("bool Call(")
- f:write("FnT a_Function")
+ f:write("const FnT & a_Function")
for i = 1, a_NumParams do
f:write(", ParamT", i, " a_Param", i)
end
diff --git a/src/BoundingBox.h b/src/BoundingBox.h
index 793466302..928e62afa 100644
--- a/src/BoundingBox.h
+++ b/src/BoundingBox.h
@@ -80,6 +80,17 @@ public:
/// Calculates the intersection of the two bounding boxes; returns true if nonempty
bool Intersect(const cBoundingBox & a_Other, cBoundingBox & a_Intersection);
+ double GetMinX(void) const { return m_Min.x; }
+ double GetMinY(void) const { return m_Min.y; }
+ double GetMinZ(void) const { return m_Min.z; }
+
+ double GetMaxX(void) const { return m_Max.x; }
+ double GetMaxY(void) const { return m_Max.y; }
+ double GetMaxZ(void) const { return m_Max.z; }
+
+ const Vector3d & GetMin(void) const { return m_Min; }
+ const Vector3d & GetMax(void) const { return m_Max; }
+
protected:
Vector3d m_Min;
Vector3d m_Max;
diff --git a/src/Chunk.cpp b/src/Chunk.cpp
index 95b8c8e5e..9fb178bc3 100644
--- a/src/Chunk.cpp
+++ b/src/Chunk.cpp
@@ -37,6 +37,7 @@
#include "MobSpawner.h"
#include "BlockInServerPluginInterface.h"
#include "SetChunkData.h"
+#include "BoundingBox.h"
#include "json/json.h"
@@ -1959,6 +1960,30 @@ bool cChunk::ForEachEntity(cEntityCallback & a_Callback)
+bool cChunk::ForEachEntityInBox(const cBoundingBox & a_Box, cEntityCallback & a_Callback)
+{
+ // The entity list is locked by the parent chunkmap's CS
+ for (cEntityList::iterator itr = m_Entities.begin(), itr2 = itr; itr != m_Entities.end(); itr = itr2)
+ {
+ ++itr2;
+ cBoundingBox EntBox((*itr)->GetPosition(), (*itr)->GetWidth() / 2, (*itr)->GetHeight());
+ if (!EntBox.DoesIntersect(a_Box))
+ {
+ // The entity is not in the specified box
+ continue;
+ }
+ if (a_Callback.Item(*itr))
+ {
+ return false;
+ }
+ } // for itr - m_Entitites[]
+ return true;
+}
+
+
+
+
+
bool cChunk::DoWithEntityByID(int a_EntityID, cEntityCallback & a_Callback, bool & a_CallbackResult)
{
// The entity list is locked by the parent chunkmap's CS
diff --git a/src/Chunk.h b/src/Chunk.h
index 3b5d1254e..0f4928b90 100644
--- a/src/Chunk.h
+++ b/src/Chunk.h
@@ -216,6 +216,10 @@ public:
/** Calls the callback for each entity; returns true if all entities processed, false if the callback aborted by returning true */
bool ForEachEntity(cEntityCallback & a_Callback); // Lua-accessible
+ /** Calls the callback for each entity that has a nonempty intersection with the specified boundingbox.
+ Returns true if all entities processed, false if the callback aborted by returning true. */
+ bool ForEachEntityInBox(const cBoundingBox & a_Box, cEntityCallback & a_Callback); // Lua-accessible
+
/** 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. */
bool DoWithEntityByID(int a_EntityID, cEntityCallback & a_Callback, bool & a_CallbackResult); // Lua-accessible
diff --git a/src/ChunkMap.cpp b/src/ChunkMap.cpp
index f14d680d4..8c765c8c9 100644
--- a/src/ChunkMap.cpp
+++ b/src/ChunkMap.cpp
@@ -1775,6 +1775,38 @@ bool cChunkMap::ForEachEntityInChunk(int a_ChunkX, int a_ChunkZ, cEntityCallback
+bool cChunkMap::ForEachEntityInBox(const cBoundingBox & a_Box, cEntityCallback & a_Callback)
+{
+ // Calculate the chunk range for the box:
+ int MinChunkX = (int)floor(a_Box.GetMinX() / cChunkDef::Width);
+ int MinChunkZ = (int)floor(a_Box.GetMinZ() / cChunkDef::Width);
+ int MaxChunkX = (int)floor((a_Box.GetMaxX() + cChunkDef::Width) / cChunkDef::Width);
+ int MaxChunkZ = (int)floor((a_Box.GetMaxZ() + cChunkDef::Width) / cChunkDef::Width);
+
+ // Iterate over each chunk in the range:
+ cCSLock Lock(m_CSLayers);
+ for (int z = MinChunkZ; z <= MaxChunkZ; z++)
+ {
+ for (int x = MinChunkX; x <= MaxChunkX; x++)
+ {
+ cChunkPtr Chunk = GetChunkNoGen(x, z);
+ if ((Chunk == NULL) || !Chunk->IsValid())
+ {
+ continue;
+ }
+ if (!Chunk->ForEachEntityInBox(a_Box, a_Callback))
+ {
+ return false;
+ }
+ } // for x
+ } // for z
+ return true;
+}
+
+
+
+
+
void cChunkMap::DoExplosionAt(double a_ExplosionSize, double a_BlockX, double a_BlockY, double a_BlockZ, cVector3iArray & a_BlocksAffected)
{
// Don't explode if outside of Y range (prevents the following test running into unallocated memory):
diff --git a/src/ChunkMap.h b/src/ChunkMap.h
index 6a379e51d..8c564d0de 100644
--- a/src/ChunkMap.h
+++ b/src/ChunkMap.h
@@ -36,6 +36,7 @@ class cBlockArea;
class cMobCensus;
class cMobSpawner;
class cSetChunkData;
+class cBoundingBox;
typedef std::list cClientHandleList;
typedef cChunk * cChunkPtr;
@@ -209,6 +210,11 @@ public:
/** 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); // Lua-accessible
+ /** Calls the callback for each entity that has a nonempty intersection with the specified boundingbox.
+ Returns true if all entities processed, false if the callback aborted by returning true.
+ If any chunk in the box is missing, ignores the entities in that chunk silently. */
+ bool ForEachEntityInBox(const cBoundingBox & a_Box, cEntityCallback & a_Callback); // Lua-accessible
+
/** Destroys and returns a list of blocks destroyed in the explosion at the specified coordinates */
void DoExplosionAt(double a_ExplosionSize, double a_BlockX, double a_BlockY, double a_BlockZ, cVector3iArray & a_BlockAffected);
diff --git a/src/World.cpp b/src/World.cpp
index dd6017f0d..2a3336dee 100644
--- a/src/World.cpp
+++ b/src/World.cpp
@@ -2696,6 +2696,15 @@ bool cWorld::ForEachEntityInChunk(int a_ChunkX, int a_ChunkZ, cEntityCallback &
+bool cWorld::ForEachEntityInBox(const cBoundingBox & a_Box, cEntityCallback & a_Callback)
+{
+ return m_ChunkMap->ForEachEntityInBox(a_Box, a_Callback);
+}
+
+
+
+
+
bool cWorld::DoWithEntityByID(int a_UniqueID, cEntityCallback & a_Callback)
{
return m_ChunkMap->DoWithEntityByID(a_UniqueID, a_Callback);
diff --git a/src/World.h b/src/World.h
index 49e30694e..49932ac9d 100644
--- a/src/World.h
+++ b/src/World.h
@@ -324,6 +324,11 @@ public:
/** 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 for each entity that has a nonempty intersection with the specified boundingbox.
+ Returns true if all entities processed, false if the callback aborted by returning true.
+ If any chunk in the box is missing, ignores the entities in that chunk silently. */
+ bool ForEachEntityInBox(const cBoundingBox & a_Box, 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. */
bool DoWithEntityByID(int a_UniqueID, cEntityCallback & a_Callback); // Exported in ManualBindings.cpp