summaryrefslogtreecommitdiffstats
path: root/MCServer/Plugins
diff options
context:
space:
mode:
Diffstat (limited to 'MCServer/Plugins')
-rw-r--r--MCServer/Plugins/APIDump/APIDesc.lua121
-rw-r--r--MCServer/Plugins/APIDump/Hooks/OnDisconnect.lua24
-rw-r--r--MCServer/Plugins/APIDump/SettingUpZeroBrane.html46
-rw-r--r--MCServer/Plugins/APIDump/Static/zbs_logo.pngbin0 -> 1306 bytes
-rw-r--r--MCServer/Plugins/APIDump/Static/zbs_workspace.pngbin0 -> 72631 bytes
-rw-r--r--MCServer/Plugins/APIDump/UsingChunkStays.html167
-rw-r--r--MCServer/Plugins/APIDump/WebWorldThreads.html48
-rw-r--r--MCServer/Plugins/APIDump/main.css3
-rw-r--r--MCServer/Plugins/APIDump/main_APIDump.lua10
m---------MCServer/Plugins/Core0
-rw-r--r--MCServer/Plugins/Debuggers/Debuggers.lua36
-rw-r--r--MCServer/Plugins/InfoReg.lua12
12 files changed, 405 insertions, 62 deletions
diff --git a/MCServer/Plugins/APIDump/APIDesc.lua b/MCServer/Plugins/APIDump/APIDesc.lua
index 01f000182..1423d64bc 100644
--- a/MCServer/Plugins/APIDump/APIDesc.lua
+++ b/MCServer/Plugins/APIDump/APIDesc.lua
@@ -200,6 +200,8 @@ g_APIDesc =
msFillAir = { Notes = "Dst is overwritten by Src only where Src has air blocks" },
msImprint = { Notes = "Src overwrites Dst anywhere where Dst has non-air blocks" },
msLake = { Notes = "Special mode for merging lake images" },
+ msSpongePrint = { Notes = "Similar to msImprint, sponge block doesn't overwrite anything, all other blocks overwrite everything"},
+ msMask = { Notes = "The blocks that are exactly the same are kept in Dst, all differing blocks are replaced by air"},
},
ConstantGroups =
{
@@ -247,6 +249,9 @@ g_APIDesc =
<tr>
<td> A </td><td> B </td><td> B </td><td> A </td><td> B </td>
</tr>
+ <tr>
+ <td> A </td><td> A </td><td> A </td><td> A </td><td> A </td>
+ </td>
</tbody></table>
<p>
@@ -258,12 +263,24 @@ g_APIDesc =
</ol>
</p>
+ <h3>Special strategies</h3>
+ <p>For each strategy, evaluate the table rows from top downwards, the first match wins.</p>
+
<p>
- Special strategies:
+ <strong>msDifference</strong> - changes all the blocks which are the same to air. Otherwise the source block gets placed.
</p>
-
+ <table><tbody<tr>
+ <th colspan="2"> area block </th><th> </th><th> Notes </th>
+ </tr><tr>
+ <td> * </td><td> B </td><td> B </td><td> The blocks are different so we use block B </td>
+ </tr><tr>
+ <td> B </td><td> B </td><td> Air </td><td> The blocks are the same so we get air. </td>
+ </tr>
+ </tbody></table>
+
+
<p>
- <strong>msLake</strong> (evaluate top-down, first match wins):
+ <strong>msLake</strong> - used for merging areas with lava and water lakes, in the appropriate generator.
</p>
<table><tbody><tr>
<th colspan="2"> area block </th><th> </th><th> Notes </th>
@@ -293,7 +310,39 @@ g_APIDesc =
<td> A </td><td> * </td><td> A </td><td> Everything else is left as it is </td>
</tr>
</tbody></table>
- ]],
+
+ <p>
+ <strong>msSpongePrint</strong> - used for most prefab-generators to merge the prefabs. Similar to
+ msImprint, but uses the sponge block as the NOP block instead, so that the prefabs may carve out air
+ pockets, too.
+ </p>
+ <table><tbody><tr>
+ <th colspan="2"> area block </th><th> </th><th> Notes </th>
+ </tr><tr>
+ <th> this </th><th> Src </th><th> result </th><th> </th>
+ </tr><tr>
+ <td> A </td><td> sponge </td><td> A </td><td> Sponge is the NOP block </td>
+ </tr><tr>
+ <td> * </td><td> B </td><td> B </td><td> Everything else overwrites anything </td>
+ </tr>
+ </tbody></table>
+
+ <p>
+ <strong>msMask</strong> - the blocks that are the same in the other area are kept, all the
+ differing blocks are replaced with air. Meta is used in the comparison, too, two blocks of the
+ same type but different meta are considered different and thus replaced with air.
+ </p>
+ <table><tbody><tr>
+ <th colspan="2"> area block </th><th> </th><th> Notes </th>
+ </tr><tr>
+ <th> this </th><th> Src </th><th> result </th><th> </th>
+ </tr><tr>
+ <td> A </td><td> A </td><td> A </td><td> Same blocks are kept </td>
+ </tr><tr>
+ <td> A </td><td> non-A </td><td> air </td><td> Differing blocks are replaced with air </td>
+ </tr>
+ </tbody></table>
+]],
}, -- Merge strategies
}, -- AdditionalInfo
}, -- cBlockArea
@@ -502,7 +551,22 @@ end
{{cPlayer}}:SendMessage(), {{cWorld}}:BroadcastChat() and {{cRoot}}:BroadcastChat().</p>
<p>
Note that most of the functions in this class are so-called modifiers - they modify the object and
- then return the object itself, so that they can be chained one after another.
+ then return the object itself, so that they can be chained one after another. See the Chaining
+ example below for details.</p>
+ <p>
+ Each part of the composite chat message takes a "Style" parameter, this is a string that describes
+ the formatting. It uses the following strings, concatenated together:
+ <table>
+ <tr><th>String</th><th>Style</th></tr>
+ <tr><td>b</td><td>Bold text</td></tr>
+ <tr><td>i</td><td>Italic text</td></tr>
+ <tr><td>u</td><td>Underlined text</td></tr>
+ <tr><td>s</td><td>Strikethrough text</td></tr>
+ <tr><td>o</td><td>Obfuscated text</td></tr>
+ <tr><td>@X</td><td>color X (X is 0 - 9 or a - f, same as dye meta</td></tr>
+ </table>
+ The following picture, taken from MineCraft Wiki, illustrates the color codes:</p>
+ <img src="http://hydra-media.cursecdn.com/minecraft.gamepedia.com/4/4c/Colors.png?version=34a0f56789a95326e1f7d82047b12232" />
]],
Functions =
{
@@ -516,6 +580,7 @@ end
AddTextPart = { Params = "Text, [Style]", Return = "self", Notes = "Adds a regular text. Chaining." },
AddUrlPart = { Params = "Text, Url, [Style]", Return = "self", Notes = "Adds a text which, when clicked, opens up a browser at the specified URL. Chaining." },
Clear = { Params = "", Return = "", Notes = "Removes all parts from this object" },
+ ExtractText = { Params = "", Return = "string", Notes = "Returns the text from the parts that comprises the human-readable data. Used for older protocols that don't support composite chat and for console-logging." },
GetMessageType = { Params = "", Return = "MessageType", Notes = "Returns the MessageType (mtXXX constant) that is associated with this message. When sent to a player, the message will be formatted according to this message type and the player's settings (adding \"[INFO]\" prefix etc.)" },
ParseText = { Params = "Text", Return = "self", Notes = "Adds text, while recognizing http and https URLs and old-style formatting codes (\"@2\"). Chaining." },
SetMessageType = { Params = "MessageType", Return = "self", Notes = "Sets the MessageType (mtXXX constant) that is associated with this message. When sent to a player, the message will be formatted according to this message type and the player's settings (adding \"[INFO]\" prefix etc.) Chaining." },
@@ -634,7 +699,7 @@ end</pre>
GetLevel = { Params = "EnchantmentNumID", Return = "number", Notes = "Returns the level of the specified enchantment stored in this object; 0 if not stored" },
IsEmpty = { Params = "", Return = "bool", Notes = "Returns true if the object stores no enchantments" },
SetLevel = { Params = "EnchantmentNumID, Level", Return = "", Notes = "Sets the level for the specified enchantment, adding it if not stored before or removing it if level < = 0" },
- StringToEnchantmentID = { Params = "EnchantmentTextID", Return = "number", Notes = "(static) Returns the enchantment numerical ID, -1 if not understood. Case insensitive" },
+ StringToEnchantmentID = { Params = "EnchantmentTextID", Return = "number", Notes = "(static) Returns the enchantment numerical ID, -1 if not understood. Case insensitive. Also understands plain numbers." },
ToString = { Params = "", Return = "string", Notes = "Returns the string description of all the enchantments stored in this object, in numerical-ID form" },
},
Constants =
@@ -728,14 +793,14 @@ end</pre>
GetMass = { Params = "", Return = "number", Notes = "Returns the mass of the entity. Currently unused." },
GetMaxHealth = { Params = "", Return = "number", Notes = "Returns the maximum number of hitpoints this entity is allowed to have." },
GetParentClass = { Params = "", Return = "string", Notes = "Returns the name of the direct parent class for this entity" },
- GetPitch = { Params = "", Return = "number", Notes = "Returns the pitch (nose-down rotation) of the entity" },
+ GetPitch = { Params = "", Return = "number", Notes = "Returns the pitch (nose-down rotation) of the entity. Measured in degrees, normal values range from -90 to +90. +90 means looking down, 0 means looking straight ahead, -90 means looking up." },
GetPosition = { Params = "", Return = "{{Vector3d}}", Notes = "Returns the entity's pivot position as a 3D vector" },
GetPosX = { Params = "", Return = "number", Notes = "Returns the X-coord of the entity's pivot" },
GetPosY = { Params = "", Return = "number", Notes = "Returns the Y-coord of the entity's pivot" },
GetPosZ = { Params = "", Return = "number", Notes = "Returns the Z-coord of the entity's pivot" },
GetRawDamageAgainst = { Params = "ReceiverEntity", Return = "number", Notes = "Returns the raw damage that this entity's equipment would cause when attacking the ReceiverEntity. This includes this entity's weapon {{cEnchantments|enchantments}}, but excludes the receiver's armor or potion effects. See {{TakeDamageInfo}} for more information on attack damage." },
GetRoll = { Params = "", Return = "number", Notes = "Returns the roll (sideways rotation) of the entity. Currently unused." },
- GetRot = { Params = "", Return = "{{Vector3f}}", Notes = "Returns the entire rotation vector (Yaw, Pitch, Roll)" },
+ GetRot = { Params = "", Return = "{{Vector3f}}", Notes = "(OBSOLETE) Returns the entire rotation vector (Yaw, Pitch, Roll)" },
GetSpeed = { Params = "", Return = "{{Vector3d}}", Notes = "Returns the complete speed vector of the entity" },
GetSpeedX = { Params = "", Return = "number", Notes = "Returns the X-part of the speed vector" },
GetSpeedY = { Params = "", Return = "number", Notes = "Returns the Y-part of the speed vector" },
@@ -743,7 +808,7 @@ end</pre>
GetUniqueID = { Params = "", Return = "number", Notes = "Returns the ID that uniquely identifies the entity within the running server. Note that this ID is not persisted to the data files." },
GetWidth = { Params = "", Return = "number", Notes = "Returns the width (X and Z size) of the entity." },
GetWorld = { Params = "", Return = "{{cWorld}}", Notes = "Returns the world where the entity resides" },
- GetYaw = { Params = "", Return = "number", Notes = "Returns the yaw (direction) of the entity." },
+ GetYaw = { Params = "", Return = "number", Notes = "Returns the yaw (direction) of the entity. Measured in degrees, values range from -180 to +180. 0 means ZP, 90 means XM, -180 means ZM, -90 means XP." },
Heal = { Params = "Hitpoints", Return = "", Notes = "Heals the specified number of hitpoints. Hitpoints is expected to be a positive number." },
IsA = { Params = "ClassName", Return = "bool", Notes = "Returns true if the entity class is a descendant of the specified class name, or the specified class itself" },
IsBoat = { Params = "", Return = "bool", Notes = "Returns true if the entity is a {{cBoat|boat}}." },
@@ -2151,7 +2216,7 @@ end
CastThunderbolt = { Params = "X, Y, Z", Return = "", Notes = "Creates a thunderbolt at the specified coords" },
ChangeWeather = { Params = "", Return = "", Notes = "Forces the weather to change in the next game tick. Weather is changed according to the normal rules: wSunny <-> wRain <-> wStorm" },
ChunkStay = { Params = "ChunkCoordTable, OnChunkAvailable, OnAllChunksAvailable", Return = "", Notes = "Queues the specified chunks to be loaded or generated and calls the specified callbacks once they are loaded. ChunkCoordTable is an arra-table of chunk coords, each coord being a table of 2 numbers: { {Chunk1x, Chunk1z}, {Chunk2x, Chunk2z}, ...}. When any of those chunks are made available (including being available at the start of this call), the OnChunkAvailable() callback is called. When all the chunks are available, the OnAllChunksAvailable() callback is called. The function signatures are: <pre class=\"prettyprint lang-lua\">function OnChunkAvailable(ChunkX, ChunkZ)\nfunction OnAllChunksAvailable()</pre> All return values from the callbacks are ignored." },
- CreateProjectile = { Params = "X, Y, Z, {{cProjectileEntity|ProjectileKind}}, {{cEntity|Creator}}, [{{Vector3d|Speed}}]", Return = "", Notes = "Creates a new projectile of the specified kind at the specified coords. The projectile's creator is set to Creator (may be nil). Optional speed indicates the initial speed for the projectile." },
+ CreateProjectile = { Params = "X, Y, Z, {{cProjectileEntity|ProjectileKind}}, {{cEntity|Creator}}, {{cItem|Originating Item}}, [{{Vector3d|Speed}}]", Return = "", Notes = "Creates a new projectile of the specified kind at the specified coords. The projectile's creator is set to Creator (may be nil). The item that created the projectile entity, commonly the {{cPlayer|player}}'s currently equipped item, is used at present for fireworks to correctly set their entity metadata. It is not used for any other projectile. Optional speed indicates the initial speed for the projectile." },
DigBlock = { Params = "X, Y, Z", Return = "", Notes = "Replaces the specified block with air, without dropping the usual pickups for the block. Wakes up the simulators for the block and its neighbors." },
DoExplosionAt = { Params = "Force, X, Y, Z, CanCauseFire, Source, SourceData", Return = "", Notes = "Creates an explosion of the specified relative force in the specified position. If CanCauseFire is set, the explosion will set blocks on fire, too. The Source parameter specifies the source of the explosion, one of the esXXX constants. The SourceData parameter is specific to each source type, usually it provides more info about the source." },
DoWithBlockEntityAt = { Params = "X, Y, Z, CallbackFunction, [CallbackData]", Return = "bool", Notes = "If there is a block entity at the specified coords, calls the CallbackFunction with the {{cBlockEntity}} parameter representing the block entity. The CallbackFunction has the following signature: <pre class=\"prettyprint lang-lua\">function Callback({{cBlockEntity|BlockEntity}}, [CallbackData])</pre> The function returns false if there is no block entity, or if there is, it returns the bool value that the callback has returned. Use {{tolua}}.cast() to cast the Callback's BlockEntity parameter to the correct {{cBlockEntity}} descendant." },
@@ -2647,11 +2712,31 @@ end
ItemToFullString = {Params = "{{cItem|cItem}}", Return = "string", Notes = "Returns the string representation of the item, in the format 'ItemTypeText:ItemDamage * Count'"},
ItemToString = {Params = "{{cItem|cItem}}", Return = "string", Notes = "Returns the string representation of the item type"},
ItemTypeToString = {Params = "ItemType", Return = "string", Notes = "Returns the string representation of ItemType "},
- LOG = {Params = "string", Notes = "Logs a text into the server console using 'normal' severity (gray text) "},
- LOGERROR = {Params = "string", Notes = "Logs a text into the server console using 'error' severity (black text on red background)"},
- LOGINFO = {Params = "string", Notes = "Logs a text into the server console using 'info' severity (yellow text)"},
- LOGWARN = {Params = "string", Notes = "Logs a text into the server console using 'warning' severity (red text); OBSOLETE, use LOGWARNING() instead"},
- LOGWARNING = {Params = "string", Notes = "Logs a text into the server console using 'warning' severity (red text)"},
+ LOG =
+ {
+ {Params = "string", Notes = "Logs a text into the server console using 'normal' severity (gray text)"},
+ {Params = "{{cCompositeChat|CompositeChat}}", Notes = "Logs the {{cCompositeChat}}'s human-readable text into the server console. The severity is converted from the CompositeChat's MessageType."},
+ },
+ LOGERROR =
+ {
+ {Params = "string", Notes = "Logs a text into the server console using 'error' severity (black text on red background)"},
+ {Params = "{{cCompositeChat|CompositeChat}}", Notes = "Logs the {{cCompositeChat}}'s human-readable text into the server console using 'error' severity (black text on red background)"},
+ },
+ LOGINFO =
+ {
+ {Params = "string", Notes = "Logs a text into the server console using 'info' severity (yellow text)"},
+ {Params = "{{cCompositeChat|CompositeChat}}", Notes = "Logs the {{cCompositeChat}}'s human-readable text into the server console using 'info' severity (yellow text)"},
+ },
+ LOGWARN =
+ {
+ {Params = "string", Notes = "Logs a text into the server console using 'warning' severity (red text); OBSOLETE, use LOGWARNING() instead"},
+ {Params = "{{cCompositeChat|CompositeChat}}", Notes = "Logs the {{cCompositeChat}}'s human-readable text into the server console using 'warning' severity (red text); OBSOLETE, use LOGWARNING() instead"},
+ },
+ LOGWARNING =
+ {
+ {Params = "string", Notes = "Logs a text into the server console using 'warning' severity (red text)"},
+ {Params = "{{cCompositeChat|CompositeChat}}", Notes = "Logs the {{cCompositeChat}}'s human-readable text into the server console using 'warning' severity (red text)"},
+ },
MirrorBlockFaceY = { Params = "{{Globals#BlockFaces|eBlockFace}}", Return = "{{Globals#BlockFaces|eBlockFace}}", Notes = "Returns the {{Globals#BlockFaces|eBlockFace}} that corresponds to the given {{Globals#BlockFaces|eBlockFace}} after mirroring it around the Y axis (or rotating 180 degrees around it)." },
NoCaseCompare = {Params = "string, string", Return = "number", Notes = "Case-insensitive string comparison; returns 0 if the strings are the same"},
NormalizeAngleDegrees = { Params = "AngleDegrees", Return = "AngleDegrees", Notes = "Returns the angle, wrapped into the [-180, +180) range." },
@@ -2844,8 +2929,10 @@ end
{
-- No sorting is provided for these, they will be output in the same order as defined here
{ FileName = "Writing-a-MCServer-plugin.html", Title = "Writing a MCServer plugin" },
- { FileName = "SettingUpDecoda.html", Title = "Setting up the Decoda Lua IDE" },
- { FileName = "WebWorldThreads.html", Title = "Webserver vs World threads" },
+ { FileName = "SettingUpDecoda.html", Title = "Setting up the Decoda Lua IDE" },
+ { FileName = "SettingUpZeroBrane.html", Title = "Setting up the ZeroBrane Studio Lua IDE" },
+ { FileName = "UsingChunkStays.html", Title = "Using ChunkStays" },
+ { FileName = "WebWorldThreads.html", Title = "Webserver vs World threads" },
}
} ;
diff --git a/MCServer/Plugins/APIDump/Hooks/OnDisconnect.lua b/MCServer/Plugins/APIDump/Hooks/OnDisconnect.lua
index a3301a8c6..204cb63d2 100644
--- a/MCServer/Plugins/APIDump/Hooks/OnDisconnect.lua
+++ b/MCServer/Plugins/APIDump/Hooks/OnDisconnect.lua
@@ -2,23 +2,33 @@ return
{
HOOK_DISCONNECT =
{
- CalledWhen = "A player has explicitly disconnected.",
+ CalledWhen = [[
+ A client has disconnected, either by explicitly sending the disconnect packet (in older protocols) or
+ their connection was terminated
+ ]],
DefaultFnName = "OnDisconnect", -- also used as pagename
Desc = [[
- This hook is called when a client is about to be disconnected from the server, for whatever reason.
-
- <p><b>Note that this hook will be removed after <1.7 protocol support is removed, as it was originally a hook for
- the client sending the server a disconnect packet, which no longer happens.</b></p>
+ This hook is called when a client has disconnected from the server, for whatever reason. It is also
+ called when the client sends the Disconnect packet (only in pre-1.7 protocols). This hook is not called
+ for server ping connections.</p>
+ <p>
+ Note that the hook is called even for connections to players who failed to auth. In such a case there's
+ no {{cPlayer}} object associated with the client.</p>
+ <p>
+ See also the {{OnHandshake|HOOK_HANDSHAKE}} hook which is called when the client connects (and presents
+ a handshake message, so that they are not just status-pinging). If you need to store a per-player
+ object, use the {{OnPlayerJoined|HOOK_PLAYER_JOINED}} and {{OnPlayerDestroyed|HOOK_PLAYER_DESTROYED}}
+ hooks instead, those are guaranteed to have the {{cPlayer}} object associated.
]],
Params =
{
- { Name = "Player", Type = "{{cPlayer}}", Notes = "The player who has disconnected" },
+ { Name = "Client", Type = "{{cClientHandle}}", Notes = "The client who has disconnected" },
{ Name = "Reason", Type = "string", Notes = "The reason that the client has sent in the disconnect packet" },
},
Returns = [[
If the function returns false or no value, MCServer calls other plugins' callbacks for this event.
If the function returns true, no other plugins are called for this event. In either case,
- the player is disconnected.
+ the client is disconnected.
]],
}, -- HOOK_DISCONNECT
}
diff --git a/MCServer/Plugins/APIDump/SettingUpZeroBrane.html b/MCServer/Plugins/APIDump/SettingUpZeroBrane.html
new file mode 100644
index 000000000..4ebbcb6e6
--- /dev/null
+++ b/MCServer/Plugins/APIDump/SettingUpZeroBrane.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+
+<html>
+ <head>
+ <title>MCServer - Setting up ZeroBrane Studio</title>
+ <link rel="stylesheet" type="text/css" href="main.css" />
+ <link rel="stylesheet" type="text/css" href="prettify.css" />
+ <script src="prettify.js"></script>
+ <script src="lang-lua.js"></script>
+ <meta charset="UTF-8">
+ </head>
+ <body>
+ <div id="content">
+ <h1>Setting up the ZeroBrane Studio IDE</h1>
+ <p>
+ This article will explain how to set up ZeroBrane Studio, an IDE for writing Lua code, so that you can develop MCServer plugins with the comfort of an IDE.</p>
+
+ <h2><img src="Static/zbs_logo.png" /> About ZeroBrane Studio</h2>
+
+ <p>To quickly introduce ZeroBrane Studio, it is an IDE for writing Lua code. It has the basic features expected of an IDE - it allows you to manage groups of files as a project, you can edit multiple files in a tabbed editor, the code is syntax-highlighted. Code completion, symbol browsing, and more. It also features a Lua debugger that allows you to debug your Lua code within any application that uses Lua and can load Lua packages. It is written using the multiplatform WxWidgets toolkit, and runs on multiple platforms, including Windows, Linux and MacOS.</p>
+ <p>Here's a screenshot of a default ZBS window with the debugger stepping through the code (scaled down):<br />
+ <img src="Static/zbs_workspace.png" /></p>
+ <p>As you can see, you can set breakpoints in the code, inspect variables' values, view the Lua call-stacks.</p>
+ <p>ZBS is open-source, the sources are on GitHub: <a href="https://github.com/pkulchenko/ZeroBraneStudio">https://github.com/pkulchenko/ZeroBraneStudio</a>. The project's homepage is at <a href="http://studio.zerobrane.com/">http://studio.zerobrane.com/</a>.
+
+ <h2><img src="Static/zbs_logo.png" /> First-time setup</h2>
+ <p>Since ZBS is a universal Lua IDE, you need to first set it up so that it is ready for MCS plugin development. For that, you need to download one file, <a href="https://raw.githubusercontent.com/pkulchenko/ZeroBranePackage/master/mcserver.lua">mcserver.lua</a> from the <a href="https://github.com/pkulchenko/ZeroBranePackage">ZBS's plugin repository</a>. Place that file in the "packages" folder inside your ZBS's folder. Note that there are other useful plugins in the repository and you may want to have a look there later on to further customize your ZBS. To install them, simply save them into the same folder.</p>
+ <p>Next you should install the code-completion support specific for MCServer. You should repeat this step from time to time, because the API evolves in time so new functions and classes are added to it quite often. You should have an APIDump plugin in your MCServer installation. Enable the APIDump plugin in the server settings, it's very cheap to keep it enabled and it doesn't cost any performance during normal gameplay. To generate the code-completion support file, enter the <code style="background: #ddd; border: 1px solid #aaa">api</code> command into the server console. This will create a new file, "mcserver_api.lua", next to the MCS executable. Move that file into the "api/lua" subfolder inside your ZBS's folder.</p>
+ <p>After you download the mcserver.lua file and install the completion support, you need to restart ZBS in order for the plugin to load. If there are no errors, you should see two new items in the Project -> Lua Interpreter submenu: "MCServer - debug mode" and "MCServer - release mode". The only difference between the two is which filename they use to launch MCServer - mcserver_debug(.exe) for the debug option and "mcserver(.exe)" for the release option. If you built your own MCServer executable and you built it in debug mode, you should select the debug mode option. In all other cases, including if you downloaded the already-compiled MCServer executable from the internet, you should select the release mode option.</p>
+ <p>For a first time user, it might be a bit overwhelming that there are no GUI settings in the ZBS, yet the IDE is very configurable. There are two files that you edit in order to change settings, either system-wide (all users of the computer share those settings) or user-wide (the settings are only for a specific user of the computer). Those files are regular Lua sources and you can quickly locate them and edit them from within the IDE itself, select Edit -> Preferences -> Settings: XYZ from the menu, with XYZ being either System or User.</p>
+ <p>There is a documentation on most of the settings on ZBS's webpage, have a look at <a href="http://studio.zerobrane.com/documentation.html">http://studio.zerobrane.com/documentation.html</a>, especially the Preferences section. Personally I recommend setting editor.usetabs to true and possibly adjusting the editor.tabwidth, turn off the editor.smartindent feature and for debugging the option debugger.alloweditting should be set to true unless you feel like punishing yourself.</p>
+
+ <h2><img src="Static/zbs_logo.png" /> Project management</h2>
+ <p>ZBS works with projects, it considers all files and subfolder in a specific folder to be a project. There's no need for a special project file nor for adding individual files to the workspace, all files are added automatically. To open a MCS plugin as the project, click the triple-dot button in the Project pane, or select Project -> Project directory -> Choose... from the menu. Browse and select the MCS plugin's folder. ZBS will load all the files in the plugin's folder and you can start editting code.</p>
+ <p>Note that although ZBS allows you to work with subfolders in your plugins (and you should, especially with larger plugins), the current mcserver ZBS plugin will not be able to start debugging unless you have a file open in the editor that is at the root level of the MCS plugin's folder.</p>
+
+ <h2><img src="Static/zbs_logo.png" /> Debugging</h2>
+ <p>You are now ready to debug your code. Before doing that, though, don't forget to save your project files. If you haven't done so already, enable your plugin in the settings.ini file. If you want the program to break at a certain line, it is best to set the breakpoint before starting the program. Set the cursor on the line and hit F9 (or use menu Project -> Toggle Breakpoint) to toggle a breakpoint on that line. Finally, hit F5, or select menu Project -> Start Debugging to launch MCServer under the debugger. The MCServer window comes up and loads your plugin. If the window doesn't come up, inspect the Output pane in ZBS, there are usually two reasons for failure:<ul>
+ <li>Your code in the currently open file has a hard syntax error. These are reported as "Compilation error" in the Output pane, double-click the line to go to the error</li>
+ <li>ZBS cannot find the MCServer executable. Make sure you are editting a file two levels down the folder hierarchy from the MCS executable and that the MCS executable is named properly (mcserver[.exe] or mcserver_debug[.exe]). Also make sure you have selected the right Interpreter (menu Project -> Lua Interpreter).</li>
+ </ul></p>
+ <p>Once running, if the execution hits a breakpoint, the ZBS window will come up and a green arrow is displayed next to the breakpoint line. You can step through the code using F10 (Step Into) and Shift+F10 (Step Over). You can also use the Watch window to inspect variable values, or simply hover your mouse over a variable to display its value in the tooltip. Use the Remote console pane to execute commands directly *inside* the MCServer's plugin context.</p>
+ <p>You can also use the Project -> Break menu item to break into the debugger as soon as possible. You can also set breakpoints while the MCS plugin is running. Note that due to the way in which the debugger is implemented, MCS may execute some more Lua code before the break / breakpoint comes into effect. If MCS is not executing any Lua code in your plugin, it will not break until the plugin code kicks in again. This may result in missed breakpoints and delays before the Break command becomes effective. Therefore it's best to set breakpoints before running the program, or while the program is waiting in another breakpoint.</p>
+ </div>
+ </body>
+</html>
diff --git a/MCServer/Plugins/APIDump/Static/zbs_logo.png b/MCServer/Plugins/APIDump/Static/zbs_logo.png
new file mode 100644
index 000000000..c8d6d6278
--- /dev/null
+++ b/MCServer/Plugins/APIDump/Static/zbs_logo.png
Binary files differ
diff --git a/MCServer/Plugins/APIDump/Static/zbs_workspace.png b/MCServer/Plugins/APIDump/Static/zbs_workspace.png
new file mode 100644
index 000000000..9ce17e35a
--- /dev/null
+++ b/MCServer/Plugins/APIDump/Static/zbs_workspace.png
Binary files differ
diff --git a/MCServer/Plugins/APIDump/UsingChunkStays.html b/MCServer/Plugins/APIDump/UsingChunkStays.html
new file mode 100644
index 000000000..d3ecc6674
--- /dev/null
+++ b/MCServer/Plugins/APIDump/UsingChunkStays.html
@@ -0,0 +1,167 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>MCServer - Using ChunkStays</title>
+ <link rel="stylesheet" type="text/css" href="main.css" />
+ <link rel="stylesheet" type="text/css" href="prettify.css" />
+ <script src="prettify.js"></script>
+ <script src="lang-lua.js"></script>
+ <meta charset="UTF-8">
+ </head>
+ <body>
+ <div id="content">
+ <h1>Using ChunkStays</h1>
+ <p>
+ A plugin may need to manipulate data in arbitrary chunks, and it needs a way to make the server
+ guarantee that the chunks are available in memory.</p>
+
+ <h2>The problem</h2>
+ <p>
+ Usually when plugins want to manipulate larger areas of world data, they need to make sure that the
+ server has the appropriate chunks loaded in the memory. When the data being manipulated can be further
+ away from the connected players, or the data is being manipulated from a console handler, there is a
+ real chance that the chunks are not loaded.</p>
+ <p>
+ This gets even more important when using the <a href="cBlockArea.html">cBlockArea</a> class for reading
+ and writing. Those functions will fail when any of the required chunks aren't valid. This means that
+ either the block area has incomplete data (Read() failed) or incomplete data has been written to the
+ world (Write() failed). Recovery from this is near impossible - you can't simply read or write again
+ later, because the world may have changed in the meantime.</p>
+
+ <h2>The solution</h2>
+ <p>
+ The naive solution would be to monitor chunk loads and unloads, and postpone the operations until all
+ the chunks are available. This would be quite ineffective and also very soon it would become very
+ difficult to maintain, if there were multiple code paths requiring this handling.</p>
+ <p>
+ An alternate approach has been implemented, accessible through a single (somewhat hidden) function
+ call: <a href="cWorld.html">cWorld:ChunkStay()</a>. All that this call basically does is, it tells the
+ server "Load these chunks for me, and call this callback function once you have them all." And the
+ server does exactly that - it remembers the callback and asks the world loader / generator to provide
+ the chunks. Once the chunks become available, it calls the callback function for the plugin.</p>
+ <p>
+ There are a few gotcha-s, though. If the code that was requesting the read or write had access to some
+ of the volatile objects, such as <a href="cPlayer.html">cPlayer</a> or
+ <a href="cEntity.html">cEntity</a> objects, those cannot be accessed by the callback anymore, because
+ they may have become invalid in the meantime - the player may have disconnected, the entity may have
+ despawned. So the callback must use the longer way to access such objects, such as calling
+ <a href="cWorld.html">cWorld:DoWithEntityByID()</a> or
+ <a href="cWorld.html">cWorld:DoWithPlayer()</a>.</p>
+
+ <h2>The example</h2>
+ <p>
+ As a simple example, consider a theoretical plugin that allows a player to save the immediate
+ surroundings of the spawn into a schematic file. The player issues a command to initiate the save, and
+ the plugin reads a 50 x 50 x 50 block area around the spawn into a cBlockArea and saves it on the disk
+ as "<PlayerName>_spawn.schematic". When it's done with the saving, it wants to send a message to the
+ player to let them know the command has succeeded.</p>
+ <p>
+ The first attempt shows the naive approach. It simply reads the block area and saves it, then sends the
+ message. I'll repeat once more, this code is <b>the wrong way</b> to do it!</p>
+<pre class="prettyprint lang-lua">
+function HandleCommandSaveSpawn(a_Split, a_Player)
+ -- Get the coords for the spawn:
+ local SpawnX = a_Player:GetWorld():GetSpawnX()
+ local SpawnY = a_Player:GetWorld():GetSpawnY()
+ local SpawnZ = a_Player:GetWorld():GetSpawnZ()
+ local Bounds = cCuboid(SpawnX - 25, SpawnY - 25, SpawnZ - 25, SpawnX + 25, SpawnY + 25, SpawnZ + 25)
+ Bounds:ClampY(0, 255)
+
+ -- Read the area around spawn into a cBlockArea, save to file:
+ local Area = cBlockArea()
+ local FileName = a_Player:GetName() .. "_spawn.schematic"
+ Area:Read(a_Player:GetWorld(), Bounds, cBlockArea.baTypes + cBlockArea.baMetas)
+ Area:SaveToSchematicFile(FileName)
+
+ -- Notify the player:
+ a_Player:SendMessage(cCompositeChat("The spawn has been saved", mtInfo))
+ return true
+end
+</pre>
+ <p>
+ Now if the player goes exploring far and uses the command to save their spawn, the chunks aren't
+ loaded, so the BlockArea reading fails, the BlockArea contains bad data. Note that the plugin fails to
+ do any error checking and if the area isn't read from the world, it happily saves the incomplete data
+ and says "hey, everything's right", althought it has just trashed any previous backup of the spawn
+ schematic with nonsense data.</p>
+ <hr/>
+ <p>
+ The following script uses the ChunkStay method to alleviate chunk-related problems. This is <b>the
+ right way</b> of doing it:</p>
+<pre class="prettyprint lang-lua">
+function HandleCommandSaveSpawn(a_Split, a_Player)
+ -- Get the coords for the spawn:
+ local SpawnX = a_Player:GetWorld():GetSpawnX()
+ local SpawnY = a_Player:GetWorld():GetSpawnY()
+ local SpawnZ = a_Player:GetWorld():GetSpawnZ()
+ local Bounds = cCuboid(SpawnX - 25, SpawnY - 25, SpawnZ - 25, SpawnX + 25, SpawnY + 25, SpawnZ + 25)
+ Bounds:ClampY(0, 255)
+
+ -- Get a list of chunks that we need loaded:
+ local MinChunkX = math.floor((SpawnX - 25) / 16)
+ local MaxChunkX = math.ceil ((SpawnX + 25) / 16)
+ local MinChunkZ = math.floor((SpawnZ - 25) / 16)
+ local MaxChunkZ = math.ceil ((SpawnZ + 25) / 16)
+ local Chunks = {}
+ for x = MinChunkX, MaxChunkX do
+ for z = MinChunkZ, MaxChunkZ do
+ table.insert(Chunks, {x, z})
+ end
+ end -- for x
+
+ -- Store the player's name and world to use in the callback, because the a_Player object may no longer be valid:
+ local PlayerName = a_Player:GetName()
+ local World = a_Player:GetWorld()
+
+ -- This is the callback that is executed once all the chunks are loaded:
+ local OnAllChunksAvailable = function()
+ -- Read the area around spawn into a cBlockArea, save to file:
+ local Area = cBlockArea()
+ local FileName = PlayerName .. "_spawn.schematic"
+ if (Area:Read(World, Bounds, cBlockArea.baTypes + cBlockArea.baMetas)) then
+ Area:SaveToSchematicFile(FileName)
+ Msg = cCompositeChat("The spawn has been saved", mtInfo)
+ else
+ Msg = cCompositeChat("Cannot save the spawn", mtFailure)
+ end
+
+ -- Notify the player:
+ -- Note that we cannot use a_Player here, because it may no longer be valid (if the player disconnected before the command completes)
+ World:DoWithPlayer(PlayerName,
+ function (a_CBPlayer)
+ a_CBPlayer:SendMessage(Msg)
+ end
+ )
+ end
+
+ -- Ask the server to load our chunks and notify us once it's done:
+ World:ChunkStay(Chunks, nil, OnAllChunksAvailable)
+
+ -- Note that code here may get executed before the callback is called!
+ -- The ChunkStay says "once you have the chunks", not "wait until you have the chunks"
+ -- So you can't notify the player here, because the saving needn't have occurred yet.
+
+ return true
+end
+</pre>
+ <p>
+ Note that this code does its error checking of the Area:Read() function, and it will not overwrite the
+ previous file unless it actually has the correct data. If you're wondering how the reading could fail
+ when we've got the chunks loaded, there's still the issue of free RAM - if the memory for the area
+ cannot be allocated, it cannot be read even with all the chunks present. So we still do need that
+ check.</p>
+
+ <h2>The conclusion</h2>
+ <p>
+ Although it makes the code a little bit longer and is a bit more difficult to grasp at first, the
+ ChunkStay is a useful technique to add to your repertoire. It is to be used whenever you need access to
+ chunks that may potentially be inaccessible, and you really need the data.</p>
+ <p>Possibly the biggest hurdle in using the ChunkStay is the fact that it does its work in the
+ background, thus invalidating all cPlayer and cEntity objects your function may hold, so you need to
+ re-acquire them from their IDs and names. This is the penalty for using multi-threaded code.</p>
+ <script>
+ prettyPrint();
+ </script>
+ </div>
+ </body>
+</html>
diff --git a/MCServer/Plugins/APIDump/WebWorldThreads.html b/MCServer/Plugins/APIDump/WebWorldThreads.html
index fc80a6178..ee0b172e6 100644
--- a/MCServer/Plugins/APIDump/WebWorldThreads.html
+++ b/MCServer/Plugins/APIDump/WebWorldThreads.html
@@ -39,31 +39,31 @@
<h2>Example</h2>
The Core has the facility to kick players using the web interface. It used the following code for the kicking (inside the webadmin handler):
- <pre class="prettyprint lang-lua">
- local KickPlayerName = Request.Params["players-kick"]
- local FoundPlayerCallback = function(Player)
- if (Player:GetName() == KickPlayerName) then
- Player:GetClientHandle():Kick("You were kicked from the game!")
- end
+<pre class="prettyprint lang-lua">
+local KickPlayerName = Request.Params["players-kick"]
+local FoundPlayerCallback = function(Player)
+ if (Player:GetName() == KickPlayerName) then
+ Player:GetClientHandle():Kick("You were kicked from the game!")
+ end
+end
+cRoot:Get():FindAndDoWithPlayer(KickPlayerName, FoundPlayerCallback)
+</pre>
+The cRoot:FindAndDoWithPlayer() is unsafe and could have caused a deadlock. The new solution is queue a task; but since we don't know in which world the player is, we need to queue the task to all worlds:
+<pre class="prettyprint lang-lua">
+cRoot:Get():ForEachWorld( -- For each world...
+ function(World)
+ World:QueueTask( -- ... queue a task...
+ function(a_World)
+ a_World:DoWithPlayer(KickPlayerName, -- ... to walk the playerlist...
+ function (a_Player)
+ a_Player:GetClientHandle():Kick("You were kicked from the game!") -- ... and kick the player
end
- cRoot:Get():FindAndDoWithPlayer(KickPlayerName, FoundPlayerCallback)
- </pre>
- The cRoot:FindAndDoWithPlayer() is unsafe and could have caused a deadlock. The new solution is queue a task; but since we don't know in which world the player is, we need to queue the task to all worlds:
- <pre class="prettyprint lang-lua">
- cRoot:Get():ForEachWorld( -- For each world...
- function(World)
- World:QueueTask( -- ... queue a task...
- function(a_World)
- a_World:DoWithPlayer(KickPlayerName, -- ... to walk the playerlist...
- function (a_Player)
- a_Player:GetClientHandle():Kick("You were kicked from the game!") -- ... and kick the player
- end
- )
- end
- )
- end
- )
- </pre>
+ )
+ end
+ )
+ end
+)
+</pre>
<script>
prettyPrint();
</script>
diff --git a/MCServer/Plugins/APIDump/main.css b/MCServer/Plugins/APIDump/main.css
index aa26bd186..8041e0d01 100644
--- a/MCServer/Plugins/APIDump/main.css
+++ b/MCServer/Plugins/APIDump/main.css
@@ -39,7 +39,8 @@ pre
body
{
- min-width: 800px;
+ min-width: 400px;
+ max-width: 1200px;
width: 95%;
margin: 10px auto;
background-color: white;
diff --git a/MCServer/Plugins/APIDump/main_APIDump.lua b/MCServer/Plugins/APIDump/main_APIDump.lua
index 7455c3cd2..a25bab9cf 100644
--- a/MCServer/Plugins/APIDump/main_APIDump.lua
+++ b/MCServer/Plugins/APIDump/main_APIDump.lua
@@ -27,10 +27,14 @@ local function LoadAPIFiles(a_Folder, a_DstTable)
-- We only want .lua files from the folder:
if (cFile:IsFile(FileName) and fnam:match(".*%.lua$")) then
local TablesFn, Err = loadfile(FileName);
- if (TablesFn == nil) then
+ if (type(TablesFn) ~= "function") then
LOGWARNING("Cannot load API descriptions from " .. FileName .. ", Lua error '" .. Err .. "'.");
else
local Tables = TablesFn();
+ if (type(Tables) ~= "table") then
+ LOGWARNING("Cannot load API descriptions from " .. FileName .. ", returned object is not a table (" .. type(Tables) .. ").");
+ break
+ end
for k, cls in pairs(Tables) do
a_DstTable[k] = cls;
end
@@ -1408,9 +1412,9 @@ end
--- Dumps the entire API table into a file in the ZBS format
local function DumpAPIZBS(a_API)
LOG("Dumping ZBS API description...")
- local f, err = io.open("mcserver.lua", "w")
+ local f, err = io.open("mcserver_api.lua", "w")
if (f == nil) then
- LOG("Cannot open mcserver.lua for writing, ZBS API will not be dumped. " .. err)
+ LOG("Cannot open mcserver_lua.lua for writing, ZBS API will not be dumped. " .. err)
return
end
diff --git a/MCServer/Plugins/Core b/MCServer/Plugins/Core
-Subproject 013a32a7fb3c8a6cfe0aef892d4c7394d4e1be5
+Subproject 3790f78d3f7503ff33a423b8e73e81a27556278
diff --git a/MCServer/Plugins/Debuggers/Debuggers.lua b/MCServer/Plugins/Debuggers/Debuggers.lua
index 2cb014875..064d5d772 100644
--- a/MCServer/Plugins/Debuggers/Debuggers.lua
+++ b/MCServer/Plugins/Debuggers/Debuggers.lua
@@ -69,12 +69,22 @@ function Initialize(Plugin)
LOG("Initialized " .. Plugin:GetName() .. " v." .. Plugin:GetVersion())
- -- TestBlockAreas();
- -- TestSQLiteBindings();
- -- TestExpatBindings();
- -- TestPluginCalls();
+ -- TestBlockAreas()
+ -- TestSQLiteBindings()
+ -- TestExpatBindings()
+ -- TestPluginCalls()
TestBlockAreasString()
+ TestStringBase64()
+
+ --[[
+ -- Test cCompositeChat usage in console-logging:
+ LOGINFO(cCompositeChat("This is a simple message with some @2 color formatting @4 and http://links.to .")
+ :AddSuggestCommandPart("(Suggested command)", "cmd")
+ :AddRunCommandPart("(Run command)", "cmd")
+ :SetMessageType(mtInfo)
+ )
+ --]]
return true
end;
@@ -243,6 +253,24 @@ end
+function TestStringBase64()
+ -- Create a binary string:
+ local s = ""
+ for i = 0, 255 do
+ s = s .. string.char(i)
+ end
+
+ -- Roundtrip through Base64:
+ local Base64 = Base64Encode(s)
+ local UnBase64 = Base64Decode(Base64)
+
+ assert(UnBase64 == s)
+end
+
+
+
+
+
function TestSQLiteBindings()
LOG("Testing SQLite bindings...");
diff --git a/MCServer/Plugins/InfoReg.lua b/MCServer/Plugins/InfoReg.lua
index 27e63aa5b..da5a9972c 100644
--- a/MCServer/Plugins/InfoReg.lua
+++ b/MCServer/Plugins/InfoReg.lua
@@ -16,22 +16,22 @@ local function ListSubcommands(a_Player, a_Subcommands, a_CmdString)
end
-- Enum all the subcommands:
- local Verbs = {};
+ local Verbs = {}
for cmd, info in pairs(a_Subcommands) do
- if (a_Player:HasPermission(info.Permission or "")) then
- table.insert(Verbs, " " .. a_CmdString .. " " .. cmd);
+ if ((a_Player == nil) or (a_Player:HasPermission(info.Permission or ""))) then
+ table.insert(Verbs, a_CmdString .. " " .. cmd)
end
end
- table.sort(Verbs);
+ table.sort(Verbs)
-- Send the list:
if (a_Player == nil) then
for idx, verb in ipairs(Verbs) do
- LOGINFO(verb);
+ LOGINFO(" " .. verb)
end
else
for idx, verb in ipairs(Verbs) do
- a_Player:SendMessage(verb);
+ a_Player:SendMessage(cCompositeChat(" ", mtInfo):AddSuggestCommandPart(verb, verb))
end
end
end