Mods update

master
Daretmavi 2021-07-07 17:02:36 +02:00
parent f11ec46f3f
commit ed2630c292
15 changed files with 632 additions and 112 deletions

View File

@ -78,7 +78,7 @@ Mod: flora/farming
origin https://github.com/daretmavi/i3.git (fetch)
upstream https://github.com/minetest-mods/i3.git (fetch)
* main 7bd3e5d [origin/main] New Variable i3_no_trash_in_survival Better bool reading from settings
* main 4e39096 [origin/main] Merge pull request #4 from daretmavi/devtest
Mod: gui/i3
origin https://repo.or.cz/minetest_hbarmor.git (fetch)
@ -118,7 +118,7 @@ origin https://notabug.org/tenplus1/mobs_redo (fetch)
Mod: lib_api/mobs_redo
origin https://github.com/appgurueu/modlib (fetch)
* master abced34 [origin/master] Deal with Lua log file memory leak
* master 35081f3 [origin/master] Localize global fields
Mod: lib_api/modlib
origin git@github.com:runsy/rcbows.git (fetch)

View File

@ -0,0 +1,9 @@
[*]
end_of_line = lf
[*.{lua,txt,md,json}]
charset = utf8
indent_size = 8
indent_style = tab
insert_final_newline = true
trim_trailing_whitespace = true

View File

