aboutsummaryrefslogtreecommitdiff
path: root/src/mapgen_v6.cpp
Commit message (Collapse)AuthorAge
* Mapgen files: Update and correct copyright creditsparamat2017-05-26
|
* Mgv6 mudflow: Remove decoration if 'dirt with grass' below flows away (#5798)Paramat2017-05-25
| | | | | | | Mudflow of a neighbouring mapchunk extends into a mapchunk's edge, and could remove 'dirt with grass' from under a decoration, creating unsupported decorations. Remove any decoration above if a 'dirt with grass' node is removed by mudflow.
* Dungeongen: Add and improve parametersparamat2017-02-26
| | | | | | | | | | | | | | | | | | Add: Bool for 'only_in_ground'. Min and max corridor length. Min and max room size with X, Y, Z components. Min and max large room size with X, Y, Z components. 'only_in_ground = false' allows core mapgens to create structures in air and water using dungeongen. Corridor length parameters replace a fixed random range. Room size parameters replace the former system where one parameter 'roomsize' was added to fixed random ranges. All parameters are set for no change to current dungeon behaviour. Remove some now-redundant and long-unused code.
* Mgv6: Add fallback node for gravelparamat2017-02-04
| | | | | | | | Gravel now falls back to stone. Gravel is not a particularly fundamental node, allowing it to fall back to stone frees up subgames from having to include a gravel node. Non-blob-ore gravel is only present in mgv6 as extremely rare 'gravel biomes'.
* Mgv6: Add stairs to desert stone dungeonsparamat2017-02-04
| | | | | | As with the other mapgens, now that wide stairs in dungeons are possible we can now finally add stairs to desert stone dungeons. Re-order some lines.
* Mapgen: Remove unused 'flat' and 'trees' flags from mg_flagsparamat2016-08-04
| | | | | | | | | | | | | | | | When the 'flat' and 'trees' flags were moved into mgv6_spflags they were left in mg_flags in an attempt to support old mgv6 worlds. However their appearence in mg_flags causes confusion, also, later, old-world support was found to be broken for mgv6 worlds with 'notrees'. This commit cleans up the mess and comes a month after a thread warning of the change, and explaining the required action, was posted in the news subforum. Only old mgv6 worlds with 'flat' or 'notrees' are affected, a small minority of worlds, the required action being correctly setting these flags in mgv6_spflags. Disable a section of the 'map settings manager' unit test which is to be changed as it is causing problems for pull requests.
* Add MapSettingsManager and new mapgen setting script API functionskwolekr2016-07-03
| | | | | | | | | | | | | | | This commit refactors the majority of the Mapgen settings system. - MapgenParams is now owned by MapSettingsManager, itself a part of ServerMap, instead of the EmergeManager. - New Script API functions added: core.get_mapgen_setting core.get_mapgen_setting_noiseparams, core.set_mapgen_setting, and core.set_mapgen_setting_noiseparams. - minetest.get/set_mapgen_params are deprecated by the above new functions. - It is now possible to view and modify any arbitrary mapgen setting from a mod, rather than the base MapgenParams structure. - MapgenSpecificParams has been removed.
* Dungeons: Generalise use, add capabilities, various modificationsparamat2016-06-17
| | | | | | | | | | | | | | | | | | | | | | | | | | | | - Generalise node names to c_wall and c_alt_wall - Remove 'mossratio' and instead disable alt_wall loop if c_alt_wall == CONTENT_IGNORE - Use one generalised 3D noise for alternative wall nodes and in mgv6 create moss distribution similar to the previous - Rename rarity noise to density noise and enable the option of multiple dungeons per chunk determined by the value. Recreate previous distribution - Add parameters for min and max rooms per dungeon - Add dungeon y limits - Integrate river water properly Generalisation is needed now that we have sandstone and desert stone dungeons by default and can choose any node for alternative structure. The current code is based around cobble dungeons with mossycobble alternative nodes, the 2 noises controlling the alternative nodes are based on wetness. Enabling multiple dungeons per chunk with definable number of rooms allows the option of very dense and complex underground structures that could interconnect to create megastructures. Y limits are added to be consistent with other mapgen elements, and enable locaton of dungeon or megastructure realms as part of our 'stacked realms' philosophy.
* Dungeongen: Remove dependency on Mapgenkwolekr2016-05-27
|
* Cavegen: Remove CavesV6 dependency on Mapgenkwolekr2016-05-27
|
* Cavegen: Rename CaveV6 to CavesV6kwolekr2016-05-27
| | | | | | | - Add comment explaining why it exists - Remove unused 'flooded' variable - Rename shadowed variable - Fix some code style
* Mapgen V6: Synchronize spflags with defaultsettingskwolekr2016-05-10
| | | | | This fixes an issue where trees are omitted from Mapgen V6 maps on configurations that explicitly defined the mgv6_spflags setting.
* FindSpawnPos: Let mapgens decide what spawn altitude is suitableparamat2016-02-09
| | | | | | | | | | | | To avoid spawn search failing in new specialised mapgens Increase spawn search range to 4000 nodes Add getSpawnLevelAtPoint() functions to EmergeManager, class Mapgen and all mapgens Remove getGroundLevelAtPoint() functions from all mapgens except mgv6 (possibly to be re-added later in the correct form to return actual ground level) Make mgvalleys flag names consistent with other mapgens Remove now unused 'vertical spawn range' setting
* Mapgen: Various fixes and improvementsparamat2016-01-11
| | | | | | | | | | | | | Lua_api.txt: Document 'minetest.registered_biomes' Minimal: Remove 'mapgen_air' alias Cavegen: Add fallback node for 'mapgen_ice' Dungeongen: Add fallback node for 'mapgen_river_water_source' Mgv5: Remove unnecessary '#include util/directiontables.h' Add missing 'this->'s in makeChunk() Mgv6: Edit empty line formatting Remove leading spaces in makeChunk() Add missing spaces after 'for' and 'if' Mgv7: Edit empty line formatting
* Mapgen: Add propagate_shadow bool to calcLightingparamat2015-12-07
| | | | | | | | | | To terminate unwanted shadows from floatlands or realms above Also add to LuaVoxelManip calc_lighting for use in mapgen mods Remove the 2 argument calcLighting, mapgens now use the 5 argument form to specify the volumes for propagateSunlight and spreadLight In mgsinglenode replace calcLighting with setLighting and clean-up use of tabs and spaces
* Mapgen: Add global 'decorations' flagparamat2015-11-21
| | | | | | | | | | Flag is set by default in MapgenParams The global 'trees' flag remains but is now undocumented and unset by default in MapgenParams Add mgv6_spflag 'trees' set by default in defaultsettings.cpp to affect new worlds only This is automatically backwards compatible for existing worlds
* Mgv6: Move global mapgen flag 'flat' into mgv6 spflagsparamat2015-11-13
| | | | | Add mgv6 spflag 'flat' Global flag is kept for backwards compatibility but is now undocumented
* Mapgen: Use mapgen-specific names for constants in headersparamat2015-10-09
| | | | | Update copyright years in all mapgens Add myself to copyright notices in mgv5 and mgv7
* Define and use limit constants for Irrlicht fixed-width typeskwolekr2015-10-04
|
* Various style cleanups + unused code removalest312015-09-19
| | | | | | | | | | -> Don't pass pointer to whole IGameDef to NodeMetadata constructors and deserializers, but only to IItemDefManager, which is needed -> Remove the unused content_mapnode_get_new_name() method -> Fix style for MapBlock::deSerialize and MapBlock::deSerialize_pre22, improving accuracy of error messages a bit -> Fix style at other serialisation methods too -> Improve accuracy of some comments
* Mgv5/6/7: Re-add #include profiler.h as commented-out optionparamat2015-09-19
|
* Add map limit config optionrubenwardy2015-08-02
|
* Remove profiler.h include where it's not needed. Remove some unreachable and ↵Loic Blot2015-07-21
| | | | very old code
* Mgv6/treegen: (Re)Add fallback nodes for compatibility with subgamesparamat2015-07-13
|
* Mgv6: Don't create air gap in tundra at y = 48 in custom high terrainparamat2015-06-18
|
* Mgv6: Enable snowbiomes by default. Double biome noise spread. 3 octaves, ↵paramat2015-05-26
| | | | 0.5 persistence for humidity
* Mapgen v5/6/7: Cleanup node resolver and aliasesparamat2015-05-12
|
* Mgv6: Fix taiga, allow pine tree spawning on snowblocksparamat2015-04-12
|
* Mgv6: Add optional snow biomesparamat2015-04-12
|
* Move globals from main.cpp to more sane locationsCraig Robbins2015-04-01
| | | | | | | | | | | | Move debug streams to log.cpp|h Move GUI-related globals to clientlauncher Move g_settings and g_settings_path to settings.cpp|h Move g_menuclouds to clouds.cpp|h Move g_profiler to profiler.cpp|h
* Mgv6: Use heightmap in placeTreesAndJungleGrass()paramat2015-03-23
|
* Mgv6: Remove addDirtGravelBlobs, replaced by blob ore in Minetest Gameparamat2015-03-18
| | | | | Desert stone above y = -32 not water_level Remove unused generateExperimental()
* Mgv6: Fix uninitialised heightmap used by cavegenparamat2015-03-11
|
* Ensure that heightmap is initialized before useCraig Robbins2015-03-10
| | | | Without this, cavegen will use values in the heightmap before they are initialized.
* Heightmaps: Fix uninitialised values in mgv5/mgv6. findGroundLevel: Return ↵paramat2015-03-08
| | | | -MAP_GENERATION_LIMIT if surface not found
* Respect game mapgen flags and save world noise paramsngosang2015-03-07
|
* For usages of assert() that are meant to persist in Release builds (when ↵Craig Robbins2015-03-07
| | | | NDEBUG is defined), replace those usages with persistent alternatives
* Fix mapgen using unitialised height map valuesCraig Robbins2015-03-06
|
* Fix memory leak in MapgenV6Craig Robbins2015-03-05
|
* Mgv6: Add heightmap. Do not make large caves that are entirely above groundparamat2015-03-02
|
* Fix all warnings and remove -Wno-unused-but-set cflagkwolekr2015-01-18
|
* Mapgen V6: Re-enable liquid flowingkwolekr2015-01-07
|
* Lighting: Fix nearly all issueskwolekr2015-01-04
| | | | | | | | | | | The cause of a single light source seemingly being lit without spread was due to its creation in the +Y mapblock boundary layer during map generation, which was ignored as the overtop. This overtop explicitly needs to be omitted during sunlight propagation, however. To accomplish this, Mapgen::calcLighting() was split into separate functions taking separate parameters. Additionally, do not diminish light too early during spread. This fixes the output inconsistency between Map::updateLighting and Mapgen::calcLighting.
* MgV5/6/7: Generate dungeons above water levelparamat2015-01-01
| | | | | | Use/add stone_surface_max_y to speed-optimise/guide dungeon generation MgV7: Don't let mountain terrain chop dungeons at mapchunk borders Make mountain terrain update stone_surface_max_y for caves in mountains
* Fix some lingering code style issueskwolekr2014-12-29
|
* Mapgens: Rename m_emerge to prevent name collisionskwolekr2014-12-12
|
* Clean up Noise macroskwolekr2014-12-11
|
* Noise: Automatically transform noise maps if neededkwolekr2014-12-10
|
* Noise: Create a deep copy of NoiseParamskwolekr2014-12-10
|
* Add flags and lacunarity as new noise parameterskwolekr2014-12-07
| | | | | | | Add 'absolute value' option to noise map functions Extend persistence modulation to 3D noise Extend 'eased' option to noise2d_perlin* functions Some noise.cpp formatting fixups
">) if has_privs then core.set_last_run_mod(cmd_def.mod_origin) local success, message = cmd_def.func(name, param) if message then core.chat_send_player(name, message) end else core.chat_send_player(name, "You don't have permission" .. " to run this command (missing privileges: " .. table.concat(missing_privs, ", ") .. ")") end return true -- Handled chat message end) if core.setting_getbool("profiler.load") then -- Run after register_chatcommand and its register_on_chat_message -- Before any chattcommands that should be profiled profiler.init_chatcommand() end -- Parses a "range" string in the format of "here (number)" or -- "(x1, y1, z1) (x2, y2, z2)", returning two position vectors local function parse_range_str(player_name, str) local p1, p2 local args = str:split(" ") if args[1] == "here" then p1, p2 = core.get_player_radius_area(player_name, tonumber(args[2])) if p1 == nil then return false, "Unable to get player " .. player_name .. " position" end else p1, p2 = core.string_to_area(str) if p1 == nil then return false, "Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)" end end return p1, p2 end -- -- Chat commands -- core.register_chatcommand("me", { params = "<action>", description = "Display chat action (e.g., '/me orders a pizza' displays" .. " '<player name> orders a pizza')", privs = {shout=true}, func = function(name, param) core.chat_send_all("* " .. name .. " " .. param) end, }) core.register_chatcommand("admin", { description = "Show the name of the server owner", func = function(name) local admin = minetest.setting_get("name") if admin then return true, "The administrator of this server is "..admin.."." else return false, "There's no administrator named in the config file." end end, }) core.register_chatcommand("help", { privs = {}, params = "[all/privs/<cmd>]", description = "Get help for commands or list privileges", func = function(name, param) local function format_help_line(cmd, def) local msg = core.colorize("#00ffff", "/"..cmd) if def.params and def.params ~= "" then msg = msg .. " " .. def.params end if def.description and def.description ~= "" then msg = msg .. ": " .. def.description end return msg end if param == "" then local msg = "" local cmds = {} for cmd, def in pairs(core.registered_chatcommands) do if core.check_player_privs(name, def.privs) then cmds[#cmds + 1] = cmd end end table.sort(cmds) return true, "Available commands: " .. table.concat(cmds, " ") .. "\n" .. "Use '/help <cmd>' to get more information," .. " or '/help all' to list everything." elseif param == "all" then local cmds = {} for cmd, def in pairs(core.registered_chatcommands) do if core.check_player_privs(name, def.privs) then cmds[#cmds + 1] = format_help_line(cmd, def) end end table.sort(cmds) return true, "Available commands:\n"..table.concat(cmds, "\n") elseif param == "privs" then local privs = {} for priv, def in pairs(core.registered_privileges) do privs[#privs + 1] = priv .. ": " .. def.description end table.sort(privs) return true, "Available privileges:\n"..table.concat(privs, "\n") else local cmd = param local def = core.registered_chatcommands[cmd] if not def then return false, "Command not available: "..cmd else return true, format_help_line(cmd, def) end end end, }) core.register_chatcommand("privs", { params = "<name>", description = "Print privileges of player", func = function(caller, param) param = param:trim() local name = (param ~= "" and param or caller) return true, "Privileges of " .. name .. ": " .. core.privs_to_string( core.get_player_privs(name), ' ') end, }) local function handle_grant_command(caller, grantname, grantprivstr) local caller_privs = minetest.get_player_privs(caller) if not (caller_privs.privs or caller_privs.basic_privs) then return false, "Your privileges are insufficient." end if not core.get_auth_handler().get_auth(grantname) then return false, "Player " .. grantname .. " does not exist." end local grantprivs = core.string_to_privs(grantprivstr) if grantprivstr == "all" then grantprivs = core.registered_privileges end local privs = core.get_player_privs(grantname) local privs_unknown = "" local basic_privs = core.string_to_privs(core.setting_get("basic_privs") or "interact,shout") for priv, _ in pairs(grantprivs) do if not basic_privs[priv] and not caller_privs.privs then return false, "Your privileges are insufficient." end if not core.registered_privileges[priv] then privs_unknown = privs_unknown .. "Unknown privilege: " .. priv .. "\n" end privs[priv] = true end if privs_unknown ~= "" then return false, privs_unknown end core.set_player_privs(grantname, privs) core.log("action", caller..' granted ('..core.privs_to_string(grantprivs, ', ')..') privileges to '..grantname) if grantname ~= caller then core.chat_send_player(grantname, caller .. " granted you privileges: " .. core.privs_to_string(grantprivs, ' ')) end return true, "Privileges of " .. grantname .. ": " .. core.privs_to_string( core.get_player_privs(grantname), ' ') end core.register_chatcommand("grant", { params = "<name> <privilege>|all", description = "Give privilege to player", func = function(name, param) local grantname, grantprivstr = string.match(param, "([^ ]+) (.+)") if not grantname or not grantprivstr then return false, "Invalid parameters (see /help grant)" end return handle_grant_command(name, grantname, grantprivstr) end, }) core.register_chatcommand("grantme", { params = "<privilege>|all", description = "Grant privileges to yourself", func = function(name, param) if param == "" then return false, "Invalid parameters (see /help grantme)" end return handle_grant_command(name, name, param) end, }) core.register_chatcommand("revoke", { params = "<name> <privilege>|all", description = "Remove privilege from player", privs = {}, func = function(name, param) if not core.check_player_privs(name, {privs=true}) and not core.check_player_privs(name, {basic_privs=true}) then return false, "Your privileges are insufficient." end local revoke_name, revoke_priv_str = string.match(param, "([^ ]+) (.+)") if not revoke_name or not revoke_priv_str then return false, "Invalid parameters (see /help revoke)" elseif not core.get_auth_handler().get_auth(revoke_name) then return false, "Player " .. revoke_name .. " does not exist." end local revoke_privs = core.string_to_privs(revoke_priv_str) local privs = core.get_player_privs(revoke_name) local basic_privs = core.string_to_privs(core.setting_get("basic_privs") or "interact,shout") for priv, _ in pairs(revoke_privs) do if not basic_privs[priv] and not core.check_player_privs(name, {privs=true}) then return false, "Your privileges are insufficient." end end if revoke_priv_str == "all" then privs = {} else for priv, _ in pairs(revoke_privs) do privs[priv] = nil end end core.set_player_privs(revoke_name, privs) core.log("action", name..' revoked (' ..core.privs_to_string(revoke_privs, ', ') ..') privileges from '..revoke_name) if revoke_name ~= name then core.chat_send_player(revoke_name, name .. " revoked privileges from you: " .. core.privs_to_string(revoke_privs, ' ')) end return true, "Privileges of " .. revoke_name .. ": " .. core.privs_to_string( core.get_player_privs(revoke_name), ' ') end, }) core.register_chatcommand("setpassword", { params = "<name> <password>", description = "Set player's password", privs = {password=true}, func = function(name, param) local toname, raw_password = string.match(param, "^([^ ]+) +(.+)$") if not toname then toname = param:match("^([^ ]+) *$") raw_password = nil end if not toname then return false, "Name field required" end local act_str_past = "?" local act_str_pres = "?" if not raw_password then core.set_player_password(toname, "") act_str_past = "cleared" act_str_pres = "clears" else core.set_player_password(toname, core.get_password_hash(toname, raw_password)) act_str_past = "set" act_str_pres = "sets" end if toname ~= name then core.chat_send_player(toname, "Your password was " .. act_str_past .. " by " .. name) end core.log("action", name .. " " .. act_str_pres .. " password of " .. toname .. ".") return true, "Password of player \"" .. toname .. "\" " .. act_str_past end, }) core.register_chatcommand("clearpassword", { params = "<name>", description = "Set empty password", privs = {password=true}, func = function(name, param) local toname = param if toname == "" then return false, "Name field required" end core.set_player_password(toname, '') core.log("action", name .. " clears password of " .. toname .. ".") return true, "Password of player \"" .. toname .. "\" cleared" end, }) core.register_chatcommand("auth_reload", { params = "", description = "Reload authentication data", privs = {server=true}, func = function(name, param) local done = core.auth_reload() return done, (done and "Done." or "Failed.") end, }) core.register_chatcommand("teleport", { params = "<X>,<Y>,<Z> | <to_name> | <name> <X>,<Y>,<Z> | <name> <to_name>", description = "Teleport to player or position", privs = {teleport=true}, func = function(name, param) -- Returns (pos, true) if found, otherwise (pos, false) local function find_free_position_near(pos) local tries = { {x=1,y=0,z=0}, {x=-1,y=0,z=0}, {x=0,y=0,z=1}, {x=0,y=0,z=-1}, } for _, d in ipairs(tries) do local p = {x = pos.x+d.x, y = pos.y+d.y, z = pos.z+d.z} local n = core.get_node_or_nil(p) if n and n.name then local def = core.registered_nodes[n.name] if def and not def.walkable then return p, true end end end return pos, false end local teleportee = nil local p = {} p.x, p.y, p.z = string.match(param, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$") p.x = tonumber(p.x) p.y = tonumber(p.y) p.z = tonumber(p.z) if p.x and p.y and p.z then local lm = tonumber(minetest.setting_get("map_generation_limit") or 31000) if p.x < -lm or p.x > lm or p.y < -lm or p.y > lm or p.z < -lm or p.z > lm then return false, "Cannot teleport out of map bounds!" end teleportee = core.get_player_by_name(name) if teleportee then teleportee:setpos(p) return true, "Teleporting to "..core.pos_to_string(p) end end local teleportee = nil local p = nil local target_name = nil target_name = param:match("^([^ ]+)$") teleportee = core.get_player_by_name(name) if target_name then local target = core.get_player_by_name(target_name) if target then p = target:getpos() end end if teleportee and p then p = find_free_position_near(p) teleportee:setpos(p) return true, "Teleporting to " .. target_name .. " at "..core.pos_to_string(p) end if not core.check_player_privs(name, {bring=true}) then return false, "You don't have permission to teleport other players (missing bring privilege)" end local teleportee = nil local p = {} local teleportee_name = nil teleportee_name, p.x, p.y, p.z = param:match( "^([^ ]+) +([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$") p.x, p.y, p.z = tonumber(p.x), tonumber(p.y), tonumber(p.z) if teleportee_name then teleportee = core.get_player_by_name(teleportee_name) end if teleportee and p.x and p.y and p.z then teleportee:setpos(p) return true, "Teleporting " .. teleportee_name .. " to " .. core.pos_to_string(p) end local teleportee = nil local p = nil local teleportee_name = nil local target_name = nil teleportee_name, target_name = string.match(param, "^([^ ]+) +([^ ]+)$") if teleportee_name then teleportee = core.get_player_by_name(teleportee_name) end if target_name then local target = core.get_player_by_name(target_name) if target then p = target:getpos() end end if teleportee and p then p = find_free_position_near(p) teleportee:setpos(p) return true, "Teleporting " .. teleportee_name .. " to " .. target_name .. " at " .. core.pos_to_string(p) end return false, 'Invalid parameters ("' .. param .. '") or player not found (see /help teleport)' end, }) core.register_chatcommand("set", { params = "[-n] <name> <value> | <name>", description = "Set or read server configuration setting", privs = {server=true}, func = function(name, param) local arg, setname, setvalue = string.match(param, "(-[n]) ([^ ]+) (.+)") if arg and arg == "-n" and setname and setvalue then core.setting_set(setname, setvalue) return true, setname .. " = " .. setvalue end local setname, setvalue = string.match(param, "([^ ]+) (.+)") if setname and setvalue then if not core.setting_get(setname) then return false, "Failed. Use '/set -n <name> <value>' to create a new setting." end core.setting_set(setname, setvalue) return true, setname .. " = " .. setvalue end local setname = string.match(param, "([^ ]+)") if setname then local setvalue = core.setting_get(setname) if not setvalue then setvalue = "<not set>" end return true, setname .. " = " .. setvalue end return false, "Invalid parameters (see /help set)." end, }) local function emergeblocks_callback(pos, action, num_calls_remaining, ctx) if ctx.total_blocks == 0 then ctx.total_blocks = num_calls_remaining + 1 ctx.current_blocks = 0 end ctx.current_blocks = ctx.current_blocks + 1 if ctx.current_blocks == ctx.total_blocks then core.chat_send_player(ctx.requestor_name, string.format("Finished emerging %d blocks in %.2fms.", ctx.total_blocks, (os.clock() - ctx.start_time) * 1000)) end end local function emergeblocks_progress_update(ctx) if ctx.current_blocks ~= ctx.total_blocks then core.chat_send_player(ctx.requestor_name, string.format("emergeblocks update: %d/%d blocks emerged (%.1f%%)", ctx.current_blocks, ctx.total_blocks, (ctx.current_blocks / ctx.total_blocks) * 100)) core.after(2, emergeblocks_progress_update, ctx) end end core.register_chatcommand("emergeblocks", { params = "(here [radius]) | (<pos1> <pos2>)", description = "Load (or, if nonexistent, generate) map blocks " .. "contained in area pos1 to pos2", privs = {server=true}, func = function(name, param) local p1, p2 = parse_range_str(name, param) if p1 == false then return false, p2 end local context = { current_blocks = 0, total_blocks = 0, start_time = os.clock(), requestor_name = name } core.emerge_area(p1, p2, emergeblocks_callback, context) core.after(2, emergeblocks_progress_update, context) return true, "Started emerge of area ranging from " .. core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1) end, }) core.register_chatcommand("deleteblocks", { params = "(here [radius]) | (<pos1> <pos2>)", description = "Delete map blocks contained in area pos1 to pos2", privs = {server=true}, func = function(name, param) local p1, p2 = parse_range_str(name, param) if p1 == false then return false, p2 end if core.delete_area(p1, p2) then return true, "Successfully cleared area ranging from " .. core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1) else return false, "Failed to clear one or more blocks in area" end end, }) core.register_chatcommand("mods", { params = "", description = "List mods installed on the server", privs = {}, func = function(name, param) return true, table.concat(core.get_modnames(), ", ") end, }) local function handle_give_command(cmd, giver, receiver, stackstring) core.log("action", giver .. " invoked " .. cmd .. ', stackstring="' .. stackstring .. '"') local itemstack = ItemStack(stackstring) if itemstack:is_empty() then return false, "Cannot give an empty item" elseif not itemstack:is_known() then return false, "Cannot give an unknown item" end local receiverref = core.get_player_by_name(receiver) if receiverref == nil then return false, receiver .. " is not a known player" end local leftover = receiverref:get_inventory():add_item("main", itemstack) local partiality if leftover:is_empty() then partiality = "" elseif leftover:get_count() == itemstack:get_count() then partiality = "could not be " else partiality = "partially " end -- The actual item stack string may be different from what the "giver" -- entered (e.g. big numbers are always interpreted as 2^16-1). stackstring = itemstack:to_string() if giver == receiver then return true, ("%q %sadded to inventory.") :format(stackstring, partiality) else core.chat_send_player(receiver, ("%q %sadded to inventory.") :format(stackstring, partiality)) return true, ("%q %sadded to %s's inventory.") :format(stackstring, partiality, receiver) end end core.register_chatcommand("give", { params = "<name> <ItemString>", description = "Give item to player", privs = {give=true}, func = function(name, param) local toname, itemstring = string.match(param, "^([^ ]+) +(.+)$") if not toname or not itemstring then return false, "Name and ItemString required" end return handle_give_command("/give", name, toname, itemstring) end, }) core.register_chatcommand("giveme", { params = "<ItemString>", description = "Give item to yourself", privs = {give=true}, func = function(name, param) local itemstring = string.match(param, "(.+)$") if not itemstring then return false, "ItemString required" end return handle_give_command("/giveme", name, name, itemstring) end, }) core.register_chatcommand("spawnentity", { params = "<EntityName> [<X>,<Y>,<Z>]", description = "Spawn entity at given (or your) position", privs = {give=true, interact=true}, func = function(name, param) local entityname, p = string.match(param, "^([^ ]+) *(.*)$") if not entityname then return false, "EntityName required" end core.log("action", ("%s invokes /spawnentity, entityname=%q") :format(name, entityname)) local player = core.get_player_by_name(name) if player == nil then core.log("error", "Unable to spawn entity, player is nil") return false, "Unable to spawn entity, player is nil" end if p == "" then p = player:getpos() else p = core.string_to_pos(p) if p == nil then return false, "Invalid parameters ('" .. param .. "')" end end p.y = p.y + 1 core.add_entity(p, entityname) return true, ("%q spawned."):format(entityname) end, }) core.register_chatcommand("pulverize", { params = "", description = "Destroy item in hand", func = function(name, param) local player = core.get_player_by_name(name) if not player then core.log("error", "Unable to pulverize, no player.") return false, "Unable to pulverize, no player." end if player:get_wielded_item():is_empty() then return false, "Unable to pulverize, no item in hand." end player:set_wielded_item(nil) return true, "An item was pulverized." end, }) -- Key = player name core.rollback_punch_callbacks = {} core.register_on_punchnode(function(pos, node, puncher) local name = puncher:get_player_name() if core.rollback_punch_callbacks[name] then core.rollback_punch_callbacks[name](pos, node, puncher) core.rollback_punch_callbacks[name] = nil end end) core.register_chatcommand("rollback_check", { params = "[<range>] [<seconds>] [limit]", description = "Check who last touched a node or a node near it" .. " within the time specified by <seconds>. Default: range = 0," .. " seconds = 86400 = 24h, limit = 5", privs = {rollback=true}, func = function(name, param) if not core.setting_getbool("enable_rollback_recording") then return false, "Rollback functions are disabled." end local range, seconds, limit = param:match("(%d+) *(%d*) *(%d*)") range = tonumber(range) or 0 seconds = tonumber(seconds) or 86400 limit = tonumber(limit) or 5 if limit > 100 then return false, "That limit is too high!" end core.rollback_punch_callbacks[name] = function(pos, node, puncher) local name = puncher:get_player_name() core.chat_send_player(name, "Checking " .. core.pos_to_string(pos) .. "...") local actions = core.rollback_get_node_actions(pos, range, seconds, limit) if not actions then core.chat_send_player(name, "Rollback functions are disabled") return end local num_actions = #actions if num_actions == 0 then core.chat_send_player(name, "Nobody has touched" .. " the specified location in " .. seconds .. " seconds") return end local time = os.time() for i = num_actions, 1, -1 do local action = actions[i] core.chat_send_player(name, ("%s %s %s -> %s %d seconds ago.") :format( core.pos_to_string(action.pos), action.actor, action.oldnode.name, action.newnode.name, time - action.time)) end end return true, "Punch a node (range=" .. range .. ", seconds=" .. seconds .. "s, limit=" .. limit .. ")" end, }) core.register_chatcommand("rollback", { params = "<player name> [<seconds>] | :<actor> [<seconds>]", description = "Revert actions of a player. Default for <seconds> is 60", privs = {rollback=true}, func = function(name, param) if not core.setting_getbool("enable_rollback_recording") then return false, "Rollback functions are disabled." end local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)") if not target_name then local player_name = nil player_name, seconds = string.match(param, "([^ ]+) *(%d*)") if not player_name then return false, "Invalid parameters. See /help rollback" .. " and /help rollback_check." end target_name = "player:"..player_name end seconds = tonumber(seconds) or 60 core.chat_send_player(name, "Reverting actions of " .. target_name .. " since " .. seconds .. " seconds.") local success, log = core.rollback_revert_actions_by( target_name, seconds) local response = "" if #log > 100 then response = "(log is too long to show)\n" else for _, line in pairs(log) do response = response .. line .. "\n" end end response = response .. "Reverting actions " .. (success and "succeeded." or "FAILED.") return success, response end, }) core.register_chatcommand("status", { description = "Print server status", func = function(name, param) return true, core.get_server_status() end, }) core.register_chatcommand("time", { params = "<0..23>:<0..59> | <0..24000>", description = "Set time of day", privs = {}, func = function(name, param) if param == "" then local current_time = math.floor(core.get_timeofday() * 1440) local minutes = current_time % 60 local hour = (current_time - minutes) / 60 return true, ("Current time is %d:%02d"):format(hour, minutes) end local player_privs = core.get_player_privs(name) if not player_privs.settime then return false, "You don't have permission to run this command " .. "(missing privilege: settime)." end local hour, minute = param:match("^(%d+):(%d+)$") if not hour then local new_time = tonumber(param) if not new_time then return false, "Invalid time." end -- Backward compatibility. core.set_timeofday((new_time % 24000) / 24000) core.log("action", name .. " sets time to " .. new_time) return true, "Time of day changed." end hour = tonumber(hour) minute = tonumber(minute) if hour < 0 or hour > 23 then return false, "Invalid hour (must be between 0 and 23 inclusive)." elseif minute < 0 or minute > 59 then return false, "Invalid minute (must be between 0 and 59 inclusive)." end core.set_timeofday((hour * 60 + minute) / 1440) core.log("action", ("%s sets time to %d:%02d"):format(name, hour, minute)) return true, "Time of day changed." end, }) core.register_chatcommand("days", { description = "Display day count", func = function(name, param) return true, "Current day is " .. core.get_day_count() end }) core.register_chatcommand("shutdown", { description = "Shutdown server", privs = {server=true}, func = function(name, param) core.log("action", name .. " shuts down server") core.request_shutdown() core.chat_send_all("*** Server shutting down (operator request).") end, }) core.register_chatcommand("ban", { params = "<name>", description = "Ban IP of player", privs = {ban=true}, func = function(name, param) if param == "" then return true, "Ban list: " .. core.get_ban_list() end if not core.get_player_by_name(param) then return false, "No such player." end if not core.ban_player(param) then return false, "Failed to ban player." end local desc = core.get_ban_description(param) core.log("action", name .. " bans " .. desc .. ".") return true, "Banned " .. desc .. "." end, }) core.register_chatcommand("unban", { params = "<name/ip>", description = "Remove IP ban", privs = {ban=true}, func = function(name, param) if not core.unban_player_or_ip(param) then return false, "Failed to unban player/IP." end core.log("action", name .. " unbans " .. param) return true, "Unbanned " .. param end, }) core.register_chatcommand("kick", { params = "<name> [reason]", description = "Kick a player", privs = {kick=true}, func = function(name, param) local tokick, reason = param:match("([^ ]+) (.+)") tokick = tokick or param if not core.kick_player(tokick, reason) then return false, "Failed to kick player " .. tokick end local log_reason = "" if reason then log_reason = " with reason \"" .. reason .. "\"" end core.log("action", name .. " kicks " .. tokick .. log_reason) return true, "Kicked " .. tokick end, }) core.register_chatcommand("clearobjects", { params = "[full|quick]", description = "Clear all objects in world", privs = {server=true}, func = function(name, param) local options = {} if param == "" or param == "full" then options.mode = "full" elseif param == "quick" then options.mode = "quick" else return false, "Invalid usage, see /help clearobjects." end core.log("action", name .. " clears all objects (" .. options.mode .. " mode).") core.chat_send_all("Clearing all objects. This may take long." .. " You may experience a timeout. (by " .. name .. ")") core.clear_objects(options) core.log("action", "Object clearing done.") core.chat_send_all("*** Cleared all objects.") end, }) core.register_chatcommand("msg", { params = "<name> <message>", description = "Send a private message", privs = {shout=true}, func = function(name, param) local sendto, message = param:match("^(%S+)%s(.+)$") if not sendto then return false, "Invalid usage, see /help msg." end if not core.get_player_by_name(sendto) then return false, "The player " .. sendto .. " is not online." end core.log("action", "PM from " .. name .. " to " .. sendto .. ": " .. message) core.chat_send_player(sendto, "PM from " .. name .. ": " .. message) return true, "Message sent." end, }) core.register_chatcommand("last-login", { params = "[name]", description = "Get the last login time of a player", func = function(name, param) if param == "" then param = name end local pauth = core.get_auth_handler().get_auth(param) if pauth and pauth.last_login then -- Time in UTC, ISO 8601 format return true, "Last login time was " .. os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login) end return false, "Last login time is unknown" end, })