diff --git a/CHANGELOG.md b/CHANGELOG.md index 162780b..721a74c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ It's about time I started a changelog! This will serve from now on as the master changelog for WorldEditAdditions. +## v1.9 (unreleased) + - Add `//many` for executing a command many times in a row + + ## v1.8: The Quality of Life Update (17th July 2020) - Update `//multi` to display human readable times (e.g. `2.11mins` instead of `126600ms`) - Far wand: Notify player when setting pos1 and pos2 diff --git a/README.md b/README.md index 882bb87..d472c92 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ If you can dream of it, it probably belongs here! ### Extras - [`//multi ....`](#multi-command_a-command_b-command_c-) + - [`//many `]() - [`//subdivide `](#subdivide-size_x-size_y-size_z-cmd_name-args) **experimental** - [`//y`](#y) - [`//n`](#n) @@ -287,6 +288,17 @@ Executes multi chat commands in sequence. Intended for _WorldEdit_ commands, but //multi /time 7:00 //1 //outset h 20 //outset v 5 //overlay dirt_with_grass //1 //2 //sphere 8 air //shift down 1 //floodfill //reset ``` +### `//many ` +Executes a single chat command many times in a row. Uses `minetest.after()` to yield to the main server thread to allow other things to happen at the same time, so technically you could have multiple `//many` calls going at once (but multithreading support is out of reach, so only a single one will be executing at the same time). + +Note that this isn't necessarily limited to executing WorldEdit / WorldEditAdditions commands. Combine with `//multi` (see above) execute multiple commands at once for even more power and flexibility! + +``` +//many 10 //bonemeal 3 100 +//many 100 //multi //1 //2 //outset 20 //set dirt +``` + + ### `//y` Confirms the execution of a command if it could potentially affect a large number of nodes and take a while. This is a regular WorldEdit command. diff --git a/worldeditadditions_commands/init.lua b/worldeditadditions_commands/init.lua index cbeb822..91c7366 100644 --- a/worldeditadditions_commands/init.lua +++ b/worldeditadditions_commands/init.lua @@ -14,6 +14,7 @@ dofile(we_c.modpath.."/player_notify_suppress.lua") dofile(we_c.modpath.."/multi.lua") +dofile(we_c.modpath.."/many.lua") -- We no longer need our own implementation of safe_region thanks to @sfan5's -- suggestion in issue #5 - yay! diff --git a/worldeditadditions_commands/many.lua b/worldeditadditions_commands/many.lua new file mode 100644 index 0000000..382fd1d --- /dev/null +++ b/worldeditadditions_commands/many.lua @@ -0,0 +1,113 @@ +--- Executes multiple worldedit commands in sequence. +-- @module worldeditadditions.multi + +-- explode(seperator, string) +-- From http://lua-users.org/wiki/SplitJoin +local function explode(delim, str) + local ll, is_done + local delim_length = string.len(delim) + ll = 0 + is_done = false + + return function() + if is_done then return end + + local result = nil + local loc = string.find(str, delim, ll, true) -- find the next d in the string + if loc ~= nil then -- if "not not" found then.. + result = string.sub(str, ll, loc - 1) -- Save it in our array. + ll = loc + delim_length -- save just after where we found it for searching next time. + else + result = string.sub(str, ll) -- Save what's left in our array. + is_done = true + end + + return result + end +end + +-- From http://lua-users.org/wiki/StringTrim +local function trim(s) + return (s:gsub("^%s*(.-)%s*$", "%1")) +end + +local function step(params) + local start_time = worldeditadditions.get_ms_time() + + local full_cmd = params.cmd_name.." "..params.args + worldedit.player_notify(params.name, "[ many ] [ /"..full_cmd.." ] "..(params.i + 1).." / "..params.count.." (~"..worldeditadditions.round(((params.i + 1) / params.count)*100, 2).."%)") + + local cmd = minetest.chatcommands[params.cmd_name] + + minetest.log("action", params.name.." runs "..full_cmd.." (time "..tostring(params.i).." of "..tostring(times)..")") + cmd.func(params.name, params.args) + + + params.times[#params.times + 1] = (worldeditadditions.get_ms_time() - start_time) + + params.i = params.i + 1 + if params.i < params.count then + minetest.after(0, step, params) + else + local total_time = (worldeditadditions.get_ms_time() - params.master_start_time) + local done_message = {} + table.insert(done_message, + string.format("Executed '"..full_cmd.."' %d times in %s (~%s / time", + #params.times, + worldeditadditions.human_time(total_time), + worldeditadditions.human_time( + worldeditadditions.average(params.times) + ) + ) + ) + -- Don't drown the player if the command was executed many times + if #params.times < 10 then + local message_parts = {} + for j=1,#params.times do + table.insert(message_parts, worldeditadditions.human_time(params.times[j])) + end + table.insert(done_message, "; ") + table.insert(done_message, table.concat(message_parts, ", ")) + end + table.insert(done_message, ")") + worldedit.player_notify(params.name, table.concat(done_message, "")) + end +end + +minetest.register_chatcommand("/many", { + params = " / ", + description = "Executes a single chat command multiple times. The number of times to repeat the command should be specified first. The command to execute follows, and the forward slashes at the beginning thereof must be the same as if you were executing it normally. Note that this command yields with minetest.after to allow other things to happen at the same time.", + privs = { worldedit = true }, + func = function(name, params_text) + + local i = 1 -- For feedback only + local master_start_time = worldeditadditions.get_ms_time() + local times = {} + + local count, cmd_name, args = params_text:match("^(%d+)%s([^%s]+)%s(.+)$") + if not count then return false, "Error: Invalid syntax" end + print("[many] count", count, "cmd_name", cmd_name, "args", args) + + count = tonumber(count) + cmd_name = trim(cmd_name):sub(2) -- Things start at 1, not 0 in Lua :-( + + -- Check the command we're going to execute + local cmd = minetest.chatcommands[cmd_name] + if not cmd then + return false, "Error: "..cmd_name.." isn't a valid command." + end + if not minetest.check_player_privs(name, cmd.privs) then + return false, "Your privileges are insufficient to run that command." + end + + step({ + master_start_time = master_start_time, + count = count, + i = 0, + times = {}, + cmd_name = cmd_name, + args = args, + name = name + }) + end +})