diff --git a/manual.md b/manual.md index d356667..fb21f45 100644 --- a/manual.md +++ b/manual.md @@ -725,6 +725,62 @@ the electrical system which is 100% efficient in moving energy around. To transfer more than 10000 EU/s between networks, connect multiple supply converters in parallel. +administrative world anchor +--------------------------- + +A world anchor is an object in the Minetest world that causes the server +to keep surrounding parts of the world running even when no players +are nearby. It is mainly used to allow machines to run unattended: +normally machines are suspended when not near a player. The technic +mod supplies a form of world anchor, as a placable block, but it is not +straightforwardly available to players. There is no recipe for it, so it +is only available if explicitly spawned into existence by someone with +administrative privileges. In a single-player world, the single player +normally has administrative privileges, and can obtain a world anchor +by entering the chat command "/give singleplayer technic:admin\_anchor". + +The world anchor tries to force a cubical area, centred upon the anchor, +to stay loaded. The distance from the anchor to the most distant map +nodes that it will keep loaded is referred to as the "radius", and can be +set in the world anchor's interaction form. The radius can be set as low +as 0, meaning that the anchor only tries to keep itself loaded, or as high +as 255, meaning that it will operate on a 511×511×511 cube. +Larger radii are forbidden, to avoid typos causing the server excessive +work; to keep a larger area loaded, use multiple anchors. Also use +multiple anchors if the area to be kept loaded is not well approximated +by a cube. + +The world is always kept loaded in units of 16×16×16 cubes, +confusingly known as "map blocks". The anchor's configured radius takes +no account of map block boundaries, but the anchor's effect is actually to +keep loaded each map block that contains any part of the configured cube. +The anchor's interaction form includes a status note showing how many map +blocks this is, and how many of those it is successfully keeping loaded. +When the anchor is disabled, as it is upon placement, it will always +show that it is keeping no map blocks loaded; this does not indicate +any kind of failure. + +The world anchor can optionally be locked. When it is locked, only +the anchor's owner, the player who placed it, can reconfigure it or +remove it. Only the owner can lock it. Locking an anchor is useful +if the use of anchors is being tightly controlled by administrators: +an administrator can set up a locked anchor and be sure that it will +not be set by ordinary players to an unapproved configuration. + +The server limits the ability of world anchors to keep parts of the world +loaded, to avoid overloading the server. The total number of map blocks +that can be kept loaded in this way is set by the server configuration +item "max\_forceloaded\_blocks" (in minetest.conf), which defaults to +only 16. For comparison, each player normally keeps 125 map blocks loaded +(a radius of 32). If an enabled world anchor shows that it is failing to +keep all the map blocks loaded that it would like to, this can be fixed +by increasing max\_forceloaded\_blocks by the amount of the shortfall. + +The tight limit on force-loading is the reason why the world anchor is +not directly available to players. With the limit so low both by default +and in common practice, the only feasible way to determine where world +anchors should be used is for administrators to decide it directly. + subjects missing from this manual --------------------------------- diff --git a/technic/locale/de.txt b/technic/locale/de.txt index 763d92a..6f9f8d2 100644 --- a/technic/locale/de.txt +++ b/technic/locale/de.txt @@ -54,6 +54,12 @@ Out: = Raus: Slot %d = Fach %d Itemwise = Einzelstuecke Stackwise = Ganzer Stapel +Owner: = +Unlocked = +Locked = +Radius: = +Enabled = +Disabled = ## Machine names # $1: Tier @@ -92,6 +98,7 @@ Fuel-Fired Furnace = Kohle-Ofen Wind Mill Frame = Windmuehlengeruest Forcefield = Kraftfeld Nuclear Reactor Rod Compartment = Brennstabfaecher +Administrative World Anchor = ## Machine-specific # $1: Pruduced EU @@ -106,6 +113,7 @@ Production at %d%% = Produktion bei %d%% Choose Milling Program: = Waehle ein Fraesprogramm: Slim Elements half / normal height: = Schmale Elemente von halber / normaler Hoehe: Current track %s = Aktueller Titel %s +Keeping %d/%d map blocks loaded = ## CNC Cylinder = Zylinder diff --git a/technic/locale/es.txt b/technic/locale/es.txt index c5b6037..db817f8 100644 --- a/technic/locale/es.txt +++ b/technic/locale/es.txt @@ -50,6 +50,12 @@ Range = Alcance Enable/Disable = Habilitar/Deshabilitar Itemwise = Stackwise = +Owner: = +Unlocked = +Locked = +Radius: = +Enabled = +Disabled = ## Machine names # $1: Tier @@ -88,6 +94,7 @@ Fuel-Fired Furnace = Horno a Carbon Forcefield = Campo de Fuerza Nuclear Reactor Rod Compartment = Compartimiento para Vara de Reactor Nuclear Wind Mill Frame = Armazon de Molino de Viento +Administrative World Anchor = ## Machine-specific # $1: Pruduced EU @@ -100,6 +107,7 @@ Power level = Nivel de Poder %s. Supply: %d Demand: %d = %s. Alimentacion: %d Demanda: %d # $1: Production percent Production at %d%% = Produccion en %d%% +Keeping %d/%d map blocks loaded = ## CNC Machine Element Edge = Elemento Borde diff --git a/technic/locale/it.txt b/technic/locale/it.txt index 5ba7e69..7b1c537 100644 --- a/technic/locale/it.txt +++ b/technic/locale/it.txt @@ -51,6 +51,12 @@ Out: = Uscita: Slot %d = Itemwise = Singolo elemento Stackwise = pila completa +Owner: = +Unlocked = +Locked = +Radius: = +Enabled = +Disabled = ## Machine names # $1: Tier @@ -89,6 +95,7 @@ Fuel-Fired Furnace = Fornace a carbone Wind Mill Frame = Pala eolica Forcefield = Campo di forza Nuclear Reactor Rod Compartment = Compartimento combustibile nucleare +Administrative World Anchor = ## Machine-specific # $1: Pruduced EU @@ -103,6 +110,7 @@ Production at %d%% = Produzione a %d%% Choose Milling Program: = Scegliere un programma di Fresatura Slim Elements half / normal height: = Metà elementi sottili / altezza normale: Current track %s = Traccia corrente %s +Keeping %d/%d map blocks loaded = ## CNC Cylinder = Cilindro diff --git a/technic/locale/template.txt b/technic/locale/template.txt index e57952d..8bd5b2d 100644 --- a/technic/locale/template.txt +++ b/technic/locale/template.txt @@ -58,6 +58,12 @@ Itemwise = Stackwise = Ignoring Mesecon Signal = Controlled by Mesecon Signal = +Owner: = +Unlocked = +Locked = +Radius: = +Enabled = +Disabled = ## Machine names # $1: Tier @@ -97,6 +103,7 @@ Fuel-Fired Furnace = Wind Mill Frame = Forcefield = Nuclear Reactor Rod Compartment = +Administrative World Anchor = ## Machine-specific # $1: Pruduced EU @@ -111,6 +118,7 @@ Production at %d%% = Choose Milling Program: = Slim Elements half / normal height: = Current track %s = +Keeping %d/%d map blocks loaded = ## CNC Cylinder = diff --git a/technic/machines/other/anchor.lua b/technic/machines/other/anchor.lua new file mode 100644 index 0000000..8730355 --- /dev/null +++ b/technic/machines/other/anchor.lua @@ -0,0 +1,109 @@ +local S = technic.getter + +local desc = S("Administrative World Anchor") + +local function compute_forceload_positions(pos, meta) + local radius = meta:get_int("radius") + local minpos = vector.subtract(pos, vector.new(radius, radius, radius)) + local maxpos = vector.add(pos, vector.new(radius, radius, radius)) + local minbpos = {} + local maxbpos = {} + for _, coord in ipairs({"x","y","z"}) do + minbpos[coord] = math.floor(minpos[coord] / 16) * 16 + maxbpos[coord] = math.floor(maxpos[coord] / 16) * 16 + end + local flposes = {} + for x = minbpos.x, maxbpos.x, 16 do + for y = minbpos.y, maxbpos.y, 16 do + for z = minbpos.z, maxbpos.z, 16 do + table.insert(flposes, vector.new(x, y, z)) + end + end + end + return flposes +end + +local function currently_forceloaded_positions(meta) + local ser = meta:get_string("forceloaded") + return ser == "" and {} or minetest.deserialize(ser) +end + +local function forceload_off(meta) + local flposes = currently_forceloaded_positions(meta) + meta:set_string("forceloaded", "") + for _, p in ipairs(flposes) do + minetest.forceload_free_block(p) + end +end + +local function forceload_on(pos, meta) + local want_flposes = compute_forceload_positions(pos, meta) + local have_flposes = {} + for _, p in ipairs(want_flposes) do + if minetest.forceload_block(p) then + table.insert(have_flposes, p) + end + end + meta:set_string("forceloaded", #have_flposes == 0 and "" or minetest.serialize(have_flposes)) +end + +local function set_display(pos, meta) + meta:set_string("infotext", S(meta:get_int("enabled") ~= 0 and "%s Enabled" or "%s Disabled"):format(desc)) + meta:set_string("formspec", + "size[5,3.5]".. + "item_image[0,0;1,1;technic:admin_anchor]".. + "label[1,0;"..minetest.formspec_escape(desc).."]".. + "label[0,1;"..minetest.formspec_escape(S("Owner:").." "..meta:get_string("owner")).."]".. + (meta:get_int("locked") == 0 and + "button[3,1;2,1;lock;"..minetest.formspec_escape(S("Unlocked")).."]" or + "button[3,1;2,1;unlock;"..minetest.formspec_escape(S("Locked")).."]").. + "field[0.25,2.3;1,1;radius;"..minetest.formspec_escape(S("Radius:"))..";"..meta:get_int("radius").."]".. + (meta:get_int("enabled") == 0 and + "button[3,2;2,1;enable;"..minetest.formspec_escape(S("Disabled")).."]" or + "button[3,2;2,1;disable;"..minetest.formspec_escape(S("Enabled")).."]").. + "label[0,3;"..minetest.formspec_escape(S("Keeping %d/%d map blocks loaded"):format(#currently_forceloaded_positions(meta), #compute_forceload_positions(pos, meta))).."]") +end + +minetest.register_node("technic:admin_anchor", { + description = desc, + drawtype = "normal", + tiles = {"technic_admin_anchor.png"}, + is_ground_content = true, + groups = {cracky=3}, + sounds = default.node_sound_stone_defaults(), + after_place_node = function (pos, placer) + local meta = minetest.get_meta(pos) + if placer and placer:is_player() then + meta:set_string("owner", placer:get_player_name()) + end + set_display(pos, meta) + end, + can_dig = function (pos, player) + local meta = minetest.get_meta(pos) + return meta:get_int("locked") == 0 or (player and player:is_player() and player:get_player_name() == meta:get_string("owner")) + end, + on_destruct = function (pos) + local meta = minetest.get_meta(pos) + forceload_off(meta) + end, + on_receive_fields = function (pos, formname, fields, sender) + local meta = minetest.get_meta(pos) + if (meta:get_int("locked") ~= 0 or fields.lock) and + not (sender and sender:is_player() and + sender:get_player_name() == meta:get_string("owner")) then + return + end + if fields.unlock then meta:set_int("locked", 0) end + if fields.lock then meta:set_int("locked", 1) end + if fields.disable or fields.enable or fields.radius then + forceload_off(meta) + if fields.disable then meta:set_int("enabled", 0) end + if fields.enable then meta:set_int("enabled", 1) end + if fields.radius and string.find(fields.radius, "^[0-9]+$") and tonumber(fields.radius) < 256 then meta:set_int("radius", fields.radius) end + if meta:get_int("enabled") ~= 0 then + forceload_on(pos, meta) + end + end + set_display(pos, meta) + end, +}) diff --git a/technic/machines/other/init.lua b/technic/machines/other/init.lua index 1499985..27a47d5 100644 --- a/technic/machines/other/init.lua +++ b/technic/machines/other/init.lua @@ -6,3 +6,4 @@ dofile(path.."/constructor.lua") if minetest.get_modpath("mesecons_mvps") ~= nil then dofile(path.."/frames.lua") end +dofile(path.."/anchor.lua") diff --git a/technic/textures/technic_admin_anchor.png b/technic/textures/technic_admin_anchor.png new file mode 100644 index 0000000..7ce9b4c Binary files /dev/null and b/technic/textures/technic_admin_anchor.png differ