@ -84,6 +84,9 @@ local styles = fmt([[
style[craft_rcp,craft_usg;noclip=true;font_size=16;sound=i3_craft;
bgimg=i3_btn9.png;bgimg_hovered=i3_btn9_hovered.png;
bgimg_pressed=i3_btn9_pressed.png;bgimg_middle=4,6]
style[confirm_trash_yes,confirm_trash_no;noclip=true;font_size=16;
bgimg=i3_btn9.png;bgimg_hovered=i3_btn9_hovered.png;
bgimg_pressed=i3_btn9_pressed.png;bgimg_middle=4,6]
]],
PNG.slot,
PNG.exit, PNG.exit_hover,

View File

@ -0,0 +1,251 @@
-- ****************************************************************************
-- Funnctions and Variables from i3 init.lua
local modpath = core.get_modpath "i3"
local maxn, sort, concat, copy, insert, remove, indexof =
table.maxn, table.sort, table.concat, table.copy,
table.insert, table.remove, table.indexof
local sprintf, find, gmatch, match, sub, split, upper, lower =
string.format, string.find, string.gmatch, string.match,
string.sub, string.split, string.upper, string.lower
local PNG, styles, fs_elements = loadfile(modpath .. "/etc/styles.lua")()
local ESC = core.formspec_escape
local S = core.get_translator "i3"
local ES = function(...)
return ESC(S(...))
end
local function fmt(elem, ...)
if not fs_elements[elem] then
return sprintf(elem, ...)
end
return sprintf(fs_elements[elem], ...)
end
local function add_subtitle(fs, name, y, ctn_len, font_size, sep, label)
fs(fmt("style[%s;font=bold;font_size=%u]", name, font_size))
fs("button", 0, y, ctn_len, 0.5, name, ESC(label))
if sep then
fs("image", 0, y + 0.55, ctn_len, 0.035, PNG.bar)
end
end
-- ****************************************************************************
-- Function to get detail info aout texture
-- Forked from skinsdb mod
local function get_skin_info_formspec(skin, xoffset, yoffset)
local texture = skin:get_texture()
local m_name = skin:get_meta_string("name")
local m_author = skin:get_meta_string("author")
local m_license = skin:get_meta_string("license")
local m_format = skin:get_meta("format")
-- overview page
local raw_size = m_format == "1.8" and "2,2" or "2,1"
--local lxoffs = 0.8 + xoffset
local cxoffs = 2 + xoffset
local rxoffs = 2 + xoffset
local formspec = "" -- = "image["..lxoffs..","..0.6 + yoffset..";1,2;"..minetest.formspec_escape(skin:get_preview()).."]"
if texture then
formspec = formspec.."label["..rxoffs..","..2 + yoffset..";"..S("Raw texture")..":]"
.."image["..1 + rxoffs..","..2.5 + yoffset..";"..raw_size..";"..texture.."]"
end
if m_name ~= "" then
formspec = formspec.."label["..cxoffs..","..0.5 + yoffset..";"..S("Name")..": "..minetest.formspec_escape(m_name).."]"
end
if m_author ~= "" then
formspec = formspec.."label["..cxoffs..","..1 + yoffset..";"..S("Author")..": "..minetest.formspec_escape(m_author).."]"
end
if m_license ~= "" then
formspec = formspec.."label["..cxoffs..","..1.5 + yoffset..";"..S("License")..": "..minetest.formspec_escape(m_license).."]"
end
return formspec
end
-- skin images and pages
local function get_skin_selection_formspec(player, context)
context.skins_list = skins.get_skinlist_for_player(player:get_player_name())
context.total_pages = 1
local xoffs = 0.8
local yoffs = 6.5
local xspc = 1.1
local yspc = 2.1
local skinwidth = 1
local skinheight = 2
local xscale = 1
local btn_y = yoffs + 4.5
local drop_y = yoffs + 4.5
local btn_width = 0.35
local btn_heigh = 0.35
local droppos = xoffs + 1.3
local droplen = 6.2
local btn_left = droppos - btn_width
local btn_right = droppos + droplen
local maxdisp = 16
local ctrls_height = 0.4
for i, skin in ipairs(context.skins_list ) do
local page = math.floor((i-1) / maxdisp)+1
skin:set_meta("inv_page", page)
skin:set_meta("inv_page_index", (i-1)%maxdisp+1)
context.total_pages = page
end
context.skins_page = context.skins_page or skins.get_player_skin(player):get_meta("inv_page") or 1
context.dropdown_values = nil
local page = context.skins_page
local formspec = ""
for i = (page-1)*maxdisp+1, page*maxdisp do
local skin = context.skins_list[i]
if not skin then
break
end
local index_p = skin:get_meta("inv_page_index")
local x = ((index_p-1) % 8) * xspc + xoffs
local y
if index_p > 8 then
y = yoffs + yspc
else
y = yoffs
end
formspec = formspec..
string.format("image_button[%f,%f;%f,%f;%s;skins_set$%i;]",
x, y, skinwidth, skinheight,
minetest.formspec_escape(skin:get_preview()), i)..
"tooltip[skins_set$"..i..";"..minetest.formspec_escape(skin:get_meta_string("name")).."]"
end
if context.total_pages > 1 then
local page_prev = page - 1
local page_next = page + 1
if page_prev < 1 then
page_prev = context.total_pages
end
if page_next > context.total_pages then
page_next = 1
end
local page_list = ""
context.dropdown_values = {}
for pg=1, context.total_pages do
local pagename = S("Page").." "..pg.."/"..context.total_pages
context.dropdown_values[pagename] = pg
if pg > 1 then page_list = page_list.."," end
page_list = page_list..pagename
end
formspec = formspec..
string.format("image_button[%f,%f;%f,%f;%s;skins_page$%i;]",
btn_left, btn_y, btn_width, btn_heigh, PNG.prev, page_prev)..
string.format("image_button[%f,%f;%f,%f;%s;skins_page$%i;]",
btn_right, btn_y, btn_width, btn_heigh, PNG.next, page_next)..
string.format("dropdown[%f,%f;%f,%f;skins_selpg;%s;%i]",
droppos, drop_y, droplen, ctrls_height, page_list, page)
end
return formspec
end
-- ****************************************************************************
-- i3 Tab definition
i3.new_tab {
name = "Skins",
description = "Skins",
image = "i3_skin.png",
formspec = function(player, data, fs)
--fs("label[3,1;Test 1]")
local name = player:get_player_name()
local ctn_len, ctn_hgt = 5.7, 6.3
local yextra = 1
local yoffset = 0
local xpos = 5
local _skins = skins.get_skinlist_for_player(name)
local skin_name = skins.get_player_skin(player).name
local sks, id = {}, 1
local props = player:get_properties()
for i, skin in ipairs(_skins) do
if skin.name == skin_name then
id = i
end
sks[#sks + 1] = skin.name
end
sks = concat(sks, ","):gsub(";", "")
add_subtitle(fs, "player_name", 0, ctn_len + 4.5, 22, true, ESC(name))
if props.mesh ~= "" then
local anim = player:get_local_animation()
local armor_skin = __3darmor or __skinsdb
local t = {}
for _, v in ipairs(props.textures) do
t[#t + 1] = ESC(v):gsub(",", "!")
end
local textures = concat(t, ","):gsub("!", ",")
local skinxoffset = 1.3
--fs("style[player_model;bgcolor=black]")
fs("model", skinxoffset, 0.2, armor_skin and 4 or 3.4, ctn_hgt,
"player_model", props.mesh, textures, "0,-150", "false", "false",
fmt("%u,%u%s", anim.x, anim.y, data.fs_version >= 5 and ";30" or ""))
else
local size = 2.5
fs("image", 0.7, 0.2, size, size * props.visual_size.y, props.textures[1])
end
fs("label", xpos, yextra, fmt("%s:", ES"Select a skin"))
fs(fmt("dropdown[%f,%f;4,0.6;skins;%s;%u;true]", xpos, yextra + 0.2, sks, id))
local skin = skins.get_player_skin(player)
local formspec = get_skin_info_formspec(skin, 3, 2)
fs(formspec)
--core.log("fs skins: "..dump(formspec))
local context = skins.get_formspec_context(player)
local formspec = get_skin_selection_formspec(player, context)
--core.log("skins context: "..dump(context))
--core.log("fs skins: "..dump(formspec))
fs(formspec)
end,
-- click button handlers - giving fields
fields = function(player, data, fields)
local name = player:get_player_name()
local sb_inv = fields.scrbar_inv
--core.log("fields: "..dump(fields))
-- set skin with dropdown from original i3 inventory
if fields.skins then
local id = tonumber(fields.skins)
local _skins = skins.get_skinlist_for_player(name)
skins.set_player_skin(player, _skins[id])
end
-- set skin with skinddb image an page change
local context = skins.get_formspec_context(player)
local action = skins.on_skin_selection_receive_fields(player, context, fields)
-- update formspec
return i3.set_fs(player)
end,
}
-- ****************************************************************************

View File

@ -20,13 +20,17 @@ local PNG, styles, fs_elements = loadfile(modpath .. "/etc/styles.lua")()
local compress_groups, compressed = loadfile(modpath .. "/etc/compress.lua")()
local group_stereotypes, group_names = loadfile(modpath .. "/etc/groups.lua")()
local progressive_mode = core.settings:get_bool("i3_progressive_mode", false)
local progressive_mode = core.settings:get_bool("i3_progressive_mode", true)
local item_compression = core.settings:get_bool("i3_item_compression", true)
local damage_enabled = core.settings:get_bool "enable_damage"
-- new settings
local creative_trash_only = core.settings:get_bool("i3_no_trash_in_survival", true)
local use_skinsdb_tab = core.settings:get_bool("i3_skinsdb_tab", true)
-- ************
local __3darmor, __skinsdb, __awards
local __sfinv, old_sfinv_fn
local __unified_inventory, old_unified_inventory_fn
local http = core.request_http_api()
local singleplayer = core.is_singleplayer()
@ -107,11 +111,11 @@ local function get_formspec_version(info)
end
local function outdated(name)
local fs = sprintf("size[5.8,1.3]image[0,0;1,1;%s]label[1,0;%s]button_exit[2.4,0.8;1,1;;OK]",
local fs = sprintf("size[6.3,1.3]image[0,0;1,1;%s]label[1,0;%s]button_exit[2.6,0.8;1,1;;OK]",
PNG.book,
"Your Minetest client is outdated.\nGet the latest version on minetest.net to use i3")
"Your Minetest client is outdated.\nGet the latest version on minetest.net to play the game.")
core.show_formspec(name, "i3", fs)
core.show_formspec(name, "i3_outdated", fs)
end
local old_is_creative_enabled = core.is_creative_enabled
@ -2011,7 +2015,6 @@ local function get_ctn_content(fs, data, player, yoffset, ctn_len, award_list, a
-- fmt("list[detached:i3_trash;main;%f,%f;1,1;]", 4.45, yoffset + 3.75))
--fs("image", 4.45, yoffset + 3.75, 1, 1, PNG.trash)
local creative_trash_only = core.settings:get_bool("i3_no_trash_in_survival", true)
if core.is_creative_enabled(name) or not creative_trash_only then
fs(fmt("list[detached:i3_trash;main;%f,%f;1,1;]", 4.45, yoffset + 3.75))
fs("image", 4.45, yoffset + 3.75, 1, 1, PNG.trash)
@ -2020,6 +2023,8 @@ local function get_ctn_content(fs, data, player, yoffset, ctn_len, award_list, a
local yextra = damage_enabled and 5.5 or 5
for i, title in ipairs(SUBCAT) do
if title ~= "skins" or not use_skinsdb_tab then
local btn_name = fmt("btn_%s", title)
fs(fmt("style[btn_%s;fgimg=%s;fgimg_hovered=%s;content_offset=0]", title,
@ -2028,6 +2033,8 @@ local function get_ctn_content(fs, data, player, yoffset, ctn_len, award_list, a
fs("image_button", 0.25 + ((i - 1) * 1.18), yextra - 0.2, 0.5, 0.5, "", btn_name, "")
fs(fmt("tooltip[%s;%s]", btn_name, title:gsub("^%l", upper)))
end
end
fs("box", 0, yextra + 0.45, ctn_len, 0.045, "#bababa50")
@ -2059,19 +2066,24 @@ local function get_ctn_content(fs, data, player, yoffset, ctn_len, award_list, a
end
elseif data.subcat == 3 then
if __skinsdb then
if __skinsdb and not use_skinsdb_tab then
local _skins = skins.get_skinlist_for_player(name)
local sks = {}
local skin_name = skins.get_player_skin(player).name
local sks, id = {}, 1
for i, skin in ipairs(_skins) do
if skin.name == skin_name then
id = i
end
for _, skin in ipairs(_skins) do
sks[#sks + 1] = skin.name
end
sks = concat(sks, ","):gsub(";", "")
fs("label", 0, yextra + 0.85, fmt("%s:", ES"Select a skin"))
fs(fmt("dropdown[0,%f;4,0.6;skins;%s;%u;true]", yextra + 1.1, sks, data.skin_id or 1))
else
fs(fmt("dropdown[0,%f;4,0.6;skins;%s;%u;true]", yextra + 1.1, sks, id))
elseif not use_skinsdb_tab then
not_installed("skinsdb")
end
@ -2138,18 +2150,26 @@ local function get_tabs_fs(player, data, fs, full_height)
end
local function get_debug_grid(data, fs, full_height)
local spacing = 0.2
fs("style_type[label;font_size=8;noclip=true]")
local spacing, i = 0.2, 1
for x = 0, data.inv_width + 8, spacing do
fs("box", x, 0, 0.01, full_height, "#ff0")
fs("label", x, full_height + 0.1, tostring(i))
i = i + 1
end
i = 61
for y = 0, full_height, spacing do
fs("box", 0, y, data.inv_width + 8, 0.01, "#ff0")
fs("label", -0.15, y, tostring(i))
i = i - 1
end
fs("box", data.inv_width / 2, 0, 0.01, full_height, "#f00")
fs("box", 0, full_height / 2, data.inv_width, 0.01, "#f00")
fs("style_type[label;font_size=16]")
end
local function make_fs(player, data)
@ -2309,20 +2329,21 @@ local function init_data(player, info)
end
local function reset_data(data)
data.filter = ""
data.expand = ""
data.pagenum = 1
data.rnum = 1
data.unum = 1
data.scrbar_rcp = 1
data.scrbar_usg = 1
data.query_item = nil
data.recipes = nil
data.usages = nil
data.export_rcp = nil
data.export_usg = nil
data.alt_items = nil
data.items = data.items_raw
data.filter = ""
data.expand = ""
data.pagenum = 1
data.rnum = 1
data.unum = 1
data.scrbar_rcp = 1
data.scrbar_usg = 1
data.query_item = nil
data.recipes = nil
data.usages = nil
data.export_rcp = nil
data.export_usg = nil
data.alt_items = nil
data.confirm_trash = nil
data.items = data.items_raw
if data.current_itab > 1 then
sort_by_category(data)
@ -2524,12 +2545,12 @@ local function get_inventory_fs(player, data, fs)
fs("scroll_container_end[]")
local btn = {
--{"trash", ES"Trash all items"},
--{"trash", ES"Clear inventory"},
{"sort_az", ES"Sort items (A-Z)"},
{"sort_za", ES"Sort items (Z-A)"},
{"compress", ES"Compress items"},
}
if core.is_creative_enabled(name) then table.insert(btn, 1, {"trash", ES"Trash all items"}) end
if core.is_creative_enabled(name) then table.insert(btn, 1, {"trash", ES"Clear inventory"}) end
for i, v in ipairs(btn) do
local btn_name, tooltip = unpack(v)
@ -2540,6 +2561,18 @@ local function get_inventory_fs(player, data, fs)
fs("image_button", i + 3.447 - (i * 0.4), 11.43, 0.35, 0.35, "", btn_name, "")
fs(fmt("tooltip[%s;%s]", btn_name, tooltip))
end
if data.confirm_trash then
fs("style_type[box;colors=#999,#999,#808080,#808080]")
for _ = 1, 3 do
fs("box", 2.97, 10.75, 4.3, 0.5, "")
end
fs("label", 3.12, 11, "Confirm trash?")
fs("image_button", 5.17, 10.75, 1, 0.5, "", "confirm_trash_yes", "Yes")
fs("image_button", 6.27, 10.75, 1, 0.5, "", "confirm_trash_no", "No")
end
end
i3.new_tab {
@ -2551,10 +2584,10 @@ i3.new_tab {
local name = player:get_player_name()
local sb_inv = fields.scrbar_inv
if fields.skins and data.skin_id ~= tonum(fields.skins) then
data.skin_id = tonum(fields.skins)
if fields.skins then
local id = tonum(fields.skins)
local _skins = skins.get_skinlist_for_player(name)
skins.set_player_skin(player, _skins[data.skin_id])
skins.set_player_skin(player, _skins[id])
end
for field in pairs(fields) do
@ -2611,9 +2644,16 @@ i3.new_tab {
end
if fields.trash then
local inv = player:get_inventory()
inv:set_list("main", {})
inv:set_list("craft", {})
data.confirm_trash = true
elseif fields.confirm_trash_yes or fields.confirm_trash_no then
if fields.confirm_trash_yes then
local inv = player:get_inventory()
inv:set_list("main", {})
inv:set_list("craft", {})
end
data.confirm_trash = nil
elseif fields.compress then
compress_items(player)
@ -2900,18 +2940,12 @@ end
core.register_on_mods_loaded(function()
get_init_items()
__sfinv = rawget(_G, "sfinv")
if __sfinv then
old_sfinv_fn = sfinv.set_player_inventory_formspec
if rawget(_G, "sfinv") then
function sfinv.set_player_inventory_formspec() return end
sfinv.enabled = false
end
__unified_inventory = rawget(_G, "unified_inventory")
if __unified_inventory then
old_unified_inventory_fn = unified_inventory.set_inventory_formspec
if rawget(_G, "unified_inventory") then
function unified_inventory.set_inventory_formspec() return end
end
end)
@ -2991,21 +3025,7 @@ core.register_on_joinplayer(function(player)
local info = core.get_player_information and core.get_player_information(name)
if not info or get_formspec_version(info) < MIN_FORMSPEC_VERSION then
if __sfinv then
sfinv.set_player_inventory_formspec = old_sfinv_fn
sfinv.enabled = true
end
if __unified_inventory then
unified_inventory.set_inventory_formspec = old_unified_inventory_fn
if __sfinv then
sfinv.enabled = false
end
end
pdata[name] = nil
return outdated(name)
end
@ -3036,7 +3056,6 @@ end)
local META_SAVES = {
bag_size = true,
waypoints = true,
skin_id = true,
inv_items = true,
known_recipes = true,
}
@ -3074,11 +3093,14 @@ end
after(SAVE_INTERVAL, routine)
core.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= "" then
local name = player:get_player_name()
if formname == "i3_outdated" then
return false, core.kick_player(name, "Come back when your client is up-to-date.")
elseif formname ~= "" then
return false
end
local name = player:get_player_name()
local data = pdata[name]
if not data then return end
@ -3286,6 +3308,7 @@ if progressive_mode then
local player = players[i]
local name = player:get_player_name()
local data = pdata[name]
if not data then return end
local inv_items = get_inv_items(player)
local diff = array_diff(inv_items, data.inv_items)
@ -3321,7 +3344,7 @@ if progressive_mode then
local name = player:get_player_name()
local data = pdata[name]
if data.show_hud ~= nil and singleplayer then
if data and data.show_hud ~= nil and singleplayer then
show_hud_success(player, data)
end
end
@ -3362,6 +3385,7 @@ if progressive_mode then
core.register_on_joinplayer(function(player)
local name = player:get_player_name()
local data = pdata[name]
if not data then return end
-- remove recipe filter to suppress progressive_mode in creative
-- on player join
@ -3413,3 +3437,7 @@ end
--dofile(modpath .. "/tests/test_tabs.lua")
--dofile(modpath .. "/tests/test_custom_recipes.lua")
if __skinsdb and use_skinsdb_tab then
dofile(modpath .. "/i3_mods_tabs.lua")
end

View File

@ -1,8 +1,15 @@
# The progressive mode shows recipes you can craft from items you ever had in your inventory.
i3_progressive_mode (Learn crafting recipes progressively) bool false
i3_progressive_mode (Learn crafting recipes progressively) bool true
# Regroup the items of the same type in the item list.
i3_item_compression (Regroup items of the same type) bool true
# ------------ NEW SETTINGS ------------
# not in original i3 available
# Show trash only in creative mode, or with creative privileges
i3_no_trash_in_survival (Trash is available only in creative) bool true
# For skin choice use tab instead of inventory dropdown only
i3_skinsdb_tab (Use tab for skins) bool true

View File

@ -0,0 +1,7 @@
Copyright 2019 - 2021 Lars Mueller alias LMD or appguru(eu)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,5 +1,6 @@
-- Localize globals
local assert, math = assert, math
local assert, math_huge, math_frexp, math_floor
= assert, math.huge, math.frexp, math.floor
-- Set environment
local _ENV = {}
@ -30,7 +31,7 @@ function read_float(read_byte, double)
mantissa = (mantissa + byte_2) / 0x80
if exponent == 0xFF then
if mantissa == 0 then
return sign * math.huge
return sign * math_huge
end
-- Differentiating quiet and signalling nan is not possible in Lua, hence we don't have to do it
-- HACK ((0/0)^1) yields nan, 0/0 yields -nan
@ -67,7 +68,7 @@ end
function write_uint(write_byte, uint, bytes)
for _ = 1, bytes do
write_byte(uint % 0x100)
uint = math.floor(uint / 0x100)
uint = math_floor(uint / 0x100)
end
assert(uint == 0)
end
@ -80,16 +81,16 @@ function write_float(write_byte, number, on_write, double)
number = -number
sign = 0x80
end
local mantissa, exponent = math.frexp(number)
local mantissa, exponent = math_frexp(number)
exponent = exponent + 127
if exponent > 1 then
-- TODO ensure this deals properly with subnormal numbers
mantissa = mantissa * 2 - 1
exponent = exponent - 1
end
local sign_byte = sign + math.floor(exponent / 2)
local sign_byte = sign + math_floor(exponent / 2)
mantissa = mantissa * 0x80
local exponent_byte = (exponent % 2) * 0x80 + math.floor(mantissa)
local exponent_byte = (exponent % 2) * 0x80 + math_floor(mantissa)
mantissa = mantissa % 1
local mantissa_bytes = {}
-- TODO ensure this check is proper
@ -102,7 +103,7 @@ function write_float(write_byte, number, on_write, double)
local len = double and 6 or 2
for index = len, 1, -1 do
mantissa = mantissa * 0x100
mantissa_bytes[index] = math.floor(mantissa)
mantissa_bytes[index] = math_floor(mantissa)
mantissa = mantissa % 1
end
assert(mantissa == 0)

View File

@ -1,5 +1,6 @@
-- Localize globals
local assert, error, ipairs, math, modlib, next, pairs, setmetatable, string, table, type, unpack = assert, error, ipairs, math, modlib, next, pairs, setmetatable, string, table, type, unpack
local assert, error, ipairs, math_floor, math_huge, modlib, next, pairs, setmetatable, string, table_insert, type, unpack
= assert, error, ipairs, math.floor, math.huge, modlib, next, pairs, setmetatable, string, table.insert, type, unpack
-- Set environment
local _ENV = {}
@ -61,8 +62,8 @@ local constants = {
[0] = "\2",
-- not possible as table entry as Lua doesn't allow +/-nan as table key
-- [0/0] = "\3",
[math.huge] = "\4",
[-math.huge] = "\5",
[math_huge] = "\4",
[-math_huge] = "\5",
[""] = "\20"
}
@ -297,18 +298,18 @@ function read(self, stream)
end
if type <= type_ranges.string then
local string = stream_read(uint(type - type_ranges.number))
table.insert(references, string)
table_insert(references, string)
return string
end
if type <= type_ranges.table then
type = type - type_ranges.string - 1
local tab = {}
table.insert(references, tab)
table_insert(references, tab)
if type == 0 then
return tab
end
local list_len = uint(type % 5)
local kv_len = uint(math.floor(type / 5))
local kv_len = uint(math_floor(type / 5))
for index = 1, list_len do
tab[index] = _read(stream_read(1))
end

View File

@ -50,6 +50,7 @@ for _, file in pairs{
"ranked_set",
"binary",
"b3d",
"luon",
"bluon",
"persistence",
"debug"

View File

@ -0,0 +1,165 @@
local assert, next, pairs, pcall, error, type, table_insert, table_concat, string_format, string_match, setfenv, math_huge, loadfile, loadstring
= assert, next, pairs, pcall, error, type, table.insert, table.concat, string.format, string.match, setfenv, math.huge, loadfile, loadstring
local count_values = modlib.table.count_values
-- Build a table with the succeeding character from A-Z
local succ = {}
for char = ("A"):byte(), ("Z"):byte() - 1 do
succ[string.char(char)] = string.char(char + 1)
end
local function quote(string)
return string_format("%q", string)
end
local _ENV = {}
setfenv(1, _ENV)
function write(object, write)
local reference = {"A"}
local function increment_reference(place)
if not reference[place] then
reference[place] = "B"
elseif reference[place] == "Z" then
reference[place] = "A"
return increment_reference(place + 1)
else
reference[place] = succ[reference[place]]
end
end
local references = {}
local to_fill = {}
for value, count in pairs(count_values(object)) do
local type_ = type(value)
if count >= 2 and ((type_ == "string" and #reference + 2 >= #value) or type_ == "table") then
local ref = table_concat(reference)
write(ref)
write"="
write(type_ == "table" and "{}" or quote(value))
write";"
references[value] = ref
if type_ == "table" then
to_fill[value] = true
end
increment_reference(1)
end
end
local function is_short_key(key)
return not references[key] and type(key) == "string" and string_match(key, "^[%a_][%a%d_]*$")
end
local function dump(value)
-- Primitive types
if value == nil then
return write"nil"
end
if value == true then
return write"true"
end
if value == false then
return write"false"
end
local type_ = type(value)
if type_ == "number" then
return write(string_format("%.17g", value))
end
-- Reference types: table and string
local ref = references[value]
if ref then
-- Referenced
if not to_fill[value] then
return write(ref)
end
-- Fill table
to_fill[value] = false
for k, v in pairs(value) do
write(ref)
if is_short_key(k) then
write"."
write(k)
else
write"["
dump(k)
write"]"
end
write"="
dump(v)
write";"
end
elseif type_ == "string" then
return write(quote(value))
elseif type_ == "table" then
local first = true
write"{"
local len = #value
for i = 1, len do
if not first then write";" end
dump(value[i])
first = false
end
for k, v in next, value do
if type(k) ~= "number" or k % 1 ~= 0 or k < 1 or k > len then
if not first then write";" end
if is_short_key(k) then
write(k)
else
write"["
dump(k)
write"]"
end
write"="
dump(v)
first = false
end
end
write"}"
else
error("unsupported type: " .. type_)
end
end
local fill_root = to_fill[object]
if fill_root then
-- Root table is circular, must return by named reference
dump(object)
write"return "
write(references[object])
else
-- Root table is not circular, can directly start writing
write"return "
dump(object)
end
end
function write_file(object, file)
return write(object, function(text)
file:write(text)
end)
end
function write_string(object)
local rope = {}
write(object, function(text)
table_insert(rope, text)
end)
return table_concat(rope)
end
function read(...)
local read = assert(...)
-- math.huge is serialized to inf, 0/0 is serialized to -nan
setfenv(read, {inf = math_huge, nan = 0/0})
local success, value_or_err = pcall(read)
if success then
return value_or_err
end
return nil, value_or_err
end
function read_file(path)
return read(loadfile(path))
end
function read_string(string)
return read(loadstring(string))
end
return _ENV

View File

@ -185,7 +185,7 @@ function colorspec.from_string(string)
if number then
return colorspec.from_number(number * 0x100 + alpha)
end
local hex_text = string:match(hex)
local hex_text = string:match("^" .. hex .. "$")
local len, num = hex_text:len(), tonumber(hex_text, 16)
if len == 8 then
return colorspec.from_number(num)

View File

@ -106,43 +106,39 @@ function lua_log_file:_dump(value, is_key)
end
reference = self.reference_count + 1
local key = "R[" .. reference .."]"
local formatted
local function create_reference()
self.reference_count = reference
self.references[value] = reference
end
if _type == "string" then
local reference_strings = self.reference_strings
if is_key and ((not reference_strings) or value:len() <= key:len()) and value:match"[%a_][%a%d_]*" then
if is_key and ((not reference_strings) or value:len() <= key:len()) and value:match"^[%a_][%a%d_]*$" then
-- Short key
return value, true
end
formatted = ("%q"):format(value)
local formatted = ("%q"):format(value)
if (not reference_strings) or formatted:len() <= key:len() then
-- Short string
return formatted
end
-- Use reference
create_reference()
self:log(key .. "=" .. formatted)
elseif _type == "table" then
-- Tables always need a reference before they are traversed to prevent infinite recursion
create_reference()
local entries = {}
for _, value in ipairs(value) do
table.insert(entries, self:_dump(value))
end
-- TODO traverse tables to determine whether this is actually needed
self:log(key .. "={}")
local tablelen = #value
for key, value in pairs(value) do
if type(key) ~= "number" or key % 1 ~= 0 or key < 1 or key > tablelen then
local dumped, short = self:_dump(key, true)
table.insert(entries, (short and dumped or ("[" .. dumped .. "]")) .. "=" .. self:_dump(value))
for k, v in pairs(value) do
if type(k) ~= "number" or k % 1 ~= 0 or k < 1 or k > tablelen then
local dumped, short = self:_dump(k, true)
self:log(key .. (short and ("." .. dumped) or ("[" .. dumped .. "]")) .. "=" .. self:_dump(v))
end
end
formatted = "{" .. table.concat(entries, ";") .. "}"
else
error("unsupported type: " .. _type)
end
self:log(key .. "=" .. formatted)
return key
end

View File

@ -337,6 +337,27 @@ function deep_foreach_any(table, func)
visit(table)
end
-- Recursively counts occurences of values in a table
-- Also counts primitive values like boolean and number
-- Does not count NaN, because that doesn't work as a table index
function count_values(value)
local counts = {}
local function count_values_(value)
-- Ignore NaN
if value ~= value then return end
local count = counts[value]
counts[value] = (count or 0) + 1
if not count and type(value) == "table" then
for k, v in pairs(value) do
count_values_(k)
count_values_(v)
end
end
end
count_values_(value)
return counts
end
function foreach_value(table, func)
for _, v in pairs(table) do
func(v)

View File

@ -166,10 +166,41 @@ for _ = 1, 1000 do
assert(distance == min_distance)
end
local function serializer_test(assert_preserves)
-- TODO nan
for _, constant in pairs{true, false, huge, -huge} do
assert_preserves(constant)
end
-- Strings
for i = 1, 1000 do
assert_preserves(_G.table.concat(table.repetition(_G.string.char(i % 256), i)))
end
-- Numbers
for _ = 1, 1000 do
local int = random(-2^50, 2^50)
assert(int % 1 == 0)
assert_preserves(int)
assert_preserves((random() - 0.5) * 2^random(-20, 20))
end
-- Simple tables
assert_preserves{hello = "world", welt = "hallo"}
assert_preserves{"hello", "hello", "hello"}
local circular = {}
circular[circular] = circular
circular[1] = circular
assert_preserves(circular)
local mixed = {1, 2, 3}
mixed[mixed] = mixed
mixed.vec = {x = 1, y = 2, z = 3}
mixed.vec2 = modlib.table.copy(mixed.vec)
mixed.blah = "blah"
assert_preserves(mixed)
end
-- bluon
do
local bluon = bluon
local function assert_preserves(object)
serializer_test(function(object)
local rope = table.rope{}
local written, read, input
local _, err = pcall(function()
@ -186,25 +217,17 @@ do
written = written and text.hexdump(written),
err = err
})
end
for _, constant in pairs{true, false, huge, -huge} do
assert_preserves(constant)
end
for i = 1, 1000 do
assert_preserves(_G.table.concat(table.repetition(_G.string.char(i % 256), i)))
end
for _ = 1, 1000 do
local int = random(-2^50, 2^50)
assert(int % 1 == 0)
assert_preserves(int)
assert_preserves((random() - 0.5) * 2^random(-20, 20))
end
assert_preserves{hello = "world", welt = "hallo"}
assert_preserves{"hello", "hello", "hello"}
local a = {}
a[a] = a
a[1] = a
assert_preserves(a)
end)
end
-- luon
do
serializer_test(function(object)
local serialized = luon.write_string(object)
assert(table.equals_references(object, luon.read_string(serialized)), serialized)
end)
local nan = luon.read_string(luon.write_string(0/0))
assert(nan ~= nan)
end
if not _G.minetest then return end
@ -230,9 +253,16 @@ local function test_logfile(reference_strings)
logfile.root = {a_longer_string = "test"}
logfile:rewrite()
logfile:set_root({a = 1}, {b = 2, c = 3, d = _G.math.huge, e = -_G.math.huge})
local circular = {}
circular[circular] = circular
logfile:set_root(circular, circular)
logfile:close()
logfile:init()
assert(table.equals(logfile.root, {a_longer_string = "test", [{a = 1}] = {b = 2, c = 3, d = _G.math.huge, e = -_G.math.huge}}))
assert(table.equals_references(logfile.root, {
a_longer_string = "test",
[{a = 1}] = {b = 2, c = 3, d = _G.math.huge, e = -_G.math.huge},
[circular] = circular,
}))
if not reference_strings then
for key in pairs(logfile.references) do
assert(type(key) ~= "string")