diff options
Diffstat (limited to 'source')
34 files changed, 2530 insertions, 128 deletions
diff --git a/source/Bindings.cpp b/source/Bindings.cpp index 8259eda81..a052d1062 100644 --- a/source/Bindings.cpp +++ b/source/Bindings.cpp @@ -1,6 +1,6 @@ /* ** Lua binding: AllToLua -** Generated automatically by tolua++-1.0.92 on 10/28/13 13:11:03. +** Generated automatically by tolua++-1.0.92 on 11/02/13 17:43:37. */ #ifndef __cplusplus @@ -5220,6 +5220,38 @@ static int tolua_AllToLua_cEntity_GetRotation00(lua_State* tolua_S) } #endif //#ifndef TOLUA_DISABLE +/* method: GetYaw of class cEntity */ +#ifndef TOLUA_DISABLE_tolua_AllToLua_cEntity_GetYaw00 +static int tolua_AllToLua_cEntity_GetYaw00(lua_State* tolua_S) +{ +#ifndef TOLUA_RELEASE + tolua_Error tolua_err; + if ( + !tolua_isusertype(tolua_S,1,"const cEntity",0,&tolua_err) || + !tolua_isnoobj(tolua_S,2,&tolua_err) + ) + goto tolua_lerror; + else +#endif + { + const cEntity* self = (const cEntity*) tolua_tousertype(tolua_S,1,0); +#ifndef TOLUA_RELEASE + if (!self) tolua_error(tolua_S,"invalid 'self' in function 'GetYaw'", NULL); +#endif + { + double tolua_ret = (double) self->GetYaw(); + tolua_pushnumber(tolua_S,(lua_Number)tolua_ret); + } + } + return 1; +#ifndef TOLUA_RELEASE + tolua_lerror: + tolua_error(tolua_S,"#ferror in function 'GetYaw'.",&tolua_err); + return 0; +#endif +} +#endif //#ifndef TOLUA_DISABLE + /* method: GetPitch of class cEntity */ #ifndef TOLUA_DISABLE_tolua_AllToLua_cEntity_GetPitch00 static int tolua_AllToLua_cEntity_GetPitch00(lua_State* tolua_S) @@ -5879,6 +5911,39 @@ static int tolua_AllToLua_cEntity_SetRotation00(lua_State* tolua_S) } #endif //#ifndef TOLUA_DISABLE +/* method: SetYaw of class cEntity */ +#ifndef TOLUA_DISABLE_tolua_AllToLua_cEntity_SetYaw00 +static int tolua_AllToLua_cEntity_SetYaw00(lua_State* tolua_S) +{ +#ifndef TOLUA_RELEASE + tolua_Error tolua_err; + if ( + !tolua_isusertype(tolua_S,1,"cEntity",0,&tolua_err) || + !tolua_isnumber(tolua_S,2,0,&tolua_err) || + !tolua_isnoobj(tolua_S,3,&tolua_err) + ) + goto tolua_lerror; + else +#endif + { + cEntity* self = (cEntity*) tolua_tousertype(tolua_S,1,0); + double a_Yaw = ((double) tolua_tonumber(tolua_S,2,0)); +#ifndef TOLUA_RELEASE + if (!self) tolua_error(tolua_S,"invalid 'self' in function 'SetYaw'", NULL); +#endif + { + self->SetYaw(a_Yaw); + } + } + return 0; +#ifndef TOLUA_RELEASE + tolua_lerror: + tolua_error(tolua_S,"#ferror in function 'SetYaw'.",&tolua_err); + return 0; +#endif +} +#endif //#ifndef TOLUA_DISABLE + /* method: SetPitch of class cEntity */ #ifndef TOLUA_DISABLE_tolua_AllToLua_cEntity_SetPitch00 static int tolua_AllToLua_cEntity_SetPitch00(lua_State* tolua_S) @@ -7990,6 +8055,38 @@ static int tolua_AllToLua_cPlayer_GetGameMode00(lua_State* tolua_S) } #endif //#ifndef TOLUA_DISABLE +/* method: GetEffectiveGameMode of class cPlayer */ +#ifndef TOLUA_DISABLE_tolua_AllToLua_cPlayer_GetEffectiveGameMode00 +static int tolua_AllToLua_cPlayer_GetEffectiveGameMode00(lua_State* tolua_S) +{ +#ifndef TOLUA_RELEASE + tolua_Error tolua_err; + if ( + !tolua_isusertype(tolua_S,1,"const cPlayer",0,&tolua_err) || + !tolua_isnoobj(tolua_S,2,&tolua_err) + ) + goto tolua_lerror; + else +#endif + { + const cPlayer* self = (const cPlayer*) tolua_tousertype(tolua_S,1,0); +#ifndef TOLUA_RELEASE + if (!self) tolua_error(tolua_S,"invalid 'self' in function 'GetEffectiveGameMode'", NULL); +#endif + { + eGameMode tolua_ret = (eGameMode) self->GetEffectiveGameMode(); + tolua_pushnumber(tolua_S,(lua_Number)tolua_ret); + } + } + return 1; +#ifndef TOLUA_RELEASE + tolua_lerror: + tolua_error(tolua_S,"#ferror in function 'GetEffectiveGameMode'.",&tolua_err); + return 0; +#endif +} +#endif //#ifndef TOLUA_DISABLE + /* method: SetGameMode of class cPlayer */ #ifndef TOLUA_DISABLE_tolua_AllToLua_cPlayer_SetGameMode00 static int tolua_AllToLua_cPlayer_SetGameMode00(lua_State* tolua_S) @@ -29965,6 +30062,7 @@ TOLUA_API int tolua_AllToLua_open (lua_State* tolua_S) tolua_function(tolua_S,"GetPosZ",tolua_AllToLua_cEntity_GetPosZ00); tolua_function(tolua_S,"GetRot",tolua_AllToLua_cEntity_GetRot00); tolua_function(tolua_S,"GetRotation",tolua_AllToLua_cEntity_GetRotation00); + tolua_function(tolua_S,"GetYaw",tolua_AllToLua_cEntity_GetYaw00); tolua_function(tolua_S,"GetPitch",tolua_AllToLua_cEntity_GetPitch00); tolua_function(tolua_S,"GetRoll",tolua_AllToLua_cEntity_GetRoll00); tolua_function(tolua_S,"GetLookVector",tolua_AllToLua_cEntity_GetLookVector00); @@ -29985,6 +30083,7 @@ TOLUA_API int tolua_AllToLua_open (lua_State* tolua_S) tolua_function(tolua_S,"SetPosition",tolua_AllToLua_cEntity_SetPosition01); tolua_function(tolua_S,"SetRot",tolua_AllToLua_cEntity_SetRot00); tolua_function(tolua_S,"SetRotation",tolua_AllToLua_cEntity_SetRotation00); + tolua_function(tolua_S,"SetYaw",tolua_AllToLua_cEntity_SetYaw00); tolua_function(tolua_S,"SetPitch",tolua_AllToLua_cEntity_SetPitch00); tolua_function(tolua_S,"SetRoll",tolua_AllToLua_cEntity_SetRoll00); tolua_function(tolua_S,"SetSpeed",tolua_AllToLua_cEntity_SetSpeed00); @@ -30058,6 +30157,7 @@ TOLUA_API int tolua_AllToLua_open (lua_State* tolua_S) tolua_function(tolua_S,"GetThrowStartPos",tolua_AllToLua_cPlayer_GetThrowStartPos00); tolua_function(tolua_S,"GetThrowSpeed",tolua_AllToLua_cPlayer_GetThrowSpeed00); tolua_function(tolua_S,"GetGameMode",tolua_AllToLua_cPlayer_GetGameMode00); + tolua_function(tolua_S,"GetEffectiveGameMode",tolua_AllToLua_cPlayer_GetEffectiveGameMode00); tolua_function(tolua_S,"SetGameMode",tolua_AllToLua_cPlayer_SetGameMode00); tolua_function(tolua_S,"IsGameModeCreative",tolua_AllToLua_cPlayer_IsGameModeCreative00); tolua_function(tolua_S,"IsGameModeSurvival",tolua_AllToLua_cPlayer_IsGameModeSurvival00); diff --git a/source/Bindings.h b/source/Bindings.h index 411e608d9..455307a89 100644 --- a/source/Bindings.h +++ b/source/Bindings.h @@ -1,6 +1,6 @@ /*
** Lua binding: AllToLua
-** Generated automatically by tolua++-1.0.92 on 10/28/13 13:11:04.
+** Generated automatically by tolua++-1.0.92 on 11/02/13 17:43:38.
*/
/* Exported function */
diff --git a/source/BlockEntities/DropSpenserEntity.cpp b/source/BlockEntities/DropSpenserEntity.cpp index a9fcdab17..25def9999 100644 --- a/source/BlockEntities/DropSpenserEntity.cpp +++ b/source/BlockEntities/DropSpenserEntity.cpp @@ -89,6 +89,8 @@ void cDropSpenserEntity::DropSpense(cChunk & a_Chunk) int SmokeDir = 0; switch (Meta) { + case E_META_DROPSPENSER_FACING_YP: SmokeDir = 4; break; // YP & YM don't have associated smoke dirs, just do 4 (centre of block) + case E_META_DROPSPENSER_FACING_YM: SmokeDir = 4; break; case E_META_DROPSPENSER_FACING_XM: SmokeDir = 3; break; case E_META_DROPSPENSER_FACING_XP: SmokeDir = 5; break; case E_META_DROPSPENSER_FACING_ZM: SmokeDir = 1; break; @@ -237,7 +239,26 @@ void cDropSpenserEntity::DropFromSlot(cChunk & a_Chunk, int a_SlotNum) cItems Pickups; Pickups.push_back(m_Contents.RemoveOneItem(a_SlotNum)); - m_World->SpawnItemPickups(Pickups, DispX, DispY, DispZ); + + const int PickupSpeed = m_World->GetTickRandomNumber(4) + 2; // At least 2, at most 6 + int PickupSpeedX = 0, PickupSpeedY = 0, PickupSpeedZ = 0; + switch (Meta) + { + case E_META_DROPSPENSER_FACING_YP: PickupSpeedY = PickupSpeed; break; + case E_META_DROPSPENSER_FACING_YM: PickupSpeedY = -PickupSpeed; break; + case E_META_DROPSPENSER_FACING_XM: PickupSpeedX = -PickupSpeed; break; + case E_META_DROPSPENSER_FACING_XP: PickupSpeedX = PickupSpeed; break; + case E_META_DROPSPENSER_FACING_ZM: PickupSpeedZ = -PickupSpeed; break; + case E_META_DROPSPENSER_FACING_ZP: PickupSpeedZ = PickupSpeed; break; + } + + double MicroX, MicroY, MicroZ; + MicroX = DispX + 0.5; + MicroY = DispY + 0.4; // Slightly less than half, to accomodate actual texture hole on DropSpenser + MicroZ = DispZ + 0.5; + + + m_World->SpawnItemPickups(Pickups, MicroX, MicroY, MicroZ, PickupSpeedX, PickupSpeedY, PickupSpeedZ); } diff --git a/source/Blocks/BlockDirt.h b/source/Blocks/BlockDirt.h index b2bc4756c..c694d79f6 100644 --- a/source/Blocks/BlockDirt.h +++ b/source/Blocks/BlockDirt.h @@ -37,7 +37,7 @@ public: if (a_BlockY < cChunkDef::Height - 1) { BLOCKTYPE Above = a_World->GetBlock(a_BlockX, a_BlockY + 1, a_BlockZ); - if (!g_BlockTransparent[Above] && !g_BlockOneHitDig[Above]) + if ((!g_BlockTransparent[Above] && !g_BlockOneHitDig[Above]) || IsBlockWater(Above)) { a_World->FastSetBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_DIRT, 0); return; @@ -69,7 +69,7 @@ public: NIBBLETYPE AboveMeta; IsValid = a_World->GetBlockTypeMeta(a_BlockX + OfsX, a_BlockY + OfsY + 1, a_BlockZ + OfsZ, AboveDest, AboveMeta); ASSERT(IsValid); // WTF - how did we get the DestBlock if AboveBlock is not valid? - if (g_BlockOneHitDig[AboveDest] || g_BlockTransparent[AboveDest]) + if ((g_BlockOneHitDig[AboveDest] || g_BlockTransparent[AboveDest]) && !IsBlockWater(AboveDest)) { a_World->FastSetBlock(a_BlockX + OfsX, a_BlockY + OfsY, a_BlockZ + OfsZ, E_BLOCK_GRASS, 0); } diff --git a/source/Blocks/BlockFire.h b/source/Blocks/BlockFire.h index d3ba499b1..46b56d7e0 100644 --- a/source/Blocks/BlockFire.h +++ b/source/Blocks/BlockFire.h @@ -16,6 +16,28 @@ public: { } + /// Portal boundary and direction variables + int XZP, XZM, Dir; // For wont of a better name... + + virtual void OnPlaced(cWorld * a_World, int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) override + { + /* + PORTAL FINDING ALGORITH + ======================= + -Get clicked base block + -Trace upwards to find first obsidian block; aborts if anything other than obsidian or air is encountered. + Uses this value as a reference (the 'ceiling') + -For both directions (if one fails, try the other), BASE (clicked) block: + -Go in one direction, only stop if a non obsidian block is encountered (abort) OR a portal border is encountered (FindObsidianCeiling returns -1) + -If a border was encountered, go the other direction and repeat above + -Write borders to XZP and XZM, write direction portal faces to Dir + -Loop through boundary variables, and fill with portal blocks based on Dir with meta from Dir + */ + + a_BlockY--; // Because we want the block below the fire + FindAndSetPortalFrame(a_BlockX, a_BlockY, a_BlockZ, a_World); // Brought to you by Aperture Science + } + virtual void OnDigging(cWorld * a_World, cPlayer * a_Player, int a_BlockX, int a_BlockY, int a_BlockZ) override { a_World->DigBlock(a_BlockX, a_BlockY, a_BlockZ); @@ -30,12 +52,176 @@ public: { return true; } - + virtual const char * GetStepSound(void) override { return "step.wood"; } -} ; + + /// Traces along YP until it finds an obsidian block, returns Y difference or 0 if no portal, and -1 for border + /// Takes the X, Y, and Z of the base block; with an optional MaxY for portal border finding + int FindObsidianCeiling(int X, int Y, int Z, cWorld * a_World, int MaxY = 0) + { + if (a_World->GetBlock(X, Y, Z) != E_BLOCK_OBSIDIAN) + { + return 0; + } + + int newY = Y + 1; + + for (newY; newY < cChunkDef::Height; newY++) + { + BLOCKTYPE Block = a_World->GetBlock(X, newY, Z); + if ((Block == E_BLOCK_AIR) || (Block == E_BLOCK_FIRE)) + { + continue; + } + else if (Block == E_BLOCK_OBSIDIAN) + { + // We found an obsidian ceiling + // Make sure MaxY has a value and newY ('ceiling' location) is at one above the base block + // This is because the frame is a solid obsidian pillar + if ((MaxY != 0) && (newY == Y + 1)) + { + return EvaluatePortalBorder(X, newY, Z, MaxY, a_World); + } + else + { + // Return ceiling Y, whoever called this function will decide if it's part of a portal or not + return newY; + } + } + else { return 0; } + } + + return 0; + } + + /// Evaluates if coords have a valid border on top, based on MaxY + int EvaluatePortalBorder(int X, int FoundObsidianY, int Z, int MaxY, cWorld * a_World) + { + for (int checkBorder = FoundObsidianY + 1; checkBorder <= MaxY - 1; checkBorder++) // FoundObsidianY + 1: FoundObsidianY has already been checked in FindObsidianCeiling; MaxY - 1: portal doesn't need corners + { + if (a_World->GetBlock(X, checkBorder, Z) != E_BLOCK_OBSIDIAN) + { + // Base obsidian, base + 1 obsidian, base + x NOT obsidian -> not complete portal + return 0; + } + } + // Everything was obsidian, found a border! + return -1; // Return -1 for a frame border + } + + /// Finds entire frame in any direction with the coordinates of a base block and fills hole with nether portal (START HERE) + void FindAndSetPortalFrame(int X, int Y, int Z, cWorld * a_World) + { + int MaxY = FindObsidianCeiling(X, Y, Z, a_World); // Get topmost obsidian block as reference for all other checks + int X1 = X + 1, Z1 = Z + 1, X2 = X - 1, Z2 = Z - 1; // Duplicate XZ values, add/subtract one as we've checked the original already the line above + + if (MaxY == 0) // Oh noes! Not a portal coordinate :( + { + return; + } + + if (!FindPortalSliceX(X1, X2, Y, Z, MaxY, a_World)) + { + if (!FindPortalSliceZ(X, Y, Z1, Z2, MaxY, a_World)) + { + return; // No eligible portal construct, abort abort abort!! + } + } + + for (int Height = Y + 1; Height <= MaxY - 1; Height++) // Loop through boundary to set portal blocks + { + for (int Width = XZM; Width <= XZP; Width++) + { + if (Dir == 1) + { + a_World->SetBlock(Width, Height, Z, E_BLOCK_NETHER_PORTAL, Dir); + } + else + { + a_World->SetBlock(X, Height, Width, E_BLOCK_NETHER_PORTAL, Dir); + } + } + } + + return; + } + + /// Evaluates if coordinates are a portal going XP/XM; returns true if so, and writes boundaries to variable + /// Takes coordinates of base block and Y coord of target obsidian ceiling + bool FindPortalSliceX(int X1, int X2, int Y, int Z, int MaxY, cWorld * a_World) + { + Dir = 1; // Set assumed direction (will change if portal turns out to be facing the other direction) + bool FoundFrameXP = false, FoundFrameXM = false; + for (X1; ((a_World->GetBlock(X1, Y, Z) == E_BLOCK_OBSIDIAN) || (a_World->GetBlock(X1, Y + 1, Z) == E_BLOCK_OBSIDIAN)); X1++) // Check XP for obsidian blocks, exempting corners + { + int Value = FindObsidianCeiling(X1, Y, Z, a_World, MaxY); + int ValueTwo = FindObsidianCeiling(X1, Y + 1, Z, a_World, MaxY); // For corners without obsidian + if ((Value == -1) || (ValueTwo == -1)) // FindObsidianCeiling returns -1 upon frame-find + { + FoundFrameXP = true; // Found a frame border in this direction, proceed in other direction (don't go further) + break; + } + else if ((Value != MaxY) && (ValueTwo != MaxY)) // Make sure that there is a valid portal 'slice' + { + return false; // Not valid slice, no portal can be formed + } + } XZP = X1 - 1; // Set boundary of frame interior, note that for some reason, the loop of X and the loop of Z go to different numbers, hence -1 here and -2 there + for (X2; ((a_World->GetBlock(X2, Y, Z) == E_BLOCK_OBSIDIAN) || (a_World->GetBlock(X2, Y + 1, Z) == E_BLOCK_OBSIDIAN)); X2--) // Go the other direction (XM) + { + int Value = FindObsidianCeiling(X2, Y, Z, a_World, MaxY); + int ValueTwo = FindObsidianCeiling(X2, Y + 1, Z, a_World, MaxY); + if ((Value == -1) || (ValueTwo == -1)) + { + FoundFrameXM = true; + break; + } + else if ((Value != MaxY) && (ValueTwo != MaxY)) + { + return false; + } + } XZM = X2 + 1; // Set boundary, see previous + return (FoundFrameXP && FoundFrameXM); + } + + /// Evaluates if coords are a portal going ZP/ZM; returns true if so, and writes boundaries to variable + bool FindPortalSliceZ(int X, int Y, int Z1, int Z2, int MaxY, cWorld * a_World) + { + Dir = 2; + bool FoundFrameZP = false, FoundFrameZM = false; + for (Z1; ((a_World->GetBlock(X, Y, Z1) == E_BLOCK_OBSIDIAN) || (a_World->GetBlock(X, Y + 1, Z1) == E_BLOCK_OBSIDIAN)); Z1++) + { + int Value = FindObsidianCeiling(X, Y, Z1, a_World, MaxY); + int ValueTwo = FindObsidianCeiling(X, Y + 1, Z1, a_World, MaxY); + if ((Value == -1) || (ValueTwo == -1)) + { + FoundFrameZP = true; + continue; + } + else if ((Value != MaxY) && (ValueTwo != MaxY)) + { + return false; + } + } XZP = Z1 - 2; + for (Z2; ((a_World->GetBlock(X, Y, Z2) == E_BLOCK_OBSIDIAN) || (a_World->GetBlock(X, Y + 1, Z2) == E_BLOCK_OBSIDIAN)); Z2--) + { + int Value = FindObsidianCeiling(X, Y, Z2, a_World, MaxY); + int ValueTwo = FindObsidianCeiling(X, Y + 1, Z2, a_World, MaxY); + if ((Value == -1) || (ValueTwo == -1)) + { + FoundFrameZM = true; + continue; + } + else if ((Value != MaxY) && (ValueTwo != MaxY)) + { + return false; + } + } XZM = Z2 + 2; + return (FoundFrameZP && FoundFrameZM); + } +}; diff --git a/source/Blocks/BlockHandler.cpp b/source/Blocks/BlockHandler.cpp index e59fee8ee..cd07b3021 100644 --- a/source/Blocks/BlockHandler.cpp +++ b/source/Blocks/BlockHandler.cpp @@ -44,6 +44,7 @@ #include "BlockOre.h" #include "BlockPiston.h" #include "BlockPlanks.h" +#include "BlockPortal.h" #include "BlockPumpkin.h" #include "BlockRail.h" #include "BlockRedstone.h" @@ -159,6 +160,7 @@ cBlockHandler * cBlockHandler::CreateBlockHandler(BLOCKTYPE a_BlockType) case E_BLOCK_PISTON: return new cBlockPistonHandler (a_BlockType); case E_BLOCK_PISTON_EXTENSION: return new cBlockPistonHeadHandler ( ); case E_BLOCK_PLANKS: return new cBlockPlanksHandler (a_BlockType); + case E_BLOCK_NETHER_PORTAL: return new cBlockPortalHandler (a_BlockType); case E_BLOCK_PUMPKIN: return new cBlockPumpkinHandler (a_BlockType); case E_BLOCK_JACK_O_LANTERN: return new cBlockPumpkinHandler (a_BlockType); case E_BLOCK_PUMPKIN_STEM: return new cBlockStemsHandler (a_BlockType); diff --git a/source/Blocks/BlockPortal.h b/source/Blocks/BlockPortal.h new file mode 100644 index 000000000..c56f0cbc8 --- /dev/null +++ b/source/Blocks/BlockPortal.h @@ -0,0 +1,108 @@ + +#pragma once + +#include "BlockHandler.h" + + + + + +class cBlockPortalHandler : + public cBlockHandler +{ +public: + cBlockPortalHandler(BLOCKTYPE a_BlockType) + : cBlockHandler(a_BlockType) + { + } + + virtual bool GetPlacementBlockTypeMeta( + cWorld * a_World, cPlayer * a_Player, + int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, + int a_CursorX, int a_CursorY, int a_CursorZ, + BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta + ) override + { + // We set to zero so MCS doesn't stop you from building weird portals like vanilla does + // CanBeAt doesn't do anything if meta is zero + // We set to zero because the client sends meta = 2 to the server (it calculates rotation itself) + + a_BlockType = m_BlockType; + a_BlockMeta = 0; + return true; + } + + + virtual void ConvertToPickups(cItems & a_Pickups, NIBBLETYPE a_BlockMeta) override + { + return; // No pickups + } + + + virtual bool CanBeAt(int a_RelX, int a_RelY, int a_RelZ, const cChunk & a_Chunk) override + { + if ((a_RelY - 1 < 0) || (a_RelY + 1 > cChunkDef::Height)) + { + return false; // In case someone places a portal with meta 1 or 2 at boundaries, and server tries to get invalid coords at Y - 1 or Y + 1 + } + + switch (a_Chunk.GetMeta(a_RelX, a_RelY, a_RelZ)) + { + case 0x1: + { + static const struct + { + int x, y, z; + } PortalCheck[] = + { + { 0, 1, 0}, + { 0,-1, 0}, + { 1, 0, 0}, + {-1, 0, 0}, + } ; + + for (int i = 0; i < ARRAYCOUNT(PortalCheck); i++) + { + BLOCKTYPE Block; + a_Chunk.UnboundedRelGetBlockType(a_RelX + PortalCheck[i].x, a_RelY + PortalCheck[i].y, a_RelZ + PortalCheck[i].z, Block); + + if ((Block != E_BLOCK_NETHER_PORTAL) && (Block != E_BLOCK_OBSIDIAN)) + { + return false; + } + } + break; + } + case 0x2: + { + static const struct + { + int x, y, z; + } PortalCheck[] = + { + { 0, 1, 0}, + { 0,-1, 0}, + { 0, 0, -1}, + { 0, 0, 1}, + } ; + + for (int i = 0; i < ARRAYCOUNT(PortalCheck); i++) + { + BLOCKTYPE Block; + a_Chunk.UnboundedRelGetBlockType(a_RelX + PortalCheck[i].x, a_RelY + PortalCheck[i].y, a_RelZ + PortalCheck[i].z, Block); + + if ((Block != E_BLOCK_NETHER_PORTAL) && (Block != E_BLOCK_OBSIDIAN)) + { + return false; + } + } + break; + } + } + return true; + } +} ; + + + + diff --git a/source/Blocks/BlockRail.h b/source/Blocks/BlockRail.h index 0e83b952d..24a101652 100644 --- a/source/Blocks/BlockRail.h +++ b/source/Blocks/BlockRail.h @@ -22,6 +22,8 @@ enum ENUM_PURE class cBlockRailHandler : public cBlockHandler { + typedef cBlockHandler super; + public: cBlockRailHandler(BLOCKTYPE a_BlockType) : cBlockHandler(a_BlockType) @@ -51,6 +53,12 @@ public: } + virtual void ConvertToPickups(cItems & a_Pickups, NIBBLETYPE a_BlockMeta) override + { + super::ConvertToPickups(a_Pickups, 0); + } + + virtual bool CanBeAt(int a_RelX, int a_RelY, int a_RelZ, const cChunk & a_Chunk) override { if (a_RelY <= 0) diff --git a/source/ByteBuffer.cpp b/source/ByteBuffer.cpp index 6659b4dd4..1cdd2f430 100644 --- a/source/ByteBuffer.cpp +++ b/source/ByteBuffer.cpp @@ -195,7 +195,7 @@ int cByteBuffer::GetUsedSpace(void) const { CHECK_THREAD; CheckValid(); - return m_BufferSize - GetFreeSpace(); + return m_BufferSize - GetFreeSpace() - 1; } @@ -555,7 +555,7 @@ bool cByteBuffer::WriteVarInt(UInt32 a_Value) -bool cByteBuffer::WriteVarUTF8String(AString & a_Value) +bool cByteBuffer::WriteVarUTF8String(const AString & a_Value) { CHECK_THREAD; CheckValid(); diff --git a/source/ByteBuffer.h b/source/ByteBuffer.h index 71ee4764e..21abb0377 100644 --- a/source/ByteBuffer.h +++ b/source/ByteBuffer.h @@ -62,7 +62,7 @@ public: bool ReadVarUTF8String (AString & a_Value); // string length as VarInt, then string as UTF-8 /// Reads VarInt, assigns it to anything that can be assigned from an UInt32 (unsigned short, char, Byte, double, ...) - template <typename T> bool ReadVarUInt(T & a_Value) + template <typename T> bool ReadVarInt(T & a_Value) { UInt32 v; bool res = ReadVarInt(v); @@ -84,7 +84,7 @@ public: bool WriteBool (bool a_Value); bool WriteBEUTF16String16(const AString & a_Value); // string length as BE short, then string as UTF-16BE bool WriteVarInt (UInt32 a_Value); - bool WriteVarUTF8String (AString & a_Value); // string length as VarInt, then string as UTF-8 + bool WriteVarUTF8String (const AString & a_Value); // string length as VarInt, then string as UTF-8 /// Reads a_Count bytes into a_Buffer; returns true if successful bool ReadBuf(void * a_Buffer, int a_Count); diff --git a/source/Chunk.cpp b/source/Chunk.cpp index c9d457af3..be75eae41 100644 --- a/source/Chunk.cpp +++ b/source/Chunk.cpp @@ -519,32 +519,28 @@ void cChunk::SpawnMobs(cMobSpawner& a_MobSpawner) ASSERT(Try_Y > 0); ASSERT(Try_Y < cChunkDef::Height-1); - BLOCKTYPE BlockType; - NIBBLETYPE BlockMeta; - BLOCKTYPE BlockType_below; - NIBBLETYPE BlockMeta_below; - BLOCKTYPE BlockType_above; - NIBBLETYPE BlockMeta_above; - if (UnboundedRelGetBlock(Try_X, Try_Y , Try_Z, BlockType, BlockMeta) && - UnboundedRelGetBlock(Try_X, Try_Y-1, Try_Z, BlockType_below, BlockMeta_below)&& - UnboundedRelGetBlock(Try_X, Try_Y+1, Try_Z, BlockType_above, BlockMeta_above) - ) - { - EMCSBiome Biome = m_ChunkMap->GetBiomeAt (Try_X, Try_Z); - // MG TODO : - // Moon cycle (for slime) - // check player and playerspawn presence < 24 blocks - // check mobs presence on the block + EMCSBiome Biome = m_ChunkMap->GetBiomeAt (Try_X, Try_Z); + // MG TODO : + // Moon cycle (for slime) + // check player and playerspawn presence < 24 blocks + // check mobs presence on the block + + // MG TODO : check that "Level" really means Y + + NIBBLETYPE SkyLight = 0; - // MG TODO: fix the "light" thing, I'm pretty sure that UnboundedRelGetBlock s not returning the right thing + NIBBLETYPE BlockLight = 0; - // MG TODO : check that "Level" really means Y - cEntity* newMob = a_MobSpawner.TryToSpawnHere(BlockType, BlockMeta, BlockType_below, BlockMeta_below, BlockType_above, BlockMeta_above, Biome, Try_Y, MaxNbOfSuccess); + if (IsLightValid()) + { + cEntity* newMob = a_MobSpawner.TryToSpawnHere(this, Try_X, Try_Y, Try_Z, Biome, MaxNbOfSuccess); if (newMob) { int WorldX, WorldY, WorldZ; PositionToWorldPosition(Try_X, Try_Y, Try_Z, WorldX, WorldY, WorldZ); - newMob->SetPosition(WorldX, WorldY, WorldZ); + double ActualX = WorldX + 0.5; + double ActualZ = WorldZ + 0.5; + newMob->SetPosition(ActualX, WorldY, ActualZ); LOGD("Spawning %s #%i at %d,%d,%d",newMob->GetClass(),newMob->GetUniqueID(),WorldX, WorldY, WorldZ); NumberOfSuccess++; } @@ -2790,6 +2786,17 @@ Vector3i cChunk::PositionToWorldPosition(int a_RelX, int a_RelY, int a_RelZ) +NIBBLETYPE cChunk::GetTimeAlteredLight(NIBBLETYPE a_Skylight) const +{ + a_Skylight -= m_World->GetSkyDarkness(); + // Because NIBBLETYPE is unsigned, we clamp it to 0 .. 15 by checking for values above 15 + return (a_Skylight < 16)? a_Skylight : 0; +} + + + + + #if !C_CHUNK_USE_INLINE # include "cChunk.inl.h" #endif diff --git a/source/Chunk.h b/source/Chunk.h index ab110c7cb..63a8f75cd 100644 --- a/source/Chunk.h +++ b/source/Chunk.h @@ -329,6 +329,10 @@ public: /// Same as QueueTickBlock(), but relative coords needn't be in this chunk (uses m_Neighbor-s in such a case), ignores unsuccessful attempts void UnboundedQueueTickBlock(int a_RelX, int a_RelY, int a_RelZ); + /// Light alterations based on time + NIBBLETYPE GetTimeAlteredLight(NIBBLETYPE a_Skylight) const; + + // Simulator data: cFireSimulatorChunkData & GetFireSimulatorData (void) { return m_FireSimulatorData; } cFluidSimulatorData * GetWaterSimulatorData(void) { return m_WaterSimulatorData; } diff --git a/source/ChunkMap.cpp b/source/ChunkMap.cpp index c3bd5f33d..d9de24cff 100644 --- a/source/ChunkMap.cpp +++ b/source/ChunkMap.cpp @@ -12,6 +12,7 @@ #include "BlockArea.h" #include "PluginManager.h" #include "Entities/TNTEntity.h" +#include "Blocks/BlockHandler.h" #include "MobCensus.h" #include "MobSpawner.h" @@ -1610,7 +1611,9 @@ void cChunkMap::DoExplosionAt(double a_ExplosionSize, double a_BlockX, double a_ // Too far away continue; } - switch (area.GetBlockType(bx + x, by + y, bz + z)) + + BLOCKTYPE Block = area.GetBlockType(bx + x, by + y, bz + z); + switch (Block) { case E_BLOCK_TNT: { @@ -1644,8 +1647,22 @@ void cChunkMap::DoExplosionAt(double a_ExplosionSize, double a_BlockX, double a_ break; } + case E_BLOCK_AIR: + { + // No pickups for air + break; + } + default: { + if (m_World->GetTickRandomNumber(10) == 5) + { + cItems Drops; + cBlockHandler * Handler = BlockHandler(Block); + + Handler->ConvertToPickups(Drops, area.GetBlockMeta(bx + x, by + y, bz + z)); + m_World->SpawnItemPickups(Drops, bx + x, by + y, bz + z); + } area.SetBlockType(bx + x, by + y, bz + z, E_BLOCK_AIR); a_BlocksAffected.push_back(Vector3i(bx + x, by + y, bz + z)); } diff --git a/source/ClientHandle.cpp b/source/ClientHandle.cpp index ea8b48f9d..6860a29ca 100644 --- a/source/ClientHandle.cpp +++ b/source/ClientHandle.cpp @@ -1470,7 +1470,7 @@ void cClientHandle::Tick(float a_Dt) } // If the chunk the player's in was just sent, spawn the player: - if (m_HasSentPlayerChunk && (m_State != csPlaying)) + if (m_HasSentPlayerChunk && (m_State != csPlaying) && !IsDestroying()) { if (!cRoot::Get()->GetPluginManager()->CallHookPlayerJoined(*m_Player)) { @@ -2138,7 +2138,7 @@ void cClientHandle::PacketUnknown(unsigned char a_PacketType) LOGERROR("Unknown packet type 0x%02x from client \"%s\" @ %s", a_PacketType, m_Username.c_str(), m_IPString.c_str()); AString Reason; - Printf(Reason, "[C->S] Unknown PacketID: 0x%02x", a_PacketType); + Printf(Reason, "Unknown [C->S] PacketType: 0x%02x", a_PacketType); SendDisconnect(Reason); Destroy(); } diff --git a/source/Entities/Entity.cpp b/source/Entities/Entity.cpp index d465c75bd..3bea7bc01 100644 --- a/source/Entities/Entity.cpp +++ b/source/Entities/Entity.cpp @@ -1178,9 +1178,9 @@ void cEntity::SetMass(double a_Mass) -void cEntity::SetRotation(double a_Rotation) +void cEntity::SetYaw(double a_Yaw) { - m_Rot.x = a_Rotation; + m_Rot.x = a_Yaw; m_bDirtyOrientation = true; WrapRotation(); } diff --git a/source/Entities/Entity.h b/source/Entities/Entity.h index c6b70a7fc..dafda7826 100644 --- a/source/Entities/Entity.h +++ b/source/Entities/Entity.h @@ -151,7 +151,8 @@ public: double GetPosY (void) const { return m_Pos.y; } double GetPosZ (void) const { return m_Pos.z; } const Vector3d & GetRot (void) const { return m_Rot; } - double GetRotation (void) const { return m_Rot.x; } + double GetRotation (void) const { return m_Rot.x; } // OBSOLETE, use GetYaw() instead + double GetYaw (void) const { return m_Rot.x; } double GetPitch (void) const { return m_Rot.y; } double GetRoll (void) const { return m_Rot.z; } Vector3d GetLookVector(void) const; @@ -173,7 +174,8 @@ public: void SetPosition(double a_PosX, double a_PosY, double a_PosZ); void SetPosition(const Vector3d & a_Pos) { SetPosition(a_Pos.x, a_Pos.y, a_Pos.z); } void SetRot (const Vector3f & a_Rot); - void SetRotation(double a_Rotation); + void SetRotation(double a_Rotation) { SetYaw(a_Rotation); } // OBSOLETE, use SetYaw() instead + void SetYaw (double a_Yaw); void SetPitch (double a_Pitch); void SetRoll (double a_Roll); void SetSpeed (double a_SpeedX, double a_SpeedY, double a_SpeedZ); diff --git a/source/Entities/Player.cpp b/source/Entities/Player.cpp index d94bc944c..2e4199629 100644 --- a/source/Entities/Player.cpp +++ b/source/Entities/Player.cpp @@ -297,7 +297,6 @@ void cPlayer::CancelChargingBow(void) void cPlayer::SetTouchGround(bool a_bTouchGround) { - // If just m_bTouchGround = a_bTouchGround; if (!m_bTouchGround) @@ -307,12 +306,11 @@ void cPlayer::SetTouchGround(bool a_bTouchGround) m_LastJumpHeight = (float)GetPosY(); } cWorld * World = GetWorld(); - if ((GetPosY() >= 0) && (GetPosY() < 256)) + if ((GetPosY() >= 0) && (GetPosY() < cChunkDef::Height)) { - BLOCKTYPE BlockType = World->GetBlock( float2int(GetPosX()), float2int(GetPosY()), float2int(GetPosZ()) ); + BLOCKTYPE BlockType = World->GetBlock(float2int(GetPosX()), float2int(GetPosY()), float2int(GetPosZ())); if (BlockType != E_BLOCK_AIR) { - // LOGD("TouchGround set to true by server"); m_bTouchGround = true; } if ( @@ -322,19 +320,18 @@ void cPlayer::SetTouchGround(bool a_bTouchGround) (BlockType == E_BLOCK_VINES) ) { - // LOGD("Water / Ladder / Torch"); m_LastGroundHeight = (float)GetPosY(); } } } - - if (m_bTouchGround) + else { float Dist = (float)(m_LastGroundHeight - floor(GetPosY())); int Damage = (int)(Dist - 3.f); - if(m_LastJumpHeight > m_LastGroundHeight) Damage++; + if (m_LastJumpHeight > m_LastGroundHeight) Damage++; m_LastJumpHeight = (float)GetPosY(); - if (Damage > 0) + + if ((Damage > 0) && (!IsGameModeCreative())) { TakeDamage(dtFalling, NULL, Damage, Damage, 0); } @@ -1416,11 +1413,11 @@ cPlayer::StringList cPlayer::GetResolvedPermissions() void cPlayer::UseEquippedItem(void) { - if (GetGameMode() == gmCreative) // No damage in creative + if (IsGameModeCreative()) // No damage in creative { return; } - + GetInventory().DamageEquippedItem(); } diff --git a/source/Entities/Player.h b/source/Entities/Player.h index 449a63231..01efa3681 100644 --- a/source/Entities/Player.h +++ b/source/Entities/Player.h @@ -4,6 +4,7 @@ #include "Pawn.h" #include "../Inventory.h" #include "../Defines.h" +#include "../World.h" @@ -99,6 +100,9 @@ public: /// Returns the current gamemode. Partly OBSOLETE, you should use IsGameModeXXX() functions wherever applicable eGameMode GetGameMode(void) const { return m_GameMode; } + /// Returns the current effective gamemode (inherited gamemode is resolved before returning) + eGameMode GetEffectiveGameMode(void) const { return (m_GameMode == gmNotSet) ? m_World->GetGameMode() : m_GameMode; } + /** Sets the gamemode for the player. The gamemode may be gmNotSet, in that case the player inherits the world's gamemode. Updates the gamemode on the client (sends the packet) diff --git a/source/Entities/ProjectileEntity.cpp b/source/Entities/ProjectileEntity.cpp index 4c8e680d0..1d5532718 100644 --- a/source/Entities/ProjectileEntity.cpp +++ b/source/Entities/ProjectileEntity.cpp @@ -474,8 +474,17 @@ cThrownEggEntity::cThrownEggEntity(cEntity * a_Creator, double a_X, double a_Y, void cThrownEggEntity::OnHitSolidBlock(const Vector3d & a_HitPos, char a_HitFace) { - // TODO: Random-spawn a chicken or four - + if (m_World->GetTickRandomNumber(7) == 1) + { + m_World->SpawnMob(a_HitPos.x, a_HitPos.y, a_HitPos.z, cMonster::mtChicken); + } + else if (m_World->GetTickRandomNumber(32) == 1) + { + m_World->SpawnMob(a_HitPos.x, a_HitPos.y, a_HitPos.z, cMonster::mtChicken); + m_World->SpawnMob(a_HitPos.x, a_HitPos.y, a_HitPos.z, cMonster::mtChicken); + m_World->SpawnMob(a_HitPos.x, a_HitPos.y, a_HitPos.z, cMonster::mtChicken); + m_World->SpawnMob(a_HitPos.x, a_HitPos.y, a_HitPos.z, cMonster::mtChicken); + } Destroy(); } diff --git a/source/Item.cpp b/source/Item.cpp index 31a09a608..5e0beb028 100644 --- a/source/Item.cpp +++ b/source/Item.cpp @@ -38,6 +38,7 @@ short cItem::GetMaxDamage(void) const { switch (m_ItemType) { + case E_ITEM_BOW: return 384; case E_ITEM_DIAMOND_AXE: return 1563; case E_ITEM_DIAMOND_HOE: return 1563; case E_ITEM_DIAMOND_PICKAXE: return 1563; diff --git a/source/Items/ItemBow.h b/source/Items/ItemBow.h index 845192ef7..7bce127b1 100644 --- a/source/Items/ItemBow.h +++ b/source/Items/ItemBow.h @@ -71,6 +71,11 @@ public: return; } a_Player->GetWorld()->BroadcastSpawnEntity(*Arrow); + + if (!a_Player->IsGameModeCreative()) + { + a_Player->UseEquippedItem(); + } } } ; diff --git a/source/Items/ItemThrowable.h b/source/Items/ItemThrowable.h index dacdb6157..85579daf2 100644 --- a/source/Items/ItemThrowable.h +++ b/source/Items/ItemThrowable.h @@ -28,9 +28,15 @@ public: virtual bool OnItemUse(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Dir) override { + if (!a_Player->IsGameModeCreative()) + { + a_Player->GetInventory().RemoveOneEquippedItem(); + } + Vector3d Pos = a_Player->GetThrowStartPos(); Vector3d Speed = a_Player->GetLookVector() * m_SpeedCoeff; a_World->CreateProjectile(Pos.x, Pos.y, Pos.z, m_ProjectileKind, a_Player, &Speed); + return true; } diff --git a/source/MobSpawner.cpp b/source/MobSpawner.cpp index 1b3796f70..d4926bbe5 100644 --- a/source/MobSpawner.cpp +++ b/source/MobSpawner.cpp @@ -124,87 +124,104 @@ cMonster::eType cMobSpawner::ChooseMobType(EMCSBiome a_Biome) -bool cMobSpawner::CanSpawnHere(cMonster::eType a_MobType, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, BLOCKTYPE a_BlockType_below, NIBBLETYPE a_BlockMeta_below, BLOCKTYPE a_BlockType_above, NIBBLETYPE a_BlockMeta_above, EMCSBiome a_Biome, int a_Level) +bool cMobSpawner::CanSpawnHere(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ, cMonster::eType a_MobType, EMCSBiome a_Biome) { - bool toReturn = false; - std::set<cMonster::eType>::iterator itr = m_AllowedTypes.find(a_MobType); - if (itr != m_AllowedTypes.end()) + BLOCKTYPE TargetBlock; + if (m_AllowedTypes.find(a_MobType) != m_AllowedTypes.end() && a_Chunk->UnboundedRelGetBlockType(a_RelX, a_RelY, a_RelZ, TargetBlock)) { - // MG TODO : find a nicer paging - if (a_MobType == cMonster::mtSquid) - { - toReturn = ( - IsBlockLiquid(a_BlockType) && - a_Level >= 45 && - a_Level <= 62 - ); - } - else if (a_MobType == cMonster::mtBat) - { - toReturn = a_Level <= 60; // MG TODO : find a real rule - } - else + NIBBLETYPE BlockLight = a_Chunk->GetBlockLight(a_RelX, a_RelY, a_RelZ); + NIBBLETYPE SkyLight = a_Chunk->GetSkyLight(a_RelX, a_RelY, a_RelZ); + BLOCKTYPE BlockAbove = a_Chunk->GetBlock(a_RelX, a_RelY + 1, a_RelZ); + BLOCKTYPE BlockBelow = a_Chunk->GetBlock(a_RelX, a_RelY - 1, a_RelZ); + + SkyLight = a_Chunk->GetTimeAlteredLight(SkyLight); + + switch(a_MobType) { - if ( - a_BlockType == E_BLOCK_AIR && - a_BlockType_above == E_BLOCK_AIR && - ! (g_BlockTransparent[a_BlockType_below]) - ) + case cMonster::mtSquid: + return IsBlockWater(TargetBlock) && (a_RelY >= 45) && (a_RelY <= 62); + + case cMonster::mtBat: + return (a_RelY <= 63) && (BlockLight <= 4) && (SkyLight <= 4) && (TargetBlock == E_BLOCK_AIR) && (!g_BlockTransparent[BlockAbove]); + + case cMonster::mtChicken: + case cMonster::mtCow: + case cMonster::mtPig: + case cMonster::mtHorse: + case cMonster::mtSheep: { - if (a_MobType == cMonster::mtChicken || a_MobType == cMonster::mtPig || a_MobType == cMonster::mtCow || a_MobType == cMonster::mtSheep) - { - toReturn = ( - a_BlockType_below == E_BLOCK_GRASS /*&& // MG TODO - a_LightLevel >= 9 */ - ); - } - else if (a_MobType == cMonster::mtOcelot) + return (TargetBlock == E_BLOCK_AIR) && (BlockAbove == E_BLOCK_AIR) && (!g_BlockTransparent[BlockBelow]) && + (BlockBelow == E_BLOCK_GRASS) && (SkyLight >= 9); + } + + case cMonster::mtOcelot: + { + return (TargetBlock == E_BLOCK_AIR) && (BlockAbove == E_BLOCK_AIR) && + ((BlockBelow == E_BLOCK_GRASS) || (BlockBelow == E_BLOCK_LEAVES)) && (a_RelY >= 62) && (m_Random.NextInt(3,a_Biome) != 0); + } + case cMonster::mtEnderman: + { + if (a_RelY < 250) { - toReturn = ( - a_Level >= 62 && - ( - a_BlockType_below == E_BLOCK_GRASS || - a_BlockType_below == E_BLOCK_LEAVES - ) && - m_Random.NextInt(3,a_Biome) != 0 - ); + BLOCKTYPE BlockTop = a_Chunk->GetBlock(a_RelX, a_RelY + 2, a_RelZ); + if (BlockTop == E_BLOCK_AIR) + { + BlockTop = a_Chunk->GetBlock(a_RelX, a_RelY + 3, a_RelZ); + return (TargetBlock == E_BLOCK_AIR) && (BlockAbove == E_BLOCK_AIR) && (BlockTop == E_BLOCK_AIR) && (!g_BlockTransparent[BlockBelow]) && + (SkyLight <= 7) && (BlockLight <= 7); + } } - else if (a_MobType == cMonster::mtCreeper || a_MobType == cMonster::mtSkeleton || a_MobType == cMonster::mtZombie || a_MobType == cMonster::mtSpider || a_MobType == cMonster::mtEnderman || a_MobType == cMonster::mtZombiePigman) + break; + } + case cMonster::mtSpider: + { + bool CanSpawn = true; + bool HaveFloor = false; + for (int x = 0; x < 2; ++x) { - toReturn = true /*a_LightLevel <= 7 MG TODO*/; - /*if (a_SunLight) MG TODO + for(int z = 0; z < 2; ++z) { - if (m_Random.NextInt(2,a_Biome) != 0) + CanSpawn = a_Chunk->UnboundedRelGetBlockType(a_RelX + x, a_RelY, a_RelZ + z, TargetBlock); + CanSpawn = CanSpawn && (TargetBlock == E_BLOCK_AIR); + if (!CanSpawn) { - toReturn = false; + return false; } - }*/ - } - else if (a_MobType == cMonster::mtSlime) - { - toReturn = a_Level <= 40; - // MG TODO : much more complicated rules - } - else if (a_MobType == cMonster::mtGhast) - { - toReturn = m_Random.NextInt(20,a_Biome) == 0; - } - else - { - LOGD("MG TODO : check I've got a Rule to write for type %d",a_MobType); - toReturn = true; + if (!HaveFloor) + { + a_Chunk->UnboundedRelGetBlockType(a_RelX + x, a_RelY - 1, a_RelZ + z, TargetBlock); + HaveFloor = HaveFloor || !g_BlockTransparent[TargetBlock]; + } + } } + return CanSpawn && HaveFloor && (SkyLight <= 7) && (BlockLight <= 7); + } + case cMonster::mtCreeper: + case cMonster::mtZombie: + return (TargetBlock == E_BLOCK_AIR) && (BlockAbove == E_BLOCK_AIR) && (!g_BlockTransparent[BlockBelow]) && + (SkyLight <= 7) && (BlockLight <= 7) && (m_Random.NextInt(2,a_Biome) == 0); + + case cMonster::mtSlime: + return (TargetBlock == E_BLOCK_AIR) && (BlockAbove == E_BLOCK_AIR) && (!g_BlockTransparent[BlockBelow]) && + ((a_RelY <= 40) || a_Biome == biSwampland); + case cMonster::mtGhast: + case cMonster::mtZombiePigman: + return (TargetBlock == E_BLOCK_AIR) && (BlockAbove == E_BLOCK_AIR) && (!g_BlockTransparent[BlockBelow]) && + (m_Random.NextInt(20,a_Biome) == 0); + default: + LOGD("MG TODO : check I've got a Rule to write for type %d",a_MobType); + return false; } } - return toReturn; + return false; } -cMonster* cMobSpawner::TryToSpawnHere(BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, BLOCKTYPE a_BlockType_below, NIBBLETYPE a_BlockMeta_below, BLOCKTYPE a_BlockType_above, NIBBLETYPE a_BlockMeta_above, EMCSBiome a_Biome, int a_Level, int& a_MaxPackSize) +cMonster* cMobSpawner::TryToSpawnHere(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ, EMCSBiome a_Biome, int& a_MaxPackSize) { cMonster* toReturn = NULL; if (m_NewPack) @@ -225,8 +242,10 @@ cMonster* cMobSpawner::TryToSpawnHere(BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockM m_NewPack = false; } + // Make sure we are looking at the right chunk to spawn in + a_Chunk = a_Chunk->GetRelNeighborChunkAdjustCoords(a_RelX, a_RelZ); - if (CanSpawnHere(m_MobType, a_BlockType, a_BlockMeta, a_BlockType_below, a_BlockMeta_below, a_BlockType_above, a_BlockMeta_above, a_Biome, a_Level)) + if (CanSpawnHere(a_Chunk, a_RelX, a_RelY, a_RelZ, m_MobType, a_Biome)) { cMonster * newMob = cMonster::NewMonsterFromType(m_MobType); if (newMob) diff --git a/source/MobSpawner.h b/source/MobSpawner.h index ba2a18f2e..ea6636310 100644 --- a/source/MobSpawner.h +++ b/source/MobSpawner.h @@ -4,6 +4,7 @@ #include <set> #include "BlockID.h" #include "ChunkDef.h" +#include "Chunk.h" #include "FastRandom.h" #include "Mobs/Monster.h" //this is a side-effect of keeping Mobfamily inside Monster class. I'd prefer to keep both (Mobfamily and Monster) inside a "Monster" namespace MG TODO : do it @@ -38,7 +39,7 @@ public : // if this is the first of a Pack : determine the type of monster // BlockType & BlockMeta are used to decide what kind of Mob can Spawn here // MaxPackSize is set to the maximal size for a pack this type of mob - cMonster * TryToSpawnHere(BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, BLOCKTYPE a_BlockType_below, NIBBLETYPE a_BlockMeta_below, BLOCKTYPE a_BlockType_above, NIBBLETYPE a_BlockMeta_above, EMCSBiome a_Biome, int a_Level, int& a_MaxPackSize); + cMonster * TryToSpawnHere(cChunk * a_Chunk, int A_RelX, int a_RelY, int a_RelZ, EMCSBiome a_Biome, int& a_MaxPackSize); // mark the beginning of a new Pack // all mobs of the same Pack are the same type @@ -52,7 +53,7 @@ public : protected : // return true if specified type of mob can spawn on specified block - bool CanSpawnHere(cMonster::eType a_MobType, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, BLOCKTYPE a_BlockType_below, NIBBLETYPE a_BlockMeta_below, BLOCKTYPE a_BlockType_above, NIBBLETYPE a_BlockMeta_above, EMCSBiome a_Biome, int a_Level); + bool CanSpawnHere(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ, cMonster::eType a_MobType, EMCSBiome a_Biome); // return a random type that can spawn on specified biome. // returns E_ENTITY_TYPE_DONOTUSE if none is possible diff --git a/source/Mobs/Horse.cpp b/source/Mobs/Horse.cpp index f9705a451..d18887ea4 100644 --- a/source/Mobs/Horse.cpp +++ b/source/Mobs/Horse.cpp @@ -1,4 +1,3 @@ - #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules #include "Horse.h" @@ -142,6 +141,10 @@ void cHorse::OnRightClicked(cPlayer & a_Player) void cHorse::GetDrops(cItems & a_Drops, cEntity * a_Killer) { AddRandomDropItem(a_Drops, 0, 2, E_ITEM_LEATHER); + if (m_bIsSaddled) + { + a_Drops.push_back(cItem(E_ITEM_SADDLE, 1)); + } } diff --git a/source/Mobs/Pig.cpp b/source/Mobs/Pig.cpp index 5427cf35f..0871a38a9 100644 --- a/source/Mobs/Pig.cpp +++ b/source/Mobs/Pig.cpp @@ -22,6 +22,10 @@ cPig::cPig(void) : void cPig::GetDrops(cItems & a_Drops, cEntity * a_Killer) { AddRandomDropItem(a_Drops, 1, 3, IsOnFire() ? E_ITEM_COOKED_PORKCHOP : E_ITEM_RAW_PORKCHOP); + if (m_bIsSaddled) + { + a_Drops.push_back(cItem(E_ITEM_SADDLE, 1)); + } } diff --git a/source/Protocol/Protocol.h b/source/Protocol/Protocol.h index 5071f5961..6bea4edbb 100644 --- a/source/Protocol/Protocol.h +++ b/source/Protocol/Protocol.h @@ -77,6 +77,7 @@ public: virtual void SendKeepAlive (int a_PingID) = 0; virtual void SendLogin (const cPlayer & a_Player, const cWorld & a_World) = 0; virtual void SendPickupSpawn (const cPickup & a_Pickup) = 0; + virtual void SendPlayerAbilities (void) = 0; virtual void SendPlayerAnimation (const cPlayer & a_Player, char a_Animation) = 0; virtual void SendPlayerListItem (const cPlayer & a_Player, bool a_IsOnline) = 0; virtual void SendPlayerMaxSpeed (void) = 0; ///< Informs the client of the maximum player speed (1.6.1+) @@ -186,6 +187,27 @@ protected: WriteInt(a_Vector.y); WriteInt(a_Vector.z); } + + void WriteVarInt(UInt32 a_Value) + { + // A 32-bit integer can be encoded by at most 5 bytes: + unsigned char b[5]; + int idx = 0; + do + { + b[idx] = (a_Value & 0x7f) | ((a_Value > 0x7f) ? 0x80 : 0x00); + a_Value = a_Value >> 7; + idx++; + } while (a_Value > 0); + + SendData((const char *)b, idx); + } + + void WriteVarUTF8String(const AString & a_String) + { + WriteVarInt(a_String.size()); + SendData(a_String.data(), a_String.size()); + } } ; diff --git a/source/Protocol/Protocol125.h b/source/Protocol/Protocol125.h index ae198780c..1da50a1d4 100644 --- a/source/Protocol/Protocol125.h +++ b/source/Protocol/Protocol125.h @@ -54,6 +54,7 @@ public: virtual void SendKeepAlive (int a_PingID) override; virtual void SendLogin (const cPlayer & a_Player, const cWorld & a_World) override; virtual void SendPickupSpawn (const cPickup & a_Pickup) override; + virtual void SendPlayerAbilities (void) override {} // This protocol doesn't support such message virtual void SendPlayerAnimation (const cPlayer & a_Player, char a_Animation) override; virtual void SendPlayerListItem (const cPlayer & a_Player, bool a_IsOnline) override; virtual void SendPlayerMaxSpeed (void) override; @@ -92,9 +93,9 @@ protected: PARSE_INCOMPLETE = -3, } ; - cByteBuffer m_ReceivedData; //< Buffer for the received data + cByteBuffer m_ReceivedData; ///< Buffer for the received data - AString m_Username; //< Stored in ParseHandshake(), compared to Login username + AString m_Username; ///< Stored in ParseHandshake(), compared to Login username virtual void SendData(const char * a_Data, int a_Size) override; diff --git a/source/Protocol/Protocol17x.cpp b/source/Protocol/Protocol17x.cpp new file mode 100644 index 000000000..38260d046 --- /dev/null +++ b/source/Protocol/Protocol17x.cpp @@ -0,0 +1,1445 @@ + +// Protocol17x.cpp + +/* +Implements the 1.7.x protocol classes: + - cProtocol172 + - release 1.7.2 protocol (#4) +(others may be added later in the future for the 1.7 release series) +*/ + +#include "Globals.h" +#include "Protocol17x.h" +#include "ChunkDataSerializer.h" +#include "../ClientHandle.h" +#include "../Root.h" +#include "../Server.h" +#include "../World.h" +#include "../WorldStorage/FastNBT.h" +#include "../StringCompression.h" +#include "../Entities/FallingBlock.h" +#include "../Entities/Pickup.h" +#include "../Entities/Player.h" + + + + + +#define HANDLE_PACKET_READ(Proc, Type, Var) \ + Type Var; \ + m_ReceivedData.Proc(Var); + + + + + +cProtocol172::cProtocol172(cClientHandle * a_Client, const AString & a_ServerAddress, UInt16 a_ServerPort, UInt32 a_State) : + super(a_Client), + m_ServerAddress(a_ServerAddress), + m_ServerPort(a_ServerPort), + m_State(a_State), + m_ReceivedData(32 KiB), + m_OutPacketBuffer(64 KiB), + m_OutPacketLenBuffer(20), // 20 bytes is more than enough for one VarInt + m_IsEncrypted(false) +{ +} + + + + + +void cProtocol172::DataReceived(const char * a_Data, int a_Size) +{ + if (m_IsEncrypted) + { + byte Decrypted[512]; + while (a_Size > 0) + { + int NumBytes = (a_Size > sizeof(Decrypted)) ? sizeof(Decrypted) : a_Size; + m_Decryptor.ProcessData(Decrypted, (byte *)a_Data, NumBytes); + AddReceivedData((const char *)Decrypted, NumBytes); + a_Size -= NumBytes; + a_Data += NumBytes; + } + } + else + { + AddReceivedData(a_Data, a_Size); + } +} + + + + + +void cProtocol172::SendAttachEntity(const cEntity & a_Entity, const cEntity * a_Vehicle) +{ + cPacketizer Pkt(*this, 0x1b); // Attach Entity packet + Pkt.WriteInt(a_Entity.GetUniqueID()); + Pkt.WriteInt((a_Vehicle != NULL) ? a_Vehicle->GetUniqueID() : 0); + Pkt.WriteBool(false); +} + + + + + +void cProtocol172::SendBlockAction(int a_BlockX, int a_BlockY, int a_BlockZ, char a_Byte1, char a_Byte2, BLOCKTYPE a_BlockType) +{ + cPacketizer Pkt(*this, 0x24); // Block Action packet + Pkt.WriteInt(a_BlockX); + Pkt.WriteShort(a_BlockY); + Pkt.WriteInt(a_BlockZ); + Pkt.WriteByte(a_Byte1); + Pkt.WriteByte(a_Byte2); + Pkt.WriteVarInt(a_BlockType); +} + + + + + +void cProtocol172::SendBlockBreakAnim(int a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage) +{ + cPacketizer Pkt(*this, 0x24); // Block Break Animation packet + Pkt.WriteInt(a_EntityID); + Pkt.WriteInt(a_BlockX); + Pkt.WriteInt(a_BlockY); + Pkt.WriteInt(a_BlockZ); + Pkt.WriteChar(a_Stage); +} + + + + + +void cProtocol172::SendBlockChange(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) +{ + cPacketizer Pkt(*this, 0x23); // Block Change packet + Pkt.WriteInt(a_BlockX); + Pkt.WriteByte(a_BlockY); + Pkt.WriteInt(a_BlockZ); + Pkt.WriteVarInt(a_BlockType); + Pkt.WriteByte(a_BlockMeta); +} + + + + + +void cProtocol172::SendBlockChanges(int a_ChunkX, int a_ChunkZ, const sSetBlockVector & a_Changes) +{ + cPacketizer Pkt(*this, 0x22); // Multi Block Change packet + Pkt.WriteInt(a_ChunkX); + Pkt.WriteInt(a_ChunkZ); + Pkt.WriteShort((short)a_Changes.size()); + Pkt.WriteInt(a_Changes.size() * 4); + for (sSetBlockVector::const_iterator itr = a_Changes.begin(), end = a_Changes.end(); itr != end; ++itr) + { + unsigned int Coords = itr->y | (itr->z << 8) | (itr->x << 12); + unsigned int Blocks = itr->BlockMeta | (itr->BlockType << 4); + Pkt.WriteInt((Coords << 16) | Blocks); + } // for itr - a_Changes[] +} + + + + + +void cProtocol172::SendChat(const AString & a_Message) +{ + cPacketizer Pkt(*this, 0x02); // Chat Message packet + Pkt.WriteString(Printf("{\"text\":\"%s\"}", EscapeString(a_Message).c_str())); +} + + + + + +void cProtocol172::SendChunkData(int a_ChunkX, int a_ChunkZ, cChunkDataSerializer & a_Serializer) +{ + // Serialize first, before creating the Packetizer (the packetizer locks a CS) + // This contains the flags and bitmasks, too + const AString & ChunkData = a_Serializer.Serialize(cChunkDataSerializer::RELEASE_1_3_2); + + cPacketizer Pkt(*this, 0x21); // Chunk Data packet + Pkt.WriteInt(a_ChunkX); + Pkt.WriteInt(a_ChunkZ); + Pkt.WriteBuf(ChunkData.data(), ChunkData.size()); +} + + + + + +void cProtocol172::SendCollectPickup(const cPickup & a_Pickup, const cPlayer & a_Player) +{ + cPacketizer Pkt(*this, 0x0d); // Collect Item packet + Pkt.WriteInt(a_Pickup.GetUniqueID()); + Pkt.WriteInt(a_Player.GetUniqueID()); +} + + + + + +void cProtocol172::SendDestroyEntity(const cEntity & a_Entity) +{ + cPacketizer Pkt(*this, 0x13); // Destroy Entities packet + Pkt.WriteByte(1); + Pkt.WriteInt(a_Entity.GetUniqueID()); +} + + + + + +void cProtocol172::SendDisconnect(const AString & a_Reason) +{ + cPacketizer Pkt(*this, 0x40); + Pkt.WriteString(a_Reason); +} + + + + + +void cProtocol172::SendEditSign(int a_BlockX, int a_BlockY, int a_BlockZ) +{ + cPacketizer Pkt(*this, 0x36); // Sign Editor Open packet + Pkt.WriteInt(a_BlockX); + Pkt.WriteInt(a_BlockY); + Pkt.WriteInt(a_BlockZ); +} + + + + + +void cProtocol172::SendEntityEquipment(const cEntity & a_Entity, short a_SlotNum, const cItem & a_Item) +{ + cPacketizer Pkt(*this, 0x04); // Entity Equipment packet + Pkt.WriteInt(a_Entity.GetUniqueID()); + Pkt.WriteShort(a_SlotNum); + Pkt.WriteItem(a_Item); +} + + + + + +void cProtocol172::SendEntityHeadLook(const cEntity & a_Entity) +{ + cPacketizer Pkt(*this, 0x19); // Entity Head Look packet + Pkt.WriteInt(a_Entity.GetUniqueID()); + Pkt.WriteByteAngle(a_Entity.GetHeadYaw()); +} + + + + + +void cProtocol172::SendEntityLook(const cEntity & a_Entity) +{ + cPacketizer Pkt(*this, 0x16); // Entity Look packet + Pkt.WriteInt(a_Entity.GetUniqueID()); + Pkt.WriteByteAngle(a_Entity.GetYaw()); + Pkt.WriteByteAngle(a_Entity.GetPitch()); +} + + + + + +void cProtocol172::SendEntityMetadata(const cEntity & a_Entity) +{ + /* + // TODO + cPacketizer Pkt(*this, 0x1c); // Entity Metadata packet + Pkt.WriteInt(a_Entity.GetUniqueID()); + Pkt.WriteEntityMetadata(a_Entity); + */ +} + + + + + +void cProtocol172::SendEntityProperties(const cEntity & a_Entity) +{ + /* + cPacketizer Pkt(*this, 0x20); // Entity Properties packet + Pkt.WriteInt(a_Entity.GetUniqueID()); + // TODO + */ +} + + + + + +void cProtocol172::SendEntityRelMove(const cEntity & a_Entity, char a_RelX, char a_RelY, char a_RelZ) +{ + cPacketizer Pkt(*this, 0x15); // Entity Relative Move packet + Pkt.WriteInt(a_Entity.GetUniqueID()); + Pkt.WriteByte(a_RelX); + Pkt.WriteByte(a_RelY); + Pkt.WriteByte(a_RelZ); +} + + + + + +void cProtocol172::SendEntityRelMoveLook(const cEntity & a_Entity, char a_RelX, char a_RelY, char a_RelZ) +{ + cPacketizer Pkt(*this, 0x17); // Entity Look And Relative Move packet + Pkt.WriteInt(a_Entity.GetUniqueID()); + Pkt.WriteByte(a_RelX); + Pkt.WriteByte(a_RelY); + Pkt.WriteByte(a_RelZ); + Pkt.WriteByteAngle(a_Entity.GetYaw()); + Pkt.WriteByteAngle(a_Entity.GetPitch()); +} + + + + + +void cProtocol172::SendEntityStatus(const cEntity & a_Entity, char a_Status) +{ + cPacketizer Pkt(*this, 0x1a); // Entity Status packet + Pkt.WriteInt(a_Entity.GetUniqueID()); + Pkt.WriteChar(a_Status); +} + + + + + +void cProtocol172::SendEntityVelocity(const cEntity & a_Entity) +{ + cPacketizer Pkt(*this, 0x12); // Entity Velocity packet + Pkt.WriteInt(a_Entity.GetUniqueID()); + // 400 = 8000 / 20 ... Conversion from our speed in m/s to 8000 m/tick + Pkt.WriteShort((short)(a_Entity.GetSpeedX() * 400)); + Pkt.WriteShort((short)(a_Entity.GetSpeedY() * 400)); + Pkt.WriteShort((short)(a_Entity.GetSpeedZ() * 400)); +} + + + + + +void cProtocol172::SendExplosion(double a_BlockX, double a_BlockY, double a_BlockZ, float a_Radius, const cVector3iArray & a_BlocksAffected, const Vector3d & a_PlayerMotion) +{ + cPacketizer Pkt(*this, 0x27); // Explosion packet + Pkt.WriteFloat((float)a_BlockX); + Pkt.WriteFloat((float)a_BlockY); + Pkt.WriteFloat((float)a_BlockZ); + Pkt.WriteFloat((float)a_Radius); + Pkt.WriteInt(a_BlocksAffected.size()); + for (cVector3iArray::const_iterator itr = a_BlocksAffected.begin(), end = a_BlocksAffected.end(); itr != end; ++itr) + { + Pkt.WriteChar((char)itr->x); + Pkt.WriteChar((char)itr->y); + Pkt.WriteChar((char)itr->z); + } // for itr - a_BlockAffected[] + Pkt.WriteFloat((float)a_PlayerMotion.x); + Pkt.WriteFloat((float)a_PlayerMotion.y); + Pkt.WriteFloat((float)a_PlayerMotion.z); +} + + + + + +void cProtocol172::SendGameMode(eGameMode a_GameMode) +{ + cPacketizer Pkt(*this, 0x2b); // Change Game State packet + Pkt.WriteByte(3); // Reason: Change game mode + Pkt.WriteFloat((float)a_GameMode); +} + + + + + +void cProtocol172::SendHealth(void) +{ + cPacketizer Pkt(*this, 0x06); // Update Health packet + Pkt.WriteFloat((float)m_Client->GetPlayer()->GetHealth()); + Pkt.WriteShort(m_Client->GetPlayer()->GetFoodLevel()); + Pkt.WriteFloat((float)m_Client->GetPlayer()->GetFoodSaturationLevel()); +} + + + + + +void cProtocol172::SendInventorySlot(char a_WindowID, short a_SlotNum, const cItem & a_Item) +{ + cPacketizer Pkt(*this, 0x2f); // Set Slot packet + Pkt.WriteChar(a_WindowID); + Pkt.WriteShort(a_SlotNum); + Pkt.WriteItem(a_Item); +} + + + + + +void cProtocol172::SendKeepAlive(int a_PingID) +{ + cPacketizer Pkt(*this, 0x00); // Keep Alive packet + Pkt.WriteInt(a_PingID); +} + + + + + +void cProtocol172::SendLogin(const cPlayer & a_Player, const cWorld & a_World) +{ + // Send the Join Game packet: + { + cPacketizer Pkt(*this, 0x01); // Join Game packet + Pkt.WriteInt(a_Player.GetUniqueID()); + Pkt.WriteByte((Byte)a_Player.GetEffectiveGameMode()); + Pkt.WriteChar((char)a_World.GetDimension()); + Pkt.WriteByte(2); // TODO: Difficulty (set to Normal) + Pkt.WriteByte(cRoot::Get()->GetServer()->GetMaxPlayers()); + Pkt.WriteString("default"); // Level type - wtf? + } + + // Send the spawn position: + { + cPacketizer Pkt(*this, 0x05); // Spawn Position packet + Pkt.WriteInt((int)a_World.GetSpawnX()); + Pkt.WriteInt((int)a_World.GetSpawnY()); + Pkt.WriteInt((int)a_World.GetSpawnZ()); + } + + // Send player abilities: + SendPlayerAbilities(); +} + + + + + +void cProtocol172::SendPickupSpawn(const cPickup & a_Pickup) +{ + { + cPacketizer Pkt(*this, 0x0e); // Spawn Object packet + Pkt.WriteInt(a_Pickup.GetUniqueID()); + Pkt.WriteByte(2); // Type = Pickup + Pkt.WriteFPInt(a_Pickup.GetPosX()); + Pkt.WriteFPInt(a_Pickup.GetPosY()); + Pkt.WriteFPInt(a_Pickup.GetPosZ()); + Pkt.WriteByteAngle(a_Pickup.GetYaw()); + Pkt.WriteByteAngle(a_Pickup.GetPitch()); + Pkt.WriteInt(0); // No object data + } + { + cPacketizer Pkt(*this, 0x1c); // Entity Metadata packet + Pkt.WriteInt(a_Pickup.GetUniqueID()); + Pkt.WriteByte((0x05 << 5) | 10); // Slot type + index 10 + Pkt.WriteItem(a_Pickup.GetItem()); + Pkt.WriteByte(0x7f); // End of metadata + } +} + + + + + +void cProtocol172::SendPlayerAbilities(void) +{ + cPacketizer Pkt(*this, 0x39); // Player Abilities packet + Byte Flags = 0; + if (m_Client->GetPlayer()->IsGameModeCreative()) + { + Flags |= 0x01; + } + // TODO: Other flags (god mode, flying, can fly + Pkt.WriteByte(Flags); + // TODO: Pkt.WriteFloat(m_Client->GetPlayer()->GetMaxFlyingSpeed()); + Pkt.WriteFloat(0.05f); + Pkt.WriteFloat((float)m_Client->GetPlayer()->GetMaxSpeed()); +} + + + + + +void cProtocol172::SendPlayerAnimation(const cPlayer & a_Player, char a_Animation) +{ + cPacketizer Pkt(*this, 0x0b); // Animation packet + Pkt.WriteInt(a_Player.GetUniqueID()); + Pkt.WriteChar(a_Animation); +} + + + + + +void cProtocol172::SendPlayerListItem(const cPlayer & a_Player, bool a_IsOnline) +{ + cPacketizer Pkt(*this, 0x38); // Playerlist Item packet + Pkt.WriteString(a_Player.GetName()); + Pkt.WriteBool(a_IsOnline); + Pkt.WriteShort(a_Player.GetClientHandle()->GetPing()); +} + + + + + +void cProtocol172::SendPlayerMaxSpeed(void) +{ + SendPlayerAbilities(); +} + + + + + +void cProtocol172::SendPlayerMoveLook(void) +{ + cPacketizer Pkt(*this, 0x08); // Player Position And Look packet + Pkt.WriteDouble(m_Client->GetPlayer()->GetPosX()); + Pkt.WriteDouble(m_Client->GetPlayer()->GetPosY()); + Pkt.WriteDouble(m_Client->GetPlayer()->GetPosZ()); + Pkt.WriteFloat((float)m_Client->GetPlayer()->GetYaw()); + Pkt.WriteFloat((float)m_Client->GetPlayer()->GetPitch()); + Pkt.WriteBool(m_Client->GetPlayer()->IsOnGround()); +} + + + + + +void cProtocol172::SendPlayerPosition(void) +{ + // There is no dedicated packet for this, send the whole thing: + SendPlayerMoveLook(); +} + + + + + +void cProtocol172::SendPlayerSpawn(const cPlayer & a_Player) +{ + // Called to spawn another player for the client + cPacketizer Pkt(*this, 0x0c); // Spawn Player packet + Pkt.WriteInt(a_Player.GetUniqueID()); + Pkt.WriteString(Printf("%d", a_Player.GetUniqueID())); // TODO: Proper UUID + Pkt.WriteString(a_Player.GetName()); + Pkt.WriteFPInt(a_Player.GetPosX()); + Pkt.WriteFPInt(a_Player.GetPosY()); + Pkt.WriteFPInt(a_Player.GetPosZ()); + Pkt.WriteByteAngle(a_Player.GetYaw()); + Pkt.WriteByteAngle(a_Player.GetPitch()); + short ItemType = a_Player.GetEquippedItem().IsEmpty() ? 0 : a_Player.GetEquippedItem().m_ItemType; + Pkt.WriteShort(ItemType); + Pkt.WriteByte((3 << 5) | 6); // Metadata: float + index 6 + Pkt.WriteFloat((float)a_Player.GetHealth()); + Pkt.WriteByte(0x7f); // Metadata: end +} + + + + + +void cProtocol172::SendRespawn(void) +{ + cPacketizer Pkt(*this, 0x07); // Respawn packet + Pkt.WriteInt(m_Client->GetPlayer()->GetWorld()->GetDimension()); + Pkt.WriteByte(2); // TODO: Difficulty (set to Normal) + Pkt.WriteByte((Byte)m_Client->GetPlayer()->GetEffectiveGameMode()); + Pkt.WriteString("default"); +} + + + + + +void cProtocol172::SendSoundEffect(const AString & a_SoundName, int a_SrcX, int a_SrcY, int a_SrcZ, float a_Volume, float a_Pitch) // a_Src coords are Block * 8 +{ + cPacketizer Pkt(*this, 0x29); // Sound Effect packet + Pkt.WriteString(a_SoundName); + Pkt.WriteInt(a_SrcX); + Pkt.WriteInt(a_SrcY); + Pkt.WriteInt(a_SrcZ); + Pkt.WriteFloat(a_Volume); + Pkt.WriteByte((Byte)(a_Pitch * 63)); +} + + + + + +void cProtocol172::SendSoundParticleEffect(int a_EffectID, int a_SrcX, int a_SrcY, int a_SrcZ, int a_Data) +{ + cPacketizer Pkt(*this, 0x28); // Effect packet + Pkt.WriteInt(a_EffectID); + Pkt.WriteInt(a_SrcX); + Pkt.WriteInt(a_SrcY); + Pkt.WriteInt(a_SrcZ); + Pkt.WriteInt(a_Data); + Pkt.WriteBool(false); +} + + + + + +void cProtocol172::SendSpawnFallingBlock(const cFallingBlock & a_FallingBlock) +{ + cPacketizer Pkt(*this, 0x0e); // Spawn Object packet + Pkt.WriteInt(a_FallingBlock.GetUniqueID()); + Pkt.WriteByte(70); // Falling block + Pkt.WriteFPInt(a_FallingBlock.GetPosX()); + Pkt.WriteFPInt(a_FallingBlock.GetPosY()); + Pkt.WriteFPInt(a_FallingBlock.GetPosZ()); + Pkt.WriteByteAngle(a_FallingBlock.GetYaw()); + Pkt.WriteByteAngle(a_FallingBlock.GetPitch()); + Pkt.WriteInt(((int)a_FallingBlock.GetBlockType()) | (((int)a_FallingBlock.GetBlockMeta()) << 12)); + Pkt.WriteShort((short)(a_FallingBlock.GetSpeedX() * 400)); + Pkt.WriteShort((short)(a_FallingBlock.GetSpeedY() * 400)); + Pkt.WriteShort((short)(a_FallingBlock.GetSpeedZ() * 400)); +} + + + + + +void cProtocol172::SendSpawnMob(const cMonster & a_Mob) +{ + cPacketizer Pkt(*this, 0x0f); // Spawn Mob packet + Pkt.WriteInt(a_Mob.GetUniqueID()); + Pkt.WriteByte((Byte)a_Mob.GetMobType()); + Pkt.WriteFPInt(a_Mob.GetPosX()); + Pkt.WriteFPInt(a_Mob.GetPosY()); + Pkt.WriteFPInt(a_Mob.GetPosZ()); + Pkt.WriteByteAngle(a_Mob.GetPitch()); + Pkt.WriteByteAngle(a_Mob.GetHeadYaw()); + Pkt.WriteByteAngle(a_Mob.GetYaw()); + Pkt.WriteShort((short)(a_Mob.GetSpeedX() * 400)); + Pkt.WriteShort((short)(a_Mob.GetSpeedY() * 400)); + Pkt.WriteShort((short)(a_Mob.GetSpeedZ() * 400)); + Pkt.WriteEntityMetadata(a_Mob); + Pkt.WriteByte(0x7f); // Metadata terminator +} + + + + + +void cProtocol172::SendSpawnObject(const cEntity & a_Entity, char a_ObjectType, int a_ObjectData, Byte a_Yaw, Byte a_Pitch) +{ + cPacketizer Pkt(*this, 0xe); // Spawn Object packet + Pkt.WriteInt(a_Entity.GetUniqueID()); + Pkt.WriteByte(a_ObjectType); + Pkt.WriteFPInt(a_Entity.GetPosX()); + Pkt.WriteFPInt(a_Entity.GetPosY()); + Pkt.WriteFPInt(a_Entity.GetPosZ()); + Pkt.WriteByteAngle(a_Entity.GetYaw()); + Pkt.WriteByteAngle(a_Entity.GetPitch()); + Pkt.WriteInt(a_ObjectData); + if (a_ObjectData != 0) + { + Pkt.WriteShort((short)(a_Entity.GetSpeedX() * 400)); + Pkt.WriteShort((short)(a_Entity.GetSpeedY() * 400)); + Pkt.WriteShort((short)(a_Entity.GetSpeedZ() * 400)); + } +} + + + + + +void cProtocol172::SendSpawnVehicle(const cEntity & a_Vehicle, char a_VehicleType, char a_VehicleSubType) +{ + cPacketizer Pkt(*this, 0xe); // Spawn Object packet + Pkt.WriteInt(a_Vehicle.GetUniqueID()); + Pkt.WriteByte(a_VehicleType); + Pkt.WriteFPInt(a_Vehicle.GetPosX()); + Pkt.WriteFPInt(a_Vehicle.GetPosY()); + Pkt.WriteFPInt(a_Vehicle.GetPosZ()); + Pkt.WriteByteAngle(a_Vehicle.GetYaw()); + Pkt.WriteByteAngle(a_Vehicle.GetPitch()); + Pkt.WriteInt(a_VehicleSubType); + if (a_VehicleSubType != 0) + { + Pkt.WriteShort((short)(a_Vehicle.GetSpeedX() * 400)); + Pkt.WriteShort((short)(a_Vehicle.GetSpeedY() * 400)); + Pkt.WriteShort((short)(a_Vehicle.GetSpeedZ() * 400)); + } +} + + + + + +void cProtocol172::SendTabCompletionResults(const AStringVector & a_Results) +{ + AString Results; + Results.reserve(500); // Make a moderate reservation to avoid excessive reallocations + for (AStringVector::const_iterator itr = a_Results.begin(), end = a_Results.end(); itr != end; ++itr) + { + Results.append(*itr); + Results.push_back(0); + } + + cPacketizer Pkt(*this, 0x3a); // Tab-Complete packet + Pkt.WriteVarInt(a_Results.size()); + Pkt.WriteString(Results); +} + + + + + +void cProtocol172::SendTeleportEntity(const cEntity & a_Entity) +{ + cPacketizer Pkt(*this, 0x18); + Pkt.WriteInt(a_Entity.GetUniqueID()); + Pkt.WriteFPInt(a_Entity.GetPosX()); + Pkt.WriteFPInt(a_Entity.GetPosY()); + Pkt.WriteFPInt(a_Entity.GetPosZ()); + Pkt.WriteByteAngle(a_Entity.GetYaw()); + Pkt.WriteByteAngle(a_Entity.GetPitch()); +} + + + + + +void cProtocol172::SendThunderbolt(int a_BlockX, int a_BlockY, int a_BlockZ) +{ + cPacketizer Pkt(*this, 0x2c); // Spawn Global Entity packet + Pkt.WriteVarInt(0); // EntityID = 0, always + Pkt.WriteByte(1); // Type = Thunderbolt + Pkt.WriteFPInt(a_BlockX); + Pkt.WriteFPInt(a_BlockY); + Pkt.WriteFPInt(a_BlockZ); +} + + + + + +void cProtocol172::SendTimeUpdate(Int64 a_WorldAge, Int64 a_TimeOfDay) +{ + cPacketizer Pkt(*this, 0x03); + Pkt.WriteInt64(a_WorldAge); + Pkt.WriteInt64(a_TimeOfDay); +} + + + + + +void cProtocol172::SendUnloadChunk(int a_ChunkX, int a_ChunkZ) +{ + // TODO +} + + + + + +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) +{ + // TODO +} + + + + + +void cProtocol172::SendUseBed(const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ ) +{ + // TODO +} + + + + + +void cProtocol172::SendWeather(eWeather a_Weather) +{ + // TODO +} + + + + + +void cProtocol172::SendWholeInventory(const cInventory & a_Inventory) +{ + // TODO +} + + + + + +void cProtocol172::SendWholeInventory(const cWindow & a_Window) +{ + // TODO +} + + + + + +void cProtocol172::SendWindowClose(const cWindow & a_Window) +{ + // TODO +} + + + + + +void cProtocol172::SendWindowOpen(char a_WindowID, char a_WindowType, const AString & a_WindowTitle, char a_NumSlots) +{ + // TODO +} + + + + + +void cProtocol172::SendWindowProperty(const cWindow & a_Window, short a_Property, short a_Value) +{ + // TODO +} + + + + + +void cProtocol172::AddReceivedData(const char * a_Data, int a_Size) +{ + if (!m_ReceivedData.Write(a_Data, a_Size)) + { + // Too much data in the incoming queue, report to caller: + m_Client->PacketBufferFull(); + return; + } + + // Handle all complete packets: + while (true) + { + UInt32 PacketLen; + if (!m_ReceivedData.ReadVarInt(PacketLen)) + { + // Not enough data + return; + } + if (!m_ReceivedData.CanReadBytes(PacketLen)) + { + // The full packet hasn't been received yet + return; + } + UInt32 PacketType; + UInt32 Mark1 = m_ReceivedData.GetReadableSpace(); + if (!m_ReceivedData.ReadVarInt(PacketType)) + { + // Not enough data + return; + } + + UInt32 NumBytesRead = Mark1 - m_ReceivedData.GetReadableSpace(); + HandlePacket(PacketType, PacketLen - NumBytesRead); + + if (Mark1 - m_ReceivedData.GetReadableSpace() > PacketLen) + { + // Read more than packet length, report as error + m_Client->PacketError(PacketType); + } + + // Go to packet end in any case: + m_ReceivedData.ResetRead(); + m_ReceivedData.ReadVarInt(PacketType); + m_ReceivedData.SkipRead(PacketLen); + m_ReceivedData.CommitRead(); + } // while (true) +} + + + + +void cProtocol172::HandlePacket(UInt32 a_PacketType, UInt32 a_RemainingBytes) +{ + switch (m_State) + { + case 1: + { + // Status + switch (a_PacketType) + { + case 0x00: HandlePacketStatusRequest(a_RemainingBytes); return; + case 0x01: HandlePacketStatusPing (a_RemainingBytes); return; + } + break; + } + + case 2: + { + // Login + switch (a_PacketType) + { + case 0x00: HandlePacketLoginStart(a_RemainingBytes); return; + case 0x01: HandlePacketLoginEncryptionResponse(a_RemainingBytes); return; + } + break; + } + + case 3: + { + // Game + switch (a_PacketType) + { + case 0x00: HandlePacketKeepAlive (a_RemainingBytes); return; + case 0x01: HandlePacketChatMessage (a_RemainingBytes); return; + case 0x02: HandlePacketUseEntity (a_RemainingBytes); return; + case 0x03: HandlePacketPlayer (a_RemainingBytes); return; + case 0x04: HandlePacketPlayerPos (a_RemainingBytes); return; + case 0x05: HandlePacketPlayerLook (a_RemainingBytes); return; + case 0x06: HandlePacketPlayerPosLook (a_RemainingBytes); return; + case 0x07: HandlePacketBlockDig (a_RemainingBytes); return; + case 0x08: HandlePacketBlockPlace (a_RemainingBytes); return; + case 0x09: HandlePacketSlotSelect (a_RemainingBytes); return; + case 0x0a: HandlePacketAnimation (a_RemainingBytes); return; + case 0x0b: HandlePacketEntityAction (a_RemainingBytes); return; + case 0x0c: HandlePacketSteerVehicle (a_RemainingBytes); return; + case 0x0d: HandlePacketWindowClose (a_RemainingBytes); return; + case 0x0e: HandlePacketWindowClick (a_RemainingBytes); return; + case 0x0f: // Confirm transaction - not used in MCS + case 0x10: HandlePacketCreativeInventoryAction(a_RemainingBytes); return; + case 0x12: HandlePacketUpdateSign (a_RemainingBytes); return; + case 0x13: HandlePacketPlayerAbilities (a_RemainingBytes); return; + case 0x14: HandlePacketTabComplete (a_RemainingBytes); return; + case 0x15: HandlePacketClientSettings (a_RemainingBytes); return; + case 0x16: HandlePacketClientStatus (a_RemainingBytes); return; + case 0x17: HandlePacketPluginMessage (a_RemainingBytes); return; + } + break; + } + } // switch (m_State) + + // Unknown packet type, report to the client: + m_Client->PacketUnknown(a_PacketType); + m_ReceivedData.SkipRead(a_RemainingBytes); + m_ReceivedData.CommitRead(); +} + + + + + +void cProtocol172::HandlePacketStatusPing(UInt32 a_RemainingBytes) +{ + ASSERT(a_RemainingBytes == 8); + if (a_RemainingBytes != 8) + { + m_Client->PacketError(0x01); + m_ReceivedData.SkipRead(a_RemainingBytes); + m_ReceivedData.CommitRead(); + return; + } + Int64 Timestamp; + m_ReceivedData.ReadBEInt64(Timestamp); + m_ReceivedData.CommitRead(); + + cPacketizer Pkt(*this, 0x01); // Ping packet + Pkt.WriteInt64(Timestamp); +} + + + + + +void cProtocol172::HandlePacketStatusRequest(UInt32 a_RemainingBytes) +{ + // No more bytes in this packet + ASSERT(a_RemainingBytes == 0); + m_ReceivedData.CommitRead(); + + // Send the response: + AString Response = "{\"version\":{\"name\":\"1.7.2\",\"protocol\":4},\"players\":{"; + AppendPrintf(Response, "\"max\":%u,\"online\":%u,\"sample\":[]},", + cRoot::Get()->GetServer()->GetMaxPlayers(), + cRoot::Get()->GetServer()->GetNumPlayers() + ); + AppendPrintf(Response, "\"description\":{\"text\":\"%s\"}", + cRoot::Get()->GetServer()->GetDescription().c_str() + ); + Response.append("}"); + + cPacketizer Pkt(*this, 0x00); // Response packet + Pkt.WriteString(Response); +} + + + + + +void cProtocol172::HandlePacketLoginEncryptionResponse(UInt32 a_RemainingBytes) +{ + // TODO: Add protocol encryption +} + + + + + +void cProtocol172::HandlePacketLoginStart(UInt32 a_RemainingBytes) +{ + AString Username; + m_ReceivedData.ReadVarUTF8String(Username); + + // TODO: Protocol encryption should be set up here if not localhost / auth + + // Send login success: + { + cPacketizer Pkt(*this, 0x02); // Login success packet + Pkt.WriteString(Printf("%d", m_Client->GetUniqueID())); // TODO: proper UUID + Pkt.WriteString(Username); + } + + m_State = 3; // State = Game + m_Client->HandleLogin(4, Username); +} + + + + + +void cProtocol172::HandlePacketAnimation(UInt32 a_RemainingBytes) +{ + HANDLE_PACKET_READ(ReadBEInt, int, EntityID); + HANDLE_PACKET_READ(ReadByte, Byte, Animation); + m_Client->HandleAnimation(Animation); +} + + + + + +void cProtocol172::HandlePacketBlockDig(UInt32 a_RemainingBytes) +{ + HANDLE_PACKET_READ(ReadByte, Byte, Status); + HANDLE_PACKET_READ(ReadBEInt, int, BlockX); + HANDLE_PACKET_READ(ReadByte, Byte, BlockY); + HANDLE_PACKET_READ(ReadBEInt, int, BlockZ); + HANDLE_PACKET_READ(ReadByte, Byte, Face); + m_Client->HandleLeftClick(BlockX, BlockY, BlockZ, Face, Status); +} + + + + + +void cProtocol172::HandlePacketBlockPlace(UInt32 a_RemainingBytes) +{ + HANDLE_PACKET_READ(ReadBEInt, int, BlockX); + HANDLE_PACKET_READ(ReadByte, Byte, BlockY); + HANDLE_PACKET_READ(ReadBEInt, int, BlockZ); + HANDLE_PACKET_READ(ReadByte, Byte, Face); + HANDLE_PACKET_READ(ReadByte, Byte, CursorX); + HANDLE_PACKET_READ(ReadByte, Byte, CursorY); + HANDLE_PACKET_READ(ReadByte, Byte, CursorZ); + m_Client->HandleRightClick(BlockX, BlockY, BlockZ, Face, CursorX, CursorY, CursorZ, m_Client->GetPlayer()->GetEquippedItem()); +} + + + + + +void cProtocol172::HandlePacketChatMessage(UInt32 a_RemainingBytes) +{ + HANDLE_PACKET_READ(ReadVarUTF8String, AString, Message); + m_Client->HandleChat(Message); +} + + + + + +void cProtocol172::HandlePacketClientSettings(UInt32 a_RemainingBytes) +{ + HANDLE_PACKET_READ(ReadVarUTF8String, AString, Locale); + HANDLE_PACKET_READ(ReadByte, Byte, ViewDistance); + HANDLE_PACKET_READ(ReadByte, Byte, ChatFlags); + HANDLE_PACKET_READ(ReadByte, Byte, Unused); + HANDLE_PACKET_READ(ReadByte, Byte, Difficulty); + HANDLE_PACKET_READ(ReadByte, Byte, ShowCape); + // TODO: handle in m_Client +} + + + + + +void cProtocol172::HandlePacketClientStatus(UInt32 a_RemainingBytes) +{ + HANDLE_PACKET_READ(ReadByte, Byte, ActionID); + switch (ActionID) + { + case 0: + { + // Respawn + m_Client->HandleRespawn(); + break; + } + case 1: + { + // Request stats + // TODO + break; + } + case 2: + { + // Open Inventory achievement + // TODO + break; + } + } +} + + + + + +void cProtocol172::HandlePacketCreativeInventoryAction(UInt32 a_RemainingBytes) +{ + // TODO +} + + + + + +void cProtocol172::HandlePacketEntityAction(UInt32 a_RemainingBytes) +{ + // TODO +} + + + + + +void cProtocol172::HandlePacketKeepAlive(UInt32 a_RemainingBytes) +{ + // TODO +} + + + + + +void cProtocol172::HandlePacketPlayer(UInt32 a_RemainingBytes) +{ + // TODO +} + + + + + +void cProtocol172::HandlePacketPlayerAbilities(UInt32 a_RemainingBytes) +{ + // TODO +} + + + + + +void cProtocol172::HandlePacketPlayerLook(UInt32 a_RemainingBytes) +{ + // TODO +} + + + + + +void cProtocol172::HandlePacketPlayerPos(UInt32 a_RemainingBytes) +{ + // TODO +} + + + + + +void cProtocol172::HandlePacketPlayerPosLook(UInt32 a_RemainingBytes) +{ + // TODO +} + + + + + +void cProtocol172::HandlePacketPluginMessage(UInt32 a_RemainingBytes) +{ + // TODO +} + + + + + +void cProtocol172::HandlePacketSlotSelect(UInt32 a_RemainingBytes) +{ + // TODO +} + + + + + +void cProtocol172::HandlePacketSteerVehicle(UInt32 a_RemainingBytes) +{ + // TODO +} + + + + + +void cProtocol172::HandlePacketTabComplete(UInt32 a_RemainingBytes) +{ + // TODO +} + + + + + +void cProtocol172::HandlePacketUpdateSign(UInt32 a_RemainingBytes) +{ + // TODO +} + + + + + +void cProtocol172::HandlePacketUseEntity(UInt32 a_RemainingBytes) +{ + // TODO +} + + + + + +void cProtocol172::HandlePacketWindowClick(UInt32 a_RemainingBytes) +{ + // TODO +} + + + + + +void cProtocol172::HandlePacketWindowClose(UInt32 a_RemainingBytes) +{ + // TODO +} + + + + + +void cProtocol172::WritePacket(cByteBuffer & a_Packet) +{ + cCSLock Lock(m_CSPacket); + AString Pkt; + a_Packet.ReadAll(Pkt); + WriteVarInt(Pkt.size()); + SendData(Pkt.data(), Pkt.size()); + Flush(); +} + + + + + +void cProtocol172::SendData(const char * a_Data, int a_Size) +{ + if (m_IsEncrypted) + { + byte Encrypted[8192]; // Larger buffer, we may be sending lots of data (chunks) + while (a_Size > 0) + { + int NumBytes = (a_Size > sizeof(Encrypted)) ? sizeof(Encrypted) : a_Size; + m_Encryptor.ProcessData(Encrypted, (byte *)a_Data, NumBytes); + m_Client->SendData((const char *)Encrypted, NumBytes); + a_Size -= NumBytes; + a_Data += NumBytes; + } + } + else + { + m_Client->SendData(a_Data, a_Size); + } +} + + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cProtocol172::cPacketizer: + +cProtocol172::cPacketizer::~cPacketizer() +{ + AString DataToSend; + + // Send the packet length + UInt32 PacketLen = m_Out.GetUsedSpace(); + m_Protocol.m_OutPacketLenBuffer.WriteVarInt(PacketLen); + m_Protocol.m_OutPacketLenBuffer.ReadAll(DataToSend); + m_Protocol.SendData(DataToSend.data(), DataToSend.size()); + m_Protocol.m_OutPacketLenBuffer.CommitRead(); + + // Send the packet data: + m_Out.ReadAll(DataToSend); + m_Protocol.SendData(DataToSend.data(), DataToSend.size()); + m_Out.CommitRead(); +} + + + + + +void cProtocol172::cPacketizer::WriteItem(const cItem & a_Item) +{ + short ItemType = a_Item.m_ItemType; + ASSERT(ItemType >= -1); // Check validity of packets in debug runtime + if (ItemType <= 0) + { + // Fix, to make sure no invalid values are sent. + ItemType = -1; + } + + if (a_Item.IsEmpty()) + { + WriteShort(-1); + return; + } + + WriteShort(ItemType); + WriteByte (a_Item.m_ItemCount); + WriteShort(a_Item.m_ItemDamage); + + if (a_Item.m_Enchantments.IsEmpty()) + { + WriteShort(-1); + return; + } + + // Send the enchantments: + cFastNBTWriter Writer; + const char * TagName = (a_Item.m_ItemType == E_ITEM_BOOK) ? "StoredEnchantments" : "ench"; + a_Item.m_Enchantments.WriteToNBTCompound(Writer, TagName); + 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) +{ + WriteByte((char)(255 * a_Angle / 360)); +} + + + + + +void cProtocol172::cPacketizer::WriteFPInt(double a_Value) +{ + int Value = (int)(a_Value * 32); + WriteInt(Value); +} + + + + + +void cProtocol172::cPacketizer::WriteEntityMetadata(const cEntity & a_Entity) +{ + // Common metadata: + Byte Flags = 0; + if (a_Entity.IsOnFire()) + { + Flags |= 0x01; + } + if (a_Entity.IsCrouched()) + { + Flags |= 0x02; + } + if (a_Entity.IsSprinting()) + { + Flags |= 0x08; + } + if (a_Entity.IsRclking()) + { + Flags |= 0x10; + } + if (a_Entity.IsInvisible()) + { + Flags |= 0x20; + } + WriteByte(0); // Byte(0) + index 0 + WriteByte(Flags); + + switch (a_Entity.GetEntityType()) + { + case cEntity::etPlayer: break; // TODO? + case cEntity::etPickup: + { + WriteByte((5 << 5) | 10); // Slot(5) + index 10 + WriteItem(((const cPickup &)a_Entity).GetItem()); + break; + } + case cEntity::etMonster: + { + WriteMobMetadata((const cMonster &)a_Entity); + break; + } + // TODO: Other types + } +} + + + + + +void cProtocol172::cPacketizer::WriteMobMetadata(const cMonster & a_Mob) +{ + // TODO +} + + + + diff --git a/source/Protocol/Protocol17x.h b/source/Protocol/Protocol17x.h new file mode 100644 index 000000000..e3a51b844 --- /dev/null +++ b/source/Protocol/Protocol17x.h @@ -0,0 +1,252 @@ + +// Protocol17x.h + +/* +Declares the 1.7.x protocol classes: + - cProtocol172 + - release 1.7.2 protocol (#4) +(others may be added later in the future for the 1.7 release series) +*/ + + + + + +#pragma once + +#include "Protocol.h" +#include "../ByteBuffer.h" +#include "../../CryptoPP/modes.h" +#include "../../CryptoPP/aes.h" + + + + + +class cProtocol172 : + public cProtocol // TODO +{ + typedef cProtocol super; // TODO + +public: + + cProtocol172(cClientHandle * a_Client, const AString & a_ServerAddress, UInt16 a_ServerPort, UInt32 a_State); + + /// Called when client sends some data: + virtual void DataReceived(const char * a_Data, int a_Size) override; + + /// Sending stuff to clients (alphabetically sorted): + virtual void SendAttachEntity (const cEntity & a_Entity, const cEntity * a_Vehicle) override; + virtual void SendBlockAction (int a_BlockX, int a_BlockY, int a_BlockZ, char a_Byte1, char a_Byte2, BLOCKTYPE a_BlockType) override; + virtual void SendBlockBreakAnim (int a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage) override; + virtual void SendBlockChange (int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) override; + virtual void SendBlockChanges (int a_ChunkX, int a_ChunkZ, const sSetBlockVector & a_Changes) override; + virtual void SendChat (const AString & a_Message) override; + virtual void SendChunkData (int a_ChunkX, int a_ChunkZ, cChunkDataSerializer & a_Serializer) override; + virtual void SendCollectPickup (const cPickup & a_Pickup, const cPlayer & a_Player) override; + virtual void SendDestroyEntity (const cEntity & a_Entity) override; + virtual void SendDisconnect (const AString & a_Reason) override; + virtual void SendEditSign (int a_BlockX, int a_BlockY, int a_BlockZ) override; ///< Request the client to open up the sign editor for the sign (1.6+) + virtual void SendEntityEquipment (const cEntity & a_Entity, short a_SlotNum, const cItem & a_Item) override; + virtual void SendEntityHeadLook (const cEntity & a_Entity) override; + virtual void SendEntityLook (const cEntity & a_Entity) override; + virtual void SendEntityMetadata (const cEntity & a_Entity) override; + virtual void SendEntityProperties (const cEntity & a_Entity) override; + virtual void SendEntityRelMove (const cEntity & a_Entity, char a_RelX, char a_RelY, char a_RelZ) override; + virtual void SendEntityRelMoveLook (const cEntity & a_Entity, char a_RelX, char a_RelY, char a_RelZ) override; + virtual void SendEntityStatus (const cEntity & a_Entity, char a_Status) override; + virtual void SendEntityVelocity (const cEntity & a_Entity) override; + virtual void SendExplosion (double a_BlockX, double a_BlockY, double a_BlockZ, float a_Radius, const cVector3iArray & a_BlocksAffected, const Vector3d & a_PlayerMotion) override; + virtual void SendGameMode (eGameMode a_GameMode) override; + virtual void SendHealth (void) override; + virtual void SendInventorySlot (char a_WindowID, short a_SlotNum, const cItem & a_Item) override; + virtual void SendKeepAlive (int a_PingID) override; + virtual void SendLogin (const cPlayer & a_Player, const cWorld & a_World) override; + virtual void SendPickupSpawn (const cPickup & a_Pickup) override; + virtual void SendPlayerAbilities (void) override; + virtual void SendPlayerAnimation (const cPlayer & a_Player, char a_Animation) override; + virtual void SendPlayerListItem (const cPlayer & a_Player, bool a_IsOnline) override; + virtual void SendPlayerMaxSpeed (void) override; + virtual void SendPlayerMoveLook (void) override; + virtual void SendPlayerPosition (void) override; + virtual void SendPlayerSpawn (const cPlayer & a_Player) override; + virtual void SendRespawn (void) override; + virtual void SendSoundEffect (const AString & a_SoundName, int a_SrcX, int a_SrcY, int a_SrcZ, float a_Volume, float a_Pitch) override; // a_Src coords are Block * 8 + virtual void SendSoundParticleEffect (int a_EffectID, int a_SrcX, int a_SrcY, int a_SrcZ, int a_Data) override; + virtual void SendSpawnFallingBlock (const cFallingBlock & a_FallingBlock) override; + virtual void SendSpawnMob (const cMonster & a_Mob) override; + virtual void SendSpawnObject (const cEntity & a_Entity, char a_ObjectType, int a_ObjectData, Byte a_Yaw, Byte a_Pitch) override; + virtual void SendSpawnVehicle (const cEntity & a_Vehicle, char a_VehicleType, char a_VehicleSubType) override; + virtual void SendTabCompletionResults(const AStringVector & a_Results) override; + virtual void SendTeleportEntity (const cEntity & a_Entity) override; + 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 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; + virtual void SendWholeInventory (const cInventory & a_Inventory) override; + virtual void SendWholeInventory (const cWindow & a_Window) override; + virtual void SendWindowClose (const cWindow & a_Window) override; + virtual void SendWindowOpen (char a_WindowID, char a_WindowType, const AString & a_WindowTitle, char a_NumSlots) override; + virtual void SendWindowProperty (const cWindow & a_Window, short a_Property, short a_Value) override; + + virtual AString GetAuthServerID(void) override { return m_AuthServerID; } + +protected: + + /// Composes individual packets in the protocol's m_OutPacketBuffer; sends them upon being destructed + class cPacketizer + { + public: + cPacketizer(cProtocol172 & a_Protocol, UInt32 a_PacketType) : + m_Protocol(a_Protocol), + m_Out(a_Protocol.m_OutPacketBuffer), + m_Lock(a_Protocol.m_CSPacket) + { + m_Out.WriteVarInt(a_PacketType); + } + + ~cPacketizer(); + + void WriteBool(bool a_Value) + { + m_Out.WriteBool(a_Value); + } + + void WriteByte(Byte a_Value) + { + m_Out.WriteByte(a_Value); + } + + void WriteChar(char a_Value) + { + m_Out.WriteChar(a_Value); + } + + void WriteShort(short a_Value) + { + m_Out.WriteBEShort(a_Value); + } + + void WriteInt(int a_Value) + { + m_Out.WriteBEInt(a_Value); + } + + void WriteInt64(Int64 a_Value) + { + m_Out.WriteBEInt64(a_Value); + } + + void WriteFloat(float a_Value) + { + m_Out.WriteBEFloat(a_Value); + } + + void WriteDouble(double a_Value) + { + m_Out.WriteBEDouble(a_Value); + } + + void WriteVarInt(UInt32 a_Value) + { + m_Out.WriteVarInt(a_Value); + } + + void WriteString(const AString & a_Value) + { + m_Out.WriteVarUTF8String(a_Value); + } + + void WriteBuf(const char * a_Data, int a_Size) + { + m_Out.Write(a_Data, a_Size); + } + + void WriteItem(const cItem & a_Item); + void WriteByteAngle(double a_Angle); // Writes the specified angle using a single byte + void WriteFPInt(double a_Value); // Writes the double value as a 27:5 fixed-point integer + 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 + + protected: + cProtocol172 & m_Protocol; + cByteBuffer & m_Out; + cCSLock m_Lock; + } ; + + AString m_ServerAddress; + + UInt16 m_ServerPort; + + AString m_AuthServerID; + + /// State of the protocol. 1 = status, 2 = login, 3 = game + UInt32 m_State; + + /// Buffer for the received data + cByteBuffer m_ReceivedData; + + /// Buffer for composing the outgoing packets, through cPacketizer + cByteBuffer m_OutPacketBuffer; + + /// Buffer for composing packet length (so that each cPacketizer instance doesn't allocate a new cPacketBuffer) + cByteBuffer m_OutPacketLenBuffer; + + bool m_IsEncrypted; + CryptoPP::CFB_Mode<CryptoPP::AES>::Decryption m_Decryptor; + CryptoPP::CFB_Mode<CryptoPP::AES>::Encryption m_Encryptor; + + + /// Adds the received (unencrypted) data to m_ReceivedData, parses complete packets + void AddReceivedData(const char * a_Data, int a_Size); + + /// Reads and handles the packet. The packet length and type have already been read. + void HandlePacket(UInt32 a_PacketType, UInt32 a_RemainingBytes); + + // Packet handlers while in the Status state (m_State == 1): + void HandlePacketStatusPing (UInt32 a_RemainingBytes); + void HandlePacketStatusRequest(UInt32 a_RemainingBytes); + + // Packet handlers while in the Login state (m_State == 2): + void HandlePacketLoginEncryptionResponse(UInt32 a_RemainingBytes); + void HandlePacketLoginStart (UInt32 a_RemainingBytes); + + // Packet handlers while in the Game state (m_State == 3): + void HandlePacketAnimation (UInt32 a_RemainingBytes); + void HandlePacketBlockDig (UInt32 a_RemainingBytes); + void HandlePacketBlockPlace (UInt32 a_RemainingBytes); + void HandlePacketChatMessage (UInt32 a_RemainingBytes); + void HandlePacketClientSettings (UInt32 a_RemainingBytes); + void HandlePacketClientStatus (UInt32 a_RemainingBytes); + void HandlePacketCreativeInventoryAction(UInt32 a_RemainingBytes); + void HandlePacketEntityAction (UInt32 a_RemainingBytes); + void HandlePacketKeepAlive (UInt32 a_RemainingBytes); + void HandlePacketPlayer (UInt32 a_RemainingBytes); + void HandlePacketPlayerAbilities (UInt32 a_RemainingBytes); + void HandlePacketPlayerLook (UInt32 a_RemainingBytes); + void HandlePacketPlayerPos (UInt32 a_RemainingBytes); + void HandlePacketPlayerPosLook (UInt32 a_RemainingBytes); + void HandlePacketPluginMessage (UInt32 a_RemainingBytes); + void HandlePacketSlotSelect (UInt32 a_RemainingBytes); + void HandlePacketSteerVehicle (UInt32 a_RemainingBytes); + void HandlePacketTabComplete (UInt32 a_RemainingBytes); + void HandlePacketUpdateSign (UInt32 a_RemainingBytes); + void HandlePacketUseEntity (UInt32 a_RemainingBytes); + void HandlePacketWindowClick (UInt32 a_RemainingBytes); + void HandlePacketWindowClose (UInt32 a_RemainingBytes); + + + /// Writes an entire packet into the output stream. a_Packet is expected to start with the packet type; data length is prepended here. + void WritePacket(cByteBuffer & a_Packet); + + /// Sends the data to the client, encrypting them if needed. + virtual void SendData(const char * a_Data, int a_Size) override; + + void SendCompass(const cWorld & a_World); +} ; + + + + diff --git a/source/Protocol/ProtocolRecognizer.cpp b/source/Protocol/ProtocolRecognizer.cpp index ceada1944..67f924d7e 100644 --- a/source/Protocol/ProtocolRecognizer.cpp +++ b/source/Protocol/ProtocolRecognizer.cpp @@ -12,6 +12,7 @@ #include "Protocol14x.h" #include "Protocol15x.h" #include "Protocol16x.h" +#include "Protocol17x.h" #include "../ClientHandle.h" #include "../Root.h" #include "../Server.h" @@ -384,6 +385,16 @@ void cProtocolRecognizer::SendPickupSpawn(const cPickup & a_Pickup) +void cProtocolRecognizer::SendPlayerAbilities(void) +{ + ASSERT(m_Protocol != NULL); + m_Protocol->SendPlayerAbilities(); +} + + + + + void cProtocolRecognizer::SendPlayerAnimation(const cPlayer & a_Player, char a_Animation) { ASSERT(m_Protocol != NULL); @@ -667,11 +678,65 @@ bool cProtocolRecognizer::TryRecognizeProtocol(void) } switch (PacketType) { - case 0x02: break; // Handshake, continue recognizing - case 0xfe: HandleServerPing(); return false; - default: return false; + case 0x02: return TryRecognizeLengthlessProtocol(); // Handshake, continue recognizing + case 0xfe: + { + // This may be either a packet length or the length-less Ping packet + Byte NextByte; + if (!m_Buffer.ReadByte(NextByte)) + { + // Not enough data for either protocol + // This could actually happen with the 1.2 / 1.3 client, but their support is fading out anyway + return false; + } + if (NextByte != 0x01) + { + // This is definitely NOT a length-less Ping packet, handle as lengthed protocol: + break; + } + if (!m_Buffer.ReadByte(NextByte)) + { + // There is no more data. Although this *could* mean TCP fragmentation, it is highly unlikely + // and rather this is a 1.4 client sending a regular Ping packet (without the following Plugin message) + SendLengthlessServerPing(); + return false; + } + if (NextByte == 0xfa) + { + // Definitely a length-less Ping followed by a Plugin message + SendLengthlessServerPing(); + return false; + } + // Definitely a lengthed Initial handshake, handle below: + break; + } + } // switch (PacketType) + + // This must be a lengthed protocol, try if it has the entire initial handshake packet: + m_Buffer.ResetRead(); + UInt32 PacketLen; + UInt32 ReadSoFar = m_Buffer.GetReadableSpace(); + if (!m_Buffer.ReadVarInt(PacketLen)) + { + // Not enough bytes for the packet length, keep waiting + return false; } - + ReadSoFar -= m_Buffer.GetReadableSpace(); + if (!m_Buffer.CanReadBytes(PacketLen)) + { + // Not enough bytes for the packet, keep waiting + return false; + } + return TryRecognizeLengthedProtocol(PacketLen - ReadSoFar); +} + + + + + +bool cProtocolRecognizer::TryRecognizeLengthlessProtocol(void) +{ + // The comm started with 0x02, which is a Handshake packet in the length-less protocol family // 1.3.2 starts with 0x02 0x39 <name-length-short> // 1.2.5 starts with 0x02 <name-length-short> and name is expected to less than 0x3900 long :) char ch; @@ -724,7 +789,56 @@ bool cProtocolRecognizer::TryRecognizeProtocol(void) -void cProtocolRecognizer::HandleServerPing(void) +bool cProtocolRecognizer::TryRecognizeLengthedProtocol(UInt32 a_PacketLengthRemaining) +{ + UInt32 PacketType; + UInt32 NumBytesRead = m_Buffer.GetReadableSpace(); + if (!m_Buffer.ReadVarInt(PacketType)) + { + return false; + } + if (PacketType != 0x00) + { + // Not an initial handshake packet, we don't know how to talk to them + LOGINFO("Client \"%s\" uses an unsupported protocol (lengthed, initial packet %u)", + m_Client->GetIPString().c_str(), PacketType + ); + m_Client->Kick("Unsupported protocol version"); + return false; + } + UInt32 ProtocolVersion; + if (!m_Buffer.ReadVarInt(ProtocolVersion)) + { + return false; + } + NumBytesRead -= m_Buffer.GetReadableSpace(); + switch (ProtocolVersion) + { + case PROTO_VERSION_1_7_2: + { + AString ServerAddress; + short ServerPort; + UInt32 NextState; + m_Buffer.ReadVarUTF8String(ServerAddress); + m_Buffer.ReadBEShort(ServerPort); + m_Buffer.ReadVarInt(NextState); + m_Buffer.CommitRead(); + m_Protocol = new cProtocol172(m_Client, ServerAddress, ServerPort, NextState); + return true; + } + } + LOGINFO("Client \"%s\" uses an unsupported protocol (lengthed, version %u)", + m_Client->GetIPString().c_str(), ProtocolVersion + ); + m_Client->Kick("Unsupported protocol version"); + return false; +} + + + + + +void cProtocolRecognizer::SendLengthlessServerPing(void) { AString Reply; switch (cRoot::Get()->GetPrimaryServerVersion()) @@ -757,10 +871,12 @@ void cProtocolRecognizer::HandleServerPing(void) // http://wiki.vg/wiki/index.php?title=Protocol&oldid=3101#Server_List_Ping_.280xFE.29 // _X 2012_10_31: I know that this needn't eat the byte, since it still may be in transit. // Who cares? We're disconnecting anyway. - if (m_Buffer.CanReadBytes(1)) + m_Buffer.ResetRead(); + if (m_Buffer.CanReadBytes(2)) { byte val; - m_Buffer.ReadByte(val); + m_Buffer.ReadByte(val); // Packet type - Serverlist ping + m_Buffer.ReadByte(val); // 0x01 magic value ASSERT(val == 0x01); } diff --git a/source/Protocol/ProtocolRecognizer.h b/source/Protocol/ProtocolRecognizer.h index c53288230..79dc5568f 100644 --- a/source/Protocol/ProtocolRecognizer.h +++ b/source/Protocol/ProtocolRecognizer.h @@ -47,6 +47,9 @@ public: PROTO_VERSION_NEXT, PROTO_VERSION_LATEST = PROTO_VERSION_NEXT - 1, ///< Automatically assigned to the last protocol version, this serves as the default for PrimaryServerVersion + + // These will be kept "under" the next / latest, because the next and latest are only needed for previous protocols + PROTO_VERSION_1_7_2 = 4, } ; cProtocolRecognizer(cClientHandle * a_Client); @@ -86,6 +89,7 @@ public: virtual void SendKeepAlive (int a_PingID) override; virtual void SendLogin (const cPlayer & a_Player, const cWorld & a_World) override; virtual void SendPickupSpawn (const cPickup & a_Pickup) override; + virtual void SendPlayerAbilities (void) override; virtual void SendPlayerAnimation (const cPlayer & a_Player, char a_Animation) override; virtual void SendPlayerListItem (const cPlayer & a_Player, bool a_IsOnline) override; virtual void SendPlayerMaxSpeed (void) override; @@ -124,8 +128,23 @@ protected: /// Tries to recognize protocol based on m_Buffer contents; returns true if recognized bool TryRecognizeProtocol(void); - /// Called when the recognizer gets a server ping packet; responds with server stats and destroys the client - void HandleServerPing(void); + /** Tries to recognize a protocol in the length-less family, based on m_Buffer; returns true if recognized. + Handles protocols before release 1.7, that didn't include packet lengths, and started with a 0x02 handshake packet + Note that length-less server ping is handled directly in TryRecognizeProtocol(), this function is called only + when the 0x02 Handshake packet has been received + */ + bool TryRecognizeLengthlessProtocol(void); + + /** Tries to recognize a protocol in the leghted family (1.7+), based on m_Buffer; returns true if recognized. + The packet length and type have already been read, type is 0 + The number of bytes remaining in the packet is passed as a_PacketLengthRemaining + **/ + bool TryRecognizeLengthedProtocol(UInt32 a_PacketLengthRemaining); + + /** Called when the recognizer gets a length-less protocol's server ping packet + Responds with server stats and destroys the client. + */ + void SendLengthlessServerPing(void); } ; diff --git a/source/World.cpp b/source/World.cpp index 786d97a4d..dd3965e3d 100644 --- a/source/World.cpp +++ b/source/World.cpp @@ -55,6 +55,12 @@ /// Up to this many m_SpreadQueue elements are handled each world tick const int MAX_LIGHTING_SPREAD_PER_TICK = 10; +const int TIME_SUNSET = 12000; +const int TIME_NIGHT_START = 13187; +const int TIME_NIGHT_END = 22812; +const int TIME_SUNRISE = 23999; +const int TIME_SPAWN_DIVISOR = 148; + @@ -229,7 +235,8 @@ cWorld::cWorld(const AString & a_WorldName) : m_RSList(0), m_Weather(eWeather_Sunny), m_WeatherInterval(24000), // Guaranteed 1 day of sunshine at server start :) - m_TickThread(*this) + m_TickThread(*this), + m_SkyDarkness(0) { LOGD("cWorld::cWorld(\"%s\")", a_WorldName.c_str()); @@ -608,6 +615,9 @@ void cWorld::Tick(float a_Dt) m_WorldAge = (Int64)(m_WorldAgeSecs * 20.0); m_TimeOfDay = (Int64)(m_TimeOfDaySecs * 20.0); + // Updates the sky darkness based on current time of day + UpdateSkyDarkness(); + // Broadcast time update every 40 ticks (2 seconds) if (m_LastTimeUpdate < m_WorldAge - 40) { @@ -868,6 +878,31 @@ void cWorld::TickClients(float a_Dt) +void cWorld::UpdateSkyDarkness(void) +{ + int TempTime = (int)m_TimeOfDay; + if (TempTime <= TIME_SUNSET) + { + m_SkyDarkness = 0; + } + else if (TempTime <= TIME_NIGHT_START) + { + m_SkyDarkness = (TIME_NIGHT_START - TempTime) / TIME_SPAWN_DIVISOR; + } + else if (TempTime <= TIME_NIGHT_END) + { + m_SkyDarkness = 8; + } + else + { + m_SkyDarkness = (TIME_SUNRISE - TempTime) / TIME_SPAWN_DIVISOR; + } +} + + + + + void cWorld::WakeUpSimulators(int a_BlockX, int a_BlockY, int a_BlockZ) { return m_ChunkMap->WakeUpSimulators(a_BlockX, a_BlockY, a_BlockZ); @@ -2676,3 +2711,4 @@ void cWorld::cTaskSaveAllChunks::Run(cWorld & a_World) + diff --git a/source/World.h b/source/World.h index f174a1c2c..c4fd06d0b 100644 --- a/source/World.h +++ b/source/World.h @@ -592,6 +592,9 @@ public: /// 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
+ NIBBLETYPE GetSkyDarkness() { return m_SkyDarkness; }
+
private:
friend class cRoot;
@@ -636,6 +639,8 @@ private: Int64 m_LastSave; // The last WorldAge (in ticks) in which save-all was triggerred
std::map<cMonster::eFamily,Int64> m_LastSpawnMonster; // The last WorldAge (in ticks) in which a monster was spawned (for each megatype of monster) // MG TODO : find a way to optimize without creating unmaintenability (if mob IDs are becoming unrowed)
+ NIBBLETYPE m_SkyDarkness;
+
eGameMode m_GameMode;
bool m_bEnabledPVP;
bool m_IsDeepSnowEnabled;
@@ -727,6 +732,8 @@ private: /// Ticks all clients that are in this world
void TickClients(float a_Dt);
+
+ void UpdateSkyDarkness();
/// 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);
|