diff options
Diffstat (limited to '')
29 files changed, 283 insertions, 223 deletions
diff --git a/src/Bindings/LuaState.cpp b/src/Bindings/LuaState.cpp index 7a5ed1425..32638df96 100644 --- a/src/Bindings/LuaState.cpp +++ b/src/Bindings/LuaState.cpp @@ -811,6 +811,18 @@ void cLuaState::GetStackValue(int a_StackPos, double & a_ReturnedVal) +void cLuaState::GetStackValue(int a_StackPos, eWeather & a_ReturnedVal) +{ + if (lua_isnumber(m_LuaState, a_StackPos)) + { + a_ReturnedVal = (eWeather)Clamp((int)tolua_tonumber(m_LuaState, a_StackPos, a_ReturnedVal), (int)wSunny, (int)wThunderstorm); + } +} + + + + + 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 8aefa7c6b..b1ac3578a 100644 --- a/src/Bindings/LuaState.h +++ b/src/Bindings/LuaState.h @@ -31,6 +31,7 @@ extern "C" } #include "../Vector3.h" +#include "../Defines.h" @@ -223,6 +224,10 @@ public: /** Retrieve value at a_StackPos, if it is a valid number. If not, a_Value is unchanged */ void GetStackValue(int a_StackPos, double & a_Value); + /** 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); + // Include the cLuaState::Call() overload implementation that is generated by the gen_LuaState_Call.lua script: #include "LuaState_Call.inc" diff --git a/src/Bindings/Plugin.h b/src/Bindings/Plugin.h index c6461c861..8ba20c026 100644 --- a/src/Bindings/Plugin.h +++ b/src/Bindings/Plugin.h @@ -72,6 +72,7 @@ public: virtual bool OnPlayerEating (cPlayer & a_Player) = 0; virtual bool OnPlayerFished (cPlayer & a_Player, const cItems & a_Reward) = 0; virtual bool OnPlayerFishing (cPlayer & a_Player, cItems & a_Reward) = 0; + virtual bool OnPlayerFoodLevelChange (cPlayer & a_Player, int a_NewFoodLevel) = 0; virtual bool OnPlayerJoined (cPlayer & a_Player) = 0; virtual bool OnPlayerLeftClick (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, char a_Status) = 0; virtual bool OnPlayerMoved (cPlayer & a_Player) = 0; diff --git a/src/Bindings/PluginLua.cpp b/src/Bindings/PluginLua.cpp index 97366cc4c..104380ea4 100644 --- a/src/Bindings/PluginLua.cpp +++ b/src/Bindings/PluginLua.cpp @@ -715,6 +715,26 @@ bool cPluginLua::OnPlayerEating(cPlayer & a_Player) +bool cPluginLua::OnPlayerFoodLevelChange(cPlayer & a_Player, int a_NewFoodLevel) +{ + cCSLock Lock(m_CriticalSection); + bool res = false; + cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PLAYER_FOOD_LEVEL_CHANGE]; + for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) + { + m_LuaState.Call((int)(**itr), &a_Player, a_NewFoodLevel, cLuaState::Return, res); + if (res) + { + return true; + } + } + return false; +} + + + + + bool cPluginLua::OnPlayerFished(cPlayer & a_Player, const cItems & a_Reward) { cCSLock Lock(m_CriticalSection); @@ -1327,18 +1347,15 @@ bool cPluginLua::OnWeatherChanging(cWorld & a_World, eWeather & a_NewWeather) { cCSLock Lock(m_CriticalSection); bool res = false; - int NewWeather = a_NewWeather; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_WEATHER_CHANGING]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) { - m_LuaState.Call((int)(**itr), &a_World, NewWeather, cLuaState::Return, res, NewWeather); + m_LuaState.Call((int)(**itr), &a_World, a_NewWeather, cLuaState::Return, res, a_NewWeather); if (res) { - a_NewWeather = (eWeather)NewWeather; return true; } } - a_NewWeather = (eWeather)NewWeather; return false; } diff --git a/src/Bindings/PluginLua.h b/src/Bindings/PluginLua.h index 598e031c0..9c9de95c6 100644 --- a/src/Bindings/PluginLua.h +++ b/src/Bindings/PluginLua.h @@ -95,6 +95,7 @@ public: virtual bool OnPlayerEating (cPlayer & a_Player) override; virtual bool OnPlayerFished (cPlayer & a_Player, const cItems & a_Reward) override; virtual bool OnPlayerFishing (cPlayer & a_Player, cItems & a_Reward) override; + virtual bool OnPlayerFoodLevelChange (cPlayer & a_Player, int a_NewFoodLevel) override; virtual bool OnPlayerJoined (cPlayer & a_Player) override; virtual bool OnPlayerMoved (cPlayer & a_Player) override; virtual bool OnPlayerLeftClick (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, char a_Status) override; diff --git a/src/Bindings/PluginManager.cpp b/src/Bindings/PluginManager.cpp index c50100d6f..7e6502515 100644 --- a/src/Bindings/PluginManager.cpp +++ b/src/Bindings/PluginManager.cpp @@ -257,18 +257,44 @@ bool cPluginManager::CallHookBlockToPickups( bool cPluginManager::CallHookChat(cPlayer * a_Player, AString & a_Message) { - bool WasCommandForbidden = false; - if (HandleCommand(a_Player, a_Message, true, WasCommandForbidden)) // We use HandleCommand as opposed to ExecuteCommand to accomodate the need to the WasCommandForbidden bool + // Check if the message contains a command, execute it: + switch (HandleCommand(a_Player, a_Message, true)) { - return true; // Chat message was handled as command - } - else if (WasCommandForbidden) // Couldn't be handled as command, was it because of insufficient permissions? - { - return true; // Yes - message was sent in HandleCommand, abort + case crExecuted: + { + // The command has executed successfully + return true; + } + + case crBlocked: + { + // The command was blocked by a plugin using HOOK_EXECUTE_COMMAND + // The plugin has most likely sent a message to the player already + return true; + } + + case crError: + { + // An error in the plugin has prevented the command from executing. Report the error to the player: + a_Player->SendMessageFailure(Printf("Something went wrong while executing command \"%s\"", a_Message.c_str())); + return true; + } + + case crNoPermission: + { + // The player is not allowed to execute this command + a_Player->SendMessageFailure(Printf("Forbidden command; insufficient privileges: \"%s\"", a_Message.c_str())); + return true; + } + + case crUnknownCommand: + { + // This was not a known command, keep processing as a message + break; + } } - // Check if it was a standard command (starts with a slash) - // If it was, we know that it was completely unrecognised (WasCommandForbidden == false) + // Check if the message is a command (starts with a slash). If it is, we know that it wasn't recognised: if (!a_Message.empty() && (a_Message[0] == '/')) { AStringVector Split(StringSplit(a_Message, " ")); @@ -695,6 +721,25 @@ bool cPluginManager::CallHookPlayerEating(cPlayer & a_Player) +bool cPluginManager::CallHookPlayerFoodLevelChange(cPlayer & a_Player, int a_NewFoodLevel) +{ + FIND_HOOK(HOOK_PLAYER_FOOD_LEVEL_CHANGE); + VERIFY_HOOK; + + for (PluginList::iterator itr = Plugins->second.begin(); itr != Plugins->second.end(); ++itr) + { + if ((*itr)->OnPlayerFoodLevelChange(a_Player, a_NewFoodLevel)) + { + return true; + } + } + return false; +} + + + + + bool cPluginManager::CallHookPlayerFished(cPlayer & a_Player, const cItems a_Reward) { FIND_HOOK(HOOK_PLAYER_FISHED); @@ -1318,28 +1363,28 @@ bool cPluginManager::CallHookWorldTick(cWorld & a_World, float a_Dt, int a_LastT -bool cPluginManager::HandleCommand(cPlayer * a_Player, const AString & a_Command, bool a_ShouldCheckPermissions, bool & a_WasCommandForbidden) +cPluginManager::CommandResult cPluginManager::HandleCommand(cPlayer * a_Player, const AString & a_Command, bool a_ShouldCheckPermissions) { ASSERT(a_Player != NULL); AStringVector Split(StringSplit(a_Command, " ")); if (Split.empty()) { - return false; + return crUnknownCommand; } CommandMap::iterator cmd = m_Commands.find(Split[0]); if (cmd == m_Commands.end()) { // Command not found - return false; + return crUnknownCommand; } // Ask plugins first if a command is okay to execute the command: if (CallHookExecuteCommand(a_Player, Split)) { LOGINFO("Player %s tried executing command \"%s\" that was stopped by the HOOK_EXECUTE_COMMAND hook", a_Player->GetName().c_str(), Split[0].c_str()); - return false; + return crBlocked; } if ( @@ -1348,15 +1393,18 @@ bool cPluginManager::HandleCommand(cPlayer * a_Player, const AString & a_Command !a_Player->HasPermission(cmd->second.m_Permission) ) { - a_Player->SendMessageFailure(Printf("Forbidden command; insufficient privileges: \"%s\"", Split[0].c_str())); LOGINFO("Player %s tried to execute forbidden command: \"%s\"", a_Player->GetName().c_str(), Split[0].c_str()); - a_WasCommandForbidden = true; - return false; + return crNoPermission; } ASSERT(cmd->second.m_Plugin != NULL); - return cmd->second.m_Plugin->HandleCommand(Split, a_Player); + if (!cmd->second.m_Plugin->HandleCommand(Split, a_Player)) + { + return crError; + } + + return crExecuted; } @@ -1554,7 +1602,7 @@ AString cPluginManager::GetCommandPermission(const AString & a_Command) -bool cPluginManager::ExecuteCommand(cPlayer * a_Player, const AString & a_Command) +cPluginManager::CommandResult cPluginManager::ExecuteCommand(cPlayer * a_Player, const AString & a_Command) { return HandleCommand(a_Player, a_Command, true); } @@ -1563,7 +1611,7 @@ bool cPluginManager::ExecuteCommand(cPlayer * a_Player, const AString & a_Comman -bool cPluginManager::ForceExecuteCommand(cPlayer * a_Player, const AString & a_Command) +cPluginManager::CommandResult cPluginManager::ForceExecuteCommand(cPlayer * a_Player, const AString & a_Command) { return HandleCommand(a_Player, a_Command, false); } diff --git a/src/Bindings/PluginManager.h b/src/Bindings/PluginManager.h index be40bd2f7..d435024bb 100644 --- a/src/Bindings/PluginManager.h +++ b/src/Bindings/PluginManager.h @@ -57,8 +57,17 @@ public: // tolua_export // Called each tick virtual void Tick(float a_Dt); - + // tolua_begin + enum CommandResult + { + crExecuted, + crUnknownCommand, + crError, + crBlocked, + crNoPermission, + } ; + enum PluginHook { HOOK_BLOCK_SPREAD, @@ -87,6 +96,7 @@ public: // tolua_export HOOK_PLAYER_EATING, HOOK_PLAYER_FISHED, HOOK_PLAYER_FISHING, + HOOK_PLAYER_FOOD_LEVEL_CHANGE, HOOK_PLAYER_JOINED, HOOK_PLAYER_LEFT_CLICK, HOOK_PLAYER_MOVING, @@ -188,6 +198,7 @@ public: // tolua_export bool CallHookPlayerEating (cPlayer & a_Player); bool CallHookPlayerFished (cPlayer & a_Player, const cItems a_Reward); bool CallHookPlayerFishing (cPlayer & a_Player, cItems a_Reward); + bool CallHookPlayerFoodLevelChange (cPlayer & a_Player, int a_NewFoodLevel); bool CallHookPlayerJoined (cPlayer & a_Player); bool CallHookPlayerMoving (cPlayer & a_Player); bool CallHookPlayerLeftClick (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, char a_Status); @@ -244,11 +255,11 @@ public: // tolua_export /** Returns the permission needed for the specified command; empty string if command not found */ AString GetCommandPermission(const AString & a_Command); // tolua_export - /** Executes the command, as if it was requested by a_Player. Checks permissions first. Returns true if executed. */ - bool ExecuteCommand(cPlayer * a_Player, const AString & a_Command); // tolua_export + /** Executes the command, as if it was requested by a_Player. Checks permissions first. Returns crExecuted if executed. */ + CommandResult ExecuteCommand(cPlayer * a_Player, const AString & a_Command); // tolua_export - /** Executes the command, as if it was requested by a_Player. Permisssions are not checked. Returns true if executed (false if not found) */ - bool ForceExecuteCommand(cPlayer * a_Player, const AString & a_Command); // tolua_export + /** Executes the command, as if it was requested by a_Player. Permisssions are not checked. Returns crExecuted if executed. */ + CommandResult ForceExecuteCommand(cPlayer * a_Player, const AString & a_Command); // tolua_export /** Removes all console command bindings that the specified plugin has made */ void RemovePluginConsoleCommands(cPlugin * a_Plugin); @@ -321,13 +332,8 @@ private: /** Adds the plugin into the internal list of plugins and initializes it. If initialization fails, the plugin is removed again. */ bool AddPlugin(cPlugin * a_Plugin); - /** Tries to match a_Command to the internal table of commands, if a match is found, the corresponding plugin is called. Returns true if the command is handled. */ - bool HandleCommand(cPlayer * a_Player, const AString & a_Command, bool a_ShouldCheckPermissions, bool & a_WasCommandForbidden); - bool HandleCommand(cPlayer * a_Player, const AString & a_Command, bool a_ShouldCheckPermissions) - { - bool DummyBoolean = false; - return HandleCommand(a_Player, a_Command, a_ShouldCheckPermissions, DummyBoolean); - } + /** Tries to match a_Command to the internal table of commands, if a match is found, the corresponding plugin is called. Returns crExecuted if the command is executed. */ + cPluginManager::CommandResult HandleCommand(cPlayer * a_Player, const AString & a_Command, bool a_ShouldCheckPermissions); } ; // tolua_export diff --git a/src/BlockEntities/EnderChestEntity.cpp b/src/BlockEntities/EnderChestEntity.cpp index e53930798..17816d63e 100644 --- a/src/BlockEntities/EnderChestEntity.cpp +++ b/src/BlockEntities/EnderChestEntity.cpp @@ -12,9 +12,8 @@ cEnderChestEntity::cEnderChestEntity(int a_BlockX, int a_BlockY, int a_BlockZ, cWorld * a_World) : - super(E_BLOCK_ENDER_CHEST, a_BlockX, a_BlockY, a_BlockZ, ContentsWidth, ContentsHeight, a_World) + super(E_BLOCK_ENDER_CHEST, a_BlockX, a_BlockY, a_BlockZ, a_World) { - cBlockEntityWindowOwner::SetBlockEntity(this); } @@ -34,95 +33,63 @@ cEnderChestEntity::~cEnderChestEntity() -bool cEnderChestEntity::LoadFromJson(const Json::Value & a_Value) +void cEnderChestEntity::UsedBy(cPlayer * a_Player) { - m_PosX = a_Value.get("x", 0).asInt(); - m_PosY = a_Value.get("y", 0).asInt(); - m_PosZ = a_Value.get("z", 0).asInt(); - - Json::Value AllSlots = a_Value.get("Slots", 0); - int SlotIdx = 0; - for (Json::Value::iterator itr = AllSlots.begin(); itr != AllSlots.end(); ++itr) + // If the window is not created, open it anew: + cWindow * Window = GetWindow(); + if (Window == NULL) { - cItem Item; - Item.FromJson(*itr); - SetSlot(SlotIdx, Item); - SlotIdx++; + OpenNewWindow(); + Window = GetWindow(); } - return true; -} - - - - - -void cEnderChestEntity::SaveToJson(Json::Value & a_Value) -{ - a_Value["x"] = m_PosX; - a_Value["y"] = m_PosY; - a_Value["z"] = m_PosZ; - - Json::Value AllSlots; - for (int i = m_Contents.GetNumSlots() - 1; i >= 0; i--) + + // Open the window for the player: + if (Window != NULL) { - Json::Value Slot; - m_Contents.GetSlot(i).GetJson(Slot); - AllSlots.append(Slot); + if (a_Player->GetWindow() != Window) + { + a_Player->OpenWindow(Window); + } } - a_Value["Slots"] = AllSlots; } -void cEnderChestEntity::SendTo(cClientHandle & a_Client) +void cEnderChestEntity::OpenNewWindow() { - // The chest entity doesn't need anything sent to the client when it's created / gets in the viewdistance - // All the actual handling is in the cWindow UI code that gets called when the chest is rclked - - UNUSED(a_Client); + OpenWindow(new cEnderChestWindow(this)); } -void cEnderChestEntity::UsedBy(cPlayer * a_Player) +void cEnderChestEntity::LoadFromJson(const Json::Value & a_Value, cItemGrid & a_Grid) { - // If the window is not created, open it anew: - cWindow * Window = GetWindow(); - if (Window == NULL) - { - OpenNewWindow(); - Window = GetWindow(); - } - - // Open the window for the player: - if (Window != NULL) + int SlotIdx = 0; + for (Json::Value::iterator itr = a_Value.begin(); itr != a_Value.end(); ++itr) { - if (a_Player->GetWindow() != Window) - { - a_Player->OpenWindow(Window); - } + cItem Item; + Item.FromJson(*itr); + a_Grid.SetSlot(SlotIdx, Item); + SlotIdx++; } - - // This is rather a hack - // Instead of marking the chunk as dirty upon chest contents change, we mark it dirty now - // We cannot properly detect contents change, but such a change doesn't happen without a player opening the chest first. - // The few false positives aren't much to worry about - int ChunkX, ChunkZ; - cChunkDef::BlockToChunk(m_PosX, m_PosZ, ChunkX, ChunkZ); - m_World->MarkChunkDirty(ChunkX, ChunkZ); } -void cEnderChestEntity::OpenNewWindow(void) +void cEnderChestEntity::SaveToJson(Json::Value & a_Value, const cItemGrid & a_Grid) { - OpenWindow(new cEnderChestWindow(this)); + for (int i = 0; i < a_Grid.GetNumSlots(); i++) + { + Json::Value Slot; + a_Grid.GetSlot(i).GetJson(Slot); + a_Value.append(Slot); + } } diff --git a/src/BlockEntities/EnderChestEntity.h b/src/BlockEntities/EnderChestEntity.h index 45beee45f..04af67683 100644 --- a/src/BlockEntities/EnderChestEntity.h +++ b/src/BlockEntities/EnderChestEntity.h @@ -1,20 +1,9 @@ #pragma once -#include "BlockEntityWithItems.h" - - - - - -namespace Json -{ - class Value; -}; - -class cClientHandle; -class cServer; -class cNBTData; +#include "BlockEntity.h" +#include "UI/WindowOwner.h" +#include "json/json.h" @@ -22,33 +11,28 @@ class cNBTData; // tolua_begin class cEnderChestEntity : - public cBlockEntityWithItems + public cBlockEntity, + public cBlockEntityWindowOwner { - typedef cBlockEntityWithItems super; - -public: - enum { - ContentsHeight = 3, - ContentsWidth = 9, - } ; + typedef cBlockEntity super; +public: // tolua_end - /// Constructor used for normal operation - cEnderChestEntity(int a_BlockX, int a_BlockY, int a_BlockZ, cWorld * a_World); - + cEnderChestEntity(int a_BlockX, int a_BlockY, int a_BlockZ, cWorld * a_World); virtual ~cEnderChestEntity(); static const char * GetClassStatic(void) { return "cEnderChestEntity"; } - - bool LoadFromJson(const Json::Value & a_Value); // cBlockEntity overrides: - virtual void SaveToJson(Json::Value & a_Value) override; - virtual void SendTo(cClientHandle & a_Client) override; virtual void UsedBy(cPlayer * a_Player) override; + virtual void SaveToJson(Json::Value & a_Value) override { UNUSED(a_Value); } + virtual void SendTo(cClientHandle & a_Client) override { UNUSED(a_Client); } + + static void LoadFromJson(const Json::Value & a_Value, cItemGrid & a_Grid); + static void SaveToJson(Json::Value & a_Value, const cItemGrid & a_Grid); - /// Opens a new chest window for this chest. Scans for neighbors to open a double chest window, if appropriate. + /** Opens a new enderchest window for this enderchest */ void OpenNewWindow(void); } ; // tolua_export diff --git a/src/Blocks/BlockButton.h b/src/Blocks/BlockButton.h index 7d9af207d..ada7d58f7 100644 --- a/src/Blocks/BlockButton.h +++ b/src/Blocks/BlockButton.h @@ -23,6 +23,7 @@ public: NIBBLETYPE Meta = (a_ChunkInterface.GetBlockMeta(a_BlockX, a_BlockY, a_BlockZ) | 0x08); a_ChunkInterface.SetBlockMeta(a_BlockX, a_BlockY, a_BlockZ, Meta); + a_WorldInterface.WakeUpSimulators(a_BlockX, a_BlockY, a_BlockZ); a_WorldInterface.GetBroadcastManager().BroadcastSoundEffect("random.click", a_BlockX * 8, a_BlockY * 8, a_BlockZ * 8, 0.5f, (Meta & 0x08) ? 0.6f : 0.5f); // Queue a button reset (unpress) diff --git a/src/Blocks/BlockLever.h b/src/Blocks/BlockLever.h index afe43abf8..4e745d413 100644 --- a/src/Blocks/BlockLever.h +++ b/src/Blocks/BlockLever.h @@ -23,6 +23,7 @@ public: NIBBLETYPE Meta = (a_ChunkInterface.GetBlockMeta(a_BlockX, a_BlockY, a_BlockZ) ^ 0x08); a_ChunkInterface.SetBlockMeta(a_BlockX, a_BlockY, a_BlockZ, Meta); + a_WorldInterface.WakeUpSimulators(a_BlockX, a_BlockY, a_BlockZ); a_WorldInterface.GetBroadcastManager().BroadcastSoundEffect("random.click", a_BlockX * 8, a_BlockY * 8, a_BlockZ * 8, 0.5f, (Meta & 0x08) ? 0.6f : 0.5f); } diff --git a/src/Blocks/WorldInterface.h b/src/Blocks/WorldInterface.h index 650a216c0..251b28d03 100644 --- a/src/Blocks/WorldInterface.h +++ b/src/Blocks/WorldInterface.h @@ -46,4 +46,7 @@ public: virtual void SetTimeOfDay(Int64 a_TimeOfDay) = 0; + /** Wakes up the simulators for the specified block */ + virtual void WakeUpSimulators(int a_BlockX, int a_BlockY, int a_BlockZ) = 0; + }; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c40e5edff..5ff1c4e3c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -95,7 +95,7 @@ if (WIN32) OUTPUT ${BINDING_OUTPUTS} # Copy the Lua DLL into the Bindings folder, so that tolua can run from there: - COMMAND copy /y ..\\..\\MCServer\\lua51.dll . + COMMAND ${CMAKE_COMMAND} -E copy_if_different ../../MCServer/lua51.dll ./lua51.dll # Regenerate bindings: COMMAND tolua -L virtual_method_hooks.lua -o Bindings.cpp -H Bindings.h AllToLua.pkg diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index 6b71f0924..efa734b44 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -1223,9 +1223,7 @@ void cClientHandle::HandleRightClick(int a_BlockX, int a_BlockY, int a_BlockZ, e { // A plugin won't let us eat, abort (send the proper packets to the client, too): m_Player->AbortEating(); - return; } - return; } else { diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index daf1ef2cc..dbb8cd26c 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -8,6 +8,7 @@ #include "../World.h" #include "../Bindings/PluginManager.h" #include "../BlockEntities/BlockEntity.h" +#include "../BlockEntities/EnderChestEntity.h" #include "../GroupManager.h" #include "../Group.h" #include "../Root.h" @@ -37,15 +38,16 @@ cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) : super(etPlayer, 0.6, 1.8) , m_bVisible(true) , m_FoodLevel(MAX_FOOD_LEVEL) - , m_FoodSaturationLevel(5) + , m_FoodSaturationLevel(5.0) , m_FoodTickTimer(0) - , m_FoodExhaustionLevel(0) + , m_FoodExhaustionLevel(0.0) , m_FoodPoisonedTicksRemaining(0) , m_LastJumpHeight(0) , m_LastGroundHeight(0) , m_bTouchGround(false) , m_Stance(0.0) , m_Inventory(*this) + , m_EnderChestContents(9, 3) , m_CurrentWindow(NULL) , m_InventoryWindow(NULL) , m_Color('-') @@ -71,6 +73,7 @@ cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) , m_FloaterID(-1) , m_Team(NULL) , m_TicksUntilNextSave(PLAYER_INVENTORY_SAVE_INTERVAL) + , m_bIsTeleporting(false) { LOGD("Created a player object for \"%s\" @ \"%s\" at %p, ID %d", a_PlayerName.c_str(), a_Client->GetIPString().c_str(), @@ -225,7 +228,7 @@ void cPlayer::Tick(float a_Dt, cChunk & a_Chunk) SendExperience(); } - if (GetPosition() != m_LastPos) // Change in position from last tick? + if (!GetPosition().EqualsEps(m_LastPos, 0.01)) // Non negligible change in position from last tick? { // Apply food exhaustion from movement: ApplyFoodExhaustionFromMovement(); @@ -521,7 +524,15 @@ void cPlayer::Heal(int a_Health) void cPlayer::SetFoodLevel(int a_FoodLevel) { - m_FoodLevel = std::max(0, std::min(a_FoodLevel, (int)MAX_FOOD_LEVEL)); + int FoodLevel = std::max(0, std::min(a_FoodLevel, (int)MAX_FOOD_LEVEL)); + + if (cRoot::Get()->GetPluginManager()->CallHookPlayerFoodLevelChange(*this, FoodLevel)) + { + m_FoodSaturationLevel = 5.0; + return; + } + + m_FoodLevel = FoodLevel; SendHealth(); } @@ -571,11 +582,9 @@ bool cPlayer::Feed(int a_Food, double a_Saturation) { return false; } - - m_FoodLevel = std::min(a_Food + m_FoodLevel, (int)MAX_FOOD_LEVEL); - m_FoodSaturationLevel = std::min(m_FoodSaturationLevel + a_Saturation, (double)m_FoodLevel); - - SendHealth(); + + SetFoodSaturationLevel(m_FoodSaturationLevel + a_Saturation); + SetFoodLevel(m_FoodLevel + a_Food); return true; } @@ -969,7 +978,8 @@ void cPlayer::Respawn(void) // Reset food level: m_FoodLevel = MAX_FOOD_LEVEL; - m_FoodSaturationLevel = 5; + m_FoodSaturationLevel = 5.0; + m_FoodExhaustionLevel = 0.0; // Reset Experience m_CurrentXp = 0; @@ -1226,6 +1236,7 @@ void cPlayer::TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ) SetPosition(a_PosX, a_PosY, a_PosZ); m_LastGroundHeight = (float)a_PosY; m_LastJumpHeight = (float)a_PosY; + m_bIsTeleporting = true; m_World->BroadcastTeleportEntity(*this, GetClientHandle()); m_ClientHandle->SendPlayerMoveLook(); @@ -1743,6 +1754,7 @@ bool cPlayer::LoadFromDisk() } m_Inventory.LoadFromJson(root["inventory"]); + cEnderChestEntity::LoadFromJson(root["enderchestinventory"], m_EnderChestContents); m_LoadedWorldName = root.get("world", "world").asString(); @@ -1780,20 +1792,24 @@ bool cPlayer::SaveToDisk() Json::Value JSON_Inventory; m_Inventory.SaveToJson(JSON_Inventory); + Json::Value JSON_EnderChestInventory; + cEnderChestEntity::SaveToJson(JSON_EnderChestInventory, m_EnderChestContents); + Json::Value root; - root["position"] = JSON_PlayerPosition; - root["rotation"] = JSON_PlayerRotation; - root["inventory"] = JSON_Inventory; - root["health"] = m_Health; - root["xpTotal"] = m_LifetimeTotalXp; - root["xpCurrent"] = m_CurrentXp; - root["air"] = m_AirLevel; - root["food"] = m_FoodLevel; - root["foodSaturation"] = m_FoodSaturationLevel; - root["foodTickTimer"] = m_FoodTickTimer; - root["foodExhaustion"] = m_FoodExhaustionLevel; - root["world"] = GetWorld()->GetName(); - root["isflying"] = IsFlying(); + root["position"] = JSON_PlayerPosition; + root["rotation"] = JSON_PlayerRotation; + root["inventory"] = JSON_Inventory; + root["enderchestinventory"] = JSON_EnderChestInventory; + root["health"] = m_Health; + root["xpTotal"] = m_LifetimeTotalXp; + root["xpCurrent"] = m_CurrentXp; + root["air"] = m_AirLevel; + root["food"] = m_FoodLevel; + root["foodSaturation"] = m_FoodSaturationLevel; + root["foodTickTimer"] = m_FoodTickTimer; + root["foodExhaustion"] = m_FoodExhaustionLevel; + root["world"] = GetWorld()->GetName(); + root["isflying"] = IsFlying(); if (m_GameMode == GetWorld()->GetGameMode()) { @@ -1895,16 +1911,13 @@ void cPlayer::TickBurning(cChunk & a_Chunk) void cPlayer::HandleFood(void) { // Ref.: http://www.minecraftwiki.net/wiki/Hunger - + if (IsGameModeCreative()) { // Hunger is disabled for Creative return; } - - // Remember the food level before processing, for later comparison - int LastFoodLevel = m_FoodLevel; - + // Heal or damage, based on the food level, using the m_FoodTickTimer: if ((m_FoodLevel > 17) || (m_FoodLevel <= 0)) { @@ -1917,7 +1930,7 @@ void cPlayer::HandleFood(void) { // Regenerate health from food, incur 3 pts of food exhaustion: Heal(1); - m_FoodExhaustionLevel += 3; + m_FoodExhaustionLevel += 3.0; } else if ((m_FoodLevel <= 0) && (m_Health > 1)) { @@ -1926,7 +1939,7 @@ void cPlayer::HandleFood(void) } } } - + // Apply food poisoning food exhaustion: if (m_FoodPoisonedTicksRemaining > 0) { @@ -1939,24 +1952,19 @@ void cPlayer::HandleFood(void) } // Apply food exhaustion that has accumulated: - if (m_FoodExhaustionLevel >= 4) + if (m_FoodExhaustionLevel >= 4.0) { - m_FoodExhaustionLevel -= 4; + m_FoodExhaustionLevel -= 4.0; - if (m_FoodSaturationLevel >= 1) + if (m_FoodSaturationLevel >= 1.0) { - m_FoodSaturationLevel -= 1; + m_FoodSaturationLevel -= 1.0; } else { - m_FoodLevel = std::max(m_FoodLevel - 1, 0); + SetFoodLevel(m_FoodLevel - 1); } } - - if (m_FoodLevel != LastFoodLevel) - { - SendHealth(); - } } @@ -2079,6 +2087,11 @@ void cPlayer::ApplyFoodExhaustionFromMovement() { return; } + if (m_bIsTeleporting) + { + m_bIsTeleporting = false; + return; + } // If riding anything, apply no food exhaustion if (m_AttachedTo != NULL) diff --git a/src/Entities/Player.h b/src/Entities/Player.h index 2f7957f16..f247ac2f9 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -124,6 +124,9 @@ public: inline double GetStance(void) const { return GetPosY() + 1.62; } // tolua_export // TODO: Proper stance when crouching etc. inline cInventory & GetInventory(void) { return m_Inventory; } // tolua_export inline const cInventory & GetInventory(void) const { return m_Inventory; } + + /** Gets the contents of the player's associated enderchest */ + cItemGrid & GetEnderChestContents(void) { return m_EnderChestContents; } inline const cItem & GetEquippedItem(void) const { return GetInventory().GetEquippedItem(); } // tolua_export @@ -449,7 +452,13 @@ protected: float m_LastGroundHeight; bool m_bTouchGround; double m_Stance; + + /** Stores the player's inventory, consisting of crafting grid, hotbar, and main slots */ cInventory m_Inventory; + + /** An item grid that stores the player specific enderchest contents */ + cItemGrid m_EnderChestContents; + cWindow * m_CurrentWindow; cWindow * m_InventoryWindow; @@ -510,8 +519,6 @@ protected: cStatManager m_Stats; - - /** Sets the speed and sends it to the client, so that they are forced to move so. */ virtual void DoSetSpeed(double a_SpeedX, double a_SpeedY, double a_SpeedZ) override; @@ -546,6 +553,11 @@ protected: Default save interval is #defined in PLAYER_INVENTORY_SAVE_INTERVAL */ unsigned int m_TicksUntilNextSave; + /** Flag used by food handling system to determine whether a teleport has just happened + Will not apply food penalties if found to be true; will set to false after processing + */ + bool m_bIsTeleporting; + } ; // tolua_export diff --git a/src/HTTPServer/HTTPServer.cpp b/src/HTTPServer/HTTPServer.cpp index d288c83c9..036b2e042 100644 --- a/src/HTTPServer/HTTPServer.cpp +++ b/src/HTTPServer/HTTPServer.cpp @@ -179,6 +179,8 @@ bool cHTTPServer::Initialize(const AString & a_PortsIPv4, const AString & a_Port // Open up requested ports: bool HasAnyPort; + m_ListenThreadIPv4.SetReuseAddr(true); + m_ListenThreadIPv6.SetReuseAddr(true); HasAnyPort = m_ListenThreadIPv4.Initialize(a_PortsIPv4); HasAnyPort = m_ListenThreadIPv6.Initialize(a_PortsIPv6) || HasAnyPort; if (!HasAnyPort) diff --git a/src/Mobs/Monster.cpp b/src/Mobs/Monster.cpp index 5843ca5a6..aaef7580d 100644 --- a/src/Mobs/Monster.cpp +++ b/src/Mobs/Monster.cpp @@ -46,8 +46,8 @@ static const struct {cMonster::mtSheep, "sheep"}, {cMonster::mtSilverfish, "silverfish"}, {cMonster::mtSkeleton, "skeleton"}, - {cMonster::mtSnowGolem, "snowgolem"}, {cMonster::mtSlime, "slime"}, + {cMonster::mtSnowGolem, "snowgolem"}, {cMonster::mtSpider, "spider"}, {cMonster::mtSquid, "squid"}, {cMonster::mtVillager, "villager"}, diff --git a/src/OSSupport/Event.cpp b/src/OSSupport/Event.cpp index 6b8fccde2..72bce4c3c 100644 --- a/src/OSSupport/Event.cpp +++ b/src/OSSupport/Event.cpp @@ -18,7 +18,7 @@ cEvent::cEvent(void) m_Event = CreateEvent(NULL, FALSE, FALSE, NULL); if (m_Event == NULL) { - LOGERROR("cEvent: cannot create event, GLE = %d. Aborting server.", GetLastError()); + LOGERROR("cEvent: cannot create event, GLE = %u. Aborting server.", (unsigned)GetLastError()); abort(); } #else // *nix @@ -86,7 +86,7 @@ void cEvent::Wait(void) DWORD res = WaitForSingleObject(m_Event, INFINITE); if (res != WAIT_OBJECT_0) { - LOGWARN("cEvent: waiting for the event failed: %d, GLE = %d. Continuing, but server may be unstable.", res, GetLastError()); + LOGWARN("cEvent: waiting for the event failed: %u, GLE = %u. Continuing, but server may be unstable.", (unsigned)res, (unsigned)GetLastError()); } #else int res = sem_wait(m_Event); @@ -107,7 +107,7 @@ void cEvent::Set(void) #ifdef _WIN32 if (!SetEvent(m_Event)) { - LOGWARN("cEvent: Could not set cEvent: GLE = %d", GetLastError()); + LOGWARN("cEvent: Could not set cEvent: GLE = %u", (unsigned)GetLastError()); } #else int res = sem_post(m_Event); diff --git a/src/OSSupport/IsThread.cpp b/src/OSSupport/IsThread.cpp index 67f336c97..1a436623a 100644 --- a/src/OSSupport/IsThread.cpp +++ b/src/OSSupport/IsThread.cpp @@ -90,7 +90,7 @@ bool cIsThread::Start(void) m_Handle = CreateThread(NULL, 0, thrExecute, this, CREATE_SUSPENDED, &m_ThreadID); if (m_Handle == NULL) { - LOGERROR("ERROR: Could not create thread \"%s\", GLE = %d!", m_ThreadName.c_str(), GetLastError()); + LOGERROR("ERROR: Could not create thread \"%s\", GLE = %u!", m_ThreadName.c_str(), (unsigned)GetLastError()); return false; } ResumeThread(m_Handle); diff --git a/src/Simulator/IncrementalRedstoneSimulator.cpp b/src/Simulator/IncrementalRedstoneSimulator.cpp index 866a5b65a..5af9a295d 100644 --- a/src/Simulator/IncrementalRedstoneSimulator.cpp +++ b/src/Simulator/IncrementalRedstoneSimulator.cpp @@ -1700,7 +1700,7 @@ bool cIncrementalRedstoneSimulator::IsWirePowered(int a_RelBlockX, int a_RelBloc { continue; } - a_PowerLevel = itr->a_PowerLevel; + a_PowerLevel = std::max(itr->a_PowerLevel , a_PowerLevel); // Get the highest power level (a_PowerLevel is initialised already and there CAN be multiple levels for one block) } for (LinkedBlocksList::const_iterator itr = m_LinkedPoweredBlocks->begin(); itr != m_LinkedPoweredBlocks->end(); ++itr) // Check linked powered list @@ -1709,7 +1709,7 @@ bool cIncrementalRedstoneSimulator::IsWirePowered(int a_RelBlockX, int a_RelBloc { continue; } - a_PowerLevel = itr->a_PowerLevel; + a_PowerLevel = std::max(itr->a_PowerLevel, a_PowerLevel); } return (a_PowerLevel != 0); // Answer the inital question: is the wire powered? diff --git a/src/UI/SlotArea.cpp b/src/UI/SlotArea.cpp index 728692f2a..4df04c9e2 100644 --- a/src/UI/SlotArea.cpp +++ b/src/UI/SlotArea.cpp @@ -468,6 +468,20 @@ void cSlotAreaCrafting::OnPlayerRemoved(cPlayer & a_Player) +void cSlotAreaCrafting::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) +{ + // Update the recipe after setting the slot, if the slot is not the result slot: + super::SetSlot(a_SlotNum, a_Player, a_Item); + if (a_SlotNum != 0) + { + UpdateRecipe(a_Player); + } +} + + + + + void cSlotAreaCrafting::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots) { UNUSED(a_ItemStack); @@ -545,16 +559,20 @@ void cSlotAreaCrafting::ShiftClickedResult(cPlayer & a_Player) // Distribute the result, this time for real: ResultCopy = Result; m_ParentWindow.DistributeStack(ResultCopy, a_Player, this, true); - + // Remove the ingredients from the crafting grid and update the recipe: cCraftingRecipe & Recipe = GetRecipeForPlayer(a_Player); cCraftingGrid Grid(PlayerSlots, m_GridSize, m_GridSize); Recipe.ConsumeIngredients(Grid); Grid.CopyToItems(PlayerSlots); UpdateRecipe(a_Player); + + // Broadcast the window, we sometimes move items to different locations than Vanilla, causing needless desyncs: + m_ParentWindow.BroadcastWholeWindow(); + + // If the recipe has changed, bail out: if (!Recipe.GetResult().IsEqual(Result)) { - // The recipe has changed, bail out return; } } @@ -1341,8 +1359,7 @@ cSlotAreaEnderChest::cSlotAreaEnderChest(cEnderChestEntity * a_EnderChest, cWind const cItem * cSlotAreaEnderChest::GetSlot(int a_SlotNum, cPlayer & a_Player) const { - // a_SlotNum ranges from 0 to 26, use that to index the chest entity's inventory directly: - return &(m_EnderChest->GetSlot(a_SlotNum)); + return &(a_Player.GetEnderChestContents().GetSlot(a_SlotNum)); } @@ -1351,7 +1368,7 @@ const cItem * cSlotAreaEnderChest::GetSlot(int a_SlotNum, cPlayer & a_Player) co void cSlotAreaEnderChest::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) { - m_EnderChest->SetSlot(a_SlotNum, a_Item); + a_Player.GetEnderChestContents().SetSlot(a_SlotNum, a_Item); } diff --git a/src/UI/SlotArea.h b/src/UI/SlotArea.h index b4b693cf6..e271ea454 100644 --- a/src/UI/SlotArea.h +++ b/src/UI/SlotArea.h @@ -232,6 +232,7 @@ public: virtual void Clicked (cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem) override; virtual void DblClicked (cPlayer & a_Player, int a_SlotNum); virtual void OnPlayerRemoved(cPlayer & a_Player) override; + virtual void SetSlot (int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) override; // Distributing items into this area is completely disabled virtual void DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots) override; diff --git a/src/UI/Window.cpp b/src/UI/Window.cpp index e465b701a..381c6e121 100644 --- a/src/UI/Window.cpp +++ b/src/UI/Window.cpp @@ -145,8 +145,7 @@ void cWindow::GetSlots(cPlayer & a_Player, cItems & a_Slots) const { int NumSlots = (*itr)->GetNumSlots(); for (int i = 0; i < NumSlots; i++) - { - + { const cItem * Item = (*itr)->GetSlot(i, a_Player); if (Item == NULL) { @@ -1017,6 +1016,7 @@ cEnderChestWindow::~cEnderChestWindow() // Send out the chest-close packet: m_World->BroadcastBlockAction(m_BlockX, m_BlockY, m_BlockZ, 1, 0, E_BLOCK_ENDER_CHEST); + // Play the closing sound m_World->BroadcastSoundEffect("random.chestclosed", m_BlockX * 8, m_BlockY * 8, m_BlockZ * 8, 1, 1); } diff --git a/src/World.cpp b/src/World.cpp index bd7694e96..48c3448a3 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -2776,10 +2776,8 @@ bool cWorld::ForEachChunkInRect(int a_MinChunkX, int a_MaxChunkX, int a_MinChunk void cWorld::SaveAllChunks(void) { - LOGINFO("Saving all chunks..."); m_LastSave = m_WorldAge; m_ChunkMap->SaveAllChunks(); - m_Storage.QueueSavedMessage(); } diff --git a/src/World.h b/src/World.h index 692d5a497..32e64c8e0 100644 --- a/src/World.h +++ b/src/World.h @@ -486,7 +486,7 @@ public: double GetSpawnZ(void) const { return m_SpawnZ; } /** Wakes up the simulators for the specified block */ - void WakeUpSimulators(int a_BlockX, int a_BlockY, int a_BlockZ); + virtual void WakeUpSimulators(int a_BlockX, int a_BlockY, int a_BlockZ) override; /** 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); diff --git a/src/WorldStorage/NBTChunkSerializer.cpp b/src/WorldStorage/NBTChunkSerializer.cpp index f35b38859..bff515386 100644 --- a/src/WorldStorage/NBTChunkSerializer.cpp +++ b/src/WorldStorage/NBTChunkSerializer.cpp @@ -820,6 +820,7 @@ void cNBTChunkSerializer::BlockEntity(cBlockEntity * a_Entity) case E_BLOCK_CHEST: AddChestEntity ((cChestEntity *) a_Entity); break; case E_BLOCK_DISPENSER: AddDispenserEntity ((cDispenserEntity *) a_Entity); break; case E_BLOCK_DROPPER: AddDropperEntity ((cDropperEntity *) a_Entity); break; + case E_BLOCK_ENDER_CHEST: /* No need to be saved */ break; case E_BLOCK_FLOWER_POT: AddFlowerPotEntity ((cFlowerPotEntity *) a_Entity); break; case E_BLOCK_FURNACE: AddFurnaceEntity ((cFurnaceEntity *) a_Entity); break; case E_BLOCK_HOPPER: AddHopperEntity ((cHopperEntity *) a_Entity); break; diff --git a/src/WorldStorage/WorldStorage.cpp b/src/WorldStorage/WorldStorage.cpp index 6867ad5bc..d3f35384b 100644 --- a/src/WorldStorage/WorldStorage.cpp +++ b/src/WorldStorage/WorldStorage.cpp @@ -17,13 +17,6 @@ -/// If a chunk with this Y coord is de-queued, it is a signal to emit the saved-all message (cWorldStorage::QueueSavedMessage()) -#define CHUNK_Y_MESSAGE 2 - - - - - /// Example storage schema - forgets all chunks ;) class cWSSForgetful : public cWSSchema @@ -168,17 +161,6 @@ void cWorldStorage::QueueSaveChunk(int a_ChunkX, int a_ChunkY, int a_ChunkZ) -void cWorldStorage::QueueSavedMessage(void) -{ - // Pushes a special coord pair into the queue, signalizing a message instead - m_SaveQueue.EnqueueItem(cChunkCoords(0, CHUNK_Y_MESSAGE, 0)); - m_Event.Set(); -} - - - - - void cWorldStorage::UnqueueLoad(int a_ChunkX, int a_ChunkY, int a_ChunkZ) { m_LoadQueue.Remove(sChunkLoad(a_ChunkX, a_ChunkY, a_ChunkZ,true)); @@ -286,19 +268,12 @@ bool cWorldStorage::SaveOneChunk(void) { cChunkCoords ToSave(0, 0, 0); bool ShouldSave = m_SaveQueue.TryDequeueItem(ToSave); - if(ShouldSave) { - if (ToSave.m_ChunkY == CHUNK_Y_MESSAGE) - { - LOGINFO("Saved all chunks in world %s", m_World->GetName().c_str()); - return ShouldSave; - } - if (ShouldSave && m_World->IsChunkValid(ToSave.m_ChunkX, ToSave.m_ChunkZ)) + if (ShouldSave && m_World->IsChunkValid(ToSave.m_ChunkX, ToSave.m_ChunkZ)) + { + m_World->MarkChunkSaving(ToSave.m_ChunkX, ToSave.m_ChunkZ); + if (m_SaveSchema->SaveChunk(ToSave)) { - m_World->MarkChunkSaving(ToSave.m_ChunkX, ToSave.m_ChunkZ); - if (m_SaveSchema->SaveChunk(ToSave)) - { - m_World->MarkChunkSaved(ToSave.m_ChunkX, ToSave.m_ChunkZ); - } + m_World->MarkChunkSaved(ToSave.m_ChunkX, ToSave.m_ChunkZ); } } return ShouldSave; diff --git a/src/WorldStorage/WorldStorage.h b/src/WorldStorage/WorldStorage.h index bb189b6c9..1204b4310 100644 --- a/src/WorldStorage/WorldStorage.h +++ b/src/WorldStorage/WorldStorage.h @@ -67,9 +67,6 @@ public: void QueueLoadChunk(int a_ChunkX, int a_ChunkY, int a_ChunkZ, bool a_Generate); // Queues the chunk for loading; if not loaded, the chunk will be generated if a_Generate is true void QueueSaveChunk(int a_ChunkX, int a_ChunkY, int a_ChunkZ); - /// Signals that a message should be output to the console when all the chunks have been saved - void QueueSavedMessage(void); - /// Loads the chunk specified; returns true on success, false on failure bool LoadChunk(int a_ChunkX, int a_ChunkY, int a_ChunkZ); |