summaryrefslogtreecommitdiffstats
path: root/Server/Plugins/Debuggers/Debuggers.lua
diff options
context:
space:
mode:
authorMattes D <github@xoft.cz>2015-08-26 10:58:51 +0200
committerMattes D <github@xoft.cz>2015-09-05 22:01:21 +0200
commitdac97826f7d0c9b9135c1a08c4d5f16b61494bd1 (patch)
treeec17a9703fe734956e4f151cb5306befc4fd7571 /Server/Plugins/Debuggers/Debuggers.lua
parentMerge pull request #2459 from cuberite/fixes (diff)
downloadcuberite-dac97826f7d0c9b9135c1a08c4d5f16b61494bd1.tar
cuberite-dac97826f7d0c9b9135c1a08c4d5f16b61494bd1.tar.gz
cuberite-dac97826f7d0c9b9135c1a08c4d5f16b61494bd1.tar.bz2
cuberite-dac97826f7d0c9b9135c1a08c4d5f16b61494bd1.tar.lz
cuberite-dac97826f7d0c9b9135c1a08c4d5f16b61494bd1.tar.xz
cuberite-dac97826f7d0c9b9135c1a08c4d5f16b61494bd1.tar.zst
cuberite-dac97826f7d0c9b9135c1a08c4d5f16b61494bd1.zip
Diffstat (limited to 'Server/Plugins/Debuggers/Debuggers.lua')
-rw-r--r--Server/Plugins/Debuggers/Debuggers.lua2047
1 files changed, 2047 insertions, 0 deletions
diff --git a/Server/Plugins/Debuggers/Debuggers.lua b/Server/Plugins/Debuggers/Debuggers.lua
new file mode 100644
index 000000000..bd0b94a06
--- /dev/null
+++ b/Server/Plugins/Debuggers/Debuggers.lua
@@ -0,0 +1,2047 @@
+
+-- Global variables
+g_DropSpensersToActivate = {}; -- A list of dispensers and droppers (as {World, X, Y Z} quadruplets) that are to be activated every tick
+g_HungerReportTick = 10;
+g_ShowFoodStats = false; -- When true, each player's food stats are sent to them every 10 ticks
+
+
+
+
+
+
+function Initialize(a_Plugin)
+ --[[
+ -- Test multiple hook handlers:
+ cPluginManager.AddHook(cPluginManager.HOOK_TICK, OnTick1);
+ cPluginManager.AddHook(cPluginManager.HOOK_TICK, OnTick2);
+ --]]
+
+ local PM = cPluginManager;
+ PM:AddHook(cPluginManager.HOOK_PLAYER_USING_BLOCK, OnPlayerUsingBlock);
+ PM:AddHook(cPluginManager.HOOK_PLAYER_USING_ITEM, OnPlayerUsingItem);
+ PM:AddHook(cPluginManager.HOOK_TAKE_DAMAGE, OnTakeDamage);
+ PM:AddHook(cPluginManager.HOOK_TICK, OnTick);
+ PM:AddHook(cPluginManager.HOOK_CHAT, OnChat);
+ PM:AddHook(cPluginManager.HOOK_PLAYER_RIGHT_CLICKING_ENTITY, OnPlayerRightClickingEntity);
+ PM:AddHook(cPluginManager.HOOK_WORLD_TICK, OnWorldTick);
+ PM:AddHook(cPluginManager.HOOK_PLUGINS_LOADED, OnPluginsLoaded);
+ PM:AddHook(cPluginManager.HOOK_PLAYER_JOINED, OnPlayerJoined);
+ PM:AddHook(cPluginManager.HOOK_PROJECTILE_HIT_BLOCK, OnProjectileHitBlock);
+ PM:AddHook(cPluginManager.HOOK_CHUNK_UNLOADING, OnChunkUnloading);
+ PM:AddHook(cPluginManager.HOOK_WORLD_STARTED, OnWorldStarted);
+ PM:AddHook(cPluginManager.HOOK_PROJECTILE_HIT_BLOCK, OnProjectileHitBlock);
+
+ -- _X: Disabled WECUI manipulation:
+ -- PM:AddHook(cPluginManager.HOOK_PLUGIN_MESSAGE, OnPluginMessage);
+ -- _X: Disabled so that the normal operation doesn't interfere with anything
+ -- PM:AddHook(cPluginManager.HOOK_CHUNK_GENERATED, OnChunkGenerated);
+
+ -- Load the InfoReg shared library:
+ dofile(cPluginManager:GetPluginsPath() .. "/InfoReg.lua")
+
+ -- Bind all the commands:
+ RegisterPluginInfoCommands();
+
+ -- Bind all the console commands:
+ RegisterPluginInfoConsoleCommands();
+
+ a_Plugin:AddWebTab("Debuggers", HandleRequest_Debuggers)
+ a_Plugin:AddWebTab("StressTest", HandleRequest_StressTest)
+
+ -- Enable the following line for BlockArea / Generator interface testing:
+ -- PluginManager:AddHook(Plugin, cPluginManager.HOOK_CHUNK_GENERATED);
+
+ -- TestBlockAreas()
+ -- TestSQLiteBindings()
+ -- TestExpatBindings()
+ TestPluginCalls()
+
+ TestBlockAreasString()
+ TestStringBase64()
+ -- TestUUIDFromName()
+ -- TestRankMgr()
+ TestFileExt()
+ TestFileLastMod()
+ TestPluginInterface()
+
+ local LastSelfMod = cFile:GetLastModificationTime(a_Plugin:GetLocalFolder() .. "/Debuggers.lua")
+ LOG("Debuggers.lua last modified on " .. os.date("%Y-%m-%dT%H:%M:%S", LastSelfMod))
+
+ --[[
+ -- 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)
+ )
+ --]]
+
+ -- Test the crash in #1889:
+ cPluginManager:AddHook(cPluginManager.HOOK_PLAYER_RIGHT_CLICKING_ENTITY,
+ function (a_CBPlayer, a_CBEntity)
+ a_CBPlayer:GetWorld():DoWithEntityByID( -- This will crash the server in #1889
+ a_CBEntity:GetUniqueID(),
+ function(Entity)
+ LOG("RightClicking an entity, crash #1889 fixed. Entity is a " .. tolua.type(Entity))
+ end
+ )
+ end
+ )
+
+ return true
+end;
+
+
+
+
+
+function TestPluginInterface()
+ cPluginManager:DoWithPlugin("Core",
+ function (a_CBPlugin)
+ if (a_CBPlugin:GetStatus() == cPluginManager.psLoaded) then
+ LOG("Core plugin was found, version " .. a_CBPlugin:GetVersion())
+ else
+ LOG("Core plugin is not loaded")
+ end
+ end
+ )
+
+ cPluginManager:ForEachPlugin(
+ function (a_CBPlugin)
+ LOG("Plugin in " .. a_CBPlugin:GetFolderName() .. " has an API name of " .. a_CBPlugin:GetName() .. " and status " .. a_CBPlugin:GetStatus())
+ end
+ )
+end
+
+
+
+
+function TestFileExt()
+ assert(cFile:ChangeFileExt("fileless_dir/", "new") == "fileless_dir/")
+ assert(cFile:ChangeFileExt("fileless_dir/", ".new") == "fileless_dir/")
+ assert(cFile:ChangeFileExt("pathless_file.ext", "new") == "pathless_file.new")
+ assert(cFile:ChangeFileExt("pathless_file.ext", ".new") == "pathless_file.new")
+ assert(cFile:ChangeFileExt("path/to/file.ext", "new") == "path/to/file.new")
+ assert(cFile:ChangeFileExt("path/to/file.ext", ".new") == "path/to/file.new")
+ assert(cFile:ChangeFileExt("path/to.dir/file", "new") == "path/to.dir/file.new")
+ assert(cFile:ChangeFileExt("path/to.dir/file", ".new") == "path/to.dir/file.new")
+ assert(cFile:ChangeFileExt("path/to.dir/file.ext", "new") == "path/to.dir/file.new")
+ assert(cFile:ChangeFileExt("path/to.dir/file.ext", ".new") == "path/to.dir/file.new")
+ assert(cFile:ChangeFileExt("path/to.dir/file.longext", "new") == "path/to.dir/file.new")
+ assert(cFile:ChangeFileExt("path/to.dir/file.longext", ".new") == "path/to.dir/file.new")
+ assert(cFile:ChangeFileExt("path/to.dir/file.", "new") == "path/to.dir/file.new")
+ assert(cFile:ChangeFileExt("path/to.dir/file.", ".new") == "path/to.dir/file.new")
+end
+
+
+
+
+
+function TestFileLastMod()
+ local f = assert(io.open("test.txt", "w"))
+ f:write("test")
+ f:close()
+ local filetime = cFile:GetLastModificationTime("test.txt")
+ local ostime = os.time()
+ LOG("file time: " .. filetime .. ", OS time: " .. ostime .. ", difference: " .. ostime - filetime)
+end
+
+
+
+
+
+function TestPluginCalls()
+ -- In order to test the inter-plugin communication, we're going to call Core's ReturnColorFromChar() function
+ -- It is a rather simple function that doesn't need any tables as its params and returns a value, too
+ -- Note the signature: function ReturnColorFromChar( Split, char ) ... return cChatColog.Gray ... end
+ -- The Split parameter should be a table, but it is not used in that function anyway,
+ -- so we can get away with passing nil to it.
+
+ LOG("Debuggers: Calling NoSuchPlugin.FnName()...")
+ cPluginManager:CallPlugin("NoSuchPlugin", "FnName", "SomeParam")
+ LOG("Debuggers: Calling Core.NoSuchFunction()...")
+ cPluginManager:CallPlugin("Core", "NoSuchFunction", "SomeParam")
+ LOG("Debuggers: Calling Core.ReturnColorFromChar(..., \"8\")...")
+ local Gray = cPluginManager:CallPlugin("Core", "ReturnColorFromChar", "split", "8")
+ if (Gray ~= cChatColor.Gray) then
+ LOGWARNING("Debuggers: Call failed, exp " .. cChatColor.Gray .. ", got " .. (Gray or "<nil>"))
+ else
+ LOG("Debuggers: Call succeeded")
+ end
+ LOG("Debuggers: Inter-plugin calls done.")
+end
+
+
+
+
+
+function TestBlockAreas()
+ LOG("Testing block areas...");
+
+ -- Debug block area merging:
+ local BA1 = cBlockArea();
+ local BA2 = cBlockArea();
+ if (BA1:LoadFromSchematicFile("schematics/test.schematic")) then
+ if (BA2:LoadFromSchematicFile("schematics/fountain.schematic")) then
+ BA2:SetRelBlockType(0, 0, 0, E_BLOCK_LAPIS_BLOCK);
+ BA2:SetRelBlockType(1, 0, 0, E_BLOCK_LAPIS_BLOCK);
+ BA2:SetRelBlockType(2, 0, 0, E_BLOCK_LAPIS_BLOCK);
+ BA1:Merge(BA2, 1, 10, 1, cBlockArea.msImprint);
+ BA1:SaveToSchematicFile("schematics/merge.schematic");
+ end
+ else
+ BA1:Create(16, 16, 16);
+ end
+
+ -- Debug block area cuboid filling:
+ BA1:FillRelCuboid(2, 9, 2, 8, 2, 8, cBlockArea.baTypes, E_BLOCK_GOLD_BLOCK);
+ BA1:RelLine(2, 2, 2, 9, 8, 8, cBlockArea.baTypes or cBlockArea.baMetas, E_BLOCK_SAPLING, E_META_SAPLING_BIRCH);
+ BA1:SaveToSchematicFile("schematics/fillrel.schematic");
+
+ -- Debug block area mirroring:
+ if (BA1:LoadFromSchematicFile("schematics/lt.schematic")) then
+ BA1:MirrorXYNoMeta();
+ BA1:SaveToSchematicFile("schematics/lt_XY.schematic");
+ BA1:MirrorXYNoMeta();
+ BA1:SaveToSchematicFile("schematics/lt_XY2.schematic");
+
+ BA1:MirrorXZNoMeta();
+ BA1:SaveToSchematicFile("schematics/lt_XZ.schematic");
+ BA1:MirrorXZNoMeta();
+ BA1:SaveToSchematicFile("schematics/lt_XZ2.schematic");
+
+ BA1:MirrorYZNoMeta();
+ BA1:SaveToSchematicFile("schematics/lt_YZ.schematic");
+ BA1:MirrorYZNoMeta();
+ BA1:SaveToSchematicFile("schematics/lt_YZ2.schematic");
+ end
+
+ -- Debug block area rotation:
+ if (BA1:LoadFromSchematicFile("schematics/rot.schematic")) then
+ BA1:RotateCWNoMeta();
+ BA1:SaveToSchematicFile("schematics/rot1.schematic");
+ BA1:RotateCWNoMeta();
+ BA1:SaveToSchematicFile("schematics/rot2.schematic");
+ BA1:RotateCWNoMeta();
+ BA1:SaveToSchematicFile("schematics/rot3.schematic");
+ BA1:RotateCWNoMeta();
+ BA1:SaveToSchematicFile("schematics/rot4.schematic");
+ end
+
+ -- Debug block area rotation:
+ if (BA1:LoadFromSchematicFile("schematics/rotm.schematic")) then
+ BA1:RotateCCW();
+ BA1:SaveToSchematicFile("schematics/rotm1.schematic");
+ BA1:RotateCCW();
+ BA1:SaveToSchematicFile("schematics/rotm2.schematic");
+ BA1:RotateCCW();
+ BA1:SaveToSchematicFile("schematics/rotm3.schematic");
+ BA1:RotateCCW();
+ BA1:SaveToSchematicFile("schematics/rotm4.schematic");
+ end
+
+ -- Debug block area mirroring:
+ if (BA1:LoadFromSchematicFile("schematics/ltm.schematic")) then
+ BA1:MirrorXY();
+ BA1:SaveToSchematicFile("schematics/ltm_XY.schematic");
+ BA1:MirrorXY();
+ BA1:SaveToSchematicFile("schematics/ltm_XY2.schematic");
+
+ BA1:MirrorXZ();
+ BA1:SaveToSchematicFile("schematics/ltm_XZ.schematic");
+ BA1:MirrorXZ();
+ BA1:SaveToSchematicFile("schematics/ltm_XZ2.schematic");
+
+ BA1:MirrorYZ();
+ BA1:SaveToSchematicFile("schematics/ltm_YZ.schematic");
+ BA1:MirrorYZ();
+ BA1:SaveToSchematicFile("schematics/ltm_YZ2.schematic");
+ end
+
+ LOG("Block areas test ended");
+end
+
+
+
+
+
+
+function TestBlockAreasString()
+ -- Write one area to string, then to file:
+ local BA1 = cBlockArea()
+ BA1:Create(5, 5, 5, cBlockArea.baTypes + cBlockArea.baMetas)
+ BA1:Fill(cBlockArea.baTypes, E_BLOCK_DIAMOND_BLOCK)
+ BA1:FillRelCuboid(1, 3, 1, 3, 1, 3, cBlockArea.baTypes, E_BLOCK_GOLD_BLOCK)
+ local Data = BA1:SaveToSchematicString()
+ if ((type(Data) ~= "string") or (Data == "")) then
+ LOG("Cannot save schematic to string")
+ return
+ end
+ cFile:CreateFolder("schematics")
+ local f = io.open("schematics/StringTest.schematic", "wb")
+ f:write(Data)
+ f:close()
+
+ -- Load a second area from that file:
+ local BA2 = cBlockArea()
+ if not(BA2:LoadFromSchematicFile("schematics/StringTest.schematic")) then
+ LOG("Cannot read schematic from string test file")
+ return
+ end
+ BA2:Clear()
+
+ -- Load another area from a string in that file:
+ f = io.open("schematics/StringTest.schematic", "rb")
+ Data = f:read("*all")
+ if not(BA2:LoadFromSchematicString(Data)) then
+ LOG("Cannot load schematic from string")
+ end
+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 TestUUIDFromName()
+ LOG("Testing UUID-from-Name resolution...")
+
+ -- Test by querying a few existing names, along with a non-existent one:
+ local PlayerNames =
+ {
+ "xoft",
+ "aloe_vera",
+ "nonexistent_player",
+ }
+ -- WARNING: Blocking operation! DO NOT USE IN TICK THREAD!
+ local UUIDs = cMojangAPI:GetUUIDsFromPlayerNames(PlayerNames)
+
+ -- Log the results:
+ for _, name in ipairs(PlayerNames) do
+ local UUID = UUIDs[name]
+ if (UUID == nil) then
+ LOG(" UUID(" .. name .. ") not found.")
+ else
+ LOG(" UUID(" .. name .. ") = \"" .. UUID .. "\"")
+ end
+ end
+
+ -- Test once more with the same players, valid-only. This should go directly from cache, so fast.
+ LOG("Testing again with the same valid players...")
+ local ValidPlayerNames =
+ {
+ "xoft",
+ "aloe_vera",
+ }
+ UUIDs = cMojangAPI:GetUUIDsFromPlayerNames(ValidPlayerNames);
+
+ -- Log the results:
+ for _, name in ipairs(ValidPlayerNames) do
+ local UUID = UUIDs[name]
+ if (UUID == nil) then
+ LOG(" UUID(" .. name .. ") not found.")
+ else
+ LOG(" UUID(" .. name .. ") = \"" .. UUID .. "\"")
+ end
+ end
+
+ -- Test yet again, cache-only:
+ LOG("Testing once more, cache only...")
+ local PlayerNames3 =
+ {
+ "xoft",
+ "aloe_vera",
+ "notch", -- Valid player name, but not cached (most likely :)
+ }
+ UUIDs = cMojangAPI:GetUUIDsFromPlayerNames(PlayerNames3, true)
+
+ -- Log the results:
+ for _, name in ipairs(PlayerNames3) do
+ local UUID = UUIDs[name]
+ if (UUID == nil) then
+ LOG(" UUID(" .. name .. ") not found.")
+ else
+ LOG(" UUID(" .. name .. ") = \"" .. UUID .. "\"")
+ end
+ end
+
+ LOG("UUID-from-Name resolution tests finished.")
+
+ LOG("Performing a Name-from-UUID test...")
+ -- local NameToTest = "aloe_vera"
+ local NameToTest = "xoft"
+ local Name = cMojangAPI:GetPlayerNameFromUUID(UUIDs[NameToTest])
+ LOG("Name(" .. UUIDs[NameToTest] .. ") = '" .. Name .. "', expected '" .. NameToTest .. "'.")
+ LOG("Name-from-UUID test finished.")
+end
+
+
+
+
+
+function TestRankMgr()
+ LOG("Testing the rank manager")
+ cRankManager:AddRank("LuaRank")
+ cRankManager:AddGroup("LuaTestGroup")
+ cRankManager:AddGroupToRank("LuaTestGroup", "LuaRank")
+ cRankManager:AddPermissionToGroup("luaperm", "LuaTestGroup")
+end
+
+
+
+
+
+function TestSQLiteBindings()
+ LOG("Testing SQLite bindings...");
+
+ -- Debug SQLite binding
+ local TestDB, ErrCode, ErrMsg = sqlite3.open("test.sqlite");
+ if (TestDB ~= nil) then
+ local function ShowRow(UserData, NumCols, Values, Names)
+ assert(UserData == 'UserData');
+ LOG("New row");
+ for i = 1, NumCols do
+ LOG(" " .. Names[i] .. " = " .. Values[i]);
+ end
+ return 0;
+ end
+ local sql = [=[
+ CREATE TABLE numbers(num1,num2,str);
+ INSERT INTO numbers VALUES(1, 11, "ABC");
+ INSERT INTO numbers VALUES(2, 22, "DEF");
+ INSERT INTO numbers VALUES(3, 33, "UVW");
+ INSERT INTO numbers VALUES(4, 44, "XYZ");
+ SELECT * FROM numbers;
+ ]=]
+ local Res = TestDB:exec(sql, ShowRow, 'UserData');
+ if (Res ~= sqlite3.OK) then
+ LOG("TestDB:exec() failed: " .. Res .. " (" .. TestDB:errmsg() .. ")");
+ end;
+ TestDB:close();
+ else
+ -- This happens if for example SQLite cannot open the file (eg. a folder with the same name exists)
+ LOG("SQLite3 failed to open DB! (" .. ErrCode .. ", " .. ErrMsg ..")");
+ end
+
+ LOG("SQLite bindings test ended");
+end
+
+
+
+
+
+function TestExpatBindings()
+ LOG("Testing Expat bindings...");
+
+ -- Debug LuaExpat bindings:
+ local count = 0
+ callbacks = {
+ StartElement = function (parser, name)
+ LOG("+ " .. string.rep(" ", count) .. name);
+ count = count + 1;
+ end,
+ EndElement = function (parser, name)
+ count = count - 1;
+ LOG("- " .. string.rep(" ", count) .. name);
+ end
+ }
+
+ local p = lxp.new(callbacks);
+ p:parse("<elem1>\nnext line\nanother line");
+ p:parse("text\n");
+ p:parse("<elem2/>\n");
+ p:parse("more text");
+ p:parse("</elem1>");
+ p:parse("\n");
+ p:parse(); -- finishes the document
+ p:close(); -- closes the parser
+
+ LOG("Expat bindings test ended");
+end
+
+
+
+
+
+function OnUsingBlazeRod(Player, BlockX, BlockY, BlockZ, BlockFace, CursorX, CursorY, CursorZ)
+ -- Magic rod of query: show block types and metas for both neighbors of the pointed face
+ local Valid, Type, Meta = Player:GetWorld():GetBlockTypeMeta(BlockX, BlockY, BlockZ);
+
+ if (Type == E_BLOCK_AIR) then
+ Player:SendMessage(cChatColor.LightGray .. "Block {" .. BlockX .. ", " .. BlockY .. ", " .. BlockZ .. "}: air:" .. Meta);
+ else
+ local TempItem = cItem(Type, 1, Meta);
+ Player:SendMessage(cChatColor.LightGray .. "Block {" .. BlockX .. ", " .. BlockY .. ", " .. BlockZ .. "}: " .. ItemToFullString(TempItem) .. " (" .. Type .. ":" .. Meta .. ")");
+ end
+
+ local X, Y, Z = AddFaceDirection(BlockX, BlockY, BlockZ, BlockFace);
+ Valid, Type, Meta = Player:GetWorld():GetBlockTypeMeta(X, Y, Z);
+ if (Type == E_BLOCK_AIR) then
+ Player:SendMessage(cChatColor.LightGray .. "Block {" .. X .. ", " .. Y .. ", " .. Z .. "}: air:" .. Meta);
+ else
+ local TempItem = cItem(Type, 1, Meta);
+ Player:SendMessage(cChatColor.LightGray .. "Block {" .. X .. ", " .. Y .. ", " .. Z .. "}: " .. ItemToFullString(TempItem) .. " (" .. Type .. ":" .. Meta .. ")");
+ end
+ return false;
+end
+
+
+
+
+
+function OnUsingDiamond(Player, BlockX, BlockY, BlockZ, BlockFace, CursorX, CursorY, CursorZ)
+ -- Rclk with a diamond to test block area cropping and expanding
+ local Area = cBlockArea();
+ Area:Read(Player:GetWorld(),
+ BlockX - 19, BlockX + 19,
+ BlockY - 7, BlockY + 7,
+ BlockZ - 19, BlockZ + 19
+ );
+
+ LOG("Size before cropping: " .. Area:GetSizeX() .. " x " .. Area:GetSizeY() .. " x " .. Area:GetSizeZ());
+ Area:DumpToRawFile("crop0.dat");
+
+ Area:Crop(2, 3, 0, 0, 0, 0);
+ LOG("Size after cropping 1: " .. Area:GetSizeX() .. " x " .. Area:GetSizeY() .. " x " .. Area:GetSizeZ());
+ Area:DumpToRawFile("crop1.dat");
+
+ Area:Crop(2, 3, 0, 0, 0, 0);
+ LOG("Size after cropping 2: " .. Area:GetSizeX() .. " x " .. Area:GetSizeY() .. " x " .. Area:GetSizeZ());
+ Area:DumpToRawFile("crop2.dat");
+
+ Area:Expand(2, 3, 0, 0, 0, 0);
+ LOG("Size after expanding 1: " .. Area:GetSizeX() .. " x " .. Area:GetSizeY() .. " x " .. Area:GetSizeZ());
+ Area:DumpToRawFile("expand1.dat");
+
+ Area:Expand(3, 2, 1, 1, 0, 0);
+ LOG("Size after expanding 2: " .. Area:GetSizeX() .. " x " .. Area:GetSizeY() .. " x " .. Area:GetSizeZ());
+ Area:DumpToRawFile("expand2.dat");
+
+ Area:Crop(0, 0, 0, 0, 3, 2);
+ LOG("Size after cropping 3: " .. Area:GetSizeX() .. " x " .. Area:GetSizeY() .. " x " .. Area:GetSizeZ());
+ Area:DumpToRawFile("crop3.dat");
+
+ Area:Crop(0, 0, 3, 2, 0, 0);
+ LOG("Size after cropping 4: " .. Area:GetSizeX() .. " x " .. Area:GetSizeY() .. " x " .. Area:GetSizeZ());
+ Area:DumpToRawFile("crop4.dat");
+
+ LOG("Crop test done");
+ Player:SendMessage("Crop / expand test done.");
+ return false;
+end
+
+
+
+
+
+function OnUsingEyeOfEnder(Player, BlockX, BlockY, BlockZ)
+ -- Rclk with an eye of ender places a predefined schematic at the cursor
+ local Area = cBlockArea();
+ if not(Area:LoadFromSchematicFile("schematics/test.schematic")) then
+ LOG("Loading failed");
+ return false;
+ end
+ LOG("Schematic loaded, placing now.");
+ Area:Write(Player:GetWorld(), BlockX, BlockY, BlockZ);
+ LOG("Done.");
+ return false;
+end
+
+
+
+
+
+function OnUsingEnderPearl(Player, BlockX, BlockY, BlockZ, BlockFace, CursorX, CursorY, CursorZ)
+ -- Rclk with an ender pearl saves a predefined area around the cursor into a .schematic file. Also tests area copying
+ local Area = cBlockArea();
+ if not(Area:Read(Player:GetWorld(),
+ BlockX - 8, BlockX + 8, BlockY - 8, BlockY + 8, BlockZ - 8, BlockZ + 8)
+ ) then
+ LOG("LUA: Area couldn't be read");
+ return false;
+ end
+ LOG("LUA: Area read, copying now.");
+ local Area2 = cBlockArea();
+ Area2:CopyFrom(Area);
+ LOG("LUA: Copied, now saving.");
+ if not(Area2:SaveToSchematicFile("schematics/test.schematic")) then
+ LOG("LUA: Cannot save schematic file.");
+ return false;
+ end
+ LOG("LUA: Done.");
+ return false;
+end
+
+
+
+
+
+function OnUsingRedstoneTorch(Player, BlockX, BlockY, BlockZ, BlockFace, CursorX, CursorY, CursorZ)
+ -- Redstone torch activates a rapid dispenser / dropper discharge (at every tick):
+ local BlockType = Player:GetWorld():GetBlock(BlockX, BlockY, BlockZ);
+ if (BlockType == E_BLOCK_DISPENSER) then
+ table.insert(g_DropSpensersToActivate, {World = Player:GetWorld(), x = BlockX, y = BlockY, z = BlockZ});
+ Player:SendMessage("Dispenser at {" .. BlockX .. ", " .. BlockY .. ", " .. BlockZ .. "} discharging");
+ return true;
+ elseif (BlockType == E_BLOCK_DROPPER) then
+ table.insert(g_DropSpensersToActivate, {World = Player:GetWorld(), x = BlockX, y = BlockY, z = BlockZ});
+ Player:SendMessage("Dropper at {" .. BlockX .. ", " .. BlockY .. ", " .. BlockZ .. "} discharging");
+ return true;
+ else
+ Player:SendMessage("Neither a dispenser nor a dropper at {" .. BlockX .. ", " .. BlockY .. ", " .. BlockZ .. "}: " .. BlockType);
+ end
+ return false;
+end
+
+
+
+
+
+function OnPlayerUsingItem(Player, BlockX, BlockY, BlockZ, BlockFace, CursorX, CursorY, CursorZ)
+
+ -- dont check if the direction is in the air
+ if (BlockFace == BLOCK_FACE_NONE) then
+ return false
+ end
+
+ local HeldItem = Player:GetEquippedItem();
+ local HeldItemType = HeldItem.m_ItemType;
+
+ if (HeldItemType == E_ITEM_STICK) then
+ -- Magic sTick of ticking: set the pointed block for ticking at the next tick
+ Player:SendMessage(cChatColor.LightGray .. "Setting next block tick to {" .. BlockX .. ", " .. BlockY .. ", " .. BlockZ .. "}")
+ Player:GetWorld():SetNextBlockTick(BlockX, BlockY, BlockZ);
+ return true
+ elseif (HeldItemType == E_ITEM_BLAZE_ROD) then
+ return OnUsingBlazeRod(Player, BlockX, BlockY, BlockZ, BlockFace, CursorX, CursorY, CursorZ);
+ elseif (HeldItemType == E_ITEM_DIAMOND) then
+ return OnUsingDiamond(Player, BlockX, BlockY, BlockZ, BlockFace, CursorX, CursorY, CursorZ);
+ elseif (HeldItemType == E_ITEM_EYE_OF_ENDER) then
+ return OnUsingEyeOfEnder(Player, BlockX, BlockY, BlockZ, BlockFace, CursorX, CursorY, CursorZ);
+ elseif (HeldItemType == E_ITEM_ENDER_PEARL) then
+ return OnUsingEnderPearl(Player, BlockX, BlockY, BlockZ, BlockFace, CursorX, CursorY, CursorZ);
+ end
+ return false;
+end
+
+
+
+
+
+function OnPlayerUsingBlock(Player, BlockX, BlockY, BlockZ, BlockFace, CursorX, CursorY, CursorZ, BlockType, BlockMeta)
+ -- dont check if the direction is in the air
+ if (BlockFace == BLOCK_FACE_NONE) then
+ return false
+ end
+
+ local HeldItem = Player:GetEquippedItem();
+ local HeldItemType = HeldItem.m_ItemType;
+
+ if (HeldItemType == E_BLOCK_REDSTONE_TORCH_ON) then
+ return OnUsingRedstoneTorch(Player, BlockX, BlockY, BlockZ, BlockFace, CursorX, CursorY, CursorZ);
+ end
+end
+
+
+
+
+
+function OnTakeDamage(Receiver, TDI)
+ -- Receiver is cPawn
+ -- TDI is TakeDamageInfo
+
+ -- LOG(Receiver:GetClass() .. " was dealt " .. DamageTypeToString(TDI.DamageType) .. " damage: Raw " .. TDI.RawDamage .. ", Final " .. TDI.FinalDamage .. " (" .. (TDI.RawDamage - TDI.FinalDamage) .. " covered by armor)");
+ return false;
+end
+
+
+
+
+
+function OnTick1()
+ -- For testing multiple hook handlers per plugin
+ LOGINFO("Tick1");
+end
+
+
+
+
+
+function OnTick2()
+ -- For testing multiple hook handlers per plugin
+ LOGINFO("Tick2");
+end
+
+
+
+
+
+--- When set to a positive number, the following OnTick() will perform GC and decrease until 0 again
+GCOnTick = 0;
+
+
+
+
+
+function OnTick()
+ -- Activate all dropspensers in the g_DropSpensersToActivate list:
+ local ActivateDrSp = function(DropSpenser)
+ if (DropSpenser:GetContents():GetFirstUsedSlot() == -1) then
+ return true;
+ end
+ DropSpenser:Activate();
+ return false;
+ end
+ -- Walk the list backwards, because we're removing some items
+ local idx = #g_DropSpensersToActivate;
+ for i = idx, 1, -1 do
+ local DrSp = g_DropSpensersToActivate[i];
+ if not(DrSp.World:DoWithDropSpenserAt(DrSp.x, DrSp.y, DrSp.z, ActivateDrSp)) then
+ table.remove(g_DropSpensersToActivate, i);
+ end
+ end
+
+
+ -- If GCOnTick > 0, do a garbage-collect and decrease by one
+ if (GCOnTick > 0) then
+ collectgarbage();
+ GCOnTick = GCOnTick - 1;
+ end
+
+ return false;
+end
+
+
+
+
+
+function OnWorldTick(a_World, a_Dt)
+ -- Report food stats, if switched on:
+ local Tick = a_World:GetWorldAge();
+ if (not(g_ShowFoodStats) or (math.mod(Tick, 10) ~= 0)) then
+ return false;
+ end
+ a_World:ForEachPlayer(
+ function(a_Player)
+ a_Player:SendMessage(
+ tostring(Tick / 10) ..
+ " > FS: fl " .. a_Player:GetFoodLevel() ..
+ "; sat " .. a_Player:GetFoodSaturationLevel() ..
+ "; exh " .. a_Player:GetFoodExhaustionLevel()
+ );
+ end
+ );
+end
+
+
+
+
+
+function OnChat(a_Player, a_Message)
+ return false, "blabla " .. a_Message;
+end
+
+
+
+
+
+function OnPlayerRightClickingEntity(a_Player, a_Entity)
+ LOG("Player " .. a_Player:GetName() .. " right-clicking entity ID " .. a_Entity:GetUniqueID() .. ", a " .. a_Entity:GetClass());
+ return false;
+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);
+
+ -- Create a sign there:
+ 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
+ local SignEntity = tolua.cast(BlockEntity, "cSignEntity");
+ SignEntity:SetLines("Chunk:", tonumber(a_ChunkX) .. ", " .. tonumber(a_ChunkZ), "", "(Debuggers)");
+ end
+
+ -- Update the heightmap:
+ a_ChunkDesc:SetHeight(0, 0, Height + 1);
+end
+
+
+
+
+
+-- Function "round" copied from http://lua-users.org/wiki/SimpleRound
+function round(num, idp)
+ local mult = 10^(idp or 0)
+ if num >= 0 then return math.floor(num * mult + 0.5) / mult
+ else return math.ceil(num * mult - 0.5) / mult end
+end
+
+
+
+
+
+function HandleNickCmd(Split, Player)
+ if (Split[2] == nil) then
+ Player:SendMessage("Usage: /nick [CustomName]");
+ return true;
+ end
+
+ Player:SetCustomName(Split[2]);
+ Player:SendMessageSuccess("Custom name setted to " .. Player:GetCustomName() .. "!")
+ return true
+end
+
+
+
+
+
+function HandleListEntitiesCmd(Split, Player)
+ local NumEntities = 0;
+
+ local ListEntity = function(Entity)
+ if (Entity:IsDestroyed()) then
+ -- The entity has already been destroyed, don't list it
+ return false;
+ end;
+ local cls = Entity:GetClass();
+ Player:SendMessage(" " .. Entity:GetUniqueID() .. ": " .. cls .. " {" .. round(Entity:GetPosX(), 2) .. ", " .. round(Entity:GetPosY(), 2) .. ", " .. round(Entity:GetPosZ(), 2) .."}");
+ if (cls == "cPickup") then
+ local Pickup = Entity;
+ tolua.cast(Pickup, "cPickup");
+ Player:SendMessage(" Age: " .. Pickup:GetAge() .. ", IsCollected: " .. tostring(Pickup:IsCollected()));
+ end
+ NumEntities = NumEntities + 1;
+ end
+
+ Player:SendMessage("Listing all entities...");
+ Player:GetWorld():ForEachEntity(ListEntity);
+ Player:SendMessage("List finished, " .. NumEntities .. " entities listed");
+ return true;
+end
+
+
+
+
+
+function HandleKillEntitiesCmd(Split, Player)
+ local NumEntities = 0;
+
+ local KillEntity = function(Entity)
+ -- kill everything except for players:
+ if (Entity:GetEntityType() ~= cEntity.etPlayer) then
+ Entity:Destroy();
+ NumEntities = NumEntities + 1;
+ end;
+ end
+
+ Player:SendMessage("Killing all entities...");
+ Player:GetWorld():ForEachEntity(KillEntity);
+ Player:SendMessage("Killed " .. NumEntities .. " entities.");
+ return true;
+end
+
+
+
+
+
+function HandleWoolCmd(Split, Player)
+ local Wool = cItem(E_BLOCK_WOOL, 1, E_META_WOOL_BLUE);
+ Player:GetInventory():SetArmorSlot(0, Wool);
+ Player:GetInventory():SetArmorSlot(1, Wool);
+ Player:GetInventory():SetArmorSlot(2, Wool);
+ Player:GetInventory():SetArmorSlot(3, Wool);
+ Player:SendMessage("You have been bluewooled :)");
+ return true;
+end
+
+
+
+
+
+function HandleTestWndCmd(a_Split, a_Player)
+ local WindowType = cWindow.wtHopper;
+ local WindowSizeX = 5;
+ local WindowSizeY = 1;
+ if (#a_Split == 4) then
+ WindowType = tonumber(a_Split[2]);
+ WindowSizeX = tonumber(a_Split[3]);
+ WindowSizeY = tonumber(a_Split[4]);
+ elseif (#a_Split ~= 1) then
+ a_Player:SendMessage("Usage: /testwnd [WindowType WindowSizeX WindowSizeY]");
+ return true;
+ end
+
+ -- Test out the OnClosing callback's ability to refuse to close the window
+ local attempt = 1;
+ local OnClosing = function(Window, Player, CanRefuse)
+ Player:SendMessage("Window closing attempt #" .. attempt .. "; CanRefuse = " .. tostring(CanRefuse));
+ attempt = attempt + 1;
+ return CanRefuse and (attempt <= 3); -- refuse twice, then allow, unless CanRefuse is set to true
+ end
+
+ -- Log the slot changes
+ local OnSlotChanged = function(Window, SlotNum)
+ LOG("Window \"" .. Window:GetWindowTitle() .. "\" slot " .. SlotNum .. " changed.");
+ end
+
+ local Window = cLuaWindow(WindowType, WindowSizeX, WindowSizeY, "TestWnd");
+ local Item2 = cItem(E_ITEM_DIAMOND_SWORD, 1, 0, "1=1");
+ local Item3 = cItem(E_ITEM_DIAMOND_SHOVEL);
+ Item3.m_Enchantments:SetLevel(cEnchantments.enchUnbreaking, 4);
+ local Item4 = cItem(Item3); -- Copy
+ Item4.m_Enchantments:SetLevel(cEnchantments.enchEfficiency, 3); -- Add enchantment
+ Item4.m_Enchantments:SetLevel(cEnchantments.enchUnbreaking, 5); -- Overwrite existing level
+ local Item5 = cItem(E_ITEM_DIAMOND_CHESTPLATE, 1, 0, "thorns=1;unbreaking=3");
+ Window:SetSlot(a_Player, 0, cItem(E_ITEM_DIAMOND, 64));
+ Window:SetSlot(a_Player, 1, Item2);
+ Window:SetSlot(a_Player, 2, Item3);
+ Window:SetSlot(a_Player, 3, Item4);
+ Window:SetSlot(a_Player, 4, Item5);
+ Window:SetOnClosing(OnClosing);
+ Window:SetOnSlotChanged(OnSlotChanged);
+
+ a_Player:OpenWindow(Window);
+
+ -- To make sure that the object has the correct life-management in Lua,
+ -- let's garbage-collect in the following few ticks
+ GCOnTick = 10;
+
+ return true;
+end
+
+
+
+
+
+function HandleGCCmd(a_Split, a_Player)
+ collectgarbage();
+ return true;
+end
+
+
+
+
+
+
+function HandleFastCmd(a_Split, a_Player)
+ if (a_Player:GetNormalMaxSpeed() <= 0.11) then
+ -- The player has normal speed, set double speed:
+ a_Player:SetNormalMaxSpeed(0.2);
+ a_Player:SendMessage("You are now fast");
+ else
+ -- The player has fast speed, set normal speed:
+ a_Player:SetNormalMaxSpeed(0.1);
+ a_Player:SendMessage("Back to normal speed");
+ end
+ return true;
+end
+
+
+
+
+
+function HandleDashCmd(a_Split, a_Player)
+ if (a_Player:GetSprintingMaxSpeed() <= 0.14) then
+ -- The player has normal sprinting speed, set double Sprintingspeed:
+ a_Player:SetSprintingMaxSpeed(0.4);
+ a_Player:SendMessage("You can now sprint very fast");
+ else
+ -- The player has fast sprinting speed, set normal sprinting speed:
+ a_Player:SetSprintingMaxSpeed(0.13);
+ a_Player:SendMessage("Back to normal sprinting");
+ end
+ return true;
+end;
+
+
+
+
+
+function HandleHungerCmd(a_Split, a_Player)
+ a_Player:SendMessage("FoodLevel: " .. a_Player:GetFoodLevel());
+ a_Player:SendMessage("FoodSaturationLevel: " .. a_Player:GetFoodSaturationLevel());
+ a_Player:SendMessage("FoodTickTimer: " .. a_Player:GetFoodTickTimer());
+ a_Player:SendMessage("FoodExhaustionLevel: " .. a_Player:GetFoodExhaustionLevel());
+ a_Player:SendMessage("FoodPoisonedTicksRemaining: " .. a_Player:GetFoodPoisonedTicksRemaining());
+ return true;
+end
+
+
+
+
+
+function HandlePoisonCmd(a_Split, a_Player)
+ a_Player:FoodPoison(15 * 20);
+ return true;
+end
+
+
+
+
+
+function HandleStarveCmd(a_Split, a_Player)
+ a_Player:SetFoodLevel(0);
+ a_Player:SendMessage("You are now starving");
+ return true;
+end
+
+
+
+
+
+function HandleFoodLevelCmd(a_Split, a_Player)
+ if (#a_Split ~= 2) then
+ a_Player:SendMessage("Missing an argument: the food level to set");
+ return true;
+ end
+
+ a_Player:SetFoodLevel(tonumber(a_Split[2]));
+ a_Player:SetFoodSaturationLevel(5);
+ a_Player:SetFoodExhaustionLevel(0);
+ a_Player:SendMessage(
+ "Food level set to " .. a_Player:GetFoodLevel() ..
+ ", saturation reset to " .. a_Player:GetFoodSaturationLevel() ..
+ " and exhaustion reset to " .. a_Player:GetFoodExhaustionLevel()
+ );
+ return true;
+end
+
+
+
+
+
+function HandleSpideyCmd(a_Split, a_Player)
+ -- Place a line of cobwebs from the player's eyes until non-air block, in the line-of-sight of the player
+ local World = a_Player:GetWorld();
+
+ local Callbacks = {
+ OnNextBlock = function(a_BlockX, a_BlockY, a_BlockZ, a_BlockType, a_BlockMeta)
+ if (a_BlockType ~= E_BLOCK_AIR) then
+ -- abort the trace
+ return true;
+ end
+ World:SetBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_COBWEB, 0);
+ end
+ };
+
+ local EyePos = a_Player:GetEyePosition();
+ local LookVector = a_Player:GetLookVector();
+ LookVector:Normalize();
+
+ -- Start cca 2 blocks away from the eyes
+ local Start = EyePos + LookVector + LookVector;
+ local End = EyePos + LookVector * 50;
+
+ cLineBlockTracer.Trace(World, Callbacks, Start.x, Start.y, Start.z, End.x, End.y, End.z);
+
+ return true;
+end
+
+
+
+
+
+function HandleEnchCmd(a_Split, a_Player)
+ local Wnd = cLuaWindow(cWindow.wtEnchantment, 1, 1, "Ench");
+ a_Player:OpenWindow(Wnd);
+ Wnd:SetProperty(0, 10);
+ Wnd:SetProperty(1, 15);
+ Wnd:SetProperty(2, 25);
+ return true;
+end
+
+
+
+
+
+function HandleFoodStatsCmd(a_Split, a_Player)
+ g_ShowFoodStats = not(g_ShowFoodStats);
+ return true;
+end
+
+
+
+
+
+function HandleArrowCmd(a_Split, a_Player)
+ local World = a_Player:GetWorld();
+ local Pos = a_Player:GetEyePosition();
+ local Speed = a_Player:GetLookVector();
+ Speed:Normalize();
+ Pos = Pos + Speed;
+
+ World:CreateProjectile(Pos.x, Pos.y, Pos.z, cProjectileEntity.pkArrow, a_Player, Speed * 10);
+ return true;
+end
+
+
+
+
+
+function HandleFireballCmd(a_Split, a_Player)
+ local World = a_Player:GetWorld();
+ local Pos = a_Player:GetEyePosition();
+ local Speed = a_Player:GetLookVector();
+ Speed:Normalize();
+ Pos = Pos + Speed * 2;
+
+ World:CreateProjectile(Pos.x, Pos.y, Pos.z, cProjectileEntity.pkGhastFireball, a_Player, Speed * 10);
+ return true;
+end
+
+
+
+
+function HandleAddExperience(a_Split, a_Player)
+ a_Player:DeltaExperience(200);
+
+ return true;
+end
+
+
+
+
+
+function HandleRemoveXp(a_Split, a_Player)
+ a_Player:SetCurrentExperience(0);
+
+ return true;
+end
+
+
+
+
+
+function HandleFill(a_Split, a_Player)
+ local World = a_Player:GetWorld();
+ local ChunkX = a_Player:GetChunkX();
+ local ChunkZ = a_Player:GetChunkZ();
+ World:ForEachBlockEntityInChunk(ChunkX, ChunkZ,
+ function(a_BlockEntity)
+ local BlockType = a_BlockEntity:GetBlockType();
+ if (
+ (BlockType == E_BLOCK_CHEST) or
+ (BlockType == E_BLOCK_DISPENSER) or
+ (BlockType == E_BLOCK_DROPPER) or
+ (BlockType == E_BLOCK_FURNACE) or
+ (BlockType == E_BLOCK_HOPPER)
+ ) then
+ -- This block entity has items (inherits from cBlockEntityWithItems), fill it:
+ -- Note that we're not touching lit furnaces, don't wanna mess them up
+ local EntityWithItems = tolua.cast(a_BlockEntity, "cBlockEntityWithItems");
+ local ItemGrid = EntityWithItems:GetContents();
+ local NumSlots = ItemGrid:GetNumSlots();
+ local ItemToSet = cItem(E_ITEM_GOLD_NUGGET);
+ for i = 0, NumSlots - 1 do
+ if (ItemGrid:GetSlot(i):IsEmpty()) then
+ ItemGrid:SetSlot(i, ItemToSet);
+ end
+ end
+ end
+ end
+ );
+ return true;
+end
+
+
+
+
+
+function HandleFurnaceRecipe(a_Split, a_Player)
+ local HeldItem = a_Player:GetEquippedItem();
+ local Out, NumTicks, In = cRoot:GetFurnaceRecipe(HeldItem);
+ if (Out ~= nil) then
+ a_Player:SendMessage(
+ "Furnace turns " .. ItemToFullString(In) ..
+ " to " .. ItemToFullString(Out) ..
+ " in " .. NumTicks .. " ticks (" ..
+ tostring(NumTicks / 20) .. " seconds)."
+ );
+ else
+ a_Player:SendMessage("There is no furnace recipe that would smelt " .. ItemToString(HeldItem));
+ end
+ return true;
+end
+
+
+
+
+
+function HandleFurnaceFuel(a_Split, a_Player)
+ local HeldItem = a_Player:GetEquippedItem();
+ local NumTicks = cRoot:GetFurnaceFuelBurnTime(HeldItem);
+ if (NumTicks > 0) then
+ a_Player:SendMessage(
+ ItemToFullString(HeldItem) .. " would power a furnace for " .. NumTicks ..
+ " ticks (" .. tostring(NumTicks / 20) .. " seconds)."
+ );
+ else
+ a_Player:SendMessage(ItemToString(HeldItem) .. " will not power furnaces.");
+ end
+ return true;
+end
+
+
+
+
+
+function HandleSched(a_Split, a_Player)
+ local World = a_Player:GetWorld()
+
+ -- Schedule a broadcast of a countdown message:
+ for i = 1, 10 do
+ World:ScheduleTask(i * 20,
+ function(a_World)
+ a_World:BroadcastChat("Countdown: " .. 11 - i)
+ end
+ )
+ end
+
+ -- Schedule a broadcast of the final message and a note to the originating player
+ -- Note that we CANNOT use the a_Player in the callback - what if the player disconnected?
+ -- Therefore we store the player's EntityID
+ local PlayerID = a_Player:GetUniqueID()
+ World:ScheduleTask(220,
+ function(a_World)
+ a_World:BroadcastChat("Countdown: BOOM")
+ a_World:DoWithEntityByID(PlayerID,
+ function(a_Entity)
+ if (a_Entity:IsPlayer()) then
+ -- Although unlikely, it is possible that this player is not the originating player
+ -- However, I leave this as an excercise to you to fix this "bug"
+ local Player = tolua.cast(a_Entity, "cPlayer")
+ Player:SendMessage("Countdown finished")
+ end
+ end
+ )
+ end
+ )
+
+ return true
+end
+
+
+
+
+
+function HandleRMItem(a_Split, a_Player)
+ -- Check params:
+ if (a_Split[2] == nil) then
+ a_Player:SendMessage("Usage: /rmitem <Item> [Count]")
+ return true
+ end
+
+ -- Parse the item type:
+ local Item = cItem()
+ if (not StringToItem(a_Split[2], Item)) then
+ a_Player:SendMessageFailure(a_Split[2] .. " isn't a valid item")
+ return true
+ end
+
+ -- Parse the optional item count
+ if (a_Split[3] ~= nil) then
+ local Count = tonumber(a_Split[3])
+ if (Count == nil) then
+ a_Player:SendMessageFailure(a_Split[3] .. " isn't a valid number")
+ return true
+ end
+
+ Item.m_ItemCount = Count
+ end
+
+ -- Remove the item:
+ local NumRemovedItems = a_Player:GetInventory():RemoveItem(Item)
+ a_Player:SendMessageSuccess("Removed " .. NumRemovedItems .. " Items!")
+ return true
+end
+
+
+
+
+
+function HandleRequest_Debuggers(a_Request)
+ local FolderContents = cFile:GetFolderContents("./");
+ return "<p>The following objects have been returned by cFile:GetFolderContents():<ul><li>" .. table.concat(FolderContents, "</li><li>") .. "</li></ul></p>";
+end
+
+
+
+
+
+local g_Counter = 0
+local g_JavaScript =
+[[
+<script>
+function createXHR()
+{
+ var request = false;
+ try {
+ request = new ActiveXObject('Msxml2.XMLHTTP');
+ }
+ catch (err2)
+ {
+ try
+ {
+ request = new ActiveXObject('Microsoft.XMLHTTP');
+ }
+ catch (err3)
+ {
+ try
+ {
+ request = new XMLHttpRequest();
+ }
+ catch (err1)
+ {
+ request = false;
+ }
+ }
+ }
+ return request;
+}
+
+function RefreshCounter()
+{
+ var xhr = createXHR();
+ xhr.onreadystatechange = function()
+ {
+ if (xhr.readyState == 4)
+ {
+ document.getElementById("cnt").innerHTML = xhr.responseText;
+ }
+ };
+ xhr.open("POST", "/~webadmin/Debuggers/StressTest", true);
+ xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+ xhr.send("counter=true");
+}
+
+setInterval(RefreshCounter, 10)
+</script>
+]]
+
+function HandleRequest_StressTest(a_Request)
+ if (a_Request.PostParams["counter"]) then
+ g_Counter = g_Counter + 1
+ return tostring(g_Counter)
+ end
+ return g_JavaScript .. "<p>The counter below should be reloading as fast as possible</p><div id='cnt'>0</div>"
+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
+
+
+
+
+
+function HandleChunkStay(a_Split, a_Player)
+ -- As an example of using ChunkStay, this call will load 3x3 chunks around the specified chunk coords,
+ -- then build an obsidian pillar in the middle of each one.
+ -- Once complete, the player will be teleported to the middle pillar
+
+ if (#a_Split ~= 3) then
+ a_Player:SendMessageInfo("Usage: /cs <ChunkX> <ChunkZ>")
+ return true
+ end
+
+ local ChunkX = tonumber(a_Split[2])
+ local ChunkZ = tonumber(a_Split[3])
+ if ((ChunkX == nil) or (ChunkZ == nil)) then
+ a_Player:SendMessageFailure("Invalid chunk coords.")
+ return true
+ end
+
+ local World = a_Player:GetWorld()
+ local PlayerID = a_Player:GetUniqueID()
+ a_Player:SendMessageInfo("Loading chunks, stand by...");
+
+ -- Set the wanted chunks:
+ local Chunks = {}
+ for z = -1, 1 do for x = -1, 1 do
+ table.insert(Chunks, {ChunkX + x, ChunkZ + z})
+ end end
+
+ -- The function that is called when all chunks are available
+ -- Will perform the actual action with all those chunks
+ -- Note that the player needs to be referenced using their EntityID - in case they disconnect before the chunks load
+ local OnAllChunksAvailable = function()
+ LOGINFO("ChunkStay all chunks now available")
+ -- Build something on the neighboring chunks, to verify:
+ for z = -1, 1 do for x = -1, 1 do
+ local BlockX = (ChunkX + x) * 16 + 8
+ local BlockZ = (ChunkZ + z) * 16 + 8
+ for y = 20, 80 do
+ World:SetBlock(BlockX, y, BlockZ, E_BLOCK_OBSIDIAN, 0)
+ end
+ end end
+
+ -- Teleport the player there for visual inspection:
+ World:DoWithEntityByID(PlayerID,
+ function (a_CallbackPlayer)
+ a_CallbackPlayer:TeleportToCoords(ChunkX * 16 + 8, 85, ChunkZ * 16 + 8)
+ a_CallbackPlayer:SendMessageSuccess("ChunkStay fully available")
+ end
+ )
+ end
+
+ -- This function will be called for each chunk that is made available
+ -- Note that the player needs to be referenced using their EntityID - in case they disconnect before the chunks load
+ local OnChunkAvailable = function(a_ChunkX, a_ChunkZ)
+ LOGINFO("ChunkStay now has chunk [" .. a_ChunkX .. ", " .. a_ChunkZ .. "]")
+ World:DoWithEntityByID(PlayerID,
+ function (a_CallbackPlayer)
+ a_CallbackPlayer:SendMessageInfo("ChunkStay now has chunk [" .. a_ChunkX .. ", " .. a_ChunkZ .. "]")
+ end
+ )
+ end
+
+ -- Process the ChunkStay:
+ World:ChunkStay(Chunks, OnChunkAvailable, OnAllChunksAvailable)
+ return true
+end
+
+
+
+
+
+function HandleCompo(a_Split, a_Player)
+ -- Send one composite message to self:
+ local msg = cCompositeChat()
+ msg:AddTextPart("Hello! ", "b@e") -- bold yellow
+ msg:AddUrlPart("MCServer", "http://mc-server.org")
+ msg:AddTextPart(" rules! ")
+ msg:AddRunCommandPart("Set morning", "/time set 0")
+ a_Player:SendMessage(msg)
+
+ -- Broadcast another one to the world:
+ local msg2 = cCompositeChat()
+ msg2:AddSuggestCommandPart(a_Player:GetName(), "/tell " .. a_Player:GetName() .. " ")
+ msg2:AddTextPart(" knows how to use cCompositeChat!");
+ a_Player:GetWorld():BroadcastChat(msg2)
+
+ return true
+end
+
+
+
+
+
+function HandleSetBiome(a_Split, a_Player)
+ local Biome = biJungle
+ local Size = 20
+ local SplitSize = #a_Split
+ if (SplitSize > 3) then
+ a_Player:SendMessage("Too many parameters. Usage: " .. a_Split[1] .. " <BiomeType>")
+ return true
+ end
+
+ if (SplitSize >= 2) then
+ Biome = StringToBiome(a_Split[2])
+ if (Biome == biInvalidBiome) then
+ a_Player:SendMessage("Unknown biome: '" .. a_Split[2] .. "'. Command ignored.")
+ return true
+ end
+ end
+ if (SplitSize >= 3) then
+ Size = tostring(a_Split[3])
+ if (Size == nil) then
+ a_Player:SendMessage("Unknown size: '" .. a_Split[3] .. "'. Command ignored.")
+ return true
+ end
+ end
+
+ local BlockX = math.floor(a_Player:GetPosX())
+ local BlockZ = math.floor(a_Player:GetPosZ())
+ a_Player:GetWorld():SetAreaBiome(BlockX - Size, BlockX + Size, BlockZ - Size, BlockZ + Size, Biome)
+ a_Player:SendMessage(
+ "Blocks {" .. (BlockX - Size) .. ", " .. (BlockZ - Size) ..
+ "} - {" .. (BlockX + Size) .. ", " .. (BlockZ + Size) ..
+ "} set to biome #" .. tostring(Biome) .. "."
+ )
+ return true
+end
+
+
+
+
+
+function HandleWESel(a_Split, a_Player)
+ -- Check if the selection is a cuboid:
+ local IsCuboid = cPluginManager:CallPlugin("WorldEdit", "IsPlayerSelectionCuboid")
+ if (IsCuboid == nil) then
+ a_Player:SendMessage(cCompositeChat():SetMessageType(mtFailure):AddTextPart("Cannot adjust selection, WorldEdit is not loaded"))
+ return true
+ elseif (IsCuboid == false) then
+ a_Player:SendMessage(cCompositeChat():SetMessageType(mtFailure):AddTextPart("Cannot adjust selection, the selection is not a cuboid"))
+ return true
+ end
+
+ -- Get the selection:
+ local SelCuboid = cCuboid()
+ local IsSuccess = cPluginManager:CallPlugin("WorldEdit", "GetPlayerCuboidSelection", a_Player, SelCuboid)
+ if not(IsSuccess) then
+ a_Player:SendMessage(cCompositeChat():SetMessageType(mtFailure):AddTextPart("Cannot adjust selection, WorldEdit reported failure while getting current selection"))
+ return true
+ end
+
+ -- Adjust the selection:
+ local NumBlocks = tonumber(a_Split[2] or "1") or 1
+ SelCuboid:Expand(NumBlocks, NumBlocks, 0, 0, NumBlocks, NumBlocks)
+
+ -- Set the selection:
+ IsSuccess = cPluginManager:CallPlugin("WorldEdit", "SetPlayerCuboidSelection", a_Player, SelCuboid)
+ if not(IsSuccess) then
+ a_Player:SendMessage(cCompositeChat():SetMessageType(mtFailure):AddTextPart("Cannot adjust selection, WorldEdit reported failure while setting new selection"))
+ return true
+ end
+ a_Player:SendMessage(cCompositeChat():SetMessageType(mtInformation):AddTextPart("Successfully adjusted the selection by " .. NumBlocks .. " block(s)"))
+ return true
+end
+
+
+
+
+
+function OnPlayerJoined(a_Player)
+ -- Test composite chat chaining:
+ a_Player:SendMessage(cCompositeChat()
+ :AddTextPart("Hello, ")
+ :AddUrlPart(a_Player:GetName(), "http://www.mc-server.org", "u@2")
+ :AddSuggestCommandPart(", and welcome.", "/help", "u")
+ :AddRunCommandPart(" SetDay", "/time set 0")
+ )
+end
+
+
+
+
+
+function OnProjectileHitBlock(a_Projectile, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_BlockHitPos)
+ -- Test projectile hooks by setting the blocks they hit on fire:
+ local BlockX, BlockY, BlockZ = AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace)
+ local World = a_Projectile:GetWorld()
+
+ World:SetBlock(BlockX, BlockY, BlockZ, E_BLOCK_FIRE, 0)
+end
+
+
+
+
+
+function OnChunkUnloading(a_World, a_ChunkX, a_ChunkZ)
+ -- Do not let chunk [0, 0] unload, so that it continues ticking [cWorld:SetChunkAlwaysTicked() test]
+ if ((a_ChunkX == 0) and (a_ChunkZ == 0)) then
+ return true
+ end
+end
+
+
+
+
+
+function OnWorldStarted(a_World)
+ -- Make the chunk [0, 0] in every world keep ticking [cWorld:SetChunkAlwaysTicked() test]
+ a_World:ChunkStay({{0, 0}}, nil,
+ function()
+ -- The chunk is loaded, make it always tick:
+ a_World:SetChunkAlwaysTicked(0, 0, true)
+ end
+ )
+end
+
+
+
+
+
+function OnProjectileHitBlock(a_ProjectileEntity, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_BlockHitPos)
+ -- This simple test is for testing issue #1326 - simply declaring this hook would crash the server upon call
+ LOG("Projectile hit block")
+ LOG(" Projectile EntityID: " .. a_ProjectileEntity:GetUniqueID())
+ LOG(" Block: {" .. a_BlockX .. ", " .. a_BlockY .. ", " .. a_BlockZ .. "}, face " .. a_BlockFace)
+ LOG(" HitPos: {" .. a_BlockHitPos.x .. ", " .. a_BlockHitPos.y .. ", " .. a_BlockHitPos.z .. "}")
+end
+
+
+
+
+
+local PossibleItems =
+{
+ cItem(E_ITEM_DIAMOND),
+ cItem(E_ITEM_GOLD),
+ cItem(E_ITEM_IRON),
+ cItem(E_ITEM_DYE, 1, E_META_DYE_BLUE), -- Lapis lazuli
+ cItem(E_ITEM_COAL),
+}
+
+
+
+
+
+function HandlePickups(a_Split, a_Player)
+ local PlayerX = a_Player:GetPosX()
+ local PlayerY = a_Player:GetPosY()
+ local PlayerZ = a_Player:GetPosZ()
+ local World = a_Player:GetWorld()
+ local Range = 12
+ for x = 0, Range do for z = 0, Range do
+ local px = PlayerX + x - Range / 2
+ local pz = PlayerZ + z - Range / 2
+ local Items = cItems()
+ Items:Add(PossibleItems[math.random(#PossibleItems)])
+ World:SpawnItemPickups(Items, px, PlayerY, pz, 0)
+ end end -- for z, for x
+ return true
+end
+
+
+
+
+function HandlePoof(a_Split, a_Player)
+ local PlayerPos = Vector3d(a_Player:GetPosition()) -- Create a copy of the position
+ PlayerPos.y = PlayerPos.y - 1
+ local Box = cBoundingBox(PlayerPos, 4, 2)
+ local NumEntities = 0
+ a_Player:GetWorld():ForEachEntityInBox(Box,
+ function (a_Entity)
+ if not(a_Entity:IsPlayer()) then
+ local AddSpeed = a_Entity:GetPosition() - PlayerPos -- Speed away from the player
+ a_Entity:AddSpeed(AddSpeed * 32 / (AddSpeed:SqrLength() + 1)) -- The further away, the less speed to add
+ NumEntities = NumEntities + 1
+ end
+ end
+ )
+ a_Player:SendMessage("Poof! (" .. NumEntities .. " entities)")
+ return true
+end
+
+
+
+
+
+-- List of hashing functions to test:
+local HashFunctions =
+{
+ {"md5", md5 },
+ {"cCryptoHash.md5", cCryptoHash.md5 },
+ {"cCryptoHash.md5HexString", cCryptoHash.md5HexString },
+ {"cCryptoHash.sha1", cCryptoHash.sha1 },
+ {"cCryptoHash.sha1HexString", cCryptoHash.sha1HexString },
+}
+
+-- List of strings to try hashing:
+local HashExamples =
+{
+ "",
+ "\0",
+ "test",
+}
+
+function HandleConsoleHash(a_Split)
+ for _, str in ipairs(HashExamples) do
+ LOG("Hashing string \"" .. str .. "\":")
+ for _, hash in ipairs(HashFunctions) do
+ if not(hash[2]) then
+ LOG("Hash function " .. hash[1] .. " doesn't exist in the API!")
+ else
+ LOG(hash[1] .. "() = " .. hash[2](str))
+ end
+ end -- for hash - HashFunctions[]
+ end -- for str - HashExamples[]
+ return true
+end
+
+
+
+
+
+--- Monitors the state of the "inh" entity-spawning hook
+-- if false, the hook is installed before the "inh" command processing
+local isInhHookInstalled = false
+
+function HandleConsoleInh(a_Split, a_FullCmd)
+ -- Check the param:
+ local kindStr = a_Split[2] or "pkArrow"
+ local kind = cProjectileEntity[kindStr]
+ if (kind == nil) then
+ return true, "There's no projectile kind '" .. kindStr .. "'."
+ end
+
+ -- Get the world to test in:
+ local world = cRoot:Get():GetDefaultWorld()
+ if (world == nil) then
+ return true, "Cannot test inheritance, no default world"
+ end
+
+ -- Install the hook, if needed:
+ if not(isInhHookInstalled) then
+ cPluginManager:AddHook(cPluginManager.HOOK_SPAWNING_ENTITY,
+ function (a_CBWorld, a_CBEntity)
+ LOG("New entity is spawning:")
+ LOG(" Lua type: '" .. type(a_CBEntity) .. "'")
+ LOG(" ToLua type: '" .. tolua.type(a_CBEntity) .. "'")
+ LOG(" GetEntityType(): '" .. a_CBEntity:GetEntityType() .. "'")
+ LOG(" GetClass(): '" .. a_CBEntity:GetClass() .. "'")
+ end
+ )
+ isInhHookInstalled = true
+ end
+
+ -- Create the projectile:
+ LOG("Creating a " .. kindStr .. " projectile in world " .. world:GetName() .. "...")
+ local msg
+ world:ChunkStay({{0, 0}},
+ nil,
+ function ()
+ -- Create a projectile at {8, 100, 8}:
+ local entityID = world:CreateProjectile(8, 100, 8, kind, nil, nil)
+ if (entityID < 0) then
+ msg = "Cannot test inheritance, projectile creation failed."
+ return
+ end
+ LOG("Entity created, ID #" .. entityID)
+
+ -- Call a function on the newly created entity:
+ local hasExecutedCallback = false
+ world:DoWithEntityByID(
+ entityID,
+ function (a_CBEntity)
+ LOG("Projectile created and found using the DoWithEntityByID() callback")
+ LOG("Lua type: '" .. type(a_CBEntity) .. "'")
+ LOG("ToLua type: '" .. tolua.type(a_CBEntity) .. "'")
+ LOG("GetEntityType(): '" .. a_CBEntity:GetEntityType() .. "'")
+ LOG("GetClass(): '" .. a_CBEntity:GetClass() .. "'")
+ hasExecutedCallback = true
+ end
+ )
+ if not(hasExecutedCallback) then
+ msg = "The callback failed to execute"
+ return
+ end
+
+ msg = "Inheritance test finished"
+ end
+ )
+
+ return true, msg
+end
+
+
+
+
+
+function HandleConsoleLoadChunk(a_Split)
+ -- Check params:
+ local numParams = #a_Split
+ if (numParams ~= 3) and (numParams ~= 4) then
+ return true, "Usage: " .. a_Split[1] .. " <ChunkX> <ChunkZ> [<WorldName>]"
+ end
+
+ -- Get the chunk coords:
+ local chunkX = tonumber(a_Split[2])
+ if (chunkX == nil) then
+ return true, "Not a number: '" .. a_Split[2] .. "'"
+ end
+ local chunkZ = tonumber(a_Split[3])
+ if (chunkZ == nil) then
+ return true, "Not a number: '" .. a_Split[3] .. "'"
+ end
+
+ -- Get the world:
+ local world
+ if (a_Split[4] == nil) then
+ world = cRoot:Get():GetDefaultWorld()
+ else
+ world = cRoot:Get():GetWorld(a_Split[4])
+ if (world == nil) then
+ return true, "There's no world named '" .. a_Split[4] .. "'."
+ end
+ end
+
+ -- Queue a ChunkStay for the chunk, log a message when the chunk is loaded:
+ world:ChunkStay({{chunkX, chunkZ}}, nil,
+ function()
+ LOG("Chunk [" .. chunkX .. ", " .. chunkZ .. "] is loaded")
+ end
+ )
+ return true
+end
+
+
+
+
+
+function HandleConsolePrepareChunk(a_Split)
+ -- Check params:
+ local numParams = #a_Split
+ if (numParams ~= 3) and (numParams ~= 4) then
+ return true, "Usage: " .. a_Split[1] .. " <ChunkX> <ChunkZ> [<WorldName>]"
+ end
+
+ -- Get the chunk coords:
+ local chunkX = tonumber(a_Split[2])
+ if (chunkX == nil) then
+ return true, "Not a number: '" .. a_Split[2] .. "'"
+ end
+ local chunkZ = tonumber(a_Split[3])
+ if (chunkZ == nil) then
+ return true, "Not a number: '" .. a_Split[3] .. "'"
+ end
+
+ -- Get the world:
+ local world
+ if (a_Split[4] == nil) then
+ world = cRoot:Get():GetDefaultWorld()
+ else
+ world = cRoot:Get():GetWorld(a_Split[4])
+ if (world == nil) then
+ return true, "There's no world named '" .. a_Split[4] .. "'."
+ end
+ end
+
+ -- Queue the chunk for preparing, log a message when prepared:
+ world:PrepareChunk(chunkX, chunkZ,
+ function(a_CBChunkX, a_CBChunkZ)
+ LOG("Chunk [" .. chunkX .. ", " .. chunkZ .. "] has been prepared")
+ end
+ )
+ return true
+end
+
+
+
+
+
+function HandleConsoleSchedule(a_Split)
+ local prev = os.clock()
+ LOG("Scheduling a task for 2 seconds in the future (current os.clock is " .. prev .. ")")
+ cRoot:Get():GetDefaultWorld():ScheduleTask(40,
+ function ()
+ local current = os.clock()
+ local diff = current - prev
+ LOG("Scheduled function is called. Current os.clock is " .. current .. ", difference is " .. diff .. ")")
+ end
+ )
+ return true, "Task scheduled"
+end
+
+
+
+
+
+--- Returns the square of the distance from the specified point to the specified line
+local function SqDistPtFromLine(x, y, x1, y1, x2, y2)
+ local dx = x - x1
+ local dy = y - y1
+ local px = x2 - x1
+ local py = y2 - y1
+ local ss = px * dx + py * dy
+ local ds = px * px + py * py
+
+ if (ss < 0) then
+ -- Return sqdistance from point 1
+ return dx * dx + dy * dy
+ end
+ if (ss > ds) then
+ -- Return sqdistance from point 2
+ return ((x2 - x) * (x2 - x) + (y2 - y) * (y2 - y))
+ end
+
+ -- Return sqdistance from the line
+ if ((px * px + py * py) == 0) then
+ return dx * dx + dy * dy
+ else
+ return (py * dx - px * dy) * (py * dx - px * dy) / (px * px + py * py)
+ end
+end
+
+
+
+
+
+function HandleConsoleTestTracer(a_Split, a_EntireCmd)
+ -- Check required params:
+ if not(a_Split[7]) then
+ return true, "Usage: " .. a_Split[1] .. " <x1> <y1> <z1> <x2> <y2> <z2> [<WorldName>]"
+ end
+ local Coords = {}
+ for i = 1, 6 do
+ local v = tonumber(a_Split[i + 1])
+ if not(v) then
+ return true, "Parameter " .. (i + 1) .. " (" .. tostring(a_Split[i + 1]) .. ") not a number "
+ end
+ Coords[i] = v
+ end
+
+ -- Get the world in which to test:
+ local World
+ if (a_Split[8]) then
+ World = cRoot:GetWorld(a_Split[2])
+ else
+ World = cRoot:Get():GetDefaultWorld()
+ end
+ if not(World) then
+ return true, "No such world"
+ end
+
+ -- Define the callbacks to use for tracing:
+ local Callbacks =
+ {
+ OnNextBlock = function(a_BlockX, a_BlockY, a_BlockZ, a_BlockType, a_BlockMeta, a_EntryFace)
+ LOG(string.format("{%d, %d, %d}: %s", a_BlockX, a_BlockY, a_BlockZ, ItemToString(cItem(a_BlockType, 1, a_BlockMeta))))
+ end,
+ OnNextBlockNoData = function(a_BlockX, a_BlockY, a_BlockZ, a_EntryFace)
+ LOG(string.format("{%d, %d, %d} (no data)", a_BlockX, a_BlockY, a_BlockZ))
+ end,
+ OnNoChunk = function()
+ LOG("Chunk not loaded")
+ end,
+ OnNoMoreHits = function()
+ LOG("Trace finished")
+ end,
+ OnOutOfWorld = function()
+ LOG("Out of world")
+ end,
+ OnIntoWorld = function()
+ LOG("Into world")
+ end,
+ }
+
+ -- Approximate the chunks needed for the trace by iterating over all chunks and measuring their center's distance from the traced line
+ local Chunks = {}
+ local sx = math.floor(Coords[1] / 16)
+ local sz = math.floor(Coords[3] / 16)
+ local ex = math.floor(Coords[4] / 16)
+ local ez = math.floor(Coords[6] / 16)
+ local sgnx = (sx < ex) and 1 or -1
+ local sgnz = (sz < ez) and 1 or -1
+ for z = sz, ez, sgnz do
+ local ChunkCenterZ = z * 16 + 8
+ for x = sx, ex, sgnx do
+ local ChunkCenterX = x * 16 + 8
+ local sqdist = SqDistPtFromLine(ChunkCenterX, ChunkCenterZ, Coords[1], Coords[3], Coords[4], Coords[6])
+ if (sqdist <= 128) then
+ table.insert(Chunks, {x, z})
+ end
+ end
+ end
+
+ -- Load the chunks and do the trace once loaded:
+ World:ChunkStay(Chunks,
+ nil,
+ function()
+ cLineBlockTracer:Trace(World, Callbacks, Coords[1], Coords[2], Coords[3], Coords[4], Coords[5], Coords[6])
+ end
+ )
+ return true
+end
+
+
+
+
+
+function HandleConsoleBBox(a_Split)
+ local bbox = cBoundingBox(0, 10, 0, 10, 0, 10)
+ local v1 = Vector3d(1, 1, 1)
+ local v2 = Vector3d(5, 5, 5)
+ local v3 = Vector3d(11, 11, 11)
+
+ if (bbox:IsInside(v1)) then
+ LOG("v1 is inside bbox")
+ else
+ LOG("v1 is not inside bbox")
+ end
+
+ if (bbox:IsInside(v2)) then
+ LOG("v2 is inside bbox")
+ else
+ LOG("v2 is not inside bbox")
+ end
+
+ if (bbox:IsInside(v3)) then
+ LOG("v3 is inside bbox")
+ else
+ LOG("v3 is not inside bbox")
+ end
+
+ if (bbox:IsInside(v1, v2)) then
+ LOG("v1*v2 is inside bbox")
+ else
+ LOG("v1*v2 is not inside bbox")
+ end
+
+ if (bbox:IsInside(v2, v1)) then
+ LOG("v2*v1 is inside bbox")
+ else
+ LOG("v2*v1 is not inside bbox")
+ end
+
+ if (bbox:IsInside(v1, v3)) then
+ LOG("v1*v3 is inside bbox")
+ else
+ LOG("v1*v3 is not inside bbox")
+ end
+
+ if (bbox:IsInside(v2, v3)) then
+ LOG("v2*v3 is inside bbox")
+ else
+ LOG("v2*v3 is not inside bbox")
+ end
+
+ return true
+end
+
+
+
+