Administrative world anchor
authorZefram <zefram@fysh.org>
Wed, 13 Aug 2014 18:07:53 +0000 (19:07 +0100)
committerZefram <zefram@fysh.org>
Wed, 13 Aug 2014 18:07:53 +0000 (19:07 +0100)
manual.md
technic/locale/de.txt
technic/locale/es.txt
technic/locale/it.txt
technic/locale/template.txt
technic/machines/other/anchor.lua [new file with mode: 0644]
technic/machines/other/init.lua
technic/textures/technic_admin_anchor.png [new file with mode: 0644]

index d3566679e93020515f43d30bb5cdf78a4cc6c5dd..fb21f457abd3ed2afaa3fc2127a76251907e5ebe 100644 (file)
--- 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&times;511&times;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&times;16&times;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
 ---------------------------------
 
index 763d92a26d8ae6ac0d79d54eaf13d967e8ecec5d..6f9f8d2a63c303d74ec35477a26754a014de66e5 100644 (file)
@@ -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
index c5b6037f52e7f919feac3befb3ce7f88a4c8a61e..db817f895e02b902247251d0a83112467a384dc8 100644 (file)
@@ -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
index 5ba7e69d63487e676cd6a463c85df1c5d621a452..7b1c5377cfc4c1de3a0f628cc6d313b0767313a1 100644 (file)
@@ -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
index e57952d9556924ae60d71b3c8257b8be77d616ab..8bd5b2d3a5e41577140bb89eefd11a8e81a159c2 100644 (file)
@@ -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 (file)
index 0000000..8730355
--- /dev/null
@@ -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,
+})
index 14999852491a8a6aec64d36b2296d4726654d35a..27a47d51c3b31c7d7e9e663c07fcd2ba0087e2e3 100644 (file)
@@ -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 (file)
index 0000000..7ce9b4c
Binary files /dev/null and b/technic/textures/technic_admin_anchor.png differ