1
0

Merge branch 'master' into Inventory

This commit is contained in:
Howaner 2015-03-09 22:39:11 +01:00
commit a96c21fc0d
77 changed files with 6381 additions and 295 deletions

6
.gitignore vendored
View File

@ -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

View File

@ -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 ###
```

View File

@ -1,7 +1,7 @@
MCServer: A performant C++ Minecraft Server
www: http://mc-server.org/
Copyright 2014 MCServer Team
Copyright 2011-2015 MCServer Team
------

View File

@ -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 = [[
@ -844,6 +866,7 @@ end</pre>
IsMinecart = { Params = "", Return = "bool", Notes = "Returns true if the entity represents a {{cMinecart|minecart}}" },
IsMob = { Params = "", Return = "bool", Notes = "Returns true if the entity represents any {{cMonster|mob}}." },
IsOnFire = { Params = "", Return = "bool", Notes = "Returns true if the entity is on fire" },
IsOnGround = { Params = "", Return = "bool", Notes = "Returns true if the entity is on ground (not falling, not jumping, not flying)" },
IsPainting = { Params = "", Return = "bool", Notes = "Returns if this entity is a painting." },
IsPawn = { Params = "", Return = "bool", Notes = "Returns true if the entity is a {{cPawn}} descendant." },
IsPickup = { Params = "", Return = "bool", Notes = "Returns true if the entity represents a {{cPickup|pickup}}." },
@ -902,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 =
{
@ -1832,7 +1855,6 @@ a_Player:OpenWindow(Window);
IsGameModeSpectator = { Params = "", Return = "bool", Notes = "Returns true if the player is in the gmSpectator gamemode, or has their gamemode unset and the world is a gmSpectator world." },
IsGameModeSurvival = { Params = "", Return = "bool", Notes = "Returns true if the player is in the gmSurvival gamemode, or has their gamemode unset and the world is a gmSurvival world." },
IsInBed = { Params = "", Return = "bool", Notes = "Returns true if the player is currently lying in a bed." },
IsOnGround = { Params = "", Return = "bool", Notes = "Returns true if the player is on ground (not falling, not jumping, not flying)" },
IsSatiated = { Params = "", Return = "bool", Notes = "Returns true if the player is satiated (cannot eat)." },
IsVisible = { Params = "", Return = "bool", Notes = "Returns true if the player is visible to other players" },
LoadRank = { Params = "", Return = "", Notes = "Reloads the player's rank, message visuals and permissions from the {{cRankManager}}, based on the player's current rank." },
@ -2233,6 +2255,27 @@ end
},
}, -- 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 =
{
Desc = [[
@ -2929,7 +2972,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 =
{

View File

@ -0,0 +1,371 @@
-- Network.lua
-- Defines the documentation for the cNetwork-related classes
return
{
cNetwork =
{
Desc =
[[
This is the namespace for high-level network-related operations. Allows plugins to make TCP
connections to the outside world using a callback-based API.</p>
<p>
All functions in this namespace are static, they should be called on the cNetwork class itself:
<pre class="prettyprint lang-lua">
local Server = cNetwork:Listen(1024, ListenCallbacks);
</pre></p>
]],
AdditionalInfo =
{
{
Header = "Using callbacks",
Contents =
[[
The entire Networking API is callback-based. Whenever an event happens on the network object, a
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 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,
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">
local LinkCallbacks =
{
OnConnected = function ({{cTCPLink|a_TCPLink}})
-- The specified {{cTCPLink|link}} has succeeded in connecting to the remote server.
-- Only called if the link is being connected as a client (using cNetwork:Connect() )
-- Not used for incoming server links
-- All returned values are ignored
end,
OnError = function ({{cTCPLink|a_TCPLink}}, a_ErrorCode, a_ErrorMsg)
-- The specified error has occured on the {{cTCPLink|link}}
-- No other callback will be called for this link from now on
-- For a client link being connected, this reports a connection error (destination unreachable etc.)
-- It is an Undefined Behavior to send data to a_TCPLink in or after this callback
-- All returned values are ignored
end,
OnReceivedData = function ({{cTCPLink|a_TCPLink}}, a_Data)
-- Data has been received on the {{cTCPLink|link}}
-- Will get called whenever there's new data on the {{cTCPLink|link}}
-- a_Data contains the raw received data, as a string
-- All returned values are ignored
end,
OnRemoteClosed = function ({{cTCPLink|a_TCPLink}})
-- The remote peer has closed the {{cTCPLink|link}}
-- The link is already closed, any data sent to it now will be lost
-- No other callback will be called for this link from now on
-- All returned values are ignored
end,
}
</pre></p>
<p>
LookupCallbacks have the following structure:<br/>
<pre class="prettyprint lang-lua">
local LookupCallbacks =
{
OnError = function (a_Query, a_ErrorCode, a_ErrorMsg)
-- The specified error has occured while doing the lookup
-- a_Query is the hostname or IP being looked up
-- No other callback will be called for this lookup from now on
-- All returned values are ignored
end,
OnFinished = function (a_Query)
-- There are no more DNS records for this query
-- a_Query is the hostname or IP being looked up
-- No other callback will be called for this lookup from now on
-- All returned values are ignored
end,
OnNameResolved = function (a_Hostname, a_IP)
-- A DNS record has been found, the specified hostname resolves to the IP
-- Called for both Hostname -&gt; IP and IP -&gt; Hostname lookups
-- May be called multiple times if a hostname resolves to multiple IPs
-- All returned values are ignored
end,
}
</pre></p>
<p>
ListenCallbacks have the following structure:<br/>
<pre class="prettyprint lang-lua">
local ListenCallbacks =
{
OnAccepted = function ({{cTCPLink|a_TCPLink}})
-- A new connection has been accepted and a {{cTCPLink|Link}} for it created
-- It is safe to send data to the link now
-- All returned values are ignored
end,
OnError = function (a_ErrorCode, a_ErrorMsg)
-- The specified error has occured while trying to listen
-- 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,
OnIncomingConnection = function (a_RemoteIP, a_RemotePort, a_LocalPort)
-- A new connection is being accepted, from the specified remote peer
-- This function needs to return either nil to drop the connection,
-- or valid LinkCallbacks to use for the new connection's {{cTCPLink|TCPLink}} object
return SomeLinkCallbacks
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>
]],
},
{
Header = "Example client connection",
Contents =
[[
The following example, adapted from the NetworkTest plugin, shows a simple example of a client
connection using the cNetwork API. It connects to www.google.com on port 80 (http) and sends a http
request for the front page. It dumps the received data to the console.</p>
<p>
First, the callbacks are defined in a table. The OnConnected() callback takes care of sending the http
request once the socket is connected. The OnReceivedData() callback sends all data to the console. The
OnError() callback logs any errors. Then, the connection is initiated using the cNetwork::Connect() API
function.</p>
<p>
<pre class="prettyprint lang-lua">
-- Define the callbacks to use for the client connection:
local ConnectCallbacks =
{
OnConnected = function (a_Link)
-- Connection succeeded, send the http request:
a_Link:Send("GET / HTTP/1.0\r\nHost: www.google.com\r\n\r\n")
end,
OnError = function (a_Link, a_ErrorCode, a_ErrorMsg)
-- Log the error to console:
LOG("An error has occurred while talking to google.com: " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")")
end,
OnReceivedData = function (a_Link, a_Data)
-- Log the received data to console:
LOG("Incoming http data:\r\n" .. a_Data)
end,
OnRemoteClosed = function (a_Link)
-- Log the event into the console:
LOG("Connection to www.google.com closed")
end,
}
-- Connect:
if not(cNetwork:Connect("www.google.com", 80, ConnectCallbacks)) then
-- Highly unlikely, but better check for errors
LOG("Cannot queue connection to www.google.com")
end
-- Note that the connection is being made on the background,
-- there's no guarantee that it's already connected at this point in code
</pre>
]],
},
{
Header = "Example server implementation",
Contents =
[[
The following example, adapted from the NetworkTest plugin, shows a simple example of creating a
server listening on a TCP port using the cNetwork API. The server listens on port 9876 and for
each incoming connection it sends a welcome message and then echoes back whatever the client has
sent ("echo server").</p>
<p>
First, the callbacks for the listening server are defined. The most important of those is the
OnIncomingConnection() callback that must return the LinkCallbacks that will be used for the new
connection. In our simple scenario each connection uses the same callbacks, so a predefined
callbacks table is returned; it is, however, possible to define different callbacks for each
connection. This allows the callbacks to be "personalised", for example by the remote IP or the
time of connection. The OnAccepted() and OnError() callbacks only log that they occurred, there's
no processing needed for them.</p>
<p>
Finally, the cNetwork:Listen() API function is used to create the listening server. The status of
the server is checked and if it is successfully listening, it is stored in a global variable, so
that Lua doesn't garbage-collect it until we actually want the server closed.</p>
<p>
<pre class="prettyprint lang-lua">
-- Define the callbacks used for the incoming connections:
local EchoLinkCallbacks =
{
OnConnected = function (a_Link)
-- This will not be called for a server connection, ever
assert(false, "Unexpected Connect callback call")
end,
OnError = function (a_Link, a_ErrorCode, a_ErrorMsg)
-- Log the error to console:
local RemoteName = "'" .. a_Link:GetRemoteIP() .. ":" .. a_Link:GetRemotePort() .. "'"
LOG("An error has occurred while talking to " .. RemoteName .. ": " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")")
end,
OnReceivedData = function (a_Link, a_Data)
-- Send the received data back to the remote peer
a_Link:Send(Data)
end,
OnRemoteClosed = function (a_Link)
-- Log the event into the console:
local RemoteName = "'" .. a_Link:GetRemoteIP() .. ":" .. a_Link:GetRemotePort() .. "'"
LOG("Connection to '" .. RemoteName .. "' closed")
end,
}
-- Define the callbacks used by the server:
local ListenCallbacks =
{
OnAccepted = function (a_Link)
-- No processing needed, just log that this happened:
LOG("OnAccepted callback called")
end,
OnError = function (a_ErrorCode, a_ErrorMsg)
-- An error has occured while listening for incoming connections, log it:
LOG("Cannot listen, error " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")"
end,
OnIncomingConnection = function (a_RemoteIP, a_RemotePort, a_LocalPort)
-- A new connection is being accepted, give it the EchoCallbacks
return EchoLinkCallbacks
end,
}
-- Start the server:
local Server = cNetwork:Listen(9876, ListenCallbacks)
if not(Server:IsListening()) then
-- The error has been already printed in the OnError() callbacks
-- Just bail out
return;
end
-- Store the server globally, so that it stays open:
g_Server = Server
...
-- Elsewhere in the code, when terminating:
-- Close the server and let it be garbage-collected:
g_Server:Close()
g_Server = nil
</pre>
]],
},
}, -- AdditionalInfo
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." },
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 =
[[
This class wraps a single TCP connection, that has been established. Plugins can create these by
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.</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
cUDPEndpoint =
{
Desc =
[[
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 this object referenced in a global variable for as long as it wants the endpoint open.
]],
Functions =
{
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." },
},
}, -- cUDPEndpoint
}

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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));

View File

@ -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:

View File

@ -0,0 +1,142 @@
-- Info.lua
-- Implements the g_PluginInfo standard plugin description
g_PluginInfo =
{
Name = "NetworkTest",
Version = "1",
Date = "2015-01-28",
Description = [[Implements test code (and examples) for the cNetwork API]],
Commands =
{
},
ConsoleCommands =
{
net =
{
Subcommands =
{
client =
{
HelpString = "Connects, as a client, to a specified webpage (google.com by default) and downloads its front page using HTTP",
Handler = HandleConsoleNetClient,
ParameterCombinations =
{
{
Params = "",
Help = "Connects, as a client, to google.com and downloads its front page using HTTP",
},
{
Params = "host [port]",
Help = "Connects, as a client, to the specified host and downloads its front page using HTTP",
},
}, -- ParameterCombinations
}, -- client
close =
{
HelpString = "Close a listening socket",
Handler = HandleConsoleNetClose,
ParameterCombinations =
{
{
Params = "[Port]",
Help = "Closes a socket listening on the specified port [1024]",
},
}, -- ParameterCombinations
}, -- close
listen =
{
HelpString = "Creates a new listening socket on the specified port with the specified service attached to it",
Handler = HandleConsoleNetListen,
ParameterCombinations =
{
{
Params = "[Port] [Service]",
Help = "Starts listening on the specified port [1024] providing the specified service [echo]",
},
}, -- ParameterCombinations
}, -- listen
lookup =
{
HelpString = "Looks up the IP addresses corresponding to the given hostname (google.com by default)",
Handler = HandleConsoleNetLookup,
ParameterCombinations =
{
{
Params = "",
Help = "Looks up the IP addresses of google.com.",
},
{
Params = "Hostname",
Help = "Looks up the IP addresses of the specified hostname.",
},
{
Params = "IP",
Help = "Looks up the canonical name of the specified IP.",
},
},
}, -- 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
},
}

View File

@ -0,0 +1,490 @@
-- NetworkTest.lua
-- Implements a few tests for the cNetwork API
--- Map of all servers currently open
-- 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
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 =
{
-- Echo service: each connection echoes back what has been sent to it
echo = function (a_Port)
return
{
-- A new connection has come, give it new link-callbacks:
OnIncomingConnection = function (a_RemoteIP, a_RemotePort)
return
{
OnError = function (a_Link, a_ErrorCode, a_ErrorMsg)
LOG("EchoServer(" .. a_Port .. ": Connection to " .. a_Link:GetRemoteIP() .. ":" .. a_Link:GetRemotePort() .. " failed: " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")")
end,
OnReceivedData = function (a_Link, a_Data)
-- Echo the received data back to the link:
a_Link:Send(a_Data)
end,
OnRemoteClosed = function (a_Link)
end
} -- Link callbacks
end, -- OnIncomingConnection()
-- Send a welcome message to newly accepted connections:
OnAccepted = function (a_Link)
a_Link:Send("Hello, " .. a_Link:GetRemoteIP() .. ", welcome to the echo server @ MCServer-Lua\r\n")
end, -- OnAccepted()
-- There was an error listening on the port:
OnError = function (a_ErrorCode, a_ErrorMsg)
LOGINFO("EchoServer(" .. a_Port .. ": Cannot listen: " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")")
end, -- OnError()
} -- Listen callbacks
end, -- echo
-- Fortune service: each incoming connection gets a welcome message plus a random fortune text; all communication is ignored afterwards
fortune = function (a_Port)
return
{
-- A new connection has come, give it new link-callbacks:
OnIncomingConnection = function (a_RemoteIP, a_RemotePort)
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 .. ")")
end,
OnReceivedData = function (a_Link, a_Data)
-- Ignore any received data
end,
OnRemoteClosed = function (a_Link)
end
} -- Link callbacks
end, -- OnIncomingConnection()
-- Send a welcome message and the fortune to newly accepted connections:
OnAccepted = function (a_Link)
a_Link:Send("Hello, " .. a_Link:GetRemoteIP() .. ", welcome to the fortune server @ MCServer-Lua\r\n\r\nYour fortune:\r\n")
a_Link:Send(g_Fortunes[math.random(#g_Fortunes)] .. "\r\n")
end, -- OnAccepted()
-- There was an error listening on the port:
OnError = function (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, ...)
}
function Initialize(a_Plugin)
-- Load the splashes.txt file into g_Fortunes, overwriting current content:
local idx = 1
for line in io.lines(a_Plugin:GetLocalFolder() .. "/splashes.txt") do
g_Fortunes[idx] = line
idx = idx + 1
end
-- Use the InfoReg shared library to process the Info.lua file:
dofile(cPluginManager:GetPluginsPath() .. "/InfoReg.lua")
RegisterPluginInfoCommands()
RegisterPluginInfoConsoleCommands()
-- Seed the random generator:
math.randomseed(os.time())
return true
end
function HandleConsoleNetClient(a_Split)
-- Get the address to connect to:
local Host = a_Split[3] or "google.com"
local Port = a_Split[4] or 80
-- Create the callbacks "personalised" for the address:
local Callbacks =
{
OnConnected = function (a_Link)
LOG("Connected to " .. Host .. ":" .. Port .. ".")
LOG("Connection stats: Remote address: " .. a_Link:GetRemoteIP() .. ":" .. a_Link:GetRemotePort() .. ", Local address: " .. a_Link:GetLocalIP() .. ":" .. a_Link:GetLocalPort())
LOG("Sending HTTP request for front page.")
a_Link:Send("GET / HTTP/1.0\r\nHost: " .. Host .. "\r\n\r\n")
end,
OnError = function (a_Link, a_ErrorCode, a_ErrorMsg)
LOG("Connection to " .. Host .. ":" .. Port .. " failed: " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")")
end,
OnReceivedData = function (a_Link, a_Data)
LOG("Received data from " .. Host .. ":" .. Port .. ":\r\n" .. a_Data)
end,
OnRemoteClosed = function (a_Link)
LOG("Connection to " .. Host .. ":" .. Port .. " was closed by the remote peer.")
end
}
-- Queue a connect request:
local res = cNetwork:Connect(Host, Port, Callbacks)
if not(res) then
LOGWARNING("cNetwork:Connect call failed immediately")
return true
end
return true, "Client connection request queued."
end
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: \"" .. a_Split[3] .. "\"."
end
-- Close the server, if there is one:
if not(g_Servers[Port]) then
return true, "There is no server currently listening on port " .. Port .. "."
end
g_Servers[Port]:Close()
g_Servers[Port] = nil
return true, "Port " .. Port .. " closed."
end
function HandleConsoleNetLookup(a_Split)
-- Get the name to look up:
local Addr = a_Split[3] or "google.com"
-- Create the callbacks "personalised" for the host:
local Callbacks =
{
OnNameResolved = function (a_Hostname, a_IP)
LOG(a_Hostname .. " resolves to " .. a_IP)
end,
OnError = function (a_Query, a_ErrorCode, a_ErrorMsg)
LOG("Failed to retrieve information for " .. a_Query .. ": " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")")
assert(a_Query == Addr)
end,
OnFinished = function (a_Query)
LOG("Resolving " .. a_Query .. " has finished.")
assert(a_Query == Addr)
end,
}
-- Queue both name and IP DNS queries;
-- we don't distinguish between an IP and a hostname in this command so we don't know which one to use:
local res = cNetwork:HostnameToIP(Addr, Callbacks)
if not(res) then
LOGWARNING("cNetwork:HostnameToIP call failed immediately")
return true
end
res = cNetwork:IPToHostname(Addr, Callbacks)
if not(res) then
LOGWARNING("cNetwork:IPToHostname call failed immediately")
return true
end
return true, "DNS query has been queued."
end
function HandleConsoleNetListen(a_Split)
-- Get the params:
local Port = tonumber(a_Split[3] or 1024)
if not(Port) then
return true, "Invalid port: \"" .. a_Split[3] .. "\"."
end
local Service = string.lower(a_Split[4] or "echo")
-- Create the callbacks specific for the service:
if (g_Services[Service] == nil) then
return true, "No such service: " .. Service
end
local Callbacks = g_Services[Service](Port)
-- Start the server:
local srv = cNetwork:Listen(Port, Callbacks)
if not(srv:IsListening()) then
-- The error message has already been printed in the Callbacks.OnError()
return true
end
g_Servers[Port] = srv
return true, Service .. " server started on port " .. Port
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

View File

@ -0,0 +1,357 @@
As seen on TV!
Awesome!
100% pure!
May contain nuts!
Better than Prey!
More polygons!
Sexy!
Limited edition!
Flashing letters!
Made by Notch!
It's here!
Best in class!
It's finished!
Kind of dragon free!
Excitement!
More than 500 sold!
One of a kind!
Heaps of hits on YouTube!
Indev!
Spiders everywhere!
Check it out!
Holy cow, man!
It's a game!
Made in Sweden!
Uses LWJGL!
Reticulating splines!
Minecraft!
Yaaay!
Singleplayer!
Keyboard compatible!
Undocumented!
Ingots!
Exploding creepers!
That's no moon!
l33t!
Create!
Survive!
Dungeon!
Exclusive!
The bee's knees!
Down with O.P.P.!
Closed source!
Classy!
Wow!
Not on steam!
Oh man!
Awesome community!
Pixels!
Teetsuuuuoooo!
Kaaneeeedaaaa!
Now with difficulty!
Enhanced!
90% bug free!
Pretty!
12 herbs and spices!
Fat free!
Absolutely no memes!
Free dental!
Ask your doctor!
Minors welcome!
Cloud computing!
Legal in Finland!
Hard to label!
Technically good!
Bringing home the bacon!
Indie!
GOTY!
Ceci n'est pas une title screen!
Euclidian!
Now in 3D!
Inspirational!
Herregud!
Complex cellular automata!
Yes, sir!
Played by cowboys!
OpenGL 2.1 (if supported)!
Thousands of colors!
Try it!
Age of Wonders is better!
Try the mushroom stew!
Sensational!
Hot tamale, hot hot tamale!
Play him off, keyboard cat!
Guaranteed!
Macroscopic!
Bring it on!
Random splash!
Call your mother!
Monster infighting!
Loved by millions!
Ultimate edition!
Freaky!
You've got a brand new key!
Water proof!
Uninflammable!
Whoa, dude!
All inclusive!
Tell your friends!
NP is not in P!
Notch <3 ez!
Music by C418!
Livestreamed!
Haunted!
Polynomial!
Terrestrial!
All is full of love!
Full of stars!
Scientific!
Cooler than Spock!
Collaborate and listen!
Never dig down!
Take frequent breaks!
Not linear!
Han shot first!
Nice to meet you!
Buckets of lava!
Ride the pig!
Larger than Earth!
sqrt(-1) love you!
Phobos anomaly!
Punching wood!
Falling off cliffs!
0% sugar!
150% hyperbole!
Synecdoche!
Let's danec!
Seecret Friday update!
Reference implementation!
Lewd with two dudes with food!
Kiss the sky!
20 GOTO 10!
Verlet intregration!
Peter Griffin!
Do not distribute!
Cogito ergo sum!
4815162342 lines of code!
A skeleton popped out!
The Work of Notch!
The sum of its parts!
BTAF used to be good!
I miss ADOM!
umop-apisdn!
OICU812!
Bring me Ray Cokes!
Finger-licking!
Thematic!
Pneumatic!
Sublime!
Octagonal!
Une baguette!
Gargamel plays it!
Rita is the new top dog!
SWM forever!
Representing Edsbyn!
Matt Damon!
Supercalifragilisticexpialidocious!
Consummate V's!
Cow Tools!
Double buffered!
Fan fiction!
Flaxkikare!
Jason! Jason! Jason!
Hotter than the sun!
Internet enabled!
Autonomous!
Engage!
Fantasy!
DRR! DRR! DRR!
Kick it root down!
Regional resources!
Woo, facepunch!
Woo, somethingawful!
Woo, /v/!
Woo, tigsource!
Woo, minecraftforum!
Woo, worldofminecraft!
Woo, reddit!
Woo, 2pp!
Google anlyticsed!
Now supports åäö!
Give us Gordon!
Tip your waiter!
Very fun!
12345 is a bad password!
Vote for net neutrality!
Lives in a pineapple under the sea!
MAP11 has two names!
Omnipotent!
Gasp!
...!
Bees, bees, bees, bees!
Jag känner en bot!
This text is hard to read if you play the game at the default resolution, but at 1080p it's fine!
Haha, LOL!
Hampsterdance!
Switches and ores!
Menger sponge!
idspispopd!
Eple (original edit)!
So fresh, so clean!
Slow acting portals!
Try the Nether!
Don't look directly at the bugs!
Oh, ok, Pigmen!
Finally with ladders!
Scary!
Play Minecraft, Watch Topgear, Get Pig!
Twittered about!
Jump up, jump up, and get down!
Joel is neat!
A riddle, wrapped in a mystery!
Huge tracts of land!
Welcome to your Doom!
Stay a while, stay forever!
Stay a while and listen!
Treatment for your rash!
"Autological" is!
Information wants to be free!
"Almost never" is an interesting concept!
Lots of truthiness!
The creeper is a spy!
Turing complete!
It's groundbreaking!
Let our battle's begin!
The sky is the limit!
Jeb has amazing hair!
Ryan also has amazing hair!
Casual gaming!
Undefeated!
Kinda like Lemmings!
Follow the train, CJ!
Leveraging synergy!
This message will never appear on the splash screen, isn't that weird?
DungeonQuest is unfair!
110813!
90210!
Check out the far lands!
Tyrion would love it!
Also try VVVVVV!
Also try Super Meat Boy!
Also try Terraria!
Also try Mount And Blade!
Also try Project Zomboid!
Also try World of Goo!
Also try Limbo!
Also try Pixeljunk Shooter!
Also try Braid!
That's super!
Bread is pain!
Read more books!
Khaaaaaaaaan!
Less addictive than TV Tropes!
More addictive than lemonade!
Bigger than a bread box!
Millions of peaches!
Fnord!
This is my true form!
Totally forgot about Dre!
Don't bother with the clones!
Pumpkinhead!
Hobo humping slobo babe!
Made by Jeb!
Has an ending!
Finally complete!
Feature packed!
Boots with the fur!
Stop, hammertime!
Testificates!
Conventional!
Homeomorphic to a 3-sphere!
Doesn't avoid double negatives!
Place ALL the blocks!
Does barrel rolls!
Meeting expectations!
PC gaming since 1873!
Ghoughpteighbteau tchoghs!
Déjà vu!
Déjà vu!
Got your nose!
Haley loves Elan!
Afraid of the big, black bat!
Doesn't use the U-word!
Child's play!
See you next Friday or so!
From the streets of Södermalm!
150 bpm for 400000 minutes!
Technologic!
Funk soul brother!
Pumpa kungen!
日本ハロー!
한국 안녕하세요!
Helo Cymru!
Cześć Polsko!
你好中国!
Привет Россия!
Γεια σου Ελλάδα!
My life for Aiur!
Lennart lennart = new Lennart();
I see your vocabulary has improved!
Who put it there?
You can't explain that!
if not ok then return end
§1C§2o§3l§4o§5r§6m§7a§8t§9i§ac
§kFUNKY LOL
SOPA means LOSER in Swedish!
Big Pointy Teeth!
Bekarton guards the gate!
Mmmph, mmph!
Don't feed avocados to parrots!
Swords for everyone!
Plz reply to my tweet!
.party()!
Take her pillow!
Put that cookie down!
Pretty scary!
I have a suggestion.
Now with extra hugs!
Now Java 6!
Woah.
HURNERJSGER?
What's up, Doc?
Now contains 32 random daily cats!
That's Numberwang!
pls rt
Do you want to join my server?
Put a little fence around it!
Throw a blanket over it!
One day, somewhere in the future, my work will be quoted!
Now with additional stuff!
Extra things!
Yay, puppies for everyone!
So sweet, like a nice bon bon!
Popping tags!
Very influential in its circle!
Now With Multiplayer!
Rise from your grave!
Warning! A huge battleship "STEVE" is approaching fast!
Blue warrior shot the food!
Run, coward! I hunger!
Flavor with no seasoning!
Strange, but not a stranger!
Tougher than diamonds, rich like cream!
Getting ready to show!
Getting ready to know!
Getting ready to drop!
Getting ready to shock!
Getting ready to freak!
Getting ready to speak!
It swings, it jives!
Cruising streets for gold!
Take an eggbeater and beat it against a skillet!
Make me a table, a funky table!
Take the elevator to the mezzanine!
Stop being reasonable, this is the Internet!
/give @a hugs 64
This is good for Realms.
Any computer is a laptop if you're brave enough!

View File

@ -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>

View File

@ -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>

View File

@ -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.");
}
}

View File

@ -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."

View File

@ -8,9 +8,14 @@ SET (SRCS
Bindings.cpp
DeprecatedBindings.cpp
LuaChunkStay.cpp
LuaNameLookup.cpp
LuaServerHandle.cpp
LuaState.cpp
LuaTCPLink.cpp
LuaUDPEndpoint.cpp
LuaWindow.cpp
ManualBindings.cpp
ManualBindings_Network.cpp
ManualBindings_RankManager.cpp
Plugin.cpp
PluginLua.cpp
@ -23,7 +28,11 @@ SET (HDRS
DeprecatedBindings.h
LuaChunkStay.h
LuaFunctions.h
LuaNameLookup.h
LuaServerHandle.h
LuaState.h
LuaTCPLink.h
LuaUDPEndpoint.h
LuaWindow.h
ManualBindings.h
Plugin.h

View File

@ -0,0 +1,88 @@
// LuaNameLookup.cpp
// Implements the cLuaNameLookup class used as the cNetwork API callbacks for name and IP lookups from Lua
#include "Globals.h"
#include "LuaNameLookup.h"
cLuaNameLookup::cLuaNameLookup(const AString & a_Query, cPluginLua & a_Plugin, int a_CallbacksTableStackPos):
m_Plugin(a_Plugin),
m_Callbacks(a_Plugin.GetLuaState(), a_CallbacksTableStackPos),
m_Query(a_Query)
{
}
void cLuaNameLookup::OnNameResolved(const AString & a_Name, const AString & a_IP)
{
// 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, "OnNameResolved"), a_Name, a_IP))
{
LOGINFO("cNetwork name lookup OnNameResolved callback failed in plugin %s looking up %s. %s resolves to %s.",
m_Plugin.GetName().c_str(), m_Query.c_str(), a_Name.c_str(), a_IP.c_str()
);
}
}
void cLuaNameLookup::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"), m_Query, a_ErrorCode, a_ErrorMsg))
{
LOGINFO("cNetwork name lookup OnError callback failed in plugin %s looking up %s. The error is %d (%s)",
m_Plugin.GetName().c_str(), m_Query.c_str(), a_ErrorCode, a_ErrorMsg.c_str()
);
}
}
void cLuaNameLookup::OnFinished(void)
{
// 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, "OnFinished"), m_Query))
{
LOGINFO("cNetwork name lookup OnFinished callback failed in plugin %s, looking up %s.",
m_Plugin.GetName().c_str(), m_Query.c_str()
);
}
}

View File

@ -0,0 +1,46 @@
// LuaNameLookup.h
// Declares the cLuaNameLookup class used as the cNetwork API callbacks for name and IP lookups from Lua
#pragma once
#include "../OSSupport/Network.h"
#include "PluginLua.h"
class cLuaNameLookup:
public cNetwork::cResolveNameCallbacks
{
public:
/** Creates a new instance of the lookup callbacks for the specified query,
attached to the specified lua plugin and wrapping the callbacks that are in a table at the specified stack pos. */
cLuaNameLookup(const AString & a_Query, cPluginLua & a_Plugin, int a_CallbacksTableStackPos);
protected:
/** The plugin for which the query is created. */
cPluginLua & m_Plugin;
/** The Lua table that holds the callbacks to be invoked. */
cLuaState::cRef m_Callbacks;
/** The query used to start the lookup (either hostname or IP). */
AString m_Query;
// cNetwork::cResolveNameCallbacks overrides:
virtual void OnNameResolved(const AString & a_Name, const AString & a_IP) override;
virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override;
virtual void OnFinished(void) override;
};

View File

@ -0,0 +1,209 @@
// LuaServerHandle.cpp
// Implements the cLuaServerHandle class wrapping the cServerHandle functionality for Lua API
#include "Globals.h"
#include "LuaServerHandle.h"
#include "LuaTCPLink.h"
#include "tolua++/include/tolua++.h"
cLuaServerHandle::cLuaServerHandle(UInt16 a_Port, cPluginLua & a_Plugin, int a_CallbacksTableStackPos):
m_Plugin(a_Plugin),
m_Callbacks(a_Plugin.GetLuaState(), a_CallbacksTableStackPos),
m_Port(a_Port)
{
}
cLuaServerHandle::~cLuaServerHandle()
{
// If the server handle is still open, close it explicitly:
Close();
}
void cLuaServerHandle::SetServerHandle(cServerHandlePtr a_ServerHandle, cLuaServerHandlePtr a_Self)
{
ASSERT(m_ServerHandle == nullptr); // The handle can be set only once
m_ServerHandle = a_ServerHandle;
m_Self = a_Self;
}
void cLuaServerHandle::Close(void)
{
// Safely grab a copy of the server handle:
cServerHandlePtr ServerHandle = m_ServerHandle;
if (ServerHandle == nullptr)
{
return;
}
// Close the underlying server handle:
ServerHandle->Close();
// Close all connections at this server:
cLuaTCPLinkPtrs Connections;
{
cCSLock Lock(m_CSConnections);
std::swap(Connections, m_Connections);
}
for (auto & conn: Connections)
{
conn->Close();
}
Connections.clear();
// Allow the internal server handle to be deallocated:
m_ServerHandle.reset();
}
bool cLuaServerHandle::IsListening(void)
{
// Safely grab a copy of the server handle:
cServerHandlePtr ServerHandle = m_ServerHandle;
if (ServerHandle == nullptr)
{
return false;
}
// Query the underlying server handle:
return ServerHandle->IsListening();
}
void cLuaServerHandle::RemoveLink(cLuaTCPLink * a_Link)
{
cCSLock Lock(m_CSConnections);
for (auto itr = m_Connections.begin(), end = m_Connections.end(); itr != end; ++itr)
{
if (itr->get() == a_Link)
{
m_Connections.erase(itr);
break;
}
} // for itr - m_Connections[]
}
void cLuaServerHandle::Release(void)
{
// Close the server, if it isn't closed yet:
Close();
// Allow self to deallocate:
m_Self.reset();
}
cTCPLink::cCallbacksPtr cLuaServerHandle::OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort)
{
// If not valid anymore, drop the connection:
if (!m_Callbacks.IsValid())
{
return nullptr;
}
// Ask the plugin for link callbacks:
cPluginLua::cOperation Op(m_Plugin);
cLuaState::cRef LinkCallbacks;
if (
!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnIncomingConnection"), a_RemoteIPAddress, a_RemotePort, m_Port, cLuaState::Return, LinkCallbacks) ||
!LinkCallbacks.IsValid()
)
{
LOGINFO("cNetwork server (port %d) OnIncomingConnection callback failed in plugin %s. Dropping connection.",
m_Port, m_Plugin.GetName().c_str()
);
return nullptr;
}
// Create the link wrapper to use with the callbacks:
auto res = std::make_shared<cLuaTCPLink>(m_Plugin, std::move(LinkCallbacks), m_Self);
// Add the link to the list of our connections:
cCSLock Lock(m_CSConnections);
m_Connections.push_back(res);
return res;
}
void cLuaServerHandle::OnAccepted(cTCPLink & a_Link)
{
// Check if we're still valid:
if (!m_Callbacks.IsValid())
{
return;
}
// Notify the plugin:
cPluginLua::cOperation Op(m_Plugin);
if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnAccepted"), static_cast<cLuaTCPLink *>(a_Link.GetCallbacks().get())))
{
LOGINFO("cNetwork server (port %d) OnAccepted callback failed in plugin %s, connection to %s:%d.",
m_Port, m_Plugin.GetName().c_str(), a_Link.GetRemoteIP().c_str(), a_Link.GetRemotePort()
);
return;
}
}
void cLuaServerHandle::OnError(int a_ErrorCode, const AString & a_ErrorMsg)
{
// Check if we're still valid:
if (!m_Callbacks.IsValid())
{
return;
}
// Notify the plugin:
cPluginLua::cOperation Op(m_Plugin);
if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnError"), a_ErrorCode, a_ErrorMsg))
{
LOGINFO("cNetwork server (port %d) OnError callback failed in plugin %s. The error is %d (%s).",
m_Port, m_Plugin.GetName().c_str(), a_ErrorCode, a_ErrorMsg.c_str()
);
return;
}
}

View File

@ -0,0 +1,89 @@
// LuaServerHandle.h
// Declares the cLuaServerHandle class wrapping the cServerHandle functionality for Lua API
#pragma once
#include "../OSSupport/Network.h"
#include "PluginLua.h"
// fwd:
class cLuaTCPLink;
typedef SharedPtr<cLuaTCPLink> cLuaTCPLinkPtr;
typedef std::vector<cLuaTCPLinkPtr> cLuaTCPLinkPtrs;
class cLuaServerHandle;
typedef SharedPtr<cLuaServerHandle> cLuaServerHandlePtr;
class cLuaServerHandle:
public cNetwork::cListenCallbacks
{
public:
/** Creates a new instance of the server handle,
attached to the specified lua plugin and wrapping the (listen-) callbacks that are in a table at the specified stack pos. */
cLuaServerHandle(UInt16 a_Port, cPluginLua & a_Plugin, int a_CallbacksTableStackPos);
~cLuaServerHandle();
/** Called by cNetwork::Listen()'s binding.
Sets the server handle around which this instance is wrapped, and a self SharedPtr for link management. */
void SetServerHandle(cServerHandlePtr a_ServerHandle, cLuaServerHandlePtr a_Self);
/** Terminates all connections and closes the listening socket. */
void Close(void);
/** Returns true if the server is currently listening. */
bool IsListening(void);
/** Removes the link from the list of links that the server is currently tracking. */
void RemoveLink(cLuaTCPLink * a_Link);
/** 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 server is created. */
cPluginLua & m_Plugin;
/** The Lua table that holds the callbacks to be invoked. */
cLuaState::cRef m_Callbacks;
/** The port on which the server is listening.
Used mainly for better error reporting. */
UInt16 m_Port;
/** The cServerHandle around which this instance is wrapped. */
cServerHandlePtr m_ServerHandle;
/** Protects m_Connections against multithreaded access. */
cCriticalSection m_CSConnections;
/** All connections that are currently active in this server.
Protected by m_CSConnections. */
cLuaTCPLinkPtrs m_Connections;
/** SharedPtr to self, given out to newly created links. */
cLuaServerHandlePtr m_Self;
// cNetwork::cListenCallbacks overrides:
virtual cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort) override;
virtual void OnAccepted(cTCPLink & a_Link) override;
virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override;
};

View File

@ -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());
@ -656,6 +668,42 @@ void cLuaState::Push(cItems * a_Items)
void cLuaState::Push(cLuaServerHandle * a_ServerHandle)
{
ASSERT(IsValid());
tolua_pushusertype(m_LuaState, a_ServerHandle, "cServerHandle");
m_NumCurrentFunctionArgs += 1;
}
void cLuaState::Push(cLuaTCPLink * a_TCPLink)
{
ASSERT(IsValid());
tolua_pushusertype(m_LuaState, a_TCPLink, "cTCPLink");
m_NumCurrentFunctionArgs += 1;
}
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());
@ -958,6 +1006,15 @@ void cLuaState::GetStackValue(int a_StackPos, pWorld & a_ReturnedVal)
void cLuaState::GetStackValue(int a_StackPos, cRef & a_Ref)
{
a_Ref.RefStack(*this, a_StackPos);
}
bool cLuaState::CallFunction(int a_NumResults)
{
ASSERT (m_NumCurrentFunctionArgs >= 0); // A function must be pushed to stack first
@ -1527,6 +1584,18 @@ cLuaState::cRef::cRef(cLuaState & a_LuaState, int a_StackPos) :
cLuaState::cRef::cRef(cRef && a_FromRef):
m_LuaState(a_FromRef.m_LuaState),
m_Ref(a_FromRef.m_Ref)
{
a_FromRef.m_LuaState = nullptr;
a_FromRef.m_Ref = LUA_REFNIL;
}
cLuaState::cRef::~cRef()
{
if (m_LuaState != nullptr)

View File

@ -59,6 +59,9 @@ class cTNTEntity;
class cHopperEntity;
class cBlockEntity;
class cBoundingBox;
class cLuaTCPLink;
class cLuaServerHandle;
class cLuaUDPEndpoint;
typedef cBoundingBox * pBoundingBox;
typedef cWorld * pWorld;
@ -84,6 +87,10 @@ public:
/** Creates a reference in the specified LuaState for object at the specified StackPos */
cRef(cLuaState & a_LuaState, int a_StackPos);
/** Moves the reference from the specified instance into a newly created instance.
The old instance is then "!IsValid()". */
cRef(cRef && a_FromRef);
~cRef();
/** Creates a reference to Lua object at the specified stack pos, binds this object to it.
@ -178,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);
@ -202,6 +211,9 @@ public:
void Push(cHopperEntity * a_Hopper);
void Push(cItem * a_Item);
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);
@ -241,6 +253,9 @@ public:
/** Retrieve value at a_StackPos, if it is a valid cWorld class. If not, a_Value is unchanged */
void GetStackValue(int a_StackPos, pWorld & a_Value);
/** Store the value at a_StackPos as a reference. */
void GetStackValue(int a_StackPos, cRef & a_Ref);
/** Call the specified Lua function.
Returns true if call succeeded, false if there was an error.
A special param of cRet & signifies the end of param list and the start of return values.

610
src/Bindings/LuaTCPLink.cpp Normal file
View File

@ -0,0 +1,610 @@
// LuaTCPLink.cpp
// Implements the cLuaTCPLink class representing a Lua wrapper for the cTCPLink class and the callbacks it needs
#include "Globals.h"
#include "LuaTCPLink.h"
#include "LuaServerHandle.h"
cLuaTCPLink::cLuaTCPLink(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("cTCPLink in plugin %s: callbacks could not be retrieved", m_Plugin.GetName().c_str());
cPluginLua::cOperation Op(m_Plugin);
Op().LogStackTrace();
}
}
cLuaTCPLink::cLuaTCPLink(cPluginLua & a_Plugin, cLuaState::cRef && a_CallbacksTableRef, cLuaServerHandleWPtr a_ServerHandle):
m_Plugin(a_Plugin),
m_Callbacks(std::move(a_CallbacksTableRef)),
m_Server(std::move(a_ServerHandle))
{
// Warn if the callbacks aren't valid:
if (!m_Callbacks.IsValid())
{
LOGWARNING("cTCPLink in plugin %s: callbacks could not be retrieved", m_Plugin.GetName().c_str());
cPluginLua::cOperation Op(m_Plugin);
Op().LogStackTrace();
}
}
cLuaTCPLink::~cLuaTCPLink()
{
// If the link is still open, close it:
cTCPLinkPtr Link = m_Link;
if (Link != nullptr)
{
Link->Close();
}
Terminated();
}
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)
{
return false;
}
// Send the data:
return Link->Send(a_Data);
}
AString cLuaTCPLink::GetLocalIP(void) const
{
// Safely grab a copy of the link:
cTCPLinkPtr Link = m_Link;
if (Link == nullptr)
{
return "";
}
// Get the IP address:
return Link->GetLocalIP();
}
UInt16 cLuaTCPLink::GetLocalPort(void) const
{
// Safely grab a copy of the link:
cTCPLinkPtr Link = m_Link;
if (Link == nullptr)
{
return 0;
}
// Get the port:
return Link->GetLocalPort();
}
AString cLuaTCPLink::GetRemoteIP(void) const
{
// Safely grab a copy of the link:
cTCPLinkPtr Link = m_Link;
if (Link == nullptr)
{
return "";
}
// Get the IP address:
return Link->GetRemoteIP();
}
UInt16 cLuaTCPLink::GetRemotePort(void) const
{
// Safely grab a copy of the link:
cTCPLinkPtr Link = m_Link;
if (Link == nullptr)
{
return 0;
}
// Get the port:
return Link->GetRemotePort();
}
void cLuaTCPLink::Shutdown(void)
{
// Safely grab a copy of the link and shut it down:
cTCPLinkPtr Link = m_Link;
if (Link != nullptr)
{
if (m_SslContext != nullptr)
{
m_SslContext->NotifyClose();
m_SslContext->ResetSelf();
m_SslContext.reset();
}
Link->Shutdown();
}
}
void cLuaTCPLink::Close(void)
{
// If the link is still open, close it:
cTCPLinkPtr Link = m_Link;
if (Link != nullptr)
{
if (m_SslContext != nullptr)
{
m_SslContext->NotifyClose();
m_SslContext->ResetSelf();
m_SslContext.reset();
}
Link->Close();
}
Terminated();
}
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:
if (m_Callbacks.IsValid())
{
m_Callbacks.UnRef();
}
// If the managing server is still alive, let it know we're terminating:
auto Server = m_Server.lock();
if (Server != nullptr)
{
Server->RemoveLink(this);
}
// If the link is still open, close it:
{
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());
}
}
void cLuaTCPLink::OnConnected(cTCPLink & a_Link)
{
// 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, "OnConnected"), this))
{
LOGINFO("cTCPLink OnConnected() callback failed in plugin %s.", m_Plugin.GetName().c_str());
}
}
void cLuaTCPLink::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"), this, a_ErrorCode, a_ErrorMsg))
{
LOGINFO("cTCPLink OnError() callback failed in plugin %s; the link error is %d (%s).",
m_Plugin.GetName().c_str(), a_ErrorCode, a_ErrorMsg.c_str()
);
}
Terminated();
}
void cLuaTCPLink::OnLinkCreated(cTCPLinkPtr a_Link)
{
// Store the cTCPLink for later use:
m_Link = a_Link;
}
void cLuaTCPLink::OnReceivedData(const char * a_Data, size_t a_Length)
{
// Check if we're still valid:
if (!m_Callbacks.IsValid())
{
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)))
{
LOGINFO("cTCPLink OnReceivedData callback failed in plugin %s.", m_Plugin.GetName().c_str());
}
}
void cLuaTCPLink::OnRemoteClosed(void)
{
// 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, "OnRemoteClosed"), this))
{
LOGINFO("cTCPLink OnRemoteClosed() callback failed in plugin %s.", m_Plugin.GetName().c_str());
}
Terminated();
}
////////////////////////////////////////////////////////////////////////////////
// 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);
}

181
src/Bindings/LuaTCPLink.h Normal file
View File

@ -0,0 +1,181 @@
// LuaTCPLink.h
// Declares the cLuaTCPLink class representing a Lua wrapper for the cTCPLink class and the callbacks it needs
#pragma once
#include "../OSSupport/Network.h"
#include "PluginLua.h"
#include "../PolarSSL++/SslContext.h"
// fwd:
class cLuaServerHandle;
typedef WeakPtr<cLuaServerHandle> cLuaServerHandleWPtr;
class cLuaTCPLink:
public cNetwork::cConnectCallbacks,
public cTCPLink::cCallbacks
{
public:
/** Creates a new instance of the link, attached to the specified plugin and wrapping the callbacks that are in a table at the specified stack pos. */
cLuaTCPLink(cPluginLua & a_Plugin, int a_CallbacksTableStackPos);
/** Creates a new instance of the link, attached to the specified plugin and wrapping the callbacks that are in the specified referenced table. */
cLuaTCPLink(cPluginLua & a_Plugin, cLuaState::cRef && a_CallbacksTableRef, cLuaServerHandleWPtr a_Server);
~cLuaTCPLink();
/** Sends the data contained in the string to the remote peer.
Returns true if successful, false on immediate failure (queueing the data failed or link not available). */
bool Send(const AString & a_Data);
/** Returns the IP address of the local endpoint of the connection. */
AString GetLocalIP(void) const;
/** Returns the port used by the local endpoint of the connection. */
UInt16 GetLocalPort(void) const;
/** Returns the IP address of the remote endpoint of the connection. */
AString GetRemoteIP(void) const;
/** Returns the port used by the remote endpoint of the connection. */
UInt16 GetRemotePort(void) const;
/** Closes the link gracefully.
The link will send any queued outgoing data, then it will send the FIN packet.
The link will still receive incoming data from remote until the remote closes the connection. */
void Shutdown(void);
/** Drops the connection without any more processing.
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;
/** The Lua table that holds the callbacks to be invoked. */
cLuaState::cRef m_Callbacks;
/** The underlying link representing the connection.
May be nullptr. */
cTCPLinkPtr m_Link;
/** 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;
// cTCPLink::cCallbacks overrides:
virtual void OnLinkCreated(cTCPLinkPtr a_Link) override;
virtual void OnReceivedData(const char * a_Data, size_t a_Length) override;
virtual void OnRemoteClosed(void) override;
// The OnError() callback is shared with cNetwork::cConnectCallbacks
};

View File

@ -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();
}

View File

@ -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;
};

View File

@ -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);
@ -165,6 +309,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 +336,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 +354,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 +372,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;
}
@ -256,7 +432,7 @@ static int tolua_Base64Decode(lua_State * tolua_S)
static cPluginLua * GetLuaPlugin(lua_State * L)
cPluginLua * GetLuaPlugin(lua_State * L)
{
// Get the plugin identification out of LuaState:
lua_getglobal(L, LUA_PLUGIN_INSTANCE_VAR_NAME);
@ -2171,11 +2347,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 +2366,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,6 +3653,14 @@ static int tolua_cCompositeChat_UnderlineUrls(lua_State * tolua_S)
void ManualBindings::Bind(lua_State * tolua_S)
{
tolua_beginmodule(tolua_S, nullptr);
// 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, "StringSplitAndTrim", tolua_StringSplitAndTrim);
@ -3397,6 +3671,7 @@ void ManualBindings::Bind(lua_State * tolua_S)
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,9 +3828,23 @@ 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);
tolua_endmodule(tolua_S);
}

View File

@ -1,6 +1,7 @@
#pragma once
struct lua_State;
class cPluginLua;
@ -17,8 +18,17 @@ protected:
/** Binds the manually implemented cRankManager glue code to tolua_S.
Implemented in ManualBindings_RankManager.cpp. */
static void BindRankManager(lua_State * tolua_S);
/** Binds the manually implemented cNetwork-related API to tolua_S.
Implemented in ManualBindings_Network.cpp. */
static void BindNetwork(lua_State * tolua_S);
};
extern cPluginLua * GetLuaPlugin(lua_State * L);

View File

@ -0,0 +1,942 @@
// ManualBindings_Network.cpp
// Implements the cNetwork-related API bindings for Lua
#include "Globals.h"
#include "LuaTCPLink.h"
#include "ManualBindings.h"
#include "tolua++/include/tolua++.h"
#include "LuaState.h"
#include "LuaTCPLink.h"
#include "LuaNameLookup.h"
#include "LuaServerHandle.h"
#include "LuaUDPEndpoint.h"
////////////////////////////////////////////////////////////////////////////////
// cNetwork API functions:
/** Binds cNetwork::Connect */
static int tolua_cNetwork_Connect(lua_State * L)
{
// Function signature:
// cNetwork:Connect(Host, Port, Callbacks) -> bool
cLuaState S(L);
if (
!S.CheckParamUserTable(1, "cNetwork") ||
!S.CheckParamString(2) ||
!S.CheckParamNumber(3) ||
!S.CheckParamTable(4) ||
!S.CheckParamEnd(5)
)
{
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:
AString Host;
int Port;
S.GetStackValues(2, Host, Port);
// Check validity:
if ((Port < 0) || (Port > 65535))
{
LOGWARNING("cNetwork:Connect() called with invalid port (%d), failing the request.", Port);
S.Push(false);
return 1;
}
// Create the LuaTCPLink glue class:
auto Link = std::make_shared<cLuaTCPLink>(*Plugin, 4);
// Try to connect:
bool res = cNetwork::Connect(Host, static_cast<UInt16>(Port), Link, Link);
S.Push(res);
return 1;
}
/** 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::HostnameToIP */
static int tolua_cNetwork_HostnameToIP(lua_State * L)
{
// Function signature:
// cNetwork:HostnameToIP(Host, Callbacks) -> bool
cLuaState S(L);
if (
!S.CheckParamUserTable(1, "cNetwork") ||
!S.CheckParamString(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:
AString Host;
S.GetStackValue(2, Host);
// Try to look up:
bool res = cNetwork::HostnameToIP(Host, std::make_shared<cLuaNameLookup>(Host, *Plugin, 3));
S.Push(res);
return 1;
}
/** Binds cNetwork::IPToHostname */
static int tolua_cNetwork_IPToHostname(lua_State * L)
{
// Function signature:
// cNetwork:IPToHostname(IP, Callbacks) -> bool
cLuaState S(L);
if (
!S.CheckParamUserTable(1, "cNetwork") ||
!S.CheckParamString(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:
AString Host;
S.GetStackValue(2, Host);
// Try to look up:
bool res = cNetwork::IPToHostName(Host, std::make_shared<cLuaNameLookup>(Host, *Plugin, 3));
S.Push(res);
return 1;
}
/** Binds cNetwork::Listen */
static int tolua_cNetwork_Listen(lua_State * L)
{
// Function signature:
// cNetwork:Listen(Port, Callbacks) -> cServerHandle
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);
if ((Port < 0) || (Port > 65535))
{
LOGWARNING("cNetwork:Listen() called with invalid port (%d), failing the request.", Port);
S.Push(false);
return 1;
}
UInt16 Port16 = static_cast<UInt16>(Port);
// Create the LuaTCPLink glue class:
auto Srv = std::make_shared<cLuaServerHandle>(Port16, *Plugin, 3);
// Listen:
Srv->SetServerHandle(cNetwork::Listen(Port16, Srv), Srv);
// Register the server to be garbage-collected by Lua:
tolua_pushusertype(L, Srv.get(), "cServerHandle");
tolua_register_gc(L, lua_gettop(L));
// Return the server handle wrapper:
S.Push(Srv.get());
return 1;
}
////////////////////////////////////////////////////////////////////////////////
// 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::Close */
static int tolua_cTCPLink_Close(lua_State * L)
{
// Function signature:
// LinkInstance:Close()
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:Close(): invalid link object. Stack trace:");
S.LogStackTrace();
return 0;
}
Link = *static_cast<cLuaTCPLink **>(lua_touserdata(L, 1));
// CLose the link:
Link->Close();
return 0;
}
/** Binds cLuaTCPLink::GetLocalIP */
static int tolua_cTCPLink_GetLocalIP(lua_State * L)
{
// Function signature:
// LinkInstance:GetLocalIP() -> string
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:GetLocalIP(): invalid link object. Stack trace:");
S.LogStackTrace();
return 0;
}
Link = *static_cast<cLuaTCPLink **>(lua_touserdata(L, 1));
// Get the IP:
S.Push(Link->GetLocalIP());
return 1;
}
/** Binds cLuaTCPLink::GetLocalPort */
static int tolua_cTCPLink_GetLocalPort(lua_State * L)
{
// Function signature:
// LinkInstance:GetLocalPort() -> number
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:GetLocalPort(): invalid link object. Stack trace:");
S.LogStackTrace();
return 0;
}
Link = *static_cast<cLuaTCPLink **>(lua_touserdata(L, 1));
// Get the Port:
S.Push(Link->GetLocalPort());
return 1;
}
/** Binds cLuaTCPLink::GetRemoteIP */
static int tolua_cTCPLink_GetRemoteIP(lua_State * L)
{
// Function signature:
// LinkInstance:GetRemoteIP() -> string
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:GetRemoteIP(): invalid link object. Stack trace:");
S.LogStackTrace();
return 0;
}
Link = *static_cast<cLuaTCPLink **>(lua_touserdata(L, 1));
// Get the IP:
S.Push(Link->GetRemoteIP());
return 1;
}
/** Binds cLuaTCPLink::GetRemotePort */
static int tolua_cTCPLink_GetRemotePort(lua_State * L)
{
// Function signature:
// LinkInstance:GetRemotePort() -> number
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:GetRemotePort(): invalid link object. Stack trace:");
S.LogStackTrace();
return 0;
}
Link = *static_cast<cLuaTCPLink **>(lua_touserdata(L, 1));
// Get the Port:
S.Push(Link->GetRemotePort());
return 1;
}
/** 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;
}
////////////////////////////////////////////////////////////////////////////////
// cUDPEndpoint bindings (routed through cLuaUDPEndpoint):
/** Called when Lua destroys the object instance.
Close the endpoint and let it deallocate on its own (it's in a SharedPtr). */
static int tolua_collect_cUDPEndpoint(lua_State * L)
{
LOGD("Lua: Collecting cUDPEndpoint");
cLuaUDPEndpoint * Endpoint = static_cast<cLuaUDPEndpoint *>(tolua_tousertype(L, 1, nullptr));
Endpoint->Release();
return 0;
}
/** Binds cLuaUDPEndpoint::Close */
static int tolua_cUDPEndpoint_Close(lua_State * L)
{
// Function signature:
// EndpointInstance:Close()
cLuaState S(L);
if (
!S.CheckParamUserType(1, "cUDPEndpoint") ||
!S.CheckParamEnd(2)
)
{
return 0;
}
// Get the endpoint:
if (lua_isnil(L, 1))
{
LOGWARNING("cUDPEndpoint:Close(): invalid endpoint object. Stack trace:");
S.LogStackTrace();
return 0;
}
auto Endpoint = *static_cast<cLuaUDPEndpoint **>(lua_touserdata(L, 1));
// Close it:
Endpoint->Close();
return 0;
}
/** Binds cLuaUDPEndpoint::EnableBroadcasts */
static int tolua_cUDPEndpoint_EnableBroadcasts(lua_State * L)
{
// Function signature:
// EndpointInstance:EnableBroadcasts()
cLuaState S(L);
if (
!S.CheckParamUserType(1, "cUDPEndpoint") ||
!S.CheckParamEnd(2)
)
{
return 0;
}
// Get the endpoint:
if (lua_isnil(L, 1))
{
LOGWARNING("cUDPEndpoint:EnableBroadcasts(): invalid endpoint object. Stack trace:");
S.LogStackTrace();
return 0;
}
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(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;
}
////////////////////////////////////////////////////////////////////////////////
// Register the bindings:
void ManualBindings::BindNetwork(lua_State * tolua_S)
{
// Create the cNetwork API classes:
tolua_usertype(tolua_S, "cNetwork");
tolua_cclass(tolua_S, "cNetwork", "cNetwork", "", nullptr);
tolua_usertype(tolua_S, "cTCPLink");
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, "CreateUDPEndpoint", tolua_cNetwork_CreateUDPEndpoint);
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);
}

View File

@ -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;

View File

@ -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);
@ -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";

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -53,6 +53,18 @@ public:
}
}
// 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

View File

@ -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);
}
}
} ;

View File

@ -777,9 +777,21 @@ 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))
// 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)
{
case biTaiga:
case biFrozenOcean:
case biFrozenRiver:
case biIcePlains:
case biIceMountains:
case biTaigaHills:
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++)
{
// 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))
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++)
case E_BLOCK_AIR:
{
switch (BlockType[i])
{
case E_BLOCK_AIR:
{
MaxSize = 0;
break;
}
case E_BLOCK_SNOW:
{
MaxSize = std::min(BlockMeta[i] + 1, MaxSize);
break;
}
}
MaxSize = 0;
break;
}
if (TopMeta < MaxSize)
case E_BLOCK_SNOW:
{
FastSetBlock(X, Height, Z, E_BLOCK_SNOW, TopMeta + 1);
}
else if (TopMeta > MaxSize)
{
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:
{
break;
}
} // switch (biome)
if (TopMeta < MaxSize)
{
FastSetBlock(X, Height, Z, E_BLOCK_SNOW, TopMeta + 1);
}
else if (TopMeta > MaxSize)
{
FastSetBlock(X, Height, Z, E_BLOCK_SNOW, TopMeta - 1);
}
}
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);
}
}

View File

@ -1895,9 +1895,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;

View File

@ -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);
}

View File

@ -444,6 +444,9 @@ public:
/** Set the invulnerable ticks from the entity */
void SetInvulnerableTicks(int a_InvulnerableTicks) { m_InvulnerableTicks = a_InvulnerableTicks; }
/** Returns whether the entity is on ground or not */
virtual bool IsOnGround(void) const { return m_bOnGround; }
// tolua_end
/// Called when the specified player right-clicks this entity

View File

@ -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
{

View File

@ -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
@ -121,7 +121,7 @@ public:
inline void SetStance( const double a_Stance) { m_Stance = a_Stance; }
double GetEyeHeight(void) const; // tolua_export
Vector3d GetEyePosition(void) const; // tolua_export
inline bool IsOnGround(void) const {return m_bTouchGround; } // tolua_export
virtual bool IsOnGround(void) const override { return m_bTouchGround; }
inline double GetStance(void) const { return GetPosY() + 1.62; } // tolua_export // TODO: Proper stance when crouching etc.
inline cInventory & GetInventory(void) { return m_Inventory; } // tolua_export
inline const cInventory & GetInventory(void) const { return m_Inventory; }
@ -581,8 +581,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;

View File

@ -1036,7 +1036,7 @@ protected:
////////////////////////////////////////////////////////////////////////////////
// cBioGenGrown:
// cBioGenProtGrown:
class cBioGenProtGrown:
public cBiomeGen

View File

@ -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);
}
}

View File

@ -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);

View File

@ -243,6 +243,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:
@ -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
}

View File

@ -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
{

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -379,8 +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

View File

@ -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:

View File

@ -26,6 +26,8 @@ public:
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;

View File

@ -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;
}
@ -64,8 +64,8 @@ public:
}
// 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;
}
} ;

View File

@ -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++)
{

View File

@ -18,6 +18,7 @@ SET (SRCS
ServerHandleImpl.cpp
StackTrace.cpp
TCPLinkImpl.cpp
UDPEndpointImpl.cpp
)
SET (HDRS
@ -36,6 +37,7 @@ SET (HDRS
ServerHandleImpl.h
StackTrace.h
TCPLinkImpl.h
UDPEndpointImpl.h
)
if(NOT MSVC)

View File

@ -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;
}

View File

@ -90,6 +90,9 @@ public:
Sends the RST packet, queued outgoing and incoming data is lost. */
virtual void Close(void) = 0;
/** Returns the callbacks that are used. */
cCallbacksPtr GetCallbacks(void) const { return m_Callbacks; }
protected:
/** Callbacks to be used for the various situations. */
cCallbacksPtr m_Callbacks;
@ -127,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:
@ -180,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;
@ -239,6 +313,11 @@ 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);
};

View File

@ -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();
}

View File

@ -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);

View File

@ -125,6 +125,7 @@ bool cServerHandleImpl::Listen(UInt16 a_Port)
bool NeedsTwoSockets = false;
int err;
evutil_socket_t MainSock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
if (!IsValidSocket(MainSock))
{
// Failed to create IPv6 socket, create an IPv4 one instead:
@ -138,6 +139,16 @@ bool cServerHandleImpl::Listen(UInt16 a_Port)
return false;
}
// Allow the port to be reused right after the socket closes:
if (evutil_make_listen_socket_reuseable(MainSock) != 0)
{
m_ErrorCode = EVUTIL_SOCKET_ERROR();
Printf(m_ErrorMsg, "Port %d cannot be made reusable: %d (%s). Restarting the server might not work.",
a_Port, m_ErrorCode, evutil_socket_error_to_string(m_ErrorCode)
);
LOG("%s", m_ErrorMsg.c_str());
}
// Bind to all interfaces:
sockaddr_in name;
memset(&name, 0, sizeof(name));
@ -164,6 +175,16 @@ bool cServerHandleImpl::Listen(UInt16 a_Port)
setsockopt(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(MainSock) != 0)
{
m_ErrorCode = EVUTIL_SOCKET_ERROR();
Printf(m_ErrorMsg, "Port %d cannot be made reusable: %d (%s). Restarting the server might not work.",
a_Port, m_ErrorCode, evutil_socket_error_to_string(m_ErrorCode)
);
LOG("%s", m_ErrorMsg.c_str());
}
// Bind to all interfaces:
sockaddr_in6 name;
memset(&name, 0, sizeof(name));
@ -193,6 +214,7 @@ bool cServerHandleImpl::Listen(UInt16 a_Port)
}
m_ConnListener = evconnlistener_new(cNetworkSingleton::Get().GetEventBase(), Callback, this, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 0, MainSock);
m_IsListening = true;
if (!NeedsTwoSockets)
{
return true;
@ -201,6 +223,7 @@ bool cServerHandleImpl::Listen(UInt16 a_Port)
// If a secondary socket is required (WinXP dual-stack), create it here:
LOGD("Creating a second socket for IPv4");
evutil_socket_t SecondSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (!IsValidSocket(SecondSock))
{
err = EVUTIL_SOCKET_ERROR();
@ -208,6 +231,16 @@ bool cServerHandleImpl::Listen(UInt16 a_Port)
return true; // Report as success, the primary socket is working
}
// Allow the port to be reused right after the socket closes:
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.",
a_Port, m_ErrorCode, evutil_socket_error_to_string(m_ErrorCode)
);
LOG("%s", m_ErrorMsg.c_str());
}
// Make the secondary socket nonblocking:
if (evutil_make_socket_nonblocking(SecondSock) != 0)
{
@ -233,7 +266,7 @@ bool cServerHandleImpl::Listen(UInt16 a_Port)
if (listen(SecondSock, 0) != 0)
{
err = EVUTIL_SOCKET_ERROR();
LOGD("Cannot listen on on secondary socket on port %d: %d (%s)", a_Port, err, evutil_socket_error_to_string(err));
LOGD("Cannot listen on secondary socket on port %d: %d (%s)", a_Port, err, evutil_socket_error_to_string(err));
evutil_closesocket(SecondSock);
return true; // Report as success, the primary socket is working
}

View File

@ -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);
@ -221,6 +254,8 @@ void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void
// Pending connection succeeded, call the connection callback:
if (a_What & BEV_EVENT_CONNECTED)
{
Self->UpdateLocalAddress();
Self->UpdateRemoteAddress();
if (Self->m_ConnectCallbacks != nullptr)
{
Self->m_ConnectCallbacks->OnConnected(*Self);
@ -228,8 +263,6 @@ void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void
Self->m_ConnectCallbacks.reset();
return;
}
Self->UpdateLocalAddress();
Self->UpdateRemoteAddress();
}
// If the connection has been closed, call the link callback and remove the connection:
@ -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:

View File

@ -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);
};

View File

@ -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);
}

View File

@ -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);
};

View File

@ -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;
}
}

View File

@ -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);
//*/

View File

@ -679,8 +679,8 @@ void cProtocol172::SendMapDecorators(int a_ID, const cMapDecoratorList & a_Decor
for (cMapDecoratorList::const_iterator it = a_Decorators.begin(); it != a_Decorators.end(); ++it)
{
ASSERT((it->GetPixelX() >= 0) && (it->GetPixelX() < 256));
ASSERT((it->GetPixelZ() >= 0) && (it->GetPixelZ() < 256));
ASSERT(it->GetPixelX() < 256);
ASSERT(it->GetPixelZ() < 256);
Pkt.WriteByte(static_cast<Byte>((it->GetType() << 4) | static_cast<Byte>(it->GetRot() & 0xf)));
Pkt.WriteByte(static_cast<Byte>(it->GetPixelX()));
Pkt.WriteByte(static_cast<Byte>(it->GetPixelZ()));
@ -694,7 +694,7 @@ void cProtocol172::SendMapDecorators(int a_ID, const cMapDecoratorList & a_Decor
void cProtocol172::SendMapInfo(int a_ID, unsigned int a_Scale)
{
ASSERT(m_State == 3); // In game mode?
ASSERT((a_Scale >= 0) && (a_Scale < 256));
ASSERT(a_Scale < 256);
cPacketizer Pkt(*this, 0x34);
Pkt.WriteVarInt(static_cast<UInt32>(a_ID));
@ -1757,7 +1757,10 @@ void cProtocol172::HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer)
void cProtocol172::HandlePacketLoginEncryptionResponse(cByteBuffer & a_ByteBuffer)
{
short EncKeyLength, EncNonceLength;
a_ByteBuffer.ReadBEShort(EncKeyLength);
if (!a_ByteBuffer.ReadBEShort(EncKeyLength))
{
return;
}
if ((EncKeyLength < 0) || (EncKeyLength > MAX_ENC_LEN))
{
LOGD("Invalid Encryption Key length: %d. Kicking client.", EncKeyLength);

View File

@ -386,7 +386,7 @@ void cProtocol180::SendEntityLook(const cEntity & a_Entity)
Pkt.WriteVarInt(a_Entity.GetUniqueID());
Pkt.WriteByteAngle(a_Entity.GetYaw());
Pkt.WriteByteAngle(a_Entity.GetPitch());
Pkt.WriteBool(true); // TODO: IsOnGround() on entities
Pkt.WriteBool(a_Entity.IsOnGround());
}
@ -429,7 +429,7 @@ void cProtocol180::SendEntityRelMove(const cEntity & a_Entity, char a_RelX, char
Pkt.WriteByte(a_RelX);
Pkt.WriteByte(a_RelY);
Pkt.WriteByte(a_RelZ);
Pkt.WriteBool(true); // TODO: IsOnGround() on entities
Pkt.WriteBool(a_Entity.IsOnGround());
}
@ -447,7 +447,7 @@ void cProtocol180::SendEntityRelMoveLook(const cEntity & a_Entity, char a_RelX,
Pkt.WriteByte(a_RelZ);
Pkt.WriteByteAngle(a_Entity.GetYaw());
Pkt.WriteByteAngle(a_Entity.GetPitch());
Pkt.WriteBool(true); // TODO: IsOnGround() on entities
Pkt.WriteBool(a_Entity.IsOnGround());
}
@ -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()));
}
}
@ -947,7 +951,7 @@ void cProtocol180::SendPlayerMoveLook(void)
Pkt.WriteDouble(Player->GetPosX());
// The "+ 0.001" is there because otherwise the player falls through the block they were standing on.
Pkt.WriteDouble(Player->GetStance() + 0.001);
Pkt.WriteDouble(Player->GetPosY() + 0.001);
Pkt.WriteDouble(Player->GetPosZ());
Pkt.WriteFloat((float)Player->GetYaw());
@ -976,7 +980,7 @@ void cProtocol180::SendPlayerSpawn(const cPlayer & a_Player)
Pkt.WriteVarInt(a_Player.GetUniqueID());
Pkt.WriteUUID(cMojangAPI::MakeUUIDShort(a_Player.GetUUID()));
Pkt.WriteFPInt(a_Player.GetPosX());
Pkt.WriteFPInt(a_Player.GetPosY());
Pkt.WriteFPInt(a_Player.GetPosY() + 0.001); // The "+ 0.001" is there because otherwise the player falls through the block they were standing on.
Pkt.WriteFPInt(a_Player.GetPosZ());
Pkt.WriteByteAngle(a_Player.GetYaw());
Pkt.WriteByteAngle(a_Player.GetPitch());
@ -1305,7 +1309,7 @@ void cProtocol180::SendTeleportEntity(const cEntity & a_Entity)
Pkt.WriteFPInt(a_Entity.GetPosZ());
Pkt.WriteByteAngle(a_Entity.GetYaw());
Pkt.WriteByteAngle(a_Entity.GetPitch());
Pkt.WriteBool(true); // TODO: IsOnGrond() on entities
Pkt.WriteBool(a_Entity.IsOnGround());
}

View File

@ -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();
}
@ -3136,8 +3143,11 @@ bool cWSSAnvil::cMCAFile::SetChunkData(const cChunkCoords & a_Chunk, const AStri
// Add padding to 4K boundary:
size_t BytesWritten = a_Data.size() + MCA_CHUNK_HEADER_LENGTH;
static const char Padding[4095] = {0};
m_File.Write(Padding, 4096 - (BytesWritten % 4096));
if (BytesWritten % 4096 != 0)
{
static const char Padding[4095] = {0};
m_File.Write(Padding, 4096 - (BytesWritten % 4096));
}
// Store the header:
ChunkSize = ((u_long)a_Data.size() + MCA_CHUNK_HEADER_LENGTH + 4095) / 4096; // Round data size *up* to nearest 4KB sector, make it a sector number

View File

@ -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>());
@ -119,6 +119,7 @@ void DoTest(void)
LOG("Server terminating.");
Server->Close();
ASSERT(!Server->IsListening());
Server.reset();
LOGD("Server has been closed.");
}

View File

@ -97,7 +97,7 @@ public:
void DoTest(void)
static void DoTest(void)
{
cEvent evtFinish;

View File

@ -46,7 +46,7 @@ public:
void DoTest(void)
static void DoTest(void)
{
cEvent evtFinish;