1
0

Merge branch master into CmakeMultiConfig.

This commit is contained in:
madmaxoft 2014-01-12 10:04:16 +01:00
commit 180b9b9099
156 changed files with 2984 additions and 2759 deletions

1
.gitignore vendored
View File

@ -19,6 +19,7 @@ cloc.xsl
## emacs
*.*~
*~
*.orig
# world inside source
ChunkWorx.ini

View File

@ -36,6 +36,11 @@ endif()
if(MSVC)
# Make build use multiple threads under MSVC:
add_flags_cxx("/MP")
elseif(APPLE)
#on os x clang adds pthread for us but we need to add it for gcc
if (NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
add_flags_cxx("-pthread")
endif()
else()
# Let gcc / clang know that we're compiling a multi-threaded app:
add_flags_cxx("-pthread")
@ -71,6 +76,17 @@ if (WIN32)
add_definitions(-DLUA_BUILD_AS_DLL)
endif()
# On Unix we use two dynamic loading libraries dl and ltdl.
# Preference is for dl on unknown systems as it is specified in POSIX
# the dynamic loader is used by lua and sqllite.
if (UNIX)
if(${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
set(DYNAMIC_LOADER ltdl)
else()
set(DYNAMIC_LOADER dl)
endif()
endif()
# The Expat library is linked in statically, make the source files aware of that:
add_definitions(-DXML_STATIC)
@ -147,21 +163,16 @@ add_subdirectory(lib/expat/)
add_subdirectory(lib/luaexpat/)
add_subdirectory(lib/md5/)
# Re-add the maximum warning level:
# Remove disabling the maximum warning level:
# clang does not like a command line that reads -Wall -Wextra -w -Wall -Wextra and does not output any warnings
# We do not do that for MSVC since MSVC produces an awful lot of warnings for its own STL headers;
# the important warnings will be turned on using #pragma in Globals.h
# the important warnings are turned on using #pragma in Globals.h
if (NOT MSVC)
#TODO: set -Wall -Werror -Wextra
add_flags_cxx("-Wall -Wextra")
string(REPLACE "-w" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
string(REPLACE "-w" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
string(REPLACE "-w" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
string(REPLACE "-w" "" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
endif()
if (NOT WIN32)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -rdynamic")
set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} -rdynamic")
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} -rdynamic")
set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_PROFILE} -rdynamic")
endif()
add_subdirectory (src)

View File

@ -1,4 +1,3 @@
Code Stuff
----------
@ -23,7 +22,9 @@ Code Stuff
- This helps prevent mistakes such as "if (a & 1 == 0)"
* White space is free, so use it freely
- "freely" as in "plentifully", not "arbitrarily"
* Please leave the first line of all files blank, to get around an IDE bug.
* Each and every control statement deserves its braces. This helps maintainability later on when the file is edited, lines added or removed - the control logic doesn't break so easily.
* Please leave the first line of all source files blank, to get around an IDE bug.
* Also leave the last line of all source files blank (GCC and GIT can complain otherwise)
Copyright

View File

@ -19,5 +19,6 @@ SamJBarney
worktycho
Sxw1212
tonibm19
Diusrex
Please add yourself to this list if you contribute to MCServer.

121
GETTING-STARTED.md Normal file
View File

@ -0,0 +1,121 @@
Hello! Thanks for wanting to work on this project :smile:, and I hope that this file will help you somewhat in getting all set up and running. I'll go through the basics of getting the projet environment set up, the code organization and style, and general development practices. I'll also show you some good issues to start off working on to get yourself familiarised with the code.
Minecraft Basics
----------------
If you don't play Minecraft or don't have a great knowledge of the basic systems, you should get to know them. The [Minecraft Wiki](http://minecraft.gamepedia.com/Minecraft_Wiki) is quite useful for this task, although some youtubers are also fairly good at teaching the basics and just playing is quite good too.
I'd say that the important topics are:
* Differnt types of blocks and how they act.
* Mobs, what they do and how.
* Redstone, pistons, and automation.
* Farming
* Fighting, health and the hunger system.
Useful Resources
----------------
* [Minecraft Wiki](http://minecraft.gamepedia.com/Minecraft_Wiki)
* [Minecraft Protocol Wiki](http://wiki.vg)
* [Lua API Documentation](http://mc-server.xoft.cz/LuaAPI)
* [VS2008 Download](http://stackoverflow.com/questions/15318560/visual-c-2008-express-download-link-dead)
Setting up a Dev Environment
============================
Requirements
------------
**Linux/BSD/Solaris/OSX:**
You'll need the basic C++ build tools:
* gcc (or clang or another C compiler)
* g++ (or clang++ or another C++ compiler)
* make
You'll also need CMake to generate the makefile to build from.
**Windows:**
If you use Windows, your best bet is the MSVC2008 (available as a free download in the Express edition from MS) or MSVS2013 (ditto), solution files for both are currently in the repo.
Setting up the Repo
-------------------
Next, you'll need to set up the repo. You can make a fork and work on that then PR in, or I can set you up with membership for the repo so you can work on branches here (still use PRs though, they're great tools and for the first few you'll definitely need some changes). If you want membership to the repo, just create an issue and I can set you up.
Once you've cloned, you need to pull down the submodules:
git submodule init
git submodule update
After that they should come down automatically when you pull but it's not bad to refresh every once in a while.
Repo Arrangement
---------------------------
The MCServer repo has recently been rearranged for better code separation and other things, but basically it's split into a few areas:
* `src`
This holds all of the MCServer source code, and is where most development takes place.
It's split into logical areas for blocks, the protocol handling and other things.
* `lib`
This holds all the 3rd party libraries for MCServer. You basically don't need to touch these, and we're thinking of switching them into submodules soon.
* `MCServer`
This folder isn't greatly named, but it contains the default plugins and environment to actually run the server. You'll find the executable (named `MCServer`) here and in the `plugins` subdir the default plugins. The config files are also stored here. Config files with `.example.ini` on the end are generated by the server or source control and should be left alone, instead you should copy them to a file without the `example` in the name which will be prioritised over the generated ones.
Code Styles
------------------
Mainly follow the code styles in [CONTRIBUTING.md](https://github.com/mc-server/MCServer/blob/master/CONTRIBUTING.md), which is definitely an important read.
How to Build
------------------
**Linux/BSD/Solaris/OSX:**
Follow the instructions in [COMPILING.md](https://github.com/mc-server/MCServer/blob/master/COMPILING.md). You probably want to build in debug mode (when you're developing) for console alerts and debugging capability, even though it's much slower for everyday use.
Basically, the process is:
cmake . -DCMAKE_BUILD_TYPE=DEBUG && make
**Windows:**
You need to first execute the `src/Bindings/AllToLua.bat` script file, then just open the solution file in your MSVC of choice and build.
How to Run
----------
The server can be run (on *nix) by a simple `./MCServer` in the `MCServer` directory. On first run it will generate the world and start a server on the default port (configurable in `settings.ini`) so you can connect in minecraft via `localhost`.
Where to Get Started
-------------------------------
There are a few fairly easy issues for you to get started with, as well as some more difficult but interesting ones.
**Easy**:
* #288
* #385
* #402
* #388
* #380
* Clean up some of the compiler warnings. (Check [Travis CI](http://travis-ci.org/mc-server/MCServer) for a list of them.) With clang, there are over 10000 lines of warnings to clean up.
**More Difficult**:
* #17
* #418
* #398
You may also want to write some plugins. They are written in lua, with excellent API documentation available via [APIDump](http://mc-server.xoft.cz/LuaAPI). The [Core](https://github.com/mc-server/Core) plugin should also help quite a bit here.
Special Things
---------------------
* MCServer uses ToLUA for the Lua API, and you'll really have to ask @madmaxoft for how to export stuff and @worktycho for how to add stuff to the auto generated bindings (he just re-worked it with CMake).
* Ask questions as much as you like, we're here to help :smiley:

View File

@ -867,6 +867,10 @@ ValueName0=SomeOtherValue
{ Params = "KeyName, Comment", Return = "", Notes = "Adds a comment to be stored in the file under the specified key" },
},
AddKeyName = { Params = "KeyName", Returns = "number", Notes = "Adds a new key of the specified name. Returns the KeyID of the new key." },
AddValue = { Params = "KeyName, ValueName, Value", Return = "", Notes = "Adds a new value of the specified name to the specified key. If another value of the same name exists in the key, both are kept (nonstandard INI file)" },
AddValueB = { Params = "KeyName, ValueName, Value", Return = "", Notes = "Adds a new bool value of the specified name to the specified key. If another value of the same name exists in the key, both are kept (nonstandard INI file)" },
AddValueF = { Params = "KeyName, ValueName, Value", Return = "", Notes = "Adds a new float value of the specified name to the specified key. If another value of the same name exists in the key, both are kept (nonstandard INI file)" },
AddValueI = { Params = "KeyName, ValueName, Value", Return = "", Notes = "Adds a new integer value of the specified name to the specified key. If another value of the same name exists in the key, both are kept (nonstandard INI file)" },
CaseInsensitive = { Params = "", Return = "", Notes = "Sets key names' and value names' comparisons to case insensitive (default)." },
CaseSensitive = { Params = "", Return = "", Notes = "Sets key names and value names comparisons to case sensitive." },
Clear = { Params = "", Return = "", Notes = "Removes all the in-memory data. Note that , like all the other operations, this doesn't affect any file data." },
@ -1713,7 +1717,7 @@ cPluginManager.AddHook(cPluginManager.HOOK_CHAT, OnChatMessage);
ForceExecuteCommand = { Params = "{{cPlayer|Player}}, CommandStr", Return = "bool", Notes = "Same as ExecuteCommand, but doesn't check permissions" },
ForEachCommand = { Params = "CallbackFn", Return = "bool", Notes = "Calls the CallbackFn function for each command that has been bound using BindCommand(). The CallbackFn has the following signature: <pre class=\"prettyprint lang-lua\">function(Command, Permission, HelpString)</pre>. If the callback returns true, the enumeration is aborted and this API function returns false; if it returns false or no value, the enumeration continues with the next command, and the API function returns true." },
ForEachConsoleCommand = { Params = "CallbackFn", Return = "bool", Notes = "Calls the CallbackFn function for each command that has been bound using BindConsoleCommand(). The CallbackFn has the following signature: <pre class=\"prettyprint lang-lua\">function (Command, HelpString)</pre>. If the callback returns true, the enumeration is aborted and this API function returns false; if it returns false or no value, the enumeration continues with the next command, and the API function returns true." },
Get = { Params = "", Return = "cPluginManager", Notes = "Returns the single instance of the plugin manager" },
Get = { Params = "", Return = "cPluginManager", Notes = "(STATIC) Returns the single instance of the plugin manager" },
GetAllPlugins = { Params = "", Return = "table", Notes = "Returns a table (dictionary) of all plugins, [name => {{cPlugin}}] pairing." },
GetCommandPermission = { Params = "Command", Return = "Permission", Notes = "Returns the permission needed for executing the specified command" },
GetCurrentPlugin = { Params = "", Return = "{{cPlugin}}", Notes = "Returns the {{cPlugin}} object for the calling plugin. This is the same object that the Initialize function receives as the argument." },

View File

@ -0,0 +1,20 @@
return
{
HOOK_PLAYER_FISHED =
{
CalledWhen = "A player gets a reward from fishing.",
DefaultFnName = "OnPlayerFished", -- also used as pagename
Desc = [[
This hook gets called after a player reels in the fishing rod. This is a notification-only hook, the reward has already been decided. If a plugin needs to modify the reward, use the {{OnPlayerFishing|HOOK_PLAYER_FISHING}} hook.
]],
Params =
{
{ Name = "Player", Type = "{{cPlayer}}", Notes = "The player who pulled the fish in." },
{ Name = "Reward", Type = "{{cItems}}", Notes = "The reward the player gets. It can be a fish, treasure and junk." },
},
Returns = [[
If the function returns false or no value, the next plugin's callback is called. If the function returns true, no other
callback is called for this event.
]],
}, -- HOOK_PLAYER_FISHED
};

View File

@ -0,0 +1,21 @@
return
{
HOOK_PLAYER_FISHING =
{
CalledWhen = "A player is about to get a reward from fishing.",
DefaultFnName = "OnPlayerFishing", -- also used as pagename
Desc = [[
This hook gets called when a player right clicks with a fishing rod while the floater is under water. The reward is already descided, but the plugin may change it.
]],
Params =
{
{ Name = "Player", Type = "{{cPlayer}}", Notes = "The player who pulled the fish in." },
{ Name = "Reward", Type = "{{cItems}}", Notes = "The reward the player gets. It can be a fish, treasure and junk." },
},
Returns = [[
If the function returns false or no value, the next plugin's callback is called. Afterwards, the
server gives the player his reward. If the function returns true, no other
callback is called for this event and the player doesn't get his reward.
]],
}, -- HOOK_PLAYER_FISHING
};

View File

@ -0,0 +1,79 @@
return
{
HOOK_PLUGINS_LOADED =
{
CalledWhen = "All the enabled plugins have been loaded",
DefaultFnName = "OnPluginsLoaded", -- also used as pagename
Desc = [[
This callback gets called when the server finishes loading and initializing plugins. This is the
perfect occasion for a plugin to query other plugins through {{cPluginManager}}:GetPlugin() and
possibly start communicating with them using the {{cPlugin}}:Call() function.
]],
Params = {},
Returns = [[
The return value is ignored, all registered callbacks are called.
]],
CodeExamples =
{
{
Title = "CoreMessaging",
Desc = [[
This example shows how to implement the CoreMessaging functionality - messages to players will be
sent through the Core plugin, formatted by that plugin. As a fallback for when the Core plugin is
not present, the messages are sent directly by this code, unformatted.
]],
Code = [[
-- These are the fallback functions used when the Core is not present:
local function SendMessageFallback(a_Player, a_Message)
a_Player:SendMessage(a_Message);
end
local function SendMessageSuccessFallback(a_Player, a_Message)
a_Player:SendMessage(a_Message);
end
local function SendMessageFailureFallback(a_Player, a_Message)
a_Player:SendMessage(a_Message);
end
-- These three "variables" will hold the actual functions to call.
-- By default they are initialized to the Fallback variants, but will be redirected to Core when all plugins load
SendMessage = SendMessageFallback;
SendMessageSuccess = SendMessageSuccessFallback;
SendMessageFailure = SendMessageFailureFallback;
-- The callback tries to connect to the Core, if successful, overwrites the three functions with Core ones
local function OnPluginsLoaded()
local CorePlugin = cPluginManager:Get():GetPlugin("Core");
if (CorePlugin == nil) then
-- The Core is not loaded, keep the Fallback functions
return;
end
-- Overwrite the three functions with Core functionality:
SendMessage = function(a_Player, a_Message)
CorePlugin:Call("SendMessage", a_Player, a_Message);
end
SendMessageSuccess = function(a_Player, a_Message)
CorePlugin:Call("SendMessageSuccess", a_Player, a_Message);
end
SendMessageFailure = function(a_Player, a_Message)
CorePlugin:Call("SendMessageFailure", a_Player, a_Message);
end
end
-- Global scope, register the callback:
cPluginManager.AddHook(cPluginManager.HOOK_PLUGINS_LOADED, CoreMessagingPluginsLoaded);
-- Usage, anywhere else in the plugin:
SendMessageFailure(a_Player, "Cannot teleport to player, the destination player " .. PlayerName .. " was not found");
]],
},
} , -- CodeExamples
}, -- HOOK_PLUGINS_LOADED
}

View File

@ -84,7 +84,7 @@ end
To register a hook, insert the following code template into the "-- Hooks" area in the previous code example.
</p>
<pre class="prettyprint lang-lua">
cPluginManager.AddHook(cPluginManager.HOOK_NAME_HERE, FunctionNameToBeCalled)
cPluginManager:AddHook(cPluginManager.HOOK_NAME_HERE, FunctionNameToBeCalled)
</pre>
<p>
What does this code do?
@ -102,7 +102,7 @@ function Initialize(Plugin)
Plugin:SetName("DerpyPlugin")
Plugin:SetVersion(1)
cPluginManager.AddHook(cPluginManager.HOOK_PLAYER_MOVING, OnPlayerMoving)
cPluginManager:AddHook(cPluginManager.HOOK_PLAYER_MOVING, OnPlayerMoving)
local PluginManager = cPluginManager:Get()
-- Command bindings
@ -200,7 +200,7 @@ function Initialize(Plugin)
local PluginManager = cPluginManager:Get()
PluginManager:BindCommand("/explode", "derpyplugin.explode", Explode, " ~ Explode a player");
cPluginManager.AddHook(cPluginManager.HOOK_COLLECTING_PICKUP, OnCollectingPickup)
cPluginManager:AddHook(cPluginManager.HOOK_COLLECTING_PICKUP, OnCollectingPickup)
LOG("Initialised " .. Plugin:GetName() .. " v." .. Plugin:GetVersion())
return true

View File

@ -30,6 +30,11 @@ pre
{
border: 1px solid #ccc;
background-color: #eee;
-moz-tab-size: 2;
-o-tab-size: 2;
-webkit-tab-size: 2;
-ms-tab-size: 2;
tab-size: 2;
}
body

View File

@ -321,6 +321,7 @@ function DumpAPIHtml()
cFile:CreateFolder("API/Static");
local localFolder = g_Plugin:GetLocalFolder();
for idx, fnam in ipairs(cFile:GetFolderContents(localFolder .. "/Static")) do
cFile:Delete("API/Static/" .. fnam);
cFile:Copy(localFolder .. "/Static/" .. fnam, "API/Static/" .. fnam);
end
@ -428,11 +429,18 @@ function DumpAPIHtml()
WriteClasses(f, API, ClassMenu);
WriteHooks(f, Hooks, UndocumentedHooks, HookNav);
-- Copy the static files to the output folder (overwrite any existing):
cFile:Copy(g_Plugin:GetLocalFolder() .. "/main.css", "API/main.css");
cFile:Copy(g_Plugin:GetLocalFolder() .. "/prettify.js", "API/prettify.js");
cFile:Copy(g_Plugin:GetLocalFolder() .. "/prettify.css", "API/prettify.css");
cFile:Copy(g_Plugin:GetLocalFolder() .. "/lang-lua.js", "API/lang-lua.js");
-- Copy the static files to the output folder:
local StaticFiles =
{
"main.css",
"prettify.js",
"prettify.css",
"lang-lua.js",
};
for idx, fnam in ipairs(StaticFiles) do
cFile:Delete("API/" .. fnam);
cFile:Copy(g_Plugin:GetLocalFolder() .. "/" .. fnam, "API/" .. fnam);
end
-- List the documentation problems:
LOG("Listing leftovers...");
@ -1169,7 +1177,7 @@ function WriteHtmlHook(a_Hook, a_HookNav)
f:write("</table>\n<p>" .. (a_Hook.Returns or "") .. "</p>\n\n");
f:write([[<hr /><h1>Code examples</h1><h2>Registering the callback</h2>]]);
f:write("<pre class=\"prettyprint lang-lua\">\n");
f:write([[cPluginManager.AddHook(cPluginManager.]] .. a_Hook.Name .. ", My" .. a_Hook.DefaultFnName .. [[);]]);
f:write([[cPluginManager:AddHook(cPluginManager.]] .. a_Hook.Name .. ", My" .. a_Hook.DefaultFnName .. [[);]]);
f:write("</pre>\n\n");
local Examples = a_Hook.CodeExamples or {};
for i, example in ipairs(Examples) do

@ -1 +1 @@
Subproject commit 351aff7efa94ce3c7ba733d5f83013e8a3fdfbfd
Subproject commit c65b56767a5e59ca387a05be72ef18791baa9aed

View File

@ -19,14 +19,16 @@ function Initialize(Plugin)
cPluginManager.AddHook(cPluginManager.HOOK_TICK, OnTick2);
--]]
cPluginManager.AddHook(cPluginManager.HOOK_PLAYER_USING_BLOCK, OnPlayerUsingBlock);
cPluginManager.AddHook(cPluginManager.HOOK_PLAYER_USING_ITEM, OnPlayerUsingItem);
cPluginManager.AddHook(cPluginManager.HOOK_TAKE_DAMAGE, OnTakeDamage);
cPluginManager.AddHook(cPluginManager.HOOK_TICK, OnTick);
cPluginManager.AddHook(cPluginManager.HOOK_CHAT, OnChat);
cPluginManager.AddHook(cPluginManager.HOOK_PLAYER_RIGHT_CLICKING_ENTITY, OnPlayerRightClickingEntity);
cPluginManager.AddHook(cPluginManager.HOOK_WORLD_TICK, OnWorldTick);
cPluginManager.AddHook(cPluginManager.HOOK_CHUNK_GENERATED, OnChunkGenerated);
cPluginManager:AddHook(cPluginManager.HOOK_PLAYER_USING_BLOCK, OnPlayerUsingBlock);
cPluginManager:AddHook(cPluginManager.HOOK_PLAYER_USING_ITEM, OnPlayerUsingItem);
cPluginManager:AddHook(cPluginManager.HOOK_TAKE_DAMAGE, OnTakeDamage);
cPluginManager:AddHook(cPluginManager.HOOK_TICK, OnTick);
cPluginManager:AddHook(cPluginManager.HOOK_CHAT, OnChat);
cPluginManager:AddHook(cPluginManager.HOOK_PLAYER_RIGHT_CLICKING_ENTITY, OnPlayerRightClickingEntity);
cPluginManager:AddHook(cPluginManager.HOOK_WORLD_TICK, OnWorldTick);
cPluginManager:AddHook(cPluginManager.HOOK_CHUNK_GENERATED, OnChunkGenerated);
cPluginManager:AddHook(cPluginManager.HOOK_PLUGINS_LOADED, OnPluginsLoaded);
cPluginManager:AddHook(cPluginManager.HOOK_PLUGIN_MESSAGE, OnPluginMessage);
PM = cRoot:Get():GetPluginManager();
PM:BindCommand("/le", "debuggers", HandleListEntitiesCmd, "- Shows a list of all the loaded entities");
@ -524,6 +526,14 @@ end
function OnPluginsLoaded()
LOG("All plugins loaded");
end
function OnChunkGenerated(a_World, a_ChunkX, a_ChunkZ, a_ChunkDesc)
-- Get the topmost block coord:
local Height = a_ChunkDesc:GetHeight(0, 0);
@ -532,7 +542,6 @@ function OnChunkGenerated(a_World, a_ChunkX, a_ChunkZ, a_ChunkDesc)
a_ChunkDesc:SetBlockTypeMeta(0, Height + 1, 0, E_BLOCK_SIGN_POST, 0);
local BlockEntity = a_ChunkDesc:GetBlockEntity(0, Height + 1, 0);
if (BlockEntity ~= nil) then
LOG("Setting sign lines...");
local SignEntity = tolua.cast(BlockEntity, "cSignEntity");
SignEntity:SetLines("Chunk:", tonumber(a_ChunkX) .. ", " .. tonumber(a_ChunkZ), "", "(Debuggers)");
end
@ -954,3 +963,28 @@ end
function OnPluginMessage(a_Client, a_Channel, a_Message)
LOGINFO("Received a plugin message from client " .. a_Client:GetUsername() .. ": channel '" .. a_Channel .. "', message '" .. a_Message .. "'");
if (a_Channel == "REGISTER") then
if (a_Message:find("WECUI")) then
-- The client has WorldEditCUI mod installed, test the comm by sending a few WECUI messages:
--[[
WECUI messages have the following generic format:
<shape>|<params>
If shape is p (cuboid selection), the params are sent individually for each corner click and have the following format:
<point-index>|<x>|<y>|<z>|<volume>
point-index is 0 or 1 (lclk / rclk)
volume is the 3D volume of the current cuboid selected (all three coords' deltas multiplied), including the edge blocks; -1 if N/A
--]]
-- Select a 51 * 51 * 51 block cuboid:
a_Client:SendPluginMessage("WECUI", "p|0|50|50|50|-1");
a_Client:SendPluginMessage("WECUI", "p|1|100|100|100|132651"); -- 132651 = 51 * 51 * 51
end
end
end

View File

@ -1,7 +1,7 @@
-- Global variables
PLUGIN = {} -- Reference to own plugin object
CHEST_WIDTH = 9
HANDY_VERSION = 1
HANDY_VERSION = 2
--[[
Handy is a plugin for other plugins. It contain no commands, no hooks, but functions to ease plugins developers' life.

View File

@ -6,350 +6,211 @@ function GetHandyVersion()
return HANDY_VERSION
end
-- Checks if handy is in proper version
function CheckForRequiedVersion(IN_version)
if (IN_version > HANDY_VERSION) then return false end
function CheckForRequiedVersion( inVersion )
if( inVersion > HANDY_VERSION ) then return false end
return true
end
--[[
MCS-specific _functions and nasty hacks :D
]]
-- There's a "GetChestHeight" function inside source code, but it's not lua-exported
function GetChestHeightCheat(IN_chest)
if (IN_chest:GetSlot(28) == nil) then -- this means we're trying to get double chest slot and FAIL
LOGWARN("HANDY: single chest checked")
return 3
end
LOGWARN("HANDY: double chest checked")
return 6
function GetChestHeightCheat( inChest )
local chestGrid = inChest:GetContents()
LOGINFO( "This function serves no purpose now! You should consider chest:GetContents():GetHeight() now!" )
LOGINFO( "Also, you might find Handy's new 'IsChestDouble()' useful for your case" )
return chestGrid:GetHeight()
end
-- Those two checks how many items of given IN_itemID chest and player have, and how much they could fit inside them
function ReadChestForItem(IN_chest, IN_itemID)
local _items_found = 0
local _free_space = 0
-- stalk through chest slots...
local _slot_counter = 0
local _slot_item
local _item_max_stack = GetItemMaxStack(IN_itemID)
while true do
_slot_item = IN_chest:GetSlot(_slot_counter)
if (_slot_item ~= nil) then
if (_slot_item.m_ItemID == IN_itemID) then
_items_found = _items_found + _slot_item.m_ItemCount
_free_space = _free_space + (_item_max_stack - _slot_item.m_ItemCount)
end
if (_slot_item:IsEmpty() == true) then
_free_space = _free_space + _item_max_stack
end
end
_slot_counter = _slot_counter + 1
if (_slot_counter == CHEST_WIDTH*GetChestHeightCheat(IN_chest)) then
break
end
function IsChestDouble( inChest )
local chestHeight = inChest:GetContents():GetHeight()
if( chestHeight == 3 ) then
return false
end
return _items_found, _free_space
return true
end
function ReadPlayerForItem(IN_player, IN_itemID)
local _items_found = 0
local _free_space = 0
-- stalk through IN_player inventory slots...
local _slot_counter = 9
if (ItemIsArmor(IN_itemID) == true) then _slot_counter = 5 end
local _slot_item
local _item_max_stack = GetItemMaxStack(IN_itemID)
while true do
_slot_item = IN_player:GetInventory():GetSlot(_slot_counter)
if (_slot_item ~= nil) then
if (_slot_item.m_ItemID == IN_itemID) then
_items_found = _items_found + _slot_item.m_ItemCount
_free_space = _free_space + (_item_max_stack - _slot_item.m_ItemCount)
end
if (_slot_item:IsEmpty() == true) then
_free_space = _free_space + _item_max_stack
end
end
_slot_counter = _slot_counter + 1
if (_slot_counter == 45) then
break
end
end
return _items_found, _free_space
-- Those two checks how many items of given inItemID chest and player have, and how much they could fit inside them
function ReadChestForItem( inChest, inItemID )
return ReadGridForItems( inChest:GetContents(), inItemID )
end
-- Following functions are for chest-related operations (since noone was bothered writing them in MCS code)
function ReadPlayerForItem( inPlayer, inItemID )
local inventoryFound, inventoryFree = ReadGridForItems( inPlayer:GetInventory():GetInventoryGrid(), inItemID )
local hotbarFound, hotbarFree = ReadGridForItems( inPlayer:GetInventory():GetHotbarGrid(), inItemID )
local itemsFound = inventoryFound + hotbarFound
local freeSpace = inventoryFree + hotbarFree
return itemsFound, freeSpace
end
-- Following functions are for chest-related operations
-- BEWARE! Those assume you did checked if chest has items/space in it!
function TakeItemsFromChest(IN_chest, IN_itemID, IN_ammount) -- UNSAFE! CHECK FOR ITEMS FIRST!!
-- stalk through chest slots...
local _slot_counter = 0
local _slot_item
local _take_count = IN_ammount
while true do
_slot_item = IN_chest:GetSlot(_slot_counter)
if (_slot_item ~= nil) then
if (_slot_item.m_ItemID == IN_itemID) then
-- assuming player have enought money
if (_take_count > 0) then
if (_take_count > _slot_item.m_ItemCount) then
_take_count = _take_count - _slot_item.m_ItemCount
IN_chest:SetSlot(_slot_counter, cItem()) -- a bit hacky, can't make cItem:Clear() work(
else
local _left_count = _slot_item.m_ItemCount - _take_count
IN_chest:SetSlot(_slot_counter, cItem(_slot_item.m_ItemID, _left_count)) -- a bit hacky
_take_count = 0
end
end
if (_take_count == 0) then
break
end
function ReadGridForItems( inGrid, inItemID )
local itemsFound = 0
local freeSpace = 0
local slotsCount = inGrid:GetNumSlots()
local testerItem = cItem( inItemID )
local maxStackSize = testerItem:GetMaxStackSize()
for index = 0, (slotsCount - 1) do
slotItem = inGrid:GetSlot( index )
if( slotItem:IsEmpty() ) then
freeSpace = freeSpace + maxStackSize
else
if( slotItem:IsStackableWith( testerItem ) ) then
itemsFound = itemsFound + slotItem.m_ItemCount
freeSpace = maxStackSize - slotItem.m_ItemCount
end
end
_slot_counter = _slot_counter + 1
if (_slot_counter == CHEST_WIDTH*GetChestHeightCheat(IN_chest)) then
break
end
end
return itemsFound, freeSpace
end
function PutItemsToChest(IN_chest, IN_itemID, IN_ammount) -- UNSAFE! CHECK FOR SPACE FIRST!!
-- stalk through chest slots...
local _slot_counter = 0
local _slot_item
local _put_count = IN_ammount
local _max_stack = GetItemMaxStack(IN_itemID)
while true do
_slot_item = IN_chest:GetSlot(_slot_counter)
local _portion = 0
local _ammount_to_set = 0
if (_slot_item ~= nil) then
if (_slot_item:IsEmpty() == true) then
_portion = math.min(_max_stack, _put_count)
_ammount_to_set = _portion
function TakeItemsFromGrid( inGrid, inItem )
local slotsCount = inGrid:GetNumSlots()
local removedItem = cItem( inItem )
for index = 0, (slotsCount - 1) do
slotItem = inGrid:GetSlot( index )
if( slotItem:IsSameType( removedItem ) ) then
if( slotItem.m_ItemCount <= removedItem.m_ItemCount ) then
removedItem.m_ItemCount = removedItem.m_ItemCount - slotItem.m_ItemCount
inGrid:EmptySlot( index )
else
if (_slot_item.m_ItemID == IN_itemID) then
-- choose between how much we need to put and how much free space left
_portion = math.min(_put_count, _max_stack - _slot_item.m_ItemCount)
_ammount_to_set = _slot_item.m_ItemCount + _portion
end
removedItem.m_ItemCount = slotItem.m_ItemCount - removedItem.m_ItemCount
inGrid:SetSlot( index, removedItem )
removedItem.m_ItemCount = 0
end
end
IN_chest:SetSlot(_slot_counter, cItem(IN_itemID, _ammount_to_set)) -- we add max stack to chest
_put_count = _put_count - _portion
if (_put_count == 0) then break end
_slot_counter = _slot_counter + 1
if (_slot_counter == CHEST_WIDTH*GetChestHeightCheat(IN_chest)) then
break
if( removedItem.m_ItemCount <= 0 ) then break end
end
end
return removedItem.m_ItemCount
end
--------------
function TakeItemsFromChest( inChest, inItemID, inAmount ) -- MIGHT BE UNSAFE! CHECK FOR ITEMS FIRST!!
local chestGrid = inChest:GetContents()
local removedItem = cItem( inItemID, inAmount )
TakeItemsFromGrid( chestGrid, removedItem )
end
function PutItemsToChest( inChest, inItemID, inAmount )
local chestGrid = inChest:GetContents()
local addedItem = cItem( inItemID, inAmount )
chestGrid:AddItem( addedItem )
end
-- Similar to chest-related.
function TakeItemsFromPlayer(IN_player, IN_itemID, IN_ammount) -- UNSAFE! CHECK FIRST!
local _put_count = IN_ammount
local _max_stack = GetItemMaxStack(IN_itemID)
while true do
local _portion = math.min(_max_stack, _put_count)
IN_player:GetInventory():RemoveItem(cItem(IN_itemID, _portion))
_put_count = _put_count - _portion
if (_put_count == 0) then break end
function TakeItemsFromPlayer( inPlayer, inItemID, inAmount ) -- MIGHT BE UNSAFE! CHECK FIRST!
local removedItem = cItem( inItemID, inAmount )
local inventoryGrid = inPlayer:GetInventory():GetInventoryGrid()
local hotbarGrid = inPlayer:GetInventory():GetHotbarGrid()
local itemsLeft = TakeItemsFromGrid( inventoryGrid, removedItem )
if( itemsLeft > 0 ) then
removedItem = cItem( inItemID, itemsLeft )
TakeItemsFromGrid( hotbarGrid, removedItem )
end
end
function GiveItemsToPlayer(IN_player, IN_itemID, IN_ammount) -- UNSAFE! CHECK FIRST!
local _put_count = IN_ammount
local _max_stack = GetItemMaxStack(IN_itemID)
while true do
local _portion = math.min(_max_stack, _put_count)
IN_player:GetInventory():AddItem(cItem(IN_itemID, _portion))
_put_count = _put_count - _portion
if (_put_count == 0) then break end
function GiveItemsToPlayer( inPlayer, inItemID, inAmount )
local addedItem = cItem( inItemID, inAmount )
local inventoryGrid = inPlayer:GetInventory():GetInventoryGrid()
local hotbarGrid = inPlayer:GetInventory():GetHotbarGrid()
local itemsAdded = inventoryGrid:AddItem( addedItem )
if( itemsAdded < inAmount ) then
addedItem.m_ItemCount = addedItem.m_ItemCount - itemsAdded
hotbarGrid:AddItem( addedItem )
end
end
-- This function returns item max stack for a given itemID. It uses vanilla max stack size, and uses several non-common items notations;
-- Those are:
-- oneonerecord (because aparently 11record wasn't the best idea in lua scripting application)
-- carrotonastick (because it wasn't added to items.txt yet)
-- waitrecord (for same reason)
-- oneonerecord( because aparently 11record wasn't the best idea in lua scripting application )
-- carrotonastick( because it wasn't added to items.txt yet )
-- waitrecord( for same reason )
-- Feel free to ignore the difference, or to add those to items.txt
function GetItemMaxStack(IN_itemID)
local _result = 64
-- Tools and swords
if (IN_itemID == woodensword) then _result = 1 end
if (IN_itemID == woodenshovel) then _result = 1 end
if (IN_itemID == woodenpickaxe) then _result = 1 end
if (IN_itemID == woodenaxe) then _result = 1 end
if (IN_itemID == woodenhoe) then _result = 1 end
if (IN_itemID == stonesword) then _result = 1 end
if (IN_itemID == stoneshovel) then _result = 1 end
if (IN_itemID == stonepickaxe) then _result = 1 end
if (IN_itemID == stoneaxe) then _result = 1 end
if (IN_itemID == stonehoe) then _result = 1 end
if (IN_itemID == ironsword) then _result = 1 end
if (IN_itemID == ironshovel) then _result = 1 end
if (IN_itemID == ironpickaxe) then _result = 1 end
if (IN_itemID == ironaxe) then _result = 1 end
if (IN_itemID == ironhoe) then _result = 1 end
if (IN_itemID == diamondsword) then _result = 1 end
if (IN_itemID == diamondshovel) then _result = 1 end
if (IN_itemID == diamondpickaxe) then _result = 1 end
if (IN_itemID == diamondaxe) then _result = 1 end
if (IN_itemID == diamondhoe) then _result = 1 end
if (IN_itemID == goldensword) then _result = 1 end
if (IN_itemID == goldenshovel) then _result = 1 end
if (IN_itemID == goldenpickaxe) then _result = 1 end
if (IN_itemID == goldenaxe) then _result = 1 end
if (IN_itemID == goldenhoe) then _result = 1 end
if (IN_itemID == flintandsteel) then _result = 1 end
if (IN_itemID == bow) then _result = 1 end
if (IN_itemID == sign) then _result = 16 end
if (IN_itemID == woodendoor) then _result = 1 end
if (IN_itemID == irondoor) then _result = 1 end
if (IN_itemID == cake) then _result = 1 end
if (IN_itemID == cauldron) then _result = 1 end
if (IN_itemID == mushroomstew) then _result = 1 end
if (IN_itemID == painting) then _result = 1 end
if (IN_itemID == bucket) then _result = 16 end
if (IN_itemID == waterbucket) then _result = 1 end
if (IN_itemID == lavabucket) then _result = 1 end
if (IN_itemID == minecart) then _result = 1 end
if (IN_itemID == saddle) then _result = 1 end
if (IN_itemID == snowball) then _result = 16 end
if (IN_itemID == boat) then _result = 1 end
if (IN_itemID == milkbucket) then _result = 1 end
if (IN_itemID == storageminecart) then _result = 1 end
if (IN_itemID == poweredminecart) then _result = 1 end
if (IN_itemID == egg) then _result = 16 end
if (IN_itemID == fishingrod) then _result = 1 end
if (IN_itemID == bed) then _result = 1 end
if (IN_itemID == map) then _result = 1 end
if (IN_itemID == shears) then _result = 1 end
if (IN_itemID == enderpearl) then _result = 16 end
if (IN_itemID == potion) then _result = 1 end
if (IN_itemID == spawnegg) then _result = 1 end
if (IN_itemID == bookandquill) then _result = 1 end
if (IN_itemID == writtenbook) then _result = 1 end
if (IN_itemID == carrotonastick) then _result = 1 end
if (IN_itemID == goldrecord) then _result = 1 end
if (IN_itemID == greenrecord) then _result = 1 end
if (IN_itemID == blocksrecord) then _result = 1 end
if (IN_itemID == chirprecord) then _result = 1 end
if (IN_itemID == farrecord) then _result = 1 end
if (IN_itemID == mallrecord) then _result = 1 end
if (IN_itemID == mellohirecord) then _result = 1 end
if (IN_itemID == stalrecord) then _result = 1 end
if (IN_itemID == stradrecord) then _result = 1 end
if (IN_itemID == wardrecord) then _result = 1 end
if (IN_itemID == oneonerecord) then _result = 1 end
if (IN_itemID == waitrecord) then _result = 1 end
--if (IN_itemID == xxxxxxxxx) then _result = 1 end
if (IN_itemID == leatherhelmet) then _result = 1 end
if (IN_itemID == leatherchestplate) then _result = 1 end
if (IN_itemID == leatherpants) then _result = 1 end
if (IN_itemID == leatherboots) then _result = 1 end
if (IN_itemID == chainmailhelmet) then _result = 1 end
if (IN_itemID == chainmailchestplate) then _result = 1 end
if (IN_itemID == chainmailpants) then _result = 1 end
if (IN_itemID == chainmailboots) then _result = 1 end
if (IN_itemID == ironhelmet) then _result = 1 end
if (IN_itemID == ironchestplate) then _result = 1 end
if (IN_itemID == ironpants) then _result = 1 end
if (IN_itemID == ironboots) then _result = 1 end
if (IN_itemID == diamondhelmet) then _result = 1 end
if (IN_itemID == diamondchestplate) then _result = 1 end
if (IN_itemID == diamondpants) then _result = 1 end
if (IN_itemID == diamondboots) then _result = 1 end
if (IN_itemID == goldenhelmet) then _result = 1 end
if (IN_itemID == goldenchestplate) then _result = 1 end
if (IN_itemID == goldenpants) then _result = 1 end
if (IN_itemID == goldenboots) then _result = 1 end
return _result
function GetItemMaxStack( inItemID )
local testerItem = cItem( inItemID )
LOGINFO( "This function serves no real purpose now, maybe consider using cItem:GetMaxStackSize()?" )
return testerItem:GetMaxStackSize()
end
function ItemIsArmor(IN_itemID)
local _result = false
if (IN_itemID == leatherhelmet) then _result = true end
if (IN_itemID == leatherchestplate) then _result = true end
if (IN_itemID == leatherpants) then _result = true end
if (IN_itemID == leatherboots) then _result = true end
function ItemIsArmor( inItemID, inCheckForHorseArmor )
inCheckForHorseArmor = inCheckForHorseArmor or false
if( inItemID == E_ITEM_LEATHER_CAP ) then return true end
if( inItemID == E_ITEM_LEATHER_TUNIC ) then return true end
if( inItemID == E_ITEM_LEATHER_PANTS ) then return true end
if( inItemID == E_ITEM_LEATHER_BOOTS ) then return true end
if (IN_itemID == chainmailhelmet) then _result = true end
if (IN_itemID == chainmailchestplate) then _result = true end
if (IN_itemID == chainmailpants) then _result = true end
if (IN_itemID == chainmailboots) then _result = true end
if( inItemID == E_ITEM_CHAIN_HELMET ) then return true end
if( inItemID == E_ITEM_CHAIN_CHESTPLATE ) then return true end
if( inItemID == E_ITEM_CHAIN_LEGGINGS ) then return true end
if( inItemID == E_ITEM_CHAIN_BOOTS ) then return true end
if (IN_itemID == ironhelmet) then _result = true end
if (IN_itemID == ironchestplate) then _result = true end
if (IN_itemID == ironpants) then _result = true end
if (IN_itemID == ironboots) then _result = true end
if( inItemID == E_ITEM_IRON_HELMET ) then return true end
if( inItemID == E_ITEM_IRON_CHESTPLATE ) then return true end
if( inItemID == E_ITEM_IRON_LEGGINGS ) then return true end
if( inItemID == E_ITEM_IRON_BOOTS ) then return true end
if (IN_itemID == diamondhelmet) then _result = true end
if (IN_itemID == diamondchestplate) then _result = true end
if (IN_itemID == diamondpants) then _result = true end
if (IN_itemID == diamondboots) then _result = true end
if( inItemID == E_ITEM_DIAMOND_HELMET ) then return true end
if( inItemID == E_ITEM_DIAMOND_CHESTPLATE ) then return true end
if( inItemID == E_ITEM_DIAMOND_LEGGINGS ) then return true end
if( inItemID == E_ITEM_DIAMOND_BOOTS ) then return true end
if (IN_itemID == goldenhelmet) then _result = true end
if (IN_itemID == goldenchestplate) then _result = true end
if (IN_itemID == goldenpants) then _result = true end
if (IN_itemID == goldenboots) then _result = true end
return _result
end
-- Returns full-length playername for a short name (usefull for parsing commands)
function GetExactPlayername(IN_playername)
local _result = IN_playername
local function SetProcessingPlayername(IN_player)
_result = IN_player:GetName()
if( inItemID == E_ITEM_GOLD_HELMET ) then return true end
if( inItemID == E_ITEM_GOLD_CHESTPLATE ) then return true end
if( inItemID == E_ITEM_GOLD_LEGGINGS ) then return true end
if( inItemID == E_ITEM_GOLD_BOOTS ) then return true end
if( inCheckForHorseArmor ) then
if( inItemID == E_ITEM_IRON_HORSE_ARMOR ) then return true end
if( inItemID == E_ITEM_GOLD_HORSE_ARMOR ) then return true end
if( inItemID == E_ITEM_DIAMOND_HORSE_ARMOR ) then return true end
end
cRoot:Get():FindAndDoWithPlayer(IN_playername, SetProcessingPlayername)
return false
end
-- Returns full-length playername for a short name( usefull for parsing commands )
function GetExactPlayername( inPlayerName )
local _result = inPlayerName
local function SetProcessingPlayername( inPlayer )
_result = inPlayer:GetName()
end
cRoot:Get():FindAndDoWithPlayer( inPlayerName, SetProcessingPlayername )
return _result
end
function GetPlayerByName(IN_playername)
function GetPlayerByName( inPlayerName )
local _player
local PlayerSetter = function (Player)
local PlayerSetter = function( Player )
_player = Player
end
cRoot:Get():FindAndDoWithPlayer(IN_playername, PlayerSetter)
cRoot:Get():FindAndDoWithPlayer( inPlayerName, PlayerSetter )
return _player
end
--[[
Not-so-usual math _functions
]]
-- Rounds floating point number. Because lua guys think this function doesn't deserve to be presented in lua's math
function round(IN_x)
if (IN_x%2 ~= 0.5) then
return math.floor(IN_x+0.5)
function round( inX )
if( inX%2 ~= 0.5 ) then
return math.floor( inX + 0.5 )
end
return IN_x-0.5
return inX - 0.5
end
--[[
Functions I use for filework and stringswork
]]
function PluralString(IN_value, IN_singular_string, IN_plural_string)
local _value_string = tostring(IN_value)
if (_value_string[#_value_string] == "1") then
return IN_singular_string
function PluralString( inValue, inSingularString, inPluralString )
local _value_string = tostring( inValue )
if( _value_string[#_value_string] == "1" ) then
return inSingularString
end
return IN_plural_string
return inPluralString
end
function PluralItemName(IN_itemID, IN_ammount) -- BEWARE! TEMPORAL SOLUTION THERE! :D
local _value_string = tostring(IN_value)
function PluralItemName( inItemID, inAmount ) -- BEWARE! TEMPORAL SOLUTION THERE! :D
local _value_string = tostring( inValue )
local _name = ""
if (_value_string[#_value_string] == "1") then
if( _value_string[#_value_string] == "1" ) then
-- singular names
_name = ItemTypeToString(IN_itemID)
_name = ItemTypeToString( inItemID )
else
-- plural names
_name = ItemTypeToString(IN_itemID).."s"
_name = ItemTypeToString( inItemID ).."s"
end
return _name
end
-- for filewriting purposes. 0 = false, 1 = true
function StringToBool(value)
if value=="1" then return true end
function StringToBool( inValue )
if( inValue == "1" ) then return true end
return false
end
-- same, but reversal
function BoolToString(value)
if value==true then return 1 end
function BoolToString( inValue )
if( inValue == true ) then return 1 end
return 0
end

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<project>
<file>
<filename>InfoDump.lua</filename>
</file>
</project>

View File

@ -0,0 +1,361 @@
#!/usr/bin/lua
-- InfoDump.lua
-- Goes through all subfolders, loads Info.lua and dumps its g_PluginInfo into various text formats
-- This is used for generating plugin documentation for the forum and for GitHub's INFO.md files
-- This script requires LuaRocks with LFS installed, instructions are printed when this is not present.
-- Check Lua version. We use 5.1-specific construct when loading the plugin info, 5.2 is not compatible!
if (_VERSION ~= "Lua 5.1") then
print("Unsupported Lua version. This script requires Lua version 5.1, this Lua is version " .. (_VERSION or "<nil>"));
return;
end
-- Try to load lfs, do not abort if not found
local lfs, err = pcall(
function()
return require("lfs")
end
);
-- Rather, print a nice message with instructions:
if not(lfs) then
print([[
Cannot load LuaFileSystem
Install it through luarocks by executing the following command:
sudo luarocks install luafilesystem
If you don't have luarocks installed, you need to install them using your OS's package manager, usually:
sudo apt-get install luarocks
On windows, a binary distribution can be downloaded from the LuaRocks homepage, http://luarocks.org/en/Download
]]);
print("Original error text: ", err);
return;
end
-- We now know that LFS is present, get it normally:
lfs = require("lfs");
--- Replaces generic formatting with forum-specific formatting
-- Also removes the single line-ends
local function ForumizeString(a_Str)
assert(type(a_Str) == "string");
-- Remove the indentation, unless in the code tag:
-- Only one code or /code tag per line is supported!
local IsInCode = false;
local function RemoveIndentIfNotInCode(s)
if (IsInCode) then
-- we're in code section, check if this line terminates it
IsInCode = (s:find("{%%/code}") ~= nil);
return s .. "\n";
else
-- we're not in code section, check if this line starts it
IsInCode = (s:find("{%%code}") ~= nil);
return s:gsub("^%s*", "") .. "\n";
end
end
a_Str = a_Str:gsub("(.-)\n", RemoveIndentIfNotInCode);
-- Replace multiple line ends with {%p} and single line ends with a space,
-- so that manual word-wrap in the Info.lua file doesn't wrap in the forum.
a_Str = a_Str:gsub("\n\n", "{%%p}");
a_Str = a_Str:gsub("\n", " ");
-- Replace the generic formatting:
a_Str = a_Str:gsub("{%%p}", "\n\n");
a_Str = a_Str:gsub("{%%b}", "[b]"):gsub("{%%/b}", "[/b]");
a_Str = a_Str:gsub("{%%i}", "[i]"):gsub("{%%/i}", "[/i]");
a_Str = a_Str:gsub("{%%list}", "[list]"):gsub("{%%/list}", "[/list]");
a_Str = a_Str:gsub("{%%li}", "[*]"):gsub("{%%/li}", "");
-- TODO: Other formatting
return a_Str;
end
--- Builds an array of categories, each containing all the commands belonging to the category,
-- and the category description, if available.
-- Returns the array table, each item has the following format:
-- { Name = "CategoryName", Description = "CategoryDescription", Commands = {{CommandString = "/cmd verb", Info = {...}}, ...}}
local function BuildCategories(a_PluginInfo)
-- The returned result
-- This will contain both an array and a dict of the categories, to allow fast search
local res = {};
-- For each command add a reference to it into all of its categories:
local function AddCommands(a_CmdPrefix, a_Commands)
for cmd, info in pairs(a_Commands) do
local NewCmd =
{
CommandString = a_CmdPrefix .. cmd,
Info = info,
}
if ((info.HelpString ~= nil) and (info.HelpString ~= "")) then
-- Add to each specified category:
local Category = info.Category;
if (type(Category) == "string") then
Category = {Category};
end
for idx, cat in ipairs(Category or {""}) do
local CatEntry = res[cat];
if (CatEntry == nil) then
-- First time we came across this category, create it:
local NewCat = {Name = cat, Description = "", Commands = {NewCmd}};
table.insert(res, NewCat);
res[cat] = NewCat;
else
-- We already have this category, just add the command to its list of commands:
table.insert(CatEntry.Commands, NewCmd);
end
end -- for idx, cat - Category[]
end -- if (HelpString valid)
-- Recurse all subcommands:
if (info.Subcommands ~= nil) then
AddCommands(a_CmdPrefix .. cmd .. " ", info.Subcommands);
end
end -- for cmd, info - a_Commands[]
end -- AddCommands()
AddCommands("", a_PluginInfo.Commands);
-- Assign descriptions to categories:
for name, desc in pairs(a_PluginInfo.Categories or {}) do
local CatEntry = res[name];
if (CatEntry ~= nil) then
-- The result has this category, add the description:
CatEntry.Description = desc.Description;
end
end
-- Alpha-sort each category's command list:
for idx, cat in ipairs(res) do
table.sort(cat.Commands,
function (cmd1, cmd2)
return (string.lower(cmd1.CommandString) < string.lower(cmd2.CommandString));
end
);
end
return res;
end
--- Writes the specified command detailed help array to the output file, in the forum dump format
local function WriteCommandParameterCombinationsForum(a_CmdString, a_ParameterCombinations, f)
assert(type(a_CmdString) == "string");
assert(type(a_ParameterCombinations) == "table");
assert(f ~= nil);
if (#a_ParameterCombinations == 0) then
-- No explicit parameter combinations to write
return;
end
f:write("The following parameter combinations are recognized:\n");
for idx, combination in ipairs(a_ParameterCombinations) do
f:write("[color=blue]", a_CmdString, "[/color] [color=green]", combination.Params, "[/color]");
if (combination.Help ~= nil) then
f:write(" - ", ForumizeString(combination.Help));
end
if (combination.Permission ~= nil) then
f:write(" (Requires permission '[color=red]", combination.Permission, "[/color]')");
end
f:write("\n");
end
end
--- Writes all commands in the specified category to the output file, in the forum dump format
local function WriteCommandsCategoryForum(a_Category, f)
-- Write category name:
local CategoryName = a_Category.Name;
if (CategoryName == "") then
CategoryName = "General";
end
f:write("\n[size=Large]", ForumizeString(a_Category.DisplayName or CategoryName), "[/size]\n");
-- Write description:
if (a_Category.Description ~= "") then
f:write(ForumizeString(a_Category.Description), "\n");
end
-- Write commands:
f:write("\n[list]");
for idx2, cmd in ipairs(a_Category.Commands) do
f:write("\n[b]", cmd.CommandString, "[/b] - ", ForumizeString(cmd.Info.HelpString or "UNDOCUMENTED"), "\n");
if (cmd.Info.Permission ~= nil) then
f:write("Permission required: [color=red]", cmd.Info.Permission, "[/color]\n");
end
if (cmd.Info.DetailedDescription ~= nil) then
f:write(cmd.Info.DetailedDescription);
end
if (cmd.Info.ParameterCombinations ~= nil) then
WriteCommandParameterCombinationsForum(cmd.CommandString, cmd.Info.ParameterCombinations, f);
end
end
f:write("[/list]\n\n")
end
local function DumpCommandsForum(a_PluginInfo, f)
-- Copy all Categories from a dictionary into an array:
local Categories = BuildCategories(a_PluginInfo);
-- Sort the categories by name:
table.sort(Categories,
function(cat1, cat2)
return (string.lower(cat1.Name) < string.lower(cat2.Name));
end
);
if (#Categories == 0) then
return;
end
f:write("\n[size=X-Large]Commands[/size]\n");
-- Dump per-category commands:
for idx, cat in ipairs(Categories) do
WriteCommandsCategoryForum(cat, f);
end
end
local function DumpAdditionalInfoForum(a_PluginInfo, f)
local AInfo = a_PluginInfo.AdditionalInfo;
if ((AInfo == nil) or (type(AInfo) ~= "table")) then
-- There is no AdditionalInfo in a_PluginInfo
return;
end
for idx, info in ipairs(a_PluginInfo.AdditionalInfo) do
if ((info.Title ~= nil) and (info.Contents ~= nil)) then
f:write("\n[size=X-Large]", ForumizeString(info.Title), "[/size]\n");
f:write(ForumizeString(info.Contents), "\n");
end
end
end
local function DumpPluginInfoForum(a_PluginFolder, a_PluginInfo)
-- Open the output file:
local f, msg = io.open(a_PluginInfo.Name .. "_forum.txt", "w");
if (f == nil) then
print("\tCannot dump forum info for plugin " .. a_PluginFolder .. ": " .. msg);
return;
end
-- Write the description:
f:write(ForumizeString(a_PluginInfo.Description), "\n");
DumpAdditionalInfoForum(a_PluginInfo, f);
DumpCommandsForum(a_PluginInfo, f);
f:close();
end
</