aboutsummaryrefslogtreecommitdiff
path: root/ch_core/lib.lua
diff options
context:
space:
mode:
Diffstat (limited to 'ch_core/lib.lua')
-rw-r--r--ch_core/lib.lua2216
1 files changed, 2216 insertions, 0 deletions
diff --git a/ch_core/lib.lua b/ch_core/lib.lua
new file mode 100644
index 0000000..299003f
--- /dev/null
+++ b/ch_core/lib.lua
@@ -0,0 +1,2216 @@
+ch_core.open_submod("lib", {data = true})
+
+-- DATA
+-- ===========================================================================
+local click_sound = {
+ name = "click",
+ gain = 0.05,
+}
+local diakritika = {
+ ["Á"] = "A",
+ ["Ä"] = "A",
+ ["Č"] = "C",
+ ["Ď"] = "D",
+ ["É"] = "E",
+ ["Ě"] = "E",
+ ["Í"] = "I",
+ ["Ĺ"] = "L",
+ ["Ľ"] = "L",
+ ["Ň"] = "N",
+ ["Ó"] = "O",
+ ["Ô"] = "O",
+ ["Ŕ"] = "R",
+ ["Ř"] = "R",
+ ["Š"] = "S",
+ ["Ť"] = "T",
+ ["Ú"] = "U",
+ ["Ů"] = "U",
+ ["Ý"] = "Y",
+ ["Ž"] = "Z",
+ ["á"] = "a",
+ ["ä"] = "a",
+ ["č"] = "c",
+ ["ď"] = "d",
+ ["é"] = "e",
+ ["ě"] = "e",
+ ["í"] = "i",
+ ["ĺ"] = "l",
+ ["ľ"] = "l",
+ ["ň"] = "n",
+ ["ó"] = "o",
+ ["ô"] = "o",
+ ["ŕ"] = "r",
+ ["ř"] = "r",
+ ["š"] = "s",
+ ["ť"] = "t",
+ ["ú"] = "u",
+ ["ů"] = "u",
+ ["ý"] = "y",
+ ["ž"] = "z",
+}
+
+local diakritika_na_mala = {
+ ["Á"] = "á",
+ ["Ä"] = "ä",
+ ["Č"] = "č",
+ ["Ď"] = "ď",
+ ["É"] = "é",
+ ["Ě"] = "Ě",
+ ["Í"] = "Í",
+ ["Ĺ"] = "ĺ",
+ ["Ľ"] = "ľ",
+ ["Ň"] = "ň",
+ ["Ó"] = "ó",
+ ["Ô"] = "ô",
+ ["Ŕ"] = "ŕ",
+ ["Ř"] = "ř",
+ ["Š"] = "š",
+ ["Ť"] = "ť",
+ ["Ú"] = "ú",
+ ["Ů"] = "ů",
+ ["Ý"] = "ý",
+ ["Ž"] = "ž",
+}
+
+local diakritika_na_velka = {
+ ["á"] = "Á",
+ ["ä"] = "Ä",
+ ["č"] = "Č",
+ ["ď"] = "Ď",
+ ["é"] = "É",
+ ["Ě"] = "Ě",
+ ["Í"] = "Í",
+ ["ĺ"] = "Ĺ",
+ ["ľ"] = "Ľ",
+ ["ň"] = "Ň",
+ ["ó"] = "Ó",
+ ["ô"] = "Ô",
+ ["ŕ"] = "Ŕ",
+ ["ř"] = "Ř",
+ ["š"] = "Š",
+ ["ť"] = "Ť",
+ ["ú"] = "Ú",
+ ["ů"] = "Ů",
+ ["ý"] = "Ý",
+ ["ž"] = "Ž",
+}
+
+local facedir_to_rotation_data = {
+ [0] = vector.new(0, 0, 0),
+ [1] = vector.new(0, -0.5 * math.pi, 0),
+ [2] = vector.new(0, -math.pi, 0),
+ [3] = vector.new(0, 0.5 * math.pi, 0),
+ [4] = vector.new(-0.5 * math.pi, 0, 0),
+ [5] = vector.new(0, -0.5 * math.pi, -0.5 * math.pi),
+ [6] = vector.new(0.5 * math.pi, 0, math.pi),
+ [7] = vector.new(0, 0.5 * math.pi, 0.5 * math.pi),
+ [8] = vector.new(0.5 * math.pi, 0, 0),
+ [9] = vector.new(0, -0.5 * math.pi, 0.5 * math.pi),
+ [10] = vector.new(-0.5 * math.pi, 0, math.pi),
+ [11] = vector.new(0, 0.5 * math.pi, -0.5 * math.pi),
+ [12] = vector.new(0, 0, 0.5 * math.pi),
+ [13] = vector.new(-0.5 * math.pi, 0, 0.5 * math.pi),
+ [14] = vector.new(0, -math.pi, -0.5 * math.pi),
+ [15] = vector.new(0.5 * math.pi, 0, 0.5 * math.pi),
+ [16] = vector.new(0, 0, -0.5 * math.pi),
+ [17] = vector.new(0.5 * math.pi, 0, -0.5 * math.pi),
+ [18] = vector.new(0, -math.pi, 0.5 * math.pi),
+ [19] = vector.new(-0.5 * math.pi, 0, -0.5 * math.pi),
+ [20] = vector.new(0, 0, math.pi),
+ [21] = vector.new(0, 0.5 * math.pi, math.pi),
+ [22] = vector.new(0, -math.pi, math.pi),
+ [23] = vector.new(0, -0.5 * math.pi, math.pi),
+}
+
+local hypertext_escape_replacements = {
+ ["\\"] = "\\\\\\\\",
+ ["<"] = "\\\\<",
+ [">"] = "\\\\>",
+ [";"] = "\\;",
+ [","] = "\\,",
+ ["["] = "\\[",
+ ["]"] = "\\]",
+}
+
+local utf8_charlen = {}
+for i = 1, 191, 1 do
+ -- 1 to 127 => jednobajtové znaky
+ -- 128 až 191 => nejsou dovoleny jako první bajt (=> vrátit 1 bajt)
+ utf8_charlen[i] = 1
+end
+for i = 192, 223, 1 do
+ utf8_charlen[i] = 2
+end
+for i = 224, 239, 1 do
+ utf8_charlen[i] = 3
+end
+for i = 240, 247, 1 do
+ utf8_charlen[i] = 4
+end
+for i = 248, 255, 1 do
+ utf8_charlen[i] = 1 -- neplatné UTF-8
+end
+
+local utf8_sort_data_1 = {
+ ["\x20"] = "\x20", -- < >
+ ["\x21"] = "\x21", -- <!>
+ ["\x22"] = "\x22", -- <">
+ ["\x23"] = "\x23", -- <#>
+ ["\x25"] = "\x24", -- <%>
+ ["\x26"] = "\x25", -- <&>
+ ["\x27"] = "\x26", -- <'>
+ ["\x28"] = "\x27", -- <(>
+ ["\x29"] = "\x28", -- <)>
+ ["\x2a"] = "\x29", -- <*>
+ ["\x2b"] = "\x2a", -- <+>
+ ["\x2c"] = "\x2b", -- <,>
+ ["\x2d"] = "\x2c", -- <->
+ ["\x2e"] = "\x2d", -- <.>
+ ["\x2f"] = "\x2e", -- </>
+ ["\x3a"] = "\x2f", -- <:>
+ ["\x3b"] = "\x30", -- <;>
+ ["\x3c"] = "\x31", -- <<>
+ ["\x3d"] = "\x32", -- <=>
+ ["\x3e"] = "\x33", -- <>>
+ ["\x3f"] = "\x34", -- <?>
+ ["\x40"] = "\x35", -- <@>
+ ["\x5b"] = "\x36", -- <[>
+ ["\x5c"] = "\x37", -- <\>
+ ["\x5d"] = "\x38", -- <]>
+ ["\x5e"] = "\x39", -- <^>
+ ["\x5f"] = "\x3a", -- <_>
+ ["\x60"] = "\x3b", -- <`>
+ ["\x7b"] = "\x3c", -- <{>
+ ["\x7c"] = "\x3d", -- <|>
+ ["\x7d"] = "\x3e", -- <}>
+ ["\x7e"] = "\x3f", -- <~>
+ ["\x24"] = "\x40", -- <$>
+ ["\x61"] = "\x41", -- <a>
+ ["\x41"] = "\x42", -- <A>
+ ["\x62"] = "\x47", -- <b>
+ ["\x42"] = "\x48", -- <B>
+ ["\x64"] = "\x4d", -- <d>
+ ["\x44"] = "\x4e", -- <D>
+ ["\x65"] = "\x51", -- <e>
+ ["\x45"] = "\x52", -- <E>
+ ["\x66"] = "\x57", -- <f>
+ ["\x46"] = "\x58", -- <F>
+ ["\x67"] = "\x59", -- <g>
+ ["\x47"] = "\x5a", -- <G>
+ ["\x68"] = "\x5b", -- <h>
+ ["\x48"] = "\x5c", -- <H>
+ ["\x69"] = "\x61", -- <i>
+ ["\x49"] = "\x62", -- <I>
+ ["\x6a"] = "\x65", -- <j>
+ ["\x4a"] = "\x66", -- <J>
+ ["\x6b"] = "\x67", -- <k>
+ ["\x4b"] = "\x68", -- <K>
+ ["\x6c"] = "\x69", -- <l>
+ ["\x4c"] = "\x6a", -- <L>
+ ["\x6d"] = "\x6f", -- <m>
+ ["\x4d"] = "\x70", -- <M>
+ ["\x6e"] = "\x71", -- <n>
+ ["\x4e"] = "\x72", -- <N>
+ ["\x6f"] = "\x75", -- <o>
+ ["\x4f"] = "\x76", -- <O>
+ ["\x70"] = "\x7b", -- <p>
+ ["\x50"] = "\x7c", -- <P>
+ ["\x71"] = "\x7d", -- <q>
+ ["\x51"] = "\x7e", -- <Q>
+ ["\x72"] = "\x7f", -- <r>
+ ["\x52"] = "\x80", -- <R>
+ ["\x73"] = "\x85", -- <s>
+ ["\x53"] = "\x86", -- <S>
+ ["\x74"] = "\x89", -- <t>
+ ["\x54"] = "\x8a", -- <T>
+ ["\x75"] = "\x8d", -- <u>
+ ["\x55"] = "\x8e", -- <U>
+ ["\x76"] = "\x93", -- <v>
+ ["\x56"] = "\x94", -- <V>
+ ["\x77"] = "\x95", -- <w>
+ ["\x57"] = "\x96", -- <W>
+ ["\x78"] = "\x97", -- <x>
+ ["\x58"] = "\x98", -- <X>
+ ["\x79"] = "\x99", -- <y>
+ ["\x59"] = "\x9a", -- <Y>
+ ["\x7a"] = "\x9d", -- <z>
+ ["\x5a"] = "\x9e", -- <Z>
+ ["\x30"] = "\xa1", -- <0>
+ ["\x31"] = "\xa2", -- <1>
+ ["\x32"] = "\xa3", -- <2>
+ ["\x33"] = "\xa4", -- <3>
+ ["\x34"] = "\xa5", -- <4>
+ ["\x35"] = "\xa6", -- <5>
+ ["\x36"] = "\xa7", -- <6>
+ ["\x37"] = "\xa8", -- <7>
+ ["\x38"] = "\xa9", -- <8>
+ ["\x39"] = "\xaa", -- <9>
+}
+
+local utf8_sort_data_2 = {
+ ["\xc3\xa1"] = "\x43", -- <á>
+ ["\xc3\x81"] = "\x44", -- <Á>
+ ["\xc3\xa4"] = "\x45", -- <ä>
+ ["\xc3\x84"] = "\x46", -- <Ä>
+ ["\xc4\x8d"] = "\x4b", -- <č>
+ ["\xc4\x8c"] = "\x4c", -- <Č>
+ ["\xc4\x8f"] = "\x4f", -- <ď>
+ ["\xc4\x8e"] = "\x50", -- <Ď>
+ ["\xc3\xa9"] = "\x53", -- <é>
+ ["\xc3\x89"] = "\x54", -- <É>
+ ["\xc4\x9b"] = "\x55", -- <ě>
+ ["\xc4\x9a"] = "\x56", -- <Ě>
+ ["\x63\x68"] = "\x5d", -- <ch>
+ ["\x63\x48"] = "\x5e", -- <cH>
+ ["\x43\x68"] = "\x5f", -- <Ch>
+ ["\x43\x48"] = "\x60", -- <CH>
+ ["\xc3\xad"] = "\x63", -- <í>
+ ["\xc3\x8d"] = "\x64", -- <Í>
+ ["\xc4\xba"] = "\x6b", -- <ĺ>
+ ["\xc4\xb9"] = "\x6c", -- <Ĺ>
+ ["\xc4\xbe"] = "\x6d", -- <ľ>
+ ["\xc4\xbd"] = "\x6e", -- <Ľ>
+ ["\xc5\x88"] = "\x73", -- <ň>
+ ["\xc5\x87"] = "\x74", -- <Ň>
+ ["\xc3\xb3"] = "\x77", -- <ó>
+ ["\xc3\x93"] = "\x78", -- <Ó>
+ ["\xc3\xb4"] = "\x79", -- <ô>
+ ["\xc3\x94"] = "\x7a", -- <Ô>
+ ["\xc5\x95"] = "\x81", -- <ŕ>
+ ["\xc5\x94"] = "\x82", -- <Ŕ>
+ ["\xc5\x99"] = "\x83", -- <ř>
+ ["\xc5\x98"] = "\x84", -- <Ř>
+ ["\xc5\xa1"] = "\x87", -- <š>
+ ["\xc5\xa0"] = "\x88", -- <Š>
+ ["\xc5\xa5"] = "\x8b", -- <ť>
+ ["\xc5\xa4"] = "\x8c", -- <Ť>
+ ["\xc3\xba"] = "\x8f", -- <ú>
+ ["\xc3\x9a"] = "\x90", -- <Ú>
+ ["\xc5\xaf"] = "\x91", -- <ů>
+ ["\xc5\xae"] = "\x92", -- <Ů>
+ ["\xc3\xbd"] = "\x9b", -- <ý>
+ ["\xc3\x9d"] = "\x9c", -- <Ý>
+ ["\xc5\xbe"] = "\x9f", -- <ž>
+ ["\xc5\xbd"] = "\xa0", -- <Ž>
+}
+
+local utf8_sort_data_3 = {
+ ["\x63"] = "\x49", -- <c>
+ ["\x43"] = "\x4a", -- <C>
+}
+
+local entity_properties_list = {
+ "hp_max",
+ "breath_max",
+ "zoom_fov",
+ "eye_height",
+ "physical",
+ "collide_with_objects",
+ "collisionbox",
+ "selectionbox",
+ "pointable",
+ "visual",
+ "visual_size",
+ "mesh",
+ "textures",
+ "colors",
+ "use_texture_alpha",
+ "spritediv",
+ "initial_sprite_basepos",
+ "is_visible",
+ "makes_footstep_sound",
+ "automatic_rotate",
+ "stepheight",
+ "automatic_face_movement_dir",
+ "automatic_face_movement_max_rotation_per_sec",
+ "backface_culling",
+ "glow",
+ "nametag",
+ "nametag_color",
+ "nametag_bgcolor",
+ "infotext",
+ "static_save",
+ "damage_texture_modifier",
+ "shaded",
+ "show_on_minimap",
+}
+
+local player_role_to_image = {
+ admin = "ch_core_koruna.png",
+ creative = "ch_core_kouzhul.png",
+ new = "ch_core_slunce.png",
+ none = "ch_core_empty.png",
+ survival = "ch_core_kladivo.png",
+}
+
+local wm_to_4dir = {
+ -- směr "wallmounted" na "4dir" (ztrátová konverze)
+ [0] = 0, [1] = 0, [2] = 1, [3] = 3, [4] = 0, [5] = 2, [6] = 0, [7] = 0,
+}
+local wmc_to_4dirc = {
+ [0] = 0,
+ [8] = 8,
+ [16] = 12,
+ [24] = 20,
+ [32] = 28,
+ [40] = 84,
+ [48] = 76,
+ [56] = 64,
+ [64] = 128,
+ [72] = 132,
+ [80] = 136,
+ [88] = 140,
+ [96] = 144,
+ [104] = 148,
+ [112] = 152,
+ [120] = 156,
+ [128] = 192,
+ [136] = 196,
+ [144] = 200,
+ [152] = 204,
+ [160] = 208,
+ [168] = 212,
+ [176] = 216,
+ [184] = 220,
+ [192] = 224,
+ [200] = 228,
+ [208] = 232,
+ [216] = 236,
+ [224] = 240,
+ [232] = 244,
+ [240] = 248,
+ [248] = 252,
+}
+
+-- KEŠ
+-- ===========================================================================
+local utf8_sort_cache = {
+}
+
+-- LOKÁLNÍ FUNKCE
+-- ===========================================================================
+local function cmp_oci(a, b)
+ return (ch_data.offline_charinfo[a].last_login or -1) < (ch_data.offline_charinfo[b].last_login or -1)
+end
+
+local function get_player_role_by_privs(privs)
+ if privs.protection_bypass then
+ return "admin"
+ elseif not privs.ch_registered_player then
+ return "new"
+ elseif privs.give then
+ return "creative"
+ else
+ return "survival"
+ end
+end
+
+-- VEŘEJNÉ FUNKCE
+-- ===========================================================================
+
+--[[
+Přidá nástroji opotřebení, pokud „player“ nemá právo usnadnění hry nebo není nil.
+]]
+function ch_core.add_wear(player, itemstack, wear_to_add)
+ local player_name = player and player.get_player_name and player:get_player_name()
+ if not player_name or not minetest.is_creative_enabled(player_name) then
+ local new_wear = itemstack:get_wear() + wear_to_add
+ if new_wear > 65535 then
+ itemstack:clear()
+ elseif new_wear >= 0 then
+ itemstack:set_wear(new_wear)
+ else
+ itemstack:set_wear(0)
+ end
+ end
+ return itemstack
+end
+
+--[[
+ Sestaví nové pole "groups" následujícím způsobem:
+ Vezme dvojice z "base"; přepíše je dvojicemi z "inherit" (případně pouze dvojicemi vybranými pomocí "inherit_list")
+ a nakonec výsledek přepíše dvojicemi z "override". Přitom dodržuje, že pokud nějaké dvojici vyjde hodnota 0,
+ tato dvojice se na výstupu vůbec neobjeví. Kterýkoliv parametr může být nil; u parametrů base, inherit a override
+ se to interpretuje jako prázdná tabulka. Je-li inherit_list == nil, pak se z "inherit" vezmou všechny dvojice.
+
+ base = table or nil, -- základ, ze kterého se vychází (dvojice s nejnižší prioritou)
+ override = table or nil, -- dvojice s nejvyšší prioritou (přepíšou vše ostatní)
+ inherit = table or nil, -- přidat/přepsat tyto hodnoty do base
+ inherit_list = {string, ...} or nil -- je-li nastaveno, pak se z inherit budou brát pouze klíče z tohoto seznamu a v uvedeném pořadí, jinak všechny klíče
+]]
+function ch_core.assembly_groups(default, override, inherit, inherit_list)
+ local result = {}
+ if default ~= nil then
+ for k, v in pairs(default) do
+ if v ~= 0 then
+ result[k] = v
+ -- result je prázdný, takže zde není třeba nastavovat nil
+ end
+ end
+ end
+ if inherit ~= nil then
+ if inherit_list ~= nil then
+ for _, group in ipairs(inherit_list) do
+ local value = inherit[group]
+ if value ~= nil then
+ if value ~= 0 then
+ result[group] = value
+ else
+ result[group] = nil
+ end
+ end
+ end
+ else
+ for group, value in pairs(inherit) do
+ if value ~= 0 then
+ result[group] = value
+ else
+ result[group] = nil
+ end
+ end
+ end
+ end
+ if override ~= nil then
+ for group, value in pairs(override) do
+ if value ~= 0 then
+ result[group] = value
+ else
+ result[group] = nil
+ end
+ end
+ end
+ return result
+end
+
+--[[
+Ověří, že předaný argument je funkce a zavolá ji s ostatními zadanými argumenty.
+Vrátí její výsledek. Není-li předaný argument funkce, nevrátí nic.
+]]
+function ch_core.call(f, ...)
+ if type(f) == "function" then
+ return f(...)
+ end
+end
+
+--[[
+Určí typ postavy (admin|creative|new|survival) a zkontroluje,
+zda je to jeden z akceptovaných.
+player_or_player_name může být PlayerRef nebo přihlašovací jméno postavy.
+accepted_roles může být buď řetězec nebo seznam řetězců.
+]]
+function ch_core.check_player_role(player_or_player_name, accepted_roles)
+ local role = ch_core.get_player_role(player_or_player_name)
+ if role == nil then
+ return nil
+ end
+ if type(accepted_roles) == "string" then
+ return role == accepted_roles
+ end
+ for _, r in ipairs(accepted_roles) do
+ if role == r then
+ return true
+ end
+ end
+ return false
+end
+
+--[[
+Smaže recepty jako minetest.clear_craft(), ale s lepším logováním.
+]]
+function ch_core.clear_crafts(log_prefix, crafts)
+ if log_prefix == nil then
+ log_prefix = ""
+ else
+ log_prefix = log_prefix.."/"
+ end
+ local get_us_time = minetest.get_us_time
+ local count = 0
+ for k, v in pairs(crafts) do
+ -- minetest.log("action", "Will clear craft "..log_prefix..k)
+ -- print("CLEAR_CRAFTS("..log_prefix.."): "..dump2(crafts))
+ if v.output ~= nil or v.type == "fuel" then
+ if minetest.clear_craft(v) then
+ count = count + 1
+ else
+ minetest.log("warning", "Craft "..log_prefix..k.." not cleared! Dump = "..dump2(v))
+ end
+ else
+ local start = get_us_time()
+ if minetest.clear_craft(v) then
+ count = count + 1
+ local stop = get_us_time()
+ minetest.log("action", "Craft "..log_prefix..k.." cleared in "..((stop - start) / 1000.0).." ms. Dump = "..dump2(v))
+ else
+ minetest.log("warning", "Craft "..log_prefix..k.." not cleared! Dump = "..dump2(v))
+ end
+ end
+ end
+ return count
+end
+
+--[[
+Převede param2 z colorwallmounted na color4dir paletu.
+]]
+function ch_core.colorwallmounted_to_color4dir(param2)
+ local dir = wm_to_4dir[param2 % 8]
+ param2 = param2 - param2 % 8
+ local color = wmc_to_4dirc[param2 - param2 % 8]
+ return dir + color
+end
+
+--[[
+Jako vstup přijímá pole dávek (např. z funkce InvRef:get_list())
+a vrátí počet prázdných dávek v něm (může být 0).
+]]
+function ch_core.count_empty_stacks(stacks)
+ local count = 0
+ for _, stack in ipairs(stacks) do
+ if stack:is_empty() then
+ count = count + 1
+ end
+ end
+ return count
+end
+
+--[[
+Serializuje pole dávek do řetězce.
+Vrací tabulku:
+{
+ success = bool, -- true v případě úspěchu, false v případě selhání
+
+ -- v případě úspěchu:
+ result = string, -- výsledný řetězec (pro prázdný inventář je to prázdný řetězec)
+ lengths = {int, ...}, -- délky itemstringů jednotlivých stacků
+ orig_result_length = int, -- délka výsledného řetězce před kompresí
+
+ -- v případě selhání:
+ reason = "single_stack_limit" or "disallow_nested", -- typ selhání
+ overlimit_index = int or nil, -- v případě selhání "single_stack_limit" index dávky, která překročila limit
+ overlimit_length = int or nil, -- v případě selhání "single_stack_limit" délku řetězce vráceného to_string()
+ nested_index = int or nil, -- v případě selhání "disallow_nested" index (první) dávky, která obsahuje vnořený inventář
+}
+]]
+function ch_core.serialize_stacklist(stacks, single_stack_limit, disallow_nested)
+ if single_stack_limit == nil then
+ single_stack_limit = 65535
+ end
+ local data = {}
+ local lengths = {}
+ for i, stack in ipairs(stacks) do
+ if stack:is_empty(stack) then
+ data[i] = ""
+ lengths[i] = 0
+ else
+ if disallow_nested then
+ -- does not contain a nested inventory?
+ local itemdef = minetest.registered_items[stack:get_name()]
+ if itemdef ~= nil and itemdef._ch_nested_inventory_meta ~= nil and stack:get_meta():get_string(itemdef._ch_nested_inventory_meta) ~= "" then
+ minetest.log("action", "Stacklist not serialized because of a nested inventory in "..stack:get_name()..".")
+ return {
+ success = false,
+ reason = "disallow_nested",
+ nested_index = i,
+ }
+ end
+ end
+ local s = stack:to_string()
+ if #s > single_stack_limit then
+ minetest.log("action", "Stacklist not serialized because of a single stack limit: "..#s.." > "..single_stack_limit..".")
+ return {
+ success = false,
+ reason = "single_stack_limit",
+ overlimit_index = i,
+ overlimit_length = #s,
+ }
+ end
+ data[i] = s
+ lengths[i] = #s
+ end
+ end
+ local orig_str = minetest.serialize(data)
+ local str = minetest.encode_base64(minetest.compress(orig_str, "deflate"))
+ minetest.log("action", "Stacklist serialized, resulting length = "..#str..".")
+ return {
+ success = true,
+ result = str,
+ lengths = lengths,
+ orig_result_length = #orig_str,
+ }
+end
+
+--[[
+Deserializuje řetězec serializovaný pomocí funkce ch_core.serialize_stacklist().
+V případě neúspěchu vrátí nil.
+]]
+function ch_core.deserialize_stacklist(str)
+ local result = minetest.deserialize(minetest.decompress(minetest.decode_base64(str), "deflate"))
+ if result ~= nil then
+ for i = 1, #result do
+ result[i] = ItemStack(result[i])
+ end
+ end
+ return result
+end
+
+--[[
+Spočítá a / b a vrátí celočíselný výsledek a zbytek.
+]]
+function ch_core.divrem(a, b)
+ local div = math.floor(a / b)
+ local rem = a % b
+ return div, rem
+end
+
+--[[
+ Vrátí funkci, která přijme jako první parametr název souboru (či cestu) a pokusí
+ se ho načíst a spustit jako funkci Lua a vrátit výsledky.
+ args = table || nil, -- pole parametrů, které mají být předávány volanému souboru,
+ pokud nejsou žádné parametry specifikovány v rámci volání; žádný z parametrů nesmí být nil!
+ options = nil || {
+ path = string || bool || nil,
+ -- je-li nil nebo true, funkce se pokusí první parametr doplnit o cestu módu, který byl načítán v momentě její konstrukce
+ -- je-li false, funkce se pokusí použít cestu tak, jak je
+ -- je-li string, daný řetězec se připojí před zadaný parametr a oddělí "/"
+ nofail = bool || nil, -- je-li true, funkce bude tiše ignorovat selhání při načítání souboru
+ }
+]]
+function ch_core.compile_dofile(args, options)
+ if args == nil then args = {} end
+ if options == nil then options = {} end
+ local modname = minetest.get_current_modname()
+ local modpath = modname and minetest.get_modpath(modname)
+ local result = function(name, ...)
+ local filepath
+ assert(name)
+ if options.path == nil or options.path == true then
+ if modpath == nil then
+ error("no mod is loading now!")
+ end
+ filepath = modpath.."/"..name
+ elseif options.path == false then
+ filepath = name
+ else
+ filepath = options.path.."/"..name
+ end
+ local largs = {...}
+ if #largs == 0 then
+ largs = args
+ end
+ local f, errmsg = loadfile(filepath)
+ if f ~= nil then
+ return f(unpack(largs))
+ elseif options.nofail == true then
+ return
+ else
+ error("dofile("..filepath..") failed!: "..(errmsg or "nil"))
+ end
+ end
+ return result
+end
+
+function ch_core.extend_player_inventory(player_name, extend)
+ local offline_charinfo = ch_data.offline_charinfo[player_name]
+ if offline_charinfo == nil then
+ minetest.log("error", "ch_core.extend_player_inventory() called on player "..player_name.." that has no offline_charinfo!")
+ return false
+ end
+ local player = minetest.get_player_by_name(player_name)
+ local target_size
+ if extend then
+ -- extend the inventory
+ target_size = ch_core.inventory_size.extended
+ if player ~= nil then
+ local inv = player:get_inventory()
+ local inv_size = inv:get_size("main")
+ if inv_size < target_size then
+ inv:set_size("main", target_size)
+ minetest.log("action", "Player inventory "..player_name.."/main was extended from "..inv_size.." slots to "..target_size..".")
+ end
+ end
+ if offline_charinfo.extended_inventory ~= 1 then
+ offline_charinfo.extended_inventory = 1
+ ch_data.save_offline_charinfo(player_name)
+ end
+
+ elseif not extend and offline_charinfo.extended_inventory == 1 then
+ -- shrink the inventory
+ target_size = ch_core.inventory_size.normal
+ if player ~= nil then
+ local inv = player:get_inventory()
+ local inv_size = inv:get_size("main")
+ if inv_size < target_size then
+ inv:set_size("main", target_size)
+ minetest.log("action", "Player inventory "..player_name.."/main was extended from "..inv_size.." slots to "..target_size..".")
+ elseif inv_size > target_size then
+ local current_items = inv:get_list("main")
+ local overflown_stacks = {}
+ inv:set_size("main", target_size)
+ for i = target_size + 1, inv_size do
+ local stack = current_items[i]
+ if not stack:is_empty() then
+ stack = inv:add_item("main", stack)
+ if not stack:is_empty() then
+ table.insert(overflown_stacks, stack)
+ end
+ end
+ end
+ minetest.log("action", "Player inventory "..player_name.."/main was shrinked from "..inv_size.." slots to "..target_size..". "..#overflown_stacks.." overflown stacks.")
+ if #overflown_stacks > 0 then
+ local player_pos = assert(player:get_pos())
+ for _, stack in ipairs(overflown_stacks) do
+ if core.add_item(player_pos, stack) == nil then
+ minetest.log("error", "Spawning overflown item "..stack:to_string().." failed! The item is lost.")
+ end
+ end
+ end
+ end
+ end
+ if offline_charinfo.extended_inventory == 1 then
+ offline_charinfo.extended_inventory = 0
+ ch_data.save_offline_charinfo(player_name)
+ end
+ else
+ -- no change
+ return
+ end
+end
+
+--[[
+Pro zadanou hodnotu facedir vrátí rotační vektor symbolizující rotaci
+z výchozího otočení (facedir = 0) do otočení cílového.
+]]
+function ch_core.facedir_to_rotation(facedir)
+ return vector.copy(facedir_to_rotation_data[facedir % 24])
+end
+
+--[[
+V zadaném textu odzvláštní všechny znaky, které mají speciální význam
+uvnitř prvku hypetext[] ve formspecu. Tato funkce již v sobě zahrnuje
+funkci minetest.formspec_escape, takže její obsah by už měl být doslovně
+vložen do formspecu bez dalšího zpracování.
+]]
+function ch_core.formspec_hypertext_escape(text)
+ local result = string.gsub(text, "[][><\\,;]", hypertext_escape_replacements)
+ return result
+end
+
+--[[
+Vrátí seznam všech známých hráčských postav (včetně těch, které nejsou ve hře).
+Ke každé postavě vrátí strukturu {prihlasovaci, zobrazovací}.
+Seznam je seřazený podle zobrazovacího jména postavy.
+-- as_map - je-li true, vrátí seznam (neseřazený) jako mapu z přihlašovacího jména postavy
+-- include_privs - je-li true, každý záznam bude navíc obsahovat položky 'role' a 'privs';
+ tuto variantu nelze volat z inicializačního kódu
+]]
+function ch_core.get_all_players(as_map, include_privs)
+ if include_privs and not minetest.get_gametime() then
+ error("ch_core.get_all_players(): include_privs == true is allowed only when the game is running!")
+ end
+ local list, map = {}, {}
+ for prihlasovaci, _ in pairs(ch_data.offline_charinfo) do
+ local exists = (not include_privs) or minetest.player_exists(prihlasovaci)
+ if exists then
+ local record = {
+ prihlasovaci = prihlasovaci,
+ zobrazovaci = ch_core.prihlasovaci_na_zobrazovaci(prihlasovaci),
+ }
+ if include_privs then
+ record.privs = minetest.get_player_privs(prihlasovaci)
+ record.role = get_player_role_by_privs(record.privs)
+ end
+ table.insert(list, record)
+ map[prihlasovaci] = record
+ end
+ end
+ if as_map then
+ return map
+ end
+ table.sort(list, function(a, b)
+ return ch_core.utf8_mensi_nez(a.zobrazovaci, b.zobrazovaci, true)
+ end)
+ return list
+end
+
+--[[
+Vrátí seznam všech/jen registrovaných postav, seřazený podle času posledního přihlášení,
+od nejčerstvějšího po nejstarší.
+
+registered_only - je-li true, počítá pouze registrované postavy
+name_to_skip - je-li nastaveno, postava s daným přihl. jménem se nepočítá
+
+Výsledkem je seznam struktur ve formátu:
+ {
+ player_name = STRING, -- přihl. jméno postavy
+ last_login_before = INT, -- před kolika (kalendářními) dny se postava přihlásila; -1, není-li k dispozici
+ played_hours_total = FLOAT, -- hodiny ve hře
+ played_hours_actively = FLOAT, -- aktivně odehrané hodiny ve hře
+ is_in_game = BOOL, -- je postava aktuálně ve hře?
+ pending_registration_type = STRING or nil,
+ is_registered = BOOL,
+ }
+]]
+function ch_core.get_last_logins(registered_only, names_to_skip)
+ local new_players = {} -- new players
+ local reg_players = {} -- registered players
+ local shifted_eod = os.time() - 946684800 -- EOD = end of day
+ shifted_eod = shifted_eod + 86400 - (shifted_eod % 86400)
+
+ if names_to_skip == nil then
+ names_to_skip = {}
+ elseif type(names_to_skip) == "string" then
+ names_to_skip = {[names_to_skip] = true}
+ elseif type(names_to_skip) ~= "table" then
+ error("names_to_skip: invalid type of argument!")
+ end
+
+ for other_player_name, _ in pairs(ch_data.offline_charinfo) do
+ if not names_to_skip[other_player_name] then
+ if minetest.check_player_privs(other_player_name, "ch_registered_player") then
+ table.insert(reg_players, other_player_name)
+ elseif not registered_only then
+ table.insert(new_players, other_player_name)
+ end
+ end
+ end
+ if registered_only then
+ new_players = reg_players
+ table.sort(new_players, cmp_oci)
+ else
+ table.sort(new_players, cmp_oci)
+ table.sort(reg_players, cmp_oci)
+ table.insert_all(new_players, reg_players)
+ end
+ local result = {}
+ for i = #new_players, 1, -1 do
+ local other_player_name = new_players[i]
+ local offline_charinfo = ch_data.offline_charinfo[other_player_name]
+ local info = {
+ player_name = other_player_name
+ }
+ local last_login = offline_charinfo.last_login
+ if last_login == 0 then
+ info.last_login_before = -1
+ else
+ info.last_login_before = math.floor((shifted_eod - last_login) / 86400)
+ end
+ info.played_hours_total = math.round(offline_charinfo.past_playtime / 36) / 100
+ info.played_hours_actively = math.round(offline_charinfo.past_ap_playtime / 36) / 100
+ info.is_in_game = ch_data.online_charinfo[other_player_name] ~= nil
+ if (offline_charinfo.pending_registration_type or "") ~= "" then
+ info.pending_registration_type = offline_charinfo.pending_registration_type or ""
+ end
+ if minetest.check_player_privs(other_player_name, "ch_registered_player") then
+ info.is_registered = true
+ else
+ info.is_registered = false
+ end
+ table.insert(result, info)
+ end
+ return result
+end
+
+--[[
+Najde hráčskou postavu nejbližší k dané pozici. Parametr player_name_to_ignore
+je volitelný; je-li vyplněn, má obsahovat přihlašovací jméno postavy
+k ignorování.
+
+Vrací „player“ a „player:get_pos()“; v případě neúspěchu vrací nil.
+]]
+local get_connected_players = minetest.get_connected_players
+function ch_core.get_nearest_player(pos, player_name_to_ignore)
+ local result_player, result_pos, result_distance_2 = 1e+20
+ for player_name, player in pairs(get_connected_players()) do
+ if player_name ~= player_name_to_ignore then
+ local player_pos = player:get_pos()
+ local x, y, z = player_pos.x - pos.x, player_pos.y - pos.y, player_pos.z - pos.z
+ local distance_2 = x * x + y * y + z * z
+ if distance_2 < result_distance_2 then
+ result_distance_2 = distance_2
+ result_player = player
+ result_pos = player_pos
+ end
+ end
+ end
+ return result_player, result_pos
+end
+
+--[[
+Vrátí t[k]. Pokud je to nil, přiřadí tam prázdnou tabulku {} a vrátí tu.
+]]
+function ch_core.get_or_add(t, k)
+ local result = t[k]
+ if result == nil then
+ result = {}
+ t[k] = result
+ end
+ return result
+end
+
+--[[
+Načte z metadat pozici uloženou pomocí ch_core.set_pos_to_meta().
+Není-li tam taková uložena, vrátí vector.zero().
+]]
+function ch_core.get_pos_from_meta(meta, key)
+ return vector.new(meta:get_float(key.."_x"), meta:get_float(key.."_y"), meta:get_float(key.."_z"))
+end
+
+--[[
+Určí podle práv typ postavy (admin|creative|new|survival).
+Pokud player_or_player_name není PlayerRef nebo jméno postavy, vrátí nil.
+]]
+function ch_core.get_player_role(player_or_player_name)
+ local result = ch_core.normalize_player(player_or_player_name).role
+ if result ~= "none" then
+ return result
+ else
+ return nil
+ end
+end
+
+--[[
+ Vrátí seznam hráčských postav (ObjectRef) ve vymezené oblasti.
+ Jde o bezprostřední náhradu za core.get_objects_inside_radius().
+]]
+function ch_core.get_players_inside_radius(center, radius)
+ local result = {}
+ for _, player in ipairs(core.get_connected_players()) do
+ if vector.distance(center, player:get_pos()) <= radius then
+ table.insert(result, player)
+ end
+ end
+ return result
+end
+
+--[[
+Vygeneruje šablonu pro stránku formspecu pro unified_inventory.
+id -- string, required -- rozlišující textové ID pro připojení za prvky ch_scrollbar[12]_
+player_viewname -- string, optional -- jméno postavy pro zobrazení v záhlaví (může být barevné)
+title -- string, optional -- nadpis pro zobrazení v záhlaví
+scrollbars -- table, required -- definuje maxima pro posuvníky oblastí a současně také rozložení oblastí;
+ tato verze podporuje jen rozložení {left = ..., right = ...} a {top = ..., bottom = ...}
+perplayer_formspec -- odpovídající parametr z rozhraní unified_inventory; definuje rozložení formuláře
+online_charinfo -- table, optional -- je-li zadáno, stavy posuvníků se nastaví podle stejně pojmenovaných polí v dané tabulce, budou-li přítomna
+
+Výstup má formát:
+ {
+ fs_begin, fs_middle, fs_end -- string; řetězce pro použití jako formspec; vlastní obsah vložte kolem fs_middle
+ form1, form2 = {x, y, w, h, key, scrollbar_max} -- udává pozice a velikost podformulářů v okně unified_inventory;
+ v praxi jsou podstatné především 'w' a 'h' (šířka a výška podoblasti)
+ }
+]]
+function ch_core.get_ui_form_template(id, player_viewname, title, scrollbars, perplayer_formspec, online_charinfo)
+ local fs = assert(perplayer_formspec)
+ local fs_begin, fs_middle, fs_end = {fs.standard_inv_bg}, {}, {}
+ local form1, form2 = {}, {}
+ local sbar_width = 0.5
+ local style
+
+ if scrollbars.left ~= nil and scrollbars.right ~= nil then
+ style = "left_right"
+ elseif scrollbars.top ~= nil and scrollbars.bottom ~= nil then
+ style = "top_bottom"
+ else
+ error("Unsupported UI formspec style!")
+ end
+
+ if style == "left_right" then
+ form1.x = fs.std_inv_x
+ form1.y = fs.form_header_y + 0.5
+ form1.w = 10.0
+ form1.h = fs.std_inv_y - fs.form_header_y - 1.25
+ form1.key = "left"
+ form1.scrollbar_max = scrollbars.left
+
+ form2.x = fs.page_x - 0.25
+ form2.y = 0.5
+ form2.w = fs.pagecols - 1
+ form2.h = fs.pagerows - 1 + fs.page_y
+ form2.key = "right"
+ form2.scrollbar_max = scrollbars.right
+ elseif style == "top_bottom" then
+ form1.x = fs.std_inv_x
+ form1.y = fs.form_header_y + 0.5
+ form1.w = 17.25
+ form1.h = fs.std_inv_y - fs.form_header_y - 1.25
+ form1.key = "top"
+ form1.scrollbar_max = scrollbars.top
+
+ form2.x = fs.page_x - 0.25
+ form2.y = fs.std_inv_y - 0.5
+ form2.w = fs.pagecols - 1
+ form2.h = 5.5 + 0.5
+ form2.scrollbar_max = scrollbars.bottom
+ else
+ error("not implemented yet")
+ end
+
+ if title ~= nil then
+ table.insert(fs_begin, "label["..(form1.x + 0.05)..","..(form1.y - 0.3)..";")
+ if player_viewname ~= nil then
+ table.insert(fs_begin, minetest.formspec_escape(ch_core.colors.light_green..player_viewname..ch_core.colors.white.." — "))
+ end
+ table.insert(fs_begin, minetest.formspec_escape(title))
+ table.insert(fs_begin, "]")
+ end
+
+ if (form1.scrollbar_max or 0) > 0 then
+ table.insert(fs_begin, "scroll_container["..form1.x..","..form1.y..";"..form1.w..","..form1.h..";ch_scrollbar1_"..id..";vertical]")
+ -- CONTENT will be inserted here
+ table.insert(fs_middle, "scroll_container_end[]")
+ -- insert a scrollbar
+ if (form1.scrollbar_max or 0) > 0 then
+ local scrollbar_state = online_charinfo ~= nil and online_charinfo["ch_scrollbar1_"..id]
+ if scrollbar_state ~= nil then
+ scrollbar_state = tostring(scrollbar_state)
+ else
+ scrollbar_state = ""
+ end
+ table.insert(fs_middle,
+ "scrollbaroptions[max="..form1.scrollbar_max..";arrows=show]"..
+ "scrollbar["..(form1.x + form1.w - sbar_width)..","..form1.y..";"..sbar_width..","..form1.h..";vertical;ch_scrollbar1_"..id..";"..
+ scrollbar_state.."]")
+ end
+ else
+ table.insert(fs_begin, "container["..form1.x..","..form1.y.."]")
+ -- CONTENT will be inserted here
+ table.insert(fs_middle, "container_end[]")
+ end
+
+ if (form2.scrollbar_max or 0) > 0 then
+ table.insert(fs_middle, "scroll_container["..form2.x..","..form2.y..";"..form2.w..","..form2.h..";ch_scrollbar2_"..id..";vertical]")
+ -- CONTENT will be inserted here
+ table.insert(fs_end, "scroll_container_end[]")
+ -- insert a scrollbar
+ if (form2.scrollbar_max or 0) > 0 then
+ local scrollbar_state = online_charinfo ~= nil and online_charinfo["ch_scrollbar2_"..id]
+ if scrollbar_state ~= nil then
+ scrollbar_state = tostring(scrollbar_state)
+ else
+ scrollbar_state = ""
+ end
+ table.insert(fs_end,
+ "scrollbaroptions[max="..form2.scrollbar_max..";arrows=show]"..
+ "scrollbar["..(form2.x + form2.w - sbar_width)..","..form2.y..";"..sbar_width..","..form2.h..";vertical;ch_scrollbar2_"..id..";"..
+ scrollbar_state.."]")
+ end
+ else
+ table.insert(fs_middle, "container["..form2.x..","..form2.y.."]")
+ -- CONTENT will be inserted here
+ table.insert(fs_end, "container_end[]")
+ end
+
+ return {
+ fs_begin = table.concat(fs_begin),
+ fs_middle = table.concat(fs_middle),
+ fs_end = table.concat(fs_end),
+ form1 = form1,
+ form2 = form2,
+ }
+end
+
+--[[
+ Vrátí t[k1]. Pokud je to nil, vyplní t[k1] = {} a vrátí přiřazenou tabulku.
+]]
+function ch_core.goa1(t, k)
+ local r = t[k]
+ if r ~= nil then return r end
+ r = {}
+ t[k] = r
+ return r
+end
+
+--[[
+ Vrátí t[k1][k2]. Pokud některá z položek chybí, vyplní ji novou prázdnou tabulkou.
+]]
+function ch_core.goa2(t, k1, k2)
+ local r1 = t[k1]
+ if r1 == nil then
+ r1 = {}
+ t[k1] = {[k2] = r1}
+ return r1
+ end
+ local r2 = r1[k2]
+ if r2 == nil then
+ r2 = {}
+ r1[k2] = r2
+ return r2
+ end
+ return r2
+end
+local goa2 = ch_core.goa2
+
+--[[
+ Vrátí t[k1][k2][k3]. Pokud některá z položek chybí, vyplní ji novou prázdnou tabulkou.
+]]
+function ch_core.goa3(t, k1, k2, k3)
+ local r = t[k1]
+ if r ~= nil then
+ return goa2(r, k2, k3)
+ else
+ r = {}
+ t[k1] = {[k2] = {[k3] = r}}
+ return r
+ end
+end
+
+--[[
+ Vrátí t[k1][k2][k3][k4]. Pokud některá z položek chybí, vyplní ji novou prázdnou tabulkou.
+]]
+function ch_core.goa4(t, k1, k2, k3, k4)
+ return goa2(goa2(t, k1, k2), k3, k4)
+end
+
+--[[
+Jednoduchá funkce, která vyhodnotí condition jako podmínku
+a podle výsledku vrátí buď true_result, nebo false_result.
+]]
+function ch_core.ifthenelse(condition, true_result, false_result)
+ if condition then
+ return true_result
+ else
+ return false_result
+ end
+end
+
+--[[
+ Vrací funkci function(itemstack, user, pointed_thing),
+ která zavolá do_item_eat() podle hodnoty skupiny ch_food,
+ drink nebo ch_poison u daného předmětu. Není-li předmět v těchto
+ skupinách, vrátí nil.
+]]
+local item_eat_cache = {}
+function ch_core.item_eat(replace_with_item)
+ if replace_with_item == nil then
+ replace_with_item = ""
+ elseif type(replace_with_item) ~= "string" then
+ error("replace_with_item must be string or nil")
+ end
+ local result = item_eat_cache[replace_with_item]
+ if result == nil then
+ result = function(itemstack, user, pointed_thing)
+ local name = itemstack:get_name()
+ if name == nil or name == "" or minetest.registered_items[name] == nil then return end
+ local food = minetest.get_item_group(name, "ch_food")
+ local drink = minetest.get_item_group(name, "drink")
+ local poison = minetest.get_item_group(name, "ch_poison")
+ local health
+ if poison ~= 0 then
+ local normal = math.max(food, drink)
+ if normal ~= 0 and math.random(5) ~= 3 then
+ health = normal
+ else
+ health = -poison
+ end
+ else
+ health = math.max(food, drink)
+ if health <= 0 then
+ return
+ end
+ end
+ return minetest.do_item_eat(health, replace_with_item, itemstack, user, pointed_thing)
+ end
+ item_eat_cache[replace_with_item] = result
+ end
+ return result
+end
+
+--[[
+ Vytvoří pomocnou strukturu pro položku dropdown[] ve formspecu.
+ Pomocná struktura obsahuje položky:
+ - function get_index_from_value(value, default_index)
+ - function get_value_from_index(index, default_index)
+ - table index_to_value // původní předaná tabulka (musí být sekvence)
+ - string formspec_list // již odzvláštněný seznam k použití ve formspecu
+ - table value_to_index // mapuje text položky na první odpovídající index
+]]
+function ch_core.make_dropdown(index_to_value)
+ local F = minetest.formspec_escape
+ local escaped_values = {}
+ local value_to_index = {}
+ for i, value in ipairs(index_to_value) do
+ escaped_values[i] = F(value)
+ end
+ for i = #index_to_value, 1, -1 do
+ value_to_index[index_to_value[i]] = i
+ end
+ return {
+ get_index_from_value = function(value, default_index)
+ if value ~= nil then
+ return value_to_index[value] or tonumber(default_index)
+ else
+ return tonumber(default_index)
+ end
+ end,
+ get_value_from_index = function(index, default_index)
+ index = tonumber(index)
+ if index ~= nil and index_to_value[index] ~= nil then
+ return index_to_value[index]
+ else
+ return index_to_value[default_index]
+ end
+ end,
+ index_to_value = index_to_value,
+ formspec_list = table.concat(escaped_values, ","),
+ value_to_index = value_to_index,
+ }
+end
+
+--[[
+Přijme parametr, kterým může být:
+ a) přihlašovací jméno postavy (bez ohledu na diakritiku)
+ b) zobrazovací jméno postavy
+ c) objekt postavy
+ d) tabulka s volatelnou funkcí get_player_name()
+Ve všech případech vrátí tabulku s prvky:
+{
+ role, -- role postavy nebo "none", pokud neexistuje
+ player_name, -- skutečné přihlašovací jméno existující postavy, nebo "", pokud postava neexistuje; nikdy nebude nil
+ viewname, -- zobrazovací jméno postavy (bez barev), nebo "", pokud postava neexistuje; nikdy nebude nil
+ player, -- je-li postava ve hře, PlayerRef, jinak nil
+ privs, -- tabulka práv postavy; pokud neexistuje, {}; nikdy nebude nil
+}
+]]
+function ch_core.normalize_player(player_name_or_player)
+ local arg_type = type(player_name_or_player)
+ local player_name, player
+ if arg_type == "string" then
+ player_name = player_name_or_player
+ elseif arg_type == "number" then
+ player_name = tostring(player_name_or_player)
+ elseif (arg_type == "table" or arg_type == "userdata") and type(player_name_or_player.get_player_name) == "function" then
+ player_name = player_name_or_player:get_player_name()
+ if type(player_name) ~= "string" then
+ player_name = ""
+ else
+ if minetest.is_player(player_name_or_player) then
+ player = player_name_or_player
+ end
+ end
+ else
+ player_name = ""
+ end
+ player_name = ch_core.jmeno_na_prihlasovaci(player_name)
+ local correct_name = ch_data.correct_player_name_casing(player_name)
+ if correct_name ~= nil then
+ player_name = correct_name
+ end
+ if player_name ~= "" and not minetest.player_exists(player_name) then
+ player_name = ch_core.jmeno_na_prihlasovaci(player_name)
+ if not minetest.player_exists(player_name) then
+ player_name = ""
+ end
+ end
+ if player_name == "" then
+ return {role = "none", player_name = "", viewname = "", privs = {}}
+ end
+ local privs = minetest.get_player_privs(player_name)
+ return {
+ role = get_player_role_by_privs(privs),
+ player_name = player_name,
+ viewname = ch_core.prihlasovaci_na_zobrazovaci(player_name),
+ privs = privs,
+ player = player or minetest.get_player_by_name(player_name),
+ }
+end
+
+--[[
+Vytvoří kopii vstupu (input) a zapíše do ní nové hodnoty skupin podle
+parametru override. Skupiny s hodnotou 0 v override z tabulky odstraní.
+Je-li některý z parametrů nil, je interpretován jako prázdná tabulka.
+
+ZASTARALÁ: použijte raději ch_core.assembly_groups().
+]]
+function ch_core.override_groups(input, override)
+ return ch_core.assembly_groups(input, override)
+end
+
+--[[
+Převede zobrazovací nebo přihlašovací jméno na přihlašovací jméno,
+bez ohledu na to, zda takové jméno existuje.
+]]
+function ch_core.jmeno_na_prihlasovaci(jmeno)
+ return ch_core.odstranit_diakritiku(jmeno):gsub(" ", "_")
+end
+
+--[[
+ Vrátí existující přihlašovací jméno postavy odpovídající uvedenému
+ jménu až na velikost písmen a diakritiku (+ konverzi ' ' na '_'), nebo nil, pokud
+ taková postava neexistuje.
+]]
+function ch_core.jmeno_na_existujici_prihlasovaci(jmeno)
+ if jmeno == nil then return nil end
+ local result = ch_core.odstranit_diakritiku(jmeno):gsub(" ", "_")
+ result = ch_data.correct_player_name_casing(result)
+ if result and ch_data.offline_charinfo[result] then
+ return result
+ else
+ return nil
+ end
+end
+
+--[[
+Převede všechna písmena v řetězci na malá, funguje i na písmena s diakritikou.
+]]
+function ch_core.na_mala_pismena(s)
+ local l = #s
+ local i = 1
+ local res = ""
+ local c
+ while i <= l do
+ c = diakritika_na_mala[s:sub(i, i + 1)]
+ if c then
+ res = res .. c
+ i = i + 2
+ else
+ res = res .. s:sub(i, i)
+ i = i + 1
+ end
+ end
+ return string.lower(res)
+end
+
+--[[
+Převede všechna písmena v řetězci na velká, funguje i na písmena s diakritikou.
+]]
+function ch_core.na_velka_pismena(s)
+ local l = #s
+ local i = 1
+ local res = ""
+ local c
+ while i <= l do
+ c = diakritika_na_velka[s:sub(i, i + 1)]
+ if c then
+ res = res .. c
+ i = i + 2
+ else
+ res = res .. s:sub(i, i)
+ i = i + 1
+ end
+ end
+ return string.upper(res)
+end
+
+--[[
+Vrátí počet bloků uvnitř oblasti vymezené dvěma krajními body (na pořadí nezáleží).
+Výsledkem je vždy kladné celé číslo.
+]]
+function ch_core.objem_oblasti(pos1, pos2)
+ return math.ceil(math.abs(pos1.x - pos2.x) + 1) * math.ceil(math.abs(pos1.y - pos2.y) + 1) * math.ceil(math.abs(pos1.z - pos2.z) + 1)
+end
+
+--[[
+Všechna písmena s diakritikou převede na odpovídající písmena bez diakritiky.
+Ostatní znaky ponechá.
+]]
+function ch_core.odstranit_diakritiku(s)
+ local l = #s
+ local i = 1
+ local res = ""
+ local c
+ while i <= l do
+ c = diakritika[s:sub(i, i + 1)]
+ if c then
+ res = res .. c
+ i = i + 2
+ else
+ res = res .. s:sub(i, i)
+ i = i + 1
+ end
+ end
+ return res
+end
+
+--[[
+Pokud je zadaný klient ve hře (musí jít o přihlašovací jméno), přehraje mu zvuk kliknutí.
+Parametr může být nil; v takovém případě neudělá nic.
+]]
+function ch_core.play_click_sound_to(player_name)
+ if player_name ~= nil then
+ minetest.sound_play(click_sound, {to_player = player_name}, true)
+ end
+end
+
+--[[
+K zadané roli postavy vrátí odpovídající ikonu.
+]]
+function ch_core.player_role_to_image(player_role, has_creative_priv, image_height)
+ if image_height ~= nil and image_height ~= 32 and image_height ~= 16 then
+ error("ch_core.player_role_to_image(): image height "..image_height.." is unsupported!")
+ end
+ local result = assert(player_role_to_image[player_role] or player_role_to_image["none"])
+ if player_role ~= "new" and has_creative_priv then
+ result = "[combine:48x32:0,0="..result..":16,0="..player_role_to_image.creative
+ if image_height == 16 then
+ result = result.."^[resize:24x16"
+ end
+ elseif image_height == 16 then
+ result = result.."^[resize:16x16"
+ end
+ return result
+end
+
+--[[
+Otestuje, zda pozice „pos“ leží uvnitř oblasti vymezené v_min a v_max.
+]]
+function ch_core.pos_in_area(pos, v_min, v_max)
+ return v_min.x <= pos.x and pos.x <= v_max.x and
+ v_min.y <= pos.y and pos.y <= v_max.y and
+ v_min.z <= pos.z and pos.z <= v_max.z
+end
+
+--[[
+vrátí dva vektory: první s minimálními souřadnicemi a druhý s maximálními,
+obojí zaokrouhlené na celočíselné souřadnice
+]]
+function ch_core.positions_to_area(v1, v2)
+ local x1, x2, y1, y2, z1, z2
+
+ if v1.x <= v2.x then
+ x1 = v1.x
+ x2 = v2.x
+ else
+ x1 = v2.x
+ x2 = v1.x
+ end
+
+ if v1.y <= v2.y then
+ y1 = v1.y
+ y2 = v2.y
+ else
+ y1 = v2.y
+ y2 = v1.y
+ end
+
+ if v1.z <= v2.z then
+ z1 = v1.z
+ z2 = v2.z
+ else
+ z1 = v2.z
+ z2 = v1.z
+ end
+
+ return vector.round(vector.new(x1, y1, z1)), vector.round(vector.new(x2, y2, z2))
+end
+
+--[[
+Pokud dané přihlašovací jméno existuje, převede ho na jméno bez barev (výchozí)
+nebo s barvami. Pro neexistující jména vrací zadaný řetězec.
+]]
+function ch_core.prihlasovaci_na_zobrazovaci(prihlasovaci, s_barvami)
+ local offline_info, jmeno
+ if not prihlasovaci then
+ error("ch_core.prihlasovaci_na_zobrazovaci() called with bad arguments!")
+ end
+ if minetest.player_exists(prihlasovaci) then
+ offline_info = ch_data.offline_charinfo[prihlasovaci] or {}
+ if s_barvami then
+ jmeno = offline_info.barevne_jmeno
+ if jmeno then return jmeno end
+ end
+ jmeno = offline_info.jmeno
+ if jmeno then return jmeno end
+ end
+ return prihlasovaci
+end
+
+--[[
+Zaregistruje bloky, které mají něco společného.
+]]
+function ch_core.register_nodes(common_def, nodes, crafts)
+ if type(common_def) ~= "table" then
+ error("common_def must be a table!")
+ end
+ if type(nodes) ~= "table" then
+ error("nodes must be a table!")
+ end
+ if crafts ~= nil and type(crafts) ~= "table" then
+ error("crafts must be a table or nil!")
+ end
+
+ for node_name, node_def in pairs(nodes) do
+ local def = table.copy(common_def)
+ for k, v in pairs(node_def) do
+ def[k] = v
+ end
+ minetest.register_node(node_name, def)
+ end
+
+ if crafts ~= nil then
+ for _, def in ipairs(crafts) do
+ minetest.register_craft(def)
+ end
+ end
+end
+
+--[[
+Smaže data týkající se dané postavy.
+]]
+function ch_core.remove_player(player_name, options)
+ if options == nil then
+ options = {}
+ end
+ player_name = ch_core.jmeno_na_prihlasovaci(player_name)
+ if not minetest.player_exists(player_name) then
+ return false, player_name.." neexistuje!"
+ end
+ local results = {player_name.." odstraněn/a:"}
+ -- remove player data
+ if options.player_data ~= false then
+ if minetest.remove_player(player_name) == 0 then
+ table.insert(results, "player_data")
+ end
+ end
+ -- remove offline charinfo
+ local f = ch_core.delete_offline_charinfo
+ if options.offline_charinfo ~= false and f ~= nil then
+ if f(player_name) then
+ table.insert(results, "offline_charinfo")
+ end
+ end
+ -- remove auth data
+ if options.player_auth ~= false then
+ if minetest.remove_player_auth(player_name) then
+ table.insert(results, "player_auth")
+ end
+ end
+ return true, table.concat(results, " ")
+end
+
+--[[
+Otočí axis-aligned bounding box o zadané otočení. Nejde-li o pravoúhlé
+otočení, výsledný kvádr bude větší. <zatím nezkoušeno>
+]]
+function ch_core.rotate_aabb(aabb, r)
+ local points = {}
+ for _, x in ipairs({r[1], r[4]}) do
+ for _, y in ipairs({r[2], r[5]}) do
+ for _, z in ipairs({r[3], r[6]}) do
+ table.insert(points, vector.rotate(vector.new(x, y, z), r))
+ end
+ end
+ end
+ local p = points[1]
+ local result = {p.x, p.y, p.z, p.x, p.y, p.z}
+ for i = 2, 8 do
+ p = points[i]
+ if p.x < result[1] then
+ result[1] = p.x
+ elseif p.x > result[4] then
+ result[4] = p.x
+ end
+ if p.y < result[2] then
+ result[2] = p.y
+ elseif p.y > result[5] then
+ result[5] = p.y
+ end
+ if p.z < result[3] then
+ result[3] = p.z
+ elseif p.z > result[6] then
+ result[6] = p.z
+ end
+ end
+ return result
+end
+
+--[[
+ Otočí axis-aligned bounding box takovým způsobem, jako zadaná facedir-hodnota 0 až 23
+ otočí blok s paramtype2 == "facedir". Vrátí nový aabb. V případě selhání vrátí nil.
+]]
+function ch_core.rotate_aabb_by_facedir(aabb, facedir)
+ if 0 <= facedir and facedir < 24 and facedir_to_rotation_data[facedir] then
+ local a = vector.new(aabb[1], aabb[2], aabb[3])
+ local b = vector.new(aabb[4], aabb[5], aabb[6])
+ local r = facedir_to_rotation_data[facedir]
+ a = vector.rotate(a, r)
+ b = vector.rotate(b, r)
+ return {
+ math.min(a.x, b.x), math.min(a.y, b.y), math.min(a.z, b.z),
+ math.max(a.x, b.x), math.max(a.y, b.y), math.max(a.z, b.z),
+ }
+ else
+ return nil
+ end
+end
+
+--[[
+Provede operaci t[k1][k2]... s tím, že pokud je kterýkoliv z parametrů nil
+nebo na nil po cestě narazí, vrátí nil. Číslo v názvu udává celkový počet
+parametrů (včetně t) a musí být v rozsahu 2 až 7 včetně.
+]]
+function ch_core.safe_get_2(t, k1)
+ if t and k1 ~= nil then return t[k1] end
+ return nil
+end
+function ch_core.safe_get_3(t, k1, k2)
+ local result
+ if t and k1 ~= nil and k2 ~= nil then
+ result = t[k1]
+ if result ~= nil then
+ return result[k2]
+ end
+ end
+ return nil
+end
+function ch_core.safe_get_4(t, k1, k2, k3)
+ local result
+ if t and k1 ~= nil and k2 ~= nil and k3 ~= nil then
+ result = t[k1]
+ if result ~= nil then
+ result = result[k2]
+ if result ~= nil then
+ return result[k3]
+ end
+ end
+ end
+ return nil
+end
+function ch_core.safe_get_5(t, k1, k2, k3, k4)
+ local result
+ if t and k1 ~= nil and k2 ~= nil and k3 ~= nil and k4 ~= nil then
+ result = t[k1]
+ if result ~= nil then
+ result = result[k2]
+ if result ~= nil then
+ result = result[k3]
+ if result ~= nil then
+ return result[k4]
+ end
+ end
+ end
+ end
+ return nil
+end
+function ch_core.safe_get_6(t, k1, k2, k3, k4, k5)
+ local result
+ if t and k1 ~= nil and k2 ~= nil and k3 ~= nil and k4 ~= nil and k5 ~= nil then
+ result = t[k1]
+ if result ~= nil then
+ result = result[k2]
+ if result ~= nil then
+ result = result[k3]
+ if result ~= nil then
+ result = result[k4]
+ if result ~= nil then
+ return result[k5]
+ end
+ end
+ end
+ end
+ end
+ return nil
+end
+function ch_core.safe_get_7(t, k1, k2, k3, k4, k5, k6)
+ local result
+ if t and k1 ~= nil and k2 ~= nil and k3 ~= nil and k4 ~= nil and k5 ~= nil and k6 ~= nil then
+ result = t[k1]
+ if result ~= nil then
+ result = result[k2]
+ if result ~= nil then
+ result = result[k3]
+ if result ~= nil then
+ result = result[k4]
+ if result ~= nil then
+ result = result[k5]
+ if result ~= nil then
+ return result[k6]
+ end
+ end
+ end
+ end
+ end
+ end
+ return nil
+end
+
+--[[
+Provede operaci t[k1][k2]... s tím, že pokud je kterýkoliv z parametrů nil
+nebo na nil po cestě narazí, vrátí nil. Číslo v názvu udává celkový počet
+parametrů (včetně t) a musí být v rozsahu 2 až 7 včetně.
+V této verzi knihovny neprovádí kontrolu, zda je t indexovatelné.
+]]
+function ch_core.safe_get_2(t, k1)
+ if t and k1 ~= nil then return t[k1] end
+ return nil
+end
+function ch_core.safe_get_3(t, k1, k2)
+ local result
+ if t and k1 ~= nil and k2 ~= nil then
+ result = t[k1]
+ if result ~= nil then
+ return result[k2]
+ end
+ end
+ return nil
+end
+function ch_core.safe_get_4(t, k1, k2, k3)
+ local result
+ if t and k1 ~= nil and k2 ~= nil and k3 ~= nil then
+ result = t[k1]
+ if result ~= nil then
+ result = result[k2]
+ if result ~= nil then
+ return result[k3]
+ end
+ end
+ end
+ return nil
+end
+function ch_core.safe_get_5(t, k1, k2, k3, k4)
+ if k4 ~= nil then
+ local result = ch_core.safe_get_4(t, k1, k2, k3)
+ if result ~= nil then
+ return result[k4]
+ end
+ end
+ return nil
+end
+function ch_core.safe_get_6(t, k1, k2, k3, k4, k5)
+ if k4 ~= nil and k5 ~= nil then
+ local result = ch_core.safe_get_4(t, k1, k2, k3)
+ if result ~= nil then
+ result = result[k4]
+ if result ~= nil then
+ return result[k5]
+ end
+ end
+ end
+ return nil
+end
+function ch_core.safe_get_7(t, k1, k2, k3, k4, k5, k6)
+ if k4 ~= nil and k5 ~= nil and k6 ~= nil then
+ local result = ch_core.safe_get_4(t, k1, k2, k3)
+ if result ~= nil then
+ result = result[k4]
+ if result ~= nil then
+ result = result[k5]
+ if result ~= nil then
+ return result[k6]
+ end
+ end
+ end
+ end
+ return nil
+end
+
+--[[
+Provede operaci t[k1][k2]... = v s tím, že pokud je kterýkoliv z parametrů
+kromě „v“ nil nebo na nil po cestě narazí, vrátí false.
+Pokud přiřazení uspěje, vrátí true.
+Číslo v názvu udává celkový počet parametrů (včetně t a v)
+a musí být v rozsahu 3 až 8 včetně.
+V této verzi knihovny neprovádí kontrolu, zda je t indexovatelné.
+]]
+function ch_core.safe_set_3(t, k1, v)
+ if t and k1 ~= nil then
+ t[k1] = v
+ return true
+ end
+ return false
+end
+function ch_core.safe_set_4(t, k1, k2, v)
+ if k2 ~= nil then
+ t = ch_core.safe_get_2(t, k1)
+ if t then
+ t[k2] = v
+ return true
+ end
+ end
+ return false
+end
+function ch_core.safe_set_5(t, k1, k2, k3, v)
+ if k3 ~= nil then
+ t = ch_core.safe_get_3(t, k1, k2)
+ if t then
+ t[k3] = v
+ return true
+ end
+ end
+ return false
+end
+function ch_core.safe_set_6(t, k1, k2, k3, k4, v)
+ if k4 ~= nil then
+ t = ch_core.safe_get_4(t, k1, k2, k3)
+ if t then
+ t[k4] = v
+ return true
+ end
+ end
+ return false
+end
+function ch_core.safe_set_7(t, k1, k2, k3, k4, k5, v)
+ if k5 ~= nil then
+ t = ch_core.safe_get_5(t, k1, k2, k3, k4)
+ if t then
+ t[k5] = v
+ return true
+ end
+ end
+ return false
+end
+function ch_core.safe_set_8(t, k1, k2, k3, k4, k5, k6, v)
+ if k6 ~= nil then
+ t = ch_core.safe_get_6(t, k1, k2, k3, k4, k5)
+ if t then
+ t[k6] = v
+ return true
+ end
+ end
+ return false
+end
+
+--[[
+Nastaví dané postavě status „immortal“. Používá se pro postavy s právem
+usnadnění hry.
+]]
+function ch_core.set_immortal(player, true_or_false)
+ if true_or_false then
+ local properties = player:get_properties()
+ player:set_armor_groups({immortal = 1})
+ player:set_hp(properties.hp_max)
+ player:set_breath(properties.breath_max)
+ else
+ player:set_armor_groups({immortal = 0})
+ end
+ return true
+end
+
+--[[
+Uloží do metadat souřadnice x, y, z včetně desetinné části.
+]]
+function ch_core.set_pos_to_meta(meta, key, pos)
+ local x_key, y_key, z_key = key.."_x", key.."_y", key.."_z"
+ meta:set_float(x_key, pos.x)
+ meta:set_float(y_key, pos.y)
+ meta:set_float(z_key, pos.z)
+ local stored_pos = vector.new(meta:get_float(x_key), meta:get_float(y_key), meta:get_float(z_key))
+ if not vector.equals(pos, stored_pos) then
+ minetest.log("warning", "Position truncated when stored to metadata: "..vector.to_string(pos).." truncated to: "..vector.to_string(stored_pos))
+ end
+ return pos
+end
+
+--[[
+Přesune klíče definující vlastnosti entity do podtabulky initial_properties.
+Provádí úpravy přímo v předané tabulce a vrací ji (tzn. nevytváří kopii).
+]]
+function ch_core.upgrade_entity_properties(entity_def, options)
+ if options == nil then
+ options = {}
+ end
+ local base_properties = options.base_properties -- základ pro doplnění zcela chybějících vlastností
+ local in_place = options.in_place ~= false -- provádět změny v původní tabulce initial_properties, je-li dostupná; výchozí: true
+ local keep_fields = options.keep_fields == true -- ponechat původní pole v původní tabulce; výchozí: false
+
+ local initial_properties
+ if entity_def.initial_properties == nil then
+ initial_properties = {}
+ elseif in_place then
+ initial_properties = entity_def.initial_properties
+ else
+ initial_properties = table.copy(entity_def.initial_properties)
+ end
+ for _, k in ipairs(entity_properties_list) do
+ if entity_def[k] ~= nil then
+ if initial_properties[k] == nil then
+ initial_properties[k] = entity_def[k]
+ end
+ if not keep_fields then
+ entity_def[k] = nil
+ end
+ end
+ end
+ if base_properties ~= nil then
+ for k, v in pairs(base_properties) do
+ if initial_properties[k] == nil then
+ initial_properties[k] = v
+ end
+ end
+ end
+ entity_def.initial_properties = initial_properties
+ return entity_def
+end
+
+--[[
+Vrátí počet UTF-8 znaků řetězce.
+]]
+function ch_core.utf8_length(s)
+ if s == "" then
+ return 0
+ end
+ local i, byte, bytes, chars
+ i = 1
+ chars = 0
+ bytes = string.len(s)
+ while i <= bytes do
+ byte = string.byte(s, i)
+ if byte < 192 then
+ i = i + 1
+ else
+ i = i + utf8_charlen[byte]
+ end
+ chars = chars + 1
+ end
+ return chars
+end
+
+--[[
+Začne v řetězci `s` na fyzickém indexu `i` a bude se posouvat o `seek`
+UTF-8 znaků doprava (pro záporný počet doleva); vrátí výsledný index
+(na první bajt znaku), nebo nil, pokud posun přesáhl začátek,
+resp. konec řetězce.
+]]
+function ch_core.utf8_seek(s, i, seek)
+ local bytes = string.len(s)
+ if i < 1 or i > bytes then
+ return nil
+ end
+ local b
+ if seek > 0 then
+ while true do
+ b = string.byte(s, i)
+ if b < 192 then
+ i = i + 1
+ else
+ i = i + utf8_charlen[b]
+ end
+ if i > bytes then
+ return nil
+ end
+ seek = seek - 1
+ if seek == 0 then
+ return i
+ end
+ end
+ elseif seek < 0 then
+ while true do
+ i = i - 1
+ if i < 1 then
+ return nil
+ end
+ b = string.byte(s, i)
+ if b < 128 or b >= 192 then
+ -- máme další znak
+ seek = seek + 1
+ if seek == 0 then
+ return i
+ end
+ end
+ end
+ else
+ return i
+ end
+end
+
+--[[
+ Je-li řetězec s delší než max_chars znaků, vrátí jeho prvních max_chars znaků
+ + "...", jinak vrátí původní řetězec.
+]]
+function ch_core.utf8_truncate_right(s, max_chars, dots_string)
+ local i = ch_core.utf8_seek(s, 1, max_chars)
+ if i then
+ return s:sub(1, i - 1) .. (dots_string or "...")
+ else
+ return s
+ end
+end
+
+--[[
+Rozdělí řetězec na pole neprázdných podřetězců o stanovené maximální délce
+v UTF-8 znacích; v každé části vynechává mezery na začátku a na konci části;
+přednostně dělí v místech mezer. Pro prázdný řetězec
+(nebo řetězec tvořený jen mezerami) vrací prázdné pole.
+]]
+function ch_core.utf8_wrap(s, max_chars, options)
+ local i = 1 -- index do vstupního řetězce s
+ local s_bytes = string.len(s)
+ local result = {} -- výstupní pole
+ local r_text = "" -- výstupní řetězec
+ local r_chars = 0 -- počet UTF-8 znaků v řetězci r
+ local r_sp_begin -- index první mezery v poslední sekvenci mezer v r_text
+ local r_sp_end -- index poslední mezery v poslední sekvenci mezer v r_text
+ local b -- kód prvního bajtu aktuálního znaku
+ local c_bytes -- počet bajtů aktuálního znaku
+
+ -- options
+ local allow_empty_lines, max_result_lines, line_separator
+ if options then
+ allow_empty_lines = options.allow_empty_lines -- true or false
+ max_result_lines = options.max_result_lines -- nil or number
+ line_separator = options.line_separator -- nil or string
+ end
+
+ while i <= s_bytes do
+ b = string.byte(s, i)
+ -- print("byte["..i.."] = "..b.." ("..s:sub(i, i)..") r_sp = ("..(r_sp_begin or "nil")..".."..(r_sp_end or "nil")..")")
+ if r_chars > 0 or (b ~= 32 and (b ~= 10 or allow_empty_lines)) then -- na začátku řádky ignorovat mezery
+ if b < 192 then
+ c_bytes = 1
+ else
+ c_bytes = utf8_charlen[b]
+ end
+ -- vložit do r další znak (není-li to konec řádky)
+ if b ~= 10 then
+ r_text = r_text..s:sub(i, i + c_bytes - 1)
+ r_chars = r_chars + 1
+
+ if b == 32 then
+ -- znak je mezera
+ if r_sp_begin then
+ if r_sp_end then
+ -- začátek nové skupiny mezer (už nějaká byla)
+ r_sp_begin = string.len(r_text)
+ r_sp_end = nil
+ end
+ elseif not r_sp_end then
+ -- začátek první skupiny mezer (ještě žádná nebyla)
+ r_sp_begin = string.len(r_text)
+ end
+ else
+ -- znak není mezera ani konec řádky
+ if r_sp_begin and not r_sp_end then
+ r_sp_end = string.len(r_text) - c_bytes -- uzavřít skupinu mezer
+ end
+ end
+ end
+
+ if r_chars >= max_chars or b == 10 then
+ -- dosažen maximální počet znaků nebo znak \n => uzavřít řádku
+ if line_separator and #result > 0 then
+ result[#result] = result[#result]..line_separator
+ end
+ if r_chars < max_chars or not r_sp_begin then
+ -- žádné mezery => tvrdé dělení
+ table.insert(result, r_text)
+ r_text = ""
+ r_chars = 0
+ r_sp_begin, r_sp_end = nil, nil
+ elseif not r_sp_end then
+ -- průběžná skupina mezer => rozdělit zde
+ table.insert(result, r_text:sub(1, r_sp_begin - 1))
+ r_text = ""
+ r_chars = 0
+ r_sp_begin, r_sp_end = nil, nil
+ else
+ -- byla skupina mezer => rozdělit tam
+ table.insert(result, r_text:sub(1, r_sp_begin - 1))
+ r_text = r_text:sub(r_sp_end + 1, -1)
+ r_chars = ch_core.utf8_length(r_text)
+ r_sp_begin, r_sp_end = nil, nil
+ if r_chars > 0 and b == 10 then
+ i = i - c_bytes -- read this \n-byte again
+ end
+ end
+ if max_result_lines and #result >= max_result_lines then
+ return result -- skip reading other lines
+ end
+ end
+ i = i + c_bytes
+ else
+ i = i + 1
+ end
+ end
+ if r_chars > 0 then
+ if line_separator and #result > 0 then
+ result[#result] = result[#result]..line_separator
+ end
+ if r_sp_begin and not r_sp_end then
+ -- průběžná skupina mezer
+ table.insert(result, r_text:sub(1, r_sp_begin - 1))
+ else
+ table.insert(result, r_text)
+ end
+ end
+ return result
+end
+function ch_core.utf8_radici_klic(s, store_to_cache)
+ local result = utf8_sort_cache[s]
+ if not result then
+ local i = 1
+ local l = s:len()
+ local c, k
+ result = {}
+ while i <= l do
+ c = s:sub(i, i)
+ k = utf8_sort_data_1[c]
+ if k then
+ table.insert(result, k)
+ i = i + 1
+ else
+ k = utf8_sort_data_2[s:sub(i, i + 1)]
+ if k then
+ table.insert(result, k)
+ i = i + 2
+ else
+ k = utf8_sort_data_3[c]
+ table.insert(result, k or c)
+ i = i + 1
+ end
+ end
+ end
+ result = table.concat(result)
+ if store_to_cache then
+ utf8_sort_cache[s] = result
+ end
+ end
+ return result
+end
+
+function ch_core.utf8_mensi_nez(a, b, store_to_cache)
+ a = ch_core.utf8_radici_klic(a, store_to_cache)
+ b = ch_core.utf8_radici_klic(b, store_to_cache)
+ return a < b
+end
+
+--[[
+-- KÓD INICIALIZACE
+-- ===========================================================================
+local dbg_table = ch_core.storage:to_table()
+if not dbg_table then
+ print("STORAGE: nil")
+else
+ for key, value in pairs(dbg_table.fields) do
+ print("STORAGE: <"..key..">=<"..value..">")
+ end
+end
+]]
+
+doors.login_to_viewname = ch_core.prihlasovaci_na_zobrazovaci
+doors.viewname_to_login = ch_core.jmeno_na_prihlasovaci
+
+-- PŘÍKAZY
+-- ===========================================================================
+def = {
+ description = "Vypíše seznam neregistrovaných postav seřazený podle času posledního přihlášení.",
+ privs = {server = true},
+ func = function(player_name, param)
+ local last_logins = ch_core.get_last_logins(false)
+ local result = {}
+
+ for i = #last_logins, 1, -1 do
+ local info = last_logins[i]
+ local viewname = ch_core.prihlasovaci_na_zobrazovaci(info.player_name)
+ local s = "- "..viewname.." (posl. přihl. před "..info.last_login_before.." dny, odehráno "..info.played_hours_total..
+ " hodin, z toho "..info.played_hours_actively.." aktivně)"
+ if info.is_in_game then
+ s = s.." <je ve hře>"
+ end
+ if info.pending_registration_type ~= nil then
+ s = s.." <plánovaná registrace: "..info.pending_registration_type..">"
+ end
+ if info.is_registered then
+ s = s.." <registrovaná postava>"
+ end
+ table.insert(result, s)
+ end
+
+ result = table.concat(result, "\n")
+ minetest.log("warning", result)
+ minetest.chat_send_player(player_name, result)
+ return true
+ end,
+}
+
+minetest.register_chatcommand("postavynauklid", def)
+minetest.register_chatcommand("postavynaúklid", def)
+
+ch_core.close_submod("lib")