diff options
author | madmaxoft <github@xoft.cz> | 2014-05-18 23:10:23 +0200 |
---|---|---|
committer | madmaxoft <github@xoft.cz> | 2014-05-18 23:10:23 +0200 |
commit | e69a11012fb7543d47230663a01af2a7ec20960c (patch) | |
tree | 2c4a5b6759f76059736bdf5b30ea17f2a6ee8570 | |
parent | Updated SandVillage prefabs to latest Gallery content. (diff) | |
parent | There's no "round" function in MSVC2008. (diff) | |
download | cuberite-e69a11012fb7543d47230663a01af2a7ec20960c.tar cuberite-e69a11012fb7543d47230663a01af2a7ec20960c.tar.gz cuberite-e69a11012fb7543d47230663a01af2a7ec20960c.tar.bz2 cuberite-e69a11012fb7543d47230663a01af2a7ec20960c.tar.lz cuberite-e69a11012fb7543d47230663a01af2a7ec20960c.tar.xz cuberite-e69a11012fb7543d47230663a01af2a7ec20960c.tar.zst cuberite-e69a11012fb7543d47230663a01af2a7ec20960c.zip |
60 files changed, 1589 insertions, 328 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0c36be8b7..03481ec48 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,7 +27,7 @@ Code Stuff - The only exception: a `switch` statement with all `case` statements being a single short statement is allowed to use the short brace-less form. - These two rules really mean that indent is governed by braces * Add an empty last line in all source files (GCC and GIT can complain otherwise) - * Use doxy-comments for functions in the header file, format as `/** Description */` + * All new public functions in all classes need documenting comments on what they do and what behavior they follow, use doxy-comments formatted as `/** Description */`. Do not use asterisks on additional lines in multi-line comments. * Use spaces after the comment markers: `// Comment` instead of `//Comment` diff --git a/MCServer/.gitignore b/MCServer/.gitignore index 32a634a03..6da9aa7c7 100644 --- a/MCServer/.gitignore +++ b/MCServer/.gitignore @@ -30,3 +30,7 @@ motd.txt *.xml mcserver_api.lua +# Ignore the webadmin certs / privkey, so that no-one commits theirs by accident: +webadmin/httpscert.crt +webadmin/httpskey.pem + diff --git a/MCServer/webadmin/GenerateSelfSignedHTTPSCertUsingOpenssl.cmd b/MCServer/webadmin/GenerateSelfSignedHTTPSCertUsingOpenssl.cmd new file mode 100644 index 000000000..3ea6963b4 --- /dev/null +++ b/MCServer/webadmin/GenerateSelfSignedHTTPSCertUsingOpenssl.cmd @@ -0,0 +1,11 @@ +echo This script generates the certificate and private key for the https webadmin +echo Note that the generated certificate is self-signed, and therefore not trusted by browsers +echo Note that this script requires openssl to be installed and in PATH +echo. +echo When OpenSSL asks you for Common Name, you need to enter the fully qualified domain name of the server, that is, e. g. gallery.xoft.cz +echo. +echo If OpenSSL fails with an error, "WARNING: can't open config file: /usr/local/ssl/openssl.cnf", you need to run this script as an administrator +echo. + +openssl req -x509 -newkey rsa:2048 -keyout httpskey.pem -out httpscert.crt -days 3650 -nodes +pause diff --git a/MCServer/webadmin/GenerateSelfSignedHTTPSCertUsingOpenssl.sh b/MCServer/webadmin/GenerateSelfSignedHTTPSCertUsingOpenssl.sh new file mode 100755 index 000000000..5cf1237c8 --- /dev/null +++ b/MCServer/webadmin/GenerateSelfSignedHTTPSCertUsingOpenssl.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +echo "This script generates the certificate and private key for the https webadmin" +echo "Note that the generated certificate is self-signed, and therefore not trusted by browsers" +echo "Note that this script requires openssl to be installed and in PATH" +echo "" +echo "When OpenSSL asks you for Common Name, you need to enter the fully qualified domain name of the server, that is, e. g. gallery.xoft.cz" +echo "" + +openssl req -x509 -newkey rsa:2048 -keyout httpskey.pem -out httpscert.crt -days 3650 -nodes diff --git a/Tools/ProtoProxy/CMakeLists.txt b/Tools/ProtoProxy/CMakeLists.txt index a136c95df..f0796363c 100644 --- a/Tools/ProtoProxy/CMakeLists.txt +++ b/Tools/ProtoProxy/CMakeLists.txt @@ -38,9 +38,9 @@ set(SHARED_SRC ../../src/MCLogger.cpp ../../src/PolarSSL++/AesCfb128Decryptor.cpp ../../src/PolarSSL++/AesCfb128Encryptor.cpp + ../../src/PolarSSL++/CryptoKey.cpp ../../src/PolarSSL++/CtrDrbgContext.cpp ../../src/PolarSSL++/EntropyContext.cpp - ../../src/PolarSSL++/PublicKey.cpp ../../src/PolarSSL++/RsaPrivateKey.cpp ) set(SHARED_HDR @@ -50,9 +50,9 @@ set(SHARED_HDR ../../src/MCLogger.h ../../src/PolarSSL++/AesCfb128Decryptor.h ../../src/PolarSSL++/AesCfb128Encryptor.h + ../../src/PolarSSL++/CryptoKey.h ../../src/PolarSSL++/CtrDrbgContext.h ../../src/PolarSSL++/EntropyContext.h - ../../src/PolarSSL++/PublicKey.h ../../src/PolarSSL++/RsaPrivateKey.h ) set(SHARED_OSS_SRC diff --git a/Tools/ProtoProxy/Connection.cpp b/Tools/ProtoProxy/Connection.cpp index 26aed2206..c5916c1ca 100644 --- a/Tools/ProtoProxy/Connection.cpp +++ b/Tools/ProtoProxy/Connection.cpp @@ -7,7 +7,7 @@ #include "Connection.h" #include "Server.h" #include <iostream> -#include "PolarSSL++/PublicKey.h" +#include "PolarSSL++/CryptoKey.h" #ifdef _WIN32 #include <direct.h> // For _mkdir() @@ -2900,7 +2900,7 @@ void cConnection::SendEncryptionKeyResponse(const AString & a_ServerPublicKey, c Byte SharedSecret[16]; Byte EncryptedSecret[128]; memset(SharedSecret, 0, sizeof(SharedSecret)); // Use all zeroes for the initial secret - cPublicKey PubKey(a_ServerPublicKey); + cCryptoKey PubKey(a_ServerPublicKey); int res = PubKey.Encrypt(SharedSecret, sizeof(SharedSecret), EncryptedSecret, sizeof(EncryptedSecret)); if (res < 0) { diff --git a/src/Bindings/AllToLua.pkg b/src/Bindings/AllToLua.pkg index 1cd7c74f8..4fe86e1c5 100644 --- a/src/Bindings/AllToLua.pkg +++ b/src/Bindings/AllToLua.pkg @@ -76,6 +76,7 @@ $cfile "../CompositeChat.h" $cfile "../Map.h" $cfile "../MapManager.h" $cfile "../Scoreboard.h" +$cfile "../Statistics.h" diff --git a/src/BlockArea.cpp b/src/BlockArea.cpp index 9e20a0983..aff0bf9c6 100644 --- a/src/BlockArea.cpp +++ b/src/BlockArea.cpp @@ -2123,7 +2123,7 @@ void cBlockArea::MergeByStrategy(const cBlockArea & a_Src, int a_RelX, int a_Rel a_Src.GetSizeX(), a_Src.GetSizeY(), a_Src.GetSizeZ(), m_Size.x, m_Size.y, m_Size.z ); - break; + return; } // case msOverwrite case cBlockArea::msFillAir: @@ -2137,7 +2137,7 @@ void cBlockArea::MergeByStrategy(const cBlockArea & a_Src, int a_RelX, int a_Rel a_Src.GetSizeX(), a_Src.GetSizeY(), a_Src.GetSizeZ(), m_Size.x, m_Size.y, m_Size.z ); - break; + return; } // case msFillAir case cBlockArea::msImprint: @@ -2151,7 +2151,7 @@ void cBlockArea::MergeByStrategy(const cBlockArea & a_Src, int a_RelX, int a_Rel a_Src.GetSizeX(), a_Src.GetSizeY(), a_Src.GetSizeZ(), m_Size.x, m_Size.y, m_Size.z ); - break; + return; } // case msImprint case cBlockArea::msLake: @@ -2165,7 +2165,7 @@ void cBlockArea::MergeByStrategy(const cBlockArea & a_Src, int a_RelX, int a_Rel a_Src.GetSizeX(), a_Src.GetSizeY(), a_Src.GetSizeZ(), m_Size.x, m_Size.y, m_Size.z ); - break; + return; } // case msLake case cBlockArea::msSpongePrint: @@ -2179,7 +2179,7 @@ void cBlockArea::MergeByStrategy(const cBlockArea & a_Src, int a_RelX, int a_Rel a_Src.GetSizeX(), a_Src.GetSizeY(), a_Src.GetSizeZ(), m_Size.x, m_Size.y, m_Size.z ); - break; + return; } // case msSpongePrint case cBlockArea::msDifference: @@ -2193,7 +2193,7 @@ void cBlockArea::MergeByStrategy(const cBlockArea & a_Src, int a_RelX, int a_Rel a_Src.GetSizeX(), a_Src.GetSizeY(), a_Src.GetSizeZ(), m_Size.x, m_Size.y, m_Size.z ); - break; + return; } // case msDifference case cBlockArea::msMask: @@ -2207,16 +2207,13 @@ void cBlockArea::MergeByStrategy(const cBlockArea & a_Src, int a_RelX, int a_Rel a_Src.GetSizeX(), a_Src.GetSizeY(), a_Src.GetSizeZ(), m_Size.x, m_Size.y, m_Size.z ); - break; + return; } // case msMask - - default: - { - LOGWARNING("Unknown block area merge strategy: %d", a_Strategy); - ASSERT(!"Unknown block area merge strategy"); - break; - } } // switch (a_Strategy) + + LOGWARNING("Unknown block area merge strategy: %d", a_Strategy); + ASSERT(!"Unknown block area merge strategy"); + return; } diff --git a/src/ByteBuffer.h b/src/ByteBuffer.h index 929c93167..adaa00330 100644 --- a/src/ByteBuffer.h +++ b/src/ByteBuffer.h @@ -30,25 +30,25 @@ public: cByteBuffer(size_t a_BufferSize); ~cByteBuffer(); - /// Writes the bytes specified to the ringbuffer. Returns true if successful, false if not + /** Writes the bytes specified to the ringbuffer. Returns true if successful, false if not */ bool Write(const void * a_Bytes, size_t a_Count); - /// Returns the number of bytes that can be successfully written to the ringbuffer + /** Returns the number of bytes that can be successfully written to the ringbuffer */ size_t GetFreeSpace(void) const; - /// Returns the number of bytes that are currently in the ringbuffer. Note GetReadableBytes() + /** Returns the number of bytes that are currently in the ringbuffer. Note GetReadableBytes() */ size_t GetUsedSpace(void) const; - /// Returns the number of bytes that are currently available for reading (may be less than UsedSpace due to some data having been read already) + /** Returns the number of bytes that are currently available for reading (may be less than UsedSpace due to some data having been read already) */ size_t GetReadableSpace(void) const; - /// Returns the current data start index. For debugging purposes. + /** Returns the current data start index. For debugging purposes. */ size_t GetDataStart(void) const { return m_DataStart; } - /// Returns true if the specified amount of bytes are available for reading + /** Returns true if the specified amount of bytes are available for reading */ bool CanReadBytes(size_t a_Count) const; - /// Returns true if the specified amount of bytes are available for writing + /** Returns true if the specified amount of bytes are available for writing */ bool CanWriteBytes(size_t a_Count) const; // Read the specified datatype and advance the read pointer; return true if successfully read: @@ -65,7 +65,7 @@ public: bool ReadVarUTF8String (AString & a_Value); // string length as VarInt, then string as UTF-8 bool ReadLEInt (int & a_Value); - /// Reads VarInt, assigns it to anything that can be assigned from an UInt32 (unsigned short, char, Byte, double, ...) + /** Reads VarInt, assigns it to anything that can be assigned from an UInt32 (unsigned short, char, Byte, double, ...) */ template <typename T> bool ReadVarInt(T & a_Value) { UInt32 v; @@ -91,37 +91,37 @@ public: bool WriteVarUTF8String (const AString & a_Value); // string length as VarInt, then string as UTF-8 bool WriteLEInt (int a_Value); - /// Reads a_Count bytes into a_Buffer; returns true if successful + /** Reads a_Count bytes into a_Buffer; returns true if successful */ bool ReadBuf(void * a_Buffer, size_t a_Count); - /// Writes a_Count bytes into a_Buffer; returns true if successful + /** Writes a_Count bytes into a_Buffer; returns true if successful */ bool WriteBuf(const void * a_Buffer, size_t a_Count); - /// Reads a_Count bytes into a_String; returns true if successful + /** Reads a_Count bytes into a_String; returns true if successful */ bool ReadString(AString & a_String, size_t a_Count); - /// Reads 2 * a_NumChars bytes and interprets it as a UTF16-BE string, converting it into UTF8 string a_String + /** Reads 2 * a_NumChars bytes and interprets it as a UTF16-BE string, converting it into UTF8 string a_String */ bool ReadUTF16String(AString & a_String, size_t a_NumChars); - /// Skips reading by a_Count bytes; returns false if not enough bytes in the ringbuffer + /** Skips reading by a_Count bytes; returns false if not enough bytes in the ringbuffer */ bool SkipRead(size_t a_Count); - /// Reads all available data into a_Data + /** Reads all available data into a_Data */ void ReadAll(AString & a_Data); - /// Reads the specified number of bytes and writes it into the destinatio bytebuffer. Returns true on success. + /** Reads the specified number of bytes and writes it into the destinatio bytebuffer. Returns true on success. */ bool ReadToByteBuffer(cByteBuffer & a_Dst, size_t a_NumBytes); - /// Removes the bytes that have been read from the ringbuffer + /** Removes the bytes that have been read from the ringbuffer */ void CommitRead(void); - /// Restarts next reading operation at the start of the ringbuffer + /** Restarts next reading operation at the start of the ringbuffer */ void ResetRead(void); - /// Re-reads the data that has been read since the last commit to the current readpos. Used by ProtoProxy to duplicate communication + /** Re-reads the data that has been read since the last commit to the current readpos. Used by ProtoProxy to duplicate communication */ void ReadAgain(AString & a_Out); - /// Checks if the internal state is valid (read and write positions in the correct bounds) using ASSERTs + /** Checks if the internal state is valid (read and write positions in the correct bounds) using ASSERTs */ void CheckValid(void) const; protected: @@ -136,7 +136,7 @@ protected: size_t m_WritePos; // Where the data ends in the ringbuffer size_t m_ReadPos; // Where the next read will start in the ringbuffer - /// Advances the m_ReadPos by a_Count bytes + /** Advances the m_ReadPos by a_Count bytes */ void AdvanceReadPos(size_t a_Count); } ; diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index 94f031ed6..6caa599cb 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -336,7 +336,7 @@ void cClientHandle::Authenticate(const AString & a_Name, const AString & a_UUID) // Send scoreboard data World->GetScoreBoard().SendTo(*this); - + // Delay the first ping until the client "settles down" // This should fix #889, "BadCast exception, cannot convert bit to fm" error in client cTimer t1; @@ -815,6 +815,16 @@ void cClientHandle::HandleLeftClick(int a_BlockX, int a_BlockY, int a_BlockZ, eB return; } + if ( + (Diff(m_Player->GetPosX(), (double)a_BlockX) > 6) || + (Diff(m_Player->GetPosY(), (double)a_BlockY) > 6) || + (Diff(m_Player->GetPosZ(), (double)a_BlockZ) > 6) + ) + { + m_Player->GetWorld()->SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, m_Player); + return; + } + cPluginManager * PlgMgr = cRoot::Get()->GetPluginManager(); if (PlgMgr->CallHookPlayerLeftClick(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_Status)) { @@ -878,6 +888,7 @@ void cClientHandle::HandleLeftClick(int a_BlockX, int a_BlockY, int a_BlockZ, eB case DIG_STATUS_CANCELLED: { // Block breaking cancelled by player + FinishDigAnimation(); return; } @@ -916,7 +927,7 @@ void cClientHandle::HandleBlockDigStarted(int a_BlockX, int a_BlockY, int a_Bloc // It is a duplicate packet, drop it right away return; } - + if ( m_Player->IsGameModeCreative() && ItemCategory::IsSword(m_Player->GetInventory().GetEquippedItem().m_ItemType) @@ -925,14 +936,17 @@ void cClientHandle::HandleBlockDigStarted(int a_BlockX, int a_BlockY, int a_Bloc // Players can't destroy blocks with a Sword in the hand. return; } - - if (cRoot::Get()->GetPluginManager()->CallHookPlayerBreakingBlock(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_OldBlock, a_OldMeta)) + + if ( + (Diff(m_Player->GetPosX(), (double)a_BlockX) > 6) || + (Diff(m_Player->GetPosY(), (double)a_BlockY) > 6) || + (Diff(m_Player->GetPosZ(), (double)a_BlockZ) > 6) + ) { - // A plugin doesn't agree with the breaking. Bail out. Send the block back to the client, so that it knows: m_Player->GetWorld()->SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, m_Player); return; } - + // Set the last digging coords to the block being dug, so that they can be checked in DIG_FINISHED to avoid dig/aim bug in the client: m_HasStartedDigging = true; m_LastDigBlockX = a_BlockX; @@ -1004,24 +1018,24 @@ void cClientHandle::HandleBlockDigFinished(int a_BlockX, int a_BlockY, int a_Blo return; } - m_HasStartedDigging = false; - if (m_BlockDigAnimStage != -1) + FinishDigAnimation(); + + cWorld * World = m_Player->GetWorld(); + cItemHandler * ItemHandler = cItemHandler::GetItemHandler(m_Player->GetEquippedItem()); + + if (cRoot::Get()->GetPluginManager()->CallHookPlayerBreakingBlock(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_OldBlock, a_OldMeta)) { - // End dig animation - m_BlockDigAnimStage = -1; - // It seems that 10 ends block animation - m_Player->GetWorld()->BroadcastBlockBreakAnimation(m_UniqueID, m_BlockDigAnimX, m_BlockDigAnimY, m_BlockDigAnimZ, 10, this); + // A plugin doesn't agree with the breaking. Bail out. Send the block back to the client, so that it knows: + m_Player->GetWorld()->SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, m_Player); + return; } - cItemHandler * ItemHandler = cItemHandler::GetItemHandler(m_Player->GetEquippedItem()); - if (a_OldBlock == E_BLOCK_AIR) { LOGD("Dug air - what the function?"); return; } - - cWorld * World = m_Player->GetWorld(); + ItemHandler->OnBlockDestroyed(World, m_Player, m_Player->GetEquippedItem(), a_BlockX, a_BlockY, a_BlockZ); // The ItemHandler is also responsible for spawning the pickups cChunkInterface ChunkInterface(World->GetChunkMap()); @@ -1036,6 +1050,36 @@ void cClientHandle::HandleBlockDigFinished(int a_BlockX, int a_BlockY, int a_Blo +void cClientHandle::FinishDigAnimation() +{ + if ( + !m_HasStartedDigging || // Hasn't received the DIG_STARTED packet + (m_LastDigBlockX == -1) || + (m_LastDigBlockY == -1) || + (m_LastDigBlockZ == -1) + ) + { + return; + } + + m_HasStartedDigging = false; + if (m_BlockDigAnimStage != -1) + { + // End dig animation + m_BlockDigAnimStage = -1; + // It seems that 10 ends block animation + m_Player->GetWorld()->BroadcastBlockBreakAnimation(m_UniqueID, m_LastDigBlockX, m_LastDigBlockY, m_LastDigBlockZ, 10, this); + } + + m_BlockDigAnimX = -1; + m_BlockDigAnimY = -1; + m_BlockDigAnimZ = -1; +} + + + + + void cClientHandle::HandleRightClick(int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, const cItem & a_HeldItem) { LOGD("HandleRightClick: {%d, %d, %d}, face %d, HeldItem: %s", @@ -1043,7 +1087,23 @@ void cClientHandle::HandleRightClick(int a_BlockX, int a_BlockY, int a_BlockZ, e ); cWorld * World = m_Player->GetWorld(); - + + if ( + (Diff(m_Player->GetPosX(), (double)a_BlockX) > 6) || + (Diff(m_Player->GetPosY(), (double)a_BlockY) > 6) || + (Diff(m_Player->GetPosZ(), (double)a_BlockZ) > 6) + ) + { + if (a_BlockFace != BLOCK_FACE_NONE) + { + AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace); + World->SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, m_Player); + World->SendBlockTo(a_BlockX, a_BlockY + 1, a_BlockZ, m_Player); // 2 block high things + m_Player->GetInventory().SendEquippedSlot(); + } + return; + } + cPluginManager * PlgMgr = cRoot::Get()->GetPluginManager(); if (PlgMgr->CallHookPlayerRightClick(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ)) { @@ -1057,7 +1117,8 @@ void cClientHandle::HandleRightClick(int a_BlockX, int a_BlockY, int a_BlockZ, e { AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace); World->SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, m_Player); - World->SendBlockTo(a_BlockX, a_BlockY + 1, a_BlockZ, m_Player); //2 block high things + World->SendBlockTo(a_BlockX, a_BlockY + 1, a_BlockZ, m_Player); // 2 block high things + m_Player->GetInventory().SendEquippedSlot(); } return; } @@ -1246,6 +1307,7 @@ void cClientHandle::HandlePlaceBlock(int a_BlockX, int a_BlockY, int a_BlockZ, e { // Handler refused the placement, send that information back to the client: World->SendBlockTo(a_BlockX, a_BlockY, a_BlockY, m_Player); + m_Player->GetInventory().SendEquippedSlot(); return; } @@ -1255,6 +1317,7 @@ void cClientHandle::HandlePlaceBlock(int a_BlockX, int a_BlockY, int a_BlockZ, e { // A plugin doesn't agree with placing the block, revert the block on the client: World->SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, m_Player); + m_Player->GetInventory().SendEquippedSlot(); return; } @@ -2437,6 +2500,15 @@ void cClientHandle::SendSpawnVehicle(const cEntity & a_Vehicle, char a_VehicleTy +void cClientHandle::SendStatistics(const cStatManager & a_Manager) +{ + m_Protocol->SendStatistics(a_Manager); +} + + + + + void cClientHandle::SendTabCompletionResults(const AStringVector & a_Results) { m_Protocol->SendTabCompletionResults(a_Results); @@ -2676,12 +2748,13 @@ void cClientHandle::PacketError(unsigned char a_PacketType) -void cClientHandle::DataReceived(const char * a_Data, size_t a_Size) +bool cClientHandle::DataReceived(const char * a_Data, size_t a_Size) { // Data is received from the client, store it in the buffer to be processed by the Tick thread: m_TimeSinceLastPacket = 0; cCSLock Lock(m_CSIncomingData); m_IncomingData.append(a_Data, a_Size); + return false; } diff --git a/src/ClientHandle.h b/src/ClientHandle.h index 4dc6ab074..9f1245be5 100644 --- a/src/ClientHandle.h +++ b/src/ClientHandle.h @@ -39,6 +39,7 @@ class cFallingBlock; class cItemHandler; class cWorld; class cCompositeChat; +class cStatManager; @@ -160,6 +161,7 @@ public: void SendSpawnMob (const cMonster & a_Mob); void SendSpawnObject (const cEntity & a_Entity, char a_ObjectType, int a_ObjectData, Byte a_Yaw, Byte a_Pitch); void SendSpawnVehicle (const cEntity & a_Vehicle, char a_VehicleType, char a_VehicleSubType = 0); + void SendStatistics (const cStatManager & a_Manager); void SendTabCompletionResults(const AStringVector & a_Results); void SendTeleportEntity (const cEntity & a_Entity); void SendThunderbolt (int a_BlockX, int a_BlockY, int a_BlockZ); @@ -374,6 +376,9 @@ private: /** Handles the DIG_FINISHED dig packet: */ void HandleBlockDigFinished(int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, BLOCKTYPE a_OldBlock, NIBBLETYPE a_OldMeta); + /** The clients will receive a finished dig animation */ + void FinishDigAnimation(); + /** Converts the protocol-formatted channel list (NUL-separated) into a proper string vector. */ AStringVector BreakApartPluginChannels(const AString & a_PluginChannels); @@ -390,7 +395,7 @@ private: void HandleAnvilItemName(const char * a_Data, size_t a_Length); // cSocketThreads::cCallback overrides: - virtual void DataReceived (const char * a_Data, size_t a_Size) override; // Data is received from the client + virtual bool DataReceived (const char * a_Data, size_t a_Size) override; // Data is received from the client virtual void GetOutgoingData(AString & a_Data) override; // Data can be sent to client virtual void SocketClosed (void) override; // The socket has been closed for any reason }; // tolua_export diff --git a/src/Defines.h b/src/Defines.h index 9fa3b3a8e..563fc308c 100644 --- a/src/Defines.h +++ b/src/Defines.h @@ -3,6 +3,7 @@ #include "ChatColor.h" #include <limits> +#include <cmath> @@ -528,6 +529,15 @@ inline float GetSpecialSignf( float a_Val ) +template<class T> inline T Diff(T a_Val1, T a_Val2) +{ + return std::abs(a_Val1 - a_Val2); +} + + + + + // tolua_begin enum eMessageType diff --git a/src/Entities/CMakeLists.txt b/src/Entities/CMakeLists.txt index c9ca44d38..205cb2cca 100644 --- a/src/Entities/CMakeLists.txt +++ b/src/Entities/CMakeLists.txt @@ -10,3 +10,5 @@ file(GLOB SOURCE ) add_library(Entities ${SOURCE}) + +target_link_libraries(Entities WorldStorage) diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp index 4cf10a219..31ad66779 100644 --- a/src/Entities/Entity.cpp +++ b/src/Entities/Entity.cpp @@ -312,12 +312,16 @@ bool cEntity::DoTakeDamage(TakeDamageInfo & a_TDI) if ((a_TDI.Attacker != NULL) && (a_TDI.Attacker->IsPlayer())) { + cPlayer * Player = (cPlayer *)a_TDI.Attacker; + // IsOnGround() only is false if the player is moving downwards - if (!((cPlayer *)a_TDI.Attacker)->IsOnGround()) // TODO: Better damage increase, and check for enchantments (and use magic critical instead of plain) + if (!Player->IsOnGround()) // TODO: Better damage increase, and check for enchantments (and use magic critical instead of plain) { a_TDI.FinalDamage += 2; m_World->BroadcastEntityAnimation(*this, 4); // Critical hit } + + Player->GetStatManager().AddValue(statDamageDealt, (StatValue)floor(a_TDI.FinalDamage * 10 + 0.5)); } m_Health -= (short)a_TDI.FinalDamage; @@ -370,6 +374,11 @@ bool cEntity::DoTakeDamage(TakeDamageInfo & a_TDI) if (m_Health <= 0) { KilledBy(a_TDI.Attacker); + + if (a_TDI.Attacker != NULL) + { + a_TDI.Attacker->Killed(this); + } } return true; } @@ -575,9 +584,16 @@ void cEntity::Tick(float a_Dt, cChunk & a_Chunk) if (m_AttachedTo != NULL) { - if ((m_Pos - m_AttachedTo->GetPosition()).Length() > 0.5) + Vector3d DeltaPos = m_Pos - m_AttachedTo->GetPosition(); + if (DeltaPos.Length() > 0.5) { SetPosition(m_AttachedTo->GetPosition()); + + if (IsPlayer()) + { + cPlayer * Player = (cPlayer *)this; + Player->UpdateMovementStats(DeltaPos); + } } } else diff --git a/src/Entities/Entity.h b/src/Entities/Entity.h index df03d635b..a111b128d 100644 --- a/src/Entities/Entity.h +++ b/src/Entities/Entity.h @@ -299,6 +299,9 @@ public: /// Called when the health drops below zero. a_Killer may be NULL (environmental damage) virtual void KilledBy(cEntity * a_Killer); + /// Called when the entity kills another entity + virtual void Killed(cEntity * a_Victim) {} + /// Heals the specified amount of HPs void Heal(int a_HitPoints); diff --git a/src/Entities/Pickup.cpp b/src/Entities/Pickup.cpp index 497b41683..0fd006485 100644 --- a/src/Entities/Pickup.cpp +++ b/src/Entities/Pickup.cpp @@ -192,6 +192,16 @@ bool cPickup::CollectedBy(cPlayer * a_Dest) int NumAdded = a_Dest->GetInventory().AddItem(m_Item); if (NumAdded > 0) { + // Check achievements + switch (m_Item.m_ItemType) + { + case E_BLOCK_LOG: a_Dest->AwardAchievement(achMineWood); break; + case E_ITEM_LEATHER: a_Dest->AwardAchievement(achKillCow); break; + case E_ITEM_DIAMOND: a_Dest->AwardAchievement(achDiamonds); break; + case E_ITEM_BLAZE_ROD: a_Dest->AwardAchievement(achBlazeRod); break; + default: break; + } + m_Item.m_ItemCount -= NumAdded; m_World->BroadcastCollectPickup(*this, *a_Dest); // Also send the "pop" sound effect with a somewhat random pitch (fast-random using EntityID ;) diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index 6ac11c270..c3b763278 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -16,6 +16,9 @@ #include "../Items/ItemHandler.h" #include "../Vector3.h" +#include "../WorldStorage/StatSerializer.h" +#include "../CompositeChat.h" + #include "inifile/iniFile.h" #include "json/json.h" @@ -191,6 +194,8 @@ void cPlayer::Tick(float a_Dt, cChunk & a_Chunk) return; } } + + m_Stats.AddValue(statMinutesPlayed, 1); if (!a_Chunk.IsValid()) { @@ -815,7 +820,7 @@ bool cPlayer::DoTakeDamage(TakeDamageInfo & a_TDI) if ((a_TDI.Attacker != NULL) && (a_TDI.Attacker->IsPlayer())) { - cPlayer* Attacker = (cPlayer*) a_TDI.Attacker; + cPlayer * Attacker = (cPlayer *)a_TDI.Attacker; if ((m_Team != NULL) && (m_Team == Attacker->m_Team)) { @@ -832,6 +837,8 @@ bool cPlayer::DoTakeDamage(TakeDamageInfo & a_TDI) // Any kind of damage adds food exhaustion AddFoodExhaustion(0.3f); SendHealth(); + + m_Stats.AddValue(statDamageTaken, (StatValue)floor(a_TDI.FinalDamage * 10 + 0.5)); return true; } return false; @@ -862,6 +869,8 @@ void cPlayer::KilledBy(cEntity * a_Killer) Pickups.Add(cItem(E_ITEM_RED_APPLE)); } + m_Stats.AddValue(statItemsDropped, Pickups.Size()); + m_World->SpawnItemPickups(Pickups, GetPosX(), GetPosY(), GetPosZ(), 10); SaveToDisk(); // Save it, yeah the world is a tough place ! @@ -871,9 +880,9 @@ void cPlayer::KilledBy(cEntity * a_Killer) } else if (a_Killer->IsPlayer()) { - GetWorld()->BroadcastChatDeath(Printf("%s was killed by %s", GetName().c_str(), ((cPlayer *)a_Killer)->GetName().c_str())); + cPlayer * Killer = (cPlayer *)a_Killer; - m_World->GetScoreBoard().AddPlayerScore(((cPlayer *)a_Killer)->GetName(), cObjective::otPlayerKillCount, 1); + GetWorld()->BroadcastChatDeath(Printf("%s was killed by %s", GetName().c_str(), Killer->GetName().c_str())); } else { @@ -883,6 +892,8 @@ void cPlayer::KilledBy(cEntity * a_Killer) GetWorld()->BroadcastChatDeath(Printf("%s was killed by a %s", GetName().c_str(), KillerClass.c_str())); } + m_Stats.AddValue(statDeaths); + m_World->GetScoreBoard().AddPlayerScore(GetName(), cObjective::otDeathCount, 1); } @@ -890,6 +901,33 @@ void cPlayer::KilledBy(cEntity * a_Killer) +void cPlayer::Killed(cEntity * a_Victim) +{ + cScoreboard & ScoreBoard = m_World->GetScoreBoard(); + + if (a_Victim->IsPlayer()) + { + m_Stats.AddValue(statPlayerKills); + + ScoreBoard.AddPlayerScore(GetName(), cObjective::otPlayerKillCount, 1); + } + else if (a_Victim->IsMob()) + { + if (((cMonster *)a_Victim)->GetMobFamily() == cMonster::mfHostile) + { + AwardAchievement(achKillMonster); + } + + m_Stats.AddValue(statMobKills); + } + + ScoreBoard.AddPlayerScore(GetName(), cObjective::otTotalKillCount, 1); +} + + + + + void cPlayer::Respawn(void) { m_Health = GetMaxHealth(); @@ -1108,6 +1146,47 @@ void cPlayer::SetIP(const AString & a_IP) +unsigned int cPlayer::AwardAchievement(const eStatistic a_Ach) +{ + eStatistic Prerequisite = cStatInfo::GetPrerequisite(a_Ach); + + // Check if the prerequisites are met + if (Prerequisite != statInvalid) + { + if (m_Stats.GetValue(Prerequisite) == 0) + { + return 0; + } + } + + StatValue Old = m_Stats.GetValue(a_Ach); + + if (Old > 0) + { + return m_Stats.AddValue(a_Ach); + } + else + { + // First time, announce it + cCompositeChat Msg; + Msg.AddTextPart(m_PlayerName + " has just earned the achievement "); + Msg.AddTextPart(cStatInfo::GetName(a_Ach)); // TODO 2014-05-12 xdot: Use the proper cCompositeChat part (cAchievement) + m_World->BroadcastChat(Msg); + + // Increment the statistic + StatValue New = m_Stats.AddValue(a_Ach); + + // Achievement Get! + m_ClientHandle->SendStatistics(m_Stats); + + return New; + } +} + + + + + void cPlayer::TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ) { SetPosition(a_PosX, a_PosY, a_PosZ); @@ -1192,6 +1271,9 @@ void cPlayer::MoveTo( const Vector3d & a_NewPos ) // TODO: should do some checks to see if player is not moving through terrain // TODO: Official server refuses position packets too far away from each other, kicking "hacked" clients; we should, too + + Vector3d DeltaPos = a_NewPos - GetPosition(); + UpdateMovementStats(DeltaPos); SetPosition( a_NewPos ); SetStance(a_NewPos.y + 1.62); @@ -1422,10 +1504,7 @@ void cPlayer::TossEquippedItem(char a_Amount) Drops.push_back(DroppedItem); } - double vX = 0, vY = 0, vZ = 0; - EulerToVector(-GetYaw(), GetPitch(), vZ, vX, vY); - vY = -vY * 2 + 1.f; - m_World->SpawnItemPickups(Drops, GetPosX(), GetEyeHeight(), GetPosZ(), vX * 3, vY * 3, vZ * 3, true); // 'true' because created by player + TossItems(Drops); } @@ -1441,6 +1520,7 @@ void cPlayer::TossHeldItem(char a_Amount) char OriginalItemAmount = Item.m_ItemCount; Item.m_ItemCount = std::min(OriginalItemAmount, a_Amount); Drops.push_back(Item); + if (OriginalItemAmount > a_Amount) { Item.m_ItemCount = OriginalItemAmount - a_Amount; @@ -1451,10 +1531,7 @@ void cPlayer::TossHeldItem(char a_Amount) } } - double vX = 0, vY = 0, vZ = 0; - EulerToVector(-GetYaw(), GetPitch(), vZ, vX, vY); - vY = -vY * 2 + 1.f; - m_World->SpawnItemPickups(Drops, GetPosX(), GetEyeHeight(), GetPosZ(), vX * 3, vY * 3, vZ * 3, true); // 'true' because created by player + TossItems(Drops); } @@ -1466,10 +1543,21 @@ void cPlayer::TossPickup(const cItem & a_Item) cItems Drops; Drops.push_back(a_Item); + TossItems(Drops); +} + + + + + +void cPlayer::TossItems(const cItems & a_Items) +{ + m_Stats.AddValue(statItemsDropped, a_Items.Size()); + double vX = 0, vY = 0, vZ = 0; EulerToVector(-GetYaw(), GetPitch(), vZ, vX, vY); vY = -vY * 2 + 1.f; - m_World->SpawnItemPickups(Drops, GetPosX(), GetEyeHeight(), GetPosZ(), vX * 3, vY * 3, vZ * 3, true); // 'true' because created by player + m_World->SpawnItemPickups(a_Items, GetPosX(), GetEyeHeight(), GetPosZ(), vX * 3, vY * 3, vZ * 3, true); // 'true' because created by player } @@ -1621,6 +1709,11 @@ bool cPlayer::LoadFromDisk() m_Inventory.LoadFromJson(root["inventory"]); m_LoadedWorldName = root.get("world", "world").asString(); + + // Load the player stats. + // We use the default world name (like bukkit) because stats are shared between dimensions/worlds. + cStatSerializer StatSerializer(cRoot::Get()->GetDefaultWorld()->GetName(), GetName(), &m_Stats); + StatSerializer.Load(); LOGD("Player \"%s\" was read from file, spawning at {%.2f, %.2f, %.2f} in world \"%s\"", m_PlayerName.c_str(), GetPosX(), GetPosY(), GetPosZ(), m_LoadedWorldName.c_str() @@ -1692,6 +1785,16 @@ bool cPlayer::SaveToDisk() LOGERROR("ERROR WRITING PLAYER JSON TO FILE \"%s\"", SourceFile.c_str()); return false; } + + // Save the player stats. + // We use the default world name (like bukkit) because stats are shared between dimensions/worlds. + cStatSerializer StatSerializer(cRoot::Get()->GetDefaultWorld()->GetName(), m_PlayerName, &m_Stats); + if (!StatSerializer.Save()) + { + LOGERROR("Could not save stats for player %s", m_PlayerName.c_str()); + return false; + } + return true; } @@ -1706,7 +1809,10 @@ cPlayer::StringList cPlayer::GetResolvedPermissions() const PermissionMap& ResolvedPermissions = m_ResolvedPermissions; for( PermissionMap::const_iterator itr = ResolvedPermissions.begin(); itr != ResolvedPermissions.end(); ++itr ) { - if( itr->second ) Permissions.push_back( itr->first ); + if (itr->second) + { + Permissions.push_back( itr->first ); + } } return Permissions; @@ -1845,6 +1951,59 @@ void cPlayer::HandleFloater() +void cPlayer::UpdateMovementStats(const Vector3d & a_DeltaPos) +{ + StatValue Value = (StatValue)floor(a_DeltaPos.Length() * 100 + 0.5); + + if (m_AttachedTo == NULL) + { + int PosX = POSX_TOINT; + int PosY = POSY_TOINT; + int PosZ = POSZ_TOINT; + + BLOCKTYPE Block; + NIBBLETYPE Meta; + if (!m_World->GetBlockTypeMeta(PosX, PosY, PosZ, Block, Meta)) + { + return; + } + + if ((Block == E_BLOCK_LADDER) && (a_DeltaPos.y > 0.0)) // Going up + { + m_Stats.AddValue(statDistClimbed, (StatValue)floor(a_DeltaPos.y * 100 + 0.5)); + } + else + { + // TODO 2014-05-12 xdot: Other types + m_Stats.AddValue(statDistWalked, Value); + } + } + else + { + switch (m_AttachedTo->GetEntityType()) + { + case cEntity::etMinecart: m_Stats.AddValue(statDistMinecart, Value); break; + case cEntity::etBoat: m_Stats.AddValue(statDistBoat, Value); break; + case cEntity::etMonster: + { + cMonster * Monster = (cMonster *)m_AttachedTo; + switch (Monster->GetMobType()) + { + case cMonster::mtPig: m_Stats.AddValue(statDistPig, Value); break; + case cMonster::mtHorse: m_Stats.AddValue(statDistHorse, Value); break; + default: break; + } + break; + } + default: break; + } + } +} + + + + + void cPlayer::ApplyFoodExhaustionFromMovement() { if (IsGameModeCreative()) diff --git a/src/Entities/Player.h b/src/Entities/Player.h index 6fc7e2875..78b534d83 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -7,6 +7,8 @@ #include "../World.h" #include "../ClientHandle.h" +#include "../Statistics.h" + @@ -174,6 +176,15 @@ public: cTeam * UpdateTeam(void); // tolua_end + + /** Return the associated statistic and achievement manager. */ + cStatManager & GetStatManager() { return m_Stats; } + + /** Awards the player an achievement. + If all prerequisites are met, this method will award the achievement and will broadcast a chat message. + If the achievement has been already awarded to the player, this method will just increment the stat counter. + Returns the _new_ stat value. (0 = Could not award achievement) */ + unsigned int AwardAchievement(const eStatistic a_Ach); void SetIP(const AString & a_IP); @@ -306,6 +317,8 @@ public: void AbortEating(void); virtual void KilledBy(cEntity * a_Killer) override; + + virtual void Killed(cEntity * a_Victim) override; void Respawn(void); // tolua_export @@ -375,6 +388,9 @@ public: /** If true the player can fly even when he's not in creative. */ void SetCanFly(bool a_CanFly); + /** Update movement-related statistics. */ + void UpdateMovementStats(const Vector3d & a_DeltaPos); + /** Returns wheter the player can fly or not. */ virtual bool CanFly(void) const { return m_CanFly; } // tolua_end @@ -487,6 +503,8 @@ protected: cTeam * m_Team; + cStatManager m_Stats; + void ResolvePermissions(void); @@ -506,6 +524,9 @@ protected: /** Called in each tick if the player is fishing to make sure the floater dissapears when the player doesn't have a fishing rod as equipped item. */ void HandleFloater(void); + /** Tosses a list of items. */ + void TossItems(const cItems & a_Items); + /** Adds food exhaustion based on the difference between Pos and LastPos, sprinting status and swimming (in water block) */ void ApplyFoodExhaustionFromMovement(); diff --git a/src/Generating/StructGen.cpp b/src/Generating/StructGen.cpp index db9d5578c..636364e17 100644 --- a/src/Generating/StructGen.cpp +++ b/src/Generating/StructGen.cpp @@ -596,24 +596,22 @@ void cStructGenDirectOverhangs::GenFinish(cChunkDesc & a_ChunkDesc) // Interpolate between FloorLo and FloorHi: for (int z = 0; z < 16; z++) for (int x = 0; x < 16; x++) { - switch (a_ChunkDesc.GetBiome(x, z)) + EMCSBiome biome = a_ChunkDesc.GetBiome(x, z); + + if ((biome == biExtremeHills) || (biome == biExtremeHillsEdge)) { - case biExtremeHills: - case biExtremeHillsEdge: + int Lo = FloorLo[x + 17 * z] / 256; + int Hi = FloorHi[x + 17 * z] / 256; + for (int y = 0; y < SEGMENT_HEIGHT; y++) { - int Lo = FloorLo[x + 17 * z] / 256; - int Hi = FloorHi[x + 17 * z] / 256; - for (int y = 0; y < SEGMENT_HEIGHT; y++) + int Val = Lo + (Hi - Lo) * y / SEGMENT_HEIGHT; + if (Val < 0) { - int Val = Lo + (Hi - Lo) * y / SEGMENT_HEIGHT; - if (Val < 0) - { - a_ChunkDesc.SetBlockType(x, y + Segment, z, E_BLOCK_AIR); - } - } // for y - break; - } - } // switch (biome) + a_ChunkDesc.SetBlockType(x, y + Segment, z, E_BLOCK_AIR); + } + } // for y + break; + } // if (biome) } // for z, x // Swap the floors: diff --git a/src/Generating/Trees.cpp b/src/Generating/Trees.cpp index 4f1553c36..522f45148 100644 --- a/src/Generating/Trees.cpp +++ b/src/Generating/Trees.cpp @@ -174,7 +174,7 @@ void GetTreeImageByBiome(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_No { GetBirchTreeImage(a_BlockX, a_BlockY, a_BlockZ, a_Noise, a_Seq, a_LogBlocks, a_OtherBlocks); } - break; + return; } case biTaiga: @@ -184,14 +184,14 @@ void GetTreeImageByBiome(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_No { // Conifers GetConiferTreeImage(a_BlockX, a_BlockY, a_BlockZ, a_Noise, a_Seq, a_LogBlocks, a_OtherBlocks); - break; + return; } case biSwampland: { // Swamp trees: GetSwampTreeImage(a_BlockX, a_BlockY, a_BlockZ, a_Noise, a_Seq, a_LogBlocks, a_OtherBlocks); - break; + return; } case biJungle: @@ -207,21 +207,21 @@ void GetTreeImageByBiome(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_No { GetJungleTreeImage(a_BlockX, a_BlockY, a_BlockZ, a_Noise, a_Seq, a_LogBlocks, a_OtherBlocks); } - break; + return; } case biBirchForest: case biBirchForestHills: { GetBirchTreeImage(a_BlockX, a_BlockY, a_BlockZ, a_Noise, a_Seq, a_LogBlocks, a_OtherBlocks); - break; + return; } case biBirchForestM: case biBirchForestHillsM: { GetTallBirchTreeImage(a_BlockX, a_BlockY, a_BlockZ, a_Noise, a_Seq, a_LogBlocks, a_OtherBlocks); - break; + return; } case biRoofedForest: @@ -257,9 +257,29 @@ void GetTreeImageByBiome(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_No { // TODO: These need their special trees GetBirchTreeImage(a_BlockX, a_BlockY, a_BlockZ, a_Noise, a_Seq, a_LogBlocks, a_OtherBlocks); - break; + return; + } + + case biDesert: + case biDesertHills: + case biRiver: + case biBeach: + case biHell: + case biSky: + case biOcean: + case biFrozenOcean: + case biFrozenRiver: + case biVariant: + case biNumBiomes: + case biNumVariantBiomes: + case biInvalidBiome: + { + // These biomes have no trees, or are non-biome members of the enum. + return; } } + + ASSERT(!"Invalid biome type!"); } diff --git a/src/HTTPServer/HTTPConnection.cpp b/src/HTTPServer/HTTPConnection.cpp index da4df0e34..b127e7091 100644 --- a/src/HTTPServer/HTTPConnection.cpp +++ b/src/HTTPServer/HTTPConnection.cpp @@ -26,6 +26,7 @@ cHTTPConnection::cHTTPConnection(cHTTPServer & a_HTTPServer) : cHTTPConnection::~cHTTPConnection() { + // LOGD("HTTP: Connection deleting: %p", this); delete m_CurrentRequest; } @@ -144,7 +145,7 @@ void cHTTPConnection::Terminate(void) -void cHTTPConnection::DataReceived(const char * a_Data, size_t a_Size) +bool cHTTPConnection::DataReceived(const char * a_Data, size_t a_Size) { switch (m_State) { @@ -162,12 +163,12 @@ void cHTTPConnection::DataReceived(const char * a_Data, size_t a_Size) m_CurrentRequest = NULL; m_State = wcsInvalid; m_HTTPServer.CloseConnection(*this); - return; + return true; } if (m_CurrentRequest->IsInHeaders()) { // The request headers are not yet complete - return; + return false; } // The request has finished parsing its headers successfully, notify of it: @@ -183,13 +184,12 @@ void cHTTPConnection::DataReceived(const char * a_Data, size_t a_Size) // Process the rest of the incoming data into the request body: if (a_Size > BytesConsumed) { - DataReceived(a_Data + BytesConsumed, a_Size - BytesConsumed); + return cHTTPConnection::DataReceived(a_Data + BytesConsumed, a_Size - BytesConsumed); } else { - DataReceived("", 0); // If the request has zero body length, let it be processed right-away + return cHTTPConnection::DataReceived("", 0); // If the request has zero body length, let it be processed right-away } - break; } case wcsRecvBody: @@ -209,7 +209,7 @@ void cHTTPConnection::DataReceived(const char * a_Data, size_t a_Size) { m_State = wcsInvalid; m_HTTPServer.CloseConnection(*this); - return; + return true; } delete m_CurrentRequest; m_CurrentRequest = NULL; @@ -223,6 +223,7 @@ void cHTTPConnection::DataReceived(const char * a_Data, size_t a_Size) break; } } + return false; } diff --git a/src/HTTPServer/HTTPConnection.h b/src/HTTPServer/HTTPConnection.h index fc11f1ba6..6ea8a1ae8 100644 --- a/src/HTTPServer/HTTPConnection.h +++ b/src/HTTPServer/HTTPConnection.h @@ -91,9 +91,15 @@ protected: // cSocketThreads::cCallback overrides: - virtual void DataReceived (const char * a_Data, size_t a_Size) override; // Data is received from the client - virtual void GetOutgoingData(AString & a_Data) override; // Data can be sent to client - virtual void SocketClosed (void) override; // The socket has been closed for any reason + /** Data is received from the client. + Returns true if the connection has been closed as the result of parsing the data. */ + virtual bool DataReceived(const char * a_Data, size_t a_Size) override; + + /** Data can be sent to client */ + virtual void GetOutgoingData(AString & a_Data) override; + + /** The socket has been closed for any reason */ + virtual void SocketClosed(void) override; } ; typedef std::vector<cHTTPConnection *> cHTTPConnections; diff --git a/src/HTTPServer/HTTPMessage.cpp b/src/HTTPServer/HTTPMessage.cpp index 4a3611050..44feda469 100644 --- a/src/HTTPServer/HTTPMessage.cpp +++ b/src/HTTPServer/HTTPMessage.cpp @@ -201,7 +201,7 @@ size_t cHTTPRequest::ParseRequestLine(const char * a_Data, size_t a_Size) return AString::npos; } // Check that there's HTTP/version at the end - if (strncmp(a_Data + URLEnd + 1, "HTTP/1.", 7) != 0) + if (strncmp(m_IncomingHeaderData.c_str() + URLEnd + 1, "HTTP/1.", 7) != 0) { m_IsValid = false; return AString::npos; diff --git a/src/HTTPServer/HTTPServer.cpp b/src/HTTPServer/HTTPServer.cpp index eaf8405a3..d288c83c9 100644 --- a/src/HTTPServer/HTTPServer.cpp +++ b/src/HTTPServer/HTTPServer.cpp @@ -8,6 +8,7 @@ #include "HTTPMessage.h" #include "HTTPConnection.h" #include "HTTPFormParser.h" +#include "SslHTTPConnection.h" @@ -142,6 +143,41 @@ cHTTPServer::~cHTTPServer() bool cHTTPServer::Initialize(const AString & a_PortsIPv4, const AString & a_PortsIPv6) { + // Read the HTTPS cert + key: + AString CertFile = cFile::ReadWholeFile("webadmin/httpscert.crt"); + AString KeyFile = cFile::ReadWholeFile("webadmin/httpskey.pem"); + if (!CertFile.empty() && !KeyFile.empty()) + { + m_Cert.reset(new cX509Cert); + int res = m_Cert->Parse(CertFile.data(), CertFile.size()); + if (res == 0) + { + m_CertPrivKey.reset(new cCryptoKey); + int res2 = m_CertPrivKey->ParsePrivate(KeyFile.data(), KeyFile.size(), ""); + if (res2 != 0) + { + // Reading the private key failed, reset the cert: + LOGWARNING("WebServer: Cannot read HTTPS certificate private key: -0x%x", -res2); + m_Cert.reset(); + } + } + else + { + LOGWARNING("WebServer: Cannot read HTTPS certificate: -0x%x", -res); + } + } + + // Notify the admin about the HTTPS / HTTP status + if (m_Cert.get() == NULL) + { + LOGWARNING("WebServer: The server is running in unsecure HTTP mode."); + } + else + { + LOGINFO("WebServer: The server is running in secure HTTPS mode."); + } + + // Open up requested ports: bool HasAnyPort; HasAnyPort = m_ListenThreadIPv4.Initialize(a_PortsIPv4); HasAnyPort = m_ListenThreadIPv6.Initialize(a_PortsIPv6) || HasAnyPort; @@ -195,7 +231,15 @@ void cHTTPServer::Stop(void) void cHTTPServer::OnConnectionAccepted(cSocket & a_Socket) { - cHTTPConnection * Connection = new cHTTPConnection(*this); + cHTTPConnection * Connection; + if (m_Cert.get() != NULL) + { + Connection = new cSslHTTPConnection(*this, m_Cert, m_CertPrivKey); + } + else + { + Connection = new cHTTPConnection(*this); + } m_SocketThreads.AddClient(a_Socket, Connection); cCSLock Lock(m_CSConnections); m_Connections.push_back(Connection); diff --git a/src/HTTPServer/HTTPServer.h b/src/HTTPServer/HTTPServer.h index 8eff7d879..522b7da62 100644 --- a/src/HTTPServer/HTTPServer.h +++ b/src/HTTPServer/HTTPServer.h @@ -12,6 +12,9 @@ #include "../OSSupport/ListenThread.h" #include "../OSSupport/SocketThreads.h" #include "inifile/iniFile.h" +#include "PolarSSL++/RsaPrivateKey.h" +#include "PolarSSL++/CryptoKey.h" +#include "PolarSSL++/X509Cert.h" @@ -66,6 +69,7 @@ public: protected: friend class cHTTPConnection; + friend class cSslHTTPConnection; cListenThread m_ListenThreadIPv4; cListenThread m_ListenThreadIPv6; @@ -78,6 +82,12 @@ protected: /// The callbacks to call for various events cCallbacks * m_Callbacks; + /** The server certificate to use for the SSL connections */ + cX509CertPtr m_Cert; + + /** The private key for m_Cert. */ + cCryptoKeyPtr m_CertPrivKey; + // cListenThread::cCallback overrides: virtual void OnConnectionAccepted(cSocket & a_Socket) override; diff --git a/src/HTTPServer/SslHTTPConnection.cpp b/src/HTTPServer/SslHTTPConnection.cpp new file mode 100644 index 000000000..d237089d9 --- /dev/null +++ b/src/HTTPServer/SslHTTPConnection.cpp @@ -0,0 +1,107 @@ + +// SslHTTPConnection.cpp + +// Implements the cSslHTTPConnection class representing a HTTP connection made over a SSL link + +#include "Globals.h" +#include "SslHTTPConnection.h" +#include "HTTPServer.h" + + + + + +cSslHTTPConnection::cSslHTTPConnection(cHTTPServer & a_HTTPServer, const cX509CertPtr & a_Cert, const cCryptoKeyPtr & a_PrivateKey) : + super(a_HTTPServer), + m_Ssl(64000), + m_Cert(a_Cert), + m_PrivateKey(a_PrivateKey) +{ + m_Ssl.Initialize(false); + m_Ssl.SetOwnCert(a_Cert, a_PrivateKey); +} + + + + + +bool cSslHTTPConnection::DataReceived(const char * a_Data, size_t a_Size) +{ + // If there is outgoing data in the queue, notify the server that it should write it out: + if (!m_OutgoingData.empty()) + { + m_HTTPServer.NotifyConnectionWrite(*this); + } + + // Process the received data: + const char * Data = a_Data; + size_t Size = a_Size; + for (;;) + { + // Try to write as many bytes into Ssl's "incoming" buffer as possible: + size_t BytesWritten = 0; + if (Size > 0) + { + BytesWritten = m_Ssl.WriteIncoming(Data, Size); + Data += BytesWritten; + Size -= BytesWritten; + } + + // Try to read as many bytes from SSL's decryption as possible: + char Buffer[32000]; + int NumRead = m_Ssl.ReadPlain(Buffer, sizeof(Buffer)); + if (NumRead > 0) + { + if (super::DataReceived(Buffer, (size_t)NumRead)) + { + // The socket has been closed, and the object is already deleted. Bail out. + return true; + } + } + + // If both failed, bail out: + if ((BytesWritten == 0) && (NumRead <= 0)) + { + return false; + } + } +} + + + + + +void cSslHTTPConnection::GetOutgoingData(AString & a_Data) +{ + for (;;) + { + // Write as many bytes from our buffer to SSL's encryption as possible: + int NumWritten = 0; + if (!m_OutgoingData.empty()) + { + NumWritten = m_Ssl.WritePlain(m_OutgoingData.data(), m_OutgoingData.size()); + if (NumWritten > 0) + { + m_OutgoingData.erase(0, (size_t)NumWritten); + } + } + + // Read as many bytes from SSL's "outgoing" buffer as possible: + char Buffer[32000]; + size_t NumBytes = m_Ssl.ReadOutgoing(Buffer, sizeof(Buffer)); + if (NumBytes > 0) + { + a_Data.append(Buffer, NumBytes); + } + + // If both failed, bail out: + if ((NumWritten <= 0) && (NumBytes == 0)) + { + return; + } + } +} + + + + diff --git a/src/HTTPServer/SslHTTPConnection.h b/src/HTTPServer/SslHTTPConnection.h new file mode 100644 index 000000000..c2c1585cd --- /dev/null +++ b/src/HTTPServer/SslHTTPConnection.h @@ -0,0 +1,45 @@ + +// SslHTTPConnection.h + +// Declared the cSslHTTPConnection class representing a HTTP connection made over a SSL link + + + + + +#pragma once + +#include "HTTPConnection.h" +#include "PolarSSL++/BufferedSslContext.h" + + + + + +class cSslHTTPConnection : + public cHTTPConnection +{ + typedef cHTTPConnection super; + +public: + /** Creates a new connection on the specified server. + Sends the specified cert as the server certificate, uses the private key for decryption. */ + cSslHTTPConnection(cHTTPServer & a_HTTPServer, const cX509CertPtr & a_Cert, const cCryptoKeyPtr & a_PrivateKey); + +protected: + cBufferedSslContext m_Ssl; + + /** The certificate to send to the client */ + cX509CertPtr m_Cert; + + /** The private key used for the certificate */ + cCryptoKeyPtr m_PrivateKey; + + // cHTTPConnection overrides: + virtual bool DataReceived (const char * a_Data, size_t a_Size) override; // Data is received from the client + virtual void GetOutgoingData(AString & a_Data) override; // Data can be sent to client +} ; + + + + diff --git a/src/Inventory.cpp b/src/Inventory.cpp index a365e4ed4..bce882c88 100644 --- a/src/Inventory.cpp +++ b/src/Inventory.cpp @@ -243,6 +243,16 @@ void cInventory::SetHotbarSlot(int a_HotBarSlotNum, const cItem & a_Item) +void cInventory::SendEquippedSlot() +{ + int EquippedSlotNum = cInventory::invArmorCount + cInventory::invInventoryCount + GetEquippedSlotNum(); + SendSlot(EquippedSlotNum); +} + + + + + const cItem & cInventory::GetSlot(int a_SlotNum) const { if ((a_SlotNum < 0) || (a_SlotNum >= invNumSlots)) diff --git a/src/Inventory.h b/src/Inventory.h index 1ad7c4776..39aef1538 100644 --- a/src/Inventory.h +++ b/src/Inventory.h @@ -56,13 +56,13 @@ public: // tolua_begin - /// Removes all items from the entire inventory + /** Removes all items from the entire inventory */ void Clear(void); - /// Returns number of items out of a_ItemStack that can fit in the storage + /** Returns number of items out of a_ItemStack that can fit in the storage */ int HowManyCanFit(const cItem & a_ItemStack, bool a_ConsiderEmptySlots); - /// Returns how many items of the specified type would fit into the slot range specified + /** Returns how many items of the specified type would fit into the slot range specified */ int HowManyCanFit(const cItem & a_ItemStack, int a_BeginSlotNum, int a_EndSlotNum, bool a_ConsiderEmptySlots); /** Adds as many items out of a_ItemStack as can fit. @@ -86,33 +86,36 @@ public: */ int AddItems(cItems & a_ItemStackList, bool a_AllowNewStacks, bool a_tryToFillEquippedFirst); - /// Removes one item out of the currently equipped item stack, returns true if successful, false if empty-handed + /** Removes one item out of the currently equipped item stack, returns true if successful, false if empty-handed */ bool RemoveOneEquippedItem(void); - /// Returns the number of items of type a_Item that are stored + /** Returns the number of items of type a_Item that are stored */ int HowManyItems(const cItem & a_Item); - /// Returns true if there are at least as many items of type a_ItemStack as in a_ItemStack + /** Returns true if there are at least as many items of type a_ItemStack as in a_ItemStack */ bool HasItems(const cItem & a_ItemStack); + + /** Sends the equipped item slot to the client */ + void SendEquippedSlot(); - /// Returns the cItemGrid object representing the armor slots + /** Returns the cItemGrid object representing the armor slots */ cItemGrid & GetArmorGrid(void) { return m_ArmorSlots; } - /// Returns the cItemGrid object representing the main inventory slots + /** Returns the cItemGrid object representing the main inventory slots */ cItemGrid & GetInventoryGrid(void) { return m_InventorySlots; } - /// Returns the cItemGrid object representing the hotbar slots + /** Returns the cItemGrid object representing the hotbar slots */ cItemGrid & GetHotbarGrid(void) { return m_HotbarSlots; } - /// Returns the player associated with this inventory + /** Returns the player associated with this inventory */ cPlayer & GetOwner(void) { return m_Owner; } - /// Copies the non-empty slots into a_ItemStacks; preserves the original a_Items contents + /** Copies the non-empty slots into a_ItemStacks; preserves the original a_Items contents */ void CopyToItems(cItems & a_Items); // tolua_end - /// Returns the player associated with this inventory (const version) + /** Returns the player associated with this inventory (const version) */ const cPlayer & GetOwner(void) const { return m_Owner; } // tolua_begin @@ -136,10 +139,10 @@ public: */ int ChangeSlotCount(int a_SlotNum, int a_AddToCount); - /// Adds the specified damage to the specified item; deletes the item and returns true if the item broke. + /** Adds the specified damage to the specified item; deletes the item and returns true if the item broke. */ bool DamageItem(int a_SlotNum, short a_Amount); - /// Adds the specified damage to the currently held item; deletes the item and returns true if the item broke. + /** Adds the specified damage to the currently held item; deletes the item and returns true if the item broke. */ bool DamageEquippedItem(short a_Amount = 1); const cItem & GetEquippedHelmet (void) const { return m_ArmorSlots.GetSlot(0); } @@ -149,13 +152,13 @@ public: // tolua_end - /// Sends the slot contents to the owner + /** Sends the slot contents to the owner */ void SendSlot(int a_SlotNum); - /// Update items (e.g. Maps) + /** Update items (e.g. Maps) */ void UpdateItems(void); - /// Converts an armor slot number into the ID for the EntityEquipment packet + /** Converts an armor slot number into the ID for the EntityEquipment packet */ static int ArmorSlotNumToEntityEquipmentID(short a_ArmorSlotNum); void SaveToJson(Json::Value & a_Value); @@ -172,10 +175,10 @@ protected: cPlayer & m_Owner; - /// Returns the ItemGrid and the (grid-local) slot number for a (global) slot number; return NULL for invalid SlotNum + /** Returns the ItemGrid and the (grid-local) slot number for a (global) slot number; return NULL for invalid SlotNum */ const cItemGrid * GetGridForSlotNum(int a_SlotNum, int & a_GridSlotNum) const; - /// Returns the ItemGrid and the (grid-local) slot number for a (global) slot number; return NULL for invalid SlotNum + /** Returns the ItemGrid and the (grid-local) slot number for a (global) slot number; return NULL for invalid SlotNum */ cItemGrid * GetGridForSlotNum(int a_SlotNum, int & a_GridSlotNum); // cItemGrid::cListener override: diff --git a/src/Item.h b/src/Item.h index 2f65d5344..acbc880dc 100644 --- a/src/Item.h +++ b/src/Item.h @@ -228,7 +228,7 @@ public: void Add (const cItem & a_Item) {push_back(a_Item); } void Delete(int a_Idx); void Clear (void) {clear(); } - size_t Size (void) {return size(); } + size_t Size (void) const { return size(); } void Set (int a_Idx, short a_ItemType, char a_ItemCount, short a_ItemDamage); void Add (short a_ItemType, char a_ItemCount, short a_ItemDamage) diff --git a/src/Mobs/Monster.cpp b/src/Mobs/Monster.cpp index 62670907f..a9ca7a2fa 100644 --- a/src/Mobs/Monster.cpp +++ b/src/Mobs/Monster.cpp @@ -355,6 +355,8 @@ void cMonster::Tick(float a_Dt, cChunk & a_Chunk) InStateEscaping(a_Dt); break; } + + case ATTACKING: break; } // switch (m_EMState) BroadcastMovementUpdate(); @@ -464,8 +466,10 @@ bool cMonster::DoTakeDamage(TakeDamageInfo & a_TDI) return false; } - if((m_SoundHurt != "") && (m_Health > 0)) + if (!m_SoundHurt.empty() && (m_Health > 0)) + { m_World->BroadcastSoundEffect(m_SoundHurt, (int)(GetPosX() * 8), (int)(GetPosY() * 8), (int)(GetPosZ() * 8), 1.0f, 0.8f); + } if (a_TDI.Attacker != NULL) { diff --git a/src/OSSupport/SocketThreads.h b/src/OSSupport/SocketThreads.h index 679e374e1..944f5f3bc 100644 --- a/src/OSSupport/SocketThreads.h +++ b/src/OSSupport/SocketThreads.h @@ -63,8 +63,10 @@ public: // Force a virtual destructor in all subclasses: virtual ~cCallback() {} - /** Called when data is received from the remote party */ - virtual void DataReceived(const char * a_Data, size_t a_Size) = 0; + /** Called when data is received from the remote party. + SocketThreads does not care about the return value, others can use it for their specific purpose - + for example HTTPServer uses it to signal if the connection was terminated as a result of the data received. */ + virtual bool DataReceived(const char * a_Data, size_t a_Size) = 0; /** Called when data can be sent to remote party The function is supposed to *set* outgoing data to a_Data (overwrite) */ diff --git a/src/PolarSSL++/BufferedSslContext.cpp b/src/PolarSSL++/BufferedSslContext.cpp index 885b30c68..9f7caeb8a 100644 --- a/src/PolarSSL++/BufferedSslContext.cpp +++ b/src/PolarSSL++/BufferedSslContext.cpp @@ -20,6 +20,37 @@ cBufferedSslContext::cBufferedSslContext(size_t a_BufferSize): +size_t cBufferedSslContext::WriteIncoming(const void * a_Data, size_t a_NumBytes) +{ + size_t NumBytes = std::min(m_IncomingData.GetFreeSpace(), a_NumBytes); + if (NumBytes > 0) + { + m_IncomingData.Write(a_Data, NumBytes); + return NumBytes; + } + return 0; +} + + + + + +size_t cBufferedSslContext::ReadOutgoing(void * a_Data, size_t a_DataMaxSize) +{ + size_t NumBytes = std::min(m_OutgoingData.GetReadableSpace(), a_DataMaxSize); + if (NumBytes > 0) + { + m_OutgoingData.ReadBuf(a_Data, NumBytes); + m_OutgoingData.CommitRead(); + return NumBytes; + } + return 0; +} + + + + + int cBufferedSslContext::ReceiveEncrypted(unsigned char * a_Buffer, size_t a_NumBytes) { // Called when PolarSSL wants to read encrypted data from the SSL peer diff --git a/src/PolarSSL++/CMakeLists.txt b/src/PolarSSL++/CMakeLists.txt index b0a592760..9a59cdb2c 100644 --- a/src/PolarSSL++/CMakeLists.txt +++ b/src/PolarSSL++/CMakeLists.txt @@ -1,4 +1,3 @@ - cmake_minimum_required (VERSION 2.6) project (MCServer) @@ -11,8 +10,8 @@ set(SOURCES BufferedSslContext.cpp CallbackSslContext.cpp CtrDrbgContext.cpp + CryptoKey.cpp EntropyContext.cpp - PublicKey.cpp RsaPrivateKey.cpp Sha1Checksum.cpp SslContext.cpp @@ -26,8 +25,8 @@ set(HEADERS BufferedSslContext.h CallbackSslContext.h CtrDrbgContext.h + CryptoKey.h EntropyContext.h - PublicKey.h RsaPrivateKey.h SslContext.h Sha1Checksum.h diff --git a/src/PolarSSL++/CryptoKey.cpp b/src/PolarSSL++/CryptoKey.cpp new file mode 100644 index 000000000..0763c387b --- /dev/null +++ b/src/PolarSSL++/CryptoKey.cpp @@ -0,0 +1,149 @@ + +// CryptoKey.cpp + +// Implements the cCryptoKey class representing a RSA public key in PolarSSL + +#include "Globals.h" +#include "CryptoKey.h" + + + + + +cCryptoKey::cCryptoKey(void) +{ + pk_init(&m_Pk); + m_CtrDrbg.Initialize("rsa_pubkey", 10); +} + + + + + +cCryptoKey::cCryptoKey(const AString & a_PublicKeyData) +{ + pk_init(&m_Pk); + m_CtrDrbg.Initialize("rsa_pubkey", 10); + int res = ParsePublic(a_PublicKeyData.data(), a_PublicKeyData.size()); + if (res != 0) + { + LOGWARNING("Failed to parse public key: -0x%x", res); + ASSERT(!"Cannot parse PubKey"); + return; + } +} + + + + + +cCryptoKey::cCryptoKey(const AString & a_PrivateKeyData, const AString & a_Password) +{ + pk_init(&m_Pk); + m_CtrDrbg.Initialize("rsa_privkey", 11); + int res = ParsePrivate(a_PrivateKeyData.data(), a_PrivateKeyData.size(), a_Password); + if (res != 0) + { + LOGWARNING("Failed to parse private key: -0x%x", res); + ASSERT(!"Cannot parse PubKey"); + return; + } +} + + + + + +cCryptoKey::~cCryptoKey() +{ + pk_free(&m_Pk); +} + + + + + +int cCryptoKey::Decrypt(const Byte * a_EncryptedData, size_t a_EncryptedLength, Byte * a_DecryptedData, size_t a_DecryptedMaxLength) +{ + ASSERT(IsValid()); + + size_t DecryptedLen = a_DecryptedMaxLength; + int res = pk_decrypt(&m_Pk, + a_EncryptedData, a_EncryptedLength, + a_DecryptedData, &DecryptedLen, a_DecryptedMaxLength, + ctr_drbg_random, m_CtrDrbg.GetInternal() + ); + if (res != 0) + { + return res; + } + return (int)DecryptedLen; +} + + + + + +int cCryptoKey::Encrypt(const Byte * a_PlainData, size_t a_PlainLength, Byte * a_EncryptedData, size_t a_EncryptedMaxLength) +{ + ASSERT(IsValid()); + + size_t EncryptedLength = a_EncryptedMaxLength; + int res = pk_encrypt(&m_Pk, + a_PlainData, a_PlainLength, a_EncryptedData, &EncryptedLength, a_EncryptedMaxLength, + ctr_drbg_random, m_CtrDrbg.GetInternal() + ); + if (res != 0) + { + return res; + } + return (int)EncryptedLength; +} + + + + + + +int cCryptoKey::ParsePublic(const void * a_Data, size_t a_NumBytes) +{ + ASSERT(!IsValid()); // Cannot parse a second key + + return pk_parse_public_key(&m_Pk, (const unsigned char *)a_Data, a_NumBytes); +} + + + + + + +int cCryptoKey::ParsePrivate(const void * a_Data, size_t a_NumBytes, const AString & a_Password) +{ + ASSERT(!IsValid()); // Cannot parse a second key + + if (a_Password.empty()) + { + return pk_parse_key(&m_Pk, (const unsigned char *)a_Data, a_NumBytes, NULL, 0); + } + else + { + return pk_parse_key( + &m_Pk, + (const unsigned char *)a_Data, a_NumBytes, + (const unsigned char *)a_Password.c_str(), a_Password.size() + ); + } +} + + + + + +bool cCryptoKey::IsValid(void) const +{ + return (pk_get_type(&m_Pk) != POLARSSL_PK_NONE); +} + + + + diff --git a/src/PolarSSL++/CryptoKey.h b/src/PolarSSL++/CryptoKey.h new file mode 100644 index 000000000..9c298e501 --- /dev/null +++ b/src/PolarSSL++/CryptoKey.h @@ -0,0 +1,76 @@ + +// CryptoKey.h + +// Declares the cCryptoKey class representing a RSA public key in PolarSSL + + + + + +#pragma once + +#include "CtrDrbgContext.h" +#include "polarssl/pk.h" + + + + + +class cCryptoKey +{ + friend class cSslContext; + +public: + /** Constructs an empty key instance. Before use, it needs to be filled by ParsePublic() or ParsePrivate() */ + cCryptoKey(void); + + /** Constructs the public key out of the DER- or PEM-encoded pubkey data */ + cCryptoKey(const AString & a_PublicKeyData); + + /** Constructs the private key out of the DER- or PEM-encoded privkey data, with the specified password. + If a_Password is empty, no password is assumed. */ + cCryptoKey(const AString & a_PrivateKeyData, const AString & a_Password); + + ~cCryptoKey(); + + /** Decrypts the data using the stored public key + Both a_EncryptedData and a_DecryptedData must be at least <KeySizeBytes> bytes large. + Returns the number of bytes decrypted, or negative number for error. */ + int Decrypt(const Byte * a_EncryptedData, size_t a_EncryptedLength, Byte * a_DecryptedData, size_t a_DecryptedMaxLength); + + /** Encrypts the data using the stored public key + Both a_EncryptedData and a_DecryptedData must be at least <KeySizeBytes> bytes large. + Returns the number of bytes decrypted, or negative number for error. */ + int Encrypt(const Byte * a_PlainData, size_t a_PlainLength, Byte * a_EncryptedData, size_t a_EncryptedMaxLength); + + /** Parses the specified data into a public key representation. + The key can be DER- or PEM-encoded. + Returns 0 on success, PolarSSL error code on failure. */ + int ParsePublic(const void * a_Data, size_t a_NumBytes); + + /** Parses the specified data into a private key representation. + If a_Password is empty, no password is assumed. + The key can be DER- or PEM-encoded. + Returns 0 on success, PolarSSL error code on failure. */ + int ParsePrivate(const void * a_Data, size_t a_NumBytes, const AString & a_Password); + + /** Returns true if the contained key is valid. */ + bool IsValid(void) const; + +protected: + /** The PolarSSL representation of the key data */ + pk_context m_Pk; + + /** The random generator used in encryption and decryption */ + cCtrDrbgContext m_CtrDrbg; + + + /** Returns the internal context ptr. Only use in PolarSSL API calls. */ + pk_context * GetInternal(void) { return &m_Pk; } +} ; + +typedef SharedPtr<cCryptoKey> cCryptoKeyPtr; + + + + diff --git a/src/PolarSSL++/CtrDrbgContext.h b/src/PolarSSL++/CtrDrbgContext.h index 65e9a2374..230db8753 100644 --- a/src/PolarSSL++/CtrDrbgContext.h +++ b/src/PolarSSL++/CtrDrbgContext.h @@ -26,7 +26,7 @@ class cCtrDrbgContext { friend class cSslContext; friend class cRsaPrivateKey; - friend class cPublicKey; + friend class cCryptoKey; public: /** Constructs the context with a new entropy context. */ diff --git a/src/PolarSSL++/PublicKey.cpp b/src/PolarSSL++/PublicKey.cpp deleted file mode 100644 index 49794a0c8..000000000 --- a/src/PolarSSL++/PublicKey.cpp +++ /dev/null @@ -1,73 +0,0 @@ - -// PublicKey.cpp - -// Implements the cPublicKey class representing a RSA public key in PolarSSL - -#include "Globals.h" -#include "PublicKey.h" - - - - - -cPublicKey::cPublicKey(const AString & a_PublicKeyDER) -{ - pk_init(&m_Pk); - if (pk_parse_public_key(&m_Pk, (const Byte *)a_PublicKeyDER.data(), a_PublicKeyDER.size()) != 0) - { - ASSERT(!"Cannot parse PubKey"); - return; - } - m_CtrDrbg.Initialize("rsa_pubkey", 10); -} - - - - - -cPublicKey::~cPublicKey() -{ - pk_free(&m_Pk); -} - - - - - -int cPublicKey::Decrypt(const Byte * a_EncryptedData, size_t a_EncryptedLength, Byte * a_DecryptedData, size_t a_DecryptedMaxLength) -{ - size_t DecryptedLen = a_DecryptedMaxLength; - int res = pk_decrypt(&m_Pk, - a_EncryptedData, a_EncryptedLength, - a_DecryptedData, &DecryptedLen, a_DecryptedMaxLength, - ctr_drbg_random, m_CtrDrbg.GetInternal() - ); - if (res != 0) - { - return res; - } - return (int)DecryptedLen; -} - - - - - -int cPublicKey::Encrypt(const Byte * a_PlainData, size_t a_PlainLength, Byte * a_EncryptedData, size_t a_EncryptedMaxLength) -{ - size_t EncryptedLength = a_EncryptedMaxLength; - int res = pk_encrypt(&m_Pk, - a_PlainData, a_PlainLength, a_EncryptedData, &EncryptedLength, a_EncryptedMaxLength, - ctr_drbg_random, m_CtrDrbg.GetInternal() - ); - if (res != 0) - { - return res; - } - return (int)EncryptedLength; -} - - - - - diff --git a/src/PolarSSL++/PublicKey.h b/src/PolarSSL++/PublicKey.h deleted file mode 100644 index 5a0a57147..000000000 --- a/src/PolarSSL++/PublicKey.h +++ /dev/null @@ -1,48 +0,0 @@ - -// PublicKey.h - -// Declares the cPublicKey class representing a RSA public key in PolarSSL - - - - - -#pragma once - -#include "CtrDrbgContext.h" -#include "polarssl/pk.h" - - - - - -class cPublicKey -{ -public: - /** Constructs the public key out of the DER-encoded pubkey data */ - cPublicKey(const AString & a_PublicKeyDER); - - ~cPublicKey(); - - /** Decrypts the data using the stored public key - Both a_EncryptedData and a_DecryptedData must be at least <KeySizeBytes> bytes large. - Returns the number of bytes decrypted, or negative number for error. */ - int Decrypt(const Byte * a_EncryptedData, size_t a_EncryptedLength, Byte * a_DecryptedData, size_t a_DecryptedMaxLength); - - /** Encrypts the data using the stored public key - Both a_EncryptedData and a_DecryptedData must be at least <KeySizeBytes> bytes large. - Returns the number of bytes decrypted, or negative number for error. */ - int Encrypt(const Byte * a_PlainData, size_t a_PlainLength, Byte * a_EncryptedData, size_t a_EncryptedMaxLength); - -protected: - /** The public key PolarSSL representation */ - pk_context m_Pk; - - /** The random generator used in encryption and decryption */ - cCtrDrbgContext m_CtrDrbg; -} ; - - - - - diff --git a/src/PolarSSL++/RsaPrivateKey.h b/src/PolarSSL++/RsaPrivateKey.h index ffacde11b..4d03f27df 100644 --- a/src/PolarSSL++/RsaPrivateKey.h +++ b/src/PolarSSL++/RsaPrivateKey.h @@ -19,6 +19,8 @@ /** Encapsulates an RSA private key used in PKI cryptography */ class cRsaPrivateKey { + friend class cSslContext; + public: /** Creates a new empty object, the key is not assigned */ cRsaPrivateKey(void); @@ -51,8 +53,14 @@ protected: /** The random generator used for generating the key and encryption / decryption */ cCtrDrbgContext m_CtrDrbg; + + + /** Returns the internal context ptr. Only use in PolarSSL API calls. */ + rsa_context * GetInternal(void) { return &m_Rsa; } } ; +typedef SharedPtr<cRsaPrivateKey> cRsaPrivateKeyPtr; + diff --git a/src/PolarSSL++/SslContext.cpp b/src/PolarSSL++/SslContext.cpp index 1994cf844..c3074f197 100644 --- a/src/PolarSSL++/SslContext.cpp +++ b/src/PolarSSL++/SslContext.cpp @@ -40,7 +40,7 @@ int cSslContext::Initialize(bool a_IsClient, const SharedPtr<cCtrDrbgContext> & if (m_IsValid) { LOGWARNING("SSL: Double initialization is not supported."); - return POLARSSL_ERR_SSL_MALLOC_FAILED; // There is no return value well-suited for this, reuse this one. + return POLARSSL_ERR_SSL_BAD_INPUT_DATA; // There is no return value well-suited for this, reuse this one. } // Set the CtrDrbg context, create a new one if needed: @@ -59,7 +59,7 @@ int cSslContext::Initialize(bool a_IsClient, const SharedPtr<cCtrDrbgContext> & return res; } ssl_set_endpoint(&m_Ssl, a_IsClient ? SSL_IS_CLIENT : SSL_IS_SERVER); - ssl_set_authmode(&m_Ssl, SSL_VERIFY_OPTIONAL); + ssl_set_authmode(&m_Ssl, a_IsClient ? SSL_VERIFY_OPTIONAL : SSL_VERIFY_NONE); // Clients ask for server's cert but don't verify strictly; servers don't ask clients for certs by default ssl_set_rng(&m_Ssl, ctr_drbg_random, &m_CtrDrbg->m_CtrDrbg); ssl_set_bio(&m_Ssl, ReceiveEncrypted, this, SendEncrypted, this); @@ -70,6 +70,18 @@ int cSslContext::Initialize(bool a_IsClient, const SharedPtr<cCtrDrbgContext> & ssl_set_dbg(&m_Ssl, &SSLDebugMessage, this); ssl_set_verify(&m_Ssl, &SSLVerifyCert, this); */ + + /* + // Set ciphersuite to the easiest one to decode, so that the connection can be wireshark-decoded: + static const int CipherSuites[] = + { + TLS_RSA_WITH_RC4_128_MD5, + TLS_RSA_WITH_RC4_128_SHA, + TLS_RSA_WITH_AES_128_CBC_SHA, + 0, // Must be 0-terminated! + }; + ssl_set_ciphersuites(&m_Ssl, CipherSuites); + */ #endif m_IsValid = true; @@ -80,8 +92,56 @@ int cSslContext::Initialize(bool a_IsClient, const SharedPtr<cCtrDrbgContext> & +void cSslContext::SetOwnCert(const cX509CertPtr & a_OwnCert, const cRsaPrivateKeyPtr & a_OwnCertPrivKey) +{ + ASSERT(m_IsValid); // Call Initialize() first + + // Check that both the cert and the key is valid: + if ((a_OwnCert.get() == NULL) || (a_OwnCertPrivKey.get() == NULL)) + { + LOGWARNING("SSL: Own certificate is not valid, skipping the set."); + return; + } + + // Make sure we have the cert stored for later, PolarSSL only uses the cert later on + m_OwnCert = a_OwnCert; + m_OwnCertPrivKey = a_OwnCertPrivKey; + + // Set into the context: + ssl_set_own_cert_rsa(&m_Ssl, m_OwnCert->GetInternal(), m_OwnCertPrivKey->GetInternal()); +} + + + + + +void cSslContext::SetOwnCert(const cX509CertPtr & a_OwnCert, const cCryptoKeyPtr & a_OwnCertPrivKey) +{ + ASSERT(m_IsValid); // Call Initialize() first + + // Check that both the cert and the key is valid: + if ((a_OwnCert.get() == NULL) || (a_OwnCertPrivKey.get() == NULL)) + { + LOGWARNING("SSL: Own certificate is not valid, skipping the set."); + return; + } + + // Make sure we have the cert stored for later, PolarSSL only uses the cert later on + m_OwnCert = a_OwnCert; + m_OwnCertPrivKey2 = a_OwnCertPrivKey; + + // Set into the context: + ssl_set_own_cert(&m_Ssl, m_OwnCert->GetInternal(), m_OwnCertPrivKey2->GetInternal()); +} + + + + + void cSslContext::SetCACerts(const cX509CertPtr & a_CACert, const AString & a_ExpectedPeerName) { + ASSERT(m_IsValid); // Call Initialize() first + // Store the data in our internal buffers, to avoid losing the pointers later on // PolarSSL will need these after this call returns, and the caller may move / delete the data before that: m_ExpectedPeerName = a_ExpectedPeerName; diff --git a/src/PolarSSL++/SslContext.h b/src/PolarSSL++/SslContext.h index 85add5f8b..6b4f2c1e7 100644 --- a/src/PolarSSL++/SslContext.h +++ b/src/PolarSSL++/SslContext.h @@ -11,6 +11,8 @@ #include "polarssl/ssl.h" #include "../ByteBuffer.h" +#include "CryptoKey.h" +#include "RsaPrivateKey.h" #include "X509Cert.h" @@ -38,7 +40,7 @@ public: /** Creates a new uninitialized context */ cSslContext(void); - ~cSslContext(); + virtual ~cSslContext(); /** Initializes the context for use as a server or client. Returns 0 on success, PolarSSL error on failure. */ @@ -47,7 +49,15 @@ public: /** Returns true if the object has been initialized properly. */ bool IsValid(void) const { return m_IsValid; } - /** Sets a cert chain as the trusted cert store for this context. + /** Sets the certificate to use as our own. Must be used when representing a server, optional when client. + Must be called after Initialize(). */ + void SetOwnCert(const cX509CertPtr & a_OwnCert, const cRsaPrivateKeyPtr & a_OwnCertPrivKey); + + /** Sets the certificate to use as our own. Must be used when representing a server, optional when client. + Must be called after Initialize(). */ + void SetOwnCert(const cX509CertPtr & a_OwnCert, const cCryptoKeyPtr & a_OwnCertPrivKey); + + /** Sets a cert chain as the trusted cert store for this context. Must be called after Initialize(). Calling this will switch the context into strict cert verification mode. a_ExpectedPeerName is the CommonName that we expect the SSL peer to have in its cert, if it is different, the verification will fail. An empty string will disable the CN check. */ @@ -93,6 +103,15 @@ protected: /** The SSL context that PolarSSL uses. */ ssl_context m_Ssl; + /** The certificate that we present to the peer. */ + cX509CertPtr m_OwnCert; + + /** Private key for m_OwnCert, if initialized from a cRsaPrivateKey. */ + cRsaPrivateKeyPtr m_OwnCertPrivKey; + + /** Private key for m_OwnCert, if initialized from a cCryptoKey. */ + cCryptoKeyPtr m_OwnCertPrivKey2; + /** True if the SSL handshake has been completed. */ bool m_HasHandshaken; diff --git a/src/Protocol/Protocol.h b/src/Protocol/Protocol.h index 8f152ad37..a543c6361 100644 --- a/src/Protocol/Protocol.h +++ b/src/Protocol/Protocol.h @@ -31,6 +31,7 @@ class cMonster; class cChunkDataSerializer; class cFallingBlock; class cCompositeChat; +class cStatManager; @@ -111,6 +112,7 @@ public: virtual void SendSpawnMob (const cMonster & a_Mob) = 0; virtual void SendSpawnObject (const cEntity & a_Entity, char a_ObjectType, int a_ObjectData, Byte a_Yaw, Byte a_Pitch) = 0; virtual void SendSpawnVehicle (const cEntity & a_Vehicle, char a_VehicleType, char a_VehicleSubType) = 0; + virtual void SendStatistics (const cStatManager & a_Manager) = 0; virtual void SendTabCompletionResults(const AStringVector & a_Results) = 0; virtual void SendTeleportEntity (const cEntity & a_Entity) = 0; virtual void SendThunderbolt (int a_BlockX, int a_BlockY, int a_BlockZ) = 0; diff --git a/src/Protocol/Protocol125.cpp b/src/Protocol/Protocol125.cpp index e7873cf7a..f3bdae3ac 100644 --- a/src/Protocol/Protocol125.cpp +++ b/src/Protocol/Protocol125.cpp @@ -99,6 +99,7 @@ enum PACKET_ENCHANT_ITEM = 0x6C, PACKET_UPDATE_SIGN = 0x82, PACKET_ITEM_DATA = 0x83, + PACKET_INCREMENT_STATISTIC = 0xC8, PACKET_PLAYER_LIST_ITEM = 0xC9, PACKET_PLAYER_ABILITIES = 0xca, PACKET_PLUGIN_MESSAGE = 0xfa, @@ -992,6 +993,33 @@ void cProtocol125::SendSpawnVehicle(const cEntity & a_Vehicle, char a_VehicleTyp +void cProtocol125::SendStatistics(const cStatManager & a_Manager) +{ + /* NOTE: + * Versions prior to minecraft 1.7 use an incremental statistic sync + * method. The current setup does not allow us to implement that, because + * of performance considerations. + */ +#if 0 + for (unsigned int i = 0; i < (unsigned int)statCount; ++i) + { + StatValue Value = m_Manager->GetValue((eStatistic) i); + + unsigned int StatID = cStatInfo::GetID((eStatistic) i); + + cCSLock Lock(m_CSPacket); + WriteByte(PACKET_INCREMENT_STATISTIC); + WriteInt(StatID); + WriteByte(Value); /* Can overflow! */ + Flush(); + } +#endif +} + + + + + void cProtocol125::SendTabCompletionResults(const AStringVector & a_Results) { // This protocol version doesn't support tab completion diff --git a/src/Protocol/Protocol125.h b/src/Protocol/Protocol125.h index 423e58d67..18a626a2d 100644 --- a/src/Protocol/Protocol125.h +++ b/src/Protocol/Protocol125.h @@ -84,6 +84,7 @@ public: 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 SendStatistics (const cStatManager & a_Manager) 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; diff --git a/src/Protocol/Protocol17x.cpp b/src/Protocol/Protocol17x.cpp index 443723e40..39feee16f 100644 --- a/src/Protocol/Protocol17x.cpp +++ b/src/Protocol/Protocol17x.cpp @@ -11,13 +11,19 @@ Implements the 1.7.x protocol classes: #include "json/json.h" #include "Protocol17x.h" #include "ChunkDataSerializer.h" +#include "PolarSSL++/Sha1Checksum.h" + #include "../ClientHandle.h" #include "../Root.h" #include "../Server.h" #include "../World.h" +#include "../StringCompression.h" +#include "../CompositeChat.h" +#include "../Statistics.h" + #include "../WorldStorage/FastNBT.h" #include "../WorldStorage/EnchantmentSerializer.h" -#include "../StringCompression.h" + #include "../Entities/ExpOrb.h" #include "../Entities/Minecart.h" #include "../Entities/FallingBlock.h" @@ -25,15 +31,15 @@ Implements the 1.7.x protocol classes: #include "../Entities/Pickup.h" #include "../Entities/Player.h" #include "../Entities/ItemFrame.h" +#include "../Entities/ArrowEntity.h" +#include "../Entities/FireworkEntity.h" + #include "../Mobs/IncludeAllMonsters.h" #include "../UI/Window.h" + #include "../BlockEntities/CommandBlockEntity.h" #include "../BlockEntities/MobHeadEntity.h" #include "../BlockEntities/FlowerPotEntity.h" -#include "../CompositeChat.h" -#include "../Entities/ArrowEntity.h" -#include "../Entities/FireworkEntity.h" -#include "PolarSSL++/Sha1Checksum.h" @@ -1169,6 +1175,28 @@ void cProtocol172::SendSpawnVehicle(const cEntity & a_Vehicle, char a_VehicleTyp +void cProtocol172::SendStatistics(const cStatManager & a_Manager) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x37); + Pkt.WriteVarInt(statCount); // TODO 2014-05-11 xdot: Optimization: Send "dirty" statistics only + + for (unsigned int i = 0; i < (unsigned int)statCount; ++i) + { + StatValue Value = a_Manager.GetValue((eStatistic) i); + + const AString & StatName = cStatInfo::GetName((eStatistic) i); + + Pkt.WriteString(StatName); + Pkt.WriteVarInt(Value); + } +} + + + + + void cProtocol172::SendTabCompletionResults(const AStringVector & a_Results) { ASSERT(m_State == 3); // In game mode? @@ -1843,13 +1871,15 @@ void cProtocol172::HandlePacketClientStatus(cByteBuffer & a_ByteBuffer) case 1: { // Request stats - // TODO + const cStatManager & Manager = m_Client->GetPlayer()->GetStatManager(); + SendStatistics(Manager); + break; } case 2: { // Open Inventory achievement - // TODO + m_Client->GetPlayer()->AwardAchievement(achOpenInv); break; } } diff --git a/src/Protocol/Protocol17x.h b/src/Protocol/Protocol17x.h index dc111e737..3c6a8c085 100644 --- a/src/Protocol/Protocol17x.h +++ b/src/Protocol/Protocol17x.h @@ -116,6 +116,7 @@ public: 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 SendStatistics (const cStatManager & a_Manager) 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; diff --git a/src/Protocol/ProtocolRecognizer.cpp b/src/Protocol/ProtocolRecognizer.cpp index 667fb5cef..b0cbb6def 100644 --- a/src/Protocol/ProtocolRecognizer.cpp +++ b/src/Protocol/ProtocolRecognizer.cpp @@ -675,6 +675,16 @@ void cProtocolRecognizer::SendSpawnVehicle(const cEntity & a_Vehicle, char a_Veh +void cProtocolRecognizer::SendStatistics(const cStatManager & a_Manager) +{ + ASSERT(m_Protocol != NULL); + m_Protocol->SendStatistics(a_Manager); +} + + + + + void cProtocolRecognizer::SendTabCompletionResults(const AStringVector & a_Results) { ASSERT(m_Protocol != NULL); diff --git a/src/Protocol/ProtocolRecognizer.h b/src/Protocol/ProtocolRecognizer.h index 37f47379d..3a291bf7a 100644 --- a/src/Protocol/ProtocolRecognizer.h +++ b/src/Protocol/ProtocolRecognizer.h @@ -119,6 +119,7 @@ public: 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 SendStatistics (const cStatManager & a_Manager) 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; diff --git a/src/RCONServer.cpp b/src/RCONServer.cpp index 3fbc9dcbc..c511968be 100644 --- a/src/RCONServer.cpp +++ b/src/RCONServer.cpp @@ -169,7 +169,7 @@ cRCONServer::cConnection::cConnection(cRCONServer & a_RCONServer, cSocket & a_So -void cRCONServer::cConnection::DataReceived(const char * a_Data, size_t a_Size) +bool cRCONServer::cConnection::DataReceived(const char * a_Data, size_t a_Size) { // Append data to the buffer: m_Buffer.append(a_Data, a_Size); @@ -187,12 +187,12 @@ void cRCONServer::cConnection::DataReceived(const char * a_Data, size_t a_Size) m_RCONServer.m_SocketThreads.RemoveClient(this); m_Socket.CloseSocket(); delete this; - return; + return false; } if (Length > (int)(m_Buffer.size() + 4)) { // Incomplete packet yet, wait for more data to come - return; + return false; } int RequestID = IntFromBuffer(m_Buffer.data() + 4); @@ -202,10 +202,11 @@ void cRCONServer::cConnection::DataReceived(const char * a_Data, size_t a_Size) m_RCONServer.m_SocketThreads.RemoveClient(this); m_Socket.CloseSocket(); delete this; - return; + return false; } m_Buffer.erase(0, Length + 4); } // while (m_Buffer.size() >= 14) + return false; } diff --git a/src/RCONServer.h b/src/RCONServer.h index b964852ab..47c746736 100644 --- a/src/RCONServer.h +++ b/src/RCONServer.h @@ -65,7 +65,7 @@ protected: // cSocketThreads::cCallback overrides: - virtual void DataReceived(const char * a_Data, size_t a_Size) override; + virtual bool DataReceived(const char * a_Data, size_t a_Size) override; virtual void GetOutgoingData(AString & a_Data) override; virtual void SocketClosed(void) override; diff --git a/src/Server.cpp b/src/Server.cpp index bfb1b1cbb..aa731cdd2 100644 --- a/src/Server.cpp +++ b/src/Server.cpp @@ -476,7 +476,37 @@ void cServer::ExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCallbac a_Output.Finished(); return; } - + if (split[0] == "load") + { + if (split.size() > 1) + { + cPluginManager::Get()->LoadPlugin(split[1]); + + return; + } + else + { + a_Output.Out("No plugin given! Command: load <pluginname>"); + a_Output.Finished(); + return; + } + } + + if (split[0] == "unload") + { + if (split.size() > 1) + { + cPluginManager::Get()->RemovePlugin(cPluginManager::Get()->GetPlugin(split[1])); + return; + } + else + { + a_Output.Out("No plugin given! Command: unload <pluginname>"); + a_Output.Finished(); + return; + } + } + // There is currently no way a plugin can do these (and probably won't ever be): if (split[0].compare("chunkstats") == 0) { @@ -567,6 +597,9 @@ void cServer::BindBuiltInConsoleCommands(void) PlgMgr->BindConsoleCommand("restart", NULL, " - Restarts the server cleanly"); PlgMgr->BindConsoleCommand("stop", NULL, " - Stops the server cleanly"); PlgMgr->BindConsoleCommand("chunkstats", NULL, " - Displays detailed chunk memory statistics"); + PlgMgr->BindConsoleCommand("load <pluginname>", NULL, " - Adds and enables the specified plugin"); + PlgMgr->BindConsoleCommand("unload <pluginname>", NULL, " - Disables the specified plugin"); + #if defined(_MSC_VER) && defined(_DEBUG) && defined(ENABLE_LEAK_FINDER) PlgMgr->BindConsoleCommand("dumpmem", NULL, " - Dumps all used memory blocks together with their callstacks into memdump.xml"); #endif diff --git a/src/Statistics.cpp b/src/Statistics.cpp index 2c980d98e..5950eb96c 100644 --- a/src/Statistics.cpp +++ b/src/Statistics.cpp @@ -13,39 +13,39 @@ cStatInfo cStatInfo::ms_Info[statCount] = { // http://minecraft.gamepedia.com/Achievements /* Type | Name | Prerequisite */ - cStatInfo(achOpenInv, "openInventory"), - cStatInfo(achMineWood, "mineWood", achOpenInv), - cStatInfo(achCraftWorkbench, "buildWorkBench", achMineWood), - cStatInfo(achCraftPickaxe, "buildPickaxe", achCraftWorkbench), - cStatInfo(achCraftFurnace, "buildFurnace", achCraftPickaxe), - cStatInfo(achAcquireIron, "acquireIron", achCraftFurnace), - cStatInfo(achCraftHoe, "buildHoe", achCraftWorkbench), - cStatInfo(achMakeBread, "makeBread", achCraftHoe), - cStatInfo(achBakeCake, "bakeCake", achCraftHoe), - cStatInfo(achCraftBetterPick, "buildBetterPickaxe", achCraftPickaxe), - cStatInfo(achCookFish, "cookFish", achAcquireIron), - cStatInfo(achOnARail, "onARail", achAcquireIron), - cStatInfo(achCraftSword, "buildSword", achCraftWorkbench), - cStatInfo(achKillMonster, "killEnemy", achCraftSword), - cStatInfo(achKillCow, "killCow", achCraftSword), - cStatInfo(achFlyPig, "flyPig", achKillCow), - cStatInfo(achSnipeSkeleton, "snipeSkeleton", achKillMonster), - cStatInfo(achDiamonds, "diamonds", achAcquireIron), - cStatInfo(achEnterPortal, "portal", achDiamonds), - cStatInfo(achReturnToSender, "ghast", achEnterPortal), - cStatInfo(achBlazeRod, "blazeRod", achEnterPortal), - cStatInfo(achBrewPotion, "potion", achBlazeRod), - cStatInfo(achEnterTheEnd, "theEnd", achBlazeRod), - cStatInfo(achDefeatDragon, "theEnd2", achEnterTheEnd), - cStatInfo(achCraftEnchantTable, "enchantments", achDiamonds), - cStatInfo(achOverkill, "overkill", achCraftEnchantTable), - cStatInfo(achBookshelf, "bookcase", achCraftEnchantTable), - cStatInfo(achExploreAllBiomes, "exploreAllBiomes", achEnterTheEnd), - cStatInfo(achSpawnWither, "spawnWither", achDefeatDragon), - cStatInfo(achKillWither, "killWither", achSpawnWither), - cStatInfo(achFullBeacon, "fullBeacon", achKillWither), - cStatInfo(achBreedCow, "breedCow", achKillCow), - cStatInfo(achThrowDiamonds, "diamondsToYou", achDiamonds), + cStatInfo(achOpenInv, "achievement.openInventory"), + cStatInfo(achMineWood, "achievement.mineWood", achOpenInv), + cStatInfo(achCraftWorkbench, "achievement.buildWorkBench", achMineWood), + cStatInfo(achCraftPickaxe, "achievement.buildPickaxe", achCraftWorkbench), + cStatInfo(achCraftFurnace, "achievement.buildFurnace", achCraftPickaxe), + cStatInfo(achAcquireIron, "achievement.acquireIron", achCraftFurnace), + cStatInfo(achCraftHoe, "achievement.buildHoe", achCraftWorkbench), + cStatInfo(achMakeBread, "achievement.makeBread", achCraftHoe), + cStatInfo(achBakeCake, "achievement.bakeCake", achCraftHoe), + cStatInfo(achCraftBetterPick, "achievement.buildBetterPickaxe", achCraftPickaxe), + cStatInfo(achCookFish, "achievement.cookFish", achAcquireIron), + cStatInfo(achOnARail, "achievement.onARail", achAcquireIron), + cStatInfo(achCraftSword, "achievement.buildSword", achCraftWorkbench), + cStatInfo(achKillMonster, "achievement.killEnemy", achCraftSword), + cStatInfo(achKillCow, "achievement.killCow", achCraftSword), + cStatInfo(achFlyPig, "achievement.flyPig", achKillCow), + cStatInfo(achSnipeSkeleton, "achievement.snipeSkeleton", achKillMonster), + cStatInfo(achDiamonds, "achievement.diamonds", achAcquireIron), + cStatInfo(achEnterPortal, "achievement.portal", achDiamonds), + cStatInfo(achReturnToSender, "achievement.ghast", achEnterPortal), + cStatInfo(achBlazeRod, "achievement.blazeRod", achEnterPortal), + cStatInfo(achBrewPotion, "achievement.potion", achBlazeRod), + cStatInfo(achEnterTheEnd, "achievement.theEnd", achBlazeRod), + cStatInfo(achDefeatDragon, "achievement.theEnd2", achEnterTheEnd), + cStatInfo(achCraftEnchantTable, "achievement.enchantments", achDiamonds), + cStatInfo(achOverkill, "achievement.overkill", achCraftEnchantTable), + cStatInfo(achBookshelf, "achievement.bookcase", achCraftEnchantTable), + cStatInfo(achExploreAllBiomes, "achievement.exploreAllBiomes", achEnterTheEnd), + cStatInfo(achSpawnWither, "achievement.spawnWither", achDefeatDragon), + cStatInfo(achKillWither, "achievement.killWither", achSpawnWither), + cStatInfo(achFullBeacon, "achievement.fullBeacon", achKillWither), + cStatInfo(achBreedCow, "achievement.breedCow", achKillCow), + cStatInfo(achThrowDiamonds, "achievement.diamondsToYou", achDiamonds), // http://minecraft.gamepedia.com/Statistics @@ -57,13 +57,14 @@ cStatInfo cStatInfo::ms_Info[statCount] = { cStatInfo(statDistFallen, "stat.fallOneCm"), cStatInfo(statDistClimbed, "stat.climbOneCm"), cStatInfo(statDistFlown, "stat.flyOneCm"), + cStatInfo(statDistDove, "stat.diveOneCm"), cStatInfo(statDistMinecart, "stat.minecartOneCm"), cStatInfo(statDistBoat, "stat.boatOneCm"), cStatInfo(statDistPig, "stat.pigOneCm"), cStatInfo(statDistHorse, "stat.horseOneCm"), cStatInfo(statJumps, "stat.jump"), cStatInfo(statItemsDropped, "stat.drop"), - cStatInfo(statDamageDealt, "stat.damageDealth"), + cStatInfo(statDamageDealt, "stat.damageDealt"), cStatInfo(statDamageTaken, "stat.damageTaken"), cStatInfo(statDeaths, "stat.deaths"), cStatInfo(statMobKills, "stat.mobKills"), @@ -113,7 +114,7 @@ eStatistic cStatInfo::GetType(const AString & a_Name) { for (unsigned int i = 0; i < ARRAYCOUNT(ms_Info); ++i) { - if (NoCaseCompare(ms_Info[i].m_Name, a_Name)) + if (NoCaseCompare(ms_Info[i].m_Name, a_Name) == 0) { return ms_Info[i].m_Type; } @@ -137,3 +138,59 @@ eStatistic cStatInfo::GetPrerequisite(const eStatistic a_Type) +cStatManager::cStatManager() +{ + Reset(); +} + + + + + +StatValue cStatManager::GetValue(const eStatistic a_Stat) const +{ + ASSERT((a_Stat > statInvalid) && (a_Stat < statCount)); + + return m_MainStats[a_Stat]; +} + + + + + +void cStatManager::SetValue(const eStatistic a_Stat, const StatValue a_Value) +{ + ASSERT((a_Stat > statInvalid) && (a_Stat < statCount)); + + m_MainStats[a_Stat] = a_Value; +} + + + + + +StatValue cStatManager::AddValue(const eStatistic a_Stat, const StatValue a_Delta) +{ + ASSERT((a_Stat > statInvalid) && (a_Stat < statCount)); + + m_MainStats[a_Stat] += a_Delta; + + return m_MainStats[a_Stat]; +} + + + + + +void cStatManager::Reset(void) +{ + for (unsigned int i = 0; i < (unsigned int)statCount; ++i) + { + m_MainStats[i] = 0; + } +} + + + + + diff --git a/src/Statistics.h b/src/Statistics.h index 540df38cc..f37f32e1e 100644 --- a/src/Statistics.h +++ b/src/Statistics.h @@ -9,6 +9,7 @@ +// tolua_begin enum eStatistic { // The order must match the order of cStatInfo::ms_Info @@ -77,6 +78,7 @@ enum eStatistic statCount }; +// tolua_end @@ -114,3 +116,49 @@ private: + +/* Signed (?) integral value. */ +typedef int StatValue; // tolua_export + + + + +/** Class that manages the statistics and achievements of a single player. */ +// tolua_begin +class cStatManager +{ +public: + // tolua_end + + cStatManager(); + + // tolua_begin + + /** Return the value of the specified stat. */ + StatValue GetValue(const eStatistic a_Stat) const; + + /** Set the value of the specified stat. */ + void SetValue(const eStatistic a_Stat, const StatValue a_Value); + + /** Reset everything. */ + void Reset(); + + /** Increment the specified stat. + * + * Returns the new value. + */ + StatValue AddValue(const eStatistic a_Stat, const StatValue a_Delta = 1); + + // tolua_end + +private: + + StatValue m_MainStats[statCount]; + + // TODO 10-05-2014 xdot: Use, mine, craft statistics + + +}; // tolua_export + + + diff --git a/src/UI/SlotArea.cpp b/src/UI/SlotArea.cpp index 788974f9c..507b45833 100644 --- a/src/UI/SlotArea.cpp +++ b/src/UI/SlotArea.cpp @@ -496,6 +496,8 @@ void cSlotAreaCrafting::ClickedResult(cPlayer & a_Player) DraggingItem = Result; Recipe.ConsumeIngredients(Grid); Grid.CopyToItems(PlayerSlots); + + HandleCraftItem(Result, a_Player); } else if (DraggingItem.IsEqual(Result)) { @@ -505,6 +507,8 @@ void cSlotAreaCrafting::ClickedResult(cPlayer & a_Player) DraggingItem.m_ItemCount += Result.m_ItemCount; Recipe.ConsumeIngredients(Grid); Grid.CopyToItems(PlayerSlots); + + HandleCraftItem(Result, a_Player); } } @@ -594,6 +598,27 @@ cCraftingRecipe & cSlotAreaCrafting::GetRecipeForPlayer(cPlayer & a_Player) +void cSlotAreaCrafting::HandleCraftItem(const cItem & a_Result, cPlayer & a_Player) +{ + switch (a_Result.m_ItemType) + { + case E_BLOCK_WORKBENCH: a_Player.AwardAchievement(achCraftWorkbench); break; + case E_BLOCK_FURNACE: a_Player.AwardAchievement(achCraftFurnace); break; + case E_BLOCK_CAKE: a_Player.AwardAchievement(achBakeCake); break; + case E_BLOCK_ENCHANTMENT_TABLE: a_Player.AwardAchievement(achCraftEnchantTable); break; + case E_BLOCK_BOOKCASE: a_Player.AwardAchievement(achBookshelf); break; + case E_ITEM_WOODEN_PICKAXE: a_Player.AwardAchievement(achCraftPickaxe); break; + case E_ITEM_WOODEN_SWORD: a_Player.AwardAchievement(achCraftSword); break; + case E_ITEM_STONE_PICKAXE: a_Player.AwardAchievement(achCraftBetterPick); break; + case E_ITEM_WOODEN_HOE: a_Player.AwardAchievement(achCraftHoe); break; + case E_ITEM_BREAD: a_Player.AwardAchievement(achMakeBread); break; + default: break; + } +} + + + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // cSlotAreaAnvil: @@ -760,7 +785,7 @@ void cSlotAreaAnvil::OnTakeResult(cPlayer & a_Player) { if (!a_Player.IsGameModeCreative()) { - a_Player.DeltaExperience(cPlayer::XpForLevel(m_MaximumCost)); + a_Player.DeltaExperience(-cPlayer::XpForLevel(m_MaximumCost)); } SetSlot(0, a_Player, cItem()); @@ -1393,7 +1418,7 @@ void cSlotAreaFurnace::OnSlotChanged(cItemGrid * a_ItemGrid, int a_SlotNum) { // Something has changed in the window, broadcast the entire window to all clients ASSERT(a_ItemGrid == &(m_Furnace->GetContents())); - + m_ParentWindow.BroadcastWholeWindow(); } @@ -1401,6 +1426,21 @@ void cSlotAreaFurnace::OnSlotChanged(cItemGrid * a_ItemGrid, int a_SlotNum) +void cSlotAreaFurnace::HandleSmeltItem(const cItem & a_Result, cPlayer & a_Player) +{ + /** TODO 2014-05-12 xdot: Figure out when to call this method. */ + switch (a_Result.m_ItemType) + { + case E_ITEM_IRON: a_Player.AwardAchievement(achAcquireIron); break; + case E_ITEM_COOKED_FISH: a_Player.AwardAchievement(achCookFish); break; + default: break; + } +} + + + + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // cSlotAreaInventoryBase: diff --git a/src/UI/SlotArea.h b/src/UI/SlotArea.h index 4da6a672f..e297bcff7 100644 --- a/src/UI/SlotArea.h +++ b/src/UI/SlotArea.h @@ -254,6 +254,9 @@ protected: /// Retrieves the recipe for the specified player from the map, or creates one if not found cCraftingRecipe & GetRecipeForPlayer(cPlayer & a_Player); + + /// Called after an item has been crafted to handle statistics e.t.c. + void HandleCraftItem(const cItem & a_Result, cPlayer & a_Player); } ; @@ -397,6 +400,9 @@ protected: // cItemGrid::cListener overrides: virtual void OnSlotChanged(cItemGrid * a_ItemGrid, int a_SlotNum) override; + + /// Called after an item has been smelted to handle statistics e.t.c. + void HandleSmeltItem(const cItem & a_Result, cPlayer & a_Player); } ; diff --git a/src/WorldStorage/NBTChunkSerializer.cpp b/src/WorldStorage/NBTChunkSerializer.cpp index 2ac1d7962..7b7cd3c7e 100644 --- a/src/WorldStorage/NBTChunkSerializer.cpp +++ b/src/WorldStorage/NBTChunkSerializer.cpp @@ -391,38 +391,41 @@ void cNBTChunkSerializer::AddFallingBlockEntity(cFallingBlock * a_FallingBlock) void cNBTChunkSerializer::AddMinecartEntity(cMinecart * a_Minecart) { - const char * EntityClass = NULL; - switch (a_Minecart->GetPayload()) - { - case cMinecart::mpNone: EntityClass = "MinecartRideable"; break; - case cMinecart::mpChest: EntityClass = "MinecartChest"; break; - case cMinecart::mpFurnace: EntityClass = "MinecartFurnace"; break; - case cMinecart::mpTNT: EntityClass = "MinecartTNT"; break; - case cMinecart::mpHopper: EntityClass = "MinecartHopper"; break; - default: - { - ASSERT(!"Unhandled minecart payload type"); - return; - } - } // switch (payload) - m_Writer.BeginCompound(""); - AddBasicEntity(a_Minecart, EntityClass); + switch (a_Minecart->GetPayload()) { case cMinecart::mpChest: { + AddBasicEntity(a_Minecart, "MinecartChest"); // Add chest contents into the Items tag: AddMinecartChestContents((cMinecartWithChest *)a_Minecart); break; } - case cMinecart::mpFurnace: { + AddBasicEntity(a_Minecart, "MinecartFurnace"); // TODO: Add "Push" and "Fuel" tags break; } + case cMinecart::mpHopper: + { + AddBasicEntity(a_Minecart, "MinecartHopper"); + // TODO: Add hopper contents? + break; + } + case cMinecart::mpTNT: + { + AddBasicEntity(a_Minecart, "MinecartTNT"); + break; + } + case cMinecart::mpNone: + { + AddBasicEntity(a_Minecart, "MinecartRideable"); + break; + } } // switch (Payload) + m_Writer.EndCompound(); } @@ -650,6 +653,13 @@ void cNBTChunkSerializer::AddHangingEntity(cHangingEntity * a_Hanging) case BLOCK_FACE_YP: m_Writer.AddByte("Dir", (unsigned char)1); break; case BLOCK_FACE_ZM: m_Writer.AddByte("Dir", (unsigned char)0); break; case BLOCK_FACE_ZP: m_Writer.AddByte("Dir", (unsigned char)3); break; + + case BLOCK_FACE_XM: + case BLOCK_FACE_XP: + case BLOCK_FACE_NONE: + { + break; + } } } diff --git a/src/WorldStorage/StatSerializer.cpp b/src/WorldStorage/StatSerializer.cpp new file mode 100644 index 000000000..74113941c --- /dev/null +++ b/src/WorldStorage/StatSerializer.cpp @@ -0,0 +1,146 @@ + +// StatSerializer.cpp + + +#include "Globals.h" +#include "StatSerializer.h" + +#include "../Statistics.h" + + + + + +cStatSerializer::cStatSerializer(const AString & a_WorldName, const AString & a_PlayerName, cStatManager * a_Manager) + : m_Manager(a_Manager) +{ + // Even though stats are shared between worlds, they are (usually) saved + // inside the folder of the default world. + + AString StatsPath; + Printf(StatsPath, "%s/stats", a_WorldName.c_str()); + + m_Path = StatsPath + "/" + a_PlayerName + ".json"; + + // Ensure that the directory exists. + cFile::CreateFolder(FILE_IO_PREFIX + StatsPath); +} + + + + + +bool cStatSerializer::Load(void) +{ + AString Data = cFile::ReadWholeFile(FILE_IO_PREFIX + m_Path); + if (Data.empty()) + { + return false; + } + + Json::Value Root; + Json::Reader Reader; + + if (Reader.parse(Data, Root, false)) + { + return LoadStatFromJSON(Root); + } + + return false; +} + + + + + +bool cStatSerializer::Save(void) +{ + Json::Value Root; + SaveStatToJSON(Root); + + cFile File; + if (!File.Open(FILE_IO_PREFIX + m_Path, cFile::fmWrite)) + { + return false; + } + + Json::StyledWriter Writer; + AString JsonData = Writer.write(Root); + + File.Write(JsonData.data(), JsonData.size()); + File.Close(); + + return true; +} + + + + + +void cStatSerializer::SaveStatToJSON(Json::Value & a_Out) +{ + for (unsigned int i = 0; i < (unsigned int)statCount; ++i) + { + StatValue Value = m_Manager->GetValue((eStatistic) i); + + if (Value != 0) + { + const AString & StatName = cStatInfo::GetName((eStatistic) i); + + a_Out[StatName] = Value; + } + + // TODO 2014-05-11 xdot: Save "progress" + } +} + + + + + +bool cStatSerializer::LoadStatFromJSON(const Json::Value & a_In) +{ + m_Manager->Reset(); + + for (Json::ValueIterator it = a_In.begin() ; it != a_In.end() ; ++it) + { + AString StatName = it.key().asString(); + + eStatistic StatType = cStatInfo::GetType(StatName); + + if (StatType == statInvalid) + { + LOGWARNING("Invalid statistic type \"%s\"", StatName.c_str()); + continue; + } + + Json::Value & Node = *it; + + if (Node.isInt()) + { + m_Manager->SetValue(StatType, Node.asInt()); + } + else if (Node.isObject()) + { + StatValue Value = Node.get("value", 0).asInt(); + + // TODO 2014-05-11 xdot: Load "progress" + + m_Manager->SetValue(StatType, Value); + } + else + { + LOGWARNING("Invalid statistic value for type \"%s\"", StatName.c_str()); + } + } + + return true; +} + + + + + + + + diff --git a/src/WorldStorage/StatSerializer.h b/src/WorldStorage/StatSerializer.h new file mode 100644 index 000000000..72f8d74f1 --- /dev/null +++ b/src/WorldStorage/StatSerializer.h @@ -0,0 +1,55 @@ + +// StatSerializer.h + +// Declares the cStatSerializer class that is used for saving stats into JSON + + + + + +#pragma once + +#include "json/json.h" + + + + + +// fwd: +class cStatManager; + + + + +class cStatSerializer +{ +public: + + cStatSerializer(const AString & a_WorldName, const AString & a_PlayerName, cStatManager * a_Manager); + + /* Try to load the player statistics. Returns whether the operation was successful or not. */ + bool Load(void); + + /* Try to save the player statistics. Returns whether the operation was successful or not. */ + bool Save(void); + + +protected: + + void SaveStatToJSON(Json::Value & a_Out); + + bool LoadStatFromJSON(const Json::Value & a_In); + + +private: + + cStatManager* m_Manager; + + AString m_Path; + + +} ; + + + + diff --git a/src/WorldStorage/WorldStorage.cpp b/src/WorldStorage/WorldStorage.cpp index 54eaaca5c..6867ad5bc 100644 --- a/src/WorldStorage/WorldStorage.cpp +++ b/src/WorldStorage/WorldStorage.cpp @@ -243,7 +243,6 @@ void cWorldStorage::Execute(void) bool Success; do { - Success = false; if (m_ShouldTerminate) { return; |