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", -- ["\x41"] = "\x42", -- ["\x62"] = "\x47", -- ["\x42"] = "\x48", -- ["\x64"] = "\x4d", -- ["\x44"] = "\x4e", -- ["\x65"] = "\x51", -- ["\x45"] = "\x52", -- ["\x66"] = "\x57", -- ["\x46"] = "\x58", -- ["\x67"] = "\x59", -- ["\x47"] = "\x5a", -- ["\x68"] = "\x5b", -- ["\x48"] = "\x5c", -- ["\x69"] = "\x61", -- ["\x49"] = "\x62", -- ["\x6a"] = "\x65", -- ["\x4a"] = "\x66", -- ["\x6b"] = "\x67", -- ["\x4b"] = "\x68", -- ["\x6c"] = "\x69", -- ["\x4c"] = "\x6a", -- ["\x6d"] = "\x6f", -- ["\x4d"] = "\x70", -- ["\x6e"] = "\x71", -- ["\x4e"] = "\x72", -- ["\x6f"] = "\x75", -- ["\x4f"] = "\x76", -- ["\x70"] = "\x7b", --

["\x50"] = "\x7c", --

["\x71"] = "\x7d", -- ["\x51"] = "\x7e", -- ["\x72"] = "\x7f", -- ["\x52"] = "\x80", -- ["\x73"] = "\x85", -- ["\x53"] = "\x86", -- ["\x74"] = "\x89", -- ["\x54"] = "\x8a", -- ["\x75"] = "\x8d", -- ["\x55"] = "\x8e", -- ["\x76"] = "\x93", -- ["\x56"] = "\x94", -- ["\x77"] = "\x95", -- ["\x57"] = "\x96", -- ["\x78"] = "\x97", -- ["\x58"] = "\x98", -- ["\x79"] = "\x99", -- ["\x59"] = "\x9a", -- ["\x7a"] = "\x9d", -- ["\x5a"] = "\x9e", -- ["\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", -- ["\x63\x48"] = "\x5e", -- ["\x43\x68"] = "\x5f", -- ["\x43\x48"] = "\x60", -- ["\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", -- ["\x43"] = "\x4a", -- } 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ší. ]] 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.." " end if info.pending_registration_type ~= nil then s = s.." " end if info.is_registered then s = s.." " 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")