diff options
Diffstat (limited to '')
102 files changed, 5174 insertions, 1033 deletions
diff --git a/src/Bindings/CMakeLists.txt b/src/Bindings/CMakeLists.txt index 40b8d4bd9..4cc73b350 100644 --- a/src/Bindings/CMakeLists.txt +++ b/src/Bindings/CMakeLists.txt @@ -12,6 +12,7 @@ SET (SRCS LuaServerHandle.cpp LuaState.cpp LuaTCPLink.cpp + LuaUDPEndpoint.cpp LuaWindow.cpp ManualBindings.cpp ManualBindings_Network.cpp @@ -31,6 +32,7 @@ SET (HDRS LuaServerHandle.h LuaState.h LuaTCPLink.h + LuaUDPEndpoint.h LuaWindow.h ManualBindings.h Plugin.h diff --git a/src/Bindings/LuaState.cpp b/src/Bindings/LuaState.cpp index 73b114599..25c77a652 100644 --- a/src/Bindings/LuaState.cpp +++ b/src/Bindings/LuaState.cpp @@ -343,6 +343,18 @@ bool cLuaState::PushFunction(const cTableRef & a_TableRef) +void cLuaState::PushNil(void) +{ + ASSERT(IsValid()); + + lua_pushnil(m_LuaState); + m_NumCurrentFunctionArgs += 1; +} + + + + + void cLuaState::Push(const AString & a_String) { ASSERT(IsValid()); @@ -680,6 +692,18 @@ void cLuaState::Push(cLuaTCPLink * a_TCPLink) +void cLuaState::Push(cLuaUDPEndpoint * a_UDPEndpoint) +{ + ASSERT(IsValid()); + + tolua_pushusertype(m_LuaState, a_UDPEndpoint, "cUDPEndpoint"); + m_NumCurrentFunctionArgs += 1; +} + + + + + void cLuaState::Push(cMonster * a_Monster) { ASSERT(IsValid()); diff --git a/src/Bindings/LuaState.h b/src/Bindings/LuaState.h index f68b022ea..159483634 100644 --- a/src/Bindings/LuaState.h +++ b/src/Bindings/LuaState.h @@ -61,6 +61,7 @@ class cBlockEntity; class cBoundingBox; class cLuaTCPLink; class cLuaServerHandle; +class cLuaUDPEndpoint; typedef cBoundingBox * pBoundingBox; typedef cWorld * pWorld; @@ -184,6 +185,8 @@ public: /** Returns true if a_FunctionName is a valid Lua function that can be called */ bool HasFunction(const char * a_FunctionName); + void PushNil(void); + // Push a const value onto the stack (keep alpha-sorted): void Push(const AString & a_String); void Push(const AStringVector & a_Vector); @@ -210,6 +213,7 @@ public: void Push(cItems * a_Items); void Push(cLuaServerHandle * a_ServerHandle); void Push(cLuaTCPLink * a_TCPLink); + void Push(cLuaUDPEndpoint * a_UDPEndpoint); void Push(cMonster * a_Monster); void Push(cPickup * a_Pickup); void Push(cPlayer * a_Player); diff --git a/src/Bindings/LuaTCPLink.cpp b/src/Bindings/LuaTCPLink.cpp index 6b8395806..d88c41120 100644 --- a/src/Bindings/LuaTCPLink.cpp +++ b/src/Bindings/LuaTCPLink.cpp @@ -64,6 +64,13 @@ cLuaTCPLink::~cLuaTCPLink() bool cLuaTCPLink::Send(const AString & a_Data) { + // If running in SSL mode, push the data into the SSL context instead: + if (m_SslContext != nullptr) + { + m_SslContext->Send(a_Data); + return true; + } + // Safely grab a copy of the link: cTCPLinkPtr Link = m_Link; if (Link == nullptr) @@ -153,10 +160,14 @@ void cLuaTCPLink::Shutdown(void) cTCPLinkPtr Link = m_Link; if (Link != nullptr) { + if (m_SslContext != nullptr) + { + m_SslContext->NotifyClose(); + m_SslContext->ResetSelf(); + m_SslContext.reset(); + } Link->Shutdown(); } - - Terminated(); } @@ -169,6 +180,12 @@ void cLuaTCPLink::Close(void) cTCPLinkPtr Link = m_Link; if (Link != nullptr) { + if (m_SslContext != nullptr) + { + m_SslContext->NotifyClose(); + m_SslContext->ResetSelf(); + m_SslContext.reset(); + } Link->Close(); } @@ -179,6 +196,110 @@ void cLuaTCPLink::Close(void) +AString cLuaTCPLink::StartTLSClient( + const AString & a_OwnCertData, + const AString & a_OwnPrivKeyData, + const AString & a_OwnPrivKeyPassword +) +{ + // Check preconditions: + if (m_SslContext != nullptr) + { + return "TLS is already active on this link"; + } + if ( + (a_OwnCertData.empty() && !a_OwnPrivKeyData.empty()) || + (!a_OwnCertData.empty() && a_OwnPrivKeyData.empty()) + ) + { + return "Either provide both the certificate and private key, or neither"; + } + + // Create the SSL context: + m_SslContext.reset(new cLinkSslContext(*this)); + m_SslContext->Initialize(true); + + // Create the peer cert, if required: + if (!a_OwnCertData.empty() && !a_OwnPrivKeyData.empty()) + { + auto OwnCert = std::make_shared<cX509Cert>(); + int res = OwnCert->Parse(a_OwnCertData.data(), a_OwnCertData.size()); + if (res != 0) + { + m_SslContext.reset(); + return Printf("Cannot parse peer certificate: -0x%x", res); + } + auto OwnPrivKey = std::make_shared<cCryptoKey>(); + res = OwnPrivKey->ParsePrivate(a_OwnPrivKeyData.data(), a_OwnPrivKeyData.size(), a_OwnPrivKeyPassword); + if (res != 0) + { + m_SslContext.reset(); + return Printf("Cannot parse peer private key: -0x%x", res); + } + m_SslContext->SetOwnCert(OwnCert, OwnPrivKey); + } + m_SslContext->SetSelf(cLinkSslContextWPtr(m_SslContext)); + + // Start the handshake: + m_SslContext->Handshake(); + return ""; +} + + + + + +AString cLuaTCPLink::StartTLSServer( + const AString & a_OwnCertData, + const AString & a_OwnPrivKeyData, + const AString & a_OwnPrivKeyPassword, + const AString & a_StartTLSData +) +{ + // Check preconditions: + if (m_SslContext != nullptr) + { + return "TLS is already active on this link"; + } + if (a_OwnCertData.empty() || a_OwnPrivKeyData.empty()) + { + return "Provide the server certificate and private key"; + } + + // Create the SSL context: + m_SslContext.reset(new cLinkSslContext(*this)); + m_SslContext->Initialize(false); + + // Create the peer cert: + auto OwnCert = std::make_shared<cX509Cert>(); + int res = OwnCert->Parse(a_OwnCertData.data(), a_OwnCertData.size()); + if (res != 0) + { + m_SslContext.reset(); + return Printf("Cannot parse server certificate: -0x%x", res); + } + auto OwnPrivKey = std::make_shared<cCryptoKey>(); + res = OwnPrivKey->ParsePrivate(a_OwnPrivKeyData.data(), a_OwnPrivKeyData.size(), a_OwnPrivKeyPassword); + if (res != 0) + { + m_SslContext.reset(); + return Printf("Cannot parse server private key: -0x%x", res); + } + m_SslContext->SetOwnCert(OwnCert, OwnPrivKey); + m_SslContext->SetSelf(cLinkSslContextWPtr(m_SslContext)); + + // Push the initial data: + m_SslContext->StoreReceivedData(a_StartTLSData.data(), a_StartTLSData.size()); + + // Start the handshake: + m_SslContext->Handshake(); + return ""; +} + + + + + void cLuaTCPLink::Terminated(void) { // Disable the callbacks: @@ -195,11 +316,36 @@ void cLuaTCPLink::Terminated(void) } // If the link is still open, close it: - cTCPLinkPtr Link = m_Link; - if (Link != nullptr) { - Link->Close(); - m_Link.reset(); + cTCPLinkPtr Link = m_Link; + if (Link != nullptr) + { + Link->Close(); + m_Link.reset(); + } + } + + // If the SSL context still exists, free it: + m_SslContext.reset(); +} + + + + + +void cLuaTCPLink::ReceivedCleartextData(const char * a_Data, size_t a_NumBytes) +{ + // Check if we're still valid: + if (!m_Callbacks.IsValid()) + { + return; + } + + // Call the callback: + cPluginLua::cOperation Op(m_Plugin); + if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnReceivedData"), this, AString(a_Data, a_NumBytes))) + { + LOGINFO("cTCPLink OnReceivedData callback failed in plugin %s.", m_Plugin.GetName().c_str()); } } @@ -269,6 +415,13 @@ void cLuaTCPLink::OnReceivedData(const char * a_Data, size_t a_Length) return; } + // If we're running in SSL mode, put the data into the SSL decryptor: + if (m_SslContext != nullptr) + { + m_SslContext->StoreReceivedData(a_Data, a_Length); + return; + } + // Call the callback: cPluginLua::cOperation Op(m_Plugin); if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnReceivedData"), this, AString(a_Data, a_Length))) @@ -302,3 +455,156 @@ void cLuaTCPLink::OnRemoteClosed(void) + +//////////////////////////////////////////////////////////////////////////////// +// cLuaTCPLink::cLinkSslContext: + +cLuaTCPLink::cLinkSslContext::cLinkSslContext(cLuaTCPLink & a_Link): + m_Link(a_Link) +{ +} + + + + + +void cLuaTCPLink::cLinkSslContext::SetSelf(cLinkSslContextWPtr a_Self) +{ + m_Self = a_Self; +} + + + + + +void cLuaTCPLink::cLinkSslContext::ResetSelf(void) +{ + m_Self.reset(); +} + + + + + +void cLuaTCPLink::cLinkSslContext::StoreReceivedData(const char * a_Data, size_t a_NumBytes) +{ + // Hold self alive for the duration of this function + cLinkSslContextPtr Self(m_Self); + + m_EncryptedData.append(a_Data, a_NumBytes); + + // Try to finish a pending handshake: + TryFinishHandshaking(); + + // Flush any cleartext data that can be "received": + FlushBuffers(); +} + + + + + +void cLuaTCPLink::cLinkSslContext::FlushBuffers(void) +{ + // Hold self alive for the duration of this function + cLinkSslContextPtr Self(m_Self); + + // If the handshake didn't complete yet, bail out: + if (!HasHandshaken()) + { + return; + } + + char Buffer[1024]; + int NumBytes; + while ((NumBytes = ReadPlain(Buffer, sizeof(Buffer))) > 0) + { + m_Link.ReceivedCleartextData(Buffer, static_cast<size_t>(NumBytes)); + if (m_Self.expired()) + { + // The callback closed the SSL context, bail out + return; + } + } +} + + + + + +void cLuaTCPLink::cLinkSslContext::TryFinishHandshaking(void) +{ + // Hold self alive for the duration of this function + cLinkSslContextPtr Self(m_Self); + + // If the handshake hasn't finished yet, retry: + if (!HasHandshaken()) + { + Handshake(); + } + + // If the handshake succeeded, write all the queued plaintext data: + if (HasHandshaken()) + { + WritePlain(m_CleartextData.data(), m_CleartextData.size()); + m_CleartextData.clear(); + } +} + + + + + +void cLuaTCPLink::cLinkSslContext::Send(const AString & a_Data) +{ + // Hold self alive for the duration of this function + cLinkSslContextPtr Self(m_Self); + + // If the handshake hasn't completed yet, queue the data: + if (!HasHandshaken()) + { + m_CleartextData.append(a_Data); + TryFinishHandshaking(); + return; + } + + // The connection is all set up, write the cleartext data into the SSL context: + WritePlain(a_Data.data(), a_Data.size()); + FlushBuffers(); +} + + + + + +int cLuaTCPLink::cLinkSslContext::ReceiveEncrypted(unsigned char * a_Buffer, size_t a_NumBytes) +{ + // Hold self alive for the duration of this function + cLinkSslContextPtr Self(m_Self); + + // If there's nothing queued in the buffer, report empty buffer: + if (m_EncryptedData.empty()) + { + return POLARSSL_ERR_NET_WANT_READ; + } + + // Copy as much data as possible to the provided buffer: + size_t BytesToCopy = std::min(a_NumBytes, m_EncryptedData.size()); + memcpy(a_Buffer, m_EncryptedData.data(), BytesToCopy); + m_EncryptedData.erase(0, BytesToCopy); + return static_cast<int>(BytesToCopy); +} + + + + + +int cLuaTCPLink::cLinkSslContext::SendEncrypted(const unsigned char * a_Buffer, size_t a_NumBytes) +{ + m_Link.m_Link->Send(a_Buffer, a_NumBytes); + return static_cast<int>(a_NumBytes); +} + + + + diff --git a/src/Bindings/LuaTCPLink.h b/src/Bindings/LuaTCPLink.h index f2af911ec..c8ae776fe 100644 --- a/src/Bindings/LuaTCPLink.h +++ b/src/Bindings/LuaTCPLink.h @@ -11,6 +11,7 @@ #include "../OSSupport/Network.h" #include "PluginLua.h" +#include "../PolarSSL++/SslContext.h" @@ -62,7 +63,82 @@ public: Sends the RST packet, queued outgoing and incoming data is lost. */ void Close(void); + /** Starts a TLS handshake as a client connection. + If a client certificate should be used for the connection, set the certificate into a_OwnCertData and + its corresponding private key to a_OwnPrivKeyData. If both are empty, no client cert is presented. + a_OwnPrivKeyPassword is the password to be used for decoding PrivKey, empty if not passworded. + Returns empty string on success, non-empty error description on failure. */ + AString StartTLSClient( + const AString & a_OwnCertData, + const AString & a_OwnPrivKeyData, + const AString & a_OwnPrivKeyPassword + ); + + /** Starts a TLS handshake as a server connection. + Set the server certificate into a_CertData and its corresponding private key to a_OwnPrivKeyData. + a_OwnPrivKeyPassword is the password to be used for decoding PrivKey, empty if not passworded. + a_StartTLSData is any data that should be pushed into the TLS before reading more data from the remote. + This is used mainly for protocols starting TLS in the middle of communication, when the TLS start command + can be received together with the TLS Client Hello message in one OnReceivedData() call, to re-queue the + Client Hello message into the TLS handshake buffer. + Returns empty string on success, non-empty error description on failure. */ + AString StartTLSServer( + const AString & a_OwnCertData, + const AString & a_OwnPrivKeyData, + const AString & a_OwnPrivKeyPassword, + const AString & a_StartTLSData + ); + protected: + // fwd: + class cLinkSslContext; + typedef SharedPtr<cLinkSslContext> cLinkSslContextPtr; + typedef WeakPtr<cLinkSslContext> cLinkSslContextWPtr; + + /** Wrapper around cSslContext that is used when this link is being encrypted by SSL. */ + class cLinkSslContext : + public cSslContext + { + cLuaTCPLink & m_Link; + + /** Buffer for storing the incoming encrypted data until it is requested by the SSL decryptor. */ + AString m_EncryptedData; + + /** Buffer for storing the outgoing cleartext data until the link has finished handshaking. */ + AString m_CleartextData; + + /** Shared ownership of self, so that this object can keep itself alive for as long as it needs. */ + cLinkSslContextWPtr m_Self; + + public: + cLinkSslContext(cLuaTCPLink & a_Link); + + /** Shares ownership of self, so that this object can keep itself alive for as long as it needs. */ + void SetSelf(cLinkSslContextWPtr a_Self); + + /** Removes the self ownership so that we can detect the SSL closure. */ + void ResetSelf(void); + + /** Stores the specified block of data into the buffer of the data to be decrypted (incoming from remote). + Also flushes the SSL buffers by attempting to read any data through the SSL context. */ + void StoreReceivedData(const char * a_Data, size_t a_NumBytes); + + /** Tries to read any cleartext data available through the SSL, reports it in the link. */ + void FlushBuffers(void); + + /** Tries to finish handshaking the SSL. */ + void TryFinishHandshaking(void); + + /** Sends the specified cleartext data over the SSL to the remote peer. + If the handshake hasn't been completed yet, queues the data for sending when it completes. */ + void Send(const AString & a_Data); + + // cSslContext overrides: + virtual int ReceiveEncrypted(unsigned char * a_Buffer, size_t a_NumBytes) override; + virtual int SendEncrypted(const unsigned char * a_Buffer, size_t a_NumBytes) override; + }; + + /** The plugin for which the link is created. */ cPluginLua & m_Plugin; @@ -76,11 +152,19 @@ protected: /** The server that is responsible for this link, if any. */ cLuaServerHandleWPtr m_Server; + /** The SSL context used for encryption, if this link uses SSL. + If valid, the link uses encryption through this context. */ + cLinkSslContextPtr m_SslContext; + /** Common code called when the link is considered as terminated. Releases m_Link, m_Callbacks and this from m_Server, each when applicable. */ void Terminated(void); + /** Called by the SSL context when there's incoming data available in the cleartext. + Reports the data via the Lua callback function. */ + void ReceivedCleartextData(const char * a_Data, size_t a_NumBytes); + // cNetwork::cConnectCallbacks overrides: virtual void OnConnected(cTCPLink & a_Link) override; virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override; diff --git a/src/Bindings/LuaUDPEndpoint.cpp b/src/Bindings/LuaUDPEndpoint.cpp new file mode 100644 index 000000000..8637eeb4e --- /dev/null +++ b/src/Bindings/LuaUDPEndpoint.cpp @@ -0,0 +1,221 @@ + +// LuaUDPEndpoint.cpp + +// Implements the cLuaUDPEndpoint class representing a Lua wrapper for the cUDPEndpoint class and the callbacks it needs + +#include "Globals.h" +#include "LuaUDPEndpoint.h" + + + + + +cLuaUDPEndpoint::cLuaUDPEndpoint(cPluginLua & a_Plugin, int a_CallbacksTableStackPos): + m_Plugin(a_Plugin), + m_Callbacks(a_Plugin.GetLuaState(), a_CallbacksTableStackPos) +{ + // Warn if the callbacks aren't valid: + if (!m_Callbacks.IsValid()) + { + LOGWARNING("cLuaUDPEndpoint in plugin %s: callbacks could not be retrieved", m_Plugin.GetName().c_str()); + cPluginLua::cOperation Op(m_Plugin); + Op().LogStackTrace(); + } +} + + + + + +cLuaUDPEndpoint::~cLuaUDPEndpoint() +{ + // If the endpoint is still open, close it: + cUDPEndpointPtr Endpoint = m_Endpoint; + if (Endpoint != nullptr) + { + Endpoint->Close(); + } + + Terminated(); +} + + + + + +bool cLuaUDPEndpoint::Open(UInt16 a_Port, cLuaUDPEndpointPtr a_Self) +{ + ASSERT(m_Self == nullptr); // Must not be opened yet + ASSERT(m_Endpoint == nullptr); + + m_Self = a_Self; + m_Endpoint = cNetwork::CreateUDPEndpoint(a_Port, *this); + return m_Endpoint->IsOpen(); +} + + + + + +bool cLuaUDPEndpoint::Send(const AString & a_Data, const AString & a_RemotePeer, UInt16 a_RemotePort) +{ + // Safely grab a copy of the endpoint: + cUDPEndpointPtr Endpoint = m_Endpoint; + if (Endpoint == nullptr) + { + return false; + } + + // Send the data: + return Endpoint->Send(a_Data, a_RemotePeer, a_RemotePort); +} + + + + + +UInt16 cLuaUDPEndpoint::GetPort(void) const +{ + // Safely grab a copy of the endpoint: + cUDPEndpointPtr Endpoint = m_Endpoint; + if (Endpoint == nullptr) + { + return 0; + } + + // Get the port: + return Endpoint->GetPort(); +} + + + + + +bool cLuaUDPEndpoint::IsOpen(void) const +{ + // Safely grab a copy of the endpoint: + cUDPEndpointPtr Endpoint = m_Endpoint; + if (Endpoint == nullptr) + { + // No endpoint means that we're not open + return false; + } + + // Get the state: + return Endpoint->IsOpen(); +} + + + + + +void cLuaUDPEndpoint::Close(void) +{ + // If the endpoint is still open, close it: + cUDPEndpointPtr Endpoint = m_Endpoint; + if (Endpoint != nullptr) + { + Endpoint->Close(); + m_Endpoint.reset(); + } + + Terminated(); +} + + + + + +void cLuaUDPEndpoint::EnableBroadcasts(void) +{ + // Safely grab a copy of the endpoint: + cUDPEndpointPtr Endpoint = m_Endpoint; + if (Endpoint != nullptr) + { + Endpoint->EnableBroadcasts(); + } +} + + + + + +void cLuaUDPEndpoint::Release(void) +{ + // Close the endpoint, if not already closed: + Close(); + + // Allow self to deallocate: + m_Self.reset(); +} + + + + + +void cLuaUDPEndpoint::Terminated(void) +{ + // Disable the callbacks: + if (m_Callbacks.IsValid()) + { + m_Callbacks.UnRef(); + } + + // If the endpoint is still open, close it: + { + cUDPEndpointPtr Endpoint = m_Endpoint; + if (Endpoint != nullptr) + { + Endpoint->Close(); + m_Endpoint.reset(); + } + } +} + + + + + +void cLuaUDPEndpoint::OnReceivedData(const char * a_Data, size_t a_NumBytes, const AString & a_RemotePeer, UInt16 a_RemotePort) +{ + // Check if we're still valid: + if (!m_Callbacks.IsValid()) + { + return; + } + + // Call the callback: + cPluginLua::cOperation Op(m_Plugin); + if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnReceivedData"), this, AString(a_Data, a_NumBytes), a_RemotePeer, a_RemotePort)) + { + LOGINFO("cUDPEndpoint OnReceivedData callback failed in plugin %s.", m_Plugin.GetName().c_str()); + } +} + + + + + +void cLuaUDPEndpoint::OnError(int a_ErrorCode, const AString & a_ErrorMsg) +{ + // Check if we're still valid: + if (!m_Callbacks.IsValid()) + { + return; + } + + // Call the callback: + cPluginLua::cOperation Op(m_Plugin); + if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnError"), a_ErrorCode, a_ErrorMsg)) + { + LOGINFO("cUDPEndpoint OnError() callback failed in plugin %s; the endpoint error is %d (%s).", + m_Plugin.GetName().c_str(), a_ErrorCode, a_ErrorMsg.c_str() + ); + } + + Terminated(); +} + + + + diff --git a/src/Bindings/LuaUDPEndpoint.h b/src/Bindings/LuaUDPEndpoint.h new file mode 100644 index 000000000..0587491ab --- /dev/null +++ b/src/Bindings/LuaUDPEndpoint.h @@ -0,0 +1,86 @@ + +// LuaUDPEndpoint.h + +// Declares the cLuaUDPEndpoint class representing a Lua wrapper for the cUDPEndpoint class and the callbacks it needs + + + + + +#pragma once + +#include "../OSSupport/Network.h" +#include "PluginLua.h" + + + + + +// fwd: +class cLuaUDPEndpoint; +typedef SharedPtr<cLuaUDPEndpoint> cLuaUDPEndpointPtr; + + + + + +class cLuaUDPEndpoint: + public cUDPEndpoint::cCallbacks +{ +public: + /** Creates a new instance of the endpoint, attached to the specified plugin and wrapping the callbacks that are in a table at the specified stack pos. */ + cLuaUDPEndpoint(cPluginLua & a_Plugin, int a_CallbacksTableStackPos); + + ~cLuaUDPEndpoint(); + + /** Opens the endpoint so that it starts listening for incoming data on the specified port. + a_Self is the shared pointer to self that the object keeps to keep itself alive for as long as it needs (for Lua). */ + bool Open(UInt16 a_Port, cLuaUDPEndpointPtr a_Self); + + /** Sends the data contained in the string to the specified remote peer. + Returns true if successful, false on immediate failure (queueing the data failed etc.) */ + bool Send(const AString & a_Data, const AString & a_RemotePeer, UInt16 a_RemotePort); + + /** Returns the port on which endpoint is listening for incoming data. */ + UInt16 GetPort(void) const; + + /** Returns true if the endpoint is open for incoming data. */ + bool IsOpen(void) const; + + /** Closes the UDP listener. */ + void Close(void); + + /** Enables outgoing broadcasts to be made using this endpoint. */ + void EnableBroadcasts(void); + + /** Called when Lua garbage-collects the object. + Releases the internal SharedPtr to self, so that the instance may be deallocated. */ + void Release(void); + +protected: + /** The plugin for which the link is created. */ + cPluginLua & m_Plugin; + + /** The Lua table that holds the callbacks to be invoked. */ + cLuaState::cRef m_Callbacks; + + /** SharedPtr to self, so that the object can keep itself alive for as long as it needs (for Lua). */ + cLuaUDPEndpointPtr m_Self; + + /** The underlying network endpoint. + May be nullptr. */ + cUDPEndpointPtr m_Endpoint; + + + /** Common code called when the endpoint is considered as terminated. + Releases m_Endpoint and m_Callbacks, each when applicable. */ + void Terminated(void); + + // cUDPEndpoint::cCallbacks overrides: + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override; + virtual void OnReceivedData(const char * a_Data, size_t a_Size, const AString & a_RemotePeer, UInt16 a_RemotePort) override; +}; + + + + diff --git a/src/Bindings/LuaWindow.cpp b/src/Bindings/LuaWindow.cpp index 35730878d..d4014059b 100644 --- a/src/Bindings/LuaWindow.cpp +++ b/src/Bindings/LuaWindow.cpp @@ -167,6 +167,24 @@ void cLuaWindow::Destroy(void) +void cLuaWindow::DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer& a_Player, cSlotArea * a_ClickedArea, bool a_ShouldApply) +{ + cSlotAreas Areas; + for (auto Area : m_SlotAreas) + { + if (Area != a_ClickedArea) + { + Areas.push_back(Area); + } + } + + super::DistributeStackToAreas(a_ItemStack, a_Player, Areas, a_ShouldApply, false); +} + + + + + void cLuaWindow::OnSlotChanged(cItemGrid * a_ItemGrid, int a_SlotNum) { if (a_ItemGrid != &m_Contents) diff --git a/src/Bindings/LuaWindow.h b/src/Bindings/LuaWindow.h index dab99a2e2..76530d99d 100644 --- a/src/Bindings/LuaWindow.h +++ b/src/Bindings/LuaWindow.h @@ -84,6 +84,7 @@ protected: // cWindow overrides: virtual bool ClosedByPlayer(cPlayer & a_Player, bool a_CanRefuse) override; virtual void Destroy(void) override; + virtual void DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Player, cSlotArea * a_ClickedArea, bool a_ShouldApply) override; // cItemGrid::cListener overrides: virtual void OnSlotChanged(cItemGrid * a_ItemGrid, int a_SlotNum) override; diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp index 24e3f0052..40ac12b41 100644 --- a/src/Bindings/ManualBindings.cpp +++ b/src/Bindings/ManualBindings.cpp @@ -3,8 +3,11 @@ #include "ManualBindings.h" #undef TOLUA_TEMPLATE_BIND +#include <sstream> +#include <iomanip> #include "tolua++/include/tolua++.h" #include "polarssl/md5.h" +#include "polarssl/sha1.h" #include "PluginLua.h" #include "PluginManager.h" #include "LuaWindow.h" @@ -28,6 +31,7 @@ #include "../LineBlockTracer.h" #include "../WorldStorage/SchematicFileSerializer.h" #include "../CompositeChat.h" +#include "../StringCompression.h" @@ -107,6 +111,146 @@ static int tolua_Clamp(lua_State * tolua_S) +static int tolua_CompressStringZLIB(lua_State * tolua_S) +{ + cLuaState S(tolua_S); + if ( + !S.CheckParamString(1) || + ( + !S.CheckParamNumber(2) && + !S.CheckParamEnd(2) + ) + ) + { + cLuaState::LogStackTrace(tolua_S); + return 0; + } + + // Get the params: + AString ToCompress; + int CompressionLevel = 5; + S.GetStackValues(1, ToCompress, CompressionLevel); + + // Compress the string: + AString res; + CompressString(ToCompress.data(), ToCompress.size(), res, CompressionLevel); + S.Push(res); + return 1; +} + + + + + +static int tolua_UncompressStringZLIB(lua_State * tolua_S) +{ + cLuaState S(tolua_S); + if ( + !S.CheckParamString(1) || + !S.CheckParamNumber(2) + ) + { + cLuaState::LogStackTrace(tolua_S); + return 0; + } + + // Get the params: + AString ToUncompress; + int UncompressedSize; + S.GetStackValues(1, ToUncompress, UncompressedSize); + + // Compress the string: + AString res; + UncompressString(ToUncompress.data(), ToUncompress.size(), res, UncompressedSize); + S.Push(res); + return 1; +} + + + + + +static int tolua_CompressStringGZIP(lua_State * tolua_S) +{ + cLuaState S(tolua_S); + if ( + !S.CheckParamString(1) || + !S.CheckParamEnd(2) + ) + { + cLuaState::LogStackTrace(tolua_S); + return 0; + } + + // Get the params: + AString ToCompress; + S.GetStackValues(1, ToCompress); + + // Compress the string: + AString res; + CompressStringGZIP(ToCompress.data(), ToCompress.size(), res); + S.Push(res); + return 1; +} + + + + + +static int tolua_UncompressStringGZIP(lua_State * tolua_S) +{ + cLuaState S(tolua_S); + if ( + !S.CheckParamString(1) || + !S.CheckParamEnd(2) + ) + { + cLuaState::LogStackTrace(tolua_S); + return 0; + } + + // Get the params: + AString ToUncompress; + S.GetStackValues(1, ToUncompress); + + // Compress the string: + AString res; + UncompressStringGZIP(ToUncompress.data(), ToUncompress.size(), res); + S.Push(res); + return 1; +} + + + + + +static int tolua_InflateString(lua_State * tolua_S) +{ + cLuaState S(tolua_S); + if ( + !S.CheckParamString(1) || + !S.CheckParamEnd(2) + ) + { + cLuaState::LogStackTrace(tolua_S); + return 0; + } + + // Get the params: + AString ToUncompress; + S.GetStackValues(1, ToUncompress); + + // Compress the string: + AString res; + InflateString(ToUncompress.data(), ToUncompress.size(), res); + S.Push(res); + return 1; +} + + + + + static int tolua_StringSplit(lua_State * tolua_S) { cLuaState LuaState(tolua_S); @@ -122,6 +266,24 @@ static int tolua_StringSplit(lua_State * tolua_S) +static int tolua_StringSplitWithQuotes(lua_State * tolua_S) +{ + cLuaState S(tolua_S); + + AString str; + AString delim; + + S.GetStackValues(1, str, delim); + + AStringVector Split = StringSplitWithQuotes(str, delim); + S.Push(Split); + return 1; +} + + + + + static int tolua_StringSplitAndTrim(lua_State * tolua_S) { cLuaState LuaState(tolua_S); @@ -165,6 +327,14 @@ static AString GetLogMessage(lua_State * tolua_S) static int tolua_LOG(lua_State * tolua_S) { + // If there's no param, spit out an error message instead of crashing: + if (lua_isnil(tolua_S, 1)) + { + LOGWARNING("Attempting to LOG a nil value!"); + cLuaState::LogStackTrace(tolua_S); + return 0; + } + // If the param is a cCompositeChat, read the log level from it: cLogger::eLogLevel LogLevel = cLogger::llRegular; tolua_Error err; @@ -184,6 +354,14 @@ static int tolua_LOG(lua_State * tolua_S) static int tolua_LOGINFO(lua_State * tolua_S) { + // If there's no param, spit out an error message instead of crashing: + if (lua_isnil(tolua_S, 1)) + { + LOGWARNING("Attempting to LOGINFO a nil value!"); + cLuaState::LogStackTrace(tolua_S); + return 0; + } + cLogger::GetInstance().LogSimple(GetLogMessage(tolua_S).c_str(), cLogger::llInfo); return 0; } @@ -194,6 +372,14 @@ static int tolua_LOGINFO(lua_State * tolua_S) static int tolua_LOGWARN(lua_State * tolua_S) { + // If there's no param, spit out an error message instead of crashing: + if (lua_isnil(tolua_S, 1)) + { + LOGWARNING("Attempting to LOGWARN a nil value!"); + cLuaState::LogStackTrace(tolua_S); + return 0; + } + cLogger::GetInstance().LogSimple(GetLogMessage(tolua_S).c_str(), cLogger::llWarning); return 0; } @@ -204,6 +390,14 @@ static int tolua_LOGWARN(lua_State * tolua_S) static int tolua_LOGERROR(lua_State * tolua_S) { + // If there's no param, spit out an error message instead of crashing: + if (lua_isnil(tolua_S, 1)) + { + LOGWARNING("Attempting to LOGERROR a nil value!"); + cLuaState::LogStackTrace(tolua_S); + return 0; + } + cLogger::GetInstance().LogSimple(GetLogMessage(tolua_S).c_str(), cLogger::llError); return 0; } @@ -2171,11 +2365,16 @@ static int tolua_cPlugin_Call(lua_State * tolua_S) -static int tolua_md5(lua_State* tolua_S) +static int tolua_md5(lua_State * tolua_S) { + // Calculate the raw md5 checksum byte array: unsigned char Output[16]; size_t len = 0; const unsigned char * SourceString = (const unsigned char *)lua_tolstring(tolua_S, 1, &len); + if (SourceString == nullptr) + { + return 0; + } md5(SourceString, len, Output); lua_pushlstring(tolua_S, (const char *)Output, ARRAYCOUNT(Output)); return 1; @@ -2185,6 +2384,91 @@ static int tolua_md5(lua_State* tolua_S) +/** Does the same as tolua_md5, but reports that the usage is obsolete and the plugin should use cCrypto.md5(). */ +static int tolua_md5_obsolete(lua_State * tolua_S) +{ + LOGWARNING("Using md5() is obsolete, please change your plugin to use cCryptoHash.md5()"); + cLuaState::LogStackTrace(tolua_S); + return tolua_md5(tolua_S); +} + + + + + +static int tolua_md5HexString(lua_State * tolua_S) +{ + // Calculate the raw md5 checksum byte array: + unsigned char md5Output[16]; + size_t len = 0; + const unsigned char * SourceString = (const unsigned char *)lua_tolstring(tolua_S, 1, &len); + if (SourceString == nullptr) + { + return 0; + } + md5(SourceString, len, md5Output); + + // Convert the md5 checksum to hex string: + std::stringstream Output; + Output << std::hex << std::setfill('0'); + for (size_t i = 0; i < ARRAYCOUNT(md5Output); i++) + { + Output << std::setw(2) << static_cast<unsigned short>(md5Output[i]); // Need to cast to a number, otherwise a char is output + } + lua_pushlstring(tolua_S, Output.str().c_str(), Output.str().size()); + return 1; +} + + + + + +static int tolua_sha1(lua_State * tolua_S) +{ + // Calculate the raw SHA1 checksum byte array from the input string: + unsigned char Output[20]; + size_t len = 0; + const unsigned char * SourceString = (const unsigned char *)lua_tolstring(tolua_S, 1, &len); + if (SourceString == nullptr) + { + return 0; + } + sha1(SourceString, len, Output); + lua_pushlstring(tolua_S, (const char *)Output, ARRAYCOUNT(Output)); + return 1; +} + + + + + +static int tolua_sha1HexString(lua_State * tolua_S) +{ + // Calculate the raw SHA1 checksum byte array from the input string: + unsigned char sha1Output[20]; + size_t len = 0; + const unsigned char * SourceString = (const unsigned char *)lua_tolstring(tolua_S, 1, &len); + if (SourceString == nullptr) + { + return 0; + } + sha1(SourceString, len, sha1Output); + + // Convert the sha1 checksum to hex string: + std::stringstream Output; + Output << std::hex << std::setfill('0'); + for (size_t i = 0; i < ARRAYCOUNT(sha1Output); i++) + { + Output << std::setw(2) << static_cast<unsigned short>(sha1Output[i]); // Need to cast to a number, otherwise a char is output + } + lua_pushlstring(tolua_S, Output.str().c_str(), Output.str().size()); + return 1; +} + + + + + static int tolua_push_StringStringMap(lua_State* tolua_S, std::map< std::string, std::string >& a_StringStringMap) { lua_newtable(tolua_S); @@ -3387,16 +3671,26 @@ static int tolua_cCompositeChat_UnderlineUrls(lua_State * tolua_S) void ManualBindings::Bind(lua_State * tolua_S) { tolua_beginmodule(tolua_S, nullptr); - tolua_function(tolua_S, "Clamp", tolua_Clamp); - tolua_function(tolua_S, "StringSplit", tolua_StringSplit); - tolua_function(tolua_S, "StringSplitAndTrim", tolua_StringSplitAndTrim); - tolua_function(tolua_S, "LOG", tolua_LOG); - tolua_function(tolua_S, "LOGINFO", tolua_LOGINFO); - tolua_function(tolua_S, "LOGWARN", tolua_LOGWARN); - tolua_function(tolua_S, "LOGWARNING", tolua_LOGWARN); - tolua_function(tolua_S, "LOGERROR", tolua_LOGERROR); - tolua_function(tolua_S, "Base64Encode", tolua_Base64Encode); - tolua_function(tolua_S, "Base64Decode", tolua_Base64Decode); + + // Create the new classes: + tolua_usertype(tolua_S, "cCryptoHash"); + tolua_cclass(tolua_S, "cCryptoHash", "cCryptoHash", "", nullptr); + tolua_usertype(tolua_S, "cStringCompression"); + tolua_cclass(tolua_S, "cStringCompression", "cStringCompression", "", nullptr); + + // Globals: + tolua_function(tolua_S, "Clamp", tolua_Clamp); + tolua_function(tolua_S, "StringSplit", tolua_StringSplit); + tolua_function(tolua_S, "StringSplitWithQuotes", tolua_StringSplitWithQuotes); + tolua_function(tolua_S, "StringSplitAndTrim", tolua_StringSplitAndTrim); + tolua_function(tolua_S, "LOG", tolua_LOG); + tolua_function(tolua_S, "LOGINFO", tolua_LOGINFO); + tolua_function(tolua_S, "LOGWARN", tolua_LOGWARN); + tolua_function(tolua_S, "LOGWARNING", tolua_LOGWARN); + tolua_function(tolua_S, "LOGERROR", tolua_LOGERROR); + tolua_function(tolua_S, "Base64Encode", tolua_Base64Encode); + tolua_function(tolua_S, "Base64Decode", tolua_Base64Decode); + tolua_function(tolua_S, "md5", tolua_md5_obsolete); // OBSOLETE, use cCryptoHash.md5() instead tolua_beginmodule(tolua_S, "cFile"); tolua_function(tolua_S, "GetFolderContents", tolua_cFile_GetFolderContents); @@ -3553,7 +3847,20 @@ void ManualBindings::Bind(lua_State * tolua_S) tolua_function(tolua_S, "GetSlotCoords", Lua_ItemGrid_GetSlotCoords); tolua_endmodule(tolua_S); - tolua_function(tolua_S, "md5", tolua_md5); + tolua_beginmodule(tolua_S, "cCryptoHash"); + tolua_function(tolua_S, "md5", tolua_md5); + tolua_function(tolua_S, "md5HexString", tolua_md5HexString); + tolua_function(tolua_S, "sha1", tolua_sha1); + tolua_function(tolua_S, "sha1HexString", tolua_sha1HexString); + tolua_endmodule(tolua_S); + + tolua_beginmodule(tolua_S, "cStringCompression"); + tolua_function(tolua_S, "CompressStringZLIB", tolua_CompressStringZLIB); + tolua_function(tolua_S, "UncompressStringZLIB", tolua_UncompressStringZLIB); + tolua_function(tolua_S, "CompressStringGZIP", tolua_CompressStringGZIP); + tolua_function(tolua_S, "UncompressStringGZIP", tolua_UncompressStringGZIP); + tolua_function(tolua_S, "InflateString", tolua_InflateString); + tolua_endmodule(tolua_S); BindRankManager(tolua_S); BindNetwork(tolua_S); diff --git a/src/Bindings/ManualBindings_Network.cpp b/src/Bindings/ManualBindings_Network.cpp index 902f687c8..628cda7f0 100644 --- a/src/Bindings/ManualBindings_Network.cpp +++ b/src/Bindings/ManualBindings_Network.cpp @@ -11,6 +11,7 @@ #include "LuaTCPLink.h" #include "LuaNameLookup.h" #include "LuaServerHandle.h" +#include "LuaUDPEndpoint.h" @@ -73,6 +74,85 @@ static int tolua_cNetwork_Connect(lua_State * L) +/** Binds cNetwork::CreateUDPEndpoint */ +static int tolua_cNetwork_CreateUDPEndpoint(lua_State * L) +{ + // Function signature: + // cNetwork:CreateUDPEndpoint(Port, Callbacks) -> cUDPEndpoint + + cLuaState S(L); + if ( + !S.CheckParamUserTable(1, "cNetwork") || + !S.CheckParamNumber(2) || + !S.CheckParamTable(3) || + !S.CheckParamEnd(4) + ) + { + return 0; + } + + // Get the plugin instance: + cPluginLua * Plugin = GetLuaPlugin(L); + if (Plugin == nullptr) + { + // An error message has been already printed in GetLuaPlugin() + S.Push(false); + return 1; + } + + // Read the params: + int Port; + S.GetStackValues(2, Port); + + // Check validity: + if ((Port < 0) || (Port > 65535)) + { + LOGWARNING("cNetwork:CreateUDPEndpoint() called with invalid port (%d), failing the request.", Port); + S.Push(false); + return 1; + } + + // Create the LuaUDPEndpoint glue class: + auto Endpoint = std::make_shared<cLuaUDPEndpoint>(*Plugin, 3); + Endpoint->Open(Port, Endpoint); + + // Register the endpoint to be garbage-collected by Lua: + tolua_pushusertype(L, Endpoint.get(), "cUDPEndpoint"); + tolua_register_gc(L, lua_gettop(L)); + + // Return the endpoint object: + S.Push(Endpoint.get()); + return 1; +} + + + + + +/** Binds cNetwork::EnumLocalIPAddresses */ +static int tolua_cNetwork_EnumLocalIPAddresses(lua_State * L) +{ + // Function signature: + // cNetwork:EnumLocalIPAddresses() -> {string, ...} + + cLuaState S(L); + if ( + !S.CheckParamUserTable(1, "cNetwork") || + !S.CheckParamEnd(2) + ) + { + return 0; + } + + // Push the enumerated addresses: + S.Push(cNetwork::EnumLocalIPAddresses()); + return 1; +} + + + + + /** Binds cNetwork::HostnameToIP */ static int tolua_cNetwork_HostnameToIP(lua_State * L) { @@ -159,7 +239,7 @@ static int tolua_cNetwork_IPToHostname(lua_State * L) static int tolua_cNetwork_Listen(lua_State * L) { // Function signature: - // cNetwork:Listen(Port, Callbacks) -> bool + // cNetwork:Listen(Port, Callbacks) -> cServerHandle cLuaState S(L); if ( @@ -212,19 +292,102 @@ static int tolua_cNetwork_Listen(lua_State * L) //////////////////////////////////////////////////////////////////////////////// +// cServerHandle bindings (routed through cLuaServerHandle): + +/** Called when Lua destroys the object instance. +Close the server and let it deallocate on its own (it's in a SharedPtr). */ +static int tolua_collect_cServerHandle(lua_State * L) +{ + cLuaServerHandle * Srv = static_cast<cLuaServerHandle *>(tolua_tousertype(L, 1, nullptr)); + Srv->Release(); + return 0; +} + + + + + +/** Binds cLuaServerHandle::Close */ +static int tolua_cServerHandle_Close(lua_State * L) +{ + // Function signature: + // ServerInstance:Close() + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cServerHandle") || + !S.CheckParamEnd(2) + ) + { + return 0; + } + + // Get the server handle: + cLuaServerHandle * Srv; + if (lua_isnil(L, 1)) + { + LOGWARNING("cServerHandle:Close(): invalid server handle object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + Srv = *static_cast<cLuaServerHandle **>(lua_touserdata(L, 1)); + + // Close it: + Srv->Close(); + return 0; +} + + + + + +/** Binds cLuaServerHandle::IsListening */ +static int tolua_cServerHandle_IsListening(lua_State * L) +{ + // Function signature: + // ServerInstance:IsListening() -> bool + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cServerHandle") || + !S.CheckParamEnd(2) + ) + { + return 0; + } + + // Get the server handle: + cLuaServerHandle * Srv; + if (lua_isnil(L, 1)) + { + LOGWARNING("cServerHandle:IsListening(): invalid server handle object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + Srv = *static_cast<cLuaServerHandle **>(lua_touserdata(L, 1)); + + // Close it: + S.Push(Srv->IsListening()); + return 1; +} + + + + + +//////////////////////////////////////////////////////////////////////////////// // cTCPLink bindings (routed through cLuaTCPLink): -/** Binds cLuaTCPLink::Send */ -static int tolua_cTCPLink_Send(lua_State * L) +/** Binds cLuaTCPLink::Close */ +static int tolua_cTCPLink_Close(lua_State * L) { // Function signature: - // LinkInstance:Send(DataString) + // LinkInstance:Close() cLuaState S(L); if ( !S.CheckParamUserType(1, "cTCPLink") || - !S.CheckParamString(2) || - !S.CheckParamEnd(3) + !S.CheckParamEnd(2) ) { return 0; @@ -234,18 +397,14 @@ static int tolua_cTCPLink_Send(lua_State * L) cLuaTCPLink * Link; if (lua_isnil(L, 1)) { - LOGWARNING("cTCPLink:Send(): invalid link object. Stack trace:"); + LOGWARNING("cTCPLink:Close(): invalid link object. Stack trace:"); S.LogStackTrace(); return 0; } Link = *static_cast<cLuaTCPLink **>(lua_touserdata(L, 1)); - // Get the data to send: - AString Data; - S.GetStackValues(2, Data); - - // Send the data: - Link->Send(Data); + // CLose the link: + Link->Close(); return 0; } @@ -389,15 +548,180 @@ static int tolua_cTCPLink_GetRemotePort(lua_State * L) +/** Binds cLuaTCPLink::Send */ +static int tolua_cTCPLink_Send(lua_State * L) +{ + // Function signature: + // LinkInstance:Send(DataString) + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cTCPLink") || + !S.CheckParamString(2) || + !S.CheckParamEnd(3) + ) + { + return 0; + } + + // Get the link: + cLuaTCPLink * Link; + if (lua_isnil(L, 1)) + { + LOGWARNING("cTCPLink:Send(): invalid link object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + Link = *static_cast<cLuaTCPLink **>(lua_touserdata(L, 1)); + + // Get the data to send: + AString Data; + S.GetStackValues(2, Data); + + // Send the data: + Link->Send(Data); + return 0; +} + + + + + +/** Binds cLuaTCPLink::Shutdown */ +static int tolua_cTCPLink_Shutdown(lua_State * L) +{ + // Function signature: + // LinkInstance:Shutdown() + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cTCPLink") || + !S.CheckParamEnd(2) + ) + { + return 0; + } + + // Get the link: + cLuaTCPLink * Link; + if (lua_isnil(L, 1)) + { + LOGWARNING("cTCPLink:Shutdown(): invalid link object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + Link = *static_cast<cLuaTCPLink **>(lua_touserdata(L, 1)); + + // Shutdown the link: + Link->Shutdown(); + return 0; +} + + + + + +/** Binds cLuaTCPLink::StartTLSClient */ +static int tolua_cTCPLink_StartTLSClient(lua_State * L) +{ + // Function signature: + // LinkInstance:StartTLSClient(OwnCert, OwnPrivKey, OwnPrivKeyPassword) -> [true] or [nil, ErrMsg] + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cTCPLink") || + !S.CheckParamString(2, 4) || + !S.CheckParamEnd(5) + ) + { + return 0; + } + + // Get the link: + cLuaTCPLink * Link; + if (lua_isnil(L, 1)) + { + LOGWARNING("cTCPLink:StartTLSClient(): invalid link object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + Link = *static_cast<cLuaTCPLink **>(lua_touserdata(L, 1)); + + // Read the params: + AString OwnCert, OwnPrivKey, OwnPrivKeyPassword; + S.GetStackValues(2, OwnCert, OwnPrivKey, OwnPrivKeyPassword); + + // Start the TLS handshake: + AString res = Link->StartTLSClient(OwnCert, OwnPrivKey, OwnPrivKeyPassword); + if (!res.empty()) + { + S.PushNil(); + S.Push(Printf("Cannot start TLS on link to %s:%d: %s", Link->GetRemoteIP().c_str(), Link->GetRemotePort(), res.c_str())); + return 2; + } + return 1; +} + + + + + +/** Binds cLuaTCPLink::StartTLSServer */ +static int tolua_cTCPLink_StartTLSServer(lua_State * L) +{ + // Function signature: + // LinkInstance:StartTLSServer(OwnCert, OwnPrivKey, OwnPrivKeyPassword, StartTLSData) -> [true] or [nil, ErrMsg] + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cTCPLink") || + !S.CheckParamString(2, 4) || + // Param 5 is optional, don't check + !S.CheckParamEnd(6) + ) + { + return 0; + } + + // Get the link: + cLuaTCPLink * Link; + if (lua_isnil(L, 1)) + { + LOGWARNING("cTCPLink:StartTLSServer(): invalid link object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + Link = *static_cast<cLuaTCPLink **>(lua_touserdata(L, 1)); + + // Read the params: + AString OwnCert, OwnPrivKey, OwnPrivKeyPassword, StartTLSData; + S.GetStackValues(2, OwnCert, OwnPrivKey, OwnPrivKeyPassword, StartTLSData); + + // Start the TLS handshake: + AString res = Link->StartTLSServer(OwnCert, OwnPrivKey, OwnPrivKeyPassword, StartTLSData); + if (!res.empty()) + { + S.PushNil(); + S.Push(Printf("Cannot start TLS on link to %s:%d: %s", Link->GetRemoteIP().c_str(), Link->GetRemotePort(), res.c_str())); + return 2; + } + return 1; +} + + + + + //////////////////////////////////////////////////////////////////////////////// -// cServerHandle bindings (routed through cLuaServerHandle): +// cUDPEndpoint bindings (routed through cLuaUDPEndpoint): /** Called when Lua destroys the object instance. -Close the server and let it deallocate on its own (it's in a SharedPtr). */ -static int tolua_collect_cServerHandle(lua_State * L) +Close the endpoint and let it deallocate on its own (it's in a SharedPtr). */ +static int tolua_collect_cUDPEndpoint(lua_State * L) { - cLuaServerHandle * Srv = static_cast<cLuaServerHandle *>(tolua_tousertype(L, 1, nullptr)); - Srv->Release(); + LOGD("Lua: Collecting cUDPEndpoint"); + cLuaUDPEndpoint * Endpoint = static_cast<cLuaUDPEndpoint *>(tolua_tousertype(L, 1, nullptr)); + Endpoint->Release(); return 0; } @@ -405,33 +729,32 @@ static int tolua_collect_cServerHandle(lua_State * L) -/** Binds cLuaServerHandle::Close */ -static int tolua_cServerHandle_Close(lua_State * L) +/** Binds cLuaUDPEndpoint::Close */ +static int tolua_cUDPEndpoint_Close(lua_State * L) { // Function signature: - // ServerInstance:Close() + // EndpointInstance:Close() cLuaState S(L); if ( - !S.CheckParamUserType(1, "cServerHandle") || + !S.CheckParamUserType(1, "cUDPEndpoint") || !S.CheckParamEnd(2) ) { return 0; } - // Get the server handle: - cLuaServerHandle * Srv; + // Get the endpoint: if (lua_isnil(L, 1)) { - LOGWARNING("cServerHandle:Close(): invalid server handle object. Stack trace:"); + LOGWARNING("cUDPEndpoint:Close(): invalid endpoint object. Stack trace:"); S.LogStackTrace(); return 0; } - Srv = *static_cast<cLuaServerHandle **>(lua_touserdata(L, 1)); + auto Endpoint = *static_cast<cLuaUDPEndpoint **>(lua_touserdata(L, 1)); // Close it: - Srv->Close(); + Endpoint->Close(); return 0; } @@ -439,33 +762,147 @@ static int tolua_cServerHandle_Close(lua_State * L) -/** Binds cLuaServerHandle::IsListening */ -static int tolua_cServerHandle_IsListening(lua_State * L) +/** Binds cLuaUDPEndpoint::EnableBroadcasts */ +static int tolua_cUDPEndpoint_EnableBroadcasts(lua_State * L) { // Function signature: - // ServerInstance:IsListening() -> bool + // EndpointInstance:EnableBroadcasts() cLuaState S(L); if ( - !S.CheckParamUserType(1, "cServerHandle") || + !S.CheckParamUserType(1, "cUDPEndpoint") || !S.CheckParamEnd(2) ) { return 0; } - // Get the server handle: - cLuaServerHandle * Srv; + // Get the endpoint: if (lua_isnil(L, 1)) { - LOGWARNING("cServerHandle:IsListening(): invalid server handle object. Stack trace:"); + LOGWARNING("cUDPEndpoint:EnableBroadcasts(): invalid endpoint object. Stack trace:"); S.LogStackTrace(); return 0; } - Srv = *static_cast<cLuaServerHandle **>(lua_touserdata(L, 1)); + auto Endpoint = *static_cast<cLuaUDPEndpoint **>(lua_touserdata(L, 1)); + + // Enable the broadcasts: + Endpoint->EnableBroadcasts(); + return 0; +} + + + + + +/** Binds cLuaUDPEndpoint::GetPort */ +static int tolua_cUDPEndpoint_GetPort(lua_State * L) +{ + // Function signature: + // Endpoint:GetPort() -> number + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cUDPEndpoint") || + !S.CheckParamEnd(2) + ) + { + return 0; + } + + // Get the endpoint: + if (lua_isnil(L, 1)) + { + LOGWARNING("cUDPEndpoint:GetPort(): invalid endpoint object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + auto Endpoint = *static_cast<cLuaUDPEndpoint **>(lua_touserdata(L, 1)); + + // Get the Port: + S.Push(Endpoint->GetPort()); + return 1; +} + + + + + +/** Binds cLuaUDPEndpoint::IsOpen */ +static int tolua_cUDPEndpoint_IsOpen(lua_State * L) +{ + // Function signature: + // Endpoint:IsOpen() -> bool + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cUDPEndpoint") || + !S.CheckParamEnd(2) + ) + { + return 0; + } + + // Get the endpoint: + if (lua_isnil(L, 1)) + { + LOGWARNING("cUDPEndpoint:IsListening(): invalid server handle object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + auto Endpoint = *static_cast<cLuaUDPEndpoint **>(lua_touserdata(L, 1)); // Close it: - S.Push(Srv->IsListening()); + S.Push(Endpoint->IsOpen()); + return 1; +} + + + + + +/** Binds cLuaUDPEndpoint::Send */ +static int tolua_cUDPEndpoint_Send(lua_State * L) +{ + // Function signature: + // LinkInstance:Send(DataString) + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cUDPEndpoint") || + !S.CheckParamString(2, 3) || + !S.CheckParamNumber(4) || + !S.CheckParamEnd(5) + ) + { + return 0; + } + + // Get the link: + if (lua_isnil(L, 1)) + { + LOGWARNING("cUDPEndpoint:Send(): invalid endpoint object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + auto Endpoint = *static_cast<cLuaUDPEndpoint **>(lua_touserdata(L, 1)); + + // Get the data to send: + AString Data, RemotePeer; + int RemotePort; + S.GetStackValues(2, Data, RemotePeer, RemotePort); + + // Check the port: + if ((RemotePort < 0) || (RemotePort > USHRT_MAX)) + { + LOGWARNING("cUDPEndpoint:Send() called with invalid port (%d), failing.", RemotePort); + S.LogStackTrace(); + S.Push(false); + return 1; + } + + // Send the data: + S.Push(Endpoint->Send(Data, RemotePeer, static_cast<UInt16>(RemotePort))); return 1; } @@ -485,27 +922,44 @@ void ManualBindings::BindNetwork(lua_State * tolua_S) tolua_cclass(tolua_S, "cTCPLink", "cTCPLink", "", nullptr); tolua_usertype(tolua_S, "cServerHandle"); tolua_cclass(tolua_S, "cServerHandle", "cServerHandle", "", tolua_collect_cServerHandle); + tolua_usertype(tolua_S, "cUDPEndpoint"); + tolua_cclass(tolua_S, "cUDPEndpoint", "cUDPEndpoint", "", tolua_collect_cUDPEndpoint); // Fill in the functions (alpha-sorted): tolua_beginmodule(tolua_S, "cNetwork"); - tolua_function(tolua_S, "Connect", tolua_cNetwork_Connect); - tolua_function(tolua_S, "HostnameToIP", tolua_cNetwork_HostnameToIP); - tolua_function(tolua_S, "IPToHostname", tolua_cNetwork_IPToHostname); - tolua_function(tolua_S, "Listen", tolua_cNetwork_Listen); - tolua_endmodule(tolua_S); - - tolua_beginmodule(tolua_S, "cTCPLink"); - tolua_function(tolua_S, "Send", tolua_cTCPLink_Send); - tolua_function(tolua_S, "GetLocalIP", tolua_cTCPLink_GetLocalIP); - tolua_function(tolua_S, "GetLocalPort", tolua_cTCPLink_GetLocalPort); - tolua_function(tolua_S, "GetRemoteIP", tolua_cTCPLink_GetRemoteIP); - tolua_function(tolua_S, "GetRemotePort", tolua_cTCPLink_GetRemotePort); + tolua_function(tolua_S, "Connect", tolua_cNetwork_Connect); + tolua_function(tolua_S, "CreateUDPEndpoint", tolua_cNetwork_CreateUDPEndpoint); + tolua_function(tolua_S, "EnumLocalIPAddresses", tolua_cNetwork_EnumLocalIPAddresses); + tolua_function(tolua_S, "HostnameToIP", tolua_cNetwork_HostnameToIP); + tolua_function(tolua_S, "IPToHostname", tolua_cNetwork_IPToHostname); + tolua_function(tolua_S, "Listen", tolua_cNetwork_Listen); tolua_endmodule(tolua_S); tolua_beginmodule(tolua_S, "cServerHandle"); tolua_function(tolua_S, "Close", tolua_cServerHandle_Close); tolua_function(tolua_S, "IsListening", tolua_cServerHandle_IsListening); tolua_endmodule(tolua_S); + + tolua_beginmodule(tolua_S, "cTCPLink"); + tolua_function(tolua_S, "Close", tolua_cTCPLink_Close); + tolua_function(tolua_S, "GetLocalIP", tolua_cTCPLink_GetLocalIP); + tolua_function(tolua_S, "GetLocalPort", tolua_cTCPLink_GetLocalPort); + tolua_function(tolua_S, "GetRemoteIP", tolua_cTCPLink_GetRemoteIP); + tolua_function(tolua_S, "GetRemotePort", tolua_cTCPLink_GetRemotePort); + tolua_function(tolua_S, "Send", tolua_cTCPLink_Send); + tolua_function(tolua_S, "Shutdown", tolua_cTCPLink_Shutdown); + tolua_function(tolua_S, "StartTLSClient", tolua_cTCPLink_StartTLSClient); + tolua_function(tolua_S, "StartTLSServer", tolua_cTCPLink_StartTLSServer); + tolua_endmodule(tolua_S); + + tolua_beginmodule(tolua_S, "cUDPEndpoint"); + tolua_function(tolua_S, "Close", tolua_cUDPEndpoint_Close); + tolua_function(tolua_S, "EnableBroadcasts", tolua_cUDPEndpoint_EnableBroadcasts); + tolua_function(tolua_S, "GetPort", tolua_cUDPEndpoint_GetPort); + tolua_function(tolua_S, "IsOpen", tolua_cUDPEndpoint_IsOpen); + tolua_function(tolua_S, "Send", tolua_cUDPEndpoint_Send); + tolua_endmodule(tolua_S); + } diff --git a/src/Bindings/Plugin.h b/src/Bindings/Plugin.h index 6210dbed4..3f9fa7655 100644 --- a/src/Bindings/Plugin.h +++ b/src/Bindings/Plugin.h @@ -57,6 +57,7 @@ public: virtual bool OnCraftingNoRecipe (cPlayer & a_Player, cCraftingGrid & a_Grid, cCraftingRecipe & a_Recipe) = 0; virtual bool OnDisconnect (cClientHandle & a_Client, const AString & a_Reason) = 0; virtual bool OnEntityAddEffect (cEntity & a_Entity, int a_EffectType, int a_EffectDurationTicks, int a_EffectIntensity, double a_DistanceModifier) = 0; + virtual bool OnEntityTeleport (cEntity & a_Entity, const Vector3d & a_OldPosition, const Vector3d & a_NewPosition) = 0; virtual bool OnExecuteCommand (cPlayer * a_Player, const AStringVector & a_Split) = 0; virtual bool OnExploded (cWorld & a_World, double a_ExplosionSize, bool a_CanCauseFire, double a_X, double a_Y, double a_Z, eExplosionSource a_Source, void * a_SourceData) = 0; virtual bool OnExploding (cWorld & a_World, double & a_ExplosionSize, bool & a_CanCauseFire, double a_X, double a_Y, double a_Z, eExplosionSource a_Source, void * a_SourceData) = 0; @@ -110,12 +111,12 @@ public: Command permissions have already been checked. Returns true if command handled successfully */ - virtual bool HandleCommand(const AStringVector & a_Split, cPlayer & a_Player) = 0; + virtual bool HandleCommand(const AStringVector & a_Split, cPlayer & a_Player, const AString & a_FullCommand) = 0; /** Handles the console command split into a_Split. Returns true if command handled successfully. Output is to be sent to the a_Output callback. */ - virtual bool HandleConsoleCommand(const AStringVector & a_Split, cCommandOutputCallback & a_Output) = 0; + virtual bool HandleConsoleCommand(const AStringVector & a_Split, cCommandOutputCallback & a_Output, const AString & a_FullCommand) = 0; /// All bound commands are to be removed, do any language-dependent cleanup here virtual void ClearCommands(void) {} diff --git a/src/Bindings/PluginLua.cpp b/src/Bindings/PluginLua.cpp index 500913e76..263d1f005 100644 --- a/src/Bindings/PluginLua.cpp +++ b/src/Bindings/PluginLua.cpp @@ -857,6 +857,26 @@ bool cPluginLua::OnPlayerMoving(cPlayer & a_Player, const Vector3d & a_OldPositi +bool cPluginLua::OnEntityTeleport(cEntity & a_Entity, const Vector3d & a_OldPosition, const Vector3d & a_NewPosition) +{ + cCSLock Lock(m_CriticalSection); + bool res = false; + cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_ENTITY_TELEPORT]; + for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) + { + m_LuaState.Call((int)(**itr), &a_Entity, a_OldPosition, a_NewPosition, cLuaState::Return, res); + if (res) + { + return true; + } + } + return false; +} + + + + + bool cPluginLua::OnPlayerPlacedBlock(cPlayer & a_Player, const sSetBlock & a_BlockChange) { cCSLock Lock(m_CriticalSection); @@ -1445,7 +1465,7 @@ bool cPluginLua::OnWorldTick(cWorld & a_World, std::chrono::milliseconds a_Dt, s -bool cPluginLua::HandleCommand(const AStringVector & a_Split, cPlayer & a_Player) +bool cPluginLua::HandleCommand(const AStringVector & a_Split, cPlayer & a_Player, const AString & a_FullCommand) { ASSERT(!a_Split.empty()); CommandMap::iterator cmd = m_Commands.find(a_Split[0]); @@ -1457,7 +1477,7 @@ bool cPluginLua::HandleCommand(const AStringVector & a_Split, cPlayer & a_Player cCSLock Lock(m_CriticalSection); bool res = false; - m_LuaState.Call(cmd->second, a_Split, &a_Player, cLuaState::Return, res); + m_LuaState.Call(cmd->second, a_Split, &a_Player, a_FullCommand, cLuaState::Return, res); return res; } @@ -1465,7 +1485,7 @@ bool cPluginLua::HandleCommand(const AStringVector & a_Split, cPlayer & a_Player -bool cPluginLua::HandleConsoleCommand(const AStringVector & a_Split, cCommandOutputCallback & a_Output) +bool cPluginLua::HandleConsoleCommand(const AStringVector & a_Split, cCommandOutputCallback & a_Output, const AString & a_FullCommand) { ASSERT(!a_Split.empty()); CommandMap::iterator cmd = m_ConsoleCommands.find(a_Split[0]); @@ -1480,7 +1500,7 @@ bool cPluginLua::HandleConsoleCommand(const AStringVector & a_Split, cCommandOut cCSLock Lock(m_CriticalSection); bool res = false; AString str; - m_LuaState.Call(cmd->second, a_Split, cLuaState::Return, res, str); + m_LuaState.Call(cmd->second, a_Split, a_FullCommand, cLuaState::Return, res, str); if (res && !str.empty()) { a_Output.Out(str); @@ -1577,6 +1597,7 @@ const char * cPluginLua::GetHookFnName(int a_HookType) case cPluginManager::HOOK_DISCONNECT: return "OnDisconnect"; case cPluginManager::HOOK_PLAYER_ANIMATION: return "OnPlayerAnimation"; case cPluginManager::HOOK_ENTITY_ADD_EFFECT: return "OnEntityAddEffect"; + case cPluginManager::HOOK_ENTITY_TELEPORT: return "OnEntityTeleport"; case cPluginManager::HOOK_EXECUTE_COMMAND: return "OnExecuteCommand"; case cPluginManager::HOOK_HANDSHAKE: return "OnHandshake"; case cPluginManager::HOOK_KILLING: return "OnKilling"; diff --git a/src/Bindings/PluginLua.h b/src/Bindings/PluginLua.h index f443f5fc0..4f3529fc9 100644 --- a/src/Bindings/PluginLua.h +++ b/src/Bindings/PluginLua.h @@ -106,6 +106,7 @@ public: virtual bool OnPlayerRightClickingEntity(cPlayer & a_Player, cEntity & a_Entity) override; virtual bool OnPlayerShooting (cPlayer & a_Player) override; virtual bool OnPlayerSpawned (cPlayer & a_Player) override; + virtual bool OnEntityTeleport (cEntity & a_Entity, const Vector3d & a_OldPosition, const Vector3d & a_NewPosition) override; virtual bool OnPlayerTossingItem (cPlayer & a_Player) override; virtual bool OnPlayerUsedBlock (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) override; virtual bool OnPlayerUsedItem (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ) override; @@ -130,9 +131,9 @@ public: virtual bool OnWorldStarted (cWorld & a_World) override; virtual bool OnWorldTick (cWorld & a_World, std::chrono::milliseconds a_Dt, std::chrono::milliseconds a_LastTickDurationMSec) override; - virtual bool HandleCommand(const AStringVector & a_Split, cPlayer & a_Player) override; + virtual bool HandleCommand(const AStringVector & a_Split, cPlayer & a_Player, const AString & a_FullCommand) override; - virtual bool HandleConsoleCommand(const AStringVector & a_Split, cCommandOutputCallback & a_Output) override; + virtual bool HandleConsoleCommand(const AStringVector & a_Split, cCommandOutputCallback & a_Output, const AString & a_FullCommand) override; virtual void ClearCommands(void) override; diff --git a/src/Bindings/PluginManager.cpp b/src/Bindings/PluginManager.cpp index 9d86c64a2..8935f7dd3 100644 --- a/src/Bindings/PluginManager.cpp +++ b/src/Bindings/PluginManager.cpp @@ -505,6 +505,24 @@ bool cPluginManager::CallHookEntityAddEffect(cEntity & a_Entity, int a_EffectTyp +bool cPluginManager::CallHookEntityTeleport(cEntity & a_Entity, const Vector3d & a_OldPosition, const Vector3d & a_NewPosition) +{ + FIND_HOOK(HOOK_ENTITY_TELEPORT); + VERIFY_HOOK; + + for (PluginList::iterator itr = Plugins->second.begin(); itr != Plugins->second.end(); ++itr) + { + if ((*itr)->OnEntityTeleport(a_Entity, a_OldPosition, a_NewPosition)) + { + return true; + } + } + return false; +} + + + + bool cPluginManager::CallHookExecuteCommand(cPlayer * a_Player, const AStringVector & a_Split) { FIND_HOOK(HOOK_EXECUTE_COMMAND); @@ -1447,7 +1465,7 @@ cPluginManager::CommandResult cPluginManager::HandleCommand(cPlayer & a_Player, ASSERT(cmd->second.m_Plugin != nullptr); - if (!cmd->second.m_Plugin->HandleCommand(Split, a_Player)) + if (!cmd->second.m_Plugin->HandleCommand(Split, a_Player, a_Command)) { return crError; } @@ -1750,7 +1768,7 @@ bool cPluginManager::IsConsoleCommandBound(const AString & a_Command) -bool cPluginManager::ExecuteConsoleCommand(const AStringVector & a_Split, cCommandOutputCallback & a_Output) +bool cPluginManager::ExecuteConsoleCommand(const AStringVector & a_Split, cCommandOutputCallback & a_Output, const AString & a_Command) { if (a_Split.empty()) { @@ -1777,7 +1795,7 @@ bool cPluginManager::ExecuteConsoleCommand(const AStringVector & a_Split, cComma return false; } - return cmd->second.m_Plugin->HandleConsoleCommand(a_Split, a_Output); + return cmd->second.m_Plugin->HandleConsoleCommand(a_Split, a_Output, a_Command); } diff --git a/src/Bindings/PluginManager.h b/src/Bindings/PluginManager.h index 97e91c1df..4efcbb6f3 100644 --- a/src/Bindings/PluginManager.h +++ b/src/Bindings/PluginManager.h @@ -109,6 +109,7 @@ public: HOOK_PLAYER_RIGHT_CLICKING_ENTITY, HOOK_PLAYER_SHOOTING, HOOK_PLAYER_SPAWNED, + HOOK_ENTITY_TELEPORT, HOOK_PLAYER_TOSSING_ITEM, HOOK_PLAYER_USED_BLOCK, HOOK_PLAYER_USED_ITEM, @@ -190,6 +191,7 @@ public: bool CallHookCraftingNoRecipe (cPlayer & a_Player, cCraftingGrid & a_Grid, cCraftingRecipe & a_Recipe); bool CallHookDisconnect (cClientHandle & a_Client, const AString & a_Reason); bool CallHookEntityAddEffect (cEntity & a_Entity, int a_EffectType, int a_EffectDurationTicks, int a_EffectIntensity, double a_DistanceModifier); + bool CallHookEntityTeleport (cEntity & a_Entity, const Vector3d & a_OldPosition, const Vector3d & a_NewPosition); bool CallHookExecuteCommand (cPlayer * a_Player, const AStringVector & a_Split); // If a_Player == nullptr, it is a console cmd bool CallHookExploded (cWorld & a_World, double a_ExplosionSize, bool a_CanCauseFire, double a_X, double a_Y, double a_Z, eExplosionSource a_Source, void * a_SourceData); bool CallHookExploding (cWorld & a_World, double & a_ExplosionSize, bool & a_CanCauseFire, double a_X, double a_Y, double a_Z, eExplosionSource a_Source, void * a_SourceData); @@ -282,7 +284,7 @@ public: bool IsConsoleCommandBound(const AString & a_Command); // tolua_export /** Executes the command split into a_Split, as if it was given on the console. Returns true if executed. Output is sent to the a_Output callback */ - bool ExecuteConsoleCommand(const AStringVector & a_Split, cCommandOutputCallback & a_Output); + bool ExecuteConsoleCommand(const AStringVector & a_Split, cCommandOutputCallback & a_Output, const AString & a_Command); /** Appends all commands beginning with a_Text (case-insensitive) into a_Results. If a_Player is not nullptr, only commands for which the player has permissions are added. diff --git a/src/BiomeDef.cpp b/src/BiomeDef.cpp index 188e06173..3e34ebdbe 100644 --- a/src/BiomeDef.cpp +++ b/src/BiomeDef.cpp @@ -222,3 +222,130 @@ bool IsBiomeCold(EMCSBiome a_Biome) + +int GetSnowStartHeight(EMCSBiome a_Biome) +{ + switch (a_Biome) + { + case biIcePlainsSpikes: + case biIcePlains: + case biIceMountains: + case biFrozenRiver: + case biColdBeach: + case biColdTaiga: + case biColdTaigaHills: + case biColdTaigaM: + { + // Always snow + return 0; + } + + case biExtremeHills: + case biExtremeHillsM: + case biExtremeHillsPlus: + case biExtremeHillsPlusM: + case biStoneBeach: + { + // Starts snowing at 96 + return 96; + } + + case biTaiga: + case biTaigaHills: + case biTaigaM: + { + // Start snowing at 130 + return 130; + } + + case biMegaTaiga: + case biMegaSpruceTaiga: + case biMegaTaigaHills: + case biMegaSpruceTaigaHills: + { + // Start snowing at 160 + return 160; + } + + case biRiver: + case biOcean: + case biDeepOcean: + { + // Starts snowing at 280 + return 280; + } + + case biBirchForest: + case biBirchForestHills: + case biBirchForestM: + case biBirchForestHillsM: + { + // Starts snowing at 335 + return 335; + } + + case biForest: + case biForestHills: + case biFlowerForest: + case biRoofedForest: + case biRoofedForestM: + { + // Starts snowing at 400 + return 400; + } + + case biPlains: + case biSunflowerPlains: + case biSwampland: + case biSwamplandM: + case biBeach: + { + // Starts snowing at 460 + return 460; + } + + case biMushroomIsland: + case biMushroomShore: + { + // Starts snowing at 520 + return 520; + } + + case biJungle: + case biJungleHills: + case biJungleM: + case biJungleEdge: + case biJungleEdgeM: + { + // Starts snowing at 550 + return 550; + } + + case biDesert: + case biDesertHills: + case biDesertM: + case biSavanna: + case biSavannaM: + case biSavannaPlateau: + case biSavannaPlateauM: + case biMesa: + case biMesaBryce: + case biMesaPlateau: + case biMesaPlateauF: + case biMesaPlateauFM: + case biMesaPlateauM: + { + // These biomes don't actualy have any downfall. + return 1000; + } + + default: + { + return 0; + } + } +} + + + + diff --git a/src/BiomeDef.h b/src/BiomeDef.h index 84751cfd7..cda12556a 100644 --- a/src/BiomeDef.h +++ b/src/BiomeDef.h @@ -129,4 +129,7 @@ extern bool IsBiomeVeryCold(EMCSBiome a_Biome); Doesn't report Very Cold biomes, use IsBiomeVeryCold() for those. */ extern bool IsBiomeCold(EMCSBiome a_Biome); +/** Returns the height when a biome when a biome starts snowing.*/ +extern int GetSnowStartHeight(EMCSBiome a_Biome); + // tolua_end diff --git a/src/BlockEntities/BeaconEntity.cpp b/src/BlockEntities/BeaconEntity.cpp index 37ce7a8ab..fb3940ce9 100644 --- a/src/BlockEntities/BeaconEntity.cpp +++ b/src/BlockEntities/BeaconEntity.cpp @@ -4,6 +4,7 @@ #include "BeaconEntity.h" #include "../BlockArea.h" #include "../Entities/Player.h" +#include "../UI/BeaconWindow.h" @@ -289,7 +290,7 @@ void cBeaconEntity::UsedBy(cPlayer * a_Player) OpenWindow(new cBeaconWindow(m_PosX, m_PosY, m_PosZ, this)); Window = GetWindow(); } - + if (Window != nullptr) { // if (a_Player->GetWindow() != Window) diff --git a/src/BlockEntities/ChestEntity.cpp b/src/BlockEntities/ChestEntity.cpp index 0cd9c66e0..3821f9aab 100644 --- a/src/BlockEntities/ChestEntity.cpp +++ b/src/BlockEntities/ChestEntity.cpp @@ -4,7 +4,7 @@ #include "ChestEntity.h" #include "../Item.h" #include "../Entities/Player.h" -#include "../UI/Window.h" +#include "../UI/ChestWindow.h" diff --git a/src/BlockEntities/DropSpenserEntity.cpp b/src/BlockEntities/DropSpenserEntity.cpp index 5e98506f1..039f5d360 100644 --- a/src/BlockEntities/DropSpenserEntity.cpp +++ b/src/BlockEntities/DropSpenserEntity.cpp @@ -8,6 +8,7 @@ #include "DropSpenserEntity.h" #include "../Entities/Player.h" #include "../Chunk.h" +#include "../UI/DropSpenserWindow.h" diff --git a/src/BlockEntities/EnderChestEntity.cpp b/src/BlockEntities/EnderChestEntity.cpp index e18490a1e..ab5c5a2de 100644 --- a/src/BlockEntities/EnderChestEntity.cpp +++ b/src/BlockEntities/EnderChestEntity.cpp @@ -4,7 +4,7 @@ #include "EnderChestEntity.h" #include "../Item.h" #include "../Entities/Player.h" -#include "../UI/Window.h" +#include "../UI/EnderChestWindow.h" diff --git a/src/BlockEntities/FurnaceEntity.cpp b/src/BlockEntities/FurnaceEntity.cpp index cc5a00af7..2621b560b 100644 --- a/src/BlockEntities/FurnaceEntity.cpp +++ b/src/BlockEntities/FurnaceEntity.cpp @@ -2,7 +2,7 @@ #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules #include "FurnaceEntity.h" -#include "../UI/Window.h" +#include "../UI/FurnaceWindow.h" #include "../Entities/Player.h" #include "../Root.h" #include "../Chunk.h" diff --git a/src/BlockEntities/HopperEntity.cpp b/src/BlockEntities/HopperEntity.cpp index c2d6cabbb..bfd4b8322 100644 --- a/src/BlockEntities/HopperEntity.cpp +++ b/src/BlockEntities/HopperEntity.cpp @@ -9,6 +9,7 @@ #include "../Entities/Player.h" #include "../Entities/Pickup.h" #include "../Bindings/PluginManager.h" +#include "../UI/HopperWindow.h" #include "ChestEntity.h" #include "FurnaceEntity.h" diff --git a/src/Blocks/BlockAnvil.h b/src/Blocks/BlockAnvil.h index 20514580e..abfa0f782 100644 --- a/src/Blocks/BlockAnvil.h +++ b/src/Blocks/BlockAnvil.h @@ -4,6 +4,7 @@ #include "BlockHandler.h" #include "../World.h" #include "../Entities/Player.h" +#include "../UI/AnvilWindow.h" diff --git a/src/Blocks/BlockDirt.h b/src/Blocks/BlockDirt.h index aae6719e2..12bca92dd 100644 --- a/src/Blocks/BlockDirt.h +++ b/src/Blocks/BlockDirt.h @@ -52,7 +52,19 @@ public: return; } } - + + // Make sure that there is enough light at the source block to spread + if (!a_Chunk.GetWorld()->IsChunkLighted(a_Chunk.GetPosX(), a_Chunk.GetPosZ())) + { + a_Chunk.GetWorld()->QueueLightChunk(a_Chunk.GetPosX(), a_Chunk.GetPosZ()); + return; + } + else if (std::max(a_Chunk.GetBlockLight(a_RelX, a_RelY + 1, a_RelZ), a_Chunk.GetTimeAlteredLight(a_Chunk.GetSkyLight(a_RelX, a_RelY + 1, a_RelZ))) < 9) + { + // Source block is not bright enough to spread + return; + } + // Grass spreads to adjacent dirt blocks: cFastRandom rand; for (int i = 0; i < 2; i++) // Pick two blocks to grow to diff --git a/src/Blocks/BlockEnchantmentTable.h b/src/Blocks/BlockEnchantmentTable.h index 81d2cb9a0..40001f356 100644 --- a/src/Blocks/BlockEnchantmentTable.h +++ b/src/Blocks/BlockEnchantmentTable.h @@ -2,7 +2,7 @@ #pragma once #include "BlockHandler.h" -#include "../UI/Window.h" +#include "../UI/EnchantingWindow.h" #include "../Entities/Player.h" diff --git a/src/Blocks/BlockOre.h b/src/Blocks/BlockOre.h index f6ea3aa3c..08d79f435 100644 --- a/src/Blocks/BlockOre.h +++ b/src/Blocks/BlockOre.h @@ -11,6 +11,7 @@ class cBlockOreHandler : public cBlockHandler { + typedef cBlockHandler super; public: cBlockOreHandler(BLOCKTYPE a_BlockType) : cBlockHandler(a_BlockType) @@ -56,6 +57,64 @@ public: } } } + + virtual void OnDestroyedByPlayer(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer * a_Player, int a_BlockX, int a_BlockY, int a_BlockZ) override + { + super::OnDestroyedByPlayer(a_ChunkInterface, a_WorldInterface, a_Player, a_BlockX, a_BlockY, a_BlockZ); + + if (a_Player->IsGameModeCreative()) + { + // Don't drop XP when the player is in creative mode. + return; + } + + if (a_Player->GetEquippedItem().m_Enchantments.GetLevel(cEnchantments::enchSilkTouch) != 0) + { + // Don't drop XP when the ore is mined with the Silk Touch enchantment + return; + } + + cFastRandom Random; + int Reward = 0; + + switch (m_BlockType) + { + case E_BLOCK_NETHER_QUARTZ_ORE: + case E_BLOCK_LAPIS_ORE: + { + // Lapis and nether quartz get 2 - 5 experience + Reward = Random.NextInt(4) + 2; + break; + } + case E_BLOCK_REDSTONE_ORE: + case E_BLOCK_REDSTONE_ORE_GLOWING: + { + // Redstone gets 1 - 5 experience + Reward = Random.NextInt(5) + 1; + break; + } + case E_BLOCK_DIAMOND_ORE: + case E_BLOCK_EMERALD_ORE: + { + // Diamond and emerald get 3 - 7 experience + Reward = Random.NextInt(5) + 3; + break; + } + case E_BLOCK_COAL_ORE: + { + // Coal gets 0 - 2 experience + Reward = Random.NextInt(3); + break; + } + + default: break; + } + + if (Reward != 0) + { + a_WorldInterface.SpawnExperienceOrb(a_BlockX, a_BlockY, a_BlockZ, Reward); + } + } } ; diff --git a/src/Blocks/BlockWorkbench.h b/src/Blocks/BlockWorkbench.h index 699badaf2..e40e15606 100644 --- a/src/Blocks/BlockWorkbench.h +++ b/src/Blocks/BlockWorkbench.h @@ -2,7 +2,7 @@ #pragma once #include "BlockHandler.h" -#include "../UI/Window.h" +#include "../UI/CraftingWindow.h" #include "../Entities/Player.h" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c39f5f6e6..b91c4f65a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -9,7 +9,7 @@ include_directories (SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/../lib/libevent/include set(FOLDERS OSSupport HTTPServer Items Blocks Protocol Generating PolarSSL++ Bindings - WorldStorage Mobs Entities Simulator UI BlockEntities Generating/Prefabs + WorldStorage Mobs Entities Simulator BlockEntities UI Generating/Prefabs Noise ) @@ -318,7 +318,7 @@ if (NOT MSVC) target_link_libraries(${EXECUTABLE} OSSupport HTTPServer Bindings Items Blocks Noise Protocol Generating Generating_Prefabs WorldStorage - Mobs Entities Simulator UI BlockEntities PolarSSL++ + Mobs Entities Simulator BlockEntities UI PolarSSL++ ) endif () if (WIN32) diff --git a/src/Chunk.cpp b/src/Chunk.cpp index a4198c322..00ac1fdb1 100644 --- a/src/Chunk.cpp +++ b/src/Chunk.cpp @@ -776,10 +776,22 @@ void cChunk::BroadcastPendingBlockChanges(void) { return; } - - for (cClientHandleList::iterator itr = m_LoadedByClient.begin(), end = m_LoadedByClient.end(); itr != end; ++itr) + + if (m_PendingSendBlocks.size() >= 10240) { - (*itr)->SendBlockChanges(m_PosX, m_PosZ, m_PendingSendBlocks); + // Resend the full chunk + for (cClientHandleList::iterator itr = m_LoadedByClient.begin(), end = m_LoadedByClient.end(); itr != end; ++itr) + { + m_World->ForceSendChunkTo(m_PosX, m_PosZ, cChunkSender::E_CHUNK_PRIORITY_MEDIUM, (*itr)); + } + } + else + { + // Only send block changes + for (cClientHandleList::iterator itr = m_LoadedByClient.begin(), end = m_LoadedByClient.end(); itr != end; ++itr) + { + (*itr)->SendBlockChanges(m_PosX, m_PosZ, m_PendingSendBlocks); + } } m_PendingSendBlocks.clear(); } @@ -874,80 +886,71 @@ void cChunk::ApplyWeatherToTop() int X = m_World->GetTickRandomNumber(15); int Z = m_World->GetTickRandomNumber(15); - switch (GetBiomeAt(X, Z)) - { - case biTaiga: - case biFrozenOcean: - case biFrozenRiver: - case biIcePlains: - case biIceMountains: - case biTaigaHills: - { - // TODO: Check light levels, don't snow over when the BlockLight is higher than (7?) - int Height = GetHeight(X, Z); - BLOCKTYPE TopBlock = GetBlock(X, Height, Z); - NIBBLETYPE TopMeta = GetMeta (X, Height, Z); - if (m_World->IsDeepSnowEnabled() && (TopBlock == E_BLOCK_SNOW)) + + // TODO: Check light levels, don't snow over when the BlockLight is higher than (7?) + int Height = GetHeight(X, Z); + + if (GetSnowStartHeight(GetBiomeAt(X, Z)) > Height) + { + return; + } + + BLOCKTYPE TopBlock = GetBlock(X, Height, Z); + NIBBLETYPE TopMeta = GetMeta (X, Height, Z); + if (m_World->IsDeepSnowEnabled() && (TopBlock == E_BLOCK_SNOW)) + { + int MaxSize = 7; + BLOCKTYPE BlockType[4]; + NIBBLETYPE BlockMeta[4]; + UnboundedRelGetBlock(X - 1, Height, Z, BlockType[0], BlockMeta[0]); + UnboundedRelGetBlock(X + 1, Height, Z, BlockType[1], BlockMeta[1]); + UnboundedRelGetBlock(X, Height, Z - 1, BlockType[2], BlockMeta[2]); + UnboundedRelGetBlock(X, Height, Z + 1, BlockType[3], BlockMeta[3]); + for (int i = 0; i < 4; i++) + { + switch (BlockType[i]) { - int MaxSize = 7; - BLOCKTYPE BlockType[4]; - NIBBLETYPE BlockMeta[4]; - UnboundedRelGetBlock(X - 1, Height, Z, BlockType[0], BlockMeta[0]); - UnboundedRelGetBlock(X + 1, Height, Z, BlockType[1], BlockMeta[1]); - UnboundedRelGetBlock(X, Height, Z - 1, BlockType[2], BlockMeta[2]); - UnboundedRelGetBlock(X, Height, Z + 1, BlockType[3], BlockMeta[3]); - for (int i = 0; i < 4; i++) - { - switch (BlockType[i]) - { - case E_BLOCK_AIR: - { - MaxSize = 0; - break; - } - case E_BLOCK_SNOW: - { - MaxSize = std::min(BlockMeta[i] + 1, MaxSize); - break; - } - } - } - if (TopMeta < MaxSize) + case E_BLOCK_AIR: { - FastSetBlock(X, Height, Z, E_BLOCK_SNOW, TopMeta + 1); + MaxSize = 0; + break; } - else if (TopMeta > MaxSize) + case E_BLOCK_SNOW: { - FastSetBlock(X, Height, Z, E_BLOCK_SNOW, TopMeta - 1); + MaxSize = std::min(BlockMeta[i] + 1, MaxSize); + break; } } - else if (cBlockInfo::IsSnowable(TopBlock) && (Height + 1 < cChunkDef::Height)) - { - SetBlock(X, Height + 1, Z, E_BLOCK_SNOW, 0); - } - else if ((TopBlock == E_BLOCK_WATER) || (TopBlock == E_BLOCK_STATIONARY_WATER)) - { - SetBlock(X, Height, Z, E_BLOCK_ICE, 0); - } - else if ( - (m_World->IsDeepSnowEnabled()) && - ( - (TopBlock == E_BLOCK_RED_ROSE) || - (TopBlock == E_BLOCK_YELLOW_FLOWER) || - (TopBlock == E_BLOCK_RED_MUSHROOM) || - (TopBlock == E_BLOCK_BROWN_MUSHROOM) - ) - ) - { - SetBlock(X, Height, Z, E_BLOCK_SNOW, 0); - } - break; - } // case (snowy biomes) - default: + } + if (TopMeta < MaxSize) { - break; + FastSetBlock(X, Height, Z, E_BLOCK_SNOW, TopMeta + 1); + } + else if (TopMeta > MaxSize) + { + FastSetBlock(X, Height, Z, E_BLOCK_SNOW, TopMeta - 1); } - } // switch (biome) + } + else if (cBlockInfo::IsSnowable(TopBlock) && (Height + 1 < cChunkDef::Height)) + { + SetBlock(X, Height + 1, Z, E_BLOCK_SNOW, 0); + } + else if (IsBlockWater(TopBlock) && (TopMeta == 0)) + { + SetBlock(X, Height, Z, E_BLOCK_ICE, 0); + } + else if ( + (m_World->IsDeepSnowEnabled()) && + ( + (TopBlock == E_BLOCK_RED_ROSE) || + (TopBlock == E_BLOCK_YELLOW_FLOWER) || + (TopBlock == E_BLOCK_RED_MUSHROOM) || + (TopBlock == E_BLOCK_BROWN_MUSHROOM) + ) + ) + { + SetBlock(X, Height, Z, E_BLOCK_SNOW, 0); + } } diff --git a/src/ChunkData.cpp b/src/ChunkData.cpp index 57b27c5e0..80f40d39e 100644 --- a/src/ChunkData.cpp +++ b/src/ChunkData.cpp @@ -361,7 +361,7 @@ void cChunkData::CopyBlockTypes(BLOCKTYPE * a_Dest, size_t a_Idx, size_t a_Lengt } else { - memset(&a_Dest[(i * SectionBlockCount) - a_Idx], 0, sizeof(BLOCKTYPE) * ToCopy); + memset(&a_Dest[(i * SectionBlockCount) + StartPos - a_Idx], 0, sizeof(BLOCKTYPE) * ToCopy); } } } diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index 9a3c59b86..ec10ac521 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -12,6 +12,9 @@ #include "BlockEntities/CommandBlockEntity.h" #include "BlockEntities/SignEntity.h" #include "UI/Window.h" +#include "UI/AnvilWindow.h" +#include "UI/BeaconWindow.h" +#include "UI/EnchantingWindow.h" #include "Item.h" #include "Mobs/Monster.h" #include "ChatColor.h" @@ -1871,9 +1874,13 @@ void cClientHandle::Tick(float a_Dt) cCSLock Lock(m_CSOutgoingData); std::swap(OutgoingData, m_OutgoingData); } - if ((m_Link != nullptr) && !OutgoingData.empty()) + if (!OutgoingData.empty()) { - m_Link->Send(OutgoingData.data(), OutgoingData.size()); + cTCPLinkPtr Link(m_Link); // Grab a copy of the link in a multithread-safe way + if ((Link != nullptr)) + { + Link->Send(OutgoingData.data(), OutgoingData.size()); + } } m_TicksSinceLastPacket += 1; diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp index c51a27961..1bc4690e1 100644 --- a/src/Entities/Entity.cpp +++ b/src/Entities/Entity.cpp @@ -1632,8 +1632,12 @@ void cEntity::TeleportToEntity(cEntity & a_Entity) void cEntity::TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ) { - SetPosition(a_PosX, a_PosY, a_PosZ); - m_World->BroadcastTeleportEntity(*this); + // ask the plugins to allow teleport to the new position. + if (!cRoot::Get()->GetPluginManager()->CallHookEntityTeleport(*this, m_LastPos, Vector3d(a_PosX, a_PosY, a_PosZ))) + { + SetPosition(a_PosX, a_PosY, a_PosZ); + m_World->BroadcastTeleportEntity(*this); + } } @@ -1913,10 +1917,7 @@ void cEntity::AddPosition(double a_AddPosX, double a_AddPosY, double a_AddPosZ) void cEntity::AddSpeed(double a_AddSpeedX, double a_AddSpeedY, double a_AddSpeedZ) { - m_Speed.x += a_AddSpeedX; - m_Speed.y += a_AddSpeedY; - m_Speed.z += a_AddSpeedZ; - WrapSpeed(); + DoSetSpeed(m_Speed.x + a_AddSpeedX, m_Speed.y + a_AddSpeedY, m_Speed.z + a_AddSpeedZ); } @@ -1925,8 +1926,7 @@ void cEntity::AddSpeed(double a_AddSpeedX, double a_AddSpeedY, double a_AddSpeed void cEntity::AddSpeedX(double a_AddSpeedX) { - m_Speed.x += a_AddSpeedX; - WrapSpeed(); + AddSpeed(a_AddSpeedX, 0, 0); } @@ -1935,8 +1935,7 @@ void cEntity::AddSpeedX(double a_AddSpeedX) void cEntity::AddSpeedY(double a_AddSpeedY) { - m_Speed.y += a_AddSpeedY; - WrapSpeed(); + AddSpeed(0, a_AddSpeedY, 0); } @@ -1945,8 +1944,7 @@ void cEntity::AddSpeedY(double a_AddSpeedY) void cEntity::AddSpeedZ(double a_AddSpeedZ) { - m_Speed.z += a_AddSpeedZ; - WrapSpeed(); + AddSpeed(0, 0, a_AddSpeedZ); } diff --git a/src/Entities/Minecart.cpp b/src/Entities/Minecart.cpp index 776f957f4..a32926838 100644 --- a/src/Entities/Minecart.cpp +++ b/src/Entities/Minecart.cpp @@ -11,6 +11,7 @@ #include "../Chunk.h" #include "Player.h" #include "../BoundingBox.h" +#include "../UI/MinecartWithChestWindow.h" #define NO_SPEED 0.0 #define MAX_SPEED 8 diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index 527380761..c89e7b87c 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -5,7 +5,7 @@ #include <unordered_map> #include "../ChatColor.h" #include "../Server.h" -#include "../UI/Window.h" +#include "../UI/InventoryWindow.h" #include "../UI/WindowOwner.h" #include "../World.h" #include "../Bindings/PluginManager.h" @@ -105,15 +105,15 @@ cPlayer::cPlayer(cClientHandlePtr a_Client, const AString & a_PlayerName) : SetPosX(World->GetSpawnX()); SetPosY(World->GetSpawnY()); SetPosZ(World->GetSpawnZ()); - SetBedPos(Vector3i((int)World->GetSpawnX(), (int)World->GetSpawnY(), (int)World->GetSpawnZ())); + SetBedPos(Vector3i(static_cast<int>(World->GetSpawnX()), static_cast<int>(World->GetSpawnY()), static_cast<int>(World->GetSpawnZ()))); LOGD("Player \"%s\" is connecting for the first time, spawning at default world spawn {%.2f, %.2f, %.2f}", a_PlayerName.c_str(), GetPosX(), GetPosY(), GetPosZ() ); } - m_LastJumpHeight = (float)(GetPosY()); - m_LastGroundHeight = (float)(GetPosY()); + m_LastJumpHeight = static_cast<float>(GetPosY()); + m_LastGroundHeight = static_cast<float>(GetPosY()); m_Stance = GetPosY() + 1.62; if (m_GameMode == gmNotSet) @@ -232,7 +232,7 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) } bool CanMove = true; - if (!GetPosition().EqualsEps(m_LastPos, 0.01)) // Non negligible change in position from last tick? + if (!GetPosition().EqualsEps(m_LastPos, 0.02)) // Non negligible change in position from last tick? 0.02 tp prevent continous calling while floating sometimes. { // Apply food exhaustion from movement: ApplyFoodExhaustionFromMovement(); @@ -278,7 +278,7 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) if (IsFlying()) { - m_LastGroundHeight = (float)GetPosY(); + m_LastGroundHeight = static_cast<float>(GetPosY()); } if (m_TicksUntilNextSave == 0) @@ -296,7 +296,7 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) -short cPlayer::CalcLevelFromXp(short a_XpTotal) +int cPlayer::CalcLevelFromXp(int a_XpTotal) { // level 0 to 15 if (a_XpTotal <= XP_TO_LEVEL15) @@ -307,18 +307,18 @@ short cPlayer::CalcLevelFromXp(short a_XpTotal) // level 30+ if (a_XpTotal > XP_TO_LEVEL30) { - return (short) (151.5 + sqrt( 22952.25 - (14 * (2220 - a_XpTotal)))) / 7; + return static_cast<int>((151.5 + sqrt( 22952.25 - (14 * (2220 - a_XpTotal)))) / 7); } // level 16 to 30 - return (short) ( 29.5 + sqrt( 870.25 - (6 * ( 360 - a_XpTotal)))) / 3; + return static_cast<int>((29.5 + sqrt( 870.25 - (6 * ( 360 - a_XpTotal)))) / 3); } -short cPlayer::XpForLevel(short a_Level) +int cPlayer::XpForLevel(int a_Level) { // level 0 to 15 if (a_Level <= 15) @@ -329,18 +329,18 @@ short cPlayer::XpForLevel(short a_Level) // level 30+ if (a_Level >= 31) { - return (short) ( (3.5 * a_Level * a_Level) - (151.5 * a_Level) + 2220); + return static_cast<int>((3.5 * a_Level * a_Level) - (151.5 * a_Level) + 2220); } // level 16 to 30 - return (short) ( (1.5 * a_Level * a_Level) - (29.5 * a_Level) + 360); + return static_cast<int>((1.5 * a_Level * a_Level) - (29.5 * a_Level) + 360); } -short cPlayer::GetXpLevel() +int cPlayer::GetXpLevel() { return CalcLevelFromXp(m_CurrentXp); } @@ -351,20 +351,20 @@ short cPlayer::GetXpLevel() float cPlayer::GetXpPercentage() { - short int currentLevel = CalcLevelFromXp(m_CurrentXp); - short int currentLevel_XpBase = XpForLevel(currentLevel); + int currentLevel = CalcLevelFromXp(m_CurrentXp); + int currentLevel_XpBase = XpForLevel(currentLevel); - return (float)(m_CurrentXp - currentLevel_XpBase) / - (float)(XpForLevel(1+currentLevel) - currentLevel_XpBase); + return static_cast<float>(m_CurrentXp - currentLevel_XpBase) / + static_cast<float>(XpForLevel(1+currentLevel) - currentLevel_XpBase); } -bool cPlayer::SetCurrentExperience(short int a_CurrentXp) +bool cPlayer::SetCurrentExperience(int a_CurrentXp) { - if (!(a_CurrentXp >= 0) || (a_CurrentXp > (std::numeric_limits<short>().max() - m_LifetimeTotalXp))) + if (!(a_CurrentXp >= 0) || (a_CurrentXp > (std::numeric_limits<int>().max() - m_LifetimeTotalXp))) { LOGWARNING("Tried to update experiece with an invalid Xp value: %d", a_CurrentXp); return false; // oops, they gave us a dodgey number @@ -382,19 +382,20 @@ bool cPlayer::SetCurrentExperience(short int a_CurrentXp) -short cPlayer::DeltaExperience(short a_Xp_delta) +int cPlayer::DeltaExperience(int a_Xp_delta) { - if (a_Xp_delta > (std::numeric_limits<short>().max() - m_CurrentXp)) + if (a_Xp_delta > (std::numeric_limits<int>().max() - m_CurrentXp)) { // Value was bad, abort and report - LOGWARNING("Attempt was made to increment Xp by %d, which overflowed the short datatype. Ignoring.", a_Xp_delta); + LOGWARNING("Attempt was made to increment Xp by %d, which overflowed the int datatype. Ignoring.", a_Xp_delta); return -1; // Should we instead just return the current Xp? } m_CurrentXp += a_Xp_delta; // Make sure they didn't subtract too much - m_CurrentXp = std::max<short>(m_CurrentXp, 0); + m_CurrentXp = std::max(m_CurrentXp, 0); + // Update total for score calculation if (a_Xp_delta > 0) @@ -466,7 +467,7 @@ void cPlayer::SetTouchGround(bool a_bTouchGround) { if (GetPosY() > m_LastJumpHeight) { - m_LastJumpHeight = (float)GetPosY(); + m_LastJumpHeight = static_cast<float>(GetPosY()); } cWorld * World = GetWorld(); if ((GetPosY() >= 0) && (GetPosY() < cChunkDef::Height)) @@ -483,13 +484,13 @@ void cPlayer::SetTouchGround(bool a_bTouchGround) (BlockType == E_BLOCK_VINES) ) { - m_LastGroundHeight = (float)GetPosY(); + m_LastGroundHeight = static_cast<float>(GetPosY()); } } } else { - float Dist = (float)(m_LastGroundHeight - floor(GetPosY())); + float Dist = static_cast<float>(m_LastGroundHeight - floor(GetPosY())); if (Dist >= 2.0) // At least two blocks - TODO: Use m_LastJumpHeight instead of m_LastGroundHeight above { @@ -497,12 +498,12 @@ void cPlayer::SetTouchGround(bool a_bTouchGround) m_Stats.AddValue(statDistFallen, (StatValue)floor(Dist * 100 + 0.5)); } - int Damage = (int)(Dist - 3.f); + int Damage = static_cast<int>(Dist - 3.f); if (m_LastJumpHeight > m_LastGroundHeight) { Damage++; } - m_LastJumpHeight = (float)GetPosY(); + m_LastJumpHeight = static_cast<float>(GetPosY()); if (Damage > 0) { @@ -510,10 +511,10 @@ void cPlayer::SetTouchGround(bool a_bTouchGround) TakeDamage(dtFalling, nullptr, Damage, Damage, 0); // Fall particles - GetWorld()->BroadcastSoundParticleEffect(2006, POSX_TOINT, (int)GetPosY() - 1, POSZ_TOINT, Damage /* Used as particle effect speed modifier */); + GetWorld()->BroadcastSoundParticleEffect(2006, POSX_TOINT, static_cast<int>(GetPosY()) - 1, POSZ_TOINT, Damage /* Used as particle effect speed modifier */); } - m_LastGroundHeight = (float)GetPosY(); + m_LastGroundHeight = static_cast<float>(GetPosY()); } } @@ -551,7 +552,7 @@ void cPlayer::SetFoodLevel(int a_FoodLevel) void cPlayer::SetFoodSaturationLevel(double a_FoodSaturationLevel) { - m_FoodSaturationLevel = Clamp(a_FoodSaturationLevel, 0.0, (double) m_FoodLevel); + m_FoodSaturationLevel = Clamp(a_FoodSaturationLevel, 0.0, static_cast<double>(m_FoodLevel)); } @@ -1274,13 +1275,17 @@ unsigned int cPlayer::AwardAchievement(const eStatistic a_Ach) void cPlayer::TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ) { - SetPosition(a_PosX, a_PosY, a_PosZ); - m_LastGroundHeight = (float)a_PosY; - m_LastJumpHeight = (float)a_PosY; - m_bIsTeleporting = true; + // ask plugins to allow teleport to the new position. + if (!cRoot::Get()->GetPluginManager()->CallHookEntityTeleport(*this, m_LastPos, Vector3d(a_PosX, a_PosY, a_PosZ))) + { + SetPosition(a_PosX, a_PosY, a_PosZ); + m_LastGroundHeight = static_cast<float>(a_PosY); + m_LastJumpHeight = static_cast<float>(a_PosY); + m_bIsTeleporting = true; - m_World->BroadcastTeleportEntity(*this, GetClientHandle()); - m_ClientHandle->SendPlayerMoveLook(); + m_World->BroadcastTeleportEntity(*this, GetClientHandle()); + m_ClientHandle->SendPlayerMoveLook(); + } } @@ -1714,9 +1719,9 @@ bool cPlayer::LoadFromFile(const AString & a_FileName, cWorldPtr & a_World) Json::Value & JSON_PlayerRotation = root["rotation"]; if (JSON_PlayerRotation.size() == 3) { - SetYaw ((float)JSON_PlayerRotation[(unsigned)0].asDouble()); - SetPitch ((float)JSON_PlayerRotation[(unsigned)1].asDouble()); - SetRoll ((float)JSON_PlayerRotation[(unsigned)2].asDouble()); + SetYaw (static_cast<float>(JSON_PlayerRotation[(unsigned)0].asDouble())); + SetPitch (static_cast<float>(JSON_PlayerRotation[(unsigned)1].asDouble())); + SetRoll (static_cast<float>(JSON_PlayerRotation[(unsigned)2].asDouble())); } m_Health = root.get("health", 0).asInt(); @@ -1725,9 +1730,9 @@ bool cPlayer::LoadFromFile(const AString & a_FileName, cWorldPtr & a_World) m_FoodSaturationLevel = root.get("foodSaturation", MAX_FOOD_LEVEL).asDouble(); m_FoodTickTimer = root.get("foodTickTimer", 0).asInt(); m_FoodExhaustionLevel = root.get("foodExhaustion", 0).asDouble(); - m_LifetimeTotalXp = (short) root.get("xpTotal", 0).asInt(); - m_CurrentXp = (short) root.get("xpCurrent", 0).asInt(); - m_IsFlying = root.get("isflying", 0).asBool(); + m_LifetimeTotalXp = root.get("xpTotal", 0).asInt(); + m_CurrentXp = root.get("xpCurrent", 0).asInt(); + m_IsFlying = root.get("isflying", 0).asBool(); m_GameMode = (eGameMode) root.get("gamemode", eGameMode_NotSet).asInt(); @@ -1812,18 +1817,18 @@ bool cPlayer::SaveToDisk() root["world"] = m_World->GetName(); if (m_GameMode == m_World->GetGameMode()) { - root["gamemode"] = (int) eGameMode_NotSet; + root["gamemode"] = static_cast<int>(eGameMode_NotSet); } else { - root["gamemode"] = (int) m_GameMode; + root["gamemode"] = static_cast<int>(m_GameMode); } } else { // This happens if the player is saved to new format after loading from the old format root["world"] = m_LoadedWorldName; - root["gamemode"] = (int) eGameMode_NotSet; + root["gamemode"] = static_cast<int>(eGameMode_NotSet); } Json::StyledWriter writer; @@ -1839,7 +1844,7 @@ bool cPlayer::SaveToDisk() ); return false; } - if (f.Write(JsonData.c_str(), JsonData.size()) != (int)JsonData.size()) + if (f.Write(JsonData.c_str(), JsonData.size()) != static_cast<int>(JsonData.size())) { LOGWARNING("Error writing player \"%s\" to file \"%s\" - cannot save data. Player will lose their progress. ", GetName().c_str(), SourceFile.c_str() @@ -1894,7 +1899,7 @@ void cPlayer::UseEquippedItem(int a_Amount) if (GetInventory().DamageEquippedItem(a_Amount)) { - m_World->BroadcastSoundEffect("random.break", GetPosX(), GetPosY(), GetPosZ(), 0.5f, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64)); + m_World->BroadcastSoundEffect("random.break", GetPosX(), GetPosY(), GetPosZ(), 0.5f, static_cast<float>(0.75 + (static_cast<float>((GetUniqueID() * 23) % 32)) / 64)); } } @@ -2042,17 +2047,17 @@ void cPlayer::UpdateMovementStats(const Vector3d & a_DeltaPos) else if (IsSubmerged()) { m_Stats.AddValue(statDistDove, Value); - AddFoodExhaustion(0.00015 * (double)Value); + AddFoodExhaustion(0.00015 * static_cast<double>(Value)); } else if (IsSwimming()) { m_Stats.AddValue(statDistSwum, Value); - AddFoodExhaustion(0.00015 * (double)Value); + AddFoodExhaustion(0.00015 * static_cast<double>(Value)); } else if (IsOnGround()) { m_Stats.AddValue(statDistWalked, Value); - AddFoodExhaustion((m_IsSprinting ? 0.001 : 0.0001) * (double)Value); + AddFoodExhaustion((m_IsSprinting ? 0.001 : 0.0001) * static_cast<double>(Value)); } else { diff --git a/src/Entities/Player.h b/src/Entities/Player.h index 10cf6f7e8..3dae58dc1 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -72,22 +72,22 @@ public: Returns true on success "should" really only be called at init or player death, plugins excepted */ - bool SetCurrentExperience(short a_XpTotal); + bool SetCurrentExperience(int a_XpTotal); /* changes Xp by Xp_delta, you "shouldn't" inc more than MAX_EXPERIENCE_ORB_SIZE Wont't allow xp to go negative Returns the new current experience, -1 on error */ - short DeltaExperience(short a_Xp_delta); + int DeltaExperience(int a_Xp_delta); /** Gets the experience total - XpTotal for score on death */ - inline short GetXpLifetimeTotal(void) { return m_LifetimeTotalXp; } + inline int GetXpLifetimeTotal(void) { return m_LifetimeTotalXp; } /** Gets the currrent experience */ - inline short GetCurrentXp(void) { return m_CurrentXp; } + inline int GetCurrentXp(void) { return m_CurrentXp; } /** Gets the current level - XpLevel */ - short GetXpLevel(void); + int GetXpLevel(void); /** Gets the experience bar percentage - XpP */ float GetXpPercentage(void); @@ -95,13 +95,13 @@ public: /** Caculates the amount of XP needed for a given level Ref: http://minecraft.gamepedia.com/XP */ - static short XpForLevel(short int a_Level); + static int XpForLevel(int a_Level); /** Inverse of XpForLevel Ref: http://minecraft.gamepedia.com/XP values are as per this with pre-calculations */ - static short CalcLevelFromXp(short int a_CurrentXp); + static int CalcLevelFromXp(int a_CurrentXp); // tolua_end @@ -591,8 +591,8 @@ protected: Int64 m_EatingFinishTick; /** Player Xp level */ - short int m_LifetimeTotalXp; - short int m_CurrentXp; + int m_LifetimeTotalXp; + int m_CurrentXp; // flag saying we need to send a xp update to client bool m_bDirtyExperience; diff --git a/src/FurnaceRecipe.cpp b/src/FurnaceRecipe.cpp index 112aa8146..ea952a852 100644 --- a/src/FurnaceRecipe.cpp +++ b/src/FurnaceRecipe.cpp @@ -291,6 +291,22 @@ const cFurnaceRecipe::cRecipe * cFurnaceRecipe::GetRecipeFrom(const cItem & a_In +bool cFurnaceRecipe::IsFuel(const cItem & a_Item) const +{ + for (auto & Fuel : m_pState->Fuel) + { + if ((Fuel.In->m_ItemType == a_Item.m_ItemType) && (Fuel.In->m_ItemCount <= a_Item.m_ItemCount)) + { + return true; + } + } + return false; +} + + + + + int cFurnaceRecipe::GetBurnTime(const cItem & a_Fuel) const { int BestFuel = 0; diff --git a/src/FurnaceRecipe.h b/src/FurnaceRecipe.h index 936ef706d..912b6aba2 100644 --- a/src/FurnaceRecipe.h +++ b/src/FurnaceRecipe.h @@ -34,6 +34,9 @@ public: /** Returns a recipe for the specified input, nullptr if no recipe found */ const cRecipe * GetRecipeFrom(const cItem & a_Ingredient) const; + + /** Returns true if the item is a fuel, false if not. */ + bool IsFuel(const cItem & a_Item) const; /** Returns the amount of time that the specified fuel burns, in ticks */ int GetBurnTime(const cItem & a_Fuel) const; diff --git a/src/Generating/BioGen.cpp b/src/Generating/BioGen.cpp index 378ece6a3..265db30ad 100644 --- a/src/Generating/BioGen.cpp +++ b/src/Generating/BioGen.cpp @@ -1036,7 +1036,7 @@ protected: //////////////////////////////////////////////////////////////////////////////// -// cBioGenGrown: +// cBioGenProtGrown: class cBioGenProtGrown: public cBiomeGen diff --git a/src/Generating/CompoGenBiomal.cpp b/src/Generating/CompoGenBiomal.cpp index 030c2baa5..3140bd754 100644 --- a/src/Generating/CompoGenBiomal.cpp +++ b/src/Generating/CompoGenBiomal.cpp @@ -542,6 +542,20 @@ protected: HasHadWater = true; } // for y a_ChunkDesc.SetBlockType(a_RelX, 0, a_RelZ, E_BLOCK_BEDROCK); + + EMCSBiome MesaVersion = a_ChunkDesc.GetBiome(a_RelX, a_RelZ); + if ((MesaVersion == biMesaPlateauF) || (MesaVersion == biMesaPlateauFM)) + { + if (Top < 95 + static_cast<int>(m_MesaFloor.CubicNoise2D(NoiseY * 2, NoiseX * 2) * 6)) + { + return; + } + + BLOCKTYPE Block = (m_MesaFloor.CubicNoise2D(NoiseX * 4, NoiseY * 4) < 0) ? E_BLOCK_DIRT : E_BLOCK_GRASS; + NIBBLETYPE Meta = (Block == E_BLOCK_GRASS) ? 0 : 1; + + a_ChunkDesc.SetBlockTypeMeta(a_RelX, Top, a_RelZ, Block, Meta); + } } diff --git a/src/Generating/ComposableGenerator.cpp b/src/Generating/ComposableGenerator.cpp index bda45ad92..4a670b064 100644 --- a/src/Generating/ComposableGenerator.cpp +++ b/src/Generating/ComposableGenerator.cpp @@ -616,6 +616,11 @@ void cComposableGenerator::InitFinishGens(cIniFile & a_IniFile) int MaxDensity = a_IniFile.GetValueSetI("Generator", "VillageMaxDensity", 80); m_FinishGens.push_back(std::make_shared<cVillageGen>(Seed, GridSize, MaxOffset, MaxDepth, MaxSize, MinDensity, MaxDensity, m_BiomeGen, m_CompositedHeightCache)); } + else if (NoCaseCompare(*itr, "Vines") == 0) + { + int Level = a_IniFile.GetValueSetI("Generator", "VinesLevel", 40); + m_FinishGens.push_back(std::make_shared<cFinishGenVines>(Seed, Level)); + } else if (NoCaseCompare(*itr, "WaterLakes") == 0) { int Probability = a_IniFile.GetValueSetI("Generator", "WaterLakesProbability", 25); diff --git a/src/Generating/FinishGen.cpp b/src/Generating/FinishGen.cpp index e10cb00f8..260253d62 100644 --- a/src/Generating/FinishGen.cpp +++ b/src/Generating/FinishGen.cpp @@ -244,6 +244,100 @@ void cFinishGenTallGrass::GenFinish(cChunkDesc & a_ChunkDesc) //////////////////////////////////////////////////////////////////////////////// +// cFinishGenVines + +bool cFinishGenVines::IsJungleVariant(EMCSBiome a_Biome) +{ + switch (a_Biome) + { + case biJungle: + case biJungleEdge: + case biJungleEdgeM: + case biJungleHills: + case biJungleM: + { + return true; + } + } + + return false; +} + + + + + +void cFinishGenVines::GenFinish(cChunkDesc & a_ChunkDesc) +{ + for (int x = 0; x < cChunkDef::Width; x++) + { + int xx = x + a_ChunkDesc.GetChunkX() * cChunkDef::Width; + for (int z = 0; z < cChunkDef::Width; z++) + { + int zz = z + a_ChunkDesc.GetChunkZ() * cChunkDef::Width; + if (!IsJungleVariant(a_ChunkDesc.GetBiome(x, z))) + { + // Current biome isn't a jungle + continue; + } + + if ((m_Noise.IntNoise2DInt(xx, zz) % 101) < 50) + { + continue; + } + + int Height = a_ChunkDesc.GetHeight(x, z); + for (int y = Height; y > m_Level; y--) + { + if (a_ChunkDesc.GetBlockType(x, y, z) != E_BLOCK_AIR) + { + // Can't place vines in non-air blocks + continue; + } + + if ((m_Noise.IntNoise3DInt(xx, y, zz) % 101) < 50) + { + continue; + } + + std::vector<NIBBLETYPE> Places; + if ((x + 1 < cChunkDef::Width) && cBlockInfo::FullyOccupiesVoxel(a_ChunkDesc.GetBlockType(x + 1, y, z))) + { + Places.push_back(8); + } + + if ((x - 1 > 0) && cBlockInfo::FullyOccupiesVoxel(a_ChunkDesc.GetBlockType(x - 1, y, z))) + { + Places.push_back(2); + } + + if ((z + 1 < cChunkDef::Width) && cBlockInfo::FullyOccupiesVoxel(a_ChunkDesc.GetBlockType(x, y, z + 1))) + { + Places.push_back(1); + } + + if ((z - 1 > 0) && cBlockInfo::FullyOccupiesVoxel(a_ChunkDesc.GetBlockType(x, y, z - 1))) + { + Places.push_back(4); + } + + if (Places.size() == 0) + { + continue; + } + + NIBBLETYPE Meta = Places[m_Noise.IntNoise3DInt(xx, y, zz) % Places.size()]; + a_ChunkDesc.SetBlockTypeMeta(x, y, z, E_BLOCK_VINES, Meta); + } + } + } +} + + + + + +//////////////////////////////////////////////////////////////////////////////// // cFinishGenSprinkleFoliage: bool cFinishGenSprinkleFoliage::TryAddSugarcane(cChunkDesc & a_ChunkDesc, int a_RelX, int a_RelY, int a_RelZ) @@ -470,30 +564,22 @@ void cFinishGenSnow::GenFinish(cChunkDesc & a_ChunkDesc) { for (int x = 0; x < cChunkDef::Width; x++) { - switch (a_ChunkDesc.GetBiome(x, z)) + int Height = a_ChunkDesc.GetHeight(x, z); + if (GetSnowStartHeight(a_ChunkDesc.GetBiome(x, z)) > Height) { - case biIcePlains: - case biIceMountains: - case biTaiga: - case biTaigaHills: - case biFrozenRiver: - case biFrozenOcean: - { - int Height = a_ChunkDesc.GetHeight(x, z); - if (cBlockInfo::IsSnowable(a_ChunkDesc.GetBlockType(x, Height, z)) && (Height < cChunkDef::Height - 1)) - { - a_ChunkDesc.SetBlockType(x, Height + 1, z, E_BLOCK_SNOW); - a_ChunkDesc.SetHeight(x, z, Height + 1); - } - break; - } - default: - { - // There's no snow in the other biomes. - break; - } + // Height isn't high enough for snow to start forming. + continue; } - } + + if (!cBlockInfo::IsSnowable(a_ChunkDesc.GetBlockType(x, Height, z)) && (Height < cChunkDef::Height - 1)) + { + // The top block can't be snown over. + continue; + } + + a_ChunkDesc.SetBlockType(x, Height + 1, z, E_BLOCK_SNOW); + a_ChunkDesc.SetHeight(x, z, Height + 1); + } // for x } // for z } @@ -511,34 +597,27 @@ void cFinishGenIce::GenFinish(cChunkDesc & a_ChunkDesc) { for (int x = 0; x < cChunkDef::Width; x++) { - switch (a_ChunkDesc.GetBiome(x, z)) + int Height = a_ChunkDesc.GetHeight(x, z); + if (GetSnowStartHeight(a_ChunkDesc.GetBiome(x, z)) > Height) { - case biIcePlains: - case biIceMountains: - case biTaiga: - case biTaigaHills: - case biFrozenRiver: - case biFrozenOcean: - { - int Height = a_ChunkDesc.GetHeight(x, z); - switch (a_ChunkDesc.GetBlockType(x, Height, z)) - { - case E_BLOCK_WATER: - case E_BLOCK_STATIONARY_WATER: - { - a_ChunkDesc.SetBlockType(x, Height, z, E_BLOCK_ICE); - break; - } - } - break; - } - default: - { - // No icy water in other biomes. - break; - } + // Height isn't high enough for snow to start forming. + continue; } - } + + if (!IsBlockWater(a_ChunkDesc.GetBlockType(x, Height, z))) + { + // The block isn't a water block. + continue; + } + + if (a_ChunkDesc.GetBlockMeta(x, Height, z) != 0) + { + // The water block isn't a source block. + continue; + } + + a_ChunkDesc.SetBlockType(x, Height, z, E_BLOCK_ICE); + } // for x } // for z } diff --git a/src/Generating/FinishGen.h b/src/Generating/FinishGen.h index ae6dee590..70696c4f8 100644 --- a/src/Generating/FinishGen.h +++ b/src/Generating/FinishGen.h @@ -118,6 +118,29 @@ protected: +class cFinishGenVines : + public cFinishGen +{ +public: + cFinishGenVines(int a_Seed, int a_Level) : + m_Noise(a_Seed), + m_Level(a_Level) + { + } + + bool IsJungleVariant(EMCSBiome a_Biome); + +protected: + cNoise m_Noise; + int m_Level; + + virtual void GenFinish(cChunkDesc & a_ChunkDesc) override; +}; + + + + + class cFinishGenSoulsandRims : public cFinishGen { diff --git a/src/Generating/HeiGen.cpp b/src/Generating/HeiGen.cpp index 61d087c17..54567cb4d 100644 --- a/src/Generating/HeiGen.cpp +++ b/src/Generating/HeiGen.cpp @@ -10,6 +10,66 @@ #include "DistortedHeightmap.h" #include "EndGen.h" #include "Noise3DGenerator.h" +#include "ProtIntGen.h" + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cHeiGenSteppy: + +class cHeiGenSteppy: + public cTerrainHeightGen +{ +public: + cHeiGenSteppy(int a_Seed) : + m_Seed(a_Seed) + { + m_Gen = + std::make_shared<cProtIntGenWeightAvg<16, 1, 0>>( + std::make_shared<cProtIntGenSmooth> (a_Seed + 1, + std::make_shared<cProtIntGenZoom> (a_Seed + 2, + std::make_shared<cProtIntGenSmooth> (a_Seed + 3, + std::make_shared<cProtIntGenZoom> (a_Seed + 4, + std::make_shared<cProtIntGenAddRnd> (a_Seed + 5, 1, + std::make_shared<cProtIntGenSmooth> (a_Seed + 6, + std::make_shared<cProtIntGenZoom> (a_Seed + 7, + std::make_shared<cProtIntGenRndBetween> (a_Seed + 8, 60, + std::make_shared<cProtIntGenAddRnd> (a_Seed + 9, 1, + std::make_shared<cProtIntGenSmooth> (a_Seed + 1, + std::make_shared<cProtIntGenZoom> (a_Seed + 2, + std::make_shared<cProtIntGenRndBetween> (a_Seed + 3, 60, + std::make_shared<cProtIntGenSmooth> (a_Seed + 4, + std::make_shared<cProtIntGenZoom> (a_Seed + 5, + std::make_shared<cProtIntGenRndBetween> (a_Seed + 6, 60, + std::make_shared<cProtIntGenRndChoice> (a_Seed + 7, 10, 50, 50, + std::make_shared<cProtIntGenSmooth> (a_Seed + 8, + std::make_shared<cProtIntGenZoom> (a_Seed + 9, + std::make_shared<cProtIntGenRndChoice> (a_Seed + 1, 10, 50, 50, + std::make_shared<cProtIntGenAddRnd> (a_Seed + 2, 2, + std::make_shared<cProtIntGenZoom> (a_Seed + 3, + std::make_shared<cProtIntGenZoom> (a_Seed + 4, + std::make_shared<cProtIntGenChoice> (a_Seed + 5, 10) + ))))))))))))))))))))))); + } + + // cTerrainHeightGen overrides: + virtual void GenHeightMap(int a_ChunkX, int a_ChunkZ, cChunkDef::HeightMap & a_HeightMap) override + { + int heights[cChunkDef::Width * cChunkDef::Width]; + m_Gen->GetInts(a_ChunkX * cChunkDef::Width, a_ChunkZ * cChunkDef::Width, cChunkDef::Width, cChunkDef::Width, heights); + for (size_t i = 0; i < ARRAYCOUNT(heights); i++) + { + a_HeightMap[i] = static_cast<HEIGHTTYPE>(std::max(std::min(60 + heights[i], cChunkDef::Height - 60), 40)); + } + } + +protected: + int m_Seed; + + SharedPtr<cProtIntGen> m_Gen; +}; @@ -821,6 +881,10 @@ cTerrainHeightGenPtr cTerrainHeightGen::CreateHeightGen(cIniFile & a_IniFile, cB // Return an empty pointer, the caller will create the proper generator: return cTerrainHeightGenPtr(); } + else if (NoCaseCompare(HeightGenName, "Steppy") == 0) + { + res = std::make_shared<cHeiGenSteppy>(a_Seed); + } else if (NoCaseCompare(HeightGenName, "Noise3D") == 0) { // Not a heightmap-based generator, but it used to be accessible via HeightGen, so we need to skip making the default out of it diff --git a/src/Generating/ProtIntGen.h b/src/Generating/ProtIntGen.h index 73ed27096..e709222fe 100644 --- a/src/Generating/ProtIntGen.h +++ b/src/Generating/ProtIntGen.h @@ -318,6 +318,350 @@ protected: +/** Averages the values of the underlying 2 * 2 neighbors. */ +class cProtIntGenAvgValues : + public cProtIntGen +{ + typedef cProtIntGen super; + +public: + cProtIntGenAvgValues(Underlying a_Underlying) : + m_Underlying(a_Underlying) + { + } + + + virtual void GetInts(int a_MinX, int a_MinZ, int a_SizeX, int a_SizeZ, int * a_Values) override + { + // Generate the underlying values: + int lowerSizeX = a_SizeX + 1; + int lowerSizeZ = a_SizeZ + 1; + ASSERT(lowerSizeX * lowerSizeZ <= m_BufferSize); + int lowerData[m_BufferSize]; + m_Underlying->GetInts(a_MinX, a_MinZ, lowerSizeX, lowerSizeZ, lowerData); + + // Average - add all 4 "neighbors" and divide by 4: + for (int z = 0; z < a_SizeZ; z++) + { + for (int x = 0; x < a_SizeX; x++) + { + int idxLower = x + lowerSizeX * z; + a_Values[x + a_SizeX * z] = ( + lowerData[idxLower] + lowerData[idxLower + 1] + + lowerData[idxLower + lowerSizeX] + lowerData[idxLower + lowerSizeX + 1] + ) / 4; + } + } + } + +protected: + Underlying m_Underlying; +}; + + + + + +/** Averages the values of the underlying 4 * 4 neighbors. */ +class cProtIntGenAvg4Values : + public cProtIntGen +{ + typedef cProtIntGen super; + +public: + cProtIntGenAvg4Values(Underlying a_Underlying) : + m_Underlying(a_Underlying) + { + } + + + virtual void GetInts(int a_MinX, int a_MinZ, int a_SizeX, int a_SizeZ, int * a_Values) override + { + // Generate the underlying values: + int lowerSizeX = a_SizeX + 4; + int lowerSizeZ = a_SizeZ + 4; + ASSERT(lowerSizeX * lowerSizeZ <= m_BufferSize); + int lowerData[m_BufferSize]; + m_Underlying->GetInts(a_MinX - 1, a_MinZ - 1, lowerSizeX, lowerSizeZ, lowerData); + + // Calculate the weighted average of all 16 "neighbors": + for (int z = 0; z < a_SizeZ; z++) + { + for (int x = 0; x < a_SizeX; x++) + { + int idxLower1 = x + lowerSizeX * z; + int idxLower2 = idxLower1 + lowerSizeX; + int idxLower3 = idxLower1 + 2 * lowerSizeX; + int idxLower4 = idxLower1 + 3 * lowerSizeX; + a_Values[x + a_SizeX * z] = ( + 1 * lowerData[idxLower1] + 2 * lowerData[idxLower1 + 1] + 2 * lowerData[idxLower1 + 2] + 1 * lowerData[idxLower1 + 3] + + 2 * lowerData[idxLower2] + 32 * lowerData[idxLower2 + 1] + 32 * lowerData[idxLower2 + 2] + 2 * lowerData[idxLower2 + 3] + + 2 * lowerData[idxLower3] + 32 * lowerData[idxLower3 + 1] + 32 * lowerData[idxLower3 + 2] + 2 * lowerData[idxLower3 + 3] + + 1 * lowerData[idxLower4] + 2 * lowerData[idxLower4 + 1] + 2 * lowerData[idxLower4 + 2] + 1 * lowerData[idxLower4 + 3] + ) / 148; + } + } + } + +protected: + Underlying m_Underlying; +}; + + + + + +/** Averages the values of the underlying 3 * 3 neighbors with custom weight. */ +template <int WeightCenter, int WeightCardinal, int WeightDiagonal> +class cProtIntGenWeightAvg : + public cProtIntGen +{ + typedef cProtIntGen super; + +public: + cProtIntGenWeightAvg(Underlying a_Underlying) : + m_Underlying(a_Underlying) + { + } + + + virtual void GetInts(int a_MinX, int a_MinZ, int a_SizeX, int a_SizeZ, int * a_Values) override + { + // Generate the underlying values: + int lowerSizeX = a_SizeX + 3; + int lowerSizeZ = a_SizeZ + 3; + ASSERT(lowerSizeX * lowerSizeZ <= m_BufferSize); + int lowerData[m_BufferSize]; + m_Underlying->GetInts(a_MinX, a_MinZ, lowerSizeX, lowerSizeZ, lowerData); + + // Calculate the weighted average the neighbors: + for (int z = 0; z < a_SizeZ; z++) + { + for (int x = 0; x < a_SizeX; x++) + { + int idxLower1 = x + lowerSizeX * z; + int idxLower2 = idxLower1 + lowerSizeX; + int idxLower3 = idxLower1 + 2 * lowerSizeX; + a_Values[x + a_SizeX * z] = ( + WeightDiagonal * lowerData[idxLower1] + WeightCardinal * lowerData[idxLower1 + 1] + WeightDiagonal * lowerData[idxLower1 + 2] + + WeightCardinal * lowerData[idxLower2] + WeightCenter * lowerData[idxLower2 + 1] + WeightCardinal * lowerData[idxLower2 + 2] + + WeightDiagonal * lowerData[idxLower3] + WeightCardinal * lowerData[idxLower3 + 1] + WeightDiagonal * lowerData[idxLower3 + 2] + ) / (4 * WeightDiagonal + 4 * WeightCardinal + WeightCenter); + } + } + } + +protected: + Underlying m_Underlying; +}; + + + + + +/** Replaces random values of the underlying data with random integers in the specified range [Min .. Min + Range). */ +class cProtIntGenRndChoice : + public cProtIntGenWithNoise +{ + typedef cProtIntGenWithNoise super; + +public: + cProtIntGenRndChoice(int a_Seed, int a_ChancePct, int a_Min, int a_Range, Underlying a_Underlying) : + super(a_Seed), + m_ChancePct(a_ChancePct), + m_Min(a_Min), + m_Range(a_Range), + m_Underlying(a_Underlying) + { + } + + + virtual void GetInts(int a_MinX, int a_MinZ, int a_SizeX, int a_SizeZ, int * a_Values) override + { + // Generate the underlying values: + m_Underlying->GetInts(a_MinX, a_MinZ, a_SizeX, a_SizeZ, a_Values); + + // Replace random values: + for (int z = 0; z < a_SizeZ; z++) + { + int BaseZ = a_MinZ + z; + for (int x = 0; x < a_SizeX; x++) + { + if (((super::m_Noise.IntNoise2DInt(BaseZ, a_MinX + x) / 13) % 101) < m_ChancePct) + { + a_Values[x + a_SizeX * z] = m_Min + (super::m_Noise.IntNoise2DInt(a_MinX + x, BaseZ) / 7) % m_Range; + } + } // for x + } // for z + } + +protected: + int m_ChancePct; + int m_Min; + int m_Range; + Underlying m_Underlying; +}; + + + + + +/** Adds a random value in range [-a_HalfRange, +a_HalfRange] to each of the underlying values. */ +class cProtIntGenAddRnd : + public cProtIntGenWithNoise +{ + typedef cProtIntGenWithNoise super; + +public: + cProtIntGenAddRnd(int a_Seed, int a_HalfRange, Underlying a_Underlying) : + super(a_Seed), + m_Range(a_HalfRange * 2 + 1), + m_HalfRange(a_HalfRange), + m_Underlying(a_Underlying) + { + } + + + virtual void GetInts(int a_MinX, int a_MinZ, int a_SizeX, int a_SizeZ, int * a_Values) override + { + // Generate the underlying values: + m_Underlying->GetInts(a_MinX, a_MinZ, a_SizeX, a_SizeZ, a_Values); + + // Add the random values: + for (int z = 0; z < a_SizeZ; z++) + { + int NoiseZ = a_MinZ + z; + for (int x = 0; x < a_SizeX; x++) + { + int noiseVal = ((super::m_Noise.IntNoise2DInt(a_MinX + x, NoiseZ) / 7) % m_Range) - m_HalfRange; + a_Values[x + z * a_SizeX] += noiseVal; + } + } + } + +protected: + int m_Range; + int m_HalfRange; + Underlying m_Underlying; +}; + + + + + +/** Replaces random underlying values with the average of the neighbors. */ +class cProtIntGenRndAvg : + public cProtIntGenWithNoise +{ + typedef cProtIntGenWithNoise super; + +public: + cProtIntGenRndAvg(int a_Seed, int a_AvgChancePct, Underlying a_Underlying) : + super(a_Seed), + m_AvgChancePct(a_AvgChancePct), + m_Underlying(a_Underlying) + { + } + + + virtual void GetInts(int a_MinX, int a_MinZ, int a_SizeX, int a_SizeZ, int * a_Values) override + { + // Generate the underlying values: + int lowerSizeX = a_SizeX + 2; + int lowerSizeZ = a_SizeZ + 2; + ASSERT(lowerSizeX * lowerSizeZ <= m_BufferSize); + int lowerData[m_BufferSize]; + m_Underlying->GetInts(a_MinX - 1, a_MinZ - 1, lowerSizeX, lowerSizeZ, lowerData); + + // Average random values: + for (int z = 0; z < a_SizeZ; z++) + { + int NoiseZ = a_MinZ + z; + for (int x = 0; x < a_SizeX; x++) + { + int idxLower = x + 1 + lowerSizeX * (z + 1); + if (((super::m_Noise.IntNoise2DInt(a_MinX + x, NoiseZ) / 7) % 100) > m_AvgChancePct) + { + // Average the 4 neighbors: + a_Values[x + z * a_SizeX] = ( + lowerData[idxLower - 1] + lowerData[idxLower + 1] + + lowerData[idxLower - lowerSizeX] + lowerData[idxLower + lowerSizeX] + ) / 4; + } + else + { + // Keep the underlying value: + a_Values[x + z * a_SizeX] = lowerData[idxLower]; + } + } + } + } + +protected: + int m_AvgChancePct; + Underlying m_Underlying; +}; + + + + + +/** Replaces random underlying values with a random value in between the max and min of the neighbors. */ +class cProtIntGenRndBetween : + public cProtIntGenWithNoise +{ + typedef cProtIntGenWithNoise super; + +public: + cProtIntGenRndBetween(int a_Seed, int a_AvgChancePct, Underlying a_Underlying) : + super(a_Seed), + m_AvgChancePct(a_AvgChancePct), + m_Underlying(a_Underlying) + { + } + + + virtual void GetInts(int a_MinX, int a_MinZ, int a_SizeX, int a_SizeZ, int * a_Values) override + { + // Generate the underlying values: + int lowerSizeX = a_SizeX + 2; + int lowerSizeZ = a_SizeZ + 2; + ASSERT(lowerSizeX * lowerSizeZ <= m_BufferSize); + int lowerData[m_BufferSize]; + m_Underlying->GetInts(a_MinX - 1, a_MinZ - 1, lowerSizeX, lowerSizeZ, lowerData); + + // Average random values: + for (int z = 0; z < a_SizeZ; z++) + { + int NoiseZ = a_MinZ + z; + for (int x = 0; x < a_SizeX; x++) + { + int idxLower = x + 1 + lowerSizeX * (z + 1); + if (((super::m_Noise.IntNoise2DInt(a_MinX + x, NoiseZ) / 7) % 100) > m_AvgChancePct) + { + // Chose a value in between the min and max neighbor: + int min = std::min(std::min(lowerData[idxLower - 1], lowerData[idxLower + 1]), std::min(lowerData[idxLower - lowerSizeX], lowerData[idxLower + lowerSizeX])); + int max = std::max(std::max(lowerData[idxLower - 1], lowerData[idxLower + 1]), std::max(lowerData[idxLower - lowerSizeX], lowerData[idxLower + lowerSizeX])); + a_Values[x + z * a_SizeX] = min + ((super::m_Noise.IntNoise2DInt(a_MinX + x, NoiseZ + 10) / 7) % (max - min + 1)); + } + else + { + // Keep the underlying value: + a_Values[x + z * a_SizeX] = lowerData[idxLower]; + } + } + } + } + +protected: + int m_AvgChancePct; + Underlying m_Underlying; +}; + + + + + /** Converts land biomes at the edge of an ocean into the respective beach biome. */ class cProtIntGenBeaches : public cProtIntGen diff --git a/src/Generating/Trees.cpp b/src/Generating/Trees.cpp index a10e0f4f1..9e72a688f 100644 --- a/src/Generating/Trees.cpp +++ b/src/Generating/Trees.cpp @@ -224,9 +224,6 @@ void GetTreeImageByBiome(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_No case biMegaTaiga: case biMegaTaigaHills: case biExtremeHillsPlus: - case biMesa: - case biMesaPlateauF: - case biMesaPlateau: case biSunflowerPlains: case biDesertM: case biExtremeHillsM: @@ -239,9 +236,6 @@ void GetTreeImageByBiome(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_No case biMegaSpruceTaiga: case biMegaSpruceTaigaHills: case biExtremeHillsPlusM: - case biMesaBryce: - case biMesaPlateauFM: - case biMesaPlateauM: { // TODO: These need their special trees GetBirchTreeImage(a_BlockX, a_BlockY, a_BlockZ, a_Noise, a_Seq, a_LogBlocks, a_OtherBlocks); @@ -264,6 +258,16 @@ void GetTreeImageByBiome(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_No return; } + case biMesa: + case biMesaPlateauF: + case biMesaPlateau: + case biMesaBryce: + case biMesaPlateauFM: + case biMesaPlateauM: + { + GetSmallAppleTreeImage(a_BlockX, a_BlockY, a_BlockZ, a_Noise, a_Seq, a_LogBlocks, a_OtherBlocks); + } + case biDesert: case biDesertHills: case biRiver: diff --git a/src/Globals.h b/src/Globals.h index 29eaac871..7c2ab38d8 100644 --- a/src/Globals.h +++ b/src/Globals.h @@ -379,9 +379,10 @@ void inline LOG(const char * a_Format, ...) #define assert_test(x) ( !!(x) || (assert(!#x), exit(1), 0)) #endif -// Unified shared ptr from before C++11. Also no silly undercores. +// Unified ptr types from before C++11. Also no silly undercores. #define SharedPtr std::shared_ptr #define WeakPtr std::weak_ptr +#define UniquePtr std::unique_ptr diff --git a/src/HTTPServer/SslHTTPConnection.cpp b/src/HTTPServer/SslHTTPConnection.cpp index f09daac8f..f8dea0731 100644 --- a/src/HTTPServer/SslHTTPConnection.cpp +++ b/src/HTTPServer/SslHTTPConnection.cpp @@ -25,6 +25,15 @@ cSslHTTPConnection::cSslHTTPConnection(cHTTPServer & a_HTTPServer, const cX509Ce +cSslHTTPConnection::~cSslHTTPConnection() +{ + m_Ssl.NotifyClose(); +} + + + + + void cSslHTTPConnection::OnReceivedData(const char * a_Data, size_t a_Size) { // Process the received data: diff --git a/src/HTTPServer/SslHTTPConnection.h b/src/HTTPServer/SslHTTPConnection.h index dc54b1eff..c461a3a24 100644 --- a/src/HTTPServer/SslHTTPConnection.h +++ b/src/HTTPServer/SslHTTPConnection.h @@ -25,6 +25,8 @@ 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); + + ~cSslHTTPConnection(); protected: cBufferedSslContext m_Ssl; diff --git a/src/Items/ItemDoor.h b/src/Items/ItemDoor.h index dacf286e5..71143d5a8 100644 --- a/src/Items/ItemDoor.h +++ b/src/Items/ItemDoor.h @@ -40,7 +40,7 @@ public: } // The door needs a compatible block below it: - if ((a_BlockY > 0) && !cBlockDoorHandler::CanBeOn(a_World.GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ))) + if (!cBlockDoorHandler::CanBeOn(a_World.GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ))) { return false; } @@ -62,10 +62,10 @@ public: return false; } } - + // Check the two blocks that will get replaced by the door: - BLOCKTYPE LowerBlockType = a_World.GetBlock(a_BlockX, a_BlockY + 1, a_BlockZ); - BLOCKTYPE UpperBlockType = a_World.GetBlock(a_BlockX, a_BlockY + 2, a_BlockZ); + BLOCKTYPE LowerBlockType = a_World.GetBlock(a_BlockX, a_BlockY, a_BlockZ); + BLOCKTYPE UpperBlockType = a_World.GetBlock(a_BlockX, a_BlockY + 1, a_BlockZ); if ( !cBlockDoorHandler::CanReplaceBlock(LowerBlockType) || !cBlockDoorHandler::CanReplaceBlock(UpperBlockType)) @@ -77,19 +77,32 @@ public: NIBBLETYPE LowerBlockMeta = cBlockDoorHandler::PlayerYawToMetaData(a_Player.GetYaw()); Vector3i RelDirToOutside = cBlockDoorHandler::GetRelativeDirectionToOutside(LowerBlockMeta); Vector3i LeftNeighborPos = RelDirToOutside; - LeftNeighborPos.TurnCCW(); + LeftNeighborPos.TurnCW(); LeftNeighborPos.Move(a_BlockX, a_BlockY, a_BlockZ); Vector3i RightNeighborPos = RelDirToOutside; - RightNeighborPos.TurnCW(); + RightNeighborPos.TurnCCW(); RightNeighborPos.Move(a_BlockX, a_BlockY, a_BlockZ); // Decide whether the hinge is on the left (default) or on the right: NIBBLETYPE UpperBlockMeta = 0x08; + BLOCKTYPE LeftNeighborBlock = a_World.GetBlock(LeftNeighborPos); + BLOCKTYPE RightNeighborBlock = a_World.GetBlock(RightNeighborPos); + /* + // DEBUG: + LOGD("Door being placed at {%d, %d, %d}", a_BlockX, a_BlockY, a_BlockZ); + LOGD("RelDirToOutside: {%d, %d, %d}", RelDirToOutside.x, RelDirToOutside.y, RelDirToOutside.z); + LOGD("Left neighbor at {%d, %d, %d}: %d (%s)", LeftNeighborPos.x, LeftNeighborPos.y, LeftNeighborPos.z, LeftNeighborBlock, ItemTypeToString(LeftNeighborBlock).c_str()); + LOGD("Right neighbor at {%d, %d, %d}: %d (%s)", RightNeighborPos.x, RightNeighborPos.y, RightNeighborPos.z, RightNeighborBlock, ItemTypeToString(RightNeighborBlock).c_str()); + */ if ( - cBlockDoorHandler::IsDoorBlockType(a_World.GetBlock(LeftNeighborPos)) || // The block to the left is a door block - cBlockInfo::IsSolid(a_World.GetBlock(RightNeighborPos)) // The block to the right is solid + cBlockDoorHandler::IsDoorBlockType(LeftNeighborBlock) || // The block to the left is a door block + ( + cBlockInfo::IsSolid(RightNeighborBlock) && // The block to the right is solid... + !cBlockDoorHandler::IsDoorBlockType(RightNeighborBlock) // ... but not a door + ) ) { + // DEBUG: LOGD("Setting hinge to right side"); UpperBlockMeta = 0x09; // Upper block | hinge on right } @@ -106,7 +119,3 @@ public: return true; } } ; - - - - diff --git a/src/MobSpawner.cpp b/src/MobSpawner.cpp index 0a32d17ef..541135996 100644 --- a/src/MobSpawner.cpp +++ b/src/MobSpawner.cpp @@ -110,7 +110,8 @@ eMonsterType cMobSpawner::ChooseMobType(EMCSBiome a_Biome) if (allowedMobsSize > 0) { std::set<eMonsterType>::iterator itr = allowedMobs.begin(); - int iRandom = m_Random.NextInt((int)allowedMobsSize, a_Biome); + static int Counter = 0; + int iRandom = m_Random.NextInt((int)allowedMobsSize, Counter++); for (int i = 0; i < iRandom; i++) { diff --git a/src/Mobs/AggressiveMonster.cpp b/src/Mobs/AggressiveMonster.cpp index 72317d66b..526b39e39 100644 --- a/src/Mobs/AggressiveMonster.cpp +++ b/src/Mobs/AggressiveMonster.cpp @@ -85,7 +85,7 @@ void cAggressiveMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) if (ReachedFinalDestination() && !LineOfSight.Trace(GetPosition(), AttackDirection, (int)AttackDirection.Length())) { // Attack if reached destination, target isn't null, and have a clear line of sight to target (so won't attack through walls) - Attack(a_Dt / 1000); + Attack(a_Dt); } } @@ -95,8 +95,7 @@ void cAggressiveMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) void cAggressiveMonster::Attack(std::chrono::milliseconds a_Dt) { - m_AttackInterval += a_Dt.count() * m_AttackRate; - + m_AttackInterval += (static_cast<float>(a_Dt.count()) / 1000) * m_AttackRate; if ((m_Target == nullptr) || (m_AttackInterval < 3.0)) { return; diff --git a/src/Mobs/Blaze.cpp b/src/Mobs/Blaze.cpp index 172ccd071..89eeb3709 100644 --- a/src/Mobs/Blaze.cpp +++ b/src/Mobs/Blaze.cpp @@ -32,7 +32,7 @@ void cBlaze::GetDrops(cItems & a_Drops, cEntity * a_Killer) void cBlaze::Attack(std::chrono::milliseconds a_Dt) { - m_AttackInterval += a_Dt.count() * m_AttackRate; + m_AttackInterval += (static_cast<float>(a_Dt.count()) / 1000) * m_AttackRate; if ((m_Target != nullptr) && (m_AttackInterval > 3.0)) { diff --git a/src/Mobs/Ghast.cpp b/src/Mobs/Ghast.cpp index ea0295102..d17047ab7 100644 --- a/src/Mobs/Ghast.cpp +++ b/src/Mobs/Ghast.cpp @@ -34,7 +34,7 @@ void cGhast::GetDrops(cItems & a_Drops, cEntity * a_Killer) void cGhast::Attack(std::chrono::milliseconds a_Dt) { - m_AttackInterval += a_Dt.count() * m_AttackRate; + m_AttackInterval += (static_cast<float>(a_Dt.count()) / 1000) * m_AttackRate; if ((m_Target != nullptr) && (m_AttackInterval > 3.0)) { diff --git a/src/Mobs/Skeleton.cpp b/src/Mobs/Skeleton.cpp index dd59d6454..331c8e8ad 100644 --- a/src/Mobs/Skeleton.cpp +++ b/src/Mobs/Skeleton.cpp @@ -69,8 +69,7 @@ void cSkeleton::MoveToPosition(const Vector3d & a_Position) void cSkeleton::Attack(std::chrono::milliseconds a_Dt) { - m_AttackInterval += a_Dt.count() * m_AttackRate; - + m_AttackInterval += (static_cast<float>(a_Dt.count()) / 1000) * m_AttackRate; if ((m_Target != nullptr) && (m_AttackInterval > 3.0)) { // Setting this higher gives us more wiggle room for attackrate diff --git a/src/OSSupport/CMakeLists.txt b/src/OSSupport/CMakeLists.txt index 6f609c519..0d3c9a63e 100644 --- a/src/OSSupport/CMakeLists.txt +++ b/src/OSSupport/CMakeLists.txt @@ -13,11 +13,13 @@ SET (SRCS HostnameLookup.cpp IPLookup.cpp IsThread.cpp + NetworkInterfaceEnum.cpp NetworkSingleton.cpp Semaphore.cpp ServerHandleImpl.cpp StackTrace.cpp TCPLinkImpl.cpp + UDPEndpointImpl.cpp ) SET (HDRS @@ -36,6 +38,7 @@ SET (HDRS ServerHandleImpl.h StackTrace.h TCPLinkImpl.h + UDPEndpointImpl.h ) if(NOT MSVC) diff --git a/src/OSSupport/HostnameLookup.cpp b/src/OSSupport/HostnameLookup.cpp index 3a2997ffd..0944153be 100644 --- a/src/OSSupport/HostnameLookup.cpp +++ b/src/OSSupport/HostnameLookup.cpp @@ -69,12 +69,24 @@ void cHostnameLookup::Callback(int a_ErrCode, evutil_addrinfo * a_Addr, void * a case AF_INET: // IPv4 { sockaddr_in * sin = reinterpret_cast<sockaddr_in *>(a_Addr->ai_addr); + if (!Self->m_Callbacks->OnNameResolvedV4(Self->m_Hostname, sin)) + { + // Callback indicated that the IP shouldn't be serialized to a string, just continue with the next address: + HasResolved = true; + continue; + } evutil_inet_ntop(AF_INET, &(sin->sin_addr), IP, sizeof(IP)); break; } case AF_INET6: // IPv6 { sockaddr_in6 * sin = reinterpret_cast<sockaddr_in6 *>(a_Addr->ai_addr); + if (!Self->m_Callbacks->OnNameResolvedV6(Self->m_Hostname, sin)) + { + // Callback indicated that the IP shouldn't be serialized to a string, just continue with the next address: + HasResolved = true; + continue; + } evutil_inet_ntop(AF_INET6, &(sin->sin6_addr), IP, sizeof(IP)); break; } diff --git a/src/OSSupport/Network.h b/src/OSSupport/Network.h index e883dfb29..95a935bbe 100644 --- a/src/OSSupport/Network.h +++ b/src/OSSupport/Network.h @@ -130,6 +130,64 @@ public: +/** Interface that provides methods available on UDP communication endpoints. */ +class cUDPEndpoint +{ +public: + /** Interface for the callbacks for events that can happen on the endpoint. */ + class cCallbacks + { + public: + // Force a virtual destructor in all descendants: + virtual ~cCallbacks() {} + + /** Called when an error occurs on the endpoint. */ + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) = 0; + + /** Called when there is an incoming datagram from a remote host. */ + virtual void OnReceivedData(const char * a_Data, size_t a_Size, const AString & a_RemoteHost, UInt16 a_RemotePort) = 0; + }; + + + // Force a virtual destructor for all descendants: + virtual ~cUDPEndpoint() {} + + /** Closes the underlying socket. + Note that there still might be callbacks in-flight after this method returns. */ + virtual void Close(void) = 0; + + /** Returns true if the endpoint is open. */ + virtual bool IsOpen(void) const = 0; + + /** Returns the local port to which the underlying socket is bound. */ + virtual UInt16 GetPort(void) const = 0; + + /** Sends the specified payload in a single UDP datagram to the specified host+port combination. + Note that in order to send to a broadcast address, you need to call EnableBroadcasts() first. */ + virtual bool Send(const AString & a_Payload, const AString & a_Host, UInt16 a_Port) = 0; + + /** Marks the socket as capable of sending broadcast, using whatever OS API is needed. + Without this call, sending to a broadcast address using Send() may fail. */ + virtual void EnableBroadcasts(void) = 0; + +protected: + /** The callbacks used for various events on the endpoint. */ + cCallbacks & m_Callbacks; + + + /** Creates a new instance of an endpoint, with the specified callbacks. */ + cUDPEndpoint(cCallbacks & a_Callbacks): + m_Callbacks(a_Callbacks) + { + } +}; + +typedef SharedPtr<cUDPEndpoint> cUDPEndpointPtr; + + + + + class cNetwork { public: @@ -183,9 +241,22 @@ public: /** Called when the hostname is successfully resolved into an IP address. May be called multiple times if a name resolves to multiple addresses. - a_IP may be either an IPv4 or an IPv6 address with their proper formatting. */ + a_IP may be either an IPv4 or an IPv6 address with their proper formatting. + Each call to OnNameResolved() is preceded by a call to either OnNameResolvedV4() or OnNameResolvedV6(). */ virtual void OnNameResolved(const AString & a_Name, const AString & a_IP) = 0; + /** Called when the hostname is successfully resolved into an IPv4 address. + May be called multiple times if a name resolves to multiple addresses. + Each call to OnNameResolvedV4 is followed by OnNameResolved with the IP address serialized to a string. + If this callback returns false, the OnNameResolved() call is skipped for this address. */ + virtual bool OnNameResolvedV4(const AString & a_Name, const sockaddr_in * a_IP) { return true; } + + /** Called when the hostname is successfully resolved into an IPv6 address. + May be called multiple times if a name resolves to multiple addresses. + Each call to OnNameResolvedV4 is followed by OnNameResolved with the IP address serialized to a string. + If this callback returns false, the OnNameResolved() call is skipped for this address. */ + virtual bool OnNameResolvedV6(const AString & a_Name, const sockaddr_in6 * a_IP) { return true; } + /** Called when an error is encountered while resolving. If an error is reported, the OnFinished() callback is not called. */ virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) = 0; @@ -242,6 +313,14 @@ public: const AString & a_IP, cResolveNameCallbacksPtr a_Callbacks ); + + /** Opens up an UDP endpoint for sending and receiving UDP datagrams on the specified port. + If a_Port is 0, the OS is free to assign any port number it likes to the endpoint. + Returns the endpoint object that can be interacted with. */ + static cUDPEndpointPtr CreateUDPEndpoint(UInt16 a_Port, cUDPEndpoint::cCallbacks & a_Callbacks); + + /** Returns all local IP addresses for network interfaces currently available. */ + static AStringVector EnumLocalIPAddresses(void); }; diff --git a/src/OSSupport/NetworkInterfaceEnum.cpp b/src/OSSupport/NetworkInterfaceEnum.cpp new file mode 100644 index 000000000..c4af1e93c --- /dev/null +++ b/src/OSSupport/NetworkInterfaceEnum.cpp @@ -0,0 +1,185 @@ + +// NetworkInterfaceEnum.cpp + +// Implements the cNetwork::EnumLocalIPAddresses() interface enumeration function + +#include "Globals.h" +#include "Network.h" +#include "event2/util.h" +#ifdef _WIN32 + #include <IPHlpApi.h> + #pragma comment(lib, "IPHLPAPI.lib") +#else // _WIN32 + #include <sys/types.h> + #include <ifaddrs.h> + #include <netinet/in.h> + #include <arpa/inet.h> +#endif // else _WIN32 + + + + + +#ifdef SELF_TEST + +static class cEnumIPAddressTest +{ +public: + cEnumIPAddressTest(void) + { + printf("Enumerating all IP addresses...\n"); + auto IPs = cNetwork::EnumLocalIPAddresses(); + for (auto & ip: IPs) + { + printf(" %s\n", ip.c_str()); + } + printf("Done.\n"); + } +} g_EnumIPAddressTest; + +#endif // SELF_TEST + + + + + +#ifdef _WIN32 + +/** Converts the SOCKET_ADDRESS structure received from the OS into an IP address string. */ +static AString PrintAddress(SOCKET_ADDRESS & a_Addr) +{ + char IP[128]; + switch (a_Addr.lpSockaddr->sa_family) + { + case AF_INET: + { + auto sin = reinterpret_cast<const sockaddr_in *>(a_Addr.lpSockaddr); + evutil_inet_ntop(a_Addr.lpSockaddr->sa_family, &(sin->sin_addr), IP, sizeof(IP)); + break; + } + case AF_INET6: + { + auto sin = reinterpret_cast<const sockaddr_in6 *>(a_Addr.lpSockaddr); + evutil_inet_ntop(a_Addr.lpSockaddr->sa_family, &(sin->sin6_addr), IP, sizeof(IP)); + break; + } + default: + { + IP[0] = 0; + break; + } + } + return IP; +} + +#else // _WIN32 + +static AString PrintAddress(ifaddrs * InterfaceAddress) +{ + switch (InterfaceAddress->ifa_addr->sa_family) + { + case AF_INET: + { // IPv4 + char AddressBuffer[INET_ADDRSTRLEN]; + sockaddr_in InternetSocket; + + std::memcpy(&InternetSocket, InterfaceAddress->ifa_addr, sizeof(InternetSocket)); + inet_ntop(AF_INET, &InternetSocket.sin_addr, AddressBuffer, INET_ADDRSTRLEN); + return AddressBuffer; + } + case AF_INET6: + { // IPv6 + char AddressBuffer[INET6_ADDRSTRLEN]; + sockaddr_in6 InternetSocket; + + std::memcpy(&InternetSocket, InterfaceAddress->ifa_addr, sizeof(InternetSocket)); + inet_ntop(AF_INET6, &InternetSocket.sin6_addr, AddressBuffer, INET6_ADDRSTRLEN); + return AddressBuffer; + } + default: + { + LOG("Unknown address family: %i", InterfaceAddress->ifa_addr->sa_family); + return ""; + } + } +} + +#endif // else _WIN32 + + + + + +AStringVector cNetwork::EnumLocalIPAddresses(void) +{ + AStringVector res; + + #ifdef _WIN32 + + // Query the OS for all adapters' addresses: + char buffer[64 KiB]; // A buffer backing the address list + PIP_ADAPTER_ADDRESSES pAddresses = reinterpret_cast<PIP_ADAPTER_ADDRESSES>(&buffer); + ULONG outBufLen = sizeof(buffer); + DWORD dwRetVal = GetAdaptersAddresses( + AF_UNSPEC, + GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_FRIENDLY_NAME, nullptr, + pAddresses, &outBufLen + ); + if (dwRetVal != ERROR_SUCCESS) + { + LOG("GetAdaptersAddresses() failed: %u", dwRetVal); + return res; + } + + // Enumerate all active adapters + for (auto pCurrAddresses = pAddresses; pCurrAddresses != nullptr; pCurrAddresses = pCurrAddresses->Next) + { + if (pCurrAddresses->OperStatus != IfOperStatusUp) + { + // Adapter not active, skip it: + continue; + } + + // Collect all IP addresses on this adapter: + for (auto pUnicast = pCurrAddresses->FirstUnicastAddress; pUnicast != nullptr; pUnicast = pUnicast->Next) + { + auto Address = PrintAddress(pUnicast->Address); + if (!Address.empty()) + { + res.push_back(Address); + } + } // for pUnicast + } // for pCurrAddresses + + #else // _WIN32 + + struct ifaddrs * ifAddrStruct = nullptr; + getifaddrs(&ifAddrStruct); + + for (auto ifa = ifAddrStruct; ifa != nullptr; ifa = ifa->ifa_next) + { + if (ifa->ifa_addr == nullptr) + { + continue; + } + + auto Address = PrintAddress(ifa); + if (!Address.empty()) + { + res.emplace_back(Address); + } + } + + if (ifAddrStruct != nullptr) + { + freeifaddrs(ifAddrStruct); + } + + #endif // else _WIN32 + + return res; +} + + + + diff --git a/src/OSSupport/NetworkSingleton.cpp b/src/OSSupport/NetworkSingleton.cpp index 000b17641..358e24438 100644 --- a/src/OSSupport/NetworkSingleton.cpp +++ b/src/OSSupport/NetworkSingleton.cpp @@ -63,8 +63,7 @@ cNetworkSingleton::cNetworkSingleton(void): } // Create the event loop thread: - std::thread EventLoopThread(RunEventLoop, this); - EventLoopThread.detach(); + m_EventLoopThread = std::thread(RunEventLoop, this); } @@ -98,7 +97,7 @@ void cNetworkSingleton::Terminate(void) // Wait for the LibEvent event loop to terminate: event_base_loopbreak(m_EventBase); - m_EventLoopTerminated.Wait(); + m_EventLoopThread.join(); // Remove all objects: { @@ -143,7 +142,6 @@ void cNetworkSingleton::LogCallback(int a_Severity, const char * a_Msg) void cNetworkSingleton::RunEventLoop(cNetworkSingleton * a_Self) { event_base_loop(a_Self->m_EventBase, EVLOOP_NO_EXIT_ON_EMPTY); - a_Self->m_EventLoopTerminated.Set(); } diff --git a/src/OSSupport/NetworkSingleton.h b/src/OSSupport/NetworkSingleton.h index e27e19012..0536a1c82 100644 --- a/src/OSSupport/NetworkSingleton.h +++ b/src/OSSupport/NetworkSingleton.h @@ -116,12 +116,12 @@ protected: /** Mutex protecting all containers against multithreaded access. */ cCriticalSection m_CS; - /** Event that gets signalled when the event loop terminates. */ - cEvent m_EventLoopTerminated; - /** Set to true if Terminate has been called. */ volatile bool m_HasTerminated; + /** The thread in which the main LibEvent loop runs. */ + std::thread m_EventLoopThread; + /** Initializes the LibEvent internals. */ cNetworkSingleton(void); diff --git a/src/OSSupport/ServerHandleImpl.cpp b/src/OSSupport/ServerHandleImpl.cpp index 72092df10..44ace448b 100644 --- a/src/OSSupport/ServerHandleImpl.cpp +++ b/src/OSSupport/ServerHandleImpl.cpp @@ -232,7 +232,7 @@ bool cServerHandleImpl::Listen(UInt16 a_Port) } // Allow the port to be reused right after the socket closes: - if (evutil_make_listen_socket_reuseable(MainSock) != 0) + if (evutil_make_listen_socket_reuseable(SecondSock) != 0) { m_ErrorCode = EVUTIL_SOCKET_ERROR(); Printf(m_ErrorMsg, "Port %d cannot be made reusable (second socket): %d (%s). Restarting the server might not work.", diff --git a/src/OSSupport/TCPLinkImpl.cpp b/src/OSSupport/TCPLinkImpl.cpp index 88fb57838..c6f1978ad 100644 --- a/src/OSSupport/TCPLinkImpl.cpp +++ b/src/OSSupport/TCPLinkImpl.cpp @@ -7,6 +7,7 @@ #include "TCPLinkImpl.h" #include "NetworkSingleton.h" #include "ServerHandleImpl.h" +#include "event2/buffer.h" @@ -17,7 +18,10 @@ cTCPLinkImpl::cTCPLinkImpl(cTCPLink::cCallbacksPtr a_LinkCallbacks): super(a_LinkCallbacks), - m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE)) + m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE)), + m_LocalPort(0), + m_RemotePort(0), + m_ShouldShutdown(false) { LOGD("Created new cTCPLinkImpl at %p with BufferEvent at %p", this, m_BufferEvent); } @@ -29,7 +33,10 @@ cTCPLinkImpl::cTCPLinkImpl(cTCPLink::cCallbacksPtr a_LinkCallbacks): cTCPLinkImpl::cTCPLinkImpl(evutil_socket_t a_Socket, cTCPLink::cCallbacksPtr a_LinkCallbacks, cServerHandleImplPtr a_Server, const sockaddr * a_Address, socklen_t a_AddrLen): super(a_LinkCallbacks), m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), a_Socket, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE)), - m_Server(a_Server) + m_Server(a_Server), + m_LocalPort(0), + m_RemotePort(0), + m_ShouldShutdown(false) { LOGD("Created new cTCPLinkImpl at %p with BufferEvent at %p", this, m_BufferEvent); @@ -111,7 +118,7 @@ void cTCPLinkImpl::Enable(cTCPLinkImplPtr a_Self) m_Self = a_Self; // Set the LibEvent callbacks and enable processing: - bufferevent_setcb(m_BufferEvent, ReadCallback, nullptr, EventCallback, this); + bufferevent_setcb(m_BufferEvent, ReadCallback, WriteCallback, EventCallback, this); bufferevent_enable(m_BufferEvent, EV_READ | EV_WRITE); } @@ -121,6 +128,11 @@ void cTCPLinkImpl::Enable(cTCPLinkImplPtr a_Self) bool cTCPLinkImpl::Send(const void * a_Data, size_t a_Length) { + if (m_ShouldShutdown) + { + LOGD("%s: Cannot send data, the link is already shut down.", __FUNCTION__); + return false; + } return (bufferevent_write(m_BufferEvent, a_Data, a_Length) == 0); } @@ -130,12 +142,15 @@ bool cTCPLinkImpl::Send(const void * a_Data, size_t a_Length) void cTCPLinkImpl::Shutdown(void) { - #ifdef _WIN32 - shutdown(bufferevent_getfd(m_BufferEvent), SD_SEND); - #else - shutdown(bufferevent_getfd(m_BufferEvent), SHUT_WR); - #endif - bufferevent_disable(m_BufferEvent, EV_WRITE); + // If there's no outgoing data, shutdown the socket directly: + if (evbuffer_get_length(bufferevent_get_output(m_BufferEvent)) == 0) + { + DoActualShutdown(); + return; + } + + // There's still outgoing data in the LibEvent buffer, schedule a shutdown when it's written to OS's TCP stack: + m_ShouldShutdown = true; } @@ -181,6 +196,24 @@ void cTCPLinkImpl::ReadCallback(bufferevent * a_BufferEvent, void * a_Self) +void cTCPLinkImpl::WriteCallback(bufferevent * a_BufferEvent, void * a_Self) +{ + ASSERT(a_Self != nullptr); + auto Self = static_cast<cTCPLinkImpl *>(a_Self); + ASSERT(Self->m_Callbacks != nullptr); + + // If there's no more data to write and the link has been scheduled for shutdown, do the shutdown: + auto OutLen = evbuffer_get_length(bufferevent_get_output(Self->m_BufferEvent)); + if ((OutLen == 0) && (Self->m_ShouldShutdown)) + { + Self->DoActualShutdown(); + } +} + + + + + void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void * a_Self) { LOGD("cTCPLink event callback for link %p, BEV %p; what = 0x%02x", a_Self, a_BufferEvent, a_What); @@ -316,6 +349,20 @@ void cTCPLinkImpl::UpdateRemoteAddress(void) +void cTCPLinkImpl::DoActualShutdown(void) +{ + #ifdef _WIN32 + shutdown(bufferevent_getfd(m_BufferEvent), SD_SEND); + #else + shutdown(bufferevent_getfd(m_BufferEvent), SHUT_WR); + #endif + bufferevent_disable(m_BufferEvent, EV_WRITE); +} + + + + + //////////////////////////////////////////////////////////////////////////////// // cNetwork API: diff --git a/src/OSSupport/TCPLinkImpl.h b/src/OSSupport/TCPLinkImpl.h index 735e8ed9d..bea21aeff 100644 --- a/src/OSSupport/TCPLinkImpl.h +++ b/src/OSSupport/TCPLinkImpl.h @@ -94,6 +94,11 @@ protected: Initialized in Enable(), cleared in Close() and EventCallback(RemoteClosed). */ cTCPLinkImplPtr m_Self; + /** If true, Shutdown() has been called and is in queue. + No more data is allowed to be sent via Send() and after all the currently buffered + data is sent to the OS TCP stack, the socket gets shut down. */ + bool m_ShouldShutdown; + /** Creates a new link to be queued to connect to a specified host:port. Used for outgoing connections created using cNetwork::Connect(). @@ -104,6 +109,9 @@ protected: /** Callback that LibEvent calls when there's data available from the remote peer. */ static void ReadCallback(bufferevent * a_BufferEvent, void * a_Self); + /** Callback that LibEvent calls when the remote peer can receive more data. */ + static void WriteCallback(bufferevent * a_BufferEvent, void * a_Self); + /** Callback that LibEvent calls when there's a non-data-related event on the socket. */ static void EventCallback(bufferevent * a_BufferEvent, short a_What, void * a_Self); @@ -115,6 +123,10 @@ protected: /** Updates m_RemoteIP and m_RemotePort based on the metadata read from the socket. */ void UpdateRemoteAddress(void); + + /** Calls shutdown on the link and disables LibEvent writing. + Called after all data from LibEvent buffers is sent to the OS TCP stack and shutdown() has been called before. */ + void DoActualShutdown(void); }; diff --git a/src/OSSupport/UDPEndpointImpl.cpp b/src/OSSupport/UDPEndpointImpl.cpp new file mode 100644 index 000000000..ece521ab8 --- /dev/null +++ b/src/OSSupport/UDPEndpointImpl.cpp @@ -0,0 +1,608 @@ + +// UDPEndpointImpl.cpp + +// Implements the cUDPEndpointImpl class representing an implementation of an endpoint in UDP communication + +#include "Globals.h" +#include "UDPEndpointImpl.h" +#include "NetworkSingleton.h" + + + + + +//////////////////////////////////////////////////////////////////////////////// +// Globals: + +static bool IsValidSocket(evutil_socket_t a_Socket) +{ + #ifdef _WIN32 + return (a_Socket != INVALID_SOCKET); + #else // _WIN32 + return (a_Socket >= 0); + #endif // else _WIN32 +} + + + + + +/** Converts a_SrcAddr in IPv4 format to a_DstAddr in IPv6 format (using IPv4-mapped IPv6). */ +static void ConvertIPv4ToMappedIPv6(sockaddr_in & a_SrcAddr, sockaddr_in6 & a_DstAddr) +{ + memset(&a_DstAddr, 0, sizeof(a_DstAddr)); + a_DstAddr.sin6_family = AF_INET6; + a_DstAddr.sin6_addr.s6_addr[10] = 0xff; + a_DstAddr.sin6_addr.s6_addr[11] = 0xff; + a_DstAddr.sin6_addr.s6_addr[12] = static_cast<Byte>((a_SrcAddr.sin_addr.s_addr >> 0) & 0xff); + a_DstAddr.sin6_addr.s6_addr[13] = static_cast<Byte>((a_SrcAddr.sin_addr.s_addr >> 8) & 0xff); + a_DstAddr.sin6_addr.s6_addr[14] = static_cast<Byte>((a_SrcAddr.sin_addr.s_addr >> 16) & 0xff); + a_DstAddr.sin6_addr.s6_addr[15] = static_cast<Byte>((a_SrcAddr.sin_addr.s_addr >> 24) & 0xff); + a_DstAddr.sin6_port = a_SrcAddr.sin_port; +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cUDPSendAfterLookup: + +/** A hostname-to-IP resolver callback that sends the data stored within to the resolved IP address. +This is used for sending UDP datagrams to hostnames, so that the cUDPEndpoint::Send() doesn't block. +Instead an instance of this callback is queued for resolving and the data is sent once the IP is resolved. */ +class cUDPSendAfterLookup: + public cNetwork::cResolveNameCallbacks +{ +public: + cUDPSendAfterLookup(const AString & a_Data, UInt16 a_Port, evutil_socket_t a_MainSock, evutil_socket_t a_SecondSock, bool a_IsMainSockIPv6): + m_Data(a_Data), + m_Port(a_Port), + m_MainSock(a_MainSock), + m_SecondSock(a_SecondSock), + m_IsMainSockIPv6(a_IsMainSockIPv6), + m_HasIPv4(false), + m_HasIPv6(false) + { + } + +protected: + /** The data to send after the hostname is resolved. */ + AString m_Data; + + /** The port to which to send the data. */ + UInt16 m_Port; + + /** The primary socket to use for sending. */ + evutil_socket_t m_MainSock; + + /** The secondary socket to use for sending, if needed by the OS. */ + evutil_socket_t m_SecondSock; + + /** True if m_MainSock is an IPv6 socket. */ + bool m_IsMainSockIPv6; + + /** The IPv4 address resolved, if any. */ + sockaddr_in m_AddrIPv4; + + /** Set to true if the name resolved to an IPv4 address. */ + bool m_HasIPv4; + + /** The IPv6 address resolved, if any. */ + sockaddr_in6 m_AddrIPv6; + + /** Set to true if the name resolved to an IPv6 address. */ + bool m_HasIPv6; + + + // cNetwork::cResolveNameCallbacks overrides: + virtual void OnNameResolved(const AString & a_Name, const AString & a_PI) override + { + // Not needed + } + + virtual bool OnNameResolvedV4(const AString & a_Name, const sockaddr_in * a_IP) override + { + if (!m_HasIPv4) + { + m_AddrIPv4 = *a_IP; + m_AddrIPv4.sin_port = htons(m_Port); + m_HasIPv4 = true; + } + + // Don't want OnNameResolved() callback + return false; + } + + virtual bool OnNameResolvedV6(const AString & a_Name, const sockaddr_in6 * a_IP) override + { + if (!m_HasIPv6) + { + m_AddrIPv6 = *a_IP; + m_AddrIPv6.sin6_port = htons(m_Port); + m_HasIPv6 = true; + } + + // Don't want OnNameResolved() callback + return false; + } + + virtual void OnFinished(void) override + { + // Send the actual data, through the correct socket and using the correct resolved address: + if (m_IsMainSockIPv6) + { + if (m_HasIPv6) + { + sendto(m_MainSock, m_Data.data(), static_cast<socklen_t>(m_Data.size()), 0, reinterpret_cast<const sockaddr *>(&m_AddrIPv6), static_cast<socklen_t>(sizeof(m_AddrIPv6))); + } + else if (m_HasIPv4) + { + // If the secondary socket is valid, it is an IPv4 socket, so use that: + if (m_SecondSock != -1) + { + sendto(m_SecondSock, m_Data.data(), static_cast<socklen_t>(m_Data.size()), 0, reinterpret_cast<const sockaddr *>(&m_AddrIPv4), static_cast<socklen_t>(sizeof(m_AddrIPv4))); + } + else + { + // Need an address conversion from IPv4 to IPv6-mapped-IPv4: + ConvertIPv4ToMappedIPv6(m_AddrIPv4, m_AddrIPv6); + sendto(m_MainSock, m_Data.data(), static_cast<socklen_t>(m_Data.size()), 0, reinterpret_cast<const sockaddr *>(&m_AddrIPv6), static_cast<socklen_t>(sizeof(m_AddrIPv6))); + } + } + else + { + LOGD("UDP endpoint queued sendto: Name not resolved"); + return; + } + } + else // m_IsMainSockIPv6 + { + // Main socket is IPv4 only, only allow IPv4 dst address: + if (!m_HasIPv4) + { + LOGD("UDP endpoint queued sendto: Name not resolved to IPv4 for an IPv4-only socket"); + return; + } + sendto(m_MainSock, m_Data.data(), static_cast<socklen_t>(m_Data.size()), 0, reinterpret_cast<const sockaddr *>(&m_AddrIPv4), static_cast<socklen_t>(sizeof(m_AddrIPv4))); + } + } + + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override + { + // Nothing needed + } +}; + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cUDPEndpointImpl: + +cUDPEndpointImpl::cUDPEndpointImpl(UInt16 a_Port, cUDPEndpoint::cCallbacks & a_Callbacks): + super(a_Callbacks), + m_Port(0), + m_MainSock(-1), + m_IsMainSockIPv6(true), + m_SecondarySock(-1), + m_MainEvent(nullptr), + m_SecondaryEvent(nullptr) +{ + Open(a_Port); +} + + + + + +void cUDPEndpointImpl::Close(void) +{ + if (m_Port == 0) + { + // Already closed + return; + } + + // Close the LibEvent handles: + if (m_MainEvent != nullptr) + { + event_free(m_MainEvent); + m_MainEvent = nullptr; + } + if (m_SecondaryEvent != nullptr) + { + event_free(m_SecondaryEvent); + m_SecondaryEvent = nullptr; + } + + // Close the OS sockets: + evutil_closesocket(m_MainSock); + m_MainSock = -1; + evutil_closesocket(m_SecondarySock); + m_SecondarySock = -1; + + // Mark as closed: + m_Port = 0; +} + + + + + +bool cUDPEndpointImpl::IsOpen(void) const +{ + return (m_Port != 0); +} + + + + + +UInt16 cUDPEndpointImpl::GetPort(void) const +{ + return m_Port; +} + + + + + +bool cUDPEndpointImpl::Send(const AString & a_Payload, const AString & a_Host, UInt16 a_Port) +{ + // If a_Host is an IP address, send the data directly: + sockaddr_storage sa; + int salen = static_cast<int>(sizeof(sa)); + memset(&sa, 0, sizeof(sa)); + if (evutil_parse_sockaddr_port(a_Host.c_str(), reinterpret_cast<sockaddr *>(&sa), &salen) != 0) + { + // a_Host is a hostname, we need to do a lookup first: + auto queue = std::make_shared<cUDPSendAfterLookup>(a_Payload, a_Port, m_MainSock, m_SecondarySock, m_IsMainSockIPv6); + return cNetwork::HostnameToIP(a_Host, queue); + } + + // a_Host is an IP address and has been parsed into "sa" + // Insert the correct port and send data: + int NumSent; + switch (sa.ss_family) + { + case AF_INET: + { + reinterpret_cast<sockaddr_in *>(&sa)->sin_port = htons(a_Port); + if (m_IsMainSockIPv6) + { + if (IsValidSocket(m_SecondarySock)) + { + // The secondary socket, which is always IPv4, is present: + NumSent = static_cast<int>(sendto(m_SecondarySock, a_Payload.data(), static_cast<socklen_t>(a_Payload.size()), 0, reinterpret_cast<const sockaddr *>(&sa), static_cast<socklen_t>(salen))); + } + else + { + // Need to convert IPv4 to IPv6 address before sending: + sockaddr_in6 IPv6; + ConvertIPv4ToMappedIPv6(*reinterpret_cast<sockaddr_in *>(&sa), IPv6); + NumSent = static_cast<int>(sendto(m_MainSock, a_Payload.data(), static_cast<socklen_t>(a_Payload.size()), 0, reinterpret_cast<const sockaddr *>(&IPv6), static_cast<socklen_t>(sizeof(IPv6)))); + } + } + else + { + NumSent = static_cast<int>(sendto(m_MainSock, a_Payload.data(), static_cast<socklen_t>(a_Payload.size()), 0, reinterpret_cast<const sockaddr *>(&sa), static_cast<socklen_t>(salen))); + } + break; + } + + case AF_INET6: + { + reinterpret_cast<sockaddr_in6 *>(&sa)->sin6_port = htons(a_Port); + NumSent = static_cast<int>(sendto(m_MainSock, a_Payload.data(), static_cast<socklen_t>(a_Payload.size()), 0, reinterpret_cast<const sockaddr *>(&sa), static_cast<socklen_t>(salen))); + break; + } + default: + { + LOGD("UDP sendto: Invalid address family for address \"%s\".", a_Host.c_str()); + return false; + } + } + return (NumSent > 0); +} + + + + + +void cUDPEndpointImpl::EnableBroadcasts(void) +{ + ASSERT(IsOpen()); + + // Enable broadcasts on the main socket: + // Some OSes use ints, others use chars, so we try both + int broadcastInt = 1; + char broadcastChar = 1; + // (Note that Windows uses const char * for option values, while Linux uses const void *) + if (setsockopt(m_MainSock, SOL_SOCKET, SO_BROADCAST, reinterpret_cast<const char *>(&broadcastInt), sizeof(broadcastInt)) == -1) + { + if (setsockopt(m_MainSock, SOL_SOCKET, SO_BROADCAST, &broadcastChar, sizeof(broadcastChar)) == -1) + { + int err = EVUTIL_SOCKET_ERROR(); + LOGWARNING("Cannot enable broadcasts on port %d: %d (%s)", m_Port, err, evutil_socket_error_to_string(err)); + return; + } + + // Enable broadcasts on the secondary socket, if opened (use char, it worked for primary): + if (IsValidSocket(m_SecondarySock)) + { + if (setsockopt(m_SecondarySock, SOL_SOCKET, SO_BROADCAST, &broadcastChar, sizeof(broadcastChar)) == -1) + { + int err = EVUTIL_SOCKET_ERROR(); + LOGWARNING("Cannot enable broadcasts on port %d (secondary): %d (%s)", m_Port, err, evutil_socket_error_to_string(err)); + } + } + return; + } + + // Enable broadcasts on the secondary socket, if opened (use int, it worked for primary): + if (IsValidSocket(m_SecondarySock)) + { + if (setsockopt(m_SecondarySock, SOL_SOCKET, SO_BROADCAST, reinterpret_cast<const char *>(&broadcastInt), sizeof(broadcastInt)) == -1) + { + int err = EVUTIL_SOCKET_ERROR(); + LOGWARNING("Cannot enable broadcasts on port %d (secondary): %d (%s)", m_Port, err, evutil_socket_error_to_string(err)); + } + } +} + + + + + +void cUDPEndpointImpl::Open(UInt16 a_Port) +{ + ASSERT(m_Port == 0); // Must not be already open + + // Make sure the cNetwork internals are innitialized: + cNetworkSingleton::Get(); + + // Set up the main socket: + // It should listen on IPv6 with IPv4 fallback, when available; IPv4 when IPv6 is not available. + bool NeedsTwoSockets = false; + m_IsMainSockIPv6 = true; + m_MainSock = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); + + int err; + if (!IsValidSocket(m_MainSock)) + { + // Failed to create IPv6 socket, create an IPv4 one instead: + m_IsMainSockIPv6 = false; + err = EVUTIL_SOCKET_ERROR(); + LOGD("Failed to create IPv6 MainSock: %d (%s)", err, evutil_socket_error_to_string(err)); + m_MainSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (!IsValidSocket(m_MainSock)) + { + err = EVUTIL_SOCKET_ERROR(); + m_Callbacks.OnError(err, Printf("Cannot create UDP socket for port %d: %s", a_Port, evutil_socket_error_to_string(err))); + return; + } + + // Allow the port to be reused right after the socket closes: + if (evutil_make_listen_socket_reuseable(m_MainSock) != 0) + { + err = EVUTIL_SOCKET_ERROR(); + LOG("UDP Port %d cannot be made reusable: %d (%s). Restarting the server might not work.", + a_Port, err, evutil_socket_error_to_string(err) + ); + } + + // Bind to all interfaces: + sockaddr_in name; + memset(&name, 0, sizeof(name)); + name.sin_family = AF_INET; + name.sin_port = ntohs(a_Port); + if (bind(m_MainSock, reinterpret_cast<const sockaddr *>(&name), sizeof(name)) != 0) + { + err = EVUTIL_SOCKET_ERROR(); + m_Callbacks.OnError(err, Printf("Cannot bind UDP port %d: %s", a_Port, evutil_socket_error_to_string(err))); + evutil_closesocket(m_MainSock); + return; + } + } + else + { + // IPv6 socket created, switch it into "dualstack" mode: + UInt32 Zero = 0; + #ifdef _WIN32 + // WinXP doesn't support this feature, so if the setting fails, create another socket later on: + int res = setsockopt(m_MainSock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<const char *>(&Zero), sizeof(Zero)); + err = EVUTIL_SOCKET_ERROR(); + NeedsTwoSockets = ((res == SOCKET_ERROR) && (err == WSAENOPROTOOPT)); + #else + setsockopt(m_MainSock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<const char *>(&Zero), sizeof(Zero)); + #endif + + // Allow the port to be reused right after the socket closes: + if (evutil_make_listen_socket_reuseable(m_MainSock) != 0) + { + err = EVUTIL_SOCKET_ERROR(); + LOG("UDP Port %d cannot be made reusable: %d (%s). Restarting the server might not work.", + a_Port, err, evutil_socket_error_to_string(err) + ); + } + + // Bind to all interfaces: + sockaddr_in6 name; + memset(&name, 0, sizeof(name)); + name.sin6_family = AF_INET6; + name.sin6_port = ntohs(a_Port); + if (bind(m_MainSock, reinterpret_cast<const sockaddr *>(&name), sizeof(name)) != 0) + { + err = EVUTIL_SOCKET_ERROR(); + m_Callbacks.OnError(err, Printf("Cannot bind to UDP port %d: %s", a_Port, evutil_socket_error_to_string(err))); + evutil_closesocket(m_MainSock); + return; + } + } + if (evutil_make_socket_nonblocking(m_MainSock) != 0) + { + err = EVUTIL_SOCKET_ERROR(); + m_Callbacks.OnError(err, Printf("Cannot make socket on UDP port %d nonblocking: %s", a_Port, evutil_socket_error_to_string(err))); + evutil_closesocket(m_MainSock); + return; + } + m_MainEvent = event_new(cNetworkSingleton::Get().GetEventBase(), m_MainSock, EV_READ | EV_PERSIST, RawCallback, this); + event_add(m_MainEvent, nullptr); + + // Read the actual port number on which the socket is listening: + { + sockaddr_storage name; + socklen_t namelen = static_cast<socklen_t>(sizeof(name)); + getsockname(m_MainSock, reinterpret_cast<sockaddr *>(&name), &namelen); + switch (name.ss_family) + { + case AF_INET: + { + sockaddr_in * sin = reinterpret_cast<sockaddr_in *>(&name); + m_Port = ntohs(sin->sin_port); + break; + } + case AF_INET6: + { + sockaddr_in6 * sin6 = reinterpret_cast<sockaddr_in6 *>(&name); + m_Port = ntohs(sin6->sin6_port); + break; + } + } + } + + // If we don't need to create another socket, bail out now: + if (!NeedsTwoSockets) + { + return; + } + + // If a secondary socket is required (WinXP dual-stack), create it here: + LOGD("Creating a second UDP socket for IPv4"); + m_SecondarySock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + + if (!IsValidSocket(m_SecondarySock)) + { + // Don't report as an error, the primary socket is working + err = EVUTIL_SOCKET_ERROR(); + LOGD("Socket creation failed for secondary UDP socket for port %d: %d, %s", m_Port, err, evutil_socket_error_to_string(err)); + return; + } + + // Allow the port to be reused right after the socket closes: + if (evutil_make_listen_socket_reuseable(m_SecondarySock) != 0) + { + // Don't report as an error, the primary socket is working + err = EVUTIL_SOCKET_ERROR(); + LOGD("UDP Port %d cannot be made reusable (second socket): %d (%s). Restarting the server might not work.", + a_Port, err, evutil_socket_error_to_string(err) + ); + evutil_closesocket(m_SecondarySock); + m_SecondarySock = -1; + return; + } + + // Make the secondary socket nonblocking: + if (evutil_make_socket_nonblocking(m_SecondarySock) != 0) + { + // Don't report as an error, the primary socket is working + err = EVUTIL_SOCKET_ERROR(); + LOGD("evutil_make_socket_nonblocking() failed for secondary UDP socket: %d, %s", err, evutil_socket_error_to_string(err)); + evutil_closesocket(m_SecondarySock); + m_SecondarySock = -1; + return; + } + + // Bind to all IPv4 interfaces: + sockaddr_in name; + memset(&name, 0, sizeof(name)); + name.sin_family = AF_INET; + name.sin_port = ntohs(m_Port); + if (bind(m_SecondarySock, reinterpret_cast<const sockaddr *>(&name), sizeof(name)) != 0) + { + // Don't report as an error, the primary socket is working + err = EVUTIL_SOCKET_ERROR(); + LOGD("Cannot bind secondary socket to UDP port %d: %d (%s)", m_Port, err, evutil_socket_error_to_string(err)); + evutil_closesocket(m_SecondarySock); + m_SecondarySock = -1; + return; + } + + m_SecondaryEvent = event_new(cNetworkSingleton::Get().GetEventBase(), m_SecondarySock, EV_READ | EV_PERSIST, RawCallback, this); + event_add(m_SecondaryEvent, nullptr); +} + + + + + +void cUDPEndpointImpl::RawCallback(evutil_socket_t a_Socket, short a_What, void * a_Self) +{ + cUDPEndpointImpl * Self = reinterpret_cast<cUDPEndpointImpl *>(a_Self); + Self->Callback(a_Socket, a_What); +} + + + + + +void cUDPEndpointImpl::Callback(evutil_socket_t a_Socket, short a_What) +{ + if ((a_What & EV_READ) != 0) + { + // Receive datagram from the socket: + char buf[64 KiB]; + socklen_t buflen = static_cast<socklen_t>(sizeof(buf)); + sockaddr_storage sa; + socklen_t salen = static_cast<socklen_t>(sizeof(sa)); + auto len = recvfrom(a_Socket, buf, buflen, 0, reinterpret_cast<sockaddr *>(&sa), &salen); + if (len >= 0) + { + // Convert the remote IP address to a string: + char RemoteHost[128]; + UInt16 RemotePort; + switch (sa.ss_family) + { + case AF_INET: + { + auto sin = reinterpret_cast<sockaddr_in *>(&sa); + evutil_inet_ntop(sa.ss_family, &sin->sin_addr, RemoteHost, sizeof(RemoteHost)); + RemotePort = ntohs(sin->sin_port); + break; + } + case AF_INET6: + { + auto sin = reinterpret_cast<sockaddr_in6 *>(&sa); + evutil_inet_ntop(sa.ss_family, &sin->sin6_addr, RemoteHost, sizeof(RemoteHost)); + RemotePort = ntohs(sin->sin6_port); + break; + } + default: + { + return; + } + } + + // Call the callback: + m_Callbacks.OnReceivedData(buf, static_cast<size_t>(len), RemoteHost, RemotePort); + } + } +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cNetwork API: + +cUDPEndpointPtr cNetwork::CreateUDPEndpoint(UInt16 a_Port, cUDPEndpoint::cCallbacks & a_Callbacks) +{ + return std::make_shared<cUDPEndpointImpl>(a_Port, a_Callbacks); +} + + + + diff --git a/src/OSSupport/UDPEndpointImpl.h b/src/OSSupport/UDPEndpointImpl.h new file mode 100644 index 000000000..75942b0cf --- /dev/null +++ b/src/OSSupport/UDPEndpointImpl.h @@ -0,0 +1,81 @@ + +// UDPEndpointImpl.h + +// Declares the cUDPEndpointImpl class representing an implementation of an endpoint in UDP communication + + + + + +#pragma once + +#include "Network.h" +#include <event2/event.h> + + + + + +// fwd: +class cUDPEndpointImpl; +typedef SharedPtr<cUDPEndpointImpl> cUDPEndpointImplPtr; + + + + + +class cUDPEndpointImpl: + public cUDPEndpoint +{ + typedef cUDPEndpoint super; + +public: + /** Creates a new instance of the endpoint, with the specified callbacks. + Tries to open on the specified port; if it fails, the endpoint is left in the "closed" state. + If a_Port is 0, the OS is free to assign any port number it likes to the endpoint. */ + cUDPEndpointImpl(UInt16 a_Port, cUDPEndpoint::cCallbacks & a_Callbacks); + + // cUDPEndpoint overrides: + virtual void Close(void) override; + virtual bool IsOpen(void) const override; + virtual UInt16 GetPort(void) const override; + virtual bool Send(const AString & a_Payload, const AString & a_Host, UInt16 a_Port) override; + virtual void EnableBroadcasts(void) override; + +protected: + /** The local port on which the endpoint is open. + If this is zero, it means the endpoint is closed - either opening has failed, or it has been closed explicitly. */ + UInt16 m_Port; + + /** The primary underlying OS socket. */ + evutil_socket_t m_MainSock; + + /** True if m_MainSock is in the IPv6 namespace (needs IPv6 addresses for sending). */ + bool m_IsMainSockIPv6; + + /** The secondary OS socket (if primary doesn't support dualstack). */ + evutil_socket_t m_SecondarySock; + + /** The LibEvent handle for the primary socket. */ + event * m_MainEvent; + + /** The LibEvent handle for the secondary socket. */ + event * m_SecondaryEvent; + + + /** Creates and opens the socket on the specified port. + If a_Port is 0, the OS is free to assign any port number it likes to the endpoint. + If the opening fails, the OnError() callback is called and the endpoint is left "closed" (IsOpen() returns false). */ + void Open(UInt16 a_Port); + + /** The callback that LibEvent calls when an event occurs on one of the sockets. + Calls Callback() on a_Self. */ + static void RawCallback(evutil_socket_t a_Socket, short a_What, void * a_Self); + + /** The callback that is called when an event occurs on one of the sockets. */ + void Callback(evutil_socket_t a_Socket, short a_What); +}; + + + + diff --git a/src/PolarSSL++/CryptoKey.cpp b/src/PolarSSL++/CryptoKey.cpp index 7c4f021b3..9354ddf50 100644 --- a/src/PolarSSL++/CryptoKey.cpp +++ b/src/PolarSSL++/CryptoKey.cpp @@ -45,7 +45,7 @@ cCryptoKey::cCryptoKey(const AString & a_PrivateKeyData, const AString & a_Passw if (res != 0) { LOGWARNING("Failed to parse private key: -0x%x", res); - ASSERT(!"Cannot parse PubKey"); + ASSERT(!"Cannot parse PrivKey"); return; } } diff --git a/src/PolarSSL++/SslContext.cpp b/src/PolarSSL++/SslContext.cpp index 66dfefc65..5ac4bc227 100644 --- a/src/PolarSSL++/SslContext.cpp +++ b/src/PolarSSL++/SslContext.cpp @@ -7,6 +7,7 @@ #include "SslContext.h" #include "EntropyContext.h" #include "CtrDrbgContext.h" +#include "polarssl/debug.h" @@ -69,6 +70,8 @@ int cSslContext::Initialize(bool a_IsClient, const SharedPtr<cCtrDrbgContext> & // These functions allow us to debug SSL and certificate problems, but produce way too much output, // so they're disabled until someone needs them ssl_set_dbg(&m_Ssl, &SSLDebugMessage, this); + debug_set_threshold(2); + ssl_set_verify(&m_Ssl, &SSLVerifyCert, this); //*/ diff --git a/src/Protocol/Protocol18x.cpp b/src/Protocol/Protocol18x.cpp index 9f4759800..56e3783b6 100644 --- a/src/Protocol/Protocol18x.cpp +++ b/src/Protocol/Protocol18x.cpp @@ -874,11 +874,15 @@ void cProtocol180::SendPlayerListUpdatePing(const cPlayer & a_Player) { ASSERT(m_State == 3); // In game mode? - cPacketizer Pkt(*this, 0x38); // Playerlist Item packet - Pkt.WriteVarInt(2); - Pkt.WriteVarInt(1); - Pkt.WriteUUID(a_Player.GetUUID()); - Pkt.WriteVarInt((UInt32)a_Player.GetClientHandle()->GetPing()); + auto ClientHandle = a_Player.GetClientHandlePtr(); + if (ClientHandle != nullptr) + { + cPacketizer Pkt(*this, 0x38); // Playerlist Item packet + Pkt.WriteVarInt(2); + Pkt.WriteVarInt(1); + Pkt.WriteUUID(a_Player.GetUUID()); + Pkt.WriteVarInt(static_cast<UInt32>(ClientHandle->GetPing())); + } } diff --git a/src/Server.cpp b/src/Server.cpp index 3f61be378..df2c7deef 100644 --- a/src/Server.cpp +++ b/src/Server.cpp @@ -549,7 +549,7 @@ void cServer::ExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCallbac } #endif - else if (cPluginManager::Get()->ExecuteConsoleCommand(split, a_Output)) + else if (cPluginManager::Get()->ExecuteConsoleCommand(split, a_Output, a_Cmd)) { a_Output.Finished(); return; diff --git a/src/StringUtils.cpp b/src/StringUtils.cpp index 4eb2d48b6..4adc6a0a0 100644 --- a/src/StringUtils.cpp +++ b/src/StringUtils.cpp @@ -140,6 +140,54 @@ AStringVector StringSplit(const AString & str, const AString & delim) +AStringVector StringSplitWithQuotes(const AString & str, const AString & delim) +{ + AStringVector results; + + size_t cutAt = 0; + size_t Prev = 0; + size_t cutAtQuote = 0; + + while ((cutAt = str.find_first_of(delim, Prev)) != str.npos) + { + AString current = str.substr(Prev, cutAt - Prev); + if ((current.front() == '"') || (current.front() == '\'')) + { + Prev += 1; + cutAtQuote = str.find_first_of(current.front(), Prev); + if (cutAtQuote != str.npos) + { + current = str.substr(Prev, cutAtQuote - Prev); + cutAt = cutAtQuote + 1; + } + } + + results.push_back(std::move(current)); + Prev = cutAt + 1; + } + + if (Prev < str.length()) + { + AString current = str.substr(Prev); + + // If the remant is wrapped in matching quotes, remove them: + if ( + (current.length() >= 2) && + ((current.front() == '"') || (current.front() == '\'')) && + (current.front() == current.back()) + ) + { + current = current.substr(1, current.length() - 2); + } + + results.push_back(current); + } + + return results; +} + + + AStringVector StringSplitAndTrim(const AString & str, const AString & delim) { diff --git a/src/StringUtils.h b/src/StringUtils.h index bc3bb7a2c..785197763 100644 --- a/src/StringUtils.h +++ b/src/StringUtils.h @@ -41,6 +41,11 @@ extern AString & AppendPrintf (AString & a_Dst, const char * format, ...) FORMAT Return the splitted strings as a stringvector. */ extern AStringVector StringSplit(const AString & str, const AString & delim); +/** Split the string at any of the listed delimiters. Keeps quoted content together +Resolves issue #490 +Return the splitted strings as a stringvector. */ +extern AStringVector StringSplitWithQuotes(const AString & str, const AString & delim); + /** Split the string at any of the listed delimiters and trim each value. Returns the splitted strings as a stringvector. */ extern AStringVector StringSplitAndTrim(const AString & str, const AString & delim); diff --git a/src/UI/AnvilWindow.cpp b/src/UI/AnvilWindow.cpp new file mode 100644 index 000000000..daa35cf47 --- /dev/null +++ b/src/UI/AnvilWindow.cpp @@ -0,0 +1,83 @@ + +// AnvilWindow.cpp + +// Representing the UI window for the anvil block + +#include "Globals.h" +#include "AnvilWindow.h" +#include "SlotArea.h" + + + + +cAnvilWindow::cAnvilWindow(int a_BlockX, int a_BlockY, int a_BlockZ) : + cWindow(wtAnvil, "Repair"), + m_RepairedItemName(""), + m_BlockX(a_BlockX), + m_BlockY(a_BlockY), + m_BlockZ(a_BlockZ) +{ + m_AnvilSlotArea = new cSlotAreaAnvil(*this); + m_SlotAreas.push_back(m_AnvilSlotArea); + m_SlotAreas.push_back(new cSlotAreaInventory(*this)); + m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); +} + + + + + +AString cAnvilWindow::GetRepairedItemName(void) const +{ + return m_RepairedItemName; +} + + + + + +void cAnvilWindow::SetRepairedItemName(const AString & a_Name, cPlayer * a_Player) +{ + m_RepairedItemName = a_Name; + if (a_Player != nullptr) + { + m_AnvilSlotArea->UpdateResult(*a_Player); + } +} + + + + + +void cAnvilWindow::GetBlockPos(int & a_PosX, int & a_PosY, int & a_PosZ) +{ + a_PosX = m_BlockX; + a_PosY = m_BlockY; + a_PosZ = m_BlockZ; +} + + + + + +void cAnvilWindow::DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Player, cSlotArea * a_ClickedArea, bool a_ShouldApply) +{ + cSlotAreas AreasInOrder; + + if (a_ClickedArea == m_SlotAreas[0]) + { + // Anvil Slot + AreasInOrder.push_back(m_SlotAreas[1]); /* Inventory */ + AreasInOrder.push_back(m_SlotAreas[2]); /* Hotbar */ + } + else + { + // Inventory or Hotbar + AreasInOrder.push_back(m_SlotAreas[0]); /* Anvil */ + } + super::DistributeStackToAreas(a_ItemStack, a_Player, AreasInOrder, a_ShouldApply, false); +} + + + + diff --git a/src/UI/AnvilWindow.h b/src/UI/AnvilWindow.h new file mode 100644 index 000000000..e23c744fe --- /dev/null +++ b/src/UI/AnvilWindow.h @@ -0,0 +1,45 @@ + +// AnvilWindow.h + +// Representing the UI window for the anvil block + + + + + +#pragma once + +#include "Window.h" + + + + + +class cAnvilWindow : + public cWindow +{ + typedef cWindow super; + +public: + cAnvilWindow(int a_BlockX, int a_BlockY, int a_BlockZ); + + /** Gets the repaired item name. */ + AString GetRepairedItemName(void) const; + + /** Set the repaired item name. */ + void SetRepairedItemName(const AString & a_Name, cPlayer * a_Player); + + /** Gets the Position from the Anvil */ + void GetBlockPos(int & a_PosX, int & a_PosY, int & a_PosZ); + + virtual void DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Player, cSlotArea * a_ClickedArea, bool a_ShouldApply) override; + +protected: + cSlotAreaAnvil * m_AnvilSlotArea; + AString m_RepairedItemName; + int m_BlockX, m_BlockY, m_BlockZ; +}; + + + + diff --git a/src/UI/BeaconWindow.cpp b/src/UI/BeaconWindow.cpp new file mode 100644 index 000000000..c1efa78ad --- /dev/null +++ b/src/UI/BeaconWindow.cpp @@ -0,0 +1,76 @@ + +// BeaconWindow.cpp + +// Representing the UI window for the beacon block + +#include "Globals.h" +#include "BeaconWindow.h" +#include "SlotArea.h" +#include "../BlockEntities/BeaconEntity.h" +#include "../Entities/Player.h" + + + + + +cBeaconWindow::cBeaconWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cBeaconEntity * a_Beacon) : + cWindow(wtBeacon, "Beacon"), + m_Beacon(a_Beacon) +{ + m_SlotAreas.push_back(new cSlotAreaBeacon(m_Beacon, *this)); + m_SlotAreas.push_back(new cSlotAreaInventory(*this)); + m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); +} + + + + + +void cBeaconWindow::DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Player, cSlotArea * a_ClickedArea, bool a_ShouldApply) +{ + cSlotAreas AreasInOrder; + + if (a_ClickedArea == m_SlotAreas[0]) + { + // Beacon Area + AreasInOrder.push_back(m_SlotAreas[2]); /* Hotbar */ + AreasInOrder.push_back(m_SlotAreas[1]); /* Inventory */ + super::DistributeStackToAreas(a_ItemStack, a_Player, AreasInOrder, a_ShouldApply, true); + } + else + { + if (cSlotAreaBeacon::IsPlaceableItem(a_ItemStack.m_ItemType) && (a_ItemStack.m_ItemCount == 1)) + { + AreasInOrder.push_back(m_SlotAreas[0]); /* Beacon */ + } + + if (a_ClickedArea == m_SlotAreas[1]) + { + // Inventory Area + AreasInOrder.push_back(m_SlotAreas[2]); /* Hotbar */ + } + else + { + // Hotbar Area + AreasInOrder.push_back(m_SlotAreas[1]); /* Inventory */ + } + super::DistributeStackToAreas(a_ItemStack, a_Player, AreasInOrder, a_ShouldApply, false); + } +} + + + + + +void cBeaconWindow::OpenedByPlayer(cPlayer & a_Player) +{ + super::OpenedByPlayer(a_Player); + + a_Player.GetClientHandle()->SendWindowProperty(*this, 0, m_Beacon->GetBeaconLevel()); + a_Player.GetClientHandle()->SendWindowProperty(*this, 1, m_Beacon->GetPrimaryEffect()); + a_Player.GetClientHandle()->SendWindowProperty(*this, 2, m_Beacon->GetSecondaryEffect()); +} + + + + diff --git a/src/UI/BeaconWindow.h b/src/UI/BeaconWindow.h new file mode 100644 index 000000000..fa28b41ba --- /dev/null +++ b/src/UI/BeaconWindow.h @@ -0,0 +1,40 @@ + +// BeaconWindow.h + +// Representing the UI window for the beacon block + + + + + +#pragma once + +#include "Window.h" +#include "../Entities/Player.h" + + + + + +class cBeaconWindow : + public cWindow +{ + typedef cWindow super; + +public: + cBeaconWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cBeaconEntity * a_Beacon); + + cBeaconEntity * GetBeaconEntity(void) const { return m_Beacon; } + + virtual void DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Player, cSlotArea * a_ClickedArea, bool a_ShouldApply) override; + + // cWindow Overrides: + virtual void OpenedByPlayer(cPlayer & a_Player) override; + +protected: + cBeaconEntity * m_Beacon; +}; + + + + diff --git a/src/UI/CMakeLists.txt b/src/UI/CMakeLists.txt index 2b094ef1d..ef4afc40a 100644 --- a/src/UI/CMakeLists.txt +++ b/src/UI/CMakeLists.txt @@ -6,11 +6,32 @@ include_directories ("${PROJECT_SOURCE_DIR}/../") SET (SRCS SlotArea.cpp - Window.cpp) + Window.cpp + AnvilWindow.cpp + BeaconWindow.cpp + ChestWindow.cpp + CraftingWindow.cpp + DropSpenserWindow.cpp + EnchantingWindow.cpp + EnderChestWindow.cpp + FurnaceWindow.cpp + HopperWindow.cpp + InventoryWindow.cpp) SET (HDRS SlotArea.h Window.h + AnvilWindow.h + BeaconWindow.h + ChestWindow.h + CraftingWindow.h + DropSpenserWindow.h + EnchantingWindow.h + EnderChestWindow.h + FurnaceWindow.h + HopperWindow.h + InventoryWindow.h + MinecartWithChestWindow.h WindowOwner.h) if(NOT MSVC) diff --git a/src/UI/ChestWindow.cpp b/src/UI/ChestWindow.cpp new file mode 100644 index 000000000..3766b132d --- /dev/null +++ b/src/UI/ChestWindow.cpp @@ -0,0 +1,141 @@ + +// ChestWindow.cpp + +// Representing the UI window for the chest block + +#include "Globals.h" +#include "ChestWindow.h" +#include "../BlockEntities/ChestEntity.h" +#include "../Entities/Player.h" + + + + + +cChestWindow::cChestWindow(cChestEntity * a_Chest) : + cWindow(wtChest, (a_Chest->GetBlockType() == E_BLOCK_CHEST) ? "Chest" : "Trapped Chest"), + m_World(a_Chest->GetWorld()), + m_BlockX(a_Chest->GetPosX()), + m_BlockY(a_Chest->GetPosY()), + m_BlockZ(a_Chest->GetPosZ()), + m_PrimaryChest(a_Chest), + m_SecondaryChest(nullptr) +{ + m_SlotAreas.push_back(new cSlotAreaChest(a_Chest, *this)); + m_SlotAreas.push_back(new cSlotAreaInventory(*this)); + m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); + + // Play the opening sound: + m_World->BroadcastSoundEffect("random.chestopen", (double)m_BlockX, (double)m_BlockY, (double)m_BlockZ, 1, 1); + + // Send out the chest-open packet: + m_World->BroadcastBlockAction(m_BlockX, m_BlockY, m_BlockZ, 1, 1, a_Chest->GetBlockType()); +} + + + + + +cChestWindow::cChestWindow(cChestEntity * a_PrimaryChest, cChestEntity * a_SecondaryChest) : + cWindow(wtChest, (a_PrimaryChest->GetBlockType() == E_BLOCK_CHEST) ? "Double Chest" : "Double Trapped Chest"), + m_World(a_PrimaryChest->GetWorld()), + m_BlockX(a_PrimaryChest->GetPosX()), + m_BlockY(a_PrimaryChest->GetPosY()), + m_BlockZ(a_PrimaryChest->GetPosZ()), + m_PrimaryChest(a_PrimaryChest), + m_SecondaryChest(a_SecondaryChest) +{ + m_SlotAreas.push_back(new cSlotAreaDoubleChest(a_PrimaryChest, a_SecondaryChest, *this)); + m_SlotAreas.push_back(new cSlotAreaInventory(*this)); + m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); + + // Play the opening sound: + m_World->BroadcastSoundEffect("random.chestopen", (double)m_BlockX, (double)m_BlockY, (double)m_BlockZ, 1, 1); + + // Send out the chest-open packet: + m_World->BroadcastBlockAction(m_BlockX, m_BlockY, m_BlockZ, 1, 1, a_PrimaryChest->GetBlockType()); +} + + + + + +cChestWindow::~cChestWindow() +{ + // Send out the chest-close packet: + m_World->BroadcastBlockAction(m_BlockX, m_BlockY, m_BlockZ, 1, 0, m_PrimaryChest->GetBlockType()); + + m_World->BroadcastSoundEffect("random.chestclosed", (double)m_BlockX, (double)m_BlockY, (double)m_BlockZ, 1, 1); +} + + + + + +bool cChestWindow::ClosedByPlayer(cPlayer & a_Player, bool a_CanRefuse) +{ + int ChunkX, ChunkZ; + + m_PrimaryChest->SetNumberOfPlayers(m_PrimaryChest->GetNumberOfPlayers() - 1); + cChunkDef::BlockToChunk(m_PrimaryChest->GetPosX(), m_PrimaryChest->GetPosZ(), ChunkX, ChunkZ); + m_PrimaryChest->GetWorld()->MarkRedstoneDirty(ChunkX, ChunkZ); + + if (m_SecondaryChest != nullptr) + { + m_SecondaryChest->SetNumberOfPlayers(m_SecondaryChest->GetNumberOfPlayers() - 1); + cChunkDef::BlockToChunk(m_SecondaryChest->GetPosX(), m_SecondaryChest->GetPosZ(), ChunkX, ChunkZ); + m_SecondaryChest->GetWorld()->MarkRedstoneDirty(ChunkX, ChunkZ); + } + + cWindow::ClosedByPlayer(a_Player, a_CanRefuse); + return true; +} + + + + + +void cChestWindow::OpenedByPlayer(cPlayer & a_Player) +{ + int ChunkX, ChunkZ; + + m_PrimaryChest->SetNumberOfPlayers(m_PrimaryChest->GetNumberOfPlayers() + 1); + cChunkDef::BlockToChunk(m_PrimaryChest->GetPosX(), m_PrimaryChest->GetPosZ(), ChunkX, ChunkZ); + m_PrimaryChest->GetWorld()->MarkRedstoneDirty(ChunkX, ChunkZ); + + if (m_SecondaryChest != nullptr) + { + m_SecondaryChest->SetNumberOfPlayers(m_SecondaryChest->GetNumberOfPlayers() + 1); + cChunkDef::BlockToChunk(m_SecondaryChest->GetPosX(), m_SecondaryChest->GetPosZ(), ChunkX, ChunkZ); + m_SecondaryChest->GetWorld()->MarkRedstoneDirty(ChunkX, ChunkZ); + } + + cWindow::OpenedByPlayer(a_Player); +} + + + + + +void cChestWindow::DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Player, cSlotArea * a_ClickedArea, bool a_ShouldApply) +{ + cSlotAreas AreasInOrder; + + if (a_ClickedArea == m_SlotAreas[0]) + { + // Chest Area + AreasInOrder.push_back(m_SlotAreas[2]); /* Hotbar */ + AreasInOrder.push_back(m_SlotAreas[1]); /* Inventory */ + super::DistributeStackToAreas(a_ItemStack, a_Player, AreasInOrder, a_ShouldApply, true); + } + else + { + // Hotbar or Inventory + AreasInOrder.push_back(m_SlotAreas[0]); /* Chest */ + super::DistributeStackToAreas(a_ItemStack, a_Player, AreasInOrder, a_ShouldApply, false); + } +} + + + + diff --git a/src/UI/ChestWindow.h b/src/UI/ChestWindow.h new file mode 100644 index 000000000..a3b20cdd9 --- /dev/null +++ b/src/UI/ChestWindow.h @@ -0,0 +1,45 @@ + +// ChestWindow.h + +// Representing the UI window for the chest block + + + + + +#pragma once + +#include "Window.h" + + + + + +class cChestWindow : + public cWindow +{ + typedef cWindow super; + +public: + cChestWindow(cChestEntity * a_Chest); + + cChestWindow(cChestEntity * a_PrimaryChest, cChestEntity * a_SecondaryChest); + + ~cChestWindow(); + + virtual bool ClosedByPlayer(cPlayer & a_Player, bool a_CanRefuse) override; + + virtual void OpenedByPlayer(cPlayer & a_Player) override; + + virtual void DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Player, cSlotArea * a_ClickedArea, bool a_ShouldApply) override; + +protected: + cWorld * m_World; + int m_BlockX, m_BlockY, m_BlockZ; // Position of the chest, for the window-close packet + cChestEntity * m_PrimaryChest; + cChestEntity * m_SecondaryChest; +}; + + + + diff --git a/src/UI/CraftingWindow.cpp b/src/UI/CraftingWindow.cpp new file mode 100644 index 000000000..ca44056f9 --- /dev/null +++ b/src/UI/CraftingWindow.cpp @@ -0,0 +1,61 @@ + +// CraftingWindow.cpp + +// Representing the UI window for the crafting block + +#include "Globals.h" +#include "CraftingWindow.h" +#include "SlotArea.h" + + + + +cCraftingWindow::cCraftingWindow(int a_BlockX, int a_BlockY, int a_BlockZ) : + cWindow(wtWorkbench, "Crafting Table") +{ + m_SlotAreas.push_back(new cSlotAreaCrafting(3, *this)); + m_SlotAreas.push_back(new cSlotAreaInventory(*this)); + m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); +} + + + + + +void cCraftingWindow::DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Player, cSlotArea * a_ClickedArea, bool a_ShouldApply) +{ + cSlotAreas AreasInOrder; + + if (a_ClickedArea == m_SlotAreas[0]) + { + // Crafting Area + if (a_Slot == 0) + { + // Result Slot + AreasInOrder.push_back(m_SlotAreas[2]); /* Hotbar */ + AreasInOrder.push_back(m_SlotAreas[1]); /* Inventory */ + } + else + { + AreasInOrder.push_back(m_SlotAreas[1]); /* Inventory */ + AreasInOrder.push_back(m_SlotAreas[2]); /* Hotbar */ + } + super::DistributeStackToAreas(a_ItemStack, a_Player, AreasInOrder, a_ShouldApply, (a_Slot == 0)); + } + else if (a_ClickedArea == m_SlotAreas[1]) + { + // Inventory Area + AreasInOrder.push_back(m_SlotAreas[2]); /* Hotbar */ + super::DistributeStackToAreas(a_ItemStack, a_Player, AreasInOrder, a_ShouldApply, false); + } + else + { + // Hotbar + AreasInOrder.push_back(m_SlotAreas[1]); /* Inventory */ + super::DistributeStackToAreas(a_ItemStack, a_Player, AreasInOrder, a_ShouldApply, false); + } +} + + + + diff --git a/src/UI/CraftingWindow.h b/src/UI/CraftingWindow.h new file mode 100644 index 000000000..01b2da73a --- /dev/null +++ b/src/UI/CraftingWindow.h @@ -0,0 +1,31 @@ + +// CraftingWindow.h + +// Representing the UI window for the crafting block + + + + + +#pragma once + +#include "Window.h" + + + + + +class cCraftingWindow : + public cWindow +{ + typedef cWindow super; + +public: + cCraftingWindow(int a_BlockX, int a_BlockY, int a_BlockZ); + + virtual void DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Player, cSlotArea * a_ClickedArea, bool a_ShouldApply) override; +}; + + + + diff --git a/src/UI/DropSpenserWindow.cpp b/src/UI/DropSpenserWindow.cpp new file mode 100644 index 000000000..aeb7c64b7 --- /dev/null +++ b/src/UI/DropSpenserWindow.cpp @@ -0,0 +1,46 @@ + +// DropSpenserWindow.cpp + +// Representing the UI window for the dropper/dispenser block + +#include "Globals.h" +#include "DropSpenserWindow.h" + + + + + +cDropSpenserWindow::cDropSpenserWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cDropSpenserEntity * a_DropSpenser) : + cWindow(wtDropSpenser, (a_DropSpenser->GetBlockType() == E_BLOCK_DISPENSER) ? "Dispenser" : "Dropper") +{ + m_SlotAreas.push_back(new cSlotAreaItemGrid(a_DropSpenser->GetContents(), *this)); + m_SlotAreas.push_back(new cSlotAreaInventory(*this)); + m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); +} + + + + + +void cDropSpenserWindow::DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Player, cSlotArea * a_ClickedArea, bool a_ShouldApply) +{ + cSlotAreas AreasInOrder; + + if (a_ClickedArea == m_SlotAreas[0]) + { + // DropSpenser Area + AreasInOrder.push_back(m_SlotAreas[2]); /* Hotbar */ + AreasInOrder.push_back(m_SlotAreas[1]); /* Inventory */ + super::DistributeStackToAreas(a_ItemStack, a_Player, AreasInOrder, a_ShouldApply, true); + } + else + { + // Inventory or Hotbar + AreasInOrder.push_back(m_SlotAreas[0]); /* DropSpenser */ + super::DistributeStackToAreas(a_ItemStack, a_Player, AreasInOrder, a_ShouldApply, false); + } +} + + + + diff --git a/src/UI/DropSpenserWindow.h b/src/UI/DropSpenserWindow.h new file mode 100644 index 000000000..edff936e5 --- /dev/null +++ b/src/UI/DropSpenserWindow.h @@ -0,0 +1,32 @@ + +// DropSpenserWindow.h + +// Representing the UI window for the dropper/dispenser block + + + + + +#pragma once + +#include "Window.h" +#include "../BlockEntities/DropSpenserEntity.h" + + + + + +class cDropSpenserWindow : + public cWindow +{ + typedef cWindow super; + +public: + cDropSpenserWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cDropSpenserEntity * a_DropSpenser); + + virtual void DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Player, cSlotArea * a_ClickedArea, bool a_ShouldApply) override; +}; + + + + diff --git a/src/UI/EnchantingWindow.cpp b/src/UI/EnchantingWindow.cpp new file mode 100644 index 000000000..fe21ee83d --- /dev/null +++ b/src/UI/EnchantingWindow.cpp @@ -0,0 +1,100 @@ + +// EnchantingWindow.cpp + +// Representing the UI window for the enchanting block + +#include "Globals.h" +#include "EnchantingWindow.h" +#include "SlotArea.h" + + + + + +cEnchantingWindow::cEnchantingWindow(int a_BlockX, int a_BlockY, int a_BlockZ) : + cWindow(wtEnchantment, "Enchant"), + m_SlotArea(), + m_BlockX(a_BlockX), + m_BlockY(a_BlockY), + m_BlockZ(a_BlockZ) +{ + m_SlotArea = new cSlotAreaEnchanting(*this, m_BlockX, m_BlockY, m_BlockZ); + m_SlotAreas.push_back(m_SlotArea); + m_SlotAreas.push_back(new cSlotAreaInventory(*this)); + m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); +} + + + + + +void cEnchantingWindow::SetProperty(short a_Property, short a_Value, cPlayer & a_Player) +{ + if ((a_Property < 0) || ((size_t)a_Property >= ARRAYCOUNT(m_PropertyValue))) + { + ASSERT(!"a_Property is invalid"); + return; + } + + m_PropertyValue[a_Property] = a_Value; + super::SetProperty(a_Property, a_Value, a_Player); +} + + + + + + +void cEnchantingWindow::SetProperty(short a_Property, short a_Value) +{ + if ((a_Property < 0) || ((size_t)a_Property >= ARRAYCOUNT(m_PropertyValue))) + { + ASSERT(!"a_Property is invalid"); + return; + } + + m_PropertyValue[a_Property] = a_Value; + super::SetProperty(a_Property, a_Value); +} + + + + + +short cEnchantingWindow::GetPropertyValue(short a_Property) +{ + if ((a_Property < 0) || ((size_t)a_Property >= ARRAYCOUNT(m_PropertyValue))) + { + ASSERT(!"a_Property is invalid"); + return 0; + } + + return m_PropertyValue[a_Property]; +} + + + + + +void cEnchantingWindow::DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Player, cSlotArea * a_ClickedArea, bool a_ShouldApply) +{ + cSlotAreas AreasInOrder; + + if (a_ClickedArea == m_SlotAreas[0]) + { + // Enchanting Area + AreasInOrder.push_back(m_SlotAreas[2]); /* Hotbar */ + AreasInOrder.push_back(m_SlotAreas[1]); /* Inventory */ + super::DistributeStackToAreas(a_ItemStack, a_Player, AreasInOrder, a_ShouldApply, true); + } + else + { + // Inventory or Hotbar + AreasInOrder.push_back(m_SlotAreas[0]); /* Enchanting */ + super::DistributeStackToAreas(a_ItemStack, a_Player, AreasInOrder, a_ShouldApply, false); + } +} + + + + diff --git a/src/UI/EnchantingWindow.h b/src/UI/EnchantingWindow.h new file mode 100644 index 000000000..bf805c6c8 --- /dev/null +++ b/src/UI/EnchantingWindow.h @@ -0,0 +1,44 @@ + +// EnchantingWindow.h + +// Representing the UI window for the enchanting block + + + + + +#pragma once + +#include "Window.h" + + + + + +class cEnchantingWindow : + public cWindow +{ + typedef cWindow super; + +public: + cEnchantingWindow(int a_BlockX, int a_BlockY, int a_BlockZ); + + virtual void SetProperty(short a_Property, short a_Value, cPlayer & a_Player) override; + + virtual void SetProperty(short a_Property, short a_Value) override; + + /** Return the value of a property */ + short GetPropertyValue(short a_Property); + + virtual void DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Player, cSlotArea * a_ClickedArea, bool a_ShouldApply) override; + + cSlotArea * m_SlotArea; + +protected: + short m_PropertyValue[3]; + int m_BlockX, m_BlockY, m_BlockZ; +}; + + + + diff --git a/src/UI/EnderChestWindow.cpp b/src/UI/EnderChestWindow.cpp new file mode 100644 index 000000000..a5484468f --- /dev/null +++ b/src/UI/EnderChestWindow.cpp @@ -0,0 +1,71 @@ + +// EnderChestWindow.cpp + +// Representing the UI window for the enderchest block + +#include "Globals.h" +#include "../World.h" +#include "EnderChestWindow.h" +#include "SlotArea.h" + + + + + +cEnderChestWindow::cEnderChestWindow(cEnderChestEntity * a_EnderChest) : + cWindow(wtChest, "Ender Chest"), + m_World(a_EnderChest->GetWorld()), + m_BlockX(a_EnderChest->GetPosX()), + m_BlockY(a_EnderChest->GetPosY()), + m_BlockZ(a_EnderChest->GetPosZ()) +{ + m_SlotAreas.push_back(new cSlotAreaEnderChest(a_EnderChest, *this)); + m_SlotAreas.push_back(new cSlotAreaInventory(*this)); + m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); + + // Play the opening sound: + m_World->BroadcastSoundEffect("random.chestopen", (double)m_BlockX, (double)m_BlockY, (double)m_BlockZ, 1, 1); + + // Send out the chest-open packet: + m_World->BroadcastBlockAction(m_BlockX, m_BlockY, m_BlockZ, 1, 1, E_BLOCK_ENDER_CHEST); +} + + + + + +cEnderChestWindow::~cEnderChestWindow() +{ + // Send out the chest-close packet: + m_World->BroadcastBlockAction(m_BlockX, m_BlockY, m_BlockZ, 1, 0, E_BLOCK_ENDER_CHEST); + + // Play the closing sound + m_World->BroadcastSoundEffect("random.chestclosed", (double)m_BlockX, (double)m_BlockY, (double)m_BlockZ, 1, 1); +} + + + + + +void cEnderChestWindow::DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Player, cSlotArea * a_ClickedArea, bool a_ShouldApply) +{ + cSlotAreas AreasInOrder; + + if (a_ClickedArea == m_SlotAreas[0]) + { + // Chest Area + AreasInOrder.push_back(m_SlotAreas[2]); /* Hotbar */ + AreasInOrder.push_back(m_SlotAreas[1]); /* Inventory */ + super::DistributeStackToAreas(a_ItemStack, a_Player, AreasInOrder, a_ShouldApply, true); + } + else + { + // Hotbar or Inventory + AreasInOrder.push_back(m_SlotAreas[0]); /* Chest */ + super::DistributeStackToAreas(a_ItemStack, a_Player, AreasInOrder, a_ShouldApply, false); + } +} + + + + diff --git a/src/UI/EnderChestWindow.h b/src/UI/EnderChestWindow.h new file mode 100644 index 000000000..006a490bf --- /dev/null +++ b/src/UI/EnderChestWindow.h @@ -0,0 +1,38 @@ + +// EnderChestWindow.h + +// Representing the UI window for the enderchest block + + + + + +#pragma once + +#include "Window.h" +#include "../BlockEntities/EnderChestEntity.h" + + + + + +class cEnderChestWindow : + public cWindow +{ + typedef cWindow super; + +public: + cEnderChestWindow(cEnderChestEntity * a_EnderChest); + + ~cEnderChestWindow(); + + virtual void DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Player, cSlotArea * a_ClickedArea, bool a_ShouldApply) override; + +protected: + cWorld * m_World; + int m_BlockX, m_BlockY, m_BlockZ; // Position of the enderchest, for the window-close packet +}; + + + + diff --git a/src/UI/FurnaceWindow.cpp b/src/UI/FurnaceWindow.cpp new file mode 100644 index 000000000..132439ff3 --- /dev/null +++ b/src/UI/FurnaceWindow.cpp @@ -0,0 +1,74 @@ + +// FurnaceWindow.cpp + +// Representing the UI window for the furnace block + +#include "Globals.h" +#include "FurnaceWindow.h" +#include "SlotArea.h" +#include "../FurnaceRecipe.h" +#include "../Root.h" + + + + + +cFurnaceWindow::cFurnaceWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cFurnaceEntity * a_Furnace) : + cWindow(wtFurnace, "Furnace") +{ + m_SlotAreas.push_back(new cSlotAreaFurnace(a_Furnace, *this)); + m_SlotAreas.push_back(new cSlotAreaInventory(*this)); + m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); +} + + + + + +void cFurnaceWindow::DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Player, cSlotArea * a_ClickedArea, bool a_ShouldApply) +{ + cSlotAreas AreasInOrder; + + if (a_ClickedArea == m_SlotAreas[0]) + { + // Furnace Area + if (a_Slot == 2) + { + // Result Slot + AreasInOrder.push_back(m_SlotAreas[2]); /* Hotbar */ + AreasInOrder.push_back(m_SlotAreas[1]); /* Inventory */ + super::DistributeStackToAreas(a_ItemStack, a_Player, AreasInOrder, a_ShouldApply, true); + } + else + { + // Furnace Input/Fuel Slot + AreasInOrder.push_back(m_SlotAreas[1]); /* Inventory */ + AreasInOrder.push_back(m_SlotAreas[2]); /* Hotbar */ + super::DistributeStackToAreas(a_ItemStack, a_Player, AreasInOrder, a_ShouldApply, false); + } + } + else + { + cFurnaceRecipe * FurnaceRecipes = cRoot::Get()->GetFurnaceRecipe(); + if ((FurnaceRecipes->GetRecipeFrom(a_ItemStack) != nullptr) || (FurnaceRecipes->IsFuel(a_ItemStack))) + { + // The item is a valid input item or fuel + AreasInOrder.push_back(m_SlotAreas[0]); /* Furnace Area */ + } + else if (a_ClickedArea == m_SlotAreas[1]) + { + // Inventory Area + AreasInOrder.push_back(m_SlotAreas[2]); /* Hotbar */ + } + else + { + // Hotbar Area + AreasInOrder.push_back(m_SlotAreas[1]); /* Inventory */ + } + super::DistributeStackToAreas(a_ItemStack, a_Player, AreasInOrder, a_ShouldApply, false); + } +} + + + + diff --git a/src/UI/FurnaceWindow.h b/src/UI/FurnaceWindow.h new file mode 100644 index 000000000..845505f8e --- /dev/null +++ b/src/UI/FurnaceWindow.h @@ -0,0 +1,32 @@ + +// FurnaceWindow.h + +// Representing the UI window for the furnace block + + + + + +#pragma once + +#include "Window.h" + + + + + +class cFurnaceWindow : + public cWindow +{ + typedef cWindow super; + +public: + cFurnaceWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cFurnaceEntity * a_Furnace); + + virtual void DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Player, cSlotArea * a_ClickedArea, bool a_ShouldApply) override; + +}; + + + + diff --git a/src/UI/HopperWindow.cpp b/src/UI/HopperWindow.cpp new file mode 100644 index 000000000..79f0767e8 --- /dev/null +++ b/src/UI/HopperWindow.cpp @@ -0,0 +1,48 @@ + +// HopperWindow.cpp + +// Representing the UI window for the hopper block + +#include "Globals.h" +#include "../BlockEntities/HopperEntity.h" +#include "HopperWindow.h" +#include "../BlockEntities/DropperEntity.h" + + + + + +cHopperWindow::cHopperWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cHopperEntity * a_Hopper) : + super(wtHopper, "Hopper") +{ + m_SlotAreas.push_back(new cSlotAreaItemGrid(a_Hopper->GetContents(), *this)); + m_SlotAreas.push_back(new cSlotAreaInventory(*this)); + m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); +} + + + + + +void cHopperWindow::DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Player, cSlotArea * a_ClickedArea, bool a_ShouldApply) +{ + cSlotAreas AreasInOrder; + + if (a_ClickedArea == m_SlotAreas[0]) + { + // Hopper Area + AreasInOrder.push_back(m_SlotAreas[2]); /* Hotbar */ + AreasInOrder.push_back(m_SlotAreas[1]); /* Inventory */ + super::DistributeStackToAreas(a_ItemStack, a_Player, AreasInOrder, a_ShouldApply, true); + } + else + { + // Inventory or Hotbar + AreasInOrder.push_back(m_SlotAreas[0]); /* Hopper */ + super::DistributeStackToAreas(a_ItemStack, a_Player, AreasInOrder, a_ShouldApply, false); + } +} + + + + diff --git a/src/UI/HopperWindow.h b/src/UI/HopperWindow.h new file mode 100644 index 000000000..2dec08666 --- /dev/null +++ b/src/UI/HopperWindow.h @@ -0,0 +1,32 @@ + +// HopperWindow.h + +// Representing the UI window for the hopper block + + + + + +#pragma once + +#include "Window.h" + + + + + +class cHopperWindow : + public cWindow +{ + typedef cWindow super; + +public: + cHopperWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cHopperEntity * a_Hopper); + + virtual void DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Player, cSlotArea * a_ClickedArea, bool a_ShouldApply) override; + +}; + + + + diff --git a/src/UI/InventoryWindow.cpp b/src/UI/InventoryWindow.cpp new file mode 100644 index 000000000..0f876e559 --- /dev/null +++ b/src/UI/InventoryWindow.cpp @@ -0,0 +1,73 @@ + +// InventoryWindow.cpp + +// Representing the UI window for the player inventory + +#include "Globals.h" +#include "InventoryWindow.h" +#include "SlotArea.h" + + + + + +cInventoryWindow::cInventoryWindow(cPlayer & a_Player) : + cWindow(wtInventory, "Inventory"), + m_Player(a_Player) +{ + m_SlotAreas.push_back(new cSlotAreaCrafting(2, *this)); // The creative inventory doesn't display it, but it's still counted into slot numbers + m_SlotAreas.push_back(new cSlotAreaArmor(*this)); + m_SlotAreas.push_back(new cSlotAreaInventory(*this)); + m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); +} + + + + + +void cInventoryWindow::DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Player, cSlotArea * a_ClickedArea, bool a_ShouldApply) +{ + cSlotAreas AreasInOrder; + + if (a_ClickedArea == m_SlotAreas[0]) + { + // Crafting Area + if (a_Slot == 0) + { + // Result Slot + AreasInOrder.push_back(m_SlotAreas[3]); /* Hotbar */ + AreasInOrder.push_back(m_SlotAreas[2]); /* Inventory */ + } + else + { + AreasInOrder.push_back(m_SlotAreas[2]); /* Inventory */ + AreasInOrder.push_back(m_SlotAreas[3]); /* Hotbar */ + } + super::DistributeStackToAreas(a_ItemStack, a_Player, AreasInOrder, a_ShouldApply, (a_Slot == 0)); + } + else if (a_ClickedArea == m_SlotAreas[1]) + { + // Armor Area + AreasInOrder.push_back(m_SlotAreas[2]); /* Inventory */ + AreasInOrder.push_back(m_SlotAreas[3]); /* Hotbar */ + super::DistributeStackToAreas(a_ItemStack, a_Player, AreasInOrder, a_ShouldApply, false); + } + else if (a_ClickedArea == m_SlotAreas[2]) + { + // Inventory Area + AreasInOrder.push_back(m_SlotAreas[1]); /* Armor */ + AreasInOrder.push_back(m_SlotAreas[3]); /* Hotbar */ + super::DistributeStackToAreas(a_ItemStack, a_Player, AreasInOrder, a_ShouldApply, false); + } + else + { + // Hotbar + AreasInOrder.push_back(m_SlotAreas[1]); /* Armor */ + AreasInOrder.push_back(m_SlotAreas[2]); /* Inventory */ + super::DistributeStackToAreas(a_ItemStack, a_Player, AreasInOrder, a_ShouldApply, false); + } +} + + + + diff --git a/src/UI/InventoryWindow.h b/src/UI/InventoryWindow.h new file mode 100644 index 000000000..10952d37f --- /dev/null +++ b/src/UI/InventoryWindow.h @@ -0,0 +1,34 @@ + +// InventoryWindow.h + +// Representing the UI window for the player inventory + + + + + +#pragma once + +#include "Window.h" + + + + + +class cInventoryWindow : + public cWindow +{ + typedef cWindow super; + +public: + cInventoryWindow(cPlayer & a_Player); + + virtual void DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Player, cSlotArea * a_ClickedArea, bool a_ShouldApply) override; + +protected: + cPlayer & m_Player; +}; + + + + diff --git a/src/UI/MinecartWithChestWindow.h b/src/UI/MinecartWithChestWindow.h new file mode 100644 index 000000000..a2b5283a6 --- /dev/null +++ b/src/UI/MinecartWithChestWindow.h @@ -0,0 +1,67 @@ + +// MinecartWithChestWindow.h + +// Representing the UI window for the minecart chest entity + + + + + +#pragma once + +#include "Window.h" +#include "../Entities/Minecart.h" + + + + + +class cMinecartWithChestWindow : + public cWindow +{ + typedef cWindow super; + +public: + cMinecartWithChestWindow(cMinecartWithChest * a_ChestCart) : + cWindow(wtChest, "Minecart with Chest"), + m_ChestCart(a_ChestCart) + { + m_SlotAreas.push_back(new cSlotAreaMinecartWithChest(a_ChestCart, *this)); + m_SlotAreas.push_back(new cSlotAreaInventory(*this)); + m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); + + a_ChestCart->GetWorld()->BroadcastSoundEffect("random.chestopen", a_ChestCart->GetPosX(), a_ChestCart->GetPosY(), a_ChestCart->GetPosZ(), 1, 1); + } + + virtual void DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Player, cSlotArea* a_ClickedArea, bool a_ShouldApply) override + { + cSlotAreas AreasInOrder; + + if (a_ClickedArea == m_SlotAreas[0]) + { + // Chest Area + AreasInOrder.push_back(m_SlotAreas[2]); /* Hotbar */ + AreasInOrder.push_back(m_SlotAreas[1]); /* Inventory */ + super::DistributeStackToAreas(a_ItemStack, a_Player, AreasInOrder, a_ShouldApply, true); + } + else + { + // Hotbar or Inventory + AreasInOrder.push_back(m_SlotAreas[0]); /* Chest */ + super::DistributeStackToAreas(a_ItemStack, a_Player, AreasInOrder, a_ShouldApply, false); + } + } + + + ~cMinecartWithChestWindow() + { + m_ChestCart->GetWorld()->BroadcastSoundEffect("random.chestclosed", m_ChestCart->GetPosX(), m_ChestCart->GetPosY(), m_ChestCart->GetPosZ(), 1, 1); + } + +private: + cMinecartWithChest * m_ChestCart; +}; + + + + diff --git a/src/UI/SlotArea.cpp b/src/UI/SlotArea.cpp index e784569d9..37683a8e5 100644 --- a/src/UI/SlotArea.cpp +++ b/src/UI/SlotArea.cpp @@ -1,3 +1,4 @@ + // SlotArea.cpp // Implements the cSlotArea class and its descendants @@ -12,6 +13,7 @@ #include "../BlockEntities/FurnaceEntity.h" #include "../Entities/Minecart.h" #include "../Items/ItemHandler.h" +#include "AnvilWindow.h" #include "Window.h" #include "../CraftingRecipes.h" #include "../Root.h" @@ -205,7 +207,7 @@ void cSlotArea::ShiftClicked(cPlayer & a_Player, int a_SlotNum, const cItem & a_ { // Make a copy of the slot, distribute it among the other areas, then update the slot to contain the leftover: cItem Slot(*GetSlot(a_SlotNum, a_Player)); - m_ParentWindow.DistributeStack(Slot, a_Player, this, true); + m_ParentWindow.DistributeStack(Slot, a_SlotNum, a_Player, this, true); if (Slot.IsEmpty()) { // Empty the slot completely, the client doesn't like left-over ItemType with zero count @@ -340,31 +342,31 @@ void cSlotArea::OnPlayerRemoved(cPlayer & a_Player) -void cSlotArea::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots) +void cSlotArea::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots, bool a_BackFill) { for (int i = 0; i < m_NumSlots; i++) { - const cItem * Slot = GetSlot(i, a_Player); + int SlotNum = (a_BackFill) ? (m_NumSlots - 1 - i) : i; + + const cItem * Slot = GetSlot(SlotNum, a_Player); if (!Slot->IsEqual(a_ItemStack) && (!Slot->IsEmpty() || a_KeepEmptySlots)) { // Different items continue; } - int NumFit = ItemHandler(Slot->m_ItemType)->GetMaxStackSize() - Slot->m_ItemCount; + char NumFit = ItemHandler(Slot->m_ItemType)->GetMaxStackSize() - Slot->m_ItemCount; if (NumFit <= 0) { // Full stack already continue; } - if (NumFit > a_ItemStack.m_ItemCount) - { - NumFit = a_ItemStack.m_ItemCount; - } + NumFit = std::min(NumFit, a_ItemStack.m_ItemCount); + if (a_ShouldApply) { cItem NewSlot(a_ItemStack); NewSlot.m_ItemCount = Slot->m_ItemCount + NumFit; - SetSlot(i, a_Player, NewSlot); + SetSlot(SlotNum, a_Player, NewSlot); } a_ItemStack.m_ItemCount -= NumFit; if (a_ItemStack.IsEmpty()) @@ -589,12 +591,13 @@ void cSlotAreaCrafting::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & -void cSlotAreaCrafting::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots) +void cSlotAreaCrafting::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots, bool a_BackFill) { UNUSED(a_ItemStack); UNUSED(a_Player); UNUSED(a_ShouldApply); UNUSED(a_KeepEmptySlots); + UNUSED(a_BackFill); } @@ -656,7 +659,7 @@ void cSlotAreaCrafting::ShiftClickedResult(cPlayer & a_Player) { // Try distributing the result. If it fails, bail out: cItem ResultCopy(Result); - m_ParentWindow.DistributeStack(ResultCopy, a_Player, this, false); + m_ParentWindow.DistributeStack(ResultCopy, 0, a_Player, this, false); if (!ResultCopy.IsEmpty()) { // Couldn't distribute all of it. Bail out @@ -665,7 +668,7 @@ void cSlotAreaCrafting::ShiftClickedResult(cPlayer & a_Player) // Distribute the result, this time for real: ResultCopy = Result; - m_ParentWindow.DistributeStack(ResultCopy, a_Player, this, true); + m_ParentWindow.DistributeStack(ResultCopy, 0, a_Player, this, true); // Remove the ingredients from the crafting grid and update the recipe: cCraftingRecipe & Recipe = GetRecipeForPlayer(a_Player); @@ -769,7 +772,7 @@ void cSlotAreaCrafting::HandleCraftItem(const cItem & a_Result, cPlayer & a_Play //////////////////////////////////////////////////////////////////////////////// // cSlotAreaAnvil: -cSlotAreaAnvil::cSlotAreaAnvil(cAnvilWindow & a_ParentWindow) : +cSlotAreaAnvil::cSlotAreaAnvil(cWindow & a_ParentWindow) : cSlotAreaTemporary(3, a_ParentWindow), m_MaximumCost(0), m_StackSizeToBeUsedInRepair(0) @@ -894,7 +897,7 @@ void cSlotAreaAnvil::ShiftClicked(cPlayer & a_Player, int a_SlotNum, const cItem return; } - m_ParentWindow.DistributeStack(Slot, a_Player, this, true); + m_ParentWindow.DistributeStack(Slot, a_SlotNum, a_Player, this, true); if (Slot.IsEmpty()) { Slot.Empty(); @@ -910,31 +913,31 @@ void cSlotAreaAnvil::ShiftClicked(cPlayer & a_Player, int a_SlotNum, const cItem -void cSlotAreaAnvil::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots) +void cSlotAreaAnvil::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots, bool a_BackFill) { for (int i = 0; i < 2; i++) { - const cItem * Slot = GetSlot(i, a_Player); + int SlotNum = (a_BackFill) ? (2 - 1 - i) : i; + + const cItem * Slot = GetSlot(SlotNum, a_Player); if (!Slot->IsEqual(a_ItemStack) && (!Slot->IsEmpty() || a_KeepEmptySlots)) { // Different items continue; } - int NumFit = ItemHandler(Slot->m_ItemType)->GetMaxStackSize() - Slot->m_ItemCount; + char NumFit = ItemHandler(Slot->m_ItemType)->GetMaxStackSize() - Slot->m_ItemCount; if (NumFit <= 0) { // Full stack already continue; } - if (NumFit > a_ItemStack.m_ItemCount) - { - NumFit = a_ItemStack.m_ItemCount; - } + NumFit = std::min(NumFit, a_ItemStack.m_ItemCount); + if (a_ShouldApply) { cItem NewSlot(a_ItemStack); NewSlot.m_ItemCount = Slot->m_ItemCount + NumFit; - SetSlot(i, a_Player, NewSlot); + SetSlot(SlotNum, a_Player, NewSlot); } a_ItemStack.m_ItemCount -= NumFit; if (a_ItemStack.IsEmpty()) @@ -1051,7 +1054,7 @@ void cSlotAreaAnvil::UpdateResult(cPlayer & a_Player) cItem SecondInput(*GetSlot(1, a_Player)); cItem Output(*GetSlot(2, a_Player)); - if (Input.IsEmpty() && !Output.IsEmpty()) + if (Input.IsEmpty()) { Output.Empty(); SetSlot(2, a_Player, Output); @@ -1335,7 +1338,7 @@ void cSlotAreaBeacon::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ -void cSlotAreaBeacon::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots) +void cSlotAreaBeacon::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots, bool a_BackFill) { const cItem * Slot = GetSlot(0, a_Player); if (!Slot->IsEmpty() || !IsPlaceableItem(a_ItemStack.m_ItemType) || (a_ItemStack.m_ItemCount != 1)) @@ -1390,13 +1393,12 @@ void cSlotAreaBeacon::OnSlotChanged(cItemGrid * a_ItemGrid, int a_SlotNum) //////////////////////////////////////////////////////////////////////////////// // cSlotAreaEnchanting: -cSlotAreaEnchanting::cSlotAreaEnchanting(cEnchantingWindow & a_ParentWindow, int a_BlockX, int a_BlockY, int a_BlockZ) : +cSlotAreaEnchanting::cSlotAreaEnchanting(cWindow & a_ParentWindow, int a_BlockX, int a_BlockY, int a_BlockZ) : cSlotAreaTemporary(1, a_ParentWindow), m_BlockX(a_BlockX), m_BlockY(a_BlockY), m_BlockZ(a_BlockZ) { - a_ParentWindow.m_SlotArea = this; } @@ -1503,7 +1505,7 @@ void cSlotAreaEnchanting::Clicked(cPlayer & a_Player, int a_SlotNum, eClickActio -void cSlotAreaEnchanting::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_Apply, bool a_KeepEmptySlots) +void cSlotAreaEnchanting::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_Apply, bool a_KeepEmptySlots, bool a_BackFill) { const cItem * Slot = GetSlot(0, a_Player); if (!Slot->IsEmpty()) @@ -1833,38 +1835,50 @@ void cSlotAreaFurnace::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a -void cSlotAreaFurnace::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots) +void cSlotAreaFurnace::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots, bool a_BackFill) { - for (int i = 0; i < 2; i++) + int SlotNum; + cFurnaceRecipe * FurnaceRecipes = cRoot::Get()->GetFurnaceRecipe(); + + if (FurnaceRecipes->GetRecipeFrom(a_ItemStack) != nullptr) { - const cItem * Slot = GetSlot(i, a_Player); - if (!Slot->IsEqual(a_ItemStack) && (!Slot->IsEmpty() || a_KeepEmptySlots)) - { - // Different items - continue; - } - int NumFit = ItemHandler(Slot->m_ItemType)->GetMaxStackSize() - Slot->m_ItemCount; - if (NumFit <= 0) - { - // Full stack already - continue; - } - if (NumFit > a_ItemStack.m_ItemCount) - { - NumFit = a_ItemStack.m_ItemCount; - } - if (a_ShouldApply) - { - cItem NewSlot(a_ItemStack); - NewSlot.m_ItemCount = Slot->m_ItemCount + NumFit; - SetSlot(i, a_Player, NewSlot); - } - a_ItemStack.m_ItemCount -= NumFit; - if (a_ItemStack.IsEmpty()) - { - return; - } - } // for i - Slots + SlotNum = 0; + } + else if (FurnaceRecipes->IsFuel(a_ItemStack)) + { + SlotNum = 1; + } + else + { + return; + } + + const cItem * Slot = GetSlot(SlotNum, a_Player); + if (!Slot->IsEqual(a_ItemStack) && (!Slot->IsEmpty() || a_KeepEmptySlots)) + { + // Different items + return; + } + + char NumFit = ItemHandler(Slot->m_ItemType)->GetMaxStackSize() - Slot->m_ItemCount; + if (NumFit <= 0) + { + // Full stack already + return; + } + NumFit = std::min(NumFit, a_ItemStack.m_ItemCount); + + if (a_ShouldApply) + { + cItem NewSlot(a_ItemStack); + NewSlot.m_ItemCount = Slot->m_ItemCount + NumFit; + SetSlot(SlotNum, a_Player, NewSlot); + } + a_ItemStack.m_ItemCount -= NumFit; + if (a_ItemStack.IsEmpty()) + { + return; + } } @@ -2013,7 +2027,7 @@ void cSlotAreaInventoryBase::SetSlot(int a_SlotNum, cPlayer & a_Player, const cI //////////////////////////////////////////////////////////////////////////////// // cSlotAreaArmor: -void cSlotAreaArmor::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots) +void cSlotAreaArmor::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots, bool a_BackFill) { if (ItemCategory::IsHelmet(a_ItemStack.m_ItemType) && GetSlot(0, a_Player)->IsEmpty()) { diff --git a/src/UI/SlotArea.h b/src/UI/SlotArea.h index 1eeeb9836..e39d372c9 100644 --- a/src/UI/SlotArea.h +++ b/src/UI/SlotArea.h @@ -17,12 +17,10 @@ class cWindow; class cPlayer; class cBeaconEntity; class cChestEntity; -class cDropSpenserEntity; class cEnderChestEntity; class cFurnaceEntity; class cMinecartWithChest; class cCraftingRecipe; -class cEnchantingWindow; class cWorld; @@ -73,7 +71,7 @@ public: if a_ShouldApply is false, only a_ItemStack is modified to reflect the number of fits (for fit-testing purposes) If a_KeepEmptySlots is true, empty slots will be skipped and won't be filled */ - virtual void DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots); + virtual void DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots, bool a_BackFill); /// Called on DblClicking to collect all stackable items into hand. /// The items are accumulated in a_Dragging and removed from the slots immediately. @@ -158,7 +156,7 @@ public: } /** Distributing the stack is allowed only for compatible items (helmets into helmet slot etc.) */ - virtual void DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots) override; + virtual void DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots, bool a_BackFill) override; /** Called when a player clicks in the window. Parameters taken from the click packet. */ virtual void Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem) override; @@ -246,7 +244,7 @@ public: virtual void SetSlot (int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) override; // Distributing items into this area is completely disabled - virtual void DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots) override; + virtual void DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots, bool a_BackFill) override; protected: @@ -285,12 +283,12 @@ class cSlotAreaAnvil : typedef cSlotAreaTemporary super; public: - cSlotAreaAnvil(cAnvilWindow & a_ParentWindow); + cSlotAreaAnvil(cWindow & a_ParentWindow); // cSlotArea overrides: virtual void Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem) override; virtual void ShiftClicked(cPlayer & a_Player, int a_SlotNum, const cItem & a_ClickedItem) override; - virtual void DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots) override; + virtual void DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots, bool a_BackFill) override; // cSlotAreaTemporary overrides: virtual void OnPlayerRemoved(cPlayer & a_Player) override; @@ -326,10 +324,10 @@ public: cSlotAreaBeacon(cBeaconEntity * a_Beacon, cWindow & a_ParentWindow); virtual ~cSlotAreaBeacon(); - bool IsPlaceableItem(short a_ItemType); + static bool IsPlaceableItem(short a_ItemType); virtual void Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem) override; - virtual void DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots) override; + virtual void DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots, bool a_BackFill) override; virtual const cItem * GetSlot(int a_SlotNum, cPlayer & a_Player) const override; virtual void SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) override; @@ -350,11 +348,11 @@ class cSlotAreaEnchanting : typedef cSlotAreaTemporary super; public: - cSlotAreaEnchanting(cEnchantingWindow & a_ParentWindow, int a_BlockX, int a_BlockY, int a_BlockZ); + cSlotAreaEnchanting(cWindow & a_ParentWindow, int a_BlockX, int a_BlockY, int a_BlockZ); // cSlotArea overrides: virtual void Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem) override; - virtual void DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots) override; + virtual void DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots, bool a_BackFill) override; virtual void SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) override; // cSlotAreaTemporary overrides: @@ -439,7 +437,7 @@ public: virtual ~cSlotAreaFurnace(); virtual void Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem) override; - virtual void DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots) override; + virtual void DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots, bool a_BackFill) override; virtual const cItem * GetSlot(int a_SlotNum, cPlayer & a_Player) const override; virtual void SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) override; diff --git a/src/UI/Window.cpp b/src/UI/Window.cpp index 1598dd3e7..bb2e2a807 100644 --- a/src/UI/Window.cpp +++ b/src/UI/Window.cpp @@ -32,7 +32,6 @@ cWindow::cWindow(WindowType a_WindowType, const AString & a_WindowTitle) : m_WindowType(a_WindowType), m_WindowTitle(a_WindowTitle), m_IsDestroyed(false), - m_ShouldDistributeToHotbarFirst(true), m_Owner(nullptr) { if (a_WindowType == wtInventory) @@ -392,43 +391,23 @@ bool cWindow::ForEachClient(cItemCallback<cClientHandle> & a_Callback) -void cWindow::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, cSlotArea * a_ExcludeArea, bool a_ShouldApply) +void cWindow::DistributeStackToAreas(cItem & a_ItemStack, cPlayer & a_Player, cSlotAreas & a_AreasInOrder, bool a_ShouldApply, bool a_BackFill) { - // Ask each slot area to take as much of the stack as it can. - // First ask only slots that already have the same kind of item - // Then ask any remaining slots - for (int Pass = 0; Pass < 2; ++Pass) + /* Ask each slot area to take as much of the stack as it can. + First ask only slots that already have the same kind of item + Then ask any remaining slots */ + for (size_t Pass = 0; Pass < 2; Pass++) { - if (m_ShouldDistributeToHotbarFirst) + for (auto SlotArea : a_AreasInOrder) { - // First distribute into the hotbar: - if (a_ExcludeArea != m_SlotAreas.back()) - { - m_SlotAreas.back()->DistributeStack(a_ItemStack, a_Player, a_ShouldApply, (Pass == 0)); - if (a_ItemStack.IsEmpty()) - { - // Distributed it all - return; - } - } - } - - // The distribute to all other areas: - cSlotAreas::iterator end = m_ShouldDistributeToHotbarFirst ? (m_SlotAreas.end() - 1) : m_SlotAreas.end(); - for (cSlotAreas::iterator itr = m_SlotAreas.begin(); itr != end; ++itr) - { - if (*itr == a_ExcludeArea) - { - continue; - } - (*itr)->DistributeStack(a_ItemStack, a_Player, a_ShouldApply, (Pass == 0)); + SlotArea->DistributeStack(a_ItemStack, a_Player, a_ShouldApply, (Pass == 0), a_BackFill); if (a_ItemStack.IsEmpty()) { // Distributed it all return; } - } // for itr - m_SlotAreas[] - } // for Pass - repeat twice + } + } } @@ -779,401 +758,3 @@ void cWindow::SetProperty(short a_Property, short a_Value, cPlayer & a_Player) - -//////////////////////////////////////////////////////////////////////////////// -// cInventoryWindow: - -cInventoryWindow::cInventoryWindow(cPlayer & a_Player) : - cWindow(wtInventory, "Inventory"), - m_Player(a_Player) -{ - m_SlotAreas.push_back(new cSlotAreaCrafting(2, *this)); // The creative inventory doesn't display it, but it's still counted into slot numbers - m_SlotAreas.push_back(new cSlotAreaArmor(*this)); - m_SlotAreas.push_back(new cSlotAreaInventory(*this)); - m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); -} - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cCraftingWindow: - -cCraftingWindow::cCraftingWindow(int a_BlockX, int a_BlockY, int a_BlockZ) : - cWindow(wtWorkbench, "Crafting Table") -{ - m_SlotAreas.push_back(new cSlotAreaCrafting(3, *this)); - m_SlotAreas.push_back(new cSlotAreaInventory(*this)); - m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); -} - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cAnvilWindow: - -cAnvilWindow::cAnvilWindow(int a_BlockX, int a_BlockY, int a_BlockZ) : - cWindow(wtAnvil, "Repair"), - m_RepairedItemName(""), - m_BlockX(a_BlockX), - m_BlockY(a_BlockY), - m_BlockZ(a_BlockZ) -{ - m_AnvilSlotArea = new cSlotAreaAnvil(*this); - m_SlotAreas.push_back(m_AnvilSlotArea); - m_SlotAreas.push_back(new cSlotAreaInventory(*this)); - m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); -} - - - - - -void cAnvilWindow::SetRepairedItemName(const AString & a_Name, cPlayer * a_Player) -{ - m_RepairedItemName = a_Name; - - if (a_Player != nullptr) - { - m_AnvilSlotArea->UpdateResult(*a_Player); - } -} - - - - - -void cAnvilWindow::GetBlockPos(int & a_PosX, int & a_PosY, int & a_PosZ) -{ - a_PosX = m_BlockX; - a_PosY = m_BlockY; - a_PosZ = m_BlockZ; -} - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cBeaconWindow: - -cBeaconWindow::cBeaconWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cBeaconEntity * a_Beacon) : - cWindow(wtBeacon, "Beacon"), - m_Beacon(a_Beacon) -{ - m_ShouldDistributeToHotbarFirst = true; - m_SlotAreas.push_back(new cSlotAreaBeacon(m_Beacon, *this)); - m_SlotAreas.push_back(new cSlotAreaInventory(*this)); - m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); -} - - - - - -void cBeaconWindow::OpenedByPlayer(cPlayer & a_Player) -{ - super::OpenedByPlayer(a_Player); - - a_Player.GetClientHandle()->SendWindowProperty(*this, 0, m_Beacon->GetBeaconLevel()); - a_Player.GetClientHandle()->SendWindowProperty(*this, 1, m_Beacon->GetPrimaryEffect()); - a_Player.GetClientHandle()->SendWindowProperty(*this, 2, m_Beacon->GetSecondaryEffect()); -} - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cEnchantingWindow: - -cEnchantingWindow::cEnchantingWindow(int a_BlockX, int a_BlockY, int a_BlockZ) : - cWindow(wtEnchantment, "Enchant"), - m_SlotArea(), - m_BlockX(a_BlockX), - m_BlockY(a_BlockY), - m_BlockZ(a_BlockZ) -{ - m_SlotAreas.push_back(new cSlotAreaEnchanting(*this, m_BlockX, m_BlockY, m_BlockZ)); - m_SlotAreas.push_back(new cSlotAreaInventory(*this)); - m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); -} - - - - - -void cEnchantingWindow::SetProperty(short a_Property, short a_Value) -{ - if ((a_Property < 0) || ((size_t)a_Property >= ARRAYCOUNT(m_PropertyValue))) - { - ASSERT(!"a_Property is invalid"); - return; - } - - m_PropertyValue[a_Property] = a_Value; - super::SetProperty(a_Property, a_Value); -} - - - - - -void cEnchantingWindow::SetProperty(short a_Property, short a_Value, cPlayer & a_Player) -{ - if ((a_Property < 0) || ((size_t)a_Property >= ARRAYCOUNT(m_PropertyValue))) - { - ASSERT(!"a_Property is invalid"); - return; - } - - m_PropertyValue[a_Property] = a_Value; - super::SetProperty(a_Property, a_Value, a_Player); -} - - - - - -short cEnchantingWindow::GetPropertyValue(short a_Property) -{ - if ((a_Property < 0) || ((size_t)a_Property >= ARRAYCOUNT(m_PropertyValue))) - { - ASSERT(!"a_Property is invalid"); - return 0; - } - - return m_PropertyValue[a_Property]; -} - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cChestWindow: - -cChestWindow::cChestWindow(cChestEntity * a_Chest) : - cWindow(wtChest, (a_Chest->GetBlockType() == E_BLOCK_CHEST) ? "Chest" : "Trapped Chest"), - m_World(a_Chest->GetWorld()), - m_BlockX(a_Chest->GetPosX()), - m_BlockY(a_Chest->GetPosY()), - m_BlockZ(a_Chest->GetPosZ()), - m_PrimaryChest(a_Chest), - m_SecondaryChest(nullptr) -{ - m_SlotAreas.push_back(new cSlotAreaChest(a_Chest, *this)); - m_SlotAreas.push_back(new cSlotAreaInventory(*this)); - m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); - - // Play the opening sound: - m_World->BroadcastSoundEffect("random.chestopen", (double)m_BlockX, (double)m_BlockY, (double)m_BlockZ, 1, 1); - - // Send out the chest-open packet: - m_World->BroadcastBlockAction(m_BlockX, m_BlockY, m_BlockZ, 1, 1, a_Chest->GetBlockType()); -} - - - - - -cChestWindow::cChestWindow(cChestEntity * a_PrimaryChest, cChestEntity * a_SecondaryChest) : - cWindow(wtChest, (a_PrimaryChest->GetBlockType() == E_BLOCK_CHEST) ? "Double Chest" : "Double Trapped Chest"), - m_World(a_PrimaryChest->GetWorld()), - m_BlockX(a_PrimaryChest->GetPosX()), - m_BlockY(a_PrimaryChest->GetPosY()), - m_BlockZ(a_PrimaryChest->GetPosZ()), - m_PrimaryChest(a_PrimaryChest), - m_SecondaryChest(a_SecondaryChest) -{ - m_SlotAreas.push_back(new cSlotAreaDoubleChest(a_PrimaryChest, a_SecondaryChest, *this)); - m_SlotAreas.push_back(new cSlotAreaInventory(*this)); - m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); - - m_ShouldDistributeToHotbarFirst = false; - - // Play the opening sound: - m_World->BroadcastSoundEffect("random.chestopen", (double)m_BlockX, (double)m_BlockY, (double)m_BlockZ, 1, 1); - - // Send out the chest-open packet: - m_World->BroadcastBlockAction(m_BlockX, m_BlockY, m_BlockZ, 1, 1, a_PrimaryChest->GetBlockType()); -} - - - - - -void cChestWindow::OpenedByPlayer(cPlayer & a_Player) -{ - int ChunkX, ChunkZ; - - m_PrimaryChest->SetNumberOfPlayers(m_PrimaryChest->GetNumberOfPlayers() + 1); - cChunkDef::BlockToChunk(m_PrimaryChest->GetPosX(), m_PrimaryChest->GetPosZ(), ChunkX, ChunkZ); - m_PrimaryChest->GetWorld()->MarkRedstoneDirty(ChunkX, ChunkZ); - - if (m_SecondaryChest != nullptr) - { - m_SecondaryChest->SetNumberOfPlayers(m_SecondaryChest->GetNumberOfPlayers() + 1); - cChunkDef::BlockToChunk(m_SecondaryChest->GetPosX(), m_SecondaryChest->GetPosZ(), ChunkX, ChunkZ); - m_SecondaryChest->GetWorld()->MarkRedstoneDirty(ChunkX, ChunkZ); - } - - cWindow::OpenedByPlayer(a_Player); -} - - - - - -bool cChestWindow::ClosedByPlayer(cPlayer & a_Player, bool a_CanRefuse) -{ - int ChunkX, ChunkZ; - - m_PrimaryChest->SetNumberOfPlayers(m_PrimaryChest->GetNumberOfPlayers() - 1); - cChunkDef::BlockToChunk(m_PrimaryChest->GetPosX(), m_PrimaryChest->GetPosZ(), ChunkX, ChunkZ); - m_PrimaryChest->GetWorld()->MarkRedstoneDirty(ChunkX, ChunkZ); - - if (m_SecondaryChest != nullptr) - { - m_SecondaryChest->SetNumberOfPlayers(m_SecondaryChest->GetNumberOfPlayers() - 1); - cChunkDef::BlockToChunk(m_SecondaryChest->GetPosX(), m_SecondaryChest->GetPosZ(), ChunkX, ChunkZ); - m_SecondaryChest->GetWorld()->MarkRedstoneDirty(ChunkX, ChunkZ); - } - - cWindow::ClosedByPlayer(a_Player, a_CanRefuse); - return true; -} - - - - - -cChestWindow::~cChestWindow() -{ - // Send out the chest-close packet: - m_World->BroadcastBlockAction(m_BlockX, m_BlockY, m_BlockZ, 1, 0, m_PrimaryChest->GetBlockType()); - - m_World->BroadcastSoundEffect("random.chestclosed", (double)m_BlockX, (double)m_BlockY, (double)m_BlockZ, 1, 1); -} - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cMinecartWithChestWindow: - -cMinecartWithChestWindow::cMinecartWithChestWindow(cMinecartWithChest * a_ChestCart) : - cWindow(wtChest, "Minecart with Chest"), - m_ChestCart(a_ChestCart) -{ - m_ShouldDistributeToHotbarFirst = false; - m_SlotAreas.push_back(new cSlotAreaMinecartWithChest(a_ChestCart, *this)); - m_SlotAreas.push_back(new cSlotAreaInventory(*this)); - m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); - - a_ChestCart->GetWorld()->BroadcastSoundEffect("random.chestopen", a_ChestCart->GetPosX(), a_ChestCart->GetPosY(), a_ChestCart->GetPosZ(), 1, 1); -} - - - - - -cMinecartWithChestWindow::~cMinecartWithChestWindow() -{ - m_ChestCart->GetWorld()->BroadcastSoundEffect("random.chestclosed", m_ChestCart->GetPosX(), m_ChestCart->GetPosY(), m_ChestCart->GetPosZ(), 1, 1); -} - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cDropSpenserWindow: - -cDropSpenserWindow::cDropSpenserWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cDropSpenserEntity * a_DropSpenser) : - cWindow(wtDropSpenser, (a_DropSpenser->GetBlockType() == E_BLOCK_DISPENSER) ? "Dispenser" : "Dropper") -{ - m_ShouldDistributeToHotbarFirst = false; - m_SlotAreas.push_back(new cSlotAreaItemGrid(a_DropSpenser->GetContents(), *this)); - m_SlotAreas.push_back(new cSlotAreaInventory(*this)); - m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); -} - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cEnderChestWindow: - -cEnderChestWindow::cEnderChestWindow(cEnderChestEntity * a_EnderChest) : - cWindow(wtChest, "Ender Chest"), - m_World(a_EnderChest->GetWorld()), - m_BlockX(a_EnderChest->GetPosX()), - m_BlockY(a_EnderChest->GetPosY()), - m_BlockZ(a_EnderChest->GetPosZ()) -{ - m_ShouldDistributeToHotbarFirst = false; - m_SlotAreas.push_back(new cSlotAreaEnderChest(a_EnderChest, *this)); - m_SlotAreas.push_back(new cSlotAreaInventory(*this)); - m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); - - // Play the opening sound: - m_World->BroadcastSoundEffect("random.chestopen", (double)m_BlockX, (double)m_BlockY, (double)m_BlockZ, 1, 1); - - // Send out the chest-open packet: - m_World->BroadcastBlockAction(m_BlockX, m_BlockY, m_BlockZ, 1, 1, E_BLOCK_ENDER_CHEST); -} - - - - - -cEnderChestWindow::~cEnderChestWindow() -{ - // Send out the chest-close packet: - m_World->BroadcastBlockAction(m_BlockX, m_BlockY, m_BlockZ, 1, 0, E_BLOCK_ENDER_CHEST); - - // Play the closing sound - m_World->BroadcastSoundEffect("random.chestclosed", (double)m_BlockX, (double)m_BlockY, (double)m_BlockZ, 1, 1); -} - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cHopperWindow: - -cHopperWindow::cHopperWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cHopperEntity * a_Hopper) : - super(wtHopper, "Hopper") -{ - m_ShouldDistributeToHotbarFirst = false; - m_SlotAreas.push_back(new cSlotAreaItemGrid(a_Hopper->GetContents(), *this)); - m_SlotAreas.push_back(new cSlotAreaInventory(*this)); - m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); -} - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cFurnaceWindow: - -cFurnaceWindow::cFurnaceWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cFurnaceEntity * a_Furnace) : - cWindow(wtFurnace, "Furnace") -{ - m_ShouldDistributeToHotbarFirst = false; - m_SlotAreas.push_back(new cSlotAreaFurnace(a_Furnace, *this)); - m_SlotAreas.push_back(new cSlotAreaInventory(*this)); - m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); -} - - - - diff --git a/src/UI/Window.h b/src/UI/Window.h index e62176d50..9821aade1 100644 --- a/src/UI/Window.h +++ b/src/UI/Window.h @@ -19,7 +19,6 @@ class cPlayer; class cWindowOwner; class cClientHandle; class cChestEntity; -class cDropSpenserEntity; class cEnderChestEntity; class cFurnaceEntity; class cHopperEntity; @@ -154,14 +153,19 @@ public: /** Called on shift-clicking to distribute the stack into other areas; Modifies a_ItemStack as it is distributed! if a_ShouldApply is true, the changes are written into the slots; + if a_ShouldApply is false, only a_ItemStack is modified to reflect the number of fits (for fit-testing purposes) */ + virtual void DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Player, cSlotArea * a_ClickedArea, bool a_ShouldApply) = 0; + + /** Called from DistributeStack() to distribute the stack into a_AreasInOrder; Modifies a_ItemStack as it is distributed! + If a_ShouldApply is true, the changes are written into the slots; if a_ShouldApply is false, only a_ItemStack is modified to reflect the number of fits (for fit-testing purposes) - */ - void DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, cSlotArea * a_ExcludeArea, bool a_ShouldApply); + If a_BackFill is true, the areas will be filled from the back (right side). (Example: Empty Hotbar -> Item get in slot 8, not slot 0) */ + void DistributeStackToAreas(cItem & a_ItemStack, cPlayer & a_Player, cSlotAreas & a_AreasInOrder, bool a_ShouldApply, bool a_BackFill); - /// Called on DblClicking to collect all stackable items from all areas into hand, starting with the specified area. - /// The items are accumulated in a_Dragging and removed from the SlotAreas immediately. - /// If a_CollectFullStacks is false, slots with full stacks in the area are skipped while collecting. - /// Returns true if full stack has been collected, false if there's space remaining to fill. + /** Called on DblClicking to collect all stackable items from all areas into hand, starting with the specified area. + The items are accumulated in a_Dragging and removed from the SlotAreas immediately. + If a_CollectFullStacks is false, slots with full stacks in the area are skipped while collecting. + Returns true if full stack has been collected, false if there's space remaining to fill. */ bool CollectItemsToHand(cItem & a_Dragging, cSlotArea & a_Area, cPlayer & a_Player, bool a_CollectFullStacks); /// Used by cSlotAreas to send individual slots to clients, a_RelativeSlotNum is the slot number relative to a_SlotArea @@ -178,7 +182,6 @@ protected: cPlayerList m_OpenedBy; bool m_IsDestroyed; - bool m_ShouldDistributeToHotbarFirst; ///< If set (default), shift+click tries to distribute to hotbar first, then other areas. False for doublechests cWindowOwner * m_Owner; @@ -219,188 +222,3 @@ protected: - -class cCraftingWindow : - public cWindow -{ - typedef cWindow super; -public: - cCraftingWindow(int a_BlockX, int a_BlockY, int a_BlockZ); -} ; - - - - - -class cAnvilWindow : - public cWindow -{ - typedef cWindow super; -public: - cAnvilWindow(int a_BlockX, int a_BlockY, int a_BlockZ); - - /** Gets the repaired item name. */ - AString GetRepairedItemName(void) const { return m_RepairedItemName; } - - /** Set the repaired item name. */ - void SetRepairedItemName(const AString & a_Name, cPlayer * a_Player); - - /** Gets the Position from the Anvil */ - void GetBlockPos(int & a_PosX, int & a_PosY, int & a_PosZ); - -protected: - cSlotAreaAnvil * m_AnvilSlotArea; - AString m_RepairedItemName; - int m_BlockX, m_BlockY, m_BlockZ; -} ; - - - - - -class cBeaconWindow : - public cWindow -{ - typedef cWindow super; -public: - cBeaconWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cBeaconEntity * a_Beacon); - - cBeaconEntity * GetBeaconEntity(void) const { return m_Beacon; } - - // cWindow Overrides: - virtual void OpenedByPlayer(cPlayer & a_Player) override; - -protected: - cBeaconEntity * m_Beacon; -} ; - - - - - -class cEnchantingWindow : - public cWindow -{ - typedef cWindow super; -public: - cEnchantingWindow(int a_BlockX, int a_BlockY, int a_BlockZ); - virtual void SetProperty(short a_Property, short a_Value, cPlayer & a_Player) override; - virtual void SetProperty(short a_Property, short a_Value) override; - - /** Return the Value of a Property */ - short GetPropertyValue(short a_Property); - - cSlotArea * m_SlotArea; - -protected: - short m_PropertyValue[3]; - int m_BlockX, m_BlockY, m_BlockZ; -}; - - - - - -class cFurnaceWindow : - public cWindow -{ - typedef cWindow super; -public: - cFurnaceWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cFurnaceEntity * a_Furnace); -} ; - - - - - -class cDropSpenserWindow : - public cWindow -{ - typedef cWindow super; -public: - cDropSpenserWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cDropSpenserEntity * a_Dispenser); -} ; - - - - - -class cHopperWindow : - public cWindow -{ - typedef cWindow super; -public: - cHopperWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cHopperEntity * a_Hopper); -} ; - - - - - -class cChestWindow : - public cWindow -{ -public: - cChestWindow(cChestEntity * a_Chest); - cChestWindow(cChestEntity * a_PrimaryChest, cChestEntity * a_SecondaryChest); - ~cChestWindow(); - - virtual bool ClosedByPlayer(cPlayer & a_Player, bool a_CanRefuse) override; - virtual void OpenedByPlayer(cPlayer & a_Player) override; - -protected: - cWorld * m_World; - int m_BlockX, m_BlockY, m_BlockZ; // Position of the chest, for the window-close packet - cChestEntity * m_PrimaryChest; - cChestEntity * m_SecondaryChest; -} ; - - - - - -class cMinecartWithChestWindow : - public cWindow -{ -public: - cMinecartWithChestWindow(cMinecartWithChest * a_ChestCart); - ~cMinecartWithChestWindow(); -private: - cMinecartWithChest * m_ChestCart; -}; - - - - - -class cEnderChestWindow : - public cWindow -{ -public: - cEnderChestWindow(cEnderChestEntity * a_EnderChest); - ~cEnderChestWindow(); - -protected: - cWorld * m_World; - int m_BlockX, m_BlockY, m_BlockZ; // Position of the enderchest, for the window-close packet -}; - - - - - -class cInventoryWindow : - public cWindow -{ -public: - cInventoryWindow(cPlayer & a_Player); - -protected: - cPlayer & m_Player; - -} ; - - - - - diff --git a/src/World.cpp b/src/World.cpp index e0e32c86b..f3837eb02 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -667,18 +667,23 @@ void cWorld::Start(void) void cWorld::GenerateRandomSpawn(void) { LOGD("Generating random spawnpoint..."); - + bool foundSpawnPoint = false; // Look for a spawn point at most 100 chunks away from map center: for (int i = 0; i < 100; i++) { EMCSBiome biome = GetBiomeAt((int)m_SpawnX, (int)m_SpawnZ); + if ( (biome != biOcean) && (biome != biFrozenOcean) && // The biome is acceptable (don't want a small ocean island) !IsBlockWaterOrIce(GetBlock((int)m_SpawnX, GetHeight((int)m_SpawnX, (int)m_SpawnZ), (int)m_SpawnZ)) // The terrain is acceptable (don't want to spawn inside a lake / river) ) { - // A good spawnpoint was found - break; + if (CheckPlayerSpawnPoint((int)m_SpawnX, GetHeight((int)m_SpawnX, (int)m_SpawnZ), (int)m_SpawnZ)) + { + // A good spawnpoint was found + foundSpawnPoint = true; + break; + } } // Try a neighboring chunk: if ((GetTickRandomNumber(4) % 2) == 0) // Randomise whether to increment X or Z coords @@ -692,8 +697,63 @@ void cWorld::GenerateRandomSpawn(void) } // for i - 100* m_SpawnY = (double)GetHeight((int)m_SpawnX, (int)m_SpawnZ) + 1.6f; // 1.6f to accomodate player height + if (foundSpawnPoint) + { + LOGINFO("Generated random spawnpoint position at {%i, %i, %i}", (int)m_SpawnX, (int)m_SpawnY, (int)m_SpawnZ); + } + else + { + LOGINFO("Did not find an acceptable spawnpoint. Generated a random spawnpoint position at {%i, %i, %i}", (int)m_SpawnX, (int)m_SpawnY, (int)m_SpawnZ); + } // Maybe widen the search instead? + +} + + + + + +bool cWorld::CheckPlayerSpawnPoint(int a_PosX, int a_PosY, int a_PosZ) +{ + static const struct + { + int x, z; + } Coords[] = + { + { 0, 0 }, + { -1, 0 }, + { 1, 0 }, + { 0, -1 }, + { 0, 1 }, + }; + + // Checking that spawnblock and surrounding blocks are air and not water/lava + for (size_t i = 0; i < ARRAYCOUNT(Coords); i++) + { + BLOCKTYPE BlockType = GetBlock(a_PosX + Coords[i].x, a_PosY, a_PosZ + Coords[i].x); + + if (cBlockInfo::IsSolid(BlockType) || IsBlockLiquid(BlockType)) + { + return false; + } + } // for i - Coords[] + + // Check if block below is solid + BLOCKTYPE BlockType = GetBlock(a_PosX, a_PosY - 1, a_PosZ); + if (!cBlockInfo::IsSolid(BlockType)) + { + return false; + } - LOGINFO("Generated random spawnpoint position {%i, %i, %i}", (int)m_SpawnX, (int)m_SpawnY, (int)m_SpawnZ); + // Checking that all the blocks above the spawnpoint is air. + for (int i = a_PosY; i < cChunkDef::Height; i++) + { + BLOCKTYPE BlockType = GetBlock(a_PosX, i, a_PosZ); + if (cBlockInfo::IsSolid(BlockType)) + { + return false; + } + } + return true; } diff --git a/src/World.h b/src/World.h index 3cac71a36..0decc8c6e 100644 --- a/src/World.h +++ b/src/World.h @@ -1077,6 +1077,9 @@ private: /** <summary>Generates a random spawnpoint on solid land by walking chunks and finding their biomes</summary> */ void GenerateRandomSpawn(void); + /** Check if player starting point is acceptable **/ + bool CheckPlayerSpawnPoint(int a_PosX, int a_PosY, int a_PosZ); + /** Chooses a reasonable transition from the current weather to a new weather **/ eWeather ChooseNewWeather(void); diff --git a/src/WorldStorage/WSSAnvil.cpp b/src/WorldStorage/WSSAnvil.cpp index ae82db346..cc8b8d3f5 100755 --- a/src/WorldStorage/WSSAnvil.cpp +++ b/src/WorldStorage/WSSAnvil.cpp @@ -1007,21 +1007,28 @@ cBlockEntity * cWSSAnvil::LoadFlowerPotFromNBT(const cParsedNBT & a_NBT, int a_T } std::unique_ptr<cFlowerPotEntity> FlowerPot(new cFlowerPotEntity(a_BlockX, a_BlockY, a_BlockZ, m_World)); - short ItemType = 0, ItemData = 0; + cItem Item; int currentLine = a_NBT.FindChildByName(a_TagIdx, "Item"); if (currentLine >= 0) { - ItemType = (short) a_NBT.GetInt(currentLine); + if (a_NBT.GetType(currentLine) == TAG_String) + { + StringToItem(a_NBT.GetString(currentLine), Item); + } + else if (a_NBT.GetType(currentLine) == TAG_Int) + { + Item.m_ItemType = (short) a_NBT.GetInt(currentLine); + } } currentLine = a_NBT.FindChildByName(a_TagIdx, "Data"); - if (currentLine >= 0) + if ((currentLine >= 0) && (a_NBT.GetType(currentLine) == TAG_Int)) { - ItemData = (short) a_NBT.GetInt(currentLine); + Item.m_ItemDamage = (short) a_NBT.GetInt(currentLine); } - FlowerPot->SetItem(cItem(ItemType, 1, ItemData)); + FlowerPot->SetItem(Item); return FlowerPot.release(); } |