summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore6
-rw-r--r--.travis.yml6
-rw-r--r--COMPILING.md5
-rw-r--r--LICENSE2
-rw-r--r--MCServer/Plugins/APIDump/APIDesc.lua50
-rw-r--r--MCServer/Plugins/APIDump/Classes/Network.lua84
-rw-r--r--MCServer/Plugins/APIDump/Hooks/OnEntityTeleport.lua29
-rw-r--r--MCServer/Plugins/Debuggers/Debuggers.lua63
-rw-r--r--MCServer/Plugins/Debuggers/Info.lua16
-rw-r--r--MCServer/Plugins/HookNotify/HookNotify.lua17
-rw-r--r--MCServer/Plugins/InfoReg.lua3
-rw-r--r--MCServer/Plugins/NetworkTest/Info.lua54
-rw-r--r--MCServer/Plugins/NetworkTest/NetworkTest.lua257
-rw-r--r--MCServer/webadmin/files/guest.html20
-rw-r--r--MCServer/webadmin/login_template.html2
-rw-r--r--Tools/ProtoProxy/Server.cpp8
-rwxr-xr-xeasyinstall.sh2
-rw-r--r--src/Bindings/CMakeLists.txt2
-rw-r--r--src/Bindings/LuaState.cpp24
-rw-r--r--src/Bindings/LuaState.h4
-rw-r--r--src/Bindings/LuaTCPLink.cpp318
-rw-r--r--src/Bindings/LuaTCPLink.h84
-rw-r--r--src/Bindings/LuaUDPEndpoint.cpp221
-rw-r--r--src/Bindings/LuaUDPEndpoint.h86
-rw-r--r--src/Bindings/LuaWindow.cpp18
-rw-r--r--src/Bindings/LuaWindow.h1
-rw-r--r--src/Bindings/ManualBindings.cpp331
-rw-r--r--src/Bindings/ManualBindings_Network.cpp550
-rw-r--r--src/Bindings/Plugin.h5
-rw-r--r--src/Bindings/PluginLua.cpp29
-rw-r--r--src/Bindings/PluginLua.h5
-rw-r--r--src/Bindings/PluginManager.cpp24
-rw-r--r--src/Bindings/PluginManager.h4
-rw-r--r--src/BiomeDef.cpp127
-rw-r--r--src/BiomeDef.h3
-rw-r--r--src/BlockEntities/BeaconEntity.cpp3
-rw-r--r--src/BlockEntities/ChestEntity.cpp2
-rw-r--r--src/BlockEntities/DropSpenserEntity.cpp1
-rw-r--r--src/BlockEntities/EnderChestEntity.cpp2
-rw-r--r--src/BlockEntities/FurnaceEntity.cpp2
-rw-r--r--src/BlockEntities/HopperEntity.cpp1
-rw-r--r--src/Blocks/BlockAnvil.h1
-rw-r--r--src/Blocks/BlockDirt.h14
-rw-r--r--src/Blocks/BlockEnchantmentTable.h2
-rw-r--r--src/Blocks/BlockOre.h59
-rw-r--r--src/Blocks/BlockWorkbench.h2
-rw-r--r--src/CMakeLists.txt4
-rw-r--r--src/Chunk.cpp141
-rw-r--r--src/ChunkData.cpp2
-rw-r--r--src/ClientHandle.cpp11
-rw-r--r--src/Entities/Entity.cpp22
-rw-r--r--src/Entities/Minecart.cpp1
-rw-r--r--src/Entities/Player.cpp107
-rw-r--r--src/Entities/Player.h18
-rw-r--r--src/FurnaceRecipe.cpp16
-rw-r--r--src/FurnaceRecipe.h3
-rw-r--r--src/Generating/BioGen.cpp2
-rw-r--r--src/Generating/CompoGenBiomal.cpp14
-rw-r--r--src/Generating/ComposableGenerator.cpp5
-rw-r--r--src/Generating/FinishGen.cpp175
-rw-r--r--src/Generating/FinishGen.h23
-rw-r--r--src/Generating/HeiGen.cpp64
-rw-r--r--src/Generating/ProtIntGen.h344
-rw-r--r--src/Generating/Trees.cpp16
-rw-r--r--src/Globals.h3
-rw-r--r--src/HTTPServer/SslHTTPConnection.cpp9
-rw-r--r--src/HTTPServer/SslHTTPConnection.h2
-rw-r--r--src/Items/ItemDoor.h33
-rw-r--r--src/MobSpawner.cpp3
-rw-r--r--src/Mobs/AggressiveMonster.cpp5
-rw-r--r--src/Mobs/Blaze.cpp2
-rw-r--r--src/Mobs/Ghast.cpp2
-rw-r--r--src/Mobs/Skeleton.cpp3
-rw-r--r--src/OSSupport/CMakeLists.txt3
-rw-r--r--src/OSSupport/HostnameLookup.cpp12
-rw-r--r--src/OSSupport/Network.h81
-rw-r--r--src/OSSupport/NetworkInterfaceEnum.cpp185
-rw-r--r--src/OSSupport/NetworkSingleton.cpp6
-rw-r--r--src/OSSupport/NetworkSingleton.h6
-rw-r--r--src/OSSupport/ServerHandleImpl.cpp2
-rw-r--r--src/OSSupport/TCPLinkImpl.cpp65
-rw-r--r--src/OSSupport/TCPLinkImpl.h12
-rw-r--r--src/OSSupport/UDPEndpointImpl.cpp608
-rw-r--r--src/OSSupport/UDPEndpointImpl.h81
-rw-r--r--src/PolarSSL++/CryptoKey.cpp2
-rw-r--r--src/PolarSSL++/SslContext.cpp3
-rw-r--r--src/Protocol/Protocol18x.cpp14
-rw-r--r--src/Server.cpp2
-rw-r--r--src/StringUtils.cpp48
-rw-r--r--src/StringUtils.h5
-rw-r--r--src/UI/AnvilWindow.cpp83
-rw-r--r--src/UI/AnvilWindow.h45
-rw-r--r--src/UI/BeaconWindow.cpp76
-rw-r--r--src/UI/BeaconWindow.h40
-rw-r--r--src/UI/CMakeLists.txt23
-rw-r--r--src/UI/ChestWindow.cpp141
-rw-r--r--src/UI/ChestWindow.h45
-rw-r--r--src/UI/CraftingWindow.cpp61
-rw-r--r--src/UI/CraftingWindow.h31
-rw-r--r--src/UI/DropSpenserWindow.cpp46
-rw-r--r--src/UI/DropSpenserWindow.h32
-rw-r--r--src/UI/EnchantingWindow.cpp100
-rw-r--r--src/UI/EnchantingWindow.h44
-rw-r--r--src/UI/EnderChestWindow.cpp71
-rw-r--r--src/UI/EnderChestWindow.h38
-rw-r--r--src/UI/FurnaceWindow.cpp74
-rw-r--r--src/UI/FurnaceWindow.h32
-rw-r--r--src/UI/HopperWindow.cpp48
-rw-r--r--src/UI/HopperWindow.h32
-rw-r--r--src/UI/InventoryWindow.cpp73
-rw-r--r--src/UI/InventoryWindow.h34
-rw-r--r--src/UI/MinecartWithChestWindow.h67
-rw-r--r--src/UI/SlotArea.cpp130
-rw-r--r--src/UI/SlotArea.h22
-rw-r--r--src/UI/Window.cpp437
-rw-r--r--src/UI/Window.h204
-rw-r--r--src/World.cpp68
-rw-r--r--src/World.h3
-rwxr-xr-xsrc/WorldStorage/WSSAnvil.cpp17
-rw-r--r--tests/Network/EchoServer.cpp2
-rw-r--r--tests/Network/Google.cpp2
-rw-r--r--tests/Network/NameLookup.cpp2
122 files changed, 5743 insertions, 1094 deletions
diff --git a/.gitignore b/.gitignore
index c7f032b00..8dc90910c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,7 +3,12 @@ nbproject/
ipch/
Win32/
MCServer/MCServer
+MCServer/buildinfo
+MCServer/CONTRIBUTORS
+MCServer/LICENSE
+MCServer/Licenses
MCServer/itemblacklist
+Testing/
ChunkWorxSave.ini
doxy/
Profiling
@@ -14,6 +19,7 @@ cloc.xsl
*.ncb
*.user
*.suo
+*.sqlite
/EveryNight.cmd
/UploadLuaAPI.cmd
AllFiles.lst
diff --git a/.travis.yml b/.travis.yml
index 84b963a89..2fd3bf991 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,5 @@
language: cpp
-compiler:
- - gcc
- - clang
+compiler: clang
before_install:
- if [ "$TRAVIS_MCSERVER_BUILD_TYPE" == "COVERAGE" ]; then sudo pip install cpp_coveralls; fi
@@ -29,8 +27,6 @@ after_success:
env:
- TRAVIS_MCSERVER_BUILD_TYPE=RELEASE MCSERVER_PATH=./MCServer
- TRAVIS_MCSERVER_BUILD_TYPE=DEBUG MCSERVER_PATH=./MCServer_debug
- - TRAVIS_MCSERVER_BUILD_TYPE=RELEASE TRAVIS_MCSERVER_FORCE32=1 MCSERVER_PATH=./MCServer
- - TRAVIS_MCSERVER_BUILD_TYPE=DEBUG TRAVIS_MCSERVER_FORCE32=1 MCSERVER_PATH=./MCServer_debug
matrix:
include:
diff --git a/COMPILING.md b/COMPILING.md
index 34b1e976f..89f88bbb5 100644
--- a/COMPILING.md
+++ b/COMPILING.md
@@ -4,8 +4,8 @@ To compile MCServer from source, you need CMake and make, as well as a C compile
## Windows ##
-We use Microsoft Visual Studio for Windows compilation. It is possible to use other toolchains, but it isn't tested nor supported. Visual Studio versions 2013 Express is being actively used for development.
-You can find download links for VS2013 Express here: http://www.microsoft.com/en-us/download/details.aspx?id=40787
+We use Microsoft Visual Studio for Windows compilation. It is possible to use other toolchains, but it isn't tested nor supported. Visual Studio versions 2013 Express for Desktop is being actively used for development.
+You can find download links for VS2013 Express here: http://go.microsoft.com/?linkid=9832280
Next, you need to download and install CMake. Download from here: http://cmake.org/cmake/resources/software.html . You should download a full installation package, so that the installer will set everything up for you (especially the paths).
@@ -73,6 +73,7 @@ Install git, make, cmake and gcc or clang, using your platform's package manager
```
sudo apt-get install git make cmake gcc g++
```
+(Ensure you have gcc/g++ 4.8 or higher installed to avoid errors during compilation)
### Getting the sources ###
```
diff --git a/LICENSE b/LICENSE
index 69b3c7390..d8a8ec1e4 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,7 +1,7 @@
MCServer: A performant C++ Minecraft Server
www: http://mc-server.org/
-Copyright 2014 MCServer Team
+Copyright 2011-2015 MCServer Team
------
diff --git a/MCServer/Plugins/APIDump/APIDesc.lua b/MCServer/Plugins/APIDump/APIDesc.lua
index 4c8dbd1e9..63fccb2f6 100644
--- a/MCServer/Plugins/APIDump/APIDesc.lua
+++ b/MCServer/Plugins/APIDump/APIDesc.lua
@@ -685,6 +685,28 @@ end</pre>
},
}, -- cCraftingRecipe
+ cCryptoHash =
+ {
+ Desc =
+ [[
+ Provides functions for generating cryptographic hashes.</p>
+ <p>
+ Note that all functions in this class are static, so they should be called in the dot convention:
+<pre class="prettyprint lang-lua">
+local Hash = cCryptoHash.sha1HexString("DataToHash")
+</pre></p>
+ <p>Each cryptographic hash has two variants, one returns the hash as a raw binary string, the other returns the hash as a hex-encoded string twice as long as the binary string.
+ ]],
+
+ Functions =
+ {
+ md5 = { Params = "Data", Return = "string", Notes = "(STATIC) Calculates the md5 hash of the data, returns it as a raw (binary) string of 16 characters." },
+ md5HexString = { Params = "Data", Return = "string", Notes = "(STATIC) Calculates the md5 hash of the data, returns it as a hex-encoded string of 32 characters." },
+ sha1 = { Params = "Data", Return = "string", Notes = "(STATIC) Calculates the sha1 hash of the data, returns it as a raw (binary) string of 20 characters." },
+ sha1HexString = { Params = "Data", Return = "string", Notes = "(STATIC) Calculates the sha1 hash of the data, returns it as a hex-encoded string of 40 characters." },
+ },
+ }, -- cCryptoHash
+
cEnchantments =
{
Desc = [[
@@ -903,8 +925,8 @@ end</pre>
{ Params = "DamageType, AttackerEntity, RawDamage, KnockbackAmount", Return = "", Notes = "Causes this entity to take damage of the specified type, from the specified attacker (may be nil). The final damage is calculated from RawDamage using the currently equipped armor." },
{ Params = "DamageType, ArrackerEntity, RawDamage, FinalDamage, KnockbackAmount", Return = "", Notes = "Causes this entity to take damage of the specified type, from the specified attacker (may be nil). The values are wrapped into a {{TakeDamageInfo}} structure and applied directly." },
},
- TeleportToCoords = { Params = "PosX, PosY, PosZ", Return = "", Notes = "Teleports the entity to the specified coords." },
- TeleportToEntity = { Params = "DestEntity", Return = "", Notes = "Teleports this entity to the specified destination entity." },
+ TeleportToCoords = { Params = "PosX, PosY, PosZ", Return = "", Notes = "Teleports the entity to the specified coords. Asks plugins if the teleport is allowed." },
+ TeleportToEntity = { Params = "DestEntity", Return = "", Notes = "Teleports this entity to the specified destination entity. Asks plugins if the teleport is allowed." },
},
Constants =
{
@@ -2232,6 +2254,27 @@ end
ShouldAuthenticate = { Params = "", Return = "bool", Notes = "Returns true iff the server is set to authenticate players (\"online mode\")." },
},
}, -- cServer
+
+ cStringCompression =
+ {
+ Desc = [[
+ Provides functions to compress or decompress string
+ <p>
+ All functions in this class are static, so they should be called in the dot convention:
+<pre class="prettyprint lang-lua">
+local CompressedString = cStringCompression.CompressStringGZIP("DataToCompress")
+</pre>
+ ]],
+
+ Functions =
+ {
+ CompressStringGZIP = {Params = "string", Return = "string", Notes = "Compress a string using GZIP"},
+ CompressStringZLIB = {Params = "string, factor", Return = "string", Notes = "Compresses a string using ZLIB. Factor 0 is no compression and factor 9 is maximum compression"},
+ InflateString = {Params = "string", Return = "string", Notes = "Uncompresses a string using Inflate"},
+ UncompressStringGZIP = {Params = "string", Return = "string", Notes = "Uncompress a string using GZIP"},
+ UncompressStringZLIB = {Params = "string, uncompressed length", Return = "string", Notes = "Uncompresses a string using ZLIB"},
+ },
+ },
cTeam =
{
@@ -2922,6 +2965,7 @@ end
RotateBlockFaceCW = { Params = "{{Globals#BlockFaces|eBlockFace}}", Return = "{{Globals#BlockFaces|eBlockFace}}", Notes = "Returns the {{Globals#BlockFaces|eBlockFace}} that corresponds to the given {{Globals#BlockFaces|eBlockFace}} after rotating it around the Y axis 90 degrees clockwise." },
StringSplit = {Params = "string, SeperatorsString", Return = "array table of strings", Notes = "Seperates string into multiple by splitting every time any of the characters in SeperatorsString is encountered."},
StringSplitAndTrim = {Params = "string, SeperatorsString", Return = "array table of strings", Notes = "Seperates string into multiple by splitting every time any of the characters in SeperatorsString is encountered. Each of the separate strings is trimmed (whitespace removed from the beginning and end of the string)"},
+ StringSplitWithQuotes = {Params = "string, SeperatorsString", Return = "array table of strings", Notes = "Seperates string into multiple by splitting every time any of the characters in SeperatorsString is encountered. Whitespace wrapped with single or double quotes will be ignored"},
StringToBiome = {Params = "string", Return = "{{Globals#BiomeTypes|BiomeType}}", Notes = "Converts a string representation to a {{Globals#BiomeTypes|BiomeType}} enumerated value"},
StringToDamageType = {Params = "string", Return = "{{Globals#DamageType|DamageType}}", Notes = "Converts a string representation to a {{Globals#DamageType|DamageType}} enumerated value."},
StringToDimension = {Params = "string", Return = "{{Globals#WorldDimension|Dimension}}", Notes = "Converts a string representation to a {{Globals#WorldDimension|Dimension}} enumerated value"},
@@ -2929,7 +2973,7 @@ end
StringToMobType = {Params = "string", Return = "{{Globals#MobType|MobType}}", Notes = "<b>DEPRECATED!</b> Please use cMonster:StringToMobType(). Converts a string representation to a {{Globals#MobType|MobType}} enumerated value"},
StripColorCodes = {Params = "string", Return = "string", Notes = "Removes all control codes used by MC for colors and styles"},
TrimString = {Params = "string", Return = "string", Notes = "Trims whitespace at both ends of the string"},
- md5 = {Params = "string", Return = "string", Notes = "converts a string to an md5 hash"},
+ md5 = {Params = "string", Return = "string", Notes = "<b>OBSOLETE</b>, use the {{cCryptoHash}} functions instead.<br>Converts a string to a raw binary md5 hash."},
},
ConstantGroups =
{
diff --git a/MCServer/Plugins/APIDump/Classes/Network.lua b/MCServer/Plugins/APIDump/Classes/Network.lua
index 065a743d8..c7626562d 100644
--- a/MCServer/Plugins/APIDump/Classes/Network.lua
+++ b/MCServer/Plugins/APIDump/Classes/Network.lua
@@ -31,11 +31,12 @@ local Server = cNetwork:Listen(1024, ListenCallbacks);
specific plugin-provided function is called. The callbacks are stored in tables which are passed
to the API functions, each table contains multiple callbacks for the various situations.</p>
<p>
- There are three different callback variants used: LinkCallbacks, LookupCallbacks and
- ListenCallbacks. Each is used in the situation appropriate by its name - LinkCallbacks are used
+ There are four different callback variants used: LinkCallbacks, LookupCallbacks, ListenCallbacks
+ and UDPCallbacks. Each is used in the situation appropriate by its name - LinkCallbacks are used
for handling the traffic on a single network link (plus additionally creation of such link when
- connecting as a client), LookupCallbacks are used when doing DNS and reverse-DNS lookups, and
- ListenCallbacks are used for handling incoming connections as a server.</p>
+ connecting as a client), LookupCallbacks are used when doing DNS and reverse-DNS lookups,
+ ListenCallbacks are used for handling incoming connections as a server and UDPCallbacks are used
+ for incoming UDP datagrams.</p>
<p>
LinkCallbacks have the following structure:<br/>
<pre class="prettyprint lang-lua">
@@ -111,7 +112,7 @@ local ListenCallbacks =
OnError = function (a_ErrorCode, a_ErrorMsg)
-- The specified error has occured while trying to listen
- -- No other callback will be called for this lookup from now on
+ -- No other callback will be called for this server handle from now on
-- This callback is called before the cNetwork:Listen() call returns
-- All returned values are ignored
end,
@@ -124,6 +125,25 @@ local ListenCallbacks =
end,
}
</pre></p>
+ <p>
+ UDPCallbacks have the following structure:<br/>
+<pre class="prettyprint lang-lua">
+local UDPCallbacks =
+{
+ OnError = function (a_ErrorCode, a_ErrorMsg)
+ -- The specified error has occured when trying to listen for incoming UDP datagrams
+ -- No other callback will be called for this endpoint from now on
+ -- This callback is called before the cNetwork:CreateUDPEndpoint() call returns
+ -- All returned values are ignored
+ end,
+
+ OnReceivedData = function ({{cUDPEndpoint|a_UDPEndpoint}}, a_Data, a_RemotePeer, a_RemotePort)
+ -- A datagram has been received on the {{cUDPEndpoint|endpoint}} from the specified remote peer
+ -- a_Data contains the raw received data, as a string
+ -- All returned values are ignored
+ end,
+}
+</pre>
]],
},
@@ -268,12 +288,32 @@ g_Server = nil
Functions =
{
Connect = { Params = "Host, Port, LinkCallbacks", Return = "bool", Notes = "(STATIC) Begins establishing a (client) TCP connection to the specified host. Uses the LinkCallbacks table to report progress, success, errors and incoming data. Returns false if it fails immediately (bad port value, bad hostname format), true otherwise. Host can be either an IP address or a hostname." },
+ CreateUDPEndpoint = { Params = "Port, UDPCallbacks", Return = "{{cUDPEndpoint|UDPEndpoint}}", Notes = "(STATIC) Creates a UDP endpoint that listens for incoming datagrams on the specified port, and can be used to send or broadcast datagrams. Uses the UDPCallbacks to report incoming datagrams or errors. If the endpoint cannot be created, the OnError callback is called with the error details and the returned endpoint will report IsOpen() == false. The plugin needs to store the returned endpoint object for as long as it needs the UDP port open; if the endpoint is garbage-collected by Lua, the socket will be closed and no more incoming data will be reported.<br>If the Port is zero, the OS chooses an available UDP port for the endpoint; use {{cUDPEndpoint}}:GetPort() to query the port number in such case." },
+ EnumLocalIPAddresses = { Params = "", Return = "array-table of strings", Notes = "(STATIC) Returns all local IP addresses for network interfaces currently available on the machine." },
HostnameToIP = { Params = "Host, LookupCallbacks", Return = "bool", Notes = "(STATIC) Begins a DNS lookup to find the IP address(es) for the specified host. Uses the LookupCallbacks table to report progress, success or errors. Returns false if it fails immediately (bad hostname format), true if the lookup started successfully. Host can be either a hostname or an IP address." },
IPToHostname = { Params = "Address, LookupCallbacks", Return = "bool", Notes = "(STATIC) Begins a reverse-DNS lookup to find out the hostname for the specified IP address. Uses the LookupCallbacks table to report progress, success or errors. Returns false if it fails immediately (bad address format), true if the lookup started successfully." },
Listen = { Params = "Port, ListenCallbacks", Return = "{{cServerHandle|ServerHandle}}", Notes = "(STATIC) Starts listening on the specified port. Uses the ListenCallbacks to report incoming connections or errors. Returns a {{cServerHandle}} object representing the server. If the listen operation failed, the OnError callback is called with the error details and the returned server handle will report IsListening() == false. The plugin needs to store the server handle object for as long as it needs the server running, if the server handle is garbage-collected by Lua, the listening socket will be closed and all current connections dropped." },
},
}, -- cNetwork
-
+
+ cServerHandle =
+ {
+ Desc =
+ [[
+ This class provides an interface for TCP sockets listening for a connection. In order to listen, the
+ plugin needs to use the {{cNetwork}}:Listen() function to create the listening socket.</p>
+ <p>
+ Note that when Lua garbage-collects this class, the listening socket is closed. Therefore the plugin
+ should keep it referenced in a global variable for as long as it wants the server running.
+ ]],
+
+ Functions =
+ {
+ Close = { Params = "", Return = "", Notes = "Closes the listening socket. No more connections will be accepted, and all current connections will be closed." },
+ IsListening = { Params = "", Return = "bool", Notes = "Returns true if the socket is listening." },
+ },
+ }, -- cServerHandle
+
cTCPLink =
{
Desc =
@@ -282,37 +322,49 @@ g_Server = nil
calling {{cNetwork}}:Connect() to connect to a remote server, or by listening using
{{cNetwork}}:Listen() and accepting incoming connections. The links are callback-based - when an event
such as incoming data or remote disconnect happens on the link, a specific callback is called. See the
- additional information in {{cNetwork}} documentation for details.
+ additional information in {{cNetwork}} documentation for details.</p>
+ <p>
+ The link can also optionally perform TLS encryption. Plugins can use the StartTLSClient() function to
+ start the TLS handshake as the client side. Since that call, the OnReceivedData() callback is
+ overridden internally so that the data is first routed through the TLS decryptor, and the plugin's
+ callback is only called for the decrypted data, once it starts arriving. The Send function changes its
+ behavior so that the data written by the plugin is first encrypted and only then sent over the
+ network. Note that calling Send() before the TLS handshake finishes is supported, but the data is
+ queued internally and only sent once the TLS handshake is completed.
]],
Functions =
{
+ Close = { Params = "", Return = "", Notes = "Closes the link forcefully (TCP RST). There's no guarantee that the last sent data is even being delivered. See also the Shutdown() method." },
GetLocalIP = { Params = "", Return = "string", Notes = "Returns the IP address of the local endpoint of the TCP connection." },
GetLocalPort = { Params = "", Return = "number", Notes = "Returns the port of the local endpoint of the TCP connection." },
GetRemoteIP = { Params = "", Return = "string", Notes = "Returns the IP address of the remote endpoint of the TCP connection." },
GetRemotePort = { Params = "", Return = "number", Notes = "Returns the port of the remote endpoint of the TCP connection." },
Send = { Params = "Data", Return = "", Notes = "Sends the data (raw string) to the remote peer. The data is sent asynchronously and there is no report on the success of the send operation, other than the connection being closed or reset by the underlying OS." },
+ Shutdown = { Params = "", Return = "", Notes = "Shuts the socket down for sending data. Notifies the remote peer that there will be no more data coming from us (TCP FIN). The data that is in flight will still be delivered. The underlying socket will be closed when the remote end shuts down as well, or after a timeout." },
+ StartTLSClient = { Params = "OwnCert, OwnPrivateKey, OwnPrivateKeyPassword", Return = "", Notes = "Starts a TLS handshake on the link, as a client side of the TLS. The Own___ parameters specify the client certificate and its corresponding private key and password; all three parameters are optional and no client certificate is presented to the remote peer if they are not used or all empty. Once the TLS handshake is started by this call, all incoming data is first decrypted before being sent to the OnReceivedData callback, and all outgoing data is queued until the TLS handshake completes, and then sent encrypted over the link." },
+ StartTLSServer = { Params = "Certificate, PrivateKey, PrivateKeyPassword, StartTLSData", Return = "", Notes = "Starts a TLS handshake on the link, as a server side of the TLS. The plugin needs to specify the server certificate and its corresponding private key and password. The StartTLSData can contain data that the link has already reported as received but it should be used as part of the TLS handshake. Once the TLS handshake is started by this call, all incoming data is first decrypted before being sent to the OnReceivedData callback, and all outgoing data is queued until the TLS handshake completes, and then sent encrypted over the link." },
},
}, -- cTCPLink
- cServerHandle =
+ cUDPEndpoint =
{
Desc =
[[
- This class provides an interface for TCP sockets listening for a connection. In order to listen, the
- plugin needs to use the {{cNetwork}}:Listen() function to create the listening socket.</p>
+ Represents a UDP socket that is listening for incoming datagrams on a UDP port and can send or broadcast datagrams to other peers on the network. Plugins can create an instance of the endpoint by calling {{cNetwork}}:CreateUDPEndpoint(). The endpoints are callback-based - when a datagram is read from the network, the OnRececeivedData() callback is called with details about the datagram. See the additional information in {{cNetwork}} documentation for details.</p>
<p>
- Note that when Lua garbage-collects this class, the listening socket is closed. Therefore the plugin
- should keep it referenced in a global variable for as long as it wants the server running.
+ Note that when Lua garbage-collects this class, the listening socket is closed. Therefore the plugin should keep this object referenced in a global variable for as long as it wants the endpoint open.
]],
Functions =
{
- Close = { Params = "", Return = "", Notes = "Closes the listening socket. No more connections will be accepted, and all current connections will be closed." },
- IsListening = { Params = "", Return = "bool", Notes = "Returns true if the socket is listening." },
+ Close = { Params = "", Return = "", Notes = "Closes the UDP endpoint. No more datagrams will be reported through the callbacks, the UDP port will be closed." },
+ EnableBroadcasts = { Params = "", Return = "", Notes = "Some OSes need this call before they allow UDP broadcasts on an endpoint." },
+ GetPort = { Params = "", Return = "number", Notes = "Returns the local port number of the UDP endpoint listening for incoming datagrams. Especially useful if the UDP endpoint was created with auto-assign port (0)." },
+ IsOpen = { Params = "", Return = "bool", Notes = "Returns true if the UDP endpoint is listening for incoming datagrams." },
+ Send = { Params = "RawData, RemoteHost, RemotePort", Return = "bool", Notes = "Sends the specified raw data (string) to the specified remote host. The RemoteHost can be either a hostname or an IP address; if it is a hostname, the endpoint will queue a DNS lookup first, if it is an IP address, the send operation is executed immediately. Returns true if there was no immediate error, false on any failure. Note that the return value needn't represent whether the packet was actually sent, only if it was successfully queued." },
},
-
- }, -- cServerHandle
+ }, -- cUDPEndpoint
}
diff --git a/MCServer/Plugins/APIDump/Hooks/OnEntityTeleport.lua b/MCServer/Plugins/APIDump/Hooks/OnEntityTeleport.lua
new file mode 100644
index 000000000..cdeb0947f
--- /dev/null
+++ b/MCServer/Plugins/APIDump/Hooks/OnEntityTeleport.lua
@@ -0,0 +1,29 @@
+return
+{
+ HOOK_ENTITY_TELEPORT =
+ {
+ CalledWhen = "Any entity teleports. Plugin may refuse teleport.",
+ DefaultFnName = "OnEntityTeleport", -- also used as pagename
+ Desc = [[
+ This function is called in each server tick for each {{cEntity|Entity}} that has
+ teleported. Plugins may refuse the teleport.
+ ]],
+ Params =
+ {
+ { Name = "Entity", Type = "{{cEntity}}", Notes = "The entity who has teleported. New position is set in the object after successfull teleport" },
+ { Name = "OldPosition", Type = "{{Vector3d}}", Notes = "The old position." },
+ { Name = "NewPosition", Type = "{{Vector3d}}", Notes = "The new position." },
+ },
+ Returns = [[
+ If the function returns true, teleport is prohibited.</p>
+ <p>
+ If the function returns false or no value, other plugins' callbacks are called and finally the new
+ position is permanently stored in the cEntity object.</p>
+ ]],
+ }, -- HOOK_ENTITY_TELEPORT
+}
+
+
+
+
+
diff --git a/MCServer/Plugins/Debuggers/Debuggers.lua b/MCServer/Plugins/Debuggers/Debuggers.lua
index a46072324..c8069a411 100644
--- a/MCServer/Plugins/Debuggers/Debuggers.lua
+++ b/MCServer/Plugins/Debuggers/Debuggers.lua
@@ -25,13 +25,14 @@ function Initialize(Plugin)
PM:AddHook(cPluginManager.HOOK_PLAYER_RIGHT_CLICKING_ENTITY, OnPlayerRightClickingEntity);
PM:AddHook(cPluginManager.HOOK_WORLD_TICK, OnWorldTick);
PM:AddHook(cPluginManager.HOOK_PLUGINS_LOADED, OnPluginsLoaded);
- PM:AddHook(cPluginManager.HOOK_PLUGIN_MESSAGE, OnPluginMessage);
PM:AddHook(cPluginManager.HOOK_PLAYER_JOINED, OnPlayerJoined);
PM:AddHook(cPluginManager.HOOK_PROJECTILE_HIT_BLOCK, OnProjectileHitBlock);
PM:AddHook(cPluginManager.HOOK_CHUNK_UNLOADING, OnChunkUnloading);
PM:AddHook(cPluginManager.HOOK_WORLD_STARTED, OnWorldStarted);
PM:AddHook(cPluginManager.HOOK_PROJECTILE_HIT_BLOCK, OnProjectileHitBlock);
+ -- _X: Disabled WECUI manipulation:
+ -- PM:AddHook(cPluginManager.HOOK_PLUGIN_MESSAGE, OnPluginMessage);
-- _X: Disabled so that the normal operation doesn't interfere with anything
-- PM:AddHook(cPluginManager.HOOK_CHUNK_GENERATED, OnChunkGenerated);
@@ -1476,7 +1477,7 @@ function HandleWESel(a_Split, a_Player)
SelCuboid:Expand(NumBlocks, NumBlocks, 0, 0, NumBlocks, NumBlocks)
-- Set the selection:
- local IsSuccess = cPluginManager:CallPlugin("WorldEdit", "SetPlayerCuboidSelection", a_Player, SelCuboid)
+ IsSuccess = cPluginManager:CallPlugin("WorldEdit", "SetPlayerCuboidSelection", a_Player, SelCuboid)
if not(IsSuccess) then
a_Player:SendMessage(cCompositeChat():SetMessageType(mtFailure):AddTextPart("Cannot adjust selection, WorldEdit reported failure while setting new selection"))
return true
@@ -1606,17 +1607,36 @@ end
-function HandleConsoleSchedule(a_Split)
- local prev = os.clock()
- LOG("Scheduling a task for 2 seconds in the future (current os.clock is " .. prev .. ")")
- cRoot:Get():GetDefaultWorld():ScheduleTask(40,
- function ()
- local current = os.clock()
- local diff = current - prev
- LOG("Scheduled function is called. Current os.clock is " .. current .. ", difference is " .. diff .. ")")
- end
- )
- return true, "Task scheduled"
+-- List of hashing functions to test:
+local HashFunctions =
+{
+ {"md5", md5 },
+ {"cCryptoHash.md5", cCryptoHash.md5 },
+ {"cCryptoHash.md5HexString", cCryptoHash.md5HexString },
+ {"cCryptoHash.sha1", cCryptoHash.sha1 },
+ {"cCryptoHash.sha1HexString", cCryptoHash.sha1HexString },
+}
+
+-- List of strings to try hashing:
+local HashExamples =
+{
+ "",
+ "\0",
+ "test",
+}
+
+function HandleConsoleHash(a_Split)
+ for _, str in ipairs(HashExamples) do
+ LOG("Hashing string \"" .. str .. "\":")
+ for _, hash in ipairs(HashFunctions) do
+ if not(hash[2]) then
+ LOG("Hash function " .. hash[1] .. " doesn't exist in the API!")
+ else
+ LOG(hash[1] .. "() = " .. hash[2](str))
+ end
+ end -- for hash - HashFunctions[]
+ end -- for str - HashExamples[]
+ return true
end
@@ -1704,3 +1724,20 @@ end
+
+function HandleConsoleSchedule(a_Split)
+ local prev = os.clock()
+ LOG("Scheduling a task for 2 seconds in the future (current os.clock is " .. prev .. ")")
+ cRoot:Get():GetDefaultWorld():ScheduleTask(40,
+ function ()
+ local current = os.clock()
+ local diff = current - prev
+ LOG("Scheduled function is called. Current os.clock is " .. current .. ", difference is " .. diff .. ")")
+ end
+ )
+ return true, "Task scheduled"
+end
+
+
+
+
diff --git a/MCServer/Plugins/Debuggers/Info.lua b/MCServer/Plugins/Debuggers/Info.lua
index b96ef3de5..63a4b9177 100644
--- a/MCServer/Plugins/Debuggers/Info.lua
+++ b/MCServer/Plugins/Debuggers/Info.lua
@@ -200,21 +200,29 @@ g_PluginInfo =
ConsoleCommands =
{
- ["sched"] =
+ ["hash"] =
{
- Handler = HandleConsoleSchedule,
- HelpString = "Tests the world scheduling",
+ Handler = HandleConsoleHash,
+ HelpString = "Tests the crypto hashing functions",
},
+
["loadchunk"] =
{
Handler = HandleConsoleLoadChunk,
HelpString = "Loads the specified chunk into memory",
},
+
["preparechunk"] =
{
Handler = HandleConsolePrepareChunk,
HelpString = "Prepares the specified chunk completely (load / gen / light)",
- }
+ },
+
+ ["sched"] =
+ {
+ Handler = HandleConsoleSchedule,
+ HelpString = "Tests the world scheduling",
+ },
}, -- ConsoleCommands
} -- g_PluginInfo
diff --git a/MCServer/Plugins/HookNotify/HookNotify.lua b/MCServer/Plugins/HookNotify/HookNotify.lua
index ed791090e..1d3d5088e 100644
--- a/MCServer/Plugins/HookNotify/HookNotify.lua
+++ b/MCServer/Plugins/HookNotify/HookNotify.lua
@@ -23,6 +23,7 @@ function Initialize(Plugin)
cPluginManager.AddHook(cPluginManager.HOOK_COLLECTING_PICKUP, OnCollectingPickup);
cPluginManager.AddHook(cPluginManager.HOOK_CRAFTING_NO_RECIPE, OnCraftingNoRecipe);
cPluginManager.AddHook(cPluginManager.HOOK_DISCONNECT, OnDisconnect);
+ cPluginManager.AddHook(cPluginManager.HOOK_ENTITY_TELEPORT, OnEntityTeleport);
cPluginManager.AddHook(cPluginManager.HOOK_EXECUTE_COMMAND, OnExecuteCommand);
cPluginManager.AddHook(cPluginManager.HOOK_HANDSHAKE, OnHandshake);
cPluginManager.AddHook(cPluginManager.HOOK_KILLING, OnKilling);
@@ -179,6 +180,22 @@ end
+function OnEntityTeleport(arg1,arg2,arg3)
+ if arg1.IsPlayer() then
+ -- if it's a player, get his name
+ LOG("OnEntityTeleport: Player: " .. arg1.GetName());
+ else
+ -- if it's a entity, get its type
+ LOG("OnEntityTeleport: EntityType: " .. arg1.GetEntityType());
+ end
+ LOG("OldPos: " .. arg2.x .. " / " .. arg2.y .. " / " .. arg2.z);
+ LOG("NewPos: " .. arg3.x .. " / " .. arg3.y .. " / " .. arg3.z);
+end
+
+
+
+
+
function OnExecuteCommand(...)
LogHook("OnExecuteCommand", unpack(arg));
diff --git a/MCServer/Plugins/InfoReg.lua b/MCServer/Plugins/InfoReg.lua
index 92c4a2e59..e34b79564 100644
--- a/MCServer/Plugins/InfoReg.lua
+++ b/MCServer/Plugins/InfoReg.lua
@@ -87,8 +87,7 @@ local function MultiCommandHandler(a_Split, a_Player, a_CmdString, a_CmdInfo, a_
LOG("Cannot find handler for command " .. a_CmdString .. " " .. Verb);
return false;
end
- MultiCommandHandler(a_Split, a_Player, a_CmdString .. " " .. Verb, Subcommand, a_Level + 1);
- return true;
+ return MultiCommandHandler(a_Split, a_Player, a_CmdString .. " " .. Verb, Subcommand, a_Level + 1);
end
-- Execute:
diff --git a/MCServer/Plugins/NetworkTest/Info.lua b/MCServer/Plugins/NetworkTest/Info.lua
index f366fd1be..d8c3095fe 100644
--- a/MCServer/Plugins/NetworkTest/Info.lua
+++ b/MCServer/Plugins/NetworkTest/Info.lua
@@ -50,6 +50,12 @@ g_PluginInfo =
}, -- ParameterCombinations
}, -- close
+ ips =
+ {
+ HelpString = "Prints all locally available IP addresses",
+ Handler = HandleConsoleNetIps,
+ }, -- ips
+
listen =
{
HelpString = "Creates a new listening socket on the specified port with the specified service attached to it",
@@ -84,6 +90,54 @@ g_PluginInfo =
},
}, -- lookup
+ udp =
+ {
+ Subcommands =
+ {
+ close =
+ {
+ Handler = HandleConsoleNetUdpClose,
+ ParameterCombinations =
+ {
+ {
+ Params = "[Port]",
+ Help = "Closes the UDP endpoint on the specified port [1024].",
+ }
+ },
+ }, -- close
+
+ listen =
+ {
+ Handler = HandleConsoleNetUdpListen,
+ ParameterCombinations =
+ {
+ {
+ Params = "[Port]",
+ Help = "Listens on the specified UDP port [1024], dumping the incoming datagrams into log",
+ },
+ },
+ }, -- listen
+
+ send =
+ {
+ Handler = HandleConsoleNetUdpSend,
+ ParameterCombinations =
+ {
+ {
+ Params = "[Host] [Port] [Message]",
+ Help = "Sends the message [\"hello\"] through UDP to the specified host [localhost] and port [1024]",
+ },
+ },
+ } -- send
+ }, -- Subcommands ("net udp")
+ }, -- udp
+
+ wasc =
+ {
+ HelpString = "Requests the webadmin homepage using https",
+ Handler = HandleConsoleNetWasc,
+ }, -- wasc
+
}, -- Subcommands
}, -- net
},
diff --git a/MCServer/Plugins/NetworkTest/NetworkTest.lua b/MCServer/Plugins/NetworkTest/NetworkTest.lua
index 7932f4b88..22056d4e9 100644
--- a/MCServer/Plugins/NetworkTest/NetworkTest.lua
+++ b/MCServer/Plugins/NetworkTest/NetworkTest.lua
@@ -11,6 +11,10 @@
-- g_Servers[PortNum] = cServerHandle
local g_Servers = {}
+--- Map of all UDP endpoints currently open
+-- g_UDPEndpoints[PortNum] = cUDPEndpoint
+local g_UDPEndpoints = {}
+
--- List of fortune messages for the fortune server
-- A random message is chosen for each incoming connection
-- The contents are loaded from the splashes.txt file on plugin startup
@@ -19,6 +23,62 @@ local g_Fortunes =
"Empty splashes.txt",
}
+-- HTTPS certificate to be used for the SSL server:
+local g_HTTPSCert = [[
+-----BEGIN CERTIFICATE-----
+MIIDfzCCAmegAwIBAgIJAOBHN+qOWodcMA0GCSqGSIb3DQEBBQUAMFYxCzAJBgNV
+BAYTAmN6MQswCQYDVQQIDAJjejEMMAoGA1UEBwwDbG9jMQswCQYDVQQKDAJfWDEL
+MAkGA1UECwwCT1UxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xNTAxMjQwODQ2MzFa
+Fw0yNTAxMjEwODQ2MzFaMFYxCzAJBgNVBAYTAmN6MQswCQYDVQQIDAJjejEMMAoG
+A1UEBwwDbG9jMQswCQYDVQQKDAJfWDELMAkGA1UECwwCT1UxEjAQBgNVBAMMCWxv
+Y2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJkFYSElu/jw
+nxqjimmj246DejKJK8uy/l9QQibb/Z4kO/3s0gVPOYo0mKv32xUFP7wYIE3XWT61
+zyfvK+1jpnlQTCtM8T5xw/7CULKgLmuIzlQx5Dhy7d+tW46kOjFKwQajS9YzwqWu
+KBOPnFamQWz6vIzuM05+7aIMXbzamInvW/1x3klIrpGQgALwSB1N+oUzTInTBRKK
+21pecUE9t3qrU40Cs5bN0fQBnBjLwbgmnTh6LEplfQZHG5wLvj0IeERVU9vH7luM
+e9/IxuEZluCiu5ViF3jqLPpjYOrkX7JDSKme64CCmNIf0KkrwtFjF104Qylike60
+YD3+kw8Q+DECAwEAAaNQME4wHQYDVR0OBBYEFHHIDTc7mrLDXftjQ5ejU9Udfdyo
+MB8GA1UdIwQYMBaAFHHIDTc7mrLDXftjQ5ejU9UdfdyoMAwGA1UdEwQFMAMBAf8w
+DQYJKoZIhvcNAQEFBQADggEBAHxCJxZPmH9tvx8GKiDV3rgGY++sMItzrW5Uhf0/
+bl3DPbVz51CYF8nXiWvSJJzxhH61hKpZiqvRlpyMuovV415dYQ+Xc2d2IrTX6e+d
+Z4Pmwfb4yaX+kYqIygjXMoyNxOJyhTnCbJzycV3v5tvncBWN9Wqez6ZonWDdFdAm
+J+Moty+atc4afT02sUg1xz+CDr1uMbt62tHwKYCdxXCwT//bOs6W21+mQJ5bEAyA
+YrHQPgX76uo8ed8rPf6y8Qj//lzq/+33EIWqf9pnbklQgIPXJU07h+5L+Y63RF4A
+ComLkzas+qnQLcEN28Dg8QElXop6hfiD0xq3K0ac2bDnjoU=
+-----END CERTIFICATE-----
+]]
+
+local g_HTTPSPrivKey = [[
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCZBWEhJbv48J8a
+o4ppo9uOg3oyiSvLsv5fUEIm2/2eJDv97NIFTzmKNJir99sVBT+8GCBN11k+tc8n
+7yvtY6Z5UEwrTPE+ccP+wlCyoC5riM5UMeQ4cu3frVuOpDoxSsEGo0vWM8KlrigT
+j5xWpkFs+ryM7jNOfu2iDF282piJ71v9cd5JSK6RkIAC8EgdTfqFM0yJ0wUSitta
+XnFBPbd6q1ONArOWzdH0AZwYy8G4Jp04eixKZX0GRxucC749CHhEVVPbx+5bjHvf
+yMbhGZbgoruVYhd46iz6Y2Dq5F+yQ0ipnuuAgpjSH9CpK8LRYxddOEMpYpHutGA9
+/pMPEPgxAgMBAAECggEAWxQ4m+I54BJYoSJ2YCqHpGvdb/b1emkvvsumlDqc2mP2
+0U0ENOTS+tATj0gXvotBRFOX5r0nAYx1oO9a1hFaJRsGOz+w19ofLqO6JJfzCU6E
+gNixXmgJ7fjhZiWZ/XzhJ3JK0VQ9px/h+sKf63NJvfQABmJBZ5dlGe8CXEZARNin
+03TnE3RUIEK+jEgwShN2OrGjwK9fjcnXMHwEnKZtCBiYEfD2N+pQmS20gIm13L1t
++ZmObIC24NqllXxl4I821qzBdhmcT7+rGmKR0OT5YKbt6wFA5FPKD9dqlzXzlKck
+r2VAh+JlCtFKxcScmWtQOnVDtf5+mcKFbP4ck724AQKBgQDLk+RDhvE5ykin5R+B
+dehUQZgHb2pPL7N1DAZShfzwSmyZSOPQDFr7c0CMijn6G0Pw9VX6Vrln0crfTQYz
+Hli+zxlmcMAD/WC6VImM1LCUzouNRy37rSCnuPtngZyHdsyzfheGnjORH7HlPjtY
+JCTLaekg0ckQvt//HpRV3DCdaQKBgQDAbLmIOTyGfne74HLswWnY/kCOfFt6eO+E
+lZ724MWmVPWkxq+9rltC2CDx2i8jjdkm90dsgR5OG2EaLnUWldUpkE0zH0ATrZSV
+ezJWD9SsxTm8ksbThD+pJKAVPxDAboejF7kPvpaO2wY+bf0AbO3M24rJ2tccpMv8
+AcfXBICDiQKBgQCSxp81/I3hf7HgszaC7ZLDZMOK4M6CJz847aGFUCtsyAwCfGYb
+8zyJvK/WZDam14+lpA0IQAzPCJg/ZVZJ9uA/OivzCum2NrHNxfOiQRrLPxuokaBa
+q5k2tA02tGE53fJ6mze1DEzbnkFxqeu5gd2xdzvpOLfBxgzT8KU8PlQiuQKBgGn5
+NvCj/QZhDhYFVaW4G1ArLmiKamL3yYluUV7LiW7CaYp29gBzzsTwfKxVqhJdo5NH
+KinCrmr7vy2JGmj22a+LTkjyU/rCZQsyDxXAoDMKZ3LILwH8WocPqa4pzlL8TGzw
+urXGE+rXCwhE0Mp0Mz7YRgZHJKMcy06duG5dh11pAoGBALHbsBIDihgHPyp2eKMP
+K1f42MdKrTBiIXV80hv2OnvWVRCYvnhrqpeRMzCR1pmVbh+QhnwIMAdWq9PAVTTn
+ypusoEsG8Y5fx8xhgjs0D2yMcrmi0L0kCgHIFNoym+4pI+sv6GgxpemfrmaPNcMx
+DXi9JpaquFRJLGJ7jMCDgotL
+-----END PRIVATE KEY-----
+]]
+
--- Map of all services that can be run as servers
-- g_Services[ServiceName] = function() -> accept-callbacks
local g_Services =
@@ -66,7 +126,7 @@ local g_Services =
return
{
OnError = function (a_Link, a_ErrorCode, a_ErrorMsg)
- LOG("FortuneServer(" .. a_Port .. ": Connection to " .. a_Link:GetRemoteIP() .. ":" .. a_Link:GetRemotePort() .. " failed: " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")")
+ LOG("FortuneServer(" .. a_Port .. "): Connection to " .. a_Link:GetRemoteIP() .. ":" .. a_Link:GetRemotePort() .. " failed: " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")")
end,
OnReceivedData = function (a_Link, a_Data)
@@ -86,11 +146,56 @@ local g_Services =
-- There was an error listening on the port:
OnError = function (a_ErrorCode, a_ErrorMsg)
- LOGINFO("FortuneServer(" .. a_Port .. ": Cannot listen: " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")")
+ LOGINFO("FortuneServer(" .. a_Port .. "): Cannot listen: " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")")
end, -- OnError()
} -- Listen callbacks
end, -- fortune
+ -- HTTPS time - serves current time for each https request received
+ httpstime = function (a_Port)
+ return
+ {
+ -- A new connection has come, give it new link-callbacks:
+ OnIncomingConnection = function (a_RemoteIP, a_RemotePort)
+ local IncomingData = "" -- accumulator for the incoming data, until processed by the http
+ return
+ {
+ OnError = function (a_Link, a_ErrorCode, a_ErrorMsg)
+ LOG("https-time server(" .. a_Port .. "): Connection to " .. a_Link:GetRemoteIP() .. ":" .. a_Link:GetRemotePort() .. " failed: " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")")
+ end,
+
+ OnReceivedData = function (a_Link, a_Data)
+ IncomingData = IncomingData .. a_Data
+ if (IncomingData:find("\r\n\r\n")) then
+ -- We have received the entire request headers, just send the response and shutdown the link:
+ local Content = os.date()
+ a_Link:Send("HTTP/1.0 200 OK\r\nContent-type: text/plain\r\nContent-length: " .. #Content .. "\r\n\r\n" .. Content)
+ a_Link:Shutdown()
+ end
+ end,
+
+ OnRemoteClosed = function (a_Link)
+ LOG("httpstime: link closed by remote")
+ end
+ } -- Link callbacks
+ end, -- OnIncomingConnection()
+
+ -- Start TLS on the new link:
+ OnAccepted = function (a_Link)
+ local res, msg = a_Link:StartTLSServer(g_HTTPSCert, g_HTTPSPrivKey, "")
+ if not(res) then
+ LOG("https-time server(" .. a_Port .. "): Cannot start TLS server: " .. msg)
+ a_Link:Close()
+ end
+ end, -- OnAccepted()
+
+ -- There was an error listening on the port:
+ OnError = function (a_ErrorCode, a_ErrorMsg)
+ LOGINFO("https-time server(" .. a_Port .. "): Cannot listen: " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")")
+ end, -- OnError()
+ } -- Listen callbacks
+ end, -- httpstime
+
-- TODO: Other services (daytime, ...)
}
@@ -167,7 +272,7 @@ function HandleConsoleNetClose(a_Split)
-- Get the port to close:
local Port = tonumber(a_Split[3] or 1024)
if not(Port) then
- return true, "Bad port number: \"" .. Port .. "\"."
+ return true, "Bad port number: \"" .. a_Split[3] .. "\"."
end
-- Close the server, if there is one:
@@ -183,6 +288,19 @@ end
+function HandleConsoleNetIps(a_Split)
+ local Addresses = cNetwork:EnumLocalIPAddresses()
+ LOG("IP addresses enumerated, " .. #Addresses .. " found")
+ for idx, addr in ipairs(Addresses) do
+ LOG(" IP #" .. idx .. ": " .. addr)
+ end
+ return true
+end
+
+
+
+
+
function HandleConsoleNetLookup(a_Split)
-- Get the name to look up:
local Addr = a_Split[3] or "google.com"
@@ -229,7 +347,7 @@ function HandleConsoleNetListen(a_Split)
-- Get the params:
local Port = tonumber(a_Split[3] or 1024)
if not(Port) then
- return true, "Invalid port: \"" .. Port .. "\"."
+ return true, "Invalid port: \"" .. a_Split[3] .. "\"."
end
local Service = string.lower(a_Split[4] or "echo")
@@ -252,3 +370,134 @@ end
+
+function HandleConsoleNetUdpClose(a_Split)
+ -- Get the port to close:
+ local Port = tonumber(a_Split[4] or 1024)
+ if not(Port) then
+ return true, "Bad port number: \"" .. a_Split[4] .. "\"."
+ end
+
+ -- Close the server, if there is one:
+ if not(g_UDPEndpoints[Port]) then
+ return true, "There is no UDP endpoint currently listening on port " .. Port .. "."
+ end
+ g_UDPEndpoints[Port]:Close()
+ g_UDPEndpoints[Port] = nil
+ return true, "UDP Port " .. Port .. " closed."
+end
+
+
+
+
+
+function HandleConsoleNetUdpListen(a_Split)
+ -- Get the params:
+ local Port = tonumber(a_Split[4] or 1024)
+ if not(Port) then
+ return true, "Invalid port: \"" .. a_Split[4] .. "\"."
+ end
+
+ local Callbacks =
+ {
+ OnReceivedData = function (a_Endpoint, a_Data, a_RemotePeer, a_RemotePort)
+ LOG("Incoming UDP datagram from " .. a_RemotePeer .. " port " .. a_RemotePort .. ":\r\n" .. a_Data)
+ end,
+
+ OnError = function (a_Endpoint, a_ErrorCode, a_ErrorMsg)
+ LOG("Error in UDP endpoint: " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")")
+ end,
+ }
+
+ g_UDPEndpoints[Port] = cNetwork:CreateUDPEndpoint(Port, Callbacks)
+ return true, "UDP listener on port " .. Port .. " started."
+end
+
+
+
+
+
+function HandleConsoleNetUdpSend(a_Split)
+ -- Get the params:
+ local Host = a_Split[4] or "localhost"
+ local Port = tonumber(a_Split[5] or 1024)
+ if not(Port) then
+ return true, "Invalid port: \"" .. a_Split[5] .. "\"."
+ end
+ local Message
+ if (a_Split[6]) then
+ Message = table.concat(a_Split, " ", 6)
+ else
+ Message = "hello"
+ end
+
+ -- Define minimum callbacks for the UDP endpoint:
+ local Callbacks =
+ {
+ OnError = function (a_Endpoint, a_ErrorCode, a_ErrorMsg)
+ LOG("Error in UDP datagram sending: " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")")
+ end,
+
+ OnReceivedData = function ()
+ -- ignore
+ end,
+ }
+
+ -- Send the data:
+ local Endpoint = cNetwork:CreateUDPEndpoint(0, Callbacks)
+ Endpoint:EnableBroadcasts()
+ if not(Endpoint:Send(Message, Host, Port)) then
+ Endpoint:Close()
+ return true, "Sending UDP datagram failed"
+ end
+ Endpoint:Close()
+ return true, "UDP datagram sent"
+end
+
+
+
+
+
+function HandleConsoleNetWasc(a_Split)
+ local Callbacks =
+ {
+ OnConnected = function (a_Link)
+ LOG("Connected to webadmin, starting TLS...")
+ local res, msg = a_Link:StartTLSClient("", "", "")
+ if not(res) then
+ LOG("Failed to start TLS client: " .. msg)
+ return
+ end
+ -- We need to send a keep-alive due to #1737
+ a_Link:Send("GET / HTTP/1.0\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n")
+ end,
+
+ OnError = function (a_Link, a_ErrorCode, a_ErrorMsg)
+ LOG("Connection to webadmin failed: " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")")
+ end,
+
+ OnReceivedData = function (a_Link, a_Data)
+ LOG("Received data from webadmin:\r\n" .. a_Data)
+
+ -- Close the link once all the data is received:
+ if (a_Data == "0\r\n\r\n") then -- Poor man's end-of-data detection; works on localhost
+ -- TODO: The Close() method is not yet exported to Lua
+ -- a_Link:Close()
+ end
+ end,
+
+ OnRemoteClosed = function (a_Link)
+ LOG("Connection to webadmin was closed")
+ end,
+ }
+
+ if not(cNetwork:Connect("localhost", "8080", Callbacks)) then
+ LOG("Canot connect to webadmin")
+ end
+
+ return true
+end
+
+
+
+
diff --git a/MCServer/webadmin/files/guest.html b/MCServer/webadmin/files/guest.html
index 7ae78a3f0..4f965b75c 100644
--- a/MCServer/webadmin/files/guest.html
+++ b/MCServer/webadmin/files/guest.html
@@ -1,2 +1,18 @@
-Hello! Welcome to the MCServer WebAdmin.<br>
-This is a default message, edit <b>files/guest.html</b> to add your own custom message.
+<!DOCTYPE html>
+<html lang="en">
+
+ <head>
+ <meta charset="UTF-8" />
+ <title>Guest Information</title>
+ </head>
+
+ <body>
+ <p>
+ Hello! Welcome to the MCServer WebAdmin.
+ </p>
+ <p>
+ This is a default message, edit <b>files/guest.html</b> to add your own custom message.
+ </p>
+ </body>
+
+</html>
diff --git a/MCServer/webadmin/login_template.html b/MCServer/webadmin/login_template.html
index 7a8601065..26b260bd3 100644
--- a/MCServer/webadmin/login_template.html
+++ b/MCServer/webadmin/login_template.html
@@ -17,7 +17,7 @@
<div class="upper">
<div class="wrapper">
<div>
- <form method="get" action="webadmin/" />
+ <form method="get" action="webadmin/">
<button type="submit" value="Log in" style="width:150px;height:25px;font-family:'Source Sans Pro',sans-serif;background:transparent;border:none!important;vertical-align:middle">
<strong><img src="login.gif" style="vertical-align:bottom" /> WebAdmin Log in</strong>
</button>
diff --git a/Tools/ProtoProxy/Server.cpp b/Tools/ProtoProxy/Server.cpp
index 9545af852..5bba98057 100644
--- a/Tools/ProtoProxy/Server.cpp
+++ b/Tools/ProtoProxy/Server.cpp
@@ -33,7 +33,7 @@ int cServer::Init(short a_ListenPort, short a_ConnectPort)
}
#endif // _WIN32
- printf("Generating protocol encryption keypair...\n");
+ LOGINFO("Generating protocol encryption keypair...");
m_PrivateKey.Generate();
m_PublicKeyDER = m_PrivateKey.GetPubKeyDER();
@@ -85,7 +85,7 @@ int cServer::Init(short a_ListenPort, short a_ConnectPort)
void cServer::Run(void)
{
- printf("Server running.\n");
+ LOGINFO("Server running.");
while (true)
{
sockaddr_in Addr;
@@ -97,10 +97,10 @@ void cServer::Run(void)
printf("accept returned an error: %d; bailing out.\n", SocketError);
return;
}
- printf("Client connected, proxying...\n");
+ LOGINFO("Client connected, proxying...");
cConnection Connection(client, *this);
Connection.Run();
- printf("Client disconnected. Ready for another connection.\n");
+ LOGINFO("Client disconnected. Ready for another connection.");
}
}
diff --git a/easyinstall.sh b/easyinstall.sh
index 9b4007144..40fa85cfe 100755
--- a/easyinstall.sh
+++ b/easyinstall.sh
@@ -7,7 +7,7 @@ case $PLATFORM in
"i686") DOWNLOADURL="http://builds.cuberite.org/job/MCServer%20Linux%20x86/lastSuccessfulBuild/artifact/MCServer.tar" ;;
"x86_64") DOWNLOADURL="http://builds.cuberite.org/job/MCServer%20Linux%20x64/lastSuccessfulBuild/artifact/MCServer.tar" ;;
# Assume that all arm devices are a raspi for now.
- "arm*") DOWNLOADURL="http://builds.cuberite.org/job/MCServer%20Linux%20armhf/lastSuccessfulBuild/artifact/MCServer/MCServer.tar"
+ arm*) DOWNLOADURL="http://builds.cuberite.org/job/MCServer%20Linux%20armhf/lastSuccessfulBuild/artifact/MCServer/MCServer.tar"
esac
echo "Downloading precompiled binaries."
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();
}
diff --git a/tests/Network/EchoServer.cpp b/tests/Network/EchoServer.cpp
index 49fb89122..2a420fbac 100644
--- a/tests/Network/EchoServer.cpp
+++ b/tests/Network/EchoServer.cpp
@@ -99,7 +99,7 @@ class cEchoServerCallbacks:
-void DoTest(void)
+static void DoTest(void)
{
LOGD("EchoServer: starting up");
cServerHandlePtr Server = cNetwork::Listen(9876, std::make_shared<cEchoServerCallbacks>());
diff --git a/tests/Network/Google.cpp b/tests/Network/Google.cpp
index 54bfa6de4..ab366f9e2 100644
--- a/tests/Network/Google.cpp
+++ b/tests/Network/Google.cpp
@@ -97,7 +97,7 @@ public:
-void DoTest(void)
+static void DoTest(void)
{
cEvent evtFinish;
diff --git a/tests/Network/NameLookup.cpp b/tests/Network/NameLookup.cpp
index 16fa8042b..4781a59ec 100644
--- a/tests/Network/NameLookup.cpp
+++ b/tests/Network/NameLookup.cpp
@@ -46,7 +46,7 @@ public:
-void DoTest(void)
+static void DoTest(void)
{
cEvent evtFinish;